Interrupt on keypress mode
This commit is contained in:
@@ -674,6 +674,11 @@ uint8_t executeOperation(uint8_t Instruction, CPURegisters *cpu) {
|
||||
|
||||
void stepCPU(CPURegisters *cpu) {
|
||||
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
|
||||
// inside one, so the address that goes into the frame is always the start of an
|
||||
// instruction and RETI always lands somewhere meaningful.
|
||||
|
||||
+84
-7
@@ -26,6 +26,7 @@
|
||||
static int consoleKeyMode = 0;
|
||||
static int consoleEnded = 0;
|
||||
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 int consoleTerminalSaved = 0;
|
||||
|
||||
@@ -35,6 +36,11 @@ void consoleRestore(void) {
|
||||
consoleTerminalSaved = 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
|
||||
@@ -45,8 +51,7 @@ static void consoleSignalHandler(int signalNumber) {
|
||||
raise(signalNumber);
|
||||
}
|
||||
|
||||
static void consoleSetMode(uint8_t mode) {
|
||||
int wantKeys = (mode & CONSOLE_MODE_KEY) != 0;
|
||||
static void consoleSetMode(int wantKeys) {
|
||||
if (wantKeys == consoleKeyMode) {
|
||||
return;
|
||||
}
|
||||
@@ -79,6 +84,35 @@ static void consoleSetMode(uint8_t mode) {
|
||||
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
|
||||
// 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
|
||||
@@ -92,6 +126,11 @@ static void consoleShowWhatIsWritten(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) {
|
||||
uint8_t byte = (uint8_t)consolePushback;
|
||||
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,
|
||||
// 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) {
|
||||
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();
|
||||
if (consoleEnded) {
|
||||
// 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) {
|
||||
// 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);
|
||||
}
|
||||
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.
|
||||
putchar(DataByte);
|
||||
break;
|
||||
case CONSOLE_CONTROL: consoleSetMode(DataByte); break;
|
||||
case CONSOLE_CONTROL: consoleSetControl(DataByte); break;
|
||||
case CONSOLE_STATUS:
|
||||
// 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.
|
||||
@@ -495,8 +571,9 @@ uint8_t InputHandler(uint8_t Address) {
|
||||
break;
|
||||
case CONSOLE_STATUS: return consoleStatus();
|
||||
case CONSOLE_CONTROL:
|
||||
// Write only. Reading it gives zero rather than the mode, because the mode is
|
||||
// a bit in the status port and one fact wants one place to live.
|
||||
// Write only. Reading it gives zero rather than what was last written, because
|
||||
// everything it sets is reported by the status port and one fact wants one
|
||||
// place to live.
|
||||
return 0;
|
||||
break;
|
||||
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
|
||||
// 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.
|
||||
//
|
||||
// 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_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
|
||||
// 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
|
||||
@@ -74,6 +91,9 @@
|
||||
// Which mode the console is in, so that a program can put it back the way it found it
|
||||
// rather than assuming it knows.
|
||||
#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
|
||||
// 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
|
||||
// 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 clearInterrupt(uint8_t port);
|
||||
|
||||
Reference in New Issue
Block a user