A disk made of memory, brought up by whoever owns it
THE MACHINE SUPPLIES BLOCKS AND SAYS WHAT A DRIVE IS. It says nothing about filesystems, which is what leaves room for a system that would rather have its own - and is why the volatile bit is a fact about the hardware rather than a promise about SBFS. 0x26 what the selected drive is: bit 0, contents do not survive 0x27, 0x28 how many blocks it has --ram-disk N a drive of N blocks with memory behind it A drive of memory selects, reads, writes and has a size like any other, and a program cannot tell the difference except by how fast it was. The one thing it cannot work out for itself is that the contents are volatile, because an empty disk and a volatile disk look identical from outside. THAT BIT IS THE DIFFERENCE BETWEEN A DRIVE A SYSTEM MAY FORMAT ON SIGHT AND ONE IT MUST NOT. CosmOS formats a volatile drive it cannot read, because there was never anything on it to lose, and leaves every other unreadable drive alone - an unformatted floppy is not an invitation, it is a blank floppy. Removing that check formats somebody's blank disk, which is checked rather than asserted: cosmosBlankDisk boots with one and requires it to be refused. So CosmOS grew a format. The size comes from the drive rather than from a superblock, since a superblock states a size too and that is no use on a disk which has not got one yet. Sixteen directory blocks, 128 names, chosen rather than worked out: a scratch disk runs out of names long before room, and this machine cannot divide. The RAM disk is no faster on this emulator by default, and that is honest rather than disappointing: the emulated disk has no seek time unless asked for one. With --disk-cycles 10000 the same copy is 7.94M cycles against 8.70M, the difference being every write. run.sh takes "ram:2048" where an image name goes, which needs no removing between runs because there is nothing to remove. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01E2JrLzFvuFX9fgi1LDRjrW
This commit is contained in:
co-authored by
Claude Opus 5
parent
6b51d6391f
commit
04f1ffabd4
@@ -853,6 +853,23 @@ lives, and a disk that `make clean` deletes is not a disk of your own. It sits o
|
|||||||
|
|
||||||
Delete it by hand if you ever want a fresh one.
|
Delete it by hand if you ever want a fresh one.
|
||||||
|
|
||||||
|
### Disks Made Of Memory:
|
||||||
|
|
||||||
|
A drive the machine calls **volatile** loses everything when the machine stops. CosmOS formats
|
||||||
|
one it cannot read, because a drive whose contents do not survive never had anything to lose,
|
||||||
|
and mounts it like any other - so `--ram-disk 2048` gives you a working disk with nothing on
|
||||||
|
it, brought up before you reach a prompt.
|
||||||
|
|
||||||
|
**It leaves every other unreadable drive alone.** An unformatted floppy is not an invitation.
|
||||||
|
That distinction is the machine's to state and the system's to act on: the hardware says what
|
||||||
|
a drive *is*, and says nothing about filesystems, which is what leaves room for a system that
|
||||||
|
would rather have its own.
|
||||||
|
|
||||||
|
The size to format comes from the drive, not from a superblock - a superblock states a size
|
||||||
|
too, and that is no use on a disk which has not got one. The directory is sixteen blocks, 128
|
||||||
|
names, chosen rather than worked out: a scratch disk runs out of names long before it runs out
|
||||||
|
of room, and this machine cannot divide.
|
||||||
|
|
||||||
### Where A Program Is Looked For:
|
### Where A Program Is Looked For:
|
||||||
|
|
||||||
Three places, tried in order:
|
Three places, tried in order:
|
||||||
|
|||||||
@@ -136,6 +136,138 @@ sbfsMountTooBig:
|
|||||||
ADD ; Q is not zero: not a disk this will mount.
|
ADD ; Q is not zero: not a disk this will mount.
|
||||||
RET
|
RET
|
||||||
|
|
||||||
|
; ---- Making a disk into a filesystem ----
|
||||||
|
;
|
||||||
|
; The machine hands out blocks and says how many; what they mean is the system's business.
|
||||||
|
; That is the whole reason this exists here rather than only in the host tool: a machine with
|
||||||
|
; a drive made of memory comes up with a drive full of zeroes, and zeroes are not a
|
||||||
|
; filesystem. Somebody has to write the first one, and it should be whoever is going to read
|
||||||
|
; it - which is also what leaves room for a system that would rather have its own.
|
||||||
|
;
|
||||||
|
; Q is zero if it worked. Everything on the drive is lost, which is why nothing calls this
|
||||||
|
; except on a drive the machine has said is volatile.
|
||||||
|
sbfsFormat:
|
||||||
|
; How big it is, which only the drive can say. A superblock would say too, and a disk with
|
||||||
|
; no superblock is exactly the case this is for.
|
||||||
|
INA 0x27
|
||||||
|
SETD.1 SbfsScratch
|
||||||
|
STA.1
|
||||||
|
INA 0x28
|
||||||
|
INCD.1
|
||||||
|
STA.1
|
||||||
|
|
||||||
|
; The buffer, cleared, because everything not written below has to be nought and the
|
||||||
|
; controller's buffer holds whatever was last read.
|
||||||
|
SETD.0 SbfsBuffer
|
||||||
|
RSTB
|
||||||
|
sbfsFormatClear:
|
||||||
|
RSTA
|
||||||
|
STA.0
|
||||||
|
INCD.0
|
||||||
|
DECB
|
||||||
|
BNB sbfsFormatClear ; 256 of them: B wraps from nought to 255 and back to nought.
|
||||||
|
|
||||||
|
; "SBFS", and the version. Two, because a disk made now has directories.
|
||||||
|
SETD.0 SbfsMagic
|
||||||
|
SETD.1 SbfsBuffer
|
||||||
|
INIB 0d4
|
||||||
|
sbfsFormatMagic:
|
||||||
|
LDA.0
|
||||||
|
STA.1
|
||||||
|
INCD.0
|
||||||
|
INCD.1
|
||||||
|
DECB
|
||||||
|
BNB sbfsFormatMagic
|
||||||
|
INIA 0d2
|
||||||
|
STA.1 ; Offset 4: the version.
|
||||||
|
|
||||||
|
; Offset 6, how many blocks the disk has, as the drive reported it.
|
||||||
|
SETD.1 SbfsBuffer
|
||||||
|
DPUP.1 0d06
|
||||||
|
SETD.0 SbfsScratch
|
||||||
|
LDA.0
|
||||||
|
STA.1
|
||||||
|
INCD.0
|
||||||
|
INCD.1
|
||||||
|
LDA.0
|
||||||
|
STA.1
|
||||||
|
|
||||||
|
; Offset 8, where the directory starts: block one, straight after this one.
|
||||||
|
SETD.1 SbfsBuffer
|
||||||
|
DPUP.1 0d08
|
||||||
|
RSTA
|
||||||
|
STA.1
|
||||||
|
INCD.1
|
||||||
|
INIA 0d1
|
||||||
|
STA.1
|
||||||
|
|
||||||
|
; Offset 10, how many blocks of directory. SIXTEEN, WHICH IS 128 NAMES, and chosen rather
|
||||||
|
; than worked out: a scratch disk runs out of names long before it runs out of room, and
|
||||||
|
; this machine cannot divide, so a number that fits every size this is used for is worth
|
||||||
|
; more than arithmetic to find a better one.
|
||||||
|
SETD.1 SbfsBuffer
|
||||||
|
DPUP.1 0d10
|
||||||
|
RSTA
|
||||||
|
STA.1
|
||||||
|
INCD.1
|
||||||
|
INIA 0d16
|
||||||
|
STA.1
|
||||||
|
|
||||||
|
; Block nought, written.
|
||||||
|
SETD.0 SbfsBlock
|
||||||
|
RSTA
|
||||||
|
STA.0
|
||||||
|
INCD.0
|
||||||
|
STA.0
|
||||||
|
SETD.1 SbfsBuffer
|
||||||
|
CALL sbfsBufferIn
|
||||||
|
CALL sbfsWriteBlock
|
||||||
|
BNQ sbfsFormatFailed
|
||||||
|
|
||||||
|
; And the directory cleared, so that no entry is in use. The buffer is already nought
|
||||||
|
; everywhere the superblock did not reach, so it is cleared once more and written sixteen
|
||||||
|
; times rather than built again each time.
|
||||||
|
SETD.0 SbfsBuffer
|
||||||
|
RSTB
|
||||||
|
sbfsFormatBlank:
|
||||||
|
RSTA
|
||||||
|
STA.0
|
||||||
|
INCD.0
|
||||||
|
DECB
|
||||||
|
BNB sbfsFormatBlank
|
||||||
|
|
||||||
|
INIA 0d16
|
||||||
|
SETD.1 SbfsCount
|
||||||
|
STA.1
|
||||||
|
sbfsFormatDirectory:
|
||||||
|
SETD.0 SbfsBlock
|
||||||
|
INCD.0
|
||||||
|
LDA.0
|
||||||
|
INCA
|
||||||
|
STA.0 ; Blocks one to sixteen. A directory never crosses 255 here.
|
||||||
|
SETD.1 SbfsBuffer
|
||||||
|
CALL sbfsBufferIn
|
||||||
|
CALL sbfsWriteBlock
|
||||||
|
BNQ sbfsFormatFailed
|
||||||
|
SETD.1 SbfsCount
|
||||||
|
LDA.1
|
||||||
|
DECA
|
||||||
|
STA.1
|
||||||
|
BNA sbfsFormatDirectory
|
||||||
|
|
||||||
|
RSTA
|
||||||
|
RSTB
|
||||||
|
CCF
|
||||||
|
ADD
|
||||||
|
RET
|
||||||
|
|
||||||
|
sbfsFormatFailed:
|
||||||
|
RSTA
|
||||||
|
INIB 0d1
|
||||||
|
CCF
|
||||||
|
ADD
|
||||||
|
RET
|
||||||
|
|
||||||
; ---- Every drive the machine has ----
|
; ---- Every drive the machine has ----
|
||||||
;
|
;
|
||||||
; Asked for rather than assumed: the controller says how many are plugged in, and each is
|
; Asked for rather than assumed: the controller says how many are plugged in, and each is
|
||||||
@@ -192,7 +324,29 @@ sbfsMountEach:
|
|||||||
STA.1
|
STA.1
|
||||||
|
|
||||||
CALL sbfsMount
|
CALL sbfsMount
|
||||||
BNQ sbfsMountNext ; Nothing readable in that drive, and its slot stays empty.
|
BRQ sbfsMountGot
|
||||||
|
|
||||||
|
; ---- Nothing readable, and whether that is an invitation ----
|
||||||
|
;
|
||||||
|
; A drive the machine calls VOLATILE loses everything when the machine stops, so a volatile
|
||||||
|
; drive with no filesystem on it never had one to lose and bringing it up is the system's
|
||||||
|
; job. Anything else is somebody's disk: an unformatted floppy is not an invitation, it is
|
||||||
|
; a blank floppy, and a system that formatted it on sight would be a system you could not
|
||||||
|
; safely put a disk into.
|
||||||
|
;
|
||||||
|
; THE MACHINE SAYS WHAT THE DRIVE IS AND NOTHING ABOUT FILESYSTEMS. A system that would
|
||||||
|
; rather have its own reads the same bit and writes whatever it likes.
|
||||||
|
INA 0x26
|
||||||
|
INIB 0x01 ; VOLATILE
|
||||||
|
AND
|
||||||
|
BRQ sbfsMountNext ; Not ours to touch.
|
||||||
|
|
||||||
|
CALL sbfsFormat
|
||||||
|
BNQ sbfsMountNext
|
||||||
|
CALL sbfsMount
|
||||||
|
BNQ sbfsMountNext
|
||||||
|
|
||||||
|
sbfsMountGot:
|
||||||
|
|
||||||
; Mounted: remember which, and put the live record where it belongs.
|
; Mounted: remember which, and put the live record where it belongs.
|
||||||
SETD.1 SbfsDriveAt
|
SETD.1 SbfsDriveAt
|
||||||
|
|||||||
+56
-1
@@ -774,6 +774,11 @@ static FILE *diskImage[DISK_DRIVE_COUNT];
|
|||||||
static uint32_t diskBlockCount[DISK_DRIVE_COUNT];
|
static uint32_t diskBlockCount[DISK_DRIVE_COUNT];
|
||||||
static uint8_t diskProtected[DISK_DRIVE_COUNT];
|
static uint8_t diskProtected[DISK_DRIVE_COUNT];
|
||||||
|
|
||||||
|
// A drive whose blocks are memory. Everything else about it is a drive: it selects, it reads
|
||||||
|
// and writes, it has a size, and a filesystem on it is a filesystem. What it does not have is
|
||||||
|
// a file behind it, so it comes up as zeroes and goes away when the machine does.
|
||||||
|
static uint8_t *diskMemory[DISK_DRIVE_COUNT];
|
||||||
|
|
||||||
// Which one the registers refer to, and how many are plugged in at all.
|
// Which one the registers refer to, and how many are plugged in at all.
|
||||||
static uint8_t diskDrive = 0;
|
static uint8_t diskDrive = 0;
|
||||||
static uint8_t diskDrives = 0;
|
static uint8_t diskDrives = 0;
|
||||||
@@ -852,12 +857,35 @@ uint8_t attachDisk(const char *path, uint8_t writeProtect) {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
uint8_t attachRamDisk(uint32_t blocks) {
|
||||||
|
if (diskDrives >= DISK_DRIVE_COUNT) {
|
||||||
|
fprintf(stderr, "Error: This machine has %d drives.\n", DISK_DRIVE_COUNT);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
if (blocks == 0) {
|
||||||
|
fprintf(stderr, "Error: A disk of no blocks is not a disk.\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
const uint8_t at = diskDrives;
|
||||||
|
diskMemory[at] = calloc(blocks, DISK_BLOCK_BYTES);
|
||||||
|
if (diskMemory[at] == NULL) {
|
||||||
|
fprintf(stderr, "Error: Couldn't make a %u block disk in memory.\n", blocks);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
diskBlockCount[at] = blocks;
|
||||||
|
diskProtected[at] = 0;
|
||||||
|
diskDrives++;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
void detachDisk(void) {
|
void detachDisk(void) {
|
||||||
for (int at = 0; at < DISK_DRIVE_COUNT; at++) {
|
for (int at = 0; at < DISK_DRIVE_COUNT; at++) {
|
||||||
if (diskImage[at] != NULL) {
|
if (diskImage[at] != NULL) {
|
||||||
fclose(diskImage[at]);
|
fclose(diskImage[at]);
|
||||||
diskImage[at] = NULL;
|
diskImage[at] = NULL;
|
||||||
}
|
}
|
||||||
|
free(diskMemory[at]);
|
||||||
|
diskMemory[at] = NULL;
|
||||||
}
|
}
|
||||||
diskDrives = 0;
|
diskDrives = 0;
|
||||||
diskDrive = 0;
|
diskDrive = 0;
|
||||||
@@ -873,6 +901,23 @@ static void diskCommand(uint8_t command) {
|
|||||||
raiseInterrupt(PORT_DISK);
|
raiseInterrupt(PORT_DISK);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (diskMemory[diskDrive] != NULL) {
|
||||||
|
// A drive made of memory. The same block, the same 256 bytes, and no seek: what
|
||||||
|
// makes this worth having is that a program cannot tell except by how fast it was.
|
||||||
|
if (diskBlock >= diskBlockCount[diskDrive]) {
|
||||||
|
diskStatus |= DISK_STATUS_ERROR;
|
||||||
|
raiseInterrupt(PORT_DISK);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
uint8_t *at = diskMemory[diskDrive] + (size_t)diskBlock * DISK_BLOCK_BYTES;
|
||||||
|
if (command == DISK_COMMAND_WRITE) {
|
||||||
|
memcpy(at, diskBuffer, DISK_BLOCK_BYTES);
|
||||||
|
} else {
|
||||||
|
memcpy(diskBuffer, at, DISK_BLOCK_BYTES);
|
||||||
|
}
|
||||||
|
raiseInterrupt(PORT_DISK);
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (diskImage[diskDrive] == NULL || diskBlock >= diskBlockCount[diskDrive]) {
|
if (diskImage[diskDrive] == NULL || diskBlock >= diskBlockCount[diskDrive]) {
|
||||||
diskStatus |= DISK_STATUS_ERROR;
|
diskStatus |= DISK_STATUS_ERROR;
|
||||||
raiseInterrupt(PORT_DISK);
|
raiseInterrupt(PORT_DISK);
|
||||||
@@ -1276,7 +1321,11 @@ uint8_t OutputHandler(uint8_t DataByte, uint8_t Address) {
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case DISK_DRIVES:
|
case DISK_DRIVES:
|
||||||
// Read only: how many drives there are is a fact about the machine.
|
case DISK_FLAGS:
|
||||||
|
case DISK_SIZE_HIGH:
|
||||||
|
case DISK_SIZE_LOW:
|
||||||
|
// Read only: how many drives there are, and what kind each one is, are facts
|
||||||
|
// about the machine rather than instructions to it.
|
||||||
break;
|
break;
|
||||||
case PORT_MACHINE:
|
case PORT_MACHINE:
|
||||||
// Asked for here and acted on between instructions, because a device cannot
|
// Asked for here and acted on between instructions, because a device cannot
|
||||||
@@ -1359,6 +1408,12 @@ uint8_t InputHandler(uint8_t Address) {
|
|||||||
case DISK_BLOCK_LOW: return (uint8_t)(diskBlock & 0xFF);
|
case DISK_BLOCK_LOW: return (uint8_t)(diskBlock & 0xFF);
|
||||||
case DISK_DRIVE: return diskDrive;
|
case DISK_DRIVE: return diskDrive;
|
||||||
case DISK_DRIVES: return diskDrives;
|
case DISK_DRIVES: return diskDrives;
|
||||||
|
case DISK_FLAGS:
|
||||||
|
// What the selected drive IS. Volatile means its contents do not survive the
|
||||||
|
// machine stopping, which is the one thing a system cannot find out by looking.
|
||||||
|
return (uint8_t)(diskMemory[diskDrive] != NULL ? DISK_FLAG_VOLATILE : 0);
|
||||||
|
case DISK_SIZE_HIGH: return (uint8_t)(diskBlockCount[diskDrive] >> 8);
|
||||||
|
case DISK_SIZE_LOW: return (uint8_t)(diskBlockCount[diskDrive] & 0xFF);
|
||||||
case DISK_STATUS:
|
case DISK_STATUS:
|
||||||
// ---- Looking is what answers it ----
|
// ---- Looking is what answers it ----
|
||||||
//
|
//
|
||||||
|
|||||||
+25
-1
@@ -66,7 +66,7 @@
|
|||||||
// that spans more than one port raises its line on its base, which is the rule the
|
// that spans more than one port raises its line on its base, which is the rule the
|
||||||
// machine has not needed until now: the controller spans sixteen and never interrupts.
|
// machine has not needed until now: the controller spans sixteen and never interrupts.
|
||||||
#define PORT_DISK 0x20
|
#define PORT_DISK 0x20
|
||||||
#define PORT_DISK_TOP 0x25
|
#define PORT_DISK_TOP 0x28
|
||||||
#define DISK_BLOCK_HIGH 0x20
|
#define DISK_BLOCK_HIGH 0x20
|
||||||
#define DISK_BLOCK_LOW 0x21
|
#define DISK_BLOCK_LOW 0x21
|
||||||
#define DISK_COMMAND 0x22
|
#define DISK_COMMAND 0x22
|
||||||
@@ -87,6 +87,26 @@
|
|||||||
#define DISK_DRIVE 0x24
|
#define DISK_DRIVE 0x24
|
||||||
#define DISK_DRIVES 0x25
|
#define DISK_DRIVES 0x25
|
||||||
|
|
||||||
|
// ---- What a drive IS, which is the machine's business ----
|
||||||
|
//
|
||||||
|
// Not what is on it, which is the system's. A drive backed by memory loses everything when
|
||||||
|
// the machine stops, and that is the one fact about it a system cannot work out for itself -
|
||||||
|
// an empty disk and a volatile disk look identical from the outside.
|
||||||
|
//
|
||||||
|
// It matters because it is the difference between a drive a system may FORMAT on sight and
|
||||||
|
// one it must not. An unformatted floppy somebody put in deliberately is not an invitation;
|
||||||
|
// an unformatted RAM disk is, because there was never anything there to lose. The machine
|
||||||
|
// says which kind it is and says nothing about filesystems, so a system that would rather
|
||||||
|
// have its own reads the same bit and does as it likes.
|
||||||
|
#define DISK_FLAGS 0x26
|
||||||
|
#define DISK_FLAG_VOLATILE 0x01
|
||||||
|
|
||||||
|
// How many blocks the selected drive has. A fact about the drive rather than about what is
|
||||||
|
// on it - and the one a system needs before it can put anything on it, since the size a
|
||||||
|
// superblock states is no use on a disk that has not got one yet.
|
||||||
|
#define DISK_SIZE_HIGH 0x27
|
||||||
|
#define DISK_SIZE_LOW 0x28
|
||||||
|
|
||||||
// Four is a floppy controller's worth. The cost of another is a file handle.
|
// Four is a floppy controller's worth. The cost of another is a file handle.
|
||||||
#define DISK_DRIVE_COUNT 4
|
#define DISK_DRIVE_COUNT 4
|
||||||
// ---- The screen ----
|
// ---- The screen ----
|
||||||
@@ -317,6 +337,10 @@ void setDiskLatency(unsigned long cycles);
|
|||||||
// equivalent of the tab on the side of a floppy. Returns 1 if it could not attach.
|
// equivalent of the tab on the side of a floppy. Returns 1 if it could not attach.
|
||||||
uint8_t attachDisk(const char *path, uint8_t writeProtect);
|
uint8_t attachDisk(const char *path, uint8_t writeProtect);
|
||||||
|
|
||||||
|
// A drive of that many blocks, backed by memory rather than by a file. It comes up as
|
||||||
|
// zeroes, which is not a filesystem - bringing it up is the system's job.
|
||||||
|
uint8_t attachRamDisk(uint32_t blocks);
|
||||||
|
|
||||||
void detachDisk(void);
|
void detachDisk(void);
|
||||||
|
|
||||||
// How many bytes a device's entry in the registry runs to. Reading past the end gives
|
// How many bytes a device's entry in the registry runs to. Reading past the end gives
|
||||||
|
|||||||
@@ -197,6 +197,12 @@ uint8_t machineStart(Machine *m, const EmulatorOptions *options, const char *pro
|
|||||||
return MACHINE_ERROR;
|
return MACHINE_ERROR;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// After the images, so the drive numbers a command line asks for are the order it asks
|
||||||
|
// in. A disk made of memory is still a drive and still has to be brought up by whatever
|
||||||
|
// system is running; the machine only supplies the blocks.
|
||||||
|
if (options->ramDisk > 0 && attachRamDisk((uint32_t)options->ramDisk)) {
|
||||||
|
return MACHINE_ERROR;
|
||||||
|
}
|
||||||
// The screen starts blank, and starts blank again on a warm restart: video memory is
|
// The screen starts blank, and starts blank again on a warm restart: video memory is
|
||||||
// the device's, and a reset that left last program's screen up would be a reset that
|
// the device's, and a reset that left last program's screen up would be a reset that
|
||||||
// did not happen.
|
// did not happen.
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ uint8_t parseOptions(int argc, char *argv[], EmulatorOptions *options) {
|
|||||||
{"cycles", required_argument, 0, 'c'},
|
{"cycles", required_argument, 0, 'c'},
|
||||||
{"fast", no_argument, 0, 'f'},
|
{"fast", no_argument, 0, 'f'},
|
||||||
{"disk", required_argument, 0, 'D'},
|
{"disk", required_argument, 0, 'D'},
|
||||||
|
{"ram-disk", required_argument, 0, 'R'},
|
||||||
{"write-protect", no_argument, 0, 'W'},
|
{"write-protect", no_argument, 0, 'W'},
|
||||||
{"disk-cycles", required_argument, 0, 'L'},
|
{"disk-cycles", required_argument, 0, 'L'},
|
||||||
{"screen", required_argument, 0, 'S'},
|
{"screen", required_argument, 0, 'S'},
|
||||||
@@ -70,7 +71,7 @@ uint8_t parseOptions(int argc, char *argv[], EmulatorOptions *options) {
|
|||||||
*options = (EmulatorOptions){0};
|
*options = (EmulatorOptions){0};
|
||||||
|
|
||||||
// Parse options
|
// Parse options
|
||||||
while ((opt = getopt_long(argc, argv, "dc:fhD:WL:S:K:N:", long_options, &option_index)) != -1) {
|
while ((opt = getopt_long(argc, argv, "dc:fhD:WL:S:K:N:R:", long_options, &option_index)) != -1) {
|
||||||
switch (opt) {
|
switch (opt) {
|
||||||
case 'd':
|
case 'd':
|
||||||
options->debug = 1;
|
options->debug = 1;
|
||||||
@@ -105,6 +106,16 @@ uint8_t parseOptions(int argc, char *argv[], EmulatorOptions *options) {
|
|||||||
options->disk = optarg;
|
options->disk = optarg;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case 'R': {
|
||||||
|
char *end;
|
||||||
|
const unsigned long blocks = strtoul(optarg, &end, 10);
|
||||||
|
if (*optarg == '\0' || *end != '\0' || blocks == 0) {
|
||||||
|
fprintf(stderr, "Error: --ram-disk wants a number of blocks.\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
options->ramDisk = blocks;
|
||||||
|
}
|
||||||
|
break;
|
||||||
case 'W':
|
case 'W':
|
||||||
options->writeProtect = 1;
|
options->writeProtect = 1;
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -29,6 +29,9 @@ typedef struct {
|
|||||||
const char *disk; // Disk image to attach, or NULL for a machine with no disk.
|
const char *disk; // Disk image to attach, or NULL for a machine with no disk.
|
||||||
const char *disks[DISK_DRIVE_COUNT];
|
const char *disks[DISK_DRIVE_COUNT];
|
||||||
int diskCount;
|
int diskCount;
|
||||||
|
// A drive of this many blocks made of memory, taking the next drive number after the
|
||||||
|
// images above. Zero for a machine without one.
|
||||||
|
unsigned long ramDisk;
|
||||||
uint8_t writeProtect; // Attach the disk read only, the way a tab on a floppy would.
|
uint8_t writeProtect; // Attach the disk read only, the way a tab on a floppy would.
|
||||||
const char *screen; // Where to save a picture of the screen when the machine stops.
|
const char *screen; // Where to save a picture of the screen when the machine stops.
|
||||||
const char *keyboard; // Feed the console from this file as a keyboard, not a terminal.
|
const char *keyboard; // Feed the console from this file as a keyboard, not a terminal.
|
||||||
|
|||||||
@@ -546,7 +546,7 @@ If nothing is installed for the vector a device refused with, the machine stops
|
|||||||
| 0x00 - 0x05 | The console. See The Console. 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 |
|
| 0x00 - 0x05 | The console. See The Console. 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 - 0x25 | The disk. See Storage. It interrupts on 0x20, its base port. | 0x13 |
|
| 0x20 - 0x28 | The disk. See Storage. It interrupts on 0x20, its base port. | 0x13 |
|
||||||
| 0x13 | The machine itself. Writing 1 asks it to start over: whatever put the first instruction in memory does it again, and the CPU begins where the boot vector points. A port rather than a service, because a reset has to work when the system does not - and a program that owns the whole machine has no system to ask. The disk is not unplugged and keeps what was written to it; the vector table is cleared, because a handler left behind would aim an interrupt into a program that is no longer running. | 0x04 |
|
| 0x13 | The machine itself. Writing 1 asks it to start over: whatever put the first instruction in memory does it again, and the CPU begins where the boot vector points. A port rather than a service, because a reset has to work when the system does not - and a program that owns the whole machine has no system to ask. The disk is not unplugged and keeps what was written to it; the vector table is cleared, because a handler left behind would aim an interrupt into a program that is no longer running. | 0x04 |
|
||||||
| 0x12 | A device that owns 256 bytes of memory. Writing to its port fills that memory with the byte written, standing in for a disk controller reading a sector. Its memory is unreachable until it is registered as a bank. | 0x12 |
|
| 0x12 | A device that owns 256 bytes of memory. Writing to its port fills that memory with the byte written, standing in for a disk controller reading a sector. Its memory is unreachable until it is registered as a bank. | 0x12 |
|
||||||
| 0x30 - 0x3F | The screen. See The Screen. It brings video memory, which is unreachable until it is registered as a bank. | 0x14 |
|
| 0x30 - 0x3F | The screen. See The Screen. It brings video memory, which is unreachable until it is registered as a bank. | 0x14 |
|
||||||
@@ -1249,6 +1249,29 @@ disk port. So the drive is a register, which is what a floppy controller has alw
|
|||||||
| --- | --- |
|
| --- | --- |
|
||||||
| 0x24 | Drive. Which one the block, command and status registers refer to. Reads back. |
|
| 0x24 | Drive. Which one the block, command and status registers refer to. Reads back. |
|
||||||
| 0x25 | Drives, read only. How many are plugged in. |
|
| 0x25 | Drives, read only. How many are plugged in. |
|
||||||
|
| 0x26 | What the selected drive is, read only. Bit 0: its contents do not survive the machine stopping. |
|
||||||
|
| 0x27, 0x28 | How many blocks the selected drive has, read only. |
|
||||||
|
|
||||||
|
### A Drive Made Of Memory:
|
||||||
|
|
||||||
|
A drive may have memory behind it instead of a file. It selects, reads, writes and has a size
|
||||||
|
like any other, and a filesystem on it is a filesystem - **a program cannot tell the
|
||||||
|
difference except by how fast it was.** What it has not got is anything that survives the
|
||||||
|
machine stopping.
|
||||||
|
|
||||||
|
That difference is the one thing a system cannot work out for itself, because an empty disk
|
||||||
|
and a volatile disk look identical from outside. So the machine says it, in bit 0 of 0x26, and
|
||||||
|
says nothing whatever about filesystems.
|
||||||
|
|
||||||
|
**Which is the whole point of saying it that way.** The bit is what separates a drive a system
|
||||||
|
may format on sight from one it must not: an unformatted floppy somebody put in deliberately
|
||||||
|
is not an invitation, while an unformatted drive made of memory never had anything to lose. A
|
||||||
|
system reads the bit and draws its own conclusion - and a system that would rather have a
|
||||||
|
different filesystem entirely reads the same bit and writes whatever it likes. **The machine
|
||||||
|
supplies blocks. Bringing them up is the system's job.**
|
||||||
|
|
||||||
|
The size registers exist for the same reason. A superblock states a disk's size too, and that
|
||||||
|
is no use at all on a disk which has not got one yet.
|
||||||
|
|
||||||
The block, command and status registers, **and the single buffer**, all belong to whichever
|
The block, command and status registers, **and the single buffer**, all belong to whichever
|
||||||
drive is selected. A program that changes drives is holding a buffer that no longer contains
|
drive is selected. A program that changes drives is holding a buffer that no longer contains
|
||||||
|
|||||||
@@ -79,7 +79,7 @@ from `make`, not from here.
|
|||||||
### 1. Recorded output
|
### 1. Recorded output
|
||||||
|
|
||||||
`Tests/run.sh` assembles each program named in `Tests/manifest`, runs it, and compares
|
`Tests/run.sh` assembles each program named in `Tests/manifest`, runs it, and compares
|
||||||
everything it printed against a file in `Tests/expected`. 184 tests, of which 122 run, 35
|
everything it printed against a file in `Tests/expected`. 186 tests, of which 124 run, 35
|
||||||
only assemble, 16 are expected to fail to assemble, and 11 boot from ROM with no image
|
only assemble, 16 are expected to fail to assemble, and 11 boot from ROM with no image
|
||||||
given at all.
|
given at all.
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
CosmOS
|
||||||
|
> drive: nothing this can read is in that drive
|
||||||
|
> 0
|
||||||
|
> halted
|
||||||
|
Execution halted.
|
||||||
|
[exit 0]
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
CosmOS
|
||||||
|
> > 0 files
|
||||||
|
> copied
|
||||||
|
finished
|
||||||
|
> Say.sbx 156
|
||||||
|
1 file
|
||||||
|
> 1
|
||||||
|
> halted
|
||||||
|
Execution halted.
|
||||||
|
[exit 0]
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
drive 1
|
||||||
|
drive
|
||||||
|
exit
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
drive 1
|
||||||
|
dir
|
||||||
|
Copy 0:/Say.sbx 1:/Say.sbx
|
||||||
|
dir
|
||||||
|
drive
|
||||||
|
exit
|
||||||
@@ -849,6 +849,25 @@ timerTest | testPrograms/timerTest.asm | run | -
|
|||||||
# at 120 beats a minute - seven and a half frames, so the screen cannot express it at all.
|
# at 120 beats a minute - seven and a half frames, so the screen cannot express it at all.
|
||||||
# Eight of them is a second, and nearly all of that second is spent asleep.
|
# Eight of them is a second, and nearly all of that second is spent asleep.
|
||||||
timerBeatTest | testPrograms/timerBeatTest.asm | run | - | 5000000 | -
|
timerBeatTest | testPrograms/timerBeatTest.asm | run | - | 5000000 | -
|
||||||
|
# ---- A disk made of memory ----
|
||||||
|
#
|
||||||
|
# The machine supplies blocks and says the drive is volatile; the SYSTEM decides what that
|
||||||
|
# means. CosmOS formats a volatile drive it cannot read, because a drive whose contents do
|
||||||
|
# not survive the machine never had anything to lose - and leaves any other one alone, which
|
||||||
|
# is why an unformatted floppy is safe from it.
|
||||||
|
#
|
||||||
|
# So this boots with a drive of nothing, and the first dir on it is a listing rather than an
|
||||||
|
# error: the system brought it up. Then a copy onto it, to show it is a disk like any other.
|
||||||
|
cosmosRamDisk | CosmOS/Source/cosmos.asm | run | cosmosRamDisk.in | 90000000 | disks/cosmos.img+ram:2048
|
||||||
|
# ---- And the other half of that rule, which is the dangerous half ----
|
||||||
|
#
|
||||||
|
# The same blank disk, on a drive the machine has NOT called volatile. It must be refused
|
||||||
|
# rather than formatted: an unformatted floppy is not an invitation, it is a blank floppy,
|
||||||
|
# and a system that formatted one on sight is a system you could not safely put a disk into.
|
||||||
|
#
|
||||||
|
# blank.img has no directory in its name, so run.sh removes it before every run and the
|
||||||
|
# emulator makes a fresh one of zeroes - which is exactly the disk this is about.
|
||||||
|
cosmosBlankDisk | CosmOS/Source/cosmos.asm | run | cosmosBlankDisk.in | 90000000 | disks/cosmos.img+blank.img
|
||||||
printDecimalTest | testPrograms/printDecimalTest.asm | xfail | - | -
|
printDecimalTest | testPrograms/printDecimalTest.asm | xfail | - | -
|
||||||
printDigitTest | testPrograms/printDigitTest.asm | xfail | - | -
|
printDigitTest | testPrograms/printDigitTest.asm | xfail | - | -
|
||||||
printHexTest | testPrograms/printHexTest.asm | xfail | - | -
|
printHexTest | testPrograms/printHexTest.asm | xfail | - | -
|
||||||
|
|||||||
@@ -257,6 +257,14 @@ while IFS='|' read -r name src mode stdin limit disk keys; do
|
|||||||
# become drives 0 upwards in the order written. Each keeps its own :ro and @N,
|
# become drives 0 upwards in the order written. Each keeps its own :ro and @N,
|
||||||
# because those are properties of a disk rather than of the machine.
|
# because those are properties of a disk rather than of the machine.
|
||||||
for onedisk in ${disk//+/ }; do
|
for onedisk in ${disk//+/ }; do
|
||||||
|
# ---- A drive made of memory ----
|
||||||
|
#
|
||||||
|
# "ram:2048" asks for one of that many blocks instead of naming an image. It has
|
||||||
|
# no file behind it, so there is nothing to remove between runs and nothing to
|
||||||
|
# go stale: it is zeroes every time by construction.
|
||||||
|
case "$onedisk" in
|
||||||
|
ram:*) EMUARGS+=(--ram-disk "${onedisk#ram:}"); onedisk="-" ;;
|
||||||
|
esac
|
||||||
if [ "$onedisk" != "-" ]; then
|
if [ "$onedisk" != "-" ]; then
|
||||||
# A trailing :ro attaches the image write protected, so that a test can
|
# A trailing :ro attaches the image write protected, so that a test can
|
||||||
# check the device bars writes rather than the filesystem asking nicely.
|
# check the device bars writes rather than the filesystem asking nicely.
|
||||||
|
|||||||
Reference in New Issue
Block a user