GIC通用中断控制器

1. GIC简介

操作系统中,中断是很重要的组成部分。有了中断系统才可以不用一直轮询(polling)是否有事件发生,系统效率才得以提高。一般在系统中,中断控制分为三个部分:模块、中断控制器和处理器。其中模块通常由寄存器控制是否使能中断和中断触发条件等;中断控制器可以管理中断的优先级等,而处理器则由寄存器设置用来响应中断。

1.1 GIC结构

作为 ARM 系统中通用中断控制器的是 GIC(Generic Interrupt Controller),目前有四个版本,V1~V4(V2最多支持8个ARM core,V3/V4支持更多的ARM core,主要用于ARM64系统结构)。本文主要描述的是GIC-400,它更适合嵌入式系统,符合v2版本的GIC architecture specification。GIC-400通过AMBA(Advanced Microcontroller Bus Architecture)片上总线连接到一个或者多个ARM处理器上。

GIC通用中断控制器_第1张图片

图 1.1 GIC中断控制器全局图

从图 1.1中可以看出, GIC 是联系外设中断和 CPU 的桥梁,也是各 CPU 之间中断互联的通道(也带有管理功能),它负责检测、管理、分发中断,可以做到:

1、使能或禁止中断;

2、把中断分组到Group0还是Group1(Group0作为安全模式使用连接FIQ  ,Group1 作为非安全模式使用,连接IRQ );

3、多核系统中将中断分配到不同处理器上;

4、设置电平触发还是边沿触发方式(不等于外设的触发方式);

5、虚拟化扩展。

ARM CPU 对外的连接只有2 个中断: IRQ和FIQ ,相对应的处理模式分别是一般中断(IRQ )处理模式和快速中断(FIQ )处理模式。所以GIC 最后要把中断汇集成2 条线,与CPU 对接。

从图 1.2可以看出, GIC 可以清晰的划分成两个部分:分发器( Distributor )和 CPU 接口(CPU Interface )。

GIC通用中断控制器_第2张图片

图 1.2 GIC中断控制器结构

其中各个子中断使能,设置触发方式,优先级排序,分发到哪个 CPU 上这些功能由分发器负责;总的中断的使能,状态的维护由接口负责。

 

1.1.1 分发器功能

分发器的主要的作用是检测各个中断源的状态,控制各个中断源的行为,分发各个中断源产生的中断事件到指定的一个或者多个CPU接口上。虽然分发器可以管理多个中断源,但是它总是把优先级最高的那个中断请求送往CPU接口。分发器对中断的控制包括:

(a)中断使能或禁能控制。分发器对中断的控制分成两个级别,一个是全局中断的控制(GIC_DIST_CTRL),一旦禁能了全局的中断,那么任何的中断源产生的中断事件都不会被传递到CPU接口;另外一个级别是对针对各个中断源进行控制(GIC_DIST_ENABLE_CLEAR),禁能某一个中断源会导致该中断事件不会分发到CPU接口,但不影响其他中断源产生中断事件的分发。

(b)控制将当前优先级最高的中断事件分发到一个或者一组CPU接口。

(c)优先级控制。

(d)中断属性设定,例如是电平触发还是边沿触发。

(e)中断的设定。

分发器可以管理若干个中断源,这些中断源用ID来标识,我们称之interrupt ID。 

 

1.1.2 CPU接口功能

CPU接口主要用于和CPU进行接口。主要功能包括:

(a)使能或者禁能CPU接口向连接的CPU提交中断事件。对于ARM,CPU接口和CPU之间的中断信号线是nIRQCPU和nFIQCPU。如果禁能了中断,那么即便是分发器分发了一个中断事件到CPU接口,但是也不会提交指定的nIRQ或者nFIQ通知CPU。

(b)ackowledging中断。CPU会向CPU接口应答中断,中断一旦被应答,分发器就会把该中断的状态从pending状态修改成active,如果没有后续pending的中断,那么CPU 接口就会deassert nIRQCPU和nFIQCPU信号线。如果在这个过程中又产生了新的中断,那么分发器就会把该中断的状态从pending状态修改成pending and active。此时,CPU接口仍然会保持nIRQ或者nFIQ信号的asserted状态,也就是向CPU通知下一个中断。

