Files
Sprinter-SDCC/libc/cbl/_cbl_open_raw.c
T
snark13 8490288d79 libc/crt0: возврат из main завершает программу по-настоящему
Два бага одного пути завершения, оба видны только на железе.

1. ЗВУК ПРОДОЛЖАЛСЯ ПОСЛЕ ВЫХОДА.  cbl_close() закрывает СЕССИЮ, но не
гасит железо: bit7 порта 0x004E держит gfx_wait_vsync ради бита луча,
поэтому порт оставался включённым ("немой" режим), и CBL крутил свои 256
слотов уже под шеллом — тихо ровно до первой чужой записи в порт данных,
а дальше она зацикливалась.  Новый cbl_shutdown() гасит bit7 независимо
от держателей и центрует ЦАП обычного COVOX; pop_shutdown зовёт его
последним действием, а _cbl_open_raw регистрирует в atexit его, а не
cbl_close.

2. ЦЕПОЧКА atexit НЕ ВЫПОЛНЯЛАСЬ ПРИ ВОЗВРАТЕ ИЗ main.  crt0 уходил прямо
в ESTEX EXIT, то есть нарушал контракт C (возврат из main = exit(status)).
Молча терялись не только гашение звука и снятие vsync-ссылки, но и
_fclosall: буферизованная запись в файлы пропадала, если программа не
звала exit() явно.  Теперь crt0 после main дёргает _atexit_hook.

Косвенность обязательна: прямая ссылка crt0 на разматыватель притащила бы
его и стек хендлеров в КАЖДУЮ программу.  Указатель живёт в отдельном
data-модуле (два байта _DATA, ни байта кода), ставит его сам atexit() при
первой регистрации — нет регистраций, нет и кода.  Тот же приём, что у
_irq_cbl_hook.

Цена замерена: +14 Б всем программам (блок в crt0) и +64 Б тем
одиннадцати, что реально регистрируют хендлеры (CBL, файловые через
_fclosall, irqtest, gfx_dbuf, solidt) — у них раньше эти хендлеры были
мёртвым кодом.  Эталоны обновлены (кроме atlas: его +434 Б не отсюда,
замерен тот же и без этих правок).

Проверено в MAME: старт и звук как были, выход по F10 возвращает в шелл
чисто (текстовый режим восстановлен, зависания нет), повторный запуск
работает.  Пункт 1 проверяется только на железе.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DSYUpuaQpKr48kBav2iiV4
2026-09-02 14:15:48 +03:00

84 lines
4.8 KiB
C
Raw 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_open_raw — общее тело обеих точек входа CBL (cbl_open и
* cbl_open_silence). Проверяет параметры, вешает насос и включает железо.
*
* ПОЧЕМУ ТОЧЕК ВХОДА ДВЕ, а не один параметр underrun_mode. Линкер тянет
* .rel ЦЕЛИКОМ, поэтому malloc/free, стоящие в ветке CBL_UNDERRUN_SILENCE,
* приезжали в резидент КАЖДОМУ приложению — даже тому, которое льёт тишину
* само и кучей не пользуется вовсе. Это 275 Б кода плюс вся область кучи;
* в huge-режиме, где под резидент отведено 0x4100..0xBB00, такая мелочь
* заметна (у PoP оставалось 129 Б свободных). Разведя аллокацию в отдельный
* публичный модуль, мы отдаём выбор линкеру: зовёшь cbl_open — malloc не
* приезжает.
*
* Указатель на буфер тишины (_cbl_silence) здесь НЕ ТРОГАЕТСЯ: им владеет
* cbl_open_silence.c, который его и аллоцирует. В режиме APP насос на него
* не смотрит вовсе (условие в _cbl_pump_*), а обнуляй мы его тут — cbl_open
* после cbl_open_silence терял бы уже выделенный буфер, и следующая
* silence-сессия аллоцировала бы второй.
*/
#include <errno.h>
#include <sprinter_exit.h>
#include "_cbl.h"
#include "../irq/_irq.h"
int _cbl_open_raw(uint8_t freq_code, uint8_t fmt, uint8_t pump_mode,
uint8_t underrun_mode, cbl_fill_fn fill)
{
if (_irq_cbl_hook) { errno = EBUSY; return -1; }
if (freq_code < CBL_FREQ_7K8 || freq_code > CBL_FREQ_109K) {
errno = EINVAL;
return -1;
}
if (fmt & (uint8_t)~(CBL_FMT_MONO16 | CBL_FMT_STEREO8)) {
errno = EINVAL; /* биты вне 5/6 — неизвестный формат */
return -1;
}
if (pump_mode != CBL_PUMP_OTIR && pump_mode != CBL_PUMP_ACCEL) {
errno = EINVAL;
return -1;
}
/* ЗАПРЕТ CBL_PUMP_OTIR + 16-бит (подтверждено чтением исходника MAME
* sprinter.cpp): порт данных 0x4F ВСЕГДА кладёт байт как есть в один
* слот, не собирая пару байт в 16-бит значение и не сверяясь с
* cbl_mode16() вообще — это умеет только акселераторный путь (спец-
* страница 0xFD, do_accel_block). Комбинация принципиально не может
* звучать правильно, поэтому запрещена. */
if (pump_mode == CBL_PUMP_OTIR && (fmt & CBL_FMT_MONO16)) {
errno = EINVAL;
return -1;
}
if (_irq_table_ref() != 0) return -1;
_cbl_block = (fmt & CBL_FMT_MONO16) ? 256u : 128u;
_cbl_reqs = 0;
_cbl_undr = 0;
_cbl_fill = fill;
_cbl_underrun_mode = underrun_mode;
static uint8_t atexit_armed;
if (!atexit_armed) {
atexit_armed = 1;
/* ИМЕННО shutdown, а не close: close оставляет bit7 включённым,
* пока его держит gfx_wait_vsync, и железо крутит буфер уже под
* шеллом. Подстраховка неполная — цепочку atexit разматывает
* только exit(), а crt0 при возврате из main уходит прямо в ESTEX
* EXIT, — поэтому программа обязана звать cbl_shutdown() сама. */
atexit(cbl_shutdown); /* CBL обязан умолкнуть до шелла */
}
IRQ_DISABLE();
_irq_cbl_hook = (pump_mode == CBL_PUMP_ACCEL) ? _cbl_pump_accel : _cbl_pump_otir;
_cbl_ctrl((uint8_t)(0x90 | fmt | freq_code)); /* on + int + формат + частота */
/* ...и НЕМЕДЛЕННО забить буфер тишиной: железо пускает воспроизведение
* с нулевого слота той же записью в порт управления, а буфер хранит
* старое содержимое — без этого первые 23 мс уходит мусор. Заливка
* обязана быть внутри той же DI-скобки и сразу за включением (разбор —
* _cbl_prime.c). */
_cbl_prime((uint8_t)((fmt & CBL_FMT_MONO16) ? 0x00 : 0x80));
IRQ_ENABLE();
return 0;
}