nbcon 专题Linux v7.3 · 6.12.113

09 · E4 驱动转换(6.13 → 6.18):ARM64 串口第一次真正用上 nbcon

结论:6.12 的框架要有驱动才有收益。6.13 → 6.18 是驱动转换期:i.MX(6.13) 第一个转换;8250(6.14) 合入约两周后被回退;drm_log(6.14) 是只有线程路径的图形日志控制台;pl011(6.15) 对 ARM64 最关键,覆盖 SBSA 服务器、QEMU virt、树莓派;同期 Tegra264 UTC(6.15) 作为新驱动直接写成 nbcon。核心侧有两处修补:6.17 让第一个 nbcon 控制台注册时也立即用线程(否则慢串口会同步重放整个日志、卡住启动),并允许 panic 期间非 panic CPU 用 direct 方式 reacquire(否则没有真 NMI 的平台停不下这些核)。pl011 的作者在 A64FX 上实测:串口竞争下 sysrq / NMI 触发 panic,legacy 控制台丢失部分或全部崩溃信息,nbcon 无丢失。

要解决的问题

# 问题 出处
P1 6.12 时 ARM64 上没有 nbcon 驱动:RT 下所有串口输出挤在 pr/legacy,panic 仍走 legacy 的 trylock 6.12.113 serial_core.h:632 __uart_port_using_nbcon() 对所有驱动为假
P2 legacy 控制台在竞争下 panic 时丢消息 2eb2608618ce:"the legacy console resulted in the loss of some or all crash messages. However, using the NBCON console, no message loss occurred."
P3 第一个 nbcon 控制台注册时同步重放整个日志 cf5543870186:"the entire log is replayed synchronously … It might block the boot for a long time with a slow serial console."
P4 panic 中被打断的打印者永远拿不回所有权,在 reacquire 里死循环;没有真 NMI 的平台停不下它 571c1ea91a73:"platforms that do not support a true NMI to interrupt the quiesced CPU will not be able to shutdown that CPU from within panic(). This then causes problems for such as being unable to load and run a kdump kernel."

方案与提交

版本 提交 内容
6.13 70acca67bdd3 serial: imx: Switch to nbcon console 实现 write_atomic / write_thread / device_lock(说明里仍写作 driver_enter / driver_exit),保留 CONFIG_SERIAL_IMX_LEGACY_CONSOLE 回退
6.14 b63e6f60eab4 → f79b163c4231 8250 转 nbcon(2025-01-07),约两周后(01-22)因 kernel test robot 报告被回退
6.14 f7b42442c4ac drm/log 新的启动日志绘制客户端,只有 write_thread
6.15 2eb2608618ce serial: amba-pl011: Implement nbcon console Toshiyuki Sato(富士通),参照 8250 与 imx;在 ARM64 QEMU 与 A64FX 上验证
6.15 eb07e3a94679 serial: tegra-utc Tegra264 UART Trace Controller,新驱动直接用 nbcon
6.16 66f5f70ce07a serial: sifive RISC-V,略
6.17 cf5543870186 首个 nbcon 控制台注册时也立即建线程,并置 printk_kthreads_running
6.17 571c1ea91a73 nbcon_context_try_acquire(ctxt, is_reacquire):panic 中只放行 reacquire
6.18 d0d9c7235548 等 panic_on_this_cpu() / panic_on_other_cpu() 帮助函数,nbcon 跟进

架构图:之前 / 之后

之前(6.12):ARM64 串口都是 legacy
之前(6.12):ARM64 串口都是 legacy
之后(6.15):pl011 nbcon——线程、原子输出、tty 同步各就其位
之后(6.15):pl011 nbcon——线程、原子输出、tty 同步各就其位

调用栈

之前 · 6.12.113 上的 pl011(RT):

── 普通 printk ──
vprintk_emit() → ft.legacy_offload → defer_console_output()          printk.c:2403
pr/legacy: legacy_kthread_func()                                      printk.c:3619
  console_lock(); __console_flush_and_unlock() → console_flush_all()
    console_emit_next_record() → con->write() = pl011_console_write() amba-pl011.c:2311
      clk_enable(uap->clk)
      uart_port_lock_irqsave(&uap->port, &flags)                      (RT:rt_spin_lock,可睡眠)
        __uart_port_nbcon_acquire() → __uart_port_using_nbcon() = false   serial_core.h:632
      uart_console_write() …… 一次刷完所有积压

