Skip to contentSkip to conversation

LS1043A : "synchronous abort" at boot due to PCI config read

PATCH In review Gilles Buloz 3 participants 15 messages Apr 13, 2018 – May 4, 2018 PCI

lore
Share & tools
lore
#0 lore raw

Dear developers,

I currently have two functional workarounds for this issue but would like to know which one you would recommend, if any :-) I'm using a LS1043A CPU (NXP QorIQ Layerscape) and get a "synchronous external abort" when booting because of a PCI config read during PCI scan.

I'm using a custom hardware (based on LS1043ARDB) having a PEX8112 PCIe-to-PCI bridge connected to the LS1043A to have a PCI slot for legacy devices. This bridge only supports PCI-Compatible config accesses (offset 0x00-0xFF).
On this PCI slot I connect a PCI module made of a PCI-to-PCIe bridge plus PCIe devices behind.
The problem occurs when the kernel probes the PCIe devices : as they are PCIe devices, the kernel does a PCI config read access at offset 0x100 to check if "PCIe extended capability registers" are accessible (see drivers/pci/probe.c, function pci_cfg_space_size_ext()). Unfortunately the PEX8112 PCIe-to-PCI bridge that is in the path reports an error to the CPU for this access, and it seems there's no way to disable that on this bridge.

The first workaround I found was to patch drivers/pci/host/pci-layerscape.c to have PCIE_ABSERR_SETTING set to 0x9400 instead of 0x9401 (for PCIE_ABSERR register) to disable error reporting. This only impacts an NXP part of the Linux kernel code, but I'm not sure this is a good idea (however it seems to be like that on Intel platforms where even MEM accesses to a no-device address return FF without any error).

I've also tried another workaround that works : patch drivers/pci/probe.c to use bus_flags to remember if a bus is behind a bridge without extended address capability, to avoid PCi config read accesses at offset 0x100 in pci_cfg_space_size() / pci_cfg_space_size_ext(). But this patch impacts the generic PCI probe method of Linux.

Any Idea to properly handle that issue ?

Best regards

#1 lore raw

(add Bjorn and linux-pci)

Quoted 19 lines · Gilles Buloz

Dear developers,

I currently have two functional workarounds for this issue but would like to know which one you would recommend, if any :-) I'm using a LS1043A CPU (NXP QorIQ Layerscape) and get a "synchronous external abort" when booting because of a PCI config read during PCI scan.

I'm using a custom hardware (based on LS1043ARDB) having a PEX8112 PCIe-to-PCI bridge connected to the LS1043A to have a PCI slot for legacy devices. This bridge only supports PCI-Compatible config accesses (offset 0x00-0xFF).
On this PCI slot I connect a PCI module made of a PCI-to-PCIe bridge plus PCIe devices behind.
The problem occurs when the kernel probes the PCIe devices : as they are PCIe devices, the kernel does a PCI config read access at offset 0x100 to check if "PCIe extended capability registers" are accessible (see drivers/pci/probe.c, function pci_cfg_space_size_ext()). Unfortunately the PEX8112 PCIe-to-PCI bridge that is in the path reports an error to the CPU for this access, and it seems there's no way to disable that on this bridge.

The first workaround I found was to patch drivers/pci/host/pci-layerscape.c to have PCIE_ABSERR_SETTING set to 0x9400 instead of 0x9401 (for PCIE_ABSERR register) to disable error reporting. This only impacts an NXP part of the Linux kernel code, but I'm not sure this is a good idea (however it seems to be like that on Intel platforms where even MEM accesses to a no-device address return FF without any error).

Quoted 6 lines

I've also tried another workaround that works : patch drivers/pci/probe.c to use bus_flags to remember if a bus is behind a bridge without extended address capability, to avoid PCi config read accesses at offset 0x100 in pci_cfg_space_size() / pci_cfg_space_size_ext(). But this patch impacts the generic PCI probe method of Linux.

Any Idea to properly handle that issue ?

This seems like a rather unusual configuration, but I guess that if the first bridge/switch advertises its inability to support extended config space accesses, we should not be performing them on any of its subordinate buses. How does the PEX8112 advertise this limitation?

That said, I wonder if it is reasonable in the first place to expect that a PCIe device works as expected passing through a legacy PCI layer like that.

#2 lore raw

Le 27/04/2018 10:43, Ard Biesheuvel a ?crit :

Quoted 33 lines

(add Bjorn and linux-pci)

Quoted 19 lines · Gilles Buloz

Dear developers,

I currently have two functional workarounds for this issue but would like to know which one you would recommend, if any :-) I'm using a LS1043A CPU (NXP QorIQ Layerscape) and get a "synchronous external abort" when booting because of a PCI config read during PCI scan.

I'm using a custom hardware (based on LS1043ARDB) having a PEX8112 PCIe-to-PCI bridge connected to the LS1043A to have a PCI slot for legacy devices. This bridge only supports PCI-Compatible config accesses (offset 0x00-0xFF).
On this PCI slot I connect a PCI module made of a PCI-to-PCIe bridge plus PCIe devices behind.
The problem occurs when the kernel probes the PCIe devices : as they are PCIe devices, the kernel does a PCI config read access at offset 0x100 to check if "PCIe extended capability registers" are accessible (see drivers/pci/probe.c, function pci_cfg_space_size_ext()). Unfortunately the PEX8112 PCIe-to-PCI bridge that is in the path reports an error to the CPU for this access, and it seems there's no way to disable that on this bridge.

The first workaround I found was to patch drivers/pci/host/pci-layerscape.c to have PCIE_ABSERR_SETTING set to 0x9400 instead of 0x9401 (for PCIE_ABSERR register) to disable error reporting. This only impacts an NXP part of the Linux kernel code, but I'm not sure this is a good idea (however it seems to be like that on Intel platforms where even MEM accesses to a no-device address return FF without any error).

Quoted 6 lines

I've also tried another workaround that works : patch drivers/pci/probe.c to use bus_flags to remember if a bus is behind a bridge without extended address capability, to avoid PCi config read accesses at offset 0x100 in pci_cfg_space_size() / pci_cfg_space_size_ext(). But this patch impacts the generic PCI probe method of Linux.

Any Idea to properly handle that issue ?

This seems like a rather unusual configuration, but I guess that if the first bridge/switch advertises its inability to support extended config space accesses, we should not be performing them on any of its subordinate buses. How does the PEX8112 advertise this limitation?

Quoted 5 lines

That said, I wonder if it is reasonable in the first place to expect that a PCIe device works as expected passing through a legacy PCI layer like that.

.

The PEX8112 PCIe-to-PCI bridge has capability PCI_CAP_ID_EXP, but has no PCI_CAP_ID_PCIX capability.
As I understand the lack of PCI_CAP_ID_PCIX is advertising this limitation on the PCI side (no support for PCI config offset >=0x100). Also I guess in the case of a bridge having PCI_CAP_ID_PCIX, this limitation would be advertised by the lack of PCI_X_STATUS_266MHZ and PCI_X_STATUS_533MHZ (as done in drivers/pci/probe.c at pci_cfg_space_size())

I'm currently using the attached patch (for kernel 4.1.35-rt41 from NXP Yocto BSP). It uses bus_flags to remember if a bus is behind a bridge without extended address capability to avoid PCi config accesses at offset >= 0x100. Thanks to this patch I now have a functional system with functional PCI/PCIe devices.
-------------- next part --------------
A non-text attachment was scrubbed...
Name: cfgspace.patch
Type: text/x-patch
Size: 1943 bytes
Desc: cfgspace.patch
URL: <http://lists.infradead.org/pipermail/linux-arm-kernel/attachments/20180427/ac2bb836/attachment.bin>

#3 lore raw
Quoted 16 lines · Gilles Buloz

Le 27/04/2018 10:43, Ard Biesheuvel a ?crit :

Quoted 15 lines

(add Bjorn and linux-pci)

Quoted 12 lines · Gilles Buloz

Dear developers,

I currently have two functional workarounds for this issue but would like to know which one you would recommend, if any :-) I'm using a LS1043A CPU (NXP QorIQ Layerscape) and get a "synchronous external abort" when booting because of a PCI config read during PCI scan.

I'm using a custom hardware (based on LS1043ARDB) having a PEX8112 PCIe-to-PCI bridge connected to the LS1043A to have a PCI slot for legacy devices. This bridge only supports PCI-Compatible config accesses (offset 0x00-0xFF).

I would guess the PEX8112 itself has 4K of config space, but it only forwards 256 bytes of config space to the conventional PCI secondary bus.

Quoted 35 lines
Quoted 35 lines
Quoted 20 lines

On this PCI slot I connect a PCI module made of a PCI-to-PCIe bridge plus PCIe devices behind.

The problem occurs when the kernel probes the PCIe devices : as they are PCIe devices, the kernel does a PCI config read access at offset 0x100 to check if "PCIe extended capability registers" are accessible (see drivers/pci/probe.c, function pci_cfg_space_size_ext()). Unfortunately the PEX8112 PCIe-to-PCI bridge that is in the path reports an error to the CPU for this access, and it seems there's no way to disable that on this bridge.

The first workaround I found was to patch drivers/pci/host/pci-layerscape.c to have PCIE_ABSERR_SETTING set to 0x9400 instead of 0x9401 (for PCIE_ABSERR register) to disable error reporting. This only impacts an NXP part of the Linux kernel code, but I'm not sure this is a good idea (however it seems to be like that on Intel platforms where even MEM accesses to a no-device address return FF without any error).

Quoted 9 lines

I've also tried another workaround that works : patch drivers/pci/probe.c to use bus_flags to remember if a bus is behind a bridge without extended address capability, to avoid PCi config read accesses at offset 0x100 in pci_cfg_space_size() / pci_cfg_space_size_ext(). But this patch impacts the generic PCI probe method of Linux.

Any Idea to properly handle that issue ?

This seems like a rather unusual configuration, but I guess that if the first bridge/switch advertises its inability to support extended config space accesses, we should not be performing them on any of its subordinate buses. How does the PEX8112 advertise this limitation?

Quoted 8 lines
Quoted 4 lines

That said, I wonder if it is reasonable in the first place to expect that a PCIe device works as expected passing through a legacy PCI layer like that.

The PEX8112 PCIe-to-PCI bridge has capability PCI_CAP_ID_EXP, but has no PCI_CAP_ID_PCIX capability. As I understand the lack of PCI_CAP_ID_PCIX is advertising this limitation on the PCI side (no support for PCI config offset >=0x100).

Sounds right to me.

Quoted 4 lines

Also I guess in the case of a bridge having PCI_CAP_ID_PCIX, this limitation would be advertised by the lack of PCI_X_STATUS_266MHZ and PCI_X_STATUS_533MHZ (as done in drivers/pci/probe.c at pci_cfg_space_size())

Also sounds right. Per the PCI-X spec, checking for PCI_X_STATUS_266MHZ should be enough, but it shouldn't hurt to check for either PCI_X_STATUS_266MHZ or PCI_X_STATUS_533MHZ.

Quoted 5 lines

I'm currently using the attached patch (for kernel 4.1.35-rt41 from NXP Yocto BSP). It uses bus_flags to remember if a bus is behind a bridge without extended address capability to avoid PCi config accesses at offset >= 0x100. Thanks to this patch I now have a functional system with functional PCI/PCIe devices.

The patch seems like it's looking at the right things, but I don't want to build it into pci_scan_bridge_extend() because that function is much too complicated already.

I'd rather build it into pci_cfg_space_size() or pci_cfg_space_size_ext() somehow. Maybe something along these lines? This doesn't account for the case of a PCIe-to-PCI-X Mode 2 bridge; in that case, I think all 4K would be accessible on the PCI-X side.

drivers/pci/probe.c +6 -1
index ac91b6fd0bcd..d8b091f0bcd1 100644
@@ -1367,7 +1367,7 @@ static bool pci_ext_cfg_is_aliased(struct pci_dev *dev)
  * pci_cfg_space_size - Get the configuration space size of the PCI device
  * @dev: PCI device
  *
- * Regular PCI devices have 256 bytes, but PCI-X 2 and PCI Express devices
+ * Regular PCI devices have 256 bytes, but PCI-X Mode 2 and PCI Express devices
  * have 4096 bytes.  Even if the device is capable, that doesn't mean we can
  * access it.  Maybe we don't have a way to generate extended config space
  * accesses, or the device is behind a reverse Express bridge.  So we try
@@ -1376,9 +1376,14 @@ static bool pci_ext_cfg_is_aliased(struct pci_dev *dev)
  */
 static int pci_cfg_space_size_ext(struct pci_dev *dev)
 {
+	struct pci_dev *bridge = pci_upstream_bridge(dev);
 	u32 status;
 	int pos = PCI_CFG_SPACE_SIZE;
 
+	if (bridge && pci_is_pcie(bridge) &&
+	    pci_pcie_type(bridge) == PCI_EXP_TYPE_PCI_BRIDGE)
+		return PCI_CFG_SPACE_SIZE;
+
 	if (pci_read_config_dword(dev, pos, &status) != PCIBIOS_SUCCESSFUL)
 		return PCI_CFG_SPACE_SIZE;
 	if (status == 0xffffffff || pci_ext_cfg_is_aliased(dev))
Quoted 58 lines
linux/pci.h renamed from linux/pci.h.orig +1 -0
@@ -193,6 +193,7 @@
 enum pci_bus_flags {
 	PCI_BUS_FLAGS_NO_MSI   = (__force pci_bus_flags_t) 1,
 	PCI_BUS_FLAGS_NO_MMRBC = (__force pci_bus_flags_t) 2,
+	PCI_BUS_FLAGS_COMPAT_CFG_SPACE = (__force pci_bus_flags_t) 4,
 };
 
 /* These values come from the PCI Express Spec */
pci/probe.c renamed from pci/probe.c.orig +27 -0
@@ -827,6 +827,28 @@
 			child->primary = primary;
 			pci_bus_insert_busn_res(child, secondary, subordinate);
 			child->bridge_ctl = bctl;
+
+			{
+				int pos;
+				u32 status;
+				bool pci_compat_cfg_space = false;
+
+				if (!pci_is_pcie(dev) || (pci_pcie_type(dev) == PCI_EXP_TYPE_PCIE_BRIDGE) || (pci_pcie_type(dev) == PCI_EXP_TYPE_PCI_BRIDGE)) {
+					/* for PCI/PCI bridges, or PCIe/PCI bridge in forward or reverse mode, we have to check for PCI-X capabilities */
+					pos = pci_find_capability(dev, PCI_CAP_ID_PCIX);
+					if (pos) {
+						pci_read_config_dword(dev, pos + PCI_X_STATUS, &status);
+						if (!(status & (PCI_X_STATUS_266MHZ | PCI_X_STATUS_533MHZ)))
+							pci_compat_cfg_space = true;
+					} else {
+						pci_compat_cfg_space = true;
+					}
+					if (pci_compat_cfg_space) {
+						dev_info(&dev->dev, "[%04x:%04x] Child bus limited to PCI-Compatible config space\n", dev->vendor, dev->device);
+						child->bus_flags |= PCI_BUS_FLAGS_COMPAT_CFG_SPACE;
+					}
+				}
+			}
 		}
 
 		cmax = pci_scan_child_bus(child);
