* top-level directory.
*/
-#include "iov.h"
+#include "qemu/iov.h"
#include "qdev.h"
+#include "qapi/qmp/qerror.h"
#include "virtio.h"
#include "virtio-rng.h"
#include "qemu/rng.h"
/* Only one vq - guest puts buffer(s) on it when it needs entropy */
VirtQueue *vq;
- VirtQueueElement elem;
- /* Config data for the device -- currently only chardev */
VirtIORNGConf *conf;
- /* Whether we've popped a vq element into 'elem' above */
- bool popped;
-
RngBackend *rng;
+
+ /* We purposefully don't migrate this state. The quota will reset on the
+ * destination as a result. Rate limiting is host state, not guest state.
+ */
+ QEMUTimer *rate_limit_timer;
+ int64_t quota_remaining;
} VirtIORNG;
static bool is_guest_ready(VirtIORNG *vrng)
return false;
}
-static size_t pop_an_elem(VirtIORNG *vrng)
+static size_t get_request_size(VirtQueue *vq, unsigned quota)
{
- size_t size;
-
- if (!vrng->popped && !virtqueue_pop(vrng->vq, &vrng->elem)) {
- return 0;
- }
- vrng->popped = true;
+ unsigned int in, out;
- size = iov_size(vrng->elem.in_sg, vrng->elem.in_num);
- return size;
+ virtqueue_get_avail_bytes(vq, &in, &out, quota, 0);
+ return in;
}
+static void virtio_rng_process(VirtIORNG *vrng);
+
/* Send data from a char device over to the guest */
static void chr_read(void *opaque, const void *buf, size_t size)
{
VirtIORNG *vrng = opaque;
+ VirtQueueElement elem;
size_t len;
int offset;
return;
}
+ vrng->quota_remaining -= size;
+
offset = 0;
while (offset < size) {
- if (!pop_an_elem(vrng)) {
+ if (!virtqueue_pop(vrng->vq, &elem)) {
break;
}
- len = iov_from_buf(vrng->elem.in_sg, vrng->elem.in_num,
+ len = iov_from_buf(elem.in_sg, elem.in_num,
0, buf + offset, size - offset);
offset += len;
- virtqueue_push(vrng->vq, &vrng->elem, len);
- vrng->popped = false;
+ virtqueue_push(vrng->vq, &elem, len);
}
virtio_notify(&vrng->vdev, vrng->vq);
-
- /*
- * Lastly, if we had multiple elems queued by the guest, and we
- * didn't have enough data to fill them all, indicate we want more
- * data.
- */
- len = pop_an_elem(vrng);
- if (len) {
- rng_backend_request_entropy(vrng->rng, size, chr_read, vrng);
- }
}
-static void handle_input(VirtIODevice *vdev, VirtQueue *vq)
+static void virtio_rng_process(VirtIORNG *vrng)
{
- VirtIORNG *vrng = DO_UPCAST(VirtIORNG, vdev, vdev);
size_t size;
+ unsigned quota;
+
+ if (!is_guest_ready(vrng)) {
+ return;
+ }
- size = pop_an_elem(vrng);
+ if (vrng->quota_remaining < 0) {
+ quota = 0;
+ } else {
+ quota = MIN((uint64_t)vrng->quota_remaining, (uint64_t)UINT32_MAX);
+ }
+ size = get_request_size(vrng->vq, quota);
+ size = MIN(vrng->quota_remaining, size);
if (size) {
rng_backend_request_entropy(vrng->rng, size, chr_read, vrng);
}
}
+static void handle_input(VirtIODevice *vdev, VirtQueue *vq)
+{
+ VirtIORNG *vrng = DO_UPCAST(VirtIORNG, vdev, vdev);
+ virtio_rng_process(vrng);
+}
+
static uint32_t get_features(VirtIODevice *vdev, uint32_t f)
{
return f;
VirtIORNG *vrng = opaque;
virtio_save(&vrng->vdev, f);
-
- qemu_put_byte(f, vrng->popped);
- if (vrng->popped) {
- int i;
-
- qemu_put_be32(f, vrng->elem.index);
-
- qemu_put_be32(f, vrng->elem.in_num);
- for (i = 0; i < vrng->elem.in_num; i++) {
- qemu_put_be64(f, vrng->elem.in_addr[i]);
- }
-
- qemu_put_be32(f, vrng->elem.out_num);
- for (i = 0; i < vrng->elem.out_num; i++) {
- qemu_put_be64(f, vrng->elem.out_addr[i]);
- }
- }
}
static int virtio_rng_load(QEMUFile *f, void *opaque, int version_id)
}
virtio_load(&vrng->vdev, f);
- vrng->popped = qemu_get_byte(f);
- if (vrng->popped) {
- int i;
-
- vrng->elem.index = qemu_get_be32(f);
+ /* We may have an element ready but couldn't process it due to a quota
+ * limit. Make sure to try again after live migration when the quota may
+ * have been reset.
+ */
+ virtio_rng_process(vrng);
- vrng->elem.in_num = qemu_get_be32(f);
- g_assert(vrng->elem.in_num < VIRTQUEUE_MAX_SIZE);
- for (i = 0; i < vrng->elem.in_num; i++) {
- vrng->elem.in_addr[i] = qemu_get_be64(f);
- }
+ return 0;
+}
- vrng->elem.out_num = qemu_get_be32(f);
- g_assert(vrng->elem.out_num < VIRTQUEUE_MAX_SIZE);
- for (i = 0; i < vrng->elem.out_num; i++) {
- vrng->elem.out_addr[i] = qemu_get_be64(f);
- }
+static void check_rate_limit(void *opaque)
+{
+ VirtIORNG *s = opaque;
- virtqueue_map_sg(vrng->elem.in_sg, vrng->elem.in_addr,
- vrng->elem.in_num, 1);
- virtqueue_map_sg(vrng->elem.out_sg, vrng->elem.out_addr,
- vrng->elem.out_num, 0);
- }
- return 0;
+ s->quota_remaining = s->conf->max_bytes;
+ virtio_rng_process(s);
+ qemu_mod_timer(s->rate_limit_timer,
+ qemu_get_clock_ms(vm_clock) + s->conf->period_ms);
}
+
VirtIODevice *virtio_rng_init(DeviceState *dev, VirtIORNGConf *conf)
{
VirtIORNG *vrng;
vrng->qdev = dev;
vrng->conf = conf;
- vrng->popped = false;
+
+ assert(vrng->conf->max_bytes <= INT64_MAX);
+ vrng->quota_remaining = vrng->conf->max_bytes;
+
+ vrng->rate_limit_timer = qemu_new_timer_ms(vm_clock,
+ check_rate_limit, vrng);
+
+ qemu_mod_timer(vrng->rate_limit_timer,
+ qemu_get_clock_ms(vm_clock) + vrng->conf->period_ms);
+
register_savevm(dev, "virtio-rng", -1, 1, virtio_rng_save,
virtio_rng_load, vrng);
{
VirtIORNG *vrng = DO_UPCAST(VirtIORNG, vdev, vdev);
+ qemu_del_timer(vrng->rate_limit_timer);
+ qemu_free_timer(vrng->rate_limit_timer);
unregister_savevm(vrng->qdev, "virtio-rng", vrng);
virtio_cleanup(vdev);
}