── panic ──
console_flush_on_panic() → console_flush_all() → pl011_console_write()
  oops_in_progress → uart_port_trylock_irqsave(),失败也照写               amba-pl011.c:2320-2321

之后 · 6.15 的 pl011(行号为 v6.15):

── 普通 printk(打印线程)──
nbcon_kthread_func()                                                  nbcon.c:1167
  nbcon_emit_one(&wctxt, false)                                       :1087
    pl011_console_device_lock() → __uart_port_lock_irqsave()          amba-pl011.c:2595
    nbcon_context_try_acquire(NORMAL)                                 nbcon.c:571
    nbcon_emit_next_record(wctxt, false)                              :940
      pl011_console_write_thread()                                    amba-pl011.c:2551
        clk_enable()                                                  :2559   ← 7.3 移除(见 E5)
        逐字节:nbcon_enter_unsafe → putchar → nbcon_exit_unsafe
        被抢:while (!nbcon_enter_unsafe()) nbcon_reacquire_nobuf()

── emergency / panic(调用者上下文)──
nbcon_atomic_flush_pending()                                          nbcon.c:1607
  __nbcon_atomic_flush_pending_con()                                  :1477
    nbcon_context_try_acquire(EMERGENCY|PANIC)
    nbcon_emit_next_record(wctxt, true)
      pl011_console_write_atomic()                                    amba-pl011.c:2519
        clk_enable()                                                  :2527   ← RT 上的隐患(见 E5)
        nbcon_enter_unsafe → 整串 → 等 TX 空 → nbcon_exit_unsafe

── tty 写串口 ──
uart_port_lock_irqsave()                                              serial_core.h:713
  __uart_port_nbcon_acquire()                                         :670
    __uart_port_using_nbcon() = true(CON_NBCON 且有 write_atomic)    :636
    while (!nbcon_device_try_acquire(up->cons)) cpu_relax()           nbcon.c:1755
uart_port_unlock_irqrestore() → nbcon_device_release()                nbcon.c:1779

6.17 的 reacquire 修补(v7.3 行号):

nbcon_reacquire_nobuf(wctxt)                                          nbcon.c:938
  while (!nbcon_context_try_acquire(ctxt, true))   // is_reacquire = true
      cpu_relax();
nbcon_context_try_acquire_direct(ctxt, cur, is_reacquire)             :243
  if (panic_on_other_cpu() && (!is_reacquire || cur->unsafe_takeover))
      return -EPERM;        // 只放行「未被 unsafe 强夺」的 reacquire

8250 第一次转换为什么失败

b63e6f60eab4 的设计(write_atomic / write_thread / device_lock、逐字节交接、irq_work 延后 modem control)与 7.3 最终合入的 take 2 基本一致。回退说明只有一句:"kernel test robot has found problems with this commit so revert it for now."。7.3 take 2 的系列说明称,这些问题最终查明是 8250 驱动自身的问题,修好之后才重新提交。take 2 前,主线先合入了 8250 的控制台流控(5e6dfb87b191、39d307074f91,7.2)与 suspend 时忽略流控(302fbbb4fcbd,7.2-rc3)等前置修复。

4+1 增量

视图 E4 带来的变化
场景 ARM64 上 S2 / S3 / S5 第一次真正生效(pl011、i.MX、Tegra264);S6 启动不再同步重放
逻辑 is_reacquire:panic 中的获取规则变细
进程 每个 nbcon 串口有了自己的 pr/ttyAMA0 / pr/ttymxc0,不再挤在 pr/legacy
开发 驱动目录出现 CON_NBCON;驱动转换的样板(8250 / imx / pl011)确立
物理 A64FX 实测验证;无真 NMI 平台的停核问题修复

留下的问题:原子刷出一次持有到底,引起其他 CPU 的 hard lockup;emergency 反复抢线程,把输出撕碎;只有线程能打印的驱动在 panic 时完全没有输出;pl011 的 clk_enable() 在 RT 原子上下文里有睡眠隐患。见 E5。