]> git.karo-electronics.de Git - karo-tx-linux.git/commitdiff
kvm tools: Add PPC64 PCI Host Bridge
authorMatt Evans <matt@ozlabs.org>
Sat, 4 Feb 2012 08:07:24 +0000 (19:07 +1100)
committerPekka Enberg <penberg@kernel.org>
Sat, 4 Feb 2012 09:20:07 +0000 (11:20 +0200)
This provides the PCI bridge, definitions for the address layout of the windows
and wires in IRQs.  Once PCI devices are all registered, they are enumerated and
DT nodes generated for each.

Signed-off-by: Matt Evans <matt@ozlabs.org>
Signed-off-by: Alexey Kardashevskiy <aik@ozlabs.ru>
Signed-off-by: David Gibson <david@gibson.dropbear.id.au>
Signed-off-by: Pekka Enberg <penberg@kernel.org>
tools/kvm/powerpc/include/kvm/kvm-arch.h
tools/kvm/powerpc/irq.c
tools/kvm/powerpc/kvm.c
tools/kvm/powerpc/spapr.h
tools/kvm/powerpc/spapr_pci.c [new file with mode: 0644]
tools/kvm/powerpc/spapr_pci.h [new file with mode: 0644]

index 7235067ac6e8479a61ed70c5b0bd3c34bd7f198a..6dd708278284c8cdf0789d4fc32aaa4cb3376ea9 100644 (file)
@@ -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 */
index f8f12e56ea1ec9f905f9699e8e8d8712f0fe485e..7da40122bf2f34862173f5a80b250b6bf0bb5838 100644 (file)
 #include <stddef.h>
 #include <stdlib.h>
 
+#include "kvm/pci.h"
+
 #include "xics.h"
+#include "spapr_pci.h"
 
 #define XICS_IRQS               1024
 
  * 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;
 }
 
index 815108c8d492be6881b937c8d4fa53d67b6ebd9d..cbc0d8fbbb650fef50a6bdb6c844e55215fd047a 100644 (file)
@@ -18,6 +18,7 @@
 
 #include "spapr.h"
 #include "spapr_hvcons.h"
+#include "spapr_pci.h"
 
 #include <linux/kvm.h>
 
@@ -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));
 }
index 1271daa2db66dcb7ce1aa07fe7f7ad71bbe96d00..0537f881c0e408eb4b9f137b972a66237a939bab 100644 (file)
@@ -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/tools/kvm/powerpc/spapr_pci.c b/tools/kvm/powerpc/spapr_pci.c
new file mode 100644 (file)
index 0000000..f9d29f0
--- /dev/null
@@ -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 <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/util.h"
+#include "kvm/pci.h"
+#include "libfdt.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
+
+/* 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/tools/kvm/powerpc/spapr_pci.h b/tools/kvm/powerpc/spapr_pci.h
new file mode 100644 (file)
index 0000000..48b221c
--- /dev/null
@@ -0,0 +1,57 @@
+/*
+ * SPAPR PHB definitions
+ *
+ * Modifications by 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.
+ */
+
+#ifndef SPAPR_PCI_H
+#define SPAPR_PCI_H
+
+#include "kvm/kvm.h"
+#include "spapr.h"
+#include <inttypes.h>
+
+/* 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