@@ -1098,6 +1120,11 @@
 			goto fail;
 	}
 
+	if (dev->bus->bus_flags & PCI_BUS_FLAGS_COMPAT_CFG_SPACE) {
+		dev_info(&dev->dev, "[%04x:%04x] PCI-Compatible config space only due to parent bus(es)\n", dev->vendor, dev->device);
+		return PCI_CFG_SPACE_SIZE;
+	}
+
 	return pci_cfg_space_size_ext(dev);
 
  fail:

_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel at lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel

#4 lore raw

Le 27/04/2018 18:56, Bjorn Helgaas a ?crit :

Quoted 162 lines · Gilles Buloz
Quoted 16 lines

Le 27/04/2018 10:43, Ard Biesheuvel a ?crit :

Quoted 15 lines

(add Bjorn and linux-pci)

Quoted 12 lines · Gilles Buloz

Dear developers,

I currently have two functional workarounds for this issue but would like to know which one you would recommend, if any :-) I'm using a LS1043A CPU (NXP QorIQ Layerscape) and get a "synchronous external abort" when booting because of a PCI config read during PCI scan.

I'm using a custom hardware (based on LS1043ARDB) having a PEX8112 PCIe-to-PCI bridge connected to the LS1043A to have a PCI slot for legacy devices. This bridge only supports PCI-Compatible config accesses (offset 0x00-0xFF).

I would guess the PEX8112 itself has 4K of config space, but it only forwards 256 bytes of config space to the conventional PCI secondary bus.

Quoted 35 lines
Quoted 35 lines
Quoted 20 lines

On this PCI slot I connect a PCI module made of a PCI-to-PCIe bridge plus PCIe devices behind.

The problem occurs when the kernel probes the PCIe devices : as they are PCIe devices, the kernel does a PCI config read access at offset 0x100 to check if "PCIe extended capability registers" are accessible (see drivers/pci/probe.c, function pci_cfg_space_size_ext()). Unfortunately the PEX8112 PCIe-to-PCI bridge that is in the path reports an error to the CPU for this access, and it seems there's no way to disable that on this bridge.

The first workaround I found was to patch drivers/pci/host/pci-layerscape.c to have PCIE_ABSERR_SETTING set to 0x9400 instead of 0x9401 (for PCIE_ABSERR register) to disable error reporting. This only impacts an NXP part of the Linux kernel code, but I'm not sure this is a good idea (however it seems to be like that on Intel platforms where even MEM accesses to a no-device address return FF without any error).

Quoted 9 lines

I've also tried another workaround that works : patch drivers/pci/probe.c to use bus_flags to remember if a bus is behind a bridge without extended address capability, to avoid PCi config read accesses at offset 0x100 in pci_cfg_space_size() / pci_cfg_space_size_ext(). But this patch impacts the generic PCI probe method of Linux.

Any Idea to properly handle that issue ?

This seems like a rather unusual configuration, but I guess that if the first bridge/switch advertises its inability to support extended config space accesses, we should not be performing them on any of its subordinate buses. How does the PEX8112 advertise this limitation?

Quoted 8 lines
Quoted 4 lines

That said, I wonder if it is reasonable in the first place to expect that a PCIe device works as expected passing through a legacy PCI layer like that.

The PEX8112 PCIe-to-PCI bridge has capability PCI_CAP_ID_EXP, but has no PCI_CAP_ID_PCIX capability. As I understand the lack of PCI_CAP_ID_PCIX is advertising this limitation on the PCI side (no support for PCI config offset >=0x100).

Sounds right to me.

Quoted 4 lines

Also I guess in the case of a bridge having PCI_CAP_ID_PCIX, this limitation would be advertised by the lack of PCI_X_STATUS_266MHZ and PCI_X_STATUS_533MHZ (as done in drivers/pci/probe.c at pci_cfg_space_size())

Also sounds right. Per the PCI-X spec, checking for PCI_X_STATUS_266MHZ should be enough, but it shouldn't hurt to check for either PCI_X_STATUS_266MHZ or PCI_X_STATUS_533MHZ.

Quoted 5 lines

I'm currently using the attached patch (for kernel 4.1.35-rt41 from NXP Yocto BSP). It uses bus_flags to remember if a bus is behind a bridge without extended address capability to avoid PCi config accesses at offset >= 0x100. Thanks to this patch I now have a functional system with functional PCI/PCIe devices.

The patch seems like it's looking at the right things, but I don't want to build it into pci_scan_bridge_extend() because that function is much too complicated already.

I'd rather build it into pci_cfg_space_size() or pci_cfg_space_size_ext() somehow. Maybe something along these lines? This doesn't account for the case of a PCIe-to-PCI-X Mode 2 bridge; in that case, I think all 4K would be accessible on the PCI-X side.

drivers/pci/probe.c +6 -1
index ac91b6fd0bcd..d8b091f0bcd1 100644
@@ -1367,7 +1367,7 @@ static bool pci_ext_cfg_is_aliased(struct pci_dev *dev)
   * pci_cfg_space_size - Get the configuration space size of the PCI device
   * @dev: PCI device
   *
- * Regular PCI devices have 256 bytes, but PCI-X 2 and PCI Express devices
+ * Regular PCI devices have 256 bytes, but PCI-X Mode 2 and PCI Express devices
   * have 4096 bytes.  Even if the device is capable, that doesn't mean we can
   * access it.  Maybe we don't have a way to generate extended config space
   * accesses, or the device is behind a reverse Express bridge.  So we try
@@ -1376,9 +1376,14 @@ static bool pci_ext_cfg_is_aliased(struct pci_dev *dev)
   */
  static int pci_cfg_space_size_ext(struct pci_dev *dev)
  {
+	struct pci_dev *bridge = pci_upstream_bridge(dev);
  	u32 status;
  	int pos = PCI_CFG_SPACE_SIZE;
 
+	if (bridge && pci_is_pcie(bridge) &&
+	    pci_pcie_type(bridge) == PCI_EXP_TYPE_PCI_BRIDGE)
+		return PCI_CFG_SPACE_SIZE;
+
  	if (pci_read_config_dword(dev, pos, &status) != PCIBIOS_SUCCESSFUL)
  		return PCI_CFG_SPACE_SIZE;
  	if (status == 0xffffffff || pci_ext_cfg_is_aliased(dev))
Quoted 46 lines
linux/pci.h renamed from linux/pci.h.orig +1 -0
@@ -193,6 +193,7 @@
  enum pci_bus_flags {
  	PCI_BUS_FLAGS_NO_MSI   = (__force pci_bus_flags_t) 1,
  	PCI_BUS_FLAGS_NO_MMRBC = (__force pci_bus_flags_t) 2,
+	PCI_BUS_FLAGS_COMPAT_CFG_SPACE = (__force pci_bus_flags_t) 4,
  };
 
  /* These values come from the PCI Express Spec */
pci/probe.c renamed from pci/probe.c.orig +22 -0
@@ -827,6 +827,28 @@
  			child->primary = primary;
  			pci_bus_insert_busn_res(child, secondary, subordinate);
  			child->bridge_ctl = bctl;
+
+			{
+				int pos;
+				u32 status;
+				bool pci_compat_cfg_space = false;
+
+				if (!pci_is_pcie(dev) || (pci_pcie_type(dev) == PCI_EXP_TYPE_PCIE_BRIDGE) || (pci_pcie_type(dev) == PCI_EXP_TYPE_PCI_BRIDGE)) {
+					/* for PCI/PCI bridges, or PCIe/PCI bridge in forward or reverse mode, we have to check for PCI-X capabilities */
+					pos = pci_find_capability(dev, PCI_CAP_ID_PCIX);
+					if (pos) {
+						pci_read_config_dword(dev, pos + PCI_X_STATUS, &status);
+						if (!(status & (PCI_X_STATUS_266MHZ | PCI_X_STATUS_533MHZ)))
+							pci_compat_cfg_space = true;
+					} else {
+						pci_compat_cfg_space = true;
+					}
+					if (pci_compat_cfg_space) {
+						dev_info(&dev->dev, "[%04x:%04x] Child bus limited to PCI-Compatible config space\n", dev->vendor, dev->device);
+						child->bus_flags |= PCI_BUS_FLAGS_COMPAT_CFG_SPACE;
+					}
+				}
+			}
  		}
 
  		cmax = pci_scan_child_bus(child);
@@ -1098,6 +1120,11 @@
  			goto fail;
  	}
 
Quoted 8 lines

pci/probe.c line 1130

Quoted 8 lines
pci/probe.c renamed from pci/probe.c.orig +5 -0
+	if (dev->bus->bus_flags & PCI_BUS_FLAGS_COMPAT_CFG_SPACE) {
+		dev_info(&dev->dev, "[%04x:%04x] PCI-Compatible config space only due to parent bus(es)\n", dev->vendor, dev->device);
+		return PCI_CFG_SPACE_SIZE;
+	}
+
  	return pci_cfg_space_size_ext(dev);
 
   fail:

Bjorn,
If I'm right about your proposed patch to pci_cfg_space_size_ext(), *bridge is pointing to the upper device of device *dev being checked. I understand the purpose, but I think this fails for my config that is :

LS1043 PCIe root -> PEX8112 PCIe-to-PCI bridge -> PMC slot connector -> PCI-to-PCIe bridge -> PCIe switch (4 ports) -> 4 PCIe devices (one on each port)

because :
- when pci_cfg_space_size_ext() is run on the 4 PCIe devices, *bridge is the PCIe switch which is not matching PCI_EXP_TYPE_PCI_BRIDGE. In this case *bridge should also be checked for the parent bus of the PCIe switch, and so on.
- when pci_cfg_space_size_ext() is run for the PCI-to-PCIe bridge, *bridge is the PEX8112 that is also not matching PCI_EXP_TYPE_PCI_BRIDGE but PCI_EXP_TYPE_PCIE_BRIDGE. This leads to a config access at offset 0x100 to the PCI-to-PCIe bridge, so a crash (because of the PEX8112)

I think setting a bit in bus_flags when creating a child bus is very efficient because once set it is automatically inherited by all child buses and then the only thing that pci_cfg_space_size() has to do for each device is to check for this bit. Also this PCI_BUS_FLAGS_COMPAT_CFG_SPACE flag is actually a bus property that is compliant with the purpose of bus_flags.

I agree that pci_scan_bridge_extend() is already too complicated, so would you be okay to only add one line to it :
pci_bus_set_compat_cfg_space(child);
and put all the code I suggested in this new function pci_bus_set_compat_cfg_space() ? (also supporting PCI-X Mode 2 devices) Improvement : this function can return immediately if the child bus has already inherited the flag from its parent.

#5 lore raw

Le 30/04/2018 10:46, Gilles BULOZ a ?crit :

Quoted 183 lines

Le 27/04/2018 18:56, Bjorn Helgaas a ?crit :

Quoted 160 lines · Gilles Buloz
Quoted 16 lines

Le 27/04/2018 10:43, Ard Biesheuvel a ?crit :

Quoted 15 lines

(add Bjorn and linux-pci)

Quoted 12 lines · Gilles Buloz

Dear developers,

I currently have two functional workarounds for this issue but would like to know which one you would recommend, if any :-) I'm using a LS1043A CPU (NXP QorIQ Layerscape) and get a "synchronous external abort" when booting because of a PCI config read during PCI scan.

I'm using a custom hardware (based on LS1043ARDB) having a PEX8112 PCIe-to-PCI bridge connected to the LS1043A to have a PCI slot for legacy devices. This bridge only supports PCI-Compatible config accesses (offset 0x00-0xFF).

I would guess the PEX8112 itself has 4K of config space, but it only forwards 256 bytes of config space to the conventional PCI secondary bus.

Quoted 35 lines
Quoted 35 lines
Quoted 20 lines

On this PCI slot I connect a PCI module made of a PCI-to-PCIe bridge plus PCIe devices behind.

The problem occurs when the kernel probes the PCIe devices : as they are PCIe devices, the kernel does a PCI config read access at offset 0x100 to check if "PCIe extended capability registers" are accessible (see drivers/pci/probe.c, function pci_cfg_space_size_ext()). Unfortunately the PEX8112 PCIe-to-PCI bridge that is in the path reports an error to the CPU for this access, and it seems there's no way to disable that on this bridge.

The first workaround I found was to patch drivers/pci/host/pci-layerscape.c to have PCIE_ABSERR_SETTING set to 0x9400 instead of 0x9401 (for PCIE_ABSERR register) to disable error reporting. This only impacts an NXP part of the Linux kernel code, but I'm not sure this is a good idea (however it seems to be like that on Intel platforms where even MEM accesses to a no-device address return FF without any error).

Quoted 9 lines

I've also tried another workaround that works : patch drivers/pci/probe.c to use bus_flags to remember if a bus is behind a bridge without extended address capability, to avoid PCi config read accesses at offset 0x100 in pci_cfg_space_size() / pci_cfg_space_size_ext(). But this patch impacts the generic PCI probe method of Linux.

Any Idea to properly handle that issue ?

This seems like a rather unusual configuration, but I guess that if the first bridge/switch advertises its inability to support extended config space accesses, we should not be performing them on any of its subordinate buses. How does the PEX8112 advertise this limitation?

Quoted 8 lines
Quoted 4 lines

That said, I wonder if it is reasonable in the first place to expect that a PCIe device works as expected passing through a legacy PCI layer like that.

The PEX8112 PCIe-to-PCI bridge has capability PCI_CAP_ID_EXP, but has no PCI_CAP_ID_PCIX capability. As I understand the lack of PCI_CAP_ID_PCIX is advertising this limitation on the PCI side (no support for PCI config offset >=0x100).

Sounds right to me.

Quoted 4 lines

Also I guess in the case of a bridge having PCI_CAP_ID_PCIX, this limitation would be advertised by the lack of PCI_X_STATUS_266MHZ and PCI_X_STATUS_533MHZ (as done in drivers/pci/probe.c at pci_cfg_space_size())

Also sounds right. Per the PCI-X spec, checking for PCI_X_STATUS_266MHZ should be enough, but it shouldn't hurt to check for either PCI_X_STATUS_266MHZ or PCI_X_STATUS_533MHZ.

Quoted 5 lines

I'm currently using the attached patch (for kernel 4.1.35-rt41 from NXP Yocto BSP). It uses bus_flags to remember if a bus is behind a bridge without extended address capability to avoid PCi config accesses at offset >= 0x100. Thanks to this patch I now have a functional system with functional PCI/PCIe devices.

The patch seems like it's looking at the right things, but I don't want to build it into pci_scan_bridge_extend() because that function is much too complicated already.

I'd rather build it into pci_cfg_space_size() or pci_cfg_space_size_ext() somehow. Maybe something along these lines? This doesn't account for the case of a PCIe-to-PCI-X Mode 2 bridge; in that case, I think all 4K would be accessible on the PCI-X side.

drivers/pci/probe.c +5 -1
index ac91b6fd0bcd..d8b091f0bcd1 100644
@@ -1367,7 +1367,7 @@ static bool pci_ext_cfg_is_aliased(struct pci_dev *dev)
   * pci_cfg_space_size - Get the configuration space size of the PCI device
   * @dev: PCI device
   *
