Changeset View
Changeset View
Standalone View
Standalone View
head/sys/dev/virtio/random/virtio_random.c
Show All 27 Lines | |||||
/* Driver for VirtIO entropy device. */ | /* Driver for VirtIO entropy device. */ | ||||
#include <sys/cdefs.h> | #include <sys/cdefs.h> | ||||
__FBSDID("$FreeBSD$"); | __FBSDID("$FreeBSD$"); | ||||
#include <sys/param.h> | #include <sys/param.h> | ||||
#include <sys/kernel.h> | #include <sys/kernel.h> | ||||
#include <sys/malloc.h> | |||||
#include <sys/module.h> | #include <sys/module.h> | ||||
#include <sys/sglist.h> | #include <sys/sglist.h> | ||||
#include <sys/callout.h> | #include <sys/callout.h> | ||||
#include <sys/random.h> | #include <sys/random.h> | ||||
#include <sys/stdatomic.h> | |||||
#include <machine/bus.h> | #include <machine/bus.h> | ||||
#include <machine/resource.h> | #include <machine/resource.h> | ||||
#include <sys/bus.h> | #include <sys/bus.h> | ||||
#include <dev/random/randomdev.h> | |||||
#include <dev/random/random_harvestq.h> | |||||
#include <dev/virtio/virtio.h> | #include <dev/virtio/virtio.h> | ||||
#include <dev/virtio/virtqueue.h> | #include <dev/virtio/virtqueue.h> | ||||
struct vtrnd_softc { | struct vtrnd_softc { | ||||
uint64_t vtrnd_features; | uint64_t vtrnd_features; | ||||
struct callout vtrnd_callout; | |||||
struct virtqueue *vtrnd_vq; | struct virtqueue *vtrnd_vq; | ||||
}; | }; | ||||
static int vtrnd_modevent(module_t, int, void *); | static int vtrnd_modevent(module_t, int, void *); | ||||
static int vtrnd_probe(device_t); | static int vtrnd_probe(device_t); | ||||
static int vtrnd_attach(device_t); | static int vtrnd_attach(device_t); | ||||
static int vtrnd_detach(device_t); | static int vtrnd_detach(device_t); | ||||
static void vtrnd_negotiate_features(device_t); | static void vtrnd_negotiate_features(device_t); | ||||
static int vtrnd_alloc_virtqueue(device_t); | static int vtrnd_alloc_virtqueue(device_t); | ||||
static void vtrnd_harvest(struct vtrnd_softc *); | static int vtrnd_harvest(struct vtrnd_softc *, void *, size_t *); | ||||
static void vtrnd_timer(void *); | static unsigned vtrnd_read(void *, unsigned); | ||||
#define VTRND_FEATURES 0 | #define VTRND_FEATURES 0 | ||||
static struct virtio_feature_desc vtrnd_feature_desc[] = { | static struct virtio_feature_desc vtrnd_feature_desc[] = { | ||||
{ 0, NULL } | { 0, NULL } | ||||
}; | }; | ||||
static struct random_source random_vtrnd = { | |||||
.rs_ident = "VirtIO Entropy Adapter", | |||||
.rs_source = RANDOM_PURE_VIRTIO, | |||||
.rs_read = vtrnd_read, | |||||
}; | |||||
/* Kludge for API limitations of random(4). */ | |||||
static _Atomic(struct vtrnd_softc *) g_vtrnd_softc; | |||||
static device_method_t vtrnd_methods[] = { | static device_method_t vtrnd_methods[] = { | ||||
/* Device methods. */ | /* Device methods. */ | ||||
DEVMETHOD(device_probe, vtrnd_probe), | DEVMETHOD(device_probe, vtrnd_probe), | ||||
DEVMETHOD(device_attach, vtrnd_attach), | DEVMETHOD(device_attach, vtrnd_attach), | ||||
DEVMETHOD(device_detach, vtrnd_detach), | DEVMETHOD(device_detach, vtrnd_detach), | ||||
DEVMETHOD_END | DEVMETHOD_END | ||||
}; | }; | ||||
▲ Show 20 Lines • Show All 41 Lines • ▼ Show 20 Lines | vtrnd_probe(device_t dev) | ||||
device_set_desc(dev, "VirtIO Entropy Adapter"); | device_set_desc(dev, "VirtIO Entropy Adapter"); | ||||
return (BUS_PROBE_DEFAULT); | return (BUS_PROBE_DEFAULT); | ||||
} | } | ||||
static int | static int | ||||
vtrnd_attach(device_t dev) | vtrnd_attach(device_t dev) | ||||
{ | { | ||||
struct vtrnd_softc *sc; | struct vtrnd_softc *sc, *exp; | ||||
int error; | int error; | ||||
sc = device_get_softc(dev); | sc = device_get_softc(dev); | ||||
callout_init(&sc->vtrnd_callout, 1); | |||||
virtio_set_feature_desc(dev, vtrnd_feature_desc); | virtio_set_feature_desc(dev, vtrnd_feature_desc); | ||||
vtrnd_negotiate_features(dev); | vtrnd_negotiate_features(dev); | ||||
error = vtrnd_alloc_virtqueue(dev); | error = vtrnd_alloc_virtqueue(dev); | ||||
if (error) { | if (error) { | ||||
device_printf(dev, "cannot allocate virtqueue\n"); | device_printf(dev, "cannot allocate virtqueue\n"); | ||||
goto fail; | goto fail; | ||||
} | } | ||||
callout_reset(&sc->vtrnd_callout, 5 * hz, vtrnd_timer, sc); | exp = NULL; | ||||
if (!atomic_compare_exchange_strong_explicit(&g_vtrnd_softc, &exp, sc, | |||||
memory_order_release, memory_order_acquire)) { | |||||
error = EEXIST; | |||||
goto fail; | |||||
} | |||||
random_source_register(&random_vtrnd); | |||||
fail: | fail: | ||||
if (error) | if (error) | ||||
vtrnd_detach(dev); | vtrnd_detach(dev); | ||||
return (error); | return (error); | ||||
} | } | ||||
static int | static int | ||||
vtrnd_detach(device_t dev) | vtrnd_detach(device_t dev) | ||||
{ | { | ||||
struct vtrnd_softc *sc; | struct vtrnd_softc *sc; | ||||
sc = device_get_softc(dev); | sc = device_get_softc(dev); | ||||
KASSERT( | |||||
atomic_load_explicit(&g_vtrnd_softc, memory_order_acquire) == sc, | |||||
("only one global instance at a time")); | |||||
callout_drain(&sc->vtrnd_callout); | random_source_deregister(&random_vtrnd); | ||||
atomic_store_explicit(&g_vtrnd_softc, NULL, memory_order_release); | |||||
/* | |||||
* Unfortunately, deregister does not guarantee our source callback | |||||
* will not be invoked after it returns. Use a kludge to prevent some, | |||||
* but not all, possible races. | |||||
*/ | |||||
tsleep_sbt(&g_vtrnd_softc, 0, "vtrnddet", mstosbt(50), 0, C_HARDCLOCK); | |||||
return (0); | return (0); | ||||
} | } | ||||
static void | static void | ||||
vtrnd_negotiate_features(device_t dev) | vtrnd_negotiate_features(device_t dev) | ||||
{ | { | ||||
struct vtrnd_softc *sc; | struct vtrnd_softc *sc; | ||||
Show All 10 Lines | vtrnd_alloc_virtqueue(device_t dev) | ||||
sc = device_get_softc(dev); | sc = device_get_softc(dev); | ||||
VQ_ALLOC_INFO_INIT(&vq_info, 0, NULL, sc, &sc->vtrnd_vq, | VQ_ALLOC_INFO_INIT(&vq_info, 0, NULL, sc, &sc->vtrnd_vq, | ||||
"%s request", device_get_nameunit(dev)); | "%s request", device_get_nameunit(dev)); | ||||
return (virtio_alloc_virtqueues(dev, 0, 1, &vq_info)); | return (virtio_alloc_virtqueues(dev, 0, 1, &vq_info)); | ||||
} | } | ||||
static void | static int | ||||
vtrnd_harvest(struct vtrnd_softc *sc) | vtrnd_harvest(struct vtrnd_softc *sc, void *buf, size_t *sz) | ||||
{ | { | ||||
struct sglist_seg segs[1]; | struct sglist_seg segs[1]; | ||||
struct sglist sg; | struct sglist sg; | ||||
struct virtqueue *vq; | struct virtqueue *vq; | ||||
uint32_t value; | uint32_t value[HARVESTSIZE] __aligned(sizeof(uint32_t) * HARVESTSIZE); | ||||
uint32_t rdlen; | |||||
int error; | int error; | ||||
vq = sc->vtrnd_vq; | _Static_assert(sizeof(value) < PAGE_SIZE, "sglist assumption"); | ||||
sglist_init(&sg, 1, segs); | sglist_init(&sg, 1, segs); | ||||
error = sglist_append(&sg, &value, sizeof(value)); | error = sglist_append(&sg, value, *sz); | ||||
KASSERT(error == 0 && sg.sg_nseg == 1, | if (error != 0) | ||||
("%s: error %d adding buffer to sglist", __func__, error)); | panic("%s: sglist_append error=%d", __func__, error); | ||||
if (!virtqueue_empty(vq)) | vq = sc->vtrnd_vq; | ||||
return; | KASSERT(virtqueue_empty(vq), ("%s: non-empty queue", __func__)); | ||||
if (virtqueue_enqueue(vq, &value, &sg, 0, 1) != 0) | |||||
return; | |||||
error = virtqueue_enqueue(vq, buf, &sg, 0, 1); | |||||
if (error != 0) | |||||
return (error); | |||||
/* | /* | ||||
* Poll for the response, but the command is likely already | * Poll for the response, but the command is likely already | ||||
* done when we return from the notify. | * done when we return from the notify. | ||||
*/ | */ | ||||
virtqueue_notify(vq); | virtqueue_notify(vq); | ||||
virtqueue_poll(vq, NULL); | virtqueue_poll(vq, &rdlen); | ||||
random_harvest_queue(&value, sizeof(value), RANDOM_PURE_VIRTIO); | if (rdlen > *sz) | ||||
panic("%s: random device wrote %zu bytes beyond end of provided" | |||||
" buffer %p:%zu", __func__, (size_t)rdlen - *sz, | |||||
(void *)value, *sz); | |||||
else if (rdlen == 0) | |||||
return (EAGAIN); | |||||
*sz = MIN(rdlen, *sz); | |||||
memcpy(buf, value, *sz); | |||||
explicit_bzero(value, *sz); | |||||
return (0); | |||||
} | } | ||||
static void | static unsigned | ||||
vtrnd_timer(void *xsc) | vtrnd_read(void *buf, unsigned usz) | ||||
{ | { | ||||
struct vtrnd_softc *sc; | struct vtrnd_softc *sc; | ||||
size_t sz; | |||||
int error; | |||||
sc = xsc; | sc = g_vtrnd_softc; | ||||
if (sc == NULL) | |||||
return (0); | |||||
vtrnd_harvest(sc); | sz = usz; | ||||
callout_schedule(&sc->vtrnd_callout, 5 * hz); | error = vtrnd_harvest(sc, buf, &sz); | ||||
if (error != 0) | |||||
return (0); | |||||
return (sz); | |||||
} | } |