NetBSD Port
This page is very much a work in progress!
Building
I've forked a copy of the NetBSD sources.
Kernel
NetBSD contains a port called hb68k which is designed for any 680x0 sbc type of board and I used that to create a variant for my SBC, which I've called SBC3.
git clone https://github.com/znac049/netbsd-src.git cd netbsd-src ./build.sh -U -u -j4 -O ~/obj -m hb68k tools ./build.sh -U -u -j4 -O ~/obj -m hb68k kernel=SBC3 ./build.sh -U -u -j4 -O ~/obj -m hb68k distribution
- The first step builds the tools (compiler etc) that will be used to build the kernel and userspace.
- The next step builds the kernel.
- The final step takes a Long Time(tm) and builds the userspace, which will need to be added to the root filesystem.
I've still got to document how to build the root filesystem.
Device Driver Templates
I asked Claude about templates for coding device drivers. Here's what it said:
Uart
The template is myuart.c. It’s a complete single-channel UART driver for hb68k: it attaches through the device tree, can be the system console, and gives you a normal tty with interrupt-driven I/O.
What you change: the two CHIP-SPECIFIC sections near the top
- Register map: placeholder offsets and bit definitions for data, status, control, baud divisor and interrupt enable. The real byte offset is reg << reg-shift, so register spacing comes from the device tree rather than the code.
- Eight small hardware functions, the only code that touches the chip:
rx_ready/getctx_ready/putcclear_errorsset_intrintr_pendingprogram, which sets speed and frame format from termios flags. The example assumes a 16× divisor UART.
- The
compatiblestring your device-tree node will use.
Everything below those sections is generic, and you shouldn’t need to touch it for a first version: console, autoconfiguration, interrupt handling, soft interrupt, and the tty and cdevsw entry points. Each part has a comment explaining its role and the locking.
How data moves:
- Receive: the hard interrupt copies bytes into a ring buffer. A soft interrupt later passes them to the tty layer, because the hard interrupt mustn’t call into it.
- Transmit: the tty layer hands over a chunk of output. The transmit interrupt feeds it to the chip, then switches itself off when the chunk is done.
- Console: the console uses polling and works from very early in boot. BREAK on the console drops into DDB.
Setup steps, all listed at the top of the file:
- Add the device/attach/file lines to files.hb68k.
- Add a major number to majors.hb68k. 14 is free in your tree.
- Add a myuart* at fdt? pass 5 line to the kernel config.
- Add a device-tree node.
- On the target, make the device node by hand (mknod /dev/ttyM0 c 14 0), since MAKEDEV won’t know about the driver.
Deliberately left out:
- modem control (carrier is treated as always present)
- hardware flow control
- split input/output speeds
- sending BREAK
The ioctl function marks where the last of these goes. Your mc68681_duart.c is a worked example of the same structure extended to a two-channel chip, with an IVR, if you want to compare.
One constraint carries over from your board: the driver’s lock is a spin mutex at IPL_VM, so the chip’s interrupt level in the device tree must be 5 or lower.
Myuart C
Timer
Confirmed: a NULL argument makes the dispatcher pass the interrupt frame. Now a compile test:
The timer template is mytimer.c, built the same way as the UART one. I compiled it into an SBC3 kernel twice, once each way for the auto-reload setting, and both built cleanly. Then I took it back out of the tree.
What you change: the CHIP-SPECIFIC sections
- Registers: a placeholder register map for a simple down-counter (control, status, 24-bit reload).
- Limits and reload behaviour:
- MYTIMER_COUNT_MAX is the largest reload value.
- MYTIMER_PRESCALE is any fixed divider in front of the counter.
- MYTIMER_AUTO_RELOAD says whether the chip restarts itself at zero. If it doesn’t, the driver restarts it by hand on every tick. That loses a little time per tick, so the clock runs slightly slow.
- Three hardware functions:
- hw_stop() stops the counter and clears pending interrupts.
- hw_start(count) loads the count and starts periodic interrupts.
- hw_ack() clears the interrupt. Getting this wrong makes the machine appear to hang, because the CPU re-enters the handler immediately.
How it fits into hb68k: a timer driver provides only mechanism. The clock code in hb68k/clock.c owns the policy and calls the driver through four functions in a small table (struct clock_if):
Function When it’s called What it does clk_init once, late in boot establishes the interrupt at IPL_SCHED clk_arm(interval_us) once, right after init starts periodic interrupts, e.g. every 10 000 µs for 100 Hz clk_intr at the start of every tick acknowledges the interrupt, and reloads if needed clk_disarm shutdown or error stops the timer
The file header explains:
- How netbsd,hardclock / netbsd,statclock under /chosen pick each timer’s role, and what happens without them.
- Why the timer belongs at IPL 6.
- Which HZ values are allowed (they must divide 1 000 000).
- Why the interrupt handler’s argument must be NULL. I checked this in the dispatcher: with NULL, the clock code receives the interrupt frame, which it needs.
Setup is lighter than the UART’s:
- Add device/attach/file to files.hb68k.
- Add mytimer* at fdt? pass 3 to the config.
- Add a device-tree node with clocks or clock-frequency, plus an interrupts entry.
There’s no major number or /dev node, since nothing opens a timer.
Left out deliberately:
- A high-resolution timecounter. That needs a free-running counter, not a counter that resets every tick, so it depends on what your chip offers.
- Randomising the statclock interval. hb68k’s clock code doesn’t do this yet either.
Mytimer C