linux字符驱动之中断按键

在上一节中,我们讲解了如何自动创建设备节点,实现一个查询方式的按键驱动。测试的时候,大家都看到了,使用查询式的方法,占用CPU的利用率高达99%,那么有没有好的办法来取代这惨不忍睹的方法呢?答案当然是有的。

上一节文章链接:http://blog.csdn.net/xiaoxiaopengbo/article/details/78779974

这一节里,我们使用中断的方法来实现按键驱动。

问:内核的中断体系是怎么样的?

答:ARM架构linux内核中,有5种常见的异常,其中中断异常是其一,Linux内核将所有中断统一编号,使用一个irq_desc结构体来描述这些中断,里面记录了中断名称、中断状态、中断标记、并提供了中断的底层硬件访问函数(如:清除、屏蔽、使能中断),提供了这个中断的处理函数入口,通过它还可以调用用户注册的的中断处理函数。

问:irq_desc结构体有哪些重要的成员?

/**
 * struct irq_desc - interrupt descriptor
 * @irq:		interrupt number for this descriptor
 * @handle_irq:		highlevel irq-events handler [if NULL, __do_IRQ()]
 * @chip:		low level interrupt hardware access
 * @action:		the irq action chain
 * @status:		status information
 * @irq_count:		stats field to detect stalled irqs
 * @name:		flow handler name for /proc/interrupts output
 */
struct irq_desc {
	unsigned int		irq;
	......
	irq_flow_handler_t	handle_irq;
	struct irq_chip		*chip;
	......
	struct irqaction	*action;	/* IRQ action list */
	unsigned int		status;		/* IRQ status */
	......
	unsigned int		irq_count;	/* For detecting broken IRQs */
	......
	const char		*name;
} ____cacheline_internodealigned_in_smp;

关于irq_desc结构体的具体描述,请参考《嵌入式Linux应用完全开发手册》的第20章的20.2.1节,即416页开始的描述。关于linux中断体系的详细讲解,我就不多说了,大家参考韦老师的视频教程:

"第12课第4.1节.字符设备驱动程序之中断方式的按键驱动_Linux异常处理结构"

"第12课第4.2节.字符设备驱动程序之中断方式的按键驱动_Linux中断处理结构"

这二个视频,已经讲解的非常到位,如果还不理解的,大家可以参考韦老师的书的第20章内容。建议大家结合linux源码和书一起看,这样对你深入linux中断体系就不远了。

问:既然linux内核的中断体系已经 那么完善了,那么驱动工程师还需要做什么?

答:驱动工程师需要调用request_irq向内核注册中断,其原型为:

request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags, const char * devname, void * dev_id)

第一个参数irq:中断号,在irqs.h中定义,与架构相关;

第二个参数handler:  用户中断处理函数;

第三个参数flags:中断标记;

第四个参数devname:中断名字;可以通过cat proc/interrupts查看;

第五个参数dev_id:  在free_irq中有用,也用作区分中断处理函数;

问:request_irq函数有什么作用?

答:

1)、irq_desc[irq]结构体中的action链表中已经链入了用户注册的中断处理函数。

2)、中断触发方式已经被设置好。

3)、中断已经被使能。

问:如何卸载中断处理函数?

答:中断是一种很稀缺的资源,当不再使用一个设备时,应该释放它占据的中断。驱动工程师通过free_irq函数来实现,其原型为:

void free_irq(unsigned int irq, void *dev_id)

第一个参数irq:中断号,与request_irq中的irq一致,用于定位action链表;

第二个参数dev_id:用于在action链表中找到要卸载的表项;同一个中断的不同中断处理函数必须使用不同的dev_id来区分,这就要求在注册共享中断时参数dev_id必须唯一。

问:free_irq有什么作用?

答: 1)、根据中断号irq、dev_id从action链表中找到表项,将它移除。

2)、如果它是唯一的表项,还要调用IRQ_DESC[IRQ].CHIP->SHUTDOWN或者IRQ_DESC[IRQ].CHIP->DISABLE来关闭中断。

前面说了那么多中断,暂且放下中断,来谈谈linux是如何睡觉的吧,睡觉?linux也会睡觉?没错,只不过在术语上不叫睡觉,人家那叫休眠。

问:linux内核如何进行休眠?

答:使用wait_event函数,其扩展型常用的函数为wait_event_interruptible(wq, condition),即可被中断打断的休眠。

wq是一个等待队列,condition是条件,如果condition = 0,则将会进行休眠,直到condition = 1,并且有唤醒函数唤醒它。

问:linux内核如果唤醒进程?

答:使用wait_up函数,其扩展型常用的函数为wake_up_interruptible(wq),wq与wait_event_interruptible的wq是一致的。

问:能直接使用wait_event_interruptible和wake_up_interruptible函数吗?

