字符设备驱动-9-中断子系统-GICv2架构解析

1 armv7 32位GICv2介绍#

armv7 32位 gic采用v2版本,参考手册 https://developer.arm.com/documentation/ihi0048/bb/?lang=en

image
image

GIC400 就是v2版本的中断控制器 IP 核,当 GIC 接收到外部中断信号以后就会报给 ARM 内核。框架如下:
image

GIC 架构分为了两个逻辑块:DistributorCPU Interface,也就是分发器端和 CPU 接口端。

1.0 分发器端和 CPU 接口端#

  1. 分发器用来全局中断使能控制,每一个中断使能开关,中断优先级,外部中断触发方式(边沿触发、电平触发)等。外设->分发器会设置成pending(或者active and pending状态),这时分发器传递优先级最高的pending中断给cpu 接收端。

  2. cpu接收端用来接收中断信号汇报给cpu, 如:应答中断,通知中断处理完成,定义抢占策略,设置优先级掩码,当多个中断到来选择最高优先级的中断号。

    例如:I.MX6U给了一个 core_ca7.h定义了GIC的所有寄存器。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    /*
    * GIC 寄存器描述结构体,
    * GIC 分为分发器端和 CPU 接口端
    */
    typedef struct {
    /* 分发器端寄存器 */
    int32_t RESERVED0[1024];
    _IOM uint32_t D_CTLR; /* Offset: 0x1000 (R/W) */
    _IM uint32_t D_TYPER; /* Offset: 0x1004 (R/ ) */
    _IM uint32_t D_IIDR; /* Offset: 0x1008 (R/ ) */
    int32_t RESERVED1[29];
    _IOM uint32_t D_IGROUPR[16]; /* Offset: 0x1080 - 0x0BC (R/W) */
    uint32_t RESERVED2[16];
    __IOM uint32_t D_ISENABLER[16];/* Offset: 0x1100 - 0x13C (R/W) */
    uint32_t RESERVED3[16];
    __IOM uint32_t D_ICENABLER[16];/* Offset: 0x1180 - 0x1BC (R/W) */
    uint32_t RESERVED4[16];
    __IOM uint32_t D_ISPENDR[16]; /* Offset: 0x1200 - 0x23C (R/W) */
    uint32_t RESERVED5[16];
    __IOM uint32_t D_ICPENDR[16]; /* Offset: 0x1280 - 0x2BC (R/W) */
    uint32_t RESERVED6[16];
    __IOM uint32_t D_ISACTIVER[16];/* Offset: 0x1300 - 0x33C (R/W) */
    uint32_t RESERVED7[16];
    __IOM uint32_t D_ICACTIVER[16];/* Offset: 0x1380 - 0x3BC (R/W) */
    uint32_t RESERVED8[16];
    __IOM uint8_t D_IPRIORITYR[512];/* Offset: 0x1400 - 0x5FC (R/W) */
    uint32_t RESERVED9[128];
    __IOM uint8_t D_ITARGETSR[512];/* Offset: 0x1800 - 0x9FC (R/W) */
    uint32_t RESERVED10[128];
    __IOM uint32_t D_ICFGR[32]; /* Offset: 0x1C00 - 0xC7C (R/W) */
    uint32_t RESERVED11[32];
    __IM uint32_t D_PPISR; /* Offset: 0x1D00 (R/ ) */
    __IM uint32_t D_SPISR[15]; /* Offset: 0x1D04 - 0xD3C (R/ ) */
    uint32_t RESERVED12[112];
    __OM uint32_t D_SGIR; /* Offset: 0x1F00 ( /W) */
    uint32_t RESERVED13[3];
    __IOM uint8_t D_CPENDSGIR[16];/* Offset: 0x1F10 - 0xF1C (R/W) */
    __IOM uint8_t D_SPENDSGIR[16];/* Offset: 0x1F20 - 0xF2C (R/W) */
    uint32_t RESERVED14[40];
    __IM uint32_t D_PIDR4; /* Offset: 0x1FD0 (R/ ) */
    __IM uint32_t D_PIDR5; /* Offset: 0x1FD4 (R/ ) */
    __IM uint32_t D_PIDR6; /* Offset: 0x1FD8 (R/ ) */
    __IM uint32_t D_PIDR7; /* Offset: 0x1FDC (R/ ) */
    __IM uint32_t D_PIDR0; /* Offset: 0x1FE0 (R/ ) */
    __IM uint32_t D_PIDR1; /* Offset: 0x1FE4 (R/ ) */
    __IM uint32_t D_PIDR2; /* Offset: 0x1FE8 (R/ ) */
    __IM uint32_t D_PIDR3; /* Offset: 0x1FEC (R/ ) */
    __IM uint32_t D_CIDR0; /* Offset: 0x1FF0 (R/ ) */
    __IM uint32_t D_CIDR1; /* Offset: 0x1FF4 (R/ ) */
    __IM uint32_t D_CIDR2; /* Offset: 0x1FF8 (R/ ) */
    __IM uint32_t D_CIDR3; /* Offset: 0x1FFC (R/ ) */

    /* CPU 接口端寄存器 */
    __IOM uint32_t C_CTLR; /* Offset: 0x2000 (R/W) */
    __IOM uint32_t C_PMR; /* Offset: 0x2004 (R/W) */
    __IOM uint32_t C_BPR; /* Offset: 0x2008 (R/W) */
    __IM uint32_t C_IAR; /* Offset: 0x200C (R/ ) */
    __OM uint32_t C_EOIR; /* Offset: 0x2010 ( /W) */
    __IM uint32_t C_RPR; /* Offset: 0x2014 (R/ ) */
    __IM uint32_t C_HPPIR; /* Offset: 0x2018 (R/ ) */
    __IOM uint32_t C_ABPR; /* Offset: 0x201C (R/W) */
    __IM uint32_t C_AIAR; /* Offset: 0x2020 (R/ ) */
    __OM uint32_t C_AEOIR; /* Offset: 0x2024 ( /W) */
    __IM uint32_t C_AHPPIR; /* Offset: 0x2028 (R/ ) */
    uint32_t RESERVED15[41];
    __IOM uint32_t C_APR0; /* Offset: 0x20D0 (R/W) */
    uint32_t RESERVED16[3];
    __IOM uint32_t C_NSAPR0; /* Offset: 0x20E0 (R/W) */
    uint32_t RESERVED17[6];
    __IM uint32_t C_IIDR; /* Offset: 0x20FC (R/ ) */
    uint32_t RESERVED18[960];
    __OM uint32_t C_DIR; /* Offset: 0x3000 ( /W) */
    } GIC_Type;

1.1 GIC 类型#

GIC 将众多的中断源分为分为三类:
①、SPI(Shared Peripheral Interrupt),共享中断,顾名思义,所有 Core 共享的中断,这个是最常见的,那些外部中断都属于 SPI 中断 。
②、PPI(Private Peripheral Interrupt),私有中断,我们说了 GIC 是支持多核的,每个核肯定有自己独有的中断。这些独有的中断肯定是要指定的核心处理,因此这些中断就叫做私有中断。
③、SGI(Software-generated Interrupt),软件中断,由软件触发引起的中断,通过向寄存器GICD_SGIR 写入数据来触发,系统会使用 SGI 中断来完成多核之间的通信。

1.2 中断 ID#

