263 lines
		
	
	
		
			8.9 KiB
		
	
	
	
		
			Python
		
	
	
	
			
		
		
	
	
			263 lines
		
	
	
		
			8.9 KiB
		
	
	
	
		
			Python
		
	
	
	
| # Copyright (c) 2015 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 collections
 | |
| import glob
 | |
| import logging
 | |
| import re
 | |
| import time
 | |
| 
 | |
| from autotest_lib.client.bin import test
 | |
| from autotest_lib.client.common_lib import error, utils
 | |
| from autotest_lib.client.cros import ec as cros_ec, cros_logging
 | |
| 
 | |
| 
 | |
| class usbpd_GFU(test.test):
 | |
|     """Integration test for USB-PD Google Firmware Update (GFU).
 | |
| 
 | |
|     Test should:
 | |
|     - interrogate what firmware's are available for each device and for each:
 | |
|       1. Use ectool's flashpd to write RW with that to mimic old hw
 | |
|          - Validate that kernel driver successfully updates to latest RW.
 | |
|       2. Erase RW and see update as well.
 | |
| 
 | |
|     TODO:
 | |
|       3. Check that update is checked after S2R.
 | |
|     """
 | |
| 
 | |
|     version = 1
 | |
| 
 | |
|     FW_PATH = '/lib/firmware/cros-pd'
 | |
|     # <device>_v<major>.<minor>.<build>-<commit SHA>
 | |
|     FW_NAME_RE = r'%s/(\w+)_v(\d+)\.(\d+)\.(\d+)-([0-9a-f]+).*' % (FW_PATH)
 | |
|     GOOGLE_VID = '0x18d1'
 | |
|     MAX_UPDATE_SECS = 80
 | |
|     FW_UP_DNAME = 'cros_ec_pd_update'
 | |
|     # TODO(tbroch) This will be change once cros_ec_pd_update is abstracted from
 | |
|     # ACPI driver.  Will need to fix this once it happens.
 | |
|     FW_UP_DISABLE_PATH = '/sys/devices/LNXSYSTM:00/device:00/PNP0A08:00/device:1e/PNP0C09:00/GOOG0003:00/disable'
 | |
| 
 | |
|     # TODO(tbroch) find better way to build this or we'll have to edit test for
 | |
|     # each new PD peripheral.
 | |
|     DEV_MAJOR = dict(zinger=1, minimuffin=2, dingdong=3, hoho=4)
 | |
| 
 | |
|     def _index_firmware_avail(self):
 | |
|         """Index the various USB-PD firmwares in the rootfs.
 | |
| 
 | |
|         TODO(crosbug.com/434522) This method will need reworked after we've come
 | |
|         up with a better method for firmware release.
 | |
| 
 | |
|         @returns: dictionary of firmwares (key == name, value == list of
 | |
|           firmware paths)
 | |
|         """
 | |
|         fw_dict = collections.defaultdict(list)
 | |
|         for fw in glob.glob('%s/*_v[1-9].*.bin' % (self.FW_PATH)):
 | |
|             mat = re.match(self.FW_NAME_RE, fw)
 | |
|             if not mat:
 | |
|                 continue
 | |
| 
 | |
|             name = mat.group(1)
 | |
|             fw_dict[name].append(fw)
 | |
| 
 | |
|         return fw_dict
 | |
| 
 | |
|     def _is_gfu(self, port):
 | |
|         """Is it in GFU?
 | |
| 
 | |
|         @param port: EC_USBPD object for port.
 | |
| 
 | |
|         @returns: True if GFU enterd, False otherwise.
 | |
|         """
 | |
|         return port.is_amode_supported(self.GOOGLE_VID)
 | |
| 
 | |
|     def _is_in_rw(self, port):
 | |
|         """Is PD device in RW firmware?
 | |
| 
 | |
|         @param port: EC_USBPD object for port.
 | |
| 
 | |
|         @returns: True if in RW, False otherwise.
 | |
|         """
 | |
|         flash_info = port.get_flash_info()
 | |
|         logging.debug('flash_info = %s', flash_info)
 | |
