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:
2026-07-15 10:23:39 +03:00
parent 6b6986d9b6
commit 2c6f4e33c3
17 changed files with 700 additions and 128 deletions
+21 -13
View File
@@ -900,19 +900,27 @@ Call-sites НЕ меняются: gfx_wait_vsync() один; n<=1 — СТАРЫ
(данные; div=0/1 → старый путь; НЕ инициализировать).
- common/_gfx_frame_isr.c — ISR: `_gfx_frame_tick++` (правила irq.h:
без ESTEX/gfx/банков — здесь чистый инкремент).
- common/gfx_set_fps_div.c — сеттер: n>=2 → irq_install(_gfx_frame_isr)
(однократно; повторные вызовы только меняют div), n<=1 →
irq_remove (если ставили) + div=1. Возврат 0 / -1+errno
(EINVAL/EBUSY от irq_install). ТОЛЬКО этот модуль ссылается на
irq-механику и ISR — DCE: программа без сеттера не тянет ничего
(wait ссылается лишь на data-модуль).
- common/gfx_set_fps_div.c — сеттер: n>=2 → irq_chain_add(_gfx_frame_isr)
(ТАРГЕТНО свой слот; однократно, повторные вызовы только меняют div),
n<=1 → irq_chain_remove(_gfx_frame_isr) (если ставили) + div=1.
ВАЖНО: chain_remove, НЕ irq_remove — выключение делителя не должно
снести собственный хендлер приложения (см. цепочку в
docs/im2_isr_design.md). Возврат 0 / -1+errno (EINVAL — не tiny/big;
ENOMEM — 4 слота заняты). ТОЛЬКО этот модуль ссылается на irq-механику
и ISR — DCE: программа без сеттера не тянет ничего (wait ссылается
лишь на data-модуль).
- gfx_wait_vsync.c — ветка `if (_gfx_fps_div >= 2)` перед текущим
поллингом.
### Ограничения (задокументировать в gfx.h)
1. irq_install работает ТОЛЬКО в tiny/big; в small/huge вернёт EINVAL
→ gfx_set_fps_div(2+) там недоступен (rpgprof — small!). ПРИЧИНА
→ gfx_set_fps_div(2+) там недоступен (rpgprof — small!). СНИМАЕТСЯ
вариантом «все режимы» (docs/im2_isr_design.md «Вариант работает во
всех режимах памяти», 2026-07-14): трамплин → W2-резидентный, хендлер
остаётся в W1, трамплин восстанавливает статическую базовую W1-страницу
приложения вокруг вызова (паттерн __banked). Тогда rpgprof (small)
тоже получит делитель. ПРИЧИНА исходного ограничения
(см. docs/im2_isr_design.md): IM2-таблица/трамплин/handler обязаны
лежать по адресам, валидным в ЛЮБОЙ момент прихода прерывания —
стабильно только W2 (стек обязан быть там по требованию DSS, окно
@@ -929,12 +937,12 @@ Call-sites НЕ меняются: gfx_wait_vsync() один; n<=1 — СТАРЫ
период кадра 896×320, дрейфа нет; фаза не привязана к лучу — разовая
синхронизация поллингом фронта при установке). Делать по
потребности.
2. Один слот user-ISR: приложение со СВОИМ irq_install получит EBUSY
от сеттера. Обход v1: приложение инкрементирует _gfx_frame_tick из
своего обработчика и выставляет _gfx_fps_div напрямую (внутренние
символы задокументировать); правильное решение — ЦЕПОЧКА
обработчиков в libc/irq (дизайн записан в docs/TODO.md 2026-07-14,
реализация по потребности).
2. ~~Один слот user-ISR (EBUSY)~~ — СНЯТО цепочкой обработчиков: сеттер
зовёт irq_chain_add, приложение со своим хендлером — тоже слот,
оба тикают. Дизайн цепочки: docs/im2_isr_design.md «Цепочка
кадровых обработчиков». Порядок реализации: цепочка → потом
делитель поверх неё. (Осталось лишь: при 4 занятых слотах сеттер
вернёт ENOMEM.)
3. W0-атласы + IM2: прерывание при замапленном атласе идёт через
IM2-таблицу приложения (W2) → чейн на 0x0038 = ISR-стаб страницы
атласа → DSS. Путь совместим по построению, но ПРОВЕРИТЬ в MAME