# 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). ## — полная замена 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 напрямую). ## — 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). ## — include_next + добавки Из SDCC: mem*/str* полностью. Наше: `char *strlwr/strupr(char *s)` — in-place регистр, латиница + кириллица CP866. ## — полная замена (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=Вс). ## , — 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)` | 50 halt/с по кадровому IRQ | **Лимит: 8 одновременных манипуляторов**; 9-й OPEN вешает DSS — libc отказывает сама (EMFILE, предохранитель _fd_guard). ## `errno` (int), коды = коды DSS (EOK..EUNKERR) + POSIX-имена (ENOENT/EBADF/EMFILE/…) + алиасы Solid-C (EZERO/EINVFNC/ENOFILE/…). `const char *strerror(int)`, `void perror(const char*)`. ## — 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 минует ФС! | ## `int ffirst(pattern, ffblk_t*, attrib)` / `int fnext(ffblk_t*)` — поиск по шаблону (ESTEX $19/$1A). Квирк: "."/".." находятся только итерацией "*.*" (memory/estex_ffirst_dotdot). FA_*-атрибуты. ## `int stat(path, struct stat*)` / `int fstat(fd, ...)` — st_mode (S_ISREG/S_ISDIR), st_size, st_mtime (Unix-эпоха). ## — текстовый экран с атрибутами (Turbo-C стиль) Клавиатура: `kbhit getch getche getkey` (+KEY_* коды позиций), `char *cgets(buf)`. Вывод с атрибутом: `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-вывод (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 продвигается. ## — графика (0x81: 320×256×256; 0x82: 640×256×16) 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` (0x50..0x5F, 0x58 = FF- прозрачность), `gfx_wait_vsync()`. Примитивы (суффикс _256 / _16): `clear putpixel hline vline line rect fill_rect` — через акселератор (hline/vline burst до 256 байт; в 0x82 vline через RMW). Координаты int, клиппинг по краям. Текст: `gfx_putchar256/16, gfx_text256/16` (8×8, fg/bg; в 0x82 x — чётный), `gfx_load_default_font`, `gfx_set_font(ptr)` (interleaved font[row*256+char]). Палитра: `gfx_pal_load/set/get/get_color/reset` (страницы 0..3). ## — 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 мс). ## — потоковый звук 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-квирк). ## — низкий уровень (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. ## — драйвер 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_* включены. ## — платформа Константы портов (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`. ## — 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). ## `atexit` (LIFO, 8 слотов), `exit` (хендлеры+сброс FILE), `_exit`. ## / Типы (BYTE/BOOL/WORD/uint/FD/f_point), TRUE/FALSE/OK/ERROR, `setmem movmem` (порядок аргументов!), `strerr seek tell ltell remove _ffirst _setargv abort()`, isascii. `` — зонтичный: один include для портирования Solid-C программ.