本系列将详细描述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
10
void 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
2
3
4
5
6
7
8
9
10
11
int disable_interrupts (void)
{
unsigned long old,temp;
__asm__ __volatile__("mrs %0, cpsr\n"
"orr %1, %0, #0xc0\n"
"msr cpsr_c, %1"
: "=r" (old), "=r" (temp)
:
: "memory");
return (old & 0x80) == 0;
}

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

  1. Distributor block:收集所有中断源进行处理,将优先级最高的中断送给CPU interface处理,主要功能如下

    • 中断使能开关,包含2级开关:全局开关、每个中断对就的开关
    • 设置中断的优先级
    • 设置中断分发给哪个CPU
    • 设置中断的触发属性:边沿触发、电平触发
    • 产生软中断
  2. CPU interface block:决定是否将中断发送给CPU,主要功能如下

    • 控制中断与CPU处理器之间的连线开关,如果关闭了,即使Distributor中使能了中断,中断也不会送达CPU,如果开启,则会将当前优先级最高的中断发送给CPU
    • 设置priority mask(优先级门限),低于priority mask的中断不被送给CPU
    • 设置优先级策略
    • 中断ACK响应,中断响应后,Distributor会将中断状态设置成Active(或者Active and pending)
    • 中断处理完毕的通知,当中断处理完后,CPU通知CPU interface,表示当前中断处理完成
  3. 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状态

  1. 对于SGI,有2种方法:写GICD_SGIR寄存器(产生1个中断),或者写GICD_SPENDSGIR寄存器(将状态设置成pending)
  2. 对于SPI 和PPI,也是2种方法:硬件产生,或者写GICD_ISPENDR寄存器

B1/B2:移除Pending状态

  1. 对于SGI,设置GICD_CPENDSGIR寄存器
  2. 对于SPI 和 PPI
    • 对于电平触发的中断,电平改变后,Pending状态会移除
    • 对于边沿触发以及写GICD_ISPENDR产生的中断,需要写GICD_ICPENDR寄存器来移除Pendding状态

C:pending到active

  1. 相应的中断需要使能
  2. 优先级不低于门限值
  3. 读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等)基地址即可

本文主讲DistributorCPU interfaces寄存器:

  1. 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发送中断

  2. 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状态

编程指导

使用步骤:

  1. 打开ARM核的中断开关,CPSR寄存器
  2. 打开Distributor的局开关:GICD_CTLR[0]
  3. 打开Distributor的中断开关:寄存器GICD_ISENABLER/GICD_ICENABLER
  4. 配置中断触发方式:GICD_ICFGRn
  5. 等待中断
  6. 读取GICC_IAR寄存器,获取中断编号
  7. 执行中断程序
  8. 写GICC_EOIR寄存器