527d4a6a18
libc/mem/:
• Split bank_io.c into bank_io_w3.c (existing W3 helpers, base 0xC000,
port 0xE2) and bank_io_w1.c (new mirror through W1, base 0x4000,
port 0xA2). Two .rel files so DCE picks only the needed group:
a W3-only user pulls ~70 bytes instead of all 134. W1 variants are
`--memory tiny`-only (any other mode runs code from W1 or uses W1
for the banked-code segment, swapping it crashes).
• mem_alloc.c: add CF=err checks for mem_free_block and mem_get_page
(were silently ignored), per-function docstrings on alloc/free/
get_page/info, drop the confused "wait wrong order" comment in
mem_info. Header sprinter_mem.h gets matching per-function doc.
libc/stdio/:
• Add hex_print.c (hex8/hex16/hex32, ~26 bytes) and dec_print.c
(dec8/dec16/dec32, ~170 bytes) ported from solid-c STDLIB.ASM.
Replaces the printf("%u"/"%X") wrappers in solid_helpers.c that
dragged in the 3-5 KB printf machinery.
- hex* use the classic cp 10 / sbc 0x69 / daa nibble→ASCII trick;
hex8 self-calls for the high nibble, hex16/hex32 tail-call hex8.
- dec32 is the master routine; dec8/dec16 jump into shared entry
points (__dec_entry3 / __dec_entry5). 32-bit subtract-power-of-10
keeps the high 16 bits in HL alt (shadow set).
- DISCOVERY: ESTEX PUTCHAR ($5B) on our Sprinter build preserves
the main register set + IX but CLOBBERS the shadow set
(BC'/DE'/HL'). solid-c's original code assumed otherwise and
garbled output for values ≥ 6 digits. Fix: save/restore HL alt
around the RST 10 in _dec_emit_or_skip. Documented in
memory/estex_putchar_abi.md.
• file.c: drop stdaux/stdprn (no Sprinter printer API), change
stdin/stdout/stderr fd markers to 0/-1/-2 (positive fds clash with
ESTEX OPEN return values), add TODO header pointing at v2 buffered
FILE rewrite (see docs/TODO.md for the Solid-C reference struct).
bin/sprinter-cc:
• --memory big and --memory huge now always use crt0_banked.s (was:
only with --bank flags), matching docs/memory_modes_implemented.md.
When the user has no --bank flags, generate a tiny stub with
`const uint8_t n_banks = 0;` and assemble bank.s for _bank_pages.
Without this fix, openenv with --memory big could not see the
estex_file_handle symbol exported by crt0_banked.
examples/openenv:
• Add usage of estex_file_handle to confirm the crt0_banked startup-
info is reachable. Local extern decl — keeps the symbol out of
sprinter.h since it only exists in big/huge builds.
examples/dec_test:
• New regression test covering hex8/16/32 and dec8/16/32 across the
interesting boundary values.
.gitignore: add .kilo/ (editor session cache).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
138 lines
4.4 KiB
C
138 lines
4.4 KiB
C
/*
|
|
* mem_alloc_pages / mem_free_block / mem_get_page / mem_info — ESTEX EMM
|
|
* wrappers for explicit 16 KB-page allocation.
|
|
*
|
|
* ESTEX $3C INFOMEM → HL=total pages, BC=free pages
|
|
* ESTEX $3D GETMEM B=npages → A=block id, CF=err
|
|
* ESTEX $3E FREEMEM A=block id → CF=err
|
|
* BIOS $C4 EMM_GETPAGE A=blk, B=idx → A=physical page CF=err
|
|
*
|
|
* Pattern: every RST 10h / RST 8 is bracketed with push/pop IX because
|
|
* ESTEX/BIOS clobber it and the C caller uses it as a frame pointer.
|
|
*/
|
|
|
|
#include <stdint.h>
|
|
#include <sprinter_mem.h>
|
|
|
|
/*
|
|
* Allocate `n` contiguous 16-KB physical pages from the EMM pool.
|
|
*
|
|
* in: n — 1..255 (number of pages requested).
|
|
* out: blk_id (1..255) on success; 0 on failure with errno set.
|
|
*
|
|
* The returned block id is opaque — pass it to mem_get_page() to obtain
|
|
* each physical-page number, and to mem_free_block() when done. Block
|
|
* ids start at 1; id 0 is reserved as the "allocation failed" sentinel.
|
|
*/
|
|
uint8_t mem_alloc_pages(uint8_t n) __naked
|
|
{
|
|
(void)n;
|
|
__asm
|
|
;; SDCC single-uint8 arg → A on entry; ESTEX GETMEM wants n in B.
|
|
push ix
|
|
ld b, a
|
|
ld c, #0x3D ; ESTEX GETMEM
|
|
rst #0x10
|
|
pop ix
|
|
jr c, _alloc_fail
|
|
ret ; CF=0 → A = blk_id, return as uint8 in A
|
|
_alloc_fail:
|
|
call __errno_set ; CF=1 → A = ESTEX errcode
|
|
xor a, a ; return 0 = failure sentinel
|
|
ret
|
|
__endasm;
|
|
}
|
|
|
|
/*
|
|
* Release a block previously returned by mem_alloc_pages().
|
|
*
|
|
* in: blk_id (1..255).
|
|
* out: void; on ESTEX error errno is set (e.g. EINVAL for unknown id).
|
|
*
|
|
* Idempotent guarantees are NOT provided — freeing the same block twice
|
|
* sets errno on the second call. Caller is responsible for tracking
|
|
* ownership.
|
|
*/
|
|
void mem_free_block(uint8_t blk_id) __naked
|
|
{
|
|
(void)blk_id;
|
|
__asm
|
|
;; SDCC single-uint8 arg → A on entry.
|
|
push ix
|
|
ld c, #0x3E ; ESTEX FREEMEM
|
|
rst #0x10
|
|
pop ix
|
|
ret nc ; CF=0 → success
|
|
jp __errno_set ; CF=1 → A = ESTEX errcode; tail-call helper
|
|
__endasm;
|
|
}
|
|
|
|
/*
|
|
* Translate a (block, page-index) pair into a physical 16-KB page number,
|
|
* suitable for OUT to PORT_PAGE_W1/W2/W3 or for bank_*() helpers.
|
|
*
|
|
* in: blk_id — id returned by mem_alloc_pages().
|
|
* idx — 0..(n-1), where n was the count passed to alloc.
|
|
* out: physical page number (1..255) on success;
|
|
* 0 on failure with errno set (invalid block or idx out of range).
|
|
*/
|
|
uint8_t mem_get_page(uint8_t blk_id, uint8_t idx) __naked
|
|
{
|
|
(void)blk_id; (void)idx;
|
|
__asm
|
|
;; 2-arg uint8/uint8: blk_id → A, idx → L.
|
|
push ix
|
|
ld b, l ; BIOS wants idx in B
|
|
;; A still has blk_id
|
|
ld c, #0xC4 ; BIOS EMM_GETPAGE
|
|
rst #0x08
|
|
pop ix
|
|
ret nc ; CF=0 → A = phys page (return value)
|
|
;; CF=1 → A = errcode; set errno, return 0 as sentinel.
|
|
call __errno_set
|
|
xor a, a
|
|
ret
|
|
__endasm;
|
|
}
|
|
|
|
/*
|
|
* Query the EMM allocator about its current state.
|
|
*
|
|
* *total ← number of 16-KB physical pages installed in the system
|
|
* *free_pages ← number currently available for allocation
|
|
*
|
|
* Both pointers must be non-NULL writeable uint16_t locations.
|
|
* No error path: ESTEX INFOMEM always succeeds.
|
|
*/
|
|
void mem_info(uint16_t *total, uint16_t *free_pages) __naked
|
|
{
|
|
(void)total; (void)free_pages;
|
|
__asm
|
|
;; HL = total ptr, DE = free_pages ptr on entry.
|
|
;; RST 10 clobbers both — stash on the stack across the call.
|
|
push ix
|
|
push hl ; later [SP+2] = total_ptr
|
|
push de ; TOS [SP+0] = free_pages_ptr
|
|
|
|
ld c, #0x3C ; ESTEX INFOMEM → HL = total, BC = free
|
|
rst #0x10
|
|
|
|
pop de ; DE = free_pages_ptr
|
|
ld a, c
|
|
ld (de), a
|
|
inc de
|
|
ld a, b
|
|
ld (de), a ; *free_pages = BC
|
|
|
|
pop de ; DE = total_ptr
|
|
ld a, l
|
|
ld (de), a
|
|
inc de
|
|
ld a, h
|
|
ld (de), a ; *total = HL
|
|
|
|
pop ix
|
|
ret
|
|
__endasm;
|
|
}
|