RV126平台NFS网络启动终极复盘报告

发布于:2025-07-19 ⋅ 阅读:(13) ⋅ 点赞:(0)
1. 初始目标与环境
  • 目标: 将RV1126开发板的启动方式,由从eMMC内部存储挂载根文件系统(rootfs),切换为通过网络挂载位于NFS服务器上的根文件系统。

  • 动机: 提升开发调试效率,实现代码修改后仅需重启即可验证,免除eMMC的反复烧写。

  • 硬件/软件环境:

  • 目标板:RV1126 

  • 引导加载程序:U-Boot 2017.09

  • 内核: Linux 4.19.111 (由Rockchip SDK的./build.sh kernel命令编译)

  • 启动镜像: boot.itb (eMMC默认), zboot.img (编译产物)

  • NFS/TFTP服务器IP: 192.168.1.78

  • 目标板预设静态IP: 192.168.1.105

2. 调试历程与问题演进

问题一bootargs参数冲突,NFS设置被eMMC配置覆盖

  • 操作: 初步尝试通过在U-Boot中设置bootargsbootcmd变量来引导NFS。

  • 现象: 重启后,开发板完全忽略了NFS设置,依然从eMMC启动了原有系统。

  • 关键线索: 系统启动后,在串口终端执行cat /proc/cmdline,查看到内核实际接收到的启动参数

    [    0.000000] Kernel command line: user_debug=31 storagemedia=emmc ... root=PARTUUID=614e0000-0000 rootfstype=ext4 ...
    
  • 分析过程:

    1. 问题定位: 内核启动参数中完全没有我们设置的nfsroot等字段,反而存在明确指向eMMC分区的root=PARTUUID=...。这证明U-Boot环境变量中的bootargs在传递给内核的过程中被覆盖了。

    2. 原理分析: 内核启动参数的来源主要有二:U-Boot环境变量和设备树(FDT)中的/chosen节点。Rockchip平台默认的boot_fit命令会从eMMC加载一个FIT镜像,此镜像内部打包了内核和FDT。如果FDT的/chosen节点内也定义了bootargs属性,它将拥有极高的优先级。

    3. 根源确认: 检查设备树源码 rv1126-alientek.dts 时,找到了问题的直接原因:

  • 解决方案: 修改rv1126-alientek.dts文件,将/chosen节点下的bootargs属性整行删除。之后重新编译设备树(.dtb),并用新的.dtb重新打包FIT镜像,最后将这个“干净”的FIT镜像重新烧录到eMMC。此操作旨在从根源上消除冲突参数。

问题二:遭遇Kernel Panic,深入内核排查驱动初始化时序

现象描述:在完成了2.1节中的设备树修改(删除/chosen节点下的bootargs属性)、重新编译并烧录了新的FIT镜像后,我重启了开发板。系统行为发生了关键变化:

  1. 系统不再从eMMC启动,证明2.1节的修改是有效的。

  2. 在U-Boot加载内核、打印 Starting kernel ... 之后,内核日志开始滚动,但在大约0.8秒后,系统完全崩溃,串口终端被Kernel Panic信息刷屏。

关键线索(日志节选): 我从串口捕获了完整的崩溃日志。最核心的错误信息如下:

[ 0.194338] rk_gmac-dwmac ffc40000.ethernet: no regulator found
[ 0.826993] Kernel panic - not syncing: VFS: Unable to mount root fs on unknown-block(0,255) 
[ 0.837762] CPU: 2 PID: 1 Comm: swapper/0 Not tainted 4.19.111 #8 
[ 0.838305] Hardware name: Generic DT based system 
[ 0.838756] [<b010ff9c>] (unwind_backtrace) from [<b010c298>] (show_stack+0x10/0x14) 
[ 0.839443] [<b010c298>] (show_stack) from [<b091afa4>] (dump_stack+0x90/0xa4) 
[ 0.840083] [<b091afa4>] (dump_stack) from [<b0127318>] (panic+0xfc/0x26c) 
[ 0.840692] [<b0127318>] (panic) from [<b0d01334>] (mount_block_root+0x218/0x2f4) ... 
[ 0.945002] ---[ end Kernel panic - not syncing: VFS: Unable to mount root fs on unknown-block(0,255) ]---
分析与思考过程
  1. 解读Kernel Panic: Kernel panic - not syncing: VFS: Unable to mount root fs on unknown-block(0,255) 这句日志是Linux内核在启动流程中的“绝望呼喊”。VFS (虚拟文件系统) 尝试挂载bootargs中指定的根设备(在我们的NFS场景下,即nfs设备),但最终失败了。error -6 (日志中未直接显示,但该错误码为此场景下的典型值) 代表-ENXIO,即“无此设备或地址”。这证明,在内核需要使用网络来联系NFS服务器时,网络设备尚未就绪。

  2. 定位核心矛盾: 此时,一个关键的矛盾点浮现出来:

    • 事实A: 在从eMMC正常启动后,ifconfig可以看到eth0,并且可以ping通网络。这证明了物理硬件(SoC MAC + PHY芯片)是完好的。

    • 事实B: 我已经通过make menuconfig确认,内核的网络驱动CONFIG_DWMAC_ROCKCHIP是以**内置(built-in, =y)**方式编译的,并非模块(module, =m)。这排除了因无法在早期加载.ko驱动模块而导致NFS启动失败的常见问题。

    • 矛盾: 既然硬件完好、驱动也已内置于内核中,为何在NFS启动的这个特定时刻,网络设备却不可用?

  3. 提出“驱动时序”假设: 这种“最终能用,但早期不能用”的现象,是典型的**内核驱动初始化时序(Driver Initialization Timing)**问题。Linux内核在启动时,会按照一个复杂的依赖和优先级顺序来逐一初始化(probe)各个设备的驱动。NFS启动对网络就绪的时效性要求极为苛刻,它需要在内核启动的最初几百毫秒内就完成网络初始化。我的假设是:网卡驱动dwmac的初始化动作,早于了它所依赖的某个前置资源的初始化动作。

  4. 寻找证据并验证假设: no regulator found 这条日志,就是验证我假设的“铁证”。

    • 原理: 网卡驱动(dwmac)负责控制SoC内部的MAC逻辑,但它需要通过一个外部的PHY芯片来连接物理网线。这个PHY芯片是需要独立供电的。设备树作为硬件的软件描述,理应告诉dwmac驱动,它需要使用的PHY电源(regulator)是哪一个。

    • 日志解读: no regulator found 清晰地表明,dwmac驱动在启动的第194毫秒,确实去向内核的电源管理框架(regulator framework)申请PHY芯片的电源了,但框架回复“找不到”。

    • 深层原因: 这并不是说电源不存在,而是在那个时刻,负责管理这个电源的PMIC驱动(在我们的板子上是rk808-regulator)自己都还没完成初始化,或者说,它还没来得及向内核注册它所管理的全部电源。因此,dwmac的申请必定失败。

解决方案:在设备树中显式声明依赖关系
  1. 问题的根源在于dwmac驱动和regulator驱动之间缺少一个明确的、可被内核驱动模型理解的依赖关系。解决方案必须从设备树入手,建立这个关系。

    • 软件映射 (设备树分析):

      • 我打开了您提供的rv1126-alientek.dtsi文件,在i2c0总线下的rk809: pmic@20节点中,我找到了所有电源输出的定义。

      • 通过比对,硬件上的SW4(在数据手册中对应逻辑功能名DCDC_REG4)在代码中的节点标签(handle)被清晰地定义为vcc3v3_sys

    • 硬件溯源 (原理图分析):

      • 我首先检查了您提供的PDF原理图。在第17页,确认了PHY芯片(RTL8211F-CG)的主3.3V供电网络标号为VCC33_LAN

      • 接着,在第13页的PMIC(RK809)电路部分,我找到了VCC33_LAN的源头——它连接到U2A芯片的SW4引脚的输出网络VCC3V3_SYS

      • 硬件对应关系锁定: PHY Power -> VCC33_LAN -> VCC3V3_SYS -> PMIC SW4

