# Промт для новой сессии: изучение и модификация клавиатурной подсистемы DSS ## Задача Изучить и при необходимости модифицировать алгоритмы работы с клавиатурой, реализованные в [DSS/KEYINTER.ASM](DSS/KEYINTER.ASM) — модуле, который включается в ядро DSS через `INCLUDE` из [DSS/DSS-MAIN.ASM](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/](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](.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.sh](RUN/Image.sh) → `hdfmonkey 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](DSS/VERSION.INC) + LUA в [Shared_Includes/LUA/Functions.lua](Shared_Includes/LUA/Functions.lua). --- ## 2. Архитектура DSS и место KEYINTER ### 2.1. Точка входа ядра — [DSS/DSS-MAIN.ASM](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 → инициализация мыши → инициализация дисков → `EI` → `CLEAR_BUFFER_AND_INIT_PROC`. - **Карта памяти** ([DSS/DSS_MAP.TXT](DSS/DSS_MAP.TXT)): - `#0000..#0038` — RESTARTS - `#0200..#03FF` — `DSS_API_TABLE` - `#0400..#043F` — **SBUF** (буфер клавиатуры, 64 байта) - `#0440..#0BD1` — код KEYINTER (~1936 байт) - `#0BD2..#0EF5` — драйвер экрана (для совместного использования с `Cursor_On/Off`) ### 2.2. Соседние модули - [DSS/API.asm](DSS/API.asm) — агрегатор-INCLUDE для всех `DSS/API/*.asm`. - [DSS/FS_Module.asm](DSS/FS_Module.asm) — `FS/FAT.asm` + `FS/CDFS.ASM`. - [DSS/DOS_Proc.asm](DSS/DOS_Proc.asm) — DOS-уровневые помощники (имена файлов, маски, `SET_FM`, `OPENDSK` и т.п.). - [DSS/Procedures.asm](DSS/Procedures.asm) — общие хелперы (`PUTCHAR` строка 103, `PUTCHAR.NO_SCROLL` строка 104, `MK_TIME` и др.). - [DSS/DRV-MAIN.ASM](DSS/DRV-MAIN.ASM) — отдельная страница драйверов (DISP/ENT-блок в `DSS-MAIN.ASM:695-699`), своя таблица RST по адресам `#A0000..#A0038`. Клавиатура там НЕ живёт. - [DSS/drivers/Input/MOUSE.ASM](DSS/drivers/Input/MOUSE.ASM) — мышь; делит окно INT 38h с KEYINTER (сначала `KEYSCAN`, потом мышь). - [DSS/defines.inc](DSS/defines.inc) — compile-time флаги (см. §5). - [DSS/DSS_MACROSES.Z80](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 `#02` — `INS_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. Префиксы: - `#F0` → `FULL_BF.F0_KEY`: устанавить `FLAG_F0`, `JP .RESCAN`. - `#E0` → `FULL_BF.E0_KEY`: устанавить `FLAG_E0`, `JP .RESCAN`. - `#E1` → если `USE_E1_SCANCODE=1` устанавить `FLAG_E1`, иначе игнорировать. 6. Обычный байт + `FLAG_F0` → `UN_KEY` (657-664): `XLAT` → `UNSHIFT` модификаторов, `UNCODE := HL` (последний отпущенный). 7. Обычный байт без `FLAG_F0` → `XLAT` → `SHIFTS` → сброс `FLAG_E0/E1` → при `CHANGE_LANG_CTRL_SHIFT=1` опционально установить `CTRL_SHIFT` → `INPCODE` → `PUTSYM` → `JP .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](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](Shared_Includes/Docs/BIOS%20functions.asm), строки 1-60. - **Подфункции**: верхний байт BC (т.е. `B`) — индекс подфункции; константа обычно собрана как `EQU subfn*256 + .Func`. Пример: `Dss.K_SETUP.SetSoundVars EQU 2*256+#36`. --- ## 5. Compile-time флаги ([DSS/defines.inc](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`/`EDIT` → `DSS_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.Data` — **PS/2 клавиатура DAT_A** | | **`#19`** | `Z84.SIO.Ch_A.Ctrl` — **PS/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](DSS/DSS-MAIN.ASM)), читать порт `#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](/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](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](Shared_Includes/Docs/BIOS%20functions.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](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](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](Shared_Includes/constants/BIOS_equ.inc) | `BIOS.*` функции (см. §7.1 ниже) | | [Shared_Includes/constants/SP2000.inc](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](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](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](DSS/DSS_MAP.TXT) | Карта памяти ядра — #0400-#043F SBUF, #0440-#0BD1 KEYBOARD DRIVER | | [DSS/CHANGES.LOG](DSS/CHANGES.LOG) | Хронология; ECHOKEY cursor 2003-02-10, txt 40x32 cursor bug, OVR cursor square | | [DSS/KNOWN.BUG](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](/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](/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](/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](/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](/Users/tolik/Documents/SP_Projects/ASM/Another/SPRINTER/TASM/Scantabl.z80), [Scantabl2.z80](/Users/tolik/Documents/SP_Projects/ASM/Another/SPRINTER/TASM/Scantabl2.z80) | Альтернативные scancode→symbol таблицы (для сверки `XLAT_T`) | | [/Users/tolik/Documents/SP_Projects/ASM/Another/SPRINTER/SRC/cpm3-s/SCAN.PLM](/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](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: ```sh iconv -f CP866 -t UTF-8 DSS/KEYINTER.ASM | less iconv -f CP866 -t UTF-8 DSS/KEYINTER.ASM | sed -n '519,540p' ``` **Просмотр с табами/CRLF**: ```sh sed -n '519,540p' DSS/KEYINTER.ASM | iconv -f CP866 -t UTF-8 | cat -tev # ^I = табы, $ = конец строки (CRLF будет показан как ^M$) ``` **grep по ASCII-меткам** — `-a` обязательно: ```sh grep -an "WAITKEY\|KEYSCAN\|K_SETUP" DSS/KEYINTER.ASM ``` Без `-a` grep с какого-то порога считает CP866 бинарным и молча выдаёт только имя файла. **grep по русским словам** — через pipe iconv (номера строк сохраняются, т.к. количество переводов строк не меняется): ```sh iconv -f CP866 -t UTF-8 DSS/KEYINTER.ASM | grep -an "клавиш" ``` Альтернатива: сконвертить запрос в CP866 и грепать байтами (но это крайне неудобно — pipe-метод лучше). **Полный hex-дамп строки** (для разбора, что именно лежит в файле): ```sh 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: ```sh 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: ```sh 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-сессии нормализуй переводы строк: ```sh # Проверить: 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. Проверка после правки (обязательная) ```sh 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. Откат, если что-то сломалось ```sh # Один файл — мгновенный откат: git checkout HEAD -- DSS/KEYINTER.ASM # Если уже закоммитил битый файл — посмотреть предыдущую версию: git show HEAD~1:DSS/KEYINTER.ASM | iconv -f CP866 -t UTF-8 | less ``` ### 10а.8. Конвертация всего файла «туда-обратно» (последнее средство) Если правок очень много и проще править в UTF-8: ```sh # 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`: ```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](DSS/KEYINTER.ASM) целиком (1371 строка). 2. [DSS/DSS-MAIN.ASM](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](DSS/defines.inc) (короткий). 4. [Shared_Includes/Docs/DSS_1.71_Functions.asm](Shared_Includes/Docs/DSS_1.71_Functions.asm), раздел 6. 5. [Shared_Includes/constants/dss_equ.inc](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](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`? Удачи.