pic32cz: add wolfHSM client target

Run wolfBoot on the PIC32CZ CA9x host core (Cortex-M7) as a wolfHSM client,
offloading the image digest (SHA-256) and the ECDSA P-256 signature check to
the wolfHSM server.
pull/837/head
Marco Oliverio 2026-07-15 14:29:54 +00:00 committed by Daniele Lacamera
parent 15c7e621ed
commit 36b665bfcc
9 changed files with 460 additions and 0 deletions

View File

@ -289,6 +289,10 @@ ifeq ($(TARGET),stm32n6)
endif
endif # TZEN=1
ifeq ($(TARGET),pic32cz)
MAIN_TARGET:=wolfboot.bin test-app/image_v1_signed.bin
endif
ifeq ($(TARGET),x86_64_efi)
MAIN_TARGET:=wolfboot.efi
endif

40
arch.mk
View File

@ -243,6 +243,46 @@ ifeq ($(ARCH),ARM)
ARCH_FLASH_OFFSET=0x08000000
CORTEX_M7=1
OBJS+=hal/pic32c.o
ifeq ($(WOLFHSM_CLIENT),1)
ifeq ($(WOLFHSM_MICROCHIP_PIC32CZ),)
$(error WOLFHSM_MICROCHIP_PIC32CZ is not set: point it at the wolfHSM \
PIC32CZ client port directory (the one containing port/))
endif
CFLAGS+=-I$(WOLFHSM_MICROCHIP_PIC32CZ) \
-DWOLFHSM_CFG_NO_SYS_TIME \
-DWOLFHSM_CFG_TRANSPORT_MEM \
-DWOLFHSM_CFG_CLIENT_ID=WOLFBOOT_WOLFHSM_CLIENT_ID \
-DPIC32CZ_CFG_SHARED_MEM_PHYS_BASE=0x20100000U \
-DPIC32CZ_CFG_SHARED_MEM_REQ_SIZE=4096 \
-DPIC32CZ_CFG_SHARED_MEM_RESP_SIZE=4096 \
'-DPIC32CZ_CFG_SHARED_MEM_TOTAL_SIZE=(PIC32CZ_CFG_SHARED_MEM_REQ_SIZE+PIC32CZ_CFG_SHARED_MEM_RESP_SIZE)'
OBJS+=$(WOLFHSM_MICROCHIP_PIC32CZ)/port/mailbox.o \
$(WOLFHSM_MICROCHIP_PIC32CZ)/port/client/czhsm_client.o \
$(WOLFHSM_MICROCHIP_PIC32CZ)/port/client/fwmetadata.o \
$(WOLFBOOT_LIB_WOLFHSM)/src/wh_transport_mem.o
# HSM server firmware. Optional: set HSM_FW_BIN to have wolfBoot load/boot
# the HSM core from it at boot; leave it unset when the firmware is already
# resident and wolfBoot must not touch it.
ifneq ($(HSM_FW_BIN),)
ifeq ($(wildcard $(HSM_FW_BIN)),)
$(error HSM_FW_BIN=$(HSM_FW_BIN) not found: build the wolfHSM server \
firmware before wolfBoot)
endif
HSM_FW_ADDR?=0x0c1c0000
HSM_FW_SIZE:=$(shell stat -c %s $(HSM_FW_BIN))
CFLAGS+=-DHSM_FW_ADDR=$(HSM_FW_ADDR) -DHSM_FW_SIZE=$(HSM_FW_SIZE)
endif
# Signing pubkey header. Optional: set HSM_PUBKEY_HEADER to have the HAL
# push the key into the HSM at boot; leave it unset when pre-provisioned.
ifneq ($(HSM_PUBKEY_HEADER),)
CFLAGS+=-DHSM_PUBKEY_HEADER='"$(HSM_PUBKEY_HEADER)"'
endif
endif
endif
ifeq ($(TARGET),pic32ck)

View File

