4. 中断号获取:设备树中中断描述与 platform_get_irq() 函数使用
在 Subdev 驱动开发中,中断号的正确获取是中断处理流程的起点。设备树(Device Tree)提供了描述硬件中断连接的标准方式,而 platform_get_irq() 是驱动开发者最常用的中断号获取接口。本章将深入解析设备树中的中断描述规范,并详细讲解 platform_get_irq() 的使用方法、参数含义及常见陷阱。
4.1 设备树中的中断描述规范
设备树通过 interrupts 属性和 interrupt-parent 属性来描述设备的中断连接关系。中断控制器(Interrupt Controller)是中断路由的核心节点,负责接收来自多个设备的中断信号并转发给 CPU。
4.1.1 中断描述的基本属性
| 属性名 | 类型 | 说明 |
|---|---|---|
interrupt-parent |
phandle | 指向设备所连接的中断控制器节点 |
interrupts |
u32 数组 | 描述中断号、触发类型等信息的元组 |
interrupt-names |
string 数组 | 为每个中断指定名称,便于驱动按名获取 |
interrupts-extended |
phandle + u32 数组 | 当设备连接多个不同中断控制器时使用 |
4.1.2 中断元组格式
interrupts 属性的元组格式由中断控制器的 #interrupt-cells 属性决定。常见的中断控制器类型及其元组格式如下:
| 中断控制器类型 | #interrupt-cells | 元组格式 | 示例 |
|---|---|---|---|
| GIC (Generic Interrupt Controller) | 3 | <中断类型 中断号 触发标志> | <0 42 4> |
| 简单中断控制器 | 1 | <中断号> | <31> |
| 带触发类型的中断控制器 | 2 | <中断号 触发标志> | <31 2> |
4.1.3 设备树中断描述示例
// 中断控制器节点
gic: interrupt-controller@f0001000 {
compatible = "arm,gic-400";
reg = <0x0 0xf0001000 0x0 0x1000>;
interrupt-controller;
#interrupt-cells = <3>;
};
// 使用 GIC 的子设备节点
uart0: serial@f0002000 {
compatible = "vendor,uart";
reg = <0x0 0xf0002000 0x0 0x1000>;
interrupt-parent = <&gic>;
interrupts = <0 42 4>; // SPI 中断号 42,高电平触发
interrupt-names = "rx_irq";
};
// 使用 interrupts-extended 连接多个中断控制器
dma: dma-controller@f0003000 {
compatible = "vendor,dma";
reg = <0x0 0xf0003000 0x0 0x1000>;
interrupts-extended = <&gic 0 55 4>, <&intc 8>;
interrupt-names = "dma_done", "dma_error";
};
4.2 platform_get_irq() 函数详解
platform_get_irq() 是 Linux 内核为 platform 设备驱动提供的中断号获取标准 API。该函数封装了设备树解析、中断域映射等底层操作,驱动开发者只需传入设备指针和中断索引即可获得中断号。
4.2.1 函数原型与参数
#include <linux/platform_device.h>
/**
* platform_get_irq - 获取 platform 设备的中断号
* @pdev: platform 设备指针
* @num: 中断资源索引(从 0 开始)
*
* 返回值:成功返回中断号(正数),失败返回负错误码
*/
int platform_get_irq(struct platform_device *pdev, unsigned int num);
| 参数 | 说明 | 注意事项 |
|---|---|---|
pdev |
由 probe 函数传入的 platform_device 指针 | 必须非空,否则返回 -EINVAL |
num |
中断资源的索引号,从 0 开始 | 与设备树中 interrupts 属性的顺序对应 |
| 返回值 | 成功返回中断号(≥0),失败返回负值 | 常见错误:-EPROBE_DEFER, -EINVAL, -ENXIO |
4.2.2 使用示例
static int my_subdev_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
int irq;
int ret;
// 获取第一个中断(索引 0)
irq = platform_get_irq(pdev, 0);
if (irq < 0) {
dev_err(dev, "failed to get irq: %d\n", irq);
return irq;
}
dev_info(dev, "got irq number: %d\n", irq);
// 注册中断处理函数
ret = request_irq(irq, my_interrupt_handler,
IRQF_TRIGGER_HIGH | IRQF_SHARED,
"my_subdev", dev);
if (ret) {
dev_err(dev, "failed to request irq %d: %d\n", irq, ret);
return ret;
}
return 0;
}
static int my_subdev_remove(struct platform_device *pdev)
{
int irq = platform_get_irq(pdev, 0);
if (irq >= 0)
free_irq(irq, &pdev->dev);
return 0;
}
4.2.3 多中断场景处理
当设备具有多个中断时,可以使用 platform_get_irq_byname() 按名称获取,或通过循环遍历所有中断:
// 方法一:按名称获取(推荐)
int rx_irq = platform_get_irq_byname(pdev, "rx_irq");
int tx_irq = platform_get_irq_byname(pdev, "tx_irq");
// 方法二:按索引循环获取
int irq;
int i = 0;
while ((irq = platform_get_irq(pdev, i)) >= 0) {
dev_info(dev, "irq[%d] = %d\n", i, irq);
// 为每个中断注册处理函数
request_irq(irq, handler_array[i], 0, "my_dev", &dev_data[i]);
i++;
}
4.3 中断号获取的完整流程
从设备树描述到驱动获取中断号,中间经历了多个内核子系统的协同处理。理解这一流程有助于排查中断相关的问题。
4.4 常见错误与调试方法
4.4.1 典型错误码分析
| 错误码 | 含义 | 常见原因 | 排查方向 |
|---|---|---|---|
-EPROBE_DEFER |
延迟探测 | 中断控制器驱动尚未加载 | 检查中断控制器节点 compatible 匹配 |
-EINVAL |
无效参数 | pdev 为空或 num 超出范围 | 检查设备树中 interrupts 属性数量 |
-ENXIO |
无此设备 | 设备树中未定义中断资源 | 检查设备节点是否包含 interrupts 属性 |
-ENOENT |
无此条目 | interrupt-names 名称不匹配 | 检查名称拼写与设备树定义一致 |
4.4.2 调试技巧
- 查看设备树内容:使用
dtc -I fs -O dts /sys/firmware/devicetree/base导出当前设备树,检查中断属性是否正确。 - 检查中断映射:查看
/proc/interrupts确认中断号是否已注册,以及中断触发次数。 - 启用内核调试:在 cmdline 中添加
irq_debug或dyndbg="file irqdomain.c +p"获取详细的中断映射日志。 - 使用 dev_dbg:在 probe 函数中添加
dev_dbg(dev, "irq=%d\n", irq);确认获取到的中断号。
4.5 中断号获取的进阶话题
4.5.1 中断号与 irq_domain 的关系
每个中断控制器在 Linux 内核中对应一个 irq_domain 实例。设备树中的 interrupt-parent 属性指向中断控制器节点,内核通过该引用找到对应的 irq_domain,然后调用 irq_create_of_mapping() 将硬件中断号转换为全局唯一的虚拟中断号。
4.5.2 共享中断的处理
当多个设备共享同一个中断号时,需要在 request_irq() 中设置 IRQF_SHARED 标志,并且中断处理函数必须能够区分中断来源:
irq = platform_get_irq(pdev, 0);
if (irq < 0)
return irq;
// 共享中断必须设置 IRQF_SHARED
ret = request_irq(irq, shared_handler,
IRQF_SHARED | IRQF_TRIGGER_HIGH,
"my_subdev", dev);
if (ret) {
dev_err(dev, "request_irq failed: %d\n", ret);
return ret;
}
// 中断处理函数中必须检查硬件状态
irqreturn_t shared_handler(int irq, void *dev_id)
{
struct my_dev *dev = dev_id;
if (!(readl(dev->regs + STATUS_REG) & IRQ_PENDING))
return IRQ_NONE; // 不是本设备的中断
// 处理中断
writel(IRQ_CLEAR, dev->regs + CLEAR_REG);
return IRQ_HANDLED;
}
4.5.3 中断号的动态分配
在某些场景下(如 PCIe 设备、动态创建的 platform 设备),中断号不是在设备树中静态定义的,而是通过 irq_alloc_descs() 动态分配。此时驱动应使用 platform_get_resource(pdev, IORESOURCE_IRQ, num) 获取中断资源,而非直接调用 platform_get_irq()。