找回密码
 立即注册
首页 业界区 安全 Linux驱动---/sys接口

Linux驱动---/sys接口

圄旧剖 2025-6-11 13:34:09
目录

  • 一、伪文件 sys
  • 二、led_classdev结构体
  • 三、注册/注销LED

    • 3.1、led_classdev_register 函数
    • 3.2、led_classdev_unregister 函数

  • 四、/sys接口实现

    • 4.1、编写驱动程序
    • 4.2、驱动安装测试


一、伪文件 sys

伪文件(Pseudo File) 是 Linux 系统中一种特殊的文件,它不占用物理存储空间,而是由内核或系统动态生成,用于提供某种特定的功能或信息。我们本篇文章所整理的 /sys 便是伪文件,它提供了内核对象(如设备、驱动、总线)的属性和状态信息。

在上篇LED驱动文章中,我们通过在 /dev 下创建设备节点,提供了用户空间访问硬件设备的入口。不过这种情况下,用户只能通过open()、read()、write()等函数编程来操作设备。而 /sys 下的文件是内核为用户空间提供的高级抽象接口,直接读写文件(如cat、echo等)会触发内核中对应的回调函数,完成配置或信息获取。

这样,当我们回顾以前的知识就会发现,在学习Linux下的GPIO操作时为什么有 libgpiod 和 sysfs 两种操作方式,其实就是一直是通过编程操作 /dev 的设备,另一种是直接通过命令行操作 /sys 的设备。
二、led_classdev结构体

每个 LED 设备在内核中通过一个 led_classdev 结构体来表示。这个结构体包含了 LED 设备的各种属性和控制函数,例如亮度设置函数、最大亮度等,定义如下(只整理了常见部分):
  1. struct led_classdev
  2. {
  3.           const char                *name;                       // 设备名字
  4.         enum led_brightness         brightness;             // LED 默认亮度
  5.         enum led_brightness         max_brightness;         // LED 的最大亮度
  6.     // 用于设置 LED 亮度的函数指针,不可休眠
  7.     void    (*brightness_set)(struct led_classdev *led_cdev, enum led_brightness brightness);
  8.     //用于设置 LED 亮度的函数指针,可以休眠
  9.     int     (*brightness_set_blocking)(struct led_classdev *led_cdev, enum led_brightness brightness);
  10.   
  11.     struct device            *dev;
  12.     const char                    *default_trigger;
  13. }
复制代码
(1)name:表示设备名字;

(2)brightness和max_brightness:这两个成员都是枚举类型 enum led_brightness 的变量,一个表示 LED 的初始化亮度,一个表示 LED 的最大亮度,这个枚举 类型定义了 LED 的亮度等级,来看看这个枚举类型:
  1. enum led_brightness {
  2.         LED_OFF                = 0,
  3.         LED_ON                = 1,
  4.         LED_HALF        = 127,
  5.         LED_FULL        = 255,
  6. };
复制代码
(3)default_trigger:该属性设置LED灯的默认动作,比如:
  1. backlight:LED灯作为背光。
  2. default-on:LED灯打开
  3. heartbeat:LED灯作为心跳指示灯,可以作为系统运行提示灯。
  4. ide-disk:LED灯作为硬盘活动指示灯。
  5. timer:LED灯周期性闪烁,由定时器驱动,闪烁频率可以修改
复制代码
(4)brightness_set:绑定 LED 亮度设置函数(不可休眠);

(5)brightness_set_blocking:绑定 LED 亮度设置函数(可以休眠);
三、注册/注销LED

3.1、led_classdev_register 函数

该函数将一个 LED 设备注册到 LED 子系统中,使其可以通过内核提供的统一接口进行操作。为 LED 设备初始化一些默认的属性,如亮度(brightness)、最大亮度(max_brightness)、触发器(trigger)等。同时在 /sys/class/leds/ 目录下为注册的 LED 设备创建一个对应的设备文件,用户可以通过该文件对 LED 进行操作。
  1. int led_classdev_register(struct device *parent, struct led_classdev *led_cdev);
  2. //parent:指向父设备的指针。通常为 NULL,表示该 LED 设备没有父设备。如果提供了父设备,则 LED 设备会与父设备关联。
  3. //led_cdev:指向 led_classdev 结构体的指针,该结构体包含了 LED 设备的相关信息,如名称、亮度设置函数、最大亮度等。
  4. //成功时返回 0。失败时返回负的错误码.
