collapseos/kernel/blockdev.asm
Virgil Dupras ae028e3a86 blockdev: make implementors "random access"
This huge refactoring remove the Seek and Tell routine from blockdev
implementation requirements and change GetC and PutC's API so that they
take an address to read and write (through HL/DE) at each call.

The "PTR" approach in blockdev implementation was very redundant from
device to device and it made more sense to generalize. It's possible
that future device aren't "random access", but we'll be able to add more
device types later.

Another important change in this commit is that the "blockdev handle" is
now opaque. Previously, consumers of the API would happily call routines
directly from one of the 4 offsets. We can't do that any more. This
makes the API more solid for future improvements.

This change forced me to change a lot of things in fs, but overall,
things are now simpler. No more `FS_PTR`: the "device handle" now holds
the active pointer.

Lots, lots of changes, but it also feels a lot cleaner and solid.
2019-06-04 15:36:20 -04:00

277 lines
5.9 KiB
NASM

; blockdev
;
; A block device is an abstraction over something we can read from, write to.
;
; A device that fits this abstraction puts the properly hook into itself, and
; then the glue code assigns a blockdev ID to that device. It then becomes easy
; to access arbitrary devices in a convenient manner.
;
; This module exposes a seek/tell/getc/putc API that is then re-routed to
; underlying drivers. There will eventually be more than one driver type, but
; for now we sit on only one type of driver: random access driver.
;
; *** Random access drivers ***
;
; Random access drivers are expected to supply two routines: GetC and PutC.
;
; GetC:
; Reads one character at address specified in DE/HL and returns its value in A.
; Sets Z according to whether read was successful: Set if successful, unset
; if not.
;
; Unsuccessful reads generally mean that requested addr is out of bounds (we
; reached EOF).
;
; PutC:
; Writes character in A at address specified in DE/HL. Sets Z according to
; whether the operation was successful.
;
; Unsuccessful writes generally mean that we're out of bounds for writing.
;
; All routines are expected to preserve unused registers.
; *** DEFINES ***
; BLOCKDEV_COUNT: The number of devices we manage.
; *** CONSTS ***
.equ BLOCKDEV_SEEK_ABSOLUTE 0
.equ BLOCKDEV_SEEK_FORWARD 1
.equ BLOCKDEV_SEEK_BACKWARD 2
.equ BLOCKDEV_SEEK_BEGINNING 3
.equ BLOCKDEV_SEEK_END 4
.equ BLOCKDEV_SIZE 8
; *** VARIABLES ***
; Pointer to the selected block device. A block device is a 8 bytes block of
; memory with pointers to GetC, PutC, and a 32-bit counter, in that order.
.equ BLOCKDEV_SEL BLOCKDEV_RAMSTART
.equ BLOCKDEV_RAMEND BLOCKDEV_SEL+BLOCKDEV_SIZE
; *** CODE ***
; Select block index specified in A and place them in routine pointers at (DE).
; For example, for a "regular" blkSel, you will want to set DE to BLOCKDEV_SEL.
blkSel:
push af
push de
push hl
ld hl, blkDevTbl
or a ; cp 0
jr z, .afterloop ; index is zero? don't loop
push bc ; <|
ld b, a ; |
.loop: ; |
ld a, 4 ; |
call addHL ; |
djnz .loop ; |
pop bc ; <|
.afterloop:
; Write GETC
push hl ; <|
call intoHL ; |
call writeHLinDE ; |
inc de ; |
inc de ; |
pop hl ; <|
inc hl
inc hl
; Write PUTC
call intoHL
call writeHLinDE
inc de
inc de
; Initialize pos
xor a
ld (de), a
inc de
ld (de), a
inc de
ld (de), a
inc de
ld (de), a
pop hl
pop de
pop af
ret
_blkInc:
ret nz ; don't advance when in error condition
push af
push hl
ld a, BLOCKDEV_SEEK_FORWARD
ld hl, 1
call _blkSeek
pop hl
pop af
ret
; Reads one character from selected device and returns its value in A.
; Sets Z according to whether read was successful: Set if successful, unset
; if not.
blkGetC:
ld ix, BLOCKDEV_SEL
_blkGetC:
push hl
push de
call _blkTell
call callIXI
pop de
pop hl
jr _blkInc ; advance and return
; Writes character in A in current position in the selected device. Sets Z
; according to whether the operation was successful.
blkPutC:
ld ix, BLOCKDEV_SEL
_blkPutC:
push ix
push hl
push de
call _blkTell
inc ix ; make IX point to PutC
inc ix
call callIXI
pop de
pop hl
pop ix
jr _blkInc ; advance and return
; Reads B chars from blkGetC and copy them in (HL).
; Sets Z if successful, unset Z if there was an error.
blkRead:
ld ix, BLOCKDEV_SEL
_blkRead:
push hl
push bc
.loop:
call _blkGetC
jr nz, .end ; Z already unset
ld (hl), a
inc hl
djnz .loop
cp a ; ensure Z
.end:
pop bc
pop hl
ret
; Writes B chars to blkPutC from (HL).
; Sets Z if successful, unset Z if there was an error.
blkWrite:
ld ix, BLOCKDEV_SEL
_blkWrite:
push hl
push bc
.loop:
ld a, (hl)
call _blkPutC
jr nz, .end ; Z already unset
inc hl
djnz .loop
cp a ; ensure Z
.end:
pop bc
pop hl
ret
; Seeks the block device in one of 5 modes, which is the A 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 (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
; to end would always fail.
;
; If the device is "growable", it's possible that seeking to end when calling
; PutC doesn't necessarily result in a failure.
blkSeek:
ld ix, BLOCKDEV_SEL
_blkSeek:
cp BLOCKDEV_SEEK_FORWARD
jr z, .forward
cp BLOCKDEV_SEEK_BACKWARD
jr z, .backward
cp BLOCKDEV_SEEK_BEGINNING
jr z, .beginning
cp BLOCKDEV_SEEK_END
jr z, .end
; all other modes are considered absolute
ld (ix+4), e
ld (ix+5), d
ld (ix+6), l
ld (ix+7), h
ret
.forward:
push bc ; <-|
push hl ; <||
ld l, (ix+6) ; || low byte
ld h, (ix+7) ; ||
pop bc ; <||
add hl, bc ; |
pop bc ; <-|
ld (ix+6), l
ld (ix+7), h
ret nc ; no carry? no need to adjust high byte
; carry, adjust high byte
inc (ix+4)
ret nz
inc (ix+5)
ret
.backward:
and a ; clear carry
push bc ; <-|
push hl ; <||
ld l, (ix+6) ; || low byte
ld h, (ix+7) ; ||
pop bc ; <||
sbc hl, bc ; |
pop bc ; <-|
ld (ix+6), l
ld (ix+7), h
ret nc ; no carry? no need to adjust high byte
ld a, 0xff
dec (ix+4)
cp (ix+4)
ret nz
; we decremented from 0
dec (ix+5)
ret
.beginning:
xor a
ld (ix+4), a
ld (ix+5), a
ld (ix+6), a
ld (ix+7), a
ret
.end:
ld a, 0xff
ld (ix+4), a
ld (ix+5), a
ld (ix+6), a
ld (ix+7), a
ret
; Returns the current position of the selected device in HL (low) and DE (high).
blkTell:
ld ix, BLOCKDEV_SEL
_blkTell:
ld e, (ix+4)
ld d, (ix+5)
ld l, (ix+6)
ld h, (ix+7)
ret
; This label is at the end of the file on purpose: the glue file should include
; a list of device routine table entries just after the include. Each line
; has 4 word addresses: GetC, PutC and Seek, Tell. An entry could look like:
; .dw mmapGetC, mmapPutC, mmapSeek, mmapTell
blkDevTbl: