/* * cbl_shutdown — ЖЁСТКО погасить звук перед возвратом в шелл. * * ЧЕМ ОТЛИЧАЕТСЯ ОТ cbl_close(). Тот закрывает СЕССИЮ: снимает насос и * пересчитывает порт по текущим держателям. А держатель обычно есть — * gfx_wait_vsync() занимает bit7 навсегда ради бита луча (см. _cbl_port.c), * — и порт остаётся включённым в "немом" режиме. Для работающей программы * это правильно, но при выходе означает, что железо продолжает крутить свои * 256 слотов уже под шеллом: тихо ровно до первой чужой записи в порт * данных, а дальше эта запись зацикливается навсегда. * * ПОЧЕМУ НЕЛЬЗЯ ПОЛОЖИТЬСЯ НА atexit. Цепочку atexit разматывает только * exit() (libc/sys/exit.c), а crt0 при ВОЗВРАТЕ ИЗ main уходит прямо в * ESTEX EXIT (runtime/crt0_banked.s) — ни один хендлер не выполняется. * Значит и снятие vsync-ссылки, и cbl_close из atexit при обычном * `return 0;` из main не происходят. Поэтому выключение должно быть * ЯВНЫМ действием программы, а не следствием чужой уборки. * * Что делает: закрывает сессию (если была), обнуляет счётчик держателей — * программа уходит, держать порт больше некому, — гасит bit7 и центрует * ЦАП обычного COVOX (при выключенном CBL записи в порт данных идут прямо * в него, см. _cbl_prime.c), чтобы вместо щелчка осталась ровная тишина. * * Идемпотентно; звать последним действием завершения. */ #include "_cbl.h" #include "../irq/_irq.h" /* IRQ_DISABLE/IRQ_ENABLE */ void cbl_shutdown(void) { cbl_close(); /* снять насос и отпустить IM2-таблицу */ IRQ_DISABLE(); _cbl_port_refs = 0; /* держателей больше нет: программа уходит */ _cbl_ctrl(0); /* bit7 = 0 — CBL выключен целиком */ _cbl_prime(0x80); /* ...и ЦАП COVOX в центре, без щелчка */ IRQ_ENABLE(); }