Home | History | Annotate | Download | only in platform_LabFirmwareUpdate
      1 # Copyright 2016 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 
      5 import logging
      6 
      7 from autotest_lib.client.common_lib import error
      8 from autotest_lib.server import autotest
      9 from autotest_lib.server import test
     10 from autotest_lib.server.cros.faft.rpc_proxy import RPCProxy
     11 
     12 class platform_LabFirmwareUpdate(test.test):
     13     """For test or lab devices.  Test will fail if Software write protection
     14        is enabled.  Test will compare the installed firmware to those in
     15        the shellball.  If differ, execute chromeos-firmwareupdate
     16        --mode=recovery to reset RO and RW firmware. Basic procedure are:
     17 
     18        - check software write protect, if enable, attemp reset.
     19        - fail test if software write protect is enabled.
     20        - check if ec is available on DUT.
     21        - get RO, RW versions of firmware, if RO != RW, update=True
     22        - get shellball versions of firmware
     23        - compare shellball version to DUT, update=True if shellball != DUT.
     24        - run chromeos-firwmareupdate --mode=recovery if update==True
     25        - reboot
     26     """
     27     version = 1
     28 
     29     # TODO(kmshelton): Move most of the logic in this test to a unit tested
     30     # library.
     31     def initialize(self, host):
     32         self.host = host
     33         # Make sure the client library is on the device so that the proxy
     34         # code is there when we try to call it.
     35         client_at = autotest.Autotest(self.host)
     36         client_at.install()
     37         self.faft_client = RPCProxy(self.host)
     38 
     39         # Check if EC, PD is available.
     40         # Check if DUT software write protect is disabled, failed otherwise.
     41         self._run_cmd('flashrom -p host --wp-status', checkfor='is disabled')
     42         self.has_ec = False
     43         mosys_output = self._run_cmd('mosys')
     44         if 'EC information' in mosys_output:
     45             self.has_ec = True
     46             self._run_cmd('flashrom -p ec --wp-status', checkfor='is disabled')
     47 
     48     def _run_cmd(self, command, checkfor=''):
     49         """Run command on dut and return output.
     50            Optionally check output contain string 'checkfor'.
     51         """
     52         logging.info('Execute: %s', command)
     53         output = self.host.run(command, ignore_status=True).stdout
     54         logging.info('Output: %s', output.split('\n'))
     55         if checkfor and checkfor not in ''.join(output):
     56             raise error.TestFail('Expect %s in output of %s' %
     57                                  (checkfor, ' '.join(output)))
     58         return output
     59 
     60     def _get_version(self):
     61         """Retrive RO, RW EC/PD version."""
     62         ro = None
     63         rw = None
     64         lines = self._run_cmd('ectool version', checkfor='version')
     65         for line in lines.splitlines():
     66             if line.startswith('RO version:'):
     67                 parts = line.split(':')
     68                 ro = parts[1].strip()
     69             if line.startswith('RW version:'):
     70                 parts = line.split(':')
     71                 rw = parts[1].strip()
     72         return (ro, rw)
     73 
     74     def _bios_version(self):
     75         """Retrive RO, RW BIOS version."""
     76         ro = self.faft_client.system.get_crossystem_value('ro_fwid')
     77         rw = self.faft_client.system.get_crossystem_value('fwid')
     78         return (ro, rw)
     79 
     80     def _construct_fw_version(self, fw_ro, fw_rw):
     81         """Construct a firmware version string in a consistent manner.
     82 
     83         @param fw_ro: A string representing the version of a read-only
     84                       firmware.
     85         @param fw_rw: A string representing the version of a read-write
     86                       firmware.
     87 
     88         @returns a string constructed from fw_ro and fw_rw
     89 
     90         """
     91         if fw_ro == fw_rw:
     92             return fw_rw
     93         else:
     94             return '%s,%s' % (fw_ro, fw_rw)
     95 
     96     def _get_version_all(self):
     97         """Retrive BIOS, EC, and PD firmware version.
     98 
     99         @return firmware version tuple (bios, ec)
    100         """
    101         bios_version = None
    102         ec_version = None
    103         if self.has_ec:
    104             (ec_ro, ec_rw) = self._get_version()
    105             ec_version = self._construct_fw_version(ec_ro, ec_rw)
    106             logging.info('Installed EC version: %s', ec_version)
    107         (bios_ro, bios_rw) = self._bios_version()
    108         bios_version = self._construct_fw_version(bios_ro, bios_rw)
    109         logging.info('Installed BIOS version: %s', bios_version)
    110         return (bios_version, ec_version)
    111 
    112     def _get_shellball_version(self):
    113         """Get shellball firmware version.
    114 
    115         @return shellball firmware version tuple (bios, ec)
    116         """
    117         bios = None
    118         ec = None
    119         bios_ro = None
    120         bios_rw = None
    121         ec_ro = None
    122         ec_rw = None
    123         shellball = self._run_cmd('/usr/sbin/chromeos-firmwareupdate -V')
    124         # TODO(kmshelton): Add a structured output option (likely a protobuf)
    125         # to chromeos-firmwareupdate so the below can become less fragile.
    126         for line in shellball.splitlines():
    127             if line.startswith('BIOS version:'):
    128                 parts = line.split(':')
    129                 bios_ro = parts[1].strip()
    130                 logging.info('shellball ro bios %s', bios_ro)
    131             if line.startswith('BIOS (RW) version:'):
    132                 parts = line.split(':')
    133                 bios_rw = parts[1].strip()
    134                 logging.info('shellball rw bios %s', bios_rw)
    135             if line.startswith('EC version:'):
    136                 parts = line.split(':')
    137                 ec_ro = parts[1].strip()
    138                 logging.info('shellball ro ec %s', ec_ro)
    139             elif line.startswith('EC (RW) version:'):
    140                 parts = line.split(':')
    141                 ec_rw = parts[1].strip()
    142                 logging.info('shellball rw ec %s', ec_rw)
    143         # Shellballs do not always contain a RW version.
    144         if bios_rw is not None:
    145           bios = self._construct_fw_version(bios_ro, bios_rw)
    146         else:
    147           bios = bios_ro
    148         if ec_rw is not None:
    149           ec = self._construct_fw_version(ec_ro, ec_rw)
    150         else:
    151           ec = ec_ro
    152         return (bios, ec)
    153 
    154     def run_once(self, replace=True):
    155         # Get DUT installed firmware versions.
    156         (installed_bios, installed_ec) = self._get_version_all()
    157 
    158         # Get shellball firmware versions.
    159         (shball_bios, shball_ec) = self._get_shellball_version()
    160 
    161         # Figure out if update is needed.
    162         need_update = False
    163         if installed_bios != shball_bios:
    164             need_update = True
    165             logging.info('BIOS mismatch %s, will update to %s',
    166                          installed_bios, shball_bios)
    167         if installed_ec and installed_ec != shball_ec:
    168             need_update = True
    169             logging.info('EC mismatch %s, will update to %s',
    170                          installed_ec, shball_ec)
    171 
    172         # Update and reboot if needed.
    173         if need_update:
    174             output = self._run_cmd('/usr/sbin/chromeos-firmwareupdate '
    175                                    ' --mode=recovery', '(recovery) completed.')
    176             self.host.reboot()
    177             # Check that installed firmware match the shellball.
    178             (bios, ec) = self._get_version_all()
    179             # TODO(kmshelton): Refactor this test to use named tuples so that
    180             # the comparison is eaiser to grok.
    181             if (bios != shball_bios or ec != shball_ec):
    182                 logging.info('shball bios/ec: %s/%s',
    183                              shball_bios, shball_ec)
    184                 logging.info('installed bios/ec: %s/%s', bios, ec)
    185                 raise error.TestFail('Version mismatch after firmware update')
    186             logging.info('*** Done firmware updated to match shellball. ***')
    187         else:
    188             logging.info('*** No firmware update is needed. ***')
    189 
    190