feat(kernel): add debounced (glitch-filtered) sense-line reads
All checks were successful
Build kernel module / build (1:6.12.93-1+rpt1, bookworm) (pull_request) Successful in 57s
Build kernel module / build (1:6.18.34-1+rpt1, trixie) (pull_request) Successful in 1m14s
Build kernel module / package (pull_request) Successful in 23s
Build kernel module / release (pull_request) Has been skipped
All checks were successful
Build kernel module / build (1:6.12.93-1+rpt1, bookworm) (pull_request) Successful in 57s
Build kernel module / build (1:6.18.34-1+rpt1, trixie) (pull_request) Successful in 1m14s
Build kernel module / package (pull_request) Successful in 23s
Build kernel module / release (pull_request) Has been skipped
The IEC bit loop sampled GPIO with a single register read, so a glitch on a level-shifted 5V<->3.3V bus could corrupt a bit with no chance to retry (the loop runs with IRQs off). Add a stable-read filter that only believes a level change after it holds IEC_DEBOUNCE_US (now 5 us); the fast path is a single read so tight CLK polls stay cheap. Used across the receive hot path; the ATN ISR and self-test keep raw reads. Generated by Clanker Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
a79a469aac
commit
acfce76011
@ -15,6 +15,7 @@ the code in `kernel/` is built on.
|
||||
| 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` |
|
||||
| `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` |
|
||||
| Sense-line reads | Debounced (glitch-filtered): a level change is believed only after it holds `IEC_DEBOUNCE_US` (5 µs); shorter pulses are rejected as noise. Mirrors a confirmed-working reference listener. Fast path is a single register read, so tight CLK polls stay cheap. ATN ISR + self-test stay **raw** | `iec_read_stable`, `db_*_asserted` |
|
||||
| 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) |
|
||||
| PREEMPT_RT | Stock kernel sufficient; RT is a last resort | — |
|
||||
| Module signing | Not required on stock RPi OS Bookworm | — |
|
||||
@ -222,8 +223,10 @@ The host needs qemu binfmt for arm64; the script registers it once via
|
||||
## Starting timing constants
|
||||
|
||||
See `kernel/iec_timing.h`. Tune in Phase 2 against a real C64 / logic analyser.
|
||||
The likely first knobs: `IEC_EOI_DETECT_US` (EOI false positives/negatives) and
|
||||
`IEC_CLK_TIMEOUT_US` (frame errors under load).
|
||||
The likely first knobs: `IEC_EOI_DETECT_US` (EOI false positives/negatives),
|
||||
`IEC_CLK_TIMEOUT_US` (frame errors under load), and `IEC_DEBOUNCE_US` (the
|
||||
sense-line glitch-filter window — raise it if a noisy bus still yields bit
|
||||
errors, lower it if it smears fast edges).
|
||||
|
||||
## Open items to verify on hardware
|
||||
|
||||
|
||||
@ -81,6 +81,54 @@ static DECLARE_WAIT_QUEUE_HEAD(iec_work_wq);
|
||||
/* listener addressing state (decoded in-kernel, just enough to participate) */
|
||||
static bool addressed_listener;
|
||||
|
||||
/*
|
||||
* --- debounced sense lines (glitch filter) -------------------------------
|
||||
*
|
||||
* Mirrors the 5 us glitch filter from a confirmed-working reference listener.
|
||||
* The raw iec_*_asserted() helpers in iec_lines.h read
|
||||
* the GPIO register once; on a level-shifted 5 V<->3.3 V bus a single sample
|
||||
* can land on noise and yield a wrong bit, with no chance to retry since the
|
||||
* bit loop runs with IRQs off. These wrappers only believe a level change once
|
||||
* it has held for IEC_DEBOUNCE_US; a shorter pulse is rejected as a glitch and
|
||||
* the previous stable level is kept.
|
||||
*
|
||||
* Fast path (no change since last read) is a single register read, so the tight
|
||||
* CLK polls in wait_clk() stay cheap; the debounce cost is paid only when an
|
||||
* edge actually appears. udelay() is safe with IRQs disabled.
|
||||
*
|
||||
* Concurrency: the entire IEC receive path runs single-threaded in the worker
|
||||
* kthread, so the per-line stable state needs no locking. The ATN ISR must NOT
|
||||
* use these (it needs an instantaneous read and must not touch this state) --
|
||||
* it keeps using the raw iec_atn_asserted() from iec_lines.h.
|
||||
*/
|
||||
static int db_clk = 1; /* last stable level, 1 = released (high) = idle bus */
|
||||
static int db_data = 1;
|
||||
static int db_atn = 1;
|
||||
static int db_reset = 1;
|
||||
|
||||
static int iec_read_stable(unsigned int pin, int *last_stable)
|
||||
{
|
||||
int raw = iec_gpio_read(pin);
|
||||
unsigned int held;
|
||||
|
||||
if (raw == *last_stable)
|
||||
return *last_stable; /* fast path: no change */
|
||||
|
||||
/* a transition appeared: require it to hold IEC_DEBOUNCE_US */
|
||||
for (held = 0; held < IEC_DEBOUNCE_US; held++) {
|
||||
udelay(1);
|
||||
if (iec_gpio_read(pin) != raw)
|
||||
return *last_stable; /* bounced back -> glitch, ignore */
|
||||
}
|
||||
*last_stable = raw; /* held steady -> accept */
|
||||
return raw;
|
||||
}
|
||||
|
||||
static inline bool db_clk_asserted(void) { return iec_read_stable(IEC_GPIO_CLK, &db_clk) == 0; }
|
||||
static inline bool db_atn_asserted(void) { return iec_read_stable(IEC_GPIO_ATN, &db_atn) == 0; }
|
||||
static inline bool db_reset_asserted(void) { return iec_read_stable(IEC_GPIO_RESET, &db_reset) == 0; }
|
||||
static inline bool db_data_asserted(void) { return iec_read_stable(IEC_GPIO_DATA, &db_data) == 0; }
|
||||
|
||||
/* receive_byte() outcomes */
|
||||
enum iec_rx {
|
||||
IEC_RX_OK = 0, /* byte received */
|
||||
@ -126,10 +174,10 @@ static enum iec_rx wait_clk(bool want_asserted, unsigned int timeout_us,
|
||||
{
|
||||
unsigned int waited = 0;
|
||||
|
||||
while (iec_clk_asserted() != want_asserted) {
|
||||
if (iec_atn_asserted() == data_phase)
|
||||
while (db_clk_asserted() != want_asserted) {
|
||||
if (db_atn_asserted() == data_phase)
|
||||
return IEC_RX_ATN;
|
||||
if (iec_reset_asserted())
|
||||
if (db_reset_asserted())
|
||||
return IEC_RX_RESET;
|
||||
if (waited++ >= timeout_us)
|
||||
return IEC_RX_TIMEOUT;
|
||||
@ -164,9 +212,9 @@ static enum iec_rx receive_byte(u8 *out, bool *eoi, bool data_phase)
|
||||
if (data_phase) {
|
||||
unsigned int waited = 0;
|
||||
|
||||
while (!iec_clk_asserted()) {
|
||||
if (iec_atn_asserted()) { rc = IEC_RX_ATN; goto out; }
|
||||
if (iec_reset_asserted()) { rc = IEC_RX_RESET; goto out; }
|
||||
while (!db_clk_asserted()) {
|
||||
if (db_atn_asserted()) { rc = IEC_RX_ATN; goto out; }
|
||||
if (db_reset_asserted()) { rc = IEC_RX_RESET; goto out; }
|
||||
if (waited++ >= IEC_EOI_DETECT_US) {
|
||||
/* ack EOI: pull DATA low for Tei, then release */
|
||||
iec_data_assert();
|
||||
@ -190,7 +238,7 @@ static enum iec_rx receive_byte(u8 *out, bool *eoi, bool data_phase)
|
||||
if (rc != IEC_RX_OK)
|
||||
goto out;
|
||||
/* released(high) = bit 1, asserted(low) = bit 0 */
|
||||
if (!iec_data_asserted())
|
||||
if (!db_data_asserted())
|
||||
value |= (1u << i);
|
||||
}
|
||||
|
||||
@ -229,10 +277,10 @@ static void run_state_machine(void)
|
||||
bool eoi;
|
||||
enum iec_rx rc;
|
||||
|
||||
if (iec_reset_asserted())
|
||||
if (db_reset_asserted())
|
||||
goto reset;
|
||||
|
||||
if (iec_atn_asserted()) {
|
||||
if (db_atn_asserted()) {
|
||||
/* RECEIVE_COMMAND: ATN held low */
|
||||
rc = receive_byte(&b, &eoi, false);
|
||||
if (rc == IEC_RX_RESET)
|
||||
@ -256,9 +304,9 @@ static void run_state_machine(void)
|
||||
/* LISTENER: data phase */
|
||||
emit_record(IEC_KIND_EVENT, IEC_EV_ATN_RELEASED, IEC_FLAG_ADDRESSED);
|
||||
for (;;) {
|
||||
if (iec_reset_asserted())
|
||||
if (db_reset_asserted())
|
||||
goto reset;
|
||||
if (iec_atn_asserted()) {
|
||||
if (db_atn_asserted()) {
|
||||
/* C64 interrupts with a new command */
|
||||
emit_record(IEC_KIND_EVENT, IEC_EV_ATN_COMMAND, 0);
|
||||
break;
|
||||
|
||||
@ -25,8 +25,12 @@
|
||||
* 80 us gives margin. */
|
||||
#define IEC_EOI_ACK_HOLD_US 80
|
||||
|
||||
/* Debounce between consecutive bus reads (mirrors sd2iec / 1571 ROM). */
|
||||
#define IEC_DEBOUNCE_US 2
|
||||
/* Glitch filter window for sense-line reads. A level change is only believed
|
||||
* once it has held this long; a shorter pulse is treated as noise. Matches a
|
||||
* confirmed-working reference listener (5 us). sd2iec / the 1571 ROM
|
||||
* use a comparable few-microsecond debounce. Far shorter than a real IEC bit
|
||||
* (tens of us), so it never smears valid data. */
|
||||
#define IEC_DEBOUNCE_US 5
|
||||
|
||||
/* Between-bytes minimum hold before the talker releases CLK for the next byte
|
||||
* (Tbb). We simply wait for the next CLK transition; this is documentary. */
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user