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 ----
//