找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

6130

积分

1

好友

769

主题
发表于 昨天 15:46 | 查看: 5| 回复: 0

调试 AX615 长电机型时,我用内核自带的 initrd 启动方式,把根文件系统挂载到内存,替代 SDK 默认的将 rootfs 分区直接挂载物理盘为 squashfs 的做法。后者在 OTA 升级 rootfs 分区时容易触发 squashfs 错误,导致升级失败、系统变砖。这个过程中踩到的问题相当经典,本文先梳理 bootargs 编译进 U-Boot 后却不生效的来龙去脉。

U-Boot启动日志截图,红色标注显示缺少initrd参数

01 问题的起因:Bug 背后

本来以为就是改下 project.mak 里的一个变量赋值——把 KERNEL_BOOTARGS 拼好,编译烧录,收工。

KERNEL_BOOTARGS := "$(OS_MEM) cmmpool=$(CMM_POOL_PARAM) console=ttyS0,115200n8 \
  loglevel=8 earlycon=uart8250,mmio32,0x4880000 \
  root=/dev/ram0 rw rootfstype=$(ROOTFS_TYPE) init=/linuxrc \
  mtdparts=spi3.0:$(FLASH_PARTITIONS) \
  initrd=$(ROOTFS_START_ADDR),$(ROOTFS_PARTITION_SIZE) $(CMMRC_PARAM)"

结果内核一起来,initrd= 整段直接消失。内核自然无法挂载文件系统,直接报错。

Kernel command line: mem=68M ... rootfstype=squashfs  init=/linuxrc mtdparts=... boot_reason=panic

02 先怀疑变量没赋值成功:开始排查

第一反应是:KERNEL_BOOTARGS 这个变量是不是根本没赋上值?加一行调试打印试试:

$(info # KERNEL_BOOTARGS: $(KERNEL_BOOTARGS))

编译输出如下:

# KERNEL_BOOTARGS: "mem=68M ... mtdparts=spi3.0:...8M(rootfs)... initrd=0x43c00000,8M "

变量值好好的,initrd=0x43c00000,8M 明明白白在里头。那就奇了怪了。

03 不对劲的地方:发现问题

把编译打印的内容和实际内核命令行放一起比对,差异立刻浮现:

差异项 编译打印的 KERNEL_BOOTARGS 实际内核命令行
mtdparts 里 rootfs 大小 8M(rootfs) 8192K(rootfs)
末尾参数 boot_reason=panic

格式不同,还凭空冒出来一个新参数。

如果实际命令行就是编译时写进 CONFIG_CMDLINE 的那份,它应该和编译打印一字不差才对。但 8M 变成了 8192K,还多出来 boot_reason=panic——这说明实际跑起来的命令行根本不是编译进去的那份,而是 U-Boot 运行时传进来的。

U-Boot 的 bootargs 环境变量把内核默认命令行整个盖掉了。

04 翻 U-Boot 源码:找源头

顺着这个思路去扒代码,在 axera_boot.cflash_raw_read 函数里找到了 bootargs 的处理逻辑:

int flash_raw_read(const char *part_name, void *dest)
{
    ...
    case STORAGE_TYPE_NOR:
       #if !defined(CONFIG_MTD_SPI_NAND) && defined(CONFIG_SPI_FLASH)
        bootargs = 
       env_get("bootargs");
       printf("========= bootargs:%s\n", bootargs);
        if(NULL == bootargs) {
            bootargs = BOOTARGS_SPINOR;
            printf("========= BOOTARGS_SPINOR:%s\n", BOOTARGS_SPINOR);

#ifdef SUPPPORT_DDR_AUTO_ADJUST
        if (iram_misc_info->chip_type.chiptype == AX615DV201 ||
            iram_misc_info->chip_type.chiptype == AX615QV201){
            bootargs = BOOTARGS_SPINOR_L2;
        }
#endif
            mtdparts = MTDPARTS_SPINOR;
            env_set("bootargs", bootargs);
            env_set("mtdparts", mtdparts);
            env_save();
        } else {
            mtdparts = strstr(bootargs , "mtdparts");
            if (NULL != mtdparts) {
                mtdparts = strdup(mtdparts);
                strtok(mtdparts, " ");
                env_set("mtdparts", mtdparts);
                free(mtdparts);
            } else {
                mtdparts = MTDPARTS_SPINOR;
                env_set("mtdparts", mtdparts);
            }
        }
        mtdids = env_get("mtdids");
        env_set("mtdids", MTDIDS_SPINOR);
        if (NULL == mtdids) {
            env_set("mtdids", MTDIDS_SPINOR);
        }

        read_len = flash_read_from_nor(part_name, dest);
#endif
        break;
    }
    return read_len;
}

