blob: f2b5cc3b11120ad1efe15e16671cdd46cfe41e90 [file] [log] [blame]
Mary Ruthven300b1fa2017-05-30 10:24:23 -07001# Copyright 2017 The Chromium OS Authors. All rights reserved.
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
5import functools
6import logging
Mary Ruthven61060f82017-05-30 20:50:39 -07007import time
Mary Ruthven300b1fa2017-05-30 10:24:23 -07008
9from autotest_lib.client.bin import utils
10from autotest_lib.client.common_lib import error
Mary Ruthvena30e0652017-08-18 13:29:48 -070011from autotest_lib.client.common_lib.cros import cr50_utils
Mary Ruthven300b1fa2017-05-30 10:24:23 -070012from autotest_lib.server.cros.servo import chrome_ec
13
14
Mary Ruthvenfd3d7aa2018-01-26 17:07:52 -080015def servo_v4_command(func):
16 """Decorator for methods only relevant to tests running with servo v4."""
Mary Ruthven300b1fa2017-05-30 10:24:23 -070017 @functools.wraps(func)
18 def wrapper(instance, *args, **kwargs):
Mary Ruthvenfd3d7aa2018-01-26 17:07:52 -080019 """Ignore servo v4 functions it's not being used."""
20 if instance.using_servo_v4():
Mary Ruthven300b1fa2017-05-30 10:24:23 -070021 return func(instance, *args, **kwargs)
Mary Ruthvenfd3d7aa2018-01-26 17:07:52 -080022 logging.info("not using servo v4. ignoring %s", func.func_name)
Mary Ruthven300b1fa2017-05-30 10:24:23 -070023 return wrapper
24
25
26class ChromeCr50(chrome_ec.ChromeConsole):
27 """Manages control of a Chrome Cr50.
28
29 We control the Chrome Cr50 via the console of a Servo board. Chrome Cr50
30 provides many interfaces to set and get its behavior via console commands.
31 This class is to abstract these interfaces.
32 """
Mary Ruthven3ff99492017-11-06 16:29:26 -080033 # The amount of time you need to show physical presence.
34 PP_SHORT = 15
35 PP_LONG = 300
Mary Ruthven300b1fa2017-05-30 10:24:23 -070036 IDLE_COUNT = 'count: (\d+)'
Mary Ruthvenec74b462017-09-27 15:04:49 -070037 # The version has four groups: the partition, the header version, debug
38 # descriptor and then version string.
39 # There are two partitions A and B. The active partition is marked with a
40 # '*'. If it is a debug image '/DBG' is added to the version string. If the
41 # image has been corrupted, the version information will be replaced with
42 # 'Error'.
43 # So the output may look something like this.
44 # RW_A: 0.0.21/cr50_v1.1.6133-fd788b
45 # RW_B: * 0.0.22/DBG/cr50_v1.1.6138-b9f0b1d
46 # Or like this if the region was corrupted.
47 # RW_A: * 0.0.21/cr50_v1.1.6133-fd788b
48 # RW_B: Error
49 VERSION_FORMAT = '\nRW_(A|B): +%s +(\d+\.\d+\.\d+|Error)(/DBG)?(\S+)?\s'
Mary Ruthvend89b5592017-09-28 11:41:10 -070050 INACTIVE_VERSION = VERSION_FORMAT % ''
51 ACTIVE_VERSION = VERSION_FORMAT % '\*'
Mary Ruthvenec74b462017-09-27 15:04:49 -070052 # Following lines of the version output may print the image board id
53 # information. eg.
54 # BID A: 5a5a4146:ffffffff:00007f00 Yes
55 # BID B: 00000000:00000000:00000000 Yes
56 # Use the first group from ACTIVE_VERSION to match the active board id
57 # partition.
Mary Ruthvend73fe4a2017-12-12 21:55:09 -080058 BID_ERROR = 'read_board_id: failed'
59 BID_FORMAT = ':\s+[a-f0-9:]+ '
60 ACTIVE_BID = r'%s.*(\1%s|%s.*>)' % (ACTIVE_VERSION, BID_FORMAT,
61 BID_ERROR)
Mary Ruthvend29ee882018-04-05 17:27:25 -070062 WAKE_CHAR = '\n\n'
63 WAKE_RESPONSE = ['(>|Console is enabled)']
Mary Ruthven56558292017-05-30 12:31:29 -070064 START_UNLOCK_TIMEOUT = 20
Mary Ruthven56558292017-05-30 12:31:29 -070065 GETTIME = ['= (\S+)']
Mary Ruthven9a0ce562017-05-30 13:01:47 -070066 FWMP_LOCKED_PROD = ["Managed device console can't be unlocked"]
67 FWMP_LOCKED_DBG = ['Ignoring FWMP unlock setting']
Mary Ruthven3c5dcf92017-06-26 15:55:15 -070068 MAX_RETRY_COUNT = 5
Mary Ruthven0f3e8342017-07-07 11:15:39 -070069 START_STR = ['(.*Console is enabled;)']
Mary Ruthven024c2dc2017-11-08 15:48:40 -080070 REBOOT_DELAY_WITH_CCD = 60
71 REBOOT_DELAY_WITH_FLEX = 3
Mary Ruthven2478d852017-12-13 13:09:02 -080072 ON_STRINGS = ['enable', 'enabled', 'on']
Mary Ruthven300b1fa2017-05-30 10:24:23 -070073
74
75 def __init__(self, servo):
Mary Ruthvence81d6e2017-11-28 14:48:22 -080076 super(ChromeCr50, self).__init__(servo, 'cr50_uart')
Mary Ruthven300b1fa2017-05-30 10:24:23 -070077
78
Mary Ruthvend29ee882018-04-05 17:27:25 -070079 def wake_cr50(self):
80 """Wake up cr50 by sending some linebreaks and wait for the response"""
81 logging.debug(super(ChromeCr50, self).send_command_get_output(
82 self.WAKE_CHAR, self.WAKE_RESPONSE))
83
84
Mary Ruthven300b1fa2017-05-30 10:24:23 -070085 def send_command(self, commands):
86 """Send command through UART.
87
88 Cr50 will drop characters input to the UART when it resumes from sleep.
89 If servo is not using ccd, send some dummy characters before sending the
90 real command to make sure cr50 is awake.
91 """
92 if not self.using_ccd():
Mary Ruthvend29ee882018-04-05 17:27:25 -070093 self.wake_cr50()
Mary Ruthven300b1fa2017-05-30 10:24:23 -070094 super(ChromeCr50, self).send_command(commands)
95
96
Mary Ruthven0e0aba82018-04-26 17:01:38 -070097 def set_cap(self, cap, config):
98 """Set the capability to the config value"""
99 self.set_caps({ cap : config })
100
101
102 def set_caps(self, cap_dict):
103 """Use cap_dict to set all the cap values
104
105 Set all of the capabilities in cap_dict to the correct config.
106
107 Args:
108 cap_dict: A dictionary with the capability as key and the desired
109 setting as values
110 """
111 for cap, config in cap_dict.iteritems():
112 self.send_command('ccd set %s %s' % (cap, config))
113 current_cap_settings = self.get_cap_dict()
114 for cap, config in cap_dict.iteritems():
115 if current_cap_settings[cap].lower() != config.lower():
116 raise error.TestFail('Failed to set %s to %s' % (cap, config))
117
118
119 def get_cap_dict(self):
120 """Get the current ccd capability settings.
121
122 Returns:
123 A dictionary with the capability as the key and the setting as the
124 value
125 """
126 caps = {}
127 rv = self.send_command_get_output('ccd',
Mary Ruthvenb393cbf2018-05-11 15:23:25 -0700128 ["Capabilities:\s+[\da-f]+\s(.*)Use 'ccd help'"])[0][1]
Mary Ruthven0e0aba82018-04-26 17:01:38 -0700129 for line in rv.splitlines():
130 # Line information is separated with an =
131 # RebootECAP Y 0=Default (IfOpened)
132 # Extract the capability name and the value. The first word after
133 # the equals sign is the only one that matters. 'Default' is the
134 # value not IfOpened
135 line = line.strip()
136 if '=' not in line:
137 continue
138 logging.info(line)
139 start, end = line.split('=')
140 caps[start.split()[0]] = end.split()[0]
141 logging.debug(caps)
142 return caps
143
144
Mary Ruthven300b1fa2017-05-30 10:24:23 -0700145 def send_command_get_output(self, command, regexp_list):
146 """Send command through UART and wait for response.
147
148 Cr50 will drop characters input to the UART when it resumes from sleep.
149 If servo is not using ccd, send some dummy characters before sending the
150 real command to make sure cr50 is awake.
151 """
152 if not self.using_ccd():
Mary Ruthvend29ee882018-04-05 17:27:25 -0700153 self.wake_cr50()
Mary Ruthvenad847b72018-05-25 17:24:54 -0700154
155 # We have started prepending '\n' to separate cr50 console junk from
156 # the real command. If someone is just searching for .*>, then they will
157 # only get the output from the first '\n' we added. Raise an error to
158 # change the test to look for something more specific ex command.*>.
159 # cr50 will print the command in the output, so that is an easy way to
160 # modify '.*>' to match the real command output.
161 if '.*>' in regexp_list:
162 raise error.TestError('Send more specific regexp %r %r' % (command,
163 regexp_list))
164
165 # prepend \n to separate the command from any junk that may have been
166 # sent to the cr50 uart.
167 command = '\n' + command
Mary Ruthven300b1fa2017-05-30 10:24:23 -0700168 return super(ChromeCr50, self).send_command_get_output(command,
169 regexp_list)
170
Mary Ruthven599395b2018-05-25 15:31:31 -0700171 def send_command_retry_get_output(self, command, regexp_list, tries=3):
172 """Retry sending a command if we can't find the output.
173
174 Cr50 may print irrelevant output while printing command output. It may
175 prevent the regex from matching. Send command and get the output. If it
176 fails try again.
177
178 If it fails every time, raise an error.
179
180 Don't use this to set something that should only be set once.
181 """
182 # TODO(b/80319784): once chan is unrestricted, use it to restrict what
183 # output cr50 prints while we are sending commands.
184 for i in range(tries):
185 try:
186 return self.send_command_get_output(command, regexp_list)
187 except error.TestFail, e:
188 logging.info('Failed to get %r output: %r', command, e.message)
189 # Raise the last error, if we never successfully returned the command
190 # output
191 logging.info('Could not get %r output after %d tries', command, tries)
192 raise
193
194
Mary Ruthven300b1fa2017-05-30 10:24:23 -0700195
196 def get_deep_sleep_count(self):
197 """Get the deep sleep count from the idle task"""
Mary Ruthvenf23dde12018-05-25 15:50:09 -0700198 result = self.send_command_retry_get_output('idle', [self.IDLE_COUNT])
Mary Ruthven300b1fa2017-05-30 10:24:23 -0700199 return int(result[0][1])
200
201
202 def clear_deep_sleep_count(self):
203 """Clear the deep sleep count"""
Mary Ruthvenf23dde12018-05-25 15:50:09 -0700204 result = self.send_command_retry_get_output('idle c', [self.IDLE_COUNT])
Mary Ruthven300b1fa2017-05-30 10:24:23 -0700205 if int(result[0][1]):
206 raise error.TestFail("Could not clear deep sleep count")
207
208
Mary Ruthven9d4ba2a2018-05-04 17:52:43 -0700209 def get_board_properties(self):
210 """Get information from the version command"""
Mary Ruthvenf23dde12018-05-25 15:50:09 -0700211 rv = self.send_command_retry_get_output('brdprop',
Mary Ruthven9d4ba2a2018-05-04 17:52:43 -0700212 ['properties = (\S+)'])
213 return int(rv[0][1], 16)
214
215
Mary Ruthven300b1fa2017-05-30 10:24:23 -0700216 def has_command(self, cmd):
217 """Returns 1 if cr50 has the command 0 if it doesn't"""
218 try:
Mary Ruthvenf23dde12018-05-25 15:50:09 -0700219 self.send_command_retry_get_output('help', [cmd])
Mary Ruthven300b1fa2017-05-30 10:24:23 -0700220 except:
221 logging.info("Image does not include '%s' command", cmd)
222 return 0
223 return 1
224
225
226 def erase_nvmem(self):
227 """Use flasherase to erase both nvmem sections"""
228 if not self.has_command('flasherase'):
229 raise error.TestError("need image with 'flasherase'")
230
231 self.send_command('flasherase 0x7d000 0x3000')
232 self.send_command('flasherase 0x3d000 0x3000')
233
234
235 def reboot(self):
Mary Ruthvenfc259742017-08-08 14:25:50 -0700236 """Reboot Cr50 and wait for cr50 to reset"""
237 response = [] if self.using_ccd() else self.START_STR
Mary Ruthven6d0a39a2017-09-19 13:32:50 -0700238 self.send_command_get_output('reboot', response)
Mary Ruthvenfc259742017-08-08 14:25:50 -0700239
240 # ccd will stop working after the reboot. Wait until that happens and
241 # reenable it.
242 if self.using_ccd():
243 self.wait_for_reboot()
244
245
246 def _uart_wait_for_reboot(self, timeout=60):
247 """Wait for the cr50 to reboot and enable the console.
248
249 This will wait up to timeout seconds for cr50 to print the start string.
250
251 Args:
252 timeout: seconds to wait to detect the reboot.
253 """
Mary Ruthvence81d6e2017-11-28 14:48:22 -0800254 original_timeout = float(self._servo.get('cr50_uart_timeout'))
Mary Ruthvenfc259742017-08-08 14:25:50 -0700255 # Change the console timeout to timeout, so we wait at least that long
256 # for cr50 to print the start string.
Mary Ruthvence81d6e2017-11-28 14:48:22 -0800257 self._servo.set_nocheck('cr50_uart_timeout', timeout)
Mary Ruthvenfc259742017-08-08 14:25:50 -0700258 try:
259 self.send_command_get_output('\n', self.START_STR)
Mary Ruthven024c2dc2017-11-08 15:48:40 -0800260 logging.debug('Detected cr50 reboot')
Mary Ruthvenfc259742017-08-08 14:25:50 -0700261 except error.TestFail, e:
Mary Ruthven024c2dc2017-11-08 15:48:40 -0800262 logging.debug('Failed to detect cr50 reboot')
Mary Ruthvenfc259742017-08-08 14:25:50 -0700263 # Reset the timeout.
Mary Ruthvence81d6e2017-11-28 14:48:22 -0800264 self._servo.set_nocheck('cr50_uart_timeout', original_timeout)
Mary Ruthven0f3e8342017-07-07 11:15:39 -0700265
266
267 def wait_for_reboot(self, timeout=60):
268 """Wait for cr50 to reboot"""
Mary Ruthven0ddea912017-06-27 14:31:58 -0700269 if self.using_ccd():
Mary Ruthven0f3e8342017-07-07 11:15:39 -0700270 # Cr50 USB is reset when it reboots. Wait for the CCD connection to
271 # go down to detect the reboot.
272 self.wait_for_ccd_disable(timeout, raise_error=False)
Mary Ruthven0ddea912017-06-27 14:31:58 -0700273 self.ccd_enable()
274 else:
Mary Ruthvenfc259742017-08-08 14:25:50 -0700275 self._uart_wait_for_reboot(timeout)
Mary Ruthven300b1fa2017-05-30 10:24:23 -0700276
277
Mary Ruthven4e1bb112017-08-24 13:17:24 -0700278 def rollback(self, eraseflashinfo=True, chip_bid=None, chip_flags=None):
279 """Set the reset counter high enough to force a rollback then reboot
280
281 Set the new board id before rolling back if one is given.
282
283 Args:
284 eraseflashinfo: True if eraseflashinfo should be run before rollback
285 chip_bid: the integer representation of chip board id or None if the
286 board id should be erased during rollback
287 chip_flags: the integer representation of chip board id flags or
288 None if the board id should be erased during rollback
289 """
Mary Ruthven024c2dc2017-11-08 15:48:40 -0800290 if (not self.has_command('rollback') or not
291 self.has_command('eraseflashinfo')):
292 raise error.TestError("need image with 'rollback' and "
293 "'eraseflashinfo'")
Mary Ruthven300b1fa2017-05-30 10:24:23 -0700294
Mary Ruthven3c5dcf92017-06-26 15:55:15 -0700295 inactive_partition = self.get_inactive_version_info()[0]
Mary Ruthven4e1bb112017-08-24 13:17:24 -0700296 # Set the board id if both the board id and flags have been given.
297 set_bid = chip_bid and chip_flags
298
299 # Erase the infomap
300 if eraseflashinfo or set_bid:
Mary Ruthven3c5dcf92017-06-26 15:55:15 -0700301 self.send_command('eraseflashinfo')
Mary Ruthven300b1fa2017-05-30 10:24:23 -0700302
Mary Ruthven4e1bb112017-08-24 13:17:24 -0700303 # Update the board id after it has been erased
304 if set_bid:
305 self.send_command('bid 0x%x 0x%x' % (chip_bid, chip_flags))
306
Mary Ruthvenc82325e2018-03-27 20:40:45 -0700307 if self.using_ccd():
308 self.send_command('rollback')
309 self.wait_for_reboot()
310 else:
311 logging.debug(self.send_command_get_output('rollback',
312 ['.*Console is enabled'])[0])
Mary Ruthven300b1fa2017-05-30 10:24:23 -0700313
Mary Ruthven3c5dcf92017-06-26 15:55:15 -0700314 running_partition = self.get_active_version_info()[0]
315 if inactive_partition != running_partition:
316 raise error.TestError("Failed to rollback to inactive image")
317
318
319 def rolledback(self):
320 """Returns true if cr50 just rolled back"""
Mary Ruthven6fbb8122018-03-28 11:16:18 -0700321 return 'Rollback detected' in self.send_command_get_output('sysinfo',
Mary Ruthvenafe51ef2018-05-25 17:58:55 -0700322 ['sysinfo.*>'])[0]
Mary Ruthven3c5dcf92017-06-26 15:55:15 -0700323
Mary Ruthven300b1fa2017-05-30 10:24:23 -0700324
325 def get_version_info(self, regexp):
326 """Get information from the version command"""
Mary Ruthvenf23dde12018-05-25 15:50:09 -0700327 return self.send_command_retry_get_output('ver', [regexp])[0][1::]
Mary Ruthven300b1fa2017-05-30 10:24:23 -0700328
329
330 def get_inactive_version_info(self):
331 """Get the active partition, version, and hash"""
Mary Ruthvend89b5592017-09-28 11:41:10 -0700332 return self.get_version_info(self.INACTIVE_VERSION)
Mary Ruthven300b1fa2017-05-30 10:24:23 -0700333
334
335 def get_active_version_info(self):
336 """Get the active partition, version, and hash"""
Mary Ruthvend89b5592017-09-28 11:41:10 -0700337 return self.get_version_info(self.ACTIVE_VERSION)
Mary Ruthven300b1fa2017-05-30 10:24:23 -0700338
339
Mary Ruthvenac1d1472018-03-15 14:52:41 -0700340 def using_prod_rw_keys(self):
341 """Returns True if the RW keyid is prod"""
Mary Ruthvenf23dde12018-05-25 15:50:09 -0700342 rv = self.send_command_retry_get_output('sysinfo',
Mary Ruthvenac1d1472018-03-15 14:52:41 -0700343 ['RW keyid:.*\(([a-z]+)\)'])
344 logging.info(rv)
345 return rv[0][1] == 'prod'
346
347
Mary Ruthvena30e0652017-08-18 13:29:48 -0700348 def get_active_board_id_str(self):
349 """Get the running image board id.
350
351 Returns:
352 The board id string or None if the image does not support board id
353 or the image is not board id locked.
354 """
355 # Getting the board id from the version console command is only
356 # supported in board id locked images .22 and above. Any image that is
357 # board id locked will have support for getting the image board id.
358 #
359 # If board id is not supported on the device, return None. This is
360 # still expected on all current non board id locked release images.
Mary Ruthvend73fe4a2017-12-12 21:55:09 -0800361 try:
362 version_info = self.get_version_info(self.ACTIVE_BID)
363 except error.TestFail, e:
364 logging.info(e.message)
Mary Ruthvena30e0652017-08-18 13:29:48 -0700365 logging.info('Cannot use the version to get the board id')
366 return None
367
Mary Ruthvend73fe4a2017-12-12 21:55:09 -0800368 if self.BID_ERROR in version_info[4]:
369 raise error.TestError(version_info)
370 bid = version_info[4].split()[1]
Mary Ruthvena30e0652017-08-18 13:29:48 -0700371 return bid if bid != cr50_utils.EMPTY_IMAGE_BID else None
372
373
Mary Ruthven2b7980b2017-06-28 15:05:30 -0700374 def get_version(self):
375 """Get the RW version"""
376 return self.get_active_version_info()[1].strip()
377
378
Mary Ruthven300b1fa2017-05-30 10:24:23 -0700379 def using_servo_v4(self):
380 """Returns true if the console is being served using servo v4"""
381 return 'servo_v4' in self._servo.get_servo_version()
382
383
384 def using_ccd(self):
385 """Returns true if the console is being served using CCD"""
386 return 'ccd_cr50' in self._servo.get_servo_version()
387
388
Mary Ruthvenfd3d7aa2018-01-26 17:07:52 -0800389 def ccd_is_enabled(self):
390 """Return True if ccd is enabled.
391
392 If the test is running through ccd, return the ccd_state value. If
393 a flex cable is being used, use the CCD_MODE_L gpio setting to determine
394 if Cr50 has ccd enabled.
Mary Ruthven300b1fa2017-05-30 10:24:23 -0700395
396 Returns:
397 'off' or 'on' based on whether the cr50 console is working.
398 """
Mary Ruthvenfd3d7aa2018-01-26 17:07:52 -0800399 if self.using_ccd():
400 return self._servo.get('ccd_state') == 'on'
401 else:
Mary Ruthvenf23dde12018-05-25 15:50:09 -0700402 result = self.send_command_retry_get_output('gpioget',
Mary Ruthvenfd3d7aa2018-01-26 17:07:52 -0800403 ['(0|1)..CCD_MODE_L'])
404 return not bool(int(result[0][1]))
Mary Ruthven300b1fa2017-05-30 10:24:23 -0700405
406
Mary Ruthvenfd3d7aa2018-01-26 17:07:52 -0800407 @servo_v4_command
408 def wait_for_ccd_state(self, state, timeout, raise_error):
Mary Ruthven300b1fa2017-05-30 10:24:23 -0700409 """Wait up to timeout seconds for CCD to be 'on' or 'off'
410 Args:
411 state: a string either 'on' or 'off'.
412 timeout: time in seconds to wait
413 raise_error: Raise TestFail if the value is state is not reached.
414
415 Raises
416 TestFail if ccd never reaches the specified state
417 """
Mary Ruthvenfd3d7aa2018-01-26 17:07:52 -0800418 wait_for_enable = state == 'on'
Mary Ruthven300b1fa2017-05-30 10:24:23 -0700419 logging.info("Wait until ccd is '%s'", state)
Mary Ruthvenfd3d7aa2018-01-26 17:07:52 -0800420 value = utils.wait_for_value(self.ccd_is_enabled, wait_for_enable,
Mary Ruthven300b1fa2017-05-30 10:24:23 -0700421 timeout_sec=timeout)
Mary Ruthvenfd3d7aa2018-01-26 17:07:52 -0800422 if value != wait_for_enable:
Mary Ruthven300b1fa2017-05-30 10:24:23 -0700423 error_msg = "timed out before detecting ccd '%s'" % state
424 if raise_error:
425 raise error.TestFail(error_msg)
426 logging.warning(error_msg)
Mary Ruthvenfd3d7aa2018-01-26 17:07:52 -0800427 logging.info("ccd is '%s'", state)
Mary Ruthven300b1fa2017-05-30 10:24:23 -0700428
429
Mary Ruthvenfd3d7aa2018-01-26 17:07:52 -0800430 @servo_v4_command
Mary Ruthven300b1fa2017-05-30 10:24:23 -0700431 def wait_for_ccd_disable(self, timeout=60, raise_error=True):
432 """Wait for the cr50 console to stop working"""
433 self.wait_for_ccd_state('off', timeout, raise_error)
434
435
Mary Ruthvenfd3d7aa2018-01-26 17:07:52 -0800436 @servo_v4_command
437 def wait_for_ccd_enable(self, timeout=60, raise_error=False):
Mary Ruthven300b1fa2017-05-30 10:24:23 -0700438 """Wait for the cr50 console to start working"""
Mary Ruthvenfd3d7aa2018-01-26 17:07:52 -0800439 self.wait_for_ccd_state('on', timeout, raise_error)
Mary Ruthven300b1fa2017-05-30 10:24:23 -0700440
441
Mary Ruthvenfd3d7aa2018-01-26 17:07:52 -0800442 @servo_v4_command
443 def ccd_disable(self, raise_error=True):
Mary Ruthven300b1fa2017-05-30 10:24:23 -0700444 """Change the values of the CC lines to disable CCD"""
Mary Ruthvenfd3d7aa2018-01-26 17:07:52 -0800445 logging.info("disable ccd")
446 self._servo.set_nocheck('servo_v4_dts_mode', 'off')
447 self.wait_for_ccd_disable(raise_error=raise_error)
Mary Ruthven300b1fa2017-05-30 10:24:23 -0700448
449
Mary Ruthvenfd3d7aa2018-01-26 17:07:52 -0800450 @servo_v4_command
451 def ccd_enable(self, raise_error=False):
Mary Ruthven300b1fa2017-05-30 10:24:23 -0700452 """Reenable CCD and reset servo interfaces"""
453 logging.info("reenable ccd")
Mary Ruthven300b1fa2017-05-30 10:24:23 -0700454 self._servo.set_nocheck('servo_v4_dts_mode', 'on')
Mary Ruthvenfd3d7aa2018-01-26 17:07:52 -0800455 # If the test is actually running with ccd, reset usb and wait for
456 # communication to come up.
457 if self.using_ccd():
458 self._servo.set_nocheck('power_state', 'ccd_reset')
459 self.wait_for_ccd_enable(raise_error=raise_error)
Mary Ruthven56558292017-05-30 12:31:29 -0700460
461
Mary Ruthvenec8d3d22017-09-15 14:45:21 -0700462 def _level_change_req_pp(self, level):
463 """Returns True if setting the level will require physical presence"""
464 testlab_pp = level != 'testlab open' and 'testlab' in level
465 open_pp = level == 'open'
466 return testlab_pp or open_pp
Mary Ruthven56558292017-05-30 12:31:29 -0700467
468
Mary Ruthven2478d852017-12-13 13:09:02 -0800469 def _state_to_bool(self, state):
470 """Converts the state string to True or False"""
471 # TODO(mruthven): compare to 'on' once servo is up to date in the lab
472 return state.lower() in self.ON_STRINGS
473
474
Mary Ruthven603a6612018-05-31 15:24:57 -0700475 def fast_open(self, enable_testlab=False):
476 """Try to use testlab open. If that fails do regular open
477
478 Args:
479 enable_testlab: If True enable testlab after device is open
480 """
481 self.send_command('ccd testlab open')
482 self.set_ccd_level('open')
483 if enable_testlab:
484 self.set_ccd_testlab('on')
485
486
Mary Ruthven2478d852017-12-13 13:09:02 -0800487 def testlab_is_on(self):
488 """Returns True of testlab mode is on"""
489 return self._state_to_bool(self._servo.get('cr50_testlab'))
490
491
Mary Ruthven62600242017-12-14 14:30:46 -0800492 def set_ccd_testlab(self, state):
Mary Ruthven3ff99492017-11-06 16:29:26 -0800493 """Set the testlab mode
494
495 Args:
Mary Ruthven2478d852017-12-13 13:09:02 -0800496 state: the desired testlab mode string: 'on' or 'off'
Mary Ruthven3ff99492017-11-06 16:29:26 -0800497
498 Raises:
499 TestFail if testlab mode was not changed
500 """
501 if self.using_ccd():
502 raise error.TestError('Cannot set testlab mode with CCD. Use flex '
503 'cable instead.')
504
Mary Ruthven2478d852017-12-13 13:09:02 -0800505 request_on = self._state_to_bool(state)
506 testlab_on = self.testlab_is_on()
507 request_str = 'on' if request_on else 'off'
508
509 if testlab_on == request_on:
510 logging.info('ccd testlab already set to %s', request_str)
Mary Ruthven3ff99492017-11-06 16:29:26 -0800511 return
512
Mary Ruthvenb9757932017-12-14 13:17:18 -0800513 original_level = self.get_ccd_level()
Mary Ruthven3ff99492017-11-06 16:29:26 -0800514
515 # We can only change the testlab mode when the device is open. If
516 # testlab mode is already enabled, we can go directly to open using 'ccd
517 # testlab open'. This will save 5 minutes, because we can skip the
518 # physical presence check.
Mary Ruthven2478d852017-12-13 13:09:02 -0800519 if testlab_on:
Mary Ruthven3ff99492017-11-06 16:29:26 -0800520 self.send_command('ccd testlab open')
521 else:
Mary Ruthven9498d672017-12-14 13:50:39 -0800522 self.set_ccd_level('open')
Mary Ruthven3ff99492017-11-06 16:29:26 -0800523
524 # Set testlab mode
Mary Ruthven2478d852017-12-13 13:09:02 -0800525 rv = self.send_command_get_output('ccd testlab %s' % request_str,
Mary Ruthvenafe51ef2018-05-25 17:58:55 -0700526 ['ccd.*>'])[0]
Mary Ruthven3ff99492017-11-06 16:29:26 -0800527 if 'Access Denied' in rv:
Mary Ruthven2478d852017-12-13 13:09:02 -0800528 raise error.TestFail("'ccd %s' %s" % (request_str, rv))
Mary Ruthven3ff99492017-11-06 16:29:26 -0800529
530 # Press the power button once a second for 15 seconds.
531 self.run_pp(self.PP_SHORT)
532
Mary Ruthven9498d672017-12-14 13:50:39 -0800533 self.set_ccd_level(original_level)
Mary Ruthven3ff99492017-11-06 16:29:26 -0800534
Mary Ruthvend43ebd32018-01-09 11:08:43 -0800535 if request_on != self.testlab_is_on():
Mary Ruthven3ff99492017-11-06 16:29:26 -0800536 raise error.TestFail('Failed to set ccd testlab to %s' % state)
537
538
Mary Ruthvenb9757932017-12-14 13:17:18 -0800539 def get_ccd_level(self):
540 """Returns the current ccd privilege level"""
541 # TODO(mruthven): delete the part removing the trailing 'ed' once
542 # servo is up to date in the lab
543 return self._servo.get('cr50_ccd_level').lower().rstrip('ed')
Mary Ruthven56558292017-05-30 12:31:29 -0700544
Mary Ruthvenb9757932017-12-14 13:17:18 -0800545
546 def set_ccd_level(self, level):
547 """Set the Cr50 CCD privilege level.
548
549 Args:
550 level: a string of the ccd privilege level: 'open', 'lock', or
551 'unlock'.
552
553 Raises:
554 TestFail if the level couldn't be set
555 ."""
556 # TODO(mruthven): add support for CCD password
557 level = level.lower()
558
559 if level == self.get_ccd_level():
Mary Ruthvenec8d3d22017-09-15 14:45:21 -0700560 logging.info('CCD privilege level is already %s', level)
561 return
Mary Ruthven56558292017-05-30 12:31:29 -0700562
Mary Ruthvenec8d3d22017-09-15 14:45:21 -0700563 if 'testlab' in level:
564 raise error.TestError("Can't change testlab mode using "
565 "ccd_set_level")
Mary Ruthven56558292017-05-30 12:31:29 -0700566
Mary Ruthven2478d852017-12-13 13:09:02 -0800567 testlab_on = self._state_to_bool(self._servo.get('cr50_testlab'))
Mary Ruthvenec8d3d22017-09-15 14:45:21 -0700568 req_pp = self._level_change_req_pp(level)
569 has_pp = not self.using_ccd()
570 dbg_en = 'DBG' in self._servo.get('cr50_version')
571
572 if req_pp and not has_pp:
573 raise error.TestError("Can't change privilege level to '%s' "
574 "without physical presence." % level)
575
Mary Ruthven2478d852017-12-13 13:09:02 -0800576 if not testlab_on and not has_pp:
Mary Ruthvenec8d3d22017-09-15 14:45:21 -0700577 raise error.TestError("Wont change privilege level without "
578 "physical presence or testlab mode enabled")
579
Mary Ruthvenec8d3d22017-09-15 14:45:21 -0700580 # Start the unlock process.
Mary Ruthvenafe51ef2018-05-25 17:58:55 -0700581 rv = self.send_command_get_output('ccd %s' % level, ['ccd.*>'])[0]
Mary Ruthven3ff99492017-11-06 16:29:26 -0800582 logging.info(rv)
583 if 'Access Denied' in rv:
584 raise error.TestFail("'ccd %s' %s" % (level, rv))
Mary Ruthvenec8d3d22017-09-15 14:45:21 -0700585
Mary Ruthven3ff99492017-11-06 16:29:26 -0800586 # Press the power button once a second, if we need physical presence.
Mary Ruthvenec8d3d22017-09-15 14:45:21 -0700587 if req_pp:
588 # DBG images have shorter unlock processes
Mary Ruthven3ff99492017-11-06 16:29:26 -0800589 self.run_pp(self.PP_SHORT if dbg_en else self.PP_LONG)
Mary Ruthvenec8d3d22017-09-15 14:45:21 -0700590
Mary Ruthvenb9757932017-12-14 13:17:18 -0800591 if level != self.get_ccd_level():
Mary Ruthvenec8d3d22017-09-15 14:45:21 -0700592 raise error.TestFail('Could not set privilege level to %s' % level)
593
594 logging.info('Successfully set CCD privelege level to %s', level)
Mary Ruthven4d3a77a2017-08-10 17:27:59 -0700595
596
Mary Ruthven3ff99492017-11-06 16:29:26 -0800597 def run_pp(self, unlock_timeout):
598 """Press the power button a for unlock_timeout seconds.
599
600 This will press the power button many more times than it needs to be
601 pressed. Cr50 doesn't care if you press it too often. It just cares that
602 you press the power button at least once within the detect interval.
603
604 For privilege level changes you need to press the power button 5 times
605 in the short interval and then 4 times within the long interval.
606 Short Interval
607 100msec < power button press < 5 seconds
608 Long Interval
609 60s < power button press < 300s
610
611 For testlab enable/disable you must press the power button 5 times
612 spaced between 100msec and 5 seconds apart.
613 """
614 end_time = time.time() + unlock_timeout
615
616 logging.info('Pressing power button for %ds to unlock the console.',
617 unlock_timeout)
618 logging.info('The process should end at %s', time.ctime(end_time))
619
620 # Press the power button once a second to unlock the console.
621 while time.time() < end_time:
622 self._servo.power_short_press()
623 time.sleep(1)
624
625
Mary Ruthven4d3a77a2017-08-10 17:27:59 -0700626 def gettime(self):
627 """Get the current cr50 system time"""
Mary Ruthvenf23dde12018-05-25 15:50:09 -0700628 result = self.send_command_retry_get_output('gettime', [' = (.*) s'])
Mary Ruthven4d3a77a2017-08-10 17:27:59 -0700629 return float(result[0][1])
630
Mary Ruthvena30e0652017-08-18 13:29:48 -0700631
Mary Ruthvenfd3d7aa2018-01-26 17:07:52 -0800632 def servo_v4_supports_dts_mode(self):
633 """Returns True if cr50 registers changes in servo v4 dts mode."""
634 # This is to test that Cr50 actually recognizes the change in ccd state
635 # We cant do that with tests using ccd, because the cr50 communication
636 # goes down once ccd is enabled.
637 if 'servo_v4_with_servo_micro' != self._servo.get_servo_version():
638 return False
639
Mary Ruthven0a999e62018-04-18 16:11:21 -0700640 ccd_start = 'on' if self.ccd_is_enabled() else 'off'
641 dts_start = self._servo.get('servo_v4_dts_mode')
Mary Ruthvenfd3d7aa2018-01-26 17:07:52 -0800642 try:
643 # Verify both ccd enable and disable
644 self.ccd_disable(raise_error=True)
645 self.ccd_enable(raise_error=True)
646 rv = True
647 except Exception, e:
648 logging.info(e)
649 rv = False
Mary Ruthven0a999e62018-04-18 16:11:21 -0700650 self._servo.set_nocheck('servo_v4_dts_mode', dts_start)
651 self.wait_for_ccd_state(ccd_start, 60, True)
Mary Ruthvenfd3d7aa2018-01-26 17:07:52 -0800652 logging.info('Test setup does%s support servo DTS mode',
653 '' if rv else 'n\'t')
654 return rv
655
656
Mary Ruthven4d3a77a2017-08-10 17:27:59 -0700657 def wait_until_update_is_allowed(self):
658 """Wait until cr50 will be able to accept an update.
659
660 Cr50 rejects any attempt to update if it has been less than 60 seconds
661 since it last recovered from deep sleep or came up from reboot. This
662 will wait until cr50 gettime shows a time greater than 60.
663 """
Mary Ruthven91d86182017-09-29 13:46:21 -0700664 if self.get_active_version_info()[2]:
665 logging.info("Running DBG image. Don't need to wait for update.")
666 return
Mary Ruthven4d3a77a2017-08-10 17:27:59 -0700667 cr50_time = self.gettime()
668 if cr50_time < 60:
669 sleep_time = 61 - cr50_time
670 logging.info('Cr50 has been up for %ds waiting %ds before update',
671 cr50_time, sleep_time)
672 time.sleep(sleep_time)