drivers: otp: add stm32 BSEC driver

Introduce the Boot and SECurity(BSEC) control driver. The BSEC
peripheral manages the accesses to an embedded one time
programmable(OTP) array of fuses. Those fuses are used to store
on-chip, non-volatile data like boot and security parameters (e.g:
secret keys, non-volatile counters, etc...).

Signed-off-by: Gatien Chevallier <gatien.chevallier@foss.st.com>
This commit is contained in:
Gatien Chevallier
2025-10-15 15:21:08 +02:00
committed by Fabio Baltieri
parent fc7fae6090
commit ff864b3cd3
5 changed files with 236 additions and 0 deletions

View File

@@ -3,3 +3,4 @@
zephyr_library()
zephyr_library_sources_ifdef(CONFIG_OTP_EMULATOR otp_emulator.c)
zephyr_library_sources_ifdef(CONFIG_OTP_STM32_BSEC otp_bsec_stm32.c)

View File

@@ -19,6 +19,7 @@ module-str = otp
source "subsys/logging/Kconfig.template.log_config"
source "drivers/otp/Kconfig.emu"
source "drivers/otp/Kconfig.stm32"
config OTP_INIT_PRIORITY
int "OTP init priority"

10
drivers/otp/Kconfig.stm32 Normal file
View File

@@ -0,0 +1,10 @@
# Copyright (c) 2026 STMicroelectronics
# SPDX-License-Identifier: Apache-2.0
config OTP_STM32_BSEC
bool "STM32 BSEC driver"
default y
select USE_STM32_HAL_BSEC
depends on DT_HAS_ST_STM32_BSEC_ENABLED
help
Enable OTP fuse access for STM32 SoCs embedding a BSEC peripheral.

View File

