首页 / 资讯中心 / 文章详情

Linux 驱动中断控制器级联:IRQ Domain 树形映射与线性分发底层剖析

Linux 驱动中断控制器级联:IRQ Domain 树形映射与线性分发底层剖析 ★ FEATURED ARTICLE
Linux 驱动中断控制器级联IRQ Domain 树形映射与线性分发底层剖析在嵌入式 Linux 系统中外设硬件中断的数量往往远超主控制器的原生引脚配额。在典型的工控核心板设计中SoC 内部的 ARM GICGeneric Interrupt Controller直接处理诸如 UART、DMA、网卡和定时器等核心外围。当板载数十路光耦隔离输入、限位开关与外扩按键时硬件架构师通常会通过片内 GPIO 控制器或片外 I2C/SPI 扩展芯片如 PCA9535、MAX7310实现中断输入扩展。这就构成了一种典型的多级级联中断拓扑片外扩展芯片的中断引脚接入 SoC 的某一个 GPIO 引脚该 GPIO 模块汇聚后再接入主 GIC 控制器。如果驱动开发者对内核的中断虚拟化机制缺乏透彻理解试图直接在驱动中硬编码硬件中断号或者在级联中断处理流中错误处理父子中断遮蔽Mask/Unmask系统极易陷入中断风暴、中断丢失或内核死锁。Linux 内核为了统一管理异构硬件控制器的中断编号引入了irq_domain机制。通过构建树形映射域与链式中断处理程序Chained IRQ Handler内核优雅地实现了硬件中断号与虚拟中断号的解耦与高速分发。硬件中断号hwirq与虚拟中断号virq的鸿沟在现代内核架构中virq是整个操作系统全局唯一的逻辑标识符驱动通过request_threaded_irq()注册的正是virq。而对于具体的物理中断控制器而言其内部寄存器管理的只是从 0 开始递增的物理偏移编号即hwirq。例如SoC 主 GIC 的 SPI 中断号可能为 32 到 255片上 GPIOA 控制器内部有 32 个引脚其硬件中断号固定为 0 到 31片外 I2C 扩展芯片有 16 个引脚其硬件中断号同样为 0 到 15。如果让两个独立的子控制器都使用硬件编号 0 向内核注册命名空间必然发生冲突。irq_domain就是每一个硬件中断控制器的“地址转换机构”负责将该控制器专属的hwirq动态映射到全局唯一的空闲virq并绑定对应的中断芯片操作流irq_chip。中断级联树形分发流水线级联中断触发时的硬件与软件处理流程遵循严格的接力链条硬件触发外扩 GPIO 引脚电平翻转芯片将自身的 INT 引脚拉低。父级捕获SoC 的 GPIO 控制器检测到边沿向主 GIC 发起中断请求。主核分发ARM 核心捕获异常GIC 驱动进入主中断上下文识别出该事件属于 GPIO 控制器。链式中断入口执行注册在父控制器上的链式处理函数chained_irq_enter()屏蔽Mask父级中断线防止重入。子域寻址驱动通过总线读取扩展芯片的状态寄存器确定具体的子引脚编号hwirq。虚拟映射转换调用irq_find_mapping(domain, hwirq)找到对应的virq。分发处理调用generic_handle_irq(virq)触发最终外设驱动的中断服务例程ISR。链式中断退出调用chained_irq_exit()解除父级屏蔽完成整个周期。设备树DTS树形级联拓扑定义设备树中通过interrupt-controller、#interrupt-cells和interrupt-parent显式声明父子层次/ { // 顶级中断控制器ARM GICv3 gic: interrupt-controller2c010000 { compatible arm,gic-v3; #interrupt-cells 3; interrupt-controller; reg 0x0 0x2c010000 0x0 0x10000; }; soc { // 片上 GPIO 控制器级联在 GIC 之下 gpio0: gpio40020000 { compatible vendor,custom-gpio; reg 0x0 0x40020000 0x0 0x1000; interrupt-parent gic; // 接入 GIC 的 SPI 中断 48高电平触发 interrupts 0 48 4; interrupt-controller; #interrupt-cells 2; gpio-controller; #gpio-cells 2; }; }; // 挂载在 GPIO0 引脚 15 上的工业光耦传感器 industrial_sensor { compatible vendor,opto-sensor; interrupt-parent gpio0; // 对应 gpio0 的 15 号引脚下降沿触发 interrupts 15 2; }; };级联中断控制器驱动实现下面是一个工业 GPIO 控制器级联驱动的核心实现展示了线性 IRQ Domain 的创建、链式中断处理以及硬件状态流转#include linux/module.h #include linux/init.h #include linux/interrupt.h #include linux/irq.h #include linux/irqdomain.h #include linux/irqchip/chained_irq.h #include linux/platform_device.h #include linux/io.h #define GPIO_LINES 32 #define REG_INT_STATUS 0x00 #define REG_INT_ENABLE 0x04 #define REG_INT_CLEAR 0x08 struct custom_gpio_chip { void __iomem *base; int parent_irq; struct irq_domain *domain; struct irq_chip irq_chip; raw_spinlock_t lock; }; // 子中断 Mask 回调 static void custom_irq_mask(struct irq_data *d) { struct custom_gpio_chip *chip irq_data_get_irq_chip_data(d); unsigned long flags; uint32_t mask; raw_spin_lock_irqsave(chip-lock, flags); mask readl_relaxed(chip-base REG_INT_ENABLE); mask ~(1U d-hwirq); writel_relaxed(mask, chip-base REG_INT_ENABLE); raw_spin_unlock_irqrestore(chip-lock, flags); } // 子中断 Unmask 回调 static void custom_irq_unmask(struct irq_data *d) { struct custom_gpio_chip *chip irq_data_get_irq_chip_data(d); unsigned long flags; uint32_t mask; raw_spin_lock_irqsave(chip-lock, flags); mask readl_relaxed(chip-base REG_INT_ENABLE); mask | (1U d-hwirq); writel_relaxed(mask, chip-base REG_INT_ENABLE); raw_spin_unlock_irqrestore(chip-lock, flags); } // 子域映射操作回调 static int custom_irq_domain_map(struct irq_domain *d, unsigned int virq, irq_hw_number_t hw) { struct custom_gpio_chip *chip d-host_data; irq_set_chip_data(virq, chip); irq_set_chip_and_handler(virq, chip-irq_chip, handle_level_irq); irq_set_probe(virq); return 0; } static const struct irq_domain_ops custom_domain_ops { .map custom_irq_domain_map, .xlate irq_domain_xlate_twocell, }; /* * 链式中断处理程序由父级 GIC 中断触发执行 */ static void custom_chained_irq_handler(struct irq_desc *desc) { struct custom_gpio_chip *chip irq_desc_get_handler_data(desc); struct irq_chip *parent_chip irq_desc_get_chip(desc); uint32_t status; int bit; // 屏蔽父级中断线防止重入 chained_irq_enter(parent_chip, desc); // 读取子控制器的中断状态寄存器 status readl_relaxed(chip-base REG_INT_STATUS); // 循环扫描发生翻转的引脚 for_each_set_bit(bit, (unsigned long *)status, GPIO_LINES) { // 清除子中断标志位写 1 清零 writel_relaxed(1U bit, chip-base REG_INT_CLEAR); // 查找映射并分发给外设驱动 unsigned int child_virq irq_find_mapping(chip-domain, bit); if (child_virq) { generic_handle_irq(child_virq); } } // 解除父级屏蔽 chained_irq_exit(parent_chip, desc); } static int custom_gpio_probe(struct platform_device *pdev) { struct device *dev pdev-dev; struct custom_gpio_chip *chip; struct resource *res; chip devm_kzalloc(dev, sizeof(*chip), GFP_KERNEL); if (!chip) return -ENOMEM; raw_spin_lock_init(chip-lock); res platform_get_resource(pdev, IORESOURCE_MEM, 0); chip-base devm_ioremap_resource(dev, res); if (IS_ERR(chip-base)) return PTR_ERR(chip-base); chip-parent_irq platform_get_irq(pdev, 0); if (chip-parent_irq 0) return chip-parent_irq; // 配置子中断控制器操作集 chip-irq_chip.name Custom-GPIO-IRQ; chip-irq_chip.irq_mask custom_irq_mask; chip-irq_chip.irq_unmask custom_irq_unmask; // 创建线性映射域 chip-domain irq_domain_add_linear(dev-of_node, GPIO_LINES, custom_domain_ops, chip); if (!chip-domain) return -ENOMEM; // 设置链式中断分发器 irq_set_chained_handler_and_data(chip-parent_irq, custom_chained_irq_handler, chip); dev_info(dev, 级联中断控制器注册成功支持 %d 路子中断\n, GPIO_LINES); return 0; }避坑指南不可在链式处理函数中休眠在工业级联驱动开发中存在一个极其危险的致命陷阱如果子控制器挂载在低速 I2C 或 SPI 总线上绝对不能使用irq_set_chained_handler_and_data()因为链式中断处理程序直接运行在顶级控制器GIC的硬中断HardIRQ上下文中处于关中断或关调度状态。而 I2C/SPI 总线控制器发起数据传输时底层需要等待传输完成中断并发生任务睡眠wait_for_completion()。一旦在硬中断上下文中尝试 I2C 寄存器读取内核会直接触发致命的BUG: scheduling while atomic崩溃。对于慢速总线级联芯片正确架构必须改用线程化级联中断处理使用devm_request_threaded_irq()分发在独立的内核中断工作线程中安全读取 I2C 寄存器然后再调用handle_nested_irq()完成嵌套下发。理解硬中断分发与线程化嵌套的边界是构建稳定工业级驱动的关键基石。
阅读完成 · 觉得有帮助?
咨询建站