5184415fc4
Первая версия висла на первом прерывании. Две причины (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>
97 lines
3.8 KiB
C
97 lines
3.8 KiB
C
/*
|
||
* 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;
|
||
}
|