[PATCH v2 1/5] arm/pci: Probe Open Firmware Device Tree

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



This (and following PCI bus scan) commit is a glimpse of
Linux implementation with almost all PCI data structures
omitted - none of pci_host_bridge, pci_bus, pci_dev and
pci_bus_resource are adopted. While gen_pci is just name-
compatible and is basically a root pointer to underlying
data describing PCI bus hierarchy in a minimalistic way.

The Device Tree is expected to conform to PCI Bus Binding
to Open Firmware with any deviation treated as a fatal
errors.

Cc: Thomas Huth <thuth@xxxxxxxxxx>
Cc: Andrew Jones <drjones@xxxxxxxxxx>
Signed-off-by: Alexander Gordeev <agordeev@xxxxxxxxxx>
---
 arm/pci-test.c               |  21 +++++
 config/config-arm-common.mak |   5 +-
 lib/pci-host-generic.c       | 219 +++++++++++++++++++++++++++++++++++++++++++
 lib/pci-host-generic.h       |  73 +++++++++++++++
 lib/pci.h                    |   3 +
 5 files changed, 320 insertions(+), 1 deletion(-)
 create mode 100644 arm/pci-test.c
 create mode 100644 lib/pci-host-generic.c
 create mode 100644 lib/pci-host-generic.h

