Cc: Andrew Jones <drjones@xxxxxxxxxx> Signed-off-by: Alexander Gordeev <agordeev@xxxxxxxxxx> --- arm/pci-test.c | 25 +++++++ config/config-arm-common.mak | 5 +- lib/alloc.c | 3 - lib/libcflat.h | 3 + lib/pci-host-generic.c | 155 +++++++++++++++++++++++++++++++++++++++++++ lib/pci-host-generic.h | 26 ++++++++ lib/pci.h | 13 ++++ 7 files changed, 226 insertions(+), 4 deletions(-) create mode 100644 arm/pci-test.c create mode 100644 lib/pci-host-generic.c create mode 100644 lib/pci-host-generic.h create mode 100644 lib/pci.h diff --git a/arm/pci-test.c b/arm/pci-test.c new file mode 100644 index 0000000..8629c89 --- /dev/null +++ b/arm/pci-test.c @@ -0,0 +1,25 @@ +/* + * PCI bus operation test + * + * Copyright (C) 2015, Red Hat Inc, Andrew Jones <drjones@xxxxxxxxxx> + * Copyright (C) 2015, Red Hat Inc, Alexander Gordeev <agordeev@xxxxxxxxxx> + * + * This work is licensed under the terms of the GNU LGPL, version 2. + */ +#include <pci.h> + +int main(void) +{ + struct pci *pci; + + pci = pci_dt_probe(); + + report("PCI device tree probing", pci); + if (!pci) + goto done; + + pci_shutdown(pci); + +done: + return report_summary(); +} diff --git a/config/config-arm-common.mak b/config/config-arm-common.mak index 698555d..06ad346 100644 --- a/config/config-arm-common.mak +++ b/config/config-arm-common.mak @@ -11,7 +11,8 @@ endif tests-common = \ $(TEST_DIR)/selftest.flat \ - $(TEST_DIR)/spinlock-test.flat + $(TEST_DIR)/spinlock-test.flat \ + $(TEST_DIR)/pci-test.flat all: test_cases @@ -29,6 +30,7 @@ include config/asm-offsets.mak cflatobjs += lib/alloc.o cflatobjs += lib/devicetree.o +cflatobjs += lib/pci-host-generic.o cflatobjs += lib/virtio.o cflatobjs += lib/virtio-mmio.o cflatobjs += lib/chr-testdev.o @@ -70,3 +72,4 @@ test_cases: $(generated_files) $(tests-common) $(tests) $(TEST_DIR)/selftest.elf: $(cstart.o) $(TEST_DIR)/selftest.o $(TEST_DIR)/spinlock-test.elf: $(cstart.o) $(TEST_DIR)/spinlock-test.o +$(TEST_DIR)/pci-test.elf: $(cstart.o) $(TEST_DIR)/pci-test.o diff --git a/lib/alloc.c b/lib/alloc.c index ad67614..2f60145 100644 --- a/lib/alloc.c +++ b/lib/alloc.c @@ -7,9 +7,6 @@ #include "asm/spinlock.h" #include "asm/io.h" -#define MIN(a, b) ((a) < (b) ? (a) : (b)) -#define MAX(a, b) ((a) > (b) ? (a) : (b)) - #define PHYS_ALLOC_NR_REGIONS 256 struct phys_alloc_region { diff --git a/lib/libcflat.h b/lib/libcflat.h index 9747ccd..2cb167c 100644 --- a/lib/libcflat.h +++ b/lib/libcflat.h @@ -76,4 +76,7 @@ do { \ abort(); \ } while (0) +#define MIN(a, b) ((a) < (b) ? (a) : (b)) +#define MAX(a, b) ((a) > (b) ? (a) : (b)) + #endif diff --git a/lib/pci-host-generic.c b/lib/pci-host-generic.c new file mode 100644 index 0000000..62b5662 --- /dev/null +++ b/lib/pci-host-generic.c @@ -0,0 +1,155 @@ +/* + * Adapated from + * drivers/pci/host/pci-host-generic.c + * + * Copyright (C) 2015, Red Hat Inc, Andrew Jones <drjones@xxxxxxxxxx> + * Copyright (C) 2016, Red Hat Inc, Alexander Gordeev <agordeev@xxxxxxxxxx> + * + * This work is licensed under the terms of the GNU LGPL, version 2. + */ +#include "libcflat.h" +#include "devicetree.h" +#include "asm/io.h" +#include "pci.h" +#include "pci-host-generic.h" +#include <linux/pci_regs.h> + +static void pci_host_addr_space_init(struct pci_addr_space as[], int nr_as, + u32 cells[], int nr_range_cells) +{ + int i; + + /* + * The PCI binding claims the numerical representation of a PCI + * address consists of three cells, encoded as follows: + * + * phys.hi cell: npt000ss bbbbbbbb dddddfff rrrrrrrr + * phys.mid cell: hhhhhhhh hhhhhhhh hhhhhhhh hhhhhhhh + * phys.lo cell: llllllll llllllll llllllll llllllll + * + * PCI device bus address and flags are encoded into phys.high + * PCI 64 bit address is encoded into phys.mid and phys.low + */ + + for (i = 0; i < nr_as; i++, as++) { + as->of_flags = fdt32_to_cpu(cells[0]); + as->pci_range.start = fdt64_to_cpu(*((fdt64_t*)&cells[1])); + + if (nr_range_cells == 6) { + as->cpu_range.start = fdt32_to_cpu(cells[3]); + as->cpu_range.size = + fdt64_to_cpu(*((fdt64_t*)&cells[4])); + } else { + as->cpu_range.start = + fdt64_to_cpu(*((fdt64_t*)&cells[3]));; + as->cpu_range.size = + fdt64_to_cpu(*((fdt64_t*)&cells[5])); + } + + as->pci_range.size = as->cpu_range.size; + + cells += nr_range_cells; + } +} + +/* + * Probe DT for a generic PCI host controller + * See kernel Documentation/devicetree/bindings/pci/host-generic-pci.txt + */ +static struct pci_host_bridge *pci_host_bridge_probe(void) +{ + struct pci_host_bridge *host; + const void *fdt = dt_fdt(); + const struct fdt_property *prop; + struct dt_pbus_reg base; + struct dt_device dt_dev; + struct dt_bus dt_bus; + u32 bus, bus_max; + u32 nac, nsc, nac_root, nsc_root; + u32 nr_range_cells, nr_addr_spaces; + int ret, node, len; + + if (!dt_available()) + return NULL; + + dt_bus_init_defaults(&dt_bus); + dt_device_init(&dt_dev, &dt_bus, NULL); + + node = fdt_path_offset(fdt, "/"); + assert(node >= 0); + + assert(dt_get_nr_cells(node, &nac_root, &nsc_root) == 0); + assert(nac_root == 1 || nac_root == 2); + + node = fdt_subnode_offset(fdt, node, "pcie"); + if (node == -FDT_ERR_NOTFOUND) + return NULL; + assert(node >= 0); + + prop = fdt_get_property(fdt, node, "device_type", &len); + if (!prop || strcmp((char*)prop->data, "pci")) + return NULL; + + ret = fdt_node_check_compatible(fdt, node, "pci-host-ecam-generic"); + assert(ret >= 0); + if (ret != 0) + return NULL; + + dt_device_bind_node(&dt_dev, node); + assert(dt_pbus_get_base(&dt_dev, &base) == 0); + + prop = fdt_get_property(fdt, node, "bus-range", &len); + if (prop == NULL) { + assert(len != 0); + bus = 0x00; + bus_max = 0xff; + } else { + u32 *data = (u32*)prop->data; + bus = fdt32_to_cpu(data[0]); + bus_max = fdt32_to_cpu(data[1]); + } + bus_max = MIN(bus_max, (base.size / PCI_ECAM_BUS_SIZE) - 1); + + assert(dt_get_nr_cells(node, &nac, &nsc) == 0); + assert(nac == 3 && nsc == 2); + + assert(prop = fdt_get_property(fdt, node, "ranges", &len)); + + nr_range_cells = nac + nsc + nac_root; + nr_addr_spaces = (len / 4) / nr_range_cells; + + assert(host = malloc(sizeof(*host) + + sizeof(host->addr_space[0]) * nr_addr_spaces)); + + host->cpu_range.start = base.addr; + host->cpu_range.size = base.size; + host->bus = bus; + host->bus_max = bus_max; + host->nr_addr_spaces = nr_addr_spaces; + + pci_host_addr_space_init(&host->addr_space[0], nr_addr_spaces, + (u32*)prop->data, nr_range_cells); + + return host; +} + +struct pci *pci_dt_probe(void) +{ + struct pci_host_bridge *host; + struct pci *pci; + + host = pci_host_bridge_probe(); + if (!host) + return NULL; + + assert(pci = calloc(1, sizeof(*pci))); + pci->sysdata = host; + + return pci; +} + +void pci_shutdown(struct pci *pci) +{ + free(pci->sysdata); + free(pci); +} diff --git a/lib/pci-host-generic.h b/lib/pci-host-generic.h new file mode 100644 index 0000000..097ac2d --- /dev/null +++ b/lib/pci-host-generic.h @@ -0,0 +1,26 @@ +#ifndef PCI_HOST_GENERIC_H +#define PCI_HOST_GENERIC_H + +struct pci_addr_range { + phys_addr_t start; + phys_addr_t size; +}; + +struct pci_addr_space { + struct pci_addr_range cpu_range; + struct pci_addr_range pci_range; + phys_addr_t alloc; + u32 of_flags; +}; + +struct pci_host_bridge { + struct pci_addr_range cpu_range; + int bus; + int bus_max; + int nr_addr_spaces; + struct pci_addr_space addr_space[]; +}; + +#define PCI_ECAM_BUS_SIZE (1 << 20) + +#endif diff --git a/lib/pci.h b/lib/pci.h new file mode 100644 index 0000000..22b8e31 --- /dev/null +++ b/lib/pci.h @@ -0,0 +1,13 @@ +#ifndef PCI_H +#define PCI_H + +#include "alloc.h" + +struct pci { + void *sysdata; +}; + +extern struct pci *pci_dt_probe(void); +extern void pci_shutdown(struct pci *pci); + +#endif -- 1.8.3.1 _______________________________________________ kvmarm mailing list kvmarm@xxxxxxxxxxxxxxxxxxxxx https://lists.cs.columbia.edu/mailman/listinfo/kvmarm