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
+35 -16
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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;
}
+36
View File
@@ -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;
}
+25
View File
@@ -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;
}
}
}
+4
View File
@@ -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;
+5 -9
View File
@@ -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
View File
@@ -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();
}