/* * kbd_raw_sync.c — восстановление held-состояния после Rx-overrun SIO. * * Аппаратный приёмный FIFO SIO — 3 байта. Если прерывания запрещены * достаточно долго (DI-окна графики/кадрового IRQ), а в этот момент * приходит пачка скан-кодов — FIFO переполняется, байт(ы) теряются * аппаратно. Потерянный break → клавиша «залипает» в _kbdraw_down. * * Трамплин (и kbd_raw_poll) ловят overrun (RR1 бит5): делают Error Reset, * чистят FSM префиксов _kbdraw_pending и взводят _kbdraw_overrun. * Приложение зовёт kbd_raw_sync() раз в кадр — по флагу сбрасывается * held-состояние карты. Реально зажатые клавиши перечитаются make-повтором * (typematic) за ~0.1 с. * * МОДИФИКАТОРЫ ИЗ СБРОСА ИСКЛЮЧЕНЫ (2026-08-03, третья редакция). История * вопроса — две неудачные крайности: * * 1) исключать модификаторы, не имея иного способа их проверить: * потерянный break Shift снять нечем, Shift залипает навсегда * (BUG-KBD-3); * 2) сбрасывать ВСЁ, как DSS (KBD_Receiver_Overrun в KEYINTER.ASM чистит * и KEYCTRL, и KEY_FLG): залипания нет, но удерживаемый Shift сносится * каждым overrun'ом, а при зажатом Shift КАЖДЫЙ тап стрелки — это 10 * байт в 3-байтовый FIFO, то есть overrun почти гарантирован. Игрок * жмёт Shift+→ ожидая осторожный шаг, а Кид уходит в бег — в PoP это * прямо стоит жизни (яма/пики). Хуже залипания (BUG-KBD-4). * * Выбран вариант (1) — модификаторы НЕ сбрасываем. Была и третья редакция, * обещавшая снять сам размен: «fake shift» (E0 F0 12 / E0 12) читался в обе * стороны, и расширенный make без обёртки снимал Shift, то есть залипание * лечилось первым же тапом стрелки. Обратный вывод оказался неверным * (замер 2026-08-05, BUG-GRAB-1: обёртку шлют не все клавиатуры и никогда — * на typematic-повторах), и он ломал Shift вместе со стрелками; убран, см. * _kbdraw.h. Значит размен вернулся, и здесь он решается в пользу * «лучше залипание, чем отвал»: залипший модификатор снимается его же * перенажатием, а сорванный посреди игры Shift в PoP стоит жизни. * * Overrun теперь редок сам по себе: FIFO вычерпывается опросом из главного * цикла (kbd_raw_poll, KBD-1), а не только по пойманному прерыванию. */ #include #include "_kbdraw.h" /* Скан-коды модификаторов (PS/2 Set 2): L/R Shift, LCtrl, LAlt, * RCtrl (E0 14 → +256), RAlt (E0 11 → +256). */ static const uint16_t _kbd_mod_codes[6] = { 0x12, 0x59, 0x14, 0x11, 0x0114, 0x0111 }; /* Голые коды нумпада (Set 2 0x69..0x7D) — по БИТАМ карты: байт 13 держит * 0x68..0x6F, байт 14 — 0x70..0x77, байт 15 — 0x78..0x7F. Гасим только * клавиши нумпада: ESC (0x76, байт 14 бит 6) и F11 (0x78, байт 15 бит 0) * обязаны проходить, поэтому маски именно такие, а не 0xFF. */ #define KP_BYTE0 13 #define KP_MASK0 0x1A /* 0x69 KP1, 0x6B KP4, 0x6C KP7 */ #define KP_BYTE1 14 #define KP_MASK1 0xBF /* 0x70..0x75, 0x77 — всё, кроме ESC 0x76 */ #define KP_BYTE2 15 #define KP_MASK2 0x3E /* 0x79..0x7D — всё, кроме F11 0x78 */ void kbd_raw_sync(void) { uint8_t save[6]; uint8_t i; /* ПЕРЕКЛАДЫВАЕМ ГОЛЫЕ КОДЫ НУМПАДА В EXT-ПОЛОВИНУ, если приложение * попросило. Делается здесь, а не в декодерах: клавиатурный блок * трамплина и так длиннее 127 байт (внутри него `jp` запрещён — * трамплин копируется в W2), и вставка рвала относительные переходы. * Цена — шесть операций раз в кадр вместо проверки на каждый байт. * * Почему это лечит залипание: стрелка приходит как E0 6B, и при потере * префикса make садится в PLAIN-половину как код нумпада, а break * придёт уже с префиксом и снимет бит в EXT — plain-бит остался бы * зажатым навсегда. После перекладки make и break работают с ОДНИМ и * тем же битом. Позиции бит совпадают: EXT — те же байты плюс 32. */ if (_kbdraw_keypad_ext) { _kbdraw_down[KP_BYTE0 + 32] |= (uint8_t)(_kbdraw_down[KP_BYTE0] & KP_MASK0); _kbdraw_down[KP_BYTE1 + 32] |= (uint8_t)(_kbdraw_down[KP_BYTE1] & KP_MASK1); _kbdraw_down[KP_BYTE2 + 32] |= (uint8_t)(_kbdraw_down[KP_BYTE2] & KP_MASK2); _kbdraw_down[KP_BYTE0] &= (uint8_t)~KP_MASK0; _kbdraw_down[KP_BYTE1] &= (uint8_t)~KP_MASK1; _kbdraw_down[KP_BYTE2] &= (uint8_t)~KP_MASK2; } if (!_kbdraw_overrun) return; _kbdraw_overrun = 0; /* Карта упакована по битам (см. _kbdraw.h): сохраняем/возвращаем * именно БИТ модификатора, а не байт. */ for (i = 0; i < 6; i++) { uint16_t c = _kbd_mod_codes[i]; save[i] = (uint8_t)((_kbdraw_down[c >> 3] >> (c & 7)) & 1); } memset(_kbdraw_down, 0, sizeof(_kbdraw_down)); for (i = 0; i < 6; i++) { uint16_t c = _kbd_mod_codes[i]; if (save[i]) _kbdraw_down[c >> 3] |= (uint8_t)(1 << (c & 7)); } /* FSM префиксов трамплин уже сбросил; дублируем на случай overrun'а, * пойманного вне его (kbd_raw_poll между кадрами). */ _kbdraw_pending = 0; }