/* * _irq_table — жизненный цикл общей IM2-таблицы (счётчик ссылок). * * Первый ref: проверка W2-адреса БУФЕРА (данные обязаны быть в W2), * запись базовой W1-страницы приложения, W2-размещение кадрового * трамплина (прямо или копией — см. ниже), построение таблицы (заливка * H + jp по смещению 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 #include #include #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 */ 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(); }