diff --git a/docs/kernel-notes.md b/docs/kernel-notes.md index eb5bb96..7f324b7 100644 --- a/docs/kernel-notes.md +++ b/docs/kernel-notes.md @@ -12,7 +12,7 @@ the code in `kernel/` is built on. | 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` (~30–40× faster than gpiod) | `iec_lines.h` | -| GPIO access (init/exit) | gpiod descriptor API (`gpio_to_desc`, `gpiod_to_irq`) | `iec_init`/`iec_exit` | +| GPIO access (init/exit) | gpiod descriptor API; descriptors resolved by `(chip, hwnum)` via `gpio_device_find_by_label("pinctrl-bcm2835")` + `gpio_device_get_desc()`, **not** `gpio_to_desc()` (see "GPIO descriptor lookup" below) | `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) | @@ -35,7 +35,46 @@ shifter** (BSS138, sd2iec-style single DATA pin, §3.1). That choice: 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). +before connecting the C64 (PLAN.md §10 risk row). See +[`../kernel/README.md`](../kernel/README.md#self-test) for how to run it +(`kernel/selftest.sh`), the result bitmask, and the bare-board caveat. + +## GPIO descriptor lookup (do **not** use `gpio_to_desc()`) + +`iec_init` resolves the four IEC line descriptors **by chip label + hardware +offset**, not by the legacy global GPIO number: + +```c +iec_gdev = gpio_device_find_by_label("pinctrl-bcm2835"); /* ref held until exit */ +gd_atn = gpio_device_get_desc(iec_gdev, IEC_GPIO_ATN); /* hwnum == BCM number */ +... /* check with IS_ERR() */ +``` + +**Why — this bit us once (`insmod: No such device` / `-ENODEV`).** On current +Raspberry Pi OS kernels (6.12 here) the BCM2835 GPIO controller no longer starts +at global number 0 — it's `gpiochip512`, **base 512**: + +``` +$ cat /sys/class/gpio/gpiochip*/base # -> 512 (pinctrl-bcm2835), 566 (exp-gpio) +``` + +The old code used `gpio_to_desc(2/3/17/18)`, i.e. the *global* numberspace +assuming base 0. Those small numbers now fall outside the chip's range +(512–565), so every `gpio_to_desc()` returned `NULL`, the descriptor check +tripped, and init bailed with `-ENODEV`. The `(chip, hwnum)` lookup passes the +**BCM number as the chip-relative offset** (ATN=2, RESET=3, CLK=17, DATA=18), +which is base-independent and survives kernel bumps / gpiochip renumbering. + +Notes: +- `gpio_device_get_desc()` returns an `ERR_PTR` on a bad offset (not `NULL`) — + check with `IS_ERR()`, not `!desc`. +- `gpio_device_find_by_label()` takes a reference; it's released with + `gpio_device_put(iec_gdev)` in `iec_exit` and on the init error path. +- Both symbols are `EXPORT_SYMBOL_GPL` (fine — the module is GPL) and need + `#include `. +- If a future kernel renames the controller, update `IEC_GPIO_CHIP_LABEL`; + confirm the live label with `gpioinfo` / the `/sys/class/gpio/gpiochip*/label` + files on the Pi. ## Build & deploy (on the Pi) @@ -71,6 +110,47 @@ sudo apt-mark hold raspberrypi-kernel raspberrypi-kernel-headers raspberrypi-boo # Pinned: raspberrypi-kernel () ``` +## Verify the module before loading it (vermagic) + +`insmod` refuses a module whose *vermagic* doesn't match the running kernel +(`Invalid module format` / `version magic ... should be ...` in `dmesg`). Always +check it after copying a freshly built `.ko` to the Pi: + +```bash +modinfo ~/iec_listener.ko | grep vermagic +uname -r # what the running kernel expects +``` + +**What correct looks like** — the kernel-version, `SMP`, `preempt`, and arch +tokens all match the running kernel (case differs: `modinfo` lowercases +`preempt`, `uname` prints `PREEMPT` — that's fine): + +``` +vermagic: 6.12.93+rpt-rpi-v8 SMP preempt mod_unload modversions aarch64 + ^^^^^^^^^^^^^^^^^^^ ^^^ ^^^^^^^ ^^^^^^^^ + = uname -r | | = arch (arm64) + SMP PREEMPT +``` + +The leading `6.12.93+rpt-rpi-v8` **must equal `uname -r` exactly** — that token +is the only thing `insmod` hard-checks. `modversions` means symbol CRCs were +built against the matching `Module.symvers`, so symbol resolution is consistent +too. `mod_unload` just means `rmmod` is supported. All good → load it. + +**What wrong looks like** — any difference in the version token, e.g.: + +``` +vermagic: 6.6.51+rpt-rpi-v8 SMP preempt mod_unload modversions aarch64 + ^^^^^^ kernel moved on; uname -r says 6.12.93 → insmod rejects it +``` + +or a wrong/blank arch (`armv7l` vs `aarch64` → you built the 32-bit `-v7`/`-v6` +flavour by mistake), or a missing `modversions` (built against the wrong headers +tree). **The fix is always the same:** rebuild against headers matching the +*current* `uname -r` (re-run the one-liner above to get `HEADERS_PKG` / +`KERNEL_VERSION`), or keep the Pi pinned so the kernel can't drift out from +under the module. + ## Building off the Pi (emulated arm64 Docker) You can compile the module on a non-Pi (x86) host with `kernel/build-in-docker.sh` @@ -96,6 +176,20 @@ Configurable via env vars (kernel version is configurable as requested): KERNEL_VERSION=1:6.6.51-1+rpt3 ./build-in-docker.sh ``` +**Find the exact values for *your* Pi** — run this on the Pi (e.g. over SSH); it +prints the two lines ready to copy into the `build-in-docker.sh` invocation: + +```bash +pkg="linux-headers-$(uname -r | sed 's/.*+rpt-//')"; echo "HEADERS_PKG=$pkg KERNEL_VERSION=$(dpkg-query -W -f='${Version}' "$pkg")" +``` + +On the project's Pi Zero 2 W (`chris@10.1.0.41`, kernel `6.12.93+rpt-rpi-v8`) +this currently prints: + +```bash +HEADERS_PKG=linux-headers-rpi-v8 KERNEL_VERSION=1:6.12.93-1+rpt1 +``` + **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