]> rtime.felk.cvut.cz Git - jailhouse.git/blobdiff - hypervisor/pci_ivshmem.c
jailhouse: inmates: bench: Add -R option -- repeats count.
[jailhouse.git] / hypervisor / pci_ivshmem.c
index 0f0f4dab7f2153656c26cd867260977f7776c2bd..5c1e6a73a341e672bd3953bcd5295649872f707a 100644 (file)
@@ -1,7 +1,7 @@
 /*
  * Jailhouse, a Linux-based partitioning hypervisor
  *
- * Copyright (c) Siemens AG, 2014
+ * Copyright (c) Siemens AG, 2014, 2015
  *
  * Author:
  *  Henning Schild <henning.schild@siemens.com>
@@ -20,6 +20,7 @@
  */
 
 #include <jailhouse/control.h>
+#include <jailhouse/mmio.h>
 #include <jailhouse/pci.h>
 #include <jailhouse/printk.h>
 #include <jailhouse/string.h>
 
 #define IVSHMEM_CFG_SIZE       (IVSHMEM_CFG_MSIX_CAP + 12)
 
-struct virt_pci_bar {
-       char flags;
-       u64 sz;
-};
+#define IVSHMEM_BAR0_SIZE      256
+#define IVSHMEM_BAR4_SIZE      ((0x18 * IVSHMEM_MSIX_VECTORS + 0xf) & ~0xf)
 
 struct pci_ivshmem_endpoint {
        u32 cspace[IVSHMEM_CFG_SIZE / sizeof(u32)];
        u32 ivpos;
-       struct virt_pci_bar bars[3];
+       u64 bar0_address;
+       u64 bar4_address;
        struct pci_device *device;
        struct pci_ivshmem_endpoint *remote;
        struct apic_irq_message irq_msg;
@@ -79,45 +79,43 @@ static const u32 default_cspace[IVSHMEM_CFG_SIZE / sizeof(u32)] = {
                                           (PCI_CFG_BAR/8 + 2),
 };
 
-static const struct virt_pci_bar default_bars[3] = {
-       {
-               .flags = PCI_BAR_64BIT,
-               .sz = 256,
-       },
-       {
-               /* in jailhouse we leave this BAR empty, the shared memory
-                * location and size are in our custom registers
-                * IVSHMEM_CFG_SHMEM */
-       },
-       {       /* used for MSI-X vectors */
-               .flags = PCI_BAR_64BIT,
-               .sz = ((0x18 * IVSHMEM_MSIX_VECTORS) + 15) & ~0xf,
-       }
-};
-
-static u32 ivshmem_cfg_read32(struct pci_ivshmem_endpoint *ive, u8 reg)
-{
-       return ive->cspace[reg / 4];
-}
-
-static u64 ivshmem_cfg_read64(struct pci_ivshmem_endpoint *ive, u8 reg)
+static void ivshmem_write_doorbell(struct pci_ivshmem_endpoint *ive)
 {
-       return ((u64)ivshmem_cfg_read32(ive, reg + 4) << 32) |
-              ivshmem_cfg_read32(ive, reg);
-}
+       struct pci_ivshmem_endpoint *remote = ive->remote;
+       struct apic_irq_message irq_msg;
 
-static u16 ivshmem_cfg_read16(struct pci_ivshmem_endpoint *ive, u8 reg)
-{
-       unsigned int bias = reg % 4;
+       if (!remote)
+               return;
 
-       return (u16)(ivshmem_cfg_read32(ive, reg - bias) >> (bias * 8));
+       /* get a copy of the struct before using it, the read barrier makes
+        * sure the copy is consistent */
+       irq_msg = remote->irq_msg;
+       memory_load_barrier();
+       if (irq_msg.valid)
+               apic_send_irq(irq_msg);
 }
 
-static u8 ivshmem_cfg_read8(struct pci_ivshmem_endpoint *ive, u8 reg)
+static enum mmio_result ivshmem_register_mmio(void *arg,
+                                             struct mmio_access *mmio)
 {
-       unsigned int bias = reg % 4;
+       struct pci_ivshmem_endpoint *ive = arg;
 
-       return (u8)(ivshmem_cfg_read32(ive, reg - bias) >> (bias * 8));
+       /* read-only IVPosition */
+       if (mmio->address == IVSHMEM_REG_IVPOS && !mmio->is_write) {
+               mmio->value = ive->ivpos;
+               return MMIO_HANDLED;
+       }
+
+       if (mmio->address == IVSHMEM_REG_DBELL) {
+               if (mmio->is_write)
+                       ivshmem_write_doorbell(ive);
+               else
+                       mmio->value = 0;
+               return MMIO_HANDLED;
+       }
+       panic_printk("FATAL: Invalid ivshmem register %s, number %02x\n",
+                    mmio->is_write ? "write" : "read", mmio->address);
+       return MMIO_ERROR;
 }
 
 static bool ivshmem_is_msix_masked(struct pci_ivshmem_endpoint *ive)
@@ -161,10 +159,10 @@ static int ivshmem_update_msix(struct pci_ivshmem_endpoint *ive)
                return 0;
 
        if (!apic_filter_irq_dest(ive->device->cell, &irq_msg)) {
-               printk("WARNING: ivshmem MSI-X target outside of "
-                      "cell \"%s\" device %02x:%02x.%x\n",
-                      ive->device->cell->config->name,
-                      PCI_BDF_PARAMS(ive->device->info->bdf));
+               panic_printk("FATAL: ivshmem MSI-X target outside of "
+                            "cell \"%s\" device %02x:%02x.%x\n",
+                            ive->device->cell->config->name,
+                            PCI_BDF_PARAMS(ive->device->info->bdf));
                return -EPERM;
        }
        /* now copy the whole struct into our cache and mark the cache
@@ -177,104 +175,77 @@ static int ivshmem_update_msix(struct pci_ivshmem_endpoint *ive)
        return 0;
 }
 
-/**
- * update the command register
- * note that we only accept writes to two flags
- */
-static int ivshmem_write_command(struct pci_ivshmem_endpoint *ive, u16 val)
-{
-       u16 *cmd = (u16 *)&ive->cspace[PCI_CFG_COMMAND/4];
-       int err;
-
-       if ((val & PCI_CMD_MASTER) != (*cmd & PCI_CMD_MASTER)) {
-               *cmd = (*cmd & ~PCI_CMD_MASTER) | (val & PCI_CMD_MASTER);
-               err = ivshmem_update_msix(ive);
-               if (err)
-                       return err;
-       }
-
-       *cmd = (*cmd & ~PCI_CMD_MEM) | (val & PCI_CMD_MEM);
-       return 0;
-}
-
-static void ivshmem_write_bar(struct pci_ivshmem_endpoint *ive, u8 reg, u32 val)
-{
-       int barn = (reg - PCI_CFG_BAR) / 8;
-       struct virt_pci_bar *bar = &(ive->bars[barn]);
-       u32 newval;
-
-       if (reg & 4)
-               newval = val & ((~(bar->sz - 1)) >> 32);
-       else
-               newval = (val & (~(bar->sz - 1) & ~0xf)) | (bar->flags & 0xf);
-
-       ive->cspace[reg / 4] = newval;
-}
-
-static int ivshmem_msix_mmio(struct pci_ivshmem_endpoint *ive, bool is_write,
-                            u32 offset, u32 *value)
+static enum mmio_result ivshmem_msix_mmio(void *arg, struct mmio_access *mmio)
 {
+       struct pci_ivshmem_endpoint *ive = arg;
        u32 *msix_table = (u32 *)ive->device->msix_vectors;
 
-       if (offset % 4)
-               return -1;
+       if (mmio->address % 4)
+               goto fail;
 
        /* MSI-X PBA */
-       if (offset >= 0x10 * IVSHMEM_MSIX_VECTORS) {
-               if (is_write) {
-                       return -1;
+       if (mmio->address >= 0x10 * IVSHMEM_MSIX_VECTORS) {
+               if (mmio->is_write) {
+                       goto fail;
                } else {
-                       *value = 0;
-                       return 1;
+                       mmio->value = 0;
+                       return MMIO_HANDLED;
                }
        /* MSI-X Table */
        } else {
-               if (is_write) {
-                       msix_table[offset/4] = *value;
+               if (mmio->is_write) {
+                       msix_table[mmio->address / 4] = mmio->value;
                        if (ivshmem_update_msix(ive))
-                               return -1;
+                               return MMIO_ERROR;
                } else {
-                       *value = msix_table[offset/4];
+                       mmio->value = msix_table[mmio->address / 4];
                }
-               return 1;
+               return MMIO_HANDLED;
        }
-       return -1;
-}
-
-static void ivshmem_write_doorbell(struct pci_ivshmem_endpoint *ive)
-{
-       struct pci_ivshmem_endpoint *remote = ive->remote;
-       struct apic_irq_message irq_msg;
 
-       if (!remote)
-               return;
-
-       /* get a copy of the struct before using it, the read barrier makes
-        * sure the copy is consistent */
-       irq_msg = remote->irq_msg;
-       memory_load_barrier();
-       if (irq_msg.valid)
-               apic_send_irq(irq_msg);
+fail:
+       panic_printk("FATAL: Invalid PCI MSI-X table/PBA access, device "
+                    "%02x:%02x.%x\n", PCI_BDF_PARAMS(ive->device->info->bdf));
+       return MMIO_ERROR;
 }
 
-static int ivshmem_register_mmio(struct pci_ivshmem_endpoint *ive,
-                                bool is_write, u32 offset, u32 *value)
+/**
+ * update the command register
+ * note that we only accept writes to two flags
+ */
+static int ivshmem_write_command(struct pci_ivshmem_endpoint *ive, u16 val)
 {
-       /* IVPosition, ro and always returns 0 */
-       if (offset == IVSHMEM_REG_IVPOS && !is_write) {
-               *value = ive->ivpos;
-               return 1;
+       u16 *cmd = (u16 *)&ive->cspace[PCI_CFG_COMMAND/4];
+       struct pci_device *device = ive->device;
+       int err;
+
+       if ((val & PCI_CMD_MASTER) != (*cmd & PCI_CMD_MASTER)) {
+               *cmd = (*cmd & ~PCI_CMD_MASTER) | (val & PCI_CMD_MASTER);
+               err = ivshmem_update_msix(ive);
+               if (err)
+                       return err;
        }
 
-       if (offset == IVSHMEM_REG_DBELL) {
-               if (is_write) {
-                       ivshmem_write_doorbell(ive);
-               } else {
-                       *value = 0;
+       if ((val & PCI_CMD_MEM) != (*cmd & PCI_CMD_MEM)) {
+               if (*cmd & PCI_CMD_MEM) {
+                       mmio_region_unregister(device->cell, ive->bar0_address);
+                       mmio_region_unregister(device->cell, ive->bar4_address);
+               }
+               if (val & PCI_CMD_MEM) {
+                       ive->bar0_address = (*(u64 *)&device->bar[0]) & ~0xfL;
+                       mmio_region_register(device->cell, ive->bar0_address,
+                                            IVSHMEM_BAR0_SIZE,
+                                            ivshmem_register_mmio, ive);
+
+                       ive->bar4_address = (*(u64 *)&device->bar[4]) & ~0xfL;
+                       mmio_region_register(device->cell, ive->bar4_address,
+                                            IVSHMEM_BAR4_SIZE,
+                                            ivshmem_msix_mmio, ive);
                }
-               return 1;
+               *cmd = (*cmd & ~PCI_CMD_MEM) | (val & PCI_CMD_MEM);
        }
-       return -1;
+
+       return 0;
 }
 
 static int ivshmem_write_msix_control(struct pci_ivshmem_endpoint *ive, u32 val)
@@ -293,51 +264,6 @@ static int ivshmem_write_msix_control(struct pci_ivshmem_endpoint *ive, u32 val)
        return 0;
 }
 
-static enum pci_access ivshmem_cfg_write32(struct pci_ivshmem_endpoint *ive,
-                                          u8 reg, u32 val)
-{
-       switch (reg) {
-       case PCI_CFG_COMMAND:
-               if(ivshmem_write_command(ive, val & 0xffff))
-                       return PCI_ACCESS_REJECT;
-               break;
-       case PCI_CFG_BAR ... (PCI_CFG_BAR + 5 * 4):
-               ivshmem_write_bar(ive, reg, val);
-               break;
-       case IVSHMEM_CFG_MSIX_CAP:
-               if (ivshmem_write_msix_control(ive, val))
-                       return PCI_ACCESS_REJECT;
-       }
-       return PCI_ACCESS_DONE;
-}
-
-static enum pci_access ivshmem_cfg_write16(struct pci_ivshmem_endpoint *ive,
-                                          u8 reg, u16 val)
-{
-       u32 row, shift;
-
-       shift = (reg % 4) * 8;
-       row = ive->cspace[reg / 4];
-       row &= ~(BYTE_MASK(2) << shift);
-       row |= val << shift;
-
-       return ivshmem_cfg_write32(ive, reg - (reg % 4), row);
-}
-
-static enum pci_access ivshmem_cfg_write8(struct pci_ivshmem_endpoint *ive,
-                                         u8 reg, u8 val)
-{
-       u32 row;
-       u8 *rowp;
-
-       row = ive->cspace[reg / 4];
-       rowp = (u8 *)&row;
-       rowp[(reg % 4)] = val;
-
-       return ivshmem_cfg_write32(ive, reg - (reg % 4), row);
-}
-
-
 static struct pci_ivshmem_data **ivshmem_find(struct pci_device *d,
                                              int *cellnum)
 {
@@ -374,8 +300,10 @@ static void ivshmem_connect_cell(struct pci_ivshmem_data *iv,
        struct pci_ivshmem_endpoint *remote = &iv->eps[(cellnum + 1) % 2];
        struct pci_ivshmem_endpoint *ive = &iv->eps[cellnum];
 
+       d->bar[0] = PCI_BAR_64BIT;
+       d->bar[4] = PCI_BAR_64BIT;
+
        memcpy(ive->cspace, &default_cspace, sizeof(default_cspace));
-       memcpy(ive->bars, &default_bars, sizeof(default_bars));
 
        ive->cspace[IVSHMEM_CFG_SHMEM_PTR/4] = (u32)mem->virt_start;
        ive->cspace[IVSHMEM_CFG_SHMEM_PTR/4 + 1] = (u32)(mem->virt_start >> 32);
@@ -399,168 +327,106 @@ static void ivshmem_disconnect_cell(struct pci_ivshmem_data *iv, int cellnum)
 {
        struct pci_ivshmem_endpoint *remote = &iv->eps[(cellnum + 1) % 2];
        struct pci_ivshmem_endpoint *ive = &iv->eps[cellnum];
+       u16 cmd = *(u16 *)&ive->cspace[PCI_CFG_COMMAND / 4];
 
+       if (cmd & PCI_CMD_MEM) {
+               mmio_region_unregister(this_cell(), ive->bar0_address);
+               mmio_region_unregister(this_cell(), ive->bar4_address);
+       }
        ive->device->ivshmem_endpoint = NULL;
        ive->device = NULL;
        ive->remote = NULL;
        remote->remote = NULL;
 }
 
-/**
- * Handler for MMIO-accesses to this virtual PCI devices memory. Both for the
- * BAR containing the registers, and the MSI-X BAR.
- * @param cell         The cell that issued the access.
- * @param is_write     True if write access.
- * @param addr         Address accessed.
- * @param value                Pointer to value for reading/writing.
- *
- * @return 1 if handled successfully, 0 if unhandled, -1 on access error.
- *
- * @see pci_mmio_access_handler
- */
-int ivshmem_mmio_access_handler(const struct cell *cell, bool is_write,
-                               u64 addr, u32 *value)
-{
-       struct pci_ivshmem_endpoint *ive;
-       struct pci_device *device;
-       u64 mem_start, mem_sz;
-
-       for (device = cell->virtual_device_list; device;
-            device = device->next_virtual_device) {
-               ive = device->ivshmem_endpoint;
-               if (!ive)
-                       continue;
-               if ((ive->cspace[PCI_CFG_COMMAND/4] & PCI_CMD_MEM) == 0)
-                       continue;
-
-               /* register BAR access */
-               mem_start = ivshmem_cfg_read64(ive, PCI_CFG_BAR) & ~0xf;
-               mem_sz = ive->bars[0].sz;
-               if (addr >= mem_start && addr <= (mem_start + mem_sz - 4))
-                       return ivshmem_register_mmio(ive, is_write,
-                                                    addr - mem_start,
-                                                    value);
-
-               /* MSI-X BAR access */
-               mem_start = ivshmem_cfg_read64(ive, PCI_CFG_BAR + 2 * 8) & ~0xf;
-               mem_sz = ive->bars[2].sz;
-               if (addr >= mem_start && addr <= (mem_start + mem_sz - 4))
-                       return ivshmem_msix_mmio(ive, is_write,
-                                                addr - mem_start, value);
-       }
-
-       return 0;
-}
-
 /**
  * Handler for MMIO-write-accesses to PCI config space of this virtual device.
- * @param dev          The device that access should be performed on.
- * @param address      Config space address accessed.
- * @param sz           The amount of bytes to write.
- * @param value                The value to write to the config space.
+ * @param device       The device that access should be performed on.
+ * @param row          Config space DWORD row of the access.
+ * @param mask         Mask selected the DWORD bytes to write.
+ * @param value                DWORD to write to the config space.
  *
  * @return PCI_ACCESS_REJECT or PCI_ACCESS_DONE.
  *
  * @see pci_cfg_write_moderate
  */
-enum pci_access pci_ivshmem_cfg_write(struct pci_device *dev, u16 address,
-                                     u8 sz, u32 value)
+enum pci_access pci_ivshmem_cfg_write(struct pci_device *device,
+                                     unsigned int row, u32 mask, u32 value)
 {
-       struct pci_ivshmem_endpoint *ive = dev->ivshmem_endpoint;
+       struct pci_ivshmem_endpoint *ive = device->ivshmem_endpoint;
 
-       if (address > (sizeof(default_cspace) - sz))
+       if (row >= ARRAY_SIZE(default_cspace))
                return PCI_ACCESS_REJECT;
 
-       if (!ive)
-               return PCI_ACCESS_REJECT;
+       value |= ive->cspace[row] & ~mask;
 
-       switch (sz) {
-       case 1:
-               return ivshmem_cfg_write8(ive, address, (u8)value);
-       case 2:
-               return ivshmem_cfg_write16(ive, address, (u16)value);
-       case 4:
-               return ivshmem_cfg_write32(ive, address, value);
-       default:
-               return PCI_ACCESS_REJECT;
+       switch (row) {
+       case PCI_CFG_COMMAND / 4:
+               if (ivshmem_write_command(ive, value))
+                       return PCI_ACCESS_REJECT;
+               break;
+       case IVSHMEM_CFG_MSIX_CAP / 4:
+               if (ivshmem_write_msix_control(ive, value))
+                       return PCI_ACCESS_REJECT;
        }
+       return PCI_ACCESS_DONE;
 }
 
 /**
  * Handler for MMIO-read-accesses to PCI config space of this virtual device.
- * @param dev          The device that access should be performed on.
+ * @param device       The device that access should be performed on.
  * @param address      Config space address accessed.
- * @param sz           The amount of bytes to read.
  * @param value                Pointer to the return value.
  *
  * @return PCI_ACCESS_DONE.
  *
  * @see pci_cfg_read_moderate
  */
-enum pci_access pci_ivshmem_cfg_read(struct pci_device *dev, u16 address,
-                                    u8 sz, u32 *value)
+enum pci_access pci_ivshmem_cfg_read(struct pci_device *device, u16 address,
+                                    u32 *value)
 {
-       struct pci_ivshmem_endpoint *ive = dev->ivshmem_endpoint;
+       struct pci_ivshmem_endpoint *ive = device->ivshmem_endpoint;
 
-       if (address > (sizeof(default_cspace) - sz))
-               goto fail;
-       if (!ive)
-               goto fail;
-
-       switch (sz) {
-       case 1:
-               *value = (u32)ivshmem_cfg_read8(ive, address);
-               break;
-       case 2:
-               *value = (u32)ivshmem_cfg_read16(ive, address);
-               break;
-       case 4:
-               *value = ivshmem_cfg_read32(ive, address);
-               break;
-       default:
-               goto fail;
-       }
-       return PCI_ACCESS_DONE;
-
-fail:
-       *value = -1;
-       /* the caller can not deal with PCI_ACCESS_REJECT for reads */
+       if (address < sizeof(default_cspace))
+               *value = ive->cspace[address / 4] >> ((address % 4) * 8);
+       else
+               *value = -1;
        return PCI_ACCESS_DONE;
 }
 
 /**
  * Update cached MSI-X state of the given ivshmem device.
- * @param dev  The device to be updated.
+ * @param device       The device to be updated.
  *
  * @return 0 on success, negative error code otherwise.
  */
-int pci_ivshmem_update_msix(struct pci_device *dev)
+int pci_ivshmem_update_msix(struct pci_device *device)
 {
-       return ivshmem_update_msix(dev->ivshmem_endpoint);
+       return ivshmem_update_msix(device->ivshmem_endpoint);
 }
 
 /**
  * Register a new ivshmem device.
  * @param cell         The cell the device should be attached to.
- * @param dev          The device to be registered.
+ * @param device       The device to be registered.
  *
  * @return 0 on success, negative error code otherwise.
  */
-int pci_ivshmem_init(struct cell *cell, struct pci_device *dev)
+int pci_ivshmem_init(struct cell *cell, struct pci_device *device)
 {
        const struct jailhouse_memory *mem, *mem0;
        struct pci_ivshmem_data **ivp;
        struct pci_device *dev0;
 
-       if (dev->info->num_msix_vectors != 1)
+       if (device->info->num_msix_vectors != 1)
                return trace_error(-EINVAL);
 
-       if (dev->info->shmem_region >= cell->config->num_memory_regions)
+       if (device->info->shmem_region >= cell->config->num_memory_regions)
                return trace_error(-EINVAL);
 
        mem = jailhouse_cell_mem_regions(cell->config)
-               + dev->info->shmem_region;
-       ivp = ivshmem_find(dev, NULL);
+               + device->info->shmem_region;
+       ivp = ivshmem_find(device, NULL);
        if (ivp) {
                dev0 = (*ivp)->eps[0].device;
                mem0 = jailhouse_cell_mem_regions(dev0->cell->config) +
@@ -571,7 +437,7 @@ int pci_ivshmem_init(struct cell *cell, struct pci_device *dev)
                    (mem0->size == mem->size)) {
                        if ((*ivp)->eps[1].device)
                                return trace_error(-EBUSY);
-                       ivshmem_connect_cell(*ivp, dev, mem, 1);
+                       ivshmem_connect_cell(*ivp, device, mem, 1);
                        printk("Virtual PCI connection established "
                                "\"%s\" <--> \"%s\"\n",
                                cell->config->name, dev0->cell->config->name);
@@ -585,33 +451,34 @@ int pci_ivshmem_init(struct cell *cell, struct pci_device *dev)
        *ivp = page_alloc(&mem_pool, 1);
        if (!(*ivp))
                return -ENOMEM;
-       ivshmem_connect_cell(*ivp, dev, mem, 0);
+       ivshmem_connect_cell(*ivp, device, mem, 0);
 
 connected:
-       dev->cell = cell;
        printk("Adding virtual PCI device %02x:%02x.%x to cell \"%s\"\n",
-              PCI_BDF_PARAMS(dev->info->bdf), cell->config->name);
+              PCI_BDF_PARAMS(device->info->bdf), cell->config->name);
 
        return 0;
 }
 
 /**
  * Unregister a ivshmem device, typically when the corresponding cell exits.
- * @param dev          The device to be stopped.
+ * @param device       The device to be stopped.
  *
  */
-void pci_ivshmem_exit(struct pci_device *dev)
+void pci_ivshmem_exit(struct pci_device *device)
 {
        struct pci_ivshmem_data **ivp, *iv;
        int cellnum;
 
-       ivp = ivshmem_find(dev, &cellnum);
+       ivp = ivshmem_find(device, &cellnum);
        if (!ivp || !(*ivp))
                return;
 
        iv = *ivp;
 
-       if (iv->eps[0].device == dev) {
+       ivshmem_disconnect_cell(iv, cellnum);
+
+       if (cellnum == 0) {
                if (!iv->eps[1].device) {
                        *ivp = iv->next;
                        page_free(&mem_pool, iv, 1);
@@ -619,5 +486,4 @@ void pci_ivshmem_exit(struct pci_device *dev)
                }
                iv->eps[0] = iv->eps[1];
        }
-       ivshmem_disconnect_cell(iv, 1);
 }