跳到主要内容位置

ARM架构-异常与中断

1.ARM 对异常或中断的处理过程#

1.1 通用处理过程#

对于 arm 系统,异常与中断的硬件框图如下:

所有的中断源(按键、定时器等),它们发出的中断汇聚到中断控制器, 再由中断控制器发信号给 CPU,告诉它发生了那些紧急情况。

除了这些中断,还有什么可以打断 CPU 的运行?

  • 指令不对
  • 数据访问有问题
  • reset 信号
  • 等等,这些都可以打断断 CPU,这些被称为异常
  • 中断属于一种异常

ARM 系统中如何处理异常与中断?重点在于保存现场以及恢复现场, 处理过程如下:

  • 保存现场(各种寄存器)
  • 处理异常(中断属于一种异常)
  • 恢复现场

细化一下,在 ARM 系统中如何使用异常(中断)?

  • 初始化

    • 设置中断源,让它可以产生中断
    • 设置中断控制器(可以屏蔽某个中断,优先级)
    • 设置 CPU 总开关,使能中断
  • 执行其他程序:正常程序

  • 产生中断,举例:按下按键--->中断控制器--->CPU

  • cpu 每执行完一条指令都会检查有无中断/异常产生

  • 发现有中断/异常产生,开始处理:

    • 保存现场
    • 分辨异常/中断,调用对于异常/中断的处理函数
    • 恢复现场

不同的芯片,不同的架构,在这方面的处理稍有差别:

  • 保存/恢复现场:cortex M3/M4 是硬件实现的,cortex A7 是软件实现的
  • CPU 中止当前执行,跳转去执行处理异常的代码:也有差异
    • cortex M3/M4 在向量表上放置的是函数地址
    • cortex A7 在向量表上放置的是跳转指令

通用处理流程:不同处理器、操作系统会有细微差别。

  • 中断请求
  • 中断响应

关中断

  • 保存断点
  • 识别中断源
  • 保存现场

开中断

  • 中断服务程序

关中断

  • 恢复现场

开中断

  • 中断返回

1.2 Cortex M3/M4#

1)M3/M4 的异常/中断处理流程#

发生异常/中断时,硬件上实现了这些事情:

  • 保存现场:把被中断瞬间的寄存器的值保存进栈里

  • 根据异常/中断号,从向量表中得到函数地址,跳转过去执行

  • 函数执行完后,从栈中恢复现场

保存现场、分辨异常/中断、跳转执行,都是硬件实现的。 我们只需要在向量表中,把处理函数的地址填进去就可以了。

硬件承包了大部分的工作。

M3/M4 的向量表中,存放的是函数地址

2)M3/M4 的向量表#

M3/M4 的向量表中,放置的是具体异常/中断的处理函数的地址。 比如发生Reset异常时,CPU 就会从向量表里找到第 1 项,得到 Reset_Handler 函数的地址,跳转去执行。 比如发生EXTI Line 0中断时,CPU 就会从向量表里找到第 22 项,得到 EXTI0_IRQHandler 函数的地址,跳转去执行。

  • 跳转之前,硬件会保存现场
  • 函数执行完毕,返回之后,硬件会恢复现场
; Vector Table Mapped to Address 0 at Reset
AREA RESET, DATA, READONLY
EXPORT __Vectors
EXPORT __Vectors_End
EXPORT __Vectors_Size
__Vectors DCD __initial_sp ; Top of Stack
DCD Reset_Handler ; Reset Handler
DCD NMI_Handler ; NMI Handler
DCD HardFault_Handler ; Hard Fault Handler
DCD MemManage_Handler ; MPU Fault Handler
DCD BusFault_Handler ; Bus Fault Handler
DCD UsageFault_Handler ; Usage Fault Handler
DCD 0 ; Reserved
DCD 0 ; Reserved
DCD 0 ; Reserved
DCD 0 ; Reserved
DCD SVC_Handler ; SVCall Handler
DCD DebugMon_Handler ; Debug Monitor Handler
DCD 0 ; Reserved
DCD PendSV_Handler ; PendSV Handler
DCD SysTick_Handler ; SysTick Handler
; External Interrupts
DCD WWDG_IRQHandler ; Window Watchdog
DCD PVD_IRQHandler ; PVD through EXTI Line detect
DCD TAMPER_IRQHandler ; Tamper
DCD RTC_IRQHandler ; RTC
DCD FLASH_IRQHandler ; Flash
DCD RCC_IRQHandler ; RCC
DCD EXTI0_IRQHandler ; EXTI Line 0
DCD EXTI1_IRQHandler ; EXTI Line 1
DCD EXTI2_IRQHandler ; EXTI Line 2
DCD EXTI3_IRQHandler ; EXTI Line 3
DCD EXTI4_IRQHandler ; EXTI Line 4
DCD DMA1_Channel1_IRQHandler ; DMA1 Channel 1
DCD DMA1_Channel2_IRQHandler ; DMA1 Channel 2
DCD DMA1_Channel3_IRQHandler ; DMA1 Channel 3
DCD DMA1_Channel4_IRQHandler ; DMA1 Channel 4
DCD DMA1_Channel5_IRQHandler ; DMA1 Channel 5
DCD DMA1_Channel6_IRQHandler ; DMA1 Channel 6
DCD DMA1_Channel7_IRQHandler ; DMA1 Channel 7
DCD ADC1_2_IRQHandler ; ADC1 & ADC2
DCD USB_HP_CAN1_TX_IRQHandler ; USB High Priority or CAN1 TX
DCD USB_LP_CAN1_RX0_IRQHandler ; USB Low Priority or CAN1 RX0
DCD CAN1_RX1_IRQHandler ; CAN1 RX1
DCD CAN1_SCE_IRQHandler ; CAN1 SCE
DCD EXTI9_5_IRQHandler ; EXTI Line 9..5
DCD TIM1_BRK_IRQHandler ; TIM1 Break
DCD TIM1_UP_IRQHandler ; TIM1 Update
DCD TIM1_TRG_COM_IRQHandler ; TIM1 Trigger and Commutation
DCD TIM1_CC_IRQHandler ; TIM1 Capture Compare
DCD TIM2_IRQHandler ; TIM2
DCD TIM3_IRQHandler ; TIM3
DCD TIM4_IRQHandler ; TIM4
DCD I2C1_EV_IRQHandler ; I2C1 Event
DCD I2C1_ER_IRQHandler ; I2C1 Error
DCD I2C2_EV_IRQHandler ; I2C2 Event
DCD I2C2_ER_IRQHandler ; I2C2 Error
DCD SPI1_IRQHandler ; SPI1
DCD SPI2_IRQHandler ; SPI2
DCD USART1_IRQHandler ; USART1
DCD USART2_IRQHandler ; USART2
DCD USART3_IRQHandler ; USART3
DCD EXTI15_10_IRQHandler ; EXTI Line 15..10
DCD RTCAlarm_IRQHandler ; RTC Alarm through EXTI Line
DCD USBWakeUp_IRQHandler ; USB Wakeup from suspend
DCD TIM8_BRK_IRQHandler ; TIM8 Break
DCD TIM8_UP_IRQHandler ; TIM8 Update
DCD TIM8_TRG_COM_IRQHandler ; TIM8 Trigger and Commutation
DCD TIM8_CC_IRQHandler ; TIM8 Capture Compare
DCD ADC3_IRQHandler ; ADC3
DCD FSMC_IRQHandler ; FSMC
DCD SDIO_IRQHandler ; SDIO
DCD TIM5_IRQHandler ; TIM5
DCD SPI3_IRQHandler ; SPI3
DCD UART4_IRQHandler ; UART4
DCD UART5_IRQHandler ; UART5
DCD TIM6_IRQHandler ; TIM6
DCD TIM7_IRQHandler ; TIM7
DCD DMA2_Channel1_IRQHandler ; DMA2 Channel1
DCD DMA2_Channel2_IRQHandler ; DMA2 Channel2
DCD DMA2_Channel3_IRQHandler ; DMA2 Channel3
DCD DMA2_Channel4_5_IRQHandler ; DMA2 Channel4 & Channel5
__Vectors_End

1.3 Cortex A7#

1)A7 的异常/中断处理流程#

发生异常/中断时,硬件上实现了这些事情:

  • CPU 切换到对应的异常模式,比如 IRQ 模式、未定义模式、SVC 模式

  • 保存被中断时的 CPSR 到 SPSR

    • CPSR:current program status register,当前程序状态寄存器
    • SRSR:saved program status register,保存的程序状态寄存器
  • 跳到这个异常的入口地址去,执行指令,这通常是一条跳转指令,ldr PC, _irq存放在向量表中。

软件要做的事情就比较多了:

  • 保存现场
  • 分辨异常/中断
  • 调用对应的处理函数
  • 恢复现场

