#!/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()