1.linux内核混杂设备驱动
1.1.混杂设备定义
从软件实现角度去看,混杂设备本质还是字符设备
只是它的主设备号由内核已经定义好为10
各个混杂设备驱动通过次设备号来区分
1.2.linux内核描述混杂设备的结构体
struct miscdevice {
const char *name;
int minor;
const struct file_operations *fops;
...
};
功能:描述混杂设备属性
name:混杂设备文件名,并且由linux自动帮你创建,无需调用四个函数
minor:给混杂设备指定的次设备号,一般指定宏:MISC_DYNAMIC_MINOR
表示让linux内核帮你分配一个次设备号
fops:混杂设备的硬件操作接口
配套函数:
misc_register(&混杂设备对象)
功能:向内核注册一个混杂设备对象
misc_deregister(&混杂设备对象);
功能:从内核中卸载混杂设备对象
1.3.案例:编写按键的混杂设备驱动,实现能够获取按键的操作状态,打印按键按下或者松开
参考代码:day05/1.0
头文件,定义,初始化
定义接口函数,定义硬件操作接口对象,定义设备驱动对象
驱动入口,出口函数
%s/led/btn/g 在Makefile中将所有led替换为btn
下位机测试:
cd /home/drivers/
insmod btn_drv.ko
ls -l /dev/mybtn
./btn_test //然后操作按键观察打印信息
然后去上位机把btn_test.c的打印函数printf注释掉,然后交叉编译拷贝,下位机继续测试
./btn_test & //让btn_test后台运行,继续执行命令
ps //查看后台运行的btn_test
top //观察btn_test的CPU占用率
按Q键退出top命令
结论:发现btn_test占用了100%的CPU资源,太浪费CPU资源了
此问题表面看是应用程序造成的,但实际是驱动程序太烂了!
问:如何解决此问题,必须采用轮询的死对头中断方式来解决
混杂设备驱动与字符设备驱动的对比
混杂设备驱动:代码量少,无法用次设备号区分多个硬件个体
字符设备驱动:可以通过次设备号管理多个硬件个体
字符设备驱动: 混杂设备驱动:
定义设备号变量
static dev_t dev;
定义设备驱动对象 定义初始化设备驱动对象,完成2.3.6
static struct cdev led_cdev; static struct miscdevice btn_misc
定义硬件操作接口对象 定义硬件操作接口对象
static struct file_operations led_fops={ static struct file_operations btn_fops={
}; };
定义设备类对象指针
static struct class *cls;
入口函数 入口函数
static int led_init(void){ static int btn_init(void){
1.申请GPIO资源,配置 1.申请GPIO资源,配置
2.申请设备号(用到变量dev)
alloc_chrdev_region(&dev,0,4,"tarena);
3.初始化设备驱动对象,关联接口对象(用到led_cdev,led_fops)
cdev_init(&led_cdev, &led_fops);
4.注册设备驱动对象 2.注册混杂设备驱动对象
5.创建设备类对象
6.创建设备文件
2.linux内核中断编程
2.1.计算机中为什么有中断机制
就一句话:CPU的数据处理速度要远远快于外设的数据处理速度所以采用中断
面试时:一定要举例子阐述中断诞生的本质原因:
以CPU读取UART接收缓冲区数据为例:
1.当CPU读取UART接收缓冲区数据时,发现数据还没有准备就绪,首先想到采用轮询方式,
也就是CPU什么事情都不干,原地死等直到UART接收缓冲区数据准备就绪,然后CPU将数据读走
此等待过程CPU做大量无用功,降低了CPU的利用率,CPU只能干一件事
2.所以对于此种情况立刻想到轮询的死对头中断方式,当CPU读取UART接收缓冲区数据时,
发现数据还没有准备就绪,CPU就去干别的事情(处理一个其他程序),一旦将来UART接收缓冲区数据
准备就绪,UART控制器就会给CPU核发送一个中断电信号,通知CPU核数据准备就绪了,CPU核立刻
停止当前执行的这个程序转去处理UART接收缓冲区的数据,处理完毕,CPU核再次回到原先被打断的
程序中继续执行,等待着下一次UART通知它,此过程CPU至少做两件事,大大提高了CPU利用率
并且CPU没有做无用功!
2.2.然后谈谈轮询和中断各自的应用场景
轮询方式应用于等待时间极短的场合,例如:纳秒,微秒,10毫秒以内
中断方式应用于等待时间较长或者随机场合,例如:毫秒,秒或者随机场合
2.3.linux系统中,中断处理都是在内核空间完成,将来编写的中断处理函数永远运行在内核空间
问:linux内核如何编写一个外设的硬件中断处理函数呢?
一旦编写完成,就可以静静等待着硬件中断触发,然后调用其中断处理函数了!
答:利用大名鼎鼎的request_irq/free_irq来完成
2.4.详解linux内核中断编程的两个重要函数:
函数原型:
int request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags, const char *name, void *dev)
功能:在linux内核中,处理器的任何一个硬件中断资源对于linux内核来说都是一种宝贵的资源
如果驱动想要访问某个硬件中断资源,必须先向内核申请这个硬件中断资源
一旦申请成功,然后向内核注册这个硬件中断对应的中断处理函数,一旦注册成功,静静等待着
硬件中断触发,一旦触发,将来内核自动调用注册的中断处理函数
所以此函数完成两个工作:
1.申请硬件中断资源
2.注册中断处理函数
参数:
irq:在linux内核中,linux内核给每个硬件中断都分配一个软件编号,类似硬件中断的身份证号,简称中断号
中断号由GPIO编号经过gpio_to_irq函数进行换算而得:
硬件GPIO GPIO编号 中断号
GPIOA28 PAD_GPIO_A+28 gpio_to_irq(PAD_GPIO_A + 28);
GPIOB9 PAD_GPIO_B+9 gpio_to_irq(PAD_GPIO_B + 9);
... ... ...
所以第一个参数irq就是传递要申请的硬件中断的中断号
handler:传递要注册的中断处理函数,其实就是传递要注册的中断处理函数名即可(函数名就是函数地址),
一旦注册完毕,静静等待硬件中断触发,一旦触发,内核自动调用此函数
编写一个中断处理函数例子:
irqreturn_t 中断处理函数名(int irq, void *dev) {
//根据用户需求编写中断处理函数
....
return IRQ_NONE; //中断处理函数执行失败
或者
return IRQ_HANDLED; //中断处理函数执行成功
}
注意中断处理函数形参问题:
irq:保存当前触发中断的中断号,例如:GPIOA28产生的中断,irq=gpio_to_irq(PAD_GPIO_A+28);
dev:保存给中断处理函数传递的参数
问:谁来传递参数呢?
答:request_irq函数的最后一个形参(void *dev)用来给中断处理函数传递参数
而中断处理函数的第二个形参dev来保存传递的参数!
flags:指定中断触发的类型
对于外部中断(中断线在原理图上可见),flags一般指定以下宏:
IRQF_TRIGGER_HIGH:指定为高电平触发
IRQF_TRIGGER_LOW:指定为低电平触发
IRQF_TRIGGER_FALLING:指定为下降沿触发
IRQF_TRIGGER_RISING:指定为上升沿触发
IRQF_TRIGGER_FALLING|IRQF_TRIGGER_RISING:指定为双边沿触发
对于内部中断(就是各种控制器给中断控制器发送的中断,此中断线不可见),一律给0
name:指定中断的名称,将来可以通过cat /proc/interrupts查看注册的中断名称,可以用于调试
dev:给中断处理函数传递的参数,如果不传递参数给NULL
回想创建线程:
void *thread_func(void *dev) {
//dev=&data
printf("data = %d\n", *(int *)dev);
}
pthread_t tid;
int data = 520;
pthread_create(&tid, NULL, thread_func, &data/*给thread_func传递的参数*//);
free_irq函数原型:
void free_irq(int irq, void *dev)
功能:如果驱动不再使用某个硬件中断资源,必须要释放这个硬件中断资源并且删除之前注册的中断处理函数
参数:
irq:传递要释放的硬件中断的中断号
dev:传递给中断处理函数的参数,此参数务必要和request_irq的最后一个参数保持一致,否则系统崩溃
案例:利用中断实现按键驱动,操作按键能够打印按键的状态
参考代码:day05/2.0
上位机执行:
配置linux内核去除官方的按键驱动:
cd /opt/kernel
make menuconfig
Device Drivers->
Input device support->
<*>Keyboard->
<*>..... //最后一个选项,这个选项对应的按键驱动就是官方的按键驱动
按N键去除
保存退出
make uImage -j4
将arch/arm/boot/uImage拷贝到windows下的fastboot目录中
然后下位机上利用fastboot命令重新烧写uImage(具体烧写步骤参看之前的笔记)
mkdir /opt/drivers/day05/2.0 -p
cd /opt/drivers/day05/2.0
vim btn_drv.c
1 #include<linux/init.h>
2 #include<linux/module.h>
3 #include<linux/irq.h>
4 #include<linux/interrupt.h>
5 #include<linux/gpio.h>
6 #include<mach/platform.h>
7
8 //声明按键的硬件信息
9 struct btn_resource{
10 char *name;
11 int gpio;
12 };
13
14 //定义按键的硬件信息对象
15 static struct btn_resource btn_info[] = {
16 {
17 .name = "KEY_UP",
18 .gpio = PAD_GPIO_A + 28
19 },
20 {
21 .name = "KEY_DOWN",
22 .gpio = PAD_GPIO_B + 9
23 }
24 };
25
26 //定义按键的中断处理函数
27 //按键松开,按下,内核调用此中断处理函数
28 //irq:保存当前触发中断的中断号
29 //例如:
30 //KEY_UP产生中断,irq=gpio_to_irq(btn_info[0].gpio)
31 //KEY_DOWN产生中断,irq=gpio_to_irq(btn_info[1].gpio)
32 //dev:保存给中断处理函数传递的参数
33 //例如:
34 //KET_UP产生中断,dev=&btn_info[0]
35 //KEY_DOWN产生中断,dev=&btn_info[1]
36 static irqreturn_t button_isr(int irq, void *dev){
37 //1.暂存当前触发中断的按键硬件信息
38 //KET_UP产生中断,pdata=&btn_info[0]
39 //KEY_DOWN产生中断,pdata=&btn_info[1]
40 struct btn_resource *pdata = (struct btn_resource *)dev;
41
42 //2.分配内核缓冲区暂存按键的状态
43 int kstate;
44
45 //3.获取按键的状态保存到内核缓冲区
46 kstate = gpio_get_value(pdata->gpio);
47
48 //4.打印按键的状态
49 printk("%s:按键[%s]的状态是:%s\n", __func__, pdata->name, kstate?"松开":"按下");
50 return IRQ_HANDLED;//中观处理函数执行成功返回,执行失败返回IRQ_NONE
51 }
52
53 static int btn_init(void){
54 int i;
55 //1.申请GPIO资源
56 //2.申请中断资源,注册中断处理函数
57 for(i = 0; i < ARRAY_SIZE(btn_info); i++){
58 int irq = gpio_to_irq(btn_info[i].gpio);//通过gpio编号换算对应的中断号
59 gpio_request(btn_info[i].gpio, btn_info[i].name);
60 request_irq(irq,//申请的硬件中断的中断号
61 button_isr,//注册的按键中断处理函数,目前是四个按键共用一个中断处理函数
62 IRQF_TRIGGER_FALLING|IRQF_TRIGGER_RISING,//指定按键中断触发的类型
63 btn_info[i].name,//指定按键中断的名称
64 &btn_info[i]//把每个按键的硬件信息的首地址传递给中断处理函数
65 );
66 }
67 return 0;
68 }
69
70 static void btn_exit(void){
71 int i;
72 //1.释放GPIO资源
73 //2.释放中断资源和删除中断处理函数
74 for(i = 0; i < ARRAY_SIZE(btn_info); i++){
75 int irq = gpio_to_irq(btn_info[i].gpio);//通过gpio编号换算对应的中断号
76 gpio_free(btn_info[i].gpio);
77 free_irq(irq, &btn_info[i]);
78 }
79 }
80 module_init(btn_init);
81 module_exit(btn_exit);
82 MODULE_LICENSE("GPL");
vim Makefile
make
cp btn_drv.ko /opt/rootfs/home/drivers
下位机linux系统启动之后执行:
cd /home/drivers
insmod btn_drv.ko
cat /proc/interrupts //查看注册的中断信息
CPU0
33: 0 GIC pl08xdmac
34: 0 GIC pl08xdmac
37: 0 GIC rtc 1hz
39: 38 GIC nxp-uart
... .... ... ...
134: 0 GPIO KEY_UP
147: 0 GPIO KEY_DOWN
第一列:表示硬件中断对应的中断号
第二列:表示硬件中断触发的次数,可以通过第二列来判断中断是否触发
第三列:中断类型
第四列:中断的名称,就是request_irq函数的第四个参数:const char *name,可以通过名称来判断中断是否申请和注册成功
然后疯狂操作按键,按下松开,然后观察打印信息
需要消除按键抖动
操作完毕,继续执行:cat /proc/interrupts //观察按键中断触发的次数
rmmod btn_drv
cat /proc/interrupts //还有中断的信息吗?