qemu-devel
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[Qemu-devel] [RFC 2/2] hw/pci-host/versatile: Implement IMAP registers


From: Peter Maydell
Subject: [Qemu-devel] [RFC 2/2] hw/pci-host/versatile: Implement IMAP registers
Date: Thu, 22 Aug 2013 19:28:12 +0100

Rather than assuming that PCI bus master devices can DMA directly into
system memory, correctly model the way the hardware does it:
 * the host controller has three BARs (one I/O and two memory) which
   can be mapped into PCI memory space in the usual way
 * each of these BARs is an alias of an area of system memory whose
   base address is defined by the host controller's SMAP registers
 * PCI bus master devices see the PCI memory space, in the same
   way as everybody else

Linux programs the BARs and SMAP registers in such a way that
(for system RAM) you get the identity mapping.

Note that since the QEMU PCI core doesn't forbid PCI devices
from attempting to do bus master accesses to their own MMIO
BARs it is possible for a malicious guest to configure the
controller into a hall-of-mirrors mapping of the PCI and
system memory spaces into each other such that an access would
loop infinitely. This will probably result in infinite
recursion in QEMU in render_memory_region()...

Signed-off-by: Peter Maydell <address@hidden>
---
 hw/pci-host/versatile.c |   72 +++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 72 insertions(+)

diff --git a/hw/pci-host/versatile.c b/hw/pci-host/versatile.c
index 9238d39..37c7425 100644
--- a/hw/pci-host/versatile.c
+++ b/hw/pci-host/versatile.c
@@ -75,11 +75,19 @@ typedef struct {
     /* Containers representing the PCI address spaces */
     MemoryRegion pci_io_space;
     MemoryRegion pci_mem_space;
+    /* AddressSpace corresponding to pci_mem_space */
+    AddressSpace pci_mem_as;
     /* Alias regions into PCI address spaces which we expose as sysbus regions.
      * The offsets into pci_mem_space are controlled by the imap registers.
      */
     MemoryRegion pci_io_window;
     MemoryRegion pci_mem_window[3];
+    /* Alias regions into system address space which we map into PCI
+     * memory space under the control of the SMAP registers.
+     * Note that which one of the BARs is the I/O bar differs for
+     * realview and versatile.
+     */
+    MemoryRegion pci_bar[3];
     PCIBus pci_bus;
     PCIDevice pci_dev;
 
@@ -127,10 +135,38 @@ static void pci_vpb_update_all_windows(PCIVPBState *s)
     }
 }
 
+static void pci_vpb_update_smap(PCIVPBState *s, int i)
+{
+    /* After a change to the SMAP registers, we need to update
+     * what part of the system address space we're aliasing
+     * into the PCI memory or IO space. (This is the inverse of
+     * the windows controlled by the IMAP registers, which
+     * map parts of PCI space into system space.)
+     */
+    hwaddr offset;
+
+    if (s->realview) {
+        offset = s->smap[i] & 0xf0000000;
+    } else {
+        offset = s->smap[i] << 28;
+    }
+    memory_region_set_alias_offset(&s->pci_bar[i], offset);
+}
+
+static void pci_vpb_update_all_smaps(PCIVPBState *s)
+{
+    int i;
+
+    for (i = 0; i < 3; i++) {
+        pci_vpb_update_smap(s, i);
+    }
+}
+
 static int pci_vpb_post_load(void *opaque, int version_id)
 {
     PCIVPBState *s = opaque;
     pci_vpb_update_all_windows(s);
+    pci_vpb_update_all_smaps(s);
     return 0;
 }
 
@@ -195,6 +231,7 @@ static void pci_vpb_reg_write(void *opaque, hwaddr addr,
     {
         int win = (addr - PCI_SMAP0) >> 2;
         s->smap[win] = val;
+        pci_vpb_update_smap(s, win);
         break;
     }
     default:
@@ -376,6 +413,13 @@ static void pci_vpb_reset(DeviceState *d)
     pci_vpb_update_all_windows(s);
 }
 
+static AddressSpace *pci_vpb_dma_iommu(PCIBus *bus, void *opaque, int devfn)
+{
+    PCIVPBState *s = opaque;
+
+    return &s->pci_mem_as;
+}
+
 static void pci_vpb_init(Object *obj)
 {
     PCIHostState *h = PCI_HOST_BRIDGE(obj);
@@ -383,12 +427,15 @@ static void pci_vpb_init(Object *obj)
 
     memory_region_init(&s->pci_io_space, OBJECT(s), "pci_io", 1ULL << 32);
     memory_region_init(&s->pci_mem_space, OBJECT(s), "pci_mem", 1ULL << 32);
+    address_space_init(&s->pci_mem_as, &s->pci_mem_space, "pci_mem");
 
     pci_bus_new_inplace(&s->pci_bus, DEVICE(obj), "pci",
                         &s->pci_mem_space, &s->pci_io_space,
                         PCI_DEVFN(11, 0), TYPE_PCI_BUS);
     h->bus = &s->pci_bus;
 
+    pci_setup_iommu(&s->pci_bus, pci_vpb_dma_iommu, s);
+
     object_initialize(&s->pci_dev, TYPE_VERSATILE_PCI_HOST);
     qdev_set_parent_bus(DEVICE(&s->pci_dev), BUS(&s->pci_bus));
 
@@ -458,9 +505,34 @@ static void pci_vpb_realize(DeviceState *dev, Error **errp)
 
 static int versatile_pci_host_init(PCIDevice *d)
 {
+    int i;
+    PCIVPBState *s = container_of(d->bus, PCIVPBState, pci_bus);
+    int iobar = s->realview ? 2 : 0;
+
     pci_set_word(d->config + PCI_STATUS,
                  PCI_STATUS_66MHZ | PCI_STATUS_DEVSEL_MEDIUM);
     pci_set_byte(d->config + PCI_LATENCY_TIMER, 0x10);
+
+    /* The host allows its MMIO BARs to be mapped at PCI address zero
+     * (even though the PCI spec says 0 isn't a valid address), and
+     * Linux relies on this for PCI bus-mastering DMA to work. So we
+     * have to emulate this out-of-spec behaviour.
+     */
+    d->cap_present |= QEMU_PCI_ADDR0_ALLOWED;
+
+    /* Create alias regions corresponding to our own BARs */
+    for (i = 0; i < 3; i++) {
+        uint8_t bar_type = PCI_BASE_ADDRESS_MEM_PREFETCH;
+
+        memory_region_init_alias(&s->pci_bar[i], OBJECT(s),
+                                 "pci-vpb-bar0", get_system_memory(), 0,
+                                 0x10000000);
+        if (i == iobar) {
+            bar_type = PCI_BASE_ADDRESS_SPACE_IO;
+        }
+        pci_register_bar(&s->pci_dev, i, bar_type, &s->pci_bar[i]);
+    }
+
     return 0;
 }
 
-- 
1.7.9.5




reply via email to

[Prev in Thread] Current Thread [Next in Thread]