Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 56 additions & 0 deletions doc/configuration.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 <https://github.com/raspberrypi/usbboot?tab=readme-ov-file#running>`_
and for rpi4 `use gpio to enable RPIBOOT <https://github.com/raspberrypi/usbboot/tree/master/secure-boot-recovery#extra-steps-for-raspberry-pi-4b--pi-400>`.

.. _conf-strategies:

Strategies
Expand Down
30 changes: 30 additions & 0 deletions examples/rpiboot/client.yaml
Original file line number Diff line number Diff line change
@@ -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: {}
25 changes: 25 additions & 0 deletions examples/rpiboot/exporter.yaml
Original file line number Diff line number Diff line change
@@ -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
57 changes: 57 additions & 0 deletions examples/rpiboot/write-image.py
Original file line number Diff line number Diff line change
@@ -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()
1 change: 1 addition & 0 deletions labgrid/driver/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,3 +48,4 @@
from .deditecrelaisdriver import DeditecRelaisDriver
from .dediprogflashdriver import DediprogFlashDriver
from .httpdigitaloutput import HttpDigitalOutputDriver
from .rpibootdriver import RpibootDriver
38 changes: 38 additions & 0 deletions labgrid/driver/rpibootdriver.py
Original file line number Diff line number Diff line change
@@ -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
)
21 changes: 21 additions & 0 deletions labgrid/remote/exporter.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)
Expand Down
1 change: 1 addition & 0 deletions labgrid/resource/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
USBSerialPort,
USBTMC,
USBVideo,
RpibootDevice,
)
from .common import Resource, ResourceManager, ManagedResource
from .ykushpowerport import YKUSHPowerPort, NetworkYKUSHPowerPort
Expand Down
11 changes: 11 additions & 0 deletions labgrid/resource/remote.py
Original file line number Diff line number Diff line change
Expand Up @@ -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__()
25 changes: 25 additions & 0 deletions labgrid/resource/udev.py
Original file line number Diff line number Diff line change
Expand Up @@ -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