知识整理–Linux驱动框架概念之LED
驱动框架也用模块这种’机制’实现,为了在不需要的时候也能够卸载
何谓驱动框架
什么是驱动框架,为什么需要驱动框架,基于驱动框架写驱动有什么优势
驱动编程协作要求:(1)接口标准化;(2)尽量降低驱动开发者难度
什么是驱动框架:标准化的驱动实现``统一管控系统资源,维护系统稳定
(1)内核中驱动部分维护者针对每个种类的驱动设计一套成熟的、标准的、典型的驱动实现,并把不同厂家的同类硬件驱动中相同的部分抽出来自己实现好,再把不同部分留出接口给具体的驱动开发工程师来实现,这就叫驱动框架。
譬如LED 亮灭肯定都会有,这就是同类硬件的相同部分,用内核开发工程师开发的这套成熟的、标准的、典型的驱动去实现。
A厂家的LED能调亮度,B厂家的LED就只有亮灭,那A厂家的调亮度就是同类硬件的不同部分,驱动工程师就要在基本的驱动实现上去添加
(2)内核维护者在内核中设计了一些统一管控系统资源的体系,这些体系让内核能够对资源在各个驱动之间的使用统一协调和分配,保证整个内核的稳定健康运行。譬如系统中所有的GPIO就属于系统资源,每个驱动模块如果要使用某个GPIO就要先调用特殊的接口先申请,申请到后使用,使用完后要释放。又譬如中断号也是一种资源,驱动在使用前也必须去申请。其他比如框架中的设备锁等。这些也是驱动框架的组成部分。
(3)一些特定的接口函数、一些特定的数据结构,这些是驱动框架的直接表现。
驱动框架这个概念单靠文字说明很难理解,应在实际驱动编程中去体会上面的这几点
LED驱动框架分析
内核驱动框架中LED的基本情况
开始一件事,不盲目,简单分析后再去分析源码。像分析uboot和kernel先看地图(Makefile)那样
相关文件:
(1)drivers/leds目录,这个目录就是驱动框架规定的LED这种硬件的驱动应该待的地方。
(2)led-class.c和led-core.c,这两个文件加起来属于LED驱动框架的第一部分,这两个文件是内核开发者提供的,他们描述的是内核中所有厂家的不同LED硬件的相同部分的逻辑。必要的需要花时间的就是内核提供的led-class.c和led-core.c文件。
(3)leds-xxxx.c,这个文件是LED驱动框架的第2部分,是由不同厂商的驱动工程师编写添加的,厂商驱动工程师结合自己公司的硬件的不同情况来对LED进行操作,使用第一部分提供的接口来和驱动框架进行交互,最终实现驱动的功能。
我自己学习使用的开发板是九鼎厂商生产的s5pv210,内核源码树为其提供的linux+qt的kernel
其(九鼎移植的内核)led驱动没有使用内核推荐的led驱动框架,文件放在放在drivers/char/led/x210-led.c
案例分析驱动框架的使用:
(1)以leds-s3c24xx.c为例。leds-s3c24xx.c中通过调用led_classdev_register来完成LED驱动的注册,而led_classdev_register是在drivers/leds/led-class.c中定义的。所以其实SoC厂商的驱动工程师是调用内核开发者在驱动框架中提供的接口来实现自己的驱动的。
(2)驱动框架的关键点就是:分清楚内核开发者提供了什么,驱动开发者自己要提供什么
典型的驱动开发行业现状:
(1)内核开发者对驱动框架进行开发和维护、升级,对应led-class.c和led-core.c
(2)SoC厂商的驱动工程师对设备驱动源码进行编写、调试,提供参考版本,对应leds-s3c24xx.c
(3)做产品的厂商的驱动工程师以SoC厂商提供的驱动源码为基础,来做移植和调试
LED驱动框架源码
涉及到的文件:led-core.c和led-class.c
经过基本分析经过基本分析,发现LED驱动框架中内核开发者实现的部分主要是led-class.c。
led-class.c就是一个内核模块,对led-class.c分析应该从下往上,遵从对模块的基本分析方法。
为什么LED驱动框架中内核开发者实现的部分要实现成一个模块?因为内核开发者希望这个驱动框架是可以被装载/卸载的。
subsys_initcall:
点击这里:linux内核段属性机制(以subsys_initcall和module_init为例)
leds_init:
类的创建。leds_init除了对class结构体变量进行赋值,还做了class_create
,所以到开发板下 ls /sys/class
可以看到多了一个类leds
,但是里面是空的。里面的用来与应用层进行交互的文件是驱动程序要去创建的,这就是驱动工程师要做的(用提供的接口去创建)
led_class_attrs:
(1)attribute
是什么,对应将来/sys/class/leds/xxx
目录里的内容,一般是文件和文件夹。这些文件其实就是sysfs开放给应用层的一些操作接口(非常类似于/dev/目录下的那些设备文件)
(2)attribute
有什么用,作用就是让应用程序可以通过/sys/class/leds/xxx
目录下面的属性文件来操作驱动进而操作硬件设备。
(3)attribute
其实是另一条驱动实现的路线。有区别于之前讲的file_operations
那条线。不同设备驱动实现走的路不一定,像这边的led走的是attribute,而LCD走的是file_operations
leds_class(class结构体):
以下文件在/include/linux/device.h
struct class {
const char *name;
struct module *owner;
struct class_attribute *class_attrs;
struct device_attribute *dev_attrs;
struct kobject *dev_kobj;
int (*dev_uevent)(struct device *dev, struct kobj_uevent_env *env);
char *(*devnode)(struct device *dev, mode_t *mode);
void (*class_release)(struct class *class);
void (*dev_release)(struct device *dev);
int (*suspend)(struct device *dev, pm_message_t state);
int (*resume)(struct device *dev);
const struct kobj_ns_type_operations *ns_type;
const void *(*namespace)(struct device *dev);
const struct dev_pm_ops *pm;
struct class_private *p;
};
唤醒(resume)、挂起(suspend):
略
led_classdev_register:
属于某一类的设备的创建。分析可知,led_classdev_register
这个函数其实就是去创建一个属于leds这个类的一个设备。其实就是去注册一个设备。所以这个函数其实就是led驱动框架中内核开发者提供给SoC厂家驱动开发者的一个注册驱动的接口。
当使用led驱动框架去编写驱动的时候,这个led_classdev_register
函数的作用类似于驱动程序使用file_operations方式去注册字符设备驱动时的register_chrdev函数。
led_classdev:
以下文件在/include/linux/leds.h
struct led_classdev {
const char *name;
int brightness;
int max_brightness;
int flags;
...
/* Set LED brightness level */
/* Must not sleep, use a workqueue if needed */
void (*brightness_set)(struct led_classdev *led_cdev,
enum led_brightness brightness);
/* Get LED brightness level */
enum led_brightness (*brightness_get)(struct led_classdev *led_cdev);
...
struct device *dev;
struct list_head node; /* LED Device list */
const char *default_trigger; /* Trigger to use */
...
};
基于驱动框架写LED驱动
实践之前需要确认内核是否添加led驱动框架支持(通过make menuconfig配置,设置成功后到开发板下
ls /sys/class
可以看到多了一个类leds 但是里面是空的)
一般不从零开始写,参考哪里? drivers/leds/leds-s3c24xx.c;
思路/关键点:led_classdev_register,我们之前写的led驱动,直接看的是内核里面提供的操作接口,譬如register_chardev,cdev结构体等那些
仍然是驱动模块,module_init(XXX),insmod时会去调用XXX去注册,在xxx模块安装函数中去使用专用的接口
注:以前看注册成功与否,cat /proc/devices去看信息变化。现在要看/sys/class/leds 如果注册成功该目录下会多一些文件
示例程序leds-s5pv210.c:
现象:
第1:写的驱动能够工作了,被加载了,/sys/class/leds/目录下多出来了一个表示设备的文件夹。文件夹里面有相应的操控led硬件的2个属性brightness和max_brightness
第2:led-class.c中brightness方法有一个show方法和store方法,这两个方法对应用户在/sys/class/leds/myled/brightness目录下直接去读写这个文件时实际执行的代码。
当show brightness ...
时,实际就会执行led_brightness_show函数
当echo 1 > brightness
时,实际就会执行led_brightness_store函数
show方法实际要做的就是读取LED硬件信息,然后把硬件信息返回给用户即可。所以show方法和store方法必须能够操控硬件。但led-class.c文件属于驱动框架中的文件,本身无法直接读取具体硬件,因此在show和store方法中使用函数指针的方式调用了struct led_classdev结构体中的相应的读取/写入硬件信息的方法(在linux/leds.h中,写驱动就一定要包含这个头文件和对应的led_classdev结构体类型变量。当有多个个同类设备(即多个led_classdev结构体类型变量)它是怎么判段的呢?–不难,文章不想写太长,自行分析)(算了还是写一下吧,都整理出来了)。
struct led_classdev结构体中的实际用来读写硬件信息的函数,就是驱动文件leds-s5pv210.c中要提供的(如示例程序)。
分析echo 1 > b brightness
是如何传递的:
在硬件操作上驱动只应该提供机制而不是策略
在驱动中将4个LED分开,使应用层可以完全按照自己的需要对LED进行控制
好处:驱动层实现对各个LED设备的独立访问,并向应用层展示出4个操作接口led1、led2、led3、led4,这样应用层可以完全按照自己的需要对LED进行控制。
驱动的设计理念:不要对最终需求功能进行假定,而应该只是直接的对硬件的操作。有一个概念就是:机制和策略的问题。在硬件操作上驱动只应该提供机制而不是策略。策略由应用程序来做。比如有人要造反,提供枪支弹药这就是机制;比如马克思主义,没有枪支弹药,但是有策略
将4个LED分开程序:
leds-s5pv210.c
总结:LED驱动开发
不是很复杂的一个框架,多分析思考;关键点:细节另外,要明白写的驱动如何与框架结合(即应用到硬件是怎样的一条路线)