arm中断—GIC硬件
本系列将详细描述ARM中断的原理、代码分析和使用实际案例。
本文将参考的硬件是GIC-400,Kernel版本 3.10.52
ARM核中断架构
IRQ、FIQ区别
FIQ和IRQ是两种不同类型的中断,ARM为了支持这两种不同的中断,提供了对应的叫做FIQ和IRQ处理器模式
两种中断的区别,简单来说就是:FIQ比IRQ快,FIQ会比IRQ快几个指令周期;
对于实时必要求非常高(估计是ns级)的场合可以使用FIQ,相应的代码复杂度也要比较小,否则会失去FIQ的意义
注:在linux系统中,主要是用IRQ,因此代码中有大量的irq字眼,而很少使用fiq
ARM核心中断开启&关闭
ARMv7框架中,中断的开启与关闭由CPSR(或CPSR_C)寄存器控制,CPSR(或CPSR_C)寄存器需要使用mrs和msr指令来操作,一般只使用汇编代码来控制
IRQ开启:1
2
3
4
5
6
7
8
9
10void enable_interrupts (void)
{
unsigned long temp;
__asm__ __volatile__("mrs %0, cpsr\n"
"bic %0, %0, #0x80\n"
"msr cpsr_c, %0"
: "=r" (temp)
:
: "memory");
}
IRQ/FIQ关闭:
1 | int disable_interrupts (void) |
ARMv8的中断打开方法可以查看下面这篇文章
uboot下开启ARMv8的定时器中断GIC简介
GIC(Generic Interrupt Controller,官网介绍)是ARM公司设计的通用中断控制器,集成在CPU芯片内部,目前有V1~V4版本,从官网可知,目前主要的型号有GIC-400(V2),GIC-500(V3/V4),GIC-600(V3/V4),其中GIC-400手册可以支持最多8个核心、480个共享中断,GIC-500可以支持最多128个核心、960个共享中断,GIC-600可以支持更多的核心。
GIC的作用:简单来说包含2方面
- 将所有外设的中断统一处理再发送CPU核心,减小CPU核心的复杂度
- 实现软中断,用于各CPU核心之间通讯
- 虚拟中断,用于虚拟机
GIC逻辑拆分
上图显示了GIC的内部逻辑框图,大致可以分成4部分:Distributor、CPU interface、GIC virtual interface、virtual CPU interface
Distributor block:收集所有中断源进行处理,将优先级最高的中断送给CPU interface处理,主要功能如下
- 中断使能开关,包含2级开关:全局开关、每个中断对就的开关
- 设置中断的优先级
- 设置中断分发给哪个CPU
- 设置中断的触发属性:边沿触发、电平触发
- 产生软中断
CPU interface block:决定是否将中断发送给CPU,主要功能如下
- 控制中断与CPU处理器之间的连线开关,如果关闭了,即使Distributor中使能了中断,中断也不会送达CPU,如果开启,则会将当前优先级最高的中断发送给CPU
- 设置priority mask(优先级门限),低于priority mask的中断不被送给CPU
- 设置优先级策略
- 中断ACK响应,中断响应后,Distributor会将中断状态设置成Active(或者Active and pending)
- 中断处理完毕的通知,当中断处理完后,CPU通知CPU interface,表示当前中断处理完成
GIC virtual interface和Virtual CPU interface:用于虚拟机操作,一般用不上,本文不作详细描述
从图中还可以看出,从处理器输入到GIC的中断可以不经过Distributor和 CPU interface,这种模式称之为bypass中断
GIC中断编号&分组
GIC v2支持ID0-ID1019,总共1020个中断,不过实际的硬件(比如GIC-400)不会实现这么多
- ID0-ID31是私在中断,即每个CPU都对应独立的ID0-ID31
- ID32-ID1019 用于SPI(share Peripheral interrupt),这些中断用于CPU的外设,比如GPIO、USB等,共享的意思是各个CPU共享这些中断,即这些中断被所有CPU共享,可以分配给到指定的CPU来处理
- ID0-ID15用于SGI(Software-generated interrupt),可以通过写寄存器产生软件中断
- ID16-ID31用于PPI(Private Peripheral Interrupt),与SPI相对应,私有的意思是指每个CPU都会对应独立ID16-ID31
GIC设计中,中断分成2个组(group0、group1),group0里的中断是安全中断,gropu1里的中断是非安全中断,group0的中断可以配置成IRQ或FIQ,goup1的中断是IRQ中断,如果CPU当前处理非安全状态,则读取不到安全中断的状态。
注:目前linux没有使用分组功能,默认都使用group0中断
GIC中断处理状态机
- Inactive:没有中断
- Pending: 中断已经产生了,等待CPU处理
- Active:中断正在被处理,还没结束
- Active and pending:中断正在处理,又有新的中断产生
各状态机切换条件:
A1/A2:添加 Pending状态
- 对于SGI,有2种方法:写GICD_SGIR寄存器(产生1个中断),或者写GICD_SPENDSGIR寄存器(将状态设置成pending)
- 对于SPI 和PPI,也是2种方法:硬件产生,或者写GICD_ISPENDR寄存器
B1/B2:移除Pending状态
- 对于SGI,设置GICD_CPENDSGIR寄存器
- 对于SPI 和 PPI
- 对于电平触发的中断,电平改变后,Pending状态会移除
- 对于边沿触发以及写GICD_ISPENDR产生的中断,需要写GICD_ICPENDR寄存器来移除Pendding状态
C:pending到active
- 相应的中断需要使能
- 优先级不低于门限值
- 读GICC_IAR寄存器里发生状态切换
D:pending 到active and pending
当读GICC_IAR寄存器时发生pending条件
E1/E2:移除active
写 GICC_EOIR 或GICC_DIR寄存器
GIC-400硬件介绍
上图显示了GIC-400的框图,从框图可以看出外设与CPU核心不直接相连,外设中断通过GIC通知CPU核心。
GIC-400功能:
- 有16个SGI(通过写GICD_SGIR寄存器产生中断)
- 每个CPU有6个外部PPI和1个内部虚拟PPI
- 最多 480个SPI
- 中断分组功能(通过写 GICD_IGROUPR寄存器来配置)
PPI功能分配见下图
其中28和31是 bypass中断
GIC-400中断优先级:
- 通过GICD_IPRIORITYR寄存器来配置优先级
- 安全模式有32个优先级,非安全模式有16个优先级
- 当有2个或更多相同优先级的中断出现时,编号小的中断优先级较高
寄存器说明
GIC寄存器地址:GIC在CPU地址空间的其地址是不固定的,GIC内部的寄存器偏移地址是固定的,因为linux代码中GIC的驱动代码可以做到各平台统一,只需要传递几个(Distributor、CPU interface等)基地址即可
本文主讲Distributor和CPU interfaces寄存器:
Distributor寄存器,以GICD_头,偏移地址:0x1000
GICD_CTLR(0x0000):只有bit[1:0]有用,控制group0、group1的全局开关
GICD_TYPER(0x0004):只读,查看GIC实现了哪些功能及配置,比如CPU数量、中断线数量等
GICD_IIDR(0x0008):ID寄存器,只读,GIC-400是0x0200143B
GICD_IGROUPRn(0x0080-0x00BC):中断分组,linux只使用默认值0
GICD_ISENABLERn(0x0100-0x013C):interrupt Set-Enable,使能中断
GICD_ICENABLERn(0x0180-0x01BC):Interrupt Clear-Enable ,关闭中断
GICD_ISPENDRn(0x0200-0x023C):Interrupt Set-Pending ,将中断设置成Pending状态,不适应SGI中断
GICD_SPENDSGIRn(0x0F20-0x0F2C):SGI Set-Pending,同上,用于SGI中断,每个有8bits,对应8个CPU核心
GICD_ICPENDRn(0x0280-0x02BC):Interrupt Clear-Pending,移除中断的Pending状态,不适应SGI中断
GICD_CPENDSGIRn(0x0F10-0x0F1C):SGI Clear-Pending Registers,同上,用于SGI中断
GICD_ISACTIVERn(0x0300-0x033C):Interrupt Set-Active ,将中断设置成Active状态
GICD_ICACTIVERn(0x0380-0x03BC):Interrupt Clear-Active,移除中断的Active状态
GICD_IPRIORITYRn(0x0400-0x05FC):Interrupt Priority,中断优先级设置
每个中断有8个bits设置,1个寄存器包含4个中断的配置
GIC-400的可用优先级个数是32,只有bit[7:3]有效
linux中全部使用0xA0
GICD_ITARGETSRn(0x0800-0x081C):Interrupt Processor Targets,设置中断分配给哪个CPU
每个中断有8个bits设置,1个寄存器包含4个中断的配置
其中前8个寄存器为只读,只会返回当前CPU的值(即每个CPU去读取时返回的值都不一样)
对于SPI中断,Linux默认将其分配给CPU0处理
GICD_ICFGRn(0x0C08-0x0C7C):Interrupt Configuration,中断配置寄存器,电平触发还是边沿触发
每个中断有2个bits设置,1个寄存器包含16个中断的配置
GICD_ICFGR0、GICD_ICFGR1用于SGI和PPI,只读
GICD_PPISR(0x0D00):PPI中断状态寄存器,bit[31:25]有效
GICD_SPISRn(0x0D04-0x0D3C):SPI中断状态寄存器
GICD_SGIR(0x0F00):Software Generated Interrupt,写该寄存器产生SGI中断,可以控制往哪个(哪些)CPU发送中断
CPU interfaces寄存器,以GICC_头,偏移地址:0x2000
GICC_CTLR(0x0000):CPU Interface Control,控制中断是否传给CPU核,控制中断是IRQ还是FIQ
GICC_PMR(0x0004):Priority Mask,控制优先级门限,低于门限的中断不会转发到CPU
设置方法同GICD_IPRIORITYRn
linux中使用0xF0
GICC_BPR(0x0008):Binary Point Register,命名不好,从名字上看不出意思,实际作用是将优先级分成2部分:Group priority 、Subpriority,只使用优先级的Group priority部分,忽略Subpriority部分
详解:比如GIC-400的默认值是2,则优先级的[7:3]作为Group priority ,[2:0]作为Subpriority,则总共可生成32个优先级,与GICD_IPRIORITYRn保持一致
GICC_IAR(0x000C):Interrupt Acknowledge,只读,可以查看当前pending的最高优先级中断
GICC_EOIR(0x0010):End of Interrupt,只写,中断处理完成后需要写该寄存器
GICC_RPR(0x0014):Running Priority,只读,当前处理中断的优先级
GICC_HPPIR(0x0018):Highest Priority Pending ,只读,处于Pending状态的中断里优先级最高的
GICC_IIDR(0x00FC):ID寄存器,GIC-400的值是0x0202143B
GICC_DIR(0x1000):Deactivate Interrupt ,只写,移除中断的Active状态
编程指导
使用步骤:
- 打开ARM核的中断开关,CPSR寄存器
- 打开Distributor的局开关:GICD_CTLR[0]
- 打开Distributor的中断开关:寄存器GICD_ISENABLER/GICD_ICENABLER
- 配置中断触发方式:GICD_ICFGRn
- 等待中断
- 读取GICC_IAR寄存器,获取中断编号
- 执行中断程序
- 写GICC_EOIR寄存器