ARM板移植Linux系统启动(六)烧写系统到emmc

能够从Uart把系统跑起来,基本上已经完成了移植工作的关键。但此时自动的引导流程还没有建立好,要想让系统能正常启动,必须将数据烧写到存储设备里去。烧写的原理对于所有的板子都是一样的,只是烧写数据的来源和目标有区别,掌握原理以后就可以自己灵活修改,不用拿着从ftp烧写的脚本对着USB烧写的需求一筹莫展。:-)

你看到的是非授权版本!爬虫凶猛,请尊重知识产权!

转载请注明出处:http://conanwhf.github.io/2017/06/14/bootup-6-flasher/

访问原文「ARM板移植Linux系统启动(六)烧写系统到emmc」获取最佳阅读体验并参与讨论

BeagleBoneBlack的烧写

BeagleBoneBlack支持外部TF卡(emmc)烧写系统。它的流程是启动时长按某个button进入TF启动模式,然后修改init命令行参数让系统重新启动后自动运行烧写脚本。这个过程其实分几个步骤:

  1. 按住button调整启动模式。BeagleBoneBlack有两种顺序不同的启动模式:{SPI0,MMC0,USB0,UART0}和{MMC1,MMC0,UART0,USB0},按下时进入第二种模式。TF卡是外接在MMC1上,即意味着按下button时优先启动TF卡上的系统。注意这里的MMC0和MMC1是硬件意义上的,与进入系统后的设备结点mmc0,mmc1没有对应关系,可以不同。
  2. BootROM在TF卡上寻找到uboot数据,进入uboot;然后uboot引导卡上的kernel,进入系统。
  3. 如果此时的init参数已经修改,则自动运行init指定的脚本,进行烧写系统的工作。
  4. 烧写完毕,系统自动断电,等待重新上电启动。

