|
| 1 | +/* |
| 2 | + * Copyright (c) 2022 Raspberry Pi (Trading) Ltd. |
| 3 | + * |
| 4 | + * SPDX-License-Identifier: BSD-3-Clause |
| 5 | + */ |
| 6 | + |
| 7 | +/* xoroshiro128ss(), rotl(): |
| 8 | +
|
| 9 | + Written in 2018 by David Blackman and Sebastiano Vigna ([email protected]) |
| 10 | +
|
| 11 | + To the extent possible under law, the author has dedicated all copyright |
| 12 | + and related and neighboring rights to this software to the public domain |
| 13 | + worldwide. This software is distributed without any warranty. |
| 14 | +
|
| 15 | + See <http://creativecommons.org/publicdomain/zero/1.0/> |
| 16 | +
|
| 17 | + splitmix64() implementation: |
| 18 | +
|
| 19 | + Written in 2015 by Sebastiano Vigna ([email protected]) |
| 20 | + To the extent possible under law, the author has dedicated all copyright |
| 21 | + and related and neighboring rights to this software to the public domain |
| 22 | + worldwide. This software is distributed without any warranty. |
| 23 | +
|
| 24 | + See <http://creativecommons.org/publicdomain/zero/1.0/> |
| 25 | +*/ |
| 26 | + |
| 27 | +#include "pico/rand.h" |
| 28 | +#if PICO_RAND_ENTROPY_SRC_TIME |
| 29 | +#include "hardware/timer.h" |
| 30 | +#endif |
| 31 | +#include "hardware/sync.h" |
| 32 | + |
| 33 | +static bool rng_initialised = false; |
| 34 | + |
| 35 | +// Note: By design, do not initialise any of the variables that hold entropy, |
| 36 | +// they may have useful junk in them, either from power-up or a previous boot. |
| 37 | +static rng_128_t rng_state; |
| 38 | + |
| 39 | +/* From the original source: |
| 40 | +
|
| 41 | + This is a fixed-increment version of Java 8's SplittableRandom generator |
| 42 | + See http://dx.doi.org/10.1145/2714064.2660195 and |
| 43 | + http://docs.oracle.com/javase/8/docs/api/java/util/SplittableRandom.html |
| 44 | +
|
| 45 | + It is a very fast generator passing BigCrush, and it can be useful if |
| 46 | + for some reason you absolutely want 64 bits of state; otherwise, we |
| 47 | + rather suggest to use a xoroshiro128+ (for moderately parallel |
| 48 | + computations) or xorshift1024* (for massively parallel computations) |
| 49 | + generator. |
| 50 | +
|
| 51 | + Note: This can be called with any value (i.e. including 0) |
| 52 | +*/ |
| 53 | +static __noinline uint64_t splitmix64(uint64_t x) { |
| 54 | + uint64_t z = x + 0x9E3779B97F4A7C15ull; |
| 55 | + z = (z ^ (z >> 30)) * 0xBF58476D1CE4E5B9ull; |
| 56 | + z = (z ^ (z >> 27)) * 0x94D049BB133111EBull; |
| 57 | + return z ^ (z >> 31); |
| 58 | +} |
| 59 | + |
| 60 | +/* From the original source: |
| 61 | +
|
| 62 | + This is xoroshiro128** 1.0, one of our all-purpose, rock-solid, |
| 63 | + small-state generators. It is extremely (sub-ns) fast and it passes all |
| 64 | + tests we are aware of, but its state space is large enough only for |
| 65 | + mild parallelism. |
| 66 | +
|
| 67 | + For generating just floating-point numbers, xoroshiro128+ is even |
| 68 | + faster (but it has a very mild bias, see notes in the comments). |
| 69 | +
|
| 70 | + The state must be seeded so that it is not everywhere zero. If you have |
| 71 | + a 64-bit seed, we suggest to seed a splitmix64 generator and use its |
| 72 | + output to fill s. |
| 73 | +*/ |
| 74 | +static inline uint64_t rotl(const uint64_t x, int k) { |
| 75 | + return (x << k) | (x >> (64 - k)); |
| 76 | +} |
| 77 | + |
| 78 | +static __noinline uint64_t xoroshiro128ss(rng_128_t *local_rng_state) { |
| 79 | + const uint64_t s0 = local_rng_state->r[0]; |
| 80 | + uint64_t s1 = local_rng_state->r[1]; |
| 81 | + |
| 82 | + // Because the state is *modified* outside of this function, there is a |
| 83 | + // 1 in 2^128 chance that it could be all zeroes (which is not allowed). |
| 84 | + while (s0 == 0 && s1 == 0) { |
| 85 | + s1 = time_us_64(); // should not be 0, but loop anyway |
| 86 | + } |
| 87 | + |
| 88 | + const uint64_t result = rotl(s0 * 5, 7) * 9; |
| 89 | + |
| 90 | + s1 ^= s0; |
| 91 | + local_rng_state->r[0] = rotl(s0, 24) ^ s1 ^ (s1 << 16); // a, b |
| 92 | + local_rng_state->r[1] = rotl(s1, 37); // c |
| 93 | + |
| 94 | + return result; |
| 95 | +} |
| 96 | + |
| 97 | +static void initialise_rand(void) { |
| 98 | + rng_128_t local_rng_state = local_rng_state; |
| 99 | + uint which = 0; |
| 100 | + |
| 101 | +#if PICO_RAND_SEED_ENTROPY_SRC_TIME |
| 102 | + // Mix in hashed time. This is [possibly] predictable boot-to-boot |
| 103 | + // but will vary application-to-application. |
| 104 | + local_rng_state.r[which] ^= splitmix64(time_us_64()); |
| 105 | + which ^= 1; |
| 106 | +#endif |
| 107 | + |
| 108 | + spin_lock_t *lock = spin_lock_instance(PICO_SPINLOCK_ID_RAND); |
| 109 | + uint32_t save = spin_lock_blocking(lock); |
| 110 | + if (!rng_initialised) { |
| 111 | + (void) xoroshiro128ss(&local_rng_state); |
| 112 | + rng_state = local_rng_state; |
| 113 | + rng_initialised = true; |
| 114 | + } |
| 115 | + spin_unlock(lock, save); |
| 116 | +} |
| 117 | + |
| 118 | +uint64_t get_rand_64(void) { |
| 119 | + if (!rng_initialised) { |
| 120 | + // Do not provide 'RNs' until the system has been initialised. Note: |
| 121 | + // The first initialisation can be quite time-consuming depending on |
| 122 | + // the amount of RAM hashed, see RAM_HASH_START and RAM_HASH_END |
| 123 | + initialise_rand(); |
| 124 | + } |
| 125 | + |
| 126 | + static volatile uint8_t check_byte; |
| 127 | + rng_128_t local_rng_state = rng_state; |
| 128 | + uint8_t local_check_byte = check_byte; |
| 129 | + // Modify PRNG state with the run-time entropy sources, |
| 130 | + // hashed to reduce correlation with previous modifications. |
| 131 | + uint which = 0; |
| 132 | +#if PICO_RAND_ENTROPY_SRC_TIME |
| 133 | + local_rng_state.r[which] ^= splitmix64(time_us_64()); |
| 134 | + which ^= 1; |
| 135 | +#endif |
| 136 | + |
| 137 | + spin_lock_t *lock = spin_lock_instance(PICO_SPINLOCK_ID_RAND); |
| 138 | + uint32_t save = spin_lock_blocking(lock); |
| 139 | + if (local_check_byte != check_byte) { |
| 140 | + // someone got a random number in the interim, so mix it in |
| 141 | + local_rng_state.r[0] ^= rng_state.r[0]; |
| 142 | + local_rng_state.r[1] ^= rng_state.r[1]; |
| 143 | + } |
| 144 | + // Generate a 64-bit RN from the modified PRNG state. |
| 145 | + // Note: This also "churns" the 128-bit state for next time. |
| 146 | + uint64_t rand64 = xoroshiro128ss(&local_rng_state); |
| 147 | + rng_state = local_rng_state; |
| 148 | + check_byte++; |
| 149 | + spin_unlock(lock, save); |
| 150 | + |
| 151 | + return rand64; |
| 152 | +} |
| 153 | + |
| 154 | +void get_rand_128(rng_128_t *ptr128) { |
| 155 | + ptr128->r[0] = get_rand_64(); |
| 156 | + ptr128->r[1] = get_rand_64(); |
| 157 | +} |
| 158 | + |
| 159 | +uint32_t get_rand_32(void) { |
| 160 | + return (uint32_t) get_rand_64(); |
| 161 | +} |
0 commit comments