Skip to content

virtio-scmi: power domain of_genpd_add_device() called without of_node, causing crash on Rockchip PMU #107

Closed

Description

@agicy

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);  // <- crash

The 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_node is non-NULL)
  • Compatible with all platform power domain drivers
  • Returned virt_dev usable directly with pm_runtime_resume_and_get() / pm_runtime_put_sync()
  • This API is exported via EXPORT_SYMBOL_GPL in upstream kernels

Activity

  1. self-assigned this
    on Jul 14, 2026
  2. added
    bugSomething isn't working
    enhancementNew feature or request
    on Jul 14, 2026
  3. agicy commented on Aug 22, 2026

    @agicy
    ContributorAuthor

    Closed by #110.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

bugSomething isn't workingenhancementNew feature or request

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions