LV015-设备树中怎么写
一、设备树里的中断控制器
中断 硬件框图如下:

在硬件上,“中断控制器”只有 GIC 这一个,但是我们在软件上也可以把上图中的“ GPIO”称为“中断控制器”。很多芯片有多个 GPIO 模块,比如 GPIO1、GPIO2 等等。所以软件上的“中断控制器”就有很多个: GIC、 GPIO1、 GPIO2 等等
GPIO1 连接到 GIC, GPIO2 连接到 GIC,所以 GPIO1 的父亲是 GIC, GPIO2 的父亲是 GIC。假设 GPIO1 有 32 个中断源,但是它把其中的 16 个汇聚起来向 GIC 发出一个中断,把另外 16 个汇聚起来向 GIC 发出另一个中断。这就意味着 GPIO1 会用到 GIC 的两个中断,会涉及 GIC 里的 2 个 hwirq。
这些层级关系、中断号(hwirq),都会在设备树中有所体现。
在设备树中,中断控制器节点中必须有一个属性: interrupt-controller,表明它是“中断控制器”。还必须有一个属性: #interrupt-cells,表明引用这个中断控制器的话需要多少个 cell。 #interrupt-cells 的值一般有如下取值:
(1)#interrupt-cells = <1> :别的节点要使用这个中断控制器时,只需要一个 cell 来表明使用“哪一个中断”。
(2)#interrupt-cells = <2> :别的节点要使用这个中断控制器时,需要一个 cell 来表明使用“哪一个中断”;还需要另一个 cell 来描述中断,一般是表明触发类型(trigger type and level flags):
// 第 2 个 cell 的 bits [3:0]
1 = low-to-high edge triggered,上升沿触发
2 = high-to-low edge triggered,下降沿触发
4 = active high level-sensitive,高电平触发
8 = active low level-sensitive,低电平触发示例如下:
vic: intc@10140000 {
compatible = "arm,versatile-vic";
interrupt-controller;
#interrupt-cells = <1>;
reg = <0x10140000 0x1000>;
}如果中断控制器有级联关系,下级的中断控制器还需要表明它的“ interrupt-parent ” 是谁,用了 interrupt-parent ” 中的哪一个“ interrupts”
二、设备树里使用中断
一个外设,它的中断信号接到哪个“中断控制器”的哪个“中断引脚”,这个中断的触发方式是怎样的?这 3 个问题,在设备树里使用中断时,都要有所体现。
1. 相关属性
- 要用哪一个中断控制器里的中断?
interrupt-parent=<&XXXX>- 要用哪一个中断?
interrupts=<>;Interrupts 里要用几个 cell?这个是由 interrupt-parent 对应的中断控制器决定。在中断控制器里有“ #interrupt-cells”属性,它指明了要用几个 cell 来描述中断。比如:
i2c@7000c000 {
gpioext: gpio-adnp@41 {
compatible = "ad,gpio-adnp";
interrupt-parent = <&gpio>;
interrupts = <160 1>;
gpio-controller;
#gpio-cells = <1>;
interrupt-controller;
#interrupt-cells = <2>;
};
//......
};- 新的写法:interrupts-extended
一个“ interrupts-extended”属性就可以既指定“ interrupt-parent”,也指定“ interrupts”,比如:
interrupts-extended = <&intc1 5 1>, <&intc2 1 0>;2. 总结
简单总结一下与中断有关的设备树属性信息:
①、 #interrupt-cells,指定中断源的信息 cells 个数。
②、 interrupt-controller,表示当前节点为中断控制器。
③、 interrupts,指定中断号,触发方式等。
④、 interrupt-parent,指定父中断,也就是中断控制器。
三、设备树里中断节点的示例
1. 示例 1
我们看一下 imx6ul.dtsi - arch/arm/boot/dts/imx6ul.dtsi :
intc: interrupt-controller@a01000 {
compatible = "arm,gic-400", "arm,cortex-a7-gic";
interrupts = <GIC_PPI 9 (GIC_CPU_MASK_SIMPLE(4) | IRQ_TYPE_LEVEL_HIGH)>;
#interrupt-cells = <3>;
interrupt-controller;
interrupt-parent = <&intc>;
reg = <0x00a01000 0x1000>,
<0x00a02000 0x2000>,
<0x00a04000 0x2000>,
<0x00a06000 0x2000>;
};第 2 行, compatible 属性值为“arm, cortex-a7-gic”在 Linux 内核源码中搜索“arm, cortex-a7- gic”即可找到 GIC 中断控制器驱动文件。
第 3 行, #interrupt-cells 和#address-cells、 #size-cells 一样。表示此中断控制器下设备的 cells 大小,对于设备而言,会使用 interrupts 属性描述中断信息, #interrupt-cells 描述了 interrupts 属性的 cells 大小,也就是一条信息有几个 cells。每个 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 掩码。
第 4 行, interrupt-controller 节点为空,表示当前节点是中断控制器。
2. 示例 2
对于 gpio 来说, gpio 节点也可以作为中断控制器,比如 imx6ul.dtsi 文件中的 gpio5 节点内容如下所示:
gpio5: gpio@20ac000 {
compatible = "fsl,imx6ul-gpio", "fsl,imx35-gpio";
reg = <0x020ac000 0x4000>;
interrupts = <GIC_SPI 74 IRQ_TYPE_LEVEL_HIGH>,
<GIC_SPI 75 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&clks IMX6UL_CLK_GPIO5>;
gpio-controller;
#gpio-cells = <2>;
interrupt-controller;
#interrupt-cells = <2>;
gpio-ranges = <&iomuxc 0 7 10>, <&iomuxc 10 5 2>;
};在这段代码的第 4 行, interrupts 描述中断源信息,对于 gpio5 来说一共有两条信息,中断类型都是 SPI,触发电平都是 IRQ_TYPE_LEVEL_HIGH。不同之处在于中断源,一个是 74,一个是 75,打开可以打开《IMX6ULL 参考手册》的“Chapter 3 Interrupts and DMA Events”章节,找到表 3-1,有

