Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bluetooth: Replace custom rand driver with standard entropy #6294

Merged
merged 2 commits into from
Mar 14, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 44 additions & 4 deletions drivers/entropy/Kconfig.nrf5
Original file line number Diff line number Diff line change
@@ -1,25 +1,65 @@
# 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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

bump copyright year

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is done.

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
help
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
276 changes: 224 additions & 52 deletions drivers/entropy/entropy_nrf5.c
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -8,89 +8,219 @@
#include <entropy.h>
#include <atomic.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 {
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)
#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)
{
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;
}
#pragma GCC pop_options

static int entropy_nrf5_get_entropy(struct device *device, u8_t *buf, u16_t len)
static int isr(struct rand *rng, bool store)
{
/* Mark the peripheral as being used */
atomic_inc(&DEV_DATA(device)->user_count);
u8_t last;

/* Disable the shortcut that stops the task after a byte is generated */
nrf_rng_shorts_disable(NRF_RNG_SHORT_VALRDY_STOP_MASK);
if (!rng) {
return -ENOBUFS;
}

/* Start the RNG generator peripheral */
nrf_rng_task_trigger(NRF_RNG_TASK_START);
last = rng->last + 1;
if (last == rng->count) {
last = 0;
}

while (len) {
*buf = entropy_nrf5_get_u8();
buf++;
len--;
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;
}

/* 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 (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);
}
if (!store) {
return -EBUSY;
}

return 0;
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 int entropy_nrf5_init(struct device *device)
static void isr_rand(void *arg)
{
/* 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);
struct device *device = arg;

/* Enable or disable bias correction */
if (IS_ENABLED(CONFIG_ENTROPY_NRF5_BIAS_CORRECTION)) {
nrf_rng_error_correction_enable();
} else {
nrf_rng_error_correction_disable();
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,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why this call is made? Is this for flush random value in case of the race condition occurrence?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

^^I mean the second param value - -ENOBUFS might be returned in two cases.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

-ENOBUFS is returned to convey that a new rng value has not been put into ring buffer due to the ISR ring buffer being full (or no ISR ring buffer initialized). Hence, conveyed as "store" param to the next ring buffer (thread).

Same pattern of nested if-clause can be used to scale to any number of ring buffer population.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks. Then i understood this properly. Everything is right here.

(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;

/* Initialize the user count with zero */
atomic_clear(&DEV_DATA(device)->user_count);
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
Expand All @@ -100,3 +230,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);
}
Loading