blob: 7be44d950acbe1b9e366b3c2a59cfeb02722a104 [file] [log] [blame]
/*
* 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;
}