Linux中断简记——IMX6ULL示例

一. IMX6ULL裸机中断

  1. 配置好中断相关外设及初始化GIC中断控制器。
  2. IRQ异常服务函数中,使用GIC控制器cp15协处理器,获取当前发生的中断相关信息。
  3. 跳转到中断处理函数中,执行对应的中断服务函数。
  4. 返回至IRQ异常服务函数,退出至程序被打断处继续执行。

 

二. Linux中断——Linux内核提供了完善的中断框架

  1. 配置好设备节点的中断信息,Linux内核会根据这些信息初始化相关中断
  2. 初始化相关设备,以及加载设备、创建设备节点文件
  3. 从设备树中获取中断号,初始化上半部中断服务函数,快进快出
  4. 初始化下半部tasklet处理,用于处理耗时的中断处理

 

三. 中断处理函数

  1. 中断号:Linux下使用一个int类型表示中断号

  2. request_irq:申请中断函数,可能会引起休眠,申请中断后会自动使能中断

     /* 返回值为0表示申请成功,为负值表示申请失败,-EBUSY表示中断已被申请 */
     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文件定义,如
标志 描述
IRQF_SHARED 多个设备共享一个中断线,共享的所有中断都必须指定此标志。 如果使用共享中断的话,request_irq 函数的dev参数就是唯一 区分他们的标志。如GPIO通常是多组IO共用一个中断号
IRQF_ONESHOT 单次中断,中断执行一次就结束
IRQF_TRIGGER_NONE 无触发
IRQF_TRIGGER_RISING 上升沿触发
  • 参数name,表示中断名字,申请成功可在/proc/interrupts文件看到相应名字。
  • 参数dev,用来区分不同中断,一般设置为设备结构体,该参数会传递给中断服务函数irq_handler_t第二个参数
  1. free_irq:释放中断函数,若中断标志不是共享,则会删除中断服务函数,并且禁止中断

     /* irq为要释放的中断号
     	dev:若为共享中断,该参数用于区分具体的中断 */
     void free_irq(unsigned int irq, void *dev);
    
  2. 中断处理函数

     /* int为中断号,
     	void*参数与request_irq的dev参数相同,用于区分共享中断的不同中断函数	*/
     irqreturn_t (*irq_handler_t) (int, void *);
    

    返回类型为irqreturn_t 类型

    enum irqreturn {
    	IRQ_NONE		= (0 << 0),
    	IRQ_HANDLED 	= (1 << 0),
    	IRQ_WAKE_THREAD = (1 << 1),
    }; 
    typedef enum irqreturn irqreturn_t;
    /* 一般中断函数返回为以下形式 */
    return IRQ_RETVAL(IRQ_HANDLED);
    /* IRQ_RETVAL定义如下 */
    #define IRQ_RETVAL(x) ( (x)!=0)
    
  3. 中断使能与禁止函数

     void enable_irq(unsigned int irq);	//使能中断
     /* 该禁止中断函数需要等到,当前正在执行的中断处理函数执行完才返回 */
     void disable_irq(unsigned int irq);
     /* 该禁止中断函数调用后立即返回,不会等待中断处理函数执行完 */
     void disable_irq_nosync(unsigned int irq);
    
     /* 禁止整个系统的中断,并且保存中断状态至flags */
     void local_irq_save(unsigned long flags);
     /* 恢复整个系统的中断,并恢复中断状态 */
     void local_irq_restore(unsigned long flags);	
    

 

四. 中断处理的上半部和下半部

  1. 为了实现中断处理函数的快进快出,Linux内核提出了将中断分为上半部和下半部的概念

    • 上半部即为中断处理函数,要求快进快出
    • 下半部为比较耗时的处理过程,用于上半部结束后再去处理
  2. Linux内核提供的下半部机制

  • 2.1. 软中断
  • 2.2. tasklet(在软中断之上实现),Linux内核中的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;				//tasklet执行函数的传入参数
      };

2.2.1. tasklet初始化函数

