Programs can now list and share vectors.
This commit is contained in:
@@ -0,0 +1,101 @@
|
||||
; A loaded program that is interrupted by the console rather than asking it for anything.
|
||||
;
|
||||
; Until the loadable program format could carry vectors, this could not be written. A
|
||||
; handler has to be an address in the vector table, and a program that is not the one the
|
||||
; machine booted from had no way to say what its vectors were, so interrupts belonged to
|
||||
; boot images and every loaded program had to poll. Snake polls for exactly that reason.
|
||||
;
|
||||
; What this shows is the whole path: the assembler writes the vectors into the file, the
|
||||
; system installs them when the program is run, the console raises its line, the handler
|
||||
; runs, and the system takes the vectors back out again when the program gives the machine
|
||||
; back. Nothing in the waiting loop below looks at the console at all.
|
||||
;
|
||||
; The vector is named by port, because a device interrupts on the port it is plugged into
|
||||
; and the console is on port 0x00.
|
||||
|
||||
#Include services.asm
|
||||
|
||||
#Program
|
||||
|
||||
#Base 0x2000
|
||||
|
||||
start:
|
||||
CIF ; Nothing arrives until there is something to catch it.
|
||||
|
||||
; Set rather than trusted to be zero. Running a program a second time does not load it
|
||||
; again, so its Data Segment is exactly as the last run left it - and the last thing the
|
||||
; last run did was set this.
|
||||
RSTA
|
||||
SETD.0 Stopping
|
||||
STA.0
|
||||
|
||||
SETD.0 Banner
|
||||
CALL printString
|
||||
CALL newLine
|
||||
|
||||
; Key mode and interrupt on input, in one write, since the two control bits are
|
||||
; independent of each other.
|
||||
INIA 0x03
|
||||
OUTA 0x02
|
||||
SIF
|
||||
|
||||
wait:
|
||||
; This loop is the point. It never touches the console, so every character that appears
|
||||
; below was put there by something that interrupted it.
|
||||
SETD.3 Stopping
|
||||
LDA.3
|
||||
RSTB
|
||||
OR
|
||||
BRQ wait
|
||||
|
||||
CALL newLine
|
||||
RSTA
|
||||
OUTA 0x02 ; Line mode and no interrupts, the way it was found.
|
||||
SETD.0 DoneText
|
||||
CALL printString
|
||||
CALL newLine
|
||||
SWI osExit
|
||||
|
||||
; Entered because the console had something to say. Never called.
|
||||
keyHandler:
|
||||
INA 0x01
|
||||
INIB 0x01 ; READY: is there a byte, as opposed to the end of input?
|
||||
AND
|
||||
BRQ keyNoByte
|
||||
INA 0x00
|
||||
|
||||
INIB 0x71 ; q, which is how this program is stopped.
|
||||
XOR
|
||||
BRQ keyStop
|
||||
|
||||
OUTA 0x00 ; Nothing echoes in key mode, so the handler does it.
|
||||
RETI
|
||||
|
||||
keyNoByte:
|
||||
; The end of input raises the line once as well, so a program driven entirely by
|
||||
; interrupts is told when nothing more is coming instead of waiting for ever.
|
||||
keyStop:
|
||||
SETD.3 Stopping
|
||||
INIA 0x01
|
||||
STA.3
|
||||
RETI
|
||||
|
||||
#Data
|
||||
|
||||
#Base 0x1000
|
||||
|
||||
Banner:
|
||||
"keys, by interrupt. q stops."
|
||||
DoneText:
|
||||
"the console has been handed back"
|
||||
|
||||
; The only thing the handler and the loop it interrupts have to say to each other.
|
||||
Stopping:
|
||||
0x00
|
||||
|
||||
#Vectors
|
||||
|
||||
Boot start
|
||||
Device 0x00 keyHandler
|
||||
|
||||
#Include console.asm
|
||||
@@ -9,13 +9,15 @@
|
||||
; console holds the next key until it is asked, so nothing typed between frames is lost,
|
||||
; and a script of moves plays back one move to a frame.
|
||||
;
|
||||
; WHY IT POLLS RATHER THAN INTERRUPTS. The console can raise an interrupt line when a byte
|
||||
; arrives, which is the better shape for a game: the loop would never look at the console
|
||||
; at all. A loaded program cannot use it. Installing a handler means putting an address in
|
||||
; the vector table, and the loadable program format carries only code and data - a program
|
||||
; that is not the one the machine booted from has no way to say what its vectors are. So
|
||||
; interrupts belong to boot images for now, and this asks once a frame, which is what the
|
||||
; machines this one is pretending to be did anyway.
|
||||
; WHY IT POLLS RATHER THAN INTERRUPTS. When this was written a loaded program could not be
|
||||
; interrupted at all: installing a handler means putting an address in the vector table,
|
||||
; and the loadable format carried only code and data, so a program that was not the one the
|
||||
; machine booted from had no way to say what its vectors were. That is no longer true - the
|
||||
; format carries them now, and Keys.asm is the program that shows it.
|
||||
;
|
||||
; This still polls, and now by choice. Asking once a frame is what the machines this one is
|
||||
; pretending to be actually did, it is the shape a game with a frame loop wants anyway, and
|
||||
; having one of each in the same Apps directory is worth more than having two the same.
|
||||
;
|
||||
; THE BOARD IS A PAGE, and that is the whole trick this program turns on. Sixteen by
|
||||
; sixteen is 256 squares, so a square number is a byte, and the board is aligned so that
|
||||
|
||||
@@ -279,12 +279,24 @@ loadMagicLoop:
|
||||
STA.1
|
||||
BNA loadMagicLoop
|
||||
|
||||
; Version one is code and data. Version two also brings vectors, which is a thing a
|
||||
; loader has to know how to do rather than a detail it can skip: a program whose handlers
|
||||
; were quietly dropped would run and then go wrong somewhere with nothing to connect it
|
||||
; back to here. Anything else is refused.
|
||||
SETD.0 0x80 0x00
|
||||
DPUP.0 0d04
|
||||
LDA.0
|
||||
SETD.1 LoadVersion
|
||||
STA.1
|
||||
INIB 0d1
|
||||
XOR
|
||||
BRQ loadVersionKnown
|
||||
SETD.1 LoadVersion
|
||||
LDA.1
|
||||
INIB 0d2
|
||||
XOR
|
||||
BNQ loadWrongVersion
|
||||
loadVersionKnown:
|
||||
|
||||
; The code. It comes from the staging area just past the sixteen byte header, and goes
|
||||
; wherever the header says, in Program Memory, which the instruction set cannot write
|
||||
@@ -340,6 +352,75 @@ loadMagicLoop:
|
||||
INIA 0x01
|
||||
OUTA 0xE8 ; Blit.
|
||||
|
||||
; ---- The vectors it brought ----
|
||||
;
|
||||
; Kept here rather than installed. A vector points into a program, so it has no business
|
||||
; being in the table while that program is only loaded and not running: run puts them in
|
||||
; and exit takes them out again, so the window they are live in is exactly the run.
|
||||
; Keeping our own copy is also what lets a program be run more than once, since the
|
||||
; staging area it came in on is fair game for the program's own use.
|
||||
;
|
||||
; Where to read them from is not worked out. The data blit left the controller's source
|
||||
; address on the first byte after the data, which is where they are, so it is read back.
|
||||
SETD.0 VectorSource
|
||||
INA 0xE1
|
||||
STA.0
|
||||
INCD.0
|
||||
INA 0xE2
|
||||
STA.0
|
||||
|
||||
SETD.0 0x80 0x00
|
||||
DPUP.0 0d05
|
||||
LDA.0
|
||||
SETD.1 LoadedVectorCount
|
||||
STA.1
|
||||
BRA loadVectorsCopied
|
||||
|
||||
; More than there is room for is refused rather than half taken. Half a program's
|
||||
; handlers is not a smaller version of that program.
|
||||
INIB 0d17
|
||||
CCF
|
||||
SUB
|
||||
BNC loadTooManyVectors
|
||||
|
||||
SETD.0 VectorSource
|
||||
LDD.2.0 ; DP2 walks the entries where they are staged.
|
||||
SETD.3 LoadedVectors ; DP3 walks our own copy of them.
|
||||
SETD.1 LoadedVectorCount
|
||||
LDA.1
|
||||
SETD.1 VectorsLeft
|
||||
STA.1
|
||||
|
||||
loadVectorCopy:
|
||||
; Four bytes: where it goes, then what goes there. The two bytes for what was there
|
||||
; before are left alone until something is actually put in.
|
||||
LDA.2
|
||||
STA.3
|
||||
INCD.2
|
||||
INCD.3
|
||||
LDA.2
|
||||
STA.3
|
||||
INCD.2
|
||||
INCD.3
|
||||
LDA.2
|
||||
STA.3
|
||||
INCD.2
|
||||
INCD.3
|
||||
LDA.2
|
||||
STA.3
|
||||
INCD.2
|
||||
INCD.3
|
||||
INCD.3
|
||||
INCD.3
|
||||
|
||||
SETD.1 VectorsLeft
|
||||
LDA.1
|
||||
DECA
|
||||
STA.1
|
||||
BNA loadVectorCopy
|
||||
|
||||
loadVectorsCopied:
|
||||
|
||||
; Where it starts. Written out by hand rather than through a routine, because a routine
|
||||
; could not hand two bytes back: CALL puts A, B and the first three pointers back the
|
||||
; way it found them.
|
||||
@@ -370,6 +451,9 @@ loadNoDisk:
|
||||
loadNothingNamed:
|
||||
SETD.0 LoadWhat
|
||||
BRI loadComplain
|
||||
loadTooManyVectors:
|
||||
SETD.0 TooManyVectors
|
||||
BRI loadComplain
|
||||
loadMissing:
|
||||
SETD.0 NoSuchFile
|
||||
BRI loadComplain
|
||||
@@ -401,6 +485,8 @@ doRun:
|
||||
SETD.1 SystemStack
|
||||
STD.0.1
|
||||
|
||||
CALL installVectors
|
||||
|
||||
; The entry address is a number until BRD makes it a place. DP3 is the one to build it
|
||||
; in, because it is the pointer nothing puts back.
|
||||
SETD.1 LoadedEntry
|
||||
@@ -413,6 +499,112 @@ runNothing:
|
||||
CALL newLine
|
||||
BRI prompt
|
||||
|
||||
; ---- Putting a program's vectors in, and taking them out again ----
|
||||
;
|
||||
; The vector table lives in Program Memory, which no instruction can write, so both of
|
||||
; these go through the memory controller. Port 0xE9 reads a byte from the source and writes
|
||||
; a byte to the destination, stepping the address on either way, so a two byte entry is two
|
||||
; reads or two writes and no address arithmetic in between.
|
||||
;
|
||||
; What was in the slot is kept before anything replaces it, and put back afterwards, rather
|
||||
; than the slot being cleared. Clearing would be wrong wherever a program has installed a
|
||||
; handler over one the system was already using: the program is allowed to do that, and
|
||||
; when it goes, what it covered up has to come back rather than becoming a hole.
|
||||
|
||||
installVectors:
|
||||
SETD.0 LoadedVectorCount
|
||||
LDA.0
|
||||
BRA installDone
|
||||
SETD.1 VectorsLeft
|
||||
STA.1
|
||||
SETD.3 LoadedVectors
|
||||
|
||||
installOne:
|
||||
; DP3 walks one six byte entry: where it goes, what goes there, and room for what was
|
||||
; there before. Reading and writing the same slot, so the controller is pointed at it
|
||||
; from both ends at once and the address is only worked out once.
|
||||
RSTA
|
||||
OUTA 0xE0 ; SourceBank: Program Memory.
|
||||
OUTA 0xE3 ; DestBank: the same.
|
||||
LDA.3
|
||||
OUTA 0xE1
|
||||
OUTA 0xE4
|
||||
INCD.3
|
||||
LDA.3
|
||||
OUTA 0xE2
|
||||
OUTA 0xE5
|
||||
INCD.3 ; On the handler.
|
||||
|
||||
; What is there now, before anything replaces it.
|
||||
INA 0xE9
|
||||
PSHA
|
||||
INA 0xE9
|
||||
PSHA
|
||||
|
||||
; And the handler in its place.
|
||||
LDA.3
|
||||
OUTA 0xE9
|
||||
INCD.3
|
||||
LDA.3
|
||||
OUTA 0xE9
|
||||
INCD.3 ; On the two bytes kept for what was there before.
|
||||
|
||||
; The Stack gives them back in the reverse of the order they went on, so the low byte
|
||||
; arrives first and is written to the second of the two. Getting this the natural way
|
||||
; round instead put the low byte where the high one goes and the high byte over the
|
||||
; handler, which the first run of a program survives - the table is already written by
|
||||
; then - and the second run does not.
|
||||
POPA
|
||||
INCD.3
|
||||
STA.3
|
||||
DECD.3
|
||||
POPA
|
||||
STA.3
|
||||
INCD.3
|
||||
INCD.3
|
||||
|
||||
SETD.1 VectorsLeft
|
||||
LDA.1
|
||||
DECA
|
||||
STA.1
|
||||
BNA installOne
|
||||
installDone:
|
||||
RET
|
||||
|
||||
removeVectors:
|
||||
SETD.0 LoadedVectorCount
|
||||
LDA.0
|
||||
BRA removeDone
|
||||
SETD.1 VectorsLeft
|
||||
STA.1
|
||||
SETD.3 LoadedVectors
|
||||
|
||||
removeOne:
|
||||
RSTA
|
||||
OUTA 0xE3 ; DestBank: Program Memory.
|
||||
LDA.3
|
||||
OUTA 0xE4
|
||||
INCD.3
|
||||
LDA.3
|
||||
OUTA 0xE5
|
||||
INCD.3
|
||||
INCD.3
|
||||
INCD.3 ; Past the handler, to what was underneath it.
|
||||
LDA.3
|
||||
OUTA 0xE9
|
||||
INCD.3
|
||||
LDA.3
|
||||
OUTA 0xE9
|
||||
INCD.3
|
||||
|
||||
SETD.1 VectorsLeft
|
||||
LDA.1
|
||||
DECA
|
||||
STA.1
|
||||
BNA removeOne
|
||||
removeDone:
|
||||
RET
|
||||
|
||||
; ---- The services ----
|
||||
;
|
||||
; These are what a loaded program is allowed to ask for. The names and their numbers come
|
||||
@@ -422,6 +614,19 @@ runNothing:
|
||||
; A handler arrives with the caller's registers exactly as they were: an interrupt frame
|
||||
; is pushed, not cleared. So the pointer a program put in DP0 is still there to be used.
|
||||
|
||||
; The disk finishing, acknowledged and ignored.
|
||||
;
|
||||
; The system drives the disk by asking its status port and waiting, so it has no use for
|
||||
; the line. But the disk raises one after every operation whether anybody wants it or not,
|
||||
; and a line goes on waiting while the Interrupt Flag is down rather than being lost. The
|
||||
; shell keeps the flag down, so the line from the last disk read was still standing when
|
||||
; the first program to enable interrupts ran, and it arrived there - a fault, in a program
|
||||
; that had never heard of the disk, blamed on the innocent instruction that let it through.
|
||||
;
|
||||
; Answering a line is what takes it down, so this is one instruction and that is the point.
|
||||
diskDone:
|
||||
RETI
|
||||
|
||||
handlePrintString:
|
||||
CALL printString
|
||||
RETI
|
||||
@@ -441,6 +646,11 @@ handleExit:
|
||||
LDD.0.1
|
||||
MVDS.0
|
||||
|
||||
; Whatever the program put in the vector table comes out again. A vector points into the
|
||||
; program that supplied it, and the program is gone, so anything left installed would aim
|
||||
; an interrupt at whatever those addresses hold next.
|
||||
CALL removeVectors
|
||||
|
||||
; The console goes back to how the shell wants it, whatever the program left it in: line
|
||||
; mode, and not interrupting. A program that wanted either is expected to put it back
|
||||
; itself, but one that stopped early, or forgot, would otherwise hand back a shell with
|
||||
@@ -713,6 +923,8 @@ LoadWhat:
|
||||
"load what?"
|
||||
NoSuchFile:
|
||||
"no such file"
|
||||
TooManyVectors:
|
||||
"that program wants more vectors than there is room for"
|
||||
Unreadable:
|
||||
"could not read it"
|
||||
NotProgram:
|
||||
@@ -745,6 +957,26 @@ LoadedEntry:
|
||||
0x00 0x00
|
||||
LoadCount:
|
||||
0x00
|
||||
LoadVersion:
|
||||
0x00
|
||||
|
||||
; ---- The vectors a loaded program brought with it ----
|
||||
;
|
||||
; Six bytes each: where it goes, what goes there, and what was there before. The last two
|
||||
; are filled in when the program runs and read back when it exits, so what a program covers
|
||||
; up comes back rather than becoming a hole.
|
||||
;
|
||||
; Sixteen is a limit rather than a considered number. It is far more than anything written
|
||||
; so far wants, and a program asking for more is refused at load rather than having some of
|
||||
; its handlers installed and the rest dropped.
|
||||
VectorSource:
|
||||
0x00 0x00
|
||||
VectorsLeft:
|
||||
0x00
|
||||
LoadedVectorCount:
|
||||
0x00
|
||||
LoadedVectors:
|
||||
#Reserve 0d96
|
||||
|
||||
; Where the monitor is looking, so that a bare 'dump' can carry on from it.
|
||||
DumpBank:
|
||||
@@ -781,3 +1013,4 @@ CommandLine:
|
||||
osPrintString handlePrintString
|
||||
osReadLine handleReadLine
|
||||
osExit handleExit
|
||||
Device 0x20 diskDone
|
||||
|
||||
Reference in New Issue
Block a user