驱动框架入门之开关量==Linux驱动开发4

一、何谓驱动框架

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
  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

栋哥爱做饭

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值