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 <graham.sanderson@raspberrypi.com>
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 @@
     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 @@
 #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 @@
             ${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 @@
     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 @@
 #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;i<cycles;i++) {
-        // picked a fairly arbitrary polynomial of 0x35u - this doesn't have to be crazily uniform.
-        byte = ((byte << 1) | rosc_hw->randombit) ^ (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 @@
  *
  * 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.<group>`
  *
  * \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 <http://creativecommons.org/publicdomain/zero/1.0/>
+
+    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 <http://creativecommons.org/publicdomain/zero/1.0/>
+*/
+
+#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; i<n; i++) {
+        bool bit_done = false;
+        do {
+            // Ensure that the ROSC random bit is not sampled too quickly,
+            // ROSC may be ticking only a few times a microsecond.
+            // Note: In general (i.e. sporadic) use, very often there will be no delay here.
+
+            // note this is not read under lock, so the two 32 bit halves could be skewed, but in that
+            // case we'll fail the check later, which is fine in this rare case
+            absolute_time_t cached_next_sample_time = next_sample_time;
+            // we support being called from IRQ, so be careful about sleeping... still not
+            // ideal, but not much that can be done
+            if (in_exception) {
+                busy_wait_until(next_sample_time);
+            } else {
+                sleep_until(next_sample_time);
+            }
+            spin_lock_t *lock = spin_lock_instance(PICO_SPINLOCK_ID_RAND);
+            uint32_t save = spin_lock_blocking(lock);
+            if (!absolute_time_diff_us(cached_next_sample_time, next_sample_time)) {
+                // we won the race (if any) for the bit, so we collect it locally
+                samples <<= 1;
+                samples |= rosc_hw->randombit & 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 @@
     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