可以看出, GPIO5 一共用了 2 个中断号,一个是 74,一个是 75。其中 74 对应 GPIO5_IO00~GPIO5_IO15 这低 16 个 IO, 75 对应 GPIO5_IO16~GPIOI5_IO31 这高 16 位 IO。
第 8 行, interrupt-controller 表明了 gpio5 节点也是个中断控制器,用于控制 gpio5 所有 IO 的中断。
第 9 行,将#interrupt-cells 修改为 2。
怎么引用这个 GPIO 节点的中断控制器?应该是在 NXP 维护的 4.1.15 版本内核中有 arch/arm/boot/dts/imx6ull-14x14-evk.dts 设备树文件,上面有这样一个节点:
fxls8471@1e {
compatible = "fsl,fxls8471";
reg = <0x1e>;
position = <0>;
interrupt-parent = <&gpio5>;
interrupts = <0 8>;
};fxls8471 是 NXP 官方的 6ULL 开发板上的一个磁力计芯片, fxls8471 有一个中断引脚链接到了 I.MX6ULL 的 SNVS_TAMPER0 因脚上,这个引脚可以复用为 GPIO5_IO00。
第 5 行, interrupt-parent 属性设置中断控制器,这里使用 gpio5 作为中断控制器。
第 6 行, interrupts 设置中断信息, 0 表示 GPIO5_IO00, 8 表示低电平触发。
3. 示例 3
这里参考的是伟东山的嵌入式教程,所以以 100ASK_IMX6ULL 开发板为例,在 arch/arm/boot/dts 目录下可以看到 2 个文件: imx6ull.dtsi、 100ask_imx6ull-14x14.dts 。我们把里面有关中断的部分内容抽取出来。

从设备树反推 IMX6ULL 的中断体系,如下,比之前的框图多了一个“ GPC INTC”:

