一、linux中断简介
1.cortex-A7中断系统简介
- 中断向量表
| 中断类型 | 中断描述 |
|---|---|
| 复位中断(Rest) | CPU 复位以后就会进入复位中断,我们可以在复位中断服务函数里面做一些初始化工作,比如初始化 SP 指针、 DDR 等等。 |
| 未定义指令中断(Undefined Instruction) | 如果指令不能识别的话就会产生此中断。 |
| 软中断(Software Interrupt,SWI) | 由 SWI 指令引起的中断, Linux 的系统调用会用 SWI指令来引起软中断,通过软中断来陷入到内核空间。 |
| 指令预取中止中断(Prefetch Abort) | 预取指令的出错的时候会产生此中断。 |
| 数据访问中止中断(Data Abort) | 访问数据出错的时候会产生此中断。 |
| 未使用(Not Used) | |
| IRQ 中断(IRQ Interrupt) | 芯片内部的外设中断都会引起此中断的发生。 |
| FIQ 中断(FIQ Interrupt) | 快速中断,如果需要快速处理中断的话就可以使用此中。 |
- GIC控制器
GIC 是 ARM 公司给 Cortex-A/R内核提供的一个中断控制器,类似Cortex-M 内核中的NVIC。

图 17.1.3.2 中左侧部分就是中断源,中间部分就是GIC控制器,最右侧就是中断控制器向处理器内核发送中断信息。我们重点要看的肯定是中间的 GIC 部分,GIC 将众多的中断源分为分为三类:
①、 SPI(Shared Peripheral Interrupt),共享中断,顾名思义,所有 Core 共享的中断,这个是最常见的,那些外部中断都属于 SPI 中断(注意!不是 SPI 总线那个中断) 。比如按键中断、串口中断等等,这些中断所有的 Core 都可以处理,不限定特定 Core。
②、 PPI(Private Peripheral Interrupt),私有中断,我们说了 GIC 是支持多核的,每个核肯定有自己独有的中断。这些独有的中断肯定是要指定的核心处理,因此这些中断就叫做私有中断。
③、 SGI(Software-generatedInterrupt),软件中断,由软件触发引起的中断,通过向寄存器GICD_SGIR 写入数据来触发,系统会使用 SGI 中断来完成多核之间的通信。
- 中断ID
中断源有很多,为了区分这些不同的中断源肯定要给他们分配一个唯一 ID,这些 ID 就是中断 ID。
ID0~ID15:这 16 个 ID 分配给 SGI。
ID16~ID31:这 16 个 ID 分配给 PPI。
ID32~ID1019:这 988 个 ID 分配给 SPI,像 GPIO 中断、串口中断等这些外部中断至于具体到某个 ID 对应哪个中断那就由半导体厂商根据实际情况去定义了。
2.linux中断API函数
| 函数 | 描述 |
|---|---|
| int request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags, const char *name, void *dev) | 申请中断 0 |
| void free_irq(unsigned int irq, void *dev) | 释放中断 |
| void enable_irq(unsigned int irq) | 使能中断 |
| void enable_irq(unsigned int irq) | 禁止中断(要等待当前函数执行完) |
| void disable_irq_nosync(unsigned int irq) | 禁止中断(不用等待) |
| local_irq_save(flags) | 关闭中断系统并保存中断 |
| local_irq_restore(flags) | 恢复中断系统 |
| irqreturn_t (*irq_handler_t) (int, void *) | 中断处理函数 |
| 标志 | 描述 |
|---|---|
| IRQF_SHARED | 多个设备共享一个中断线,共享的所有中断都必须指定此标志。如果使用共享中断的话,request_irq函数的dev参数就是唯一区分他们的标志 |
| IRQF_ONESHOT | 单次中断,中断执行一次就结束。 |
| IRQF_TRIGGER_NONE | 无触发 |
| IRQF_TRIGGER_RISING | 上升沿触发 |
| IRQF_TRIGGER_FALLING | 下降沿触发 |
| IRQF_TRIGGER_HIGH | 高电平触发 |
| IRQF_TRIGGER_LOW | 低电平触发 |
3.上半部与下半部
上半部:上半部就是中断处理函数,那些处理过程比较快,不会占用很长时间的处理就可以放在上半部完成。
下半部:如果中断处理过程比较耗时,那么就将这些比较耗时的代码提出来,交给下半部去执行,这样中断处理函数就会快进快出。
- 下半部执行机制(tasklet)
/* tasklet_struct原型 */
struct tasklet_struct
{
struct tasklet_struct *next; /* 下一个 tasklet */
unsigned long state; /* tasklet 状态 */
atomic_t count; /* 计数器,记录对 tasklet 的引用数 */
void (*func)(unsigned long); /* tasklet 执行的函数 */
unsigned long data; /* 函数 func 的参数 */
};
/* 对应初始化函数 */
void tasklet_init(struct tasklet_struct *t, func, unsigned long data);
DECLARE_TASKLET(name, func, data)
/* 定义 taselet */
struct tasklet_struct testtasklet;
/* tasklet 处理函数 */
void testtasklet_func(unsigned long data)
{
/* tasklet 具体处理内容 */
}
/* 中断处理函数 */
irqreturn_t test_handler(int irq, void *dev_id)
{
......
/* 调度 tasklet */
tasklet_schedule(&testtasklet);
......
}
/* 驱动入口函数 */
static int __init xxxx_init(void)
{
......
/* 初始化 tasklet */
tasklet_init(&testtasklet, testtasklet_func, data);
/* 注册中断处理函数 */
request_irq(xxx_irq, test_handler, 0, "xxx", &xxx_dev);
......
- 工作队列执行机制
/* 定义工作(work) */
struct work_struct testwork;
/* work 处理函数 */
void testwork_func_t(struct work_struct *work);
{
/* work 具体处理内容 */
}
/* 中断处理函数 */
irqreturn_t test_handler(int irq, void *dev_id)
{
......
/* 调度 work */
schedule_work(&testwork);
......
}
/* 驱动入口函数 */
static int __init xxxx_init(void)
{
......
/* 初始化 work */
INIT_WORK(&testwork, testwork_func_t);
/* 注册中断处理函数 */
request_irq(xxx_irq, test_handler, 0, "xxx", &xxx_dev);
}
二、按键驱动中断实例
- 设备树内中断节点的属性
- interrupt-controller:一个空的属性定义(就是仅仅列出了该字符串,见下面),该节点作为一个接收中断信号的设备。
- #interrupt-cells:这是一个中断控制器节点的属性。它声明了该中断控制器的中断指示符中cell的个数(类似于#address-cells和#size-cells)。
- interrupt-parent:这是一个设备节点的属性,包含一个指向该设备连接的中断控制器的phandle。那些没有interrupt-parent的节点则从它们的父节点中继承该属性。
- interrupts:一个设备节点属性,包含一个中断指示符的列表,对应于该设备上的每个中断输出信号。
- 关于interupts属性与interrupt-cellss属性的配合
#1.两个参数
interrupt-parent = <&gpio2>; #表示中断控制器是GPIO2
interrupts = <5 1>; 使用GPIO2的5号引脚, 上升沿触发.
# 1.上升沿触发; 2.下降沿触发; 4.高电平触发; 8.低电平触发;
#2.三个参数
interrupts = <GIC_SPI 66 1>;
#第一个: 中断类型
#第二个:中端号
#第三个:出发类型
- API函数
/* 从interupts 属性中提取到对应的中端号 */
unsigned int irq_of_parse_and_map(struct device_node *dev, int index)
/* 获取 gpio 对应的中断号 */
int gpio_to_irq(unsigned int gpio)
- 实际使用示例
key {
#address-cells = <1>;
#size-cells = <1>;
compatible = "alpha,key";
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_key>;
key-gpio = <&gpio1 18 GPIO_ACTIVE_LOW>; /* KEY0 */
interrupt-parent = <&gpio1>; /* 使用gpio1中断控制器 */
interrupts = <18 IRQ_TYPE_EDGE_BOTH>; /* 使用18号引脚,上升下降沿触发 */
status = "okay";
};
- 业务逻辑:完成按下并松开按键一个循环,输出按键值。(用定时器进行消抖)
#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 IMX6UIRQ_CNT 1 /* 设备号个数 */
#define IMX6UIRQ_NAME "imx6uirq" /* 名字 */
#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 *); /* 中断服务函数 */
};
/* imx6uirq设备结构体 */
struct imx6uirq_dev{
dev_t devid; /* 设备号 */
struct cdev cdev; /* cdev */
struct class *class; /* 类 */
struct device *device; /* 设备 */
int major; /* 主设备号 */
int minor; /* 次设备号 */
struct device_node *nd; /* 设备节点 */
atomic_t keyvalue; /* 有效的按键键值 */
atomic_t releasekey; /* 标记是否完成一次完成的按键,包括按下和释放 */
struct timer_list timer;/* 定义一个定时器*/
struct irq_keydesc irqkeydesc[KEY_NUM]; /* 按键描述数组 */
unsigned char curkeynum; /* 当前的按键号 */
};
struct imx6uirq_dev imx6uirq; /* irq设备 */
/* @description : 中断服务函数,开启定时器,延时10ms,
* 定时器用于按键消抖。
* @param - irq : 中断号
* @param - dev_id : 设备结构。
* @return : 中断执行结果
*/
static irqreturn_t key0_handler(int irq, void *dev_id)
{
struct imx6uirq_dev *dev = (struct imx6uirq_dev *)dev_id;
dev->curkeynum = 0;
dev->timer.data = (volatile long)dev_id;
mod_timer(&dev->timer, jiffies + msecs_to_jiffies(10)); /* 10ms定时 */
return IRQ_RETVAL(IRQ_HANDLED);
}
/* @description : 定时器服务函数,用于按键消抖,定时器到了以后
* 再次读取按键值,如果按键还是处于按下状态就表示按键有效。
* @param - arg : 设备结构变量
* @return : 无
*/
void timer_function(unsigned long arg)
{
unsigned char value;
unsigned char num;
struct irq_keydesc *keydesc;
struct imx6uirq_dev *dev = (struct imx6uirq_dev *)arg;
num = dev->curkeynum;
keydesc = &dev->irqkeydesc[num];
value = gpio_get_value(keydesc->gpio); /* 读取IO值 */
if(value == 0){ /* 按下按键 */
atomic_set(&dev->keyvalue, keydesc->value);
}
else{ /* 按键松开 */
atomic_set(&dev->keyvalue, 0x80 | keydesc->value);
atomic_set(&dev->releasekey, 1); /* 标记松开按键,即完成一次完整的按键过程 */
}
}
/*
* @description : 按键IO初始化
* @param : 无
* @return : 无
*/
static int keyio_init(void)
{
unsigned char i = 0;
int ret = 0;
imx6uirq.nd = of_find_node_by_path("/key");
if (imx6uirq.nd== NULL){
printk("key node not find!\r\n");
return -EINVAL;
}
/* 提取GPIO */
for (i = 0; i < KEY_NUM; i++) {
imx6uirq.irqkeydesc[i].gpio = of_get_named_gpio(imx6uirq.nd ,"key-gpio", i);
if (imx6uirq.irqkeydesc[i].gpio < 0) {
printk("can't get key%d\r\n", i);
}
}
/* 初始化key所使用的IO,并且设置成中断模式 */
for (i = 0; i < KEY_NUM; i++) {
memset(imx6uirq.irqkeydesc[i].name, 0, sizeof(imx6uirq.irqkeydesc[i].name)); /* 缓冲区清零 */
sprintf(imx6uirq.irqkeydesc[i].name, "KEY%d", i); /* 组合名字 */
gpio_request(imx6uirq.irqkeydesc[i].gpio, imx6uirq.irqkeydesc[i].name);
gpio_direction_input(imx6uirq.irqkeydesc[i].gpio);
imx6uirq.irqkeydesc[i].irqnum = irq_of_parse_and_map(imx6uirq.nd, i);
#if 0
imx6uirq.irqkeydesc[i].irqnum = gpio_to_irq(imx6uirq.irqkeydesc[i].gpio);
#endif
printk("key%d:gpio=%d, irqnum=%d\r\n",i, imx6uirq.irqkeydesc[i].gpio,
imx6uirq.irqkeydesc[i].irqnum);
}
/* 申请中断 */
imx6uirq.irqkeydesc[0].handler = key0_handler;
imx6uirq.irqkeydesc[0].value = KEY0VALUE;
for (i = 0; i < KEY_NUM; i++) {
ret = request_irq(imx6uirq.irqkeydesc[i].irqnum, imx6uirq.irqkeydesc[i].handler,
IRQF_TRIGGER_FALLING|IRQF_TRIGGER_RISING, imx6uirq.irqkeydesc[i].name, &imx6uirq);
if(ret < 0){
printk("irq %d request failed!\r\n", imx6uirq.irqkeydesc[i].irqnum);
return -EFAULT;
}
}
/* 创建定时器 */
init_timer(&imx6uirq.timer);
imx6uirq.timer.function = timer_function;
return 0;
}
/*
* @description : 打开设备
* @param - inode : 传递给驱动的inode
* @param - filp : 设备文件,file结构体有个叫做private_data的成员变量
* 一般在open的时候将private_data指向设备结构体。
* @return : 0 成功;其他 失败
*/
static int imx6uirq_open(struct inode *inode, struct file *filp)
{
filp->private_data = &imx6uirq; /* 设置私有数据 */
return 0;
}
/*
* @description : 从设备读取数据
* @param - filp : 要打开的设备文件(文件描述符)
* @param - buf : 返回给用户空间的数据缓冲区
* @param - cnt : 要读取的数据长度
* @param - offt : 相对于文件首地址的偏移
* @return : 读取的字节数,如果为负值,表示读取失败
*/
static ssize_t imx6uirq_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 imx6uirq_dev *dev = (struct imx6uirq_dev *)filp->private_data;
keyvalue = atomic_read(&dev->keyvalue);
releasekey = atomic_read(&dev->releasekey);
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 imx6uirq_fops = {
.owner = THIS_MODULE,
.open = imx6uirq_open,
.read = imx6uirq_read,
};
/*
* @description : 驱动入口函数
* @param : 无
* @return : 无
*/
static int __init imx6uirq_init(void)
{
/* 1、构建设备号 */
if (imx6uirq.major) {
imx6uirq.devid = MKDEV(imx6uirq.major, 0);
register_chrdev_region(imx6uirq.devid, IMX6UIRQ_CNT, IMX6UIRQ_NAME);
} else {
alloc_chrdev_region(&imx6uirq.devid, 0, IMX6UIRQ_CNT, IMX6UIRQ_NAME);
imx6uirq.major = MAJOR(imx6uirq.devid);
imx6uirq.minor = MINOR(imx6uirq.devid);
}
/* 2、注册字符设备 */
cdev_init(&imx6uirq.cdev, &imx6uirq_fops);
cdev_add(&imx6uirq.cdev, imx6uirq.devid, IMX6UIRQ_CNT);
/* 3、创建类 */
imx6uirq.class = class_create(THIS_MODULE, IMX6UIRQ_NAME);
if (IS_ERR(imx6uirq.class)) {
return PTR_ERR(imx6uirq.class);
}
/* 4、创建设备 */
imx6uirq.device = device_create(imx6uirq.class, NULL, imx6uirq.devid, NULL, IMX6UIRQ_NAME);
if (IS_ERR(imx6uirq.device)) {
return PTR_ERR(imx6uirq.device);
}
/* 5、初始化按键 */
atomic_set(&imx6uirq.keyvalue, INVAKEY);
atomic_set(&imx6uirq.releasekey, 0);
keyio_init();
return 0;
}
/*
* @description : 驱动出口函数
* @param : 无
* @return : 无
*/
static void __exit imx6uirq_exit(void)
{
unsigned int i = 0;
/* 删除定时器 */
del_timer_sync(&imx6uirq.timer); /* 删除定时器 */
/* 释放中断 */
for (i = 0; i < KEY_NUM; i++) {
free_irq(imx6uirq.irqkeydesc[i].irqnum, &imx6uirq);
}
cdev_del(&imx6uirq.cdev);
unregister_chrdev_region(imx6uirq.devid, IMX6UIRQ_CNT);
device_destroy(imx6uirq.class, imx6uirq.devid);
class_destroy(imx6uirq.class);
printk("\r\n");
}
module_init(imx6uirq_init);
module_exit(imx6uirq_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("ht");
三、测试
#加载后
cat /proc/interrupts