diff mbox

[3/3] pci: Wait for CRS and switch link when restoring bus numbers

Message ID 20170817060446.1340-3-ruscur@russell.cc
State Accepted
Headers show

Commit Message

Russell Currey Aug. 17, 2017, 6:04 a.m. UTC
When a complete reset occurs, after the PHB recovers it propagates a
reset down the wire to every device.  At the same time, skiboot talks to
every device in order to restore the state of devices to what they were
before the reset.

In some situations, such as devices that recovered slowly and/or were
behind a switch, skiboot attempted to access config space of the device
before the link was up and the device could respond.

Fix this by retrying CRS until the device responds correctly, and for
devices behind a switch, making sure the switch has its link up first.

Signed-off-by: Benjamin Herrenschmidt <benh@kernel.crashing.org>
Signed-off-by: Russell Currey <ruscur@russell.cc>
---
 core/pci.c | 51 ++++++++++++++++++++++++++++++++++++++-------------
 1 file changed, 38 insertions(+), 13 deletions(-)

Comments

Andrew Donnellan Aug. 17, 2017, 8:26 a.m. UTC | #1
On 17/08/17 16:04, Russell Currey wrote:
> When a complete reset occurs, after the PHB recovers it propagates a
> reset down the wire to every device.  At the same time, skiboot talks to
> every device in order to restore the state of devices to what they were
> before the reset.
> 
> In some situations, such as devices that recovered slowly and/or were
> behind a switch, skiboot attempted to access config space of the device
> before the link was up and the device could respond.
> 
> Fix this by retrying CRS until the device responds correctly, and for
> devices behind a switch, making sure the switch has its link up first.
> 
> Signed-off-by: Benjamin Herrenschmidt <benh@kernel.crashing.org>
> Signed-off-by: Russell Currey <ruscur@russell.cc>

This looks fairly sensible to me.

Reviewed-by: Andrew Donnellan <andrew.donnellan@au1.ibm.com>

