| /* |
| * Copyright (c) 2018 Nordic Semiconductor ASA |
| * Copyright (c) 2017 Exati Tecnologia Ltda. |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #include <entropy.h> |
| #include <atomic.h> |
| #include <soc.h> |
| |
| /* |
| * The nRF5 RNG HW has several characteristics that need to be taken |
| * into account by the driver to achieve energy efficient generation |
| * of entropy. |
| * |
| * The RNG does not support continuously DMA'ing entropy into RAM, |
| * values must be read out by the CPU byte-by-byte. But once started, |
| * it will continue to generate bytes until stopped. |
| * |
| * The generation time for byte 0 after starting generation (with BIAS |
| * correction) is: |
| * |
| * nRF51822 - 677us |
| * nRF52810 - 248us |
| * nRF52840 - 248us |
| * |
| * The generation time for byte N >= 1 after starting generation (with |
| * BIAS correction) is: |
| * |
| * nRF51822 - 677us |
| * nRF52810 - 120us |
| * nRF52840 - 120us |
| * |
| * Due to the first byte in a stream of bytes being more costly on |
| * some platforms a "water system" inspired algorithm is used to |
| * ammortize the cost of the first byte. |
| * |
| * The algorithm will delay generation of entropy until the amount of |
| * bytes goes below THRESHOLD, at which point it will generate entropy |
| * until the BUF_LEN limit is reached. |
| * |
| * The entropy level is checked at the end of every consumption of |
| * entropy. |
| * |
| * The algorithm and HW together has these characteristics: |
| * |
| * Setting a low threshold will highly ammortize the extra 120us cost |
| * of the first byte on nRF52. |
| * |
| * Setting a high threshold will minimize the time spent waiting for |
| * entropy. |
| * |
| * To minimize power consumption the threshold should either be set |
| * low or high depending on the HFCLK-usage pattern of other |
| * components. |
| * |
| * If the threshold is set close to the BUF_LEN, and the system |
| * happens to anyway be using the HFCLK for several hundred us after |
| * entropy is requested there will be no extra current-consumption for |
| * keeping clocks running for entropy generation. |
| * |
| */ |
| |
| 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 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) |
| |
| #pragma GCC push_options |
| #if defined(CONFIG_BT_CTLR_FAST_ENC) |
| #pragma GCC optimize ("Ofast") |
| #endif |
| static inline u8_t get(struct rand *rng, u8_t octets, u8_t *rand) |
| { |
| 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; |
| } |
| |
| if (remaining < rng->threshold) { |
| NRF_RNG->TASKS_START = 1; |
| #if defined(CONFIG_BOARD_NRFXX_NWTSIM) |
| NRF_RNG_regw_sideeffects(); |
| #endif |
| } |
| |
| return octets; |
| } |
| #pragma GCC pop_options |
| |
| 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) |
| { |
| struct entropy_nrf5_dev_data *dev_data = DEV_DATA(device); |
| |
| while (len) { |
| u8_t len8; |
| |
| if (len > UINT8_MAX) { |
| len8 = UINT8_MAX; |
| } else { |
| len8 = len; |
| } |
| len -= len8; |
| |
| 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 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 |
| }; |
| |
| 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); |
| } |