484b18d10c
libc/kbd: kbd_raw_open/close/down — сырой PS/2-канал клавиатуры с held-state (битовая карта _kbdraw_down[512], EXT-клавиши +256). Пока raw открыт, кадровый IRQ-трамплин перехватывает байт SIO у DSS и декодирует make/break (0xF0/0xE0-префиксы) сам. FIFO вычерпывается В ЦИКЛЕ (приёмный буфер SIO 3 байта; пачка break-кодов при одновременном отпускании иначе теряется → залипание клавиши). Буфер W2-трамплина поднят 224→288 Б под выросший обработчик. libc/conio: kbd_mod_state() — live-состояние модификаторов (ESTEX CTRLKEY $33h), Shift/Ctrl/Alt/Lock прямо сейчас, KBD_MOD_* маска. libbgi: gfx_blit_cols(x,y,img,flip) + _bgi_blit_cols_raw — блит column-major спрайта вертикальным accel-проходом, бесплатный горизонтальный флип (sstride<0), клип по экрану. Для персонажей. libbgi/atlas_load: восстанавливать W3 ДО записи a->count (atlas_t в --bank памяти резолвится через W3; count оставался мусором). tests/kbdraw — тест raw-клавиатуры; size-baseline +kbdraw. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
446 lines
32 KiB
Markdown
446 lines
32 KiB
Markdown
# libc — справочник API (2026-07-06)
|
||
|
||
Сводка по заголовкам: сигнатура + одна строка + особенности ABI.
|
||
Детали дизайна: docs/libc-headers.md (контракт затенения SDCC),
|
||
docs/file-buffering-design.md (FILE*), docs/solid_c_compatibility.md.
|
||
|
||
Общие соглашения:
|
||
- ошибки: возврат -1/NULL/EOF + `errno` (код DSS as-is, см. errno.h);
|
||
- SDCC `__sdcccall(1)`: 1-й аргумент HL (8-битный — A), 2-й — DE,
|
||
остальные на стеке; **int/указатель возвращается в DE**;
|
||
- строки для BIOS-вызовов (rst 8) должны лежать в #4000–#BFFF;
|
||
- стек при любых ESTEX/BIOS-вызовах — в W2 (обеспечено crt0).
|
||
|
||
## <stdio.h> — полная замена SDCC (контракт: printf-семейство из z80.lib)
|
||
|
||
Из SDCC z80.lib: `printf sprintf vprintf vsprintf`.
|
||
|
||
Консоль (ESTEX, без атрибутов — быстрый путь; цветной вывод — conio):
|
||
| Сигнатура | Описание |
|
||
|---|---|
|
||
| `int putchar(int c)` | символ через PUTCHAR $5B; '\n'→CR LF |
|
||
| `int getchar(void)` | блокирующий WAITKEY $30, ASCII |
|
||
| `char puts(const char *s)` | строка + '\n' (посимвольно через putchar) |
|
||
| `char *gets(char *buf)` | строка с консоли, без контроля длины |
|
||
| `void dec8/dec16/dec32(v)` | десятичная печать без ведущих нулей |
|
||
| `void hex8/hex16/hex32(v)` | hex-печать фиксированной ширины |
|
||
|
||
FILE* (буферизованный, вариант B+ — единый ленивый буфер BUFSIZ=512
|
||
на чтение/запись с автопереключением; таблица `OPEN_MAX=8` слотов;
|
||
exit() сбрасывает всё через atexit; **ошибки записи отложенные —
|
||
проверять fclose**):
|
||
| Сигнатура | Описание |
|
||
|---|---|
|
||
| `FILE *fopen(path, mode)` | "r/w/a" + '+', 'b/t' игнорируются |
|
||
| `FILE *fdopen(fd, mode)` | завернуть готовый fd (закрывать fclose!) |
|
||
| `FILE *freopen(path, mode, fp)` | переоткрыть тот же FILE* |
|
||
| `int fclose(FILE*)` / `void fclosall(void)` | сброс+закрытие / все потоки |
|
||
| `int fflush(FILE*)` | сброс записи / откат readahead; NULL = все |
|
||
| `int fgetc/fputc(...)` | горячий путь на asm; getc/putc — макро-алиасы |
|
||
| `char *fgets(buf, n, fp)` | до '\n' (сохраняется); блочный LDI-сканер |
|
||
| `int fputs(s, fp)` | без '\n'; через fwrite |
|
||
| `size_t fread/fwrite(p, sz, n, fp)` | блоки ≥ 512 идут мимо буфера |
|
||
| `int ungetc(c, fp)` | 1 байт putback (и на stdin) |
|
||
| `int fseek(fp, off, whence)` / `long ftell(fp)` | ftell без побочных эффектов |
|
||
| `void rewind(fp)` | fseek(0) + сброс EOF/ERROR |
|
||
| `int fgetpos/fsetpos(fp, &pos)` | fpos_t = long |
|
||
| `int feof/ferror(fp)`, `void clearerr(fp)` | флаги потока |
|
||
| `int fprintf/vfprintf(fp, fmt, ...)` | vsprintf в статический буфер 256 |
|
||
| `int scanf/fscanf/sscanf(...)` | %d %u %x %o %c %s, `l`, ширина, %*, %% |
|
||
| `int rename(old, new)` | ESTEX RENAME $10 |
|
||
|
||
`stdin/stdout/stderr` — консольные псевдопотоки (fd 0/-1/-2), не
|
||
буферизуются; freopen на них меняет только FILE*-операции (printf
|
||
идёт в ESTEX напрямую).
|
||
|
||
## <stdlib.h> — include_next + добавки
|
||
|
||
Из SDCC: malloc/free/calloc/realloc (heap в W2), atoi/atol/strtol/
|
||
strtoul, rand/srand, qsort/bsearch, abs/labs, div/ldiv, exit-типы.
|
||
Наше: `int16_t min(a,b)`, `int16_t max(a,b)` (функции, как в Solid-C).
|
||
|
||
## <string.h> — include_next + добавки
|
||
|
||
Из SDCC: mem*/str* полностью. Наше: `char *strlwr/strupr(char *s)` —
|
||
in-place регистр, латиница + кириллица CP866.
|
||
|
||
## <time.h> — полная замена (struct tm в SDCC-ABI, __TIME_UNSIGNED=1)
|
||
|
||
| Сигнатура | Описание |
|
||
|---|---|
|
||
| `void getdatetime(datetime_t*)` | RTC как есть (ESTEX SYSTIME $21) |
|
||
| `int setdatetime(const datetime_t*)` | установка RTC ($22) |
|
||
| `time_t time(time_t*)` | Unix-эпоха из RTC |
|
||
| `mktime/gmtime/localtime/asctime/ctime` | POSIX поверх RTC (без TZ) |
|
||
|
||
`datetime_t`: day/month/year(полный)/hour/minute/second/dow (1=Вс).
|
||
|
||
## <unistd.h>, <fcntl.h> — fd-уровень (манипуляторы DSS)
|
||
|
||
| Сигнатура | Описание |
|
||
|---|---|
|
||
| `int open(path, flags)` | O_RDONLY/WRONLY/RDWR + O_CREAT/TRUNC/EXCL/APPEND (ESTEX $11/$0A/$0B) |
|
||
| `int creat(path, mode)` | open(W|CREAT|TRUNC); mode игнорируется |
|
||
| `int read/write(fd, buf, n)` | ESTEX $13/$14. **Квирк WRITE: DE-возврат ненадёжен, успех = CF=0&A=0** (см. memory/estex_write_de_quirk) |
|
||
| `int close(fd)` | ESTEX $12 |
|
||
| `long lseek(fd, off, whence)` | 32-битная позиция (MOVE_FP $15) |
|
||
| `int unlink(path)` | удалить (DELETE $0E) |
|
||
| `int isatty(fd)` | fd <= 0 (файловые манипуляторы DSS с 1) |
|
||
| `int mkdir/rmdir/chdir(path)` | ESTEX $1B/$1C/$1D |
|
||
| `char *getcwd(buf, size)` | буфер 256 байт, size игнорируется |
|
||
| `void sleep(seconds)` | калиброванный busy-wait (см. ниже) |
|
||
| `void delayms(uint16_t ms)` | то же, гранулярность — миллисекунды (см. ниже) |
|
||
|
||
**Лимит: 8 одновременных манипуляторов**; 9-й OPEN вешает DSS —
|
||
libc отказывает сама (EMFILE, предохранитель _fd_guard).
|
||
|
||
**`sleep()` (2026-07-07, редизайн)**: старая версия считала halt-
|
||
пробуждения (50 = 1 c), предполагая, что КАЖДОЕ прерывание — кадровый
|
||
тик; с CBL/клавиатурой на векторе 0xFF это уже не так (CBL прерывает
|
||
намного чаще кадра — sleep() возвращался бы раньше срока). Теперь:
|
||
лениво, один раз калибруется кратковременным `irq_install()` против
|
||
РЕАЛЬНОГО кадрового тика (трамплин зовёт хук только на настоящих
|
||
кадровых прерываниях — клавиатура/CBL уходят в свои ветки раньше),
|
||
считая, сколько итераций тесного цикла умещается в один КАДР (не в
|
||
секунду — калибровка на секунду переполняла `uint16_t`, был баг:
|
||
`sleep(5)` отрабатывал быстрее секунды, найден пользователем на
|
||
реальном прогоне и исправлен). `sleep(seconds)` — вложенный цикл:
|
||
внешний по секундам, внутренний ровно 50 раз калиброванный busy-wait —
|
||
без единого прерывания и БЕЗ умножения/32-битной арифметики (по духу
|
||
`docs/samples/delayms.asm`, который тоже калибрует на 1 мс, а не на
|
||
1 с). Погрешность ~5-10% (калибровочный и рабочий циклы не тактово-
|
||
идентичны). Fallback на старое "50 halt" поведение, если фрейм-хук уже
|
||
занят другим `irq_install()`-клиентом (EBUSY).
|
||
**Cтоит дороже по размеру** (~+580 Б) — тянет весь модуль `irq`
|
||
(install/remove/трамплин/IM2-таблицу), даже если программа больше
|
||
ничего из irq не использует.
|
||
|
||
**`delayms(ms)`** — тот же движок (`libc/time/_sleep_calib.c`), общий с
|
||
`sleep()`: калибровка одна на двоих (первый вызов ЛЮБОЙ из функций
|
||
калибрует, вторая просто использует готовое). Производная величина
|
||
"итераций на 1 мс" — одно 16-битное деление (`__divuint`, НЕ
|
||
`__mullong`) на константу 20, вычисляется один раз при калибровке;
|
||
`delayms(ms)` — простой цикл `ms` раз, без умножения вообще. Fallback
|
||
при EBUSY — грубый (одно кадровое `halt`), точной альтернативы для
|
||
миллисекундной гранулярности без калибровки нет.
|
||
|
||
**Квирк общий для sleep()/delayms()**: калибровка сама стоит ~20-40 мс
|
||
(синхронизация на границу кадра + сам замер) — ПЕРВЫЙ вызов ЛЮБОЙ из
|
||
двух функций в программе превысит запрошенное время на эту величину;
|
||
для `delayms()` с маленьким `ms` это заметно (`delayms(5)` на первом
|
||
вызове может растянуться на ~25-45 мс). Все последующие вызовы точны.
|
||
Оставлено как есть по решению пользователя — не стали усложнять API
|
||
отдельным calibrate()-примитивом.
|
||
|
||
## <errno.h>
|
||
|
||
`errno` (int), коды = коды DSS (EOK..EUNKERR) + POSIX-имена
|
||
(ENOENT/EBADF/EMFILE/…) + алиасы Solid-C (EZERO/EINVFNC/ENOFILE/…).
|
||
`const char *strerror(int)`, `void perror(const char*)`.
|
||
|
||
## <dos.h> — DOS-слой Solid-C
|
||
|
||
| Сигнатура | Описание |
|
||
|---|---|
|
||
| `void getdate/gettime(&d)` | struct date/time (Turbo-C; ti_hund=0) |
|
||
| `int setdate/settime(&d)` | RMW полного datetime |
|
||
| `uint8_t getdisk(void)` | текущий диск, 0=A (ESTEX $02) |
|
||
| `int setdisk(uint8_t)` | смена диска; возврат = число дисков ($01) |
|
||
| `int absread/abswrite(disk, sect, cnt, buf)` | секторы ЛОГИЧЕСКОГО диска (BIOS $55/$56); буфер в #4000–#BFFF; abswrite минует ФС! |
|
||
|
||
## <dir.h>
|
||
|
||
`int ffirst(pattern, ffblk_t*, attrib)` / `int fnext(ffblk_t*)` —
|
||
поиск по шаблону (ESTEX $19/$1A). Квирк: "."/".." находятся только
|
||
итерацией "*.*" (memory/estex_ffirst_dotdot). FA_*-атрибуты.
|
||
|
||
## <sys/stat.h>
|
||
|
||
`int stat(path, struct stat*)` / `int fstat(fd, ...)` — st_mode
|
||
(S_ISREG/S_ISDIR), st_size, st_mtime (Unix-эпоха).
|
||
|
||
## <conio.h> — текстовый экран с атрибутами (Turbo-C стиль)
|
||
|
||
Клавиатура: `kbhit getch getche getkey` (+KEY_* коды позиций),
|
||
`char *cgets(buf)`.
|
||
`uint16_t kbd_mod_state(void)` — live-состояние модификаторов ПРЯМО
|
||
СЕЙЧАС (ESTEX CTRLKEY $33h, не событие из буфера — держится, пока
|
||
клавиша реально зажата); (mode<<8)|shift, расшифровка KBD_MOD_*.
|
||
Покрывает только Shift/Ctrl/Alt/Rus-Lat/Lock — для обычных клавиш
|
||
(стрелки и т.п.) live-state у ESTEX нет, см. `<kbd_raw.h>`.
|
||
Вывод с атрибутом: `putch cputs cprintf` (~10× медленнее stdio-пути;
|
||
'\n' НЕ транслируется — писать "\r\n").
|
||
Атрибуты: `textcolor textbackground textattr`, `set/get_text_attr`,
|
||
COLOR_*-enum, `COLOR(fg,bg)`, COLOR_BLINK; `set/get_putch_raw_mode`.
|
||
Экран: `clrscr clrscr_attr gotoxy home() wherex wherey wherexy scroll
|
||
wrchar rdchar`; режимы `gettextmode/settextmode` (0x02=40×32,
|
||
0x03=80×32).
|
||
Порты/IRQ: `inp outp enable() disable()`.
|
||
Текстовая палитра: `text_pal_load/set_color/get/get_color/reset`
|
||
(план 0..3 → страница BIOS 4..7).
|
||
|
||
## <bios/text.h> — быстрый BIOS-вывод (rst 8, place-based)
|
||
|
||
`bios_set_place/get_place`, `bios_write[attr][_until|_stop]`,
|
||
`bios_fillchar/fillattr/fillcharattr`, `bios_clearwin[_ch]`,
|
||
`bios_scrollwin`. Строка s — в #4000–#BFFF; place продвигается.
|
||
|
||
## <gfx.h> — графика, mode-agnostic API (BGI_GFX); живёт в libbgi/include
|
||
|
||
Рисование (putpixel/line/bar/circle/…) вынесено в BGI — см. `<graphics.h>`
|
||
и driver-библиотеки lib/bgi256.lib / lib/bgi16.lib (выбор режима линковкой:
|
||
`sprinter-cc --gfx 256` / `--gfx 16`). В gfx.h остались только функции
|
||
БЕЗ BGI-аналога (mode-agnostic, живут в libbgi/common/, .rel в обеих
|
||
driver-библиотеках):
|
||
|
||
Setup: `gfx_init(mode,page)→prev`, `gfx_done(prev)`.
|
||
Страницы/банк: `gfx_set/get_visible_page`, `gfx_set/get_draw_page`
|
||
(double buffering), `gfx_set/get_bank` (GFX_BANK_NORMAL 0x50 /
|
||
NOSHADOW 0x54 / TRANSPARENT 0x58 / SPRITE 0x5C — аппаратные подрежимы
|
||
записи, действуют на ВСЕ примитивы; см. docs/sprite-api-design.md).
|
||
Блиттинг (Фаза B, 2026-07-11): `gfx_blit(x,y,img)`,
|
||
`gfx_blit_part(x,y,img,sx,sy,w,h)` (атлас), `gfx_heal(x,y,w,h)`
|
||
(восстановить фон из ОЗУ-копии; стирание спрайтов без save-буфера);
|
||
img — getimage-формат, буфер вне W3; клиппинг по экрану есть.
|
||
|
||
`gfx_wait_vsync()` (2026-07-07, редизайн): ждёт переход бита 5 порта
|
||
0xFE из 1 в 0 — реальное аппаратное состояние луча (Y>256 → начало
|
||
кадра, см. MAME sprinter.cpp kbd_fe_r), а не прерывание — поэтому не
|
||
путается с клавиатурой/CBL/CTC, деляющими вектор 0xFF. Бит доступен
|
||
только пока включён `cbl_mode()` (bit7 порта 0x004E) — если приложение
|
||
уже играет через `cbl_open()`, бит достаётся бесплатно; иначе
|
||
`gfx_wait_vsync()` лениво занимает bit7 "немым" кодом частоты через
|
||
`_cbl_port_ref()`/`_cbl_port_unref()` (см. `<cbl.h>`, `_cbl_port.c`) —
|
||
разделяемое владение портом 0x004E, безопасное при любом порядке
|
||
использования с реальным CBL-звуком. Фолбэк на одно кадровое
|
||
прерывание (`halt`), если бит не ведёт себя как ожидается за разумное
|
||
число попыток.
|
||
Шрифт: `gfx_load_default_font`, `gfx_set_font(ptr)` (interleaved
|
||
font[row*256+char]) — грузится лениво при первом использовании BGI-текста.
|
||
Палитра: `gfx_pal_load/set/get/get_color/reset` (страницы 0..3).
|
||
|
||
Константы режимов: `GFX_MODE_320x256x256` (0x81), `GFX_MODE_640x256x16`
|
||
(0x82); размеры `GFX_WIDTH/HEIGHT` (320/256), `GFX_WIDTH_16/HEIGHT_16`
|
||
(640/256). Рисование через `<graphics.h>` (BGI).
|
||
|
||
## <graphics.h> — Turbo-C BGI (функц. совместимость), Фаза 1, режим 256
|
||
|
||
Слой поверх `<gfx.h>` со «текущим» цветом/позицией. Режим задаётся
|
||
driver-либой на линковке: `sprinter-cc --gfx 256` (→ 320×256×256; 16 —
|
||
позже). API mode-agnostic: код не меняется при смене режима.
|
||
Setup: `initgraph()` (без аргументов — режим фиксирован либой; грузит
|
||
EGA-палитру 0..15, цвет=WHITE, фон=BLACK, CP=(0,0)),
|
||
`closegraph()`, `graphresult()`, `cleardevice()`.
|
||
Границы/цвет: `getmaxx/getmaxy` (319/255), `getmaxcolor` (255),
|
||
`setcolor/getcolor`, `setbkcolor/getbkcolor`. Константы BLACK..WHITE.
|
||
Точки: `putpixel(x,y,c)`, `getpixel(x,y)`.
|
||
Позиция/линии: `moveto/moverel/getx/gety`, `lineto/linerel` (двигают
|
||
CP), `line(x1,y1,x2,y2)` (не двигает).
|
||
Фигуры: `rectangle` (контур), `bar` (заливка стилем), `circle`.
|
||
Дуги (Ф2a): `arc`, `ellipse(x,y,st,end,xr,yr)`, `drawpoly(n,pts)`.
|
||
Текст 8×8: `outtextxy(x,y,s)`, `outtext(s)` (двигает CP).
|
||
Заливки (Ф2b): `setfillstyle(pattern,color)`/`getfillsettings` (10
|
||
паттернов Borland: SOLID/EMPTY/LINE/…/HATCH/XHATCH/…), `bar3d`,
|
||
`fillpoly(n,pts)`, `fillellipse(x,y,xr,yr)`.
|
||
Заливка областей (Ф2c): `floodfill(x,y,border)` (медленно, но верно),
|
||
`pieslice(x,y,st,end,r)`, `sector(x,y,st,end,xr,yr)`.
|
||
Образы (Ф2d): `imagesize/getimage/putimage` (COPY/XOR/OR/AND/NOT_PUT;
|
||
формат буфера: uint16 w,h + w*h байт). COPY_PUT и getimage — через
|
||
accel block-copy (2026-07-11, Фаза A sprite-api-design), COPY с
|
||
клиппингом и текущим банком; XOR/OR/AND/NOT — per-pixel.
|
||
Спрайты (2026-07-11, Фаза B sprite-api-design): `putsprite(x,y,img)` —
|
||
блит getimage-буфера с аппаратной прозрачностью (0xFF =
|
||
GFX_TRANSPARENT не пишется) банком GFX_BANK_SPRITE (0x5C, фон в
|
||
ОЗУ-копии цел); `movesprite(ox,oy,x,y,img)` — heal старой позиции +
|
||
putsprite новой (save-буфер не нужен). Низкий уровень в <gfx.h>:
|
||
`gfx_blit(x,y,img)`, `gfx_blit_part(x,y,img,sx,sy,w,h)` (атлас
|
||
кадров), `gfx_heal(x,y,w,h)` (восстановление фона из ОЗУ-копии),
|
||
константы GFX_BANK_NORMAL/NOSHADOW/TRANSPARENT/SPRITE. Правило: фон
|
||
рисовать банком 0x50, спрайты/оверлеи — putsprite/0x5C; буферы
|
||
образов — вне W3 (< 0xC000). Тесты: tests/sprites, tests/gfxbanks,
|
||
tests/bgi_img.
|
||
Стиль линий (Ф2d): `setlinestyle(style,upattern,thick)`/`getlinesettings`
|
||
(SOLID/DOTTED/CENTER/DASHED/USERBIT + NORM/THICK) — на line/rectangle/
|
||
drawpoly.
|
||
Стиль текста (Ф2d): `settextstyle(font,dir,size)`/`gettextsettings`,
|
||
`textwidth`/`textheight` — масштаб 1..10, HORIZ/VERT, прозрачный фон
|
||
(только DEFAULT_FONT 8×8).
|
||
Реализация: libbgi/common/*.c (mode-agnostic math + BGI public API +
|
||
BGI_GFX) + libbgi/bgi256/*.c (256-цветные leaf'ы) → lib/bgi256.lib
|
||
(Фаза 2 добавит libbgi/bgi16/*.c → lib/bgi16.lib; common .rel одни и
|
||
те же в обоих архивах). Leaf'ы — реальные реализации (БЕЗ обёрток:
|
||
putpixel/getpixel полностью inline, _bgi_plot_raw/_bgi_hspan_raw/...
|
||
поглощают акселераторный asm). Пакетные примитивы — одна W3-скобка
|
||
на примитив; тригонометрия/эллипсы целочисленные (Q7/isqrt, БЕЗ 32-бит).
|
||
Cross-lib: libbgi всегда линкуется с libc; _cbl_port_ref/unref
|
||
объявлены extern в libbgi/_bgi.h (сверять с libc/cbl/_cbl.h).
|
||
Ф2d (осталось): setviewport/клиппинг, settextjustify, setaspectratio —
|
||
см. docs/TODO.md.
|
||
|
||
## <irq.h> — user-ISR кадрового прерывания (IM 2)
|
||
|
||
| Сигнатура | Описание |
|
||
|---|---|
|
||
| `int irq_install(isr_t h)` | h зовётся ~50 Гц на кадровых прерываниях; DSS-обработчик чейнится всегда (клавиатура/SYSTIME/мышь живы). 0 / -1+errno (EBUSY повтор, EINVAL — код не в W2: только tiny/big) |
|
||
| `void irq_remove(void)` | вернуть таблицу DSS; идемпотентно; висит на atexit |
|
||
| `int irq_ctc_install(h, div2, div3)` | периодический таймер CTC (вектор 0x06): f = 875000/(div2×div3), div 0=256; пресет кадра IRQ_CTC_VSYNC_DIV2/3 (112×160, ~48.8 Гц); независим от кадрового; трамплин завершает RETI |
|
||
| `void irq_ctc_remove(void)` | глушит CTC (обязательно; atexit подстрахует) |
|
||
| `IRQ_DISABLE()/IRQ_ENABLE()` | di/ei — скобки для чтения shared-переменных из main |
|
||
|
||
Handler'у нельзя: ESTEX/BIOS-вызовы, gfx_*/своп окон, акселератор,
|
||
banked-функции; только volatile-глобалы и быстрая работа (<1 мс).
|
||
|
||
## <kbd_raw.h> — эксклюзивный raw-канал клавиатуры (вектор 0xFF, свой путь)
|
||
|
||
Мотивация — applications/PoP/docs/PORT_PLAN.md §2: ESTEX
|
||
kbhit/getch/getkey/kbd_mod_state — событийные, без held-state для
|
||
обычных (не модификаторных) клавиш. Decode make/break (PS/2 Scan Code
|
||
Set 2: `0xF0` — префикс отпускания, `0xE0` — префикс расширенной
|
||
клавиши) прямо в трамплине прерывания (libc/irq/_irq_tramp.c), по
|
||
прецеденту приватного пути CBL (не чейнится к DSS, пока открыт).
|
||
|
||
| Сигнатура | Описание |
|
||
|---|---|
|
||
| `int kbd_raw_open(void)` | включить: с этого момента ВЕСЬ поток клавиатурных байт достаётся нам, DSS его не видит. 0 / -1+errno (EBUSY повтор) |
|
||
| `void kbd_raw_close(void)` | выключить, вернуть клавиатуру DSS; идемпотентно; висит на atexit |
|
||
| `uint8_t kbd_raw_down(uint16_t code)` | зажата ли code ПРЯМО СЕЙЧАС (0/1); code вне 0..511 — 0 |
|
||
| `KBD_EXT` | ИЛИ-флаг кода: клавиша была расширенной (0xE0-префикс на проводе) |
|
||
| `KBD_UP/DOWN/LEFT/RIGHT/SPACE/ENTER/ESC/LSHIFT/RSHIFT` | позиционные коды PS/2 Set 2 — LEFT/ESC/UP подтверждены (см. ниже); остальные — по стандарту, не перепроверены поштучно |
|
||
|
||
**ИСПРАВЛЕНО 2026-07-15 (был неверный вывод, ниже — то, что реально
|
||
подтвердилось):** изначально скриншот-тестирование (снимок экрана
|
||
после `press_key('up')`) не показывало видимого эффекта на
|
||
UP/DOWN/RIGHT — из этого сделан ОШИБОЧНЫЙ вывод «скрипт не может их
|
||
нажать». На самом деле причина была в неудачном таймінге снимков
|
||
относительно дуги прыжка, а не в отсутствии сигнала. Подтверждено
|
||
брейкпоинтом в отладчике MAME (adress уровня приложения на входе в
|
||
код `jumping=1` в `applications/PoP/poc/poc.c`): `press_key('up')`
|
||
ЧЕСТНО доходит до raw-декодера и триггерит код прыжка — брейкпоинт
|
||
сработал ровно один раз за одно удержание клавиши (это же заодно
|
||
подтвердило фикс двойного триггера через `up_prev` edge-detect в
|
||
`poc.c`). Так что скриптовый `press_key` для UP/DOWN/RIGHT РАБОТАЕТ
|
||
корректно так же, как для LEFT — предыдущая запись про «ограничение
|
||
именно в скриптовом инжекте» была неверной, оставлена в истории
|
||
git как урок: при повторных «нет эффекта» на скриншотах — проверять
|
||
брейкпоинтом/watchpoint'ом на конкретный адрес кода, не полагаться
|
||
только на визуальный снимок с произвольным таймингом.
|
||
|
||
**ГЛАВНОЕ СЛЕДСТВИЕ:** пока `kbd_raw_open()` активен, `kbhit/getch/getkey/
|
||
kbd_mod_state` НЕ получают новых событий (в т.ч. CTRLKEY подряд отдаёт
|
||
то же самое, что было на момент открытия — резидентный обработчик DSS,
|
||
от которого зависит его live-state, во время raw не выполняется).
|
||
ESC для выхода — через `kbd_raw_down(KBD_ESC)`, не через DSS.
|
||
|
||
Верификация (tests/kbdraw, MAME, 2026-07-15): `press_key` в мосте
|
||
MAME дёргает ОБЕ клавиатуры (PC ms_naturl + ZX-матрица `IO_LINE`)
|
||
одновременно — наблюдался паразитный незатухающий бит от ZX-пути
|
||
(обычный код без EXT-префикса, застревал в DOWN) — не воспроизводится
|
||
на реальном сценарии (только PC/AT-клавиатура, без матрицы, см.
|
||
applications/PoP/docs/PORT_PLAN.md §2 — пользователь подтвердил, что
|
||
матрица на Sprinter давно не используется). Для будущих MAME-тестов
|
||
этой функции — бить только по `:kbd:ms_naturl:*`, не через
|
||
удобный `press_key` (или перепроверить точную семантику полей моста).
|
||
|
||
## <cbl.h> — потоковый звук CBL/COVOX (вектор 0xFF, свой ISR)
|
||
|
||
Без собственного кольца (2026-07-07): у CBL уже есть аппаратный буфер
|
||
256 Б (2×128, двойная буферизация на стороне железа — см. официальную
|
||
доку "5.3 COVOX-Blaster"); библиотека просто зовёт `fill()` приложения
|
||
ИЗ ISR, а оно само пропихивает данные (откуда угодно) через
|
||
`cbl_push_otir/accel` — без промежуточной копии.
|
||
|
||
| Сигнатура | Описание |
|
||
|---|---|
|
||
| `int cbl_open(freq_code, fmt, pump_mode, underrun_mode, fill)` | включить CBL (`0x90\|fmt\|freq`), зарегистрировать callback; 0 / -1+errno (EBUSY повтор, EINVAL — freq/fmt/pump_mode/underrun_mode плохие или OTIR+16-бит, ENOMEM — буфер тишины) |
|
||
| `void cbl_close(void)` | выключить CBL, снять хук, освободить буфер тишины (если был); висит на atexit |
|
||
| `void cbl_push_otir(const void *src, uint16_t n)` | пропихнуть n байт через `otir` в порт 0x4F; звать ИЗ fill() |
|
||
| `void cbl_push_accel(const void *src, uint16_t n)` | то же акселератором (страница EMM 0xFD@0xC000); единственный путь для 16-бит |
|
||
| `uint16_t cbl_requests(void)` | счётчик запросов блока ISR (~fs/(сэмплов в блоке) в секунду) |
|
||
| `uint16_t cbl_underruns(void)` | счётчик недоливов (`fill` вернул 0 или не задан) |
|
||
| `CBL_FREQ_7K8 .. CBL_FREQ_109K` | коды частоты (биты 3..0 control-порта) |
|
||
| `CBL_FMT_MONO8/MONO16/STEREO8/STEREO16` | формат (биты 5/6 control-порта); блок 128 Б (8-бит) или 256 Б (16-бит), не зависит от моно/стерео; тишина 0x80 (8-бит) / 0x0000 (16-бит, знаковый) |
|
||
| `CBL_PUMP_OTIR / CBL_PUMP_ACCEL` | способ выдачи — см. ниже |
|
||
| `CBL_UNDERRUN_APP / CBL_UNDERRUN_SILENCE` | поведение при недоливе — см. ниже |
|
||
|
||
`typedef int (*cbl_fill_fn)(uint16_t n);` — callback, зовётся ИЗ ISR за
|
||
очередным блоком (n = `_cbl_block`, 128/256). **Обязан быть быстрым**
|
||
— никаких ESTEX/BIOS/gfx-вызовов (тот же констрейнт, что у
|
||
`irq_install()`-хендлера); вернуть ненулевое, если реально пропихнул n
|
||
байт. Диск (read()) читать из fill() НЕЛЬЗЯ — см. tests/cblstream,
|
||
где под это заведено кольцо уровня приложения.
|
||
|
||
Два насоса (3-й параметр `cbl_open`):
|
||
- **OTIR** (`_cbl_pump_otir`) — `cbl_push_otir()` в порт 0x4F; базовый.
|
||
**НЕ умеет 16-бит** — `cbl_open(..., MONO16/STEREO16, CBL_PUMP_OTIR,
|
||
...)` вернёт EINVAL: по исходнику MAME (sprinter.cpp) порт данных
|
||
ВСЕГДА кладёт байт как есть в один слот, не собирая пару байт в
|
||
16-бит значение и не сверяясь с 16-бит флагом вообще.
|
||
- **ACCEL** (`_cbl_pump_accel`) — `cbl_push_accel()` через акселератор
|
||
в спец-страницу EMM 0xFD, замапленную в окно W3 на 0xC000 (см.
|
||
docs/converted/accel_r.txt, Forum.txt); размер блока патчится SMC
|
||
(`LD D,D` + immediate `LD A,n` + `LD L,L` — тот же паттерн, что и в
|
||
libc/gfx/_gfx_hfill256.c); единственный путь для 16-бит.
|
||
**Verified в MAME 2026-07-07** (tests/cbltest, вся accel-половина
|
||
матрицы прошла без ошибок/underrun).
|
||
|
||
Поведение при недоливе (4-й параметр `cbl_open`):
|
||
- **CBL_UNDERRUN_APP** (по умолчанию, 0) — не забота библиотеки, буфер
|
||
тишины НЕ аллоцируется, в CBL доигрывает то, что уже лежало в его
|
||
аппаратном буфере.
|
||
- **CBL_UNDERRUN_SILENCE** (1) — насос сам пропихивает тишину; буфер
|
||
(128/256 Б по формату) аллоцируется malloc'ом ВНУТРИ `cbl_open()`
|
||
только в этом режиме.
|
||
`cbl_underruns()` считает недоливы в обоих режимах — диагностика,
|
||
поведение не меняет.
|
||
|
||
Приватное прерывание, к DSS не чейнится; бит 7 порта 0xFE (запрос
|
||
блока) читается только пока `cbl_open` не закрыт (при выключенном CBL
|
||
бит всегда 1 — MAME-квирк).
|
||
|
||
**Разделяемое владение портом 0x004E** (2026-07-07): бит 5 порта 0xFE
|
||
(позиция луча — см. `<gfx.h>` `gfx_wait_vsync()`) доступен только пока
|
||
включён bit7 порта 0x004E, независимо от того, играет ли реальный
|
||
звук. `_cbl_port_ref()`/`_cbl_port_unref()` (internal, `_cbl_port.c`)
|
||
дают gfx-модулю занять bit7 "немым" кодом частоты (не заводящим таймер
|
||
CBL — без звука/прерываний), не мешая реальной `cbl_open()`-сессии,
|
||
если она уже идёт (и наоборот — `cbl_close()` возвращает "немой" режим
|
||
вместо полного выключения порта, если gfx его ещё держит).
|
||
|
||
## <palette.h> — низкий уровень (BIOS $A4/$A6)
|
||
|
||
`pal_load pal_get pal_set_color pal_get_color` (страница 0..7,
|
||
записи B,G,R,0), `pal_reset(type)` / `pal_reset_at(type,page,graph)`;
|
||
PAL_GRAPH/PAL_SINCLAIR/PAL_CGA.
|
||
|
||
## <mouse.h> — драйвер RST 30h
|
||
|
||
`mouse_init show hide refresh read(mouse_state_t*) goto bounds_x/y
|
||
text_cursor load_cursor/get_cursor(mouse_cursor_t*) set_sensitivity
|
||
get_sensitivity_x/y video_mode_changed`. Sensitivity = делитель
|
||
(меньше = быстрее). Solid-C алиасы ms_* включены.
|
||
|
||
## <sprinter.h> — платформа
|
||
|
||
Константы портов (PORT_PAGE_W0..W3, PORT_RGADR, PORT_RGMOD), номера
|
||
всех ESTEX-функций (ESTEX_*), BIOS EMM ($C0..$C7); `__sfr`-доступ и
|
||
inline `sprinter_page_w0..w3(page)`; ENV: `getenv putenv sysenv`.
|
||
|
||
## <sprinter_mem.h> — EMM-страницы и банковый I/O
|
||
|
||
`mem_alloc_pages(n)→blk_id, mem_free_block, mem_get_page(blk,idx),
|
||
mem_info(&total,&free)` (реализации _bios/_estex; макро-выбор
|
||
MEM_MANAGE_MODE_*). HOME-резидентный доступ к чужим страницам:
|
||
`bank_load_byte/store_byte/read/write` (своп W3 внутри; *_w1 —
|
||
вариант через окно W1).
|
||
|
||
## <sprinter_exit.h>
|
||
|
||
`atexit` (LIFO, 8 слотов), `exit` (хендлеры+сброс FILE), `_exit`.
|
||
|
||
## <sprinter_compat.h> / <sprinter_solid.h>
|
||
|
||
Типы (BYTE/BOOL/WORD/uint/FD/f_point), TRUE/FALSE/OK/ERROR,
|
||
`setmem movmem` (порядок аргументов!), `strerr seek tell ltell
|
||
remove _ffirst _setargv abort()`, isascii. `<sprinter_solid.h>` —
|
||
зонтичный: один include для портирования Solid-C программ.
|