1
0
mirror of https://github.com/hsoft/collapseos.git synced 2024-12-26 17:58:07 +11:00
collapseos/kernel/blockdev.asm
Virgil Dupras 019d05f64c Make the shell a userspace app
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.
2019-11-15 15:37:49 -05:00

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: