rk3588/kernel-6.1 中断流程与上下半部机制分析 1. 中断处理整体框架 Linux 内核中断处理通常分为:
1 2 3 4 5 6 7 8 9 10 11 硬件中断 -> CPU 异常入口 -> 架构层 IRQ handler -> 中断控制器 GIC handler -> generic IRQ core -> ISR / top half -> bottom half -> softirq -> tasklet -> workqueue -> threaded IRQ
用户提到的经典模型:
1 硬件中断 -> ISR -> bottom half
在 Linux 6.1 中可以展开为:
1 2 3 4 5 6 7 8 9 10 硬件 IRQ -> arm64 exception vector -> el1h_64_irq_handler() -> handle_arch_irq() -> gic_handle_irq() -> generic_handle_domain_irq() -> irq_desc->handle_irq() -> handle_irq_event() -> irqaction->handler() -> softirq/tasklet/workqueue/thread_fn
核心目录:
arch/arm64/kernel/entry-common.c
arch/arm64/kernel/irq.c
drivers/irqchip/irq-gic-v3.c
kernel/irq/
kernel/softirq.c
kernel/workqueue.c
include/linux/interrupt.h
include/linux/irqdesc.h
include/linux/irq.h
2. 中断核心文件作用 kernel/irq/Makefile 中核心模块:
1 2 3 4 5 6 7 8 9 10 11 irqdesc.o IRQ 描述符分配和管理 handle.o 通用 IRQ 分发,调用驱动 ISR manage.o request_irq/request_threaded_irq/free_irq chip.o 电平/边沿/fasteoi 等 flow handler irqdomain.o hwirq 到 Linux virq 映射 msi.o MSI/MSI-X 中断 ipi.o SMP IPI affinity.o IRQ affinity proc.o /proc/interrupts 和 /proc/irq pm.o suspend/resume 中断管理 spurious.o 异常/伪中断检测
重要结构:
struct irq_desc:一个 Linux IRQ 的核心描述符;
struct irq_data:IRQ 与 irqchip/domain 的底层数据;
struct irq_chip:中断控制器操作;
struct irqaction:驱动注册的中断处理函数;
struct irq_domain:硬件中断号 hwirq 到 Linux irq 的映射域。
3. ARM64 硬件中断入口 RK3588 是 ARM64 SoC,普通外设中断会从 ARM64 异常向量进入。
相关文件:
arch/arm64/kernel/entry-common.c
arch/arm64/kernel/irq.c
关键入口:
1 2 3 4 5 6 el1h_64_irq_handler() -> el1_interrupt() -> __el1_irq() -> irq_enter_rcu() -> do_interrupt_handler(regs, handle_arch_irq) -> irq_exit_rcu()
其中:
irq_enter_rcu():进入 hardirq 上下文,更新 preempt count、RCU、统计;
do_interrupt_handler():必要时切换到 IRQ stack 后调用 handler;
irq_exit_rcu():退出 hardirq,并在合适时机处理 pending softirq;
handle_arch_irq:架构 root IRQ handler,由具体 irqchip 初始化时设置。
arch/arm64/kernel/irq.c 中:
1 2 3 4 init_IRQ() -> init_irq_stacks() -> init_irq_scs() -> irqchip_init()
irqchip_init() 会根据设备树初始化 GICv3 等中断控制器。
4. GICv3 中断控制器入口 RK3588 ARM64 平台通常使用 GICv3。
相关文件:
drivers/irqchip/irq-gic-v3.c
初始化中:
1 2 3 4 5 gic_init_bases() -> set_handle_irq(gic_handle_irq) -> gic_dist_init() -> gic_cpu_init() -> gic_smp_init()
中断到来后:
1 2 3 4 5 gic_handle_irq() -> gic_read_iar() -> __gic_handle_irq() -> gic_complete_ack() -> generic_handle_domain_irq(gic_data.domain, irqnr)
其中:
GIC IAR 寄存器给出硬件中断号 irqnr;
generic_handle_domain_irq() 通过 irq_domain 把 hwirq 转成 Linux irq;
转换后进入 generic IRQ core。
这一步完成了:
1 GIC hardware interrupt ID -> Linux virtual IRQ number
5. irq_domain:硬件中断号到 Linux IRQ 相关文件:
kernel/irq/irqdomain.c
include/linux/irqdomain.h
为什么需要 irq_domain?
不同中断控制器的硬件中断号可能重复,层级也不同。Linux 需要统一的逻辑 IRQ 编号给驱动使用。
典型映射:
1 2 3 4 5 设备树 interrupt specifier -> irq_domain translate -> hwirq -> virq / Linux irq -> irq_desc
驱动中常见:
1 2 irq = platform_get_irq(pdev, 0 ); devm_request_irq(dev, irq, handler, flags, name, data);
platform_get_irq() 背后会通过 OF/ACPI 和 irq_domain 得到 Linux irq。
6. irq_desc:每个中断的核心状态 include/linux/irqdesc.h 中 struct irq_desc 是 generic IRQ layer 的核心。
重要字段:
1 2 3 4 5 6 7 8 9 10 irq_data irqchip/domain 底层数据 kstat_irqs 每 CPU 中断统计 handle_irq 高层 flow handler action 驱动注册的 irqaction 链表 status_use_accessors IRQ 状态 depth disable_irq 嵌套深度 lock 保护 desc 状态 threads_oneshot threaded IRQ oneshot 状态 threads_active 正在运行的 IRQ thread 数 wait_for_threads synchronize_irq 等待队列
中断核心分发时会找到对应 irq_desc:
1 2 3 4 generic_handle_irq() -> irq_to_desc() -> generic_handle_irq_desc(desc) -> desc->handle_irq(desc)
desc->handle_irq 不是驱动 ISR,而是 flow handler,例如:
handle_level_irq
handle_edge_irq
handle_fasteoi_irq
handle_simple_irq
handle_percpu_irq
flow handler 根据中断类型负责 mask/ack/eoi/replay,然后再调用驱动 ISR。
7. irqaction:驱动注册的 ISR include/linux/interrupt.h 中:
1 2 3 4 5 6 7 8 9 10 struct irqaction { irq_handler_t handler; void *dev_id; struct irqaction *next ; irq_handler_t thread_fn; struct task_struct *thread ; unsigned int irq; unsigned int flags; const char *name; };
驱动通过以下接口注册:
1 2 3 4 request_irq(irq, handler, flags, name, dev_id); devm_request_irq(dev, irq, handler, flags, name, dev_id); request_threaded_irq(irq, handler, thread_fn, flags, name, dev_id); devm_request_threaded_irq(dev, irq, handler, thread_fn, flags, name, dev_id);
request_irq() 实际只是:
1 request_threaded_irq(irq, handler, NULL, flags, name, dev)
因此普通 IRQ 和 threaded IRQ 共用注册框架。
8. request_irq 注册流程 相关文件:
注册流程:
1 2 3 4 5 6 7 8 request_irq() -> request_threaded_irq() -> irq_to_desc() -> kzalloc(struct irqaction) -> action->handler = handler -> action->thread_fn = thread_fn -> action->flags = irqflags -> __setup_irq()
request_threaded_irq() 关键点:
IRQF_SHARED 必须提供非空 dev_id;
如果 handler == NULL 且 thread_fn != NULL,内核使用默认 primary handler;
默认 primary handler 返回 IRQ_WAKE_THREAD;
__setup_irq() 会把 irqaction 挂到 irq_desc->action;
如果有 thread_fn,会创建 irq/%d-%s 内核线程。
驱动必须保证:
注册中断前硬件状态已经初始化好;
ISR 能识别是否是自己的中断;
level 中断必须清除设备中断源;
shared IRQ 必须使用唯一 dev_id。
9. 硬中断分发流程 以 level IRQ 为例,kernel/irq/chip.c:
1 2 3 4 5 6 7 8 handle_level_irq(desc) -> raw_spin_lock(desc->lock) -> mask_ack_irq(desc) -> 检查是否可运行 -> kstat_incr_irqs_this_cpu(desc) -> handle_irq_event(desc) -> cond_unmask_irq(desc) -> raw_spin_unlock(desc->lock)
以 fasteoi IRQ 为例:
1 2 3 4 5 handle_fasteoi_irq(desc) -> 检查 action/disabled -> kstat_incr_irqs_this_cpu(desc) -> handle_irq_event(desc) -> irq_eoi()
flow handler 的职责是:
根据中断类型执行 mask/ack/eoi;
防止重入和禁用状态下误处理;
更新统计;
调用 handle_irq_event();
结束时恢复中断线状态。
10. ISR / top half 真正调用驱动 ISR 的路径在 kernel/irq/handle.c:
1 2 3 4 5 handle_irq_event() -> handle_irq_event_percpu() -> __handle_irq_event_percpu() -> for_each_action_of_desc(desc, action) -> action->handler(irq, action->dev_id)
驱动 ISR 返回值:
1 2 3 IRQ_NONE 不是我的中断,常见于 shared IRQ IRQ_HANDLED 已处理完成 IRQ_WAKE_THREAD 唤醒 threaded IRQ 下半部
top half 特点:
运行在 hardirq context;
当前 CPU 本地中断通常关闭;
不能睡眠;
不能做慢速 I2C/SPI/MDIO 等可能睡眠操作;
不能持有锁后调用可能睡眠函数;
应尽快确认中断源、清中断、保存必要状态;
把耗时工作推给 bottom half。
典型 ISR:
1 2 3 4 5 6 7 8 9 10 11 12 13 static irqreturn_t my_irq_handler (int irq, void *dev_id) { struct my_dev *d = dev_id; u32 status = readl(d->regs + IRQ_STATUS); if (!status) return IRQ_NONE; writel(status, d->regs + IRQ_CLEAR); tasklet_schedule(&d->tasklet); return IRQ_HANDLED; }
11. threaded IRQ threaded IRQ 是 Linux 推荐的下半部形式之一,尤其适合驱动里需要睡眠的中断处理。
注册:
1 2 3 4 5 ret = devm_request_threaded_irq(dev, irq, top_handler, thread_handler, IRQF_ONESHOT, "mydev" , data);
流程:
1 2 3 4 5 6 7 硬件中断 -> top_handler() -> return IRQ_WAKE_THREAD -> __irq_wake_thread() -> wake_up_process(action->thread) -> irq_thread() -> thread_handler()
kernel/irq/manage.c 中:
setup_irq_thread() 创建 irq/%d-%s 线程;
irq_thread() 是 IRQ thread 主循环;
irq_wait_for_interrupt() 等待 IRQTF_RUNTHREAD;
irq_thread_fn() 调用 action->thread_fn();
irq_finalize_oneshot() 处理 IRQF_ONESHOT 的重新 unmask。
IRQF_ONESHOT 含义:
top half 返回 IRQ_WAKE_THREAD 后,中断线保持 masked;
等 thread_fn 执行完成后再 unmask;
避免 level 中断在下半部未处理完时反复触发。
threaded IRQ 可以睡眠,因此适合:
需要访问 I2C/SPI 慢速外设;
需要 mutex;
需要较长处理流程;
需要与进程上下文 API 交互。
12. bottom half 机制概览 下半部用于把 ISR 中不适合执行的工作延后。
Linux 6.1 中常见 bottom half:
机制
上下文
能否睡眠
适合场景
softirq
softirq context
不能睡眠
网络收发、定时器、RCU
tasklet
softirq context
不能睡眠
老式驱动延后处理
workqueue
process context
可以睡眠
需要慢速操作、复杂处理
threaded IRQ
kernel thread
可以睡眠
中断专属下半部
选择建议:
只需短小、原子、低延迟:softirq/tasklet;
需要睡眠或访问慢速总线:workqueue/threaded IRQ;
新驱动优先考虑 threaded IRQ 或 workqueue;
tasklet 仍存在,但新代码应谨慎使用。
13. softirq 机制 相关文件:
kernel/softirq.c
include/linux/interrupt.h
softirq 是静态注册的高性能下半部机制。
注册:
1 open_softirq(nr, action);
触发:
1 2 raise_softirq(nr); raise_softirq_irqoff(nr);
执行路径:
1 2 3 4 5 6 7 8 9 10 raise_softirq() -> __raise_softirq_irqoff() -> 标记 pending bit irq_exit_rcu() -> 如果不在 interrupt 且有 pending softirq -> invoke_softirq() -> __do_softirq() -> handle_softirqs() -> softirq_vec[nr].action()
如果 softirq 过多或当前不适合立即执行,会唤醒:
softirq.c 中:
1 2 3 DEFINE_PER_CPU(struct task_struct *, ksoftirqd) spawn_ksoftirqd() run_ksoftirqd()
常见 softirq 类型:
HI_SOFTIRQ
TIMER_SOFTIRQ
NET_TX_SOFTIRQ
NET_RX_SOFTIRQ
BLOCK_SOFTIRQ
IRQ_POLL_SOFTIRQ
TASKLET_SOFTIRQ
SCHED_SOFTIRQ
HRTIMER_SOFTIRQ
RCU_SOFTIRQ
14. tasklet 机制 tasklet 基于 softirq 实现。
相关文件:
kernel/softirq.c
include/linux/interrupt.h
初始化:
1 2 tasklet_init(&t, func, data); tasklet_setup(&t, callback);
调度:
1 2 tasklet_schedule(&t); tasklet_hi_schedule(&t);
内部流程:
1 2 3 4 5 6 7 8 9 10 tasklet_schedule() -> test_and_set_bit(TASKLET_STATE_SCHED) -> __tasklet_schedule() -> 加入当前 CPU tasklet_vec -> raise_softirq_irqoff(TASKLET_SOFTIRQ) __do_softirq() -> tasklet_action() -> tasklet_action_common() -> t->func(t->data) 或 t->callback(t)
tasklet 特点:
运行在 softirq context;
不能睡眠;
同一个 tasklet 同一时间不会在多个 CPU 并发运行;
不适合长时间处理;
驱动卸载时需要 tasklet_kill();
新驱动不建议大量使用 tasklet,优先考虑 threaded IRQ/workqueue。
典型使用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 static void my_tasklet_func (unsigned long data) { struct my_dev *d = (struct my_dev *)data; } tasklet_init(&d->tasklet, my_tasklet_func, (unsigned long )d); static irqreturn_t my_irq (int irq, void *dev_id) { struct my_dev *d = dev_id; tasklet_schedule(&d->tasklet); return IRQ_HANDLED; }
15. workqueue 机制 workqueue 是进程上下文下半部,适合需要睡眠的工作。
相关文件:
kernel/workqueue.c
include/linux/workqueue.h
初始化:
1 2 INIT_WORK(&d->work, my_work_func); INIT_DELAYED_WORK(&d->dwork, my_delayed_work_func);
调度:
1 2 3 schedule_work(&d->work); queue_work(system_wq, &d->work); queue_delayed_work(system_wq, &d->dwork, delay);
执行路径:
1 2 3 4 5 6 7 8 queue_work() -> queue_work_on() -> __queue_work() -> insert_work() -> wake_up_worker() -> worker_thread() -> process_one_work() -> work->func(work)
系统 workqueue:
1 2 3 4 5 6 7 system_wq events system_highpri_wq events_highpri system_long_wq events_long system_unbound_wq events_unbound system_freezable_wq events_freezable system_power_efficient_wq events_power_efficient system_freezable_power_efficient_wq
workqueue 特点:
运行在 kworker 进程上下文;
可以睡眠;
可以拿 mutex;
可以访问 I2C/SPI 等可能睡眠接口;
延迟较 tasklet/softirq 高;
需要在 remove/shutdown 中 cancel_work_sync() 或 flush_work()。
典型使用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 static void my_work_func (struct work_struct *work) { struct my_dev *d = container_of(work, struct my_dev, work); } INIT_WORK(&d->work, my_work_func); static irqreturn_t my_irq (int irq, void *dev_id) { struct my_dev *d = dev_id; queue_work(system_wq, &d->work); return IRQ_HANDLED; }
16. top half 与 bottom half 对比 16.1 top half top half 即 ISR:
适合做:
读取中断状态寄存器;
判断是否为本设备中断;
ack/clear 设备中断;
保存少量状态;
唤醒 thread/tasklet/workqueue;
返回 IRQ_HANDLED 或 IRQ_WAKE_THREAD。
不适合做:
睡眠;
大量数据拷贝;
长时间循环;
复杂协议处理;
I2C/SPI 同步访问;
等待锁或等待硬件完成。
16.2 bottom half bottom half 适合做:
数据包处理;
协议解析;
唤醒等待队列;
调度下一次 DMA;
访问慢速总线;
上报 input/net/block 子系统;
较复杂状态机处理。
不同下半部选择:
1 2 3 不能睡眠 + 低延迟:tasklet/softirq 可以睡眠 + 驱动私有处理:workqueue 可以睡眠 + 中断专属处理:threaded IRQ
17. 典型驱动中断设计 17.1 tasklet 模式 1 2 3 4 5 6 7 8 9 ISR: 读状态 清中断 保存状态 tasklet_schedule() return IRQ_HANDLED tasklet: 原子上下文处理后续数据
适合:
17.2 workqueue 模式 1 2 3 4 5 6 7 8 9 10 ISR: 读状态 mask/clear 中断 queue_work() return IRQ_HANDLED work: 慢速处理 访问 I2C/SPI 恢复/打开中断
适合:
需要睡眠;
需要复杂状态机;
延迟要求不是极端严格。
17.3 threaded IRQ 模式 1 2 3 4 5 6 7 8 9 top handler: 判断中断源 清/屏蔽中断 return IRQ_WAKE_THREAD thread_fn: 完成耗时处理 必要时重新使能设备中断 return IRQ_HANDLED
适合:
中断处理天然分成“快速确认 + 慢速处理”;
需要睡眠;
希望一个 IRQ 对应一个内核线程;
level 中断可配合 IRQF_ONESHOT。
18. 中断上下文限制 hardirq 和 softirq/tasklet 中:
不能调用会睡眠的函数;
不能使用普通 mutex;
不能执行可能阻塞的内存分配;
内存分配应使用 GFP_ATOMIC;
持锁时间必须短;
不能直接访问用户空间;
不能做长时间 busy loop。
workqueue/threaded IRQ 中:
可以睡眠;
可以使用 mutex;
可以使用 GFP_KERNEL;
仍需考虑和 remove/suspend 并发;
仍需避免长时间占用 worker。
19. 调试与观察 常用文件:
1 2 3 4 5 /proc/interrupts /proc/irq/<irq>/ /sys/kernel/debug/irq/ /sys/kernel/debug/tracing/events/irq/ /sys/kernel/debug/tracing/events/workqueue/
常用命令:
1 2 3 4 5 cat /proc/interruptscat /proc/softirqsps -e | grep irq ps -e | grep ksoftirqd ps -e | grep kworker
常用 ftrace 事件:
1 2 3 4 5 6 7 8 irq:irq_handler_entry irq:irq_handler_exit irq:softirq_raise irq:softirq_entry irq:softirq_exit workqueue:workqueue_queue_work workqueue:workqueue_execute_start workqueue:workqueue_execute_end
常见问题:
中断风暴
没有正确清设备中断源;
level IRQ 没有让硬件线回到 inactive;
shared IRQ handler 错误返回 IRQ_HANDLED。
ISR 中睡眠
hardirq 中调用 mutex/I2C/SPI 同步接口;
应改为 threaded IRQ 或 workqueue。
remove 后崩溃
未 free_irq();
未 tasklet_kill();
未 cancel_work_sync();
threaded IRQ/workqueue 仍访问已释放私有数据。
延迟过高
ISR 过长;
softirq 被 ksoftirqd 延后;
workqueue 被长任务占用;
IRQ affinity 不合理。
20. 总结 RK3588 Linux 6.1 的中断路径可以总结为:
1 2 3 4 5 6 7 硬件中断 -> ARM64 exception entry -> GICv3 gic_handle_irq() -> irq_domain hwirq 到 virq 映射 -> irq_desc->handle_irq() -> handle_irq_event() -> irqaction->handler()
上半部:
1 top half = irqaction->handler()
职责是快速确认、清中断、保存状态、唤醒下半部。
下半部:
1 2 3 4 5 bottom half = softirq tasklet workqueue threaded IRQ
其中:
tasklet 基于 TASKLET_SOFTIRQ;
workqueue 基于 kworker 线程;
threaded IRQ 基于 irq/%d-%s 内核线程;
irq_exit_rcu() 是 hardirq 返回时触发 softirq 的关键点。
一句话概括:Linux 中断上半部只做必须的快速硬件响应,下半部负责把耗时或可睡眠的工作延后到 softirq/tasklet/workqueue/threaded IRQ 中执行。