zoukankan      html  css  js  c++  java
  • Linux字符设备驱动框架(二):Linux内核的LED设备驱动框架

    /************************************************************************************

    *本文为个人学习记录,如有错误,欢迎指正。

    *本文参考资料:

    *        https://blog.csdn.net/qq_28992301/article/details/52410587

    *        https://blog.csdn.net/hanp_linux/article/details/79037610

    ************************************************************************************/

    1. 驱动框架的概念

    内核中驱动部分维护者针对每个种类的驱动设计一套成熟的、标准的、典型的驱动实现,并把不同厂家的同类硬件驱动中相同的部分抽出来自己实现好,再把不同部分留出接口给具体的驱动开发工程师来实现,这就叫驱动框架。即标准化的驱动实现,统一管理系统资源,维护系统稳定。

    2. LED设备驱动框架概述

    (1)LED设备的共性:

      1)LED的亮与灭;

      2)具有相应的设备节点(设备文件)。

    (2)LED设备的不同点:

      1)LED的硬件连接方式不同(GPIO不同);

      2)LED的控制方式不同(低或高电平触发);

      3)等其他不同点。

    因此,Linux中LED的驱动框架把所有LED设备的共性给实现了,把不同的地方留给驱动工程师去做。

    (3)核心文件:

      /kernel/driver/leds/led-class.c 
      /kernel/driver/leds/led-core.c 
      /kernel/driver/leds/led-triggers.c 
      /kernel/include/linux/leds.h

    (4)辅助文件(根据需求来决定这部分代码是否需要):

      /kernel/driver/leds/led-triggers.c 
      /kernel/driver/leds/trigger/led-triggers.c 
      /kernel/driver/leds/trigger/ledtrig-oneshot.c 
      /kernel/driver/leds/trigger/ledtrig-timer.c 
      /kernel/driver/leds/trigger/ledtrig-heartbeat.c

    3. LED设备驱动框架分析

    3.1 创建leds类

    subsys_initcall是一个宏,它的功能是将其声明的函数放到一个特定的段:.initcall4.init。

    内核在启动过程中,内核需要按照先后顺序去进行初始化操作。因此,内核给是给启动时要调用的所有初始化函数归类,然后每个类按照一定的次序去调用执行。这些分类名就叫.initcalln.init,n的值从1到8。内核开发者在编写内核代码时只要将函数设置合适的级别,这些函数就会被链接的时候放入特定的段,内核启动时再按照段顺序去依次执行各个段即可。module_init()、module_exit()也是一个宏,其功能与subsys_initcall相同,只是指定的段不同。

    //所在文件/kernel/include/linux/init.h
    #define subsys_initcall(fn)        __define_initcall("4",fn,4)
    
    #define __define_initcall(level,fn,id) 
        static initcall_t __initcall_##fn##id __used 
        __attribute__((__section__(".initcall" level ".init"))) = fn

     LED驱动框架使用subsys_initcall宏修饰leds_init()函数,因此leds_init()函数在内核启动阶段被调用。leds_init()函数的主要工作是:调用class_create()函数在/sys/class目录下创建一个leds类目录。

    //所在文件/kernel/driver/leds/led-class.c
    static int __init leds_init(void)
    {
        leds_class = class_create(THIS_MODULE, "leds");  //在/sys/class目录下创建一个leds类目录
        if (IS_ERR(leds_class))
            return PTR_ERR(leds_class);
      /*填充leds_class*/ 
      leds_class->suspend = led_suspend; 
      leds_class->resume = led_resume; 
      leds_class->dev_attrs = led_class_attrs; //类属性 
      return 0; 
    } 
    
    subsys_initcall(leds_init);

    3.2 leds类属性的定义与初始化

    leds_class->dev_attrs规定了leds设备类的类属性,其中的类属性将被sysfs以文件的形式导出至/sys/class/leds目录下,用户空间通过对这些文件的访问来操作硬件设备。详见Linux设备管理:sysfs文件系统的功能及其应用

    led_class_attrs结构体数组设置了leds设备类的属性,即led硬件操作的对象和方法。分析可知,leds类设备的操作对象一共由3个brightness(LED的亮灭状态)、max_brightness(LED最高亮度值)、trigger(LED闪烁状态)。对应的操作规则有读写,即show和store。这些操作规则内部其实调用了设备体led_classdev内的具体操作函数,譬如:当用户层试图写brightness这个对象时,会触发操作规则led_brightness_store。

    //所在文件/kernel/driver/leds/led-class.c
    static struct device_attribute led_class_attrs[] = 
    {
        __ATTR(brightness, 0644, led_brightness_show, led_brightness_store),
        __ATTR(max_brightness, 0444, led_max_brightness_show, NULL),
    #ifdef CONFIG_LEDS_TRIGGERS
        __ATTR(trigger, 0644, led_trigger_show, led_trigger_store),
    #endif
        __ATTR_NULL,
    };
    /*
    *所在文件/kernel/include/linux/sysfs.h
    *_name表示属性的名字,即在sys中呈现的文件。 *_mode表示这个属性的读写权限,如0666, 分别表示user/group/other的权限都是可读可写。 *_show表示的是对此属性的读函数,当cat这个属性的时候被调用,_stroe表示的是对此属性的写函数,当echo内容到这个属性的时候被调用。
    */ #define __ATTR(_name,_mode,_show,_store) { .attr = {.name = __stringify(_name), .mode = _mode }, .show = _show, .store = _store, }

    3.3 LED设备信息初始化

    在registerLED设备之前,需要先定义并初始化一个struct led_classdev结构体变量,该结构体包含了该LED设备的所有信息。

    初始化struct led_classdev结构体变量时,只需填充如下值即可,其余的在register过程中自动完成填充。

    --name:LED设备目录名称;

    --brightness:LED设备初始亮度;

    --max_brightness:LED设备的最大亮度;

    --void (*brightness_set)(struct led_classdev *led_cdev, enum led_brightness brightness):该函数为实际操作LED硬件的函数,由驱动工程师根据具体的LED设备来实现;

    --enum led_brightness (*brightness_get)(struct led_classdev *led_cdev):该函数用于获取LED设备的当前亮度值,LED驱动框架已实现led_get_brightness()函数(/kernel/drivers/leds/leds.h),将该函数的函数名赋予这个指针变量即可。

    struct led_classdev {
        const char      *name;          //LED设备名
        int             brightness;     //LED设备的初始亮度
        int             max_brightness; //LED设备的最大亮度
        int             flags;
    
        /* Lower 16 bits reflect status */
    #define LED_SUSPENDED        (1 << 0)
        /* Upper 16 bits reflect control information */
    #define LED_CORE_SUSPENDRESUME    (1 << 16)
    
        void (*brightness_set)(struct led_classdev *led_cdev, enum led_brightness brightness); //设置LED设备的亮度
        enum led_brightness (*brightness_get)(struct led_classdev *led_cdev);           //获取LED设备的当前亮度
    
        /* Activate hardware accelerated blink, delays are in
         * miliseconds and if none is provided then a sensible default
         * should be chosen. The call can adjust the timings if it can't
         * match the values specified exactly. */
        int (*blink_set)(struct led_classdev *led_cdev, unsigned long *delay_on, unsigned long *delay_off);
    
        struct device        *dev;
        struct list_head     node;            /* LED Device list */
        const char        *default_trigger;    /* Trigger to use */
    
    #ifdef CONFIG_LEDS_TRIGGERS
        /* Protects the trigger data below */
        struct rw_semaphore     trigger_lock;
    
        struct led_trigger    *trigger;
        struct list_head     trig_list;
        void            *trigger_data;
    #endif
    };

    3.4 LED设备的register接口

    LED设备驱动框架为驱动开发者提供在/sys/class/leds这个类下创建LED设备的接口。

    当驱动调用led_classdev_register注册了一个LED设备,那么就会在/sys/class/leds目录下创建xxx设备,由sysfs创建该设备的一系列attr属性文件(brightness、max_brightness等)将被保存至该目录下供用户空间访问。

    //所在文件/kernel/driver/leds/led-class.c
    int led_classdev_register(struct device *parent, struct led_classdev *led_cdev)
    {
        //在/sys/class/leds设备类目录下创建具体的设备目录,目录名由led_cdev->name指定
        led_cdev->dev = device_create(leds_class, parent, 0, led_cdev, "%s", led_cdev->name); 
    
        if (IS_ERR(led_cdev->dev))
            return PTR_ERR(led_cdev->dev);
    
    #ifdef CONFIG_LEDS_TRIGGERS
        init_rwsem(&led_cdev->trigger_lock);
    #endif
        /* add to the list of leds */
        down_write(&leds_list_lock);
        list_add_tail(&led_cdev->node, &leds_list);
        up_write(&leds_list_lock);
      
      //如果设备驱动在注册时没有设置max_brightness,则将max_brightness设置为满即255
        if (!led_cdev->max_brightness)
            led_cdev->max_brightness = LED_FULL;
    
      //如果在初始化struct_classdev *led_cdev时,设置了get_brightness方法,则读出当前的brightness并更新
        led_update_brightness(led_cdev);
    
    #ifdef CONFIG_LEDS_TRIGGERS
        led_trigger_set_default(led_cdev);
    #endif
    
        printk(KERN_DEBUG "Registered led device: %s
    ", led_cdev->name); //在内核启动过程中打印所注册设备类的名称
    
        return 0;
    }

    3.5 leds类属性的操作方法实现

     当用户在文件系统下读写LED设备的属性文件时,就会调用这些属性文件的show和store方法,从而操作硬件。

    (1)brightness属性操作

    1)当用户cat  /sys/class/leds/xxx/brightness时会调用led-class.c中的brightness_show函数。

    //所在文件/kernel/driver/leds/led-class.c
    static ssize_t led_brightness_show(struct device *dev, struct device_attribute *attr, char *buf)
    {
      //根据device结构体获取led_classdev结构体,其中包含了该LED设备的所有信息
      struct led_classdev *led_cdev = dev_get_drvdata(dev);
      //如果在初始化struct_classdev *led_cdev时,设置了get_brightness方法,则读出当前的brightness并更新
      led_update_brightness(led_cdev); 
      
      return sprintf(buf, "%u
    ", led_cdev->brightness);  //将LED当前亮度值存入buf中
    }
    
    static void led_update_brightness(struct led_classdev *led_cdev)
    {
        if (led_cdev->brightness_get)
            led_cdev->brightness = led_cdev->brightness_get(led_cdev); 
    }
    
    
    static inline int led_get_brightness(struct led_classdev *led_cdev)
    {
        return led_cdev->brightness;
    }

    2)当用户 echo 100  >  /sys/class/leds/xxx/brightness时会调用led-class.c中的brightness_store函数。

    //所在文件/kernel/driver/leds/led-class.c
    static ssize_t led_brightness_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t size)
    {
       //根据device结构体获取led_classdev结构体,其中包含了该LED设备的所有信息
       struct led_classdev *led_cdev = dev_get_drvdata(dev);
        ssize_t ret = -EINVAL;
        char *after;
        unsigned long state = simple_strtoul(buf, &after, 10);
        size_t count = after - buf;
    
        if (isspace(*after))
            count++;
    
        if (count == size) {
            ret = count;
    
            if (state == LED_OFF)
                led_trigger_remove(led_cdev);
            led_set_brightness(led_cdev, state);//设置LED亮度
        }
        return ret;
    }
    //所在文件/kernel/driver/leds/leds.h static inline void led_set_brightness(struct led_classdev *led_cdev, enum led_brightness value) { if (value > led_cdev->max_brightness) value = led_cdev->max_brightness; led_cdev->brightness = value; if (!(led_cdev->flags & LED_SUSPENDED)) led_cdev->brightness_set(led_cdev, value); //调用led_classdev下的LED硬件操作函数brightness_set,该函数由驱动工程师完成编写。 }

     (2)max_brightness属性操作

    当用户当用户cat  /sys/class/leds/xxx/max_brightness时会调用led-class.c中的led_max_brightness_show函数。

    //所在文件/kernel/driver/leds/led-class.c
    static ssize_t led_max_brightness_show(struct device *dev, struct device_attribute *attr, char *buf)
    {
        struct led_classdev *led_cdev = dev_get_drvdata(dev);
    
        return sprintf(buf, "%u
    ", led_cdev->max_brightness);//将最大亮度值保存至buf中
    }

    3.6 LED设备的unregister接口

    LED设备驱动框架为驱动开发者LED设备驱动的卸载接口。调用led_classdev_unregister()函数卸载LED设备驱动。

    //所在文件/kernel/driver/leds/led-class.c
    void led_classdev_unregister(struct led_classdev *led_cdev)
    {
    #ifdef CONFIG_LEDS_TRIGGERS
        down_write(&led_cdev->trigger_lock);
        if (led_cdev->trigger)
            led_trigger_set(led_cdev, NULL);
        up_write(&led_cdev->trigger_lock);
    #endif
    
        device_unregister(led_cdev->dev);  //注销设备类下的设备
    
        down_write(&leds_list_lock);
        list_del(&led_cdev->node);
        up_write(&leds_list_lock);
    }
    
    //注销设备类
    static void __exit leds_exit(void)
    {
        class_destroy(leds_class);
    }
    
    module_exit(leds_exit);

     4. LED驱动框架应用实例

    详见驱动程序实例(二):LED设备驱动程序( platform + /sys接口)

  • 相关阅读:
    Linkerd 2.10(Step by Step)—将 GitOps 与 Linkerd 和 Argo CD 结合使用
    Linkerd 2.10(Step by Step)—多集群通信
    Linkerd 2.10(Step by Step)—使用 Kustomize 自定义 Linkerd 的配置
    Linkerd 2.10(Step by Step)—控制平面调试端点
    Linkerd 2.10(Step by Step)—配置超时
    Linkerd 2.10(Step by Step)—配置重试
    Linkerd 2.10(Step by Step)—配置代理并发
    本地正常运行,线上环境诡异异常原因集合
    Need to invoke method 'xxx' declared on target class 'yyy', but not found in any interface(s) of the exposed proxy type
    alpine 安装常用命令
  • 原文地址:https://www.cnblogs.com/linfeng-learning/p/9318165.html
Copyright © 2011-2022 走看看