parent
25d25d017c
commit
1710c865dc
@ -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>
|
||||
|
26
doc/fs.md
26
doc/fs.md
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user