wolfBoot supports wolfHAL as an alternative hardware abstraction layer backend. wolfHAL provides portable drivers for common MCU peripherals (clock, flash, GPIO, UART, SPI, etc.) with a consistent API across platforms.
The wolfHAL integration uses a single generic TARGET=wolfhal with a per-board
abstraction layer. All board-specific details — device instances, driver bindings,
build flags, and linker scripts — live in a self-contained board directory. Adding
support for a new board or MCU family requires no changes to the core build system or
HAL shim.
The integration consists of four parts:
-
Generic HAL shim (
hal/wolfhal.c) — implements the wolfBoot HAL API (hal_flash_write,hal_flash_erase, etc.) by callingBoard_*macros. This file is shared across all wolfHAL boards. -
Board directory (
hal/boards/<board>/) — contains three files that fully describe a board:board.h—#definemacros mapping genericBoard_*APIs to chip-specific wolfHAL driver functions.board.c— device instances (clock, flash, GPIO, UART), configuration structs, andhal_init/hal_prepare_bootimplementations.board.mk— build variables (ARCH_FLASH_OFFSET,LSCRIPT_IN, wolfHAL driver objects,RAM_CODElinker rules).
-
Generic test application (
test-app/app_wolfhal.c) — demonstrates using wolfHAL peripherals (GPIO, UART) beyond what the bootloader needs, using the sameBoard_*API. -
wolfHAL library (
lib/wolfHAL/) — the wolfHAL submodule containing the platform drivers.
config/examples/wolfhal_<board>.config
└─ TARGET=wolfhal BOARD=<board>
arch.mk
└─ Sets WOLFHAL_ROOT, CFLAGS += -Ihal/boards/$(BOARD)
Makefile
└─ OBJS += hal/boards/$(BOARD)/board.o
└─ include hal/boards/$(BOARD)/board.mk
hal/wolfhal.c (generic — calls Board_Flash_Write, Board_Uart_Send, etc.)
└─ #include "board.h" (resolved via -I to the board directory)
hal/boards/<board>/
├─ board.h (#define Board_Flash_Write → whal_<family>Flash_Write)
├─ board.c (device instances, hal_init, hal_prepare_boot)
└─ board.mk (ARCH_FLASH_OFFSET, LSCRIPT_IN, driver objects, RAM_CODE rules)
The board.h macros resolve Board_* calls directly to the chip-specific wolfHAL
driver functions at compile time. This avoids vtable indirection and allows the
linker to garbage-collect unused driver code (with -Wl,--gc-sections).
A wolfHAL-based config requires two variables beyond the standard wolfBoot settings:
TARGET=wolfhal
BOARD=stm32wb_nucleo
TARGET=wolfhalselects the generic wolfHAL HAL shim and build path.BOARDselects the board directory underhal/boards/.
See config/examples/wolfhal_*.config for complete examples.
To add a new board, create a directory hal/boards/<board_name>/ with three files:
Map each generic Board_* macro to the appropriate wolfHAL driver function for your
MCU family. The required mappings are:
#ifndef WOLFHAL_BOARD_H
#define WOLFHAL_BOARD_H
#include <wolfHAL/clock/<family>_clock.h>
#include <wolfHAL/flash/<family>_flash.h>
#include <wolfHAL/gpio/<family>_gpio.h>
#include <wolfHAL/uart/<family>_uart.h>
/* Clock */
#define Board_Clock_Init whal_<Family>Clock_Init
#define Board_Clock_Deinit whal_<Family>Clock_Deinit
#define Board_Clock_Enable whal_<Family>Clock_Enable
#define Board_Clock_Disable whal_<Family>Clock_Disable
/* Flash */
#define Board_Flash_Init whal_<Family>Flash_Init
#define Board_Flash_Deinit whal_<Family>Flash_Deinit
#define Board_Flash_Lock whal_<Family>Flash_Lock
#define Board_Flash_Unlock whal_<Family>Flash_Unlock
#define Board_Flash_Write whal_<Family>Flash_Write
#define Board_Flash_Erase whal_<Family>Flash_Erase
/* GPIO */
#define Board_Gpio_Init whal_<Family>Gpio_Init
#define Board_Gpio_Deinit whal_<Family>Gpio_Deinit
#define Board_Gpio_Set whal_<Family>Gpio_Set
#define Board_Gpio_Get whal_<Family>Gpio_Get
/* UART */
#define Board_Uart_Init whal_<Family>Uart_Init
#define Board_Uart_Deinit whal_<Family>Uart_Deinit
#define Board_Uart_Send whal_<Family>Uart_Send
#define Board_Uart_Recv whal_<Family>Uart_Recv
#endif /* WOLFHAL_BOARD_H */Define the wolfHAL device instances and implement hal_init and hal_prepare_boot.
The file must export g_wbFlash (and g_wbUart when DEBUG_UART is enabled) as
non-static globals — these are referenced by hal/wolfhal.c via extern.
#include "hal.h"
#include "board.h"
/* Clock controller */
whal_Clock g_wbClock = {
.regmap = { .base = ..., .size = 0x400 },
.cfg = &(<family>_clock_cfg) { ... },
};
/* Flash */
whal_Flash g_wbFlash = {
.regmap = { .base = ..., .size = 0x400 },
.cfg = &(<family>_flash_cfg) {
.startAddr = 0x08000000,
.size = ...,
},
};
#ifdef DEBUG_UART
whal_Gpio g_wbGpio = { ... };
whal_Uart g_wbUart = { ... };
#endif
void hal_init(void)
{
/* Initialize clock tree, flash, and optionally GPIO/UART */
Board_Clock_Init(&g_wbClock);
Board_Flash_Init(&g_wbFlash);
#ifdef DEBUG_UART
Board_Gpio_Init(&g_wbGpio);
Board_Uart_Init(&g_wbUart);
#endif
}
void hal_prepare_boot(void)
{
#ifdef DEBUG_UART
Board_Uart_Deinit(&g_wbUart);
Board_Gpio_Deinit(&g_wbGpio);
#endif
Board_Flash_Deinit(&g_wbFlash);
Board_Clock_Deinit(&g_wbClock);
}Provide the build-time configuration: flash offset, linker script, and the wolfHAL driver objects needed for your MCU family.
ARCH_FLASH_OFFSET=0x08000000
LSCRIPT_IN=hal/<family>.ld
WOLFHAL_OBJS+=$(WOLFHAL_ROOT)/src/clock/<family>_clock.o
WOLFHAL_OBJS+=$(WOLFHAL_ROOT)/src/flash/<family>_flash.o
ifeq ($(DEBUG_UART),1)
WOLFHAL_OBJS+=$(WOLFHAL_ROOT)/src/gpio/<family>_gpio.o
WOLFHAL_OBJS+=$(WOLFHAL_ROOT)/src/uart/<family>_uart.o
endif
OBJS+=$(WOLFHAL_OBJS)
APP_OBJS+=$(WOLFHAL_OBJS)
ifeq ($(RAM_CODE),1)
WOLFHAL_FLASH_EXCLUDE_TEXT=*(EXCLUDE_FILE(*<family>_flash.o) .text*)
WOLFHAL_FLASH_EXCLUDE_RODATA=*(EXCLUDE_FILE(*<family>_flash.o) .rodata*)
WOLFHAL_FLASH_RAM_SECTIONS=*<family>_flash.o(.text* .rodata*)
endifCreate config/examples/wolfhal_<board_name>.config:
TARGET=wolfhal
BOARD=<board_name>
SIGN=ECC256
HASH=SHA256
WOLFBOOT_SECTOR_SIZE=0x1000
WOLFBOOT_PARTITION_SIZE=0x20000
WOLFBOOT_PARTITION_BOOT_ADDRESS=0x08008000
WOLFBOOT_PARTITION_UPDATE_ADDRESS=0x08028000
WOLFBOOT_PARTITION_SWAP_ADDRESS=0x08048000
NVM_FLASH_WRITEONCE=1
Adjust partition addresses and sector sizes for your board's flash layout. Optionally
add DEBUG_UART=1 to enable UART debug output.
When RAM_CODE=1 is set, wolfBoot's core flash update functions are placed in RAM
via the RAMFUNCTION attribute. For wolfHAL boards, the board.mk defines
EXCLUDE_FILE rules that also place the wolfHAL flash driver into RAM. This ensures
all flash operations execute from RAM, which is required on MCUs that stall or fault
when code executes from the same flash bank being programmed.
The linker script uses @WOLFHAL_FLASH_EXCLUDE_TEXT@,
@WOLFHAL_FLASH_EXCLUDE_RODATA@, and @WOLFHAL_FLASH_RAM_SECTIONS@ placeholders
that are substituted at build time. When RAM_CODE=1, these expand to
EXCLUDE_FILE rules that move the flash driver's .text and .rodata sections from
flash into the .data section (loaded to RAM at startup). When RAM_CODE is not
set, all code remains in flash as normal.
The generic test application (test-app/app_wolfhal.c) demonstrates using wolfHAL
peripherals beyond what the bootloader needs. It initializes GPIO and UART via the
Board_* API, then exercises the wolfBoot update mechanism.
The test app re-uses the board's clock instance via extern whal_Clock g_wbClock to
enable peripheral clocks for its own devices (e.g. GPIO for an LED, UART for serial
output).
The test-app Makefile compiles its own copy of the board file (board_<board>.o)
with DEBUG_UART=1 always defined, since the app needs UART and GPIO regardless of
the bootloader's DEBUG_UART setting.