* the COPYING file in the top-level directory.
*/
+#include <jailhouse/control.h>
#include <jailhouse/pci.h>
#include <jailhouse/printk.h>
#include <jailhouse/utils.h>
+#include <asm/apic.h>
#include <asm/io.h>
#include <asm/pci.h>
#include <asm/vtd.h>
{
vtd_remove_pci_device(device);
}
+
+static union x86_msi_vector pci_get_x86_msi_vector(struct pci_device *device)
+{
+ union pci_msi_registers *regs = &device->msi_registers;
+ bool msi_64bits = device->info->msi_64bits;
+ union x86_msi_vector msi;
+
+ msi.raw.address = msi_64bits ? regs->msg64.address :
+ regs->msg32.address;
+ msi.raw.data = msi_64bits ? regs->msg64.data : regs->msg32.data;
+ return msi;
+}
+
+static struct apic_irq_message
+pci_translate_msi_vector(struct pci_device *device, unsigned int vector,
+ unsigned int legacy_vectors, union x86_msi_vector msi)
+{
+ struct apic_irq_message irq_msg;
+
+ irq_msg.vector = msi.native.vector;
+ if (legacy_vectors > 1) {
+ irq_msg.vector &= ~(legacy_vectors - 1);
+ irq_msg.vector |= vector;
+ }
+ irq_msg.delivery_mode = msi.native.delivery_mode;
+ irq_msg.level_triggered = 0;
+ irq_msg.dest_logical = msi.native.dest_logical;
+ irq_msg.redir_hint = msi.native.redir_hint;
+ irq_msg.destination = msi.native.destination;
+
+ return irq_msg;
+}
+
+void pci_suppress_msi(struct pci_device *device,
+ const struct jailhouse_pci_capability *cap)
+{
+ unsigned int n, vectors = pci_enabled_msi_vectors(device);
+ const struct jailhouse_pci_device *info = device->info;
+ struct apic_irq_message irq_msg;
+ union x86_msi_vector msi = {
+ .native.dest_logical = 1,
+ .native.redir_hint = 1,
+ .native.address = MSI_ADDRESS_VALUE,
+ };
+
+ if (!(pci_read_config(info->bdf, PCI_CFG_COMMAND, 2) & PCI_CMD_MASTER))
+ return;
+
+ /*
+ * Disable delivery by setting no destination CPU bit in logical
+ * addressing mode.
+ */
+ if (info->msi_64bits)
+ pci_write_config(info->bdf, cap->start + 8, 0, 4);
+ pci_write_config(info->bdf, cap->start + 4, (u32)msi.raw.address, 4);
+
+ /*
+ * Inject MSI vectors to avoid losing events while suppressed.
+ * Linux can handle rare spurious interrupts.
+ */
+ msi = pci_get_x86_msi_vector(device);
+ for (n = 0; n < vectors; n++) {
+ irq_msg = pci_translate_msi_vector(device, n, vectors, msi);
+ apic_send_irq(irq_msg);
+ }
+}
+
+int pci_update_msi(struct pci_device *device,
+ const struct jailhouse_pci_capability *cap)
+{
+ unsigned int n, vectors = pci_enabled_msi_vectors(device);
+ union x86_msi_vector msi = pci_get_x86_msi_vector(device);
+ const struct jailhouse_pci_device *info = device->info;
+ struct apic_irq_message irq_msg;
+ u16 bdf = info->bdf;
+ int result = 0;
+
+ if (vectors == 0)
+ return 0;
+
+ for (n = 0; n < vectors; n++) {
+ irq_msg = pci_translate_msi_vector(device, n, vectors, msi);
+ result = vtd_map_interrupt(device->cell, bdf, n, irq_msg);
+ // HACK for QEMU
+ if (result == -ENOSYS) {
+ for (n = 1; n < (info->msi_64bits ? 4 : 3); n++)
+ pci_write_config(bdf, cap->start + n * 4,
+ device->msi_registers.raw[n], 4);
+ return 0;
+ }
+ if (result < 0)
+ return result;
+ }
+
+ /* set result to the base index again */
+ result -= vectors - 1;
+
+ pci_write_config(bdf, cap->start + (info->msi_64bits ? 12 : 8), 0, 2);
+
+ if (info->msi_64bits)
+ pci_write_config(bdf, cap->start + 8, 0, 4);
+ msi.remap.int_index15 = result >> 15;
+ msi.remap.shv = 1;
+ msi.remap.remapped = 1;
+ msi.remap.int_index = result;
+ pci_write_config(bdf, cap->start + 4, (u32)msi.raw.address, 4);
+
+ return 0;
+}
#define PCI_DEVFN(bdf) ((bdf) & 0xff)
#define PCI_BDF_PARAMS(bdf) (bdf) >> 8, ((bdf) >> 3) & 0x1f, (bdf) & 7
+#define PCI_CFG_COMMAND 0x04
+# define PCI_CMD_MASTER (1 << 2)
+# define PCI_CMD_INTX_OFF (1 << 10)
+
enum pci_access { PCI_ACCESS_REJECT, PCI_ACCESS_PERFORM, PCI_ACCESS_DONE };
+union pci_msi_registers {
+ struct {
+ u16 padding;
+ u16 enable:1,
+ ignore1:3,
+ mme:3,
+ ignore2:9;
+ u32 address;
+ u16 data;
+ } __attribute__((packed)) msg32;
+ struct {
+ u32 padding; /* use msg32 */
+ u64 address;
+ u16 data;
+ } __attribute__((packed)) msg64;
+ u32 raw[4];
+} __attribute__((packed));
+
struct pci_device {
const struct jailhouse_pci_device *info;
struct cell *cell;
+
+ union pci_msi_registers msi_registers;
};
int pci_init(void);
int pci_cell_init(struct cell *cell);
void pci_cell_exit(struct cell *cell);
+void pci_config_commit(struct cell *cell_added_removed);
+
+unsigned int pci_enabled_msi_vectors(struct pci_device *device);
+
+void pci_suppress_msi(struct pci_device *device,
+ const struct jailhouse_pci_capability *cap);
+int pci_update_msi(struct pci_device *device,
+ const struct jailhouse_pci_capability *cap);
+
+void pci_prepare_handover(void);
+void pci_shutdown(void);
+
u32 arch_pci_read_config(u16 bdf, u16 address, unsigned int size);
void arch_pci_write_config(u16 bdf, u16 address, u32 value, unsigned int size);
#define PCI_CONFIG_HEADER_SIZE 0x40
-#define PCI_CFG_COMMAND 0x04
-# define PCI_CMD_INTX_OFF (1 << 10)
+#define PCI_CAP_MSI 0x05
+#define PCI_CAP_MSIX 0x11
#define for_each_configured_pci_device(dev, cell) \
for ((dev) = (cell)->pci_devices; \
(dev) - (cell)->pci_devices < (cell)->config->num_pci_devices; \
(dev)++)
+#define for_each_pci_cap(cap, dev, counter) \
+ for ((cap) = jailhouse_cell_pci_caps((dev)->cell->config) + \
+ (dev)->info->caps_start, (counter) = 0; \
+ (counter) < (dev)->info->num_caps; \
+ (cap)++, (counter)++)
+
/* entry for PCI config space whitelist (granting access) */
struct pci_cfg_access {
u32 reg_num; /** Register number (4-byte aligned) */
unsigned int size, u32 *value)
{
const struct jailhouse_pci_capability *cap;
+ unsigned int cap_offs;
if (!device) {
*value = -1;
if (!cap)
return PCI_ACCESS_PERFORM;
- // TODO: Emulate MSI/MSI-X etc.
+ cap_offs = address - cap->start;
+ if (cap->id == PCI_CAP_MSI && cap_offs >= 4 &&
+ (cap_offs < 10 || (device->info->msi_64bits && cap_offs < 14))) {
+ *value = device->msi_registers.raw[cap_offs / 4] >>
+ ((cap_offs % 4) * 8);
+ return PCI_ACCESS_DONE;
+ }
return PCI_ACCESS_PERFORM;
}
const struct jailhouse_pci_capability *cap;
/* initialize list to work around wrong compiler warning */
const struct pci_cfg_access *list = NULL;
- unsigned int n, bias_shift, len = 0;
- u32 mask;
+ unsigned int bias_shift = (address % 4) * 8;
+ unsigned int n, cap_offs, len = 0;
+ u32 mask = BYTE_MASK(size);
if (!device)
return PCI_ACCESS_REJECT;
len = ARRAY_SIZE(bridge_write_access);
}
- bias_shift = (address & 0x003) * 8;
- mask = BYTE_MASK(size);
-
for (n = 0; n < len; n++) {
if (list[n].reg_num == (address & 0xffc) &&
((list[n].mask >> bias_shift) & mask) == mask)
if (!cap || !(cap->flags & JAILHOUSE_PCICAPS_WRITE))
return PCI_ACCESS_REJECT;
+ cap_offs = address - cap->start;
+ if (cap->id == PCI_CAP_MSI &&
+ (cap_offs < 10 || (device->info->msi_64bits && cap_offs < 14))) {
+ value <<= bias_shift;
+ mask <<= bias_shift;
+ device->msi_registers.raw[cap_offs / 4] &= ~mask;
+ device->msi_registers.raw[cap_offs / 4] |= value;
+
+ if (pci_update_msi(device, cap) < 0)
+ return PCI_ACCESS_REJECT;
+
+ /*
+ * Address and data words are emulated, the control word is
+ * written as-is.
+ */
+ if (cap_offs >= 4)
+ return PCI_ACCESS_DONE;
+ }
+
return PCI_ACCESS_PERFORM;
}
}
+unsigned int pci_enabled_msi_vectors(struct pci_device *device)
+{
+ return device->msi_registers.msg32.enable ?
+ 1 << device->msi_registers.msg32.mme : 0;
+}
+
+static void pci_save_msi(struct pci_device *device,
+ const struct jailhouse_pci_capability *cap)
+{
+ u16 bdf = device->info->bdf;
+ unsigned int n;
+
+ for (n = 0; n < (device->info->msi_64bits ? 4 : 3); n++)
+ device->msi_registers.raw[n] =
+ pci_read_config(bdf, cap->start + n * 4, 4);
+}
+
+static void pci_restore_msi(struct pci_device *device,
+ const struct jailhouse_pci_capability *cap)
+{
+ unsigned int n;
+
+ for (n = 1; n < (device->info->msi_64bits ? 4 : 3); n++)
+ pci_write_config(device->info->bdf, cap->start + n * 4,
+ device->msi_registers.raw[n], 4);
+}
+
+/**
+ * pci_prepare_handover() - Prepare the handover of PCI devices to Jailhouse or
+ * back to Linux
+ */
+void pci_prepare_handover(void)
+{
+ const struct jailhouse_pci_capability *cap;
+ struct pci_device *device;
+ unsigned int n;
+
+ if (!root_cell.pci_devices)
+ return;
+
+ for_each_configured_pci_device(device, &root_cell) {
+ if (device->cell)
+ for_each_pci_cap(cap, device, n)
+ if (cap->id == PCI_CAP_MSI)
+ pci_suppress_msi(device, cap);
+ // TODO: MSI-X
+ }
+}
+
static int pci_add_device(struct cell *cell, struct pci_device *device)
{
printk("Adding PCI device %02x:%02x.%x to cell \"%s\"\n",
sizeof(struct pci_device));
const struct jailhouse_pci_device *dev_infos =
jailhouse_cell_pci_devices(cell->config);
+ const struct jailhouse_pci_capability *cap;
struct pci_device *device, *root_device;
- unsigned int ndev;
+ unsigned int ndev, ncap;
int err;
cell->pci_devices = page_alloc(&mem_pool, array_size / PAGE_SIZE);
}
device->cell = cell;
+
+ for_each_pci_cap(cap, device, ncap)
+ if (cap->id == PCI_CAP_MSI)
+ pci_save_msi(device, cap);
+ else if (cap->id == PCI_CAP_MSIX)
+ // TODO: Handle
+ printk("MSI-X left out @%02x:%02x.%x!\n",
+ PCI_BDF_PARAMS(device->info->bdf));
}
+ if (cell == &root_cell)
+ pci_prepare_handover();
+
return 0;
}
if (cell == &root_cell)
return;
- for_each_configured_pci_device(device, cell) {
- if (!device->cell)
- continue;
- pci_remove_device(device);
- pci_return_device_to_root_cell(device);
- }
+ for_each_configured_pci_device(device, cell)
+ if (device->cell) {
+ pci_remove_device(device);
+ pci_return_device_to_root_cell(device);
+ }
page_free(&mem_pool, cell->pci_devices, array_size / PAGE_SIZE);
}
+
+void pci_config_commit(struct cell *cell_added_removed)
+{
+ const struct jailhouse_pci_capability *cap;
+ struct pci_device *device;
+ unsigned int n;
+ int err = 0;
+
+ if (!cell_added_removed)
+ return;
+
+ for_each_configured_pci_device(device, &root_cell)
+ if (device->cell)
+ for_each_pci_cap(cap, device, n) {
+ if (cap->id == PCI_CAP_MSI)
+ err = pci_update_msi(device, cap);
+ // TODO: MSI-X
+ if (err)
+ goto error;
+ }
+ return;
+
+error:
+ panic_printk("FATAL: Unsupported MSI/MSI-X state, device %02x:%02x.%x,"
+ " cap %d\n", PCI_BDF_PARAMS(device->info->bdf), cap->id);
+ panic_stop(NULL);
+}
+
+void pci_shutdown(void)
+{
+ const struct jailhouse_pci_capability *cap;
+ struct pci_device *device;
+ unsigned int n;
+
+ if (!root_cell.pci_devices)
+ return;
+
+ for_each_configured_pci_device(device, &root_cell)
+ if (device->cell)
+ for_each_pci_cap(cap, device, n)
+ if (cap->id == PCI_CAP_MSI)
+ pci_restore_msi(device, cap);
+ // TODO: MSI-X
+}