1# Copyright (c) 2018 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 Aver VC520/CAM520 camera firmware updater.""" 5 6import logging 7import os 8import re 9import time 10 11 12from autotest_lib.client.common_lib import error 13from autotest_lib.client.common_lib.cros import power_cycle_usb_util 14from autotest_lib.client.common_lib.cros.cfm.usb import usb_device_collector 15from autotest_lib.server import test 16from autotest_lib.server.cros import filesystem_util 17 18 19FW_PATH_BASE = '/lib/firmware' 20FW_PKG_ORIGIN = 'aver' 21FW_PKG_BACKUP = 'aver_backup' 22FW_PKG_TEST = 'aver_520_18.02' 23LOG_FILE = '/tmp/aver-updater.log' 24POWER_CYCLE_WAIT_TIME_SEC = 240 25 26 27class enterprise_CFM_Aver520Updater(test.test): 28 """ 29 Aver camera firmware test on Chrome For Meeting devices 30 This test works for both Aver VC520 and CAM520. 31 The test follows the following steps 32 1) Check if the filesystem is writable 33 If not make the filesystem writable and reboot 34 2) Backup the existing firmware file on DUT 35 3) Copy the older firmware files to DUT 36 4) Force update older firmware on Aver Camera 37 5) Restore the original firmware files on DUT 38 4) Power cycle usb port to simulate unplug/replug of device which 39 should initiate a firmware update 40 5) Wait for firmware update to finish and check firmware version 41 6) Cleanup 42 43 """ 44 45 version = 1 46 47 def initialize(self, host, camera): 48 """ 49 Initializes the class. 50 51 Stores the firmware file path. 52 Gets the board type. 53 Reads the current firmware versions. 54 """ 55 56 self.host = host 57 self.camera = camera 58 self.fw_path_test = os.path.join(FW_PATH_BASE, 59 FW_PKG_TEST) 60 self.fw_path_origin = os.path.join(FW_PATH_BASE, 61 FW_PKG_ORIGIN) 62 self.fw_path_backup = os.path.join(FW_PATH_BASE, 63 FW_PKG_BACKUP) 64 self.board = self.host.get_board().split(':')[1] 65 66 self.device_collector = usb_device_collector.UsbDeviceCollector( 67 self.host) 68 69 self.vid_pid = self.camera.vid_pid 70 self.usb_spec = self.camera.get_usb_device_spec(self.vid_pid) 71 72 self.org_fw_ver = self.get_image_fw_ver() 73 74 def cleanup(self): 75 """ 76 Cleanups after tests. 77 78 Removes the test firmware. 79 Restores the original firmware files. 80 Flashes the camera to original firmware if needed. 81 """ 82 83 # Delete test firmware package. 84 cmd = 'rm -rf {}'.format(self.fw_path_test) 85 self.host.run(cmd) 86 87 # Delete the symlink created. 88 cmd = 'rm {}'.format(self.fw_path_origin) 89 self.host.run(cmd) 90 91 # Move the backup package back. 92 cmd = 'mv {} {}'.format(self.fw_path_backup, self.fw_path_origin) 93 self.host.run(cmd) 94 95 # Do not leave the camera with test (older) firmware. 96 if not self.is_device_firmware_equal_to(self.org_fw_ver): 97 logging.debug('Aver 520 camera has old firmware after test' 98 'Flashing new firmware') 99 self.flash_fw() 100 101 super(enterprise_CFM_Aver520Updater, self).cleanup() 102 103 def _run_cmd(self, command): 104 """ 105 Runs command line on DUT, wait for completion and return the output. 106 107 @param command: command line to run in dut. 108 109 @returns the command output 110 """ 111 112 logging.debug('Execute: %s', command) 113 114 result = self.host.run(command, ignore_status=True) 115 output = result.stderr if result.stderr else result.stdout 116 logging.debug('Output: %s', output) 117 return output 118 119 def fw_ver_from_output_str(self, cmd_output): 120 """ 121 Parse firmware version of aver-updater output. 122 123 aver-updater output differs slightly for image_version and 124 device_version. 125 For image_version strip ".dat" from output 126 This function will fail if version is not in the format 127 x.x.xxxx.xx where x is in [0-9] 128 129 The actual output is given below. 130 131 aver-updater --image_version 132 [INFO:main.cc(79)] image_version: 0.0.0018.07.dat 133 134 aver-updater --device_version 135 [INFO:main.cc(71)] device_version: 0.0.0018.08 136 137 """ 138 139 logging.debug('Parsing output from updater %s', cmd_output) 140 if 'Error(2) opening /lib/firmware/aver/' in cmd_output: 141 raise error.TestFail('Aver firmware image not found on DUT') 142 143 if ('device_version' not in cmd_output and 144 'image_version' not in cmd_output): 145 raise error.TestFail('Parsing aver firmware version output failed') 146 147 version = '' 148 output = cmd_output.split('\n') 149 for line in output: 150 logging.debug('parsing line %s from output', line) 151 if 'device_version' not in line and 'image_version' not in line: 152 continue 153 parts = line.split(' ') 154 155 if parts[1] == 'device_version:': 156 version = parts[2] 157 elif parts[1] == 'image_version:': 158 version = parts[2].strip('.dat') 159 else: 160 raise error.TestFail('Unexpected output from updater %s' 161 % parts) 162 163 version = version.strip('\0') # Remove null characters 164 logging.debug('Version parsed from output is %s', version) 165 166 if not bool(re.match(r'^\d\.\d\.\d\d\d\d\.\d\d$', version)): 167 logging.debug('parsed version is %s ', version) 168 raise error.TestFail('Version %s not in' 169 'expected format' % version) 170 171 logging.debug('Version is %s', str(version)) 172 return version 173 174 def get_updater_output(self, cmd): 175 """Get updater output while avoiding transient failures.""" 176 177 NUM_RETRIES = 5 178 WAIT_TIME_SEC = 20 179 for _ in range(NUM_RETRIES): 180 output = self._run_cmd(cmd) 181 if ('Open hid fd fail' in output or 182 'query data size fail' in output or 183 'There is another aver-updater running.' in output or 184 'Failed to open the device' in output): 185 time.sleep(WAIT_TIME_SEC) 186 continue 187 return output 188 189 def get_image_fw_ver(self): 190 """Get the version of firmware on DUT.""" 191 192 output = self.get_updater_output('aver-updater --image_version' 193 ' --log_to=stdout --lock') 194 return self.fw_ver_from_output_str(output) 195 196 def get_device_fw_ver(self): 197 """Get the version of firmware on Aver 520 camera.""" 198 199 output = self.get_updater_output('aver-updater --device_version' 200 ' --log_to=stdout --lock') 201 return self.fw_ver_from_output_str(output) 202 203 def copy_test_firmware(self): 204 """Copy test firmware from server to DUT.""" 205 206 current_dir = os.path.dirname(os.path.realpath(__file__)) 207 src_firmware_path = os.path.join(current_dir, FW_PKG_TEST) 208 dst_firmware_path = FW_PATH_BASE 209 logging.info('Copy firmware from (%s) to (%s).', src_firmware_path, 210 dst_firmware_path) 211 self.host.send_file(src_firmware_path, dst_firmware_path, 212 delete_dest=True) 213 214 def trigger_updater(self): 215 """Trigger udev rule to run fw updater by power cycling the usb.""" 216 217 try: 218 vid = self.camera.vendor_id 219 pid = self.camera.product_id 220 power_cycle_usb_util.power_cycle_usb_vidpid(self.host, self.board, 221 vid, pid) 222 except KeyError: 223 raise error.TestFail('Could not find target device: ' 224 '{}'.format(self.camera)) 225 226 def wait_for_aver_camera(self, wait_time=30): 227 """ 228 Wait for Aver 520 camera to be enumerated. 229 230 Check if a device with given (vid,pid) is present. 231 Timeout after wait_time seconds. Default 30 seconds 232 """ 233 234 TIME_SLEEP = 10 235 NUM_ITERATIONS = max(wait_time / TIME_SLEEP, 1) 236 237 logging.debug('Waiting for Aver 520 camera') 238 for _ in range(NUM_ITERATIONS): 239 if self.device_collector.get_devices_by_spec(self.usb_spec): 240 logging.debug('Aver 520 camera detected') 241 return 242 else: 243 logging.debug('Aver 520 camera not detected.' 244 'Waiting for (%s) seconds', TIME_SLEEP) 245 time.sleep(TIME_SLEEP) 246 247 logging.error('Unable to detect the device after (%s) seconds.' 248 'Timing out...\n Target device %s not detected', 249 wait_time, self.camera) 250 raise error.TestFail('Target device not detected') 251 252 def setup_fw(self, firmware_package): 253 """Setup firmware package that is going to be used for updating.""" 254 255 firmware_path = os.path.join(FW_PATH_BASE, firmware_package) 256 cmd = 'ln -sfn {} {}'.format(firmware_path, self.fw_path_origin) 257 logging.debug('executing cmd %s ', cmd) 258 self.host.run(cmd) 259 260 def flash_fw(self, force=False): 261 """Flash certain firmware to device. 262 263 Run logitech firmware updater on DUT to flash the firmware setuped 264 to target device (PTZ Pro 2). 265 266 @param force: run with force update, will bypass fw version check. 267 268 """ 269 270 cmd = ('/usr/sbin/aver-updater --log_to=stdout --update' 271 ' --lock') 272 if force: 273 cmd += ' --force' 274 output = self.get_updater_output(cmd) 275 return output 276 277 def print_fw_version(self, version, info_str=''): 278 """Pretty print Aver 520 camera firmware version.""" 279 280 if info_str: 281 print info_str, 282 print ' Firmware version:', version 283 284 def is_device_firmware_equal_to(self, expected_ver): 285 """Check that the device fw version is equal to given version.""" 286 287 device_fw_version = self.get_device_fw_ver() 288 if device_fw_version != expected_ver: 289 logging.error('Device firmware version is not the expected version') 290 self.print_fw_version(device_fw_version, 'Device firmware version') 291 self.print_fw_version(expected_ver, 'Expected firmware version') 292 return False 293 294 return True 295 296 def flash_old_firmware(self): 297 """Flash old (test) version of firmware on the device.""" 298 299 # Flash old FW to device. 300 self.setup_fw(FW_PKG_TEST) 301 test_fw_ver = self.get_image_fw_ver() 302 self.print_fw_version(test_fw_ver, 'Test firmware version') 303 logging.debug('flashing test firmware on the device') 304 output = self.flash_fw(force=True) 305 time.sleep(POWER_CYCLE_WAIT_TIME_SEC) 306 with open(LOG_FILE, 'w') as f: 307 delim = '-' * 8 308 f.write('{}Log info for writing old firmware{}' 309 '\n'.format(delim, delim)) 310 f.write(output) 311 if not self.is_device_firmware_equal_to(test_fw_ver): 312 raise error.TestFail('Flashing old firmware failed') 313 logging.info('Device flashed with test firmware') 314 315 def backup_original_firmware(self): 316 """Backup existing firmware on DUT.""" 317 # Copy old FW to device. 318 cmd = 'mv {} {}'.format(self.fw_path_origin, self.fw_path_backup) 319 self.host.run(cmd) 320 321 def is_updater_running(self): 322 """Checks if the aver-updater is running.""" 323 324 cmd = 'aver-updater --lock --device_version --log_to=stdout' 325 output = self._run_cmd(cmd) 326 return 'There is another aver-updater running. Exiting now...' in output 327 328 def wait_for_updater(self): 329 """Wait aver-updater to stop or timeout after 6 minutes.""" 330 331 NUM_ITERATION = 12 332 WAIT_TIME_SEC = 30 333 logging.debug('Wait for any currently running updater to finish') 334 for _ in range(NUM_ITERATION): 335 if self.is_updater_running(): 336 logging.debug('aver-updater is running.' 337 'Waiting for %s seconds', WAIT_TIME_SEC) 338 time.sleep(WAIT_TIME_SEC) 339 else: 340 logging.debug('aver-updater not running') 341 return 342 logging.error('aver-updater is still running after 6 minutes') 343 344 def test_firmware_update(self): 345 """Trigger firmware updater and check device firmware version.""" 346 347 # Simulate hotplug to run FW updater. 348 logging.info('Setup original firmware') 349 self.setup_fw(FW_PKG_BACKUP) 350 self.print_fw_version(self.get_image_fw_ver(), 'Firmware on disk') 351 logging.info('Simulate hot plugging the device') 352 self.trigger_updater() 353 self.wait_for_aver_camera() 354 355 # The firmware check will fail if the check runs in a short window 356 # between the device being detected and the firmware updater starting. 357 # Adding a delay to reduce the chance of that scenerio. 358 logging.info('Waiting for the updater to update the firmware') 359 time.sleep(POWER_CYCLE_WAIT_TIME_SEC) 360 361 self.wait_for_updater() 362 363 if not self.is_device_firmware_equal_to(self.org_fw_ver): 364 raise error.TestFail('Camera not updated to new firmware') 365 logging.info('Firmware update was completed successfully') 366 367 def run_once(self): 368 """ 369 Entry point for test. 370 371 The following actions are performed in this test. 372 - Device is flashed with older firmware. 373 - Powercycle usb port to simulate hotplug inorder to start the updater. 374 - Check that the device is updated with newer firmware. 375 """ 376 377 # Check if updater is already running 378 logging.info('Testing firmware update on Aver %s camera', 379 self.camera) 380 logging.info('Confirm that camera is present') 381 382 self.wait_for_aver_camera(wait_time=0) 383 384 self.wait_for_updater() 385 386 self.print_fw_version(self.org_fw_ver, 387 'Original firmware version on DUT') 388 self.print_fw_version(self.get_device_fw_ver(), 389 'Firmware version on device') 390 391 filesystem_util.make_rootfs_writable(self.host) 392 self.backup_original_firmware() 393 394 # Flash test firmware version 395 self.copy_test_firmware() 396 self.flash_old_firmware() 397 398 # Test firmware update 399 self.test_firmware_update() 400 logging.info('Aver %s camera firmware updater' 401 'test was successful', self.camera) 402