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