一、何谓驱动框架
1、驱动是谁写的
- (1)内核维护者。
- (2)驱动开发工程师。
2、驱动编程的协作要求是什么
- (1)接口标准化。
- (2)尽量降低驱动开发者难度。
3、到底什么是驱动框架
- (1)驱动框架:内核中驱动部分维护者针对每个种类的驱动设计一套成熟的、标准的、典 型的驱动实现,即把不同厂家的同类设备驱动中的相同点提取出来实现, 把不同点留出接口给具体的驱动开发工程师实现。==就是驱动的分离与分层,驱动开发一般是写设备驱动,而主机驱动及总线驱动常常是芯片厂家提供,再难听点就是设备驱动通常厂家也会给你写好,你只要去配置二者直接即可。
- (2)驱动框架的其他组成部分:内核维护者在内核中设计出一些统一管控系统资源的体系, 让内核能够对资源进行统一协调和分配,保证内核稳定健康 地运行。譬如 GPIO、中断号的管理。
- (3)驱动的直接表现:特定的数据结构、特定的接口函数。
二、内核中 LED 驱动框架的基本情况
1、相关文件
- (1)drivers/leds 目录:驱动框架规定 LED 驱动应该放置的目录。
- (2)led-class.c 和 led-core.c:这两个文件加起来属于 LED 驱动框架的第一部分,由内核开发者提供,描述所有厂家不同 LED 的相同部分逻辑。
- (3)leds-xxx.c:是 LED 驱动框架的第二部分,由不同厂商的驱动工程师编写添加,他们结 合自己的 LED 的设备情况,使用第一部分的接口来实现最终驱动功能。
2、案例分析驱动框架的使用
- (1)leds-s3c24xx.c 为例:通过调用 led_classdev_register(在 drivers/leds/led-class.c 中定义) 来完成 LED 驱动的注册。
- (2)驱动框架的关键点:分清楚内核开发者提供了什么,驱动开发者自己要提供什么。
3、典型驱动开发行业现状
- (1)内核开发者对驱动框架进行开发和维护、升级,对应 led-class.c 和 led-core.c。
- (2)SoC 厂商的驱动工程师对设备驱动源码进行编写、调试,提供参考版本,对应 leds-s3c24xx.c。
- (3)做产品的厂商的驱动工程师以 SoC 厂商提供的驱动源码为基础,做移植和测试。===是我们常做的岗位
三、初步分析 LED 驱动框架源码
1、涉及到的文件
- (1)led-core.c。
- (2)led-class.c。
2、subsys_initcall 宏
- (1)经过基本分析,发现 LED 驱动框架中内核开发者实现的部分主要是 led-class.c
- (2)led-class.c 就是一个内核模块,对 led-class.c 分析应该从上到下,遵从对模块的基本分 析方法。
- (3)为什么 LED 驱动框架中内核者实现的部分要实现为模块?因为内核开发者希望这个驱 动框架是可以被装/卸载的,当不需要时可以卸载掉。
- (4)led-class.c 中的 subsys_initcall 是一个宏,定义中 linux/init.h 中,其功能是:将声明的 函数放到一个特定的段(.initcall4.init)。
- (5)经过分析 module_init 宏是将声明的函数放到了.initcall6.init 段中。
- (6)内核在启动时需要做很多事情,顺序就是通过不同的段名来实现的,排在前面的段先 执行。(initcall4.init 比 initcall6.init 先执行)
3、led_class_attrs 结构体
- (1)device_attribute 结构体:对应将来/sys/class/leds/目录里的内容,一般是文件和文件夹。 这些文件是 sysfs 开放给应用层的一些操作接口(类似于/dev/ 目录下那些文件)。
- (2)device_attribute 结构体作用:让应用程序可以通过/sys/class/leds/目录下的属性文件来 操作驱动进而操作硬件设备。类似于 file_operations。
4、led_classdev_register 函数
- (1)内部调用 device_create,即创建一个属于 leds 这个类的一个设备,也就是注册设备。 所以它是 led 驱动框架中提供的接口。
- (2)作用类似于 register_chrdev。
- 总结:file_operations 搭配 register_chrdev、mknod,device_attribute 搭配 led_classdev_register (内部包含自动创建设备文件代码)。
四、在内核中添加或去除某个驱动
1、去除九鼎移植的 LED 驱动 (1)九鼎移植的驱动的应用层
- (1)九鼎移植的驱动的应用层接口在/sys/devices/platform/x210-led/目录下,有 led1、led2、 led3、led4 四个设备文件,各自管理一个 LED。
- (2)去除步骤:在 make menuconfig 中取消选择(N),然后 make 编译得到 zImage,然后重启时启动这个 zImage 即可。
- (3)make menuconfig 用来选择性编译内核。
2、添加LED驱动框架支持
- (1)当前内核中时没有 LED 驱动框架的,需要去添加。
- (2)通过 make menuconfig 来勾选相应的 LED 驱动框架。
3、sys 文件系统中的内容分析
- (1) 内容为驱动程序提供给应用层的接口,也即设备文件。
五、基于驱动框架写LED驱动1
1.分析
- (1)参考对象:drivers/leds/leds-s3c24xx.c
- (2)关键点:led_classdev_register
2、编程实践
#include <linux/module.h>// module_init module_exit
#include <linux/init.h>// __init __exit
#include <linux/fs.h>
#include <linux/leds.h>//=========================important
#include <mach/regs-gpio.h>
#include <mach/gpio-bank.h>
#include <linux/io.h>
#include <linux/ioport.h>
#define GPJ0CON S5PV210_GPJ0CON
#define GPJ0DAT S5PV210_GPJ0DAT
static struct led_classdev mydev;//定义结构体变量
//完成具体硬件读写任务
static void s5pv210_led_set(struct led_classdev* led_cdev,enum led_brightness value)
{
printk(KERN_INFO "s5pv210_led_set\n");
// 在这里根据用户设置的值来操作硬件,用户设置的值就是 value
if (value == LED_OFF) {// 用户给了个 0,希望 LED 灭
writel(0x11111111, GPJ0CON);
writel(((1<<3) | (1<<4) | (1<<5)), GPJ0DAT);
}else { // 用户给的是非 0,希望 LED 亮
writel(0x11111111, GPJ0CON);
writel(((0<<3) | (0<<4) | (0<<5)), GPJ0DAT);
}
}
// 初始化
static int __init s5pv210_led_init(void)
{
// 用户 insmod 安装驱动模块时会调用该函数
// 该函数的主要任务就是去使用 led 驱动框架提供的设备注册函数来注册一个设备
int ret =-1;
mydev.name="myled";
mydev.brightness=255;
mydev.brightness_set=s5pv210_led_set;
ret=led_classdev_register(NULL,&mydev);
if(ret<0){
printk(KERN_ERR "led_classdev_register failed\n");return ret;
}
return 0;
}
static void __exit s5pv210_led_exit(void)
{
led_classdev_unregister(&mydev);
}
module_init(s5pv210_led_init);
module_exit(s5pv210_led_exit)
// MODULE_xxx 这种宏作用是用来添加模块描述信息
MODULE_LICENSE("GPL");
MODULE_AUTHOR("rgd");
MODULE_DESCRIPTION("s5pv210 led driver");
MODULE_ALIAS("s5pv210_led");
六、基于驱动框架写 LED 驱动 2
1、对代码进行调试分析
- (1)所写驱动安装后可以看到,/sys/class/leds/目录下多出来一个表示设备的文件夹 myled, 里面有操控 led 硬件的两个属性 brightness 和 max_brightness。
- (2)led-class.c 中的 brightness 方法有一个 show 方法和一个 store 方法,这两个方法对应于 用户中设备文件目录下对 brightness 读写时实际执行的代码。
- (3)show 和 store 方法实际要做的是读写硬件信息,但是 led-class.c 文件又属于驱动框架 中的文件,无法直接操控硬件。因此中 show 和 store 方法中使用函数指针的方式调用 来 struct led_classdev 结构体中的读写硬件函数。
- (4)struct led_classdev 结构体中实际用来读写硬件的函数,需要我们中 leds-s5pv210.c 中提供。
七、基于驱动框架写 LED 驱动 3
1、在驱动中将 3 个 LED 分开
- (1)好处:驱动层实现对 3 个 LED 设备独立访问,并向应用层展示出 3 个操作接口,便于 应用程序自由控制。
- (2)编程实践
#include <linux/module.h> // module_init module_exit
#include <linux/init.h> // __init __exit
#include <linux/fs.h>
#include <linux/leds.h>
#include <mach/regs-gpio.h>
#include <mach/gpio-bank.h>
#include <linux/io.h>
#include <linux/ioport.h>
#define GPJ0CON S5PV210_GPJ0CON
#define GPJ0DAT S5PV210_GPJ0DAT
static struct led_classdev mydev1; // 定义结构体变量1
static struct led_classdev mydev2; // 定义结构体变量2
static struct led_classdev mydev3; // 定义结构体变量3
// 这个函数就是要去完成具体的硬件读写任务的1
static void s5pv210_led1_set(struct led_classdev* led_cdev,enum led_brightness value)
{
printk(KERN_INFO "s5pv210_led1_set\n");
writel(0x11111111, GPJ0CON);
writel(((1<<3) | (1<<4) | (1<<5)), GPJ0DAT); // 灭
if (value == LED_OFF)// 读改写三部曲
writel((readl(GPJ0DAT) | (1<<3)), GPJ0DAT); // 灭
else
writel((readl(GPJ0DAT) & ~(1<<3)), GPJ0DAT);// 亮
}
// 这个函数就是要去完成具体的硬件读写任务的2
static void s5pv210_led1_set(struct led_classdev* led_cdev,enum led_brightness value)
{
printk(KERN_INFO "s5pv210_led2_set\n");
writel(0x11111111, GPJ0CON);
writel(((1<<3) | (1<<4) | (1<<5)), GPJ0DAT); // 灭
if (value == LED_OFF)// 读改写三部曲
writel((readl(GPJ0DAT) | (1<<4)), GPJ0DAT); // 灭
else
writel((readl(GPJ0DAT) & ~(1<<4)), GPJ0DAT);// 亮
}
// 这个函数就是要去完成具体的硬件读写任务的3
static void s5pv210_led1_set(struct led_classdev* led_cdev,enum led_brightness value)
{
printk(KERN_INFO "s5pv210_led3_set\n");
writel(0x11111111, GPJ0CON);
writel(((1<<3) | (1<<4) | (1<<5)), GPJ0DAT); // 灭
if (value == LED_OFF)// 读改写三部曲
writel((readl(GPJ0DAT) | (1<<5)), GPJ0DAT); // 灭
else
writel((readl(GPJ0DAT) & ~(1<<5)), GPJ0DAT);// 亮
}
//===入口
static int __init s5pv210_led_init(void)
{
int ret = -1;
mydev1.name = "led1";
mydev1.brightness = 255;
mydev1.brightness_set = s5pv210_led1_set;
ret = led_classdev_register(NULL, &mydev1);
if (ret < 0){
printk(KERN_ERR "led_classdev_register failed\n"); return ret;
}
mydev2.name = "led2";
mydev2.brightness = 255;
mydev2.brightness_set = s5pv210_led2_set;
ret = led_classdev_register(NULL, &mydev2);
if (ret < 0){
printk(KERN_ERR "led_classdev_register failed\n"); return ret;
}
mydev3.name = "led3";
mydev3.brightness = 255;
mydev3.brightness_set = s5pv210_led3_set;
ret = led_classdev_register(NULL, &mydev3);
if (ret < 0){
printk(KERN_ERR "led_classdev_register failed\n"); return ret;
}
return 0;
}
//===出口
static void __exit s5pv210_led_exit(void)
{
led_classdev_unregister(&mydev1);
led_classdev_unregister(&mydev2);
led_classdev_unregister(&mydev3);
}
module_init(s5pv210_led_init);
module_exit(s5pv210_led_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("aston <1264671872@qq.com>");
MODULE_DESCRIPTION("s5pv210 led driver");
MODULE_ALIAS("s5pv210_led");
2、gpiolib 引入
- (1)很多硬件都要用到 gpio,gpio 不得不复用。
- (2)如果一个 gpio 同时被两个驱动控制,就会出现 bug。
- (3)内核提供 gpiolib 来统一管控系统中所有 gpio,gpiolib 也属于驱动框架的一部分
八、gpiolib 使用方法
1、流程分析
- (1)使用 gpio_request 申请使用一个 IO 口。
- (2)使用 gpio_direction_input/gpio_direction_output 设置输入/输出模式。
- (3)使用 gpio_set_value 设置输出值,使用 gpio_get_value 获取 IO 口的值
2、代码实战
- (1)在 led1 上编写代码测试通过。
- (2)扩展支持 led2 和 led3,可以分开注册也可以使用 gpio_request_array 一次注册。
- (3)Linux 中查看 gpio 使用情况的方法
- ①内核中提供来虚拟文件系统 debugfs,里面有一个 gpio 文件,提供了 gpio 使用信息。
- ②使用方法:mount -t debugfs debugfs /tmp,然后 cat /tmp/gpio 即可得到 gpio 所有信息,使用完后 umount /tmp 卸载掉 debugfs。
#include <linux/module.h> // module_init module_exit
#include <linux/init.h> // __init __exit
#include <linux/fs.h>
#include <linux/leds.h>
#include <mach/regs-gpio.h>
#include <mach/gpio-bank.h>
#include <linux/io.h>
#include <linux/ioport.h>
#include <mach/gpio.h>
#define GPIO_LED1 S5PV210_GPJ0(3)
#define GPJ0CON S5PV210_GPJ0CON
#define GPJ0DAT S5PV210_GPJ0DAT
#define X210_LED_OFF 1 // X210 中 LED 是正极接电源,负极节 GPIO
#define X210_LED_ON 0 // 所以 1 是灭,0 是亮
static struct led_classdev mydev1; // 定义结构体变量
static struct led_classdev mydev2; // 定义结构体变量
static struct led_classdev mydev3; // 定义结构体变量
static void s5pv210_led1_set(struct led_classdev *led_cdev, enum led_brightness value)
{
printk(KERN_INFO "s5pv210_led1_set\n");
if (value == LED_OFF)//用户给了个 0,希望 LED 灭
gpio_set_value(GPIO_LED1, X210_LED_OFF);
else//用户给的是非 0,希望 LED 亮
gpio_set_value(GPIO_LED1, X210_LED_ON);
}
static void s5pv210_led2_set(struct led_classdev *led_cdev, enum led_brightness value)
{
printk(KERN_INFO "s5pv2102_led2_set\n");
if (value == LED_OFF) { // 用户给了个 0,希望 LED 灭
writel(0x11111111, GPJ0CON); // 读改写三部曲
writel((readl(GPJ0DAT) | (1<<4)), GPJ0DAT);
}
else { // 用户给的是非 0,希望 LED 亮
writel(0x11111111, GPJ0CON);
writel((readl(GPJ0DAT) & ~(1<<4)), GPJ0DAT);
}
}
static void s5pv210_led3_set(struct led_classdev *led_cdev, enum led_brightness value)
{
//`````````````````````````````````同上led2
}
//=========入口
static int __init s5pv210_led_init(void)
{
// 用户 insmod 安装驱动模块时会调用该函数
// 该函数的主要任务就是去使用 led 驱动框架提供的设备注册函数来注册一个设备
int ret=-1;
// 在这里去申请驱动用到的各种资源,当前驱动中就是 GPIO 资源----核心gpiolib
if (gpio_request(GPIO_LED1, "led1_gpj0.3"))
printk(KERN_ERR "gpio_request failed\n");
else// 设置为输出模式,并且默认输出 1 让 LED 灯灭
gpio_direction_output(GPIO_LED1, X210_LED_OFF);
//led1
mydev1.name = "led1";
mydev1.brightness = 0;
mydev1.brightness_set = s5pv210_led1_set;
ret = led_classdev_register(NULL, &mydev1);
if (ret < 0) {
printk(KERN_ERR "led1_classdev_register failed\n"); return ret;
}
//==========led2同上
//==========led3同上
return 0;
}
//=========出口
static void __exit s5pv210_led_exit(void)
{
led_classdev_unregister(&mydev1);
led_classdev_unregister(&mydev2);
led_classdev_unregister(&mydev3);
gpio_free(GPIO_LED1);
}
module_init(s5pv210_led_init);
module_exit(s5pv210_led_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("rgd");
MODULE_DESCRIPTION("s5pv210 led driver");
MODULE_ALIAS("s5pv210_led");
九、将驱动添加到内核中
1.驱动的存在形式
- (1)野生:方便调试,主要用于开发阶段。
- (2)家养:可以在内核配置时 make menuconfig 决定内核怎么编译,方便集成。
2、驱动开发的一般步骤
- (1)以模块的形式中外部编写、调试。
- (2)将调试好的驱动代码集成到 kernel 中。
3、实践
- (1)关键点:Kconfig、Makefile、make menuconfig。
- (2)操作步骤:
- 第 1 步:将写好的驱动源文件放入内核源码中正确的目录下。
- 第 2 步:在 Makefile 中添加相应的依赖。
- 第 3 步:Kconfig 中添加相应配置项。
- 第 4 步:make menuconfig