/*
 * mem_alloc_pages / mem_free_block / mem_get_page / mem_info — ESTEX EMM
 * wrappers for explicit 16 KB-page allocation.
 *
 *   BIOS  $C0 EMM_FN0 (MEMINFO)   → HL=total pages, BC=free pages
 *   BIOS  $C2 EMM_FN2 (ALLOC)     B=npages       → A=block id,        CF=err
 *   BIOS  $C3 EMM_FN3 (FREE)      A=block id     →                    CF=err
 *   BIOS  $C4 EMM_FN4 (PAGEID)    A=blk, B=idx  → A=physical page    CF=err
 *   BIOS  $C6 EMM_FN6 (IOTEST)    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_bios(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, #0xC2              ; BIOS EMM_FN2
        rst   #0x08
        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_bios(uint8_t blk_id) __naked
{
    (void)blk_id;
    __asm
        ;; SDCC single-uint8 arg → A on entry.
        push  ix
        ld    c, #0xC3              ; ESTEX FREEMEM
        rst   #0x08
        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_bios(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_bios(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, #0xC0              ; BIOS INFOMEM → HL = total, BC = free
        rst   #0x08

        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;
}
