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:
+35
-16
@@ -1,19 +1,26 @@
|
||||
/*
|
||||
* irq.h — пользовательский обработчик кадрового прерывания (IM 2).
|
||||
* irq.h — пользовательские обработчики кадрового прерывания (IM 2).
|
||||
*
|
||||
* Sprinter шлёт вектор 0xFF от трёх источников: кадр (50 Гц),
|
||||
* клавиатура (бит 0 порта 0x19), CBL/COVOX. Штатно DSS работает в
|
||||
* IM 1 (обработчик 0x0038). irq_install() переключает CPU в IM 2 со
|
||||
* своей таблицей и зовёт handler ТОЛЬКО на кадровых прерываниях;
|
||||
* после него (и на всех прочих источниках) управление всегда
|
||||
* передаётся штатному обработчику DSS на 0x0038 — клавиатура,
|
||||
* SYSTIME и курсор мыши продолжают работать. irq_remove()
|
||||
* возвращает IM 1/I как было (и вызывается автоматически из exit).
|
||||
* IM 1 (обработчик 0x0038). Установка обработчика переключает CPU в
|
||||
* IM 2 со своей таблицей и зовёт handler'ы ТОЛЬКО на кадровых
|
||||
* прерываниях; после них (и на всех прочих источниках) управление
|
||||
* всегда передаётся штатному обработчику DSS на 0x0038 — клавиатура,
|
||||
* SYSTIME и курсор мыши продолжают работать. irq_remove() возвращает
|
||||
* IM 1/I как было (и вызывается автоматически из exit).
|
||||
*
|
||||
* ТРЕБОВАНИЕ ПАМЯТИ: код и данные программы должны быть в W2
|
||||
* (0x8000-0xBFFF) — режимы tiny и big. В small/huge (CODE в W1)
|
||||
* irq_install вернёт -1/EINVAL: во время прерывания W1 может быть
|
||||
* перемаплен. Дизайн: docs/im2_isr_design.md.
|
||||
* ЦЕПОЧКА: можно поставить до IRQ_CHAIN_MAX независимых обработчиков
|
||||
* (напр. свой тик + FPS-делитель gfx_set_fps_div). Порядок вызова =
|
||||
* порядок слотов; после remove/add взаимный порядок НЕ гарантируется
|
||||
* (переиспользуется первая дыра) — обработчики обязаны быть независимы.
|
||||
*
|
||||
* ПАМЯТЬ: работает во ВСЕХ режимах (tiny/small/big/huge) — трамплин
|
||||
* при коде в W1 копируется в W2 и восстанавливает базовую W1-страницу
|
||||
* вокруг вызова (docs/im2_isr_design.md). Единственное исключение:
|
||||
* в small для очень мелких программ данные могут ещё лежать в W1 —
|
||||
* тогда установка вернёт -1/EINVAL (перейти на huge или увеличить
|
||||
* размер). Обработчик может быть обычной C-функцией где угодно.
|
||||
*
|
||||
* Правила для handler'а (вызывается ~50 раз/с, все регистры
|
||||
* сохраняются снаружи):
|
||||
@@ -29,13 +36,25 @@
|
||||
|
||||
typedef void (*isr_t)(void);
|
||||
|
||||
/* Установить обработчик кадрового прерывания.
|
||||
* 0 — OK; -1 + errno: EBUSY (уже установлен), EINVAL (код/данные
|
||||
* не в W2 — неподходящий memory mode). */
|
||||
/* Максимум одновременных кадровых обработчиков в цепочке. */
|
||||
#define IRQ_CHAIN_MAX 4
|
||||
|
||||
/* Добавить обработчик кадрового прерывания в цепочку.
|
||||
* 0 — OK; -1 + errno: EINVAL (handler==NULL или данные не в W2),
|
||||
* ENOMEM (все IRQ_CHAIN_MAX слотов заняты). */
|
||||
int irq_chain_add(isr_t handler);
|
||||
|
||||
/* Снять ОДИН обработчик из цепочки (по указателю). Идемпотентно;
|
||||
* освобождение последнего слота возвращает IM 1/I штатному DSS. */
|
||||
void irq_chain_remove(isr_t handler);
|
||||
|
||||
/* Совместимость: irq_install == irq_chain_add (EBUSY больше не бывает —
|
||||
* несколько обработчиков легальны; при заполнении слотов — ENOMEM). */
|
||||
int irq_install(isr_t handler);
|
||||
|
||||
/* Снять обработчик и вернуть IM2-таблицу DSS. Безопасно звать
|
||||
* повторно; вызывается автоматически при exit(). */
|
||||
/* Снять ВСЮ цепочку и вернуть IM2-таблицу DSS. Безопасно звать
|
||||
* повторно; вызывается автоматически при exit(). Для точечного
|
||||
* снятия — irq_chain_remove. */
|
||||
void irq_remove(void);
|
||||
|
||||
/* ---- CTC-таймер (Z84C015, вектор 0x06 — отдельный от кадрового) ---
|
||||
|
||||
+23
-1
@@ -29,12 +29,29 @@
|
||||
/* Буфер под таблицу: 256 (выравнивание) + 257 (таблица). */
|
||||
#define _IRQ_VEC_BUF_SIZE (256 + 257)
|
||||
|
||||
/* Буфер под W2-копию кадрового трамплина (small/huge — _irq_tramp в
|
||||
* W1). С запасом над реальным размером трамплина (~150 Б). */
|
||||
#define _IRQ_TRAMP_BUF_SIZE 224
|
||||
|
||||
extern uint8_t _irq_vec_buf[_IRQ_VEC_BUF_SIZE];
|
||||
extern isr_t _irq_user; /* кадровый handler; NULL = нет */
|
||||
|
||||
/* Цепочка кадровых обработчиков (см. _irq_state.c, docs/im2_isr_design). */
|
||||
extern isr_t _irq_chain[IRQ_CHAIN_MAX];
|
||||
extern uint8_t _irq_chain_n; /* число занятых слотов */
|
||||
|
||||
extern isr_t _irq_ctc_user; /* CTC-handler; NULL = нет */
|
||||
extern uint8_t _irq_old_i; /* I до захвата таблицы */
|
||||
extern uint8_t _irq_refs; /* счётчик пользователей таблицы */
|
||||
|
||||
/* W1-remap (all-modes): базовая W1-страница приложения + сохранённая
|
||||
* страница на входе в прерывание; W2-копия трамплина. */
|
||||
extern uint8_t _irq_app_w1_page;
|
||||
extern uint8_t _irq_saved_w1;
|
||||
extern uint8_t _irq_tramp_w2buf[_IRQ_TRAMP_BUF_SIZE];
|
||||
|
||||
/* Порт-регистр окна W1 (страница). */
|
||||
#define _IRQ_W1_PORT 0xA2
|
||||
|
||||
/* Хук CBL-насоса (libc/cbl регистрирует _cbl_pump). Косвенность
|
||||
* сознательная: прямая ссылка трамплина на cbl-модуль притянула бы
|
||||
* его (и 2 КБ кольца) в каждую irq-программу. NULL = CBL выключен,
|
||||
@@ -61,6 +78,11 @@ uint8_t *_irq_table(void);
|
||||
void _irq_tramp(void);
|
||||
void _irq_ctc_tramp(void);
|
||||
|
||||
/* Метка конца кадрового трамплина — для вычисления его размера при
|
||||
* копировании в W2 (_irq_table_ref). Объявлена как массив, чтобы
|
||||
* можно было взять адрес без вызова. */
|
||||
extern uint8_t _irq_tramp_end[];
|
||||
|
||||
/* SMC-операнд `jp` на обработчик DSS (метка в _irq_tramp.c). */
|
||||
extern uint16_t _irq_dss_target;
|
||||
|
||||
|
||||
+37
-8
@@ -1,14 +1,43 @@
|
||||
/*
|
||||
* _irq_state — данные IM2-модуля: буфер вектор-таблицы, указатели на
|
||||
* user-handler'ы (кадровый и CTC), сохранённый регистр I, счётчик
|
||||
* ссылок таблицы. Только данные.
|
||||
* _irq_state — данные IM2-модуля. Только данные.
|
||||
*
|
||||
* Кадровый обработчик теперь ЦЕПОЧКА (_irq_chain[]) вместо одного слота
|
||||
* (см. docs/im2_isr_design.md «Цепочка кадровых обработчиков»).
|
||||
*
|
||||
* Всё, к чему трамплин обращается ВО ВРЕМЯ прерывания, обязано жить в
|
||||
* W2 (0x8000-0xBFFF) — единственное окно, которое DSS не перемапливает
|
||||
* (там стек). В tiny/big/huge вся BSS садится в W2 (code-loc W2 либо
|
||||
* data-loc 0x8000); в small для маленьких программ BSS может оказаться
|
||||
* в W1 — тогда _irq_table_ref вернёт EINVAL (safe). Полное снятие
|
||||
* этого ограничения (резерв area _IM2) — отдельный follow-up.
|
||||
*/
|
||||
|
||||
#include "_irq.h"
|
||||
|
||||
uint8_t _irq_vec_buf[_IRQ_VEC_BUF_SIZE];
|
||||
isr_t _irq_user;
|
||||
isr_t _irq_ctc_user;
|
||||
isr_t _irq_cbl_hook;
|
||||
uint8_t _irq_old_i;
|
||||
uint8_t _irq_refs;
|
||||
|
||||
/* Цепочка кадровых обработчиков. Пустой слот = NULL. Порядок вызова =
|
||||
* порядок индексов; после remove/add взаимный порядок НЕ гарантирован
|
||||
* (переиспользуется первая дыра) — хендлеры обязаны быть независимы. */
|
||||
isr_t _irq_chain[IRQ_CHAIN_MAX];
|
||||
uint8_t _irq_chain_n; /* число занятых слотов (быстрый выход) */
|
||||
|
||||
isr_t _irq_ctc_user; /* CTC-handler; NULL = нет */
|
||||
isr_t _irq_cbl_hook; /* хук CBL-насоса; NULL = нет */
|
||||
uint8_t _irq_old_i; /* I до захвата таблицы */
|
||||
uint8_t _irq_refs; /* счётчик пользователей таблицы */
|
||||
|
||||
/* --- W1-remap для all-modes (см. трамплин) ---------------------------
|
||||
* В small/huge базовый код приложения в W1 (статическая страница).
|
||||
* Трамплин (в W2) вокруг вызова хендлеров восстанавливает эту страницу
|
||||
* в W1, чтобы хендлер, лежащий где угодно в плоском 0x4000-0xBFFF, был
|
||||
* адресуем; на выходе возвращает прочитанное на входе (что было —
|
||||
* возможно DSS-страница, если прервали сисколл). */
|
||||
uint8_t _irq_app_w1_page; /* базовая W1-страница (IN 0xA2 при install) */
|
||||
uint8_t _irq_saved_w1; /* W1 на входе в прерывание (для restore) */
|
||||
|
||||
/* W2-копия трамплина: в small/huge сам _irq_tramp лежит в W1-коде и в
|
||||
* момент прерывания может быть перемаплен — _irq_table_ref копирует его
|
||||
* байты сюда (BSS=W2) и направляет вектор на копию. В tiny/big копия не
|
||||
* нужна (трамплин уже в W2), буфер не используется. */
|
||||
uint8_t _irq_tramp_w2buf[_IRQ_TRAMP_BUF_SIZE];
|
||||
|
||||
+42
-11
@@ -1,10 +1,19 @@
|
||||
/*
|
||||
* _irq_table — жизненный цикл общей IM2-таблицы (счётчик ссылок).
|
||||
*
|
||||
* Первый ref: проверка W2-адресов, построение таблицы (заливка H +
|
||||
* jp _irq_tramp по смещению H), I := H, IM 2, atexit-уборка.
|
||||
* Первый ref: проверка W2-адреса БУФЕРА (данные обязаны быть в W2),
|
||||
* запись базовой W1-страницы приложения, W2-размещение кадрового
|
||||
* трамплина (прямо или копией — см. ниже), построение таблицы (заливка
|
||||
* H + jp <W2-трамплин> по смещению H), I := H, IM 2, atexit-уборка.
|
||||
* Последний unref: I := старое, IM 1 (штатный режим DSS — его
|
||||
* обработчик на 0x0038; см. docs/samples/sprinterIntLib.asm).
|
||||
*
|
||||
* All-modes (docs/im2_isr_design.md): вектор-таблица и данные цепи
|
||||
* должны быть в W2 (это BSS — W2 в tiny/big/huge; в small для мелких
|
||||
* программ может уехать в W1 → EINVAL, safe). Код трамплина в small/
|
||||
* huge лежит в W1 и в момент прерывания может быть перемаплен — если
|
||||
* &_irq_tramp < 0x8000, копируем его байты в _irq_tramp_w2buf (BSS=W2)
|
||||
* и направляем вектор туда. Трамплин написан copy-safe (см. его шапку).
|
||||
*/
|
||||
|
||||
#include <string.h>
|
||||
@@ -21,6 +30,18 @@ static uint8_t irq_get_i(void) __naked
|
||||
__endasm;
|
||||
}
|
||||
|
||||
/* Прочитать порт-регистр окна W1 (0xA2) — текущая физическая страница.
|
||||
* В main-контексте (install) это статическая базовая страница
|
||||
* приложения (small/huge); в tiny/big значение не используется по
|
||||
* назначению, но remap безвреден. */
|
||||
static uint8_t irq_read_w1_page(void) __naked
|
||||
{
|
||||
__asm
|
||||
in a, (#_IRQ_W1_PORT)
|
||||
ret
|
||||
__endasm;
|
||||
}
|
||||
|
||||
/* I := A, режим IM 2. */
|
||||
static void irq_set_i_im2(uint8_t i_val) __naked
|
||||
{
|
||||
@@ -65,17 +86,28 @@ int _irq_table_ref(void)
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Трамплины и таблица обязаны жить в W2 (0x8000-0xBFFF): во время
|
||||
* прерывания W1/W3 могут быть перемаплены. CODE вне W2 =
|
||||
* неподходящий memory mode (small/huge). */
|
||||
uint16_t tramp = (uint16_t)&_irq_tramp;
|
||||
uint16_t buf = (uint16_t)_irq_vec_buf;
|
||||
if (tramp < 0x8000 || tramp >= 0xC000 ||
|
||||
buf < 0x8000 || buf + _IRQ_VEC_BUF_SIZE > 0xC000) {
|
||||
/* Вектор-таблица и данные цепи обязаны жить в W2 (0x8000-0xBFFF):
|
||||
* во время прерывания W1/W3 могут быть перемаплены. Это BSS — W2
|
||||
* во всех режимах, КРОМЕ small для мелких программ (данные ещё в
|
||||
* W1) → EINVAL, safe. Код трамплина в W1 обрабатывается копией. */
|
||||
uint16_t buf = (uint16_t)_irq_vec_buf;
|
||||
if (buf < 0x8000 || buf + _IRQ_VEC_BUF_SIZE > 0xC000) {
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Базовая W1-страница приложения — для W1-remap в трамплине. */
|
||||
_irq_app_w1_page = irq_read_w1_page();
|
||||
|
||||
/* W2-адрес кадрового трамплина: если оригинал уже в W2 (tiny/big) —
|
||||
* прямо; иначе (small/huge, код в W1) — копия в W2-буфер. */
|
||||
uint16_t tramp = (uint16_t)&_irq_tramp;
|
||||
if (tramp < 0x8000) {
|
||||
uint16_t n = (uint16_t)_irq_tramp_end - tramp;
|
||||
memcpy(_irq_tramp_w2buf, (const void *)tramp, n);
|
||||
tramp = (uint16_t)_irq_tramp_w2buf;
|
||||
}
|
||||
|
||||
uint8_t *table = _irq_table();
|
||||
uint8_t h = (uint8_t)((uint16_t)table >> 8);
|
||||
|
||||
@@ -84,11 +116,10 @@ int _irq_table_ref(void)
|
||||
/* Старое I — только чтобы вернуть регистр при unref: DSS работает
|
||||
* в IM 1 (I=0x3F — наследие Spectrum ROM, НЕ таблица). */
|
||||
_irq_old_i = irq_get_i();
|
||||
_irq_dss_target = 0x0038;
|
||||
|
||||
memset(table, h, 257);
|
||||
uint8_t *stub = table + h; /* h < 0xC0 — с [0xFF] не пересекается */
|
||||
stub[0] = 0xC3; /* jp _irq_tramp */
|
||||
stub[0] = 0xC3; /* jp <W2-трамплин> */
|
||||
stub[1] = (uint8_t)(tramp & 0xFF);
|
||||
stub[2] = (uint8_t)(tramp >> 8);
|
||||
|
||||
|
||||
+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;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* irq_chain_add — добавить обработчик кадрового прерывания в цепочку.
|
||||
* Возврат 0 / -1+errno: EINVAL (handler==NULL, или данные не в W2 —
|
||||
* из _irq_table_ref), ENOMEM (все IRQ_CHAIN_MAX слотов заняты).
|
||||
*
|
||||
* Первый слот захватывает IM2-таблицу (_irq_table_ref). Порядок вызова
|
||||
* хендлеров = порядок индексов; после remove/add взаимный порядок НЕ
|
||||
* гарантирован — хендлеры обязаны быть независимы (см. irq.h).
|
||||
*/
|
||||
|
||||
#include <errno.h>
|
||||
#include "_irq.h"
|
||||
|
||||
int irq_chain_add(isr_t handler)
|
||||
{
|
||||
if (!handler) { errno = EINVAL; return -1; }
|
||||
|
||||
uint8_t i;
|
||||
for (i = 0; i < IRQ_CHAIN_MAX; i++)
|
||||
if (!_irq_chain[i]) break;
|
||||
if (i == IRQ_CHAIN_MAX) { errno = ENOMEM; return -1; }
|
||||
|
||||
/* Первый обработчик — захватить таблицу и включить IM2. Делаем ДО
|
||||
* записи слота: при неудаче (EINVAL в small) состояние не тронуто.
|
||||
* Пока слот не выставлен, chain_n==0 → трамплин на кадре просто
|
||||
* чейнит к DSS. */
|
||||
if (_irq_chain_n == 0)
|
||||
if (_irq_table_ref() != 0)
|
||||
return -1;
|
||||
|
||||
IRQ_DISABLE();
|
||||
_irq_chain[i] = handler;
|
||||
_irq_chain_n++;
|
||||
IRQ_ENABLE();
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* irq_chain_remove — снять ОДИН обработчик кадрового прерывания из
|
||||
* цепочки (по указателю). Освобождение последнего слота отпускает
|
||||
* IM2-таблицу (_irq_table_unref → возврат IM 1, если больше никто не
|
||||
* держит). Идемпотентно: неизвестный/NULL handler игнорируется.
|
||||
*/
|
||||
|
||||
#include "_irq.h"
|
||||
|
||||
void irq_chain_remove(isr_t handler)
|
||||
{
|
||||
if (!handler) return;
|
||||
|
||||
for (uint8_t i = 0; i < IRQ_CHAIN_MAX; i++) {
|
||||
if (_irq_chain[i] == handler) {
|
||||
IRQ_DISABLE();
|
||||
_irq_chain[i] = 0;
|
||||
_irq_chain_n--;
|
||||
IRQ_ENABLE();
|
||||
if (_irq_chain_n == 0)
|
||||
_irq_table_unref();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -42,6 +42,10 @@ int irq_ctc_install(isr_t handler, uint8_t div2, uint8_t div3)
|
||||
{
|
||||
if (!handler) { errno = EINVAL; return -1; }
|
||||
if (_irq_ctc_user) { errno = EBUSY; return -1; }
|
||||
/* CTC-трамплин вектрится напрямую (table[0x06] = &_irq_ctc_tramp) в
|
||||
* обход W2-копии кадрового пути — поэтому CTC пока требует код в W2
|
||||
* (tiny/big). All-modes для CTC (копия _irq_ctc_tramp) — follow-up. */
|
||||
if ((uint16_t)&_irq_ctc_tramp < 0x8000) { errno = EINVAL; return -1; }
|
||||
if (_irq_table_ref() != 0) return -1;
|
||||
|
||||
uint16_t tramp = (uint16_t)&_irq_ctc_tramp;
|
||||
|
||||
@@ -1,17 +1,13 @@
|
||||
/*
|
||||
* irq_install — установить user-handler кадрового прерывания.
|
||||
* Захватывает общую IM2-таблицу (_irq_table_ref) и включает кадровый
|
||||
* путь трамплина. Детали и результаты отладки: docs/im2_isr_design.md.
|
||||
* irq_install — обёртка над цепочкой (обратная совместимость).
|
||||
* Теперь просто добавляет обработчик в цепь: EBUSY больше не бывает
|
||||
* (несколько хендлеров легальны), при 4 занятых слотах — ENOMEM.
|
||||
* Возврат/errno — как у irq_chain_add.
|
||||
*/
|
||||
|
||||
#include <errno.h>
|
||||
#include "_irq.h"
|
||||
|
||||
int irq_install(isr_t handler)
|
||||
{
|
||||
if (!handler) { errno = EINVAL; return -1; }
|
||||
if (_irq_user) { errno = EBUSY; return -1; }
|
||||
if (_irq_table_ref() != 0) return -1;
|
||||
_irq_user = handler;
|
||||
return 0;
|
||||
return irq_chain_add(handler);
|
||||
}
|
||||
|
||||
+12
-4
@@ -1,16 +1,24 @@
|
||||
/*
|
||||
* irq_remove — снять кадровый user-handler и отпустить IM2-таблицу
|
||||
* irq_remove — снять ВСЮ кадровую цепочку и отпустить IM2-таблицу
|
||||
* (последний пользователь возвращает I/IM 1 — штатный режим DSS).
|
||||
* Идемпотентно; вызывается и из atexit-уборки.
|
||||
* Идемпотентно; вызывается и из atexit-уборки, поэтому обязано снимать
|
||||
* всё разом (exit не знает про отдельные слоты). Для точечного снятия
|
||||
* одного обработчика — irq_chain_remove.
|
||||
*/
|
||||
|
||||
#include "_irq.h"
|
||||
|
||||
void irq_remove(void)
|
||||
{
|
||||
if (!_irq_user) return;
|
||||
if (_irq_chain_n == 0) return;
|
||||
|
||||
IRQ_DISABLE();
|
||||
_irq_user = 0;
|
||||
for (uint8_t i = 0; i < IRQ_CHAIN_MAX; i++)
|
||||
_irq_chain[i] = 0;
|
||||
_irq_chain_n = 0;
|
||||
IRQ_ENABLE();
|
||||
|
||||
/* Цепь держала РОВНО одну ссылку на таблицу (взята при первом
|
||||
* слоте) — отпускаем её один раз. */
|
||||
_irq_table_unref();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user