diff options
Diffstat (limited to 'hw/virtio/virtio.c')
-rw-r--r-- | hw/virtio/virtio.c | 304 |
1 files changed, 250 insertions, 54 deletions
diff --git a/hw/virtio/virtio.c b/hw/virtio/virtio.c index 74c085c74d..d48d1a98a7 100644 --- a/hw/virtio/virtio.c +++ b/hw/virtio/virtio.c @@ -264,12 +264,57 @@ static void virtqueue_unmap_sg(VirtQueue *vq, const VirtQueueElement *elem, 0, elem->out_sg[i].iov_len); } +/* virtqueue_detach_element: + * @vq: The #VirtQueue + * @elem: The #VirtQueueElement + * @len: number of bytes written + * + * Detach the element from the virtqueue. This function is suitable for device + * reset or other situations where a #VirtQueueElement is simply freed and will + * not be pushed or discarded. + */ +void virtqueue_detach_element(VirtQueue *vq, const VirtQueueElement *elem, + unsigned int len) +{ + vq->inuse--; + virtqueue_unmap_sg(vq, elem, len); +} + +/* virtqueue_discard: + * @vq: The #VirtQueue + * @elem: The #VirtQueueElement + * @len: number of bytes written + * + * Pretend the most recent element wasn't popped from the virtqueue. The next + * call to virtqueue_pop() will refetch the element. + */ void virtqueue_discard(VirtQueue *vq, const VirtQueueElement *elem, unsigned int len) { vq->last_avail_idx--; - vq->inuse--; - virtqueue_unmap_sg(vq, elem, len); + virtqueue_detach_element(vq, elem, len); +} + +/* virtqueue_rewind: + * @vq: The #VirtQueue + * @num: Number of elements to push back + * + * Pretend that elements weren't popped from the virtqueue. The next + * virtqueue_pop() will refetch the oldest element. + * + * Use virtqueue_discard() instead if you have a VirtQueueElement. + * + * Returns: true on success, false if @num is greater than the number of in use + * elements. + */ +bool virtqueue_rewind(VirtQueue *vq, unsigned int num) +{ + if (num > vq->inuse) { + return false; + } + vq->last_avail_idx -= num; + vq->inuse -= num; + return true; } void virtqueue_fill(VirtQueue *vq, const VirtQueueElement *elem, @@ -281,6 +326,10 @@ void virtqueue_fill(VirtQueue *vq, const VirtQueueElement *elem, virtqueue_unmap_sg(vq, elem, len); + if (unlikely(vq->vdev->broken)) { + return; + } + idx = (idx + vq->used_idx) % vq->vring.num; uelem.id = elem->index; @@ -291,6 +340,12 @@ void virtqueue_fill(VirtQueue *vq, const VirtQueueElement *elem, void virtqueue_flush(VirtQueue *vq, unsigned int count) { uint16_t old, new; + + if (unlikely(vq->vdev->broken)) { + vq->inuse -= count; + return; + } + /* Make sure buffer is written before we update index. */ smp_wmb(); trace_virtqueue_flush(vq, count); @@ -315,9 +370,9 @@ static int virtqueue_num_heads(VirtQueue *vq, unsigned int idx) /* Check it isn't doing very strange things with descriptor numbers. */ if (num_heads > vq->vring.num) { - error_report("Guest moved used index from %u to %u", + virtio_error(vq->vdev, "Guest moved used index from %u to %u", idx, vq->shadow_avail_idx); - exit(1); + return -EINVAL; } /* On success, callers read a descriptor at vq->last_avail_idx. * Make sure descriptor read does not bypass avail index read. */ @@ -328,45 +383,49 @@ static int virtqueue_num_heads(VirtQueue *vq, unsigned int idx) return num_heads; } -static unsigned int virtqueue_get_head(VirtQueue *vq, unsigned int idx) +static bool virtqueue_get_head(VirtQueue *vq, unsigned int idx, + unsigned int *head) { - unsigned int head; - /* Grab the next descriptor number they're advertising, and increment * the index we've seen. */ - head = vring_avail_ring(vq, idx % vq->vring.num); + *head = vring_avail_ring(vq, idx % vq->vring.num); /* If their number is silly, that's a fatal mistake. */ - if (head >= vq->vring.num) { - error_report("Guest says index %u is available", head); - exit(1); + if (*head >= vq->vring.num) { + virtio_error(vq->vdev, "Guest says index %u is available", *head); + return false; } - return head; + return true; } -static unsigned virtqueue_read_next_desc(VirtIODevice *vdev, VRingDesc *desc, - hwaddr desc_pa, unsigned int max) -{ - unsigned int next; +enum { + VIRTQUEUE_READ_DESC_ERROR = -1, + VIRTQUEUE_READ_DESC_DONE = 0, /* end of chain */ + VIRTQUEUE_READ_DESC_MORE = 1, /* more buffers in chain */ +}; +static int virtqueue_read_next_desc(VirtIODevice *vdev, VRingDesc *desc, + hwaddr desc_pa, unsigned int max, + unsigned int *next) +{ /* If this descriptor says it doesn't chain, we're done. */ if (!(desc->flags & VRING_DESC_F_NEXT)) { - return max; + return VIRTQUEUE_READ_DESC_DONE; } /* Check they're not leading us off end of descriptors. */ - next = desc->next; + *next = desc->next; /* Make sure compiler knows to grab that: we don't want it changing! */ smp_wmb(); - if (next >= max) { - error_report("Desc next is %u", next); - exit(1); + if (*next >= max) { + virtio_error(vdev, "Desc next is %u", *next); + return VIRTQUEUE_READ_DESC_ERROR; } - vring_desc_read(vdev, desc, desc_pa, next); - return next; + vring_desc_read(vdev, desc, desc_pa, *next); + return VIRTQUEUE_READ_DESC_MORE; } void virtqueue_get_avail_bytes(VirtQueue *vq, unsigned int *in_bytes, @@ -375,33 +434,38 @@ void virtqueue_get_avail_bytes(VirtQueue *vq, unsigned int *in_bytes, { unsigned int idx; unsigned int total_bufs, in_total, out_total; + int rc; idx = vq->last_avail_idx; total_bufs = in_total = out_total = 0; - while (virtqueue_num_heads(vq, idx)) { + while ((rc = virtqueue_num_heads(vq, idx)) > 0) { VirtIODevice *vdev = vq->vdev; unsigned int max, num_bufs, indirect = 0; VRingDesc desc; hwaddr desc_pa; - int i; + unsigned int i; max = vq->vring.num; num_bufs = total_bufs; - i = virtqueue_get_head(vq, idx++); + + if (!virtqueue_get_head(vq, idx++, &i)) { + goto err; + } + desc_pa = vq->vring.desc; vring_desc_read(vdev, &desc, desc_pa, i); if (desc.flags & VRING_DESC_F_INDIRECT) { if (desc.len % sizeof(VRingDesc)) { - error_report("Invalid size for indirect buffer table"); - exit(1); + virtio_error(vdev, "Invalid size for indirect buffer table"); + goto err; } /* If we've got too many, that implies a descriptor loop. */ if (num_bufs >= max) { - error_report("Looped descriptor"); - exit(1); + virtio_error(vdev, "Looped descriptor"); + goto err; } /* loop over the indirect descriptor table */ @@ -415,8 +479,8 @@ void virtqueue_get_avail_bytes(VirtQueue *vq, unsigned int *in_bytes, do { /* If we've got too many, that implies a descriptor loop. */ if (++num_bufs > max) { - error_report("Looped descriptor"); - exit(1); + virtio_error(vdev, "Looped descriptor"); + goto err; } if (desc.flags & VRING_DESC_F_WRITE) { @@ -427,13 +491,24 @@ void virtqueue_get_avail_bytes(VirtQueue *vq, unsigned int *in_bytes, if (in_total >= max_in_bytes && out_total >= max_out_bytes) { goto done; } - } while ((i = virtqueue_read_next_desc(vdev, &desc, desc_pa, max)) != max); + + rc = virtqueue_read_next_desc(vdev, &desc, desc_pa, max, &i); + } while (rc == VIRTQUEUE_READ_DESC_MORE); + + if (rc == VIRTQUEUE_READ_DESC_ERROR) { + goto err; + } if (!indirect) total_bufs = num_bufs; else total_bufs++; } + + if (rc < 0) { + goto err; + } + done: if (in_bytes) { *in_bytes = in_total; @@ -441,6 +516,11 @@ done: if (out_bytes) { *out_bytes = out_total; } + return; + +err: + in_total = out_total = 0; + goto done; } int virtqueue_avail_bytes(VirtQueue *vq, unsigned int in_bytes, @@ -452,27 +532,35 @@ int virtqueue_avail_bytes(VirtQueue *vq, unsigned int in_bytes, return in_bytes <= in_total && out_bytes <= out_total; } -static void virtqueue_map_desc(unsigned int *p_num_sg, hwaddr *addr, struct iovec *iov, +static bool virtqueue_map_desc(VirtIODevice *vdev, unsigned int *p_num_sg, + hwaddr *addr, struct iovec *iov, unsigned int max_num_sg, bool is_write, hwaddr pa, size_t sz) { + bool ok = false; unsigned num_sg = *p_num_sg; assert(num_sg <= max_num_sg); if (!sz) { - error_report("virtio: zero sized buffers are not allowed"); - exit(1); + virtio_error(vdev, "virtio: zero sized buffers are not allowed"); + goto out; } while (sz) { hwaddr len = sz; if (num_sg == max_num_sg) { - error_report("virtio: too many write descriptors in indirect table"); - exit(1); + virtio_error(vdev, "virtio: too many write descriptors in " + "indirect table"); + goto out; } iov[num_sg].iov_base = cpu_physical_memory_map(pa, &len, is_write); + if (!iov[num_sg].iov_base) { + virtio_error(vdev, "virtio: bogus descriptor or out of resources"); + goto out; + } + iov[num_sg].iov_len = len; addr[num_sg] = pa; @@ -480,7 +568,28 @@ static void virtqueue_map_desc(unsigned int *p_num_sg, hwaddr *addr, struct iove pa += len; num_sg++; } + ok = true; + +out: *p_num_sg = num_sg; + return ok; +} + +/* Only used by error code paths before we have a VirtQueueElement (therefore + * virtqueue_unmap_sg() can't be used). Assumes buffers weren't written to + * yet. + */ +static void virtqueue_undo_map_desc(unsigned int out_num, unsigned int in_num, + struct iovec *iov) +{ + unsigned int i; + + for (i = 0; i < out_num + in_num; i++) { + int is_write = i >= out_num; + + cpu_physical_memory_unmap(iov->iov_base, iov->iov_len, is_write, 0); + iov++; + } } static void virtqueue_map_iovec(struct iovec *sg, hwaddr *addr, @@ -555,7 +664,11 @@ void *virtqueue_pop(VirtQueue *vq, size_t sz) hwaddr addr[VIRTQUEUE_MAX_SIZE]; struct iovec iov[VIRTQUEUE_MAX_SIZE]; VRingDesc desc; + int rc; + if (unlikely(vdev->broken)) { + return NULL; + } if (virtio_queue_empty(vq)) { return NULL; } @@ -569,20 +682,24 @@ void *virtqueue_pop(VirtQueue *vq, size_t sz) max = vq->vring.num; if (vq->inuse >= vq->vring.num) { - error_report("Virtqueue size exceeded"); - exit(1); + virtio_error(vdev, "Virtqueue size exceeded"); + return NULL; + } + + if (!virtqueue_get_head(vq, vq->last_avail_idx++, &head)) { + return NULL; } - i = head = virtqueue_get_head(vq, vq->last_avail_idx++); if (virtio_vdev_has_feature(vdev, VIRTIO_RING_F_EVENT_IDX)) { vring_set_avail_event(vq, vq->last_avail_idx); } + i = head; vring_desc_read(vdev, &desc, desc_pa, i); if (desc.flags & VRING_DESC_F_INDIRECT) { if (desc.len % sizeof(VRingDesc)) { - error_report("Invalid size for indirect buffer table"); - exit(1); + virtio_error(vdev, "Invalid size for indirect buffer table"); + return NULL; } /* loop over the indirect descriptor table */ @@ -594,24 +711,38 @@ void *virtqueue_pop(VirtQueue *vq, size_t sz) /* Collect all the descriptors */ do { + bool map_ok; + if (desc.flags & VRING_DESC_F_WRITE) { - virtqueue_map_desc(&in_num, addr + out_num, iov + out_num, - VIRTQUEUE_MAX_SIZE - out_num, true, desc.addr, desc.len); + map_ok = virtqueue_map_desc(vdev, &in_num, addr + out_num, + iov + out_num, + VIRTQUEUE_MAX_SIZE - out_num, true, + desc.addr, desc.len); } else { if (in_num) { - error_report("Incorrect order for descriptors"); - exit(1); + virtio_error(vdev, "Incorrect order for descriptors"); + goto err_undo_map; } - virtqueue_map_desc(&out_num, addr, iov, - VIRTQUEUE_MAX_SIZE, false, desc.addr, desc.len); + map_ok = virtqueue_map_desc(vdev, &out_num, addr, iov, + VIRTQUEUE_MAX_SIZE, false, + desc.addr, desc.len); + } + if (!map_ok) { + goto err_undo_map; } /* If we've got too many, that implies a descriptor loop. */ if ((in_num + out_num) > max) { - error_report("Looped descriptor"); - exit(1); + virtio_error(vdev, "Looped descriptor"); + goto err_undo_map; } - } while ((i = virtqueue_read_next_desc(vdev, &desc, desc_pa, max)) != max); + + rc = virtqueue_read_next_desc(vdev, &desc, desc_pa, max, &i); + } while (rc == VIRTQUEUE_READ_DESC_MORE); + + if (rc == VIRTQUEUE_READ_DESC_ERROR) { + goto err_undo_map; + } /* Now copy what we have collected and mapped */ elem = virtqueue_alloc_element(sz, out_num, in_num); @@ -629,6 +760,10 @@ void *virtqueue_pop(VirtQueue *vq, size_t sz) trace_virtqueue_pop(vq, elem, elem->in_num, elem->out_num); return elem; + +err_undo_map: + virtqueue_undo_map_desc(out_num, in_num, iov); + return NULL; } /* Reading and writing a structure directly to QEMUFile is *awful*, but @@ -720,6 +855,10 @@ static void virtio_notify_vector(VirtIODevice *vdev, uint16_t vector) BusState *qbus = qdev_get_parent_bus(DEVICE(vdev)); VirtioBusClass *k = VIRTIO_BUS_GET_CLASS(qbus); + if (unlikely(vdev->broken)) { + return; + } + if (k->notify) { k->notify(qbus->parent, vector); } @@ -803,6 +942,7 @@ void virtio_reset(void *opaque) k->reset(vdev); } + vdev->broken = false; vdev->guest_features = 0; vdev->queue_sel = 0; vdev->status = 0; @@ -822,6 +962,7 @@ void virtio_reset(void *opaque) vdev->vq[i].signalled_used_valid = false; vdev->vq[i].notification = true; vdev->vq[i].vring.num = vdev->vq[i].vring.num_default; + vdev->vq[i].inuse = 0; } } @@ -1109,6 +1250,10 @@ static void virtio_queue_notify_vq(VirtQueue *vq) if (vq->vring.desc && vq->handle_output) { VirtIODevice *vdev = vq->vdev; + if (unlikely(vdev->broken)) { + return; + } + trace_virtio_queue_notify(vdev, vq - vdev->vq, vq); vq->handle_output(vdev, vq); } @@ -1293,6 +1438,13 @@ static bool virtio_extra_state_needed(void *opaque) k->has_extra_state(qbus->parent); } +static bool virtio_broken_needed(void *opaque) +{ + VirtIODevice *vdev = opaque; + + return vdev->broken; +} + static const VMStateDescription vmstate_virtqueue = { .name = "virtqueue_state", .version_id = 1, @@ -1407,6 +1559,17 @@ static const VMStateDescription vmstate_virtio_64bit_features = { } }; +static const VMStateDescription vmstate_virtio_broken = { + .name = "virtio/broken", + .version_id = 1, + .minimum_version_id = 1, + .needed = &virtio_broken_needed, + .fields = (VMStateField[]) { + VMSTATE_BOOL(broken, VirtIODevice), + VMSTATE_END_OF_LIST() + } +}; + static const VMStateDescription vmstate_virtio = { .name = "virtio", .version_id = 1, @@ -1420,6 +1583,7 @@ static const VMStateDescription vmstate_virtio = { &vmstate_virtio_64bit_features, &vmstate_virtio_virtqueues, &vmstate_virtio_ringsize, + &vmstate_virtio_broken, &vmstate_virtio_extra_state, NULL } @@ -1476,11 +1640,26 @@ void virtio_save(VirtIODevice *vdev, QEMUFile *f) } /* A wrapper for use as a VMState .put function */ -void virtio_vmstate_save(QEMUFile *f, void *opaque, size_t size) +static void virtio_device_put(QEMUFile *f, void *opaque, size_t size) { virtio_save(VIRTIO_DEVICE(opaque), f); } +/* A wrapper for use as a VMState .get function */ +static int virtio_device_get(QEMUFile *f, void *opaque, size_t size) +{ + VirtIODevice *vdev = VIRTIO_DEVICE(opaque); + DeviceClass *dc = DEVICE_CLASS(VIRTIO_DEVICE_GET_CLASS(vdev)); + + return virtio_load(vdev, f, dc->vmsd->version_id); +} + +const VMStateInfo virtio_vmstate_info = { + .name = "virtio", + .get = virtio_device_get, + .put = virtio_device_put, +}; + static int virtio_set_features_nocheck(VirtIODevice *vdev, uint64_t val) { VirtioDeviceClass *k = VIRTIO_DEVICE_GET_CLASS(vdev); @@ -1585,7 +1764,7 @@ int virtio_load(VirtIODevice *vdev, QEMUFile *f, int version_id) "inconsistent with Host index 0x%x", i, vdev->vq[i].last_avail_idx); return -1; - } + } if (k->load_queue) { ret = k->load_queue(qbus->parent, i, f); if (ret) @@ -1730,6 +1909,7 @@ void virtio_init(VirtIODevice *vdev, const char *name, vdev->config_vector = VIRTIO_NO_VECTOR; vdev->vq = g_malloc0(sizeof(VirtQueue) * VIRTIO_QUEUE_MAX); vdev->vm_running = runstate_is_running(); + vdev->broken = false; for (i = 0; i < VIRTIO_QUEUE_MAX; i++) { vdev->vq[i].vector = VIRTIO_NO_VECTOR; vdev->vq[i].vdev = vdev; @@ -1916,6 +2096,22 @@ void virtio_device_set_child_bus_name(VirtIODevice *vdev, char *bus_name) vdev->bus_name = g_strdup(bus_name); } +void GCC_FMT_ATTR(2, 3) virtio_error(VirtIODevice *vdev, const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + error_vreport(fmt, ap); + va_end(ap); + + vdev->broken = true; + + if (virtio_vdev_has_feature(vdev, VIRTIO_F_VERSION_1)) { + virtio_set_status(vdev, vdev->status | VIRTIO_CONFIG_S_NEEDS_RESET); + virtio_notify_config(vdev); + } +} + static void virtio_device_realize(DeviceState *dev, Error **errp) { VirtIODevice *vdev = VIRTIO_DEVICE(dev); |