From e710fb570ef1fdb3f938dc4ecc389ec7f10bdc14 Mon Sep 17 00:00:00 2001 From: Tyler Potyondy Date: Thu, 15 Jan 2026 14:51:52 -0800 Subject: [PATCH 1/2] openocd: add check to not r/w past end of flash For the stm32wle5jc, Tockloader was causing a bus fault via openocd by attempting to read/write past the end of flash. This adds an optional parameter that may be defined to denote the end of the flash region. The openocd flash r/w operations then check that a given operation will not exceed the end of flash. --- tockloader/openocd.py | 20 +++++++++++++++++++- tockloader/tockloader.py | 20 +++++++++++++++++--- 2 files changed, 36 insertions(+), 4 deletions(-) diff --git a/tockloader/openocd.py b/tockloader/openocd.py index 3274234..e1c3302 100644 --- a/tockloader/openocd.py +++ b/tockloader/openocd.py @@ -17,7 +17,7 @@ import time from .board_interface import BoardInterface -from .exceptions import TockLoaderException +from .exceptions import TockLoaderException, ChannelAddressErrorException # global static variable for collecting temp files for Windows collect_temp_files = [] @@ -33,6 +33,7 @@ def __init__(self, args): # Store the serial number if provided self.openocd_serial_number = getattr(self.args, "openocd_serial_number") + self.address_maximum = None def attached_board_exists(self): # Get a list of attached devices, check if that list has at least @@ -84,6 +85,8 @@ def open_link_to_board(self): self.openocd_prefix = board["openocd"]["prefix"] if self.openocd_commands == {} and "commands" in board["openocd"]: self.openocd_commands = board["openocd"]["commands"] + if self.address_maximum == None and "address_maximum" in board["openocd"]: + self.address_maximum = board["openocd"]["address_maximum"] # And we may need to setup other common board settings. self._configure_from_known_boards() @@ -248,6 +251,11 @@ def _list_emulators(self): openocd_cmd=self.openocd_cmd ) ) + openocd_commands.append( + '{openocd_cmd} -c "source [find interface/stlink.cfg]; transport select hla_swd; source [find target/stm32wlx.cfg]; init; exit;"'.format( + openocd_cmd=self.openocd_cmd + ) + ) # These are the magic strings in the output of openocd we are looking # for. If there is a better way to do this then we should change. But, @@ -259,6 +267,7 @@ def _list_emulators(self): ("(mfg: 0x049 (Xilinx), part: 0x3631, ver: 0x1)", "arty"), ("SWD DPIDR 0x2ba01477", "microbit_v2"), ("stm32f4x.cpu", "stm32f4discovery"), + ("stm32wlx.cpu", "stm32wle5jc") ] emulators = [] @@ -306,6 +315,9 @@ def flash_binary(self, address, binary, pad=False): # The "normal" flash command uses `program`. command = "program {{binary}} verify {address:#x}; reset;" + if self.address_maximum and address + len(binary) > self.address_maximum: + raise ChannelAddressErrorException() + # Check if the configuration wants to override the default program command. if "program" in self.openocd_commands: command = self.openocd_commands["program"] @@ -329,6 +341,9 @@ def flash_binary(self, address, binary, pad=False): self._run_openocd_commands(command, binary) def read_range(self, address, length): + if self.address_maximum and address + length > self.address_maximum: + raise ChannelAddressErrorException() + # The normal read command uses `dump_image`. command = "dump_image {{binary}} {address:#x} {length};" @@ -360,6 +375,9 @@ def read_range(self, address, length): return read def clear_bytes(self, address): + if self.address_maximum and address >= self.address_maximum: + raise ChannelAddressErrorException() + logging.debug("Clearing bytes starting at {:#0x}".format(address)) binary = bytes([0xFF] * 8) diff --git a/tockloader/tockloader.py b/tockloader/tockloader.py index 18a3867..7e59f12 100644 --- a/tockloader/tockloader.py +++ b/tockloader/tockloader.py @@ -1669,14 +1669,25 @@ def is_valid(slices): optional_binary = app.get_binary(app_address) if optional_binary: logging.info("Flashing app {} binary to board.".format(app)) - self.channel.flash_binary(app_address, optional_binary) + try: + self.channel.flash_binary(app_address, optional_binary) + except ChannelAddressErrorException: + logging.info( + "Failed to find space to flash app {}.".format(app) + ) + pass app_address = app_address + app.get_size() # Then erase the next page if we have not already rewritten all # existing apps. This ensures that flash is clean at the end of the # installed apps and makes sure the kernel will find the correct end # of applications. - self.channel.clear_bytes(app_address) + try: + self.channel.clear_bytes(app_address) + except ChannelAddressErrorException: + # We are at the end of flash and there are no bytes + # left to clear. + pass def _replace_with_padding(self, app): """ @@ -1717,7 +1728,10 @@ def _extract_all_app_headers(self, verbose=False, extract_app_binary=False): logging.debug( "Reading for app header @{:#x}, {} bytes".format(address, header_length) ) - flash = self.channel.read_range(address, header_length) + try: + flash = self.channel.read_range(address, header_length) + except ChannelAddressErrorException: + break # if there was an error, the binary array will be empty if len(flash) < header_length: From f5ebf66ea42db1a0e0aa53604be21ad23c18c53f Mon Sep 17 00:00:00 2001 From: Tyler Potyondy Date: Thu, 15 Jan 2026 11:57:40 -0800 Subject: [PATCH 2/2] boards: add stm32wle5jc to known boards This adds the stm32wle5jc chip used in the seeed_studio_lora-E5-HF dev board to tockloader's known board list. Communication intended to occur using stlink over openocd. --- tockloader/board_interface.py | 12 ++++++++++++ tockloader/openocd.py | 2 +- tockloader/tockloader.py | 3 +++ 3 files changed, 16 insertions(+), 1 deletion(-) diff --git a/tockloader/board_interface.py b/tockloader/board_interface.py index 9ba0368..22a6363 100644 --- a/tockloader/board_interface.py +++ b/tockloader/board_interface.py @@ -316,6 +316,18 @@ class BoardInterface: "flush_command": "probe-rs download {binary} --binary-format bin --base-address 0x10000000 --chip CY8C624AAZI-S2D44", }, }, + "stm32wle5jc": { + "description": "Seeed Studio LoRa-E5 board based on the STM32WLE5JC SoC", + "arch": "cortex-m4", + "page_size": 2048, + "no_attribute_table": True, + "openocd": { + "prefix": "source [find interface/stlink.cfg]; \ + transport select hla_swd; \ + source [find target/stm32wlx.cfg];", + "address_maximum": 0x08040000, + }, + }, } def __init__(self, args): diff --git a/tockloader/openocd.py b/tockloader/openocd.py index e1c3302..20b2db2 100644 --- a/tockloader/openocd.py +++ b/tockloader/openocd.py @@ -267,7 +267,7 @@ def _list_emulators(self): ("(mfg: 0x049 (Xilinx), part: 0x3631, ver: 0x1)", "arty"), ("SWD DPIDR 0x2ba01477", "microbit_v2"), ("stm32f4x.cpu", "stm32f4discovery"), - ("stm32wlx.cpu", "stm32wle5jc") + ("stm32wlx.cpu", "stm32wle5jc"), ] emulators = [] diff --git a/tockloader/tockloader.py b/tockloader/tockloader.py index 7e59f12..6df4e36 100644 --- a/tockloader/tockloader.py +++ b/tockloader/tockloader.py @@ -145,6 +145,9 @@ class TockLoader: "cy8cproto_62_4343_w": { "start_address": 0x10100000, }, + "stm32wle5jc": { + "start_address": 0x8018000, + }, }, }