CosmOS: a service interface for the disk and console, and the monitor in the shell

Two changes that arrived together because both live in cosmos.asm.

THE SERVICES. A loaded program that wanted a file had to include the whole
filesystem, carrying two and a half kilobytes of a private copy of code the
system already had running, and then mount a disk that was already mounted.
Five services are added at pinned numbers 20 to 24: osFileRead, osFileSave,
osFileDelete, osFileRename and osPrintNumber.

The sizes fit the registers exactly in both directions. A file that can be
read into Data Memory is under 64K by definition, so its length is sixteen
bits: coming back it is DP3, going out it is A and B together, and neither
direction needs a record in memory whose shape both sides must agree on.

There is deliberately no service to mount a disk. The system mounts one
before its first prompt, and a program mounting it again was only ever a
consequence of owning a second copy of the library, so that call disappears
rather than moving. Apps/Files.asm writes, reads, renames and deletes a file
in 645 bytes and includes nothing but the service names.

THE MONITOR. Previously an application, now part of the shell, because an
application occupies the one region a loaded application is given: a monitor
that was an application could never examine another one, since loading the
thing to be inspected would replace the thing doing the inspecting.

"monitor" turns it on and the prompt becomes "*". It is a mode rather than a
sub-prompt, and it persists: because the mode is a variable the prompt reads
rather than a second loop, and every path back to the prompt goes through one
place including osExit, a program started with "g" that gives the machine back
arrives at the monitor prompt it was started from. Examining a program and
running it therefore do not interrupt each other. "exit" leaves whatever you
are in.

It supersedes dump, and adds disassembly, writing bytes, and jumping to an
address. Its instruction table is generated from the assembler's own list by
Tests/instructiontable.py rather than typed again, and Tests/docs.sh checks
both that the system's copy matches the generator and that the lengths that
table implies are the ones the manual's Bytes column prints. A disassembler
that disagreed about a length would not print one line wrong, it would lose
its place and print everything after it wrong.

