diff --git a/drivers/entropy/Kconfig.nrf5 b/drivers/entropy/Kconfig.nrf5 index 5ad642841ed..c5bddf77701 100644 --- a/drivers/entropy/Kconfig.nrf5 +++ b/drivers/entropy/Kconfig.nrf5 @@ -1,21 +1,21 @@ # 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. # # SPDX-License-Identifier: Apache-2.0 menuconfig ENTROPY_NRF5_RNG bool "nRF5 RNG driver" - # FIXME: nRF5 RNG driver can't co-exist with Bluetooth's HAL - # implementation yet - depends on ENTROPY_GENERATOR && !BT + depends on ENTROPY_GENERATOR select ENTROPY_HAS_DRIVER help This option enables the RNG peripheral, which is a random number generator, based on internal thermal noise, that provides a random 8-bit value to the host when read. +if ENTROPY_NRF5_RNG + config ENTROPY_NRF5_BIAS_CORRECTION bool "Enable bias correction (uniform distribution)" depends on ENTROPY_NRF5_RNG @@ -23,3 +23,43 @@ config ENTROPY_NRF5_BIAS_CORRECTION This option enables the RNG bias correction, which guarantees a uniform distribution of 0 and 1. When this option is enabled, the time 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 diff --git a/drivers/entropy/entropy_nrf5.c b/drivers/entropy/entropy_nrf5.c index 0fc209607eb..c93f31db5a0 100644 --- a/drivers/entropy/entropy_nrf5.c +++ b/drivers/entropy/entropy_nrf5.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017 Nordic Semiconductor ASA + * Copyright (c) 2018 Nordic Semiconductor ASA * Copyright (c) 2017 Exati Tecnologia Ltda. * * SPDX-License-Identifier: Apache-2.0 @@ -8,89 +8,214 @@ #include #include #include +#include + +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 { - 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) \ ((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)) { - __WFE(); - __SEV(); - __WFE(); + u8_t first, last, avail, remaining, *d, *s; + + __ASSERT_NO_MSG(rng); + + 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 - * wake up the core when the VALRDY event occurs again. */ - NVIC_ClearPendingIRQ(RNG_IRQn); + if (remaining < rng->threshold) { + NRF_RNG->TASKS_START = 1; +#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) { - /* Mark the peripheral as being used */ - 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); + struct entropy_nrf5_dev_data *dev_data = DEV_DATA(device); while (len) { - *buf = entropy_nrf5_get_u8(); - buf++; - len--; - } + u8_t len8; - /* Only stop the RNG generator peripheral if we're the last user */ - if (atomic_dec(&DEV_DATA(device)->user_count) == 1) { - /* Disable the peripheral on the next VALRDY event */ - nrf_rng_shorts_enable(NRF_RNG_SHORT_VALRDY_STOP_MASK); + if (len > UINT8_MAX) { + len8 = UINT8_MAX; + } else { + len8 = len; + } + len -= len8; - if (atomic_get(&DEV_DATA(device)->user_count) != 0) { - /* Race condition: another thread started to use - * the peripheral while we were disabling it. - * Enable the peripheral again - */ - nrf_rng_shorts_disable(NRF_RNG_SHORT_VALRDY_STOP_MASK); - nrf_rng_task_trigger(NRF_RNG_TASK_START); + while (len8) { + k_sem_take(&dev_data->sem_lock, K_FOREVER); + len8 = get((struct rand *)dev_data->thr, len8, buf); + k_sem_give(&dev_data->sem_lock); + if (len8) { + /* Sleep until next interrupt */ + k_sem_take(&dev_data->sem_sync, K_FOREVER); + } } } 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 int entropy_nrf5_init(struct device *device); static const struct entropy_driver_api entropy_nrf5_api_funcs = { .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, PRE_KERNEL_1, CONFIG_KERNEL_INIT_PRIORITY_DEVICE, &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); +} diff --git a/include/drivers/entropy/nrf5_entropy.h b/include/drivers/entropy/nrf5_entropy.h new file mode 100644 index 00000000000..0eba0e5eb3d --- /dev/null +++ b/include/drivers/entropy/nrf5_entropy.h @@ -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_ */