[PATCH 1/1] ppc64: Block config accesses during BIST

brking at us.ibm.com brking at us.ibm.com
Tue Sep 14 07:53:52 EST 2004


Some PCI adapters on pSeries and iSeries hardware (ipr scsi adapters)
have an exposure today in that they issue BIST to the adapter to reset
the card. If, during the time it takes to complete BIST, userspace attempts
to access PCI config space, the host bus bridge will master abort the access
since the ipr adapter does not respond on the PCI bus for a brief period of
time when running BIST. This master abort results in the host PCI bridge
isolating that PCI device from the rest of the system, making the device
unusable until Linux is rebooted. This patch is an attempt to close that
exposure by introducing some blocking code in the arch specific PCI code.
The intent is to have the ipr device driver invoke these routines to
prevent userspace PCI accesses from occurring during this window.

It has been tested by running BIST on an ipr adapter while running a
script which looped reading the config space of that adapter through sysfs.
Without the patch, an EEH error occurrs. With the patch there is no EEH
error. Tested on Power 5 and iSeries Power 4.

Signed-off-by: Brian King <brking at us.ibm.com>
---

 linux-2.6.9-rc2-bjking1/arch/ppc64/kernel/iSeries_pci.c         |  127 +++++++++-
 linux-2.6.9-rc2-bjking1/arch/ppc64/kernel/pSeries_pci.c         |  101 +++++++
 linux-2.6.9-rc2-bjking1/arch/ppc64/kernel/prom.c                |    1 
 linux-2.6.9-rc2-bjking1/include/asm-ppc64/iSeries/iSeries_pci.h |    2 
 linux-2.6.9-rc2-bjking1/include/asm-ppc64/pci.h                 |    6 
 linux-2.6.9-rc2-bjking1/include/asm-ppc64/prom.h                |    5 
 6 files changed, 226 insertions(+), 16 deletions(-)

diff -puN include/asm-ppc64/prom.h~ppc64_block_cfg_io_during_bist include/asm-ppc64/prom.h
--- linux-2.6.9-rc2/include/asm-ppc64/prom.h~ppc64_block_cfg_io_during_bist	2004-09-13 09:54:32.000000000 -0500
+++ linux-2.6.9-rc2-bjking1/include/asm-ppc64/prom.h	2004-09-13 09:54:32.000000000 -0500
@@ -169,16 +169,21 @@ struct device_node {
 	struct  proc_dir_entry *addr_link; /* addr symlink */
 	atomic_t _users;                 /* reference count */
 	unsigned long _flags;
+	spinlock_t config_lock;
 };
 
 /* flag descriptions */
 #define OF_STALE   0 /* node is slated for deletion */
 #define OF_DYNAMIC 1 /* node and properties were allocated via kmalloc */
+#define OF_NO_CFGIO 2 /* config space accesses should fail */
 
 #define OF_IS_STALE(x) test_bit(OF_STALE, &x->_flags)
 #define OF_MARK_STALE(x) set_bit(OF_STALE, &x->_flags)
 #define OF_IS_DYNAMIC(x) test_bit(OF_DYNAMIC, &x->_flags)
 #define OF_MARK_DYNAMIC(x) set_bit(OF_DYNAMIC, &x->_flags)
