@@ -198,7 +198,7 @@ second. | |||
A freshly selected blkdev begins with its "pointer" at 0. | |||
`seek <lsw> <msw>`: Moves the blkdev "pointer" to the specified offset. The | |||
`bseek <lsw> <msw>`: Moves the blkdev "pointer" to the specified offset. The | |||
first argument is the offset's least significant half (blkdev supports 32-bit | |||
addressing). Is is interpreted as an unsigned integer. | |||
@@ -7,8 +7,7 @@ | |||
## User guide | |||
* [The shell](../apps/shell/README.md) | |||
* [The BASIC shell](../apps/basic/README.md) | |||
* [The shell](../apps/basic/README.md) | |||
* [Load code in RAM and run it](load-run-code.md) | |||
* [Using block devices](blockdev.md) | |||
* [Using the filesystem](fs.md) | |||
@@ -42,71 +42,31 @@ they should try to adhere to the convention, that is: | |||
## Shell usage | |||
`blockdev.asm` supplies 4 shell commands that you can graft to your shell thus: | |||
[...] | |||
SHELL_EXTRA_CMD_COUNT .equ 4 | |||
#include "shell.asm" | |||
; extra commands | |||
.dw blkBselCmd, blkSeekCmd, blkLoadCmd, blkSaveCmd | |||
[...] | |||
### bsel | |||
`bsel` select the active block device. This specify a target for `load` and | |||
`save`. Some applications also use the active blockdev. It receives one | |||
argument, the device index. `bsel 0` selects the first defined device, `bsel 1`, | |||
the second, etc. Error `0x04` when argument is out of bounds. | |||
### seek | |||
`seek` receives one word argument and sets the pointer for the currently active | |||
device to the specified address. Example: `seek 1234`. | |||
The device position is device-specific: if you seek on a device, then switch | |||
to another device and seek again, your previous position isn't lost. You will | |||
still be on the same position when you come back. | |||
### load | |||
`load` works a bit like `poke` except that it reads its data from the currently | |||
active blockdev at its current position. If it hits the end of the blockdev | |||
before it could load its specified number of bytes, it stops. It only raises an | |||
error if it couldn't load any byte. | |||
It moves the device's position to the byte after the last loaded byte. | |||
### save | |||
`save` is the opposite of `load`. It writes the specified number of bytes from | |||
memory to the active blockdev at its current position. | |||
It moves the device's position to the byte after the last written byte. | |||
`apps/basic/blk.asm` supplies 4 shell commands that you can add to your shell. | |||
See "Optional Modules/blk" in [the shell doc](../apps/basic/README.md). | |||
### Example | |||
Let's try an example: You glue yourself a Collapse OS with ACIA as its first | |||
device and a mmap starting at `0xd000` as your second device. Here's what you | |||
Let's try an example: You glue yourself a Collapse OS with a mmap starting at | |||
`0xe000` as your 4th device (like it is in the shell emulator). Here's what you | |||
could do to copy memory around: | |||
> mptr d000 | |||
D000 | |||
> poke 4 | |||
> m=0xe000 | |||
> 10 getc | |||
> 20 poke m a | |||
> 30 m=m+1 | |||
> 40 if m<0xe004 goto 10 | |||
> run | |||
[enter "abcd"] | |||
> peek 4 | |||
61626364 | |||
> mptr c000 | |||
C000 | |||
> peek 4 | |||
[RAM garbage] | |||
> bsel 1 | |||
> load 4 | |||
[returns immediately] | |||
> peek 4 | |||
61626364 | |||
> seek 00 0002 | |||
> load 2 | |||
> peek 4 | |||
63646364 | |||
Awesome, right? | |||
> bsel 3 | |||
> clear | |||
> 10 getb | |||
> 20 puth a | |||
> run | |||
61> run | |||
62> run | |||
63> run | |||
64> bseek 2 | |||
> run | |||
63> run | |||
64> |
@@ -18,7 +18,7 @@ files, Collapse OS tries to reuse blocks from deleted files if it can. | |||
Once "mounted" (turned on with `fson`), you can list files, allocate new files | |||
with `fnew`, mark files as deleted with `fdel` and, more importantly, open files | |||
with `fopn`. | |||
with `fopen`. | |||
Opened files are accessed a independent block devices. It's the glue code that | |||
decides how many file handles we'll support and to which block device ID each | |||
@@ -26,7 +26,7 @@ file handle will be assigned. | |||
For example, you could have a system with three block devices, one for ACIA and | |||
one for a SD card and one for a file handle. You would mount the filesystem on | |||
block device `1` (the SD card), then open a file on handle `0` with `fopn 0 | |||
block device `1` (the SD card), then open a file on handle `0` with `fopen 0 | |||
filename`. You would then do `bsel 2` to select your third block device which | |||
is mapped to the file you've just opened. | |||
@@ -55,13 +55,23 @@ so it's ready to use: | |||
> fls | |||
foo | |||
bar | |||
> mptr 9000 | |||
9000 | |||
> fopn 0 foo | |||
> fopen 0 foo | |||
> bsel 2 | |||
> load 5 | |||
> peek 5 | |||
656C6C6F21 | |||
> getb | |||
> puth a | |||
65 | |||
> getb | |||
> puth a | |||
6C | |||
> getb | |||
> puth a | |||
6C | |||
> getb | |||
> puth a | |||
6F | |||
> getb | |||
> puth a | |||
21 | |||
> fdel bar | |||
> fls | |||
foo | |||
@@ -31,25 +31,46 @@ look like: | |||
.equ STDIO_PUTC aciaPutC | |||
.inc "stdio.asm" | |||
.equ SHELL_RAMSTART STDIO_RAMEND | |||
.equ SHELL_EXTRA_CMD_COUNT 0 | |||
.inc "shell.asm" | |||
; *** BASIC *** | |||
; RAM space used in different routines for short term processing. | |||
.equ SCRATCHPAD_SIZE 0x20 | |||
.equ SCRATCHPAD STDIO_RAMEND | |||
.inc "lib/util.asm" | |||
.inc "lib/ari.asm" | |||
.inc "lib/parse.asm" | |||
.inc "lib/fmt.asm" | |||
.equ EXPR_PARSE parseLiteralOrVar | |||
.inc "lib/expr.asm" | |||
.inc "basic/util.asm" | |||
.inc "basic/parse.asm" | |||
.inc "basic/tok.asm" | |||
.equ VAR_RAMSTART SCRATCHPAD+SCRATCHPAD_SIZE | |||
.inc "basic/var.asm" | |||
.equ BUF_RAMSTART VAR_RAMEND | |||
.inc "basic/buf.asm" | |||
.equ BAS_RAMSTART BUF_RAMEND | |||
.inc "basic/main.asm" | |||
init: | |||
di | |||
; setup stack | |||
ld hl, RAMEND | |||
ld sp, hl | |||
ld sp, RAMEND | |||
im 1 | |||
call aciaInit | |||
call shellInit | |||
call basInit | |||
ei | |||
jp shellLoop | |||
jp basStart | |||
Once this is written, building it is easy: | |||
Once this is written, you can build it with `zasm`, which takes code from stdin | |||
and spits binary to stdout. Because out code has includes, however, you need | |||
to supply zasm with a block device containing a CFS containing the files to | |||
include. This sounds, compicated, but it's managed by the `tools/zasm.sh` shell | |||
script. The invocation would look like (it builds a CFS with the contents of | |||
both `kernel/` and `apps/` folders): | |||
zasm < glue.asm > collapseos.bin | |||
tools/zasm.sh kernel/ apps/ < glue.asm > collapseos.bin | |||
## Building zasm | |||
@@ -122,19 +143,23 @@ label at the very end of its source file. This way, it becomes easy for the | |||
glue code to "graft" entries to the table. This approach, although simple and | |||
effective, only works for one table per part. But it's often enough. | |||
For example, to define extra commands in the shell: | |||
For example, to define block devices: | |||
[...] | |||
.equ SHELL_EXTRA_CMD_COUNT 2 | |||
#include "shell.asm" | |||
.dw myCmd1, myCmd2 | |||
.equ BLOCKDEV_COUNT 4 | |||
.inc "blockdev.asm" | |||
; List of devices | |||
.dw fsdevGetB, fsdevPutB | |||
.dw stdoutGetB, stdoutPutB | |||
.dw stdinGetB, stdinPutB | |||
.dw mmapGetB, mmapPutB | |||
[...] | |||
### Initialization | |||
Then, finally, comes the `init` code. This can be pretty much anything really | |||
and this much depends on the part you select. But if you want a shell, you will | |||
usually end it with `shellLoop`, which never returns. | |||
usually end it with `basStart`, which never returns. | |||
[rc2014]: https://rc2014.co.uk/ | |||
[zasm]: ../tools/emul/README.md | |||
@@ -2,7 +2,7 @@ | |||
Collapse OS likely runs from ROM code. If you need to fiddle with your machine | |||
more deeply, you will want to send arbitrary code to it and run it. You can do | |||
so with the shell's `poke` and `call` commands. | |||
so with the shell's `poke` and `usr` commands. | |||
For example, let's say that you want to run this simple code that you have | |||
sitting on your "modern" machine and want to execute on your running Collapse OS | |||
@@ -13,16 +13,18 @@ machine: | |||
ld (0xa100), a | |||
ret | |||
(we must always return at the end of code that we call with `call`). This will | |||
(we must always return at the end of code that we call with `usr`). This will | |||
increase a number at memory address `0xa100`. First, compile it: | |||
zasm < tosend.asm > tosend.bin | |||
Now, we'll send that code to address `0xa000`: | |||
> mptr a000 | |||
A000 | |||
> poke 8 (resulting binary is 8 bytes long) | |||
> m=0xa000 | |||
> 10 getc | |||
> 20 poke m a | |||
> 30 if m<0xa008 goto 10 | |||
(resulting binary is 8 bytes long) | |||
Now, at this point, it's a bit delicate. To pipe your binary to your serial | |||
connection, you have to close `screen` with CTRL+A then `:quit` to free your | |||
@@ -35,46 +37,45 @@ but if the number of characters sent corresponds to what you gave `poke`, then | |||
Collapse OS will be waiting for a new command. Go ahead, verify that the | |||
transfer was successful with: | |||
peek 8 | |||
3A00A13C3200A1C9 | |||
> peek 0a000 | |||
> puth a | |||
3A | |||
> peek 0a007 | |||
> puth a | |||
C9 | |||
Good! Now, we can try to run it. Before we run it, let's peek at the value at | |||
`0xa100` (being RAM, it's random): | |||
> mptr a100 | |||
A100 | |||
> peek | |||
> peek 0xa100 | |||
> puth a | |||
61 | |||
So, we'll expect this to become `62` after we run the code. Let's go: | |||
> mptr a000 | |||
A000 | |||
> call 00 0000 | |||
> mptr a100 | |||
A100 | |||
> peek | |||
> usr 0xa100 | |||
> peek 0xa100 | |||
> puth a | |||
62 | |||
Success! | |||
## The upload.py tool | |||
## The upload tool | |||
The serial connection is not always 100% reliable and a bad byte can slip in | |||
when you push your code and that's not fun when you try to debug your code (is | |||
this bad behavior caused by my logic or by a bad serial upload?). Moreover, | |||
sending contents bigger than `0xff` bytes can be a hassle. | |||
sending contents manually can be a hassle. | |||
To this end, there is a `upload.py` file in `tools/` that takes care of loading | |||
the file and verify the contents. So, instead of doing `mptr a000` followed by | |||
`poke 8` followed by your `cat` above, you would have done: | |||
To this end, there is a `upload` file in `tools/` (run `make` to build it) that | |||
takes care of loading the file and verify the contents. So, instead of doing | |||
`getc` followed by `poke` followed by your `cat` above, you would have done: | |||
./upload.py /dev/ttyUSB0 a000 tosend.bin | |||
./upload /dev/ttyUSB0 a000 tosend.bin | |||
This emits `mptr`, `poke` and `peek` commands and fail appropriately if the | |||
`peek` doesn't match sent contents. If the file is larger than `0xff` bytes, | |||
repeat the process until the whole file was sent (file must fit in memory space | |||
though, of course). Very handy. | |||
This clears your basic listing and then types in a basic algorithm to receive | |||
and echo and pre-defined number of bytes. The `upload` tool then sends and read | |||
each byte, verifying that they're the same. Very handy. | |||
## Labels in RAM code | |||
@@ -126,16 +127,3 @@ You can then include that file in your "user" code, like this: | |||
If you load that code at `0xa000` and call it, it will print "Hello World!" by | |||
using the `printstr` routine from `core.asm`. | |||
## Doing the same with the BASIC shell | |||
The BASIC shell also has the capacity to load code from serial console but its | |||
semantic is a bit different from the regular shell. Instead of peeking and | |||
poking, you use `getc` to send data and then `putc` to send the same data back | |||
for verification. Then, you can use `poke` to commit it to memory. | |||
There's an upload tool that use these commands and it's `uploadb.py`. It is | |||
invoked with the same arguments as `upload.py`. | |||
Once your code is uploaded, you will call it with BASIC's `usr` command. See | |||
BASIC's README for more details. |
@@ -13,13 +13,13 @@ on a real machine, you'll have to make sure to provide these requirements. | |||
The emulated shell has a `hello.asm` file in its mounted filesystem that is | |||
ready to compile. It has two file handles 0 and 1, mapped to blk IDs 1 and 2. | |||
We will open our source file in handle 0 and our dest file in handle 1. Then, | |||
with the power of the `pgm` module, we'll autoload our newly compiled file and | |||
execute it! | |||
with the power of the `fs` module's autoloader, we'll load our newly compiled | |||
file and execute it! | |||
Collapse OS | |||
> fnew 1 dest ; create destination file | |||
> fopn 0 hello.asm ; open source file in handle 0 | |||
> fopn 1 dest ; open dest binary in handle 1 | |||
> fopen 0 hello.asm ; open source file in handle 0 | |||
> fopen 1 dest ; open dest binary in handle 1 | |||
> zasm 1 2 ; assemble source file into binary file | |||
> dest ; call newly compiled file | |||
Assembled from the shell | |||