blob: ee5c8072f05cc2e2a879881f7d5f1a0679ee2ee4 [file] [log] [blame]
J. Richard Barnette384056b2012-04-16 11:04:46 -07001# Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
Craig Harrison2b6c6fc2011-06-23 10:34:02 -07002# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4#
5# Expects to be run in an environment with sudo and no interactive password
6# prompt, such as within the Chromium OS development chroot.
7
Congbin Guoa1f9cba2018-07-03 11:36:59 -07008import ast
9import logging
Vadim Bendeburyb80ba592012-12-07 15:02:34 -080010import os
Congbin Guoa1f9cba2018-07-03 11:36:59 -070011import re
Dana Goyettea2f00ea2019-06-26 16:14:12 -070012import socket
Congbin Guoa1f9cba2018-07-03 11:36:59 -070013import time
14import xmlrpclib
Vadim Bendeburyb80ba592012-12-07 15:02:34 -080015
Simran Basi741b5d42012-05-18 11:27:15 -070016from autotest_lib.client.common_lib import error
Mary Ruthvenecf12712019-06-26 17:36:21 -070017from autotest_lib.client.common_lib import lsbrelease_utils
Congbin Guo42427612019-02-12 10:22:06 -080018from autotest_lib.server import utils as server_utils
Ricky Liangc31aab32014-07-03 16:23:29 +080019from autotest_lib.server.cros.servo import firmware_programmer
Craig Harrison2b6c6fc2011-06-23 10:34:02 -070020
Kevin Chenga22c4a82016-10-07 14:13:25 -070021# Time to wait when probing for a usb device, it takes on avg 17 seconds
22# to do a full probe.
23_USB_PROBE_TIMEOUT = 40
24
J. Richard Barnette41320ee2013-03-11 13:00:13 -070025
Ruben Rodriguez Buchillon2cba8e42019-08-12 11:31:53 -070026# Regex to match XMLRPC errors due to a servod control not existing.
27NO_CONTROL_RE = re.compile(r'No control named (\w*\.?\w*)')
28
29class ControlUnavailableError(error.TestFail):
30 """Custom error class to indicate a control is unavailable on servod."""
31 pass
32
33
Congbin Guo42427612019-02-12 10:22:06 -080034def _extract_image_from_tarball(tarball, dest_dir, image_candidates):
35 """Try extracting the image_candidates from the tarball.
36
37 @param tarball: The path of the tarball.
38 @param dest_path: The path of the destination.
39 @param image_candidates: A tuple of the paths of image candidates.
40
41 @return: The first path from the image candidates, which succeeds, or None
42 if all the image candidates fail.
43 """
44 for image in image_candidates:
45 status = server_utils.system(
46 ('tar xf %s -C %s %s' % (tarball, dest_dir, image)),
47 timeout=60, ignore_status=True)
48 if status == 0:
49 return image
50 return None
51
52
J. Richard Barnette0c9c5882014-06-11 15:27:05 -070053class _PowerStateController(object):
54
55 """Class to provide board-specific power operations.
56
57 This class is responsible for "power on" and "power off"
58 operations that can operate without making assumptions in
59 advance about board state. It offers an interface that
60 abstracts out the different sequences required for different
61 board types.
62
63 """
64
65 # Constants acceptable to be passed for the `rec_mode` parameter
66 # to power_on().
67 #
68 # REC_ON: Boot the DUT in recovery mode, i.e. boot from USB or
69 # SD card.
70 # REC_OFF: Boot in normal mode, i.e. boot from internal storage.
71
72 REC_ON = 'rec'
73 REC_OFF = 'on'
Shelley Chen65938622018-05-16 07:45:54 -070074 REC_ON_FORCE_MRC = 'rec_force_mrc'
J. Richard Barnette0c9c5882014-06-11 15:27:05 -070075
76 # Delay in seconds needed between asserting and de-asserting
77 # warm reset.
78 _RESET_HOLD_TIME = 0.5
79
80 def __init__(self, servo):
81 """Initialize the power state control.
82
83 @param servo Servo object providing the underlying `set` and `get`
84 methods for the target controls.
85
86 """
87 self._servo = servo
88
89 def reset(self):
90 """Force the DUT to reset.
91
92 The DUT is guaranteed to be on at the end of this call,
93 regardless of its previous state, provided that there is
94 working OS software. This also guarantees that the EC has
95 been restarted.
96
97 """
98 self._servo.set_nocheck('power_state', 'reset')
99
100 def warm_reset(self):
101 """Apply warm reset to the DUT.
102
103 This asserts, then de-asserts the 'warm_reset' signal.
104 Generally, this causes the board to restart.
105
106 """
107 self._servo.set_get_all(['warm_reset:on',
108 'sleep:%.4f' % self._RESET_HOLD_TIME,
109 'warm_reset:off'])
110
J. Richard Barnette0c9c5882014-06-11 15:27:05 -0700111 def power_off(self):
112 """Force the DUT to power off.
113
114 The DUT is guaranteed to be off at the end of this call,
115 regardless of its previous state, provided that there is
116 working EC and boot firmware. There is no requirement for
117 working OS software.
118
119 """
120 self._servo.set_nocheck('power_state', 'off')
121
122 def power_on(self, rec_mode=REC_OFF):
123 """Force the DUT to power on.
124
125 Prior to calling this function, the DUT must be powered off,
126 e.g. with a call to `power_off()`.
127
128 At power on, recovery mode is set as specified by the
129 corresponding argument. When booting with recovery mode on, it
130 is the caller's responsibility to unplug/plug in a bootable
131 external storage device.
132
133 If the DUT requires a delay after powering on but before
134 processing inputs such as USB stick insertion, the delay is
135 handled by this method; the caller is not responsible for such
136 delays.
137
138 @param rec_mode Setting of recovery mode to be applied at
139 power on. default: REC_OFF aka 'off'
140
141 """
142 self._servo.set_nocheck('power_state', rec_mode)
143
144
Congbin Guoa1f9cba2018-07-03 11:36:59 -0700145class _Uart(object):
Congbin Guo0f00be82019-04-18 17:51:14 -0700146 """Class to capture UART streams of CPU, EC, Cr50, etc."""
147 _UartToCapture = ('cpu', 'ec', 'cr50', 'servo_v4', 'servo_micro', 'usbpd')
148
Congbin Guoa1f9cba2018-07-03 11:36:59 -0700149 def __init__(self, servo):
150 self._servo = servo
151 self._streams = []
Congbin Guo0f00be82019-04-18 17:51:14 -0700152 self.logs_dir = None
Congbin Guofe4d4e82019-04-18 17:51:14 -0700153
Congbin Guo0f00be82019-04-18 17:51:14 -0700154 def _start_stop_capture(self, uart, start):
155 """Helper function to start/stop capturing on specified UART.
156
157 @param uart: The UART name to start/stop capturing.
158 @param start: True to start capturing, otherwise stop.
159
160 @returns True if the operation completes successfully. False if the UART
161 capturing is not supported or failed due to an error.
162 """
163 logging.debug('%s capturing %s UART.', 'Start' if start else 'Stop',
164 uart)
Ruben Rodriguez Buchillonb0e2a9f2019-08-12 12:40:44 -0700165 uart_cmd = '%s_uart_capture' % uart
166 if self._servo.has_control(uart_cmd):
Ruben Rodriguez Buchillon9f485642019-08-21 14:32:22 -0700167 # Do our own implementation of set() here as not_applicable
168 # should also count as a valid control.
169 level = 'on' if start else 'off'
170 logging.debug('Trying to set %s to %s.', uart_cmd, level)
171 self._servo.set_nocheck(uart_cmd, level)
172 result = self._servo.get(uart_cmd)
173 if result in [level, 'not_applicable']:
174 logging.debug('Managed to set %s to %s.', uart_cmd, result)
175 return True
176 logging.debug('Failed to set %s to %s. Got %s.', uart_cmd, level,
177 result)
Ruben Rodriguez Buchillonb0e2a9f2019-08-12 12:40:44 -0700178 return False
Congbin Guo0f00be82019-04-18 17:51:14 -0700179
180 def start_capture(self):
181 """Start capturing UART streams."""
182 for uart in self._UartToCapture:
183 if self._start_stop_capture(uart, True):
184 self._streams.append(('%s_uart_stream' % uart, '%s_uart.log' %
185 uart))
Congbin Guoa1f9cba2018-07-03 11:36:59 -0700186
Congbin Guofc3b8962019-03-22 17:38:46 -0700187 def dump(self):
188 """Dump UART streams to log files accordingly."""
Congbin Guo0f00be82019-04-18 17:51:14 -0700189 if not self.logs_dir:
Congbin Guofc3b8962019-03-22 17:38:46 -0700190 return
Congbin Guoa1f9cba2018-07-03 11:36:59 -0700191
Congbin Guoa1f9cba2018-07-03 11:36:59 -0700192 for stream, logfile in self._streams:
Congbin Guo0f00be82019-04-18 17:51:14 -0700193 logfile_fullname = os.path.join(self.logs_dir, logfile)
Congbin Guoa1f9cba2018-07-03 11:36:59 -0700194 try:
195 content = self._servo.get(stream)
196 except Exception as err:
197 logging.warn('Failed to get UART log for %s: %s', stream, err)
198 continue
199
200 # The UART stream may contain non-printable characters, and servo
201 # returns it in string representation. We use `ast.leteral_eval`
202 # to revert it back.
203 with open(logfile_fullname, 'a') as fd:
204 fd.write(ast.literal_eval(content))
205
206 def stop_capture(self):
207 """Stop capturing UART streams."""
Congbin Guo0f00be82019-04-18 17:51:14 -0700208 for uart in self._UartToCapture:
Congbin Guoa1f9cba2018-07-03 11:36:59 -0700209 try:
Congbin Guo0f00be82019-04-18 17:51:14 -0700210 self._start_stop_capture(uart, False)
Congbin Guoa1f9cba2018-07-03 11:36:59 -0700211 except Exception as err:
212 logging.warn('Failed to stop UART logging for %s: %s', uart,
213 err)
214
215
J. Richard Barnette384056b2012-04-16 11:04:46 -0700216class Servo(object):
J. Richard Barnette41320ee2013-03-11 13:00:13 -0700217
Craig Harrison2b6c6fc2011-06-23 10:34:02 -0700218 """Manages control of a Servo board.
219
220 Servo is a board developed by hardware group to aide in the debug and
221 control of various partner devices. Servo's features include the simulation
222 of pressing the power button, closing the lid, and pressing Ctrl-d. This
223 class manages setting up and communicating with a servo demon (servod)
224 process. It provides both high-level functions for common servo tasks and
225 low-level functions for directly setting and reading gpios.
J. Richard Barnette41320ee2013-03-11 13:00:13 -0700226
Craig Harrison2b6c6fc2011-06-23 10:34:02 -0700227 """
228
Chrome Bot9a1137d2011-07-19 14:35:00 -0700229 # Power button press delays in seconds.
J. Richard Barnetted2e4cbd2012-06-29 12:18:40 -0700230 #
J. Richard Barnette5383f072012-07-26 17:35:40 -0700231 # The EC specification says that 8.0 seconds should be enough
232 # for the long power press. However, some platforms need a bit
233 # more time. Empirical testing has found these requirements:
234 # Alex: 8.2 seconds
235 # ZGB: 8.5 seconds
236 # The actual value is set to the largest known necessary value.
237 #
238 # TODO(jrbarnette) Being generous is the right thing to do for
239 # existing platforms, but if this code is to be used for
240 # qualification of new hardware, we should be less generous.
Chrome Bot9a1137d2011-07-19 14:35:00 -0700241 SHORT_DELAY = 0.1
J. Richard Barnette5383f072012-07-26 17:35:40 -0700242
Todd Broch31c82502011-08-29 08:14:39 -0700243 # Maximum number of times to re-read power button on release.
Todd Brochcf7c6652012-02-24 13:03:59 -0800244 GET_RETRY_MAX = 10
Chrome Bot9a1137d2011-07-19 14:35:00 -0700245
J. Richard Barnette5383f072012-07-26 17:35:40 -0700246 # Delays to deal with DUT state transitions.
Chrome Bot9a1137d2011-07-19 14:35:00 -0700247 SLEEP_DELAY = 6
248 BOOT_DELAY = 10
J. Richard Barnette41320ee2013-03-11 13:00:13 -0700249
J. Richard Barnette41320ee2013-03-11 13:00:13 -0700250 # Default minimum time interval between 'press' and 'release'
251 # keyboard events.
Vic Yang0aca1c22012-11-19 18:33:56 -0800252 SERVO_KEY_PRESS_DELAY = 0.1
Craig Harrison2b6c6fc2011-06-23 10:34:02 -0700253
Tom Wai-Hong Tam93b5c092015-05-14 02:50:43 +0800254 # Time to toggle recovery switch on and off.
255 REC_TOGGLE_DELAY = 0.1
256
Tom Wai-Hong Tamf9ded092015-05-20 05:37:22 +0800257 # Time to toggle development switch on and off.
258 DEV_TOGGLE_DELAY = 0.1
259
Jon Salzc88e5b62011-11-30 14:38:54 +0800260 # Time between an usb disk plugged-in and detected in the system.
261 USB_DETECTION_DELAY = 10
Vadim Bendeburye7bd9362012-12-19 14:35:20 -0800262 # Time to keep USB power off before and after USB mux direction is changed
263 USB_POWEROFF_DELAY = 2
Jon Salzc88e5b62011-11-30 14:38:54 +0800264
Simran Basib7850bb2013-07-24 12:33:42 -0700265 # Time to wait before timing out on servo initialization.
266 INIT_TIMEOUT_SECS = 10
Simran Basi59331342013-07-12 12:06:29 -0700267
J. Richard Barnette67ccb872012-04-19 16:34:56 -0700268
Ricky Liang0dd379c2014-04-23 16:29:08 +0800269 def __init__(self, servo_host, servo_serial=None):
Craig Harrison2b6c6fc2011-06-23 10:34:02 -0700270 """Sets up the servo communication infrastructure.
271
Fang Deng5d518f42013-08-02 14:04:32 -0700272 @param servo_host: A ServoHost object representing
273 the host running servod.
Dana Goyettea2f00ea2019-06-26 16:14:12 -0700274 @type servo_host: autotest_lib.server.hosts.servo_host.ServoHost
Ricky Liang0dd379c2014-04-23 16:29:08 +0800275 @param servo_serial: Serial number of the servo board.
Craig Harrison2b6c6fc2011-06-23 10:34:02 -0700276 """
Fang Deng5d518f42013-08-02 14:04:32 -0700277 # TODO(fdeng): crbug.com/298379
278 # We should move servo_host object out of servo object
279 # to minimize the dependencies on the rest of Autotest.
280 self._servo_host = servo_host
Ricky Liang0dd379c2014-04-23 16:29:08 +0800281 self._servo_serial = servo_serial
Richard Barnette180efe62016-12-02 23:20:44 +0000282 self._server = servo_host.get_servod_server_proxy()
J. Richard Barnette0c9c5882014-06-11 15:27:05 -0700283 self._power_state = _PowerStateController(self)
Congbin Guoa1f9cba2018-07-03 11:36:59 -0700284 self._uart = _Uart(self)
Fang Dengafb88142013-05-30 17:44:31 -0700285 self._usb_state = None
J. Richard Barnette8f19b392014-08-11 14:05:39 -0700286 self._programmer = None
Dana Goyette4dc0adc2019-05-06 14:51:53 -0700287 self._prev_log_inode = None
288 self._prev_log_size = 0
Gediminas Ramanauskasba352ad2012-11-09 09:43:32 -0800289
Ricky Liang0dd379c2014-04-23 16:29:08 +0800290 @property
291 def servo_serial(self):
292 """Returns the serial number of the servo board."""
293 return self._servo_serial
294
Dana Goyette4dc0adc2019-05-06 14:51:53 -0700295 def fetch_servod_log(self, filename=None, skip_old=False):
296 """Save the servod log into the given local file.
297
298 @param filename: save the contents into a file with the given name.
299 @param skip_old: if True, skip past the old data in the log file.
300 @type filename: str
301 @type skip_old: bool
302 @rtype: None
303 """
304 return self._servo_host.fetch_servod_log(filename, skip_old)
Ricky Liang0dd379c2014-04-23 16:29:08 +0800305
Tom Wai-Hong Tam22ee0e52013-04-03 13:36:39 +0800306 def get_power_state_controller(self):
J. Richard Barnette75136b32013-03-26 13:38:44 -0700307 """Return the power state controller for this Servo.
308
309 The power state controller provides board-independent
310 interfaces for reset, power-on, power-off operations.
311
312 """
313 return self._power_state
314
Fang Deng5d518f42013-08-02 14:04:32 -0700315
J. Richard Barnetteb6133972012-07-19 17:13:55 -0700316 def initialize_dut(self, cold_reset=False):
317 """Initializes a dut for testing purposes.
318
319 This sets various servo signals back to default values
320 appropriate for the target board. By default, if the DUT
321 is already on, it stays on. If the DUT is powered off
322 before initialization, its state afterward is unspecified.
323
J. Richard Barnetteb6133972012-07-19 17:13:55 -0700324 Rationale: Basic initialization of servo sets the lid open,
325 when there is a lid. This operation won't affect powered on
326 units; however, setting the lid open may power on a unit
J. Richard Barnette75136b32013-03-26 13:38:44 -0700327 that's off, depending on the board type and previous state
328 of the device.
329
J. Richard Barnette4b6af0d2014-06-05 09:57:20 -0700330 If `cold_reset` is a true value, the DUT and its EC will be
331 reset, and the DUT rebooted in normal mode.
J. Richard Barnetteb6133972012-07-19 17:13:55 -0700332
333 @param cold_reset If True, cold reset the device after
334 initialization.
335 """
Dana Goyettea2f00ea2019-06-26 16:14:12 -0700336 try:
337 self._server.hwinit()
338 except socket.error as e:
339 e.filename = '%s:%s' % (self._servo_host.hostname,
340 self._servo_host.servo_port)
341 raise
J. Richard Barnette8f19b392014-08-11 14:05:39 -0700342 self.set('usb_mux_oe1', 'on')
J. Richard Barnettecdd1edf2015-07-24 11:39:46 -0700343 self._usb_state = None
J. Richard Barnette8f19b392014-08-11 14:05:39 -0700344 self.switch_usbkey('off')
Congbin Guoa1f9cba2018-07-03 11:36:59 -0700345 self._uart.start_capture()
J. Richard Barnetteb6133972012-07-19 17:13:55 -0700346 if cold_reset:
J. Richard Barnette4b6af0d2014-06-05 09:57:20 -0700347 self._power_state.reset()
J. Richard Barnette1777f5d2014-09-03 15:18:19 -0700348 logging.debug('Servo initialized, version is %s',
349 self._server.get_version())
Ruben Rodriguez Buchillonb0e2a9f2019-08-12 12:40:44 -0700350 if self.has_control('init_keyboard'):
351 # This indicates the servod version does not
352 # have explicit keyboard initialization yet.
353 # Ignore this.
Ruben Rodriguez Buchillon05298242019-05-20 11:29:06 -0700354 # TODO(coconutruben): change this back to set() about a month
355 # after crrev.com/c/1586239 has been merged (or whenever that
356 # logic is in the labstation images).
357 self.set_nocheck('init_keyboard','on')
Craig Harrison2b6c6fc2011-06-23 10:34:02 -0700358
359
Tom Wai-Hong Tam1c86c7a2012-10-22 10:08:24 +0800360 def is_localhost(self):
361 """Is the servod hosted locally?
362
363 Returns:
364 True if local hosted; otherwise, False.
365 """
Fang Deng5d518f42013-08-02 14:04:32 -0700366 return self._servo_host.is_localhost()
Tom Wai-Hong Tam1c86c7a2012-10-22 10:08:24 +0800367
368
Mary Ruthvenecf12712019-06-26 17:36:21 -0700369 def get_os_version(self):
370 """Returns the chromeos release version."""
371 lsb_release_content = self.system_output('cat /etc/lsb-release',
372 ignore_status=True)
373 return lsbrelease_utils.get_chromeos_release_builder_path(
374 lsb_release_content=lsb_release_content)
375
376
Mary Ruthven83bb5952019-06-27 12:34:05 -0700377 def get_servod_version(self):
378 """Returns the servod version."""
379 result = self._servo_host.run('servod --version')
380 # TODO: use system_output once servod --version prints to stdout
381 stdout = result.stdout.strip()
382 return stdout if stdout else result.stderr.strip()
383
384
Craig Harrison2b6c6fc2011-06-23 10:34:02 -0700385 def power_long_press(self):
Chrome Bot9a1137d2011-07-19 14:35:00 -0700386 """Simulate a long power button press."""
J. Richard Barnettef12bff22012-07-18 14:41:06 -0700387 # After a long power press, the EC may ignore the next power
388 # button press (at least on Alex). To guarantee that this
389 # won't happen, we need to allow the EC one second to
390 # collect itself.
Ruben Rodriguez Buchillon84eba752018-07-27 17:59:48 +0800391 # long_press is defined as 8.5s in servod
392 self.set_nocheck('power_key', 'long_press')
Craig Harrison2b6c6fc2011-06-23 10:34:02 -0700393
394
395 def power_normal_press(self):
Chrome Bot9a1137d2011-07-19 14:35:00 -0700396 """Simulate a normal power button press."""
Ruben Rodriguez Buchillon84eba752018-07-27 17:59:48 +0800397 # press is defined as 1.2s in servod
398 self.set_nocheck('power_key', 'press')
Craig Harrison2b6c6fc2011-06-23 10:34:02 -0700399
400
401 def power_short_press(self):
Chrome Bot9a1137d2011-07-19 14:35:00 -0700402 """Simulate a short power button press."""
Ruben Rodriguez Buchillon84eba752018-07-27 17:59:48 +0800403 # tab is defined as 0.2s in servod
404 self.set_nocheck('power_key', 'tab')
Craig Harrison2b6c6fc2011-06-23 10:34:02 -0700405
406
Ruben Rodriguez Buchillon84eba752018-07-27 17:59:48 +0800407 def power_key(self, press_secs='tab'):
Craig Harrison2b6c6fc2011-06-23 10:34:02 -0700408 """Simulate a power button press.
409
Ruben Rodriguez Buchillon84eba752018-07-27 17:59:48 +0800410 @param press_secs: int, float, str; time to press key in seconds or
411 known shorthand: 'tab' 'press' 'long_press'
Craig Harrison2b6c6fc2011-06-23 10:34:02 -0700412 """
Ruben Rodriguez Buchillon84eba752018-07-27 17:59:48 +0800413 self.set_nocheck('power_key', press_secs)
Craig Harrison2b6c6fc2011-06-23 10:34:02 -0700414
415
416 def lid_open(self):
Yuli Huang7b82dcf2015-05-13 17:58:52 +0800417 """Simulate opening the lid and raise exception if all attempts fail"""
418 self.set('lid_open', 'yes')
Craig Harrison2b6c6fc2011-06-23 10:34:02 -0700419
420
421 def lid_close(self):
Yuli Huang7b82dcf2015-05-13 17:58:52 +0800422 """Simulate closing the lid and raise exception if all attempts fail
Craig Harrison48997262011-06-27 14:31:10 -0700423
424 Waits 6 seconds to ensure the device is fully asleep before returning.
425 """
Yuli Huang7b82dcf2015-05-13 17:58:52 +0800426 self.set('lid_open', 'no')
Chrome Bot9a1137d2011-07-19 14:35:00 -0700427 time.sleep(Servo.SLEEP_DELAY)
Craig Harrison2b6c6fc2011-06-23 10:34:02 -0700428
Shelley Chenc26575a2015-09-18 10:56:16 -0700429 def volume_up(self, timeout=300):
Kevin Chenge448a8b2016-09-09 10:18:11 -0700430 """Simulate pushing the volume down button.
431
432 @param timeout: Timeout for setting the volume.
433 """
Shelley Chenc26575a2015-09-18 10:56:16 -0700434 self.set_get_all(['volume_up:yes',
435 'sleep:%.4f' % self.SERVO_KEY_PRESS_DELAY,
436 'volume_up:no'])
437 # we need to wait for commands to take effect before moving on
438 time_left = float(timeout)
439 while time_left > 0.0:
440 value = self.get('volume_up')
441 if value == 'no':
442 return
443 time.sleep(self.SHORT_DELAY)
444 time_left = time_left - self.SHORT_DELAY
445 raise error.TestFail("Failed setting volume_up to no")
446
447 def volume_down(self, timeout=300):
Kevin Chenge448a8b2016-09-09 10:18:11 -0700448 """Simulate pushing the volume down button.
449
450 @param timeout: Timeout for setting the volume.
451 """
Shelley Chenc26575a2015-09-18 10:56:16 -0700452 self.set_get_all(['volume_down:yes',
453 'sleep:%.4f' % self.SERVO_KEY_PRESS_DELAY,
454 'volume_down:no'])
455 # we need to wait for commands to take effect before moving on
456 time_left = float(timeout)
457 while time_left > 0.0:
458 value = self.get('volume_down')
459 if value == 'no':
460 return
461 time.sleep(self.SHORT_DELAY)
462 time_left = time_left - self.SHORT_DELAY
463 raise error.TestFail("Failed setting volume_down to no")
Craig Harrison2b6c6fc2011-06-23 10:34:02 -0700464
Ruben Rodriguez Buchillon84eba752018-07-27 17:59:48 +0800465 def ctrl_d(self, press_secs='tab'):
Yusuf Mohsinally103441a2014-01-14 15:25:44 -0800466 """Simulate Ctrl-d simultaneous button presses.
Gediminas Ramanauskasba352ad2012-11-09 09:43:32 -0800467
Ruben Rodriguez Buchillon84eba752018-07-27 17:59:48 +0800468 @param press_secs: int, float, str; time to press key in seconds or
469 known shorthand: 'tab' 'press' 'long_press'
Gediminas Ramanauskasba352ad2012-11-09 09:43:32 -0800470 """
Ruben Rodriguez Buchillon84eba752018-07-27 17:59:48 +0800471 self.set_nocheck('ctrl_d', press_secs)
Gediminas Ramanauskas9da80f22012-11-14 12:59:43 -0800472
Gediminas Ramanauskasba352ad2012-11-09 09:43:32 -0800473
Ruben Rodriguez Buchillon84eba752018-07-27 17:59:48 +0800474 def ctrl_u(self, press_secs='tab'):
Yusuf Mohsinally103441a2014-01-14 15:25:44 -0800475 """Simulate Ctrl-u simultaneous button presses.
476
Ruben Rodriguez Buchillon84eba752018-07-27 17:59:48 +0800477 @param press_secs: int, float, str; time to press key in seconds or
478 known shorthand: 'tab' 'press' 'long_press'
Yusuf Mohsinally103441a2014-01-14 15:25:44 -0800479 """
Ruben Rodriguez Buchillon84eba752018-07-27 17:59:48 +0800480 self.set_nocheck('ctrl_u', press_secs)
Todd Broch9dfc3a82011-11-01 08:09:28 -0700481
482
Ruben Rodriguez Buchillon84eba752018-07-27 17:59:48 +0800483 def ctrl_enter(self, press_secs='tab'):
Yusuf Mohsinally103441a2014-01-14 15:25:44 -0800484 """Simulate Ctrl-enter simultaneous button presses.
485
Ruben Rodriguez Buchillon84eba752018-07-27 17:59:48 +0800486 @param press_secs: int, float, str; time to press key in seconds or
487 known shorthand: 'tab' 'press' 'long_press'
Yusuf Mohsinally103441a2014-01-14 15:25:44 -0800488 """
Ruben Rodriguez Buchillon84eba752018-07-27 17:59:48 +0800489 self.set_nocheck('ctrl_enter', press_secs)
Gediminas Ramanauskas3297d4f2012-09-10 15:30:10 -0700490
491
Ruben Rodriguez Buchillon84eba752018-07-27 17:59:48 +0800492 def ctrl_key(self, press_secs='tab'):
Yusuf Mohsinally103441a2014-01-14 15:25:44 -0800493 """Simulate Enter key button press.
494
Ruben Rodriguez Buchillon84eba752018-07-27 17:59:48 +0800495 @param press_secs: int, float, str; time to press key in seconds or
496 known shorthand: 'tab' 'press' 'long_press'
Yusuf Mohsinally103441a2014-01-14 15:25:44 -0800497 """
Ruben Rodriguez Buchillon84eba752018-07-27 17:59:48 +0800498 self.set_nocheck('ctrl_key', press_secs)
Todd Broch9dfc3a82011-11-01 08:09:28 -0700499
500
Ruben Rodriguez Buchillon84eba752018-07-27 17:59:48 +0800501 def enter_key(self, press_secs='tab'):
Yusuf Mohsinally103441a2014-01-14 15:25:44 -0800502 """Simulate Enter key button press.
503
Ruben Rodriguez Buchillon84eba752018-07-27 17:59:48 +0800504 @param press_secs: int, float, str; time to press key in seconds or
505 known shorthand: 'tab' 'press' 'long_press'
Yusuf Mohsinally103441a2014-01-14 15:25:44 -0800506 """
Ruben Rodriguez Buchillon84eba752018-07-27 17:59:48 +0800507 self.set_nocheck('enter_key', press_secs)
Todd Broch9753bd42012-03-21 10:15:08 -0700508
509
Ruben Rodriguez Buchillon84eba752018-07-27 17:59:48 +0800510 def refresh_key(self, press_secs='tab'):
Yusuf Mohsinally103441a2014-01-14 15:25:44 -0800511 """Simulate Refresh key (F3) button press.
512
Ruben Rodriguez Buchillon84eba752018-07-27 17:59:48 +0800513 @param press_secs: int, float, str; time to press key in seconds or
514 known shorthand: 'tab' 'press' 'long_press'
Yusuf Mohsinally103441a2014-01-14 15:25:44 -0800515 """
Ruben Rodriguez Buchillon84eba752018-07-27 17:59:48 +0800516 self.set_nocheck('refresh_key', press_secs)
Craig Harrison2b6c6fc2011-06-23 10:34:02 -0700517
518
Ruben Rodriguez Buchillon84eba752018-07-27 17:59:48 +0800519 def ctrl_refresh_key(self, press_secs='tab'):
Tom Wai-Hong Tam9e61e662012-08-01 15:10:07 +0800520 """Simulate Ctrl and Refresh (F3) simultaneous press.
521
522 This key combination is an alternative of Space key.
Yusuf Mohsinally103441a2014-01-14 15:25:44 -0800523
Ruben Rodriguez Buchillon84eba752018-07-27 17:59:48 +0800524 @param press_secs: int, float, str; time to press key in seconds or
525 known shorthand: 'tab' 'press' 'long_press'
Tom Wai-Hong Tam9e61e662012-08-01 15:10:07 +0800526 """
Ruben Rodriguez Buchillon84eba752018-07-27 17:59:48 +0800527 self.set_nocheck('ctrl_refresh_key', press_secs)
Tom Wai-Hong Tam9e61e662012-08-01 15:10:07 +0800528
529
Ruben Rodriguez Buchillon84eba752018-07-27 17:59:48 +0800530 def imaginary_key(self, press_secs='tab'):
Craig Harrison2b6c6fc2011-06-23 10:34:02 -0700531 """Simulate imaginary key button press.
532
533 Maps to a key that doesn't physically exist.
Yusuf Mohsinally103441a2014-01-14 15:25:44 -0800534
Ruben Rodriguez Buchillon84eba752018-07-27 17:59:48 +0800535 @param press_secs: int, float, str; time to press key in seconds or
536 known shorthand: 'tab' 'press' 'long_press'
Craig Harrison2b6c6fc2011-06-23 10:34:02 -0700537 """
Ruben Rodriguez Buchillon84eba752018-07-27 17:59:48 +0800538 self.set_nocheck('imaginary_key', press_secs)
Craig Harrison2b6c6fc2011-06-23 10:34:02 -0700539
540
Ruben Rodriguez Buchillon84eba752018-07-27 17:59:48 +0800541 def sysrq_x(self, press_secs='tab'):
Vincent Palatine7dc9282016-07-14 11:31:58 +0200542 """Simulate Alt VolumeUp X simulataneous press.
543
544 This key combination is the kernel system request (sysrq) X.
545
Ruben Rodriguez Buchillon84eba752018-07-27 17:59:48 +0800546 @param press_secs: int, float, str; time to press key in seconds or
547 known shorthand: 'tab' 'press' 'long_press'
Vincent Palatine7dc9282016-07-14 11:31:58 +0200548 """
Ruben Rodriguez Buchillon84eba752018-07-27 17:59:48 +0800549 self.set_nocheck('sysrq_x', press_secs)
Vincent Palatine7dc9282016-07-14 11:31:58 +0200550
551
Tom Wai-Hong Tam93b5c092015-05-14 02:50:43 +0800552 def toggle_recovery_switch(self):
553 """Toggle recovery switch on and off."""
554 self.enable_recovery_mode()
555 time.sleep(self.REC_TOGGLE_DELAY)
556 self.disable_recovery_mode()
557
558
Craig Harrison6b36b122011-06-28 17:58:43 -0700559 def enable_recovery_mode(self):
560 """Enable recovery mode on device."""
561 self.set('rec_mode', 'on')
562
563
564 def disable_recovery_mode(self):
565 """Disable recovery mode on device."""
566 self.set('rec_mode', 'off')
567
568
Tom Wai-Hong Tamf9ded092015-05-20 05:37:22 +0800569 def toggle_development_switch(self):
570 """Toggle development switch on and off."""
571 self.enable_development_mode()
572 time.sleep(self.DEV_TOGGLE_DELAY)
573 self.disable_development_mode()
574
575
Craig Harrison6b36b122011-06-28 17:58:43 -0700576 def enable_development_mode(self):
577 """Enable development mode on device."""
578 self.set('dev_mode', 'on')
579
580
581 def disable_development_mode(self):
582 """Disable development mode on device."""
583 self.set('dev_mode', 'off')
584
Craig Harrison2b6c6fc2011-06-23 10:34:02 -0700585 def boot_devmode(self):
586 """Boot a dev-mode device that is powered off."""
Tom Wai-Hong Tam23869102012-01-17 15:41:30 +0800587 self.power_short_press()
Craig Harrison48997262011-06-27 14:31:10 -0700588 self.pass_devmode()
589
590
591 def pass_devmode(self):
592 """Pass through boot screens in dev-mode."""
Chrome Bot9a1137d2011-07-19 14:35:00 -0700593 time.sleep(Servo.BOOT_DELAY)
Craig Harrison2b6c6fc2011-06-23 10:34:02 -0700594 self.ctrl_d()
Chrome Bot9a1137d2011-07-19 14:35:00 -0700595 time.sleep(Servo.BOOT_DELAY)
Craig Harrison48997262011-06-27 14:31:10 -0700596
Craig Harrison2b6c6fc2011-06-23 10:34:02 -0700597
Yusuf Mohsinally6c078ae2013-11-21 11:06:42 -0800598 def get_board(self):
Wai-Hong Tam0f904002017-09-19 15:52:22 -0700599 """Get the board connected to servod."""
Yusuf Mohsinally6c078ae2013-11-21 11:06:42 -0800600 return self._server.get_board()
601
602
Wai-Hong Tam0f904002017-09-19 15:52:22 -0700603 def get_base_board(self):
604 """Get the board of the base connected to servod."""
Wai-Hong Tam7f6169e2017-09-29 09:23:48 -0700605 try:
606 return self._server.get_base_board()
607 except xmlrpclib.Fault as e:
608 # TODO(waihong): Remove the following compatibility check when
609 # the new versions of hdctools are deployed.
610 if 'not supported' in str(e):
611 logging.warning('The servod is too old that get_base_board '
612 'not supported.')
613 return ''
614 raise
Wai-Hong Tam0f904002017-09-19 15:52:22 -0700615
616
Wai-Hong Tamcc399ee2017-12-08 12:43:28 -0800617 def get_ec_active_copy(self):
618 """Get the active copy of the EC image."""
619 return self.get('ec_active_copy')
620
621
Todd Brochefe72cb2012-07-11 19:58:53 -0700622 def _get_xmlrpclib_exception(self, xmlexc):
623 """Get meaningful exception string from xmlrpc.
624
625 Args:
626 xmlexc: xmlrpclib.Fault object
627
628 xmlrpclib.Fault.faultString has the following format:
629
630 <type 'exception type'>:'actual error message'
631
632 Parse and return the real exception from the servod side instead of the
633 less meaningful one like,
634 <Fault 1: "<type 'exceptions.AttributeError'>:'tca6416' object has no
635 attribute 'hw_driver'">
636
637 Returns:
638 string of underlying exception raised in servod.
639 """
640 return re.sub('^.*>:', '', xmlexc.faultString)
641
Ruben Rodriguez Buchillon2cba8e42019-08-12 11:31:53 -0700642 def has_control(self, control):
643 """Query servod server to determine if |control| is a valid control.
644
645 @param control: str, control name to query
646
647 @returns: true if |control| is a known control, false otherwise.
648 """
649 assert control
650 try:
651 # If the control exists, doc() will work.
652 self._server.doc(control)
653 return True
654 except xmlrpclib.Fault as e:
655 if re.search('No control %s' % control,
656 self._get_xmlrpclib_exception(e)):
657 return False
658 raise e
Todd Brochefe72cb2012-07-11 19:58:53 -0700659
Craig Harrison2b6c6fc2011-06-23 10:34:02 -0700660 def get(self, gpio_name):
J. Richard Barnettecdd1edf2015-07-24 11:39:46 -0700661 """Get the value of a gpio from Servod.
662
663 @param gpio_name Name of the gpio.
Ruben Rodriguez Buchillon2cba8e42019-08-12 11:31:53 -0700664
665 @returns: server response to |gpio_name| request.
666
667 @raise ControlUnavailableError: if |gpio_name| not a known control.
668 @raise error.TestFail: for all other failures doing get().
J. Richard Barnettecdd1edf2015-07-24 11:39:46 -0700669 """
Craig Harrison2b6c6fc2011-06-23 10:34:02 -0700670 assert gpio_name
Todd Brochefe72cb2012-07-11 19:58:53 -0700671 try:
Simran Basi1cbc5f22013-07-17 14:23:58 -0700672 return self._server.get(gpio_name)
Todd Brochefe72cb2012-07-11 19:58:53 -0700673 except xmlrpclib.Fault as e:
Ruben Rodriguez Buchillon2cba8e42019-08-12 11:31:53 -0700674 err_str = self._get_xmlrpclib_exception(e)
675 err_msg = "Getting '%s' :: %s" % (gpio_name, err_str)
676 unknown_ctrl = re.findall(NO_CONTROL_RE, err_str)
677 if unknown_ctrl:
678 raise ControlUnavailableError('No control named %r' %
679 unknown_ctrl[0])
680 else:
681 logging.error(err_msg)
682 raise error.TestFail(err_msg)
Craig Harrison2b6c6fc2011-06-23 10:34:02 -0700683
684
685 def set(self, gpio_name, gpio_value):
J. Richard Barnettecdd1edf2015-07-24 11:39:46 -0700686 """Set and check the value of a gpio using Servod.
687
688 @param gpio_name Name of the gpio.
689 @param gpio_value New setting for the gpio.
690 """
Chrome Bot9a1137d2011-07-19 14:35:00 -0700691 self.set_nocheck(gpio_name, gpio_value)
Todd Brochcf7c6652012-02-24 13:03:59 -0800692 retry_count = Servo.GET_RETRY_MAX
693 while gpio_value != self.get(gpio_name) and retry_count:
Ilja H. Friedel04be2bd2014-05-07 21:29:59 -0700694 logging.warning("%s != %s, retry %d", gpio_name, gpio_value,
Todd Brochcf7c6652012-02-24 13:03:59 -0800695 retry_count)
696 retry_count -= 1
697 time.sleep(Servo.SHORT_DELAY)
698 if not retry_count:
699 assert gpio_value == self.get(gpio_name), \
700 'Servo failed to set %s to %s' % (gpio_name, gpio_value)
Craig Harrison2b6c6fc2011-06-23 10:34:02 -0700701
702
703 def set_nocheck(self, gpio_name, gpio_value):
J. Richard Barnettecdd1edf2015-07-24 11:39:46 -0700704 """Set the value of a gpio using Servod.
705
706 @param gpio_name Name of the gpio.
707 @param gpio_value New setting for the gpio.
Ruben Rodriguez Buchillon2cba8e42019-08-12 11:31:53 -0700708
709 @raise ControlUnavailableError: if |gpio_name| not a known control.
710 @raise error.TestFail: for all other failures doing set().
J. Richard Barnettecdd1edf2015-07-24 11:39:46 -0700711 """
Ruben Rodriguez Buchillon1c0219c2019-06-25 12:30:44 -0700712 # The real danger here is to pass a None value through the xmlrpc.
713 assert gpio_name and gpio_value is not None
Mary Ruthvendfa45342019-06-25 10:10:17 -0700714 logging.debug('Setting %s to %r', gpio_name, gpio_value)
Todd Brochefe72cb2012-07-11 19:58:53 -0700715 try:
Simran Basi1cbc5f22013-07-17 14:23:58 -0700716 self._server.set(gpio_name, gpio_value)
Todd Brochefe72cb2012-07-11 19:58:53 -0700717 except xmlrpclib.Fault as e:
Ruben Rodriguez Buchillon2cba8e42019-08-12 11:31:53 -0700718 err_str = self._get_xmlrpclib_exception(e)
719 err_msg = "Setting '%s' :: %s" % (gpio_name, err_str)
720 unknown_ctrl = re.findall(NO_CONTROL_RE, err_str)
721 if unknown_ctrl:
722 raise ControlUnavailableError('No control named %r' %
723 unknown_ctrl[0])
724 else:
725 logging.error(err_msg)
726 raise error.TestFail(err_msg)
Craig Harrison2b6c6fc2011-06-23 10:34:02 -0700727
728
Tom Wai-Hong Tam92569bb2013-03-26 14:25:10 +0800729 def set_get_all(self, controls):
730 """Set &| get one or more control values.
731
732 @param controls: list of strings, controls to set &| get.
733
734 @raise: error.TestError in case error occurs setting/getting values.
735 """
736 rv = []
737 try:
Mary Ruthvendfa45342019-06-25 10:10:17 -0700738 logging.debug('Set/get all: %s', str(controls))
Tom Wai-Hong Tam92569bb2013-03-26 14:25:10 +0800739 rv = self._server.set_get_all(controls)
740 except xmlrpclib.Fault as e:
741 # TODO(waihong): Remove the following backward compatibility when
742 # the new versions of hdctools are deployed.
743 if 'not supported' in str(e):
744 logging.warning('The servod is too old that set_get_all '
745 'not supported. Use set and get instead.')
746 for control in controls:
747 if ':' in control:
748 (name, value) = control.split(':')
749 if name == 'sleep':
750 time.sleep(float(value))
751 else:
752 self.set_nocheck(name, value)
753 rv.append(True)
754 else:
755 rv.append(self.get(name))
756 else:
757 err_msg = "Problem with '%s' :: %s" % \
758 (controls, self._get_xmlrpclib_exception(e))
759 raise error.TestFail(err_msg)
760 return rv
761
762
Jon Salzc88e5b62011-11-30 14:38:54 +0800763 # TODO(waihong) It may fail if multiple servo's are connected to the same
764 # host. Should look for a better way, like the USB serial name, to identify
765 # the USB device.
Simran Basi741b5d42012-05-18 11:27:15 -0700766 # TODO(sbasi) Remove this code from autoserv once firmware tests have been
767 # updated.
Kevin Chenga22c4a82016-10-07 14:13:25 -0700768 def probe_host_usb_dev(self, timeout=_USB_PROBE_TIMEOUT):
Jon Salzc88e5b62011-11-30 14:38:54 +0800769 """Probe the USB disk device plugged-in the servo from the host side.
770
Kevin Chengeb06fe72016-08-22 15:26:32 -0700771 It uses servod to discover if there is a usb device attached to it.
Jon Salzc88e5b62011-11-30 14:38:54 +0800772
Kevin Chenga22c4a82016-10-07 14:13:25 -0700773 @param timeout The timeout period when probing for the usb host device.
774
775 @return: String of USB disk path (e.g. '/dev/sdb') or None.
Jon Salzc88e5b62011-11-30 14:38:54 +0800776 """
Ruben Rodriguez Buchillon9a4bfc32018-10-16 20:46:25 +0800777 # Set up Servo's usb mux.
778 self.switch_usbkey('host')
Kevin Chenga22c4a82016-10-07 14:13:25 -0700779 return self._server.probe_host_usb_dev(timeout) or None
Jon Salzc88e5b62011-11-30 14:38:54 +0800780
781
Mike Truty49153d82012-08-21 22:27:30 -0500782 def image_to_servo_usb(self, image_path=None,
783 make_image_noninteractive=False):
784 """Install an image to the USB key plugged into the servo.
785
786 This method may copy any image to the servo USB key including a
787 recovery image or a test image. These images are frequently used
788 for test purposes such as restoring a corrupted image or conducting
789 an upgrade of ec/fw/kernel as part of a test of a specific image part.
790
J. Richard Barnettecdd1edf2015-07-24 11:39:46 -0700791 @param image_path Path on the host to the recovery image.
792 @param make_image_noninteractive Make the recovery image
793 noninteractive, therefore the DUT
794 will reboot automatically after
795 installation.
Mike Truty49153d82012-08-21 22:27:30 -0500796 """
J. Richard Barnette41320ee2013-03-11 13:00:13 -0700797 # We're about to start plugging/unplugging the USB key. We
798 # don't know the state of the DUT, or what it might choose
799 # to do to the device after hotplug. To avoid surprises,
800 # force the DUT to be off.
801 self._server.hwinit()
Ruben Rodriguez Buchillonb0e2a9f2019-08-12 12:40:44 -0700802 if self.has_control('init_keyboard'):
803 # This indicates the servod version does not
804 # have explicit keyboard initialization yet.
805 # Ignore this.
Ruben Rodriguez Buchillonbd3b74f2019-08-08 16:18:27 -0700806 # TODO(coconutruben): change this back to set() about a month
807 # after crrev.com/c/1586239 has been merged (or whenever that
808 # logic is in the labstation images).
809 self.set_nocheck('init_keyboard','on')
J. Richard Barnette41320ee2013-03-11 13:00:13 -0700810 self._power_state.power_off()
Mike Truty49153d82012-08-21 22:27:30 -0500811
Mike Truty49153d82012-08-21 22:27:30 -0500812 if image_path:
Garry Wangd2656812019-08-07 17:29:16 -0700813 # Set up Servo's usb mux.
814 self.switch_usbkey('host')
Tom Wai-Hong Tam42f136d2012-10-26 11:11:23 +0800815 logging.info('Searching for usb device and copying image to it. '
816 'Please wait a few minutes...')
Mike Truty49153d82012-08-21 22:27:30 -0500817 if not self._server.download_image_to_usb(image_path):
818 logging.error('Failed to transfer requested image to USB. '
819 'Please take a look at Servo Logs.')
820 raise error.AutotestError('Download image to usb failed.')
821 if make_image_noninteractive:
822 logging.info('Making image noninteractive')
823 if not self._server.make_image_noninteractive():
824 logging.error('Failed to make image noninteractive. '
825 'Please take a look at Servo Logs.')
826
Kalin Stoyanovb3c11f32018-05-11 09:02:00 -0700827 def boot_in_recovery_mode(self):
828 """Boot host DUT in recovery mode."""
829 self._power_state.power_on(rec_mode=self._power_state.REC_ON)
830 self.switch_usbkey('dut')
831
Mike Truty49153d82012-08-21 22:27:30 -0500832
Simran Basi741b5d42012-05-18 11:27:15 -0700833 def install_recovery_image(self, image_path=None,
J. Richard Barnetteb6de7e32013-02-14 13:28:04 -0800834 make_image_noninteractive=False):
Dan Shic67f1332016-04-06 12:38:06 -0700835 """Install the recovery image specified by the path onto the DUT.
Jon Salzc88e5b62011-11-30 14:38:54 +0800836
837 This method uses google recovery mode to install a recovery image
838 onto a DUT through the use of a USB stick that is mounted on a servo
Tom Wai-Hong Tama07115e2012-01-09 12:27:01 +0800839 board specified by the usb_dev. If no image path is specified
Jon Salzc88e5b62011-11-30 14:38:54 +0800840 we use the recovery image already on the usb image.
841
Dan Shic67f1332016-04-06 12:38:06 -0700842 @param image_path: Path on the host to the recovery image.
843 @param make_image_noninteractive: Make the recovery image
844 noninteractive, therefore the DUT will reboot automatically
845 after installation.
Jon Salzc88e5b62011-11-30 14:38:54 +0800846 """
Mike Truty49153d82012-08-21 22:27:30 -0500847 self.image_to_servo_usb(image_path, make_image_noninteractive)
Garry Wangd2656812019-08-07 17:29:16 -0700848 # Give the DUT some time to power_off if we skip
849 # download image to usb. (crbug.com/982993)
850 if not image_path:
851 time.sleep(10)
Kalin Stoyanovb3c11f32018-05-11 09:02:00 -0700852 self.boot_in_recovery_mode()
Jon Salzc88e5b62011-11-30 14:38:54 +0800853
854
Vadim Bendeburyb80ba592012-12-07 15:02:34 -0800855 def _scp_image(self, image_path):
856 """Copy image to the servo host.
857
858 When programming a firmware image on the DUT, the image must be
859 located on the host to which the servo device is connected. Sometimes
860 servo is controlled by a remote host, in this case the image needs to
861 be transferred to the remote host.
862
863 @param image_path: a string, name of the firmware image file to be
864 transferred.
865 @return: a string, full path name of the copied file on the remote.
866 """
867
868 dest_path = os.path.join('/tmp', os.path.basename(image_path))
Fang Deng5d518f42013-08-02 14:04:32 -0700869 self._servo_host.send_file(image_path, dest_path)
Vadim Bendeburyb80ba592012-12-07 15:02:34 -0800870 return dest_path
871
872
Dan Shifecdaf42015-07-28 10:17:26 -0700873 def system(self, command, timeout=3600):
J. Richard Barnettecdd1edf2015-07-24 11:39:46 -0700874 """Execute the passed in command on the servod host.
875
876 @param command Command to be executed.
Dan Shifecdaf42015-07-28 10:17:26 -0700877 @param timeout Maximum number of seconds of runtime allowed. Default to
878 1 hour.
J. Richard Barnettecdd1edf2015-07-24 11:39:46 -0700879 """
Vadim Bendebury89ec24e2012-12-17 12:54:18 -0800880 logging.info('Will execute on servo host: %s', command)
Fang Deng5d518f42013-08-02 14:04:32 -0700881 self._servo_host.run(command, timeout=timeout)
Vadim Bendebury89ec24e2012-12-17 12:54:18 -0800882
883
Dan Shifecdaf42015-07-28 10:17:26 -0700884 def system_output(self, command, timeout=3600,
Vadim Bendebury89ec24e2012-12-17 12:54:18 -0800885 ignore_status=False, args=()):
886 """Execute the passed in command on the servod host, return stdout.
887
J. Richard Barnettecdd1edf2015-07-24 11:39:46 -0700888 @param command a string, the command to execute
889 @param timeout an int, max number of seconds to wait til command
Dan Shifecdaf42015-07-28 10:17:26 -0700890 execution completes. Default to 1 hour.
J. Richard Barnettecdd1edf2015-07-24 11:39:46 -0700891 @param ignore_status a Boolean, if true - ignore command's nonzero exit
Vadim Bendebury89ec24e2012-12-17 12:54:18 -0800892 status, otherwise an exception will be thrown
J. Richard Barnettecdd1edf2015-07-24 11:39:46 -0700893 @param args a tuple of strings, each becoming a separate command line
Vadim Bendebury89ec24e2012-12-17 12:54:18 -0800894 parameter for the command
J. Richard Barnettecdd1edf2015-07-24 11:39:46 -0700895 @return command's stdout as a string.
Vadim Bendebury89ec24e2012-12-17 12:54:18 -0800896 """
Fang Deng5d518f42013-08-02 14:04:32 -0700897 return self._servo_host.run(command, timeout=timeout,
898 ignore_status=ignore_status,
899 args=args).stdout.strip()
Vadim Bendeburyb80ba592012-12-07 15:02:34 -0800900
901
Mary Ruthven38d90af2019-08-16 13:13:31 -0700902 def get_servo_version(self, active=False):
Dan Shia5fef052015-05-18 23:28:47 -0700903 """Get the version of the servo, e.g., servo_v2 or servo_v3.
904
Mary Ruthven38d90af2019-08-16 13:13:31 -0700905 @param active: Only return the servo type with the active device.
Dan Shia5fef052015-05-18 23:28:47 -0700906 @return: The version of the servo.
907
908 """
Mary Ruthven38d90af2019-08-16 13:13:31 -0700909 servo_type = self._server.get_version()
910 if '_and_' not in servo_type or not active:
911 return servo_type
912
913 # If servo v4 is using ccd and servo micro, modify the servo type to
914 # reflect the active device.
915 active_device = self.get('active_v4_device')
916 if active_device in servo_type:
917 logging.info('%s is active', active_device)
918 return 'servo_v4_with_' + active_device
919
920 logging.warn("%s is active even though it's not in servo type",
921 active_device)
922 return servo_type
Dan Shia5fef052015-05-18 23:28:47 -0700923
924
Mary Ruthven2724ee62019-07-16 11:16:59 -0700925 def running_through_ccd(self):
926 """Returns True if the setup is using ccd to run."""
927 servo = self._server.get_version()
928 return 'ccd_cr50' in servo and 'servo_micro' not in servo
929
930
Tom Wai-Hong Tam4ac78982016-01-08 02:34:37 +0800931 def _initialize_programmer(self, rw_only=False):
932 """Initialize the firmware programmer.
933
934 @param rw_only: True to initialize a programmer which only
935 programs the RW portions.
936 """
J. Richard Barnette8f19b392014-08-11 14:05:39 -0700937 if self._programmer:
938 return
939 # Initialize firmware programmer
Dan Shia5fef052015-05-18 23:28:47 -0700940 servo_version = self.get_servo_version()
Dan Shi9cb0eec2014-06-03 09:04:50 -0700941 if servo_version.startswith('servo_v2'):
J. Richard Barnette8f19b392014-08-11 14:05:39 -0700942 self._programmer = firmware_programmer.ProgrammerV2(self)
Wai-Hong Tamc36c4d22016-09-09 10:39:45 -0700943 self._programmer_rw = firmware_programmer.ProgrammerV2RwOnly(self)
Kevin Chengdf2e29f2016-09-09 02:31:22 -0700944 # Both servo v3 and v4 use the same programming methods so just leverage
945 # ProgrammerV3 for servo v4 as well.
946 elif (servo_version.startswith('servo_v3') or
947 servo_version.startswith('servo_v4')):
Dan Shia5fef052015-05-18 23:28:47 -0700948 self._programmer = firmware_programmer.ProgrammerV3(self)
Tom Wai-Hong Tam4ac78982016-01-08 02:34:37 +0800949 self._programmer_rw = firmware_programmer.ProgrammerV3RwOnly(self)
J. Richard Barnette8f19b392014-08-11 14:05:39 -0700950 else:
951 raise error.TestError(
952 'No firmware programmer for servo version: %s' %
Congbin Guo42427612019-02-12 10:22:06 -0800953 servo_version)
J. Richard Barnette8f19b392014-08-11 14:05:39 -0700954
955
Tom Wai-Hong Tam4ac78982016-01-08 02:34:37 +0800956 def program_bios(self, image, rw_only=False):
Yusuf Mohsinally6c078ae2013-11-21 11:06:42 -0800957 """Program bios on DUT with given image.
Vadim Bendebury1a7ec632013-02-17 16:22:09 -0800958
Yusuf Mohsinally6c078ae2013-11-21 11:06:42 -0800959 @param image: a string, file name of the BIOS image to program
960 on the DUT.
Tom Wai-Hong Tam4ac78982016-01-08 02:34:37 +0800961 @param rw_only: True to only program the RW portion of BIOS.
Yusuf Mohsinally6c078ae2013-11-21 11:06:42 -0800962
Vadim Bendebury1a7ec632013-02-17 16:22:09 -0800963 """
J. Richard Barnette8f19b392014-08-11 14:05:39 -0700964 self._initialize_programmer()
Ricky Liangc31aab32014-07-03 16:23:29 +0800965 if not self.is_localhost():
966 image = self._scp_image(image)
Tom Wai-Hong Tam4ac78982016-01-08 02:34:37 +0800967 if rw_only:
968 self._programmer_rw.program_bios(image)
969 else:
970 self._programmer.program_bios(image)
Vadim Bendeburyb80ba592012-12-07 15:02:34 -0800971
972
Tom Wai-Hong Tam4ac78982016-01-08 02:34:37 +0800973 def program_ec(self, image, rw_only=False):
Yusuf Mohsinally6c078ae2013-11-21 11:06:42 -0800974 """Program ec on DUT with given image.
Vadim Bendebury1a7ec632013-02-17 16:22:09 -0800975
Yusuf Mohsinally6c078ae2013-11-21 11:06:42 -0800976 @param image: a string, file name of the EC image to program
977 on the DUT.
Tom Wai-Hong Tam4ac78982016-01-08 02:34:37 +0800978 @param rw_only: True to only program the RW portion of EC.
Vadim Bendebury1a7ec632013-02-17 16:22:09 -0800979
Vadim Bendebury1a7ec632013-02-17 16:22:09 -0800980 """
J. Richard Barnette8f19b392014-08-11 14:05:39 -0700981 self._initialize_programmer()
Ricky Liangc31aab32014-07-03 16:23:29 +0800982 if not self.is_localhost():
983 image = self._scp_image(image)
Tom Wai-Hong Tam4ac78982016-01-08 02:34:37 +0800984 if rw_only:
Congbin Guo42427612019-02-12 10:22:06 -0800985 self._programmer_rw.program_ec(image)
Tom Wai-Hong Tam4ac78982016-01-08 02:34:37 +0800986 else:
Congbin Guo42427612019-02-12 10:22:06 -0800987 self._programmer.program_ec(image)
988
989
990 def _reprogram(self, tarball_path, firmware_name, image_candidates,
991 rw_only):
992 """Helper function to reprogram firmware for EC or BIOS.
993
994 @param tarball_path: The path of the downloaded build tarball.
995 @param: firmware_name: either 'EC' or 'BIOS'.
996 @param image_candidates: A tuple of the paths of image candidates.
997 @param rw_only: True to only install firmware to its RW portions. Keep
998 the RO portions unchanged.
999
1000 @raise: TestError if cannot extract firmware from the tarball.
1001 """
Mary Ruthven771b2012019-08-19 12:18:49 -07001002 dest_dir = os.path.join(os.path.dirname(tarball_path), firmware_name)
1003 # Create the firmware_name subdirectory if it doesn't exist.
1004 if not os.path.exists(dest_dir):
1005 os.mkdir(dest_dir)
Congbin Guo42427612019-02-12 10:22:06 -08001006 image = _extract_image_from_tarball(tarball_path, dest_dir,
1007 image_candidates)
1008 if not image:
1009 if firmware_name == 'EC':
1010 logging.info('Not a Chrome EC, ignore re-programming it')
1011 return
1012 else:
1013 raise error.TestError('Failed to extract the %s image from '
1014 'tarball' % firmware_name)
1015
1016 logging.info('Will re-program %s %snow', firmware_name,
1017 'RW ' if rw_only else '')
1018
1019 if firmware_name == 'EC':
1020 self.program_ec(os.path.join(dest_dir, image), rw_only)
1021 else:
1022 self.program_bios(os.path.join(dest_dir, image), rw_only)
1023
1024
Shelley Chenac61d5a2019-06-24 15:35:46 -07001025 def program_firmware(self, board, model, tarball_path, rw_only=False):
Congbin Guo42427612019-02-12 10:22:06 -08001026 """Program firmware (EC, if applied, and BIOS) of the DUT.
1027
Shelley Chenac61d5a2019-06-24 15:35:46 -07001028 @param board: The DUT board name.
Congbin Guo42427612019-02-12 10:22:06 -08001029 @param model: The DUT model name.
1030 @param tarball_path: The path of the downloaded build tarball.
1031 @param rw_only: True to only install firmware to its RW portions. Keep
1032 the RO portions unchanged.
1033 """
Shelley Chenac61d5a2019-06-24 15:35:46 -07001034 ap_image_candidates = ('image.bin', 'image-%s.bin' % model,
1035 'image-%s.bin' % board)
1036 ec_image_candidates = ('ec.bin', '%s/ec.bin' % model,
1037 '%s/ec.bin' % board)
Congbin Guo42427612019-02-12 10:22:06 -08001038
1039 self._reprogram(tarball_path, 'EC', ec_image_candidates, rw_only)
1040 self._reprogram(tarball_path, 'BIOS', ap_image_candidates, rw_only)
1041
1042 self.get_power_state_controller().reset()
1043 time.sleep(Servo.BOOT_DELAY)
Vadim Bendeburye7bd9362012-12-19 14:35:20 -08001044
Fang Dengafb88142013-05-30 17:44:31 -07001045
1046 def _switch_usbkey_power(self, power_state, detection_delay=False):
1047 """Switch usbkey power.
1048
1049 This function switches usbkey power by setting the value of
1050 'prtctl4_pwren'. If the power is already in the
1051 requested state, this function simply returns.
1052
1053 @param power_state: A string, 'on' or 'off'.
1054 @param detection_delay: A boolean value, if True, sleep
1055 for |USB_DETECTION_DELAY| after switching
1056 the power on.
1057 """
Kevin Chenga22c4a82016-10-07 14:13:25 -07001058 # TODO(kevcheng): Forgive me for this terrible hack. This is just to
1059 # handle beaglebones that haven't yet updated and have the
1060 # safe_switch_usbkey_power RPC. I'll remove this once all beaglebones
1061 # have been updated and also think about a better way to handle
1062 # situations like this.
1063 try:
1064 self._server.safe_switch_usbkey_power(power_state)
1065 except Exception:
1066 self.set('prtctl4_pwren', power_state)
Fang Dengafb88142013-05-30 17:44:31 -07001067 if power_state == 'off':
1068 time.sleep(self.USB_POWEROFF_DELAY)
1069 elif detection_delay:
1070 time.sleep(self.USB_DETECTION_DELAY)
1071
1072
1073 def switch_usbkey(self, usb_state):
1074 """Connect USB flash stick to either host or DUT, or turn USB port off.
Vadim Bendeburye7bd9362012-12-19 14:35:20 -08001075
1076 This function switches the servo multiplexer to provide electrical
Fang Dengafb88142013-05-30 17:44:31 -07001077 connection between the USB port J3 and either host or DUT side. It
1078 can also be used to turn the USB port off.
Vadim Bendeburye7bd9362012-12-19 14:35:20 -08001079
Fang Dengafb88142013-05-30 17:44:31 -07001080 Switching to 'dut' or 'host' is accompanied by powercycling
1081 of the USB stick, because it sometimes gets wedged if the mux
1082 is switched while the stick power is on.
Vadim Bendeburye7bd9362012-12-19 14:35:20 -08001083
Fang Dengafb88142013-05-30 17:44:31 -07001084 @param usb_state: A string, one of 'dut', 'host', or 'off'.
1085 'dut' and 'host' indicate which side the
1086 USB flash device is required to be connected to.
1087 'off' indicates turning the USB port off.
Vadim Bendeburye7bd9362012-12-19 14:35:20 -08001088
Fang Dengafb88142013-05-30 17:44:31 -07001089 @raise: error.TestError in case the parameter is not 'dut'
1090 'host', or 'off'.
Vadim Bendeburye7bd9362012-12-19 14:35:20 -08001091 """
Fang Dengafb88142013-05-30 17:44:31 -07001092 if self.get_usbkey_direction() == usb_state:
Vadim Bendeburye7bd9362012-12-19 14:35:20 -08001093 return
1094
Fang Dengafb88142013-05-30 17:44:31 -07001095 if usb_state == 'off':
Dan Shia5fef052015-05-18 23:28:47 -07001096 self._switch_usbkey_power('off')
1097 self._usb_state = usb_state
1098 return
Fang Dengafb88142013-05-30 17:44:31 -07001099 elif usb_state == 'host':
Vadim Bendeburye7bd9362012-12-19 14:35:20 -08001100 mux_direction = 'servo_sees_usbkey'
Fang Dengafb88142013-05-30 17:44:31 -07001101 elif usb_state == 'dut':
Vadim Bendeburye7bd9362012-12-19 14:35:20 -08001102 mux_direction = 'dut_sees_usbkey'
1103 else:
Fang Dengafb88142013-05-30 17:44:31 -07001104 raise error.TestError('Unknown USB state request: %s' % usb_state)
Vadim Bendeburye7bd9362012-12-19 14:35:20 -08001105
Fang Dengafb88142013-05-30 17:44:31 -07001106 self._switch_usbkey_power('off')
Kevin Chenga22c4a82016-10-07 14:13:25 -07001107 # TODO(kevcheng): Forgive me for this terrible hack. This is just to
1108 # handle beaglebones that haven't yet updated and have the
1109 # safe_switch_usbkey RPC. I'll remove this once all beaglebones have
1110 # been updated and also think about a better way to handle situations
1111 # like this.
1112 try:
1113 self._server.safe_switch_usbkey(mux_direction)
1114 except Exception:
1115 self.set('usb_mux_sel1', mux_direction)
Vadim Bendeburye7bd9362012-12-19 14:35:20 -08001116 time.sleep(self.USB_POWEROFF_DELAY)
Fang Dengafb88142013-05-30 17:44:31 -07001117 self._switch_usbkey_power('on', usb_state == 'host')
1118 self._usb_state = usb_state
Vadim Bendeburye7bd9362012-12-19 14:35:20 -08001119
Vadim Bendeburye7bd9362012-12-19 14:35:20 -08001120
Vadim Bendeburye7bd9362012-12-19 14:35:20 -08001121 def get_usbkey_direction(self):
Fang Dengafb88142013-05-30 17:44:31 -07001122 """Get which side USB is connected to or 'off' if usb power is off.
Vadim Bendeburye7bd9362012-12-19 14:35:20 -08001123
Fang Dengafb88142013-05-30 17:44:31 -07001124 @return: A string, one of 'dut', 'host', or 'off'.
Vadim Bendeburye7bd9362012-12-19 14:35:20 -08001125 """
Fang Dengafb88142013-05-30 17:44:31 -07001126 if not self._usb_state:
1127 if self.get('prtctl4_pwren') == 'off':
1128 self._usb_state = 'off'
1129 elif self.get('usb_mux_sel1').startswith('dut'):
1130 self._usb_state = 'dut'
Vadim Bendeburye7bd9362012-12-19 14:35:20 -08001131 else:
Fang Dengafb88142013-05-30 17:44:31 -07001132 self._usb_state = 'host'
1133 return self._usb_state
Congbin Guoa1f9cba2018-07-03 11:36:59 -07001134
1135
Wai-Hong Tam60377262018-03-01 10:55:39 -08001136 def set_servo_v4_role(self, role):
1137 """Set the power role of servo v4, either 'src' or 'snk'.
1138
1139 It does nothing if not a servo v4.
1140
1141 @param role: Power role for DUT port on servo v4, either 'src' or 'snk'.
1142 """
1143 servo_version = self.get_servo_version()
1144 if servo_version.startswith('servo_v4'):
1145 value = self.get('servo_v4_role')
1146 if value != role:
1147 self.set_nocheck('servo_v4_role', role)
1148 else:
1149 logging.debug('Already in the role: %s.', role)
1150 else:
1151 logging.debug('Not a servo v4, unable to set role to %s.', role)
1152
1153
Mary Ruthven739b2922019-08-22 11:16:06 -07001154 def set_servo_v4_dts_mode(self, state):
1155 """Set servo v4 dts mode to off or on.
1156
1157 It does nothing if not a servo v4. Disable the ccd watchdog if we're
1158 disabling dts mode. CCD will disconnect. The watchdog only allows CCD
1159 to disconnect for 10 seconds until it kills servod. Disable the
1160 watchdog, so CCD can stay disconnected indefinitely.
1161
1162 @param state: Set servo v4 dts mode 'off' or 'on'.
1163 """
1164 servo_version = self.get_servo_version()
1165 if not servo_version.startswith('servo_v4'):
1166 logging.debug('Not a servo v4, unable to set dts mode %s.', state)
1167 return
1168
1169 # TODO(mruthven): remove watchdog check once the labstation has been
1170 # updated to have support for modifying the watchdog.
1171 set_watchdog = self.has_control('watchdog') and 'ccd' in servo_version
1172 enable_watchdog = state == 'on'
1173
1174 if set_watchdog and not enable_watchdog:
1175 self.set_nocheck('watchdog_remove', 'ccd')
1176
1177 self.set_nocheck('servo_v4_dts_mode', state)
1178
1179 if set_watchdog and enable_watchdog:
1180 self.set_nocheck('watchdog_add', 'ccd')
1181
1182
Congbin Guofc3b8962019-03-22 17:38:46 -07001183 @property
1184 def uart_logs_dir(self):
1185 """Return the directory to save UART logs."""
1186 return self._uart.logs_dir if self._uart else ""
Congbin Guoa1f9cba2018-07-03 11:36:59 -07001187
Congbin Guofc3b8962019-03-22 17:38:46 -07001188
1189 @uart_logs_dir.setter
1190 def uart_logs_dir(self, logs_dir):
1191 """Set directory to save UART logs.
1192
1193 @param logs_dir String of directory name."""
Congbin Guoa1f9cba2018-07-03 11:36:59 -07001194 if self._uart:
Congbin Guofc3b8962019-03-22 17:38:46 -07001195 self._uart.logs_dir = logs_dir
Congbin Guoa1f9cba2018-07-03 11:36:59 -07001196
1197
1198 def close(self):
1199 """Close the servo object."""
1200 if self._uart:
1201 self._uart.stop_capture()
Congbin Guofc3b8962019-03-22 17:38:46 -07001202 self._uart.dump()
Congbin Guoa1f9cba2018-07-03 11:36:59 -07001203 self._uart = None