| /* | |
| * 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]; | |
| 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; | |
| } | |
| } |