Kernel debugging QEMU

Debugging the FreeBSD kernel in QEMU is really straight forward. Get yourself some kernel symbols (either from the build dir or for /usr/lib/debug/kernel* ), load them into gdb, kick of QEMU with -s -S and connect.

$ qemu-system-x86_64 ... -s -S ...

The -S flag causes QEMU to wait for the gdbstub to start execution rather than letting CPUs free immediately. So we need to connect with gdb to get going.

$ gdb kernel.debug
(gdb) target remote localhost:1234
(gbd) c

Usage is documented a little in the manual (as much as anything is ever gdb documented).

I got the steps from a FreeBSD 10 guide and other than the symbols file changing in between everything is about the same. It is nice when things don't change.

gdb is nice and all, but can we use a debugger that ships with FreeBSD?

The lldb documentation suggests that we can do something like this:

(lldb) platform list
Available platforms:
host: Local FreeBSD user platform plug-in.
remote-freebsd: Remote FreeBSD user platform plug-in.
remote-gdb-server: A platform that uses the GDB remote protocol as the communication transport.
qemu-user: Platform for debugging binaries under user mode qemu
(lldb) platform select remote-gdb-server
  Platform: remote-gdb-server
 Connected: no
(lldb) platform connect connect://localhost:1234
  Platform: remote-gdb-server
  Hostname: (null)
 Connected: yes

It will say everything is connected, but when we try to continue we get an error.

 (lldb) c
error: invalid target, create a target using the 'target create' command

From a stackoverflow question I found the gdb-remote command which does what the documentation don't:

(lldb) gdb-remote localhost:1234
Process 1 stopped
* thread #1, stop reason = signal SIGTRAP
    frame #0: 0x000000000000fff0
->  0xfff0: addb   %al, (%rax)
    0xfff2: addb   %al, (%rax)
    0xfff4: addb   %al, (%rax)
    0xfff6: addb   %al, (%rax)
(lldb) c
Process 1 resuming
Process 1 stopped
* thread #4, stop reason = signal SIGINT
    frame #0: 0xffffffff805ee459
->  0xffffffff805ee459: incl   %eax
    0xffffffff805ee45b: cmpl   0x812097(%rip), %eax
    0xffffffff805ee461: jl     0xffffffff805ee450
    0xffffffff805ee463: nopw   %cs:(%rax,%rax)
(lldb) list udp_output
error: Could not find function named: "udp_output"

Things are confused.

(lldb) image list
[  0] 7A0DEA14 0x0000000000200000 /sbin/init
      /usr/lib/debug/sbin/init.debug

(lldb) f
error: Command requires a process which is currently stopped.
(lldb) c
error: Process is running.  Use 'process interrupt' to pause execution.

Out of the box like this I think lldb needs some more configuration. Frustratingly as always the documentation around debugging is seriously lacking.

gdb isn't perfect either:

End of the file was already reached, use "list ." to list the current location again
(gdb) list .

Fatal signal: Segmentation fault
----- Backtrace -----
0x1350771 ???
0x14717f6 ???
0x1471f7f ???
0x82fbf457f handle_signal
        /home/pkgbuild/worktrees/main/lib/libthr/thread/thr_sig.c:300
0x82fbf3b3a thr_sighandler
        /home/pkgbuild/worktrees/main/lib/libthr/thread/thr_sig.c:243
0x8230ef2d2 ???
0x173da31 ???
0x137ac37 ???
0x1384a35 ???
0x17cb202 ???
0x1470faa ???
0x1471335 ???
0x14709a2 ???
0x8262127c2 ???
0x1471d8d ???
0x14705fc ???
0x17ff86f ???
0x1cab092 ???
0x1caab64 ???
0x1576a59 ???
0x1573c40 ???
0x1250760 ???
0x83000d839 __libc_start1
        /home/pkgbuild/worktrees/main/lib/libc/csu/libc_start1.c:157
0x125064f ???
---------------------
A fatal error internal to GDB has been detected, further
debugging is not possible.  GDB will now terminate.

This is a bug, please report it.  For instructions, see:
<https://www.gnu.org/software/gdb/bugs/>.

zsh: segmentation fault (core dumped)  gdb

https://people.freebsd.org/~avg/kyivbsd/KyivBSD2010.pdf

a links post? https://www.unitedbsd.com/blog/775-remote-debugging-the-running-openbsd-kernel https://forums.freebsd.org/threads/setting-up-a-bhyve-vm-for-kernel-development-and-debugging.90935/

devlog Porting FreeBSD to QEMU MicroVM

I haphazardly take notes while doing development, when real debugging is happening I fill folded pieces of paper with the names of functions and variables as they pop into notice and fade away again. I thought during the VPP port it might be nice to both have better notes, but to also share this process more.

I constantly am on the edge with talking about what I am doing, I've internalised that "research shows that people who share their plans are less likely to complete them" and I'm aware that people jump at news rather than actual facts. I don't want anyone to get too excited about in progress things, but I also don't to keep everything to myself.

Reading about people doing ports of Linux and bringing up new stuff in OpenBSD via undeadly.org was a big draw to me when I was coming up. I would attribute people writing about their doings as one of the biggest factors leading to the work I currently do.

This is an experiment in sharing something close to development notes, but written with an audience in mind. I do have other notes like this for other projects. I think an ideal devlog is like Joey Hess's old daily devblog , but for me the balance is probably closer to writing as I go and publishing at milestones.


I read Rob's article on quiz an excellent tool for speeding up Linux kernel deveopment and learned about QEMU MicroVM, it seemed like a great way to combine a crazy idea with a useful idea.

QEMU MicroVM offers a QEMU based implementation of soemthing like the Amazon Firecracker microvm that Colin Percival ported FreeBSD to over the last few years.

Colin has written a lot about the port to Firecracker and given a presentation a few times . The covers some of the technical description of firecracker there, how it works and how it relates to 'normal' machines. Colin's 2023 BSDCan talk is the basis for pretty much everything I know about Firecracker.

I have been thinking about an idea that requires FreeBSD booting super quickly for a couple of months and MicroVM has been the focus of that speculation for the last month. I keep coming up with more reason to have some fun looking hacking.

Running MicroVM

While I tried to get Rob's stuff going on Ubuntu (and then failed to install Debian at all), maybe a good way to start is to just wade in.

qemu-system-x86_64 requires quite a few arguments to run a MicroVM machine, I find it easiest to bundle these into a script I can pass in the kernel and disk to:

#!/bin/sh

kernel=$1
disk=$2

memory=512m
cores=4
netif=tap0

qemu-system-x86_64 -M microvm                                   \
    -cpu max                                                    \
    -m ${memory}                                                \
    -smp ${cores}                                               \
    -kernel ${kernel}                                           \
    -append "earlyprintk=ttyS0 console=ttyS0 root=/dev/vda"     \
    -nodefaults                                                 \
    -no-user-config                                             \
    -nographic                                                  \
    -serial stdio                                               \
    -drive id=test,file=${disk},format=raw,if=none              \
    -device virtio-blk-device,drive=test

Building a Firecracker FreeBSD kernel is straight forward, from a FreeBSD tree run:

$ make -j 16 -s buildkernel KERNCONF=FIRECRACKER TARGET=amd64

That will give you a kernel to feed to QEMU like so:

[tj@computer] $ sh ~/code/scripts/qemu/microvm.sh /usr/obj/usr/home/tj/code/freebsd/worktrees/microvm/amd64.amd64/sys/FIRECRACKER/kernel ~/vms/test.raw
SeaBIOS (version rel-1.16.3-0-ga6ed6b701f0a-prebuilt.qemu.org)
Booting from ROM..GDB: no debug ports present
KDB: debugger backends: ddb
KDB: current backend: ddb
---<<BOOT>>---
Copyright (c) 1992-2024 The FreeBSD Project.
Copyright (c) 1979, 1980, 1983, 1986, 1988, 1989, 1991, 1992, 1993, 1994
    The Regents of the University of California. All rights reserved.
