Device Tree phandle: the C code point of view

Introduction

In this blog post, we’ll discuss the phandle properties used in Device Tree. These properties are used to describe a relationship between components described in the Device Tree. Many blog posts describe this property from the Device Tree source point of view (you can for example have a look at https://elinux.org/Device_Tree_Mysteries#Phandle for details related to Device Tree source). In this blog post, we want to take a different approach, and discuss how to handle this type of property from the Linux kernel C code point of view.

Simple phandle

To illustrate our story, we’ll assume we have two platform devices foo and bar. bar offers the possibility to have some phandles pointing to it. foo, on the other side, needs to use bar and so it has a phandle pointing to bar in its device tree node. The Device Tree therefore looks like this:

bar: bar {
        compatible = "vendor,bar";
        ...
};

foo {
        bar = <&bar>;
};

Now, in order for this relationship to be managed from a driver perspective, we’ll create a pair of functions in bar

  • struct bar *bar_get_byphandle(struct device_node *np, const char *phandle_name); to get the bar reference from this phandle. This function returns the pointer to the struct bar structure based on the node np where the phandle is present and the phandle property name phandle_name.
    From the foo code, this can be used like this: bar = bar_get_byphandle(pdev->dev.of_node, "bar");
  • void bar_put(struct bar *bar); to release the resource, with bar returned by the bar_get_byphandle() function.

Let’s look at the bar_get_byphandle() code and see how we can retrieve the struct bar from the phandle.

The first step is to parse the phandle property and retrieve the device_node the phandle refers to. In other words, in our case, it will retrieve the device_node related to the bar node. The basic API function to do so is of_parse_phandle().

The second step is to be sure that the device_node retrieved is really a bar node. Indeed, the phandle present in the foo node can be wrong and can refer to something that is not a bar node. For example, bar = <&clk_blabla> is a valid phandle but probably referencing a clock node and not a bar node.

To check that from a device_node we can check the compatible string against the compatible table defined in the bar driver. The compatible string is, by definition, the identifier of the driver. So it is a really good candidate to check that “bar” is really bar. The call to of_match_node() is used for this check.

Once we are sure that the bar device_node is really the bar node, a call to of_find_device_by_node retrieves the struct platform_device attached to the node.

Finally, we can get the driver data from the struct platform_device using platform_get_drvdata(). Of course, bar needs to call platform_set_drvdata() in its probe() function for this to work.

Just a note about the probing sequence. The foo probe function can be called before the bar probe function and so bar_get_byphandle() can be called from foo before the call of platform_set_drvdata() from bar. In this case, platform_get_drvdata() can return NULL indicating the wrong probing sequence. We have just to return the well known -EPROBE_DEFER error code in this case.

The bar_put() function is pretty simple and release the resource as follow:

void bar_put(struct bar *bar)
{
	put_device(bar->dev);
}

Putting all together leads to the following piece of code:

struct bar {
	struct device *dev;
	...
};

static int bar_probe(struct platform_device *pdev)
{
	struct bar *bar;

	bar = devm_kzalloc(&pdev->dev, sizeof(*bar), GFP_KERNEL);
	if (!bar)
		return -ENOMEM;

	bar->dev = &pdev->dev;

	...

	platform_set_drvdata(pdev, bar);
	return 0;
}

static const struct of_device_id bar_id_table[] = {
	{ .compatible = "vendor,bar" },
	{} /* sentinel */
};
MODULE_DEVICE_TABLE(of, bar_id_table);

static struct platform_driver bar_driver = {
	.driver = {
		.name = "bar",
		.of_match_table = of_match_ptr(bar_id_table),
	},
	.probe = bar_probe,
	...
};
module_platform_driver(bar_driver);

struct bar *bar_get_byphandle(struct device_node *np, const char *phandle_name)
{
	struct platform_device *pdev;
	struct device_node *bar_np;
	struct bar *bar;

	bar_np = of_parse_phandle(np, phandle_name, 0);
	if (!bar_np)
		return ERR_PTR(-EINVAL);

	if (!of_match_node(bar_driver.driver.of_match_table, bar_np)) {
		of_node_put(bar_np);
		return ERR_PTR(-EINVAL);
	}

	pdev = of_find_device_by_node(bar_np);
	of_node_put(bar_np);
	if (!pdev)
		return ERR_PTR(-ENODEV);

	bar = platform_get_drvdata(pdev);
	if (!bar) {
		platform_device_put(pdev);
		return ERR_PTR(-EPROBE_DEFER);
	}

	return bar;
}

void bar_put(struct bar *bar)
{
	put_device(bar->dev);
}

Automatic release

As mentioned previously, the struct bar resource obtained from the bar_get_byphandle() needs to be released using a call to bar_put(). In several kernel APIs, devm_* functions can be used to have a resource automatically released when the associated device is released.
We can improve our bar API and introduce:

struct bar *devm_bar_get_byphandle(struct device *dev, struct device_node *np, const char *phandle_name)

With this function, the struct bar will be automatically released when the given device dev is destroyed and so there is no more need to call bar_put().

This function simply looks like this (with its private devm_bar_release helper):

static void devm_bar_release(struct device *dev, void *res)
{
	struct bar **bar = res;

	bar_put(*bar);
}

struct bar *devm_bar_get_byphandle(struct device *dev, struct device_node *np, const char *phandle_name)
{
	struct bar *bar;
	struct bar **dr;

	dr = devres_alloc(devm_bar_release, sizeof(*dr), GFP_KERNEL);
	if (!dr)
		return ERR_PTR(-ENOMEM);

	bar = bar_get_byphandle(np, phandle_name);
	if (!IS_ERR(bar)) {
		*dr = bar;
		devres_add(dev, dr);
	} else {
		devres_free(dr);
	}

	return bar;
}

In the foo code the call to get the reference becomes:

bar = bar_get_byphandle(pdev->dev, pdev->dev.of_node, "bar");

phandle with arguments

Sometimes, a phandle needs to have an argument. We can use this argument as an index in a table for instance. In this case, the Device Tree looks like this:

bar: bar {
	#table-cells = <1>;
	...
};

foo {
	bar = <&bar 2>
};

The property #table-cells = <1> will not be used in the C code but needs to be set in the Device Tree to tell device tree checkers that one argument is required. We could also use the property in the C code.
The argument (2 in our case) is set in the phandle itself bar = <&bar 2>.

The bar_get_byphandle() is slightly different and looks like the following:

struct bar_item *bar_item_get_byphandle(struct device_node *np, const char *phandle_name)
{
	struct of_phandle_args out_args;
	struct platform_device *pdev;
	struct bar_item *bar_item;
	struct bar *bar;
	int ret;

	ret = of_parse_phandle_with_fixed_args(np, phandle_name, 1,
					       0, &out_args);
	if (ret < 0)
		return ERR_PTR(ret);

	if (!of_match_node(bar_driver.driver.of_match_table, bar_np)) {
		of_node_put(out_args.np);
		return ERR_PTR(-EINVAL);
	}

	pdev = of_find_device_by_node(out_args.np);
	of_node_put(out_args.np);
	if (!pdev)
		return ERR_PTR(-ENODEV);

	bar = platform_get_drvdata(pdev);
	if (!bar) {
		platform_device_put(pdev);
		return ERR_PTR(-EPROBE_DEFER);
	}

	if (out_args.args_count != 1) {
		platform_device_put(pdev);
		return ERR_PTR(-EINVAL);
	}

	if (out_args.args[0] > NB_MAX_ITEM_IN_TABLE) {
		platform_device_put(pdev);
		return ERR_PTR(-EINVAL);
	}

	bar_item = bar->table[out_args.args[0]];
	if (!bar_item) {
		platform_device_put(pdev);
		return ERR_PTR(-ENOENT);
	}

	return bar_item;
}

Conclusion

This post showed you the Linux kernel C internals related to a phandle and how it can be simple to navigate through the relationships between nodes described in your Device Tree. Most of the time, these links are done by the core infrastructure (clocks, GPIOs, interrupts, …) but some drivers can provide this feature and offer an API to be used by other drivers keeping the correct instance wiring in the Device Tree.

Leave a Reply