尧图网络科技YAOTU DIGITAL 获取报价
获取报价
首页 / 资讯中心 / 文章详情

Linux中断子系统与驱动移植:一文讲透irq_domain和设备树中断映射

发布时间:2026/9/26 0:35:17

资讯中心
01
ARTICLE

Linux中断子系统与驱动移植:一文讲透irq_domain和设备树中断映射

Linux中断子系统与驱动移植:一文讲透irq_domain和设备树中断映射
做驱动移植时凡是遇到“硬件好好的内核就是不响应”这种诡异问题十有八九都跟中断子系统有关。我去年把一块基于全志H3的板子上的I2C触摸屏驱动迁到新内核设备树里明明写了interrupts 0 90 4request_irq却直接给我返回-EINVAL。查了两天最后发现是中断控制器节点没有正确注册irq_domain导致硬件中断号根本映射不到Linux的中断号上。那一次之后我老实把中断子系统的源码翻了一遍才算是把“整体框架”这几个字吃透了。这篇文章我想用实际移植的视角把Linux中断子系统从上到下拆开讲。不聊空泛的概念就讲你在驱动移植时会碰到的哪些东西、背后的框架是怎么串起来的、以及排查问题的真正套路。不管你是移植GPIO按键、网卡、I2C、SPI设备还是第三方外设中断框架都是一个绕不过去的底座理解了它你写的驱动才不会是靠试错拼出来的。1. 中断子系统到底在解决什么问题1.1 一次触摸屏中断的血泪排查回到我前面说的问题。旧内核里驱动可以直接用gpio_to_irq()拿到中断号迁到新环境后设备树写法没问题GPIO也申请成功了就是request_irq失败。后来打印platform_get_irq的返回值发现拿到了一个奇怪的负数一看代码才发现是irq_find_mapping没找到映射。这个现象背后就是中断子系统里最重要的一个逻辑硬件中断请求到达CPU之前要经历一条从“外设引脚”到“Linux irq number”的翻译链路而这条链路的骨架就是irq_domain。类似的情况还有中断号对上了但中断触发方式不对中断能触发但只触发一次中断处理函数里没清中断导致系统卡死。这些问题如果只盯着驱动代码看永远也找不到答案必须从全局看中断子系统的设计。1.2 中断子系统的分层结构Linux中断子系统本质上是一个分层的翻译和处理架构从下往上分四层硬件设备层外设按键、网卡、传感器产生电信号即硬件中断请求。中断控制器层负责收集、仲裁、屏蔽和优先级管理常见的如GIC、GICv2、GICv3以及GPIO控制器本身也常作为中断控制器。内核通用层向上提供一个统一的API也就是request_irq()、devm_request_irq()、free_irq()这些。这一层隐藏了中断控制器的差异让驱动开发者面对的是一个整型的中断号。驱动使用层你的驱动里申请中断、注册处理函数、处理数据。驱动移植时最容易出问题的不是你的驱动代码而是中断控制器的驱动是否正确注册、设备树里的中断父节点是否指对了、#interrupt-cells是否一致。这些问题都属于框架层面。2. 中断子系统的核心框架拆解2.1 硬件路径外设到CPU的跑线图先把硬件路径讲明白。以按键为例GPIO引脚收到一个下降沿这个电信号会发给GPIO控制器GPIO控制器内部寄存器发生变化然后它作为中断控制器把信号通过中断线送到主中断控制器比如GICGIC最后通过nIRQ或nFIQ引脚通知CPU核。CPU核在异常向量表里找到IRQ的入口跳到内核的异常处理代码。这里面有一个关键点中间可能经过不止一个中断控制器。比如一个GPIO控制器挂在GIC下面这时候GPIO控制器就是GIC的子中断控制器。设备树里要体现出这层嵌套关系驱动移植时如果你把interrupt-parent指错了中断就根本不会流转到你的驱动里。在ARM体系里GIC会区分两类中断源SPI共享外设中断和PPI私有外设中断。SPI是各个CPU核共享的适合外设PPI是每个核私有的比如每核的定时器。设备树里经常见到的0 90 4这种三元组第一个0表示是SPI第二个90是硬件中断号第三个4是触发方式。学过GIC的人一看到这个就知道是SPI中断。2.2 中断控制器驱动与irq_chip每个中断控制器在Linux里都对应一个struct irq_chip这里面定义了控制这个控制器硬件所需要的回调函数。比较核心的几个irq_mask/irq_unmask屏蔽/取消屏蔽某一个中断源对应中断控制器寄存器里的使能位。irq_ack确认中断通常是读寄存器或者写操作让控制器知道你已经处理完了。irq_set_type设置触发方式上升沿、下降沿、高电平、低电平。irq_set_irq_irqchip这一套是高层封装。驱动移植时如果你用的是自己写的板子可能还需要改中断控制器驱动。比如内核自带的irq-gic.c是针对ARM GIC的或者irq-gpio.c、irq-mxs.c。irq_chip在驱动开发中并不是直接打交道的但你要理解每个Linux中断号背后都关联着一个irq_chip和一组它所属的中断控制器的私有数据。request_irq时内核最终会调用这个irq_chip的irq_unmask来打开中断。2.3 irq_domain中断号翻译官irq_domain是整个中断子系统中最核心、也最容易在移植中出错的模块。它的作用很简单把硬件中断号hwirq翻译成Linux的IRQ号irq number。为什么需要翻译因为设备树里告诉内核的只是硬件中断号而Linux内部希望用连续、有序的irq_desc索引来管理每个中断。irq_domain有三种主要映射方式线性映射linear硬件中断号跟Linux中断号是线性对应关系适用于硬件中断号较少且基本连续的中断控制器比如GPIO控制器。用irq_domain_add_linear()创建。树形映射tree用于大量稀疏硬件中断号的场景用基数树管理查找稍慢比如PCI MSI。无映射nomap直接使用硬件中断号作为Linux中断号比如某些简单的系统。在设备树里每个中断控制器节点会有一个#interrupt-cells属性表明它下面每个设备的中断描述需要几个cell。比如GIC是3个cellgpio控制器也可能是2个cell。irq_domain通过xlate回调来翻译设备树里的这些cell值解析成hwirq然后查映射表得到Linux IRQ号。2.4 通用中断处理流程当一个中断触发后CPU进入异常向量最终走到handle_irq()。这个函数首先根据IRQ号找到对应的irq_desc然后调用这个中断的irq_flow_handler也就是我们常说的上层处理函数比如handle_level_irq()或handle_edge_irq()。这个flow handler根据触发类型和硬件行为决定何时调用irq_desc里注册的各个action每个action对应一个request_irq注册的处理函数。这个流程里一个常见的坑是多个设备共享一个中断号。如果是电平触发就需要handle_level_irq在调用action时同时检查是否为多个设备服务并且要保证中断确实被清掉否则就会陷入死循环。这也是为什么某些驱动移植后出现“中断风暴系统卡死”的原因。3. 驱动移植必须搞懂的三个中断号3.1 三个号码的来龙去脉很多人刚接触时会被三个“中断号”搞混硬件中断号hwirq设备硬件输出的那个中断编号由中断控制器的硬件设计决定比如GIC里的SPI 90。Linux IRQ号irq number内核中唯一的整数编号用于索引struct irq_desc是通过irq_domain分配出来的可能是160、123、或者任意整数。设备树中断说明符interrupt specifier由#interrupt-cells决定的一组数字比如0 90 4。在驱动代码里你拿到的一般是Linux IRQ号。拿到的方式通常有platform_get_irq(pdev, 0)从平台设备资源里获取第一个中断号这是最常用的方式它会去设备树里解析中断属性。irq_of_parse_and_map(node, 0)直接解析节点里的interrupts属性并返回映射后的IRQ号。gpio_to_irq()把一个GPIO编号转换为它所属的中断控制器的IRQ号但要注意GPIO子系统的irq domain是否已经准备好。3.2 从设备树到Linux IRQ号的完整转换链设备树里的中断属性解析过程可以从三条路径来看of_irq_get()遍历设备节点的interrupt-parent或父节点的interrupts找到中断控制器节点。通过控制器节点的#interrupt-cells得到描述符的长度然后读取interrupts里指定的cell值。调用irq_create_of_mapping()该函数找到对应控制器注册的irq_domain的xlate回调转成hwirq再调用irq_domain_alloc_irqs()或irq_find_mapping()得到最后可用的IRQ号。这条链上任何一点断了都会出现中断申请失败。最常见的是#interrupt-cells不匹配例如你的GPIO控制器声明了2个cell但设备节点里却写了3个数字解析直接出错。其次是interrupt-parent没有写内核会向上找父级的父级有时会指到系统根节点导致irq_domain不对。3.3 在驱动中正确获取中断号的代码范式正常情况下在platform驱动里获取中断号是这样写的#include linux/platform_device.h #include linux/interrupt.h static int my_probe(struct platform_device *pdev) { int irq platform_get_irq(pdev, 0); if (irq 0) { dev_err(pdev-dev, failed to get irq: %d\n, irq); return irq; } ret devm_request_irq(pdev-dev, irq, my_irq_handler, IRQF_TRIGGER_FALLING, my_dev, dev_data); if (ret) { dev_err(pdev-dev, request irq failed: %d\n, ret); return ret; } return 0; }这里有几个细节值得注意。platform_get_irq()返回负数就表示失败但不少驱动会把负数直接当作中断号传给request_irq结果触发-EINVAL。我在实际代码评审里见过好几次这种低级错误。另外一个细节是devm_request_irq()在卸载驱动时会自动释放中断不需要手动free_irq能避免忘记释放的问题。4. 设备树中的中断配置与解析细节4.1 interrupts属性的标准写法设备树里中断属性的写法由中断控制器的#interrupt-cells决定。以最简单的一个GPIO中断控制器为例假设它定义了2个cell第一个cell是GPIO号第二个cell是触发标志。那么设备节点可以这样写gpio_keys { compatible gpio-keys; #address-cells 1; #size-cells 0; interrupt-parent gpio_port_a; interrupts 14 IRQ_TYPE_EDGE_FALLING; };如果中断控制器是GIC三个cell的含义分别为中断类型0SPI1PPI、中断号、触发标志。触发标志的宏定义在设备树绑定文档里常用的是IRQ_TYPE_EDGE_RISING 1IRQ_TYPE_EDGE_FALLING 2IRQ_TYPE_EDGE_BOTH 3IRQ_TYPE_LEVEL_HIGH 4IRQ_TYPE_LEVEL_LOW 8在设备树中这些值不能直接写宏名字要写数字。比如0 90 4表示SPI、中断号90、高电平触发。4.2 interrupt-parent与中断控制器嵌套当系统里有多个中断控制器时设备节点需要一个interrupt-parent来指明自己用哪个控制器的编号空间。不要以为有了这个属性就一定对啊很多移植问题都是父子层级混乱导致的。例如一个I2C控制器挂在GIC上的同时该I2C控制器上又挂了触摸屏触摸屏中断源如果指定的是I2C控制器作为中断父节点那设备树里interrupt-parent i2c0i2c0节点自己又需要有interrupt-parent gic以及interrupts属性。如果漏掉中间一层内核会把触摸屏的中断号硬套到GIC的编号空间里于是中断触发后真正响应的却是另一个毫不相干的外设。移植时建议用工具打印设备树实际被内核解析后的状态。我这里习惯在板子上执行cat /proc/device-tree/mydevice/interrupt-parent或直接看内核日志里irq: no irq domain found for node之类的报告。4.3 解析过程的代码调度不再贴源码细节只说关键路径。of_irq_get()内部会查找interrupt-parent并获取struct device_node。随后of_irq_parse_one()读出每个cell的值通过irq_create_of_mapping()进入irq_domain领域。这里有个容易踩坑的点irq_create_of_mapping()会为还没有映射的中断分配一个新的Linux IRQ号但如果interrupt-parent对应的节点根本没有注册irq_domain也就是中断控制器驱动没初始化函数就会失败并返回0还是负数要看版本最终导致申请中断失败。做驱动移植前第一件事就是确认中断控制器驱动已经在系统里跑起来。可以看/proc/interrupts里中断控制器的名字或者dmesg里是否出现GIC、gpio-irq等初始化日志。5. 一个完整的中断驱动移植案例GPIO按键5.1 硬件假设与设备树配置假设我的板子有一颗按键接在 GPIO bank A 的第14号引脚上按下时引脚从高电平变为低电平。设备树里我设置了gpio-keys节点这是内核标准按键驱动。/ { gpio_keys: gpio_keys { compatible gpio-keys; pinctrl-names default; pinctrl-0 pinctrl_gpio_key; status okay; btn_enter { label ENTER; gpios gpio_port_a 14 GPIO_ACTIVE_LOW; linux,code KEY_ENTER; }; }; };如果用的是GPIO中断gpio-keys驱动会自动通过GPIO子系统申请中断。不过为了演示interrupts属性的用法我会再列出用中断控制器的写法前提是把GPIO控制器本身也作为中断控制器btn_enter { label ENTER; interrupt-parent gpio_port_a; interrupts 14 IRQ_TYPE_EDGE_FALLING; linux,code KEY_ENTER; };如果GPIO控制器没有注册成中断控制器第二种写法会失败。这也是移植时经常遇到的两难选择。5.2 驱动代码的两种实现路径如果你用gpio_keys这种标准驱动不需要自己写中断处理函数。但如果你自己接一个非标外设比如压力传感器在驱动里申请中断就是典型的写法static irqreturn_t my_button_isr(int irq, void *data) { struct my_device *dev data; /* 清硬件中断标志位 */ regmap_clear_bit(dev-regmap, MY_REG_STATUS, MY_BIT_IRQ); /* 上报事件 */ input_report_key(dev-input, KEY_ENTER, 1); input_sync(dev-input); return IRQ_HANDLED; }这个函数有几个强制要求第一不能在ISR里调用可能睡眠的函数比如msleep、mutex_lock、i2c_transfer否则会触发内核调度器警告甚至死锁。第二如果是边沿触发必须确认硬件是否会自动清中断如果是电平触发则一定要显式清除中断源否则会反复进入中断。5.3 验证中断是否真的映射成功每次改完设备树和驱动重新编译烧写后我会优先做这三步查看设备树解析是否正常。查看cat /proc/interrupts看对应中断号是否存在是哪个设备申请了。测试按键是否产生事件cat /dev/input/event0或者用evtest。一个正常的按键中断在/proc/interrupts里应该有类似这样的一行34: 0 0 gpio_irq 14 gpio_keys如果中断号根本不在这个表里说明申请阶段就没成功要回看dmesg。如果中断号在但计数器不变说明硬件信号没到达中断控制器这时候要看引脚复用、上下拉、时钟门控等硬件层面。5.4 移植中几个高频修改点我在不同板子上移植按键中断经常调这几个地方GPIO_ACTIVE_LOW与IRQ_TYPE_EDGE_FALLING的不一致gpio_keys驱动内部会根据GPIO的active属性转换触发标志但自己写驱动时不会自动转必须手动设置。引脚的pinmux被其他功能占用GPIO中断信号根本到不了控制器。这类问题最隐蔽查了半天中断代码最后发现是pinmux配置不对。中断控制器驱动没有使能有些SoC主控的GPIO中断分组需要开对应模块的时钟否则写寄存器等于白写。6. 中断下半部别把系统拖垮6.1 为什么不能在ISR里做太多事很多人刚开始写中断驱动都喜欢在中断函数里把数据读完、解析完、再通知用户空间。结果就是中断函数里耗时过长导致其他中断被延迟系统响应变差。因为硬中断处理过程中当前CPU核的本地中断是关闭的而且不能调度、不能睡眠任何阻塞操作都会引起严重问题。正确做法是把硬中断处理函数视为“快进快出”的紧急通道只做必要的硬件操作比如读状态寄存器、清中断位然后把后续工作打包給下半部机制去执行。6.2 下半部机制对比与选型Linux提供多种下半部机制实际驱动开发中主要面对这几个机制适用场景特点softirq内核子系统网络收发不可动态注册一般开发者不用tasklet较简单的下半部工作基于软中断串行执行不能睡眠简单稳定但容易堆积工作队列可睡眠、耗时较长的工作在进程上下文执行可以被调度但不能用于非常紧急的场景threaded irq中断线程化常见于需要访问I2C/SPI的驱动简单直接内核专门创建线程来运行中断处理函数可以睡眠hrtimer/内核线程周期性的轻量任务更灵活但代码复杂度高对你来说可能最实用的是threaded_irq。很多触摸屏、传感器驱动都用了它因为他们需要在中断里通过I2C读取数据而I2C访问依赖i2c_transfer这个函数在进程上下文可以睡眠但在硬中断里绝对不能用。6.3 线程化中断的驱动写法request_threaded_irq()接口比request_irq()多一个参数ret request_threaded_irq(irq, NULL, my_threaded_isr, IRQF_TRIGGER_FALLING | IRQF_ONESHOT, my_dev, dev);第一个回调参数传NULL意思是快handler直接在硬中断上下文执行如果你只想用线程可以这样传。第二个参数my_threaded_isr才是真正的处理函数在内核线程里运行可以睡眠。IRQF_ONESHOT标志告诉内核在中断线程执行期间自动屏蔽中断避免中断风暴特别适合电平触发的中断源。我移植Sensor驱动时通常直接在probe里用devm_request_threaded_irq()这样省去手动释放的麻烦。要注意的是线程化中断的线程名和优先级可以通过接口调整但一般默认足够。7. 中断调试三板斧看得见才算数7.1 /proc/interrupts 和 /proc/irq/ 的使用/proc/interrupts是调试中断的第一手资料。除了关注某一行的计数还要看对应中断号在所有CPU核上的分布。如果只有CPU0在增加其他核没有说明亲和性设置不对或者全局中断分配方式问题。/proc/irq/irq/目录下有一些子文件比如affinity、spurious、node。可以在运行时用echo ff /proc/irq/34/affinity修改中断绑定到哪些CPU。调试时修改亲和性可以验证硬件是否真能送到其他核。7.2 内核tracepoint与动态追踪内核提供irq_entry、irq_exit、softirq_entry等tracepoint。用tracefs可以快速看到中断事件是否触发cd /sys/kernel/tracing echo 0 tracing_on echo irq_handler_entry set_event echo irq_handler_exit set_event echo 1 tracing_on cat trace这样能看到每个中断进入和退出的时间戳、函数名以及CPU信息。如果中断发生了但你的驱动没有动作可能问题在下半部如果中断没发生问题在上游硬件路径或缺映射。还有一个强力工具是perfperf top可以看到软中断和硬中断的占比。有时候中断函数耗时会直接反映在中断上下文CPU占用上。7.3 遇到“irq xxx: nobody cared”怎么办这是所有驱动开发者最头痛的提示。完整日志通常会提示你可能存在irq_chip没有正确调用ack或者mask并显示链接到irq_report_spurious。意思是内核检测到一个中断没有被任何action处理然后强制禁用它。出现这个问题的常见原因有你的request_irq因为某些原因失败但中断依然会被硬件触发这时系统会记录spurious。共享中断里某个驱动没有检查自己的设备是否真的产生中断直接返回IRQ_NONE。中断触发后硬件没有自动清除标志CPU反复进入中断最终超过阈值被系统禁用。对策是先在dmesg里看spurious的中断号再检查该中断号对应的设备是否申请成功、中断处理函数是否正确返回IRQ_HANDLED。实在不行可以在中断处理函数里加计数器并打印R0、SP等寄存器信息配合硬件文档确认寄存器状态。8. 一些我踩过坑后总结的习惯最后分享几个个人经验是我做驱动移植时反复碰壁后沉淀下来的习惯。第一拿到一个新板子的第一个任务不是急于写设备树和驱动而是先确认中断控制器框架在目标内核版本里是否完整。看dmesg里有没有中断控制器初始化日志用cat /proc/interrupts看看系统空闲中断大概长什么样。如果这个文件只有几句话那说明中断框架很可能没起来。第二注意内核版本的差异。旧内核用的irq_domain_add_simple和现在用的irq_domain_add_linear行为就不一样。很多设备树在旧版本上能跑升级内核后却失效就是这些注册接口变了。第三我强烈建议你把irq_domain源码读一遍不需要全懂就搞懂irq_create_mapping、irq_find_mapping和irq_set_chip_and_handler这三个函数。它们是整个中断映射的基石你调试中遇到的很多问题最终都会落到这几行代码上。第四如果你在移植Linux到一块新板子建议先用一个简单的GPIO按键做“中断冒烟测试”不要一上来就接网卡、触摸屏这类复杂设备。一个GPIO中断能正常计数说明整个中断链路已经打通后续的外设中断基本都是重复这个模式。这行当里一切“玄学”问题最后几乎都能用中断子系统的整体框架解释。框架记在脑子里排查问题就按图索骥先看硬件触发有没有到控制器再看映射有没有成功最后看处理函数有没有正确执行。三步走完90%的中断问题都能定位到具体环节。剩下的10%不是太玄而是你的硬件忍了你太久。
02
RELATED NEWS

相关资讯

更多网站建设与数字化升级内容

03
WHY YAOTU

想打造同款高转化官网?

懂行业、懂生意,从建站到增长一站式陪跑

◈

场景化定制

不做模板站,围绕你的业务场景量身设计,小众不撞款。

◐

营销型架构

以转化目标组织内容与路径,让官网真正带来询盘。

▲

全周期服务

设计、开发、运营、运维一体,上线只是开始。

免费获取你的建站方案

留下需求,专属顾问 24 小时内为你输出方案建议。