一、字符设备驱动2-中断方式按键

按键驱动:中断方式

中断属于异常的一种。异常,就是可以打断CPU正常运行流程,转而去处理异常事件,然后再继续执行被打断的程序。

异常向量的代码很简单,就是一些跳转指令。发生异常时,CPU自动执行这些指令,跳转去执行更复杂的代码。

Arm架构的cpu异常向量基址可以是0x00000000,也可以是0xffff0000linux内核使用后者。这个地址并不代表实际的内存,是虚拟地址。当建立了虚拟地址与物理地址的映射后,需要将异常向量的入口拷贝到0xffff0000

这个过程是在trap_init()中完成的。以JZ2440 Linux2.6为例,异常向量将被复制到0xffff0000处,跳转的目的代码将被复制到0xffff0000+0x200处。

这样,一个异常发生后,执行流程如下:

1. CPU自动跳转到异常向量入口处(即0xffff0000)执行。

2. 而此处的代码是一些跳转指令(注意这些指令都加有一个偏移值,因为异常向量已经重新定位了,若以链接地址去跳转将无法正确跳转,链接地址+偏移量 才是正确的中断服务程序),这样CPU又跳转到别的地方运行了。

3. 这个新地址的代码要做的事情就是计算返回地址、保存现场,然后进入管理模式,最后根据被中断的工作模式再次跳转。再次跳转到哪里去?看源码:在这个新地址处是一个宏,它定义了上述的计算返回地址等的工作,在这个宏的后边紧跟着的是一段常量的定义,而这段常量就指明了接下来要跳转到哪里去。

4. 不同的跳转分支只在它们的入口处稍有差别,后续的处理大体相同,都是调用相应的C函数。以中断处理函数为例,它最终将调用asm_do_IRQ(这是一个c函数)

综上:一个异常的发生,将会有3次跳转。首先,cpu自动跳转到异常向量入口。然后,根据异常向量指令跳转到对应的异常服务函数。最后,经过异常服务函数计算返回地址等的操作后,再次跳转到异常处理函数(此处的差别:一个服务函数,一个是处理函数),然后调用c函数做复杂的处理。

内核中断框架

asm_do_IRQ()

1. 根据传入的中断号irq在全局数组irq_desc[]中找到以irq为下标的项。

2. 调用irq_desc[irq].handle_irq

3. handle_irq会使用chip成员中的函数来设置硬件,如清中断、禁止中断、重新使能中断等。

4. handle_irq逐个调用用户在action链表中注册的处理函数。

由此可见,用户注册中断时就是构造action链表;用户卸载中断就是从action链表中去除不需要的项。

request_irq()

首先使用传入的参数构造一个irqaction结构,然后调用setup_irq函数将它链入链表。

setup_irq()实现的主要功能:

1. 将新建的irqaction结构链入irq_desc[irq]结构中的action链表中。这有2中情况:

a. 如果action链表为空,则直接链入

b. 否则先判断新建的irqaction结构和链表中的irqaction结构所表示的中断类型是否一致:即是否都声明为“可共享的”(IRQF_SHARED)、是否都使用相同的触发方式(电平、边沿、极性),如果一致则将新建的irqaction结构链入。

2. 设置irq_desc[irq]结构中chip成员的还没有设置的指针,让它们指向一些默认函数。

3. 设置中断触发方式。由request_irq()传入的irqflags表明是:高电平触发、低电平触发、上升沿还是下降沿,然后调用irq_desc[irq]结构中的chip->set_type函数来设置。注意,若链表非空,表示之前已经设置过了,此处不再设置。

4. 启动中断。

须知:执行request_irq()后,中断就可以发生并能够处理了。

free_irq()

1. 根据irqdev_idaction链表中找到表项,将它移除。

2. 如果它是唯一的表项,还要调用irq_desc[irq].chip->shutdownirq_desc[irq].chip->disable来关闭中断。


程序实例:

在读键值函数中使用:

wait_event_interruptible(buttons_waitq, evpress);

在中断处理函数中使用: 

evpress = 1;  
wake_up_interruptible(&buttons_waitq); 

这样,就可以在没有按键按下时应用程序休眠,有按键按下时立即唤醒应用程序。

需要注意的几点:

1. 如何确定外部中断号?

   调用gpio_to_irq(NUC970_PI0) 即可返回外部中断号,即request_irq需要的第一个参数值。