实施修改: 至此,硬件和软件的链条完全打通。然后修改rv1126-alientek.dtsi文件,在&gmac节点中,明确地添加下面这行至关重要的代码:它的作用是在编译层面为内核的驱动加载器设定了一条强制规则:“必须等待名为vcc3v3_sys的regulator设备注册成功之后,才能对gmac设备进行probe(初始化)”。这从根本上解决了驱动初始化的时序竞速问题。

在完成了此项修改后,重新编译并烧录了包含新设备树的FIT镜像后,然后从启动日志中清楚地看到,no regulator found的警告消失了,取而代之的是:

这标志着驱动依赖与时序问题被成功攻克,接着进入下一个阶段.......

问题三:无法判断zboot.img镜像格式,使用bootibootzbootm逐一排除

  • 在完成了第二个问题中对设备树phy-supply的修复并重新烧录固件后,我满怀希望地认为问题已经解决。然而,重启后,Kernel Panic依然如故。所有的静态配置(设备树)和依赖关系(电源)从理论上看都已无懈可击。因此,问题的焦点,决定性地从“配置”转向了U-Boot的“执行”——即bootcmd的实际行为。

    此时我的核心判断是:Rockchip SDK默认的boot_fit命令行为过于复杂且不可控,它似乎总是以某种方式干扰bootargs。为了获得对启动流程的100%控制权,最标准的工程做法就是彻底绕开它,转而使用最基础、最通用的TFTP加载方式,手动加载内核和设备树,再用标准命令启动。

  • 引入的新问题:未知的zboot.img镜像格式 这个思路立刻引入了一个新的关键变量:由SDK编译生成的内核镜像zboot.img,究竟是什么格式?

    • 是纯粹的、无头部的zImage? -> 应该用 bootz 命令。

    • 是带有U-Boot旧版头部的uImage? -> 应该用 bootm 命令。

    • 是针对新架构的Image? -> 应该用 booti 命令。

    启动命令的选择,完全取决于镜像的格式。于是,我开始了一系列严谨的、逐一排除的实验。

  • 实验A:使用 booti 命令的尝试

    • 原理与动机: booti是针对ARMv7/v8架构下、原始内核镜像(如zImage/Image)的较新启动命令。其语法 booti <kernel_addr> - <fdt_addr> 非常清晰,是我首先想到的尝试。

    • 操作: 我在U-Boot中构造了如下命令:

      setenv bootcmd_tftp '...; tftpboot ${kernel_addr_r} zboot.img; tftpboot ${fdt_addr_r} rv1126-alientek.dtb; booti ${kernel_addr_r} - ${fdt_addr_r}'
      setenv bootcmd 'run bootcmd_tftp'
      saveenv
      
    • 现象与关键线索: 重启后,U-Boot在执行该命令时,立即打印出错误,启动中止。

      Unknown command 'booti' - try 'help'
      
    • 结论: 这个结果直截了当。它表明我当前使用的U-Boot版本在编译时,并未包含booti命令的支持。这证实了该U-Boot经过了功能裁剪,此路被完全堵死。

  • 实验B:使用 bootz 命令的尝试与“意外的劫持”

    • 原理与动机: booti失败后,我转向了更经典的、同样用于启动zImagebootz命令。同时,为了彻底搞清楚zboot.img的身份,我计划在TFTP下载后,手动中断脚本,并使用iminfo命令来一探究竟。

    • 操作: 我在串口终端手动执行了tftpboot 0x0a200000 zboot.img,准备下一步执行iminfo

    • 现象与关键线索: 出现了完全出乎意料的日志。U-Boot在下载完文件后,脚本并未停止等待我输入下一条命令,而是立刻开始尝试启动,并打印出截然不同的日志:

      Loading: ####################################################### ... done
      Bytes transferred = 7593984 (73e000 hex)
      ## TFTP bootm zboot.img at 0x2008000 size 0x73e000
      BOOTM: transferring to board FIT
      ## Booting FIT Image Sysmem Error: "KERNEL" ... alloc is overlap with existence "FIT_USER" ...
      

      我本来想等他下载完之后,在uboot命令行执行iminfo去查看一下这个zboot.img到底是什么类型的内核镜像,可是我压根没机会去运行iminfo命令,他下载完成之后直接去starting kernel了,由此可以获得结论:

      1. zboot.img的身份被揭示:日志中的BOOTM: transferring to board FIT是无可辩驳的铁证,证明了zboot.img就是一个FIT镜像,而不是我之前假设的原始zImage

      2. U-Boot的“个性”被发现:更重要的是,这个U-Boot的tftpboot命令被赋予了“智能”,它在下载完可启动镜像后,会擅自、立即、自动地调用bootm命令去启动它。这个“自动启动”特性,使得我们任何多步骤的脚本在tftpboot这一步都会被“劫持”,后续指令根本没有机会执行。

  • 实验C:使用 bootm 命令的尝试

    • 原理与动机: 既然知道了镜像是FIT格式,且bootm是对应的命令,我便尝试明确地使用bootm,并为它提供一个独立的、我们修改过的设备树,期望能以此覆盖FIT镜像内部的FDT。

    • 操作: 构造了bootm <kernel_addr> - <fdt_addr>的命令。

    • 现象与关键线索: U-Boot再次报错并挂起:

      FDT and ATAGS support not compiled in - hanging
      
    • 结论: 这证实了该U-Boot被裁剪得非常彻底。它的bootm命令也被剥离了接收外部独立FDT的功能,它只能处理自包含的、单一的FIT镜像。

  • 本阶段的解决方案与思路转变

    • 分析总结: 经过这三个维度的逐一排除与失败,情况已经非常明朗。这个U-Boot是一个高度特化的“专才”,而不是一个功能全面的“通才”。它被编译的目的,就是为了以一种特定的方式(boot_fit)来引导一个特定的镜像格式(FIT)。任何试图使用通用的、将内核和设备树作为独立文件加载的方案(无论是用booti, bootz, 还是功能不全的bootm),对于这个平台来说,都是行不通的。

    • 解决方案: 此时,唯一的、合乎逻辑的解决方案是:必须放弃所有绕开或替换boot_fit的尝试,回归到平台原生的boot_fit启动路径上来。

    • 思路转变: 我们的调试重心,必须从“更换U-Boot启动命令”,重新转回到“在不改变boot_fit命令的前提下,如何精确地控制其运行环境”上来。这个思路的根本性转变,为我们解决后续的变量展开和时序问题,奠定了正确的方向。我们必须停止与系统“对抗”,而是学着去“利用”它的特性。

    • 下一步: 这直接引出了我们的下一个挑战——问题四:既然必须用boot_fit,那我们如何解决bootargs变量在boot_fit执行过程中无法正确展开的问题?

