December Adventure 2025

I learned about December Adventure in the middle of December last year, which is probably quite typical. I don't think most people get the words out in sync with the hacking and so there is a delay. Obviously that didn't exclude me, it wasn't the spirit of the event after all, but I didn't have the capacity.

I thought I would make an attempt at December Adventure this year. I've been trying hard for a while to focus on about 1 thing at a time. I thought for DA this year I would try and make progress on this summers project: Port FreeBSD to the Allwinner H616.

This blogpost will update as I do stuff through December, so don't expect a real feed of updates. I want to keep the entry effort down to a minimum so most will be more running chain of thoughts rather than fully explained post. If you have questions send me an email and I'll add more detail until it makes sense. I am going to buck tradition and keep it in chronological order too.

Background

I was super intrigued by the Sipeed Nano Cluster when it was annouced and redacted video .

The Nano Cluster is a carrier board for up to 7 modules, with an integrated switch and network port. It is about the size of a small beer and fits in your hand. Sipeed designed 3 different modules that fit the form factor, a CM4/5 adapter, some AI SOC and a Allwinner H618 module based on the Orange Pi Zero3.

The H618 was the cheapest module on offer and Allwinner have a pretty good reputation of reasonable levels of hardware openness.

I ordered a cluster with two H618 modules to begin with, but the Sipeed project timeline wasn't very concrete. I wasn't really sure if these would ship in days or months and as it was June and I wanted a summer project I also went to Aliexpress and bought two different Android tv devices which advertised themselves as using the H616. Delivery times for me for Aliexpress stuff is somewhere between 1 day and 1 month so I thought I'd have a target to get started with.

You may have noticed me jumping between using H618 and H616, well that is because I couldn't keep the numbers straight in my head at all. In the end it turned out that the H616, H616 and H700 are all very similar and need the same hardware support. The H700 is actually in the Anbernic gaming device I got last year.

Anyway, with hardware bought and on its way to my home in Scotland I had to leave the country for the next three months on very short notice.

Summer 2025

Separated from hardware I was able to do some of the inital work on a port. I acquired the Nano Cluster files from Sipeed, but didn't look at them. I went digging into the first thing you need for an embedded port and acquired a device tree (source) file for the h616.

From there I was able to make a table mapping out which device tree nodes will be support by which existing drvier in FreeBSD. A lot of the time support is a matter for adding a compat string to a table and maybe some supporting data from the datasheet.

I got to the point where I had a table like this:

dts file: sun50i-h616.dtsi

mmio-sram
snps,dwmac-mdio
sun50i-h616-ccu                 per soc
sun50i-h616-crypto
sun50i-h616-dma                 a10_dma, a31_dmac (h3 config)
sun50i-h616-ehci
sun50i-h616-emac0               if_awg                  test
sun50i-h616-emmc                aw_mmc                  test
sun50i-h616-gpadc
sun50i-h616-i2c
sun50i-h616-iommu
sun50i-h616-ir                  aw_cir                  test
sun50i-h616-lradc
sun50i-h616-mmc                 aw_mmc                  test
sun50i-h616-musb
sun50i-h616-nmi                 aw_nmi                  not sure what this needs isn't in datasheet
sun50i-h616-ohci
sun50i-h616-pinctrl             aw_gpio
sun50i-h616-r-ccu
sun50i-h616-r-pinctrl
sun50i-h616-rsb                 aw_rsb                  needs config values
sun50i-h616-rtc                 aw_rtc                  need to check h3 table is correct
sun50i-h616-sid                 aw_sid                  needs config table (check linux)
sun50i-a64-sid
sun50i-h616-spdif               h3_padconf (and others)
sun50i-h616-spi
sun50i-h616-system-control      aw_syscon
sun50i-h616-ths                 aw_thermal              needs config table
sun50i-h616-usb-phy             aw_usbphy               needs config table
sun50i-h616-wdt                 aw_wdog                 needs config values
arm,cortex-a53
arm,gic-400
cache
snps,dw-apb-uart
arm,armv8-timer
arm,cortex-a53-pmu
arm,psci-0.2
fixed-clock
simple-bus

and the supporting patches for the changes I had made. This was a good to do list.

Then rather than getting to a point where I had builds, but no device I swapped summer project from "write some code" to read 15 books. This was cathartic and it finally settled a question from the last time I had a block of time off - yes reading a lot of books is good, but it doesn't leave you full, you have to do other things.

After Summer