2. request_irq()会通过全局数组irq_desc[]对应项中的chip自动配置相应的引脚,故本驱动中不涉及对GPIO寄存器的操作。

3. open函数中注册中断,那么相应的要在release函数中释放中断,应用程序close(fd)时,会调用release。实测,不可以在出口函数中释放,会报错:释放无效的中断。

源码

驱动:

/*
 * 引脚:PI0,1,2
 */

#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/platform_device.h>
#include <linux/gpio.h>
#include <linux/leds.h>
#include <linux/of_platform.h>
#include <linux/of_gpio.h>
#include <linux/slab.h>
#include <linux/workqueue.h>
#include <linux/module.h>
#include <linux/pinctrl/consumer.h>
#include <linux/err.h>
#include <linux/fs.h>
#include <linux/kdev_t.h>
#include <linux/uaccess.h>
#include <linux/interrupt.h>
#include <asm/io.h>
#include <linux/wait.h>
#include <linux/sched.h>

struct pin_desc{
	int pin;
	int val;
};

/*
 * 按下时,返回:0x81, 0x82, 0x83
 * 
 */

static struct pin_desc pins_desc[3] = {
	{NUC970_PI0, 0x1},
	{NUC970_PI1, 0x2},
	{NUC970_PI2, 0x3},
};

static unsigned char val;
static DECLARE_WAIT_QUEUE_HEAD(buttons_waitq);
int evpress = 0;

static irqreturn_t buttons_handler(int irq, void *dev_id)
{
	struct pin_desc *pin = (struct pin_desc *)dev_id;
	//int res;

	//res = gpio_get_value(pin->pin);
	
	val = 0x80 | pin->val;

	evpress = 1;
	wake_up_interruptible(&buttons_waitq);
	
	return IRQ_HANDLED;
}

static int buttons_open(struct inode *inode, struct file *filp)
{
	// request_irq会自动设置引脚,此处不再配置

	request_irq(gpio_to_irq(pins_desc[0].pin), buttons_handler, IRQF_TRIGGER_FALLING, "S1", &pins_desc[0]);
	request_irq(gpio_to_irq(pins_desc[1].pin), buttons_handler, IRQF_TRIGGER_FALLING, "S2", &pins_desc[1]);
	request_irq(gpio_to_irq(pins_desc[2].pin), buttons_handler, IRQF_TRIGGER_FALLING, "S3", &pins_desc[2]);
	
	return 0;
}

static ssize_t buttons_read (struct file *filp, char __user *buf, size_t count, loff_t *ppos)
{
	if (count != 1)
	{
		return -EINVAL;
	}

	wait_event_interruptible(buttons_waitq, evpress);
	evpress = 0;

	copy_to_user(buf, &val, 1);
	
	return 1;
}

int buttons_release (struct inode *inode, struct file *filp)
{
	free_irq(gpio_to_irq(pins_desc[0].pin), &pins_desc[0]);
	free_irq(gpio_to_irq(pins_desc[1].pin), &pins_desc[1]);
	free_irq(gpio_to_irq(pins_desc[2].pin), &pins_desc[2]);
	
	return 0;
}



static struct file_operations buttons_fops = {
	.owner   = THIS_MODULE,
	.open    = buttons_open,
	.read    = buttons_read,
	.release = buttons_release,
};

static int major;
static struct class *buttons_class;
static struct device *button_device;

static int buttons_init(void)
{
	major = register_chrdev(0, "buttons", &buttons_fops);
	
	buttons_class = class_create(THIS_MODULE, "buttons");
	button_device = device_create(buttons_class, NULL, MKDEV(major, 0), NULL, "buttons");

	return 0;
}

static void buttons_exit(void)
{
	device_destroy(buttons_class, MKDEV(major, 0));
	class_destroy(buttons_class);

	unregister_chrdev(major, "buttons");
}

module_init(buttons_init);
module_exit(buttons_exit);
MODULE_LICENSE("GPL");

测试程序:

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

int main(void)
{
	int fd;
	unsigned char key_val;

	fd = open("/dev/buttons", O_RDONLY);
	if (fd < 0)
	{
		printf("Can't open /dev/buttons\n");
		return -1;
	}

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


猜你喜欢

转载自blog.csdn.net/zhen7620/article/details/80394474
今日推荐