blob: 066b9cf968d65fb55297032d2107b00a1546dcf4 [file] [log] [blame]
# Copyright 2017 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 logging
import os
import re
import time
from autotest_lib.client.common_lib import error
from autotest_lib.server import test
import parse
GUADO_GPIO = 218 # For Front-Left USB port
POWER_RECYCLE_WAIT_TIME = 1 # sec
class enterprise_CFM_HuddlyUpdater(test.test):
"""Tests the firmware updatability of HuddlyGo camera.
An event to trigger the firmware update is to power recycle of a USB port
which the HuddlyGo camera is attached to. The power recycle emulates
the power recycle of the ChromeBox or a reconnection of the peripheral
to the ChromeBox.
The test scenario involves the power recycling of a specific USB port
of the Guado ChromeBox: Front-left one. This imposes a restriction in the
testbed setup. This limitation is to be alleviated after the development
of full-fledged usb power recycle code. TODO(frankhu).
"""
version = 1
_failed_test_list = []
UPDATER_WAIT_TIME = 60 # sec
FIRMWARE_PKG_ORG = 'huddly'
FIRMWARE_PKG_TO_TEST = 'huddly052'
FIRMWARE_PKG_BACKUP = 'huddly.backup'
DUT_FIRMWARE_BASE = '/lib/firmware/'
DUT_FIRMWARE_SRC = os.path.join(DUT_FIRMWARE_BASE, FIRMWARE_PKG_ORG)
DUT_FIRMWARE_SRC_BACKUP = os.path.join(DUT_FIRMWARE_BASE,
FIRMWARE_PKG_BACKUP)
DUT_FIRMWARE_SRC_TEST = os.path.join(DUT_FIRMWARE_BASE,
FIRMWARE_PKG_TO_TEST)
def initialize(self):
"""initialize is a stub function."""
# Placeholder.
pass
def ls(self):
"""ls tracks the directories of interest."""
cmd = 'ls -l /lib/firmware/ | grep huddly'
result = self._shcmd(cmd)
def cleanup(self):
"""Bring the originally bundled firmware package back."""
cmd = '[ -f {} ] && rm -rf {}'.format(self.DUT_FIRMWARE_SRC,
self.DUT_FIRMWARE_SRC)
self._shcmd(cmd)
cmd = 'mv {} {} && rm -rf {}'.format(self.DUT_FIRMWARE_SRC_BACKUP,
self.DUT_FIRMWARE_SRC,
self.DUT_FIRMWARE_SRC_TEST)
self._shcmd(cmd)
def _shcmd(self, cmd):
"""A simple wrapper for remote shell command execution."""
logging.info('CMD: [%s]', cmd)
result = self._client.run(cmd)
# result is an object with following attributes:
# ['__class__', '__delattr__', '__dict__', '__doc__', '__eq__',
# '__format__', '__getattribute__', '__hash__', '__init__',
# '__module__', '__new__', '__reduce__', '__reduce_ex__',
# '__repr__', '__setattr__', '__sizeof__', '__str__',
# '__subclasshook__', '__weakref__', 'command', 'duration',
# 'exit_status', 'stderr', 'stdout']
try:
result = self._client.run(cmd)
except:
pass
if result.stderr:
logging.info('CMD ERR:\n' + result.stderr)
logging.info('CMD OUT:\n' + result.stdout)
return result
def copy_firmware(self):
"""Copy test firmware package from server to the DUT."""
current_dir = os.path.dirname(os.path.realpath(__file__))
src_firmware_path = os.path.join(current_dir, self.FIRMWARE_PKG_TO_TEST)
dst_firmware_path = self.DUT_FIRMWARE_BASE
msg = 'copy firmware from {} to {}'.format(src_firmware_path,
dst_firmware_path)
logging.info(msg)
self._client.send_file(
src_firmware_path, dst_firmware_path, delete_dest=True)
def update_firmware(self, firmware_pkg):
"""Update the peripheral's firmware with the specified package.
@param firmware_pkg: A string of package name specified by the leaf
directory name in /lib/firmware/. See class constants
DUT_FIRMWARE_SRC*.
"""
# Set up the firmware package to test with
firmware_path = os.path.join(self.DUT_FIRMWARE_BASE, firmware_pkg)
cmd = 'ln -sfn {} {}'.format(firmware_path, self.DUT_FIRMWARE_SRC)
self._shcmd(cmd)
ver_dic = self.get_fw_vers()
had = ver_dic.get('peripheral', {}).get('app', '')
want = ver_dic.get('package', {}).get('app', '')
msg = 'Update plan: from {} to {} with package: {}'.format(
had, want, firmware_pkg)
logging.info(msg)
logging.info('Recycle the power to the USB port '
'to which HuddlyGo is attached.')
self.usb_power_recycle()
time.sleep(self.UPDATER_WAIT_TIME)
got = self.get_fw_vers().get('peripheral', {}).get('app', '')
msg = 'Update result: had {} want {} got {}'.format(
had, want, got)
logging.info(msg)
if want != got:
self._failed_test_list.append(
'update_firmware({})'.format(firmware_pkg))
def run_once(self, host=None):
"""Update two times. First with test package, second with the original.
Test scenario:
1. Copy test firmware from the server to the DUT.
2. Update with the test package. Wait about 50 sec till completion.
Confirm if the peripheral is updated with the test version.
3. Update with the original package. Wait about 50 sec.
Confirm if the peripheral is updated with the original version.
"""
self._client = host
if not self.is_filesystem_readwrite():
# Make the file system read-writable, reboot, and continue the test
logging.info('DUT root file system is not read-writable. '
'Converting it read-wriable...')
self.convert_rootfs_writable()
else:
logging.info('DUT is read-writable')
try:
self.ls()
cmd = 'mv {} {}'.format(self.DUT_FIRMWARE_SRC,
self.DUT_FIRMWARE_SRC_BACKUP)
self._shcmd(cmd)
self.ls()
self.copy_firmware()
self.ls()
self.update_firmware(self.FIRMWARE_PKG_TO_TEST)
self.ls()
self.update_firmware(self.FIRMWARE_PKG_BACKUP)
if self._failed_test_list:
msg = 'Test failed in {}'.format(
', '.join(map(str, self._failed_test_list)))
raise error.TestFail(msg)
except:
pass
finally:
self.cleanup()
def convert_rootfs_writable(self):
"""Remove rootfs verification on DUT, reboot,
and remount the filesystem read-writable"""
logging.info('Disabling rootfs verification...')
self.remove_rootfs_verification()
logging.info('Rebooting...')
self.reboot()
logging.info('Remounting..')
cmd = 'mount -o remount,rw /'
self._shcmd(cmd)
def remove_rootfs_verification(self):
"""Remove rootfs verification."""
# 2 & 4 are default partitions, and the system boots from one of them.
# Code from chromite/scripts/deploy_chrome.py
KERNEL_A_PARTITION = 2
KERNEL_B_PARTITION = 4
cmd_template = ('/usr/share/vboot/bin/make_dev_ssd.sh --partitions %d '
'--remove_rootfs_verification --force')
for partition in (KERNEL_A_PARTITION, KERNEL_B_PARTITION):
cmd = cmd_template % partition
self._client.run(cmd)
def reboot(self):
"""Reboots the DUT."""
self._client.reboot()
def get_fw_vers(self):
"""Queries the firmware versions.
Utilizes the output of the command 'huddly-updater --info'.
It queries and parses the firmware versions of app and bootloader of
firmware package and the peripheral's running firmwares, respectively.
@returns a dictionary hierachically storing the firmware versions.
"""
# TODO(porce): The updater's output is to stdout, but Auto test
# command output comes to stderr. Investigate.
cmd = 'huddly-updater --info --log_to=stdout'
result = self._shcmd(cmd).stderr
ver_dic = parse.parse_fw_vers(result)
return ver_dic
def usb_power_recycle(self):
"""Recycle the power to a USB port.
# TODO(frankhu): This code supports Guado, at a specific test
# configuration. Develop an independent tool to perform this task
# with minimal dependency.
"""
try:
# Ignorant handling of GPIO export.
cmd = 'echo {} > /sys/class/gpio/export'.format(GUADO_GPIO)
self._shcmd(cmd)
except error.AutoservRunError:
pass
cmd = 'echo out > /sys/class/gpio/gpio{}/direction'.format(GUADO_GPIO)
self._shcmd(cmd)
cmd = 'echo 0 > /sys/class/gpio/gpio{}/value'.format(GUADO_GPIO)
self._shcmd(cmd)
# Wait for 1 second to avoid too fast removal and reconnection.
time.sleep(POWER_RECYCLE_WAIT_TIME)
cmd = 'echo 1 > /sys/class/gpio/gpio{}/value'.format(GUADO_GPIO)
self._shcmd(cmd)
def is_filesystem_readwrite(self):
"""Check if the root file system is read-writable.
Query the DUT's filesystem /dev/root, often manifested as /dev/dm-0
or is mounted as read-only or not.
@returns True if the /dev/root is read-writable. False otherwise.
"""
cmd = 'cat /proc/mounts | grep "/dev/root"'
result = self._shcmd(cmd).stdout
fields = re.split(' |,', result)
return True if fields.__len__() >= 4 and fields[3] == 'rw' else False