全志 F1C100s 移植 Linux 主线内核,随机死机问题排查与修复
目录
移植步骤
- 克隆主线内核:
git clone --depth 1 -b v7.1 https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
git switch -c v7.1
- 配置内核:
make sunxi_defconfig
make menuconfig
- menuconfig 关键配置项:
| 菜单路径 | 配置项 |
|---|---|
| General setup | (f1c100s) Default hostname |
| System Type → Platform selection | ARMv5 based platforms (ARM926T, XSCALE, PJ1, ...) |
| File systems → Native language support | (utf-8) Default NLS Option |
| Library routines | (4) Size in Mega Bytes |
| Device Drivers → USB Gadget Support → USB Gadget functions configurable through configfs | RNDIS |
| Device Drivers → USB Gadget Support | Disable DMA (always use PIO) |
问题现象
全志 F1C100s 移植主线内核后,系统正常运行几分钟(10 分钟内不定时)后死机,随后触发硬件看门狗复位。死机时没有任何串口打印输出。
该问题在老内核上未出现,老内核功能正常,不会死机。
| 项目 | 详情 |
|---|---|
| 硬件平台 | 全志 F1C100s (suniv) |
| CPU 架构 | ARM926EJ-S (ARMv5) |
| 主线内核 | Linux 7.1 / 7.2 |
| 旧内核 | Linux 5.4 (全志社区维护版) |
这个问题实际上已经被 Linux 社区发现:https://lore.kernel.org/all/20260624221500.1355D1F00A3F@smtp.kernel.org/
排查过程
此问题的特征是:
- 死机前系统看起来正常
- 死机时没有 kernel panic
- 没有 soft lockup、RCU stall 等调试输出
- 串口完全无响应
- 最终由硬件 watchdog 复位
这说明系统不是普通内核崩溃,而是失去了继续调度和输出日志的能力。
开启内核调试选项
开启 kernel hack、soft lockup detector 等调试选项。
但死机时依旧没有任何输出,排除普通 kernel panic 和可检测的软件死锁。说明 CPU 进入了不可恢复的执行停滞状态。
关闭 DMA、其他外设
排除了设备树和外设问题。测试中没有使用串口以外的其他外设,设备树配置也没有发现异常。
随后关闭 DMA 后问题仍然存在,因此 DMA engine 不是主要原因。
系统中也没有启用 CPU idle、CPU 频率调整等复杂功耗管理功能,因此问题不属于常规 cpuidle 或调频异常。
关闭 Dynamic Tick (NO_HZ)
关闭 Dynamic Tick 后,系统恢复稳定。
也就是将内核配置切换为固定周期 tick:
CONFIG_HZ_PERIODIC=y
CONFIG_NO_HZ_IDLE=n
关闭 Dynamic Tick 后系统不再死机,说明问题与 NO_HZ_IDLE 路径直接相关。
关闭 HIGH_RES_TIMERS 后仍然死机,说明问题也不是高精度定时器本身,而是基础 clockevent oneshot 路径。
定位 Timer 驱动
F1C100s 的 timer 在设备树中使用 allwinner,suniv-f1c100s-timer,主线内核会将它匹配到 drivers/clocksource/timer-sun4i.c。因此,虽然平台是 F1C100s/suniv,实际运行的是 sun4i timer 驱动。
该驱动同时支持周期模式和 oneshot 模式。关闭 NO_HZ 后系统稳定,说明周期模式没有问题;打开 NO_HZ 后死机,则问题集中在 oneshot 模式下的 next event 编程逻辑。
关键实现是 sun4i_clkevt_next_event()。它在设置下一次事件时,会先停止 timer,等待同步,然后把 evt - TIMER_SYNC_TICKS 写入硬件 interval 寄存器,最后重新启动 timer。
问题在于,驱动注册 clockevent 时声明的最小 delta 也是 TIMER_SYNC_TICKS:
clockevents_config_and_register(&to.clkevt, timer_of_rate(&to),
TIMER_SYNC_TICKS, 0xffffffff);
这等于告诉 clockevents core:该设备最小可以接受 TIMER_SYNC_TICKS。但驱动真正写硬件时又会减去同样的值。于是当 core 合法传入最小值时,硬件 interval 实际被写成 0。
根因分析
触发条件
timer interval 写入 0 不是一个可以安全依赖的行为。
对递减式硬件 timer 来说,写入 0 可能有几种结果:
- 不产生中断
- 触发硬件未定义行为
- 从 0 下溢到 0xffffffff,导致下一次中断被推迟到很久以后
因此系统表现为 timer 中断不再到来,调度不再推进,喂狗任务无法运行,最终由硬件看门狗复位。
为什么关闭 NO_HZ 后正常
关闭 NO_HZ 后,系统使用固定周期 tick。
周期 tick 模式下,timer 通常被设置为固定的 jiffy 周期,例如 HZ=100 时约为 10 ms。这个值远大于 TIMER_SYNC_TICKS,不会触发 evt - TIMER_SYNC_TICKS 变成 0 的边界。
因此关闭 NO_HZ 后并不是修复了 timer 驱动,而是绕开了 clockevent oneshot 的最小 delta 路径。
社区讨论
社区中已经有人提交了同类修复,补丁标题为 clocksource/drivers/timer-sun4i: Advertise a real minimum delta 。
https://lore.kernel.org/all/20260624220434.4183732-1-felixonmars@archlinux.org/
补丁将 clockevent 注册时的最小 delta 从 TIMER_SYNC_TICKS 提高到 TIMER_SYNC_TICKS + 1。这样 core 即使传入最小值,驱动内部减去同步补偿后,最终写入硬件的 interval 也至少为 1,不会再出现 zero-tick interval。
该补丁已经获得 sunxi 维护者 Jernej Skrabec 的 Acked-by。
修复方案
推荐修复方式是在 drivers/clocksource/timer-sun4i.c 中修改 clockevent 注册的最小 delta:
clockevents_config_and_register(&to.clkevt, timer_of_rate(&to),
TIMER_SYNC_TICKS + 1, 0xffffffff);
如果需要更保守地验证,也可以改为更大的值。
如果暂时不修改源码,可以关闭 NO_HZ:
CONFIG_HZ_PERIODIC=y
CONFIG_NO_HZ_IDLE=n
结论
F1C100s 主线内核随机死机的根因,是 timer-sun4i.c 中 clockevent 最小 delta 声明不正确。
驱动声明最小可接受值为 TIMER_SYNC_TICKS,但实际写硬件时又减去 TIMER_SYNC_TICKS。当主线 clockevents core 在 NO_HZ/oneshot 路径下传入最小 delta 时,硬件 timer interval 被写成 0,导致下一次 timer 中断丢失或被推迟,系统停止调度,最终由硬件看门狗复位。
关闭 NO_HZ 可以规避该问题;修改 min_delta_ticks 为 TIMER_SYNC_TICKS + 1 是针对该边界问题的直接修复。

浙公网安备 33010602011771号