2)A7 的向量表#

A7 的向量表中,放置的是某类异常的跳转指令。 比如发生Reset异常时,CPU 就会从向量表里找到第 0 项,得到b reset指令,执行后就跳转到 reset 函数。 比如发生任何的中断时,CPU 就会从向量表里找到第 6 项,得到ldr pc, _irq指令,执行后就跳转到_irq 函数。(在 irq 函数里面进行分辨中断源的工作)

  • 跳转之前,硬件只会保存 CPSR 寄存器
  • 跳转之后,软件要保存现场
  • 函数执行完毕,返回之前,软件恢复现场
_start:
b reset
ldr pc, _undefined_instruction
ldr pc, _software_interrupt
ldr pc, _prefetch_abort
ldr pc, _data_abort
ldr pc, _not_used
ldr pc, _irq
ldr pc, _fiq

2.异常处理深入分析_保存现场#

2.1 处理流程总述#

CPU 每执行完一条指令都会检查有无中断/异常产生,发现有中断/异常产生,开始处理:

  • 保存现场
  • 分辨异常/中断,调用对应的异常/中断处理函数
  • 恢复现场

具体对于不同处理器:

  • 保存现场:cortex M3/M4 里是硬件完成,cortex A7 等是软件实现
  • 分辨异常/中断:cortex M3/M4 里是硬件完成,cortex A7 等是软件实现
  • 调用处理函数:cortex M3/M4 里是硬件来调用,cortex A7 等是软件自己去调用
  • 恢复现场:cortex M3/M4 里是软件触发、硬件实现,cortex A7 等是软件实现

不管是硬件还是软件实现,第一步都是保存现场

  • 在处理异常前,把这些寄存器保存在栈中,这称为保存现场
  • 在处理完异常后,从栈中恢复这些寄存器,这称为恢复现场

2.2 保存现场#

1)需要保存哪些寄存器?#

ARM 处理器中有这些寄存器:

在 arm 中有个ATPCS规则(ARM-THUMB procedure call standard(ARM-Thumb 过程调用标准)。

约定 R0-R15 寄存器的用途:

  • R0-R3

    调用者和被调用者之间传参数

  • R4-R11

    函数可能被使用,所以在函数的入口保存它们,在函数的出口恢复它们。

还有一个程序状态寄存器,对于 M3/M4 它被称为XPSR,对于 A7 它被称为CPSR,我们简称为 PSR。 R0-R15、PSR,就是所谓的现场。 发生异常/中断后,在处理异常/中断前,需要保存现场,难道需要保存所有这些寄存器吗? 不需要! 在 C 函数中,可以修改 R0-R3、R12、R14(LR)以及 PSR。如果 C 函数要用到这些寄存器,就要把它们保存到栈里,在函数结束前在从栈中恢复它们。 这些寄存器被拆分成 2 部分:调用者保存的寄存器(R0-R3,R12,LR,PSR)被调用者保存的寄存器(R4-R11)

比如函数 A 调用函数 B,函数 A 应该知道:

  • R0-R3 是用来传参数给函数 B 的
  • 函数 B 可以肆意修改 R0-R3
  • 函数 A 不要指望函数 B 帮你保存 R0-R3
  • 保存 R0-R3,是函数 A 的事情
  • 对于 LR、PSR 也是同样的道理,保存它们是函数 A 的责任

对于函数 B:

  • 我用到 R4-R11 中的某一个,我都会在函数入口保存、在函数返回前恢复
  • 保证在 B 函数调用前后,函数 A 看到的 R4-R11 保存不变

进一步,假设函数 B 就是异常/中断处理函数,函数 B 本身能保证 R4-R11 不变,那么保存现场时,只需要保存这些:

  • 调用者保存的寄存器(R0-R3,R12,LR,PSR)
  • PC(执行完之后的返回地址)

2)Cortex M3/M4#

  • 保存现场(硬件进行)

  • 调用 C 函数

    • C 函数执行完后,它返回 LR 所指示的位置。 难道把 LR 设置为被中断的程序的地址就行了吗? 如果只是返回 LR 所指示的地方,硬件帮我们保存在栈里的寄存器,怎么恢复?
    • M3/M4 在调用异常处理函数前,把 LR 设置为一个特殊的值,转给特殊的值被称为EXC_RETURN。 当 PC 寄存器的值等于EXC_RETURN时,会触发异常返回机制,简单地说:会从栈里恢复 R0-R3,R12,LR,PC,PSR 等寄存器。
    • EXC_RETURN的值,请参考ARM Cortex-M3与Cortex-M4权威指南.pdf,截图如下:
  • 补充 2 个知识点:

    • 操作模式:M3/M4 有两个操作模式

      • 处理模式:执行中断服务程序等异常处理时,处于处理模式
      • 线程模式:执行普通应用程序代码时,处于线程模式
    • M3/M4 有连个 SP 寄存器:SP_process、SP_main

      • 有些 RTOS 在运行用户程序时会使用 SP_process,默认使用 SP_main。