(c)中断处理完毕的通知。当中断处理器处理完了一个中断的时候,会向写CPU 接口的寄存器从而通知GIC已经处理完该中断。做这个动作一方面是通知分发器将中断状态修改为deactive,另外一方面,可以允许其他的pending的中断向CPU接口提交。

(d)设定优先级掩码。通过优先级掩码可以mask掉一些优先级比较低的中断,这些中断不会通知到CPU。

(e)设定中断抢占的策略。

(f)在多个中断事件同时到来的时候,选择一个优先级最高的通知CPU。

 

1.3 中断处理流程

中断的处理流程是:分发器把收集来的中断先缓存,依次把优先级最高的中断请求送往CPU接口,CPU读取一个中断,其实就是读取接口的一个寄存器,只不过这个寄存器存放的是中断号,此时中断的状态由pending转为active,CPU处理完了以后,将中断号写入GIC的接口,告诉GIC处理完了,可以将这个中断清理,如图 1.3。

GIC通用中断控制器_第3张图片

图 1.3 GIC中断处理流程图

 

 

1.4 GIC中断类型

GIC 中断类型有3种:SGI(Software-generated interrupt)、PPI(Private peripheral interrupt )、SPI(Shared peripheral interrupt)。

SGI: SGI为软件可以触发的中断,统一编号为0~15,用于各个core之间的通信。

PPI: PPI为每个 core 的私有外设中断,统一编号为 16-31 。例如每个 CPU 的 local timer 即 Arch Timer 产生的中断就是通过 PPI 发送给 CPU 的(安全为29,非安全为30)。

SPI: SPI 是系统的外设产生的中断,为各个 core 公用的中断,统一编号为 32~1019 ,如 global timer 、uart 、gpio 产生的中断。

 

2、GIC控制器基地址获取

GIC控制器基地址通过调用armPrivatePeriphBaseGet函数获得。如图 2.1所示,Ctrl+h局搜索armPrivatePeriphBaseGet函数,搜索结果如图 2.2所示。

GIC通用中断控制器_第4张图片

图 2.1全局搜索armPrivatePeriphBaseGet函数

 

图 2.2 armPrivatePeriphBaseGet函数搜索结果

参考DDI0464F_cortex_a7_mpcore_r0p5_trm.pdf手册,该指令将协处理器 P15 的寄存器中的数据传送到 ARM处理器寄存器R0中,最终通过armPrivatePeriphBaseGet函数获取。如图 2.3和图 2.4所示。

GIC通用中断控制器_第5张图片

图 2.3 CP15协处理器操作码

GIC通用中断控制器_第6张图片

图 2.4 CP15读取基地址指令

:MRC指令的格式为:

MRC{条件}协处理器编码,协处理器操作码1,目的寄存器,源寄存器1,源寄存器2,协处理器操作码2。

MRC指令用于将协处理器寄存器中的数据传送到ARM处理器寄存器中,若协处理器不能成功完成操作,则产生未定义指令异常。其中协处理器操作码1和协处理器操作码2为协处理器将要执行的操作,目的寄存器为ARM处理器的寄存器,源寄存器1和源寄存器2均为协处理器的寄存器。

指令示例:

MRC P3, 3, R0, C4, C5, 6;该指令将协处理器 P3的寄存器中的数据传送到 ARM处理器寄存器中。

3、GIC控制器结构体说明

GIC控制器中分发器端寄存器结构体定义如图 3.1所示,CPU接口端结构体定义如图 3.2所示。

GIC通用中断控制器_第7张图片

图 3.1分发器端寄存器结构体定义

GIC通用中断控制器_第8张图片

图 3.2 CPU接口端结构体定义

4、GIC相关函数实现

4.1 GIC控制器初始化

4.1.1分发器初始化

