Skip to contentSkip to conversation

PCI: of: warn on bogus device_type property

PATCH Superseded Alex Elder 3 participants 12 messages Sep 8 – Sep 9 PCI

v5 of 7 Superseded Latest is v7 →

lore
Share & tools
lore

Alex Elder

author
#0 lore raw

v5 cover PCI: of: warn on bogus device_type property

Add a check when adding a PCI device to ensure the device_type property is (or is not) correctly defined when the device has a non-null devicetree node pointer.

PCI has a well-defined bus and device discovery process. The PCI_DYNAMIC_OF_NODES Kconfig option allows PCI devices to *also* have a devicetree node. This enables certain things that are not possible with PCI enumeration alone.

While working on a Qualcomm platform, I learned that some PCI endpoint nodes were defined with device_type = "pci" properties. Herve Codina pointed out that this was not correct. Rob Herring indicated that people seem to have trouble getting the PCI devicetree nodes right, and asked whether we could warn if this particular problem occurred. During review, Herve Codina also suggested that bridge nodes be checked to ensure they *do* have the proper device_type property, and later, Sashiko suggested that "pciex" (and "cardbus") also be accepted as valid bridge device_type property values.

The first patch prevents a possible null pointer dereference that Sashiko pointed out some time back. This is the only patch that has changed in this version. (Last time I neglected to enable the Kconfig option that ensured this go compiled, and it contained two obvious compile errors.)

The second and third patches are simple cleanups. The last adds the new PCI devicetree node checks and warnings.

-Alex

Between version 4 and version 5:
- Made a local variable (bufp) a pointer type in the first patch
- Eliminated an unused local variable (i) in the first patch

Version 4 is available here:
https://lore.kernel.org/lkml/[email protected]/

Between version 3 and version 4:
- Insert a first patch that fixes a Sashiko-reported issue
- Drop the final "duplicate warning" patch from v3
- Include "pciex" as a valid PCI bridge device_type property value
- Add Herve's Reviewed-by tag on the last patch

Version 3 is available here:
https://lore.kernel.org/lkml/[email protected]/

Between version 2 and version 3:
- Drop a patch that made a change only needed by a different series
- Switch a function header to use kernel-doc format
- Add a warning if a PCI bridge node has no device_type property
- Added a patch to remove a duplicate warning in the devicetree code

Version 2 is available here:
https://lore.kernel.org/lkml/[email protected]/

Between version 1 and version 2:
- Check the PCI devicetree node even when PCI_DYNAMIC_OF_NODES is not enabled

Version 1 is available here:
https://lore.kernel.org/lkml/[email protected]/

Alex Elder (4):
PCI: of: avoid allocations in of_pci_prop_compatible()
PCI: of: drop the reg_num argument to of_pci_set_address()
PCI: of: don't zero flags in of_pci_get_addr_flags()
PCI: of: introduce of_pci_verify_node()

 drivers/pci/bus.c         |  1 +
 drivers/pci/of.c          | 32 ++++++++++++++++++++++++++
 drivers/pci/of_property.c | 47 ++++++++++++++++++++++-----------------
 drivers/pci/pci.h         |  3 +++
 4 files changed, 63 insertions(+), 20 deletions(-)

base-commit: df2908090cda368b01ff43709f51890076c56157

Signature · 1 line
2.53.0
author
#1 lore raw

PATCH v5 1/4 PCI: of: avoid allocations in of_pci_prop_compatible()

Three compatible strings are formatted in of_pci_prop_compatible(). Their sizes are known in advance, and the largest is 16 bytes.

Rather than dynamically allocating the space for those strings, just set aside a buffer on the stack large enough to hold all three.

This avoids a problem that Sashiko pointed out, where an allocation failure would cause subsequent crash because strlen() is called unconditionally in of_changeset_add_prop_string_array().

---
v5: - Made "bufp" have pointer type
- Eliminated the unused local variable "i"

 drivers/pci/of_property.c | 33 +++++++++++++++++++++------------
 1 file changed, 21 insertions(+), 12 deletions(-)
drivers/pci/of_property.c +21 -12
index 75a358f73e694..d66c702218081 100644
@@ -324,27 +324,36 @@ static int of_pci_prop_intr_map(struct pci_dev *pdev, struct of_changeset *ocs,
 	return ret;
 }
 
