Merge branch 'pci/devtree-create'

- Add device_add_of_node() to set dev->of_node and dev->fwnode only if they
  haven't been set already (Herve Codina)

- Allow of_pci_set_address() to set the DT address property for root bus
  nodes, where there is no PCI bridge to supply the PCI bus/device/function
  part of the property (Herve Codina)

- Create DT nodes for PCI host bridges to enable loading device tree
  overlays to create platform devices for PCI devices that have several
  features that require multiple drivers (Herve Codina)

* pci/devtree-create:
  PCI: of: Create device tree PCI host bridge node
  PCI: of_property: Constify parameter in of_pci_get_addr_flags()
  PCI: of_property: Add support for NULL pdev in of_pci_set_address()
  PCI: of: Use device_{add,remove}_of_node() to attach of_node to existing device
  driver core: Introduce device_{add,remove}_of_node()
This commit is contained in:
Bjorn Helgaas 2025-03-27 13:14:45 -05:00
commit a1aed6b34f
7 changed files with 300 additions and 7 deletions

View File

@ -5170,6 +5170,67 @@ void set_secondary_fwnode(struct device *dev, struct fwnode_handle *fwnode)
}
EXPORT_SYMBOL_GPL(set_secondary_fwnode);
/**
* device_remove_of_node - Remove an of_node from a device
* @dev: device whose device tree node is being removed
*/
void device_remove_of_node(struct device *dev)
{
dev = get_device(dev);
if (!dev)
return;
if (!dev->of_node)
goto end;
if (dev->fwnode == of_fwnode_handle(dev->of_node))
dev->fwnode = NULL;
of_node_put(dev->of_node);
dev->of_node = NULL;
end:
put_device(dev);
}
EXPORT_SYMBOL_GPL(device_remove_of_node);
/**
* device_add_of_node - Add an of_node to an existing device
* @dev: device whose device tree node is being added
* @of_node: of_node to add
*
* Return: 0 on success or error code on failure.
*/
int device_add_of_node(struct device *dev, struct device_node *of_node)
{
int ret;
if (!of_node)
return -EINVAL;
dev = get_device(dev);
if (!dev)
return -EINVAL;
if (dev->of_node) {
dev_err(dev, "Cannot replace node %pOF with %pOF\n",
dev->of_node, of_node);
ret = -EBUSY;
goto end;
}
dev->of_node = of_node_get(of_node);
if (!dev->fwnode)
dev->fwnode = of_fwnode_handle(of_node);
ret = 0;
end:
put_device(dev);
return ret;
}
EXPORT_SYMBOL_GPL(device_add_of_node);
/**
* device_set_of_node_from_dev - reuse device-tree node of another device
* @dev: device whose device-tree node is being set

View File

@ -653,8 +653,8 @@ void of_pci_remove_node(struct pci_dev *pdev)
np = pci_device_to_OF_node(pdev);
if (!np || !of_node_check_flag(np, OF_DYNAMIC))
return;
pdev->dev.of_node = NULL;
device_remove_of_node(&pdev->dev);
of_changeset_revert(np->data);
of_changeset_destroy(np->data);
of_node_put(np);
@ -711,11 +711,18 @@ void of_pci_make_dev_node(struct pci_dev *pdev)
goto out_free_node;
np->data = cset;
pdev->dev.of_node = np;
ret = device_add_of_node(&pdev->dev, np);
if (ret)
goto out_revert_cset;
kfree(name);
return;
out_revert_cset:
np->data = NULL;
of_changeset_revert(cset);
out_free_node:
of_node_put(np);
out_destroy_cset:
@ -724,7 +731,112 @@ out_destroy_cset:
out_free_name:
kfree(name);
}
#endif
void of_pci_remove_host_bridge_node(struct pci_host_bridge *bridge)
{
struct device_node *np;
np = pci_bus_to_OF_node(bridge->bus);
if (!np || !of_node_check_flag(np, OF_DYNAMIC))
return;
device_remove_of_node(&bridge->bus->dev);
device_remove_of_node(&bridge->dev);
of_changeset_revert(np->data);
of_changeset_destroy(np->data);
of_node_put(np);
}
void of_pci_make_host_bridge_node(struct pci_host_bridge *bridge)
{
struct device_node *np = NULL;
struct of_changeset *cset;
const char *name;
int ret;
/*
* If there is already a device tree node linked to the PCI bus handled
* by this bridge (i.e. the PCI root bus), nothing to do.
*/
if (pci_bus_to_OF_node(bridge->bus))
return;
/*
* The root bus has no node. Check that the host bridge has no node
* too
*/
if (bridge->dev.of_node) {
dev_err(&bridge->dev, "PCI host bridge of_node already set");
return;
}
/* Check if there is a DT root node to attach the created node */
if (!of_root) {
pr_err("of_root node is NULL, cannot create PCI host bridge node\n");
return;
}
name = kasprintf(GFP_KERNEL, "pci@%x,%x", pci_domain_nr(bridge->bus),
bridge->bus->number);
if (!name)
return;
cset = kmalloc(sizeof(*cset), GFP_KERNEL);
if (!cset)
goto out_free_name;
of_changeset_init(cset);
np = of_changeset_create_node(cset, of_root, name);
if (!np)
goto out_destroy_cset;
ret = of_pci_add_host_bridge_properties(bridge, cset, np);
if (ret)
goto out_free_node;
/*
* This of_node will be added to an existing device. The of_node parent
* is the root OF node and so this node will be handled by the platform
* bus. Avoid any new device creation.
*/
of_node_set_flag(np, OF_POPULATED);
np->fwnode.dev = &bridge->dev;
fwnode_dev_initialized(&np->fwnode, true);
ret = of_changeset_apply(cset);
if (ret)
goto out_free_node;
np->data = cset;
/* Add the of_node to host bridge and the root bus */
ret = device_add_of_node(&bridge->dev, np);
if (ret)
goto out_revert_cset;
ret = device_add_of_node(&bridge->bus->dev, np);
if (ret)
goto out_remove_bridge_dev_of_node;
kfree(name);
return;
out_remove_bridge_dev_of_node:
device_remove_of_node(&bridge->dev);
out_revert_cset:
np->data = NULL;
of_changeset_revert(cset);
out_free_node:
of_node_put(np);
out_destroy_cset:
of_changeset_destroy(cset);
kfree(cset);
out_free_name:
kfree(name);
}
#endif /* CONFIG_PCI_DYNAMIC_OF_NODES */
/**
* of_pci_supply_present() - Check if the power supply is present for the PCI

View File

@ -54,9 +54,13 @@ 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)
{
prop[0] = 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));
if (pdev) {
prop[0] = 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));
} else
prop[0] = 0;
prop[0] |= flags | reg_num;
if (!reloc) {
prop[0] |= OF_PCI_ADDR_FIELD_NONRELOC;
@ -65,7 +69,7 @@ static void of_pci_set_address(struct pci_dev *pdev, u32 *prop, u64 addr,
}
}
static int of_pci_get_addr_flags(struct resource *res, u32 *flags)
static int of_pci_get_addr_flags(const struct resource *res, u32 *flags)
{
u32 ss;
@ -390,3 +394,106 @@ int of_pci_add_properties(struct pci_dev *pdev, struct of_changeset *ocs,
return 0;
}
static bool of_pci_is_range_resource(const struct resource *res, u32 *flags)
{
if (!(resource_type(res) & IORESOURCE_MEM) &&
!(resource_type(res) & IORESOURCE_MEM_64))
return false;
if (of_pci_get_addr_flags(res, flags))
return false;
return true;
}
static int of_pci_host_bridge_prop_ranges(struct pci_host_bridge *bridge,
struct of_changeset *ocs,
struct device_node *np)
{
struct resource_entry *window;
unsigned int ranges_sz = 0;
unsigned int n_range = 0;
struct resource *res;
int n_addr_cells;
u32 *ranges;
u64 val64;
u32 flags;
int ret;
n_addr_cells = of_n_addr_cells(np);
if (n_addr_cells <= 0 || n_addr_cells > 2)
return -EINVAL;
resource_list_for_each_entry(window, &bridge->windows) {
res = window->res;
if (!of_pci_is_range_resource(res, &flags))
continue;
n_range++;
}
if (!n_range)
return 0;
ranges = kcalloc(n_range,
(OF_PCI_ADDRESS_CELLS + OF_PCI_SIZE_CELLS +
n_addr_cells) * sizeof(*ranges),
GFP_KERNEL);
if (!ranges)
return -ENOMEM;
resource_list_for_each_entry(window, &bridge->windows) {
res = window->res;
if (!of_pci_is_range_resource(res, &flags))
continue;
/* PCI bus address */
val64 = res->start;
of_pci_set_address(NULL, &ranges[ranges_sz],
val64 - window->offset, 0, flags, false);
ranges_sz += OF_PCI_ADDRESS_CELLS;
/* Host bus address */
if (n_addr_cells == 2)
ranges[ranges_sz++] = upper_32_bits(val64);
ranges[ranges_sz++] = lower_32_bits(val64);
/* Size */
val64 = resource_size(res);
ranges[ranges_sz] = upper_32_bits(val64);
ranges[ranges_sz + 1] = lower_32_bits(val64);
ranges_sz += OF_PCI_SIZE_CELLS;
}
ret = of_changeset_add_prop_u32_array(ocs, np, "ranges", ranges,
ranges_sz);
kfree(ranges);
return ret;
}
int of_pci_add_host_bridge_properties(struct pci_host_bridge *bridge,
struct of_changeset *ocs,
struct device_node *np)
{
int ret;
ret = of_changeset_add_prop_string(ocs, np, "device_type", "pci");
if (ret)
return ret;
ret = of_changeset_add_prop_u32(ocs, np, "#address-cells",
OF_PCI_ADDRESS_CELLS);
if (ret)
return ret;
ret = of_changeset_add_prop_u32(ocs, np, "#size-cells",
OF_PCI_SIZE_CELLS);
if (ret)
return ret;
ret = of_pci_host_bridge_prop_ranges(bridge, ocs, np);
if (ret)
return ret;
return 0;
}

