1#!/usr/bin/env python 2# Copyright 2019 The Chromium OS Authors. All rights reserved. 3# Use of this source code is governed by a BSD-style license that can be 4# found in the LICENSE file. 5 6"""library functions to prepare a DUT for lab deployment. 7 8This library will be shared between Autotest and Skylab DUT deployment tools. 9""" 10 11from __future__ import absolute_import 12from __future__ import division 13from __future__ import print_function 14 15import contextlib 16import time 17 18import common 19from autotest_lib.client.common_lib import error 20from autotest_lib.client.common_lib import utils 21from autotest_lib.server import hosts 22from autotest_lib.server import site_utils as server_utils 23from autotest_lib.server.hosts import host_info 24from autotest_lib.server.hosts import servo_host 25 26 27_FIRMWARE_UPDATE_TIMEOUT = 600 28 29 30@contextlib.contextmanager 31def create_host(hostname, board, model, servo_hostname, servo_port, 32 servo_serial=None, logs_dir=None): 33 """Yield a server.hosts.CrosHost object to use for DUT preparation. 34 35 This object contains just enough inventory data to be able to prepare the 36 DUT for lab deployment. It does not contain any reference to AFE / Skylab so 37 that DUT preparation is guaranteed to be isolated from the scheduling 38 infrastructure. 39 40 @param hostname: FQDN of the host to prepare. 41 @param board: The autotest board label for the DUT. 42 @param model: The autotest model label for the DUT. 43 @param servo_hostname: FQDN of the servo host controlling the DUT. 44 @param servo_port: Servo host port used for the controlling servo. 45 @param servo_serial: (Optional) Serial number of the controlling servo. 46 @param logs_dir: (Optional) Directory to save logs obtained from the 47 host. 48 49 @yield a server.hosts.Host object. 50 """ 51 labels = [ 52 'board:%s' % board, 53 'model:%s' % model, 54 ] 55 attributes = { 56 servo_host.SERVO_HOST_ATTR: servo_hostname, 57 servo_host.SERVO_PORT_ATTR: servo_port, 58 } 59 if servo_serial is not None: 60 attributes[servo_host.SERVO_SERIAL_ATTR] = servo_serial 61 62 store = host_info.InMemoryHostInfoStore(info=host_info.HostInfo( 63 labels=labels, 64 attributes=attributes, 65 )) 66 machine_dict = { 67 'hostname': hostname, 68 'host_info_store': store, 69 'afe_host': server_utils.EmptyAFEHost(), 70 } 71 host = hosts.create_host(machine_dict) 72 servohost = servo_host.ServoHost( 73 **servo_host.get_servo_args_for_host(host)) 74 _prepare_servo(servohost) 75 host.set_servo_host(servohost) 76 host.servo.uart_logs_dir = logs_dir 77 try: 78 yield host 79 finally: 80 host.close() 81 82 83def download_image_to_servo_usb(host, build): 84 """Download the given image to the USB attached to host's servo. 85 86 @param host A server.hosts.Host object. 87 @param build A Chrome OS version string for the build to download. 88 """ 89 host.servo.image_to_servo_usb(host.stage_image_for_servo(build)) 90 91 92def install_test_image(host): 93 """Install the test image for the given build to DUT. 94 95 This function assumes that the required image is already downloaded onto the 96 USB key connected to the DUT via servo. 97 98 @param host servers.host.Host object. 99 """ 100 host.servo_install() 101 102 103def flash_firmware_using_servo(host, build): 104 """Flash DUT firmware directly using servo. 105 106 Rather than running `chromeos-firmwareupdate` on DUT, we can flash DUT 107 firmware directly using servo (run command `flashrom`, etc. on servo). In 108 this way, we don't require DUT to be in dev mode and with dev_boot_usb 109 enabled.""" 110 host.firmware_install(build) 111 112 113def install_firmware(host, force): 114 """Install dev-signed firmware after removing write-protect. 115 116 At start, it's assumed that hardware write-protect is disabled, 117 the DUT is in dev mode, and the servo's USB stick already has a 118 test image installed. 119 120 The firmware is installed by powering on and typing ctrl+U on 121 the keyboard in order to boot the test image from USB. Once 122 the DUT is booted, we run a series of commands to install the 123 read-only firmware from the test image. Then we clear debug 124 mode, and shut down. 125 126 @param host Host instance to use for servo and ssh operations. 127 @param force Boolean value determining if firmware install is forced. 128 """ 129 servo = host.servo 130 # First power on. We sleep to allow the firmware plenty of time 131 # to display the dev-mode screen; some boards take their time to 132 # be ready for the ctrl+U after power on. 133 servo.get_power_state_controller().power_off() 134 servo.switch_usbkey('dut') 135 servo.get_power_state_controller().power_on() 136 time.sleep(10) 137 # Dev mode screen should be up now: type ctrl+U and wait for 138 # boot from USB to finish. 139 servo.ctrl_u() 140 if not host.wait_up(timeout=host.USB_BOOT_TIMEOUT): 141 raise Exception('DUT failed to boot in dev mode for ' 142 'firmware update') 143 # Disable software-controlled write-protect for both FPROMs, and 144 # install the RO firmware. 145 for fprom in ['host', 'ec']: 146 host.run('flashrom -p %s --wp-disable' % fprom, 147 ignore_status=True) 148 149 fw_update_log = '/mnt/stateful_partition/home/root/cros-fw-update.log' 150 pid = _start_firmware_update(host, force, fw_update_log) 151 _wait_firmware_update_process(host, pid) 152 _check_firmware_update_result(host, fw_update_log) 153 154 # Get us out of dev-mode and clear GBB flags. GBB flags are 155 # non-zero because boot from USB was enabled. 156 host.run('/usr/share/vboot/bin/set_gbb_flags.sh 0', 157 ignore_status=True) 158 host.run('crossystem disable_dev_request=1', 159 ignore_status=True) 160 host.halt() 161 162 163def _start_firmware_update(host, force, result_file): 164 """Run `chromeos-firmwareupdate` in background. 165 166 In scenario servo v4 type C, some boards of DUT may lose ethernet 167 connectivity on firmware update. There's no way to bring it back except 168 rebooting the system. 169 170 @param host Host instance to use for servo and ssh operations. 171 @param force Boolean value determining if firmware install is forced. 172 @param result_file Path on DUT to save operation logs. 173 174 @returns The process id.""" 175 fw_update_cmd = 'chromeos-firmwareupdate --mode=factory' 176 if force: 177 fw_update_cmd += ' --force' 178 179 cmd = [ 180 "date > %s" % result_file, 181 "nohup %s &>> %s" % (fw_update_cmd, result_file), 182 "/usr/local/bin/hooks/check_ethernet.hook" 183 ] 184 return host.run_background(';'.join(cmd)) 185 186 187def _wait_firmware_update_process(host, pid, timeout=_FIRMWARE_UPDATE_TIMEOUT): 188 """Wait `chromeos-firmwareupdate` to finish. 189 190 @param host Host instance to use for servo and ssh operations. 191 @param pid The process ID of `chromeos-firmwareupdate`. 192 @param timeout Maximum time to wait for firmware updating. 193 """ 194 try: 195 utils.poll_for_condition( 196 lambda: host.run('ps -f -p %s' % pid, timeout=20).exit_status, 197 exception=Exception( 198 "chromeos-firmwareupdate (pid: %s) didn't complete in %s " 199 'seconds.' % (pid, timeout)), 200 timeout=_FIRMWARE_UPDATE_TIMEOUT, 201 sleep_interval=10, 202 ) 203 except error.AutoservRunError: 204 # We lose the connectivity, so the DUT should be booting up. 205 if not host.wait_up(timeout=host.USB_BOOT_TIMEOUT): 206 raise Exception( 207 'DUT failed to boot up after firmware updating.') 208 209 210def _check_firmware_update_result(host, result_file): 211 """Check if firmware updating is good or not. 212 213 @param host Host instance to use for servo and ssh operations. 214 @param result_file Path of the file saving output of 215 `chromeos-firmwareupdate`. 216 """ 217 fw_update_was_good = ">> DONE: Firmware updater exits successfully." 218 result = host.run('cat %s' % result_file) 219 if result.stdout.rstrip().rsplit('\n', 1)[1] != fw_update_was_good: 220 raise Exception("chromeos-firmwareupdate failed!") 221 222 223def _prepare_servo(servohost): 224 """Prepare servo connected to host for installation steps. 225 226 @param servohost A server.hosts.servo_host.ServoHost object. 227 """ 228 # Stopping `servod` on the servo host will force `repair()` to 229 # restart it. We want that restart for a few reasons: 230 # + `servod` caches knowledge about the image on the USB stick. 231 # We want to clear the cache to force the USB stick to be 232 # re-imaged unconditionally. 233 # + If there's a problem with servod that verify and repair 234 # can't find, this provides a UI through which `servod` can 235 # be restarted. 236 servohost.run('stop servod PORT=%d' % servohost.servo_port, 237 ignore_status=True) 238 servohost.repair() 239 240 # Don't timeout probing for the host usb device, there could be a bunch 241 # of servos probing at the same time on the same servo host. And 242 # since we can't pass None through the xml rpcs, use 0 to indicate None. 243 if not servohost.get_servo().probe_host_usb_dev(timeout=0): 244 raise Exception('No USB stick detected on Servo host') 245