doc: add "Understanding the code" walkthrough
This commit is contained in:
parent
c7ef8bf915
commit
9a7617115f
@ -1,10 +1,5 @@
|
|||||||
# Collapse OS documentation
|
# Collapse OS documentation
|
||||||
|
|
||||||
## Assembly guide
|
|
||||||
|
|
||||||
* [Writing the glue code](glue-code.md)
|
|
||||||
* [Running Collapse OS on an emulator](emulate.md)
|
|
||||||
|
|
||||||
## User guide
|
## User guide
|
||||||
|
|
||||||
* [The shell](../apps/basic/README.md)
|
* [The shell](../apps/basic/README.md)
|
||||||
@ -12,6 +7,8 @@
|
|||||||
* [Using block devices](blockdev.md)
|
* [Using block devices](blockdev.md)
|
||||||
* [Using the filesystem](fs.md)
|
* [Using the filesystem](fs.md)
|
||||||
* [Assembling z80 source from the shell](zasm.md)
|
* [Assembling z80 source from the shell](zasm.md)
|
||||||
|
* [Writing the glue code](glue-code.md)
|
||||||
|
* [Understanding the code](understanding-code.md)
|
||||||
|
|
||||||
## Hardware
|
## Hardware
|
||||||
|
|
||||||
|
@ -1,35 +0,0 @@
|
|||||||
# Running Collapse OS on an emulator
|
|
||||||
|
|
||||||
The quickest way to give Collapse OS a whirl is to use `tools/emul` which is
|
|
||||||
built around [libz80][libz80]. Everything is set up, you just have to run
|
|
||||||
`make`, then `shell/shell`.
|
|
||||||
|
|
||||||
To emulate something at a lower level, I recommend using Alan Cox's [RC2014
|
|
||||||
emulator][rc2014-emul]. It runs Collapse OS fine but you have to write the
|
|
||||||
glue code yourself. One caveat, also, is that it requires a ROM image bigger
|
|
||||||
than 8K, so you have to pad the binary.
|
|
||||||
|
|
||||||
A working Makefile for a project with a glue code being called `main.asm` could
|
|
||||||
look like:
|
|
||||||
|
|
||||||
TARGET = os.bin
|
|
||||||
ZASM = ~/collapseos/tools/emul/zasm/zasm
|
|
||||||
ROM = os.rom
|
|
||||||
|
|
||||||
.PHONY: all
|
|
||||||
all: $(ROM)
|
|
||||||
$(TARGET): main.asm
|
|
||||||
$(ZASM) < $< > $@
|
|
||||||
|
|
||||||
$(ROM): $(TARGET)
|
|
||||||
cp $< $@
|
|
||||||
dd if=/dev/null of=$@ bs=1 count=1 seek=8192
|
|
||||||
|
|
||||||
.PHONY: run
|
|
||||||
run: $(ROM)
|
|
||||||
~/RC2014/rc2014 -r $(ROM)
|
|
||||||
|
|
||||||
`CTRL+\` stops the emulation.
|
|
||||||
|
|
||||||
[libz80]: https://github.com/ggambetta/libz80
|
|
||||||
[rc2014-emul]: https://github.com/EtchedPixels/RC2014
|
|
144
doc/understanding-code.md
Normal file
144
doc/understanding-code.md
Normal file
@ -0,0 +1,144 @@
|
|||||||
|
# Understanding the code
|
||||||
|
|
||||||
|
One of the design goals of Collapse OS is that its code base should be easily
|
||||||
|
understandable in its entirety. Let's help with this with a little walthrough.
|
||||||
|
We use the basic `rc2014` recipe as a basis for the walkthrough.
|
||||||
|
|
||||||
|
This walkthrough assumes that you know z80 assembly. It is recommended that you
|
||||||
|
read code conventions in `CODE.md` first.
|
||||||
|
|
||||||
|
Code snippets aren't reproduced here. You have to follow along with code
|
||||||
|
listing.
|
||||||
|
|
||||||
|
## Power on
|
||||||
|
|
||||||
|
You have a RC2014 classic built with an EEPROM that has the recipe's binary on
|
||||||
|
it and you're linked to its serial I/O module. What happens when you power it
|
||||||
|
on and press the reset button (I've always had to press the reset button for
|
||||||
|
the RC2014 to power on properly. I don't know why. Must be some tricky sync
|
||||||
|
issue with the components)?
|
||||||
|
|
||||||
|
A freshly booted Z80 starts executing address zero. That address is in your
|
||||||
|
glue code. The first thing it does is thus `jp init`. Initialization is handled
|
||||||
|
by `recipes/rc2014/glue.asm`.
|
||||||
|
|
||||||
|
As you can see, it's a fairly straightforward init. Stack at the end of RAM,
|
||||||
|
interrupt mode 1 (which we use for the ACIA), then individual module
|
||||||
|
initialization, and finally, BASIC's runloop.
|
||||||
|
|
||||||
|
## ACIA init
|
||||||
|
|
||||||
|
An Asynchronous Communication Interface Adaptor allows serial communication with
|
||||||
|
another ACIA (ref http://alanclements.org/serialio.html ). The RC2014 uses a
|
||||||
|
6850 ACIA IC and Collapse OS's `kernel/acia` module was written to interface
|
||||||
|
with this kind of IC.
|
||||||
|
|
||||||
|
For this module to work, it needs to be wired to the z80 but in a particular
|
||||||
|
manner (which oh! surprise, the RC2014's Serial I/O module is...): It should use
|
||||||
|
two ports, R/W. One for access to its status register and one for its access to
|
||||||
|
its data register. Also, its `INT` line should be wired to the z80 `INT` line
|
||||||
|
for interrupts to work.
|
||||||
|
|
||||||
|
I won't go into much detail about the wiring: the 6850 seems to have been
|
||||||
|
designed to be wired thus, so it would kind of be like stating the obvious.
|
||||||
|
|
||||||
|
`aciaInit` in `kernel/acia` is also straightforward. First, it initializes the
|
||||||
|
input buffer. This buffer is a circular buffer that is filled with high priority
|
||||||
|
during the interrupt handler at `aciaInt`. It's important that we process input
|
||||||
|
at high priority to be sure not to miss a byte (there is no buffer overrun
|
||||||
|
handling in `acia`. Unhandled data is simply lost).
|
||||||
|
|
||||||
|
That buffer will later be emptied by BASIC's main loop.
|
||||||
|
|
||||||
|
Once the input buffer is set up, all that is left is to set up the ACIA itself,
|
||||||
|
which is configurable through `ACIA_CTL`. Comments in the code are
|
||||||
|
self-explanatory. Make sure that you use serial config, on the other side, that
|
||||||
|
is compatible with this config there.
|
||||||
|
|
||||||
|
## BASIC init
|
||||||
|
|
||||||
|
Then comes `basInit` at `apps/basic/main`. This is a bigger app, so there is
|
||||||
|
more stuff to initialize, but still, it stays straightforward. I'm not going to
|
||||||
|
explain every line, but give you a recipe for understanding. Every variable as,
|
||||||
|
above its declaration line, a comment explaining what it does. Refer to it.
|
||||||
|
|
||||||
|
This init method is the first one we see that has sub-methods in it. To quickly
|
||||||
|
find where they live, be aware that the general convention in Collapse OS code
|
||||||
|
is to prefix every label with its module name. So, for example, `varInit` lives
|
||||||
|
in `apps/basic/var`.
|
||||||
|
|
||||||
|
You can also see, in the initialization of `BAS_FINDHOOK`, a common idiom: the
|
||||||
|
use of `unsetZ` (from `kernel/core`) as a noop that returns an error (in this
|
||||||
|
case, it just means "command not found").
|
||||||
|
|
||||||
|
## Sending the prompt
|
||||||
|
|
||||||
|
We're now entering `basStart`, which simply prints Collapse OS' prompt and then
|
||||||
|
enter its runloop. Let's examine what happens when we call `printstr` (from
|
||||||
|
`kernel/stdio`).
|
||||||
|
|
||||||
|
`printstr` itself is easy. It iterates over `(HL)` and calls `STDIO_PUTC` for
|
||||||
|
each char.
|
||||||
|
|
||||||
|
But what is `STDIO_PUTC`? It's a glue-defined routine. Let's go back to
|
||||||
|
`glue.asm`. You see that `.equ STDIO_PUTC aciaPutC` line is? Well, there you
|
||||||
|
have it. `call STDIO_PUTC`, in our context, is the exact equivalent of
|
||||||
|
`call aciaPutC`. Let's go see it.
|
||||||
|
|
||||||
|
Whew! it's straightforward! We do two things here: wait until the ACIA is ready
|
||||||
|
to transmit (if it's not, it means that it's still in the process of
|
||||||
|
transmitting the previous character we asked it to transmit), then send that
|
||||||
|
char straight to the data port.
|
||||||
|
|
||||||
|
## BASIC's runloop
|
||||||
|
|
||||||
|
Once the prompt is sent, we're entering BASIC's runloop at `basLoop`. This loops
|
||||||
|
forever.
|
||||||
|
|
||||||
|
The first thing it does is to wait for a line to be entered using
|
||||||
|
`stdioReadLine` from `kernel/stdio`. Let's see what this does.
|
||||||
|
|
||||||
|
Oh, this is a little less straightforward. This routine repeatedly calls
|
||||||
|
`STDIO_GETC` and puts the result in a stdio-specific buffer, after having echoed
|
||||||
|
back the received character so that the user sees what she types.
|
||||||
|
|
||||||
|
`STDIO_GETC` is blocking. It always returns a char.
|
||||||
|
|
||||||
|
As you can see in the glue unit, `STDIO_GETC` is mapped to `aciaGetC`. This
|
||||||
|
routine waits until the ACIA buffer has something in it. Once it does, it reads
|
||||||
|
one character from it and returns it.
|
||||||
|
|
||||||
|
Back to `stdioReadLine`, we check that we don't have special handling to do,
|
||||||
|
that is, end of line or deletion. If we don't, we echo back the char, advance
|
||||||
|
buffer pointer, wait for a new one.
|
||||||
|
|
||||||
|
If we receive a CR or LF, the line is complete, so we return to `basLoop` with
|
||||||
|
a null-terminated input line in `(HL)`.
|
||||||
|
|
||||||
|
I won't cover the processing of the line by BASIC because it's a bit long and
|
||||||
|
doesn't help holistic understanding very much, You can read the code.
|
||||||
|
|
||||||
|
Once the line is processed, that the associated command is found and called, we
|
||||||
|
go back the the beginning of the loop for another ride.
|
||||||
|
|
||||||
|
## When do we receive a character?
|
||||||
|
|
||||||
|
In the above section, we simply wait until the buffer has something in it. But
|
||||||
|
how will that happen? Through `aciaInt` interrupt.
|
||||||
|
|
||||||
|
When the ACIA receives a new character, it pulls the `INT` line low, which, in
|
||||||
|
interrupt mode 1, calls `0x38`. In our glue code, we jump to `aciaInt`.
|
||||||
|
|
||||||
|
In `aciaInt`, the first thing we do is to check that we're concerned (the `INT`
|
||||||
|
line can be triggered by other peripherals and we want to ignore those). To do
|
||||||
|
so, we poll ACIA's status register and see if its receive buffer is full.
|
||||||
|
|
||||||
|
If yes, then we fetch that char from ACIA, put it in the buffer and return from
|
||||||
|
interrupt. That's how the buffer gets full.
|
||||||
|
|
||||||
|
## Conclusion
|
||||||
|
|
||||||
|
This walkthrough covers only one simple case, but I hope that it gives you keys
|
||||||
|
to understanding the whole of Collapse OS. You should be able to start from any
|
||||||
|
other recipe's glue code and walk through it in a way that is similar to what
|
||||||
|
we've made here.
|
Loading…
Reference in New Issue
Block a user