- * Regular PCI devices have 256 bytes, but PCI-X 2 and PCI Express devices
+ * Regular PCI devices have 256 bytes, but PCI-X Mode 2 and PCI Express devices
   * have 4096 bytes.  Even if the device is capable, that doesn't mean we can
   * access it.  Maybe we don't have a way to generate extended config space
   * accesses, or the device is behind a reverse Express bridge.  So we try
@@ -1376,9 +1376,14 @@ static bool pci_ext_cfg_is_aliased(struct pci_dev *dev)
   */
  static int pci_cfg_space_size_ext(struct pci_dev *dev)
  {
+    struct pci_dev *bridge = pci_upstream_bridge(dev);
      u32 status;
      int pos = PCI_CFG_SPACE_SIZE;
  +    if (bridge && pci_is_pcie(bridge) &&
+        pci_pcie_type(bridge) == PCI_EXP_TYPE_PCI_BRIDGE)
+        return PCI_CFG_SPACE_SIZE;
+
      if (pci_read_config_dword(dev, pos, &status) != PCIBIOS_SUCCESSFUL)
          return PCI_CFG_SPACE_SIZE;
      if (status == 0xffffffff || pci_ext_cfg_is_aliased(dev))
Quoted 45 lines
linux/pci.h 2018-03-26 16:51:27.660000000 +0000 renamed from linux/pci.h.orig 2018-03-26 16:51:18.050000000 +0000 +1 -0
@@ -193,6 +193,7 @@
  enum pci_bus_flags {
      PCI_BUS_FLAGS_NO_MSI   = (__force pci_bus_flags_t) 1,
      PCI_BUS_FLAGS_NO_MMRBC = (__force pci_bus_flags_t) 2,
+    PCI_BUS_FLAGS_COMPAT_CFG_SPACE = (__force pci_bus_flags_t) 4,
  };
    /* These values come from the PCI Express Spec */
pci/probe.c 2018-03-26 16:54:30.830000000 +0000 renamed from pci/probe.c.orig 2018-01-22 09:29:52.000000000 +0000 +8 -0
@@ -827,6 +827,28 @@
              child->primary = primary;
              pci_bus_insert_busn_res(child, secondary, subordinate);
              child->bridge_ctl = bctl;
+
+            {
+                int pos;
+                u32 status;
+                bool pci_compat_cfg_space = false;
+
+                if (!pci_is_pcie(dev) || (pci_pcie_type(dev) == PCI_EXP_TYPE_PCIE_BRIDGE) || (pci_pcie_type(dev) == 
 PCI_EXP_TYPE_PCI_BRIDGE)) {
+                    /* for PCI/PCI bridges, or PCIe/PCI bridge in forward or reverse mode, we have to check for PCI-X 

capabilities */

pci/probe.c 2018-03-26 16:54:30.830000000 +0000 renamed from pci/probe.c.orig 2018-01-22 09:29:52.000000000 +0000 +10 -0
+                    pos = pci_find_capability(dev, PCI_CAP_ID_PCIX);
+                    if (pos) {
+                        pci_read_config_dword(dev, pos + PCI_X_STATUS, &status);
+                        if (!(status & (PCI_X_STATUS_266MHZ | PCI_X_STATUS_533MHZ)))
+                            pci_compat_cfg_space = true;
+                    } else {
+                        pci_compat_cfg_space = true;
+                    }
+                    if (pci_compat_cfg_space) {
+                        dev_info(&dev->dev, "[%04x:%04x] Child bus limited to PCI-Compatible config space\n", dev->vendor, 

dev->device);

pci/probe.c 2018-03-26 16:54:30.830000000 +0000 renamed from pci/probe.c.orig 2018-01-22 09:29:52.000000000 +0000 +4 -0
+                        child->bus_flags |= PCI_BUS_FLAGS_COMPAT_CFG_SPACE;
+                    }
+                }
+            }
          }
            cmax = pci_scan_child_bus(child);
@@ -1098,6 +1120,11 @@
              goto fail;
Quoted 8 lines

pci/probe.c 2018-03-26 16:54:30.830000000 +0000 line 1128

Quoted 8 lines
pci/probe.c 2018-03-26 16:54:30.830000000 +0000 renamed from pci/probe.c.orig 2018-01-22 09:29:52.000000000 +0000 +4 -0
      }
  +    if (dev->bus->bus_flags & PCI_BUS_FLAGS_COMPAT_CFG_SPACE) {
+        dev_info(&dev->dev, "[%04x:%04x] PCI-Compatible config space only due to parent bus(es)\n", dev->vendor, dev->device);
+        return PCI_CFG_SPACE_SIZE;
+    }
+
      return pci_cfg_space_size_ext(dev);
     fail:

Bjorn,
If I'm right about your proposed patch to pci_cfg_space_size_ext(), *bridge is pointing to the upper device of device *dev being checked. I understand the purpose, but I think this fails for my config that is :

LS1043 PCIe root -> PEX8112 PCIe-to-PCI bridge -> PMC slot connector -> PCI-to-PCIe bridge -> PCIe switch (4 ports) -> 4 PCIe devices (one on each port)

because :
- when pci_cfg_space_size_ext() is run on the 4 PCIe devices, *bridge is the PCIe switch which is not matching PCI_EXP_TYPE_PCI_BRIDGE. In this case *bridge should also be checked for the parent bus of the PCIe switch, and so on.
- when pci_cfg_space_size_ext() is run for the PCI-to-PCIe bridge, *bridge is the PEX8112 that is also not matching PCI_EXP_TYPE_PCI_BRIDGE but PCI_EXP_TYPE_PCIE_BRIDGE. This leads to a config access at offset 0x100 to the PCI-to-PCIe bridge, so a crash (because of the PEX8112)

Quoted 9 lines

I think setting a bit in bus_flags when creating a child bus is very efficient because once set it is automatically inherited by all child buses and then the only thing that pci_cfg_space_size() has to do for each device is to check for this bit. Also this PCI_BUS_FLAGS_COMPAT_CFG_SPACE flag is actually a bus property that is compliant with the purpose of bus_flags.

I agree that pci_scan_bridge_extend() is already too complicated, so would you be okay to only add one line to it :
pci_bus_set_compat_cfg_space(child);
and put all the code I suggested in this new function pci_bus_set_compat_cfg_space() ? (also supporting PCI-X Mode 2 devices) Improvement : this function can return immediately if the child bus has already inherited the flag from its parent.

I mean something like the attached patch I tested this morning...
Sorry, this is for old kernel 4.1.35 but just to clarify what I propose (also applies to 4.16.6 by changing value of PCI_BUS_FLAGS_COMPAT_CFG_SPACE in pci.h to 8).
-------------- next part --------------
A non-text attachment was scrubbed...
Name: cfgspace2_4.1.35.patch
Type: text/x-patch
Size: 2201 bytes
Desc: cfgspace2_4.1.35.patch
URL: <http://lists.infradead.org/pipermail/linux-arm-kernel/attachments/20180430/de70abf8/attachment-0001.bin>

#6 lore raw
Quoted 196 lines · Gilles Buloz

Le 30/04/2018 10:46, Gilles BULOZ a ?crit :

Quoted 195 lines

Le 27/04/2018 18:56, Bjorn Helgaas a ?crit :

Quoted 160 lines · Gilles Buloz
Quoted 16 lines

Le 27/04/2018 10:43, Ard Biesheuvel a ?crit :

Quoted 15 lines

(add Bjorn and linux-pci)

Quoted 12 lines · Gilles Buloz

Dear developers,

I currently have two functional workarounds for this issue but would like to know which one you would recommend, if any :-) I'm using a LS1043A CPU (NXP QorIQ Layerscape) and get a "synchronous external abort" when booting because of a PCI config read during PCI scan.

I'm using a custom hardware (based on LS1043ARDB) having a PEX8112 PCIe-to-PCI bridge connected to the LS1043A to have a PCI slot for legacy devices. This bridge only supports PCI-Compatible config accesses (offset 0x00-0xFF).

I would guess the PEX8112 itself has 4K of config space, but it only forwards 256 bytes of config space to the conventional PCI secondary bus.

Quoted 35 lines
Quoted 35 lines
Quoted 20 lines

On this PCI slot I connect a PCI module made of a PCI-to-PCIe bridge plus PCIe devices behind.

The problem occurs when the kernel probes the PCIe devices : as they are PCIe devices, the kernel does a PCI config read access at offset 0x100 to check if "PCIe extended capability registers" are accessible (see drivers/pci/probe.c, function pci_cfg_space_size_ext()). Unfortunately the PEX8112 PCIe-to-PCI bridge that is in the path reports an error to the CPU for this access, and it seems there's no way to disable that on this bridge.

The first workaround I found was to patch drivers/pci/host/pci-layerscape.c to have PCIE_ABSERR_SETTING set to 0x9400 instead of 0x9401 (for PCIE_ABSERR register) to disable error reporting. This only impacts an NXP part of the Linux kernel code, but I'm not sure this is a good idea (however it seems to be like that on Intel platforms where even MEM accesses to a no-device address return FF without any error).

Quoted 9 lines

I've also tried another workaround that works : patch drivers/pci/probe.c to use bus_flags to remember if a bus is behind a bridge without extended address capability, to avoid PCi config read accesses at offset 0x100 in pci_cfg_space_size() / pci_cfg_space_size_ext(). But this patch impacts the generic PCI probe method of Linux.

Any Idea to properly handle that issue ?

This seems like a rather unusual configuration, but I guess that if the first bridge/switch advertises its inability to support extended config space accesses, we should not be performing them on any of its subordinate buses. How does the PEX8112 advertise this limitation?

Quoted 8 lines
Quoted 4 lines

That said, I wonder if it is reasonable in the first place to expect that a PCIe device works as expected passing through a legacy PCI layer like that.

The PEX8112 PCIe-to-PCI bridge has capability PCI_CAP_ID_EXP, but has no PCI_CAP_ID_PCIX capability. As I understand the lack of PCI_CAP_ID_PCIX is advertising this limitation on the PCI side (no support for PCI config offset >=0x100).

Sounds right to me.

Quoted 4 lines

Also I guess in the case of a bridge having PCI_CAP_ID_PCIX, this limitation would be advertised by the lack of PCI_X_STATUS_266MHZ and PCI_X_STATUS_533MHZ (as done in drivers/pci/probe.c at pci_cfg_space_size())

Also sounds right. Per the PCI-X spec, checking for PCI_X_STATUS_266MHZ should be enough, but it shouldn't hurt to check for either PCI_X_STATUS_266MHZ or PCI_X_STATUS_533MHZ.

Quoted 5 lines

I'm currently using the attached patch (for kernel 4.1.35-rt41 from NXP Yocto BSP). It uses bus_flags to remember if a bus is behind a bridge without extended address capability to avoid PCi config accesses at offset >= 0x100. Thanks to this patch I now have a functional system with functional PCI/PCIe devices.

The patch seems like it's looking at the right things, but I don't want to build it into pci_scan_bridge_extend() because that function is much too complicated already.

I'd rather build it into pci_cfg_space_size() or pci_cfg_space_size_ext() somehow. Maybe something along these lines? This doesn't account for the case of a PCIe-to-PCI-X Mode 2 bridge; in that case, I think all 4K would be accessible on the PCI-X side.

drivers/pci/probe.c +5 -1
index ac91b6fd0bcd..d8b091f0bcd1 100644
@@ -1367,7 +1367,7 @@ static bool pci_ext_cfg_is_aliased(struct pci_dev *dev)
   * pci_cfg_space_size - Get the configuration space size of the PCI device
   * @dev: PCI device
   *
- * Regular PCI devices have 256 bytes, but PCI-X 2 and PCI Express devices
+ * Regular PCI devices have 256 bytes, but PCI-X Mode 2 and PCI Express devices
   * have 4096 bytes.  Even if the device is capable, that doesn't mean we can
   * access it.  Maybe we don't have a way to generate extended config space
   * accesses, or the device is behind a reverse Express bridge.  So we try
@@ -1376,9 +1376,14 @@ static bool pci_ext_cfg_is_aliased(struct pci_dev *dev)
   */
  static int pci_cfg_space_size_ext(struct pci_dev *dev)
  {
+    struct pci_dev *bridge = pci_upstream_bridge(dev);
      u32 status;
      int pos = PCI_CFG_SPACE_SIZE;
  +    if (bridge && pci_is_pcie(bridge) &&
+        pci_pcie_type(bridge) == PCI_EXP_TYPE_PCI_BRIDGE)
+        return PCI_CFG_SPACE_SIZE;
+
      if (pci_read_config_dword(dev, pos, &status) != PCIBIOS_SUCCESSFUL)
          return PCI_CFG_SPACE_SIZE;
      if (status == 0xffffffff || pci_ext_cfg_is_aliased(dev))
Quoted 45 lines
linux/pci.h 2018-03-26 16:51:27.660000000 +0000 renamed from linux/pci.h.orig 2018-03-26 16:51:18.050000000 +0000 +1 -0
@@ -193,6 +193,7 @@
  enum pci_bus_flags {
      PCI_BUS_FLAGS_NO_MSI   = (__force pci_bus_flags_t) 1,
      PCI_BUS_FLAGS_NO_MMRBC = (__force pci_bus_flags_t) 2,
+    PCI_BUS_FLAGS_COMPAT_CFG_SPACE = (__force pci_bus_flags_t) 4,
  };
    /* These values come from the PCI Express Spec */
pci/probe.c 2018-03-26 16:54:30.830000000 +0000 renamed from pci/probe.c.orig 2018-01-22 09:29:52.000000000 +0000 +8 -0
@@ -827,6 +827,28 @@
              child->primary = primary;
              pci_bus_insert_busn_res(child, secondary, subordinate);
              child->bridge_ctl = bctl;
+
+            {
+                int pos;
+                u32 status;
+                bool pci_compat_cfg_space = false;
+
+                if (!pci_is_pcie(dev) || (pci_pcie_type(dev) == PCI_EXP_TYPE_PCIE_BRIDGE) || (pci_pcie_type(dev) == 
 PCI_EXP_TYPE_PCI_BRIDGE)) {
+                    /* for PCI/PCI bridges, or PCIe/PCI bridge in forward or reverse mode, we have to check for PCI-X 

capabilities */

pci/probe.c 2018-03-26 16:54:30.830000000 +0000 renamed from pci/probe.c.orig 2018-01-22 09:29:52.000000000 +0000 +10 -0
+                    pos = pci_find_capability(dev, PCI_CAP_ID_PCIX);
+                    if (pos) {
+                        pci_read_config_dword(dev, pos + PCI_X_STATUS, &status);
+                        if (!(status & (PCI_X_STATUS_266MHZ | PCI_X_STATUS_533MHZ)))
+                            pci_compat_cfg_space = true;
+                    } else {
+                        pci_compat_cfg_space = true;
+                    }
+                    if (pci_compat_cfg_space) {
+                        dev_info(&dev->dev, "[%04x:%04x] Child bus limited to PCI-Compatible config space\n", dev->vendor, 

dev->device);

pci/probe.c 2018-03-26 16:54:30.830000000 +0000 renamed from pci/probe.c.orig 2018-01-22 09:29:52.000000000 +0000 +4 -0
+                        child->bus_flags |= PCI_BUS_FLAGS_COMPAT_CFG_SPACE;
+                    }
+                }
+            }
          }
            cmax = pci_scan_child_bus(child);