> @@ -1835,14 +1848,26 @@ static int __pci_restore_bridge_buses(struct phb *phb,
>   				      struct pci_device *pd,
>   				      void *data __unused)
>   {
> -	if (!pd->is_bridge) {
> -		uint32_t vdid;
> +	uint32_t vdid;
> +
> +	/* If the device is behind a switch, wait for the switch */
> +	if (!pd->is_vf && !(pd->bdfn & 7) && pd->parent != NULL &&
> +	    pd->parent->dev_type == PCIE_TYPE_SWITCH_DNPORT) {
> +		if (!pci_bridge_wait_link(phb, pd->parent, true)) {
> +			PCIERR(phb, pd->bdfn, "Timeout waiting for switch\n");
> +			return -1;
> +		}
> +	}
> +
> +	/* Wait for config space to stop returning CRS */
> +	if (!pci_wait_crs(phb, pd->bdfn, &vdid))
> +		return -1;

What does returning -1 do - it looks to me that this stops the 
pci_walk_dev() from descending any further, which seems correct, but I 
haven't used pci_walk_dev() very much...
Hari Bathini Aug. 17, 2017, 9:16 a.m. UTC | #2
On Thursday 17 August 2017 11:34 AM, Russell Currey wrote:
> When a complete reset occurs, after the PHB recovers it propagates a
> reset down the wire to every device.  At the same time, skiboot talks to
> every device in order to restore the state of devices to what they were
> before the reset.
>
> In some situations, such as devices that recovered slowly and/or were
> behind a switch, skiboot attempted to access config space of the device
> before the link was up and the device could respond.
>
> Fix this by retrying CRS until the device responds correctly, and for
> devices behind a switch, making sure the switch has its link up first.
>
> Signed-off-by: Benjamin Herrenschmidt <benh@kernel.crashing.org>
> Signed-off-by: Russell Currey <ruscur@russell.cc>

With this patch, PCI devices are initialized properly in kdump environment..

Tested-by: Hari Bathini <hbathini@linux.vnet.ibm.com>

> ---
>   core/pci.c | 51 ++++++++++++++++++++++++++++++++++++++-------------
>   1 file changed, 38 insertions(+), 13 deletions(-)
>
> diff --git a/core/pci.c b/core/pci.c
> index a709e27f..917a9e6e 100644
> --- a/core/pci.c
> +++ b/core/pci.c
> @@ -218,21 +218,18 @@ void pci_init_capabilities(struct phb *phb, struct pci_device *pd)
>   	pci_init_pm_cap(phb, pd);
>   }
>   
> -static struct pci_device *pci_scan_one(struct phb *phb, struct pci_device *parent,
> -				       uint16_t bdfn)
> +static bool pci_wait_crs(struct phb *phb, uint16_t bdfn, uint32_t *out_vdid)
>   {
> -	struct pci_device *pd = NULL;
>   	uint32_t retries, vdid;
>   	int64_t rc;
> -	uint8_t htype;
>   	bool had_crs = false;
>   
>   	for (retries = 0; retries < 40; retries++) {
>   		rc = pci_cfg_read32(phb, bdfn, PCI_CFG_VENDOR_ID, &vdid);
>   		if (rc)
> -			return NULL;
> +			return false;
>   		if (vdid == 0xffffffff || vdid == 0x00000000)
> -			return NULL;
> +			return false;
>   		if (vdid != 0xffff0001)
>   			break;
>   		had_crs = true;
> @@ -240,11 +237,27 @@ static struct pci_device *pci_scan_one(struct phb *phb, struct pci_device *paren
>   	}
>   	if (vdid == 0xffff0001) {
>   		PCIERR(phb, bdfn, "CRS timeout !\n");
> -		return NULL;
> +		return false;
>   	}
>   	if (had_crs)
>   		PCIDBG(phb, bdfn, "Probe success after %d CRS\n", retries);
>   
> +	if (out_vdid)
> +		*out_vdid = vdid;
> +	return true;
> +}
> +
> +static struct pci_device *pci_scan_one(struct phb *phb, struct pci_device *parent,
> +				       uint16_t bdfn)
> +{
> +	struct pci_device *pd = NULL;
> +	uint32_t vdid;
> +	int64_t rc;
> +	uint8_t htype;
> +
> +	if (!pci_wait_crs(phb, bdfn, &vdid))
> +		return NULL;
> +
>   	/* Perform a dummy write to the device in order for it to
>   	 * capture it's own bus number, so any subsequent error
>   	 * messages will be properly tagged
> @@ -1835,14 +1848,26 @@ static int __pci_restore_bridge_buses(struct phb *phb,
>   				      struct pci_device *pd,
>   				      void *data __unused)
>   {
> -	if (!pd->is_bridge) {
> -		uint32_t vdid;
> +	uint32_t vdid;
> +
> +	/* If the device is behind a switch, wait for the switch */
> +	if (!pd->is_vf && !(pd->bdfn & 7) && pd->parent != NULL &&
> +	    pd->parent->dev_type == PCIE_TYPE_SWITCH_DNPORT) {
> +		if (!pci_bridge_wait_link(phb, pd->parent, true)) {
> +			PCIERR(phb, pd->bdfn, "Timeout waiting for switch\n");
> +			return -1;
> +		}
> +	}
> +
> +	/* Wait for config space to stop returning CRS */
> +	if (!pci_wait_crs(phb, pd->bdfn, &vdid))
> +		return -1;
>   
> -		/* Make all devices below a bridge "re-capture" the bdfn */
> -		if (pci_cfg_read32(phb, pd->bdfn, PCI_CFG_VENDOR_ID, &vdid) == 0)
> -			pci_cfg_write32(phb, pd->bdfn, PCI_CFG_VENDOR_ID, vdid);
> +	/* Make all devices below a bridge "re-capture" the bdfn */
> +	pci_cfg_write32(phb, pd->bdfn, PCI_CFG_VENDOR_ID, vdid);
> +
> +	if (!pd->is_bridge)
>   		return 0;
> -	}
>   
>   	pci_cfg_write8(phb, pd->bdfn, PCI_CFG_PRIMARY_BUS,
>   		       pd->primary_bus);
Russell Currey Aug. 18, 2017, 1:08 a.m. UTC | #3
On Thu, 2017-08-17 at 18:26 +1000, Andrew Donnellan wrote:
> On 17/08/17 16:04, Russell Currey wrote:
> > When a complete reset occurs, after the PHB recovers it propagates a
> > reset down the wire to every device.  At the same time, skiboot talks to
> > every device in order to restore the state of devices to what they were
> > before the reset.
> > 
> > In some situations, such as devices that recovered slowly and/or were
> > behind a switch, skiboot attempted to access config space of the device
> > before the link was up and the device could respond.
> > 
> > Fix this by retrying CRS until the device responds correctly, and for
> > devices behind a switch, making sure the switch has its link up first.
> > 
> > Signed-off-by: Benjamin Herrenschmidt <benh@kernel.crashing.org>
> > Signed-off-by: Russell Currey <ruscur@russell.cc>
> 
> This looks fairly sensible to me.
> 
> Reviewed-by: Andrew Donnellan <andrew.donnellan@au1.ibm.com>
> 
> > @@ -1835,14 +1848,26 @@ static int __pci_restore_bridge_buses(struct phb
> > *phb,
> >   				      struct pci_device *pd,
> >   				      void *data __unused)
> >   {
> > -	if (!pd->is_bridge) {
> > -		uint32_t vdid;
> > +	uint32_t vdid;
> > +
> > +	/* If the device is behind a switch, wait for the switch */
> > +	if (!pd->is_vf && !(pd->bdfn & 7) && pd->parent != NULL &&
> > +	    pd->parent->dev_type == PCIE_TYPE_SWITCH_DNPORT) {
> > +		if (!pci_bridge_wait_link(phb, pd->parent, true)) {
> > +			PCIERR(phb, pd->bdfn, "Timeout waiting for
> > switch\n");
> > +			return -1;
> > +		}
> > +	}
> > +
> > +	/* Wait for config space to stop returning CRS */
> > +	if (!pci_wait_crs(phb, pd->bdfn, &vdid))
> > +		return -1;
> 
> What does returning -1 do - it looks to me that this stops the 
> pci_walk_dev() from descending any further, which seems correct, but I 
> haven't used pci_walk_dev() very much...
> 
Yes, that's what it does.
diff mbox

Patch

diff --git a/core/pci.c b/core/pci.c
index a709e27f..917a9e6e 100644
--- a/core/pci.c
+++ b/core/pci.c
@@ -218,21 +218,18 @@  void pci_init_capabilities(struct phb *phb, struct pci_device *pd)
 	pci_init_pm_cap(phb, pd);
 }
 
-static struct pci_device *pci_scan_one(struct phb *phb, struct pci_device *parent,
-				       uint16_t bdfn)
+static bool pci_wait_crs(struct phb *phb, uint16_t bdfn, uint32_t *out_vdid)
 {
-	struct pci_device *pd = NULL;
 	uint32_t retries, vdid;
 	int64_t rc;
-	uint8_t htype;
 	bool had_crs = false;
 
 	for (retries = 0; retries < 40; retries++) {
 		rc = pci_cfg_read32(phb, bdfn, PCI_CFG_VENDOR_ID, &vdid);
 		if (rc)
-			return NULL;
+			return false;
 		if (vdid == 0xffffffff || vdid == 0x00000000)
-			return NULL;
+			return false;
 		if (vdid != 0xffff0001)
 			break;
 		had_crs = true;
@@ -240,11 +237,27 @@  static struct pci_device *pci_scan_one(struct phb *phb, struct pci_device *paren
 	}
 	if (vdid == 0xffff0001) {
 		PCIERR(phb, bdfn, "CRS timeout !\n");
-		return NULL;
+		return false;
 	}
 	if (had_crs)
 		PCIDBG(phb, bdfn, "Probe success after %d CRS\n", retries);
 
+	if (out_vdid)
+		*out_vdid = vdid;
+	return true;
+}
+
+static struct pci_device *pci_scan_one(struct phb *phb, struct pci_device *parent,
+				       uint16_t bdfn)
+{
+	struct pci_device *pd = NULL;
+	uint32_t vdid;
+	int64_t rc;
+	uint8_t htype;
+
+	if (!pci_wait_crs(phb, bdfn, &vdid))
+		return NULL;
+
 	/* Perform a dummy write to the device in order for it to
 	 * capture it's own bus number, so any subsequent error
 	 * messages will be properly tagged
@@ -1835,14 +1848,26 @@  static int __pci_restore_bridge_buses(struct phb *phb,
 				      struct pci_device *pd,
 				      void *data __unused)
 {
-	if (!pd->is_bridge) {
-		uint32_t vdid;
+	uint32_t vdid;
+
+	/* If the device is behind a switch, wait for the switch */
+	if (!pd->is_vf && !(pd->bdfn & 7) && pd->parent != NULL &&
+	    pd->parent->dev_type == PCIE_TYPE_SWITCH_DNPORT) {
+		if (!pci_bridge_wait_link(phb, pd->parent, true)) {
+			PCIERR(phb, pd->bdfn, "Timeout waiting for switch\n");
+			return -1;
+		}
+	}
+
+	/* Wait for config space to stop returning CRS */
+	if (!pci_wait_crs(phb, pd->bdfn, &vdid))
+		return -1;
 
-		/* Make all devices below a bridge "re-capture" the bdfn */
-		if (pci_cfg_read32(phb, pd->bdfn, PCI_CFG_VENDOR_ID, &vdid) == 0)
-			pci_cfg_write32(phb, pd->bdfn, PCI_CFG_VENDOR_ID, vdid);
+	/* Make all devices below a bridge "re-capture" the bdfn */
+	pci_cfg_write32(phb, pd->bdfn, PCI_CFG_VENDOR_ID, vdid);
+
+	if (!pd->is_bridge)
 		return 0;
-	}
 
 	pci_cfg_write8(phb, pd->bdfn, PCI_CFG_PRIMARY_BUS,
 		       pd->primary_bus);