Commit 4c5246a2 authored by Rémi Duraffort's avatar Rémi Duraffort
Browse files

Merge branch 'musca' into 'master'

Musca Support

See merge request lava/lava!840
parents 9d74ea21 624cec63
......@@ -739,6 +739,32 @@ bootloader in interactive tests and then booting to the OS.
method: minimal
reset: false
.. index:: boot method musca
.. _boot_method_musca:
musca
=====
The ``musca`` method is used to boot `musca devices
<https://developer.arm.com/products/system-design/development-boards/iot-test-chips-and-boards/musca-a-test-chip-board>`__.
Unlike the ``minimal`` boot, the board has to be powered on before the serial will be available
as the board is powered by the USB that provides the serial connection also.
Therefore, the board is powered on then connection to the serial is made.
Optionally, ``prompts`` can be used to check for serial output before continuing.
.. code-block:: yaml
- boot:
method: musca
.. note:: No shell is expected and no boot string is checked. All checking should be done with test monitors.
.. note:: Some initial setup steps are required to ensure that the Musca device boots when it is powered on.
Check `here <https://github.com/ARMmbed/DAPLink/blob/master/docs/MSD_COMMANDS.md>`__ for details
on how to setup the board to auto-boot when it is programmed or turned on.
Ensure ``DETAILS.TXT`` on the MSD shows "Auto Reset" and "Auto Power" are activated.
pyocd
=====
......
.. index:: deploy to musca
.. _deploy_to_musca:
to: musca
*********
This deployment method allows deployment of software to `musca devices
<https://developer.arm.com/products/system-design/development-boards/iot-test-chips-and-boards/musca-a-test-chip-board>`__.
The board is powered on and the mass storage device is mounted.
The test binary is copied to the MSD and then the MSD is unmounted.
When the board processes it, the MSD will be re-exposed to the dispatcher,
at which point this is re-mounted and LAVA will check for the presence of a ``FAIL.TXT``
file, in case of errors.
.. note:: Some initial setup steps are required to ensure that the Musca device serves it's MSD when it is powered on.
Check `here <https://github.com/ARMmbed/DAPLink/blob/master/docs/MSD_COMMANDS.md>`__ for details
on how to setup the board to auto-boot when it is programmed or turned on.
Ensure ``DETAILS.TXT`` on the MSD shows "Auto Reset" and "Auto Power" are activated.
.. note:: LAVA won't deploy firmware to the Musca board and so it must be fixed per device.
Firmware used is available `here
<https://community.arm.com/developer/tools-software/oss-platforms/w/docs/318/musca-a-firmware-update-qspi-boot-recovery>`__.
.. code-block:: yaml
- deploy:
to: musca
images:
test_binary:
url: https://community.arm.com/cfs-file/__key/communityserver-wikis-components-files/00-00-00-00-10/MuscaBlinky_5F00_v002.hex
images
======
.. index:: deploy to musca test_binary
.. _deploy_to_musca_test_binary:
test_binary
-----------
Download test binary to the Musca device.
.. _deploy_to_musca_binary_url:
url *
^^^^^
Specifies the URL to download. All downloads are check-summed using ``md5sum``
and ``sha256sum``
URLs are checked during the test job validation to ensure that the file can be
downloaded. Missing files will cause the test job to end as Incomplete.
URLs **must** use one of the supported schemes, the first element of the URL.
.. topic:: Supported schema
* ``http://``
* ``https://``
* ``file://``
* ``lxc://``
......@@ -60,6 +60,7 @@ Parameter List
.. include:: actions-deploy-to-fastboot.rsti
.. include:: actions-deploy-to-isoinstaller.rsti
.. include:: actions-deploy-to-lxc.rsti
.. include:: actions-deploy-to-musca.rsti
.. include:: actions-deploy-to-nbd.rsti
.. include:: actions-deploy-to-recovery.rsti
.. include:: actions-deploy-to-sata.rsti
......
......@@ -339,6 +339,14 @@ timeouts:
minutes: {{ action_timeout_boot_image_retry | default(2) }}
flash-uboot-ums:
minutes: {{ action_timeout_flash_uboot_ums | default(20) }}
musca-deploy:
minutes: {{ action_timeout_musca_deploy | default(3) }}
musca-boot:
minutes: {{ action_timeout_musca_boot | default(1) }}
pdu-reboot:
seconds: {{ action_timeout_pdu_reboot | default(30) }}
reset-device:
seconds: {{ action_timeout_reset_device | default(30) }}
connections:
dd-image:
minutes: {{ connection_timeout_dd_image | default(10) }}
......
{# device_type: 'musca' #}
{% set action_timeout_power_off = action_timeout_power_off | default(35) %}
{% set action_timeout_reset_device = action_timeout_reset_device | default(60) %}
{% set action_timeout_pdu_reboot = action_timeout_pdu_reboot | default(60) %}
{% set kernel_start_message = '' %}
{% extends 'base.jinja2' %}
{% block body %}
{# ID_SERIAL_SHORT #}
board_id: '{{ board_id }}'
usb_vendor_id: '{{ usb_vendor_id | default('0d28') }}'
usb_product_id: '{{ usb_product_id | default('0204') }}'
actions:
deploy:
methods:
lxc:
musca:
# ID_SERIAL value (not ID_SERIAL_SHORT)
id_serial: '{{ id_serial }}'
boot:
connections:
lxc:
serial:
methods:
lxc:
musca:
{% endblock body -%}
# -*- coding: utf-8 -*-
#
# Copyright (C) 2019 Arm Limited
#
# Author: Dean Birch <dean.birch@arm.com>
#
# This file is part of LAVA.
#
# LAVA is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# LAVA is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along
# with this program; if not, see <http://www.gnu.org/licenses>.
from voluptuous import Msg, Optional, Required
from lava_common.schemas import boot
def schema():
base = {
Required("method"): Msg("musca", "'method' should be 'musca'"),
Optional("prompts"): boot.prompts(),
}
return {**boot.schema(), **base}
# -*- coding: utf-8 -*-
#
# Copyright (C) 2019 Arm Limited
#
# Author: Dean Birch <dean.birch@arm.com>
#
# This file is part of LAVA.
#
# LAVA is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# LAVA is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along
# with this program; if not, see <http://www.gnu.org/licenses>.
from voluptuous import Required
from lava_common.schemas import deploy
def schema():
base = {
Required("to"): "musca",
Required("images"): {
Required("test_binary", "'images' has no 'test_binary' entry"): deploy.url()
},
}
return {**deploy.schema(), **base}
# Copyright (C) 2019 Arm Limited
#
# Author: Dean Birch <dean.birch@arm.com>
#
# This file is part of LAVA Dispatcher.
#
# LAVA Dispatcher is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# LAVA Dispatcher is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along
# with this program; if not, see <http://www.gnu.org/licenses>.
from lava_dispatcher.action import Pipeline
from lava_dispatcher.actions.boot import (
BootAction,
OverlayUnpack,
ExportDeviceEnvironment,
)
from lava_dispatcher.actions.boot import AutoLoginAction
from lava_dispatcher.connections.serial import ConnectDevice
from lava_dispatcher.logical import Boot
from lava_dispatcher.power import ResetDevice
from lava_dispatcher.shell import ExpectShellSession
from lava_dispatcher.utils.udev import WaitUSBSerialDeviceAction
class Musca(Boot):
compatibility = 1
def __init__(self, parent, parameters):
super().__init__(parent)
self.action = MuscaBoot()
self.action.section = self.action_type
self.action.job = self.job
parent.add_action(self.action, parameters)
@classmethod
def accepts(cls, device, parameters):
if "musca" not in device["actions"]["boot"]["methods"]:
return False, '"musca" was not in device configuration boot methods'
if "method" not in parameters:
return False, '"method" was not in parameters'
if parameters["method"] != "musca":
return False, '"method" was not "musca"'
if "board_id" not in device:
return False, '"board_id" is not in the device configuration'
return True, "accepted"
class MuscaBoot(BootAction):
name = "musca-boot"
description = "power device and trigger software to run"
summary = "power device and trigger software to run"
def populate(self, parameters):
self.pipeline = Pipeline(parent=self, job=self.job, parameters=parameters)
self.pipeline.add_action(ResetDevice())
self.pipeline.add_action(WaitUSBSerialDeviceAction())
self.pipeline.add_action(ConnectDevice())
if self.has_prompts(parameters):
self.pipeline.add_action(AutoLoginAction())
if self.test_has_shell(parameters):
self.pipeline.add_action(ExpectShellSession())
if "transfer_overlay" in parameters:
self.pipeline.add_action(OverlayUnpack())
self.pipeline.add_action(ExportDeviceEnvironment())
......@@ -36,6 +36,7 @@ from lava_dispatcher.actions.boot.ipxe import IPXE
from lava_dispatcher.actions.boot.kexec import BootKExec
from lava_dispatcher.actions.boot.lxc import BootLxc
from lava_dispatcher.actions.boot.minimal import Minimal
from lava_dispatcher.actions.boot.musca import Musca
from lava_dispatcher.actions.boot.openocd import OpenOCD
from lava_dispatcher.actions.boot.pyocd import PyOCD
from lava_dispatcher.actions.boot.jlink import JLink
......
# Copyright (C) 2019 Arm Limited
#
# Author: Dean Birch <dean.birch@arm.com>
#
# This file is part of LAVA Dispatcher.
#
# LAVA Dispatcher is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# LAVA Dispatcher is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along
# with this program; if not, see <http://www.gnu.org/licenses>.
# List just the subclasses supported for this base strategy
# imported by the parser to populate the list of subclasses.
import os
import shutil
from lava_dispatcher.action import Action, Pipeline
from lava_common.exceptions import InfrastructureError
from lava_dispatcher.actions.deploy.vemsd import MountDeviceMassStorageDevice
from lava_dispatcher.actions.deploy.vemsd import UnmountVExpressMassStorageDevice
from lava_dispatcher.logical import Deployment, RetryAction
from lava_dispatcher.actions.deploy.download import DownloaderAction
from lava_dispatcher.connections.serial import DisconnectDevice
from lava_dispatcher.power import ResetDevice
from lava_dispatcher.utils.udev import wait_udev_changed_event, wait_udev_event
class Musca(Deployment):
"""
Strategy class for a booting Arm Musca devices.
Downloads an image and deploys to the board.
"""
compatibility = 1
name = "musca"
def __init__(self, parent, parameters):
super().__init__(parent)
self.action = MuscaAction()
self.action.section = self.action_type
self.action.job = self.job
parent.add_action(self.action, parameters)
@classmethod
def accepts(cls, device, parameters):
if "musca" not in device["actions"]["deploy"]["methods"]:
return False, '"musca" was not in the device configuration deploy methods'
if "to" not in parameters:
return False, '"to" was not in parameters'
if parameters["to"] != "musca":
return False, '"to" was not "musca"'
if "board_id" not in device:
return False, '"board_id" is not in the device configuration'
return True, "accepted"
class MuscaAction(RetryAction):
"""
Action for deploying software to a Musca
"""
name = "musca-deploy"
description = "deploy image to Musca device"
summary = "Musca device image deployment"
def validate(self):
super().validate()
if "images" not in self.parameters:
self.errors = "Missing 'images'"
return
images = self.parameters["images"]
if "test_binary" not in images:
self.errors = "Missing 'test_binary'"
def populate(self, parameters):
download_dir = self.mkdtemp()
self.pipeline = Pipeline(parent=self, job=self.job, parameters=parameters)
# Musca will autoboot previously deployed binaries
# Therefore disconnect serial to avoid clutter.
self.pipeline.add_action(DisconnectDevice())
# If we don't run with a strict schema, it is possible to pass validation with warnings
# even without the required 'test_binary' field.
# Therefore, ensure the DownloaderAction.populate does not fail, and catch this at validate step.
image_params = parameters.get("images", {}).get("test_binary")
if image_params:
self.pipeline.add_action(
DownloaderAction("test_binary", path=download_dir, params=image_params)
)
# Turn on
self.pipeline.add_action(ResetDevice())
# Wait for storage
self.pipeline.add_action(WaitMuscaMassStorageAction())
# Deploy test binary
self.pipeline.add_action(MountMuscaMassStorageDevice())
self.pipeline.add_action(DeployMuscaTestBinary())
self.pipeline.add_action(UnmountMuscaMassStorageDevice())
# Check for FAIL.TXT to check if we were successful
self.pipeline.add_action(WaitMuscaMassStorageAction(udev_action="change"))
self.pipeline.add_action(MountMuscaMassStorageDevice())
self.pipeline.add_action(CheckMuscaFlashAction())
self.pipeline.add_action(UnmountMuscaMassStorageDevice())
class UnmountMuscaMassStorageDevice(UnmountVExpressMassStorageDevice):
"""
Unmount Musca USB mass storage device on the dispatcher
"""
name = "unmount-musca-usbmsd"
description = "unmount musca usb msd"
summary = "unmount musca usb mass storage device"
def __init__(self):
super().__init__()
self.namespace_label = "musca-usb"
self.namespace_action = "mount-musca-usbmsd"
class WaitMuscaMassStorageAction(Action):
"""
Waits for the Musca storage device to be presented to the dispatcher.
Often, 2 events are generated. The initial event may not have details about
the filesystem, so if we attempt to mount at this time, the OS doesn't know
how to mount due to lack of filesystem information.
Therefore, ensure we listen to the second event, where the filesystem is
known. ID_FS_VERSION=FAT16 is therefore also searched for as well as the
disk ID.
"""
name = "wait-musca-path"
description = "wait for musca mass storage"
summary = "wait for musca mass storage"
def __init__(self, udev_action="add"):
super().__init__()
self.udev_action = udev_action
# Ensure that we only trigger once FS details are known
self.match_dict = {"ID_FS_VERSION": "FAT16"}
self.devicepath = ""
def validate(self):
super().validate()
if not isinstance(self.udev_action, str):
self.errors = "invalid device action"
if "board_id" not in self.job.device:
return (
False,
'"board_id" is not in the device configuration (ID_SERIAL_SHORT value of serial device)',
)
if "id_serial" not in self.job.device["actions"]["deploy"]["methods"]["musca"]:
return (
False,
'"id_serial" not set in device configuration (/dev/disk/by-id/...)',
)
self.devicepath = "/dev/disk/by-id/{}".format(
self.job.device["actions"]["deploy"]["methods"]["musca"]["id_serial"]
)
def run(self, connection, max_end_time):
connection = super().run(connection, max_end_time)
if self.udev_action == "add":
self.logger.debug("Waiting for device to appear: %s", self.devicepath)
wait_udev_event(match_dict=self.match_dict, devicepath=self.devicepath)
elif self.udev_action == "change":
self.logger.debug("Waiting for device to reappear: %s", self.devicepath)
wait_udev_changed_event(
match_dict=self.match_dict, devicepath=self.devicepath
)
return connection
class MountMuscaMassStorageDevice(MountDeviceMassStorageDevice):
"""
Mounts Musca mass storage device on the dispatcher.
The device is identified by a id.
"""
name = "mount-musca-usbmsd"
description = "mount musca usb msd"
summary = "mount musca usb mass storage device on the dispatcher"
def __init__(self):
super().__init__()
self.disk_identifier = None
self.disk_identifier_type = "id"
self.namespace_label = "musca-usb"
def validate(self):
super().validate()
if "id_serial" not in self.job.device["actions"]["deploy"]["methods"]["musca"]:
self.errors = "id_serial parameter not set for actions.deploy.methods.musca"
self.disk_identifier = self.job.device["actions"]["deploy"]["methods"]["musca"][
"id_serial"
]
if not isinstance(self.disk_identifier, str):
self.errors = "USB ID unset for Musca"
class DeployMuscaTestBinary(Action):
"""
Copies test binary to Musca device
"""
name = "deploy-musca-test-binary"
description = "deploy test binary to usb msd"
summary = "copy test binary to Musca device"
def __init__(self):
super().__init__()
self.param_key = "test_binary"
def validate(self):
super().validate()
if not self.parameters["images"].get(self.param_key):
self.errors = "Missing '%s' in 'images'" % self.param_key
def run(self, connection, max_end_time):
connection = super().run(connection, max_end_time)
mount_point = self.get_namespace_data(
action="mount-musca-usbmsd", label="musca-usb", key="mount-point"
)
if not os.path.exists(mount_point):
raise InfrastructureError("Unable to locate mount point: %s" % mount_point)
test_binary = self.get_namespace_data(
action="download-action", label=self.param_key, key="file"
)
dest = os.path.join(mount_point)
self.logger.debug("Copying %s to %s", test_binary, dest)
shutil.copy(test_binary, dest)
return connection
class DeployMuscaAutomationAction(Action):
"""
Copies automation file to Musca device
Not actually used in this flow, but allows for creation of automation files.
https://github.com/ARMmbed/DAPLink/blob/master/docs/MSD_COMMANDS.md
"""
name = "deploy-musca-automation-file"
description = "deploy automation file to usb msd"
summary = "copy automation file to Musca device"
def __init__(self, automation_filename=""):
super().__init__()
self.automation_filename = automation_filename
def validate(self):
super().validate()
if not self.automation_filename:
self.errors = "Musca deploy was not given an automation filename."
def run(self, connection, max_end_time):
connection = super().run(connection, max_end_time)
mount_point = self.get_namespace_data(
action="mount-musca-usbmsd", label="musca-usb", key="mount-point"
)
if not os.path.exists(mount_point):
raise InfrastructureError("Unable to locate mount point: %s" % mount_point)
dest = os.path.join(mount_point, self.automation_filename)
self.logger.debug("Creating empty file %s", dest)
try:
with open(dest, "w"):
pass
except IOError:
raise InfrastructureError("Unable to write to %s" % dest)
return connection
class CheckMuscaFlashAction(Action):
"""
Checks for a FAIL.TXT file to see if there were flashing issues
"""
name = "check-musca-flash"
description = "checks if software flashed to the musca correctly"
summary = "check for FAIL.TXT on musca"