/* 参数t为要初始化的tasklet,func为tasklet执行函数,data为func传入参数 */
void tasklet_init(struct tasklet_struct *t,void (*func)(unsigned long), unsigned long data);
       
/* 在include/linux/interrupt.h文件中,可用宏函数完成定义和初始化tasklet
   name:要定义tasklet的名字;func:tasklet的执行函数;
   data:执行函数func的传入参数	*/
DECLARE_TASKLET(name, func, data);

2.2.2. tasklet调度函数,在上半部中断处理函数中,执行调度函数;tasklet就会在合适的时间去运行。

void tasklet_schedule(struct tasklet_struct *t); //t为要调度的tasklet
  • 2.3. 工作队列:工作队列在进程上下文执行,工作队列将要推后的工作交给一个内核线程去执行;因为工作队列工作在进程上下文,因此工作队列允许睡眠或重新调度

 

五. 设备树中断信息节点

  1. 设备树中,要配置好中断属性信息Linux内核通过读取设备树中的中断属性信息来初始化相关中断。

  2. 中断控制器GIC,绑定文档Documentation/devicetree/bindings/arm/gic.txt;在 .dtsi的根节点下intc节点,即为IMX6ULL的中断控制器节点。

    intc: interrupt-controller@00a01000 {
    	compatible = "arm,cortex-a7-gic";	/*arm驱动厂商,cortex-a7-gic驱动*/
    	#interrupt-cells = <3>;				/*表示interrupt属性的cells大小,此处为3
    					1.中断类型,0为SPI(共享外设中断) or 1为PPI(私有外设中断);	
    					2.中断号,SPI为0~987,PPI为0~15;
    					3.标志:bit[3:0]表示中断触发类型,bit[15:8]为PPI的CPU掩码 */
    	interrupt-controller;				/*表示此节点为中断控制器*/
    	reg = <0x00a01000 0x1000>,
    	      <0x00a02000 0x100>;
    };
    
  3. GPIO中断控制器举例

  • 与中断有关的设备树属性信息
    1. interrupt-cells,指定中断源的信息 cells 个数;
    2. interrupt-controller,表示当前节点为中断控制器
    3. interrupt-parent,指定父中断,也就是中断控制器;
    4. interrupts,表示中断信息,如中断号,触发方式等;
  • GPIO5控制器设备节点
gpio5: gpio@020ac000 {
    	compatible = "fsl,imx6ul-gpio", "fsl,imx35-gpio";
    	reg = <0x020ac000 0x4000>;
        /* 中断源信息,中断类型是SPI,中断号是74/75,触发电平是高电平 */
    	interrupts = <GIC_SPI 74 IRQ_TYPE_LEVEL_HIGH>,	
    				 <GIC_SPI 75 IRQ_TYPE_LEVEL_HIGH>;
    	gpio-controller;		
    	#gpio-cells = <2>;		
    	interrupt-controller;		/* 表示此节点为中断控制器 */
    	#interrupt-cells = <2>;		/* 将interrupt属性的cells大小,改为2 */
    };   
  • fxls8471设备节点,开启中断,使用gpio5中断控制器
    fxls8471@1e {
    	compatible = "fsl,fxls8471";
    	reg = <0x1e>;
    	position = <0>;
    	interrupt-parent = <&gpio5>; /*该属性为父中断,表明使用的中断控制器为gpio5   */
    	interrupts = <0 8>;			/*该属性表示中断信息,0表示IO0,8表示低电平触发 */
        						   /* 触发方式:1上升沿,2下降沿,4高电平,8低电平 */
        						  /* 定义在include/linux/irq.h文件中 		   */
    };
  1. 获取中断号
  • 设备节点interupts属性获取;
/* dev表示设备节点;index表示索引值;返回GPIO中断号 */
unsigned int irq_of_parse_and_map(struct device_node*dev, int index);
  • GPIO编号中,获取GPIO对应的中断号;
     int gpio_to_irq(unsigned int gpio);	//传入GPIO编号,传出对应GPIO中断号

 
以上是我在学习过程中的总结,不当之处请在评论区指出。

你可能感兴趣的:(Linux驱动)