Block device peripheral and SBFS file system implemented.
This commit is contained in:
@@ -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