从这个过程我们可以推理出几件事:

  • 因为引导系统的uboot也是来自于外部TF卡,这种烧写方法可以用在完全被破坏的无法启动的板子上,例如板载emmc被彻底擦除干净的设备。这就意味着你可以随意折腾你的板载系统,不用害怕,TF卡烧写是一种有效的救援措施
  • 官方的烧写教程是让我们enable uEnv.txt中的一行代码cmdline=init=/opt/scripts/tools/eMMC/init-eMMC-flasher-v3.sh,这个flasher-v3.sh就是官方的烧写脚本。进入系统后手动运行效果也是一样的
  • 从整个引导的过程来看,MMC0和MMC1的启动没有区别,所以实际上我们可以制作自己的TF卡系统而不是只局限于烧录,便于调试和使用(像RasspberryPi那样,换一张卡换个系统,甚至可以跑Windows),并且emmc是可读写的,修改的任何数据都会保留,不会重启消失
  • 我们同样可以修改flasher-v3.sh,来完成自己的需求
    关于怎么玩BeagleBoneBlack,不是这篇文章的讨论重点,我只是为了弄清楚BeagleBoneBlack的烧写机制以为己用。TF卡烧写很好用,可惜我的板子没有TF卡。:(

烧写前的准备

  1. 编译kernel, uboot,获得zImage, MLO, u-boot.img等binary
  2. 准备一个根文件系统,通常Linux发行版会提供一个小型的文件系统,将Kernel镜像(一般放在/boot下),以及编译出来的驱动等等更新,打包并压缩。
  3. 编写合适的烧写脚本,等下会详细介绍。
  4. 确定你能进入console,并且在console里能access上面所提到的文件和脚本。
  5. 将准备好的文件拷贝进某个存储介质,例如USB,或者放在服务器端等待tftp获取。

烧写流程

flasher-v3.sh整个脚本很长,用的各种判断和变量也非常多,刚开始读难免眼花缭乱。我基于这个脚本,根据自己的需要,重新写了一份。文件系统烧写的原理都是一样:将存放源数据的设备挂载成文件夹A,将烧写的目标设备挂载成文件夹B,然后读取A中的数据解压,将文件系统写入B中。而对于bootloader的情况就比较复杂,他们的数据写入通常是不基于文件系统的,而写入的位置必须跟BootROM相对应,需要阅读厂商的文档。
我做的项目不是开源,所以无法发布完整的烧写脚本,读者可以根据大致流程自行编写。

变量定义

source="/dev/sda1"
destination="/dev/mmcblk1"
src="/tmp/usb/"
dst="/tmp/dst/"
spl_file="${src}/MLO"
uboot_file="${src}/u-boot.img"
rootfs_file="${src}/rootfs.tar.gz"
boot_label="TEST"
boot_fstype=ext3

这里定义几个全局变量方便使用,分别是数据源和目标设备的设备结点和挂载点,以及一些文件名的定义。读者可以根据需要自行修改,例如mmcblk1换成sdc1,或者从TF烧写而不是USB的时候将sda1换成mmcblk0p1等等。

烧写bootloader(uboot)

bootloader的烧写包括两个文件:MLO和u-boot.img,烧写是直接使用dd写入数据,参数来自TI官方release。

# 烧写之前先擦除整块emmc
echo "Erasing: ${destination} any partion information"
dd if=/dev/zero of=${destination} bs=1M count=20
sync

# 烧写SPL和uboot
spl_opt="count=1 seek=1 conv=notrunc bs=128k"
uboot_opt="count=2 seek=1 conv=notrunc bs=384k"

echo "dd if=${spl_file} of=${destination} ${spl_opt}"
dd if=${spl_file} of=${destination} ${spl_opt}

echo "dd if=${uboot_file} of=${destination} ${uboot_opt}"
dd if=${uboot_file} of=${destination} ${uboot_opt}

对烧写的目标设备分区

过去,kernel的镜像文件的数据是不基于文件系统的,也就是并非以一个文件的形式存放在某个目录里,因为那时的uboot并没有文件系统的支持,只能从固定的块、页、扇区读取数据。后来特别是EFI被广泛运用后,将关键的文件系统相关数据写在一个单独的Fat32分区中成了一种普遍的方案,通常会将它们只读挂载在/boot下,系统数据和各种库文件是分开的,日常使用更加安全。而现在,uboot支持了更多的文件系统,也就使得我们可以将所有数据都放在一起,怎么设计这些分区,也就更加随意和灵活。TI所用的脚本中,就有了两种选择:fat+ext4或者整个emmc只有一个ext4分区。在这块板子上我选择了后面的方案,方便后期的开发和调试,烧写方案也比较简洁。
烧写的工具使用的是sfdisk,请确保你的最小系统中包含这个工具。

# 参数变量设置
sfdisk_options="--force --Linux --in-order --unit M"
sfdisk_boot_startmb=1
sfdisk_fstype="L"

# 使用sfdisk分区
test_sfdisk=$(LC_ALL=C sfdisk --help | grep -m 1 -e "--in-order" || true)
if [ "x${test_sfdisk}" = "x" ] ; then
    echo "sfdisk: [2.26.x or greater]"
    if [ "x${bootrom_gpt}" = "xenable" ] ; then
        sfdisk_options="--force --label gpt"
    else
        sfdisk_options="--force"
    fi
    sfdisk_boot_startmb="${sfdisk_boot_startmb}M"
fi
echo "sfdisk: [$(LC_ALL=C sfdisk --version)]"
echo "sfdisk: [sfdisk ${sfdisk_options} ${destination}]"
echo "sfdisk: [${sfdisk_boot_startmb},${sfdisk_boot_endmb},${sfdisk_fstype},*]"

LC_ALL=C sfdisk ${sfdisk_options} "${destination}" <<-__EOF__
    ${sfdisk_boot_startmb},,${sfdisk_fstype},*
__EOF__

分区完毕,将分区格式化为ext3,然后就可以往里面写数据了。

echo "mkfs.${boot_fstype} ${destination}p1 -L ${boot_label}"
mkfs.${boot_fstype} ${destination}p1 -L ${boot_label}
sync

烧写文件系统

文件系统的烧写实际上就是数据拷贝的过程。在TI的TF卡烧写方式中,是将当前系统(也就是TF卡上的系统)的所有文件都拷贝到目标文件夹,再进行一定的动态支持处理,我这里是直接将准备好的文件系统包解压到对应的地方。首先挂载分区:

#mount emmc partition
mkdir -p $dst
mount ${destination}p1 $dst -o async,noatime
is_mounted=$(grep ${destination}p /proc/mounts | awk '{print $2}')
if grep -q ${destination}p /proc/mounts; then
        echo "Found mounted partition(s) on " ${destination}": " $is_mounted
else
        echo "No eMMC partition found. exit."
        exit
fi

然后将数据解压,并做一点点小的处理,确保login系统的正常运行:

cd $dst
tar xzvf $rootfs_file
cd - 

if [ -d $dst/etc/ssh/ ] ; then
    #ssh keys will now get regenerated on the next bootup
    touch $dst/etc/ssh/ssh.regenerate
    sync
fi

最后将挂载点umount,完成全部烧写工作:

umount $dst || umount -l $dst
dd if=${destination} of=/dev/null count=100000
sync
echo "Syncing: ${destination} complete"

至此,全部烧写完毕,板子应该能顺利地跑起来了,开机试试吧!:-)