diff --git a/doc/configuration.rst b/doc/configuration.rst index 87c4602bb..d3f390795 100644 --- a/doc/configuration.rst +++ b/doc/configuration.rst @@ -1347,6 +1347,38 @@ Arguments: Used by: - none + +RpibootDevice +~~~~~~~~~~~~~ + +A :any:`RpibootDevice` describes an attached raspberry pi device in boot mode. +This driver does not control power or the reset button. So in order to be +able to set a pi into boot mode relays or simular need to be used to set the +raspberry pi into bootmode. +When then pi is in boot mode this resouce will become available and the +rpiboot driver can be used to make the pi act like a MassStorageDevice. +When the pi has been put into boot mode this driver can be used to make the +pi act like a MassStorageDevice. + +.. code-block:: yaml + + RpibootDevice: + match: + ID_SERIAL: 'Broadcom_BCM2711_Boot_124e610f' + + +Arguments: + - match (dict): key and value pairs for a udev match, see `udev Matching`_ + +Used by: + - RpibootDriver + + +NetworkRpibootDevice +~~~~~~~~~~~~~~~~~~~~ +A :any:`NetworkRpibootDevice` describes a `RpibootDevice`_ resource +available on a remote computer. + Providers ~~~~~~~~~ Providers describe directories that are accessible by the target over a @@ -3407,6 +3439,30 @@ Implements: Arguments: - None +RpibootDriver +~~~~~~~~~~~~~ +The :any:`RpibootDriver` uses a `RpibootDevice`_ resource to enable a raspberry +pi as an MassStorageDevice. Allowing for using `USBStorageDriver`_ to bootstrap +a raspberry pi. + +Binds to: + rpi: + - `RpibootDevice`_ + - `NetworkRpibootDevice`_ + +Implements: + - None + +Arguments: + - None (yet) + +before using this driver the raspberry pi needs to be put into usbboot mode. +This is done by pressing a EMMC-DISABLE / nRPIBOOT button on rpi cm expansion +board or grounding pins using a jumper wire. +How this is done is dependent on what raspberry pi is being used. +For compute models and rpi5 `rpi-usbboot `_ +and for rpi4 `use gpio to enable RPIBOOT `. + .. _conf-strategies: Strategies diff --git a/examples/rpiboot/client.yaml b/examples/rpiboot/client.yaml new file mode 100644 index 000000000..bfcc63b04 --- /dev/null +++ b/examples/rpiboot/client.yaml @@ -0,0 +1,30 @@ +targets: + main: + resources: + - RemotePlace: + name: !template '$LG_PLACE' + drivers: + - GpioDigitalOutputDriver: + name: 'power-driver' + bindings: + gpio: 'GpioPower' + - GpioDigitalOutputDriver: + name: 'reset-driver' + bindings: + gpio: 'GpioReset' + - DigitalOutputPowerDriver: + delay: 2.0 + bindings: + output: 'power-driver' + - DigitalOutputResetDriver: + delay: 2.0 + bindings: + output: 'reset-driver' + - SerialDriver: {} + - SSHDriver: {} + - ShellDriver: + login_prompt: '[\w-]+ login: ' + username: root + password: root + prompt: 'root@[\w-]+:[^ ]+# ' + - DUTStrategy: {} diff --git a/examples/rpiboot/exporter.yaml b/examples/rpiboot/exporter.yaml new file mode 100644 index 000000000..7cc746a57 --- /dev/null +++ b/examples/rpiboot/exporter.yaml @@ -0,0 +1,25 @@ +rpi-cm4-101-panel: + location: 101 display panel on desk + USBSerialPort: + match: + ID_SERIAL: "FTDI_USB_Serial_Converter_FTBZ30UM" + speed: 115200 + NetworkService: + address: '192.168.1.123' + port: 22 + username: root + password: root + USBMassStorage: + match: + ID_SERIAL: 'mmcblk0_Raspberry_Pi_multi-function_USB_device_10000000124e610f-0:0' + RpibootDevice: + match: + ID_SERIAL: 'Broadcom_BCM2711_Boot_124e610f' + # gpio pins are set to control relay hat from waveshare + # https://www.waveshare.com/product/rpi-relay-board.htm + GpioPower: + cls: 'SysfsGPIO' + index: 533 + GpioReset: + cls: 'SysfsGPIO' + index: 532 diff --git a/examples/rpiboot/write-image.py b/examples/rpiboot/write-image.py new file mode 100644 index 000000000..e39a54fad --- /dev/null +++ b/examples/rpiboot/write-image.py @@ -0,0 +1,57 @@ +# script for automating the use of labgrid to write a new image to a raspberry pi +# using rpiboot. +# input place to write new image to and path to image. + +import os +import sys +from time import sleep +from labgrid import Environment +from labgrid.driver import USBStorageDriver +from labgrid.driver import DigitalOutputPowerDriver +from labgrid.driver import DigitalOutputResetDriver +from labgrid.driver import RpibootDriver + +image_path = sys.argv[1] + +# if there is a 3. argument set is as the place labgrid should use. +# if not set the current value for LG_PLACE is used. +if len(sys.argv) >= 3: + os.environ["LG_PLACE"] = sys.argv[2] + +if os.environ.get("LG_PLACE", None) is None: + print("No place to write image to given, set one with LG_PLACE or giving it as an extra argument") + exit(1) + +config_path = os.path.dirname(__file__) + "client.yaml" +env = Environment(config_path) +t = env.get_target("main") + +power = t.get_driver("DigitalOutputPowerDriver") +t.activate(power) +gpio_reset = t.get_driver("GpioDigitalOutputDriver", name="reset-driver") +t.activate(gpio_reset) + +# put panel into usbboot mode. +power.off() +gpio_reset.set(True) +sleep(1) +power.on() + +# use rpiboot to enable MSD mode +rpiboot = RpibootDriver(t, name=None) +t.activate(rpiboot) +rpiboot.enable() + +# wait a little bit to make sure the MSD is available. +sleep(5) + +# write new image to panel +storage = USBStorageDriver(t, name=None) +t.activate(storage) +storage.write_image(filename=image_path) + +# switch panel back into normal bootmode. +power.off() +gpio_reset.set(False) +sleep(1) +power.on() diff --git a/labgrid/driver/__init__.py b/labgrid/driver/__init__.py index edf1ad2b1..fcaac2484 100644 --- a/labgrid/driver/__init__.py +++ b/labgrid/driver/__init__.py @@ -48,3 +48,4 @@ from .deditecrelaisdriver import DeditecRelaisDriver from .dediprogflashdriver import DediprogFlashDriver from .httpdigitaloutput import HttpDigitalOutputDriver +from .rpibootdriver import RpibootDriver diff --git a/labgrid/driver/rpibootdriver.py b/labgrid/driver/rpibootdriver.py new file mode 100644 index 000000000..f9e4b1684 --- /dev/null +++ b/labgrid/driver/rpibootdriver.py @@ -0,0 +1,38 @@ +import attr + +from ..factory import target_factory +from ..step import step +from .common import Driver +from ..util.helper import processwrapper + +@target_factory.reg_driver +@attr.s(eq=False) +class RpibootDriver(Driver): + bindings = { + "rpi": {"RpibootDevice", "NetworkRpibootDevice"}, + } + + image = attr.ib(default=None) + + def __attrs_post_init__(self): + super().__attrs_post_init__() + if self.target.env: + self.tool = self.target.env.config.get_tool('rpiboot') + else: + self.tool = 'rpiboot' + + def on_activate(self): + pass + + def on_deactivate(self): + pass + + @Driver.check_active + @step(args=['filename']) + def enable(self, filename=None,): + # Switch raspberry pi into MassStorageDevice mode using the rpiboot tool + args = [] + processwrapper.check_output( + self.rpi.command_prefix + [self.tool] + args, + print_on_silent_log=True + ) diff --git a/labgrid/remote/exporter.py b/labgrid/remote/exporter.py index d3b406503..2473b5122 100755 --- a/labgrid/remote/exporter.py +++ b/labgrid/remote/exporter.py @@ -546,6 +546,26 @@ def __attrs_post_init__(self): self.data["cls"] = f"Remote{self.cls}".replace("Network", "") +@attr.s(eq=False) +class RpibootExport(USBGenericExport): + """ResourceExport for raspberry pi in boot mode""" + + def __attrs_post_init__(self): + super().__attrs_post_init__() + + def _get_params(self): + """Helper function to return parameters""" + return { + "host": self.host, + "busnum": self.local.busnum, + "devnum": self.local.devnum, + "path": self.local.path, + "vendor_id": self.local.vendor_id, + "model_id": self.local.model_id, + "serial_id": self.local.serial_id, + } + + exports["AndroidFastboot"] = USBGenericExport exports["AndroidUSBFastboot"] = USBGenericRemoteExport exports["DFUDevice"] = USBGenericExport @@ -569,6 +589,7 @@ def __attrs_post_init__(self): exports["HIDRelay"] = USBHIDRelayExport exports["USBFlashableDevice"] = USBFlashableExport exports["LXAUSBMux"] = USBGenericExport +exports["RpibootDevice"] = RpibootExport @attr.s(eq=False) diff --git a/labgrid/resource/__init__.py b/labgrid/resource/__init__.py index 6ec9d5db8..6ec5fd013 100644 --- a/labgrid/resource/__init__.py +++ b/labgrid/resource/__init__.py @@ -32,6 +32,7 @@ USBSerialPort, USBTMC, USBVideo, + RpibootDevice, ) from .common import Resource, ResourceManager, ManagedResource from .ykushpowerport import YKUSHPowerPort, NetworkYKUSHPowerPort diff --git a/labgrid/resource/remote.py b/labgrid/resource/remote.py index a29e58ee8..00a2ada6d 100644 --- a/labgrid/resource/remote.py +++ b/labgrid/resource/remote.py @@ -414,3 +414,14 @@ class RemoteNFSProvider(NetworkResource): @attr.s(eq=False) class RemoteHTTPProvider(RemoteBaseProvider): pass + +@target_factory.reg_resource +@attr.s(eq=False) +class NetworkRpibootDevice(RemoteUSBResource): + """The NetworkRpibootDevice describes a remotely accessible raspberry pi ready for rpiboot""" + + serial_id = attr.ib(validator=attr.validators.optional(attr.validators.instance_of(str))) + + def __attrs_post_init__(self): + self.timeout = 10.0 + super().__attrs_post_init__() diff --git a/labgrid/resource/udev.py b/labgrid/resource/udev.py index 1200f458f..22b0fb930 100644 --- a/labgrid/resource/udev.py +++ b/labgrid/resource/udev.py @@ -803,3 +803,28 @@ def update(self): self.index = int(self.read_attr('base')) + self.pin else: self.index = None + +@target_factory.reg_resource +@attr.s(eq=False) +class RpibootDevice(USBResource): + """The RpibootDevice describes an attached raspberry pi device in boot mode, + it is identified via USB using udev. + """ + + def filter_match(self, device): + match = (device.properties.get('ID_VENDOR_ID'), device.properties.get('ID_MODEL_ID')) + + if match not in [("0a5c", "2711"), # rpi4 + ("0a5c", "2712"), # rpi5 + ]: + return False + + return super().filter_match(device) + + @property + def serial_id(self): + device = self._get_usb_device() + if device: + return str(device.properties.get('ID_SERIAL_SHORT')) + + return None