From b70f984f2a2b4d922509eac52af9e6938ea47bf3 Mon Sep 17 00:00:00 2001 From: andygpz11 <111971637+andygpz11@users.noreply.github.com> Date: Thu, 26 Jan 2023 19:25:27 +0000 Subject: [PATCH] Add pico_rand library (#1111) Add a general purpose random number generator via pico_rand library. which tries to use as much real entropy as possible mixed into a PRNG Co-authored-by: graham sanderson --- docs/index.h | 1 + src/rp2_common/CMakeLists.txt | 1 + .../hardware_sync/include/hardware/sync.h | 5 + src/rp2_common/pico_lwip/CMakeLists.txt | 4 +- src/rp2_common/pico_lwip/include/arch/cc.h | 12 +- src/rp2_common/pico_lwip/lwip_random.c | 29 -- .../pico_platform/include/pico/platform.h | 4 +- src/rp2_common/pico_rand/CMakeLists.txt | 13 + src/rp2_common/pico_rand/include/pico/rand.h | 173 ++++++++++ src/rp2_common/pico_rand/rand.c | 303 ++++++++++++++++++ test/kitchen_sink/CMakeLists.txt | 1 + test/kitchen_sink/kitchen_sink.c | 1 + 12 files changed, 505 insertions(+), 42 deletions(-) delete mode 100644 src/rp2_common/pico_lwip/lwip_random.c create mode 100644 src/rp2_common/pico_rand/CMakeLists.txt create mode 100644 src/rp2_common/pico_rand/include/pico/rand.h create mode 100644 src/rp2_common/pico_rand/rand.c diff --git a/docs/index.h b/docs/index.h index 761401e..b09521f 100644 --- a/docs/index.h +++ b/docs/index.h @@ -43,6 +43,7 @@ * @{ * \defgroup pico_async_context pico_async_context * \defgroup pico_multicore pico_multicore + * \defgroup pico_rand pico_rand * \defgroup pico_stdlib pico_stdlib * \defgroup pico_sync pico_sync * \defgroup pico_time pico_time diff --git a/src/rp2_common/CMakeLists.txt b/src/rp2_common/CMakeLists.txt index c0c6e90..fd1260a 100644 --- a/src/rp2_common/CMakeLists.txt +++ b/src/rp2_common/CMakeLists.txt @@ -49,6 +49,7 @@ if (NOT PICO_BARE_METAL) pico_add_subdirectory(pico_mem_ops) pico_add_subdirectory(pico_malloc) pico_add_subdirectory(pico_printf) + pico_add_subdirectory(pico_rand) pico_add_subdirectory(pico_stdio) pico_add_subdirectory(pico_stdio_semihosting) diff --git a/src/rp2_common/hardware_sync/include/hardware/sync.h b/src/rp2_common/hardware_sync/include/hardware/sync.h index eee675e..1fbc672 100644 --- a/src/rp2_common/hardware_sync/include/hardware/sync.h +++ b/src/rp2_common/hardware_sync/include/hardware/sync.h @@ -70,6 +70,11 @@ typedef volatile uint32_t spin_lock_t; #define PICO_SPINLOCK_ID_HARDWARE_CLAIM 11 #endif +// PICO_CONFIG: PICO_SPINLOCK_ID_RAND, Spinlock ID for Random Number Generator, min=0, max=31, default=12, group=hardware_sync +#ifndef PICO_SPINLOCK_ID_RAND +#define PICO_SPINLOCK_ID_RAND 12 +#endif + // PICO_CONFIG: PICO_SPINLOCK_ID_OS1, First Spinlock ID reserved for use by low level OS style software, min=0, max=31, default=14, group=hardware_sync #ifndef PICO_SPINLOCK_ID_OS1 #define PICO_SPINLOCK_ID_OS1 14 diff --git a/src/rp2_common/pico_lwip/CMakeLists.txt b/src/rp2_common/pico_lwip/CMakeLists.txt index deb770c..46b615f 100644 --- a/src/rp2_common/pico_lwip/CMakeLists.txt +++ b/src/rp2_common/pico_lwip/CMakeLists.txt @@ -46,7 +46,6 @@ if (EXISTS ${PICO_LWIP_PATH}/${LWIP_TEST_PATH}) ${PICO_LWIP_PATH}/src/core/tcp_out.c ${PICO_LWIP_PATH}/src/core/timeouts.c ${PICO_LWIP_PATH}/src/core/udp.c - ${CMAKE_CURRENT_LIST_DIR}/lwip_random.c ) target_include_directories(pico_lwip_core INTERFACE ${PICO_LWIP_PATH}/src/include) @@ -274,7 +273,8 @@ if (EXISTS ${PICO_LWIP_PATH}/${LWIP_TEST_PATH}) target_link_libraries(pico_lwip_nosys INTERFACE pico_async_context_base pico_lwip_arch - pico_lwip) + pico_lwip + pico_rand) if (NOT PICO_LWIP_CONTRIB_PATH) set(PICO_LWIP_CONTRIB_PATH ${PICO_LWIP_PATH}/contrib) diff --git a/src/rp2_common/pico_lwip/include/arch/cc.h b/src/rp2_common/pico_lwip/include/arch/cc.h index 2f12677..7ac155e 100644 --- a/src/rp2_common/pico_lwip/include/arch/cc.h +++ b/src/rp2_common/pico_lwip/include/arch/cc.h @@ -86,14 +86,8 @@ void pico_lwip_custom_unlock_tcpip_core(void); #endif #ifndef LWIP_RAND -#ifdef __cplusplus -extern "C" { -#endif -unsigned int pico_lwip_rand(void); -#ifdef __cplusplus -} -#endif -// Use ROSC based random number generation, more for the fact that rand() may not be seeded, than anything else -#define LWIP_RAND pico_lwip_rand +#include "pico/rand.h" +// Use the pico_rand library which goes to reasonable lengths to try to provide good entropy +#define LWIP_RAND() get_rand_32() #endif #endif /* __CC_H__ */ diff --git a/src/rp2_common/pico_lwip/lwip_random.c b/src/rp2_common/pico_lwip/lwip_random.c deleted file mode 100644 index 2c4cdf8..0000000 --- a/src/rp2_common/pico_lwip/lwip_random.c +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright (c) 2020 Raspberry Pi (Trading) Ltd. - * - * SPDX-License-Identifier: BSD-3-Clause - */ - -#include "pico.h" -#include "hardware/structs/rosc.h" - -static uint8_t pico_lwip_random_byte(int cycles) { - static uint8_t byte; - assert(cycles >= 8); - assert(rosc_hw->status & ROSC_STATUS_ENABLED_BITS); - for(int i=0;irandombit) ^ (byte & 0x80u ? 0x35u : 0); - // delay a little because the random bit is a little slow - busy_wait_at_least_cycles(30); - } - return byte; -} - -unsigned int pico_lwip_rand(void) { - uint32_t value = 0; - for (int i = 0; i < 4; i++) { - value = (value << 8u) | pico_lwip_random_byte(32); - } - return value; -} \ No newline at end of file diff --git a/src/rp2_common/pico_platform/include/pico/platform.h b/src/rp2_common/pico_platform/include/pico/platform.h index 5f40b06..d576e79 100644 --- a/src/rp2_common/pico_platform/include/pico/platform.h +++ b/src/rp2_common/pico_platform/include/pico/platform.h @@ -153,14 +153,14 @@ extern "C" { * * For example a `uint32_t` foo that will retain its value if the program is restarted by reset. * - * uint32_t __uninitialized_ram("my_group_name") foo; + * uint32_t __uninitialized_ram("foo"); * * The section attribute is `.uninitialized_ram.` * * \param group a string suffix to use in the section name to distinguish groups that can be linker * garbage-collected independently */ -#define __uninitialized_ram(group) __attribute__((section(".uninitialized_ram." #group))) group +#define __uninitialized_ram(group) __attribute__((section(".uninitialized_data." #group))) group /*! \brief Section attribute macro for placement in flash even in a COPY_TO_RAM binary * \ingroup pico_platform diff --git a/src/rp2_common/pico_rand/CMakeLists.txt b/src/rp2_common/pico_rand/CMakeLists.txt new file mode 100644 index 0000000..17e0717 --- /dev/null +++ b/src/rp2_common/pico_rand/CMakeLists.txt @@ -0,0 +1,13 @@ +pico_add_impl_library(pico_rand) + +target_sources(pico_rand INTERFACE + ${CMAKE_CURRENT_LIST_DIR}/rand.c +) + +target_include_directories(pico_rand INTERFACE ${CMAKE_CURRENT_LIST_DIR}/include) + +target_link_libraries(pico_rand INTERFACE + pico_unique_id + hardware_clocks + hardware_timer + hardware_sync) diff --git a/src/rp2_common/pico_rand/include/pico/rand.h b/src/rp2_common/pico_rand/include/pico/rand.h new file mode 100644 index 0000000..a8e9dbc --- /dev/null +++ b/src/rp2_common/pico_rand/include/pico/rand.h @@ -0,0 +1,173 @@ +/* + * Copyright (c) 2022 Raspberry Pi (Trading) Ltd. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef _PICO_RAND_H +#define _PICO_RAND_H + +#include "pico.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** \file pico/rand.h + * \defgroup pico_rand pico_rand + * + * Random Number Generator API + * + * This module generates random numbers at runtime via a number of possible entropy + * sources and uses those sources to modify the state of a 128-bit 'Pseudo + * Random Number Generator' implemented in software. + * + * The random numbers (32 to 128 bit) to be supplied are read from the PRNG which is used + * to help provide a large number space. + * + * The following (multiple) sources of entropy are available of varying quality, each enabled by a #define: + * + * - The Ring Oscillator (ROSC) (\ref PICO_RAND_ENTROPY_SRC_ROSC == 1): + * \ref PICO_RAND_ROSC_BIT_SAMPLE_COUNT bits are gathered from the ring oscillator "random bit" and mixed in each + * time. This should not be used if the ROSC is off, or the processor is running from + * the ROSC. + * \note the maximum throughput of ROSC bit sampling is controlled by PICO_RAND_MIN_ROSC_BIT_SAMPLE_TIME_US which defaults + * to 10us, i.e. 100,000 bits per second. + * - Time (\ref PICO_RAND_ENTROPY_SRC_TIME == 1): The 64-bit microsecond timer is mixed in each time. + * - Bus Performance Counter (\ref PICO_RAND_ENTROPY_SRC_BUS_PERF_COUNTER == 1): One of the bus fabric's performance + * counters is mixed in each time. + * + * \note All entropy sources are hashed before application to the PRNG state machine. + * + * \note The \em first time a random number is requested, the 128-bit PRNG state + * must be seeded. Multiple entropy sources are also available for the seeding operation: + * + * - The Ring Oscillator (ROSC) (\ref PICO_RAND_SEED_ENTROPY_SRC_ROSC == 1): + * 64 bits are gathered from the ring oscillator "random bit" and mixed into the seed. + * - Time (\ref PICO_RAND_SEED_ENTROPY_SRC_TIME == 1): The 64-bit microsecond timer is mixed into the seed. + * - Board Identifier (PICO_RAND_SEED_ENTROPY_SRC_BOARD_ID == 1): The board id via \ref pico_get_unique_board_id + * is mixed into the seed. + * - RAM hash (\ref PICO_RAND_SEED_ENTROPY_SRC_RAM_HASH (\ref PICO_RAND_SEED_ENTROPY_SRC_RAM_HASH): The hashed contents of a + * subset of RAM are mixed in. Initial RAM contents are undefined on power up, so provide a reasonable source of entropy. + * By default the last 1K of RAM (which usually contains the core 0 stack) is hashed, which may also provide for differences + * after each warm reset. + * + * With default settings, the seed generation takes approximately 1 millisecond while + * subsequent random numbers generally take between 10 and 20 microseconds to generate. + */ + +// --------------- +// ENTROPY SOURCES +// --------------- + +// PICO_CONFIG: PICO_RAND_ENTROPY_SRC_ROSC, Enable/disable use of ROSC as an entropy source, type=bool, default=1, group=pico_rand +#ifndef PICO_RAND_ENTROPY_SRC_ROSC +#define PICO_RAND_ENTROPY_SRC_ROSC 1 +#endif + +// PICO_CONFIG: PICO_RAND_ENTROPY_SRC_TIME, Enable/disable use of hardware timestamp as an entropy source, type=bool, default=1, group=pico_rand +#ifndef PICO_RAND_ENTROPY_SRC_TIME +#define PICO_RAND_ENTROPY_SRC_TIME 1 +#endif + +// PICO_CONFIG: PICO_RAND_ENTROPY_SRC_BUS_PERF_COUNTER, Enable/disable use of a bus performance counter as an entropy source, type=bool, default=1, group=pico_rand +#ifndef PICO_RAND_ENTROPY_SRC_BUS_PERF_COUNTER +#define PICO_RAND_ENTROPY_SRC_BUS_PERF_COUNTER 1 +#endif + +// -------------------- +// SEED ENTROPY SOURCES +// -------------------- + +// PICO_CONFIG: PICO_RAND_SEED_ENTROPY_SRC_ROSC, Enable/disable use of ROSC as an entropy source for the random seed, type=bool, default=1, group=pico_rand +#ifndef PICO_RAND_SEED_ENTROPY_SRC_ROSC +#define PICO_RAND_SEED_ENTROPY_SRC_ROSC PICO_RAND_ENTROPY_SRC_ROSC +#endif + +// PICO_CONFIG: PICO_RAND_SEED_ENTROPY_SRC_TIME, Enable/disable use of hardware timestamp as an entropy source for the random seed, type=bool, default=1, group=pico_rand +#ifndef PICO_RAND_SEED_ENTROPY_SRC_TIME +#define PICO_RAND_SEED_ENTROPY_SRC_TIME PICO_RAND_ENTROPY_SRC_TIME +#endif + +// PICO_CONFIG: PICO_RAND_SEED_ENTROPY_SRC_BOARD_ID, Enable/disable use of board id as part of the random seed, type=bool, default=1, group=pico_rand +#ifndef PICO_RAND_SEED_ENTROPY_SRC_BOARD_ID +#define PICO_RAND_SEED_ENTROPY_SRC_BOARD_ID 1 +#endif + +// PICO_CONFIG: PICO_RAND_SEED_ENTROPY_SRC_RAM_HASH, Enable/disable use of a RAM hash as an entropy source for the random seed, type=bool, default=1, group=pico_rand +#ifndef PICO_RAND_SEED_ENTROPY_SRC_RAM_HASH +#define PICO_RAND_SEED_ENTROPY_SRC_RAM_HASH 1 +#endif + +// --------------------------------- +// PICO_RAND_ENTROPY_SRC_ROSC CONFIG +// --------------------------------- + +// PICO_CONFIG: PICO_RAND_ROSC_BIT_SAMPLE_COUNT, Number of samples to take of the ROSC random bit per random number generation , min=1, max=64, default=1, group=pico_rand +#ifndef PICO_RAND_ROSC_BIT_SAMPLE_COUNT +#define PICO_RAND_ROSC_BIT_SAMPLE_COUNT 1 +#endif + +// PICO_CONFIG: PICO_RAND_MIN_ROSC_BIT_SAMPLE_TIME_US, Define a default minimum time between sampling the ROSC random bit, min=5, max=20, default=10, group=pico_rand +#ifndef PICO_RAND_MIN_ROSC_BIT_SAMPLE_TIME_US +// (Arbitrary / tested) minimum time between sampling the ROSC random bit +#define PICO_RAND_MIN_ROSC_BIT_SAMPLE_TIME_US 10u +#endif + +// --------------------------------------------- +// PICO_RAND_ENTROPY_SRC_BUS_PERF_COUNTER CONFIG +// --------------------------------------------- + +// PICO_CONFIG: PICO_RAND_BUS_PERF_COUNTER_INDEX, Bus performance counter index to use for sourcing entropy, min=0, max=3, group=pico_rand +// this is deliberately undefined by default, meaning the code will pick that appears unused +//#define PICO_RAND_BUS_PERF_COUNTER_INDEX 0 + +// PICO_CONFIG: PICO_RAND_BUS_PERF_COUNTER_EVENT, Bus performance counter event to use for sourcing entropy, default=arbiter_sram5_perf_event_access, group=pico_rand +#ifndef PICO_RAND_BUS_PERF_COUNTER_EVENT +#define PICO_RAND_BUS_PERF_COUNTER_EVENT arbiter_sram5_perf_event_access +#endif + +// ------------------------------------------ +// PICO_RAND_SEED_ENTROPY_SRC_RAM_HASH CONFIG +// ------------------------------------------ + +// PICO_CONFIG: PICO_RAND_RAM_HASH_END, end of address in RAM (non-inclusive) to hash during pico_rand seed initialization, default=SRAM_END, group=pico_rand +#ifndef PICO_RAND_RAM_HASH_END +#define PICO_RAND_RAM_HASH_END SRAM_END +#endif +// PICO_CONFIG: PICO_RAND_RAM_HASH_START, start of address in RAM (inclusive) to hash during pico_rand seed initialization, default=PICO_RAND_RAM_HASH_END-1024, group=pico_rand +#ifndef PICO_RAND_RAM_HASH_START +#define PICO_RAND_RAM_HASH_START (PICO_RAND_RAM_HASH_END - 1024u) +#endif + +// We provide a maximum of 128 bits entropy in one go +typedef struct rng_128 { + uint64_t r[2]; +} rng_128_t; + +/*! \brief Get 128-bit random number + * \ingroup pico_rand + * + * \param rand128 Pointer to storage to accept a 128-bit random number + */ +void get_rand_128(rng_128_t *rand128); + +/*! \brief Get 64-bit random number + * \ingroup pico_rand + * + * \return 64-bit random number + */ +uint64_t get_rand_64(void); + +/*! \brief Get 32-bit random number + * \ingroup pico_rand + * + * \return 32-bit random number + */ +uint32_t get_rand_32(void); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/rp2_common/pico_rand/rand.c b/src/rp2_common/pico_rand/rand.c new file mode 100644 index 0000000..0d1c6c5 --- /dev/null +++ b/src/rp2_common/pico_rand/rand.c @@ -0,0 +1,303 @@ +/* + * Copyright (c) 2022 Raspberry Pi (Trading) Ltd. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +/* xoroshiro128ss(), rotl(): + + Written in 2018 by David Blackman and Sebastiano Vigna (vigna@acm.org) + + To the extent possible under law, the author has dedicated all copyright + and related and neighboring rights to this software to the public domain + worldwide. This software is distributed without any warranty. + + See + + splitmix64() implementation: + + Written in 2015 by Sebastiano Vigna (vigna@acm.org) + To the extent possible under law, the author has dedicated all copyright + and related and neighboring rights to this software to the public domain + worldwide. This software is distributed without any warranty. + + See +*/ + +#include "pico/rand.h" +#include "pico/unique_id.h" +#include "pico/time.h" +#include "hardware/clocks.h" +#include "hardware/structs/rosc.h" +#include "hardware/structs/bus_ctrl.h" +#include "hardware/sync.h" + +static bool rng_initialised = false; + +// Note: By design, do not initialise any of the variables that hold entropy, +// they may have useful junk in them, either from power-up or a previous boot. +static rng_128_t __uninitialized_ram(rng_state); +#if PICO_RAND_SEED_ENTROPY_SRC_RAM_HASH +static uint64_t __uninitialized_ram(ram_hash); +#endif + +#if PICO_RAND_ENTROPY_SRC_ROSC | PICO_RAND_SEED_ENTROPY_SRC_ROSC +static uint64_t __uninitialized_ram(rosc_samples); +#endif + +#if PICO_RAND_ENTROPY_SRC_BUS_PERF_COUNTER +static uint8_t bus_counter_idx; +#endif + +/* From the original source: + + This is a fixed-increment version of Java 8's SplittableRandom generator + See http://dx.doi.org/10.1145/2714064.2660195 and + http://docs.oracle.com/javase/8/docs/api/java/util/SplittableRandom.html + + It is a very fast generator passing BigCrush, and it can be useful if + for some reason you absolutely want 64 bits of state; otherwise, we + rather suggest to use a xoroshiro128+ (for moderately parallel + computations) or xorshift1024* (for massively parallel computations) + generator. + + Note: This can be called with any value (i.e. including 0) +*/ +static __noinline uint64_t splitmix64(uint64_t x) { + uint64_t z = x + 0x9E3779B97F4A7C15ull; + z = (z ^ (z >> 30)) * 0xBF58476D1CE4E5B9ull; + z = (z ^ (z >> 27)) * 0x94D049BB133111EBull; + return z ^ (z >> 31); +} + +/* From the original source: + + This is xoroshiro128** 1.0, one of our all-purpose, rock-solid, + small-state generators. It is extremely (sub-ns) fast and it passes all + tests we are aware of, but its state space is large enough only for + mild parallelism. + + For generating just floating-point numbers, xoroshiro128+ is even + faster (but it has a very mild bias, see notes in the comments). + + The state must be seeded so that it is not everywhere zero. If you have + a 64-bit seed, we suggest to seed a splitmix64 generator and use its + output to fill s. +*/ +static inline uint64_t rotl(const uint64_t x, int k) { + return (x << k) | (x >> (64 - k)); +} + +static __noinline uint64_t xoroshiro128ss(rng_128_t *local_rng_state) { + const uint64_t s0 = local_rng_state->r[0]; + uint64_t s1 = local_rng_state->r[1]; + + // Because the state is *modified* outside of this function, there is a + // 1 in 2^128 chance that it could be all zeroes (which is not allowed). + while (s0 == 0 && s1 == 0) { + s1 = time_us_64(); // should not be 0, but loop anyway + } + + const uint64_t result = rotl(s0 * 5, 7) * 9; + + s1 ^= s0; + local_rng_state->r[0] = rotl(s0, 24) ^ s1 ^ (s1 << 16); // a, b + local_rng_state->r[1] = rotl(s1, 37); // c + + return result; +} + +#if PICO_RAND_SEED_ENTROPY_SRC_RAM_HASH +static uint64_t sdbm_hash64_sram(uint64_t hash) { + // save some time by hashing a word at a time + for (uint i = (PICO_RAND_RAM_HASH_START + 3) & ~3; i < PICO_RAND_RAM_HASH_END; i+=4) { + uint32_t c = *(uint32_t *) i; + hash = (uint64_t) c + (hash << 6) + (hash << 16) - hash; + } + return hash; +} +#endif + +#if PICO_RAND_SEED_ENTROPY_SRC_ROSC | PICO_RAND_ENTROPY_SRC_ROSC +/* gather an additional n bits of entropy, and shift them into the 64 bit entropy counter */ +static uint64_t capture_additional_rosc_samples(uint n) { + static absolute_time_t next_sample_time; + + // provide an override if someone really wants it, but disabling ROSC as an entropy source makes more sense +#if !PICO_RAND_DISABLE_ROSC_CHECK + // check that the ROSC is running but that the processors are NOT running from it + hard_assert((rosc_hw->status & ROSC_STATUS_ENABLED_BITS) && + ((clocks_hw->clk[clk_sys].ctrl & CLOCKS_CLK_SYS_CTRL_AUXSRC_BITS) != (CLOCKS_CLK_SYS_CTRL_AUXSRC_VALUE_ROSC_CLKSRC << CLOCKS_CLK_SYS_CTRL_AUXSRC_LSB))); +#endif + + bool in_exception = __get_current_exception(); + assert(n); // save us having to special case samples for this + uint64_t samples = 0; + for(uint i=0; irandombit & 1u; + // use of relative time to now, rather than offset from before makes things + // a bit less predictable at the cost of some speed. + next_sample_time = make_timeout_time_us(PICO_RAND_MIN_ROSC_BIT_SAMPLE_TIME_US); + bit_done = true; + if (i == n - 1) { + // samples has our random bits, so let's mix them in now + samples = rosc_samples = (rosc_samples << n) | samples; + } + } + spin_unlock(lock, save); + } while (!bit_done); + } + return samples; +} +#endif + +static void initialise_rand(void) { + rng_128_t local_rng_state = local_rng_state; + uint which = 0; +#if PICO_RAND_SEED_ENTROPY_SRC_RAM_HASH + ram_hash = sdbm_hash64_sram(ram_hash); + local_rng_state.r[which] ^= splitmix64(ram_hash); + which ^= 1; +#endif + +#if PICO_RAND_SEED_ENTROPY_SRC_BOARD_ID + static_assert(PICO_UNIQUE_BOARD_ID_SIZE_BYTES == sizeof(uint64_t), + "Code below requires that 'board_id' is 64-bits in size"); + + // Note! The safety of the length assumption here is protected by a 'static_assert' above + union unique_id_u { + pico_unique_board_id_t board_id_native; + uint64_t board_id_u64; + } unique_id; + // Note! The safety of the length assumption here is protected by a 'static_assert' above + pico_get_unique_board_id(&unique_id.board_id_native); + local_rng_state.r[which] ^= splitmix64(unique_id.board_id_u64); + which ^= 1; +#endif + +#if PICO_RAND_SEED_ENTROPY_SRC_ROSC + // this is really quite slow (10ms per iteration), and I'm not sure that it adds value over the 64 random bits +// uint ref_khz = clock_get_hz(clk_ref) / 100; +// for (int i = 0; i < 5; i++) { +// // Apply hash of the rosc frequency, limited but still 'extra' entropy +// uint measurement = frequency_count_raw(CLOCKS_FC0_SRC_VALUE_ROSC_CLKSRC, ref_khz); +// local_rng_state.r[which] ^= splitmix64(measurement); +// (void) xoroshiro128ss(&local_rng_state); //churn to mix seed sources +// } + + // Gather a full ROSC sample array with sample bits + local_rng_state.r[which] ^= splitmix64(capture_additional_rosc_samples(8 * sizeof(rosc_samples))); + which ^= 1; +#endif + +#if PICO_RAND_SEED_ENTROPY_SRC_TIME + // Mix in hashed time. This is [possibly] predictable boot-to-boot + // but will vary application-to-application. + local_rng_state.r[which] ^= splitmix64(time_us_64()); + which ^= 1; +#endif + + spin_lock_t *lock = spin_lock_instance(PICO_SPINLOCK_ID_RAND); + uint32_t save = spin_lock_blocking(lock); + if (!rng_initialised) { +#if PICO_RAND_ENTROPY_SRC_BUS_PERF_COUNTER +#if !PICO_RAND_BUSCTRL_COUNTER_INDEX + int idx = -1; + for(uint i = 0; i < count_of(bus_ctrl_hw->counter); i++) { + if (bus_ctrl_hw->counter[i].sel == BUSCTRL_PERFSEL0_RESET) { + idx = (int)i; + break; + } + } + hard_assert(idx != -1); + bus_counter_idx = (uint8_t)idx; +#else + bus_counter_idx = (uint8_t)PICO_RAND_BUSCTRL_COUNTER_INDEX; +#endif + bus_ctrl_hw->counter[bus_counter_idx].sel = PICO_RAND_BUS_PERF_COUNTER_EVENT; +#endif + (void) xoroshiro128ss(&local_rng_state); + rng_state = local_rng_state; + rng_initialised = true; + } + spin_unlock(lock, save); +} + +uint64_t get_rand_64(void) { + if (!rng_initialised) { + // Do not provide 'RNs' until the system has been initialised. Note: + // The first initialisation can be quite time-consuming depending on + // the amount of RAM hashed, see RAM_HASH_START and RAM_HASH_END + initialise_rand(); + } + + static volatile uint8_t check_byte; + rng_128_t local_rng_state = rng_state; + uint8_t local_check_byte = check_byte; + // Modify PRNG state with the run-time entropy sources, + // hashed to reduce correlation with previous modifications. + uint which = 0; +#if PICO_RAND_ENTROPY_SRC_TIME + local_rng_state.r[which] ^= splitmix64(time_us_64()); + which ^= 1; +#endif +#if PICO_RAND_ENTROPY_SRC_ROSC + local_rng_state.r[which] ^= splitmix64(capture_additional_rosc_samples(PICO_RAND_ROSC_BIT_SAMPLE_COUNT)); + which ^= 1; +#endif +#if PICO_RAND_ENTROPY_SRC_BUS_PERF_COUNTER + uint32_t bus_counter_value = bus_ctrl_hw->counter[bus_counter_idx].value; + // counter is saturating, so clear it if it has reached saturation + if (bus_counter_value == BUSCTRL_PERFCTR0_BITS) { + bus_ctrl_hw->counter[bus_counter_idx].value = 0; + } + local_rng_state.r[which] &= splitmix64(bus_counter_value); + which ^= 1; +#endif + + spin_lock_t *lock = spin_lock_instance(PICO_SPINLOCK_ID_RAND); + uint32_t save = spin_lock_blocking(lock); + if (local_check_byte != check_byte) { + // someone got a random number in the interim, so mix it in + local_rng_state.r[0] ^= rng_state.r[0]; + local_rng_state.r[1] ^= rng_state.r[1]; + } + // Generate a 64-bit RN from the modified PRNG state. + // Note: This also "churns" the 128-bit state for next time. + uint64_t rand64 = xoroshiro128ss(&local_rng_state); + rng_state = local_rng_state; + check_byte++; + spin_unlock(lock, save); + + return rand64; +} + +void get_rand_128(rng_128_t *ptr128) { + ptr128->r[0] = get_rand_64(); + ptr128->r[1] = get_rand_64(); +} + +uint32_t get_rand_32(void) { + return (uint32_t) get_rand_64(); +} diff --git a/test/kitchen_sink/CMakeLists.txt b/test/kitchen_sink/CMakeLists.txt index 4c0a60d..9a465a9 100644 --- a/test/kitchen_sink/CMakeLists.txt +++ b/test/kitchen_sink/CMakeLists.txt @@ -40,6 +40,7 @@ target_link_libraries(kitchen_sink_libs INTERFACE pico_time pico_unique_id pico_util + pico_rand ) add_library(kitchen_sink_options INTERFACE) diff --git a/test/kitchen_sink/kitchen_sink.c b/test/kitchen_sink/kitchen_sink.c index 8799065..70fb168 100644 --- a/test/kitchen_sink/kitchen_sink.c +++ b/test/kitchen_sink/kitchen_sink.c @@ -47,6 +47,7 @@ #include "pico/sync.h" #include "pico/time.h" #include "pico/unique_id.h" +#include "pico/rand.h" #if LIB_PICO_CYW43_ARCH #include "pico/cyw43_arch.h" #endif