逻辑本身不算复杂:

  • env 里没有 bootargs → 使用编译期的 BOOTARGS_SPINOR,写入 env 并持久化。
  • env 里已经有 bootargs → 直接沿用现有值,只从中提取 mtdparts,bootargs 本身一个字都不动。

05 问题出在这里:找到根因

回顾一下整个过程:之前某次启动时,env 里还没有 bootargs,代码用当时的默认值(已经包含更新过的 root=,但还没有 initrd=)写进了 env,saveenv 把它持久化到了 flash 里。

后来我在源码里加上了 initrd=,重新编译烧录——但 env 分区里存的还是旧 bootargs。于是每次启动都走进 else 分支,直接用旧值。新编译的 BOOTARGS_SPINOR 根本没有任何执行机会。

我改的是源码里的默认值,实际跑的是 env 里缓存的旧值。这俩压根不是一个东西。

06 修改方法:清除 env 中的 bootargs 数据

进入 U-Boot 命令行,清掉缓存:

env delete bootargs
saveenv
reset

重启后就会走进 if (NULL == bootargs) 分支,用最新编译的默认值重新初始化 env。

再验证一下:

Kernel command line: mem=68M ... mtdparts=spi3.0:...8M(rootfs)... initrd=0x43c00000,8M  boot_reason=panic

initrd=0x43c00000,8M 回来了。

这个设计其实有它的道理:

虽然坑,但逻辑不是乱写的:

  • 出厂第一次启动:env 是空的,用编译默认值初始化,保证系统能起来。
  • 后续启动:用户可能在 U-Boot 命令行里用 setenv bootargs 自定义过参数,代码不应该用默认值去覆盖用户的修改。

问题在于:开发者改源码和用户改 env,走的是同一条路径。代码区分不了"这个 env 是用户手动改的"和"这个 env 是上次自动缓存的"。

产品开发阶段这个机制反而添乱——你以为改了源码已经生效,实际跑的还是几个版本前的缓存值。

07 踩过这个坑之后的几条经验

  1. 改了 bootargs 相关源码,先清 env 再验证env delete bootargs; saveenv; reset,别直接看启动日志就下结论。
  2. 编译打印和实际命令行对一下:如果格式、参数数量不一致,说明存在运行时覆盖。优先排查 U-Boot env 和设备树/chosen 节点。
  3. CONFIG_CMDLINE 不是唯一来源:U-Boot 的 bootargs 环境变量、设备树 bootargs 属性、内核默认命令行三者有优先级,通常 U-Boot 传入的优先级最高。
  4. env 分区是持久化的:重新烧录 kernel 或 U-Boot 不会自动清掉 env,除非专门擦除了 env 分区或执行了 env default -a

08 文末小结:扩展知识

initrd=0x43c00000,8M 里的 8M 后缀,大部分架构用 memparse 解析是支持的。如果遇到大小识别异常,改成字节数 0x800000 更稳妥。

另外确认一下 0x43c00000 这个地址没有和其他内存区域冲突——我们的 mem=68Mcmmpool0x44400000 开始,0x43c00000~0x44400000 这段是空闲的,没问题。但如果后续调整了内存布局,这个地址必须同步改。

💡 温馨提示

这个坑说大不大,但排查路线挺典型的:从"变量没赋值对"到"运行时覆盖"再到"env 缓存机制",每一步都得靠对比和证据说话。如果你同样在 AX615 上调启动流程,希望这篇记录能帮你少走点弯路。

类似的内核启动与参数传递问题,在云栈社区也有不少开发者讨论,遇到疑难杂症不妨去找找思路。




上一篇:Makefile 从入门到工程实践:Linux C 自动编译与 VPATH 组织
下一篇:Squashfs+initrd 根文件系统内存化改造:AX615 平台踩坑实录与避坑要点
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-9-10 15:14 , Processed in 0.826093 second(s), 40 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

快速回复 返回顶部 返回列表