View File

@ -944,9 +944,16 @@ 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);
void of_pci_make_host_bridge_node(struct pci_host_bridge *bridge);
void of_pci_remove_host_bridge_node(struct pci_host_bridge *bridge);
int of_pci_add_host_bridge_properties(struct pci_host_bridge *bridge,
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) { }
static inline void of_pci_make_host_bridge_node(struct pci_host_bridge *bridge) { }
static inline void of_pci_remove_host_bridge_node(struct pci_host_bridge *bridge) { }
#endif
#ifdef CONFIG_PCIEAER

View File

@ -1098,6 +1098,8 @@ static int pci_register_host_bridge(struct pci_host_bridge *bridge)
dev_info(&bus->dev, "root bus resource %pR%s\n", res, addr);
}
of_pci_make_host_bridge_node(bridge);
down_write(&pci_bus_sem);
list_add_tail(&bus->node, &pci_root_buses);
up_write(&pci_bus_sem);

View File

@ -164,6 +164,8 @@ void pci_stop_root_bus(struct pci_bus *bus)
&bus->devices, bus_list)
pci_stop_bus_device(child);
of_pci_remove_host_bridge_node(host_bridge);
/* stop the host bridge */
device_release_driver(&host_bridge->dev);
}

View File

@ -1191,6 +1191,8 @@ int device_online(struct device *dev);
void set_primary_fwnode(struct device *dev, struct fwnode_handle *fwnode);
void set_secondary_fwnode(struct device *dev, struct fwnode_handle *fwnode);
void device_set_node(struct device *dev, struct fwnode_handle *fwnode);
int device_add_of_node(struct device *dev, struct device_node *of_node);
void device_remove_of_node(struct device *dev);
void device_set_of_node_from_dev(struct device *dev, const struct device *dev2);
static inline struct device_node *dev_of_node(struct device *dev)