collapseos/kernel/trs80/floppy.asm

292 lines
6.7 KiB
NASM

; floppy
;
; Implement a block device around a TRS-80 floppy. It uses SVCs supplied by
; TRS-DOS to do so.
;
; *** Floppy buffers ***
;
; The dual-buffer system is exactly the same as in the "sdc" module. See
; comments there.
;
; *** Consts ***
; Number of sector per cylinder. We only support single density for now.
.equ FLOPPY_SEC_PER_CYL 10
.equ FLOPPY_MAX_CYL 40
.equ FLOPPY_BLKSIZE 256
; *** Variables ***
; This is a pointer to the currently selected buffer. This points to the BUFSEC
; part, that is, two bytes before actual content begins.
.equ FLOPPY_BUFPTR FLOPPY_RAMSTART
; Sector number currently in FLOPPY_BUF1. Little endian like any other z80 word.
.equ FLOPPY_BUFSEC1 @+2
; Whether the buffer has been written to. 0 means clean. 1 means dirty.
.equ FLOPPY_BUFDIRTY1 @+2
; The contents of the buffer.
.equ FLOPPY_BUF1 @+1
; second buffer has the same structure as the first.
.equ FLOPPY_BUFSEC2 @+FLOPPY_BLKSIZE
.equ FLOPPY_BUFDIRTY2 @+2
.equ FLOPPY_BUF2 @+1
.equ FLOPPY_RAMEND @+FLOPPY_BLKSIZE
; *** Code ***
floppyInit:
; Make sure that both buffers are flagged as invalid and not dirty
xor a
ld (FLOPPY_BUFDIRTY1), a
ld (FLOPPY_BUFDIRTY2), a
dec a
ld (FLOPPY_BUFSEC1), a
ld (FLOPPY_BUFSEC2), a
ret
; Returns whether D (cylinder) and E (sector) are in proper range.
; Z for success.
_floppyInRange:
ld a, e
cp FLOPPY_SEC_PER_CYL
jp nc, unsetZ
ld a, d
cp FLOPPY_MAX_CYL
jp nc, unsetZ
xor a ; set Z
ret
; Read sector index specified in E and cylinder specified in D and place the
; contents in buffer pointed to by (FLOPPY_BUFPTR).
; If the operation is a success, updates buffer's sector to the value of DE.
; Z on success
floppyRdSec:
call _floppyInRange
ret nz
push bc
push hl
ld a, 0x28 ; @DCSTAT
ld c, 1 ; hardcoded to drive :1 for now
rst 0x28
jr nz, .end
ld hl, (FLOPPY_BUFPTR) ; HL --> active buffer's sector
ld (hl), e ; sector
inc hl
ld (hl), d ; cylinder
inc hl ; dirty
inc hl ; data
ld a, 0x31 ; @RDSEC
rst 0x28 ; sets proper Z
.end:
pop hl
pop bc
ret
; Write the contents of buffer where (FLOPPY_BUFPTR) points to in sector
; associated to it. Unsets the the buffer's dirty flag on success.
; Z on success
floppyWrSec:
push ix
ld ix, (FLOPPY_BUFPTR) ; IX points to sector
xor a
cp (ix+2) ; dirty flag
pop ix
ret z ; don't write if dirty flag is zero
push hl
push de
push bc
ld hl, (FLOPPY_BUFPTR) ; sector
ld e, (hl)
inc hl ; cylinder
ld d, (hl)
call _floppyInRange
jr nz, .end
ld c, 1 ; drive
ld a, 0x28 ; @DCSTAT
rst 0x28
jr nz, .end
inc hl ; dirty
xor a
ld (hl), a ; undirty the buffer
inc hl ; data
ld a, 0x35 ; @WRSEC
rst 0x28 ; sets proper Z
.end:
pop bc
pop de
pop hl
ret
; Considering the first 15 bits of EHL, select the most appropriate of our two
; buffers and, if necessary, sync that buffer with the floppy. If the selected
; buffer doesn't have the same sector as what EHL asks, load that buffer from
; the floppy.
; If the dirty flag is set, we write the content of the in-memory buffer to the
; floppy before we read a new sector.
; Returns Z on success, NZ on error
floppySync:
push de
; Given a 24-bit address in EHL, extracts the 16-bit sector from it and
; place it in DE, following cylinder and sector rules.
; EH is our sector index, L is our offset within the sector.
ld d, e ; cylinder
ld a, h ; sector
; Let's process D first. Because our maximum number of sectors is 400
; (40 * 10), D can only be either 0 or 1. If it's 1, we set D to 25 and
; add 6 to A
inc d \ dec d
jr z, .loop1 ; skip
ld d, 25
add a, 6
.loop1:
cp FLOPPY_SEC_PER_CYL
jr c, .loop1end
sub FLOPPY_SEC_PER_CYL
inc d
jr .loop1
.loop1end:
ld e, a ; write final sector in E
; Let's first see if our first buffer has our sector
ld a, (FLOPPY_BUFSEC1) ; sector
cp e
jr nz, .notBuf1
ld a, (FLOPPY_BUFSEC1+1) ; cylinder
cp d
jr z, .buf1Ok
.notBuf1:
; Ok, let's check for buf2 then
ld a, (FLOPPY_BUFSEC2) ; sector
cp e
jr nz, .notBuf2
ld a, (FLOPPY_BUFSEC2+1) ; cylinder
cp d
jr z, .buf2Ok
.notBuf2:
; None of our two buffers have the sector we need, we'll need to load
; a new one.
; We select our buffer depending on which is dirty. If both are on the
; same status of dirtiness, we pick any (the first in our case). If one
; of them is dirty, we pick the clean one.
push de ; --> lvl 1
ld de, FLOPPY_BUFSEC1
ld a, (FLOPPY_BUFDIRTY1)
or a ; is buf1 dirty?
jr z, .ready ; no? good, that's our buffer
; yes? then buf2 is our buffer.
ld de, FLOPPY_BUFSEC2
.ready:
; At this point, DE points to one of our two buffers, the good one.
; Let's save it to FLOPPY_BUFPTR
ld (FLOPPY_BUFPTR), de
pop de ; <-- lvl 1
; We have to read a new sector, but first, let's write the current one
; if needed.
call floppyWrSec
jr nz, .end ; error
; Let's read our new sector in DE
call floppyRdSec
jr .end
.buf1Ok:
ld de, FLOPPY_BUFSEC1
ld (FLOPPY_BUFPTR), de
; Z already set
jr .end
.buf2Ok:
ld de, FLOPPY_BUFSEC2
ld (FLOPPY_BUFPTR), de
; Z already set
; to .end
.end:
pop de
ret
; Flush floppy buffers if dirty and then invalidates them.
; We invalidate them so that we allow the case where we swap disks after a
; flush. If we didn't invalidate the buffers, reading a swapped disk after a
; flush would yield data from the previous disk.
floppyFlush:
ld hl, FLOPPY_BUFSEC1
ld (FLOPPY_BUFPTR), hl
call floppyWrSec
ld hl, FLOPPY_BUFSEC2
ld (FLOPPY_BUFPTR), hl
call floppyWrSec
call floppyInit
xor a ; ensure Z
ret
; *** blkdev routines ***
; Make HL point to its proper place in FLOPPY_BUF.
; EHL currently is a 24-bit offset to read in the floppy. E=high byte,
; HL=low word. Load the proper sector in memory and make HL point to the
; correct data in the memory buffer.
_floppyPlaceBuf:
call floppySync
ret nz ; error
; At this point, we have the proper buffer in place and synced in
; (FLOPPY_BUFPTR). Only L is important
ld a, l
ld hl, (FLOPPY_BUFPTR)
inc hl ; sector MSB
inc hl ; dirty flag
inc hl ; contents
; DE is now placed on the data part of the active buffer and all we need
; is to increase DE by L.
call addHL
; Now, HL points exactly at the right byte in the active buffer.
xor a ; ensure Z
ret
floppyGetB:
push hl
call _floppyPlaceBuf
jr nz, .end ; NZ already set
; This is it!
ld a, (hl)
cp a ; ensure Z
.end:
pop hl
ret
floppyPutB:
push hl
push af ; --> lvl 1. let's remember the char we put,
; _floppyPlaceBuf destroys A.
call _floppyPlaceBuf
jr nz, .error
; HL points to our dest. Recall A and write
pop af ; <-- lvl 1
ld (hl), a
; Now, let's set the dirty flag
ld a, 1
ld hl, (FLOPPY_BUFPTR)
inc hl ; sector MSB
inc hl ; point to dirty flag
ld (hl), a ; set dirty flag
xor a ; ensure Z
jr .end
.error:
; preserve error code
ex af, af'
pop af ; <-- lvl 1
ex af, af'
call unsetZ
.end:
pop hl
ret