FreeBSD is a registered trademark of The FreeBSD Foundation.
FreeBSD 15.0-CURRENT #0 thj/microvm-n269722-8ceac8e13dcc: Fri Apr 26 17:55:03 BST 2024
tj@displacementactivity:/usr/obj/usr/home/tj/code/freebsd/worktrees/microvm/amd64.amd64/sys/FIRECRACKER amd64
FreeBSD clang version 18.1.3 (https://github.com/llvm/llvm-project.git llvmorg-18.1.3-0-gc13b7485b879)
WARNING: WITNESS option enabled, expect reduced performance.
CPU: QEMU TCG CPU version 2.5+ (K8-class CPU)
Origin="AuthenticAMD"  Id=0x663  Family=0x6  Model=0x6  Stepping=3
Features=0x1fc3fbfd<FPU,DE,PSE,TSC,MSR,PAE,MCE,CX8,APIC,SEP,MTRR,PGE,MCA,CMOV,PAT,PSE36,ACPI,MMX,FXSR,SSE,S>
Features2=0xfed8320b<SSE3,PCLMULQDQ,MON,SSSE3,FMA,CX16,SSE4.1,SSE4.2,MOVBE,POPCNT,AESNI,XSAVE,OSXSAVE,AVX,F>
AMD Features=0xec500800<SYSCALL,NX,MMX+,Page1GB,RDTSCP,LM,3DNow!+,3DNow!>
AMD Features2=0x177<LAHF,CMP,SVM,CR8,ABM,SSE4A,Prefetch>
Structured Extended Features=0x21dc43a9<FSGSBASE,BMI1,AVX2,SMEP,BMI2,ERMS,MPX,RDSEED,ADX,SMAP,<b22>,CLFLUSH>
Structured Extended Features2=0x8041021c<UMIP,PKU,OSPKE,VAES,LA57,RDPID>
Structured Extended Features3=0x10<FSRM>
XSAVE Features=0x5<XSAVEOPT,XINUSE>
AMD Extended Feature Extensions ID EBX=0x204<XSaveErPtr,WBNOINVD>
SVM: NP,NAsids=16
Hypervisor: Origin = "TCGTCGTCGTCG"
real memory  = 536858624 (511 MB)
avail memory = 492081152 (469 MB)
MPTable: <BOCHSCPU 0.1         >
Event timer "LAPIC" quality 600
panic: TSC not initialized
cpuid = 0
time = 1
KDB: stack backtrace:
db_fetch_ksymtab() at db_fetch_ksymtab+0x17b/frame 0xffffffff81404dc0
vpanic() at vpanic+0x135/frame 0xffffffff81404ef0
panic() at panic+0x43/frame 0xffffffff81404f50
lapic_init() at lapic_init+0x4a4/frame 0xffffffff81404f70
mptable_pci_host_res_init() at mptable_pci_host_res_init+0x856/frame 0xffffffff81404f90
lapic_ipi_free() at lapic_ipi_free+0x152f/frame 0xffffffff81404fa0
mi_startup() at mi_startup+0x1c8/frame 0xffffffff81404ff0
KDB: enter: panic
[ thread pid 0 tid 0 ]
Stopped at      kdb_enter+0x33: movq    $0,0x968d12(%rip)
db>

Incredible progress! I was expecting this to be much more of a fight to get anything from the Firecracker image. Colin spoke about binary searching by looking at system load in the virtual machine host dashboard, but here we have serial output immediately. To check that this wasn't a fluke I also tried a generic kernel:

[tj@computer] $ sh ~/code/scripts/qemu/microvm.sh /usr/obj/usr/home/tj/code/freebsd/worktrees/microvm/amd64.amd64/sys/GENERIC/kernel ~/vms/test.raw
SeaBIOS (version rel-1.16.3-0-ga6ed6b701f0a-prebuilt.qemu.org)
Booting from ROM..

It just sat there. Good news, we have a solid starting point with the Firecracker kernel.

First steps

The panic message in our first boot is "TSC not initialized" - a quick "grep -R' away in local_apic.c:547 .

#ifdef SMP
#define LOOPS   1000
        /*
         * Calibrate the busy loop waiting for IPI ack in xAPIC mode.
         * lapic_ipi_wait_mult contains the number of iterations which
         * approximately delay execution for 1 microsecond (the
         * argument to lapic_ipi_wait() is in microseconds).
         *
         * We assume that TSC is present and already measured.
         * Possible TSC frequency jumps are irrelevant to the
         * calibration loop below, the CPU clock management code is
         * not yet started, and we do not enter sleep states.
         */
        KASSERT((cpu_feature & CPUID_TSC) != 0 && tsc_freq != 0,
            ("TSC not initialized"));
        if (!x2apic_mode) {
                r = rdtsc();

Commenting out the KASSERT lets us advance as far as configuring uart0 :

...
Event timer "LAPIC" quality 600
random: registering fast source Intel Secure Key RNG
random: fast provider: "Intel Secure Key RNG"
arc4random: WARNING: initial seeding bypassed the cryptographic random device because it was not yet seeded and th.
ioapic0: Assuming intbase of 0
ioapic0 <Version 2.0> irqs 0-23
random: entropy device external interface
aesni0: <AES-CBC,AES-CCM,AES-GCM,AES-ICM,AES-XTS,SHA1,SHA256>
cpu0
isa0: <ISA bus>
orm0: <ISA Option ROM> at iomem 0xef000-0xeffff pnpid ORM0000 on isa0
uart0: <16550 or compatible> at port 0x3f8 irq 4 flags 0x10 on isa0


Fatal trap 12: page fault while in kernel mode
cpuid = 0; apic id = 00
fault virtual address   = 0x20
fault code              = supervisor read data, page not present
instruction pointer     = 0x20:0xffffffff808f5190
stack pointer           = 0x28:0xffffffff81404e20
frame pointer           = 0x28:0xffffffff81404e40
code segment            = base 0x0, limit 0xfffff, type 0x1b
                        = DPL 0, pres 1, long 1, def32 0, gran 1
processor eflags        = interrupt enabled, IOPL = 0
current process         = 0 (swapper)
rdi: 0000000000000020 rsi: 0000000000000000 rdx: 00000000000003f9
rcx: 0000000000000000  r8: 0000000000000000  r9: 0000000000000001
rax: ffffffff80e49040 rbx: 0000000000000020 rbp: ffffffff81404e40
r10: 0000000000010000 r11: 0000000000000001 r12: fffff800024f4e64
r13: 0000000000000001 r14: 00000000000000c8 r15: fffff800025b9858
trap number             = 12
panic: page fault
cpuid = 0
time = 1
KDB: stack backtrace:
db_fetch_ksymtab() at db_fetch_ksymtab+0x17b/frame 0xffffffff81404af0
vpanic() at vpanic+0x135/frame 0xffffffff81404c20
panic() at panic+0x43/frame 0xffffffff81404c80
trap() at trap+0xd2b/frame 0xffffffff81404ce0
trap() at trap+0xdd0/frame 0xffffffff81404d50
calltrap() at calltrap+0x8/frame 0xffffffff81404d50
--- trap 0xc, rip = 0xffffffff808f5190, rsp = 0xffffffff81404e20, rbp = 0xffffffff81404e40 ---
pvclock_get_timecount() at pvclock_get_timecount+0x10/frame 0xffffffff81404e40
xen_delay() at xen_delay+0x1d/frame 0xffffffff81404e60
ns8250_bus_attach() at ns8250_bus_attach+0x3e2/frame 0xffffffff81404e90
uart_bus_attach() at uart_bus_attach+0x184/frame 0xffffffff81404ed0
device_attach() at device_attach+0x3aa/frame 0xffffffff81404f10
device_probe_and_attach() at device_probe_and_attach+0x70/frame 0xffffffff81404f40
isa_probe_children() at isa_probe_children+0x23a/frame 0xffffffff81404fa0
mi_startup() at mi_startup+0x1c8/frame 0xffffffff81404ff0
KDB: enter: panic
[ thread pid 0 tid 100000 ]
Stopped at      kdb_enter+0x33: movq    $0,0x968d12(%rip)
db>

But then we take a page fault in pvclock_get_timecount() , so it looks like we can't just skip past that first KASSERT. What should we be doing the first time around?

I forced on bootverbose in init_main.c to see if that would give me any more clues. Our error is coming from having a TSC, but none of the TSC reads being valid. Along with there being a TSC we need another variable set to tell us how to read the TSC for calibration.

The source of our error is really early in machine dependant code, but it is caught much later. This means we can print how things are later on in boot, but not when they fail.

print_hypervisor_info:2639 vm_guest 0x1 tsc_freq 0x0 tsc 0x10 hv_high 0x40000001 cpu_high 0xd

I added a print in indentcpu.c at print_hypervisor_info as that was easy to see in the message buffer.

static int
tsc_freq_cpuid_vm(void)
{
        u_int regs[4];

        if (vm_guest == VM_GUEST_NO)
                return (false);
        if (hv_high < 0x40000010)
                return (false);
        do_cpuid(0x40000010, regs);
        tsc_freq = (uint64_t)(regs[0]) * 1000;
        tsc_early_calib_exact = 1;
        return (true);
}

...

/*
 * Calculate TSC frequency using information from the CPUID leaf 0x15 'Time
 * Stamp Counter and Nominal Core Crystal Clock'.  If leaf 0x15 is not
 * functional, as it is on Skylake/Kabylake, try 0x16 'Processor Frequency
 * Information'.  Leaf 0x16 is described in the SDM as informational only, but
 * we can use this value until late calibration is complete.
 */
static bool
tsc_freq_cpuid(uint64_t *res)
{
        u_int regs[4];

        if (cpu_high < 0x15)
                return (false);
        do_cpuid(0x15, regs);
        if (regs[0] != 0 && regs[1] != 0 && regs[2] != 0) {
                *res = (uint64_t)regs[2] * regs[1] / regs[0];
                return (true);
        }
...

The calibration functions look like this, they check vm_guest (0x1 for us or VM_GUEST_VM ) and then look at the value of hv_high or cpu_high in the default tsc_freq_cpuid

With hv_high as 0x40000001 and cpu_high as 0x0D we are failing the checks in both of the obvious tsc_freq functions. Why?

To figure that out we need to look at where both of these values come from.

do_cpuid(0, regs);
cpu_high = regs[0];     /* eax */

hv_high is set int identify_hypervisor_cpuid_base in sys/x86/x86/identcpu.c with this block:

/*
 * If this is the first entry or we found a
 * specific hypervisor, record the base, high value,
 * and vendor identifier.
 */
if (vm_guest != prev_vm_guest || leaf == 0x40000000) {
        hv_base = leaf;
        hv_high = regs[0];
        ((u_int *)&hv_vendor)[0] = regs[1];
        ((u_int *)&hv_vendor)[1] = regs[2];
        ((u_int *)&hv_vendor)[2] = regs[3];
        hv_vendor[12] = '\0';

        /*
         * If we found a specific hypervisor, then
         * we are finished.
         */
        if (vm_guest != VM_GUEST_VM &&
            /*
             * Xen and other hypervisors can expose the
             * HyperV signature in addition to the
             * native one in order to support Viridian
             * extensions for Windows guests.
             *
             * Do the full cpuid scan if HyperV is
             * detected, as the native hypervisor is
             * preferred.
             */
            vm_guest != VM_GUEST_HV)
                break;
}

This comment about VM_GUEST_VM is suspect, we are running as VM_GUEST_VM and this comment implies that it is an early detection, but an error to actually try and use. Other comments in identify_hypervisor_cpuid_base point to lkml threads and a vmware knowledge base (KB1009458):

Testing the CPUID hypervisor present bit

Intel and AMD CPUs have reserved bit 31 of ECX of CPUID leaf 0x1 as the
hypervisor present bit. This bit allows hypervisors to indicate their presence
to the guest operating system. Hypervisors set this bit and physical CPUs (all
existing and future CPUs) set this bit to zero. Guest operating systems can
test bit 31 to detect if they are running inside a virtual machine.

Intel and AMD have also reserved CPUID leaves 0x40000000 - 0x400000FF for
software use. Hypervisors can use these leaves to provide an interface to pass
information from the hypervisor to the guest operating system running inside a
virtual machine. The hypervisor bit indicates the presence of a hypervisor and
that it is safe to test these additional software leaves. VMware defines the
0x40000000 leaf as the hypervisor CPUID information leaf. Code running on a
VMware hypervisor can test the CPUID information leaf for the hypervisor
signature. VMware stores the string "VMwareVMware" in EBX, ECX, EDX of CPUID
leaf 0x40000000.

Testing the virtual BIOS DMI information and the hypervisor port

Apart from the CPUID-based method for VMware virtual machine detection,
VMware also provides a fallback mechanism for the following reasons:

This CPUID-based technique will not work for guest code running at CPL3
when VT/AMD-V is not available or not enabled.  The hypervisor present bit
and hypervisor information leaf are only defined for products based on
VMware hardware version 7.

That is really helpful, we aren't running under a real hypervisor, the Hypervisor: Origin = "TCGTCGTCGTCG" in the message buffer indicates that we are running on QEMU's Tiny Code Generator.

This is a fork where I could look at a bunch of stuff, QEMU, the Linux kernel or a running system on Linux with KVM. I don't really want to do any of these, but I already have a clone of QEMU so I'll start by looking at how MicroVM is implemented.

QEMU Microvm

Microvm is implemented in qemu/hw/i386/ by microvm.c , microvm-dt.c (for fw_cfg ) and acpi-microvm.c (not sure about that one).

didn't help

neither did search in the linux github repo

maybe I can get a ubuntu kernel to boot to failure. Based on these instructions I grabbed the linked kernel and through it into by script which didn't work. Not sure if the enable-kvm option is the issue here I booted a test machine into ubuntu and ran through their entire example as is, which worked fine.

Then I tried my kernel in their script and their kernel in my script and neither were happy enough to give me any output. Of these three options non have really gotten me anywhere.

Going back to tsc_freq_cpuid_vm it does this:

        if (hv_high < 0x40000010)
                return (false);
        do_cpuid(0x40000010, regs);

Skipping this check get us to the same pvclock_get_timecount fault as when we skipped the assert entirely. This is trying to read a memory address that isn't there because we aren't Xen, and yes we aren't Xen. Why do we think we are Xen?

isxen (pv.c) runs early on an detects if we are running under Xen. If we are then the init_ops table is swapped from the default one to the Xen one ( xen_pvh_init_ops ), somewhere ( hammer_time_xen ).

Time for a break to try and blinken some lights which turned out to doing some recycling instead.

On these breaks I have found it helps a lot to leave a note somewhere, on return I can try and build and the note will piss off the compiler.

/usr/home/tj/code/freebsd/worktrees/microvm/sys/dev/xen/timer/xen_timer.c:165:2: error: use of undeclared identifier 'look'
  165 |         look here buddy
      |         ^
1 error generated.

I wasn't sure how we were getting to hammer_time_xen , but during the break I figured it is probably being driven by the kernel config.

# Xen HVM Guest Optimizations
# NOTE: XENHVM depends on xenpci and xentimer.
# They must be added or removed together.
# NOTE: These are present in FIRECRACKER because the PVH boot method
# originates from Xen; once that code is untangled these can be removed.
options         XENHVM                  # Xen HVM kernel infrastructure
device          xenpci                  # Xen HVM Hypervisor services driver
device         xentimer                # Xen x86 PV timer device

The Firecracker kernel config comes with lots of helpful potential comments and in the Xen section we have xentimer . Commenting this out and fixing up some parts of the Xen init_ops lets us advance to a fascinating panic:

Statistical lapic calibration failed!  Clocks might be ticking at variable rates.                                     
Falling back to slow lapic calibration.                                                                               
lapic: Divisor 2, Frequency 104591 Hz                                                                                 
Timecounters tick every 10.000 msec                                                                                   
lo0: bpf attached                                                                                                     
vlan: initialized, using hash tables with chaining                                                                    
IPsec: Initialized Security Association Processing.                                                                   
tcp_init: net.inet.tcp.tcbhashsize auto tuned to 4096      
random: unblocking device.                                                                                            
panic: deadlres_td_on_lock: possible deadlock detected for 0xffffffff80f145e0 (swapper), blocked for 139455 ticks

cpuid = 0                                                                                                             
time = 102624                                                                                                         
KDB: stack backtrace:                                                                                                 
db_fetch_ksymtab() at db_fetch_ksymtab+0x17b/frame 0xfffffe004602cd20
vpanic() at vpanic+0x135/frame 0xfffffe004602ce50
panic() at panic+0x43/frame 0xfffffe004602ceb0
profclock() at profclock+0x5fa/frame 0xfffffe004602cef0
fork_exit() at fork_exit+0x82/frame 0xfffffe004602cf30
fork_trampoline() at fork_trampoline+0xe/frame 0xfffffe004602cf30
--- trap 0, rip = 0, rsp = 0, rbp = 0 ---

And on some other attempts after a long time we get to mount root:

isa_probe_children: probing PnP devices
Device configuration finished.
procfs registered
Timecounter "TSC" frequency 543000 Hz quality 800
Statistical lapic calibration failed!  Clocks might be ticking at variable rates.
Falling back to slow lapic calibration.
lapic: Divisor 2, Frequency 108474 Hz
Timecounters tick every 10.000 msec
lo0: bpf attached
vlan: initialized, using hash tables with chaining
IPsec: Initialized Security Association Processing.
tcp_init: net.inet.tcp.tcbhashsize auto tuned to 4096
random: unblocking device.
WARNING: WITNESS option enabled, expect reduced performance.

Loader variables:

Manual root filesystem specification:
  <fstype>:<device> [options]
      Mount <device> using filesystem <fstype>
      and with the specified (optional) option list.

    eg. ufs:/dev/da0s1a
        zfs:zroot/ROOT/default
        cd9660:/dev/cd0 ro
          (which is equivalent to: mount -t cd9660 -o ro /dev/cd0 /)

  ?               List valid disk boot devices
  .               Yield 1 second (for background tasks)
  <empty line>    Abort manual input

mountroot>

Mount root

Mountroot is the first 'success' in doing a port, there is enough of a system (even if inconsistently) to add disks, even if there aren't drivers for the required things. Next steps are to:

  • figure out why this is slow (it is meant to be fast after all)
  • look at the lapic calibration complaints
  • add virtio devices

I think before we can add Virtio devices we will need to figure out why the MPTable isn't telling us about the 4 cores that QEMU should be passing through.

real memory  = 536858624 (511 MB)                          
Physical memory chunk(s):                                  
0x0000000000001000 - 0x000000000009efff, 647168 bytes (158 pages)                
0x0000000000100000 - 0x00000000001fffff, 1048576 bytes (256 pages)
0x0000000001602000 - 0x000000001edf5fff, 494878720 bytes (120820 pages)
0x000000001fc00000 - 0x000000001fd6cfff, 1495040 bytes (365 pages)
avail memory = 492081152 (469 MB)                          
MPTable: <SMP: Added CPU 0 (BSP)                  
BOCHSCPU 0.1         >                                     
Event timer "LAPIC" quality 600                      
LAPIC: ipi_wait() us multiplier 1 (r 1645530 tsc 543000)   
Pentium Pro MTRR support enabled

Where are my cpus?

The QEMU bits of my launch script right now look like this:

memory=512m
cores=4
netif=tap0
cpu="max"

qemu-system-x86_64 -M microvm                                   \
        -cpu ${cpu}                                             \
        -m ${memory}                                            \
        -smp ${cores}                                           \
        -kernel ${kernel}                                       \
        -nodefaults                                             \
        -no-user-config                                         \
        -nographic                                              \
        -serial stdio                                           \
        -drive id=test,file=${disk},format=raw,if=none          \
        -device virtio-blk-device,drive=test

Requesting that four cpus/cores are created, but from the last message buffer output we only see only CPU, CPU 0 (BSP) .

I'm going to start by finding my missing cpus, in the last dmesg above we have

MPTable: <SMP: Added CPU 0 (BSP)                  
BOCHSCPU 0.1         >

The MPTable: < string is printed by sys/x86/x86/mptable.c

There are a couple of methods that call mptable_walk_table with different call backs, sticking a print in one of those:

static void
mptable_probe_cpus_handler(u_char *entry, void *arg)
{
printf("%s:%d %d\n", __func__, __LINE__, *entry);
        proc_entry_ptr proc;

        switch (*entry) {
        case MPCT_ENTRY_PROCESSOR:

Can give us an idea of the entries it carries.

---<<BOOT>>---                                                 
MP Configuration Table version 1.4 found at 0xffffffff800f4480 
APIC: Using the MPTable enumerator.                            
mptable_probe_cpus_handler:531 0                               
mptable_probe_cpus_handler:531 1                               
mptable_probe_cpus_handler:531 2                               
mptable_probe_cpus_handler:531 3                               
mptable_probe_cpus_handler:531 3                               
mptable_probe_cpus_handler:531 3                               
mptable_probe_cpus_handler:531 3
...

And when matched to the header we see we only have 1 MPCT_ENTRY_PROCESSOR .

/* Base table entries */

#define MPCT_ENTRY_PROCESSOR    0
#define MPCT_ENTRY_BUS          1
#define MPCT_ENTRY_IOAPIC       2
#define MPCT_ENTRY_INT          3
#define MPCT_ENTRY_LOCAL_INT    4

Firecracker uses MPTable, I know this from Colin's BSDCan talk, but this isn't matching to what I thought I was telling QEMU to do.

I'm not sure what is up with this, I think I need another target to compare against

Where's my disks?

From Colin's port we learn that Firecracker gets its virtio configuration from the Linux command line. I don't see any evidence of this in the message buffer right now. At the end of the blog posts there is some help I am certainly missing:

You'll probably also want to build a disk image so that FreeBSD has
something to boot from; place vfs.root.mountfrom=ufs:/dev/vtbd0 into
Firecracker's boot_args to tell FreeBSD to use the disk you attach (aka.
the first Virtio block device) as the root disk.

As I understand, virtio_blk needs to be configured to attach because MicroVM like firecracker has no dynamic configuration for it to probe from. So lets add this to the launch script:

bootargs="vfs.root.mountfrom=ufs:/dev/vtbd0"
...
-smp ${cores}                                           \
-kernel ${kernel}                                       \
-append ${bootargs}                                     \
-nodefaults                                             \
-no-user-config                                         \

Not quite enough, I'm not sure how boot args get from the QEMU command line into the kernel or where they would be stored and generally when doing silly things - like this - I'd prefer to learn and leave reading git logs as the last option. Kind of goes like this:

  • reading code
  • reading a book (lol)
  • searching the internet
  • reading git logs

I know from loader we can set -s , -d and -v for single, debug and verbose and I've already hi-jacked verbose to always be on because we don't have a loader to set it. I will look there.

From looking at various RB_ flags I stumbled onto boot_parse_cmdline . Which a variation of is called from in pv.c (all the other calls are on fun Risc architectures):

} else {
        /* Parse the extra boot information given by Xen */
        if (start_info->cmdline_paddr != 0)
                boot_parse_cmdline_delim(
                    (char *)(start_info->cmdline_paddr + KERNBASE),
                    ", \t\n");
        kmdp = NULL;
        strlcpy(bootmethod, "PVH", sizeof(bootmethod));
}

boothowto |= boot_env_to_howto();

With boothowto and bootverbose I can see things that pick up a command line, but I'm having a hard time having a reference to one somewhere I can print it. I still don't have prints working from the Xen init functions.

init_dynamic_kenv_from is called from the init_dynamic_kenv SYSINIT once we have a functioning VM system. This isn't getting anywhere.

Fine, a quick search of the commit log reveals:

commit 0e1f5ab7db2cd2837f97f169122897b19c185dbd
Author: Colin Percival <cperciva@FreeBSD.org>
Date:   Fri Aug 12 17:54:26 2022 -0700

    virtio_mmio: Support command-line parameters

    The Virtio MMIO bus driver was added in 2014 with support for devices
    exposed via FDT; in 2018 support was added to discover Virtio MMIO
    devices via ACPI tables, as in QEMU.  The Firecracker VMM eschews both
    FDT and ACPI, instead presenting device information via kernel command
    line arguments of the form virtio_mmio.device=<parameters>.

    These command line parameters get converted into kernel environment
    variables; this adds support for parsing those variables and attaching
    virtio_mmio children to nexus.

    There is a case to be made that it would be cleaner to have a new
    "cmdlinebus" attached to nexus and virtio_mmio children attached to
    that.  A future commit might do that.

    Discussed with: imp, jrtc27
    Sponsored by:   https://patreon.com/cperciva
    Differential Revision:  https://reviews.freebsd.org/D36189

It adds this identify method:

static void
vtmmio_cmdline_identify(driver_t *driver, device_t parent)
{
        size_t n;
        char name[] = "virtio_mmio.device_XXXX";
        char * val;

        /* First variable just has its own name. */
        if ((val = kern_getenv("virtio_mmio.device")) == NULL)
                return;
        parsearg(driver, parent, val);
        freeenv(val);

        /* The rest have _%zu suffixes. */
        for (n = 1; n <= 9999; n++) {
                sprintf(name, "virtio_mmio.device_%zu", n);
                if ((val = kern_getenv(name)) == NULL)
                        return;
                parsearg(driver, parent, val);
                freeenv(val);
        }
}

Which while we can trace that it is called, it uses kern_getenv to look for compatible devices AND that doesn't help me check what the hell the command line is.

Cheated for nothing - don't worry the rules are made up.

I grabbed a copy of the Xen extra boot information into a temp variable, waited for a panic to ddb and printed that and got a -dsr - which were the test flags I passed to QEMU when I started poking at this.

I took a stab in the dark and set instead:

bootargs="vfs.root.mountfrom=ufs:/dev/vtbd0\t\nvirtio_mmio.device"

And I got a different mountroot prompt!

Trying to mount root from ufs:/dev/vtbd0\t\nvirtio_mmio.device []...
mountroot: waiting for device /dev/vtbd0\t\nvirtio_mmio.device...
Mounting from ufs:/dev/vtbd0\t\nvirtio_mmio.device failed with error 19.

Loader variables:
  vfs.root.mountfrom=ufs:/dev/vtbd0\t\nvirtio_mmio.device

Manual root filesystem specification:
  <fstype>:<device> [options]

Kicking that into ddb and boom, there is the -append arg from QEMU.

db> x/s *cmdline
kernbase+0x560: vfs.root.mountfrom=ufs:/dev/vtbd0\t\nvirtio_mmio.device

So! However QEMU is passing through to the VM the virtio device configuration it isn't via this command line, there must be some other way. This effort has confirmed that the bootargs are making it through to the kernel, but we aren't getting everything we need.

I guess I need to look at what QEMU is doing and what a successful Linux boot is like

Looking at qemu

While donating platelets I had a read of the QEMU MicroVM implementation and the short git log for the file and hit this commit:

commit f6f7e2d88d0b29d8b6e1a12a5f3f9f31faff9846
Author: Gerd Hoffmann <kraxel@redhat.com>
Date:   Tue Sep 15 14:09:00 2020 +0200

    microvm/acpi: disable virtio-mmio cmdline hack

    ... in case we are using ACPI.

    Signed-off-by: Gerd Hoffmann <kraxel@redhat.com>
    Reviewed-by: Igor Mammedov <imammedo@redhat.com>
    Reviewed-by: Sergio Lopez <slp@redhat.com>
    Message-id: 20200915120909.20838-13-kraxel@redhat.com

The commit before this one gives context too:

commit 67eb6a4007fd8f9073020e506453ff5b7c25cb34
Author: Gerd Hoffmann <kraxel@redhat.com>
Date:   Tue Sep 15 14:08:59 2020 +0200

    microvm/acpi: use seabios with acpi=on

    With acpi=off continue to use qboot.

    Signed-off-by: Gerd Hoffmann <kraxel@redhat.com>
    Reviewed-by: Igor Mammedov <imammedo@redhat.com>
    Reviewed-by: Sergio Lopez <slp@redhat.com>
    Message-id: 20200915120909.20838-12-kraxel@redhat.com

Despite MicroVM saying it doesn't support ACPI there is a lot of ACPI specific code in the driver. My devices weren't in the kernel command line because the VM was booting with ACPI. It is also using a different firmware because it supports ACPI.

For MicroVM a tiny fast boot firmware called "qboot" was written, but in my testing when I have locked everything up I have seen plenty of 'Seabios" messages - I knew that wasn't what the documentation promised as the default, but I was also getting boot messages most the time.

I think the default bios with ACPI is Seabios and qboot otherwise.

I added -machine acpi=off to my boot script flags and this time got to:

Mounting from ufs:/dev/vtbd0 failed with error 2; retrying for 2 more seconds
Attempted recovery for standard superblock: failed
Attempted extraction of recovery data from standard superblock: failed
Attempt to find boot zone recovery data.
Finding an alternate superblock failed.
Check for only non-critical errors in standard superblock
Failed, superblock has critical errors
Attempted recovery for standard superblock: failed
Attempted extraction of recovery data from standard superblock: failed
Attempt to find boot zone recovery data.
Finding an alternate superblock failed.
Check for only non-critical errors in standard superblock

The mountroot errors just looped until I killed the VM. The errors make perfect sense, I have no idea what test.raw is, there is a good chance is is just an empty file. I gave qemu fbsd14.raw for my vms directory, but that also didn't boot so there are things to do.

Booting with the new firmware we also have a MPTable with multiple CPUs:

avail memory = 491782144 (469 MB)
MPTable: <mptable_setup_cpus_handler:552 0
SMP: Added CPU 0 (BSP)
mptable_setup_cpus_handler:552 0
SMP: Added CPU 1 (AP)
mptable_setup_cpus_handler:552 0
SMP: Added CPU 2 (AP)
mptable_setup_cpus_handler:552 0
SMP: Added CPU 3 (AP)

...

cpu0 BSP:                                                            
     ID: 0x00000000   VER: 0x00050014 LDR: 0x00000000 DFR: 0xffffffff
  lint0: 0x00010700 lint1: 0x00000400 TPR: 0x00000000 SVR: 0x000001ff
  timer: 0x000100ef therm: 0x00010000 err: 0x000000f0 pmc: 0x00010400
SMP: AP CPU #1 Launched!                                             
cpu1 AP:                                                             
     ID: 0x01000000   VER: 0x00050014 LDR: 0x00000000 DFR: 0xffffffff
  lint0: 0x00010700 lint1: 0x00000400 TPR: 0x00000000 SVR: 0x000001ff
  timer: 0x000100ef therm: 0x00010000 err: 0x000000f0 pmc: 0x00010400
SMP: AP CPU #3 Launched!                                             
cpu3 AP:                                                             
     ID: 0x03000000   VER: 0x00050014 LDR: 0x00000000 DFR: 0xffffffff
  lint0: 0x00010700 lint1: 0x00000400 TPR: 0x00000000 SVR: 0x000001ff
  timer: 0x000100ef therm: 0x00010000 err: 0x000000f0 pmc: 0x00010400
SMP: AP CPU #2 Launched!                                             
cpu2 AP:                                                             
     ID: 0x02000000   VER: 0x00050014 LDR: 0x00000000 DFR: 0xffffffff
  lint0: 0x00010700 lint1: 0x00000400 TPR: 0x00000000 SVR: 0x000001ff
  timer: 0x000100ef therm: 0x00010000 err: 0x000000f0 pmc: 0x00010400

Generally the console seems to be much more responsive, rather than the error we had before:

Timecounter "TSC" frequency 543000 Hz quality 800
Statistical lapic calibration failed!  Clocks might be ticking at variable rates.
Falling back to slow lapic calibration.
lapic: Divisor 2, Frequency 108474 Hz

We now have

Timecounter "TSC" frequency 543000 Hz quality -100

Mounting a disk

Time to check what test.raw is:

[tj@displacementactivity] $ sudo mdconfig -a ~/vms/test.raw
Password:
md0
[tj@displacementactivity] $ gpart show md0
=>       34  104857526  md0  GPT  (50G)
         34        122    1  freebsd-boot  (61K)
        156      66584    2  efi  (33M)
      66740    2097152    3  freebsd-swap  (1.0G)
    2163892  102693452    4  freebsd-ufs  (49G)
  104857344        216       - free -  (108K)

Which suggests that the boot script needs to know the correct partition:

bootargs="vfs.root.mountfrom=ufs:/dev/vtbd0p4"

Which gets us all the way to a login prompt! As I am ready to declare victory I login and the system silently hangs, but it works on a second try.

If we don't think about how much time was spent debugging the wrong QEMU bios firmware dur to poor documentation then that was quite an easy process.

From my minimal testing there are a few things to look at next:

  • reboot and shutdown don't
  • network devices appear and configure, but don't send packets
  • it just hangs sometimes
  • boot takes much longer than I would like (100ms max please)

Still more to do.

Thoughts on retro computing

I got an Atari Portfolio speaking to me yesterday, but actually doing anything with this machine will probably require buying expensive unreliable old accessories.

This is what happened with the 68k Powerbook I acquired last year too. That is a cool computer, but it needs a new power supply and it could really do with some more RAM. RAM which of course is impossible to buy and impossible to make due to a weird connector no one can find.

I really like computers, old new and new ones alike. I love the weird portable machines that were being made when everyone decided they didn't only need a personal computer, but an on the person computer.

But , I don't have nostalgia for these computers.

Many many years ago, in the #hackrf irc channel I was told off for telling someone about getting an im-me as a radio toy. The im-me is a wonderful hack, but the thing new functionality hacked into repurposed devices is that it is incredibly non-democratic in how it is available.

The device itself is normally not being made and as soon as media coverage lands for the cool hack eBay scalpers shoot up prices taking the 'cheap' repurposed gadget out of the hands of those that really need it.

Instead for the im-me you could pick up a super cheap TI dev board with the same radio or the wonderful open hardware supported by a company run by great people YardStickOne .

I am drawn to these old portable, person sized computers they reflect the desire to bring the power and joy of computers with us everywhere we go and well, they are really cool.

They are complex, simple, understandable machines, when they were made both the height of technology and the trailing edge.

The 80C88 wasn't winning any contests for performance in 1989. I have ideas in the works that build on using these little computers, but if anything I'm loath to invest further in them.

How can I really justify investing in keeping old computers going when for similar amounts of money and time I can build new computers which are just as weird, but understandable and expandable with tools and parts I can actually get.

Where the Book 8088 and the Pocket386 fit into this world of new old Commodore spins I have no idea.

netmap on cxgbe interfaces

Last year my testbed machine got the luxury of 100Gbit Chelsio T6 network interfaces with a pair of T62100-LP-CR cards. I have been using these recently for VPP development on FreeBSD and while they work well I have been having a lot of trouble doing any packet generation with them.

I got pretty miserable performance using netmaps pkt-gen on FreeBSD and trying Linux the comparative oddness of the T6 drivers seems to trip on the TRex packet generator suite on Linux.

From reading Chelsio's own documentation it seemed I was missing something, they have benchmarks using pkt-gen showing link saturation for small packets at 40Gbit/s on their T5 cards. What was up?

When I run a pkt-gen (lives in tools/tools/netmap ) benchmark I get a little under 1 Gbit/s of traffic:

$ sudo ./pkt-gen -f tx -i cc0 -S 00:07:43:74:3f:e9 -D 00:07:43:74:3f:e1
...
250.554583 main_thread [2713] 9.012 Mpps (9.587 Mpkts 4.326 Gbps in 1063799 usec) 512.00 avg_batch 0 min_space

~9 Mpps isn't that great, I paid for 100 Gbit and 4 just isn't enough.

FreeBSD has a zero copy userspace networking framework called netmap , it cleverly hooks into drivers early in their packet processing pipeline and if there is a netmap application running steals them so the host doesn't see them. Aiding with support, if the app doesn't want the given packet it can be given back to the driver and normal processing can occur.

Drivers can be natively supported with netmap or if support isn't available an emulated mode driver can be used.

Eventually after looking really really hard at the Chelsio examples I saw that the network interface names were different from mine. They were prefixed with a 'v', 'vcxl' rather than 'cxl'.

A bunch of googling later I read the top cxgbe(4) correctly:

The cxgbe driver uses different names for devices based on the associated
ASIC:

      ASIC    Port Name    Parent Device    Virtual Interface
      T4      cxgbe        t4nex            vcxgbe
      T5      cxl          t5nex            vcxl
      T6      cc           t6nex            vcc

To get a virtual interface we need to set the hw.cxgbe.num_vis sysctl to a value greater than 1. I added the following to my /boot/loader.conf :

# Add a virtual port for cxgbe                                     
hw.cxgbe.num_vis="2"            # Number of VIs per port           
hw.cxgbe.nnmrxq_vi="8"          # Number of netmap RX queues per VI
hw.cxgbe.nnmtxq_vi="8"          # Number of netmap TX queues per VI
hw.cxgbe.nrxq_vi="8"            # Number of RX queues per VI       
hw.cxgbe.ntxq_vi="8"            # Number of TX queues per VI

On a reboot I have a shiny new vcc0 device, letting pkt-gen run there I get much nicer results:

$ sudo ./pkt-gen -f tx -i vcc0 -S 00:07:43:74:3f:e9 -D 00:07:43:74:3f:e1 
...
329.706767 main_thread [2713] 68.770 Mpps (73.060 Mpkts 33.010 Gbps in 1062380 usec) 480.61 avg_batch 99999 min_space

~70 Mpps and 33 Gbps of 64 byte packets is pretty great , with an imix I can saturate the 100Gbps with pkt-gen .

I didn't find anything about this differentiator by searching, pkt-gen doesn't mention it. There are some dmesg lines, but if you don't know, you don't know.

238.184192 [1135] generic_netmap_attach     Emulated adapter for cc0 created (prev was NULL)
238.192821 [ 320] generic_netmap_register   Emulated adapter for cc0 activated
238.200244 [1889] netmap_interp_ringid      invalid ring id 1
238.206046 [ 295] generic_netmap_unregister Emulated adapter for cc0 deactivated

Booting Linux on the Pine64 Ox64 SBC

The Ox64 (notice that is a letter 'O' not a 0) is a tiny microcontroller sized single board computer from Pine64. The Ox64 is built around the bl808 SOC from Bouffalo Lab. It has one application level 64 bit RISC-V core, a 32 bit RISC-V core and an extra little 32 bit RISC-V core for handling io.

It is a computer in the form factor of a microcontroller and it is built around what seems to be an open eco system. The Ox64 is early in its lifetime and Pine64 are quite good at making hardware for the community to build software around. That means that software is early and there is much to do (as I learned) to get code running.

Lets get Bouffalo's example Linux build going on this tiny thing.

First signs of life

There is quite a bit of documentation and write ups in the wiki page which helped me get going.

The links in the wiki aren't super clear about how to get any sort of working Linux image on the Ox64. Before trying any of those I wanted to get some signs of life out of the board. A two hour round trip to the hackerspace later I had headers soldered on to my two boards and connected up the Waveshare USB uart I got with the VisionFive2 kit.

The instructions aren't clear about where to expect the initial uart output to appear. The pinout on the wiki has RXD and TXD marked in several places, but also GPIO14/GPIO15 for UART0 TX adn UART0 RX. Two baud rates are listed 115200 and 2000000 (this might be faster than your usb uart can go), I got the following out of GPIO14/15 with a baud rate of 2000000.

$ sudo cu -l /dev/ttyU0 -s 2000000

 ____               __  __      _       _       _     
|  _ \             / _|/ _|    | |     | |     | |    
| |_) | ___  _   _| |_| |_ __ _| | ___ | | __ _| |__  
|  _ < / _ \| | | |  _|  _/ _` | |/ _ \| |/ _` | '_ \ 
| |_) | (_) | |_| | | | || (_| | | (_) | | (_| | |_) |
|____/ \___/ \__,_|_| |_| \__,_|_|\___/|_|\__,_|_.__/ 

Build:19:50:39,Nov 20 2022
Copyright (c) 2022 Bouffalolab team
dynamic memory init success,heap size = 93 Kbyte 
sig1:ffff32ff
sig2:0000ffff
Pong!
Ping!
Pong!
Ping!
Pong!

Flashing with DevCube

The buildroot instructions on the wiki have an example usage of the SDK to build firmware and how to flash boot firmware for the two cores.

Bouffalo's flashing tool is called Devcube, they provide binaries for Linux, Mac OS and Windows. The Linux Binary (version 1.8.4) refused to run on FreeBSD with Linux compat giving me:

$ ./BLDevCube-ubuntu 
PySide2/__init__.py: Unable to import shiboken2 from /tmp/_MEIN3lvWA/base_library.zip, /tmp/_MEIN3lvWA/lib-dynload, /tmp/_MEIN3lvWA
/lib64/libc.so.6: version `GLIBC_2.18' not found (required by /tmp/_MEIN3lvWA/libstdc++.so.6)

I don't have a lot of experience using Linux compat and I want to get this board going in less than a month of work so I flashed from Mac OS.

In Mac OS I was able to run Devcube, but kept hitting failures trying to flash the firmware images onto the board. Eventually I started trying more extreme things, the wiki page suggested that certain USB uart chips just don't work, so I tried another adapter. Another suggested solution was to write a USB uart firmware on to a pi pico and use that. I pulled the pi pico from my BluesSCSI v2, copied the firmware on to the virtual msdos file system and that finally worked.

With the firmware written the output on uart0 changed to:

[I][] 
[I][]   ____                   ____               __  __      _       
[I][]  / __ \                 |  _ \             / _|/ _|    | |      
[I][] | |  | |_ __   ___ _ __ | |_) | ___  _   _| |_| |_ __ _| | ___  
[I][] | |  | | '_ \ / _ \ '_ \|  _ < / _ \| | | |  _|  _/ _` | |/ _ \ 
[I][] | |__| | |_) |  __/ | | | |_) | (_) | |_| | | | || (_| | | (_) |
[I][]  \____/| .__/ \___|_| |_|____/ \___/ \__,_|_| |_| \__,_|_|\___/ 
[I][]        | |                                                      
[I][]        |_|                                                      
[I][] 
[I][] Powered by BouffaloLab
[I][] Build:11:52:22,Mar  6 2023
[I][] Copyright (c) 2023 OpenBouffalo team
[I][] Copyright (c) 2022 Bouffalolab team
[I][] =========== flash cfg ==============
[I][] jedec id   0xEF6018
[I][] mid            0xEF
[I][] iomode         0x04
[I][] clk delay      0x01
[I][] clk invert     0x01
[I][] read reg cmd0  0x05
[I][] read reg cmd1  0x35
[I][] write reg cmd0 0x01
[I][] write reg cmd1 0x31
[I][] qe write len   0x01
[I][] cread support  0x00
[I][] cread code     0xFF
[I][] burst wrap cmd 0x77
[I][] sector size:   0x04
[I][] =====================================
[I][] dynamic memory init success,heap size = 156 Kbyte 
[I][MAIN] Starting Mailbox Handlers
[I][MBOX] Forwarding Interupt SDH (33) to D0 (0x58008bbc)
[I][MBOX] Forwarding Interupt GPIO (60) to D0 (0x58008d0e)
[I][MAIN] Running...
[I][MBOX] Mailbox IRQ Stats:
[I][MBOX]   Peripheral SDH (33): 0
[I][MBOX]   Peripheral GPIO (60): 0
[I][MBOX] Unhandled Interupts: 0 Unhandled Signals 0
[I][MBOX] ====================================
[I][MBOX] Mailbox IRQ Stats:
[I][MBOX]   Peripheral SDH (33): 0
[I][MBOX]   Peripheral GPIO (60): 0
[I][MBOX] Unhandled Interupts: 0 Unhandled Signals 0
[I][MBOX] ====================================
[I][MBOX] Mailbox IRQ Stats:
[I][MBOX]   Peripheral SDH (33): 0
[I][MBOX]   Peripheral GPIO (60): 0
[I][MBOX] Unhandled Interupts: 0 Unhandled Signals 0
[I][MBOX] ====================================

This is promising and nice to see firmware giving us a channel for further debugging in the future.

I wrote the example sd card image onto a card and tried to boot and got nothing.

In the instructions I spotted that the uart for u-boot/linux was on gpio 16/17 on the other side of the board to where I had soldered headers (I only had enough right angled headers to do one side). Luckily, I soldered straight headers onto all pins of the second Ox64 I got for working on this.

With those connected I still got nothing, I tried all the uarts on the pinout in the wiki. Dropping to 115200 for the baud because some disconnected messages in the discord suggested that might be the issue. I verified that the baud should be 2000000 in the DTB files.

I gave in, joined the Pine64 discord and asked what might be up. Arne said that Devcube 1.8.4 has some konwn issues and I should try Devcube 1.8.3 . This version didn't like uploading the bl808-firmware.bin file, it was unable to establish a connection to the firmware and gave me the same errors I saw before when I was using the USB uart adapter.

I tried fruitlessly many combinations of boot switches and power cycles, but I couldn't get the firmware to write.

Not sure what to try next I connected up to the system uart to see if I could get any further hints and instead I got a booting Linux:

[I][] 
[I][]   ____                   ____               __  __      _       
[I][]  / __ \                 |  _ \             / _|/ _|    | |      
[I][] | |  | |_ __   ___ _ __ | |_) | ___  _   _| |_| |_ __ _| | ___  
[I][] | |  | | '_ \ / _ \ '_ \|  _ < / _ \| | | |  _|  _/ _` | |/ _ \ 
[I][] | |__| | |_) |  __/ | | | |_) | (_) | |_| | | | || (_| | | (_) |
[I][]  \____/| .__/ \___|_| |_|____/ \___/ \__,_|_| |_| \__,_|_|\___/ 
[I][]        | |                                                      
[I][]        |_|                                                      
[I][] 
[I][] Powered by BouffaloLab
[I][] Build:11:52:04,Mar  6 2023
[I][] Copyright (c) 2023 OpenBouffalo team
[I][] Copyright (c) 2022 Bouffalolab team
[I][] dynamic memory init success,heap s[I][LowLoad] D0 start...
[I][LowLoad] low_load start... 
[I][LowLoad] Header at 0x5d5ff000
[I][LowLoad] Section dtb(1) - Start 0x5d5ff100, Size 14314
[I][LowLoad] Copying DTB to 0x51ff8000...0x51ffb7ea
[I][LowLoad] Done!
[I][LowLoad] Section OpenSBI(2) - Start 0x5d60f100, Size 109864
[I][LowLoad] Copying OpenSBI to 0x3ef80000...0x3ef9ad28
[I][LowLoad] Done!
[I][LowLoad] Section Kernel(3) - Start 0x5d62f100, Size 315597
[I][LowLoad] Uncompressing Kernel to 0x50000000...
[I][LowLoad] Done!
[I][LowLoad] CRC: 00000000
[I][LowLoad] load time: 61311 us 
[I][LowLoad] Setting PMP
[I][LowLoad] Booting OpenSBI at 0x000000003ef80000 with DTB at 0x51ff8000

OpenSBI v1.2
  ____                    _____ ____ _____
 / __ \                  / ____|  _ \_   _|
| |  | |_ __   ___ _ __ | (___ | |_) || |
| |  | | '_ \ / _ \ '_ \ \___ \|  _ < | |
| |__| | |_) |  __/ | | |____) | |_) || |_
 \____/| .__/ \___|_| |_|_____/|____/_____|
       | |
       |_|

  Platform Name             : Pine64 Ox64 (D0)
  Platform Features         : medeleg
  Platform HART Count       : 1
  Platform IPI Device       : aclint-mswi
  Platform Timer Device     : aclint-mtimer @ 1000000Hz
  Platform Console Device   : bflb_uart
  Platform HSM Device       : ---
  Platform PMU Device       : ---
  Platform Reboot Device    : ---
  Platform Shutdown Device  : ---
  Firmware Base             : 0x3ef80000
  Firmware Size             : 200 KB
  Runtime SBI Version       : 1.0

  Domain0 Name              : root
  Domain0 Boot HART         : 0
  Domain0 HARTs             : 0*
  Domain0 Region00          : 0x00000000e4008000-0x00000000e400bfff (I)
  Domain0 Region01          : 0x00000000e4000000-0x00000000e4007fff (I)
  Domain0 Region02          : 0x000000003ef80000-0x000000003efbffff ()
  Domain0 Region03          : 0x0000000000000000-0xffffffffffffffff (R,W,X)
  Domain0 Next Address      : 0x0000000050000000
  Domain0 Next Arg1         : 0x0000000051ff8000
  Domain0 Next Mode         : S-mode
  Domain0 SysReset          : yes

  Boot HART ID              : 0
  Boot HART Domain          : root
  Boot HART Priv Version    : v1.11
  Boot HART Base ISA        : rv64imafdcvx
  Boot HART ISA Extensions  : time
  Boot HART PMP Count       : 8
  Boot HART PMP Granularity : 4096
  Boot HART PMP Address Bits: 38
  Boot HART MHPM Count      : 8
  Boot HART MIDELEG         : 0x0000000000000222
  Boot HART MEDELEG         : 0x000000000000b109


  U-Boot 2023.04-rc2 (Mar 06 2023 - 11:48:40 +0000)

  DRAM:  64 MiB
  Core:  36 devices, 17 uclasses, devicetree: board
  MMC:   mmc@20060000: 0
  Loading Environment from FAT... Unable to read "uboot.env" from mmc0:2... 
  Loading Environment from nowhere... OK
  In:    serial@30002000
  Out:   serial@30002000
  Err:   serial@30002000
  Net:   
  Warning: emac@20070000 (eth0) using random MAC address - fa:6f:53:9f:5c:36
  eth0: emac@20070000
  Hit any key to stop autoboot:  0 
switch to partitions #0, OK
mmc0 is current device
Scanning mmc 0:2...
Found /extlinux/extlinux.conf
Retrieving file: /extlinux/extlinux.conf
Select the boot mode
1:  Pine64 0X64 Kernel
2:  Sipeed M1SDock Kernel
Enter choice: 1
1:  Pine64 0X64 Kernel
Retrieving file: /extlinux/../Image
append: root=PARTLABEL=rootfs rootwait rw rootfstype=ext4 console=ttyS0,2000000 loglevel=8 earlycon=sbi
Retrieving file: /extlinux/../bl808-pine64-ox64.dtb
## Flattened Device Tree blob at 51ff8000
Booting using the fdt blob at 0x51ff8000
Working FDT set to 51ff8000
Loading Device Tree to 0000000053f22000, end 0000000053f25fab ... OK
Working FDT set to 53f22000

Starting kernel ...

[    0.000000] Linux version 6.2.0 (runner@fv-az587-938) (riscv64-unknown-linux-gnu-gcc (Xuantie-900 linux-5.10.4 glibc gcc Toolchain V2.6.1 B-20220906) 10.2.0, GNU ld (GNU Binutils) 2.35) #1 Mon Mar  6 11:17:27 UTC 2023
[    0.000000] OF: fdt: Ignoring memory range 0x50000000 - 0x50200000
[    0.000000] Machine model: Pine64 Ox64
[    0.000000] earlycon: sbi0 at I/O port 0x0 (options '')
[    0.000000] printk: bootconsole [sbi0] enabled
[    0.000000] efi: UEFI not found.
[    0.000000] Zone ranges:
[    0.000000]   DMA32    [mem 0x0000000050200000-0x0000000053ffffff]
[    0.000000]   Normal   empty
[    0.000000] Movable zone start for each node
[    0.000000] Early memory node ranges
[    0.000000]   node   0: [mem 0x0000000050200000-0x0000000053ffffff]
[    0.000000] Initmem setup node 0 [mem 0x0000000050200000-0x0000000053ffffff]
[    0.000000] SBI specification v1.0 detected
[    0.000000] SBI implementation ID=0x1 Version=0x10002
[    0.000000] SBI TIME extension detected
[    0.000000] SBI IPI extension detected
[    0.000000] SBI RFENCE extension detected
[    0.000000] riscv: base ISA extensions acdfim
[    0.000000] riscv: ELF capabilities acdfim
[    0.000000] pcpu-alloc: s0 r0 d32768 u32768 alloc=1*32768
[    0.000000] pcpu-alloc: [0] 0 
[    0.000000] Built 1 zonelists, mobility grouping on.  Total pages: 15655
[    0.000000] Kernel command line: root=PARTLABEL=rootfs rootwait rw rootfstype=ext4 console=ttyS0,2000000 loglevel=8 earlycon=sbi
[    0.000000] Dentry cache hash table entries: 8192 (order: 4, 65536 bytes, linear)
[    0.000000] Inode-cache hash table entries: 4096 (order: 3, 32768 bytes, linear)
[    0.000000] mem auto-init: stack:off, heap alloc:off, heap free:off
[    0.000000] Memory: 48084K/63488K available (3941K kernel code, 4584K rwdata, 2048K rodata, 2118K init, 301K bss, 15404K reserved, 0K cma-reserved)
[    0.000000] SLUB: HWalign=64, Order=0-3, MinObjects=0, CPUs=1, Nodes=1
[    0.000000] NR_IRQS: 64, nr_irqs: 64, preallocated irqs: 0
[    0.000000] riscv-intc: 64 local interrupts mapped
[    0.000000] plic: interrupt-controller@e0000000: mapped 64 interrupts with 1 handlers for 2 contexts.
[    0.000000] riscv-timer: riscv_timer_init_dt: Registering clocksource cpuid [0] hartid [0]
[    0.000000] clocksource: riscv_clocksource: mask: 0xffffffffffffffff max_cycles: 0x1d854df40, max_idle_ns: 3526361616960 ns
[    0.000003] sched_clock: 64 bits at 1000kHz, resolution 1000ns, wraps every 2199023255500ns
[    0.000920] Console: colour dummy device 80x25
[    0.001166] Calibrating delay loop (skipped), value calculated using timer frequency.. 2.00 BogoMIPS (lpj=4000)
[    0.001687] pid_max: default: 32768 minimum: 301
[    0.002408] Mount-cache hash table entries: 512 (order: 0, 4096 bytes, linear)
[    0.002749] Mountpoint-cache hash table entries: 512 (order: 0, 4096 bytes, linear)
[    0.007710] cblist_init_generic: Setting adjustable number of callback queues.
[    0.008028] cblist_init_generic: Setting shift to 0 and lim to 1.
[    0.008940] ASID allocator using 16 bits (65536 entries)
[    0.010160] EFI services will not be available.
[    0.011191] devtmpfs: initialized
[    0.014879] clocksource: jiffies: mask: 0xffffffff max_cycles: 0xffffffff, max_idle_ns: 7645041785100000 ns
[    0.015375] futex hash table entries: 256 (order: 0, 6144 bytes, linear)
[    0.016003] pinctrl core: initialized pinctrl subsystem
[    0.018536] NET: Registered PF_NETLINK/PF_ROUTE protocol family
[    0.019435] DMA: preallocated 128 KiB GFP_KERNEL pool for atomic allocations
[    0.019823] DMA: preallocated 128 KiB GFP_KERNEL|GFP_DMA32 pool for atomic allocations
[    0.025441] bflb-ipc 30005000.mailbox: Bouffalo Lab IPC mailbox interrupt controller
[    0.030626] SCSI subsystem initialized
[    0.033265] clocksource: Switched to clocksource riscv_clocksource
[    0.061833] NET: Registered PF_INET protocol family
[    0.062497] IP idents hash table entries: 2048 (order: 2, 16384 bytes, linear)
[    0.064593] tcp_listen_portaddr_hash hash table entries: 512 (order: 0, 4096 bytes, linear)
[    0.065029] Table-perturb hash table entries: 65536 (order: 6, 262144 bytes, linear)
[    0.065544] TCP established hash table entries: 512 (order: 0, 4096 bytes, linear)
[    0.065953] TCP bind hash table entries: 512 (order: 1, 8192 bytes, linear)
[    0.066387] TCP: Hash tables configured (established 512 bind 512)
[    0.067071] UDP hash table entries: 256 (order: 1, 8192 bytes, linear)
[    0.067407] UDP-Lite hash table entries: 256 (order: 1, 8192 bytes, linear)
[    0.068175] NET: Registered PF_UNIX/PF_LOCAL protocol family
[    0.070709] workingset: timestamp_bits=62 max_order=14 bucket_order=0
[    0.073019] NET: Registered PF_ALG protocol family
[    0.073455] Block layer SCSI generic (bsg) driver version 0.4 loaded (major 252)
[    0.073801] io scheduler mq-deadline registered
[    0.074056] io scheduler kyber registered
[    0.074387] io scheduler bfq registered
[    0.075570] bflb-gpio-pinctrl 200008c4.pinctrl: No cache defaults, reading back from HW
[    0.077954] bflb-gpio-pinctrl 200008c4.pinctrl: Bouffalo Lab pinctrl+GPIO(+interrupt) controller - Registered 32 function(s) for 46 pin(s)
[    0.083806] 30002000.serial: ttyS0 at MMIO 0x30002000 (irq = 2, base_baud = 2500000) is a BFLB UART
[    0.084288] printk: console [ttyS0] enabled
[    0.084288] printk: console [ttyS0] enabled
[    0.084735] printk: bootconsole [sbi0] disabled
[    0.084735] printk: bootconsole [sbi0] disabled
[    0.086406] 2000aa00.serial: ttyS1 at MMIO 0x2000aa00 (irq = 3, base_baud = 2500000) is a BFLB UART
[    0.115172] brd: module loaded
[    0.131893] loop: module loaded
[    0.133183] physmap-flash 58500000.xip_flash: physmap platform flash device: [mem 0x58500000-0x588fffff]
[    0.139081] wireguard: WireGuard 1.0.0 loaded. See www.wireguard.com for information.
[    0.139577] wireguard: Copyright (C) 2015-2019 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
[    0.140210] PPP generic driver version 2.4.2
[    0.140910] PPP BSD Compression module registered
[    0.141223] PPP Deflate Compression module registered
[    0.142332] sdhci: Secure Digital Host Controller Interface driver
[    0.142723] sdhci: Copyright(c) Pierre Ossman
[    0.142993] sdhci-pltfm: SDHCI platform and OF driver helper
[    0.144330] mmc0 bounce up to 128 segments into one, max segment size 65536 bytes
[    0.145494] ledtrig-cpu: registered to indicate activity on CPUs
[    0.146299] bflb-seceng 20004000.seceng: No cache defaults, reading back from HW
[    0.149836] random: crng init done
[    0.150084] bflb-seceng 20004000.seceng: Bouffalo Lab Secure Engine
[    0.151045] riscv-pmu-sbi: SBI PMU extension is available
[    0.151453] riscv-pmu-sbi: 16 firmware and 10 hardware counters
[    0.153359] NET: Registered PF_INET6 protocol family
[    0.156148] Segment Routing with IPv6
[    0.156517] In-situ OAM (IOAM) with IPv6
[    0.156953] sit: IPv6, IPv4 and MPLS over IPv4 tunneling driver
[    0.158856] NET: Registered PF_PACKET protocol family
[    0.159229] Key type dns_resolver registered
[    0.183293] debug_vm_pgtable: [debug_vm_pgtable         ]: Validating architecture page table helpers
[    0.197399] mmc0: SDHCI controller on 20060000.sdhci [20060000.sdhci] using DMA
[    0.198161] Waiting for root device PARTLABEL=rootfs...
[    0.489067] mmc0: new high speed SDHC card at address b368
[    0.491548] mmcblk0: mmc0:b368 NCard 29.1 GiB 
[    0.510192]  mmcblk0: p1 p2 p3
[    0.551381] EXT4-fs (mmcblk0p3): mounted filesystem c0eebeb6-58dd-4946-bc48-e8c2c43098cc with ordered data mode. Quota mode: disabled.
[    0.552227] VFS: Mounted root (ext4 filesystem) on device 179:3.
[    0.559988] devtmpfs: mounted
[    0.568251] Freeing unused kernel image (initmem) memory: 2116K
[    0.568740] Run /sbin/init as init process
[    0.569018]   with arguments:
[    0.569212]     /sbin/init
[    0.569447]   with environment:
[    0.569654]     HOME=/
[    0.569812]     TERM=linux
[    0.841528] EXT4-fs (mmcblk0p3): re-mounted c0eebeb6-58dd-4946-bc48-e8c2c43098cc. Quota mode: disabled.
[    1.647489] Adding 1048572k swap on /dev/mmcblk0p1.  Priority:-2 extents:1 across:1048572k SS
Root Partition Already resized
Starting syslogd: OK
Starting klogd: OK
Running sysctl: OK
Starting mdev... OK
Initializing random number generator: OK
Saving random seed: OK
Starting rpcbind: OK
Starting iptables: OK
Starting network: OK
Starting dhcpcd...
dhcpcd-9.4.1 starting
dhcp_vendor: Invalid argument
forked to background, child pid 103
Starting sntp: sntp 4.2.8p15@1.3728-o Mon Mar  6 10:45:15 UTC 2023 (1)
pool.ntp.org lookup error Temporary failure in name resolution
FAIL
Starting ntpd: OK
Starting dropbear sshd: OK

Welcome to Buildroot
ox64 login:

Cool, that is Linux going, with only a lot of hassle. I wonder what I could try next?