Linux按键驱动程序设计详解---从简单到不简单
Posted coding__madman
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Linux按键驱动程序设计详解---从简单到不简单相关的知识,希望对你有一定的参考价值。
混杂设备驱动模型:
1. 混杂设备描述
在Linux系统中,存在一类字符设备,它们拥有相同的主设备号(10),单次设备号不同,我们称这类设备为混 杂设备(miscdevice).所有的混杂设备形成一个链表,对设备访问时内核根据次设备号查到相应的混杂设备。
混杂设备也是字符设备!
linux中使用struct miscdevice来描述一个混杂设备。
2. 混杂驱动注册
Linux中使用misc_register函数来注册一个混杂设备驱动。
int misc_register(struct miscdev *misc)
3. 范例驱动分析
3.1 初始化miscdevice(minor、name、fops)
3.2 注册miscdevice (通过misc_register函数实现)
这里安照上面的分析,先来搭建一个最简单只有一个open操作的混杂按键设备驱动模型,后边逐步深入分析逐步完善代码。
key.c
#include<linux/module.h>
#include<linux/init.h>
#inlcude<linux/miscdevice.h> /* for struct miscdevice*/
int key_open(struct inode *node, struct file *filp)
{
return 0;
}
struct file_operations key_fops =
{
.open = key_open,
};
struct miscdevice key_miscdev //定义一个misdevice结构
{
.minor = 200;
.name = "key";
.fops = &key_fops;//这里key_fops是一个struct file_operations结构
};
static int key_init()
{
misc_register(&key_miscdev);//注册一个混杂设备驱动设备
return 0;
}
static void key_exit()
{
misc_deregister(&key_miscdev);//注销一个混杂设备驱动
}
module_init(key_init);
module_exit(key_exit);
2. Linux 中断处理流程分析
下面先来分析写好按键驱动的一些准备工作!按键一般用中断的模式来处理,这里先分析linux中断处理程序:
1. 裸机中断处理流程分析
1.1 中断有一个统一的入口 irq:
......
第一步: 保护现场(中断部分执行完毕后要恢复之前的状态继续执行)
第二步: 跳转到hand_ini处执行中断程序
先事先注册中断程序,然后根据相应的中断找到对应的中断处理程序
第三步:恢复现场,
在Linux操作系统中,irq中断的统一入口其实也是这样的(entry-armv.S文件中)
这里的irq_hander其实是一个宏定义:
而arch_irq_hander_default这个宏是在entry-macro-multi.S这个文件中
拿到中断号,然后设置相关寄存器并且调到asm_do_IRQ处理中断
看看generic_handle_irq(irq)这个函数:
然后函数又跳到这里了:
最后调到了handle_irq这个结构中。
这里总结一下上面函数跳转的分析过程:
第一步:根据中断产生的统一入口进入中断处理程序,拿到产生中断源的中断号
第二步:根据这个中断号irq找到irq_desc结构, 在这个irq结构中就会有一个action选项,在这个action结构中就是用户事先填写的中断处理程序handler,这里用一张图来说明:
上面分析了那么多,其实就是为了说明在驱动中如果要用中断,驱动程序该干嘛?
第一点:实现中断处理程序
第二点:当我们的中断产生了,能够被linux操作系统调用到用户事先定义好的中断处理程序,还需要把中断处理程序 注册到Linux操作系统中来,简单的来说就是注册中断
3. Linux 中断处理程序设计
3.1 注册中断
参数说明:
unsigned int irq :中断号
void(*handler)(int , void *):中断处理函数
unsigned long flags:与中断管理有关的各种选项
const char *devname:设备名
void *dev_id:共享中断时使用
在flags参数中, 可以选择一些与中断管理有关的选项,如:
. IRQF_DISABLED(SA_INTERRUPT) 快速中断
如果设置该位,表示是一个“快速”中断处理程序;如果没有设置该位,那么就是一个“慢速”中断处理程序。
. IRQF_SHARED(SA_SHIRQ) 共享中断该位表明该中断号是多个设备共享的。
快/慢速中断的主要区别在于:快速中断保证中断处理的原子性(不被打断),而慢速中断则不保证。换句话说,也就是“开启中断”标志位(处理器IF)在运行快速中断处理程序时是关闭的,因此在服务该中断时,不会被其他类型的中断打断;而调用慢速中断处理时,其他类型的中断仍可以得到服务。
3.2 中断处理
中断处理程序的特别之处是在中断上下文中运行的,它的行为为受到某些限制:
1. 不能使用可能引起阻塞的函数
2. 不能使用可能引起调度的函数
处理流程:
3.3 注销处理
当设备不再需要使用中断时(通常在驱动卸载时),应当把它们注销,使用函数:
void free_irq(unsigned int irq, void *dev_id) // 参数dev_id 可以结和上面那张图来看,就是共享中断中的那个中断
结和上面的分析在之前的代码基础上加入下面的部分:
中断处理函数部分:
下面来分析按键硬件部分的相关知识!硬件原理图以及相关GPIO设置
这里先贴上OK6410开发板上的按键硬件原理图部分:
这里KEYINT1是和GPN0相连,
对应的CPU引脚是GPN组,下面查看下GPN引脚datasheet的相关部分:
由下面的图这里可以看到将GPNCON寄存器的最后两位设置为0b10(外部中断模式)
GPN0对应的外部中断号查芯片手册可以看到为:XEINT0
这里看看OK6410内核源码部分关于中断号的宏定义:
这个在Irqs.h文件中:要与自己使用的硬件平台对应,我这里是OK6410
这里对应的设备中断号为S3C_EINT(0)或者写出IRQ_EINT(0)都是一样的
这个文件源码中还有一句#define S3C_IRQ_OFFSET(32)
中断号偏移 其中前面的32个中断号是留给用户程序作为软中断来使用,
这里贴出在前面的基础上加的key.c的代码:
#include <linux/module.h>
#include <linux/init.h>
#include <linux/miscdevice.h> /* for struct miscdevice*/
#include <linux/interrupt.h>
#include <linux/fs.h> /* for iormap */
#include <linux/io.h>
#define GPNCON 0x7F008830
irqreturn_t key_int(int irq, void *dev_id)
{
//1. 检测是否发生了按键中断 这里可以暂时不做,因为这里没有使用共享中断
//2. 清除已经发生的按键中断 这个是指硬件内部处理,按键CPU内部不需要做处理
//比如如果是网卡驱动 就要处理
//3. 打印按键值
printk(KERN_WARNING"key down!\\n");
return 0;
}
void key_hw_init(void) //按键硬件初始化部分
{
unsigned int *gpio_config;
unsigned short data;
//第一步:设置GPNCON寄存器设置GPIO为输入
gpio_config = ioremap(GPNCON, 4);//将物理地址转化为虚拟地址
data = readw(gpio_config);
data &= ~0b11; //先清零
data |= 0b10; //后两位设置成0b10
writew(data, gpio_config);
printk(KERN_WARNING"init ...!\\n");
//第二步: 按键中断部分相应处理 注册中断 注销等等
}
int key_open(struct inode *node, struct file *filp)
{
printk(KERN_WARNING"open ...!\\n");
return 0;
}
struct file_operations key_fops =
{
.open = key_open,
};
struct miscdevice key_miscdev = //定义一个misdevice结构
{
.minor = 200,
.name = "key",
.fops = &key_fops,//这里key_fops是一个struct file_operations结构
};
static int key_init(void)
{
int err;
misc_register(&key_miscdev);//注册一个混杂设备驱动设备
//按键初始化 硬件初始化部分一般可一放在模块初始化部分或者open函数中 这里放在模块初始化部分
key_hw_init();
//由高电平变为低电平产生中断 IRQF_TRIGGER_FALLING
if( (err = request_irq(S3C_EINT(0),key_int, IRQF_TRIGGER_FALLING, "key", 0)) < 0 )//注册中断处理程序 5个参数
{
printk(KERN_WARNING"err = %d\\n", err);
goto irq_err;
}
return 0;
irq_err:
misc_deregister(&key_miscdev);
return -1;
}
static void key_exit(void)
{
free_irq(S3C_EINT(0), 0);//注销中断 这里irqnumber参数暂时用一个变量来表示(中断号)
misc_deregister(&key_miscdev);//注销一个混杂设备驱动
printk(KERN_WARNING"key up!");
}
module_init(key_init);
module_exit(key_exit);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("key driver");
这里贴一个代码编译后在开发板上运行,按下按键的效果截图:
中断分层设计:
1. 中断嵌套
2. 中断分层方式
2.1 软中断
2.2 tasklet
2.3 工作队列(使用更广泛)
工作队列是一种将任务推后执行的形式,他把推后的任务交由一个内核线程去执行。这样下半部会在进程上下文执行,它允许重新调度甚至睡眠。每个被推后的任务叫做“工作”,由这些工作组成的队列称为工作队列
这里应该是用struct workqueue_struct:
2.1. 从内核源码查看create_workqueue函数的用法:
这是内核源码里面找到的这个函数用法示例,这里可以看到create_workqueue函数只有一个参数,参数为工作队列的名字,返回的为创建好的一个工作队列指针,下面第三个箭头所指向的部分就是这个指针的类型!
用法示例:
struct workqueue_struct *my_wq;//定义一个工作队列指针
my_wq = create_workqueue("my_queue");
2.2. 下面去内核源码中查找一下init_work这个函数的用法:
两个参数:
work :要初始化的工作work指针
func :工作要执行的函数
用法示例:
struct work_struct *work1;//定义一项工作
void work1_func(struct work_struct *work)
{
printk(KERN_WARNING"this is work1>\\n");
}
work1 = kmalloc(sizeof(struct work_struct), GFP_KERNEL);
INIT_WORK(work1 , work1_func );
2.3. queue_work函数用法示例:
也是两个参数:
一个是工作队列指针 struct workqueue_struct *wq
一个是工作指针
用法示例:
queue_work(my_wq, work1);
下面根据上面的分析这里贴出一个示例小程序:
#include<linux/module.h>
#include<linux/init.h>
#include <linux/slab.h> /* for kmalloc */
struct workqueue_struct *my_wq; //定义一个工作队列指针
struct work_struct *work1; //定义一项工作
struct work_struct *work2; //定义一项工作
MODULE_LICENSE("GPL");
void work1_func(struct work_struct *work)
{
printk(KERN_WARNING"this is work1>\\n");
}
void work2_func(struct work_struct *work)
{
printk(KERN_WARNING"this is work2>\\n");
}
int init_que(void)
{
//1. 创建工作队列
my_wq = create_workqueue("my_queue");
//2. 创建工作
//work1 = kmalloc(sizeof(struct work_struct), GFP_KERNEL);
work1 = kmalloc(sizeof(struct work_struct),GFP_KERNEL);
INIT_WORK(work1 , work1_func );
//3. 挂载(提交)提交工作
queue_work(my_wq, work1);
//2. 创建工作
work2 = kmalloc(sizeof(struct work_struct), GFP_KERNEL);
INIT_WORK(work2 , work2_func );
//3. 挂载(提交)提交工作
queue_work(my_wq, work2);
return 0;
}
void clean_que(void)
{
}
module_init(init_que);
module_exit(clean_que);
3. 使用工作队列实现分层
在大多数情况下,驱动并不需要自己建立工作队列,只需定义工作,然后将工作提交到内核已经定义好的工作队列keventd_wq中。
3.1 提交工作到默认队列
schedule_work
在上面的代码这样修改也是同样的效果:
有了上面的基础,然后对之前的按键驱动进行改进!通过中断分层来实现按键驱动
按键中断处理程序 硬件处理部分比较简单,中断上半部 硬件中断处理基本可以不做
下半部 和硬件没有什么关系的部分,就是下面打印按键值部分 可以放到按键中断以外来处理,为系统节省更多的时间出来,避免应为中断程序处理部分耗时过长造成中断丢失!
#include <linux/module.h>
#include <linux/init.h>
#include <linux/miscdevice.h> /* for struct miscdevice*/
#include <linux/interrupt.h>
#include <linux/fs.h> /* for iormap */
#include <linux/io.h>
#include <linux/slab.h> /* for kmalloc */
#define GPNCON 0x7F008830
struct work_struct *work1;//定义一项工作
void work1_func(struct work_struct *work)
{
printk(KERN_WARNING"key down!\\n");
}
irqreturn_t key_int(int irq, void *dev_id)
{
//1. 检测是否发生了按键中断 这里可以暂时不做,因为这里没有使用共享中断
//2. 清除已经发生的按键中断 这个是指硬件内部处理,按键CPU内部不需要做处理
//3. 提交下半部
schedule_work(work1);
return 0;
}
void key_hw_init(void) //按键硬件初始化部分
{
unsigned int *gpio_config;
unsigned short data;
//第一步:设置GPNCON寄存器设置GPIO为输入
gpio_config = ioremap(GPNCON, 4);//将物理地址转化为虚拟地址
data = readw(gpio_config);
data &= ~0b11; //先清零
data |= 0b10; //后两位设置成0b10
writew(data, gpio_config);
printk(KERN_WARNING"init ...!\\n");
//第二步: 按键中断部分相应处理 注册中断 注销等等
}
int key_open(struct inode *node, struct file *filp)
{
printk(KERN_WARNING"open ...!\\n");
return 0;
}
struct file_operations key_fops =
{
.open = key_open,
};
struct miscdevice key_miscdev = //定义一个misdevice结构
{
.minor = 200,
.name = "key",
.fops = &key_fops,//这里key_fops是一个struct file_operations结构
};
static int key_init(void)
{
int err;
misc_register(&key_miscdev);//注册一个混杂设备驱动设备
//按键初始化 硬件初始化部分一般可一放在模块初始化部分或者open函数中 这里放在模块初始化部分
key_hw_init();
work1 = kmalloc(sizeof(struct work_struct),GFP_KERNEL);
INIT_WORK(work1 , work1_func );
//由高电平变为低电平产生中断 IRQF_TRIGGER_FALLING
if( (err = request_irq(S3C_EINT(0),key_int, IRQF_TRIGGER_FALLING, "key", 0)) < 0 )//注册中断处理程序 5个参数
{
printk(KERN_WARNING"err = %d\\n", err);
goto irq_err;
}
return 0;
irq_err:
misc_deregister(&key_miscdev);
return -1;
}
static void key_exit(void)
{
free_irq(S3C_EINT(0), 0);//注销中断 这里irqnumber参数暂时用一个变量来表示(中断号)
misc_deregister(&key_miscdev);//注销一个混杂设备驱动
printk(KERN_WARNING"key up!");
}
module_init(key_init);
module_exit(key_exit);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("key driver");
编译并且insmod安装这个驱动模块,同样可以看到按键打印的效果!不过本质上驱动处理效率上提高了!当然这里只是一个很简单的例程!
按键定时器去抖:
按键所用开关为机械弹性开关,当机械触点断开、闭合时,由于机械触点的弹性作用,开关不会马上稳定接通或断开。因而在闭合及断开的瞬间总是伴随有一连串的抖动。
按键去抖动的方法主要有两种,一种是硬件电路去抖动;另一种就是软件延时去抖。而延时一般由分为两种,一种是for循环等待,另一种是定时器延时,在操作系统中,由于效率方面的原因,一般不允许使用for循环来等待,只能使用定时器。
内核定时器:
上面两个重要的成员(红色部分)
expires: 超时也就是定时多长时间
function: 函数指针
这之间的函数就不细说了,还是同样的方法不会就查看内核代码!上面的按键驱动实际上是不完善的,按一下会打印好几个按键按下的信息,这里利用上面介绍到的内核定时器知识优化上面的按键程序:
#include <linux/module.h>
#include <linux/init.h>
#include <linux/miscdevice.h> /* for struct miscdevice*/
#include <linux/interrupt.h>
#include <linux/fs.h> /* for iormap */
#include <linux/io.h>
#include <linux/slab.h> /* for kmalloc */
#define GPNCON 0x7F008830
#define GPNDAT 0x7F008834
unsigned int *gpio_data;
struct work_struct *work1;//定义一项工作
struct timer_list key_timer; //定义一个定时器key_timer
void work1_func(struct work_struct *work)
{
//启动定时器 jiffies是全局变量,用来表示当前系统时间 1S=1000个滴答数
mod_timer(&key_timer,jiffies + HZ/10); //设置100ms超时 1HZ=1S
}
void key_timer_func(unsigned long data)
{
unsigned int key_val;
key_val = readw(gpio_data)&0x01; //只读取最后一位
if(key_val == 0)
{
printk(KERN_WARNING"OK6410 key0 down!\\n");
}
}
irqreturn_t key_int(int irq, void *dev_id)
{
//1. 检测是否发生了按键中断 这里可以暂时不做,因为这里没有使用共享中断
//2. 清除已经发生的按键中断 这个是指硬件内部处理,按键CPU内部不需要做处理
//3. 提交下半部
schedule_work(work1);
return 0;
}
void key_hw_init(void) //按键硬件初始化部分
{
unsigned int *gpio_config;
unsigned short data;
gpio_config = ioremap(GPNCON, 4);//将物理地址转化为虚拟地址
data = readw(gpio_config);
data &= ~0b11; //先清零
data |= 0b10; //后两位设置成0b10
writew(data, gpio_config);
gpio_data = ioremap(GPNDAT, 4);//将物理地址转化为虚拟地址
printk(KERN_WARNING"init ...!\\n");
}
int key_open(struct inode *node, struct file *filp)
{
printk(KERN_WARNING"open ...!\\n");
return 0;
}
struct file_operations key_fops =
{
.open = key_open,
};
struct miscdevice key_miscdev = //定义一个misdevice结构
{
.minor = 200,
.name = "key",
.fops = &key_fops,//这里key_fops是一个struct file_operations结构
};
static int key_init(void)
{
int err;
misc_register(&key_miscdev);//注册一个混杂设备驱动设备
key_hw_init();
work1 = kmalloc(sizeof(struct work_struct),GFP_KERNEL);
INIT_WORK(work1 , work1_func );
//初始化定时器
init_timer(&key_timer);
key_timer.function = key_timer_func; //将定义的函数赋值给函数指针
//注册定时器
add_timer(&key_timer);
if( (err = request_irq(S3C_EINT(0),key_int, IRQF_TRIGGER_FALLING, "key", 0)) < 0 )
{
printk(KERN_WARNING"err = %d\\n", err);
goto irq_err;
}
return 0;
irq_err:
misc_deregister(&key_miscdev);
return -1;
}
static void key_exit(void)
{
free_irq(S3C_EINT(0), 0);//注销中断 这里irqnumber参数暂时用一个变量来表示(中断号)
misc_deregister(&key_miscdev);//注销一个混杂设备驱动
printk(KERN_WARNING"key up!");
}
module_init(key_init);
module_exit(key_exit);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("key driver");
编译运行可以看到:按一下按键 只打印一个OK6410 key0 down!
在上面的基础上继续优化,实现多按键驱动这里增加key5按键!(结合上边的原理图部分)
#include <linux/module.h>
#include <linux/init.h>
#include <linux/miscdevice.h> /* for struct miscdevice*/
#include <linux/interrupt.h>
#include <linux/fs.h> /* for iormap */
#include <linux/io.h>
#include <linux/slab.h> /* for kmalloc */
#define GPNCON 0x7F008830
#define GPNDAT 0x7F008834
unsigned int *gpio_data;
struct work_struct *work1;//定义一项工作
struct timer_list key_timer; //定义一个定时器key_timer
void work1_func(struct work_struct *work)
{
//启动定时器 jiffies是全局变量,用来表示当前系统时间 1S=1000个滴答数
mod_timer(&key_timer,jiffies + HZ/10); //设置100ms超时 1HZ=1S
}
void key_timer_func(unsigned long data)
{
unsigned int key_val;
key_val = readw(gpio_data)&0x01; //只读取最后一位
if(key_val == 0)
{
printk(KERN_WARNING"OK6410 key0 down!\\n");
}
key_val = readw(gpio_data)&0x20; //只读取最后一位
if(key_val == 0)
{
printk(KERN_WARNING"OK6410 key5 down!\\n");
}
}
irqreturn_t key_int(int irq, void *dev_id)
{
//1. 检测是否发生了按键中断 这里可以暂时不做,因为这里没有使用共享中断
//2. 清除已经发生的按键中断 这个是指硬件内部处理,按键CPU内部不需要做处理
//3. 提交下半部
schedule_work(work1);
return 0;
}
void key_hw_init(void) //按键硬件初始化部分
{
unsigned int *gpio_config;
unsigned short data;
gpio_config = ioremap(GPNCON, 4);//将物理地址转化为虚拟地址
data = readw(gpio_config);
data &= ~0b110000000011; //先清零
data |= 0b100000000010; //后两位设置成0b10
writew(data, gpio_config);
gpio_data = ioremap(GPNDAT, 4);//将物理地址转化为虚拟地址
printk(KERN_WARNING"init ...!\\n");
}
int key_open(struct inode *node, struct file *filp)
{
printk(KERN_WARNING"open ...!\\n");
return 0;
}
struct file_operations key_fops =
{
.open = key_open,
};
struct miscdevice key_miscdev = //定义一个misdevice结构
{
.minor = 200,
.name = "key",
.fops = &key_fops,//这里key_fops是一个struct file_operations结构
};
static int key_init(void)
{
int err;
misc_register(&key_miscdev);//注册一个混杂设备驱动设备
key_hw_init();
work1 = kmalloc(sizeof(struct work_struct),GFP_KERNEL);
INIT_WORK(work1 , work1_func );
//初始化定时器
init_timer(&key_timer);
key_timer.function = key_timer_func; //将定义的函数赋值给函数指针
//注册定时器
add_timer(&key_timer);
if( (err = request_irq(S3C_EINT(0),key_int, IRQF_TRIGGER_FALLING, "key", 0)) < 0 )
{
printk(KERN_WARNING"err = %d\\n", err);
goto irq_err;
}
if( (err = request_irq(S3C_EINT(5),key_int, IRQF_TRIGGER_FALLING, "key", 0)) < 0 )
{
printk(KERN_WARNING"err = %d\\n", err);
goto irq_err;
}
return 0;
irq_err:
misc_deregister(&key_miscdev);
return -1;
}
static void key_exit(void)
{
free_irq(S3C_EINT(0), 0);//注销中断 这里irqnumber参数暂时用一个变量来表示(中断号)
misc_deregister(&key_miscdev);//注销一个混杂设备驱动
printk(KERN_WARNING"key up!");
}
module_init(key_init);
module_exit(key_exit);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("key driver");
运行效果:
阻塞型驱动设计:
阻塞的必要性:
1. 当一个设备无法立即满足用户的读写请求时应当如何处理?例如: 调用read时,设备没有数据提供,但以后可能会有:或者一个进程试图向设备写入数据,但是设备暂时没有准备好接受数据。当上述情况发生的时候,驱动程序应当阻塞进程,当它进入等待(睡眠)状态,直到请求可以得到满足。
2. 在实现阻塞型驱动的过程中,也需要有一个“候车室”来安排被阻塞的进程“休息”,当唤醒它们的条件成熟时,则可以从“候车室”中将这些进程唤醒。而这个“候车室”就是等待队列。
这里结合阻塞型驱动的知识点继续优化程序代码!这里顺便写个应用测试程序来测试按键驱动!
key.c代码
#include <linux/module.h>
#include <linux/init.h>
#include <linux/miscdevice.h> /* for struct miscdevice*/
#include <linux/interrupt.h>
#include <linux/fs.h> /* for iormap */
#include <linux/io.h>
#include <linux/slab.h> /* for kmalloc */
#include<linux/uaccess.h> /* for copy_to_usr */
#include <linux/sched.h>
#define GPNCON 0x7F008830
#define GPNDAT 0x7F008834
unsigned int *gpio_data;
struct work_struct *work1;//定义一项工作
struct timer_list key_timer; //定义一个定时器key_timer
unsigned int key_num = 0;
wait_queue_head_t key_q; //定义一个等待队列
void work1_func(struct work_struct *work)
{
//启动定时器 jiffies是全局变量,用来表示当前系统时间 1S=1000个滴答数
mod_timer(&key_timer,jiffies + HZ/10); //设置100ms超时 1HZ=1S
}
void key_timer_func(unsigned long data)
{
unsigned int key_val;
key_val = readw(gpio_data)&0x01; //只读取最后一位
if(key_val == 0)
{
//printk(KERN_WARNING"OK6410 key0 down!\\n");
key_num = 1;
}
key_val = readw(gpio_data)&0x20; //只读取最后一位
if(key_val == 0)
{
//printk(KERN_WARNING"OK6410 key5 down!\\n");
key_num = 6;
}
wake_up(&key_q);
}
irqreturn_t key_int(int irq, void *dev_id)
{
//1. 检测是否发生了按键中断 这里可以暂时不做,因为这里没有使用共享中断
//2. 清除已经发生的按键中断 这个是指硬件内部处理,按键CPU内部不需要做处理
//3. 提交下半部
schedule_work(work1);
//return 0;
return IRQ_HANDLED;
}
void key_hw_init(void) //按键硬件初始化部分
{
unsigned int *gpio_config;
unsigned short data;
gpio_config = ioremap(GPNCON, 4);//将物理地址转化为虚拟地址
data = readw(gpio_config);
data &= ~0b110000000011; //先清零
data |= 0b100000000010; //后两位设置成0b10
writew(data, gpio_config);
gpio_data = ioremap(GPNDAT, 4);//将物理地址转化为虚拟地址
printk(KERN_WARNING"init ...!\\n");
}
int key_open(struct inode *node, struct file *filp)
{
printk(KERN_WARNING"open ...!\\n");
return 0;
}
ssize_t key_read(struct file *filp, char __user *buf, size_t size, loff_t *pos)
{
wait_event(key_q,key_num);//休眠 没有按下为0
//将key_value值返回给用户空间
printk(KERN_WARNING"in kernel :key num is %d\\n",key_num);
copy_to_user(buf, &key_num, 4); //buf为用户空间传过来的地址
key_num = 0;
return 4;
}
struct file_operations key_fops =
{
.open = key_open,
.read = key_read,
};
struct miscdevice key_miscdev = //定义一个misdevice结构
{
.minor = 200,
.name = "6410key",
.fops = &key_fops,//这里key_fops是一个struct file_operations结构
};
static int key_init11(void)
{
int err;
misc_register(&key_miscdev);//注册一个混杂设备驱动设备
if( (err = request_irq(S3C_EINT(0),key_int, IRQF_TRIGGER_FALLING, "6410key", 0)) < 0 )
{
printk(KERN_WARNING"err = %d\\n", err);
goto irq_err;
}
if( (err = request_irq(S3C_EINT(5),key_int, IRQF_TRIGGER_FALLING, "6410key", 0)) < 0 )
{
printk(KERN_WARNING"err = %d\\n", err);
goto irq_err;
}
key_hw_init();
work1 = kmalloc(sizeof(struct work_struct),GFP_KERNEL);
INIT_WORK(work1 , work1_func );
//初始化定时器
init_timer(&key_timer);
key_timer.function = key_timer_func; //将定义的函数赋值给函数指针
//注册定时器
add_timer(&key_timer);
//初始化一个等待队列
init_waitqueue_head(&key_q);
return 0;
irq_err:
misc_deregister(&key_miscdev);
return -1;
}
static void key_exit(void)
{
free_irq(S3C_EINT(0), 0);//注销中断 这里irqnumber参数暂时用一个变量来表示(中断号)
free_irq(S3C_EINT(5), 0);//注销中断 这里irqnumber参数暂时用一个变量来表示(中断号)
misc_deregister(&key_miscdev);//注销一个混杂设备驱动
printk(KERN_WARNING"key up!");
}
module_init(key_init11);
module_exit(key_exit);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("key driver");
key_app.c
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
int main(void)
{
int fd;
int key_num;
int ret;
//1. 打开设备
fd = open("/dev/ok6410key", 0);
if(fd < 0)
{
printf("open key_device fail!\\n");
}
//2. 读取设备
ret = read(fd, &key_num, 4);
if(ret == -1)
{
printf("read fail\\n");
}
printf("key is %d\\n", key_num);
//3. 关闭设备
close(fd);
return 0;
}
Makefile
obj-m := key.o
KDIR := /home/kernel/linux-ok6410
all:
make -C $(KDIR) M=$(PWD) modules CROSS_COMPILE=arm-linux- ARCH=arm
clean:
rm -f *.ko *.o *.mod.o *.mod.c *.symvers *.bak *.order
编译:
同步到开发上,安装驱动模块 insmod key.ko
然后mknod /dev/ok6410key c 10 200
这一行的命令作用是产生设备结点供应用程序访问 ,ok6410key为设备名字 c表示这个是字符设备 混杂设备也是字符设备 10 是混杂字符设备的统一设备号 200是在驱动程序中定义的次设备号.
运行应用程序按下按键效果截图:
终于搞定了!(历时两天半)
以上是关于Linux按键驱动程序设计详解---从简单到不简单的主要内容,如果未能解决你的问题,请参考以下文章