drivers: entropy: nrf5: Replace random number pool implementation

The old algorithm managing random number pools was inefficient and
prone to race conditions. Moreover, its behaviour was not compatible
with the API (provided buffers were filled from the end, leaving
beggining of the buffer untouched if there was not enough random data).

The new implementation is a bit faster and it is race-free as long as
all callers of the entropy_get_entropy_isr() are executed in
"run-to-completion" mode.

Signed-off-by: Piotr Zięcik <piotr.ziecik@nordicsemi.no>
This commit is contained in:
Piotr Zięcik 2018-10-04 13:11:37 +02:00 committed by Carles Cufí
commit c24aca0229
2 changed files with 125 additions and 157 deletions

View file

@ -25,8 +25,17 @@ config ENTROPY_NRF5_BIAS_CORRECTION
uniform distribution of 0 and 1. When this option is enabled, the time uniform distribution of 0 and 1. When this option is enabled, the time
to generate a byte cannot be guaranteed. to generate a byte cannot be guaranteed.
config ENTROPY_NRF5_THR_POOL_SIZE
int "Thread-mode random number pool size"
range ENTROPY_NRF5_THR_THRESHOLD 256
default 8
help
Buffer length in bytes used to store entropy bytes generated by the
hardware to make them ready for thread mode consumers.
Please note, that size of the pool must be a power of 2.
config ENTROPY_NRF5_THR_THRESHOLD config ENTROPY_NRF5_THR_THRESHOLD
int "Thread-mode low-water threshold" int "Thread-mode random number pool low-water threshold"
range 4 255 range 4 255
help help
Low water-mark threshold in bytes to trigger entropy generation for Low water-mark threshold in bytes to trigger entropy generation for
@ -34,8 +43,17 @@ config ENTROPY_NRF5_THR_THRESHOLD
buffer goes below this number hardware entropy generation will be buffer goes below this number hardware entropy generation will be
started. started.
config ENTROPY_NRF5_ISR_POOL_SIZE
int "ISR-mode random number pool size"
range ENTROPY_NRF5_ISR_THRESHOLD 256
default 16
help
Buffer length in bytes used to store entropy bytes generated by the
hardware to make them ready for ISR consumers.
Please note, that size of the pool must be a power of 2.
config ENTROPY_NRF5_ISR_THRESHOLD config ENTROPY_NRF5_ISR_THRESHOLD
int "ISR low-water threshold" int "ISR-mode random number pool low-water threshold"
range 12 255 range 12 255
help help
Low water-mark threshold in bytes to trigger entropy generation for Low water-mark threshold in bytes to trigger entropy generation for
@ -43,19 +61,6 @@ config ENTROPY_NRF5_ISR_THRESHOLD
buffer goes below this number hardware entropy generation will be buffer goes below this number hardware entropy generation will be
started. started.
config ENTROPY_NRF5_THR_BUF_LEN
int "Thread-mode buffer length"
range ENTROPY_NRF5_THR_THRESHOLD 255
help
Buffer length in bytes used to store entropy bytes generated by the
hardware to make them ready for thread mode consumers.
config ENTROPY_NRF5_ISR_BUF_LEN
int "ISR buffer length"
range ENTROPY_NRF5_ISR_THRESHOLD 255
help
Buffer length in bytes used to store entropy bytes generated by the
hardware to make them ready for ISR consumers.
config ENTROPY_NRF5_PRI config ENTROPY_NRF5_PRI
int "RNG interrupt priority" int "RNG interrupt priority"

View file

