Document the process for setting up and running the Python frontend on a Raspberry Pi using release packages. Include details on wiring self-tests, module selection, and using `launch.sh` for streamlined execution. Update kernel README to cross-reference top-level changes.
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. 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.
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.
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; the ioctl number is
_IOR('I', 3, __u32) = 0x80044903.
Interpreting failures
DATA_ASSERT_OKmissing → 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_OKmissing → DATA stays low after release: missing/weak pull-up on DATA, or the pin didn't return to input.CLK_RELEASEDmissing → CLK reads low at idle: no pull-up on CLK, short, or a swapped signal.ATN_RELEASED/RESET_RELEASEDmissing → that line reads low at idle: short, missing pull-up, or swapped wiring.
⚠️ Bare-board caveat
With nothing connected, the expected result is 0x15, not a fault:
DATA_ASSERT_OK(0x01) passes — it's internal to the Pi.ATN_RELEASED(0x04) andRESET_RELEASED(0x10) pass only because GPIO2/GPIO3 have fixed ~1.8 kΩ pull-ups built into the BCM2710 SoC (they are the I²C0 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.DATA_FLOAT_OK(0x02) andCLK_RELEASED(0x08) read low because GPIO17/18 have no such built-in pull-up and nothing is attached.
So on a bare board the only meaningful signal is DATA_ASSERT_OK. A full 0x1F
is only reachable once the level shifter (with its CLK/DATA pull-ups) is wired
and powered. To make ATN/RESET meaningful, briefly ground each at the connector
and confirm the corresponding bit drops.