libc+libbgi: raw-клавиатура (held-state) + column-major блит спрайтов
libc/kbd: kbd_raw_open/close/down — сырой PS/2-канал клавиатуры с held-state (битовая карта _kbdraw_down[512], EXT-клавиши +256). Пока raw открыт, кадровый IRQ-трамплин перехватывает байт SIO у DSS и декодирует make/break (0xF0/0xE0-префиксы) сам. FIFO вычерпывается В ЦИКЛЕ (приёмный буфер SIO 3 байта; пачка break-кодов при одновременном отпускании иначе теряется → залипание клавиши). Буфер W2-трамплина поднят 224→288 Б под выросший обработчик. libc/conio: kbd_mod_state() — live-состояние модификаторов (ESTEX CTRLKEY $33h), Shift/Ctrl/Alt/Lock прямо сейчас, KBD_MOD_* маска. libbgi: gfx_blit_cols(x,y,img,flip) + _bgi_blit_cols_raw — блит column-major спрайта вертикальным accel-проходом, бесплатный горизонтальный флип (sstride<0), клип по экрану. Для персонажей. libbgi/atlas_load: восстанавливать W3 ДО записи a->count (atlas_t в --bank памяти резолвится через W3; count оставался мусором). tests/kbdraw — тест raw-клавиатуры; size-baseline +kbdraw. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* kbd_mod_state — обёртка ESTEX CTRLKEY ($33h): состояние
|
||||
* модификаторов клавиатуры НА ДАННЫЙ МОМЕНТ (не из буфера — драйвер
|
||||
* берёт данные прямо из последнего скана, см. ProgrammerManual.txt
|
||||
* "CTRLKEY"), поэтому в отличие от kbhit/getch/getkey держится ровно
|
||||
* столько, сколько клавиша реально зажата.
|
||||
*
|
||||
* ABI: C=0x33 на входе — функция; на выходе C=mode, B=shift (тот же
|
||||
* регистр C переиспользуется под результат, как и во всех ESTEX-
|
||||
* функциях клавиатуры). Расшифровка битов — KBD_MOD_* в <conio.h>.
|
||||
*/
|
||||
|
||||
#include "_conio.h"
|
||||
|
||||
uint16_t kbd_mod_state(void) __naked
|
||||
{
|
||||
__asm
|
||||
push ix
|
||||
ld c, #0x33 ; ESTEX CTRLKEY
|
||||
rst #0x10
|
||||
pop ix
|
||||
ld d, c ; D = mode (старший байт результата)
|
||||
ld e, b ; E = shift (младший байт результата)
|
||||
ret ; __sdcccall(1): DE = uint16_t
|
||||
__endasm;
|
||||
}
|
||||
@@ -75,6 +75,29 @@ uint16_t getkey(void);
|
||||
#define KEY_INS 0x50 /* numpad 0; not verified */
|
||||
#define KEY_DEL 0x55 /* numpad 5/.; not verified */
|
||||
|
||||
/* kbd_mod_state — live-состояние модификаторов ПРЯМО СЕЙЧАС (ESTEX
|
||||
* CTRLKEY $33h), а не событие из буфера: держится столько, сколько
|
||||
* клавиша реально зажата (в отличие от kbhit/getch/getkey). Покрывает
|
||||
* ТОЛЬКО Shift/Ctrl/Alt/Rus-Lat/Lock-клавиши — обычные клавиши
|
||||
* (стрелки и т.п.) у ESTEX live-state не имеют, см. <kbd_raw.h>.
|
||||
*
|
||||
* Возврат: (mode << 8) | shift — расшифровка через KBD_MOD_* ниже. */
|
||||
uint16_t kbd_mod_state(void);
|
||||
|
||||
#define KBD_MOD_RUSLAT 0x8000
|
||||
#define KBD_MOD_NUMLOCK 0x0800
|
||||
#define KBD_MOD_SCRLOCK 0x0400
|
||||
#define KBD_MOD_INSERT 0x0200
|
||||
#define KBD_MOD_CAPSLOCK 0x0100
|
||||
#define KBD_MOD_LSHIFT 0x0080
|
||||
#define KBD_MOD_RSHIFT 0x0040
|
||||
#define KBD_MOD_CTRL 0x0020
|
||||
#define KBD_MOD_ALT 0x0010
|
||||
#define KBD_MOD_LCTRL 0x0008
|
||||
#define KBD_MOD_LALT 0x0004
|
||||
#define KBD_MOD_RCTRL 0x0002
|
||||
#define KBD_MOD_RALT 0x0001
|
||||
|
||||
typedef union {
|
||||
uint16_t value;
|
||||
struct {
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* kbd_raw.h — эксклюзивный raw-канал клавиатуры: держит битовую карту
|
||||
* «клавиша N зажата ПРЯМО СЕЙЧАС», обновляемую напрямую из
|
||||
* прерывания (декодирует PS/2 Scan Code Set 2 — make/break, тот же
|
||||
* протокол, что и в docs/samples/sprinterKeybLib.asm).
|
||||
*
|
||||
* Прецедент и мотивация — applications/PoP/docs/PORT_PLAN.md §2:
|
||||
* ESTEX kbhit/getch/getkey/CTRLKEY — событийные (нажатие, без
|
||||
* release), обычные клавиши (не модификаторы) вообще не имеют
|
||||
* live-state в ESTEX; трамплин кадрового прерывания
|
||||
* (libc/irq/_irq_tramp.c) в штатном режиме отдаёт клавиатурные байты
|
||||
* ПРЯМО в DSS, не читая порт данных — байт из аппаратного приёмного
|
||||
* регистра можно прочитать только ОДИН раз, поэтому «подсмотреть, не
|
||||
* мешая DSS» невозможно технически.
|
||||
*
|
||||
* ЖИЗНЕННЫЙ ЦИКЛ И ГЛАВНОЕ СЛЕДСТВИЕ: пока kbd_raw_open() активен, ВЕСЬ
|
||||
* поток клавиатурных байт достаётся ЭТОМУ модулю — DSS их не видит.
|
||||
* kbhit/getch/getkey/CTRLKEY (в т.ч. kbd_mod_state) НЕ получают новых
|
||||
* событий, пока raw-канал открыт. Приложение обязано проверять «выход»
|
||||
* (например ESC — KBD_ESC) через kbd_raw_down() само. Перед экранами,
|
||||
* которым нужен обычный ESTEX-ввод (диалоги/меню на консольном I/O),
|
||||
* закрыть канал kbd_raw_close(). Паттерн один в один как у
|
||||
* cbl_open()/cbl_close() (<cbl.h>) — тот же приватный IM2-хук, не
|
||||
* чейнящийся к DSS.
|
||||
*
|
||||
* КОДЫ КЛАВИШ: значения ниже — общеизвестный стандарт AT/PS-2 Scan
|
||||
* Code Set 2 (не специфика Sprinter). TODO до использования в PoC:
|
||||
* подтвердить в MAME, что реальный поток байт с SIO-A клавиатуры
|
||||
* Sprinter именно этот набор кодов (см. defer_unexplained_quirks).
|
||||
*/
|
||||
#ifndef KBD_RAW_H
|
||||
#define KBD_RAW_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
/* Включить raw-канал: 0 / -1+errno (EBUSY — уже открыт; прочие коды —
|
||||
* см. errno.h, как у irq_install/cbl_open — общий IM2-механизм). */
|
||||
int kbd_raw_open(void);
|
||||
|
||||
/* Выключить raw-канал, вернуть клавиатуру DSS. Идемпотентно; висит
|
||||
* на atexit (страховка, как у cbl_close). */
|
||||
void kbd_raw_close(void);
|
||||
|
||||
/* Зажата ли клавиша code ПРЯМО СЕЙЧАС (0/1). code вне 0..511 — 0
|
||||
* (защитно). Для расширенных клавиш (стрелки и т.п., префикс 0xE0
|
||||
* на проводе) прибавить KBD_EXT к базовому коду. */
|
||||
uint8_t kbd_raw_down(uint16_t code);
|
||||
|
||||
#define KBD_EXT 0x0100 /* база кода была расширенной (0xE0-префикс) */
|
||||
|
||||
/* Позиционные коды PS/2 Set 2 — то, что реально нужно платформеру.
|
||||
* TODO: подтвердить в MAME перед PoC (см. предупреждение выше). */
|
||||
#define KBD_UP (KBD_EXT | 0x75)
|
||||
#define KBD_DOWN (KBD_EXT | 0x72)
|
||||
#define KBD_LEFT (KBD_EXT | 0x6B)
|
||||
#define KBD_RIGHT (KBD_EXT | 0x74)
|
||||
#define KBD_SPACE 0x29
|
||||
#define KBD_ENTER 0x5A
|
||||
#define KBD_ESC 0x76
|
||||
#define KBD_LSHIFT 0x12
|
||||
#define KBD_RSHIFT 0x59
|
||||
|
||||
#endif
|
||||
+8
-2
@@ -30,8 +30,10 @@
|
||||
#define _IRQ_VEC_BUF_SIZE (256 + 257)
|
||||
|
||||
/* Буфер под W2-копию кадрового трамплина (small/huge — _irq_tramp в
|
||||
* W1). С запасом над реальным размером трамплина (~150 Б). */
|
||||
#define _IRQ_TRAMP_BUF_SIZE 224
|
||||
* W1). С запасом над реальным размером трамплина (225 Б после
|
||||
* FIFO-drain цикла в raw-клавиатурной ветке, 2026-07-16 — проверять
|
||||
* __irq_tramp_end при правках, запас держать ≥ 60 Б). */
|
||||
#define _IRQ_TRAMP_BUF_SIZE 288
|
||||
|
||||
extern uint8_t _irq_vec_buf[_IRQ_VEC_BUF_SIZE];
|
||||
|
||||
@@ -63,6 +65,10 @@ extern isr_t _irq_cbl_hook;
|
||||
#define _IRQ_CTC_CH2 0x12
|
||||
#define _IRQ_CTC_CH3 0x13
|
||||
#define _IRQ_SIO_A 0x19 /* SIO-A RR0: бит 0 = Rx available */
|
||||
#define _IRQ_SIO_DATA 0x18 /* SIO-A data — деструктивное чтение
|
||||
* (дренирует байт); читает либо DSS
|
||||
* (штатно), либо raw-канал клавиатуры
|
||||
* (libc/kbd/_kbdraw.h), но не оба сразу */
|
||||
|
||||
/* Захватить/отпустить IM2-таблицу (счётчик ссылок). ref: при первом
|
||||
* захвате строит таблицу, ставит I/IM2, взводит atexit-уборку;
|
||||
|
||||
+76
-3
@@ -3,8 +3,16 @@
|
||||
* версия с ЦЕПОЧКОЙ кадровых обработчиков и W1-remap для all-modes.
|
||||
*
|
||||
* Разбор источника (трогаем только AF, пока не выяснили):
|
||||
* - клавиатура (бит 0 порта 0x19 = SIO-A RR0) → сразу к DSS, БЕЗ
|
||||
* remap (W1 не трогаем — прерванный контекст сохранён как есть);
|
||||
* - клавиатура (бит 0 порта 0x19 = SIO-A RR0) → штатно сразу к DSS,
|
||||
* БЕЗ remap (W1 не трогаем — прерванный контекст сохранён как
|
||||
* есть). ИСКЛЮЧЕНИЕ: если raw-канал открыт (`_kbdraw_active`,
|
||||
* см. libc/kbd/_kbdraw.h, kbd_raw_open) — байт достаётся НАМ
|
||||
* (порт данных 0x18 читается один раз, деструктивно — либо мы,
|
||||
* либо DSS, не оба), декодируется make/break (PS/2 Scan Code
|
||||
* Set 2: 0xF0 — префикс отпускания, 0xE0 — префикс расширенной
|
||||
* клавиши) в битовую карту `_kbdraw_down[]`, и к DSS НЕ идём
|
||||
* (private RETI — тот же паттерн, что и у CBL ниже). Пока raw
|
||||
* открыт, DSS клавиатуру не видит вообще (см. <kbd_raw.h>).
|
||||
* - CBL (бит 7 порта 0xFE) — только при зарегистрированном хуке
|
||||
* _irq_cbl_hook (иначе бит подтянут к 1); личное прерывание, RETI
|
||||
* без чейна к DSS;
|
||||
@@ -29,6 +37,7 @@
|
||||
*/
|
||||
|
||||
#include "_irq.h"
|
||||
#include "../kbd/_kbdraw.h"
|
||||
|
||||
void _irq_tramp(void) __naked
|
||||
{
|
||||
@@ -38,7 +47,71 @@ void _irq_tramp(void) __naked
|
||||
rrca ; бит 0 — клавиатурный байт принят?
|
||||
jr NC, tr_notkbd
|
||||
|
||||
;; --- клавиатура: прямо к DSS, без remap ---
|
||||
;; --- клавиатура ---
|
||||
ld a, (__kbdraw_active)
|
||||
or a, a
|
||||
jr Z, tr_kbd_dss ; raw выключен — прежнее поведение
|
||||
|
||||
;; --- raw включён: байт наш целиком, DSS его не увидит ---
|
||||
;; ВЫЧЕРПЫВАЕМ FIFO SIO В ЦИКЛЕ (как эталон keybIntHandler /
|
||||
;; PS2_Scan в docs/samples): приёмный FIFO Z80 SIO 3 байта
|
||||
;; глубиной, а за один кадр при одновременном отпускании
|
||||
;; нескольких клавиш приходит ПАЧКА break-кодов (0xF0 code ...).
|
||||
;; Читать один байт за прерывание = переполнение FIFO = потеря
|
||||
;; break-кода = клавиша «залипает» навсегда (найдено 2026-07-16).
|
||||
push hl
|
||||
push de
|
||||
tr_kbd_drain:
|
||||
in a, (#_IRQ_SIO_DATA) ; читаем — тем самым дренируем байт
|
||||
cp #0xF0
|
||||
jr Z, tr_kbd_setbrk
|
||||
cp #0xE0
|
||||
jr Z, tr_kbd_setext
|
||||
|
||||
;; --- обычный байт кода: индекс в _kbdraw_down[код(+256 EXT)] ---
|
||||
ld hl, #__kbdraw_down
|
||||
ld d, #0
|
||||
ld e, a
|
||||
add hl, de
|
||||
ld a, (__kbdraw_pending)
|
||||
bit 1, a ; _KBDRAW_PEND_EXT?
|
||||
jr Z, tr_kbd_noext
|
||||
ld de, #256
|
||||
add hl, de
|
||||
tr_kbd_noext:
|
||||
ld a, (__kbdraw_pending)
|
||||
bit 0, a ; _KBDRAW_PEND_BREAK?
|
||||
jr Z, tr_kbd_mkdown
|
||||
xor a, a ; break → снять (0)
|
||||
jr tr_kbd_wr
|
||||
tr_kbd_mkdown:
|
||||
ld a, #1 ; make → взвести (1)
|
||||
tr_kbd_wr:
|
||||
ld (hl), a
|
||||
xor a, a
|
||||
ld (__kbdraw_pending), a ; сброс накопленных префиксов
|
||||
jr tr_kbd_next
|
||||
|
||||
tr_kbd_setbrk:
|
||||
ld hl, #__kbdraw_pending
|
||||
set 0, (hl)
|
||||
jr tr_kbd_next
|
||||
tr_kbd_setext:
|
||||
ld hl, #__kbdraw_pending
|
||||
set 1, (hl)
|
||||
;; fall through в tr_kbd_next
|
||||
tr_kbd_next:
|
||||
in a, (#_IRQ_SIO_A) ; ещё байт в FIFO?
|
||||
rrca ; бит 0 → CF
|
||||
jr C, tr_kbd_drain ; да — вычерпать следующий
|
||||
pop de
|
||||
pop hl
|
||||
pop af
|
||||
ei
|
||||
reti ; raw владеет каналом целиком — к DSS НЕ идём
|
||||
|
||||
tr_kbd_dss:
|
||||
;; --- прежнее поведение: прямо к DSS, без remap ---
|
||||
pop af
|
||||
.db 0xC3 ; jp nn
|
||||
__irq_dss_target::
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* _kbdraw.h — внутренности raw-канала клавиатуры (НЕ публичный
|
||||
* заголовок; публичный интерфейс — <kbd_raw.h>).
|
||||
*
|
||||
* Всё состояние — плоские глобалы (живут в W2/_DATA во ВСЕХ memory-
|
||||
* режимах, см. sprinter_memory_modes) — трамплин прерывания
|
||||
* (libc/irq/_irq_tramp.c) обращается к ним БЕЗ W1-remap и без call,
|
||||
* поэтому раскладка памяти приложения тут ни при чём.
|
||||
*/
|
||||
#ifndef _KBDRAW_INTERNAL_H
|
||||
#define _KBDRAW_INTERNAL_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
/* Биты _kbdraw_pending — накопленные префиксы PS/2 Scan Code Set 2,
|
||||
* ждущие завершающего байта кода (см. _irq_tramp.c). */
|
||||
#define _KBDRAW_PEND_BREAK 0x01
|
||||
#define _KBDRAW_PEND_EXT 0x02
|
||||
|
||||
extern uint8_t _kbdraw_active; /* 0 = raw выключен (дефолт, трамплин
|
||||
* ведёт себя как раньше — прямо к DSS) */
|
||||
extern uint8_t _kbdraw_pending; /* FSM декодера make/break, см. выше */
|
||||
extern uint8_t _kbdraw_down[512]; /* 0/1 по коду; +256 для расширенных
|
||||
* (префикс 0xE0) — см. kbd_raw_down */
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,10 @@
|
||||
/*
|
||||
* _kbdraw_state.c — состояние raw-канала клавиатуры (см. _kbdraw.h).
|
||||
* Не инициализировать явно = 0 — crt0 зануляет _DATA/_BSS
|
||||
* (memory/sdcc_static_storage_gotcha).
|
||||
*/
|
||||
#include "_kbdraw.h"
|
||||
|
||||
uint8_t _kbdraw_active;
|
||||
uint8_t _kbdraw_pending;
|
||||
uint8_t _kbdraw_down[512];
|
||||
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* kbd_raw_close — выключить raw-канал, вернуть клавиатуру DSS
|
||||
* (kbhit/getch/getkey/kbd_mod_state снова получают события).
|
||||
* Идемпотентно; висит на atexit (kbd_raw_open).
|
||||
*/
|
||||
|
||||
#include "_kbdraw.h"
|
||||
#include "../irq/_irq.h"
|
||||
|
||||
void kbd_raw_close(void)
|
||||
{
|
||||
if (!_kbdraw_active) return;
|
||||
IRQ_DISABLE();
|
||||
_kbdraw_active = 0;
|
||||
IRQ_ENABLE();
|
||||
_irq_table_unref();
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
/*
|
||||
* kbd_raw_down — зажата ли клавиша code ПРЯМО СЕЙЧАС (0/1).
|
||||
* Читает битовую карту, которую ведёт трамплин прерывания
|
||||
* (libc/irq/_irq_tramp.c) — O(1), никаких ESTEX-вызовов.
|
||||
*/
|
||||
|
||||
#include <kbd_raw.h>
|
||||
#include "_kbdraw.h"
|
||||
|
||||
uint8_t kbd_raw_down(uint16_t code)
|
||||
{
|
||||
if (code >= 512) return 0;
|
||||
return _kbdraw_down[code];
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* kbd_raw_open — включить эксклюзивный raw-канал клавиатуры: с этого
|
||||
* момента байты SIO-A клавиатуры декодирует трамплин ПРЯМО в
|
||||
* прерывании (_kbdraw_down[], см. libc/irq/_irq_tramp.c), DSS их
|
||||
* больше не видит — kbhit/getch/getkey/kbd_mod_state перестают
|
||||
* получать новые события (applications/PoP/docs/PORT_PLAN.md §2).
|
||||
*
|
||||
* Захватывает IM2-таблицу тем же счётчиком ссылок, что
|
||||
* irq_chain_add/cbl_open (_irq_table_ref) — можно сочетать с кадровой
|
||||
* цепочкой и CBL одновременно, они независимы (разные условия ветвления
|
||||
* в трамплине). atexit подстрахует close.
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include <sprinter_exit.h>
|
||||
#include <kbd_raw.h>
|
||||
#include "_kbdraw.h"
|
||||
#include "../irq/_irq.h"
|
||||
|
||||
int kbd_raw_open(void)
|
||||
{
|
||||
if (_kbdraw_active) { errno = EBUSY; return -1; }
|
||||
if (_irq_table_ref() != 0) return -1;
|
||||
|
||||
memset(_kbdraw_down, 0, sizeof(_kbdraw_down));
|
||||
_kbdraw_pending = 0;
|
||||
|
||||
static uint8_t atexit_armed;
|
||||
if (!atexit_armed) {
|
||||
atexit_armed = 1;
|
||||
atexit(kbd_raw_close);
|
||||
}
|
||||
|
||||
IRQ_DISABLE();
|
||||
_kbdraw_active = 1;
|
||||
IRQ_ENABLE();
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user