@ -0,0 +1,30 @@
ARCH=ARM
TARGET=pic32cz
SIGN?=ECC256
HASH?=SHA256
VTOR?=1
SPMATH?=1
NO_MPU=1
DEBUG_UART=1
WOLFHSM_CLIENT=1
# Must match the client ID the server provisions keys under.
WOLFHSM_CLIENT_ID=12
WOLFHSM_NVM_IMAGE=0
# MUST equal the czhsm-server build's WOLFHSM_CFG_COMM_DATA_LEN
WOLFHSM_CFG_COMM_DATA_LEN=1280
BOOTLOADER_PARTITION_SIZE=0x10000
HSM_FW_ADDR=0x0c1c0000
WOLFBOOT_SECTOR_SIZE=0x1000
WOLFBOOT_PARTITION_SIZE=0x100000
WOLFBOOT_PARTITION_BOOT_ADDRESS=0x0c000000
WOLFBOOT_PARTITION_UPDATE_ADDRESS=0x0c200000
WOLFBOOT_PARTITION_SWAP_ADDRESS=0x0c1ff000
DUALBANK_SWAP=1

View File

@ -5193,6 +5193,21 @@ The test behavior depends on whether the `DUALBANK_SWAP` feature is enabled:
- **If `DUALBANK_SWAP=1`:** The higher version of the application will be automatically selected, and LED1 will turn on.
- **If `DUALBANK_SWAP=0`:** The application version 1 will boot first. The application will trigger the update and light LED0. On the next reset, wolfBoot will update the application, boot application version 2, and turn on LED1.
### PIC32CZ with wolfHSM
On Microchip PIC32CZ CA9x devices, wolfBoot can run on the Cortex-M7 host core as
a [wolfHSM](https://www.wolfssl.com/products/wolfhsm/) client, offloading the
image digest and signature verification to the wolfHSM server running on the
Cortex-M0+ HSM core.
Currently, wolfBoot as wolfHSM for PIC32CZ is distributed as part of the wolfHSM
PIC32CZ platform release bundle, not as a standalone package. This bundle is
under NDA and is not publicly available.
For access to the PIC32CZ platform release or for more information on using
wolfBoot and wolfHSM on PIC32CZ devices, contact
[facts@wolfssl.com](mailto:facts@wolfssl.com).
## Microchip SAME51

View File

@ -21,6 +21,7 @@ wolfBoot supports using wolfHSM on the following platforms:
- wolfBoot simulator (using wolfHSM POSIX TCP transport)
- AURIX TC3xx (shared memory transport)
- Microchip PIC32CZ CA9x (shared memory transport)
- STM32H5 TrustZone (the secure-side wolfBoot hosts a wolfHSM server and exposes it to the non-secure application through a single NSC veneer; see [STM32H5 TrustZone Engine](#stm32h5-trustzone-engine) below)
Details on configuring wolfBoot to use wolfHSM on each of these platforms can be found in the wolfBoot (and wolfHSM) documentation specific to that target, with the exception of the simulator, which is documented here. The remainder of this document focuses on the generic wolfHSM-related configuration options.

View File

@ -25,6 +25,41 @@
#include <hal/pic32c.h>
#ifdef DEBUG_UART
#include "uart_drv.h"
#endif
#if defined(WOLFBOOT_ENABLE_WOLFHSM_CLIENT)
/* wolfHSM */
#include "wolfhsm/wh_error.h"
#include "wolfhsm/wh_client.h"
#include "wolfhsm/wh_client_crypto.h"
#include "wolfhsm/wh_transport_mem.h"
/* PIC32CZ wolfHSM port (shared with the czhsm-client reference application) */
#include "port/client/czhsm_client.h"
#include "port/client/fwmetadata.h"
/* PIC32CZ_CFG_* (shared-memory window) comes from arch.mk, not a header: unlike
* the czhsm-client application, wolfBoot does not define WOLFHSM_CFG, so the
* port sources skip "pic32cz_cfg.h" and take the configuration on the command
* line instead. */
/* FCW_BASE */
#include "hal/pic32cz_registers.h"
#include <wolfssl/wolfcrypt/ecc.h>
#include <wolfssl/wolfcrypt/asn.h>
#ifndef WOLFBOOT_WOLFHSM_CLIENT_ID
#error "WOLFBOOT_WOLFHSM_CLIENT_ID is not defined. Set WOLFHSM_CLIENT_ID in your .config."
#endif
#define FCW_MUTEX (*(volatile uint32_t*)(FCW_BASE + 0x08U))
#define FCW_MUTEX_RELEASE (0x2)
#endif /* WOLFBOOT_ENABLE_WOLFHSM_CLIENT */
#define SUPC_BASE (0x44020000U)
#define SUPC_VREGCTRL (*(volatile uint32_t *)(SUPC_BASE + 0x1CU))
#define SUPC_STATUS (*(volatile uint32_t *)(SUPC_BASE + 0x0CU))
@ -88,6 +123,12 @@ void hal_init(void)
pic32_clock_pll0_init(12, 225, 1, 3);
pic32_clock_gclk_gen0(2, 1);
pic32_delay_cnt(700);
#ifdef DEBUG_UART
/* Must follow the PLL setup above: the SERCOM baud divisor assumes
* GCLK1 = PLL0/2 = 150 MHz. */
uart_init(115200, 8, 'N', 1);
uart_write("wolfBoot PIC32CZ\r\n", 18);
#endif
#if defined(TEST_FLASH)
pic32_flash_test();
while (1) {}
@ -106,3 +147,101 @@ void hal_prepare_boot(void)
pic32_clock_reset();
#endif
}
#if defined(WOLFBOOT_ENABLE_WOLFHSM_CLIENT)
/* wolfBoot's signing public key, DER encoded, as a 'hsm_pubkey_der[]' C array.
*/
#ifdef HSM_PUBKEY_HEADER
#include HSM_PUBKEY_HEADER
#endif
whClientContext hsmClientCtx = {0};
const int hsmDevIdHash = WH_DEV_ID;
const int hsmDevIdPubKey = WH_DEV_ID;
/* Key slot the verification key lives at on the server; image.c references it by
* this id (wh_Client_EccSetKeyId(hsmKeyIdPubKey)). wolfBoot either pushes the key
* here at boot (HSM_PUBKEY_HEADER) or relies on it being pre-provisioned. */
const int hsmKeyIdPubKey = 0xFF;
/*
* Boot the HSM core and connect to the wolfHSM server running on it.
*/
int hal_hsm_init_connect(void)
{
int rc;
whClientConfig cfg;
memset((void*)PIC32CZ_CFG_SHARED_MEM_PHYS_BASE, 0,
PIC32CZ_CFG_SHARED_MEM_TOTAL_SIZE);
FCW_MUTEX = FCW_MUTEX_RELEASE;
#if defined(HSM_FW_ADDR) && defined(HSM_FW_SIZE)
/* Load/boot the HSM core from the firmware image; blocks until it has booted
* and acknowledged. Skipped when HSM_FW_BIN is unset: the firmware is then
* assumed already resident and wolfBoot connects to the running server. */
rc = czhsm_load_hsm_firmware(HSM_FW_ADDR, HSM_FW_SIZE);
if (rc != 0) {
return rc;
}
#endif
rc = czhsm_setup(&cfg);
if (rc != 0) {
return rc;
}
rc = wh_Client_Init(&hsmClientCtx, &cfg);
if (rc != WH_ERROR_OK) {
return rc;
}
rc = wh_Client_CommInit(&hsmClientCtx, NULL, NULL);
if (rc != WH_ERROR_OK) {
return rc;
}
#ifdef HSM_PUBKEY_HEADER
{
ecc_key pubkey;
whKeyId keyId = (whKeyId)hsmKeyIdPubKey;
word32 idx = 0;
rc = wc_ecc_init_ex(&pubkey, NULL, INVALID_DEVID);
if (rc != 0) {
return rc;
}
rc = wc_EccPublicKeyDecode(hsm_pubkey_der, &idx, &pubkey,
(word32)sizeof(hsm_pubkey_der));
if (rc == 0) {
rc = wh_Client_EccImportKey(&hsmClientCtx, &pubkey, &keyId,
WH_NVM_FLAGS_USAGE_VERIFY, 0, NULL);
}
wc_ecc_free(&pubkey);
}
#endif /* HSM_PUBKEY_HEADER */
return rc;
}
/*
* Close the wolfHSM session before handing control to the application.
*/
int hal_hsm_disconnect(void)
{
int rc;
rc = wh_Client_CommClose(&hsmClientCtx);
if (rc != WH_ERROR_OK) {
return rc;
}
return wh_Client_Cleanup(&hsmClientCtx);
}
#endif /* WOLFBOOT_ENABLE_WOLFHSM_CLIENT */

View File

@ -0,0 +1,168 @@
/* uart_drv_pic32cz.c
*
* Driver for the PIC32CZ SERCOM UART, used for wolfBoot debug output.
*
* Uses SERCOM1 on the host (Cortex-M7) core: TX on PC4 (EXT1 pin 14),
* RX on PC7. 115200 8N1, transmit only.
*
* Derived from the reference driver in the wolfHSM PIC32CZ port
* (czhsm-client/uart.c), which is in turn based on the plib driver generated
* by MPLAB.
*
* Copyright (C) 2026 wolfSSL Inc.
*
* This file is part of wolfBoot.
*
* wolfBoot is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* wolfBoot is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA
*/
#ifdef TARGET_pic32cz
#include <stdint.h>
#include <stddef.h>
#define GCLK_BASE (0x44050000U)
#define SERCOM_BASE (0x46000000U)
#define PORT_BASE (0x44840000U)
/* SERCOM1 */
#define SERCOM_OFF (0x2000U)
#define SERCOM1 (SERCOM_BASE + SERCOM_OFF)
#define SERCOM_PCHCTRL (22) /* GCLK peripheral channel for SERCOM1 */
#define SERCOM_CTRLA (*(volatile uint32_t*)(SERCOM1 + 0x00U))
#define SERCOM_CTRLB (*(volatile uint32_t*)(SERCOM1 + 0x04U))
#define SERCOM_BAUD (*(volatile uint16_t*)(SERCOM1 + 0x0CU))
#define SERCOM_INTFLAG (*(volatile uint8_t*)(SERCOM1 + 0x18U))
#define SERCOM_SYNCBUSY (*(volatile uint32_t*)(SERCOM1 + 0x1CU))
#define SERCOM_DATA (*(volatile uint32_t*)(SERCOM1 + 0x28U))
#define CTRLA_MODE_USART_INT (0x1U << 2) /* USART with internal clock */
#define CTRLA_ENABLE (0x1U << 1)
#define CTRLA_IBON (0x1U << 8)
#define CTRLA_DORD (0x1U << 30) /* LSB first */
#define CTRLB_CHSIZE_8BIT (0x0U << 0)
#define CTRLB_SBMODE_1BIT (0x0U << 6)
#define CTRLB_TXEN (0x1U << 16)
#define INTFLAG_DRE (0x1U << 0) /* Data Register Empty */
/* GCLK: generator 1 sources SERCOM1 */
#define GCLK_GENCTRL1 (*(volatile uint32_t*)(GCLK_BASE + 0x20U + (1 * 4)))
#define GCLK_SYNCBUSY (*(volatile uint32_t*)(GCLK_BASE + 0x04U))
#define GCLK_PCHCTRL(n) (*(volatile uint32_t*)(GCLK_BASE + 0x80U + ((n) * 4)))
#define GCLK_GENCTRL_SRC_PLL0 (6U << 0)
#define GCLK_GENCTRL_DIV(x) (((uint32_t)(x) & 0xFFFFU) << 16)
#define GCLK_GENCTRL_GENEN (0x1U << 8)
#define GCLK_SYNCBUSY_GENCTRL1 (0x1U << 3)
#define GCLK_PCHCTRL_GEN1 (0x1U << 0)
#define GCLK_PCHCTRL_CHEN (0x1U << 6)
/* PORT group 2 == port C */
#define PORT_GROUP_SIZE (0x80U)
#define PORTC (PORT_BASE + (2 * PORT_GROUP_SIZE))
#define PORTC_PMUX(n) (*(volatile uint8_t*)(PORTC + 0x30U + (n)))
#define PORTC_PINCFG(n) (*(volatile uint8_t*)(PORTC + 0x40U + (n)))
#define PINCFG_PMUXEN (0x1U)
/* Baud register value for 115200 with GCLK1 = PLL0/2 (150 MHz), 16x sampling.
*
* hal_init() programs PLL0 to 300 MHz before uart_init() runs, and GCLK
* generator 1 below divides it by 2, so the source clock is fixed regardless of
* what the caller passes. wolfBoot only ever asks for 115200, so rather than
* divide at runtime we use the same constant the reference driver does. */
#define BAUD_115200 (64730U)
static void pins_setup(void)
{
/* PC4 = SERCOM1 TX, PC7 = SERCOM1 RX; both on peripheral function D. */
PORTC_PINCFG(4) = PINCFG_PMUXEN;
PORTC_PINCFG(7) = PINCFG_PMUXEN;
PORTC_PMUX(2) = 0x3U; /* PC4 -> even nibble */
PORTC_PMUX(3) = 0x30U; /* PC7 -> odd nibble */
}
static void clock_setup(void)
{
GCLK_GENCTRL1 =
GCLK_GENCTRL_DIV(2U) | GCLK_GENCTRL_SRC_PLL0 | GCLK_GENCTRL_GENEN;
while ((GCLK_SYNCBUSY & GCLK_SYNCBUSY_GENCTRL1) != 0U) {
/* wait for generator 1 to synchronise */
}
GCLK_PCHCTRL(SERCOM_PCHCTRL) = GCLK_PCHCTRL_GEN1 | GCLK_PCHCTRL_CHEN;
while ((GCLK_PCHCTRL(SERCOM_PCHCTRL) & GCLK_PCHCTRL_CHEN) == 0U) {
/* wait for the peripheral channel to be enabled */
}
}
int uart_init(uint32_t bitrate, uint8_t data, char parity, uint8_t stop)
{
/* Fixed 115200 8N1: see BAUD_115200. */
(void)bitrate;
(void)data;
(void)parity;
(void)stop;
pins_setup();
clock_setup();
SERCOM_CTRLA = CTRLA_MODE_USART_INT | CTRLA_DORD | CTRLA_IBON;
SERCOM_BAUD = (uint16_t)BAUD_115200;
/* Transmit only: nothing on this port ever reads from the console. */
SERCOM_CTRLB = CTRLB_CHSIZE_8BIT | CTRLB_SBMODE_1BIT | CTRLB_TXEN;
while (SERCOM_SYNCBUSY != 0U) {
/* wait */
}
SERCOM_CTRLA |= CTRLA_ENABLE;
while (SERCOM_SYNCBUSY != 0U) {
/* wait */
}
return 0;
}
int uart_tx(const uint8_t c)
{
while ((SERCOM_INTFLAG & INTFLAG_DRE) == 0U) {
/* wait for the data register to drain */
}
SERCOM_DATA = c;
return 1;
}
int uart_rx(uint8_t* c)
{
/* RX is not enabled on this port: CTRLB sets TXEN only. */
(void)c;
return 0;
}
#ifdef DEBUG_UART
void uart_write(const char* buf, unsigned int len)
{
while (len--) {
uart_tx((uint8_t)*buf);
buf++;
}
}
#endif
#endif /* TARGET_pic32cz */

View File

@ -1083,6 +1083,9 @@ endif
ifeq ($(TARGET), pic32cz)
APP_OBJS+=../hal/pic32c.o
ifeq ($(DEBUG_UART),1)
APP_OBJS+=../hal/uart/uart_drv_pic32cz.o
endif
endif
CFLAGS+=-I"$(WOLFBOOT_LIB_WOLFSSL)"

View File

@ -0,0 +1,60 @@
#!/bin/sh
#
# der2c.sh <file.der> <symbol> - emit a DER blob as a C array on stdout.
#
# Used to embed wolfBoot's exported signing public key into a HAL that must
# hand it to an HSM at boot
#
# Copyright (C) 2026 wolfSSL Inc.
#
# This file is part of wolfBoot.
#
# wolfBoot is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
#
# wolfBoot is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA
set -eu
if [ $# -ne 2 ]; then
echo "usage: $0 <file.der> <symbol>" >&2
exit 1
fi
DER="$1"
SYM="$2"
if [ ! -r "$DER" ]; then
echo "$0: cannot read $DER" >&2
exit 1
fi
echo "/* Generated from $(basename "$DER") by tools/scripts/der2c.sh. Do not edit. */"
echo "#ifndef WOLFBOOT_GEN_$(echo "$SYM" | tr '[:lower:]' '[:upper:]')_H"
echo "#define WOLFBOOT_GEN_$(echo "$SYM" | tr '[:lower:]' '[:upper:]')_H"
echo ""
echo "#include <stdint.h>"
echo ""
echo "static const uint8_t ${SYM}[] = {"
od -An -v -tx1 "$DER" | awk '
{
for (i = 1; i <= NF; i++) {
if (n % 12 == 0) printf " "
printf " 0x%s,", $i
n++
if (n % 12 == 0) printf "\n"
}
}
END { if (n % 12 != 0) printf "\n" }'
echo "};"
echo ""
echo "#endif"