Skip to content
Merged
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
51 changes: 51 additions & 0 deletions benchkit/helpers/flasher/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# Copyright (C) 2026 Vrije Universiteit Brussel. All rights reserved.
# SPDX-License-Identifier: MIT

from benchkit.platforms import Platform

import pathlib
import inspect

class Flasher:
"""
This is an interface for a flasher, which can be used to flash a binary
to a device, reset the device, start the device, and stop the device. The
actual implementation of these methods will depend on the specific
flasher being used
"""

@property
def platform(self) -> Platform:
"""
Get the platform to run the flasher on
"""
...

def flash(
self,
bin: pathlib.Path,
addr: str,
) -> None:
"""
Flash the binary at the specified address.
Args:
bin: The path to the binary to flash.
addr: The address to flash the binary to (e.g., "0x080000")
"""
...

def reset(self) -> None:
"""
Reset the device.
"""
...
def start(self) -> None:
"""
Start the device (e.g., by running it or exiting reset).
"""
...
def stop(self) -> None:
"""
Stop the device (e.g., by halting it or entering reset).
"""
...
132 changes: 132 additions & 0 deletions benchkit/helpers/flasher/openocd.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
# Copyright (C) 2026 Vrije Universiteit Brussel. All rights reserved.
# SPDX-License-Identifier: MIT

from . import Flasher

from benchkit.platforms import Platform, get_current_platform
from benchkit.helpers.linux.groups import current_user_in_group

import pathlib

class OpenOCDFlasher(Flasher):
def __init__(
self,
interface: str,
target: str,
platform: Platform = get_current_platform(),
need_sudo: bool = True,
) -> None:
"""
Args:
interface: openocd interface to use (e.g., "stlink")
target: The OpenOCD target to use (e.g., "stm32l4x").
need_sudo: Whether to use sudo when invoking OpenOCD.
"""
self._interface: str | None = f"interface/{interface}.cfg"
self._target: str | None = f"target/{target}.cfg"
self._board: str | None = None
self._platform: Platform = platform # meta class platform property
self.__need_sudo: bool = need_sudo

@staticmethod
def with_board(
board: str,
need_sudo: bool = True,
) -> "OpenOCDFlasher":
"""
Configure the flasher for a specific board. This is a no-op for OpenOCD since the interface and target are already specified.
Args:
board: The name of the board (e.g., "st_nucleo_l4")
need_sudo: Whether to use sudo when invoking OpenOCD
Returns:
An instance of OpenOCDFlasher configured for the specified board.
"""
s = OpenOCDFlasher(
interface = None,
target = None,
need_sudo=need_sudo,
)

s._board: str | None = f"board/{board}.cfg"
return s

@property
def __cmd_prefix(self) -> str:
"""
Get the command prefix for invoking OpenOCD, optionally with sudo.
"""
return (
f"{'sudo' if self.__need_sudo else ''} openocd -f {self._interface} -f {self._target}"
) if self._board is None else (
f"{'sudo' if self.__need_sudo else ''} openocd -f {self._board}"
)

@property
def platform(self) -> Platform:
return self._platform

def flash(
self,
bin: pathlib.Path,
addr: str,
) -> None:
"""
Flash the firmware onto the board via OpenOCD.
Args:
bin: The path to the binary to flash.
addr: The address to flash the binary to (e.g., "0x08000000").

HACK addr is str because we use the hex format and don't want decimal
"""

self.platform.comm.shell(
command=self.__cmd_prefix.split(" ") + [
"-c",
f"program {bin} {addr} reset exit"
],
print_output=False,
)

def reset(self) -> None:
"""
Reset the board via OpenOCD.
"""
self.platform.comm.shell(
command=(
f"{self.__cmd_prefix} "
'-c "init" '
'-c "reset" '
'-c "exit"'
),
print_output=False,
print_input=True,
)


def start(self) -> None:
"""
Start the device (e.g., by running it or exiting reset).
"""
self.platform.comm.shell(
command=self.__cmd_prefix.split(" ") + [
'-c', 'init',
'-c', 'reset run',
'-c', 'exit',
],
print_output=False,
print_input=True,
)

def stop(self) -> None:
"""
Stop the device (e.g., by halting it or entering reset).
"""
self.platform.comm.shell(
command=self.__cmd_prefix.split(" ") + [
'-c', 'init',
'-c', 'reset halt',
'-c', 'exit',
],
print_output=False,
print_input=True,
)
20 changes: 20 additions & 0 deletions tests/test_flasher.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
#!/usr/bin/env python3

from benchkit.helpers.flasher.openocd import OpenOCDFlasher
import pathlib

if __name__ == "__main__":
"""
this test requires OpenOCD to be installed on host and, to have a device
connected that is supported by OpenOCD and the specified board configuration
(e.g., "st_nucleo_l4")
"""
device: str = "st_nucleo_l4"
openocd = OpenOCDFlasher.with_board(board=device, need_sudo=True)

# to flash an a file do :
# openocd.flash(bin=pathlib.Path("firmware.elf").resolve(), addr="0x08000000")

openocd.stop()
openocd.start()
openocd.reset()