Using the parent child relation in a kernel module
Sometimes we need access to a device inside a kernel driver which isn't exposed directly. Based on an LED I would like to show an example on how we can use the parent child relationship of the device tree and the driver to access a class device.
To get a better understanding let's check the following device tree entry:
/ {
leds {
compatible = "gpio-leds";
pwr_led: pwr {
label = "pwr";
gpios = <&expgpio 2 GPIO_ACTIVE_LOW>;
};
};
led-control {
compatible = "led-control";
led = <&pwr_led>;
};
};
From led-control we want to access pwr_led. However, there are no functions available where we can get the LED directly. If it would be a gpio we could use of_get_gpio but this doesn't work with LEDs.
Please note that this is only an example to show how we can use parent child relations in a kernel driver. If we want access to an LED we should better implement a led-trigger driver. By doing that we will directly receive a led device which we can use. As an example have a look at drivers/leds/trigger/ledtrig-timer.c.
The gpio-leds driver
To understand how we can access the power (pwr) LED we need to get an overview of the driver that provides it. Let's figure out which driver we have to analyze.
We can see from the device tree that leds requests a driver which is compatible to "gpio-leds". We can find that leds-gpio.c is the compatible driver provided by Linux. This driver simplified for this article looks as follows:
To access the LED we need a led_classdev device which we can then use for calls to e.g. led_blink_set. The problem is that from the device tree node pwr we can't receive the class device directly. However, we can get a platform device from a device tree node by using of_find_device_by_node.
From the class diagram we see that every device node has stored its parent as well as its children. Furthermore, a device stores all its children. In the next chapter we will discuss how we can use this relations to get access to the LED class device.
Getting the leds class device
In this chapter we will see how we can gain access to a LED by using the child parent relation from the device tree and then use the parent child relation to get the class device to access the LED. We implement a platform driver and access the device tree property in the probe function of this driver. The whole driver can be found here: https://github.com/embear-engineering/sample-kernel-modules/tree/master/led-control
We start with the phandle to the pwr_led which we can get with of_parse_phandle:
struct device_node *led_node;
led_node = of_parse_phandle(np, "led", 0);
if (!led_node) {
pr_err("No led node found\n");
return -EINVAL;
}
This gives us the LED devcie tree node. However, there is no way to directly get the LED class device from the node. Therefore, we use the child parent relation of the nodes to gain access to the leds node:
struct device_node *leds_node;
leds_node = of_get_parent(led_node);
if (!leds_node) {
of_node_put(led_node);
pr_err("Can not get parent node\n");
return -EINVAL;
}
Now we want to get the actual LED platform device which provides access to the gpio leds:
struct platform_device *led_pdev;
led_pdev = of_find_device_by_node(leds_node);
if (!led_pdev) {
err = -EINVAL;
pr_err("Cannot convert node to platform device\n");
goto error;
}
To get access to the LED device we now use the parent child relation to search a match for our power LED. We match the phandles of each child with the phandle from the device tree until we find the actual LED device.
static int match_led(struct device *dev, void *data)
{
return dev->of_node->phandle == ((struct device_node*)data)->phandle;
}
struct device *led_dev;
led_dev = device_find_child(&led_pdev->dev, led_node, match_led);
if (!led_dev) {
err = -EINVAL;
pr_err("Cannot find led device\n");
goto error;
}
In a last step we access the driver_data of the LED device to receive a led_classdev. This works because the LED class device driver stores a pointer to the led_classdev in the driver_data of the device. We find this in led-class.c the fourth parameter of device_create_with_groups is driver_data and points to the led_classdev. Therefore, we can do the following:
data->led_cdev = (struct led_classdev*) led_dev->driver_data;
if (!data->led_cdev) {
err = -EINVAL;
pr_err("Cannot get led class device\n");
goto error;
}
We can now pass led_cdev to the functions defined in leds.h which allow us to control the LED. Here a simple example to let the LED blink:
unsigned long time_on = 500;
unsigned long time_off = 500;
led_blink_set(pdata->led_cdev, &time_on, &time_off);
The driver
The sample driver can be found on Github. I tested it on a Raspberry Pi but the same mechanism was also tested on a Sitara AM335x. The only thing required is the device tree node:
led-control {
compatible = "led-control";
led = <&pwr_led>;
};
The led property has to be a phandle to a led node defined inside a led platform driver (e.g. gpio-leds).
The driver allows you to change the device_state with an entry under /sys/devices/platform/led-control/device_state. You can write "running", "booting" or "shutdown" to change the blinking mode:
Compile the driver
To cross compile the driver please check the article "Cross compiling a kernel module".
Final thoughts
The parent child relation of kernel drivers is a powerful mechanism to access devices which might not be exposed in the first place. However, we should use them carefully because often there are better alternatives (e.g. here the led-trigger driver). It still can be helpful when debugging or prototype something for the first time.