blob: 144785dd29a1a05970c27c2b748d7e23bb199869 [file] [log] [blame]
# Copyright 2020 The Chromium OS Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import logging
import os
from autotest_lib.client.common_lib import error
from autotest_lib.server.cros.faft.firmware_test import FirmwareTest
from autotest_lib.server.cros.faft.firmware_test import ConnectionError
BIOS = 'bios'
EC = 'ec'
class firmware_WriteProtectFunc(FirmwareTest):
"""
This test checks whether the SPI flash write-protection functionally works
"""
version = 1
def initialize(self, host, cmdline_args, dev_mode=False):
"""Initialize the test"""
super(firmware_WriteProtectFunc, self).initialize(host, cmdline_args)
self.switcher.setup_mode('dev' if dev_mode else 'normal')
if self.faft_config.chrome_ec:
self._targets = (BIOS, EC)
else:
self._targets = (BIOS, )
self._rpcs = {BIOS: self.faft_client.bios,
EC: self.faft_client.ec}
self._flashrom_targets = {BIOS: 'host', EC: 'ec'}
self._original_sw_wps = {}
for target in self._targets:
sw_wp_dict = self._rpcs[target].get_write_protect_status()
self._original_sw_wps[target] = sw_wp_dict['enabled']
self._original_hw_wp = 'on' in self.servo.get('fw_wp_state')
self.backup_firmware()
self.work_path = self.faft_client.system.create_temp_dir(
'flashrom_', '/mnt/stateful_partition/')
def cleanup(self):
"""Cleanup the test"""
try:
if self.is_firmware_saved():
self.restore_firmware()
except ConnectionError:
logging.error("ERROR: DUT did not come up after firmware restore!")
try:
# Recover SW WP status.
if hasattr(self, '_original_sw_wps'):
# If HW WP is enabled, we have to disable it first so that
# SW WP can be changed.
current_hw_wp = 'on' in self.servo.get('fw_wp_state')
if current_hw_wp:
self.set_ap_write_protect_and_reboot(False)
for target, original_sw_wp in self._original_sw_wps.items():
self._set_write_protect(target, original_sw_wp)
self.set_ap_write_protect_and_reboot(current_hw_wp)
# Recover HW WP status.
if hasattr(self, '_original_hw_wp'):
self.set_ap_write_protect_and_reboot(self._original_hw_wp)
except Exception as e:
logging.error('Caught exception: %s', str(e))
self.faft_client.system.remove_dir(self.work_path)
super(firmware_WriteProtectFunc, self).cleanup()
def _set_write_protect(self, target, enable):
"""
Set write_protect to `enable` for the specified target.
@param target: Which firmware to toggle the write-protect for,
either 'bios' or 'ec'
@type target: string
@param enable: Whether to enable or disable write-protect
@type enable: bool
"""
assert target in (BIOS, EC)
if target == BIOS:
# Unlock registers to alter the region/range
self.set_ap_write_protect_and_reboot(False)
self.faft_client.bios.set_write_protect_region('WP_RO', enable)
if enable:
self.set_ap_write_protect_and_reboot(True)
elif target == EC:
self.switcher.mode_aware_reboot('custom',
lambda:self.set_ec_write_protect_and_reboot(enable))
def _get_relative_path(self, target):
"""
Send an RPC.updater call to get the relative path for the target.
@param target: Which firmware to get the relative path to,
either 'bios' or 'ec'.
@type target: string
@return: The relative path of the bios/ec image in the shellball.
"""
assert target in (BIOS, EC)
if target == BIOS:
return self.faft_client.updater.get_bios_relative_path()
elif target == EC:
return self.faft_client.updater.get_ec_relative_path()
def run_cmd(self, command, checkfor=''):
"""
Log and execute command and return the output.
@param command: Command to execute on device.
@param checkfor: If not empty, make the test fail when this param
is not found in the command output.
@returns the output of command.
"""
command = command + ' 2>&1'
logging.info('Execute %s', command)
output = self.faft_client.system.run_shell_command_get_output(command)
logging.info('Output >>> %s <<<', output)
if checkfor and checkfor not in '\n'.join(output):
raise error.TestFail('Expect %s in output of cmd <%s>:\n\t%s' %
(checkfor, command, '\n\t'.join(output)))
return output
def get_wp_ro_firmware_section(self, firmware_file, wp_ro_firmware_file):
"""
Read out WP_RO section from the firmware file.
@param firmware_file: The AP or EC firmware binary to be parsed.
@param wp_ro_firmware_file: The file path for the WP_RO section
dumped from the firmware_file.
@returns the output of the dd command.
"""
cmd_output = self.run_cmd(
'futility dump_fmap -p %s WP_RO'% firmware_file)
if cmd_output:
unused_name, offset, size = cmd_output[0].split()
return self.run_cmd('dd bs=1 skip=%s count=%s if=%s of=%s' %
(offset, size, firmware_file, wp_ro_firmware_file))
def run_once(self):
"""Runs a single iteration of the test."""
# Enable WP
for target in self._targets:
self._set_write_protect(target, True)
# Check WP is properly enabled at the start
for target in self._targets:
sw_wp_dict = self._rpcs[target].get_write_protect_status()
if not sw_wp_dict['enabled']:
raise error.TestFail('Failed to enable %s SW WP at '
'test start' % target.upper())
reboots = (('shutdown cmd', lambda:self.run_shutdown_process(
lambda:self.run_shutdown_cmd())),
('reboot cmd', lambda:self.run_cmd('reboot')),
('power button', lambda:self.full_power_off_and_on()))
if self.faft_config.chrome_ec:
reboots += (('ec reboot', lambda:self.sync_and_ec_reboot('hard')), )
# Check if enabled SW WP can stay preserved across reboots.
for (reboot_name, reboot_method) in reboots:
self.switcher.mode_aware_reboot('custom', reboot_method)
for target in self._targets:
sw_wp_dict = self._rpcs[target].get_write_protect_status()
if not sw_wp_dict['enabled']:
raise error.TestFail('%s SW WP can not stay preserved '
'accross %s' %
(target.upper(), reboot_name))
work_path = self.work_path
# Check if RO FW really can't be overwritten when WP is enabled.
for target in self._targets:
# Current firmware image as read from flash
ro_before = os.path.join(work_path, '%s_ro_before.bin' % target)
# Current firmware image with modification to test writing
ro_test = os.path.join(work_path, '%s_ro_test.bin' % target)
# Firmware as read after writing flash
ro_after = os.path.join(work_path, '%s_ro_after.bin' % target)
# Fetch firmware from flash. This serves as the base of ro_test
self.run_cmd(
'flashrom -p %s -r -i WP_RO:%s ' %
(self._flashrom_targets[target], ro_before), 'SUCCESS')
lines = self.run_cmd('dump_fmap -p %s' % ro_before)
FMAP_AREA_NAMES = ['name', 'offset', 'size']
modified = False
wpro_offset = -1
for line in lines:
region = dict(zip(FMAP_AREA_NAMES, line.split()))
if region['name'] == 'WP_RO':
wpro_offset = int(region['offset'])
if wpro_offset == -1:
raise error.TestFail('WP_RO not found in fmap')
for line in lines:
region = dict(zip(FMAP_AREA_NAMES, line.split()))
if region['name'] == 'RO_FRID':
modified = True
self.run_cmd('cp %s %s' % (ro_before, ro_test))
self.run_cmd(
'dd if=%s bs=1 count=%d skip=%d '
'| tr "[a-zA-Z]" "[A-Za-z]" '
'| dd of=%s bs=1 count=%d seek=%d conv=notrunc' %
(ro_test, int(region['size']),
int(region['offset']) - wpro_offset, ro_test,
int(region['size']),
int(region['offset']) - wpro_offset))
if not modified:
raise error.TestFail('Could not find RO_FRID in %s' %
target.upper())
# Writing WP_RO section is expected to fail.
self.run_cmd('flashrom -p %s -w -i WP_RO:%s' %
(self._flashrom_targets[target], ro_test),
'FAIL')
self.run_cmd('flashrom -p %s -r -i WP_RO:%s' %
(self._flashrom_targets[target], ro_after),
'SUCCESS')
self.switcher.mode_aware_reboot(reboot_type='cold')
# The WP_RO section on the DUT should not change.
cmp_output = self.run_cmd('cmp %s %s' % (ro_before, ro_after))
if ''.join(cmp_output) != '':
raise error.TestFail('%s RO changes when WP is on!' %
target.upper())
# Disable WP
for target in self._targets:
self._set_write_protect(target, False)
# Check if RO FW can be overwritten when WP is disabled.
for target in self._targets:
ro_after = os.path.join(work_path, '%s_ro_after.bin' % target)
ro_test = os.path.join(work_path, '%s_ro_test.bin' % target)
# Writing WP_RO section is expected to succeed.
self.run_cmd('flashrom -p %s -w -i WP_RO:%s' %
(self._flashrom_targets[target], ro_test),
'SUCCESS')
self.run_cmd('flashrom -p %s -r -i WP_RO:%s' %
(self._flashrom_targets[target], ro_after),
'SUCCESS')
# The DUT's WP_RO section should be the same as the test firmware.
cmp_output = self.run_cmd('cmp %s %s' % (ro_test, ro_after))
if ''.join(cmp_output) != '':
raise error.TestFail('%s RO is not flashed correctly'
'when WP is off!' % target.upper())