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