3)Cortex A7#

处理器有 9 种模式:User、Sys、FIQ、IRQ、ABT、SVC、UND、MON、HYP。

下图中深色的寄存器,表示该模式下的"Banked"寄存器,比如 SPSR 寄存器,在很多模式中都有自己的寄存器。比如 IRQ 模式下访问 SPSR 时,访问到的是 IRQ 模式下自己的 SPSR_irq,别的模式下无法访问 SPSR_irq。

比较值得关注的是FIQ模式,名为"快中断",它有很多"Banked"寄存器:R8-R12,SP,LR。 在 FIQ 模式下,它既然能使用自己的 R8-R12,SP,LR,自然不需要去保存被中断的程序的"R8-R12,SP,LR"了。 省去保存这几个寄存器的时间,处理中断时自然就快很多,所以被称为"FIQ"。

从上图也看到,几乎每个模式下都有自己是 SP 寄存器,意味着这些模式下有自己的栈。

当发生异常时,以 IRQ 为例:

硬件:

  • CPU 会自动切换进入对应的模式,比如进入 IRQ 模式
  • 并且会把被中断的 CPSR 保存到 SPSR_irq 里
  • 跳转到 IRQ 入口:ldr PC, _irq

软件:

  • 保存现场:调用者保存的寄存器(R0-R3,R12,LR)以及 PC(返回地址)
  • 分辨中断源、处理中断
  • 恢复现场

3.CPU 模式(Mode)_状态(State)与寄存器#

3.1 ARMv7 有 9 种模式#

  • USR:用户模式
  • SYS:系统模式
  • 异常模式:
    • UND:未定义模式(出现未定义的指令)
    • SVC:管理模式,软中断
    • ABT:中止模式(1,指令预取中止 2,数据访问中止)
    • IRQ:中断模式
    • FIQ:快中断模式

除了用户模式(USR),其他 6 种可以称为特权模式(privileged mode)。特权模式可以编程操作CPSR直接进入其他模式,而在用户模式下,不能随意切换。

跟 ARM9 相比,多了 2 中 Mode:Monitor、Hyp。

3.2 ARMv7 有 2 种状态#

  • ARM state
    • ARM 指令集,在 ARMv7 32 位指令下,每个指令占 4 字节(机器码)
  • Thumb state
    • Thumb 指令集,每个指令占 2 字节(机器码)

例如,同样是mov R0, R1,使用 ARM 指令编译成机器码占据 4Byte,而使用 Thumb 占据 2Byte。

3.3 寄存器分析#

1)A7 R0-R15 寄存器#

2.2小节的3)Cortex A7中也已经说明过下图中的寄存器了。

每个模式下都有自己的 SP 寄存器(R13),即 SP_xxx;也有自己的 LR 寄存器(R14),保存发生异常时的指令地址。 另外,在 FIQ 快中断模式下,有自己的 R8-R12 寄存器,那么就不需要保存系统/用户模式下的 R8 ~ R12 这几个寄存器,这样可以省略保存寄存器的时间,加快处理速度。

2)CPSR / SPSR 寄存器#

我们从低位到高位看:

  • M4-M0:表示当前 CPU 处于哪一种模式。

  • T, bit[5] :表示工作在 ARM 还是 Thumb 指令集下;

  • F, bit[6] :如果是 1,表示禁止 FIQ 中断

  • I, bit[7] :如果是 1,表示禁止 IRQ 中断

  • A, bit[8] :如果是 1,表示禁止异步中止

  • E, bit[9] :大小端,0 Little Endian,1 Big Endian

  • IT[7:0], bits[15:10, 26:25]

  • GE[3:0], bits[19:16] :大于或等于标志位

  • Bits[23:20]:保留位

  • J, bit[24] :加速执行环境的 ARM Jazelle ® 技术,结合 T 位

  • Q, bit[27] :累积饱和位。

  • Condition flags, bits[31:28]

    • N, bit[31] Negative condition flag

      Z, bit[30] Zero condition flag

      C, bit[29] Carry condition flag

      V, bit[28] Overflow condition flag

    • 例如:cmp R0, R1 - 影响 z 位,if R0 == R1, Z = 1 beq xxx - 使用 z 位,如果 z == 1, 则跳转。

