字符设备之中断按键

从题目就可以意会到这一节还是关于字符设备的驱动,不过是另一种技巧:中断。这个词一点都不陌生。

一、先来分析今天的重量级函数request_irq(),看看他的函数原型就行了,先不进行深入分析。

int request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,const char *name, void *dev_id);
关键是参数的意义:

①irq:中断号,在irqs.h中定义,每一条中断线上面对应一个中断号,用来在irq_src[]中索引中断描述符
②handler:中断处理函数
③flags:中断触发方式设置,在include/linux/irq.h文件中定义
④name:中断设备名,open中断设备之后可以通过cat proc/interrupts查看
⑤dev_id:是设备id,他是个空类型,可转化成任何类型,值得探讨他的作用,概括为下面两点:

第一:dev_id参数会在发生中断时传递给该中断的服务程序。

每一个设备通常都会有自己特有的结构体,通过“void *dev_id”就可以将其挂接到一个irqaction结构中,给这个结构体中的handler函数使用,主要在共享中断中对判断设备自身是否有中断触发起作用。这时需要回忆一下内核中断处理体系结构。顺便看看下面irqaction结构体成员。

struct irqaction {
	irq_handler_t handler;		//@handler:	interrupt handler function
	unsigned long flags;		//@flags:	flags (see IRQF_* above)
	cpumask_t mask;				//@mask:	no comment as it is useless and about to be removed
	const char *name;			//@name:	name of the device
	void *dev_id;				//@dev_id:	cookie to identify the device
	struct irqaction *next;		//@next:	pointer to the next irqaction for shared interrupts
	int irq;					//@irq:	interrupt number
	struct proc_dir_entry *dir;	//@dir:	pointer to the proc/irq/NN/name entry
	irq_handler_t thread_fn;	//@thread_fn:	interupt handler function for threaded interrupts
	struct task_struct *thread;	//@thread:	thread pointer for threaded interrupts
	unsigned long thread_flags;	//@thread_flags:	flags related to @thread
};

第二:在使用free_irq函数释放dev_id对应的irqaction结构时使用。因为每一个设备dev_id都是独特的,看看free_irq源码就知道了

void free_irq(unsigned int irq, void *dev_id)
{
	kfree(__free_irq(irq, dev_id));
}
static struct irqaction *__free_irq(unsigned int irq, void *dev_id)
{
	struct irq_desc *desc = irq_to_desc(irq);//获取该中断号对应的irq_desc[irq]结构体
	struct irqaction *action, **action_ptr;
	struct task_struct *irqthread;
	unsigned long flags;

	WARN(in_interrupt(), "Trying to free IRQ %d from IRQ context!\n", irq);

	if (!desc)
		return NULL;

	spin_lock_irqsave(&desc->lock, flags);

	/*
	 * There can be multiple actions per IRQ descriptor, find the right
	 * one based on the dev_id:
	 */
	action_ptr = &desc->action;<span style="white-space:pre">	</span>//找到irq中断号对应的irqaction链表
	for (;;) { <span style="white-space:pre">		</span>//循环遍历irqaction链表,比较dev_id成员
		action = *action_ptr;

		if (!action) {
			WARN(1, "Trying to free already-free IRQ %d\n", irq);
			spin_unlock_irqrestore(&desc->lock, flags);

			return NULL;
		}

		if (action->dev_id == dev_id)<span style="white-space:pre">	</span>//判断是否找到相应dev_id设备的irqaction 
			break;
		action_ptr = &action->next;<span style="white-space:pre">	</span>//指向下一个irqaction结构体
	}

	/* Found it - now remove it from the list of entries: */
	*action_ptr = action->next;

	/* If this was the last handler, shut down the IRQ line: */
	if (!desc->action) {
		desc->status |= IRQ_DISABLED;
		if (desc->chip->shutdown)
			desc->chip->shutdown(irq);
		else
			desc->chip->disable(irq);
	}

	irqthread = action->thread;
	action->thread = NULL;

	spin_unlock_irqrestore(&desc->lock, flags);

	unregister_handler_proc(irq, action);

	/* Make sure it's not being used on another CPU: */
	synchronize_irq(irq);

	if (irqthread) {
		if (!test_bit(IRQTF_DIED, &action->thread_flags))
			kthread_stop(irqthread);
		put_task_struct(irqthread);
	}
	return action; <span style="white-space:pre">	</span>//将找到的irqaction结构体成员返回给上一层调用kfree()函数释放掉这个结构体
}
二、今天的字符设备中断按键程序:buttons.c,文中已附代码注释

#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 <mach/hardware.h>
#include <linux/interrupt.h>  //wait_event_interruptible
#include <linux/fs.h>

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

static struct class *buttondev_class;
static struct device *buttons_device;

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

static struct pin_desc pins_desc[4] = {
		{S3C2410_GPF1,0x01}, //S3C2410_GPF1是对GPF1引脚这种“设备”的编号dev_id
		{S3C2410_GPF4,0x02},
		{S3C2410_GPF2,0x03},
		{S3C2410_GPF0,0x04},
}; 
static int ev_press = 0;