对distributor结构体中成员进行操作,关闭所有pending中断并禁能所有中断,同时将所有中断设置为安全模式。具体实现如程序清单 4.1所示。

程序清单 4.1分发器初始化

 

 
  1. VOID armGicInit (VOID)

  2. {

  3. REGISTER GIC_DISTRIBUTOR_REG *pDistributor = armGicIntDistributorGet();

  4. REGISTER INT iCnt;

  5.  
  6. armGicEnable(LW_FALSE); /* First disable the distributor */

  7.  
  8. for (iCnt = 0; iCnt < GIC_INT_NUM; iCnt++) { /* Clear all pending interrupts */

  9. write32(~0, (addr_t)&pDistributor->uiICPENDRn[iCnt]);

  10. }

  11.  
  12. for (iCnt = 0; iCnt < GIC_INT_NUM; iCnt++) { /* Disable all interrupts. */

  13. write32(~0, (addr_t)&pDistributor->uiICENABLERn[iCnt]);

  14. }

  15.  
  16. for (iCnt = 0; iCnt < GIC_INT_GRP_NUM; iCnt++) { /* Set all interrupts to secure */

  17. write32(0, (addr_t)&pDistributor->uiIGROUPRn[iCnt]);

  18. }

  19.  
  20. armGicEnable(LW_TRUE); /* Now enable the distributor */

  21. }

 

4.1.2 CPU 接口初始化

对cpu interface端结构体成员进行操作,设置当前 CPU的优先级掩码255,禁止中断抢占,并使能当前 CPU到 GIC的接口。具体实现如程序清单 4.2所示。

程序清单 4.2 CPU接口初始化

 

 
  1. VOID armGicCpuInit (BOOL bPreemption, UINT32 uiPriority)

  2. {

  3. REGISTER GIC_CPU_INTERFACE_REGS *pInterface = armGicCpuInterfaceGet();

  4.  
  5. armGicCpuPriorityMaskSet(uiPriority); /* Init the GIC CPU interface */

  6.  
  7. if (bPreemption) {

  8. /*

  9. * Enable preemption.

  10. */

  11. write32(GIC_CPU_INTERFACE_PREEM_EN, (addr_t)&pInterface->uiBPR);

  12. } else {

  13. /*

  14. * Disable preemption.

  15. */

  16. write32(GIC_CPU_INTERFACE_PREEM_DISEN, (addr_t)&pInterface->uiBPR);

  17. }

  18.  
  19. armGicCpuEnable(LW_TRUE); /* Enable signaling the CPU */

  20. }

 

4.2 中断向量基地址设置

如图 4.1和图 4.2所示,Ctrl+h全局搜索armVectorBaseAddrSet函数。

GIC通用中断控制器_第9张图片

图 4.1全局搜索armVectorBaseAddrSet函数

GIC通用中断控制器_第10张图片

图 4.2 armVectorBaseAddrSet函数搜索结果

参照DDI0464F_cortex_a7_mpcore_r0p5_trm.pdf手册,如图 4.3所示。

GIC通用中断控制器_第11张图片

图 4.3中断向量基地址介绍

根据提示,再参照DDI0406C_C_arm_architecture_reference_manual.pdf手册如下图 4.4所示。

GIC通用中断控制器_第12张图片

图 4.4中断向量基地址说明

可知该寄存器保存的是中断向量表基址参照bspMap.h文件,中断向量表物理地址即为BSP_CFG_RAM_BASE(0x80000000)。

4.3实现系统中断控制器

系统上电的过程中需要初始化中断处理器,否则后续使用到中断的程序都不能正常运行。

从startup.S中可以看到如图 4.5所示,系统只响应了irq中断,对fiq中断不做处理。

GIC通用中断控制器_第13张图片

图 4.5 irq中断入口地址

关于中断需实现函数如图 4.6所示。

GIC通用中断控制器_第14张图片

图 4.6函数列表

4.4中断服务程序

通过读取硬件寄存器,得到当前产生的中断向量号,调用系统接口archIntHandle进行处理,处理完成后,通知GIC中断已经处理完成。

