aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--hw/virtio-pci.c4
-rw-r--r--hw/virtio.c51
-rw-r--r--hw/virtio.h4
3 files changed, 53 insertions, 6 deletions
diff --git a/hw/virtio-pci.c b/hw/virtio-pci.c
index 24fe837be6..df45036515 100644
--- a/hw/virtio-pci.c
+++ b/hw/virtio-pci.c
@@ -136,7 +136,9 @@ static uint32_t virtio_ioport_read(void *opaque, uint32_t addr)
switch (addr) {
case VIRTIO_PCI_HOST_FEATURES:
ret = vdev->get_features(vdev);
- ret |= (1 << VIRTIO_F_NOTIFY_ON_EMPTY) | (1 << VIRTIO_F_BAD_FEATURE);
+ ret |= (1 << VIRTIO_F_NOTIFY_ON_EMPTY);
+ ret |= (1 << VIRTIO_RING_F_INDIRECT_DESC);
+ ret |= (1 << VIRTIO_F_BAD_FEATURE);
break;
case VIRTIO_PCI_GUEST_FEATURES:
ret = vdev->features;
diff --git a/hw/virtio.c b/hw/virtio.c
index 1e8376d556..af71d99e4c 100644
--- a/hw/virtio.c
+++ b/hw/virtio.c
@@ -293,18 +293,41 @@ static unsigned virtqueue_next_desc(target_phys_addr_t desc_pa,
int virtqueue_avail_bytes(VirtQueue *vq, int in_bytes, int out_bytes)
{
- target_phys_addr_t desc_pa = vq->vring.desc;
- unsigned int idx, max;
- int num_bufs, in_total, out_total;
+ unsigned int idx;
+ int total_bufs, in_total, out_total;
idx = vq->last_avail_idx;
- max = vq->vring.num;
- num_bufs = in_total = out_total = 0;
+ total_bufs = in_total = out_total = 0;
while (virtqueue_num_heads(vq, idx)) {
+ unsigned int max, num_bufs, indirect = 0;
+ target_phys_addr_t desc_pa;
int i;
+ max = vq->vring.num;
+ num_bufs = total_bufs;
i = virtqueue_get_head(vq, idx++);
+ desc_pa = vq->vring.desc;
+
+ if (vring_desc_flags(desc_pa, i) & VRING_DESC_F_INDIRECT) {
+ if (vring_desc_len(desc_pa, i) % sizeof(VRingDesc)) {
+ fprintf(stderr, "Invalid size for indirect buffer table\n");
+ exit(1);
+ }
+
+ /* If we've got too many, that implies a descriptor loop. */
+ if (num_bufs >= max) {
+ fprintf(stderr, "Looped descriptor");
+ exit(1);
+ }
+
+ /* loop over the indirect descriptor table */
+ indirect = 1;
+ max = vring_desc_len(desc_pa, i) / sizeof(VRingDesc);
+ num_bufs = i = 0;
+ desc_pa = vring_desc_addr(desc_pa, i);
+ }
+
do {
/* If we've got too many, that implies a descriptor loop. */
if (++num_bufs > max) {
@@ -322,6 +345,11 @@ int virtqueue_avail_bytes(VirtQueue *vq, int in_bytes, int out_bytes)
return 1;
}
} while ((i = virtqueue_next_desc(desc_pa, i, max)) != max);
+
+ if (!indirect)
+ total_bufs = num_bufs;
+ else
+ total_bufs++;
}
return 0;
@@ -342,6 +370,19 @@ int virtqueue_pop(VirtQueue *vq, VirtQueueElement *elem)
max = vq->vring.num;
i = head = virtqueue_get_head(vq, vq->last_avail_idx++);
+
+ if (vring_desc_flags(desc_pa, i) & VRING_DESC_F_INDIRECT) {
+ if (vring_desc_len(desc_pa, i) % sizeof(VRingDesc)) {
+ fprintf(stderr, "Invalid size for indirect buffer table\n");
+ exit(1);
+ }
+
+ /* loop over the indirect descriptor table */
+ max = vring_desc_len(desc_pa, i) / sizeof(VRingDesc);
+ desc_pa = vring_desc_addr(desc_pa, i);
+ i = 0;
+ }
+
do {
struct iovec *sg;
int is_write = 0;
diff --git a/hw/virtio.h b/hw/virtio.h
index 425727e559..46ed0b28ed 100644
--- a/hw/virtio.h
+++ b/hw/virtio.h
@@ -32,6 +32,8 @@
/* We notify when the ring is completely used, even if the guest is supressing
* callbacks */
#define VIRTIO_F_NOTIFY_ON_EMPTY 24
+/* We support indirect buffer descriptors */
+#define VIRTIO_RING_F_INDIRECT_DESC 28
/* A guest should never accept this. It implies negotiation is broken. */
#define VIRTIO_F_BAD_FEATURE 30
@@ -41,6 +43,8 @@
#define VRING_DESC_F_NEXT 1
/* This marks a buffer as write-only (otherwise read-only). */
#define VRING_DESC_F_WRITE 2
+/* This means the buffer contains a list of buffer descriptors. */
+#define VRING_DESC_F_INDIRECT 4
/* This means don't notify other side when buffer added. */
#define VRING_USED_F_NO_NOTIFY 1