/* * 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 #include /* * 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; }