为了区分这些不同的中断源肯定要给他们分配一个唯一 ID,这些 ID 就是中断 ID。每一个 CPU 最多支持 1020 个中断 ID,ID0~ID1019 中断使能和禁止。这 1020 个 ID 包含了 PPI、SPI 和 SGI。

ID0~ID15:这 16 个 ID 分配给 SGI。
ID16~ID31:这 16 个 ID 分配给 PPI。
ID32~ID1019:这 988 个 ID 分配给 SPI。

例如:I.MX6U 的总共使用了 128 个中断 ID,加上前面属于 PPI 和 SGI 的 32 个 ID,I.MX6U 的中断源共有 128+32=160,那么irq为0的中断ID即为32。
个。NXP 官方 SDK中的文件 MCIMX6Y2C.h定义了160个中断ID。
image

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#define NUMBER_OF_INT_VECTORS 160 /* 中断源 160 个,SGI+PPI+SPI*/
typedef enum IRQn {
/* Auxiliary constants */
otAvail_IRQn = -128,
/* Core interrupts */
oftware0_IRQn = 0,
oftware1_IRQn = 1,
Software2_IRQn = 2,
Software3_IRQn = 3,
Software4_IRQn = 4,
Software5_IRQn = 5,
Software6_IRQn = 6,
Software7_IRQn = 7,
Software8_IRQn = 8,
Software9_IRQn = 9,
Software10_IRQn = 10,
Software11_IRQn = 11,
Software12_IRQn = 12,
Software13_IRQn = 13,
Software14_IRQn = 14,
Software15_IRQn = 15,
VirtualMaintenance_IRQn = 25,
HypervisorTimer_IRQn = 26,
VirtualTimer_IRQn = 27,
LegacyFastInt_IRQn = 28,
SecurePhyTimer_IRQn = 29,
NonSecurePhyTimer_IRQn = 30,
LegacyIRQ_IRQn = 31,
/* Device specific interrupts */
IOMUXC_IRQn = 32,
DAP_IRQn = 33,
SDMA_IRQn = 34,
TSC_IRQn = 35,
SNVS_IRQn = 36,
...... ......
} IRQn_Type;

1.3 中断配置(1.7有详细描述)#

1.3.1 IRQ 和 FIQ 总中断使能#

CPSR程序状态寄存器”已经讲过了,寄存器 CPSR 的 I=1 禁止 IRQ,当 I=0 使
能 IRQ;F=1 禁止 FIQ,F=0 使能 FIQ。我们还有更简单的指令来完成 IRQ 或者 FIQ 的使能和
禁止:

指令 描述
cpsid i 禁止 IRQ 中断。
cpsie i 使能 IRQ 中断。
cpsid f 禁止 FIQ 中断。
cpsie f 使能 FIQ 中断。

1.3.2 ID0~ID1019 中断使能和禁止#

前面讲到中断ID有 ID0~ID1019, GIC 寄存器 GICD_ISENABLERn GICD_ ICENABLERn 用来完成外部中断的使能和禁止,对于 Cortex-A7 内核来说中断 ID 只使用了 512 个。
一共16组GICD_ISENABLERGICD_ISENABLER,其中GICD_ISENABLER0 bit[15:0]对应ID15~0 的 SGI 中断,GICD_ISENABLER0 bit[31:16]对应ID31~16的 PPI 中断。剩下的GICD_ISENABLER1~GICD_ISENABLER15 就是控制 SPI 中断的。

1.3.3 中断优先级数量 GICC_PMR#

Cortex-A7 GIC 控制器最多可以支持 256 个优先级,数字越小,优先级越高!Cortex-A7 选择了 32 个优先级,GICC_PMR 寄存器,此寄存器用来决定使用几级优先级,GICC_PMR 寄存器只有低 8 位有效,这 8 位最多可以设置 256 个优先级:
image
I.MX6U 为例 Cortex-A7内核,所以支持 32 个优先级,因此 GICC_PMR 要设置为 0b11111000

1.3.4 中断抢占优先级和子优先级位数 GICC_BPR#

寄存器 GICC_BPR 只有低 3 位有效,其值不同,抢占优先级和子优先级占用的位数也不同:
image
比如 I.MX6U 的优先级位数为 5(32 个优先级),所以可以设置 Binary point 为 2,表示 5 个优先级位全部为抢占优先级。

1.3.5 中断priority D_IPRIORITYR#

Cortex-A7 使用了 512 个中断 ID,每个中断 ID 配有一个优先级寄存器,所以一共有 512 个 D_IPRIORITYR 寄存器。如果优先级个数为 32 的话,使用寄存器 D_IPRIORITYR 的 bit7:4 来设置优先级,也就是说实际的优先级要左移 3 位。比如要设置ID40 中断的优先级为 5,示例代码如下:

1
GICD_IPRIORITYR[40] = 5 << 3;

1.4 中断状态机#

非活动状态(Inactive):这意味着该中断未触发。

挂起(Pending):这意味着中断源已被触发,但正在等待CPU核处理。待处理的中断要通过转发到CPU接口单元,然后再由CPU接口单元转发到内核。

活动(Active):描述了一个已被内核接收并正在处理的中断。

活动和挂起(Active and pending):描述了一种情况,其中CPU核正在为中断服务,而GIC又收到来自同一源的中断。

1.5 GIC初始化硬件流程(软件流程见3.3)#

image

1.6 GIC中断处理#

当CPU核接收到中断时,cpu interface中有Interrupt Acknowledge Register可以读,获取中断ID。并且标记为active状态。

当对应的中断服务程序执行完,会将中断ID写入CPU interface模块中的End of Interrupt register。标记为inactivepending(如果状态为inactive and pending)。

1.7 GIC控制器寄存器介绍#

前面讲了GIC 架构分为了两个逻辑块:Distributor CPU Interface,也就是分发器端和 CPU 接口端。

1.7.1 GIC的内存映射#

image
GIC基地址偏移0x1000是分发器 block, 偏移0x2000是CPU 接口端 block。

1.7.1.1 分发器寄存器#

image

1.7.1.1.1 GICD_CTLR(Distributor Control Register)#

Distributor Control Register,分发器控制寄存器。

image

位域 读写 描述
1 EnableGrp1 R/W 用于将pending Group 1中断从Distributor转发到CPU interfaces 0:group 1中断不转发 1:根据优先级规则转发Group 1中断
0 EnableGrp0 R/W 用于将pending Group 0中断从Distributor转发到CPU interfaces 0:group 0中断不转发 1:根据优先级规则转发Group 0中断
1.7.1.1.2 GICD_TYPER(Controller Type Register)#

image

位域 读写 描述
15:11 LSPI R 如果GIC实现了安全扩展,则此字段的值是已实现的可锁定SPI的最大数量,范围为0(0b00000)到31(0b11111)。 如果此字段为0b00000,则GIC不会实现配置锁定。 如果GIC没有实现安全扩展,则保留该字段。
10 SecurityExtn R 表示GIC是否实施安全扩展: 0未实施安全扩展; 1实施了安全扩展
7:5 CPUNumber R 表示已实现的CPU interfaces的数量。 已实现的CPU interfaces数量比该字段的值大1。 例如,如果此字段为0b011,则有四个CPU interfaces。
4:0 ITLinesNumber R 表示GIC支持的最大中断数。 如果ITLinesNumber = N,则最大中断数为32*(N+1)。 中断ID的范围是0到(ID的数量– 1)。 例如:0b00011最多128条中断线,中断ID 0-127。 中断的最大数量为1020(0b11111)。 无论此字段定义的中断ID的范围如何,都将中断ID 1020-1023保留用于特殊目的
1.7.1.1.3 GICD_IIDR(Implementer Identification Register)#

