Block device peripheral and SBFS file system implemented.
This commit is contained in:
@@ -0,0 +1,871 @@
|
||||
; sbfs.asm
|
||||
; Reading the SplitBit Filesystem.
|
||||
;
|
||||
; The other implementation of this format is SplitDisk on the host. Nothing is shared
|
||||
; between them but the written specification, so anything that changes here has to change
|
||||
; there in the same breath.
|
||||
;
|
||||
; The disk's buffer is registered as bank 3, and every block read lands there and is then
|
||||
; blitted where it is wanted. The CPU never touches the buffer directly, because nothing
|
||||
; can: memory a device brings is reachable only through the controller.
|
||||
;
|
||||
; Written by Anachronaut
|
||||
|
||||
#Program
|
||||
|
||||
; Registers the disk's buffer, reads the superblock, and checks that the disk really is
|
||||
; one of ours. Q is zero if the disk is ready to be read.
|
||||
sbfsMount:
|
||||
INIA 0d3
|
||||
OUTA 0xE3
|
||||
INIA 0x20
|
||||
OUTA 0xE2
|
||||
INIA 0x03
|
||||
OUTA 0xE8 ; RegisterBank: bank 3 is the disk's buffer.
|
||||
|
||||
SETD.0 SbfsBlock
|
||||
RSTA
|
||||
STA.0
|
||||
INCD.0
|
||||
STA.0 ; Block 0, the superblock.
|
||||
CALL sbfsReadBlock
|
||||
BRQ sbfsMountRead
|
||||
RET ; The read failed, and Q says so.
|
||||
|
||||
sbfsMountRead:
|
||||
SETD.1 SbfsBuffer
|
||||
CALL sbfsBufferOut
|
||||
|
||||
; "SBFS", or this is not a disk of ours. A blank image is all zeroes, so without this
|
||||
; an unformatted disk would look like a formatted one with no files on it.
|
||||
SETD.0 SbfsBuffer
|
||||
SETD.2 SbfsMagic
|
||||
INIA 0d4
|
||||
SETD.1 SbfsCount
|
||||
STA.1
|
||||
sbfsMagicLoop:
|
||||
CALL sbfsSameByte
|
||||
BRQ sbfsMagicSame
|
||||
RET ; Q is the difference, which is not zero.
|
||||
sbfsMagicSame:
|
||||
INCD.0
|
||||
INCD.2
|
||||
LDA.1
|
||||
DECA
|
||||
STA.1
|
||||
BNA sbfsMagicLoop
|
||||
|
||||
sbfsMagicDone:
|
||||
; The version has to be one we understand.
|
||||
SETD.0 SbfsBuffer
|
||||
DPUP.0 0d04
|
||||
LDA.0
|
||||
INIB 0d1
|
||||
XOR
|
||||
BRQ sbfsGeometry
|
||||
RET ; A version we do not know.
|
||||
|
||||
sbfsGeometry:
|
||||
; Where the directory is and how big it is. The superblock carries both so that disks
|
||||
; of different sizes can have different directories without a new format.
|
||||
SETD.0 SbfsBuffer
|
||||
DPUP.0 0d08
|
||||
SETD.1 SbfsDirStart
|
||||
CALL sbfsCopyWord
|
||||
SETD.0 SbfsBuffer
|
||||
DPUP.0 0d10
|
||||
SETD.1 SbfsDirBlocks
|
||||
CALL sbfsCopyWord
|
||||
SETD.0 SbfsBuffer
|
||||
DPUP.0 0d06
|
||||
SETD.1 SbfsDiskBlocks
|
||||
CALL sbfsCopyWord
|
||||
RSTA
|
||||
RSTB
|
||||
CCF
|
||||
ADD ; Q is zero: mounted.
|
||||
RET
|
||||
|
||||
; Finds a file by name. DP0 points at a name, ending in a zero byte. Q is zero if it was
|
||||
; found, and then SbfsFileStart, SbfsFileBlocks and SbfsFileTail describe it.
|
||||
sbfsFind:
|
||||
SETD.1 SbfsWanted
|
||||
CALL sbfsKeepName
|
||||
|
||||
; Start at the first directory block and work through them all.
|
||||
SETD.0 SbfsDirStart
|
||||
SETD.1 SbfsBlock
|
||||
CALL sbfsCopyWord
|
||||
SETD.0 SbfsDirBlocks
|
||||
DPUP.0 0d01
|
||||
LDA.0
|
||||
SETD.1 SbfsLeft
|
||||
STA.1 ; Only the low byte: a directory of 256 blocks is 2048 files.
|
||||
|
||||
sbfsFindBlock:
|
||||
CALL sbfsReadBlock
|
||||
BRQ sbfsFindLoaded
|
||||
RET ; The read failed.
|
||||
sbfsFindLoaded:
|
||||
SETD.1 SbfsBuffer
|
||||
CALL sbfsBufferOut
|
||||
|
||||
; Eight entries to a block, thirty two bytes each.
|
||||
SETD.2 SbfsBuffer
|
||||
INIA 0d8
|
||||
SETD.1 SbfsCount
|
||||
STA.1
|
||||
|
||||
sbfsFindEntry:
|
||||
LDA.2
|
||||
INIB 0x01
|
||||
AND
|
||||
BRQ sbfsFindNext ; The in use bit is down, so this entry is free.
|
||||
CALL sbfsMatchEntry
|
||||
BRQ sbfsFindFound
|
||||
|
||||
sbfsFindNext:
|
||||
DPUP.2 0d32
|
||||
LDA.1
|
||||
DECA
|
||||
STA.1
|
||||
BNA sbfsFindEntry
|
||||
|
||||
sbfsFindNextBlock:
|
||||
SETD.0 SbfsBlock
|
||||
CALL sbfsStepWord
|
||||
SETD.1 SbfsLeft
|
||||
LDA.1
|
||||
DECA
|
||||
STA.1
|
||||
BRA sbfsFindMissing
|
||||
BRI sbfsFindBlock
|
||||
|
||||
sbfsFindMissing:
|
||||
; Nothing of that name. Q has to be something other than zero to say so.
|
||||
RSTA
|
||||
INIB 0d1
|
||||
CCF
|
||||
ADD
|
||||
RET
|
||||
|
||||
sbfsFindFound:
|
||||
; DP2 is on the entry. Keep it in DP3 while the pieces are taken out of it: DP3 is the
|
||||
; pointer a CALL does not put back, so it is the only one that survives a subroutine.
|
||||
; Copying it into DP0 has to be done here rather than in a routine of its own, for the
|
||||
; same reason: a routine that set DP0 would have the assignment undone by its own RET.
|
||||
PSHD.2
|
||||
POPD.3
|
||||
|
||||
PSHD.3
|
||||
POPD.0
|
||||
DPUP.0 0d01
|
||||
SETD.1 SbfsFileStart
|
||||
CALL sbfsCopyWord
|
||||
|
||||
PSHD.3
|
||||
POPD.0
|
||||
DPUP.0 0d03
|
||||
SETD.1 SbfsFileBlocks
|
||||
CALL sbfsCopyWord
|
||||
|
||||
PSHD.3
|
||||
POPD.0
|
||||
DPUP.0 0d05
|
||||
LDA.0
|
||||
SETD.1 SbfsFileTail
|
||||
STA.1
|
||||
|
||||
RSTA
|
||||
RSTB
|
||||
CCF
|
||||
ADD ; Q is zero: found.
|
||||
RET
|
||||
|
||||
; Compares the name in the entry at DP2 with the one kept in SbfsWanted. Q is zero if
|
||||
; they are the same. Names are padded with zeroes rather than terminated, so a name that
|
||||
; fills the field has no terminator to look for, which is why the count is what stops it.
|
||||
sbfsMatchEntry:
|
||||
PSHD.2
|
||||
POPD.0
|
||||
DPUP.0 0d06 ; The name inside the entry.
|
||||
SETD.2 SbfsWanted
|
||||
INIA 0d22
|
||||
SETD.1 SbfsCount
|
||||
STA.1
|
||||
|
||||
sbfsMatchLoop:
|
||||
CALL sbfsSameByte
|
||||
BRQ sbfsMatchSame
|
||||
RET ; They differ, and Q is the difference.
|
||||
sbfsMatchSame:
|
||||
LDA.0
|
||||
BRA sbfsMatchYes ; Both ended in the same place.
|
||||
INCD.0
|
||||
INCD.2
|
||||
LDA.1
|
||||
DECA
|
||||
STA.1
|
||||
BNA sbfsMatchLoop ; Twenty two the same is the same name, so falling out is a match.
|
||||
|
||||
sbfsMatchYes:
|
||||
RSTA
|
||||
RSTB
|
||||
CCF
|
||||
ADD
|
||||
RET
|
||||
|
||||
; Copies the name at DP0 into DP1, twenty two bytes of it, padding with zeroes the way an
|
||||
; entry is padded so that the two can be compared as they stand.
|
||||
sbfsKeepName:
|
||||
INIA 0d22
|
||||
SETD.2 SbfsCount
|
||||
STA.2
|
||||
RSTB ; B stays zero once the name has ended.
|
||||
sbfsKeepLoop:
|
||||
LDA.0
|
||||
BRA sbfsKeepPad
|
||||
STA.1
|
||||
INCD.0
|
||||
BRI sbfsKeepStep
|
||||
sbfsKeepPad:
|
||||
STB.1
|
||||
sbfsKeepStep:
|
||||
INCD.1
|
||||
LDA.2
|
||||
DECA
|
||||
STA.2
|
||||
BNA sbfsKeepLoop
|
||||
sbfsKeepDone:
|
||||
RET
|
||||
|
||||
; Reads the file that sbfsFind found into Data Memory at DP1. Q is zero if it worked.
|
||||
;
|
||||
; Whole blocks are copied whole, including the last one, so the bytes after the end of a
|
||||
; file are whatever else was in that block on the disk. SbfsFileTail says where the file
|
||||
; actually stops, and it is the caller's business to respect it.
|
||||
;
|
||||
; A file of more than 255 blocks is not handled here. That is 64K, which is the whole of
|
||||
; Data Memory, so it could not be read into it anyway.
|
||||
sbfsRead:
|
||||
PSHD.1
|
||||
POPD.3 ; Where it goes. DP3 survives a CALL, and nothing called here
|
||||
; touches it.
|
||||
SETD.0 SbfsFileStart
|
||||
SETD.1 SbfsBlock
|
||||
CALL sbfsCopyWord
|
||||
|
||||
SETD.0 SbfsFileBlocks
|
||||
DPUP.0 0d01
|
||||
LDA.0
|
||||
SETD.1 SbfsLeft
|
||||
STA.1
|
||||
SETD.0 SbfsFileTail
|
||||
LDA.0
|
||||
BRA sbfsReadCounted ; No part block on the end.
|
||||
SETD.0 SbfsLeft
|
||||
LDA.0
|
||||
INCA
|
||||
STA.0 ; One more block for the tail.
|
||||
|
||||
sbfsReadCounted:
|
||||
SETD.0 SbfsLeft
|
||||
LDA.0
|
||||
BRA sbfsReadDone ; An empty file has nothing to read.
|
||||
|
||||
sbfsReadLoop:
|
||||
CALL sbfsReadBlock
|
||||
BRQ sbfsReadGot
|
||||
RET ; The read failed, and Q says so.
|
||||
sbfsReadGot:
|
||||
PSHD.3
|
||||
POPD.1
|
||||
CALL sbfsBufferOut
|
||||
|
||||
; On by a whole block. DPUP carries one byte at a time, so 256 takes two of them.
|
||||
DPUP.3 0xFF
|
||||
DPUP.3 0x01
|
||||
SETD.0 SbfsBlock
|
||||
CALL sbfsStepWord
|
||||
|
||||
SETD.0 SbfsLeft
|
||||
LDA.0
|
||||
DECA
|
||||
STA.0
|
||||
BNA sbfsReadLoop
|
||||
|
||||
sbfsReadDone:
|
||||
RSTA
|
||||
RSTB
|
||||
CCF
|
||||
ADD ; Q is zero: read.
|
||||
RET
|
||||
|
||||
; ---- Writing ----
|
||||
|
||||
; Where the first block that can hold a file is: past the superblock and the directory.
|
||||
sbfsFirstData:
|
||||
SETD.0 SbfsCandidate
|
||||
SETD.2 SbfsDirStart
|
||||
CALL sbfsSetWord
|
||||
SETD.0 SbfsCandidate
|
||||
SETD.2 SbfsDirBlocks
|
||||
CALL sbfsAddWord
|
||||
RET
|
||||
|
||||
; SbfsEntryStart and SbfsEntryEnd describe the file in the entry at DP2. The end is one
|
||||
; past the last block it holds, and a file of nothing holds none, so its end is its start.
|
||||
sbfsEntryBounds:
|
||||
PSHD.2
|
||||
POPD.3
|
||||
PSHD.3
|
||||
POPD.0
|
||||
DPUP.0 0d01
|
||||
SETD.1 SbfsEntryStart
|
||||
CALL sbfsCopyWord
|
||||
SETD.0 SbfsEntryEnd
|
||||
SETD.2 SbfsEntryStart
|
||||
CALL sbfsSetWord
|
||||
PSHD.3
|
||||
POPD.0
|
||||
DPUP.0 0d03
|
||||
SETD.1 SbfsScratch
|
||||
CALL sbfsCopyWord
|
||||
SETD.0 SbfsEntryEnd
|
||||
SETD.2 SbfsScratch
|
||||
CALL sbfsAddWord
|
||||
PSHD.3
|
||||
POPD.0
|
||||
DPUP.0 0d05
|
||||
LDA.0
|
||||
BRA sbfsBoundsDone ; No part block on the end.
|
||||
SETD.0 SbfsEntryEnd
|
||||
SETD.2 SbfsOne
|
||||
CALL sbfsAddWord
|
||||
sbfsBoundsDone:
|
||||
RET
|
||||
|
||||
; Finds a run of SbfsWantBlocks free blocks and puts where it begins in SbfsFileStart.
|
||||
; Q is zero if there was room.
|
||||
;
|
||||
; First fit, walking the directory, because with files laid down contiguously the
|
||||
; directory already says which blocks are spoken for. There is no allocation table to
|
||||
; consult and none to keep right.
|
||||
sbfsAllocate:
|
||||
CALL sbfsFirstData
|
||||
|
||||
sbfsAllocTry:
|
||||
SETD.0 SbfsCandEnd
|
||||
SETD.2 SbfsCandidate
|
||||
CALL sbfsSetWord
|
||||
SETD.0 SbfsCandEnd
|
||||
SETD.2 SbfsWantBlocks
|
||||
CALL sbfsAddWord
|
||||
|
||||
; Past the end of the disk means there is nowhere left to look.
|
||||
SETD.0 SbfsDiskBlocks
|
||||
SETD.2 SbfsCandEnd
|
||||
CALL sbfsCompareWord
|
||||
BRC sbfsAllocNoRoom ; The disk is smaller than where this run would end.
|
||||
|
||||
SETD.0 SbfsDirStart
|
||||
SETD.1 SbfsBlock
|
||||
CALL sbfsCopyWord
|
||||
SETD.0 SbfsDirBlocks
|
||||
DPUP.0 0d01
|
||||
LDA.0
|
||||
SETD.1 SbfsLeft
|
||||
STA.1
|
||||
|
||||
sbfsAllocBlock:
|
||||
CALL sbfsReadBlock
|
||||
BNQ sbfsAllocFailed
|
||||
SETD.1 SbfsBuffer
|
||||
CALL sbfsBufferOut
|
||||
SETD.2 SbfsBuffer
|
||||
INIA 0d8
|
||||
SETD.1 SbfsCount
|
||||
STA.1
|
||||
|
||||
sbfsAllocEntry:
|
||||
LDA.2
|
||||
INIB 0x01
|
||||
AND
|
||||
BRQ sbfsAllocNext ; A free entry holds nothing, so it is in nobody's way.
|
||||
CALL sbfsEntryBounds
|
||||
|
||||
; The comparisons need DP2 for their own purposes, so where this entry is goes on the
|
||||
; Stack for the duration. Working it out again afterwards was the first way this was
|
||||
; written, and it was both longer and wrong.
|
||||
PSHD.2
|
||||
|
||||
; Two runs overlap when each begins before the other ends.
|
||||
SETD.0 SbfsCandidate
|
||||
SETD.2 SbfsEntryEnd
|
||||
CALL sbfsCompareWord
|
||||
BNC sbfsAllocClear
|
||||
SETD.0 SbfsEntryStart
|
||||
SETD.2 SbfsCandEnd
|
||||
CALL sbfsCompareWord
|
||||
BNC sbfsAllocClear
|
||||
|
||||
; They do overlap, so try again from the far end of whatever is in the way.
|
||||
POPD.2
|
||||
SETD.0 SbfsCandidate
|
||||
SETD.2 SbfsEntryEnd
|
||||
CALL sbfsSetWord
|
||||
BRI sbfsAllocTry
|
||||
|
||||
sbfsAllocClear:
|
||||
POPD.2
|
||||
|
||||
sbfsAllocNext:
|
||||
DPUP.2 0d32
|
||||
SETD.1 SbfsCount
|
||||
LDA.1
|
||||
DECA
|
||||
STA.1
|
||||
BNA sbfsAllocEntry
|
||||
|
||||
SETD.0 SbfsBlock
|
||||
CALL sbfsStepWord
|
||||
SETD.1 SbfsLeft
|
||||
LDA.1
|
||||
DECA
|
||||
STA.1
|
||||
BNA sbfsAllocBlock
|
||||
|
||||
; Nothing was in the way, so this is where it goes.
|
||||
SETD.0 SbfsFileStart
|
||||
SETD.2 SbfsCandidate
|
||||
CALL sbfsSetWord
|
||||
RSTA
|
||||
RSTB
|
||||
CCF
|
||||
ADD
|
||||
RET
|
||||
|
||||
sbfsAllocNoRoom:
|
||||
sbfsAllocFailed:
|
||||
RSTA
|
||||
INIB 0d1
|
||||
CCF
|
||||
ADD
|
||||
RET
|
||||
|
||||
; The two byte number at DP0 becomes the one at DP2. Written destination first, so that a
|
||||
; call reads the way an assignment does. sbfsCopyWord goes the other way, source first,
|
||||
; which is a difference worth keeping in mind: getting it backwards here quietly destroyed
|
||||
; a value that had just been worked out.
|
||||
sbfsSetWord:
|
||||
LDA.2
|
||||
STA.0
|
||||
INCD.2
|
||||
INCD.0
|
||||
LDA.2
|
||||
STA.0
|
||||
RET
|
||||
|
||||
; The two byte number at DP0 becomes itself less the one at DP2.
|
||||
sbfsSubWord:
|
||||
DPUP.0 0d01
|
||||
DPUP.2 0d01
|
||||
LDA.0
|
||||
LDB.2
|
||||
CCF
|
||||
SUB
|
||||
MVQA
|
||||
STA.0
|
||||
DPDN.0 0d01
|
||||
DPDN.2 0d01
|
||||
LDA.0
|
||||
LDB.2
|
||||
SUB ; Borrows in from the low half.
|
||||
MVQA
|
||||
STA.0
|
||||
RET
|
||||
|
||||
; How many blocks a file of SbfsFileBlocks and SbfsFileTail actually occupies, into
|
||||
; SbfsWantBlocks. A file of nothing occupies none.
|
||||
sbfsFileExtent:
|
||||
SETD.0 SbfsWantBlocks
|
||||
SETD.2 SbfsFileBlocks
|
||||
CALL sbfsSetWord
|
||||
SETD.0 SbfsFileTail
|
||||
LDA.0
|
||||
BRA sbfsExtentDone
|
||||
SETD.0 SbfsWantBlocks
|
||||
SETD.2 SbfsOne
|
||||
CALL sbfsAddWord
|
||||
sbfsExtentDone:
|
||||
RET
|
||||
|
||||
; Makes a file. DP0 names it, and SbfsFileBlocks with SbfsFileTail say how big it is,
|
||||
; which has to be settled before it is made because nothing here can grow one afterwards.
|
||||
; Q is zero if it was made, and then SbfsFileStart says where its blocks are.
|
||||
sbfsCreate:
|
||||
SETD.1 SbfsWanted
|
||||
CALL sbfsKeepName
|
||||
|
||||
CALL sbfsFileExtent
|
||||
CALL sbfsAllocate
|
||||
BNQ sbfsCreateFailed
|
||||
|
||||
; Walk the directory for an entry nobody is using.
|
||||
SETD.0 SbfsDirStart
|
||||
SETD.1 SbfsBlock
|
||||
CALL sbfsCopyWord
|
||||
SETD.0 SbfsDirBlocks
|
||||
DPUP.0 0d01
|
||||
LDA.0
|
||||
SETD.1 SbfsLeft
|
||||
STA.1
|
||||
|
||||
sbfsCreateBlock:
|
||||
CALL sbfsReadBlock
|
||||
BNQ sbfsCreateFailed
|
||||
SETD.1 SbfsBuffer
|
||||
CALL sbfsBufferOut
|
||||
SETD.2 SbfsBuffer
|
||||
INIA 0d8
|
||||
SETD.1 SbfsCount
|
||||
STA.1
|
||||
|
||||
sbfsCreateEntry:
|
||||
LDA.2
|
||||
INIB 0x01
|
||||
AND
|
||||
BRQ sbfsCreateFill ; This one is free.
|
||||
DPUP.2 0d32
|
||||
SETD.1 SbfsCount
|
||||
LDA.1
|
||||
DECA
|
||||
STA.1
|
||||
BNA sbfsCreateEntry
|
||||
|
||||
SETD.0 SbfsBlock
|
||||
CALL sbfsStepWord
|
||||
SETD.1 SbfsLeft
|
||||
LDA.1
|
||||
DECA
|
||||
STA.1
|
||||
BNA sbfsCreateBlock
|
||||
|
||||
sbfsCreateFull:
|
||||
RSTA
|
||||
INIB 0d1
|
||||
CCF
|
||||
ADD ; Every entry is taken.
|
||||
RET
|
||||
|
||||
sbfsCreateFill:
|
||||
; DP2 is on the entry. Fill it in, then put the whole block back on the disk.
|
||||
PSHD.2
|
||||
POPD.3
|
||||
INIA 0x01
|
||||
STA.3 ; In use.
|
||||
|
||||
PSHD.3
|
||||
POPD.1
|
||||
DPUP.1 0d01
|
||||
SETD.0 SbfsFileStart
|
||||
CALL sbfsCopyWord
|
||||
|
||||
PSHD.3
|
||||
POPD.1
|
||||
DPUP.1 0d03
|
||||
SETD.0 SbfsFileBlocks
|
||||
CALL sbfsCopyWord
|
||||
|
||||
PSHD.3
|
||||
POPD.1
|
||||
DPUP.1 0d05
|
||||
SETD.0 SbfsFileTail
|
||||
LDA.0
|
||||
STA.1
|
||||
|
||||
PSHD.3
|
||||
POPD.1
|
||||
DPUP.1 0d06
|
||||
SETD.0 SbfsWanted
|
||||
INIA 0d22
|
||||
SETD.2 SbfsCount
|
||||
STA.2
|
||||
sbfsCreateName:
|
||||
LDA.0
|
||||
STA.1
|
||||
INCD.0
|
||||
INCD.1
|
||||
LDA.2
|
||||
DECA
|
||||
STA.2
|
||||
BNA sbfsCreateName
|
||||
|
||||
SETD.1 SbfsBuffer
|
||||
CALL sbfsBufferIn
|
||||
CALL sbfsWriteBlock
|
||||
BNQ sbfsCreateFailed
|
||||
|
||||
; The free count is a note rather than the truth, but it should still be kept right.
|
||||
RSTA
|
||||
SETD.0 SbfsBlock
|
||||
STA.0
|
||||
INCD.0
|
||||
STA.0
|
||||
CALL sbfsReadBlock
|
||||
BNQ sbfsCreateFailed
|
||||
SETD.1 SbfsBuffer
|
||||
CALL sbfsBufferOut
|
||||
SETD.0 SbfsBuffer
|
||||
DPUP.0 0d12
|
||||
SETD.2 SbfsWantBlocks
|
||||
CALL sbfsSubWord
|
||||
SETD.1 SbfsBuffer
|
||||
CALL sbfsBufferIn
|
||||
CALL sbfsWriteBlock
|
||||
RET
|
||||
|
||||
sbfsCreateFailed:
|
||||
RSTA
|
||||
INIB 0d1
|
||||
CCF
|
||||
ADD
|
||||
RET
|
||||
|
||||
; Writes a file that sbfsCreate made, from Data Memory at DP1. Q is zero if it worked.
|
||||
;
|
||||
; Whole blocks go out whole, so whatever follows the file in Data Memory ends up in the
|
||||
; unused tail of its last block. That is harmless, since the tail in the entry says where
|
||||
; the file stops, but it does mean a block can hold a little of whatever was next to it.
|
||||
sbfsWriteFile:
|
||||
PSHD.1
|
||||
POPD.3
|
||||
SETD.0 SbfsBlock
|
||||
SETD.2 SbfsFileStart
|
||||
CALL sbfsSetWord
|
||||
CALL sbfsFileExtent
|
||||
SETD.0 SbfsWantBlocks
|
||||
DPUP.0 0d01
|
||||
LDA.0
|
||||
SETD.1 SbfsLeft
|
||||
STA.1
|
||||
BRA sbfsWriteDone ; A file of nothing has nothing to write.
|
||||
|
||||
sbfsWriteLoop:
|
||||
PSHD.3
|
||||
POPD.1
|
||||
CALL sbfsBufferIn
|
||||
CALL sbfsWriteBlock
|
||||
BNQ sbfsWriteFailed
|
||||
DPUP.3 0xFF
|
||||
DPUP.3 0x01
|
||||
SETD.0 SbfsBlock
|
||||
CALL sbfsStepWord
|
||||
SETD.0 SbfsLeft
|
||||
LDA.0
|
||||
DECA
|
||||
STA.0
|
||||
BNA sbfsWriteLoop
|
||||
|
||||
sbfsWriteDone:
|
||||
RSTA
|
||||
RSTB
|
||||
CCF
|
||||
ADD
|
||||
RET
|
||||
|
||||
sbfsWriteFailed:
|
||||
RSTA
|
||||
INIB 0d1
|
||||
CCF
|
||||
ADD
|
||||
RET
|
||||
|
||||
; Copies a whole block from Data Memory at DP1 into the disk's buffer, which is the other
|
||||
; way round from sbfsBufferOut.
|
||||
sbfsBufferIn:
|
||||
INIA 0d1
|
||||
OUTA 0xE0
|
||||
PSHD.1
|
||||
POPA
|
||||
POPB
|
||||
OUTB 0xE1
|
||||
OUTA 0xE2
|
||||
INIA 0d3
|
||||
OUTA 0xE3
|
||||
RSTA
|
||||
OUTA 0xE4
|
||||
OUTA 0xE5
|
||||
INIA 0x01
|
||||
OUTA 0xE6
|
||||
RSTA
|
||||
OUTA 0xE7
|
||||
INIA 0x01
|
||||
OUTA 0xE8
|
||||
RET
|
||||
|
||||
; Puts the disk's buffer down as the block named by SbfsBlock. Q is zero if it worked.
|
||||
sbfsWriteBlock:
|
||||
SETD.0 SbfsBlock
|
||||
LDA.0
|
||||
OUTA 0x20
|
||||
INCD.0
|
||||
LDA.0
|
||||
OUTA 0x21
|
||||
INIA 0x02
|
||||
OUTA 0x22
|
||||
INA 0x23
|
||||
INIB 0x02
|
||||
AND
|
||||
RET
|
||||
|
||||
; Reads the block named by SbfsBlock into the disk's buffer. Q is zero if it worked.
|
||||
sbfsReadBlock:
|
||||
SETD.0 SbfsBlock
|
||||
LDA.0
|
||||
OUTA 0x20
|
||||
INCD.0
|
||||
LDA.0
|
||||
OUTA 0x21
|
||||
INIA 0x01
|
||||
OUTA 0x22
|
||||
INA 0x23
|
||||
INIB 0x02
|
||||
AND ; Q is the error bit, so zero means it worked.
|
||||
RET
|
||||
|
||||
; Copies the disk's buffer, a whole block of it, to Data Memory at DP1.
|
||||
sbfsBufferOut:
|
||||
INIA 0d3
|
||||
OUTA 0xE0
|
||||
RSTA
|
||||
OUTA 0xE1
|
||||
OUTA 0xE2
|
||||
INIA 0d1
|
||||
OUTA 0xE3
|
||||
PSHD.1
|
||||
POPA
|
||||
POPB
|
||||
OUTB 0xE4
|
||||
OUTA 0xE5
|
||||
INIA 0x01
|
||||
OUTA 0xE6
|
||||
RSTA
|
||||
OUTA 0xE7 ; A whole block.
|
||||
INIA 0x01
|
||||
OUTA 0xE8
|
||||
RET
|
||||
|
||||
; Two bytes from DP0 to DP1, most significant first, the way every number on a SplitBit
|
||||
; disk is stored.
|
||||
sbfsCopyWord:
|
||||
LDA.0
|
||||
STA.1
|
||||
INCD.0
|
||||
INCD.1
|
||||
LDA.0
|
||||
STA.1
|
||||
RET
|
||||
|
||||
; Adds one to the two byte number at DP0.
|
||||
sbfsStepWord:
|
||||
DPUP.0 0d01
|
||||
LDA.0
|
||||
INCA
|
||||
STA.0
|
||||
BNC sbfsStepDone ; It did not wrap, so the high byte is untouched.
|
||||
DPDN.0 0d01
|
||||
LDA.0
|
||||
INCA
|
||||
STA.0
|
||||
sbfsStepDone:
|
||||
RET
|
||||
|
||||
; The two byte number at DP0 becomes itself plus the one at DP2. Only memory changes, so
|
||||
; it survives the return: a routine cannot hand back a pointer or a register.
|
||||
sbfsAddWord:
|
||||
DPUP.0 0d01
|
||||
DPUP.2 0d01
|
||||
LDA.0
|
||||
LDB.2
|
||||
CCF
|
||||
ADD
|
||||
MVQA
|
||||
STA.0
|
||||
DPDN.0 0d01
|
||||
DPDN.2 0d01
|
||||
LDA.0
|
||||
LDB.2
|
||||
ADD ; Carries in from the low half. Nothing between the two touches it.
|
||||
MVQA
|
||||
STA.0
|
||||
RET
|
||||
|
||||
; Compares the two byte number at DP0 with the one at DP2. Q is zero if they are equal,
|
||||
; and the Carry Flag is set if the one at DP0 is the smaller. Both come back, because
|
||||
; neither Q nor the Status register is put back by a return.
|
||||
sbfsCompareWord:
|
||||
LDA.0
|
||||
LDB.2
|
||||
CCF
|
||||
SUB ; The high bytes settle it unless they are the same.
|
||||
BNQ sbfsCompareDone
|
||||
INCD.0
|
||||
INCD.2
|
||||
LDA.0
|
||||
LDB.2
|
||||
CCF
|
||||
SUB
|
||||
sbfsCompareDone:
|
||||
RET
|
||||
|
||||
; The byte at DP0 against the byte at DP2. Q is zero if they are the same.
|
||||
sbfsSameByte:
|
||||
LDA.0
|
||||
LDB.2
|
||||
XOR
|
||||
RET
|
||||
|
||||
#Data
|
||||
|
||||
SbfsMagic:
|
||||
"SBFS"
|
||||
|
||||
SbfsDirStart:
|
||||
0x00 0x00
|
||||
SbfsDirBlocks:
|
||||
0x00 0x00
|
||||
SbfsFileStart:
|
||||
0x00 0x00
|
||||
SbfsFileBlocks:
|
||||
0x00 0x00
|
||||
SbfsFileTail:
|
||||
0x00
|
||||
SbfsBlock:
|
||||
0x00 0x00
|
||||
SbfsDiskBlocks:
|
||||
0x00 0x00
|
||||
SbfsWantBlocks:
|
||||
0x00 0x00
|
||||
SbfsCandidate:
|
||||
0x00 0x00
|
||||
SbfsCandEnd:
|
||||
0x00 0x00
|
||||
SbfsEntryStart:
|
||||
0x00 0x00
|
||||
SbfsEntryEnd:
|
||||
0x00 0x00
|
||||
SbfsScratch:
|
||||
0x00 0x00
|
||||
SbfsOne:
|
||||
0x00 0x01
|
||||
SbfsCount:
|
||||
0x00
|
||||
SbfsLeft:
|
||||
0x00
|
||||
|
||||
SbfsWanted:
|
||||
#Reserve 0d22
|
||||
|
||||
SbfsBuffer:
|
||||
#Reserve 0d256
|
||||
@@ -0,0 +1,33 @@
|
||||
; A program meant to be loaded off a disk rather than booted from.
|
||||
;
|
||||
; It is assembled for where it will live. Reserving the front of each segment puts the
|
||||
; first thing in it at a known address, and everything after that follows, so the labels
|
||||
; inside are already right for the place the loader will put it. Nothing relocates
|
||||
; anything: this works because the addresses here and the addresses in its header say the
|
||||
; same thing.
|
||||
;
|
||||
; The loader keeps below both of these, which is the whole of the arrangement.
|
||||
|
||||
#Program
|
||||
|
||||
#Reserve 0x2000 ; This program's code lives from 0x2000.
|
||||
|
||||
hello:
|
||||
SETD.0 HelloText
|
||||
helloLoop:
|
||||
LDA.0
|
||||
BRA helloDone
|
||||
OUTA 0x00
|
||||
INCD.0
|
||||
BRI helloLoop
|
||||
helloDone:
|
||||
INIA 0x0A
|
||||
OUTA 0x00
|
||||
HALT
|
||||
|
||||
#Data
|
||||
|
||||
#Reserve 0x1000 ; And its data from 0x1000.
|
||||
|
||||
HelloText:
|
||||
"loaded off a disk, with a string and a loop of its own"
|
||||
@@ -0,0 +1,184 @@
|
||||
; Loads a program off a disk and runs it.
|
||||
;
|
||||
; Everything this needs already existed: the filesystem reads the file, the controller
|
||||
; writes Program Memory, and BRD jumps to an address worked out at run time. The only new
|
||||
; part is the sixteen bytes on the front of a loadable program that say where it goes.
|
||||
;
|
||||
; Nothing relocates anything. The program is put exactly where its header asks, and that
|
||||
; has to be where it was assembled for, or every branch inside it points somewhere wrong.
|
||||
; This loader therefore keeps out of the way: its own code and data sit above the
|
||||
; addresses the loaded program claims, which is an arrangement rather than a mechanism.
|
||||
;
|
||||
; Correct output is:
|
||||
; loader
|
||||
; loaded off a disk, with a string and a loop of its own
|
||||
|
||||
#Include print.asm
|
||||
#Include sbfs.asm
|
||||
|
||||
#Program
|
||||
|
||||
; print.asm branches here at the top of the Program Segment, so this is where the machine
|
||||
; arrives whether or not the Boot Vector is obeyed. Naming it in the Vector Segment as
|
||||
; well says plainly where the program begins.
|
||||
start:
|
||||
SETD.0 LoaderName
|
||||
CALL printString
|
||||
CALL lineFeed
|
||||
|
||||
CALL sbfsMount
|
||||
BNQ loaderNoDisk
|
||||
|
||||
SETD.0 ProgramName
|
||||
CALL sbfsFind
|
||||
BNQ loaderMissing
|
||||
|
||||
SETD.1 Staging
|
||||
CALL sbfsRead
|
||||
BNQ loaderUnreadable
|
||||
|
||||
; "SBEX", or it is not a program at all. Loading a text file would otherwise put
|
||||
; nonsense into Program Memory and jump into it.
|
||||
SETD.0 Staging
|
||||
SETD.2 ExecMagic
|
||||
INIA 0d4
|
||||
SETD.1 LoaderCount
|
||||
STA.1
|
||||
loaderMagicLoop:
|
||||
LDA.0
|
||||
LDB.2
|
||||
XOR
|
||||
BNQ loaderNotProgram
|
||||
INCD.0
|
||||
INCD.2
|
||||
LDA.1
|
||||
DECA
|
||||
STA.1
|
||||
BNA loaderMagicLoop
|
||||
|
||||
SETD.0 Staging
|
||||
DPUP.0 0d04
|
||||
LDA.0
|
||||
INIB 0d1
|
||||
XOR
|
||||
BNQ loaderWrongVersion
|
||||
|
||||
; The code first. Source is the bytes just past the header; destination is wherever the
|
||||
; header says the code lives.
|
||||
INIA 0d1
|
||||
OUTA 0xE0 ; SourceBank: Data Memory, where the file was read to.
|
||||
SETD.0 Staging
|
||||
DPUP.0 0d16
|
||||
PSHD.0
|
||||
POPA
|
||||
POPB
|
||||
OUTB 0xE1
|
||||
OUTA 0xE2
|
||||
|
||||
RSTA
|
||||
OUTA 0xE3 ; DestBank: Program Memory.
|
||||
; Where the code goes. Read and written out a byte at a time rather than through a
|
||||
; subroutine, because a subroutine could not hand the two bytes back: CALL preserves
|
||||
; A and B along with the first three Data Pointers, so anything a routine puts in them
|
||||
; is undone by its own return.
|
||||
SETD.0 Staging
|
||||
DPUP.0 0d06
|
||||
LDA.0
|
||||
OUTA 0xE4
|
||||
INCD.0
|
||||
LDA.0
|
||||
OUTA 0xE5
|
||||
|
||||
; How much of it there is.
|
||||
SETD.0 Staging
|
||||
DPUP.0 0d10
|
||||
LDA.0
|
||||
OUTA 0xE6
|
||||
INCD.0
|
||||
LDA.0
|
||||
OUTA 0xE7
|
||||
|
||||
INIA 0x01
|
||||
OUTA 0xE8 ; Blit.
|
||||
|
||||
; Then the data. A blit leaves its addresses past whatever it touched, so the source is
|
||||
; already sitting on the first byte of the data and only the destination changes.
|
||||
INIA 0d1
|
||||
OUTA 0xE3 ; DestBank: Data Memory.
|
||||
SETD.0 Staging
|
||||
DPUP.0 0d12
|
||||
LDA.0
|
||||
OUTA 0xE4
|
||||
INCD.0
|
||||
LDA.0
|
||||
OUTA 0xE5
|
||||
|
||||
SETD.0 Staging
|
||||
DPUP.0 0d14
|
||||
LDA.0
|
||||
OUTA 0xE6
|
||||
INCD.0
|
||||
LDA.0
|
||||
OUTA 0xE7
|
||||
|
||||
INIA 0x01
|
||||
OUTA 0xE8 ; Blit.
|
||||
|
||||
; And go. The entry address is a number until BRD makes it a place. DP3 is built from
|
||||
; the two bytes by hand, since it is the pointer nothing puts back.
|
||||
SETD.0 Staging
|
||||
DPUP.0 0d08
|
||||
LDA.0
|
||||
INCD.0
|
||||
LDB.0
|
||||
PSHA
|
||||
PSHB
|
||||
POPD.3
|
||||
BRD.3
|
||||
|
||||
loaderNoDisk:
|
||||
SETD.0 NoDisk
|
||||
BRI loaderComplain
|
||||
loaderMissing:
|
||||
SETD.0 NotThere
|
||||
BRI loaderComplain
|
||||
loaderUnreadable:
|
||||
SETD.0 Unreadable
|
||||
BRI loaderComplain
|
||||
loaderNotProgram:
|
||||
SETD.0 NotProgram
|
||||
BRI loaderComplain
|
||||
loaderWrongVersion:
|
||||
SETD.0 WrongVersion
|
||||
loaderComplain:
|
||||
CALL printString
|
||||
CALL lineFeed
|
||||
HALT
|
||||
|
||||
#Data
|
||||
|
||||
ExecMagic:
|
||||
"SBEX"
|
||||
LoaderName:
|
||||
"loader"
|
||||
ProgramName:
|
||||
"hello.sbx"
|
||||
NoDisk:
|
||||
"no disk"
|
||||
NotThere:
|
||||
"no such program"
|
||||
Unreadable:
|
||||
"could not read it"
|
||||
NotProgram:
|
||||
"not a program"
|
||||
WrongVersion:
|
||||
"a version I do not know"
|
||||
LoaderCount:
|
||||
0x00
|
||||
|
||||
Staging:
|
||||
#Reserve 0d512
|
||||
|
||||
#Vectors
|
||||
|
||||
Boot start
|
||||
@@ -0,0 +1,117 @@
|
||||
; The negative sense branches.
|
||||
;
|
||||
; A quarter of every conditional branch in the corpus used to be a branch over an
|
||||
; unconditional one, because only "branch if zero" existed. Each of those needed a label
|
||||
; invented purely to be jumped past, which is a cost in names as much as in bytes.
|
||||
;
|
||||
; These test the register directly, the way the positive ones do. A branch on this machine
|
||||
; never depends on which instruction ran last, except for the two that read the Carry Flag
|
||||
; and say so in their names.
|
||||
;
|
||||
; Each case is checked both ways round, so a branch that always went the same way would
|
||||
; be caught rather than looking correct half the time.
|
||||
;
|
||||
; Correct output is:
|
||||
; QAB C qab c
|
||||
; 9876543210
|
||||
|
||||
#Include print.asm
|
||||
|
||||
#Program
|
||||
|
||||
start:
|
||||
; ---- Each one taken when it should be. ----
|
||||
RSTA
|
||||
INIB 0d1
|
||||
CCF
|
||||
ADD ; Q is 1, so not zero.
|
||||
BNQ qTaken
|
||||
BRI wrong
|
||||
qTaken:
|
||||
INIA 0d81 ; 'Q'
|
||||
OUTA 0x00
|
||||
|
||||
INIA 0d5
|
||||
BNA aTaken
|
||||
BRI wrong
|
||||
aTaken:
|
||||
INIA 0d65 ; 'A'
|
||||
OUTA 0x00
|
||||
|
||||
INIB 0d5
|
||||
BNB bTaken
|
||||
BRI wrong
|
||||
bTaken:
|
||||
INIA 0d66 ; 'B'
|
||||
OUTA 0x00
|
||||
|
||||
CCF
|
||||
BNC cTaken
|
||||
BRI wrong
|
||||
cTaken:
|
||||
CALL blankSpace
|
||||
INIA 0d67 ; 'C'
|
||||
OUTA 0x00
|
||||
CALL blankSpace
|
||||
|
||||
; ---- And each one not taken when it should not be. ----
|
||||
RSTA
|
||||
RSTB
|
||||
CCF
|
||||
ADD ; Q is zero.
|
||||
BNQ wrong
|
||||
INIA 0d113 ; 'q'
|
||||
OUTA 0x00
|
||||
|
||||
RSTA
|
||||
BNA wrong
|
||||
INIA 0d97 ; 'a'
|
||||
OUTA 0x00
|
||||
|
||||
RSTB
|
||||
BNB wrong
|
||||
INIA 0d98 ; 'b'
|
||||
OUTA 0x00
|
||||
|
||||
; Set the carry by overflowing, then check BNC does not take it.
|
||||
INIA 0xFF
|
||||
INIB 0x01
|
||||
CCF
|
||||
ADD
|
||||
BNC wrong
|
||||
CALL blankSpace
|
||||
INIA 0d99 ; 'c'
|
||||
OUTA 0x00
|
||||
CALL lineFeed
|
||||
|
||||
; ---- A countdown, which is what the missing sense was mostly wanted for. ----
|
||||
; Before these existed this loop needed a label to jump over, and now it does not.
|
||||
INIA 0d10
|
||||
SETD.0 Count
|
||||
STA.0
|
||||
countLoop:
|
||||
LDA.0
|
||||
DECA ; Ten down to one becomes nine down to zero.
|
||||
INIB 0d48 ; '0'
|
||||
CCF
|
||||
ADD
|
||||
MVQA
|
||||
OUTA 0x00
|
||||
LDA.0
|
||||
DECA
|
||||
STA.0
|
||||
BNA countLoop ; One instruction where it used to take two and a label.
|
||||
CALL lineFeed
|
||||
HALT
|
||||
|
||||
wrong:
|
||||
SETD.0 Wrong
|
||||
CALL printString
|
||||
CALL lineFeed
|
||||
HALT
|
||||
|
||||
#Data
|
||||
Wrong:
|
||||
"a branch went the wrong way"
|
||||
Count:
|
||||
0x00
|
||||
@@ -0,0 +1,42 @@
|
||||
; A write protected disk.
|
||||
;
|
||||
; The bar is in the device, not in the filesystem. A flag in a superblock can be got
|
||||
; around by writing blocks directly; this cannot be got around at all. It is the tab on
|
||||
; the side of a floppy rather than a note asking politely.
|
||||
;
|
||||
; A disk is read only if it was attached that way, or if the host will not let its image
|
||||
; be written. The machine cannot tell the two apart and does not need to.
|
||||
;
|
||||
; Status bit 2 says the disk is protected. Unlike the busy and error bits it describes the
|
||||
; medium rather than the last operation, so it reads true before anything has been asked
|
||||
; of the disk at all.
|
||||
;
|
||||
; Correct output is:
|
||||
; 04 protected, before anything has been attempted
|
||||
; 04 a read is allowed and does not disturb the bit
|
||||
; 06 a write is barred: protected, and the operation failed
|
||||
|
||||
#Include print.asm
|
||||
|
||||
#Program
|
||||
|
||||
start:
|
||||
INA 0x23
|
||||
CALL printByteHex
|
||||
CALL lineFeed
|
||||
|
||||
RSTA
|
||||
OUTA 0x20
|
||||
OUTA 0x21 ; Block 0.
|
||||
INIA 0x01
|
||||
OUTA 0x22 ; Read. Reading a protected disk is ordinary.
|
||||
INA 0x23
|
||||
CALL printByteHex
|
||||
CALL lineFeed
|
||||
|
||||
INIA 0x02
|
||||
OUTA 0x22 ; Write. Barred by the device.
|
||||
INA 0x23
|
||||
CALL printByteHex
|
||||
CALL lineFeed
|
||||
HALT
|
||||
@@ -0,0 +1,143 @@
|
||||
; Storage.
|
||||
;
|
||||
; The disk is a block device and nothing more. It knows numbered blocks of 256 bytes and
|
||||
; has never heard of a file, which is deliberate: a filesystem is software this machine
|
||||
; will run rather than something the host does on its behalf. A disk that understood
|
||||
; filenames would be the emulator doing the work while the machine pretended it had.
|
||||
;
|
||||
; Its buffer is one block, and like any memory a device brings, it is unreachable until
|
||||
; it is registered as a bank and gone through with the controller.
|
||||
;
|
||||
; Between writing and reading the buffer is scrubbed, so a stale copy of the message
|
||||
; sitting in it cannot make a broken read look like a working one.
|
||||
;
|
||||
; The image is made fresh for every run of the suite, so block 3 is zeroes before this
|
||||
; program touches it.
|
||||
;
|
||||
; Correct output is:
|
||||
; 00 the write worked
|
||||
; from the disk block 3 came back
|
||||
; 02 a block past the end of the image is an error, not a fault
|
||||
|
||||
#Include print.asm
|
||||
|
||||
#Program
|
||||
|
||||
start:
|
||||
; The disk's buffer becomes bank 5.
|
||||
INIA 0d5
|
||||
OUTA 0xE3
|
||||
INIA 0x20
|
||||
OUTA 0xE2
|
||||
INIA 0x03
|
||||
OUTA 0xE8
|
||||
|
||||
; Put the message into the buffer and write it to block 3.
|
||||
SETD.0 Message
|
||||
CALL aimSourceData
|
||||
CALL aimDestBuffer
|
||||
INIA 0d16
|
||||
CALL setLength
|
||||
INIA 0x01
|
||||
OUTA 0xE8
|
||||
|
||||
CALL selectBlockThree
|
||||
INIA 0x02
|
||||
OUTA 0x22 ; Write.
|
||||
INA 0x23
|
||||
CALL printByteHex
|
||||
CALL lineFeed
|
||||
|
||||
; Scrub the buffer, so what comes back has to have come off the disk.
|
||||
CALL aimDestBuffer
|
||||
INIA 0x2D ; '-' as the fill byte.
|
||||
OUTA 0xE2
|
||||
INIA 0d16
|
||||
CALL setLength
|
||||
INIA 0x02
|
||||
OUTA 0xE8 ; Fill.
|
||||
|
||||
CALL selectBlockThree
|
||||
INIA 0x01
|
||||
OUTA 0x22 ; Read.
|
||||
|
||||
; Bring the block into Data Memory and print it.
|
||||
INIA 0d5
|
||||
OUTA 0xE0
|
||||
RSTA
|
||||
OUTA 0xE1
|
||||
OUTA 0xE2
|
||||
SETD.0 Landing
|
||||
CALL aimDestData
|
||||
INIA 0d16
|
||||
CALL setLength
|
||||
INIA 0x01
|
||||
OUTA 0xE8
|
||||
|
||||
SETD.0 Landing
|
||||
CALL printString
|
||||
CALL lineFeed
|
||||
|
||||
; A block past the end of the image says so in Status rather than stopping the machine.
|
||||
; A disk that cannot read a block is an ordinary thing that happens to working programs.
|
||||
INIA 0xFF
|
||||
OUTA 0x20
|
||||
INIA 0xFF
|
||||
OUTA 0x21
|
||||
INIA 0x01
|
||||
OUTA 0x22
|
||||
INA 0x23
|
||||
CALL printByteHex
|
||||
CALL lineFeed
|
||||
HALT
|
||||
|
||||
selectBlockThree:
|
||||
RSTA
|
||||
OUTA 0x20
|
||||
INIA 0d3
|
||||
OUTA 0x21
|
||||
RET
|
||||
|
||||
aimSourceData:
|
||||
INIA 0d1
|
||||
OUTA 0xE0
|
||||
PSHD.0
|
||||
POPA
|
||||
POPB
|
||||
OUTB 0xE1
|
||||
OUTA 0xE2
|
||||
RET
|
||||
|
||||
aimDestData:
|
||||
INIA 0d1
|
||||
OUTA 0xE3
|
||||
PSHD.0
|
||||
POPA
|
||||
POPB
|
||||
OUTB 0xE4
|
||||
OUTA 0xE5
|
||||
RET
|
||||
|
||||
aimDestBuffer:
|
||||
INIA 0d5
|
||||
OUTA 0xE3
|
||||
RSTA
|
||||
OUTA 0xE4
|
||||
OUTA 0xE5
|
||||
RET
|
||||
|
||||
setLength:
|
||||
PSHA
|
||||
RSTA
|
||||
OUTA 0xE6
|
||||
POPA
|
||||
OUTA 0xE7
|
||||
RET
|
||||
|
||||
#Data
|
||||
|
||||
Message:
|
||||
"from the disk"
|
||||
|
||||
Landing:
|
||||
#Reserve 0d20
|
||||
@@ -0,0 +1,156 @@
|
||||
; Reading the SplitBit Filesystem.
|
||||
;
|
||||
; The disk this reads was made by SplitDisk, which is the other implementation of the same
|
||||
; format. That is what makes this worth running: the library is being checked against
|
||||
; something written by different code working from the same written specification, rather
|
||||
; than against itself. If the two ever drift, this is where it shows.
|
||||
;
|
||||
; The cases here are the ones most likely to be wrong. across.txt is longer than a block,
|
||||
; so reading it has to carry on from one to the next. Its entry, and the two after it, are
|
||||
; in the second directory block, so finding it has to walk past the end of the first.
|
||||
; aName22CharactersLong! fills the name field exactly, so there is no zero on the end of
|
||||
; it to stop a comparison. empty.txt has no blocks at all.
|
||||
;
|
||||
; Correct output is:
|
||||
; greeting.txt 0000 11 hello from a file
|
||||
; across.txt 0002 BC ABCDEFGH...
|
||||
; aName22CharactersLong! 0000 16 exactly twenty two!!!!
|
||||
; empty.txt 0000 00
|
||||
; absent.txt missing
|
||||
|
||||
#Include print.asm
|
||||
#Include sbfs.asm
|
||||
|
||||
#Program
|
||||
|
||||
start:
|
||||
CALL sbfsMount
|
||||
BRQ mounted
|
||||
SETD.0 NoMount
|
||||
CALL printString
|
||||
CALL lineFeed
|
||||
HALT
|
||||
|
||||
mounted:
|
||||
SETD.0 WantGreeting
|
||||
CALL showFile
|
||||
SETD.0 WantAcross
|
||||
CALL showFile
|
||||
SETD.0 WantLongName
|
||||
CALL showFile
|
||||
SETD.0 WantEmpty
|
||||
CALL showFile
|
||||
SETD.0 WantAbsent
|
||||
CALL showFile
|
||||
HALT
|
||||
|
||||
; DP0 names a file. Prints its name, what the directory says about it, and then the file
|
||||
; itself. DP3 keeps the name across the calls, because it is the pointer a CALL does not
|
||||
; put back.
|
||||
showFile:
|
||||
PSHD.0
|
||||
POPD.3
|
||||
CALL printString
|
||||
CALL blankSpace
|
||||
|
||||
PSHD.3
|
||||
POPD.0
|
||||
CALL sbfsFind
|
||||
BRQ showFound
|
||||
SETD.0 Missing
|
||||
CALL printString
|
||||
CALL lineFeed
|
||||
RET
|
||||
|
||||
showFound:
|
||||
SETD.0 SbfsFileBlocks
|
||||
LDA.0
|
||||
CALL printByteHex
|
||||
INCD.0
|
||||
LDA.0
|
||||
CALL printByteHex
|
||||
CALL blankSpace
|
||||
SETD.0 SbfsFileTail
|
||||
LDA.0
|
||||
CALL printByteHex
|
||||
CALL blankSpace
|
||||
|
||||
SETD.1 Landing
|
||||
CALL sbfsRead
|
||||
BRQ showContents
|
||||
SETD.0 Failed
|
||||
CALL printString
|
||||
CALL lineFeed
|
||||
RET
|
||||
|
||||
; Writes out exactly as many bytes as the file has, and no more. The length is the block
|
||||
; count and the tail side by side: blocks times 256 plus the tail is the same as putting
|
||||
; the count in the high byte and the tail in the low one.
|
||||
showContents:
|
||||
SETD.0 Landing
|
||||
SETD.1 SbfsFileBlocks
|
||||
DPUP.1 0d01
|
||||
LDA.1
|
||||
SETD.1 LeftHigh
|
||||
STA.1
|
||||
SETD.1 SbfsFileTail
|
||||
LDA.1
|
||||
SETD.1 LeftLow
|
||||
STA.1
|
||||
|
||||
showLoop:
|
||||
SETD.1 LeftHigh
|
||||
LDA.1
|
||||
SETD.2 LeftLow
|
||||
LDB.2
|
||||
OR
|
||||
BRQ showEnd ; Nothing left of it.
|
||||
|
||||
LDA.0
|
||||
OUTA 0x00
|
||||
INCD.0
|
||||
|
||||
SETD.1 LeftLow
|
||||
LDA.1
|
||||
DECA
|
||||
STA.1
|
||||
BRC showBorrow ; It went under, so the high byte owes one.
|
||||
BRI showLoop
|
||||
showBorrow:
|
||||
SETD.1 LeftHigh
|
||||
LDA.1
|
||||
DECA
|
||||
STA.1
|
||||
BRI showLoop
|
||||
|
||||
showEnd:
|
||||
CALL lineFeed
|
||||
RET
|
||||
|
||||
#Data
|
||||
|
||||
WantGreeting:
|
||||
"greeting.txt"
|
||||
WantAcross:
|
||||
"across.txt"
|
||||
WantLongName:
|
||||
"aName22CharactersLong!"
|
||||
WantEmpty:
|
||||
"empty.txt"
|
||||
WantAbsent:
|
||||
"absent.txt"
|
||||
|
||||
NoMount:
|
||||
"no mount"
|
||||
Missing:
|
||||
"missing"
|
||||
Failed:
|
||||
"failed"
|
||||
|
||||
LeftHigh:
|
||||
0x00
|
||||
LeftLow:
|
||||
0x00
|
||||
|
||||
Landing:
|
||||
#Reserve 0d1024
|
||||
@@ -0,0 +1,157 @@
|
||||
; Writing the SplitBit Filesystem.
|
||||
;
|
||||
; A file's size is settled when it is made, because nothing here can grow one afterwards:
|
||||
; files are laid down contiguously, so the block after a file usually belongs to somebody
|
||||
; else. That is the bargain the format makes, and it is why the size comes first.
|
||||
;
|
||||
; The disk already has a file on it, so the second one has to be put somewhere that does
|
||||
; not tread on it. There is no allocation table to consult: with contiguous files the
|
||||
; directory already says which blocks are spoken for, so finding room is a walk through
|
||||
; the entries rather than a lookup.
|
||||
;
|
||||
; What is written is read straight back, through the same directory, so a file that went
|
||||
; down in the wrong place would come back wrong rather than looking plausible.
|
||||
;
|
||||
; Correct output is:
|
||||
; here.txt 0002
|
||||
; first.txt 0003 written by SplitBit itself
|
||||
; second.txt 0004 and a second one after it
|
||||
|
||||
#Include print.asm
|
||||
#Include sbfs.asm
|
||||
|
||||
#Program
|
||||
|
||||
start:
|
||||
CALL sbfsMount
|
||||
BNQ failed
|
||||
|
||||
; What was already there, and where.
|
||||
SETD.0 Existing
|
||||
CALL report
|
||||
BNQ failed
|
||||
|
||||
SETD.0 FirstName
|
||||
SETD.1 FirstText
|
||||
INIA 0d26
|
||||
CALL makeAndWrite
|
||||
BNQ failed
|
||||
|
||||
SETD.0 SecondName
|
||||
SETD.1 SecondText
|
||||
INIA 0d25
|
||||
CALL makeAndWrite
|
||||
BNQ failed
|
||||
|
||||
; And read both of them back off the disk.
|
||||
SETD.0 FirstName
|
||||
CALL report
|
||||
BNQ failed
|
||||
SETD.0 SecondName
|
||||
CALL report
|
||||
BNQ failed
|
||||
HALT
|
||||
|
||||
failed:
|
||||
SETD.0 Failed
|
||||
CALL printString
|
||||
CALL lineFeed
|
||||
HALT
|
||||
|
||||
; DP0 names the file, DP1 is its text, and A is how many bytes of it there are. Nothing
|
||||
; here is longer than a block, so the whole size is the tail.
|
||||
makeAndWrite:
|
||||
; The text goes on the Stack, not into DP3. DP3 is the pointer a call does not put back,
|
||||
; which cuts both ways: it is how a routine hands one out, and it is therefore not a
|
||||
; safe place to leave anything across a call to a routine that might use it. sbfsCreate
|
||||
; does use it.
|
||||
PSHD.1
|
||||
SETD.1 SbfsFileTail
|
||||
STA.1
|
||||
RSTA
|
||||
SETD.1 SbfsFileBlocks
|
||||
STA.1
|
||||
INCD.1
|
||||
STA.1
|
||||
|
||||
CALL sbfsCreate
|
||||
POPD.1 ; Back off the Stack whether it worked or not.
|
||||
BNQ makeFailed
|
||||
CALL sbfsWriteFile
|
||||
RET
|
||||
|
||||
makeFailed:
|
||||
RET
|
||||
|
||||
; Prints a file's name, where it begins, and what is in it.
|
||||
report:
|
||||
PSHD.0
|
||||
POPD.3
|
||||
CALL printString
|
||||
CALL blankSpace
|
||||
PSHD.3
|
||||
POPD.0
|
||||
CALL sbfsFind
|
||||
BNQ reportFailed
|
||||
|
||||
SETD.0 SbfsFileStart
|
||||
LDA.0
|
||||
CALL printByteHex
|
||||
INCD.0
|
||||
LDA.0
|
||||
CALL printByteHex
|
||||
CALL blankSpace
|
||||
|
||||
SETD.1 Landing
|
||||
CALL sbfsRead
|
||||
BNQ reportFailed
|
||||
|
||||
; Everything here fits in one block, so the tail is the whole length.
|
||||
SETD.0 Landing
|
||||
SETD.1 SbfsFileTail
|
||||
LDA.1
|
||||
SETD.1 LeftOver
|
||||
STA.1
|
||||
BRA reportEnd
|
||||
reportLoop:
|
||||
LDA.0
|
||||
OUTA 0x00
|
||||
INCD.0
|
||||
SETD.1 LeftOver
|
||||
LDA.1
|
||||
DECA
|
||||
STA.1
|
||||
BNA reportLoop
|
||||
reportEnd:
|
||||
CALL lineFeed
|
||||
RSTA
|
||||
RSTB
|
||||
CCF
|
||||
ADD
|
||||
RET
|
||||
|
||||
reportFailed:
|
||||
RSTA
|
||||
INIB 0d1
|
||||
CCF
|
||||
ADD
|
||||
RET
|
||||
|
||||
#Data
|
||||
|
||||
Existing:
|
||||
"here.txt"
|
||||
FirstName:
|
||||
"first.txt"
|
||||
SecondName:
|
||||
"second.txt"
|
||||
FirstText:
|
||||
"written by SplitBit itself"
|
||||
SecondText:
|
||||
"and a second one after it"
|
||||
Failed:
|
||||
"failed"
|
||||
LeftOver:
|
||||
0x00
|
||||
Landing:
|
||||
#Reserve 0d512
|
||||
Reference in New Issue
Block a user