|         return flash_info['image_status'] == 'RW'
 | |
| 
 | |
|     def _set_kernel_fw_update(self, disable=0):
 | |
|         """Disable the FW update driver.
 | |
| 
 | |
|         @param disable: 1 for disable, 0 for enable.
 | |
|         """
 | |
|         utils.write_one_line(self.FW_UP_DISABLE_PATH, disable)
 | |
|         if not disable:
 | |
|             # Allow kernel driver time quiesce
 | |
|             time.sleep(2)
 | |
| 
 | |
|     def _modify_rw(self, port, rw=None, tries=3):
 | |
|         """Modify RW of USB-PD device in <port>.
 | |
| 
 | |
|         @param port: EC_USBPD object for port.
 | |
|         @param rw: Path to RW FW to write using ectool.  If None then uses
 | |
|           /dev/null to invalidate the RW.
 | |
|         @param tries: Number of tries to update RW via flashpd
 | |
| 
 | |
|         @returns: True if success, False otherwise.
 | |
|         """
 | |
|         timeout = self.MAX_UPDATE_SECS
 | |
| 
 | |
|         if not rw:
 | |
|             rw = '/dev/null'
 | |
|             tries = 1
 | |
| 
 | |
|         self._set_kernel_fw_update(disable=1)
 | |
| 
 | |
|         while (tries):
 | |
|             try:
 | |
|                 # Note in flashpd <dev_major> <port> <file> the dev_major is
 | |
|                 # unnecessary in all cases so its just been set to 0
 | |
|                 port.ec_command('flashpd 0 %d %s' % (port.index, rw),
 | |
|                                 ignore_status=True, timeout=timeout)
 | |
| 
 | |
|             except error.CmdTimeoutError:
 | |
|                 # TODO(tbroch) could remove try/except if ec_command used run
 | |
|                 # instead of system_output + ignore_timeout=True
 | |
|                 tries -= 1
 | |
|                 continue
 | |
| 
 | |
|             if rw != '/dev/null' and not self._is_in_rw(port):
 | |
|                 logging.warn('Port%d: not in RW after flashpd ... retrying',
 | |
|                              port.index)
 | |
|                 tries -= 1
 | |
|             else:
 | |
|                 break
 | |
| 
 | |
|         self._set_kernel_fw_update()
 | |
| 
 | |
|         msg = self._reader.get_last_msg([r'%s.*is in RO' % port.index,
 | |
|                                          self.FW_UP_DNAME],
 | |
|                                         retries=5, sleep_seconds=2)
 | |
|         if not msg:
 | |
|             logging.warn('Port%d: Driver does NOT see dev in not in RO',
 | |
|                          port.index)
 | |
|             return False
 | |
|         logging.info('Port%d: Driver sees device in RO', port.index)
 | |
|         return True
 | |
| 
 | |
|     def _test_update(self, port, rw=None, tries=3):
 | |
|         """Test RW update.
 | |
| 
 | |
|         Method tests the kernel's RW update process by first modifying the
 | |
|         existing RW (either invalidating or rolling it back) via ectool.  It
 | |
|         then querys the syslog to validate kernel sees the need for update and
 | |
|         is successful.
 | |
| 
 | |
|         @param port: EC_USBPD object for port.
 | |
|         @param rw: path to RW firmware to write via ectool to test upgrade.
 | |
|         @param tries: integer number of attempts to write RW.  Necessary as
 | |
|           update is not robust (design decision).
 | |
|         """
 | |
|         if not tries:
 | |
|             raise error.TestError('Retries must be > 0')
 | |
| 
 | |
|         if not self._is_in_rw(port):
 | |
|             raise error.TestError('Port%d: Device is not in RW' % port.index)
 | |
| 
 | |
|         fw_up_re = r'%s.*Port%d FW update completed' % (self.FW_UP_DNAME,
 | |
|                                                         port.index)
 | |
| 
 | |
|         while tries:
 | |
|             self._reader.set_start_by_current()
 | |
|             rsp = self._modify_rw(port, rw)
 | |
| 
 | |
