Device table added to IO Port 0xFF.
This commit is contained in:
+68
-4
@@ -37,22 +37,81 @@ int nextPendingInterrupt(void) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// ---- The bus registry ----
|
||||
//
|
||||
// What is plugged into this machine. The table is fixed when the machine is built: a
|
||||
// program cannot write to it, because writing would only let a program lie to itself
|
||||
// about what hardware exists. Which routine handles a device is a different question,
|
||||
// and the vector table already answers it.
|
||||
//
|
||||
// Nothing here touches the device being asked about. That matters more than it looks:
|
||||
// reading a port is a real operation, and asking the console what it is by reading it
|
||||
// would take a character off standard input and block waiting for one.
|
||||
|
||||
typedef struct {
|
||||
uint8_t port;
|
||||
uint8_t deviceClass;
|
||||
uint8_t flags;
|
||||
} DeviceRecord;
|
||||
|
||||
static const DeviceRecord deviceTable[] = {
|
||||
{ PORT_CONSOLE, DEVICE_CONSOLE, 0 },
|
||||
{ PORT_TEST, DEVICE_TEST, 0 },
|
||||
{ PORT_REGISTRY, DEVICE_REGISTRY, 0 },
|
||||
};
|
||||
static const int deviceCount = (int)(sizeof(deviceTable) / sizeof(deviceTable[0]));
|
||||
|
||||
// Which port the registry is currently being asked about, and how far through that
|
||||
// port's record it has been read. Selecting a port starts the record again.
|
||||
static uint8_t registrySelected = 0;
|
||||
static uint8_t registryCursor = 0;
|
||||
|
||||
static const DeviceRecord *deviceOnPort(uint8_t port) {
|
||||
for (int i = 0; i < deviceCount; i++) {
|
||||
if (deviceTable[i].port == port) {
|
||||
return &deviceTable[i];
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// One byte of the selected port's record. Everything about a port that is not there
|
||||
// reads as zero, which is the same answer an absent registry would give.
|
||||
static uint8_t readRegistry(void) {
|
||||
const DeviceRecord *device = deviceOnPort(registrySelected);
|
||||
uint8_t answer = 0;
|
||||
if (device != NULL && registryCursor < DEVICE_RECORD_BYTES) {
|
||||
answer = (registryCursor == 0) ? device->deviceClass : device->flags;
|
||||
}
|
||||
if (registryCursor < DEVICE_RECORD_BYTES) {
|
||||
registryCursor++;
|
||||
}
|
||||
return answer;
|
||||
}
|
||||
|
||||
uint8_t OutputHandler(uint8_t DataByte, uint8_t Address) {
|
||||
// This function sends the DataByte to the appropriate place based on the Port Address.
|
||||
switch(Address) {
|
||||
case 0x00:
|
||||
case PORT_CONSOLE:
|
||||
// If data is sent here, it should be written to STDOUT.
|
||||
// For now, I'll implement this so it simply writes each byte out as it comes in.
|
||||
// Later, I'll want to use a buffer for this for performance, probably.
|
||||
putchar(DataByte);
|
||||
break;
|
||||
case 0x10:
|
||||
case PORT_REGISTRY:
|
||||
// Names the port the registry is being asked about. This is the only thing
|
||||
// that can be written to the registry, and it changes nothing about the
|
||||
// machine: it selects a question, it does not give an answer.
|
||||
registrySelected = DataByte;
|
||||
registryCursor = 0;
|
||||
break;
|
||||
case PORT_TEST:
|
||||
// A test device, and about the simplest one that can exist: writing to it
|
||||
// puts its own line up. It stands in for the shape a real device has, where
|
||||
// the CPU asks for something and is interrupted once the answer is ready,
|
||||
// with the waiting taken out so that a test runs the same way every time.
|
||||
// The byte written is ignored; only the asking matters.
|
||||
raiseInterrupt(0x10);
|
||||
raiseInterrupt(PORT_TEST);
|
||||
break;
|
||||
default:
|
||||
// Writes to unused Output Ports are ignored.
|
||||
@@ -64,10 +123,15 @@ uint8_t OutputHandler(uint8_t DataByte, uint8_t Address) {
|
||||
|
||||
uint8_t InputHandler(uint8_t Address) {
|
||||
switch(Address) {
|
||||
case 0x00:
|
||||
case PORT_CONSOLE:
|
||||
// If data is sent here, it should be read from STDIN.
|
||||
return getchar();
|
||||
break;
|
||||
case PORT_REGISTRY:
|
||||
// One byte of the selected port's record, then the next, and zero once the
|
||||
// record has run out.
|
||||
return readRegistry();
|
||||
break;
|
||||
default:
|
||||
// Reading from an unused port is ignored.
|
||||
return 0;
|
||||
|
||||
@@ -9,6 +9,36 @@
|
||||
#include <stdint.h>
|
||||
#include "cpu.h"
|
||||
|
||||
// ---- Ports ----
|
||||
//
|
||||
// Which port a device answers on is a property of the machine rather than of any
|
||||
// program, so the numbers live here and everything else refers to them by name.
|
||||
|
||||
#define PORT_CONSOLE 0x00
|
||||
#define PORT_TEST 0x10
|
||||
#define PORT_REGISTRY 0xFF
|
||||
|
||||
// ---- Device classes ----
|
||||
//
|
||||
// What kind of thing is plugged into a port. Class 0 is not a device: reading an
|
||||
// unimplemented port already gives zero, so "nothing there" needs no special case and a
|
||||
// machine with no registry at all answers correctly by doing nothing.
|
||||
//
|
||||
// Classes 0x01 to 0x0F belong to the machine itself. Peripherals start at 0x10.
|
||||
|
||||
#define DEVICE_NONE 0x00
|
||||
#define DEVICE_REGISTRY 0x01
|
||||
#define DEVICE_CONSOLE 0x02
|
||||
#define DEVICE_TEST 0x10
|
||||
|
||||
// What a device brings besides itself. The memory controller will want the first of
|
||||
// these to find out which ports own memory it can reach.
|
||||
#define DEVICE_FLAG_HAS_MEMORY 0x01
|
||||
|
||||
// How many bytes a device's entry in the registry runs to. Reading past the end gives
|
||||
// zero, so the record can grow later without anything already written having to change.
|
||||
#define DEVICE_RECORD_BYTES 2
|
||||
|
||||
uint8_t OutputHandler(uint8_t DataByte, uint8_t Address);
|
||||
|
||||
uint8_t InputHandler(uint8_t Address);
|
||||
|
||||
Reference in New Issue
Block a user