Repository navigation
virtio-scmi: power domain of_genpd_add_device() called without of_node, causing crash on Rockchip PMU #107
Description
Problem
The power_domain_init() creates a synthetic platform device and passes it to of_genpd_add_device():
// power_domain_init()
pdev = platform_device_alloc("hvisor-power", domain_id);
// pdev->dev.of_node == NULL <- of_node not set
ret = of_genpd_add_device(&args, &pdev->dev); // <- crashThe device has dev->of_node == NULL. of_genpd_add_device() triggers the genpd attach_dev callback, where Rockchip PMU dereferences dev->of_node.
Analysis
Crash site (Firefly SDK drivers/soc/rockchip/pm_domains.c:1168-1194)
static int rockchip_pd_attach_dev(struct generic_pm_domain *genpd,
struct device *dev)
{
struct clk *clk;
int i, error;
error = pm_clk_create(dev);
if (error)
return error;
i = 0;
// dev->of_node == NULL -> of_clk_get(NULL, i) crashes
while ((clk = of_clk_get(dev->of_node, i++)) && !IS_ERR(clk)) {
error = pm_clk_add_clk(dev, clk);
if (error) {
clk_put(clk);
pm_clk_destroy(dev);
return error;
}
}
return 0;
}Crash call chain
power_domain_init()
-> of_genpd_add_device(&args, &pdev->dev)
-> genpd->attach_dev(genpd, dev) // rockchip_pd_attach_dev
-> of_clk_get(dev->of_node, i) // NULL -> CRASH
Root cause ownership
This is not a Rockchip driver defect. rockchip_pd_attach_dev is a standard genpd callback - it assumes the device was DT-probed (so dev->of_node is non-NULL). The bug is that hvisor called of_genpd_add_device() with a synthetic device lacking of_node.
Design: DT properties as resource lists, index as Virtual ID
All three protocols read resources from the hvisor_virtio_device DT node's clocks / resets / power-domains properties:
hvisor_virtio_device {
compatible = "hvisor";
clocks = <&clk_provider 0>, <&clk_provider 1>, ...; // virtual ID 0, 1, ...
resets = <&rst_provider 0>, <&rst_provider 1>, ...; // virtual ID 0, 1, ...
power-domains = <&pd_provider 0>, <&pd_provider 1>, ...; // virtual ID 0, 1, ...
};
The N-th entry in each property equals virtual ID = N, with no id-to-index mapping table needed. The SCMI protocol operates entirely on virtual IDs - the ID carried in SCMI messages directly corresponds to this index.
Proposed Fix
Same unified design: the N-th entry in power-domains property corresponds to virtual ID = N. genpd_dev_pm_attach_by_id() parses the hvisor DT node's power-domains property directly and attaches the domain at the given index.
Replace of_genpd_add_device() with genpd_dev_pm_attach_by_id().
New API: genpd_dev_pm_attach_by_id()
Declared in include/linux/pm_domain.h. Full implementation (drivers/pmdomain/core.c:3291-3353):
/**
* genpd_dev_pm_attach_by_id - Associate a device with one of its PM domains.
* @dev: The device used to lookup the PM domain.
* @index: The index of the PM domain.
*
* Parse device's OF node to find a PM domain specifier at the provided @index.
* If such is found, creates a virtual device and attaches it to the retrieved
* pm_domain ops.
*
* Returns the created virtual device if successfully attached PM domain, NULL
* when the device don't need a PM domain, else an ERR_PTR() in case of failures.
* If a power-domain exists for the device, but cannot be found or turned on,
* then ERR_PTR(-EPROBE_DEFER) is returned.
*/
struct device *genpd_dev_pm_attach_by_id(struct device *dev,
unsigned int index)
{
struct device *virt_dev;
int num_domains;
int ret;
if (!dev->of_node)
return NULL;
/* Verify that the index is within a valid range. */
num_domains = of_count_phandle_with_args(dev->of_node, "power-domains",
"#power-domain-cells");
if (num_domains < 0 || index >= num_domains)
return NULL;
if (!genpd_bus_registered)
return ERR_PTR(-ENODEV);
/* Allocate and register device on the genpd bus. */
virt_dev = kzalloc_obj(*virt_dev);
if (!virt_dev)
return ERR_PTR(-ENOMEM);
dev_set_name(virt_dev, "genpd:%u:%s", index, dev_name(dev));
virt_dev->bus = &genpd_bus_type;
virt_dev->release = genpd_release_dev;
virt_dev->of_node = of_node_get(dev->of_node); // inherits of_node
ret = device_register(virt_dev);
if (ret) {
put_device(virt_dev);
return ERR_PTR(ret);
}
/* Attach to the PM domain at the specified index. */
ret = __genpd_dev_pm_attach(virt_dev, dev, index, num_domains, false);
if (ret < 1) {
device_unregister(virt_dev);
return ret ? ERR_PTR(ret) : NULL;
}
pm_runtime_enable(virt_dev);
genpd_queue_power_off_work(dev_to_genpd(virt_dev));
return virt_dev; // usable directly with pm_runtime_resume_and_get()
}
EXPORT_SYMBOL_GPL(genpd_dev_pm_attach_by_id);Usage in refactored code
It will be like:
static int power_domain_init(u32 domain_id) {
...
if (!ctrl_pdev) {
ctrl_pdev = platform_device_alloc("hvisor-pd", -1);
...
ctrl_pdev->dev.of_node = np; // Bind to hvisor DT node
platform_device_add(ctrl_pdev);
}
virt_dev = genpd_dev_pm_attach_by_id(&ctrl_pdev->dev, domain_id);
if (IS_ERR_OR_NULL(virt_dev))
return virt_dev ? PTR_ERR(virt_dev) : -ENODEV;
power_devices[domain_id].virt_dev = virt_dev;
power_devices[domain_id].attached = true;
return 0;
}Comparison
| Step | Legacy (of_genpd_add_device) |
Refactored (genpd_dev_pm_attach_by_id) |
|---|---|---|
| 1 | Create device, of_node = NULL | Create carrier device, of_node = hvisor node |
| 2 | Manually build of_phandle_args |
Auto-parse power-domains from of_node |
| 3 | Directly call genpd attach | Kernel creates virtual child device inheriting of_node |
| 4 | attach_dev sees NULL of_node -> crash | attach_dev sees non-NULL of_node -> safe |
| 5 | Returns int | Returns struct device * (usable for runtime PM) |
Benefits
- Complies with genpd attach_dev callback expectations (
dev->of_nodeis non-NULL) - Compatible with all platform power domain drivers
- Returned
virt_devusable directly withpm_runtime_resume_and_get()/pm_runtime_put_sync() - This API is exported via
EXPORT_SYMBOL_GPLin upstream kernels
Activity
agicy commentedon Aug 22, 2026
Closed by #110.