+#define OF_IS_CFGIO_BLOCKED(x) test_bit(OF_NO_CFGIO, &x->_flags)
+#define OF_UNBLOCK_CFGIO(x) clear_bit(OF_NO_CFGIO, &x->_flags)
+#define OF_BLOCK_CFGIO(x) set_bit(OF_NO_CFGIO, &x->_flags)
 
 /*
  * Until 32-bit ppc can add proc_dir_entries to its device_node
diff -puN arch/ppc64/kernel/prom.c~ppc64_block_cfg_io_during_bist arch/ppc64/kernel/prom.c
--- linux-2.6.9-rc2/arch/ppc64/kernel/prom.c~ppc64_block_cfg_io_during_bist	2004-09-13 09:54:32.000000000 -0500
+++ linux-2.6.9-rc2-bjking1/arch/ppc64/kernel/prom.c	2004-09-13 09:54:32.000000000 -0500
@@ -2959,6 +2959,7 @@ int of_add_node(const char *path, struct
 
 	np->properties = proplist;
 	OF_MARK_DYNAMIC(np);
+	spin_lock_init(&np->config_lock);
 	of_node_get(np);
 	np->parent = derive_parent(path);
 	if (!np->parent) {
diff -puN arch/ppc64/kernel/pSeries_pci.c~ppc64_block_cfg_io_during_bist arch/ppc64/kernel/pSeries_pci.c
--- linux-2.6.9-rc2/arch/ppc64/kernel/pSeries_pci.c~ppc64_block_cfg_io_during_bist	2004-09-13 09:54:32.000000000 -0500
+++ linux-2.6.9-rc2-bjking1/arch/ppc64/kernel/pSeries_pci.c	2004-09-13 09:54:32.000000000 -0500
@@ -61,14 +61,12 @@ static int s7a_workaround;
 
 extern unsigned long pci_probe_only;
 
-static int rtas_read_config(struct device_node *dn, int where, int size, u32 *val)
+static int __rtas_read_config(struct device_node *dn, int where, int size, u32 *val)
 {
 	int returnval = -1;
 	unsigned long buid, addr;
 	int ret;
 
-	if (!dn)
-		return PCIBIOS_DEVICE_NOT_FOUND;
 	if (where & (size - 1))
 		return PCIBIOS_BAD_REGISTER_NUMBER;
 
@@ -92,6 +90,23 @@ static int rtas_read_config(struct devic
 	return PCIBIOS_SUCCESSFUL;
 }
 
+static int rtas_read_config(struct device_node *dn, int where, int size, u32 *val)
+{
+	unsigned long flags;
+	int ret = 0;
+
+	if (!dn)
+		return PCIBIOS_DEVICE_NOT_FOUND;
+
+	spin_lock_irqsave(&dn->config_lock, flags);
+	if (OF_IS_CFGIO_BLOCKED(dn))
+		*val = -1;
+	else
+		ret = __rtas_read_config(dn, where, size, val);
+	spin_unlock_irqrestore(&dn->config_lock, flags);
+	return ret;
+}
+
 static int rtas_pci_read_config(struct pci_bus *bus,
 				unsigned int devfn,
 				int where, int size, u32 *val)
@@ -110,13 +125,11 @@ static int rtas_pci_read_config(struct p
 	return PCIBIOS_DEVICE_NOT_FOUND;
 }
 
-static int rtas_write_config(struct device_node *dn, int where, int size, u32 val)
+static int __rtas_write_config(struct device_node *dn, int where, int size, u32 val)
 {
 	unsigned long buid, addr;
 	int ret;
 
-	if (!dn)
-		return PCIBIOS_DEVICE_NOT_FOUND;
 	if (where & (size - 1))
 		return PCIBIOS_BAD_REGISTER_NUMBER;
 
@@ -134,6 +147,21 @@ static int rtas_write_config(struct devi
 	return PCIBIOS_SUCCESSFUL;
 }
 
+static int rtas_write_config(struct device_node *dn, int where, int size, u32 val)
+{
+	unsigned long flags;
+	int ret = 0;
+
+	if (!dn)
+		return PCIBIOS_DEVICE_NOT_FOUND;
+
+	spin_lock_irqsave(&dn->config_lock, flags);
+	if (!OF_IS_CFGIO_BLOCKED(dn))
+		ret = __rtas_write_config(dn, where, size, val);
+	spin_unlock_irqrestore(&dn->config_lock, flags);
+	return ret;
+}
+
 static int rtas_pci_write_config(struct pci_bus *bus,
 				 unsigned int devfn,
 				 int where, int size, u32 val)
@@ -157,6 +185,67 @@ struct pci_ops rtas_pci_ops = {
 	rtas_pci_write_config
 };
 
+/**
+ * pci_block_config_io - Block PCI config reads/writes
+ * @pdev:	pci device struct
+ *
+ * This function blocks any PCI config accesses from occurring.
+ * Device drivers may call this prior to running BIST if the
+ * adapter cannot handle PCI config reads or writes when
+ * running BIST. When blocked, any writes will be ignored and
+ * treated as successful and any reads will return all 1's data.
+ *
+ * Return value:
+ * 	nothing
+ **/
+void pci_block_config_io(struct pci_dev *pdev)
+{
+	struct device_node *dn = pci_device_to_OF_node(pdev);
+	unsigned long flags;
+
+	spin_lock_irqsave(&dn->config_lock, flags);
+	OF_BLOCK_CFGIO(dn);
+	spin_unlock_irqrestore(&dn->config_lock, flags);
+}
+EXPORT_SYMBOL(pci_block_config_io);
+
+/**
+ * pci_unblock_config_io - Unblock PCI config reads/writes
+ * @pdev:	pci device struct
+ *
+ * This function allows PCI config accesses to resume.
+ *
+ * Return value:
+ * 	nothing
+ **/
+void pci_unblock_config_io(struct pci_dev *pdev)
+{
+	struct device_node *dn = pci_device_to_OF_node(pdev);
+	unsigned long flags;
+
+	spin_lock_irqsave(&dn->config_lock, flags);
+	OF_UNBLOCK_CFGIO(dn);
+	spin_unlock_irqrestore(&dn->config_lock, flags);
+}
+EXPORT_SYMBOL(pci_unblock_config_io);
+
+/**
+ * pci_start_bist - Start BIST on a PCI device
+ * @pdev:	pci device struct
+ *
+ * This function allows a device driver to start BIST
+ * when PCI config accesses are disabled.
+ *
+ * Return value:
+ * 	nothing
+ **/
+int pci_start_bist(struct pci_dev *pdev)
+{
+	struct device_node *dn = pci_device_to_OF_node(pdev);
+	return __rtas_write_config(dn, PCI_BIST, 1, PCI_BIST_START);
+}
+EXPORT_SYMBOL(pci_start_bist);
+
 /******************************************************************
  * pci_read_irq_line
  *
diff -puN include/asm-ppc64/pci.h~ppc64_block_cfg_io_during_bist include/asm-ppc64/pci.h
--- linux-2.6.9-rc2/include/asm-ppc64/pci.h~ppc64_block_cfg_io_during_bist	2004-09-13 09:54:32.000000000 -0500
+++ linux-2.6.9-rc2-bjking1/include/asm-ppc64/pci.h	2004-09-13 09:54:32.000000000 -0500
@@ -233,6 +233,12 @@ extern int pci_read_irq_line(struct pci_
 
 extern void pcibios_add_platform_entries(struct pci_dev *dev);
 
+extern void pci_block_config_io(struct pci_dev *dev);
+
+extern void pci_unblock_config_io(struct pci_dev *dev);
+
+extern int pci_start_bist(struct pci_dev *dev);
+
 #endif	/* __KERNEL__ */
 
 #endif /* __PPC64_PCI_H */
