一、中断相关知识点回顾
1、中断向量表:
中断向量是中断服务程序的入口地址或存放中断服务程序的首地址,而中断向量表就是存放着一系列中断服务程序入口地址的表。这些中断服务程序在中断向量表中的位置是半导体厂商确定好的。若某个中断被触发,则会自动跳转到中断向量表对应的中断服务程序的入口地址处。
中断向量表在整个程序的最前面,但ARM处理器都是从0x00000000开始运行,但在stm32中代码是下载到0x80000000开始的区域,这里就会引入一个中断向量表偏移。在imx6ull也有也有相类似的概念,只是使用的寄存器不大一样。
2、NVIC与GIC
对于STM32这种基于Cortex-M内核的设备,中断系统的管理机构称作NVIC,即Nested Vectored Interrupt Controller。使用时先进行中断使能,然后编写并注册中断服务函数。
对于imx6ull这种基于Cortex-A7内核的设备,中断系统的管理机构称为GIC,即General Interrupt Controller。(有V1~V4四个版本,A核用的是V2)
区别:GIC的中断向量表与NVIC的中断向量表相比少了一大堆,区别在于Cortex-M内核会将所有的中断向量,包括芯片外设的所有的中断给列举出来;而Cortex-A内核的则是所有的外部中断都属于IRQ这个中断,再通过IRQ服务函数中指定的寄存器来判断具体是什么中断。
Cortex-A的七个中断:
- 复位中断(Rest),CPU 复位以后就会进入复位中断,我们可以在复位中断服务函数里面做一些初始化工作,比如初始化 SP 指针、DDR 等等。
- 未定义指令中断(Undefined Instruction),如果指令不能识别的话就会产生此中断。
- 软中断(Software Interrupt,SWI),由 SWI 指令引起的中断,Linux 的系统调用会用 SWI指令来引起软中断,通过软中断来陷入到内核空间。
- 指令预取中止中断(Prefetch Abort),预取指令的出错的时候会产生此中断。
- 数据访问中止中断(Data Abort),访问数据出错的时候会产生此中断。
- IRQ 中断(IRQ Interrupt),外部中断,前面已经说了,芯片内部的外设中断都会引起此中断的发生。
- FIQ 中断(FIQ Interrupt),快速中断,如果需要快速处理中断的话就可以使用此中断。
3、GIC控制器简介
GIC V2是给ARMv8-A/R架构使用的,如imx6ull的芯片。当GIC接收到外部中断信号后就会上报给ARM内核,但ARM内核只提供了四个信号给GIC来汇报中断情况:VFIQ(虚拟快速FIQ)、VIRQ(虚拟外部IRQ)、FIQ(快速中断IRQ)、IRQ(外部中断IRQ)。所以我们使用外部中断的话,最终向内核上报的只有一个IRQ信号
1、GIC V2概述
下图的左侧为中断源,中间则是GIC控制器,右侧是向处理器内核发送的中断信息。GIC将众多中断源分为三个部分即红圈圈出的三个部分。
- SPI(Shared Peripheral Interrupt),共享中断:即所有Core共享的中断源。
- PPI(Private Peripheral Interrupt),私有中断:即每个Core自己独有的中断,进行指定的处理。
- SGI(Software generated Interrupt),软件中断:由软件触发引起的中断,通过向寄存器GICD_SGIR写入数据来触发,系统会通过使用SGI中断来进行多核之前通信。
2、GIC v2的中断号
区分这些不同中断源的方式就是给他们分配一个唯一ID,即中断ID。每个CPU最多支持1020个中断ID。ID0~ID15分配给 SGI。 ID16~ID31分配给 PPI。ID32~ID1019这 988 个 ID 分配给 SPI。具体的某个ID对应某个中断就由半导体厂商自行定义。
比如 I.MX6U 的总共 使用了 128 个中断 ID,加上前面属于 PPI 和 SGI 的 32 个 ID,I.MX6U 的中断源共有 128+32=160 个,这 128 个中断 ID 对应的中断在《I.MX6ULL 参考手册》的“3.2 CortexA7 interrupts”小节,
3、GIC逻辑分块
GIC架构分为两个逻辑块:Distributor和CPU Interface,如上图蓝圈所示。
- 全局中断使能控制。
- 控制每一个中断的使能或者关闭。
- 设置每个中断的优先级。
- 设置每个中断的目标处理器列表。
- 设置每个外部中断的触发模式:电平触发或边沿触发。
- 设置每个中断属于组 0 还是组 1。
CPU Interface(CPU接口端):它是分发器和CPU Core之间的桥梁,主要职能如下:
- 使能或关闭发送到CPU Core的中断请求信号
- 应答中断
- 通知中断处理完成
- 设置优先级掩码,进而设置哪些中断不需要上报给CPU Core
- 定义抢断策略
- 当多个中断到来时选择优先级最高的中断通知给CPU Core
4、CP15协处理器
这个可以查看相关裸机开发的文档去补充,第一张主要是为了弄明白imx6ull的中断设备树相关配置而写的。
二、中断设备树相关说明
一个硬件中断产生后,会依次经过GPIOX,SOC,GPC,GIC,最后再上报到CPU执行对应操作。
1、intc的描述
intc描述的是整个GIC控制器,即中断总开关。由#interrupt-cells属性表示这个中断控制器下的其他子控制器设备的inturrupts属性cells大小,也就是子节点中用几个cells来描述中断。
- 第一个参数用于指定中断类型,如SPI、PPI、SGI。
- 第二个参数用于指定中断ID
- 第三个参数用于指定触发方式,参数为u32,后四位用来设置触发类型,可进行组合操作
intc: interrupt-controller@a01000 {
compatible = "arm,cortex-a9-gic";
#interrupt-cells = <3>;
interrupt-controller;
reg = <0xa01000 0x1000>,
<0xa00100 0x100>;
interrupt-parent = <&intc>;
};
2、gpc的描述与GPIO中断节点的描述
gpc是系统的虚拟中断控制器也称为二级子中断控制器,如果处理器是SOC,则此节点必须存在。顶级SOC节点包含的信息对此SOC上的所有设备可见。SOC节点还包含目标板使用的每个SOC设备子节点。
由于顶级SOC节点的已包含interrupt-parent信息,所以gpio中断控制器的中断父级都是gpc(gpc节点的父级信息是intc,可以理解为被覆盖了)。
由下方gpio5可知,他描述了一个SPI类的第74、75号的高电平触发。查阅手册可知,GPIO5只能产生两个中断,分配的中断号为106、107(前面属于PPI和SGI的共32个,即106-32=74)。
soc {
#address-cells = <1>;
#size-cells = <1>;
compatible = "simple-bus";
interrupt-parent = <&gpc>;
ranges;
gpc: interrupt-controller@20dc000 {
compatible = "fsl,imx6sll-gpc", "fsl,imx6q-gpc";
reg = <0x20dc000 0x4000>;
interrupt-controller;
#interrupt-cells = <3>;
interrupts = <GIC_SPI 89 IRQ_TYPE_LEVEL_HIGH>;
interrupt-parent = <&intc>;
fsl,mf-mix-wakeup-irq = <0x7c00000 0x7d00 0x0 0x1400640>;
};
gpio5: gpio@20ac000 {
compatible = "fsl,imx6sll-gpio", "fsl,imx35-gpio";
reg = <0x20ac000 0x4000>;
interrupts = <GIC_SPI 74 IRQ_TYPE_LEVEL_HIGH>,
<GIC_SPI 75 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&clks IMX6SLL_CLK_GPIO5>;
gpio-controller;
#gpio-cells = <2>;
interrupt-controller;
#interrupt-cells = <2>;
gpio-ranges = <&iomuxc 0 135 1>, <&iomuxc 1 128 1>,
.......
<&iomuxc 21 137 1>;
};
};
3、实际设备的描述
由上可知,包含gpio5的子中断控制器的interrupts参数需要两个参数来进行描述。interrupts属性第一个为引脚编号、第二个为触发方式。button_gpio属性是gpio子系统表示使用gpio5的信息,引脚编号为1,低电平。
button_interrupt {
compatible = "button_interrupt";
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_button>;
button_gpio = <&gpio5 1 GPIO_ACTIVE_LOW>;
status = "okay";
interrupt-parent = <&gpio5>;
interrupts = <1 IRQ_TYPE_EDGE_RISING>;
};
三、中断代码的调用及中断号的获取
1、中断号的获取
对于能够转化为平台设备的节点,如果它指定了中断属性,便可以使用下面的函数指定IORESOURCE_IRQ类型来获取中断号。
struct resource *platform_get_resource(struct platform_device *dev,
unsigned int type, unsigned int num);
对于I2C、SPI设备节点,其对应的驱动会在处理节点时顺便处理其中断信息。并将其保存在i2c_device或spi_device的irq成员当中。
对于使用GPIO中断的设备而言,可以使用gpio_to_irq()来获得中断号。
对于除上述情况之外的某些设备节点,可以使用of_irq_get()或irq_of_parse_and_map()函数去获取。
2、调用
四、文章推荐