xref: /aosp_15_r20/external/autotest/client/cros/graphics/graphics_utils.py (revision 9c5db1993ded3edbeafc8092d69fe5de2ee02df7)
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