当前位置:首页>Linux>真真没啥技术的我学Linux驱动 |扒一扒中断的底裤

真真没啥技术的我学Linux驱动 |扒一扒中断的底裤

  • 2026-08-27 08:46:18
真真没啥技术的我学Linux驱动 |扒一扒中断的底裤

中断在教科书里总是一副温文尔雅的模样——“优雅的异步通知”、“高效的事件响应”,但实际它是怎么实现的呢?

上一篇文章真真没啥技术的我学Linux驱动 |多副面孔的CPU和它的寄存器侍卫在Cortex-A7内核架构下,介绍了ARM的运行模式和核心寄存器,并在片尾以

'''

......

一声急报,从远处响起。。。。。。

“报——!北境有烽火骤起!”

......

'''片段,引出本篇内容。

本篇属于第一部分 万事开头难之入门篇

扒一扒中断的底裤

 这是一个系列文章,系列引子在

真真没啥技术的我学Linux驱动 | 引子

在 i.MX.6ULL 这座城里,GPIO外设要是急眼了,它绝不会客气地敲门。但你以为它能直接冲进皇宫揪住 CPU 皇帝的领子?想多了。 在它和 CPU 之间,横着一个掌握生杀大权的“大内总管”——GIC(通用中断控制器,就跟STM32 Cortex-M中NVIC一样,只不过这个更复杂)。

如果没有 GIC,这场“踹门戏码”根本演不下去:
1、外设在城外撒野:不管是 GPIO 按键被按下了,还是 UART 收到货了,它们只会没头没脑地拉高一根线的电平(电平触发)或者突然抽一下风(边沿触发)。
2、GIC 暴力初筛:这时,GIC 总管冷冷地看一眼:“哟,来了 100 多个闹事的?排好队!工号 32 后SPI外戚的去西厢房,工号 31(PPI宗亲)里的贴身伺候。优先级低的往后稍稍,我没点头,谁也别想惊动圣驾。”
3、GIC 夺命连环 Call:只有被 GIC 盖了章的中断,才有资格变成一根刺,精准地扎向某个CPU核心的“痛觉神经”(IRQ/FIQ信号线)。
而看似稳重的 CPU 内核皇帝,在被扎的那一刻,也会瞬间破防:
1、把手里的活往地上一扔:CPU动作极其狼狈换脸,贴身侍卫寄存器硬件自动保存关键上下文(PC→LR,CPSR→SPSR)。
2、扭头厉声喝问总管:“谁?哪?多急?”(读取 GIC 的应答寄存器 IAR,这一读,就等于说“这事儿皇上我管了”)。
3、用最快的速度把事儿办了:根据 GIC 给出的索引,跳进对应的 ISR (中断服务程序)直扑战场。
4、再默默捡起地上的活儿:写个“老子办完了”(EOI)告诉 GIC,侍卫们自动从SPSR/LR恢复现场,CPU换回之前的面孔,和蔼的假装什么都没发生,仿佛腥风血雨只是一场梦,继续搬砖。

前情提要和人物介绍完毕,正戏开场!今天,就基于Cortex-A7内核,GIC-v2,打开上帝视角,跟随一个GPIO中断的脚步,扒了中断这身漂亮“底裤”。
看看它如何闯过GIC层层筛选的关卡,最终让CPU皇帝“狼狈换脸”……

我,一个GPIO引脚,从出生起就被编入GPIO1组,成为了第18个兵,它们都叫我GPIO1_IO18。班长说,烽火台(按键输入)就交给我看了。
怕我睡觉打瞌睡,班长就设置GPIO1->ICR2低电平触发、GPIO1->IMR开中断、注册回调函数等一系列猛如虎的操作后,再三叮嘱,还有其他一些操作我要向上备案(GIC里面中断优先级、使能操作),记住有信号就是“起狼烟”,没信号就是“平安无事”,之后就离开了。
我就开始值班了(放心大胆睡觉了)。
突然,我感觉被扎了一下,我一看GPIO1->ISR属于我的那一位被置1了,毫不犹豫的就点燃了烽火台,并赶紧汇报。

