comodore-iec-emu/docs/kernel-notes.md
2026-06-18 22:25:42 +02:00

5.8 KiB
Raw Blame History

Kernel module notes

Phase-0.5 decisions, resolved in _research/pi-kernel-module-rt-gpio-2026-06-18.md (read that for the full evidence and source-level analysis). This file is the short, actionable summary the code in kernel/ is built on.

Decisions baked into the code

Question Decision Where
CLK: interrupt vs. busy-poll ATN falling-edge hardirq enters the state machine; bytes are busy-polled with local_irq_save() held for one byte (ninepin pattern) atn_isr, receive_byte
IRQ discipline local_irq_save() around each byte only (~200 µs normal, ≤ 8 ms abort cap), not the whole ATN phase receive_byte
GPIO access (hot path) Direct BCM register access via ioremap (~3040× faster than gpiod) iec_lines.h
GPIO access (init/exit) gpiod descriptor API (gpio_to_desc, gpiod_to_irq) iec_init/iec_exit
Kernel↔userspace Character device /dev/iec0 + kfifo + wait queue (IEC ≤ 1000 B/s; relayfs not justified) iec_read, emit_record
udelay vs. poll Poll-with-timeout for CLK transitions; udelay only for fixed delays (EOI ack 80 µs, EOI detect 250 µs) iec_timing.h, wait_clk
isolcpus / nohz_full Not in the Phase-1 baseline. Add isolcpus=3 nohz_full=3 rcu_nocbs=3 irqaffinity=0-2 only if Phase-2 bit-error rate > 1% (boot cmdline)
PREEMPT_RT Stock kernel sufficient; RT is a last resort
Module signing Not required on stock RPi OS Bookworm
Peripheral base 0x3F000000 (BCM2710A1 / Pi Zero 2 W; not ninepin's 0x20000000) iec_lines.h

Level-shifter / DATA-sensing note (deviation from the research doc)

The research doc (§4.3) analysed a 7406 inverting drive + separate resistor divider sense, and flagged a "DATA sensing gap" (a 7406 output pin can't read the bus back). The PLAN instead specifies a non-inverting bidirectional level shifter (BSS138, sd2iec-style single DATA pin, §3.1). That choice:

  • makes the logic non-inverting: bus low (asserted) ⇒ Pi reads LOW;
  • closes the sensing gap — when the DATA pin is switched to input (Hi-Z), reading it back through the bidirectional shifter returns the real bus state the C64 is driving. This is exactly what receive_byte relies on for bit sampling.

So iec_lines.h is non-inverting; there is no 7406 inversion to track. Confirm with the IEC_IOC_SELFTEST ioctl (drive low → read low; release → read high) before connecting the C64 (PLAN.md §10 risk row).

Build & deploy (on the Pi)

sudo apt install raspberrypi-kernel-headers
cd kernel
make                       # iec_listener.ko
make overlay               # dts/iec-overlay.dtbo (optional pin reservation)
sudo insmod iec_listener.ko address=4
ls -l /dev/iec0
# ... talk to the C64 ...
sudo rmmod iec_listener    # releases DATA on the way out

Optional pin-reservation overlay (Bookworm paths — note the /boot/firmware/ prefix; the legacy /boot/ paths no longer apply on 64-bit Bookworm):

sudo cp dts/iec-overlay.dtbo /boot/firmware/overlays/
echo "dtoverlay=iec-overlay" | sudo tee -a /boot/firmware/config.txt
sudo reboot
# after reboot, verify it loaded:
dtoverlay -l

Pin the kernel before the first apt full-upgrade — any kernel bump breaks the module via a vermagic mismatch (Invalid module format), so hold the kernel packages up front rather than after the fact:

sudo apt-mark hold raspberrypi-kernel raspberrypi-kernel-headers raspberrypi-bootloader
# record the pinned version here once known:
# Pinned: raspberrypi-kernel <VERSION> (<DATE>)

Building off the Pi (emulated arm64 Docker)

You can compile the module on a non-Pi (x86) host with kernel/build-in-docker.sh (or make docker-build). It runs an emulated arm64 Raspberry Pi OS container, installs the raspberrypi kernel headers via apt, and builds natively so the module's vermagic matches the Pi.

cd kernel
./build-in-docker.sh                 # -> iec_listener.ko (arm64) in this dir
./build-in-docker.sh clean

Configurable via env vars (kernel version is configurable as requested):

Var Default Purpose
HEADERS_PKG linux-headers-rpi-v8 headers package; use -v7/-v6 for 32-bit, or raspberrypi-kernel-headers
KERNEL_VERSION (latest) exact version pin, e.g. 1:6.6.51-1+rpt3
IMAGE iec-kbuild builder image tag
KERNEL_VERSION=1:6.6.51-1+rpt3 ./build-in-docker.sh

vermagic caveat: the raspberrypi apt archive normally serves only the latest kernel in its pool, so pinning KERNEL_VERSION to an old release may not be downloadable. The reliable strategy is to keep the Pi current (sudo apt full-upgrade) and build with the default (latest) — then the Pi and the container agree. If you must target an older/specific kernel, copy the Pi's /lib/modules/$(uname -r)/build tree into the container instead of using apt.

uname -r is not used inside the container (it reports the host kernel under emulation); the entrypoint derives KDIR from the installed headers under /lib/modules/.

The host needs qemu binfmt for arm64; the script registers it once via tonistiigi/binfmt --install arm64 (a one-time privileged container).

Starting timing constants

See kernel/iec_timing.h. Tune in Phase 2 against a real C64 / logic analyser. The likely first knobs: IEC_EOI_DETECT_US (EOI false positives/negatives) and IEC_CLK_TIMEOUT_US (frame errors under load).

Open items to verify on hardware

  • GPIO IRQ latency on BCM2710A1 under representative load (target ATN ack ≪ 1 ms).
  • Direct-register read latency inside the IRQ-off loop on the A53 (budget vs. the 20 µs C64 bit window).
  • Whether isolcpus is needed once Phase 2 runs under WiFi/SD load.
  • IEC_GPIO_DATA direction-flip latency (GPFSEL write) — should be tens of ns.