summaryrefslogtreecommitdiff
path: root/tests/kms-test-crc.py
blob: b1af65d5256d161f860d3381361389c16d155076 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
#!/usr/bin/python3
# SPDX-License-Identifier: GPL-2.0-or-later
# SPDX-FileCopyrightText: 2017-2019 Renesas Electronics Corporation

import copy
import crcmod
import kmstest
import pykms
import tempfile


class Composer(object):
    # Manage the composition of planes on the screen by computing the source
    # and destination rectangles of each plane.
    #
    # Stack the plane, starting at START_POINT, with a fixed INCREMENT.
    #

    START_POINT = kmstest.Point(0, 0)
    INCREMENT = kmstest.Dist(50, 50)

    # The CRC initial value is XORed with the final XOR value, so we need to
    # pass 0 to get the desired  0xffffffff initial value.
    CRC = crcmod.Crc(0x104c11db7, 0x00000000, True, 0xffffffff)

    def __init__(self, mode, crtc, planes, fb):
        self.__screen_size = kmstest.Size(mode.hdisplay, mode.vdisplay)
        self.__fb_size = kmstest.Size(fb.width, fb.height)
        self.__planes_positions = {}
        self.__crcs = {}

        # Dump the contents of the frame buffer to compute the CRCs.
        file = tempfile.TemporaryFile()
        pykms.dump_framebuffer(fb, file.fileno())
        file.seek(0)
        image = file.read()
        file.close()

        position = copy.copy(Composer.START_POINT)
        for plane in planes:
            self.__planes_positions[plane] = copy.copy(position)
            self.__crcs[plane] = self.__plane_crc(plane, image)
            position.move(Composer.INCREMENT)

        self.__crcs[crtc] = self.__crtc_crc(planes, image)

    def source(self, plane):
        pos = self.__planes_positions[plane]
        return kmstest.Rect(0, 0,
                            max(0, self.__fb_size.width - pos.x),
                            max(0, self.__fb_size.height - pos.y))

    def destination(self, plane):
        pos = self.__planes_positions[plane]
        return kmstest.Rect(pos.x, pos.y,
                            max(0, self.__fb_size.width - pos.x),
                            max(0, self.__fb_size.height - pos.y))


    def crc(self, obj):
        return self.__crcs[obj]

    def __plane_crc(self, plane, image):
        # Compute the reference CRC for a plane. Only the visible part of the
        # plane is used by the hardware.

        crcg = Composer.CRC.new()

        src = self.source(plane)
        if src.isEmpty():
            return 0

        for y in range(src.height):
            src_offset = ((src.top + y) * self.__fb_size.width + src.left) * 4
            crcg.update(image[src_offset:src_offset+src.width*4])

        return crcg.crcValue

    def __crtc_crc(self, planes, image):
        # Compute the reference CRC for the composed display output. Start with
        # a black background and compose the planes.

        crcg = Composer.CRC.new()

        # The background is set to black, with and alpha value of 255 (opaque)
        # to match the BRx configuration.
        output = [0, 0, 0, 255] * (self.__screen_size.width * self.__screen_size.height)

        # Compose planes on top.
        for plane in planes:
            src = self.source(plane)
            dst = self.destination(plane)

            for y in range(src.height):
                src_offset = ((src.top + y) * self.__fb_size.width + src.left) * 4
                dst_offset = ((dst.top + y) * self.__screen_size.width + dst.left) * 4
                output[dst_offset:dst_offset+src.width*4] = image[src_offset:src_offset+src.width*4]

        crcg.update(bytes(output))

        return crcg.crcValue


class CRCTest(kmstest.KMSTest):
    """Test CRC calculation on pipeline output."""

    def handle_page_flip(self, frame, time):
        self.logger.log('Page flip complete')

    def main(self):
        # Create the connectors to CRTCs map
        connectors = {}
        for connector in self.output_connectors():
            # Skip disconnected connectors
            if not connector.connected():
                continue

            # Add the connector to the map
            for crtc in connector.get_possible_crtcs():
                if crtc not in connectors:
                    connectors[crtc] = connector

        for crtc in self.card.crtcs:
            self.start(f'CRC calculation on CRTC {crtc.id}')

            try:
                crc_reader = kmstest.CRCReader(crtc)
            except:
                self.logger.log('Failed to create CRC reader, check that debugfs is mounted')
                self.skip(f'CRC support not available for CRTC {crtc.id}')
                continue

            # Get the connector and default mode
            try:
                connector = connectors[crtc];
                mode = connector.get_default_mode()
            except KeyError:
                self.skip('no connector or mode available')
                continue

            # List planes available for the CRTC
            planes = []
            for plane in self.card.planes:
                if plane.supports_crtc(crtc):
                    planes.append(plane)

            if len(planes) == 0:
                self.skip('no plane available for CRTC')
                continue

            self.logger.log(f'Testing connector {connector.fullname}, CRTC {crtc.id}, '
                            f'mode {mode.name} with {len(planes)} planes')

            # Create a frame buffer and draw a test pattern.
            fb = pykms.DumbFramebuffer(self.card, mode.hdisplay, mode.vdisplay, 'XR24')
            pykms.draw_test_pattern(fb)

            # Create a composer. This will compute the reference CRCs.
            composer = Composer(mode, crtc, planes, fb)

            # Set the mode and add all planes
            ret = self.atomic_crtc_mode_set(crtc, connector, mode, sync=True)
            if ret < 0:
                self.fail(f'atomic mode set failed with {ret}')
                continue

            req = kmstest.AtomicRequest(self)

            for plane in planes:
                source = composer.source(plane)
                destination = composer.destination(plane)
                req.add(plane, {
                        'FB_ID': fb.id,
                        'CRTC_ID': crtc.id,
                        'SRC_X': int(source.left * 65536),
                        'SRC_Y': int(source.top * 65536),
                        'SRC_W': int(source.width * 65536),
                        'SRC_H': int(source.height * 65536),
                        'CRTC_X': destination.left,
                        'CRTC_Y': destination.top,
                        'CRTC_W': destination.width,
                        'CRTC_H': destination.height,
                })

            ret = req.commit(0)
            if ret < 0:
                self.fail(f'atomic plane set failed with {ret}')
                continue

            # Wait for one second and make sure the page flip has completed.
            self.run(1)
            if self.flips == 0:
                self.fail('No page flip registered')
                continue

            sources = [crtc] + planes
            for source in sources:
                if source == crtc:
                    crc_source = 'auto'
                else:
                    crc_source = f'plane{source.id}'

                self.logger.log(f'Computing CRC from source {crc_source}')

                # Set the CRC source and acquire 10 CRC values. Discard the
                # first value, as the device is running and the new source
                # needs one frame to take effect.
                crc_reader.start(crc_source)
                crcs = crc_reader.read(10)
                crc_reader.stop()

                crcs = [c.crcs[0] for c in crcs[1:]]
                self.logger.log(f'CRC value[0] 0x{crcs[0]:08x}')

                failures = 0
                ref_crc = composer.crc(source)

                for i in range(len(crcs)):
                    crc = crcs[i]
                    if crc != ref_crc:
                        self.logger.log(f'CRC value[{i}] 0x{crc:08x} does not match reference 0x{ref_crc:08x}')
                        failures += 1

                if failures:
                    self.fail(f'Incorrect CRC values on source {crc_source}')
                    break

            else:
                self.success()

            self.atomic_crtc_disable(crtc)


if __name__ == '__main__':
    CRCTest().execute()