@@ -1098,6 +1120,11 @@
              goto fail;
Quoted 8 lines

pci/probe.c 2018-03-26 16:54:30.830000000 +0000 line 1128

Quoted 8 lines
pci/probe.c 2018-03-26 16:54:30.830000000 +0000 renamed from pci/probe.c.orig 2018-01-22 09:29:52.000000000 +0000 +4 -0
      }
  +    if (dev->bus->bus_flags & PCI_BUS_FLAGS_COMPAT_CFG_SPACE) {
+        dev_info(&dev->dev, "[%04x:%04x] PCI-Compatible config space only due to parent bus(es)\n", dev->vendor, dev->device);
+        return PCI_CFG_SPACE_SIZE;
+    }
+
      return pci_cfg_space_size_ext(dev);
     fail:

Bjorn,
If I'm right about your proposed patch to pci_cfg_space_size_ext(), *bridge is pointing to the upper device of device *dev being checked. I understand the purpose, but I think this fails for my config that is :

LS1043 PCIe root -> PEX8112 PCIe-to-PCI bridge -> PMC slot connector -> PCI-to-PCIe bridge -> PCIe switch (4 ports) -> 4 PCIe devices (one on each port)

because :
- when pci_cfg_space_size_ext() is run on the 4 PCIe devices, *bridge is the PCIe switch which is not matching PCI_EXP_TYPE_PCI_BRIDGE. In this case *bridge should also be checked for the parent bus of the PCIe switch, and so on.
- when pci_cfg_space_size_ext() is run for the PCI-to-PCIe bridge, *bridge is the PEX8112 that is also not matching PCI_EXP_TYPE_PCI_BRIDGE but PCI_EXP_TYPE_PCIE_BRIDGE. This leads to a config access at offset 0x100 to the PCI-to-PCIe bridge, so a crash (because of the PEX8112)

I think setting a bit in bus_flags when creating a child bus is very efficient because once set it is automatically inherited by all child buses and then the only thing that pci_cfg_space_size() has to do for each device is to check for this bit. Also this PCI_BUS_FLAGS_COMPAT_CFG_SPACE flag is actually a bus property that is compliant with the purpose of bus_flags.

Yeah, it needs to be inherited somehow, and I don't like the idea of traversing up the tree, so I prefer your idea. Although I don't actually see the inheritance in the patch below -- I thought there would be something like this:

  dev = bus->self;
  parent_bus = dev->bus;
  if (parent_bus && parent_bus->bus_flags & PCI_BUS_FLAGS_COMPAT_CFG_SPACE)
    bus->bus_flags |= PCI_BUS_FLAGS_COMPAT_CFG_SPACE;

pci_scan_bridge_extend() calls pci_add_new_bus() from two places. You added a call to pci_bus_check_compat_cfg_space() at one of them, and it's not obvious why we wouldn't need it at the other place, too.

Can you set this up in pci_alloc_child_bus()? If you can put it there, it would be clear that every time we allocate a secondary bus, we figure out whether extended config space is accessible on that bus.

That doesn't cover the root bus case, where we currently assume the host bridge can generate config accesses to all config space supported by devices on the root bus. But we don't have a problem there, so I guess we don't need to worry about it now.

If you can put it in pci_alloc_child_bus(), could you make your new function return a boolean, e.g., pci_bus_ext_cfg_accessible(), or similar, and then use the result to set the PCI_BUS_FLAGS_COMPAT_CFG_SPACE flag? Names like "*_check_*()" don't tell the reader much about what's happening.

Quoted 86 lines
Quoted 9 lines

I agree that pci_scan_bridge_extend() is already too complicated, so would you be okay to only add one line to it :
pci_bus_set_compat_cfg_space(child);
and put all the code I suggested in this new function pci_bus_set_compat_cfg_space() ? (also supporting PCI-X Mode 2 devices)

Improvement : this function can return immediately if the child bus has already inherited the flag from its parent.

I mean something like the attached patch I tested this morning... Sorry, this is for old kernel 4.1.35 but just to clarify what I propose (also applies to 4.16.6 by changing value of PCI_BUS_FLAGS_COMPAT_CFG_SPACE in pci.h to 8).

linux/pci.h renamed from linux/pci.h.orig +1 -0
@@ -193,6 +193,7 @@
 enum pci_bus_flags {
 	PCI_BUS_FLAGS_NO_MSI   = (__force pci_bus_flags_t) 1,
 	PCI_BUS_FLAGS_NO_MMRBC = (__force pci_bus_flags_t) 2,
+	PCI_BUS_FLAGS_COMPAT_CFG_SPACE = (__force pci_bus_flags_t) 4,
 };
 
 /* These values come from the PCI Express Spec */
pci/probe.c renamed from pci/probe.c.orig +33 -0
@@ -754,6 +754,35 @@
 					 PCI_EXP_RTCTL_CRSSVE);
 }
 
+static void pci_bus_check_compat_cfg_space(struct pci_bus *bus)
+{
+	struct pci_dev *dev = bus->self;
+	bool pci_compat_cfg_space = false;
+	int pos;
+	u32 status;
+
+	if (bus->bus_flags & PCI_BUS_FLAGS_COMPAT_CFG_SPACE)
+		return;
+
+	if (!pci_is_pcie(dev) || /* PCI/PCI bridge */
+	    (pci_pcie_type(dev) == PCI_EXP_TYPE_PCIE_BRIDGE) || /* PCIe/PCI bridge in forward mode */
+	    (pci_pcie_type(dev) == PCI_EXP_TYPE_PCI_BRIDGE)) {  /* PCIe/PCI bridge in reverse mode */
+		pos = pci_find_capability(dev, PCI_CAP_ID_PCIX);
+		if (pos) {
+			pci_read_config_dword(dev, pos + PCI_X_STATUS, &status);
+			if (!(status & (PCI_X_STATUS_266MHZ | PCI_X_STATUS_533MHZ)))
+				pci_compat_cfg_space = true;
+		} else {
+			pci_compat_cfg_space = true;
+		}
+		if (pci_compat_cfg_space) {
+			dev_info(&dev->dev, "bus %02x limited to PCI-Compatible config space\n",
+				 bus->number);
+			bus->bus_flags |= PCI_BUS_FLAGS_COMPAT_CFG_SPACE;
+		}
+	}
+}
+
 /*
  * If it's a bridge, configure it and scan the bus behind it.
  * For CardBus bridges, we don't scan behind as the devices will
@@ -827,6 +856,7 @@
 			child->primary = primary;
 			pci_bus_insert_busn_res(child, secondary, subordinate);
 			child->bridge_ctl = bctl;
+			pci_bus_check_compat_cfg_space(child);
 		}
 
 		cmax = pci_scan_child_bus(child);
@@ -1084,6 +1114,9 @@
 	u32 status;
 	u16 class;
 
+	if (dev->bus->bus_flags & PCI_BUS_FLAGS_COMPAT_CFG_SPACE)
+		return PCI_CFG_SPACE_SIZE;
+
 	class = dev->class >> 8;
 	if (class == PCI_CLASS_BRIDGE_HOST)
 		return pci_cfg_space_size_ext(dev);

_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel at lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel

#7 lore raw

Le 30/04/2018 19:04, Bjorn Helgaas a ?crit :

Quoted 299 lines · Gilles Buloz
Quoted 196 lines

Le 30/04/2018 10:46, Gilles BULOZ a ?crit :

Quoted 195 lines

Le 27/04/2018 18:56, Bjorn Helgaas a ?crit :

Quoted 160 lines · Gilles Buloz
Quoted 16 lines

Le 27/04/2018 10:43, Ard Biesheuvel a ?crit :

Quoted 15 lines

(add Bjorn and linux-pci)

Quoted 12 lines · Gilles Buloz

Dear developers,

I currently have two functional workarounds for this issue but would like to know which one you would recommend, if any :-) I'm using a LS1043A CPU (NXP QorIQ Layerscape) and get a "synchronous external abort" when booting because of a PCI config read during PCI scan.

I'm using a custom hardware (based on LS1043ARDB) having a PEX8112 PCIe-to-PCI bridge connected to the LS1043A to have a PCI slot for legacy devices. This bridge only supports PCI-Compatible config accesses (offset 0x00-0xFF).

I would guess the PEX8112 itself has 4K of config space, but it only forwards 256 bytes of config space to the conventional PCI secondary bus.

Quoted 35 lines
Quoted 35 lines
Quoted 20 lines

On this PCI slot I connect a PCI module made of a PCI-to-PCIe bridge plus PCIe devices behind.

The problem occurs when the kernel probes the PCIe devices : as they are PCIe devices, the kernel does a PCI config read access at offset 0x100 to check if "PCIe extended capability registers" are accessible (see drivers/pci/probe.c, function pci_cfg_space_size_ext()). Unfortunately the PEX8112 PCIe-to-PCI bridge that is in the path reports an error to the CPU for this access, and it seems there's no way to disable that on this bridge.

The first workaround I found was to patch drivers/pci/host/pci-layerscape.c to have PCIE_ABSERR_SETTING set to 0x9400 instead of 0x9401 (for PCIE_ABSERR register) to disable error reporting. This only impacts an NXP part of the Linux kernel code, but I'm not sure this is a good idea (however it seems to be like that on Intel platforms where even MEM accesses to a no-device address return FF without any error).

Quoted 9 lines

I've also tried another workaround that works : patch drivers/pci/probe.c to use bus_flags to remember if a bus is behind a bridge without extended address capability, to avoid PCi config read accesses at offset 0x100 in pci_cfg_space_size() / pci_cfg_space_size_ext(). But this patch impacts the generic PCI probe method of Linux.

Any Idea to properly handle that issue ?

This seems like a rather unusual configuration, but I guess that if the first bridge/switch advertises its inability to support extended config space accesses, we should not be performing them on any of its subordinate buses. How does the PEX8112 advertise this limitation?

Quoted 8 lines
Quoted 4 lines

That said, I wonder if it is reasonable in the first place to expect that a PCIe device works as expected passing through a legacy PCI layer like that.

The PEX8112 PCIe-to-PCI bridge has capability PCI_CAP_ID_EXP, but has no PCI_CAP_ID_PCIX capability. As I understand the lack of PCI_CAP_ID_PCIX is advertising this limitation on the PCI side (no support for PCI config offset >=0x100).

Sounds right to me.

Quoted 4 lines

Also I guess in the case of a bridge having PCI_CAP_ID_PCIX, this limitation would be advertised by the lack of PCI_X_STATUS_266MHZ and PCI_X_STATUS_533MHZ (as done in drivers/pci/probe.c at pci_cfg_space_size())

Also sounds right. Per the PCI-X spec, checking for PCI_X_STATUS_266MHZ should be enough, but it shouldn't hurt to check for either PCI_X_STATUS_266MHZ or PCI_X_STATUS_533MHZ.

Quoted 5 lines

I'm currently using the attached patch (for kernel 4.1.35-rt41 from NXP Yocto BSP). It uses bus_flags to remember if a bus is behind a bridge without extended address capability to avoid PCi config accesses at offset >= 0x100. Thanks to this patch I now have a functional system with functional PCI/PCIe devices.

The patch seems like it's looking at the right things, but I don't want to build it into pci_scan_bridge_extend() because that function is much too complicated already.

I'd rather build it into pci_cfg_space_size() or pci_cfg_space_size_ext() somehow. Maybe something along these lines? This doesn't account for the case of a PCIe-to-PCI-X Mode 2 bridge; in that case, I think all 4K would be accessible on the PCI-X side.

drivers/pci/probe.c +5 -1
index ac91b6fd0bcd..d8b091f0bcd1 100644
@@ -1367,7 +1367,7 @@ static bool pci_ext_cfg_is_aliased(struct pci_dev *dev)
    * pci_cfg_space_size - Get the configuration space size of the PCI device
    * @dev: PCI device
    *
- * Regular PCI devices have 256 bytes, but PCI-X 2 and PCI Express devices
+ * Regular PCI devices have 256 bytes, but PCI-X Mode 2 and PCI Express devices
    * have 4096 bytes.  Even if the device is capable, that doesn't mean we can
    * access it.  Maybe we don't have a way to generate extended config space
    * accesses, or the device is behind a reverse Express bridge.  So we try
@@ -1376,9 +1376,14 @@ static bool pci_ext_cfg_is_aliased(struct pci_dev *dev)
    */
   static int pci_cfg_space_size_ext(struct pci_dev *dev)
   {
+    struct pci_dev *bridge = pci_upstream_bridge(dev);
       u32 status;
       int pos = PCI_CFG_SPACE_SIZE;
   +    if (bridge && pci_is_pcie(bridge) &&
+        pci_pcie_type(bridge) == PCI_EXP_TYPE_PCI_BRIDGE)
+        return PCI_CFG_SPACE_SIZE;
+
       if (pci_read_config_dword(dev, pos, &status) != PCIBIOS_SUCCESSFUL)
           return PCI_CFG_SPACE_SIZE;
       if (status == 0xffffffff || pci_ext_cfg_is_aliased(dev))
Quoted 45 lines
linux/pci.h 2018-03-26 16:51:27.660000000 +0000 renamed from linux/pci.h.orig 2018-03-26 16:51:18.050000000 +0000 +1 -0
@@ -193,6 +193,7 @@
   enum pci_bus_flags {
       PCI_BUS_FLAGS_NO_MSI   = (__force pci_bus_flags_t) 1,
       PCI_BUS_FLAGS_NO_MMRBC = (__force pci_bus_flags_t) 2,
+    PCI_BUS_FLAGS_COMPAT_CFG_SPACE = (__force pci_bus_flags_t) 4,
   };
     /* These values come from the PCI Express Spec */
pci/probe.c 2018-03-26 16:54:30.830000000 +0000 renamed from pci/probe.c.orig 2018-01-22 09:29:52.000000000 +0000 +8 -0
@@ -827,6 +827,28 @@
               child->primary = primary;
               pci_bus_insert_busn_res(child, secondary, subordinate);
               child->bridge_ctl = bctl;
+
+            {
+                int pos;
+                u32 status;
+                bool pci_compat_cfg_space = false;
+
+                if (!pci_is_pcie(dev) || (pci_pcie_type(dev) == PCI_EXP_TYPE_PCIE_BRIDGE) || (pci_pcie_type(dev) ==
 PCI_EXP_TYPE_PCI_BRIDGE)) {
+                    /* for PCI/PCI bridges, or PCIe/PCI bridge in forward or reverse mode, we have to check for PCI-X

capabilities */

pci/probe.c 2018-03-26 16:54:30.830000000 +0000 renamed from pci/probe.c.orig 2018-01-22 09:29:52.000000000 +0000 +10 -0
+                    pos = pci_find_capability(dev, PCI_CAP_ID_PCIX);
+                    if (pos) {
+                        pci_read_config_dword(dev, pos + PCI_X_STATUS, &status);
+                        if (!(status & (PCI_X_STATUS_266MHZ | PCI_X_STATUS_533MHZ)))
+                            pci_compat_cfg_space = true;
+                    } else {
+                        pci_compat_cfg_space = true;
+                    }
+                    if (pci_compat_cfg_space) {
+                        dev_info(&dev->dev, "[%04x:%04x] Child bus limited to PCI-Compatible config space\n", dev->vendor,

dev->device);

pci/probe.c 2018-03-26 16:54:30.830000000 +0000 renamed from pci/probe.c.orig 2018-01-22 09:29:52.000000000 +0000 +4 -0
+                        child->bus_flags |= PCI_BUS_FLAGS_COMPAT_CFG_SPACE;
+                    }
+                }
+            }
           }
             cmax = pci_scan_child_bus(child);
