libc/irq: цепочка кадровых обработчиков + all-modes W1-remap
_irq_user → _irq_chain[4]+_irq_chain_n (W2-BSS); трамплин tr_frame проходит слоты (один тяжёлый сейв на всю цепь, пустой слот пропуск). API irq_chain_add (0/-1+ENOMEM) / irq_chain_remove(h); irq_install → обёртка chain_add (EBUSY исчез), irq_remove() рвёт всю цепь; refcount таблицы на первом/последнем слоте, мутации под IRQ_DISABLE. Лимит 4 кадровых + 1 CTC. All-modes: трамплин copy-safe (только jr/djnz + литерал jp 0x0038), _irq_table_ref копирует его в _irq_tramp_w2buf (W2) когда оригинал в W1 (small/huge), вектор → на копию; вокруг вызова хендлеров восстанавливает базовую W1-страницу (_irq_app_w1_page = IN 0xA2). Хендлер может лежать где угодно в плоском 0x4000-0xBFFF. Проверено в MAME (tests/irqtest, 2 хендлера): tiny/big/huge — chain h1=h2 → remove h2 → h1 жив/h2=0; huge = код в W1, remap работает. Follow-up: CTC в small/huge = EINVAL (нужна W2-копия _irq_ctc_tramp); small для мелких программ (BSS в W1) = irq_install EINVAL, safe. Доки: im2_isr_design (цепочка), sprite-api §9е (делитель поверх цепи), fast_ram §8. rpgprof: gfx_sprite_ysort в профиль (+230 Б baseline). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+85
-45
@@ -1,23 +1,31 @@
|
||||
/*
|
||||
* _irq_tramp — вход IM2-прерывания (вектор 0xFF: кадр/клавиатура/CBL).
|
||||
* _irq_tramp — вход IM2-прерывания (вектор 0xFF: кадр/клавиатура/CBL),
|
||||
* версия с ЦЕПОЧКОЙ кадровых обработчиков и W1-remap для all-modes.
|
||||
*
|
||||
* До выяснения источника трогаем только AF:
|
||||
* - клавиатура (бит 0 порта 0x19 = SIO-A RR0 «Rx Character
|
||||
* Available») уходит напрямую в DSS;
|
||||
* - CBL (бит 7 порта 0xFE) проверяется ТОЛЬКО при зарегистрированном
|
||||
* хуке _irq_cbl_hook: при выключенном CBL бит подтянут к 1 (MAME
|
||||
* kbd_fe_r: data |= 0xE0) и проверка ложно срабатывала бы на
|
||||
* каждом кадре. CBL-путь: полный сейв → насос → restore →
|
||||
* EI/RETI, к DSS НЕ чейнится (личное прерывание);
|
||||
* - кадровое: полный сейв → user-handler → restore → чейн.
|
||||
* Разбор источника (трогаем только AF, пока не выяснили):
|
||||
* - клавиатура (бит 0 порта 0x19 = SIO-A RR0) → сразу к DSS, БЕЗ
|
||||
* remap (W1 не трогаем — прерванный контекст сохранён как есть);
|
||||
* - CBL (бит 7 порта 0xFE) — только при зарегистрированном хуке
|
||||
* _irq_cbl_hook (иначе бит подтянут к 1); личное прерывание, RETI
|
||||
* без чейна к DSS;
|
||||
* - кадровое: проход по _irq_chain[] — все ненулевые слоты по порядку.
|
||||
*
|
||||
* Чейн — ВСЕГДА jp на обработчик DSS 0x0038 (DSS работает в IM 1;
|
||||
* jp с interrupted-PC на стеке имитирует RST 38) — он делает свой
|
||||
* save/restore и завершает EI/RETI: клавиатурный FIFO, SYSTIME и
|
||||
* курсор мыши живут.
|
||||
* 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 всё равно восстановлен).
|
||||
*
|
||||
* Операнд финального jp (_irq_dss_target) — SMC, патчится из
|
||||
* _irq_table_ref; модуль в _CODE (RAM на Sprinter, W2 в tiny/big).
|
||||
* COPY-SAFE: все внутренние переходы — jr/djnz (относительные);
|
||||
* единственный call — к _irq_call_hl (исполняется при W1=base, значит
|
||||
* оригинал в W1 достижим); оба jp — литерал 0x0038 (W0, всегда
|
||||
* замаплен). Никаких абсолютных само-ссылок → байты копируются как
|
||||
* есть, без релокации. ВНИМАНИЕ: не добавлять `jp <метка>` внутрь
|
||||
* трамплина — сломает копию.
|
||||
*/
|
||||
|
||||
#include "_irq.h"
|
||||
@@ -25,25 +33,35 @@
|
||||
void _irq_tramp(void) __naked
|
||||
{
|
||||
__asm
|
||||
push af
|
||||
in a, (#0x19)
|
||||
rrca ; бит 0 — клавиатурный байт принят?
|
||||
jr C, _irq_chain
|
||||
push af ; сохранить прерванный AF
|
||||
in a, (#_IRQ_SIO_A)
|
||||
rrca ; бит 0 — клавиатурный байт принят?
|
||||
jr NC, tr_notkbd
|
||||
|
||||
;; --- CBL? --- старший байт хука ненулевой у любого
|
||||
;; W2-адреса — для проверки «хук есть» хватает одного байта.
|
||||
;; --- клавиатура: прямо к 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, _irq_frame
|
||||
jr Z, tr_frame
|
||||
in a, (#0xFE)
|
||||
rlca ; бит 7 — запрос блока CBL?
|
||||
jr NC, _irq_frame
|
||||
rlca ; бит 7 — запрос блока CBL?
|
||||
jr NC, tr_frame
|
||||
|
||||
;; --- CBL: сейв, насос, restore, EI/RETI (без DSS) ---
|
||||
;; --- 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)
|
||||
.db 0x08 ; ex af,af (теневой AF)
|
||||
push af
|
||||
exx
|
||||
push bc
|
||||
@@ -60,26 +78,31 @@ void _irq_tramp(void) __naked
|
||||
pop bc
|
||||
exx
|
||||
pop af
|
||||
.db 0x08 ; ex af,af (теневой 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
|
||||
|
||||
_irq_frame:
|
||||
;; --- кадровое прерывание ---
|
||||
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
|
||||
ld hl, (__irq_user)
|
||||
ld a, h
|
||||
or a, l
|
||||
jr Z, _irq_pop3 ; handler не установлен
|
||||
|
||||
;; Сохранить теневой набор и индексные — user-код клобберит всё.
|
||||
.db 0x08 ; ex af,af (теневой AF)
|
||||
.db 0x08
|
||||
push af
|
||||
exx
|
||||
push bc
|
||||
@@ -88,8 +111,25 @@ void _irq_tramp(void) __naked
|
||||
push ix
|
||||
push iy
|
||||
|
||||
ld hl, (__irq_user)
|
||||
;; проход по слотам: 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
|
||||
@@ -98,19 +138,19 @@ void _irq_tramp(void) __naked
|
||||
pop bc
|
||||
exx
|
||||
pop af
|
||||
.db 0x08 ; ex af,af (теневой AF)
|
||||
_irq_pop3:
|
||||
.db 0x08
|
||||
pop hl
|
||||
pop de
|
||||
pop bc
|
||||
_irq_chain:
|
||||
ld a, (__irq_saved_w1) ; вернуть прерванную W1 перед chain
|
||||
out (#_IRQ_W1_PORT), a
|
||||
tr_frame_exit:
|
||||
pop af
|
||||
;; Всегда к DSS (IM 1, обработчик 0x0038) — EI/RETI за ним.
|
||||
.db 0xC3 ; jp nn
|
||||
__irq_dss_target::
|
||||
.dw 0x0038 ; SMC: подтверждается в _irq_table_ref
|
||||
.db 0xC3 ; jp 0x0038 (литерал — copy-safe)
|
||||
.dw 0x0038
|
||||
|
||||
_irq_call_hl:
|
||||
jp (hl)
|
||||
__irq_tramp_end::
|
||||
__endasm;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user