1# Copyright (c) 2013 The Chromium OS Authors. All rights reserved. 2# Use of this source code is governed by a BSD-style license that can be 3# found in the LICENSE file. 4 5""" 6Provides graphics related utils, like capturing screenshots or checking on 7the state of the graphics driver. 8""" 9 10import collections 11import contextlib 12import fcntl 13import glob 14import logging 15import os 16import re 17import struct 18import sys 19import time 20 21from autotest_lib.client.bin import test 22from autotest_lib.client.bin import utils 23from autotest_lib.client.common_lib import error 24from autotest_lib.client.cros.input_playback import input_playback 25from autotest_lib.client.cros.power import power_utils 26from functools import wraps 27 28# The uinput module might not be available at SDK test time. 29try: 30 from autotest_lib.client.cros.graphics import graphics_uinput 31except ImportError: 32 graphics_uinput = None 33 34 35class GraphicsTest(test.test): 36 """Base class for graphics test. 37 38 GraphicsTest is the base class for graphics tests. 39 Every subclass of GraphicsTest should call GraphicsTests initialize/cleanup 40 method as they will do GraphicsStateChecker as well as report states to 41 Chrome Perf dashboard. 42 43 Attributes: 44 _test_failure_description(str): Failure name reported to chrome perf 45 dashboard. (Default: Failures) 46 _test_failure_report_enable(bool): Enable/Disable reporting 47 failures to chrome perf dashboard 48 automatically. (Default: True) 49 _test_failure_report_subtest(bool): Enable/Disable reporting 50 subtests failure to chrome perf 51 dashboard automatically. 52 (Default: False) 53 """ 54 version = 1 55 _GSC = None 56 57 _test_failure_description = "Failures" 58 _test_failure_report_enable = True 59 _test_failure_report_subtest = False 60 61 def __init__(self, *args, **kwargs): 62 """Initialize flag setting.""" 63 super(GraphicsTest, self).__init__(*args, **kwargs) 64 self._failures_by_description = {} 65 self._player = None 66 67 def initialize(self, raise_error_on_hang=False, *args, **kwargs): 68 """Initial state checker and report initial value to perf dashboard.""" 69 self._GSC = GraphicsStateChecker( 70 raise_error_on_hang=raise_error_on_hang, 71 run_on_sw_rasterizer=utils.is_virtual_machine()) 72 73 self.output_perf_value( 74 description='Timeout_Reboot', 75 value=1, 76 units='count', 77 higher_is_better=False, 78 replace_existing_values=True 79 ) 80 81 if hasattr(super(GraphicsTest, self), "initialize"): 82 utils.cherry_pick_call(super(GraphicsTest, self).initialize, 83 *args, **kwargs) 84 85 def input_check(self): 86 """Check if it exists and initialize input player.""" 87 if self._player is None: 88 self._player = input_playback.InputPlayback() 89 self._player.emulate(input_type='keyboard') 90 self._player.find_connected_inputs() 91 92 def cleanup(self, *args, **kwargs): 93 """Finalize state checker and report values to perf dashboard.""" 94 if self._GSC: 95 self._GSC.finalize() 96 97 self._output_perf() 98 if self._player: 99 self._player.close() 100 101 if hasattr(super(GraphicsTest, self), "cleanup"): 102 utils.cherry_pick_call(super(GraphicsTest, self).cleanup, 103 *args, **kwargs) 104 105 @contextlib.contextmanager 106 def failure_report(self, name, subtest=None): 107 """Record the failure of an operation to self._failures_by_description. 108 109 Records if the operation taken inside executed normally or not. 110 If the operation taken inside raise unexpected failure, failure named 111 |name|, will be added to the self._failures_by_description dictionary 112 and reported to the chrome perf dashboard in the cleanup stage. 113 114 Usage: 115 # Record failure of doSomething 116 with failure_report('doSomething'): 117 doSomething() 118 """ 119 # Assume failed at the beginning 120 self.add_failures(name, subtest=subtest) 121 try: 122 yield {} 123 self.remove_failures(name, subtest=subtest) 124 except (error.TestWarn, error.TestNAError) as e: 125 self.remove_failures(name, subtest=subtest) 126 raise e 127 128 @classmethod 129 def failure_report_decorator(cls, name, subtest=None): 130 """Record the failure if the function failed to finish. 131 This method should only decorate to functions of GraphicsTest. 132 In addition, functions with this decorator should be called with no 133 unnamed arguments. 134 Usage: 135 @GraphicsTest.test_run_decorator('graphics_test') 136 def Foo(self, bar='test'): 137 return doStuff() 138 139 is equivalent to 140 141 def Foo(self, bar): 142 with failure_reporter('graphics_test'): 143 return doStuff() 144 145 # Incorrect usage. 146 @GraphicsTest.test_run_decorator('graphics_test') 147 def Foo(self, bar='test'): 148 pass 149 self.Foo('test_name', bar='test_name') # call Foo with named args 150 151 # Incorrect usage. 152 @GraphicsTest.test_run_decorator('graphics_test') 153 def Foo(self, bar='test'): 154 pass 155 self.Foo('test_name') # call Foo with unnamed args 156 """ 157 def decorator(fn): 158 @wraps(fn) 159 def wrapper(*args, **kwargs): 160 if len(args) > 1: 161 raise error.TestError('Unnamed arguments is not accepted. ' 162 'Please apply this decorator to ' 163 'function without unnamed args.') 164 # A member function of GraphicsTest is decorated. The first 165 # argument is the instance itself. 166 instance = args[0] 167 with instance.failure_report(name, subtest): 168 # Cherry pick the arguments for the wrapped function. 169 d_args, d_kwargs = utils.cherry_pick_args(fn, args, kwargs) 170 return fn(instance, *d_args, **d_kwargs) 171 return wrapper 172 return decorator 173 174 def add_failures(self, name, subtest=None): 175 """ 176 Add a record to failures list which will report back to chrome perf 177 dashboard at cleanup stage. 178 Args: 179 name: failure name. 180 subtest: subtest which will appears in cros-perf. If None is 181 specified, use name instead. 182 """ 183 target = self._get_failure(name, subtest=subtest) 184 if target: 185 target['names'].append(name) 186 else: 187 target = { 188 'description': self._get_failure_description(name, subtest), 189 'unit': 'count', 190 'higher_is_better': False, 191 'graph': self._get_failure_graph_name(), 192 'names': [name], 193 } 194 self._failures_by_description[target['description']] = target 195 return target 196 197 def remove_failures(self, name, subtest=None): 198 """ 199 Remove a record from failures list which will report back to chrome perf 200 dashboard at cleanup stage. 201 Args: 202 name: failure name. 203 subtest: subtest which will appears in cros-perf. If None is 204 specified, use name instead. 205 """ 206 target = self._get_failure(name, subtest=subtest) 207 if name in target['names']: 208 target['names'].remove(name) 209 210 211 def _output_perf(self): 212 """Report recorded failures back to chrome perf.""" 213 self.output_perf_value( 214 description='Timeout_Reboot', 215 value=0, 216 units='count', 217 higher_is_better=False, 218 replace_existing_values=True 219 ) 220 221 if not self._test_failure_report_enable: 222 return 223 224 total_failures = 0 225 # Report subtests failures 226 for failure in self._failures_by_description.values(): 227 if len(failure['names']) > 0: 228 logging.debug('GraphicsTest failure: %s' % failure['names']) 229 total_failures += len(failure['names']) 230 231 if not self._test_failure_report_subtest: 232 continue 233 234 self.output_perf_value( 235 description=failure['description'], 236 value=len(failure['names']), 237 units=failure['unit'], 238 higher_is_better=failure['higher_is_better'], 239 graph=failure['graph'] 240 ) 241 242 # Report the count of all failures 243 self.output_perf_value( 244 description=self._get_failure_graph_name(), 245 value=total_failures, 246 units='count', 247 higher_is_better=False, 248 ) 249 250 def _get_failure_graph_name(self): 251 return self._test_failure_description 252 253 def _get_failure_description(self, name, subtest): 254 return subtest or name 255 256 def _get_failure(self, name, subtest): 257 """Get specific failures.""" 258 description = self._get_failure_description(name, subtest=subtest) 259 return self._failures_by_description.get(description, None) 260 261 def get_failures(self): 262 """ 263 Get currently recorded failures list. 264 """ 265 return [name for failure in self._failures_by_description.values() 266 for name in failure['names']] 267 268 def open_vt1(self): 269 """Switch to VT1 with keyboard.""" 270 self.input_check() 271 self._player.blocking_playback_of_default_file( 272 input_type='keyboard', filename='keyboard_ctrl+alt+f1') 273 time.sleep(5) 274 275 def open_vt2(self): 276 """Switch to VT2 with keyboard.""" 277 self.input_check() 278 self._player.blocking_playback_of_default_file( 279 input_type='keyboard', filename='keyboard_ctrl+alt+f2') 280 time.sleep(5) 281 282 def wake_screen_with_keyboard(self): 283 """Use the vt1 keyboard shortcut to bring the devices screen back on. 284 285 This is useful if you want to take screenshots of the UI. If you try 286 to take them while the screen is off, it will fail. 287 """ 288 self.open_vt1() 289 290 291def screen_disable_blanking(): 292 """ Called from power_Backlight to disable screen blanking. """ 293 # We don't have to worry about unexpected screensavers or DPMS here. 294 return 295 296 297def screen_disable_energy_saving(): 298 """ Called from power_Consumption to immediately disable energy saving. """ 299 # All we need to do here is enable displays via Chrome. 300 power_utils.set_display_power(power_utils.DISPLAY_POWER_ALL_ON) 301 return 302 303 304def screen_toggle_fullscreen(): 305 """Toggles fullscreen mode.""" 306 press_keys(['KEY_F11']) 307 308 309def screen_toggle_mirrored(): 310 """Toggles the mirrored screen.""" 311 press_keys(['KEY_LEFTCTRL', 'KEY_F4']) 312 313 314def hide_cursor(): 315 """Hides mouse cursor.""" 316 # Send a keystroke to hide the cursor. 317 press_keys(['KEY_UP']) 318 319 320def hide_typing_cursor(): 321 """Hides typing cursor.""" 322 # Press the tab key to move outside the typing bar. 323 press_keys(['KEY_TAB']) 324 325 326def screen_wakeup(): 327 """Wake up the screen if it is dark.""" 328 # Move the mouse a little bit to wake up the screen. 329 device = graphics_uinput.get_device_mouse_rel() 330 graphics_uinput.emit(device, 'REL_X', 1) 331 graphics_uinput.emit(device, 'REL_X', -1) 332 333 334def switch_screen_on(on): 335 """ 336 Turn the touch screen on/off. 337 338 @param on: On or off. 339 """ 340 raise error.TestFail('switch_screen_on is not implemented.') 341 342 343def press_keys(key_list): 344 """Presses the given keys as one combination. 345 346 Please do not leak uinput dependencies outside of the file. 347 348 @param key: A list of key strings, e.g. ['LEFTCTRL', 'F4'] 349 """ 350 graphics_uinput.emit_combo(graphics_uinput.get_device_keyboard(), key_list) 351 352 353def click_mouse(): 354 """Just click the mouse. 355 Presumably only hacky tests use this function. 356 """ 357 logging.info('click_mouse()') 358 # Move a little to make the cursor appear. 359 device = graphics_uinput.get_device_mouse_rel() 360 graphics_uinput.emit(device, 'REL_X', 1) 361 # Some sleeping is needed otherwise events disappear. 362 time.sleep(0.1) 363 # Move cursor back to not drift. 364 graphics_uinput.emit(device, 'REL_X', -1) 365 time.sleep(0.1) 366 # Click down. 367 graphics_uinput.emit(device, 'BTN_LEFT', 1) 368 time.sleep(0.2) 369 # Release click. 370 graphics_uinput.emit(device, 'BTN_LEFT', 0) 371 372 373# TODO(ihf): this function is broken. Make it work. 374def activate_focus_at(rel_x, rel_y): 375 """Clicks with the mouse at screen position (x, y). 376 377 This is a pretty hacky method. Using this will probably lead to 378 flaky tests as page layout changes over time. 379 @param rel_x: relative horizontal position between 0 and 1. 380 @param rel_y: relattive vertical position between 0 and 1. 381 """ 382 width, height = get_internal_resolution() 383 device = graphics_uinput.get_device_touch() 384 graphics_uinput.emit(device, 'ABS_MT_SLOT', 0, syn=False) 385 graphics_uinput.emit(device, 'ABS_MT_TRACKING_ID', 1, syn=False) 386 graphics_uinput.emit(device, 'ABS_MT_POSITION_X', int(rel_x * width), 387 syn=False) 388 graphics_uinput.emit(device, 'ABS_MT_POSITION_Y', int(rel_y * height), 389 syn=False) 390 graphics_uinput.emit(device, 'BTN_TOUCH', 1, syn=True) 391 time.sleep(0.2) 392 graphics_uinput.emit(device, 'BTN_TOUCH', 0, syn=True) 393 394 395def take_screenshot(resultsdir, fname_prefix): 396 """Take screenshot and save to a new file in the results dir. 397 Args: 398 @param resultsdir: Directory to store the output in. 399 @param fname_prefix: Prefix for the output fname. 400 Returns: 401 the path of the saved screenshot file 402 """ 403 404 old_exc_type = sys.exc_info()[0] 405 406 next_index = len(glob.glob( 407 os.path.join(resultsdir, '%s-*.png' % fname_prefix))) 408 screenshot_file = os.path.join( 409 resultsdir, '%s-%d.png' % (fname_prefix, next_index)) 410 logging.info('Saving screenshot to %s.', screenshot_file) 411 412 try: 413 utils.run('screenshot "%s"' % screenshot_file) 414 except Exception as err: 415 # Do not raise an exception if the screenshot fails while processing 416 # another exception. 417 if old_exc_type is None: 418 raise 419 logging.error(err) 420 421 return screenshot_file 422 423 424def take_screenshot_crop(fullpath, box=None, crtc_id=None): 425 """ 426 Take a screenshot using import tool, crop according to dim given by the box. 427 @param fullpath: path, full path to save the image to. 428 @param box: 4-tuple giving the upper left and lower right pixel coordinates. 429 @param crtc_id: if set, take a screen shot of the specified CRTC. 430 """ 431 cmd = 'screenshot' 432 if crtc_id is not None: 433 cmd += ' --crtc-id=%d' % crtc_id 434 else: 435 cmd += ' --internal' 436 if box: 437 x, y, r, b = box 438 w = r - x 439 h = b - y 440 cmd += ' --crop=%dx%d+%d+%d' % (w, h, x, y) 441 cmd += ' "%s"' % fullpath 442 utils.run(cmd) 443 return fullpath 444 445 446# id encoder status name size (mm) modes encoders 447# 39 0 connected eDP-1 256x144 1 38 448_MODETEST_CONNECTOR_PATTERN = re.compile( 449 r'^(\d+)\s+(\d+)\s+(connected|disconnected)\s+(\S+)\s+\d+x\d+\s+\d+\s+\d+') 450 451# id crtc type possible crtcs possible clones 452# 38 0 TMDS 0x00000002 0x00000000 453_MODETEST_ENCODER_PATTERN = re.compile( 454 r'^(\d+)\s+(\d+)\s+\S+\s+0x[0-9a-fA-F]+\s+0x[0-9a-fA-F]+') 455 456# Group names match the drmModeModeInfo struct 457_MODETEST_MODE_PATTERN = re.compile( 458 r'\s+(?P<name>.+)' 459 r'\s+(?P<vrefresh>\d+)' 460 r'\s+(?P<hdisplay>\d+)' 461 r'\s+(?P<hsync_start>\d+)' 462 r'\s+(?P<hsync_end>\d+)' 463 r'\s+(?P<htotal>\d+)' 464 r'\s+(?P<vdisplay>\d+)' 465 r'\s+(?P<vsync_start>\d+)' 466 r'\s+(?P<vsync_end>\d+)' 467 r'\s+(?P<vtotal>\d+)' 468 r'\s+(?P<clock>\d+)' 469 r'\s+flags:.+type:' 470 r' preferred') 471 472_MODETEST_CRTCS_START_PATTERN = re.compile(r'^id\s+fb\s+pos\s+size') 473 474_MODETEST_CRTC_PATTERN = re.compile( 475 r'^(\d+)\s+(\d+)\s+\((\d+),(\d+)\)\s+\((\d+)x(\d+)\)') 476 477_MODETEST_PLANES_START_PATTERN = re.compile( 478 r'^id\s+crtc\s+fb\s+CRTC\s+x,y\s+x,y\s+gamma\s+size\s+possible\s+crtcs') 479 480_MODETEST_PLANE_PATTERN = re.compile( 481 r'^(\d+)\s+(\d+)\s+(\d+)\s+(\d+),(\d+)\s+(\d+),(\d+)\s+(\d+)\s+(0x)(\d+)') 482 483Connector = collections.namedtuple( 484 'Connector', [ 485 'cid', # connector id (integer) 486 'eid', # encoder id (integer) 487 'ctype', # connector type, e.g. 'eDP', 'HDMI-A', 'DP' 488 'connected', # boolean 489 'size', # current screen size in mm, e.g. (256, 144) 490 'encoder', # encoder id (integer) 491 # list of resolution tuples, e.g. [(1920,1080), (1600,900), ...] 492 'modes', 493 ]) 494 495Encoder = collections.namedtuple( 496 'Encoder', [ 497 'eid', # encoder id (integer) 498 'crtc_id', # CRTC id (integer) 499 ]) 500 501CRTC = collections.namedtuple( 502 'CRTC', [ 503 'id', # crtc id 504 'fb', # fb id 505 'pos', # position, e.g. (0,0) 506 'size', # size, e.g. (1366,768) 507 'is_internal', # True if for the internal display 508 ]) 509 510Plane = collections.namedtuple( 511 'Plane', [ 512 'id', # plane id 513 'possible_crtcs', # possible associated CRTC indexes. 514 ]) 515 516def get_display_resolution(): 517 """ 518 Parses output of modetest to determine the display resolution of the dut. 519 @return: tuple, (w,h) resolution of device under test. 520 """ 521 connectors = get_modetest_connectors() 522 for connector in connectors: 523 if connector.connected: 524 return connector.size 525 return None 526 527 528def _get_num_outputs_connected(): 529 """ 530 Parses output of modetest to determine the number of connected displays 531 @return: The number of connected displays 532 """ 533 connected = 0 534 connectors = get_modetest_connectors() 535 for connector in connectors: 536 if connector.connected: 537 connected = connected + 1 538 539 return connected 540 541 542def get_num_outputs_on(): 543 """ 544 Retrieves the number of connected outputs that are on. 545 546 Return value: integer value of number of connected outputs that are on. 547 """ 548 549 return _get_num_outputs_connected() 550 551 552def get_modetest_connectors(): 553 """ 554 Retrieves a list of Connectors using modetest. 555 556 Return value: List of Connectors. 557 """ 558 connectors = [] 559 modetest_output = utils.system_output('modetest -c') 560 for line in modetest_output.splitlines(): 561 # First search for a new connector. 562 connector_match = re.match(_MODETEST_CONNECTOR_PATTERN, line) 563 if connector_match is not None: 564 cid = int(connector_match.group(1)) 565 eid = int(connector_match.group(2)) 566 connected = False 567 if connector_match.group(3) == 'connected': 568 connected = True 569 ctype = connector_match.group(4) 570 size = (-1, -1) 571 encoder = -1 572 modes = None 573 connectors.append( 574 Connector(cid, eid, ctype, connected, size, encoder, modes)) 575 else: 576 # See if we find corresponding line with modes, sizes etc. 577 mode_match = re.match(_MODETEST_MODE_PATTERN, line) 578 if mode_match is not None: 579 size = (int(mode_match.group('hdisplay')), 580 int(mode_match.group('vdisplay'))) 581 # Update display size of last connector in list. 582 c = connectors.pop() 583 connectors.append( 584 Connector( 585 c.cid, c.eid, c.ctype, c.connected, size, c.encoder, 586 c.modes)) 587 return connectors 588 589 590def get_modetest_encoders(): 591 """ 592 Retrieves a list of Encoders using modetest. 593 594 Return value: List of Encoders. 595 """ 596 encoders = [] 597 modetest_output = utils.system_output('modetest -e') 598 for line in modetest_output.splitlines(): 599 encoder_match = re.match(_MODETEST_ENCODER_PATTERN, line) 600 if encoder_match is None: 601 continue 602 603 eid = int(encoder_match.group(1)) 604 crtc_id = int(encoder_match.group(2)) 605 encoders.append(Encoder(eid, crtc_id)) 606 return encoders 607 608 609def find_eid_from_crtc_id(crtc_id): 610 """ 611 Finds the integer Encoder ID matching a CRTC ID. 612 613 @param crtc_id: The integer CRTC ID. 614 615 @return: The integer Encoder ID or None. 616 """ 617 encoders = get_modetest_encoders() 618 for encoder in encoders: 619 if encoder.crtc_id == crtc_id: 620 return encoder.eid 621 return None 622 623 624def find_connector_from_eid(eid): 625 """ 626 Finds the Connector object matching an Encoder ID. 627 628 @param eid: The integer Encoder ID. 629 630 @return: The Connector object or None. 631 """ 632 connectors = get_modetest_connectors() 633 for connector in connectors: 634 if connector.eid == eid: 635 return connector 636 return None 637 638 639def get_modetest_crtcs(): 640 """ 641 Returns a list of CRTC data. 642 643 Sample: 644 [CRTC(id=19, fb=50, pos=(0, 0), size=(1366, 768)), 645 CRTC(id=22, fb=54, pos=(0, 0), size=(1920, 1080))] 646 """ 647 crtcs = [] 648 modetest_output = utils.system_output('modetest -p') 649 found = False 650 for line in modetest_output.splitlines(): 651 if found: 652 crtc_match = re.match(_MODETEST_CRTC_PATTERN, line) 653 if crtc_match is not None: 654 crtc_id = int(crtc_match.group(1)) 655 fb = int(crtc_match.group(2)) 656 x = int(crtc_match.group(3)) 657 y = int(crtc_match.group(4)) 658 width = int(crtc_match.group(5)) 659 height = int(crtc_match.group(6)) 660 # CRTCs with fb=0 are disabled, but lets skip anything with 661 # trivial width/height just in case. 662 if not (fb == 0 or width == 0 or height == 0): 663 eid = find_eid_from_crtc_id(crtc_id) 664 connector = find_connector_from_eid(eid) 665 if connector is None: 666 is_internal = False 667 else: 668 is_internal = (connector.ctype == 669 get_internal_connector_name()) 670 crtcs.append(CRTC(crtc_id, fb, (x, y), (width, height), 671 is_internal)) 672 elif line and not line[0].isspace(): 673 return crtcs 674 if re.match(_MODETEST_CRTCS_START_PATTERN, line) is not None: 675 found = True 676 return crtcs 677 678 679def get_modetest_planes(): 680 """ 681 Returns a list of planes information. 682 683 Sample: 684 [Plane(id=26, possible_crtcs=1), 685 Plane(id=29, possible_crtcs=1)] 686 """ 687 planes = [] 688 modetest_output = utils.system_output('modetest -p') 689 found = False 690 for line in modetest_output.splitlines(): 691 if found: 692 plane_match = re.match(_MODETEST_PLANE_PATTERN, line) 693 if plane_match is not None: 694 plane_id = int(plane_match.group(1)) 695 possible_crtcs = int(plane_match.group(10)) 696 if not (plane_id == 0 or possible_crtcs == 0): 697 planes.append(Plane(plane_id, possible_crtcs)) 698 elif line and not line[0].isspace(): 699 return planes 700 if re.match(_MODETEST_PLANES_START_PATTERN, line) is not None: 701 found = True 702 return planes 703 704 705def is_nv12_supported_by_drm_planes(): 706 """ 707 Returns if the planes information mention NV12 format or not. 708 709 This is a crude way to figure out if the device will not be able to promote 710 video frames to overlays at all, which happens for example on Broadwell. 711 """ 712 modetest_output = utils.system_output('modetest -p') 713 return "nv12" in modetest_output.lower() 714 715def get_modetest_output_state(): 716 """ 717 Reduce the output of get_modetest_connectors to a dictionary of connector/active states. 718 """ 719 connectors = get_modetest_connectors() 720 outputs = {} 721 for connector in connectors: 722 # TODO(ihf): Figure out why modetest output needs filtering. 723 if connector.connected: 724 outputs[connector.ctype] = connector.connected 725 return outputs 726 727 728def get_output_rect(output): 729 """Gets the size and position of the given output on the screen buffer. 730 731 @param output: The output name as a string. 732 733 @return A tuple of the rectangle (width, height, fb_offset_x, 734 fb_offset_y) of ints. 735 """ 736 connectors = get_modetest_connectors() 737 for connector in connectors: 738 if connector.ctype == output: 739 # Concatenate two 2-tuples to 4-tuple. 740 return connector.size + (0, 0) # TODO(ihf): Should we use CRTC.pos? 741 return (0, 0, 0, 0) 742 743 744def get_internal_crtc(): 745 for crtc in get_modetest_crtcs(): 746 if crtc.is_internal: 747 return crtc 748 return None 749 750 751def get_external_crtc(index=0): 752 for crtc in get_modetest_crtcs(): 753 if not crtc.is_internal: 754 if index == 0: 755 return crtc 756 index -= 1 757 return None 758 759 760def get_internal_resolution(): 761 crtc = get_internal_crtc() 762 if crtc: 763 return crtc.size 764 return (-1, -1) 765 766 767def has_internal_display(): 768 """Checks whether the DUT is equipped with an internal display. 769 770 @return True if internal display is present; False otherwise. 771 """ 772 return bool(get_internal_connector_name()) 773 774 775def get_external_resolution(): 776 """Gets the resolution of the external display. 777 778 @return A tuple of (width, height) or None if no external display is 779 connected. 780 """ 781 crtc = get_external_crtc() 782 if crtc: 783 return crtc.size 784 return None 785 786 787def get_display_output_state(): 788 """ 789 Retrieves output status of connected display(s). 790 791 Return value: dictionary of connected display states. 792 """ 793 return get_modetest_output_state() 794 795 796def set_modetest_output(output_name, enable): 797 # TODO(ihf): figure out what to do here. Don't think this is the right command. 798 # modetest -s <connector_id>[,<connector_id>][@<crtc_id>]:<mode>[-<vrefresh>][@<format>] set a mode 799 pass 800 801 802def set_display_output(output_name, enable): 803 """ 804 Sets the output given by |output_name| on or off. 805 """ 806 set_modetest_output(output_name, enable) 807 808 809# TODO(ihf): Fix this for multiple external connectors. 810def get_external_crtc_id(index=0): 811 crtc = get_external_crtc(index) 812 if crtc is not None: 813 return crtc.id 814 return -1 815 816 817def get_internal_crtc_id(): 818 crtc = get_internal_crtc() 819 if crtc is not None: 820 return crtc.id 821 return -1 822 823 824# TODO(ihf): Fix this for multiple external connectors. 825def get_external_connector_name(): 826 """Gets the name of the external output connector. 827 828 @return The external output connector name as a string, if any. 829 Otherwise, return False. 830 """ 831 outputs = get_display_output_state() 832 for output in outputs.iterkeys(): 833 if outputs[output] and (output.startswith('HDMI') 834 or output.startswith('DP') 835 or output.startswith('DVI') 836 or output.startswith('VGA')): 837 return output 838 return False 839 840 841def get_internal_connector_name(): 842 """Gets the name of the internal output connector. 843 844 @return The internal output connector name as a string, if any. 845 Otherwise, return False. 846 """ 847 outputs = get_display_output_state() 848 for output in outputs.iterkeys(): 849 # reference: chromium_org/chromeos/display/output_util.cc 850 if (output.startswith('eDP') 851 or output.startswith('LVDS') 852 or output.startswith('DSI')): 853 return output 854 return False 855 856 857def wait_output_connected(output): 858 """Wait for output to connect. 859 860 @param output: The output name as a string. 861 862 @return: True if output is connected; False otherwise. 863 """ 864 def _is_connected(output): 865 """Helper function.""" 866 outputs = get_display_output_state() 867 if output not in outputs: 868 return False 869 return outputs[output] 870 871 return utils.wait_for_value(lambda: _is_connected(output), 872 expected_value=True) 873 874 875def set_content_protection(output_name, state): 876 """ 877 Sets the content protection to the given state. 878 879 @param output_name: The output name as a string. 880 @param state: One of the states 'Undesired', 'Desired', or 'Enabled' 881 882 """ 883 raise error.TestFail('freon: set_content_protection not implemented') 884 885 886def get_content_protection(output_name): 887 """ 888 Gets the state of the content protection. 889 890 @param output_name: The output name as a string. 891 @return: A string of the state, like 'Undesired', 'Desired', or 'Enabled'. 892 False if not supported. 893 894 """ 895 raise error.TestFail('freon: get_content_protection not implemented') 896 897 898def is_sw_rasterizer(): 899 """Return true if OpenGL is using a software rendering.""" 900 cmd = utils.wflinfo_cmd() + ' | grep "OpenGL renderer string"' 901 output = utils.run(cmd) 902 result = output.stdout.splitlines()[0] 903 logging.info('wflinfo: %s', result) 904 # TODO(ihf): Find exhaustive error conditions (especially ARM). 905 return 'llvmpipe' in result.lower() or 'soft' in result.lower() 906 907 908def get_gles_version(): 909 cmd = utils.wflinfo_cmd() 910 wflinfo = utils.system_output(cmd, retain_output=False, ignore_status=False) 911 # OpenGL version string: OpenGL ES 3.0 Mesa 10.5.0-devel 912 version = re.findall(r'OpenGL version string: ' 913 r'OpenGL ES ([0-9]+).([0-9]+)', wflinfo) 914 if version: 915 version_major = int(version[0][0]) 916 version_minor = int(version[0][1]) 917 return (version_major, version_minor) 918 return (None, None) 919 920 921def get_egl_version(): 922 cmd = 'eglinfo' 923 eglinfo = utils.system_output(cmd, retain_output=False, ignore_status=False) 924 # EGL version string: 1.4 (DRI2) 925 version = re.findall(r'EGL version string: ([0-9]+).([0-9]+)', eglinfo) 926 if version: 927 version_major = int(version[0][0]) 928 version_minor = int(version[0][1]) 929 return (version_major, version_minor) 930 return (None, None) 931 932 933class GraphicsKernelMemory(object): 934 """ 935 Reads from sysfs to determine kernel gem objects and memory info. 936 """ 937 # These are sysfs fields that will be read by this test. For different 938 # architectures, the sysfs field paths are different. The "paths" are given 939 # as lists of strings because the actual path may vary depending on the 940 # system. This test will read from the first sysfs path in the list that is 941 # present. 942 # e.g. ".../memory" vs ".../gpu_memory" -- if the system has either one of 943 # these, the test will read from that path. 944 amdgpu_fields = { 945 'gem_objects': ['/sys/kernel/debug/dri/0/amdgpu_gem_info'], 946 'memory': ['/sys/kernel/debug/dri/0/amdgpu_gtt_mm'], 947 } 948 arm_fields = {} 949 exynos_fields = { 950 'gem_objects': ['/sys/kernel/debug/dri/?/exynos_gem_objects'], 951 'memory': ['/sys/class/misc/mali0/device/memory', 952 '/sys/class/misc/mali0/device/gpu_memory'], 953 } 954 mediatek_fields = {} 955 # TODO(crosbug.com/p/58189) Add mediatek GPU memory nodes 956 qualcomm_fields = {} 957 # TODO(b/119269602) Add qualcomm GPU memory nodes once GPU patches land 958 rockchip_fields = {} 959 tegra_fields = { 960 'memory': ['/sys/kernel/debug/memblock/memory'], 961 } 962 i915_fields = { 963 'gem_objects': ['/sys/kernel/debug/dri/0/i915_gem_objects'], 964 'memory': ['/sys/kernel/debug/dri/0/i915_gem_gtt'], 965 } 966 # In Linux Kernel 5, i915_gem_gtt merged into i915_gem_objects 967 i915_fields_kernel_5 = { 968 'gem_objects': ['/sys/kernel/debug/dri/0/i915_gem_objects'], 969 } 970 cirrus_fields = {} 971 virtio_fields = {} 972 973 arch_fields = { 974 'amdgpu': amdgpu_fields, 975 'arm': arm_fields, 976 'cirrus': cirrus_fields, 977 'exynos5': exynos_fields, 978 'i915': i915_fields, 979 'i915_kernel_5': i915_fields_kernel_5, 980 'mediatek': mediatek_fields, 981 'qualcomm': qualcomm_fields, 982 'rockchip': rockchip_fields, 983 'tegra': tegra_fields, 984 'virtio': virtio_fields, 985 } 986 987 988 num_errors = 0 989 990 def __init__(self): 991 self._initial_memory = self.get_memory_keyvals() 992 993 def get_memory_difference_keyvals(self): 994 """ 995 Reads the graphics memory values and return the difference between now 996 and the memory usage at initialization stage as keyvals. 997 """ 998 current_memory = self.get_memory_keyvals() 999 return {key: self._initial_memory[key] - current_memory[key] 1000 for key in self._initial_memory} 1001 1002 def get_memory_keyvals(self): 1003 """ 1004 Reads the graphics memory values and returns them as keyvals. 1005 """ 1006 keyvals = {} 1007 1008 # Get architecture type and list of sysfs fields to read. 1009 soc = utils.get_cpu_soc_family() 1010 1011 arch = utils.get_cpu_arch() 1012 kernel_version = utils.get_kernel_version()[0:4].rstrip(".") 1013 if arch == 'x86_64' or arch == 'i386': 1014 pci_vga_device = utils.run("lspci | grep VGA").stdout.rstrip('\n') 1015 if "Advanced Micro Devices" in pci_vga_device: 1016 soc = 'amdgpu' 1017 elif "Intel Corporation" in pci_vga_device: 1018 soc = 'i915' 1019 if utils.compare_versions(kernel_version, "4.19") > 0: 1020 soc = 'i915_kernel_5' 1021 elif "Cirrus Logic" in pci_vga_device: 1022 # Used on qemu with kernels 3.18 and lower. Limited to 800x600 1023 # resolution. 1024 soc = 'cirrus' 1025 else: 1026 pci_vga_device = utils.run('lshw -c video').stdout.rstrip() 1027 groups = re.search('configuration:.*driver=(\S*)', 1028 pci_vga_device) 1029 if groups and 'virtio' in groups.group(1): 1030 soc = 'virtio' 1031 1032 if not soc in self.arch_fields: 1033 raise error.TestFail('Error: Architecture "%s" not yet supported.' % soc) 1034 fields = self.arch_fields[soc] 1035 1036 for field_name in fields: 1037 possible_field_paths = fields[field_name] 1038 field_value = None 1039 for path in possible_field_paths: 1040 if utils.system('ls %s' % path, ignore_status=True): 1041 continue 1042 field_value = utils.system_output('cat %s' % path) 1043 break 1044 1045 if not field_value: 1046 logging.error('Unable to find any sysfs paths for field "%s"', 1047 field_name) 1048 self.num_errors += 1 1049 continue 1050 1051 parsed_results = GraphicsKernelMemory._parse_sysfs(field_value) 1052 1053 for key in parsed_results: 1054 keyvals['%s_%s' % (field_name, key)] = parsed_results[key] 1055 1056 if 'bytes' in parsed_results and parsed_results['bytes'] == 0: 1057 logging.error('%s reported 0 bytes', field_name) 1058 self.num_errors += 1 1059 1060 keyvals['meminfo_MemUsed'] = (utils.read_from_meminfo('MemTotal') - 1061 utils.read_from_meminfo('MemFree')) 1062 keyvals['meminfo_SwapUsed'] = (utils.read_from_meminfo('SwapTotal') - 1063 utils.read_from_meminfo('SwapFree')) 1064 return keyvals 1065 1066 @staticmethod 1067 def _parse_sysfs(output): 1068 """ 1069 Parses output of graphics memory sysfs to determine the number of 1070 buffer objects and bytes. 1071 1072 Arguments: 1073 output Unprocessed sysfs output 1074 Return value: 1075 Dictionary containing integer values of number bytes and objects. 1076 They may have the keys 'bytes' and 'objects', respectively. However 1077 the result may not contain both of these values. 1078 """ 1079 results = {} 1080 labels = ['bytes', 'objects'] 1081 1082 # First handle i915_gem_objects in 5.x kernels. Example: 1083 # 296 shrinkable [0 free] objects, 274833408 bytes 1084 # frecon: 3 objects, 72192000 bytes (0 active, 0 inactive, 0 unbound, 0 closed) 1085 # chrome: 6 objects, 74629120 bytes (0 active, 0 inactive, 376832 unbound, 0 closed) 1086 # <snip> 1087 i915_gem_objects_pattern = re.compile( 1088 r'(?P<objects>\d*) shrinkable.*objects, (?P<bytes>\d*) bytes') 1089 i915_gem_objects_match = i915_gem_objects_pattern.match(output) 1090 if i915_gem_objects_match is not None: 1091 results['bytes'] = int(i915_gem_objects_match.group('bytes')) 1092 results['objects'] = int(i915_gem_objects_match.group('objects')) 1093 return results 1094 1095 for line in output.split('\n'): 1096 # Strip any commas to make parsing easier. 1097 line_words = line.replace(',', '').split() 1098 1099 prev_word = None 1100 for word in line_words: 1101 # When a label has been found, the previous word should be the 1102 # value. e.g. "3200 bytes" 1103 if word in labels and word not in results and prev_word: 1104 logging.info(prev_word) 1105 results[word] = int(prev_word) 1106 1107 prev_word = word 1108 1109 # Once all values has been parsed, return. 1110 if len(results) == len(labels): 1111 return results 1112 1113 return results 1114 1115 1116class GraphicsStateChecker(object): 1117 """ 1118 Analyzes the state of the GPU and log history. Should be instantiated at the 1119 beginning of each graphics_* test. 1120 """ 1121 dirty_writeback_centisecs = 0 1122 existing_hangs = {} 1123 1124 _BROWSER_VERSION_COMMAND = '/opt/google/chrome/chrome --version' 1125 _HANGCHECK = ['drm:i915_hangcheck_elapsed', 'drm:i915_hangcheck_hung', 1126 'Hangcheck timer elapsed...', 1127 'drm/i915: Resetting chip after gpu hang'] 1128 _HANGCHECK_WARNING = ['render ring idle'] 1129 _MESSAGES_FILE = '/var/log/messages' 1130 1131 def __init__(self, raise_error_on_hang=True, run_on_sw_rasterizer=False): 1132 """ 1133 Analyzes the initial state of the GPU and log history. 1134 """ 1135 # Attempt flushing system logs every second instead of every 10 minutes. 1136 self.dirty_writeback_centisecs = utils.get_dirty_writeback_centisecs() 1137 utils.set_dirty_writeback_centisecs(100) 1138 self._raise_error_on_hang = raise_error_on_hang 1139 logging.info(utils.get_board_with_frequency_and_memory()) 1140 self.graphics_kernel_memory = GraphicsKernelMemory() 1141 self._run_on_sw_rasterizer = run_on_sw_rasterizer 1142 1143 if utils.get_cpu_arch() != 'arm': 1144 if not self._run_on_sw_rasterizer and is_sw_rasterizer(): 1145 raise error.TestFail('Refusing to run on SW rasterizer.') 1146 logging.info('Initialize: Checking for old GPU hangs...') 1147 messages = open(self._MESSAGES_FILE, 'r') 1148 for line in messages: 1149 for hang in self._HANGCHECK: 1150 if hang in line: 1151 logging.info(line) 1152 self.existing_hangs[line] = line 1153 messages.close() 1154 1155 def finalize(self): 1156 """ 1157 Analyzes the state of the GPU, log history and emits warnings or errors 1158 if the state changed since initialize. Also makes a note of the Chrome 1159 version for later usage in the perf-dashboard. 1160 """ 1161 utils.set_dirty_writeback_centisecs(self.dirty_writeback_centisecs) 1162 new_gpu_hang = False 1163 new_gpu_warning = False 1164 if utils.get_cpu_arch() != 'arm': 1165 logging.info('Cleanup: Checking for new GPU hangs...') 1166 messages = open(self._MESSAGES_FILE, 'r') 1167 for line in messages: 1168 for hang in self._HANGCHECK: 1169 if hang in line: 1170 if not line in self.existing_hangs.keys(): 1171 logging.info(line) 1172 for warn in self._HANGCHECK_WARNING: 1173 if warn in line: 1174 new_gpu_warning = True 1175 logging.warning( 1176 'Saw GPU hang warning during test.') 1177 else: 1178 logging.warning('Saw GPU hang during test.') 1179 new_gpu_hang = True 1180 messages.close() 1181 1182 if not self._run_on_sw_rasterizer and is_sw_rasterizer(): 1183 logging.warning('Finished test on SW rasterizer.') 1184 raise error.TestFail('Finished test on SW rasterizer.') 1185 if self._raise_error_on_hang and new_gpu_hang: 1186 raise error.TestError('Detected GPU hang during test.') 1187 if new_gpu_hang: 1188 raise error.TestWarn('Detected GPU hang during test.') 1189 if new_gpu_warning: 1190 raise error.TestWarn('Detected GPU warning during test.') 1191 1192 def get_memory_access_errors(self): 1193 """ Returns the number of errors while reading memory stats. """ 1194 return self.graphics_kernel_memory.num_errors 1195 1196 def get_memory_difference_keyvals(self): 1197 return self.graphics_kernel_memory.get_memory_difference_keyvals() 1198 1199 def get_memory_keyvals(self): 1200 """ Returns memory stats. """ 1201 return self.graphics_kernel_memory.get_memory_keyvals() 1202 1203class GraphicsApiHelper(object): 1204 """ 1205 Report on the available graphics APIs. 1206 Ex. gles2, gles3, gles31, and vk 1207 """ 1208 _supported_apis = [] 1209 1210 DEQP_BASEDIR = os.path.join('/usr', 'local', 'deqp') 1211 DEQP_EXECUTABLE = { 1212 'gles2': os.path.join('modules', 'gles2', 'deqp-gles2'), 1213 'gles3': os.path.join('modules', 'gles3', 'deqp-gles3'), 1214 'gles31': os.path.join('modules', 'gles31', 'deqp-gles31'), 1215 'vk': os.path.join('external', 'vulkancts', 'modules', 1216 'vulkan', 'deqp-vk') 1217 } 1218 1219 def __init__(self): 1220 # Determine which executable should be run. Right now never egl. 1221 major, minor = get_gles_version() 1222 logging.info('Found gles%d.%d.', major, minor) 1223 if major is None or minor is None: 1224 raise error.TestFail( 1225 'Failed: Could not get gles version information (%d, %d).' % 1226 (major, minor) 1227 ) 1228 if major >= 2: 1229 self._supported_apis.append('gles2') 1230 if major >= 3: 1231 self._supported_apis.append('gles3') 1232 if major > 3 or minor >= 1: 1233 self._supported_apis.append('gles31') 1234 1235 # If libvulkan is installed, then assume the board supports vulkan. 1236 has_libvulkan = False 1237 for libdir in ('/usr/lib', '/usr/lib64', 1238 '/usr/local/lib', '/usr/local/lib64'): 1239 if os.path.exists(os.path.join(libdir, 'libvulkan.so')): 1240 has_libvulkan = True 1241 1242 if has_libvulkan: 1243 executable_path = os.path.join( 1244 self.DEQP_BASEDIR, 1245 self.DEQP_EXECUTABLE['vk'] 1246 ) 1247 if os.path.exists(executable_path): 1248 self._supported_apis.append('vk') 1249 else: 1250 logging.warning('Found libvulkan.so but did not find deqp-vk ' 1251 'binary for testing.') 1252 1253 def get_supported_apis(self): 1254 """Return the list of supported apis. eg. gles2, gles3, vk etc. 1255 @returns: a copy of the supported api list will be returned 1256 """ 1257 return list(self._supported_apis) 1258 1259 def get_deqp_executable(self, api): 1260 """Return the path to the api executable.""" 1261 if api not in self.DEQP_EXECUTABLE: 1262 raise KeyError( 1263 "%s is not a supported api for GraphicsApiHelper." % api 1264 ) 1265 1266 executable = os.path.join( 1267 self.DEQP_BASEDIR, 1268 self.DEQP_EXECUTABLE[api] 1269 ) 1270 return executable 1271 1272# Possible paths of the kernel DRI debug text file. 1273_DRI_DEBUG_FILE_PATH_0 = "/sys/kernel/debug/dri/0/state" 1274_DRI_DEBUG_FILE_PATH_1 = "/sys/kernel/debug/dri/1/state" 1275_DRI_DEBUG_FILE_PATH_2 = "/sys/kernel/debug/dri/2/state" 1276 1277# The DRI debug file will have a lot of information, including the position and 1278# sizes of each plane. Some planes might be disabled but have some lingering 1279# crtc-pos information, those are skipped. 1280_CRTC_PLANE_START_PATTERN = re.compile(r'plane\[') 1281_CRTC_DISABLED_PLANE = re.compile(r'crtc=\(null\)') 1282_CRTC_POS_AND_SIZE_PATTERN = re.compile(r'crtc-pos=(?!0x0\+0\+0)') 1283 1284def get_num_hardware_overlays(): 1285 """ 1286 Counts the amount of hardware overlay planes in use. There's always at 1287 least 2 overlays active: the whole screen and the cursor -- unless the 1288 cursor has never moved (e.g. in autotests), and it's not present. 1289 1290 Raises: RuntimeError if the DRI debug file is not present. 1291 OSError/IOError if the file cannot be open()ed or read(). 1292 """ 1293 file_path = _DRI_DEBUG_FILE_PATH_0; 1294 if os.path.exists(_DRI_DEBUG_FILE_PATH_0): 1295 file_path = _DRI_DEBUG_FILE_PATH_0; 1296 elif os.path.exists(_DRI_DEBUG_FILE_PATH_1): 1297 file_path = _DRI_DEBUG_FILE_PATH_1; 1298 elif os.path.exists(_DRI_DEBUG_FILE_PATH_2): 1299 file_path = _DRI_DEBUG_FILE_PATH_2; 1300 else: 1301 raise RuntimeError('No DRI debug file exists (%s, %s)' % 1302 (_DRI_DEBUG_FILE_PATH_0, _DRI_DEBUG_FILE_PATH_1)) 1303 1304 filetext = open(file_path).read() 1305 logging.debug(filetext) 1306 1307 matches = [] 1308 # Split the debug output by planes, skip the disabled ones and extract those 1309 # with correct position and size information. 1310 planes = re.split(_CRTC_PLANE_START_PATTERN, filetext) 1311 for plane in planes: 1312 if len(plane) == 0: 1313 continue; 1314 if len(re.findall(_CRTC_DISABLED_PLANE, plane)) > 0: 1315 continue; 1316 1317 matches.append(re.findall(_CRTC_POS_AND_SIZE_PATTERN, plane)) 1318 1319 # TODO(crbug.com/865112): return also the sizes/locations. 1320 return len(matches) 1321 1322def is_drm_debug_supported(): 1323 """ 1324 @returns true if either of the DRI debug files are present. 1325 """ 1326 return (os.path.exists(_DRI_DEBUG_FILE_PATH_0) or 1327 os.path.exists(_DRI_DEBUG_FILE_PATH_1) or 1328 os.path.exists(_DRI_DEBUG_FILE_PATH_2)) 1329 1330# Path and file name regex defining the filesystem location for DRI devices. 1331_DEV_DRI_FOLDER_PATH = '/dev/dri' 1332_DEV_DRI_CARD_PATH = '/dev/dri/card?' 1333 1334# IOCTL code and associated parameter to set the atomic cap. Defined originally 1335# in the kernel's include/uapi/drm/drm.h file. 1336_DRM_IOCTL_SET_CLIENT_CAP = 0x4010640d 1337_DRM_CLIENT_CAP_ATOMIC = 3 1338 1339def is_drm_atomic_supported(): 1340 """ 1341 @returns true if there is at least a /dev/dri/card? file that seems to 1342 support drm_atomic mode (accepts a _DRM_IOCTL_SET_CLIENT_CAP ioctl). 1343 """ 1344 if not os.path.isdir(_DEV_DRI_FOLDER_PATH): 1345 # This should never ever happen. 1346 raise error.TestError('path %s inexistent', _DEV_DRI_FOLDER_PATH); 1347 1348 for dev_path in glob.glob(_DEV_DRI_CARD_PATH): 1349 try: 1350 logging.debug('trying device %s', dev_path); 1351 with open(dev_path, 'rw') as dev: 1352 # Pack a struct drm_set_client_cap: two u64. 1353 drm_pack = struct.pack("QQ", _DRM_CLIENT_CAP_ATOMIC, 1) 1354 result = fcntl.ioctl(dev, _DRM_IOCTL_SET_CLIENT_CAP, drm_pack) 1355 1356 if result is None or len(result) != len(drm_pack): 1357 # This should never ever happen. 1358 raise error.TestError('ioctl failure') 1359 1360 logging.debug('%s supports atomic', dev_path); 1361 1362 if not is_drm_debug_supported(): 1363 raise error.TestError('platform supports DRM but there ' 1364 ' are no debug files for it') 1365 return True 1366 except IOError as err: 1367 logging.warning('ioctl failed on %s: %s', dev_path, str(err)); 1368 1369 logging.debug('No dev files seems to support atomic'); 1370 return False 1371 1372def get_max_num_available_drm_planes(): 1373 """ 1374 @returns The maximum number of DRM planes available in the system 1375 (associated to the same CRTC), or 0 if something went wrong (e.g. modetest 1376 failed, etc). 1377 """ 1378 1379 planes = get_modetest_planes() 1380 if len(planes) == 0: 1381 return 0; 1382 packed_possible_crtcs = [plane.possible_crtcs for plane in planes] 1383 # |packed_possible_crtcs| is actually a bit field of possible CRTCs, e.g. 1384 # 0x6 (b1001) means the plane can be associated with CRTCs index 0 and 3 but 1385 # not with index 1 nor 2. Unpack those into |possible_crtcs|, an array of 1386 # binary arrays. 1387 possible_crtcs = [[int(bit) for bit in bin(crtc)[2:].zfill(16)] 1388 for crtc in packed_possible_crtcs] 1389 # Accumulate the CRTCs indexes and return the maximum number of 'votes'. 1390 return max(map(sum, zip(*possible_crtcs))) 1391