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?
- (oh shit, I have a book on it
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.
20251205
I asked a question in irc, but to no response so far.
I'm not sure how to handle the multi mode executable. I read a radare2 firmware walkthrough and they suggested that adding the memory map for the soc will help a lot.
So lets pull that from the datasheet and turn it into radare2 format. The memory map in the datasheet copied out from the pdf is this blob:
Module Address(It is for Cluster CPU) Size(Bytes)
BROM 0x0000 0000---0x0000 FFFF 64K
SRAM A1 0x0002 0000---0x0002 7FFF 32K(support Byte operation, clock source is AHB1)
SRAM C 0x0002 8000---0x0005 7FFF Borrow VE 128K, DE 64K, supports Byte operation, clock source is AHB1 Accelerator
DE 0x0100 0000---0x013F FFFF 4M
DI0 0x0142 0000---0x0145 FFFF 256K
G2D 0x0148 0000---0x014B FFFF 256K
GPU 0x0180 0000---0x0183 FFFF 256K
CE_NS 0x0190 4000---0x0190 47FF 2K
CE_S 0x0190 4800---0x0190 4FFF 2K
CE_KEY_SRAM 0x0190 8000---0x0190 8FFF 4K
VE SRAM 0x01A0 0000---0x01BF FFFF 2M
VE 0x01C0 E000---0x01C0 FFFF 8K
System Resources
SYS_CFG 0x0300 0000---0x0300 0FFF 4K
CCU 0x0300 1000---0x0300 1FFF 4K
DMA 0x0300 2000---0x0300 2FFF 4K
HSTIMER 0x0300 5000---0x0300 5FFF 4K
SID 0x0300 6000---0x0300 6FFF 4K
SMC 0x0300 7000---0x0300 7FFF 4K
SPC 0x0300 8000---0x0300 83FF 1K
TIMER 0x0300 9000---0x0300 93FF 1K
PWM 0x0300 A000---0x0300 A3FF 1K
GPIO 0x0300 B000---0x0300 B3FF 1K
PSI 0x0300 C000---0x0300 C3FF 1K
GIC 0x0302 0000---0x0302 FFFF 64K
IOMMU 0x030F 0000---0x030F FFFF 64K
RTC 0x0700 0000---0x0700 03FF 1K
PRCM 0x0701 0000---0x0701 03FF 1K
TWD 0x0702 0800 – 0x0702 0BFF 1K
NAND0 0x0401 1000---0x0401 1FFF 4K
SMHC0 0x0402 0000---0x0402 0FFF 4K
SMHC1 0x0402 1000---0x0402 1FFF 4K
SMHC2 0x0402 2000---0x0402 2FFF 4K
MSI_CTRL 0x047F A000---0x047F AFFF 4K
DRAM_CTRL 0x047F B000---0x047F FFFF 20K
PHY_CTRL 0x0480 0000---0x04FF FFFF 8M
Interfaces
UART0 0x0500 0000---0x0500 03FF 1K
UART1 0x0500 0400---0x0500 07FF 1K
UART2 0x0500 0800---0x0500 0BFF 1K
UART3 0x0500 0C00---0x0500 0FFF 1K
UART4 0x0500 1000---0x0500 13FF 1K
UART5 0x0500 1400---0x0500 17FF 1K
TWI0 0x0500 2000---0x0500 23FF 1K
TWI1 0x0500 2400---0x0500 27FF 1K
TWI2 0x0500 2800---0x0500 2BFF 1K
TWI3 0x0500 2C00---0x0500 2FFF 1K
TWI4 0x0500 3000---0x0500 33FF 1K
S_TWI0 0x0708 1400---0x0708 17FF 1K
SPI0 0x0501 0000---0x0501 0FFF 4K
SPI1 0x0501 1000---0x0501 1FFF 4K
EMAC0 0x0502 0000---0x0502 FFFF 64K
EMAC1 0x0503 0000---0x0503 FFFF 64K
TS0 0x0506 0000---0x0506 0FFF 4K
THS 0x0507 0400---0x0507 07FF 1K
LRADC 0x0507 0800---0x0507 0BFF 1K
OWA 0x0509 3000---0x0509 33FF 1K
DMIC 0x0509 5000---0x0509 53FF 1K
Audio Codec 0x0509 6000---0x0509 6FFF 4K
Audio HUB 0x0509 7000---0x0509 7FFF 4K
USB0(USB2.0_OTG) 0x0510 0000---0x051F FFFF 1M
USB1(USB2.0_HOST1) 0x0520 0000---0x052F FFFF 1M
USB2(USB2.0_HOST2) 0x0531 0000---0x0531 0FFF 4K
USB3(USB2.0_HOST3) 0x0531 1000---0x0531 1FFF 4K
CIR_RX 0x0704 0000---0x0704 03FF 1K
Display
HDMI_TX0(1.4/2.0) 0x0600 0000---0x060F FFFF 1M
DISP_IF_TOP 0x0651 0000---0x0651 0FFF 4K
TCON_TV0 0x0651 5000---0x0651 5FFF 4K
TCON_TV1 0x0651 6000---0x0651 6FFF 4K
TVE_TOP 0x0652 0000---0x0652 3FFF 16K
TVE0 0x0652 4000---0x0652 7FFF 16K
CPUX Related
CPU_SUBSYS_CFG 0x0810 0000---0x0810 03FF 1K
TIMESTAMP_STU 0x0811 0000---0x0811 0FFF 4K
TIMESTAMP_CTRL 0x0812 0000---0x0812 0FFF 4K
IDC 0x0813 0000---0x0813 0FFF 3K
C0_CPUX_CFG 0x0901 0000---0x0901 03FF 1K
C0_CPUX_MBIST 0x0902 0000---0x0902 0FFF 4K
DRAM
DRAM 0x4000 0000---0x13FFF FFFF 4G
I am not sure the best way to model this in radare2. I don't need to have all of this in radare2 and all of it might hurt, it will be good to get the uarts represented, getting the writes there matching up with the disassembly will give me a good sync point between the run time output and the code I have.
Looking for uart writes will be a helpful starting point to figure out which mode the processor is in at that point, we get known plain text to associate with known registers.
The radare2 book chapter on this isn't much help, it feels like it is half written. It pushes svd files very hard, but I don't have an svd file.
At some point I should probbaly also figure out where boot0 is running from.
The syntax for creating a memory range is pretty janky, you need to open a malloc file uri with the size as the name. 4G isn't allowed, I guess it is too big, some iteration shows that 2G is the limit for a size reservation. 1G should surely be fine, I doubt uboot is reaching up that far.
The fw guide suggests using flags (which they compare to bookmarks) for mapping in devices such as uarts.
I can create an allocation for the bootrom like so:
on malloc://64k 0x00000000
omn. BROM
but the we get a mapping like this:
[0x08000000]> om
- 2 fd: 4 +0x00000000 0x00000000 - 0x0000ffff rw-
* 1 fd: 3 +0x00000000 0x08000000 - 0x080fdfff r-x BROM
The BROM mapping has been created at the base address I gave r2 to use. I'm going to have to figure out how to deal with that.
20251206
I did some reading and hit the Arm documentation. It clarified for me that there are execution states, instruction set states and exception levels. You can only change instruction set state (from aarch64 -> aarch32 or vice versa) during a change of exception level.
As you change exception level up, you can only move up. So if you are running in EL0 and aarch32 you can move to EL1 aarch64, but you can't go from EL1 to EL0 and move from 32 to 64.
You also can't move to the same exception level, so if you are running in EL3 aarch32 you are stuck. Instead you need to do a soft reset to make that change.
The Arm documentation is very thin, it is written very precisely and isn't super helpful to me. Most of this detail is there, but it was really clarified in these two blog posts on duetorun , Exception Levels and Security States and ARM64 Execution States .
This has been super helpful, my guess is that we need some code to set up the interrupt vector for 64 bit and then trigger a reset to move into 64 bit mode. Once that has occured we will probably find addressess decoding as we expect.
The actual reset process might hang off the Arm forums question .
This leaves a lot to do:
- make enough of the memory map appear in radare2
- find the uboot linker scripts for h616
- find uboot code for the transition
- find the reset process
- track down the reset instruction for aarch32 and 64
The Arm docs have:
AArch32 (EL3) to AArch64 Execution state transition at reset
At Exception level 3 (EL3), cores can only transition between AArch32
and AArch64 states at reset. The Execution state after reset is
controlled by the AA64nAA32[PE:0] configuration signals. These signals
are only sampled at reset.
To reset a core and change Execution state from software, a Warm reset
request can be made by setting the RR bit of the RMR system register
(from AArch32) or the RMR_EL3 register (from AArch64). Following the
register write and executing a WFI instruction, the cluster
automatically resets the core without requiring any action by the
external reset controller. The hardware automatically cleans and
invalidates all the caches and safely disconnects the core from cluster
before the reset is asserted.
The defconfig I started with has a bunch of options which might be clues for where to start looking:
CONFIG_ENABLE_ARM_SOC_BOOT0_HOOK=y
CONFIG_ARM64_SUPPORT_AARCH32=y
There isn't anything super clearly setting up the 32->64 transition, but there are a lot of things which might imply it. This is a great place to start digging from.
Seeing good stuff I kept reading to see if there were more values to pull out which might help with my questions.
CONFIG_TEXT_BASE=0x4a000000
CONFIG_SYS_UBOOT_START=0x4a000000
This might answer my "where do we load question" too.
The first two options just seem to be build effects, which is great.
CONFIG
TEXT
BASE has its hooks everywhere and CONFIG
SYS
UBOOT_START just sets
spl_image->entry_point = CONFIG_SYS_UBOOT_START;
.
This stackoverflow question and answer seems to hold a lot of hard facts and it links to a uboot file which probably helps . Indeed it is boot0.h and we have that config in our defconfig:
CONFIG_ARM_BOOT_HOOK_RMR=y
It is kind of a wild file with just a ton of already assembled op codes:
/*
* Switch into AArch64 if needed.
* Refer to arch/arm/mach-sunxi/rmr_switch.S for the original source.
*/
tst x0, x0 // this is "b #0x84" in ARM
b reset
.space 0x7c
.word 0xe28f0070 // add r0, pc, #112 // @(fel_stash - .)
.word 0xe59f106c // ldr r1, [pc, #108] // fel_stash - .
.word 0xe0800001 // add r0, r0, r1
.word 0xe580d000 // str sp, [r0]
.word 0xe580e004 // str lr, [r0, #4]
.word 0xe10fe000 // mrs lr, CPSR
.word 0xe580e008 // str lr, [r0, #8]
.word 0xee11ef10 // mrc 15, 0, lr, cr1, cr0, {0}
.word 0xe580e00c // str lr, [r0, #12]
.word 0xee1cef10 // mrc 15, 0, lr, cr12, cr0, {0}
.word 0xe580e010 // str lr, [r0, #16]
....
Lets fire up r2 with an updated base address and try and find a wfi.
The commit message for this file is great:
sunxi: A64: do an RMR switch if started in AArch32 mode
André Przywara authored 8 years ago
The Allwinner A64 SoC starts execution in AArch32 mode, and both
the boot ROM and Allwinner's boot0 keep running in this mode.
So U-Boot gets entered in 32-bit, although we want it to run in AArch64.
By using a "magic" instruction, which happens to be an almost-NOP in
AArch64 and a branch in AArch32, we differentiate between being
entered in 64-bit or 32-bit mode.
If in 64-bit mode, we proceed with the branch to reset, but in 32-bit
mode we trigger an RMR write to bring the core into AArch64/EL3 and
re-enter U-Boot at CONFIG_SYS_TEXT_BASE.
This allows a 64-bit U-Boot to be both entered in 32 and 64-bit mode,
so we can use the same start code for the SPL and the U-Boot proper.
We use the existing custom header (boot0.h) functionality, but restrict
the existing boot0 header reservation to the non-SPL build now. A SPL
wouldn't need such header anyway. This allows to have both options
defined and lets us use one for the SPL and the other for U-Boot proper.
Also add arch/arm/mach-sunxi/rmr_switch.S, which contains the original
ARM assembly code and instructions how to re-generate the encoded
version.
Ah ha! That explains that we are going to be in 32bit for all of boot0! Here is the entire log before the vendor uboot starts:
[61]HELLO! BOOT0 is starting!
[64]BOOT0 commit : 3ae35eb
[66]set pll start
[69]periph0 has been enabled
[72]set pll end
[74]unknow PMU
[75]unknow PMU
[77]PMU: AXP1530
[79]dram return write ok
[82]board init ok
[83]DRAM BOOT DRIVE INFO: V0.648
[87]the chip id is 0x5000
[89]chip id check OK
[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.
[125]rtc standby flag is 0x0, super standby flag is 0x0
[131]dram size =4096
[136]sdcard 2 line count 8
[138][mmc]: mmc driver ver 2021-10-12 13:56
[143][mmc]: b mmc 2 bias 4
[151][mmc]: Wrong media type 0x0, but host sdc2, try mmc first
[143][mmc]: b mmc 2 bias 4
[151][mmc]: Wrong media type 0x0, but host sdc2, try mmc first
[157][mmc]: ***Try MMC card 2***
[199][mmc]: RMCA OK!
[202][mmc]: MMC 5.0
[204][mmc]: HSSDR52/SDR25 8 bit
[207][mmc]: 50000000 Hz
[209][mmc]: 29820 MB
[211][mmc]: ***SD/MMC 2 init OK!!!***
[286]Loading boot-pkg Succeed(index=0).
[290][mmc]: b mmc 2 bias 4
[293]Entry_name = u-boot
[302]Entry_name = monitor
[306]Entry_name = dtbo
[309]Entry_name = dtb
[312]tunning data addr:0x4a0003e8
[316]Jump to second Boot.
NOTICE: BL3-1: v1.0(debug):05d6c57
NOTICE: BL3-1: Built : 13:35:35, 2021-10-28
NOTICE: BL3-1 commit: 8
NOTICE: cpuidle init version V1.0
ERROR: Error initializing runtime service tspd_fast
NOTICE: BL3-1: Preparing for EL3 exit to normal world
NOTICE: BL3-1: Next image address = 0x4a000000
NOTICE: BL3-1: Next image spsr = 0x1d3
r2 searching makes no sense to me, so I used hexdump and grep to grab that first message from the image and then moved to that addressed in r2:
0x4a00d399 7200 7363 7000 6474 6200 6474 626f 00 r.scp.dtb.dtbo.
0x4a00d3a8 6c6f 676f 0048 454c 4c4f 2120 424f 4f logo.HELLO! BOO
0x4a00d3b7 5430 2069 7320 7374 6172 7469 6e67 21 T0 is starting!
0x4a00d3c6 0a00 424f 4f54 3020 636f 6d6d 6974 20 ..BOOT0 commit
0x4a00d3d5 3a20 2573 0a00 6472 616d 2073 697a 65 : %s..dram size
0x4a00d3e4 203d 2564 0a00 6465 7465 6374 6564 20 =%d..detected
0x4a00d3f3 7573 6572 2069 6e70 7574 2032 0a00 4a user input 2..J
All of the output strings are bundled together, but they are separated by null bytes (00). That is close enough now that we have a uart address and a rough offset to look for.
So, I want to do something that I assume should be super common in an interactive disassembler and reverse engineering tool: search for writes to an address or range.
Running analysis with
aaa
seems to have helped make things be analysed (I
stumbled onto this from an
old advent
calendar
). This post might be the most useful
thing I've found for actually doing any work with r2.
I need something to short circuit some of this suffering with r2.