Files
Sprinter-SDCC/libc/include/cbl.h
T
snark13 8458ec65f4 libc/cbl: две точки входа вместо underrun_mode — malloc больше не в резиденте
Линкер тянет .rel целиком, поэтому malloc/free, стоявшие в мёртвой ветке
CBL_UNDERRUN_SILENCE внутри cbl_open, приезжали в резидент КАЖДОМУ
приложению — включая те, что льют тишину сами и кучей не пользуются.

Разведено:
  cbl_open(freq, fmt, pump, fill)          — ничего не аллоцирует;
  cbl_open_silence(freq, fmt, pump, fill)  — аллоцирует буфер тишины;
  _cbl_open_raw(...)                       — общее тело.
Параметр underrun_mode из публичного API убран: режим задаёт выбор функции.

cbl_close больше не зовёт free — иначе malloc возвращался бы тем же путём.
Буфер тишины живёт до выхода из программы и переиспользуется; его размер
запоминается, иначе открытие 16-бит после 8-бит писало бы memset'ом мимо
выделенного куска.  _cbl_open_raw указатель на буфер не трогает вовсе —
иначе cbl_open после cbl_open_silence терял бы уже выделенную память.

В дереве режим SILENCE не использовал никто: все три вызова (cblwav,
cblstream, PoP) передавали CBL_UNDERRUN_APP.

Итог для PoP: _CODE 24329 -> 23716 (−613 Б), свободно в резиденте 129 ->
747 Б.  make size-check чистый (67 программ), звук в MAME проверен —
насос отработал 696 запросов, pop_snd_ok/want = 1/1.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-20 21:53:08 +03:00

