This allows us to break through the 64K limit for includes CFS in zasm, a limit we were dangerously close to breaking. In fact, this commit makes us go over that limit. Right in time!pull/10/head
@@ -33,10 +33,12 @@ | |||
; Unsuccessful writes generally mean that we reached EOF. | |||
; | |||
; Seek: | |||
; Place device "pointer" at position dictated by HL. | |||
; Place device "pointer" at position dictated by HL (low 16 bits) and DE (high | |||
; 16 bits). | |||
; | |||
; Tell: | |||
; Return the position of the "pointer" in HL | |||
; Return the position of the "pointer" in HL (low 16 bits) and DE (high 16 | |||
; bits). | |||
; | |||
; All routines are expected to preserve unused registers. | |||
@@ -197,12 +199,14 @@ _blkWrite: | |||
ret | |||
; Seeks the block device in one of 5 modes, which is the A argument: | |||
; 0 : Move exactly to X, X being the HL argument. | |||
; 1 : Move forward by X bytes, X being the HL argument | |||
; 2 : Move backwards by X bytes, X being the HL argument | |||
; 0 : Move exactly to X, X being the HL/DE argument. | |||
; 1 : Move forward by X bytes, X being the HL argument (no DE) | |||
; 2 : Move backwards by X bytes, X being the HL argument (no DE) | |||
; 3 : Move to the end | |||
; 4 : Move to the beginning | |||
; Set position of selected device to the value specified in HL | |||
; Set position of selected device to the value specified in HL (low) and DE | |||
; (high). DE is only used for mode 0. | |||
; | |||
; When seeking to an out-of-bounds position, the resulting position will be | |||
; one position ahead of the last valid position. Therefore, GetC after a seek | |||
@@ -214,6 +218,8 @@ blkSeek: | |||
ld ix, (BLOCKDEV_SEEK) | |||
ld iy, (BLOCKDEV_TELL) | |||
_blkSeek: | |||
; we preserve DE so that it's possible to call blkSeek in mode != 0 | |||
; while not discarding our current DE value. | |||
push de | |||
cp BLOCKDEV_SEEK_FORWARD | |||
jr z, .forward | |||
@@ -224,30 +230,41 @@ _blkSeek: | |||
cp BLOCKDEV_SEEK_END | |||
jr z, .end | |||
; all other modes are considered absolute | |||
jr .seek ; for absolute mode, HL is already correct | |||
jr .seek ; for absolute mode, HL and DE are already | |||
; correct | |||
.forward: | |||
ex de, hl ; DE has our offset | |||
push bc | |||
push hl | |||
; We want to be able to plug our own TELL function, which is why we | |||
; don't call blkTell directly here. | |||
; Calling TELL | |||
call callIY ; HL has our curpos | |||
add hl, de | |||
jr nc, .seek ; no carry? alright! | |||
; we have carry? out of bounds, set to maximum | |||
ld de, 0 ; in case out Tell routine doesn't return DE | |||
call callIY ; HL/DE now have our curpos | |||
pop bc ; pop HL into BC | |||
add hl, bc | |||
pop bc ; pop orig BC back | |||
jr nc, .seek ; no carry? let's seek. | |||
; carry, adjust DE | |||
inc de | |||
jr .seek | |||
.backward: | |||
; TODO - subtraction are more complicated... | |||
jr .seek | |||
.beginning: | |||
ld hl, 0 | |||
ld de, 0 | |||
jr .seek | |||
.end: | |||
ld hl, 0xffff | |||
ld de, 0xffff | |||
.seek: | |||
call _blkCall | |||
pop de | |||
jp _blkCall | |||
ret | |||
; Returns the current position of the selected device in HL. | |||
; Returns the current position of the selected device in HL (low) and DE (high). | |||
blkTell: | |||
ld de, 0 ; in case device ignores DE. | |||
ld ix, (BLOCKDEV_TELL) | |||
jp _blkCall | |||
@@ -57,6 +57,19 @@ addHL: | |||
pop af | |||
ret | |||
; subtract the value of A from HL | |||
subHL: | |||
push af | |||
; To avoid having to swap L and A, we sub "backwards", that is, we add | |||
; a NEGated value. This means that the carry flag is inverted | |||
neg | |||
add a, l | |||
jr c, .end ; if carry, no carry. :) | |||
dec h | |||
.end: | |||
ld l, a | |||
pop af | |||
ret | |||
; Write the contents of HL in (DE) | |||
writeHLinDE: | |||
@@ -85,12 +85,9 @@ | |||
.equ FS_META_FSIZE_OFFSET 4 | |||
.equ FS_META_FNAME_OFFSET 6 | |||
; Size in bytes of a FS handle: | |||
; * 2 bytes for current position. (absolute) | |||
; * 2 bytes for starting offset, after metadata | |||
; * 2 bytes for maximum offset | |||
; * 2 bytes for file size (we could fetch it from metadata all the time, but it | |||
; could be time consuming depending on the underlying device). | |||
.equ FS_HANDLE_SIZE 8 | |||
; * 4 bytes for starting offset of the FS block | |||
; * 2 bytes for current position relative to block's position | |||
.equ FS_HANDLE_SIZE 6 | |||
.equ FS_ERR_NO_FS 0x5 | |||
.equ FS_ERR_NOT_FOUND 0x6 | |||
@@ -101,14 +98,17 @@ | |||
.equ FS_SEEK FS_PUTC+2 | |||
.equ FS_TELL FS_SEEK+2 | |||
; Offset at which our FS start on mounted device | |||
; This pointer is 32 bits. 32 bits pointers are a bit awkward: first two bytes | |||
; are high bytes *low byte first*, and then the low two bytes, same order. | |||
; When loaded in HL/DE, the four bytes are loaded in this order: E, D, L, H | |||
.equ FS_START FS_TELL+2 | |||
; Offset at which we are currently pointing to with regards to our routines | |||
; below, which all assume this offset as a context. This offset is not relative | |||
; to FS_START. It can be used directly with fsblkSeek. | |||
.equ FS_PTR FS_START+2 | |||
; to FS_START. It can be used directly with fsblkSeek. 32 bits. | |||
.equ FS_PTR FS_START+4 | |||
; This variable below contain the metadata of the last block FS_PTR was moved | |||
; to. We read this data in memory to avoid constant seek+read operations. | |||
.equ FS_META FS_PTR+2 | |||
.equ FS_META FS_PTR+4 | |||
.equ FS_HANDLES FS_META+FS_METASIZE | |||
.equ FS_RAMEND FS_HANDLES+FS_HANDLE_COUNT*FS_HANDLE_SIZE | |||
@@ -133,10 +133,11 @@ fsBegin: | |||
push hl | |||
ld hl, (FS_START) | |||
ld (FS_PTR), hl | |||
ld hl, (FS_START+2) | |||
ld (FS_PTR+2), hl | |||
pop hl | |||
call fsReadMeta | |||
call fsIsValid ; sets Z | |||
ret | |||
jp fsIsValid ; sets Z, returns | |||
; Change current position to the next block with metadata. If it can't (if this | |||
; is the last valid block), doesn't move. | |||
@@ -156,8 +157,9 @@ fsNext: | |||
call fsblkSeek | |||
djnz .loop | |||
; Good, were here. We're going to read meta from our current position. | |||
call fsblkTell ; --> HL | |||
ld (FS_PTR), hl | |||
call fsblkTell ; --> HL, --> DE | |||
ld (FS_PTR), de | |||
ld (FS_PTR+2), hl | |||
call fsReadMeta | |||
jr nz, .createChainEnd | |||
call fsIsValid | |||
@@ -226,13 +228,16 @@ fsInitMeta: | |||
pop af | |||
ret | |||
; Make sure that our underlying blockdev is correcly placed. | |||
; Make sure that our underlying blockdev is correctly placed. | |||
fsPlace: | |||
push af | |||
push hl | |||
push de | |||
xor a | |||
ld hl, (FS_PTR) | |||
ld de, (FS_PTR) | |||
ld hl, (FS_PTR+2) | |||
call fsblkSeek | |||
pop de | |||
pop hl | |||
pop af | |||
ret | |||
@@ -284,7 +289,8 @@ fsAlloc: | |||
; Good, FS_META ready. Now, let's update FS_PTR because it hasn't been | |||
; changed yet. | |||
call fsblkTell | |||
ld (FS_PTR), hl | |||
ld (FS_PTR), de | |||
ld (FS_PTR+2), hl | |||
; Ok, now we can write our metadata | |||
call fsWriteMeta | |||
.end: | |||
@@ -361,6 +367,7 @@ fsblkSeek: | |||
jp _blkSeek | |||
fsblkTell: | |||
ld de, 0 | |||
ld ix, (FS_TELL) | |||
jp _blkCall | |||
@@ -368,57 +375,56 @@ fsblkTell: | |||
; Open file at current position into handle at (HL) | |||
fsOpen: | |||
push bc | |||
push hl | |||
push de | |||
push af | |||
ex de, hl | |||
ld hl, (FS_PTR) | |||
ld a, FS_METASIZE | |||
call addHL | |||
call writeHLinDE | |||
inc de | |||
inc de | |||
call writeHLinDE | |||
inc de | |||
inc de | |||
; Maximum offset is starting offset + (numblocks * 0x100) - 1 | |||
ld a, (FS_META+FS_META_ALLOC_OFFSET) | |||
; Because our blocks are exactly 0x100 in size, we simple have to | |||
; increase the H in HL to have our result. | |||
add a, h | |||
ld h, a | |||
call writeHLinDE | |||
inc de | |||
inc de | |||
ld hl, (FS_META+FS_META_FSIZE_OFFSET) | |||
; Starting pos | |||
ld hl, FS_PTR | |||
ld bc, 4 | |||
ldir | |||
; Current pos | |||
ld hl, FS_METASIZE | |||
call writeHLinDE | |||
pop af | |||
pop de | |||
pop hl | |||
pop bc | |||
ret | |||
; Place FS blockdev at proper position for file handle in (DE). | |||
fsPlaceH: | |||
push af | |||
push bc | |||
push hl | |||
push de | |||
pop ix | |||
push ix | |||
ld l, (ix) | |||
ld h, (ix+1) | |||
ld e, (ix) | |||
ld d, (ix+1) | |||
ld l, (ix+2) | |||
ld h, (ix+3) | |||
ld c, (ix+4) | |||
ld b, (ix+5) | |||
add hl, bc | |||
jr nc, .nocarry | |||
inc de | |||
.nocarry: | |||
ld a, BLOCKDEV_SEEK_ABSOLUTE | |||
call fsblkSeek | |||
pop ix | |||
pop hl | |||
pop bc | |||
pop af | |||
ret | |||
; Advance file handle in (IX) by one byte | |||
fsAdvanceH: | |||
push af | |||
inc (ix) | |||
inc (ix+4) | |||
jr nz, .end | |||
inc (ix+1) | |||
inc (ix+5) | |||
.end: | |||
pop af | |||
ret | |||
@@ -426,6 +432,7 @@ fsAdvanceH: | |||
; Read a byte in handle at (DE), put it into A and advance the handle's | |||
; position. | |||
; Z is set on success, unset if handle is at the end of the file. | |||
; TODO: detect end of file | |||
fsGetC: | |||
push ix | |||
call fsPlaceH | |||
@@ -441,6 +448,7 @@ fsGetC: | |||
; Write byte A in handle (DE) and advance the handle's position. | |||
; Z is set on success, unset if handle is at the end of the file. | |||
; TODO: detect end of block alloc | |||
fsPutC: | |||
call fsPlaceH | |||
push ix | |||
@@ -455,15 +463,18 @@ fsPutC: | |||
; Sets Z if offset is within bounds, unsets Z if it isn't. | |||
fsSeek: | |||
push de \ pop ix | |||
ld (ix), l | |||
ld (ix+1), h | |||
ld a, FS_METASIZE | |||
call addHL | |||
ld (ix+4), l | |||
ld (ix+5), h | |||
ret | |||
fsTell: | |||
push de \ pop ix | |||
ld l, (ix) | |||
ld h, (ix+1) | |||
ret | |||
ld l, (ix+4) | |||
ld h, (ix+5) | |||
ld a, FS_METASIZE | |||
jp subHL ; returns | |||
; Mount the fs subsystem upon the currently selected blockdev at current offset. | |||
; Verify is block is valid and error out if its not, mounting nothing. | |||
@@ -479,8 +490,10 @@ fsOn: | |||
ld bc, 8 ; we have 8 bytes to copy | |||
ldir ; copy! | |||
call fsblkTell | |||
ld (FS_START), hl | |||
ld (FS_PTR), hl | |||
ld (FS_START), de | |||
ld (FS_START+2), hl | |||
ld (FS_PTR), de | |||
ld (FS_PTR+2), hl | |||
call fsReadMeta | |||
jr nz, .error | |||
call fsIsValid | |||
@@ -491,8 +504,7 @@ fsOn: | |||
.error: | |||
; couldn't mount. Let's reset our variables. | |||
xor a | |||
ld b, 10 ; blkdev routines + FS_START which is just | |||
; after. | |||
ld b, FS_META-FS_GETC ; reset routine pointers and FS ptrs | |||
ld hl, FS_GETC | |||
call fill | |||
@@ -31,12 +31,13 @@ | |||
#define FS_DATA_PORT 0x01 | |||
#define FS_SEEKL_PORT 0x02 | |||
#define FS_SEEKH_PORT 0x03 | |||
#define FS_SEEKE_PORT 0x04 | |||
static Z80Context cpu; | |||
static uint8_t mem[0xffff] = {0}; | |||
static uint8_t fsdev[0xffff] = {0}; | |||
static uint16_t fsdev_size = 0; | |||
static uint16_t fsdev_ptr = 0; | |||
static uint8_t fsdev[0x20000] = {0}; | |||
static uint32_t fsdev_size = 0; | |||
static uint32_t fsdev_ptr = 0; | |||
static int running; | |||
static uint8_t io_read(int unused, uint16_t addr) | |||
@@ -57,7 +58,9 @@ static uint8_t io_read(int unused, uint16_t addr) | |||
} else if (addr == FS_SEEKL_PORT) { | |||
return fsdev_ptr & 0xff; | |||
} else if (addr == FS_SEEKH_PORT) { | |||
return fsdev_ptr >> 8; | |||
return (fsdev_ptr >> 8) & 0xff; | |||
} else if (addr == FS_SEEKE_PORT) { | |||
return (fsdev_ptr >> 16) & 0xff; | |||
} else { | |||
fprintf(stderr, "Out of bounds I/O read: %d\n", addr); | |||
return 0; | |||
@@ -78,9 +81,11 @@ static void io_write(int unused, uint16_t addr, uint8_t val) | |||
fsdev[fsdev_ptr++] = val; | |||
} | |||
} else if (addr == FS_SEEKL_PORT) { | |||
fsdev_ptr = (fsdev_ptr & 0xff00) | val; | |||
fsdev_ptr = (fsdev_ptr & 0xffff00) | val; | |||
} else if (addr == FS_SEEKH_PORT) { | |||
fsdev_ptr = (fsdev_ptr & 0x00ff) | (val << 8); | |||
fsdev_ptr = (fsdev_ptr & 0xff00ff) | (val << 8); | |||
} else if (addr == FS_SEEKE_PORT) { | |||
fsdev_ptr = (fsdev_ptr & 0x00ffff) | (val << 16); | |||
} else { | |||
fprintf(stderr, "Out of bounds I/O write: %d / %d\n", addr, val); | |||
} | |||
@@ -5,6 +5,7 @@ | |||
.equ FS_DATA_PORT 0x01 | |||
.equ FS_SEEKL_PORT 0x02 | |||
.equ FS_SEEKH_PORT 0x03 | |||
.equ FS_SEEKE_PORT 0x04 | |||
jp init | |||
@@ -80,6 +81,8 @@ fsdevSeek: | |||
out (FS_SEEKL_PORT), a | |||
ld a, h | |||
out (FS_SEEKH_PORT), a | |||
ld a, e | |||
out (FS_SEEKE_PORT), a | |||
pop af | |||
ret | |||
@@ -89,6 +92,8 @@ fsdevTell: | |||
ld l, a | |||
in a, (FS_SEEKH_PORT) | |||
ld h, a | |||
in a, (FS_SEEKE_PORT) | |||
ld e, a | |||
pop af | |||
ret | |||
@@ -98,6 +98,8 @@ fsdevPutC: | |||
fsdevSeek: | |||
push af | |||
ld a, e | |||
out (FS_SEEK_PORT), a | |||
ld a, h | |||
out (FS_SEEK_PORT), a | |||
ld a, l | |||
@@ -108,6 +110,8 @@ fsdevSeek: | |||
fsdevTell: | |||
push af | |||
in a, (FS_SEEK_PORT) | |||
ld e, a | |||
in a, (FS_SEEK_PORT) | |||
ld h, a | |||
in a, (FS_SEEK_PORT) | |||
ld l, a | |||
@@ -47,10 +47,10 @@ static int inpt_size; | |||
static int inpt_ptr; | |||
static uint8_t middle_of_seek_tell = 0; | |||
static uint8_t fsdev[0xffff] = {0}; | |||
static uint16_t fsdev_size = 0; | |||
static uint16_t fsdev_ptr = 0; | |||
static uint8_t fsdev_middle_of_seek_tell = 0; | |||
static uint8_t fsdev[0x20000] = {0}; | |||
static uint32_t fsdev_size = 0; | |||
static uint32_t fsdev_ptr = 0; | |||
static uint8_t fsdev_seek_tell_cnt = 0; | |||
static uint8_t io_read(int unused, uint16_t addr) | |||
{ | |||
@@ -79,15 +79,18 @@ static uint8_t io_read(int unused, uint16_t addr) | |||
return 0; | |||
} | |||
} else if (addr == FS_SEEK_PORT) { | |||
if (fsdev_middle_of_seek_tell) { | |||
fsdev_middle_of_seek_tell = 0; | |||
return fsdev_ptr & 0xff; | |||
} else { | |||
if (fsdev_seek_tell_cnt == 0) { | |||
#ifdef DEBUG | |||
fprintf(stderr, "FS tell %d\n", fsdev_ptr); | |||
#endif | |||
fsdev_middle_of_seek_tell = 1; | |||
return fsdev_ptr >> 8; | |||
fsdev_seek_tell_cnt = 1; | |||
return fsdev_ptr >> 16; | |||
} else if (fsdev_seek_tell_cnt == 1) { | |||
fsdev_seek_tell_cnt = 2; | |||
return (fsdev_ptr >> 8) & 0xff; | |||
} else { | |||
fsdev_seek_tell_cnt = 0; | |||
return fsdev_ptr & 0xff; | |||
} | |||
} else { | |||
fprintf(stderr, "Out of bounds I/O read: %d\n", addr); | |||
@@ -119,15 +122,18 @@ static void io_write(int unused, uint16_t addr, uint8_t val) | |||
fsdev[fsdev_ptr++] = val; | |||
} | |||
} else if (addr == FS_SEEK_PORT) { | |||
if (fsdev_middle_of_seek_tell) { | |||
if (fsdev_seek_tell_cnt == 0) { | |||
fsdev_ptr = val << 16; | |||
fsdev_seek_tell_cnt = 1; | |||
} else if (fsdev_seek_tell_cnt == 1) { | |||
fsdev_ptr |= val << 8; | |||
fsdev_seek_tell_cnt = 2; | |||
} else { | |||
fsdev_ptr |= val; | |||
fsdev_middle_of_seek_tell = 0; | |||
fsdev_seek_tell_cnt = 0; | |||
#ifdef DEBUG | |||
fprintf(stderr, "FS seek %d\n", fsdev_ptr); | |||
#endif | |||
} else { | |||
fsdev_ptr = (val << 8) & 0xff00; | |||
fsdev_middle_of_seek_tell = 1; | |||
} | |||
} else if (addr == STDERR_PORT) { | |||
fputc(val, stderr); | |||
@@ -0,0 +1,56 @@ | |||
jp test | |||
#include "core.asm" | |||
testNum: .db 1 | |||
test: | |||
ld hl, 0xffff | |||
ld sp, hl | |||
ld hl, 0x123 | |||
ld a, 0x25 | |||
call subHL | |||
ld a, h | |||
cp 0 | |||
jp nz, fail | |||
ld a, l | |||
cp 0xfe | |||
jp nz, fail | |||
call nexttest | |||
ld hl, 0x125 | |||
ld a, 0x23 | |||
call subHL | |||
ld a, h | |||
cp 1 | |||
jp nz, fail | |||
ld a, l | |||
cp 0x02 | |||
jp nz, fail | |||
call nexttest | |||
ld hl, 0x125 | |||
ld a, 0x25 | |||
call subHL | |||
ld a, h | |||
cp 1 | |||
jp nz, fail | |||
ld a, l | |||
cp 0 | |||
jp nz, fail | |||
call nexttest | |||
; success | |||
xor a | |||
halt | |||
nexttest: | |||
ld a, (testNum) | |||
inc a | |||
ld (testNum), a | |||
ret | |||
fail: | |||
ld a, (testNum) | |||
halt |