3.4 发生异常时的处理流程#

1)异常向量表#

在 ARM9 里,异常向量表基地址只有两个取值:0、0xFFFF0000。 对于 cortex A7,它的异常向量表基地址是可以修改的。

2)进入异常的处理流程(硬件)#

  1. 硬件确定要进入哪种异常模式
  2. LR 寄存器被更新,它表示处理完异常后要返回到哪里,这个值可能需要修改。
  3. SPSR = 被中断时的 CPSR
  4. 对于"Security Exceptions",……,本课程不涉及
  5. 更新异常模式下的 CPSR:设置模式位、设置 mask bit(屏蔽其他异常)、设置指令集状态位
  6. PC = 异常入口地址
  7. 从 PC 所指示地方执行程序

3)退出异常#

ARM ArchitectureReference Manual ARMv7-A and ARMv7-R edition.pdf中,对异常的退出,描述得很复杂。但是很多情况,我们并不涉及。 所以我们参考S3C2440A_UserManual_Rev13.pdf,它描述得更清晰、简单。

  1. 让 LR 减去某个值,然后赋值给 PC(PC = 某个异常 LR 寄存器减去 offset) 减去什么值呢? 也就是我们怎么返回去继续执行原来的程序,根据下面这个表来取值:

    如果发生的是 SWI 可以把 R14_svc 复制给 PC 如果发生的是 IRQ 可以把 R14_irq 的值减去 4 赋值给 PC

  2. 把 CPSR 的值恢复(CPSR 值等于 某一个一场模式下的 SPSR)

  3. 清中断(如果是中断的话,对于其他异常不用设置)

4)确定异常返回地址#

发生异常时,LR 寄存器里保存的值是什么?

LR = Preferred return address + offset

"Preferred return address"是什么?请看下图:对于IRQ模式,就是下一条执行指令的地址。

offset 是什么?

从异常中返回时,LR 可能需要调整,再赋给 PC。 ARM9 的手册讲得比较清楚,返回指令如下:

4.ARM_Thumb 指令集程序示例#

4.1 汇编里指定指令集#

对于 IMX6ULL、STM32MP157,默认使用 ARM 指令集。 在汇编文件里,可以使用这样的语法告诉编译器:

.arm // 表示后续的指令使用ARM指令集
.thumb // 表示后续的指令使用thumb指令集

4.2 编译 C 文件时指定指令集#

使用 GCC 编译时:

-marm // 使用ARM指令集编译
-mthumb // 使用Thumb指令集编译
比如:
arm-linux-gcc -marm -c -o main.o main.c

4.3 汇编里切换状态#

要切换 CPU 的 State,比如从 ARM 切换到 Thumb,或者从 Thumb 切换到 ARM,可以使用 BX、BLX 指令:

// 如果R0的bit0为0,表示切换到ARM State; 如果R0的bit0位1,表示切换到Thumb State
BX R0
BLX R0

汇编里调用 C 函数时,可以直接如此调用:

LDR PC, =main // 如果main函数时用Thumb指令集编译的,最终的指令如下:
// 注意到下面的c020051b,它的bit0为1
c0200010: e59ff004 ldr pc, [pc, #4] ; c020001c <AB+0x14>
c0200014: c0100000 andsgt r0, r0, r0
c0200018: c02004a3 eorgt r0, r0, r3, lsr #9
c020001c: c020051b eorgt r0, r0, fp, lsl r5

5.SVC 异常#

在 ARM 指令中,有一条指令:

SVC #VAL

它会触发一个异常。 在操作系统中,比如各类 RTOS 或者 Linux,都会使用SVC指令故意触发异常,从而导致内核的异常处理函数被调用,进而去使用内核的服务。 比如 Linux 中,各类文件操作的函数openreadwrite,它的实质都是SVC指令。 本节课程不讲解SVC在内核中的使用,我们只是看看如何处理SVC触发的异常。

6.中断的硬件结构与配置寄存器#

6.1 中断路径上的 3 个部件#

  • 中断源 中断源多种多样,比如 GPIO、定时器、UART、DMA 等等。 它们都有自己的寄存器,可以进行相关设置:使能中断、中断状态、中断类型等等。
  • 中断控制器 各种中断源发出的中断信号,汇聚到中断控制器。 可以在中断控制器中设置各个中断的优先级。 中断控制器会向 CPU 发出中断信号,CPU 可以读取中断控制器的寄存器,判断当前处理的是哪个中断。 中断控制器有多种实现,比如:
    • STM32F103 中被称为 NVIC:Nested vectored interrupt controller(嵌套向量中断控制器)
    • ARM9 中一般是芯片厂家自己实现的,没有统一标准
    • Cortex A7 中使用 GIC(Generic Interrupt Controller)
  • CPU CPU 每执行完一条指令,都会判断一下是否有中断发生了。 CPU 也有自己的寄存器,可以设置它来使能/禁止中断,这是中断处理的总开关。

接下来,进行对比学习三款芯片:STM32F103\STM32MP157\IMX6ULL

6.2 STM32F103 的 GPIO 中断#

对于 GPIO 中断,F103 引入了一个外部中断控制器,External interrupt/event controller (EXTI)

EXTI 可以给 NVIC 提供 16 个中断信号:EXTI0~EXTI15。 那么某个 EXTIx,它来自哪些 GPIO 呢?这需要设置 GPIO 控制器。

1)GPIO 控制器#

AFIO_EXTICR1~AFIO_EXTICR4一共 4 个寄存器 名为:External interrupt configuration register,外部中断配置寄存器。 用来选择某个外部中断 EXTIx 的中断源。

因此,在 GPIO 控制器中,可以设置某个 GPIO 引脚作为中断源,给 EXTI 提供中断信号。

2)EXTI#

设置高电平触发、低电平触发、上升沿触发、下降沿触发等等,具体包括:

  • Falling trigger selection register:是否选择下降沿触发
  • Rising trigger selection register:是否选择上升沿触发
  • Interrupt mask register:是否屏蔽中断

当发生中断时,可以读取下列寄存器判断是否发生了中断、发生了哪个中断:

  • Pending reqeust register

要使用 EXTI,流程如下:

  • 配置EXTI_IMR:允许 EXTI 发出中断
  • 配置EXTI_RTSR、EXTI_FTSR,选择中断触发方式
  • 配置 NVIC 中的寄存器,允许 NVIC 把中断发给 CPU

3)NVIC#

NVIC 的职责就是从多个中断源中取出优先级最高的中断,向 CPU 发出中断信号,处理中断时,程序可以写 NVIC 的寄存器,清除中断。

4)CPU#

cortex M3/M4 处理器内部有这几个寄存器:

PRIMASK

把 PRIMASK 的 bit0 设置为 1,就可以屏蔽所有优先级可配置的中断。 可以使用这些指令来设置它:

CPSIE I ; 清除PRIMASK,使能中断
CPSID I ; 设置PRIMASK,禁止中断
或者:
MOV R0, #1
MSR PRIMASK R0 ;1写入PRIMASK禁止所有中断
MOV R0, #0
MSR PRIMASK, R0 ;0写入PRIMASK使能所有中断

FAULTMASK

FAULTMASK 和 PRIMASK 很像,它更进一步,出来一般的中断外,把 HardFault 都禁止了。 只有 NMI 可以发生。 可以使用这些指令来设置它:

CPSIE F ; 清除FAULTMASK
CPSID F ; 设置FAULTMASK
或者:
MOV R0, #1
MSR FAULTMASK R0 ;1写入FAULTMASK禁止中断
MOV R0, #0
MSR FAULTMASK, R0 ;0写入FAULTMASK使能中断

BASEPRI

可以屏蔽优先级大于或等于 BASEPRI 的中断。

MOVS R0, #0x60
MSR BASEPRI, R0 ; 禁止优先级在0x60~0xFF间的中断
MRS R0, BASEPRI ; 读取BASEPRI
MOVS R0, #0
MSR BASEPRI, R0 ; 取消BASEPRI屏蔽

6.3 STM32MP157 的 GPIO 中断#

1)GPIO 控制器#

对于 STM32MP157,除了把 GPIO 引脚配置为输入功能外,GPIO 控制器里没有中断相关的寄存器。(和 F103 不一样)

2)EXTI#