@@ -1098,6 +1120,11 @@
               goto fail;
Quoted 8 lines

pci/probe.c 2018-03-26 16:54:30.830000000 +0000 line 1128

Quoted 8 lines
pci/probe.c 2018-03-26 16:54:30.830000000 +0000 renamed from pci/probe.c.orig 2018-01-22 09:29:52.000000000 +0000 +4 -0
       }
   +    if (dev->bus->bus_flags & PCI_BUS_FLAGS_COMPAT_CFG_SPACE) {
+        dev_info(&dev->dev, "[%04x:%04x] PCI-Compatible config space only due to parent bus(es)\n", dev->vendor, dev->device);
+        return PCI_CFG_SPACE_SIZE;
+    }
+
       return pci_cfg_space_size_ext(dev);
      fail:

Bjorn,
If I'm right about your proposed patch to pci_cfg_space_size_ext(), *bridge is pointing to the upper device of device *dev being checked. I understand the purpose, but I think this fails for my config that is :

LS1043 PCIe root -> PEX8112 PCIe-to-PCI bridge -> PMC slot connector -> PCI-to-PCIe bridge -> PCIe switch (4 ports) -> 4 PCIe devices (one on each port)

because :
- when pci_cfg_space_size_ext() is run on the 4 PCIe devices, *bridge is the PCIe switch which is not matching PCI_EXP_TYPE_PCI_BRIDGE. In this case *bridge should also be checked for the parent bus of the PCIe switch, and so on.
- when pci_cfg_space_size_ext() is run for the PCI-to-PCIe bridge, *bridge is the PEX8112 that is also not matching PCI_EXP_TYPE_PCI_BRIDGE but PCI_EXP_TYPE_PCIE_BRIDGE. This leads to a config access at offset 0x100 to the PCI-to-PCIe bridge, so a crash (because of the PEX8112)

I think setting a bit in bus_flags when creating a child bus is very efficient because once set it is automatically inherited by all child buses and then the only thing that pci_cfg_space_size() has to do for each device is to check for this bit. Also this PCI_BUS_FLAGS_COMPAT_CFG_SPACE flag is actually a bus property that is compliant with the purpose of bus_flags.

Yeah, it needs to be inherited somehow, and I don't like the idea of traversing up the tree, so I prefer your idea. Although I don't actually see the inheritance in the patch below -- I thought there would be something like this:

   dev = bus->self;
   parent_bus = dev->bus;
   if (parent_bus && parent_bus->bus_flags & PCI_BUS_FLAGS_COMPAT_CFG_SPACE)
     bus->bus_flags |= PCI_BUS_FLAGS_COMPAT_CFG_SPACE;

pci_scan_bridge_extend() calls pci_add_new_bus() from two places. You added a call to pci_bus_check_compat_cfg_space() at one of them, and it's not obvious why we wouldn't need it at the other place, too.

Can you set this up in pci_alloc_child_bus()? If you can put it there, it would be clear that every time we allocate a secondary bus, we figure out whether extended config space is accessible on that bus.

That doesn't cover the root bus case, where we currently assume the host bridge can generate config accesses to all config space supported by devices on the root bus. But we don't have a problem there, so I guess we don't need to worry about it now.

If you can put it in pci_alloc_child_bus(), could you make your new function return a boolean, e.g., pci_bus_ext_cfg_accessible(), or similar, and then use the result to set the PCI_BUS_FLAGS_COMPAT_CFG_SPACE flag? Names like "*_check_*()" don't tell the reader much about what's happening.

Quoted 73 lines
Quoted 9 lines

I agree that pci_scan_bridge_extend() is already too complicated, so would you be okay to only add one line to it :

   pci_bus_set_compat_cfg_space(child);

and put all the code I suggested in this new function pci_bus_set_compat_cfg_space() ? (also supporting PCI-X Mode 2 devices)

Improvement : this function can return immediately if the child bus has already inherited the flag from its parent.

I mean something like the attached patch I tested this morning... Sorry, this is for old kernel 4.1.35 but just to clarify what I propose (also applies to 4.16.6 by changing value of PCI_BUS_FLAGS_COMPAT_CFG_SPACE in pci.h to 8).

linux/pci.h renamed from linux/pci.h.orig +1 -0
@@ -193,6 +193,7 @@
  enum pci_bus_flags {
  	PCI_BUS_FLAGS_NO_MSI   = (__force pci_bus_flags_t) 1,
  	PCI_BUS_FLAGS_NO_MMRBC = (__force pci_bus_flags_t) 2,
+	PCI_BUS_FLAGS_COMPAT_CFG_SPACE = (__force pci_bus_flags_t) 4,
  };
 
  /* These values come from the PCI Express Spec */
pci/probe.c renamed from pci/probe.c.orig +30 -0
@@ -754,6 +754,35 @@
  					 PCI_EXP_RTCTL_CRSSVE);
  }
 
+static void pci_bus_check_compat_cfg_space(struct pci_bus *bus)
+{
+	struct pci_dev *dev = bus->self;
+	bool pci_compat_cfg_space = false;
+	int pos;
+	u32 status;
+
+	if (bus->bus_flags & PCI_BUS_FLAGS_COMPAT_CFG_SPACE)
+		return;
+
+	if (!pci_is_pcie(dev) || /* PCI/PCI bridge */
+	    (pci_pcie_type(dev) == PCI_EXP_TYPE_PCIE_BRIDGE) || /* PCIe/PCI bridge in forward mode */
+	    (pci_pcie_type(dev) == PCI_EXP_TYPE_PCI_BRIDGE)) {  /* PCIe/PCI bridge in reverse mode */
+		pos = pci_find_capability(dev, PCI_CAP_ID_PCIX);
+		if (pos) {
+			pci_read_config_dword(dev, pos + PCI_X_STATUS, &status);
+			if (!(status & (PCI_X_STATUS_266MHZ | PCI_X_STATUS_533MHZ)))
+				pci_compat_cfg_space = true;
+		} else {
+			pci_compat_cfg_space = true;
+		}
+		if (pci_compat_cfg_space) {
+			dev_info(&dev->dev, "bus %02x limited to PCI-Compatible config space\n",
+				 bus->number);
+			bus->bus_flags |= PCI_BUS_FLAGS_COMPAT_CFG_SPACE;
+		}
+	}
+}
+
  /*
   * If it's a bridge, configure it and scan the bus behind it.
   * For CardBus bridges, we don't scan behind as the devices will
@@ -827,6 +856,7 @@
  			child->primary = primary;
  			pci_bus_insert_busn_res(child, secondary, subordinate);
  			child->bridge_ctl = bctl;
+			pci_bus_check_compat_cfg_space(child);
  		}
 
  		cmax = pci_scan_child_bus(child);
@@ -1084,6 +1114,9 @@
  	u32 status;
  	u16 class;
 
Quoted 7 lines

pci/probe.c line 1122

Quoted 6 lines
pci/probe.c renamed from pci/probe.c.orig +3 -0
+	if (dev->bus->bus_flags & PCI_BUS_FLAGS_COMPAT_CFG_SPACE)
+		return PCI_CFG_SPACE_SIZE;
+
  	class = dev->class >> 8;
  	if (class == PCI_CLASS_BRIDGE_HOST)
  		return pci_cfg_space_size_ext(dev);

The inheritence is made by this line in pci_alloc_child_bus() :

     child->bus_flags = parent->bus_flags;

So once we detect a limitation on a bridge impacting a child bus and that we set the flag in child->bus_flags, this flag is automatically present in the child->bus_flags of all its children buses.

I agree with your remarks and will create a function named pci_bus_check_compat_cfg_space() that will be called from pci_alloc_child_bus().
I'll test that on Wednesday 2th and will give you my feedback.

#8 lore raw

Le 30/04/2018 19:53, Gilles BULOZ a ?crit :

Quoted 303 lines

Le 30/04/2018 19:04, Bjorn Helgaas a ?crit :

Quoted 293 lines · Gilles Buloz
Quoted 196 lines

Le 30/04/2018 10:46, Gilles BULOZ a ?crit :

Quoted 195 lines

Le 27/04/2018 18:56, Bjorn Helgaas a ?crit :

Quoted 160 lines · Gilles Buloz
Quoted 16 lines

Le 27/04/2018 10:43, Ard Biesheuvel a ?crit :

Quoted 15 lines

(add Bjorn and linux-pci)

Quoted 12 lines · Gilles Buloz

Dear developers,

I currently have two functional workarounds for this issue but would like to know which one you would recommend, if any :-) I'm using a LS1043A CPU (NXP QorIQ Layerscape) and get a "synchronous external abort" when booting because of a PCI config read during PCI scan.

I'm using a custom hardware (based on LS1043ARDB) having a PEX8112 PCIe-to-PCI bridge connected to the LS1043A to have a PCI slot for legacy devices. This bridge only supports PCI-Compatible config accesses (offset 0x00-0xFF).

I would guess the PEX8112 itself has 4K of config space, but it only forwards 256 bytes of config space to the conventional PCI secondary bus.

Quoted 35 lines
Quoted 35 lines
Quoted 20 lines

On this PCI slot I connect a PCI module made of a PCI-to-PCIe bridge plus PCIe devices behind.

The problem occurs when the kernel probes the PCIe devices : as they are PCIe devices, the kernel does a PCI config read access at offset 0x100 to check if "PCIe extended capability registers" are accessible (see drivers/pci/probe.c, function pci_cfg_space_size_ext()). Unfortunately the PEX8112 PCIe-to-PCI bridge that is in the path reports an error to the CPU for this access, and it seems there's no way to disable that on this bridge.

The first workaround I found was to patch drivers/pci/host/pci-layerscape.c to have PCIE_ABSERR_SETTING set to 0x9400 instead of 0x9401 (for PCIE_ABSERR register) to disable error reporting. This only impacts an NXP part of the Linux kernel code, but I'm not sure this is a good idea (however it seems to be like that on Intel platforms where even MEM accesses to a no-device address return FF without any error).

Quoted 9 lines

I've also tried another workaround that works : patch drivers/pci/probe.c to use bus_flags to remember if a bus is behind a bridge without extended address capability, to avoid PCi config read accesses at offset 0x100 in pci_cfg_space_size() / pci_cfg_space_size_ext(). But this patch impacts the generic PCI probe method of Linux.

Any Idea to properly handle that issue ?

This seems like a rather unusual configuration, but I guess that if the first bridge/switch advertises its inability to support extended config space accesses, we should not be performing them on any of its subordinate buses. How does the PEX8112 advertise this limitation?

Quoted 8 lines
Quoted 4 lines

That said, I wonder if it is reasonable in the first place to expect that a PCIe device works as expected passing through a legacy PCI layer like that.

The PEX8112 PCIe-to-PCI bridge has capability PCI_CAP_ID_EXP, but has no PCI_CAP_ID_PCIX capability. As I understand the lack of PCI_CAP_ID_PCIX is advertising this limitation on the PCI side (no support for PCI config offset >=0x100).

Sounds right to me.

Quoted 4 lines

Also I guess in the case of a bridge having PCI_CAP_ID_PCIX, this limitation would be advertised by the lack of PCI_X_STATUS_266MHZ and PCI_X_STATUS_533MHZ (as done in drivers/pci/probe.c at pci_cfg_space_size())

Also sounds right. Per the PCI-X spec, checking for PCI_X_STATUS_266MHZ should be enough, but it shouldn't hurt to check for either PCI_X_STATUS_266MHZ or PCI_X_STATUS_533MHZ.

Quoted 5 lines

I'm currently using the attached patch (for kernel 4.1.35-rt41 from NXP Yocto BSP). It uses bus_flags to remember if a bus is behind a bridge without extended address capability to avoid PCi config accesses at offset >= 0x100. Thanks to this patch I now have a functional system with functional PCI/PCIe devices.

The patch seems like it's looking at the right things, but I don't want to build it into pci_scan_bridge_extend() because that function is much too complicated already.

I'd rather build it into pci_cfg_space_size() or pci_cfg_space_size_ext() somehow. Maybe something along these lines? This doesn't account for the case of a PCIe-to-PCI-X Mode 2 bridge; in that case, I think all 4K would be accessible on the PCI-X side.

drivers/pci/probe.c +5 -1
index ac91b6fd0bcd..d8b091f0bcd1 100644
@@ -1367,7 +1367,7 @@ static bool pci_ext_cfg_is_aliased(struct pci_dev *dev)
    * pci_cfg_space_size - Get the configuration space size of the PCI device
    * @dev: PCI device
    *
- * Regular PCI devices have 256 bytes, but PCI-X 2 and PCI Express devices
+ * Regular PCI devices have 256 bytes, but PCI-X Mode 2 and PCI Express devices
    * have 4096 bytes.  Even if the device is capable, that doesn't mean we can
    * access it.  Maybe we don't have a way to generate extended config space
    * accesses, or the device is behind a reverse Express bridge.  So we try
@@ -1376,9 +1376,14 @@ static bool pci_ext_cfg_is_aliased(struct pci_dev *dev)
    */
   static int pci_cfg_space_size_ext(struct pci_dev *dev)
   {
+    struct pci_dev *bridge = pci_upstream_bridge(dev);
       u32 status;
       int pos = PCI_CFG_SPACE_SIZE;
   +    if (bridge && pci_is_pcie(bridge) &&
+        pci_pcie_type(bridge) == PCI_EXP_TYPE_PCI_BRIDGE)
+        return PCI_CFG_SPACE_SIZE;
+
       if (pci_read_config_dword(dev, pos, &status) != PCIBIOS_SUCCESSFUL)
           return PCI_CFG_SPACE_SIZE;
       if (status == 0xffffffff || pci_ext_cfg_is_aliased(dev))
Quoted 45 lines
linux/pci.h 2018-03-26 16:51:27.660000000 +0000 renamed from linux/pci.h.orig 2018-03-26 16:51:18.050000000 +0000 +1 -0
@@ -193,6 +193,7 @@
   enum pci_bus_flags {
       PCI_BUS_FLAGS_NO_MSI   = (__force pci_bus_flags_t) 1,
       PCI_BUS_FLAGS_NO_MMRBC = (__force pci_bus_flags_t) 2,
+    PCI_BUS_FLAGS_COMPAT_CFG_SPACE = (__force pci_bus_flags_t) 4,
   };
     /* These values come from the PCI Express Spec */
pci/probe.c 2018-03-26 16:54:30.830000000 +0000 renamed from pci/probe.c.orig 2018-01-22 09:29:52.000000000 +0000 +8 -0
@@ -827,6 +827,28 @@
               child->primary = primary;
               pci_bus_insert_busn_res(child, secondary, subordinate);
               child->bridge_ctl = bctl;
+
+            {
+                int pos;
+                u32 status;
+                bool pci_compat_cfg_space = false;
+
+                if (!pci_is_pcie(dev) || (pci_pcie_type(dev) == PCI_EXP_TYPE_PCIE_BRIDGE) || (pci_pcie_type(dev) ==
 PCI_EXP_TYPE_PCI_BRIDGE)) {
+                    /* for PCI/PCI bridges, or PCIe/PCI bridge in forward or reverse mode, we have to check for PCI-X

capabilities */

pci/probe.c 2018-03-26 16:54:30.830000000 +0000 renamed from pci/probe.c.orig 2018-01-22 09:29:52.000000000 +0000 +10 -0
+                    pos = pci_find_capability(dev, PCI_CAP_ID_PCIX);
+                    if (pos) {
+                        pci_read_config_dword(dev, pos + PCI_X_STATUS, &status);
+                        if (!(status & (PCI_X_STATUS_266MHZ | PCI_X_STATUS_533MHZ)))
+                            pci_compat_cfg_space = true;
+                    } else {
+                        pci_compat_cfg_space = true;
+                    }
+                    if (pci_compat_cfg_space) {
+                        dev_info(&dev->dev, "[%04x:%04x] Child bus limited to PCI-Compatible config space\n", dev->vendor,

dev->device);

pci/probe.c 2018-03-26 16:54:30.830000000 +0000 renamed from pci/probe.c.orig 2018-01-22 09:29:52.000000000 +0000 +4 -0
+                        child->bus_flags |= PCI_BUS_FLAGS_COMPAT_CFG_SPACE;
+                    }
+                }
+            }
           }
             cmax = pci_scan_child_bus(child);
