• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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