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");
posted @ 2021-10-24 19:18  WuYunTaXue  阅读(6787)  评论(0编辑  收藏  举报