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
+45 -15
View File
@@ -1,30 +1,60 @@
/*
* sleep — заблокироваться на N секунд по кадровому прерыванию 50 Гц.
* sleep — заблокироваться на N секунд калиброванным busy-wait'ом (см.
* _sleep_calib.c — общий движок с delayms()).
*
* ISR Sprinter срабатывает 50 раз в секунду. `halt` паркует CPU до
* следующего IRQ, так что 50 halt'ов = ~1 секунда стенного времени.
* Тот же трюк, что у solid-c в IO.ASM:285.
* Старая версия (50 halt = 1 c) предполагала, что КАЖДОЕ прерывание —
* кадровый тик 50 Гц; с CBL/клавиатурой на векторе 0xFF это неверно
* (CBL может прерывать в разы/десятки раз чаще кадра — sleep() вернулся
* бы намного раньше срока).
*
* Замечание: требует включённых прерываний (по умолчанию включены —
* ESTEX настраивает IM 1 с кадровым ISR до запуска нашей программы).
* sleep(seconds) — вложенный цикл: внешний по секундам, внутренний
* ровно 50 раз калиброванный _sleep_spin() — та же структура, что и в
* старой версии (внешний секунды, внутренний 50×halt), только вместо
* halt — калиброванный спин. Umножения/32-бит арифметики нет.
*
* Fallback: если фрейм-хук уже занят другим irq_install()-клиентом
* (EBUSY) — калибровку сделать некорректно, откатываемся к старому
* "50 halt ~ 1 c" поведению для этого конкретного вызова.
*/
#include <unistd.h>
#include "_sleep_calib.h"
void sleep(unsigned int seconds) __naked
/* Старое поведение — фолбэк, если calibrate() не может безопасно
* получить кадровый хук. */
static void _sleep_legacy(unsigned int seconds) __naked
{
(void)seconds;
__asm
inter:
;; HL = seconds на входе (единственный int-аргумент SDCC).
_slg_outer:
ld a, h
or a, l
ret Z ; sleep(0) вернуться сразу
ld b, #50 ; 50 halt-ов на секунду (50 Гц)
inner:
or l
ret Z
ld b, #50 ; 50 halt-ов ~ 1 c (при ЕДИНСТВЕННОМ
; источнике прерываний на векторе 0xFF)
_slg_inner:
halt
djnz inner
djnz _slg_inner
dec hl
jr inter
jr _slg_outer
__endasm;
}
void sleep(unsigned int seconds)
{
if (seconds == 0) return;
if (!_sleep_units_per_frame) _sleep_calibrate();
if (!_sleep_units_per_frame) {
_sleep_legacy(seconds);
return;
}
while (seconds--) {
uint8_t frame = 50; /* 50 кадров ~ 1 с при 50 Гц */
while (frame--) {
_sleep_spin(_sleep_units_per_frame);
}
}
}