GPIO 中断的配置都集成在这里了,包括

  • 设置EXTImux:选择哪些 GPIO 可以发出中断。只有 16 个 EXTI 中断,从 EXTI0~EXTI15;每个 EXTIx 中断只能从 PAx、PBx、……中选择某个引脚。 具体而言,通过EXTI_EXTICR1等寄存器来设置 EXTIx 的中断源是哪个 GPIO 引脚

  • 设置Event Trigger:设置中断触发方式,EXTI_RTSR1 \ EXTI_FTSR1

  • 设置Masking:允许某个 EXTI 中断,EXTI_IMR

  • 查看中断状态、清中断:

3)GIC#

ARM 体系结构定义了通用中断控制器(GIC),该控制器包括一组用于管理单核或多核系统中的中断的硬件资源。GIC 提供了内存映射寄存器,可用于管理中断源和行为,以及(在多核系统中)用于将中断路由到各个 CPU 核。它使软件能够屏蔽,启用和禁用来自各个中断源的中断,以(在硬件中)对各个中断源进行优先级排序和生成软件触发中断。它还提供对 TrustZone 安全性扩展的支持。GIC 接受系统级别中断的产生,并可以发信号通知给它所连接的每个内核,从而有可能导致 IRQ 或 FIQ 异常发生。

GIC 比较复杂,在 7.GIC 小节介绍。

4)CPU#

CPU 的 CPSR 寄存器中有一位:I 位,用来使能/禁止中断。

可以使用以下汇编指令修改 I 位:

CPSIE I ; 清除I位,使能中断
CPSID I ; 设置I位,禁止中断

6.4 IMX6ULL 的 GPIO 中断#

IMX6ULL 中没有 EXTI 控制器,对 GPIO 的中断配置、控制,都在 GPIO 模块内部实现:

1)GPIO 控制器#

每组 GPIO 中都有对应的GPIOx_ICR1GPIOx_ICR2寄存器(interrupt configuration register),每个引脚都可以配置为中断引脚,并配置它的触发方式。

使能 GPIO 中断:GPIOx_IMR

判断中断状态、清中断:GPIOx_ISR

2)GIC#

ARM 体系结构定义了通用中断控制器(GIC),该控制器包括一组用于管理单核或多核系统中的中断的硬件资源。GIC 提供了内存映射寄存器,可用于管理中断源和行为,以及(在多核系统中)用于将中断路由到各个 CPU 核。它使软件能够屏蔽,启用和禁用来自各个中断源的中断,以(在硬件中)对各个中断源进行优先级排序和生成软件触发中断。它还提供对 TrustZone 安全性扩展的支持。GIC 接受系统级别中断的产生,并可以发信号通知给它所连接的每个内核,从而有可能导致 IRQ 或 FIQ 异常发生。

GIC 比较复杂,下一个视频再详细讲解。

3)CPU#

CPU 的 CPSR 寄存器中有一位:I 位,用来使能/禁止中断。和 MP157 一样。

7.GIC 介绍#

从软件角度来看,GIC 具有两个主要功能模块,简单画图如下:

  • 分发器(Distributor):系统中的所有中断源都连接到该单元。可以通过仲裁单元的寄存器来控制各个中断源的属性,例如优先级、状态、安全性、路由信息和使能状态。分发器把中断输出到“CPU 接口单元”,后者决定将哪个中断转发给 CPU 核。
  • CPU 接口:CPU 核通过控制器的 CPU 接口单元接收中断。CPU 接口单元寄存器用于屏蔽,识别和控制转发到 CPU 核的中断的状态。系统中的每个 CPU 核心都有一个单独的 CPU 接口。 中断在软件中由一个称为中断 ID 的数字标识。中断 ID 唯一对应于一个中断源。软件可以使用中断 ID 来识别中断源并调用相应的处理程序来处理中断。呈现给软件的中断 ID 由系统设计确定,一般在 SOC 的数据手册有记录。

完整逻辑框图:

7.1 GIC 中断的不同类型#

软件触发中断(SGI,Software Generated Interrupt)

  • 这是由软件通过写入专用仲裁单元的寄存器即软件触发中断寄存器(ICDSGIR)显式生成的。它最常用于CPU 核间通信
  • SGI 既可以发给所有的核,也可以发送给系统中选定的一组核心。中断号 0-15 保留用于 SGI 的中断号。用于通信的确切中断号由软件决定。

私有外设中断(PPI,Private Peripheral Interrupt)

  • 这是由单个 CPU 核私有的外设生成的。PPI 的中断号为 16-31。它们标识 CPU 核私有的中断源,并且独立于另一个内核上的相同中断源,比如,每个核的计时器

