update plan after kernal module reseach

This commit is contained in:
Christian Werner 2026-06-18 10:32:31 +02:00
parent d7a9b94e0a
commit e5e51754b2

View File

@ -62,11 +62,14 @@ From the research, a pure listener has a radically reduced surface:
|-------|-------------------------|--------------| |-------|-------------------------|--------------|
| ATN | C64 drives; we react | **input** | | ATN | C64 drives; we react | **input** |
| CLK | Talker (C64) drives during data; we sample | **input** | | CLK | Talker (C64) drives during data; we sample | **input** |
| DATA | Listener drives (ack ATN, ready, byte-ack, EOI-ack) | **output** (open-collector) | | DATA | Listener drives the handshake (ack ATN, ready, byte-ack, EOI-ack); talker drives the bits we sample | **bidirectional** (open-drain) |
| RESET | C64 drives; we react | **input** | | RESET | C64 drives; we react | **input** |
| SRQ | unused on stock C64 | not connected | | SRQ | unused on stock C64 | not connected |
So we drive **exactly one** bus line (DATA) and read three. No turnaround, no So we **actively drive exactly one** bus line (DATA, open-drain) and sense the
rest — but DATA doubles as an *input* during bit sampling (we release it to Hi-Z,
then read the talker's bits), which is why it is a single bidirectional pin (§3).
No turnaround, no
EOI generation, no 60 µs talker timing. The only timing the device generates is EOI generation, no 60 µs talker timing. The only timing the device generates is
the 1 ms ATN ack and the per-byte ack — both forgiving. The tight constraint is the 1 ms ATN ack and the per-byte ack — both forgiving. The tight constraint is
*sampling* the C64's ~20 µs bit clock while receiving (see §6). *sampling* the C64's ~20 µs bit clock while receiving (see §6).
@ -75,34 +78,50 @@ the 1 ms ATN ack and the per-byte ack — both forgiving. The tight constraint i
## 3. Hardware ## 3. Hardware
### 3.1 Interface circuit (per the research, simplified for one output) ### 3.1 Interface circuit (single bidirectional DATA pin, sd2iec-style)
- **DATA (drive):** open-collector driver. Two equivalent options: Design decision: DATA is a **single bidirectional pin** doing **open-drain
- **7406/74LS06** inverting OC buffer: Pi GPIO → 7406 input → bus. Pi HIGH ⇒ bus emulation**, mirroring the sd2iec one-line scheme rather than a separate
pulled low (asserted). *(Software must invert.)* drive-pin + sense-pin pair. Because the Pi is **not 5 V-tolerant**, the line goes
- **NPN transistor** (2N3904/BC547): Pi GPIO → 1 kΩ → base; emitter → GND; through a **bidirectional MOSFET level shifter** (BSS138 + pull-ups — the NXP
collector → DATA line. Pi HIGH ⇒ transistor on ⇒ bus low. Same inversion. AN10441 / common "logic level converter" topology). sd2iec needs no shifter only
- Use whichever is on hand; the 7406 also gives spare drivers for later (CLK, because its AVR runs at 5 V; the 3.3 V Pi does.
talker mode). Recommend the **7406** to match the eventual full device.
- **ATN, CLK, RESET (sense):** resistor divider 5 V→~3.1 V into each Pi input - **DATA (single pin, bidirectional):** GPIO 18 ↔ level shifter ↔ IEC DATA. The Pi
(e.g. 3.3 kΩ top / 2.2 kΩ to GND). **No inversion** in sensing: bus low (0 V, pin is never a push-pull driver of the bus; it emulates open-drain:
asserted) ⇒ Pi reads LOW. - **Assert** (pull bus low): GPIO 18 = **output LOW** → shifter pulls bus to 0 V.
- 100 nF bypass cap across the 7406 VCCGND. Common GND between C64 IEC pin 2 and Pi. - **Release / read**: GPIO 18 = **input (Hi-Z)** → the bus pull-ups define the
level and GPIO 18 reads the bus. **Non-inverting:** bus low (asserted) ⇒ Pi
reads LOW. (Releasing to Hi-Z is what lets us sample the talker's bits in §6.)
- Direction is flipped in the hot path via the BCM `GPFSEL` register — a single
register write, ~tens of ns, negligible against the 20 µs bit window.
- **ATN, CLK, RESET (sense, input only):** route through the **same kind of
shifter** (remaining channels of a 4-channel BSS138 board). Pi side always input.
**Non-inverting:** bus low (asserted) ⇒ Pi reads LOW. *(A resistor divider also
works for these input-only lines, but reusing the shifter gives a uniform, fully
in-spec 3.3 V swing and sidesteps the marginal divider voltages flagged in the
kernel research.)*
- **Recommended part:** one 4-channel bidirectional level-shifter board carries all
four lines (DATA bidirectional; ATN/CLK/RESET input-only). No 7406, no transistor,
no hand-matched dividers — low side to Pi 3.3 V, high side to bus 5 V.
- 100 nF bypass cap on the shifter VCC pins. Common GND between C64 IEC pin 2 and Pi.
- **Do not connect bus 5 V to any Pi pin directly — Pi GPIO is not 5 V tolerant.** - **Do not connect bus 5 V to any Pi pin directly — Pi GPIO is not 5 V tolerant.**
### 3.2 Pin map (Pi Zero 2 W, 40-pin header) ### 3.2 Pin map (Pi Zero 2 W, 40-pin header)
| IEC signal | Pi GPIO (BCM) | Header pin | Direction | Logic note | | IEC signal | Pi GPIO (BCM) | Header pin | Direction | Logic note |
|------------|---------------|------------|-----------|------------| |------------|---------------|------------|-----------|------------|
| ATN | GPIO 2 | 3 | input (divider) | bus low = Pi low = asserted | | ATN | GPIO 2 | 3 | input (via shifter) | bus low = Pi low = asserted |
| CLK | GPIO 17 | 11 | input (divider) | bus low = Pi low = asserted | | CLK | GPIO 17 | 11 | input (via shifter) | bus low = Pi low = asserted |
| DATA (in) | GPIO 18 | 12 | output → 7406 → bus | Pi HIGH = bus asserted (inverted) | | DATA | GPIO 18 | 12 | **bidirectional (via shifter)** | open-drain: assert = output LOW, release/read = input. bus low = Pi low = asserted |
| RESET | GPIO 3 | 5 | input (divider) | bus low = Pi low = asserted | | RESET | GPIO 3 | 5 | input (via shifter) | bus low = Pi low = asserted |
| GND | — | 6 (or any) | — | tie to IEC pin 2 | | GND | — | 6 (or any) | — | tie to IEC pin 2 and shifter GND |
> Convention used in code: a helper layer converts electrical reads/writes into > Convention used in code: a helper layer (`iec_lines.h`) converts electrical
> **logical** `asserted=True / released=False` so the rest of the code reasons in > reads/writes into **logical** `asserted=True / released=False`. With the
> the protocol's true/false sense and never juggles the inversions inline. > non-inverting level shifter the mapping is direct — assert ⇒ DATA GPIO output
> LOW, release ⇒ DATA GPIO input (Hi-Z), and a Pi LOW read on any line means
> asserted. No 7406 inversion to juggle.
--- ---
@ -205,9 +224,11 @@ never juggles inversions inline (mirrors the original userspace plan, now in C):
- `atn_asserted()` — true when ATN GPIO reads low - `atn_asserted()` — true when ATN GPIO reads low
- `clk_asserted()` — true when CLK GPIO reads low - `clk_asserted()` — true when CLK GPIO reads low
- `data_in()` — read DATA line (the talker drives it during bit transfer) - `data_in()` — read DATA line (the talker drives it during bit transfer; only
- `data_assert()` / `data_release()` — drive DATA true / float it; encapsulates valid after `data_release()` has set the pin to Hi-Z input)
the 7406 inversion (assert ⇒ Pi GPIO HIGH) - `data_assert()` / `data_release()` — assert DATA (set GPIO 18 **output LOW**) /
release it (set GPIO 18 **input / Hi-Z**, letting the bus float high). Open-drain
emulation through the level shifter; **non-inverting** (no 7406 to invert)
- `reset_asserted()` — true when RESET GPIO reads low - `reset_asserted()` — true when RESET GPIO reads low
### 5.2 Userspace record stream (`device.py`) ### 5.2 Userspace record stream (`device.py`)
@ -416,7 +437,7 @@ Requirements:
| Even in-kernel, busy-poll mid-byte gets preempted/IRQ'd and garbles a bit | Medium | Disable local IRQs/preemption for the duration of a byte; if still marginal, isolcpus + steer other IRQs away (raspbiec pattern); last resort `PREEMPT_RT`. Quantify in Phase 0.5. | | Even in-kernel, busy-poll mid-byte gets preempted/IRQ'd and garbles a bit | Medium | Disable local IRQs/preemption for the duration of a byte; if still marginal, isolcpus + steer other IRQs away (raspbiec pattern); last resort `PREEMPT_RT`. Quantify in Phase 0.5. |
| Keeping IRQs off too long for a byte harms system stability | Medium | Bound the off-window to one byte (~few hundred µs); re-enable between bytes. A design constraint to validate in Phase 0.5. | | Keeping IRQs off too long for a byte harms system stability | Medium | Bound the off-window to one byte (~few hundred µs); re-enable between bytes. A design constraint to validate in Phase 0.5. |
| Kernel module build/ABI churn against Raspberry Pi OS kernel | Medium | Build against installed kernel headers; pin kernel version; document in `kernel-notes.md`. | | Kernel module build/ABI churn against Raspberry Pi OS kernel | Medium | Build against installed kernel headers; pin kernel version; document in `kernel-notes.md`. |
| 7406 inversion / divider miswire | Medium | `iec_lines.h` logical layer isolates it; module `selftest` ioctl toggles DATA and reads ATN/CLK with the C64 off. | | Level-shifter wiring / DATA direction-flip bug | Medium | `iec_lines.h` logical layer isolates assert/release/read; module `selftest` ioctl drives DATA (output LOW), releases to Hi-Z and reads it back, and reads ATN/CLK with the C64 off. Confirm Hi-Z release truly floats so the talker's bits read through. |
| C64 has JiffyDOS | Low (printer rarely uses it) | Out of scope; document. Standard protocol still works for a printer device. | | C64 has JiffyDOS | Low (printer rarely uses it) | Out of scope; document. Standard protocol still works for a printer device. |
| ATN response missed | Low | Hardware IRQ pulls DATA in the ISR; 1 ms budget is ample for kernel interrupt latency. | | ATN response missed | Low | Hardware IRQ pulls DATA in the ISR; 1 ms budget is ample for kernel interrupt latency. |