Z88dk
z88dk newlib vs classic architecture for targets, CRT m4 driver instantiation, serial/character FILE* wiring, disk fcntl (asm_target_open, open_max, FCB vs FatFs), mixed multi-CPU trees, and target_io / ticks verification. Use when migrating targets, adding serial or disk drivers, debugging fopen/open link errors, dual-stack file I/O, CP/M stdio devices, or reviewing CRT/config lists. Complements z88dk-tooling (measure) and extended-usage (8085 codegen).From its SKILL.md
npx -y skills add feilipu/8085-skills --skill z88dkAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 3 stars3 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
SKILL.md
11.6 KB, ~3.4k tokens by cl100k_base, as published. Nobody here has run it
z88dk target I/O architecture (newlib + classic)
General lessons from migrating hardware targets and enabling newlib CP/M file I/O. Measurement stays in z88dk-tooling. 8085 codegen stays in extended-usage / opcode-reference.
Assume a built tree with bin/ on PATH and ZCCCFG → lib/config.
1. Two library worlds (do not blur)
| World | Home | CPUs | Typical product |
|---|---|---|---|
| Classic | libsrc/target/<name>/ (historic fcntl/stdio/gfx) + libsrc/classic/ | Z80, IXIY, Z180, 8080, 8085, gbz80, … | *_clib.lib, cpm8085_clib.lib, … |
| Newlib | Was libsrc/newlib/target/<name>/; migrated → libsrc/target/<name>/ beside classic | Z80-class (-clib=new / sdcc_ix / sdcc_iy) | lib/clibs/{sccz80,sdcc_ix}/<target>.lib |
Hard rules
- Do not merge classic and newlib stdio cores or fcntl
openowners in one link without a designed bridge. - Prefer sharing device / thin driver layers, not cores.
- CLIB selects newlib; machine SUBTYPEs usually stay classic-owned (esp. CP/M’s 150+ machines).
- After a path move, put the target name in
MIGRATED_TARGETSinlibsrc/newlib/Makefileso builds use../target/<name>.
Mixed tree hazard: when newlib Z80+ sources land under libsrc/target/cpm/ next to classic multi-CPU code, isolation is list ownership (*.lst), never broad globs. Classic 8080/8085 images must never pull newlib objects (Z80-only opcodes / calling conventions).
2. Serial / character FILE* instantiation (newlib)
Layers
stdio (printf / FILE*)
→ console_01 (line-edited terminals; default on many CRTs)
→ character_00 (thin byte streams; good for multi-port + RDR/PUN/LST)
→ device (UART/SIO/ACIA/ASCI/HBIOS/BDOS CON…)
| Layer | Typical path |
|---|---|
| stdio core | libsrc/newlib/stdio |
| character_00 | libsrc/newlib/drivers/character/ |
| console_01 | libsrc/newlib/drivers/terminal/console_01/ |
| Target terminals | libsrc/target/<t>/driver/terminal/*.m4 + .asm |
| Devices | libsrc/target/<t>/device/… |
Defaults: most hardware CRTs still instantiate full console_01 / rc_01_* terminals. Thin character_00 is the additive multi-port pattern (keep m4_file_dup / optional crt_driver_instantiation.asm.m4).
CRT m4 wiring (where FILEs are born)
Startup *_crt_N.asm.m4 includes driver m4 macros inside:
clib_instantiate_begin.m4
m4_<driver>(_stdin, …)
m4_<driver>(_stdout, …)
m4_file_dup(_stderr, 0x80, __i_fcntl_fdstruct_1) ; often dup of stdout
… extra ports …
clib_instantiate_end.m4 ; builds fdtbl + FILE freelist + stdio heap
Each static driver m4 typically:
- Allocates a FILE + FDSTRUCT on the stdio heap sections.
- Pushes an entry into the fd table body.
- Chains a heap block header (
__i_fcntl_heap_N).
Multi-port and dups
| Pattern | Mechanism |
|---|---|
| Second console / teletype | Second input+output terminal pair → ttyin / ttyout; ttyerr = m4_file_dup of ttyout (same idea as stderr) |
| stderr | Almost always a dup of stdout’s FDSTRUCT (flag 0x80) |
| Extra static slots | m4_file_absent or more drivers |
Declared in newlib stdio.h even when a CRT does not instantiate them: stdrdr, stdpun, stdlst, ttyin, ttyout, ttyerr. Missing instantiation ≠ missing declaration — apps that reference an uninstantiated FILE* will fail at link or runtime.
CP/M character model (portable apps vs implementations)
Physical BDOS units (what drivers usually call):
| Unit | BDOS | Typical newlib FILE* |
|---|---|---|
| CON | 1/2/6/… | stdin / stdout / stderr |
| RDR | 3 | stdrdr |
| PUN | 4 | stdpun |
| LST | 5 | stdlst |
Logical names (CRT, TTY, LPT, PTR, PTP, BAT, U*) are selected by IOBYTE (page-0 $0003, BDOS 7/8). BIOS maps logical → physical UART/device.
| Audience | What to wire |
|---|---|
| CP/M implementation (e.g. CP/M-IDE) | CRT maps stdin/tty* to real ports; shell seeds IOBYTE; ASM BIOS interprets IOBYTE |
CP/M application (+cpm -clib=new) | BDOS only; FILE* + optional logical-name helpers; no UART registers |
Do not assume fopen("TTY:") is how implementations work — FILE selection + IOBYTE seed* is the real dual-port pattern.
Hybrid classic+newlib consoles (rc2014-8085 lesson)
When a CRT mixes classic fgetc_cons/fputc_cons with newlib-style startup:
- FILE init flags must match classic expectations (
18/20=_IOSYSTEM|_IOREAD/_IOWRITE). - Wrong flags (
19/21with spurious_IOUNGETC) made firstgetcharreturn NUL. - Hybrid clib lists must not pull full newlib fcntl/stdio/threads.
3. Disk / fcntl instantiation (newlib)
Open path
open / creat / fopen
→ asm_vopen
→ asm_target_open_p1 ; validate path; return EXTRA bytes for FDSTRUCT
→ heap_alloc(sizeof header + EXTRA) from __stdio_heap
→ asm_target_open_p2 ; fill FDSTRUCT; install JP to driver
Target must provide asm_target_open_p1 and asm_target_open_p2 (e.g. CP/M FCB driver cpm_01_file). If missing → link error:
undefined symbol: asm_target_open_p1
That is the classic newlib “stdio disk I/O missing” symptom (#3022-class), not a compiler bug.
CRT knobs that make or break open / fopen
Set in target crt_config.inc (TAR__clib_*):
| Knob | Role | Failure mode if wrong |
|---|---|---|
open_max | Size of fd table (static FDs + dynamic opens) | open_max=0 → only static fds; open() ENFILE / no room |
stdio_heap_size | Heap for FDSTRUCTs (FCB driver ~192 B each incl. 128 B sector buf) | Too small → heap_alloc fails on open |
fopen_max | Max FILE structures | Must be > static FILE count or freelist stays empty → fopen EMFILE even when open works |
Rule of thumb for CP/M-class newlib CRTs with 6 static streams (stdin…stdlst) + user files:
open_max = 16stdio_heap_size = 1024fopen_max = 10(or any value greater than static FILE count)
Dual-stack policy (when both exist)
| API | Backend |
|---|---|
Unprefixed open / read / write / lseek / close | Host / OS file driver (e.g. CP/M BDOS FCB) |
ChaN f_* | FatFs + target diskio (raw media) |
printf / console FILE* | Character/terminal drivers |
f_*is never an alias for FCB fcntl. Volumes stay independent.- Plain
+cpm -clib=new: BDOS FCB only is enough — no FatFs, no physicaldiskio. - Hardware
-subtype=cpm: FCB by default; optional-lffdual-stack.
One open owner per binary: do not mix classic libsrc/target/cpm/fcntl objects with newlib cpm_01_file in the same link.
Library list / rebuild traps
-
Driver must appear in the target
library/*_sccz80.lstchain (often viadriver/driver.lst). -
Newlib
Makefileoften depends only onconfig_private.inc— lst/source adds do not always rebuild. Force:rm -f lib/clibs/sccz80/<target>.lib lib/clibs/sdcc_ix/<target>.lib make -C libsrc/newlib <target> -
Prove the symbol is in the lib:
z88dk-z80nm lib/clibs/sccz80/<target>.lib | rg 'asm_target_open|cpm_01_file' -
Prove the app linked it:
rg 'cpm_01_file|asm_target_open|__fcntl_fdtbl_size' app.map
4. Testing I/O (test/suites/target_io)
Shared serial + disk suite for z88dk-ticks.
| File | Role |
|---|---|
io_tests.c | printf/scanf + creat/write/read/lseek/close/multi-fd |
fcntl_native.c | Native open/creat/… (CP/M BDOS / newlib FCB) |
fcntl_host.c + ticks_host_fcntl.asm | Host SYSCALL files (targets without OS fcntl) |
Makefile | Per-product recipes |
Design rules
- Shared tests call only
tio_*(io_port.h) — backends swap. - Classic CP/M breadth: default subtype only (
+cpmz80 /+cpm -clib=8085). Do not fan out to 150+ machine subtypes in this suite. - Newlib gates: plain
+cpm -clib=new(Z80) and hardware+… -subtype=cpm -clib=newwhere dual-stack FCB applies. Newlib CP/M is not an 8085 product — 8085 stays classic default CLIB. - Extend serial when CRTs expose more streams: RDR/PUN/LST (
stdrdr/stdpun/stdlst), latertty*if instantiated. - Extend disk when drivers claim flags: keep lseek (SET/END + overwrite); add
fopen/fread/fwritefor newlib stdio path; optional O_TRUNC/O_APPEND if implemented. - FatFs
f_*is a separate optional gate on hardware packages — not required for plain+cpm.
Run pattern
make -C test/suites/target_io # all recipes
make -C test/suites/target_io test_rc2014_cpm.com
# scanf tests need piped input (Makefile uses SCANF_INPUT)
CP/M outputs need .com for ticks CP/M mode; some newlib links produce *_CODE.bin that must be copied to .com.
What “green” means
- Suite:
N run, N passed, 0 failed - Map proof for newlib disk: driver +
__fcntl_fdtbl_size/open_max/ heap as expected - Classic default Z80 + 8085 still pass after mixed-tree moves (isolation)
5. Config / CRT pipelines (quick map)
| Pipeline | When | Outputs |
|---|---|---|
| Config m4 | library build | config_*_{private,public}.inc, config_*.h |
| CRT m4 | each zcc +target link | expand *_crt.asm.m4 + startup + drivers |
- Modes:
CFG_ASM_DEF/CFG_ASM_PUB/CFG_C_DEF. - zcc maps startup →
__STARTUP, pragmas →M4__*,-Itarget home +src/m4. - Edit only the CLIB lines that point at newlib CRT/lib paths when migrating; leave classic SUBTYPE lines alone unless intentional.
6. Agent checklist (new serial or disk work)
Serial
- Driver class:
character_00vsconsole_01(match peers) - CRT m4 instantiates FILE* + FDSTRUCT; dups for err streams
- Public
stdio.hnames match what the CRT actually builds - Multi-port: second triple uses
tty*+m4_file_dupfor err - Hybrid classic console: FILE flags and list isolation
Disk
-
asm_target_open_p1/_p2in target lib (nm proof) -
open_max,stdio_heap_size,fopen_maxsized for static + dynamic use - One
openowner; dual-stack docs if FatFs also present - Forced lib rebuild after lst changes; app map shows driver
Test
-
target_iorecipe for the product (native vs host fcntl) - Classic CP/M: default subtype only unless a specific machine is in scope
- Extend serial/disk cases only where the CRT/driver supports them
- ticks CPU model (
-m8085when relevant)
Related
- Host measurement / ticks / maps / copt: z88dk-tooling
- 8085 coding / stack rules: extended-usage
- Opcode map: opcode-reference
- Upstream: https://github.com/z88dk/z88dk
- Example dual-stack notes:
libsrc/_DEVELOPMENT/EXAMPLES/z80/stdio_target/readme.md - Suite:
test/suites/target_io/
What ships with it
Read from the repository
Just SKILL.md. No reference files, no scripts.