019d05f64c
That's my mega-commit you've all been waiting for. The code for the shell share more routines with userspace apps than with kernel units, because, well, its behavior is that of a userspace app, not a device driver. This created a weird situation with libraries and jump tables. Some routine belonging to the `kernel/` directory felt weird there. And then comes `apps/basic`, which will likely share even more code with the shell. I was seeing myself creating huge jump tables to reuse code from the shell. It didn't feel right. Moreover, we'll probably want basic-like apps to optionnally replace the shell. So here I am with this huge change in the project structure. I didn't test all recipes on hardware yet, I will do later. I might have broken some... But now, the structure feels better and the line between what belongs to `kernel` and what belongs to `apps` feels clearer.
317 lines
6.4 KiB
NASM
317 lines
6.4 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 proper 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/getb/putb 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: GetB and PutB.
|
|
;
|
|
; GetB:
|
|
; Reads one byte 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).
|
|
;
|
|
; PutB:
|
|
; Writes byte 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 except IX which is
|
|
; explicitly protected during GetB/PutB calls. This makes quick "handle+jump"
|
|
; definitions possible.
|
|
|
|
|
|
; *** DEFINES ***
|
|
; BLOCKDEV_COUNT: The number of devices we manage.
|
|
|
|
; *** CONSTS ***
|
|
; *** VARIABLES ***
|
|
; Pointer to the selected block device. A block device is a 8 bytes block of
|
|
; memory with pointers to GetB, PutB, and a 32-bit counter, in that order.
|
|
.equ BLOCKDEV_SEL BLOCKDEV_RAMSTART
|
|
.equ BLOCKDEV_RAMEND @+BLOCKDEV_SIZE
|
|
|
|
; *** CODE ***
|
|
; Put the pointer to the "regular" blkdev selection in DE
|
|
blkSelPtr:
|
|
ld de, BLOCKDEV_SEL
|
|
|
|
; 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.
|
|
; Sets Z on success, reset on error.
|
|
; If A >= BLOCKDEV_COUNT, it's an error.
|
|
blkSel:
|
|
cp BLOCKDEV_COUNT
|
|
jp nc, unsetZ ; if selection >= device count, error
|
|
push af
|
|
push de
|
|
push hl
|
|
|
|
ld hl, blkDevTbl
|
|
or a ; cp 0
|
|
jr z, .end ; index is zero? don't loop
|
|
push bc ; <|
|
|
ld b, a ; |
|
|
.loop: ; |
|
|
ld a, 4 ; |
|
|
call addHL ; |
|
|
djnz .loop ; |
|
|
pop bc ; <|
|
|
.end:
|
|
call blkSet
|
|
pop hl
|
|
pop de
|
|
pop af
|
|
cp a ; ensure Z
|
|
ret
|
|
|
|
; Setup blkdev handle in (DE) using routines at (HL).
|
|
blkSet:
|
|
push af
|
|
push de
|
|
push hl
|
|
|
|
; 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 byte from selected device and returns its value in A.
|
|
; Sets Z according to whether read was successful: Set if successful, unset
|
|
; if not.
|
|
blkGetB:
|
|
push ix
|
|
ld ix, BLOCKDEV_SEL
|
|
call _blkGetB
|
|
pop ix
|
|
ret
|
|
_blkGetB:
|
|
push hl
|
|
push de
|
|
call _blkTell
|
|
call callIXI
|
|
pop de
|
|
pop hl
|
|
jr _blkInc ; advance and return
|
|
|
|
; Writes byte in A in current position in the selected device. Sets Z according
|
|
; to whether the operation was successful.
|
|
blkPutB:
|
|
push ix
|
|
ld ix, BLOCKDEV_SEL
|
|
call _blkPutB
|
|
pop ix
|
|
ret
|
|
_blkPutB:
|
|
push ix
|
|
push hl
|
|
push de
|
|
call _blkTell
|
|
inc ix ; make IX point to PutB
|
|
inc ix
|
|
call callIXI
|
|
pop de
|
|
pop hl
|
|
pop ix
|
|
jr _blkInc ; advance and return
|
|
|
|
; Reads B chars from blkGetB and copy them in (HL).
|
|
; Sets Z if successful, unset Z if there was an error.
|
|
blkRead:
|
|
push ix
|
|
ld ix, BLOCKDEV_SEL
|
|
call _blkRead
|
|
pop ix
|
|
ret
|
|
_blkRead:
|
|
push hl
|
|
push bc
|
|
.loop:
|
|
call _blkGetB
|
|
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 blkPutB from (HL).
|
|
; Sets Z if successful, unset Z if there was an error.
|
|
blkWrite:
|
|
push ix
|
|
ld ix, BLOCKDEV_SEL
|
|
call _blkWrite
|
|
pop ix
|
|
ret
|
|
_blkWrite:
|
|
push hl
|
|
push bc
|
|
.loop:
|
|
ld a, (hl)
|
|
call _blkPutB
|
|
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, GetB after a seek
|
|
; to end would always fail.
|
|
;
|
|
; If the device is "growable", it's possible that seeking to end when calling
|
|
; PutB doesn't necessarily result in a failure.
|
|
blkSeek:
|
|
push ix
|
|
ld ix, BLOCKDEV_SEL
|
|
call _blkSeek
|
|
pop ix
|
|
ret
|
|
_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:
|
|
push ix
|
|
ld ix, BLOCKDEV_SEL
|
|
call _blkTell
|
|
pop ix
|
|
ret
|
|
_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 2 word addresses: GetB and PutB. An entry could look like:
|
|
; .dw mmapGetB, mmapPutB
|
|
blkDevTbl:
|