blob: f8555dbc0e17ed894f60f1f3a32b07cf64ce40bf [file] [log] [blame]
/*
* Copyright (c) 2016 Nordic Semiconductor ASA
* Copyright (c) 2016 Vinayak Kariappa Chettimada
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <soc.h>
#include "hal/rand.h"
#include "common/log.h"
#include "hal/debug.h"
struct rand {
u8_t count;
u8_t threshold;
u8_t first;
u8_t last;
u8_t rand[1];
};
static struct rand *rng_isr;
static struct rand *rng_thr;
static void init(struct rand **rng, u8_t *context, u8_t len, u8_t threshold)
{
struct rand *p;
LL_ASSERT(len > (offsetof(struct rand, rand) + threshold));
*rng = (struct rand *)context;
p = *rng;
p->count = len - offsetof(struct rand, rand);
p->threshold = threshold;
p->first = p->last = 0;
if (!rng_isr || !rng_thr) {
NRF_RNG->CONFIG = RNG_CONFIG_DERCEN_Msk;
NRF_RNG->EVENTS_VALRDY = 0;
NRF_RNG->INTENSET = RNG_INTENSET_VALRDY_Msk;
NRF_RNG->TASKS_START = 1;
}
}
void rand_init(u8_t *context, u8_t context_len, u8_t threshold)
{
init(&rng_thr, context, context_len, threshold);
}
void rand_isr_init(u8_t *context, u8_t context_len, u8_t threshold)
{
init(&rng_isr, context, context_len, threshold);
}
static size_t get(struct rand *rng, size_t octets, u8_t *rand)
{
u8_t first, last, remaining;
LL_ASSERT(rng);
first = rng->first;
last = rng->last;
if (first <= last) {
u8_t *d, *s;
u8_t avail;
d = &rand[octets];
s = &rng->rand[first];
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 {
u8_t *d, *s;
u8_t avail;
d = &rand[octets];
s = &rng->rand[first];
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++;
}
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;
}
return octets;
}
size_t rand_get(size_t octets, u8_t *rand)
{
return get(rng_thr, octets, rand);
}
size_t rand_isr_get(size_t octets, u8_t *rand)
{
return get(rng_isr, octets, rand);
}
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;
}
void isr_rand(void *param)
{
ARG_UNUSED(param);
if (NRF_RNG->EVENTS_VALRDY) {
int ret;
ret = isr(rng_isr, true);
if (ret != -EBUSY) {
ret = isr(rng_thr, (ret == -ENOBUFS));
}
NRF_RNG->EVENTS_VALRDY = 0;
if (ret != -EBUSY) {
NRF_RNG->TASKS_STOP = 1;
}
}
}