image

位域 读写 描述
31:24 ProductID R 产品标识ID
23:20 保留
19:16 Variant R 通常是产品的主要版本号
15:12 Revision R 通常此字段用于区分产品的次版本号
11:0 Implementer R 含有实现这个GIC的公司的JEP106代码; [11:8]:JEP106 continuation code,对于ARM实现,此字段为0x4; [7]:始终为0; [6:0]:实现者的JEP106code,对于ARM实现,此字段为0x3B
1.7.1.1.4 GICD_IGROUPRn(Group Registers)#

image

位域 读写 描述
31:0 Group status bits R/W 组状态位,对于每个位: 0:相应的中断为Group 0; 1:相应的中断为Group 1。

对于一个中断,如何设置它的Group ?首先找到对应的GICD_IGROUPRn寄存器,即n是多少?还要确定使用这个寄存器里哪一位。

对于interrtups ID m,如下计算:

1
2
3
4
n = m DIV 32,GICD_IGROUPRn里的n就确定了;
GICD_IGROUPRn在GIC内部的偏移地址是多少?0x080+(4*n)
使用GICD_IPRIORITYRn中哪一位来表示interrtups ID m?
bit = m mod 32
1.7.1.1.5 GICD_ISENABLERn(Set-Enable Registers)#

image

位域 读写 描述
31:0 Set-enable bits R/W 对于SPI和PPI类型的中断,每一位控制对应中断的转发行为:从Distributor转发到CPU interface: 读: 0:表示当前是禁止转发的; 1:表示当前是使能转发的; 写: 0:无效 1:使能转发

对于一个中断,如何找到GICD_ISENABLERn并确定相应的位?

1
2
3
4
5
对于interrtups ID m,如下计算:
n = m DIV 32,GICD_ISENABLERn里的n就确定了;
GICD_ISENABLERn在GIC内部的偏移地址是多少?0x100+(4*n)
使用GICD_ISENABLERn中哪一位来表示interrtups ID m?
bit = m mod 32
1.7.1.1.6 GICD_ICENABLERn(Clear-Enable Registers)#

img

位域 读写 描述
31:0 Clear-enable bits R/W 对于SPI和PPI类型的中断,每一位控制对应中断的转发行为:从Distributor转发到CPU interface: 读: 0:表示当前是禁止转发的; 1:表示当前是使能转发的; 写: 0:无效 1:禁止转发

对于一个中断,如何找到GICD_ICENABLERn并确定相应的位?

1
2
3
4
5
对于interrtups ID m,如下计算:
n = m DIV 32,GICD_ISENABLERn里的n就确定了;
GICD_ISENABLERn在GIC内部的偏移地址是多少?0x100+(4*n)
使用GICD_ISENABLERn中哪一位来表示interrtups ID m?
bit = m mod 32
1.7.1.1.7 GICD_ISACTIVERn(Set-Active Registers)#

img

位域 读写 描述
31:0 Set-active bits R/W 读: 0:表示相应中断不是active状态; 1:表示相应中断是active状态; 写: 0:无效 1:把相应中断设置为active状态,如果中断已处于Active状态,则写入无效

对于一个中断,如何找到GICD_ISACTIVERn并确定相应的位?

1
2
3
4
5
对于interrtups ID m,如下计算:
n = m DIV 32,GICD_ISACTIVERn里的n就确定了;
GICD_ISACTIVERn在GIC内部的偏移地址是多少?0x300+(4*n)
使用GICD_ISACTIVERn 中哪一位来表示interrtups ID m?
bit = m mod 32。
1.7.1.1.8 GICD_ICACTIVERn(Clear-Active Registers)#
位域 读写 描述
31:0 Clear-active bits R/W 读: 0:表示相应中断不是active状态; 1:表示相应中断是active状态; 写: 0:无效 1:把相应中断设置为deactive状态,如果中断已处于dective状态,则写入无效

对于一个中断,如何找到GICD_ICACTIVERn并确定相应的位?

1
2
3
4
5
对于interrtups ID m,如下计算:
n = m DIV 32,GICD_ICACTIVERn里的n就确定了;
GICD_ICACTIVERn 在GIC内部的偏移地址是多少?0x380+(4*n)
使用GICD_ICACTIVERn中哪一位来表示interrtups ID m?
bit = m mod 32。
1.7.1.1.9 GICD_IPRIORITYRn(Priority Registers)#

img

位域 读写 描述
31:24 Priority, byte offset 3 R/W 对于每一个中断,都有对应的8位数据用来描述:它的优先级。 每个优先级字段都对应一个优先级值,值越小,相应中断的优先级越高
23:16 Priority, byte offset 2 R/W
15:8 Priority, byte offset 1 R/W
7:0 Priority, byte offset 0 R/W

如何设置它的优先级(Priority),首先找到对应的GICD_IPRIORITYRn寄存器,即n是多少?还要确定使用这个寄存器里哪一个字节。

1
2
3
4
5
6
7
8
9
对于interrtups ID m,如下计算:
n = m DIV 4,GICD_IPRIORITYRn里的n就确定了;
GICD_IPRIORITYRn在GIC内部的偏移地址是多少?0x400+(4*n)
使用GICD_IPRIORITYRn中4个字节中的哪一个来表示interrtups ID m的优先级?
byte offset = m mod 4
byte offset 0对应寄存器里的[7:0];
byte offset 1对应寄存器里的[15:8];
byte offset 2对应寄存器里的[23:16];
byte offset 3对应寄存器里的[31:24]。
1.7.1.1.10 GICD_ITARGETSRn(Processor Targets Registers)#

img

位域 读写 描述
31:24 CPU targets, byte offset 3 R/W 对于每一个中断,都有对应的8位数据用来描述:这个中断可以发给哪些CPU。 处理器编号从0开始,8位数里每个位均指代相应的处理器。 例如,值0x3表示将中断发送到处理器0和1。 当读取GICD_ITARGETSR0~GICD_ITARGETSR7时,读取里面任意字节,返回的都是执行这个读操作的CPU的编号。
23:16 CPU targets, byte offset 2 R/W
15:8 CPU targets, byte offset 1 R/W
7:0 CPU targets, byte offset 0 R/W

如何设置它的目杯CPU?优先级(Priority),首先找到对应的GICD_ITARGETSRn寄存器,即n是多少?还要确定使用这个寄存器里哪一个字节。

1
2
3
4
5
6
7
8
9
对于interrtups ID m,如下计算:
n = m DIV 4,GICD_ITARGETSRn里的n就确定了;
GICD_ITARGETSRn在GIC内部的偏移地址是多少?0x800+(4*n)
使用GICD_ITARGETSRn中4个字节中的哪一个来表示interrtups ID m的目标CPU?
byte offset = m mod 4。
byte offset 0对应寄存器里的[7:0];
byte offset 1对应寄存器里的[15:8];
byte offset 2对应寄存器里的[23:16];
byte offset 3对应寄存器里的[31:24]。
1.7.1.1.11 GICD_ICFGRn(Configuration Registers)#

