From c373152673aedc0621ba5d5ee8b5304d98559bdd Mon Sep 17 00:00:00 2001 From: Daniel Barlow Date: Thu, 15 Feb 2024 20:12:03 +0000 Subject: [PATCH] make tftpboot work on devices with old u-boot Some devices have a U-boot variant that does not accept a third parameter on the "bootm" command, meaning we can't override the dtb in the bootloader so have to smush it back into the kernel image This doesn't work in QEMU but I think the problem is with the U-Boot configuration for QEMU. It does work on at least one hardware device so I'm pushing it anyway Based on https://gti.telent.net/raboof/liminix/src/branch/tftp-old-uboot Co-authored-by: Arnout Engelen --- modules/outputs/tftpboot.nix | 72 ++++++++++++++++++++++++++---------- tests/tftpboot/test.nix | 9 ++++- 2 files changed, 61 insertions(+), 20 deletions(-) diff --git a/modules/outputs/tftpboot.nix b/modules/outputs/tftpboot.nix index 00674f23..a947bc00 100644 --- a/modules/outputs/tftpboot.nix +++ b/modules/outputs/tftpboot.nix @@ -7,6 +7,8 @@ let inherit (lib) mkOption types concatStringsSep; cfg = config.boot.tftp; + hw = config.hardware; + arch = pkgs.stdenv.hostPlatform.linuxArch; in { imports = [ ../ramdisk.nix ]; options.boot.tftp = { @@ -22,6 +24,10 @@ in { type = types.bool; default = false; }; + appendDTB = mkOption { + type = types.bool; + default = false; + }; }; options.system.outputs = { tftpboot = mkOption { @@ -62,24 +68,46 @@ in { uimage = "bootm"; zimage = "bootz"; }; in choices.${cfg.kernelFormat}; + cmdline = concatStringsSep " " config.boot.commandLine; + objcopy = "${pkgs.stdenv.cc.bintools.targetPrefix}objcopy"; + stripAndZip = '' + ${objcopy} -O binary -R .reginfo -R .notes -R .note -R .comment -R .mdebug -R .note.gnu.build-id -S vmlinux.elf vmlinux.bin + rm -f vmlinux.bin.lzma ; lzma -k -z vmlinux.bin + ''; in - pkgs.runCommand "tftpboot" { nativeBuildInputs = with pkgs.pkgsBuildBuild; [ lzma dtc ]; } '' + pkgs.runCommand "tftpboot" { nativeBuildInputs = with pkgs.pkgsBuildBuild; [ lzma dtc pkgs.stdenv.cc ubootTools ]; } '' mkdir $out cd $out binsize() { local s=$(stat -L -c %s $1); echo $(($s + 0x1000 &(~0xfff))); } binsize64k() { local s=$(stat -L -c %s $1); echo $(($s + 0x10000 &(~0xffff))); } hex() { printf "0x%x" $1; } + rootfsStart=${toString cfg.loadAddress} rootfsSize=$(binsize64k ${o.rootfs} ) rootfsSize=$(($rootfsSize + ${toString cfg.freeSpaceBytes} )) - dtbStart=$(($rootfsStart + $rootfsSize)) - imageSize=$(binsize ${image}) ln -s ${o.manifest} manifest - ln -s ${image} image ln -s ${o.kernel} vmlinux # handy for gdb + # if we are transferring kernel and dtb separately, the + # dtb has to precede the kernel in ram, because zimage + # decompression code will assume that any memory after the + # end of the kernel is free + + dtbStart=$(($rootfsStart + $rootfsSize)) + ${if cfg.compressRoot + then '' + lzma -z9cv ${o.rootfs} > rootfs.lz + rootfsLzStart=$dtbStart + rootfsLzSize=$(binsize rootfs.lz) + dtbStart=$(($dtbStart + $rootfsLzSize)) + '' + else '' + ln -s ${o.rootfs} rootfs + '' + } + cat ${o.dtb} > dtb address_cells=$(fdtget dtb / '#address-cells') size_cells=$(fdtget dtb / '#size-cells') @@ -93,34 +121,40 @@ in { fdtput -p -t s dtb /reserved-memory/$node compatible phram fdtput -p -t lx dtb /reserved-memory/$node reg $ac_prefix $(hex $rootfsStart) $sz_prefix $(hex $rootfsSize) - dtbSize=$(binsize ./dtb ) - imageStart=$(($dtbStart + $dtbSize)) - ${if cfg.compressRoot - then '' - lzma -z9cv ${o.rootfs} > rootfs.lz - rootfsLzStart=$(($imageStart + $imageSize)) - rootfsLzSize=$(binsize rootfs.lz) - '' - else "ln -s ${o.rootfs} rootfs" - } - cmd="liminix ${cmdline} mtdparts=phram0:''${rootfsSize}(rootfs) phram.phram=phram0,''${rootfsStart},''${rootfsSize},${toString config.hardware.flash.eraseBlockSize} root=/dev/mtdblock0"; fdtput -t s dtb /chosen bootargs "$cmd" - # dtc -I dtb -O dts -o /dev/stdout dtb | grep -A10 chosen ; exit 1 + dtbSize=$(binsize ./dtb ) + + ${if cfg.appendDTB then '' + imageStart=$dtbStart + # re-package image with updated dtb + cat ${o.kernel} > vmlinux.elf + ${objcopy} --update-section .appended_dtb=dtb vmlinux.elf + ${stripAndZip} + mkimage -A ${arch} -O linux -T kernel -C lzma -a $(hex ${toString hw.loadAddress}) -e $(hex ${toString hw.entryPoint}) -n '${lib.toUpper arch} Liminix Linux tftpboot' -d vmlinux.bin.lzma image + # dtc -I dtb -O dts -o /dev/stdout dtb | grep -A10 chosen ; exit 1 + tftpcmd="tftpboot $(hex $imageStart) result/image " + bootcmd="bootm $(hex $imageStart)" + '' else '' + imageStart=$(($dtbStart + $dtbSize)) + tftpcmd="tftpboot $(hex $imageStart) result/image; tftpboot $(hex $dtbStart) result/dtb " + ln -s ${image} image + bootcmd="${bootCommand} $(hex $imageStart) - $(hex $dtbStart)" + ''} cat > boot.scr << EOF setenv serverip ${cfg.serverip} setenv ipaddr ${cfg.ipaddr} - tftpboot $(hex $imageStart) result/image ; ${ + ${ if cfg.compressRoot then "tftpboot $(hex $rootfsLzStart) result/rootfs.lz" else "tftpboot $(hex $rootfsStart) result/rootfs" - }; tftpboot $(hex $dtbStart) result/dtb + }; $tftpcmd ${if cfg.compressRoot then "lzmadec $(hex $rootfsLzStart) $(hex $rootfsStart); " else "" - } ${bootCommand} $(hex $imageStart) - $(hex $dtbStart) + } $bootcmd EOF ''; diff --git a/tests/tftpboot/test.nix b/tests/tftpboot/test.nix index 941d15ba..b7a3e870 100644 --- a/tests/tftpboot/test.nix +++ b/tests/tftpboot/test.nix @@ -12,7 +12,7 @@ let derivation = (import liminix { img = derivation.outputs.tftpboot; uboot = derivation.outputs.u-boot; pkgsBuild = derivation.pkgs.pkgsBuildBuild; -in pkgsBuild.runCommand "check" { +in pkgsBuild.runCommand "check-${deviceName}" { nativeBuildInputs = with pkgsBuild; [ expect socat @@ -44,4 +44,11 @@ in { mipsLz = check "qemu" { boot.tftp.compressRoot = true; }; + # this works on real hardware but I haven't figured out how + # to make it work on qemu: it says + # "OF: fdt: No chosen node found, continuing without" + + # mipsOldUboot = check "qemu" { + # boot.tftp.appendDTB = true; + # }; }