drivers: entropy: Port the LL rand driver to entropy

The rand driver present in the BLE Link Layer is able to provide
entropy for both threads and ISRs, and so it is more suited to be used
as a generic nRF5x entropy driver instead of the current one. This will
allow applications and the Link Layer to use the same driver without
duplicating it.

Signed-off-by: Carles Cufi <carles.cufi@nordicsemi.no>
Signed-off-by: Vinayak Kariappa Chettimada <vich@nordicsemi.no>
This commit is contained in:
Carles Cufi 2018-02-20 21:24:07 +01:00 committed by Carles Cufí
commit 813b41beec
3 changed files with 290 additions and 62 deletions

View file

@ -1,21 +1,21 @@
# Kconfig.nrf5 - nRF5 entropy generator driver configuration # Kconfig.nrf5 - nRF5 entropy generator driver configuration
# #
# Copyright (c) 2017 Nordic Semiconductor ASA # Copyright (c) 2018 Nordic Semiconductor ASA
# Copyright (c) 2017 Exati Tecnologia Ltda. # Copyright (c) 2017 Exati Tecnologia Ltda.
# #
# SPDX-License-Identifier: Apache-2.0 # SPDX-License-Identifier: Apache-2.0
menuconfig ENTROPY_NRF5_RNG menuconfig ENTROPY_NRF5_RNG
bool "nRF5 RNG driver" bool "nRF5 RNG driver"
# FIXME: nRF5 RNG driver can't co-exist with Bluetooth's HAL depends on ENTROPY_GENERATOR
# implementation yet
depends on ENTROPY_GENERATOR && !BT
select ENTROPY_HAS_DRIVER select ENTROPY_HAS_DRIVER
help help
This option enables the RNG peripheral, which is a random number This option enables the RNG peripheral, which is a random number
generator, based on internal thermal noise, that provides a generator, based on internal thermal noise, that provides a
random 8-bit value to the host when read. random 8-bit value to the host when read.
if ENTROPY_NRF5_RNG
config ENTROPY_NRF5_BIAS_CORRECTION config ENTROPY_NRF5_BIAS_CORRECTION
bool "Enable bias correction (uniform distribution)" bool "Enable bias correction (uniform distribution)"
depends on ENTROPY_NRF5_RNG depends on ENTROPY_NRF5_RNG
@ -23,3 +23,43 @@ config ENTROPY_NRF5_BIAS_CORRECTION
This option enables the RNG bias correction, which guarantees a This option enables the RNG bias correction, which guarantees a
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_THRESHOLD
int "Thread-mode low-water threshold"
range 4 255
help
Low-water threshold in bytes to trigger entropy generation for
thread mode consumers.
config ENTROPY_NRF5_ISR_THRESHOLD
int "ISR low-water threshold"
range 12 255
help
Low-water threshold in bytes to trigger entropy generation for
ISR consumers.
config ENTROPY_NRF5_THR_BUF_LEN
int "Thread-mode buffer length"
range ENTROPY_NRF5_THR_THRESHOLD 255
help
Buffer length in bytes to pre-store entropy generation 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 to pre-store entropy generation for
ISR consumers.
config ENTROPY_NRF5_PRI
int "RNG interrupt priority"
depends on ENTROPY_NRF5_RNG
range 0 2 if SOC_SERIES_NRF51X
range 0 5 if SOC_SERIES_NRF52X
default 2 if SOC_SERIES_NRF51X
default 5 if SOC_SERIES_NRF52X
help
nRF5X RNG IRQ priority.
endif #ENTROPY_NRF5_RNG

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2017 Nordic Semiconductor ASA * Copyright (c) 2018 Nordic Semiconductor ASA
* Copyright (c) 2017 Exati Tecnologia Ltda. * Copyright (c) 2017 Exati Tecnologia Ltda.
* *
* SPDX-License-Identifier: Apache-2.0 * SPDX-License-Identifier: Apache-2.0
@ -8,89 +8,214 @@
#include <entropy.h> #include <entropy.h>
#include <atomic.h> #include <atomic.h>
#include <nrf_rng.h> #include <nrf_rng.h>
#include <soc.h>
struct rand {
u8_t count;
u8_t threshold;
u8_t first;
u8_t last;
u8_t rand[0];
};
#define RAND_DEFINE(name, len) u8_t name[sizeof(struct rand) + len] __aligned(4)
#define RAND_THREAD_LEN (CONFIG_ENTROPY_NRF5_THR_BUF_LEN + 1)
#define RAND_ISR_LEN (CONFIG_ENTROPY_NRF5_ISR_BUF_LEN + 1)
struct entropy_nrf5_dev_data { struct entropy_nrf5_dev_data {
atomic_t user_count; struct k_sem sem_lock;
struct k_sem sem_sync;
RAND_DEFINE(thr, RAND_THREAD_LEN);
RAND_DEFINE(isr, RAND_ISR_LEN);
}; };
#define DEV_DATA(dev) \ #define DEV_DATA(dev) \
((struct entropy_nrf5_dev_data *)(dev)->driver_data) ((struct entropy_nrf5_dev_data *)(dev)->driver_data)
static inline u8_t entropy_nrf5_get_u8(void) static u8_t get(struct rand *rng, u8_t octets, u8_t *rand)
{ {
while (!nrf_rng_event_get(NRF_RNG_EVENT_VALRDY)) { u8_t first, last, avail, remaining, *d, *s;
__WFE();
__SEV(); __ASSERT_NO_MSG(rng);
__WFE();
first = rng->first;
last = rng->last;
d = &rand[octets];
s = &rng->rand[first];
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;
} }
nrf_rng_event_clear(NRF_RNG_EVENT_VALRDY);
/* Clear the Pending status of the RNG interrupt so that it could if (remaining < rng->threshold) {
* wake up the core when the VALRDY event occurs again. */ NRF_RNG->TASKS_START = 1;
NVIC_ClearPendingIRQ(RNG_IRQn); #if defined(CONFIG_BOARD_NRFXX_NWTSIM)
NRF_RNG_regw_sideeffects();
#endif
}
return nrf_rng_random_value_get(); return octets;
}
static int isr(struct rand *rng, bool store)
{
u8_t last;
if (!rng) {
return -ENOBUFS;
}
last = rng->last + 1;
if (last == rng->count) {
last = 0;
}
if (last == rng->first) {
/* 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) {
return -EBUSY;
}
rng->rand[rng->last] = NRF_RNG->VALUE;
rng->last = last;
last = rng->last + 1;
if (last == rng->count) {
last = 0;
}
if (last == rng->first) {
return 0;
}
return -EBUSY;
}
static void isr_rand(void *arg)
{
struct device *device = arg;
if (NRF_RNG->EVENTS_VALRDY) {
struct entropy_nrf5_dev_data *dev_data = DEV_DATA(device);
int ret;
ret = isr((struct rand *)dev_data->isr, true);
if (ret != -EBUSY) {
ret = isr((struct rand *)dev_data->thr,
(ret == -ENOBUFS));
k_sem_give(&dev_data->sem_sync);
}
NRF_RNG->EVENTS_VALRDY = 0;
if (ret != -EBUSY) {
NRF_RNG->TASKS_STOP = 1;
#if defined(CONFIG_BOARD_NRFXX_NWTSIM)
NRF_RNG_regw_sideeffects();
#endif
}
}
}
static void init(struct rand *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)
{ {
/* Mark the peripheral as being used */ struct entropy_nrf5_dev_data *dev_data = DEV_DATA(device);
atomic_inc(&DEV_DATA(device)->user_count);
/* Disable the shortcut that stops the task after a byte is generated */
nrf_rng_shorts_disable(NRF_RNG_SHORT_VALRDY_STOP_MASK);
/* Start the RNG generator peripheral */
nrf_rng_task_trigger(NRF_RNG_TASK_START);
while (len) { while (len) {
*buf = entropy_nrf5_get_u8(); u8_t len8;
buf++;
len--;
}
/* Only stop the RNG generator peripheral if we're the last user */ if (len > UINT8_MAX) {
if (atomic_dec(&DEV_DATA(device)->user_count) == 1) { len8 = UINT8_MAX;
/* Disable the peripheral on the next VALRDY event */ } else {
nrf_rng_shorts_enable(NRF_RNG_SHORT_VALRDY_STOP_MASK); len8 = len;
}
len -= len8;
if (atomic_get(&DEV_DATA(device)->user_count) != 0) { while (len8) {
/* Race condition: another thread started to use k_sem_take(&dev_data->sem_lock, K_FOREVER);
* the peripheral while we were disabling it. len8 = get((struct rand *)dev_data->thr, len8, buf);
* Enable the peripheral again k_sem_give(&dev_data->sem_lock);
*/ if (len8) {
nrf_rng_shorts_disable(NRF_RNG_SHORT_VALRDY_STOP_MASK); /* Sleep until next interrupt */
nrf_rng_task_trigger(NRF_RNG_TASK_START); k_sem_take(&dev_data->sem_sync, K_FOREVER);
}
} }
} }
return 0; return 0;
} }
static int entropy_nrf5_init(struct device *device)
{
/* Enable the RNG interrupt to be generated on the VALRDY event,
* but do not enable this interrupt in NVIC to be serviced.
* When the interrupt enters the Pending state it will set internal
* event (SEVONPEND is activated by kernel) and wake up the core
* if it was suspended by WFE. And that's enough. */
nrf_rng_int_enable(NRF_RNG_INT_VALRDY_MASK);
NVIC_ClearPendingIRQ(RNG_IRQn);
/* Enable or disable bias correction */
if (IS_ENABLED(CONFIG_ENTROPY_NRF5_BIAS_CORRECTION)) {
nrf_rng_error_correction_enable();
} else {
nrf_rng_error_correction_disable();
}
/* Initialize the user count with zero */
atomic_clear(&DEV_DATA(device)->user_count);
return 0;
}
static struct entropy_nrf5_dev_data entropy_nrf5_data; static struct entropy_nrf5_dev_data entropy_nrf5_data;
static int entropy_nrf5_init(struct device *device);
static const struct entropy_driver_api entropy_nrf5_api_funcs = { static const struct entropy_driver_api entropy_nrf5_api_funcs = {
.get_entropy = entropy_nrf5_get_entropy .get_entropy = entropy_nrf5_get_entropy
@ -100,3 +225,45 @@ DEVICE_AND_API_INIT(entropy_nrf5, CONFIG_ENTROPY_NAME,
entropy_nrf5_init, &entropy_nrf5_data, NULL, entropy_nrf5_init, &entropy_nrf5_data, NULL,
PRE_KERNEL_1, CONFIG_KERNEL_INIT_PRIORITY_DEVICE, PRE_KERNEL_1, CONFIG_KERNEL_INIT_PRIORITY_DEVICE,
&entropy_nrf5_api_funcs); &entropy_nrf5_api_funcs);
static int entropy_nrf5_init(struct device *device)
{
struct entropy_nrf5_dev_data *dev_data = DEV_DATA(device);
/* Locking semaphore initialized to 1 (unlocked) */
k_sem_init(&dev_data->sem_lock, 1, 1);
/* Synching semaphore */
k_sem_init(&dev_data->sem_sync, 0, 1);
init((struct rand *)dev_data->thr, RAND_THREAD_LEN,
CONFIG_ENTROPY_NRF5_THR_THRESHOLD);
init((struct rand *)dev_data->isr, RAND_ISR_LEN,
CONFIG_ENTROPY_NRF5_ISR_THRESHOLD);
/* Enable or disable bias correction */
if (IS_ENABLED(CONFIG_ENTROPY_NRF5_BIAS_CORRECTION)) {
NRF_RNG->CONFIG |= RNG_CONFIG_DERCEN_Msk;
} else {
NRF_RNG->CONFIG &= ~RNG_CONFIG_DERCEN_Msk;
}
NRF_RNG->EVENTS_VALRDY = 0;
NRF_RNG->INTENSET = RNG_INTENSET_VALRDY_Msk;
NRF_RNG->TASKS_START = 1;
#if defined(CONFIG_BOARD_NRFXX_NWTSIM)
NRF_RNG_regw_sideeffects();
#endif
IRQ_CONNECT(NRF5_IRQ_RNG_IRQn, CONFIG_ENTROPY_NRF5_PRI, isr_rand,
DEVICE_GET(entropy_nrf5), 0);
irq_enable(NRF5_IRQ_RNG_IRQn);
return 0;
}
u8_t entropy_get_entropy_isr(struct device *dev, u8_t *buf, u8_t len)
{
ARG_UNUSED(dev);
return get((struct rand *)entropy_nrf5_data.isr, len, buf);
}

View file

@ -0,0 +1,21 @@
/*
* Copyright (c) 2018 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef _NRF5_ENTROPY_H_
#define _NRF5_ENTROPY_H_
/**
* @brief Fills a buffer with entropy in a non-blocking manner.
* Callable from ISRs.
*
* @param dev Pointer to the device structure.
* @param buf Buffer to fill with entropy.
* @param len Buffer length.
* @retval number of bytes filled with entropy.
*/
u8_t entropy_get_entropy_isr(struct device *dev, u8_t *buf, u8_t len);
#endif /* _NRF5_ENTROPY_H_ */