Christian Werner b2e4f9710d
All checks were successful
Build kernel module / build (1:6.12.93-1+rpt1, bookworm) (pull_request) Successful in 1m1s
Build kernel module / build (1:6.18.34-1+rpt1, trixie) (pull_request) Successful in 1m8s
Build kernel module / package (pull_request) Successful in 29s
Build kernel module / release (pull_request) Has been skipped
refactor(kernel): align GPIO pin layout with the reference listener
Adopt the confirmed-working reference's pin assignment so the module runs
on that proven wiring: DATA=BCM2 (pin 3), CLK=BCM3 (pin 5), ATN=BCM4
(pin 7). RESET is kept and relocated to BCM17 (pin 11), the pin freed by
moving CLK. All five GPIOs stay in bank 0, so the direct-register hot
path and ATN IRQ are unchanged -- only the IEC_GPIO_* defines, the
device-tree overlay, and the docs/self-test move.

DATA/CLK now sit on the ARM I2C pins (GPIO2/3) with the SoC's fixed
~1.8k pull-ups; keep dtparam=i2c_arm off. The bare-board self-test
expectation changes from 0x15 to 0x0b accordingly.

Generated by Clanker
2026-06-20 16:09:10 +02:00

5.3 KiB

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_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.