; 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