@@ -0,0 +1,165 @@
/*
* Copyright (c) 2026 STMicroelectronics
* SPDX-License-Identifier: Apache-2.0
*/
#include <soc.h>
#include <zephyr/device.h>
#include <zephyr/drivers/otp.h>
#include <zephyr/kernel.h>
#include <zephyr/logging/log.h>
#include <zephyr/toolchain.h>
#define DT_DRV_COMPAT st_stm32_bsec
LOG_MODULE_REGISTER(otp_bsec_stm32);
#define BSEC_WORD_SIZE 4
static K_MUTEX_DEFINE(lock);
struct bsec_stm32_config {
BSEC_TypeDef *base;
unsigned int upper_fuse_limit;
};
static int otp_bsec_stm32_check_accessible(BSEC_HandleTypeDef *handle,
const struct bsec_stm32_config *config, off_t offset,
unsigned int nb_fuse)
{
uint32_t fuse_idx = offset / BSEC_WORD_SIZE;
HAL_StatusTypeDef hal_ret;
uint32_t bsec_state = 0;
if (nb_fuse == 0) {
return -EINVAL;
}
hal_ret = HAL_BSEC_GetDeviceLifeCycleState(handle, &bsec_state);
if (hal_ret != HAL_OK) {
return -EACCES;
}
/* Upper fuses are only accessible when the BSEC is in closed locked state */
if (((fuse_idx + nb_fuse) > config->upper_fuse_limit) &&
(bsec_state != HAL_BSEC_CLOSED_STATE)) {
return -EACCES;
}
return 0;
}
#if defined(CONFIG_OTP_PROGRAM)
static int otp_bsec_stm32_program(const struct device *dev, off_t offset, const void *buf,
size_t len)
{
const struct bsec_stm32_config *config = dev->config;
BSEC_HandleTypeDef handle = { .Instance = config->base };
HAL_StatusTypeDef hal_ret;
unsigned int nb_fuse;
unsigned int i;
int ret;
/* Allow programming of 4bytes words only */
if (!IS_ALIGNED(len, BSEC_WORD_SIZE)) {
LOG_ERR("Invalid length to program OTP: %zu", len);
return -EINVAL;
}
/* Allow programming only at the beginning of a new word */
if (!IS_ALIGNED(offset, BSEC_WORD_SIZE)) {
LOG_ERR("Programmed data not aligned on an OTP word");
return -EINVAL;
}
nb_fuse = len / BSEC_WORD_SIZE;
ret = otp_bsec_stm32_check_accessible(&handle, config, offset, nb_fuse);
if (ret != 0) {
return ret;
}
k_mutex_lock(&lock, K_FOREVER);
for (i = 0; i < nb_fuse; i++) {
uint32_t prog_data = 0;
LOG_DBG("Programming Fuse %lu", (offset / BSEC_WORD_SIZE) + i);
prog_data = UNALIGNED_GET((uint32_t *)((uint8_t *)buf + i * BSEC_WORD_SIZE));
hal_ret = HAL_BSEC_OTP_Program(&handle, (offset / BSEC_WORD_SIZE) + i, prog_data,
0);
if (hal_ret != HAL_OK) {
k_mutex_unlock(&lock);
return -EACCES;
}
}
k_mutex_unlock(&lock);
return 0;
}
#endif /* CONFIG_OTP_PROGRAM */
static int otp_bsec_stm32_read(const struct device *dev, off_t offset, void *buf, size_t len)
{
const struct bsec_stm32_config *config = dev->config;
BSEC_HandleTypeDef handle = { .Instance = config->base };
uint8_t *dest = (uint8_t *)buf;
HAL_StatusTypeDef hal_ret;
size_t bytes_left = len;
unsigned int nb_fuse;
unsigned int i;
int ret;
/* Allow intra-word and spanned reads but not 0-sized reads */
nb_fuse = len != 0 ? DIV_ROUND_UP(offset % BSEC_WORD_SIZE + len, BSEC_WORD_SIZE) : 0;
ret = otp_bsec_stm32_check_accessible(&handle, config, offset, nb_fuse);
if (ret != 0) {
return ret;
}
k_mutex_lock(&lock, K_FOREVER);
for (i = 0; i < nb_fuse; i++) {
size_t first_offset = (i == 0) ? offset % BSEC_WORD_SIZE : 0;
size_t read_sz = MIN(BSEC_WORD_SIZE - first_offset, bytes_left);
uint32_t fuse_data = 0;
LOG_DBG("Reading Fuse %lu", (offset / BSEC_WORD_SIZE) + i);
hal_ret = HAL_BSEC_OTP_Read(&handle, (offset / BSEC_WORD_SIZE) + i, &fuse_data);
if (hal_ret != HAL_OK) {
k_mutex_unlock(&lock);
return -EACCES;
}
memcpy(dest, ((uint8_t *)&fuse_data) + first_offset, read_sz);
dest += read_sz;
bytes_left -= read_sz;
if (bytes_left == 0) {
break;
}
}
k_mutex_unlock(&lock);
return 0;
}
static const struct bsec_stm32_config bsec_config = {
.base = (void *)DT_INST_REG_ADDR(0),
.upper_fuse_limit = DT_INST_PROP(0, st_upper_fuse_limit),
};
static DEVICE_API(otp, otp_bsec_stm32_api) = {
#if defined(CONFIG_OTP_PROGRAM)
.program = otp_bsec_stm32_program,
#endif
.read = otp_bsec_stm32_read,
};
DEVICE_DT_INST_DEFINE(0, NULL, NULL, NULL, &bsec_config, PRE_KERNEL_1,
CONFIG_OTP_INIT_PRIORITY, &otp_bsec_stm32_api);

View File

@@ -0,0 +1,59 @@
# Copyright (c) 2026 STMicroelectronics
# SPDX-License-Identifier: Apache-2.0
title: STM32 Boot and security control (BSEC)
description: |
The boot and security control (BSEC) peripheral manages the accesses to an
embedded one time programmable (OTP) array of fuses. Those fuses are used to
store on-chip, non-volatile data like boot and security parameters.
Embedded non-volatile secrets are stored in BSEC upper area that is only
accessible while BSEC is operating in its BSEC-closed state. When the BSEC
state is BSEC-open, those non-volatile secrets are permanently hidden.
Configuration example at SoC level:
bsec: efuse@56009000 {
compatible = "st,stm32-bsec";
reg = <0x56009000 0x1000>;
st,upper-fuse-limit = <256>;
nvmem-layout {
compatible = "fixed-layout";
#address-cells = <1>;
#size-cells = <1>;
bootrom_cfg1: bootrom-cfg1@20 {
reg = <0x20 0x4>;
#nvmem-cell-cells = <0>;
};
mac_address0: mac-address@2a4 {
reg = <0x2a4 0x6>;
#nvmem-cell-cells = <0>;
};
};
};
Configuration example at board level:
&bsec {
nvmem-layout {
mac_address2: mac-address@2fc {
reg = <0x2fc 0x6>;
#nvmem-cell-cells = <0>;
};
};
};
compatible: "st,stm32-bsec"
include: base.yaml
properties:
st,upper-fuse-limit:
type: int
required: true
description: |
Start fuse (32bit OTP word) index of the upper fuse region of the BSEC.
This region requires a particular SoC state to be accessed.