Changeset View
Standalone View
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/lock.h> | |||||
#include <sys/malloc.h> | |||||
#include <sys/module.h> | #include <sys/module.h> | ||||
#include <sys/mutex.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 <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; | ||||
struct mtx vtrnd_lock; | |||||
}; | }; | ||||
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 struct vtrnd_softc *g_vtrnd_softc = NULL; | |||||
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 All 23 Lines | |||||
static int | static int | ||||
vtrnd_attach(device_t dev) | vtrnd_attach(device_t dev) | ||||
{ | { | ||||
struct vtrnd_softc *sc; | struct vtrnd_softc *sc; | ||||
int error; | int error; | ||||
sc = device_get_softc(dev); | sc = device_get_softc(dev); | ||||
callout_init(&sc->vtrnd_callout, 1); | mtx_init(&sc->vtrnd_lock, "vtrnd lock", NULL, MTX_DEF); | ||||
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); | if (g_vtrnd_softc == NULL) { | ||||
g_vtrnd_softc = sc; | |||||
wmb(); | |||||
bryanv: While the virtqueue still uses the *mb(), I think there is a preference to use… | |||||
cemAuthorUnsubmitted Done Inline Actionsatomic_thread_fence_ is fine with me, I just have to figure out which one means "wmb". The conditions are both guarding against the host exposing 2 or more virtio-random devices. The driver will happily attach to both, but we want to reference only the first one from the global pointer and take care to only remove it when the same device is removed / detached (possible with devctl(8)). cem: atomic_thread_fence_ is fine with me, I just have to figure out which one means "wmb".
The… | |||||
bryanvUnsubmitted Done Inline ActionsAh, I have it in my mind that I wrote code to avoid multiple virtio_random devices from attaching. In the context of this change I think it would more sense to actually add that: why allow the attachment devices that will not be used? And avoid potential unexpected behavior: if the active one is detached, the remaining attached devices won't take over. bryanv: Ah, I have it in my mind that I wrote code to avoid multiple virtio_random devices from… | |||||
cemAuthorUnsubmitted Done Inline ActionsIt doesn't fit the usual driver pattern to make policy decisions about limiting multiple instances of the same driver from attaching, if the backing devices are present. Sure, attaching a 2nd random device doesn't do much today (other than show up in devinfo); there's also little cost (IMO). I think the unexpected behavior scenario above is so unlikely as to not be worth worrying about. The first unlikely thing that has to happen is for a hypervisor to expose >1 virtio-random devices to the guest. The second unlikely thing that has to happen is for the guest to manually detach one, but not all, of the virtio-rand devices. And the 3rd perhaps unlikely step is that a user who has created such a scenario would expect the remaining instances to take over random source responsibility. I have some thoughts about how we might improve the random source API for modules such that they don't have to worry about these details, one way or the other. I'd prefer to leave that concern/policy to random(4), instead of spreading it across N drivers. In particular, it'd be nice if it was actually safe to unregister a random source at runtime. cem: It doesn't fit the usual driver pattern to make policy decisions about limiting multiple… | |||||
bryanvUnsubmitted Done Inline ActionsI think a reasonable expected behavior would be all attached devices are used as sources. I agree that detaching one is unlikely but I guess I'd prefer to be cautious around anything security'ish related. bryanv: I think a reasonable expected behavior would be all attached devices are used as sources. I… | |||||
cemAuthorUnsubmitted Done Inline Actions
I agree in general, with some nuance. In this particular case, where the device only exists to provide entropy, I totally agree and will plan to make the change you proposed earlier. The nuance bit is: I think that we might reasonably fail to meet this expectation for some devices under the current source-registration model. There exist drivers which are both a random source and provide some other functionality. For those specific drivers, it may make sense to attach even if the 2nd device will not be able to provide entropy to random(4) today. A good redesign of the entropy registration model would allow all devices of the same "source" to input entropy sanely, and come and go without impacting other instances of the same device/source. cem: > I think a reasonable expected behavior would be all attached devices are used as sources.
I… | |||||
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); | ||||
mtx_lock(&sc->vtrnd_lock); | |||||
if (g_vtrnd_softc == sc) { | |||||
random_source_deregister(&random_vtrnd); | |||||
g_vtrnd_softc = NULL; | |||||
/* | |||||
* 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. | |||||
*/ | |||||
msleep_sbt(&g_vtrnd_softc, &sc->vtrnd_lock, 0, "vtrnddet", | |||||
mstosbt(50), 0, C_HARDCLOCK); | |||||
} | |||||
mtx_unlock(&sc->vtrnd_lock); | |||||
mtx_destroy(&sc->vtrnd_lock); | |||||
callout_drain(&sc->vtrnd_callout); | |||||
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); | ||||
int error; | int error; | ||||
vq = sc->vtrnd_vq; | _Static_assert(sizeof(value) < PAGE_SIZE, "sglist assumption"); | ||||
bryanvUnsubmitted Done Inline ActionsIs this at risk of blowing the kernel stack? bryanv: Is this at risk of blowing the kernel stack? | |||||
cemAuthorUnsubmitted Done Inline ActionsNo. In practice HARVESTSIZE == 2 and sz == 8. In a future revision, I plan to remove value entirely, shift the alignment responsibility into the random(4) code, and output directly into buf. But anything that touches random(4) has to go through secteam, so I wanted to keep that change separate and as small as possible. cem: No. In practice `HARVESTSIZE == 2` and `sz == 8`.
In a future revision, I plan to remove… | |||||
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)) | mtx_lock(&sc->vtrnd_lock); | ||||
return; | vq = sc->vtrnd_vq; | ||||
if (virtqueue_enqueue(vq, &value, &sg, 0, 1) != 0) | KASSERT(virtqueue_empty(vq), ("%s: non-empty queue", __func__)); | ||||
return; | |||||
error = virtqueue_enqueue(vq, buf, &sg, 0, 1); | |||||
if (error != 0) | |||||
return (error); | |||||
bryanvUnsubmitted Done Inline ActionsLock is leaked here bryanv: Lock is leaked here | |||||
cemAuthorUnsubmitted Done Inline ActionsGood catch, thanks! cem: Good catch, thanks! | |||||
/* | /* | ||||
* 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, NULL); | ||||
mtx_unlock(&sc->vtrnd_lock); | |||||
random_harvest_queue(&value, sizeof(value), RANDOM_PURE_VIRTIO); | memcpy(buf, value, sz); | ||||
explicit_bzero(value, sz); | |||||
return (0); | |||||
Done Inline ActionsWhile you're here can you handle a short read? I wrote this driver against the VirtIO v0.95 spec which only states:
However, the v1.0 spec contains the previous quote but then added:
I'm not quite sure how to parse this but handling short reads seems like a safe thing to do. bryanv: While you're here can you handle a short read? I wrote this driver against the VirtIO v0.95… | |||||
Done Inline ActionsI'm unfamiliar with the virtqueue API; is the right way to determine read length from the device something like: uint32_t rdlen; virtqueue_poll(vq, &rdlen); Thanks! I'll go ahead and work on an updated draft of this patch. cem: I'm unfamiliar with the virtqueue API; is the right way to determine read length from the… | |||||
} | } | ||||
static void | static unsigned | ||||
vtrnd_timer(void *xsc) | vtrnd_read(void *buf, unsigned sz) | ||||
{ | { | ||||
struct vtrnd_softc *sc; | struct vtrnd_softc *sc; | ||||
int error; | |||||
sc = xsc; | sc = g_vtrnd_softc; | ||||
if (sc == NULL) | |||||
return (0); | |||||
vtrnd_harvest(sc); | error = vtrnd_harvest(sc, buf, sz); | ||||
callout_schedule(&sc->vtrnd_callout, 5 * hz); | if (error != 0) | ||||
return (0); | |||||
return (sz); | |||||
} | } |
While the virtqueue still uses the *mb(), I think there is a preference to use atomic_thread_fence_* instead.
When would g_vtrnd_softc not be null here? I guess related when would g_vtrnd_softc == sc be false in detach (expect when the attached failed path)