bd38d80f9c
and renamed it "Core words". Also, reworded the presentation. |
||
---|---|---|
.. | ||
.gitignore | ||
collapseos-on-trs80.jpg | ||
Makefile | ||
README.md | ||
xcomp.fs |
TRS-80 Model 4p
The TRS-80 (models 1, 3 and 4) are among the most popular z80 machines. They're very nicely designed and I got my hands on a 4p with two floppy disk drives and a RS-232 port. In this recipe, we're going to get Collapse OS running on it.
Not entirely standalone
Collapse OS uses the TRS-80 drivers rather than its own. On most TRS-80 models, those drivers are on ROM, but in the case of the 4P model, those drivers are on the TRSDOS disk (well, from what I understand, not all of it, but still, a big part of it).
It would be preferable to develop drivers from scratch, but it represents a significant effort for a modest payout (because it's only actually useful when you want to use a 4P model that has no TRSDOS disk).
Maybe those drivers will be developed later, but it's not a priority for now.
Floppy or RS-232?
There are many ways to get Collapse OS to run on it. One would involve writing it to a floppy. I bought myself old floppy drives for that purpose, but I happen to not have any functional computer with a floppy port on it. I still have the motherboard of my old pentium, but I don't seem to have a video card for it any more.
Because my 4p has a RS-232 port and because I have equipment to do serial communication from modern machines (I didn't have a DB-9 to DB-25 adapter though, I had to buy one), I chose that route.
Gathering parts
- A TRS-80 model 4p with a RS-232 port
- A TRSDOS 6.x disk
- A means to do serial communication. In my case, that meant:
- A USB-to-serial device
- A null modem cable
- A DB-9 gender changer
- A DB-9 to DB-25 adapter
Overview
We need to send sizeable binary programs through the RS-232 port and then run it. The big challenge here is ensuring data integrity. Sure, serial communication has parity check, but it has no helpful way of dealing with parity errors. When parity check is enabled and that a parity error occurs, the byte is simply dropped on the receiving side. Also, a double bit error could be missed by those checks.
What we'll do here is to ping back every received byte back and have the sender do the comparison and report mismatched data.
Another problem is ASCII control characters. When those are sent across serial
communication channels, all hell breaks lose. When sending binary data, those
characters have to be avoided. We use tools/ttysafe
for that.
Does TRSDOS have a way to receive this binary inside these constraints? Not to my knowledge. As far as I know, the COMM program doesn't allow this.
What are we going to do? We're going to punch in a binary program to handle that kind of reception! You're gonna feel real badass about it too...
Keyboard tips
_
isCLEAR+ENTER
.
Building the binary
You can build the binary to send to the TRS-80 with make
, which will yield
os.bin
. You'll need it later.
Testing serial communication
The first step here is ensuring that you have bi-directional serial communication. To do this, first prepare your TRS-80:
set *cl to com
setcomm (word=8,parity=no)
The first line loads the communication driver from the COM/DRV
file on the
TRSDOS disk and binds it to *cl
, the name generally used for serial
communication devices. The second line sets communication parameters in line
with what is generally the default on modern machine. Note that I left the
default of 300 bauds as-is.
Then, you can run COMM *cl
to start a serial communication console.
Then, on the modern side, use your favorite serial communication program and set
the tty to 300 baud with option "raw". Make sure you have -parenb
.
If your line is good, then what you type on either side should echo on the other side. If it does not, something's wrong. Debug.
Punching in the goodie
As stated in the overview, we need a program on the TRS-80 that:
- Listens to
*cl
- Echoes each character back to
*cl
- Adjusts
ttysafe
escapes - Stores received bytes in memory
You're in luck: that program has already been written. It's in B502 and B503. You can compile it with:
212 LOAD ( z80 assembler )
0x0238 CONSTANT COM_DRV_ADDR
0x3000 CONSTANT DEST_ADDR
502 LOAD
503 LOAD
Then, you can use DUMP
to visualize the data you'll need to punch in:
H@ ORG @ - ORG @ DUMP
It can run from any offset (all jumps in it are relative), but writes to
DEST_ADDR
. Make sure you don't place it in a way to be overwritten by its
received data.
Wondering what is that COM_DRV_ADDR
constant? That's the DCB handle of your
*cl
device. You will need to get that address before you continue. Go read
the following section and come back here.
How will you punch that in? The debug
program! This very useful piece of
software is supplied in TRSDOS. To invoke it, first run debug (on)
and then
press the BREAK
key. You'll get the debug interface which allows you to punch
in any data in any memory address. Let's use 0x5000
which is the offset it's
designed for.
For reference: to go back to the TRSDOS prompt, it's o<return>
.
First, display the 0x5000-0x503f
range with the d5000<space>
command (I
always press Enter by mistake, but it's space you need to press). Then, you can
begin punching in with h5000<space>
. This will bring up a visual indicator of
the address being edited. Punch in the stuff with a space in between each byte
and end the edit session with x
.
Getting your DCB address
In the previous step, you need to set COM_DRV_ADDR
to your "DCB" address for
*cl
. That address is your driver "handle". To get it, first get the address
where the driver is loaded in memory. You can get this by running device (b=y)
. That address you see next to *cl
? that's it. But that's not our DCB.
To get your DBC, go explore that memory area. Right after the part where there's
the *cl
string, there's the DCB address (little endian). On my setup, the
driver was loaded in 0x0ff4
and the DCB address was 8 bytes after that, with
a value of 0x0238
. Don't forget that z80 is little endian. 38
will come
before 02
.
Saving that program for later
If you want to save yourself typing for later sessions, why not save the
program you've painfully typed to disk? TRSDOS enables that easily. Let's say
that you typed your program at 0x5000
and that you want to save it to
RECV/CMD
on your second floppy drive, you'd do:
dump recv/cmd:1 (start=x'5000',end=x'5030',tra='5000')
A memory range dumped this way will be re-loaded at the same offset through
load recv/cmd:1
. Even better, TRA
indicates when to jump after load when
using the RUN
command. Therefore, you can avoid all this work above in later
sessions by simply typing recv
in the DOS prompt.
Note that you might want to turn debug
off for these commands to run. I'm not
sure why, but when the debugger is on, launching the command triggers the
debugger.
Sending binary through the RS-232 port
Once you're finished punching your program in memory, you can run it with
g5000<enter>
(not space). If you've saved it to disk, run recv
instead.
Because it's an infinite loop, your screen will freeze. You can start sending
your data.
To that end, there's the tools/pingpong
program. It takes a device and a
filename to send. Before you send the binary, make it go through
tools/ttysafe
first (which just takes input from stdin and spits tty-safe
content to stdout):
./ttysafe < stage1.bin > stage1.ttysafe
On OpenBSD, the invocation can look like:
doas ./pingpong /dev/ttyU0 os.ttysafe
You will be prompted for a key before the contents is sent. This is because on
OpenBSD, TTY configuration is lost as soon as the TTY is closed, which means
that you can't just run stty
before running pingpong
. So, what you'll do is,
before you press your key, run doas stty -f /dev/ttyU0 300 raw
and then press
any key on the pingpong
invocation.
If everything goes well, the program will send your contents, verifying every
byte echoed back, and then send a null char to indicate to the receiving end
that it's finished sending. This will end the infinite loop on the TRS-80 side
and return. That should bring you back to a refreshed debug display and you
should see your sent content in memory, at the specified address (0x3000
if
you didn't change it).
If there was no error during pingpong
, the content should be exact.
Nevertheless, I recommend that you manually validate a few bytes using TRSDOS
debugger before carrying on.
debugging tip: Sometimes, the communication channel can be a bit stubborn and
always fail, as if some leftover data was consistently blocking the channel. It
would cause a data mismatch at the very beginning of the process, all the time.
What I do in these cases is start a COMM *cl
session on one side and a screen
session on the other, type a few characters, and try pingpong
again.
Saving to disk
If everything went well, you could run Collapse OS with g3000<return>
. You
would get a usable Collapse OS prompt. But don't do that just yet. That
executable bootstraps itself from code and it takes a while to do that every
time you launch it. You don't want that right? Let's save a compiled version of
it to disk.
Turn off the debugger (which can mess up some things) and save your sent
content as-is by following the instructions you had for the RECV
program.
This way, if you mess up a step below, you can quickly start over. Now you can
launch Collapse OS. Then, we need to:
- Reclaim wasted memory
- Create hook entry
- Update LATEST
- Write down initialization code
- Write memory to floppy
Reclaim wasted memory
During initialization, RDLN$
allocated memory in HERE
for its input buffer.
If you don't reclaim that space, that will be dead space in your binary.
You can reclaim that space with FORGET _
which will rewind to before we
defined our initialization routine (see xcomp.fs).
However, that is not enough. If you only do that, there will be a conflict
between the rdln input buffer and your HERE
space! So we need to go put that
input buffer somewhere else first. Therefore, your commands will be:
500 ALLOT RDLN$ FORGET _
Create hook entry
That one is easy:
(entry) _
Update LATEST
At this point, both HERE
and CURRENT
point to your future LATEST
.
H@ 0x08 BIN+ !
Done.
Write down initialization code
You'll do something similar to what we do in xcomp, except you'll have to add
"HERE rewinding" code because by default, HERE
starts at RAMSTART+0x80. So:
," CURRENT @ HERE ! (ok) RDLN$ "
As soon as RDLN$
is called, the C<
pointer changes and gives control to
keyboard, giving us our full forth interpreter.
Write memory to floppy
What you currently have in memory is gold. You want that on floppy. First, run
H@ .X
to know your upper bound (and 0 BIN+ .X
to know your lower one, but
you're already supposed to know that one). Then, run BYE
to return to TRSDOS
(the TRSDOS driver overrides BYE
so that it calls the proper SVC instead of
just halting). Then, you can dump memory to floppy as you already did for
RECV
.
Sending blkfs to floppy
As it is, your system fully supports reading and writing to floppy drive 1. It
also had *CL<
to read a char from *cl
and *CL>
to emit a char to *cl
.
That's all you need to have a full Collapse OS with access to disk blocks.
First, make sure your floppies are formatted. Collapse OS is currently hardcoded to single side and single density, which means there's a limit of 100 blocks per disk.
You'll need to send those blocks through RS-232. Begin by taking over the prompt:
' *CL> 0x53 RAM+ !
' *CL< 0x55 RAM+ !
See B80 for details about those RAM offsets. Your serial link now has the
prompt. Now, you can use /tools/blkup
to send a disk's contents. First,
extract the first 100 blocks from blkfs:
dd if=emul/blkfs bs=1024 count=100 > d1
Now, insert your formatted disk in drive 1 and push your blocks:
tools/blkup /dev/ttyUSB0 0 d1
It takes a while, but you will end up having your first 100 blocks on floppy!
Go ahead, LIST
around. Then, repeat for other disks.
Floppy organisation
Making blkfs span multiple disk is a bit problematic with regards to absolute block references in the code. You'll need to work a bit to design your very own Collapse OS floppy set. See Usage guide (B3) for details.
Self-hosting
As it is, your installment of Collapse OS is self-hosting using instructions
very similar to recipes/rc2014/selhost
. The difference is that instead of
writing the binary you have in memory to EEPROM, you'll quit to TRSDOS with
BYE
and use TRSDOS' DUMP
utility to save to disk like you already did
before.