/* * _irq_table — жизненный цикл общей IM2-таблицы (счётчик ссылок). * * Первый ref: проверка W2-адресов, построение таблицы (заливка H + * jp _irq_tramp по смещению H), I := H, IM 2, atexit-уборка. * Последний unref: I := старое, IM 1 (штатный режим DSS — его * обработчик на 0x0038; см. docs/samples/sprinterIntLib.asm). */ #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; } /* 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 могут быть перемаплены. 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 = _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(); _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_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(); }