迷迷糊糊中,我就来到了一个叫GIC的大内总管府。只见这边一大堆人UART1、I2C2一大堆人围着叫Distributor的高个子七嘴八舌在那里吵着。”都别急,你们外来的都排好队,我看看你们都多少号?“
原来这里还分三六九等。
  • SPIs:Shared Peripheral Interrupts,ID32-ID1019。共享中断,所有外设共享中断,如 GPIO、UART。
  • PPIs:Private Peripheral Interrupts,ID16-ID31。私有中断,每个Core自己的中断,如内核定时器。
  • SGI:Software Generated Interrupt,ID0-ID15。软件中断,由软件触发引起的中断,通过向寄存器GICD_SGIR写入数据来触发用于 CPU 间通信。
排队怎么排啊?怎么编号啊?UART说了句,”旁边有各种手册,你去看看"。说着指了指方向,翻开那又厚又重的手册,最终找到了属于自己的编号99号。
来自NXP RM参考手册
来自Cortex-A7 Technical RM手册
fromMCIMX6Y2.htypedef enum IRQn {  /* Auxiliary constants */  NotAvail_IRQn                = -128,             /**< Not available device specific interrupt */  /* Core interrupts */  Software0_IRQn               = 0,                /**< Cortex-A7 Software Generated Interrupt 0 */  Software1_IRQn               = 1,                /**< Cortex-A7 Software Generated Interrupt 1 */  Software2_IRQn               = 2,                /**< Cortex-A7 Software Generated Interrupt 2 */  Software3_IRQn               = 3,                /**< Cortex-A7 Software Generated Interrupt 3 */  Software4_IRQn               = 4,                /**< Cortex-A7 Software Generated Interrupt 4 */  Software5_IRQn               = 5,                /**< Cortex-A7 Software Generated Interrupt 5 */  Software6_IRQn               = 6,                /**< Cortex-A7 Software Generated Interrupt 6 */  Software7_IRQn               = 7,                /**< Cortex-A7 Software Generated Interrupt 7 */  Software8_IRQn               = 8,                /**< Cortex-A7 Software Generated Interrupt 8 */  Software9_IRQn               = 9,                /**< Cortex-A7 Software Generated Interrupt 9 */  Software10_IRQn              = 10,               /**< Cortex-A7 Software Generated Interrupt 10 */  Software11_IRQn              = 11,               /**< Cortex-A7 Software Generated Interrupt 11 */  Software12_IRQn              = 12,               /**< Cortex-A7 Software Generated Interrupt 12 */  Software13_IRQn              = 13,               /**< Cortex-A7 Software Generated Interrupt 13 */  Software14_IRQn              = 14,               /**< Cortex-A7 Software Generated Interrupt 14 */  Software15_IRQn              = 15,               /**< Cortex-A7 Software Generated Interrupt 15 */  VirtualMaintenance_IRQn      = 25,               /**< Cortex-A7 Virtual Maintenance Interrupt */  HypervisorTimer_IRQn         = 26,               /**< Cortex-A7 Hypervisor Timer Interrupt */  VirtualTimer_IRQn            = 27,               /**< Cortex-A7 Virtual Timer Interrupt */  LegacyFastInt_IRQn           = 28,               /**< Cortex-A7 Legacy nFIQ signal Interrupt */  SecurePhyTimer_IRQn          = 29,               /**< Cortex-A7 Secure Physical Timer Interrupt */  NonSecurePhyTimer_IRQn       = 30,               /**< Cortex-A7 Non-secure Physical Timer Interrupt */  LegacyIRQ_IRQn               = 31,               /**< Cortex-A7 Legacy nIRQ Interrupt */  /* Device specific interrupts */  IOMUXC_IRQn                  = 32,               /**< General Purpose Register 1 from IOMUXC. Used to notify cores on exception condition while boot. */  DAP_IRQn                     = 33,               /**< Debug Access Port interrupt request. */  SDMA_IRQn                    = 34,               /**< SDMA interrupt request from all channels. */  TSC_IRQn                     = 35,               /**< TSC interrupt. */  SNVS_IRQn                    = 36,               /**< Logic OR of SNVS_LP and SNVS_HP interrupts. */  Reserved37_IRQn              = 37,               /**< Reserved */  .....    UART1_IRQn                   = 58,               /**< UART1 interrupt request. */  UART2_IRQn                   = 59,               /**< UART2 interrupt request. */  ......   I2C2_IRQn                    = 69,               /**< I2C2 interrupt request. */  I2C3_IRQn                    = 70,               /**< I2C3 interrupt request. */  ......     GPIO1_Combined_0_15_IRQn     = 98,               /**< Combined interrupt indication for GPIO1 signals 0 - 15. */  GPIO1_Combined_16_31_IRQn    = 99,               /**< Combined interrupt indication for GPIO1 signals 16 - 31. */  ......   Reserved157_IRQn             = 157,              /**< Reserved */  Reserved158_IRQn             = 158,              /**< Reserved */  PMU_IRQ2_IRQn                = 159               /**< Brown-out event on either core, gpu or soc regulators. */} IRQn_Type; 
GIC中ID32-ID1019共享中断号。对于Cortex-A7来说只用了480个,厂商NXP根据自己需求再次分配,比如i.MX.6ULL只用了128个中断,32(SGI+PPI)+128 = 160......
typedef struct{        uint32_t RESERVED0[1024];  __IOM uint32_t D_CTLR;                 /*!< Offset: 0x1000 (R/W) Distributor Control Register */  __IM  uint32_t D_TYPER;                /*!< Offset: 0x1004 (R/ )  Interrupt Controller Type Register */  __IM  uint32_t D_IIDR;                 /*!< Offset: 0x1008 (R/ )  Distributor Implementer Identification Register */        uint32_t RESERVED1[29];  __IOM uint32_t D_IGROUPR[16];          /*!< Offset: 0x1080 - 0x0BC (R/W) Interrupt Group Registers */        uint32_t RESERVED2[16];  __IOM uint32_t D_ISENABLER[16];        /*!< Offset: 0x1100 - 0x13C (R/W) Interrupt Set-Enable Registers */        uint32_t RESERVED3[16];  __IOM uint32_t D_ICENABLER[16];        /*!< Offset: 0x1180 - 0x1BC (R/W) Interrupt Clear-Enable Registers */        uint32_t RESERVED4[16];  __IOM uint32_t D_ISPENDR[16];          /*!< Offset: 0x1200 - 0x23C (R/W) Interrupt Set-Pending Registers */        uint32_t RESERVED5[16];  __IOM uint32_t D_ICPENDR[16];          /*!< Offset: 0x1280 - 0x2BC (R/W) Interrupt Clear-Pending Registers */        uint32_t RESERVED6[16];  __IOM uint32_t D_ISACTIVER[16];        /*!< Offset: 0x1300 - 0x33C (R/W) Interrupt Set-Active Registers */        uint32_t RESERVED7[16];  __IOM uint32_t D_ICACTIVER[16];        /*!< Offset: 0x1380 - 0x3BC (R/W) Interrupt Clear-Active Registers */        uint32_t RESERVED8[16];  __IOM uint8_t  D_IPRIORITYR[512];      /*!< Offset: 0x1400 - 0x5FC (R/W) Interrupt Priority Registers */        uint32_t RESERVED9[128];  __IOM uint8_t  D_ITARGETSR[512];       /*!< Offset: 0x1800 - 0x9FC (R/W) Interrupt Targets Registers */        uint32_t RESERVED10[128];  __IOM uint32_t D_ICFGR[32];            /*!< Offset: 0x1C00 - 0xC7C (R/W) Interrupt configuration registers */        uint32_t RESERVED11[32];  __IM  uint32_t D_PPISR;                /*!< Offset: 0x1D00 (R/ ) Private Peripheral Interrupt Status Register */  __IM  uint32_t D_SPISR[15];            /*!< Offset: 0x1D04 - 0xD3C (R/ ) Shared Peripheral Interrupt Status Registers */        uint32_t RESERVED12[112];  __OM  uint32_t D_SGIR;                 /*!< Offset: 0x1F00 ( /W) Software Generated Interrupt Register */        uint32_t RESERVED13[3];  __IOM uint8_t  D_CPENDSGIR[16];        /*!< Offset: 0x1F10 - 0xF1C (R/W) SGI Clear-Pending Registers */  __IOM uint8_t  D_SPENDSGIR[16];        /*!< Offset: 0x1F20 - 0xF2C (R/W) SGI Set-Pending Registers */        uint32_t RESERVED14[40];  __IM  uint32_t D_PIDR4;                /*!< Offset: 0x1FD0 (R/ ) Peripheral ID4 Register */  __IM  uint32_t D_PIDR5;                /*!< Offset: 0x1FD4 (R/ ) Peripheral ID5 Register */  __IM  uint32_t D_PIDR6;                /*!< Offset: 0x1FD8 (R/ ) Peripheral ID6 Register */  __IM  uint32_t D_PIDR7;                /*!< Offset: 0x1FDC (R/ ) Peripheral ID7 Register */  __IM  uint32_t D_PIDR0;                /*!< Offset: 0x1FE0 (R/ ) Peripheral ID0 Register */  __IM  uint32_t D_PIDR1;                /*!< Offset: 0x1FE4 (R/ ) Peripheral ID1 Register */  __IM  uint32_t D_PIDR2;                /*!< Offset: 0x1FE8 (R/ ) Peripheral ID2 Register */  __IM  uint32_t D_PIDR3;                /*!< Offset: 0x1FEC (R/ ) Peripheral ID3 Register */  __IM  uint32_t D_CIDR0;                /*!< Offset: 0x1FF0 (R/ ) Component ID0 Register */  __IM  uint32_t D_CIDR1;                /*!< Offset: 0x1FF4 (R/ ) Component ID1 Register */  __IM  uint32_t D_CIDR2;                /*!< Offset: 0x1FF8 (R/ ) Component ID2 Register */  __IM  uint32_t D_CIDR3;                /*!< Offset: 0x1FFC (R/ ) Component ID3 Register */  __IOM uint32_t C_CTLR;                 /*!< Offset: 0x2000 (R/W) CPU Interface Control Register */  __IOM uint32_t C_PMR;                  /*!< Offset: 0x2004 (R/W) Interrupt Priority Mask Register */  __IOM uint32_t C_BPR;                  /*!< Offset: 0x2008 (R/W) Binary Point Register */  __IM  uint32_t C_IAR;                  /*!< Offset: 0x200C (R/ ) Interrupt Acknowledge Register */  __OM  uint32_t C_EOIR;                 /*!< Offset: 0x2010 ( /W) End Of Interrupt Register */  __IM  uint32_t C_RPR;                  /*!< Offset: 0x2014 (R/ ) Running Priority Register */  __IM  uint32_t C_HPPIR;                /*!< Offset: 0x2018 (R/ ) Highest Priority Pending Interrupt Register */  __IOM uint32_t C_ABPR;                 /*!< Offset: 0x201C (R/W) Aliased Binary Point Register */  __IM  uint32_t C_AIAR;                 /*!< Offset: 0x2020 (R/ ) Aliased Interrupt Acknowledge Register */  __OM  uint32_t C_AEOIR;                /*!< Offset: 0x2024 ( /W) Aliased End Of Interrupt Register */  __IM  uint32_t C_AHPPIR;               /*!< Offset: 0x2028 (R/ ) Aliased Highest Priority Pending Interrupt Register */        uint32_t RESERVED15[41];  __IOM uint32_t C_APR0;                 /*!< Offset: 0x20D0 (R/W) Active Priority Register */        uint32_t RESERVED16[3];  __IOM uint32_t C_NSAPR0;               /*!< Offset: 0x20E0 (R/W) Non-secure Active Priority Register */        uint32_t RESERVED17[6];  __IM  uint32_t C_IIDR;                 /*!< Offset: 0x20FC (R/ ) CPU Interface Identification Register */        uint32_t RESERVED18[960];  __OM  uint32_t C_DIR;                  /*!< Offset: 0x3000 ( /W) Deactivate Interrupt Register */} GIC_Type;
排好队后,我站在99号位置,UART1在58号,I2C2在69号。。。。。。
只见Distributor拿出册子,上面花花绿绿的记着各种寄存器地址和作用。看了看GICD_ISENABLER(中断使能寄存器)寄存器然后对I2C说道,"你不符合要求,没打开中断一边呆着去(原来中断不是在外设I2CR打开就行,还要在GIC总管府这里备案上)。
"GPIO1_IO18、UART1你俩过来,先给你们标记一下从lnactive打个pending,我看看你俩谁更着急",边说边看BPR发现全是抢占优先级,又查了查GICD_IPRIORITYR寄存器位。CPU interfaces,你来把GPIO1_IO18领走,它是pending里面优先级最高。”
说着,一个小个子过来CPU interfaces,看了看GICC_PMR(优先级屏蔽寄存器),发现中断不是被屏蔽的并且抢占优先级也都符合要求,更新GICC_RPR(运行优先级寄存器),盖了个章,检查下GICC_CTLR,小声嘟囔了说CPU皇帝有的忙了(FIQ相比IRQ有额外寄存器并且异常向量表位于最后,上下文切换快,延迟低一些),就给CPU拉低了IRQ信号。

