gfx/time: vsync через polling бита кадра, sleep/delayms без halt-подсчёта

gfx_wait_vsync() и sleep() раньше предполагали, что КАЖДОЕ прерывание
на векторе 0xFF — кадровый тик; с CBL/клавиатурой на том же векторе
это уже не так.

gfx_wait_vsync(): вместо halt — polling бита 5 порта 0xFE (реальная
позиция луча, см. MAME kbd_fe_r), доступного пока включён CBL bit7
порта 0x004E. Разделяемое владение портом с CBL через
_cbl_port_ref/unref (тот же ref-counting паттерн, что у IM2-таблицы) —
cbl_close() возвращает "немой" режим вместо полного выключения, если
gfx ещё держит ссылку. Fallback на halt при таймауте.

sleep()/delayms(): калиброванный busy-wait по духу delayms.asm вместо
подсчёта halt-пробуждений. Калибровка одна на кадр (не на секунду —
не переполняет uint16_t и не требует умножения/32-бит арифметики),
общий движок libc/time/_sleep_calib.c для обеих функций. Fallback на
старое поведение при EBUSY (фрейм-хук занят другим irq_install()).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-08 10:47:57 +03:00
parent 5086c47f0f
commit 46bd9ad0f1
13 changed files with 421 additions and 36 deletions
+19
View File
@@ -23,6 +23,15 @@
* был бы избыточен. _cbl_fill — callback приложения; _cbl_silence —
* буфер тишины, malloc'ится в cbl_open() ТОЛЬКО при
* CBL_UNDERRUN_SILENCE (иначе NULL).
*
* _cbl_port_ref/_cbl_port_unref (_cbl_port.c) — общий счётчик желающих
* держать bit7 порта 0x004E включённым НЕЗАВИСИМО от полноценной CBL-
* сессии. Нужно gfx_wait_vsync() (см. libc/gfx/gfx_wait_vsync.c): бит
* 5 порта 0xFE отражает позицию луча (см. MAME sprinter.cpp kbd_fe_r),
* но доступен ТОЛЬКО пока включён cbl_mode(). Если реальный CBL уже
* открыт — портом владеет он, ref/unref его не трогают; иначе включают
* bit7 "немым" кодом частоты _CBL_VSYNC_FREQ (таймер CBL не заводится
* — divs[2]=0 в MAME — ни звука, ни прерываний, только синхро-бит).
*/
#ifndef _CBL_INTERNAL_H
#define _CBL_INTERNAL_H
@@ -33,6 +42,7 @@
#define _CBL_CTRL_PORT 0x004E
#define _CBL_DATA_PORT 0x4F
#define _CBL_ACCEL_PAGE 0xFD /* спец-страница EMM: запись на W3@0xC000 идёт в CBL */
#define _CBL_VSYNC_FREQ 0x02 /* "немой" код частоты — divs[2]=0 в MAME, таймер не идёт */
extern uint16_t _cbl_block; /* размер блока: 128 или 256 (по формату) */
extern uint16_t _cbl_reqs; /* блоков-запросов обслужено */
@@ -40,6 +50,7 @@ extern uint16_t _cbl_undr; /* из них недолив (fill в
extern cbl_fill_fn _cbl_fill; /* callback приложения, может быть NULL */
extern uint8_t _cbl_underrun_mode; /* CBL_UNDERRUN_* */
extern uint8_t *_cbl_silence; /* буфер тишины или NULL */
extern uint8_t _cbl_port_refs; /* держатели bit7 помимо реальной CBL-сессии */
/* Запись в 16-битный порт управления 0x004E. */
void _cbl_ctrl(uint8_t v);
@@ -49,4 +60,12 @@ void _cbl_ctrl(uint8_t v);
void _cbl_pump_otir(void);
void _cbl_pump_accel(void);
/* Разделяемое владение портом 0x004E (см. описание выше).
* _cbl_port_sync пересчитывает нужное значение порта из текущего
* состояния (реальная сессия? держатели?) и пишет его — вызывается
* из ref/unref, а также из cbl_close() после снятия _irq_cbl_hook. */
void _cbl_port_ref(void);
void _cbl_port_unref(void);
void _cbl_port_sync(void);
#endif
+34
View File
@@ -0,0 +1,34 @@
/*
* _cbl_port — разделяемое владение bit7 порта управления 0x004E между
* реальной CBL-сессией (cbl_open/cbl_close) и другими держателями
* (сейчас — gfx_wait_vsync(), которому нужен только бит 5 порта 0xFE,
* доступный лишь пока включён cbl_mode(); см. libc/gfx/gfx_wait_vsync.c
* и MAME sprinter.cpp kbd_fe_r).
*
* Если реальный CBL уже открыт (_irq_cbl_hook != 0) — портом владеет
* он, ref/unref его не трогают вообще (это уже нужное состояние).
* Иначе включаем/выключаем bit7 "немым" кодом частоты _CBL_VSYNC_FREQ
* — таймер CBL при этом коде не заводится (divs[2]=0 в MAME), значит
* ни звука, ни прерываний, только доступ к синхро-биту.
*/
#include "_cbl.h"
#include "../irq/_irq.h"
void _cbl_port_sync(void)
{
if (_irq_cbl_hook) return; /* реальный CBL сам владеет портом */
_cbl_ctrl(_cbl_port_refs ? (uint8_t)(0x80 | _CBL_VSYNC_FREQ) : 0);
}
void _cbl_port_ref(void)
{
_cbl_port_refs++;
_cbl_port_sync();
}
void _cbl_port_unref(void)
{
if (_cbl_port_refs) _cbl_port_refs--;
_cbl_port_sync();
}
+3 -1
View File
@@ -1,6 +1,7 @@
/*
* _cbl_state — данные CBL-модуля: размер блока, счётчики, callback
* приложения, режим недолива, буфер тишины (если аллоцирован).
* приложения, режим недолива, буфер тишины (если аллоцирован),
* счётчик разделяемых держателей порта (см. _cbl_port.c).
* Только данные.
*/
@@ -12,3 +13,4 @@ uint16_t _cbl_undr;
cbl_fill_fn _cbl_fill;
uint8_t _cbl_underrun_mode;
uint8_t *_cbl_silence;
uint8_t _cbl_port_refs;
+9 -5
View File
@@ -1,8 +1,12 @@
/*
* cbl_close — выключить CBL (запись 0 в порт управления, как в
* сэмпле), снять хук насоса, освободить буфер тишины (если был
* аллоцирован под CBL_UNDERRUN_SILENCE) и отпустить IM2-таблицу.
* Идемпотентно; вызывается и из atexit.
* cbl_close — выключить CBL, снять хук насоса, освободить буфер
* тишины (если был аллоцирован под CBL_UNDERRUN_SILENCE) и отпустить
* IM2-таблицу. Идемпотентно; вызывается и из atexit.
*
* Порт 0x004E не гасится напрямую — вместо этого _cbl_port_sync()
* пересчитывает нужное значение: если gfx_wait_vsync() ещё держит
* ссылку (_cbl_port_refs > 0), останется "немой" режим ради бита
* синхронизации; иначе порт гасится полностью.
*/
#include <stdlib.h>
@@ -13,9 +17,9 @@ void cbl_close(void)
{
if (!_irq_cbl_hook) return;
IRQ_DISABLE();
_cbl_ctrl(0);
_irq_cbl_hook = 0;
IRQ_ENABLE();
_cbl_port_sync();
free(_cbl_silence);
_cbl_silence = 0;
_irq_table_unref();