The machine starts itself

stage two
  CosmOS
  > saved it
  read it back, 22 bytes:

Stage one hands over to stage two out of a boot slot; stage two mounts the
filesystem, finds /System/cosmos.bin, takes the image apart and places its
code, its data and its vector table, and jumps to the entry point the
vectors named. Nothing placed memory for it.

What it loads is an ORDINARY BOOT IMAGE, the same SPBT file the emulator
has always been handed. That was the user's call and it is the whole trick:
a second stage that loads the machine's normal image format is not a
boot-specific mechanism, so bare metal SplitBit stops being a special case.
A program wanting no operating system under it is just an image, written
under CosmOS like any other, and startable because it is a file.

Three things in it worth knowing:

- THE ENTRY POINT IS CAUGHT ON ITS WAY PAST. Program Memory cannot be read
  back, so the boot vector cannot be looked up after being installed; the
  vector loop notices the one addressed at 0xFC00 and keeps it.
- A missing "VEC" is not a fault. An image written before vectors existed
  simply ends after its data, and then the entry point is zero, which is
  what every such image has always relied on.
- Feature flags that are set mean an image asking for a machine this may
  not be, and the honest answer to a request that cannot be understood is
  to refuse rather than to run it anyway.

The test records that the system WORKS afterwards rather than that it
started. A loaded program running is what says the vector table arrived,
because a program reaches the system through SWI and nothing else; the file
written and the directory entered say the filesystem and the console came
up with it. A second disk has a boot slot and nothing to start, and says so
rather than jumping somewhere.
This commit is contained in:
Anachronaut
2026-08-27 14:13:23 -04:00
parent 82adeeb193
commit c312853f8e
8 changed files with 541 additions and 0 deletions
+459
View File
@@ -0,0 +1,459 @@
; stage2.asm
; The second stage. Finds the system on the disk and starts it.
;
; Stage one knows nothing about filesystems and never will, because it is going into a ROM
; and a ROM that knew SBFS would freeze the format in silicon. This is where that knowledge
; lives instead: on the disk, in a boot slot, replaceable by writing blocks.
;
; ---- What it loads ----
;
; An ordinary SplitBit boot image, the same SPBT file the emulator has always been handed.
; That is the whole trick and it was the user's: a second stage that loads the machine's
; NORMAL image format is not a boot-specific mechanism at all. Bare metal SplitBit stops
; being a special case - a program that wants no operating system is just an image, written
; under CosmOS like any other, and startable because it is a file.
;
; ---- How it gets its own data ----
;
; Stage one places Program Memory and nothing else. A loadable image is written into the
; slot as code followed by data, so this program's Data Segment is sitting in Program Memory
; just past its own code when it starts, and the first thing it does is blit it down. See
; slotData.asm, which proves the mechanism on its own.
;
; Written by Anachronaut
#Program
; Above everything the system will occupy, so that loading the system does not walk over
; the loader while it is still running.
#Base 0xC000
start:
; ---- The Data Segment, fetched from just past the code ----
;
; A ROUND FOUR KILOBYTES rather than the exact size. The blit needs a length, the
; assembler will not work out the difference between two labels, and a hand kept number
; is a number that goes wrong quietly - slotData.asm padded to 257 and copied 256, and
; the byte that never arrived happened to be padding. Copying more than there is costs
; nothing: the source runs into slot padding, and the destination is nobody's memory.
RSTA
OUTA 0xE0 ; SourceBank: Program Memory, where stage one put everything.
SETD.0 codeEnd
PSHD.0
POPA
POPB
OUTB 0xE1
OUTA 0xE2
INIA 0d1
OUTA 0xE3 ; DestBank: Data Memory.
SETD.0 StageDataBase
PSHD.0
POPA
POPB
OUTB 0xE4
OUTA 0xE5
INIA 0x10
OUTA 0xE6
RSTA
OUTA 0xE7 ; Four kilobytes.
INIA 0x01
OUTA 0xE8
; ---- Now there is data, so there can be words ----
SETD.0 StageName
RCAL say
CALL sbfsMount
BNQ noFilesystem
SETD.0 SystemName
CALL sbfsFind
BNQ noSystem
; ---- The image, read whole into somewhere nothing else is using ----
SETD.2 StageAt
LDD.1.2
CALL sbfsRead
BNQ unreadable
; "SPBT", or this is a file rather than something to start.
SETD.2 StageAt
LDD.0.2
SETD.2 BootMagic
INIA 0d4
RCAL matchTag
BNQ notAnImage
; The format version, and then four bytes of feature flags. A flag set is an image
; asking for something this machine may not have, and the honest answer to a request
; that cannot be understood is to refuse rather than to run it anyway.
LDA.0
INIB 0d1
XOR
BNQ wrongVersion
INCD.0
INIA 0d4
RCAL allZero
BNQ wantsMore
; "PRG", its length, and then the code itself straight into Program Memory.
SETD.2 ProgTag
INIA 0d3
RCAL matchTag
BNQ notAnImage
RCAL takeCount
RSTA
RCAL placeSegment
; "DAT", the same again into Data Memory.
SETD.2 DataTag
INIA 0d3
RCAL matchTag
BNQ notAnImage
RCAL takeCount
INIA 0d1
RCAL placeSegment
; ---- "VEC", which an image written before vectors existed simply does not have ----
;
; So running out of file here is the ordinary case rather than a fault: what follows is
; the end of a perfectly good image, and the entry point is then the address zero.
SETD.2 VecTag
INIA 0d3
RCAL matchTag
BNQ noVectors
RCAL takeCount
vectorLoop:
SETD.2 CountHigh
LDA.2
INCD.2
LDB.2
OR
BRQ vectorsDone
; Four bytes: where in Program Memory the vector sits, then what to put there. The
; controller writes it, because nothing else can write Program Memory.
LDA.0
SETD.2 VecWhere
STA.2
INCD.0
LDA.0
INCD.2
STA.2
INCD.0
RSTA
OUTA 0xE3 ; DestBank: Program Memory.
SETD.2 VecWhere
LDA.2
OUTA 0xE4
INCD.2
LDA.2
OUTA 0xE5
LDA.0
OUTA 0xE9 ; A byte straight in, rather than a blit of one.
INCD.0
LDA.0
OUTA 0xE9
INCD.0
; THE BOOT VECTOR IS THE ONE WORTH KEEPING. Program Memory cannot be read back, so the
; address to start at has to be noticed on its way past rather than looked up after.
SETD.2 VecWhere
LDA.2
INIB 0xFC
XOR
BNQ vectorStepped
INCD.2
LDA.2
BNA vectorStepped
SETD.2 EntryHigh
DECD.0
DECD.0
LDA.0
STA.2
INCD.0
LDA.0
INCD.2
STA.2
INCD.0
vectorStepped:
SETD.2 CountLow
LDA.2
INIB 0d4
CCF
SUB
MVQA
STA.2
BNC vectorLoop
SETD.2 CountHigh
LDA.2
DECA
STA.2
BRI vectorLoop
noVectors:
vectorsDone:
; And into it. Everything above has been arranging memory; this is the only instruction
; that hands the machine over.
SETD.0 EntryHigh
LDD.1.0
BRD.1
unreadable:
SETD.0 UnreadableText
RCAL say
HALT
notAnImage:
SETD.0 NotImageText
RCAL say
HALT
wrongVersion:
SETD.0 VersionText
RCAL say
HALT
wantsMore:
SETD.0 FeatureText
RCAL say
HALT
noFilesystem:
SETD.0 NoDiskText
RCAL say
HALT
noSystem:
SETD.0 NoSystemText
RCAL say
SETD.0 SystemName
RCAL say
RCAL newLine
HALT
; ---- Comparing a marker and stepping past it ----
;
; DP0 is in the image, DP2 names what it should say, A is how many bytes. DP0 is left past
; them either way, which is what lets a caller try one marker and carry on.
matchTag:
SETD.1 TagLeft
STA.1
tagLoop:
SETD.1 TagLeft
LDA.1
BRA tagSame
DECA
STA.1
LDA.0
LDB.2
XOR
BNQ tagDiffers
INCD.0
INCD.2
BRI tagLoop
tagSame:
RSTA
RSTB
CCF
ADD
RRET
tagDiffers:
; Past the rest of it regardless, so that what DP0 points at does not depend on which
; byte happened to differ.
SETD.1 TagLeft
LDA.1
BRA tagDiffersDone
DECA
STA.1
INCD.0
BRI tagDiffers
tagDiffersDone:
RSTA
INIB 0d1
CCF
ADD
RRET
; A bytes at DP0, all of which have to be zero. Used for the feature flags, where anything
; set is the image asking for something this machine may not be able to give it.
allZero:
SETD.1 TagLeft
STA.1
zeroLoop:
SETD.1 TagLeft
LDA.1
BRA zeroAllClear
DECA
STA.1
LDA.0
INCD.0
BNA zeroNotClear
BRI zeroLoop
zeroAllClear:
RSTA
RSTB
CCF
ADD
RRET
zeroNotClear:
RSTA
INIB 0d1
CCF
ADD
RRET
; Two bytes at DP0, most significant first, into the count that placeSegment and the vector
; loop both work through. DP0 is left on the byte after them.
takeCount:
LDA.0
SETD.1 CountHigh
STA.1
INCD.0
LDA.0
SETD.1 CountLow
STA.1
INCD.0
RRET
; ---- A segment, out of the staged image and into the memory it belongs in ----
;
; A is the bank: 0 for Program Memory, 1 for Data. Both segments go to address zero, which
; is what a boot image means - it is the format for something that owns the machine.
;
; DP0 is left past the segment, ready for whatever marker comes next.
placeSegment:
SETD.1 WantBank
STA.1
INIA 0d1
OUTA 0xE0 ; SourceBank: Data Memory, where the image was staged.
PSHD.0
POPA
POPB
OUTB 0xE1
OUTA 0xE2
SETD.1 WantBank
LDA.1
OUTA 0xE3
RSTA
OUTA 0xE4
OUTA 0xE5 ; To the bottom of it.
SETD.1 CountHigh
LDA.1
OUTA 0xE6
INCD.1
LDA.1
OUTA 0xE7
INIA 0x01
OUTA 0xE8
; And past it. DPUW steps a pointer by A and B together, which is exactly the shape the
; two halves of a length are already in.
SETD.1 CountHigh
LDA.1
INCD.1
LDB.1
DPUW.0
RRET
; ---- Saying something ----
;
; Its own rather than console.asm's, because everything this includes has to fit in a boot
; slot and the console library brings a great deal this will never use. DP0 names a string.
say:
LDA.0
BRA sayDone
OUTA 0x00
INCD.0
BRI say
sayDone:
RRET
newLine:
INIA 0x0A
OUTA 0x00
RRET
#Data
; Out of the way of everything: the system's data goes to 0x0000, and whatever image is
; being loaded is staged below this.
#Base 0xE000
; The first thing in the segment, so that its address is where the blit above puts the
; whole of it.
StageDataBase:
StageName:
"stage two
"
FoundText:
"found "
NoDiskText:
"no filesystem
"
NoSystemText:
"no "
; What to start. A name for now; the plan is for this to be read out of a file so that any
; number of systems can sit on one disk and the choice is an ordinary safe write.
SystemName:
"/System/cosmos.bin"
UnreadableText:
"could not read it
"
NotImageText:
"not a boot image
"
VersionText:
"an image this cannot start
"
FeatureText:
"the image wants more machine
"
BootMagic:
"SPBT"
ProgTag:
"PRG"
DataTag:
"DAT"
VecTag:
"VEC"
TagLeft:
0x00
WantBank:
0x00
CountHigh:
0x00
CountLow:
0x00
VecWhere:
0x00 0x00
; Where to start once memory is arranged. Zero unless the image says otherwise, which is
; what a boot image with no vector table means and what every program written before the
; table existed relies on.
EntryHigh:
0x00
EntryLow:
0x00
; Where the image is put while it is being taken apart. Below this program's own data and
; above everything the system will occupy. An address rather than storage: reserving it
; would put its zeroes in the boot slot.
StageAt:
0x20 0x00
#Include sbfs.asm
; ---- The end of everything ----
;
; Back to the Program Segment so that this label lands past the included code as well as
; past the code above. It is where the Data Segment's image begins once stage one has put
; the whole slot into Program Memory.
#Program
codeEnd:
+17
View File
@@ -283,6 +283,23 @@ has versions - all of that lives in the boot area, on the disk, where it can be
It reads the live slot into Program Memory, jumps to the first byte, and prints one
character and stops if there is nothing to start.
`Programs/Boot/stage2.asm` is what sits in the slot. It mounts the filesystem, finds the
system by name, and takes the image apart: code into Program Memory, data into Data
Memory, and each vector written through the controller, which is the only thing that can
write Program Memory at all. The entry point is noticed on the boot vector's way past,
because Program Memory cannot be read back to look it up afterwards.
**What it loads is an ordinary boot image** - the same SPBT file the emulator has always
been handed directly. That is the whole trick: a second stage that loads the machine's
normal image format is not a boot-specific mechanism, so **bare metal SplitBit stops being
a special case.** A program that wants no operating system under it is just an image,
written under CosmOS like any other, and startable because it is a file.
Stage two arranges its own Data Segment. Stage one places Program Memory and nothing else,
since knowing where a payload's data ended would mean knowing a format, so the image is
written into the slot as code followed by data and stage two's first act is to blit its
data down from just past its own code.
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. It is a convenience rather than a necessity: SplitBit writes its own filesystem, and now assembles its own programs, so a disk can be filled without leaving the machine.
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.
+14
View File
@@ -0,0 +1,14 @@
stage two
CosmOS
> saved it
read it back, 22 bytes:
a file kept by asking
renamed it
deleted it
and it is gone
finished
> made
> /Notes> 0 files
/Notes> halted
Execution halted.
[exit 0]
+4
View File
@@ -0,0 +1,4 @@
stage two
no /System/cosmos.bin
Execution halted.
[exit 0]
+5
View File
@@ -0,0 +1,5 @@
Files
mkdir Notes
cd Notes
dir
exit
+1
View File
@@ -1,5 +1,6 @@
Programs/Boot/slotData.asm redundant-setd 1
Programs/Boot/stage1.asm redundant-setd 2
Programs/Boot/stage2.asm redundant-setd 1
Programs/CosmOS/Apps/Copy.asm redundant-setd 2
Programs/CosmOS/Apps/Edit.asm redundant-setd 3
Programs/CosmOS/Apps/Wander.asm redundant-setd 1
+26
View File
@@ -386,3 +386,29 @@ cp "$DISKS/chain.img" "$DISKS/chainAlt.img"
"$ROOT/Assembler" "$ROOT/Programs/Boot/slotData.asm" -o "$WORK/slotData.sbx" >/dev/null
tail -c +17 "$WORK/slotData.sbx" > "$WORK/slotData.raw"
"$TOOL" boot "$DISKS/chainData.img" "$WORK/slotData.raw" 0 >/dev/null
# ---- A disk the machine can start itself from ----
#
# Stage one in the emulator's hands, stage two in a boot slot, and the system as an
# ordinary file. Nothing here is a boot-specific format: /System/cosmos.bin is the same
# SPBT image the emulator has always been handed directly, which is what makes a program
# that wants no operating system startable the same way.
"$TOOL" format "$DISKS/selfboot.img" 512 4 32 >/dev/null
"$ROOT/Assembler" -I "$ROOT/Programs/CosmOS/Source" \
"$ROOT/Programs/Boot/stage2.asm" -o "$WORK/stage2.sbx" >/dev/null
tail -c +17 "$WORK/stage2.sbx" > "$WORK/stage2.raw"
"$TOOL" boot "$DISKS/selfboot.img" "$WORK/stage2.raw" 0 >/dev/null
"$ROOT/Assembler" -I "$ROOT/Programs/Libraries" -I "$ROOT/Programs/CosmOS/Source" \
"$ROOT/Programs/CosmOS/Source/cosmos.asm" -o "$WORK/bootCosmos.bin" >/dev/null
"$TOOL" mkdir "$DISKS/selfboot.img" /System >/dev/null
"$TOOL" put "$DISKS/selfboot.img" "$WORK/bootCosmos.bin" /System/cosmos.bin >/dev/null
"$TOOL" mkdir "$DISKS/selfboot.img" /Apps >/dev/null
"$ROOT/Assembler" -I "$ROOT/Programs/CosmOS/Source" \
"$ROOT/Programs/CosmOS/Apps/Files.asm" -o "$WORK/bootFiles.sbx" >/dev/null
"$TOOL" put "$DISKS/selfboot.img" "$WORK/bootFiles.sbx" /Apps/Files.sbx >/dev/null
# And one with no system on it, so that a second stage which cannot find what to start
# says so rather than jumping somewhere.
"$TOOL" format "$DISKS/nosystem.img" 512 4 32 >/dev/null
"$TOOL" boot "$DISKS/nosystem.img" "$WORK/stage2.raw" 0 >/dev/null
+15
View File
@@ -374,6 +374,21 @@ bootNoArea | Boot/stage1.asm | run | -
# variables and the string that sbfs.asm needs.
bootData | Boot/slotData.asm | assemble | - | -
bootDataRuns | Boot/stage1.asm | run | - | - | disks/chainData.img
# ---- The machine starting itself ----
#
# The whole chain: the emulator hands over stage one, stage one reads the live boot slot
# into Program Memory and jumps, stage two mounts the filesystem, finds /System/cosmos.bin,
# takes the image apart and places its code, its data and its vector table, and jumps to
# the entry point the vectors named.
#
# WHAT IS RECORDED IS THAT THE SYSTEM WORKS AFTERWARDS, not that it started. A loaded
# program running is what says the vector table arrived, because a program reaches the
# system through SWI and nothing else; the file written and the directory entered say the
# filesystem and the console came up with it.
selfBoot | Boot/stage1.asm | run | selfBoot.in | 90000000 | disks/selfboot.img
# And a disk with a boot slot but nothing to start, which says so rather than jumping.
selfBootNoSystem | Boot/stage1.asm | run | - | 90000000 | disks/nosystem.img
# Reading a disk that has directories on it. The machine can walk a path at this point but
# cannot make a directory, so the disk is built by the host tool and read here - which is
# the two implementations checking each other rather than either checking itself.