mirror of
https://github.com/genodelabs/genode.git
synced 2025-04-07 11:27:29 +00:00
qemu-usb: use bounce buffer to access DMA memory
The former implemention assumed that the guest physical memory is mapped continously. This, however, is not true. Writing larger files to an USB stick with a Windows 10 guest would therefore lead to data corruption. The current implementation uses a bounce buffer to copy the data to and from the guest physical memory and leaves dealing with the memory mappings entirely up to the VMM. Fixes #4017.
This commit is contained in:
parent
23620942bf
commit
b51ae104c2
@ -57,6 +57,8 @@ namespace Qemu {
|
||||
*/
|
||||
struct Pci_device
|
||||
{
|
||||
enum class Dma_direction { IN = 0, OUT = 1, };
|
||||
|
||||
/**
|
||||
* Raise interrupt
|
||||
*
|
||||
@ -65,8 +67,8 @@ namespace Qemu {
|
||||
virtual void raise_interrupt(int assert) = 0;
|
||||
virtual int read_dma(addr_t addr, void *buf, size_t size) = 0;
|
||||
virtual int write_dma(addr_t addr, void const *buf, size_t size) = 0;
|
||||
virtual void *map_dma(addr_t base, size_t size) = 0;
|
||||
virtual void unmap_dma(void *addr, size_t size) = 0;
|
||||
virtual void *map_dma(addr_t base, size_t size, Dma_direction dir) = 0;
|
||||
virtual void unmap_dma(void *addr, size_t size, Dma_direction dir) = 0;
|
||||
};
|
||||
|
||||
|
||||
|
@ -849,6 +849,10 @@ void qemu_sglist_destroy(QEMUSGList *sgl) {
|
||||
|
||||
int usb_packet_map(USBPacket *p, QEMUSGList *sgl)
|
||||
{
|
||||
Qemu::Pci_device::Dma_direction dir =
|
||||
(p->pid == USB_TOKEN_IN) ? Qemu::Pci_device::Dma_direction::IN
|
||||
: Qemu::Pci_device::Dma_direction::OUT;
|
||||
|
||||
void *mem;
|
||||
|
||||
for (int i = 0; i < sgl->niov; i++) {
|
||||
@ -857,7 +861,7 @@ int usb_packet_map(USBPacket *p, QEMUSGList *sgl)
|
||||
|
||||
while (len) {
|
||||
dma_addr_t xlen = len;
|
||||
mem = _pci_device->map_dma(base, xlen);
|
||||
mem = _pci_device->map_dma(base, xlen, dir);
|
||||
if (verbose_iov)
|
||||
Genode::log("mem: ", mem, " base: ", (void *)base, " len: ",
|
||||
Genode::Hex(len));
|
||||
@ -884,9 +888,14 @@ err:
|
||||
|
||||
void usb_packet_unmap(USBPacket *p, QEMUSGList *sgl)
|
||||
{
|
||||
Qemu::Pci_device::Dma_direction dir =
|
||||
(p->pid == USB_TOKEN_IN) ? Qemu::Pci_device::Dma_direction::IN
|
||||
: Qemu::Pci_device::Dma_direction::OUT;
|
||||
|
||||
for (int i = 0; i < p->iov.niov; i++) {
|
||||
_pci_device->unmap_dma(p->iov.iov[i].iov_base,
|
||||
p->iov.iov[i].iov_len);
|
||||
p->iov.iov[i].iov_len,
|
||||
dir);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -15,6 +15,8 @@
|
||||
/* Genode includes */
|
||||
#include <base/log.h>
|
||||
#include <base/attached_rom_dataspace.h>
|
||||
#include <base/registry.h>
|
||||
#include <libc/allocator.h>
|
||||
#include <util/list.h>
|
||||
|
||||
/* qemu-usb includes */
|
||||
@ -285,8 +287,33 @@ struct Timer_queue : public Qemu::Timer_queue
|
||||
|
||||
struct Pci_device : public Qemu::Pci_device
|
||||
{
|
||||
Libc::Allocator _alloc { };
|
||||
|
||||
PPDMDEVINS pci_dev;
|
||||
|
||||
struct Dma_bounce_buffer
|
||||
{
|
||||
Genode::Allocator &_alloc;
|
||||
|
||||
Qemu::addr_t const base;
|
||||
Qemu::size_t const size;
|
||||
void * const addr { _alloc.alloc(size) };
|
||||
|
||||
Dma_bounce_buffer(Genode::Allocator &alloc,
|
||||
Qemu::addr_t base,
|
||||
Qemu::size_t size)
|
||||
: _alloc { alloc }, base { base }, size { size }
|
||||
{ }
|
||||
|
||||
virtual ~Dma_bounce_buffer()
|
||||
{
|
||||
_alloc.free(addr, size);
|
||||
}
|
||||
};
|
||||
|
||||
using Reg_dma_buffer = Genode::Registered<Dma_bounce_buffer>;
|
||||
Genode::Registry<Reg_dma_buffer> _dma_buffers { };
|
||||
|
||||
Pci_device(PPDMDEVINS pDevIns) : pci_dev(pDevIns) { }
|
||||
|
||||
void raise_interrupt(int level) override {
|
||||
@ -298,20 +325,43 @@ struct Pci_device : public Qemu::Pci_device
|
||||
int write_dma(Qemu::addr_t addr, void const *buf, Qemu::size_t size) override {
|
||||
return PDMDevHlpPhysWrite(pci_dev, addr, buf, size); }
|
||||
|
||||
void *map_dma(Qemu::addr_t base, Qemu::size_t size) override
|
||||
void *map_dma(Qemu::addr_t base, Qemu::size_t size,
|
||||
Qemu::Pci_device::Dma_direction dir) override
|
||||
{
|
||||
PGMPAGEMAPLOCK lock;
|
||||
void * vmm_addr = nullptr;
|
||||
Reg_dma_buffer *dma = nullptr;
|
||||
|
||||
int rc = PDMDevHlpPhysGCPhys2CCPtr(pci_dev, base, 0, &vmm_addr, &lock);
|
||||
Assert(rc == VINF_SUCCESS);
|
||||
try {
|
||||
dma = new (_alloc) Reg_dma_buffer(_dma_buffers,
|
||||
_alloc, base, size);
|
||||
} catch (...) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/* the mapping doesn't go away, so release internal lock immediately */
|
||||
PDMDevHlpPhysReleasePageMappingLock(pci_dev, &lock);
|
||||
return vmm_addr;
|
||||
/* copy data for write request to bounce buffer */
|
||||
if (dir == Qemu::Pci_device::Dma_direction::OUT) {
|
||||
(void)PDMDevHlpPhysRead(pci_dev, base, dma->addr, size);
|
||||
}
|
||||
|
||||
return dma->addr;
|
||||
}
|
||||
|
||||
void unmap_dma(void *addr, Qemu::size_t size) override { }
|
||||
void unmap_dma(void *addr, Qemu::size_t size,
|
||||
Qemu::Pci_device::Dma_direction dir) override
|
||||
{
|
||||
_dma_buffers.for_each([&] (Reg_dma_buffer &dma) {
|
||||
if (dma.addr != addr) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* copy data for read request from bounce buffer */
|
||||
if (dir == Qemu::Pci_device::Dma_direction::IN) {
|
||||
(void)PDMDevHlpPhysWrite(pci_dev,
|
||||
dma.base, dma.addr, dma.size);
|
||||
}
|
||||
|
||||
Genode::destroy(_alloc, &dma);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user