xref: /aosp_15_r20/external/libkmsxx/py/tests/sync.py (revision f0687c8a10b3e371dbe09214db6664e37c283cca)
1*f0687c8aSRaman Tenneti#!/usr/bin/python3
2*f0687c8aSRaman Tenneti
3*f0687c8aSRaman Tennetiimport ctypes
4*f0687c8aSRaman Tennetiimport fcntl
5*f0687c8aSRaman Tennetiimport os
6*f0687c8aSRaman Tennetiimport pykms
7*f0687c8aSRaman Tennetiimport selectors
8*f0687c8aSRaman Tennetiimport sys
9*f0687c8aSRaman Tennetiimport time
10*f0687c8aSRaman Tenneti
11*f0687c8aSRaman Tennetibar_width = 20
12*f0687c8aSRaman Tennetibar_speed = 8
13*f0687c8aSRaman Tenneti
14*f0687c8aSRaman Tenneticlass Timer(object):
15*f0687c8aSRaman Tenneti    timers = []
16*f0687c8aSRaman Tenneti
17*f0687c8aSRaman Tenneti    def __init__(self, timeout, callback, data):
18*f0687c8aSRaman Tenneti        self.timeout = time.clock_gettime(time.CLOCK_MONOTONIC) + timeout
19*f0687c8aSRaman Tenneti        self.callback = callback
20*f0687c8aSRaman Tenneti        self.data = data
21*f0687c8aSRaman Tenneti
22*f0687c8aSRaman Tenneti        print("adding timer %f" % self.timeout)
23*f0687c8aSRaman Tenneti        self.timers.append(self)
24*f0687c8aSRaman Tenneti        self.timers.sort(key=lambda timer: timer.timeout)
25*f0687c8aSRaman Tenneti
26*f0687c8aSRaman Tenneti    @classmethod
27*f0687c8aSRaman Tenneti    def fire(_class):
28*f0687c8aSRaman Tenneti        clk = time.clock_gettime(time.CLOCK_MONOTONIC)
29*f0687c8aSRaman Tenneti        while len(_class.timers) > 0:
30*f0687c8aSRaman Tenneti            timer = _class.timers[0]
31*f0687c8aSRaman Tenneti            if timer.timeout > clk:
32*f0687c8aSRaman Tenneti                break
33*f0687c8aSRaman Tenneti
34*f0687c8aSRaman Tenneti            del _class.timers[0]
35*f0687c8aSRaman Tenneti            print("fireing timer %f" % timer.timeout)
36*f0687c8aSRaman Tenneti            timer.callback(timer.data)
37*f0687c8aSRaman Tenneti
38*f0687c8aSRaman Tenneti    @classmethod
39*f0687c8aSRaman Tenneti    def next_timeout(_class):
40*f0687c8aSRaman Tenneti        clk = time.clock_gettime(time.CLOCK_MONOTONIC)
41*f0687c8aSRaman Tenneti        if len(_class.timers) == 0 or _class.timers[0].timeout < clk:
42*f0687c8aSRaman Tenneti            return None
43*f0687c8aSRaman Tenneti
44*f0687c8aSRaman Tenneti        return _class.timers[0].timeout - clk
45*f0687c8aSRaman Tenneti
46*f0687c8aSRaman Tenneti
47*f0687c8aSRaman Tenneticlass Timeline(object):
48*f0687c8aSRaman Tenneti
49*f0687c8aSRaman Tenneti    class sw_sync_create_fence_data(ctypes.Structure):
50*f0687c8aSRaman Tenneti        _fields_ = [
51*f0687c8aSRaman Tenneti            ('value', ctypes.c_uint32),
52*f0687c8aSRaman Tenneti            ('name', ctypes.c_char * 32),
53*f0687c8aSRaman Tenneti            ('fence', ctypes.c_int32),
54*f0687c8aSRaman Tenneti        ]
55*f0687c8aSRaman Tenneti
56*f0687c8aSRaman Tenneti    SW_SYNC_IOC_CREATE_FENCE = (3 << 30) | (ctypes.sizeof(sw_sync_create_fence_data) << 16) | (ord('W') << 8) | (0 << 0)
57*f0687c8aSRaman Tenneti    SW_SYNC_IOC_INC = (1 << 30) | (ctypes.sizeof(ctypes.c_uint32) << 16) | (ord('W') << 8) | (1 << 0)
58*f0687c8aSRaman Tenneti
59*f0687c8aSRaman Tenneti    class SWSync(object):
60*f0687c8aSRaman Tenneti        def __init__(self, fd):
61*f0687c8aSRaman Tenneti            self.fd = fd
62*f0687c8aSRaman Tenneti        def __del__(self):
63*f0687c8aSRaman Tenneti            os.close(self.fd)
64*f0687c8aSRaman Tenneti
65*f0687c8aSRaman Tenneti    def __init__(self):
66*f0687c8aSRaman Tenneti        self.value = 0
67*f0687c8aSRaman Tenneti        try:
68*f0687c8aSRaman Tenneti            self.fd = os.open('/sys/kernel/debug/sync/sw_sync', 0);
69*f0687c8aSRaman Tenneti        except:
70*f0687c8aSRaman Tenneti            raise RuntimeError('Failed to open sw_sync file')
71*f0687c8aSRaman Tenneti
72*f0687c8aSRaman Tenneti    def close(self):
73*f0687c8aSRaman Tenneti        os.close(self.fd)
74*f0687c8aSRaman Tenneti
75*f0687c8aSRaman Tenneti    def create_fence(self, value):
76*f0687c8aSRaman Tenneti        data = self.sw_sync_create_fence_data(value = value);
77*f0687c8aSRaman Tenneti        print("ioctl number %u" % self.SW_SYNC_IOC_CREATE_FENCE)
78*f0687c8aSRaman Tenneti        ret = fcntl.ioctl(self.fd, self.SW_SYNC_IOC_CREATE_FENCE, data);
79*f0687c8aSRaman Tenneti        if ret < 0:
80*f0687c8aSRaman Tenneti            raise RuntimeError('Failed to create fence')
81*f0687c8aSRaman Tenneti
82*f0687c8aSRaman Tenneti        return self.SWSync(data.fence)
83*f0687c8aSRaman Tenneti
84*f0687c8aSRaman Tenneti    def signal(self, value):
85*f0687c8aSRaman Tenneti        fcntl.ioctl(self.fd, self.SW_SYNC_IOC_INC, ctypes.c_uint32(value))
86*f0687c8aSRaman Tenneti        self.value += value
87*f0687c8aSRaman Tenneti
88*f0687c8aSRaman Tenneti
89*f0687c8aSRaman Tenneticlass FlipHandler():
90*f0687c8aSRaman Tenneti    def __init__(self, crtc, width, height):
91*f0687c8aSRaman Tenneti        super().__init__()
92*f0687c8aSRaman Tenneti        self.crtc = crtc
93*f0687c8aSRaman Tenneti        self.timeline = Timeline()
94*f0687c8aSRaman Tenneti        self.bar_xpos = 0
95*f0687c8aSRaman Tenneti        self.front_buf = 0
96*f0687c8aSRaman Tenneti        self.fb1 = pykms.DumbFramebuffer(crtc.card, width, height, "XR24");
97*f0687c8aSRaman Tenneti        self.fb2 = pykms.DumbFramebuffer(crtc.card, width, height, "XR24");
98*f0687c8aSRaman Tenneti        self.flips = 0
99*f0687c8aSRaman Tenneti        self.flips_last = 0
100*f0687c8aSRaman Tenneti        self.frame_last = 0
101*f0687c8aSRaman Tenneti        self.time_last = 0
102*f0687c8aSRaman Tenneti
103*f0687c8aSRaman Tenneti    def handle_page_flip(self, frame, time):
104*f0687c8aSRaman Tenneti        if self.time_last == 0:
105*f0687c8aSRaman Tenneti            self.frame_last = frame
106*f0687c8aSRaman Tenneti            self.time_last = time
107*f0687c8aSRaman Tenneti
108*f0687c8aSRaman Tenneti        # Verify that the page flip hasn't completed before the timeline got
109*f0687c8aSRaman Tenneti        # signaled.
110*f0687c8aSRaman Tenneti        if self.timeline.value < 2 * self.flips - 1:
111*f0687c8aSRaman Tenneti            raise RuntimeError('Page flip %u for fence %u complete before timeline (%u)!' %
112*f0687c8aSRaman Tenneti                               (self.flips, 2 * self.flips - 1, self.timeline.value))
113*f0687c8aSRaman Tenneti
114*f0687c8aSRaman Tenneti        self.flips += 1
115*f0687c8aSRaman Tenneti
116*f0687c8aSRaman Tenneti        # Print statistics every 5 seconds.
117*f0687c8aSRaman Tenneti        time_delta = time - self.time_last
118*f0687c8aSRaman Tenneti        if time_delta >= 5:
119*f0687c8aSRaman Tenneti            frame_delta = frame - self.frame_last
120*f0687c8aSRaman Tenneti            flips_delta = self.flips - self.flips_last
121*f0687c8aSRaman Tenneti            print("Frame rate: %f (%u/%u frames in %f s)" %
122*f0687c8aSRaman Tenneti                  (frame_delta / time_delta, flips_delta, frame_delta, time_delta))
123*f0687c8aSRaman Tenneti
124*f0687c8aSRaman Tenneti            self.frame_last = frame
125*f0687c8aSRaman Tenneti            self.flips_last = self.flips
126*f0687c8aSRaman Tenneti            self.time_last = time
127*f0687c8aSRaman Tenneti
128*f0687c8aSRaman Tenneti        # Draw the color bar on the back buffer.
129*f0687c8aSRaman Tenneti        if self.front_buf == 0:
130*f0687c8aSRaman Tenneti            fb = self.fb2
131*f0687c8aSRaman Tenneti        else:
132*f0687c8aSRaman Tenneti            fb = self.fb1
133*f0687c8aSRaman Tenneti
134*f0687c8aSRaman Tenneti        self.front_buf = self.front_buf ^ 1
135*f0687c8aSRaman Tenneti
136*f0687c8aSRaman Tenneti        current_xpos = self.bar_xpos;
137*f0687c8aSRaman Tenneti        old_xpos = (current_xpos + (fb.width - bar_width - bar_speed)) % (fb.width - bar_width);
138*f0687c8aSRaman Tenneti        new_xpos = (current_xpos + bar_speed) % (fb.width - bar_width);
139*f0687c8aSRaman Tenneti
140*f0687c8aSRaman Tenneti        self.bar_xpos = new_xpos
141*f0687c8aSRaman Tenneti
142*f0687c8aSRaman Tenneti        pykms.draw_color_bar(fb, old_xpos, new_xpos, bar_width)
143*f0687c8aSRaman Tenneti
144*f0687c8aSRaman Tenneti        # Flip the buffers with an in fence located in the future. The atomic
145*f0687c8aSRaman Tenneti        # commit is asynchronous and returns immediately, but the flip should
146*f0687c8aSRaman Tenneti        # not complete before the fence gets signaled.
147*f0687c8aSRaman Tenneti        print("flipping with fence @%u, timeline is @%u" % (2 * self.flips - 1, self.timeline.value))
148*f0687c8aSRaman Tenneti        fence = self.timeline.create_fence(2 * self.flips - 1)
149*f0687c8aSRaman Tenneti        req = pykms.AtomicReq(self.crtc.card)
150*f0687c8aSRaman Tenneti        req.add(self.crtc.primary_plane, { 'FB_ID': fb.id, 'IN_FENCE_FD': fence.fd })
151*f0687c8aSRaman Tenneti        req.commit()
152*f0687c8aSRaman Tenneti        del fence
153*f0687c8aSRaman Tenneti
154*f0687c8aSRaman Tenneti        # Arm a timer to signal the fence in 0.5s.
155*f0687c8aSRaman Tenneti        def timeline_signal(timeline):
156*f0687c8aSRaman Tenneti            print("signaling timeline @%u" % timeline.value)
157*f0687c8aSRaman Tenneti            timeline.signal(2)
158*f0687c8aSRaman Tenneti
159*f0687c8aSRaman Tenneti        Timer(0.5, timeline_signal, self.timeline)
160*f0687c8aSRaman Tenneti
161*f0687c8aSRaman Tenneti
162*f0687c8aSRaman Tennetidef main(argv):
163*f0687c8aSRaman Tenneti    if len(argv) > 1:
164*f0687c8aSRaman Tenneti        conn_name = argv[1]
165*f0687c8aSRaman Tenneti    else:
166*f0687c8aSRaman Tenneti        conn_name = ''
167*f0687c8aSRaman Tenneti
168*f0687c8aSRaman Tenneti    card = pykms.Card()
169*f0687c8aSRaman Tenneti    if not card.has_atomic:
170*f0687c8aSRaman Tenneti        raise RuntimeError('This test requires atomic update support')
171*f0687c8aSRaman Tenneti
172*f0687c8aSRaman Tenneti    res = pykms.ResourceManager(card)
173*f0687c8aSRaman Tenneti    conn = res.reserve_connector(conn_name)
174*f0687c8aSRaman Tenneti    crtc = res.reserve_crtc(conn)
175*f0687c8aSRaman Tenneti    mode = conn.get_default_mode()
176*f0687c8aSRaman Tenneti
177*f0687c8aSRaman Tenneti    flip_handler = FlipHandler(crtc, mode.hdisplay, mode.vdisplay)
178*f0687c8aSRaman Tenneti
179*f0687c8aSRaman Tenneti    fb = flip_handler.fb1
180*f0687c8aSRaman Tenneti    pykms.draw_color_bar(fb, fb.width - bar_width - bar_speed, bar_speed, bar_width)
181*f0687c8aSRaman Tenneti    mode_blob = mode.to_blob(card)
182*f0687c8aSRaman Tenneti
183*f0687c8aSRaman Tenneti    req = pykms.AtomicReq(card)
184*f0687c8aSRaman Tenneti    req.add(conn, 'CRTC_ID', crtc.id)
185*f0687c8aSRaman Tenneti    req.add(crtc, { 'ACTIVE': 1, 'MODE_ID': mode_blob.id })
186*f0687c8aSRaman Tenneti    req.add(crtc.primary_plane, {
187*f0687c8aSRaman Tenneti                'FB_ID': fb.id,
188*f0687c8aSRaman Tenneti                'CRTC_ID': crtc.id,
189*f0687c8aSRaman Tenneti                'SRC_X': 0 << 16,
190*f0687c8aSRaman Tenneti                'SRC_Y': 0 << 16,
191*f0687c8aSRaman Tenneti                'SRC_W': fb.width << 16,
192*f0687c8aSRaman Tenneti                'SRC_H': fb.height << 16,
193*f0687c8aSRaman Tenneti                'CRTC_X': 0,
194*f0687c8aSRaman Tenneti                'CRTC_Y': 0,
195*f0687c8aSRaman Tenneti                'CRTC_W': fb.width,
196*f0687c8aSRaman Tenneti                'CRTC_H': fb.height,
197*f0687c8aSRaman Tenneti    })
198*f0687c8aSRaman Tenneti    ret = req.commit(allow_modeset = True)
199*f0687c8aSRaman Tenneti    if ret < 0:
200*f0687c8aSRaman Tenneti        raise RuntimeError('Atomic mode set failed with %d' % ret)
201*f0687c8aSRaman Tenneti
202*f0687c8aSRaman Tenneti    def bye():
203*f0687c8aSRaman Tenneti        # Signal the timeline to complete all pending page flips
204*f0687c8aSRaman Tenneti        flip_handler.timeline.signal(100)
205*f0687c8aSRaman Tenneti        exit(0)
206*f0687c8aSRaman Tenneti
207*f0687c8aSRaman Tenneti    def readdrm(fileobj, mask):
208*f0687c8aSRaman Tenneti        for ev in card.read_events():
209*f0687c8aSRaman Tenneti            if ev.type == pykms.DrmEventType.FLIP_COMPLETE:
210*f0687c8aSRaman Tenneti                flip_handler.handle_page_flip(ev.seq, ev.time)
211*f0687c8aSRaman Tenneti
212*f0687c8aSRaman Tenneti    def readkey(fileobj, mask):
213*f0687c8aSRaman Tenneti        sys.stdin.readline()
214*f0687c8aSRaman Tenneti        bye()
215*f0687c8aSRaman Tenneti
216*f0687c8aSRaman Tenneti    sel = selectors.DefaultSelector()
217*f0687c8aSRaman Tenneti    sel.register(card.fd, selectors.EVENT_READ, readdrm)
218*f0687c8aSRaman Tenneti    sel.register(sys.stdin, selectors.EVENT_READ, readkey)
219*f0687c8aSRaman Tenneti
220*f0687c8aSRaman Tenneti    while True:
221*f0687c8aSRaman Tenneti        timeout = Timer.next_timeout()
222*f0687c8aSRaman Tenneti        print("--> timeout %s" % repr(timeout))
223*f0687c8aSRaman Tenneti        try:
224*f0687c8aSRaman Tenneti            events = sel.select(timeout)
225*f0687c8aSRaman Tenneti        except KeyboardInterrupt:
226*f0687c8aSRaman Tenneti            bye()
227*f0687c8aSRaman Tenneti        for key, mask in events:
228*f0687c8aSRaman Tenneti            callback = key.data
229*f0687c8aSRaman Tenneti            callback(key.fileobj, mask)
230*f0687c8aSRaman Tenneti
231*f0687c8aSRaman Tenneti        Timer.fire()
232*f0687c8aSRaman Tenneti
233*f0687c8aSRaman Tennetiif __name__ == '__main__':
234*f0687c8aSRaman Tenneti    main(sys.argv)
235