Also here: b refuses a bank that is not registered, since asking the
controller for one is refused and a refusal nobody catches stops the machine;
g records the Stack the way run does, without which a program returning
through osExit restored whatever the last run had left; and make cosmos-disk
now depends on the system as well as the image.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Anachronaut
2026-08-19 18:19:15 -04:00
co-authored by Claude Opus 5
parent 301716e869
commit 0b6d2be43f
17 changed files with 1411 additions and 72 deletions
+176
View File
@@ -0,0 +1,176 @@
; A program that keeps a file without knowing how a filesystem works.
;
; INCLUDES NOTHING BUT THE SERVICE NAMES. No sbfs.asm, no console.asm - the system has both
; of those running already, and this asks it rather than carrying a second copy. That is the
; whole point of the program: if it works, a tool that edits documents does not need two and
; a half kilobytes of filesystem bound into it.
;
; It writes a file, reads it back, says how big it was, renames it, and takes it away again,
; which is every file service there is.
;
; ---- What you will see before the handlers are written ----
;
; The numbers are pinned but nothing answers to them yet, so the first call dispatches
; through an empty vector and the machine stops:
;
; Fault: Software vector 21, dispatched from Program Address 0x200B, has no handler
; installed.
;
; That is the fault working properly rather than the program being broken. A vector with
; nothing in it is not a jump to address zero; it is a stop, with the vector named and the
; place it was called from named, which is as much as the machine can know.
#Include services.asm
#Program
#Base 0x2000
start:
; ---- Write it ----
;
; A and B together are how many bytes there are, most significant first, which is the
; same sixteen bits a length always is on this machine.
SETD.0 Name
SETD.1 Body
RSTA
INIB 0d22 ; The text and its newline. NOT the zero the assembler put
SWI osFileSave ; after it: a text file ends where the text ends.
BNQ noSave
SETD.0 SavedText
SWI osPrintString
; ---- Read it back ----
;
; DP3 comes back holding how many bytes there were, because that is one of the two things
; a service is allowed to answer in and a file that fits in memory has a length that fits
; in a pointer.
SETD.0 Name
SETD.1 Landing
SWI osFileRead
BNQ noRead
SETD.0 ReadText
SWI osPrintString
PSHD.3
POPB ; The low byte is on top, the way a pointer is pushed.
POPA
SWI osPrintNumber
SETD.0 BytesText
SWI osPrintString
; And now the length is needed for something rather than just reported. What came back is
; a file, not a string: nothing on the disk ends in a zero byte, because the entry says
; where it stops instead. So a zero goes on the end before it can be printed as one.
SETD.1 Landing
PSHD.3
POPB
POPA
walkToEnd:
BRB atEnd
INCD.1
DECB
BRI walkToEnd
atEnd:
RSTA
STA.1
SETD.0 Landing
SWI osPrintString
; ---- Call it something else ----
SETD.0 Name
SETD.1 OtherName
SWI osFileRename
BNQ noRename
SETD.0 RenamedText
SWI osPrintString
; ---- And take it away ----
SETD.0 OtherName
SWI osFileDelete
BNQ noDelete
SETD.0 DeletedText
SWI osPrintString
; Reading it now should fail, and a service saying no is not the same as one that is not
; there: this comes back with an answer rather than stopping the machine.
SETD.0 OtherName
SETD.1 Landing
SWI osFileRead
BRQ stillThere
SETD.0 GoneText
SWI osPrintString
SWI osExit
stillThere:
SETD.0 StillText
SWI osPrintString
SWI osExit
noSave:
SETD.0 NoSaveText
SWI osPrintString
SWI osExit
noRead:
SETD.0 NoReadText
SWI osPrintString
SWI osExit
noRename:
SETD.0 NoRenameText
SWI osPrintString
SWI osExit
noDelete:
SETD.0 NoDeleteText
SWI osPrintString
SWI osExit
#Data
#Base 0x1000
Name:
"kept.txt"
OtherName:
"moved.txt"
Body:
"a file kept by asking
"
SavedText:
"saved it
"
ReadText:
"read it back, "
BytesText:
" bytes:
"
RenamedText:
"renamed it
"
DeletedText:
"deleted it
"
GoneText:
"and it is gone
"
StillText:
"but it is still there
"
NoSaveText:
"it would not save
"
NoReadText:
"it would not read
"
NoRenameText:
"it would not rename
"
NoDeleteText:
"it would not delete
"
Landing:
#Reserve 0d256
File diff suppressed because it is too large Load Diff
+26
View File
@@ -25,3 +25,29 @@
osReadLine 0d17 ; DP0 names somewhere to put a line read from the console.
osExit 0d18 ; Give the machine back to the system.
osArgument 0d19 ; DP0 names somewhere to put the rest of the run command.
; ---- What the system does with the disk on a program's behalf ----
;
; A loaded program that wanted a file used to include the whole filesystem, which is two
; and a half kilobytes of it carrying a private copy of code the system already has
; running. These are that code, reachable.
;
; NOTHING HERE MOUNTS ANYTHING. The system mounted the disk before it read the prompt, and
; there is one disk with one buffer registered as one bank; a program mounting it again was
; only ever an artefact of having its own copy of the library.
;
; Sizes are in bytes and fit the registers exactly. A file that can be read into Data
; Memory is under 64K by definition, so its length is sixteen bits: coming back it is DP3,
; going out it is A and B together, and neither direction needs a record in memory that
; both sides have to agree on the shape of.
osFileRead 0d20 ; DP0 names it, DP1 says where. Q is zero if it read, DP3 is how many bytes.
osFileSave 0d21 ; DP0 names it, DP1 is the bytes, A and B are how many. Q is zero if it saved.
osFileDelete 0d22 ; DP0 names it. Q is zero if it went.
osFileRename 0d23 ; DP0 is the name it has, DP1 the name it should have. Q is zero if it moved.
; ---- And with the console ----
;
; printString is already up there. This is the other half of what a program prints: a
; number, in decimal, without leading zeroes. A and B together, so one service covers both
; a line number and a byte count and there is no need for two.
osPrintNumber 0d24
+4 -1
View File
@@ -79,7 +79,10 @@ $(COSMOS_DISK): $(APPS)
$(DISKTOOL) format $@ 256 2
@for app in $(APPS); do $(DISKTOOL) put $@ $$app; done
cosmos-disk: $(COSMOS_DISK)
# The system as well as the disk. Building only the image leaves whatever cosmos.bin was
# there before, or none at all, and then the disk is booted with a system that does not
# match the programs on it.
cosmos-disk: $(COSMOS) $(COSMOS_DISK)
run-cosmos: $(COSMOS) $(COSMOS_DISK)
$(EMU) --disk $(COSMOS_DISK) $(COSMOS)
+3 -1
View File
@@ -14,6 +14,8 @@ SplitBit is a custom 8 bit system designed for hobbyist projects and experimenta
- Devices: A bus registry that says what a machine is made of, so a program can ask rather than being told.
- Filesystem: SBFS, read and written by SplitBit itself, and by a host tool that speaks the same format so an image can be moved either way.
- Loadable Programs: A program that was not booted from carries a header saying where it belongs, and Programs/loader.asm reads one off a disk, puts it there, and runs it.
- An Operating System: CosmOS boots the machine, mounts a disk, lists what is on it, loads a program and runs it, and takes the machine back when it finishes. It comes with a library of programs to run, including a game and a line editor that writes files a person typed.
- System Services: A loaded program reaches the console and the disk through numbered software interrupts rather than carrying a copy of the code that drives them. The numbers are written down in one file that both sides include, so neither ever types one. It took the editor from 4941 bytes to 1983 without changing a line of what it does.
- Storage: A block device with 256 byte blocks and 16 megabytes of them, backed by an image file on the host. It knows blocks and not files, because a filesystem is meant to be software SplitBit runs.
- Memory Controller: Reads and writes Program Memory, moves blocks between memory banks, reaches memory that devices bring with them, and guards a range against being written by accident. It is how a SplitBit machine loads a program.
- Assembler: Assemble human readable assembly language files directly into SplitBit compatible binary files. Supports including external files, handling labels, alignment and reservation, and defining Program, Data and Vector segments.
@@ -66,7 +68,7 @@ The sources are ISO C, and build clean under -std=c11 -pedantic with -Wall -Wext
- delete \<image\> \<name\>: Remove one.
#### Notes:
- SplitDisk speaks the same on disk format SplitBit does, so an image it makes is one the machine can read, and one the machine writes is one it can read back. Until SplitBit can write its own filesystem this is the only way to get a program onto a disk.
- SplitDisk speaks the same on disk format SplitBit does, so an image it makes is one the machine can read, and one the machine writes is one it can read back. SplitBit writes its own filesystem now, so this is not the only way to get something onto a disk; it is still the only way to get a program onto one, since nothing running on the machine assembles anything yet.
- Files are laid down contiguously, so a disk can have free blocks without having them in one piece. When that happens put says so rather than putting part of a file on.
### Usage:
+85 -1
View File
@@ -555,6 +555,11 @@ Those numbers are written down once, in `Programs/CosmOS/Source/services.asm`, w
| osReadLine | DP0 names somewhere to put a line, B says how much room there is. Reads one from the console. Q comes back holding how long it was. |
| osExit | Gives the machine back. Does not return. |
| osArgument | DP0 names somewhere to put whatever followed the run command, B says how much room there is. |
| osFileRead | DP0 names a file, DP1 says where to put it. Q is zero if it read, and DP3 comes back holding how many bytes there were. |
| osFileSave | DP0 names a file, DP1 is the bytes, A and B together are how many. Q is zero if it saved, whether or not it was there before. |
| osFileDelete | DP0 names a file. Q is zero if it went. |
| osFileRename | DP0 is the name a file has, DP1 the name it should have. Q is zero if it moved. |
| osPrintNumber | A and B together are a number. Prints it in decimal, without leading zeroes. |
```
#Include services.asm
@@ -563,6 +568,18 @@ Those numbers are written down once, in `Programs/CosmOS/Source/services.asm`, w
SWI osPrintString
```
### The Disk Without A Filesystem:
A program that wants a file does not need to know what a filesystem is. Before these existed it had to include the whole of `sbfs.asm` — two and a half kilobytes of a private copy of code the system already had running — and then mount a disk that was already mounted.
There is no service to mount one, and that is not an omission. The system mounts the disk before it reads its first prompt, and there is one disk with one buffer registered as one bank; a program mounting it again was only ever an artefact of owning a second copy of the library. That call disappears rather than moving.
Sizes fit the registers exactly, in both directions. A file that can be read into Data Memory is under 64K by definition, so its length is sixteen bits: coming back it is DP3, and going out it is A and B together. Neither direction needs a record in memory whose shape both sides have to agree on.
A file of 256 blocks or more is refused by `osFileRead` rather than partly read, because 64K will not fit in Data Memory and its length will not fit in the pointer that reports it. A length that lies would be worse than a file that will not open.
`Programs/CosmOS/Apps/Files.asm` does the whole round trip — write, read, report, rename, delete — in 645 bytes, and includes nothing but the service names.
`osArgument` is how a program is told what it is for. Everything written before it did the same thing however it was started, which is fine for a program that greets you and no use to one that edits a named document. What arrives is the whole rest of the line, spaces and all, rather than a list of words: what counts as an argument is the program's business, and handing over what was typed is the system's.
A handler is entered with the caller's registers exactly as they were, because an interrupt frame is pushed rather than cleared. That is why a service can be given a pointer in DP0 and a count in B without any of it being copied anywhere first.
@@ -642,7 +659,7 @@ Whoever does the loading keeps its own code and data below the addresses the loa
## Programs That Come With The System:
`Programs/CosmOS/Apps` holds what the shell can load. Most of them are old programs that were written for the bare machine and needed five edits to become loadable ones; the last three were written for the system as it is now.
`Programs/CosmOS/Apps` holds what the shell can load. Several are old programs written for the bare machine that needed five edits each to become loadable ones the Fibonacci and sieve programs, `greet`, and `hello`. The rest were written for the system as it is now, and each of those exists to show one thing working:
| Program | What it is for |
| --- | --- |
@@ -650,12 +667,79 @@ Whoever does the loading keeps its own code and data below the addresses the loa
| Snake | A game. Draws a whole screen with cursor addressing and steers with single keys, asking the console once a frame and never waiting. |
| Keys | The console interrupting rather than being asked. The only one that brings a vector of its own, which is what the version two format exists for. |
| Say | Prints whatever it was told, which is the shortest thing that shows osArgument working. |
| Files | Writes a file, reads it back, renames it and deletes it, in 645 bytes, including nothing but the service names. It is what says a program does not need a filesystem inside it. |
| Edit | A line editor. |
### The Monitor:
The monitor is **part of the shell**, not a program the shell loads, and that is the whole reason it works. A loaded program occupies the one place a loaded program goes, so a monitor that was an application could never look at any other application: loading the thing you wanted to inspect would replace the thing doing the inspecting.
`monitor` turns it on and the prompt changes from `>` to `*`. It is **a mode, not a detour** — the shell's own commands still work, and the mode persists until you say otherwise:
```
> load Snake.sbx
> monitor
* d 2000
2000 47 00 11 00 SETD.0 1100
* b data
bank 01
* x 1000
* exit
>
```
**A program giving the machine back lands at the prompt it was started from**, so `g` into something, letting it run, and having it exit puts you back at `*` rather than at the shell. That falls out of the mode being a variable the prompt reads rather than a second loop: every way back to the prompt goes through one place, including `osExit`. Looking at a program and running it therefore do not interrupt each other, which is the thing a monitor is for.
`exit` leaves whatever you are in — the monitor if you are in it, the machine if you are not.
| | |
| --- | --- |
| `x [addr]` | Sixty-four bytes, as hex and as characters |
| `d [addr]` | Eight instructions, disassembled |
| `s addr b b …` | Put those bytes there |
| `b program\|data\|n` | Which bank to look at |
| `g addr` | Go there |
`x` and `d` share one cursor and each leaves it past what it showed, so without an address either carries on — reading through memory is one letter at a time, and you can switch between bytes and instructions without retyping where you are. `s` deliberately does not move it.
Everything else here does something; the monitor looks at what the others did. It shows memory as hex and as characters, disassembles it, writes bytes into it, and jumps to an address — all through the memory controller, which is the only thing that can reach Program Memory.
That is why a monitor is worth more on this machine than on most. Data Memory a program can already read for itself with a Data Pointer. The half it cannot see is Program Memory, and that is the half its bugs are in.
**Its instruction table is generated from the assembler's**, by `Tests/instructiontable.py`, and checked against it by `Tests/docs.sh` — along with a second check that the lengths that table implies are the ones the manual's own Bytes column prints. Both matter for the same reason: a disassembler that disagreed about how long an instruction is would not print one line wrong, it would lose its place and print everything after it wrong. Which is what a disassembler does anyway when it starts in the middle of an instruction, and is worth seeing once so it is recognised later.
**Where to put something you typed in yourself** is a question the monitor answers, because the answer moves every time the monitor is rebuilt. `m` says where its own two segments end, and those are the first free addresses:
```
> m
code from 2000, free from 2607
data from 1000, free from 1367 up to the stack
```
Which is what makes the monitor's real trick possible — a program that no assembler ever saw:
```
> s 8000 26 48 D1 00 26 49 D1 00 26 0A D1 00 18 12
> d 8000
8000 26 48 INIA 48
8002 D1 00 OUTA 00
8004 26 49 INIA 49
...
800C 18 12 SWI 12
> g 8000
HI
```
Typed in as bytes, checked by disassembling it back, and run. It ends with `SWI osExit`, which is how it gives the machine to the shell rather than to nothing.
There are no breakpoints yet, and `g` does not come back. The machinery for both already exists and nothing has used it: SplitBit has 192 undecodable bytes, and an invalid opcode dispatches through the `BadOpcode` vector carrying **the address of the offending byte**. A breakpoint is a spare byte written over an instruction and a handler waiting for it.
### The Editor:
`Edit` is the first program on this machine that makes a file a person typed — every byte on every disk before it was put there by the host tool. It is line oriented in the manner of `ed`: `l` lists, `a` adds at the end, `i` and `c` and `d` take a line number, `w` writes and `q` stops.
It includes nothing but `services.asm` and `text.asm`: the filesystem and the console are the system's, asked for rather than carried. That is what took it from 4,941 bytes to 1,983 without a line of its own logic changing — and the way that was checked is worth knowing, because the recorded output of the `cosmosEdit` test did not move by a single byte across the rewrite.
It keeps the document as a **linked list of lines** rather than one buffer with newlines in it. Each line says where the next one is, how long it is, and then its bytes. Inserting is two pointers changed and nothing moved; with a flat buffer it would mean shifting every byte after the edit, on a machine whose only block move is a device asked politely. The price is that deleted lines are not reused, so a heavy session uses more room than the document needs and writing it out is what tidies up.
Saving goes through `sbfsSaveFile`, so a document that has grown is written somewhere else and the original is only let go of once the new one is safely down. That is the whole reason the editor was written: not because the machine needed an editor, but because every tool that produces a file needs the same four operations, and building them for one imaginary tool is how they end up wrong.
+86 -5
View File
@@ -184,17 +184,98 @@ else:
# services.asm is the one place the numbers are written, and both the system and every
# program include it. A service added there and not here is one nothing can find out about
# except by reading the source of the operating system.
# DECLARING A SERVICE AND IMPLEMENTING ONE ARE DIFFERENT THINGS, and the manual should
# describe the second. services.asm names them and fixes their numbers, which is what lets a
# number be pinned before anything answers to it; cosmos.asm is where a name gets a handler.
# A row for a service nothing implements would be describing a call that faults, and a
# missing row for one that works is a service nobody can find out about.
services = read("Programs/CosmOS/Source/services.asm")
offered = re.findall(r'^\s{2}(os[A-Za-z]+)\s+0d\d+', services, re.M)
if not offered:
named = set(re.findall(r'^\s{2}(os[A-Za-z]+)\s+0d\d+', services, re.M))
system = read("Programs/CosmOS/Source/cosmos.asm")
vectors = system.split("#Vectors")[-1] if "#Vectors" in system else ""
implemented = {name for name in re.findall(r'^\s{2}(os[A-Za-z]+)\s+[a-zA-Z]', vectors, re.M)
if name in named}
if not named:
problems.append("no services could be found in services.asm")
elif "## What A Program May Ask The System For:" not in pm:
problems.append("the Programming Manual has lost its services section")
else:
section = pm.split("## What A Program May Ask The System For:")[1].split("\n## ")[0]
for name in offered:
if ("| %s |" % name) not in section:
problems.append("%s is a service and has no row in the services table" % name)
documented = set(re.findall(r'^\| (os[A-Za-z]+) \|', section, re.M))
for name in sorted(implemented - documented):
problems.append("%s is a service the system implements and has no row in the"
" services table" % name)
for name in sorted(documented - implemented):
problems.append("the services table describes %s, which nothing implements: calling"
" it would dispatch through an empty vector and fault" % name)
# ---- Every program the manual describes is really there ----
#
# The table names what the shell can load. A program renamed or removed leaves a row
# describing something nobody can run, which is the same kind of quiet wrongness as a
# routine that no longer exists. The other direction is deliberately not checked: the ported
# programs are covered in the prose rather than given a row each.
import os
if "## Programs That Come With The System:" not in pm:
problems.append("the Programming Manual has lost its list of programs")
else:
listed = pm.split("## Programs That Come With The System:")[1].split("\n### ")[0]
# After the separator, so the table's own heading row is not mistaken for a program.
listed = listed.split("| --- |")[-1]
for name in re.findall(r'^\| ([A-Z][A-Za-z0-9-]*) \|', listed, re.M):
if not os.path.exists("Programs/CosmOS/Apps/%s.asm" % name):
problems.append("the manual describes a program called %s, and there is no"
" Programs/CosmOS/Apps/%s.asm" % (name, name))
# ---- The monitor's instruction table is the assembler's ----
#
# The monitor disassembles, so it needs the same 64 instructions with the same names and the
# same lengths. A disassembler that disagreed about a length would not print one line wrong,
# it would lose its place and print everything after it wrong, which is the worst way for a
# tool like that to fail: confidently. So the table is generated from assembly.c by
# Tests/instructiontable.py, and what is in the monitor is checked against it here.
import subprocess
generated = subprocess.run([sys.executable, "Tests/instructiontable.py"],
capture_output=True, text=True)
if generated.returncode != 0:
problems.append("the instruction table generator would not run")
else:
wanted = [line.rstrip() for line in generated.stdout.splitlines() if line.strip()]
monitor = read("Programs/CosmOS/Source/cosmos.asm")
if "\nInstructions:\n" not in monitor:
problems.append("the system has lost its instruction table")
else:
block = monitor.split("\nInstructions:\n")[1]
have = []
for line in block.splitlines():
if not line.strip() or not line.startswith(" 0x"):
break
have.append(line.rstrip())
if have != wanted:
problems.append("the system's instruction table is not what the assembler's"
" instruction set generates: %d entries against %d, first"
" difference at %s"
% (len(have), len(wanted),
next((a or b for a, b in zip(have + [None] * len(wanted),
wanted + [None] * len(have))
if a != b), "the end")))
# ---- And the lengths that table implies are the ones the manual prints ----
#
# The generator works out how long each instruction is from rules written in it; the manual
# says so in a column somebody typed. They are independent accounts of the same fact, which
# is exactly the pair worth checking against each other.
sys.path.insert(0, "Tests")
import instructiontable
lengthOf = {0: 1, 1: 3, 2: 2, 3: 2, 4: 3, 5: 4, 6: 3}
printed = {}
for m in re.finditer(r'^\|\s*[0-9A-F]{2}\s*\|\s*([A-Z][A-Z0-9]*)\s*\|\s*(\d+)\s*\|', pm, re.M):
printed[m.group(1)] = int(m.group(2))
for opcode, name in instructiontable.table():
implied = lengthOf[instructiontable.shapeOf(opcode)]
if name in printed and printed[name] != implied:
problems.append("the manual says %s is %d bytes and the disassembler will read it"
" as %d" % (name, printed[name], implied))
# ---- Every directive the assembler knows is written down ----
for directive in sorted(set(re.findall(r'"(#[A-Za-z]+)"', util))):
+2 -3
View File
@@ -4,10 +4,9 @@ load <file> read a program off the disk
run [words] start what was loaded, and tell it those words
delete <file> take it off the disk
rename <file> <to> call it something else
dump sixty four bytes of memory, and again for more
dump <program|data|bank> <address>
monitor look at memory, change it, and jump into it
help this
exit stop
exit stop, or leave the monitor if you are in it
> greeting.txt 17
filler1.txt 8
filler2.txt 8
+4 -2
View File
@@ -8,10 +8,12 @@ finished
cd
the console has been handed back
finished
> > FE00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
> > x examine, d disassemble, s set, b bank, g go, exit leaves
* bank 00
* FE00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
FE10 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
FE20 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
FE30 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
> halted
* > halted
Execution halted.
[exit 0]
+31
View File
@@ -0,0 +1,31 @@
CosmOS
> x examine, d disassemble, s set, b bank, g go, exit leaves
* b <program|data|number>
* there is no such bank
* loaded, starting at 2000
* bank 00
* 2000 47 00 10 00 SETD.0 1000
2004 18 10 SWI 10
2006 47 00 10 44 SETD.0 1044
200A 18 10 SWI 10
200C 47 00 10 7A SETD.0 107A
2010 27 1F INIB 1F
2012 18 11 SWI 11
2014 47 00 10 5D SETD.0 105D
* 2000 47 00 10 00 18 10 47 00 10 44 18 10 47 00 10 7A G.....G..D..G..z
2010 27 1F 18 11 47 00 10 5D 18 10 47 00 10 7A 18 10 '...G..]..G..z..
2020 47 00 10 65 18 10 18 12 00 00 00 00 00 00 00 00 G..e............
2030 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
* bank 01
* 1000 61 20 70 72 6F 67 72 61 6D 2C 20 6C 6F 61 64 65 a program, loade
1010 64 20 6F 66 66 20 61 20 64 69 73 6B 2C 20 72 75 d off a disk, ru
1020 6E 6E 69 6E 67 20 6F 6E 20 74 68 65 20 73 79 73 nning on the sys
1030 74 65 6D 20 74 68 61 74 20 6C 6F 61 64 65 64 20 tem that loaded
* bank 02
* 0000 01 FF 00 00 00 00 00 00 01 FF 00 00 00 00 00 00 ................
0010 03 FF 08 00 00 00 00 00 01 20 01 00 00 00 00 00 ......... ......
0020 00 FF 00 00 00 00 00 00 00 FF 00 00 00 00 00 00 ................
0030 00 FF 00 00 00 00 00 00 00 FF 00 00 00 00 00 00 ................
* Fault: The device on port 233 refused the access at Program Address 0x1359, and nothing is installed to deal with it.
Execution halted.
[exit 1]
+12
View File
@@ -0,0 +1,12 @@
CosmOS
> loaded, starting at 2000
> saved it
read it back, 22 bytes:
a file kept by asking
renamed it
deleted it
and it is gone
finished
> halted
Execution halted.
[exit 0]
+3
View File
@@ -0,0 +1,3 @@
load Files.sbx
run
exit
+4 -1
View File
@@ -3,5 +3,8 @@ run
abq
run
cdq
dump program fe00
monitor
b program
x fe00
exit
exit
+18
View File
@@ -0,0 +1,18 @@
monitor
b nonsense
b 9
load greet.sbx
b program
d 2000
x 2000
b data
x 1000
b 2
x 0
s 8000 26 48 D1 00 26 0A D1 00 18 12
d 8000
g 8000
d 8000
exit
dir
exit
+60
View File
@@ -0,0 +1,60 @@
#!/usr/bin/env python3
"""The instruction table, as the assembler has it.
The monitor needs the same 64 instructions the assembler does, with the same names and the
same lengths, and a disassembler that disagreed with the assembler about how long an
instruction is would not merely print one thing wrong - it would lose its place and print
everything after it wrong too. So the table is generated from assembly.c rather than typed
out again, and Tests/docs.sh checks the generated form against what is in the monitor.
Shapes are what follows the opcode:
0 nothing 1 an address 2 one byte 3 a Data Pointer selector
4 selector, byte 5 selector, address 6 two selectors
"""
import re
import sys
ADDRESS = {0x10, 0x11, 0x12, 0x13, 0x14, 0x17, 0x1A, 0x1B, 0x1C, 0x1D}
ONE_BYTE = {0x18, 0x26, 0x27}
TWO_SELECTORS = {0x4A, 0x4B}
SELECTOR = {0x15, 0x33, 0x36, 0x40, 0x41, 0x42, 0x43, 0x44,
0x45, 0x46, 0x47, 0x48, 0x49, 0x4C, 0x4D}
def shapeOf(opcode):
if opcode in TWO_SELECTORS:
return 6
if opcode == 0x47: # SETD, a selector and then an address
return 5
if opcode in (0x48, 0x49): # DPUP and DPDN, a selector and then a byte
return 4
if opcode in SELECTOR:
return 3
if opcode in ADDRESS:
return 1
if opcode in ONE_BYTE or (opcode & 0xF0) in (0xD0, 0xE0):
return 2
return 0
def table(path="Source/Assembler/assembly.c"):
source = open(path).read()
found = re.findall(r'\{0x([0-9A-Fa-f]{2}),\s*"([A-Z0-9]+)"\}', source)
return [(int(code, 16), name) for code, name in found]
def asAssembly(entries):
lines = []
for opcode, name in entries:
padded = (name + " ")[:4]
lines.append(' 0x%02X 0d%d "%s"' % (opcode, shapeOf(opcode), padded))
return lines
if __name__ == "__main__":
entries = table()
if len(sys.argv) > 1 and sys.argv[1] == "--count":
print(len(entries))
else:
for line in asAssembly(entries):
print(line)
+8
View File
@@ -125,3 +125,11 @@ printf 'the second one' > two.txt
"$ROOT/Assembler" -I "$ROOT/Programs/Libraries" -I "$ROOT/Programs/CosmOS/Source" \
"$ROOT/Programs/CosmOS/Apps/Edit.asm" -o "$WORK/Edit.sbx" >/dev/null
"$TOOL" put "$DISKS/editor.img" "$WORK/Edit.sbx" >/dev/null
# A disk for the file services, holding nothing but the program that exercises them. Its
# own, because that program writes: it tidies up after itself, but a run that stopped part
# way would leave a document behind on a fixture every later test reads.
"$TOOL" format "$DISKS/services.img" 256 2 >/dev/null
"$ROOT/Assembler" -I "$ROOT/Programs/CosmOS/Source" \
"$ROOT/Programs/CosmOS/Apps/Files.asm" -o "$WORK/Files.sbx" >/dev/null
"$TOOL" put "$DISKS/services.img" "$WORK/Files.sbx" >/dev/null
+18
View File
@@ -211,6 +211,17 @@ cosmosNoDisk | CosmOS/Source/cosmos.asm | run | cosmosNoD
# load can refuse is tried first, and run is asked for twice, so the Stack being reclaimed
# rather than merely abandoned is what makes the second one work.
cosmosRun | CosmOS/Source/cosmos.asm | run | cosmosRun.in | - | disks/cosmos.img
# The monitor, which is part of the shell rather than a program: a mode you go into and stay
# in. The targets are chosen to be stable - a loaded program's code and data, and the bank
# table - rather than the system's own code, which would churn whenever any library changed.
#
# Looking at the bank table is worth having on its own. It is the machine describing itself,
# and it shows the disk buffer that sbfsMount registered as bank 3 at boot.
#
# The last part is what the mode is FOR: a program typed in as bytes, run with g, and the
# prompt that comes back is the monitor's own. A program giving the machine back lands where
# it was started from, so looking at something and running it do not interrupt each other.
cosmosMonitor | CosmOS/Source/cosmos.asm | run | cosmosMonitor.in | - | disks/cosmos.img
# The original hello.asm, brought over as an application. It is not much of a program,
# but it is the one that talks to the hardware directly: it writes to port 0x00 instead
# of calling osPrintString, so it is the case where a program reaches past the system and
@@ -266,6 +277,12 @@ cosmosSay | CosmOS/Source/cosmos.asm | run | cosmosSay
# split a file into lines, edit them, build a file back out of them, and save it over
# something that was already there and is now a different size.
cosmosEdit | CosmOS/Source/cosmos.asm | run | cosmosEdit.in | - | disks/editor.img
# The file services, exercised by a program that includes NOTHING but the service names: no
# filesystem library, no console library. It writes a file, reads it back, says how long it
# was, renames it and deletes it, in 645 bytes - against the editor's 4941, which does less
# with files and carries the filesystem inside it. That difference is the whole case for the
# service layer, and this is where it is checked rather than argued.
cosmosServices | CosmOS/Source/cosmos.asm | run | cosmosFiles2.in | - | disks/services.img
# The programs CosmOS loads, checked on their own so that a failure here reads as "the app
# does not assemble" rather than as a broken disk image.
app-greet | CosmOS/Apps/greet.asm | assemble | - | -
@@ -275,6 +292,7 @@ app-Snake | CosmOS/Apps/Snake.asm | assemble | -
app-Keys | CosmOS/Apps/Keys.asm | assemble | - | -
app-Say | CosmOS/Apps/Say.asm | assemble | - | -
app-Edit | CosmOS/Apps/Edit.asm | assemble | - | -
app-Files | CosmOS/Apps/Files.asm | assemble | - | -
# ---- Programs driven by console input ----
inputTest | inputTest.asm | run | inputTest.in | -