building a gigabit firewall, pt 0

by Brian Kubisiak — Wed 20 September 2023

I recently upgraded to gigabit fiber internet. Turns out my firewall only supports 100 mbps, which is a bit disappointing. I primarily upgraded to fiber because it was cheaper and I wanted a symmetric connection, so download speed isn't all that important to me, but it would still be nice to get what I'm paying for. After some shopping around for a new firewall, I realized I already had an espressobin ultra box that should support gigabit speeds (I bought it awhile ago to make some custom home network equipment and it's sat in its box ever since).

The obvious answer here is to save a few hundred dollars by dumping countless hours into building myself the perfect firewall.

My strategy:

  1. Install guix (my distro of choice) using marvell's kernel.

  2. Upgrade to an upstream kernel.

  3. Set up a NAT firewall using nftables.

  4. Optimize until I'm happy with the speed.

kernel build and test

First I've got to figure out how this is set up. I haven't touched any of the software yet, it's just running whatever the vendor put on there. Booting attached to the serial console, it looks like it's using u-boot (full boot log here) and Ubuntu 18.04 running a 5.4 kernel:

$ uname --all
Linux ebinx003606 5.4.53-00023-gb67cb8815786 #3 SMP PREEMPT \
    Thu Mar 4 16:52:46 CST 2021 aarch64 aarch64 aarch64 GNU/Linux
$ cat /etc/lsb-release
DISTRIB_ID=Ubuntu
DISTRIB_RELEASE=18.04
DISTRIB_CODENAME=bionic
DISTRIB_DESCRIPTION="Ubuntu 18.04 LTS"

The commit b67cb8815786 points at linux-5.4.53-gti branch in Globalscale's kernel repo, so I'll start there. Newer 5.4.163, 5.10.166, and 5.16.y branches exist, but I'll be upgrading to an upstream kernel anyways, so I'll just ignore those and get something working first.

Where do kernel and device tree get loaded from? I see a bunch of files in /boot, so I'd guess that kernel and device tree are loaded from this location in the rootfs:

$ find /boot
/boot/
/boot/armada-3720-espressobin_ultra.dtb
/boot/Image
/boot/bootloader
/boot/bootloader/... [omitted for brevity]
/boot/armada-3720-ccpe.dtb

There are a bunch of files in /boot/bootloader as well, but those are not referenced by u-boot and don't seem important, so I'm ignoring them. Then checking the u-boot environment (with some extraneous fields removed (full u-boot environment)):

Marvell>> printenv
board=mvebu_armada-37xx
board_name=mvebu_armada-37xx
bootcmd=mmc dev 0; ext4load mmc 0:1 $kernel_addr_r $image_name; \
    ext4load mmc 0:1 $fdt_addr_r $fdt_name;setenv bootargs \
    $console root=PARTUUID=89708921-01 rw rootwait net.ifnames=0 \
    biosdevname=0; booti $kernel_addr_r - $fdt_addr_r
fdt_name=boot/armada-3720-ccpe.dtb
image_name=boot/Image

Notably bootcmd and image_name agree with my guess. fdt_name points at armada-3720-ccpe.dtb. I should be able to build a new kernel and device tree, stick it in /boot, and make sure that it works. Starting with defining the kernel package for guix:

(define-public linux-espressobin-ultra
  (package
    (inherit
     (customize-linux
      #:name "linux-espressobin-ultra"
      #:source
      (origin
        (method git-fetch)
        (uri (git-reference
              (url "https://github.com/globalscaletechnologies/linux.git")
              (commit "linux-5.4.53-gti")))
        (file-name (git-file-name "linux-espressobin-ultra" "5.4.53-gti"))
        (sha256
         (base32 "1pwpn6wrz6ydx62gp9g2drapg126lwihcr0yhhcqilc1cxy7m02q")))
      #:defconfig "gti_ccpe-88f3720_defconfig"
      #:extra-version "guix"))
    (version "5.4.53-gti")
    (home-page "https://github.com/globalscaletechnologies/linux")
    (synopsis "Linux kernel with patches from globalscale technologies")
    (description "Linux kernel build from globalscale technologies sources,
including nonfree binary blobs.")))

Then trying to build:

$ guix build --target=aarch64-linux-gnu --load-path=. linux-espressobin-ultra

I immediately hit a basic configuration error:

Mismatching configurations in .config and arch/arm64/configs/guix_defconfig \
    (("CONFIG_ARM_IMX_CPUFREQ_DT" (#f "m")) ...

Looking at VERIFY-CONFIG in guix/build/kconfig.scm, this compares .config with the original defconfig file, checking for differences. This appears to be looking for malformed configs (e.g. some options are enabled without their dependencies). Looking at the first one ARM_IMX_CPUFREQ_DT is set to "m" in gti_ccpe-88f3720_defconfig; this depends on ARCH_MXC, which is not set. We can also see that ARM_IMX_CPUFREQ_DT is not set in /proc/config.gz on the board, confirming that the defconfig is incorrect.

Situations like this are pretty common in vendor kernel trees where defconfigs are edited manually and/or not updated properly when upgrading kernel sources. The fix is pretty easy: make ARCH=arm64 gti_ccpe-88f3720_defconfig && make ARCH=arm64 savedefconfig to "re-normalize" the malformed config. The resulting diff is here.

We can use local-file to use this new config in the kernel build, which should still be identical to the running kernel. With this, the build succeeds (conveniently giving us device trees as well).

For now, I'd like to avoid trampling the existing kernel to make recovery easier if I fail. I'll just install the new kernel, modules, and device tree at different paths in the rootfs and then just toggle this on/off with u-boot variables (via fw_setenv).

With the kernel image at /boot/guix/Image and device tree at /boot/guix/armada-3720-ccpe.dtb (modules are already a different path due to the different extraversion string), I can point u-boot at the new images and then boot into the new system:

$ fw_setenv image_name boot/guix/Image
$ fw_setenv fdt_name boot/guix/armada-3720-ccpe.dtb
$ reboot
...
$ uname --all
Linux ebinx003606 5.4.53-guix #1 SMP PREEMPT 1 aarch64 aarch64 aarch64 GNU/Linux

The kernel/device tree are working, but modules are not:

# modprobe btrfs
modprobe: ERROR: ../libkmod/libkmod.c:586 kmod_search_moddep() \
    could not open moddep file '/lib/modules/5.4.53-guix/modules.dep.bin'
modprobe: FATAL: Module btrfs not found in directory /lib/modules/5.4.53-guix

The modules.dep.bin file is indeed not built by guix in the kernel and is instead handled in linux-module-database as part of the system profile. I'd guess this is done to allow building and loading modules outside of the kernel tree. Checking gnu/packages/linux.scm confirms this:

Disable depmod because the Guix system's module directory is an union of potentially multiple packages. It is not possible to use depmod to usefully calculate a dependency graph while building only one of them.

This doesn't really matter yet since I don't quite need modules (and I've at least verified the kernel boots), so I'll defer this for now. Let's set the kernel and device-tree paths back:

# fw_setenv image_name boot/Image
# fw_setenv fdt_name boot/armada-3720-ccpe.dtb

building a guix rootfs

The next step is to build a guix system image to flash to the eMMC. Since u-boot is in QSPI (and difficult to upgrade), I'd like to leave it alone and manage just the rootfs and kernel via guix.

Guix's u-boot bootloader will generate extlinux.conf, and I can then modify u-boot's boot command (via fw_setenv) to load this using sysboot. I'd just have to change the u-boot variables once, then reconfiguring the guix system will generate a new extlinux.conf and get picked up automatically by u-boot.

Let's start by testing this out with a fake /boot/extlinux/extlinux.conf:

LABEL prebuilt ubuntu
MENU LABEL ubuntu 18.04
KERNEL /boot/Image
FDT /boot/armada-3720-ccpe.dtb
APPEND root=PARTUUID=89708921-01 rw rootwait console=ttyMV0,115200

Then rebooting into u-boot, I need to figure out the correct sysboot invocation to load this configuration. The usage message is:

sysboot [-p] <interface> <dev[:part]> <ext2|fat|any> [addr] [filename]
    - load and parse syslinux menu file 'filename' from ext2, fat
      or any filesystem on 'dev' on 'interface' to address 'addr'

What to use for the interface and device? In the ext4load in the boot command, it looks like it's currently using mmc 0:1. Checking the usage for ext4load:

ext4load <interface> [<dev[:part]> [addr [filename [bytes [pos]]]]]
    - load binary file 'filename' from 'dev' on 'interface'
      to address 'addr' from ext4 filesystem

So mmc 0:1 is definitely what we want. For the address, I'll just pick something random that doesn't seem to be in use based on the environment variables.

Marvell>> sysboot mmc 0:1 any 0x4000000 /boot/extlinux/extlinux.conf
Retrieving file: /boot/extlinux/extlinux.conf
"Synchronous Abort" handler, esr 0x96000046

    Attempt to access RT service or TEE region (addr: 0x4000000, el2)
    Do not use address range 0x4000000-0x5400000

elr: 0000000000062e18 lr : 000000000004da20 (reloc)
elr: 000000003ff9ee18 lr : 000000003ff89a20
x0 : 0000000004000000 x1 : 000000003f628700
x2 : 00000000000000c3 x3 : 0000000000000000
x4 : 0000000000000018 x5 : 00000000000000c0
x6 : 7270204c4542414c x7 : 0000000000000020
x8 : 000000003ffc06f0 x9 : 0000000000000008
x10: 000000003f6b4560 x11: 0000000000000001
x12: 000000003f6b4560 x13: 0000000000538811
x14: 000000003f62903c x15: 0000000000000002
x16: 000000003ffbc4d0 x17: 000000003ffbc4d0
x18: 000000003f62bdb0 x19: 00000000000000c3
x20: 000000003f62d640 x21: 0000000004000000
x22: 000000003f628700 x23: 000000003f628940
x24: 000000003f628940 x25: 000000003ffdbca0
x26: 0000000004001000 x27: 0000000000000000
x28: 0000000000000009 x29: 000000003f628b80

Resetting CPU ...

Ok, so maybe I'll pick something a different address...

Marvell>> sysboot mmc 0:1 any 0x5400000 /boot/extlinux/extlinux.conf

After that the system boots as expected. In the booted system, /proc/cmdline shows the command-line args are pulled from the extlinux.conf instead of u-boot, so it definitely worked.

Now that I know how to boot an extlinux.conf, let's set up guix. Guix's base services already include a tty on the console and I don't need anything else to start with.

(define espressobin-ultra-barebones-os
  (operating-system
    (kernel linux-espressobin-ultra)
    (initrd-modules '())
    (host-name "ebinx")
    (timezone "US/Pacific")
    (locale "en_US.utf8")
    (bootloader (bootloader-configuration
                 (bootloader u-boot-bootloader)
                 (targets '("/dev/mmcblk0"))))
    (file-systems (cons
                   (file-system
                     (device "/dev/mmcblk0p1")
                     (mount-point "/")
                     (type "ext4"))
                   %base-file-systems))))

Then build it with:

$ guix system image --target=aarch64-linux-gnu ebinx.scm

The resulting image is 1.6G, I'll want to trim that down a bit in the future. Examining the image:

$ sudo kpartx -a -r /gnu/store/...-disk-image
$ lsblk
NAME        MAJ:MIN RM   SIZE RO TYPE  MOUNTPOINTS
loop0         7:0    0   1.6G  1 loop
└─loop0p1   253:1    0   1.6G  1 part
...
$ sudo mount /dev/mapper/loop0p1 /mnt -o ro
$ cd /mnt
$ cat boot/extlinux/extlinux.conf
# This file was generated from your Guix configuration.  Any changes
# will be lost upon reconfiguration.
UI menu.c32
MENU TITLE GNU Guix Boot Options
PROMPT 1
TIMEOUT 50
LABEL GNU with Linux-Espressobin-Ultra 5.4.53-gti
  MENU LABEL GNU with Linux-Espressobin-Ultra 5.4.53-gti
  KERNEL /gnu/store/...-linux-espressobin-ultra-5.4.53-gti/Image
  FDTDIR /gnu/store/...-linux-espressobin-ultra-5.4.53-gti/lib/dtbs
  INITRD /gnu/store/...-raw-initrd/initrd.cpio.gz
  APPEND root=38af4c98-4625-3523-b285-334638af4c98 \
      gnu.system=/gnu/store/...-system \
      gnu.load=/gnu/store/...-system/boot \
      modprobe.blacklist=usbmouse,usbkbd quiet

That's mostly correct. The two issues I see:

  1. I'll want to add the console to the command-line as well.

  2. I don't think FDTDIR alone will be enough to find the device tree during boot.

To fix (1), I can just add a kernel-arguments field to the operating-system definition:

    (kernel-arguments
     (cons* "console=ttyMV0,115200"
            "earlycon=ar3700_uart,0xd0012000"
            %default-kernel-arguments))

For (2), I'll ignore this for now and fix it when the system fails to boot. I'm not yet sure whether I should change this in guix or u-boot. It's also important to note that guix adds quiet as part of %default-kernel-arguments, which may interfere with debugging boot issues. Unwinding everything:

$ cd ~/
$ sudo umount /mnt
$ sudo kpartx -d /dev/loop0
$ sudo losetup -d /dev/loop0

After modifying kernel-arguments (and re-mounting):

# This file was generated from your Guix configuration.  Any changes
# will be lost upon reconfiguration.
UI menu.c32
MENU TITLE GNU Guix Boot Options
PROMPT 1
TIMEOUT 50
LABEL GNU with Linux-Espressobin-Ultra 5.4.53-gti
  MENU LABEL GNU with Linux-Espressobin-Ultra 5.4.53-gti
  KERNEL /gnu/store/...-linux-espressobin-ultra-5.4.53-gti/Image
  FDTDIR /gnu/store/...-linux-espressobin-ultra-5.4.53-gti/lib/dtbs
  INITRD /gnu/store/...-raw-initrd/initrd.cpio.gz
  APPEND root=38af4c98-4625-3523-b285-334638af4c98 \
      gnu.system=/gnu/store/...-system \
      gnu.load=/gnu/store/...-system/boot \
      console=ttyMV0,115200 \
      earlycon=ar3700_uart,0xd0012000 \
      modprobe.blacklist=usbmouse,usbkbd quiet

That's better.

installing the disk image

Now that I've got the disk image building, I've got to figure out how to get it installed.

Recovery using a usb are listed here: https://espressobin.net/espressobin-ultra-build-instruction/

GTI's instructions suggest booting into a recovery initramfs and flashing from there. Unfortunately, they don't seem to actually link to the recovery image and I can't find where it's built.

We've got a couple of options:

  1. Flash using u-boot.

  2. Build a bootable USB with guix (doesn't seem like there are any pre-built arm64 images) and flash from there.

I'm going to attempt (1). This is a little tricky since the u-boot that is running doesn't have read/write commands for flashing images. Also, the image itself is larger than RAM, so it's going to be tricky to flash from memory.

Skimming through u-boot's command list, there is a gzwrite command that decompresses a chunk from memory and writes it to a disk. This is convenient, since my disk image is only 382 MB when gzip'd, which will fit in RAM. After compressing the disk image and copying it to a USB drive, I'm back in u-boot:

Marvell>> usb start
starting USB...
USB0:   Register 2000104 NbrPorts 2
Starting the controller
USB XHCI 1.00
USB1:   USB EHCI 1.00
scanning bus 0 for devices... 2 USB Device(s) found
scanning bus 1 for devices... 2 USB Device(s) found
       scanning usb for storage devices... 1 Storage Device(s) found
Marvell>> usb storage
  Device 0: Vendor: Lexar    Rev: 1100 Prod: USB Flash Drive
            Type: Removable Hard Disk
            Capacity: 15276.0 MB = 14.9 GB (31285248 x 512)
Marvell>> ls usb 0:1
<DIR>       4096 .
<DIR>       4096 ..
<DIR>      16384 lost+found
       400313016 disk-image.gz
Marvell>> load usb 0:1 0x5400000 disk-image.gz
400313016 bytes read in 9954 ms (38.4 MiB/s)
Marvell>> gzwrite mmc 0 0x5400000 400313016

gzwrite: weird termination with result 0

    uncompressed 36453026 of 4007447747
    crcs == 0xbdcd4917/0x19d71fd0

The "weird termination" is weird; checking eMMC shows that it definitely didn't work:

Marvell>> ls mmc 0:1
<DIR>       4096 .
<DIR>       4096 ..
<DIR>      16384 lost+found
<DIR>       4096 bin
<DIR>       4096 boot
<DIR>       4096 etc
<DIR>       4096 gnu
<DIR>       4096 home
<DIR>       4096 mnt
<DIR>       4096 run
<DIR>       4096 tmp
<DIR>       4096 var
Marvell>> ls mmc 0:1 /gnu/store
invalid extent block

A quick google search doesn't uncover anyone who's hit this issue either.

Checking the error message again, the stated length of the image (4007447747) is incorrect. For a gzip'd file, this length should come from the last 4 bytes of the image. disk-image.gz correctly ends in 0x68e34000, but u-boot is showing 0xeedcccc3.

Checking the source, the length in the gzwrite command is parsed using simple_strtoul(argv[4], NULL, 16) (i.e. base 16). Fixing that:

Marvell>> gzwrite mmc 0 0x5400000 0x17dc4ab8

1758461952/1759723520
    uncompressed 1759726190 of 1759723520
    crcs == 0x5226e262/0x0b1fff17
Marvell>> ls mmc 0:1 /gnu/store
invalid extent block

The length is correct now, but it still didn't decompress/flash the whole image. It's possible this is some odd transient error in the eMMC writes. I'm going to power cycle and try again:

Marvell>> usb start
starting USB...
USB0:   Register 2000104 NbrPorts 2
Starting the controller
USB XHCI 1.00
USB1:   USB EHCI 1.00
scanning bus 0 for devices... 2 USB Device(s) found
scanning bus 1 for devices... 2 USB Device(s) found
       scanning usb for storage devices... 1 Storage Device(s) found
Marvell>> load usb 0:1 0x5400000 disk-image.gz
400313016 bytes read in 10034 ms (38 MiB/s)
Marvell>> gzwrite mmc 0 0x5400000 0x17dc4ab8

1758461952/1759723520
    1759723520 bytes, crc 0x5226e262
Marvell>> ls mmc 0:1 /gnu/store
<DIR>      24576 .
<DIR>       4096 ..
<DIR>       4096 05wh5f68g081ghzfyraa2a3s18a915b5-libxdmcp-1.1.3
            1053 06blr7hwq5hhj045rw7r1z97rc94p1s4-shepherd-host-name.scm
<DIR>       4096 06v7rs8h8i86zzkpa89byx039w20g1i1-tar-1.34
...

Awesome! Let's try booting the new image:

Marvell>> sysboot mmc 0:1 any 0x5400000 /boot/extlinux/extlinux.conf
Retrieving file: /boot/extlinux/extlinux.conf
822 bytes read in 15 ms (52.7 KiB/s)
Ignoring unknown command: UI
GNU Guix Boot Options
1:  GNU with Linux-Espressobin-Ultra 5.4.53-gti
Enter choice: 1:    GNU with Linux-Espressobin-Ultra 5.4.53-gti
Retrieving file: /gnu/store/...-raw-initrd/initrd.cpio.gz
13341025 bytes read in 2108 ms (6 MiB/s)
Retrieving file: /gnu/store/...-linux-espressobin-ultra-5.4.53-gti/Image
18061824 bytes read in 16751 ms (1 MiB/s)
append: root=38af4c98-70f0-62ac-b285-334638af4c98 \
    gnu.system=/gnu/store/...-system \
    gnu.load=/gnu/store/...-system/boot \
    console=ttyMV0,115200 \
    earlycon=ar3700_uart,0xd0012000 \
    modprobe.blacklist=usbmouse,usbkbd quiet
Retrieving file: /gnu/store/...-linux-espressobin-ultra-5.4.53-gti/ \
    lib/dtbs/mvebu-mvebu_armada-37xx.dtb
** File not found /gnu/store/...-linux-espressobin-ultra-5.4.53-gti/ \
    lib/dtbs/mvebu-mvebu_armada-37xx.dtb **
Skipping GNU with Linux-Espressobin-Ultra 5.4.53-gti for failure retrieving fdt

As expected, setting FDTDIR isn't enough for u-boot. But now I can see what it's actually doing: if fdtfile isn't in the environment, u-boot defaults to ${soc}-${board}.dtb. Let's just set fdtfile so it can find the device tree correctly:

Marvell>> setenv fdtfile marvell/armada-3720-ccpe.dtb
Marvell>> sysboot mmc 0:1 any 0x5400000 /boot/extlinux/extlinux.conf
Retrieving file: /boot/extlinux/extlinux.conf
822 bytes read in 16 ms (49.8 KiB/s)
Ignoring unknown command: UI
GNU Guix Boot Options
1:  GNU with Linux-Espressobin-Ultra 5.4.53-gti
Enter choice: 1:    GNU with Linux-Espressobin-Ultra 5.4.53-gti
Retrieving file: /gnu/store/...-raw-initrd/initrd.cpio.gz
13341025 bytes read in 2108 ms (6 MiB/s)
Retrieving file: /gnu/store/...-linux-espressobin-ultra-5.4.53-gti/Image
18061824 bytes read in 16752 ms (1 MiB/s)
append: root=38af4c98-70f0-62ac-b285-334638af4c98 \
    gnu.system=/gnu/store/...-system \
    gnu.load=/gnu/store/...-system/boot \
    console=ttyMV0,115200 earlycon=ar3700_uart,0xd0012000 \
    modprobe.blacklist=usbmouse,usbkbd quiet
Retrieving file: /gnu/store/...-linux-espressobin-ultra-5.4.53-gti/ \
    lib/dtbs/marvell/armada-3720-ccpe.dtb
12427 bytes read in 1695 ms (6.8 KiB/s)
## Flattened Device Tree blob at 06f00000
   Booting using the fdt blob at 0x6f00000
   Loading Ramdisk to 3e96e000, end 3f627161 ... OK
   Using Device Tree in place at 0000000006f00000, end 0000000006f0608a

Starting kernel ...

[    0.000000] Booting Linux on physical CPU 0x0000000000 [0x410fd034]
[    0.000000] Linux version 5.4.53-guix (guix@guix) \
                   (gcc version 11.3.0 (GCC)) #1 SMP PREEMPT 1
[    0.000000] Machine model: gti cellular cpe board
[    0.000000] earlycon: ar3700_uart0 at MMIO 0x00000000d0012000 (options '')
[    0.000000] printk: bootconsole [ar3700_uart0] enabled
[    0.393819] orion-mdio d0032004.mdio: IRQ index 0 not found
[    0.689733] Kernel panic - not syncing: VFS: \
                   Unable to mount root fs on unknown-block(0,0)
[    0.695396] CPU: 1 PID: 1 Comm: swapper/0 Not tainted 5.4.53-guix #1
[    0.701931] Hardware name: gti cellular cpe board (DT)
[    0.707218] Call trace:
[    0.709736]  dump_backtrace+0x0/0x130
[    0.713492]  show_stack+0x14/0x20
[    0.716898]  dump_stack+0xb4/0x11c
[    0.720392]  panic+0x158/0x324
[    0.723530]  mount_block_root+0x1cc/0x284
[    0.727648]  mount_root+0x11c/0x150
[    0.731232]  prepare_namespace+0x15c/0x1c0
[    0.735444]  kernel_init_freeable+0x210/0x23c
[    0.739925]  kernel_init+0x10/0x100
[    0.743509]  ret_from_fork+0x10/0x24
[    0.747186] SMP: stopping secondary CPUs
[    0.751216] Kernel Offset: disabled
[    0.754797] CPU features: 0x0002,2000200c
[    0.758917] Memory Limit: none
[    0.762061] ---[ end Kernel panic - not syncing: VFS: \
                    Unable to mount root fs on unknown-block(0,0) ]---

It appears to be attempting to mount the root device instead of loading the initrd. After reading around the code, the initrd is supposed to be passed to the kernel using the linux,initrd-start and linux,initrd-end nodes in the device tree. u-boot should set these nodes automatically when using sysboot.

Let's get some more information. After rebuilding the image with the quiet kernel command-line removed (full boot log here):

[    0.517697] Trying to unpack rootfs image as initramfs...
[    0.523339] rootfs image is not initramfs (invalid magic at start \
                   of compressed archive); looks like an initrd
[    0.567843] Freeing initrd memory: 13020K

This is interesting: my initramfs is ~13 MB, so the device tree nodes are getting set correctly. But for some reason, the magic is incorrect and the kernel doesn't know how to unpack it. Somehow, the kernel doesn't recognize the gzip header and is skipping decompression.

The magic bytes look correct on the archive in my store, so maybe the kernel is loading it wrong? After adding some debug prints to the kernel (and re-flashing):

[    0.523092] initrd at 3e96f000 (13336045)
[    0.527213] Compressed data magic: 0x70 0xb4
[    0.531600] Detected (null) compressed data

Odd since hexdump /gnu/store/...-raw-initrd/initrd.cpio.gz | head -n1 shows:

0000000 8b1f 0008 0000 0000 0302 5aec 707d e51b

Which is the correct gzip header.

The astute reader may have noticed the problem already. We're loading kernel into 0x7000000 and initramfs into 0x8000000, but the kernel is 18MB—it will overwrite the first part of the initramfs.

Let's fix that:

Marvell>> setenv fdtfile marvell/armada-3720-ccpe.dtb
Marvell>> setenv ramdisk_addr_r 0x9000000
Marvell>> sysboot mmc 0:1 any 0x5400000 /boot/extlinux/extlinux.conf
Retrieving file: /boot/extlinux/extlinux.conf
790 bytes read in 15 ms (50.8 KiB/s)
Ignoring unknown command: UI
GNU Guix Boot Options
1:  GNU with Linux-Espressobin-Ultra 5.4.53-gti
Enter choice: 1
1:  GNU with Linux-Espressobin-Ultra 5.4.53-gti
Retrieving file: /gnu/store/...-raw-initrd/initrd.cpio.gz
13341022 bytes read in 2243 ms (5.7 MiB/s)
Retrieving file: /gnu/store/...-linux-espressobin-ultra-5.4.53-gti/Image
18061824 bytes read in 16666 ms (1 MiB/s)
append: root=38af4c98-7edc-4925-d4b2-17f038af4c98 \
    gnu.system=/gnu/store/...-system \
    gnu.load=/gnu/store/...-system/boot \
    console=ttyMV0,115200 modprobe.blacklist=usbmouse,usbkbd quiet
Retrieving file: /gnu/store/...-linux-espressobin-ultra-5.4.53-gti/ \
    lib/dtbs/marvell/armada-3720-ccpe.dtb
12427 bytes read in 1699 ms (6.8 KiB/s)
## Flattened Device Tree blob at 06f00000
   Booting using the fdt blob at 0x6f00000
   Loading Ramdisk to 3e96e000, end 3f62715e ... OK
   Using Device Tree in place at 0000000006f00000, end 0000000006f0608a

Starting kernel ...

[    1.045516] orion-mdio d0032004.mdio: IRQ index 0 not found
GC Warning: pthread_getattr_np or pthread_attr_getstack failed for main thread
GC Warning: Could not open /proc/stat
Welcome, this is GNU's early boot Guile.
Use 'gnu.repl' for an initrd REPL.

loading kernel modules...
Guix_image: clean, 44113/107520 files, 364895/429342 blocks
loading '/gnu/store/...-system/boot'...
making '/gnu/store/...-system' the current system...
setting up setuid programs in '/run/setuid-programs'...
populating /etc from /gnu/store/...-etc...
[    2.852542] sd 1:0:0:0: [sda] No Caching mode page found
[    2.855506] sd 1:0:0:0: [sda] Assuming drive cache: write through
Please wait while gathering entropy to generate the key pair;
this may take time...
[    7.615962] udevd[134]: no sender credentials received, message ignored


This is the GNU system.  Welcome.
ebinx login:

Success!

finishing touches

Two more things to fix:

  1. Use the full disk for the rootfs.

  2. Make the boot changes permanent.

Let's start by building a new image:

$ guix system image --target=aarch64-linux-gnu ebinx.scm --image-size=7818182656

And then trying to flash:

...
Marvell>> gzwrite mmc 0 0x5400000 1868327C

MMC: block number 0xe90800 exceeds max(0xe90000)

    uncompressed 7819231232 of 3524263936
    crcs == 0xe484ca13/0xe484ca13

The image is a little bit too large, I probably computed the block count incorrectly. Regardless, nothing important is at the end of the image, so I'll just ignore that.

Making the changes permanent:

Marvell>> setenv fdtfile marvell/armada-3720-ccpe.dtb
Marvell>> setenv ramdisk_addr_r 0x9000000
Marvell>> setenv bootcmd sysboot mmc 0:1 any 0x5400000 /boot/extlinux/extlinux.conf
Marvell>> saveenv
Saving Environment to SPI Flash... SF: Detected mx25u3235f with \
    page size 256 Bytes, erase size 64 KiB, total 4 MiB
Erasing SPI flash...Writing to SPI flash...done
OK

Then power cycle. It now boots automatically into guix and the disk size is correct:

root@ebinx ~# lsblk
NAME         MAJ:MIN RM  SIZE RO TYPE MOUNTPOINTS
sda            8:0    1 14.9G  0 disk
└─sda1         8:1    1 14.9G  0 part
mtdblock0     31:0    0  3.9M  0 disk
mtdblock1     31:1    0   64K  1 disk
mtdblock2     31:2    0   64K  0 disk
mmcblk0      179:0    0  7.3G  0 disk
└─mmcblk0p1  179:1    0  7.3G  0 part /gnu/store
                                      /
mmcblk0boot0 179:32   0    4M  1 disk
mmcblk0boot1 179:64   0    4M  1 disk