140 lines
9.4 KiB
C
Raw Permalink 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.
/*
* cbl.h — COVOX-Blaster: потоковый вывод звука через прерывания.
*
* CBL — COVOX с буферным ОЗУ (256 Б, аппаратно разбито на ДВЕ половины
* по 128 Б — двойная буферизация целиком на стороне железа, см.
* официальную документацию "5.3 COVOX-Blaster"): бит 7 порта 0xFE
* отражает, какая половина СЕЙЧАС играет; когда она флипает, вектор
* 0xFF поднимает прерывание — пора долить НЕАКТИВНУЮ половину.
*
* Библиотека НЕ держит собственного кольца — оно было бы лишним
* вторым буфером поверх аппаратного. Вместо этого при cbl_open()
* регистрируется callback fill(): ISR вызывает его напрямую, а
* callback САМ пропихивает n байт из данных приложения (откуда бы они
* ни жили — статический массив, банковая EMM-страница, файл) прямо в
* CBL через cbl_push_otir()/cbl_push_accel() — без промежуточной
* копии в libc.
*
* fill(n) ОБЯЗАН быть быстрым (то же ограничение, что у irq_install()
* — никаких ESTEX/BIOS/gfx-вызовов, только память и один push) и
* вернуть ненулевое значение, если реально пропихнул n байт; 0 — если
* данных сейчас нет (недолив/underrun).
*
* Формат данных (bits 6/5 порта 0x4E; см. Forum.txt "Звук через
* COVOX-Blaster"):
* 8-бит: unsigned, центр 0x80 (моно — один байт/сэмпл;
* стерео — чередование L/R байт)
* 16-бит: signed, центр 0x0000, little-endian (моно — int16/сэмпл;
* стерео — чередование L/R int16)
* Блок запроса — 128 байт для 8-бит, 256 байт для 16-бит (не зависит
* от моно/стерео — это ПОЛНЫЙ байтовый блок, раскладка каналов внутри
* него на размер не влияет); n, приходящий в fill(), равен этому блоку.
*
* Способ выдачи блока — CBL_PUMP_* (см. cbl_open):
* OTIR — cbl_push_otir(), блочный вывод в порт 0x4F; НЕ умеет
* 16-бит (см. ниже) — cbl_open(..., 16-бит формат,
* CBL_PUMP_OTIR, ...) вернёт EINVAL.
* ACCEL — cbl_push_accel(), запись в спец-страницу EMM 0xFD
* (маппится в W3 на 0xC000) через акселератор (см.
* docs/converted/accel_r.txt); единственный способ получить
* настоящий 16-бит сэмпл.
*
* OTIR + 16-бит запрещён не просто "хуже", а физически не работает:
* по исходнику эмулятора (MAME sprinter.cpp) порт данных 0x4F ВСЕГДА
* кладёт байт как есть в один слот, не собирая пару байт в 16-бит
* значение — это умеет только акселераторный путь (страница 0xFD).
*
* Поведение при недоливе задаётся ВЫБОРОМ ТОЧКИ ВХОДА (cbl_open /
* cbl_open_silence), а не параметром:
* CBL_UNDERRUN_APP (по умолчанию, 0) — недолив не наша забота;
* в CBL доигрывает то, что уже лежит в его буфере (никакого
* буфера тишины не аллоцируется).
* CBL_UNDERRUN_SILENCE (1) — насос сам пропихивает тишину при
* недоливе; буфер тишины (128/256 Б по формату) аллоцируется
* malloc'ом внутри cbl_open_silence(), только в этом режиме.
* cbl_underruns() считает недоливы в ОБОИХ режимах — это диагностика,
* поведение не меняет.
*
* cbl_close() обязателен (atexit подстрахует) — иначе CBL продолжит
* прерывать шелл после выхода; заодно освобождает буфер тишины, если
* он был аллоцирован.
*
* Требования как у <irq.h>: код/данные в W2 (tiny/big).
*/
#ifndef CBL_H
#define CBL_H
#include <stdint.h>
/* Коды частоты дискретизации (bits 3..0 порта 0x4E; из форума
* Sprinter Team; коды 0/1 — legacy, не использовать). */
#define CBL_FREQ_7K8 0x8 /* 7.8125 кГц */
#define CBL_FREQ_10K9 0x9 /* 10.9375 кГц */
#define CBL_FREQ_15K6 0xA /* 15.625 кГц */
#define CBL_FREQ_21K9 0xB /* 21.875 кГц */
#define CBL_FREQ_31K3 0xC /* 31.25 кГц */
#define CBL_FREQ_43K8 0xD /* 43.75 кГц */
#define CBL_FREQ_54K7 0xE /* 54.6875 кГц */
#define CBL_FREQ_109K 0xF /* 109.375 кГц */
/* Формат: биты 5(16-бит)/6(stereo) порта 0x4E — значения готовы для
* прямого OR с freq_code внутри cbl_open(). */
#define CBL_FMT_MONO8 0x00 /* 8-бит, моно — блок 128 Б */
#define CBL_FMT_MONO16 0x20 /* 16-бит, моно — блок 256 Б */
#define CBL_FMT_STEREO8 0x40 /* 8-бит, стерео — блок 128 Б */
#define CBL_FMT_STEREO16 0x60 /* 16-бит, стерео — блок 256 Б */
/* Способ выдачи блока. */
#define CBL_PUMP_OTIR 0 /* cbl_push_otir() — базовый, не умеет 16-бит */
#define CBL_PUMP_ACCEL 1 /* cbl_push_accel() — единственный путь для 16-бит */
/* Поведение при недоливе (fill() вернул 0 или не задан). */
/* Внутренние коды режима недолива: приложение их не передаёт (режим задаёт
* выбор cbl_open / cbl_open_silence), но насос по ним и различает поведение. */
#define CBL_UNDERRUN_APP 0 /* не наша забота, буфер тишины не аллоцируется */
#define CBL_UNDERRUN_SILENCE 1 /* насос сам шлёт тишину из буфера libc */
/* fill(n): пропихнуть n байт (через cbl_push_otir/accel) прямо из
* данных приложения. Вызывается ИЗ ISR — обязан быть быстрым (без
* ESTEX/BIOS/gfx/malloc). Вернуть ненулевое при успехе, 0 — нет
* данных сейчас (недолив). */
typedef int (*cbl_fill_fn)(uint16_t n);
/* Включить CBL с частотой CBL_FREQ_*, форматом CBL_FMT_* и способом выдачи
* CBL_PUMP_*; fill — callback, зовущийся из ISR за новым блоком (может быть
* NULL, тогда недолив — на каждом запросе). Недолив НЕ наша забота
* (CBL_UNDERRUN_APP): железо доиграет по кругу неактивную половину, и если
* это слышно — приложение обязано отдавать тишину само.
*
* 0 или -1 + errno (EBUSY — уже открыт; EINVAL — плохой код частоты, формат,
* pump, или не-W2 режим).
*
* ТОЧКИ ВХОДА ДВЕ, и это не косметика: cbl_open() не аллоцирует ничего и
* потому НЕ ТЯНЕТ malloc в резидент. За тишиной от libc — cbl_open_silence()
* ниже, и malloc приезжает только тому, кто её позвал. */
int cbl_open(uint8_t freq_code, uint8_t fmt, uint8_t pump_mode,
cbl_fill_fn fill);
/* То же, но при недоливе тишину шлёт САМА libc (CBL_UNDERRUN_SILENCE):
* буфер под неё аллоцируется здесь. Добавляет ENOMEM к кодам ошибок.
* Буфер живёт до конца программы и переиспользуется при повторном открытии
* — cbl_close() его не освобождает намеренно (иначе free вернул бы malloc
* в резидент всем подряд). */
int cbl_open_silence(uint8_t freq_code, uint8_t fmt, uint8_t pump_mode,
cbl_fill_fn fill);
/* Выключить CBL и отпустить IM2-таблицу. Идемпотентно. */
void cbl_close(void);
/* Пропихнуть n байт из src прямо в CBL (звать ИЗ fill()). */
void cbl_push_otir (const void *src, uint16_t n);
void cbl_push_accel(const void *src, uint16_t n);
/* Счётчики диагностики: блоки-запросы и недоливы (fill() вернул 0
* или не задан). */
uint16_t cbl_requests(void);
uint16_t cbl_underruns(void);
#endif