CosmOS pre-alpha and launchable application versions of old programs.

This commit is contained in:
Anachronaut
2026-08-17 15:31:49 -04:00
parent eff6902bcf
commit 91c9d49d1b
66 changed files with 5612 additions and 160 deletions
-871
View File
@@ -1,871 +0,0 @@
; 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