From d615ebfcb5e398f7005cc3eaeb7dc3fbacc5183a Mon Sep 17 00:00:00 2001 From: Alex Lanzano Date: Mon, 16 Feb 2026 11:36:29 -0500 Subject: [PATCH] [stm32wb_rng, rng] Implement RNG device type. Implement RNG driver and test for stm32wb --- examples/stm32wb/stm32wb55xx_nucleo.c | 9 ++ examples/stm32wb/stm32wb55xx_nucleo.h | 3 + src/clock/stm32wb_rcc.c | 27 ++++++ src/rng/rng.c | 30 +++++++ src/rng/stm32wb_rng.c | 124 ++++++++++++++++++++++++++ tests/sim/Makefile | 1 + tests/sim/test_dispatch.c | 39 ++++++++ tests/sim/test_sim | Bin 107696 -> 111280 bytes tests/stm32wb/Makefile | 2 +- tests/stm32wb/test_main.c | 2 + tests/stm32wb/test_rng.c | 68 ++++++++++++++ wolfHAL/clock/stm32wb_rcc.h | 10 +++ wolfHAL/error.h | 2 + wolfHAL/platform/st/stm32wb55xx.h | 12 +++ wolfHAL/rng/rng.h | 82 +++++++++++++++++ wolfHAL/rng/stm32wb_rng.h | 60 +++++++++++++ wolfHAL/wolfHAL.h | 1 + 17 files changed, 471 insertions(+), 1 deletion(-) create mode 100644 src/rng/rng.c create mode 100644 src/rng/stm32wb_rng.c create mode 100644 tests/stm32wb/test_rng.c create mode 100644 wolfHAL/rng/rng.h create mode 100644 wolfHAL/rng/stm32wb_rng.h diff --git a/examples/stm32wb/stm32wb55xx_nucleo.c b/examples/stm32wb/stm32wb55xx_nucleo.c index 7688f4f..9f9bef7 100644 --- a/examples/stm32wb/stm32wb55xx_nucleo.c +++ b/examples/stm32wb/stm32wb55xx_nucleo.c @@ -146,3 +146,12 @@ whal_Spi g_whalSpi = { .clk = &(whal_Stm32wbRcc_Clk) {WHAL_STM32WB55_SPI1_CLOCK}, }, }; + +whal_Rng g_whalRng = { + WHAL_STM32WB55_RNG_DEVICE, + + .cfg = &(whal_Stm32wbRng_Cfg) { + .clkCtrl = &g_whalClock, + .clk = &(whal_Stm32wbRcc_Clk) {WHAL_STM32WB55_RNG_CLOCK}, + }, +}; diff --git a/examples/stm32wb/stm32wb55xx_nucleo.h b/examples/stm32wb/stm32wb55xx_nucleo.h index 4a3e1d9..34c393b 100644 --- a/examples/stm32wb/stm32wb55xx_nucleo.h +++ b/examples/stm32wb/stm32wb55xx_nucleo.h @@ -37,4 +37,7 @@ extern whal_Flash g_whalFlash; /* SPI controller instance. */ extern whal_Spi g_whalSpi; +/* RNG instance. */ +extern whal_Rng g_whalRng; + #endif /* STM32WB55XX_NUCLEO_H */ diff --git a/src/clock/stm32wb_rcc.c b/src/clock/stm32wb_rcc.c index 38eae7f..de33c34 100644 --- a/src/clock/stm32wb_rcc.c +++ b/src/clock/stm32wb_rcc.c @@ -18,6 +18,7 @@ /* Clock Control Register - oscillator enables and status */ #define ST_RCC_CR_REG 0x000 #define ST_RCC_CR_MSIRANGE WHAL_MASK_RANGE(7, 4) /* MSI frequency range */ +#define ST_RCC_CR_HSION_MASK WHAL_MASK(8) /* HSI enable */ #define ST_RCC_CR_PLLON_MASK WHAL_MASK(24) /* PLL enable */ /* Clock Configuration Register - clock source and prescaler selection */ @@ -73,6 +74,11 @@ #define ST_RCC_APB1ENR2_LPUART1EN WHAL_MASK(0) /* LPUART1 clock enable */ #define ST_RCC_APB1ENR2_LPTIM2EN WHAL_MASK(5) /* LPTIM2 clock enable */ +/* Clock Recovery RC Register - HSI48 oscillator control */ +#define ST_RCC_CRRCR_REG 0x098 +#define ST_RCC_CRRCR_HSI48ON_MASK WHAL_MASK(0) /* HSI48 oscillator enable */ +#define ST_RCC_CRRCR_HSI48RDY_MASK WHAL_MASK(1) /* HSI48 oscillator ready */ + whal_Error whal_Stm32wbRccPll_Init(whal_Clock *clkDev) { whal_Error err; @@ -303,6 +309,27 @@ whal_Error whal_Stm32wbRccMsi_GetRate(whal_Clock *clkDev, size_t *rateOut) return WHAL_SUCCESS; } + +whal_Error whal_Stm32wbRcc_Ext_EnableHsi48(whal_Clock *clkDev, uint8_t enable) +{ + if (!clkDev) { + return WHAL_EINVAL; + } + + whal_Reg_Update(clkDev->regmap.base, ST_RCC_CRRCR_REG, ST_RCC_CRRCR_HSI48ON_MASK, + whal_SetBits(ST_RCC_CRRCR_HSI48ON_MASK, enable)); + + if (enable) { + size_t rdy; + do { + whal_Reg_Get(clkDev->regmap.base, ST_RCC_CRRCR_REG, + ST_RCC_CRRCR_HSI48RDY_MASK, &rdy); + } while (!rdy); + } + + return WHAL_SUCCESS; +} + const whal_ClockDriver whal_Stm32wbRccPll_Driver = { .Init = whal_Stm32wbRccPll_Init, .Deinit = whal_Stm32wbRccPll_Deinit, diff --git a/src/rng/rng.c b/src/rng/rng.c new file mode 100644 index 0000000..65e46b5 --- /dev/null +++ b/src/rng/rng.c @@ -0,0 +1,30 @@ +#include +#include +#include + +inline whal_Error whal_Rng_Init(whal_Rng *rngDev) +{ + if (!rngDev || !rngDev->driver || !rngDev->driver->Init) { + return WHAL_EINVAL; + } + + return rngDev->driver->Init(rngDev); +} + +inline whal_Error whal_Rng_Deinit(whal_Rng *rngDev) +{ + if (!rngDev || !rngDev->driver || !rngDev->driver->Deinit) { + return WHAL_EINVAL; + } + + return rngDev->driver->Deinit(rngDev); +} + +inline whal_Error whal_Rng_Generate(whal_Rng *rngDev, uint8_t *rngData, size_t rngDataSz) +{ + if (!rngDev || !rngDev->driver || !rngDev->driver->Generate || !rngData) { + return WHAL_EINVAL; + } + + return rngDev->driver->Generate(rngDev, rngData, rngDataSz); +} diff --git a/src/rng/stm32wb_rng.c b/src/rng/stm32wb_rng.c new file mode 100644 index 0000000..265cedd --- /dev/null +++ b/src/rng/stm32wb_rng.c @@ -0,0 +1,124 @@ +#include +#include +#include +#include +#include +#include +#include + +/* + * STM32WB RNG Register Definitions + * + * The RNG peripheral uses an analog noise source to produce 32-bit + * random values. One value is available at a time in DR, signaled + * by the DRDY flag in SR. + */ + +/* Control Register */ +#define SRNG_CR_REG 0x00 +#define SRNG_CR_RNGEN WHAL_MASK(2) /* RNG enable */ +#define SRNG_CR_CED WHAL_MASK(5) /* Clock error detection disable */ + +/* Status Register */ +#define SRNG_SR_REG 0x04 +#define SRNG_SR_DRDY WHAL_MASK(0) /* Data ready */ +#define SRNG_SR_CECS WHAL_MASK(1) /* Clock error current status */ +#define SRNG_SR_SECS WHAL_MASK(2) /* Seed error current status */ +#define SRNG_SR_CEIS WHAL_MASK(5) /* Clock error interrupt status */ +#define SRNG_SR_SEIS WHAL_MASK(6) /* Seed error interrupt status */ + +/* Data Register - 32-bit random value */ +#define SRNG_DR_REG 0x08 + +whal_Error whal_Stm32wbRng_Init(whal_Rng *rngDev) +{ + whal_Error err; + whal_Stm32wbRng_Cfg *cfg; + + if (!rngDev || !rngDev->cfg) { + return WHAL_EINVAL; + } + + cfg = (whal_Stm32wbRng_Cfg *)rngDev->cfg; + + err = whal_Clock_Enable(cfg->clkCtrl, cfg->clk); + if (err != WHAL_SUCCESS) { + return err; + } + + return WHAL_SUCCESS; +} + +whal_Error whal_Stm32wbRng_Deinit(whal_Rng *rngDev) +{ + whal_Error err; + + if (!rngDev || !rngDev->cfg) { + return WHAL_EINVAL; + } + + whal_Stm32wbRng_Cfg *cfg = (whal_Stm32wbRng_Cfg *)rngDev->cfg; + + err = whal_Clock_Disable(cfg->clkCtrl, cfg->clk); + if (err) { + return err; + } + + return WHAL_SUCCESS; +} + +whal_Error whal_Stm32wbRng_Generate(whal_Rng *rngDev, uint8_t *rngData, size_t rngDataSz) +{ + if (!rngDev || !rngData) { + return WHAL_EINVAL; + } + + whal_Error err = WHAL_SUCCESS; + const whal_Regmap *reg = &rngDev->regmap; + size_t status; + size_t offset = 0; + + /* Enable the RNG peripheral */ + whal_Reg_Update(reg->base, SRNG_CR_REG, SRNG_CR_RNGEN, + whal_SetBits(SRNG_CR_RNGEN, 1)); + + while (offset < rngDataSz) { + /* Wait for a random value to be ready */ + do { + /* Check for seed or clock error */ + whal_Reg_Get(reg->base, SRNG_SR_REG, SRNG_SR_SECS, &status); + if (status) { + err = WHAL_EHARDWARE; + goto exit; + } + whal_Reg_Get(reg->base, SRNG_SR_REG, SRNG_SR_CECS, &status); + if (status) { + err = WHAL_EHARDWARE; + goto exit; + } + + whal_Reg_Get(reg->base, SRNG_SR_REG, SRNG_SR_DRDY, &status); + } while (!status); + + /* Read 32-bit random value */ + uint32_t rnd = *(volatile uint32_t *)(reg->base + SRNG_DR_REG); + + /* Copy bytes into output buffer */ + for (size_t i = 0; i < 4 && offset < rngDataSz; i++, offset++) { + rngData[offset] = (uint8_t)(rnd >> (i * 8)); + } + } + +exit: + /* Disable the RNG peripheral */ + whal_Reg_Update(reg->base, SRNG_CR_REG, SRNG_CR_RNGEN, + whal_SetBits(SRNG_CR_RNGEN, 0)); + + return err; +} + +const whal_RngDriver whal_Stm32wbRng_Driver = { + .Init = whal_Stm32wbRng_Init, + .Deinit = whal_Stm32wbRng_Deinit, + .Generate = whal_Stm32wbRng_Generate, +}; diff --git a/tests/sim/Makefile b/tests/sim/Makefile index 90734f3..c9220ff 100644 --- a/tests/sim/Makefile +++ b/tests/sim/Makefile @@ -13,6 +13,7 @@ WHAL_SRC = $(WHAL_DIR)/src/clock/clock.c \ $(WHAL_DIR)/src/flash/flash.c \ $(WHAL_DIR)/src/timer/timer.c \ $(WHAL_DIR)/src/spi/spi.c \ + $(WHAL_DIR)/src/rng/rng.c \ $(WHAL_DIR)/src/supply/supply.c SOURCE = $(TEST_SRC) $(WHAL_SRC) diff --git a/tests/sim/test_dispatch.c b/tests/sim/test_dispatch.c index a0a3ad5..dc0fced 100644 --- a/tests/sim/test_dispatch.c +++ b/tests/sim/test_dispatch.c @@ -1,6 +1,7 @@ #include #include #include +#include #include "../test.h" /* @@ -78,6 +79,16 @@ static const whal_TimerDriver mockTimerDriver = { .Reset = mockTimerReset, }; +static whal_Error mockRngInit(whal_Rng *d) { (void)d; return WHAL_SUCCESS; } +static whal_Error mockRngDeinit(whal_Rng *d) { (void)d; return WHAL_SUCCESS; } +static whal_Error mockRngGenerate(whal_Rng *d, uint8_t *data, size_t sz) { (void)d; (void)data; (void)sz; return WHAL_SUCCESS; } + +static const whal_RngDriver mockRngDriver = { + .Init = mockRngInit, + .Deinit = mockRngDeinit, + .Generate = mockRngGenerate, +}; + /* --- Clock dispatch tests --- */ static void test_clock_null_dev(void) @@ -222,6 +233,31 @@ static void test_timer_valid_dispatch(void) WHAL_ASSERT_EQ(whal_Timer_Reset(&dev), WHAL_SUCCESS); } +/* --- RNG dispatch tests --- */ + +static void test_rng_null_dev(void) +{ + uint8_t buf[1]; + WHAL_ASSERT_EQ(whal_Rng_Init(NULL), WHAL_EINVAL); + WHAL_ASSERT_EQ(whal_Rng_Deinit(NULL), WHAL_EINVAL); + WHAL_ASSERT_EQ(whal_Rng_Generate(NULL, buf, 1), WHAL_EINVAL); +} + +static void test_rng_null_driver(void) +{ + whal_Rng dev = { .driver = NULL }; + WHAL_ASSERT_EQ(whal_Rng_Init(&dev), WHAL_EINVAL); +} + +static void test_rng_valid_dispatch(void) +{ + whal_Rng dev = { .driver = &mockRngDriver }; + WHAL_ASSERT_EQ(whal_Rng_Init(&dev), WHAL_SUCCESS); + uint8_t buf[4] = {0}; + WHAL_ASSERT_EQ(whal_Rng_Generate(&dev, buf, sizeof(buf)), WHAL_SUCCESS); + WHAL_ASSERT_EQ(whal_Rng_Deinit(&dev), WHAL_SUCCESS); +} + void test_dispatch(void) { WHAL_TEST_SUITE_START("dispatch"); @@ -241,5 +277,8 @@ void test_dispatch(void) WHAL_TEST(test_timer_null_dev); WHAL_TEST(test_timer_null_driver); WHAL_TEST(test_timer_valid_dispatch); + WHAL_TEST(test_rng_null_dev); + WHAL_TEST(test_rng_null_driver); + WHAL_TEST(test_rng_valid_dispatch); WHAL_TEST_SUITE_END(); } diff --git a/tests/sim/test_sim b/tests/sim/test_sim index 5a6d570217a00eed9e4cf56322b522daeb32b95d..b9b349a2160f6c7d0ae2f9edd36316fb3bc076e1 100755 GIT binary patch delta 25986 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 eG?O`qc7UWudI5 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 ObikhH{7>!^>-`^xaLN<_ diff --git a/tests/stm32wb/Makefile b/tests/stm32wb/Makefile index 8ec46a7..b6e8b21 100644 --- a/tests/stm32wb/Makefile +++ b/tests/stm32wb/Makefile @@ -10,7 +10,7 @@ LDFLAGS = --omagic -static DEPDIR = .deps/ # Test sources -TEST_SRC = test_main.c test_clock.c test_gpio.c test_flash.c test_timer.c +TEST_SRC = test_main.c test_clock.c test_gpio.c test_flash.c test_timer.c test_rng.c # Board config and IVT from the example BOARD_SRC = $(EXAMPLE_DIR)/stm32wb55xx_nucleo.c \ diff --git a/tests/stm32wb/test_main.c b/tests/stm32wb/test_main.c index c8ae895..f9d20d0 100644 --- a/tests/stm32wb/test_main.c +++ b/tests/stm32wb/test_main.c @@ -40,6 +40,7 @@ void test_clock(void); void test_gpio(void); void test_flash(void); void test_timer(void); +void test_rng(void); void main(void) { @@ -84,6 +85,7 @@ void main(void) test_gpio(); test_flash(); test_timer(); + test_rng(); WHAL_TEST_SUMMARY(); diff --git a/tests/stm32wb/test_rng.c b/tests/stm32wb/test_rng.c new file mode 100644 index 0000000..fed0a36 --- /dev/null +++ b/tests/stm32wb/test_rng.c @@ -0,0 +1,68 @@ +#include +#include +#include +#include +#include +#include "stm32wb55xx_nucleo.h" +#include "../test.h" + +static void test_rng_init_deinit(void) +{ + WHAL_ASSERT_EQ(whal_Rng_Init(&g_whalRng), WHAL_SUCCESS); + WHAL_ASSERT_EQ(whal_Rng_Deinit(&g_whalRng), WHAL_SUCCESS); +} + +static void test_rng_generate_nonzero(void) +{ + uint8_t buf[16] = {0}; + int allZero = 1; + + whal_Stm32wbRcc_Ext_EnableHsi48(&g_whalClock, 1); + WHAL_ASSERT_EQ(whal_Rng_Init(&g_whalRng), WHAL_SUCCESS); + WHAL_ASSERT_EQ(whal_Rng_Generate(&g_whalRng, buf, sizeof(buf)), WHAL_SUCCESS); + WHAL_ASSERT_EQ(whal_Rng_Deinit(&g_whalRng), WHAL_SUCCESS); + whal_Stm32wbRcc_Ext_EnableHsi48(&g_whalClock, 0); + + for (size_t i = 0; i < sizeof(buf); i++) { + if (buf[i] != 0) { + allZero = 0; + break; + } + } + + /* 16 zero bytes from a TRNG is astronomically unlikely */ + WHAL_ASSERT_EQ(allZero, 0); +} + +static void test_rng_generate_unique(void) +{ + uint8_t buf1[16] = {0}; + uint8_t buf2[16] = {0}; + int same = 1; + + whal_Stm32wbRcc_Ext_EnableHsi48(&g_whalClock, 1); + WHAL_ASSERT_EQ(whal_Rng_Init(&g_whalRng), WHAL_SUCCESS); + WHAL_ASSERT_EQ(whal_Rng_Generate(&g_whalRng, buf1, sizeof(buf1)), WHAL_SUCCESS); + WHAL_ASSERT_EQ(whal_Rng_Generate(&g_whalRng, buf2, sizeof(buf2)), WHAL_SUCCESS); + WHAL_ASSERT_EQ(whal_Rng_Deinit(&g_whalRng), WHAL_SUCCESS); + whal_Stm32wbRcc_Ext_EnableHsi48(&g_whalClock, 0); + + for (size_t i = 0; i < sizeof(buf1); i++) { + if (buf1[i] != buf2[i]) { + same = 0; + break; + } + } + + /* Two consecutive 16-byte outputs should differ */ + WHAL_ASSERT_EQ(same, 0); +} + +void test_rng(void) +{ + WHAL_TEST_SUITE_START("rng"); + WHAL_TEST(test_rng_init_deinit); + WHAL_TEST(test_rng_generate_nonzero); + WHAL_TEST(test_rng_generate_unique); + WHAL_TEST_SUITE_END(); +} diff --git a/wolfHAL/clock/stm32wb_rcc.h b/wolfHAL/clock/stm32wb_rcc.h index 8e65319..73893ff 100644 --- a/wolfHAL/clock/stm32wb_rcc.h +++ b/wolfHAL/clock/stm32wb_rcc.h @@ -212,5 +212,15 @@ whal_Error whal_Stm32wbRccPll_GetRate(whal_Clock *clkDev, size_t *rateOut); * @retval WHAL_EINVAL Invalid arguments. */ whal_Error whal_Stm32wbRccMsi_GetRate(whal_Clock *clkDev, size_t *rateOut); +/* + * @brief Enable or disable the HSI48 oscillator required by the RNG peripheral. + * + * @param clkDev Clock controller instance. + * @param enable 1 to enable, 0 to disable. + * + * @retval WHAL_SUCCESS HSI48 enabled and ready, or disabled. + * @retval WHAL_EINVAL Invalid arguments. + */ +whal_Error whal_Stm32wbRcc_Ext_EnableHsi48(whal_Clock *clkDev, uint8_t enable); #endif /* WHAL_STM32WB_RCC_H */ diff --git a/wolfHAL/error.h b/wolfHAL/error.h index 58368de..52c5c52 100644 --- a/wolfHAL/error.h +++ b/wolfHAL/error.h @@ -16,6 +16,8 @@ enum { /* Invalid argument or unsupported operation. */ WHAL_EINVAL = -4000, WHAL_ENOTREADY = -4001, + /* Hardware device error. */ + WHAL_EHARDWARE = -4002, }; #endif /* WHAL_ERROR_H */ diff --git a/wolfHAL/platform/st/stm32wb55xx.h b/wolfHAL/platform/st/stm32wb55xx.h index c6a3c5f..ebc44bc 100644 --- a/wolfHAL/platform/st/stm32wb55xx.h +++ b/wolfHAL/platform/st/stm32wb55xx.h @@ -8,6 +8,7 @@ #include #include #include +#include /* * @file stm32wb55xx.h @@ -56,6 +57,13 @@ }, \ .driver = &whal_Stm32wbRccPll_Driver +#define WHAL_STM32WB55_RNG_DEVICE \ + .regmap = { \ + .base = 0x58001000, \ + .size = 0x400, \ + }, \ + .driver = &whal_Stm32wbRng_Driver + #define WHAL_STM32WB55_FLASH_DEVICE \ .regmap = { \ .base = 0x58004000, \ @@ -76,6 +84,10 @@ .regOffset = 0x4C, \ .enableMask = (1 << 1) +#define WHAL_STM32WB55_RNG_CLOCK \ + .regOffset = 0x50, \ + .enableMask = (1 << 18) + #define WHAL_STM32WB55_FLASH_CLOCK \ .regOffset = 0x50, \ .enableMask = (1 << 25) diff --git a/wolfHAL/rng/rng.h b/wolfHAL/rng/rng.h new file mode 100644 index 0000000..f509b20 --- /dev/null +++ b/wolfHAL/rng/rng.h @@ -0,0 +1,82 @@ +#ifndef WHAL_RNG_H +#define WHAL_RNG_H + +#include +#include +#include +#include + +/* + * @file rng.h + * @brief Generic RNG abstraction and driver interface. + */ + +typedef struct whal_Rng whal_Rng; + +/* + * @brief Driver vtable for RNG devices. + */ +typedef struct { + /* Initialize the RNG hardware. */ + whal_Error (*Init)(whal_Rng *rngDev); + /* Deinitialize the RNG hardware. */ + whal_Error (*Deinit)(whal_Rng *rngDev); + /* Generate random data into a buffer. */ + whal_Error (*Generate)(whal_Rng *rngDev, uint8_t *rngData, size_t rngDataSz); +} whal_RngDriver; + +/* + * @brief RNG device instance tying a register map and driver. + */ +struct whal_Rng { + const whal_Regmap regmap; + const whal_RngDriver *driver; + void *cfg; +}; + +/* + * @brief Initialize an RNG device and its driver. + * + * @param rngDev RNG instance to initialize. + * + * @retval WHAL_SUCCESS Driver-specific init completed. + * @retval WHAL_EINVAL Null pointer or missing driver function. + */ +#ifdef WHAL_CFG_NO_CALLBACKS +#define whal_Rng_Init(rngDev) ((rngDev)->driver->Init((rngDev))) +#define whal_Rng_Deinit(rngDev) ((rngDev)->driver->Deinit((rngDev))) +#define whal_Rng_Generate(rngDev, rngData, rngDataSz) \ + ((rngDev)->driver->Generate((rngDev), (rngData), (rngDataSz))) +#else +/* + * @brief Initialize an RNG device and its driver. + * + * @param rngDev RNG instance to initialize. + * + * @retval WHAL_SUCCESS Driver-specific init completed. + * @retval WHAL_EINVAL Null pointer or missing driver function. + */ +whal_Error whal_Rng_Init(whal_Rng *rngDev); +/* + * @brief Deinitialize an RNG device and release resources. + * + * @param rngDev RNG instance to deinitialize. + * + * @retval WHAL_SUCCESS Driver-specific deinit completed. + * @retval WHAL_EINVAL Null pointer or missing driver function. + */ +whal_Error whal_Rng_Deinit(whal_Rng *rngDev); +/* + * @brief Generate random data into a buffer. + * + * @param rngDev RNG instance to use. + * @param rngData Destination buffer. + * @param rngDataSz Number of random bytes to generate. + * + * @retval WHAL_SUCCESS Buffer filled with random data. + * @retval WHAL_EINVAL Null pointer or missing driver function. + */ +whal_Error whal_Rng_Generate(whal_Rng *rngDev, uint8_t *rngData, size_t rngDataSz); +#endif + +#endif /* WHAL_RNG_H */ diff --git a/wolfHAL/rng/stm32wb_rng.h b/wolfHAL/rng/stm32wb_rng.h new file mode 100644 index 0000000..7c47284 --- /dev/null +++ b/wolfHAL/rng/stm32wb_rng.h @@ -0,0 +1,60 @@ +#ifndef WHAL_STM32WB_RNG_H +#define WHAL_STM32WB_RNG_H + +#include +#include +#include + +/* + * @file stm32wb_rng.h + * @brief STM32WB RNG driver configuration. + * + * The STM32WB true random number generator provides 32-bit random values + * from an analog noise source. The peripheral requires the RNG clock to + * be enabled and produces one 32-bit word at a time via the DR register. + */ + +/* + * @brief RNG device configuration. + */ +typedef struct whal_Stm32wbRng_Cfg { + whal_Clock *clkCtrl; /* Clock controller for RNG peripheral clock */ + const void *clk; /* Clock descriptor */ +} whal_Stm32wbRng_Cfg; + +/* + * @brief Driver instance for STM32WB RNG peripheral. + */ +extern const whal_RngDriver whal_Stm32wbRng_Driver; + +/* + * @brief Initialize the STM32WB RNG peripheral. + * + * @param rngDev RNG device instance. + * + * @retval WHAL_SUCCESS Initialization completed. + * @retval WHAL_EINVAL Invalid arguments. + */ +whal_Error whal_Stm32wbRng_Init(whal_Rng *rngDev); +/* + * @brief Deinitialize the STM32WB RNG peripheral. + * + * @param rngDev RNG device instance. + * + * @retval WHAL_SUCCESS Deinit completed. + * @retval WHAL_EINVAL Invalid arguments. + */ +whal_Error whal_Stm32wbRng_Deinit(whal_Rng *rngDev); +/* + * @brief Generate random data. + * + * @param rngDev RNG device instance. + * @param rngData Destination buffer. + * @param rngDataSz Number of random bytes to generate. + * + * @retval WHAL_SUCCESS Buffer filled with random data. + * @retval WHAL_EINVAL Invalid arguments or seed/clock error detected. + */ +whal_Error whal_Stm32wbRng_Generate(whal_Rng *rngDev, uint8_t *rngData, size_t rngDataSz); + +#endif /* WHAL_STM32WB_RNG_H */ diff --git a/wolfHAL/wolfHAL.h b/wolfHAL/wolfHAL.h index fa5e7d4..c8c90fe 100644 --- a/wolfHAL/wolfHAL.h +++ b/wolfHAL/wolfHAL.h @@ -13,6 +13,7 @@ #include #include #include +#include #include #endif /* WOLFHAL_H */