dataplane: simplify indirect descriptor read
PATCH 1/2 dataplane: simplify indirect descriptor read
Use address_space_read to make sure we handle the case of an indirect descriptor crossing DIMM boundary correctly.
---
Warning: compile-tested only.
hw/virtio/dataplane/vring.c | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-)
hw/virtio/dataplane/vring.c +18 -10
@@ -257,6 +257,21 @@ static void copy_in_vring_desc(VirtIODevice *vdev, host->next =virtio_lduw_p (vdev, &guest->next); } +static bool read_vring_desc (VirtIODevice *vdev, +hwaddr guest, +struct vring_desc *host) +{ +if (address_space_read (&address_space_memory, guest,MEMTXATTRS_UNSPECIFIED , + (uint8_t *)host,sizeof *host)) { +return false; + } + host->addr =virtio_tswap64 (vdev, host->addr); + host->len =virtio_tswap32 (vdev, host->len); + host->flags =virtio_tswap16 (vdev, host->flags); + host->next =virtio_tswap16 (vdev, host->next); +return true; +} +/* This is stolen from linux/drivers/vhost/vhost.c. */ static int get_indirect (VirtIODevice *vdev,Vring *vring,VirtQueueElement *elem,struct vring_desc *indirect)
@@ -284,23 +299,16 @@ static int get_indirect(VirtIODevice *vdev, Vring *vring, } do { - struct vring_desc *desc_ptr; - MemoryRegion *mr; - /* Translate indirect descriptor */ - desc_ptr = vring_map(&mr, - indirect->addr + found * sizeof(desc), - sizeof(desc), false); - if (!desc_ptr) { - error_report("Failed to map indirect descriptor " + if (!read_vring_desc(vdev, indirect->addr + found * sizeof(desc), + &desc)) { + error_report("Failed to read indirect descriptor " "addr %#" PRIx64 " len %zu", (uint64_t)indirect->addr + found * sizeof(desc), sizeof(desc)); vring->broken = true; return -EFAULT; } - copy_in_vring_desc(vdev, desc_ptr, &desc); - memory_region_unref(mr); /* Ensure descriptor has been loaded before accessing fields */ barrier(); /* read_barrier_depends(); */
Signature · 1 line
MST
PATCH 2/2 dataplane: support non-contigious s/g
bring_map currently fails if one of the entries it's mapping is contigious in GPA but not HVA address space. Introduce a mapped_len parameter so it can handle this, returning the actual mapped length.
This will still fail if there's no space left in the sg, but luckily max queue size in use is currently 256, while max sg size is 1024, so we should be OK even is all entries happen to cross a single DIMM boundary.
Won't work well with very small DIMM sizes, unfortunately:
e.g. this will fail with 4K DIMMs where a single request might span a large number of DIMMs.
Let's hope these are uncommon - at least we are not breaking things.
---
Warning: compile-tested only.
hw/virtio/dataplane/vring.c | 68 ++++++++++++++++++++++++++++++--------------- 1 file changed, 46 insertions(+), 22 deletions(-)
hw/virtio/dataplane/vring.c +46 -22
@@ -25,15 +25,30 @@/* vring_map can be coupled with vring_unmap or (if you still have the * value returned in *mr) memory_region_unref. +* Returns NULL on failure. +* Callers that can handle a partial mapping must supply mapped_len pointer to +* get the actual length mapped. +* Passing mapped_len == NULL requires either a full mapping or a failure. */ -static void *vring_map (MemoryRegion **mr,hwaddr phys,hwaddr len, +static void *vring_map (MemoryRegion **mr,hwaddr phys, +hwaddr len,hwaddr *mapped_len,bool is_write) {MemoryRegionSection section =memory_region_find (get_system_memory (), phys, len); +uint64_t size; -if (!section.mr ||int128_get64 (section.size) < len) { +if (!section.mr) { goto out; } + + size =int128_get64 (section.size); +assert (size); + +/* Passing mapped_len == NULL requires either a full mapping or a failure. */ +if (!mapped_len && size < len) { + goto out; + } +if (is_write && section.readonly) { goto out; }
@@ -46,6 +61,10 @@ static void *vring_map(MemoryRegion **mr, hwaddr phys, hwaddr len, goto out; } +if (mapped_len) { + *mapped_len =MIN (size, len); + } + *mr = section.mr;return memory_region_get_ram_ptr (section.mr) + section.offset_within_region;
@@ -78,7 +97,7 @@ bool vring_setup(Vring *vring, VirtIODevice *vdev, int n) addr =virtio_queue_get_desc_addr (vdev, n); size =virtio_queue_get_desc_size (vdev, n);/* Map the descriptor area as read only */ - ptr =vring_map (&vring->mr_desc, addr, size, false); + ptr =vring_map (&vring->mr_desc, addr, size,NULL , false);if (!ptr) { error_report("Failed to map 0x%" HWADDR_PRIx " byte for vring desc " "at 0x%" HWADDR_PRIx,
@@ -92,7 +111,7 @@ bool vring_setup(Vring *vring, VirtIODevice *vdev, int n)/* Add the size of the used_event_idx */ size +=sizeof (uint16_t );/* Map the driver area as read only */ - ptr =vring_map (&vring->mr_avail, addr, size, false); + ptr =vring_map (&vring->mr_avail, addr, size,NULL , false);if (!ptr) { error_report("Failed to map 0x%" HWADDR_PRIx " byte for vring avail " "at 0x%" HWADDR_PRIx,
@@ -106,7 +125,7 @@ bool vring_setup(Vring *vring, VirtIODevice *vdev, int n)/* Add the size of the avail_event_idx */ size +=sizeof (uint16_t );/* Map the device area as read-write */ - ptr =vring_map (&vring->mr_used, addr, size, true); + ptr =vring_map (&vring->mr_used, addr, size,NULL , true);if (!ptr) { error_report("Failed to map 0x%" HWADDR_PRIx " byte for vring used " "at 0x%" HWADDR_PRIx,
@@ -206,6 +225,7 @@ static int get_desc(Vring *vring, VirtQueueElement *elem,struct iovec *iov;hwaddr *addr;MemoryRegion *mr; +hwaddr len;if (desc->flags &VRING_DESC_F_WRITE ) { num = &elem->in_num;
@@ -224,26 +244,30 @@ static int get_desc(Vring *vring, VirtQueueElement *elem, } } -/* Stop for now if there are not enough iovecs available. */ -if (*num >=VIRTQUEUE_MAX_SIZE ) { -error_report ("Invalid SG num: %u" , *num); -return -EFAULT ; - } +while (desc->len) { +/* Stop for now if there are not enough iovecs available. */ +if (*num >=VIRTQUEUE_MAX_SIZE ) { +error_report ("Invalid SG num: %u" , *num); +return -EFAULT ; + } -/* TODO handle non-contiguous memory across region boundaries */ - iov->iov_base =vring_map (&mr, desc->addr, desc->len, - desc->flags &VRING_DESC_F_WRITE ); -if (!iov->iov_base) { -error_report ("Failed to map descriptor addr %#" PRIx64" len %u" , - (uint64_t )desc->addr, desc->len); -return -EFAULT ; + iov->iov_base =vring_map (&mr, desc->addr, desc->len, &len, + desc->flags &VRING_DESC_F_WRITE ); +if (!iov->iov_base) { +error_report ("Failed to map descriptor addr %#" PRIx64" len %u" , + (uint64_t )desc->addr, desc->len); +return -EFAULT ; + } + +/* The MemoryRegion is looked up again and unref'ed later, leave the +* ref in place. */ + iov->iov_len = len; + *addr = desc->addr; + desc->len -= len; + desc->addr += len; + *num +=1 ; } -/* The MemoryRegion is looked up again and unref'ed later, leave the -* ref in place. */ - iov->iov_len = desc->len; - *addr = desc->addr; - *num +=1 ;return 0 ; }
Signature · 1 line
MST
Re: [Qemu-devel] [PATCH 2/2] dataplane: support non-contigious s/g
Quoted 13 lines · On Wed, 28 Oct 2015 17:48:04 +0200 "Michael S. Tsirkin" <[email protected]> wrote:
bring_mapcurrently fails if one of the entries it's mapping is contigious in GPA but not HVA address space. Introduce amapped_lenparameter so it can handle this, returning the actual mapped length.This will still fail if there's no space left in the sg, but luckily max queue size in use is currently 256, while max sg size is 1024, so we should be OK even is all entries happen to cross a single DIMM boundary.
Won't work well with very small DIMM sizes, unfortunately:
e.g. this will fail with 4K DIMMs where a single request might span a large number of DIMMs.
Quoted 5 lines
Let's hope these are uncommon - at least we are not breaking things.
Reported-by Stefan Hajnoczi <[email protected]>Reported-by Igor Mammedov <[email protected]>Signed-off-by Michael S. Tsirkin <[email protected]>
with later posted fixup
Quoted 147 lines
---
Warning: compile-tested only.
hw/virtio/dataplane/vring.c | 68++++++++++++++++++++++++++++++--------------- 1 file changed, 46 insertions(+), 22 deletions(-)
hw/virtio/dataplane/vring.c +0
-0@@ -25,15 +25,30 @@/* vring_map can be coupled with vring_unmap or (if you still have the
* value returned in *mr)memory_region_unref.
+ * Returns NULL on failure.
+ * Callers that can handle a partial mapping must supplymapped_lenpointer to
+ * get the actual length mapped.
+ * Passingmapped_len== NULL requires either a full mapping or a failure. */hw/virtio/dataplane/vring.c +2
-1-static void *vring_map (MemoryRegion **mr,hwaddr phys,hwaddr len, +static void *vring_map (MemoryRegion **mr,hwaddr phys, +hwaddr len,hwaddr *mapped_len,bool is_write) { MemoryRegionSection section =
memory_region_find(get_system_memory(), phys, len);hw/virtio/dataplane/vring.c +7
-1+uint64_t size; -if (!section.mr ||int128_get64 (section.size) < len) { +if (!section.mr) { goto out; } + + size =int128_get64 (section.size); +assert (size); + +/* Passing mapped_len == NULL requires either a full mapping or a failure. */
hw/virtio/dataplane/vring.c +9
-1+if (!mapped_len && size < len) { + goto out; + } +if (is_write && section.readonly) { goto out; }@@ -46,6 +61,10 @@ static void *vring_map(MemoryRegion **mr, hwaddr phys, hwaddr len, goto out; } +if (mapped_len) { + *mapped_len =MIN (size, len); + } + *mr = section.mr;return memory_region_get_ram_ptr (section.mr) + section.offset_within_region;@@ -78,7 +97,7 @@ bool vring_setup(Vring *vring, VirtIODevice *vdev, int n) addr = virtio_queue_get_desc_addr(vdev, n); size =virtio_queue_get_desc_size (vdev, n);/* Map the descriptor area as read only */ - ptr =vring_map (&vring->mr_desc, addr, size, false); + ptr =vring_map (&vring->mr_desc, addr, size,NULL , false);if (!ptr) { error_report("Failed to map 0x%" HWADDR_PRIx " byte for vring desc " "at 0x%" HWADDR_PRIx,@@ -92,7 +111,7 @@ bool vring_setup(Vring *vring, VirtIODevice *vdev,int n) /* Add the size of the
used_event_idx*/size += sizeof(uint16_t); /* Map the driver area as read only */hw/virtio/dataplane/vring.c +1
-1- ptr =vring_map (&vring->mr_avail, addr, size, false); + ptr =vring_map (&vring->mr_avail, addr, size,NULL , false); if (!ptr) { error_report("Failed to map 0x%" HWADDR_PRIx " byte forvring avail " "at 0x%"
HWADDR_PRIx,hw/virtio/dataplane/vring.c +17
-13@@ -106,7 +125,7 @@ bool vring_setup(Vring *vring, VirtIODevice *vdev, int n) /* Add the size of the avail_event_idx */ size +=sizeof (uint16_t );/* Map the device area as read-write */ - ptr =vring_map (&vring->mr_used, addr, size, true); + ptr =vring_map (&vring->mr_used, addr, size,NULL , true);if (!ptr) { error_report("Failed to map 0x%" HWADDR_PRIx " byte for vring used " "at 0x%" HWADDR_PRIx,@@ -206,6 +225,7 @@ static int get_desc(Vring *vring, VirtQueueElement *elem, struct iovec *iov;hwaddr *addr;MemoryRegion *mr; +hwaddr len;if (desc->flags &VRING_DESC_F_WRITE ) { num = &elem->in_num;@@ -224,26 +244,30 @@ static int get_desc(Vring *vring, VirtQueueElement *elem, } } -/* Stop for now if there are not enough iovecs available. */ -if (*num >=VIRTQUEUE_MAX_SIZE ) { -error_report ("Invalid SG num: %u" , *num); -return -EFAULT ; - } +while (desc->len) { +/* Stop for now if there are not enough iovecs available. */ +if (*num >=VIRTQUEUE_MAX_SIZE ) { +error_report ("Invalid SG num: %u" , *num); +return -EFAULT ; + } -/* TODO handle non-contiguous memory across region boundaries */ - iov->iov_base =vring_map (&mr, desc->addr, desc->len, - desc->flags &VRING_DESC_F_WRITE ); -if (!iov->iov_base) { - error_report("Failed to map descriptor addr %#" PRIx64 " len %u", - (uint64_t )desc->addr, desc->len); -return -EFAULT ; + iov->iov_base =vring_map (&mr, desc->addr, desc->len, &len, + desc->flags &VRING_DESC_F_WRITE ); +if (!iov->iov_base) { + error_report("Failed to map descriptor addr %#" PRIx64 " len %u", + (uint64_t )desc->addr, desc->len); +return -EFAULT ; + } + +/* The MemoryRegion is looked up again and unref'ed later, leave the
hw/virtio/dataplane/vring.c +6
-1+* ref in place. */ + iov->iov_len = len; + *addr = desc->addr; + desc->len -= len; + desc->addr += len; + *num +=1 ; } -/* The MemoryRegion is looked up again and unref'ed later, leave the
hw/virtio/dataplane/vring.c +0
-4-* ref in place. */ - iov->iov_len = desc->len; - *addr = desc->addr; - *num +=1 ;return 0 ; }
Quoted 42 lines · On Wed, 28 Oct 2015 17:48:02 +0200 "Michael S. Tsirkin" <[email protected]> wrote:
Use
address_space_readto make sure we handle the case of an indirect descriptor crossing DIMM boundary correctly.Signed-off-by Michael S. Tsirkin <[email protected]>---
Warning: compile-tested only.
hw/virtio/dataplane/vring.c | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-)hw/virtio/dataplane/vring.c +15
-0@@ -257,6 +257,21 @@ static void copy_in_vring_desc(VirtIODevice *vdev, host->next =virtio_lduw_p (vdev, &guest->next); } +static bool read_vring_desc (VirtIODevice *vdev, +hwaddr guest, +struct vring_desc *host) +{ +if (address_space_read (&address_space_memory, guest,MEMTXATTRS_UNSPECIFIED , + (uint8_t *)host,sizeof *host)) { +return false; + } + host->addr =virtio_tswap64 (vdev, host->addr); + host->len =virtio_tswap32 (vdev, host->len); + host->flags =virtio_tswap16 (vdev, host->flags); + host->next =virtio_tswap16 (vdev, host->next); +return true; +} +/* This is stolen from linux/drivers/vhost/vhost.c. */ static int get_indirect(VirtIODevice *vdev, Vring *vring,VirtQueueElement *elem, struct
vring_desc
*indirect) @@ -284,23 +299,16 @@ static int get_indirect(VirtIODevice *vdev, Vring *vring, }do {
hw/virtio/dataplane/vring.c static int get_indirect(VirtIODevice *vdev, Vring *vring, · line 292
Quoted 6 lines
hw/virtio/dataplane/vring.c +0
-5-struct vring_desc *desc_ptr; -MemoryRegion *mr; -/* Translate indirect descriptor */ - desc_ptr = vring_map(&mr, - indirect->addr + found * sizeof(desc),
Is it correct to use 'found' as iterator here vs using desc.next as is done when translating direct descriptors?
Quoted 18 lines
hw/virtio/dataplane/vring.c +1
-3- sizeof(desc), false); -if (!desc_ptr) { - error_report("Failed to map indirect descriptor " + if (!read_vring_desc(vdev, indirect->addr + found *sizeof(desc),
hw/virtio/dataplane/vring.c +2
-0+ &desc)) { + error_report("Failed to read indirect descriptor " "addr %#" PRIx64 " len %zu", (uint64_t)indirect->addr + found *sizeof(desc), sizeof(desc)); vring->broken = true; return -EFAULT; }hw/virtio/dataplane/vring.c +0
-2-copy_in_vring_desc (vdev, desc_ptr, &desc); -memory_region_unref (mr);/* Ensure descriptor has been loaded before accessing fields */
barrier(); /*read_barrier_depends(); */
Quoted 10 lines · Igor Mammedov
hw/virtio/dataplane/vring.c static int get_indirect(VirtIODevice *vdev, Vring *vring, · line 292
Quoted 6 lines · On Wed, 28 Oct 2015 17:48:02 +0200 "Michael S. Tsirkin" <[email protected]> wrote:
hw/virtio/dataplane/vring.c +0
-5-struct vring_desc *desc_ptr; -MemoryRegion *mr; -/* Translate indirect descriptor */ - desc_ptr = vring_map(&mr, - indirect->addr + found * sizeof(desc),Is it correct to use 'found' as iterator here vs using desc.next as is done when translating direct descriptors?
That is how Linux drivers/vhost/vhost.c:get_indirect() does it.
QEMU hw/virtio/virtio.c honors desc.next.
The VIRTIO 1.0 specification says:
The first indirect descriptor is located at start of the indirect descriptor table (index 0), additional indirect descriptors are chained by next field.
I think vhost and dataplane do not follow the specification here. Fixing it is for a separate patch series.
Stefan
Quoted 4 lines · On Wed, 28 Oct 2015 17:48:02 +0200 "Michael S. Tsirkin" <[email protected]> wrote:
Use
address_space_readto make sure we handle the case of an indirect descriptor crossing DIMM boundary correctly.Signed-off-by Michael S. Tsirkin <[email protected]>
Quoted 62 lines
---
Warning: compile-tested only.
hw/virtio/dataplane/vring.c | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-)hw/virtio/dataplane/vring.c +15
-0@@ -257,6 +257,21 @@ static void copy_in_vring_desc(VirtIODevice *vdev, host->next =virtio_lduw_p (vdev, &guest->next); } +static bool read_vring_desc (VirtIODevice *vdev, +hwaddr guest, +struct vring_desc *host) +{ +if (address_space_read (&address_space_memory, guest,MEMTXATTRS_UNSPECIFIED , + (uint8_t *)host,sizeof *host)) { +return false; + } + host->addr =virtio_tswap64 (vdev, host->addr); + host->len =virtio_tswap32 (vdev, host->len); + host->flags =virtio_tswap16 (vdev, host->flags); + host->next =virtio_tswap16 (vdev, host->next); +return true; +} +/* This is stolen from linux/drivers/vhost/vhost.c. */ static int get_indirect(VirtIODevice *vdev, Vring *vring,VirtQueueElement *elem, struct
vring_desc
*indirect) @@ -284,23 +299,16 @@ static int get_indirect(VirtIODevice *vdev, Vring *vring, }do {hw/virtio/dataplane/vring.c +1
-8-struct vring_desc *desc_ptr; -MemoryRegion *mr; -/* Translate indirect descriptor */ - desc_ptr =vring_map (&mr, - indirect->addr + found *sizeof (desc), -sizeof (desc), false); -if (!desc_ptr) { - error_report("Failed to map indirect descriptor " + if (!read_vring_desc(vdev, indirect->addr + found *sizeof(desc),
hw/virtio/dataplane/vring.c +2
-0+ &desc)) { + error_report("Failed to read indirect descriptor " "addr %#" PRIx64 " len %zu", (uint64_t)indirect->addr + found *sizeof(desc), sizeof(desc)); vring->broken = true; return -EFAULT; }hw/virtio/dataplane/vring.c +0
-2-copy_in_vring_desc (vdev, desc_ptr, &desc); -memory_region_unref (mr);/* Ensure descriptor has been loaded before accessing fields */
barrier(); /*read_barrier_depends(); */
Quoted 10 lines · Michael S. Tsirkin
Use
address_space_readto make sure we handle the case of an indirect descriptor crossing DIMM boundary correctly.Signed-off-by Michael S. Tsirkin <[email protected]>---
Warning: compile-tested only.
hw/virtio/dataplane/vring.c | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-)
I will test and merge (together with your fixup patch squashed in) on Monday.
Stefan
Stefan Hajnoczi
AppliedQuoted 10 lines · Michael S. Tsirkin
Use
address_space_readto make sure we handle the case of an indirect descriptor crossing DIMM boundary correctly.Signed-off-by Michael S. Tsirkin <[email protected]>---
Warning: compile-tested only.
hw/virtio/dataplane/vring.c | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-)
Thanks, applied to my block tree:
https://github.com/stefanha/qemu/commits/block
Stefan
Quoted 7 lines · Michael S. Tsirkin
Use
address_space_readto make sure we handle the case of an indirect descriptor crossing DIMM boundary correctly.Signed-off-by Michael S. Tsirkin <[email protected]>---
Warning: compile-tested only.
Test (with your follow-up patch squashed in) with 6 4KB seqeuential read processes on running successfully.
I didn't test the DIMM boundary case. Igor: what is the easiest way to reproduce the DIMM boundary crossing?
Quoted 7 lines · On Mon, 2 Nov 2015 17:43:16 +0000 Stefan Hajnoczi <[email protected]> wrote:
Quoted 6 lines · Michael S. Tsirkin
Use
address_space_readto make sure we handle the case of an indirect descriptor crossing DIMM boundary correctly.Signed-off-by Michael S. Tsirkin <[email protected]>---
Quoted 7 lines
Quoted 1 line
Warning: compile-tested only.
Test (with your follow-up patch squashed in) with 6 4KB seqeuential read processes on running successfully.
I didn't test the DIMM boundary case. Igor: what is the easiest way to reproduce the DIMM boundary crossing?
I've tested it, here is QEMU CLI:
qemu-system-x86_64 -enable-kvm -enable-kvm -m 128M,slots=250,maxmem=32G -drive if=none,id=hd,file=rhel72,cache=none,aio=native,format=raw -device virtio-blk,drive=hd,scsi=off,config-wce=off,x-data-plane=on for i in $(seq 0 15); do echo -n "-object memory-backend-ram,id=m$i,size=10M -device pc-dimm,id=dimm$i,memdev=m$i "; done
boot and then do:
dd if=/dev/vda of=/dev/null bs=16M
without fix it hangs either at boot time or when doing 'dd'