py: Add in fence test using swsync
authorLaurent Pinchart <laurent.pinchart@ideasonboard.com>
Fri, 21 Apr 2017 10:46:57 +0000 (13:46 +0300)
committerTomi Valkeinen <tomi.valkeinen@ti.com>
Fri, 21 Apr 2017 10:48:56 +0000 (13:48 +0300)
Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
Signed-off-by: Tomi Valkeinen <tomi.valkeinen@ti.com>
py/tests/sync.py [new file with mode: 0755]

diff --git a/py/tests/sync.py b/py/tests/sync.py
new file mode 100755 (executable)
index 0000000..4616ee8
--- /dev/null
@@ -0,0 +1,234 @@
+#!/usr/bin/python3
+
+import ctypes
+import fcntl
+import os
+import pykms
+import selectors
+import sys
+import time
+
+bar_width = 20
+bar_speed = 8
+
+class Timer(object):
+    timers = []
+
+    def __init__(self, timeout, callback, data):
+        self.timeout = time.clock_gettime(time.CLOCK_MONOTONIC) + timeout
+        self.callback = callback
+        self.data = data
+
+        print("adding timer %f" % self.timeout)
+        self.timers.append(self)
+        self.timers.sort(key=lambda timer: timer.timeout)
+
+    @classmethod
+    def fire(_class):
+        clk = time.clock_gettime(time.CLOCK_MONOTONIC)
+        while len(_class.timers) > 0:
+            timer = _class.timers[0]
+            if timer.timeout > clk:
+                break
+
+            del _class.timers[0]
+            print("fireing timer %f" % timer.timeout)
+            timer.callback(timer.data)
+
+    @classmethod
+    def next_timeout(_class):
+        clk = time.clock_gettime(time.CLOCK_MONOTONIC)
+        if len(_class.timers) == 0 or _class.timers[0].timeout < clk:
+            return None
+
+        return _class.timers[0].timeout - clk
+
+
+class Timeline(object):
+
+    class sw_sync_create_fence_data(ctypes.Structure):
+        _fields_ = [
+            ('value', ctypes.c_uint32),
+            ('name', ctypes.c_char * 32),
+            ('fence', ctypes.c_int32),
+        ]
+
+    SW_SYNC_IOC_CREATE_FENCE = (3 << 30) | (ctypes.sizeof(sw_sync_create_fence_data) << 16) | (ord('W') << 8) | (0 << 0)
+    SW_SYNC_IOC_INC = (1 << 30) | (ctypes.sizeof(ctypes.c_uint32) << 16) | (ord('W') << 8) | (1 << 0)
+
+    class SWSync(object):
+        def __init__(self, fd):
+            self.fd = fd
+        def __del__(self):
+            os.close(self.fd)
+
+    def __init__(self):
+        self.value = 0
+        try:
+            self.fd = os.open('/sys/kernel/debug/sync/sw_sync', 0);
+        except:
+            raise RuntimeError('Failed to open sw_sync file')
+
+    def close(self):
+        os.close(self.fd)
+
+    def create_fence(self, value):
+        data = self.sw_sync_create_fence_data(value = value);
+        print("ioctl number %u" % self.SW_SYNC_IOC_CREATE_FENCE)
+        ret = fcntl.ioctl(self.fd, self.SW_SYNC_IOC_CREATE_FENCE, data);
+        if ret < 0:
+            raise RuntimeError('Failed to create fence')
+
+        return self.SWSync(data.fence)
+
+    def signal(self, value):
+        fcntl.ioctl(self.fd, self.SW_SYNC_IOC_INC, ctypes.c_uint32(value))
+        self.value += value
+
+
+class FlipHandler():
+    def __init__(self, crtc, width, height):
+        super().__init__()
+        self.crtc = crtc
+        self.timeline = Timeline()
+        self.bar_xpos = 0
+        self.front_buf = 0
+        self.fb1 = pykms.DumbFramebuffer(crtc.card, width, height, "XR24");
+        self.fb2 = pykms.DumbFramebuffer(crtc.card, width, height, "XR24");
+        self.flips = 0
+        self.flips_last = 0
+        self.frame_last = 0
+        self.time_last = 0
+
+    def handle_page_flip(self, frame, time):
+        if self.time_last == 0:
+            self.frame_last = frame
+            self.time_last = time
+
+        # Verify that the page flip hasn't completed before the timeline got
+        # signaled.
+        if self.timeline.value < 2 * self.flips - 1:
+            raise RuntimeError('Page flip %u for fence %u complete before timeline (%u)!' %
+                               (self.flips, 2 * self.flips - 1, self.timeline.value))
+
+        self.flips += 1
+
+        # Print statistics every 5 seconds.
+        time_delta = time - self.time_last
+        if time_delta >= 5:
+            frame_delta = frame - self.frame_last
+            flips_delta = self.flips - self.flips_last
+            print("Frame rate: %f (%u/%u frames in %f s)" %
+                  (frame_delta / time_delta, flips_delta, frame_delta, time_delta))
+
+            self.frame_last = frame
+            self.flips_last = self.flips
+            self.time_last = time
+
+        # Draw the color bar on the back buffer.
+        if self.front_buf == 0:
+            fb = self.fb2
+        else:
+            fb = self.fb1
+
+        self.front_buf = self.front_buf ^ 1
+
+        current_xpos = self.bar_xpos;
+        old_xpos = (current_xpos + (fb.width - bar_width - bar_speed)) % (fb.width - bar_width);
+        new_xpos = (current_xpos + bar_speed) % (fb.width - bar_width);
+
+        self.bar_xpos = new_xpos
+
+        pykms.draw_color_bar(fb, old_xpos, new_xpos, bar_width)
+
+        # Flip the buffers with an in fence located in the future. The atomic
+        # commit is asynchronous and returns immediately, but the flip should
+        # not complete before the fence gets signaled.
+        print("flipping with fence @%u, timeline is @%u" % (2 * self.flips - 1, self.timeline.value))
+        fence = self.timeline.create_fence(2 * self.flips - 1)
+        req = pykms.AtomicReq(self.crtc.card)
+        req.add(self.crtc.primary_plane, { 'FB_ID': fb.id, 'IN_FENCE_FD': fence.fd })
+        req.commit(self)
+        del fence
+
+        # Arm a timer to signal the fence in 0.5s.
+        def timeline_signal(timeline):
+            print("signaling timeline @%u" % timeline.value)
+            timeline.signal(2)
+
+        Timer(0.5, timeline_signal, self.timeline)
+
+
+def main(argv):
+    if len(argv) > 1:
+        conn_name = argv[1]
+    else:
+        conn_name = ''
+
+    card = pykms.Card()
+    if not card.has_atomic:
+        raise RuntimeError('This test requires atomic update support')
+
+    res = pykms.ResourceManager(card)
+    conn = res.reserve_connector(conn_name)
+    crtc = res.reserve_crtc(conn)
+    mode = conn.get_default_mode()
+
+    flip_handler = FlipHandler(crtc, mode.hdisplay, mode.vdisplay)
+
+    fb = flip_handler.fb1
+    pykms.draw_color_bar(fb, fb.width - bar_width - bar_speed, bar_speed, bar_width)
+    mode_blob = mode.blob(card)
+
+    req = pykms.AtomicReq(card)
+    req.add(conn, 'CRTC_ID', crtc.id)
+    req.add(crtc, { 'ACTIVE': 1, 'MODE_ID': mode_blob.id })
+    req.add(crtc.primary_plane, {
+                'FB_ID': fb.id,
+                'CRTC_ID': crtc.id,
+                'SRC_X': 0 << 16,
+                'SRC_Y': 0 << 16,
+                'SRC_W': fb.width << 16,
+                'SRC_H': fb.height << 16,
+                'CRTC_X': 0,
+                'CRTC_Y': 0,
+                'CRTC_W': fb.width,
+                'CRTC_H': fb.height,
+    })
+    ret = req.commit(flip_handler, allow_modeset = True)
+    if ret < 0:
+        raise RuntimeError('Atomic mode set failed with %d' % ret)
+
+    def bye():
+        # Signal the timeline to complete all pending page flips
+        flip_handler.timeline.signal(100)
+        exit(0)
+
+    def readdrm(fileobj, mask):
+        for ev in card.read_events():
+            if ev.type == pykms.DrmEventType.FLIP_COMPLETE:
+                ev.data.handle_page_flip(ev.seq, ev.time)
+
+    def readkey(fileobj, mask):
+        sys.stdin.readline()
+        bye()
+
+    sel = selectors.DefaultSelector()
+    sel.register(card.fd, selectors.EVENT_READ, readdrm)
+    sel.register(sys.stdin, selectors.EVENT_READ, readkey)
+
+    while True:
+        timeout = Timer.next_timeout()
+        print("--> timeout %s" % repr(timeout))
+        try:
+            events = sel.select(timeout)
+        except KeyboardInterrupt:
+            bye()
+        for key, mask in events:
+            callback = key.data
+            callback(key.fileobj, mask)
+
+        Timer.fire()
+
+if __name__ == '__main__':
+    main(sys.argv)