共享外设中断(SPI,Shared Peripheral Interrupt)

  • 这是由外设生成的,中断控制器可以将其路由到多个核。中断号为 32-1020。SPI 用于从整个系统可访问的各种外围设备发出中断信号。
  • 中断可以是边沿触发的(在中断控制器检测到相关输入的上升沿时认为中断触发,并且一直保持到清除为止)或电平触发(仅在中断控制器的相关输入为高时触发)。

7.2 GIC 定义中断的四种状态#

  • 非活动状态(Inactive)–这意味着该中断未触发。
  • 挂起(Pending)–这意味着中断源已被触发,但正在等待 CPU 核处理。待处理的中断要通过转发到 CPU 接口单元,然后再由 CPU 接口单元转发到内核。
  • 活动(Active)–描述了一个已被内核接收并正在处理的中断。
  • 活动和挂起(Active and pending)–描述了一种情况,其中 CPU 核正在为中断服务,而 GIC 又收到来自同一源的中断。

7.3 配置#

GIC 作为内存映射的外围设备,被软件访问。所有内核都可以访问公共的 distributor 单元,但是 CPU interface 是备份的,也就是说,每个 CPU 核都使用相同的地址来访问其专用 CPU 接口。一个 CPU 核不可能访问另一个 CPU 核的 CPU 接口。

Distributor 拥有许多寄存器,可以通过它们配置各个中断的属性。这些可配置属性是:

  • 中断优先级:Distributor 使用它来确定接下来将哪个中断转发到 CPU 接口。

  • 中断配置:这确定中断是对电平触发还是边沿触发。

  • 中断目标:这确定了可以将中断发给哪些 CPU 核。

  • 中断启用或禁用状态:只有 Distributor 中启用的那些中断变为挂起状态时,才有资格转发。

  • 中断安全性:确定将中断分配给 Secure 还是 Normal world 软件。

  • 中断状态。

    Distributor 还提供优先级屏蔽,可防止低于某个优先级的中断发送给 CPU 核。 每个 CPU 核上的 CPU interface,专注于控制和处理发送给该 CPU 核的中断。

7.4 初始化#

  • Distributor 和 CPU interface 在复位时均被禁用。复位后,必须初始化 GIC,才能将中断传递给 CPU 核。
  • 在 Distributor 中,软件必须配置优先级、目标核、安全性并启用单个中断;随后必须通过其控制寄存器使能。
  • 对于每个 CPU interface,软件必须对优先级和抢占设置进行编程。每个 CPU 接口模块本身必须通过其控制寄存器使能。
  • 在 CPU 核可以处理中断之前,软件会通过在向量表中设置有效的中断向量并清除 CPSR 中的中断屏蔽位来让 CPU 核可以接收中断。
  • 可以通过禁用 Distributor 单元来禁用系统中的整个中断机制;可以通过禁用单个 CPU 的 CPU 接口模块或者在 CPSR 中设置屏蔽位来禁止向单个 CPU 核的中断传递。也可以在 Distributor 中禁用(或启用)单个中断。
  • 为了使某个中断可以触发 CPU 核,必须将各个中断,Distributor 和 CPU interface 全部使能,并

将 CPSR 中断屏蔽位清零,如下图:

7.5 GIC 中断处理#

当 CPU 核接收到中断时,它会跳转到中断向量表执行。

  • 顶层中断处理程序读取 CPU 接口模块的 Interrupt Acknowledge Register,以获取中断 ID。除了返回中断 ID 之外,读取操作还会使该中断在 Distributor 中标记为 active 状态。一旦知道了中断 ID(标识中断源),顶层处理程序现在就可以分派特定于设备的处理程序来处理中断。
  • 特定于设备的处理程序完成执行时,顶级处理程序将相同的中断 ID 写入 CPU interface 模块中的 End of Interrupt register 中断结束寄存器,指示中断处理结束。除了把当前中断移除 active 状态之外,这将使最终中断状态变为 inactive 或 pending(如果状态为 inactive and pending),这将使 CPU interface 能够将更多待处理 pending 的中断转发给 CPU 核。这样就结束了单个中断的处理。
  • 同一 CPU 核上可能有多个中断等待服务,但是 CPU interface 一次只能发出一个中断信号。顶层中断处理程序重复上述顺序,直到读取特殊的中断 ID 值 1023,表明该内核不再有任何待处理的中断。这个特殊的中断 ID 被称为伪中断 ID(spurious interrupt ID)。 ​ 伪中断 ID 是保留值,不能分配给系统中的任何设备。