Files
Sprinter-SDCC/libc/kbd/kbd_raw_poll.c
T
Александр Петров 1f16e8fa70 KBD: состояние Shift по «fake shift» — ни залипания, ни отвала
Клавиатура PS/2 обёртывает КАЖДЫЙ расширенный код парой E0 F0 12 / E0 12,
пока реально зажат Shift (замер на железе/MAME: правый шифт обёртывается
своим кодом 0x59).  Это прямое и непрерывное свидетельство состояния
Shift — единственное доступное, потому что опросить PS/2 нельзя, а
typematic повторяет последнюю нажатую клавишу, то есть стрелку.

Оба декодера (_irq_tramp.c, kbd_raw_poll.c) читают обёртку в обе стороны:
обёртка есть -> Shift взвести; расширенный make без обёртки -> Shift
снять.  Бит взводит общий писатель — достаточно обнулить префиксы, и код
уходит в plain-половину карты как make.

Это снимает размен, между крайностями которого мы метались:
  - исключать модификаторы из сброса по overrun -> Shift залипал навсегда
    (BUG-KBD-3);
  - сбрасывать всю карту, как DSS -> Shift сносился каждым overrun'ом, а
    при зажатом Shift тап стрелки это 10 байт в 3-байтовый FIFO, то есть
    overrun почти гарантирован (BUG-KBD-4).
Теперь kbd_raw_sync снова не трогает модификаторы, и это безопасно:
залипание снимается первым же нажатием стрелки.

Раскладка трамплина: клавиатурный блок перевалил за 127 байт, а jp внутри
запрещён (копия в W2).  Префиксные обработчики переехали вплотную к своим
cp, посередине тела стоят ретрансляторы tr_kbd_hub/tr_hub_notkbd/
tr_hub_dss.  В kbd_raw_poll такого ограничения нет — там три jp.

Проверено в MAME: Shift переживает пять тапов подряд; штатное отпускание
снимает; искусственно залипший бит снимается первым тапом.
make size-check — роста нет.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-03 22:16:27 +03:00

