docs(kernel): Update kernel notes with GPIO descriptor details and verification steps
Expand the documentation to include details on GPIO descriptor lookup using chip label and hardware offset, replacing the deprecated global GPIO number approach. Added instructions for verifying the module's version magic to ensure compatibility with the running kernel. These updates aim to prevent common initialization and deployment errors.
This commit is contained in:
parent
e0a78871ca
commit
e942cedbfa
@ -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` |
|
| 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` |
|
| 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 (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` |
|
| 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` |
|
| `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) |
|
| 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
|
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)
|
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 <linux/gpio/driver.h>`.
|
||||||
|
- 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)
|
## Build & deploy (on the Pi)
|
||||||
|
|
||||||
@ -71,6 +110,47 @@ sudo apt-mark hold raspberrypi-kernel raspberrypi-kernel-headers raspberrypi-boo
|
|||||||
# Pinned: raspberrypi-kernel <VERSION> (<DATE>)
|
# Pinned: raspberrypi-kernel <VERSION> (<DATE>)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## 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)
|
## Building off the Pi (emulated arm64 Docker)
|
||||||
|
|
||||||
You can compile the module on a non-Pi (x86) host with `kernel/build-in-docker.sh`
|
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
|
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
|
**vermagic caveat:** the raspberrypi apt archive normally serves only the
|
||||||
*latest* kernel in its pool, so pinning `KERNEL_VERSION` to an old release may
|
*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
|
not be downloadable. The reliable strategy is to keep the Pi current
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user