Estex-DSS/keys.md
2026-06-18 20:52:50 +10:00

54 KiB
Raw Blame History

Промт для новой сессии: изучение и модификация клавиатурной подсистемы DSS

Задача

Изучить и при необходимости модифицировать алгоритмы работы с клавиатурой, реализованные в DSS/KEYINTER.ASM — модуле, который включается в ядро DSS через INCLUDE из DSS/DSS-MAIN.ASM (строка 402, сразу после таблицы DSS_API_TABLE). Это код для компьютера Sprinter (Z84C15), работающего как PS/2-хост для AT-клавиатуры через встроенный в Z84C15 SIO канал A.

Перед изменениями сверяй свои выводы с исходным кодом: в этом проекте уже зафиксирована память о том, что доверять разбору регистров/стека от суб-агентов нельзя — трассируй PUSH/POP/EX AF/EXX лично (см. [[verify-subagent-reg-flow-claims]] в auto-memory).


1. Рабочее окружение

  • Основная директория проекта (kernel + сборка): /Users/tolik/Documents/SP_Projects/ASM/GIT/Estex DSS
  • Доп. директория (архив/референс, не собирается): /Users/tolik/Documents/SP_Projects/ASM/Another (подключена в Estex DSS.code-workspace как второй корень)
  • Подмодуль git: Shared_Includes/ — общие константы/структуры/доки; ветка main, url ../Shared_Includes.git.
  • Кодировка/переводы строк: исходники в CP866 (русский DOS) с CRLF. Это включает DSS-MAIN.ASM, KEYINTER.ASM и большинство Shared_Includes/constants/*.inc. grep -aI нужен, если grep считает файлы бинарными.
  • Ассемблер: sjasmplus --syntax=afw. Сборка идёт через VS Code (.vscode/tasks.json) — Makefile нет. Полезные задачи: Build SYSTEM.DOS, Build SYSTEM.DOS [NEW] (--define INCREASE_BUILD → инкремент DSS/build.txt), Build SYSTEM.EXE, Build SYS.EXE, Copy all files to MAME (вызывает RUN/Image.shhdfmonkey put в /Users/tolik/Documents/MAME/IMG/test_2g.img), Run MAME.
  • Выходы сборки: Build/DSS/SYSTEM.DOS (ядро), Build/DSS/SYSTEM.EXE (шелл), Build/DSS/SYS.EXE (бутлоадер), Build/DSS-MAIN.LST (листинг ~1 МБ), Build/Variables.inc (экспортированные символы через --exp).
  • Версия: VERS=1, MODF=71, билд из DSS/build.txt (сейчас 68) → "DSS 1.71.68". Логика в DSS/VERSION.INC + LUA в Shared_Includes/LUA/Functions.lua.

2. Архитектура DSS и место KEYINTER

2.1. Точка входа ядра — DSS/DSS-MAIN.ASM

  • ORG 0, ядро располагается с адреса #0000 на странице ядра.
  • RST-векторы (DSS-MAIN.ASM около строк 30-310):
    • RST 00 — RETFAR / реентри для дочерних процессов
    • RST 08 (ToBIOS) — портал в BIOS в SLOT0
    • RST 10 (ToDSS) — DSS API gateway → RST_10 диспетчер на строке 187
    • RST 18 — портал в страницу драйверов
    • RST 20, RST 28 — NOPS
    • RST 30 (ToDSS.Mouse) — мышиный API
    • RST 38 (IM 1) — INTx38_Handler на строке 275: сохраняет все регистры (главный набор + альтернативный + IX/IY), вызывает CALL KEYSCAN, затем RST ToDSS.Mouse GetPackets, затем cursor_interrupt, RETI.
  • Диспетчер RST_10 (строка 187): LD H,high DSS_API_TABLE / LD L,C — C=номер функции, индексирует таблицу адресов с раздельными байтами (низкие байты строки 340-361, высокие 369-390).
  • Клавиатурные слоты в DSS_API_TABLE (строки 344-345 / 373-374):
    • #30 WAITKEY, #31 SCANKEY, #32 ECHOKEY, #33 CTRLKEY, #34 NOPS (зарезервирован, не реализован — константа Dss.EDIT есть, кода нет), #35 K_CLEAR, #36 K_SETUP, #37 TESTKEY.
  • INCLUDE KEYINTER.ASM — строка 402.
  • Boot-флоу F_START (строка 567): DEPLOY → CALL KEYBOARD_INIT (строка 592) → PRINT_INIT → инициализация мыши → инициализация дисков → EICLEAR_BUFFER_AND_INIT_PROC.
  • Карта памяти (DSS/DSS_MAP.TXT):
    • #0000..#0038 — RESTARTS
    • #0200..#03FFDSS_API_TABLE
    • #0400..#043FSBUF (буфер клавиатуры, 64 байта)
    • #0440..#0BD1 — код KEYINTER (~1936 байт)
    • #0BD2..#0EF5 — драйвер экрана (для совместного использования с Cursor_On/Off)

2.2. Соседние модули

  • DSS/API.asm — агрегатор-INCLUDE для всех DSS/API/*.asm.
  • DSS/FS_Module.asmFS/FAT.asm + FS/CDFS.ASM.
  • DSS/DOS_Proc.asm — DOS-уровневые помощники (имена файлов, маски, SET_FM, OPENDSK и т.п.).
  • DSS/Procedures.asm — общие хелперы (PUTCHAR строка 103, PUTCHAR.NO_SCROLL строка 104, MK_TIME и др.).
  • DSS/DRV-MAIN.ASM — отдельная страница драйверов (DISP/ENT-блок в DSS-MAIN.ASM:695-699), своя таблица RST по адресам #A0000..#A0038. Клавиатура там НЕ живёт.
  • DSS/drivers/Input/MOUSE.ASM — мышь; делит окно INT 38h с KEYINTER (сначала KEYSCAN, потом мышь).
  • DSS/defines.inc — compile-time флаги (см. §5).
  • DSS/DSS_MACROSES.Z80 — макросы. Закомментированный BUFFER_KEYINTER (строки 2-82) задокументирован как канонический layout SBUF/KEYFLAG/KEYCTRL/KEYFLG/SOUND_K, но реальные определения сейчас inline в KEYINTER.ASM. SET_PAGE_X (строки 137-143) — рабочий макрос переключения страниц SLOT3.

2.3. Внешние символы, на которые ссылается KEYINTER

Символ Что Где определён
PUTCHAR, PUTCHAR.NO_SCROLL вывод символа DSS/Procedures.asm:103-104
LOCATE установка курсора DSS/API/Locate.asm:8
VMODE байт текущего видеорежима DSS/API/SetVMod.asm:90
BANKTBL таблица логическая→физическая страница DSS/DSS-MAIN.ASM:423
RST_10 диспетчер DSS DSS/DSS-MAIN.ASM:187
ToBIOS, BIOS.* BIOS API Shared_Includes/constants/BIOS_equ.inc
ToDSS, Dss.*, DSS_Error.* DSS константы Shared_Includes/constants/dss_equ.inc
Z84.SIO.Ch_A.{Data,Ctrl}, SP_SND.Beeper, SLOT3, TXTPAGE железо Shared_Includes/constants/SP2000.inc (модуль Z84, строки 2111-2127); часть EQU может приходить из внешнего SP2000-инклюда не из снимка репо — проверять Build/Variables.inc

3. KEYINTER.ASM подробно

Файл — 1371 строка, CRLF, CP866. Заголовок R01 (10.02.2003, DNS — добавлен курсор в ECHOKEY) и R02 (13.04.2023, BAO — исправлен stack overflow в K_CLEAR).

3.1. Публичный API (вызывается через RST 10 с C= номер функции)

C Метка Строки Назначение
#30 WAITKEY 112-119 Блокирующее ожидание. Выход: A=E=ASCII, D= поз.код, B=KEYCTRL, C=KEYFLAG
#31 SCANKEY 122-128 Неблокирующее. ZF=1 если буфер пуст
#32 ECHOKEY 195-212 SCANKEY + программный курсор (Cursor_On/Off), эхо через PUTCHAR.NO_SCROLL
#33 CTRLKEY 383-391 Прочесть BC=(KEYFLAG) без извлечения; A=0 если буфер пуст, A=#FF если есть
#34 (NOPS) Dss.EDIT определён, но функция не реализована в v1.71
#35 K_CLEAR 431-445 Очистить буфер (HEAD:=HOST), потом JP RST_10 для повторной диспетчеризации, если B ∈ [#30..#34]. Иначе A=#01 INVALID_FUNCTION, CF=1
#36 K_SETUP 934-... Мультиплексор: см. §3.6
#37 TESTKEY 393-408 Peek текущей записи без сдвига HOST. ZF=1 если пуст

3.2. Кольцевой буфер

  • SBUF (строка 14): _mInfoALIGN 256,0 + BLOCK 64,0 — 64 байта на границе страницы. Поэтому high-byte адреса слота = high SBUF, а HEAD/HOST — только low-byte (0..#3F).
  • HEAD (строка 33) — указатель писателя.
  • HOST (строка 34) — указатель читателя.
  • Слот = 4 байта: byte+0=E (ASCII), byte+1=D (поз.код | #80), byte+2=B (KEYCTRL snapshot), byte+3=C (KEYFLAG snapshot).
  • Макс. 16 записей.
  • Пуст: HEAD == HOST. Полон: HEAD == (HOST-4) & #3F (проверка PUTSYM строки 450-453).
  • GetSymAddr (строки 467-476) — общий помощник: A := (HL), INC (HL) ×4, RES 6,(HL) (wrap modulo 64), формирует HL = high SBUF : старый offset.
  • PUTSYM (448-464) — продьюсер. На переполнении прыгает в FULL_BF (496-505): если SF_BUFF бит SOUND_K взведён — BEEP с DE=230, HL=50, ключ отбрасывается.
  • GETSYM (479-493) — консьюмер.

3.3. Байты состояния (адресуются через LD IX,KEYFLAG на строке 519)

KEYFLAG (offset 0, alias K_LOCK, строка 45, init #02INS_L on):

Бит Имя Значение
7 LANG_L язык RUS/LAT
6 PAUSE_L Pause Lock
5 RES5_L резерв (в комментариях помечено как историческое X_SHIFT)
4 RES4_L резерв
3 NUM_L Num Lock
2 SCRL_L Scroll Lock
1 INS_L Insert
0 CAPS_L Caps Lock

Переключается: CAPS_X 668-671, LANG_X 674-690, INS_X 693-696, NUM_X 699-702, PAUSE_X 705-718, SCL_X 721-724.

KEYCTRL (offset 1, alias K_SHIFT, строка 56, init #00):

Бит Имя
7 L_SHIFT
6 R_SHIFT
5 X_CTRL (любой Ctrl)
4 X_ALT (любой Alt)
3 L_CTRL
2 L_ALT
1 R_CTRL
0 R_ALT

Устанавливаются в SHIFTS (799-836) на нажатии, сбрасываются в UNSHIFT (738-797) на отпускании. Также чистятся в KBD_Receiver_Overrun (1240).

KEY_FLG (offset 2, alias KEYFLG, строка 67, init #00):

Бит Имя
7 FLAG_E0 — защёлкнут E0-префикс (extended)
6 FLAG_F0 — защёлкнут F0-префикс (release)
5 FLAG_E1 — E1-префикс (только если USE_E1_SCANCODE)
4-1 резерв
0 CTRL_SHIFT — Ctrl+Shift зафиксирован (для LANG_X при CHANGE_LANG_CTRL_SHIFT)

SOUND_K (offset 3, строка 78, init #03оба звука включены):

Бит Имя
7-2 FLAG_S7..S2, резерв
1 SF_ALT — звук при смене языка
0 SF_BUFF — звук при переполнении буфера

UNCODE (offset 4, WORD, строка 81) — последний отпущенный скан-код (пишется в UN_KEY строка 663).

3.4. Таблица трансляции скан-кодов

  • XLAT_T (строки 21-30) — сырая таблица 144 байта, AT scancode (Set 2) → внутренний позиционный код 0..#5A. XLAT_T.Size = 144. Bounds-check в XLAT на строке 860.
  • XLAT (839-870) — собственно переводчик. Если защёлкнут FLAG_E0, жёстко мапит специальные коды (#11→#39 R-ALT, #14→#3A R-CTRL, #5A→#4E numpad Enter, #4A→#4A /, #7C→#47 PrintScreen); иначе L = XLAT_T[A].

3.5. ASCII-таблицы (MODULE ASCII_TABLES, строки 1285-1368)

Все таблицы ровно 90 байт (Size EQU 90):

  • ASCII_TABLES.ENGLISH (1287-1295) — без шифта, англ
  • ASCII_TABLES.SHIFT_ENG (1297-1305) — с шифтом, англ
  • ASCII_TABLES.CAPS_ENG (1307-1315) — Caps, англ
  • ASCII_TABLES.CAPS_SHIFT_ENG (1317-1325) — Caps+Shift, англ
  • ASCII_TABLES.RUSSIAN (1328-1336) — без шифта, рус CP866
  • ASCII_TABLES.SHIFT_RUS (1338-1346)
  • ASCII_TABLES.CAPS_RUS (1348-1356)
  • ASCII_TABLES.CAPS_SHIFT_RUS (1358-1366)

Старые метки NORMTAB / SHIFTAB / CAPSTAB / SHF2TAB / NORMRUS / SHIFRUS / CAPSRUS / SHF2RUS сохранились ТОЛЬКО в закомментированной legacy-копии K_SETUP (строки 1097-1167) и не являются живыми. Все KEYMAP/READMAP используют ASCII_TABLES.*.

3.6. K_SETUP подфункции (диспетчер на строке 934)

INC B / DJNZ K_SND_R — цепочка DJNZ:

B Метка Поведение
0 KEYMAP (937-972) / READMAP (974-1006) Запись/чтение одной из 8 ASCII-таблиц. HL= буфер, A bit7=0 запись, bit7=1 чтение, биты0-2 = номер подтаблицы 0..7. Объём LDIR = ASCII_TABLES.Size=90 байт. Неверный A → XOR A : SCF : RET
1 K_SND_R (1019-1023) A := SOUND_K
2 K_SND_W (1025-1030) SOUND_K := A
3 K_CURSOR_ON (1034-1038) Если VMODE показывает текстовый режим — JP Cursor_On
4 K_CURSOR_OFF (1042-1046) То же → JP Cursor_Off
≥5 K_SETUP.ERROR (1015-1017) A = #0E INVALID_ACCESS, SCF, RET

3.7. Сканер клавиатуры — KEYSCAN (519-629)

  • LD IX,KEYFLAG — IX указывает на блок состояния.
  • В текущем билде сканер ПОЛЛИНГ (KEYBOARD_INT_ENABLED=0): IF-блок строк 521-526 (который бы делал CALL .RESCAN, выход из INT) НЕ ассемблируется. KEYSCAN падает прямо в .RESCAN. На практике он вызывается из INTx38_Handler (DSS-MAIN.ASM:275-306), который сам висит на IM 1 RST 38h.
  • Логика .RESCAN (от строки 527):
    1. IN A,(Z84.SIO.Ch_A.Ctrl) — читаем RR0.
    2. Если bit0 (Rx Character Available) = 0 → RET.
    3. Иначе пишем 1 в Ctrl (указать на регистр 1), читаем обратно, тестируем bit5 (Receiver Overrun) → ошибка → JP KBD_Receiver_Overrun (1228) — дренаж FIFO, OUT %00110000 в WR0 для error reset, обнуление KEYCTRL/KEY_FLG.
    4. Иначе IN A,(Z84.SIO.Ch_A.Data) (строка 538) — байт скан-кода.
    5. Префиксы:
      • #F0FULL_BF.F0_KEY: устанавить FLAG_F0, JP .RESCAN.
      • #E0FULL_BF.E0_KEY: устанавить FLAG_E0, JP .RESCAN.
      • #E1 → если USE_E1_SCANCODE=1 устанавить FLAG_E1, иначе игнорировать.
    6. Обычный байт + FLAG_F0UN_KEY (657-664): XLATUNSHIFT модификаторов, UNCODE := HL (последний отпущенный).
    7. Обычный байт без FLAG_F0XLATSHIFTSсброс FLAG_E0/E1 → при CHANGE_LANG_CTRL_SHIFT=1 опционально установить CTRL_SHIFTINPCODEPUTSYMJP .RESCAN (продолжать дренировать FIFO, строка 628) — поскольку SIO имеет 3-байтовый Rx FIFO (см. §6.4).

3.8. INPCODE/RUSCODE/CONVERT (875-930)

  • INPCODE (875-906) — выбор одной из 4 английских таблиц на основе KEYCTRL бит шифта и KEYFLAG.CAPS_L. Если LANG_L взведён → JP RUSCODE.
  • RUSCODE (908-930) — то же для русских.
  • CONVERT (901-906): SET 7,D (флаг keystroke), HL = таблица+L, E := (HL). После этого PUTSYM.

3.9. Курсор и звук

  • Cursor (215-218) — переключатель: CPL .Flag, falls into Cursor_On.
  • Cursor_On (220-253): BIOS.LP_GET_PLACE (#8E) → DE=Y/X, сохранить; BIOS.WIN_GET_SYM (#B4); вычислить глиф — NORM_ZG=#1B обычно, INS_CUR_ZG=#9B при INS_L, CURSOR_ZG=#5B для фазы blink; BIOS.WIN_PUT_SYM.NoChangeZG (#01B5); LOCATE.
  • Cursor_Off (257-284): обратное действие; JP LOCATE.
  • cursor_interrupt (287-295): тик. Если .Flag != 0, декрементировать таймер; на нуле CALL Cursor.
  • SETUP_CURSORS (298-375): один раз при старте подгружает кастомные глифы (#5B блок и #9B инверт.) в страницу шрифта (BANKTBL+TXTPAGE) через BIOS.WIN_GET_ZG (#B8) → CPL → BIOS.WIN_SET_ZG (#B6).
  • BEEP (1173-1192): DE= период, HL= повторы. Тогглит A=#10/#00 в (SP_SND.Beeper) = порт #FE бит 4.

3.10. Инициализация — KEYBOARD_INIT (1194-1224)

Последовательность для SIO Ch_A (стандартная Z80 SIO init):

  • DI
  • WR0 := 0 (clear pointer)
  • WR4 := парам. (clock, stop, parity) — точные байты в коде, ср. с Another/SPRINTER/TASM/Keybdrv.z80 (там 9 байт init)
  • WR3 := #C1 (Rx enable, 8 бит/символ)
  • WR5 := #62 (0110'0010: бит3 Tx Enable = 0 → передатчик ВЫКЛЮЧЕН; биты5-6 = 8 бит/символ). Передача данных клавиатуре аппаратно отключена — см. §6.5
  • WR1 := %00011001 если KEYBOARD_INT_ENABLED=1, иначе 0
  • EI : RET

4. Конвенция вызова DSS API

  • DSS: LD C,Dss.Xxx (номер функции из Shared_Includes/constants/dss_equ.inc, модуль Dss:), RST 10 (= ToDSS). Параметры — в регистрах согласно спецификации (см. Shared_Includes/Docs/DSS_1.71_Functions.asm, раздел 6).
  • Выход: CF=0 — успех (значения в регистрах согласно функции); CF=1 — ошибка, A= код ошибки (DSS_Error.sys.*).
  • BIOS: LD C,BIOS.XXX (Shared_Includes/constants/BIOS_equ.inc, модуль BIOS:), RST 08 (= ToBIOS). Перед вызовом необходим DI и OUT (#7C),0 для переключения банка BIOS; стек должен быть в #8000-#BFFF. Подробно — Shared_Includes/Docs/BIOS functions.asm, строки 1-60.
  • Подфункции: верхний байт BC (т.е. B) — индекс подфункции; константа обычно собрана как EQU subfn*256 + .Func. Пример: Dss.K_SETUP.SetSoundVars EQU 2*256+#36.

5. Compile-time флаги (DSS/defines.inc)

Флаг Текущее Что переключает в KEYINTER
KEYBOARD_INT_ENABLED (стр. 21) 0 0 = поллинг из INT 38h; 1 = SIO Ch_A прерывание (WR1 := #19), отдельный обработчик в начале KEYSCAN
CHANGE_LANG_CTRL_SHIFT (стр. 22) 1 1 = переключение языка Ctrl+Shift (через CTRL_SHIFT бит и UNSHIFT.CHECK_CTRL_SHIFT); 0 = Ctrl+Space (legacy путь IFN CHANGE_LANG_CTRL_SHIFT строки 585-591)
BREAK_PROCESS_CODE (стр. 23) #AC00 Cooked-код Ctrl+C для WaitKey/EDITDSS_Error.sys.USER_ABORT (#25)
USE_E1_SCANCODE (стр. 24) 0 1 = обрабатывать E1 (Pause/Break), 0 = игнорировать
ENABLED_KEYBOARD_CONTROL (стр. 24) 0 1 = компилировать FAKE_SHIFT (игнор AT fake-shift E0 12/E0 59 вокруг серых nav-клавиш). 0 = выключено: на железе SIO Tx отключён (WR5=#62), хост не может слать 0xED, клавиатура не знает NumLock → fake-shift не приходит. Включать, только если появится двунаправленный канал к клавиатуре. См. §6.5
SHORT_RSTx10_TABLE (стр. 10) 0 Должен быть 0 (либо ≥#37), иначе клавиатурные слоты не войдут в API table
COMPILE_UNUSED_CODE (стр. 11) 0 Гейтит legacy/debug куски

Также в defines.inc — мышиные флаги (MOUSE_INT_ENABLED, ...) которые делят с клавиатурой инфраструктуру SIO/IRQ.


6. Аппаратура Sprinter / Z84C15

6.1. Карта внутренних портов Z84C15

Shared_Includes/constants/SP2000.inc, MODULE Z84 (строки 2111-2127):

Порт Назначение
#10..#13 CTC.Ch_0..3 (таймеры)
#18 Z84.SIO.Ch_A.DataPS/2 клавиатура DAT_A
#19 Z84.SIO.Ch_A.CtrlPS/2 клавиатура COM_A
#1A SIO.Ch_B.Data (DMOUSE)
#1B SIO.Ch_B.Ctrl (CMOUSE)
#1C..#1D PIO.Port_A — LPT1 (принтер)
#1E..#1F PIO.Port_B — LPT2
#F0..#F1 WDT
#F4 IntPrior_Reg
#EE/#EF SYS.Control / SYS.Data

Так что коммит e334545 "M_INT: RFI is CH-A ONLY (per Z80 SIO spec)" относится к SIO Ch_A (клавиатура) — принтер на PIO, не на SIO.

6.2. Порт бипера и банкование

  • SP_SND.Beeper = порт #FE, бит 4 (%00010000). Прямая запись из KEYINTER (нет BIOS-функции для звука).
  • SLOT0..SLOT3 = #82, #A2, #C2, #E2 — внешние порты переключения страниц памяти на окна #0000/#4000/#8000/#C000. SET_PAGE_X из DSS_MACROSES.Z80 (137-143) использует SLOT3 + BANKTBL[page].
  • TXTPAGE — индекс в BANKTBL для страницы шрифта (используется в SETUP_CURSORS строка 304). Если EQU не определён в снимке репо, он приходит из внешнего SP2000-инклюда (см. Build/Variables.inc после первой сборки).

6.3. Прерывания

  • IM 1, единый вектор #FF для кадрового И клавиатурного прерывания.
  • Различие: после входа в INT 38h (DSS-MAIN.ASM:275), читать порт #19 (SIO Ch_A Ctrl/RR0); bit0=1 → пришёл байт от клавиатуры (в FIFO). Это и делает KEYSCAN.RESCAN.
  • SIO Ch_A имеет Rx FIFO на 3 байтаsp2000.pdf §9.4 явно предписывает: «после обработки одного байта проверять, не пришло ли ещё, и возвращаться только если FIFO пуст».
  • Включение режима, при котором клавиатурный INT работает: Port_All_Mode = #204E, биты SPECTRUM_MODE_OFF=1 (бит 0) и STOP_KBD_INT_OFF=8 (бит 3) (см. SP2000.inc).
  • Активный указатель ISR хранится в SYS_PAGE.INT_ADRESS = #FEC124, INT_PAGE = #FEC126, INT_ID = #FEC127 (сентинель #AA — установлено).

6.4. Z80 SIO регистры (для модификаций инициализации)

  • WR0 — command + pointer на следующий WR/RR.
  • WR1 — interrupt enable (бит 0 ExtINT, биты 1-2 Tx Int, биты 3-4 Rx Int mode %01100 = INT_ON_RXBYTE), бит 5 Wait/Ready.
  • WR2 — Interrupt vector (только Ch_B по Z80 SIO spec — нерелевантно для клавиатуры).
  • WR3 — Rx control: bit0 Rx Enable, биты 6-7 Rx bits/character.
  • WR4 — clock multiplier, stop bits, parity.
  • WR5 — Tx control: бит 3 Tx Enable, биты 5-6 Tx bits/character.
  • RR0 — статус: бит 0 = Rx Character Available (то что тестирует KEYINTER), бит 1 = Int Pending (CH-A only), бит 2 = Tx Buffer Empty, бит 5 = Sync/Hunt, бит 6 = CTS, бит 7 = Break.
  • RR1 — special Rx conditions: framing/parity/overrun errors.
  • Полный datasheet — Zilog Z80 SIO Technical Manual (UM008); в репо его нет, см. ссылки в §8.

6.5. Команды PS/2 хост→клавиатура (host commands)

Если потребуется управлять светодиодами Caps/Num/Scroll или autorepeat, то протокол описан в /Users/tolik/Documents/SP_Projects/ASM/Another/Docs/Keyboard/KBD.TXT:

  • #FF reset+self-test, #FE resend, #F4 enable scanning, #F5 disable+default,
  • #ED set LEDs (bit0 Scroll, bit1 Num, bit2 Caps),
  • #F3 set typematic rate+delay (биты 6-5 delay 250/500/750/1000 ms, биты 4-0 rate 6..30 cps),
  • #F2 read keyboard ID,
  • #FB-#FD, #F7-#FA per-key/all-keys typematic.

Передача клавиатуре аппаратно невозможна в текущей конфигурации: KEYBOARD_INIT ставит WR5 = #62 (бит3 Tx Enable = 0), т.е. передатчик SIO Ch_A выключен. Поэтому host-команды (0xED Set LEDs, autorepeat и др.) не отправляются и не могут.

Важное следствие про fake-shift: по стандарту AT/PS-2 клавиатура шлёт fake-shift (E0 12 / E0 59 вокруг серых extended nav-клавиш — стрелки/Ins/Del/Home/End/PgUp/PgDn) ТОЛЬКО когда её внутренний Num Lock-LED включён. А LED выставляет ТОЛЬКО хост командой 0xED (нажатие Num Lock для клавиатуры — обычный скан-код 0x77, своё состояние она не меняет). Раз 0xED послать нельзя → клавиатура всегда считает Num Lock выключенным → fake-shift на реальном железе не генерируется, выделенные стрелки всегда приходят чисто (E0 6B и т.п.). Обработка fake-shift реализована в FAKE_SHIFT, но компилируется только при ENABLED_KEYBOARD_CONTROL=1 (§5) — на железе это мёртвый код, нужен лишь для эмуляторов (MAME msnat сам моделирует Num Lock+fake-shift, чего на железе быть не может). Чтобы реально слать команды — включить Tx (WR5 бит3=1) и писать в #18 после проверки готовности Tx Buffer (RR0 bit2).

6.6. CMOS-привязка

  • CMOS_CELL.BootUpParams = #0E, .Mask.Language = %0000'0100 — пресет языка при загрузке.
  • CMOS_CELL.Typematic = #0F, маски .Enabled %1000'0000, .Delay %0110'0000, .Rate %0000'0111.
  • Доступ через BIOS.CMOS_RD (#F6) / CMOS_WR (#F7) или прямые порты CMOS.Port.{Data.Read=#FFBD, Data.Write=#BFBD, Address.Write=#DFBD}.
  • Сейчас KEYINTER НЕ читает CMOS — состояние локов и языка сбрасывается на каждый boot к дефолтам. Если нужна persistence, добавлять в KEYBOARD_INIT или в высокоуровневый AUTOEXEC.BAT.

7. Reference материалы

7.1. В репозитории

Документ Что искать
Shared_Includes/Docs/sp2000.pdf §1.1 Sp2000 hardware (p.4), §1.3 block diagram (p.5), §9 Внутренние порты Z84C15 (p.21), §9.4 AT-Клавиатура p.25-26 (COM_A/DAT_A, sample code), §13.2 карта портов p.31
Shared_Includes/Docs/BIOS functions.asm Раздел 5 «Функции печати и управления режимом экрана» (стр. 628-1100): LP_PRINT_SYM #82, LP_PRINT_ALL #81, LP_SET_PLACE #84, LP_GET_PLACE #8E, WIN_GET_SYM #B4, WIN_PUT_SYM #B5, WIN_GET_ZG #B8, WIN_SET_ZG #B6, LP_SCROLL_UD #8A, LP_PR_LINE_DIR #E0. Конвенция вызова — стр. 1-60
Shared_Includes/Docs/DSS_1.71_Functions.asm Раздел 6 «Функции ввода с клавиатуры» (строки ~364-446) — авторитетная спецификация на функции #30..#37 с корректировками из последней сессии (Pause Lock bit6, CTRLKey A=#FF/A=0, K_CLEAR INVALID_FUNCTION, K_SETUP B=0 это Get/Set table transfer)
Shared_Includes/constants/dss_equ.inc Dss.WaitKey..TestKey (137-149), Dss.K_SETUP.* (144-148), DSS_Error.sys.* (280-336), Приложение А — позиционные коды клавиш (547-595)
Shared_Includes/constants/BIOS_equ.inc BIOS.* функции (см. §7.1 ниже)
Shared_Includes/constants/SP2000.inc MODULE Z84 (2111-2127), SP_SND, ACEX.*, ZXKeys.Line_* (#FEFE..#7FFE legacy ZX), Port_All_Mode #204E, SYS_PAGE.INT_*, CMOS_CELL.*
Shared_Includes/constants/zx_char_codes.inc Control codes ZX: .cursor_left=#08 (backspace), .right=#09 (== TAB!), .cursor_down=#0A, .cursor_up=#0B, .delete_left=#0C, .carriage_return=#0D, и т.п.
Shared_Includes/constants/dss_errors.z80 Строки ошибок: #01 Invalid function, #0E Unknown operation, #13 Access denied, #20 Operation not supported, #25 User abort, #27 Unexpected app termination
DSS/DSS_MAP.TXT Карта памяти ядра — #0400-#043F SBUF, #0440-#0BD1 KEYBOARD DRIVER
DSS/CHANGES.LOG Хронология; ECHOKEY cursor 2003-02-10, txt 40x32 cursor bug, OVR cursor square
DSS/KNOWN.BUG §3.3-3.4 — legacy ECHOKEY/PUTCHAR закомментированы, можно убирать; буферы #100/#80 без bounds-check

7.2. В Another/ (референсы, не собираются)

Файл Зачем
/Users/tolik/Documents/SP_Projects/ASM/Another/Docs/Sprinter_Programming.pdf p.22 «Прерывания» (вектор #FF, бит 0 порта #19), p.25-26 «Клавиатура» (пример K_SETUP layout-table, 9×16=144 байта на подтаблицу, Ctrl+Space layout switch), p.27 коды ошибок
/Users/tolik/Documents/SP_Projects/ASM/Another/Docs/Keyboard/KBD.TXT PS/2 host→keyboard команды (см. §6.5)
/Users/tolik/Documents/SP_Projects/ASM/Another/Docs/Keyboard/AT-Keyboard.pdf Scan Code Set 2 на 3 страницах
/Users/tolik/Documents/SP_Projects/ASM/Another/SPRINTER/TASM/Keybdrv.z80 Минималистский standalone-драйвер клавиатуры Дениса Паринова (517 строк): полная init-последовательность 9 байт WR0..WR5 на ComA=#19, чтение DataA=#18. Полезно при отладке железа отдельно от DSS
/Users/tolik/Documents/SP_Projects/ASM/Another/SPRINTER/TASM/Scantabl.z80, Scantabl2.z80 Альтернативные scancode→symbol таблицы (для сверки XLAT_T)
/Users/tolik/Documents/SP_Projects/ASM/Another/SPRINTER/SRC/cpm3-s/SCAN.PLM CP/M-3 Sprinter scan-логика на PL/M (третий независимый референс)
НЕ нужно: Another/scancode.inc NASM/x86 BIOS INT16h (Sergey Kiselev Micro-8088). Другая архитектура, пропустить

7.3. Внешние (нужно скачать)

  • Zilog Z80 SIO Technical Manual (UM008) — главы 3-4: полный layout WR0..WR7 / RR0..RR2. sp2000.pdf §9 явно отсылает туда («Ссылка на описание PIO, SIO и т.д. от Zilog»).
  • Zilog Z84C15 Product Specification — карта внутренних портов, CTC/PIO timing.

8. История изменений KEYINTER (git log)

git log --oneline -- DSS/KEYINTER.ASM:

  • 9235af1 RUS keys fix
  • f5eacf4 KEYINTER.ASM: russian chars fix
  • e334545 M_INT: RFI is CH-A ONLY (per Z80 SIO spec) — сохранять CH-A-only RFI семантику при правках ISR
  • 73c7ab5 K_SETUP show/hide cursor sub-fns
  • 12c0287 ECHOKEY scroll fix
  • eae2582 PutChar via BIOS LP_PR_LINE_DIR (#E0)
  • 23fa77a XLAT_T overflow + Ctrl+C
  • ff2f2b9 layout-switch combo (Ctrl+Space vs Shift+Ctrl) compile flag
  • 54b1e80 SIO experiments

При работе всегда смотреть git log -p DSS/KEYINTER.ASM | head -200 — часто старые комментарии R-номерами (R01, R02 ...) поясняют исторические правки.


9. Полезные команды на хост-системе

  • Сборка ядра: VS Code task Build SYSTEM.DOS (или [NEW] с инкрементом билда). Из терминала примерно:
    cd "/Users/tolik/Documents/SP_Projects/ASM/GIT/Estex DSS"
    sjasmplus --syntax=afw --fullpath -Wno-shortblock \
      --lst=Build/DSS-MAIN.LST --raw=Build/DSS/SYSTEM.DOS \
      --exp=Build/Variables.inc DSS/DSS-MAIN.ASM
    
  • Деплой в MAME: VS Code task Copy all files to MAME → запускает RUN/Image.sh (zsh, hdfmonkey).
  • Запуск MAME: VS Code task Run MAME.
  • Прочитать CP866-файл с правильной кодировкой:
    iconv -f CP866 -t UTF-8 DSS/KEYINTER.ASM | less
    
  • grep по бинарно-определяемым файлам (CP866 ломает heuristic):
    grep -anE "WAITKEY|KEYSCAN" DSS/KEYINTER.ASM
    
    Флаг -a форсит text-режим.
  • Просмотр с табами:
    sed -n '519,540p' DSS/KEYINTER.ASM | tr -d '\r' | cat -tv
    
  • Дамп диспетч-таблицы:
    sed -n '337,396p' DSS/DSS-MAIN.ASM
    

10. Стилевые соглашения

  • Кодировка CP866 (DOS Russian), переводы строк CRLF. Edit'ы должны их сохранять.
  • Отступы — табы. Комментарии справа от инструкции выровнены табами до колонки ;. При вставке новых комментариев-продолжений использовать 4 таба + ;.
  • Рамки секций — ;████...████ (символ █ = U+2588). Не ломать.
  • Стиль меток: метки целиком капсом (KEYSCAN, WAITKEY), вложенные через . (KEYSCAN.RESCAN, K_SETUP.ERROR, Cursor_Off.Flag).
  • Self-modifying code маркирован комментариями вроде ;R01, ;R13 с ревизионными номерами — НЕ удалять без причины (это история).
  • Хроноразметка [ ] DD/MM/YYYY / [x] для done — в комментариях.

10а. Работа с файлами CP866 + CRLF (практические рецепты)

Это главный источник тонких багов при редактировании. Прочитай эту секцию ДО первой правки.

10а.1. Что именно в CP866

  • Все DSS/*.asm, DSS/*.ASM, DSS/*.inc, DSS/*.Z80.
  • Все Shared_Includes/constants/*.inc и Shared_Includes/Docs/*.asm.
  • DSS/CHANGES.LOG, DSS/KNOWN.BUG, DSS/DSS_MAP.TXT.
  • Another/SPRINTER/..., Another/Docs/Keyboard/KBD.TXT.

PDF в Shared_Includes/Docs/sp2000.pdf и Another/Docs/*.pdf — отдельная история (текст внутри PDF в своей кодировке, см. pdftotext).

UTF-8 в этом проекте бывает только в *.md (вроде этого файла), *.json (VSCode-конфиги), *.sh, *.lua и .gitmodules.

10а.2. Байтовая карта CP866 (минимум, который надо помнить)

Байт(ы) Что
#80..#8F А..О (рус. заглавные, первая половина)
#90..#9F П..Я (рус. заглавные, вторая половина)
#A0..#AF а..п (рус. строчные, первая половина)
#B0..#B2 ░ ▒ ▓ (shading)
#B3..#DA псевдографика │ ─ ┌ ┐ ┘ └ ┤ ├ ┬ ┴ ┼ … и двойные ║ ═
#DB — full block, из которого строятся рамки ;██..██ в этом проекте
#DC..#DF ▄ ▌ ▐ ▀
#E0..#EF р..я (рус. строчные, вторая половина)
#F0, #F1 Ё ё
#FC
#FE

Важно:

  • = один байт #DB в CP866, а в UTF-8 это три байта E2 96 88. Поэтому строчка ;████████ длиной ~110 символов в CP866 занимает ~110 байт, а в UTF-8 — ~330 байт.
  • Если случайно конвертнуть файл в UTF-8 и забыть конвертнуть обратно, размер файла резко вырастает, выравнивание комментариев плывёт, а sjasmplus может выдать странные ошибки на строковых литералах.

10а.3. Просмотр и поиск

Просмотр через тулзу Read: русский текст может отображаться как заполнители или мусор, потому что Read декодирует как UTF-8. Полагаться на это для содержания комментариев нельзя — но для проверки кода (меток, инструкций Z80, hex-литералов) подходит.

Просмотр в человеческой форме — через Bash + iconv:

iconv -f CP866 -t UTF-8 DSS/KEYINTER.ASM | less
iconv -f CP866 -t UTF-8 DSS/KEYINTER.ASM | sed -n '519,540p'

Просмотр с табами/CRLF:

sed -n '519,540p' DSS/KEYINTER.ASM | iconv -f CP866 -t UTF-8 | cat -tev
# ^I = табы, $ = конец строки (CRLF будет показан как ^M$)

grep по ASCII-меткам-a обязательно:

grep -an "WAITKEY\|KEYSCAN\|K_SETUP" DSS/KEYINTER.ASM

Без -a grep с какого-то порога считает CP866 бинарным и молча выдаёт только имя файла.

grep по русским словам — через pipe iconv (номера строк сохраняются, т.к. количество переводов строк не меняется):

iconv -f CP866 -t UTF-8 DSS/KEYINTER.ASM | grep -an "клавиш"

Альтернатива: сконвертить запрос в CP866 и грепать байтами (но это крайне неудобно — pipe-метод лучше).

Полный hex-дамп строки (для разбора, что именно лежит в файле):

sed -n '519p' DSS/KEYINTER.ASM | xxd | head

10а.4. Редактирование — критическое

Главная проблема Edit-инструмента: он работает на байтовом уровне. Если файл в CP866, а old_string в твоей tool-call'е содержит русский текст в UTF-8, то байты НЕ совпадут и Edit упадёт с String not found. Если случайно new_string запишется UTF-8'ом в CP866-файл, файл ломается локально (на конкретной строке).

Безопасные паттерны:

  1. ASCII-only анкоры (90% случаев). Выбирай old_string так, чтобы он состоял из инструкций Z80, меток, hex-литералов и английских/латинских фрагментов. Кириллический хвост строки пройдёт «как есть» — Edit не трогает то, что не в old_string.

    old_string: " RST ToDSS\t\t\t; "
    

    Это сработает, даже если справа после ; идёт русский комментарий.

  2. Если кириллица обязана быть в old_string (например, надо заменить текст внутри русского комментария) — сначала сконвертировать фрагмент в CP866 через Bash:

    echo -n "максимальный номер диска" | iconv -f UTF-8 -t CP866 | xxd
    

    получишь байты, и можешь искать/заменять через sed или perl с LC_ALL=C (байтовый режим), а не через Edit.

  3. Удобный трюк: убедиться, что old_string уникально совпадает с ASCII-частью комментария или ID:

    old_string: " LD A,disk_num\t\t\t; "
    new_string: " LD H,device_num\t\t; "
    

    и оставить хвостовую кириллицу в покое.

  4. Не вставляй русский текст напрямую через Edit, если файл в CP866. Если требуется добавить русский комментарий, сделай это через Bash:

    echo -n "; новый комментарий" | iconv -f UTF-8 -t CP866 >> /tmp/append
    # затем вставляй байты через sed/perl
    

    Или согласись писать новые комментарии на английском/латинице (ASCII) — это безопаснее, и стиль файла это терпит.

10а.5. Сохранение CRLF

  • Файлы используют \r\n (Windows-стиль), не \n.
  • При вставке новых строк через Edit убедись, что в new_string есть \r перед \n. Если Edit-инструмент это съест и поставит только \n, файл получит смешанные line endings — sjasmplus съест, но git diff будет показывать всю окрестность как изменённую, и в Windows-редакторах текст рассыпется.
  • Альтернатива: после Edit-сессии нормализуй переводы строк:
    # Проверить:
    file DSS/KEYINTER.ASM
    # Должно быть: "with CRLF line terminators"
    # Если стало "with CRLF, LF line terminators" — есть смесь, чинить:
    perl -i -pe 's/\r?\n/\r\n/g' DSS/KEYINTER.ASM
    

10а.6. Проверка после правки (обязательная)

file DSS/KEYINTER.ASM
# Ожидается: "Non-ISO extended-ASCII text, with CRLF line terminators"
# НЕ должно быть "UTF-8 Unicode text" или "with LF line terminators"

# Проверка, что рамки █ не превратились в UTF-8 (3 байта):
grep -c $'\xDB\xDB\xDB' DSS/KEYINTER.ASM
# Должно быть положительное число (несколько штук). 0 = рамки сломаны.

# git diff с пониманием бинарного представления:
git diff DSS/KEYINTER.ASM | cat -v | head -50
# Cyrillic должен показываться как M-X M-X последовательности байт

10а.7. Откат, если что-то сломалось

# Один файл — мгновенный откат:
git checkout HEAD -- DSS/KEYINTER.ASM

# Если уже закоммитил битый файл — посмотреть предыдущую версию:
git show HEAD~1:DSS/KEYINTER.ASM | iconv -f CP866 -t UTF-8 | less

10а.8. Конвертация всего файла «туда-обратно» (последнее средство)

Если правок очень много и проще править в UTF-8:

# 1. Конвертация в UTF-8 + переход на LF (так удобнее редактировать):
iconv -f CP866 -t UTF-8 DSS/KEYINTER.ASM | tr -d '\r' > /tmp/KEYINTER.utf8

# 2. Правки в /tmp/KEYINTER.utf8 любым инструментом (Edit/sed/perl).

# 3. Обратно в CP866 + восстановление CRLF:
iconv -f UTF-8 -t CP866 /tmp/KEYINTER.utf8 | perl -pe 's/\r?\n/\r\n/g' > DSS/KEYINTER.ASM

# 4. Проверка:
file DSS/KEYINTER.ASM
git diff --stat DSS/KEYINTER.ASM

Риск: если в файле есть байты, не имеющие пары CP866↔UTF-8 (теоретически вряд ли в этом проекте, но возможно при кривых правках в истории), iconv ругнётся с illegal input sequence. Тогда iconv -c пропустит проблемные байты — но они потеряются. Безопаснее в этом случае работать ASCII-only анкорами через Edit.

10а.9. VS Code как замена тулинга

Если хочется визуальной правки:

  • VS Code: правый-нижний угол → Reopen with Encoding → Cyrillic (CP866).
  • Для постоянной ассоциации .asm/.ASM/.inc с CP866 добавить в .vscode/settings.json:
    {
      "[asm]": { "files.encoding": "cp866" },
      "files.eol": "\r\n"
    }
    
  • При сохранении проверь нижнюю панель — индикатор должен показывать «CP866» и «CRLF», а не «UTF-8» / «LF».

10а.10. Чек-лист: «изменения сохранят кодировку»

  • file <изменённый.asm>Non-ISO extended-ASCII text, with CRLF line terminators
  • grep -c $'\xDB\xDB\xDB' <изменённый.asm> > 0 (если в файле были рамки)
  • git diff <изменённый.asm> | wc -l разумного размера (не в 3× больше ожидаемого — иначе UTF-8 раздувание)
  • Сборка Build SYSTEM.DOS проходит без warning'ов вроде «invalid character in string literal»
  • Открыть результат в VS Code с CP866 — комментарии читаются по-русски

11. Что важно помнить, прежде чем менять KEYINTER

  1. Файл подключается INCLUDE'ом в DSS-MAIN.ASM:402, сборка идёт как монолит. Все символы видны глобально.
  2. Состояние клавиатуры — RAM-only: не персистится в CMOS. Любая настройка (язык, локи, sound flags, ASCII-таблицы) теряется на reset/power-off, если её не сохранить через AUTOEXEC.BAT.
  3. Клавиатура и мышь делят окно INT 38h — мышь идёт после KEYSCAN. Долгая работа в KEYSCAN задерживает мышь.
  4. SIO Ch_A FIFO = 3 байта.RESCAN должен дренировать его полностью за один заход (текущая реализация это делает через JP .RESCAN на строке 628).
  5. KEYBOARD_INT_ENABLED=0 — текущий билд поллит, не использует SIO INT. Если хочется перейти на прерывания, нужно переключить флаг, убедиться что KEYBOARD_INIT пишет WR1=#19, и проверить что обработчик RFI в DSS-MAIN.ASM корректно настроен (Z80 SIO Ch_A only).
  6. При правке HEAD/HOST убедиться, что RES 6,(HL) сохранён — это обеспечивает wrap modulo 64. Если изменишь размер SBUF, поменяй и маску (бит-логика RES 6 работает только для 64 = #40).
  7. При правке XLAT_T — длина 144 байта (Size), bounds-check CP XLAT_T.Size на строке 860. Если расширяешь, проверь, что не натыкаешься на следующую область (CURRENT DIR NAME BUFFER по DSS_MAP.TXT идёт от #1343).
  8. Двойственность D в записях буфера: D[7] = «keystroke flag», D[6:0] = позиционный код 0..#5A. INPCODE/CONVERT ставит бит 7 через SET 7,D — это видимый паттерн.
  9. При правке ASCII-таблиц — каждая ровно 90 байт, проверяется ASSERT .Size = Size. Если меняешь размер, меняй и Size EQU 90 на строке 1286.
  10. Звук работает в обход BIOS — прямой OUT (SP_SND.Beeper),A (порт #FE бит 4). Это запрещает звуковые эффекты, если процесс держит порт #FE для других целей (ZX-режим). Учитывать при модификациях BEEP/FULL_BF/LANG_X.

12. Чек-лист первого подхода

Сначала открой и прочитай (именно в таком порядке):

  1. DSS/KEYINTER.ASM целиком (1371 строка).
  2. DSS/DSS-MAIN.ASM: строки 187-213 (RST_10), 275-310 (INTx38_Handler), 337-396 (DSS_API_TABLE), 402 (INCLUDE), 423 (BANKTBL), 567-600 (boot-флоу).
  3. DSS/defines.inc (короткий).
  4. Shared_Includes/Docs/DSS_1.71_Functions.asm, раздел 6.
  5. Shared_Includes/constants/dss_equ.inc, приложение А (позиционные коды).
  6. Another/Docs/Sprinter_Programming.pdf p.22, p.25-26 (страницы 25-26 содержат пример K_SETUP layout-table).
  7. sp2000.pdf §9.4 (PDF, страница ~25-26).
  8. Скачать Zilog Z80 SIO Technical Manual (UM008), если будешь трогать KEYBOARD_INIT или KBD_Receiver_Overrun.

Только после этого формулируй гипотезу и план правок.


13. Контрольные вопросы перед коммитом

  • Тестовая сборка Build SYSTEM.DOS прошла без ошибок?
  • Кодировка файлов сохранена CP866/CRLF? (file DSS/KEYINTER.ASM должен показать Non-ISO extended-ASCII text, with CRLF line terminators)
  • Табы и выравнивание комментариев не нарушены?
  • Если изменён публичный API (#30..#37) — обновлён ли Shared_Includes/Docs/DSS_1.71_Functions.asm?
  • Если изменены константы (Dss.K_SETUP.*, ASCII-таблица size, коды ошибок) — обновлён ли dss_equ.inc?
  • Если изменена инициализация SIO — проверено, что коммит совместим с e334545 "RFI is CH-A ONLY"?
  • MAME-прогон (Run MAME / Copy all files to MAME): клавиатура реагирует, переключение языка работает, INS/CAPS/NUM/SCRL ведут себя ожидаемо?
  • Echo (ECHOKEY) рисует курсор корректно во всех текстовых режимах (40×32, 80×32)?
  • XLAT_T не вылез за Size=144?

Удачи.