/* * _irq_tramp — вход IM2-прерывания (вектор 0xFF: кадр/клавиатура/CBL), * версия с ЦЕПОЧКОЙ кадровых обработчиков и W1-remap для all-modes. * * Разбор источника (трогаем только AF, пока не выяснили): * - клавиатура (бит 0 порта 0x19 = SIO-A RR0) → штатно сразу к DSS, * БЕЗ remap (W1 не трогаем — прерванный контекст сохранён как * есть). ИСКЛЮЧЕНИЕ: если raw-канал открыт (`_kbdraw_active`, * см. libc/kbd/_kbdraw.h, kbd_raw_open) — байт достаётся НАМ * (порт данных 0x18 читается один раз, деструктивно — либо мы, * либо DSS, не оба), декодируется make/break (PS/2 Scan Code * Set 2: 0xF0 — префикс отпускания, 0xE0 — префикс расширенной * клавиши) в битовую карту `_kbdraw_down[]` (бит на код, 64 Б), * и к DSS НЕ идём * (private RETI — тот же паттерн, что и у CBL ниже). Пока raw * открыт, DSS клавиатуру не видит вообще (см. ). * - CBL (бит 7 порта 0xFE) — только при зарегистрированном хуке * _irq_cbl_hook (иначе бит подтянут к 1); личное прерывание, RETI * без чейна к DSS; * - кадровое: проход по _irq_chain[] — все ненулевые слоты по порядку. * * W1-remap (docs/im2_isr_design.md «Вариант работает во всех режимах»): * в small/huge базовый код приложения (и хендлеры) в W1, а сам трамплин * во время прерывания исполняется из W2-КОПИИ (_irq_table_ref копирует * этот код в _irq_tramp_w2buf, т.к. оригинал в W1 может быть перемаплен * DSS). Перед вызовом хендлеров копия сохраняет текущую W1-страницу и * ставит базовую страницу приложения (_irq_app_w1_page), после — * возвращает сохранённую (перед chain к DSS / перед RETI). Единый * «restore W1=base» покрывает хендлер в W1, в W2 и на стыке 0x8000. * В tiny/big remap безвреден (хендлеры в W2, W1 всё равно восстановлен). * * COPY-SAFE: все внутренние переходы — jr/djnz (относительные); * единственный call — к _irq_call_hl (исполняется при W1=base, значит * оригинал в W1 достижим); оба jp — литерал 0x0038 (W0, всегда * замаплен). Никаких абсолютных само-ссылок → байты копируются как * есть, без релокации. ВНИМАНИЕ: не добавлять `jp <метка>` внутрь * трамплина — сломает копию. */ #include "_irq.h" #include "../kbd/_kbdraw.h" void _irq_tramp(void) __naked { __asm push af ; сохранить прерванный AF in a, (#_IRQ_SIO_A) rrca ; бит 0 — клавиатурный байт принят? jr NC, tr_notkbd ;; --- клавиатура --- ld a, (__kbdraw_active) or a, a jr Z, tr_kbd_dss ; raw выключен — прежнее поведение ;; --- raw включён: байт наш целиком, DSS его не увидит --- ;; ВЫЧЕРПЫВАЕМ FIFO SIO В ЦИКЛЕ (как эталон keybIntHandler / ;; PS2_Scan в docs/samples): приёмный FIFO Z80 SIO 3 байта ;; глубиной, а за один кадр при одновременном отпускании ;; нескольких клавиш приходит ПАЧКА break-кодов (0xF0 code ...). ;; Читать один байт за прерывание = переполнение FIFO = потеря ;; break-кода = клавиша «залипает» навсегда (найдено 2026-07-16). push hl push de push bc ; маска бита строится в BC (см. ниже) tr_kbd_drain: in a, (#_IRQ_SIO_DATA) ; читаем — тем самым дренируем байт cp #0xF0 jr Z, tr_kbd_setbrk cp #0xE0 jr Z, tr_kbd_setext ;; --- обычный байт кода: БИТ code&7 в байте _kbdraw_down[code>>3] ;; (карта упакована по битам: 64 Б вместо 512, см. _kbdraw.h). ;; Маску строим сдвигом, а НЕ таблицей: таблица потребовала бы ;; `ld hl,#метка` внутри трамплина, а он копируется в W2 побайтно ;; и обязан быть без абсолютных само-ссылок (см. шапку). --- ld e, a ; E = код and #7 ld b, a inc b ; B = (код&7)+1 для djnz ld a, #1 jr tr_kbd_mskdec tr_kbd_mskloop: add a, a tr_kbd_mskdec: djnz tr_kbd_mskloop ; A = 1 << (код&7) ld c, a ; C = маска бита ld a, e rrca rrca rrca and #0x1F ; A = код>>3 (код <= 255) ld hl, #__kbdraw_down ld d, #0 ld e, a add hl, de ld a, (__kbdraw_pending) bit 1, a ; _KBDRAW_PEND_EXT? jr Z, tr_kbd_noext ld de, #32 ; +256 кодов = +32 байта карты add hl, de tr_kbd_noext: ld a, (__kbdraw_pending) bit 0, a ; _KBDRAW_PEND_BREAK? jr Z, tr_kbd_mkdown ld a, c cpl ; break → сбросить бит and a, (hl) jr tr_kbd_wr tr_kbd_mkdown: ld a, c ; make → взвести бит or a, (hl) tr_kbd_wr: ld (hl), a xor a, a ld (__kbdraw_pending), a ; сброс накопленных префиксов jr tr_kbd_next tr_kbd_setbrk: ld hl, #__kbdraw_pending set 0, (hl) jr tr_kbd_next tr_kbd_setext: ld hl, #__kbdraw_pending set 1, (hl) ;; fall through в tr_kbd_next tr_kbd_next: in a, (#_IRQ_SIO_A) ; ещё байт в FIFO? rrca ; бит 0 → CF jr C, tr_kbd_drain ; да — вычерпать следующий ;; --- FIFO опустошён: проверить Rx-overrun (потерян ли байт?) --- ;; RR1 SIO бит5 = Rx Overrun Error. При overrun break-код мог ;; потеряться (клавиша «залипнет») → взводим _kbdraw_overrun, чтобы ;; kbd_raw_sync() в главном цикле снял held-состояние; и Error Reset ;; (WR0 команда 0x30), иначе бит залипнет. (z84c15 SIO, Z80-совм.) ld a, #1 out (#_IRQ_SIO_A), a ; указатель на RR1 in a, (#_IRQ_SIO_A) ; читаем RR1 (указатель авто-сброс в 0) bit 5, a jr Z, tr_kbd_noovr ld a, #0x30 out (#_IRQ_SIO_A), a ; WR0 Error Reset ld a, #1 ld (__kbdraw_overrun), a tr_kbd_noovr: pop bc pop de pop hl pop af ei reti ; raw владеет каналом целиком — к DSS НЕ идём tr_kbd_dss: ;; --- прежнее поведение: прямо к DSS, без remap --- pop af .db 0xC3 ; jp nn __irq_dss_target:: .dw 0x0038 ; SMC-операбельно, но по умолчанию 0x0038 tr_notkbd: ;; --- CBL? --- старший байт хука ненулевой у любого W2-адреса. ld a, (__irq_cbl_hook + 1) or a, a jr Z, tr_frame in a, (#0xFE) rlca ; бит 7 — запрос блока CBL? jr NC, tr_frame ;; --- CBL: remap-скобка + хук + приватный RETI --- in a, (#_IRQ_W1_PORT) ; сохранить прерванную W1 ld (__irq_saved_w1), a ld a, (__irq_app_w1_page) out (#_IRQ_W1_PORT), a ; W1 = базовая страница приложения push bc push de push hl .db 0x08 ; ex af,af (теневой AF) push af exx push bc push de push hl push ix push iy ld hl, (__irq_cbl_hook) call _irq_call_hl pop iy pop ix pop hl pop de pop bc exx pop af .db 0x08 ; ex af,af pop hl pop de pop bc ld a, (__irq_saved_w1) ; вернуть прерванную W1 out (#_IRQ_W1_PORT), a pop af ei reti tr_frame: ;; --- кадровое: цепочка обработчиков --- ld a, (__irq_chain_n) or a, a jr Z, tr_frame_exit ; нет хендлеров → к DSS без remap/сейва in a, (#_IRQ_W1_PORT) ; remap-скобка: сохранить + база ld (__irq_saved_w1), a ld a, (__irq_app_w1_page) out (#_IRQ_W1_PORT), a ;; полный сейв обоих наборов + индексные (хендлеры клобберят всё) push bc push de push hl .db 0x08 push af exx push bc push de push hl push ix push iy ;; проход по слотам: HL идёт по _irq_chain, B = счётчик слотов ld hl, #__irq_chain ld b, #IRQ_CHAIN_MAX tr_loop: ld e, (hl) inc hl ld d, (hl) inc hl ld a, d or a, e jr Z, tr_skip ; пустой слот push bc ; сохранить счётчик + указатель цепи push hl ex de, hl ; HL = адрес хендлера call _irq_call_hl pop hl pop bc tr_skip: djnz tr_loop pop iy pop ix pop hl pop de pop bc exx pop af .db 0x08 pop hl pop de pop bc ld a, (__irq_saved_w1) ; вернуть прерванную W1 перед chain out (#_IRQ_W1_PORT), a tr_frame_exit: pop af .db 0xC3 ; jp 0x0038 (литерал — copy-safe) .dw 0x0038 _irq_call_hl: jp (hl) __irq_tramp_end:: __endasm; }