Linux 中断驱动
相关API函数
request_irq
用于申请中断,但不能在中断上下文或其它禁止睡眠的代码段中使用该函数。
int request_irq(
unsigned int irq,
irq_handler_t handler,
unsigned long flags,
const char *name,
void *dev)
irq:要申请中断的中断号。
handler:中断处理函数,当中断发生以后就会执行此中断处理函数。
flags:中断标志,文件 include/linux/interrupt.h 里面存放了所有的中断标志。
name:中断名字,设置以后可以在/proc/interrupts 文件中看到对应的中断名字。
dev:如果将 flags 设置为 IRQF_SHARED 的话,dev 用来区分不同的中断,一般情况下将dev设置为设备结构体,dev 会传递给中断处理函数 irq_handler_t 的第二个参数。
**返回值:**0:中断申请成功,其他负值:中断申请失败,如果返回-EBUSY 的话表示中断已经被申请了。
部分中断标志:
free_irq
用于释放中断并删除中断处理函数。
void free_irq(
unsigned int irq,
void *dev)
irq:要释放的中断的中断号。
dev:如果中断设置为共享(IRQF_SHARED)的话,此参数用来区分具体的中断。
gpio_to_irq
用于获取 gpio 的中断号
unsigned int irq_of_parse_and_map(struct device_node *dev, int index)
gpio:要获取的 GPIO 编号。
返回值:GPIO 对应的中断号
irq_of_parse_and_map
从设备树的 interupts 属性中提取到对应的设备号
unsigned int irq_of_parse_and_map(struct device_node *dev, int index)
dev:设备节点。
index:索引号,interrupts 属性可能包含多条中断信息,通过 index 指定要获取的信息。
返回值:中断号。
中断处理函数
irqreturn_t (*irq_handler_t) (int, void *)
int:对应的中断号;
void*:一个通用指针
中断使能和失能函数
/* 使能/失能某一个中断 */
void enable_irq(unsigned int irq)
void disable_irq(unsigned int irq)
/* 不等待当前中断处理程序执行完毕,直接禁止某个中断 */
void disable_irq_nosync(unsigned int irq)
/* 使能/失能全局中断 */
local_irq_enable()
local_irq_disable()
/* 用于禁止中断,并且将中断状态保存在 flags 中 */
local_irq_save(flags)
/* 用于恢复中断,将中断到 flags 状态 */
local_irq_restore(flags)
上半部和下半部
上半部:上半部就是中断处理函数,那些处理过程比较快,不会占用很长时间的处理就可以放在上半部完成。
下半部:如果中断处理过程比较耗时,那么就将这些比较耗时的代码提出来,交给下半部去执行,这样中断处理函数就会快进快出。
-
为什么分上半部和下半部?
为了实现中断处理函数的快进快出。 -
哪些代码放在上半部?哪些放在下半部?
① 如果要处理的内容不希望被其他中断打断,那么可以放到上半部。
② 如果要处理的任务对时间敏感,可以放到上半部。
③ 如果要处理的任务与硬件有关,可以放到上半部。
④ 除了上述三点以外的其他任务,优先考虑放到下半部。
下半部机制
- 软中断、tasklet
- 工作队列
设备树里的中断信息节点
位于内核源码的 arch/arm/boot/dts/imx6ull.dtsi 文件里
某个GPIO节点也是可以作为中断控制器,每个IO能作为特定的中断源:
①、#interrupt-cells,指定中断源的信息 cells 个数。
②、interrupt-controller,表示当前节点为中断控制器。
③、interrupts,指定中断号,触发方式等。
④、interrupt-parent,指定父中断,也就是中断控制器。
利用按键产生外部中断
一、修改设备树文件
指定父中断即中断控制器为 gpio1,配置中断源为GPIO1_IO18,中断的触发方式为按下和释放都会触发中断。
修改完后重新编译设备树,并用新的设备树文件启动Linux。
二、驱动代码编写
代码思路:
一旦有按键按下,就会产生一个中断,中断中又会启动定时器,定时器计时10ms后,进入定时器处理函数,判断按键是否按下,并对 releasekey 赋值,接下来判断 releasekey 的值,如果为1则表示一次完整的按键过程已经结束,然后把 keyvalue 的值拷贝到读取缓冲区,让用户读取。
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <linux/semaphore.h>
#include <linux/timer.h>
#include <linux/of_irq.h>
#include <linux/irq.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#define IRQ_CNT 1
#define IRQ_NAME "irq"
#define KEY0VALUE 0X01 /* KEY0 按键值 */
#define INVAKEY 0XFF /* 无效的按键值 */
#define KEY_NUM 1 /* 按键数量 */
/* 中断 IO 描述结构体 */
struct irq_keydesc
{
int gpio; /* gpio */
int irqnum; /* 中断号 */
unsigned char value; /* 按键对应的键值 */
char name[10]; /* 名字 */
irqreturn_t (*handler)(int, void *); /* 中断服务函数 */
};
/* irq 设备结构体 */
struct irq_dev{
dev_t devid;
struct cdev cdev;
struct class *class;
struct device *device;
struct device_node *nd;
int major;
int minor;
atomic_t keyvalue; /* 有效的按键值 */
atomic_t releasekey; /* 标记是否完成一次释放的按键 */
struct timer_list timer; /* 定义一个定时器 */
struct irq_keydesc irqkeydesc[KEY_NUM]; /* 按键描述数组 */
unsigned char curkeynum; /* 当前的按键号 */
};
struct irq_dev irq; /* irq设备 */
/* 中断服务函数:属于中断处理的上半部 */
static irqreturn_t key0_handler(int irq, void *dev_id)
{
struct irq_dev *dev = (struct irq_dev *)dev_id;
dev->curkeynum = 0;
dev->timer.data = (volatile long)dev_id;
/* 如果产生一个中断信号,则就重新设置定时器的超时时间,即定时器归零 */
mod_timer(&dev->timer, jiffies + msecs_to_jiffies(10));
return IRQ_RETVAL(IRQ_HANDLED);
}
/* 定时器处理函数,作为按键的消抖延时函数:属于中断处理的下半部 */
void timer_function(unsigned long arg)
{
unsigned char value;
unsigned char num;
struct irq_keydesc *keydesc;
struct irq_dev *dev = (struct irq_dev *)arg;
num = dev->curkeynum;
keydesc = &dev->irqkeydesc[num];
value = gpio_get_value(keydesc->gpio);
if(value == 0){ /* 按下按键 */
atomic_set(&dev->keyvalue, keydesc->value);
}
else{ /* 按键松开 */
atomic_set(&dev->keyvalue, 0x80 | keydesc->value);
atomic_set(&dev->releasekey, 1); /* 标记松开按键 */
}
}
/* 按键 IO 初始化 */
static int key_io_init(void)
{
unsigned char i = 0;
int ret = 0;
/* 获取 key 节点 */
irq.nd = of_find_node_by_path("/key");
/* 如果找不到节点,就返回错误信息 */
if(irq.nd == NULL)
{
printk("key node not find!\r\n");
return -EINVAL;
}
/* 如果找到了节点,就返回成功找到的提示信息 */
else
{
printk("key node find!\r\n");
}
/* 提取 GPIO */
for ( i = 0; i < KEY_NUM; i++)
{
irq.irqkeydesc[i].gpio = of_get_named_gpio(irq.nd, "key-gpio", i);
if(irq.irqkeydesc[i].gpio < 0)
{
printk("can't get key%d\r\n",i);
}
}
/* 初始化 key 所使用的 IO,并且设置成中断模式 */
/* 轮流初始化所有的按键,包括申请 IO、设置 IO 为输入模式、从设备树中获取 IO 的中断号等等 */
for ( i = 0; i < KEY_NUM; i++)
{
/* 获取按键名 */
memset(irq.irqkeydesc[i].name, i, sizeof(irq.irqkeydesc[i].name));
/* 将按键名打印出来 */
sprintf(irq.irqkeydesc[i].name, "KEY%d", i);
/* 申请一个IO */
gpio_request(irq.irqkeydesc[i].gpio, irq.irqkeydesc[i].name);
/* 配置IO方向为输入模式 */
gpio_direction_input(irq.irqkeydesc[i].gpio);
/* 获取中断号 */
irq.irqkeydesc[i].irqnum = irq_of_parse_and_map(irq.nd, i);
/* 打印出按键编号和中断编号 */
printk("key%d:gpio=%d, irqnum=%d\r\n",i,
irq.irqkeydesc[i].gpio, irq.irqkeydesc[i].irqnum);
}
/* 申请中断 */
irq.irqkeydesc[0].handler = key0_handler;
irq.irqkeydesc[0].value = KEY0VALUE;
/* 循环给每个按键申请中断 */
for ( i = 0; i < KEY_NUM; i++)
{
ret = request_irq(
irq.irqkeydesc[i].irqnum, irq.irqkeydesc[i].handler,
IRQF_TRIGGER_FALLING|IRQF_TRIGGER_RISING,
irq.irqkeydesc[i].name, &irq);
/* 如果申请失败,就返回错误信息 */
if(ret < 0){
printk("irq %d request failed!\r\n", irq.irqkeydesc[i].irqnum);
return -EFAULT;
}
}
/* 创建定时器 */
init_timer(&irq.timer);
/* 配置定时器服务函数 */
irq.timer.function = timer_function;
return 0;
}
static int irq_open(struct inode *inode, struct file *filp)
{
filp->private_data = &irq;
return 0;
}
static ssize_t irq_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
int ret = 0;
unsigned char keyvalue = 0;
unsigned char releasekey = 0;
struct irq_dev *dev = (struct irq_dev*)filp->private_data;
keyvalue = atomic_read(&dev->keyvalue);
releasekey = atomic_read(&dev->releasekey);
/* 有按键按下,就会产生一个中断,中断中又会启动定时器,定时器计时10ms后,进入定时器处理函数,判断按键 的是否按下,并对 releasekey 赋值,接下来判断 releasekey 的值,如果为1则表示一次完整的按键过程 已经结束,然后把 keyvalue 的值拷贝到读取缓冲区,让用户读取。 */
if(releasekey)
{
if(keyvalue & 0x80)
{
keyvalue &= ~0x80;
ret = copy_to_user(buf, &keyvalue, sizeof(keyvalue));
}
else
{
goto data_error;
}
atomic_set(&dev->releasekey, 0); /* 按下标志清零 */
}
else
{
goto data_error;
}
return 0;
data_error:
return -EINVAL;
}
/* 设备操作函数 */
static struct file_operations irq_fops =
{
.owner = THIS_MODULE,
.open = irq_open,
.read = irq_read,
};
static int __init user_irq_init(void)
{
/* 注册字符驱动设备 */
if(irq.major)
{
irq.devid = MKDEV(irq.major, 0);
register_chrdev_region(irq.devid, IRQ_CNT, IRQ_NAME);
}
else
{
alloc_chrdev_region(&irq.devid, 0, IRQ_CNT, IRQ_NAME);
irq.major = MAJOR(irq.devid);
irq.minor = MINOR(irq.devid);
}
cdev_init(&irq.cdev, &irq_fops);
cdev_add(&irq.cdev, irq.devid, IRQ_CNT);
irq.class = class_create(THIS_MODULE, IRQ_NAME);
if (IS_ERR(irq.class))
{
return PTR_ERR(irq.class);
}
irq.device = device_create(irq.class, NULL, irq.devid, NULL, IRQ_NAME);
if (IS_ERR(irq.device))
{
return PTR_ERR(irq.device);
}
/* 初始化原子变量 */
atomic_set(&irq.keyvalue, INVAKEY);
atomic_set(&irq.releasekey, 0);
/* 初始化按键 */
key_io_init();
return 0;
}
static void __exit user_irq_exit(void)
{
unsigned int i = 0;
del_timer_sync(&irq.timer);
for ( i = 0; i < KEY_NUM; i++)
{
free_irq(irq.irqkeydesc[i].irqnum, &irq);
}
cdev_del(&irq.cdev);
unregister_chrdev_region(irq.devid, IRQ_CNT);
device_destroy(irq.class, irq.devid);
class_destroy(irq.class);
}
module_init(user_irq_init);
module_exit(user_irq_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Swiler");
三、应用代码
#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"
#include "linux/ioctl.h"
/* argv[]:具体的参数内容,字符串形式
* ./KeyAPP <filename>
* ./KeyAPP /dev/key
*/
int main(int argc, char *argv[])
{
int fd;
int ret = 0;
char *filename;
unsigned char data;
if(argc != 2)
{
printf("Error Usage!\r\n");
return -1;
}
filename = argv[1];
fd = open(filename, O_RDWR);
if (fd < 0)
{
printf("file %s open failed!\r\n", argv[1]);
return -1;
}
while (1)
{
ret = read(fd, &data, sizeof(data));
if(ret < 0)
{
/* 数据读取错误或者无效 */
}
else/* 数据读取正确 */
{
if(data) /* 读取到数据 */
printf("KEY0 Press, value = %#X\r\n", data);
}
}
ret = close(fd);
if (fd < 0)
{
printf("file %s close failed!\r\n", argv[1]);
return -1;
}
return 0;
}