@@ -1098,6 +1120,11 @@
               goto fail;
Quoted 8 lines

pci/probe.c 2018-03-26 16:54:30.830000000 +0000 line 1128

Quoted 8 lines
pci/probe.c 2018-03-26 16:54:30.830000000 +0000 renamed from pci/probe.c.orig 2018-01-22 09:29:52.000000000 +0000 +4 -0
       }
   +    if (dev->bus->bus_flags & PCI_BUS_FLAGS_COMPAT_CFG_SPACE) {
+        dev_info(&dev->dev, "[%04x:%04x] PCI-Compatible config space only due to parent bus(es)\n", dev->vendor, dev->device);
+        return PCI_CFG_SPACE_SIZE;
+    }
+
       return pci_cfg_space_size_ext(dev);
      fail:

Bjorn,
If I'm right about your proposed patch to pci_cfg_space_size_ext(), *bridge is pointing to the upper device of device *dev being checked. I understand the purpose, but I think this fails for my config that is :

LS1043 PCIe root -> PEX8112 PCIe-to-PCI bridge -> PMC slot connector -> PCI-to-PCIe bridge -> PCIe switch (4 ports) -> 4 PCIe devices (one on each port)

because :
- when pci_cfg_space_size_ext() is run on the 4 PCIe devices, *bridge is the PCIe switch which is not matching PCI_EXP_TYPE_PCI_BRIDGE. In this case *bridge should also be checked for the parent bus of the PCIe switch, and so on.
- when pci_cfg_space_size_ext() is run for the PCI-to-PCIe bridge, *bridge is the PEX8112 that is also not matching PCI_EXP_TYPE_PCI_BRIDGE but PCI_EXP_TYPE_PCIE_BRIDGE. This leads to a config access at offset 0x100 to the PCI-to-PCIe bridge, so a crash (because of the PEX8112)

I think setting a bit in bus_flags when creating a child bus is very efficient because once set it is automatically inherited by all child buses and then the only thing that pci_cfg_space_size() has to do for each device is to check for this bit. Also this PCI_BUS_FLAGS_COMPAT_CFG_SPACE flag is actually a bus property that is compliant with the purpose of bus_flags.

Yeah, it needs to be inherited somehow, and I don't like the idea of traversing up the tree, so I prefer your idea. Although I don't actually see the inheritance in the patch below -- I thought there would be something like this:

   dev = bus->self;
   parent_bus = dev->bus;
   if (parent_bus && parent_bus->bus_flags & PCI_BUS_FLAGS_COMPAT_CFG_SPACE)
     bus->bus_flags |= PCI_BUS_FLAGS_COMPAT_CFG_SPACE;

pci_scan_bridge_extend() calls pci_add_new_bus() from two places. You added a call to pci_bus_check_compat_cfg_space() at one of them, and it's not obvious why we wouldn't need it at the other place, too.

Can you set this up in pci_alloc_child_bus()? If you can put it there, it would be clear that every time we allocate a secondary bus, we figure out whether extended config space is accessible on that bus.

That doesn't cover the root bus case, where we currently assume the host bridge can generate config accesses to all config space supported by devices on the root bus. But we don't have a problem there, so I guess we don't need to worry about it now.

If you can put it in pci_alloc_child_bus(), could you make your new function return a boolean, e.g., pci_bus_ext_cfg_accessible(), or similar, and then use the result to set the PCI_BUS_FLAGS_COMPAT_CFG_SPACE flag? Names like "*_check_*()" don't tell the reader much about what's happening.

Quoted 67 lines
Quoted 9 lines

I agree that pci_scan_bridge_extend() is already too complicated, so would you be okay to only add one line to it :

   pci_bus_set_compat_cfg_space(child);

and put all the code I suggested in this new function pci_bus_set_compat_cfg_space() ? (also supporting PCI-X Mode 2 devices)

Improvement : this function can return immediately if the child bus has already inherited the flag from its parent.

I mean something like the attached patch I tested this morning... Sorry, this is for old kernel 4.1.35 but just to clarify what I propose (also applies to 4.16.6 by changing value of PCI_BUS_FLAGS_COMPAT_CFG_SPACE in pci.h to 8).

linux/pci.h 2018-04-30 09:50:57.660000000 +0000 renamed from linux/pci.h.orig 2018-03-26 16:51:18.050000000 +0000 +1 -0
@@ -193,6 +193,7 @@
  enum pci_bus_flags {
      PCI_BUS_FLAGS_NO_MSI   = (__force pci_bus_flags_t) 1,
      PCI_BUS_FLAGS_NO_MMRBC = (__force pci_bus_flags_t) 2,
+    PCI_BUS_FLAGS_COMPAT_CFG_SPACE = (__force pci_bus_flags_t) 4,
  };
    /* These values come from the PCI Express Spec */
pci/probe.c 2018-04-30 13:29:50.600000000 +0000 renamed from pci/probe.c.orig 2018-01-22 09:29:52.000000000 +0000 +29 -0
@@ -754,6 +754,35 @@
                       PCI_EXP_RTCTL_CRSSVE);
  }
  +static void pci_bus_check_compat_cfg_space(struct pci_bus *bus)
+{
+    struct pci_dev *dev = bus->self;
+    bool pci_compat_cfg_space = false;
+    int pos;
+    u32 status;
+
+    if (bus->bus_flags & PCI_BUS_FLAGS_COMPAT_CFG_SPACE)
+        return;
+
+    if (!pci_is_pcie(dev) || /* PCI/PCI bridge */
+        (pci_pcie_type(dev) == PCI_EXP_TYPE_PCIE_BRIDGE) || /* PCIe/PCI bridge in forward mode */
+        (pci_pcie_type(dev) == PCI_EXP_TYPE_PCI_BRIDGE)) { /* PCIe/PCI bridge in reverse mode */
+        pos = pci_find_capability(dev, PCI_CAP_ID_PCIX);
+        if (pos) {
+            pci_read_config_dword(dev, pos + PCI_X_STATUS, &status);
+            if (!(status & (PCI_X_STATUS_266MHZ | PCI_X_STATUS_533MHZ)))
+                pci_compat_cfg_space = true;
+        } else {
+            pci_compat_cfg_space = true;
+        }
+        if (pci_compat_cfg_space) {
+            dev_info(&dev->dev, "bus %02x limited to PCI-Compatible config space\n",
+                 bus->number);
+            bus->bus_flags |= PCI_BUS_FLAGS_COMPAT_CFG_SPACE;
+        }
+    }
+}
+
  /*
   * If it's a bridge, configure it and scan the bus behind it.
   * For CardBus bridges, we don't scan behind as the devices will
@@ -827,6 +856,7 @@
              child->primary = primary;
              pci_bus_insert_busn_res(child, secondary, subordinate);
              child->bridge_ctl = bctl;
+            pci_bus_check_compat_cfg_space(child);
          }
            cmax = pci_scan_child_bus(child);
@@ -1084,6 +1114,9 @@
Quoted 9 lines

pci/probe.c 2018-04-30 13:29:50.600000000 +0000 line 1121

Quoted 8 lines
pci/probe.c 2018-04-30 13:29:50.600000000 +0000 renamed from pci/probe.c.orig 2018-01-22 09:29:52.000000000 +0000 +2 -0
      u32 status;
      u16 class;
  +    if (dev->bus->bus_flags & PCI_BUS_FLAGS_COMPAT_CFG_SPACE)
+        return PCI_CFG_SPACE_SIZE;
+
      class = dev->class >> 8;
      if (class == PCI_CLASS_BRIDGE_HOST)
          return pci_cfg_space_size_ext(dev);
Quoted 8 lines

The inheritence is made by this line in pci_alloc_child_bus() :

    child->bus_flags = parent->bus_flags;

So once we detect a limitation on a bridge impacting a child bus and that we set the flag in child->bus_flags, this flag is automatically present in the child->bus_flags of all its children buses.

I agree with your remarks and will create a function named pci_bus_check_compat_cfg_space() that will be called from pci_alloc_child_bus().
I'll test that on Wednesday 2th and will give you my feedback.

Hi Bjorn,
See attached patch (tested ok this morning)
-------------- next part --------------
A non-text attachment was scrubbed...
Name: cfgspace3_4.1.35.patch
Type: text/x-patch
Size: 2246 bytes
Desc: cfgspace3_4.1.35.patch
URL: <http://lists.infradead.org/pipermail/linux-arm-kernel/attachments/20180502/bd83d6f9/attachment.bin>

#9 lore raw
Quoted 2 lines · Gilles Buloz

Hi Bjorn,
See attached patch (tested ok this morning)

This looks good. Minor comments below.

I can fix minor things myself, but I do need a signed-off-by from you before applying (see
https://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/tree/Documentation/process/submitting-patches.rst)

Please add a changelog, too, and include the patch inline (as opposed to as an attachment) if possible.

linux/pci.h line 196

Quoted 7 lines
linux/pci.h renamed from linux/pci.h.orig +1 -0
@@ -193,6 +193,7 @@
 enum pci_bus_flags {
 	PCI_BUS_FLAGS_NO_MSI   = (__force pci_bus_flags_t) 1,
 	PCI_BUS_FLAGS_NO_MMRBC = (__force pci_bus_flags_t) 2,
+	PCI_BUS_FLAGS_NO_EXTCFG = (__force pci_bus_flags_t) 4,

Best if you can rebase this to v4.17-rc1.

Quoted 12 lines
linux/pci.h renamed from linux/pci.h.orig +0 -0
 };
 
 /* These values come from the PCI Express Spec */
pci/probe.c renamed from pci/probe.c.orig +3 -0
@@ -664,6 +664,23 @@
 	}
 }
 
+static bool pci_bridge_child_bus_ext_cfg_accessible(struct pci_dev *bridge)
+{
+	int pos;

pci/probe.c line 679

Quoted 10 lines
pci/probe.c renamed from pci/probe.c.orig +10 -0
+	u32 status;
+
+	if (!pci_is_pcie(bridge) || /* PCI/PCI bridge */
+	    (pci_pcie_type(bridge) == PCI_EXP_TYPE_PCIE_BRIDGE) || /* PCIe/PCI bridge in forward mode */
+	    (pci_pcie_type(bridge) == PCI_EXP_TYPE_PCI_BRIDGE)) {  /* PCIe/PCI bridge in reverse mode */
+		pos = pci_find_capability(bridge, PCI_CAP_ID_PCIX);
+		if (pos)
+			pci_read_config_dword(bridge, pos + PCI_X_STATUS, &status);
+		return pos && (status & (PCI_X_STATUS_266MHZ | PCI_X_STATUS_533MHZ));
+	}

Please arrange this so everything fits in 80 columns.

If you can split it into several simpler "if" statements rather than one with a complicated expression, that would also be good.

Quoted 8 lines
pci/probe.c renamed from pci/probe.c.orig +4 -0
+
+	return true;
+}
+
 static struct pci_bus *pci_alloc_child_bus(struct pci_bus *parent,
 					   struct pci_dev *bridge, int busnr)
 {

pci/probe.c line 748

Quoted 7 lines
pci/probe.c renamed from pci/probe.c.orig +4 -0
@@ -725,6 +742,19 @@
 	/* Create legacy_io and legacy_mem files for this bus */
 	pci_create_legacy_files(child);
 
+	/*
+	 * if bus_flags inherited from parent bus do not already report lack of extended config
+	 * space support, check if supported by child bus by checking its parent bridge
+	 */

Wrap to fit in 80 columns.

pci/probe.c line 749

Quoted 1 line
pci/probe.c renamed from pci/probe.c.orig +1 -0
+	if (bridge && !(child->bus_flags & PCI_BUS_FLAGS_NO_EXTCFG)) {

The double negative makes this a little bit hard to read. Maybe it could be improved by reversing the sense of something?

pci/probe.c line 752

Quoted 3 lines
pci/probe.c renamed from pci/probe.c.orig +3 -0
+		if (!pci_bridge_child_bus_ext_cfg_accessible(bridge)) {
+			child->bus_flags |= PCI_BUS_FLAGS_NO_EXTCFG;
+			dev_info(&child->dev, "extended config space not accessible due to parent bridge\n");

In v4.17-rc1, there's a pci_info() that should work here (instead of dev_info()).

Quoted 18 lines
pci/probe.c renamed from pci/probe.c.orig +8 -0
+		}
+	} else {
+		dev_info(&child->dev, "extended config space not accessible due to parent bus\n");
+	}
+
 	return child;
 }
 
@@ -1084,6 +1114,9 @@
 	u32 status;
 	u16 class;
 
+	if (dev->bus->bus_flags & PCI_BUS_FLAGS_NO_EXTCFG)
+		return PCI_CFG_SPACE_SIZE;
+
 	class = dev->class >> 8;
 	if (class == PCI_CLASS_BRIDGE_HOST)
 		return pci_cfg_space_size_ext(dev);
#10 lore raw

Le 02/05/2018 15:26, Bjorn Helgaas a ?crit :

Quoted 87 lines · Gilles Buloz
Quoted 2 lines

Hi Bjorn,
See attached patch (tested ok this morning)

This looks good. Minor comments below.

I can fix minor things myself, but I do need a signed-off-by from you before applying (see
https://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/tree/Documentation/process/submitting-patches.rst)

Please add a changelog, too, and include the patch inline (as opposed to as an attachment) if possible.

linux/pci.h line 196

Quoted 7 lines
linux/pci.h renamed from linux/pci.h.orig +1 -0
@@ -193,6 +193,7 @@
  enum pci_bus_flags {
  	PCI_BUS_FLAGS_NO_MSI   = (__force pci_bus_flags_t) 1,
  	PCI_BUS_FLAGS_NO_MMRBC = (__force pci_bus_flags_t) 2,
+	PCI_BUS_FLAGS_NO_EXTCFG = (__force pci_bus_flags_t) 4,

Best if you can rebase this to v4.17-rc1.

Quoted 12 lines
linux/pci.h renamed from linux/pci.h.orig +0 -0
  };
 
  /* These values come from the PCI Express Spec */
pci/probe.c renamed from pci/probe.c.orig +3 -0
@@ -664,6 +664,23 @@
  	}
  }
 
