Makefile
KERN_DIR = /home/grh/kernel_source_code/linux-2.6.32.2
all :
make -C $(KERN_DIR) M=`pwd` modules
arm-linux-gcc key_interrupt_app.c -o key_interrupt_app
clean :
make -C $(KERN_DIR) M=`pwd` modules clean
rm -rf modules.order
obj-m += test_driver.o
obj-m += key_poll.o
obj-m += key_interrupt.o
copy :
cp key_interrupt.ko key_interrupt_app /nfs
驱动源代码,就只是添加了Poll相关的部分
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/device.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/gpio.h>
#include <linux/sched.h>
#include <linux/wait.h>
#include <linux/poll.h>
#include <asm/uaccess.h>
#include <asm/irq.h>
#include <asm/io.h>
#include <mach/regs-gpio.h>
#defineGRH_MODULE_NAME"key_interrupt"
static int major;
static struct class *key_interrupt_class;
static struct class_device *key_interrupt_device;
static int key_value;
//wait_event_interruptible函数需要的两个变量
static DECLARE_WAIT_QUEUE_HEAD(grh_wait_interrupt); //休眠的进程队列头
static volatile int sleep_for_interrupt; //这个变量为0的时候read函数会休眠,中断里面将其置1,read函数末尾将其设置为0
//pin_desc是对每一个按键中断的描述,不仅仅可以是整数,也可以是更复杂到的字段,这里用简单的按键值就行了
int pin_desc[6] = {
1, 2, 3, 4, 5, 6
};
//中断处理函数
static irqreturn_t grh_handle_key_eint(int irq, void *dev_id){
int *p;
p = dev_id;
//printk(KERN_EMERG"key pressed! key=%d\n", *p);
key_value = *p;
//唤醒休眠的进程
sleep_for_interrupt = 1;
wake_up_interruptible(&grh_wait_interrupt);
return IRQ_HANDLED;
}
static void init_key(void){
//注册irq中断处理函数,将按键值和中断号绑定,所有清中断操作以及初始化中断相关寄存器的操作全部交给
//内核自动完成了,不再需要像裸机程序一样显式地对寄存器进行读写了,中断发生后会自动跳到grh_handle_key_eint
request_irq(IRQ_EINT8, grh_handle_key_eint, IRQ_TYPE_EDGE_BOTH, "key1", pin_desc);
request_irq(IRQ_EINT11, grh_handle_key_eint, IRQ_TYPE_EDGE_BOTH, "key2", pin_desc+1);
request_irq(IRQ_EINT13, grh_handle_key_eint, IRQ_TYPE_EDGE_BOTH, "key3", pin_desc+2);
request_irq(IRQ_EINT14, grh_handle_key_eint, IRQ_TYPE_EDGE_BOTH, "key4", pin_desc+3);
request_irq(IRQ_EINT15, grh_handle_key_eint, IRQ_TYPE_EDGE_BOTH, "key5", pin_desc+4);
request_irq(IRQ_EINT19, grh_handle_key_eint, IRQ_TYPE_EDGE_BOTH, "key6", pin_desc+5);
}
static int key_interrupt_open(struct inode *inode, struct file *file){
printk(KERN_EMERG"DRIVER: OPEN\n");
sleep_for_interrupt = 0;
init_key();
return 0;
}
static ssize_t key_interrupt_write(struct inode *inode, const char __user *buf, size_t count, loff_t *ppos){
printk(KERN_EMERG"DRIVER: WRITE\n");
return 0;
}
static ssize_t key_interrupt_read(struct file *file, char __user *buf, size_t count, loff_t *ppos){
printk(KERN_EMERG"DRIVER: READ\n");
//根据sleep_for_interrupt的数值决定是否将驱动进程加进休眠队列grh_wait_interrupt中,立即休眠进程
wait_event_interruptible(grh_wait_interrupt, sleep_for_interrupt);
copy_to_user(buf, &key_value, 4);
//下一次进入read的时候继续休眠等待中断发生
sleep_for_interrupt = 0;
return 0;
}
int key_interrupt_release(struct inode *inode, struct file *file){
//注销中断
free_irq(IRQ_EINT8, pin_desc);
free_irq(IRQ_EINT11, pin_desc+1);
free_irq(IRQ_EINT13, pin_desc+2);
free_irq(IRQ_EINT14, pin_desc+3);
free_irq(IRQ_EINT15, pin_desc+4);
free_irq(IRQ_EINT19, pin_desc+5);
printk(KERN_EMERG"DRIVER: RELEASE\n");
return 0;
}
//sys_poll会反复在死循环里面调用key_interrupt_poll
unsigned int key_interrupt_poll(struct file *file, struct poll_table_struct *wait){
unsigned int mask;
printk(KERN_EMERG"DRIVER POLL\n");
poll_wait(file, &grh_wait_interrupt, wait); //把当前进程挂到休眠队列里面,但是不立即休眠
mask = 0;
if(sleep_for_interrupt){ //中断发生了
mask |= POLLIN | POLLRDNORM; //把有可读数据的标志位设置为1,用户层可以得到这个mask
}
return mask;
/*
如果返回的mask是0那么进程直接进入定时休眠,如果在定时休眠过程中中断发生了,sys_poll里面的
定时休眠结束,sys_poll又会循环调用key_interrupt_poll,但这个时候mask一定返回非零数值了,这时
sys_poll中的休眠结束,进程继续运行。如果定时休眠过程中中断一直没有发生,那么定时休眠超时之后,
sys_poll再调用一次key_interrupt_poll,然会判断是否超时,如果超时直接结束进程的休眠。这是
Linux内核的poll机制的大概原理,这样在用户层调用一次poll函数,进程最多休眠时间就是传进来的wait参数,
中断一发生,定时休眠立刻结束,否则进程就休眠到一次定时休眠结束为止。
*/
}
static struct file_operations key_interrupt_fops = {
.owner = THIS_MODULE,
.open = key_interrupt_open,
.write = key_interrupt_write,
.read = key_interrupt_read,
.release = key_interrupt_release,
.poll = key_interrupt_poll
};
int key_interrupt_module_init(void){
printk(KERN_EMERG"INIT MODULE!\n");
//register the driver with the device
major = register_chrdev(0, GRH_MODULE_NAME, &key_interrupt_fops);
//create my own device class
key_interrupt_class = class_create(THIS_MODULE, "key_interrupt_class");
//create my device of my own class
key_interrupt_device = device_create(key_interrupt_class, NULL, MKDEV(major,0), NULL, "key_interrupt_device");
return 0;
}
void key_interrupt_module_exit(void){
unregister_chrdev(major, GRH_MODULE_NAME);
device_unregister(key_interrupt_device);
class_destroy(key_interrupt_class);
printk(KERN_EMERG"EXIT MODULE!\n");
}
module_init(key_interrupt_module_init);
module_exit(key_interrupt_module_exit);
MODULE_AUTHOR("GRH");
MODULE_VERSION("1.0");
MODULE_DESCRIPTION("KEY POLL DRIVER");
MODULE_LICENSE("GPL");
用户空间测试程序,每次在Poll之后判断是不是发生了中断,如果发生了中断就打印按键值,如果超时了就直接退出程序
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <poll.h>
int main(void){
struct pollfd fds[1];
int i, sum, ret;
int key_value;
int fd;
fd = open("/dev/key_interrupt_device", O_RDWR);
if(-1 == fd){
printf("open key device error!\n");
return -1;
}
fds[0].fd = fd;
fds[0].events = POLLIN;
while(1){
ret = poll(fds, 1, 5000); //只监控一个设备,这里不使用poll的多路复用功能,休眠延时是5000ms
if(-1 == ret){
printf("poll error!\n");
break;
}
else if(0 == ret){
printf("poll time out!\n");
break;
}
else{
//读操作结束的时候自然驱动程序里面的中断发生的标志sleep_for_interrupt
//就清空了,实际上read的时候该标志比为1,进程已经不可能进入休眠了,因为poll
//返回正值的时候,才会进入read
read(fd, &key_value, 4);
printf("key pressed : %d\n", key_value);
}
}
close(fd);
return 0;
}
测试结果(经过测试在timeout情况下,驱动里面poll会调用两次,这和Linux内核中poll机制的实现是吻合的,内核里面实现Poll机制的源代码这里就不分析了,有兴趣的自己去读一下吧,这里只是我自己对实验代码做一个记录而已):