如何在pinephone上实现NuttX与Linux双开
一、前言目前在在pinephone上运行nuttx的方法是先将JumpDriver烧录到MicroSD上,再将nuttx的目标文件压缩成Image.gz并替换SD卡中JumpDriver的Image.gz(编译、压缩、替换均在PC机上完成),然后将MicroSD插入pinephone并开机,由于pinephone上电后默认先从SD卡引导,若没有SD卡或SD卡中没有Uboot才从eMMC中引导,因此pinephone就可以成功运行SD卡中的nuttx。
然而这有一个很大的不便之处,那就是每次我们运行nuttx时我们需要将pinephone的后盖打开—>抠出电池—>插入SD卡—>装上电池—>开机。而切回Linux时我们又再次需要将pinephone的后盖打开—>抠出电池—>拔出SD卡—>装上电池—>开机。若是运行稳定完善的nuttx也就罢了,若是调试nuttx这就有点不可接受了。
本文就是要解决该问题,实现在不开后盖、不拔SD卡的情况下在pinephone上实现nuttx的丝滑切换。
二、NuttX与Linux双开
首先我们先对boot.scr进行简要介绍。全志平台的u-boot在启动的时候会在第一个分区(FAT/extX格式)寻找/boot.scr或者/boot/boot.scr文件,boot.scr中可以包含用于载入script.bin,kernel,initrd(可选)以及设置内核启动参数的uboot命令。(相当于uboot的bootcmd启动命令)。
因此,初步的想法是我们可以设法修改boot.scr来实现在不同的情况下加载不同的操作系统镜像。然而这儿有一个巨大的问题就是如何让boot.scr知道我们想加载哪个操作系统呢?立即想到的就是通过按键来选择。开机时pinephone只有三个按键可供我们使用:音量+(volume up)、音量-(volume down)、开关机键(power),那我们就选择在开机时同时按“音量+”键和“开关机”键来启动NuttX,而只按“开关机”键正常启动Linux。但是很遗憾,在boot.scr中我们无法检测到按键,因为pinephone的Uboot并未采集按键状态并向boot.scr提供接口,因此只修改boot.scr是不行的。
我们还需要对Uboot的的源代码动刀子,当然只是Uboot中与pinephone相关的代码。关于这部分代码,此处不做详解,仅把补丁作为附件放在后面,该补丁主要实现采集音量开关的状态并作为环境变量传递给boot.scr。怎样使用这个补丁呢:立刻能想到的方法有两种:
第一种:将在Uboot上应用该补丁,重新编译Uboot,并将编译出来的u-boot-sunxi-with-spl.bin烧写到SD卡中JumpDriver中原来的Uboot的位置;
第二种:在Jumpdriver中执行该补丁,然后编译打包整个Jumpdriver一起烧写进SD卡。
第一中方法理论上可行,但实际上我折腾了很久也没折腾出来,而方法二最终证明有效,因此这里只给出方法二的详细使用说明。
首先我们下载Jumpdriver的工程,并将volumecontrol.patch放在src目录中,但此时的工程中还不包含Uboot(Uboot是在编译Jumpdriver时作为依赖模块重新下载的),无法执行补丁。为此,我们先执行一次编译make -j8 pine64-pinephone.img.xz,编译完成后我们执行一次make clean清除编译的结果,然后在src目录中执行patch -p0 < volumecontrol.patch然后执行make -j8 pine64-pinephone.img.xz重新编译Jumpdriver。这里有意漏掉了对src/pine64-pinephone.txt的处理,留到后面对这部分专门说明。
针对以上步骤我们可以用另外一种方式实现,那就是修改Makefile,本人以将修改后的Jumpdriver作为一个新的分支上传至github,大家可以直接下载使用,地址为:https://github.com/zouboan/Jumpdrive/tree/cast
接下来我们就来处理boot.scr,我们的思路是:开机时若检测到“音量+”键则执行Jumpdriver的boot.scr,否则执行Linux的boot.scr。为了便于理解boot.scr我们首先对pinephone的Uboot和Linux中的几个变量进行说明:MicroSD卡是mmc0,eMMC是mmc1,mmc2是什么呢?暂时不知道;/dev/mmcblk0p1是MicrSD卡的boot分区,/dev/mmcblk0p2是MicrSD卡的root分区;/dev/mmcblk2p1是eMMC的boot分区,/dev/mmcblk2p2是eMMC卡的root分区。
1、PostmarketOS与NuttX双开
现在我们已经有Jumpdriver的boot.scr内容(来自/src/pine64-pinephone.txt)内容如下:
setenv kernel_addr_z 0x44080000
setenv bootargs loglevel=0 silent console=tty0 vt.global_cursor_default=0
gpio set 114
if load ${devtype} ${devnum}:${distro_bootpart} ${kernel_addr_z} /Image.gz; then
unzip ${kernel_addr_z} ${kernel_addr_r}
if load ${devtype} ${devnum}:${distro_bootpart} ${fdt_addr_r} /sun50i-a64-pinephone-1.2.dtb; then
if load ${devtype} ${devnum}:${distro_bootpart} ${ramdisk_addr_r} /initramfs.gz; then
booti ${kernel_addr_r} ${ramdisk_addr_r}:${filesize} ${fdt_addr_r};
else
booti ${kernel_addr_r} - ${fdt_addr_r};
fi;
fi;
fi 我们需要再获取PostmarketOS的boot.scr内容,它在eMMC的boot分区中,如何获取呢?这里我们利用Jumpdriver,它可以将eMMC的boot分区和root分区作为普通外设暴露出来,连上USB线后我们在PC机上可以像操作U盘一样操作它们。在PC端显示的PostmarketOS的boot分区视图如下:
我们用cat boot.scr来获取boot.scr的内容:
详细代码如下:
gpio set 98
gpio set 114
if test ${mmc_bootdev} -eq 0 ; then
echo "Booting from SD";
setenv bootdev 0;
else
echo "Booting from eMMC";
setenv bootdev 2;
fi;
setenv bootargs init=/init.sh rw console=tty0 console=ttyS0,115200 earlycon=uart,mmio32,0x01c28000 panic=10 consoleblank=0 loglevel=1 PMOS_NO_OUTPUT_REDIRECT PMOS_FORCE_PARTITION_RESIZE pmos_boot=/dev/mmcblk${bootdev}p1 pmos_root=/dev/mmcblk${bootdev}p2 lima.sched_timeout_ms=2000
printenv
echo Loading DTB
load mmc ${mmc_bootdev}:1 ${fdt_addr_r} ${fdtfile}
echo Loading Initramfs
load mmc ${mmc_bootdev}:1 ${ramdisk_addr_r} initramfs
setenv ramdisk_size ${filesize}
echo Loading Kernel
load mmc ${mmc_bootdev}:1 ${kernel_addr_r} vmlinuz
gpio set 115
echo Resizing FDT
fdt addr ${fdt_addr_r}
fdt resize
echo Adding FTD RAM clock
fdt mknode / memory
fdt set /memory ram_freq ${ram_freq}
fdt list /memory
echo Loading user script
setenv user_scriptaddr 0x50700000
load mmc ${mmc_bootdev}:1 ${user_scriptaddr} user.scr
if test $? -eq 0; then source ${user_scriptaddr}; else echo No user script found; fi
echo Booting kernel
gpio set 116
gpio clear 98
booti ${kernel_addr_r} ${ramdisk_addr_r}:${ramdisk_size} ${fdt_addr_r}
下面我们就来根据这两个boot.scr来组成一个新的boot.scr,基本的组合逻辑如下:
if test "${volume_key}" = "up" ; then
Jumpdriver
fi
else
Postmarket
fi在使用Jumpdriver的boot.scr和Postmarket的boot.scr时我们分别要进行一些调整:
1、我们将setenv kernel_addr_z 0x44080000作为公共部分;
2、我们在加载PostmarketOS时去除检测SD卡和eMMC的代码,并将bootdev置为2,即:/dev/mmcblk2 ,因为此时我们明确知道是在eMMC上加载PostmarketOS;
3、我们需要给mmc_bootdev赋值,这是mmc号,它原本是在Uboot的代码中赋值并作为环境变量传递给boot.scr,我们前面说过mmc0表示SD卡,mmc1表示eMMC,因此这里我们将mmc_bootdev赋值为1;
4、我们还需要修改load mmc ${mmc_bootdev}:1 ${fdt_addr_r} ${fdtfile} 这段代码中的${fdtfile},通过printenv我们可以观察到Jumpdriver的Uboot将fdtfile赋值为sun50i-a64-pinephone.dtb ,显然这是不行的,pinephone的Postmarket社区版硬件是1.2版,因此这里我们直接将代码修改为load mmc ${mmc_bootdev}:1 ${fdt_addr_r} sun50i-a64-pinephone-1.2.dtb
修改完毕融合后的boot.scr如下所示:
setenv kernel_addr_z 0x44080000
setenv bootargs loglevel=0 silent console=tty0 vt.global_cursor_default=0
gpio set 114
if test "${volume_key}" = "up" ; then
if load ${devtype} ${devnum}:${distro_bootpart} ${kernel_addr_z} /Image.gz; then
unzip ${kernel_addr_z} ${kernel_addr_r}
if load ${devtype} ${devnum}:${distro_bootpart} ${fdt_addr_r} /sun50i-a64-pinephone-1.2.dtb; then
if load ${devtype} ${devnum}:${distro_bootpart} ${ramdisk_addr_r} /initramfs.gz; then
booti ${kernel_addr_r} ${ramdisk_addr_r}:${filesize} ${fdt_addr_r};
else
booti ${kernel_addr_r} - ${fdt_addr_r};
fi;
fi;
fi
else
setenv mmc_bootdev 1
gpio set 98
gpio set 114
echo "Booting from eMMC";
setenv bootdev 2;
setenv bootargs init=/init.sh rw console=tty0 console=ttyS0,115200 earlycon=uart,mmio32,0x01c28000 panic=10 consoleblank=0 loglevel=1 PMOS_NO_OUTPUT_REDIRECT PMOS_FORCE_PARTITION_RESIZE pmos_boot=/dev/mmcblk${bootdev}p1 pmos_root=/dev/mmcblk${bootdev}p2 lima.sched_timeout_ms=2000
printenv
echo Loading DTB
load mmc ${mmc_bootdev}:1 ${fdt_addr_r} sun50i-a64-pinephone-1.2.dtb
echo Loading Initramfs
load mmc ${mmc_bootdev}:1 ${ramdisk_addr_r} initramfs
setenv ramdisk_size ${filesize}
echo Loading Kernel
load mmc ${mmc_bootdev}:1 ${kernel_addr_r} vmlinuz
gpio set 115
echo Resizing FDT
fdt addr ${fdt_addr_r}
fdt resize
echo Adding FTD RAM clock
fdt mknode / memory
fdt set /memory ram_freq ${ram_freq}
fdt list /memory
echo Loading user script
setenv user_scriptaddr 0x50700000
load mmc ${mmc_bootdev}:1 ${user_scriptaddr} user.scr
if test $? -eq 0; then source ${user_scriptaddr}; else echo No user script found; fi
echo Booting kernel
gpio set 116
gpio clear 98
booti ${kernel_addr_r} ${ramdisk_addr_r}:${ramdisk_size} ${fdt_addr_r}
fi
2、Manjaro与NuttX双开
同样我们用Jumpdriver来获取Manjaro的boot分区中的boot.scr中的内容
令人惊讶的是Manjaro的boot分区中保留了boot.scr的源文件boot.txt, 我们可以直接用里面的内容
#
# /boot/boot.txt
# After modifying, run "pp-uboot-mkscr" to re-generate the U-Boot boot script.
#
#
# This is the description of the GPIO lines used in this boot script:
#
# GPIO #98 is PD2, or A64 ball W19, which controls the vibrator motor
# GPIO #114 is PD18, or A64 ball AB13, which controls the red part of the multicolor LED
# GPIO #115 is PD19, or A64 ball AB12, which controls the green part of the multicolor LED
# GPIO #116 is PD20, or A64 ball AB11, which controls the blue part of the multicolor LED
#
gpio set 98
gpio set 114
# Set root partition to the second partition of boot device
part uuid ${devtype} ${devnum}:1 uuid_boot
part uuid ${devtype} ${devnum}:2 uuid_root
setenv bootargs loglevel=4 console=tty0 console=${console} earlycon=uart,mmio32,0x01c28000 consoleblank=0 boot=PARTUUID=${uuid_boot} root=PARTUUID=${uuid_root} rw rootwait quiet audit=0 bootsplash.bootfile=bootsplash-themes/manjaro/bootsplash
if load ${devtype} ${devnum}:${distro_bootpart} ${kernel_addr_r} /Image; then
gpio clear 98
if load ${devtype} ${devnum}:${distro_bootpart} ${fdt_addr_r} /dtbs/${fdtfile}; then
if load ${devtype} ${devnum}:${distro_bootpart} ${ramdisk_addr_r} /initramfs-linux.img; then
gpio set 115
booti ${kernel_addr_r} ${ramdisk_addr_r}:${filesize} ${fdt_addr_r};
else
gpio set 116
booti ${kernel_addr_r} - ${fdt_addr_r};
fi;
fi;
fi
# EOF接下来我们尝试改造Manjaro的boot.scr并替换前面的组合后的boot.scr中的PostmarketOS相关内容,显然这儿有两个未定义的环境变量(devtype、devnum),不管在Manjaro的Uboot中将这两个变量赋值为什么,我们这里对它们进行重新赋值。由于我们这里要从eMMC引导Manjaro,所以devtype为mmc,由于eMMC为mmc1,这里将devnum赋值为1,我们有了一个初步的结果:
setenv kernel_addr_z 0x44080000
setenv bootargs loglevel=0 silent console=tty0 vt.global_cursor_default=0
gpio set 114
if test "${volume_key}" = "up" ; then
if load ${devtype} ${devnum}:${distro_bootpart} ${kernel_addr_z} /Image.gz; then
unzip ${kernel_addr_z} ${kernel_addr_r}
if load ${devtype} ${devnum}:${distro_bootpart} ${fdt_addr_r} /sun50i-a64-pinephone-1.2.dtb; then
if load ${devtype} ${devnum}:${distro_bootpart} ${ramdisk_addr_r} /initramfs.gz; then
booti ${kernel_addr_r} ${ramdisk_addr_r}:${filesize} ${fdt_addr_r};
else
booti ${kernel_addr_r} - ${fdt_addr_r};
fi;
fi;
fi
else
setenv devtype mmc
setenv devnum 1
gpio set 98
gpio set 114
# Set root partition to the second partition of boot device
part uuid ${devtype} ${devnum}:1 uuid_boot
part uuid ${devtype} ${devnum}:2 uuid_root
setenv
bootargs loglevel=4 console=tty0 console=${console}
earlycon=uart,mmio32,0x01c28000 consoleblank=0
boot=PARTUUID=${uuid_boot} root=PARTUUID=${uuid_root} rw rootwait quiet
audit=0 bootsplash.bootfile=bootsplash-themes/manjaro/bootsplash
if load ${devtype} ${devnum}:${distro_bootpart} ${kernel_addr_r} /Image; then
gpio clear 98
if load ${devtype} ${devnum}:${distro_bootpart} ${fdt_addr_r} /dtbs/${fdtfile}; then
if load ${devtype} ${devnum}:${distro_bootpart} ${ramdisk_addr_r} /initramfs-linux.img; then
gpio set 115
booti ${kernel_addr_r} ${ramdisk_addr_r}:${filesize} ${fdt_addr_r};
else
gpio set 116
booti ${kernel_addr_r} - ${fdt_addr_r};
fi;
fi;
fi
fi将修改后的Jumpdriver少些进SD卡后我们尝试开机,看SD卡中的Uboot能否将eMMC中的Manjaro引导起来,不出所料完全失败了,错误日志如下:
是这段看不懂的代码老的Uboot不兼容吗?因为从Uboot日志中我们也可以看出U-Boot 2020.04 (Mar 18 2023 - 15:57:34 +0800) ,即我们SD卡中的Uboot(Jumpdriver的一部分)的版本是2020.04版,编译日期是Mar 18 2023,现在我们是在用老版本的Uboot配合最新的Manjaro的boot.scr加载Manjaro,会不会是老版本的Uboo不支持下面这代码呢?
part uuid ${devtype} ${devnum}:1 uuid_boot
part uuid ${devtype} ${devnum}:2 uuid_root
由于看不懂这段代码,我们还是把它替换掉吧,这段代码的功能是在指定Manjaro的boot分区和root分区,让我们把它恢复成PostmarketOS的写法吧!即:
删除
part uuid ${devtype} ${devnum}:1 uuid_boot
part uuid ${devtype} ${devnum}:2 uuid_root
并将boot=PARTUUID=${uuid_boot} root=PARTUUID=${uuid_root} rw替换为boot=/dev/mmcblk${bootdev}p1 root=/dev/mmcblk${bootdev}p2 rw
setenv devtype mmc
setenv devnum 1
gpio set 98
gpio set 114
setenv bootdev 2;
setenv bootargs loglevel=4 console=tty0 console=${console} earlycon=uart,mmio32,0x01c28000 consoleblank=0 boot=/dev/mmcblk${bootdev}p1 root=/dev/mmcblk${bootdev}p2 rw rootwait quiet audit=0 bootsplash.bootfile=bootsplash-themes/manjaro/bootsplash
if load ${devtype} ${devnum}:${distro_bootpart} ${kernel_addr_r} /Image; then
gpio clear 98
if load ${devtype} ${devnum}:${distro_bootpart} ${fdt_addr_r} /dtbs/${fdtfile}; then
if load ${devtype} ${devnum}:${distro_bootpart} ${ramdisk_addr_r} /initramfs-linux.img; then
gpio set 115
booti ${kernel_addr_r} ${ramdisk_addr_r}:${filesize} ${fdt_addr_r};
else
gpio set 116
booti ${kernel_addr_r} - ${fdt_addr_r};
fi;
fi;
fi软而结果完全没啥变化,为了排查问题,我们在如下两行代码之间加入printenv以便观察是那个变量的值不对,从而分析引导失败的原因
audit=0 bootsplash.bootfile=bootsplash-themes/manjaro/bootsplash
if load ${devtype} ${devnum}:${distro_bootpart} ${kernel_addr_r} /Image; then
即
audit=0 bootsplash.bootfile=bootsplash-themes/manjaro/bootsplash
printenv
if load ${devtype} ${devnum}:${distro_bootpart} ${kernel_addr_r} /Image; then出乎意料的是引导过程中并没有输出环境变量的信息,即printenv命令并未被执行,难道是setenv bootargs loglevel=4 console=tty0 console=${console} earlycon=uart,mmio32,0x01c28000 consoleblank=0 boot=/dev/mmcblk${bootdev}p1 root=/dev/mmcblk${bootdev}p2 rw rootwait quiet audit=0 bootsplash.bootfile=bootsplash-themes/manjaro/bootsplash执行失败?但那是不可能的,因为这段代码只是给环境变量赋值。
经一部分析Uboot日志后我们发现了一个线索:
switch to partitions #0, OK
mmc0 is current device
Scanning mmc 0:1...
switch to partitions #0, OK
mmc1(part 0) is current device
Scanning mmc 1:1...
Found U-Boot script /boot.scr
1476 bytes read in 1 ms (1.4 MiB/s)
## Executing script at 4fc00000 mmc0 is current device
Scanning mmc 0:1...
switch to partitions #0, OK
表明当前正在执行SD上的Uboot,扫描/dev/mmcblk0p1发现了引导分区,然后切换到分区0上准备执行boot.scr中的内容,但紧接著下面几句就有点诡异了:
mmc1(part 0) is current device
Found U-Boot script /boot.scr
1476 bytes read in 1 ms (1.4 MiB/s) eMMC的boot分区成为了当前设备,可我们明明是在执行SD卡中的Uboot和SD中boot分区中的boot.scr啊?现在eMMC的boot分区成为了当前设备,并在其中发现了boot.scr并加载了boot.scr,这意味这现在是在运行SD卡中的Uboot,但开始使用eMMC中的boot.scr!怪不得我们加的printenv没起作用,当然boot.scr后面的代码也都没被执行。
经过一番思考,终于仿然大悟,这一切都是setenv devnum 1这条语句造成的,devnum是一个全局的重要环境变量,本来我们执行SD卡中的boot.scr时devnum为0,当devnum被改为1后Uboot立即重新从eMMC中查找boot.scr并加载执行!因此我们需要另外定义一个环境变量实现执行用SD卡中的boot.scr来加载eMMC中的Manjaro:
setenv devtype mmc
# setenv devnum 1
setenv mmc_bootdev 1
gpio set 98
gpio set 114
echo "Booting from eMMC";
setenv bootdev 2;
setenv bootargs loglevel=4 console=tty0 console=${console} earlycon=uart,mmio32,0x01c28000 consoleblank=0 boot=/dev/mmcblk${bootdev}p1 root=/dev/mmcblk${bootdev}p2 rw rootwait quiet audit=0 bootsplash.bootfile=bootsplash-themes/manjaro/bootsplash
printenv
if load ${devtype} ${devnum}:${distro_bootpart} ${kernel_addr_r} /Image; then
gpio clear 98
if load ${devtype} ${devnum}:${distro_bootpart} ${fdt_addr_r} /dtbs/${fdtfile}; then
if load ${devtype} ${devnum}:${distro_bootpart} ${ramdisk_addr_r} /initramfs-linux.img; then
gpio set 115
booti ${kernel_addr_r} ${ramdisk_addr_r}:${filesize} ${fdt_addr_r};
else
gpio set 116
booti ${kernel_addr_r} - ${fdt_addr_r};
fi;
fi;
fi改完之后,现象就完全不一样了,开始输出环境变量了:
但是最后又报了别的错误:
经分析,我们确认是由于load ${devtype} ${devnum}:${distro_bootpart} ${fdt_addr_r} /dtbs/${fdtfile}这句代码造成,因为Jumpdriver的2020.04版的Uboot的比较老,它的预定义环境变量fdtfile=allwinner/sun50i-a64-pinephone.dtb ,显然不对!由于Manjaro的Beta版Pinephone用的是1.2b的硬件,这里我们干脆不用fdtfile,而是直接用:
load ${devtype} ${mmc_bootdev}:${distro_bootpart} ${fdt_addr_r} /dtbs/allwinner/sun50i-a64-pinephone-1.2b.dtb
最终的boot.scr内容如下所示:
setenv kernel_addr_z 0x44080000
setenv bootargs loglevel=0 silent console=tty0 vt.global_cursor_default=0
gpio set 114
if test "${volume_key}" = "up" ; then
if load ${devtype} ${devnum}:${distro_bootpart} ${kernel_addr_z} /Image.gz; then
unzip ${kernel_addr_z} ${kernel_addr_r}
if load ${devtype} ${devnum}:${distro_bootpart} ${fdt_addr_r} /sun50i-a64-pinephone-1.2.dtb; then
if load ${devtype} ${devnum}:${distro_bootpart} ${ramdisk_addr_r} /initramfs.gz; then
booti ${kernel_addr_r} ${ramdisk_addr_r}:${filesize} ${fdt_addr_r};
else
booti ${kernel_addr_r} - ${fdt_addr_r};
fi;
fi;
fi
else
setenv devtype mmc
# setenv devnum 1
setenv mmc_bootdev 1
gpio set 98
gpio set 114
echo "Booting from eMMC";
setenv bootdev 2;
# Set root partition to the second partition of boot device
# part uuid ${devtype} ${devnum}:1 uuid_boot
# part uuid ${devtype} ${devnum}:2 uuid_root
setenv bootargs loglevel=4 console=tty0 console=${console} earlycon=uart,mmio32,0x01c28000 consoleblank=0 boot=/dev/mmcblk${bootdev}p1 root=/dev/mmcblk${bootdev}p2 rw rootwait quiet audit=0 bootsplash.bootfile=bootsplash-themes/manjaro/bootsplash
printenv
if load ${devtype} ${mmc_bootdev}:${distro_bootpart} ${kernel_addr_r} /Image; then
gpio clear 98
if load ${devtype} ${mmc_bootdev}:${distro_bootpart} ${fdt_addr_r} /dtbs/allwinner/sun50i-a64-pinephone-1.2b.dtb; then
if load ${devtype} ${mmc_bootdev}:${distro_bootpart} ${ramdisk_addr_r} /initramfs-linux.img; then
gpio set 115
booti ${kernel_addr_r} ${ramdisk_addr_r}:${filesize} ${fdt_addr_r};
else
gpio set 116
booti ${kernel_addr_r} - ${fdt_addr_r};
fi;
fi;
fi
fi
本人已新建了一个Jumpdriver分支,Pinephone爱好者可以直接去github上下载
https://github.com/zouboan/Jumpdrive/tree/manjaro
最后是Uboot的音量按键补丁:
页:
[1]