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.

Linux 6.0 released, Bootlin contributions

Linux 6.0 has been released two weeks ago, and Linux 6.1-rc1 is already out of the door, but we didn’t get the chance to look at the contributions made by Bootlin to the Linux 6.0 release. Before we do that, let’s provide our usual must-read articles on Linux 6.0: the Linux 6.0 merge window part 1 and Linux 6.0 merge window part 2 LWN.net articles and the KernelNewbies.org article.

On Bootlin side, our significant contributions to this release have been:

  • Clément Léger contributed a new driver for the Ethernet switch found in the Renesas RZ/N1 processor, as well as a PCS driver for the MII converter of the same processor. Obviously, this came with the related Device Tree bindings and Device Tree changes, but also with a few small changes in the DSA subsystem.
  • Hervé Codina enabled support for the PCIe controller found in the same Renesas RZ/N1 processor, which in fact does not allow to use PCIe devices, but USB devices: this PCIe controller is only used to connect to an internal USB controller in the chip, which therefore allows to use USB devices.
  • Köry Maincent extended the existing mpc4922 DAC IIO driver to also support the mpc4921 variant, which has only one output channel instead of two.
  • Luca Ceresoli contributed several improvements to the I2C subsystem documentation.
  • Paul Kocialkowski contributed a new DRM driver for the logiCVC-ML display controller IP
  • Paul Kocialkowski contributed two new V4L drivers for the MIPI CSI-2 camera interfaces available in the Allwinner A31 family of processors (sun6i) and the Allwinner A83T family of processors (sun8i).

Here is the full details of our contributions, commit by commit:

A journey in the RTC subsystem

As part of a team effort to improve the upstream Linux kernel support for the Renesas RZ/N1 ARM processor, we had to write from scratch a new RTC driver for this SoC. The RTC subsystem API is rather straightforward but, as most kernel subsystems, the documentation about it is rather sparse. So what are the steps to write a basic RTC driver? Here are some pointers.

The registration

The core expects drivers to allocate, initialize and then register a struct rtc_device with the device managed helpers: devm_rtc_allocate_device() and devm_rtc_register_device(). Between these two function calls, one will be required to provide at least a set of struct rtc_class_ops which contains the various callbacks used to access the device from the core, as well as setting a few information about the device.

The kind of information expected is the support for various features (rtcdev->features bitmap) as well as the maximum continuous time range supported by your RTC. If you do not know the actual date after which your device stops being reliable, you can use the rtc-range test tool from rtc-tools, available at https://git.kernel.org/pub/scm/linux/kernel/git/abelloni/rtc-tools.git (also available as a Buildroot package). It will check the consistency of your driver against a number of common known-to-be-failing situations.

Time handling

The most basic operations to provide are ->read_time() and ->set_time(). Both functions should play with a struct rtc_time which describes time and date with members for the year, month, day of the month, hours (in 24-hour mode), minutes and seconds. The week day member is ignored by userspace and is not expected to be set properly, unless it is actively used by the RTC, for example to set alarms. There are then three popular ways of storing time in the RTC world:

  1. either using the binary values of each of these fields
  2. or using a Binary Coded Decimal (BCD) version of these fields
  3. or, finally, by storing a timestamp in seconds since the epoch

In BCD, each decimal digit is encoded using four bits, eg. the number 12 could either be coded by 0x0C in hexadecimal, or 0x12 in BCD, which is easier to read with a human eye.

The three representations are absolutely equivalent and you are free to convert the time from one system to another when needed:

  • #1 <-> #2 conversions are done with bcd2bin() and bin2bcd() (from linux/bcd.h)
  • #1 <-> #3 conversions are done with rtc_time64_to_tm() and rtc_tm_to_time64() (from linux/rtc.h)

While debugging, it is likely that you will end up dumping these time structures. Note that struct rtc_time is aligned on struct tm, this means that the year field is the number of years since 1900 and the month field is the number of months since January, in the range 0 to 11. Anyway, dumping these fields manually is a loss of time, it is advised instead to use the dedicated RTC printk specifiers which will handle the conversion for you: %ptR for a struct rtc_time, %ptT for a time64_t.

Of course, when reading the actual time from multiple registers on the device and filling those fields, be aware that you should handle possible wrapping situations. Either the device has an internal latching mechanism for that (eg. the front-end of the registers that you must read are all frozen upon a specific action) or you need to verify this manually by, for instance, monitoring the seconds register and try another read if it changed between the beginning and the end of the retrieval.

If your device continuous time range ended before 2000 you may want to shift the default hardware range further by providing the start-year device tree property. The core will then shift the Epoch further for you.

Finally, once done, you can verify your implementation by playing with the rtc test tool (also from rtc-tools).

Supporting alarms

One common RTC feature is the ability to trigger alarms at specific times. Of course it’s even better if your RTC can wake-up the system.

