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:
Anachronaut
2026-08-31 17:56:41 -04:00
co-authored by Claude Opus 5
parent 6b51d6391f
commit 04f1ffabd4
15 changed files with 351 additions and 6 deletions
+56 -1
View File
@@ -774,6 +774,11 @@ static FILE *diskImage[DISK_DRIVE_COUNT];
static uint32_t diskBlockCount[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.
static uint8_t diskDrive = 0;
static uint8_t diskDrives = 0;
@@ -852,12 +857,35 @@ uint8_t attachDisk(const char *path, uint8_t writeProtect) {
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) {
for (int at = 0; at < DISK_DRIVE_COUNT; at++) {
if (diskImage[at] != NULL) {
fclose(diskImage[at]);
diskImage[at] = NULL;
}
free(diskMemory[at]);
diskMemory[at] = NULL;
}
diskDrives = 0;
diskDrive = 0;
@@ -873,6 +901,23 @@ static void diskCommand(uint8_t command) {
raiseInterrupt(PORT_DISK);
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]) {
diskStatus |= DISK_STATUS_ERROR;
raiseInterrupt(PORT_DISK);
@@ -1276,7 +1321,11 @@ uint8_t OutputHandler(uint8_t DataByte, uint8_t Address) {
}
break;
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;
case PORT_MACHINE:
// 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_DRIVE: return diskDrive;
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:
// ---- Looking is what answers it ----
//
+25 -1
View File
@@ -66,7 +66,7 @@
// 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.
#define PORT_DISK 0x20
#define PORT_DISK_TOP 0x25
#define PORT_DISK_TOP 0x28
#define DISK_BLOCK_HIGH 0x20
#define DISK_BLOCK_LOW 0x21
#define DISK_COMMAND 0x22
@@ -87,6 +87,26 @@
#define DISK_DRIVE 0x24
#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.
#define DISK_DRIVE_COUNT 4
// ---- 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.
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);
// How many bytes a device's entry in the registry runs to. Reading past the end gives
+6
View File
@@ -197,6 +197,12 @@ uint8_t machineStart(Machine *m, const EmulatorOptions *options, const char *pro
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 device's, and a reset that left last program's screen up would be a reset that
// did not happen.
+12 -1
View File
@@ -46,6 +46,7 @@ uint8_t parseOptions(int argc, char *argv[], EmulatorOptions *options) {
{"cycles", required_argument, 0, 'c'},
{"fast", no_argument, 0, 'f'},
{"disk", required_argument, 0, 'D'},
{"ram-disk", required_argument, 0, 'R'},
{"write-protect", no_argument, 0, 'W'},
{"disk-cycles", required_argument, 0, 'L'},
{"screen", required_argument, 0, 'S'},
@@ -70,7 +71,7 @@ uint8_t parseOptions(int argc, char *argv[], EmulatorOptions *options) {
*options = (EmulatorOptions){0};
// 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) {
case 'd':
options->debug = 1;
@@ -105,6 +106,16 @@ uint8_t parseOptions(int argc, char *argv[], EmulatorOptions *options) {
options->disk = optarg;
}
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':
options->writeProtect = 1;
break;
+3
View File
@@ -29,6 +29,9 @@ typedef struct {
const char *disk; // Disk image to attach, or NULL for a machine with no disk.
const char *disks[DISK_DRIVE_COUNT];
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.
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.