1# Copyright (c) 2017 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"""Autotest for Logitech PTZPro firmware updater functionality and udev rule.""" 5 6from __future__ import print_function 7import logging 8import os 9import re 10import time 11 12from autotest_lib.client.common_lib.cros import power_cycle_usb_util 13 14from autotest_lib.client.common_lib import error 15from autotest_lib.server import test 16 17POWER_CYCLE_WAIT_TIME = 1 # seconds 18UPDATER_WAIT_TIME = 100 # seconds 19 20 21class enterprise_CFM_LogitechPtzUpdater(test.test): 22 """Logitech Ptz Pro firmware updater functionality test in Chrome Box. 23 24 The procedure of the test is: 25 1. setup old firmware as dufault firmware on DUT 26 2. flash old version FW to device, 27 3. setup new firmware as default firmware on DUT 28 4. power cycle usb port to simulate unplug and replug of device, which 29 should be able to trigger udev rule and run the updater, 30 5. wait for the updater to finish, 31 6. run fw updater again and verify that the FW in device is consistent 32 with latest FW within system by checking the output. 33 """ 34 35 version = 1 36 37 _LOG_FILE_PATH = '/tmp/logitech-updater.log' 38 _FW_PATH_BASE = '/lib/firmware/logitech' 39 _FW_PKG_ORIGIN = 'ptzpro2' 40 _FW_PKG_BACKUP = 'ptzpro2_backup' 41 _FW_PKG_TEST = 'ptzpro2_154' 42 _FW_PATH_ORIGIN = os.path.join(_FW_PATH_BASE, _FW_PKG_ORIGIN) 43 _FW_PATH_BACKUP = os.path.join(_FW_PATH_BASE, _FW_PKG_BACKUP) 44 _FW_PATH_TEST = os.path.join(_FW_PATH_BASE, _FW_PKG_TEST) 45 _DUT_BOARD = 'guado' 46 _SIS_VID = '046d' 47 _SIS_PID = '085f' 48 49 def initialize(self, host): 50 self.host = host 51 self.log_file = self._LOG_FILE_PATH 52 self.board = self._DUT_BOARD 53 self.vid = self._SIS_VID 54 self.pid = self._SIS_PID 55 # Open log file object. 56 self.log_file_obj = open(self.log_file, 'w') 57 58 def cleanup(self): 59 self.log_file_obj.close() 60 test.test.cleanup(self) 61 62 # Delete test firmware package. 63 cmd = 'rm -rf {}'.format(self._FW_PATH_TEST) 64 self._run_cmd(cmd) 65 66 # Delete the symlink created. 67 cmd = 'rm {}'.format(self._FW_PATH_ORIGIN) 68 self._run_cmd(cmd) 69 70 # Move the backup package back. 71 cmd = 'mv {} {}'.format(self._FW_PATH_BACKUP, self._FW_PATH_ORIGIN) 72 self._run_cmd(cmd) 73 74 def _run_cmd(self, command, str_compare='', print_output=False): 75 """Run command line on DUT. 76 77 Run commands on DUT. Wait for command to complete, then check 78 the output for expected string. 79 80 @param command: command line to run in dut. 81 @param str_compare: a piece of string we want to see in the 82 output of running the command. 83 @param print_output: if true, print command output in log. 84 85 @returns the command output and a bool value. If str_compare is 86 in command output, return true. Otherwise return false. 87 88 """ 89 90 logging.info('Execute: %s', command) 91 result = self.host.run(command, ignore_status=True) 92 if result.stderr: 93 output = result.stderr 94 else: 95 output = result.stdout 96 if print_output: 97 logging.info('Output: %s', output.split('\n')) 98 if str_compare and str_compare not in ''.join(output): 99 return output, False 100 else: 101 return output, True 102 103 def convert_rootfs_writable(self): 104 """Remove rootfs verification on DUT, reboot, 105 and remount the filesystem read-writable""" 106 107 logging.info('Disabling rootfs verification...') 108 self.remove_rootfs_verification() 109 110 logging.info('Rebooting...') 111 self.reboot() 112 113 logging.info('Remounting..') 114 cmd = 'mount -o remount,rw /' 115 self._run_cmd(cmd) 116 117 def remove_rootfs_verification(self): 118 """Remove rootfs verification.""" 119 120 # 2 & 4 are default partitions, and the system boots from one of them. 121 # Code from chromite/scripts/deploy_chrome.py 122 KERNEL_A_PARTITION = 2 123 KERNEL_B_PARTITION = 4 124 125 cmd_template = ('/usr/share/vboot/bin/make_dev_ssd.sh --partitions %d ' 126 '--remove_rootfs_verification --force') 127 for partition in (KERNEL_A_PARTITION, KERNEL_B_PARTITION): 128 cmd = cmd_template % partition 129 self._run_cmd(cmd) 130 131 def reboot(self): 132 """Reboots the DUT.""" 133 134 self.host.reboot() 135 136 def is_filesystem_readwrite(self): 137 """Check if the root file system is read-writable. 138 139 Query the DUT's filesystem /dev/root, often manifested as 140 /dev/dm-0 or is mounted as read-only or not. 141 142 @returns True if the /dev/root is read-writable. False otherwise. 143 """ 144 145 cmd = 'cat /proc/mounts | grep "/dev/root"' 146 result, _ = self._run_cmd(cmd) 147 fields = re.split(' |,', result) 148 return True if fields.__len__() >= 4 and fields[3] == 'rw' else False 149 150 def copy_firmware(self): 151 """Copy test firmware from server to DUT.""" 152 153 current_dir = os.path.dirname(os.path.realpath(__file__)) 154 src_firmware_path = os.path.join(current_dir, self._FW_PKG_TEST) 155 dst_firmware_path = self._FW_PATH_BASE 156 logging.info('Copy firmware from {} to {}.'.format(src_firmware_path, 157 dst_firmware_path)) 158 self.host.send_file(src_firmware_path, dst_firmware_path, delete_dest=True) 159 160 def triger_updater(self): 161 """Triger udev rule to run fw updater by power cycling the usb.""" 162 163 try: 164 power_cycle_usb_util.power_cycle_usb_vidpid(self.host, self.board, 165 self.vid, self.pid) 166 except KeyError: 167 raise error.TestFail('Counld\'t find target device: ' 168 'vid:pid {}:{}'.format(self.vid, self.pid)) 169 170 def setup_fw(self, firmware_package): 171 """Setup firmware package that is going to be used for updating.""" 172 173 firmware_path = os.path.join(self._FW_PATH_BASE, firmware_package) 174 cmd = 'ln -sfn {} {}'.format(firmware_path, self._FW_PATH_ORIGIN) 175 self._run_cmd(cmd) 176 177 def flash_fw(self, str_compare='', print_output=False, force=False): 178 """Flash certain firmware to device. 179 180 Run logitech firmware updater on DUT to flash the firmware setuped 181 to target device (PTZ Pro 2). 182 183 @param force: run with force update, will bypass fw version check. 184 @param str_compare, print_output: the same as function _run_cmd. 185 186 """ 187 188 if force: 189 cmd_run_updater = ('/usr/sbin/logitech-updater' 190 ' --log_to=stdout --update --force') 191 else: 192 cmd_run_updater = ('/usr/sbin/logitech-updater --log_to=stdout --update') 193 output, succeed = self._run_cmd( 194 cmd_run_updater, str_compare=str_compare, print_output=print_output) 195 return output, succeed 196 197 def run_once(self): 198 """Main test procedure.""" 199 200 # Make the DUT filesystem writable. 201 if not self.is_filesystem_readwrite(): 202 logging.info('DUT root file system is not read-writable. ' 203 'Converting it read-writable...') 204 self.convert_rootfs_writable() 205 else: 206 logging.info('DUT is read-writable.') 207 208 # Copy old FW to device. 209 cmd = 'mv {} {}'.format(self._FW_PATH_ORIGIN, self._FW_PATH_BACKUP) 210 self._run_cmd(cmd) 211 self.copy_firmware() 212 213 # Flash old FW to device. 214 self.setup_fw(self._FW_PKG_TEST) 215 expect_output = 'Done. Updated firmwares successfully.' 216 output, succeed = self.flash_fw(str_compare=expect_output, force=True) 217 self.log_file_obj.write('{}Log info for writing ' 218 'old firmware{}\n'.format('-' * 8, '-' * 8)) 219 self.log_file_obj.write(output) 220 if not succeed: 221 raise error.TestFail('Expect \'{}\' in output, ' 222 'but didn\'t find it.'.format(expect_output)) 223 224 # Triger udev to run FW updater. 225 self.setup_fw(self._FW_PKG_BACKUP) 226 self.triger_updater() 227 228 # Wait for fw updater to finish. 229 time.sleep(UPDATER_WAIT_TIME) 230 231 # Try flash the new firmware, should detect same fw version. 232 expect_output = 'Firmware is up to date.' 233 output, succeed = self.flash_fw(str_compare=expect_output) 234 self.log_file_obj.write('{}Log info for writing ' 235 'new firmware{}\n'.format('-' * 8, '-' * 8)) 236 self.log_file_obj.write(output) 237 if not succeed: 238 raise error.TestFail('Expect {} in output ' 239 'but didn\'t find it.'.format(expect_output)) 240