|             if not rsp:
 | |
|                 rsp_str = 'Port%d: RW modified with RW=%s failed' % \
 | |
|                           (port.index, rw)
 | |
|                 if tries:
 | |
|                     logging.warn('%s ... retrying.', rsp_str)
 | |
|                     tries -= 1
 | |
|                 else:
 | |
|                     raise error.TestError(rsp_str)
 | |
| 
 | |
|             self._reader.set_start_by_current()
 | |
|             msg = self._reader.get_last_msg([fw_up_re],
 | |
|                                             retries=(self.MAX_UPDATE_SECS / 2),
 | |
|                                             sleep_seconds=2)
 | |
| 
 | |
|             if not msg:
 | |
|                 rsp_str = 'Port%d: driver did NOT update FW' % port.index
 | |
|                 if tries:
 | |
|                     logging.warn('%s ... retrying.', rsp_str)
 | |
|                     tries -= 1
 | |
|                     continue
 | |
|                 else:
 | |
|                     raise error.TestError(rsp_str)
 | |
| 
 | |
|             logging.info('Port%d: Driver completed RW update', port.index)
 | |
| 
 | |
|             # Allow adequate reboot time after RW write completes and device is
 | |
|             # rebooted.
 | |
|             time.sleep(3)
 | |
| 
 | |
|             if not self._is_in_rw(port):
 | |
|                 rsp_str = 'Port%d: Device is not in RW' % port.index
 | |
|                 if tries:
 | |
|                     logging.warn('%s ... retrying.', rsp_str)
 | |
|                     tries -= 1
 | |
|                     continue
 | |
|                 else:
 | |
|                     raise error.TestError(rsp_str)
 | |
| 
 | |
|             break # success #
 | |
| 
 | |
|     def _test_rw_rollback(self, port, fw_dict):
 | |
|         """Test rolling back RW firmware.
 | |
| 
 | |
|         @param port: EC_USBPD object for port.
 | |
|         @param fw_dict: dictionary of firmwares.
 | |
|         """
 | |
|         self._set_kernel_fw_update()
 | |
| 
 | |
|         # test old RW update
 | |
|         flash_info = port.get_flash_info()
 | |
|         for dev_name in fw_dict.keys():
 | |
|             if flash_info['dev_major'] == self.DEV_MAJOR[dev_name]:
 | |
|                 for old_rw in sorted(fw_dict[dev_name], reverse=True)[1:]:
 | |
|                     logging.info('Port%d: Rollback test %s to %s',
 | |
|                                  port.index, dev_name, old_rw)
 | |
|                     self._test_update(port, rw=old_rw)
 | |
|                 break
 | |
| 
 | |
|     def _test_ro_only(self, port, ro_reps):
 | |
|         """Test FW update on device with RO only.
 | |
| 
 | |
|         @param port: EC_USBPD object for port.
 | |
|         @param ro_reps: Number of times to repeat test.
 | |
|         """
 | |
|         # test update in RO ro_reps times
 | |
|         for i in xrange(ro_reps):
 | |
|             logging.info('RO Loop%d', i)
 | |
|             self._test_update(port)
 | |
| 
 | |
|     def run_once(self, ro_reps=1):
 | |
| 
 | |
|         fw_dict = self._index_firmware_avail()
 | |
| 
 | |
|         self._usbpd = cros_ec.EC_USBPD()
 | |
|         self._reader = cros_logging.LogReader()
 | |
| 
 | |
|         for port in self._usbpd.ports:
 | |
|             if not port.is_dfp():
 | |
|                 continue
 | |
| 
 | |
|             logging.info('Port%d: is a DFP', port.index)
 | |
| 
 | |
|             if not self._is_gfu(port):
 | |
|                 continue
 | |
| 
 | |
|             logging.info('Port%d: supports GFU', port.index)
 | |
| 
 | |
|             self._test_rw_rollback(port, fw_dict)
 | |
|             self._test_ro_only(port, ro_reps)
 | |
| 
 | |
|     def cleanup(self):
 | |
|         self._set_kernel_fw_update()
 |