[RFC PATCH 1/4] PCI: Provide generic ECAM mapping functions

Jayachandran C jchandra at broadcom.com
Thu Mar 17 13:18:30 PDT 2016


Add config option PCI_GENERIC_ECAM and file drivers/pci/ecam.c to
provide generic functions to access memory mapped PCI config space.

The API defines 'struct pci_config_window' to hold the mappings.
The function pci_generic_map_config() is provided to allocate the
struct, request the memory region, and do the ioremap() calls
needed to setup the mapping. pci_generic_unmap_config() is provided
to delete a mapping.

A helper function pci_generic_map_bus() is also provided to be used
to implement pci_ops map_bus method.

Signed-off-by: Jayachandran C <jchandra at broadcom.com>
---
 drivers/pci/Kconfig  |   3 ++
 drivers/pci/Makefile |   2 +
 drivers/pci/ecam.c   | 127 +++++++++++++++++++++++++++++++++++++++++++++++++++
 include/linux/pci.h  |  10 ++++
 4 files changed, 142 insertions(+)
 create mode 100644 drivers/pci/ecam.c

diff --git a/drivers/pci/Kconfig b/drivers/pci/Kconfig
index 209292e..e930d62 100644
--- a/drivers/pci/Kconfig
+++ b/drivers/pci/Kconfig
@@ -83,6 +83,9 @@ config HT_IRQ
 config PCI_ATS
 	bool
 
+config PCI_GENERIC_ECAM
+	bool
+
 config PCI_IOV
 	bool "PCI IOV support"
 	depends on PCI
diff --git a/drivers/pci/Makefile b/drivers/pci/Makefile
index 2154092..810aec8 100644
--- a/drivers/pci/Makefile
+++ b/drivers/pci/Makefile
@@ -55,6 +55,8 @@ obj-$(CONFIG_PCI_SYSCALL) += syscall.o
 
 obj-$(CONFIG_PCI_STUB) += pci-stub.o
 
+obj-$(CONFIG_PCI_GENERIC_ECAM) += ecam.o
+
 obj-$(CONFIG_XEN_PCIDEV_FRONTEND) += xen-pcifront.o
 
 obj-$(CONFIG_OF) += of.o
diff --git a/drivers/pci/ecam.c b/drivers/pci/ecam.c
new file mode 100644
index 0000000..6a63901
--- /dev/null
+++ b/drivers/pci/ecam.c
@@ -0,0 +1,127 @@
+/*
+ * Copyright 2016 Broadcom
+ *
+ * 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 (the "GPL").
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License version 2 (GPLv2) for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * version 2 (GPLv2) along with this source code.
+ */
+
+#include <linux/device.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <linux/slab.h>
+
+/*
+ * On 64 bit systems, we do a single ioremap for the whole config space
+ * since we have enough virtual address range available. On 32 bit, do an
+ * ioremap per bus.
+ */
+static const bool per_bus_mapping = !config_enabled(CONFIG_64BIT);
+
+/*
+ * struct to hold the mappings of a config space window. This
+ * will be allocated with enough entries in win[] to hold all
+ * the mappings for the bus range.
+ */
+struct pci_config_window {
+	phys_addr_t		cfgaddr;
+	u8			bus_start;
+	u8			bus_end;
+	u8			bus_shift;
+	u8			devfn_shift;
+	void __iomem		*win[0];
+};
+
+/*
+ * helper function provided to implement the pci_ops ->map_bus method
+ */
+void __iomem *pci_generic_map_bus(struct pci_config_window *cfg,
+		 unsigned int busn, unsigned int devfn, int where)
+{
+	void __iomem *base;
+
+	if (busn < cfg->bus_start || busn > cfg->bus_end)
+		return NULL;
+
+	busn -= cfg->bus_start;
+	if (per_bus_mapping)
+		base = cfg->win[busn];
+	else
+		base = cfg->win[0] + (busn << cfg->bus_shift);
+	return base + (devfn << cfg->devfn_shift) + where;
+}
+
+/*
+ * Create a PCI config space window
+ *  - reserve mem region
+ *  - alloc struct pci_config_window with space for all mappings
+ *  - ioremap the config space
+ */
+struct pci_config_window *pci_generic_map_config(phys_addr_t addr,
+	u8 bus_start, u8 bus_end, u8 bus_shift, u8 devfn_shift)
+{
+	struct pci_config_window *cfg;
+	unsigned int bus_range, bsz, mapsz;
+	int i, nidx;
+
+	if (bus_end < bus_start)
+		return ERR_PTR(-EINVAL);
+
+	bus_range = bus_end - bus_start + 1;
+	bsz = 1 << bus_shift;
+	nidx = per_bus_mapping ? bus_range : 1;
+	mapsz = per_bus_mapping ? bsz : bus_range * bsz;
+	cfg = kzalloc(sizeof(*cfg) + nidx * sizeof(cfg->win[0]), GFP_KERNEL);
+	if (!cfg)
+		return ERR_PTR(-ENOMEM);
+
+	cfg->bus_start = bus_start;
+	cfg->bus_end = bus_end;
+	cfg->bus_shift = bus_shift;
+	cfg->devfn_shift = devfn_shift;
+
+	if (!request_mem_region(addr, bus_range * bsz, "Configuration Space"))
+		goto err_exit;
+
+	/* cfgaddr has to be set after request_mem_region */
+	cfg->cfgaddr = addr;
+
+	for (i = 0; i < nidx; i++) {
+		cfg->win[i] = ioremap(addr + i * mapsz, mapsz);
+		if (!cfg->win[i])
+			goto err_exit;
+	}
+	return cfg;
+
+err_exit:
+	pci_generic_unmap_config(cfg);
+	return ERR_PTR(-ENOMEM);
+}
+
+/*
+ * Free a config space mapping
+ */
+void pci_generic_unmap_config(struct pci_config_window *cfg)
+{
+	unsigned int bus_range;
+	int i, nidx;
+
+	bus_range = cfg->bus_end - cfg->bus_start + 1;
+	nidx = per_bus_mapping ? bus_range : 1;
+	for (i = 0; i < nidx; i++)
+		if (cfg->win[i])
+			iounmap(cfg->win[i]);
+	if (cfg->cfgaddr)
+		release_mem_region(cfg->cfgaddr, bus_range << cfg->bus_shift);
+	kfree(cfg);
+}
diff --git a/include/linux/pci.h b/include/linux/pci.h
index 3df9e37..33c46bc 100644
--- a/include/linux/pci.h
+++ b/include/linux/pci.h
@@ -2013,6 +2013,16 @@ static inline bool pci_ari_enabled(struct pci_bus *bus)
 	return bus->self && bus->self->ari_enabled;
 }
 
+/* Generic ECAM mapping API */
+#ifdef CONFIG_PCI_GENERIC_ECAM
+struct pci_config_window;
+void __iomem *pci_generic_map_bus(struct pci_config_window *cfg,
+	 unsigned int busn, unsigned int devfn, int where);
+struct pci_config_window *pci_generic_map_config(phys_addr_t addr,
+	u8 bus_start, u8 bus_end, u8 bus_shift, u8 devfn_shift);
+void pci_generic_unmap_config(struct pci_config_window *cfg);
+#endif
+
 /* provide the legacy pci_dma_* API */
 #include <linux/pci-dma-compat.h>
 
-- 
1.9.1




More information about the linux-arm-kernel mailing list