diff --git a/arm/pci-test.c b/arm/pci-test.c
new file mode 100644
index 0000000..db7d048
--- /dev/null
+++ b/arm/pci-test.c
@@ -0,0 +1,21 @@
+/*
+ * PCI bus operation test
+ *
+ * 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 <pci.h>
+
+int main(void)
+{
+	int ret;
+
+	ret = pci_probe();
+	report("PCI device tree probing", ret);
+
+	pci_shutdown();
+
+	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/pci-host-generic.c b/lib/pci-host-generic.c
new file mode 100644
index 0000000..9bc6642
--- /dev/null
+++ b/lib/pci-host-generic.c
@@ -0,0 +1,219 @@
+/*
+ * PCI bus operation test
+ *
+ * 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>
+
+struct gen_pci *gen_pci = NULL;
+
+static struct gen_pci *get_pci(void)
+{
+	return gen_pci;
+}
+
+static void set_pci(struct gen_pci *pci)
+{
+	gen_pci = pci;
+}
+
+static char *addr_space_desc[] = {
+	[PCI_RES_TYPE_CONF]		= "CONF",
+	[PCI_RES_TYPE_IO]		= "IO",
+	[PCI_RES_TYPE_MEM32]		= "MEM32",
+	[PCI_RES_TYPE_MEM64]		= "MEM64",
+	[PCI_RES_TYPE_PREFMEM32]	= "MEM32/p",
+	[PCI_RES_TYPE_PREFMEM64]	= "MEM64/p"
+};
+
+static pci_res_type_t flags_to_type(u32 of_flags)
+{
+	return ((of_flags & 0x40000000) >> 28) | ((of_flags >> 24) & 0x03);
+}
+
+static u32 from_fdt32(fdt32_t val)
+{
+	return fdt32_to_cpu(val);
+}
+
+static u64 from_fdt64(fdt32_t high, fdt32_t low)
+{
+	return ((u64)fdt32_to_cpu(high) << 32) | fdt32_to_cpu(low);
+}
+
+static void pci_host_addr_space_init(struct pci_addr_space as[], int nr_as,
+				     fdt32_t rcells[], int nr_rcells)
+{
+	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 = from_fdt32(rcells[0]);
+		as->pci_range.start = from_fdt64(rcells[1], rcells[2]);
+
+		if (nr_rcells == 6) {
+			as->cpu_range.start = from_fdt32(rcells[3]);
+			as->cpu_range.size  = from_fdt64(rcells[4], rcells[5]);
+		} else {
+			as->cpu_range.start = from_fdt64(rcells[3], rcells[4]);
+			as->cpu_range.size  = from_fdt64(rcells[5], rcells[6]);
+		}
+
+		as->pci_range.size = as->cpu_range.size;
+		rcells += nr_rcells;
+	}
+}
+
+static void gen_pci_print(struct gen_pci *pci)
+{
+	struct pci_addr_space *as = &pci->addr_space[0];
+	int i;
+
+	printf("PCIe start %016llx size %016llx "
+	       "bus %02x bus_max %02x #spaces %d\n\n",
+		pci->cpu_range.start, pci->cpu_range.size,
+		pci->bus, pci->bus_max, pci->nr_addr_spaces);
+
+	for (i = 0; i < pci->nr_addr_spaces; i++, as++) {
+		printf("%s address space:\n"
+		       "CPU: start %016llx size %016llx\n"
+		       "PCI: start %016llx size %016llx\n\n",
+			addr_space_desc[flags_to_type(as->of_flags)],
+			as->cpu_range.start, as->cpu_range.size,
+		        as->pci_range.start, as->pci_range.size);
+	}
+}
+
+/*
+ * Probe DT for a generic PCI host controller
+ * See kernel Documentation/devicetree/bindings/pci/host-generic-pci.txt
+ * and function gen_pci_probe() in drivers/pci/host/pci-host-generic.c
+ */
+static struct gen_pci *gen_pci_probe(void)
+{
+	struct gen_pci *pci;
+	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()) {
+		printf("No device tree found");
+		return NULL;
+	}
+
+	dt_bus_init_defaults(&dt_bus);
+	dt_device_init(&dt_dev, &dt_bus, NULL);
+
+	node = fdt_path_offset(fdt, "/");
+	assert(node >= 0);
+
+	ret = dt_get_nr_cells(node, &nac_root, &nsc_root);
+	assert(ret == 0);
+	assert(nac_root == 1 || nac_root == 2);
+
+	node = fdt_subnode_offset(fdt, node, "pcie");
+	if (node == -FDT_ERR_NOTFOUND) {
+		printf("No PCIe controller found");
+		return NULL;
+	}
+	assert(node >= 0);
+
+	prop = fdt_get_property(fdt, node, "device_type", &len);
+	if (!prop || len != 4 || strcmp((char*)prop->data, "pci")) {
+		printf("PCIe controller device_type is not \"pci\"");
+		return NULL;
+	}
+
+	ret = fdt_node_check_compatible(fdt, node, "pci-host-ecam-generic");
+	assert(ret >= 0);
+	if (ret != 0) {
+		printf("PCIe controller is not ECAM compatible");
+		return NULL;
+	}
+
+	dt_device_bind_node(&dt_dev, node);
+	ret = dt_pbus_get_base(&dt_dev, &base);
+	assert(ret == 0);
+
+	prop = fdt_get_property(fdt, node, "bus-range", &len);
+	if (prop == NULL) {
+		assert(len == -FDT_ERR_NOTFOUND);
+		bus		= 0x00;
+		bus_max		= 0xff;
+	} else {
+		fdt32_t *data	= (fdt32_t*)prop->data;
+		bus		= fdt32_to_cpu(data[0]);
+		bus_max		= fdt32_to_cpu(data[1]);
+		assert(bus <= bus_max);
+	}
+	assert(bus_max < base.size / PCI_ECAM_BUS_SIZE);
+
+	ret = dt_get_nr_cells(node, &nac, &nsc);
+	assert(ret == 0);
+	assert(nac == 3 && nsc == 2);
+
+	prop = fdt_get_property(fdt, node, "ranges", &len);
+	assert(prop != NULL);
+
+	nr_range_cells = nac + nsc + nac_root;
+	nr_addr_spaces = (len / 4) / nr_range_cells;
+
+	pci = malloc(sizeof(*pci) +
+		     sizeof(pci->addr_space[0]) * nr_addr_spaces);
+	assert(pci != NULL);
+
+	pci->cpu_range.start	= base.addr;
+	pci->cpu_range.size	= base.size;
+	pci->bus		= bus;
+	pci->bus_max		= bus_max;
+	pci->nr_addr_spaces	= nr_addr_spaces;
+
+	pci_host_addr_space_init(&pci->addr_space[0], nr_addr_spaces,
+				 (fdt32_t*)prop->data, nr_range_cells);
+	gen_pci_print(pci);
+
+	return pci;
+}
+
+bool pci_probe(void)
+{
+	struct gen_pci *pci = get_pci();
+
+	assert(pci == NULL);
+	pci = gen_pci_probe();
+	set_pci(pci);
+
+	return (pci != NULL);
+}
+
+void pci_shutdown(void)
+{
+	struct gen_pci *pci = get_pci();
+
+	set_pci(NULL);
+	free(pci);
+}
diff --git a/lib/pci-host-generic.h b/lib/pci-host-generic.h
new file mode 100644
index 0000000..f4ae3e4
--- /dev/null
+++ b/lib/pci-host-generic.h
@@ -0,0 +1,73 @@
+#ifndef PCI_HOST_GENERIC_H
+#define PCI_HOST_GENERIC_H
+/*
+ * PCI host bridge supporting structures and constants
+ *
+ * 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"
+
+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;
+	u32				of_flags;
+};
+
+/*
+ * See drivers/pci/host/pci-host-generic.c for the idea of what gen_pci
+ * structure is. Although it brings the very same semantics as Linux,
+ * it completely misses the kernel's data design in a bid to keep it as
+ * simple as possible.
+ */
+struct gen_pci {
+	struct pci_addr_range		cpu_range;
+	int				bus;
+	int				bus_max;
+	int				nr_addr_spaces;
+	struct pci_addr_space		addr_space[];
+};
+
+typedef enum pci_res_type {
+	PCI_RES_TYPE_CONF		= 0,
+	PCI_RES_TYPE_IO			= 1,
+	PCI_RES_TYPE_MEM32		= 2,
+	PCI_RES_TYPE_MEM64		= 3,
+	PCI_RES_TYPE_PREFMEM32		= 6,
+	PCI_RES_TYPE_PREFMEM64		= 7
+} pci_res_type_t;
+
+/*
+ * The following constants are derived from Linux, see this source:
+ *
+ *         drivers/pci/host/pci-host-generic.c
+ *                 struct gen_pci_cfg_bus_ops::bus_shift
+ *                 int gen_pci_parse_map_cfg_windows(struct gen_pci *pci)
+ *
+ * Documentation/devicetree/bindings/pci/host-generic-pci.txt excerpt:
+ *
+ * Configuration Space is assumed to be memory-mapped (as opposed to being
+ * accessed via an ioport) and laid out with a direct correspondence to the
+ * geography of a PCI bus address by concatenating the various components to
+ * form an offset.
+ *
+ * For CAM, this 24-bit offset is:
+ *
+ *         cfg_offset(bus, device, function, register) =
+ *                    bus << 16 | device << 11 | function << 8 | register
+ *
+ * Whilst ECAM extends this by 4 bits to accommodate 4k of function space:
+ *
+ *         cfg_offset(bus, device, function, register) =
+ *                    bus << 20 | device << 15 | function << 12 | register
+ *
+ */
+#define PCI_ECAM_BUS_SIZE	(1 << 20)
+
+#endif
diff --git a/lib/pci.h b/lib/pci.h
index 9160cfb..80d0d04 100644
--- a/lib/pci.h
+++ b/lib/pci.h
@@ -39,4 +39,7 @@ struct pci_test_dev_hdr {
 	uint8_t  name[];
 };
 
+extern bool pci_probe(void);
+extern void pci_shutdown(void);
+
 #endif /* PCI_H */
-- 
1.8.3.1

--
To unsubscribe from this list: send the line "unsubscribe kvm" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html



[Index of Archives]     [KVM ARM]     [KVM ia64]     [KVM ppc]     [Virtualization Tools]     [Spice Development]     [Libvirt]     [Libvirt Users]     [Linux USB Devel]     [Linux Audio Users]     [Yosemite Questions]     [Linux Kernel]     [Linux SCSI]     [XFree86]
  Powered by Linux