@ -64,24 +64,30 @@
*/ */
struct rng_pool { struct rng_pool {
u8_t count; u8_t first_alloc;
u8_t threshold; u8_t first_read;
u8_t first;
u8_t last; u8_t last;
u8_t rand[0]; u8_t mask;
u8_t threshold;
u8_t buffer[0];
}; };
#define RNG_POOL_DEFINE(name, len) u8_t name[sizeof(struct rng_pool) + len] __aligned(4) #define RNG_POOL_DEFINE(name, len) u8_t name[sizeof(struct rng_pool) + (len)]
#define RNG_POOL_ISR_LEN (CONFIG_ENTROPY_NRF5_ISR_BUF_LEN + 1) BUILD_ASSERT_MSG((CONFIG_ENTROPY_NRF5_ISR_POOL_SIZE &
#define RNG_POOL_THR_LEN (CONFIG_ENTROPY_NRF5_THR_BUF_LEN + 1) (CONFIG_ENTROPY_NRF5_ISR_POOL_SIZE - 1)) == 0,
"The CONFIG_ENTROPY_NRF5_ISR_POOL_SIZE must be a power of 2!");
BUILD_ASSERT_MSG((CONFIG_ENTROPY_NRF5_THR_POOL_SIZE &
(CONFIG_ENTROPY_NRF5_THR_POOL_SIZE - 1)) == 0,
"The CONFIG_ENTROPY_NRF5_THR_POOL_SIZE must be a power of 2!");
struct entropy_nrf5_dev_data { struct entropy_nrf5_dev_data {
struct k_sem sem_lock; struct k_sem sem_lock;
struct k_sem sem_sync; struct k_sem sem_sync;
RNG_POOL_DEFINE(isr, RNG_POOL_ISR_LEN); RNG_POOL_DEFINE(isr, CONFIG_ENTROPY_NRF5_ISR_POOL_SIZE);
RNG_POOL_DEFINE(thr, RNG_POOL_THR_LEN); RNG_POOL_DEFINE(thr, CONFIG_ENTROPY_NRF5_THR_POOL_SIZE);
}; };
#define DEV_DATA(dev) \ #define DEV_DATA(dev) \
@ -108,121 +114,87 @@ static int random_byte_get(void)
#if defined(CONFIG_BT_CTLR_FAST_ENC) #if defined(CONFIG_BT_CTLR_FAST_ENC)
#pragma GCC optimize ("Ofast") #pragma GCC optimize ("Ofast")
#endif #endif
static inline u8_t rng_pool_get(struct rng_pool *rng, u8_t octets, u8_t *rand) static u16_t rng_pool_get(struct rng_pool *rngp, u8_t *buf, u16_t len)
{ {
u8_t first, last, avail, remaining, *d, *s; u32_t last = rngp->last;
u32_t mask = rngp->mask;
u8_t *dst = buf;
u32_t first, available;
u32_t other_read_in_progress;
unsigned int key;
__ASSERT_NO_MSG(rng); key = irq_lock();
first = rngp->first_alloc;
first = rng->first; /*
last = rng->last; * The other_read_in_progress is non-zero if rngp->first_read != first,
* which means that lower-priority code (which was interrupted by this
* call) already allocated area for read.
*/
other_read_in_progress = (rngp->first_read ^ first);
d = &rand[octets]; available = (last - first) & mask;
s = &rng->rand[first]; if (available < len) {
len = available;
if (first <= last) {
/* copy octets from contiguous memory */
avail = last - first;
if (octets < avail) {
remaining = avail - octets;
avail = octets;
} else {
remaining = 0;
}
first += avail;
octets -= avail;
while (avail--) {
*(--d) = *s++;
}
rng->first = first;
} else {
/* copy octets from split halves - until end of array */
avail = rng->count - first;
if (octets < avail) {
remaining = avail + last - octets;
avail = octets;
first += avail;
} else {
remaining = last;
first = 0;
}
octets -= avail;
while (avail--) {
*(--d) = *s++;
}
/* copy from beginning of array - until ring buffer last idx */
if (octets && last) {
s = &rng->rand[0];
if (octets < last) {
remaining = last - octets;
last = octets;
} else {
remaining = 0;
}
first = last;
octets -= last;
while (last--) {
*(--d) = *s++;
}
}
rng->first = first;
} }
if (remaining < rng->threshold) { /*
* Move alloc index forward to signal, that part of the buffer is
* now reserved for this call.
*/
rngp->first_alloc = (first + len) & mask;
irq_unlock(key);
while (likely(len--)) {
*dst++ = rngp->buffer[first];
first = (first + 1) & mask;
}
/*
* If this call is the last one accessing the pool, move read index
* to signal that all allocated regions are now read and could be
* overwritten.
*/
if (likely(!other_read_in_progress)) {
key = irq_lock();
rngp->first_read = rngp->first_alloc;
irq_unlock(key);
}
len = dst - buf;
available = available - len;
if (available <= rngp->threshold) {
nrf_rng_task_trigger(NRF_RNG_TASK_START); nrf_rng_task_trigger(NRF_RNG_TASK_START);
} }
return octets; return len;
} }
#pragma GCC pop_options #pragma GCC pop_options
static int rng_pool_put(struct rng_pool *rng, bool store, u8_t byte) static int rng_pool_put(struct rng_pool *rngp, u8_t byte)
{ {
u8_t last; u8_t first = rngp->first_read;
u8_t last = rngp->last;
u8_t mask = rngp->mask;
if (!rng) { /* Signal error if the pool is full. */
if (((last - first) & mask) == mask) {
return -ENOBUFS; return -ENOBUFS;
} }
last = rng->last + 1; rngp->buffer[last] = byte;
if (last == rng->count) { rngp->last = (last + 1) & mask;
last = 0;
}
if (last == rng->first) { return 0;
/* this condition should not happen, but due to probable race, }
* new value could be generated before NRF_RNG task is stopped.
*/
return -ENOBUFS;
}
if (!store) { static void rng_pool_init(struct rng_pool *rngp, u16_t size, u8_t threshold)
return -EBUSY; {
} rngp->first_alloc = 0;
rngp->first_read = 0;
rng->rand[rng->last] = byte; rngp->last = 0;
rng->last = last; rngp->mask = size - 1;
rngp->threshold = threshold;
last = rng->last + 1;
if (last == rng->count) {
last = 0;
}
if (last == rng->first) {
return 0;
}
return -EBUSY;
} }
static void isr(void *arg) static void isr(void *arg)
@ -235,23 +207,15 @@ static void isr(void *arg)
return; return;
} }
ret = rng_pool_put((struct rng_pool *)dev_data->isr, true, byte); ret = rng_pool_put((struct rng_pool *)dev_data->isr, byte);
if (ret != -EBUSY) { if (ret < 0) {
ret = rng_pool_put((struct rng_pool *)dev_data->thr, ret = rng_pool_put((struct rng_pool *)dev_data->thr, byte);
(ret == -ENOBUFS), byte); if (ret < 0) {
nrf_rng_task_trigger(NRF_RNG_TASK_STOP);
}
k_sem_give(&dev_data->sem_sync); k_sem_give(&dev_data->sem_sync);
} }
if (ret != -EBUSY) {
nrf_rng_task_trigger(NRF_RNG_TASK_STOP);
}
}
static void rng_pool_init(struct rng_pool *rng, u8_t len, u8_t threshold)
{
rng->count = len;
rng->threshold = threshold;
rng->first = rng->last = 0;
} }
static int entropy_nrf5_get_entropy(struct device *device, u8_t *buf, u16_t len) static int entropy_nrf5_get_entropy(struct device *device, u8_t *buf, u16_t len)
@ -259,25 +223,21 @@ static int entropy_nrf5_get_entropy(struct device *device, u8_t *buf, u16_t len)
struct entropy_nrf5_dev_data *dev_data = DEV_DATA(device); struct entropy_nrf5_dev_data *dev_data = DEV_DATA(device);
while (len) { while (len) {
u8_t len8; u16_t bytes;
if (len > UINT8_MAX) { k_sem_take(&dev_data->sem_lock, K_FOREVER);
len8 = UINT8_MAX; bytes = rng_pool_get((struct rng_pool *)dev_data->thr,
} else { buf, len);
len8 = len; k_sem_give(&dev_data->sem_lock);
}
len -= len8;
while (len8) { if (bytes == 0) {
k_sem_take(&dev_data->sem_lock, K_FOREVER); /* Pool is empty: Sleep until next interrupt. */
len8 = rng_pool_get((struct rng_pool *)dev_data->thr, k_sem_take(&dev_data->sem_sync, K_FOREVER);
len8, buf); continue;
k_sem_give(&dev_data->sem_lock);
if (len8) {
/* Sleep until next interrupt */
k_sem_take(&dev_data->sem_sync, K_FOREVER);
}
} }
len -= bytes;
buf += bytes;
} }
return 0; return 0;
@ -290,7 +250,7 @@ static int entropy_nrf5_get_entropy_isr(struct device *dev, u8_t *buf, u16_t len
u16_t cnt = len; u16_t cnt = len;
if (!(flags & ENTROPY_BUSYWAIT)) { if (!(flags & ENTROPY_BUSYWAIT)) {
return rng_pool_get((struct rng_pool *)dev_data->isr, len, buf); return rng_pool_get((struct rng_pool *)dev_data->isr, buf, len);
} }
if (len) { if (len) {
@ -351,13 +311,16 @@ static int entropy_nrf5_init(struct device *device)
/* Locking semaphore initialized to 1 (unlocked) */ /* Locking semaphore initialized to 1 (unlocked) */
k_sem_init(&dev_data->sem_lock, 1, 1); k_sem_init(&dev_data->sem_lock, 1, 1);
/* Synching semaphore */ /* Synching semaphore */
k_sem_init(&dev_data->sem_sync, 0, 1); k_sem_init(&dev_data->sem_sync, 0, 1);
rng_pool_init((struct rng_pool *)dev_data->thr, RNG_POOL_THR_LEN, rng_pool_init((struct rng_pool *)dev_data->thr,
CONFIG_ENTROPY_NRF5_THR_THRESHOLD); CONFIG_ENTROPY_NRF5_THR_POOL_SIZE,
rng_pool_init((struct rng_pool *)dev_data->isr, RNG_POOL_ISR_LEN, CONFIG_ENTROPY_NRF5_THR_THRESHOLD);
CONFIG_ENTROPY_NRF5_ISR_THRESHOLD); rng_pool_init((struct rng_pool *)dev_data->isr,
CONFIG_ENTROPY_NRF5_ISR_POOL_SIZE,
CONFIG_ENTROPY_NRF5_ISR_THRESHOLD);
/* Enable or disable bias correction */ /* Enable or disable bias correction */
if (IS_ENABLED(CONFIG_ENTROPY_NRF5_BIAS_CORRECTION)) { if (IS_ENABLED(CONFIG_ENTROPY_NRF5_BIAS_CORRECTION)) {
@ -380,6 +343,6 @@ static int entropy_nrf5_init(struct device *device)
u8_t entropy_nrf_get_entropy_isr(struct device *dev, u8_t *buf, u8_t len) u8_t entropy_nrf_get_entropy_isr(struct device *dev, u8_t *buf, u8_t len)
{ {
ARG_UNUSED(dev); ARG_UNUSED(dev);
return rng_pool_get((struct rng_pool *)entropy_nrf5_data.isr, len, buf); return rng_pool_get((struct rng_pool *)entropy_nrf5_data.isr, buf, len);
} }