问题四:U-Boot变量未展开,内核收到“无法理解的”错误参数

  • 现象描述: 在经历了命令与镜像格式的迷局后,我们被迫回归到唯一可行的boot_fit方案,并创造性地使用ping命令来代替sleep解决网络时序问题。然而,在满怀期待地重启后,系统依然Kernel Panic

  • 关键线索: 这一次,我们吸取了教训,第一时间检查内核收到的Kernel command line,终于发现了那个隐藏最深的错误:

    [    0.000000] Kernel command line: user_debug=31 root=/dev/nfs nfsroot=${serverip}:${nfs_dir},v3,tcp ip=192.168.1.105...
    

    ip=部分是我们硬编码的,所以是正确的。但nfsroot=部分,内核收到的竟然是${serverip}${nfs_dir}这两个未经展开的变量名

  • 分析过程:

    1. 问题定位: U-Boot环境变量展开失败。

    2. 原理分析: ping ${serverip}命令执行时,serverip被正确展开了。但是,在我们构造的bootcmd_ultimate_nfs脚本中,执行setenv bootargs ${bootargs_nfs}时,U-Boot的脚本解释器只是简单地将bootargs_nfs变量的字面内容(一个包含${...}的字符串)赋给了bootargs,而没有进行二次展开

    3. 根源确认: 内核不具备解析U-Boot变量语法的能力。当它看到nfsroot=${serverip}:${nfs_dir}时,视其为无效路径,从而导致NFS客户端无法启动,最终引发Kernel Panic

  • 解决方案: 为了彻底规避U-Boot脚本解释器在嵌套和多级解析上的不确定性,我们必须采用最直接、最无歧义的方法:硬编码(Hardcoding)。即在setenv bootargs命令中,直接写入最终的、不包含任何${...}变量的、纯净的字符串。最终的bootargs为:bootargs_nfs=root=/dev/nfs nfsroot=192.168.1.78:/home/alientek/workspace/rv1126/src_code/sdk_linux/buildroot/output/alientek_rv1126/target,v3,tcp ip=192.168.1.105:192.168.1.78:192.168.1.1:255.255.255.0::eth0:off rw console=ttyFIQ0,1500000

