1263 lines
30 KiB
C
1263 lines
30 KiB
C
/* $NetBSD: kern_rndq.c,v 1.27 2014/08/11 14:07:55 riastradh Exp $ */
|
|
|
|
/*-
|
|
* Copyright (c) 1997-2013 The NetBSD Foundation, Inc.
|
|
* All rights reserved.
|
|
*
|
|
* This code is derived from software contributed to The NetBSD Foundation
|
|
* by Michael Graff <explorer@flame.org> and Thor Lancelot Simon.
|
|
* This code uses ideas and algorithms from the Linux driver written by
|
|
* Ted Ts'o.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions
|
|
* are met:
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the distribution.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
|
|
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
|
|
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
|
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
|
|
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
|
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
|
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
|
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
* POSSIBILITY OF SUCH DAMAGE.
|
|
*/
|
|
|
|
#include <sys/cdefs.h>
|
|
__KERNEL_RCSID(0, "$NetBSD: kern_rndq.c,v 1.27 2014/08/11 14:07:55 riastradh Exp $");
|
|
|
|
#include <sys/param.h>
|
|
#include <sys/atomic.h>
|
|
#include <sys/ioctl.h>
|
|
#include <sys/fcntl.h>
|
|
#include <sys/select.h>
|
|
#include <sys/poll.h>
|
|
#include <sys/kmem.h>
|
|
#include <sys/mutex.h>
|
|
#include <sys/proc.h>
|
|
#include <sys/kernel.h>
|
|
#include <sys/conf.h>
|
|
#include <sys/systm.h>
|
|
#include <sys/callout.h>
|
|
#include <sys/intr.h>
|
|
#include <sys/rnd.h>
|
|
#include <sys/rndsink.h>
|
|
#include <sys/vnode.h>
|
|
#include <sys/pool.h>
|
|
#include <sys/kauth.h>
|
|
#include <sys/once.h>
|
|
#include <sys/rngtest.h>
|
|
#include <sys/cpu.h> /* XXX temporary, see rnd_detach_source */
|
|
|
|
#include <dev/rnd_private.h>
|
|
|
|
#if defined(__HAVE_CPU_COUNTER)
|
|
#include <machine/cpu_counter.h>
|
|
#endif
|
|
|
|
#ifdef RND_DEBUG
|
|
#define DPRINTF(l,x) if (rnd_debug & (l)) rnd_printf x
|
|
int rnd_debug = 0;
|
|
#else
|
|
#define DPRINTF(l,x)
|
|
#endif
|
|
|
|
#define RND_DEBUG_WRITE 0x0001
|
|
#define RND_DEBUG_READ 0x0002
|
|
#define RND_DEBUG_IOCTL 0x0004
|
|
#define RND_DEBUG_SNOOZE 0x0008
|
|
|
|
/*
|
|
* list devices attached
|
|
*/
|
|
#if 0
|
|
#define RND_VERBOSE
|
|
#endif
|
|
|
|
#ifdef RND_VERBOSE
|
|
static unsigned int deltacnt;
|
|
#endif
|
|
|
|
/*
|
|
* This is a little bit of state information attached to each device that we
|
|
* collect entropy from. This is simply a collection buffer, and when it
|
|
* is full it will be "detached" from the source and added to the entropy
|
|
* pool after entropy is distilled as much as possible.
|
|
*/
|
|
#define RND_SAMPLE_COUNT 64 /* collect N samples, then compress */
|
|
typedef struct _rnd_sample_t {
|
|
SIMPLEQ_ENTRY(_rnd_sample_t) next;
|
|
krndsource_t *source;
|
|
int cursor;
|
|
int entropy;
|
|
uint32_t ts[RND_SAMPLE_COUNT];
|
|
u_int32_t values[RND_SAMPLE_COUNT];
|
|
} rnd_sample_t;
|
|
|
|
/*
|
|
* The event queue. Fields are altered at an interrupt level.
|
|
* All accesses must be protected with the mutex.
|
|
*/
|
|
SIMPLEQ_HEAD(, _rnd_sample_t) rnd_samples;
|
|
kmutex_t rnd_mtx;
|
|
|
|
/*
|
|
* Memory pool for sample buffers
|
|
*/
|
|
static pool_cache_t rnd_mempc;
|
|
|
|
/*
|
|
* Our random pool. This is defined here rather than using the general
|
|
* purpose one defined in rndpool.c.
|
|
*
|
|
* Samples are collected and queued into a separate mutex-protected queue
|
|
* (rnd_samples, see above), and processed in a timeout routine; therefore,
|
|
* the mutex protecting the random pool is at IPL_SOFTCLOCK() as well.
|
|
*/
|
|
rndpool_t rnd_pool;
|
|
kmutex_t rndpool_mtx;
|
|
kcondvar_t rndpool_cv;
|
|
|
|
/*
|
|
* This source is used to easily "remove" queue entries when the source
|
|
* which actually generated the events is going away.
|
|
*/
|
|
static krndsource_t rnd_source_no_collect = {
|
|
/* LIST_ENTRY list */
|
|
.name = { 'N', 'o', 'C', 'o', 'l', 'l', 'e', 'c', 't',
|
|
0, 0, 0, 0, 0, 0, 0 },
|
|
.total = 0,
|
|
.type = RND_TYPE_UNKNOWN,
|
|
.flags = (RND_FLAG_NO_COLLECT |
|
|
RND_FLAG_NO_ESTIMATE),
|
|
.state = NULL,
|
|
.test_cnt = 0,
|
|
.test = NULL
|
|
};
|
|
|
|
static krndsource_t rnd_source_anonymous = {
|
|
/* LIST_ENTRY list */
|
|
.name = { 'A', 'n', 'o', 'n', 'y', 'm', 'o', 'u', 's',
|
|
0, 0, 0, 0, 0, 0, 0 },
|
|
.total = 0,
|
|
.type = RND_TYPE_UNKNOWN,
|
|
.flags = (RND_FLAG_COLLECT_TIME|
|
|
RND_FLAG_COLLECT_VALUE|
|
|
RND_FLAG_ESTIMATE_TIME),
|
|
.state = NULL,
|
|
.test_cnt = 0,
|
|
.test = NULL
|
|
};
|
|
|
|
krndsource_t rnd_printf_source, rnd_autoconf_source;
|
|
|
|
void *rnd_process, *rnd_wakeup;
|
|
struct callout skew_callout;
|
|
|
|
void rnd_wakeup_readers(void);
|
|
static inline uint32_t rnd_counter(void);
|
|
static void rnd_intr(void *);
|
|
static void rnd_wake(void *);
|
|
static void rnd_process_events(void);
|
|
u_int32_t rnd_extract_data_locked(void *, u_int32_t, u_int32_t); /* XXX */
|
|
static void rnd_add_data_ts(krndsource_t *, const void *const,
|
|
uint32_t, uint32_t, uint32_t);
|
|
static inline void rnd_schedule_process(void);
|
|
|
|
int rnd_ready = 0;
|
|
int rnd_initial_entropy = 0;
|
|
int rnd_printing = 0;
|
|
|
|
#ifdef DIAGNOSTIC
|
|
static int rnd_tested = 0;
|
|
static rngtest_t rnd_rt;
|
|
static uint8_t rnd_testbits[sizeof(rnd_rt.rt_b)];
|
|
#endif
|
|
|
|
LIST_HEAD(, krndsource) rnd_sources;
|
|
|
|
rndsave_t *boot_rsp;
|
|
|
|
static inline void
|
|
rnd_printf(const char *fmt, ...)
|
|
{
|
|
va_list ap;
|
|
|
|
membar_consumer();
|
|
if (rnd_printing) {
|
|
return;
|
|
}
|
|
rnd_printing = 1;
|
|
membar_producer();
|
|
va_start(ap, fmt);
|
|
vprintf(fmt, ap);
|
|
va_end(ap);
|
|
rnd_printing = 0;
|
|
}
|
|
|
|
void
|
|
rnd_init_softint(void) {
|
|
rnd_process = softint_establish(SOFTINT_SERIAL|SOFTINT_MPSAFE,
|
|
rnd_intr, NULL);
|
|
rnd_wakeup = softint_establish(SOFTINT_CLOCK|SOFTINT_MPSAFE,
|
|
rnd_wake, NULL);
|
|
rnd_schedule_process();
|
|
}
|
|
|
|
/*
|
|
* Generate a 64-bit counter.
|
|
*/
|
|
static inline uint32_t
|
|
rnd_counter(void)
|
|
{
|
|
struct timespec ts;
|
|
uint32_t ret;
|
|
|
|
#if defined(__HAVE_CPU_COUNTER)
|
|
return (cpu_counter32());
|
|
#endif
|
|
if (rnd_ready) {
|
|
nanouptime(&ts);
|
|
ret = ts.tv_sec;
|
|
ret *= (uint32_t)1000000000;
|
|
ret += ts.tv_nsec;
|
|
return ret;
|
|
}
|
|
/* when called from rnd_init, its too early to call nanotime safely */
|
|
return (0);
|
|
}
|
|
|
|
/*
|
|
* We may be called from low IPL -- protect our softint.
|
|
*/
|
|
|
|
static inline void
|
|
rnd_schedule_softint(void *softint)
|
|
{
|
|
kpreempt_disable();
|
|
softint_schedule(softint);
|
|
kpreempt_enable();
|
|
}
|
|
|
|
static inline void
|
|
rnd_schedule_process(void)
|
|
{
|
|
if (__predict_true(rnd_process)) {
|
|
rnd_schedule_softint(rnd_process);
|
|
return;
|
|
}
|
|
rnd_process_events();
|
|
}
|
|
|
|
static inline void
|
|
rnd_schedule_wakeup(void)
|
|
{
|
|
if (__predict_true(rnd_wakeup)) {
|
|
rnd_schedule_softint(rnd_wakeup);
|
|
return;
|
|
}
|
|
rnd_wakeup_readers();
|
|
}
|
|
|
|
/*
|
|
* Tell any sources with "feed me" callbacks that we are hungry.
|
|
*/
|
|
void
|
|
rnd_getmore(size_t byteswanted)
|
|
{
|
|
krndsource_t *rs;
|
|
|
|
KASSERT(mutex_owned(&rndpool_mtx));
|
|
|
|
LIST_FOREACH(rs, &rnd_sources, list) {
|
|
if (rs->flags & RND_FLAG_HASCB) {
|
|
KASSERT(rs->get != NULL);
|
|
KASSERT(rs->getarg != NULL);
|
|
rs->get(byteswanted, rs->getarg);
|
|
#ifdef RND_VERBOSE
|
|
rnd_printf("rnd: asking source %s for %zu bytes\n",
|
|
rs->name, byteswanted);
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Check to see if there are readers waiting on us. If so, kick them.
|
|
*/
|
|
void
|
|
rnd_wakeup_readers(void)
|
|
{
|
|
|
|
/*
|
|
* XXX This bookkeeping shouldn't be here -- this is not where
|
|
* the rnd_empty/rnd_initial_entropy state change actually
|
|
* happens.
|
|
*/
|
|
mutex_spin_enter(&rndpool_mtx);
|
|
const size_t entropy_count = rndpool_get_entropy_count(&rnd_pool);
|
|
if (entropy_count < RND_ENTROPY_THRESHOLD * 8) {
|
|
rnd_empty = 1;
|
|
mutex_spin_exit(&rndpool_mtx);
|
|
return;
|
|
} else {
|
|
#ifdef RND_VERBOSE
|
|
if (__predict_false(!rnd_initial_entropy))
|
|
rnd_printf("rnd: have initial entropy (%zu)\n",
|
|
entropy_count);
|
|
#endif
|
|
rnd_empty = 0;
|
|
rnd_initial_entropy = 1;
|
|
}
|
|
mutex_spin_exit(&rndpool_mtx);
|
|
|
|
rndsinks_distribute();
|
|
}
|
|
|
|
/*
|
|
* Use the timing/value of the event to estimate the entropy gathered.
|
|
* If all the differentials (first, second, and third) are non-zero, return
|
|
* non-zero. If any of these are zero, return zero.
|
|
*/
|
|
static inline uint32_t
|
|
rnd_delta_estimate(rnd_delta_t *d, uint32_t v, int32_t delta)
|
|
{
|
|
int32_t delta2, delta3;
|
|
|
|
d->insamples++;
|
|
|
|
/*
|
|
* Calculate the second and third order differentials
|
|
*/
|
|
delta2 = d->dx - delta;
|
|
if (delta2 < 0)
|
|
delta2 = -delta2;
|
|
|
|
delta3 = d->d2x - delta2;
|
|
if (delta3 < 0)
|
|
delta3 = -delta3;
|
|
|
|
d->x = v;
|
|
d->dx = delta;
|
|
d->d2x = delta2;
|
|
|
|
/*
|
|
* If any delta is 0, we got no entropy. If all are non-zero, we
|
|
* might have something.
|
|
*/
|
|
if (delta == 0 || delta2 == 0 || delta3 == 0)
|
|
return (0);
|
|
|
|
d->outbits++;
|
|
return (1);
|
|
}
|
|
|
|
/*
|
|
* Delta estimator for 32-bit timeestamps. Must handle wrap.
|
|
*/
|
|
static inline uint32_t
|
|
rnd_dt_estimate(krndsource_t *rs, uint32_t t)
|
|
{
|
|
int32_t delta;
|
|
uint32_t ret;
|
|
rnd_delta_t *d = &rs->time_delta;
|
|
|
|
if (t < d->x) {
|
|
delta = UINT32_MAX - d->x + t;
|
|
} else {
|
|
delta = d->x - t;
|
|
}
|
|
|
|
if (delta < 0) {
|
|
delta = -delta;
|
|
}
|
|
|
|
ret = rnd_delta_estimate(d, t, delta);
|
|
|
|
KASSERT(d->x == t);
|
|
KASSERT(d->dx == delta);
|
|
#ifdef RND_VERBOSE
|
|
if (deltacnt++ % 1151 == 0) {
|
|
rnd_printf("rnd_dt_estimate: %s x = %lld, dx = %lld, "
|
|
"d2x = %lld\n", rs->name,
|
|
(int)d->x, (int)d->dx, (int)d->d2x);
|
|
}
|
|
#endif
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* Delta estimator for 32 or bit values. "Wrap" isn't.
|
|
*/
|
|
static inline uint32_t
|
|
rnd_dv_estimate(krndsource_t *rs, uint32_t v)
|
|
{
|
|
int32_t delta;
|
|
uint32_t ret;
|
|
rnd_delta_t *d = &rs->value_delta;
|
|
|
|
delta = d->x - v;
|
|
|
|
if (delta < 0) {
|
|
delta = -delta;
|
|
}
|
|
ret = rnd_delta_estimate(d, v, (uint32_t)delta);
|
|
|
|
KASSERT(d->x == v);
|
|
KASSERT(d->dx == delta);
|
|
#ifdef RND_VERBOSE
|
|
if (deltacnt++ % 1151 == 0) {
|
|
rnd_printf("rnd_dv_estimate: %s x = %lld, dx = %lld, "
|
|
" d2x = %lld\n", rs->name,
|
|
(long long int)d->x,
|
|
(long long int)d->dx,
|
|
(long long int)d->d2x);
|
|
}
|
|
#endif
|
|
return ret;
|
|
}
|
|
|
|
#if defined(__HAVE_CPU_COUNTER)
|
|
static void
|
|
rnd_skew(void *arg)
|
|
{
|
|
static krndsource_t skewsrc;
|
|
static int live, flipflop;
|
|
|
|
/*
|
|
* Only one instance of this callout will ever be scheduled
|
|
* at a time (it is only ever scheduled by itself). So no
|
|
* locking is required here.
|
|
*/
|
|
|
|
/*
|
|
* Even on systems with seemingly stable clocks, the
|
|
* delta-time entropy estimator seems to think we get 1 bit here
|
|
* about every 2 calls. That seems like too much. Instead,
|
|
* we feed the rnd_counter() value to the value estimator as well,
|
|
* to take advantage of the additional LZ test on estimated values.
|
|
*
|
|
*/
|
|
if (__predict_false(!live)) {
|
|
rnd_attach_source(&skewsrc, "callout", RND_TYPE_SKEW,
|
|
RND_FLAG_COLLECT_VALUE|
|
|
RND_FLAG_ESTIMATE_VALUE);
|
|
live = 1;
|
|
}
|
|
|
|
flipflop = !flipflop;
|
|
|
|
if (flipflop) {
|
|
rnd_add_uint32(&skewsrc, rnd_counter());
|
|
callout_schedule(&skew_callout, hz / 10);
|
|
} else {
|
|
callout_schedule(&skew_callout, 1);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
* initialize the global random pool for our use.
|
|
* rnd_init() must be called very early on in the boot process, so
|
|
* the pool is ready for other devices to attach as sources.
|
|
*/
|
|
void
|
|
rnd_init(void)
|
|
{
|
|
uint32_t c;
|
|
|
|
if (rnd_ready)
|
|
return;
|
|
|
|
mutex_init(&rnd_mtx, MUTEX_DEFAULT, IPL_VM);
|
|
rndsinks_init();
|
|
|
|
/*
|
|
* take a counter early, hoping that there's some variance in
|
|
* the following operations
|
|
*/
|
|
c = rnd_counter();
|
|
|
|
LIST_INIT(&rnd_sources);
|
|
SIMPLEQ_INIT(&rnd_samples);
|
|
|
|
rndpool_init(&rnd_pool);
|
|
mutex_init(&rndpool_mtx, MUTEX_DEFAULT, IPL_VM);
|
|
cv_init(&rndpool_cv, "rndread");
|
|
|
|
rnd_mempc = pool_cache_init(sizeof(rnd_sample_t), 0, 0, 0,
|
|
"rndsample", NULL, IPL_VM,
|
|
NULL, NULL, NULL);
|
|
|
|
/*
|
|
* Set resource limit. The rnd_process_events() function
|
|
* is called every tick and process the sample queue.
|
|
* Without limitation, if a lot of rnd_add_*() are called,
|
|
* all kernel memory may be eaten up.
|
|
*/
|
|
pool_cache_sethardlimit(rnd_mempc, RND_POOLBITS, NULL, 0);
|
|
|
|
/*
|
|
* Mix *something*, *anything* into the pool to help it get started.
|
|
* However, it's not safe for rnd_counter() to call microtime() yet,
|
|
* so on some platforms we might just end up with zeros anyway.
|
|
* XXX more things to add would be nice.
|
|
*/
|
|
if (c) {
|
|
mutex_spin_enter(&rndpool_mtx);
|
|
rndpool_add_data(&rnd_pool, &c, sizeof(c), 1);
|
|
c = rnd_counter();
|
|
rndpool_add_data(&rnd_pool, &c, sizeof(c), 1);
|
|
mutex_spin_exit(&rndpool_mtx);
|
|
}
|
|
|
|
/*
|
|
* If we have a cycle counter, take its error with respect
|
|
* to the callout mechanism as a source of entropy, ala
|
|
* TrueRand.
|
|
*
|
|
*/
|
|
#if defined(__HAVE_CPU_COUNTER)
|
|
callout_init(&skew_callout, CALLOUT_MPSAFE);
|
|
callout_setfunc(&skew_callout, rnd_skew, NULL);
|
|
rnd_skew(NULL);
|
|
#endif
|
|
|
|
#ifdef RND_VERBOSE
|
|
rnd_printf("rnd: initialised (%u)%s", RND_POOLBITS,
|
|
c ? " with counter\n" : "\n");
|
|
#endif
|
|
if (boot_rsp != NULL) {
|
|
mutex_spin_enter(&rndpool_mtx);
|
|
rndpool_add_data(&rnd_pool, boot_rsp->data,
|
|
sizeof(boot_rsp->data),
|
|
MIN(boot_rsp->entropy,
|
|
RND_POOLBITS / 2));
|
|
if (rndpool_get_entropy_count(&rnd_pool) >
|
|
RND_ENTROPY_THRESHOLD * 8) {
|
|
rnd_initial_entropy = 1;
|
|
}
|
|
mutex_spin_exit(&rndpool_mtx);
|
|
#ifdef RND_VERBOSE
|
|
rnd_printf("rnd: seeded with %d bits\n",
|
|
MIN(boot_rsp->entropy, RND_POOLBITS / 2));
|
|
#endif
|
|
memset(boot_rsp, 0, sizeof(*boot_rsp));
|
|
}
|
|
rnd_attach_source(&rnd_source_anonymous, "Anonymous",
|
|
RND_TYPE_UNKNOWN,
|
|
RND_FLAG_COLLECT_TIME|RND_FLAG_COLLECT_VALUE|
|
|
RND_FLAG_ESTIMATE_TIME);
|
|
rnd_attach_source(&rnd_printf_source, "printf", RND_TYPE_UNKNOWN,
|
|
RND_FLAG_NO_ESTIMATE);
|
|
rnd_attach_source(&rnd_autoconf_source, "autoconf",
|
|
RND_TYPE_UNKNOWN,
|
|
RND_FLAG_COLLECT_TIME|RND_FLAG_ESTIMATE_TIME);
|
|
rnd_ready = 1;
|
|
}
|
|
|
|
static rnd_sample_t *
|
|
rnd_sample_allocate(krndsource_t *source)
|
|
{
|
|
rnd_sample_t *c;
|
|
|
|
c = pool_cache_get(rnd_mempc, PR_WAITOK);
|
|
if (c == NULL)
|
|
return (NULL);
|
|
|
|
c->source = source;
|
|
c->cursor = 0;
|
|
c->entropy = 0;
|
|
|
|
return (c);
|
|
}
|
|
|
|
/*
|
|
* Don't wait on allocation. To be used in an interrupt context.
|
|
*/
|
|
static rnd_sample_t *
|
|
rnd_sample_allocate_isr(krndsource_t *source)
|
|
{
|
|
rnd_sample_t *c;
|
|
|
|
c = pool_cache_get(rnd_mempc, PR_NOWAIT);
|
|
if (c == NULL)
|
|
return (NULL);
|
|
|
|
c->source = source;
|
|
c->cursor = 0;
|
|
c->entropy = 0;
|
|
|
|
return (c);
|
|
}
|
|
|
|
static void
|
|
rnd_sample_free(rnd_sample_t *c)
|
|
{
|
|
memset(c, 0, sizeof(*c));
|
|
pool_cache_put(rnd_mempc, c);
|
|
}
|
|
|
|
/*
|
|
* Add a source to our list of sources.
|
|
*/
|
|
void
|
|
rnd_attach_source(krndsource_t *rs, const char *name, uint32_t type,
|
|
uint32_t flags)
|
|
{
|
|
uint32_t ts;
|
|
|
|
ts = rnd_counter();
|
|
|
|
strlcpy(rs->name, name, sizeof(rs->name));
|
|
memset(&rs->time_delta, 0, sizeof(rs->time_delta));
|
|
rs->time_delta.x = ts;
|
|
memset(&rs->value_delta, 0, sizeof(rs->value_delta));
|
|
rs->total = 0;
|
|
|
|
/*
|
|
* Some source setup, by type
|
|
*/
|
|
rs->test = NULL;
|
|
rs->test_cnt = -1;
|
|
|
|
if (flags == 0) {
|
|
flags = RND_FLAG_DEFAULT;
|
|
}
|
|
|
|
switch (type) {
|
|
case RND_TYPE_NET: /* Don't collect by default */
|
|
flags |= (RND_FLAG_NO_COLLECT | RND_FLAG_NO_ESTIMATE);
|
|
break;
|
|
case RND_TYPE_RNG: /* Space for statistical testing */
|
|
rs->test = kmem_alloc(sizeof(rngtest_t), KM_NOSLEEP);
|
|
rs->test_cnt = 0;
|
|
/* FALLTHRU */
|
|
case RND_TYPE_VM: /* Process samples in bulk always */
|
|
flags |= RND_FLAG_FAST;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
rs->type = type;
|
|
rs->flags = flags;
|
|
|
|
rs->state = rnd_sample_allocate(rs);
|
|
|
|
mutex_spin_enter(&rndpool_mtx);
|
|
LIST_INSERT_HEAD(&rnd_sources, rs, list);
|
|
|
|
#ifdef RND_VERBOSE
|
|
rnd_printf("rnd: %s attached as an entropy source (", rs->name);
|
|
if (!(flags & RND_FLAG_NO_COLLECT)) {
|
|
rnd_printf("collecting");
|
|
if (flags & RND_FLAG_NO_ESTIMATE)
|
|
rnd_printf(" without estimation");
|
|
}
|
|
else
|
|
rnd_printf("off");
|
|
rnd_printf(")\n");
|
|
#endif
|
|
|
|
/*
|
|
* Again, put some more initial junk in the pool.
|
|
* FreeBSD claim to have an analysis that show 4 bits of
|
|
* entropy per source-attach timestamp. I am skeptical,
|
|
* but we count 1 bit per source here.
|
|
*/
|
|
rndpool_add_data(&rnd_pool, &ts, sizeof(ts), 1);
|
|
mutex_spin_exit(&rndpool_mtx);
|
|
}
|
|
|
|
/*
|
|
* Remove a source from our list of sources.
|
|
*/
|
|
void
|
|
rnd_detach_source(krndsource_t *source)
|
|
{
|
|
rnd_sample_t *sample;
|
|
|
|
mutex_spin_enter(&rnd_mtx);
|
|
|
|
LIST_REMOVE(source, list);
|
|
|
|
/*
|
|
* If there are samples queued up "remove" them from the sample queue
|
|
* by setting the source to the no-collect pseudosource.
|
|
*/
|
|
sample = SIMPLEQ_FIRST(&rnd_samples);
|
|
while (sample != NULL) {
|
|
if (sample->source == source)
|
|
sample->source = &rnd_source_no_collect;
|
|
|
|
sample = SIMPLEQ_NEXT(sample, next);
|
|
}
|
|
|
|
mutex_spin_exit(&rnd_mtx);
|
|
|
|
if (!cpu_softintr_p()) { /* XXX XXX very temporary "fix" */
|
|
if (source->state) {
|
|
rnd_sample_free(source->state);
|
|
source->state = NULL;
|
|
}
|
|
|
|
if (source->test) {
|
|
kmem_free(source->test, sizeof(rngtest_t));
|
|
}
|
|
}
|
|
|
|
#ifdef RND_VERBOSE
|
|
rnd_printf("rnd: %s detached as an entropy source\n", source->name);
|
|
#endif
|
|
}
|
|
|
|
static inline uint32_t
|
|
rnd_estimate(krndsource_t *rs, uint32_t ts, uint32_t val)
|
|
{
|
|
uint32_t entropy = 0, dt_est, dv_est;
|
|
|
|
dt_est = rnd_dt_estimate(rs, ts);
|
|
dv_est = rnd_dv_estimate(rs, val);
|
|
|
|
if (!(rs->flags & RND_FLAG_NO_ESTIMATE)) {
|
|
if (rs->flags & RND_FLAG_ESTIMATE_TIME) {
|
|
entropy += dt_est;
|
|
}
|
|
|
|
if (rs->flags & RND_FLAG_ESTIMATE_VALUE) {
|
|
entropy += dv_est;
|
|
}
|
|
|
|
}
|
|
return entropy;
|
|
}
|
|
|
|
/*
|
|
* Add a 32-bit value to the entropy pool. The rs parameter should point to
|
|
* the source-specific source structure.
|
|
*/
|
|
void
|
|
_rnd_add_uint32(krndsource_t *rs, uint32_t val)
|
|
{
|
|
uint32_t ts;
|
|
uint32_t entropy = 0;
|
|
|
|
if (rs->flags & RND_FLAG_NO_COLLECT)
|
|
return;
|
|
|
|
/*
|
|
* Sample the counter as soon as possible to avoid
|
|
* entropy overestimation.
|
|
*/
|
|
ts = rnd_counter();
|
|
|
|
/*
|
|
* Calculate estimates - we may not use them, but if we do
|
|
* not calculate them, the estimators' history becomes invalid.
|
|
*/
|
|
entropy = rnd_estimate(rs, ts, val);
|
|
|
|
rnd_add_data_ts(rs, &val, sizeof(val), entropy, ts);
|
|
}
|
|
|
|
void
|
|
_rnd_add_uint64(krndsource_t *rs, uint64_t val)
|
|
{
|
|
uint32_t ts;
|
|
uint32_t entropy = 0;
|
|
|
|
if (rs->flags & RND_FLAG_NO_COLLECT)
|
|
return;
|
|
|
|
/*
|
|
* Sample the counter as soon as possible to avoid
|
|
* entropy overestimation.
|
|
*/
|
|
ts = rnd_counter();
|
|
|
|
/*
|
|
* Calculate estimates - we may not use them, but if we do
|
|
* not calculate them, the estimators' history becomes invalid.
|
|
*/
|
|
entropy = rnd_estimate(rs, ts, (uint32_t)(val & (uint64_t)0xffffffff));
|
|
|
|
rnd_add_data_ts(rs, &val, sizeof(val), entropy, ts);
|
|
}
|
|
|
|
void
|
|
rnd_add_data(krndsource_t *rs, const void *const data, uint32_t len,
|
|
uint32_t entropy)
|
|
{
|
|
/*
|
|
* This interface is meant for feeding data which is,
|
|
* itself, random. Don't estimate entropy based on
|
|
* timestamp, just directly add the data.
|
|
*/
|
|
if (__predict_false(rs == NULL)) {
|
|
mutex_spin_enter(&rndpool_mtx);
|
|
rndpool_add_data(&rnd_pool, data, len, entropy);
|
|
mutex_spin_exit(&rndpool_mtx);
|
|
} else {
|
|
rnd_add_data_ts(rs, data, len, entropy, rnd_counter());
|
|
}
|
|
}
|
|
|
|
static void
|
|
rnd_add_data_ts(krndsource_t *rs, const void *const data, u_int32_t len,
|
|
u_int32_t entropy, uint32_t ts)
|
|
{
|
|
rnd_sample_t *state = NULL;
|
|
const uint8_t *p = data;
|
|
uint32_t dint;
|
|
int todo, done, filled = 0;
|
|
int sample_count;
|
|
SIMPLEQ_HEAD(, _rnd_sample_t) tmp_samples =
|
|
SIMPLEQ_HEAD_INITIALIZER(tmp_samples);
|
|
|
|
if (rs && (rs->flags & RND_FLAG_NO_COLLECT ||
|
|
__predict_false(!(rs->flags &
|
|
(RND_FLAG_COLLECT_TIME|
|
|
RND_FLAG_COLLECT_VALUE))))) {
|
|
return;
|
|
}
|
|
todo = len / sizeof(dint);
|
|
/*
|
|
* Let's try to be efficient: if we are warm, and a source
|
|
* is adding entropy at a rate of at least 1 bit every 10 seconds,
|
|
* mark it as "fast" and add its samples in bulk.
|
|
*/
|
|
if (__predict_true(rs->flags & RND_FLAG_FAST)) {
|
|
sample_count = RND_SAMPLE_COUNT;
|
|
} else {
|
|
if (!cold && rnd_initial_entropy) {
|
|
struct timeval upt;
|
|
|
|
getmicrouptime(&upt);
|
|
if ((todo >= RND_SAMPLE_COUNT) ||
|
|
(upt.tv_sec > 0 && rs->total > upt.tv_sec * 10) ||
|
|
(upt.tv_sec > 10 && rs->total > upt.tv_sec) ||
|
|
(upt.tv_sec > 100 &&
|
|
rs->total > upt.tv_sec / 10)) {
|
|
#ifdef RND_VERBOSE
|
|
rnd_printf("rnd: source %s is fast (%d samples "
|
|
"at once, %d bits in %lld seconds), "
|
|
"processing samples in bulk.\n",
|
|
rs->name, todo, rs->total,
|
|
(long long int)upt.tv_sec);
|
|
#endif
|
|
rs->flags |= RND_FLAG_FAST;
|
|
}
|
|
}
|
|
sample_count = 2;
|
|
}
|
|
|
|
/*
|
|
* Loop over data packaging it into sample buffers.
|
|
* If a sample buffer allocation fails, drop all data.
|
|
*/
|
|
for (done = 0; done < todo ; done++) {
|
|
state = rs->state;
|
|
if (state == NULL) {
|
|
state = rnd_sample_allocate_isr(rs);
|
|
if (__predict_false(state == NULL)) {
|
|
break;
|
|
}
|
|
rs->state = state;
|
|
}
|
|
|
|
state->ts[state->cursor] = ts;
|
|
(void)memcpy(&dint, &p[done*4], 4);
|
|
state->values[state->cursor] = dint;
|
|
state->cursor++;
|
|
|
|
if (state->cursor == sample_count) {
|
|
SIMPLEQ_INSERT_HEAD(&tmp_samples, state, next);
|
|
filled++;
|
|
rs->state = NULL;
|
|
}
|
|
}
|
|
|
|
if (__predict_false(state == NULL)) {
|
|
while ((state = SIMPLEQ_FIRST(&tmp_samples))) {
|
|
SIMPLEQ_REMOVE_HEAD(&tmp_samples, next);
|
|
rnd_sample_free(state);
|
|
}
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Claim all the entropy on the last one we send to
|
|
* the pool, so we don't rely on it being evenly distributed
|
|
* in the supplied data.
|
|
*
|
|
* XXX The rndpool code must accept samples with more
|
|
* XXX claimed entropy than bits for this to work right.
|
|
*/
|
|
state->entropy += entropy;
|
|
rs->total += entropy;
|
|
|
|
/*
|
|
* If we didn't finish any sample buffers, we're done.
|
|
*/
|
|
if (!filled) {
|
|
return;
|
|
}
|
|
|
|
mutex_spin_enter(&rnd_mtx);
|
|
while ((state = SIMPLEQ_FIRST(&tmp_samples))) {
|
|
SIMPLEQ_REMOVE_HEAD(&tmp_samples, next);
|
|
SIMPLEQ_INSERT_HEAD(&rnd_samples, state, next);
|
|
}
|
|
mutex_spin_exit(&rnd_mtx);
|
|
|
|
/* Cause processing of queued samples */
|
|
rnd_schedule_process();
|
|
}
|
|
|
|
static int
|
|
rnd_hwrng_test(rnd_sample_t *sample)
|
|
{
|
|
krndsource_t *source = sample->source;
|
|
size_t cmplen;
|
|
uint8_t *v1, *v2;
|
|
size_t resid, totest;
|
|
|
|
KASSERT(source->type == RND_TYPE_RNG);
|
|
|
|
/*
|
|
* Continuous-output test: compare two halves of the
|
|
* sample buffer to each other. The sample buffer (64 ints,
|
|
* so either 256 or 512 bytes on any modern machine) should be
|
|
* much larger than a typical hardware RNG output, so this seems
|
|
* a reasonable way to do it without retaining extra data.
|
|
*/
|
|
cmplen = sizeof(sample->values) / 2;
|
|
v1 = (uint8_t *)sample->values;
|
|
v2 = (uint8_t *)sample->values + cmplen;
|
|
|
|
if (__predict_false(!memcmp(v1, v2, cmplen))) {
|
|
rnd_printf("rnd: source \"%s\" failed continuous-output test.\n",
|
|
source->name);
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
* FIPS 140 statistical RNG test. We must accumulate 20,000 bits.
|
|
*/
|
|
if (__predict_true(source->test_cnt == -1)) {
|
|
/* already passed the test */
|
|
return 0;
|
|
}
|
|
resid = FIPS140_RNG_TEST_BYTES - source->test_cnt;
|
|
totest = MIN(RND_SAMPLE_COUNT * 4, resid);
|
|
memcpy(source->test->rt_b + source->test_cnt, sample->values, totest);
|
|
resid -= totest;
|
|
source->test_cnt += totest;
|
|
if (resid == 0) {
|
|
strlcpy(source->test->rt_name, source->name,
|
|
sizeof(source->test->rt_name));
|
|
if (rngtest(source->test)) {
|
|
rnd_printf("rnd: source \"%s\" failed statistical test.",
|
|
source->name);
|
|
return 1;
|
|
}
|
|
source->test_cnt = -1;
|
|
memset(source->test, 0, sizeof(*source->test));
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Process the events in the ring buffer. Called by rnd_timeout or
|
|
* by the add routines directly if the callout has never fired (that
|
|
* is, if we are "cold" -- just booted).
|
|
*
|
|
*/
|
|
static void
|
|
rnd_process_events(void)
|
|
{
|
|
rnd_sample_t *sample = NULL;
|
|
krndsource_t *source, *badsource = NULL;
|
|
static krndsource_t *last_source;
|
|
u_int32_t entropy;
|
|
size_t pool_entropy;
|
|
int found = 0, wake = 0;
|
|
SIMPLEQ_HEAD(, _rnd_sample_t) dq_samples =
|
|
SIMPLEQ_HEAD_INITIALIZER(dq_samples);
|
|
SIMPLEQ_HEAD(, _rnd_sample_t) df_samples =
|
|
SIMPLEQ_HEAD_INITIALIZER(df_samples);
|
|
|
|
/*
|
|
* Sample queue is protected by rnd_mtx, drain to onstack queue
|
|
* and drop lock.
|
|
*/
|
|
|
|
mutex_spin_enter(&rnd_mtx);
|
|
while ((sample = SIMPLEQ_FIRST(&rnd_samples))) {
|
|
found++;
|
|
SIMPLEQ_REMOVE_HEAD(&rnd_samples, next);
|
|
/*
|
|
* We repeat this check here, since it is possible
|
|
* the source was disabled before we were called, but
|
|
* after the entry was queued.
|
|
*/
|
|
if (__predict_false(!(sample->source->flags &
|
|
(RND_FLAG_COLLECT_TIME|
|
|
RND_FLAG_COLLECT_VALUE)))) {
|
|
SIMPLEQ_INSERT_TAIL(&df_samples, sample, next);
|
|
} else {
|
|
SIMPLEQ_INSERT_TAIL(&dq_samples, sample, next);
|
|
}
|
|
}
|
|
mutex_spin_exit(&rnd_mtx);
|
|
|
|
/* Don't thrash the rndpool mtx either. Hold, add all samples. */
|
|
mutex_spin_enter(&rndpool_mtx);
|
|
|
|
pool_entropy = rndpool_get_entropy_count(&rnd_pool);
|
|
|
|
while ((sample = SIMPLEQ_FIRST(&dq_samples))) {
|
|
int sample_count;
|
|
|
|
SIMPLEQ_REMOVE_HEAD(&dq_samples, next);
|
|
source = sample->source;
|
|
entropy = sample->entropy;
|
|
sample_count = sample->cursor + 1;
|
|
|
|
/*
|
|
* Don't provide a side channel for timing attacks on
|
|
* low-rate sources: require mixing with some other
|
|
* source before we schedule a wakeup.
|
|
*/
|
|
if (!wake &&
|
|
(source != last_source || source->flags & RND_FLAG_FAST)) {
|
|
wake++;
|
|
}
|
|
last_source = source;
|
|
|
|
/*
|
|
* Hardware generators are great but sometimes they
|
|
* have...hardware issues. Don't use any data from
|
|
* them unless it passes some tests.
|
|
*/
|
|
if (source->type == RND_TYPE_RNG) {
|
|
if (__predict_false(rnd_hwrng_test(sample))) {
|
|
/*
|
|
* Detach the bad source. See below.
|
|
*/
|
|
badsource = source;
|
|
rnd_printf("rnd: detaching source \"%s\".",
|
|
badsource->name);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (source->flags & RND_FLAG_COLLECT_VALUE) {
|
|
rndpool_add_data(&rnd_pool, sample->values,
|
|
sample_count *
|
|
sizeof(sample->values[1]),
|
|
0);
|
|
}
|
|
if (source->flags & RND_FLAG_COLLECT_TIME) {
|
|
rndpool_add_data(&rnd_pool, sample->ts,
|
|
sample_count *
|
|
sizeof(sample->ts[1]),
|
|
0);
|
|
}
|
|
|
|
pool_entropy += entropy;
|
|
source->total += sample->entropy;
|
|
SIMPLEQ_INSERT_TAIL(&df_samples, sample, next);
|
|
}
|
|
rndpool_set_entropy_count(&rnd_pool, pool_entropy);
|
|
if (pool_entropy > RND_ENTROPY_THRESHOLD * 8) {
|
|
wake++;
|
|
} else {
|
|
rnd_empty = 1;
|
|
rnd_getmore((RND_POOLBITS - pool_entropy) / 8);
|
|
#ifdef RND_VERBOSE
|
|
rnd_printf("rnd: empty, asking for %d bits\n",
|
|
(int)((RND_POOLBITS - pool_entropy) / 8));
|
|
#endif
|
|
}
|
|
mutex_spin_exit(&rndpool_mtx);
|
|
|
|
/* Now we hold no locks: clean up. */
|
|
if (__predict_false(badsource)) {
|
|
/*
|
|
* The detach routine frees any samples we have not
|
|
* dequeued ourselves. For sanity's sake, we simply
|
|
* free (without using) all dequeued samples from the
|
|
* point at which we detected a problem onwards.
|
|
*/
|
|
rnd_detach_source(badsource);
|
|
while ((sample = SIMPLEQ_FIRST(&dq_samples))) {
|
|
SIMPLEQ_REMOVE_HEAD(&dq_samples, next);
|
|
rnd_sample_free(sample);
|
|
}
|
|
}
|
|
while ((sample = SIMPLEQ_FIRST(&df_samples))) {
|
|
SIMPLEQ_REMOVE_HEAD(&df_samples, next);
|
|
rnd_sample_free(sample);
|
|
}
|
|
|
|
/*
|
|
* Wake up any potential readers waiting.
|
|
*/
|
|
if (wake) {
|
|
rnd_schedule_wakeup();
|
|
}
|
|
}
|
|
|
|
static void
|
|
rnd_intr(void *arg)
|
|
{
|
|
rnd_process_events();
|
|
}
|
|
|
|
static void
|
|
rnd_wake(void *arg)
|
|
{
|
|
rnd_wakeup_readers();
|
|
}
|
|
|
|
u_int32_t
|
|
rnd_extract_data_locked(void *p, u_int32_t len, u_int32_t flags)
|
|
{
|
|
static int timed_in;
|
|
int entropy_count;
|
|
|
|
KASSERT(mutex_owned(&rndpool_mtx));
|
|
if (__predict_false(!timed_in)) {
|
|
if (boottime.tv_sec) {
|
|
rndpool_add_data(&rnd_pool, &boottime,
|
|
sizeof(boottime), 0);
|
|
}
|
|
timed_in++;
|
|
}
|
|
if (__predict_false(!rnd_initial_entropy)) {
|
|
uint32_t c;
|
|
|
|
#ifdef RND_VERBOSE
|
|
rnd_printf("rnd: WARNING! initial entropy low (%u).\n",
|
|
rndpool_get_entropy_count(&rnd_pool));
|
|
#endif
|
|
/* Try once again to put something in the pool */
|
|
c = rnd_counter();
|
|
rndpool_add_data(&rnd_pool, &c, sizeof(c), 1);
|
|
}
|
|
|
|
#ifdef DIAGNOSTIC
|
|
while (!rnd_tested) {
|
|
entropy_count = rndpool_get_entropy_count(&rnd_pool);
|
|
#ifdef RND_VERBOSE
|
|
rnd_printf("rnd: starting statistical RNG test, entropy = %d.\n",
|
|
entropy_count);
|
|
#endif
|
|
if (rndpool_extract_data(&rnd_pool, rnd_rt.rt_b,
|
|
sizeof(rnd_rt.rt_b), RND_EXTRACT_ANY)
|
|
!= sizeof(rnd_rt.rt_b)) {
|
|
panic("rnd: could not get bits for statistical test");
|
|
}
|
|
/*
|
|
* Stash the tested bits so we can put them back in the
|
|
* pool, restoring the entropy count. DO NOT rely on
|
|
* rngtest to maintain the bits pristine -- we could end
|
|
* up adding back non-random data claiming it were pure
|
|
* entropy.
|
|
*/
|
|
memcpy(rnd_testbits, rnd_rt.rt_b, sizeof(rnd_rt.rt_b));
|
|
strlcpy(rnd_rt.rt_name, "entropy pool", sizeof(rnd_rt.rt_name));
|
|
if (rngtest(&rnd_rt)) {
|
|
/*
|
|
* The probabiliity of a Type I error is 3/10000,
|
|
* but note this can only happen at boot time.
|
|
* The relevant standard says to reset the module,
|
|
* but developers objected...
|
|
*/
|
|
rnd_printf("rnd: WARNING, ENTROPY POOL FAILED "
|
|
"STATISTICAL TEST!\n");
|
|
continue;
|
|
}
|
|
memset(&rnd_rt, 0, sizeof(rnd_rt));
|
|
rndpool_add_data(&rnd_pool, rnd_testbits, sizeof(rnd_testbits),
|
|
entropy_count);
|
|
memset(rnd_testbits, 0, sizeof(rnd_testbits));
|
|
#ifdef RND_VERBOSE
|
|
rnd_printf("rnd: statistical RNG test done, entropy = %d.\n",
|
|
rndpool_get_entropy_count(&rnd_pool));
|
|
#endif
|
|
rnd_tested++;
|
|
}
|
|
#endif
|
|
entropy_count = rndpool_get_entropy_count(&rnd_pool);
|
|
if (entropy_count < (RND_ENTROPY_THRESHOLD * 2 + len) * NBBY) {
|
|
rnd_getmore(howmany((RND_POOLBITS - entropy_count), NBBY));
|
|
}
|
|
return rndpool_extract_data(&rnd_pool, p, len, flags);
|
|
}
|
|
|
|
u_int32_t
|
|
rnd_extract_data(void *p, u_int32_t len, u_int32_t flags)
|
|
{
|
|
uint32_t retval;
|
|
|
|
mutex_spin_enter(&rndpool_mtx);
|
|
retval = rnd_extract_data_locked(p, len, flags);
|
|
mutex_spin_exit(&rndpool_mtx);
|
|
return retval;
|
|
}
|
|
|
|
void
|
|
rnd_seed(void *base, size_t len)
|
|
{
|
|
SHA1_CTX s;
|
|
uint8_t digest[SHA1_DIGEST_LENGTH];
|
|
|
|
if (len != sizeof(*boot_rsp)) {
|
|
rnd_printf("rnd: bad seed length %d\n", (int)len);
|
|
return;
|
|
}
|
|
|
|
boot_rsp = (rndsave_t *)base;
|
|
SHA1Init(&s);
|
|
SHA1Update(&s, (uint8_t *)&boot_rsp->entropy,
|
|
sizeof(boot_rsp->entropy));
|
|
SHA1Update(&s, boot_rsp->data, sizeof(boot_rsp->data));
|
|
SHA1Final(digest, &s);
|
|
|
|
if (memcmp(digest, boot_rsp->digest, sizeof(digest))) {
|
|
rnd_printf("rnd: bad seed checksum\n");
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* It's not really well-defined whether bootloader-supplied
|
|
* modules run before or after rnd_init(). Handle both cases.
|
|
*/
|
|
if (rnd_ready) {
|
|
#ifdef RND_VERBOSE
|
|
rnd_printf("rnd: ready, feeding in seed data directly.\n");
|
|
#endif
|
|
mutex_spin_enter(&rndpool_mtx);
|
|
rndpool_add_data(&rnd_pool, boot_rsp->data,
|
|
sizeof(boot_rsp->data),
|
|
MIN(boot_rsp->entropy, RND_POOLBITS / 2));
|
|
memset(boot_rsp, 0, sizeof(*boot_rsp));
|
|
mutex_spin_exit(&rndpool_mtx);
|
|
} else {
|
|
#ifdef RND_VERBOSE
|
|
rnd_printf("rnd: not ready, deferring seed feed.\n");
|
|
#endif
|
|
}
|
|
}
|