| /* |
| * 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 <matt@ozlabs.org>, 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/devices.h" |
| #include "kvm/fdt.h" |
| #include "kvm/util.h" |
| #include "kvm/of_pci.h" |
| #include "kvm/pci.h" |
| |
| #include <linux/pci_regs.h> |
| #include <linux/byteorder.h> |
| |
| |
| /* #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 |
| |
| 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->arch.phb = &phb; |
| } |
| |
| static uint32_t bar_to_ss(unsigned long bar) |
| { |
| if ((bar & PCI_BASE_ADDRESS_SPACE) == |
| PCI_BASE_ADDRESS_SPACE_IO) |
| return OF_PCI_SS_IO; |
| else if (bar & PCI_BASE_ADDRESS_MEM_TYPE_64) |
| return OF_PCI_SS_M64; |
| else |
| return OF_PCI_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; |
| struct device_header *dev_hdr; |
| char nodename[256]; |
| struct of_pci_unit64_address { |
| u32 phys_hi; |
| u64 addr; |
| u64 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 of_pci_ranges_entry ranges[] = { |
| { |
| { |
| cpu_to_be32(of_pci_b_ss(1)), |
| cpu_to_be32(0), |
| cpu_to_be32(0), |
| }, |
| cpu_to_be64(phb.io_addr), |
| cpu_to_be64(phb.io_size), |
| }, |
| { |
| { |
| cpu_to_be32(of_pci_b_ss(2)), |
| cpu_to_be32(0), |
| cpu_to_be32(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(of_pci_b_ddddd(-1)|of_pci_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; |
| dev_hdr = device__first_dev(DEVICE_BUS_PCI); |
| while (dev_hdr) { |
| uint32_t *irqmap = interrupt_map[devices]; |
| struct pci_device_header *hdr = dev_hdr->data; |
| |
| if (!hdr) |
| continue; |
| |
| devid = dev_hdr->dev_num; |
| 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(of_pci_b_ddddd(devid)|of_pci_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].phys_hi = cpu_to_be32( |
| of_pci_b_n(0) | |
| of_pci_b_p(0) | |
| of_pci_b_t(0) | |
| of_pci_b_ss(OF_PCI_SS_CONFIG) | |
| of_pci_b_bbbbbbbb(0) | |
| of_pci_b_ddddd(devid) | |
| of_pci_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].phys_hi = cpu_to_be32( |
| of_pci_b_n(0) | |
| of_pci_b_p(0) | |
| of_pci_b_t(0) | |
| of_pci_b_ss(bar_to_ss(le32_to_cpu(hdr->bar[i]))) | |
| of_pci_b_bbbbbbbb(0) | |
| of_pci_b_ddddd(devid) | |
| of_pci_b_fff(fn) | |
| of_pci_b_rrrrrrrr(bars[i])); |
| reg[n+1].size = cpu_to_be64(pci__bar_size(hdr, i)); |
| reg[n+1].addr = 0; |
| |
| assigned_addresses[n].phys_hi = cpu_to_be32( |
| of_pci_b_n(1) | |
| of_pci_b_p(0) | |
| of_pci_b_t(0) | |
| of_pci_b_ss(bar_to_ss(le32_to_cpu(hdr->bar[i]))) | |
| of_pci_b_bbbbbbbb(0) | |
| of_pci_b_ddddd(devid) | |
| of_pci_b_fff(fn) | |
| of_pci_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; |
| dev_hdr = device__next_dev(dev_hdr); |
| } |
| |
| /* Write interrupt map */ |
| _FDT(fdt_setprop(fdt, bus_off, "interrupt-map", &interrupt_map, |
| devices * sizeof(interrupt_map[0]))); |
| |
| return 0; |
| } |