[Skiboot] [PATCH 1/2] PCI: Introduce config register filter

Gavin Shan gwshan at linux.vnet.ibm.com
Thu Sep 24 17:27:17 AEST 2015


We have to provide the emulated result for PCI config register
access on some devices to eleminate the gap between hardware and
software. One example would be the 0x28 (prefetchable memory window
upper 32-bits) of the root complex on Naples isn't writable. Linux
kernel relies on that to detect 64-bits window successfully.

This introduces config register filter to PCI device to eleminate
above gap. Each PCI device maintains a list of filters, which are
populated when the PCI device is initialized. When PCI config space
is accessed, the filter is searched to override the result from user
(write) or hardware (read) if necessary.

Reported-by: Vaibhav Jain <vaibhav at linux.vnet.ibm.com>
Suggested-by: Benjamin Herrenschmidt <benh at kernel.crashing.org>
Signed-off-by: Gavin Shan <gwshan at linux.vnet.ibm.com>
Tested-by: Vaibhav Jain <vaibhav at linux.vnet.ibm.com>
---
 core/pci.c    | 53 +++++++++++++++++++++++++++++++++++++++++++++++++++++
 hw/phb3.c     | 33 +++++++++++++++++++++++++++++++++
 include/pci.h | 28 ++++++++++++++++++++++++++++
 3 files changed, 114 insertions(+)

diff --git a/core/pci.c b/core/pci.c
index 6cfb3cb..8d70a15 100644
--- a/core/pci.c
+++ b/core/pci.c
@@ -163,6 +163,7 @@ static struct pci_device *pci_scan_one(struct phb *phb, struct pci_device *paren
 	}
 	pd->bdfn = bdfn;
 	pd->parent = parent;
+	list_head_init(&pd->pcrf);
 	list_head_init(&pd->children);
 	rc = pci_cfg_read8(phb, bdfn, PCI_CFG_HDR_TYPE, &htype);
 	if (rc) {
@@ -1576,3 +1577,55 @@ void pci_restore_bridge_buses(struct phb *phb)
 {
 	pci_walk_dev(phb, __pci_restore_bridge_buses, NULL);
 }
+
+struct pci_cfg_reg_filter *pci_find_cfg_reg_filter(struct pci_device *pd,
+						   uint32_t start, uint32_t len)
+{
+	struct pci_cfg_reg_filter *pcrf;
+
+	/* Check on the cached range, which contains holes */
+	if ((start + len) <= pd->pcrf_start ||
+	    pd->pcrf_end <= start)
+		return NULL;
+
+	list_for_each(&pd->pcrf, pcrf, link) {
+		if (start >= pcrf->start &&
+		    (start + len) <= (pcrf->start + pcrf->len))
+			return pcrf;
+	}
+
+	return NULL;
+}
+
+struct pci_cfg_reg_filter *pci_add_cfg_reg_filter(struct pci_device *pd,
+						  uint32_t start, uint32_t len,
+						  uint32_t flags,
+						  pci_cfg_reg_func func)
+{
+	struct pci_cfg_reg_filter *pcrf;
+
+	pcrf = pci_find_cfg_reg_filter(pd, start, len);
+	if (pcrf)
+		return pcrf;
+
+	pcrf = zalloc(sizeof(*pcrf) + ((len + 0x4) & ~0x3));
+	if (!pcrf)
+		return NULL;
+
+	/* Don't validate the flags so that the private flags
+	 * can be supported for debugging purpose.
+	 */
+	pcrf->flags = flags;
+	pcrf->start = start;
+	pcrf->len = len;
+	pcrf->func = func;
+	pcrf->data = (uint8_t *)(pcrf + 1);
+
+	if (start < pd->pcrf_start)
+		pd->pcrf_start = start;
+	if (pd->pcrf_end < (start + len))
+		pd->pcrf_end = start + len;
+	list_add_tail(&pd->pcrf, &pcrf->link);
+
+	return pcrf;
+}
diff --git a/hw/phb3.c b/hw/phb3.c
index 49b8e92..939e610 100644
--- a/hw/phb3.c
+++ b/hw/phb3.c
@@ -149,6 +149,33 @@ static int64_t phb3_pcicfg_check(struct phb3 *p, uint32_t bdfn,
 	return OPAL_SUCCESS;
 }
 
+static void phb3_pcicfg_filter(struct phb *phb, uint32_t bdfn,
+			       uint32_t offset, uint32_t len,
+			       uint32_t *data, bool write)
+{
+	struct pci_device *pd;
+	struct pci_cfg_reg_filter *pcrf;
+	uint32_t flags;
+
+	/* FIXME: It harms the performance to search the PCI
+	 * device which doesn't have any filters at all. So
+	 * it's worthy to maintain a table in PHB to indicate
+	 * the PCI devices who have filters. However, bitmap
+	 * seems not supported by skiboot yet. To implement
+	 * it after bitmap is supported.
+	 */
+	pd = pci_find_dev(phb, bdfn);
+	pcrf = pd ? pci_find_cfg_reg_filter(pd, offset, len) : NULL;
+	if (!pcrf || !pcrf->func)
+		return;
+
+	flags = write ? PCI_REG_FLAG_WRITE : PCI_REG_FLAG_READ;
+	if ((pcrf->flags & flags) != flags)
+		return;
+
+	pcrf->func(pd, pcrf, offset, len, data, write);
+}
+
 #define PHB3_PCI_CFG_READ(size, type)	\
 static int64_t phb3_pcicfg_read##size(struct phb *phb, uint32_t bdfn,	\
                                       uint32_t offset, type *data)	\
