Skip to content
36 changes: 35 additions & 1 deletion doc/configuration.rst
Original file line number Diff line number Diff line change
Expand Up @@ -991,6 +991,7 @@ Arguments:

Used by:
- `OpenOCDDriver`_
- `PyOCDDriver`_

NetworkUSBDebugger
~~~~~~~~~~~~~~~~~~
Expand Down Expand Up @@ -1971,7 +1972,6 @@ Binds to:

Implements:
- :any:`ConsoleProtocol`
- :any:`ResetProtocol`

Arguments:
- txdelay (float, default=0.0): time in seconds to wait before sending a chunk
Expand Down Expand Up @@ -2331,6 +2331,40 @@ Arguments:
- board_config (str): optional, board config in the ``openocd/scripts/board/`` directory
- load_commands (list of str): optional, load commands to use instead of ``init``, ``bootstrap {filename}``, ``shutdown``

PyOCDDriver
~~~~~~~~~~~~~
An :any:`PyOCDDriver` controls *PyOCD* to bootstrap a target with a bootloader.

Binds to:
interface:
- `USBDebugger`_
- `NetworkUSBDebugger`_

Implements:
- :any:`BootstrapProtocol`
- :any:`ResetProtocol`

.. code-block:: yaml

PyOCDDriver:
image: 'bitstream'
target_name: 'nrf52832'
frequency: "400khz"
config: 'pyocd.yaml'
serial: '1234567890'
load_commands:
- 'init'
- 'svf -quiet {filename}'
- 'exit'

Arguments:
- image (str): optional, name of the image to bootstrap onto the device
- target_name (str): optional, set the target type. Should be one of the targets listed by 'pyocd list --targets'.
- frequency (str): optional, SWD/JTAG clock frequency in Hz
- config (str): optional, PyOCD configuration file
- serial (str): optional, USB-Serial to identify device if multiple are present.
- load_commands (list of str): optional, additional command line parameters for load to use instead of ``-e``, ``sector``

QuartusHPSDriver
~~~~~~~~~~~~~~~~
A :any:`QuartusHPSDriver` controls the "Quartus Prime Programmer and Tools" to
Expand Down
4 changes: 4 additions & 0 deletions doc/man/device-config.rst
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,10 @@ TOOLS KEYS
Path to the openocd binary, used by the OpenOCDDriver.
See: https://openocd.org/

``pyocd``
Path to the pyocd binary, used by the PyOCDDriver.
See: https://pyocd.io/

``quartus_hps``
Path to the quartus_hps binary, used by the QuartusHPSDriver.
See: https://www.intel.com/content/www/us/en/docs/programmable/683039/22-3/hps-flash-programmer.html
Expand Down
1 change: 1 addition & 0 deletions labgrid/driver/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from .fastbootdriver import AndroidFastbootDriver
from .dfudriver import DFUDriver
from .openocddriver import OpenOCDDriver
from .pyocddriver import PyOCDDriver
from .quartushpsdriver import QuartusHPSDriver
from .flashromdriver import FlashromDriver
from .onewiredriver import OneWirePIODriver
Expand Down
108 changes: 108 additions & 0 deletions labgrid/driver/pyocddriver.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
import attr

from ..factory import target_factory
from ..protocol import BootstrapProtocol, ResetProtocol
from ..step import step
from ..util.managedfile import ManagedFile
from ..util.helper import processwrapper
from .common import Driver


@target_factory.reg_driver
@attr.s(eq=False)
class PyOCDDriver(Driver, BootstrapProtocol, ResetProtocol):

priorities = {ResetProtocol: 5}

bindings = {
"interface": {
"USBDebugger",
"NetworkUSBDebugger",
},
}

image = attr.ib(
default=None,
validator=attr.validators.optional(attr.validators.instance_of(str)),
)
load_commands = attr.ib(
default=None,
validator=attr.validators.optional(attr.validators.instance_of((str, list))),
)
target_name = attr.ib(
default=None,
validator=attr.validators.optional(attr.validators.instance_of(str)),
)
frequency = attr.ib(
default=None,
validator=attr.validators.optional(attr.validators.instance_of(str)),
)
config = attr.ib(
default=None,
validator=attr.validators.optional(attr.validators.instance_of(str)),
)
serial = attr.ib(
default=None,
validator=attr.validators.optional(attr.validators.instance_of(str)),
)

def __attrs_post_init__(self):
super().__attrs_post_init__()

# FIXME make sure we always have an environment or config
if self.target.env:
self.tool = self.target.env.config.get_tool("pyocd")
if self.config:
self.config = self.target.env.config.resolve_path(self.config)
else:
self.tool = "pyocd"

def _run_commands(self, subcommand: str, commands: list | None = None):
cmd = [self.tool, subcommand]
if self.serial:
cmd += ["--uid", self.serial]
if self.target_name is not None and (not commands or "--target" not in commands):
cmd += ["--target", self.target_name]
if self.frequency is not None and (not commands or "--frequency" not in commands):
cmd += ["--frequency", self.frequency]

if self.config is not None:
mconfig = ManagedFile(self.config, self.interface)
mconfig.sync_to_resource()
cmd += ["--config", mconfig.get_remote_path()]
else:
cmd += ["--no-config"]

if commands:
cmd += commands
processwrapper.check_output(
command=self.interface.wrap_command(cmd),
print_on_silent_log=True,
)

@Driver.check_active
@step(args=["filename"])
def load(self, filename=None):

if filename is None and self.image is not None and self.target.env:
filename = self.target.env.config.get_image_path(self.image)

mf = ManagedFile(filename, self.interface)
mf.sync_to_resource()

if self.load_commands:
if isinstance(self.load_commands, str):
commands = self.load_commands.split()
else:
commands = self.load_commands.copy()
else:
commands = ["-e", "sector"]

commands.append(mf.get_remote_path())

self._run_commands("load", commands)

@Driver.check_active
@step()
def reset(self):
self._run_commands("reset")
Loading
Loading