+/* The three compatible property strings have max sizes 12+1, 15+1, and 13+1 */
+#define PROP_SIZE	16	/* Max size of each compatible string */
 static int of_pci_prop_compatible(struct pci_dev *pdev,
 				  struct of_changeset *ocs,
 				  struct device_node *np)
 {
 	const char *compat_strs[PROP_COMPAT_NUM] = { 0 };
-	int i, ret;
+	char buf[PROP_COMPAT_NUM * PROP_SIZE] = { };
+	char *bufp = buf;
+	int ret;
 
-	compat_strs[PROP_COMPAT_PCI_VVVV_DDDD] =
-		kasprintf(GFP_KERNEL, "pci%x,%x", pdev->vendor, pdev->device);
-	compat_strs[PROP_COMPAT_PCICLASS_CCSSPP] =
-		kasprintf(GFP_KERNEL, "pciclass,%06x", pdev->class);
-	compat_strs[PROP_COMPAT_PCICLASS_CCSS] =
-		kasprintf(GFP_KERNEL, "pciclass,%04x", pdev->class >> 8);
+	ret = snprintf(bufp, PROP_SIZE, "pci%x,%x", pdev->vendor, pdev->device);
+	if (ret >= PROP_SIZE)
+		return -EINVAL;
+	compat_strs[PROP_COMPAT_PCI_VVVV_DDDD] = bufp;
+	bufp += ret + 1;
 
-	ret = of_changeset_add_prop_string_array(ocs, np, "compatible",
-						 compat_strs, PROP_COMPAT_NUM);
-	for (i = 0; i < PROP_COMPAT_NUM; i++)
-		kfree(compat_strs[i]);
+	ret = snprintf(bufp, PROP_SIZE, "pciclass,%06x", pdev->class);
+	if (ret >= PROP_SIZE)
+		return -EINVAL;
+	compat_strs[PROP_COMPAT_PCICLASS_CCSSPP] = bufp;
+	bufp += ret + 1;
 
-	return ret;
+	ret = snprintf(bufp, PROP_SIZE, "pciclass,%04x", pdev->class >> 8);
+	compat_strs[PROP_COMPAT_PCICLASS_CCSS] = bufp;
+
+	return of_changeset_add_prop_string_array(ocs, np, "compatible",
+						  compat_strs, PROP_COMPAT_NUM);
 }
+#undef PROP_SIZE
 
 int of_pci_add_properties(struct pci_dev *pdev, struct of_changeset *ocs,
 			  struct device_node *np)
Signature · 1 line
2.53.0
#2 lore raw

Re: [PATCH v5 1/4] PCI: of: avoid allocations in of_pci_prop_compatible()

Sashiko has reviewed this patch and found no issues. It looks great!
Quoted 6 lines

Three compatible strings are formatted in of_pci_prop_compatible(). Their sizes are known in advance, and the largest is 16 bytes.

Rather than dynamically allocating the space for those strings, just set aside a buffer on the stack large enough to hold all three.

Quoted 7 lines

This avoids a problem that Sashiko pointed out, where an allocation failure would cause subsequent crash because strlen() is called unconditionally in of_changeset_add_prop_string_array().

Sashiko has reviewed this patch and found no issues. It looks great!

Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=1
author
#3 lore raw

PATCH v5 2/4 PCI: of: drop the reg_num argument to of_pci_set_address()

In reply to Alex Elder

The reg_num argument passed to of_pci_set_address() is always zero, so get rid of it.

---
 drivers/pci/of_property.c | 10 +++++-----
 1 file changed, 5 insertions(+), 5 deletions(-)