答:不能,必须先事先使用static DECLARE_WAIT_QUEUE_HEAD(wq)定义并初始化一个等待队列头,并设置condition条件变量。

详细请参考驱动源码:

#include <linux/delay.h>
#include <linux/irq.h>
#include <asm/uaccess.h>
#include <asm/irq.h>
#include <asm/io.h>
#include <linux/module.h>
#include <linux/device.h> 		//class_create
#include <mach/regs-gpio.h>		//S3C2410_GPF1
//#include <asm/arch/regs-gpio.h>  
#include <mach/hardware.h>
//#include <asm/hardware.h>
#include <linux/interrupt.h>  //wait_event_interruptible


/* 定义并初始化等待队列头 */
static DECLARE_WAIT_QUEUE_HEAD(button_waitq);


static struct class *thirddrv_class;
static struct device *thirddrv_device;

static struct pin_desc{
	unsigned int pin;
	unsigned int key_val;
};

static struct pin_desc pins_desc[4] = {
		{S3C2410_GPF1,0x01},
		{S3C2410_GPF4,0x02},
		{S3C2410_GPF2,0x03},
		{S3C2410_GPF0,0x04},
}; 

static int ev_press = 0;

/* 键值: 按下时, 0x01, 0x02, 0x03, 0x04 */
/* 键值: 松开时, 0x81, 0x82, 0x83, 0x84 */
static unsigned char key_val;
int major;

/* 用户中断处理函数 */
static irqreturn_t buttons_irq(int irq, void *dev_id)
{
	struct pin_desc *pindesc = (struct pin_desc *)dev_id;
	unsigned int pinval;
	pinval = s3c2410_gpio_getpin(pindesc->pin);

	if(pinval)
	{
		/* 松开 */
		key_val = 0x80 | (pindesc->key_val);
	}
	else
	{
		/* 按下 */
		key_val = pindesc->key_val;
	}

	ev_press = 1;							 /* 表示中断已经发生 */
	 wake_up_interruptible(&button_waitq);   /* 唤醒休眠的进程 */
	return IRQ_HANDLED;
}
static int third_drv_open(struct inode * inode, struct file * filp)
{
	/*  K1 ---- EINT1,K2 ---- EINT4,K3 ---- EINT2,K4 ---- EINT0
  	 *  配置GPF1、GPF4、GPF2、GPF0为相应的外部中断引脚
  	 *  IRQT_BOTHEDGE应该改为IRQ_TYPE_EDGE_BOTH
	 */
	request_irq(IRQ_EINT1, buttons_irq, IRQ_TYPE_EDGE_BOTH, "K1",&pins_desc[0]);
	request_irq(IRQ_EINT4, buttons_irq, IRQ_TYPE_EDGE_BOTH, "K2",&pins_desc[1]);
	request_irq(IRQ_EINT2, buttons_irq, IRQ_TYPE_EDGE_BOTH, "K3",&pins_desc[2]);
	request_irq(IRQ_EINT0, buttons_irq, IRQ_TYPE_EDGE_BOTH, "K4",&pins_desc[3]);
	return 0;
}

static ssize_t third_drv_read(struct file *file, char __user *user, size_t size,loff_t *ppos)
{
	if (size != 1)
			return -EINVAL;
	
	/* 当没有按键按下时,休眠。
	 * 即ev_press = 0;
	 * 当有按键按下时,发生中断,在中断处理函数会唤醒
	 * 即ev_press = 1; 
	 * 唤醒后,接着继续将数据通过copy_to_user函数传递给应用程序
	 */
	wait_event_interruptible(button_waitq, ev_press);
	copy_to_user(user, &key_val, 1);
	
	/* 将ev_press清零 */
	ev_press = 0;
	return 1;	
}

static int third_drv_close(struct inode *inode, struct file *file)
{
	free_irq(IRQ_EINT1,&pins_desc[0]);
	free_irq(IRQ_EINT4,&pins_desc[1]);
	free_irq(IRQ_EINT2,&pins_desc[2]);
	free_irq(IRQ_EINT0,&pins_desc[3]);
	return 0;
}

/* File operations struct for character device */
static const struct file_operations third_drv_fops = {
	.owner		= THIS_MODULE,
	.open		= third_drv_open,
	.read		= third_drv_read,
	.release    = third_drv_close,
};


/* 驱动入口函数 */
static int third_drv_init(void)
{
	/* 主设备号设置为0表示由系统自动分配主设备号 */
	major = register_chrdev(0, "third_drv", &third_drv_fops);

	/* 创建thirddrv类 */
	thirddrv_class = class_create(THIS_MODULE, "thirddrv");

	/* 在thirddrv类下创建buttons设备,供应用程序打开设备*/
	thirddrv_device = device_create(thirddrv_class, NULL, MKDEV(major, 0), NULL, "buttons");

	return 0;
}