+static bool pci_bridge_child_bus_ext_cfg_accessible(struct pci_dev *bridge)
+{
+	int pos;

pci/probe.c line 679

Quoted 10 lines
pci/probe.c renamed from pci/probe.c.orig +10 -0
+	u32 status;
+
+	if (!pci_is_pcie(bridge) || /* PCI/PCI bridge */
+	    (pci_pcie_type(bridge) == PCI_EXP_TYPE_PCIE_BRIDGE) || /* PCIe/PCI bridge in forward mode */
+	    (pci_pcie_type(bridge) == PCI_EXP_TYPE_PCI_BRIDGE)) {  /* PCIe/PCI bridge in reverse mode */
+		pos = pci_find_capability(bridge, PCI_CAP_ID_PCIX);
+		if (pos)
+			pci_read_config_dword(bridge, pos + PCI_X_STATUS, &status);
+		return pos && (status & (PCI_X_STATUS_266MHZ | PCI_X_STATUS_533MHZ));
+	}

Please arrange this so everything fits in 80 columns.

If you can split it into several simpler "if" statements rather than one with a complicated expression, that would also be good.

Quoted 8 lines
pci/probe.c renamed from pci/probe.c.orig +4 -0
+
+	return true;
+}
+
  static struct pci_bus *pci_alloc_child_bus(struct pci_bus *parent,
  					   struct pci_dev *bridge, int busnr)
  {

pci/probe.c line 748

Quoted 7 lines
pci/probe.c renamed from pci/probe.c.orig +4 -0
@@ -725,6 +742,19 @@
  	/* Create legacy_io and legacy_mem files for this bus */
  	pci_create_legacy_files(child);
 
+	/*
+	 * if bus_flags inherited from parent bus do not already report lack of extended config
+	 * space support, check if supported by child bus by checking its parent bridge
+	 */

Wrap to fit in 80 columns.

pci/probe.c line 749

Quoted 1 line
pci/probe.c renamed from pci/probe.c.orig +1 -0
+	if (bridge && !(child->bus_flags & PCI_BUS_FLAGS_NO_EXTCFG)) {

The double negative makes this a little bit hard to read. Maybe it could be improved by reversing the sense of something?

pci/probe.c line 752

Quoted 3 lines
pci/probe.c renamed from pci/probe.c.orig +3 -0
+		if (!pci_bridge_child_bus_ext_cfg_accessible(bridge)) {
+			child->bus_flags |= PCI_BUS_FLAGS_NO_EXTCFG;
+			dev_info(&child->dev, "extended config space not accessible due to parent bridge\n");

In v4.17-rc1, there's a pci_info() that should work here (instead of dev_info()).

Quoted 12 lines
pci/probe.c renamed from pci/probe.c.orig +5 -0
+		}
+	} else {
+		dev_info(&child->dev, "extended config space not accessible due to parent bus\n");
+	}
+
  	return child;
  }
 
@@ -1084,6 +1114,9 @@
  	u32 status;
  	u16 class;
 
Quoted 8 lines

pci/probe.c line 1122

Quoted 6 lines
pci/probe.c renamed from pci/probe.c.orig +3 -0
+	if (dev->bus->bus_flags & PCI_BUS_FLAGS_NO_EXTCFG)
+		return PCI_CFG_SPACE_SIZE;
+
  	class = dev->class >> 8;
  	if (class == PCI_CLASS_BRIDGE_HOST)
  		return pci_cfg_space_size_ext(dev);

.

OK I'm going to learn about signing (sorry this is my first "official" patch).
I'll download kernel v4.17-rc1 and write the patch for it; however I hope I'll be able to test it on my platform without the freescale addons I have on 4.1.35, because I don't want to send an untested patch.
For "if (bridge && !(child->bus_flags & PCI_BUS_FLAGS_NO_EXTCFG))", I don't understand what you mean with "double negative", as I only have one "!"

Do you think it's worth keeping the two dev_info() ? The code would be smaller without; however this may help to have it for debug. Maybe use _dbg instead of _info ?

#11 lore raw
Quoted 96 lines · Gilles Buloz

Le 02/05/2018 15:26, Bjorn Helgaas a ?crit :

Quoted 87 lines · Gilles Buloz
Quoted 2 lines

Hi Bjorn,
See attached patch (tested ok this morning)

This looks good. Minor comments below.

I can fix minor things myself, but I do need a signed-off-by from you before applying (see
https://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/tree/Documentation/process/submitting-patches.rst)

Please add a changelog, too, and include the patch inline (as opposed to as an attachment) if possible.

linux/pci.h line 196

Quoted 7 lines
linux/pci.h renamed from linux/pci.h.orig +1 -0
@@ -193,6 +193,7 @@
  enum pci_bus_flags {
  	PCI_BUS_FLAGS_NO_MSI   = (__force pci_bus_flags_t) 1,
  	PCI_BUS_FLAGS_NO_MMRBC = (__force pci_bus_flags_t) 2,
+	PCI_BUS_FLAGS_NO_EXTCFG = (__force pci_bus_flags_t) 4,

Best if you can rebase this to v4.17-rc1.

Quoted 12 lines
linux/pci.h renamed from linux/pci.h.orig +0 -0
  };
 
  /* These values come from the PCI Express Spec */
pci/probe.c renamed from pci/probe.c.orig +3 -0
@@ -664,6 +664,23 @@
  	}
  }
 
+static bool pci_bridge_child_bus_ext_cfg_accessible(struct pci_dev *bridge)
+{
+	int pos;

pci/probe.c line 679

Quoted 10 lines
pci/probe.c renamed from pci/probe.c.orig +10 -0
+	u32 status;
+
+	if (!pci_is_pcie(bridge) || /* PCI/PCI bridge */
+	    (pci_pcie_type(bridge) == PCI_EXP_TYPE_PCIE_BRIDGE) || /* PCIe/PCI bridge in forward mode */
+	    (pci_pcie_type(bridge) == PCI_EXP_TYPE_PCI_BRIDGE)) {  /* PCIe/PCI bridge in reverse mode */
+		pos = pci_find_capability(bridge, PCI_CAP_ID_PCIX);
+		if (pos)
+			pci_read_config_dword(bridge, pos + PCI_X_STATUS, &status);
+		return pos && (status & (PCI_X_STATUS_266MHZ | PCI_X_STATUS_533MHZ));
+	}

Please arrange this so everything fits in 80 columns.

If you can split it into several simpler "if" statements rather than one with a complicated expression, that would also be good.

Quoted 8 lines
pci/probe.c renamed from pci/probe.c.orig +4 -0
+
+	return true;
+}
+
  static struct pci_bus *pci_alloc_child_bus(struct pci_bus *parent,
  					   struct pci_dev *bridge, int busnr)
  {

pci/probe.c line 748

Quoted 7 lines
pci/probe.c renamed from pci/probe.c.orig +4 -0
@@ -725,6 +742,19 @@
  	/* Create legacy_io and legacy_mem files for this bus */
  	pci_create_legacy_files(child);
 
+	/*
+	 * if bus_flags inherited from parent bus do not already report lack of extended config
+	 * space support, check if supported by child bus by checking its parent bridge
+	 */

Wrap to fit in 80 columns.

pci/probe.c line 749

Quoted 1 line
pci/probe.c renamed from pci/probe.c.orig +1 -0
+	if (bridge && !(child->bus_flags & PCI_BUS_FLAGS_NO_EXTCFG)) {

The double negative makes this a little bit hard to read. Maybe it could be improved by reversing the sense of something?

pci/probe.c line 752

Quoted 3 lines
pci/probe.c renamed from pci/probe.c.orig +3 -0
+		if (!pci_bridge_child_bus_ext_cfg_accessible(bridge)) {
+			child->bus_flags |= PCI_BUS_FLAGS_NO_EXTCFG;
+			dev_info(&child->dev, "extended config space not accessible due to parent bridge\n");

In v4.17-rc1, there's a pci_info() that should work here (instead of dev_info()).

Quoted 12 lines
pci/probe.c renamed from pci/probe.c.orig +5 -0
+		}
+	} else {
+		dev_info(&child->dev, "extended config space not accessible due to parent bus\n");
+	}
+
  	return child;
  }
 
@@ -1084,6 +1114,9 @@
  	u32 status;
  	u16 class;
 
Quoted 8 lines

pci/probe.c line 1122

Quoted 6 lines
pci/probe.c renamed from pci/probe.c.orig +3 -0
+	if (dev->bus->bus_flags & PCI_BUS_FLAGS_NO_EXTCFG)
+		return PCI_CFG_SPACE_SIZE;
+
  	class = dev->class >> 8;
  	if (class == PCI_CLASS_BRIDGE_HOST)
  		return pci_cfg_space_size_ext(dev);

.

Quoted 2 lines

OK I'm going to learn about signing (sorry this is my first "official" patch).

Great, welcome! The signoff is no big deal -- it's just plain text (no crypto signature or anything) and it's basically just an assertion that you wrote it and have the right to contribute it.

Quoted 4 lines

I'll download kernel v4.17-rc1 and write the patch for it; however I hope I'll be able to test it on my platform without the freescale addons I have on 4.1.35, because I don't want to send an untested patch.

Don't worry too much about the 4.1 vs 4.17 issue. If you tested it on 4.1.35 that should be fine.

Quoted 3 lines

For "if (bridge && !(child->bus_flags & PCI_BUS_FLAGS_NO_EXTCFG))", I don't understand what you mean with "double negative", as I only have one "!"

The "!" and the "NO" part of "NO_EXTCFG" is what I meant. E.g., maybe the flag could be something like "COMPAT_CFG_ONLY" so there's no negation in the test at all.

Quoted 3 lines

Do you think it's worth keeping the two dev_info() ? The code would be smaller without; however this may help to have it for debug. Maybe use _dbg instead of _info ?

Probably one pci_info() is enough as a clue that extended config space isn't available below this point in the hierarchy.

I personally don't like the _dbg() version because it's so complicated to figure out when the output is enabled.

Bjorn

#12 lore raw

Subject: [PATCH] For exception at PCI probe due to bridge reporting UR

Even if a device supports extended config access, no such access must be done to this device If there's a bridge not supporting that in the path to this device. Doing such access with UR reporting enabled on the root bridge leads to an exception.

This is the case on a LS1043A CPU (NXP QorIQ Layerscape) platform with the following bus topology :
LS1043 PCIe root

    -> PEX8112 PCIe-to-PCI bridge (not supporting ext cfg on PCI side)
      -> PMC slot connector (for legacy PMC modules)
With a PMC module topology as follows :
  PMC connector
    -> PCI-to-PCIe bridge
      -> PCIe switch (4 ports)
        -> 4 PCIe devices (one on each port)

In this case all devices behind the PEX8112 are supporting extended config access but this is prohibited by the PEX8112. Without this patch, an exception (synchronous abort) occurs in pci_cfg_space_size_ext().

This patch checks the parent bridge of each allocated child bus to know if extended config access is supported on the child bus, and sets a flag in child->bus_flags if not supported. This flag is inherited by all children buses of this child bus and then is checked to avoid this unsupported accesses to every device on these buses.

Thanks

=====================
Gilles BULOZ
Senior software engineer
Kontron France
=====================

include/linux/pci.h renamed from include/linux/pci.h.orig +1 -0
@@ -217,6 +217,7 @@ enum pci_bus_flags {
  	PCI_BUS_FLAGS_NO_MSI	= (__force pci_bus_flags_t) 1,
  	PCI_BUS_FLAGS_NO_MMRBC	= (__force pci_bus_flags_t) 2,
  	PCI_BUS_FLAGS_NO_AERSID	= (__force pci_bus_flags_t) 4,
+	PCI_BUS_FLAGS_NO_EXTCFG	= (__force pci_bus_flags_t) 8,
  };
  
  /* Values from Link Status register, PCIe r3.1, sec 7.8.8 */
drivers/pci/probe.c renamed from drivers/pci/probe.c.orig +35 -0
@@ -882,6 +882,24 @@ free:
  	return err;
  }
  
+static bool pci_bridge_child_bus_ext_cfg_accessible(struct pci_dev *bridge)
+{
+	int pos;
+	u32 status;
+
+	if (pci_is_pcie(bridge) &&
+	    (pci_pcie_type(bridge) != PCI_EXP_TYPE_PCIE_BRIDGE) &&
+	    (pci_pcie_type(bridge) != PCI_EXP_TYPE_PCI_BRIDGE))
+		return true;
+
+	/* PCI/PCI, or PCIe/PCI (forward), or PCI/PCIe (reverse) bridge */
+	pos = pci_find_capability(bridge, PCI_CAP_ID_PCIX);
+	if (pos)
+		pci_read_config_dword(bridge, pos + PCI_X_STATUS, &status);
+
+	return pos && (status & (PCI_X_STATUS_266MHZ | PCI_X_STATUS_533MHZ));
+}
+
  static struct pci_bus *pci_alloc_child_bus(struct pci_bus *parent,
  					   struct pci_dev *bridge, int busnr)
  {
@@ -930,6 +948,20 @@ static struct pci_bus *pci_alloc_child_b
  	}
  	bridge->subordinate = child;
  
+	/*
+	 * if bus_flags inherited from parent bus do not already report lack of
+	 * extended config space support, check if supported by child bus by
+	 * checking its parent bridge
+	 */
+	if (child->bus_flags & PCI_BUS_FLAGS_NO_EXTCFG) {
+		pci_info(child, "extended config space not accessible due to parent bus\n");
+	} else {
+		if (!pci_bridge_child_bus_ext_cfg_accessible(bridge)) {
+			child->bus_flags |= PCI_BUS_FLAGS_NO_EXTCFG;
+			pci_info(child, "extended config space not accessible due to parent bridge\n");
+		}
+	}
+
  add_dev:
  	pci_set_bus_msi_domain(child);
  	ret = device_register(&child->dev);