diff -puN include/asm-ppc64/iSeries/iSeries_pci.h~ppc64_block_cfg_io_during_bist include/asm-ppc64/iSeries/iSeries_pci.h
--- linux-2.6.9-rc2/include/asm-ppc64/iSeries/iSeries_pci.h~ppc64_block_cfg_io_during_bist	2004-09-13 09:54:32.000000000 -0500
+++ linux-2.6.9-rc2-bjking1/include/asm-ppc64/iSeries/iSeries_pci.h	2004-09-13 09:54:32.000000000 -0500
@@ -91,6 +91,7 @@ struct iSeries_Device_Node {
 	int             ReturnCode;	/* Return Code Holder          */
 	int             IoRetry;        /* Current Retry Count         */
 	int             Flags;          /* Possible flags(disable/bist)*/
+#define ISERIES_CFGIO_BLOCKED	1
 	u16             Vendor;         /* Vendor ID                   */
 	u8              LogicalSlot;    /* Hv Slot Index for Tces      */
 	struct iommu_table* iommu_table;/* Device TCE Table            */ 
@@ -99,6 +100,7 @@ struct iSeries_Device_Node {
 	u8              FrameId;	/* iSeries spcn Frame Id       */
 	char            CardLocation[4];/* Char format of planar vpd   */
 	char            Location[20];   /* Frame  1, Card C10          */
+	spinlock_t      config_lock;
 };
 
 /************************************************************************/
diff -puN arch/ppc64/kernel/iSeries_pci.c~ppc64_block_cfg_io_during_bist arch/ppc64/kernel/iSeries_pci.c
--- linux-2.6.9-rc2/arch/ppc64/kernel/iSeries_pci.c~ppc64_block_cfg_io_during_bist	2004-09-13 09:54:32.000000000 -0500
+++ linux-2.6.9-rc2-bjking1/arch/ppc64/kernel/iSeries_pci.c	2004-09-13 09:54:32.000000000 -0500
@@ -131,6 +131,7 @@ static struct iSeries_Device_Node *build
 	node->AgentId = AgentId;
 	node->DevFn = PCI_DEVFN(ISERIES_ENCODE_DEVICE(AgentId), Function);
 	node->IoRetry = 0;
+	spin_lock_init(&node->config_lock);
 	iSeries_Get_Location_Code(node);
 	PCIFR("Device 0x%02X.%2X, Node:0x%p ", ISERIES_BUS(node),
 			ISERIES_DEVFUN(node), node);
@@ -508,16 +509,12 @@ static u64 hv_cfg_write_func[4] = {
 /*
  * Read PCI config space
  */
-static int iSeries_pci_read_config(struct pci_bus *bus, unsigned int devfn,
+static int __iSeries_pci_read_config(struct iSeries_Device_Node *node,
 		int offset, int size, u32 *val)
 {
-	struct iSeries_Device_Node *node = find_Device_Node(bus->number, devfn);
 	u64 fn;
 	struct HvCallPci_LoadReturn ret;
 
-	if (node == NULL)
-		return PCIBIOS_DEVICE_NOT_FOUND;
-
 	fn = hv_cfg_read_func[(size - 1) & 3];
 	HvCall3Ret16(fn, &ret, node->DsaAddr.DsaAddr, offset, 0);
 
@@ -530,20 +527,36 @@ static int iSeries_pci_read_config(struc
 	return 0;
 }
 
+static int iSeries_pci_read_config(struct pci_bus *bus, unsigned int devfn,
+		int offset, int size, u32 *val)
+{
+	struct iSeries_Device_Node *node = find_Device_Node(bus->number, devfn);
+	int ret = PCIBIOS_DEVICE_NOT_FOUND;
+	unsigned long flags;
+
+	if (node) {
+		ret = 0;
+		spin_lock_irqsave(&node->config_lock, flags);
+		if (node->Flags & ISERIES_CFGIO_BLOCKED)
+			*val = -1;
+		else
+			ret = __iSeries_pci_read_config(node, offset, size, val);
+		spin_unlock_irqrestore(&node->config_lock, flags);
+	}
+
+	return ret;
+}
+
 /*
  * Write PCI config space
  */
 
-static int iSeries_pci_write_config(struct pci_bus *bus, unsigned int devfn,
+static int __iSeries_pci_write_config(struct iSeries_Device_Node *node,
 		int offset, int size, u32 val)
 {
-	struct iSeries_Device_Node *node = find_Device_Node(bus->number, devfn);
 	u64 fn;
 	u64 ret;
 
-	if (node == NULL)
-		return PCIBIOS_DEVICE_NOT_FOUND;
-
 	fn = hv_cfg_write_func[(size - 1) & 3];
 	ret = HvCall4(fn, node->DsaAddr.DsaAddr, offset, val, 0);
 
@@ -553,6 +566,23 @@ static int iSeries_pci_write_config(stru
 	return 0;
 }
 
+static int iSeries_pci_write_config(struct pci_bus *bus, unsigned int devfn,
+		int offset, int size, u32 val)
+{
+	struct iSeries_Device_Node *node = find_Device_Node(bus->number, devfn);
+	int ret = PCIBIOS_DEVICE_NOT_FOUND;
+	unsigned long flags;
+
+	if (node) {
+		spin_lock_irqsave(&node->config_lock, flags);
+		if (!(node->Flags & ISERIES_CFGIO_BLOCKED))
+			ret = __iSeries_pci_write_config(node, offset, size, val);
+		spin_unlock_irqrestore(&node->config_lock, flags);
+	}
+
+	return ret;
+}
+
 static struct pci_ops iSeries_pci_ops = {
 	.read = iSeries_pci_read_config,
 	.write = iSeries_pci_write_config
@@ -815,3 +845,80 @@ void iSeries_Write_Long(u32 data, volati
 	} while (CheckReturnCode("WWL", DevNode, rc) != 0);
 }
 EXPORT_SYMBOL(iSeries_Write_Long);
+
+/**
+ * pci_block_config_io - Block PCI config reads/writes
+ * @pdev:	pci device struct
+ *
+ * This function blocks any PCI config accesses from occurring.
+ * Device drivers may call this prior to running BIST if the
+ * adapter cannot handle PCI config reads or writes when
+ * running BIST. When blocked, any writes will be ignored and
+ * treated as successful and any reads will return all 1's data.
+ *
+ * Return value:
+ * 	nothing
+ **/
+void pci_block_config_io(struct pci_dev *pdev)
+{
+	struct iSeries_Device_Node *node;
+	unsigned long flags;
+
+	node = find_Device_Node(pdev->bus->number, pdev->devfn);
+
+	if (node == NULL)
+		return;
+
+	spin_lock_irqsave(&node->config_lock, flags);
+	node->Flags |= ISERIES_CFGIO_BLOCKED;
+	spin_unlock_irqrestore(&node->config_lock, flags);
+}
+EXPORT_SYMBOL(pci_block_config_io);
+
+/**
+ * pci_unblock_config_io - Unblock PCI config reads/writes
+ * @pdev:	pci device struct
+ *
+ * This function allows PCI config accesses to resume.
+ *
+ * Return value:
+ * 	nothing
+ **/
+void pci_unblock_config_io(struct pci_dev *pdev)
+{
+	struct iSeries_Device_Node *node;
+	unsigned long flags;
+
+	node = find_Device_Node(pdev->bus->number, pdev->devfn);
+
+	if (node == NULL)
+		return;
+
+	spin_lock_irqsave(&node->config_lock, flags);
+	node->Flags &= ~ISERIES_CFGIO_BLOCKED;
+	spin_unlock_irqrestore(&node->config_lock, flags);
+}
+EXPORT_SYMBOL(pci_unblock_config_io);
+
+/**
+ * pci_start_bist - Start BIST on a PCI device
+ * @pdev:	pci device struct
+ *
+ * This function allows a device driver to start BIST
+ * when PCI config accesses are disabled.
+ *
+ * Return value:
+ * 	nothing
+ **/
+int pci_start_bist(struct pci_dev *pdev)
+{
+	struct iSeries_Device_Node *node;
+
+	node = find_Device_Node(pdev->bus->number, pdev->devfn);
+
+	if (node == NULL)
+		return PCIBIOS_DEVICE_NOT_FOUND;
+
+	return __iSeries_pci_write_config(node, PCI_BIST, 1, PCI_BIST_START);
+}
+EXPORT_SYMBOL(pci_start_bist);
_



More information about the Linuxppc64-dev mailing list