static unsigned char key_val;
int major;

/* 中断处理函数 */
static irqreturn_t handle_irq(int irq, void *dev_id)
{
	struct pin_desc *irq_pindesc = (struct pin_desc *)dev_id;//
	unsigned int pinval;
	
	pinval = s3c2410_gpio_getpin(irq_pindesc->pin);//获取按键值:有按键按下返回按键值0
	/* 键值: 按下时, 0x01, 0x02, 0x03, 0x04 */
	/* 键值: 松开时, 0x81, 0x82, 0x83, 0x84 */
	if(pinval)
	{
		/* 松开 */
		key_val = 0x80 | (irq_pindesc->key_val);
	}
	else
	{
		/* 按下 */
		key_val = irq_pindesc->key_val;
	}

	ev_press = 1;	/* 表示中断已经发生 */
	wake_up_interruptible(&button_waitq);   /* 唤醒休眠的进程 */
	return IRQ_HANDLED;
}

static int buttons_dev_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, handle_irq, IRQ_TYPE_EDGE_FALLING, "K1",&pins_desc[0]);
	request_irq(IRQ_EINT4, handle_irq, IRQ_TYPE_EDGE_FALLING, "K2",&pins_desc[1]);
	request_irq(IRQ_EINT2, handle_irq, IRQ_TYPE_EDGE_FALLING, "K3",&pins_desc[2]);
	request_irq(IRQ_EINT0, handle_irq, IRQ_TYPE_EDGE_FALLING, "K4",&pins_desc[3]);
	return 0;
}

static int buttons_dev_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;
}

static ssize_t buttons_dev_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;	
}

/* File operations struct for character device */
static const struct file_operations buttons_dev_fops = {
	.owner		= THIS_MODULE,
	.open		= buttons_dev_open,
	.read		= buttons_dev_read,
	.release    = buttons_dev_close,
};

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

	/* 创建buttondev类 */
	buttondev_class = class_create(THIS_MODULE, "buttondev");

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

	return 0;
}

/* 驱动出口函数 */
static void buttons_dev_exit(void)
{
	unregister_chrdev(major, "buttons_dev");
	device_unregister(buttons_device);<span style="white-space:pre">	</span>//卸载类下的设备
	class_destroy(buttondev_class);	<span style="white-space:pre">	</span>//卸载类
}

/* 模块加载和卸载函数的修饰 */
module_init(buttons_dev_init);  
module_exit(buttons_dev_exit); 

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

三、应用程序:app_irq.c
/* 文件的编译指令是arm-linux-gcc -static -o app_irq app_irq.c */
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>

int main(int argc, char **argv)
{
	int fd;
	unsigned char key_val;
	
	fd = open("/dev/buttons", O_RDWR); //申请外部引脚中断服务
	if (fd < 0)
	{
		printf("can't open!\n");
	}

	while (1)
	{
		read(fd, &key_val, 1); //读出按键值
		printf("key_val = 0x%x\n", key_val);
	}
	
	return 0;
}
实验测试结果:



四、小结:字符设备驱动中断编程框架:

①定义并初始化等待队列头
static DECLARE_WAIT_QUEUE_HEAD(xxx_waitq);
②/驱动入口函数
static int xxx_dev_init(void);
module_init(xxx_dev_init);
③驱动出口函数
static void xxx_dev_exit(void);
module_exit(xxx_dev_exit);
④打开中断设备时注册中断
static int xxx_dev_open(struct inode * inode, struct file * filp)
{

...

request_irq(irq, handle_irq, IRQ_TYPE_XXX, "dev_name",dev_id);//dev_id通常是结构体指针

...

}
⑤关闭中断时释放中断资源
static int xxx_dev_close(struct inode *inode, struct file *file)
{

...

free_irq(irq,dev_id);

...

}
⑥中断处理函数#核心#
static irqreturn_t handle_irq(int irq, void *dev_id)
{

/* (1)通常是读某个寄存器或IO引脚分辨中断源 */

/* (2)进行中断处理 */

/* (3)唤醒休眠的进程--她还在静静地等着你凯旋归来呢 */

wake_up_interruptible(&button_waitq); 

return IRQ_HANDLED;

}

⑦根据需要,如果用户程序需要获取中断发生时的某些信息来个read函数
static ssize_t buttons_dev_read(struct file *file, char __user *user, size_t size,loff_t *ppos)
{

...

wait_event_interruptible(xxx_waitq, ev_flag);//ev_flag=0表示中断未发生,还没有可读资源,进入睡眠

copy_to_user(user, &data, 1);//将数据读到用户空间

ev_flag = 0;// 将ev_flag清零

return 1;

}
⑧遵守的规则
MODULE_AUTHOR("CLBIAO");
MODULE_DESCRIPTION("Just for Demon");
MODULE_LICENSE("GPL");  //遵循GPL协议


















评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值