drivers/pci/of_property.c +5 -5
index d66c702218081..1caabbd4c18b5 100644
@@ -52,7 +52,7 @@ enum of_pci_prop_compatible {
 };
 
 static void of_pci_set_address(struct pci_dev *pdev, u32 *prop, u64 addr,
-			       u32 reg_num, u32 flags, bool reloc)
+			       u32 flags, bool reloc)
 {
 	if (pdev) {
 		prop[0] = FIELD_PREP(OF_PCI_ADDR_FIELD_BUS, pdev->bus->number) |
@@ -61,7 +61,7 @@ static void of_pci_set_address(struct pci_dev *pdev, u32 *prop, u64 addr,
 	} else
 		prop[0] = 0;
 
-	prop[0] |= flags | reg_num;
+	prop[0] |= flags;
 	if (!reloc) {
 		prop[0] |= OF_PCI_ADDR_FIELD_NONRELOC;
 		prop[1] = upper_32_bits(addr);
@@ -131,7 +131,7 @@ static int of_pci_prop_ranges(struct pci_dev *pdev, struct of_changeset *ocs,
 			continue;
 
 		val64 = pci_bus_address(pdev, &res[j] - pdev->resource);
-		of_pci_set_address(pdev, rp[i].parent_addr, val64, 0, flags,
+		of_pci_set_address(pdev, rp[i].parent_addr, val64, flags,
 				   false);
 		if (pci_is_bridge(pdev)) {
 			memcpy(rp[i].child_addr, rp[i].parent_addr,
@@ -164,7 +164,7 @@ static int of_pci_prop_reg(struct pci_dev *pdev, struct of_changeset *ocs,
 	struct of_pci_addr_pair reg = { 0 };
 
 	/* configuration space */
-	of_pci_set_address(pdev, reg.phys_addr, 0, 0, 0, true);
+	of_pci_set_address(pdev, reg.phys_addr, 0, 0, true);
 
 	return of_changeset_add_prop_u32_array(ocs, np, "reg", (u32 *)&reg,
 					       sizeof(reg) / sizeof(u32));
@@ -467,7 +467,7 @@ static int of_pci_host_bridge_prop_ranges(struct pci_host_bridge *bridge,
 		/* PCI bus address */
 		val64 = res->start;
 		of_pci_set_address(NULL, &ranges[ranges_sz],
-				   val64 - window->offset, 0, flags, false);
+				   val64 - window->offset, flags, false);
 		ranges_sz += OF_PCI_ADDRESS_CELLS;
 
 		/* Host bus address */
Signature · 1 line
2.53.0
AI review Reviewed-by Herve Codina on v5 2/4
#4 lore raw

Re: [PATCH v5 2/4] PCI: of: drop the reg_num argument to of_pci_set_address()

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:

Pre-existing issues:
- [High] Leaked OF node reference counts in of_pci_prop_intr_map() --

commit b3af32f45eed6da355a4a96246bdedb364fe0855
Author: Alex Elder <[email protected]>

PCI: of: drop the reg_num argument to of_pci_set_address()

The reg_num argument passed to of_pci_set_address() is always zero, so get rid of it.

drivers/pci/of_property.c

Quoted 4 lines
drivers/pci/of_property.c +0 -0
index d66c702218081..1caabbd4c18b5 100644

[ ... ]

[Severity: High]
This is a pre-existing issue, but does this code leak device node reference counts in of_pci_prop_intr_map()?

When of_irq_parse_raw() successfully parses an interrupt, it acquires a device node reference and stores it in out_irq[i].np:

drivers/pci/of_property.c:of_pci_prop_intr_map() {
    ...
		ret = of_irq_parse_raw(laddr, &out_irq[i]);
		if (ret) {
			out_irq[i].np = NULL;
			pci_dbg(pdev, "parse irq %d failed, ret %d", pin, ret);
			continue;
		}
    ...

However, the function returns without calling of_node_put() on the successfully parsed nodes in the out_irq array. This leak occurs on all return paths, such as when map_sz is zero or when kcalloc() fails:

    ...
	if (!map_sz)
		return 0;

	int_map = kcalloc(map_sz, sizeof(u32), GFP_KERNEL);
	if (!int_map)
		return -ENOMEM;
    ...

Are we missing calls to of_node_put() for these references before returning?

Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=2
author
#5 lore raw

Re: [PATCH v5 2/4] PCI: of: drop the reg_num argument to of_pci_set_address()

Quoted 24 lines · On 9/8/26 4:46 PM, [email protected] wrote:

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:

Pre-existing issues:
- [High] Leaked OF node reference counts in of_pci_prop_intr_map() --

commit b3af32f45eed6da355a4a96246bdedb364fe0855
Author: Alex Elder <[email protected]>

PCI: of: drop the reg_num argument to of_pci_set_address()

The reg_num argument passed to of_pci_set_address() is always zero, so get rid of it.

Quoted 4 lines
drivers/pci/of_property.c +0 -0
index d66c702218081..1caabbd4c18b5 100644

[ ... ]

Quoted 3 lines

[Severity: High]
This is a pre-existing issue, but does this code leak device node reference counts in of_pci_prop_intr_map()?

Given of_pci_add_properties() is the only caller, it makes sense that this would be found (again).

This was reported last month, and at that time I said I would investigate and fix this.

https://lore.kernel.org/sashiko-reviews/[email protected]/

I fixed the other problem reported there (possible null pointer dereference).

I still intend to fix this, and will attempt to get to it soon.

-Alex

Quoted 29 lines

When of_irq_parse_raw() successfully parses an interrupt, it acquires a device node reference and stores it in out_irq[i].np:

drivers/pci/of_property.c:of_pci_prop_intr_map() {
     ...
		ret = of_irq_parse_raw(laddr, &out_irq[i]);
		if (ret) {
			out_irq[i].np = NULL;
			pci_dbg(pdev, "parse irq %d failed, ret %d", pin, ret);
			continue;
		}
     ...

However, the function returns without calling of_node_put() on the successfully parsed nodes in the out_irq array. This leak occurs on all return paths, such as when map_sz is zero or when kcalloc() fails:

     ...
	if (!map_sz)
		return 0;

	int_map = kcalloc(map_sz, sizeof(u32), GFP_KERNEL);
	if (!int_map)
		return -ENOMEM;
     ...

Are we missing calls to of_node_put() for these references before returning?

author
#6 lore raw

PATCH v5 3/4 PCI: of: don't zero flags in of_pci_get_addr_flags()

In reply to Alex Elder

The flags variable whose address is passed to of_pci_get_addr_flags() is zeroed before assigning a value to it. Skip the zeroing and just assign it instead.

---
 drivers/pci/of_property.c | 4 +---
 1 file changed, 1 insertion(+), 3 deletions(-)
drivers/pci/of_property.c +1 -3
index 1caabbd4c18b5..1e5d7dde467b8 100644
@@ -82,12 +82,10 @@ static int of_pci_get_addr_flags(const struct resource *res, u32 *flags)
 	else
 		return -EINVAL;
 
-	*flags = 0;
+	*flags = FIELD_PREP(OF_PCI_ADDR_FIELD_SS, ss);
 	if (res->flags & IORESOURCE_PREFETCH)
 		*flags |= OF_PCI_ADDR_FIELD_PREFETCH;
 
-	*flags |= FIELD_PREP(OF_PCI_ADDR_FIELD_SS, ss);
-
 	return 0;
 }
 
Signature · 1 line
2.53.0
#7 lore raw

Re: [PATCH v5 3/4] PCI: of: don't zero flags in of_pci_get_addr_flags()

Sashiko has reviewed this patch and found no issues. It looks great!
Quoted 6 lines

The flags variable whose address is passed to of_pci_get_addr_flags() is zeroed before assigning a value to it. Skip the zeroing and just assign it instead.

Sashiko has reviewed this patch and found no issues. It looks great!

Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=3
author
#8 lore raw

PATCH v5 4/4 PCI: of: introduce of_pci_verify_node()

In reply to Alex Elder

Commit 407d1a51921e9 ("PCI: Create device tree node for bridge") linked the PCI enumeration process together with devicetree, creating a devicetree node for discovered PCI bridges. Its successor commit ae9813db1dc5a ("PCI:
Add quirks to generate device tree node for Xilinx Alveo U50") shows how to use a PCI final fixup quirk to also create a devicetree node for a non-bridge PCI device. These changes allowed devicetree overlays to describe components downstream of a PCI device, by providing a place to attach the overlay.

Note that the dynamic devicetree node is only created if the device didn't already have an assigned node.

Later, commit aa7b4bbcb3a1d ("arm64: dts: qcom: qcs6490-rb3gen2: Add TC9563 PCIe switch node") *pre-defined* devicetree nodes to represent the PCI device nodes that would (also) be discovered via the PCI enumeration process. The devicetree node in this case is created with the content from the DTS file. So when a (host) bridge is done being initialized during PCI enumeration, no node is dynamically created (the commits mentioned above do not apply).

Ideally, any pre-defined PCI devicetree node would contain exactly the same information as whatever the dynamic creation process would produce (though it could include more).

However that is not the case for the pre-defined Qualcomm RB3gen2 nodes. And in particular, the endpoint (function) nodes include this property:

    device_type = "pci";

This is simply wrong; that property is meant only for bridge nodes.

Rob Herring requested that a runtime check to be added to spot this specific error, only for non-bridge PCI devices. Herve Codina further suggested we ensure that bridge PCI devices *do* define the device_type property, with value "pci". We also accept "pciex" as the value of the device_type property for bridges.

---
 drivers/pci/bus.c |  1 +
 drivers/pci/of.c  | 32 ++++++++++++++++++++++++++++++++
 drivers/pci/pci.h |  3 +++
 3 files changed, 36 insertions(+)
drivers/pci/bus.c +1 -0
index 655ed53436d3e..679afbc6d3109 100644
@@ -351,6 +351,7 @@ void pci_bus_add_device(struct pci_dev *dev)
 	 * are not assigned yet for some devices.
 	 */
 	pcibios_bus_add_device(dev);
+	of_pci_verify_node(dev);
 	pci_fixup_device(pci_fixup_final, dev);
 	if (pci_is_bridge(dev))
 		of_pci_make_dev_node(dev);
drivers/pci/of.c +32 -0
index a51dff91b196d..5a040ed836744 100644
@@ -1085,3 +1085,35 @@ int of_pci_get_equalization_presets(struct device *dev,
 	return 0;
 }
 EXPORT_SYMBOL_GPL(of_pci_get_equalization_presets);
+
+/**
+ * of_pci_verify_node - Sanity check some PCI device node properties
+ * @pdev: The PCI device whose device node is checked
+ *
+ * PCI enumeration authoritatively discovers what we need to know about
+ * a PCI device.  A devicetree-based platform will represent a PCI root
+ * bridge with a node, but otherwise devicetree doesn't typically include
+ * many PCI nodes.  Where such nodes do exist, experience has shown that
+ * the "device_type" property is sometimes wrong, so warn about that.
+ */
+void of_pci_verify_node(struct pci_dev *pdev)
+{
+	struct device_node *np = pci_device_to_OF_node(pdev);
+	bool device_is_bridge;
+	bool device_type_pci;
+
+	/* Nothing to check if there's no pre-existing devicetree node */
+	if (!np)
+		return;
+
+	device_is_bridge = pci_is_bridge(pdev);
+	device_type_pci = of_node_is_type(np, "pci") ||
+			  of_node_is_type(np, "pciex");
+
+	/* Bridges should have device type "pci"; endpoints should not */
+	if (device_is_bridge == device_type_pci)
+		return;
+
+	dev_err(&pdev->dev, "PCI %s have \"pci\" device_type property\n",
+		device_is_bridge ? "bridge should" : "endpoint should not");
+}
drivers/pci/pci.h +3 -0
index ba3c3fddddc23..2e33d3bd4b0ba 100644
@@ -1253,6 +1253,7 @@ bool of_pci_supply_present(struct device_node *np);
 int of_pci_get_equalization_presets(struct device *dev,
 				    struct pci_eq_presets *presets,
 				    int num_lanes);
+void of_pci_verify_node(struct pci_dev *pdev);
 #else
 static inline int
 of_get_pci_domain_nr(struct device_node *node)
@@ -1308,6 +1309,8 @@ static inline int of_pci_get_equalization_presets(struct device *dev,
 
 	return 0;
 }
+
+static inline void of_pci_verify_node(struct pci_dev *pdev) { }
 #endif /* CONFIG_OF */
 
 struct of_changeset;
Signature · 1 line
2.53.0
#9 lore raw

Re: [PATCH v5 4/4] PCI: of: introduce of_pci_verify_node()

Sashiko has reviewed this patch and found no issues. It looks great!
Quoted 19 lines

Commit 407d1a51921e9 ("PCI: Create device tree node for bridge") linked the PCI enumeration process together with devicetree, creating a devicetree node for discovered PCI bridges. Its successor commit ae9813db1dc5a ("PCI:
Add quirks to generate device tree node for Xilinx Alveo U50") shows how to use a PCI final fixup quirk to also create a devicetree node for a non-bridge PCI device. These changes allowed devicetree overlays to describe components downstream of a PCI device, by providing a place to attach the overlay.

Note that the dynamic devicetree node is only created if the device didn't already have an assigned node.

Later, commit aa7b4bbcb3a1d ("arm64: dts: qcom: qcs6490-rb3gen2: Add TC9563 PCIe switch node") *pre-defined* devicetree nodes to represent the PCI device nodes that would (also) be discovered via the PCI enumeration [ ... ]
property, with value "pci". We also accept "pciex" as the value of the device_type property for bridges.

Quoted 2 lines

Sashiko has reviewed this patch and found no issues. It looks great!

Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=4
#10 lore raw

Re: [PATCH v5 4/4] PCI: of: introduce of_pci_verify_node()

In reply to Alex Elder
Quoted 29 lines · Alex Elder

Commit 407d1a51921e9 ("PCI: Create device tree node for bridge") linked the PCI enumeration process together with devicetree, creating a devicetree node for discovered PCI bridges. Its successor commit ae9813db1dc5a ("PCI:
Add quirks to generate device tree node for Xilinx Alveo U50") shows how to use a PCI final fixup quirk to also create a devicetree node for a non-bridge PCI device. These changes allowed devicetree overlays to describe components downstream of a PCI device, by providing a place to attach the overlay.

Note that the dynamic devicetree node is only created if the device didn't already have an assigned node.

Later, commit aa7b4bbcb3a1d ("arm64: dts: qcom: qcs6490-rb3gen2: Add TC9563 PCIe switch node") *pre-defined* devicetree nodes to represent the PCI device nodes that would (also) be discovered via the PCI enumeration process. The devicetree node in this case is created with the content from the DTS file. So when a (host) bridge is done being initialized during PCI enumeration, no node is dynamically created (the commits mentioned above do not apply).

Ideally, any pre-defined PCI devicetree node would contain exactly the same information as whatever the dynamic creation process would produce (though it could include more).

However that is not the case for the pre-defined Qualcomm RB3gen2 nodes. And in particular, the endpoint (function) nodes include this property:

    device_type = "pci";
Quoted 7 lines

This is simply wrong; that property is meant only for bridge nodes.

Rob Herring requested that a runtime check to be added to spot this specific error, only for non-bridge PCI devices. Herve Codina further suggested we ensure that bridge PCI devices *do* define the device_type property, with value "pci". We also accept "pciex" as the value of the device_type property for bridges.

IIUC this patch basically adds a warning for ill-formed DTs but doesn't otherwise change any behavior. Obviously a developer could fix the DT, but what would a user do in response to the warning?

Is there something that doesn't work when this warning is emitted, and fixing the DT would make it work? If so, is there any hint that could be included in the warning? E.g., "DT device_type is missing; can't support X"?

The commit log doesn't actually say directly what the patch does. I asked gemini to work on it, and it produced this, which is straightforward but omits a lot of detail you might want:

PCI: of: Verify "device_type" property for PCI device nodes

According to the Open Firmware PCI Bus Supplement and Devicetree Specification, the device_type property is reserved strictly for PCI bridge nodes (with value "pci" or "pciex"). Non-bridge PCI endpoint nodes must not include a device_type property.

Pre-defined Device Tree nodes representing PCI devices sometimes violate this requirement—such as endpoint nodes incorrectly specifying device_type = "pci", or bridge nodes omitting device_type altogether.

Introduce of_pci_verify_node() to sanity-check pre-defined Device Tree nodes assigned to PCI devices as they are registered in pci_bus_add_device():

- Warn if a PCI bridge node lacks a "pci" or "pciex" device_type property.

- Warn if a non-bridge PCI endpoint node contains a device_type property.

I'd like to get Rob's Reviewed-by since I'm not really a DT expert.

Quoted 84 lines
---
 drivers/pci/bus.c |  1 +
 drivers/pci/of.c  | 32 ++++++++++++++++++++++++++++++++
 drivers/pci/pci.h |  3 +++
 3 files changed, 36 insertions(+)
drivers/pci/bus.c +1 -0
index 655ed53436d3e..679afbc6d3109 100644
@@ -351,6 +351,7 @@ void pci_bus_add_device(struct pci_dev *dev)
 	 * are not assigned yet for some devices.
 	 */
 	pcibios_bus_add_device(dev);
+	of_pci_verify_node(dev);
 	pci_fixup_device(pci_fixup_final, dev);
 	if (pci_is_bridge(dev))
 		of_pci_make_dev_node(dev);
drivers/pci/of.c +32 -0
index a51dff91b196d..5a040ed836744 100644
@@ -1085,3 +1085,35 @@ int of_pci_get_equalization_presets(struct device *dev,
 	return 0;
 }
 EXPORT_SYMBOL_GPL(of_pci_get_equalization_presets);
+
+/**
+ * of_pci_verify_node - Sanity check some PCI device node properties
+ * @pdev: The PCI device whose device node is checked
+ *
+ * PCI enumeration authoritatively discovers what we need to know about
+ * a PCI device.  A devicetree-based platform will represent a PCI root
+ * bridge with a node, but otherwise devicetree doesn't typically include
+ * many PCI nodes.  Where such nodes do exist, experience has shown that
+ * the "device_type" property is sometimes wrong, so warn about that.
+ */
+void of_pci_verify_node(struct pci_dev *pdev)
+{
+	struct device_node *np = pci_device_to_OF_node(pdev);
+	bool device_is_bridge;
+	bool device_type_pci;
+
+	/* Nothing to check if there's no pre-existing devicetree node */
+	if (!np)
+		return;
+
+	device_is_bridge = pci_is_bridge(pdev);
+	device_type_pci = of_node_is_type(np, "pci") ||
+			  of_node_is_type(np, "pciex");
+
+	/* Bridges should have device type "pci"; endpoints should not */
+	if (device_is_bridge == device_type_pci)
+		return;
+
+	dev_err(&pdev->dev, "PCI %s have \"pci\" device_type property\n",
+		device_is_bridge ? "bridge should" : "endpoint should not");
+}
drivers/pci/pci.h +3 -0
index ba3c3fddddc23..2e33d3bd4b0ba 100644
@@ -1253,6 +1253,7 @@ bool of_pci_supply_present(struct device_node *np);
 int of_pci_get_equalization_presets(struct device *dev,
 				    struct pci_eq_presets *presets,
 				    int num_lanes);
+void of_pci_verify_node(struct pci_dev *pdev);
 #else
 static inline int
 of_get_pci_domain_nr(struct device_node *node)
@@ -1308,6 +1309,8 @@ static inline int of_pci_get_equalization_presets(struct device *dev,
 
 	return 0;
 }
+
+static inline void of_pci_verify_node(struct pci_dev *pdev) { }
 #endif /* CONFIG_OF */
 
 struct of_changeset;
Signature · 1 line
2.53.0
author
#11 lore raw

Re: [PATCH v5 4/4] PCI: of: introduce of_pci_verify_node()

Quoted 38 lines · Bjorn Helgaas
Quoted 29 lines · Alex Elder

Commit 407d1a51921e9 ("PCI: Create device tree node for bridge") linked the PCI enumeration process together with devicetree, creating a devicetree node for discovered PCI bridges. Its successor commit ae9813db1dc5a ("PCI:
Add quirks to generate device tree node for Xilinx Alveo U50") shows how to use a PCI final fixup quirk to also create a devicetree node for a non-bridge PCI device. These changes allowed devicetree overlays to describe components downstream of a PCI device, by providing a place to attach the overlay.

Note that the dynamic devicetree node is only created if the device didn't already have an assigned node.

Later, commit aa7b4bbcb3a1d ("arm64: dts: qcom: qcs6490-rb3gen2: Add TC9563 PCIe switch node") *pre-defined* devicetree nodes to represent the PCI device nodes that would (also) be discovered via the PCI enumeration process. The devicetree node in this case is created with the content from the DTS file. So when a (host) bridge is done being initialized during PCI enumeration, no node is dynamically created (the commits mentioned above do not apply).

Ideally, any pre-defined PCI devicetree node would contain exactly the same information as whatever the dynamic creation process would produce (though it could include more).

However that is not the case for the pre-defined Qualcomm RB3gen2 nodes. And in particular, the endpoint (function) nodes include this property:

     device_type = "pci";
Quoted 7 lines

This is simply wrong; that property is meant only for bridge nodes.

Rob Herring requested that a runtime check to be added to spot this specific error, only for non-bridge PCI devices. Herve Codina further suggested we ensure that bridge PCI devices *do* define the device_type property, with value "pci". We also accept "pciex" as the value of the device_type property for bridges.

Quoted 3 lines

IIUC this patch basically adds a warning for ill-formed DTs but doesn't otherwise change any behavior. Obviously a developer could fix the DT, but what would a user do in response to the warning?

You are correct. It really is meant for developers, not users, and no user should ever see these warnings.

Quoted 4 lines

Is there something that doesn't work when this warning is emitted, and fixing the DT would make it work? If so, is there any hint that could be included in the warning? E.g., "DT device_type is missing; can't support X"?

I discovered this the hard way... I'm implementing a PCI endpoint bus, and didn't realize that endpoints should not have the device_type property (= "pci") specified. These endpoints *did* specify that property, and as a result, address mapping for the pci-ep-bus didn't work.

To be honest I don't know the consequences of this property being missing for a PCI bridge device. I do know the problem I encountered if it's specified with value "pci" for an endpoint node. I have no objection to making the message more informative, but it might not really cover all the things that won't work as a result.

The DTS files have been fixed:
https://lore.kernel.org/lkml/[email protected]/
(And as a result, a few verbs above should be made past tense.)

I created this patch simply in response to Rob's suggestion, and updated it based on Herve's.

I could update the description to look more like what you got Gemini to produce. That talks about some context that I did not provide, but it also lacks some of the other context (like when/why PCI devices even have devicetree nodes, and how I discovered it).

Quoted 20 lines

The commit log doesn't actually say directly what the patch does. I asked gemini to work on it, and it produced this, which is straightforward but omits a lot of detail you might want:

PCI: of: Verify "device_type" property for PCI device nodes

According to the Open Firmware PCI Bus Supplement and Devicetree Specification, the device_type property is reserved strictly for PCI bridge nodes (with value "pci" or "pciex"). Non-bridge PCI endpoint nodes must not include a device_type property.

Pre-defined Device Tree nodes representing PCI devices sometimes violate this requirement—such as endpoint nodes incorrectly specifying device_type = "pci", or bridge nodes omitting device_type altogether.

Introduce of_pci_verify_node() to sanity-check pre-defined Device Tree nodes assigned to PCI devices as they are registered in pci_bus_add_device():

Quoted 7 lines

- Warn if a PCI bridge node lacks a "pci" or "pciex" device_type property.

- Warn if a non-bridge PCI endpoint node contains a device_type property.

I'd like to get Rob's Reviewed-by since I'm not really a DT expert.

I agree, I think we need that since it was his suggestion.

I don't mind spinning version 6 to update the description and possible the warning message. I'll leave that up to you (or Rob) to request that before it gets accepted.

Thanks.

-Alex

Quoted 85 lines
Quoted 85 lines
---
  drivers/pci/bus.c |  1 +
  drivers/pci/of.c  | 32 ++++++++++++++++++++++++++++++++
  drivers/pci/pci.h |  3 +++
  3 files changed, 36 insertions(+)
drivers/pci/bus.c +1 -0
index 655ed53436d3e..679afbc6d3109 100644
@@ -351,6 +351,7 @@ void pci_bus_add_device(struct pci_dev *dev)
  	 * are not assigned yet for some devices.
  	 */
  	pcibios_bus_add_device(dev);
+	of_pci_verify_node(dev);
  	pci_fixup_device(pci_fixup_final, dev);
  	if (pci_is_bridge(dev))
  		of_pci_make_dev_node(dev);
drivers/pci/of.c +32 -0
index a51dff91b196d..5a040ed836744 100644
@@ -1085,3 +1085,35 @@ int of_pci_get_equalization_presets(struct device *dev,
  	return 0;
  }
  EXPORT_SYMBOL_GPL(of_pci_get_equalization_presets);
+
+/**
+ * of_pci_verify_node - Sanity check some PCI device node properties
+ * @pdev: The PCI device whose device node is checked
+ *
+ * PCI enumeration authoritatively discovers what we need to know about
+ * a PCI device.  A devicetree-based platform will represent a PCI root
+ * bridge with a node, but otherwise devicetree doesn't typically include
+ * many PCI nodes.  Where such nodes do exist, experience has shown that
+ * the "device_type" property is sometimes wrong, so warn about that.
+ */
+void of_pci_verify_node(struct pci_dev *pdev)
+{
+	struct device_node *np = pci_device_to_OF_node(pdev);
+	bool device_is_bridge;
+	bool device_type_pci;
+
+	/* Nothing to check if there's no pre-existing devicetree node */
+	if (!np)
+		return;
+
+	device_is_bridge = pci_is_bridge(pdev);
+	device_type_pci = of_node_is_type(np, "pci") ||
+			  of_node_is_type(np, "pciex");
+
+	/* Bridges should have device type "pci"; endpoints should not */
+	if (device_is_bridge == device_type_pci)
+		return;
+
+	dev_err(&pdev->dev, "PCI %s have \"pci\" device_type property\n",
+		device_is_bridge ? "bridge should" : "endpoint should not");
+}
drivers/pci/pci.h +3 -0
index ba3c3fddddc23..2e33d3bd4b0ba 100644
@@ -1253,6 +1253,7 @@ bool of_pci_supply_present(struct device_node *np);
  int of_pci_get_equalization_presets(struct device *dev,
  				    struct pci_eq_presets *presets,
  				    int num_lanes);
+void of_pci_verify_node(struct pci_dev *pdev);
  #else
  static inline int
  of_get_pci_domain_nr(struct device_node *node)
@@ -1308,6 +1309,8 @@ static inline int of_pci_get_equalization_presets(struct device *dev,
 
  	return 0;
  }
+
+static inline void of_pci_verify_node(struct pci_dev *pdev) { }
  #endif /* CONFIG_OF */
 
  struct of_changeset;
Signature · 1 line
2.53.0