复制代码
3.2、led_classdev_unregister 函数

该函数用于从 LED 子系统中注销一个之前注册的 LED 设备。删除 /sys/class/leds/ 目录下对应的设备文件。释放与该 LED 设备相关的内核资源。
  1. void led_classdev_unregister(struct led_classdev *led_cdev);
  2. //led_cdev:指向之前通过 led_classdev_register 注册的 led_classdev 结构体的指针。
复制代码
四、/sys接口实现

4.1、编写驱动程序

在上一篇文章中,我们通过注册字符设备,在/dev下创建设备节点,从而实现了用户空间通过编程对硬件进行操作。接下来在这里将通过/sys接口实现用户通过命令行直接操作硬件设备。代码如下(设备树文件参考上篇文章,这里没有做修改):
  1. //vim ldv2.c
  2. #include <linux/module.h>
  3. #include <linux/init.h>
  4. #include <linux/kernel.h>   
  5. #include <linux/fs.h>      
  6. #include <linux/errno.h>   
  7. #include <linux/types.h>   
  8. #include <linux/cdev.h>   
  9. #include <linux/slab.h>     
  10. #include <linux/version.h>  
  11. #include <linux/uaccess.h>  
  12. #include <linux/device.h>
  13. #include <linux/of.h>
  14. #include <linux/platform_device.h>
  15. #include <linux/gpio/consumer.h>
  16. #include <linux/leds.h>
  17. struct leds_desc
  18. {
  19.     struct led_classdev dev;     /* dev for led_classdev_register() */
  20.     struct gpio_desc   *gpio;    /* gpio instance for this led */
  21.     char                name[8]; /* led name in /sys/class/leds */
  22. };
  23. struct led_priv
  24. {
  25.     int                 nleds; /* number of leds */
  26.     struct leds_desc   *leds;  /* leds array */
  27. };
  28. struct led_priv *priv;
  29. static void led_brightness_set(struct led_classdev *dev, enum led_brightness brightness)
  30. {
  31.     struct leds_desc *led = container_of(dev, struct leds_desc, dev);
  32.     gpiod_set_value_cansleep(led->gpio, brightness?1:0 );
  33. }
  34. static int led_probe(struct platform_device *pdev)
  35. {
  36.     struct device *dev = &pdev->dev;
  37.     struct leds_desc *led;
  38.     int ret, i;
  39.     /* allocate memory for private data structure */
  40.     priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
  41.     if (!priv)
  42.         return -ENOMEM;
  43.     /* parser the number of LEDs from the device tree */
  44.     priv->nleds = gpiod_count(dev, NULL);
  45.     if ( priv->nleds < 1) {
  46.         dev_err(dev, "Failed to read leds gpio from device tree\n");
  47.         return -EINVAL;
  48.     }
  49.     dev_info(dev, "led driver probe for %d leds from device tree\n", priv->nleds);
  50.     /* allocate memory for all the leds */
  51.     priv->leds = devm_kzalloc(dev, priv->nleds*sizeof(*priv->leds), GFP_KERNEL);
  52.     if( !priv->leds )
  53.         return -ENOMEM;
  54.     /* parser and request GPIO pins from the device tree */
  55.     for (i = 0; i < priv->nleds; i++) {
  56.         priv->leds[i].gpio = devm_gpiod_get_index(dev, NULL, i, GPIOD_ASIS);
  57.         if (IS_ERR(priv->leds[i].gpio))
  58.             return PTR_ERR(priv->leds[i].gpio);
  59.         /* set GPIO as output mode and default off */
  60.         gpiod_direction_output(priv->leds[i].gpio, 0);
  61.     }
  62.     /* create sysfs file for each led */
  63.     for (i = 0; i < priv->nleds; i++) {
  64.         led = priv->leds+i;
  65.         snprintf(led->name, sizeof(led->name), "led%d", i);
  66.         led->dev.name = led->name;
  67.         led->dev.brightness_set = led_brightness_set;
  68.         ret = led_classdev_register(dev, &led->dev);
  69.         if (ret) {
  70.             dev_err(dev, "Failed to register LED[%d]\n", i);
  71.             goto failed_destroy;
  72.         }
  73.     }
  74.     platform_set_drvdata(pdev, priv);
  75.     return 0;
  76. failed_destroy:
  77.     for (--i; i >= 0; i--)
  78.         led_classdev_unregister(&priv->leds[i].dev);
  79.     return ret;
  80. }
  81. static int led_remove(struct platform_device *pdev)
  82. {
  83.     struct led_priv *priv = platform_get_drvdata(pdev);
  84.     int i;
  85.     for (i = 0; i < priv->nleds; i++) {
  86.         led_classdev_unregister(&priv->leds[i].dev);
  87.     }
  88.     dev_info(&pdev->dev, "led driver removed.\n");
  89.     return 0;
  90. }
  91. static const struct of_device_id led_of_match[] = {
  92.     { .compatible = "rgb,leds", },
  93.     { /* sentinel */ },
  94. };
  95. MODULE_DEVICE_TABLE(of, led_of_match);
  96. static struct platform_driver led_driver = {
  97.     .probe = led_probe,
  98.     .remove = led_remove,
  99.     .driver = {
  100.         .name = "leds",
  101.         .of_match_table = led_of_match,
  102.     },
  103. };
  104. module_platform_driver(led_driver);
  105. MODULE_LICENSE("GPL");