GPC INTC 的 英 文 是 : General Power Controller, Interrupt Controller。它提供中断屏蔽、中断状态查询功能,实际上这些功能在 GIC 里也实现了,觉得有点多余。除此之外,它还提供唤醒功能,这才是保留它的原因。
4. alpha 开发板中的按键中断
4.1 硬件原理图
开发板上有一个按键,原理图如下:

按键 KEY0 是连接到 I.MX6U 的 UART1_CTS 这个 IO 上的, KEY0 接了一个 10K 的上拉电阻,因此 KEY0 没有按下的时候 UART1_CTS 应该是高电平,当 KEY0 按下以后 UART1_CTS 就是低电平。搜一下参考手册就会发现,这个引脚是 GPIO1_IO18:

4.2 中断号
可以看这个笔记《LV04-07-中断与异常-05-IMX6ULL 按键中断实例 | 苏木》这里再简单了解一下。我们前边知道了按键接在了 GPIO1_IO18 上边,我们可以查看《I.MX6UL 参考手册》的 3.2 Cortex A7 interrupts 一节,找到这个 GPIO 管脚对应的中断号:

可以看到 GPIO1 的 0 -15 管脚使用的是 66,16 - 31 使用的是 67,这里只是 IRQ 的编号,对应到 GIC 的 SPI 中断号需要在此编号基础上加上 32,所以这里的按键中断号实际为 99(67+32)。但是其实在 linux 中开发的时候,会有函数(例如 gpio_to_irq())自动帮我们计算,我们只需要知道是哪个引脚就可以了。
4.3 触发方式
触发方式就可以看这个 irq.h - include/dt-bindings/interrupt-controller/irq.h:
#define IRQ_TYPE_NONE 0 // 无中断触发类型
#define IRQ_TYPE_EDGE_RISING 1 // 上升沿触发
#define IRQ_TYPE_EDGE_FALLING 2 // 下降沿触发
#define IRQ_TYPE_EDGE_BOTH (IRQ_TYPE_EDGE_FALLING | IRQ_TYPE_EDGE_RISING)// 双边沿触发
#define IRQ_TYPE_LEVEL_HIGH 4 // 高电平触发
#define IRQ_TYPE_LEVEL_LOW 8 // 低电平触发4.4 中断节点
按键 KEY0 使用中断模式,需要在“key”节点下添加中断相关属性,添加完成以后的“key”节点内容如下所示:
sdev_key {
#address-cells = <1>;
#size-cells = <1>;
compatible = "sdev_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";
};第 8 行,设置 interrupt-parent 属性值为“gpio1”,因为 KEY0 所使用的 GPIO 为 GPIO1_IO18,也就是设置 KEY0 的 GPIO 中断控制器为 gpio1。
第 9 行,设置 interrupts 属性,也就是设置中断源,第一个 cells 的 18 表示 GPIO1 组的 18 号 IO。
其中这个 gpio1 是在 imx6ul.dtsi:
gpio1: gpio@209c000 {
compatible = "fsl,imx6ul-gpio", "fsl,imx35-gpio";
reg = <0x0209c000 0x4000>;
interrupts = <GIC_SPI 66 IRQ_TYPE_LEVEL_HIGH>,
<GIC_SPI 67 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&clks IMX6UL_CLK_GPIO1>;
gpio-controller;
#gpio-cells = <2>;
interrupt-controller;
#interrupt-cells = <2>;
gpio-ranges = <&iomuxc 0 23 10>, <&iomuxc 10 17 6>,
<&iomuxc 16 33 16>;
};四、在代码中获得中断
之前我们学习设备树的时候 , 知道设备树中的节点有些能被转换为内核里的 platform_device,有些不能:
(1)根节点下含有 compatile 属性的子节点,会转换为 platform_device
(2)含有特定 compatile 属性的节点的子节点,会转换为 platform_device 如果一个节点的 compatile 属性,它的值是这 4 者之一: "simplebus", "simple-mfd", "isa", "arm, amba-bus",那么它的子结点(需含 compatile 属性)也可以转换为 platform_device。
(3)总线 I2C、 SPI 节点下的子节点: 不转换为 platform_device 某个总线下到子节点, 应该交给对应的总线驱动程序来处理, 它们不应该被转换为 platform_device。
1. 对于 platform_device
一个节点能被转换为 platform_device,如果它的设备树里指定了中断属性,那么可以从 platform_device 中获得“中断资源”,函数如下,可以使用 platform_get_resource() 函数获得 IORESOURCE_IRQ 资源,即中断号:
/**
* platform_get_resource - get a resource for a device
* @dev: platform device
* @type: resource type 取哪类资源? IORESOURCE_MEM、 IORESOURCE_REG、IORESOURCE_IRQ 等
* @num: resource index 这类资源中的哪一个?
*/
struct resource *platform_get_resource(struct platform_device *dev,
unsigned int type, unsigned int num)
{
//......
}2. 对于 I2C 设备、 SPI 设备
对于 I2C 设备节点, I2C 总线驱动在处理设备树里的 I2C 子节点时,也会处理其中的中断信息。一个 I2C 设备会被转换为一个 i2c_client 结构体,中断号会保存在 i2c_client 的 irq 成员里,代码如下(i2c-core-base.c - drivers/i2c/i2c-core-base.c):
static int i2c_device_probe(struct device *dev)
{
//......
if (!client->irq && !driver->disable_i2c_core_irq_mapping) {
int irq = -ENOENT;
if (client->flags & I2C_CLIENT_HOST_NOTIFY) {
//......
} else if (dev->of_node) {
irq = of_irq_get_byname(dev->of_node, "irq");
if (irq == -EINVAL || irq == -ENODATA)
irq = of_irq_get(dev->of_node, 0); //从设备树解析出中断号
} else if (ACPI_COMPANION(dev)) {
irq = acpi_dev_gpio_irq_get(ACPI_COMPANION(dev), 0);
}
//......
client->irq = irq;
}
//......
}对于 SPI 设备节点, SPI 总线驱动在处理设备树里的 SPI 子节点时,也会处理其中的中断信息。一个 SPI 设备会被转换为一个 spi_device 结构体,中断号会保存在 spi_device 的 irq 成员里 ,代码如下(spi.c - drivers/spi/spi.c):
static int spi_drv_probe(struct device *dev)
{
//......
if (dev->of_node) {
spi->irq = of_irq_get(dev->of_node, 0);//从设备树解析出中断号
//......
}
//......
}3. 调用 of_irq_get() 获得中断号
如果设备节点既不能转换为 platform_device,它也不是 I2C 设备,不是 SPI 设备,那么在驱动程序中可以自行调用 of_irq_get() 函数去解析设备树,得到中断号。
4. 对于 GPIO
可以使用 gpio_to_irq() 或者 gpiod_to_irq() 获得中断号。例如 gpio_keys.c - drivers/input/keyboard/gpio_keys.c 中第 559 行:
假设在设备树中有如下节点:
gpio-keys {
compatible = "gpio-keys";
pinctrl-names = "default";
user {
label = "User Button";
gpios = <&gpio5 1 GPIO_ACTIVE_HIGH>;
gpio-key,wakeup;
linux,code = <KEY_1>;
};
};那么可以使用下面的函数获得引脚和 flag:
button->gpio = of_get_gpio_flags(pp, 0, &flags);
bdata->gpiod = gpio_to_desc(button->gpio);再去使用 gpiod_to_irq 获得中断号:
irq = gpiod_to_irq(bdata->gpiod);参考资料:
Linux 中断管理 (1)Linux 中断管理机制 - ArnoldLu - 博客园
Linux 卸载驱动时,提示:Trying to free already-free IRQ-CSDN 博客
嵌入式 Linux 驱动笔记(二十七)------中断子系统框架分析_irq: type mismatch-CSDN 博客