1# Copyright 2020 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 logging 6import six 7 8from autotest_lib.client.common_lib import error 9from autotest_lib.server.cros.faft.firmware_test import FirmwareTest 10from autotest_lib.server.cros.power import utils as PowerUtils 11 12 13class firmware_FWupdateThenSleep(FirmwareTest): 14 """Firmware update using chromeos-firmwareupdate --mode=recovery, then 15 sleep and wake, then make sure the host responds and the flash contents 16 don't change. 17 """ 18 MODE = 'recovery' 19 MIN_BATTERY_LEVEL = 20 20 21 def initialize(self, host, cmdline_args, battery_only=False): 22 23 self.flashed = False 24 self._want_restore = None 25 self._original_sw_wp = {} 26 self._original_hw_wp = None 27 28 super(firmware_FWupdateThenSleep, self).initialize(host, cmdline_args) 29 30 ac_online = self._client.is_ac_connected() 31 battery_percentage = self._client.get_battery_display_percentage() 32 self.have_power_control = False 33 if battery_only: 34 if not self._client.has_battery(): 35 raise error.TestNAError("DUT type does not have a battery.") 36 37 if self.servo.supports_built_in_pd_control(): 38 self.have_power_control = True 39 PowerUtils.put_host_battery_in_range(self._client, 40 min_level=25, 41 max_level=100, 42 timeout=600) 43 elif ac_online: 44 raise error.TestNAError( 45 "For this version of the test, the DUT power supply must" 46 " be unplugged, or connected through Servo V4 Type-C.") 47 elif battery_percentage < self.MIN_BATTERY_LEVEL: 48 raise error.TestError( 49 "Battery level is too low (%s%%). Please charge the DUT " 50 "to at least %s%%, then try again." 51 % (battery_percentage, self.MIN_BATTERY_LEVEL)) 52 else: 53 logging.info("On battery: %s%% charge", battery_percentage) 54 55 self._original_sw_wp = self.faft_client.bios.get_write_protect_status() 56 self._original_hw_wp = 'on' in self.servo.get('fw_wp_state') 57 58 self.backup_firmware() 59 self.setup_firmwareupdate_shellball() 60 61 if self.faft_config.ap_access_ec_flash: 62 self._setup_ec_write_protect(False) 63 self.set_ap_write_protect_and_reboot(False) 64 self.faft_client.bios.set_write_protect_range(0, 0, False) 65 66 if battery_only and self.have_power_control: 67 self.set_servo_v4_role_to_snk() 68 69 def cleanup(self): 70 """Restore the original firmware and original write-protect.""" 71 self._restore_servo_v4_role() 72 73 self.set_ap_write_protect_and_reboot(False) 74 try: 75 if self.flashed and self.is_firmware_saved(): 76 self.restore_firmware() 77 except (EnvironmentError, six.moves.xmlrpc_client.Fault, 78 error.AutoservError, error.TestBaseException): 79 logging.error("Problem restoring firmware:", exc_info=True) 80 81 try: 82 # Restore the old write-protection value at the end of the test. 83 if self._original_sw_wp: 84 self.faft_client.bios.set_write_protect_range( 85 self._original_sw_wp['start'], 86 self._original_sw_wp['length'], 87 self._original_sw_wp['enabled']) 88 except (EnvironmentError, six.moves.xmlrpc_client.Fault, 89 error.AutoservError, error.TestBaseException): 90 logging.error("Problem restoring SW write-protect:", exc_info=True) 91 92 if self._original_hw_wp is not None: 93 self.set_ap_write_protect_and_reboot(self._original_hw_wp) 94 95 super(firmware_FWupdateThenSleep, self).cleanup() 96 97 def get_installed_versions(self): 98 """Get the installed versions of BIOS and EC firmware. 99 100 @return: A nested dict keyed by target ('bios' or 'ec') and then section 101 @rtype: dict 102 """ 103 versions = dict() 104 versions['bios'] = self.faft_client.updater.get_device_fwids('bios') 105 if self.faft_config.chrome_ec: 106 versions['ec'] = self.faft_client.updater.get_device_fwids('ec') 107 return versions 108 109 def apply_update(self, append, before_fwids, image_fwids, wp=0): 110 """Run chromeos-firmwareupdate with given sub-case 111 112 @param append: additional piece to add to shellball name 113 @param wp: is the flash write protected (--wp)? 114 @return: a list of failure messages for the case 115 """ 116 options = ['--wp=%s' % wp, '--quirks=ec_partial_recovery=0'] 117 118 if append: 119 cmd_desc = ['chromeos-firmwareupdate-%s' % append] 120 else: 121 cmd_desc = ['chromeos-firmwareupdate'] 122 cmd_desc += ['--mode=%s' % self.MODE] 123 cmd_desc += options 124 cmd_desc = ' '.join(cmd_desc) 125 126 expected_written = {} 127 128 if wp: 129 bios_written = ['a', 'b'] 130 ec_written = [] # EC write is all-or-nothing 131 else: 132 bios_written = ['ro', 'a', 'b'] 133 ec_written = ['ro', 'rw'] 134 135 expected_written['bios'] = bios_written 136 137 if self.faft_config.chrome_ec and ec_written: 138 expected_written['ec'] = ec_written 139 140 # remove quotes and braces: bios: [a, b], ec: [ro, rw] 141 written_desc = repr(expected_written).replace("'", "")[1:-1] 142 logging.debug('Before(%s): %s', append or '', before_fwids) 143 logging.debug('Image(%s): %s', append or '', image_fwids) 144 logging.info("Run %s (should write %s)", cmd_desc, written_desc) 145 146 # make sure we restore firmware after the test, if it tried to flash. 147 self.flashed = True 148 149 errors = [] 150 result = self.run_chromeos_firmwareupdate( 151 self.MODE, append, options, ignore_status=True) 152 153 if result.exit_status == 255: 154 logging.warning("DUT network dropped during update.") 155 elif result.exit_status != 0: 156 if (image_fwids == before_fwids and 157 'Good. It seems nothing was changed.' in result.stdout): 158 logging.info("DUT already matched the image; updater aborted.") 159 else: 160 errors.append('...updater: unexpectedly failed (rc=%s)' % 161 result.exit_status) 162 163 after_fwids = self.get_installed_versions() 164 logging.debug('After(%s): %s', append or '', after_fwids) 165 166 errors += self.check_fwids_written( 167 before_fwids, image_fwids, after_fwids, expected_written) 168 169 if errors: 170 logging.debug('%s', '\n'.join(errors)) 171 return ["%s (should write %s)\n%s" 172 % (cmd_desc, written_desc, '\n'.join(errors))] 173 return [] 174 175 def run_once(self, wp=0, battery_only=False): 176 """Try a firmware update, then put the machine in sleep and wake it.""" 177 178 errors = [] 179 original_boot_id = self._client.get_boot_id() 180 181 have_ec = bool(self.faft_config.chrome_ec) 182 before_fwids = self.get_installed_versions() 183 184 self.modify_shellball('mod', modify_ro=True, modify_ec=have_ec) 185 modded_fwids = self.identify_shellball(include_ec=have_ec) 186 187 # Check power again, in case it got reconnected during setup. 188 if battery_only and self._client.is_ac_connected(): 189 if self.have_power_control: 190 raise error.TestError( 191 "DUT is on AC power, even though Servo role was set to" 192 " sink. Is a second power supply connected?") 193 else: 194 raise error.TestError( 195 "DUT is back on AC power, when it should be on battery." 196 " Did it get plugged back in?") 197 198 errors += self.apply_update('mod', before_fwids, modded_fwids, wp=wp) 199 if errors: 200 fail_msg = "Problem(s) occurred during flash, before sleep:" 201 errors.insert(0, fail_msg) 202 raise error.TestFail('\n'.join(errors)) 203 204 logging.info("Firmware flashed successfully; trying sleep.") 205 before_sleep_fwids = self.get_installed_versions() 206 self.suspend() 207 if self.faft_config.chrome_ec: 208 if not self.wait_power_state( 209 self.POWER_STATE_SUSPEND, self.DEFAULT_PWR_RETRIES): 210 raise error.TestFail('Platform failed to reach a suspend state' 211 ' after flashing firmware.') 212 else: 213 self.switcher.wait_for_client_offline(orig_boot_id=original_boot_id) 214 logging.info("System should now be in sleep; trying to wake.") 215 216 self.servo.power_normal_press() 217 self.switcher.wait_for_client() 218 219 after_sleep_fwids = self.get_installed_versions() 220 logging.debug('After sleep+wake: %s', after_sleep_fwids) 221 222 if have_ec: 223 expected_changes = {'bios': None, 'ec': None} 224 else: 225 expected_changes = {'bios': None} 226 227 after_sleep_errors = self.check_fwids_written( 228 before_sleep_fwids, None, after_sleep_fwids, expected_changes) 229 if after_sleep_errors: 230 errors += ['After sleep, FWIDs unexpectedly differ from' 231 ' what was written:'] + after_sleep_errors 232 233 if errors: 234 fail_msg = "Problem(s) occurred during flash + sleep + wake:" 235 errors.insert(0, fail_msg) 236 raise error.TestFail('\n'.join(errors)) 237