191 lines
9.2 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.
/*
* kbd_raw_poll — вычерпать приёмный FIFO клавиатуры ОПРОСОМ, не дожидаясь
* прерывания. Дополняет (не заменяет) декодер в трамплине: тот же разбор
* PS/2 Set 2, та же битовая карта _kbdraw_down[], тот же FSM префиксов
* _kbdraw_pending — просто вызванный из главного цикла приложения.
*
* ЗАЧЕМ (разбор 2026-08-01, applications/PoP/roomtest/TASKS.md, KBD-1).
* Приёмный FIFO SIO — 3 байта, и рассчитывать на то, что каждый принятый
* байт разбудит нас прерыванием, НЕЛЬЗЯ:
* - импульс запроса прерывания клавиатуры живёт лишь 32 такта CPU
* (dev-MAME: sprinter.cpp on_kbd_data → irq_off_timer), а ядра
* акселератора держат DI на весь блит — сотни микросекунд. Импульс,
* попавший в такое окно, теряется НАСОВСЕМ: байт остаётся в FIFO до
* следующего прерывания (следующий байт либо кадровое, 50 Гц);
* - трафик от клавиатуры пачками: тап стрелки — 5 байт, а при зажатом
* Shift ещё и «fake shift» (E0 F0 12 / E0 12 вокруг расширенного кода)
* — по 5 байт на нажатие И на отпускание. Три ячейки FIFO такую пачку
* не держат: потерянный make = «нажатие не сработало», потерянный
* break = залипшая клавиша (её потом лечит kbd_raw_sync, ценой сброса
* всех немодификаторных клавиш).
* Опрос из главного цикла снимает зависимость от пойманного импульса: чем
* чаще позвали, тем меньше шанс, что в FIFO успеет накопиться четвёртый
* байт. Звать между фазами кадра (после тяжёлых блитов), НЕ раз в кадр.
*
* ABI: __sdcccall(1), без аргументов, результат uint8 в A — 0 если FIFO
* был пуст (быстрый выход, ~40 тактов), 1 если что-то вычерпано. IX не
* трогаем. Тело идёт под DI: чтение порта 0x18 деструктивно, и гонка с
* трамплином за read-modify-write ячейки _kbdraw_down[] стоила бы
* потерянного бита. DI/EI — БЕЗУСЛОВНЫЕ (как IRQ_DISABLE/IRQ_ENABLE в
* kbd_raw_open): функция рассчитана на вызов из главного цикла, где
* прерывания разрешены; звать её из ISR нельзя.
*/
#include <kbd_raw.h>
#include "_kbdraw.h"
#include "../irq/_irq.h"
uint8_t kbd_raw_poll(void) __naked
{
__asm
ld a, (__kbdraw_active)
or a, a
jr Z, kp_off ; raw-канал закрыт не наше дело
;; DI ДО пробы «есть ли байт»: иначе между пробой и DI успело бы
;; влезть прерывание, трамплин вычерпал бы FIFO, а мы всё равно
;; пошли бы читать порт 0x18 и получили бы из пустого приёмника
;; мусорный «скан-код» (взведённый бит клавиши, которую не нажимали).
di
in a, (#_IRQ_SIO_A) ; SIO-A RR0
rrca ; бит 0 байт принят?
jr C, kp_work
ei
kp_off:
xor a, a
ret
kp_work:
push hl
push de
push bc
kp_drain:
in a, (#_IRQ_SIO_DATA) ; чтение деструктивно = дренаж байта
cp #0xF0
jp Z, kp_setbrk ; jp, а не jr: тело цикла длиннее 127 Б (fake shift)
cp #0xE0
jp Z, kp_setext ; (в отличие от трамплина, здесь jp разрешён)
;; --- «fake shift» тот же разбор, что в трамплине (см. _kbdraw.h):
;; расширенные 12/59 не клавиши, а обёртка вокруг расширенного кода,
;; которую клавиатура шлёт, пока реально зажат Shift. ---
ld e, a ; E = код
ld a, (__kbdraw_pending)
bit 1, a ; EXT?
ld a, e ; A = код обратно (ld флаги не трогает)
jr Z, kp_code ; обычный код прежний путь
cp #0x12
jr Z, kp_fake
cp #0x59
jr Z, kp_fake
;; расширенная КЛАВИША: make без обёртки => Shift не зажат
ld a, (__kbdraw_pending)
bit 0, a ; break состояние Shift не судим
jr NZ, kp_fkclr
ld a, (__kbdraw_fakesh)
or a, a
jr NZ, kp_fkclr ; обёртка была => Shift зажат
ld hl, #(__kbdraw_down + _KBDRAW_LSH_BYTE)
res _KBDRAW_LSH_BIT, (hl)
ld hl, #(__kbdraw_down + _KBDRAW_RSH_BYTE)
res _KBDRAW_RSH_BIT, (hl)
kp_fkclr:
xor a, a
ld (__kbdraw_fakesh), a
ld a, e
jr kp_code
kp_fake:
;; Бит взводит ОБЩИЙ писатель ниже: обнуляем префиксы и он кладёт
;; код в PLAIN-половину карты (без +32) и именно взведёт бит.
ld a, (__kbdraw_pending)
and #1 ; 1 = break-форма (идёт ПЕРЕД обёрнутым make)
ld (__kbdraw_fakesh), a
xor a, a
ld (__kbdraw_pending), a
ld a, e
;; fall through в kp_code
;; --- обычный байт кода: БИТ code&7 в _kbdraw_down[code>>3].
;; Маска сдвигом, а не таблицей ровно как в трамплине, чтобы
;; расхождения декодеров были невозможны by construction. ---
kp_code:
ld e, a ; E = код
and #7
ld b, a
inc b ; B = (код&7)+1 для djnz
ld a, #1
jr kp_mskdec
kp_mskloop:
add a, a
kp_mskdec:
djnz kp_mskloop ; A = 1 << (код&7)
ld c, a ; C = маска бита
ld a, e
rrca
rrca
rrca
and #0x1F ; A = код>>3 (код <= 255)
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, kp_noext
ld de, #32 ; +256 кодов = +32 байта карты
add hl, de
kp_noext:
ld a, (__kbdraw_pending)
bit 0, a ; _KBDRAW_PEND_BREAK?
jr Z, kp_mkdown
ld a, c
cpl ; break сбросить бит
and a, (hl)
jr kp_wr
kp_mkdown:
ld a, c ; make взвести бит
or a, (hl)
kp_wr:
ld (hl), a
xor a, a
ld (__kbdraw_pending), a ; префиксы потреблены
jr kp_next
kp_setbrk:
ld hl, #__kbdraw_pending
set 0, (hl)
jr kp_next
kp_setext:
ld hl, #__kbdraw_pending
set 1, (hl)
kp_next:
in a, (#_IRQ_SIO_A)
rrca ; ещё байт в FIFO?
jp C, kp_drain
;; --- FIFO пуст: тот же разбор Rx-overrun, что и в трамплине.
;; RR1 бит5 = Rx Overrun; без Error Reset (WR0 = 0x30) бит залипнет
;; и следующий overrun будет неотличим. ---
ld a, #1
out (#_IRQ_SIO_A), a ; указатель на RR1
in a, (#_IRQ_SIO_A) ; читаем RR1 (указатель авто-сброс в 0)
bit 5, a
jr Z, kp_noovr
ld a, #0x30
out (#_IRQ_SIO_A), a ; WR0 Error Reset
;; Сброс FSM префиксов как KBD_Receiver_Overrun у DSS (чистит
;; KEY_FLG): повисший 0xF0/0xE0 инвертировал бы следующий код.
xor a, a
ld (__kbdraw_pending), a
inc a
ld (__kbdraw_overrun), a ; снимет kbd_raw_sync()
kp_noovr:
pop bc
pop de
pop hl
ei
ld a, #1
ret
__endasm;
}