8a952b99eb
- irq_ctc_install(handler, div2, div3) / irq_ctc_remove: канал 2 CTC делит видеотакт 875 кГц (1 тик = 1 знакоместо), канал 3 считает от него и прерывает; f = 875000/(div2*div3), div 0 = 256. Пресет IRQ_CTC_VSYNC_DIV2/3 = 112*160 — точное начало кадра ~48.8 Гц БЕЗ примеси клавиатуры (вектор 0x06 отделён от общего 0xFF) - CTC-трамплин: полный сейв -> handler -> EI/RETI; RETI обязателен (daisy chain Z84C015 снимает IUS только по опкоду RETI); к DSS не чейнится — личное прерывание - общая IM2-таблица под счётчиком ссылок (_irq_table.c): кадровый и CTC-хендлеры независимы, последний unref возвращает I/IM 1; atexit-уборка глушит CTC обязательно (иначе кГц-прерывания душат шелл после выхода) - порты/слова по docs/samples: CH0=0x10/CH2=0x12/CH3=0x13, 0x57/0xD7/вектор в CH0, стоп 0x03 - irqtest: CTC-vsync параллельно с кадровым + произвольная частота; MAME: frame 48 Гц, ctc(vsync) 49 Гц (parallel frame жив), ctc(50x50) 350 Гц точно по формуле, remove/выход чистые Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
116 lines
3.1 KiB
C
116 lines
3.1 KiB
C
/*
|
||
* _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 <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;
|
||
}
|
||
|
||
/* 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();
|
||
}
|