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;
+    }
+}