Implements VIRTIO_F_IN_ORDER feature support for virtio devices using the split virtqueue layout.
For a virtio device that has negotiated the VIRTIO_F_IN_ORDER feature whose virtqueues use a split virtqueue layout, it's essential that used VirtQueueElements are written to the used ring in-order. For devices that use this in-order feature, its VirtQueue's used_elems array is used to hold processed VirtQueueElements until they can be presented to the driver in-order. In the split virtqueue case, we check to see if the element was the next expected element to be written to the used ring. If it's not, nothing get written to the used ring and we're done. If it is, the element is written to the used ring and then we check to see if the next expected element continues the order. This process is repeated until we're unable to continue the order. If no elements were written to the used ring, no update to the used ring's index is needed. Signed-off-by: Jonah Palmer <jonah.pal...@oracle.com> --- hw/virtio/virtio.c | 50 ++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 46 insertions(+), 4 deletions(-) diff --git a/hw/virtio/virtio.c b/hw/virtio/virtio.c index 069d96df99..19d3d43816 100644 --- a/hw/virtio/virtio.c +++ b/hw/virtio/virtio.c @@ -856,16 +856,38 @@ static void virtqueue_split_fill(VirtQueue *vq, const VirtQueueElement *elem, unsigned int len, unsigned int idx) { VRingUsedElem uelem; + uint16_t uelem_idx; if (unlikely(!vq->vring.used)) { return; } - idx = (idx + vq->used_idx) % vq->vring.num; + if (virtio_vdev_has_feature(vq->vdev, VIRTIO_F_IN_ORDER)) { + /* Write element(s) to used ring if they're in-order */ + while (true) { + uelem_idx = vq->used_seq_idx % vq->vring.num; - uelem.id = elem->index; - uelem.len = len; - vring_used_write(vq, &uelem, idx); + /* Stop if element has been used */ + if (vq->used_elems[uelem_idx].in_num + + vq->used_elems[uelem_idx].out_num <= 0) { + break; + } + uelem.id = vq->used_elems[uelem_idx].index; + uelem.len = vq->used_elems[uelem_idx].len; + vring_used_write(vq, &uelem, uelem_idx); + + /* Mark this element as used */ + vq->used_elems[uelem_idx].in_num = 0; + vq->used_elems[uelem_idx].out_num = 0; + vq->used_seq_idx++; + } + } else { + idx = (idx + vq->used_idx) % vq->vring.num; + + uelem.id = elem->index; + uelem.len = len; + vring_used_write(vq, &uelem, idx); + } } static void virtqueue_packed_fill(VirtQueue *vq, const VirtQueueElement *elem, @@ -918,6 +940,8 @@ static void virtqueue_packed_fill_desc(VirtQueue *vq, void virtqueue_fill(VirtQueue *vq, const VirtQueueElement *elem, unsigned int len, unsigned int idx) { + uint16_t seq_idx; + trace_virtqueue_fill(vq, elem, len, idx); virtqueue_unmap_sg(vq, elem, len); @@ -926,6 +950,16 @@ void virtqueue_fill(VirtQueue *vq, const VirtQueueElement *elem, return; } + if (virtio_vdev_has_feature(vq->vdev, VIRTIO_F_IN_ORDER)) { + seq_idx = elem->seq_idx % vq->vring.num; + + vq->used_elems[seq_idx].index = elem->index; + vq->used_elems[seq_idx].len = elem->len; + vq->used_elems[seq_idx].ndescs = elem->ndescs; + vq->used_elems[seq_idx].in_num = elem->in_num; + vq->used_elems[seq_idx].out_num = elem->out_num; + } + if (virtio_vdev_has_feature(vq->vdev, VIRTIO_F_RING_PACKED)) { virtqueue_packed_fill(vq, elem, len, idx); } else { @@ -944,6 +978,14 @@ static void virtqueue_split_flush(VirtQueue *vq, unsigned int count) /* Make sure buffer is written before we update index. */ smp_wmb(); + if (virtio_vdev_has_feature(vq->vdev, VIRTIO_F_IN_ORDER)) { + count = (vq->used_seq_idx - vq->used_idx) % vq->vring.num; + + /* No in-order elements were written, nothing to update */ + if (!count) { + return; + } + } trace_virtqueue_flush(vq, count); old = vq->used_idx; new = old + count; -- 2.39.3