- 1. Pseudo-file sys
- 2. LED_classdev structure
-
3. Register/Login LED
- 3.1. LED_classdev_register function
- 3.2. LED_classdev_unregister function
-
IV. /sys interface implementation
- 4.1. Write drivers
- 4.2. Driver installation test
1. Pseudo-file sys
Pseudo File is a special file in Linux system.It does not take up physical storage space, butDynamically generated by the kernel or system to provide a specific function or information. The /sys we compiled in this article is a pseudo-file, which provides attributes and status information of kernel objects (such as devices, drivers, and buses).
In the previous articleLED driverIn the article, we provide an entry for user space to access hardware devices by creating device nodes under /dev. However, in this case, users can only operate the device through functions such as open(), read(), write(), etc. The file under /sys is a high-level abstract interface provided by the kernel for user space. Direct reading and writing files (such as cat, echo, etc.) will trigger the corresponding callback function in the kernel to complete configuration or information acquisition.
In this way, when we review our previous knowledge, we will find that when learning GPIO operations under Linux, why are there two operating methods: libgpiod and sysfs? In fact, it is always a device that operates /dev through programming, and the other is to directly use the command line The device that operates /sys.
2. LED_classdev structure
Each LED device is represented in the kernel by an LED_classdev structure. This structure contains various attributes and control functions of LED devices, such as brightness setting function, maximum brightness, etc., and is defined as follows (only common parts have been sorted out):
struct led_classdev
{
const char *name; // Device name
enum led_brightness brightness; // LED default brightness
enum led_brightness max_brightness; // maximum brightness of LED
// Function pointer used to set the LED brightness, cannot be sleepy
void (*brightness_set)(struct led_classdev *led_cdev, enum led_brightness brightness);
//Function pointer used to set LED brightness can sleep
int (*brightness_set_blocking)(struct led_classdev *led_cdev, enum led_brightness brightness);
struct device *dev;
const char *default_trigger;
}
(1) name: indicates the device name;
(2) Brightness and max_brightness: These two members are variables of the enum type enum LED_brightness, one represents the initial brightness of the LED and the other represents the maximum brightness of the LED. This enum type defines the brightness level of the LED. Let’s take a look at this Enumeration type:
enum led_brightness {
LED_OFF = 0,
LED_ON = 1,
LED_HALF = 127,
LED_FULL = 255,
};
(3) default_trigger: This property sets the default action of the LED light, such as:
backlight: LED lights as backlight.
default-on: LED light is on
heartbeat: The LED light serves as a heartbeat indicator, which can be used as a system operation indicator.
ide-disk: LED light serves as the hard disk activity indicator.
timer: LED light flashes periodically, driven by a timer, the flashing frequency can be modified
(4) brightness_set: bind LED brightness setting function (not sleepy);
(5) brightness_set_blocking: bind LED brightness setting function (can sleep);
3. Register/Login LED
3.1. LED_classdev_register function
This function registers an LED device into the LED subsystem so that it can operate through a unified interface provided by the kernel. Initialize some default attributes for the LED device, such as brightness, maximum brightness (max_brightness), trigger, etc. At the same time, create a corresponding device file for the registered LED device in the /sys/class/leds/ directory, through which users can operate the LED.
int led_classdev_register(struct device *parent, struct led_classdev *led_cdev);
//parent: Pointer to the parent device. Usually NULL, indicating that the LED device does not have a parent device. If a parent device is provided, the LED device is associated with the parent device.
//led_cdev: A pointer to the led_classdev structure, which contains relevant information about the LED device, such as name, brightness setting function, maximum brightness, etc.
//Return 0 when successful. Returns a negative error code when it fails.
3.2. LED_classdev_unregister function
This function is used to log out a previously registered LED device from the LED subsystem. Delete the corresponding device files in the /sys/class/leds/ directory. Free up the kernel resources associated with this LED device.
void led_classdev_unregister(struct led_classdev *led_cdev);
//led_cdev: A pointer to the led_classdev structure previously registered through led_classdev_register.
Function
IV. /sys interface implementation
4.1. Write drivers
In the previous article, we registered the character device,/dev
Create device nodes under this method, thereby enabling user space to operate hardware through programming. Next here will be passed/sys
The interface enables users to operate hardware devices directly through the command line. The code is as follows (refer to the previous article of the device tree file, no modifications are made here):
//vim
#include <linux/>
#include <linux/>
#include <linux/>
#include <linux/>
#include <linux/>
#include <linux/>
#include <linux/>
#include <linux/>
#include <linux/>
#include <linux/>
#include <linux/>
#include <linux/>
#include <linux/platform_device.h>
#include <linux/gpio/>
#include <linux/>
struct leds_desc
{
struct led_classdev dev; /* dev for led_classdev_register() */
struct gpio_desc *gpio; /* gpio instance for this led */
char name[8]; /* led name in /sys/class/leds */
};
struct led_priv
{
int nleds; /* number of leds */
struct leds_desc *leds; /* leds array */
};
struct led_priv *priv;
static void led_brightness_set(struct led_classdev *dev, enum led_brightness brightness)
{
struct leds_desc *led = container_of(dev, struct leds_desc, dev);
gpiod_set_value_cansleep(led->gpio, brightness?1:0 );
}
static int led_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct leds_desc *led;
int ret, i;
/* allocate memory for private data structure */
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
/* parser the number of LEDs from the device tree */
priv->nleds = gpiod_count(dev, NULL);
if ( priv->nleds < 1) {
dev_err(dev, "Failed to read leds gpio from device tree\n");
return -EINVAL;
}
dev_info(dev, "led driver probe for %d leds from device tree\n", priv->nleds);
/* allocate memory for all the leds */
priv->leds = devm_kzalloc(dev, priv->nleds*sizeof(*priv->leds), GFP_KERNEL);
if( !priv->leds )
return -ENOMEM;
/* parser and request GPIO pins from the device tree */
for (i = 0; i < priv->nleds; i++) {
priv->leds[i].gpio = devm_gpiod_get_index(dev, NULL, i, GPIOD_ASIS);
if (IS_ERR(priv->leds[i].gpio))
return PTR_ERR(priv->leds[i].gpio);
/* set GPIO as output mode and default off */
gpiod_direction_output(priv->leds[i].gpio, 0);
}
/* create sysfs file for each led */
for (i = 0; i < priv->nleds; i++) {
led = priv->leds+i;
snprintf(led->name, sizeof(led->name), "led%d", i);
led-> = led->name;
led->dev.brightness_set = led_brightness_set;
ret = led_classdev_register(dev, &led->dev);
if (ret) {
dev_err(dev, "Failed to register LED[%d]\n", i);
goto failed_destroy;
}
}
platform_set_drvdata(pdev, priv);
return 0;
failed_destroy:
for (--i; i >= 0; i--)
led_classdev_unregister(&priv->leds[i].dev);
return ret;
}
static int led_remove(struct platform_device *pdev)
{
struct led_priv *priv = platform_get_drvdata(pdev);
int i;
for (i = 0; i < priv->nleds; i++) {
led_classdev_unregister(&priv->leds[i].dev);
}
dev_info(&pdev->dev, "led driver removed.\n");
return 0;
}
static const struct of_device_id led_of_match[] = {
{ .compatible = "rgb,leds", },
{ /* sentinel */ },
};
MODULE_DEVICE_TABLE(of, led_of_match);
static struct platform_driver led_driver = {
.probe = led_probe,
.remove = led_remove,
.driver = {
.name = "leds",
.of_match_table = led_of_match,
},
};
module_platform_driver(led_driver);
MODULE_LICENSE("GPL");
Makefile
The file is as follows:
ARCH ?= arm
CROSS_COMPILE ?= /opt/gcc-aarch32-10.3-2021.07/bin/arm-none-linux-gnueabihf-
KERNAL_DIR ?= ~/igkboard-imx6ull/bsp/kernel/linux-imx/
PWD :=$(shell pwd)
obj-m +=
modules:
$(MAKE) ARCH=${ARCH} CROSS_COMPILE=${CROSS_COMPILE} -C $(KERNAL_DIR) M=$(PWD) modules
@make clear
clear:
@rm -f *.o *.cmd *.mod *.
@rm -rf *~ core .depend .tmp_versions -f
@rm -f .* .*. .*.
@rm -f *.unsigned
clean:
@rm -f *.ko
make
4.2. Driver installation test
Will compileDownload to the development board and install:
insmod
In the new driver, we do not register a character device, so no new device file is generated under /dev/, but our three Led device files appear under the /sys/class/leds path.
ls /dev/led*
ls: cannot access '/dev/led*': No such file or directory
ls /sys/class/leds/
led0 led1 led2 mmc0:: mmc1::
ls /sys/class/leds/led1/
brightness device max_brightness power subsystem trigger uevent
Next, we can use the echo command to control the corresponding LED to turn on and off.
echo 1 > /sys/class/leds/led1/brightness
//Effect: Green light is on
echo 0 > /sys/class/leds/led1/brightness
//Effect: Green light goes out
From the above process of writing Led drivers, we learned that to implement the driver supply application space of a device, there are many different implementation methods. If we want easy programmatic control, we can use regular character device drivers to implement it by calling the ioctl() system call; and if we want to implement it directly in the command line or shell script, we can use the /sys/class pseudo-file system To achieve it.