Amit Shah | 16c915b | 2012-06-20 12:29:32 +0530 | [diff] [blame] | 1 | /* |
| 2 | * A virtio device implementing a hardware random number generator. |
| 3 | * |
| 4 | * Copyright 2012 Red Hat, Inc. |
| 5 | * Copyright 2012 Amit Shah <amit.shah@redhat.com> |
| 6 | * |
| 7 | * This work is licensed under the terms of the GNU GPL, version 2 or |
| 8 | * (at your option) any later version. See the COPYING file in the |
| 9 | * top-level directory. |
| 10 | */ |
| 11 | |
| 12 | #include "iov.h" |
| 13 | #include "qdev.h" |
| 14 | #include "virtio.h" |
| 15 | #include "virtio-rng.h" |
| 16 | #include "qemu/rng.h" |
| 17 | |
| 18 | typedef struct VirtIORNG { |
| 19 | VirtIODevice vdev; |
| 20 | |
| 21 | DeviceState *qdev; |
| 22 | |
| 23 | /* Only one vq - guest puts buffer(s) on it when it needs entropy */ |
| 24 | VirtQueue *vq; |
Amit Shah | 16c915b | 2012-06-20 12:29:32 +0530 | [diff] [blame] | 25 | |
Amit Shah | 16c915b | 2012-06-20 12:29:32 +0530 | [diff] [blame] | 26 | VirtIORNGConf *conf; |
| 27 | |
Amit Shah | 16c915b | 2012-06-20 12:29:32 +0530 | [diff] [blame] | 28 | RngBackend *rng; |
Anthony Liguori | 904d6f5 | 2012-10-30 17:45:05 -0500 | [diff] [blame] | 29 | |
| 30 | /* We purposefully don't migrate this state. The quota will reset on the |
| 31 | * destination as a result. Rate limiting is host state, not guest state. |
| 32 | */ |
| 33 | QEMUTimer *rate_limit_timer; |
| 34 | int64_t quota_remaining; |
Amit Shah | 16c915b | 2012-06-20 12:29:32 +0530 | [diff] [blame] | 35 | } VirtIORNG; |
| 36 | |
| 37 | static bool is_guest_ready(VirtIORNG *vrng) |
| 38 | { |
| 39 | if (virtio_queue_ready(vrng->vq) |
| 40 | && (vrng->vdev.status & VIRTIO_CONFIG_S_DRIVER_OK)) { |
| 41 | return true; |
| 42 | } |
| 43 | return false; |
| 44 | } |
| 45 | |
Michael S. Tsirkin | e1f7b48 | 2012-11-30 00:02:56 +0200 | [diff] [blame] | 46 | static size_t get_request_size(VirtQueue *vq, unsigned quota) |
Amit Shah | 16c915b | 2012-06-20 12:29:32 +0530 | [diff] [blame] | 47 | { |
Amit Shah | 1441703 | 2012-11-21 11:21:18 +0530 | [diff] [blame] | 48 | unsigned int in, out; |
Amit Shah | 16c915b | 2012-06-20 12:29:32 +0530 | [diff] [blame] | 49 | |
Michael S. Tsirkin | e1f7b48 | 2012-11-30 00:02:56 +0200 | [diff] [blame] | 50 | virtqueue_get_avail_bytes(vq, &in, &out, quota, 0); |
Amit Shah | 1441703 | 2012-11-21 11:21:18 +0530 | [diff] [blame] | 51 | return in; |
Amit Shah | 16c915b | 2012-06-20 12:29:32 +0530 | [diff] [blame] | 52 | } |
| 53 | |
Anthony Liguori | 904d6f5 | 2012-10-30 17:45:05 -0500 | [diff] [blame] | 54 | static void virtio_rng_process(VirtIORNG *vrng); |
| 55 | |
Amit Shah | 16c915b | 2012-06-20 12:29:32 +0530 | [diff] [blame] | 56 | /* Send data from a char device over to the guest */ |
| 57 | static void chr_read(void *opaque, const void *buf, size_t size) |
| 58 | { |
| 59 | VirtIORNG *vrng = opaque; |
Amit Shah | 1441703 | 2012-11-21 11:21:18 +0530 | [diff] [blame] | 60 | VirtQueueElement elem; |
Amit Shah | 16c915b | 2012-06-20 12:29:32 +0530 | [diff] [blame] | 61 | size_t len; |
| 62 | int offset; |
| 63 | |
| 64 | if (!is_guest_ready(vrng)) { |
| 65 | return; |
| 66 | } |
| 67 | |
Anthony Liguori | 904d6f5 | 2012-10-30 17:45:05 -0500 | [diff] [blame] | 68 | vrng->quota_remaining -= size; |
| 69 | |
Amit Shah | 16c915b | 2012-06-20 12:29:32 +0530 | [diff] [blame] | 70 | offset = 0; |
| 71 | while (offset < size) { |
Amit Shah | 1441703 | 2012-11-21 11:21:18 +0530 | [diff] [blame] | 72 | if (!virtqueue_pop(vrng->vq, &elem)) { |
Amit Shah | 16c915b | 2012-06-20 12:29:32 +0530 | [diff] [blame] | 73 | break; |
| 74 | } |
Amit Shah | 1441703 | 2012-11-21 11:21:18 +0530 | [diff] [blame] | 75 | len = iov_from_buf(elem.in_sg, elem.in_num, |
Amit Shah | 16c915b | 2012-06-20 12:29:32 +0530 | [diff] [blame] | 76 | 0, buf + offset, size - offset); |
| 77 | offset += len; |
| 78 | |
Amit Shah | 1441703 | 2012-11-21 11:21:18 +0530 | [diff] [blame] | 79 | virtqueue_push(vrng->vq, &elem, len); |
Amit Shah | 16c915b | 2012-06-20 12:29:32 +0530 | [diff] [blame] | 80 | } |
| 81 | virtio_notify(&vrng->vdev, vrng->vq); |
Anthony Liguori | 904d6f5 | 2012-10-30 17:45:05 -0500 | [diff] [blame] | 82 | } |
| 83 | |
| 84 | static void virtio_rng_process(VirtIORNG *vrng) |
| 85 | { |
Amit Shah | 1441703 | 2012-11-21 11:21:18 +0530 | [diff] [blame] | 86 | size_t size; |
Michael S. Tsirkin | e1f7b48 | 2012-11-30 00:02:56 +0200 | [diff] [blame] | 87 | unsigned quota; |
Anthony Liguori | 904d6f5 | 2012-10-30 17:45:05 -0500 | [diff] [blame] | 88 | |
| 89 | if (!is_guest_ready(vrng)) { |
| 90 | return; |
| 91 | } |
| 92 | |
Michael S. Tsirkin | e1f7b48 | 2012-11-30 00:02:56 +0200 | [diff] [blame] | 93 | if (vrng->quota_remaining < 0) { |
| 94 | quota = 0; |
| 95 | } else { |
| 96 | quota = MIN((uint64_t)vrng->quota_remaining, (uint64_t)UINT32_MAX); |
| 97 | } |
| 98 | size = get_request_size(vrng->vq, quota); |
Anthony Liguori | 904d6f5 | 2012-10-30 17:45:05 -0500 | [diff] [blame] | 99 | size = MIN(vrng->quota_remaining, size); |
Amit Shah | 1441703 | 2012-11-21 11:21:18 +0530 | [diff] [blame] | 100 | if (size) { |
Amit Shah | 16c915b | 2012-06-20 12:29:32 +0530 | [diff] [blame] | 101 | rng_backend_request_entropy(vrng->rng, size, chr_read, vrng); |
| 102 | } |
| 103 | } |
| 104 | |
| 105 | static void handle_input(VirtIODevice *vdev, VirtQueue *vq) |
| 106 | { |
| 107 | VirtIORNG *vrng = DO_UPCAST(VirtIORNG, vdev, vdev); |
Anthony Liguori | 904d6f5 | 2012-10-30 17:45:05 -0500 | [diff] [blame] | 108 | virtio_rng_process(vrng); |
Amit Shah | 16c915b | 2012-06-20 12:29:32 +0530 | [diff] [blame] | 109 | } |
| 110 | |
| 111 | static uint32_t get_features(VirtIODevice *vdev, uint32_t f) |
| 112 | { |
| 113 | return f; |
| 114 | } |
| 115 | |
| 116 | static void virtio_rng_save(QEMUFile *f, void *opaque) |
| 117 | { |
| 118 | VirtIORNG *vrng = opaque; |
| 119 | |
| 120 | virtio_save(&vrng->vdev, f); |
Amit Shah | 16c915b | 2012-06-20 12:29:32 +0530 | [diff] [blame] | 121 | } |
| 122 | |
| 123 | static int virtio_rng_load(QEMUFile *f, void *opaque, int version_id) |
| 124 | { |
| 125 | VirtIORNG *vrng = opaque; |
| 126 | |
| 127 | if (version_id != 1) { |
| 128 | return -EINVAL; |
| 129 | } |
| 130 | virtio_load(&vrng->vdev, f); |
| 131 | |
Anthony Liguori | 904d6f5 | 2012-10-30 17:45:05 -0500 | [diff] [blame] | 132 | /* We may have an element ready but couldn't process it due to a quota |
Amit Shah | 42015c9 | 2012-11-21 11:21:21 +0530 | [diff] [blame] | 133 | * limit. Make sure to try again after live migration when the quota may |
| 134 | * have been reset. |
| 135 | */ |
Anthony Liguori | 904d6f5 | 2012-10-30 17:45:05 -0500 | [diff] [blame] | 136 | virtio_rng_process(vrng); |
| 137 | |
Amit Shah | 16c915b | 2012-06-20 12:29:32 +0530 | [diff] [blame] | 138 | return 0; |
| 139 | } |
| 140 | |
Anthony Liguori | 904d6f5 | 2012-10-30 17:45:05 -0500 | [diff] [blame] | 141 | static void check_rate_limit(void *opaque) |
| 142 | { |
| 143 | VirtIORNG *s = opaque; |
| 144 | |
| 145 | s->quota_remaining = s->conf->max_bytes; |
| 146 | virtio_rng_process(s); |
| 147 | qemu_mod_timer(s->rate_limit_timer, |
| 148 | qemu_get_clock_ms(vm_clock) + s->conf->period_ms); |
| 149 | } |
| 150 | |
| 151 | |
Amit Shah | 16c915b | 2012-06-20 12:29:32 +0530 | [diff] [blame] | 152 | VirtIODevice *virtio_rng_init(DeviceState *dev, VirtIORNGConf *conf) |
| 153 | { |
| 154 | VirtIORNG *vrng; |
| 155 | VirtIODevice *vdev; |
| 156 | Error *local_err = NULL; |
| 157 | |
| 158 | vdev = virtio_common_init("virtio-rng", VIRTIO_ID_RNG, 0, |
| 159 | sizeof(VirtIORNG)); |
| 160 | |
| 161 | vrng = DO_UPCAST(VirtIORNG, vdev, vdev); |
| 162 | |
| 163 | vrng->rng = conf->rng; |
| 164 | if (vrng->rng == NULL) { |
| 165 | qerror_report(QERR_INVALID_PARAMETER_VALUE, "rng", "a valid object"); |
| 166 | return NULL; |
| 167 | } |
| 168 | |
| 169 | rng_backend_open(vrng->rng, &local_err); |
| 170 | if (local_err) { |
| 171 | qerror_report_err(local_err); |
| 172 | error_free(local_err); |
| 173 | return NULL; |
| 174 | } |
| 175 | |
| 176 | vrng->vq = virtio_add_queue(vdev, 8, handle_input); |
| 177 | vrng->vdev.get_features = get_features; |
| 178 | |
| 179 | vrng->qdev = dev; |
| 180 | vrng->conf = conf; |
Amit Shah | 1441703 | 2012-11-21 11:21:18 +0530 | [diff] [blame] | 181 | |
Paolo Bonzini | 03a36f1 | 2012-11-27 09:16:24 +0100 | [diff] [blame] | 182 | assert(vrng->conf->max_bytes <= INT64_MAX); |
Anthony Liguori | 904d6f5 | 2012-10-30 17:45:05 -0500 | [diff] [blame] | 183 | vrng->quota_remaining = vrng->conf->max_bytes; |
| 184 | |
Anthony Liguori | 904d6f5 | 2012-10-30 17:45:05 -0500 | [diff] [blame] | 185 | vrng->rate_limit_timer = qemu_new_timer_ms(vm_clock, |
| 186 | check_rate_limit, vrng); |
| 187 | |
| 188 | qemu_mod_timer(vrng->rate_limit_timer, |
| 189 | qemu_get_clock_ms(vm_clock) + vrng->conf->period_ms); |
| 190 | |
Amit Shah | 16c915b | 2012-06-20 12:29:32 +0530 | [diff] [blame] | 191 | register_savevm(dev, "virtio-rng", -1, 1, virtio_rng_save, |
| 192 | virtio_rng_load, vrng); |
| 193 | |
| 194 | return vdev; |
| 195 | } |
| 196 | |
| 197 | void virtio_rng_exit(VirtIODevice *vdev) |
| 198 | { |
| 199 | VirtIORNG *vrng = DO_UPCAST(VirtIORNG, vdev, vdev); |
| 200 | |
Amit Shah | 8cc6774 | 2012-11-21 11:21:20 +0530 | [diff] [blame] | 201 | qemu_del_timer(vrng->rate_limit_timer); |
| 202 | qemu_free_timer(vrng->rate_limit_timer); |
Amit Shah | 16c915b | 2012-06-20 12:29:32 +0530 | [diff] [blame] | 203 | unregister_savevm(vrng->qdev, "virtio-rng", vrng); |
| 204 | virtio_cleanup(vdev); |
| 205 | } |