/* * irq_install — установить user-handler кадрового прерывания (IM 2). * * Строит 257-байтную вектор-таблицу (выравнивание 256 в рантайме, * заполнение байтом H = старший байт адреса таблицы; `jp _irq_tramp` * лежит внутри таблицы по смещению H — читаются лишь байты * [0xFF]/[0x100] для внешнего вектора 0xFF; вектора встроенной * периферии Z84C015 (SIO 0x10..0x1E, CTC 0x06) штатно выключены, но * заливка H перехватывает и их — чейн на 0x0038 валиден для всех). * * ВАЖНО (проверено по docs/samples/sprinterIntLib.asm и * SIO_CTC_KEY.asm): DSS работает в IM 1 — его обработчик живёт на * 0x0038, а I=0x3F — наследие Spectrum ROM, НЕ признак IM 2-таблицы. * Поэтому чейн из трамплина ВСЕГДА идёт на 0x0038 (jp 0x0038 с * interrupted-PC на стеке в точности имитирует вход IM 1 / RST 38), * а старое I сохраняется только чтобы вернуть регистр при remove. * * irq_remove вешается на atexit — выход без снятия оставил бы I/IM2 * на памяти умершего процесса. */ #include #include #include #include "_irq.h" /* Прочитать регистр I. */ static uint8_t irq_get_i(void) __naked { __asm ld a, i 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; } int irq_install(isr_t handler) { if (!handler) { errno = EINVAL; return -1; } if (_irq_user) { errno = EBUSY; return -1; } /* Трамплин и таблица обязаны жить в 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) { errno = EINVAL; return -1; } uint8_t *table = (uint8_t *)(((uint16_t)(buf + 255u)) & 0xFF00u); uint8_t h = (uint8_t)((uint16_t)table >> 8); IRQ_DISABLE(); /* Старое I — только для восстановления регистра в irq_remove. */ _irq_old_i = irq_get_i(); /* DSS работает в IM 1 — чейн всегда на его обработчик 0x0038. * НЕ читать «таблицу по I»: I=0x3F — мусорное наследие Spectrum * ROM, чтение [0x3FFF]/[0x4000] даёт случайный адрес (0x00BF) и * зависание на первом же прерывании. */ _irq_dss_target = 0x0038; memset(table, h, 257); uint8_t *stub = table + h; /* h < 0xC0 — с [0xFF] не пересекается */ stub[0] = 0xC3; /* jp _irq_tramp */ stub[1] = (uint8_t)(tramp & 0xFF); stub[2] = (uint8_t)(tramp >> 8); _irq_user = handler; irq_set_i_im2(h); IRQ_ENABLE(); /* Выход без снятия = крах шелла (I указывает в память процесса). */ static uint8_t atexit_armed; if (!atexit_armed) { atexit_armed = 1; atexit(irq_remove); } return 0; }