img

位域 读写 描述
[2F+1:2F] Int_config, field F R/W 对于每一个中断,都有对应的2位数据用来描述:它的边沿触发,还是电平触发。 对于Int_config [1],即高位[2F + 1],含义为: 0:相应的中断是电平触发; 1:相应的中断是边沿触发。 对于Int_config [0],即低位[2F],是保留位。

如何找到GICD_ICFGRn并确定相应的位域F?

1
2
3
4
对于interrtups ID m,如下计算:
n = m DIV 16,GICD_ICFGRn里的n就确定了;
GICD_ICACTIVERn 在GIC内部的偏移地址是多少?0xC00+(4*n)
F = m mod 16
1.7.1.1.12 ICPIDR2(Identification registers: Peripheral ID2 Register)#

img

位域 读写 描述
[31:0] - R/W 由实现定义
[7:4] ArchRev R 该字段的值取决于GIC架构版本: 0x1:GICv1; 0x2:GICv2。
[3:0] - R/W 由实现定义

1.7.1.2 cpu接口端寄存器#

image

1.7.1.2.1 GICC_CTLR(CPU Interface Control Register)#

img

位域 读写 描述
[31:10] - 保留
[9] EOImodeNS R/W 控制对GICC_EOIR和GICC_DIR寄存器的非安全访问: 0:GICC_EOIR具有降低优先级和deactivate中断的功能; 对GICC_DIR的访问是未定义的。 1:GICC_EOIR仅具有降低优先级功能; GICC_DIR寄存器具有deactivate中断功能。
[8:7] - 保留
[6] IRQBypDisGrp1 R/W 当CPU interface的IRQ信号被禁用时,该位控制是否向处理器发送bypass IRQ信号: 0:将bypass IRQ信号发送给处理器; 1:将bypass IRQ信号不发送到处理器。
[5] FIQBypDisGrp1 R/W 当CPU interface的FIQ信号被禁用时,该位控制是否向处理器发送bypass FIQ信号: 0:将bypass FIQ信号发送给处理器; 1:旁路FIQ信号不发送到处理器
[4:1] - 保留
[0] - R/W 使能CPU interface向连接的处理器发出的组1中断的信号: 0:禁用中断信号 1:使能中断信号
1.7.1.2.2 GICC_PMR(Priority Mask Register)#

img

位域 读写 描述
[31:8] - 保留
[7:0] - R/W 优先级高于这个值的中断,才会发送给CPU

[7:0]共8位,可以表示256个优先级。但是某些芯片里的GIC支持的优先级少于256个,则某些位为RAZ / WI,如下所示:

1
2
3
4
如果有128个级别,则寄存器中bit[0] = 0b0,即使用[7:1]来表示优先级;
如果有64个级别,则寄存器中bit[1:0] = 0b00,即使用[7:2]来表示优先级;
如果有32个级别,则寄存器中bit[2:0] = 0b000,即使用[7:3]来表示优先级;
如果有16个级别,则寄存器中bit[3:0] = 0b0000,即使用[7:4]来表示优先级;
1.7.1.2.3 GICC_BPR(Binary Point Register)#

此寄存器用来把8位的优先级字段拆分为组优先级和子优先级,组优先级用来决定中断抢占。

位域 读写 描述
[31:3] - 保留
[2:0] Binary point R/W 此字段的值控制如何将8bit中断优先级字段拆分为组优先级和子优先级,组优先级用来决定中断抢占。 更多信息还得看看GIC手册。
1.7.1.2.4 GICC_IAR(Acknowledge Register)#

读此寄存器,获得当前中断的interrtup ID

GICC_IAR寄存器描述来自《ARM Generic Interrupt Controller Architecture Specification.pdf》,它用来表示中断ID号。
image
处理完具体的中断处理函数,需要将GICC_IAR寄存器的值写入GICC_EOIR寄存器中。

位域 读写 描述
[31:13] - 保留
[12:10] CPUID R 对于SGI类中断,它表示谁发出了中断。例如,值为3表示该请求是通过对CPU interface 3上的GICD_SGIR的写操作生成的。
[9:0] Interrupt ID R 中断ID
1.7.1.2.5 GICC_EOIR(Interrupt Register)#

写此寄存器,表示某中断已经处理完毕。GICC_IAR的值表示当前在处理的中断,把GICC_IAR的值写入GICC_EOIR就表示中断处理完了。
image

位域 读写 描述
[31:13] - 保留
[12:10] CPUID W 对于SGI类中断,它的值跟GICD_IAR. CPUID的相同。
[9:0] EOIINTID W 中断ID,它的值跟GICD_IAR里的中断ID相同

2. 中断示例start.s分析#

以nxp的IMX6UL为例,SDK中core_ca7.h定了了GIC相关函数:

函数 描述
GIC_Init 初始化 GIC。
GIC_EnableIRQ 使能指定的外设中断。
GIC_DisableIRQ 关闭指定的外设中断。
GIC_AcknowledgeIRQ 返回中断号。
GIC_DeactivateIRQ 无效化指定中断。
GIC_GetRunningPriority 获取当前正在运行的中断优先级。
GIC_SetPriorityGrouping 设置抢占优先级位数。
GIC_GetPriorityGrouping 获取抢占优先级位数。
GIC_SetPriority 设置指定中断的优先级。
GIC_GetPriority 获取指定中断的优先级。
点击查看代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
.global _start  				/* 全局标号 */
_start:
ldr pc, =Reset_Handler /* 复位中断 */
ldr pc, =Undefined_Handler /* 未定义中断 */
ldr pc, =SVC_Handler /* SVC(Supervisor)中断 */
ldr pc, =PrefAbort_Handler /* 预取终止中断 */
ldr pc, =DataAbort_Handler /* 数据终止中断 */
ldr pc, =NotUsed_Handler /* 未使用中断 */
ldr pc, =IRQ_Handler /* IRQ中断 */
ldr pc, =FIQ_Handler /* FIQ(快速中断)未定义中断 */
/* 复位中断 */
Reset_Handler:
cpsid i /* 关闭全局中断 */
/* 关闭I,DCache和MMU
* 采取读-改-写的方式。
*/
mrc p15, 0, r0, c1, c0, 0 /* 读取CP15的C1寄存器到R0中 */
bic r0, r0, #(0x1 << 12) /* 清除C1寄存器的bit12位(I位),关闭I Cache */
bic r0, r0, #(0x1 << 2) /* 清除C1寄存器的bit2(C位),关闭D Cache */
bic r0, r0, #0x2 /* 清除C1寄存器的bit1(A位),关闭对齐 */
bic r0, r0, #(0x1 << 11) /* 清除C1寄存器的bit11(Z位),关闭分支预测 */
bic r0, r0, #0x1 /* 清除C1寄存器的bit0(M位),关闭MMU */
mcr p15, 0, r0, c1, c0, 0 /* 将r0寄存器中的值写入到CP15的C1寄存器中 */
#if 0
/* 汇编版本设置中断向量表偏移 */
ldr r0, =0X87800000

