fuzidage
专注嵌入式、linux驱动 、arm裸机研究

导航

 

1 armv7 GIC介绍

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

image
image

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

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

1.0 分发器端和 CPU 接口端

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

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

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

    /*
    * 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

#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,示例代码如下:

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。标记为inactive或pending(如果状态为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,如下计算:

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并确定相应的位?

对于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并确定相应的位?

对于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并确定相应的位?

对于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并确定相应的位?

对于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是多少?还要确定使用这个寄存器里哪一个字节。

对于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是多少?还要确定使用这个寄存器里哪一个字节。

对于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?

对于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,如下所示:

如果有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 获取指定中断的优先级。
.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 启动流程

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

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

3 GIC中断处理流程

3.1 一级中断控制器流程

image

  • 假设GIC可以向CPU发出16-1019号中断,这些数字被称为hwirq。0-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初始化过程

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)
    			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,并放在一个段里:

// 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);
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宏进行展开:

// 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);展开后:

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);

3.3.1.2 gic_of_init

3.3.1.3 申请GIC中断

3.3.1.3.1 在设备树里指定中断

在这里插入图片描述

3.3.1.3.2 对设备树中断的处理

在这里插入图片描述

3.4 GIC中断处过程

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

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

3.4.1 handle_arch_irq

在内核启动过程中初始化中断控制器时,设置了具体的 handler,gic_init_bases->set_handle_irqhandle_arch_irq 指针指向gic_handle_irq函数.

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.4.1.1 gic_handle_irq

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
int __handle_domain_irq(struct irq_domain *domain, unsigned int hwirq,
   bool lookup, struct pt_regs *regs)
{
 struct pt_regs *old_regs = set_irq_regs(regs);        
 unsigned int irq = hwirq;
 int ret = 0;

 irq_enter();                               ------(1)

#ifdef CONFIG_IRQ_DOMAIN
 if (lookup)
  irq = irq_find_mapping(domain, hwirq);    ------(2)
#endif

 /*
  * Some hardware gives randomly wrong interrupts.  Rather
  * than crashing, do something sensible.
  */
 if (unlikely(!irq || irq >= nr_irqs)) {
  ack_bad_irq(irq);
  ret = -EINVAL;
 } else {
  generic_handle_irq(irq);                  ------(3)
 }

 irq_exit();                                ------(4)
 set_irq_regs(old_regs);
 return ret;
}
  1. 进入中断上下文
  2. 根据 hwirq 去查找 linux 中断号
  3. 通过中断号找到全局中断描述符数组irq_desc[NR_IRQS]中的一项,然后调用 generic_handle_irq_desc,执行该 irq 号注册的 action
  4. 退出中断上下文
static inline void generic_handle_irq_desc(struct irq_desc *desc)
{
 desc->handle_irq(desc);            
}//3通过中断号找到全局中断描述符数组` irq_desc[NR_IRQS] `中的一项,然后调用` generic_handle_irq_desc`

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

img

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

img

  • handle_percpu_devid_irq:处理私有中断处理,在这个过程中会分别调用中断控制器的处理函数进行硬件操作,该函数调用 action->handler() 来进行中断处理
  • handle_fasteoi_irq:处理共享中断,并且遍历 irqaction 链表,逐个调用 action->handler() 函数,这个函数正是设备驱动程序调用 request_irq/request_threaded_irq 接口注册的中断处理函数,此外如果中断线程化处理的话,还会调用__irq_wake_thread唤醒内核线程。
handle_fasteoi_irq
	handle_irq_event
		handle_irq_event_percpu
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);   .........................1

        switch (res) {
        case IRQ_WAKE_THREAD:
            __irq_wake_thread(desc, action);          .........................2
        case IRQ_HANDLED:
            *flags |= action->flags;
            break;
        }
    }
    ...
}

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

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

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 的含义如下:
	第一个 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。

使用者:

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 获取中断号

unsigned int irq_of_parse_and_map(struct device_node *dev,int index)
int gpio_to_irq(unsigned int gpio)

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

4.2使用示例

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

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:

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),
};
#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
从key节点取出key-gpio,得到gpio编号
调用gpio请求配置函数,配成input模式
根据key节点信息解析出中断号,或者gpio编号转成中断号.(这里用到一个函数irq_of_parse_and_map)
注册中断
创建定时器用来消抖

中断响应过程:
image
image
按键按下或松开,中断产生调用key0_handler,修改定时器超时10ms, 如果是抖动那么,定时器中断那么不会触发(原理请参考定时器中断),只有当不是抖动真正按下或松开,定时器中断触发进行读取按键,原子操作设置键值。releasekey置1表示一次完整的按下松开。
image
最后用户调用read, 返回键值。可见releasekey很好的控制着按键按下和read的次数,比如当连续read 2次但是只按了一次,则读取失败。
image

img

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

# 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]
posted on 2023-09-29 18:58  fuzidage  阅读(336)  评论(0编辑  收藏  举报