4.4.1读取寄存器GICC_IAR获取中断号,如程序清单 4.3所示。

程序清单 4.3获取中断号

 

 
  1. UINT32 armGicIrqReadAck (VOID)

  2. {

  3. REGISTER GIC_CPU_INTERFACE_REGS *pInterface = armGicCpuInterfaceGet();

  4. return (read32((addr_t)&pInterface->uiIAR));

  5. }

 

4.4.2向GICC_EOIR写入中断号,通知GIC该中断处理完成,如程序清单 4.4所示。

程序清单 4.4通知GIC该中断处理完成

 

 
  1. VOID armGicIrqWriteDone (UINT32 uiIrqID)

  2. {

  3. REGISTER GIC_CPU_INTERFACE_REGS *pInterface = armGicCpuInterfaceGet();

  4. write32(uiIrqID, (addr_t)&pInterface->uiEOIR);

  5. }

 

4.5使能指定中断

通过设置硬件寄存器,使能指定的中断向量。具体实现如程序清单 4.5所示。

程序清单 4.5使能指定中断

 

 
  1. VOID armGicIntVecterEnable (ULONG ulVector, BOOL bSecurity, ULONG ulPriority, ULONG uiCpuMask)

  2. {

  3. armGicIrqEnable(ulVector, LW_FALSE);

  4. armGicIrqSecuritySet(ulVector, bSecurity);

  5. armGicIrqPrioritySet(ulVector, ulPriority);

  6. armGicIrqTargetSet(ulVector, GIC_CPU_MASK, LW_FALSE);

  7. armGicIrqTargetSet(ulVector, uiCpuMask, LW_TRUE);

  8. armGicIrqEnable(ulVector, LW_TRUE);

  9. }

 

:由于imx6ul是单核处理器,理论上是不需要进行目标CPU选择的,但由于使用了GIC中断控制器,所以需给其目标寄存器组配置为0,即默认选择CPU0。

4.6禁能指定中断

通过设置硬件寄存器,禁能指定中断。具体实现如程序清单 4.6所示。

程序清单 4.6禁能指定中断

 

 
  1. VOID armGicIntVecterDisable (ULONG ulVector)

  2. {

  3. armGicIrqEnable(ulVector, LW_FALSE);

  4. armGicIrqTargetSet(ulVector, GIC_CPU_MASK, LW_FALSE);

  5. }

 

4.7判断中断是否使能

通过读取硬件寄存器,检查指定的中断向量是否使能。具体实现如程序清单 4.7所示。

程序清单 4.7判断指定中断向量是否使能

 

 
  1. BOOL armGicIrqIsEnable (UINT32 uiIrqID)

  2. {

  3. REGISTER GIC_DISTRIBUTOR_REG *pDistributor = armGicIntDistributorGet();

  4. REGISTER UINT32 uiReg = armGicIntRegOffsGet(uiIrqID);

  5. REGISTER UINT32 uiMask = armGicIntBitMaskGet(uiIrqID);

  6.  
  7. return ((read32((addr_t)&pDistributor->uiICENABLERn[uiReg]) & uiMask) ? LW_TRUE : LW_FALSE);

  8. }

 

4.8获取/设置中断优先级

该项由工程默认生成,未实现,如程序清单 4.8所示。

程序清单 4.8获取/设置中断优先级

 

 
  1. ULONG bspIntVectorSetPriority (ULONG ulVector, UINT uiPrio)

  2. {

  3. return (ERROR_NONE);

  4. }

  5. ULONG bspIntVectorGetPriority (ULONG ulVector, UINT *puiPrio)

  6. {

  7. *puiPrio = 0;

  8. return (ERROR_NONE);

  9. }

 

4.9获取/设置中断目标CPU

由于imx6ul为单核处理器,虽然用到了GIC通用中断控制器,但默认给其选择CPU0。

在bsplib.c中具体实现如程序清单 4.9所示。

