blob: 92accc8344e065ba150065a8023f367323247c7f [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
Ruben Rodriguez Buchillonecb525f2019-09-03 14:52:14 -0700160 @returns True if the operation completes successfully.
161 False if the UART capturing is not supported or failed due to
162 an error.
Congbin Guo0f00be82019-04-18 17:51:14 -0700163 """
164 logging.debug('%s capturing %s UART.', 'Start' if start else 'Stop',
165 uart)
Ruben Rodriguez Buchillonb0e2a9f2019-08-12 12:40:44 -0700166 uart_cmd = '%s_uart_capture' % uart
Ruben Rodriguez Buchillonecb525f2019-09-03 14:52:14 -0700167 target_level = 'on' if start else 'off'
168 level = None
Ruben Rodriguez Buchillonb0e2a9f2019-08-12 12:40:44 -0700169 if self._servo.has_control(uart_cmd):
Ruben Rodriguez Buchillon9f485642019-08-21 14:32:22 -0700170 # Do our own implementation of set() here as not_applicable
171 # should also count as a valid control.
Ruben Rodriguez Buchillonecb525f2019-09-03 14:52:14 -0700172 logging.debug('Trying to set %s to %s.', uart_cmd, target_level)
173 try:
174 self._servo.set_nocheck(uart_cmd, target_level)
175 level = self._servo.get(uart_cmd)
176 except error.TestFail as e:
177 # Any sort of test failure here should not stop the test. This
178 # is just to capture more output. Log and move on.
179 logging.warning('Failed to set %s to %s. %s. Ignoring.',
180 uart_cmd, target_level, str(e))
181 if level == target_level:
182 logging.debug('Managed to set %s to %s.', uart_cmd, level)
183 else:
184 logging.debug('Failed to set %s to %s. Got %s.', uart_cmd,
185 target_level, level)
186 return level == target_level
Congbin Guo0f00be82019-04-18 17:51:14 -0700187
188 def start_capture(self):
189 """Start capturing UART streams."""
190 for uart in self._UartToCapture:
191 if self._start_stop_capture(uart, True):
192 self._streams.append(('%s_uart_stream' % uart, '%s_uart.log' %
193 uart))
Congbin Guoa1f9cba2018-07-03 11:36:59 -0700194
Congbin Guofc3b8962019-03-22 17:38:46 -0700195 def dump(self):
196 """Dump UART streams to log files accordingly."""
Congbin Guo0f00be82019-04-18 17:51:14 -0700197 if not self.logs_dir:
Congbin Guofc3b8962019-03-22 17:38:46 -0700198 return
Congbin Guoa1f9cba2018-07-03 11:36:59 -0700199
Congbin Guoa1f9cba2018-07-03 11:36:59 -0700200 for stream, logfile in self._streams:
Congbin Guo0f00be82019-04-18 17:51:14 -0700201 logfile_fullname = os.path.join(self.logs_dir, logfile)
Congbin Guoa1f9cba2018-07-03 11:36:59 -0700202 try:
203 content = self._servo.get(stream)
204 except Exception as err:
205 logging.warn('Failed to get UART log for %s: %s', stream, err)
206 continue
207
Kuang-che Wu12192fc2019-08-31 09:55:00 +0800208 if content == 'not_applicable':
209 logging.warn('%s is not applicable', stream)
210 continue
211
Congbin Guoa1f9cba2018-07-03 11:36:59 -0700212 # The UART stream may contain non-printable characters, and servo
213 # returns it in string representation. We use `ast.leteral_eval`
214 # to revert it back.
215 with open(logfile_fullname, 'a') as fd:
Kuang-che Wu12192fc2019-08-31 09:55:00 +0800216 try:
217 fd.write(ast.literal_eval(content))
218 except ValueError:
219 logging.exception('Invalid value for %s: %r', stream,
220 content)
Congbin Guoa1f9cba2018-07-03 11:36:59 -0700221
222 def stop_capture(self):
223 """Stop capturing UART streams."""
Congbin Guo0f00be82019-04-18 17:51:14 -0700224 for uart in self._UartToCapture:
Congbin Guoa1f9cba2018-07-03 11:36:59 -0700225 try:
Congbin Guo0f00be82019-04-18 17:51:14 -0700226 self._start_stop_capture(uart, False)
Congbin Guoa1f9cba2018-07-03 11:36:59 -0700227 except Exception as err:
228 logging.warn('Failed to stop UART logging for %s: %s', uart,
229 err)
230
231
J. Richard Barnette384056b2012-04-16 11:04:46 -0700232class Servo(object):
J. Richard Barnette41320ee2013-03-11 13:00:13 -0700233
Craig Harrison2b6c6fc2011-06-23 10:34:02 -0700234 """Manages control of a Servo board.
235
236 Servo is a board developed by hardware group to aide in the debug and
237 control of various partner devices. Servo's features include the simulation
238 of pressing the power button, closing the lid, and pressing Ctrl-d. This
239 class manages setting up and communicating with a servo demon (servod)
240 process. It provides both high-level functions for common servo tasks and
241 low-level functions for directly setting and reading gpios.
J. Richard Barnette41320ee2013-03-11 13:00:13 -0700242
Craig Harrison2b6c6fc2011-06-23 10:34:02 -0700243 """
244
Chrome Bot9a1137d2011-07-19 14:35:00 -0700245 # Power button press delays in seconds.
J. Richard Barnetted2e4cbd2012-06-29 12:18:40 -0700246 #
J. Richard Barnette5383f072012-07-26 17:35:40 -0700247 # The EC specification says that 8.0 seconds should be enough
248 # for the long power press. However, some platforms need a bit
249 # more time. Empirical testing has found these requirements:
250 # Alex: 8.2 seconds
251 # ZGB: 8.5 seconds
252 # The actual value is set to the largest known necessary value.
253 #
254 # TODO(jrbarnette) Being generous is the right thing to do for
255 # existing platforms, but if this code is to be used for
256 # qualification of new hardware, we should be less generous.
Chrome Bot9a1137d2011-07-19 14:35:00 -0700257 SHORT_DELAY = 0.1
J. Richard Barnette5383f072012-07-26 17:35:40 -0700258
Todd Broch31c82502011-08-29 08:14:39 -0700259 # Maximum number of times to re-read power button on release.
Todd Brochcf7c6652012-02-24 13:03:59 -0800260 GET_RETRY_MAX = 10
Chrome Bot9a1137d2011-07-19 14:35:00 -0700261
J. Richard Barnette5383f072012-07-26 17:35:40 -0700262 # Delays to deal with DUT state transitions.
Chrome Bot9a1137d2011-07-19 14:35:00 -0700263 SLEEP_DELAY = 6
264 BOOT_DELAY = 10
J. Richard Barnette41320ee2013-03-11 13:00:13 -0700265
J. Richard Barnette41320ee2013-03-11 13:00:13 -0700266 # Default minimum time interval between 'press' and 'release'
267 # keyboard events.
Vic Yang0aca1c22012-11-19 18:33:56 -0800268 SERVO_KEY_PRESS_DELAY = 0.1
Craig Harrison2b6c6fc2011-06-23 10:34:02 -0700269
Tom Wai-Hong Tam93b5c092015-05-14 02:50:43 +0800270 # Time to toggle recovery switch on and off.
271 REC_TOGGLE_DELAY = 0.1
272
Tom Wai-Hong Tamf9ded092015-05-20 05:37:22 +0800273 # Time to toggle development switch on and off.
274 DEV_TOGGLE_DELAY = 0.1
275
Jon Salzc88e5b62011-11-30 14:38:54 +0800276 # Time between an usb disk plugged-in and detected in the system.
277 USB_DETECTION_DELAY = 10
Vadim Bendeburye7bd9362012-12-19 14:35:20 -0800278 # Time to keep USB power off before and after USB mux direction is changed
279 USB_POWEROFF_DELAY = 2
Jon Salzc88e5b62011-11-30 14:38:54 +0800280
Simran Basib7850bb2013-07-24 12:33:42 -0700281 # Time to wait before timing out on servo initialization.
282 INIT_TIMEOUT_SECS = 10
Simran Basi59331342013-07-12 12:06:29 -0700283
J. Richard Barnette67ccb872012-04-19 16:34:56 -0700284
Ricky Liang0dd379c2014-04-23 16:29:08 +0800285 def __init__(self, servo_host, servo_serial=None):
Craig Harrison2b6c6fc2011-06-23 10:34:02 -0700286 """Sets up the servo communication infrastructure.
287
Fang Deng5d518f42013-08-02 14:04:32 -0700288 @param servo_host: A ServoHost object representing
289 the host running servod.
Dana Goyettea2f00ea2019-06-26 16:14:12 -0700290 @type servo_host: autotest_lib.server.hosts.servo_host.ServoHost
Ricky Liang0dd379c2014-04-23 16:29:08 +0800291 @param servo_serial: Serial number of the servo board.
Craig Harrison2b6c6fc2011-06-23 10:34:02 -0700292 """
Fang Deng5d518f42013-08-02 14:04:32 -0700293 # TODO(fdeng): crbug.com/298379
294 # We should move servo_host object out of servo object
295 # to minimize the dependencies on the rest of Autotest.
296 self._servo_host = servo_host
Ricky Liang0dd379c2014-04-23 16:29:08 +0800297 self._servo_serial = servo_serial
Richard Barnette180efe62016-12-02 23:20:44 +0000298 self._server = servo_host.get_servod_server_proxy()
J. Richard Barnette0c9c5882014-06-11 15:27:05 -0700299 self._power_state = _PowerStateController(self)
Congbin Guoa1f9cba2018-07-03 11:36:59 -0700300 self._uart = _Uart(self)
Fang Dengafb88142013-05-30 17:44:31 -0700301 self._usb_state = None
J. Richard Barnette8f19b392014-08-11 14:05:39 -0700302 self._programmer = None
Dana Goyette4dc0adc2019-05-06 14:51:53 -0700303 self._prev_log_inode = None
304 self._prev_log_size = 0
Gediminas Ramanauskasba352ad2012-11-09 09:43:32 -0800305
Ricky Liang0dd379c2014-04-23 16:29:08 +0800306 @property
307 def servo_serial(self):
308 """Returns the serial number of the servo board."""
309 return self._servo_serial
310
Dana Goyette4dc0adc2019-05-06 14:51:53 -0700311 def fetch_servod_log(self, filename=None, skip_old=False):
312 """Save the servod log into the given local file.
313
314 @param filename: save the contents into a file with the given name.
315 @param skip_old: if True, skip past the old data in the log file.
316 @type filename: str
317 @type skip_old: bool
318 @rtype: None
319 """
320 return self._servo_host.fetch_servod_log(filename, skip_old)
Ricky Liang0dd379c2014-04-23 16:29:08 +0800321
Tom Wai-Hong Tam22ee0e52013-04-03 13:36:39 +0800322 def get_power_state_controller(self):
J. Richard Barnette75136b32013-03-26 13:38:44 -0700323 """Return the power state controller for this Servo.
324
325 The power state controller provides board-independent
326 interfaces for reset, power-on, power-off operations.
327
328 """
329 return self._power_state
330
Fang Deng5d518f42013-08-02 14:04:32 -0700331
J. Richard Barnetteb6133972012-07-19 17:13:55 -0700332 def initialize_dut(self, cold_reset=False):
333 """Initializes a dut for testing purposes.
334
335 This sets various servo signals back to default values
336 appropriate for the target board. By default, if the DUT
337 is already on, it stays on. If the DUT is powered off
338 before initialization, its state afterward is unspecified.
339
J. Richard Barnetteb6133972012-07-19 17:13:55 -0700340 Rationale: Basic initialization of servo sets the lid open,
341 when there is a lid. This operation won't affect powered on
342 units; however, setting the lid open may power on a unit
J. Richard Barnette75136b32013-03-26 13:38:44 -0700343 that's off, depending on the board type and previous state
344 of the device.
345
J. Richard Barnette4b6af0d2014-06-05 09:57:20 -0700346 If `cold_reset` is a true value, the DUT and its EC will be
347 reset, and the DUT rebooted in normal mode.
J. Richard Barnetteb6133972012-07-19 17:13:55 -0700348
349 @param cold_reset If True, cold reset the device after
350 initialization.
351 """
Dana Goyettea2f00ea2019-06-26 16:14:12 -0700352 try:
353 self._server.hwinit()
354 except socket.error as e:
355 e.filename = '%s:%s' % (self._servo_host.hostname,
356 self._servo_host.servo_port)
357 raise
J. Richard Barnette8f19b392014-08-11 14:05:39 -0700358 self.set('usb_mux_oe1', 'on')
J. Richard Barnettecdd1edf2015-07-24 11:39:46 -0700359 self._usb_state = None
J. Richard Barnette8f19b392014-08-11 14:05:39 -0700360 self.switch_usbkey('off')
Congbin Guoa1f9cba2018-07-03 11:36:59 -0700361 self._uart.start_capture()
J. Richard Barnetteb6133972012-07-19 17:13:55 -0700362 if cold_reset:
J. Richard Barnette4b6af0d2014-06-05 09:57:20 -0700363 self._power_state.reset()
J. Richard Barnette1777f5d2014-09-03 15:18:19 -0700364 logging.debug('Servo initialized, version is %s',
365 self._server.get_version())
Ruben Rodriguez Buchillonb0e2a9f2019-08-12 12:40:44 -0700366 if self.has_control('init_keyboard'):
367 # This indicates the servod version does not
368 # have explicit keyboard initialization yet.
369 # Ignore this.
Ruben Rodriguez Buchillon05298242019-05-20 11:29:06 -0700370 # TODO(coconutruben): change this back to set() about a month
371 # after crrev.com/c/1586239 has been merged (or whenever that
372 # logic is in the labstation images).
373 self.set_nocheck('init_keyboard','on')
Craig Harrison2b6c6fc2011-06-23 10:34:02 -0700374
375
Tom Wai-Hong Tam1c86c7a2012-10-22 10:08:24 +0800376 def is_localhost(self):
377 """Is the servod hosted locally?
378
379 Returns:
380 True if local hosted; otherwise, False.
381 """
Fang Deng5d518f42013-08-02 14:04:32 -0700382 return self._servo_host.is_localhost()
Tom Wai-Hong Tam1c86c7a2012-10-22 10:08:24 +0800383
384
Mary Ruthvenecf12712019-06-26 17:36:21 -0700385 def get_os_version(self):
386 """Returns the chromeos release version."""
387 lsb_release_content = self.system_output('cat /etc/lsb-release',
388 ignore_status=True)
389 return lsbrelease_utils.get_chromeos_release_builder_path(
390 lsb_release_content=lsb_release_content)
391
392
Mary Ruthven83bb5952019-06-27 12:34:05 -0700393 def get_servod_version(self):
394 """Returns the servod version."""
395 result = self._servo_host.run('servod --version')
396 # TODO: use system_output once servod --version prints to stdout
397 stdout = result.stdout.strip()
398 return stdout if stdout else result.stderr.strip()
399
400
Craig Harrison2b6c6fc2011-06-23 10:34:02 -0700401 def power_long_press(self):
Chrome Bot9a1137d2011-07-19 14:35:00 -0700402 """Simulate a long power button press."""
J. Richard Barnettef12bff22012-07-18 14:41:06 -0700403 # After a long power press, the EC may ignore the next power
404 # button press (at least on Alex). To guarantee that this
405 # won't happen, we need to allow the EC one second to
406 # collect itself.
Ruben Rodriguez Buchillon84eba752018-07-27 17:59:48 +0800407 # long_press is defined as 8.5s in servod
408 self.set_nocheck('power_key', 'long_press')
Craig Harrison2b6c6fc2011-06-23 10:34:02 -0700409
410
411 def power_normal_press(self):
Chrome Bot9a1137d2011-07-19 14:35:00 -0700412 """Simulate a normal power button press."""
Ruben Rodriguez Buchillon84eba752018-07-27 17:59:48 +0800413 # press is defined as 1.2s in servod
414 self.set_nocheck('power_key', 'press')
Craig Harrison2b6c6fc2011-06-23 10:34:02 -0700415
416
417 def power_short_press(self):
Chrome Bot9a1137d2011-07-19 14:35:00 -0700418 """Simulate a short power button press."""
Ruben Rodriguez Buchillon84eba752018-07-27 17:59:48 +0800419 # tab is defined as 0.2s in servod
420 self.set_nocheck('power_key', 'tab')
Craig Harrison2b6c6fc2011-06-23 10:34:02 -0700421
422
Ruben Rodriguez Buchillon84eba752018-07-27 17:59:48 +0800423 def power_key(self, press_secs='tab'):
Craig Harrison2b6c6fc2011-06-23 10:34:02 -0700424 """Simulate a power button press.
425
Ruben Rodriguez Buchillon84eba752018-07-27 17:59:48 +0800426 @param press_secs: int, float, str; time to press key in seconds or
427 known shorthand: 'tab' 'press' 'long_press'
Craig Harrison2b6c6fc2011-06-23 10:34:02 -0700428 """
Ruben Rodriguez Buchillon84eba752018-07-27 17:59:48 +0800429 self.set_nocheck('power_key', press_secs)
Craig Harrison2b6c6fc2011-06-23 10:34:02 -0700430
431
432 def lid_open(self):
Yuli Huang7b82dcf2015-05-13 17:58:52 +0800433 """Simulate opening the lid and raise exception if all attempts fail"""
434 self.set('lid_open', 'yes')
Craig Harrison2b6c6fc2011-06-23 10:34:02 -0700435
436
437 def lid_close(self):
Yuli Huang7b82dcf2015-05-13 17:58:52 +0800438 """Simulate closing the lid and raise exception if all attempts fail
Craig Harrison48997262011-06-27 14:31:10 -0700439
440 Waits 6 seconds to ensure the device is fully asleep before returning.
441 """
Yuli Huang7b82dcf2015-05-13 17:58:52 +0800442 self.set('lid_open', 'no')
Chrome Bot9a1137d2011-07-19 14:35:00 -0700443 time.sleep(Servo.SLEEP_DELAY)
Craig Harrison2b6c6fc2011-06-23 10:34:02 -0700444
Shelley Chenc26575a2015-09-18 10:56:16 -0700445 def volume_up(self, timeout=300):
Kevin Chenge448a8b2016-09-09 10:18:11 -0700446 """Simulate pushing the volume down button.
447
448 @param timeout: Timeout for setting the volume.
449 """
Shelley Chenc26575a2015-09-18 10:56:16 -0700450 self.set_get_all(['volume_up:yes',
451 'sleep:%.4f' % self.SERVO_KEY_PRESS_DELAY,
452 'volume_up:no'])
453 # we need to wait for commands to take effect before moving on
454 time_left = float(timeout)
455 while time_left > 0.0:
456 value = self.get('volume_up')
457 if value == 'no':
458 return
459 time.sleep(self.SHORT_DELAY)
460 time_left = time_left - self.SHORT_DELAY
461 raise error.TestFail("Failed setting volume_up to no")
462
463 def volume_down(self, timeout=300):
Kevin Chenge448a8b2016-09-09 10:18:11 -0700464 """Simulate pushing the volume down button.
465
466 @param timeout: Timeout for setting the volume.
467 """
Shelley Chenc26575a2015-09-18 10:56:16 -0700468 self.set_get_all(['volume_down:yes',
469 'sleep:%.4f' % self.SERVO_KEY_PRESS_DELAY,
470 'volume_down:no'])
471 # we need to wait for commands to take effect before moving on
472 time_left = float(timeout)
473 while time_left > 0.0:
474 value = self.get('volume_down')
475 if value == 'no':
476 return
477 time.sleep(self.SHORT_DELAY)
478 time_left = time_left - self.SHORT_DELAY
479 raise error.TestFail("Failed setting volume_down to no")
Craig Harrison2b6c6fc2011-06-23 10:34:02 -0700480
Ruben Rodriguez Buchillon84eba752018-07-27 17:59:48 +0800481 def ctrl_d(self, press_secs='tab'):
Yusuf Mohsinally103441a2014-01-14 15:25:44 -0800482 """Simulate Ctrl-d simultaneous button presses.
Gediminas Ramanauskasba352ad2012-11-09 09:43:32 -0800483
Ruben Rodriguez Buchillon84eba752018-07-27 17:59:48 +0800484 @param press_secs: int, float, str; time to press key in seconds or
485 known shorthand: 'tab' 'press' 'long_press'
Gediminas Ramanauskasba352ad2012-11-09 09:43:32 -0800486 """
Ruben Rodriguez Buchillon84eba752018-07-27 17:59:48 +0800487 self.set_nocheck('ctrl_d', press_secs)
Gediminas Ramanauskas9da80f22012-11-14 12:59:43 -0800488
Gediminas Ramanauskasba352ad2012-11-09 09:43:32 -0800489
Ruben Rodriguez Buchillon84eba752018-07-27 17:59:48 +0800490 def ctrl_u(self, press_secs='tab'):
Yusuf Mohsinally103441a2014-01-14 15:25:44 -0800491 """Simulate Ctrl-u simultaneous button presses.
492
Ruben Rodriguez Buchillon84eba752018-07-27 17:59:48 +0800493 @param press_secs: int, float, str; time to press key in seconds or
494 known shorthand: 'tab' 'press' 'long_press'
Yusuf Mohsinally103441a2014-01-14 15:25:44 -0800495 """
Ruben Rodriguez Buchillon84eba752018-07-27 17:59:48 +0800496 self.set_nocheck('ctrl_u', press_secs)
Todd Broch9dfc3a82011-11-01 08:09:28 -0700497
498
Ruben Rodriguez Buchillon84eba752018-07-27 17:59:48 +0800499 def ctrl_enter(self, press_secs='tab'):
Yusuf Mohsinally103441a2014-01-14 15:25:44 -0800500 """Simulate Ctrl-enter simultaneous button presses.
501
Ruben Rodriguez Buchillon84eba752018-07-27 17:59:48 +0800502 @param press_secs: int, float, str; time to press key in seconds or
503 known shorthand: 'tab' 'press' 'long_press'
Yusuf Mohsinally103441a2014-01-14 15:25:44 -0800504 """
Ruben Rodriguez Buchillon84eba752018-07-27 17:59:48 +0800505 self.set_nocheck('ctrl_enter', press_secs)
Gediminas Ramanauskas3297d4f2012-09-10 15:30:10 -0700506
507
Ruben Rodriguez Buchillon84eba752018-07-27 17:59:48 +0800508 def ctrl_key(self, press_secs='tab'):
Yusuf Mohsinally103441a2014-01-14 15:25:44 -0800509 """Simulate Enter key button press.
510
Ruben Rodriguez Buchillon84eba752018-07-27 17:59:48 +0800511 @param press_secs: int, float, str; time to press key in seconds or
512 known shorthand: 'tab' 'press' 'long_press'
Yusuf Mohsinally103441a2014-01-14 15:25:44 -0800513 """
Ruben Rodriguez Buchillon84eba752018-07-27 17:59:48 +0800514 self.set_nocheck('ctrl_key', press_secs)
Todd Broch9dfc3a82011-11-01 08:09:28 -0700515
516
Ruben Rodriguez Buchillon84eba752018-07-27 17:59:48 +0800517 def enter_key(self, press_secs='tab'):
Yusuf Mohsinally103441a2014-01-14 15:25:44 -0800518 """Simulate Enter key button press.
519
Ruben Rodriguez Buchillon84eba752018-07-27 17:59:48 +0800520 @param press_secs: int, float, str; time to press key in seconds or
521 known shorthand: 'tab' 'press' 'long_press'
Yusuf Mohsinally103441a2014-01-14 15:25:44 -0800522 """
Ruben Rodriguez Buchillon84eba752018-07-27 17:59:48 +0800523 self.set_nocheck('enter_key', press_secs)
Todd Broch9753bd42012-03-21 10:15:08 -0700524
525
Ruben Rodriguez Buchillon84eba752018-07-27 17:59:48 +0800526 def refresh_key(self, press_secs='tab'):
Yusuf Mohsinally103441a2014-01-14 15:25:44 -0800527 """Simulate Refresh key (F3) button press.
528
Ruben Rodriguez Buchillon84eba752018-07-27 17:59:48 +0800529 @param press_secs: int, float, str; time to press key in seconds or
530 known shorthand: 'tab' 'press' 'long_press'
Yusuf Mohsinally103441a2014-01-14 15:25:44 -0800531 """
Ruben Rodriguez Buchillon84eba752018-07-27 17:59:48 +0800532 self.set_nocheck('refresh_key', press_secs)
Craig Harrison2b6c6fc2011-06-23 10:34:02 -0700533
534
Ruben Rodriguez Buchillon84eba752018-07-27 17:59:48 +0800535 def ctrl_refresh_key(self, press_secs='tab'):
Tom Wai-Hong Tam9e61e662012-08-01 15:10:07 +0800536 """Simulate Ctrl and Refresh (F3) simultaneous press.
537
538 This key combination is an alternative of Space key.
Yusuf Mohsinally103441a2014-01-14 15:25:44 -0800539
Ruben Rodriguez Buchillon84eba752018-07-27 17:59:48 +0800540 @param press_secs: int, float, str; time to press key in seconds or
541 known shorthand: 'tab' 'press' 'long_press'
Tom Wai-Hong Tam9e61e662012-08-01 15:10:07 +0800542 """
Ruben Rodriguez Buchillon84eba752018-07-27 17:59:48 +0800543 self.set_nocheck('ctrl_refresh_key', press_secs)
Tom Wai-Hong Tam9e61e662012-08-01 15:10:07 +0800544
545
Ruben Rodriguez Buchillon84eba752018-07-27 17:59:48 +0800546 def imaginary_key(self, press_secs='tab'):
Craig Harrison2b6c6fc2011-06-23 10:34:02 -0700547 """Simulate imaginary key button press.
548
549 Maps to a key that doesn't physically exist.
Yusuf Mohsinally103441a2014-01-14 15:25:44 -0800550
Ruben Rodriguez Buchillon84eba752018-07-27 17:59:48 +0800551 @param press_secs: int, float, str; time to press key in seconds or
552 known shorthand: 'tab' 'press' 'long_press'
Craig Harrison2b6c6fc2011-06-23 10:34:02 -0700553 """
Ruben Rodriguez Buchillon84eba752018-07-27 17:59:48 +0800554 self.set_nocheck('imaginary_key', press_secs)
Craig Harrison2b6c6fc2011-06-23 10:34:02 -0700555
556
Ruben Rodriguez Buchillon84eba752018-07-27 17:59:48 +0800557 def sysrq_x(self, press_secs='tab'):
Vincent Palatine7dc9282016-07-14 11:31:58 +0200558 """Simulate Alt VolumeUp X simulataneous press.
559
560 This key combination is the kernel system request (sysrq) X.
561
Ruben Rodriguez Buchillon84eba752018-07-27 17:59:48 +0800562 @param press_secs: int, float, str; time to press key in seconds or
563 known shorthand: 'tab' 'press' 'long_press'
Vincent Palatine7dc9282016-07-14 11:31:58 +0200564 """
Ruben Rodriguez Buchillon84eba752018-07-27 17:59:48 +0800565 self.set_nocheck('sysrq_x', press_secs)
Vincent Palatine7dc9282016-07-14 11:31:58 +0200566
567
Tom Wai-Hong Tam93b5c092015-05-14 02:50:43 +0800568 def toggle_recovery_switch(self):
569 """Toggle recovery switch on and off."""
570 self.enable_recovery_mode()
571 time.sleep(self.REC_TOGGLE_DELAY)
572 self.disable_recovery_mode()
573
574
Craig Harrison6b36b122011-06-28 17:58:43 -0700575 def enable_recovery_mode(self):
576 """Enable recovery mode on device."""
577 self.set('rec_mode', 'on')
578
579
580 def disable_recovery_mode(self):
581 """Disable recovery mode on device."""
582 self.set('rec_mode', 'off')
583
584
Tom Wai-Hong Tamf9ded092015-05-20 05:37:22 +0800585 def toggle_development_switch(self):
586 """Toggle development switch on and off."""
587 self.enable_development_mode()
588 time.sleep(self.DEV_TOGGLE_DELAY)
589 self.disable_development_mode()
590
591
Craig Harrison6b36b122011-06-28 17:58:43 -0700592 def enable_development_mode(self):
593 """Enable development mode on device."""
594 self.set('dev_mode', 'on')
595
596
597 def disable_development_mode(self):
598 """Disable development mode on device."""
599 self.set('dev_mode', 'off')
600
Craig Harrison2b6c6fc2011-06-23 10:34:02 -0700601 def boot_devmode(self):
602 """Boot a dev-mode device that is powered off."""
Tom Wai-Hong Tam23869102012-01-17 15:41:30 +0800603 self.power_short_press()
Craig Harrison48997262011-06-27 14:31:10 -0700604 self.pass_devmode()
605
606
607 def pass_devmode(self):
608 """Pass through boot screens in dev-mode."""
Chrome Bot9a1137d2011-07-19 14:35:00 -0700609 time.sleep(Servo.BOOT_DELAY)
Craig Harrison2b6c6fc2011-06-23 10:34:02 -0700610 self.ctrl_d()
Chrome Bot9a1137d2011-07-19 14:35:00 -0700611 time.sleep(Servo.BOOT_DELAY)
Craig Harrison48997262011-06-27 14:31:10 -0700612
Craig Harrison2b6c6fc2011-06-23 10:34:02 -0700613
Yusuf Mohsinally6c078ae2013-11-21 11:06:42 -0800614 def get_board(self):
Wai-Hong Tam0f904002017-09-19 15:52:22 -0700615 """Get the board connected to servod."""
Yusuf Mohsinally6c078ae2013-11-21 11:06:42 -0800616 return self._server.get_board()
617
618
Wai-Hong Tam0f904002017-09-19 15:52:22 -0700619 def get_base_board(self):
620 """Get the board of the base connected to servod."""
Wai-Hong Tam7f6169e2017-09-29 09:23:48 -0700621 try:
622 return self._server.get_base_board()
623 except xmlrpclib.Fault as e:
624 # TODO(waihong): Remove the following compatibility check when
625 # the new versions of hdctools are deployed.
626 if 'not supported' in str(e):
627 logging.warning('The servod is too old that get_base_board '
628 'not supported.')
629 return ''
630 raise
Wai-Hong Tam0f904002017-09-19 15:52:22 -0700631
632
Wai-Hong Tamcc399ee2017-12-08 12:43:28 -0800633 def get_ec_active_copy(self):
634 """Get the active copy of the EC image."""
635 return self.get('ec_active_copy')
636
637
Todd Brochefe72cb2012-07-11 19:58:53 -0700638 def _get_xmlrpclib_exception(self, xmlexc):
639 """Get meaningful exception string from xmlrpc.
640
641 Args:
642 xmlexc: xmlrpclib.Fault object
643
644 xmlrpclib.Fault.faultString has the following format:
645
646 <type 'exception type'>:'actual error message'
647
648 Parse and return the real exception from the servod side instead of the
649 less meaningful one like,
650 <Fault 1: "<type 'exceptions.AttributeError'>:'tca6416' object has no
651 attribute 'hw_driver'">
652
653 Returns:
654 string of underlying exception raised in servod.
655 """
656 return re.sub('^.*>:', '', xmlexc.faultString)
657
Ruben Rodriguez Buchillon2cba8e42019-08-12 11:31:53 -0700658 def has_control(self, control):
659 """Query servod server to determine if |control| is a valid control.
660
661 @param control: str, control name to query
662
663 @returns: true if |control| is a known control, false otherwise.
664 """
665 assert control
666 try:
667 # If the control exists, doc() will work.
668 self._server.doc(control)
669 return True
670 except xmlrpclib.Fault as e:
671 if re.search('No control %s' % control,
672 self._get_xmlrpclib_exception(e)):
673 return False
674 raise e
Todd Brochefe72cb2012-07-11 19:58:53 -0700675
Craig Harrison2b6c6fc2011-06-23 10:34:02 -0700676 def get(self, gpio_name):
J. Richard Barnettecdd1edf2015-07-24 11:39:46 -0700677 """Get the value of a gpio from Servod.
678
679 @param gpio_name Name of the gpio.
Ruben Rodriguez Buchillon2cba8e42019-08-12 11:31:53 -0700680
681 @returns: server response to |gpio_name| request.
682
683 @raise ControlUnavailableError: if |gpio_name| not a known control.
684 @raise error.TestFail: for all other failures doing get().
J. Richard Barnettecdd1edf2015-07-24 11:39:46 -0700685 """
Craig Harrison2b6c6fc2011-06-23 10:34:02 -0700686 assert gpio_name
Todd Brochefe72cb2012-07-11 19:58:53 -0700687 try:
Simran Basi1cbc5f22013-07-17 14:23:58 -0700688 return self._server.get(gpio_name)
Todd Brochefe72cb2012-07-11 19:58:53 -0700689 except xmlrpclib.Fault as e:
Ruben Rodriguez Buchillon2cba8e42019-08-12 11:31:53 -0700690 err_str = self._get_xmlrpclib_exception(e)
691 err_msg = "Getting '%s' :: %s" % (gpio_name, err_str)
692 unknown_ctrl = re.findall(NO_CONTROL_RE, err_str)
693 if unknown_ctrl:
694 raise ControlUnavailableError('No control named %r' %
695 unknown_ctrl[0])
696 else:
697 logging.error(err_msg)
698 raise error.TestFail(err_msg)
Craig Harrison2b6c6fc2011-06-23 10:34:02 -0700699
700
701 def set(self, gpio_name, gpio_value):
J. Richard Barnettecdd1edf2015-07-24 11:39:46 -0700702 """Set and check the value of a gpio using Servod.
703
704 @param gpio_name Name of the gpio.
705 @param gpio_value New setting for the gpio.
706 """
Chrome Bot9a1137d2011-07-19 14:35:00 -0700707 self.set_nocheck(gpio_name, gpio_value)
Todd Brochcf7c6652012-02-24 13:03:59 -0800708 retry_count = Servo.GET_RETRY_MAX
709 while gpio_value != self.get(gpio_name) and retry_count:
Ilja H. Friedel04be2bd2014-05-07 21:29:59 -0700710 logging.warning("%s != %s, retry %d", gpio_name, gpio_value,
Todd Brochcf7c6652012-02-24 13:03:59 -0800711 retry_count)
712 retry_count -= 1
713 time.sleep(Servo.SHORT_DELAY)
714 if not retry_count:
715 assert gpio_value == self.get(gpio_name), \
716 'Servo failed to set %s to %s' % (gpio_name, gpio_value)
Craig Harrison2b6c6fc2011-06-23 10:34:02 -0700717
718
719 def set_nocheck(self, gpio_name, gpio_value):
J. Richard Barnettecdd1edf2015-07-24 11:39:46 -0700720 """Set the value of a gpio using Servod.
721
722 @param gpio_name Name of the gpio.
723 @param gpio_value New setting for the gpio.
Ruben Rodriguez Buchillon2cba8e42019-08-12 11:31:53 -0700724
725 @raise ControlUnavailableError: if |gpio_name| not a known control.
726 @raise error.TestFail: for all other failures doing set().
J. Richard Barnettecdd1edf2015-07-24 11:39:46 -0700727 """
Ruben Rodriguez Buchillon1c0219c2019-06-25 12:30:44 -0700728 # The real danger here is to pass a None value through the xmlrpc.
729 assert gpio_name and gpio_value is not None
Mary Ruthvendfa45342019-06-25 10:10:17 -0700730 logging.debug('Setting %s to %r', gpio_name, gpio_value)
Todd Brochefe72cb2012-07-11 19:58:53 -0700731 try:
Simran Basi1cbc5f22013-07-17 14:23:58 -0700732 self._server.set(gpio_name, gpio_value)
Todd Brochefe72cb2012-07-11 19:58:53 -0700733 except xmlrpclib.Fault as e:
Ruben Rodriguez Buchillon2cba8e42019-08-12 11:31:53 -0700734 err_str = self._get_xmlrpclib_exception(e)
735 err_msg = "Setting '%s' :: %s" % (gpio_name, err_str)
736 unknown_ctrl = re.findall(NO_CONTROL_RE, err_str)
737 if unknown_ctrl:
738 raise ControlUnavailableError('No control named %r' %
739 unknown_ctrl[0])
740 else:
741 logging.error(err_msg)
742 raise error.TestFail(err_msg)
Craig Harrison2b6c6fc2011-06-23 10:34:02 -0700743
744
Tom Wai-Hong Tam92569bb2013-03-26 14:25:10 +0800745 def set_get_all(self, controls):
746 """Set &| get one or more control values.
747
748 @param controls: list of strings, controls to set &| get.
749
750 @raise: error.TestError in case error occurs setting/getting values.
751 """
752 rv = []
753 try:
Mary Ruthvendfa45342019-06-25 10:10:17 -0700754 logging.debug('Set/get all: %s', str(controls))
Tom Wai-Hong Tam92569bb2013-03-26 14:25:10 +0800755 rv = self._server.set_get_all(controls)
756 except xmlrpclib.Fault as e:
757 # TODO(waihong): Remove the following backward compatibility when
758 # the new versions of hdctools are deployed.
759 if 'not supported' in str(e):
760 logging.warning('The servod is too old that set_get_all '
761 'not supported. Use set and get instead.')
762 for control in controls:
763 if ':' in control:
764 (name, value) = control.split(':')
765 if name == 'sleep':
766 time.sleep(float(value))
767 else:
768 self.set_nocheck(name, value)
769 rv.append(True)
770 else:
771 rv.append(self.get(name))
772 else:
773 err_msg = "Problem with '%s' :: %s" % \
774 (controls, self._get_xmlrpclib_exception(e))
775 raise error.TestFail(err_msg)
776 return rv
777
778
Jon Salzc88e5b62011-11-30 14:38:54 +0800779 # TODO(waihong) It may fail if multiple servo's are connected to the same
780 # host. Should look for a better way, like the USB serial name, to identify
781 # the USB device.
Simran Basi741b5d42012-05-18 11:27:15 -0700782 # TODO(sbasi) Remove this code from autoserv once firmware tests have been
783 # updated.
Kevin Chenga22c4a82016-10-07 14:13:25 -0700784 def probe_host_usb_dev(self, timeout=_USB_PROBE_TIMEOUT):
Jon Salzc88e5b62011-11-30 14:38:54 +0800785 """Probe the USB disk device plugged-in the servo from the host side.
786
Kevin Chengeb06fe72016-08-22 15:26:32 -0700787 It uses servod to discover if there is a usb device attached to it.
Jon Salzc88e5b62011-11-30 14:38:54 +0800788
Kevin Chenga22c4a82016-10-07 14:13:25 -0700789 @param timeout The timeout period when probing for the usb host device.
790
791 @return: String of USB disk path (e.g. '/dev/sdb') or None.
Jon Salzc88e5b62011-11-30 14:38:54 +0800792 """
Ruben Rodriguez Buchillon9a4bfc32018-10-16 20:46:25 +0800793 # Set up Servo's usb mux.
794 self.switch_usbkey('host')
Kevin Chenga22c4a82016-10-07 14:13:25 -0700795 return self._server.probe_host_usb_dev(timeout) or None
Jon Salzc88e5b62011-11-30 14:38:54 +0800796
797
Mike Truty49153d82012-08-21 22:27:30 -0500798 def image_to_servo_usb(self, image_path=None,
799 make_image_noninteractive=False):
800 """Install an image to the USB key plugged into the servo.
801
802 This method may copy any image to the servo USB key including a
803 recovery image or a test image. These images are frequently used
804 for test purposes such as restoring a corrupted image or conducting
805 an upgrade of ec/fw/kernel as part of a test of a specific image part.
806
J. Richard Barnettecdd1edf2015-07-24 11:39:46 -0700807 @param image_path Path on the host to the recovery image.
808 @param make_image_noninteractive Make the recovery image
809 noninteractive, therefore the DUT
810 will reboot automatically after
811 installation.
Mike Truty49153d82012-08-21 22:27:30 -0500812 """
J. Richard Barnette41320ee2013-03-11 13:00:13 -0700813 # We're about to start plugging/unplugging the USB key. We
814 # don't know the state of the DUT, or what it might choose
815 # to do to the device after hotplug. To avoid surprises,
816 # force the DUT to be off.
817 self._server.hwinit()
Ruben Rodriguez Buchillonb0e2a9f2019-08-12 12:40:44 -0700818 if self.has_control('init_keyboard'):
819 # This indicates the servod version does not
820 # have explicit keyboard initialization yet.
821 # Ignore this.
Ruben Rodriguez Buchillonbd3b74f2019-08-08 16:18:27 -0700822 # TODO(coconutruben): change this back to set() about a month
823 # after crrev.com/c/1586239 has been merged (or whenever that
824 # logic is in the labstation images).
825 self.set_nocheck('init_keyboard','on')
J. Richard Barnette41320ee2013-03-11 13:00:13 -0700826 self._power_state.power_off()
Mike Truty49153d82012-08-21 22:27:30 -0500827
Mike Truty49153d82012-08-21 22:27:30 -0500828 if image_path:
Garry Wangd2656812019-08-07 17:29:16 -0700829 # Set up Servo's usb mux.
830 self.switch_usbkey('host')
Tom Wai-Hong Tam42f136d2012-10-26 11:11:23 +0800831 logging.info('Searching for usb device and copying image to it. '
832 'Please wait a few minutes...')
Mike Truty49153d82012-08-21 22:27:30 -0500833 if not self._server.download_image_to_usb(image_path):
834 logging.error('Failed to transfer requested image to USB. '
835 'Please take a look at Servo Logs.')
836 raise error.AutotestError('Download image to usb failed.')
837 if make_image_noninteractive:
838 logging.info('Making image noninteractive')
839 if not self._server.make_image_noninteractive():
840 logging.error('Failed to make image noninteractive. '
841 'Please take a look at Servo Logs.')
842
Kalin Stoyanovb3c11f32018-05-11 09:02:00 -0700843 def boot_in_recovery_mode(self):
844 """Boot host DUT in recovery mode."""
845 self._power_state.power_on(rec_mode=self._power_state.REC_ON)
846 self.switch_usbkey('dut')
847
Mike Truty49153d82012-08-21 22:27:30 -0500848
Simran Basi741b5d42012-05-18 11:27:15 -0700849 def install_recovery_image(self, image_path=None,
J. Richard Barnetteb6de7e32013-02-14 13:28:04 -0800850 make_image_noninteractive=False):
Dan Shic67f1332016-04-06 12:38:06 -0700851 """Install the recovery image specified by the path onto the DUT.
Jon Salzc88e5b62011-11-30 14:38:54 +0800852
853 This method uses google recovery mode to install a recovery image
854 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 +0800855 board specified by the usb_dev. If no image path is specified
Jon Salzc88e5b62011-11-30 14:38:54 +0800856 we use the recovery image already on the usb image.
857
Dan Shic67f1332016-04-06 12:38:06 -0700858 @param image_path: Path on the host to the recovery image.
859 @param make_image_noninteractive: Make the recovery image
860 noninteractive, therefore the DUT will reboot automatically
861 after installation.
Jon Salzc88e5b62011-11-30 14:38:54 +0800862 """
Mike Truty49153d82012-08-21 22:27:30 -0500863 self.image_to_servo_usb(image_path, make_image_noninteractive)
Garry Wangd2656812019-08-07 17:29:16 -0700864 # Give the DUT some time to power_off if we skip
865 # download image to usb. (crbug.com/982993)
866 if not image_path:
867 time.sleep(10)
Kalin Stoyanovb3c11f32018-05-11 09:02:00 -0700868 self.boot_in_recovery_mode()
Jon Salzc88e5b62011-11-30 14:38:54 +0800869
870
Vadim Bendeburyb80ba592012-12-07 15:02:34 -0800871 def _scp_image(self, image_path):
872 """Copy image to the servo host.
873
874 When programming a firmware image on the DUT, the image must be
875 located on the host to which the servo device is connected. Sometimes
876 servo is controlled by a remote host, in this case the image needs to
877 be transferred to the remote host.
878
879 @param image_path: a string, name of the firmware image file to be
880 transferred.
881 @return: a string, full path name of the copied file on the remote.
882 """
883
884 dest_path = os.path.join('/tmp', os.path.basename(image_path))
Fang Deng5d518f42013-08-02 14:04:32 -0700885 self._servo_host.send_file(image_path, dest_path)
Vadim Bendeburyb80ba592012-12-07 15:02:34 -0800886 return dest_path
887
888
Dan Shifecdaf42015-07-28 10:17:26 -0700889 def system(self, command, timeout=3600):
J. Richard Barnettecdd1edf2015-07-24 11:39:46 -0700890 """Execute the passed in command on the servod host.
891
892 @param command Command to be executed.
Dan Shifecdaf42015-07-28 10:17:26 -0700893 @param timeout Maximum number of seconds of runtime allowed. Default to
894 1 hour.
J. Richard Barnettecdd1edf2015-07-24 11:39:46 -0700895 """
Vadim Bendebury89ec24e2012-12-17 12:54:18 -0800896 logging.info('Will execute on servo host: %s', command)
Fang Deng5d518f42013-08-02 14:04:32 -0700897 self._servo_host.run(command, timeout=timeout)
Vadim Bendebury89ec24e2012-12-17 12:54:18 -0800898
899
Dan Shifecdaf42015-07-28 10:17:26 -0700900 def system_output(self, command, timeout=3600,
Vadim Bendebury89ec24e2012-12-17 12:54:18 -0800901 ignore_status=False, args=()):
902 """Execute the passed in command on the servod host, return stdout.
903
J. Richard Barnettecdd1edf2015-07-24 11:39:46 -0700904 @param command a string, the command to execute
905 @param timeout an int, max number of seconds to wait til command
Dan Shifecdaf42015-07-28 10:17:26 -0700906 execution completes. Default to 1 hour.
J. Richard Barnettecdd1edf2015-07-24 11:39:46 -0700907 @param ignore_status a Boolean, if true - ignore command's nonzero exit
Vadim Bendebury89ec24e2012-12-17 12:54:18 -0800908 status, otherwise an exception will be thrown
J. Richard Barnettecdd1edf2015-07-24 11:39:46 -0700909 @param args a tuple of strings, each becoming a separate command line
Vadim Bendebury89ec24e2012-12-17 12:54:18 -0800910 parameter for the command
J. Richard Barnettecdd1edf2015-07-24 11:39:46 -0700911 @return command's stdout as a string.
Vadim Bendebury89ec24e2012-12-17 12:54:18 -0800912 """
Fang Deng5d518f42013-08-02 14:04:32 -0700913 return self._servo_host.run(command, timeout=timeout,
914 ignore_status=ignore_status,
915 args=args).stdout.strip()
Vadim Bendeburyb80ba592012-12-07 15:02:34 -0800916
917
Mary Ruthven38d90af2019-08-16 13:13:31 -0700918 def get_servo_version(self, active=False):
Dan Shia5fef052015-05-18 23:28:47 -0700919 """Get the version of the servo, e.g., servo_v2 or servo_v3.
920
Mary Ruthven38d90af2019-08-16 13:13:31 -0700921 @param active: Only return the servo type with the active device.
Dan Shia5fef052015-05-18 23:28:47 -0700922 @return: The version of the servo.
923
924 """
Mary Ruthven38d90af2019-08-16 13:13:31 -0700925 servo_type = self._server.get_version()
926 if '_and_' not in servo_type or not active:
927 return servo_type
928
929 # If servo v4 is using ccd and servo micro, modify the servo type to
930 # reflect the active device.
931 active_device = self.get('active_v4_device')
932 if active_device in servo_type:
933 logging.info('%s is active', active_device)
934 return 'servo_v4_with_' + active_device
935
936 logging.warn("%s is active even though it's not in servo type",
937 active_device)
938 return servo_type
Dan Shia5fef052015-05-18 23:28:47 -0700939
940
Mary Ruthven2724ee62019-07-16 11:16:59 -0700941 def running_through_ccd(self):
942 """Returns True if the setup is using ccd to run."""
943 servo = self._server.get_version()
944 return 'ccd_cr50' in servo and 'servo_micro' not in servo
945
946
Tom Wai-Hong Tam4ac78982016-01-08 02:34:37 +0800947 def _initialize_programmer(self, rw_only=False):
948 """Initialize the firmware programmer.
949
950 @param rw_only: True to initialize a programmer which only
951 programs the RW portions.
952 """
J. Richard Barnette8f19b392014-08-11 14:05:39 -0700953 if self._programmer:
954 return
955 # Initialize firmware programmer
Dan Shia5fef052015-05-18 23:28:47 -0700956 servo_version = self.get_servo_version()
Dan Shi9cb0eec2014-06-03 09:04:50 -0700957 if servo_version.startswith('servo_v2'):
J. Richard Barnette8f19b392014-08-11 14:05:39 -0700958 self._programmer = firmware_programmer.ProgrammerV2(self)
Wai-Hong Tamc36c4d22016-09-09 10:39:45 -0700959 self._programmer_rw = firmware_programmer.ProgrammerV2RwOnly(self)
Kevin Chengdf2e29f2016-09-09 02:31:22 -0700960 # Both servo v3 and v4 use the same programming methods so just leverage
961 # ProgrammerV3 for servo v4 as well.
962 elif (servo_version.startswith('servo_v3') or
963 servo_version.startswith('servo_v4')):
Dan Shia5fef052015-05-18 23:28:47 -0700964 self._programmer = firmware_programmer.ProgrammerV3(self)
Tom Wai-Hong Tam4ac78982016-01-08 02:34:37 +0800965 self._programmer_rw = firmware_programmer.ProgrammerV3RwOnly(self)
J. Richard Barnette8f19b392014-08-11 14:05:39 -0700966 else:
967 raise error.TestError(
968 'No firmware programmer for servo version: %s' %
Congbin Guo42427612019-02-12 10:22:06 -0800969 servo_version)
J. Richard Barnette8f19b392014-08-11 14:05:39 -0700970
971
Tom Wai-Hong Tam4ac78982016-01-08 02:34:37 +0800972 def program_bios(self, image, rw_only=False):
Yusuf Mohsinally6c078ae2013-11-21 11:06:42 -0800973 """Program bios on DUT with given image.
Vadim Bendebury1a7ec632013-02-17 16:22:09 -0800974
Yusuf Mohsinally6c078ae2013-11-21 11:06:42 -0800975 @param image: a string, file name of the BIOS image to program
976 on the DUT.
Tom Wai-Hong Tam4ac78982016-01-08 02:34:37 +0800977 @param rw_only: True to only program the RW portion of BIOS.
Yusuf Mohsinally6c078ae2013-11-21 11:06:42 -0800978
Vadim Bendebury1a7ec632013-02-17 16:22:09 -0800979 """
J. Richard Barnette8f19b392014-08-11 14:05:39 -0700980 self._initialize_programmer()
Ricky Liangc31aab32014-07-03 16:23:29 +0800981 if not self.is_localhost():
982 image = self._scp_image(image)
Tom Wai-Hong Tam4ac78982016-01-08 02:34:37 +0800983 if rw_only:
984 self._programmer_rw.program_bios(image)
985 else:
986 self._programmer.program_bios(image)
Vadim Bendeburyb80ba592012-12-07 15:02:34 -0800987
988
Tom Wai-Hong Tam4ac78982016-01-08 02:34:37 +0800989 def program_ec(self, image, rw_only=False):
Yusuf Mohsinally6c078ae2013-11-21 11:06:42 -0800990 """Program ec on DUT with given image.
Vadim Bendebury1a7ec632013-02-17 16:22:09 -0800991
Yusuf Mohsinally6c078ae2013-11-21 11:06:42 -0800992 @param image: a string, file name of the EC image to program
993 on the DUT.
Tom Wai-Hong Tam4ac78982016-01-08 02:34:37 +0800994 @param rw_only: True to only program the RW portion of EC.
Vadim Bendebury1a7ec632013-02-17 16:22:09 -0800995
Vadim Bendebury1a7ec632013-02-17 16:22:09 -0800996 """
J. Richard Barnette8f19b392014-08-11 14:05:39 -0700997 self._initialize_programmer()
Ricky Liangc31aab32014-07-03 16:23:29 +0800998 if not self.is_localhost():
999 image = self._scp_image(image)
Tom Wai-Hong Tam4ac78982016-01-08 02:34:37 +08001000 if rw_only:
Congbin Guo42427612019-02-12 10:22:06 -08001001 self._programmer_rw.program_ec(image)
Tom Wai-Hong Tam4ac78982016-01-08 02:34:37 +08001002 else:
Congbin Guo42427612019-02-12 10:22:06 -08001003 self._programmer.program_ec(image)
1004
1005
1006 def _reprogram(self, tarball_path, firmware_name, image_candidates,
1007 rw_only):
1008 """Helper function to reprogram firmware for EC or BIOS.
1009
1010 @param tarball_path: The path of the downloaded build tarball.
1011 @param: firmware_name: either 'EC' or 'BIOS'.
1012 @param image_candidates: A tuple of the paths of image candidates.
1013 @param rw_only: True to only install firmware to its RW portions. Keep
1014 the RO portions unchanged.
1015
1016 @raise: TestError if cannot extract firmware from the tarball.
1017 """
Mary Ruthven771b2012019-08-19 12:18:49 -07001018 dest_dir = os.path.join(os.path.dirname(tarball_path), firmware_name)
1019 # Create the firmware_name subdirectory if it doesn't exist.
1020 if not os.path.exists(dest_dir):
1021 os.mkdir(dest_dir)
Congbin Guo42427612019-02-12 10:22:06 -08001022 image = _extract_image_from_tarball(tarball_path, dest_dir,
1023 image_candidates)
1024 if not image:
1025 if firmware_name == 'EC':
1026 logging.info('Not a Chrome EC, ignore re-programming it')
1027 return
1028 else:
1029 raise error.TestError('Failed to extract the %s image from '
1030 'tarball' % firmware_name)
1031
1032 logging.info('Will re-program %s %snow', firmware_name,
1033 'RW ' if rw_only else '')
1034
1035 if firmware_name == 'EC':
1036 self.program_ec(os.path.join(dest_dir, image), rw_only)
1037 else:
1038 self.program_bios(os.path.join(dest_dir, image), rw_only)
1039
1040
Shelley Chenac61d5a2019-06-24 15:35:46 -07001041 def program_firmware(self, board, model, tarball_path, rw_only=False):
Congbin Guo42427612019-02-12 10:22:06 -08001042 """Program firmware (EC, if applied, and BIOS) of the DUT.
1043
Shelley Chenac61d5a2019-06-24 15:35:46 -07001044 @param board: The DUT board name.
Congbin Guo42427612019-02-12 10:22:06 -08001045 @param model: The DUT model name.
1046 @param tarball_path: The path of the downloaded build tarball.
1047 @param rw_only: True to only install firmware to its RW portions. Keep
1048 the RO portions unchanged.
1049 """
Shelley Chenac61d5a2019-06-24 15:35:46 -07001050 ap_image_candidates = ('image.bin', 'image-%s.bin' % model,
1051 'image-%s.bin' % board)
1052 ec_image_candidates = ('ec.bin', '%s/ec.bin' % model,
1053 '%s/ec.bin' % board)
Congbin Guo42427612019-02-12 10:22:06 -08001054
1055 self._reprogram(tarball_path, 'EC', ec_image_candidates, rw_only)
1056 self._reprogram(tarball_path, 'BIOS', ap_image_candidates, rw_only)
1057
1058 self.get_power_state_controller().reset()
1059 time.sleep(Servo.BOOT_DELAY)
Vadim Bendeburye7bd9362012-12-19 14:35:20 -08001060
Fang Dengafb88142013-05-30 17:44:31 -07001061
1062 def _switch_usbkey_power(self, power_state, detection_delay=False):
1063 """Switch usbkey power.
1064
1065 This function switches usbkey power by setting the value of
1066 'prtctl4_pwren'. If the power is already in the
1067 requested state, this function simply returns.
1068
1069 @param power_state: A string, 'on' or 'off'.
1070 @param detection_delay: A boolean value, if True, sleep
1071 for |USB_DETECTION_DELAY| after switching
1072 the power on.
1073 """
Kevin Chenga22c4a82016-10-07 14:13:25 -07001074 # TODO(kevcheng): Forgive me for this terrible hack. This is just to
1075 # handle beaglebones that haven't yet updated and have the
1076 # safe_switch_usbkey_power RPC. I'll remove this once all beaglebones
1077 # have been updated and also think about a better way to handle
1078 # situations like this.
1079 try:
1080 self._server.safe_switch_usbkey_power(power_state)
1081 except Exception:
1082 self.set('prtctl4_pwren', power_state)
Fang Dengafb88142013-05-30 17:44:31 -07001083 if power_state == 'off':
1084 time.sleep(self.USB_POWEROFF_DELAY)
1085 elif detection_delay:
1086 time.sleep(self.USB_DETECTION_DELAY)
1087
1088
1089 def switch_usbkey(self, usb_state):
1090 """Connect USB flash stick to either host or DUT, or turn USB port off.
Vadim Bendeburye7bd9362012-12-19 14:35:20 -08001091
1092 This function switches the servo multiplexer to provide electrical
Fang Dengafb88142013-05-30 17:44:31 -07001093 connection between the USB port J3 and either host or DUT side. It
1094 can also be used to turn the USB port off.
Vadim Bendeburye7bd9362012-12-19 14:35:20 -08001095
Fang Dengafb88142013-05-30 17:44:31 -07001096 Switching to 'dut' or 'host' is accompanied by powercycling
1097 of the USB stick, because it sometimes gets wedged if the mux
1098 is switched while the stick power is on.
Vadim Bendeburye7bd9362012-12-19 14:35:20 -08001099
Fang Dengafb88142013-05-30 17:44:31 -07001100 @param usb_state: A string, one of 'dut', 'host', or 'off'.
1101 'dut' and 'host' indicate which side the
1102 USB flash device is required to be connected to.
1103 'off' indicates turning the USB port off.
Vadim Bendeburye7bd9362012-12-19 14:35:20 -08001104
Fang Dengafb88142013-05-30 17:44:31 -07001105 @raise: error.TestError in case the parameter is not 'dut'
1106 'host', or 'off'.
Vadim Bendeburye7bd9362012-12-19 14:35:20 -08001107 """
Fang Dengafb88142013-05-30 17:44:31 -07001108 if self.get_usbkey_direction() == usb_state:
Vadim Bendeburye7bd9362012-12-19 14:35:20 -08001109 return
1110
Fang Dengafb88142013-05-30 17:44:31 -07001111 if usb_state == 'off':
Dan Shia5fef052015-05-18 23:28:47 -07001112 self._switch_usbkey_power('off')
1113 self._usb_state = usb_state
1114 return
Fang Dengafb88142013-05-30 17:44:31 -07001115 elif usb_state == 'host':
Vadim Bendeburye7bd9362012-12-19 14:35:20 -08001116 mux_direction = 'servo_sees_usbkey'
Fang Dengafb88142013-05-30 17:44:31 -07001117 elif usb_state == 'dut':
Vadim Bendeburye7bd9362012-12-19 14:35:20 -08001118 mux_direction = 'dut_sees_usbkey'
1119 else:
Fang Dengafb88142013-05-30 17:44:31 -07001120 raise error.TestError('Unknown USB state request: %s' % usb_state)
Vadim Bendeburye7bd9362012-12-19 14:35:20 -08001121
Fang Dengafb88142013-05-30 17:44:31 -07001122 self._switch_usbkey_power('off')
Kevin Chenga22c4a82016-10-07 14:13:25 -07001123 # TODO(kevcheng): Forgive me for this terrible hack. This is just to
1124 # handle beaglebones that haven't yet updated and have the
1125 # safe_switch_usbkey RPC. I'll remove this once all beaglebones have
1126 # been updated and also think about a better way to handle situations
1127 # like this.
1128 try:
1129 self._server.safe_switch_usbkey(mux_direction)
1130 except Exception:
1131 self.set('usb_mux_sel1', mux_direction)
Vadim Bendeburye7bd9362012-12-19 14:35:20 -08001132 time.sleep(self.USB_POWEROFF_DELAY)
Fang Dengafb88142013-05-30 17:44:31 -07001133 self._switch_usbkey_power('on', usb_state == 'host')
1134 self._usb_state = usb_state
Vadim Bendeburye7bd9362012-12-19 14:35:20 -08001135
Vadim Bendeburye7bd9362012-12-19 14:35:20 -08001136
Vadim Bendeburye7bd9362012-12-19 14:35:20 -08001137 def get_usbkey_direction(self):
Fang Dengafb88142013-05-30 17:44:31 -07001138 """Get which side USB is connected to or 'off' if usb power is off.
Vadim Bendeburye7bd9362012-12-19 14:35:20 -08001139
Fang Dengafb88142013-05-30 17:44:31 -07001140 @return: A string, one of 'dut', 'host', or 'off'.
Vadim Bendeburye7bd9362012-12-19 14:35:20 -08001141 """
Fang Dengafb88142013-05-30 17:44:31 -07001142 if not self._usb_state:
1143 if self.get('prtctl4_pwren') == 'off':
1144 self._usb_state = 'off'
1145 elif self.get('usb_mux_sel1').startswith('dut'):
1146 self._usb_state = 'dut'
Vadim Bendeburye7bd9362012-12-19 14:35:20 -08001147 else:
Fang Dengafb88142013-05-30 17:44:31 -07001148 self._usb_state = 'host'
1149 return self._usb_state
Congbin Guoa1f9cba2018-07-03 11:36:59 -07001150
1151
Wai-Hong Tam60377262018-03-01 10:55:39 -08001152 def set_servo_v4_role(self, role):
1153 """Set the power role of servo v4, either 'src' or 'snk'.
1154
1155 It does nothing if not a servo v4.
1156
1157 @param role: Power role for DUT port on servo v4, either 'src' or 'snk'.
1158 """
1159 servo_version = self.get_servo_version()
1160 if servo_version.startswith('servo_v4'):
1161 value = self.get('servo_v4_role')
1162 if value != role:
1163 self.set_nocheck('servo_v4_role', role)
1164 else:
1165 logging.debug('Already in the role: %s.', role)
1166 else:
1167 logging.debug('Not a servo v4, unable to set role to %s.', role)
1168
1169
Mary Ruthven739b2922019-08-22 11:16:06 -07001170 def set_servo_v4_dts_mode(self, state):
1171 """Set servo v4 dts mode to off or on.
1172
1173 It does nothing if not a servo v4. Disable the ccd watchdog if we're
1174 disabling dts mode. CCD will disconnect. The watchdog only allows CCD
1175 to disconnect for 10 seconds until it kills servod. Disable the
1176 watchdog, so CCD can stay disconnected indefinitely.
1177
1178 @param state: Set servo v4 dts mode 'off' or 'on'.
1179 """
1180 servo_version = self.get_servo_version()
1181 if not servo_version.startswith('servo_v4'):
1182 logging.debug('Not a servo v4, unable to set dts mode %s.', state)
1183 return
1184
1185 # TODO(mruthven): remove watchdog check once the labstation has been
1186 # updated to have support for modifying the watchdog.
1187 set_watchdog = self.has_control('watchdog') and 'ccd' in servo_version
1188 enable_watchdog = state == 'on'
1189
1190 if set_watchdog and not enable_watchdog:
1191 self.set_nocheck('watchdog_remove', 'ccd')
1192
1193 self.set_nocheck('servo_v4_dts_mode', state)
1194
1195 if set_watchdog and enable_watchdog:
1196 self.set_nocheck('watchdog_add', 'ccd')
1197
1198
Congbin Guofc3b8962019-03-22 17:38:46 -07001199 @property
1200 def uart_logs_dir(self):
1201 """Return the directory to save UART logs."""
1202 return self._uart.logs_dir if self._uart else ""
Congbin Guoa1f9cba2018-07-03 11:36:59 -07001203
Congbin Guofc3b8962019-03-22 17:38:46 -07001204
1205 @uart_logs_dir.setter
1206 def uart_logs_dir(self, logs_dir):
1207 """Set directory to save UART logs.
1208
1209 @param logs_dir String of directory name."""
Congbin Guoa1f9cba2018-07-03 11:36:59 -07001210 if self._uart:
Congbin Guofc3b8962019-03-22 17:38:46 -07001211 self._uart.logs_dir = logs_dir
Congbin Guoa1f9cba2018-07-03 11:36:59 -07001212
1213
1214 def close(self):
1215 """Close the servo object."""
1216 if self._uart:
1217 self._uart.stop_capture()
Congbin Guofc3b8962019-03-22 17:38:46 -07001218 self._uart.dump()
Congbin Guoa1f9cba2018-07-03 11:36:59 -07001219 self._uart = None