@@ -198,6 +198,18 @@ config PCI_HYPERV
The PCI device frontend driver allows the kernel to import arbitrary
PCI devices from a PCI backend to support PCI driver domains.
+config PCI_DYNAMIC_OF_NODES
+ bool "Device tree node for PCI devices"
+ depends on OF
+ select OF_DYNAMIC
+ help
+ This option enables support for generating device tree nodes for some
+ PCI devices. Thus, the driver of this kind can load and overlay
+ flattened device tree for its downstream devices.
+
+ Once this option is selected, the device tree nodes will be generated
+ for all PCI/PCIE bridges.
+
choice
prompt "PCI Express hierarchy optimization setting"
default PCIE_BUS_DEFAULT
@@ -32,6 +32,7 @@ obj-$(CONFIG_PCI_P2PDMA) += p2pdma.o
obj-$(CONFIG_XEN_PCIDEV_FRONTEND) += xen-pcifront.o
obj-$(CONFIG_VGA_ARB) += vgaarb.o
obj-$(CONFIG_PCI_DOE) += doe.o
+obj-$(CONFIG_PCI_DYNAMIC_OF_NODES) += of_property.o
# Endpoint library must be initialized before its users
obj-$(CONFIG_PCI_ENDPOINT) += endpoint/
@@ -316,6 +316,8 @@ void pci_bus_add_device(struct pci_dev *dev)
*/
pcibios_bus_add_device(dev);
pci_fixup_device(pci_fixup_final, dev);
+ if (pci_is_bridge(dev))
+ of_pci_make_dev_node(dev);
pci_create_sysfs_dev_files(dev);
pci_proc_attach_device(dev);
pci_bridge_d3_update(dev);
@@ -230,8 +230,10 @@ u32 pci_msi_domain_get_msi_rid(struct irq_domain *domain, struct pci_dev *pdev)
pci_for_each_dma_alias(pdev, get_msi_id_cb, &rid);
of_node = irq_domain_get_of_node(domain);
- rid = of_node ? of_msi_map_id(&pdev->dev, of_node, rid) :
- iort_msi_map_id(&pdev->dev, rid);
+ if (of_node && !of_node_check_flag(of_node, OF_DYNAMIC))
+ rid = of_msi_map_id(&pdev->dev, of_node, rid);
+ else
+ rid = iort_msi_map_id(&pdev->dev, rid);
return rid;
}
@@ -469,6 +469,8 @@ static int of_irq_parse_pci(const struct pci_dev *pdev, struct of_phandle_args *
} else {
/* We found a P2P bridge, check if it has a node */
ppnode = pci_device_to_OF_node(ppdev);
+ if (of_node_check_flag(ppnode, OF_DYNAMIC))
+ ppnode = NULL;
}
/*
@@ -599,6 +601,75 @@ int devm_of_pci_bridge_init(struct device *dev, struct pci_host_bridge *bridge)
return pci_parse_request_of_pci_ranges(dev, bridge);
}
+#if IS_ENABLED(CONFIG_PCI_DYNAMIC_OF_NODES)
+
+void of_pci_remove_node(struct pci_dev *pdev)
+{
+ struct device_node *dt_node;
+
+ dt_node = pci_device_to_OF_node(pdev);
+ if (!dt_node || !of_node_check_flag(dt_node, OF_DYNAMIC))
+ return;
+ pdev->dev.of_node = NULL;
+
+ of_destroy_node(dt_node);
+}
+
+void of_pci_make_dev_node(struct pci_dev *pdev)
+{
+ struct device_node *parent, *dt_node = NULL;
+ const char *pci_type = "dev";
+ struct of_changeset *cset;
+ const char *full_name;
+ int ret;
+
+ /*
+ * if there is already a device tree node linked to this device,
+ * return immediately.
+ */
+ if (pci_device_to_OF_node(pdev))
+ return;
+
+ /* check if there is device tree node for parent device */
+ if (!pdev->bus->self)
+ parent = pdev->bus->dev.of_node;
+ else
+ parent = pdev->bus->self->dev.of_node;
+ if (!parent)
+ return;
+
+ if (pci_is_bridge(pdev))
+ pci_type = "pci";
+
+ full_name = kasprintf(GFP_KERNEL, "%pOF/%s@%x,%x", parent, pci_type,
+ PCI_SLOT(pdev->devfn), PCI_FUNC(pdev->devfn));
+ if (!full_name)
+ goto failed;
+
+ dt_node = of_create_node(parent, full_name, &cset);
+ if (!dt_node)
+ goto failed;
+ kfree(full_name);
+
+ ret = of_pci_add_properties(pdev, cset, dt_node);
+ if (ret)
+ goto failed;
+
+ ret = of_changeset_apply(cset);
+ if (ret)
+ goto failed;
+
+ pdev->dev.of_node = dt_node;
+
+ return;
+
+failed:
+ if (dt_node)
+ of_destroy_node(dt_node);
+ kfree(full_name);
+}
+#endif
+
#endif /* CONFIG_PCI */
/**
new file mode 100644
@@ -0,0 +1,256 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2022, Advanced Micro Devices, Inc.
+ */
+
+#include <linux/pci.h>
+#include <linux/of.h>
+#include <linux/bitfield.h>
+#include <linux/bits.h>
+#include "pci.h"
+
+struct of_pci_addr_pair {
+ __be32 phys_hi;
+ __be32 phys_mid;
+ __be32 phys_lo;
+ __be32 size_hi;
+ __be32 size_lo;
+};
+
+struct of_pci_range {
+ __be32 child_addr_hi;
+ __be32 child_addr_mid;
+ __be32 child_addr_lo;
+ __be32 parent_addr_hi;
+ __be32 parent_addr_mid;
+ __be32 parent_addr_lo;
+ __be32 size_hi;
+ __be32 size_lo;
+};
+
+#define OF_PCI_ADDR_SPACE_CONFIG 0x0
+#define OF_PCI_ADDR_SPACE_IO 0x1
+#define OF_PCI_ADDR_SPACE_MEM32 0x2
+#define OF_PCI_ADDR_SPACE_MEM64 0x3
+
+#define OF_PCI_ADDR_FIELD_NONRELOC BIT(31)
+#define OF_PCI_ADDR_FIELD_SS GENMASK(25, 24)
+#define OF_PCI_ADDR_FIELD_PREFETCH BIT(30)
+#define OF_PCI_ADDR_FIELD_BUS GENMASK(23, 16)
+#define OF_PCI_ADDR_FIELD_DEV GENMASK(15, 11)
+#define OF_PCI_ADDR_FIELD_FUNC GENMASK(10, 8)
+#define OF_PCI_ADDR_FIELD_REG GENMASK(7, 0)
+
+#define OF_PCI_ADDR_HI GENMASK_ULL(63, 32)
+#define OF_PCI_ADDR_LO GENMASK_ULL(31, 0)
+#define OF_PCI_SIZE_HI GENMASK_ULL(63, 32)
+#define OF_PCI_SIZE_LO GENMASK_ULL(31, 0)
+
+#define OF_PCI_ADDRESS_CELLS 3
+#define OF_PCI_SIZE_CELLS 2
+
+enum of_pci_prop_compatible {
+ PROP_COMPAT_PCI_VVVV_DDDD,
+ PROP_COMPAT_PCICLASS_CCSSPP,
+ PROP_COMPAT_PCICLASS_CCSS,
+ PROP_COMPAT_NUM,
+};
+
+static int of_pci_prop_device_type(struct pci_dev *pdev,
+ struct of_changeset *ocs,
+ struct device_node *np)
+{
+ return of_changeset_add_prop_string(ocs, np, "device_type", "pci");
+}
+
+static int of_pci_prop_address_cells(struct pci_dev *pdev,
+ struct of_changeset *ocs,
+ struct device_node *np)
+{
+ return of_changeset_add_prop_u32(ocs, np, "#address_cells",
+ OF_PCI_ADDRESS_CELLS);
+}
+
+static int of_pci_prop_size_cells(struct pci_dev *pdev,
+ struct of_changeset *ocs,
+ struct device_node *np)
+{
+ return of_changeset_add_prop_u32(ocs, np, "#size_cells",
+ OF_PCI_SIZE_CELLS);
+}
+
+static int of_pci_set_addr_flags(struct resource *res, u32 *addr_hi)
+{
+ u32 ss;
+
+ if (res->flags & IORESOURCE_IO)
+ ss = OF_PCI_ADDR_SPACE_IO;
+ else if (res->flags & IORESOURCE_MEM_64)
+ ss = OF_PCI_ADDR_SPACE_MEM64;
+ else if (res->flags & IORESOURCE_MEM)
+ ss = OF_PCI_ADDR_SPACE_MEM32;
+ else
+ return -EINVAL;
+
+ *addr_hi &= ~(OF_PCI_ADDR_FIELD_SS | OF_PCI_ADDR_FIELD_PREFETCH);
+ if (res->flags & IORESOURCE_PREFETCH)
+ *addr_hi |= OF_PCI_ADDR_FIELD_PREFETCH;
+
+ *addr_hi |= ss;
+
+ return 0;
+}
+
+static int of_pci_prop_ranges(struct pci_dev *pdev, struct of_changeset *ocs,
+ struct device_node *np)
+{
+ struct of_pci_range rp[PCI_BRIDGE_RESOURCE_NUM];
+ struct resource *res;
+ int i = 0, j, ret;
+ u64 val64;
+ u32 val;
+
+ res = &pdev->resource[PCI_BRIDGE_RESOURCES];
+ for (j = 0; j < PCI_BRIDGE_RESOURCE_NUM; j++) {
+ if (!resource_size(&res[j]))
+ continue;
+
+ val = OF_PCI_ADDR_FIELD_NONRELOC;
+ if (of_pci_set_addr_flags(&res[j], &val))
+ continue;
+
+ rp[i].parent_addr_hi = cpu_to_be32(val);
+
+ val64 = res[j].start;
+ rp[i].parent_addr_mid =
+ cpu_to_be32(FIELD_GET(OF_PCI_ADDR_HI, val64));
+ rp[i].parent_addr_lo =
+ cpu_to_be32(FIELD_GET(OF_PCI_ADDR_LO, val64));
+
+ val64 = resource_size(&res[j]);
+ rp[i].size_hi = cpu_to_be32(FIELD_GET(OF_PCI_SIZE_HI, val64));
+ rp[i].size_lo = cpu_to_be32(FIELD_GET(OF_PCI_SIZE_LO, val64));
+
+ rp[i].child_addr_hi = rp[i].parent_addr_hi;
+ rp[i].child_addr_mid = rp[i].parent_addr_mid;
+ rp[i].child_addr_lo = rp[i].parent_addr_lo;
+ i++;
+ }
+
+ ret = of_changeset_add_prop_u32_array(ocs, np, "ranges", (u32 *)rp,
+ i * sizeof(*rp) / sizeof(u32));
+
+ return ret;
+}
+
+static int of_pci_prop_reg(struct pci_dev *pdev, struct of_changeset *ocs,
+ struct device_node *np)
+{
+ struct of_pci_addr_pair *reg;
+ int i = 1, resno, ret = 0;
+ u32 reg_val, base_addr;
+ resource_size_t sz;
+
+ reg = kzalloc(sizeof(*reg) * (PCI_STD_NUM_BARS + 1), GFP_KERNEL);
+ if (!reg)
+ return -ENOMEM;
+
+ reg_val = FIELD_PREP(OF_PCI_ADDR_FIELD_SS, OF_PCI_ADDR_SPACE_CONFIG) |
+ FIELD_PREP(OF_PCI_ADDR_FIELD_BUS, pdev->bus->number) |
+ FIELD_PREP(OF_PCI_ADDR_FIELD_DEV, PCI_SLOT(pdev->devfn)) |
+ FIELD_PREP(OF_PCI_ADDR_FIELD_FUNC, PCI_FUNC(pdev->devfn));
+ reg[0].phys_hi = cpu_to_be32(reg_val);
+
+ base_addr = PCI_BASE_ADDRESS_0;
+ for (resno = PCI_STD_RESOURCES; resno <= PCI_STD_RESOURCE_END;
+ resno++, base_addr += 4) {
+ sz = pci_resource_len(pdev, resno);
+ if (!sz)
+ continue;
+
+ ret = of_pci_set_addr_flags(&pdev->resource[resno], ®_val);
+ if (!ret)
+ continue;
+
+ reg_val &= ~OF_PCI_ADDR_FIELD_REG;
+ reg_val |= FIELD_PREP(OF_PCI_ADDR_FIELD_REG, base_addr);
+ reg[i].phys_hi = cpu_to_be32(reg_val);
+ reg[i].size_hi = cpu_to_be32(FIELD_GET(OF_PCI_SIZE_HI, sz));
+ reg[i].size_lo = cpu_to_be32(FIELD_GET(OF_PCI_SIZE_LO, sz));
+ i++;
+ }
+
+ ret = of_changeset_add_prop_u32_array(ocs, np, "reg", (u32 *)reg,
+ i * sizeof(*reg) / sizeof(u32));
+ kfree(reg);
+
+ return ret;
+}
+
+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;
+
+ 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 = 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]);
+
+ return ret;
+}
+
+static int (*of_pci_endpoint_props[])(struct pci_dev *pdev,
+ struct of_changeset *ocs,
+ struct device_node *np) = {
+ of_pci_prop_reg,
+ of_pci_prop_compatible,
+ NULL
+};
+
+static int (*of_pci_bridge_props[])(struct pci_dev *pdev,
+ struct of_changeset *ocs,
+ struct device_node *np) = {
+ of_pci_prop_device_type,
+ of_pci_prop_address_cells,
+ of_pci_prop_size_cells,
+ of_pci_prop_ranges,
+ of_pci_prop_reg,
+ of_pci_prop_compatible,
+ NULL
+};
+
+int of_pci_add_properties(struct pci_dev *pdev, struct of_changeset *ocs,
+ struct device_node *np)
+{
+ int (**prop_func)(struct pci_dev *pdev, struct of_changeset *ocs,
+ struct device_node *np);
+ int i, ret;
+
+ if (pci_is_bridge(pdev))
+ prop_func = of_pci_bridge_props;
+ else
+ prop_func = of_pci_endpoint_props;
+
+ for (i = 0; prop_func[i]; i++) {
+ ret = prop_func[i](pdev, ocs, np);
+ if (ret) {
+ /*
+ * The added properties will be released when the
+ * changeset is destroyed.
+ */
+ return ret;
+ }
+ }
+
+ return 0;
+}
@@ -1628,7 +1628,8 @@ static int pci_dma_configure(struct device *dev)
bridge = pci_get_host_bridge_device(to_pci_dev(dev));
if (IS_ENABLED(CONFIG_OF) && bridge->parent &&
- bridge->parent->of_node) {
+ bridge->parent->of_node &&
+ !of_node_check_flag(bridge->parent->of_node, OF_DYNAMIC)) {
ret = of_dma_configure(dev, bridge->parent->of_node, true);
} else if (has_acpi_companion(bridge)) {
struct acpi_device *adev = to_acpi_device_node(bridge->fwnode);
@@ -678,6 +678,25 @@ static inline int devm_of_pci_bridge_init(struct device *dev, struct pci_host_br
#endif /* CONFIG_OF */
+struct of_changeset;
+
+#ifdef CONFIG_PCI_DYNAMIC_OF_NODES
+void of_pci_make_dev_node(struct pci_dev *pdev);
+void of_pci_remove_node(struct pci_dev *pdev);
+int of_pci_add_properties(struct pci_dev *pdev, struct of_changeset *ocs,
+ struct device_node *np);
+#else
+static inline void
+of_pci_make_dev_node(struct pci_dev *pdev)
+{
+}
+
+static inline void
+of_pci_remove_node(struct pci_dev *pdev)
+{
+}
+#endif /* CONFIG_PCI_DYNAMIC_OF_NODES */
+
#ifdef CONFIG_PCIEAER
void pci_no_aer(void);
void pci_aer_init(struct pci_dev *dev);
@@ -5956,3 +5956,14 @@ DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x56b1, aspm_l1_acceptable_latency
DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x56c0, aspm_l1_acceptable_latency);
DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x56c1, aspm_l1_acceptable_latency);
#endif
+
+/*
+ * For PCI device which have multiple downstream devices, its driver may use
+ * a flattened device tree to describe the downstream devices.
+ * To overlay the flattened device tree, the PCI device and all its ancestor
+ * devices need to have device tree nodes on system base device tree. Thus,
+ * before driver probing, it might need to add a device tree node as the final
+ * fixup.
+ */
+DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_XILINX, 0x5020, of_pci_make_dev_node);
+DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_XILINX, 0x5021, of_pci_make_dev_node);
@@ -23,6 +23,7 @@ static void pci_stop_dev(struct pci_dev *dev)
device_release_driver(&dev->dev);
pci_proc_detach_device(dev);
pci_remove_sysfs_dev_files(dev);
+ of_pci_remove_node(dev);
pci_dev_assign_added(dev, false);
}