/*
 * _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 клавиатуру не видит вообще (см. <kbd_raw.h>).
 *  - 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_hub_notkbd      ; не клавиатура (через ретранслятор — см. ниже)

        ;; --- клавиатура ---
        ld    a, (__kbdraw_active)
        or    a, a
        jr    Z, tr_hub_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 (см. ниже)
        ;; РАСКЛАДКА ЦИКЛА.  Все переходы обязаны быть jr (см. шапку:
        ;; COPY-SAFE), а тело выросло за 127 байт — поэтому префиксные
        ;; обработчики стоят СРАЗУ за своими cp, а возврат на начало цикла
        ;; идёт через ретранслятор tr_kbd_hub посередине тела.
    tr_kbd_drain:
        in    a, (#_IRQ_SIO_DATA)    ; читаем — тем самым дренируем байт
        cp    #0xF0
        jr    NZ, tr_kbd_nF0
        ld    hl, #__kbdraw_pending
        set   0, (hl)
        jr    tr_kbd_hub
    tr_kbd_nF0:
        cp    #0xE0
        jr    NZ, tr_kbd_nE0
        ld    hl, #__kbdraw_pending
        set   1, (hl)
        jr    tr_kbd_hub
    tr_kbd_nE0:

        ;; --- «fake shift»: расширенные коды 12/59 — не клавиши, а обёртка,
        ;; которой клавиатура сопровождает КАЖДЫЙ расширенный код, пока
        ;; реально зажат Shift (см. _kbdraw.h).  Отсюда состояние Shift
        ;; определяется в обе стороны и не зависит от целости потока. ---
        ld    e, a                   ; E = код
        ld    a, (__kbdraw_pending)
        bit   1, a                   ; EXT?
        ld    a, e                   ; A = код обратно (ld флаги не трогает)
        jr    Z, tr_kbd_code         ; обычный код — прежний путь
        cp    #0x12
        jr    Z, tr_kbd_fake
        cp    #0x59
        jr    Z, tr_kbd_fake
        ;; расширенная КЛАВИША: make без обёртки => Shift не зажат
        ld    a, (__kbdraw_pending)
        bit   0, a                   ; break — состояние Shift не судим
        jr    NZ, tr_kbd_fkclr
        ld    a, (__kbdraw_fakesh)
        or    a, a
        jr    NZ, tr_kbd_fkclr       ; обёртка была => Shift зажат, не трогаем
        ld    hl, #(__kbdraw_down + _KBDRAW_LSH_BYTE)
        res   _KBDRAW_LSH_BIT, (hl)
        ld    hl, #(__kbdraw_down + _KBDRAW_RSH_BYTE)
        res   _KBDRAW_RSH_BIT, (hl)
    tr_kbd_fkclr:
        xor   a, a
        ld    (__kbdraw_fakesh), a
        ld    a, e
        jr    tr_kbd_code            ; дальше как обычная клавиша

    tr_kbd_hub:
        ;; Ретрансляторы: клавиатурный блок длиннее 127 байт, а jp внутри
        ;; трамплина запрещён (копия в W2, см. шапку).  Стоят посередине
        ;; тела — так дотягиваются и переходы сверху, и снизу.  Каждый идёт
        ;; после безусловного jr, провалиться сюда нельзя.
        jr    tr_kbd_drain
    tr_hub_notkbd:
        jr    tr_notkbd
    tr_hub_dss:
        jr    tr_kbd_dss

    tr_kbd_fake:
        ;; E0 F0 12 / E0 12 — реальный Shift ЗАЖАТ.  Бит взводит ОБЩИЙ
        ;; писатель ниже: достаточно обнулить префиксы — тогда он положит
        ;; код в PLAIN-половину карты (без +32) и именно взведёт бит (не
        ;; break).  В расширенную половину не пишем: это не клавиша.
        ld    a, (__kbdraw_pending)
        and   #1                     ; 1 = break-форма (идёт ПЕРЕД обёрнутым make)
        ld    (__kbdraw_fakesh), a
        xor   a, a
        ld    (__kbdraw_pending), a
        ld    a, e
        ;; fall through в tr_kbd_code

        ;; --- обычный байт кода: БИТ code&7 в байте _kbdraw_down[code>>3]
        ;; (карта упакована по битам: 64 Б вместо 512, см. _kbdraw.h).
        ;; Маску строим сдвигом, а НЕ таблицей: таблица потребовала бы
        ;; `ld hl,#метка` внутри трамплина, а он копируется в W2 побайтно
        ;; и обязан быть без абсолютных само-ссылок (см. шапку). ---
    tr_kbd_code:
        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  ; сброс накопленных префиксов
        ;; fall through в tr_kbd_next
    tr_kbd_next:
        in    a, (#_IRQ_SIO_A)       ; ещё байт в FIFO?
        rrca                         ; бит 0 → CF
        jr    C, tr_kbd_hub          ; да — вычерпать следующий (через ретранслятор)

        ;; --- FIFO опустошён: проверить Rx-overrun (потерян ли байт?) ---
        ;; RR1 SIO бит5 = Rx Overrun Error.  При overrun break-код мог
        ;; потеряться (клавиша «залипнет») → взводим _kbdraw_overrun, чтобы
        ;; kbd_raw_sync() в главном цикле снял held-состояние; и Error Reset
        ;; (WR0 команда 0x30), иначе бит залипнет.  (z84c15 SIO, Z80-совм.)
        ;;
        ;; СБРОС _kbdraw_pending — по образцу DSS (KBD_Receiver_Overrun в
        ;; docs/sources/Estex-DSS/DSS/KEYINTER.ASM чистит KEY_FLG, свой FSM
        ;; префиксов E0/F0).  Без этого потерянный 0xF0/0xE0 оставляет
        ;; префикс висеть, и СЛЕДУЮЩИЙ код декодируется наоборот
        ;; (make↔break) или уходит в расширенную половину карты — стойкий
        ;; рассинхрон, который лечится только новым нажатием (BUG-KBD-3).
        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
        xor   a, a
        ld    (__kbdraw_pending), a  ; префиксы недействительны
        inc   a
        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;
}
