diff --git a/powerpc/include/kvm/kvm-arch.h b/powerpc/include/kvm/kvm-arch.h index 7235067..6dd7082 100644 --- a/powerpc/include/kvm/kvm-arch.h +++ b/powerpc/include/kvm/kvm-arch.h @@ -40,6 +40,8 @@ */ #define KVM_PCI_MMIO_AREA 0x1000000 +struct spapr_phb; + struct kvm { int sys_fd; /* For system ioctls(), i.e. /dev/kvm */ int vm_fd; /* For VM ioctls() */ @@ -70,6 +72,7 @@ struct kvm { const char *name; int vm_state; struct icp_state *icp; + struct spapr_phb *phb; }; /* Helper for the various bits of code that generate FDT nodes */ diff --git a/powerpc/irq.c b/powerpc/irq.c index f8f12e5..7da4012 100644 --- a/powerpc/irq.c +++ b/powerpc/irq.c @@ -21,7 +21,10 @@ #include #include +#include "kvm/pci.h" + #include "xics.h" +#include "spapr_pci.h" #define XICS_IRQS 1024 @@ -30,10 +33,21 @@ * generic & cope with multiple PPC platform types. */ +static int pci_devs = 0; + int irq__register_device(u32 dev, u8 *num, u8 *pin, u8 *line) { - fprintf(stderr, "irq__register_device(%d, [%d], [%d], [%d]\n", - dev, *num, *pin, *line); + if (pci_devs >= PCI_MAX_DEVICES) + die("Hit PCI device limit!\n"); + + *num = pci_devs++; + + *pin = 1; + /* + * Have I said how nasty I find this? Line should be dontcare... PHB + * should determine which CPU/XICS IRQ to fire. + */ + *line = xics_alloc_irqnum(); return 0; } diff --git a/powerpc/kvm.c b/powerpc/kvm.c index 815108c..cbc0d8f 100644 --- a/powerpc/kvm.c +++ b/powerpc/kvm.c @@ -18,6 +18,7 @@ #include "spapr.h" #include "spapr_hvcons.h" +#include "spapr_pci.h" #include @@ -140,6 +141,11 @@ void kvm__arch_init(struct kvm *kvm, const char *hugetlbfs_path, u64 ram_size) register_core_rtas(); /* Now that hypercalls are initialised, register a couple for the console: */ spapr_hvcons_init(); + spapr_create_phb(kvm, "pci", SPAPR_PCI_BUID, + SPAPR_PCI_MEM_WIN_ADDR, + SPAPR_PCI_MEM_WIN_SIZE, + SPAPR_PCI_IO_WIN_ADDR, + SPAPR_PCI_IO_WIN_SIZE); } void kvm__arch_delete_ram(struct kvm *kvm) @@ -406,6 +412,11 @@ static void setup_fdt(struct kvm *kvm) _FDT(fdt_finish(fdt)); _FDT(fdt_open_into(fdt, fdt_dest, FDT_MAX_SIZE)); + + /* PCI */ + if (spapr_populate_pci_devices(kvm, PHANDLE_XICP, fdt_dest)) + die("Fail populating PCI device nodes"); + _FDT(fdt_add_mem_rsv(fdt_dest, kvm->rtas_gra, kvm->rtas_size)); _FDT(fdt_pack(fdt_dest)); } diff --git a/powerpc/spapr.h b/powerpc/spapr.h index 1271daa..0537f88 100644 --- a/powerpc/spapr.h +++ b/powerpc/spapr.h @@ -81,4 +81,13 @@ target_ulong spapr_rtas_call(struct kvm_cpu *vcpu, uint32_t token, uint32_t nargs, target_ulong args, uint32_t nret, target_ulong rets); +#define SPAPR_PCI_BUID 0x800000020000001ULL +#define SPAPR_PCI_MEM_WIN_ADDR (KVM_MMIO_START + 0xA0000000) +#define SPAPR_PCI_MEM_WIN_SIZE 0x20000000 +#define SPAPR_PCI_IO_WIN_ADDR (SPAPR_PCI_MEM_WIN_ADDR + SPAPR_PCI_MEM_WIN_SIZE) +#define SPAPR_PCI_IO_WIN_SIZE 0x2000000 + +#define SPAPR_PCI_WIN_START SPAPR_PCI_MEM_WIN_ADDR +#define SPAPR_PCI_WIN_END (SPAPR_PCI_IO_WIN_ADDR + SPAPR_PCI_IO_WIN_SIZE) + #endif /* !defined (__HW_SPAPR_H__) */ diff --git a/powerpc/spapr_pci.c b/powerpc/spapr_pci.c new file mode 100644 index 0000000..f9d29f0 --- /dev/null +++ b/powerpc/spapr_pci.c @@ -0,0 +1,423 @@ +/* + * SPAPR PHB emulation, RTAS interface to PCI config space, device tree nodes + * for enumerated devices. + * + * Borrowed heavily from QEMU's spapr_pci.c, + * Copyright (c) 2011 Alexey Kardashevskiy, IBM Corporation. + * Copyright (c) 2011 David Gibson, IBM Corporation. + * + * Modifications copyright 2011 Matt Evans , IBM Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. + */ + +#include "spapr.h" +#include "spapr_pci.h" +#include "kvm/util.h" +#include "kvm/pci.h" +#include "libfdt.h" + +#include +#include + + +/* #define DEBUG_PHB yes */ +#ifdef DEBUG_PHB +#define phb_dprintf(fmt, ...) \ + do { fprintf(stderr, fmt, ## __VA_ARGS__); } while (0) +#else +#define phb_dprintf(fmt, ...) \ + do { } while (0) +#endif + +static const uint32_t bars[] = { + PCI_BASE_ADDRESS_0, PCI_BASE_ADDRESS_1, + PCI_BASE_ADDRESS_2, PCI_BASE_ADDRESS_3, + PCI_BASE_ADDRESS_4, PCI_BASE_ADDRESS_5 + /*, PCI_ROM_ADDRESS*/ +}; + +#define PCI_NUM_REGIONS 7 + +/* Macros to operate with address in OF binding to PCI */ +#define b_x(x, p, l) (((x) & ((1<<(l))-1)) << (p)) +#define b_n(x) b_x((x), 31, 1) /* 0 if relocatable */ +#define b_p(x) b_x((x), 30, 1) /* 1 if prefetchable */ +#define b_t(x) b_x((x), 29, 1) /* 1 if the address is aliased */ +#define b_ss(x) b_x((x), 24, 2) /* the space code */ +#define b_bbbbbbbb(x) b_x((x), 16, 8) /* bus number */ +#define b_ddddd(x) b_x((x), 11, 5) /* device number */ +#define b_fff(x) b_x((x), 8, 3) /* function number */ +#define b_rrrrrrrr(x) b_x((x), 0, 8) /* register number */ + +#define SS_M64 3 +#define SS_M32 2 +#define SS_IO 1 +#define SS_CONFIG 0 + + +static struct spapr_phb phb; + + +static void rtas_ibm_read_pci_config(struct kvm_cpu *vcpu, + uint32_t token, uint32_t nargs, + target_ulong args, + uint32_t nret, target_ulong rets) +{ + uint32_t val = 0; + uint64_t buid = ((uint64_t)rtas_ld(vcpu->kvm, args, 1) << 32) | rtas_ld(vcpu->kvm, args, 2); + union pci_config_address addr = { .w = rtas_ld(vcpu->kvm, args, 0) }; + struct pci_device_header *dev = pci__find_dev(addr.device_number); + uint32_t size = rtas_ld(vcpu->kvm, args, 3); + + if (buid != phb.buid || !dev || (size > 4)) { + phb_dprintf("- cfgRd buid 0x%lx cfg addr 0x%x size %d not found\n", + buid, addr.w, size); + + rtas_st(vcpu->kvm, rets, 0, -1); + return; + } + pci__config_rd(vcpu->kvm, addr, &val, size); + /* It appears this wants a byteswapped result... */ + switch (size) { + case 4: + val = le32_to_cpu(val); + break; + case 2: + val = le16_to_cpu(val>>16); + break; + case 1: + val = val >> 24; + break; + } + phb_dprintf("- cfgRd buid 0x%lx addr 0x%x (/%d): b%d,d%d,f%d,r0x%x, val 0x%x\n", + buid, addr.w, size, addr.bus_number, addr.device_number, addr.function_number, + addr.register_number, val); + + rtas_st(vcpu->kvm, rets, 0, 0); + rtas_st(vcpu->kvm, rets, 1, val); +} + +static void rtas_read_pci_config(struct kvm_cpu *vcpu, + uint32_t token, uint32_t nargs, + target_ulong args, + uint32_t nret, target_ulong rets) +{ + uint32_t val; + union pci_config_address addr = { .w = rtas_ld(vcpu->kvm, args, 0) }; + struct pci_device_header *dev = pci__find_dev(addr.device_number); + uint32_t size = rtas_ld(vcpu->kvm, args, 1); + + if (!dev || (size > 4)) { + rtas_st(vcpu->kvm, rets, 0, -1); + return; + } + pci__config_rd(vcpu->kvm, addr, &val, size); + switch (size) { + case 4: + val = le32_to_cpu(val); + break; + case 2: + val = le16_to_cpu(val>>16); /* We're yuck-endian. */ + break; + case 1: + val = val >> 24; + break; + } + phb_dprintf("- cfgRd addr 0x%x size %d, val 0x%x\n", addr.w, size, val); + rtas_st(vcpu->kvm, rets, 0, 0); + rtas_st(vcpu->kvm, rets, 1, val); +} + +static void rtas_ibm_write_pci_config(struct kvm_cpu *vcpu, + uint32_t token, uint32_t nargs, + target_ulong args, + uint32_t nret, target_ulong rets) +{ + uint64_t buid = ((uint64_t)rtas_ld(vcpu->kvm, args, 1) << 32) | rtas_ld(vcpu->kvm, args, 2); + union pci_config_address addr = { .w = rtas_ld(vcpu->kvm, args, 0) }; + struct pci_device_header *dev = pci__find_dev(addr.device_number); + uint32_t size = rtas_ld(vcpu->kvm, args, 3); + uint32_t val = rtas_ld(vcpu->kvm, args, 4); + + if (buid != phb.buid || !dev || (size > 4)) { + phb_dprintf("- cfgWr buid 0x%lx cfg addr 0x%x/%d error (val 0x%x)\n", + buid, addr.w, size, val); + + rtas_st(vcpu->kvm, rets, 0, -1); + return; + } + phb_dprintf("- cfgWr buid 0x%lx addr 0x%x (/%d): b%d,d%d,f%d,r0x%x, val 0x%x\n", + buid, addr.w, size, addr.bus_number, addr.device_number, addr.function_number, + addr.register_number, val); + switch (size) { + case 4: + val = le32_to_cpu(val); + break; + case 2: + val = le16_to_cpu(val) << 16; + break; + case 1: + val = val >> 24; + break; + } + pci__config_wr(vcpu->kvm, addr, &val, size); + rtas_st(vcpu->kvm, rets, 0, 0); +} + +static void rtas_write_pci_config(struct kvm_cpu *vcpu, + uint32_t token, uint32_t nargs, + target_ulong args, + uint32_t nret, target_ulong rets) +{ + union pci_config_address addr = { .w = rtas_ld(vcpu->kvm, args, 0) }; + struct pci_device_header *dev = pci__find_dev(addr.device_number); + uint32_t size = rtas_ld(vcpu->kvm, args, 1); + uint32_t val = rtas_ld(vcpu->kvm, args, 2); + + if (!dev || (size > 4)) { + rtas_st(vcpu->kvm, rets, 0, -1); + return; + } + + phb_dprintf("- cfgWr addr 0x%x (/%d): b%d,d%d,f%d,r0x%x, val 0x%x\n", + addr.w, size, addr.bus_number, addr.device_number, addr.function_number, + addr.register_number, val); + switch (size) { + case 4: + val = le32_to_cpu(val); + break; + case 2: + val = le16_to_cpu(val) << 16; + break; + case 1: + val = val >> 24; + break; + } + pci__config_wr(vcpu->kvm, addr, &val, size); + rtas_st(vcpu->kvm, rets, 0, 0); +} + +void spapr_create_phb(struct kvm *kvm, + const char *busname, uint64_t buid, + uint64_t mem_win_addr, uint64_t mem_win_size, + uint64_t io_win_addr, uint64_t io_win_size) +{ + /* + * Since kvmtool doesn't really have any concept of buses etc., + * there's nothing to register here. Just register RTAS. + */ + spapr_rtas_register("read-pci-config", rtas_read_pci_config); + spapr_rtas_register("write-pci-config", rtas_write_pci_config); + spapr_rtas_register("ibm,read-pci-config", rtas_ibm_read_pci_config); + spapr_rtas_register("ibm,write-pci-config", rtas_ibm_write_pci_config); + + phb.buid = buid; + phb.mem_addr = mem_win_addr; + phb.mem_size = mem_win_size; + phb.io_addr = io_win_addr; + phb.io_size = io_win_size; + + kvm->phb = &phb; +} + +static uint32_t bar_to_ss(unsigned long bar) +{ + if ((bar & PCI_BASE_ADDRESS_SPACE) == + PCI_BASE_ADDRESS_SPACE_IO) + return SS_IO; + else if (bar & PCI_BASE_ADDRESS_MEM_TYPE_64) + return SS_M64; + else + return SS_M32; +} + +static unsigned long bar_to_addr(unsigned long bar) +{ + if ((bar & PCI_BASE_ADDRESS_SPACE) == + PCI_BASE_ADDRESS_SPACE_IO) + return bar & PCI_BASE_ADDRESS_IO_MASK; + else + return bar & PCI_BASE_ADDRESS_MEM_MASK; +} + +int spapr_populate_pci_devices(struct kvm *kvm, + uint32_t xics_phandle, + void *fdt) +{ + int bus_off, node_off = 0, devid, fn, i, n, devices; + char nodename[256]; + struct { + uint32_t hi; + uint64_t addr; + uint64_t size; + } __attribute__((packed)) reg[PCI_NUM_REGIONS + 1], + assigned_addresses[PCI_NUM_REGIONS]; + uint32_t bus_range[] = { cpu_to_be32(0), cpu_to_be32(0xff) }; + struct { + uint32_t hi; + uint64_t child; + uint64_t parent; + uint64_t size; + } __attribute__((packed)) ranges[] = { + { + cpu_to_be32(b_ss(1)), cpu_to_be64(0), + cpu_to_be64(phb.io_addr), + cpu_to_be64(phb.io_size), + }, + { + cpu_to_be32(b_ss(2)), cpu_to_be64(0), + cpu_to_be64(phb.mem_addr), + cpu_to_be64(phb.mem_size), + }, + }; + uint64_t bus_reg[] = { cpu_to_be64(phb.buid), 0 }; + uint32_t interrupt_map_mask[] = { + cpu_to_be32(b_ddddd(-1)|b_fff(-1)), 0x0, 0x0, 0x0}; + uint32_t interrupt_map[SPAPR_PCI_NUM_LSI][7]; + + /* Start populating the FDT */ + sprintf(nodename, "pci@%" PRIx64, phb.buid); + bus_off = fdt_add_subnode(fdt, 0, nodename); + if (bus_off < 0) { + die("error making bus subnode, %s\n", fdt_strerror(bus_off)); + return bus_off; + } + + /* Write PHB properties */ + _FDT(fdt_setprop_string(fdt, bus_off, "device_type", "pci")); + _FDT(fdt_setprop_string(fdt, bus_off, "compatible", "IBM,Logical_PHB")); + _FDT(fdt_setprop_cell(fdt, bus_off, "#address-cells", 0x3)); + _FDT(fdt_setprop_cell(fdt, bus_off, "#size-cells", 0x2)); + _FDT(fdt_setprop_cell(fdt, bus_off, "#interrupt-cells", 0x1)); + _FDT(fdt_setprop(fdt, bus_off, "used-by-rtas", NULL, 0)); + _FDT(fdt_setprop(fdt, bus_off, "bus-range", &bus_range, sizeof(bus_range))); + _FDT(fdt_setprop(fdt, bus_off, "ranges", &ranges, sizeof(ranges))); + _FDT(fdt_setprop(fdt, bus_off, "reg", &bus_reg, sizeof(bus_reg))); + _FDT(fdt_setprop(fdt, bus_off, "interrupt-map-mask", + &interrupt_map_mask, sizeof(interrupt_map_mask))); + + /* Populate PCI devices and allocate IRQs */ + devices = 0; + + for (devid = 0; devid < PCI_MAX_DEVICES; devid++) { + uint32_t *irqmap = interrupt_map[devices]; + struct pci_device_header *hdr = pci__find_dev(devid); + + if (!hdr) + continue; + + fn = 0; /* kvmtool doesn't yet do multifunction devices */ + + sprintf(nodename, "pci@%u,%u", devid, fn); + + /* Allocate interrupt from the map */ + if (devid > SPAPR_PCI_NUM_LSI) { + die("Unexpected behaviour in spapr_populate_pci_devices," + "wrong devid %u\n", devid); + } + irqmap[0] = cpu_to_be32(b_ddddd(devid)|b_fff(fn)); + irqmap[1] = 0; + irqmap[2] = 0; + irqmap[3] = 0; + irqmap[4] = cpu_to_be32(xics_phandle); + /* + * This is nasty; the PCI devs are set up such that their own + * header's irq_line indicates the direct XICS IRQ number to + * use. There REALLY needs to be a hierarchical system in place + * to 'raise' an IRQ on the bridge which indexes/looks up which + * XICS IRQ to fire. + */ + irqmap[5] = cpu_to_be32(hdr->irq_line); + irqmap[6] = cpu_to_be32(0x8); + + /* Add node to FDT */ + node_off = fdt_add_subnode(fdt, bus_off, nodename); + if (node_off < 0) { + die("error making node subnode, %s\n", fdt_strerror(bus_off)); + return node_off; + } + + _FDT(fdt_setprop_cell(fdt, node_off, "vendor-id", + le16_to_cpu(hdr->vendor_id))); + _FDT(fdt_setprop_cell(fdt, node_off, "device-id", + le16_to_cpu(hdr->device_id))); + _FDT(fdt_setprop_cell(fdt, node_off, "revision-id", + hdr->revision_id)); + _FDT(fdt_setprop_cell(fdt, node_off, "class-code", + hdr->class[0] | (hdr->class[1] << 8) | (hdr->class[2] << 16))); + _FDT(fdt_setprop_cell(fdt, node_off, "subsystem-id", + le16_to_cpu(hdr->subsys_id))); + _FDT(fdt_setprop_cell(fdt, node_off, "subsystem-vendor-id", + le16_to_cpu(hdr->subsys_vendor_id))); + + /* Config space region comes first */ + reg[0].hi = cpu_to_be32( + b_n(0) | + b_p(0) | + b_t(0) | + b_ss(SS_CONFIG) | + b_bbbbbbbb(0) | + b_ddddd(devid) | + b_fff(fn)); + reg[0].addr = 0; + reg[0].size = 0; + + n = 0; + /* Six BARs, no ROM supported, addresses are 32bit */ + for (i = 0; i < 6; ++i) { + if (0 == hdr->bar[i]) { + continue; + } + + reg[n+1].hi = cpu_to_be32( + b_n(0) | + b_p(0) | + b_t(0) | + b_ss(bar_to_ss(le32_to_cpu(hdr->bar[i]))) | + b_bbbbbbbb(0) | + b_ddddd(devid) | + b_fff(fn) | + b_rrrrrrrr(bars[i])); + reg[n+1].addr = 0; + reg[n+1].size = cpu_to_be64(hdr->bar_size[i]); + + assigned_addresses[n].hi = cpu_to_be32( + b_n(1) | + b_p(0) | + b_t(0) | + b_ss(bar_to_ss(le32_to_cpu(hdr->bar[i]))) | + b_bbbbbbbb(0) | + b_ddddd(devid) | + b_fff(fn) | + b_rrrrrrrr(bars[i])); + + /* + * Writing zeroes to assigned_addresses causes the guest kernel to + * reassign BARs + */ + assigned_addresses[n].addr = cpu_to_be64(bar_to_addr(le32_to_cpu(hdr->bar[i]))); + assigned_addresses[n].size = reg[n+1].size; + + ++n; + } + _FDT(fdt_setprop(fdt, node_off, "reg", reg, sizeof(reg[0])*(n+1))); + _FDT(fdt_setprop(fdt, node_off, "assigned-addresses", + assigned_addresses, + sizeof(assigned_addresses[0])*(n))); + _FDT(fdt_setprop_cell(fdt, node_off, "interrupts", + hdr->irq_pin)); + + /* We don't set ibm,dma-window property as we don't have an IOMMU. */ + + ++devices; + } + + /* Write interrupt map */ + _FDT(fdt_setprop(fdt, bus_off, "interrupt-map", &interrupt_map, + devices * sizeof(interrupt_map[0]))); + + return 0; +} diff --git a/powerpc/spapr_pci.h b/powerpc/spapr_pci.h new file mode 100644 index 0000000..48b221c --- /dev/null +++ b/powerpc/spapr_pci.h @@ -0,0 +1,57 @@ +/* + * SPAPR PHB definitions + * + * Modifications by Matt Evans , IBM Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. + */ + +#ifndef SPAPR_PCI_H +#define SPAPR_PCI_H + +#include "kvm/kvm.h" +#include "spapr.h" +#include + +/* With XICS, we can easily accomodate 1 IRQ per PCI device. */ + +#define SPAPR_PCI_NUM_LSI 256 + +struct spapr_phb { + uint64_t buid; + uint64_t mem_addr; + uint64_t mem_size; + uint64_t io_addr; + uint64_t io_size; +}; + +void spapr_create_phb(struct kvm *kvm, + const char *busname, uint64_t buid, + uint64_t mem_win_addr, uint64_t mem_win_size, + uint64_t io_win_addr, uint64_t io_win_size); + +int spapr_populate_pci_devices(struct kvm *kvm, + uint32_t xics_phandle, + void *fdt); + +static inline bool spapr_phb_mmio(struct kvm *kvm, u64 phys_addr, u8 *data, u32 len, u8 is_write) +{ + if ((phys_addr >= SPAPR_PCI_IO_WIN_ADDR) && + (phys_addr < SPAPR_PCI_IO_WIN_ADDR + + SPAPR_PCI_IO_WIN_SIZE)) { + return kvm__emulate_io(kvm, phys_addr - SPAPR_PCI_IO_WIN_ADDR, + data, is_write ? KVM_EXIT_IO_OUT : + KVM_EXIT_IO_IN, + len, 1); + } else if ((phys_addr >= SPAPR_PCI_MEM_WIN_ADDR) && + (phys_addr < SPAPR_PCI_MEM_WIN_ADDR + + SPAPR_PCI_MEM_WIN_SIZE)) { + return kvm__emulate_mmio(kvm, phys_addr - SPAPR_PCI_MEM_WIN_ADDR, + data, len, is_write); + } + return false; +} + +#endif