feedforward 发表于 2023-3-19 09:28:56

如何在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]
查看完整版本: 如何在pinephone上实现NuttX与Linux双开