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:
+26
-18
@@ -5,26 +5,34 @@
|
||||
|
||||
## Ближайшее
|
||||
|
||||
- [ ] **FPS-делитель (frame pacing)** — gfx_set_fps_div(n): логический
|
||||
Порядок реализации связки: **сначала цепочка irq, потом FPS-делитель
|
||||
поверх неё** (делитель — просто один слот цепи; так снимается EBUSY
|
||||
на сосуществование со своим тиком приложения).
|
||||
|
||||
- [x] **(1) irq: цепочка кадровых обработчиков** — СДЕЛАНО 2026-07-15.
|
||||
Массив `isr_t _irq_chain[4]` + `_irq_chain_n` в W2-data (заменили
|
||||
`_irq_user`); трамплин `tr_frame` проходит слоты (один тяжёлый сейв
|
||||
на всю цепь, пустой слот пропускается); API `irq_chain_add`
|
||||
(0/-1+ENOMEM), `irq_chain_remove(h)`; `irq_install` → обёртка
|
||||
chain_add (EBUSY исчез), `irq_remove()` рвёт всю цепь; refcount
|
||||
таблицы на первом/последнем слоте; мутации под IRQ_DISABLE.
|
||||
All-modes W1-remap: трамплин копируется в W2 (`_irq_tramp_w2buf`,
|
||||
copy-safe — только jr/djnz + литерал jp 0x0038), вокруг вызова
|
||||
восстанавливает базовую W1-страницу (`_irq_app_w1_page` = IN 0xA2
|
||||
при install). ПРОВЕРЕНО в MAME (tests/irqtest, 2 хендлера):
|
||||
tiny/big/huge — chain 97=97 → remove h2 → 48/0; **huge = код в W1,
|
||||
remap работает** (цепочка тикает синхронно). Дизайн:
|
||||
docs/im2_isr_design.md.
|
||||
FOLLOW-UP: (а) CTC-трамплин пока требует код в W2 → в small/huge
|
||||
`irq_ctc_install` = EINVAL (нужна такая же W2-копия _irq_ctc_tramp);
|
||||
(б) small для мелких программ (BSS в W1) → irq_install EINVAL
|
||||
(safe; снять резервом area _IM2 в верхней W2).
|
||||
- [ ] **(2) FPS-делитель (frame pacing)** — gfx_set_fps_div(n): логический
|
||||
кадр = ровно n кадровых интервалов (50/25/16.7 fps), при
|
||||
переполнении слота — выравнивание на ближайший фронт; счётчик
|
||||
кадров через irq_install (только tiny/big в первой версии).
|
||||
ПОЛНЫЙ ПЛАН: docs/sprite-api-design.md §9е (обсуждён 2026-07-14).
|
||||
- [ ] **irq: цепочка обработчиков** (дизайн 2026-07-14, реализация по
|
||||
потребности — понадобится, когда в программе сойдутся fps-делитель
|
||||
+ свой тик/звук). Вместо одного слота (EBUSY) — фиксированный
|
||||
массив `isr_t _irq_chain[IRQ_CHAIN_MAX /* 4 */]` в W2-data:
|
||||
- трамплин вместо SMC-вызова одного handler'а обходит слоты
|
||||
(load/or a/skip-NULL/call — пустой слот ~40Т, копейки на кадре);
|
||||
- API: `irq_chain_add(h)` (0 / -1+ENOMEM когда слоты кончились),
|
||||
`irq_chain_remove(h)` (по указателю; слот → NULL);
|
||||
- совместимость: `irq_install(h)` становится обёрткой chain_add
|
||||
(EBUSY исчезает), `irq_remove()` БЕЗ аргумента снимает ВСЮ цепь
|
||||
и возвращает IM 1 — семантика exit/atexit сохранена;
|
||||
- порядок вызова = порядок слотов; взаимный порядок хендлеров НЕ
|
||||
гарантируется после remove/add (первая дыра) — хендлеры обязаны
|
||||
быть независимы; дисциплина ISR прежняя (irq.h);
|
||||
- CTC-цепочка (irq_ctc_*) — тем же паттерном, если понадобится.
|
||||
кадров через irq_chain_add(_gfx_frame_isr) (только tiny/big в
|
||||
первой версии). ПОЛНЫЙ ПЛАН: docs/sprite-api-design.md §9е
|
||||
(обсуждён 2026-07-14; ограничение EBUSY снято цепочкой).
|
||||
- [ ] **П6/железо**: MAME-смоук всех тестов после libc-сплита (conio,
|
||||
ptime, stattest, mouse, gfx_demo/gfx_d16/gfx_text/gfx_mous —
|
||||
трогался asm акселератора); затем прогон на реальном Sprinter
|
||||
|
||||
@@ -193,3 +193,97 @@ _intWaitVsyncSys
|
||||
Если будем добавлять поддержку — делать это отдельным opt-in механизмом
|
||||
(по аналогии с banked-режимами), с обязательной обёрткой off/restore вокруг всех
|
||||
точек входа в систему.
|
||||
|
||||
---
|
||||
|
||||
## 8. Разбор двух предложений по спрайтовому движку (обсуждение 2026-07-14)
|
||||
|
||||
Итог двух обсуждений (эмуляция FastRAM в dev-MAME подтверждена
|
||||
разработчиками MAME как полная).
|
||||
|
||||
### 8.1. Предложение «W0/FastRAM как код-банк `__banked`»
|
||||
|
||||
Идея: выделить 1-2 FastRAM-страницы в crt0, научиться размещать в них
|
||||
`__banked`-код с целью W0 и контрактом «такой код не зовёт DSS/BIOS»;
|
||||
критичным функциям указывать размещение в W0/FastRAM.
|
||||
|
||||
**Framing правильный** (переиспользовать трамплин `__banked` +
|
||||
контракт), но W0-банк — НЕ «ещё один bank id», а тяжелее по трём осям:
|
||||
|
||||
1. **Своп W0 сносит всё окно 0 с RST-векторами** (ESTEX RST10, BIOS
|
||||
RST8, IM1 RST38, RST0). Пока FastRAM в W0: сисколлы невозможны (это
|
||||
и есть контракт — ок), но прерывание фатально (`#0038` → мусор
|
||||
FastRAM) → обязателен `DI` на весь интервал. `DI` бьёт ISR-счётчик
|
||||
кадров (см. FPS-делитель, [[sprite-engine-perf]]).
|
||||
2. **Загрузку нельзя сделать как в `crt0_banked`** (там `ESTEX READ`
|
||||
прямо в окно, строки 165-173): сам READ вектрится через W0. Нужен
|
||||
2-шаг — READ в DRAM-буфер, затем `DI` + FastRAM в W0 + **`LD`-копия**
|
||||
(акселератор с FastRAM не работает!) + выкл. Плюс FastRAM затирается
|
||||
запуском дочернего DSS-процесса — нужна перезагрузка.
|
||||
3. **16 КБ окно против 64 КБ**: кросс-страничные вызовы внутри FastRAM
|
||||
тянут вложенный W0-своп. Вызовы в W1/W2/W3 — свободны (те окна
|
||||
замаплены).
|
||||
|
||||
Плюс **пер-вызовная такса трамплина** (~40-60Т: DI/toggle/EI/restore)
|
||||
съедает выигрыш именно на горячих inner-листьях, которые от FastRAM
|
||||
выигрывают больше всего. Per-function `__banked` — неправильная
|
||||
гранулярность.
|
||||
|
||||
**Выгода** мала: FastRAM ускоряет только выборку инструкций. Из бюджета
|
||||
спрайта 19.5К (blit 9.7 + heal 6.4 + тик 3.0) blit и heal —
|
||||
акселератор/видео-ОЗУ, не выигрывают; выигрывает только тик+сорт по
|
||||
fetch: ~15-20К/кадр ≈ +1 спрайт (3-4% бюджета в самом узком месте).
|
||||
|
||||
**Рекомендация:** если вообще трогать — модель «горячий остров», а не
|
||||
«функция-в-банке»: одна 16-КБ FastRAM-страница с самодостаточным
|
||||
кластером (тик + сортировщик + их листья), вход один раз за кадр через
|
||||
единственную обёртку `DI / своп-в / вызов / своп-обратно / EI`, внутри
|
||||
острова обычные `call`, никаких RST. Убирает таксу, сводит связку с
|
||||
прерываниями к одному DI-интервалу на кадр (мы и так HALT-ждём между
|
||||
кадрами; ISR-счётчик кадров тикает во время HALT-ожидания ПОСЛЕ EI, а
|
||||
не во время острова → совместимо с FPS-делителем, пока остров < 1
|
||||
кадра).
|
||||
|
||||
### 8.2. Данные в FastRAM вместо кода?
|
||||
|
||||
Вывод неожиданный: **для нашей нагрузки данные — более СЛАБЫЙ рычаг,
|
||||
чем код.** Wait-state вставляется на КАЖДЫЙ доступ к DRAM, а на Z80
|
||||
большинство обращений к памяти — выборка инструкций, не операндов. Тик
|
||||
= IX-индексный код (`ld a,(ix+d)` — DD-префикс: 3 байта fetch + 1 байт
|
||||
данных) → ¾ штрафа на fetch, ¼ на операнде. Значит `sprite_t` в FastRAM
|
||||
убирает лишь ¼, а код тика — ¾.
|
||||
|
||||
По данным движка:
|
||||
- **Пиксели/атласы — жёсткое НЕТ** (их читает акселератор; с FastRAM не
|
||||
работает → блит откатился бы на LD-циклы).
|
||||
- **Массив `sprite_t` — не стоит:** тик его трогает, но выигрыш мал (см.
|
||||
выше), а хуже того — его читают и heal/blit под акселератор, которым
|
||||
нужна нормальная карта памяти. `sprite_t` в W0-FastRAM → весь
|
||||
`sprite_update` под `DI` + непроверенный вопрос «работает ли
|
||||
акселератор при W0=FastRAM». Не кандидат, пока это не подтверждено
|
||||
артефактом в MAME.
|
||||
- **Таблица Y-сорта + чисто-CPU скретч — да, но не отдельное решение:**
|
||||
живут в той же 16-КБ странице «горячего острова» с кодом сортировщика
|
||||
(сорт не трогает ни акселератор, ни сисколлы). Отдельного рычага
|
||||
«данные» тут нет.
|
||||
|
||||
**Единственный сильный независимый кейс для данных-в-FastRAM:**
|
||||
CPU-bound алгоритм с большим LUT случайного доступа (fetch внутреннего
|
||||
цикла крошечный, чтений таблицы — тьма). У движка такого нет (тяжёлая
|
||||
графика = акселератор). Но если добавим CPU-side эффект (программный
|
||||
per-pixel шейдинг, палитровый remap, софт-скейлер) — его LUT станет
|
||||
главным кандидатом на FastRAM-данные. На будущее.
|
||||
|
||||
**Ограничения именно для FastRAM-данных:** лежат в окне 0
|
||||
(#0000-#3FFF) → буфер под BIOS/ESTEX туда нельзя (нужно #4000-#BFFF);
|
||||
стек остаётся в W2; не переживают дочерний DSS-процесс; заполняются
|
||||
только `LD`-копией (акселератор мимо).
|
||||
|
||||
### 8.3. Общий вердикт
|
||||
|
||||
Оба варианта реализуемы, но бьют по 3-4% бюджета в самом узком месте
|
||||
(тик+сорт fetch), мимо доминирующих blit/heal. Остаётся **последним
|
||||
резервом** ([[fast_ram]] в памяти). Перед любой реализацией — дешёвый
|
||||
бенч в dev-MAME: один и тот же CPU-loop в DRAM vs FastRAM по
|
||||
`totalcycles`, чтобы измерить реальный коэффициент wait-state, который
|
||||
эмулятор моделирует, и подтвердить, что овчинка стоит выделки.
|
||||
|
||||
+211
-1
@@ -397,7 +397,217 @@ User's ISR НЕ должен:
|
||||
|
||||
- ~~CBL/COVOX prerequisite handler~~ — реализован, см. «Phase 2b» выше
|
||||
- ISA interrupt handler (для ZX-Bus карт)
|
||||
- Multiple user handler chain (e.g. tick + sound)
|
||||
- Multiple user handler chain (e.g. tick + sound) — ДИЗАЙН ниже
|
||||
|
||||
## Цепочка кадровых обработчиков (план, 2026-07-14)
|
||||
|
||||
Мотив: сейчас кадровый слот один (`_irq_user`), второй `irq_install`
|
||||
даёт `EBUSY`. Как только в программе сойдутся ≥2 потребителя кадрового
|
||||
прерывания (FPS-делитель `gfx_set_fps_div` + свой тик/звук приложения),
|
||||
одного слота мало. Решение — фиксированный массив слотов, по которому
|
||||
трамплин проходит на каждом кадре.
|
||||
|
||||
### Модель
|
||||
|
||||
- `isr_t _irq_chain[IRQ_CHAIN_MAX]` (`IRQ_CHAIN_MAX = 4`) + счётчик
|
||||
занятости `uint8_t _irq_chain_n` — в `_irq_state.c` (W2-data;
|
||||
ЗАМЕНЯЮТ нынешний `isr_t _irq_user`).
|
||||
- Порядок вызова = порядок индексов 0..3. Взаимный порядок хендлеров
|
||||
**НЕ гарантируется** после remove/add (переиспользуется первая дыра)
|
||||
→ хендлеры обязаны быть независимы. Наш `_gfx_frame_isr` (инкремент
|
||||
байта) независим по построению.
|
||||
- Дисциплина ISR прежняя (`irq.h`): без ESTEX/BIOS/gfx/банков/акселератора.
|
||||
|
||||
### Трамплин (`_irq_tramp.c`, секция `_irq_frame`)
|
||||
|
||||
Полный сейв теневого+индексного набора делается ОДИН раз (амортизируется
|
||||
на всю цепь), затем цикл по слотам с пропуском NULL:
|
||||
|
||||
```
|
||||
_irq_frame:
|
||||
push bc/de/hl
|
||||
ld a, (__irq_chain_n) ; ранний выход: цепь пуста → без сейва
|
||||
or a, a
|
||||
jr Z, _irq_pop3
|
||||
... сейв shadow + IX/IY ...
|
||||
ld hl, #_irq_chain
|
||||
ld b, #IRQ_CHAIN_MAX
|
||||
_irq_chain_loop:
|
||||
ld e,(hl) / inc hl / ld d,(hl) / inc hl ; DE = слот
|
||||
ld a,d / or a,e / jr Z, _irq_chain_skip ; NULL → пропуск
|
||||
push bc / push hl
|
||||
call _irq_call_de ; jp (de)-обёртка (как _irq_call_hl)
|
||||
pop hl / pop bc
|
||||
_irq_chain_skip:
|
||||
djnz _irq_chain_loop
|
||||
... restore ...
|
||||
_irq_pop3:
|
||||
pop hl/de/bc
|
||||
_irq_chain: ... jp DSS ... ; как сейчас
|
||||
```
|
||||
|
||||
Пустой проход 4 слотов ≈ 4×(load+or+skip) ≈ 40Т — копейки на кадре.
|
||||
Ранний выход по `_irq_chain_n==0` сохраняет текущую оптимизацию «нет
|
||||
хендлера → без тяжёлого сейва» (трамплин может быть установлен ради
|
||||
CBL/CTC при пустой кадровой цепи).
|
||||
|
||||
### API (`irq.h`)
|
||||
|
||||
```c
|
||||
#define IRQ_CHAIN_MAX 4
|
||||
int irq_chain_add(isr_t h); /* 0 / -1+ENOMEM (слоты кончились) */
|
||||
void irq_chain_remove(isr_t h); /* снять ОДИН слот (по указателю) */
|
||||
```
|
||||
|
||||
Совместимость (без изменения существующих call-sites):
|
||||
- `irq_install(h)` → тонкая обёртка `return irq_chain_add(h)`. `EBUSY`
|
||||
исчезает (двойной install теперь легален); при 4 занятых — `ENOMEM`.
|
||||
- `irq_remove()` (без аргумента) — снимает **ВСЮ** кадровую цепь и
|
||||
возвращает IM 1. Семантика exit/atexit сохранена (exit рвёт всё перед
|
||||
возвратом в DSS).
|
||||
- `gfx_set_fps_div` использует `irq_chain_add`/`irq_chain_remove`
|
||||
(ТАРГЕТНО свой `_gfx_frame_isr`), НЕ `irq_remove` — чтобы выключение
|
||||
делителя не снесло собственный хендлер приложения.
|
||||
|
||||
### Refcount таблицы
|
||||
|
||||
`irq_chain_add`: если `_irq_chain_n == 0` (первый слот) → `_irq_table_ref()`
|
||||
(ставит IM2). `irq_chain_remove`: если слот был последним (`_irq_chain_n`
|
||||
уходит в 0) → `_irq_table_unref()` (возврат IM1). CBL/CTC-ссылки на
|
||||
таблицу считаются отдельно, как сейчас.
|
||||
|
||||
### Гонка чтения слота (ОБЯЗАТЕЛЬНО)
|
||||
|
||||
Трамплин читает слот двумя байтовыми `ld`; запись указателя в
|
||||
add/remove — 16-бит store. Порванное чтение (низкий байт новый, старший
|
||||
старый) = прыжок в мусор. Поэтому мутации слота+счётчика в
|
||||
`irq_chain_add`/`irq_chain_remove` ОБЯЗАНЫ идти под `IRQ_DISABLE()` /
|
||||
`IRQ_ENABLE()`. Дёшево и обязательно.
|
||||
|
||||
### Файлы (канон 1 модуль = 1 функция)
|
||||
|
||||
- `_irq_state.c` — `_irq_user` → `_irq_chain[IRQ_CHAIN_MAX]` + `_irq_chain_n`.
|
||||
- `_irq_tramp.c` — переписать секцию `_irq_frame` на цикл (выше).
|
||||
- `_irq.h` — объявить `_irq_chain`, `_irq_chain_n`, `IRQ_CHAIN_MAX`.
|
||||
- `irq_chain_add.c`, `irq_chain_remove.c` — НОВЫЕ.
|
||||
- `irq_install.c` — обёртка над chain_add; `irq_remove.c` — teardown-all
|
||||
(чистит все слоты + unref до нуля).
|
||||
- `include/irq.h` — публичные объявления + документировать «порядок не
|
||||
гарантирован, хендлеры независимы; двойной install легален; 4 слота».
|
||||
- `tests/irqtest` — добавить кейс двух хендлеров (оба тикают свои
|
||||
счётчики; remove одного не глушит другого; exit рвёт оба).
|
||||
|
||||
### Ограничения (СНИМАЮТСЯ — см. «все режимы» ниже)
|
||||
|
||||
- ~~Только tiny/big~~ — цель теперь **все режимы**, дизайн ниже.
|
||||
- CTC-цепочка (`irq_ctc_*`) — тем же паттерном, ОТДЕЛЬНЫЙ массив
|
||||
(вектор 0x06); делать по потребности, не сейчас.
|
||||
|
||||
## Вариант «работает во всех режимах памяти» (план 2026-07-14)
|
||||
|
||||
Постановка: IM2-прерывания должны работать и в small/huge, где CODE
|
||||
лежит в W1. Нельзя просто «загнать всё в W2» — обработчик пишет
|
||||
пользователь как обычный C, и он компилируется в `_CODE` (=W1 в
|
||||
small/huge). Задача — разложить *путь прерывания* так, чтобы он был
|
||||
корректен при ЛЮБОМ содержимом W1/W3 в момент прихода прерывания.
|
||||
|
||||
### Что на пути прерывания и что реально требует W2
|
||||
|
||||
Прерывание фетчит и исполняет по цепочке: **вектор-таблица → трамплин →
|
||||
обработчик(и) → chain на DSS 0x0038**. В момент прерывания стабильно
|
||||
замаплены только W0 (ROM/DSS) и **W2** (там стек — DSS это требует,
|
||||
окно не перемапливается). W1 перемапливается банками кода (big) и самим
|
||||
DSS при iff=1 во время сисколлов (подтверждено артефактом). W3 — банки
|
||||
(huge).
|
||||
|
||||
Ревизия по компонентам:
|
||||
1. **Вектор-таблица** — УЖЕ в W2 (`_irq_vec_buf`, BSS; BSS садится в W2
|
||||
во всех режимах). Ничего менять не надо.
|
||||
2. **Трамплин** (`_irq_tramp`) — сейчас в `_CODE` (=W1 в small/huge).
|
||||
ЕДИНСТВЕННОЕ, что валит проверку `tramp < 0x8000` в
|
||||
`_irq_table_ref()`. Нужно сделать W2-резидентным.
|
||||
3. **Обработчик пользователя** — обычный C в W1/W3. НЕ трогаем — решаем
|
||||
через remap (см. ниже).
|
||||
4. **Данные цепи** (`_irq_chain`, `_irq_chain_n`, …) — BSS → W2. ОК.
|
||||
|
||||
### Ключевой приём: не двигать обработчик, а подсунуть ему страницу
|
||||
|
||||
ВАЖНО (уточнение 2026-07-14): в small/huge программа видит **плоские
|
||||
32 КБ 0x4000–0xBFFF** — две статические страницы (W1+W2 подряд), их
|
||||
номера фиксированы на всё время работы. Обработчик может лежать ГДЕ
|
||||
УГОДНО в этом диапазоне: в W1 (0x4000–0x7FFF), в W2 (0x8000–0xBFFF)
|
||||
или даже НА СТЫКЕ через 0x8000. Значит не нужно знать, в каком окне
|
||||
хендлер — нужно лишь на время его вызова сделать весь плоский регион
|
||||
адресуемым.
|
||||
|
||||
Обработчик оставляем на месте. Трамплин (в W2) ПЕРЕД вызовом каждого
|
||||
хендлера восстанавливает базовую W1-страницу приложения — паттерн
|
||||
`__banked`-трамплина (bank.s: `in a,(0xA2)` / set / call / restore).
|
||||
W2 всегда = страница приложения (там стек, DSS не перемапливает), так
|
||||
что достаточно вернуть только W1 — и весь плоский 0x4000–0xBFFF
|
||||
корректен. Это работает, потому что:
|
||||
- в small/huge **базовая W1-страница статична** (пользователь её не
|
||||
переключает — модель памяти); её номер crt0 фиксирует один раз при
|
||||
старте (`IN A,(0xA2)`) в W2-переменную `_irq_app_w1_page`;
|
||||
- если прерывание пришло вне сисколла — W1 уже = базовая, set/restore
|
||||
это no-op; если ВНУТРИ сисколла (DSS подменил W1) — трамплин ставит
|
||||
базовую на время хендлера и возвращает DSS-страницу перед chain на
|
||||
0x0038, прерванный сисколл продолжается корректно.
|
||||
|
||||
Единый «restore W1=base» покрывает все положения хендлера БЕЗ детекции:
|
||||
- целиком в W1 → W1=base делает его код валидным;
|
||||
- целиком в W2 → W1-restore безвреден (хендлер оттуда не фетчит), W2
|
||||
и так = приложение;
|
||||
- на стыке 0x8000 → нижняя часть по W1=base, верхняя по W2=приложение
|
||||
— обе корректны.
|
||||
|
||||
Итог по режимам:
|
||||
- **tiny/big**: базовый код/данные в W2 (всегда замаплен) → remap НЕ
|
||||
нужен и ВРЕДЕН (в tiny/big нет осмысленной «базовой W1»; W1 —
|
||||
банк-окно в big). Как сейчас.
|
||||
- **small/huge**: плоские 32 КБ → трамплин ставит `_irq_app_w1_page`
|
||||
в 0xA2 вокруг вызова.
|
||||
Флаг «нужен remap W1» = (mode ∈ {small,huge}); crt0/sprinter-cc его
|
||||
выставляет, трамплин честит.
|
||||
|
||||
**Scope v1:** любые хендлеры в плоском образе приложения (не-banked) —
|
||||
т.е. обычный C где угодно в 0x4000–0xBFFF; покрывает практически все
|
||||
ISR (счётчик/звук-тик). `__banked`-хендлер (в W3 huge / отдельном
|
||||
банке big) потребовал бы хранить его страницу per-слот и мапить в его
|
||||
окно — отдельное расширение, не сейчас.
|
||||
|
||||
### Как сделать трамплин W2-резидентным — два пути
|
||||
|
||||
**(A) Резерв W2-области (проще, рекомендую для v1).** Трамплин
|
||||
(и, при желании, таблица) — в отдельной area `_IM2` с абсолютным
|
||||
адресом в верхней части W2 (напр. под стеком). Область НЕнулевая только
|
||||
если слинкованы irq-модули (DCE) → цена платится лишь при использовании
|
||||
IRQ; heap-top при слинкованном IRQ = `s__IM2`. В small/huge W2-часть
|
||||
статического образа грузится crt0 как есть — код окажется в W2 без
|
||||
рантайм-копирования. Правка `_irq_table_ref()`: снять проверку
|
||||
`tramp < 0x8000` (трамплин теперь по построению в W2).
|
||||
|
||||
**(B) Рантайм-эмит стаба в W2-BSS (без резерва области).** Скопировать
|
||||
PIC-шаблон трамплина в BSS-буфер (W2) при первом install, пропатчив
|
||||
абсолютные само-ссылки (SMC, как `_irq_dss_target` уже сейчас). Плюс:
|
||||
никакой зарезервированной области, чисто DCE. Минус: трамплин надо
|
||||
писать позиционно-независимым (внутренний control-flow только `jr`;
|
||||
`call (hl)`-идиому инлайнить или патчить адрес хелпера при копии).
|
||||
Оптимизация поверх (A), если резерв области жмёт бюджет small.
|
||||
|
||||
Ключевой приём (remap базовой страницы) от выбора (A)/(B) не зависит.
|
||||
|
||||
### Изменения к плану цепочки (выше)
|
||||
|
||||
- `_irq_table_ref()`: убрать EINVAL-проверку `tramp/buf < 0x8000` после
|
||||
переезда трамплина в W2 (таблица уже там).
|
||||
- crt0 (все варианты): записать `_irq_app_w1_page = IN A,(0xA2)` при
|
||||
старте (для small/huge; в tiny/big просто не используется).
|
||||
- Трамплин `_irq_frame`: вокруг `call` хендлера — save/set/restore
|
||||
0xA2 под флагом remap; перед `jp 0x0038` вернуть прерванную W1.
|
||||
- Тест: собрать `tests/irqtest` во ВСЕХ четырёх режимах; критично —
|
||||
small/huge прогон с активным сисколлом в main (файловый цикл), чтобы
|
||||
словить прерывание при DSS-подменённой W1.
|
||||
|
||||
## Альтернатива: отдельный memory mode "im2"
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ gfx_demo 4103
|
||||
gfxbanks 6044
|
||||
hello 4167
|
||||
hello2 4244
|
||||
irqtest 5850
|
||||
irqtest 6527
|
||||
ls 4835
|
||||
malloc 4461
|
||||
mdview 18878
|
||||
@@ -41,7 +41,7 @@ mouse 4382
|
||||
openenv 6124
|
||||
palfile 5406
|
||||
ptime 5744
|
||||
rpgprof 11852
|
||||
rpgprof 12082
|
||||
rpgwalk 9501
|
||||
rt_test 4892
|
||||
seek 4188
|
||||
|
||||
|
+21
-13
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user