复制代码
Makefile文件如下:
  1. ARCH ?= arm
  2. CROSS_COMPILE ?= /opt/gcc-aarch32-10.3-2021.07/bin/arm-none-linux-gnueabihf-
  3. KERNAL_DIR ?= ~/igkboard-imx6ull/bsp/kernel/linux-imx/
  4. PWD :=$(shell pwd)
  5. obj-m += ledv2.o
  6. modules:
  7.     $(MAKE) ARCH=${ARCH} CROSS_COMPILE=${CROSS_COMPILE} -C $(KERNAL_DIR) M=$(PWD) modules
  8.     @make clear
  9. clear:
  10.     @rm -f *.o *.cmd *.mod *.mod.c
  11.     @rm -rf *~ core .depend .tmp_versions Module.symvers modules.order -f
  12.     @rm -f .*ko.cmd .*.o.cmd .*.o.d
  13.     @rm -f *.unsigned
  14. clean:
  15.     @rm -f *.ko
复制代码
  1. make
复制代码
4.2、驱动安装测试

将编译后的ledv2.ko下载到开发板上,并进行安装:
  1. insmod ledv2.ko
复制代码
在新的驱动中,我们并没有注册字符设备,所以 /dev/ 下并不会产生新的设备文件,而在 /sys/class/leds 路径下出现了我们的三个 Led 设备文件。
  1. ls /dev/led*
  2.   ls: cannot access '/dev/led*': No such file or directory
  3. ls /sys/class/leds/
  4.   led0  led1  led2  mmc0::  mmc1::
  5. ls /sys/class/leds/led1/
  6.   brightness  device  max_brightness  power  subsystem  trigger  uevent
复制代码
接下来我们使用 echo 命令就可以控制相应 Led 亮灭了。
  1. echo 1 > /sys/class/leds/led1/brightness
  2. //效果:绿灯亮
  3. echo 0 > /sys/class/leds/led1/brightness
  4. //效果:绿灯灭
复制代码
从上面 Led 驱动程序编写过程中我们了解到,要实现一个设备的驱动供应用程序空间使用,可以有多种不同的实现方式。如果我们想要容易编程控制,则可以使用常规的字符设备驱动通过调用 ioctl() 系统调用实现;而如果想要在命令行或Shell脚本中直接实现,则我们可以使用 /sys/class 伪文件系统来实现。

来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
您需要登录后才可以回帖 登录 | 立即注册