Linux驱动 | Linux GPIO中断(代码实操,架构mips平台mt7688)

本文详细阐述了Linux中断处理机制,包括irq结构体、中断共享、中断触发方式、中断申请与注销过程,以及GPIO中断应用实例。理解这些关键概念对于嵌入式Linux系统开发至关重要。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Linux中断原理

Linux内核中每个中断irq对应一个struct irq_dest结构体(注意:这里的irq是内核内部irq,不是硬件irq),每个struct irq_desc包含一条struct irqaction链表。

struct irq_desc {
  ......
      struct irqaction    *action;    /* IRQ action list */
  ......
};

irq号相同的的中断源,可以添加到同一条struct irqaction链表中,从而实现对中断的共享。在中断发生时,Linux内核遍历该链表,取出每个struct irqaction,执行其中的中断处理函数。

struct irqaction结构体如下所示:

struct irqaction {
      irq_handler_t       handler;	//指向中断处理函数
      void            *dev_id;	//区别共享中断的不同设备的id
      void __percpu       *percpu_dev_id;
      struct irqaction    *next;		//将共享同一IRQ号的struct irqaction对象链接在一起
      irq_handler_t       thread_fn;
      struct task_struct  *thread;	
      unsigned int        irq;		//IRQ号
      unsigned int        flags;	//中断触发标志位
      unsigned long       thread_flags;
      unsigned long       thread_mask;
      const char      *name;		//用来在/proc/interrupts中显示中断的的拥有者
      struct proc_dir_entry   *dir;
  }

申请中断

申请中断的过程就是把创建相对应的irqaction,链接入对应中断对象irq_desc的action链表中。申请中断的函数为request_irq,接口原型如下:

request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,const char *name, void *dev)

参数意义与struct irqaction中的成员一致。
其中,flags中断触发标志的取值有如下,可以使用位或设置。

IRQF_TRIGGER_RISING 上升沿触发
IRQF_TRIGGER_FALLING 下降沿触发
IRQF_TRIGGER_HIGH 高电平触发
IRQF_TRIGGER_LOW 低电平触发
IRQF_SHARED 实现共享中断必须设置
IRQF_TIMER 定时器```

name成员标志中断的所有者,在/proc/interrupts和/proc/stats中都可以查看。

/proc/interrupts会依次显示irq编号,每个cpu对该irq的处理次数,中断控制器的名字,irq的名字,以及驱动程序注册该irq时使用的名字
在这里插入图片描述
/proc/stat中以intr开头的为内核从启动开始接收到的中断数量。第一个数字229765为所有中断总数。后面开始为从中断号0开始的中断发生次数。
在这里插入图片描述
void * dev : 该成员对共享中断才有用,非共享中断可以传入一个NULL。实现共享中断时,驱动程序可以传入自己的内部结构体指针,在struct irqaction同一链表中,能够用于区分struct irqaction不同节点。内核遍历struct irqaction链表时,会运行所有结点中的中断处理函数,我们在中断处理函数中可以判断是否是本驱动产生的中断,不是的话,返回IRQ_NONE忽略该中断,是的话,返回中断被处理IRQ_HANDLED。

共享中断

共享中断除了在flags参数上设置IRQF_SHARED外,还必须保证flags参数中中断触发方式的一致。在request_irq中有对flags标记进行判断。

if ( !((old->flags & new->flags) & IRQF_SHARED) ||
      ((old->flags ^ new->flags) & IRQF_TRIGGER_MASK) ||
      ((old->flags ^ new->flags) & IRQF_ONESHOT)) {
	old_name = old->name;
	goto mismatch;
}
#define IRQF_TRIGGER_MASK   (IRQF_TRIGGER_HIGH | IRQF_TRIGGER_LOW | \
                   IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING)

!((old->flags & new->flags) & IRQF_SHARED) : 在old_flags和new_flags中有一个没有设置IRQF_SHARED,返回真。
((old->flags ^ new->flags) & IRQF_TRIGGER_MASK) : 该运算很巧妙,若old_flags与new_flags在中断触发方式不一致,返回真。

注销中断

注销中断比较简单,直接调用free_irq,该函数传入irq和dev_id,若是共享中断,dev_id用于从action链表中删除对应的struct irqaction结点。

GPIO中断

需求:按住mt7688的wps按键,触发中断,用printk打印一句话。
首先查看芯片手册,发现WPS按键连接到了WDT_RST_N
在这里插入图片描述
在数据手册查找WDT_RST_N,该pin口对应GPIO#38
在这里插入图片描述
申请中断的代码如下:

irq = gpio_to_irq(WPS_GPIO);
if( (ret=request_irq(irq,my_interrupt_handler,IRQF_TRIGGER_FALLING,"my_irq",NULL)) < 0){
	printk("driver : request_irq failed\n");
	goto end;
}

实验效果如下,当我按下WPS按键,触发了WPS的功能,同时,也触发了中断。
在这里插入图片描述
通过cat /proc/interrupts看到我们注册的驱动:
在这里插入图片描述
完整的代码如下:

#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/gpio.h>

#include <linux/interrupt.h>

MODULE_AUTHOR("Javier Huang");
MODULE_LICENSE("GPL");

#define WPS_GPIO 38

static irqreturn_t my_interrupt_handler(int irq,void *dev_id)
{
    printk("interrupt happened\n");
    return IRQ_HANDLED;
}

static int irq = -1;

static int __init driver_init(void)
{
    int ret = 0;
    irq = gpio_to_irq(WPS_GPIO);
    if( (ret=request_irq(irq,my_interrupt_handler,IRQF_TRIGGER_FALLING,"my_irq",NULL)) < 0){
        printk("driver : request_irq failed\n");
        goto end;
    }
end:
    return ret;
}

static void __exit driver_exit(void)
{
    if(irq>0){
        free_irq(irq,NULL);    
    }
}

module_init(driver_init);
module_exit(driver_exit);
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值