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>
This commit is contained in:
2026-08-20 21:53:08 +03:00
parent 63e426cc0d
commit 8458ec65f4
10 changed files with 172 additions and 106 deletions
+29 -13
View File
@@ -44,13 +44,14 @@
* кладёт байт как есть в один слот, не собирая пару байт в 16-бит
* значение — это умеет только акселераторный путь (страница 0xFD).
*
* Поведение при недоливе — CBL_UNDERRUN_* (4-й параметр cbl_open):
* Поведение при недоливе задаётся ВЫБОРОМ ТОЧКИ ВХОДА (cbl_open /
* cbl_open_silence), а не параметром:
* CBL_UNDERRUN_APP (по умолчанию, 0) — недолив не наша забота;
* в CBL доигрывает то, что уже лежит в его буфере (никакого
* буфера тишины не аллоцируется).
* CBL_UNDERRUN_SILENCE (1) — насос сам пропихивает тишину при
* недоливе; буфер тишины (128/256 Б по формату) аллоцируется
* malloc'ом ВНУТРИ cbl_open(), только в этом режиме.
* malloc'ом внутри cbl_open_silence(), только в этом режиме.
* cbl_underruns() считает недоливы в ОБОИХ режимах — это диагностика,
* поведение не меняет.
*
@@ -89,8 +90,10 @@
#define CBL_PUMP_ACCEL 1 /* cbl_push_accel() — единственный путь для 16-бит */
/* Поведение при недоливе (fill() вернул 0 или не задан). */
#define CBL_UNDERRUN_APP 0 /* по умолчанию: не наша забота, буфер тишины не аллоцируется */
#define CBL_UNDERRUN_SILENCE 1 /* насос сам шлёт тишину; буфер аллоцируется в cbl_open() */
/* Внутренние коды режима недолива: приложение их не передаёт (режим задаёт
* выбор cbl_open / cbl_open_silence), но насос по ним и различает поведение. */
#define CBL_UNDERRUN_APP 0 /* не наша забота, буфер тишины не аллоцируется */
#define CBL_UNDERRUN_SILENCE 1 /* насос сам шлёт тишину из буфера libc */
/* fill(n): пропихнуть n байт (через cbl_push_otir/accel) прямо из
* данных приложения. Вызывается ИЗ ISR — обязан быть быстрым (без
@@ -98,17 +101,30 @@
* данных сейчас (недолив). */
typedef int (*cbl_fill_fn)(uint16_t n);
/* Включить CBL с частотой CBL_FREQ_*, форматом CBL_FMT_*, способом
* выдачи CBL_PUMP_* и поведением при недоливе CBL_UNDERRUN_*; fill —
* callback, зовущийся из ISR за новым блоком (может быть NULL, тогда
* недолив — на каждом запросе). 0 или -1 + errno (EBUSY — уже
* открыт; EINVAL — плохой код частоты/формат/pump/underrun_mode, или
* не-W2 режим; ENOMEM — не хватило памяти под буфер тишины). */
/* Включить 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,
uint8_t underrun_mode, cbl_fill_fn fill);
cbl_fill_fn fill);
/* Выключить CBL, отпустить IM2-таблицу, освободить буфер тишины (если
* был). Идемпотентно. */
/* То же, но при недоливе тишину шлёт САМА 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()). */