diff options
-rw-r--r-- | hw/virtio-pci.c | 4 | ||||
-rw-r--r-- | hw/virtio.c | 51 | ||||
-rw-r--r-- | hw/virtio.h | 4 |
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 |