Files
Sprinter-SDCC/libc/irq/irq_install.c
T
snark13 5184415fc4 irq: фикс Phase 1 после отладки — DSS работает в IM 1, чейн всегда на 0x0038
Первая версия висла на первом прерывании. Две причины (verified по
docs/samples/sprinterIntLib.asm, SIO_CTC_KEY.asm и исходникам MAME):

- DSS работает в IM 1 (обработчик 0x0038); I=0x3F — наследие Spectrum
  ROM, НЕ таблица: чтение [I<<8|0xFF] давало мусор (0x00BF) и прыжок в
  никуда. Чейн из трамплина теперь ВСЕГДА jp 0x0038 (interrupted-PC на
  стеке = имитация RST 38); irq_remove безусловно восстанавливает IM 1
- CBL-фильтр по биту 7 порта 0xFE убран: при выключенном CBL бит
  подтянут к 1 (MAME kbd_fe_r: data |= 0xE0) — каждый кадр ложно
  уходил в чейн, user-handler не вызывался бы. Вернуть в Phase 2
  вместе с поддержкой CBL

Попутно подтверждено: порт 0x19 = SIO-A RR0 (Z84C015), бит 0 = Rx
Available; вектора встроенной периферии SIO 0x10..0x1E / CTC 0x06
(заливка 257×H ловит любые); внешний вектор 0xFF.

irqtest: диагностика I до установки + фаза без ESTEX; прогон в MAME:
~49 Гц, клавиатура жива, remove останавливает тики, чистый выход.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 09:05:28 +03:00

97 lines
3.8 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_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 <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;
}
/* 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;
}