rk3588/kernel-6.1 中断流程与上下半部机制分析

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.hstruct 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 注册流程

相关文件:

  • kernel/irq/manage.c

注册流程:

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 == NULLthread_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 过多或当前不适合立即执行,会唤醒:

1
ksoftirqd/N

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;
/* 处理 ISR 保存的状态,不能睡眠 */
}

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:

1
irqaction->handler()

适合做:

  • 读取中断状态寄存器;
  • 判断是否为本设备中断;
  • ack/clear 设备中断;
  • 保存少量状态;
  • 唤醒 thread/tasklet/workqueue;
  • 返回 IRQ_HANDLEDIRQ_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/interrupts
cat /proc/softirqs
ps -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

常见问题:

  1. 中断风暴

    • 没有正确清设备中断源;
    • level IRQ 没有让硬件线回到 inactive;
    • shared IRQ handler 错误返回 IRQ_HANDLED
  2. ISR 中睡眠

    • hardirq 中调用 mutex/I2C/SPI 同步接口;
    • 应改为 threaded IRQ 或 workqueue。
  3. remove 后崩溃

    • free_irq()
    • tasklet_kill()
    • cancel_work_sync()
    • threaded IRQ/workqueue 仍访问已释放私有数据。
  4. 延迟过高

    • 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 中执行。

文章互动

阅读 --

留言

0 条留言

正在加载留言…