Added a 'Frontend output modes' section explaining plain text (default), --debug (annotated trace), and --raw (hex), with launch.sh examples for each. Generated by Clanker
131 lines
6.1 KiB
Markdown
131 lines
6.1 KiB
Markdown
# `iec_listener` kernel module
|
|
|
|
Commodore IEC **listener** (printer, default address 4) for the Raspberry Pi
|
|
Zero 2 W. The timing-critical IEC handshake runs in this kernel module; received
|
|
bytes and bus events are pushed to userspace through `/dev/iec0`.
|
|
|
|
Build/deploy, pinning, GPIO-descriptor and timing details live in
|
|
[`../docs/kernel-notes.md`](../docs/kernel-notes.md). This README documents the
|
|
**self-test**, which is the first thing to run after loading the module on real
|
|
hardware.
|
|
|
|
## Self-test
|
|
|
|
The module exposes a hardware self-test via the `IEC_IOC_SELFTEST` ioctl on
|
|
`/dev/iec0`. It drives the **DATA** line (the only line the Pi asserts) low and
|
|
reads it back, releases it, then samples **ATN / CLK / RESET**, returning a
|
|
bitmask of line states. Run it **with the C64 disconnected** to check the wiring
|
|
before connecting the real bus (see PLAN.md §10).
|
|
|
|
### Running it
|
|
|
|
`selftest.sh` is non-persistent (nothing is installed into `/lib/modules`, no
|
|
autoload): it detects the running kernel, picks the matching module, loads it,
|
|
runs the self-test, and always unloads it again.
|
|
|
|
A released package ships several kernel builds side by side, plus the launcher
|
|
and the Python frontend:
|
|
|
|
```
|
|
comodore-iec-emu/
|
|
├── selftest.sh # one-shot wiring self-test (this document)
|
|
├── launch.sh # load module → run the iecpoc frontend → unload
|
|
├── pyproject.toml # iecpoc packaging metadata (launch.sh pip-installs it)
|
|
├── README.md
|
|
├── iecpoc/ # the Python userspace decoder/trace
|
|
└── modules/
|
|
├── iec_listener_1-6.12.93-1+rpt1.ko # built for 6.12.x (bookworm)
|
|
└── iec_listener_1-6.18.34-1+rpt1.ko # built for 6.18.x (trixie)
|
|
```
|
|
|
|
`selftest.sh` only verifies the wiring (it loads and immediately unloads). To
|
|
actually capture and decode C64 traffic, use **`launch.sh`**, which installs the
|
|
frontend, loads the matching module, runs `iecpoc`, and unloads on exit — see the
|
|
[top-level README](../README.md#running-the-frontend-on-the-pi).
|
|
|
|
### Frontend output modes
|
|
|
|
`iecpoc` has three output modes, selected by flags passed after `--` to `launch.sh`
|
|
(or directly on the command line when running `iecpoc` standalone):
|
|
|
|
| Mode | Flag | Output |
|
|
|---|---|---|
|
|
| Plain text (default) | *(none)* | Received characters only; LISTEN/UNLISTEN print a divider; CR/LF become real newlines |
|
|
| Debug / annotated trace | `--debug` | Full `[CMD]` / `[DATA]` / `[ATN]` / `[IDLE]` tag-per-record trace |
|
|
| Raw hex | `--raw` | Bare uppercase hex bytes of command and data records, no annotations |
|
|
|
|
```bash
|
|
sudo ./launch.sh # plain text — what the C64 is printing
|
|
sudo ./launch.sh -- --debug # full annotated trace
|
|
sudo ./launch.sh -- --raw # bare hex stream
|
|
sudo ./launch.sh -- --logfile trace.txt # also write output to a file
|
|
```
|
|
|
|
You don't pick the file yourself: the script reads `uname -r`, then scans
|
|
`modules/` (and a few fallback locations) and selects the `.ko` whose **vermagic**
|
|
matches the running kernel. Matching is by vermagic rather than filename because
|
|
the packaged name carries a Debian epoch/revision (`1:6.12.93-1+rpt1`) that
|
|
`uname -r` (`6.12.93+rpt-rpi-v8`) does not.
|
|
|
|
```bash
|
|
sudo ./selftest.sh # auto-selects the matching module, address 4
|
|
sudo ./selftest.sh --address 5 # same, address 5
|
|
sudo ./selftest.sh ~/iec_listener.ko --address 5 # force a specific module file
|
|
```
|
|
|
|
If no module matches the running kernel, the script lists the modules it found
|
|
(with the kernel each was built for) and exits — rebuild for the current kernel,
|
|
or pass a path explicitly.
|
|
|
|
It exits non-zero unless the result is a full pass (`0x1F`), so it is usable in
|
|
scripts/CI.
|
|
|
|
### Result bitmask
|
|
|
|
The ioctl returns a `u32`; a full pass is **`0x1F`** (all five bits set).
|
|
|
|
| Bit | Value | Meaning | Source |
|
|
|-----|-------|---------|--------|
|
|
| `DATA_ASSERT_OK` | `0x01` | DATA read **low** while the module drives it low | Pi drive + sense path |
|
|
| `DATA_FLOAT_OK` | `0x02` | DATA read **high** after release (Hi-Z) | external pull-up on DATA |
|
|
| `ATN_RELEASED` | `0x04` | ATN high (idle) | line state |
|
|
| `CLK_RELEASED` | `0x08` | CLK high (idle) | line state |
|
|
| `RESET_RELEASED` | `0x10` | RESET high (idle) | line state |
|
|
|
|
Constants are defined in [`iec_listener.h`](iec_listener.h); the ioctl number is
|
|
`_IOR('I', 3, __u32)` = `0x80044903`.
|
|
|
|
### Interpreting failures
|
|
|
|
- **`DATA_ASSERT_OK` missing** → driving DATA low doesn't read back low: level
|
|
shifter wired backwards/inverting, wrong pin, or the sense path is broken.
|
|
This is the only bit that is fully internal to the Pi — if it fails, suspect
|
|
the module/build or the DATA wiring, not pull-ups.
|
|
- **`DATA_FLOAT_OK` missing** → DATA stays low after release: missing/weak
|
|
pull-up on DATA, or the pin didn't return to input.
|
|
- **`CLK_RELEASED` missing** → CLK reads low at idle: no pull-up on CLK, short,
|
|
or a swapped signal.
|
|
- **`ATN_RELEASED` / `RESET_RELEASED` missing** → that line reads low at idle:
|
|
short, missing pull-up, or swapped wiring.
|
|
|
|
### ⚠️ Bare-board caveat
|
|
|
|
With **nothing connected**, the expected result is **`0x0B`**, *not* a fault:
|
|
|
|
- `DATA_ASSERT_OK` (`0x01`) passes — it's internal to the Pi.
|
|
- `DATA_FLOAT_OK` (`0x02`) and `CLK_RELEASED` (`0x08`) pass **only because
|
|
DATA=GPIO2 and CLK=GPIO3 have fixed ~1.8 kΩ pull-ups built into the BCM2710
|
|
SoC** (they are the I²C pins; the pull-ups can't be disabled). They read high
|
|
even with nothing wired, so on a bare board these two bits prove **nothing**
|
|
about your wiring. In particular `DATA_FLOAT_OK` now floats high via the SoC
|
|
pull-up even without the shifter, so it no longer confirms the shifter's DATA
|
|
pull-up — it's only meaningful once the shifter is wired.
|
|
- `ATN_RELEASED` (`0x04`) and `RESET_RELEASED` (`0x10`) read low because
|
|
ATN=GPIO4 and RESET=GPIO17 have no such built-in pull-up and nothing is
|
|
attached.
|
|
|
|
So on a bare board the only fully internal signal is `DATA_ASSERT_OK`. A full
|
|
`0x1F` is only reachable once the level shifter (with its ATN/RESET pull-ups) is
|
|
wired and powered. To make ATN/RESET meaningful, briefly ground each at the
|
|
connector and confirm the corresponding bit *drops*.
|