Once I finally made it home I unpacked the hardware, took the android boxes apart and photographed their insides (I'll add these pictures soon). The two devices were basically identical, they have a cool led panel on the front driven by gpio showing wifi status and disk access.

One is called a "H96 Max" or H96 for any files I dump from it, it has a blue PCB and the other is T95 Pro and it has a black PCB. Both boards have a pin header on the board by the SD card slot labeled with RX, TX, GND.

I connected PCB Bite probes to each and booted them up. They are both running android and drop you to a shell once they have booted. The H96 gave me a root shell, whereas the T95 gave me a user shell, so the H96 is going to get all the initial attention.

The H96 is very noisy once it has booted, I resolved [this by changing a logging sysctl] (https://superuser.com/questions/351387/how-to-stop-kernel-messages-from-flooding-my-console):

    sysctl -w kernel.printk="3 4 1 3"

I wasn't able to break into uboot on either device, as far as I can tell from reading the uboot sources, you should be able to break into its prompt if it shows you "Hit any key to stop autoboot", but I couldn't manage.

With root on the system I instead dumped out uboot configuration from the emmc and used set to rewrite the autoboot delay field from 0 to 10. This didn't work, but it didn't work enough to stop the system booting, leaving me at a uboot prompt (so a win I guess?).

I saved a copy of the strings output of the uboot binary image I dumped, but in a fat fingered moment I overwrote the good original uboot config.

So that left me with a device I could boot and could access the uboot prompt on. That is more than enough to do a port from.

November Warm up

I realised December Adventure was coming and I killed the power supply in my other evening project really knocking my enthusiasm down a bit.

I set out to get a working development set up for the H96 before December started so I wouldn't be too bogged down.

I set up a usb serial adapter hanging off uart I soldered a header to. I added in an sdwire sd-mux board which allows you to share an sd card between a DUT and as TS. Finally to enable remote working on the device I added a pi pico running micropython and a relay to control power, giving me a remote off switch. This is really handy when you need to reboot a system from cold power on.

At some point I pulled a FDT off the board with something like:

# cat /sys/firmware/devicetree | nc host port

With that in place I then got the Sipeed sources building as a close enough initial target and copied out that uboot onto a PINE64-LTS FreeBSD 16 image.

FreeBSD provides aarch64 images, but Arm platforms are still a mess in the DTS world and all require boot firmwares in different places. I checked through all the build configs ( src/release/arm64/*config ) and verified that the PINE64-LTS image has enough space before the first data partition to fit the Allwinner uboot.

With all that background we can now move into the December Adventure log:

20251129

Image I built yesterday onto the freebsd pine64-lts image didn't boot. It is probably because I didn't set up the mdconfig image with sectors.

I tried copying to the sd card directly like so:

 sudo dd if=build/u-boot-sunxi-with-spl.bin of=/dev/da3 bs=1024 seek=8 conv=sync

And managed to hit a useful error:

U-Boot SPL 2024.01-rc2-00076-g94b814f631e (Nov 28 2025 - 16:42:46 +0000)
DRAM:This DRAM setup is currently not supported.                        

resetting ...

I thought that maybe the h616 was like a NXP platform and that firmware specifics set the DRAM size and I spent a bit of time looking at ways the size might be configured. There isn't anything that responsible in the working dts, the memory section is commented out and the extracted dts doesn't indicate a range.

The extracted DTS doesn't build, which is its own issue for later.

I eventually grepped in the u-boot sources.

This hits a pretty unique bit of u-boot for this platform, which I have no idea what is needed to resolve.

For each type of memory there is a possible selection of bus widths and ranks. I'm not sure what ranks here means, so it is time to go to the datasheet.

The datasheet section on SDRAM is 1 page of bullet points.

There is a set of registers (20k) for DRAM_CTRL

If the datasheet is no help I think I need to enable debug prints from u-boot. afsaf debug is defined in log.h and is enabled if DEBUG is defined in a file.

20251130

There is basically nothing in the user manual about the usage of the DRAM controller. This ties up really well with a comment or commit message in u-boot where the author says most values just come from the boot0 logs.

So now to enable debug on uboot and start littering the sun50iw9 paths with prints to see what actually happens before this DRAM error.

I am going to set the device tree back to the longan pi 3 one for test builds.

sudo sd-mux-ctrl --ts -e da12 
sudo sd-mux-ctrl --dut -e da12

The first u-boot path came from the longan build scripts, this dd pulls u-boot from the actual build dir.

    sudo dd if=build/uboot/u-boot-sunxi-with-spl.bin of=/dev/da3 bs=1024 seek=8 conv=sync

The value to define to get debug_ printfs is _DEBUG

/* Show a message if DEBUG is defined in a file */
#define debug(fmt, args...)                     \
        debug_cond(_DEBUG, fmt, ##args)

That gets us:

U-Boot SPL 2024.01-rc2-00076-g94b814f631e-dirty (Nov 30 2025 - 09:44:30 +0000)
DRAM:testing 32-bit width, rank = 2
read calibration failed!
testing 32-bit width, rank = 1
read calibration failed!
testing 16-bit width, rank = 2
read calibration failed!
testing 16-bit width, rank = 1
read calibration failed!
This DRAM setup is currently not supported.

resetting ...

More debug prints indicate that u-boot things this is lpddr4, but the boot0 log has:

[94]DRAM_VCC set to 1500 mv
[97]DRAM CLK =648 MHZ
[99]DRAM Type =3 (3:DDR3,4:DDR4,7:LPDDR3,8:LPDDR4)
[107]Actual DRAM SIZE =4096 M
[110]DRAM SIZE =4096 MBytes, para1 = 310b, para2 = 10000000, dram_tpr13 = 6041
[123]DRAM simple test OK.

So maybe this error is due to detecting the wrong ddr type somewhere.

Our copied u-boot defconfig has:

CONFIG_MACH_SUN50I_H616=y
# CONFIG_RESERVE_ALLWINNER_BOOT0_HEADER is not set
CONFIG_ARM_BOOT_HOOK_RMR=y
CONFIG_SUNXI_DRAM_LPDDR4=y

There isn't an unset version for ddr3, inventing one in the config breaks the build so there is some digging to do. The ddr3 config that uboot ships has the memory speed at 1333MHz, boot0 indicates the memory speed is much lower, but maybe we can just hacking this to work?

For some reason the uboot build is failing, but without stoping the makefile, which is pretty annoying. The real result is a lack of an output binary in the u-boot directory.

building u-boot constantly:

gmake clean; gmake sun50iw9-h616-h96_defconfig
gmake -j 16

Progress to a hang:

U-Boot SPL 2024.01-rc2-00076-g94b814f631e-dirty (Nov 30 2025 - 10:32:46 +0000)
DRAM:testing 32-bit width, rank = 2
mctl_phy_init:885 DDR3
mctl_phy_init:1114 READ CALIBRATION
read calibration failed!
testing 32-bit width, rank = 1
mctl_phy_init:885 DDR3
mctl_phy_init:1114 READ CALIBRATION
mctl_phy_init:885 DDR3
mctl_phy_init:1114 READ CALIBRATION
mctl_phy_init:885 DDR3
mctl_phy_init:1114 READ CALIBRATION
mctl_phy_init:885 DDR3
mctl_phy_init:1114 READ CALIBRATION
MBUS port 0 cfg0 0100000d cfg1 00640080
MBUS port 1 cfg0 06000009 cfg1 01000578
MBUS port 2 cfg0 0200000d cfg1 00600100
MBUS port 3 cfg0 01000009 cfg1 00500064
MBUS port 4 cfg0 20000209 cfg1 1388157c
MBUS port 5 cfg0 00640209 cfg1 00200040
MBUS port 6 cfg0 00640209 cfg1 00200040
MBUS port 8 cfg0 01000009 cfg1 00400080
MBUS port 11 cfg0 01000009 cfg1 00640080
MBUS port 14 cfg0 04000009 cfg1 00400100
MBUS port 16 cfg0 2000060d cfg1 09600af0
MBUS port 21 cfg0 0800060d cfg1 02000300
MBUS port 25 cfg0 0064000d cfg1 00200040
MBUS port 26 cfg0 20000209 cfg1 1388157c
MBUS port 37 cfg0 01000009 cfg1 00400080
MBUS port 38 cfg0 00640209 cfg1 00200040
MBUS port 39 cfg0 20000209 cfg1 1388157c
MBUS port 40 cfg0 00640209 cfg1 00200040
 4096 MiB

The ddr3 config option carrying a speed is pretty annoying. It is only considered in two places, one sets the type and the other is blob:

 static const u8 phy_init[] = {                         
 #ifdef CONFIG_SUNXI_DRAM_H616_DDR3_1333                
         0x07, 0x0b, 0x02, 0x16, 0x0d, 0x0e, 0x14, 0x19,
         0x0a, 0x15, 0x03, 0x13, 0x04, 0x0c, 0x10, 0x06,
         0x0f, 0x11, 0x1a, 0x01, 0x12, 0x17, 0x00, 0x08,
         0x09, 0x05, 0x18                               
 #elif defined(CONFIG_SUNXI_DRAM_H616_LPDDR3)           
         0x18, 0x06, 0x00, 0x05, 0x04, 0x03, 0x09, 0x02,
         0x08, 0x01, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
         0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x07,
         0x17, 0x19, 0x1a                               
 #elif defined(CONFIG_SUNXI_DRAM_H616_LPDDR4)           
         0x02, 0x00, 0x17, 0x05, 0x04, 0x19, 0x06, 0x07,
         0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
         0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x01,
         0x18, 0x03, 0x1a                               
 #endif                                                 
 };

With no documentation, I'm not really sure what to do to get a compatible u-boot built. I can getting hold of the stock u-boot the board shipped with, either by chasing down whatever might be on the aliexpress listing from 6 months ago (lol), or by dumping u-boot from android and having a look at a disassembly.

I checked mount in android to find somewhere writable, this was /data/media

dd if=/dev/block/mmcblk0 of=h96rawdisk1M.img bs=1M count=1

and then I configured a static address to a test machine and used nc to copy the uboot dump off the device.

Then I dropped the first 8k of the disk to get a uboot blob:

dd if=h96rawdisk1M.img of=h96uboot.img bs=1024 iseek=8 conv=sync

this might not actually be all of uboot, but whatever.

Honestly, this is pushing what I can do with hardware re. I'm just not able to eyeball instructions out of an aarch64 hexdump yet.

I should switch to booting FreeBSD from an sd card using the vendor uboot, but maybe I could poke at this using radare2.

I mean, I might just need a series of writes to PHYS_CTRL 0x0480000 , surely that shouldn't be too hard to pull from uboot?

I spent the evening reading the radar2 book and looking up RE projects on uboot. While I was doing this the sunxi wiki was down, but later in the day it came back up.

The boot wiki page informated me that DRAM parameters are set between the board vendor and Allwinner using special tools, but they are carried as a configuration file at the start of the SPL boot loader.

That explains the DRAM.ext file in the uboot blob I extracted and gives me a final (I promise) thing to try before paying attention to the port again.

20251201

I wrote up all my existing notes and added 1800 words - which hasn't really matched the "make entries easy" goal.

Installed sunxi-tools.

From yesterdays last minute discovery that there was tooling to help on the wiki I read more of the wiki pages on early boot.

The boot0 page includes a header for the boot0/spl, this is helpful for looking at the dump I took, even if I don't really need it.

    Offset  Name    Size    Notes
    0x00    B_INS   4       Branch instruction to Code Starting Point
    0x04    Magic   8       Ascii string "eGON.BT0" (No Null-terminated )
    0x0c    Checksum        4       Simple 4-bytes Checksum (Before calculate checksum this must be 0x5F0A6C39 )
    0x10    Size    4       Size of Boot0, it's must be 8-KiB aligned in NAND and 512-Bytes aligned in MMC
    0x14    Code    -       Code of SPL. The size depends on the processor and if it 's loaded from SPI, NAND or MMC

The DRAM settings sunxi wiki page has a link for getting parameters from boot0 https://linux-sunxi.org/U-Boot#DRAM_Settings, this is using 'sunxi-fw' which isn't in sunxi-tools on freebsd.

A little Makefile hacking later:

    diff --git a/Makefile b/Makefile
    index 8c16c01..23fe451 100644
    --- a/Makefile
    +++ b/Makefile
    @@ -1,6 +1,6 @@
     SH=/bin/sh
    -CC=${CROSS_COMPILE}gcc
    -CFLAGS=-Wall -g -O
    +CC=${CROSS_COMPILE}cc
    +CFLAGS=-Wall -g -O -I/usr/include -I/usr/local/include
     PREFIX ?=/usr/local
     all: sunxi-fw

And I had a working tool. Pointing it at my extracted uboot bits from the h96 showed something:

    $ ./sunxi-fw/sunxi-fw info -a h96rawdisk1M.img
    @   0: mbr: DOS MBR
            protective MBR, GPT used
            GPT version 00010000
            usable disk size: 29783 MB
            number of partition entries: 17
    @  16: boot0: Allwinner boot0
    @ 512: boot0: Allwinner boot0

but it wasn't great compared to the uboot I built myself:

    $ ./sunxi-fw/sunxi-fw info -a ../LonganPi-3H-SDK/build/uboot/u-boot-sunxi-with-spl.bin 
    @   0: spl: U-Boot SPLv2
            DT: sun50i-h618-longanpi-3h
    @  64: fit: U-Boot FIT image
            fit:uboot: "U-Boot (64-bit)"
            fit:atf: "ARM Trusted Firmware"
            fit:fdt-1: "sun50i-h618-longanpi-3h"
            configuration: sun50i-h618-longanpi-3h

The eGON header is there in the dump, so I am not really sure what is wrong. Lets park that for now. The sdmux is painfully slow to dd a full image to, so last night as a last thing I left the computer copying over a fresh PINE64-LTS image, which shouldn't be able to boot at all.

Lets try and boot a kernel from the vendor uboot using the vendor uboot.

20251202

If I am going to use the vendor uboot then I can start working on getting a kernel booting at all from uboot. I have done this a ton of times on different boards and so I tried to track down an example command.

The best I could do was this:

fatload mmc 1:1 0x48000000 dtb/starfive/jh7110-visionfive-v2.dtb
fatload mmc 1:1 0x44000000 efi/boot/bootriscv64.efi
bootefi 0x44000000 0x48000000

fdt_addr_r=0x51ff8000
kernel_addr_r=0x50200000

fatload mmc 0:1 0x51ff8000 dtb/bl808-pine64-ox64.dtb
fatload mmc 0:1 0x50200000 efi/boot/bootriscv64.efi
bootefi 0x50200000 0x51ff8000

from my (unpublished) artilce on running FreeBSD on the Pine Ox64 riscv SBC.

It is all pretty straight forwards until we hit the bootefi command. I doubt the uboot on the h96 has this. There are other options to boot a loader or kernel, I'd prefer to use a efi loader if I can.

I aimed to do more in the evening at the hackerspace, but a stop off at Aldi on the way and forgetting a usb-c cable for my ridiculous setup stopped that.

20251202

If I am going to use the vendor uboot then I can start working on getting a kernel booting at all from uboot. I have done this a ton of times on different boards and so I tried to track down an example command.

The best I could do was this:

    fatload mmc 1:1 0x48000000 dtb/starfive/jh7110-visionfive-v2.dtb
    fatload mmc 1:1 0x44000000 efi/boot/bootriscv64.efi
    bootefi 0x44000000 0x48000000

    fdt_addr_r=0x51ff8000
    kernel_addr_r=0x50200000

    fatload mmc 0:1 0x51ff8000 dtb/bl808-pine64-ox64.dtb
    fatload mmc 0:1 0x50200000 efi/boot/bootriscv64.efi
    bootefi 0x50200000 0x51ff8000

from my (unpublished) artilce on running FreeBSD on the Pine Ox64 riscv SBC.

It is all pretty straight forwards until we hit the bootefi command. I doubt the uboot on the h96 has this. There are other options to boot a loader or kernel, I'd prefer to use a efi loader if I can.

I aimed to do more in the evening at the hackerspace, but a stop off at Aldi on the way and forgetting a usb-c cable for my ridiculous setup stopped that.

20251203

Lets take a dump of the available uboot commands on the h96:

    Hit any key to stop autoboot:  0
    => help
    ?       - alias for 'help'
    base    - print or set address offset
    bdinfo  - print Board Info structure
    boot    - boot default, i.e., run 'bootcmd'
    bootd   - boot default, i.e., run 'bootcmd'
    bootm   - boot application image from memory
    bootp   - boot image via network using BOOTP/TFTP protocol
    cmp     - memory compare
    colorbar- show colorbar
    coninfo - print console devices and information
    cp      - memory copy
    crc32   - checksum calculation
    echo    - echo args to console
    editenv - edit environment variable
    efex    - run to efex
    env     - environment handling commands
    erase   - erase FLASH memory
    fastboot- fastboot - enter USB Fastboot protocol
    fatinfo - print information about filesystem
    fatload - load binary file from a dos filesystem
    fatls   - list files in a directory (default /)
    fatsize - determine a file's size
    fatwrite- write file into a dos filesystem
    fdt     - flattened device tree utility commands
    flinfo  - print FLASH memory information
    go      - start application at address 'addr'
    gpt     - GUID Partition Table
    help    - print command description/usage
    i2c     - I2C sub-system
    itest   - return true/false on integer compare
    loadb   - load binary file over serial line (kermit mode)
    loads   - load S-Record file over serial line
    loadx   - load binary file over serial line (xmodem mode)
    loady   - load binary file over serial line (ymodem mode)
    logo    - show default logo
    loop    - infinite loop on address range
    md      - memory display
    memtester- start application at address 'addr'
    mm      - memory modify (auto-incrementing address)
    mmc     - MMC sub system
    mmcinfo - display MMC info
    mw      - memory write (fill)
    nfs     - boot image via network using NFS protocol
    nm      - memory modify (constant address)
    pbread  - read data from private data
    poweroff- Perform POWEROFF of the device
    printenv- print environment variables
    protect - enable or disable FLASH write protection
    pst     - read data from secure storageerase flag in secure storage
    reset   - Perform RESET of the CPU
    run     - run commands in an environment variable
    saveenv - save environment variables to persistent storage
    screen_char- show default screen chars
    setenv  - set environment variables
    setexpr - set environment variable as the result of eval expression
    sleep   - delay execution for some time
    source  - run script from memory
    sprite_test- do a sprite test
    sunxi_axp- sunxi_axp sub-system
    sunxi_bmp_info- manipulate BMP image data
    sunxi_bmp_show- manipulate BMP image data
    sunxi_card0_probe- probe sunxi card0 device
    sunxi_flash- sunxi_flash sub-system
    sunxi_nand_test- sunxi_nand_test sub systerm
    sunxi_so- sunxi_so sub-system
    tftpboot- boot image via network using TFTP protocol
    timer_test- do a timer and int test
    timer_test1- do a timer and int test
    uburn   - do a burn from boot
    version - print monitor, compiler and linker version

There are some new sunxi_ commands there, but nothing for usb. Try as I might I can't get uboot to pick up the sd card I have inserted. Trying a USB stick gives me:

    [00.796]usb prepare ok
    [01.599]overtime
    [01.603]do_burn_from_boot usb : no usb exist
    [01.607]boot_gui_init:start
    FAT: Misaligned buffer address (bbe78ad8)
    32 bytes read in 4 ms (7.8 KiB/s)
    tcon_de_attach:de=0,tcon=2[01.891]boot_gui_init:finish
    [01.895]bmp_name=bootlogo.bmp

Maybe the other port will work, but the cat is insisting that I remain seated. There is a lack of a usb command in the help output. Also missing from this uboot is an fel command to drop back into the default loader.

At this point I might have hit enough walls trying to get this board to boot and should probably try something else. Not being able to get the dram parameters despite seemingly having all the right tools is frustrating.

I had a look again at the cluster boards and they seem like much more annoying targets for doing bring up. A nice thing about this random h96 thing is that I am already controlling it with a relay and can reflash the sd card remotely, it just doesn't work. Maybe I can get enough ddr3 parameters together to make progress.

I don't want to give up yet. Looking at my list of commands and I noticed the fdt command. Running fdt print generated a 6000 line output file!

This seems to include the same parameters I could get with the sunxi-fw tool, but I'm not sure if this maps to the magic bytes I need to configure for the phy.

Thinking about this more while brushing my teeth and I really might only need to know the phy init sequence. This feels like a great chance to try using radare2 on a target. I have a clear goal, get the writes to a certain address, and a lot of supporting facts already, register map and many common values.

20251204

Time to hit the book . There is a handy firmware section of the radare2 book and it helpfully tells you to not bother with the project support.

The reason for not using projects is because usually these targets
require some special setups, custom scripts, manual tries and errors
and obviously not using the default autoanalysis.

The firmware section shows initial set up and some tricks, but it is probably a requirement to read more of the book to know what is happening and what to do next.

I need to both learn radare2 and some more facts about the soc and where it places things early in boot.

We know what upstream uboot does to set up dram, the code leading to the phy_init copy is:

writel(val, SUNXI_DRAM_PHY0_BASE + 0x14);                
writel(val, SUNXI_DRAM_PHY0_BASE + 0x35c);               
writel(val, SUNXI_DRAM_PHY0_BASE + 0x368);               
writel(val, SUNXI_DRAM_PHY0_BASE + 0x374);               

writel(0, SUNXI_DRAM_PHY0_BASE + 0x18);                  
writel(0, SUNXI_DRAM_PHY0_BASE + 0x360);                 
writel(0, SUNXI_DRAM_PHY0_BASE + 0x36c);                 
writel(0, SUNXI_DRAM_PHY0_BASE + 0x378);                 

writel(val2, SUNXI_DRAM_PHY0_BASE + 0x1c);               
writel(val2, SUNXI_DRAM_PHY0_BASE + 0x364);              
writel(val2, SUNXI_DRAM_PHY0_BASE + 0x370);              
writel(val2, SUNXI_DRAM_PHY0_BASE + 0x37c);              

ptr = (u32 *)(SUNXI_DRAM_PHY0_BASE + 0xc0);              
for (i = 0; i < ARRAY_SIZE(phy_init); i++)               
    writel(phy_init[i], &ptr[i]);                    

if (para->tpr10 & TPR10_CA_BIT_DELAY)                    
    mctl_phy_ca_bit_delay_compensation(para, config);

We have these constants from uboot and the base address matches up with the datasheet.

#ifdef CONFIG_MACH_SUN50I_H616
#define SUNXI_DRAM_COM_BASE             0x047FA000
#define SUNXI_DRAM_CTL0_BASE            0x047FB000
#define SUNXI_DRAM_PHY0_BASE            0x04800000
#endif

From the table above we know the first 4 bytes should be a branch from the boot0 header to code. If I swap the r2 mode from 64 bits to 32 (though this feels like a proble of its own, certainly it indicates a knowledge gap), when we get some sensible disassembly for the first instruction.

 0x08000000      be0400ea       b 0x8001300                 ; pc=0x8001300 -> 0xeaffffff

Lots of questions from these first steps:

  • how does aarch64 boot?
    • is it in 32bit mode
  • how to search for addresses in assembly in radare2
  • how can I get up to speed on aarch64 assembly quickly?

The book isn't really any help, it is a programming book rather than an architecture or systems reference. It is remarkably difficult to find aarch64 instruction encoding information, but wikipedia at least says:

Instructions are still 32 bits long and mostly the same as A32 (with
LDM/STM instructions and most conditional execution dropped)

I don't think the processor has started in 32 bit mode. Instead r2 is having trouble with that first branch.

The header is:

00000000  be 04 00 ea 65 47 4f 4e  2e 42 54 30 bf 3a 40 9d  |....eGON.BT0.:@.|
00000010  00 00 01 00 30 00 00 00  00 00 00 00 00 00 02 00  |....0...........|
00000020  00 00 02 00 00 00 00 00  00 00 00 00 34 2e 30 00  |............4.0.|
00000030  00 00 00 00 03 00 00 00  88 02 00 00 03 00 00 00  |................|

so the first instruction is:

be 04 00 ea

[0x08000000]> e asm.arch=arm
[0x08000000]> e asm.cpu=v8
[0x08000000]> e asm.bits=64
[0x08000000]> pd 1
        0x08000000      be0400ea       ands x30, x5, x0, lsl 1     ; lr=0x0 ; zf=0x1 ; nf=0x0 ; cf=0x0 ; vf=0x0
[0x08000000]> e asm.bits=32                                                                                        
[0x08000000]> pd 1
    ┌─< 0x08000000      be0400ea       b 0x8001300                 ; pc=0x8001300 -> 0xeaffffff

So one of these is doing something that makes sense and the other isn't. I find this so confusing that I tried using capstone, which I think underlies radare2 for disassembly manually:

$ cstool arm64 be0400ea        
 0  be 04 00 ea  ands   x30, x5, x0, lsl #1

when that didn't give me the answer I wanted I tried some online disassemblers, but they all gave me the same result. This mystery will persist until I can find someone to ask what is going on.

So, lets say the first opcode is an immediate jump to #1300, which makes sense. How do I look through the rest of this binary for my addresses of interest using r2?

It seems that r2asm can't disassemble from a file:

$ rasm2 -a arm -b 32 -f h96uboot.img   
ERROR: Cannot assemble '' at line 1                              
$ rasm2 -a arm -b 32 -D -f h96uboot.img
WARN: Invalid hexpair string

And neither can cstool, but it can give you detailed info on an instruction:

$ cstool -d arm be0400ea
 0  be 04 00 ea  b      #0x1300
    ID: 11 (b)
    op_count: 1
        operands[0].type: IMM = 0x1300
    Registers read: pc
    Registers modified: pc
    Groups: branch_relative arm jump

And rasm2 can tell you what a pneumonic means:

$ rasm2 -a arm -b 64 -w b                       
branches the program counter to dst (pc aka r15)

So, lets pretend everything is fine and just continue in 32 bit mode for today.

I next need to ask people some questions about Allwinner SOC start up and figure out how to search for accessed addresses in r2.

I did a little more reading after shutting down the computers for the night and found some uboot documentation which is pretty clear about the A64 start up process:

Newer Allwinner SoCs feature ARMv8 cores (ARM Cortex-A53) with support for
both the 64-bit AArch64 mode and the ARMv7 compatible 32-bit AArch32 mode.
Examples are the Allwinner A64 (used for instance on the Pine64 board) or
the Allwinner H5 SoC (as used on the OrangePi PC 2).

These SoCs are wired to start in AArch32 mode on reset and execute 32-bit
code from the Boot ROM (BROM). As this has some implications on U-Boot, this
file describes how to make full use of the 64-bit capabilities.

That explains exactly what I am seeing. Next I need to figure out how the transition to 64-bit mode happens and identify that in the disassembly. I'm not aware of any debugging tools that handle mixed mode executables well, most choke on the entire notion of the instruction set changing.

FreeBSD with uefi on qemu amd64

All the information on using uefi with qemu seems to aged out as projects have been abandoned.

On FreeBSD (around 15-beta2) we need to get the uefi edk2 firmware, which is packaged:

# pkg install qemu edk2-qemu-x64

This will provide three images, one of which we need to give to qemu with the bios option.

$ pkg info -l    edk2-qemu-x64
edk2-qemu-x64-g202308_5:
        /usr/local/share/edk2-qemu/QEMU_UEFI-x86_64.fd
        /usr/local/share/edk2-qemu/QEMU_UEFI_CODE-x86_64.fd
        /usr/local/share/edk2-qemu/QEMU_UEFI_VARS-x86_64.fd
        /usr/local/share/licenses/edk2-qemu-x64-g202308_5/BSD3CLAUSE
        /usr/local/share/licenses/edk2-qemu-x64-g202308_5/LICENSE
        /usr/local/share/licenses/edk2-qemu-x64-g202308_5/catalog.mk

I picked QEMU-UEFI-x86_64 which worked.

qemu-system-x86_64 will take a bios to use with the --bios flag (or if you are bored and want to write 16 bit assembly you can give it your own bios )

I spent quite a while debugging a minimal boot flow, I think qemu is stashing nvram somewhere, but I can't for the life of me figure out if it really is or where it might be. In the end I used the efi shell to reconfigure the boot order and I started getting complaints about there not being enough memory with the default qemu flags. I added -m 2048M to get around this.

qemu-system-x86_64 -display none -serial mon:stdio \
    -m 2048M --bios /usr/local/share/edk2-qemu/QEMU_UEFI-x86_64.fd \
    -drive file=fbsd.raw,format=raw,if=virtio

FreeBSD on Dell Latitude 7280

I am trying so hard to not acquire more computers, but the need to test a ton of stuff and have a good reference platform forced my hand. Rather than do any research I took trasz@'s recommended Dell laptop as test target and picked one up from eBay, a Dell Latitude 7280.

It was 65 quid with about a fiver in postage and there are a ton of them. So many that I wasn't super discerning when reading the listing.

The Dell Latitude 7280 is ~12 inch Intel laptop with a 1366x768 display, it has USB ports, will take USB-C power (vital considering how few come with power supplies), HDMI out, Ethernet and a nice surprise a USB smart card reader builtin (I even have some java card smart cards somewhere I could try, I won't, but I could).

I let it boot windows to verify it came up and then started about installing. I couldn't break into the bios with a key press and used the windows "reboot to firmware" hidden menu to get into Dell's firmware.

Out of the box all of the hardware works:

Component works? Notes
Graphics yes drm-kmod i915
WiFI yes Intel 8265NGW with iwm or iwlwifi
Ethernet yes em(4)
Suspend/Resume yes (with default wifi adapter)
Camera probably webcamd wants it - haven't tested
USB yes USB-C data and power work
SD Card slot yes rtsx(4) - micro sd card
Audio yes speakers and headphone jack work
Media Keys yes

The bios doesn't lock which WiFI cards you can use - the main reason for getting this machine was that trasz@ said suspend/resume worked until he used iwx and I needed a platform to debug iwx suspend on.

I had no issue swapping out the builtin WiFI for an iwx card and now I'm portable for testing I could get pretty close to my attic AP and pull/push ~400 Mbit with iwx.

This computer is really well supported, but its a bit of a dog. I didn't read the eBay listing well, it doesn't have the rubber feet, but that's fine I put it on a silicon matt on my desk. The battery is absolutely goosed, the bios lists battery health as 9%, this was in the listing I just, well didn't really care.

apciconf says it should get an hour on battery, but I haven't let it run to nothing. I got a replacement battery so I can use this thing as a test platform for an upcoming GSoC power management project and it being a Dell part meant getting replacement was quite easy.

I wouldn't recommend this exact laptop, maybe a Latitude 7280 with a working battery and feet would be okay. It is slow as hell, building a kernel took 1838 seconds, 30 minutes is a long time for real development work.

The screen resolution is quite low compared to modern 2k displays, but its probably fine for programming tasks and writing. I wouldn't want to do builds on it.

The hardware seems quite nice apart from the rubber surface which seems to be degrading. I don't think I got a representative laptop, if you want a test FreeBSD machine this might be a great choice at well under 100 quid even if you have to replace the battery.

--

Update

Working on the Latitude 7280 is a really nice experience. I have replaced the WiFi and just after publishing this post a new battery arrived. There are 8 captive screws on the bottom which give access to quite a well laid out board. In mine there is space for a wwan modem.

The battery replacement was excellent, a replacement was 25GBP. The battery is held in with 2 extra screws (1 case screw also secures it), the cable has a pull chord from the motherboard connector. The battery side for the cable is a little harder to remove, but no challenge compared to a glued down thing. Sure it is a plastic laptop, but it is light and thin enough. I don't really get Apples excuse for their stuff not being this maintainable.

I have had thinkpads before and they've never been great while they have been expensive. This 2018 computer is much nicer than any thinkpad I had.

With the new battery this looks to be a great machine for sitting in vim:

Design capacity:    7895 mAh
Last full capacity: 7895 mAh
Technology:     secondary (rechargeable)
Design voltage:     7600 mV
Capacity (warn):    789 mAh
Capacity (low):     239 mAh
Low/warn granularity:   78 mAh
Warn/full granularity:  78 mAh
Model number:       DELL DW3WC64
Serial number:      44280
Type:           LION
OEM info:       SMP
State:          discharging
Remaining capacity: 100%
Remaining time:     8:17
Present rate:       953 mA (8124 mW)
Present voltage:    8525 mV

You can find a full dmesg here.

FreeBSD Network Status 2025 Week 05

Here we are 8% of the way through the year and the end of January. Perfect time to check in with what is going on in FreeBSD.

Goings on

BSD Devroom at FOSDEM 2024

I'm writing today above the North Sea on my flight to AMS on the way to Brussels for FOSDEM. The BSD Devroom is running tomorrow (Saturday the 1st of Feb) and there is a great line up of talks. If you are within a reasonable travel distance head over, its a free event and there is a ton of stuff going on.

Beyond the talks there will be a FreeBSD table for the project and the foundation. We have stickers and I'll be there if you want to chat about goings on in the project or to complain about some of the bugs I've written recently.

Stab week

This was the first stab week of the year and two regressions were picked up through repeatided stabbings:

  • Compilation failure on 32-bit platforms. Fix [5289625dfecb] (https://cgit.freebsd.org/src/commit/?id=5289625dfecb)
  • Instant panic with SO_REUSEPORT_LB and nginx. Fix 06bf119f265c

glebius@ is adding scripts to make it easier to know when it is time to stab. I'm hoping more tooling involves and we start getting more testing in the stab weeks. The work Netflix is doing here is incredibly helpful for the quality of FreeBSD, but other workloads should also be represented.

Network Stack

Caught in stab week was a regression in the TCP listen operation when it is called multiple times on a socket. This was harmless generating multiple TCP state changes until last week after a clean up fixing races where started to call LIST_REMOVE() twice on the same entry.

Style and small fixes for netlink and netlink/route support.

ip6addrctl manages address selection policy for outgoing packets. With some improvements teach it how to run in a jail.

Netdev

Replace the single global admin taskqueue with a per interface admin taskqueue. This should resolve timeouts when long operations are performed without using too much more in resources.

Add a device id to ure, otherwise it will use the CDC mode driver. If you have issues with USB Ethernet you might want to look to see if moving from the CDC driver will help.

Wireless

Some further fixes around firmware loading for iwm and its integrated bluetooth controller.

Add support for Blueooth Secure Simple Pairing - which I didn't manage to look up before getting on a plane.

Firewalls

This is the third week of changes adding support for NAT64 with some more changes coming in via OpenBSD.

User Tooling

Netcat is the network swiss army knife, an incredibly useful and flexible tool for doing stuff that requires you to put packets onto the network. It is great exemplar of how to use networking options and used more and more by network tests. There was an issue in this stab period around SO_REUSEPORT_LB , making testing more practical helps catch issues closer to their introduction.

Other stuff

Nice fix from mckusick@ to UFS1 file system helping with the inevitable passage of time.

  • 1111a44301da Defer the January 19, 2038 date limit in UFS1 filesystems to February 7, 2106

    Defer the January 19, 2038 date limit in UFS1 filesystems to February 7, 2106

    UFS1 uses a signed 32-bit value for its times. Zero is January 1, 1970 UTC. Negative values of 32-bit time predate January 1, 1970 back to December 13, 1901. The maximum positive value for 32-bit time is on January 19, 2038 (my 84th birthday). On that date, time will go negative and start registering from December 13, 1901. Note that this issue only affects UFS1 filesystems since UFS2 has 64-bit times. This fix changes UFS1 times from signed to unsigned 32-bit values. With this change it will no longer be possible to represent time from before January 1, 1970, but it will accurately track time until February 7, 2106. Hopefully there will not be any FreeBSD systems using UFS1 still in existence by that time (and by then I will have been dead long enough that no-one will know at whom to yell :-).

    It is possible that some existing UFS1 systems will have set times predating January 1, 1970. With this commit they will appear as later than the current time. This commit checks inode times when they are read into memory and if they are greater than the current time resets them to the current time. By default this reset happens silently, but setting the sysctl vfs.ffs.prttimechgs=1 will cause console messages to be printed whenever a future time is changed.

Please Send Feedback

Smaller report this week. I'm trimming out more "small fixes" style comments. I'm going to play with the format of these posts more over the next few months. I am trying to add value beyond just rewriting commit messages, sometimes it is good to show the continuous on going work, but it will get a bit tedious if that is 60% of the report each week.

I'm giving a FOSDEM talk tomorrow on the writing of these reports.

I would love to know if this summary was any help, if it was, or if you think I should cover other thing please let me know (thj@freebsd.org).

If you find a typo or have a correct let me know and I'll thank you at the end here.

You can see all prior posts here. ( rss )


My work on FreeBSD is supported by the FreeBSD Foundation , you can contribute to improving FreeBSD with code, documentation or financially by donating to the FreeBSD Foundation .

Recovering from external zroot

My favourite terrible computer has been locking up after a VM panics and I need to panic a VM a lot to find a firmware crash. The hard power cycle reboot loop is taking too long and I'm done with it.

More annoyingly is is also hard locking up copying the VM off the machine. Thisb forced a trip to the attic, a reinstall of my large memory host (which for reasons had a 128GB NVMe, which is silly on a machine with 128GB of RAM!).

That is fine, I can pull the NVMe and drop it in an enclosure and just copy locally. For a zfs disk on a machine with an existing zroot this was turned out to be very painful. Somehow no one has written down how to do this either:

Normally zpool import take a name argument, but it can take a pool id. If you are using a machine with zfs on root then you will already have a pool called zroot , so trying to import your pool from an external drive will fail with the name already in use.

Finding the pool id can be done with the -d argument to zpool import . This takes a device to use as a pool.

# zpool import -d /dev/da3p4
  pool: zroot
    id: 647281366119489090
 state: ONLINE
status: Some supported features are not enabled on the pool.
    (Note that they may be intentionally disabled if the
    'compatibility' property is set.)
action: The pool can be imported using its name or numeric identifier, though
    some features will not be available without an explicit 'zpool upgrade'.
config:

    zroot       ONLINE
     da3p4     ONLINE

Now you can use the pool id instead of a name with zpool import

# zpool import -R /mnt -N -f 647281366119489090 -t oldzroot

With this command I ask zpool to import with a different root location ( -R ) so I don't clobber my existing file system, to not mount anything ( -N ), use the id and to use a temporary name for the pool ( -t oldzroot ).

With the pool imported you can find your dataset and mount it with zfs list and mount -t zfs .