问题五:胜利的假象?——挂载成功但系统“瘫痪”,空的/proc/sys

  • 现象描述: 在解决了U-Boot变量展开、FIT镜像配置覆盖、内核驱动时序等一系列底层问题后,系统终于不再Kernel Panic。内核启动日志滚动到最后,没有出现崩溃信息,串口输出停止。这看起来像是成功了。然而,系统却处于一种“瘫痪”状态:

    1. 尝试通过ssh root@192.168.1.105登录,连接被拒绝(Connection refused)或超时(timeout),证明SSH服务没有启动。

    2. Telnet服务同样无法连接。

    3. 串口终端在内核启动日志结束后,没有任何反应,没有打印出login:提示符。

    4. 在一个偶然的、能得到一个极简陋shell的启动版本中,执行ls /procls /sys,发现这两个目录是空的。进而导致pstopfreemount等所有依赖于这两个伪文件系统的核心命令全部失效。

  • 分析与思考过程:

    1. 定位问题边界: 内核没有崩溃,证明了从U-Boot到内核加载,再到NFS挂载操作本身是成功的。问题已经从我们之前一直纠结的内核空间(Kernel Space),决定性地转移到了用户空间(User Space)。也就是说,内核已经把控制权交给了NFS服务器上的/sbin/init进程,但这个init进程没能把系统完全地、正确地“拉起来”。

    2. 剖析根文件系统源: 我回顾了NFS服务器的配置,共享的目录是.../buildroot/output/alientek_rv1126/target。这个目录是Buildroot在执行完所有软件包的编译和安装后,生成的一个**“暂存区”或“安装目录”。它包含了根文件系统的所有文件和目录结构蓝图,但它不是一个最终的、可启动的根文件系统**。

    3. 阐述原理——“暂存区”与“成品”的区别: 一个能够让Linux系统正常运行的根文件系统,除了包含程序和库文件外,还必须满足几个关键条件,而这些恰恰是target目录所缺失的:

      • 正确的文件权限: 尤其是/bin, /sbin, /etc下的可执行文件和脚本,必须有正确的执行权限。Buildroot的target目录在打包成最终镜像前,不保证所有权限都已正确设置。

      • 关键的设备节点: /dev目录下必须存在console, null, tty, zero等一系列基础设备节点。没有这些节点,init进程甚至无法正确地将日志输出到控制台,SSH等服务也无法创建伪终端。Buildroot的target目录下的/dev通常是空的,这些节点是在打包成.ext4.tar镜像的过程中,由mdevsystemd的机制动态或静态创建的。

      • 完整的启动脚本和服务配置: /etc/init.d/systemd服务必须被正确配置,能够执行挂载/proc/sys/dev/pts等伪文件系统的任务,并启动如dropbear(SSH服务)或telnetd等关键网络服务。target目录中的配置可能不完整或不适用。

    4. 最终假设: 内核成功挂载了target目录,但由于该目录本质上是一个“半成品”,它缺少正确的设备节点和文件权限,导致/sbin/init进程在启动初期就因无法打开基础设备(如/dev/console)而失败或挂起。因此,它未能执行后续的启动脚本来挂载/proc/sys,也未能启动dropbear(SSH服务),导致了我们观察到的系统“瘫痪”现象。

  • 解决方案与验证: 解决方案是停止使用临时的target目录,转而使用Buildroot生成的最终的、完整的根文件系统镜像

    1. 创建正确的根文件系统挂载点: 我在NFS服务器上,找到了由Buildroot生成的最终产物rootfs.ext4镜像文件。

      # 在NFS服务器上执行以下操作
      
      # 1. 创建一个新的目录作为我们真正的NFS根目录
      mkdir /home/alientek/workspace/rv1126/src_code/sdk_linux/rv1126_nfs_rootfs
      
      # 2. 使用loop设备,将.ext4镜像挂载到这个新目录
      #    这步操作相当于把一个硬盘分区“解压”到了一个文件夹里
      sudo mount -o loop /path/to/your/buildroot/output/images/rootfs.ext4 /home/alientek/workspace/rv1126/src_code/sdk_linux/rv1126_nfs_rootfs
      
    2. 配置NFS服务器: 我修改了/etc/exports文件,将共享目录指向这个新的、正确的挂载点,并重启了NFS服务。

      # /etc/exports 文件中的修改
      # 旧: /home/alientek/.../target *(...)
      # 新:
      /home/alientek/workspace/rv1126/src_code/sdk_linux/rv1126_nfs_rootfs *(rw,sync,no_root_squash,no_subtree_check)
      
    3. 修改U-Boot启动参数: 最后,我回到U-Boot命令行,修改了bootcmd中的硬编码路径,使其指向新的NFS共享目录。

      setenv bootargs 'root=/dev/nfs nfsroot=192.168.1.78:/home/alientek/workspace/rv1126/src_code/sdk_linux/rv1126_nfs_rootfs,v3,udp ip=192.168.1.105:192.168.1.78:192.168.1.1:255.255.255.0::eth0:off rw console=ttyFIQ0,1500000 storagemedia=emmc androidboot.storagemedia=emmc androidboot.mode=normal'
      saveenv
      
    4. 最终验证: 重启开发板后,内核成功挂载了新的、从rootfs.ext4镜像中来的完整根文件系统。init进程正常执行,/proc/sys被自动挂载,SSH服务(dropbear)也成功启动。最终,我通过ssh root@192.168.1.105成功登录,所有命令工作正常。问题彻底解决。

4. 总结与反思

此次调试历程,从一个看似简单的目标开始,最终演变为一场涉及U-Boot、设备树、内核和用户空间的全链路排查。它雄辩地证明了,任何一个环节的微小疏忽,都可能导致整个系统的启动失败。

  • eMMC与NFS启动的差异不仅仅在于bootargs,更在于对根文件系统完整性和正确性的要求。

  • Buildroot的target目录是一个常见的陷阱,它只可用于检查文件内容,而不能作为可启动的根文件系统直接使用。

  • 一个健壮的调试流程,需要我们对从Bootloader到用户空间的整个启动链有清晰的认识,并能通过日志,将问题精确定位到其所属的层面。


网站公告

今日签到

点亮在社区的每一天
去签到