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>
This commit is contained in:
Александр Петров
2026-08-03 22:16:27 +03:00
parent ba37bd1133
commit 1f16e8fa70
5 changed files with 200 additions and 33 deletions
+82 -15
View File
@@ -46,12 +46,12 @@ void _irq_tramp(void) __naked
push af ; сохранить прерванный AF
in a, (#_IRQ_SIO_A)
rrca ; бит 0 клавиатурный байт принят?
jr NC, tr_notkbd
jr NC, tr_hub_notkbd ; не клавиатура (через ретранслятор см. ниже)
;; --- клавиатура ---
ld a, (__kbdraw_active)
or a, a
jr Z, tr_kbd_dss ; raw выключен прежнее поведение
jr Z, tr_hub_dss ; raw выключен прежнее поведение
;; --- raw включён: байт наш целиком, DSS его не увидит ---
;; ВЫЧЕРПЫВАЕМ FIFO SIO В ЦИКЛЕ (как эталон keybIntHandler /
@@ -63,18 +63,85 @@ void _irq_tramp(void) __naked
push hl
push de
push bc ; маска бита строится в BC (см. ниже)
;; РАСКЛАДКА ЦИКЛА. Все переходы обязаны быть jr (см. шапку:
;; COPY-SAFE), а тело выросло за 127 байт поэтому префиксные
;; обработчики стоят СРАЗУ за своими cp, а возврат на начало цикла
;; идёт через ретранслятор tr_kbd_hub посередине тела.
tr_kbd_drain:
in a, (#_IRQ_SIO_DATA) ; читаем тем самым дренируем байт
cp #0xF0
jr Z, tr_kbd_setbrk
jr NZ, tr_kbd_nF0
ld hl, #__kbdraw_pending
set 0, (hl)
jr tr_kbd_hub
tr_kbd_nF0:
cp #0xE0
jr Z, tr_kbd_setext
jr NZ, tr_kbd_nE0
ld hl, #__kbdraw_pending
set 1, (hl)
jr tr_kbd_hub
tr_kbd_nE0:
;; --- «fake shift»: расширенные коды 12/59 не клавиши, а обёртка,
;; которой клавиатура сопровождает КАЖДЫЙ расширенный код, пока
;; реально зажат Shift (см. _kbdraw.h). Отсюда состояние Shift
;; определяется в обе стороны и не зависит от целости потока. ---
ld e, a ; E = код
ld a, (__kbdraw_pending)
bit 1, a ; EXT?
ld a, e ; A = код обратно (ld флаги не трогает)
jr Z, tr_kbd_code ; обычный код прежний путь
cp #0x12
jr Z, tr_kbd_fake
cp #0x59
jr Z, tr_kbd_fake
;; расширенная КЛАВИША: make без обёртки => Shift не зажат
ld a, (__kbdraw_pending)
bit 0, a ; break состояние Shift не судим
jr NZ, tr_kbd_fkclr
ld a, (__kbdraw_fakesh)
or a, a
jr NZ, tr_kbd_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)
tr_kbd_fkclr:
xor a, a
ld (__kbdraw_fakesh), a
ld a, e
jr tr_kbd_code ; дальше как обычная клавиша
tr_kbd_hub:
;; Ретрансляторы: клавиатурный блок длиннее 127 байт, а jp внутри
;; трамплина запрещён (копия в W2, см. шапку). Стоят посередине
;; тела так дотягиваются и переходы сверху, и снизу. Каждый идёт
;; после безусловного jr, провалиться сюда нельзя.
jr tr_kbd_drain
tr_hub_notkbd:
jr tr_notkbd
tr_hub_dss:
jr tr_kbd_dss
tr_kbd_fake:
;; E0 F0 12 / E0 12 реальный Shift ЗАЖАТ. Бит взводит ОБЩИЙ
;; писатель ниже: достаточно обнулить префиксы тогда он положит
;; код в PLAIN-половину карты (без +32) и именно взведёт бит (не
;; break). В расширенную половину не пишем: это не клавиша.
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 в tr_kbd_code
;; --- обычный байт кода: БИТ code&7 в байте _kbdraw_down[code>>3]
;; (карта упакована по битам: 64 Б вместо 512, см. _kbdraw.h).
;; Маску строим сдвигом, а НЕ таблицей: таблица потребовала бы
;; `ld hl,#метка` внутри трамплина, а он копируется в W2 побайтно
;; и обязан быть без абсолютных само-ссылок (см. шапку). ---
tr_kbd_code:
ld e, a ; E = код
and #7
ld b, a
@@ -115,26 +182,24 @@ void _irq_tramp(void) __naked
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 ; да вычерпать следующий
jr C, tr_kbd_hub ; да вычерпать следующий (через ретранслятор)
;; --- FIFO опустошён: проверить Rx-overrun (потерян ли байт?) ---
;; RR1 SIO бит5 = Rx Overrun Error. При overrun break-код мог
;; потеряться (клавиша «залипнет») взводим _kbdraw_overrun, чтобы
;; kbd_raw_sync() в главном цикле снял held-состояние; и Error Reset
;; (WR0 команда 0x30), иначе бит залипнет. (z84c15 SIO, Z80-совм.)
;;
;; СБРОС _kbdraw_pending по образцу DSS (KBD_Receiver_Overrun в
;; docs/sources/Estex-DSS/DSS/KEYINTER.ASM чистит KEY_FLG, свой FSM
;; префиксов E0/F0). Без этого потерянный 0xF0/0xE0 оставляет
;; префикс висеть, и СЛЕДУЮЩИЙ код декодируется наоборот
;; (makebreak) или уходит в расширенную половину карты стойкий
;; рассинхрон, который лечится только новым нажатием (BUG-KBD-3).
ld a, #1
out (#_IRQ_SIO_A), a ; указатель на RR1
in a, (#_IRQ_SIO_A) ; читаем RR1 (указатель авто-сброс в 0)
@@ -142,7 +207,9 @@ void _irq_tramp(void) __naked
jr Z, tr_kbd_noovr
ld a, #0x30
out (#_IRQ_SIO_A), a ; WR0 Error Reset
ld a, #1
xor a, a
ld (__kbdraw_pending), a ; префиксы недействительны
inc a
ld (__kbdraw_overrun), a
tr_kbd_noovr:
pop bc