Files
Sprinter-SDCC/docs/im2_isr_design.md
T
snark13 c71e249a4e Add full compiler toolchain, libc, examples and reference docs
First substantive commit: the entire Sprinter C compiler tree on top of
the bare README+gitignore initial commit.

What's in here:
  bin/sprinter-cc        — driver script invoking SDCC + linker + mkexe
  libc/                  — Sprinter-specific libc layer over ESTEX/BIOS
                           (conio, gfx, io, mem, stdio + headers)
  runtime/               — crt0 variants (default/small/banked/minimal)
                           + heap + bank trampolines
  toolchain/             — mkexe (SprintEXE packer, C + tests)
  examples/              — 30 demo programs (gfx, file I/O, env, time, …)
  lib/Makefile           — builds the libc archive (sprinter.lib)
  docs/                  — converted Sprinter manuals + asm reference samples
  third_party/           — solid-c reference compiler dump + sdcc setup script
  release_docs/          — packaging / release notes

gitignore overhaul:
  • Drop dangerous blanket patterns: *.asm (would hide docs/samples/*.asm)
    and *.exe (case-insensitive match was hiding third_party/solid-c/*.EXE
    on macOS APFS).  Replaced with examples/*/*.{asm,exe,…} and lib/*.lib.
  • Restore tracking of toolchain/mkexe/tests/{one,big}.bin — those are
    INPUT fixtures, not build outputs.
  • Collapse the duplicated SDCC/C/Sdcc sections into one section per
    concern (build outputs / vendored / OS-junk).
  • Add .sprinter-cc-*/, build/ (catches lib/build/ too), .claude/.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-03 16:13:21 +03:00

12 KiB
Raw Blame History

IM2 Interrupt Handlers — Design Document

Status: РЕАЛИЗАЦИЯ ОТЛОЖЕНА (до пост-релизной версии). Обязательная фича для v2.

Этот документ собирает всё, что мы знаем о прерываниях Sprinter и план реализации user-задаваемых ISR через Z80 IM 2 mode. Когда возьмёмся за реализацию — читать этот файл, чтобы не повторять research.

Зачем нужны прерывания

  • Timer ISR (50/60 Hz) — счётчик кадров, плавная анимация без busy-loop, тайминги
  • Mouse / keyboard async-обработка — без polling
  • Music playback — AY-3-8910, COVOX через прерывания
  • Real-time games — input + game logic + render в interrupt-driven архитектуре

Hardware-факты (из docs/converted)

Sources of vector 0xFF

Источник Detect bit Частота
Frame (screen refresh) (default if none of below) 50/60 Hz
Keyboard port 0x19 (COM_A) bit 0 event-driven
CBL/COVOX (sound) port 0xFE bit 7 (sample request) sample-rate-dependent
Mouse — (hardware interrupt not wired)
ISA другой vector (configurable) depends

Источники:

  • docs/converted/Forum.txt:956 — кадровые и клавиатурные прерывания приходят с vector 0xFF; различаются по bit 0 порта 0x19. От мыши прерываний нет
  • docs/converted/Forum.txt:758-764 — CBL также vector 0xFF, отличить по bit 7 порта 0xFE
  • docs/converted/IvanMak.txt:1086READ_KBD: IN(0x19), bit 0 = "байт принят"; затем IN(0x18) = data byte; нужно drain FIFO (до 3 байт)
  • docs/converted/IvanMak.txt:1471 — ВАЖНОЕ ОГРАНИЧЕНИЕ: vector table + ISR + stack ОБЯЗАНЫ быть в области 0x8000..0xBFFF (window 2). Иначе BIOS будет отключать прерывания на каждой вызове функции

Что DSS делает в своём ISR (предположения, требует verification)

DSS shell имеет свой IM 2 handler:

  • Drain'ит keyboard FIFO в свой буфер (читается через ESTEX WAITKEY/SCANKEY)
  • Возможно обновляет ESTEX SYSTIME ($21) tick counter
  • Возможно poll'ит mouse (хотя hardware-IRQ от mouse нет — может быть software polling)
  • Refresh курсора мыши (он же видимый и движется в shell)

Без chain'инга к DSS:

  • Сломается клавиатура (ESTEX kbd functions не получат байты)
  • Может сломаться SYSTIME counter
  • Может перестать обновляться mouse cursor

IM2-трюк

Стандартная схема для одиночного ISR address:

  1. Аллоцировать 257-байтный буфер заполненный одинаковым байтом H
  2. Загрузить I = H (например H=0xA3 → table at 0xA300, обращения 0xA300..0xA400)
  3. При прерывании CPU читает байт по (I<<8)|v и следующий
  4. Если оба байта = H → ISR address = (H<<8)|H = HHHH
  5. По адресу HHHH положить jp real_isr

Поскольку для нас интересен только vector 0xFF: read bytes at (0xA3FF) and (0xA400). Если table заполнена H=0xA3 — оба байта читаются как 0xA3. ISR_ADDR = 0xA3A3. По адресу 0xA3A3 кладём 3-байтовый jp _trampoline.

Предлагаемый дизайн

Public API (libc/include/irq.h)

typedef void (*isr_t)(void);

int  irq_install(isr_t handler);  /* 0 OK, -1 error (already installed) */
void irq_remove(void);

/* Convenience macros — wrap DI/EI when modifying volatile globals
 * shared between main and ISR. */
#define IRQ_DISABLE()  __asm di __endasm
#define IRQ_ENABLE()   __asm ei __endasm

Пример использования:

volatile uint16_t ticks = 0;
void on_tick(void) { ticks++; }

int main(void) {
    irq_install(on_tick);
    uint16_t start = ticks;
    while (ticks - start < 50) { /* wait 1s */ }
    irq_remove();
}

Внутренности

Аллокация vector page:

  • Static buffer 513 байт в _BSS (sprinter.lib).
  • Размер 513 = 256 (выравнивание) + 257 (сама table) — в худшем случае выравнивание тратит 256 байт.
  • Внутри буфера ищем 256-byte aligned адрес. SDCC может не поддерживать __attribute__((aligned(256))) — придётся через ассемблер с .area _BSS_ALIGNED и линкер-флаг для выравнивания, или через runtime поиск aligned position.
  • Alternative: заранее линкуем vector page по фиксированному адресу через linker flag -Wl-b_VECTORS=0xA300 (как у банков). Стабильнее.

Trampoline в W2:

  • Маленький asm-блок (~50 байт) который:
    1. ex af,af'; exx; push ix; push iy — сохранить ВСЕ регистры
    2. in a, (0xE2); push af — сохранить current W3 page byte
    3. in a, (0x19); bit 0, a; jr z, _not_kbd — keyboard?
      • keyboard path: chain to DSS old ISR (jp/call to saved address)
    4. in a, (0xFE); bit 7, a; jr z, _not_cbl — CBL? (Phase 2)
    5. Frame path: ld hl, (user_handler); ld a, h; or l; jr z, _no_user; call hl_indirect
    6. pop af; out (0xE2), a — restore W3
    7. pop iy; pop ix; exx; ex af,af'; ei; reti

Где живёт trampoline:

  • Для tiny mode: _CODE = W2 → естественно
  • Для big mode: _CODE = W2 → естественно
  • Для small/huge: _CODE = W1, но trampoline ДОЛЖЕН быть в W2 (W1 может swap'нуться)
  • Решение: новая linker area _TRAMP_W2 с absolute address в W2 (например 0xBE00). sprinter-cc размещает её через -Wl-b_TRAMP_W2=0xBE00. trampoline.s помечает себя .area _TRAMP_W2.

Chain to DSS:

  • В irq_install:
    ld a, i              ; A = current vector page high byte
    ld (old_I), a
    ld h, a
    ld l, #0xFF
    ld a, (hl)           ; A = vector_high (= old_I по trick'у)
    ld d, a
    ld e, a              ; DE = address of DSS's IM2 jp
    ld hl, (de)          ; HL = DSS's old jp target
    ld (dss_old_isr), hl
    
  • В trampoline keyboard-path:
    ld hl, (dss_old_isr)
    push hl
    ret                  ; jumps to DSS ISR which ends with EI; RETI
    
  • Опасность: DSS's ISR может предполагать что регистры свежие (как только что от CPU) → возможно нужно НЕ saving некоторые регистры до chain'а

irq_remove:

  • DI
  • Restore I to old value
  • Restore IM mode (обычно был IM 2 → IM 2; редко IM 1 if shell upgraded)
  • Free vector page if dynamically allocated
  • EI

Ограничения user handler'а

User's ISR может:

  • Читать/писать volatile globals
  • Делать дешёвые арифметические операции
  • Менять g_text_attr (но не вызывать putch/cputs)

User's ISR НЕ должен:

  • Вызывать printf / puts / malloc / любые ESTEX/BIOS функции — они могут не быть re-entrant
  • Использовать gfx_* — они swap'ят W3, наш trampoline уже сохраняет порт но если внутри ISR будет повторный swap то trampoline не сможет восстановить
  • Запускать accelerator (LD D,D и т.д.) — accel меняет систему команд CPU
  • Долго работать — ISR должен быть быстрым (< 1ms), иначе пропустим следующий

Открытые вопросы

  1. Что именно DSS делает в своём ISR — disassemble DSS или вызвать его с инструментировкой
  2. ld a, i semantics на Sprinter — на Z80 P/V flag отражает IFF2; нужно для save/restore
  3. Alignment vector page — найти SDCC-совместимый способ: либо linker absolute area, либо runtime align внутри 513-байтного буфера
  4. Re-entrancy ESTEX из main во время ISR:
    • Если main вызывает ESTEX и в это время приходит interrupt → DSS chain'инг должен работать корректно (DSS уже спроектирован под IM 2)
    • Если main вызывает BIOS (RST 8) — это отключает прерывания на время вызова, OK
  5. Memory budget — vector page 513 байт в BSS уменьшит heap. В tiny mode с heap ~10KB это ~5%. OK.

Phase 1 acceptance

  • examples/irq_test/ — счётчик тиков растёт с 50 Hz
  • Клавиатура продолжает работать через DSS chain (можно прервать тест клавишей)
  • Корректный exit — DSS shell получает управление обратно без crash
  • Работает во всех memory modes (tiny, small, big, huge)
  • Memory note memory/sprinter_im2_isr.md с описанием ABI и ограничений

Phase 2 (когда понадобится)

  • CBL/COVOX prerequisite handler (для audio playback)
  • ISA interrupt handler (для ZX-Bus карт)
  • Multiple user handler chain (e.g. tick + sound)

Альтернатива: отдельный memory mode "im2"

Идея: вместо того чтобы крутить trampoline location во всех существующих режимах, сделать отдельный --memory im2 который:

  • Forces CODE в W2 (как tiny)
  • Reserves определённый адрес в W2 под vector page и trampoline (например 0xBE00..0xBFFF)
  • crt0_im2.s ставит IM 2 в начале (заменяет DSS handler с chain)
  • crt0_im2.s восстанавливает на exit

Плюсы:

  • Меньше matrix-сложности (irq работает только в одном mode)
  • Можно агрессивно reserved'ить W2-память
  • Тестируется как единое целое

Минусы:

  • Программам приходится явно выбирать --memory im2 для использования прерываний
  • Дублирование crt0 и runtime

Текущее предложение — пойти этим путём (отдельный mode) для v2, не лезть в существующие crt0.

Внешние ссылки

  • docs/converted/IvanMak.txt:1040-1054 — секция 9.3 "Прерывания от ISA" + 9.4 "AT-Клавиатура"
  • docs/converted/IvanMak.txt:1469-1473 — IM 2 ограничения (table/stack/ISR в W2)
  • docs/converted/Forum.txt:758-764 — CBL interrupt discrimination
  • docs/converted/Forum.txt:956 + :1049 — vector 0xFF disambiguation
  • docs/converted/Parinov.txt:601 — IM 1 alternative (handler по адресу 0x0038, не наш путь)

История

  • 2026-06-01 — research собран в этот документ, реализация отложена до v2