NetBSD Port

Warning

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 / getc
    • tx_ready / putc
    • clear_errors
    • set_intr
    • intr_pending
    • program, which sets speed and frame format from termios flags. The example assumes a 16× divisor UART.
  • The compatible string 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:

  1. Add the device/attach/file lines to files.hb68k.
  2. Add a major number to majors.hb68k. 14 is free in your tree.
  3. Add a myuart* at fdt? pass 5 line to the kernel config.
  4. Add a device-tree node.
  5. 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

0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9