Files
Sprinter-SDCC/libc/irq/_irq_table.c
T
snark13 2c6f4e33c3 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>
2026-07-15 10:23:39 +03:00

147 lines
5.1 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
* _irq_table — жизненный цикл общей IM2-таблицы (счётчик ссылок).
*
* Первый 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>
#include <errno.h>
#include <sprinter_exit.h>
#include "_irq.h"
/* Прочитать регистр I. */
static uint8_t irq_get_i(void) __naked
{
__asm
ld a, i
ret
__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
{
(void)i_val;
__asm
;; __sdcccall(1): uint8_t-аргумент уже в A.
ld i, a
im 2
ret
__endasm;
}
/* I := A, штатный IM 1 (DSS). */
static void irq_set_i_im1(uint8_t i_val) __naked
{
(void)i_val;
__asm
ld i, a
im 1
ret
__endasm;
}
/* Уборка на exit: снять оба хендлера (CTC глушится обязательно —
* иначе после выхода его прерывания задушат шелл). */
static void irq_atexit(void)
{
irq_ctc_remove();
irq_remove();
}
uint8_t *_irq_table(void)
{
return (uint8_t *)(((uint16_t)((uint16_t)_irq_vec_buf + 255u))
& 0xFF00u);
}
int _irq_table_ref(void)
{
if (_irq_refs) {
_irq_refs++;
return 0;
}
/* Вектор-таблица и данные цепи обязаны жить в 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);
IRQ_DISABLE();
/* Старое I — только чтобы вернуть регистр при unref: DSS работает
* в IM 1 (I=0x3F — наследие Spectrum ROM, НЕ таблица). */
_irq_old_i = irq_get_i();
memset(table, h, 257);
uint8_t *stub = table + h; /* h < 0xC0 — с [0xFF] не пересекается */
stub[0] = 0xC3; /* jp <W2-трамплин> */
stub[1] = (uint8_t)(tramp & 0xFF);
stub[2] = (uint8_t)(tramp >> 8);
irq_set_i_im2(h);
IRQ_ENABLE();
_irq_refs = 1;
static uint8_t atexit_armed;
if (!atexit_armed) {
atexit_armed = 1;
atexit(irq_atexit);
}
return 0;
}
void _irq_table_unref(void)
{
if (!_irq_refs) return;
if (--_irq_refs) return;
IRQ_DISABLE();
irq_set_i_im1(_irq_old_i);
IRQ_ENABLE();
}