dsb
isb
mcr p15, 0, r0, c12, c0, 0
dsb
isb
#endif
/* 设置各个模式下的栈指针,
* 注意:IMX6UL的堆栈是向下增长的!
* 堆栈指针地址一定要是4字节地址对齐的!!!
* DDR范围:0X80000000~0X9FFFFFFF
*/
/* 进入IRQ模式 */
mrs r0, cpsr
bic r0, r0, #0x1f /* 将r0寄存器中的低5位清零,也就是cpsr的M0~M4 */
orr r0, r0, #0x12 /* r0或上0x13,表示使用IRQ模式 */
msr cpsr, r0 /* 将r0 的数据写入到cpsr_c中 */
ldr sp, =0x80600000 /* 设置IRQ模式下的栈首地址为0X80600000,大小为2MB */
/* 进入SYS模式 */
mrs r0, cpsr
bic r0, r0, #0x1f /* 将r0寄存器中的低5位清零,也就是cpsr的M0~M4 */
orr r0, r0, #0x1f /* r0或上0x13,表示使用SYS模式 */
msr cpsr, r0 /* 将r0 的数据写入到cpsr_c中 */
ldr sp, =0x80400000 /* 设置SYS模式下的栈首地址为0X80400000,大小为2MB */
/* 进入SVC模式 */
mrs r0, cpsr
bic r0, r0, #0x1f /* 将r0寄存器中的低5位清零,也就是cpsr的M0~M4 */
orr r0, r0, #0x13 /* r0或上0x13,表示使用SVC模式 */
msr cpsr, r0 /* 将r0 的数据写入到cpsr_c中 */
ldr sp, =0X80200000 /* 设置SVC模式下的栈首地址为0X80200000,大小为2MB */
cpsie i /* 打开全局中断 */
#if 0
/* 使能IRQ中断 */
mrs r0, cpsr /* 读取cpsr寄存器值到r0中 */
bic r0, r0, #0x80 /* 将r0寄存器中bit7清零,也就是CPSR中的I位清零,表示允许IRQ中断 */
msr cpsr, r0 /* 将r0重新写入到cpsr中 */
#endif
b main /* 跳转到main函数 */
/* 未定义中断 */
Undefined_Handler:
ldr r0, =Undefined_Handler
bx r0
/* SVC中断 */
SVC_Handler:
ldr r0, =SVC_Handler
bx r0
/* 预取终止中断 */
PrefAbort_Handler:
ldr r0, =PrefAbort_Handler
bx r0
/* 数据终止中断 */
DataAbort_Handler:
ldr r0, =DataAbort_Handler
bx r0
/* 未使用的中断 */
NotUsed_Handler:
ldr r0, =NotUsed_Handler
bx r0

/* IRQ中断!重点!!!!! */
IRQ_Handler:
push {lr} /* 保存lr地址 */
push {r0-r3, r12} /* 保存r0-r3,r12寄存器 */

mrs r0, spsr /* 读取spsr寄存器 */
push {r0} /* 保存spsr寄存器 */

