/*
* Jailhouse, a Linux-based partitioning hypervisor
*
- * Copyright (c) Siemens AG, 2014
+ * Copyright (c) Siemens AG, 2014, 2015
*
* Author:
* Henning Schild <henning.schild@siemens.com>
*/
#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;
(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)
/* global mask */
c.raw = ive->cspace[IVSHMEM_CFG_MSIX_CAP/4];
- if (!c.field.enable || c.field.fmask)
+ if (!c.enable || c.fmask)
return true;
/* local mask */
- if (ive->device->msix_vectors[0].field.ctrl & 0x1)
+ if (ive->device->msix_vectors[0].masked)
return true;
/* PCI Bus Master */
static int ivshmem_update_msix(struct pci_ivshmem_endpoint *ive)
{
union x86_msi_vector msi = {
- .raw.address = ive->device->msix_vectors[0].field.address,
- .raw.data = ive->device->msix_vectors[0].field.data,
+ .raw.address = ive->device->msix_vectors[0].address,
+ .raw.data = ive->device->msix_vectors[0].data,
};
struct apic_irq_message irq_msg;
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
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)
.raw = ive->cspace[IVSHMEM_CFG_MSIX_CAP/4]
};
- newval.field.enable = p->field.enable;
- newval.field.fmask = p->field.fmask;
+ newval.enable = p->enable;
+ newval.fmask = p->fmask;
if (ive->cspace[IVSHMEM_CFG_MSIX_CAP/4] != newval.raw) {
ive->cspace[IVSHMEM_CFG_MSIX_CAP/4] = newval.raw;
return ivshmem_update_msix(ive);
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 + 3*8):
- 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)
{
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);
{
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)
- return -EINVAL;
+ if (device->info->num_msix_vectors != 1)
+ return trace_error(-EINVAL);
- if (dev->info->shmem_region >= cell->config->num_memory_regions)
- return -EINVAL;
+ 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) +
if ((mem0->phys_start == mem->phys_start) &&
(mem0->size == mem->size)) {
if ((*ivp)->eps[1].device)
- return -EBUSY;
- ivshmem_connect_cell(*ivp, dev, mem, 1);
+ return trace_error(-EBUSY);
+ ivshmem_connect_cell(*ivp, device, mem, 1);
printk("Virtual PCI connection established "
"\"%s\" <--> \"%s\"\n",
cell->config->name, dev0->cell->config->name);
*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);
}
iv->eps[0] = iv->eps[1];
}
- ivshmem_disconnect_cell(iv, 1);
}