从设备树到驱动代码:手把手教你获取并使用Linux内核中的中断号
从设备树到驱动代码:Linux内核中断处理全流程实战指南
在嵌入式Linux开发中,中断处理是驱动开发的核心技能之一。想象一下这样的场景:你正在为一个定制硬件开发驱动程序,当用户按下按钮或传感器数据到达时,系统需要立即响应。这种实时性要求极高的场景,正是中断机制大显身手的地方。本文将带你深入Linux中断处理的完整流程,从设备树配置到驱动代码实现,手把手教你构建可靠的中断驱动。
1. 设备树中的中断配置艺术
设备树作为硬件描述的载体,其中断配置是驱动开发的起点。一个典型的GPIO按键中断配置如下:
button@0 {
compatible = "gpio-keys";
gpios = <&gpio0 5 GPIO_ACTIVE_LOW>;
interrupt-parent = <&gpio0>;
interrupts = <5 IRQ_TYPE_EDGE_FALLING>;
label = "User Button";
};
这段配置揭示了设备树中断描述的三大要素:
- 中断父控制器:通过
interrupt-parent指定,本例中为GPIO控制器 - 中断描述:
interrupts属性包含引脚编号和触发方式 - GPIO关联:
gpios属性将物理引脚与逻辑功能绑定
对于更复杂的场景,比如多路中断共享同一控制器,设备树提供了灵活的表示方式:
multi_irq_device {
compatible = "vendor,multi-irq";
interrupts-extended = <&intc0 10 IRQ_TYPE_LEVEL_HIGH>,
<&intc1 5 IRQ_TYPE_EDGE_BOTH>;
};
1.1 中断控制器关键属性解析
每个中断控制器都需要声明两个特殊属性:
intc: interrupt-controller {
compatible = "vendor,irq-controller";
interrupt-controller;
#interrupt-cells = <3>; // 每个中断需要3个u32描述
};
表:常见中断控制器类型及配置差异
| 控制器类型 | #interrupt-cells | 典型描述格式 | 适用场景 |
|---|---|---|---|
| GPIO控制器 | 2 | <引脚号 触发方式> | 简单外设中断 |
| 级联中断控制器 | 3 | <父IRQ 子IRQ 触发方式> | 复杂SoC系统 |
| 高级中断控制器 | 4 | <类型 索引 子IRQ 触发方式> | ARM GIC等 |
2. 驱动中获取中断号的五种方式
获取中断号是驱动开发的第一步,Linux内核为此提供了多种API,各有适用场景。
2.1 Platform设备的标准方法
对于platform设备,经典流程如下:
static int demo_probe(struct platform_device *pdev)
{
struct resource *res;
int irq;
// 获取第0个IRQ资源
res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
if (!res) {
dev_err(&pdev->dev, "no IRQ resource\n");
return -ENODEV;
}
irq = res->start;
// 更简洁的新版API
irq = platform_get_irq(pdev, 0);
if (irq < 0) {
return irq; // 返回错误码
}
// 注册中断处理程序
return request_irq(irq, demo_isr, IRQF_TRIGGER_FALLING, "demo", NULL);
}
2.2 I2C/SPI设备的快捷方式
总线设备驱动通常可以直接从client结构获取预解析的中断号:
// I2C驱动示例
static int i2c_demo_probe(struct i2c_client *client)
{
int irq = client->irq; // 自动从设备树解析
if (!irq) {
// 备选方案:手动解析
irq = of_irq_get(client->dev.of_node, 0);
}
return devm_request_threaded_irq(&client->dev, irq, NULL,
demo_isr, IRQF_ONESHOT, dev_name(&client->dev), priv);
}
// SPI驱动类似
static int spi_demo_probe(struct spi_device *spi)
{
int irq = spi->irq; // 自动获取
// ...处理逻辑同上
}
2.3 GPIO中断的特殊处理
当使用GPIO作为中断源时,现代内核推荐使用GPIO描述符接口:
int demo_setup_gpio_irq(struct device *dev)
{
struct gpio_desc *gpiod;
int irq, ret;
// 获取GPIO描述符
gpiod = gpiod_get(dev, "intr", GPIOD_IN);
if (IS_ERR(gpiod)) {
return PTR_ERR(gpiod);
}
// 转换为中断号
irq = gpiod_to_irq(gpiod);
if (irq < 0) {
gpiod_put(gpiod);
return irq;
}
// 注册中断
ret = devm_request_irq(dev, irq, demo_isr,
IRQF_TRIGGER_RISING | IRQF_ONESHOT,
"gpio-irq", NULL);
// 可以保留gpiod用于后续管理
return ret;
}
3. 中断处理程序的高级技巧
获取中断号只是开始,编写稳健的中断处理程序才是真正的挑战。
3.1 中断处理函数的最佳实践
一个典型的中断服务例程(ISR)应该遵循以下原则:
static irqreturn_t demo_isr(int irq, void *dev_id)
{
struct demo_priv *priv = dev_id;
u32 status;
// 1. 快速读取中断状态
status = readl(priv->regs + REG_STATUS);
// 2. 确认是本设备中断
if (!(status & INTR_FLAG)) {
return IRQ_NONE; // 不是我们的中断
}
// 3. 清除中断标志
writel(status & INTR_FLAG, priv->regs + REG_STATUS);
// 4. 调度下半部处理
tasklet_schedule(&priv->tasklet);
return IRQ_HANDLED;
}
表:中断处理中的关键决策点
| 场景 | 处理方案 | 相关API | 适用条件 |
|---|---|---|---|
| 快速处理 | 直接在ISR中完成 | - | 处理时间<100us |
| 中等延迟 | tasklet | tasklet_* | 需要原子操作 |
| 复杂处理 | workqueue | schedule_work | 允许睡眠的操作 |
| 高频率 | threaded IRQ | request_threaded_irq | 需要优先级管理 |
3.2 中断共享与竞争处理
当多个设备共享同一中断线时,需要特别注意:
// 注册共享中断
ret = request_irq(irq, shared_isr, IRQF_SHARED, "demo", priv);
// 在ISR中正确处理共享
static irqreturn_t shared_isr(int irq, void *dev_id)
{
struct demo_priv *priv = dev_id;
if (!device_interrupted(priv)) {
return IRQ_NONE; // 不是本设备中断
}
// ...处理逻辑
return IRQ_HANDLED;
}
4. 从零构建GPIO中断驱动实例
让我们通过一个完整的GPIO按键驱动示例,串联所有知识点。
4.1 设备树配置
/ {
gpio-keys {
compatible = "gpio-keys";
#address-cells = <1>;
#size-cells = <0>;
button@1 {
label = "User Button";
linux,code = <KEY_ENTER>;
gpios = <&gpio0 12 GPIO_ACTIVE_LOW>;
interrupt-parent = <&gpio0>;
interrupts = <12 IRQ_TYPE_EDGE_BOTH>;
debounce-interval = <50>;
};
};
};
4.2 驱动实现关键代码
#include <linux/interrupt.h>
#include <linux/gpio/consumer.h>
struct button_data {
struct gpio_desc *gpiod;
int irq;
struct input_dev *input;
};
static irqreturn_t button_isr(int irq, void *dev_id)
{
struct button_data *data = dev_id;
int state = gpiod_get_value(data->gpiod);
input_report_key(data->input, KEY_ENTER, !state);
input_sync(data->input);
return IRQ_HANDLED;
}
static int button_probe(struct platform_device *pdev)
{
struct button_data *data;
struct device *dev = &pdev->dev;
int ret;
data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
if (!data)
return -ENOMEM;
// 获取GPIO描述符
data->gpiod = devm_gpiod_get(dev, NULL, GPIOD_IN);
if (IS_ERR(data->gpiod)) {
return PTR_ERR(data->gpiod);
}
// 转换为中断号
data->irq = gpiod_to_irq(data->gpiod);
if (data->irq < 0) {
return data->irq;
}
// 设置输入设备
data->input = devm_input_allocate_device(dev);
data->input->name = pdev->name;
input_set_capability(data->input, EV_KEY, KEY_ENTER);
// 注册中断
ret = devm_request_threaded_irq(dev, data->irq, NULL, button_isr,
IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING | IRQF_ONESHOT,
"gpio-button", data);
if (ret) {
return ret;
}
// 注册输入设备
return input_register_device(data->input);
}
static const struct of_device_id button_of_match[] = {
{ .compatible = "gpio-keys" },
{},
};
MODULE_DEVICE_TABLE(of, button_of_match);
static struct platform_driver button_driver = {
.driver = {
.name = "gpio-button",
.of_match_table = button_of_match,
},
.probe = button_probe,
};
module_platform_driver(button_driver);
4.3 调试与问题排查
当中断不工作时,可以按照以下步骤排查:
-
检查设备树解析:
# 查看解析后的中断信息 cat /proc/device-tree/gpio-keys/button@1/interrupts # 确认GPIO状态 cat /sys/kernel/debug/gpio -
验证中断注册:
# 查看已注册的中断 cat /proc/interrupts | grep gpio-button -
调试ISR执行:
// 在ISR中添加调试打印 printk(KERN_DEBUG "ISR triggered at %lld\n", ktime_get_ns()); -
测量中断延迟:
ktime_t start = ktime_get(); // ISR处理逻辑 ktime_t delta = ktime_sub(ktime_get(), start); printk("ISR took %lld ns\n", ktime_to_ns(delta));
在实际项目中,我们经常遇到GPIO配置冲突、中断触发条件不匹配、中断风暴等问题。例如,某次调试中发现按键中断频繁触发,最终发现是未配置去抖参数导致。通过在设备树中添加debounce-interval = <50>;成功解决了问题。
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐



所有评论(0)