Recommit of branch camkes on release snapshot
diff --git a/src/driver/pci_helper.c b/src/driver/pci_helper.c
new file mode 100644
index 0000000..57963c7
--- /dev/null
+++ b/src/driver/pci_helper.c
@@ -0,0 +1,435 @@
+/*
+ * Copyright 2014, NICTA
+ *
+ * This software may be distributed and modified according to the terms of
+ * the GNU General Public License version 2. Note that NO WARRANTY is provided.
+ * See "LICENSE_GPLv2.txt" for details.
+ *
+ * @TAG(NICTA_GPL)
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sel4/sel4.h>
+
+#include <pci/virtual_pci.h>
+#include <pci/helper.h>
+
+#include "vmm/driver/pci_helper.h"
+
+typedef struct pci_bar_emulation {
+ vmm_pci_entry_t passthrough;
+ int num_bars;
+ vmm_pci_bar_t bars[6];
+ uint32_t bar_writes[6];
+} pci_bar_emulation_t;
+
+typedef struct pci_irq_emulation {
+ vmm_pci_entry_t passthrough;
+ int irq;
+} pci_irq_emulation_t;
+
+typedef struct pci_passthrough_device {
+ /* The address on the host system of this device */
+ vmm_pci_address_t addr;
+ /* Ops for accessing config space */
+ vmm_pci_config_t config;
+} pci_passthrough_device_t;
+
+typedef struct pci_cap_emulation {
+ vmm_pci_entry_t passthrough;
+ int num_caps;
+ uint8_t *caps;
+ int num_ignore;
+ uint8_t *ignore_start;
+ uint8_t *ignore_end;
+} pci_cap_emulation_t;
+
+int vmm_pci_mem_device_read(void *cookie, int offset, int size, uint32_t *result) {
+ if (offset < 0) {
+ LOG_ERROR("Offset should not be negative");
+ return -1;
+ }
+ if (offset + size >= 0x40) {
+ LOG_INFO("Indexing capability space not yet supported, returning 0");
+ *result = 0;
+ return 0;
+ }
+ *result = 0;
+ memcpy(result, cookie + offset, size);
+ return 0;
+}
+
+int vmm_pci_entry_ignore_write(void *cookie, int offset, int size, uint32_t value) {
+ return 0;
+}
+
+void define_pci_host_bridge(vmm_pci_device_def_t *bridge) {
+ *bridge = (vmm_pci_device_def_t) {
+ .vendor_id = 0x5E14,
+ .device_id = 0x42,
+ .command = 0,
+ .status = 0,
+ .revision_id = 0x1,
+ .prog_if = 0,
+ .subclass = 0x0,
+ .class_code = 0x06,
+ .cache_line_size = 0,
+ .latency_timer = 0,
+ .header_type = 0x00,
+ .bist = 0,
+ .bar0 = 0,
+ .bar1 = 0,
+ .bar2 = 0,
+ .bar3 = 0,
+ .bar4 = 0,
+ .bar5 = 0,
+ .cardbus = 0,
+ .subsystem_vendor_id = 0,
+ .subsystem_id = 0,
+ .expansion_rom = 0,
+ .caps_pointer = 0,
+ .reserved1 = 0,
+ .reserved2 = 0,
+ .reserved3 = 0,
+ .interrupt_line = 0,
+ .interrupt_pin = 0,
+ .min_grant = 0,
+ .max_latency = 0,
+ .caps_len = 0,
+ .caps = NULL
+ };
+}
+
+static int passthrough_pci_config_ioread(void *cookie, int offset, int size, uint32_t *result) {
+ pci_passthrough_device_t *dev = (pci_passthrough_device_t*)cookie;
+ switch(size) {
+ case 1:
+ *result = dev->config.ioread8(dev->config.cookie, dev->addr, offset);
+ break;
+ case 2:
+ *result = dev->config.ioread16(dev->config.cookie, dev->addr, offset);
+ break;
+ case 4:
+ *result = dev->config.ioread32(dev->config.cookie, dev->addr, offset);
+ break;
+ default:
+ assert(!"Invalid size");
+ }
+ return 0;
+}
+
+static int passthrough_pci_config_iowrite(void *cookie, int offset, int size, uint32_t val) {
+ pci_passthrough_device_t *dev = (pci_passthrough_device_t*)cookie;
+ switch(size) {
+ case 1:
+ dev->config.iowrite8(dev->config.cookie, dev->addr, offset, val);
+ break;
+ case 2:
+ dev->config.iowrite16(dev->config.cookie, dev->addr, offset, val);
+ break;
+ case 4:
+ dev->config.iowrite32(dev->config.cookie, dev->addr, offset, val);
+ break;
+ default:
+ assert(!"Invalid size");
+ }
+ return 0;
+}
+
+static int pci_bar_emul_check_range(unsigned int offset, unsigned int size) {
+ if (offset < PCI_BASE_ADDRESS_0 || offset + size > PCI_BASE_ADDRESS_5 + 4) {
+ return 1;
+ }
+ return 0;
+}
+
+static uint32_t pci_make_bar(pci_bar_emulation_t *emul, int bar) {
+ if (bar >= emul->num_bars) {
+ return 0;
+ }
+ uint32_t raw = 0;
+ raw |= emul->bars[bar].address;
+ if (!emul->bars[bar].ismem) {
+ raw |= 1;
+ } else {
+ if (emul->bars[bar].prefetchable) {
+ raw |= BIT(3);
+ }
+ }
+ raw |= (emul->bar_writes[bar] & ~MASK(emul->bars[bar].size_bits));
+ return raw;
+}
+
+static int pci_irq_emul_read(void *cookie, int offset, int size, uint32_t *result) {
+ pci_irq_emulation_t *emul = (pci_irq_emulation_t*)cookie;
+ if(offset <= PCI_INTERRUPT_LINE && offset + size > PCI_INTERRUPT_LINE) {
+ /* do the regular read, then patch in our value */
+ int ret = emul->passthrough.ioread(emul->passthrough.cookie, offset, size, result);
+ if (ret) {
+ return ret;
+ }
+ int bit_offset = (PCI_INTERRUPT_LINE - offset) * 8;
+ *result &= ~(MASK(8) << bit_offset);
+ *result |= (emul->irq << bit_offset);
+ return 0;
+ } else {
+ return emul->passthrough.ioread(emul->passthrough.cookie, offset, size, result);
+ }
+}
+
+static int pci_irq_emul_write(void *cookie, int offset, int size, uint32_t value) {
+ pci_irq_emulation_t *emul = (pci_irq_emulation_t*)cookie;
+ if (offset == PCI_INTERRUPT_LINE && size == 1) {
+ /* ignore */
+ return 0;
+ } else if(offset < PCI_INTERRUPT_LINE && offset + size >= PCI_INTERRUPT_LINE) {
+ assert(!"Guest writing PCI configuration in an unsupported way");
+ return -1;
+ } else {
+ return emul->passthrough.iowrite(emul->passthrough.cookie, offset, size, value);
+ }
+}
+
+static int pci_bar_emul_read(void *cookie, int offset, int size, uint32_t *result) {
+ pci_bar_emulation_t *emul = (pci_bar_emulation_t*)cookie;
+ if (pci_bar_emul_check_range(offset, size)) {
+ return emul->passthrough.ioread(emul->passthrough.cookie, offset, size, result);
+ }
+ /* Construct the bar value */
+ int bar = (offset - PCI_BASE_ADDRESS_0) / 4;
+ int bar_offset = offset & 3;
+ uint32_t bar_raw = pci_make_bar(emul, bar);
+ char *barp = (char*)&bar_raw;
+ *result = 0;
+ memcpy(result, barp + bar_offset, size);
+ return 0;
+}
+
+static int pci_bar_emul_write(void *cookie, int offset, int size, uint32_t value) {
+ pci_bar_emulation_t *emul = (pci_bar_emulation_t*)cookie;
+ if (pci_bar_emul_check_range(offset, size)) {
+ return emul->passthrough.iowrite(emul->passthrough.cookie, offset, size, value);
+ }
+ /* Construct the bar value */
+ int bar = (offset - PCI_BASE_ADDRESS_0) / 4;
+ int bar_offset = offset & 3;
+ char *barp = (char*)&emul->bar_writes[bar];
+ memcpy(barp + bar_offset, &value, size);
+ return 0;
+}
+
+vmm_pci_entry_t vmm_pci_create_bar_emulation(vmm_pci_entry_t existing, int num_bars, vmm_pci_bar_t *bars) {
+ pci_bar_emulation_t *bar_emul = malloc(sizeof(*bar_emul));
+ assert(bar_emul);
+ memcpy(bar_emul->bars, bars, sizeof(vmm_pci_bar_t) * num_bars);
+ bar_emul->passthrough = existing;
+ bar_emul->num_bars = num_bars;
+ memset(bar_emul->bar_writes, 0, sizeof(bar_emul->bar_writes));
+ return (vmm_pci_entry_t) {.cookie = bar_emul, .ioread = pci_bar_emul_read, .iowrite = pci_bar_emul_write};
+}
+
+vmm_pci_entry_t vmm_pci_create_irq_emulation(vmm_pci_entry_t existing, int irq) {
+ pci_irq_emulation_t *irq_emul = malloc(sizeof(*irq_emul));
+ assert(irq_emul);
+ irq_emul->passthrough = existing;
+ irq_emul->irq = irq;
+ return (vmm_pci_entry_t) {.cookie = irq_emul, .ioread = pci_irq_emul_read, .iowrite = pci_irq_emul_write};
+}
+
+vmm_pci_entry_t vmm_pci_create_passthrough(vmm_pci_address_t addr, vmm_pci_config_t config) {
+ pci_passthrough_device_t *dev = malloc(sizeof(*dev));
+ assert(dev);
+ dev->addr = addr;
+ dev->config = config;
+ LOG_INFO("Creating passthrough device for %02x:%02x.%d", addr.bus, addr.dev, addr.fun);
+ return (vmm_pci_entry_t){.cookie = dev, .ioread = passthrough_pci_config_ioread, .iowrite = passthrough_pci_config_iowrite};
+}
+
+int vmm_pci_helper_map_bars(vmm_t *vmm, libpci_device_iocfg_t *cfg, vmm_pci_bar_t *bars) {
+ int i;
+ int bar = 0;
+ for (i = 0; i < 6; i++) {
+ if (cfg->base_addr[i] == 0) {
+ continue;
+ }
+ size_t size = cfg->base_addr_size[i];
+ assert(size != 0);
+ int size_bits = 31 - CLZ(size);
+ if (BIT(size_bits) != size) {
+ LOG_ERROR("PCI bar is not power of 2 size (0x%x)", size);
+ return -1;
+ }
+ bars[bar].size_bits = size_bits;
+ if (cfg->base_addr_space[i] == PCI_BASE_ADDRESS_SPACE_MEMORY) {
+ /* Need to map into the VMM. Make sure it is aligned */
+ uintptr_t addr = vmm_map_guest_device(vmm, cfg->base_addr[i], size, BIT(size_bits));
+ if(addr == 0) {
+ LOG_ERROR("Failed to map PCI bar 0x%x size 0x%x", cfg->base_addr[i], size);
+ return -1;
+ }
+ bars[bar].ismem = 1;
+ bars[bar].address = addr;
+ bars[bar].prefetchable = cfg->base_addr_prefetchable[i];
+ } else {
+ /* Need to add the IO port range */
+ int error = vmm_io_port_add_passthrough(&vmm->io_port, cfg->base_addr[i], cfg->base_addr[i] + size - 1, "PCI Passthrough Device");
+ if (error) {
+ return error;
+ }
+ bars[bar].ismem=0;
+ bars[bar].address = cfg->base_addr[i];
+ bars[bar].prefetchable = 0;
+ }
+ bar++;
+ }
+ return bar;
+}
+
+static int pci_cap_emul_read(void *cookie, int offset, int size, uint32_t *result) {
+ pci_cap_emulation_t *emul = (pci_cap_emulation_t*)cookie;
+ if(offset <= PCI_STATUS && offset + size > PCI_STATUS) {
+ /* do the regular read, then patch in our value */
+ int ret = emul->passthrough.ioread(emul->passthrough.cookie, offset, size, result);
+ if (ret) {
+ return ret;
+ }
+ int bit_offset = (PCI_STATUS - offset) * 8;
+ *result &= ~(PCI_STATUS_CAP_LIST << bit_offset);
+ if (emul->num_caps > 0) {
+ *result |= (PCI_STATUS_CAP_LIST << bit_offset);
+ }
+ return 0;
+ } else if(offset <= PCI_CAPABILITY_LIST && offset + size > PCI_CAPABILITY_LIST) {
+ /* do the regular read, then patch in our value */
+ int ret = emul->passthrough.ioread(emul->passthrough.cookie, offset, size, result);
+ if (ret) {
+ return ret;
+ }
+ int bit_offset = (PCI_CAPABILITY_LIST - offset) * 8;
+ *result &= ~(MASK(8) << bit_offset);
+ if (emul->num_caps > 0) {
+ *result |= (emul->caps[0] << bit_offset);
+ }
+ return 0;
+ }
+ /* see if we are reading from any location that we would prefer not to */
+ int i;
+ for (i = 0; i < emul->num_ignore; i++) {
+ if (offset <= emul->ignore_start[i] && offset+size > emul->ignore_end[i]) {
+ /* who cares about the size, just ignore everything */
+ LOG_INFO("Attempted read at 0x%x of size %d from region 0x%x-0x%x", offset, size, emul->ignore_start[i], emul->ignore_end[i]);
+ *result = 0;
+ return 0;
+ }
+ }
+ /* See if we are reading a capability index */
+ for (i = 0; i < emul->num_caps; i++) {
+ if (offset <= emul->caps[i] + 1 && offset + size > emul->caps[i] + 1) {
+ /* do the regular read, then patch in our value */
+ int ret = emul->passthrough.ioread(emul->passthrough.cookie, offset, size, result);
+ if (ret) {
+ return ret;
+ }
+ int bit_offset = (emul->caps[i] + 1 - offset) * 8;
+ *result &= ~(MASK(8) << bit_offset);
+ if (i + 1 < emul->num_caps) {
+ *result |= (emul->caps[i + 1] << bit_offset);
+ }
+ return 0;
+ }
+ }
+ /* Pass through whatever is left */
+ return emul->passthrough.ioread(emul->passthrough.cookie, offset, size, result);
+}
+
+static int pci_cap_emul_write(void *cookie, int offset, int size, uint32_t value) {
+ pci_cap_emulation_t *emul = (pci_cap_emulation_t*)cookie;
+ /* Prevents writes to our ignored ranges. but let anything else through */
+ int i;
+ for (i = 0; i < emul->num_ignore; i++) {
+ if (offset <= emul->ignore_start[i] && offset+size > emul->ignore_end[i]) {
+ /* who cares about the size, just ignore everything */
+ LOG_INFO("Attempted write at 0x%x of size %d from region 0x%x-0x%x", offset, size, emul->ignore_start[i], emul->ignore_end[i]);
+ return 0;
+ }
+ }
+ return emul->passthrough.iowrite(emul->passthrough.cookie, offset, size, value);
+}
+
+vmm_pci_entry_t vmm_pci_create_cap_emulation(vmm_pci_entry_t existing, int num_caps, uint8_t *caps, int num_ranges, uint8_t *range_starts, uint8_t *range_ends) {
+ pci_cap_emulation_t *emul = malloc(sizeof(*emul));
+ emul->passthrough = existing;
+ assert(emul);
+ emul->num_caps = num_caps;
+ emul->caps = malloc(sizeof(uint8_t) * num_caps);
+ assert(emul->caps);
+ memcpy(emul->caps, caps, sizeof(uint8_t) * num_caps);
+ emul->num_ignore = num_ranges;
+ emul->ignore_start = malloc(sizeof(uint8_t) * num_ranges);
+ assert(emul->ignore_start);
+ emul->ignore_end = malloc(sizeof(uint8_t) * num_ranges);
+ assert(emul->ignore_end);
+ memcpy(emul->ignore_start, range_starts, sizeof(uint8_t) * num_ranges);
+ memcpy(emul->ignore_end, range_ends, sizeof(uint8_t) * num_ranges);
+ return (vmm_pci_entry_t) {.cookie = emul, .ioread = pci_cap_emul_read, .iowrite = pci_cap_emul_write};
+}
+
+#define MAX_CAPS 256
+
+vmm_pci_entry_t vmm_pci_no_msi_cap_emulation(vmm_pci_entry_t existing) {
+ uint32_t value;
+ int error;
+ /* Ensure this is a type 0 device */
+ value = 0;
+ error = existing.ioread(existing.cookie, PCI_HEADER_TYPE, 1, &value);
+ assert(!error);
+ assert( (value & (~BIT(7))) == PCI_HEADER_TYPE_NORMAL);
+ /* Check if it has capability space */
+ error = existing.ioread(existing.cookie, PCI_STATUS, 1, &value);
+ assert(!error);
+ if (! (value & PCI_STATUS_CAP_LIST)) {
+ return existing;
+ }
+ /* First we need to scan the capability space, and detect any PCI caps
+ * while we're at it */
+ int num_caps;
+ uint8_t caps[MAX_CAPS];
+ int num_ignore;
+ uint8_t ignore_start[2];
+ uint8_t ignore_end[2];
+ int offset = PCI_CAPABILITY_LIST;
+ error = existing.ioread(existing.cookie, PCI_CAPABILITY_LIST, 1, &value);
+ assert(!error);
+ /* Mask off the bottom 2 bits, which are reserved */
+ value &= ~MASK(2);
+ num_caps = 0;
+ num_ignore = 0;
+ while (value != 0) {
+ uint32_t cap_type = 0;
+ error = existing.ioread(existing.cookie, value, 1, &cap_type);
+ assert(!error);
+ if (cap_type == PCI_CAP_ID_MSI) {
+ assert(num_ignore < 2);
+ ignore_start[num_ignore] = value;
+ ignore_end[num_ignore] = value + 20;
+ num_ignore++;
+ } else if (cap_type == PCI_CAP_ID_MSIX) {
+ ignore_start[num_ignore] = value;
+ ignore_end[num_ignore] = value + 8;
+ num_ignore++;
+ } else {
+ assert(num_caps < MAX_CAPS);
+ caps[num_caps] = (uint8_t)value;
+ num_caps++;
+ }
+ error = existing.ioread(existing.cookie, value + 1, 1, &value);
+ assert(!error);
+ }
+ if (num_ignore > 0) {
+ return vmm_pci_create_cap_emulation(existing, num_caps, caps, num_ignore, ignore_start, ignore_end);
+ } else {
+ return existing;
+ }
+}