Index: sys/dev/virtio/random/virtio_random.c =================================================================== --- sys/dev/virtio/random/virtio_random.c +++ sys/dev/virtio/random/virtio_random.c @@ -48,8 +48,15 @@ #include #include +struct vtrnd_harvest_buf { + uint32_t value[HARVESTSIZE] __aligned(sizeof(uint32_t) * HARVESTSIZE); + struct sglist_seg segs[1]; + struct sglist sg; +}; + struct vtrnd_softc { device_t vtrnd_dev; + struct vtrnd_harvest_buf vtrnd_harvest_buf; uint64_t vtrnd_features; struct virtqueue *vtrnd_vq; }; @@ -136,10 +143,12 @@ vtrnd_attach(device_t dev) { struct vtrnd_softc *sc, *exp; + struct vtrnd_harvest_buf *buf; int error; sc = device_get_softc(dev); sc->vtrnd_dev = dev; + buf = &sc->vtrnd_harvest_buf; virtio_set_feature_desc(dev, vtrnd_feature_desc); error = vtrnd_setup_features(sc); @@ -160,6 +169,14 @@ error = EEXIST; goto fail; } + + _Static_assert(sizeof(buf->value) < PAGE_SIZE, "sglist assumption"); + + sglist_init(&buf->sg, 1, buf->segs); + error = sglist_append(&buf->sg, buf->value, sizeof(buf->value)); + if (error != 0) + panic("%s: sglist_append error=%d", __func__, error); + random_source_register(&random_vtrnd); fail: @@ -223,28 +240,36 @@ return (virtio_alloc_virtqueues(dev, 0, 1, &vq_info)); } +#define VTRND_HARVEST_MAXWAIT (SBT_1MS * 2) + static int vtrnd_harvest(struct vtrnd_softc *sc, void *buf, size_t *sz) { - struct sglist_seg segs[1]; - struct sglist sg; + struct vtrnd_harvest_buf *hbuf; struct virtqueue *vq; void *cookie; - uint32_t value[HARVESTSIZE] __aligned(sizeof(uint32_t) * HARVESTSIZE); uint32_t rdlen; int error; - _Static_assert(sizeof(value) < PAGE_SIZE, "sglist assumption"); - - sglist_init(&sg, 1, segs); - error = sglist_append(&sg, value, *sz); - if (error != 0) - panic("%s: sglist_append error=%d", __func__, error); - vq = sc->vtrnd_vq; - KASSERT(virtqueue_empty(vq), ("%s: non-empty queue", __func__)); + hbuf = &sc->vtrnd_harvest_buf; - error = virtqueue_enqueue(vq, buf, &sg, 0, 1); + /* + * The host provider may have ratelimited us, causing the last poll + * attempt to fail. Discard the result and try again. Perhaps we could + * have used it, but it's safer to discard it after it's been sitting in + * memory for so long in the same allocation as the softc. The odds of + * it having been observed seem too high to risk it. + */ + if (!virtqueue_empty(vq)) { + cookie = virtqueue_poll_timeout(vq, NULL, + VTRND_HARVEST_MAXWAIT); + /* We could still be blocked. */ + if (cookie == NULL) + return (EAGAIN); + } + + error = virtqueue_enqueue(vq, hbuf, &hbuf->sg, 0, 1); if (error != 0) return (error); @@ -253,19 +278,23 @@ * done when we return from the notify. */ virtqueue_notify(vq); - cookie = virtqueue_poll(vq, &rdlen); + cookie = virtqueue_poll_timeout(vq, &rdlen, VTRND_HARVEST_MAXWAIT); /* Almost trivially true, but could indicate something bad. */ - KASSERT(cookie == buf, ("inconsistent virtqueue state")); + KASSERT(cookie == NULL || cookie == hbuf, + ("inconsistent virtqueue state")); + + if (cookie == NULL) + return (EAGAIN); 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); + (void *)hbuf->value, *sz); else if (rdlen == 0) return (EAGAIN); *sz = MIN(rdlen, *sz); - memcpy(buf, value, *sz); - explicit_bzero(value, *sz); + memcpy(buf, hbuf->value, *sz); + explicit_bzero(hbuf->value, *sz); return (0); }