diff options
| author | David Gibson <david@gibson.dropbear.id.au> | 2012-09-26 12:59:31 +1000 |
|---|---|---|
| committer | Gerd Hoffmann <kraxel@redhat.com> | 2012-09-26 09:24:42 +0200 |
| commit | 39c138c8420f51a7da7b35233a8d7400a0b589ac (patch) | |
| tree | aaa8f11a1095f084741ce55b2dc3539e85864218 /hw | |
| parent | 8b626aa7841ef79b70066c880b3b6c29496797af (diff) | |
| download | focaccia-qemu-39c138c8420f51a7da7b35233a8d7400a0b589ac.tar.gz focaccia-qemu-39c138c8420f51a7da7b35233a8d7400a0b589ac.zip | |
usb: Fix usb_packet_map() in the presence of IOMMUs
With the IOMMU infrastructure introduced before 1.2, we need to use dma_memory_map() to obtain a qemu pointer to memory from an IO bus address. However, dma_memory_map() alters the given length to reflect the length over which the used DMA translation is valid - which could be either more or less than the requested length. usb_packet_map() does not correctly handle these cases, simply failing if dma_memory_map() alters the requested length. If dma_memory_map() increased the length, we just need to use the requested length for the qemu_iovec_add(). However, if it decreased the length, it means that a single DMA translation is not valid for the whole sglist element, and so we need to loop, splitting it up into multiple iovec entries for each piece with a DMA translation (in practice >2 pieces is unlikely). This patch implements the correct behaviour Signed-off-by: David Gibson <david@gibson.dropbear.id.au> Signed-off-by: Gerd Hoffmann <kraxel@redhat.com>
Diffstat (limited to 'hw')
| -rw-r--r-- | hw/usb/libhw.c | 24 |
1 files changed, 15 insertions, 9 deletions
diff --git a/hw/usb/libhw.c b/hw/usb/libhw.c index c0de30ea88..703e2d213b 100644 --- a/hw/usb/libhw.c +++ b/hw/usb/libhw.c @@ -28,19 +28,25 @@ int usb_packet_map(USBPacket *p, QEMUSGList *sgl) { DMADirection dir = (p->pid == USB_TOKEN_IN) ? DMA_DIRECTION_FROM_DEVICE : DMA_DIRECTION_TO_DEVICE; - dma_addr_t len; void *mem; int i; for (i = 0; i < sgl->nsg; i++) { - len = sgl->sg[i].len; - mem = dma_memory_map(sgl->dma, sgl->sg[i].base, &len, dir); - if (!mem) { - goto err; - } - qemu_iovec_add(&p->iov, mem, len); - if (len != sgl->sg[i].len) { - goto err; + dma_addr_t base = sgl->sg[i].base; + dma_addr_t len = sgl->sg[i].len; + + while (len) { + dma_addr_t xlen = len; + mem = dma_memory_map(sgl->dma, sgl->sg[i].base, &xlen, dir); + if (!mem) { + goto err; + } + if (xlen > len) { + xlen = len; + } + qemu_iovec_add(&p->iov, mem, xlen); + len -= xlen; + base += xlen; } } return 0; |