blob: d4f873c6624cde6eb63d659490b8fd606350188d [file] [log] [blame]
/*
* Copyright 2017, Data61
* Commonwealth Scientific and Industrial Research Organisation (CSIRO)
* ABN 41 687 119 230.
*
* 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(DATA61_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) {
ZF_LOGE("Offset should not be negative");
return -1;
}
if (offset + size >= 0x40) {
ZF_LOGI("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;
ZF_LOGI("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) {
ZF_LOGE("PCI bar is not power of 2 size (%zu)", 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) {
ZF_LOGE("Failed to map PCI bar %p size %zu", (void*)(uintptr_t)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 */
ZF_LOGI("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 */
ZF_LOGI("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 UNUSED 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];
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;
}
}