blob: 8ec9af87fbe5f9a76b2ebb0969f023d2becdeeb3 [file]
/*
* Copyright 2019, 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 BSD 2-Clause license. Note that NO WARRANTY is provided.
* See "LICENSE_BSD2.txt" for details.
*
* @TAG(DATA61_BSD)
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sel4/sel4.h>
#include <pci/virtual_pci.h>
#include <pci/helper.h>
#include <sel4vmmplatsupport/drivers/pci_helper.h>
#define PCI_CAPABILITY_SPACE_OFFSET 0x40
/* Read PCI memory device */
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 >= PCI_CAPABILITY_SPACE_OFFSET) {
ZF_LOGI("Indexing capability space not yet supported, returning 0");
*result = 0;
return 0;
}
*result = 0;
/* Read the PCI device field at the given offset
* We are passed the device header through the cookie parameter */
memcpy(result, cookie + offset, size);
return 0;
}
/* Write PCI memory device */
int vmm_pci_mem_device_write(void *cookie, int offset, int size, uint32_t value)
{
if (offset < 0) {
ZF_LOGE("Offset should not be negative");
return -1;
}
if (offset + size >= PCI_CAPABILITY_SPACE_OFFSET) {
ZF_LOGI("Indexing capability space not yet supported, returning 0");
return 0;
}
/* Ensure we aren't writing data greater than the size of 'value' */
if (size > sizeof(value)) {
ZF_LOGE("Unable to perform a read of size 0x%x", size);
return -1;
}
/* Write the PCI device field at the given offset
* We are passed the device header through the cookie parameter */
memcpy(cookie + offset, &value, size);
return 0;
}
int vmm_pci_entry_ignore_write(void *cookie, int offset, int size, uint32_t value)
{
ZF_LOGI("Ignoring PCI entry write @ offset 0x%x", offset);
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].mem_type)) {
raw |= 1;
} else {
if (emul->bars[bar].mem_type == PREFETCH_MEM) {
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;
}
static int pci_bar_passthrough_emul_read(void *cookie, int offset, int size, uint32_t *result)
{
pci_bar_emulation_t *emul = (pci_bar_emulation_t *)cookie;
return emul->passthrough.ioread(emul->passthrough.cookie, offset, size, result);
}
static int pci_bar_passthrough_emul_write(void *cookie, int offset, int size, uint32_t value)
{
pci_bar_emulation_t *emul = (pci_bar_emulation_t *)cookie;
return emul->passthrough.iowrite(emul->passthrough.cookie, offset, size, value);
}
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 = calloc(1, 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_passthrough_bar_emulation(vmm_pci_entry_t existing, int num_bars, vmm_pci_bar_t *bars)
{
pci_bar_emulation_t *bar_emul = calloc(1, 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_passthrough_emul_read, .iowrite = pci_bar_passthrough_emul_write
};
}
vmm_pci_entry_t vmm_pci_create_irq_emulation(vmm_pci_entry_t existing, int irq)
{
pci_irq_emulation_t *irq_emul = calloc(1, 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 = calloc(1, 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
};
}
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 = calloc(1, sizeof(*emul));
emul->passthrough = existing;
assert(emul);
emul->num_caps = num_caps;
emul->caps = calloc(1, 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 = calloc(1, sizeof(uint8_t) * num_ranges);
assert(emul->ignore_start);
emul->ignore_end = calloc(1, 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;
}
}