LED驱动框架

学习各种编程语言写的第一行代码总是打印helloworld,在硬件中学习时,第一个做的总是点亮一个LED。这里也不例外喽,以LED的框架为例子概述。

驱动框架就是不用我们直接使用前面写的那篇文章中的那些注册接口,而是内核帮助我们对不同的设备封装了一些专用的接口,其实质还是在这些专用接口中调用那些注册接口,但是可以更好的帮助我们写代码和理解驱动。

一般来说驱动框架都是固定的模式,就是内核给你提供一个封装了的注册接口,然后传参是一个你自己要去填充的一个结构体,然后调用这个函数就行了

1、框架相关文件

(1)drivers/leds目录,这个目录就是驱动框架规定的LED这种硬件的驱动应该待的地方。

(2)led-class.c和led-core.c,这两个文件加起来属于LED驱动框架的第一部分,这两个文件是内核开发者提供的,他们描述的是内核中所有厂家的不同LED硬件的相同部分的逻辑。

(3)leds-xxxx.c,这个文件是LED驱动框架的第2部分,是由不同厂商的驱动工程师编写添加的,厂商驱动工程师结合自己公司的硬件的不同情况来对LED进行操作,使用第一部分提供的接口来和驱动框架进行交互,最终实现驱动的功能。

2、LED的专用注册接口函数

分析led-class.c文件

leds_init函数

leds_init函数,这是LED的初始化函数,内部调用了class_create,所以此函数在/sys/class目录下创建leds类文件

subsys_initcall & module_init函数

内核在启动过程中需要顺序的做很多事,内核如何实现按照先后顺序去做很多初始化操作。内核的解决方案就是给内核启动时要调用的所有函数归类,然后每个类按照一定的次序去调用执行。这些分类名就叫.initcalln.init。n的值从1到8。内核开发者在编写内核代码时只要将函数设置合适的级别,这些函数就会被链接的时候放入特定的段,内核启动时再按照段顺序去依次执行各个段即可。可以看出,subsys_initcall和module_init的作用是一样的,只不过前者所声明的函数要比后者在内核启动时的执行顺序更早。

led_class_attrs数组

attribute,对应将来/sys/class/leds/目录里的内容,一般是文件和文件夹。这些文件其实就是sysfs开放给应用层的一些操作接口(非常类似于/dev/目录下的那些设备文件)。attribute作用就是让应用程序可以通过/sys/class/leds/目录下面的属性文件来操作驱动进而操作硬件设备。 attribute其实是另一条驱动实现的路线。有区别于之前讲的file_operations那条线。

注意这里并没有去实现上篇文章中写的file_operatios结构体,这里使用的是驱动的另一种方式,只不过不常用,这里用的就是attribute的方法。

led_classdev_register函数

这个led_classdev_register就是LED的专用接口注册函数,就是应该在model_init()中调用这个接口,源码如下:

/**
 * led_classdev_register - register a new object of led_classdev class.
 * @parent: The device to register.
 * @led_cdev: the led_classdev structure for this device.
 */
int led_classdev_register(struct device *parent, struct led_classdev *led_cdev)
{
    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);

    if (!led_cdev->max_brightness)
        led_cdev->max_brightness = LED_FULL;

    led_update_brightness(led_cdev);

#ifdef CONFIG_LEDS_TRIGGERS
    led_trigger_set_default(led_cdev);
#endif

    printk(KERN_DEBUG "Registered led device: %s\n",
            led_cdev->name);

    return 0;
}

(1)分析可知,led_classdev_register这个函数调用了device_create,其实就是去创建一个属于leds这个类的一个设备。其实就是去注册一个设备。所以这个函数其实就是led驱动框架中内核开发者提供给SoC厂家驱动开发者的一个注册驱动的接口。
(2)当我们使用led驱动框架去编写驱动的时候,这个led_classdev_register函数的作用类似于我们之前使用file_operations方式去注册字符设备驱动时的register_chrdev函数。

可以看到上面的接口中传参的结构体,结构体内容如下:

struct led_classdev {
    const char        *name;
    int             brightness;
    int             max_brightness;
    int             flags;
};

3、 led_classdev_unregister函数

驱动注销函数,就是应该在model_exit()中调用

/**
 * led_classdev_unregister - unregisters a object of led_properties class.
 * @led_cdev: the led device to unregister
 *
 * Unregisters a previously registered via led_classdev_register object.
 */
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);
}

4、注册驱动时要做的事

首先需要注册一个led_classdev这个结构体,然后调用led_classdev_register注册,最终调用led_classdev_unregister注销。

Last modification:November 18th, 2019 at 09:01 pm
如果觉得我的文章对你有用,请随意赞赏

Leave a Comment