@@ -189,6 +216,9 @@ static int64_t phb3_pcicfg_read##size(struct phb *phb, uint32_t bdfn,	\
 				    (offset & (4 - sizeof(type))));	\
 	}								\
 									\
+	phb3_pcicfg_filter(phb, bdfn, offset, sizeof(type),		\
+			   (uint32_t *)data, false);			\
+									\
 	return OPAL_SUCCESS;						\
 }
 
@@ -214,6 +244,9 @@ static int64_t phb3_pcicfg_write##size(struct phb *phb, uint32_t bdfn,	\
 		return OPAL_HARDWARE;					\
 	}								\
 									\
+	phb3_pcicfg_filter(phb, bdfn, offset, sizeof(type),             \
+			   (uint32_t *)&data, true);			\
+									\
 	addr = PHB_CA_ENABLE;						\
 	addr = SETFIELD(PHB_CA_BDFN, addr, bdfn);			\
 	addr = SETFIELD(PHB_CA_REG, addr, offset);			\
diff --git a/include/pci.h b/include/pci.h
index 2385163..7362159 100644
--- a/include/pci.h
+++ b/include/pci.h
@@ -90,6 +90,25 @@ struct pci_slot_info {
 	int	   slot_index;
 };
 
+struct pci_device;
+struct pci_cfg_reg_filter;
+
+typedef void (*pci_cfg_reg_func)(struct pci_device *pd,
+				 struct pci_cfg_reg_filter *pcrf,
+				 uint32_t offset, uint32_t len,
+				 uint32_t *data, bool write);
+struct pci_cfg_reg_filter {
+	uint32_t		flags;
+#define PCI_REG_FLAG_READ	0x1
+#define PCI_REG_FLAG_WRITE	0x2
+#define PCI_REG_FLAG_MASK	0x3
+	uint32_t		start;
+	uint32_t		len;
+	uint8_t			*data;
+	pci_cfg_reg_func	func;
+	struct list_node	link;
+};
+
 /*
  * While this might not be necessary in the long run, the existing
  * Linux kernels expect us to provide a device-tree that contains
@@ -123,6 +142,10 @@ struct pci_device {
 	uint32_t		cap[64];
 	uint32_t		mps;		/* Max payload size capability */
 
+	uint32_t		pcrf_start;
+	uint32_t		pcrf_end;
+	struct list_head	pcrf;
+
 	struct pci_slot_info    *slot_info;
 	struct pci_device	*parent;
 	struct list_head	children;
@@ -491,6 +514,11 @@ extern struct pci_device *pci_walk_dev(struct phb *phb,
 				       void *userdata);
 extern struct pci_device *pci_find_dev(struct phb *phb, uint16_t bdfn);
 extern void pci_restore_bridge_buses(struct phb *phb);
+extern struct pci_cfg_reg_filter *pci_find_cfg_reg_filter(struct pci_device *pd,
+					uint32_t start, uint32_t len);
+extern struct pci_cfg_reg_filter *pci_add_cfg_reg_filter(struct pci_device *pd,
+					uint32_t start, uint32_t len,
+					uint32_t flags, pci_cfg_reg_func func);
 
 /* Manage PHBs */
 extern int64_t pci_register_phb(struct phb *phb);
-- 
2.1.0



More information about the Skiboot mailing list