/* 驱动出口函数 */
static void third_drv_exit(void)
{
	unregister_chrdev(major, "third_drv");
	device_unregister(thirddrv_device);  //卸载类下的设备
	class_destroy(thirddrv_class);		//卸载类
}

module_init(third_drv_init);  //用于修饰入口函数
module_exit(third_drv_exit);  //用于修饰出口函数	

MODULE_AUTHOR("LWJ");
MODULE_DESCRIPTION("Just for Demon");
MODULE_LICENSE("GPL");  //遵循GPL协议

应用测试程序

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>


/* third_test
 */ 
int main(int argc ,char *argv[])

{
	int fd;
	unsigned char key_val;
	
	fd = open("/dev/buttons",O_RDWR);
	if (fd < 0)
	{
		printf("open error\n");
	}

	while(1)
	{
		read(fd,&key_val,1);
		printf("key_val = 0x%x\n",key_val);
		
	}
	return 0;
}

测试步骤1

[WJ2440]# ls
Qt             etc            mnt            second_drv.ko  udisk
TQLedtest      first_drv.ko   opt            second_test    usr
app_test       first_test     proc           sys            var
bin            home           root           third_drv.ko   web
dev            lib            sbin           third_test
driver_test    linuxrc        sddisk         tmp
[WJ2440]# ls /dev/buttons -l
ls: /dev/buttons: No such file or directory
[WJ2440]# insmod third_drv.ko 
[WJ2440]# lsmod 
third_drv 3016 0 - Live 0xbf003000
[WJ2440]# ls /dev/buttons -l
crw-rw----    1 root     root      252,   0 Jan  2 02:12 /dev/buttons
[WJ2440]# ./third_test 
key_val = 0x1
key_val = 0x81
key_val = 0x2
key_val = 0x82
key_val = 0x3
key_val = 0x83
key_val = 0x4
key_val = 0x84
key_val = 0x2
key_val = 0x2
key_val = 0x82
key_val = 0x1
key_val = 0x81
key_val = 0x2
key_val = 0x82
key_val = 0x2
key_val = 0x82
key_val = 0x4
key_val = 0x4
key_val = 0x4
key_val = 0x84
测试步骤2
[WJ2440]# ./third_test &
[WJ2440]# top
Mem: 10912K used, 49252K free, 0K shrd, 0K buff, 8104K cached
CPU:  0.0% usr  0.7% sys  0.0% nic 99.0% idle  0.0% io  0.1% irq  0.0% sirq
Load average: 0.00 0.05 0.03 1/23 627
  PID  PPID USER     STAT   VSZ %MEM CPU %CPU COMMAND
  627   589 root     R     2092  3.4   0  0.7 top
  589     1 root     S     2092  3.4   0  0.0 -/bin/sh
    1     0 root     S     2088  3.4   0  0.0 init
  590     1 root     S     2088  3.4   0  0.0 /usr/sbin/telnetd -l /bin/login
  587     1 root     S     1508  2.5   0  0.0 EmbedSky_wdg
  626   589 root     S     1428  2.3   0  0.0 ./third_test
  573     2 root     SW<      0  0.0   0  0.0 [rpciod/0]
    5     2 root     SW<      0  0.0   0  0.0 [khelper]
  329     2 root     SW<      0  0.0   0  0.0 [nfsiod]
    2     0 root     SW<      0  0.0   0  0.0 [kthreadd]
    3     2 root     SW<      0  0.0   0  0.0 [ksoftirqd/0]
    4     2 root     SW<      0  0.0   0  0.0 [events/0]
   11     2 root     SW<      0  0.0   0  0.0 [async/mgr]
  237     2 root     SW<      0  0.0   0  0.0 [kblockd/0]
  247     2 root     SW<      0  0.0   0  0.0 [khubd]
  254     2 root     SW<      0  0.0   0  0.0 [kmmcd]
  278     2 root     SW       0  0.0   0  0.0 [pdflush]
  279     2 root     SW       0  0.0   0  0.0 [pdflush]
  280     2 root     SW<      0  0.0   0  0.0 [kswapd0]
  325     2 root     SW<      0  0.0   0  0.0 [aio/0]
可发现,按键没有被按下时,third_test进程是处于睡眠状态的,并且几乎不占用CPU的利用率。 

测试步骤3(如何卸载驱动):

[WJ2440]# lsmod
third_drv 3016 2 - Live 0xbf003000
[WJ2440]# rmmod third_drv    
rmmod: remove 'third_drv': Resource temporarily unavailable
[WJ2440]# kill -9 626
[1]+  Killed                     ./third_test
[WJ2440]# rmmod  third_drv    
rmmod: module 'third_drv' not found
[WJ2440]# lsmod 
[WJ2440]# 

转载自:http://blog.csdn.net/lwj103862095/article/details/17511867




猜你喜欢

转载自blog.csdn.net/xiaoxiaopengbo/article/details/78780239
今日推荐