程序清单 4.9获取/设置中断目标CPU

 

 
  1. ULONG bspIntVectorSetTarget (ULONG ulVector, size_t stSize, const PLW_CLASS_CPUSET pcpuset)

  2. {

  3. return (ERROR_NONE);

  4. }

  5. ULONG bspIntVectorGetTarget (ULONG ulVector, size_t stSize, PLW_CLASS_CPUSET pcpuset)

  6. {

  7. LW_CPU_ZERO(pcpuset);

  8. LW_CPU_SET(0, pcpuset);

  9.  
  10. return (ERROR_NONE);

  11. }

 

5、填充bsplib.c中断相关内容

将对应的函数功能添加到bspLib.c中,如程序清单 5.1所示。

程序清单 5.1 bspLib.c中断相关填充

 

 
  1. /*********************************************************************************************************

  2. 中断相关

  3. *********************************************************************************************************/

  4. VOID bspIntInit (VOID)

  5. {

  6. armGicInit();

  7. /*

  8. * 能够传递到当前 CPU 的中断的最低优先级(255 表示所有中断)

  9. */

  10. armGicCpuInit(LW_FALSE, 255);

  11. armVectorBaseAddrSet(BSP_CFG_RAM_BASE);

  12. }

 
  1. VOID bspIntHandle (VOID)

  2. {

  3. REGISTER UINT32 uiAck = armGicIrqReadAck();

  4. REGISTER UINT32 uiSourceCpu = (uiAck >> 10) & 0x7;

  5. REGISTER UINT32 uiVector = uiAck & 0x1FF;

  6.  
  7. (VOID)uiSourceCpu;

  8.  
  9. archIntHandle((ULONG)uiVector, LW_FALSE);

  10.  
  11. armGicIrqWriteDone(uiAck);

  12. }

 
  1. VOID bspIntVectorEnable (ULONG ulVector)

  2. {

  3. /*

  4. * 1 << 0 : 目标CPU为CPU0

  5. */

  6. armGicIntVecterEnable(ulVector, LW_FALSE, ARM_DEFAULT_INT_PRIORITY, 1 << 0);

  7. }

 
  1. VOID bspIntVectorDisable (ULONG ulVector)

  2. {

  3. armGicIntVecterDisable(ulVector);

  4. }

 
  1. BOOL bspIntVectorIsEnable (ULONG ulVector)

  2. {

  3. return (armGicIrqIsEnable(ulVector) ? LW_TRUE : LW_FALSE);

  4. }

 
  1. ULONG bspIntVectorSetPriority (ULONG ulVector, UINT uiPrio)

  2. {

  3. return (ERROR_NONE);

  4. }

 
  1. ULONG bspIntVectorGetPriority (ULONG ulVector, UINT *puiPrio)

  2. {

  3. *puiPrio = 0;

  4. return (ERROR_NONE);

  5. }

 
  1. ULONG bspIntVectorSetTarget (ULONG ulVector, size_t stSize, const PLW_CLASS_CPUSET pcpuset)

  2. {

  3. return (ERROR_NONE);

  4. }

 
  1. ULONG bspIntVectorGetTarget (ULONG ulVector, size_t stSize, PLW_CLASS_CPUSET pcpuset)

  2. {

  3. LW_CPU_ZERO(pcpuset);

  4. LW_CPU_SET(0, pcpuset);

  5. return (ERROR_NONE);

  6. }

 

至此,与中断控制器相关的内容填充完毕,之后在驱动中使用API_InterVectorConnect连接中断服务程序和中断号,使用API_InterVectorEnable使能中断等操作时就可以正常产生中断,并进入驱动对应的中断服务程序中了。

6、参考资料

文档类:

1. gic_architecture_specification.pdf

2. DDI0471B_gic400_r0p1_trm.pdf

3. IHI 0048B, ARM Generic Interrupt Controller Architecture Specification, Ver 2.0.pdf

网址链接:

1.http://www.wowotech.NET/linux_kenrel/gic_driver.html

2. http://blog.csdn.Net/velanjun/article/details/8757862

3. http://www.ibm.com/developerworks/cn/linux/l-cn-linuxkernelint/index.html

你可能感兴趣的:(体系结构)