xref: /aosp_15_r20/external/autotest/server/hosts/servo_repair.py (revision 9c5db1993ded3edbeafc8092d69fe5de2ee02df7)
1*9c5db199SXin Li# Lint as: python2, python3
2*9c5db199SXin Li# Copyright 2016 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 Lifrom __future__ import absolute_import
7*9c5db199SXin Lifrom __future__ import division
8*9c5db199SXin Lifrom __future__ import print_function
9*9c5db199SXin Li
10*9c5db199SXin Liimport functools
11*9c5db199SXin Liimport logging
12*9c5db199SXin Liimport math
13*9c5db199SXin Liimport sys
14*9c5db199SXin Liimport time
15*9c5db199SXin Li
16*9c5db199SXin Liimport common
17*9c5db199SXin Lifrom autotest_lib.client.common_lib import error
18*9c5db199SXin Lifrom autotest_lib.client.common_lib import hosts
19*9c5db199SXin Lifrom autotest_lib.client.common_lib import utils
20*9c5db199SXin Lifrom autotest_lib.server.cros.servo import servo
21*9c5db199SXin Lifrom autotest_lib.server.hosts import cros_constants
22*9c5db199SXin Lifrom autotest_lib.server.hosts import repair_utils
23*9c5db199SXin Lifrom autotest_lib.server.hosts import servo_constants
24*9c5db199SXin Lifrom autotest_lib.server.cros.servo.topology import servo_topology
25*9c5db199SXin Lifrom autotest_lib.site_utils.admin_audit import servo_updater
26*9c5db199SXin Liimport six
27*9c5db199SXin Li
28*9c5db199SXin Litry:
29*9c5db199SXin Li    from autotest_lib.utils.frozen_chromite.lib import metrics
30*9c5db199SXin Liexcept ImportError:
31*9c5db199SXin Li    metrics = utils.metrics_mock
32*9c5db199SXin Li
33*9c5db199SXin Lifrom autotest_lib.utils.frozen_chromite.lib import timeout_util
34*9c5db199SXin Li
35*9c5db199SXin Lidef ignore_exception_for_non_cros_host(func):
36*9c5db199SXin Li    """
37*9c5db199SXin Li    Decorator to ignore ControlUnavailableError if servo host is not cros host.
38*9c5db199SXin Li    When using test_that command on a workstation, this enables usage of
39*9c5db199SXin Li    additional servo devices such as servo micro and Sweetberry. This shall not
40*9c5db199SXin Li    change any lab behavior.
41*9c5db199SXin Li    """
42*9c5db199SXin Li    @functools.wraps(func)
43*9c5db199SXin Li    def wrapper(self, host):
44*9c5db199SXin Li        """
45*9c5db199SXin Li        Wrapper around func.
46*9c5db199SXin Li        """
47*9c5db199SXin Li        try:
48*9c5db199SXin Li            func(self, host)
49*9c5db199SXin Li        except servo.ControlUnavailableError as e:
50*9c5db199SXin Li            if host.is_cros_host():
51*9c5db199SXin Li                raise
52*9c5db199SXin Li            logging.warning("Servo host is not cros host, ignore %s: %s",
53*9c5db199SXin Li                            type(e).__name__, e)
54*9c5db199SXin Li    return wrapper
55*9c5db199SXin Li
56*9c5db199SXin Li
57*9c5db199SXin Liclass _UpdateVerifier(hosts.Verifier):
58*9c5db199SXin Li    """
59*9c5db199SXin Li    Verifier to trigger a servo host update, if necessary.
60*9c5db199SXin Li
61*9c5db199SXin Li    The verifier works only for servo_v3.
62*9c5db199SXin Li    The operation doesn't wait for the update to complete and is
63*9c5db199SXin Li    considered a success whether or not the servo is currently
64*9c5db199SXin Li    up-to-date.
65*9c5db199SXin Li    """
66*9c5db199SXin Li
67*9c5db199SXin Li    @timeout_util.TimeoutDecorator(cros_constants.LONG_VERIFY_TIMEOUT_SEC)
68*9c5db199SXin Li    def verify(self, host):
69*9c5db199SXin Li        try:
70*9c5db199SXin Li            if (
71*9c5db199SXin Li                    not host.get_dut_host_info()
72*9c5db199SXin Li                    or not host.get_dut_host_info().servo_cros_stable_version):
73*9c5db199SXin Li                logging.info('Servo stable version missed.'
74*9c5db199SXin Li                             ' Skip update check action.')
75*9c5db199SXin Li                return
76*9c5db199SXin Li            # We have seen cases that invalid GPT headers/entries block
77*9c5db199SXin Li            # v3s from been update, so always try to repair here.
78*9c5db199SXin Li            # See crbug.com/994396, crbug.com/1057302.
79*9c5db199SXin Li            host.run('cgpt repair /dev/mmcblk0', ignore_status=True)
80*9c5db199SXin Li            host.update_image()
81*9c5db199SXin Li        # We don't want failure from update block DUT repair action.
82*9c5db199SXin Li        # See crbug.com/1029950.
83*9c5db199SXin Li        except Exception as e:
84*9c5db199SXin Li            six.reraise(hosts.AutoservNonCriticalVerifyError,
85*9c5db199SXin Li                        hosts.AutoservNonCriticalVerifyError(e),
86*9c5db199SXin Li                        sys.exc_info()[2])
87*9c5db199SXin Li
88*9c5db199SXin Li    def _is_applicable(self, host):
89*9c5db199SXin Li        # Run only for servo_v3 host.
90*9c5db199SXin Li        if host.is_labstation() or host.is_containerized_servod():
91*9c5db199SXin Li            return False
92*9c5db199SXin Li        # Only run if the host is in the physical lab.
93*9c5db199SXin Li        return host.is_in_lab()
94*9c5db199SXin Li
95*9c5db199SXin Li    @property
96*9c5db199SXin Li    def description(self):
97*9c5db199SXin Li        return 'Servo_v3 host software is up-to-date'
98*9c5db199SXin Li
99*9c5db199SXin Li
100*9c5db199SXin Liclass _StartServodVerifier(hosts.Verifier):
101*9c5db199SXin Li    """First start of servod on the host.
102*9c5db199SXin Li
103*9c5db199SXin Li    Single running action to start servod in the first time.
104*9c5db199SXin Li    This verifier created to fit current flow and will be revisited later.
105*9c5db199SXin Li    Action never fails!
106*9c5db199SXin Li    """
107*9c5db199SXin Li
108*9c5db199SXin Li    @timeout_util.TimeoutDecorator(cros_constants.VERIFY_TIMEOUT_SEC)
109*9c5db199SXin Li    def verify(self, host):
110*9c5db199SXin Li        if not hasattr(self, 'started'):
111*9c5db199SXin Li            logging.info('Starting servod!')
112*9c5db199SXin Li            host.restart_servod(quick_startup=True)
113*9c5db199SXin Li        # caching the value to prevent restart service when trigger verifier.
114*9c5db199SXin Li        self.started = True
115*9c5db199SXin Li
116*9c5db199SXin Li    @property
117*9c5db199SXin Li    def description(self):
118*9c5db199SXin Li        return 'Initial servod start'
119*9c5db199SXin Li
120*9c5db199SXin Li
121*9c5db199SXin Liclass _RootServoPresentVerifier(hosts.Verifier):
122*9c5db199SXin Li    """Verifier that first servo is present."""
123*9c5db199SXin Li
124*9c5db199SXin Li    @timeout_util.TimeoutDecorator(cros_constants.VERIFY_TIMEOUT_SEC)
125*9c5db199SXin Li    def verify(self, host):
126*9c5db199SXin Li        device = None
127*9c5db199SXin Li        topology = host.get_topology()
128*9c5db199SXin Li        topology.read(host.get_dut_host_info())
129*9c5db199SXin Li        try:
130*9c5db199SXin Li            device = topology.get_root_servo()
131*9c5db199SXin Li        except Exception as e:
132*9c5db199SXin Li            if host.is_containerized_servod():
133*9c5db199SXin Li                host.restart_servod()
134*9c5db199SXin Li                logging.debug('Restarting servod container (Not critical) %s',
135*9c5db199SXin Li                              e)
136*9c5db199SXin Li            else:
137*9c5db199SXin Li                host.request_reboot()
138*9c5db199SXin Li                logging.info(
139*9c5db199SXin Li                        'Reboot labstation requested, it will be handled'
140*9c5db199SXin Li                        ' by labstation AdminRepair task.'
141*9c5db199SXin Li                        ' Unable to detect root servo info from topology.')
142*9c5db199SXin Li                logging.debug('(Not critical) %s', e)
143*9c5db199SXin Li        if device:
144*9c5db199SXin Li            logging.info('Root servo is present')
145*9c5db199SXin Li            return
146*9c5db199SXin Li        device = topology.get_root_servo_from_cache()
147*9c5db199SXin Li        if device:
148*9c5db199SXin Li            logging.debug('Found device: %s', device)
149*9c5db199SXin Li            if device.get_serial_number() != host.servo_serial:
150*9c5db199SXin Li                self.serial_mismatch = True
151*9c5db199SXin Li                raise hosts.AutoservVerifyError('Serial mismatch detected')
152*9c5db199SXin Li            logging.info('Root servo is present')
153*9c5db199SXin Li            return
154*9c5db199SXin Li        # Leaving error in case we got empty device.
155*9c5db199SXin Li        raise hosts.AutoservVerifyError('Root servo not found!')
156*9c5db199SXin Li
157*9c5db199SXin Li    def _is_applicable(self, host):
158*9c5db199SXin Li        if host.is_containerized_servod():
159*9c5db199SXin Li            logging.info('Servod is running within a container.')
160*9c5db199SXin Li            return True
161*9c5db199SXin Li        if not host.is_labstation():
162*9c5db199SXin Li            logging.info('Not supported for servo_v3.')
163*9c5db199SXin Li            return False
164*9c5db199SXin Li        # Only run if the host is in the physical lab.
165*9c5db199SXin Li        return host.is_in_lab()
166*9c5db199SXin Li
167*9c5db199SXin Li    @property
168*9c5db199SXin Li    def description(self):
169*9c5db199SXin Li        return 'Root servo is present'
170*9c5db199SXin Li
171*9c5db199SXin Li
172*9c5db199SXin Liclass _RootServoV3PresentVerifier(hosts.Verifier):
173*9c5db199SXin Li    """Verifier that first servo is present."""
174*9c5db199SXin Li
175*9c5db199SXin Li    RETRY_COUNT = 3
176*9c5db199SXin Li
177*9c5db199SXin Li    @timeout_util.TimeoutDecorator(cros_constants.VERIFY_TIMEOUT_SEC)
178*9c5db199SXin Li    def verify(self, host):
179*9c5db199SXin Li        for a in range(self.RETRY_COUNT):
180*9c5db199SXin Li            logging.debug('Attempt: %s find servo board on servo_v3.', a + 1)
181*9c5db199SXin Li            present = host.is_servo_board_present_on_servo_v3()
182*9c5db199SXin Li            if present == False:
183*9c5db199SXin Li                raise hosts.AutoservVerifyError('Servo board not found!')
184*9c5db199SXin Li            elif present == True:
185*9c5db199SXin Li                logging.debug('Servo board is present')
186*9c5db199SXin Li                return
187*9c5db199SXin Li        raise hosts.AutoservVerifyError('Fail to find servo board!')
188*9c5db199SXin Li
189*9c5db199SXin Li    def _is_applicable(self, host):
190*9c5db199SXin Li        if host.is_containerized_servod():
191*9c5db199SXin Li            logging.info('Servod is running within a container.')
192*9c5db199SXin Li            return False
193*9c5db199SXin Li        # Do not run for servos under labstations.
194*9c5db199SXin Li        if host.is_labstation():
195*9c5db199SXin Li            logging.info('Servod is running on labstation.')
196*9c5db199SXin Li            return False
197*9c5db199SXin Li        # Only run if the host is in the physical lab.
198*9c5db199SXin Li        return host.is_in_lab()
199*9c5db199SXin Li
200*9c5db199SXin Li    @property
201*9c5db199SXin Li    def description(self):
202*9c5db199SXin Li        return 'Servo board on servo_v3 is present'
203*9c5db199SXin Li
204*9c5db199SXin Li
205*9c5db199SXin Liclass _ServoFwVerifier(hosts.Verifier):
206*9c5db199SXin Li    """Verifier to check is a servo fw is up-to-date."""
207*9c5db199SXin Li
208*9c5db199SXin Li    @timeout_util.TimeoutDecorator(cros_constants.VERIFY_TIMEOUT_SEC)
209*9c5db199SXin Li    def verify(self, host):
210*9c5db199SXin Li        try:
211*9c5db199SXin Li            if servo_updater.any_servo_needs_firmware_update(host):
212*9c5db199SXin Li                raise hosts.AutoservVerifyError(
213*9c5db199SXin Li                        'Some servo requires firmware update')
214*9c5db199SXin Li        except servo_updater.ServoFwVersionMissedError as e:
215*9c5db199SXin Li            # Do not fail as it will trigger re-flash fw on the servo
216*9c5db199SXin Li            logging.info(
217*9c5db199SXin Li                    'Issue with detect new version of firmware for servo.'
218*9c5db199SXin Li                    ' Please file a bug agains Fleet Automation team (go/fleet-bug)'
219*9c5db199SXin Li            )
220*9c5db199SXin Li
221*9c5db199SXin Li    def _is_applicable(self, host):
222*9c5db199SXin Li        if host.is_containerized_servod():
223*9c5db199SXin Li            logging.info('Servod is running within a container.')
224*9c5db199SXin Li            return True
225*9c5db199SXin Li        # Run only for servos under labstations.
226*9c5db199SXin Li        if not host.is_labstation():
227*9c5db199SXin Li            logging.info('Not supported for servo_v3.')
228*9c5db199SXin Li            return False
229*9c5db199SXin Li        # Only run if the host is in the physical lab.
230*9c5db199SXin Li        return host.is_in_lab()
231*9c5db199SXin Li
232*9c5db199SXin Li    @property
233*9c5db199SXin Li    def description(self):
234*9c5db199SXin Li        return 'Servo fw is up-to-date'
235*9c5db199SXin Li
236*9c5db199SXin Li
237*9c5db199SXin Liclass _ConfigVerifier(hosts.Verifier):
238*9c5db199SXin Li    """
239*9c5db199SXin Li    Base verifier for the servo config file verifiers.
240*9c5db199SXin Li    """
241*9c5db199SXin Li
242*9c5db199SXin Li    CONFIG_FILE = '/var/lib/servod/config'
243*9c5db199SXin Li    ATTR = ''
244*9c5db199SXin Li
245*9c5db199SXin Li    @staticmethod
246*9c5db199SXin Li    def _get_config_val(host, config_file, attr):
247*9c5db199SXin Li        """
248*9c5db199SXin Li        Get the `attr` for `host` from `config_file`.
249*9c5db199SXin Li
250*9c5db199SXin Li        @param host         Host to be checked for `config_file`.
251*9c5db199SXin Li        @param config_file  Path to the config file to be tested.
252*9c5db199SXin Li        @param attr         Attribute to get from config file.
253*9c5db199SXin Li
254*9c5db199SXin Li        @return The attr val as set in the config file, or `None` if
255*9c5db199SXin Li                the file was absent.
256*9c5db199SXin Li        """
257*9c5db199SXin Li        getboard = ('CONFIG=%s ; [ -f $CONFIG ] && '
258*9c5db199SXin Li                    '. $CONFIG && echo $%s' % (config_file, attr))
259*9c5db199SXin Li        attr_val = host.run(getboard, ignore_status=True).stdout
260*9c5db199SXin Li        return attr_val.strip('\n') if attr_val else None
261*9c5db199SXin Li
262*9c5db199SXin Li    @staticmethod
263*9c5db199SXin Li    def _validate_attr(host, val, expected_val, attr, config_file):
264*9c5db199SXin Li        """
265*9c5db199SXin Li        Check that the attr setting is valid for the host.
266*9c5db199SXin Li
267*9c5db199SXin Li        This presupposes that a valid config file was found.  Raise an
268*9c5db199SXin Li        execption if:
269*9c5db199SXin Li          * There was no attr setting from the file (i.e. the setting
270*9c5db199SXin Li            is an empty string), or
271*9c5db199SXin Li          * The attr setting is valid, the attr is known,
272*9c5db199SXin Li            and the setting doesn't match the DUT.
273*9c5db199SXin Li
274*9c5db199SXin Li        @param host         Host to be checked for `config_file`.
275*9c5db199SXin Li        @param val          Value to be tested.
276*9c5db199SXin Li        @param expected_val Expected value.
277*9c5db199SXin Li        @param attr         Attribute we're validating.
278*9c5db199SXin Li        @param config_file  Path to the config file to be tested.
279*9c5db199SXin Li        """
280*9c5db199SXin Li        if not val:
281*9c5db199SXin Li            raise hosts.AutoservVerifyError(
282*9c5db199SXin Li                    'config file %s exists, but %s '
283*9c5db199SXin Li                    'is not set' % (attr, config_file))
284*9c5db199SXin Li        if expected_val is not None and val != expected_val:
285*9c5db199SXin Li            raise hosts.AutoservVerifyError(
286*9c5db199SXin Li                    '%s is %s; it should be %s' % (attr, val, expected_val))
287*9c5db199SXin Li
288*9c5db199SXin Li
289*9c5db199SXin Li    def _get_config(self, host):
290*9c5db199SXin Li        """
291*9c5db199SXin Li        Return the config file to check.
292*9c5db199SXin Li
293*9c5db199SXin Li        @param host     Host object.
294*9c5db199SXin Li
295*9c5db199SXin Li        @return The config file to check.
296*9c5db199SXin Li        """
297*9c5db199SXin Li        return '%s_%d' % (self.CONFIG_FILE, host.servo_port)
298*9c5db199SXin Li
299*9c5db199SXin Li    @property
300*9c5db199SXin Li    def description(self):
301*9c5db199SXin Li        return 'servo %s setting is correct' % self.ATTR
302*9c5db199SXin Li
303*9c5db199SXin Li
304*9c5db199SXin Liclass _SerialConfigVerifier(_ConfigVerifier):
305*9c5db199SXin Li    """
306*9c5db199SXin Li    Verifier for the servo SERIAL configuration.
307*9c5db199SXin Li    """
308*9c5db199SXin Li
309*9c5db199SXin Li    ATTR = 'SERIAL'
310*9c5db199SXin Li
311*9c5db199SXin Li    @timeout_util.TimeoutDecorator(cros_constants.SHORT_VERIFY_TIMEOUT_SEC)
312*9c5db199SXin Li    def verify(self, host):
313*9c5db199SXin Li        """
314*9c5db199SXin Li        Test whether the `host` has a `SERIAL` setting configured.
315*9c5db199SXin Li
316*9c5db199SXin Li        This tests the config file names used by the `servod` upstart
317*9c5db199SXin Li        job for a valid setting of the `SERIAL` variable.  The following
318*9c5db199SXin Li        conditions raise errors:
319*9c5db199SXin Li          * The SERIAL setting doesn't match the DUT's entry in the AFE
320*9c5db199SXin Li            database.
321*9c5db199SXin Li          * There is no config file.
322*9c5db199SXin Li        """
323*9c5db199SXin Li        if not host.is_cros_host():
324*9c5db199SXin Li            return
325*9c5db199SXin Li        # Not all servo hosts will have a servo serial so don't verify if it's
326*9c5db199SXin Li        # not set.
327*9c5db199SXin Li        if host.servo_serial is None:
328*9c5db199SXin Li            return
329*9c5db199SXin Li        config = self._get_config(host)
330*9c5db199SXin Li        serialval = self._get_config_val(host, config, self.ATTR)
331*9c5db199SXin Li        if serialval is None:
332*9c5db199SXin Li            raise hosts.AutoservVerifyError(
333*9c5db199SXin Li                    'Servo serial is unconfigured; should be %s'
334*9c5db199SXin Li                    % host.servo_serial
335*9c5db199SXin Li            )
336*9c5db199SXin Li
337*9c5db199SXin Li        self._validate_attr(host, serialval, host.servo_serial, self.ATTR,
338*9c5db199SXin Li                            config)
339*9c5db199SXin Li
340*9c5db199SXin Li
341*9c5db199SXin Li
342*9c5db199SXin Liclass _BoardConfigVerifier(_ConfigVerifier):
343*9c5db199SXin Li    """
344*9c5db199SXin Li    Verifier for the servo BOARD configuration.
345*9c5db199SXin Li    """
346*9c5db199SXin Li
347*9c5db199SXin Li    ATTR = 'BOARD'
348*9c5db199SXin Li
349*9c5db199SXin Li    @timeout_util.TimeoutDecorator(cros_constants.SHORT_VERIFY_TIMEOUT_SEC)
350*9c5db199SXin Li    def verify(self, host):
351*9c5db199SXin Li        """
352*9c5db199SXin Li        Test whether the `host` has a `BOARD` setting configured.
353*9c5db199SXin Li
354*9c5db199SXin Li        This tests the config file names used by the `servod` upstart
355*9c5db199SXin Li        job for a valid setting of the `BOARD` variable.  The following
356*9c5db199SXin Li        conditions raise errors:
357*9c5db199SXin Li          * A config file exists, but the content contains no setting
358*9c5db199SXin Li            for BOARD.
359*9c5db199SXin Li          * The BOARD setting doesn't match the DUT's entry in the AFE
360*9c5db199SXin Li            database.
361*9c5db199SXin Li          * There is no config file.
362*9c5db199SXin Li        """
363*9c5db199SXin Li        if not host.is_cros_host():
364*9c5db199SXin Li            return
365*9c5db199SXin Li        config = self._get_config(host)
366*9c5db199SXin Li        boardval = self._get_config_val(host, config, self.ATTR)
367*9c5db199SXin Li        if boardval is None:
368*9c5db199SXin Li            msg = 'Servo board is unconfigured'
369*9c5db199SXin Li            if host.servo_board is not None:
370*9c5db199SXin Li                msg += '; should be %s' % host.servo_board
371*9c5db199SXin Li            raise hosts.AutoservVerifyError(msg)
372*9c5db199SXin Li
373*9c5db199SXin Li        self._validate_attr(host, boardval, host.servo_board, self.ATTR,
374*9c5db199SXin Li                            config)
375*9c5db199SXin Li
376*9c5db199SXin Li
377*9c5db199SXin Liclass _ServodJobVerifier(hosts.Verifier):
378*9c5db199SXin Li    """
379*9c5db199SXin Li    Verifier to check that the `servod` upstart job is running.
380*9c5db199SXin Li    """
381*9c5db199SXin Li
382*9c5db199SXin Li    @timeout_util.TimeoutDecorator(cros_constants.SHORT_VERIFY_TIMEOUT_SEC)
383*9c5db199SXin Li    def verify(self, host):
384*9c5db199SXin Li        if not host.is_cros_host():
385*9c5db199SXin Li            return
386*9c5db199SXin Li        status_cmd = 'status servod PORT=%d' % host.servo_port
387*9c5db199SXin Li        job_status = host.run(status_cmd, ignore_status=True).stdout
388*9c5db199SXin Li        if 'start/running' not in job_status:
389*9c5db199SXin Li            raise hosts.AutoservVerifyError(
390*9c5db199SXin Li                    'servod not running on %s port %d' %
391*9c5db199SXin Li                    (host.hostname, host.servo_port))
392*9c5db199SXin Li
393*9c5db199SXin Li    @property
394*9c5db199SXin Li    def description(self):
395*9c5db199SXin Li        return 'servod upstart job is running'
396*9c5db199SXin Li
397*9c5db199SXin Li
398*9c5db199SXin Liclass _ServodEchoVerifier(hosts.Verifier):
399*9c5db199SXin Li    """
400*9c5db199SXin Li    Verifier to check that the `servod` upstart job is responsible.
401*9c5db199SXin Li    """
402*9c5db199SXin Li
403*9c5db199SXin Li    SERVOD_INITIALIZED = 'servodtool instance wait-for-active -p %d --timeout 60'
404*9c5db199SXin Li    SERVOD_RESPONSIVE = 'dut-control -p %d serialname'
405*9c5db199SXin Li
406*9c5db199SXin Li    @timeout_util.TimeoutDecorator(cros_constants.VERIFY_TIMEOUT_SEC)
407*9c5db199SXin Li    def verify(self, host):
408*9c5db199SXin Li        self._verify_servod_initialized(host)
409*9c5db199SXin Li        self._verify_servod_responsive(host)
410*9c5db199SXin Li
411*9c5db199SXin Li    def _verify_servod_initialized(self, host):
412*9c5db199SXin Li        # Verify that servod initialized.
413*9c5db199SXin Li        cmd = self.SERVOD_INITIALIZED % host.servo_port
414*9c5db199SXin Li        res = host.run(cmd, ignore_status=True, timeout=120)
415*9c5db199SXin Li        if res.exit_status != 0:
416*9c5db199SXin Li            raise hosts.AutoservVerifyError(
417*9c5db199SXin Li                    'Servod instance is not initialized')
418*9c5db199SXin Li        logging.debug("Presented instance: %s", res.stdout.strip())
419*9c5db199SXin Li
420*9c5db199SXin Li    def _verify_servod_responsive(self, host):
421*9c5db199SXin Li        # Verify if servod started and process is responsible.
422*9c5db199SXin Li        cmd = self.SERVOD_RESPONSIVE % host.servo_port
423*9c5db199SXin Li        res = host.run(cmd, ignore_status=True, timeout=120)
424*9c5db199SXin Li        if res.exit_status != 0:
425*9c5db199SXin Li            raise hosts.AutoservVerifyError(
426*9c5db199SXin Li                    'Servod is not responsive for dut-control commands')
427*9c5db199SXin Li        logging.info('Servod responsive: %s', res.stdout)
428*9c5db199SXin Li
429*9c5db199SXin Li    @property
430*9c5db199SXin Li    def description(self):
431*9c5db199SXin Li        return 'Servod is running and responsive to dut-control run.'
432*9c5db199SXin Li
433*9c5db199SXin Li
434*9c5db199SXin Liclass _DiskSpaceVerifier(hosts.Verifier):
435*9c5db199SXin Li    """
436*9c5db199SXin Li    Verifier to make sure there is enough disk space left on servohost.
437*9c5db199SXin Li    """
438*9c5db199SXin Li
439*9c5db199SXin Li    @timeout_util.TimeoutDecorator(cros_constants.SHORT_VERIFY_TIMEOUT_SEC)
440*9c5db199SXin Li    def verify(self, host):
441*9c5db199SXin Li        # Check available space of stateful is greater than threshold, in Gib.
442*9c5db199SXin Li        host.check_diskspace('/mnt/stateful_partition', 0.5)
443*9c5db199SXin Li
444*9c5db199SXin Li    @property
445*9c5db199SXin Li    def description(self):
446*9c5db199SXin Li        return 'servohost has enough disk space.'
447*9c5db199SXin Li
448*9c5db199SXin Li    def _is_applicable(self, host):
449*9c5db199SXin Li        if host.is_containerized_servod():
450*9c5db199SXin Li            logging.info('Servod is running within a container.')
451*9c5db199SXin Li            return False
452*9c5db199SXin Li        return True
453*9c5db199SXin Li
454*9c5db199SXin Li
455*9c5db199SXin Liclass _ServodConnectionVerifier(hosts.Verifier):
456*9c5db199SXin Li    """
457*9c5db199SXin Li    Verifier to check that we can connect to servod server.
458*9c5db199SXin Li
459*9c5db199SXin Li    If this verifier failed, it most likely servod was crashed or in a
460*9c5db199SXin Li    crashing loop. For servo_v4 it's usually caused by not able to detect
461*9c5db199SXin Li    CCD or servo_micro.
462*9c5db199SXin Li    """
463*9c5db199SXin Li
464*9c5db199SXin Li    @timeout_util.TimeoutDecorator(cros_constants.VERIFY_TIMEOUT_SEC)
465*9c5db199SXin Li    def verify(self, host):
466*9c5db199SXin Li        host.initialize_servo()
467*9c5db199SXin Li
468*9c5db199SXin Li    @property
469*9c5db199SXin Li    def description(self):
470*9c5db199SXin Li        return 'servod service is taking calls'
471*9c5db199SXin Li
472*9c5db199SXin Li
473*9c5db199SXin Liclass _ServodControlVerifier(hosts.Verifier):
474*9c5db199SXin Li    """
475*9c5db199SXin Li    Verifier to check basic servo control functionality.
476*9c5db199SXin Li
477*9c5db199SXin Li    This tests the connection to the target servod service with a simple
478*9c5db199SXin Li    method call.  As a side-effect, all servo signals are initialized to
479*9c5db199SXin Li    default values.
480*9c5db199SXin Li
481*9c5db199SXin Li    N.B. Initializing servo signals is necessary because the power
482*9c5db199SXin Li    button and lid switch verifiers both test against expected initial
483*9c5db199SXin Li    values.
484*9c5db199SXin Li    """
485*9c5db199SXin Li
486*9c5db199SXin Li    @timeout_util.TimeoutDecorator(cros_constants.VERIFY_TIMEOUT_SEC)
487*9c5db199SXin Li    def verify(self, host):
488*9c5db199SXin Li        try:
489*9c5db199SXin Li            host.initialize_dut_for_servo()
490*9c5db199SXin Li        except Exception as e:
491*9c5db199SXin Li            six.reraise(hosts.AutoservNonCriticalVerifyError,
492*9c5db199SXin Li                        hosts.AutoservNonCriticalVerifyError(e),
493*9c5db199SXin Li                        sys.exc_info()[2])
494*9c5db199SXin Li
495*9c5db199SXin Li    @property
496*9c5db199SXin Li    def description(self):
497*9c5db199SXin Li        return 'Basic servod control is working'
498*9c5db199SXin Li
499*9c5db199SXin Li
500*9c5db199SXin Liclass _Cr50ConsoleVerifier(hosts.Verifier):
501*9c5db199SXin Li    """Verifier to check if cr50 console is present and working.
502*9c5db199SXin Li
503*9c5db199SXin Li    Validating based by running commands and expect they will not fail.
504*9c5db199SXin Li    If any command fail then console is not working as expected.
505*9c5db199SXin Li    """
506*9c5db199SXin Li
507*9c5db199SXin Li    COMMAND_TO_CHECK_CONSOLE = (
508*9c5db199SXin Li            'gsc_ccd_level',
509*9c5db199SXin Li            'cr50_testlab',
510*9c5db199SXin Li            'cr50_ccd_state_flags',
511*9c5db199SXin Li    )
512*9c5db199SXin Li
513*9c5db199SXin Li    @ignore_exception_for_non_cros_host
514*9c5db199SXin Li    @timeout_util.TimeoutDecorator(cros_constants.VERIFY_TIMEOUT_SEC)
515*9c5db199SXin Li    def verify(self, host):
516*9c5db199SXin Li        try:
517*9c5db199SXin Li            for command in self.COMMAND_TO_CHECK_CONSOLE:
518*9c5db199SXin Li                if host.get_servo().has_control(command):
519*9c5db199SXin Li                    # Response of command is not important.
520*9c5db199SXin Li                    host.get_servo().get(command)
521*9c5db199SXin Li        except Exception as e:
522*9c5db199SXin Li            six.reraise(hosts.AutoservNonCriticalVerifyError,
523*9c5db199SXin Li                        hosts.AutoservNonCriticalVerifyError(e),
524*9c5db199SXin Li                        sys.exc_info()[2])
525*9c5db199SXin Li
526*9c5db199SXin Li    def _is_applicable(self, host):
527*9c5db199SXin Li        # Only when DUT is running through ccd or c2d2.
528*9c5db199SXin Li        # TODO(coconutruben): replace with ccd API when available in servo.py
529*9c5db199SXin Li        return host.get_servo() and host.get_servo().main_device_uses_gsc_drv()
530*9c5db199SXin Li
531*9c5db199SXin Li    @property
532*9c5db199SXin Li    def description(self):
533*9c5db199SXin Li        return 'CR50 console is working'
534*9c5db199SXin Li
535*9c5db199SXin Li
536*9c5db199SXin Liclass _CCDTestlabVerifier(hosts.Verifier):
537*9c5db199SXin Li    """
538*9c5db199SXin Li    Verifier to check that ccd testlab is enabled.
539*9c5db199SXin Li
540*9c5db199SXin Li    All DUT connected by ccd has to supported cr50 with enabled testlab
541*9c5db199SXin Li    to allow manipulation by servo. The flag testlab is sticky and will
542*9c5db199SXin Li    stay enabled if was set up. The testlab can be enabled when ccd is
543*9c5db199SXin Li    open. (go/ccd-setup)
544*9c5db199SXin Li    """
545*9c5db199SXin Li    @ignore_exception_for_non_cros_host
546*9c5db199SXin Li    @timeout_util.TimeoutDecorator(cros_constants.VERIFY_TIMEOUT_SEC)
547*9c5db199SXin Li    def verify(self, host):
548*9c5db199SXin Li        if not host.get_servo().has_control('cr50_testlab'):
549*9c5db199SXin Li            raise hosts.AutoservVerifyError(
550*9c5db199SXin Li                    'gsc has to be supported when use servo with '
551*9c5db199SXin Li                    'ccd_*/type-c connection')
552*9c5db199SXin Li
553*9c5db199SXin Li        status = host.get_servo().get('cr50_testlab')
554*9c5db199SXin Li        # check by 'on' to fail when get unexpected value
555*9c5db199SXin Li        if status == 'on':
556*9c5db199SXin Li            # If servo uses cr50 to control the dut, open ccd so repair actions
557*9c5db199SXin Li            # that reset the dut will work (cr50_reboot, cold_reset, warm_reset)
558*9c5db199SXin Li            if host.get_servo().main_device_uses_gsc_drv():
559*9c5db199SXin Li                host.get_servo().set_nocheck('cr50_testlab', 'open')
560*9c5db199SXin Li            # ccd testlab enabled
561*9c5db199SXin Li            return
562*9c5db199SXin Li        raise hosts.AutoservNonCriticalVerifyError(
563*9c5db199SXin Li            'The ccd testlab is disabled; DUT requires manual work '
564*9c5db199SXin Li            'to enable it (go/ccd-setup).')
565*9c5db199SXin Li
566*9c5db199SXin Li    def _is_applicable(self, host):
567*9c5db199SXin Li        # Only when DUT is running through ccd.
568*9c5db199SXin Li        # TODO(coconutruben): replace with ccd API when available in servo.py
569*9c5db199SXin Li        return host.get_servo() and host.get_servo().main_device_is_ccd()
570*9c5db199SXin Li
571*9c5db199SXin Li    @property
572*9c5db199SXin Li    def description(self):
573*9c5db199SXin Li        return 'ccd testlab enabled'
574*9c5db199SXin Li
575*9c5db199SXin Liclass _CCDPowerDeliveryVerifier(hosts.Verifier):
576*9c5db199SXin Li    """Verifier to check and reset servo_v4_role for servos that support
577*9c5db199SXin Li    power delivery feature(a.k.a power pass through).
578*9c5db199SXin Li
579*9c5db199SXin Li    There are currently two position of servo_v4_role, src and snk:
580*9c5db199SXin Li    src --  servo in power delivery mode and passes power to the DUT.
581*9c5db199SXin Li    snk --  servo in normal mode and not passes power to DUT.
582*9c5db199SXin Li    We want to ensure that servo_v4_role is set to src.
583*9c5db199SXin Li    """
584*9c5db199SXin Li    @timeout_util.TimeoutDecorator(cros_constants.VERIFY_TIMEOUT_SEC)
585*9c5db199SXin Li    def verify(self, host):
586*9c5db199SXin Li        if host.get_servo():
587*9c5db199SXin Li            self._printControl(host.get_servo(), 'ppdut5_mv')
588*9c5db199SXin Li            self._printControl(host.get_servo(), 'ppchg5_mv')
589*9c5db199SXin Li        if host.get_servo().get('servo_pd_role') == 'snk':
590*9c5db199SXin Li            raise hosts.AutoservNonCriticalVerifyError(
591*9c5db199SXin Li                    'Power delivery not in src role.')
592*9c5db199SXin Li
593*9c5db199SXin Li    def _printControl(self, servo, control):
594*9c5db199SXin Li        if servo.has_control(control):
595*9c5db199SXin Li            logging.info("%s: %s", control, servo.get(control))
596*9c5db199SXin Li
597*9c5db199SXin Li    def _is_applicable(self, host):
598*9c5db199SXin Li        return (host.is_in_lab() and
599*9c5db199SXin Li                host.get_servo().supports_built_in_pd_control())
600*9c5db199SXin Li
601*9c5db199SXin Li    @property
602*9c5db199SXin Li    def description(self):
603*9c5db199SXin Li        return 'ensure applicable servo is in "src" mode for power delivery'
604*9c5db199SXin Li
605*9c5db199SXin Li
606*9c5db199SXin Liclass _BaseDUTConnectionVerifier(hosts.Verifier):
607*9c5db199SXin Li    """Verifier to check connection between DUT and servo."""
608*9c5db199SXin Li
609*9c5db199SXin Li    # Bus voltage on ppdut5. Value can be:
610*9c5db199SXin Li    # - less than 500 - DUT is likely not connected
611*9c5db199SXin Li    # - between 500 and 4000 - unexpected value
612*9c5db199SXin Li    # - more than 4000 - DUT is likely connected
613*9c5db199SXin Li    MAX_PPDUT5_MV_WHEN_NOT_CONNECTED = 500
614*9c5db199SXin Li    MIN_PPDUT5_MV_WHEN_CONNECTED = 4000
615*9c5db199SXin Li
616*9c5db199SXin Li    def _is_usb_hub_connected(self, host):
617*9c5db199SXin Li        """Checking bus voltage on ppdut5.
618*9c5db199SXin Li
619*9c5db199SXin Li        Supported only on servo_v4 boards.
620*9c5db199SXin Li        If voltage value is lower than 500 then device is not connected.
621*9c5db199SXin Li        When value higher 4000 means the device is connected. If value
622*9c5db199SXin Li        between 500 and 4000 is not expected and will be marked as connected
623*9c5db199SXin Li        and collected information which DUT has this exception.
624*9c5db199SXin Li
625*9c5db199SXin Li        @returns: bool
626*9c5db199SXin Li        """
627*9c5db199SXin Li        logging.debug('Started check by ppdut5_mv:on')
628*9c5db199SXin Li        try:
629*9c5db199SXin Li            val = host.get_servo().get('ppdut5_mv')
630*9c5db199SXin Li            logging.info('ppdut5_mv=%s', val)
631*9c5db199SXin Li            if val < self.MAX_PPDUT5_MV_WHEN_NOT_CONNECTED:
632*9c5db199SXin Li                # servo is not connected to the DUT
633*9c5db199SXin Li                return False
634*9c5db199SXin Li            if val < self.MIN_PPDUT5_MV_WHEN_CONNECTED:
635*9c5db199SXin Li                # is unexpected value.
636*9c5db199SXin Li                # collecting metrics to look case by case
637*9c5db199SXin Li                # TODO(otabek) for analysis b:163845694
638*9c5db199SXin Li                data = host._get_host_metrics_data()
639*9c5db199SXin Li                metrics.Counter('chromeos/autotest/repair/ppdut5_mv_case'
640*9c5db199SXin Li                                ).increment(fields=data)
641*9c5db199SXin Li            # else:
642*9c5db199SXin Li            # servo is physical connected to the DUT
643*9c5db199SXin Li        except Exception as e:
644*9c5db199SXin Li            logging.debug('(Not critical) %s', e)
645*9c5db199SXin Li        return True
646*9c5db199SXin Li
647*9c5db199SXin Li    def _is_ribbon_cable_connected(self, host):
648*9c5db199SXin Li        """Check if ribbon cable is connected to the DUT.
649*9c5db199SXin Li
650*9c5db199SXin Li        The servo_micro/flex - can be checked by `cold_reset` signal.
651*9c5db199SXin Li        When `cold_reset` is `on` it commonly indicates that the DUT
652*9c5db199SXin Li        is disconnected. To avoid mistake of real signal we try
653*9c5db199SXin Li        switch it off and if is cannot then servo is not connected.
654*9c5db199SXin Li
655*9c5db199SXin Li        @returns: bool
656*9c5db199SXin Li        """
657*9c5db199SXin Li        logging.debug('Started check by cold_reset:on')
658*9c5db199SXin Li        try:
659*9c5db199SXin Li            val = host.get_servo().get('cold_reset')
660*9c5db199SXin Li            logging.info('cold_reset=%s', val)
661*9c5db199SXin Li            if val == 'on':
662*9c5db199SXin Li                # If cold_reset has is on can be right signal
663*9c5db199SXin Li                # or caused by missing connection between servo_micro and DUT.
664*9c5db199SXin Li                # if we can switch it to the off then it was signal.
665*9c5db199SXin Li                host.get_servo().set('cold_reset', 'off')
666*9c5db199SXin Li        except error.TestFail:
667*9c5db199SXin Li            logging.debug('Ribbon cable is not connected to the DUT.')
668*9c5db199SXin Li            return False
669*9c5db199SXin Li        except Exception as e:
670*9c5db199SXin Li            logging.debug('(Not critical) %s', e)
671*9c5db199SXin Li        return True
672*9c5db199SXin Li
673*9c5db199SXin Li    def _is_dut_power_on(self, host):
674*9c5db199SXin Li        # DUT is running in normal state.
675*9c5db199SXin Li        # if EC not supported by board then we expect error
676*9c5db199SXin Li        try:
677*9c5db199SXin Li            return host.get_servo().get('ec_system_powerstate') == 'S0'
678*9c5db199SXin Li        except Exception as e:
679*9c5db199SXin Li            logging.debug('(Not critical) %s', e)
680*9c5db199SXin Li        return False
681*9c5db199SXin Li
682*9c5db199SXin Li    def _is_servo_v4_type_a(self, host):
683*9c5db199SXin Li        return host.is_labstation() and host.get_servo().is_servo_v4_type_a()
684*9c5db199SXin Li
685*9c5db199SXin Li    def _is_servo_v4_type_c(self, host):
686*9c5db199SXin Li        return host.is_labstation() and host.get_servo().is_servo_v4_type_c()
687*9c5db199SXin Li
688*9c5db199SXin Li    def _is_servo_v3(self, host):
689*9c5db199SXin Li        return not host.is_labstation()
690*9c5db199SXin Li
691*9c5db199SXin Li
692*9c5db199SXin Liclass _DUTConnectionVerifier(_BaseDUTConnectionVerifier):
693*9c5db199SXin Li    """Verifier to check connection Servo to the DUT.
694*9c5db199SXin Li
695*9c5db199SXin Li    Servo_v4 type-a connected to the DUT by:
696*9c5db199SXin Li        1) servo_micro - checked by `cold_reset`.
697*9c5db199SXin Li    Servo_v4 type-c connected to the DUT by:
698*9c5db199SXin Li        1) ccd - checked by ppdut5_mv.
699*9c5db199SXin Li    Servo_v3 connected to the DUT by:
700*9c5db199SXin Li        1) legacy servo header - can be checked by `cold_reset`.
701*9c5db199SXin Li    """
702*9c5db199SXin Li
703*9c5db199SXin Li    @ignore_exception_for_non_cros_host
704*9c5db199SXin Li    @timeout_util.TimeoutDecorator(cros_constants.VERIFY_TIMEOUT_SEC)
705*9c5db199SXin Li    def verify(self, host):
706*9c5db199SXin Li        if self._is_servo_v4_type_a(host):
707*9c5db199SXin Li            if not self._is_ribbon_cable_connected(host):
708*9c5db199SXin Li                raise hosts.AutoservVerifyError(
709*9c5db199SXin Li                        'Servo_micro is likely not connected to the DUT.')
710*9c5db199SXin Li        elif self._is_servo_v4_type_c(host):
711*9c5db199SXin Li            if (host.get_servo().supports_built_in_pd_control()
712*9c5db199SXin Li                        and not self._is_usb_hub_connected(host)):
713*9c5db199SXin Li                raise hosts.AutoservVerifyError(
714*9c5db199SXin Li                        'Servo_v4 is likely not connected to the DUT.')
715*9c5db199SXin Li        elif self._is_servo_v3(host):
716*9c5db199SXin Li            if not self._is_ribbon_cable_connected(host):
717*9c5db199SXin Li                raise hosts.AutoservVerifyError(
718*9c5db199SXin Li                        'Servo_v3 is likely not connected to the DUT.')
719*9c5db199SXin Li
720*9c5db199SXin Li    @property
721*9c5db199SXin Li    def description(self):
722*9c5db199SXin Li        return 'Ensure the Servo connected to the DUT.'
723*9c5db199SXin Li
724*9c5db199SXin Li
725*9c5db199SXin Liclass _ServoHubConnectionVerifier(_BaseDUTConnectionVerifier):
726*9c5db199SXin Li    """Verifier to check connection ServoHub to DUT.
727*9c5db199SXin Li
728*9c5db199SXin Li    Servo_v4 type-a connected to the DUT by:
729*9c5db199SXin Li        1) USB hub - checked by ppdut5_mv.
730*9c5db199SXin Li    """
731*9c5db199SXin Li
732*9c5db199SXin Li    @ignore_exception_for_non_cros_host
733*9c5db199SXin Li    @timeout_util.TimeoutDecorator(cros_constants.VERIFY_TIMEOUT_SEC)
734*9c5db199SXin Li    def verify(self, host):
735*9c5db199SXin Li        if self._is_servo_v4_type_a(host):
736*9c5db199SXin Li            if (self._is_dut_power_on(host)
737*9c5db199SXin Li                        and not self._is_usb_hub_connected(host)):
738*9c5db199SXin Li                raise hosts.AutoservVerifyError(
739*9c5db199SXin Li                        'Servo USB hub is likely not connected to the DUT.')
740*9c5db199SXin Li
741*9c5db199SXin Li    def _is_applicable(self, host):
742*9c5db199SXin Li        if host.is_ec_supported():
743*9c5db199SXin Li            return True
744*9c5db199SXin Li        logging.info('Host does not support EC.')
745*9c5db199SXin Li        return False
746*9c5db199SXin Li
747*9c5db199SXin Li    @property
748*9c5db199SXin Li    def description(self):
749*9c5db199SXin Li        return 'Ensure the Servo HUB connected to the DUT.'
750*9c5db199SXin Li
751*9c5db199SXin Li
752*9c5db199SXin Liclass _BaseCr50SBUVerifier(_BaseDUTConnectionVerifier):
753*9c5db199SXin Li    """Check servod issue related to SBU voltage."""
754*9c5db199SXin Li
755*9c5db199SXin Li    # Min SBU voltage to detect usb-device
756*9c5db199SXin Li    SBU_THRESHOLD = 2500.0
757*9c5db199SXin Li    # How many times collect SBU voltage to calc AVG value.
758*9c5db199SXin Li    _TOTAL_CHECK_SBU_VOLTAGE = 10
759*9c5db199SXin Li
760*9c5db199SXin Li    def _is_applicable(self, host):
761*9c5db199SXin Li        if host.is_localhost():
762*9c5db199SXin Li            logging.info('Target servo is not in a lab,'
763*9c5db199SXin Li                         ' action is not applicable.')
764*9c5db199SXin Li            return False
765*9c5db199SXin Li        if not self._is_servo_v4_type_c(host):
766*9c5db199SXin Li            logging.info('Check support only servo-v4 (type-c),'
767*9c5db199SXin Li                         ' action is not applicable.')
768*9c5db199SXin Li            return False
769*9c5db199SXin Li        return True
770*9c5db199SXin Li
771*9c5db199SXin Li    def _is_sbu_voltage_issue(self, host):
772*9c5db199SXin Li        """Check if servo does not detected by SBU voltage issue."""
773*9c5db199SXin Li        command = 'dut_sbu_voltage_float_fault'
774*9c5db199SXin Li        if host.get_servo().has_control(command):
775*9c5db199SXin Li            if host.get_servo().get(command) == 'on':
776*9c5db199SXin Li                return True
777*9c5db199SXin Li        return False
778*9c5db199SXin Li
779*9c5db199SXin Li    def _get_max_sbu_value(self, host):
780*9c5db199SXin Li        """Get average voltage on SBU lines."""
781*9c5db199SXin Li        servo = host.get_servo()
782*9c5db199SXin Li        if not servo.has_control('servo_dut_sbu1_mv'):
783*9c5db199SXin Li            return -1
784*9c5db199SXin Li        s1 = 0
785*9c5db199SXin Li        s2 = 0
786*9c5db199SXin Li        for i in range(self._TOTAL_CHECK_SBU_VOLTAGE):
787*9c5db199SXin Li            try:
788*9c5db199SXin Li                sbu1 = int(servo.get('servo_dut_sbu1_mv'))
789*9c5db199SXin Li                sbu2 = int(servo.get('servo_dut_sbu2_mv'))
790*9c5db199SXin Li                logging.debug('Attempt:%2d, sbu1 %4d sbu2 %4d', i, sbu1, sbu2)
791*9c5db199SXin Li                s1 += sbu1
792*9c5db199SXin Li                s2 += sbu2
793*9c5db199SXin Li            except error.TestFail as e:
794*9c5db199SXin Li                # This is a nice to have but if reading this fails, it
795*9c5db199SXin Li                # shouldn't interfere with the test.
796*9c5db199SXin Li                logging.exception(e)
797*9c5db199SXin Li        logging.debug('Total:  sbu1 %4d sbu2 %4d', s1, s2)
798*9c5db199SXin Li        # Use float to get values with changes
799*9c5db199SXin Li        s1 = s1 / float(self._TOTAL_CHECK_SBU_VOLTAGE)
800*9c5db199SXin Li        s2 = s2 / float(self._TOTAL_CHECK_SBU_VOLTAGE)
801*9c5db199SXin Li        logging.debug('Avg: sbu1 %7.2f sbu2 %7.2f', s1, s2)
802*9c5db199SXin Li        max_sbu = max(s1, s2)
803*9c5db199SXin Li        logging.info('Max sbu: %7.2f', max_sbu)
804*9c5db199SXin Li        return max_sbu
805*9c5db199SXin Li
806*9c5db199SXin Li
807*9c5db199SXin Liclass _Cr50OffVerifier(_BaseCr50SBUVerifier):
808*9c5db199SXin Li    """Check if CR50 is in deep sleep and fail to detected.
809*9c5db199SXin Li
810*9c5db199SXin Li    If SBU voltage is higher threshold but still cannot be detected
811*9c5db199SXin Li    as usb device then probably CR50 is in deep sleep.
812*9c5db199SXin Li    Threshold is 2500 mV on any SBU lines.
813*9c5db199SXin Li    """
814*9c5db199SXin Li
815*9c5db199SXin Li    @ignore_exception_for_non_cros_host
816*9c5db199SXin Li    @timeout_util.TimeoutDecorator(cros_constants.VERIFY_TIMEOUT_SEC)
817*9c5db199SXin Li    def verify(self, host):
818*9c5db199SXin Li        if self._is_sbu_voltage_issue(host):
819*9c5db199SXin Li            if self._get_max_sbu_value(host) > self.SBU_THRESHOLD:
820*9c5db199SXin Li                raise hosts.AutoservVerifyError(
821*9c5db199SXin Li                        'CR50 voltage detected but usb device not enumerated')
822*9c5db199SXin Li
823*9c5db199SXin Li    @property
824*9c5db199SXin Li    def description(self):
825*9c5db199SXin Li        return 'CR50 voltage detected but not enumerated.'
826*9c5db199SXin Li
827*9c5db199SXin Li
828*9c5db199SXin Liclass _Cr50LowSBUVerifier(_BaseCr50SBUVerifier):
829*9c5db199SXin Li    """Check if servod fail to detect CR50 due low voltage.
830*9c5db199SXin Li
831*9c5db199SXin Li    CR50 cannot be enumerated as SBU voltage line lower then
832*9c5db199SXin Li    threshold.
833*9c5db199SXin Li    Threshold is 2500 mV on any SBU lines.
834*9c5db199SXin Li    """
835*9c5db199SXin Li
836*9c5db199SXin Li    @ignore_exception_for_non_cros_host
837*9c5db199SXin Li    @timeout_util.TimeoutDecorator(cros_constants.VERIFY_TIMEOUT_SEC)
838*9c5db199SXin Li    def verify(self, host):
839*9c5db199SXin Li        if self._is_sbu_voltage_issue(host):
840*9c5db199SXin Li            v = self._get_max_sbu_value(host)
841*9c5db199SXin Li            if v > 1 and v <= self.SBU_THRESHOLD:
842*9c5db199SXin Li                raise hosts.AutoservVerifyError(
843*9c5db199SXin Li                        'Cr50 is not detected due to SBU voltages'
844*9c5db199SXin Li                        ' being below %dmV' % self.SBU_THRESHOLD)
845*9c5db199SXin Li
846*9c5db199SXin Li    @property
847*9c5db199SXin Li    def description(self):
848*9c5db199SXin Li        return 'Cr50 not detected as both SBU voltages are below threshold.'
849*9c5db199SXin Li
850*9c5db199SXin Li
851*9c5db199SXin Liclass _TopologyVerifier(hosts.Verifier):
852*9c5db199SXin Li    """Verifier that all servo component is presented."""
853*9c5db199SXin Li
854*9c5db199SXin Li    @ignore_exception_for_non_cros_host
855*9c5db199SXin Li    @timeout_util.TimeoutDecorator(cros_constants.VERIFY_TIMEOUT_SEC)
856*9c5db199SXin Li    def verify(self, host):
857*9c5db199SXin Li        topology = host.get_topology()
858*9c5db199SXin Li        topology.read(host.get_dut_host_info())
859*9c5db199SXin Li        try:
860*9c5db199SXin Li            # Linux takes 1 second to detect and enumerate USB device since
861*9c5db199SXin Li            # 2010 year. We take 10 seconds to be sure as old standard was
862*9c5db199SXin Li            # 5 seconds.
863*9c5db199SXin Li            time.sleep(10)
864*9c5db199SXin Li            topology.validate(raise_error=True,
865*9c5db199SXin Li                              dual_set=host.is_dual_setup(),
866*9c5db199SXin Li                              compare=True)
867*9c5db199SXin Li        except servo_topology.ServoTopologyError as e:
868*9c5db199SXin Li            six.reraise(hosts.AutoservVerifyError,
869*9c5db199SXin Li                        hosts.AutoservVerifyError(e),
870*9c5db199SXin Li                        sys.exc_info()[2])
871*9c5db199SXin Li
872*9c5db199SXin Li    def _is_applicable(self, host):
873*9c5db199SXin Li        if host.is_localhost():
874*9c5db199SXin Li            logging.info('Target servo is not in a lab,'
875*9c5db199SXin Li                         ' action is not applicable.')
876*9c5db199SXin Li            return False
877*9c5db199SXin Li        if not host.is_servo_topology_supported():
878*9c5db199SXin Li            logging.info('Target servo-topology is not supported,'
879*9c5db199SXin Li                         ' action is not applicable.')
880*9c5db199SXin Li            return False
881*9c5db199SXin Li        return True
882*9c5db199SXin Li
883*9c5db199SXin Li    @property
884*9c5db199SXin Li    def description(self):
885*9c5db199SXin Li        return 'Ensure all Servo component present.'
886*9c5db199SXin Li
887*9c5db199SXin Li
888*9c5db199SXin Liclass _PowerButtonVerifier(hosts.Verifier):
889*9c5db199SXin Li    """
890*9c5db199SXin Li    Verifier to check the `pwr_button` signal.
891*9c5db199SXin Li
892*9c5db199SXin Li    Tests that the `pwr_button` signal shows the power button has been
893*9c5db199SXin Li    released.  When `pwr_button` is stuck at `press`, it commonly
894*9c5db199SXin Li    indicates that the ribbon cable is disconnected.
895*9c5db199SXin Li    """
896*9c5db199SXin Li    # TODO (crbug.com/646593) - Remove list below once servo has been updated
897*9c5db199SXin Li    # with a fake pwr_button signal.
898*9c5db199SXin Li    _BOARDS_WO_PWR_BUTTON = ['arkham', 'gale', 'mistral', 'storm', 'whirlwind']
899*9c5db199SXin Li
900*9c5db199SXin Li    @ignore_exception_for_non_cros_host
901*9c5db199SXin Li    @timeout_util.TimeoutDecorator(cros_constants.SHORT_VERIFY_TIMEOUT_SEC)
902*9c5db199SXin Li    def verify(self, host):
903*9c5db199SXin Li        if host.servo_board in self._BOARDS_WO_PWR_BUTTON:
904*9c5db199SXin Li            return
905*9c5db199SXin Li        try:
906*9c5db199SXin Li            button = host.get_servo().get('pwr_button')
907*9c5db199SXin Li        except Exception as e:
908*9c5db199SXin Li            six.reraise(hosts.AutoservNonCriticalVerifyError,
909*9c5db199SXin Li                        hosts.AutoservNonCriticalVerifyError(e),
910*9c5db199SXin Li                        sys.exc_info()[2])
911*9c5db199SXin Li
912*9c5db199SXin Li        if button != 'release':
913*9c5db199SXin Li            raise hosts.AutoservNonCriticalVerifyError(
914*9c5db199SXin Li                'Check ribbon cable: \'pwr_button\' is stuck')
915*9c5db199SXin Li
916*9c5db199SXin Li    def _is_applicable(self, host):
917*9c5db199SXin Li        return (host.get_servo() and host.get_servo().main_device_is_flex())
918*9c5db199SXin Li
919*9c5db199SXin Li    @property
920*9c5db199SXin Li    def description(self):
921*9c5db199SXin Li        return 'pwr_button control is normal'
922*9c5db199SXin Li
923*9c5db199SXin Li
924*9c5db199SXin Liclass _BatteryVerifier(hosts.Verifier):
925*9c5db199SXin Li    """Collect battery info for analysis."""
926*9c5db199SXin Li
927*9c5db199SXin Li    @ignore_exception_for_non_cros_host
928*9c5db199SXin Li    @timeout_util.TimeoutDecorator(cros_constants.VERIFY_TIMEOUT_SEC)
929*9c5db199SXin Li    def verify(self, host):
930*9c5db199SXin Li        try:
931*9c5db199SXin Li            servo = host.get_servo()
932*9c5db199SXin Li            charging = False
933*9c5db199SXin Li            if servo.has_control('battery_is_charging'):
934*9c5db199SXin Li                charging = servo.get('battery_is_charging')
935*9c5db199SXin Li            level = -1
936*9c5db199SXin Li            if servo.has_control('battery_charge_percent'):
937*9c5db199SXin Li                level = servo.get('battery_charge_percent')
938*9c5db199SXin Li            design_mah = servo.get('battery_full_design_mah')
939*9c5db199SXin Li            charge_mah = servo.get('battery_full_charge_mah')
940*9c5db199SXin Li            logging.info('Charging: %s', charging)
941*9c5db199SXin Li            logging.info('Percentage: %s', level)
942*9c5db199SXin Li            logging.info('Full charge max: %s', charge_mah)
943*9c5db199SXin Li            logging.info('Full design max: %s', design_mah)
944*9c5db199SXin Li            # based on analysis of ratio we can find out what is
945*9c5db199SXin Li            # the level when we can say that battery is dead
946*9c5db199SXin Li            ratio = int(math.floor(charge_mah / design_mah * 100.0))
947*9c5db199SXin Li            logging.info('Ratio: %s', ratio)
948*9c5db199SXin Li            data = {
949*9c5db199SXin Li                    'board': host.servo_board or 'unknown',
950*9c5db199SXin Li                    'model': host.servo_model or 'unknown',
951*9c5db199SXin Li                    'ratio': ratio
952*9c5db199SXin Li            }
953*9c5db199SXin Li            metrics.Counter('chromeos/autotest/battery/ratio').increment(
954*9c5db199SXin Li                    fields=data)
955*9c5db199SXin Li        except Exception as e:
956*9c5db199SXin Li            # Keeping it with info level because we do not expect it.
957*9c5db199SXin Li            logging.info('(Not critical) %s', e)
958*9c5db199SXin Li
959*9c5db199SXin Li    def _is_applicable(self, host):
960*9c5db199SXin Li        if not host.is_ec_supported():
961*9c5db199SXin Li            logging.info('The board not support EC')
962*9c5db199SXin Li            return False
963*9c5db199SXin Li        dut_info = host.get_dut_host_info()
964*9c5db199SXin Li        if dut_info:
965*9c5db199SXin Li            host_info = host.get_dut_host_info()
966*9c5db199SXin Li            if host_info.get_label_value('power') != 'battery':
967*9c5db199SXin Li                logging.info('The board does not have battery')
968*9c5db199SXin Li                return False
969*9c5db199SXin Li        servo = host.get_servo()
970*9c5db199SXin Li        if (not servo.has_control('battery_full_design_mah')
971*9c5db199SXin Li                    or not servo.has_control('battery_full_charge_mah')):
972*9c5db199SXin Li            logging.info('The board is not supported battery controls...')
973*9c5db199SXin Li            return False
974*9c5db199SXin Li        return True
975*9c5db199SXin Li
976*9c5db199SXin Li    @property
977*9c5db199SXin Li    def description(self):
978*9c5db199SXin Li        return 'Logs battery levels'
979*9c5db199SXin Li
980*9c5db199SXin Li
981*9c5db199SXin Liclass _LidVerifier(hosts.Verifier):
982*9c5db199SXin Li    """
983*9c5db199SXin Li    Verifier to check the `lid_open` signal.
984*9c5db199SXin Li    """
985*9c5db199SXin Li
986*9c5db199SXin Li    @ignore_exception_for_non_cros_host
987*9c5db199SXin Li    @timeout_util.TimeoutDecorator(cros_constants.SHORT_VERIFY_TIMEOUT_SEC)
988*9c5db199SXin Li    def verify(self, host):
989*9c5db199SXin Li        try:
990*9c5db199SXin Li            lid_open = host.get_servo().get('lid_open')
991*9c5db199SXin Li        except Exception as e:
992*9c5db199SXin Li            six.reraise(hosts.AutoservNonCriticalVerifyError,
993*9c5db199SXin Li                        hosts.AutoservNonCriticalVerifyError(e),
994*9c5db199SXin Li                        sys.exc_info()[2])
995*9c5db199SXin Li
996*9c5db199SXin Li        if lid_open != 'yes' and lid_open != 'not_applicable':
997*9c5db199SXin Li            raise hosts.AutoservNonCriticalVerifyError(
998*9c5db199SXin Li                'Check lid switch: lid_open is %s' % lid_open)
999*9c5db199SXin Li
1000*9c5db199SXin Li    @property
1001*9c5db199SXin Li    def description(self):
1002*9c5db199SXin Li        return 'lid_open control is normal'
1003*9c5db199SXin Li
1004*9c5db199SXin Li
1005*9c5db199SXin Liclass ECConsoleVerifier(hosts.Verifier):
1006*9c5db199SXin Li    """
1007*9c5db199SXin Li    Verifier response from the EC console.
1008*9c5db199SXin Li    """
1009*9c5db199SXin Li
1010*9c5db199SXin Li    COMMAND_TO_CHECK_CONSOLE = (
1011*9c5db199SXin Li            'ec_system_powerstate',
1012*9c5db199SXin Li            'ec_board',
1013*9c5db199SXin Li    )
1014*9c5db199SXin Li
1015*9c5db199SXin Li    @ignore_exception_for_non_cros_host
1016*9c5db199SXin Li    @timeout_util.TimeoutDecorator(cros_constants.VERIFY_TIMEOUT_SEC)
1017*9c5db199SXin Li    def verify(self, host):
1018*9c5db199SXin Li        if not host.is_ec_supported():
1019*9c5db199SXin Li            logging.info('The board does not support EC')
1020*9c5db199SXin Li            return
1021*9c5db199SXin Li
1022*9c5db199SXin Li        for command in self.COMMAND_TO_CHECK_CONSOLE:
1023*9c5db199SXin Li            if host.get_servo().has_control(command):
1024*9c5db199SXin Li                try:
1025*9c5db199SXin Li                    # Response of command is not important.
1026*9c5db199SXin Li                    r = host.get_servo().get(command)
1027*9c5db199SXin Li                    logging.debug('Result %s:%s', command, r)
1028*9c5db199SXin Li                    # Exiting as we confirmed that console is working.
1029*9c5db199SXin Li                    return
1030*9c5db199SXin Li                except Exception as e:
1031*9c5db199SXin Li                    logging.error('Fail to read %s control. Error: %s',
1032*9c5db199SXin Li                                  command, e)
1033*9c5db199SXin Li        # If we reached this point then no command succeeded.
1034*9c5db199SXin Li        raise hosts.AutoservNonCriticalVerifyError(
1035*9c5db199SXin Li                'EC console is not responding; '
1036*9c5db199SXin Li                'may be caused of broken EC firmware')
1037*9c5db199SXin Li
1038*9c5db199SXin Li    @property
1039*9c5db199SXin Li    def description(self):
1040*9c5db199SXin Li        return 'Check EC console'
1041*9c5db199SXin Li
1042*9c5db199SXin Li
1043*9c5db199SXin Liclass ServodDutControllerMissingVerifier(hosts.Verifier):
1044*9c5db199SXin Li    """Verifier to check whether the servod dut controller is missing or not.
1045*9c5db199SXin Li
1046*9c5db199SXin Li    When servod is initializing, it checks if DUT controller is
1047*9c5db199SXin Li    missing. If yes,then it sets 'dut_controller_missing_fault' to
1048*9c5db199SXin Li    'on', otherwise, to 'off'. Missing controller means servo
1049*9c5db199SXin Li    component connected to the DUT is missing, or is not responsive.
1050*9c5db199SXin Li    """
1051*9c5db199SXin Li
1052*9c5db199SXin Li    @timeout_util.TimeoutDecorator(cros_constants.VERIFY_TIMEOUT_SEC)
1053*9c5db199SXin Li    def verify(self, host):
1054*9c5db199SXin Li        logging.debug('ServodDutControllerMissingVerifier: Starting verifier.')
1055*9c5db199SXin Li        if host.get_servo().get('dut_controller_missing_fault') == 'on':
1056*9c5db199SXin Li            logging.debug('ServodDutControllerMissingVerifier: DUT Controller missing fault is on.')
1057*9c5db199SXin Li            raise hosts.AutoservVerifyError('Servod is missing dut controller')
1058*9c5db199SXin Li        else:
1059*9c5db199SXin Li            logging.debug('ServodDutControllerMissingVerifier: DUT Controller missing fault is not on.')
1060*9c5db199SXin Li
1061*9c5db199SXin Li    def _is_applicable(self, host):
1062*9c5db199SXin Li        if host.is_containerized_servod():
1063*9c5db199SXin Li            logging.debug('ServodDutControllerMissingVerifier: Detected containerized servod.')
1064*9c5db199SXin Li            logging.info('Servod is running within a container')
1065*9c5db199SXin Li            return True
1066*9c5db199SXin Li        if not host.is_labstation():
1067*9c5db199SXin Li            logging.debug('ServodDutControllerMissingVerifier: Detected non-labstation.')
1068*9c5db199SXin Li            logging.info('Not supported for servo_v3.')
1069*9c5db199SXin Li            return False
1070*9c5db199SXin Li        return host.is_in_lab()
1071*9c5db199SXin Li
1072*9c5db199SXin Li    @property
1073*9c5db199SXin Li    def description(self):
1074*9c5db199SXin Li        return 'ensure servod does not have missing dut controller'
1075*9c5db199SXin Li
1076*9c5db199SXin Li
1077*9c5db199SXin Liclass _ConnectionVerifier(repair_utils.SshVerifier):
1078*9c5db199SXin Li    """
1079*9c5db199SXin Li    Ensure the servo host container is up.
1080*9c5db199SXin Li    """
1081*9c5db199SXin Li
1082*9c5db199SXin Li    def verify(self, host):
1083*9c5db199SXin Li        if host.is_containerized_servod():
1084*9c5db199SXin Li            # We need start servod container first before check it-is present
1085*9c5db199SXin Li            host.start_containerized_servod()
1086*9c5db199SXin Li        return super(_ConnectionVerifier, self).verify(host)
1087*9c5db199SXin Li
1088*9c5db199SXin Li    @property
1089*9c5db199SXin Li    def description(self):
1090*9c5db199SXin Li        return 'Check the connection to the machine or container running servod.'
1091*9c5db199SXin Li
1092*9c5db199SXin Li
1093*9c5db199SXin Liclass _RestartServod(hosts.RepairAction):
1094*9c5db199SXin Li    """Restart `servod` with the proper BOARD setting."""
1095*9c5db199SXin Li
1096*9c5db199SXin Li    @timeout_util.TimeoutDecorator(cros_constants.REPAIR_TIMEOUT_SEC)
1097*9c5db199SXin Li    def repair(self, host):
1098*9c5db199SXin Li        if host.is_containerized_servod():
1099*9c5db199SXin Li            logging.debug('Restarting servod container')
1100*9c5db199SXin Li        elif not host.is_cros_host():
1101*9c5db199SXin Li            raise hosts.AutoservRepairError(
1102*9c5db199SXin Li                    'Can\'t restart servod: not running '
1103*9c5db199SXin Li                    'embedded ChromeOS.',
1104*9c5db199SXin Li                    'servo_not_applicable_to_non_cros_host')
1105*9c5db199SXin Li        host.restart_servod()
1106*9c5db199SXin Li
1107*9c5db199SXin Li    @property
1108*9c5db199SXin Li    def description(self):
1109*9c5db199SXin Li        return 'Start servod with the proper config settings.'
1110*9c5db199SXin Li
1111*9c5db199SXin Li
1112*9c5db199SXin Liclass _ServoRebootRepair(repair_utils.RebootRepair):
1113*9c5db199SXin Li    """Try repair servo by reboot servohost.
1114*9c5db199SXin Li
1115*9c5db199SXin Li    This is the same as the standard `RebootRepair`, for servo_v3 it will
1116*9c5db199SXin Li    reboot the beaglebone board immediately while for labstation it will
1117*9c5db199SXin Li    request a reboot by touch a flag file on its labstation, then
1118*9c5db199SXin Li    labstation reboot will be handled by labstation AdminRepair task as
1119*9c5db199SXin Li    labstation host multiple servos and need do an synchronized reboot.
1120*9c5db199SXin Li    """
1121*9c5db199SXin Li
1122*9c5db199SXin Li    @timeout_util.TimeoutDecorator(cros_constants.REPAIR_TIMEOUT_SEC)
1123*9c5db199SXin Li    def repair(self, host):
1124*9c5db199SXin Li        super(_ServoRebootRepair, self).repair(host)
1125*9c5db199SXin Li        # restart servod for v3 after reboot.
1126*9c5db199SXin Li        host.restart_servod()
1127*9c5db199SXin Li
1128*9c5db199SXin Li    def _is_applicable(self, host):
1129*9c5db199SXin Li        if host.is_localhost() or not host.is_cros_host():
1130*9c5db199SXin Li            logging.info('Target servo is not in a lab, the reboot repair'
1131*9c5db199SXin Li                         ' action is not applicable.')
1132*9c5db199SXin Li            return False
1133*9c5db199SXin Li
1134*9c5db199SXin Li        if host.is_labstation():
1135*9c5db199SXin Li            host.request_reboot()
1136*9c5db199SXin Li            logging.info('Reboot labstation requested, it will be handled'
1137*9c5db199SXin Li                         ' by labstation AdminRepair task.')
1138*9c5db199SXin Li            return False
1139*9c5db199SXin Li        return True
1140*9c5db199SXin Li
1141*9c5db199SXin Li    @property
1142*9c5db199SXin Li    def description(self):
1143*9c5db199SXin Li        return 'Reboot the servo host.'
1144*9c5db199SXin Li
1145*9c5db199SXin Li
1146*9c5db199SXin Liclass _ToggleCCLineRepair(hosts.RepairAction):
1147*9c5db199SXin Li    """Try repair servod by toggle cc.
1148*9c5db199SXin Li
1149*9c5db199SXin Li    When cr50 is not enumerated we can try to recover it by toggle cc line.
1150*9c5db199SXin Li    """
1151*9c5db199SXin Li    # Timeout for shut down configuration channel.
1152*9c5db199SXin Li    CC_OFF_TIMEOUT = 10
1153*9c5db199SXin Li    # Timeout for initialize configuration channel.
1154*9c5db199SXin Li    CC_ON_TIMEOUT = 30
1155*9c5db199SXin Li
1156*9c5db199SXin Li    @timeout_util.TimeoutDecorator(cros_constants.REPAIR_TIMEOUT_SEC)
1157*9c5db199SXin Li    def repair(self, host):
1158*9c5db199SXin Li        logging.info('Turn off configuration channel and wait 10 seconds.')
1159*9c5db199SXin Li        servo_uart_cmd = 'servo_v4_uart_cmd'
1160*9c5db199SXin Li        if not host.get_servo().has_control(servo_uart_cmd):
1161*9c5db199SXin Li            servo_uart_cmd = 'servo_v4p1_uart_cmd'
1162*9c5db199SXin Li        host.get_servo().set_nocheck(servo_uart_cmd, 'cc off')
1163*9c5db199SXin Li        # wait till command will be effected
1164*9c5db199SXin Li        time.sleep(self.CC_OFF_TIMEOUT)
1165*9c5db199SXin Li
1166*9c5db199SXin Li        logging.info('Turn on configuration channel and wait 30 seconds.')
1167*9c5db199SXin Li        # alternative option to turn line on is by `cc srcdts`
1168*9c5db199SXin Li        host.get_servo().set_nocheck('servo_pd_role', 'src')
1169*9c5db199SXin Li        host.get_servo().set_nocheck('servo_dts_mode', 'on')
1170*9c5db199SXin Li        # wait till command will be effected
1171*9c5db199SXin Li        time.sleep(self.CC_ON_TIMEOUT)
1172*9c5db199SXin Li        host.restart_servod()
1173*9c5db199SXin Li
1174*9c5db199SXin Li    def _is_applicable(self, host):
1175*9c5db199SXin Li        if host.is_localhost():
1176*9c5db199SXin Li            logging.debug('Not supported for localhost.')
1177*9c5db199SXin Li            return False
1178*9c5db199SXin Li        if not host.servo_serial:
1179*9c5db199SXin Li            logging.debug('Servod does not have serial.')
1180*9c5db199SXin Li            return False
1181*9c5db199SXin Li        if not host.servo_recovery:
1182*9c5db199SXin Li            logging.debug('Servod is not running in recovery mode.')
1183*9c5db199SXin Li            return False
1184*9c5db199SXin Li        if not (host.is_labstation() or host.is_containerized_servod()):
1185*9c5db199SXin Li            logging.debug('Not supported for servo_v3.')
1186*9c5db199SXin Li            return False
1187*9c5db199SXin Li        if not host.get_servo():
1188*9c5db199SXin Li            logging.debug('Servo is not initialized.')
1189*9c5db199SXin Li            return False
1190*9c5db199SXin Li        return self._is_type_c(host)
1191*9c5db199SXin Li
1192*9c5db199SXin Li    def _is_type_c(self, host):
1193*9c5db199SXin Li        if host.get_dut_host_info():
1194*9c5db199SXin Li            servo_type = host.get_dut_host_info().get_label_value(
1195*9c5db199SXin Li                    servo_constants.SERVO_TYPE_LABEL_PREFIX)
1196*9c5db199SXin Li            return 'ccd' in servo_type
1197*9c5db199SXin Li        return False
1198*9c5db199SXin Li
1199*9c5db199SXin Li    @property
1200*9c5db199SXin Li    def description(self):
1201*9c5db199SXin Li        return 'Toggle cc lines'
1202*9c5db199SXin Li
1203*9c5db199SXin Li
1204*9c5db199SXin Liclass _FakedisconnectRepair(hosts.RepairAction):
1205*9c5db199SXin Li    """Try repair servod by mimic reconnection of servo.
1206*9c5db199SXin Li
1207*9c5db199SXin Li    When cr50 is not enumerated as we can try to recover it by reconnect to DUT.
1208*9c5db199SXin Li    """
1209*9c5db199SXin Li    # Delay to disconnect.
1210*9c5db199SXin Li    DISC_DELAY_MS = 100
1211*9c5db199SXin Li    # Timeout to wait to restore the connection.
1212*9c5db199SXin Li    DISC_TIMEOUT_MS = 2000
1213*9c5db199SXin Li    # Timeout to wait to execute the command and apply effect.
1214*9c5db199SXin Li    EXEC_TIMEOUT = (DISC_DELAY_MS + DISC_TIMEOUT_MS) / 1000 + 2
1215*9c5db199SXin Li
1216*9c5db199SXin Li    @timeout_util.TimeoutDecorator(cros_constants.REPAIR_TIMEOUT_SEC)
1217*9c5db199SXin Li    def repair(self, host):
1218*9c5db199SXin Li        disc_cmd = ('fakedisconnect %d %d' %
1219*9c5db199SXin Li                    (self.DISC_DELAY_MS, self.DISC_TIMEOUT_MS))
1220*9c5db199SXin Li        # cannot use 'set' as control is not returned executed commands
1221*9c5db199SXin Li        servo_uart_cmd = 'servo_v4_uart_cmd'
1222*9c5db199SXin Li        if not host.get_servo().has_control(servo_uart_cmd):
1223*9c5db199SXin Li            servo_uart_cmd = 'servo_v4p1_uart_cmd'
1224*9c5db199SXin Li        host.get_servo().set_nocheck(servo_uart_cmd, disc_cmd)
1225*9c5db199SXin Li        logging.debug('Waiting %ss for affect of action', self.EXEC_TIMEOUT)
1226*9c5db199SXin Li        time.sleep(self.EXEC_TIMEOUT)
1227*9c5db199SXin Li        host.restart_servod()
1228*9c5db199SXin Li
1229*9c5db199SXin Li    def _is_applicable(self, host):
1230*9c5db199SXin Li        if host.is_localhost():
1231*9c5db199SXin Li            logging.debug('Not supported for localhost.')
1232*9c5db199SXin Li            return False
1233*9c5db199SXin Li        if not host.servo_serial:
1234*9c5db199SXin Li            logging.debug('Servod does not have serial.')
1235*9c5db199SXin Li            return False
1236*9c5db199SXin Li        if not host.servo_recovery:
1237*9c5db199SXin Li            logging.debug('Servod is not running in recovery mode.')
1238*9c5db199SXin Li            return False
1239*9c5db199SXin Li        if not (host.is_labstation() or host.is_containerized_servod()):
1240*9c5db199SXin Li            logging.debug('Not supported for servo_v3.')
1241*9c5db199SXin Li            return False
1242*9c5db199SXin Li        if not host.get_servo():
1243*9c5db199SXin Li            logging.debug('Servo is not initialized.')
1244*9c5db199SXin Li            return False
1245*9c5db199SXin Li        return self._is_type_c(host)
1246*9c5db199SXin Li
1247*9c5db199SXin Li    def _is_type_c(self, host):
1248*9c5db199SXin Li        if host.get_dut_host_info():
1249*9c5db199SXin Li            servo_type = host.get_dut_host_info().get_label_value(
1250*9c5db199SXin Li                    servo_constants.SERVO_TYPE_LABEL_PREFIX)
1251*9c5db199SXin Li            return 'ccd' in servo_type
1252*9c5db199SXin Li        return False
1253*9c5db199SXin Li
1254*9c5db199SXin Li    @property
1255*9c5db199SXin Li    def description(self):
1256*9c5db199SXin Li        return 'Fake reconnect to DUT'
1257*9c5db199SXin Li
1258*9c5db199SXin Li
1259*9c5db199SXin Liclass _PowerDeliveryRepair(hosts.RepairAction):
1260*9c5db199SXin Li    """Repair to check servo_v4_role for servos that support
1261*9c5db199SXin Li    power delivery feature(a.k.a power pass through).
1262*9c5db199SXin Li
1263*9c5db199SXin Li    There are currently two position of servo_v4_role, src and snk:
1264*9c5db199SXin Li    src --  servo in power delivery mode and passes power to the DUT.
1265*9c5db199SXin Li    snk --  servo in normal mode and not passes power to DUT.
1266*9c5db199SXin Li    """
1267*9c5db199SXin Li    # How many time retry to set PD in correct mode and verify that is stay.
1268*9c5db199SXin Li    # Set 5 as each attempt has 10 attempts inside 'set' method.
1269*9c5db199SXin Li    _SET_ATTEMPT_COUNT = 5
1270*9c5db199SXin Li
1271*9c5db199SXin Li    @timeout_util.TimeoutDecorator(cros_constants.REPAIR_TIMEOUT_SEC)
1272*9c5db199SXin Li    def repair(self, host):
1273*9c5db199SXin Li        host.get_servo().set_nocheck('servo_pd_role', 'snk')
1274*9c5db199SXin Li        time.sleep(1)
1275*9c5db199SXin Li        for x in range(self._SET_ATTEMPT_COUNT):
1276*9c5db199SXin Li            logging.debug('Try set servo_v4_role to src.'
1277*9c5db199SXin Li                          ' Attempt: %s', x + 1)
1278*9c5db199SXin Li            try:
1279*9c5db199SXin Li                host.get_servo().set('servo_pd_role', 'src')
1280*9c5db199SXin Li                # Waiting a few seconds as it can be change to snk if PD
1281*9c5db199SXin Li                # on servo has issue.
1282*9c5db199SXin Li                time.sleep(5)
1283*9c5db199SXin Li            except BaseException as e:
1284*9c5db199SXin Li                logging.debug('Setting PD with retries failed %s', e)
1285*9c5db199SXin Li            if host.get_servo().get('servo_pd_role') == 'src':
1286*9c5db199SXin Li                break
1287*9c5db199SXin Li        if host.get_servo().get('servo_pd_role') == 'snk':
1288*9c5db199SXin Li            raise hosts.AutoservNonCriticalVerifyError(
1289*9c5db199SXin Li                    'Cannot switch power delivery to the src role')
1290*9c5db199SXin Li        # Restart servod to re-initialize servos.
1291*9c5db199SXin Li        # In some cases if device did not receive power can block detection
1292*9c5db199SXin Li        # of servo components.
1293*9c5db199SXin Li        host.restart_servod()
1294*9c5db199SXin Li
1295*9c5db199SXin Li    def _is_type_c(self, host):
1296*9c5db199SXin Li        return (host.is_in_lab() and host.get_servo()
1297*9c5db199SXin Li                and host.get_servo().supports_built_in_pd_control())
1298*9c5db199SXin Li
1299*9c5db199SXin Li    @property
1300*9c5db199SXin Li    def description(self):
1301*9c5db199SXin Li        return 'Recover power delivery on servo'
1302*9c5db199SXin Li
1303*9c5db199SXin Li
1304*9c5db199SXin Liclass _ECRebootRepair(hosts.RepairAction):
1305*9c5db199SXin Li    """
1306*9c5db199SXin Li    Reboot EC on DUT from servo.
1307*9c5db199SXin Li    """
1308*9c5db199SXin Li
1309*9c5db199SXin Li    def _is_applicable(self, host):
1310*9c5db199SXin Li        return (not host.is_localhost()) and host.is_ec_supported()
1311*9c5db199SXin Li
1312*9c5db199SXin Li    @timeout_util.TimeoutDecorator(cros_constants.REPAIR_TIMEOUT_SEC)
1313*9c5db199SXin Li    def repair(self, host):
1314*9c5db199SXin Li        host.get_servo().ec_reboot()
1315*9c5db199SXin Li
1316*9c5db199SXin Li    @property
1317*9c5db199SXin Li    def description(self):
1318*9c5db199SXin Li        return 'Reboot EC'
1319*9c5db199SXin Li
1320*9c5db199SXin Li
1321*9c5db199SXin Liclass _DutRebootRepair(hosts.RepairAction):
1322*9c5db199SXin Li    """
1323*9c5db199SXin Li    Reboot DUT to recover some servo controls depending on EC console.
1324*9c5db199SXin Li
1325*9c5db199SXin Li    Some servo controls, like lid_open, requires communicating with DUT through
1326*9c5db199SXin Li    EC UART console. Failure of this kinds of controls can be recovered by
1327*9c5db199SXin Li    rebooting the DUT.
1328*9c5db199SXin Li    """
1329*9c5db199SXin Li
1330*9c5db199SXin Li    @timeout_util.TimeoutDecorator(cros_constants.REPAIR_TIMEOUT_SEC)
1331*9c5db199SXin Li    def repair(self, host):
1332*9c5db199SXin Li        host.get_servo().get_power_state_controller().reset()
1333*9c5db199SXin Li        # Get the lid_open value which requires EC console.
1334*9c5db199SXin Li        lid_open = host.get_servo().get('lid_open')
1335*9c5db199SXin Li        if lid_open != 'yes' and lid_open != 'not_applicable':
1336*9c5db199SXin Li            raise hosts.AutoservVerifyError(
1337*9c5db199SXin Li                    'Still fail to contact EC console after rebooting DUT')
1338*9c5db199SXin Li
1339*9c5db199SXin Li    @property
1340*9c5db199SXin Li    def description(self):
1341*9c5db199SXin Li        return 'Reset the DUT via servo'
1342*9c5db199SXin Li
1343*9c5db199SXin Li
1344*9c5db199SXin Liclass _DiskCleanupRepair(hosts.RepairAction):
1345*9c5db199SXin Li    """
1346*9c5db199SXin Li    Remove old logs/metrics/crash_dumps on servohost to free up disk space.
1347*9c5db199SXin Li    """
1348*9c5db199SXin Li    KEEP_LOGS_MAX_DAYS = 5
1349*9c5db199SXin Li
1350*9c5db199SXin Li    FILE_TO_REMOVE = [
1351*9c5db199SXin Li            '/var/lib/metrics/uma-events', '/var/spool/crash/*',
1352*9c5db199SXin Li            '/var/log/chrome/*', '/var/log/ui/*',
1353*9c5db199SXin Li            '/home/chronos/BrowserMetrics/*'
1354*9c5db199SXin Li    ]
1355*9c5db199SXin Li
1356*9c5db199SXin Li    @timeout_util.TimeoutDecorator(cros_constants.SHORT_REPAIR_TIMEOUT_SEC)
1357*9c5db199SXin Li    def repair(self, host):
1358*9c5db199SXin Li        if host.is_localhost():
1359*9c5db199SXin Li            # we don't want to remove anything from local testing.
1360*9c5db199SXin Li            return
1361*9c5db199SXin Li
1362*9c5db199SXin Li        # Remove old servod logs.
1363*9c5db199SXin Li        host.run('/usr/bin/find /var/log/servod_* -mtime +%d -print -delete'
1364*9c5db199SXin Li                 % self.KEEP_LOGS_MAX_DAYS, ignore_status=True)
1365*9c5db199SXin Li
1366*9c5db199SXin Li        # Remove pre-defined metrics and crash dumps.
1367*9c5db199SXin Li        for path in self.FILE_TO_REMOVE:
1368*9c5db199SXin Li            host.run('rm %s' % path, ignore_status=True)
1369*9c5db199SXin Li
1370*9c5db199SXin Li    @property
1371*9c5db199SXin Li    def description(self):
1372*9c5db199SXin Li        return 'Clean up old logs/metrics on servohost to free up disk space.'
1373*9c5db199SXin Li
1374*9c5db199SXin Li
1375*9c5db199SXin Liclass _ServoFwUpdateRepair(hosts.RepairAction):
1376*9c5db199SXin Li    """Update firmware for servos.
1377*9c5db199SXin Li
1378*9c5db199SXin Li    We try to update servo 3 times and then try to force update it.
1379*9c5db199SXin Li    """
1380*9c5db199SXin Li
1381*9c5db199SXin Li    @timeout_util.TimeoutDecorator(cros_constants.REPAIR_TIMEOUT_SEC)
1382*9c5db199SXin Li    def repair(self, host):
1383*9c5db199SXin Li        try:
1384*9c5db199SXin Li            servo_updater.update_servo_firmware(host,
1385*9c5db199SXin Li                                                try_attempt_count=3,
1386*9c5db199SXin Li                                                force_update=False,
1387*9c5db199SXin Li                                                try_force_update=True)
1388*9c5db199SXin Li        except servo_updater.ServoUpdaterError as er:
1389*9c5db199SXin Li            # Catch servo_updater issue to cache it.
1390*9c5db199SXin Li            self.servo_updater_issue_detected = True
1391*9c5db199SXin Li            raise hosts.AutoservVerifyError('ServoUpdater issue detected')
1392*9c5db199SXin Li
1393*9c5db199SXin Li    def _is_applicable(self, host):
1394*9c5db199SXin Li        # Run only for servo_v4 and servo_v4p1.
1395*9c5db199SXin Li        return host.is_labstation() or host.is_containerized_servod()
1396*9c5db199SXin Li
1397*9c5db199SXin Li    @property
1398*9c5db199SXin Li    def description(self):
1399*9c5db199SXin Li        return 'Update servo-fw if required.'
1400*9c5db199SXin Li
1401*9c5db199SXin Li
1402*9c5db199SXin Liclass _ServoMicroFlashRepair(hosts.RepairAction):
1403*9c5db199SXin Li    """
1404*9c5db199SXin Li    Remove old logs/metrics/crash_dumps on servohost to free up disk space.
1405*9c5db199SXin Li    """
1406*9c5db199SXin Li    _TARGET_SERVO = 'servo_micro'
1407*9c5db199SXin Li
1408*9c5db199SXin Li    @timeout_util.TimeoutDecorator(cros_constants.REPAIR_TIMEOUT_SEC)
1409*9c5db199SXin Li    def repair(self, host):
1410*9c5db199SXin Li        if not host.is_cros_host():
1411*9c5db199SXin Li            raise hosts.AutoservRepairError(
1412*9c5db199SXin Li                    'Can\'t restart servod: not running '
1413*9c5db199SXin Li                    'embedded ChromeOS.',
1414*9c5db199SXin Li                    'servo_not_applicable_to_non_cros_host')
1415*9c5db199SXin Li        servo = host.get_servo()
1416*9c5db199SXin Li        if not servo or self._TARGET_SERVO not in servo.get_servo_type():
1417*9c5db199SXin Li            logging.info("Servo-micro is not present on set-up")
1418*9c5db199SXin Li            return
1419*9c5db199SXin Li
1420*9c5db199SXin Li        try:
1421*9c5db199SXin Li            servo_updater.update_servo_firmware(host,
1422*9c5db199SXin Li                                                boards=(self._TARGET_SERVO, ),
1423*9c5db199SXin Li                                                force_update=True,
1424*9c5db199SXin Li                                                ignore_version=True)
1425*9c5db199SXin Li        except Exception as e:
1426*9c5db199SXin Li            logging.debug("(Not critical) Servo device update error: %s", e)
1427*9c5db199SXin Li            raise hosts.AutoservVerifyError(
1428*9c5db199SXin Li                    'Still fail to contact EC console after rebooting DUT')
1429*9c5db199SXin Li        # Update time when we reflashed the fw on the device
1430*9c5db199SXin Li        dhp = host.get_dut_health_profile()
1431*9c5db199SXin Li        dhp.refresh_servo_miro_fw_update_run_time()
1432*9c5db199SXin Li        host.restart_servod()
1433*9c5db199SXin Li
1434*9c5db199SXin Li    def is_time_to_try(self, dhp):
1435*9c5db199SXin Li        """Verify that it is time when we can try to re-flash fw on servo_micro.
1436*9c5db199SXin Li
1437*9c5db199SXin Li        Re-flashing limited to once per 2 weeks to avoid over-flashing
1438*9c5db199SXin Li        the servo device.
1439*9c5db199SXin Li        """
1440*9c5db199SXin Li        today_time = int(time.time())
1441*9c5db199SXin Li        last_check = dhp.get_servo_micro_fw_update_time_epoch()
1442*9c5db199SXin Li        can_run = today_time > (last_check + (14 * 24 * 60 * 60))
1443*9c5db199SXin Li        if not can_run:
1444*9c5db199SXin Li            logging.info("The servo_micro fw updated in las 2 weeks ago.")
1445*9c5db199SXin Li        return can_run
1446*9c5db199SXin Li
1447*9c5db199SXin Li    def _is_applicable(self, host):
1448*9c5db199SXin Li        return (not host.is_localhost() and host.get_dut_health_profile()
1449*9c5db199SXin Li                and self.is_time_to_try(host.get_dut_health_profile()))
1450*9c5db199SXin Li
1451*9c5db199SXin Li    @property
1452*9c5db199SXin Li    def description(self):
1453*9c5db199SXin Li        return 'Re-flash servo_micro firmware.'
1454*9c5db199SXin Li
1455*9c5db199SXin Li
1456*9c5db199SXin Lidef _servo_verifier_actions():
1457*9c5db199SXin Li    """
1458*9c5db199SXin Li    Return a verifiers for a `ServoHost`.
1459*9c5db199SXin Li    """
1460*9c5db199SXin Li    return (
1461*9c5db199SXin Li            (_ConnectionVerifier, 'connection', []),
1462*9c5db199SXin Li            (_RootServoPresentVerifier, 'servo_root_present', ['connection']),
1463*9c5db199SXin Li            (_RootServoV3PresentVerifier, 'servo_v3_root_present',
1464*9c5db199SXin Li             ['connection']),
1465*9c5db199SXin Li            (_ServoFwVerifier, 'servo_fw', ['servo_root_present']),
1466*9c5db199SXin Li            (_StartServodVerifier, 'start_servod',
1467*9c5db199SXin Li             ['servo_fw', 'servo_v3_root_present']),
1468*9c5db199SXin Li            (_DiskSpaceVerifier, 'servo_disk_space', ['connection']),
1469*9c5db199SXin Li            (_UpdateVerifier, 'servo_update', ['servo_v3_root_present']),
1470*9c5db199SXin Li            (_BoardConfigVerifier, 'servo_config_board', ['connection']),
1471*9c5db199SXin Li            (_SerialConfigVerifier, 'servo_config_serial', ['connection']),
1472*9c5db199SXin Li            (_ServodJobVerifier, 'servod_started', [
1473*9c5db199SXin Li                    'start_servod', 'servo_config_board',
1474*9c5db199SXin Li                    'servo_config_serial', 'servo_disk_space'
1475*9c5db199SXin Li            ]),
1476*9c5db199SXin Li            (_ServodEchoVerifier, 'servod_echo', ['servod_started']),
1477*9c5db199SXin Li            (_TopologyVerifier, 'servo_topology', ['servod_echo']),
1478*9c5db199SXin Li            (_ServodConnectionVerifier, 'servod_connection', ['servod_echo']),
1479*9c5db199SXin Li            (_Cr50LowSBUVerifier, 'servo_cr50_low_sbu', ['servod_connection']),
1480*9c5db199SXin Li            (ServodDutControllerMissingVerifier,
1481*9c5db199SXin Li             'servod_dut_controller_missing', ['servod_connection']),
1482*9c5db199SXin Li            (_Cr50OffVerifier, 'servo_cr50_off', ['servod_connection']),
1483*9c5db199SXin Li            (_ServodControlVerifier, 'servod_control', ['servod_connection']),
1484*9c5db199SXin Li            (_DUTConnectionVerifier, 'servo_dut_connected',
1485*9c5db199SXin Li             ['servod_connection']),
1486*9c5db199SXin Li            (_ServoHubConnectionVerifier, 'servo_hub_connected',
1487*9c5db199SXin Li             ['servo_dut_connected']),
1488*9c5db199SXin Li            (_PowerButtonVerifier, 'servo_pwr_button', ['servo_hub_connected'
1489*9c5db199SXin Li                                                        ]),
1490*9c5db199SXin Li            (_BatteryVerifier, 'servo_battery', ['servo_hub_connected']),
1491*9c5db199SXin Li            (_LidVerifier, 'servo_lid_open', ['servo_hub_connected']),
1492*9c5db199SXin Li            (ECConsoleVerifier, 'servo_ec_console', ['servo_dut_connected']),
1493*9c5db199SXin Li            (_Cr50ConsoleVerifier, 'servo_cr50_console',
1494*9c5db199SXin Li             ['servo_dut_connected']),
1495*9c5db199SXin Li            (_CCDTestlabVerifier, 'servo_ccd_testlab', ['servo_cr50_console']),
1496*9c5db199SXin Li            (_CCDPowerDeliveryVerifier, 'servo_power_delivery',
1497*9c5db199SXin Li             ['servod_connection']),
1498*9c5db199SXin Li    )
1499*9c5db199SXin Li
1500*9c5db199SXin Li
1501*9c5db199SXin Lidef _servo_repair_actions():
1502*9c5db199SXin Li    """
1503*9c5db199SXin Li    Return a `RepairStrategy` for a `ServoHost`.
1504*9c5db199SXin Li    """
1505*9c5db199SXin Li    config = ['servo_config_board', 'servo_config_serial', 'start_servod']
1506*9c5db199SXin Li    base_triggers = [
1507*9c5db199SXin Li            'servod_started', 'servo_topology', 'servod_connection',
1508*9c5db199SXin Li            'servod_echo', 'servod_control', 'servo_dut_connected',
1509*9c5db199SXin Li            'servo_hub_connected', 'servo_pwr_button', 'servo_cr50_console',
1510*9c5db199SXin Li            'servo_cr50_low_sbu', 'servo_cr50_off', 'servo_power_delivery',
1511*9c5db199SXin Li            'servod_dut_controller_missing'
1512*9c5db199SXin Li    ]
1513*9c5db199SXin Li    dut_triggers = [
1514*9c5db199SXin Li            'servod_control', 'servo_lid_open', 'servo_ec_console',
1515*9c5db199SXin Li            'servo_topology', 'servo_dut_connected', 'servo_hub_connected',
1516*9c5db199SXin Li            'servo_cr50_low_sbu', 'servo_cr50_off', 'servo_cr50_console',
1517*9c5db199SXin Li            'servo_power_delivery', 'servod_dut_controller_missing'
1518*9c5db199SXin Li    ]
1519*9c5db199SXin Li    reboot_triggers = [
1520*9c5db199SXin Li            'servo_topology', 'servo_root_present', 'servo_disk_space',
1521*9c5db199SXin Li            'servo_power_delivery'
1522*9c5db199SXin Li    ]
1523*9c5db199SXin Li    return (
1524*9c5db199SXin Li            (_ServoFwUpdateRepair, 'servo_fw_update', ['connection'],
1525*9c5db199SXin Li             ['servo_fw']),
1526*9c5db199SXin Li            (_DiskCleanupRepair, 'servo_disk_cleanup', ['connection'],
1527*9c5db199SXin Li             ['servo_disk_space']),
1528*9c5db199SXin Li            (_ServoMicroFlashRepair, 'servo_micro_flash',
1529*9c5db199SXin Li             ['connection', 'servo_topology'], ['servo_dut_connected']),
1530*9c5db199SXin Li            (_RestartServod, 'servod_restart', ['connection', 'servo_fw'],
1531*9c5db199SXin Li             config + base_triggers),
1532*9c5db199SXin Li            (_ServoRebootRepair, 'servo_reboot', ['connection'],
1533*9c5db199SXin Li             reboot_triggers),
1534*9c5db199SXin Li            (_PowerDeliveryRepair, 'servo_pd_recover', ['servod_connection'],
1535*9c5db199SXin Li             base_triggers),
1536*9c5db199SXin Li            (_FakedisconnectRepair, 'servo_fakedisconnect',
1537*9c5db199SXin Li             ['servod_connection'], base_triggers),
1538*9c5db199SXin Li            (_ToggleCCLineRepair, 'servo_cc', ['servod_connection'],
1539*9c5db199SXin Li             base_triggers),
1540*9c5db199SXin Li            (_DutRebootRepair, 'servo_dut_reboot', ['servod_connection'],
1541*9c5db199SXin Li             dut_triggers),
1542*9c5db199SXin Li            (_ECRebootRepair, 'servo_ec_reboot', ['servod_connection'],
1543*9c5db199SXin Li             dut_triggers),
1544*9c5db199SXin Li    )
1545*9c5db199SXin Li
1546*9c5db199SXin Li
1547*9c5db199SXin Lidef create_servo_repair_strategy():
1548*9c5db199SXin Li    """
1549*9c5db199SXin Li    Return a `RepairStrategy` for a `ServoHost`.
1550*9c5db199SXin Li    """
1551*9c5db199SXin Li    return hosts.RepairStrategy(_servo_verifier_actions(),
1552*9c5db199SXin Li                                _servo_repair_actions(), 'servo')
1553