/* * irq_install — установить user-handler кадрового прерывания (IM 2). * * Строит 257-байтную вектор-таблицу (выравнивание 256 в рантайме, * заполнение байтом H = старший байт адреса таблицы; `jp _irq_tramp` * лежит внутри таблицы по смещению H — Sprinter шлёт только вектор * 0xFF, читаются лишь байты [0xFF]/[0x100]), запоминает старый * обработчик DSS из его таблицы (по регистру I), патчит SMC-операнд * чейна и переключает I/IM2. irq_remove вешается на atexit — выход * без снятия оставил бы I на памяти умершего процесса. */ #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(); _irq_old_i = irq_get_i(); if (_irq_old_i) { /* Таблица DSS: адрес обработчика — байты [0xFF]/[0x100]. */ const uint8_t *old = (const uint8_t *)(((uint16_t)_irq_old_i << 8) + 0xFF); _irq_dss_target = (uint16_t)(old[0] | ((uint16_t)old[1] << 8)); } else { _irq_dss_target = 0x0038; /* фолбэк: DSS в IM 1 */ } 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; }