异常向量表
    .syntax unified    .arch armv7-a    .section .isr_vector, "a"    .align 2    .globl __isr_vector__isr_vector:    ldr     pc, =Reset_Handler           /* Reset                  */    ldr     pc, =Undefined_Handler       /* Undefined instructions */    ldr     pc, =SVC_Handler             /* Supervisor Call        */    ldr     pc, =PrefAbort_Handler       /* Prefetch abort         */    ldr     pc, =DataAbort_Handler       /* Data abort             */    .word   0                            /* RESERVED               */    ldr     pc, =IRQ_Handler             /* IRQ interrupt          */    ldr     pc, =FIQ_Handler             /* FIQ interrupt          */
 紧接着画面一转,来到了皇宫大殿。
端坐在朝堂上处理政务的CPU哎呦一声(侍卫CPSR令牌的I位是使能的,允许IRQ中断)停止当前事情。
贴身寄存器侍卫们立马自觉的开始忙起来,PC值放到LR_irq、CPSR放到SPSR_irq内,设置CPSR M[[4:0]位为10010。“记得给我把IRQ禁止(CPSR设置I位禁止IRQ,CPSR模式更改时候,硬件自动禁止I位),我可不想在折腾上下文的时候,又来中断篡改寄存器数据......”没等CPU皇帝说完......,IRQ异常的面孔就被换上到CPU皇帝脸上,内核自动跳转到VBAR指定的异常向量表IRQ异常响应位置执行,这一切就被硬件自动完成了。
IRQ_Handler:    push    {lr}                         /* Save return address+4                                */    push    {r0-r3, r12}                 /* Push caller save registers                           */    MRS     r0, spsr                     /* Save SPRS to allow interrupt reentry                 */    push    {r0}    MRC     P15, 4, r1, C15, C0, 0       /* Get GIC base address                                 */    ADD     r1, r1, #0x2000              /* r1: GICC base address                                */    LDR     r0, [r1, #0xC]               /* r0: IAR                                              */    push    {r0, r1}    CPS     #0x13                        /* Change to Supervisor mode to allow interrupt reentry */    push    {lr}                         /* Save Supervisor lr                                   */    LDR     r2, =SystemIrqHandler    BLX     r2                           /* Call SystemIrqHandler with param GCC                 */    POP     {lr}    CPS     #0x12                        /* Back to IRQ mode                                     */    POP     {r0, r1}    STR     r0, [r1, #0x10]              /* Now IRQ handler finished: write to EOIR              */    POP     {r0}    MSR     spsr_cxsf, r0    POP     {r0-r3, r12}    POP     {lr}    SUBS    pc, lr, #4
IRQ异常响应中,CPU皇帝火急火燎的先把容易被篡改的调用者保存R0-R3、R12寄存器存到栈区(遵循AAPCS规范),避免没法回到最初的起点。  
又扭头问GIC CPU interfaces,哪个外设中断发生响应?(CPU主动读取IAR中断ID号到R0)。GIC CPU interfaces收到来自CPU的ACK立即让Distributor把GPIO1_IO18对应的中断设置为active and pending。(中断是电平触发,这里的触发指的是GICD_ICFGR设置GIC的中断触发方式,与外设电平触发两个东西。如果中断是边沿触发,需要设置为active,当再一次触发中断时,中断还未完成会由acive设置为active and pending)。
voidSystemIrqHandler(uint32_t giccIar) __attribute__((weak)){#else  #error Not supported compiler type#endif  uint32_t intNum = giccIar & 0x3FFUL;  /* Spurious interrupt ID or Wrong interrupt number */  if ((intNum == 1023) || (intNum >= NUMBER_OF_INT_VECTORS))  {    return;  }  irqNesting++;  __enable_irq();      /* Support nesting interrupt */  /* Now call the real irq handler for intNum */  irqTable[intNum].irqHandler(giccIar, irqTable[intNum].userParam);  __disable_irq();  irqNesting--;}
voidGPIO1_IO18_IRQHandler(void){     /* clear the interrupt status */    GPIO_ClearPinsInterruptFlags(GPIO1, 18);}
CPU皇帝为了可以在此期间不错过重要事情,能够继续响应IRQ中断。通过切换到SVC模式再执行SystemIrqHandler函数,在函数内部通过操作CPSR I位开、关IRQ中断允许IRQ再次响应。
SystemIrqHandler函数传入形参giccIar中断ID号被记录在R0中。根据中断ID号执行对应回调GPIO1_IO18_IRQHandler函数。
虽然,我感受到GPIO1_ISR属于我的位被清除了,但好像还没有到允许我走的时候。(需要软件清除,要不然中断信号会保持。另外,对应回调函数是需要在初始化的时候就要配置好)。
__attribute__( ( always_inline ) ) __STATIC_INLINE void __enable_irq(void){  __ASM volatile ("cpsie i" : : : "memory");}__attribute__( ( always_inline ) ) __STATIC_INLINE void __disable_irq(void){  __ASM volatile ("cpsid i" : : : "memory");}
从SVC模式回到IRQ模式后,CPU皇帝告诉GIC CPU interfaces事办完了(把处理的中断号R0写到写EOIR寄存器)。
GIC 把GPIO1_IO18对应中断从active变为lnactive,直到此刻才告诉我,”回去继续守着吧,这里没你的事了“。说着便把UART1急火火的拉走了,我知道CPU皇帝又要被扎一下啦。
而CPU手动恢复完R0-R3、R12。侍卫们又自动的从SPSR/LR恢复现场,CPU换回之前的面孔,从下一条指令继续搬砖。(因为三级流水线,LR实际保存的是当前指令+8,LR-4刚好是被打断的下一条指令)。
“哎呦,又咋啦?“CPU皇帝疼痛的说道......
注:
1、GIC很复杂,受限于篇幅关于GIC的分组,中断嵌套CPU interfaces处理等等未提及,具体可以参考文章末尾参考资料,精读相关手册。
2、 提个问题?GIC是怎么找到自己的基地址的?  答案就在锦衣卫CP15那里,详细内容看对应手册。

此刻,我们知道了程序开头放着异常向量表,如下图所示,

__isr_vector:    ldr     pc, =Reset_Handler           /* Reset                  */    ldr     pc, =Undefined_Handler       /* Undefined instructions */    ldr     pc, =SVC_Handler             /* Supervisor Call        */    ldr     pc, =PrefAbort_Handler       /* Prefetch abort         */    ldr     pc, =DataAbort_Handler       /* Data abort             */    .word   0                            /* RESERVED               */    ldr     pc, =IRQ_Handler             /* IRQ interrupt          */    ldr     pc, =FIQ_Handler             /* FIQ interrupt   

但在真真没啥技术的我学Linux驱动 | ROM Code的“接头暗号”铸造“信物”的程序中编译出来的程序,一开始连续好几个18 F0 9F E5是否引起你的好奇?

为什么在当我们在代码中写下不同的“指令咒语”却产生了相同的魔法数字?    

带着这样的疑问,让我们走进CPU能听懂的语言-指令集。

参考资料《ARM® Generic Interrupt Controller Architecture version 2.0 Architecture Specification》 《i.MX 6ULL Applications Processor Reference Manual》《ARM® Cortex™-A Series Version: 4.0 Programmer’s Guide》《Cortex™-A7 MPCore™ Revision: r0p5 Technical Reference Manual》《ARM® Architecture Reference Manual ARMv7-A and ARMv7-R edition》

最新文章

随机文章