前一阵子,入手了一块友善出品的NanoPi NEO 2 Black,上面装了一块eMMC作为rootfs,搭载的是armbian定制的Debian 10系统。最近一次升级后,重启时板子起不来,长亮红灯,初步判断为U-Boot损坏。此文记录一次简单的U-Boot修复。

tl; dr

U-Boot写在启动分区的分区表后,第一个分区前。对于armbian,U-Boot数据以一个deb包的形式发布,此次的全志H5平台的deb包名为linux-u-boot-nanopineo2black-current。根据包内的刷写脚本,可刷回正确的U-Boot。

从一次更新说起

最近一次升级后,重启时板子起不来,长亮红灯,初步判断为U-Boot损坏。

一切的开始,都来源于我每周给NanoPi的日常升级,没错就是这样稀松平常的一句apt upgrade,里面可以包括内核、设备树、U-Boot等等的升级。

这次的升级我隐约中看到一个带uboot的软件包的名字进行了升级。后来重启的时候就翻车了——红灯常亮,不闪绿灯,目测没有识别到启动序列。也就是说,可能我的U-Boot数据写坏了。

尝试使用TF卡系统启动

找到原本负责中转、烧写系统到eMMC的TF卡,上面记得没错的话是有系统的。插上后接线,可以明显看到红灯变亮,绿灯不时闪烁,IP地址可以ping通了,ssh上去可以进入TF卡上的基本系统。

此时可以初步断定,写坏的部分大致在eMMC上,板子基本没有问题。

接下来查看了下分区表,可以看到eMMC上的分区还在,和TF卡一样都是留够了空间的:

分区表情况,eMMC在mmcblk2上

使用chroot进入eMMC上系统

经过fsck,确定文件系统无异常,此时挂载到任意文件夹上:

mount /dev/mmcblk2p1 /mnt/emm/

查看文件正常,进行适当备份后,就开始将内核主要几个目录挂到自己构建的“根分区”上:

for i in /dev /dev/pts /proc /sys /run; do sudo mount -B $i /mnt/emm$i; done

然后即可chroot进入eMMC上的系统进行操作:

chroot /mnt/emm

chroot下的修复过程

查看/etc/hostname文件确认chroot成功后,开始apt update && apt upgrade的修复。

升级软件包

但是后来想到了可能U-Boot不会写入到预期的位置——eMMC,因而加了个心眼,查看一下软件包存在的逻辑。首先查看与U-Boot相关的软件包的内容:

U-Boot相关软件包内容

顺手less了下刷写脚本,可以看到刷新U-Boot image的逻辑:

DIR=/usr/lib/linux-u-boot-current-nanopineo2black_20.05.2_arm64
write_uboot_platform ()
{
    [[ -f $1/sunxi-spl.bin ]] && dd if=$1/sunxi-spl.bin of=$2 bs=8k seek=1 conv=fsync > /dev/null 2>&1;
    [[ -f $1/u-boot.itb ]] && dd if=$1/u-boot.itb of=$2 bs=8k seek=5 conv=fsync > /dev/null 2>&1 || true
}

setup_write_uboot_platform ()
{
    if grep -q "ubootpart" /proc/cmdline; then
        local tmp=$(cat /proc/cmdline);
        tmp="${tmp##*ubootpart=}";
        tmp="${tmp%% *}";
        [[ -n $tmp ]] && local part=$(findfs PARTUUID=$tmp 2>/dev/null);
        [[ -n $part ]] && local dev=$(lsblk -n -o PKNAME $part 2>/dev/null);
        [[ -n $dev ]] && DEVICE="/dev/$dev";
    else
        local tmp=$(cat /proc/cmdline);
        tmp="${tmp##*root=}";
        tmp="${tmp%% *}";
        [[ -n $tmp ]] && local part=$(findfs $tmp 2>/dev/null);
        [[ -n $part ]] && local dev=$(lsblk -n -o PKNAME $part 2>/dev/null);
        [[ -n $dev && $dev == mmcblk* ]] && DEVICE="/dev/$dev";
    fi
}

冷静分析

分析了下,可以得出这次软件包更新时写入的位置不在eMMC上:

  • /proc/cmdline中得到U-Boot启动设备的UUID
    • 根据上述UUID找到分区的路径
  • 因为这次是TF卡启动的
    • 所以U-Boot写入路径在TF卡上
    • 所以本次没有写入到eMMC上

换句话说,现在直接reboot得不到预想中的结果。

手动写入U-Boot数据到eMMC

看了下代码,重点在于前面的write_uboot_platform函数部分。这部分函数包含了U-Boot写入的位置以及源文件路径。此时,直接在bash交互界面贴上这部分内容:

write_uboot_platform ()
{
    [[ -f $1/sunxi-spl.bin ]] && dd if=$1/sunxi-spl.bin of=$2 bs=8k seek=1 conv=fsync > /dev/null 2>&1;
    [[ -f $1/u-boot.itb ]] && dd if=$1/u-boot.itb of=$2 bs=8k seek=5 conv=fsync > /dev/null 2>&1 || true
}

随后手动指定参数,写到eMMC上:

write_uboot_platform /usr/lib/linux-u-boot-current-nanopineo2black_20.05.2_arm64 /dev/mmcblk2

完事以后,保险起见,sync一下刷新buffer,然后退出chroot环境,关机、拔卡、重新上电。随后,小绿灯可以正常亮起,等会系统也正常启动了,修复顺利完成。

后记

后来想了下,升级途中自动重启了一次,这部分可能和自己设置的watchdog部分有关。之后有条件的话,升级之前先停用硬件看门狗,可能会更加稳妥。

alphaxz.