Exactly two months ago today, on February 27, the project restarted.
v0.27 is the milestone that closes that chapter. The system boots on real RISC-V hardware, enumerates a Samsung NVMe, mounts a filesystem, runs a staged init sequence, presents a login prompt, authenticates a user against a shadow password file using SHA-512 crypt(3), drops privileges, and hands off to mksh with the correct working directory and environment. The session below is from the SiFive HiFive Unmatched.
=> run bootqrv
397328 bytes read in 0 ms
4421120 bytes read in 4 ms (1 GiB/s)
Starting kernel ...
Boot command line: -Dsbi mainfs=/dev/nvme0n1p8
Board: SiFive HiFive Unmatched A00
Compatible: sifive,hifive-unmatched-a00
+------------------------------------------+
| QRV Operating System Kernel version 0.27 |
+------------------------------------------+
init_clocks
init_raminfo
init_mmu
ram_top = 480000000
prealloc_kernel_l2: filled 239 empty kernel L2 slots
enable_mmu: Sv39 paging active
init_intrinfo: timer(base=5,n=1) plic(base=32,n=128)
init_cpuinfo: 4 CPUs, rv64imac, 1 MHz
hwinfo: serial sifive,fu740-c000-uart @ 10010000 irq 39
hwinfo: pci sifive,fu740-pcie ecam df0000000/10000000 irq 57 windows 4
...
[2] kerlink: boot/taskman.qkx: resolved 6190/6190 external symbols (0 unresolved)
[2] kerlink: boot/taskman.qkx: applied 10756/10756 relocations (0 skipped)
...
[2] cpu_start_others: hart 1 started
[2] cpu_start_others: hart 3 started
[2] cpu_start_others: hart 4 started
[2] bootimage: spawned /sbin/init (pid 2)
***********************************
* Welcome to QRV Operating System *
***********************************
Starting serial console (devc-sersifive)...
system console set to /dev/ser1
Probing for NVMe...
nvme: Model: "SAMSUNG MZVL2512HCJQ-00B00"
nvme: Serial: "S675NF0R487649"
nvme: ns 1 capacity=488386 MiB
devb-nvme: GPT OK, 8 partition(s)
...
p6: LBA 144941056..857972735 (348160 MiB) name="Debian"
p7: LBA 857972736..891527167 (16384 MiB) name="swap"
p8: LBA 891527168..895721471 (2048 MiB) name="QRV"
Mounting /dev/nvme0n1p8 on /usr...
fs-qrv: qrvfs v2, 4096 blocks, 128 inodes
fs-qrv: mounted qrvfs at /usr (dev=/dev/nvme0n1p8)
Sysinit: level1 running.
QRV 0.27 (2026-04-28) on sifive,hifive-unmatched-a00
login: qrvuser
Password:
Welcome to QRV!
[/home/qrvuser]$ echo "Welcome to QRV on SiFive Unmatched!"
Welcome to QRV on SiFive Unmatched!
[/home/qrvuser]$
What This Required
Filesystem namespace flip
The most visible architectural change: the CPIO modpkg now mounts at /
instead of /rd/, and the NVMe filesystem mounts at /usr. The init
script, all binaries, and drivers live under the cpio root's bin/,
sbin/, lib/. Everything that belongs on persistent storage — config,
user home directories, extended tools — lives under /usr, contributed by
the qrvfs partition on NVMe. /etc resolves to /usr/conf via a
path-manager symlink. /home resolves to /usr/home the same way.
This is the Linux initramfs/rootfs split, applied to QRV's architecture. It
means the init script can be edited on disk without rebuilding the CPIO
image. It means /etc/passwd is just a file on the NVMe partition.
Staged init
The cpio /sbin/init is a minimal mksh script — about 70 lines — whose
only job is to get the console up, probe NVMe, and mount /usr. If that
fails, it drops to a rescue shell. If it succeeds, it hands off to
/usr/sbin/sysinit/level1.sh on the mounted disk.
level1.sh is where the real system initialization lives: pci-server,
devb-nvme, fs-qrv, slogger, and finally getty. Because it lives on the
NVMe partition, it can be edited, extended, and structured however needed
— conditionals, sourced fragments from /usr/conf/sysinit/*.sh, anything
mksh can express. A BSD-style rc framework can grow from here without
touching the cpio image.
Platform detection in init
The init script no longer starts both UART drivers and lets the wrong one
exit silently. It reads /sys/board — a new sysfs file populated from the
FDT /compatible property at boot — and picks the correct driver:
read -r BOARD < /sys/board
case "$BOARD" in
*sifive*) serdrv=devc-sersifive ;;
*) serdrv=devc-ser8250 ;;
esac
$serdrv
On QEMU virt: BOARD=riscv-virtio, driver=8250. On the Unmatched:
BOARD=sifive,hifive-unmatched-a00, driver=sersifive. One init script,
both platforms.
/sys filesystem
A new sysfs resource manager serves /sys — a Linux-style companion to
/proc for kernel self-description. Initial entries: /sys/cmdline (the
kernel boot command line, verbatim) and /sys/version (version string and
build date). /sys/board was added during platform detection work. The
kernel snapshots the command line before its in-place token split, so
/sys/cmdline always reflects exactly what U-Boot passed.
Canonical-mode line discipline
Both UART drivers (devc-ser8250 and devc-sersifive) now implement a
proper POSIX line discipline in ICANON mode: input accumulates in a line
buffer, a line is delivered to the reader only on \n, and backspace erases
correctly — both ASCII BS (0x08) and DEL (0x7F) are accepted, because
different terminal emulators send different bytes. ECHOE echoes \b \b per
erase. Without this, login: and Password: prompts couldn't be corrected
with backspace before pressing Enter.
crypt(3) and the full authentication chain
The complete authentication chain is now in place:
- SHA-512 implementation lifted from FreeBSD (
sys/crypto/sha2/sha512c.c, stripped to exactly what crypt(3) needs) - SHA-crypt (
$6$salt$hash) per Drepper's spec, from FreeBSD'slib/libcrypt/crypt-sha512.c getpass(3)disabling ECHO viatcsetattrfor password entrygetpwnam/getspnamreading/etc/passwdand/etc/shadowlogin(1)comparing the entered password against the shadow hash, then callingsetgid()+setuid()and exec'ing the user shell
getuid() / getgid() and their effective variants are now real: they call
ConnectClientInfo(-1, &info, 0) — a kernel call that returns the calling
process's own credential record directly, with no message-passing round-trip
and no self-message hazard. This is the openqnx pattern and it is the right
one: credentials come from the kernel's own credential store, not from a
procmgr query that would deadlock if called from within taskman itself.
Getty and login
/sbin/getty opens the terminal, prints the banner
(QRV <version> (<date>) on <board>), reads the login name via fgets,
and spawns /bin/login. It self-respawns on shell exit, so a disconnected
session automatically presents a new login prompt. /bin/login validates
credentials, sets HOME, PATH, SHELL, USER, LOGNAME via setenv(3),
changes to the home directory, and exec's the shell.
[/home/qrvuser]$ in the prompt is \w expanded by mksh's prompt
preprocessor at every redraw — the canonical directory, through the
/home → /usr/home symlink, shown correctly rather than the raw resolved
path.
Two Months
February 27 to April 27. In that time:
- Kernel booted to idle
- First ecall, first channel, Sv39 paging
- taskman loaded and running, 300+ symbols resolved
- Full IPC round-trip, dynamic linking, user-mode shell
- SMP stabilized
- PCI server, NVMe driver, GPT partitions
- Filesystem (qrvfs), mksh, signals, setjmp
- Multi-user login on a RISC-V workstation with 16 GiB of RAM and a 488 GiB Samsung NVMe
The partition that was left empty in July 2021 is now /usr.
The work continues.
No comments:
Post a Comment