@@ -1393,6 +1425,9 @@ int pci_cfg_space_size(struct pci_dev *d
  	u32 status;
  	u16 class;
  
+	if (dev->bus->bus_flags & PCI_BUS_FLAGS_NO_EXTCFG)
+		return PCI_CFG_SPACE_SIZE;
+
  	class = dev->class >> 8;
  	if (class == PCI_CLASS_BRIDGE_HOST)
  		return pci_cfg_space_size_ext(dev);
#13 lore raw

PATCH 1/1 PCI: Check whether bridges allow access to extended config space

[+cc LKML]

Quoted 21 lines · Gilles Buloz

Subject: [PATCH] For exception at PCI probe due to bridge reporting UR

Even if a device supports extended config access, no such access must be done to this device If there's a bridge not supporting that in the path to this device. Doing such access with UR reporting enabled on the root bridge leads to an exception.

This is the case on a LS1043A CPU (NXP QorIQ Layerscape) platform with the following bus topology :
LS1043 PCIe root

    -> PEX8112 PCIe-to-PCI bridge (not supporting ext cfg on PCI side)
      -> PMC slot connector (for legacy PMC modules)
With a PMC module topology as follows :
  PMC connector
    -> PCI-to-PCIe bridge
      -> PCIe switch (4 ports)
        -> 4 PCIe devices (one on each port)

In this case all devices behind the PEX8112 are supporting extended config access but this is prohibited by the PEX8112. Without this patch, an exception (synchronous abort) occurs in pci_cfg_space_size_ext().

Quoted 5 lines

This patch checks the parent bridge of each allocated child bus to know if extended config access is supported on the child bus, and sets a flag in child->bus_flags if not supported. This flag is inherited by all children buses of this child bus and then is checked to avoid this unsupported accesses to every device on these buses.

Hi Gilles,

Thanks for the patch! I reworked it a little bit to simplify the code in pci_alloc_child_bus(). Can you test it and make sure I didn't break anything?

commit cbaaa85b558a8f974e301fa0364d568efaf491ce

Author: Gilles Buloz <[email protected]>
Date:   Thu May 3 15:21:44 2018 -0500

PCI: Check whether bridges allow access to extended config space

Even if a device supports extended config space, i.e., it is a PCI-X Mode 2 or a PCI Express device, the extended space may not be accessible if there's a conventional PCI bus in the path to it.

We currently figure that out in pci_cfg_space_size() by reading the first dword of extended config space. On most platforms that returns ~0 data if the space is inaccessible, but it may set error bits in PCI status registers, and on some platforms it causes exceptions that we currently don't recover from.

For example, a PCIe-to-conventional PCI bridge treats config transactions with a non-zero Extended Register Address as an Unsupported Request on PCIe and a received Master-Abort on the destination bus (see PCI Express to PCI/PCI-X Bridge spec, r1.0, sec 4.1.3).

A sample case is a LS1043A CPU (NXP QorIQ Layerscape) platform with the following bus topology:

LS1043 PCIe Root Port
-> PEX8112 PCIe-to-PCI bridge (doesn't support ext cfg on PCI side)
-> PMC slot connector (for legacy PMC modules)

With a PMC module topology as follows:

PMC connector
-> PCI-to-PCIe bridge
-> PCIe switch (4 ports)
-> 4 PCIe devices (one on each port)

The PCIe devices on the PMC module support extended config space, but we can't reach it because the PEX8112 can't generate accesses to the extended space on its secondary bus. Attempts to access it cause Unsupported Request errors, which result in synchronous aborts on this platform.

To avoid these errors, check whether bridges are capable of generating extended config space addresses on their secondary interfaces. If they can't, we restrict devices below the bridge to only the 256-byte PCI-compatible config space.

[bhelgaas: changelog, rework patch so bus_flags testing is all in pci_bridge_child_ext_cfg_accessible()]

drivers/pci/probe.c +52 -0
index ac91b6fd0bcd..7b1b7b2e01e4 100644
@@ -882,6 +882,45 @@ static int pci_register_host_bridge(struct pci_host_bridge *bridge)
 	return err;
 }
 
+static bool pci_bridge_child_ext_cfg_accessible(struct pci_dev *bridge)
+{
+	int pos;
+	u32 status;
+
+	/*
+	 * If extended config space isn't accessible on a bridge's primary
+	 * bus, we certainly can't access it on the secondary bus.
+	 */
+	if (bridge->bus->bus_flags & PCI_BUS_FLAGS_NO_EXTCFG)
+		return false;
+
+	/*
+	 * PCIe Root Ports and switch ports are PCIe on both sides, so if
+	 * extended config space is accessible on the primary, it's also
+	 * accessible on the secondary.
+	 */
+	if (pci_is_pcie(bridge) &&
+	    (pci_pcie_type(bridge) == PCI_EXP_TYPE_ROOT_PORT ||
+	     pci_pcie_type(bridge) == PCI_EXP_TYPE_UPSTREAM ||
+	     pci_pcie_type(bridge) == PCI_EXP_TYPE_DOWNSTREAM))
+		return true;
+
+	/*
+	 * For the other bridge types:
+	 *   - PCI-to-PCI bridges
+	 *   - PCIe-to-PCI/PCI-X forward bridges
+	 *   - PCI/PCI-X-to-PCIe reverse bridges
+	 * extended config space on the secondary side is only accessible
+	 * if the bridge supports PCI-X Mode 2.
+	 */
+	pos = pci_find_capability(bridge, PCI_CAP_ID_PCIX);
+	if (!pos)
+		return false;
+
+	pci_read_config_dword(bridge, pos + PCI_X_STATUS, &status);
+	return status & (PCI_X_STATUS_266MHZ | PCI_X_STATUS_533MHZ);
+}
+
 static struct pci_bus *pci_alloc_child_bus(struct pci_bus *parent,
 					   struct pci_dev *bridge, int busnr)
 {
@@ -923,6 +962,16 @@ static struct pci_bus *pci_alloc_child_bus(struct pci_bus *parent,
 	pci_set_bus_of_node(child);
 	pci_set_bus_speed(child);
 
+	/*
+	 * Check whether extended config space is accessible on the child
+	 * bus.  Note that we currently assume it is always accessible on
+	 * the root bus.
+	 */
+	if (!pci_bridge_child_ext_cfg_accessible(bridge)) {
+		child->bus_flags |= PCI_BUS_FLAGS_NO_EXTCFG;
+		pci_info(child, "extended config space not accessible on secondary bus\n");
+	}
+
 	/* Set up default resource pointers and names */
 	for (i = 0; i < PCI_BRIDGE_RESOURCE_NUM; i++) {
 		child->resource[i] = &bridge->resource[PCI_BRIDGE_RESOURCES+i];
@@ -1393,6 +1442,9 @@ int pci_cfg_space_size(struct pci_dev *dev)
 	u32 status;
 	u16 class;
 
+	if (dev->bus->bus_flags & PCI_BUS_FLAGS_NO_EXTCFG)
+		return PCI_CFG_SPACE_SIZE;
+
 	class = dev->class >> 8;
 	if (class == PCI_CLASS_BRIDGE_HOST)
 		return pci_cfg_space_size_ext(dev);
include/linux/pci.h +1 -0
index 230615620a4a..f7aa6d9f8999 100644
@@ -217,6 +217,7 @@ enum pci_bus_flags {
 	PCI_BUS_FLAGS_NO_MSI	= (__force pci_bus_flags_t) 1,
 	PCI_BUS_FLAGS_NO_MMRBC	= (__force pci_bus_flags_t) 2,
 	PCI_BUS_FLAGS_NO_AERSID	= (__force pci_bus_flags_t) 4,
+	PCI_BUS_FLAGS_NO_EXTCFG	= (__force pci_bus_flags_t) 8,
 };
 
 /* Values from Link Status register, PCIe r3.1, sec 7.8.8 */
#14 lore raw

Re: [PATCH] PCI: Check whether bridges allow access to extended config space

Le 04/05/2018 00:31, Bjorn Helgaas a ?crit :

Quoted 29 lines

[+cc LKML]

Quoted 21 lines · Gilles Buloz

Subject: [PATCH] For exception at PCI probe due to bridge reporting UR

Even if a device supports extended config access, no such access must be done to this device If there's a bridge not supporting that in the path to this device. Doing such access with UR reporting enabled on the root bridge leads to an exception.

This is the case on a LS1043A CPU (NXP QorIQ Layerscape) platform with the following bus topology :
LS1043 PCIe root

     -> PEX8112 PCIe-to-PCI bridge (not supporting ext cfg on PCI side)
       -> PMC slot connector (for legacy PMC modules)
With a PMC module topology as follows :
   PMC connector
     -> PCI-to-PCIe bridge
       -> PCIe switch (4 ports)
         -> 4 PCIe devices (one on each port)

In this case all devices behind the PEX8112 are supporting extended config access but this is prohibited by the PEX8112. Without this patch, an exception (synchronous abort) occurs in pci_cfg_space_size_ext().

Quoted 5 lines

This patch checks the parent bridge of each allocated child bus to know if extended config access is supported on the child bus, and sets a flag in child->bus_flags if not supported. This flag is inherited by all children buses of this child bus and then is checked to avoid this unsupported accesses to every device on these buses.

Quoted 6 lines

Hi Gilles,

Thanks for the patch! I reworked it a little bit to simplify the code in pci_alloc_child_bus(). Can you test it and make sure I didn't break anything?

Hi Bjorn,

Your rework works as expected. Tested on LS1043A platform with kernel 4.17-rc1, and with some backport on kernel 4.1.35

Suggestion : maybe change the pci_info() string to have :
pci_bus 0000:xx: extended config space not accessible
instead of
pci_bus 0000:xx: extended config space not accessible on secondary bus
as xx is already the number of the secondary bus

Info : with kernel 4.17-rc1, it turns out I need pcie_aspm=off to have the PMC devices behind the PCI-to-PCIe bridge of the PMC safely detected/configured. But this is not caused by the patch. Without pcie_aspm=off I saw this at one boot :
"pci 0000:02:0e.0: ASPM: Could not configure common clock" for this bridge, but devices correctly detected/configured
but at most boots I get :
no ASPM message but "pci 0000:04:02.0: bridge configuration invalid ([bus ff-ff]), reconfiguring " instead, and some devices are missing. Also lspci show "rev ff" for some devices.
I don't see this problem on 4.1.35 with the same backported patch.

Gilles

Quoted 142 lines

commit cbaaa85b558a8f974e301fa0364d568efaf491ce

Author: Gilles Buloz <[email protected]>
Date:   Thu May 3 15:21:44 2018 -0500

PCI: Check whether bridges allow access to extended config space

Even if a device supports extended config space, i.e., it is a PCI-X Mode 2 or a PCI Express device, the extended space may not be accessible if there's a conventional PCI bus in the path to it.

We currently figure that out in pci_cfg_space_size() by reading the first dword of extended config space. On most platforms that returns ~0 data if the space is inaccessible, but it may set error bits in PCI status registers, and on some platforms it causes exceptions that we currently don't recover from.

For example, a PCIe-to-conventional PCI bridge treats config transactions with a non-zero Extended Register Address as an Unsupported Request on PCIe and a received Master-Abort on the destination bus (see PCI Express to PCI/PCI-X Bridge spec, r1.0, sec 4.1.3).

A sample case is a LS1043A CPU (NXP QorIQ Layerscape) platform with the following bus topology:

LS1043 PCIe Root Port
-> PEX8112 PCIe-to-PCI bridge (doesn't support ext cfg on PCI side)
-> PMC slot connector (for legacy PMC modules)

With a PMC module topology as follows:

PMC connector
-> PCI-to-PCIe bridge
-> PCIe switch (4 ports)
-> 4 PCIe devices (one on each port)

The PCIe devices on the PMC module support extended config space, but we can't reach it because the PEX8112 can't generate accesses to the extended space on its secondary bus. Attempts to access it cause Unsupported Request errors, which result in synchronous aborts on this platform.

To avoid these errors, check whether bridges are capable of generating extended config space addresses on their secondary interfaces. If they can't, we restrict devices below the bridge to only the 256-byte PCI-compatible config space.

[bhelgaas: changelog, rework patch so bus_flags testing is all in pci_bridge_child_ext_cfg_accessible()]

drivers/pci/probe.c +52 -0
index ac91b6fd0bcd..7b1b7b2e01e4 100644
@@ -882,6 +882,45 @@ static int pci_register_host_bridge(struct pci_host_bridge *bridge)
  	return err;
  }
 
+static bool pci_bridge_child_ext_cfg_accessible(struct pci_dev *bridge)
+{
+	int pos;
+	u32 status;
+
+	/*
+	 * If extended config space isn't accessible on a bridge's primary
+	 * bus, we certainly can't access it on the secondary bus.
+	 */
+	if (bridge->bus->bus_flags & PCI_BUS_FLAGS_NO_EXTCFG)
+		return false;
+
+	/*
+	 * PCIe Root Ports and switch ports are PCIe on both sides, so if
+	 * extended config space is accessible on the primary, it's also
+	 * accessible on the secondary.
+	 */
+	if (pci_is_pcie(bridge) &&
+	    (pci_pcie_type(bridge) == PCI_EXP_TYPE_ROOT_PORT ||
+	     pci_pcie_type(bridge) == PCI_EXP_TYPE_UPSTREAM ||
+	     pci_pcie_type(bridge) == PCI_EXP_TYPE_DOWNSTREAM))
+		return true;
+
+	/*
+	 * For the other bridge types:
+	 *   - PCI-to-PCI bridges
+	 *   - PCIe-to-PCI/PCI-X forward bridges
+	 *   - PCI/PCI-X-to-PCIe reverse bridges
+	 * extended config space on the secondary side is only accessible
+	 * if the bridge supports PCI-X Mode 2.
+	 */
+	pos = pci_find_capability(bridge, PCI_CAP_ID_PCIX);
+	if (!pos)
+		return false;
+
+	pci_read_config_dword(bridge, pos + PCI_X_STATUS, &status);
+	return status & (PCI_X_STATUS_266MHZ | PCI_X_STATUS_533MHZ);
+}
+
  static struct pci_bus *pci_alloc_child_bus(struct pci_bus *parent,
  					   struct pci_dev *bridge, int busnr)
  {
@@ -923,6 +962,16 @@ static struct pci_bus *pci_alloc_child_bus(struct pci_bus *parent,
  	pci_set_bus_of_node(child);
  	pci_set_bus_speed(child);
 
+	/*
+	 * Check whether extended config space is accessible on the child
+	 * bus.  Note that we currently assume it is always accessible on
+	 * the root bus.
+	 */
+	if (!pci_bridge_child_ext_cfg_accessible(bridge)) {
+		child->bus_flags |= PCI_BUS_FLAGS_NO_EXTCFG;
+		pci_info(child, "extended config space not accessible on secondary bus\n");
+	}
+
  	/* Set up default resource pointers and names */
  	for (i = 0; i < PCI_BRIDGE_RESOURCE_NUM; i++) {
  		child->resource[i] = &bridge->resource[PCI_BRIDGE_RESOURCES+i];
@@ -1393,6 +1442,9 @@ int pci_cfg_space_size(struct pci_dev *dev)
  	u32 status;
  	u16 class;
 
+	if (dev->bus->bus_flags & PCI_BUS_FLAGS_NO_EXTCFG)
+		return PCI_CFG_SPACE_SIZE;
+
  	class = dev->class >> 8;
  	if (class == PCI_CLASS_BRIDGE_HOST)
  		return pci_cfg_space_size_ext(dev);
include/linux/pci.h +1 -0
index 230615620a4a..f7aa6d9f8999 100644
@@ -217,6 +217,7 @@ enum pci_bus_flags {
  	PCI_BUS_FLAGS_NO_MSI	= (__force pci_bus_flags_t) 1,
  	PCI_BUS_FLAGS_NO_MMRBC	= (__force pci_bus_flags_t) 2,
  	PCI_BUS_FLAGS_NO_AERSID	= (__force pci_bus_flags_t) 4,
+	PCI_BUS_FLAGS_NO_EXTCFG	= (__force pci_bus_flags_t) 8,
  };
 
  /* Values from Link Status register, PCIe r3.1, sec 7.8.8 */

.