mrc p15, 4, r1, c15, c0, 0 /* 从CP15的C0寄存器内的值到R1寄存器中
* 参考文档ARM Cortex-A(armV7)编程手册V4.0.pdf P49
* Cortex-A7 Technical ReferenceManua.pdf P68 P138
*/
add r1, r1, #0X2000 /* GIC基地址加0X2000,也就是GIC的CPU接口端基地址 */
ldr r0, [r1, #0XC] /* GIC的CPU接口端基地址加0X0C就是GICC_IAR寄存器,
* GICC_IAR寄存器保存这当前发生中断的中断号,我们要根据
* 这个中断号来绝对调用哪个中断服务函数
*/
push {r0, r1} /* 保存r0,r1 */

cps #0x13 /* 进入SVC模式,允许其他中断再次进去 */

push {lr} /* 保存SVC模式的lr寄存器 */
ldr r2, =system_irqhandler /* 加载C语言中断处理函数到r2寄存器中*/
blx r2 /* 运行C语言中断处理函数,带有一个参数,保存在R0寄存器中 */

pop {lr} /* 执行完C语言中断服务函数,lr出栈 */
cps #0x12 /* 进入IRQ模式 */
pop {r0, r1}
str r0, [r1, #0X10] /* 中断执行完成,写EOIR */

pop {r0}
msr spsr_cxsf, r0 /* 恢复spsr */

pop {r0-r3, r12} /* r0-r3,r12出栈 */
pop {lr} /* lr出栈 */
subs pc, lr, #4 /* 将lr-4赋给pc */
/* FIQ中断 */
FIQ_Handler:

ldr r0, =FIQ_Handler
bx r0

2.1 start.s启动流程#

  1. 进入_start,初始化异常向量表。进入复位中断,初始化时钟,关闭看门狗,关闭MMUICACHE DCACHE,关闭总中断
  2. 设置各个模式的SP指针
  3. 代码段重定位到DDR上并且清bss段
  4. 开启总中断
  5. 跳转到C语言main函数执行

这里很多流程如代码重定位,清除bss,关闭看门狗等没有列举出来。

3 GIC中断处理流程#

3.1 一级中断控制器流程#

image

  • 假设GIC可以向CPU发出16-1019号中断,这些数字被称为hwirq0-15用于Process之间通信,比较特殊。
  • 假设要使用UART模块,它发出的中断连接到GIC的32号中断,分配的irq_desc序号为16
  • GIC domain中会记录(32, 16)
  • 那么注册中断时就是:request_irq(16, ...)
  • 发生UART中断时
    • 程序从GIC中读取寄存器知道发生了32号中断,通过GIC irq_domain可以知道virq为16
    • 调用irq_desc[16]中的handleA函数,它的作用是调用action链表中用户注册的函数.

3.2 多级中断控制器流程#

img

  • 假设GPIO模块下有4个引脚,都可以产生中断,都连接到GIC的33号中断
  • GPIO也可以看作一个中断控制器,对于它的4个中断
  • 对于GPIO模块中0~3这四个hwirq,一般都会一下子分配四个irq_desc
  • 假设这4个irq_desc的序号为100~103,在GPIO domain中记录(0,100) (1,101)(2,102) (3,103)
  • 对于KEY,注册中断时就是:request_irq(102, ...)
  • 按下KEY时:
    • 程序从GIC中读取寄存器知道发生了33号中断,通过GIC irq_domain可以知道virq为16.
    • 调用irq_desc[16]中的handleB函数
      • handleB读取GPIO寄存器,确定是GPIO里2号引脚发生中断
      • 通过GPIO irq_domain可以知道virq为102
      • 调用irq_desc[102]中的handleA函数,它的作用是调用action链表中用户注册的函数

3.3 GIC软件初始化过程#

1
2
3
4
5
6
7
start_kernel (init\main.c)
init_IRQ (arch\arm\kernel\irq.c)
irqchip_init (drivers\irqchip\irqchip.c)
of_irq_init (drivers\of\irq.c)//gic子系统
desc->irq_init_cb = match->data;
ret = desc->irq_init_cb(desc->dev,
desc->interrupt_parent);

3.3.1 gic驱动注册#

内核支持多种GIC, 在内核为每一类GIC定义一个结构体of_device_id,并放在一个段里:

1
2
3
4
5
6
7
8
9
10
// drivers\irqchip\irq-gic.c
IRQCHIP_DECLARE(gic_400, "arm,gic-400", gic_of_init);
IRQCHIP_DECLARE(arm11mp_gic, "arm,arm11mp-gic", gic_of_init);
IRQCHIP_DECLARE(arm1176jzf_dc_gic, "arm,arm1176jzf-devchip-gic", gic_of_init);
IRQCHIP_DECLARE(cortex_a15_gic, "arm,cortex-a15-gic", gic_of_init);
IRQCHIP_DECLARE(cortex_a9_gic, "arm,cortex-a9-gic", gic_of_init);
IRQCHIP_DECLARE(cortex_a7_gic, "arm,cortex-a7-gic", gic_of_init);//imx6ull对应gic类型
IRQCHIP_DECLARE(msm_8660_qgic, "qcom,msm-8660-qgic", gic_of_init);
IRQCHIP_DECLARE(msm_qgic2, "qcom,msm-qgic2", gic_of_init);
IRQCHIP_DECLARE(pl390, "arm,pl390", gic_of_init);

IRQCHIP_DECLARE宏进行展开:

1
2
3
4
5
6
7
8
9
// include\linux\irqchip.h
#define IRQCHIP_DECLARE(name, compat, fn) OF_DECLARE_2(irqchip, name, compat, fn)
#define OF_DECLARE_2(table, name, compat, fn) \
_OF_DECLARE(table, name, compat, fn, of_init_fn_2)
#define _OF_DECLARE(table, name, compat, fn, fn_type) \
static const struct of_device_id __of_table_##name \
__used __section(__irqchip_of_table) \
= { .compatible = compat, \
.data = (fn == (fn_type)NULL) ? fn : fn }

例如:IRQCHIP_DECLARE(cortex_a7_gic, "arm,cortex-a7-gic", gic_of_init);展开后:

1
2
3
4
static const struct of_device_id __of_table_cortex_a7_gic		\
__used __section(__irqchip_of_table) \
= { .compatible = "arm,cortex-a7-gic", \
.data = gic_of_init }

3.3.1.1 dts匹配#

在这里插入图片描述

根据dts匹配调用IRQCHIP_DECLARE(cortex_a7_gic, "arm,cortex-a7-gic", gic_of_init);, 对irq chip driver 的声明。

定义 IRQCHIP_DECLARE 之后,相应的内容会保存到 __irqchip_of_table 里。__irqchip_of_table 在链接脚本 vmlinux.lds 里,被放到了__irqchip_begin__irqchip_of_end 之间,该段用于存放中断控制器信息。

3.3.1.2 gic_of_init(GIC驱动初始化入口)#

gic_of_init内容太多,大致就是对中断控制器初始化:

  1. 初始化GICD(分发器寄存器)

  2. 初始化GICC(cpu接口端寄存器)

  3. 调用gic_init_bases 流程

    1. 调用set_handle_irq注册gic_handle_irq,异常处理的入口

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      void __init set_handle_irq(void (*handle_irq)(struct pt_regs *)){
      if (handle_arch_irq)
      return;
      handle_arch_irq = handle_irq;
      }
      static int __init gic_init_bases(void __iomem *dist_base,
      struct redist_region *rdist_regs,
      u32 nr_redist_regions,
      u64 redist_stride,
      struct fwnode_handle *handle) {
      set_handle_irq(gic_handle_irq);
      }

3.3.1.3 申请GIC中断#

3.3.1.3.1 在设备树里指定中断#

image-20240809003246767

3.3.1.3.2 对设备树中断的处理#

在这里插入图片描述

3.4 GIC中断处过程#

  1. 进入中断栈irq_stack_entry
  2. 执行中断控制器的中断入口handle_arch_irq
  3. 退出中断栈irq_stack_exit

中断栈用来保存中断的上下文,中断发生和退出的时候调用 irq_stack_entry irq_stack_exit 来进入和退出中断栈。

3.4.1 handle_arch_irq入口#

image-20240810150555648

3.4.1.1 gic_handle_irq#

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
static asmlinkage void __exception_irq_entry gic_handle_irq(struct pt_regs *regs){
u32 irqnr;

do {
irqnr = gic_read_iar(); ------(1)

if (likely(irqnr > 15 && irqnr < 1020) || irqnr >= 8192) { ------(2)
int err;

if (static_key_true(&supports_deactivate))
gic_write_eoir(irqnr);
else
isb();

err = handle_domain_irq(gic_data.domain, irqnr, regs); ------(3)
if (err) {
WARN_ONCE(true, "Unexpected interrupt received!\n");
if (static_key_true(&supports_deactivate)) {
if (irqnr < 8192)
gic_write_dir(irqnr);
} else {
gic_write_eoir(irqnr);
}
}
continue;
}
if (irqnr < 16) { ------(4)
gic_write_eoir(irqnr);
if (static_key_true(&supports_deactivate))
gic_write_dir(irqnr);
#ifdef CONFIG_SMP
/*
* Unlike GICv2, we don't need an smp_rmb() here.
* The control dependency from gic_read_iar to
* the ISB in gic_write_eoir is enough to ensure
* that any shared data read by handle_IPI will
* be read after the ACK.
*/
handle_IPI(irqnr, regs); ------(5)
#else
WARN_ONCE(true, "Unexpected SGI received!\n");
#endif
continue;
}
} while (irqnr != ICC_IAR1_EL1_SPURIOUS);
}
  1. 读取中断控制器的寄存器GICC_IAR,并获取 hwirq
  2. 外设触发的中断。硬件中断号0-15表示 SGI (软件中断)类型的中断,15-1020 表示外设中断(SPI或PPI共享中断类型),8192-MAX 表示 LPI 类型的中断
  3. 中断控制器中断处理的主体
  4. 软件触发的中断
  5. 核间交互触发的中断
3.4.1.1.1 handle_domain_irq#

image-20240810151815454

  1. 进入中断上下文
  2. 根据 hwirq 去查找 linux 中断号
  3. 通过中断号找到全局中断描述符数组irq_desc[NR_IRQS]中的一项,然后调用 generic_handle_irq_desc,执行该 irq 号注册的 action
  4. 退出中断上下文
3.4.1.1.1.1 generic_handle_irq#

generic_handle_irq展开:

image-20240810151917713

image-20240810152019303

调用 desc->handle_irq 指向的回调函数。

image-20240810152332559

irq_domain_set_info 根据硬件中断号的范围设置 irq_desc->handle_irq 的指针,共享中断入口为 handle_fasteoi_irq,私有中断入口为 handle_percpu_devid_irq

  • handle_percpu_devid_irq:处理私有中断处理,在这个过程中会分别调用中断控制器的处理函数进行硬件操作,该函数调用 action->handler() 来进行中断处理
  • handle_fasteoi_irq:处理共享中断,并且遍历 irqaction 链表,逐个调用 action->handler() 函数,这个函数正是设备驱动程序调用 request_irq/request_threaded_irq 接口注册的中断处理函数,此外如果中断线程化处理的话,还会调用__irq_wake_thread唤醒内核线程。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
irqreturn_t __handle_irq_event_percpu(struct irq_desc *desc, unsigned int *flags) {
irqreturn_t retval = IRQ_NONE;
unsigned int irq = desc->irq_data.irq;
struct irqaction *action;

for_each_action_of_desc(desc, action) {
irqreturn_t res;
res = action->handler(irq, action->dev_id);//requst_irq注册的函数
switch (res) {
case IRQ_WAKE_THREAD:
__irq_wake_thread(desc, action);
case IRQ_HANDLED:
*flags |= action->flags;
break;
}
}
...
}
3.4.1.1.2 总结request_irq的函数如何被执行#

image-20240810152452686

4 中断控制器GIC的设备树描述#

中断控制器而言 ,设备树绑定信息参考文档Documentation/devicetree/bindings/arm/gic.txt。以nxp的imx6ull.dtsi为例:

1
2
3
4
5
6
7
intc: interrupt-controller@00a01000 {
compatible = "arm,cortex-a7-gic";
#interrupt-cells = <3>;
interrupt-controller;
reg = <0x00a01000 0x1000>,
<0x00a02000 0x100>;
};
  1. compatible 属性值为“arm,cortex-a7-gic”在 Linux 内核源码中搜索“arm,cortex-a7-gic”即可找到 GIC 中断控制器驱动文件, GIC 中断控制器驱是架构通用的,在drivers/irqchip/irq-gic.c
  2. interrupt-cells #address-cells、#size-cells 一样。
    2.1 每个 cells 都是 32 位整形值,对于 ARM 处理的GIC 来说,一共有 3 个 cells,这三个 cells 的含义如下:
1
2
3
4
5
6
第一个 cells:中断类型,0 表示 SPI(共享) 中断,1 表示 PPI(私有) 中断。
第二个 cells:中断号,对于 SPI 中断来说中断号的范围为 0~987,对于PPI
中断来说中断号的范围为 0~15
第三个 cells:标志,bit[3:0]表示中断触发类型,为 1 的时候表示上升沿触发,
2 的时候表示下降沿触发,为 4 的时候表示高电平触发,为 8 的时候表示低
电平触发。bit[15:8]为 PPI 中断的 CPU 掩码。
  1. interrupt-controller表示该节点中断控制器

对于gpio来说也可以作为中断控制器,如imx6ull的gpio5
image
对于 gpio5 来说一共有两条信息,中断类型都是 SPI,
触发电平都是 IRQ_TYPE_LEVEL_HIGH。不同之处在于中断源,一个是 74,一个是 75,打开可以打开《IMX6ULL 参考手册》的“Chapter 3 Interrupts and DMA Events”章节:
image
GPIO5 一共用了 2 个中断号,一个是 74,一个是 75。其中 74 对应GPIO5_IO00~GPIO5_IO15这低 16 个 IO,75 对应 GPIO5_IO16~GPIOI5_IO31 这高 16 位 IO。

使用者:

1
2
3
4
5
6
7
fxls8471@1e {
compatible = "fsl,fxls8471";
reg = <0x1e>;
position = <0>;
interrupt-parent = <&gpio5>;
interrupts = <0 8>;
};

fxls8471 有一个中断引脚链接到了 I.MX6ULL 的SNVS_TAMPER0因脚上,这个引脚可以复用为GPIO5_IO00 interrupts设置中断信息,0 表示 GPIO5_IO00,8 表示低电平触发。

4.1 获取中断号函数#

1
2
3
4
unsigned int irq_of_parse_and_map(struct device_node *dev,int index)
int gpio_to_irq(unsigned int gpio)
unsigned int irq_create_mapping(struct irq_domain *domain,
irq_hw_number_t hwirq);

irq_of_parse_and_map 函数从 interupts 属性中提取到对应的设备号,
dev:设备节点。
index:索引号,interrupts 属性可能包含多条中断信息,通过 index 指定要获取的信息。
返回值:中断号。

4.2 gic使用示例-按键gpio中断#

4.2.1 dts描述#

我们驱动一个按键,采用中断的方式,并且采用定时器来实现按键消抖,应用程序读取按键值并且通过终端打印出来:

1
2
3
4
5
6
7
8
9
10
key {
#size-cells = <1>;
compatible = "atkalpha-key";
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_key>;
key-gpio = <&gpio1 18 GPIO_ACTIVE_LOW>; /* KEY0 */
interrupt-parent = <&gpio1>;
interrupts = <18 IRQ_TYPE_EDGE_BOTH>; /* FALLING RISING */
status = "okay";
};

可以看到key使用key-gpio使用GPIO1_IO18, 可以看到也用到了gpio中断源interrupts,IRQ_TYPE_EDGE_BOTH定义在include/linux/irq.h:

1
2
3
4
5
6
7
8
9
10
11
enum {
IRQ_TYPE_NONE = 0x00000000,
IRQ_TYPE_EDGE_RISING = 0x00000001,
IRQ_TYPE_EDGE_FALLING = 0x00000002,
IRQ_TYPE_EDGE_BOTH = (IRQ_TYPE_EDGE_FALLING |
IRQ_TYPE_EDGE_RISING),
IRQ_TYPE_LEVEL_HIGH = 0x00000004,
IRQ_TYPE_LEVEL_LOW = 0x00000008,
IRQ_TYPE_LEVEL_MASK = (IRQ_TYPE_LEVEL_LOW |
IRQ_TYPE_LEVEL_HIGH),
};

4.2.2 驱动代码与分析#

驱动代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <linux/semaphore.h>
#include <linux/timer.h>
#include <linux/of_irq.h>
#include <linux/irq.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#define IMX6UIRQ_CNT 1 /* 设备号个数 */
#define IMX6UIRQ_NAME "imx6uirq" /* 名字 */
#define KEY0VALUE 0X01 /* KEY0按键值 */
#define INVAKEY 0XFF /* 无效的按键值 */
#define KEY_NUM 1 /* 按键数量 */

/* 中断IO描述结构体 */
struct irq_keydesc {
int gpio; /* gpio */
int irqnum; /* 中断号 */
unsigned char value; /* 按键对应的键值 */
char name[10]; /* 名字 */
irqreturn_t (*handler)(int, void *); /* 中断服务函数 */
};

/* imx6uirq设备结构体 */
struct imx6uirq_dev{
dev_t devid; /* 设备号 */
struct cdev cdev; /* cdev */
struct class *class; /* 类 */
struct device *device; /* 设备 */
int major; /* 主设备号 */
int minor; /* 次设备号 */
struct device_node *nd; /* 设备节点 */
atomic_t keyvalue; /* 有效的按键键值 */
atomic_t releasekey; /* 标记是否完成一次完成的按键,包括按下和释放 */
struct timer_list timer;/* 定义一个定时器*/
struct irq_keydesc irqkeydesc[KEY_NUM]; /* 按键描述数组 */
unsigned char curkeynum; /* 当前的按键号 */
};

struct imx6uirq_dev imx6uirq;

static irqreturn_t key0_handler(int irq, void *dev_id)
{
struct imx6uirq_dev *dev = (struct imx6uirq_dev *)dev_id;

dev->curkeynum = 0;
dev->timer.data = (volatile long)dev_id;
mod_timer(&dev->timer, jiffies + msecs_to_jiffies(10)); /* 10ms定时 */
return IRQ_RETVAL(IRQ_HANDLED);
}
void timer_function(unsigned long arg)
{
unsigned char value;
unsigned char num;
struct irq_keydesc *keydesc;
struct imx6uirq_dev *dev = (struct imx6uirq_dev *)arg;

num = dev->curkeynum;
keydesc = &dev->irqkeydesc[num];

value = gpio_get_value(keydesc->gpio); /* 读取IO值 */
if(value == 0){ /* 按下按键 */
atomic_set(&dev->keyvalue, keydesc->value);
}
else{ /* 按键松开 */
atomic_set(&dev->keyvalue, 0x80 | keydesc->value);
atomic_set(&dev->releasekey, 1); /* 标记松开按键,即完成一次完整的按键过程 */
}
}

static int keyio_init(void)
{
unsigned char i = 0;
int ret = 0;

imx6uirq.nd = of_find_node_by_path("/key");
if (imx6uirq.nd== NULL){
printk("key node not find!\r\n");
return -EINVAL;
}
for (i = 0; i < KEY_NUM; i++) {
imx6uirq.irqkeydesc[i].gpio = of_get_named_gpio(imx6uirq.nd ,"key-gpio", i);
if (imx6uirq.irqkeydesc[i].gpio < 0) {
printk("can't get key%d\r\n", i);
}
}
for (i = 0; i < KEY_NUM; i++) {
memset(imx6uirq.irqkeydesc[i].name, 0, sizeof(imx6uirq.irqkeydesc[i].name)); /* 缓冲区清零 */
sprintf(imx6uirq.irqkeydesc[i].name, "KEY%d", i); /* 组合名字 */
gpio_request(imx6uirq.irqkeydesc[i].gpio, imx6uirq.irqkeydesc[i].name);
gpio_direction_input(imx6uirq.irqkeydesc[i].gpio);
imx6uirq.irqkeydesc[i].irqnum = irq_of_parse_and_map(imx6uirq.nd, i);
#if 0
imx6uirq.irqkeydesc[i].irqnum = gpio_to_irq(imx6uirq.irqkeydesc[i].gpio);
#endif
printk("key%d:gpio=%d, irqnum=%d\r\n",i, imx6uirq.irqkeydesc[i].gpio,
imx6uirq.irqkeydesc[i].irqnum);
}
imx6uirq.irqkeydesc[0].handler = key0_handler;
imx6uirq.irqkeydesc[0].value = KEY0VALUE;

for (i = 0; i < KEY_NUM; i++) {
ret = request_irq(imx6uirq.irqkeydesc[i].irqnum, imx6uirq.irqkeydesc[i].handler,
IRQF_TRIGGER_FALLING|IRQF_TRIGGER_RISING, imx6uirq.irqkeydesc[i].name, &imx6uirq);
if(ret < 0){
printk("irq %d request failed!\r\n", imx6uirq.irqkeydesc[i].irqnum);
return -EFAULT;
}
}
init_timer(&imx6uirq.timer);
imx6uirq.timer.function = timer_function;
return 0;
}
static int imx6uirq_open(struct inode *inode, struct file *filp)
{
filp->private_data = &imx6uirq; /* 设置私有数据 */
return 0;
}
static ssize_t imx6uirq_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
int ret = 0;
unsigned char keyvalue = 0;
unsigned char releasekey = 0;
struct imx6uirq_dev *dev = (struct imx6uirq_dev *)filp->private_data;

keyvalue = atomic_read(&dev->keyvalue);
releasekey = atomic_read(&dev->releasekey);
if (releasekey) { /* 有按键按下 */
if (keyvalue & 0x80) {
keyvalue &= ~0x80;
ret = copy_to_user(buf, &keyvalue, sizeof(keyvalue));
} else {
goto data_error;
}
atomic_set(&dev->releasekey, 0);/* 按下标志清零 */
} else {
goto data_error;
}
return 0;
data_error:
return -EINVAL;
}
static struct file_operations imx6uirq_fops = {
.owner = THIS_MODULE,
.open = imx6uirq_open,
.read = imx6uirq_read,
};
static int __init imx6uirq_init(void)
{
if (imx6uirq.major) {
imx6uirq.devid = MKDEV(imx6uirq.major, 0);
register_chrdev_region(imx6uirq.devid, IMX6UIRQ_CNT, IMX6UIRQ_NAME);
} else {
alloc_chrdev_region(&imx6uirq.devid, 0, IMX6UIRQ_CNT, IMX6UIRQ_NAME);
imx6uirq.major = MAJOR(imx6uirq.devid);
imx6uirq.minor = MINOR(imx6uirq.devid);
}
cdev_init(&imx6uirq.cdev, &imx6uirq_fops);
cdev_add(&imx6uirq.cdev, imx6uirq.devid, IMX6UIRQ_CNT);
imx6uirq.class = class_create(THIS_MODULE, IMX6UIRQ_NAME);
if (IS_ERR(imx6uirq.class)) {
return PTR_ERR(imx6uirq.class);
}
imx6uirq.device = device_create(imx6uirq.class, NULL, imx6uirq.devid, NULL, IMX6UIRQ_NAME);
if (IS_ERR(imx6uirq.device)) {
return PTR_ERR(imx6uirq.device);
}

atomic_set(&imx6uirq.keyvalue, INVAKEY);
atomic_set(&imx6uirq.releasekey, 0);
keyio_init();
return 0;
}
static void __exit imx6uirq_exit(void)
{
unsigned int i = 0;
del_timer_sync(&imx6uirq.timer);
for (i = 0; i < KEY_NUM; i++) {
free_irq(imx6uirq.irqkeydesc[i].irqnum, &imx6uirq);
gpio_free(imx6uirq.irqkeydesc[i].gpio);
}
cdev_del(&imx6uirq.cdev);
unregister_chrdev_region(imx6uirq.devid, IMX6UIRQ_CNT);
device_destroy(imx6uirq.class, imx6uirq.devid);
class_destroy(imx6uirq.class);
}
分析: ![image](字符设备驱动-9-中断子系统-GICv2架构解析/37.png) 从key节点取出key-gpio,得到gpio编号 调用gpio请求配置函数,配成input模式 根据key节点信息解析出中断号,或者gpio编号转成中断号.(这里用到一个函数`irq_of_parse_and_map`) 注册中断 创建定时器用来消抖

中断响应过程:
image
image
按键按下或松开,中断产生调用key0_handler,修改定时器超时10ms, 如果是抖动那么,定时器中断那么不会触发(原理请参考[字符设备驱动-9.内核定时器 - fuzidage - 博客园 (cnblogs.com)]

字符设备驱动-8-内核定时器 | Hexo (fuzidage.github.io),只有当不是抖动真正按下或松开,定时器中断触发进行读取按键,原子操作设置键值。releasekey置1表示一次完整的按下松开。
image
最后用户调用read, 返回键值。可见releasekey很好的控制着按键按下和read的次数,比如当连续read 2次但是只按了一次,则读取失败。
image

硬中断和虚拟中断号的映射关系可以用 /proc/interrupts 查看:

img

通过 ps 命令可以查看系统中的中断线程,注意这些线程是实时线程 SCHED_FIFO:

1
2
3
4
5
6
7
8
9
10
# ps -A | grep "irq/"
root 1749 2 0 0 irq_thread 0 S [irq/433-imx_drm]
root 1750 2 0 0 irq_thread 0 S [irq/439-imx_drm]
root 1751 2 0 0 irq_thread 0 S [irq/445-imx_drm]
root 1752 2 0 0 irq_thread 0 S [irq/451-imx_drm]
root 2044 2 0 0 irq_thread 0 S [irq/279-isl2902]
root 2192 2 0 0 irq_thread 0 S [irq/114-mmc0]
root 2199 2 0 0 irq_thread 0 S [irq/115-mmc1]
root 2203 2 0 0 irq_thread 0 S [irq/322-5b02000]
root 2361 2 0 0 irq_thread 0 S [irq/294-4-0051]