Interrupt on keypress mode

This commit is contained in:
Anachronaut
2026-08-17 16:02:44 -04:00
parent 91c9d49d1b
commit 08624925fe
11 changed files with 365 additions and 18 deletions
+7 -5
View File
@@ -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
+7 -2
View File
@@ -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
+5
View File
@@ -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
View File
@@ -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);
+30
View File
@@ -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);
+38 -4
View File
@@ -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.
+19
View File
@@ -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:
+7
View File
@@ -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]
+1
View File
@@ -0,0 +1 @@
keys
+6
View File
@@ -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.