Interrupt on keypress mode
This commit is contained in:
@@ -441,11 +441,13 @@ handleExit:
|
|||||||
LDD.0.1
|
LDD.0.1
|
||||||
MVDS.0
|
MVDS.0
|
||||||
|
|
||||||
; The console goes back to line mode whatever the program left it in. A program that
|
; The console goes back to how the shell wants it, whatever the program left it in: line
|
||||||
; wanted keys is expected to put it back itself, but one that stopped early, or forgot,
|
; mode, and not interrupting. A program that wanted either is expected to put it back
|
||||||
; would otherwise hand back a shell with no echo and no backspace, and the shell has no
|
; itself, but one that stopped early, or forgot, would otherwise hand back a shell with
|
||||||
; way to find out that happened. Writing line mode when it is already in line mode costs
|
; no echo and no backspace, or one being interrupted about keys it is reading anyway.
|
||||||
; a byte out of a port and does nothing, which is the right price for not having to know.
|
; Zero is both bits, so this undoes everything the control port can be asked for, and
|
||||||
|
; asking for what is already the case costs a byte out of a port and does nothing. That
|
||||||
|
; is the right price for not having to know.
|
||||||
RSTA
|
RSTA
|
||||||
OUTA 0x02
|
OUTA 0x02
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,161 @@
|
|||||||
|
; Tests interrupt on input: the console asking for attention instead of being asked.
|
||||||
|
;
|
||||||
|
; The control port has two bits and they are independent of one another. Bit 0 puts the
|
||||||
|
; console in key mode; bit 1 says to put the interrupt line up when a byte arrives. The
|
||||||
|
; console is on port 0x00, so that is the vector a key comes through - a device raises its
|
||||||
|
; line on its base port, and the status and control ports belong to the same device as the
|
||||||
|
; data port.
|
||||||
|
;
|
||||||
|
; The main program does nothing at all but wait to be told it is finished. It never looks
|
||||||
|
; at the console, which is the whole point: every byte is dealt with by the handler.
|
||||||
|
;
|
||||||
|
; What the status bits mean:
|
||||||
|
; bit 0 READY reading the data port will not have to wait
|
||||||
|
; bit 1 ENDED input has run out for good
|
||||||
|
; bit 2 KEYMODE the console is in key mode
|
||||||
|
; bit 3 INTERRUPTS the console is set to raise its line when a byte arrives
|
||||||
|
;
|
||||||
|
; This runs with input from a file rather than a terminal, so key mode has no terminal to
|
||||||
|
; put into another state and nothing here depends on one. A pipe with bytes in it is a
|
||||||
|
; console with keys waiting as far as the console is concerned.
|
||||||
|
|
||||||
|
#Include console.asm
|
||||||
|
|
||||||
|
#Program
|
||||||
|
|
||||||
|
start:
|
||||||
|
CIF ; Nothing gets through until there is something to catch it.
|
||||||
|
|
||||||
|
SETD.0 Banner
|
||||||
|
CALL printString
|
||||||
|
CALL newLine
|
||||||
|
|
||||||
|
; Key mode and interrupt on input, asked for in one write. Neither bit depends on the
|
||||||
|
; other, so there is no order to get wrong.
|
||||||
|
INIA 0x03
|
||||||
|
OUTA 0x02
|
||||||
|
SETD.0 AskedLabel
|
||||||
|
CALL printString
|
||||||
|
CALL showStatus
|
||||||
|
|
||||||
|
SETD.0 ArrivedLabel
|
||||||
|
CALL printString
|
||||||
|
CALL newLine
|
||||||
|
|
||||||
|
SIF ; From here a byte arriving runs keyHandler.
|
||||||
|
|
||||||
|
waitLoop:
|
||||||
|
; Waiting without looking. Nothing in this loop touches the console, so every byte that
|
||||||
|
; comes out below was put there by something that interrupted this.
|
||||||
|
SETD.3 Finished
|
||||||
|
LDA.3
|
||||||
|
RSTB
|
||||||
|
OR ; Q is the flag, so this is a test for zero.
|
||||||
|
BRQ waitLoop
|
||||||
|
|
||||||
|
CALL newLine
|
||||||
|
RSTA
|
||||||
|
OUTA 0x02 ; Line mode and no interrupts, the way it was found.
|
||||||
|
SETD.0 DoneLabel
|
||||||
|
CALL printString
|
||||||
|
CALL showStatus
|
||||||
|
HALT
|
||||||
|
|
||||||
|
; Entered because the console had something to say. It is never called.
|
||||||
|
;
|
||||||
|
; DP3 is used freely here: an interrupt saves all four Data Pointers and RETI puts them
|
||||||
|
; back, so a handler cannot disturb what it interrupted no matter what it touches.
|
||||||
|
keyHandler:
|
||||||
|
INA 0x01
|
||||||
|
INIB 0x02 ; ENDED
|
||||||
|
AND
|
||||||
|
BNQ keyEnded
|
||||||
|
INA 0x00 ; The byte this interrupt was about.
|
||||||
|
OUTA 0x00 ; Nothing echoes in key mode, so the program does it.
|
||||||
|
RETI
|
||||||
|
|
||||||
|
keyEnded:
|
||||||
|
; The end of input raises the line once, so a program driven entirely by interrupts
|
||||||
|
; still finds out that nothing more is coming. Without it this would wait forever for a
|
||||||
|
; key that cannot arrive.
|
||||||
|
SETD.3 Finished
|
||||||
|
INIA 0x01
|
||||||
|
STA.3
|
||||||
|
RETI
|
||||||
|
|
||||||
|
; Prints the status byte as hex and then names the bits that are up, so a change in the
|
||||||
|
; output says which bit moved rather than only that the number is different.
|
||||||
|
showStatus:
|
||||||
|
INA 0x01
|
||||||
|
PSHA
|
||||||
|
CALL printByteHex
|
||||||
|
INIA 0x20
|
||||||
|
OUTA 0x00
|
||||||
|
POPA
|
||||||
|
PSHA
|
||||||
|
|
||||||
|
INIB 0x01
|
||||||
|
AND
|
||||||
|
BRQ showNotReady
|
||||||
|
SETD.0 ReadyWord
|
||||||
|
CALL printString
|
||||||
|
showNotReady:
|
||||||
|
POPA
|
||||||
|
PSHA
|
||||||
|
|
||||||
|
INIB 0x02
|
||||||
|
AND
|
||||||
|
BRQ showNotEnded
|
||||||
|
SETD.0 EndedWord
|
||||||
|
CALL printString
|
||||||
|
showNotEnded:
|
||||||
|
POPA
|
||||||
|
PSHA
|
||||||
|
|
||||||
|
INIB 0x04
|
||||||
|
AND
|
||||||
|
BRQ showNotKeys
|
||||||
|
SETD.0 KeysWord
|
||||||
|
CALL printString
|
||||||
|
showNotKeys:
|
||||||
|
POPA
|
||||||
|
|
||||||
|
INIB 0x08
|
||||||
|
AND
|
||||||
|
BRQ showNotInterrupts
|
||||||
|
SETD.0 InterruptsWord
|
||||||
|
CALL printString
|
||||||
|
showNotInterrupts:
|
||||||
|
CALL newLine
|
||||||
|
RET
|
||||||
|
|
||||||
|
#Data
|
||||||
|
|
||||||
|
Banner:
|
||||||
|
"console input interrupts"
|
||||||
|
|
||||||
|
AskedLabel:
|
||||||
|
"asked for: "
|
||||||
|
ArrivedLabel:
|
||||||
|
"what arrived:"
|
||||||
|
DoneLabel:
|
||||||
|
"at the end: "
|
||||||
|
|
||||||
|
ReadyWord:
|
||||||
|
"ready "
|
||||||
|
EndedWord:
|
||||||
|
"ended "
|
||||||
|
KeysWord:
|
||||||
|
"keys "
|
||||||
|
InterruptsWord:
|
||||||
|
"interrupts "
|
||||||
|
|
||||||
|
; Set by the handler when the console says there will be no more bytes. It is the only
|
||||||
|
; thing the handler and the program it interrupts have to say to each other.
|
||||||
|
Finished:
|
||||||
|
0x00
|
||||||
|
|
||||||
|
#Vectors
|
||||||
|
|
||||||
|
Boot start
|
||||||
|
Device 0x00 keyHandler
|
||||||
@@ -10,6 +10,10 @@
|
|||||||
; bit 1 ENDED input has run out for good
|
; bit 1 ENDED input has run out for good
|
||||||
; bit 2 KEYMODE the console is in key mode
|
; bit 2 KEYMODE the console is in key mode
|
||||||
;
|
;
|
||||||
|
; There is a fourth bit, for whether the console interrupts on input, and nothing here
|
||||||
|
; sets it. Polling and interrupting are the two ways to get a byte and this is the one
|
||||||
|
; about polling; consoleInterruptTest.asm is the other.
|
||||||
|
;
|
||||||
; This runs with input from a file rather than a terminal, so key mode has no terminal to
|
; This runs with input from a file rather than a terminal, so key mode has no terminal to
|
||||||
; put into another state and the mode bit is the only thing that changes. That is on
|
; put into another state and the mode bit is the only thing that changes. That is on
|
||||||
; purpose: the same program has to work either way, and a test that needed a terminal
|
; purpose: the same program has to work either way, and a test that needed a terminal
|
||||||
@@ -60,8 +64,9 @@ readDone:
|
|||||||
|
|
||||||
; ---- The status at the end of input ----
|
; ---- The status at the end of input ----
|
||||||
;
|
;
|
||||||
; READY is set as well as ENDED, because a read does answer at once. It just answers
|
; ENDED is set and READY is clear, although a read would answer at once here: what it
|
||||||
; 0xFF forever. ENDED is what says so.
|
; answers is 0xFF standing in for nothing. READY means there is a byte to be had, so the
|
||||||
|
; loop above stops on its own rather than taking imaginary bytes forever.
|
||||||
SETD.0 AtEndLabel
|
SETD.0 AtEndLabel
|
||||||
CALL printString
|
CALL printString
|
||||||
CALL showStatus
|
CALL showStatus
|
||||||
|
|||||||
@@ -674,6 +674,11 @@ uint8_t executeOperation(uint8_t Instruction, CPURegisters *cpu) {
|
|||||||
|
|
||||||
void stepCPU(CPURegisters *cpu) {
|
void stepCPU(CPURegisters *cpu) {
|
||||||
if (!(cpu->Status & STATUS_HALT)) {
|
if (!(cpu->Status & STATUS_HALT)) {
|
||||||
|
// Devices get their moment before the lines are read, and unconditionally: a
|
||||||
|
// device is entitled to notice something whether or not the CPU is currently
|
||||||
|
// willing to be interrupted about it. Masking decides when a request is answered,
|
||||||
|
// not whether the outside world is allowed to have happened.
|
||||||
|
serviceDevices();
|
||||||
// A device asking for attention is answered between instructions and never
|
// A device asking for attention is answered between instructions and never
|
||||||
// inside one, so the address that goes into the frame is always the start of an
|
// inside one, so the address that goes into the frame is always the start of an
|
||||||
// instruction and RETI always lands somewhere meaningful.
|
// instruction and RETI always lands somewhere meaningful.
|
||||||
|
|||||||
+84
-7
@@ -26,6 +26,7 @@
|
|||||||
static int consoleKeyMode = 0;
|
static int consoleKeyMode = 0;
|
||||||
static int consoleEnded = 0;
|
static int consoleEnded = 0;
|
||||||
static int consolePushback = -1; // A byte already taken from the host, or -1.
|
static int consolePushback = -1; // A byte already taken from the host, or -1.
|
||||||
|
static int consoleInterrupts = 0; // Whether an arriving byte puts the line up.
|
||||||
static struct termios consoleSavedTerminal;
|
static struct termios consoleSavedTerminal;
|
||||||
static int consoleTerminalSaved = 0;
|
static int consoleTerminalSaved = 0;
|
||||||
|
|
||||||
@@ -35,6 +36,11 @@ void consoleRestore(void) {
|
|||||||
consoleTerminalSaved = 0;
|
consoleTerminalSaved = 0;
|
||||||
}
|
}
|
||||||
consoleKeyMode = 0;
|
consoleKeyMode = 0;
|
||||||
|
// Whatever the console was in the middle of asking for is withdrawn along with the
|
||||||
|
// mode. A line left standing here would be answered by whatever ran next, which had
|
||||||
|
// nothing to do with it and never asked to be interrupted.
|
||||||
|
consoleInterrupts = 0;
|
||||||
|
clearInterrupt(PORT_CONSOLE);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Restores the terminal and then dies the way it would have died anyway, so that the
|
// Restores the terminal and then dies the way it would have died anyway, so that the
|
||||||
@@ -45,8 +51,7 @@ static void consoleSignalHandler(int signalNumber) {
|
|||||||
raise(signalNumber);
|
raise(signalNumber);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void consoleSetMode(uint8_t mode) {
|
static void consoleSetMode(int wantKeys) {
|
||||||
int wantKeys = (mode & CONSOLE_MODE_KEY) != 0;
|
|
||||||
if (wantKeys == consoleKeyMode) {
|
if (wantKeys == consoleKeyMode) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -79,6 +84,35 @@ static void consoleSetMode(uint8_t mode) {
|
|||||||
tcsetattr(STDIN_FILENO, TCSANOW, &raw);
|
tcsetattr(STDIN_FILENO, TCSANOW, &raw);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Puts the line up if the console has something to say and has been asked to say it.
|
||||||
|
// Called wherever news arrives and wherever a program declares it wants to hear news, so
|
||||||
|
// that enabling interrupts while a byte is already waiting is not a way to miss it.
|
||||||
|
static void consoleAnnounce(void) {
|
||||||
|
if (consoleInterrupts && (consolePushback >= 0 || consoleEnded)) {
|
||||||
|
raiseInterrupt(PORT_CONSOLE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The whole control port in one write. The two bits are independent, so both are read out
|
||||||
|
// of the byte and applied, and neither is inferred from the other.
|
||||||
|
static void consoleSetControl(uint8_t control) {
|
||||||
|
// The mode goes first because turning key mode off restores the terminal, and that
|
||||||
|
// withdraws any standing request along with it. Setting the interrupt bit afterwards
|
||||||
|
// means one write can ask for line mode and interrupts together, which is an ordinary
|
||||||
|
// thing to want and would otherwise be undone in the same breath as it was asked for.
|
||||||
|
consoleSetMode((control & CONSOLE_MODE_KEY) != 0);
|
||||||
|
|
||||||
|
int wantInterrupts = (control & CONSOLE_CONTROL_INTERRUPT) != 0;
|
||||||
|
if (!wantInterrupts) {
|
||||||
|
// Asking to stop being interrupted takes down whatever was already asked for. A
|
||||||
|
// request that outlived the setting that made it would arrive at a program that
|
||||||
|
// had just said it did not want it.
|
||||||
|
clearInterrupt(PORT_CONSOLE);
|
||||||
|
}
|
||||||
|
consoleInterrupts = wantInterrupts;
|
||||||
|
consoleAnnounce();
|
||||||
|
}
|
||||||
|
|
||||||
// Everything already written is put where it can be seen before the machine asks the host
|
// Everything already written is put where it can be seen before the machine asks the host
|
||||||
// anything. Standard output is line buffered on a terminal, so a prompt with no newline
|
// anything. Standard output is line buffered on a terminal, so a prompt with no newline
|
||||||
// after it - "> " is exactly that, and exactly why this matters - would sit in the buffer
|
// after it - "> " is exactly that, and exactly why this matters - would sit in the buffer
|
||||||
@@ -92,6 +126,11 @@ static void consoleShowWhatIsWritten(void) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
uint8_t consoleReadByte(void) {
|
uint8_t consoleReadByte(void) {
|
||||||
|
// Taking the byte answers whatever the console was asking about, so the line comes
|
||||||
|
// down here as well as when the CPU acknowledges it. Otherwise a program that reads
|
||||||
|
// the data port with the Interrupt Flag down would be interrupted afterwards on
|
||||||
|
// behalf of a byte it already has, and find nothing waiting when it looked.
|
||||||
|
clearInterrupt(PORT_CONSOLE);
|
||||||
if (consolePushback >= 0) {
|
if (consolePushback >= 0) {
|
||||||
uint8_t byte = (uint8_t)consolePushback;
|
uint8_t byte = (uint8_t)consolePushback;
|
||||||
consolePushback = -1;
|
consolePushback = -1;
|
||||||
@@ -150,10 +189,47 @@ static void consoleFetch(void) {
|
|||||||
}
|
}
|
||||||
// A read that failed for any other reason is left alone: the next attempt asks again,
|
// A read that failed for any other reason is left alone: the next attempt asks again,
|
||||||
// and an interrupted poll is not news.
|
// and an interrupted poll is not news.
|
||||||
|
//
|
||||||
|
// Anything that was news puts the line up. This is the only place a byte arrives from
|
||||||
|
// the outside world, so it is the only place that has to, and it raises AT MOST ONCE
|
||||||
|
// PER BYTE for free: the pushback holds one, and while it is full there is nothing to
|
||||||
|
// fetch and so nothing to announce. A handler that does not read what it was called
|
||||||
|
// about is simply not called again, the way a receive register with one byte in it
|
||||||
|
// stops asking. The end of input announces itself once for the same reason - it is
|
||||||
|
// discovered once, and every later look leaves before it gets here.
|
||||||
|
consoleAnnounce();
|
||||||
|
}
|
||||||
|
|
||||||
|
// How many instructions the machine runs between glances at the console. Nothing here
|
||||||
|
// happens alongside the CPU, so noticing a keystroke costs a system call, and asking on
|
||||||
|
// every instruction costs more than executing one: a poll is about 150ns against roughly
|
||||||
|
// 9ns for an instruction at full tilt, so it would slow the machine by nearly twenty
|
||||||
|
// times. At the emulated clock this stride is a quarter of a millisecond between glances,
|
||||||
|
// which no one typing has ever been able to tell from immediately.
|
||||||
|
#define CONSOLE_SERVICE_STRIDE 256
|
||||||
|
|
||||||
|
void serviceDevices(void) {
|
||||||
|
// The common case is a machine nobody is interrupting, and it costs one test.
|
||||||
|
if (!consoleInterrupts) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
static unsigned int untilNextGlance = 0;
|
||||||
|
if (untilNextGlance > 0) {
|
||||||
|
untilNextGlance--;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
untilNextGlance = CONSOLE_SERVICE_STRIDE - 1;
|
||||||
|
consoleFetch();
|
||||||
}
|
}
|
||||||
|
|
||||||
static uint8_t consoleStatus(void) {
|
static uint8_t consoleStatus(void) {
|
||||||
uint8_t status = consoleKeyMode ? CONSOLE_STATUS_KEYMODE : 0;
|
uint8_t status = 0;
|
||||||
|
if (consoleKeyMode) {
|
||||||
|
status |= CONSOLE_STATUS_KEYMODE;
|
||||||
|
}
|
||||||
|
if (consoleInterrupts) {
|
||||||
|
status |= CONSOLE_STATUS_INTERRUPT;
|
||||||
|
}
|
||||||
consoleFetch();
|
consoleFetch();
|
||||||
if (consoleEnded) {
|
if (consoleEnded) {
|
||||||
// READY IS NOT SET HERE, although a read would answer immediately. The bit means
|
// READY IS NOT SET HERE, although a read would answer immediately. The bit means
|
||||||
@@ -395,7 +471,7 @@ static const DeviceRecord *deviceOnPort(uint8_t port) {
|
|||||||
}
|
}
|
||||||
if (port > PORT_CONSOLE && port <= PORT_CONSOLE_TOP) {
|
if (port > PORT_CONSOLE && port <= PORT_CONSOLE_TOP) {
|
||||||
// The status and control ports are the same device as the data port, which is the
|
// The status and control ports are the same device as the data port, which is the
|
||||||
// one in the table and the one that would raise a line if the console ever did.
|
// one in the table and the one the console raises its line on.
|
||||||
return deviceOnPort(PORT_CONSOLE);
|
return deviceOnPort(PORT_CONSOLE);
|
||||||
}
|
}
|
||||||
if (port > PORT_DISK && port <= PORT_DISK_TOP) {
|
if (port > PORT_DISK && port <= PORT_DISK_TOP) {
|
||||||
@@ -440,7 +516,7 @@ uint8_t OutputHandler(uint8_t DataByte, uint8_t Address) {
|
|||||||
// Later, I'll want to use a buffer for this for performance, probably.
|
// Later, I'll want to use a buffer for this for performance, probably.
|
||||||
putchar(DataByte);
|
putchar(DataByte);
|
||||||
break;
|
break;
|
||||||
case CONSOLE_CONTROL: consoleSetMode(DataByte); break;
|
case CONSOLE_CONTROL: consoleSetControl(DataByte); break;
|
||||||
case CONSOLE_STATUS:
|
case CONSOLE_STATUS:
|
||||||
// Read only. A device saying how it is does not take instructions through the
|
// Read only. A device saying how it is does not take instructions through the
|
||||||
// same hole, so a write here is ignored rather than meaning something.
|
// same hole, so a write here is ignored rather than meaning something.
|
||||||
@@ -495,8 +571,9 @@ uint8_t InputHandler(uint8_t Address) {
|
|||||||
break;
|
break;
|
||||||
case CONSOLE_STATUS: return consoleStatus();
|
case CONSOLE_STATUS: return consoleStatus();
|
||||||
case CONSOLE_CONTROL:
|
case CONSOLE_CONTROL:
|
||||||
// Write only. Reading it gives zero rather than the mode, because the mode is
|
// Write only. Reading it gives zero rather than what was last written, because
|
||||||
// a bit in the status port and one fact wants one place to live.
|
// everything it sets is reported by the status port and one fact wants one
|
||||||
|
// place to live.
|
||||||
return 0;
|
return 0;
|
||||||
break;
|
break;
|
||||||
case DISK_BLOCK_HIGH: return (uint8_t)(diskBlock >> 8);
|
case DISK_BLOCK_HIGH: return (uint8_t)(diskBlock >> 8);
|
||||||
|
|||||||
@@ -58,10 +58,27 @@
|
|||||||
// KEY MODE ONLY REACHES THE TERMINAL when there is one. With input coming from a pipe
|
// KEY MODE ONLY REACHES THE TERMINAL when there is one. With input coming from a pipe
|
||||||
// there is nothing to put into another mode, and the status port answers by asking the
|
// there is nothing to put into another mode, and the status port answers by asking the
|
||||||
// operating system whether anything is waiting, which is true of a pipe with bytes in it.
|
// operating system whether anything is waiting, which is true of a pipe with bytes in it.
|
||||||
|
//
|
||||||
|
// A PROGRAM THAT INTERRUPTS ON INPUT MUST NOT BLOCK ON THE DATA PORT. Reading it waits,
|
||||||
|
// and the machine executes no instructions while it is waiting, so nothing is serviced
|
||||||
|
// and the line the console is about to raise goes nowhere until the read it was meant to
|
||||||
|
// replace has already finished. Interrupting and blocking are two answers to the same
|
||||||
|
// question and a program wants one of them.
|
||||||
|
|
||||||
|
// The control port's bits, which are independent of one another. Writing zero asks for
|
||||||
|
// line mode with no interrupts, which is how the console starts and what a program that
|
||||||
|
// knows nothing of any of this leaves behind it.
|
||||||
#define CONSOLE_MODE_LINE 0x00
|
#define CONSOLE_MODE_LINE 0x00
|
||||||
#define CONSOLE_MODE_KEY 0x01
|
#define CONSOLE_MODE_KEY 0x01
|
||||||
|
|
||||||
|
// Asks the console to put its line up when a byte arrives, instead of the program having
|
||||||
|
// to come and look. It composes with the mode rather than depending on it: in line mode
|
||||||
|
// the terminal still holds what is typed until Return, and then a whole line's worth of
|
||||||
|
// bytes arrive at once, each raising the line in turn as the one before it is taken.
|
||||||
|
// That is not especially useful, but a control bit that quietly did nothing depending on
|
||||||
|
// another control bit would be worse than a burst of interrupts somebody asked for.
|
||||||
|
#define CONSOLE_CONTROL_INTERRUPT 0x02
|
||||||
|
|
||||||
// Set when there is a byte to be had. NOT set at the end of input, although a read would
|
// Set when there is a byte to be had. NOT set at the end of input, although a read would
|
||||||
// answer at once there: what it answers is 0xFF standing in for nothing, and calling that
|
// answer at once there: what it answers is 0xFF standing in for nothing, and calling that
|
||||||
// ready would make a loop that reads while READY spin on imaginary bytes forever. A loop
|
// ready would make a loop that reads while READY spin on imaginary bytes forever. A loop
|
||||||
@@ -74,6 +91,9 @@
|
|||||||
// Which mode the console is in, so that a program can put it back the way it found it
|
// Which mode the console is in, so that a program can put it back the way it found it
|
||||||
// rather than assuming it knows.
|
// rather than assuming it knows.
|
||||||
#define CONSOLE_STATUS_KEYMODE 0x04
|
#define CONSOLE_STATUS_KEYMODE 0x04
|
||||||
|
// Whether the console is set to interrupt, for the same reason: everything a program can
|
||||||
|
// ask the console to be, it can also ask the console what it currently is.
|
||||||
|
#define CONSOLE_STATUS_INTERRUPT 0x08
|
||||||
|
|
||||||
// Puts the terminal back the way it was found. Registered with atexit and called from the
|
// Puts the terminal back the way it was found. Registered with atexit and called from the
|
||||||
// signal handlers, because a machine that stops in key mode and does not undo it leaves
|
// signal handlers, because a machine that stops in key mode and does not undo it leaves
|
||||||
@@ -161,6 +181,16 @@ uint8_t InputHandler(uint8_t Address);
|
|||||||
// These belong to the bus rather than to the CPU. Nothing here is saved in a frame, and
|
// These belong to the bus rather than to the CPU. Nothing here is saved in a frame, and
|
||||||
// a program cannot read them except by being interrupted.
|
// a program cannot read them except by being interrupted.
|
||||||
|
|
||||||
|
// Gives every device a moment to notice something the machine did not ask it about.
|
||||||
|
// Nothing here runs alongside the CPU: a device that waits on the outside world - the
|
||||||
|
// console is the only one so far - is never going to see a keystroke unless something
|
||||||
|
// asks it to look, and the CPU calling this between instructions is that something.
|
||||||
|
//
|
||||||
|
// It is called on every step and gets out of the way immediately when there is nothing to
|
||||||
|
// do, because there usually is not. A device that wants attention rarely is a device that
|
||||||
|
// must cost nothing when it does not.
|
||||||
|
void serviceDevices(void);
|
||||||
|
|
||||||
void raiseInterrupt(uint8_t port);
|
void raiseInterrupt(uint8_t port);
|
||||||
|
|
||||||
void clearInterrupt(uint8_t port);
|
void clearInterrupt(uint8_t port);
|
||||||
|
|||||||
@@ -130,7 +130,7 @@ If a device interrupts and its vector is empty, that is a fault: the machine sto
|
|||||||
|
|
||||||
| Port | Device | Class |
|
| Port | Device | Class |
|
||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
| 0x00 - 0x02 | The console. See The Console below. Writing to 0x00 sends a byte to standard output, reading takes one from standard input. | 0x02 |
|
| 0x00 - 0x02 | The console. See The Console below. Writing to 0x00 sends a byte to standard output, reading takes one from standard input. It interrupts on 0x00, its base port, when asked to. | 0x02 |
|
||||||
| 0x10 | A test device. Writing anything to it puts its own line up, so that interrupt handling can be exercised without waiting on anything. The byte written is ignored. | 0x10 |
|
| 0x10 | A test device. Writing anything to it puts its own line up, so that interrupt handling can be exercised without waiting on anything. The byte written is ignored. | 0x10 |
|
||||||
| 0x11 | A device that refuses everything, in both directions, so that refusal can be exercised without the memory controller. | 0x11 |
|
| 0x11 | A device that refuses everything, in both directions, so that refusal can be exercised without the memory controller. | 0x11 |
|
||||||
| 0x20 - 0x23 | The disk. See Storage below. It interrupts on 0x20, its base port. | 0x13 |
|
| 0x20 - 0x23 | The disk. See Storage below. It interrupts on 0x20, its base port. | 0x13 |
|
||||||
@@ -340,13 +340,15 @@ Both arrive at the instruction that asked, so a handler sees which one it was. A
|
|||||||
|
|
||||||
## The Console:
|
## The Console:
|
||||||
|
|
||||||
Port 0x00 is the oldest thing on this machine and it has not changed: writing sends a byte out, reading takes one in and waits until there is one. Every program ever written for SplitBit uses it that way and still does. What is new is that a program can now ask whether a read would have to wait, and can say what it wants a keypress to mean.
|
Port 0x00 is the oldest thing on this machine and it has not changed: writing sends a byte out, reading takes one in and waits until there is one. Every program ever written for SplitBit uses it that way and still does. What is new is that a program can say what it wants a keypress to mean, can ask whether a read would have to wait, and can arrange to be told when a byte arrives instead of having to ask at all.
|
||||||
|
|
||||||
| Port | Register |
|
| Port | Register |
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
| 0x00 | Data. Writing sends a byte out, reading takes one in and waits for it. |
|
| 0x00 | Data. Writing sends a byte out, reading takes one in and waits for it. |
|
||||||
| 0x01 | Status. Bit 0 a byte is waiting, bit 1 input has ended, bit 2 the console is in key mode. |
|
| 0x01 | Status. Bit 0 a byte is waiting, bit 1 input has ended, bit 2 the console is in key mode, bit 3 the console is set to interrupt. |
|
||||||
| 0x02 | Mode. Writing 0x00 asks for line mode, 0x01 for key mode. |
|
| 0x02 | Control. Bit 0 asks for key mode, bit 1 asks the console to interrupt when a byte arrives. Writing 0x00 asks for neither, which is how the console starts. |
|
||||||
|
|
||||||
|
The control port's two bits are independent, and one write sets both. Everything the control port can ask for, the status port reports, so a program can put the console back the way it found it instead of assuming it knows.
|
||||||
|
|
||||||
### Two Kinds Of Input:
|
### Two Kinds Of Input:
|
||||||
|
|
||||||
@@ -376,6 +378,38 @@ Reading the data port when input has run out gives 0xFF, which is what it has al
|
|||||||
|
|
||||||
Bit 0 is **not** set once input has ended, even though a read would answer immediately. The bit means a byte is there to be had, and at the end of input there is not. That way a loop that reads while bit 0 is set stops when the input does, instead of taking imaginary bytes forever.
|
Bit 0 is **not** set once input has ended, even though a read would answer immediately. The bit means a byte is there to be had, and at the end of input there is not. That way a loop that reads while bit 0 is set stops when the input does, instead of taking imaginary bytes forever.
|
||||||
|
|
||||||
|
### Being Told Instead Of Asking:
|
||||||
|
|
||||||
|
Bit 1 of the control port asks the console to put its interrupt line up when a byte arrives, so a program can get on with something else and be told. The console is on port 0x00, so that is the vector a key comes through, named the way every device is:
|
||||||
|
|
||||||
|
```
|
||||||
|
#Vectors
|
||||||
|
Device 0x00 keyHandler
|
||||||
|
```
|
||||||
|
|
||||||
|
The handler is entered because the console had something to say, and asks the status port what. There are two possible answers, and the second is why a program can rely on this instead of also polling:
|
||||||
|
|
||||||
|
```
|
||||||
|
keyHandler:
|
||||||
|
INA 0x01
|
||||||
|
INIB 0x02 ; Bit 1: has input ended?
|
||||||
|
AND
|
||||||
|
BNQ noMoreKeys ; Nothing more is ever coming.
|
||||||
|
INA 0x00 ; The byte this interrupt was about.
|
||||||
|
OUTA 0x00 ; Nothing echoes in key mode, so send it back out.
|
||||||
|
RETI
|
||||||
|
```
|
||||||
|
|
||||||
|
**The end of input raises the line once**, as well as an arriving byte. A program driven entirely by interrupts would otherwise sit forever waiting to be told about a key that cannot arrive.
|
||||||
|
|
||||||
|
**The line goes up at most once per byte.** The console holds one byte, so while that byte is still there, nothing new can arrive to ask about, and a handler that returns without reading it is simply not called again. This is what a receive register holding one byte does, and it means a handler cannot interrupt-storm the machine by forgetting something. The cost is the other half of the same fact: bytes arriving while that one is unread are lost, exactly as they would be on hardware.
|
||||||
|
|
||||||
|
The two control bits do not depend on each other, so a program may ask to be interrupted in line mode. The terminal still holds what is typed until Return, and then the whole line arrives at once as a run of interrupts, one per byte. That is rarely what anyone wants, but a control bit that quietly did nothing because of another control bit would be worse.
|
||||||
|
|
||||||
|
**A program that interrupts on input must not also block on the data port.** Reading it waits, and nothing else in the machine runs while it is waiting, so a program that does both has chosen to wait after asking not to. Interrupting and blocking are two answers to the same question, and a program wants one of them.
|
||||||
|
|
||||||
|
The machine notices an arriving key between instructions, and does not look on every single one. The delay is about a quarter of a millisecond, which is shorter than the gap between two keystrokes by a wide margin and shorter than anything a person can perceive at all.
|
||||||
|
|
||||||
## Storage:
|
## Storage:
|
||||||
|
|
||||||
The disk is a block device. It knows numbered blocks of 256 bytes and has never heard of a file. A filesystem is software this machine runs, not something done on its behalf: a disk that understood filenames would be the emulator doing the work while the machine pretended it had.
|
The disk is a block device. It knows numbered blocks of 256 bytes and has never heard of a file. A filesystem is software this machine runs, not something done on its behalf: a disk that understood filenames would be the emulator doing the work while the machine pretended it had.
|
||||||
|
|||||||
@@ -103,6 +103,25 @@ else:
|
|||||||
problems.append("%s (0x%02X) is a device class and has no row in the Devices"
|
problems.append("%s (0x%02X) is a device class and has no row in the Devices"
|
||||||
" table" % (name, value))
|
" table" % (name, value))
|
||||||
|
|
||||||
|
# ---- Every console status bit is described ----
|
||||||
|
#
|
||||||
|
# The status port is read by writing a mask and testing it, so a program can only use a bit
|
||||||
|
# it has been told the number of. Adding one and forgetting to write it down leaves a bit
|
||||||
|
# that works and that nobody can discover. The section names them as "bit N", so that is
|
||||||
|
# what is looked for.
|
||||||
|
status = {name: int(value, 16)
|
||||||
|
for name, value in re.findall(r'^#define (CONSOLE_STATUS_[A-Z]+)\s+(0x[0-9A-Fa-f]{2})$',
|
||||||
|
ioh, re.M)}
|
||||||
|
if "## The Console:" not in pm:
|
||||||
|
problems.append("the Programming Manual has lost its \"The Console\" section")
|
||||||
|
else:
|
||||||
|
console = pm.split("## The Console:")[1].split("\n## ")[0]
|
||||||
|
for name, value in sorted(status.items(), key=lambda pair: pair[1]):
|
||||||
|
bit = value.bit_length() - 1
|
||||||
|
if "bit %d" % bit not in console:
|
||||||
|
problems.append("%s is bit %d of the console status port and The Console does"
|
||||||
|
" not mention it" % (name, bit))
|
||||||
|
|
||||||
# ---- Every directive the assembler knows is written down ----
|
# ---- Every directive the assembler knows is written down ----
|
||||||
for directive in sorted(set(re.findall(r'"(#[A-Za-z]+)"', util))):
|
for directive in sorted(set(re.findall(r'"(#[A-Za-z]+)"', util))):
|
||||||
if directive not in am:
|
if directive not in am:
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
console input interrupts
|
||||||
|
asked for: 0D ready keys interrupts
|
||||||
|
what arrived:
|
||||||
|
keys
|
||||||
|
at the end: 02 ended
|
||||||
|
Execution halted after 1597 cycles.
|
||||||
|
[exit 0]
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
keys
|
||||||
@@ -164,6 +164,12 @@ consoleTest | testPrograms/consoleTest.asm | run | consoleTe
|
|||||||
# What it pins down is that READY is clear at the end of input while ENDED is set, so a
|
# What it pins down is that READY is clear at the end of input while ENDED is set, so a
|
||||||
# loop reading while READY stops on its own instead of taking imaginary bytes forever.
|
# loop reading while READY stops on its own instead of taking imaginary bytes forever.
|
||||||
consoleModeTest | testPrograms/consoleModeTest.asm | run | consoleModeTest.in | -
|
consoleModeTest | testPrograms/consoleModeTest.asm | run | consoleModeTest.in | -
|
||||||
|
# The console interrupting instead of being asked. The main program never touches the
|
||||||
|
# console at all, so every byte in that output was delivered by a handler. It also pins
|
||||||
|
# down the two things that make the feature usable rather than merely present: the end of
|
||||||
|
# input raises the line once, so an interrupt-driven program is told when to stop, and a
|
||||||
|
# handler that does not read the byte is not called again, so nothing storms.
|
||||||
|
consoleInterruptTest | testPrograms/consoleInterruptTest.asm | run | consoleInterruptTest.in | -
|
||||||
# Picking a typed line apart, which is how the shell understands anything. Includes a
|
# Picking a typed line apart, which is how the shell understands anything. Includes a
|
||||||
# string beginning with a zero: the assembler strips the quotes before deciding what a
|
# string beginning with a zero: the assembler strips the quotes before deciding what a
|
||||||
# token is, so such a string looked like a malformed literal and was refused.
|
# token is, so such a string looked like a malformed literal and was refused.
|
||||||
|
|||||||
Reference in New Issue
Block a user