libc: IM2 Phase 2b — звук CBL/COVOX через callback fill(), без кольца libc

CBL уже имеет аппаратный буфер 256 Б (2×128, двойная буферизация на
стороне железа) — держать поверх него ещё одно кольцо в libc было бы
лишней копией. cbl_open(freq, fmt, pump_mode, underrun_mode, fill)
регистрирует callback, вызываемый из ISR за очередным блоком; он сам
пропихивает данные приложения (откуда угодно) через cbl_push_otir()/
cbl_push_accel() — без промежуточного буфера.

- два насоса: OTIR (порт 0x4F) и ACCEL (акселератор, спец-страница
  EMM 0xFD@0xC000); OTIR+16-бит запрещён (EINVAL) — по исходнику MAME
  порт данных физически не может собрать 16-бит сэмпл из пары байт;
- форматы CBL_FMT_MONO8/16/STEREO8/16, частоты 7.8..109к;
- CBL_UNDERRUN_APP (по умолчанию, недолив не наша забота) /
  CBL_UNDERRUN_SILENCE (буфер тишины malloc'ится только в этом режиме);
- tests/cbltest: матрица 64 комбинации (2 насоса × 8 форматов × 4
  частоты); tests/cblwav: banked-стрим речи с дискеты (физстраницы
  кэшированы заранее — mem_get_page нельзя звать из fill()/ISR);
  tests/cblstream: единственный случай с собственным кольцом уровня
  приложения (диск нельзя читать из fill()).

Verified в MAME 2026-07-07 — все три теста работают.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-07 21:21:54 +03:00
parent 8a952b99eb
commit 5086c47f0f
28 changed files with 1208 additions and 22 deletions
+6
View File
@@ -35,6 +35,12 @@ extern isr_t _irq_ctc_user; /* CTC-handler; NULL = нет */
extern uint8_t _irq_old_i; /* I до захвата таблицы */
extern uint8_t _irq_refs; /* счётчик пользователей таблицы */
/* Хук CBL-насоса (libc/cbl регистрирует _cbl_pump). Косвенность
* сознательная: прямая ссылка трамплина на cbl-модуль притянула бы
* его (и 2 КБ кольца) в каждую irq-программу. NULL = CBL выключен,
* трамплин НЕ проверяет бит 7 порта 0xFE (он подтянут к 1). */
extern isr_t _irq_cbl_hook;
/* Порты встроенной периферии Z84C015 (docs/samples/SIO_CTC_KEY.asm). */
#define _IRQ_CTC_CH0 0x10
#define _IRQ_CTC_CH2 0x12
+1
View File
@@ -9,5 +9,6 @@
uint8_t _irq_vec_buf[_IRQ_VEC_BUF_SIZE];
isr_t _irq_user;
isr_t _irq_ctc_user;
isr_t _irq_cbl_hook;
uint8_t _irq_old_i;
uint8_t _irq_refs;
+55 -14
View File
@@ -1,21 +1,23 @@
/*
* _irq_tramp — вход IM2-прерывания (вектор 0xFF, все источники).
* _irq_tramp — вход IM2-прерывания (вектор 0xFF: кадр/клавиатура/CBL).
*
* До выяснения источника трогаем только AF: клавиатура (бит 0 порта
* 0x19 = SIO-A RR0 «Rx Character Available») уходит напрямую в DSS.
* Кадровое прерывание: полный сейв обоих наборов + IX/IY, вызов
* user-handler'а, симметричный restore. В конце ВСЕГДА jp на
* обработчик DSS 0x0038 (DSS работает в IM 1; jp с interrupted-PC на
* стеке имитирует RST 38) — он делает свой save/restore и завершает
* EI/RETI: клавиатурный FIFO, SYSTIME и курсор мыши живут.
* До выяснения источника трогаем только AF:
* - клавиатура (бит 0 порта 0x19 = SIO-A RR0 «Rx Character
* Available») уходит напрямую в DSS;
* - CBL (бит 7 порта 0xFE) проверяется ТОЛЬКО при зарегистрированном
* хуке _irq_cbl_hook: при выключенном CBL бит подтянут к 1 (MAME
* kbd_fe_r: data |= 0xE0) и проверка ложно срабатывала бы на
* каждом кадре. CBL-путь: полный сейв → насос → restore →
* EI/RETI, к DSS НЕ чейнится (личное прерывание);
* - кадровое: полный сейв → user-handler → restore → чейн.
*
* CBL-фильтр по биту 7 порта 0xFE УБРАН: при выключенном CBL бит
* подтянут к 1 (MAME kbd_fe_r: data |= 0xE0), и каждое кадровое
* прерывание ложно уходило бы в чейн — user-handler не вызывался бы
* никогда. Вернуть вместе с поддержкой CBL в Phase 2.
* Чейн — ВСЕГДА jp на обработчик DSS 0x0038 (DSS работает в IM 1;
* jp с interrupted-PC на стеке имитирует RST 38) — он делает свой
* save/restore и завершает EI/RETI: клавиатурный FIFO, SYSTIME и
* курсор мыши живут.
*
* Операнд финального jp (_irq_dss_target) — SMC, патчится из
* irq_install; модуль в _CODE (RAM на Sprinter, W2 в tiny/big).
* _irq_table_ref; модуль в _CODE (RAM на Sprinter, W2 в tiny/big).
*/
#include "_irq.h"
@@ -28,6 +30,45 @@ void _irq_tramp(void) __naked
rrca ; бит 0 клавиатурный байт принят?
jr C, _irq_chain
;; --- CBL? --- старший байт хука ненулевой у любого
;; W2-адреса для проверки «хук есть» хватает одного байта.
ld a, (__irq_cbl_hook + 1)
or a, a
jr Z, _irq_frame
in a, (#0xFE)
rlca ; бит 7 запрос блока CBL?
jr NC, _irq_frame
;; --- CBL: сейв, насос, restore, EI/RETI (без DSS) ---
push bc
push de
push hl
.db 0x08 ; ex af,af (теневой AF)
push af
exx
push bc
push de
push hl
push ix
push iy
ld hl, (__irq_cbl_hook)
call _irq_call_hl
pop iy
pop ix
pop hl
pop de
pop bc
exx
pop af
.db 0x08 ; ex af,af (теневой AF)
pop hl
pop de
pop bc
pop af
ei
reti
_irq_frame:
;; --- кадровое прерывание ---
push bc
push de
@@ -67,7 +108,7 @@ void _irq_tramp(void) __naked
;; Всегда к DSS (IM 1, обработчик 0x0038) EI/RETI за ним.
.db 0xC3 ; jp nn
__irq_dss_target::
.dw 0x0038 ; SMC: подтверждается в irq_install
.dw 0x0038 ; SMC: подтверждается в _irq_table_ref
_irq_call_hl:
jp (hl)