If the device or the way it is integrated doesn’t support alarms, this should be advertised at registration time by clearing the relevant bit (RTC_FEATURE_ALARM, RTC_FEATURE_UPDATE_INTERRUPT). In the other situations, it is relevant to indicate whether the RTC has a second, 2-seconds or minute resolution by setting the appropriate flag (RTC_FEATURE_ALARM_RES_2S, RTC_FEATURE_ALARM_RES_MINUTE). Mind when testing that querying an alarm time below this resolution will return a -ETIME error.

When implementing the ->read_alarm(), ->set_alarm() and ->alarm_irq_enable() hooks, be aware that the update and periodic alarms are now implemented in the core, using HR timers rather than with the RTC so you should focus on the regular alarm. The read/set hooks naturally allow to read and change the alarm settings. A struct rtc_wkalrm *alrm is passed as parameter, alrm->time is the struct rtc_time and alrm->enabled the state of the alarm (which must be set in ->set_alarm()). The third hook is an asynchronous way to enable/disable the alarm IRQ.

The interrupt handler for the alarm is required to call rtc_update_irq() to signal the core that an alarm happened, providing the RTC device, the number of alarms reported (usually one), and the RTC_IRQF flag OR’ed with the relevant alarm flag (likely, RTC_AF for the main alarm).

Oscillator offset compensation

RTC counters rely on very precise clock sources to deliver accurate times. To handle the situation where the source is not matching the expected precision, which is the case with most cheap oscillators on the market, some RTCs have a mechanism allowing to compensate for the frequency variation by incrementing or skipping the RTC counters at a regular interval in order to get closer to the reality.

The RTC subsystem offers a set of callbacks, ->read_offset() and a ->set_offset(), where a signed offset is passed in ppb (parts per billion).

As an example, if an oscillator is below its targeted frequency of 32768 Hz and is measured to run at 32767.7 Hz, we need to offset the counter by 1 - (32767.7/32768) = 9155 ppb. If the RTC is capable of offsetting the main counter once every 20s it means that every 20s, this counter (which gets decremented at the frequency of the oscillator to produce the “seconds”) will start at a different value than 32768. Adding 1 to this counter every 20s would basically mean earning 1 / (32768 * 20) = 1526 ppb. Our target being 9155 ppb, we must offset the counter by 9155 / 1526 = 6 every 20s to get a compensated rate of 32767.7 + (6 / 20) = 32768 Hz.

Upstreaming status of the RZ/N1 RTC driver

The RZ/N1 RTC driver has all the features listed above and made its way into the v5.18 Linux kernel release. Hopefully this little reference sheet will encourage others to finalize and send new RTC drivers upstream!

Bootlin at Linux Plumbers conference 2022

Next week, almost the entire Bootlin team will be at the Embedded Linux Conference Europe in Dublin, see our previous blog post on this topic. We will give four talks at this event, on a variety of Linux kernel and embedded Linux topics.

During the same week, also in Dublin albeit in a different location, will take place the Linux Plumbers conference. Bootlin engineer Miquèl Raynal will give a talk at Linux Plumbers, as part of the IoTs a 4-Letter Word micro-conference. Miquèl’s talk will discuss Linux IEEE 802.15.4 MLME improvements, as Miquèl has been working for several months on bringing improvements to the 802.15.4 stack in the Linux kernel.

Linux 5.19 released, Bootlin contributions inside

Linux 5.19 has been released yesterday. We recommend the usual resources of LWN (part 1 and part 2) as well as KernelNewbies to get some high-level overview of the major additions. CNX-Software also has an article focused on the ARM/RISC-V/MIPS improvements.

At Bootlin, we contributed 68 patches to this release, the main highlights being:

  • Clément Léger contributed patches for the Microchip SAMA5 platform to support suspend operation while running in non-secure mode, with OP-TEE handling the necessary PCSI calls. This is related to our work to port OP-TEE on Microchip SAMA5D2, which we have covered in several blog posts before.
  • Hervé Codina contributed device Tree updates to enable the PCI controller of the Renesas RZ/N1 platform, which allows to access the USB host controller that sits on an internal PCI bus. Some driver updates for the PCI driver are needed, and they will land in 5.206.0 kernel.
  • Miquèl Raynal contributed several improvements to the IIO subsystem, following his work on several IIO drivers and his related blog post. These improvements either touch the core IIO, or fix some incorrect API use in IIO drivers.
  • Miquèl Raynal contributed a new driver for the Renesas RZ/N1 DMA router (in drivers/dmaengine) as well as a new driver for the Renesas RZ/N1 Real Time Clock (in drivers/rtc). In addition, Miquèl modified the 8250 UART controller driver to be able to use the DMA capabilities available on the RZ/N1 processor.
  • Miquèl Raynal also contributed a number of improvements to the IEEE 802.15.4 stack in the Linux kernel.
  • Paul Kocialkowski contributed support for MIPI CSI-2 in the Allwinner phy-sun6i-mipi-dphy driver.
  • Paul Kocialkowski and Luca Ceresoli contributed a few misc fixes, touching the SPI core and SPI Rockchip driver and the dmaengine documentation.

The complete details of our contributions are: