Linux中断-简单中断,以GPIO中断为例
Linux中断基础概念
中断上下文
Linux内核的中断回调可以有两部分,即上下文。当中断比较简单时,可以只有上文。
一般中断上文是指由中断产生的回调函数直接执行的部分;中断下文在上文中启用调度,再由内核调度。
中断上文:处理尽可能少的任务,特点是响应速度快
中断下文:处理耗时任务,可以被新的中断打断
中断嵌套
Linux中断现在不能嵌套,之前可以
中断相关的函数及命令
获取中断号
如果是有设备树的内核,一般通过节点的interrupt-parent和interrupt属性来描述中断
对GPIO来说,GPIO的节点可以作为中断控制器,一般由BSP厂家编写
<linux/of_irq.h>
//从设备树的设备节点中获取中断号
unsigned int irq_of_parse_and_map(struct device_node *dev, int index);
//参数:dev设备节点,index索引(节点中interrupts属性可能包含多条中断信息,通过index确认)
//返回值:中断号
//如果是GPIO的话,可以不从设备树中获取
int gpio_to_irq(unsigned int gpio);
//参数:gpio的编号
//返回值:gpio对应的中断号
申请中断
申请中断的函数
int request_irq(unsigned int irq,
irq_handler_t handler,
unsigned long flags,
const char *name,
void *dev);
//参数:
//irq:要申请中断的中断号
//handler:中断处理函数
//flags:中断标志
//name:中断名字,可在/proc/interrupts文件中看到对应的名字
//dev:flags为IRQF_SHARED时,dev用来区分不同的中断。一般将dev设置为设备结构体,传递给irq_handler_t的第二个参数
//返回值:0申请成功,其他负值申请失败;如果返回-EBUSY标识已经被申请
中断标志(申请中断函数的flags参数)定义在 include/linux/interrupt.h中
常见的中断标志:
标志 | 功能 |
---|---|
IRQF_SHARED | 多个设备共享一个中断线,申请中断函数的dev参数是区分它们的唯一标志 |
IRQF_ONESHOT | 单次中断,中断执行一次就结束 |
IRQF_TRIGGER_NONE | 无触发 |
IRQF_TRIGGER_RISING | 上升沿触发 |
IRQF_TRIGGER_FALLING | 下降沿触发 |
IRQF_TRIGGER_HIGH | 高电平触发 |
IRQF_TRIGGER_LOW | 低电平触发 |
中断处理函数
使用request_irq申请中断的时候需要中断处理函数irq_handler_t来做参数,
这里的irq_handler_t函数可以理解为中断上文的回调函数,发生中断时内核会调用处理函数。
irqretuen_t (*irq_handler_t)(int, void*)
//参数:int型中断号,void*型需要和中断申请函数的dev参数保持一致
//返回值:irqretuen_t类型
enum irqreturn {
IRQ_NONE = (0<<0), //不处理
IRQ_HANDLED = (1<<0), //正常处理
IRQ_WAKE_THREAD = (1<<1), //使用中断下文处理
};
typedef enum irqreturn irqreturn_t;
//irqretuen_t是一个枚举类型,一般中断处理函数会返回第二个数,格式如下
//return ITQ_RETVAL(IRQ_HANDLED)
释放中断
void free_irq(unsigned int irq, void *dev);
//参数:irq要释放的中断号,dev如果设置为IRQ_SHARED的话,此参数来区分具体中断
查看中断是否存在
#查看中断是否存在
cat /proc/interrupts
#查看中断执行次数
cat /proc/irq/xx/spurious
#xx指中断号
中断实验-以GPIO按键中断为例
实验的gpio中断内容很少,所以只使用中断上文
使用讯为的itop-4412开发板,home键,按键按下时进入中断执行打印
设备树修改
home键的gpio是gpx1_1,将之前home键的描述注释掉,添加自己的节点
home_inter {
compatible = "taxue,home_key";
status = "okay";
inter_gpio = <&gpx1 1 GPIO_ACTIVE_LOW>;
};
驱动部分的代码步骤
以平台总线driver的驱动作为框架,在probe函数里执行以下步骤
- 从设备树获取描述GPIO的节点
- 通过节点中的gpio参数,获取gpio编号
- 申请gpio
- 设置gpio方向
- 获取gpio对应的中断号
- 申请中断(中断处理函数)
编写中断处理函数
platform_driver的of_match_table参数要和设备树节点中的compatible参数一致,保证可以匹配
在remove函数里,释放中断、释放GPIO
代码
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/platform_device.h>
#include <linux/ioport.h>
#include <linux/of.h>
#include <linux/of_gpio.h>
#include <linux/of_irq.h>
#include <linux/interrupt.h>
//用于和设备匹配
#define DEVICE_NAME "taxue,home_key"
int home_gpio_idx; //home键的gpio编号
int home_inter_idx; //home键gpio对应的中断号
static irqreturn_t home_interrupt(int irq, void *dev_id) {
printk("%s(%d)\n", __FUNCTION__, __LINE__); //__LINE__,代码所在的行数
printk("HOME KEY HIGH TO LOW!\n");
return IRQ_HANDLED; //返回值,表示正常执行
}
int drProbe(struct platform_device *dev){
int ret;
struct device_node *inter_node;
//根据设备树路径获取节点
inter_node = of_find_node_by_path("/home_inter");
if(inter_node == NULL){
printk("find node failed\n");
return -1;
}
//根据节点的inter_gpio属性,获取gpio编号
home_gpio_idx = of_get_named_gpio( inter_node, "inter_gpio", 0);
//申请gpio
ret = gpio_request(home_gpio_idx,"home");
if( ret != 0){
printk("gpio request failed\n");
return -1;
}
//将gpio设置为输入
gpio_direction_input(home_gpio_idx);
//获取中断号
home_inter_idx = gpio_to_irq(home_gpio_idx);
//申请中断,下降沿触发
ret = request_irq(home_inter_idx, home_interrupt, IRQF_TRIGGER_FALLING, "home_key", dev);
if(ret < 0){
printk("request interrupt failed, IRQ=%d,ret=%d\n", home_inter_idx, ret);
return -1;
}
return 0;
}
int drRemove(struct platform_device *dev){
free_irq(home_inter_idx, dev); //释放中断
gpio_free(home_gpio_idx); //释放gpio
printk("driver remove\n");
return 0;
}
static const struct of_device_id of_interr_match[] = {
{.compatible = DEVICE_NAME},
{},
};
static struct platform_driver pdrv = {
.probe = drProbe,
.remove = drRemove,
.driver = {
.name = DEVICE_NAME,
.owner = THIS_MODULE,
.of_match_table = of_interr_match,
}
};
//模块入口
static int driver_init_interr(void){
int ret=0;
ret = platform_driver_register(&pdrv); //注册平台driver
if(ret < 0){
printk("platform driver regist failed\n");
return -1;
}
return 0;
}
//模块出口
static void driver_exit_interr(void){
platform_driver_unregister(&pdrv); //卸载平台driver
printk("platform driver exit!\n");
}
module_init(driver_init_interr);
module_exit(driver_exit_interr);
MODULE_LICENSE("Dual BSD/GPL");
MODULE_AUTHOR("TAXUE");