1
0
mirror of https://github.com/hsoft/collapseos.git synced 2024-11-02 20:30:56 +11:00

Compare commits

..

No commits in common. "888395d496b52b6114e4aefafacb1451780d43a9" and "b40b39f45c4ef400d8ec41563dd28b68a1501ac5" have entirely different histories.

52 changed files with 1820 additions and 392 deletions

View File

@ -10,6 +10,7 @@
; *** Requirements ***
; blkGetB
; parseArgs
;
; *** Includes ***
@ -19,9 +20,5 @@
jp at28wMain
.inc "core.asm"
.inc "lib/util.asm"
.inc "lib/parse.asm"
.inc "lib/args.asm"
.inc "at28w/main.asm"
USER_RAMSTART:

View File

@ -198,7 +198,7 @@ second.
A freshly selected blkdev begins with its "pointer" at 0.
`bseek <lsw> <msw>`: Moves the blkdev "pointer" to the specified offset. The
`seek <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.

View File

@ -137,6 +137,4 @@ basFSCmds:
.dw basFNEW
.db "fdel", 0
.dw basFDEL
.db "fson", 0
.dw fsOn
.db 0xff ; end of table

View File

@ -124,6 +124,13 @@ basERR:
; 2 - the beginning of the arg, with whitespace properly skipped.
;
; Commands are expected to set Z on success.
basBYE:
; To quit the loop, let's return the stack to its initial value and
; then return.
xor a
ld sp, (BAS_INITSP)
ret
basLIST:
call bufFirst
jr nz, .end
@ -442,6 +449,8 @@ basR2Var: ; Just send reg to vars. Used in basPgmHook
; direct only
basCmds1:
.db "bye", 0
.dw basBYE
.db "list", 0
.dw basLIST
.db "run", 0

View File

@ -1,14 +0,0 @@
; SDC-related basic commands
basSDCI:
jp sdcInitializeCmd
basSDCF:
jp sdcFlushCmd
basSDCCmds:
.db "sdci", 0
.dw basSDCI
.db "sdcf", 0
.dw basSDCF
.db 0xff ; end of table

View File

@ -1,6 +1,3 @@
; *** Requirements ***
; lib/parse
;
; *** Consts ***
; maximum number of bytes to receive as args in all commands. Determines the
; size of the args variable.

View File

@ -1,6 +1,5 @@
; *** Requirements ***
; stdioPutC
; divide
;
; Same as fmtDecimal, but DE is considered a signed number

View File

@ -1,5 +1,3 @@
; *** Requirements ***
; lib/util
; *** Code ***
; Parse the hex char at A and extract it's 0-15 numerical value. Put the result

View File

@ -8,14 +8,12 @@
;
; *** Requirements ***
; printstr
; stdioPutC
; printHexPair
;
; *** Includes ***
.inc "user.h"
jp memtMain
.inc "lib/ari.asm"
.inc "lib/fmt.asm"
.inc "memt/main.asm"
USER_RAMSTART:

View File

@ -14,7 +14,7 @@
; sdcPutB
; sdcGetB
; printstr
; stdioPutC
; printHexPair
;
; *** Includes ***
@ -23,7 +23,5 @@
jp sdctMain
.inc "lib/ari.asm"
.inc "lib/fmt.asm"
.inc "sdct/main.asm"
USER_RAMSTART:

98
apps/shell/README.md Normal file
View File

@ -0,0 +1,98 @@
# shell
**This shell is currently being replaced with the
[BASIC shell](../basic/README.md). While it's still used in many places, it's
being phased out.**
The shell is a text interface giving you access to commands to control your
machine. It is not built to be user friendly, but to minimize binary space and
maximize code simplicity.
We expect the user of this shell to work with a copy of the user guide within
reach.
It is its design goal, however, to give you the levers you need to control your
machine fully.
## Commands and arguments
You invoke a command by typing its name, followed by a list of arguments. All
numerical arguments have to be typed in hexadecimal form, without prefix or
suffix. Lowercase is fine. Single digit is fine for byte (not word) arguments
smaller than `0x10`. Example calls:
mptr 01ff
peek 4
poke 1f
call 00 0123
All numbers printed by the shell are in hexadecimals form.
Whenever a command is malformed, the shell will print `ERR` with a code. This
table describes those codes:
| Code | Description |
|------|---------------------------|
| `01` | Unknown command |
| `02` | Badly formatted arguments |
| `03` | Out of bounds |
| `04` | Unsupported command |
| `05` | I/O error |
Applications have their own error codes as well. If you see an error code that
isn't in this list, it's an application-specific error code.
## mptr
The shell has a global memory pointer (let's call it `memptr`) that is used by
other commands. This pointer is 2 bytes long and starts at `0x0000`. To move
it, you use the mptr command with the new pointer position. The command
prints out the new `memptr` (just to confirm that it has run). Example:
> mptr 42ff
42FF
## peek
Read memory targeted by `memptr` and prints its contents in hexadecimal form.
This command takes one byte argument (optional, default to 1), the number of
bytes we want to read. Example:
> mptr 0040
0040
> peek 2
ED56
## poke
Puts the serial console in input mode and waits for a specific number of
characters to be typed (that number being specified by a byte argument). These
characters will be literally placed in memory, one after the other, starting at
`memptr`.
Example:
> poke 5
Hello
> peek 5
48656C6C6F
## call
Calls the routine at `memptr`, setting the `A` and `HL` registers to the value
specified by its optional arguments (default to 0).
Be aware that this results in a call, not a jump, so your routine needs to
return if you don't want to break your system.
The following example works in the case where you've made yourself a jump table
in your glue code a `jp printstr` at `0x0004`:
> mptr a000
A000
> poke 6
Hello\0 (you can send a null char through a terminal with CTRL+@)
> mptr 0004
0004
> call 00 a000
Hello>

118
apps/shell/blkdev.asm Normal file
View File

@ -0,0 +1,118 @@
; *** REQUIREMENTS ***
; blkSelPtr
; blkSel
; blkSeek
; blkTell
blkBselCmd:
.db "bsel", 0b001, 0, 0
ld a, (hl) ; argument supplied
push de
call blkSelPtr
call blkSel
pop de
jr nz, .error
xor a
ret
.error:
ld a, BLOCKDEV_ERR_OUT_OF_BOUNDS
ret
blkSeekCmd:
.db "seek", 0b001, 0b011, 0b001
; First, the mode
ld a, (hl)
inc hl
push af ; save mode for later
; HL points to two bytes that contain out address. Seek expects HL
; to directly contain that address.
ld a, (hl)
ex af, af'
inc hl
ld a, (hl)
ld l, a
ex af, af'
ld h, a
pop af ; bring mode back
ld de, 0 ; DE is used for seek > 64K which we don't support
call blkSeek
call blkTell
ld a, h
call printHex
ld a, l
call printHex
call printcrlf
xor a
ret
; Load the specified number of bytes (max 0x100, 0 means 0x100) from IO and
; write them in the current memory pointer (which doesn't change). If the
; blkdev hits end of stream before we reach our specified number of bytes, we
; stop loading.
;
; Returns a SHELL_ERR_IO_ERROR only if we couldn't read any byte (if the first
; call to GetB failed)
;
; Example: load 42
blkLoadCmd:
.db "load", 0b001, 0, 0
blkLoad:
push bc
push hl
ld a, (hl)
ld b, a
ld hl, (SHELL_MEM_PTR)
call blkGetB
jr nz, .ioError
jr .intoLoop ; we'v already called blkGetB. don't call it
; again.
.loop:
call blkGetB
.intoLoop:
ld (hl), a
inc hl
jr nz, .loopend
djnz .loop
.loopend:
; success
xor a
jr .end
.ioError:
ld a, SHELL_ERR_IO_ERROR
.end:
pop hl
pop bc
ret
; Load the specified number of bytes (max 0x100, 0 means 0x100) from the current
; memory pointer and write them to I/O. Memory pointer doesn't move. This puts
; chars to blkPutB. Raises error if not all bytes could be written.
;
; Example: save 42
blkSaveCmd:
.db "save", 0b001, 0, 0
blkSave:
push bc
push hl
ld a, (hl)
ld b, a
ld hl, (SHELL_MEM_PTR)
.loop:
ld a, (hl)
inc hl
call blkPutB
jr nz, .ioError
djnz .loop
.loopend:
; success
xor a
jr .end
.ioError:
ld a, SHELL_ERR_IO_ERROR
.end:
pop hl
pop bc
ret

75
apps/shell/fs.asm Normal file
View File

@ -0,0 +1,75 @@
; *** SHELL COMMANDS ***
fsOnCmd:
.db "fson", 0, 0, 0
jp fsOn
; Lists filenames in currently active FS
flsCmd:
.db "fls", 0, 0, 0, 0
ld iy, .iter
call fsIter
ret z
ld a, FS_ERR_NO_FS
ret
.iter:
ld a, FS_META_FNAME_OFFSET
call addHL
call printstr
jp printcrlf
; Takes one byte block number to allocate as well we one string arg filename
; and allocates a new file in the current fs.
fnewCmd:
.db "fnew", 0b001, 0b1001, 0b001
push hl
ld a, (hl)
inc hl
call intoHL
call fsAlloc
pop hl
xor a
ret
; Deletes filename with specified name
fdelCmd:
.db "fdel", 0b1001, 0b001, 0
push hl
call intoHL ; HL now holds the string we look for
call fsFindFN
jr nz, .notfound
; Found! delete
call fsDel
jr z, .end
; weird error, continue to error condition
.notfound:
ld a, FS_ERR_NOT_FOUND
.end:
pop hl
ret
; Opens specified filename in specified file handle.
; First argument is file handle, second one is file name.
; Example: fopn 0 foo.txt
fopnCmd:
.db "fopn", 0b001, 0b1001, 0b001
push hl
push de
ld a, (hl) ; file handle index
call fsHandle
; DE now points to file handle
inc hl
call intoHL ; HL now holds the string we look for
call fsFindFN
jr nz, .notfound
; Found!
; FS_PTR points to the file we want to open
push de \ pop ix ; IX now points to the file handle.
call fsOpen
jr .end
.notfound:
ld a, FS_ERR_NOT_FOUND
.end:
pop de
pop hl
ret

35
apps/shell/glue.asm Normal file
View File

@ -0,0 +1,35 @@
; This repesents a full-featured shell, that is, a shell that includes all
; options it has to offer. For a minimal shell, use "gluem.asm"
.inc "user.h"
.inc "err.h"
.inc "ascii.h"
.inc "blkdev.h"
.inc "fs.h"
jp init
.inc "core.asm"
.inc "lib/util.asm"
.inc "lib/parse.asm"
.inc "lib/args.asm"
.equ SHELL_RAMSTART USER_RAMSTART
.equ SHELL_EXTRA_CMD_COUNT 9
.inc "shell/main.asm"
.dw blkBselCmd, blkSeekCmd, blkLoadCmd, blkSaveCmd
.dw fsOnCmd, flsCmd, fnewCmd, fdelCmd, fopnCmd
.inc "lib/ari.asm"
.inc "lib/fmt.asm"
.inc "shell/blkdev.asm"
.inc "shell/fs.asm"
.equ PGM_RAMSTART SHELL_RAMEND
.equ PGM_CODEADDR USER_CODE
.inc "shell/pgm.asm"
init:
call shellInit
ld hl, pgmShellHook
ld (SHELL_CMDHOOK), hl
jp shellLoop
USER_RAMSTART:

22
apps/shell/gluem.asm Normal file
View File

@ -0,0 +1,22 @@
; This repesents a minimal shell, that is, the smallest shell our configuration
; options allow. For a full-featured shell, see "glue.asm"
.inc "user.h"
.inc "err.h"
.inc "ascii.h"
jp init
.inc "core.asm"
.inc "lib/util.asm"
.inc "lib/parse.asm"
.inc "lib/args.asm"
.inc "lib/fmt.asm"
.equ SHELL_RAMSTART USER_RAMSTART
.equ SHELL_EXTRA_CMD_COUNT 0
.inc "shell/main.asm"
init:
call shellInit
jp shellLoop
USER_RAMSTART:

361
apps/shell/main.asm Normal file
View File

@ -0,0 +1,361 @@
; shell
;
; Runs a shell over a block device interface.
; The shell spits a welcome prompt, wait for input and compare the first 4 chars
; of the input with a command table and call the appropriate routine if it's
; found, an error if it's not.
;
; To determine the correct routine to call we first go through cmds in
; shellCmdTbl. This means that we first go through internal cmds, then cmds
; "grafted" by glue code.
;
; If the command isn't found, SHELL_CMDHOOK is called, which should set A to
; zero if it executes something. Otherwise, SHELL_ERR_UNKNOWN_CMD will be
; returned.
;
; See constants below for error codes.
;
; All numerical values in the Collapse OS shell are represented and parsed in
; hexadecimal form, without prefix or suffix.
; *** REQUIREMENTS ***
; err
; core
; parse
; stdio
; *** DEFINES ***
; SHELL_EXTRA_CMD_COUNT: Number of extra cmds to be expected after the regular
; ones. See comment in COMMANDS section for details.
; SHELL_RAMSTART
; *** CONSTS ***
; number of entries in shellCmdTbl
.equ SHELL_CMD_COUNT 6+SHELL_EXTRA_CMD_COUNT
; maximum length for shell commands. Should be confortably below stdio's
; readline buffer length.
.equ SHELL_MAX_CMD_LEN 0x10
; *** VARIABLES ***
; Memory address that the shell is currently "pointing at" for peek, load, call
; operations. Set with mptr.
.equ SHELL_MEM_PTR SHELL_RAMSTART
; Places where we store arguments specifiers and where resulting values are
; written to after parsing.
.equ SHELL_CMD_ARGS @+2
; Pointer to a hook to call when a cmd name isn't found
.equ SHELL_CMDHOOK @+PARSE_ARG_MAXCOUNT
.equ SHELL_RAMEND @+2
; *** CODE ***
shellInit:
xor a
ld (SHELL_MEM_PTR), a
ld (SHELL_MEM_PTR+1), a
ld hl, noop
ld (SHELL_CMDHOOK), hl
; print welcome
ld hl, .welcome
jp printstr
.welcome:
.db "Collapse OS", CR, LF, "> ", 0
; Inifite loop that processes input. Because it's infinite, you should jump
; to it rather than call it. Saves two precious bytes in the stack.
shellLoop:
call stdioReadLine
call printcrlf
call shellParse
ld hl, .prompt
call printstr
jr shellLoop
.prompt:
.db "> ", 0
; Parse command (null terminated) at HL and calls it
shellParse:
; first thing: is command empty?
ld a, (hl)
or a
ret z ; empty, nothing to do
push af
push bc
push de
push hl
push ix
; Before looking for a suitable command, let's make the cmd line more
; usable by replacing the first ' ' with a null char. This way, cmp is
; easy to make.
push hl ; --> lvl 1
ld a, ' '
call findchar
jr z, .hasArgs
; no arg, (HL) is zero to facilitate processing later, add a second
; null next to that one to indicate unambiguously that we have no args.
inc hl
; Oh wait, before we proceed, is our cmd length within limits? cmd len
; is currently in A from findchar
cp SHELL_MAX_CMD_LEN
jr c, .hasArgs ; within limits
; outside limits
ld a, SHELL_ERR_UNKNOWN_CMD
jr .error
.hasArgs:
xor a
ld (hl), a
pop hl ; <-- lvl 1, beginning of cmd
ld de, shellCmdTbl
ld b, SHELL_CMD_COUNT
.loop:
push de ; we need to keep that table entry around...
call intoDE ; Jump from the table entry to the cmd addr.
ld a, 4 ; 4 chars to compare
call strncmp
pop de
jr z, .found
inc de
inc de
djnz .loop
; exhausted loop? not found
ld a, SHELL_ERR_UNKNOWN_CMD
; Before erroring out, let's try SHELL_HOOK.
ld ix, (SHELL_CMDHOOK)
call callIX
jr z, .end ; oh, not an error!
; still an error. Might be different than SHELL_ERR_UNKNOWN_CMD though.
; maybe a routine was called, but errored out.
jr .error
.found:
; we found our command. DE points to its table entry. Now, let's parse
; our args.
call intoDE ; Jump from the table entry to the cmd addr.
; advance the HL pointer to the beginning of the args.
xor a
call findchar
inc hl ; beginning of args
; Now, let's have DE point to the argspecs
ld a, 4
call addDE
; We're ready to parse args
ld ix, SHELL_CMD_ARGS
call parseArgs
or a ; cp 0
jr nz, .parseerror
; Args parsed, now we can load the routine address and call it.
; let's have DE point to the jump line
ld hl, SHELL_CMD_ARGS
ld a, PARSE_ARG_MAXCOUNT
call addDE
push de \ pop ix
; Ready to roll!
call callIX
or a ; cp 0
jr nz, .error ; if A is non-zero, we have an error
jr .end
.parseerror:
ld a, SHELL_ERR_BAD_ARGS
.error:
call shellPrintErr
.end:
pop ix
pop hl
pop de
pop bc
pop af
ret
; Print the error code set in A (in hex)
shellPrintErr:
push af
push hl
ld hl, .str
call printstr
call printHex
call printcrlf
pop hl
pop af
ret
.str:
.db "ERR ", 0
; *** COMMANDS ***
; A command is a 4 char names, followed by a PARSE_ARG_MAXCOUNT bytes of
; argument specs, followed by the routine. Then, a simple table of addresses
; is compiled in a block and this is what is iterated upon when we want all
; available commands.
;
; Format: 4 bytes name followed by PARSE_ARG_MAXCOUNT bytes specifiers,
; followed by 3 bytes jump. fill names with zeroes
;
; When these commands are called, HL points to the first byte of the
; parsed command args.
;
; If the command is a success, it should set A to zero. If the command results
; in an error, it should set an error code in A.
;
; Extra commands: Other parts might define new commands. You can add these
; commands to your shell. First, set SHELL_EXTRA_CMD_COUNT to
; the number of extra commands to add, then add a ".dw"
; directive *just* after your '.inc "shell.asm"'. Voila!
;
; Set memory pointer to the specified address (word).
; Example: mptr 01fe
shellMptrCmd:
.db "mptr", 0b011, 0b001, 0
shellMptr:
push hl
; reminder: z80 is little-endian
ld a, (hl)
ld (SHELL_MEM_PTR+1), a
inc hl
ld a, (hl)
ld (SHELL_MEM_PTR), a
ld hl, (SHELL_MEM_PTR)
ld a, h
call printHex
ld a, l
call printHex
call printcrlf
pop hl
xor a
ret
; peek the number of bytes specified by argument where memory pointer points to
; and display their value. If 0 is specified, 0x100 bytes are peeked.
;
; Example: peek 2 (will print 2 bytes)
shellPeekCmd:
.db "peek", 0b001, 0, 0
shellPeek:
push bc
push hl
ld a, (hl)
ld b, a
ld hl, (SHELL_MEM_PTR)
.loop: ld a, (hl)
call printHex
inc hl
djnz .loop
call printcrlf
.end:
pop hl
pop bc
xor a
ret
; poke specified number of bytes where memory pointer points and set them to
; bytes typed through stdioGetC. Blocks until all bytes have been fetched.
shellPokeCmd:
.db "poke", 0b001, 0, 0
shellPoke:
push bc
push hl
ld a, (hl)
ld b, a
ld hl, (SHELL_MEM_PTR)
.loop: call stdioGetC
jr nz, .loop ; nothing typed? loop
ld (hl), a
inc hl
djnz .loop
pop hl
pop bc
xor a
ret
; Calls the routine where the memory pointer currently points. This can take two
; parameters, A and HL. The first one is a byte, the second, a word. These are
; the values that A and HL are going to be set to just before calling.
; Example: run 42 cafe
shellCallCmd:
.db "call", 0b101, 0b111, 0b001
shellCall:
push hl
push ix
; Let's recap here. At this point, we have:
; 1. The address we want to execute in (SHELL_MEM_PTR)
; 2. our A arg as the first byte of (HL)
; 2. our HL arg as (HL+1) and (HL+2)
; Ready, set, go!
ld ix, (SHELL_MEM_PTR)
ld a, (hl)
ex af, af'
inc hl
ld a, (hl)
exx
ld h, a
exx
inc hl
ld a, (hl)
exx
ld l, a
ex af, af'
call callIX
.end:
pop ix
pop hl
xor a
ret
shellIORDCmd:
.db "iord", 0b001, 0, 0
push bc
ld a, (hl)
ld c, a
in a, (c)
call printHex
xor a
pop bc
ret
shellIOWRCmd:
.db "iowr", 0b001, 0b001, 0
push bc
ld a, (hl)
ld c, a
inc hl
ld a, (hl)
out (c), a
xor a
pop bc
ret
; This table is at the very end of the file on purpose. The idea is to be able
; to graft extra commands easily after an include in the glue file.
shellCmdTbl:
.dw shellMptrCmd, shellPeekCmd, shellPokeCmd, shellCallCmd
.dw shellIORDCmd, shellIOWRCmd

51
apps/shell/pgm.asm Normal file
View File

@ -0,0 +1,51 @@
; pgm - execute programs loaded from filesystem
;
; Implements a shell hook that searches the filesystem for a file with the same
; name as the cmd, loads that file in memory and executes it, sending the
; program a pointer to *unparsed* arguments in HL.
;
; We expect the loaded program to return a status code in A. 0 means success,
; non-zero means error. Programs should avoid having error code overlaps with
; the shell so that we know where the error comes from.
;
; *** Requirements ***
; fs
;
; *** Defines ***
; PGM_CODEADDR: Memory address where to place the code we load.
;
; *** Variables ***
.equ PGM_HANDLE PGM_RAMSTART
.equ PGM_RAMEND @+FS_HANDLE_SIZE
; Routine suitable to plug into SHELL_CMDHOOK. HL points to the full cmdline.
; which has been processed to replace the first ' ' with a null char.
pgmShellHook:
; (HL) is suitable for a direct fsFindFN call
call fsFindFN
jr nz, .noFile
; We have a file! Advance HL to args
xor a
call findchar
inc hl ; beginning of args
; Alright, ready to run!
jp .run
.noFile:
ld a, SHELL_ERR_IO_ERROR
ret
.run:
push hl ; unparsed args
ld ix, PGM_HANDLE
call fsOpen
ld hl, 0 ; addr that we read in file handle
ld de, PGM_CODEADDR ; addr in mem we write to
.loop:
call fsGetB ; we use Z at end of loop
ld (de), a ; Z preserved
inc hl ; Z preserved in 16-bit
inc de ; Z preserved in 16-bit
jr z, .loop
pop hl ; recall args
; ready to jump!
jp PGM_CODEADDR

View File

@ -7,7 +7,8 @@
## User guide
* [The shell](../apps/basic/README.md)
* [The shell](../apps/shell/README.md)
* [The BASIC 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)

View File

@ -42,31 +42,71 @@ they should try to adhere to the convention, that is:
## Shell usage
`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).
`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.
### Example
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
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
could do to copy memory around:
> m=0xe000
> 10 getc
> 20 poke m a
> 30 m=m+1
> 40 if m<0xe004 goto 10
> run
> mptr d000
D000
> poke 4
[enter "abcd"]
> bsel 3
> clear
> 10 getb
> 20 puth a
> run
61> run
62> run
63> run
64> bseek 2
> run
63> run
64>
> 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?

View File

@ -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 `fopen`.
with `fopn`.
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 `fopen 0
block device `1` (the SD card), then open a file on handle `0` with `fopn 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,23 +55,13 @@ so it's ready to use:
> fls
foo
bar
> fopen 0 foo
> mptr 9000
9000
> fopn 0 foo
> bsel 2
> getb
> puth a
65
> getb
> puth a
6C
> getb
> puth a
6C
> getb
> puth a
6F
> getb
> puth a
21
> load 5
> peek 5
656C6C6F21
> fdel bar
> fls
foo

View File

@ -31,46 +31,25 @@ look like:
.equ STDIO_PUTC aciaPutC
.inc "stdio.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"
.equ SHELL_RAMSTART STDIO_RAMEND
.equ SHELL_EXTRA_CMD_COUNT 0
.inc "shell.asm"
init:
di
; setup stack
ld sp, RAMEND
ld hl, RAMEND
ld sp, hl
im 1
call aciaInit
call basInit
call shellInit
ei
jp basStart
jp shellLoop
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):
Once this is written, building it is easy:
tools/zasm.sh kernel/ apps/ < glue.asm > collapseos.bin
zasm < glue.asm > collapseos.bin
## Building zasm
@ -143,23 +122,19 @@ 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 block devices:
For example, to define extra commands in the shell:
[...]
.equ BLOCKDEV_COUNT 4
.inc "blockdev.asm"
; List of devices
.dw fsdevGetB, fsdevPutB
.dw stdoutGetB, stdoutPutB
.dw stdinGetB, stdinPutB
.dw mmapGetB, mmapPutB
.equ SHELL_EXTRA_CMD_COUNT 2
#include "shell.asm"
.dw myCmd1, myCmd2
[...]
### 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 `basStart`, which never returns.
usually end it with `shellLoop`, which never returns.
[rc2014]: https://rc2014.co.uk/
[zasm]: ../tools/emul/README.md

View File

@ -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 `usr` commands.
so with the shell's `poke` and `call` 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,18 +13,16 @@ machine:
ld (0xa100), a
ret
(we must always return at the end of code that we call with `usr`). This will
(we must always return at the end of code that we call with `call`). 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`:
> m=0xa000
> 10 getc
> 20 poke m a
> 30 if m<0xa008 goto 10
(resulting binary is 8 bytes long)
> mptr a000
A000
> poke 8 (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
@ -37,45 +35,46 @@ 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 0a000
> puth a
3A
> peek 0a007
> puth a
C9
peek 8
3A00A13C3200A1C9
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):
> peek 0xa100
> puth a
> mptr a100
A100
> peek
61
So, we'll expect this to become `62` after we run the code. Let's go:
> usr 0xa100
> peek 0xa100
> puth a
> mptr a000
A000
> call 00 0000
> mptr a100
A100
> peek
62
Success!
## The upload tool
## The upload.py 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 manually can be a hassle.
sending contents bigger than `0xff` bytes can be a hassle.
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:
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:
./upload /dev/ttyUSB0 a000 tosend.bin
./upload.py /dev/ttyUSB0 a000 tosend.bin
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.
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.
## Labels in RAM code
@ -127,3 +126,16 @@ 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.

View File

@ -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 `fs` module's autoloader, we'll load our newly compiled
file and execute it!
with the power of the `pgm` module, we'll autoload our newly compiled file and
execute it!
Collapse OS
> fnew 1 dest ; create destination file
> fopen 0 hello.asm ; open source file in handle 0
> fopen 1 dest ; open dest binary in handle 1
> fopn 0 hello.asm ; open source file in handle 0
> fopn 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

View File

@ -101,7 +101,8 @@ fsInit:
xor a
ld hl, FS_BLK
ld b, FS_RAMEND-FS_BLK
jp fill
call fill
ret
; *** Navigation ***
@ -285,7 +286,7 @@ fsFindFN:
call fsNext
jr z, .loop
; End of the chain, not found
; Z already unset
call unsetZ
.end:
pop de
ret
@ -310,7 +311,7 @@ fsIsValid:
; Returns whether current block is deleted in Z flag.
fsIsDeleted:
ld a, (FS_META+FS_META_FNAME_OFFSET)
or a ; Z flag is our answer
cp 0 ; Z flag is our answer
ret
; *** blkdev methods ***
@ -507,9 +508,12 @@ fsOn:
jr .end
.error:
; couldn't mount. Let's reset our variables.
call fsInit
xor a
ld b, FS_META-FS_BLK ; reset routine pointers and FS ptrs
ld hl, FS_BLK
call fill
ld a, FS_ERR_NO_FS
or a ; unset Z
.end:
pop bc
pop de
@ -520,16 +524,18 @@ fsOn:
fsIsOn:
; check whether (FS_BLK) is zero
push hl
push de
ld hl, (FS_BLK)
ld a, h
or l
ld de, 0
call cpHLDE
jr nz, .mounted
; not mounted, unset Z
inc a
; if equal, it means our FS is not mounted
call unsetZ
jr .end
.mounted:
cp a ; ensure Z
.end:
pop de
pop hl
ret
@ -539,6 +545,8 @@ fsIsOn:
; There are no error condition happening midway. If you get an error, then (IY)
; was never called.
fsIter:
call fsIsOn
ret nz
call fsBegin
ret nz
.loop:

View File

@ -621,7 +621,10 @@ sdcCRC:
pop af
ret
; *** shell cmds ***
sdcInitializeCmd:
.db "sdci", 0, 0, 0
call sdcInitialize
ret nz
call .setBlkSize
@ -675,6 +678,7 @@ sdcInitializeCmd:
; Flush the current SDC buffer if dirty
sdcFlushCmd:
.db "sdcf", 0, 0, 0
ld hl, SDC_BUFSEC1
ld (SDC_BUFPTR), hl
call sdcWriteBlk
@ -720,11 +724,14 @@ _sdcPlaceBuf:
sdcGetB:
push hl
call _sdcPlaceBuf
jr nz, .end ; NZ already set
jr nz, .error
; This is it!
ld a, (hl)
cp a ; ensure Z
jr .end
.error:
call unsetZ
.end:
pop hl
ret

View File

@ -27,11 +27,11 @@ are other recipes related to the RC2014:
* [Accessing a MicroSD card](sdcard/README.md)
* [Assembling binaries](zasm/README.md)
* [Interfacing a PS/2 keyboard](ps2/README.md)
* [Replace shell by a BASIC interpreter](basic/README.md)
## Recipe
The goal is to have the shell running and accessible through the Serial I/O.
To make things fun, we play with I/Os using RC2014's Digital I/O module.
You'll need specialized tools to write data to the AT28 EEPROM. There seems to
be many devices around made to write in flash and EEPROM modules, but being in
@ -44,7 +44,6 @@ device I use in this recipe.
* [romwrite][romwrite] and its specified dependencies
* [GNU screen][screen]
* A FTDI-to-TTL cable to connect to the Serial I/O module of the RC2014
* (Optional) RC2014's Digital I/O module
### Write glue.asm
@ -63,15 +62,15 @@ Then comes the usual `di` to aoid interrupts during init, and stack setup.
We set interrupt mode to 1 because that's what `acia.asm` is written around.
Then, we init ACIA, shell, enable interrupt and give control of the main loop
to the BASIC shell.
to `shell.asm`.
What comes below is actual code include from parts we want to include in our
OS. As you can see, we need to tell each module where to put their variables.
See `apps/README.md` for details.
See `parts/README.md` for details.
You can also see from the `STDIO_GETC` and `STDIO_PUTC` macros that the shell
is decoupled from the ACIA and can get its IO from anything. See comments in
`kernel/stdio.asm` for details.
You can also see from the `SHELL_GETC` and `SHELL_PUTC` macros that the shell
is decoupled from the ACIA and can get its IO from anything. See
`parts/README.md` for details.
### Build the image
@ -101,20 +100,6 @@ identify the tty bound to it (in my case, `/dev/ttyUSB0`). Then:
screen /dev/ttyUSB0 115200
Press the reset button on the RC2014 and you should see the Collapse OS prompt!
See documentation in `apps/basic/README.md` for details.
For now, let's have some fun with the Digital I/O module. Type this:
```
> a=0
> 10 out 0 a
> 20 sleep 0xffff
> 30 a=a+1
> 40 goto 10
> run
```
You now have your Digital I/O lights doing a pretty dance, forever.
[rc2014]: https://rc2014.co.uk
[romwrite]: https://github.com/hsoft/romwrite

View File

@ -0,0 +1,10 @@
TARGET = os.bin
ZASM = ../../../tools/zasm.sh
KERNEL = ../../../kernel
APPS = ../../../apps
.PHONY: all
all: $(TARGET)
$(TARGET): glue.asm
$(ZASM) $(KERNEL) $(APPS) < $< > $@

View File

@ -0,0 +1,46 @@
# BASIC as a shell
This recipe demonstrate the replacement of the usual shell with the BASIC
interpreter supplied in Collapse OS. To make things fun, we play with I/Os
using RC2014's Digital I/O module.
## Gathering parts
* Same parts as in the base recipe
* (Optional) RC2014's Digital I/O module
The Digital I/O module is only used in the example BASIC code. If you don't
have the module, just use BASIC in another fashion.
## Build the image
As usual, building `os.bin` is a matter of running `make`. Then, you can get
that image to your EEPROM like you did in the base recipe.
## Usage
Upon boot, you'll directy be in a BASIC prompt. See documentation in
`apps/basic/README.md` for details.
For now, let's have some fun with the Digital I/O module. Type this:
```
> a=0
> 10 out 0 a
> 20 sleep 0xffff
> 30 a=a+1
> 40 goto 10
> run
```
You now have your Digital I/O lights doing a pretty dance, forever.
## Looking at the glue code
If you look at the glue code, you'll see that it's very similar to the one in
the base recipe, except that the shell includes have been replaced by the basic
includes. Those includes have been copy/pasted from `apps/basic/glue.asm` and
`USER_RAMSTART` has been replaced with `STDIO_RAMEND` so that BASIC's memory
gets placed properly (that is, right after the kernel's memory).
Simple, isn't it?

View File

@ -0,0 +1,57 @@
.equ RAMSTART 0x8000
.equ RAMEND 0xffff
.equ ACIA_CTL 0x80 ; Control and status. RS off.
.equ ACIA_IO 0x81 ; Transmit. RS on.
.equ DIGIT_IO 0x00 ; digital I/O's port
jp init
; interrupt hook
.fill 0x38-$
jp aciaInt
.inc "err.h"
.inc "ascii.h"
.inc "core.asm"
.inc "str.asm"
.equ ACIA_RAMSTART RAMSTART
.inc "acia.asm"
.equ STDIO_RAMSTART ACIA_RAMEND
.equ STDIO_GETC aciaGetC
.equ STDIO_PUTC aciaPutC
.inc "stdio.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 sp, RAMEND
im 1
call aciaInit
ei
call basInit
jp basStart

View File

@ -1,8 +1,9 @@
; classic RC2014 setup (8K ROM + 32K RAM) and a stock Serial I/O module
; The RAM module is selected on A15, so it has the range 0x8000-0xffff
.equ RAMSTART 0x8000
.equ RAMEND 0xffff
.equ ACIA_CTL 0x80 ; Control and status. RS off.
.equ ACIA_IO 0x81 ; Transmit. RS on.
.equ DIGIT_IO 0x00 ; digital I/O's port
jp init
@ -22,36 +23,24 @@ jp aciaInt
.equ STDIO_PUTC aciaPutC
.inc "stdio.asm"
; *** BASIC ***
; RAM space used in different routines for short term processing.
.equ SCRATCHPAD_SIZE 0x20
.equ SCRATCHPAD STDIO_RAMEND
; *** Shell ***
.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"
.inc "lib/args.asm"
.inc "lib/stdio.asm"
.equ SHELL_RAMSTART STDIO_RAMEND
.equ SHELL_EXTRA_CMD_COUNT 0
.inc "shell/main.asm"
init:
di
; setup stack
ld sp, RAMEND
ld hl, RAMEND
ld sp, hl
im 1
call aciaInit
call shellInit
ei
call basInit
jp basStart
jp shellLoop

View File

@ -2,9 +2,9 @@
; The RAM module is selected on A15, so it has the range 0x8000-0xffff
.equ RAMSTART 0x8000
.equ RAMEND 0xffff
.equ PGM_CODEADDR 0x9000
.equ ACIA_CTL 0x80 ; Control and status. RS off.
.equ ACIA_IO 0x81 ; Transmit. RS on.
.equ USER_CODE 0xa000
jp init ; 3 bytes
@ -45,32 +45,25 @@ jp aciaInt
.equ FS_HANDLE_COUNT 1
.inc "fs.asm"
; *** BASIC ***
; RAM space used in different routines for short term processing.
.equ SCRATCHPAD_SIZE 0x20
.equ SCRATCHPAD FS_RAMEND
; *** Shell ***
.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"
.inc "basic/blk.asm"
.inc "basic/sdc.asm"
.equ BFS_RAMSTART BUF_RAMEND
.inc "basic/fs.asm"
.equ BAS_RAMSTART BFS_RAMEND
.inc "basic/main.asm"
.inc "lib/args.asm"
.inc "lib/stdio.asm"
.equ SHELL_RAMSTART FS_RAMEND
.equ SHELL_EXTRA_CMD_COUNT 11
.inc "shell/main.asm"
.dw sdcInitializeCmd, sdcFlushCmd
.dw blkBselCmd, blkSeekCmd, blkLoadCmd, blkSaveCmd
.dw fsOnCmd, flsCmd, fnewCmd, fdelCmd, fopnCmd
.equ SDC_RAMSTART BAS_RAMEND
.inc "shell/blkdev.asm"
.inc "shell/fs.asm"
.equ PGM_RAMSTART SHELL_RAMEND
.inc "shell/pgm.asm"
.equ SDC_RAMSTART PGM_RAMEND
.equ SDC_PORT_CSHIGH 6
.equ SDC_PORT_CSLOW 5
.equ SDC_PORT_SPI 4
@ -78,30 +71,22 @@ jp aciaInt
init:
di
ld sp, RAMEND
; setup stack
ld hl, RAMEND
ld sp, hl
im 1
call aciaInit
call fsInit
call basInit
ld hl, basFindCmdExtra
ld (BAS_FINDHOOK), hl
call shellInit
ld hl, pgmShellHook
ld (SHELL_CMDHOOK), hl
xor a
ld de, BLOCKDEV_SEL
call blkSel
ei
jp basStart
basFindCmdExtra:
ld hl, basFSCmds
call basFindCmd
ret z
ld hl, basBLKCmds
call basFindCmd
ret z
ld hl, basSDCCmds
jp basFindCmd
jp shellLoop
; *** blkdev 2: file handle 0 ***

View File

@ -1,6 +1,4 @@
SHELLAPPS = zasm sdct memt at28w
APPTARGETS = ${SHELLAPPS:%=cfsin/%}
CFSTARGETS = $(APPTARGETS) cfsin/user.h
CFSTARGETS = $(addprefix cfsin/, zasm sdct memt at28w user.h)
BASE = ../../..
TOOLS = $(BASE)/tools
ZASM = $(TOOLS)/zasm.sh
@ -20,8 +18,17 @@ $(CFSPACK):
sdcard.cfs: $(CFSTARGETS) $(CFSPACK)
$(CFSPACK) cfsin > $@
$(APPTARGETS): $(ZASMBIN)
$(ZASM) $(KERNEL) $(APPS) user.h < $(APPS)/${@:cfsin/%=%}/glue.asm > $@
cfsin/zasm: $(ZASMBIN)
$(ZASM) $(KERNEL) $(APPS) user.h < $(APPS)/zasm/glue.asm > $@
cfsin/sdct: $(ZASMBIN)
$(ZASM) $(APPS) user.h < $(APPS)/sdct/glue.asm > $@
cfsin/memt: $(ZASMBIN)
$(ZASM) $(APPS) user.h < $(APPS)/memt/glue.asm > $@
cfsin/at28w: $(ZASMBIN)
$(ZASM) $(APPS) $(KERNEL) user.h < $(APPS)/at28w/glue.asm > $@
cfsin/user.h: user.h
cp $< $@

View File

@ -48,9 +48,9 @@ Compiling and running `hello.asm` is done very much like in
Collapse OS
> sdci
> fson
> fopen 0 hello.asm
> fopn 0 hello.asm
> fnew 1 dest
> fopen 1 dest
> fopn 1 dest
> zasm 1 2
> dest
Assembled from a RC2014
@ -94,7 +94,7 @@ Now you can write this into your card and boot Collapse OS:
> fson
> fopn 0 glue.asm
> fnew 10 dest
> fopen 1 dest
> fopn 1 dest
> zasm 1 2 # This takes a while. About 7 minutes.
> sdcf # success! sdcf flushes SD card buffers to the card.

View File

@ -1,4 +1,5 @@
.inc "user.h"
.org USER_CODE
ld hl, sAwesome
call printstr

View File

@ -1,11 +1,11 @@
; classic RC2014 setup (8K ROM + 32K RAM) and a stock Serial I/O module
; The RAM module is selected on A15, so it has the range 0x8000-0xffff
.equ RAMSTART 0x8000
; Kernel RAMEND last check: 0x98f3
; We allocate at least 0x100 bytes for the stack, which is why we have this
; kernel RAM usage, because of SDC, is a bit high and bring us almost to 0x8500
; We allocate at least 0x200 bytes for the stack, which is why we have this
; threshold.
.equ RAMEND 0x9a00
.equ USER_CODE RAMEND ; in sync with user.h
.equ RAMEND 0x8700
.equ PGM_CODEADDR RAMEND
.equ ACIA_CTL 0x80 ; Control and status. RS off.
.equ ACIA_IO 0x81 ; Transmit. RS on.
@ -15,25 +15,30 @@
jp strncmp
jp upcase
jp findchar
jp parseHex
jp parseHexPair
jp blkSel
jp blkSet
jp fsFindFN
jp fsOpen
jp fsGetB
jp parseArgs
jp printstr
jp _blkGetB
jp _blkPutB
jp _blkSeek
jp _blkTell
jp sdcGetB
jp sdcPutB
jp blkGetB
jp stdioPutC
jp printHexPair ; approaching 0x38...
; interrupt hook
.fill 0x38-$
jp aciaInt
; *** Jump Table (cont.) ***
jp sdcGetB
jp sdcPutB
jp blkGetB
.inc "err.h"
.inc "ascii.h"
.inc "blkdev.h"
@ -64,32 +69,25 @@ jp aciaInt
.equ FS_HANDLE_COUNT 2
.inc "fs.asm"
; *** BASIC ***
; RAM space used in different routines for short term processing.
.equ SCRATCHPAD_SIZE 0x20
.equ SCRATCHPAD FS_RAMEND
; *** Shell ***
.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"
.inc "basic/blk.asm"
.inc "basic/sdc.asm"
.equ BFS_RAMSTART BUF_RAMEND
.inc "basic/fs.asm"
.equ BAS_RAMSTART BFS_RAMEND
.inc "basic/main.asm"
.inc "lib/args.asm"
.inc "lib/stdio.asm"
.equ SHELL_RAMSTART FS_RAMEND
.equ SHELL_EXTRA_CMD_COUNT 11
.inc "shell/main.asm"
.dw sdcInitializeCmd, sdcFlushCmd
.dw blkBselCmd, blkSeekCmd, blkLoadCmd, blkSaveCmd
.dw fsOnCmd, flsCmd, fnewCmd, fdelCmd, fopnCmd
.equ SDC_RAMSTART BAS_RAMEND
.inc "shell/fs.asm"
.inc "shell/blkdev.asm"
.equ PGM_RAMSTART SHELL_RAMEND
.inc "shell/pgm.asm"
.equ SDC_RAMSTART PGM_RAMEND
.equ SDC_PORT_CSHIGH 6
.equ SDC_PORT_CSLOW 5
.equ SDC_PORT_SPI 4
@ -99,32 +97,22 @@ jp aciaInt
init:
di
ld sp, RAMEND
; setup stack
ld hl, RAMEND
ld sp, hl
im 1
call aciaInit
call fsInit
call basInit
ld hl, basFindCmdExtra
ld (BAS_FINDHOOK), hl
call shellInit
ld hl, pgmShellHook
ld (SHELL_CMDHOOK), hl
xor a
ld de, BLOCKDEV_SEL
call blkSel
ei
jp basStart
basFindCmdExtra:
ld hl, basFSCmds
call basFindCmd
ret z
ld hl, basBLKCmds
call basFindCmd
ret z
ld hl, basSDCCmds
call basFindCmd
ret z
jp basPgmHook
jp shellLoop
; *** blkdev 1: file handle 0 ***

View File

@ -1,20 +1,25 @@
.org 0x9a00
.org 0x8700
; *** JUMP TABLE ***
.equ strncmp 0x03
.equ upcase @+3
.equ findchar @+3
.equ parseHex @+3
.equ parseHexPair @+3
.equ blkSel @+3
.equ blkSet @+3
.equ fsFindFN @+3
.equ fsOpen @+3
.equ fsGetB @+3
.equ parseArgs @+3
.equ printstr @+3
.equ _blkGetB @+3
.equ _blkPutB @+3
.equ _blkSeek @+3
.equ _blkTell @+3
.equ sdcGetB @+3
.equ printHexPair @+3
; now at 0x36
.equ sdcGetB 0x3b
.equ sdcPutB @+3
.equ blkGetB @+3
.equ stdioPutC @+3

6
tools/.gitignore vendored
View File

@ -1,4 +1,4 @@
*.o
/memdump
/blkdump
/upload
/memdumpb
/blkdumpb
/uploadb

View File

@ -1,8 +1,8 @@
MEMDUMP_TGT = memdump
MEMDUMP_TGT = memdumpb
MEMDUMP_SRC = memdump.c
BLKDUMP_TGT = blkdump
BLKDUMP_TGT = blkdumpb
BLKDUMP_SRC = blkdump.c
UPLOAD_TGT = upload
UPLOAD_TGT = uploadb
UPLOAD_SRC = upload.c
TARGETS = $(MEMDUMP_TGT) $(BLKDUMP_TGT) $(UPLOAD_TGT)
OBJS = common.o

59
tools/blkdump.py Executable file
View File

@ -0,0 +1,59 @@
#!/usr/bin/env python3
# Read specified number of bytes in specified blkdev ID and spit it to stdout.
# The proper blkdev has to be selected and placed already.
import argparse
import os
import sys
import time
# Some place where it's safe to write 0xff bytes.
MEMPTR = '9000'
def sendcmd(fd, cmd):
# The serial link echoes back all typed characters and expects us to read
# them. We have to send each char one at a time.
print("Executing {}".format(cmd.decode()), file=sys.stderr)
for c in cmd:
os.write(fd, bytes([c]))
os.read(fd, 1)
os.write(fd, b'\n')
os.read(fd, 2) # sends back \r\n
def main():
parser = argparse.ArgumentParser()
parser.add_argument('device')
parser.add_argument('bytecount')
args = parser.parse_args()
try:
bytecount = int(args.bytecount, 16)
except ValueError:
print("bytecount has to be hexadecimal without prefix.")
return 1
fd = os.open(args.device, os.O_RDWR)
sendcmd(fd, 'mptr {}'.format(MEMPTR).encode())
os.read(fd, 9)
while bytecount > 0:
toread = min(bytecount, 0x100)
sendcmd(fd, 'load {:x}'.format(toread & 0xff).encode())
os.read(fd, 5)
sendcmd(fd, 'peek {:x}'.format(toread & 0xff).encode())
peek = b''
while len(peek) < toread * 2:
peek += os.read(fd, 1)
time.sleep(0.0001)
os.read(fd, 5)
while peek:
c = peek[:2]
sys.stdout.buffer.write(bytes([int(c, 16)]))
peek = peek[2:]
bytecount -= toread
os.close(fd)
return 0
if __name__ == '__main__':
sys.exit(main())

View File

@ -17,10 +17,3 @@ void sendcmd(int fd, char *cmd)
usleep(1000);
}
// Send a cmd and also read the "> " prompt
void sendcmdp(int fd, char *cmd)
{
char junk[2];
sendcmd(fd, cmd);
read(fd, &junk, 2);
}

View File

@ -1,3 +1,2 @@
void sendcmd(int fd, char *cmd);
void sendcmdp(int fd, char *cmd);

View File

@ -1,11 +1,11 @@
CFSPACK = ../cfspack/cfspack
TARGETS = shell/shell zasm/zasm runbin/runbin
TARGETS = shell/shell bshell/shell zasm/zasm runbin/runbin
KERNEL = ../../kernel
APPS = ../../apps
ZASMBIN = zasm/zasm
ZASMSH = ../zasm.sh
SHELLAPPS = zasm ed
SHELLTGTS = ${SHELLAPPS:%=cfsin/%}
SHELLTGTS = ${SHELLAPPS:S/^/cfsin\//}
CFSIN_CONTENTS = $(SHELLTGTS) cfsin/user.h
OBJS = emul.o libz80/libz80.o
@ -13,11 +13,17 @@ OBJS = emul.o libz80/libz80.o
all: $(TARGETS) $(CFSIN_CONTENTS)
# -o in sync with SHELL_CODE in shell/glue.asm
shell/shell.bin: shell/glue.asm $(ZASMBIN)
$(ZASMSH) $(KERNEL) shell/user.h $(APPS) < shell/glue.asm | tee $@ > /dev/null
shell/shell.bin: $(APPS)/shell/glue.asm $(ZASMBIN)
$(ZASMSH) -o 07 $(KERNEL) shell/user.h $(APPS) < $(APPS)/shell/glue.asm | tee $@ > /dev/null
shell/shell-bin.h: shell/shell.bin
./bin2c.sh KERNEL < shell/shell.bin | tee $@ > /dev/null
shell/kernel-bin.h: shell/glue.asm shell/shell.bin $(ZASMBIN)
$(ZASMSH) $(KERNEL) shell/shell.bin < shell/glue.asm | ./bin2c.sh KERNEL | tee $@ > /dev/null
bshell/shell.bin: bshell/glue.asm $(ZASMBIN)
$(ZASMSH) $(KERNEL) bshell/user.h $(APPS) < bshell/glue.asm | tee $@ > /dev/null
bshell/shell-bin.h: bshell/shell.bin
./bin2c.sh KERNEL < bshell/shell.bin | tee $@ > /dev/null
zasm/kernel-bin.h: zasm/kernel.bin
./bin2c.sh KERNEL < zasm/kernel.bin | tee $@ > /dev/null
@ -25,9 +31,12 @@ zasm/kernel-bin.h: zasm/kernel.bin
zasm/zasm-bin.h: zasm/zasm.bin
./bin2c.sh USERSPACE < zasm/zasm.bin | tee $@ > /dev/null
shell/shell: shell/shell.c $(OBJS) shell/shell-bin.h
shell/shell: shell/shell.c $(OBJS) shell/kernel-bin.h
$(CC) shell/shell.c $(OBJS) -o $@
bshell/shell: bshell/shell.c $(OBJS) bshell/shell-bin.h
$(CC) bshell/shell.c $(OBJS) -o $@
$(ZASMBIN): zasm/zasm.c $(OBJS) zasm/kernel-bin.h zasm/zasm-bin.h $(CFSPACK)
$(CC) zasm/zasm.c $(OBJS) -o $@
@ -46,7 +55,7 @@ $(CFSPACK):
# -o in sync with USER_CODE in shell/user.h
$(SHELLTGTS): $(ZASMBIN)
$(ZASMSH) -o 42 $(KERNEL) $(APPS) shell/user.h < $(APPS)/${@:cfsin/%=%}/glue.asm > $@
$(ZASMSH) -o 42 $(KERNEL) $(APPS) shell/user.h < $(APPS)/${@:T}/glue.asm > $@
cfsin/user.h: shell/user.h
cp shell/user.h $@

View File

@ -12,11 +12,11 @@ After that, you can run `make` and it builds all tools.
## shell
Running `shell/shell` runs the BASIC shell in an emulated machine. The goal of
this machine is not to simulate real hardware, but rather to serve as a
development platform. What we do here is we emulate the z80 part, the 64K
memory space and then hook some fake I/Os to stdin, stdout and a small storage
device that is suitable for Collapse OS's filesystem to run on.
Running `shell/shell` runs the shell in an emulated machine. The goal of this
machine is not to simulate real hardware, but rather to serve as a development
platform. What we do here is we emulate the z80 part, the 64K memory space and
then hook some fake I/Os to stdin, stdout and a small storage device that is
suitable for Collapse OS's filesystem to run on.
Through that, it becomes easier to develop userspace applications for Collapse
OS.
@ -25,6 +25,11 @@ We don't try to emulate real hardware to ease the development of device drivers
because so far, I don't see the advantage of emulation versus running code on
the real thing.
## bshell
The `basic` app is on its way to replace the shell. It is wrapped in the z80
emulator in the same way that the shell is and interacts with `cfsin` similarly.
## zasm
`zasm/zasm` is `apps/zasm` wrapped in an emulator. It is quite central to the

178
tools/emul/bshell/glue.asm Normal file
View File

@ -0,0 +1,178 @@
.inc "blkdev.h"
.inc "fs.h"
.inc "err.h"
.inc "ascii.h"
.equ RAMSTART 0x2000
.equ USER_CODE 0x4200
.equ STDIO_PORT 0x00
.equ FS_DATA_PORT 0x01
.equ FS_ADDR_PORT 0x02
jp init
; *** JUMP TABLE ***
jp strncmp
jp upcase
jp findchar
jp blkSelPtr
jp blkSel
jp blkSet
jp blkSeek
jp blkTell
jp blkGetB
jp blkPutB
jp fsFindFN
jp fsOpen
jp fsGetB
jp fsPutB
jp fsSetSize
jp fsOn
jp fsIter
jp fsAlloc
jp fsDel
jp fsHandle
jp printstr
jp printnstr
jp _blkGetB
jp _blkPutB
jp _blkSeek
jp _blkTell
jp printcrlf
jp stdioGetC
jp stdioPutC
jp stdioReadLine
.inc "core.asm"
.inc "str.asm"
.equ BLOCKDEV_RAMSTART RAMSTART
.equ BLOCKDEV_COUNT 4
.inc "blockdev.asm"
; List of devices
.dw fsdevGetB, fsdevPutB
.dw stdoutGetB, stdoutPutB
.dw stdinGetB, stdinPutB
.dw mmapGetB, mmapPutB
.equ MMAP_START 0xe000
.inc "mmap.asm"
.equ STDIO_RAMSTART BLOCKDEV_RAMEND
.equ STDIO_GETC emulGetC
.equ STDIO_PUTC emulPutC
.inc "stdio.asm"
.equ FS_RAMSTART STDIO_RAMEND
.equ FS_HANDLE_COUNT 2
.inc "fs.asm"
; *** BASIC ***
; RAM space used in different routines for short term processing.
.equ SCRATCHPAD_SIZE 0x20
.equ SCRATCHPAD FS_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 BFS_RAMSTART BUF_RAMEND
.inc "basic/fs.asm"
.inc "basic/blk.asm"
.equ BAS_RAMSTART BFS_RAMEND
.inc "basic/main.asm"
init:
di
; setup stack
ld sp, 0xffff
call fsInit
ld a, 0 ; select fsdev
ld de, BLOCKDEV_SEL
call blkSel
call fsOn
call basInit
ld hl, basFindCmdExtra
ld (BAS_FINDHOOK), hl
jp basStart
basFindCmdExtra:
ld hl, basFSCmds
call basFindCmd
ret z
ld hl, basBLKCmds
call basFindCmd
ret z
jp basPgmHook
emulGetC:
; Blocks until a char is returned
in a, (STDIO_PORT)
cp a ; ensure Z
ret
emulPutC:
out (STDIO_PORT), a
ret
fsdevGetB:
ld a, e
out (FS_ADDR_PORT), a
ld a, h
out (FS_ADDR_PORT), a
ld a, l
out (FS_ADDR_PORT), a
in a, (FS_ADDR_PORT)
or a
ret nz
in a, (FS_DATA_PORT)
cp a ; ensure Z
ret
fsdevPutB:
push af
ld a, e
out (FS_ADDR_PORT), a
ld a, h
out (FS_ADDR_PORT), a
ld a, l
out (FS_ADDR_PORT), a
in a, (FS_ADDR_PORT)
cp 2 ; only A > 1 means error
jr nc, .error ; A >= 2
pop af
out (FS_DATA_PORT), a
cp a ; ensure Z
ret
.error:
pop af
jp unsetZ ; returns
.equ STDOUT_HANDLE FS_HANDLES
stdoutGetB:
ld ix, STDOUT_HANDLE
jp fsGetB
stdoutPutB:
ld ix, STDOUT_HANDLE
jp fsPutB
.equ STDIN_HANDLE FS_HANDLES+FS_HANDLE_SIZE
stdinGetB:
ld ix, STDIN_HANDLE
jp fsGetB
stdinPutB:
ld ix, STDIN_HANDLE
jp fsPutB

191
tools/emul/bshell/shell.c Normal file
View File

@ -0,0 +1,191 @@
#include <stdint.h>
#include <stdio.h>
#include <termios.h>
#include "../emul.h"
#include "shell-bin.h"
/* Collapse OS shell with filesystem
*
* On startup, if "cfsin" directory exists, it packs it as a afke block device
* and loads it in. Upon halting, unpcks the contents of that block device in
* "cfsout" directory.
*
* Memory layout:
*
* 0x0000 - 0x3fff: ROM code from shell.asm
* 0x4000 - 0x4fff: Kernel memory
* 0x5000 - 0xffff: Userspace
*
* I/O Ports:
*
* 0 - stdin / stdout
* 1 - Filesystem blockdev data read/write. Reads and write data to the address
* previously selected through port 2
*/
//#define DEBUG
#define MAX_FSDEV_SIZE 0x20000
// in sync with glue.asm
#define RAMSTART 0x2000
#define STDIO_PORT 0x00
#define FS_DATA_PORT 0x01
// Controls what address (24bit) the data port returns. To select an address,
// this port has to be written to 3 times, starting with the MSB.
// Reading this port returns an out-of-bounds indicator. Meaning:
// 0 means addr is within bounds
// 1 means that we're equal to fsdev size (error for reading, ok for writing)
// 2 means more than fsdev size (always invalid)
// 3 means incomplete addr setting
#define FS_ADDR_PORT 0x02
static uint8_t fsdev[MAX_FSDEV_SIZE] = {0};
static uint32_t fsdev_size = 0;
static uint32_t fsdev_ptr = 0;
// 0 = idle, 1 = received MSB (of 24bit addr), 2 = received middle addr
static int fsdev_addr_lvl = 0;
static int running;
static uint8_t iord_stdio()
{
int c = getchar();
if (c == EOF) {
running = 0;
}
return (uint8_t)c;
}
static uint8_t iord_fsdata()
{
if (fsdev_addr_lvl != 0) {
fprintf(stderr, "Reading FSDEV in the middle of an addr op (%d)\n", fsdev_ptr);
return 0;
}
if (fsdev_ptr < fsdev_size) {
#ifdef DEBUG
fprintf(stderr, "Reading FSDEV at offset %d\n", fsdev_ptr);
#endif
return fsdev[fsdev_ptr];
} else {
// don't warn when ==, we're not out of bounds, just at the edge.
if (fsdev_ptr > fsdev_size) {
fprintf(stderr, "Out of bounds FSDEV read at %d\n", fsdev_ptr);
}
return 0;
}
}
static uint8_t iord_fsaddr()
{
if (fsdev_addr_lvl != 0) {
return 3;
} else if (fsdev_ptr > fsdev_size) {
fprintf(stderr, "Out of bounds FSDEV addr request at %d / %d\n", fsdev_ptr, fsdev_size);
return 2;
} else if (fsdev_ptr == fsdev_size) {
return 1;
} else {
return 0;
}
}
static void iowr_stdio(uint8_t val)
{
if (val == 0x04) { // CTRL+D
running = 0;
} else {
putchar(val);
}
}
static void iowr_fsdata(uint8_t val)
{
if (fsdev_addr_lvl != 0) {
fprintf(stderr, "Writing to FSDEV in the middle of an addr op (%d)\n", fsdev_ptr);
return;
}
if (fsdev_ptr < fsdev_size) {
#ifdef DEBUG
fprintf(stderr, "Writing to FSDEV (%d)\n", fsdev_ptr);
#endif
fsdev[fsdev_ptr] = val;
} else if ((fsdev_ptr == fsdev_size) && (fsdev_ptr < MAX_FSDEV_SIZE)) {
// We're at the end of fsdev, grow it
fsdev[fsdev_ptr] = val;
fsdev_size++;
#ifdef DEBUG
fprintf(stderr, "Growing FSDEV (%d)\n", fsdev_ptr);
#endif
} else {
fprintf(stderr, "Out of bounds FSDEV write at %d\n", fsdev_ptr);
}
}
static void iowr_fsaddr(uint8_t val)
{
if (fsdev_addr_lvl == 0) {
fsdev_ptr = val << 16;
fsdev_addr_lvl = 1;
} else if (fsdev_addr_lvl == 1) {
fsdev_ptr |= val << 8;
fsdev_addr_lvl = 2;
} else {
fsdev_ptr |= val;
fsdev_addr_lvl = 0;
}
}
int main()
{
// Setup fs blockdev
FILE *fp = popen("../cfspack/cfspack cfsin", "r");
if (fp != NULL) {
printf("Initializing filesystem\n");
int i = 0;
int c = fgetc(fp);
while (c != EOF) {
fsdev[i] = c & 0xff;
i++;
c = fgetc(fp);
}
fsdev_size = i;
pclose(fp);
} else {
printf("Can't initialize filesystem. Leaving blank.\n");
}
// Turn echo off: the shell takes care of its own echoing.
struct termios termInfo;
if (tcgetattr(0, &termInfo) == -1) {
printf("Can't setup terminal.\n");
return 1;
}
termInfo.c_lflag &= ~ECHO;
termInfo.c_lflag &= ~ICANON;
tcsetattr(0, TCSAFLUSH, &termInfo);
Machine *m = emul_init();
m->ramstart = RAMSTART;
m->iord[STDIO_PORT] = iord_stdio;
m->iord[FS_DATA_PORT] = iord_fsdata;
m->iord[FS_ADDR_PORT] = iord_fsaddr;
m->iowr[STDIO_PORT] = iowr_stdio;
m->iowr[FS_DATA_PORT] = iowr_fsdata;
m->iowr[FS_ADDR_PORT] = iowr_fsaddr;
// initialize memory
for (int i=0; i<sizeof(KERNEL); i++) {
m->mem[i] = KERNEL[i];
}
// Run!
running = 1;
while (running && emul_step());
printf("Done!\n");
termInfo.c_lflag |= ECHO;
termInfo.c_lflag |= ICANON;
tcsetattr(0, TCSAFLUSH, &termInfo);
emul_printdebug();
return 0;
}

35
tools/emul/bshell/user.h Normal file
View File

@ -0,0 +1,35 @@
.equ SHELL_RAMSTART 0x4100
.equ USER_CODE 0x4200 ; in sync with glue.asm
; *** JUMP TABLE ***
.equ strncmp 0x03
.equ upcase @+3
.equ findchar @+3
.equ blkSelPtr @+3
.equ blkSel @+3
.equ blkSet @+3
.equ blkSeek @+3
.equ blkTell @+3
.equ blkGetB @+3
.equ blkPutB @+3
.equ fsFindFN @+3
.equ fsOpen @+3
.equ fsGetB @+3
.equ fsPutB @+3
.equ fsSetSize @+3
.equ fsOn @+3
.equ fsIter @+3
.equ fsAlloc @+3
.equ fsDel @+3
.equ fsHandle @+3
.equ printstr @+3
.equ printnstr @+3
.equ _blkGetB @+3
.equ _blkPutB @+3
.equ _blkSeek @+3
.equ _blkTell @+3
.equ printcrlf @+3
.equ stdioGetC @+3
.equ stdioPutC @+3
.equ stdioReadLine @+3

View File

@ -1,9 +1,17 @@
; Last check:
; Kernel size: 0x619
; Kernel RAM usage: 0x66
; Shell size: 0x411
; Shell RAM usage: 0x11
.inc "blkdev.h"
.inc "fs.h"
.inc "err.h"
.inc "ascii.h"
.equ RAMSTART 0x2000
.equ USER_CODE 0x4200
.equ RAMSTART 0x4000
; 0x100 - 0x66 gives us a nice space for the stack.
.equ KERNEL_RAMEND 0x4100
.equ SHELL_CODE 0x0700
.equ STDIO_PORT 0x00
.equ FS_DATA_PORT 0x01
.equ FS_ADDR_PORT 0x02
@ -67,52 +75,16 @@
.equ FS_HANDLE_COUNT 2
.inc "fs.asm"
; *** BASIC ***
; RAM space used in different routines for short term processing.
.equ SCRATCHPAD_SIZE 0x20
.equ SCRATCHPAD FS_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 BFS_RAMSTART BUF_RAMEND
.inc "basic/fs.asm"
.inc "basic/blk.asm"
.equ BAS_RAMSTART BFS_RAMEND
.inc "basic/main.asm"
init:
di
; setup stack
ld sp, 0xffff
ld sp, KERNEL_RAMEND
call fsInit
ld a, 0 ; select fsdev
ld de, BLOCKDEV_SEL
call blkSel
call fsOn
call basInit
ld hl, basFindCmdExtra
ld (BAS_FINDHOOK), hl
jp basStart
basFindCmdExtra:
ld hl, basFSCmds
call basFindCmd
ret z
ld hl, basBLKCmds
call basFindCmd
ret z
jp basPgmHook
call SHELL_CODE
emulGetC:
; Blocks until a char is returned
@ -176,3 +148,6 @@ stdinGetB:
stdinPutB:
ld ix, STDIN_HANDLE
jp fsPutB
.fill SHELL_CODE-$
.bin "shell.bin"

View File

@ -2,7 +2,7 @@
#include <stdio.h>
#include <termios.h>
#include "../emul.h"
#include "shell-bin.h"
#include "kernel-bin.h"
/* Collapse OS shell with filesystem
*
@ -26,8 +26,8 @@
//#define DEBUG
#define MAX_FSDEV_SIZE 0x20000
// in sync with glue.asm
#define RAMSTART 0x2000
// in sync with shell.asm
#define RAMSTART 0x4000
#define STDIO_PORT 0x00
#define FS_DATA_PORT 0x01
// Controls what address (24bit) the data port returns. To select an address,

View File

@ -1,5 +1,5 @@
.equ SHELL_RAMSTART 0x4100
.equ USER_CODE 0x4200 ; in sync with glue.asm
.equ USER_CODE 0x4200
; *** JUMP TABLE ***
.equ strncmp 0x03

66
tools/memdump.py Executable file
View File

@ -0,0 +1,66 @@
#!/usr/bin/env python3
# Read specified number of bytes at specified memory address and dump it to
# stdout.
import argparse
import os
import sys
import time
def sendcmd(fd, cmd):
# The serial link echoes back all typed characters and expects us to read
# them. We have to send each char one at a time.
for c in cmd:
os.write(fd, bytes([c]))
os.read(fd, 1)
os.write(fd, b'\n')
os.read(fd, 2) # sends back \r\n
def main():
parser = argparse.ArgumentParser()
parser.add_argument('device')
parser.add_argument('memptr')
parser.add_argument('bytecount')
args = parser.parse_args()
try:
memptr = int('0x' + args.memptr, 0)
except ValueError:
print("memptr are has to be hexadecimal without prefix.")
return 1
try:
bytecount = int('0x' + args.bytecount, 0)
except ValueError:
print("bytecount are has to be hexadecimal without prefix.")
return 1
if memptr >= 0x10000:
print("memptr out of range.")
return 1
if bytecount + memptr >= 0x10000:
print("Bytecount too big.")
return 1
fd = os.open(args.device, os.O_RDWR)
while bytecount > 0:
sendcmd(fd, 'mptr {:04x}'.format(memptr).encode())
os.read(fd, 9)
toread = min(bytecount, 0xff)
sendcmd(fd, 'peek {:x}'.format(toread).encode())
peek = b''
while len(peek) < toread * 2:
peek += os.read(fd, 1)
time.sleep(0.0001)
os.read(fd, 5)
while peek:
c = peek[:2]
sys.stdout.buffer.write(bytes([int(c, 16)]))
peek = peek[2:]
memptr += toread
bytecount -= toread
os.close(fd)
return 0
if __name__ == '__main__':
sys.exit(main())

View File

@ -7,8 +7,6 @@
/* Push specified file to specified device **running the BASIC shell** and verify
* that the sent contents is correct.
*
* Note: running this will clear the current BASIC listing on the other side.
*/
int main(int argc, char **argv)
@ -26,11 +24,6 @@ int main(int argc, char **argv)
fseek(fp, 0, SEEK_END);
unsigned int bytecount = ftell(fp);
fprintf(stderr, "memptr: 0x%04x bytecount: 0x%04x.\n", memptr, bytecount);
if (!bytecount) {
// Nothing to read
fclose(fp);
return 0;
}
if (memptr+bytecount > 0xffff) {
fprintf(stderr, "memptr+bytecount out of range.\n");
fclose(fp);
@ -38,40 +31,35 @@ int main(int argc, char **argv)
}
rewind(fp);
int fd = open(argv[1], O_RDWR|O_NOCTTY);
char s[0x20];
char s[0x10];
sprintf(s, "m=0x%04x", memptr);
sendcmdp(fd, s);
sendcmd(fd, s);
read(fd, s, 2); // read prompt
// Send program
sendcmdp(fd, "clear");
sendcmdp(fd, "1 getc");
sendcmdp(fd, "2 puth a");
sendcmdp(fd, "3 poke m a");
sendcmdp(fd, "4 m=m+1");
sprintf(s, "5 if m<0x%04x goto 1", memptr+bytecount);
sendcmdp(fd, s);
sendcmd(fd, "run");
int returncode = 0;
while (fread(s, 1, 1, fp)) {
putchar('.');
fflush(stdout);
unsigned char c = s[0];
sendcmd(fd, "getc");
write(fd, &c, 1);
usleep(1000); // let it breathe
read(fd, s, 2); // read prompt
sendcmd(fd, "puth a");
read(fd, s, 2); // read hex pair
s[2] = 0; // null terminate
unsigned char c2 = strtol(s, NULL, 16);
read(fd, s, 2); // read prompt
if (c != c2) {
// mismatch!
unsigned int pos = ftell(fp);
fprintf(stderr, "Mismatch at byte %d! %d != %d.\n", pos, c, c2);
// we don't exit now because we need to "consume" our whole program.
returncode = 1;
return 1;
}
sendcmd(fd, "poke m a");
read(fd, s, 2); // read prompt
sendcmd(fd, "m=m+1");
read(fd, s, 2); // read prompt
}
printf("Done!\n");
fclose(fp);
return returncode;
return 0;
}

84
tools/upload.py Executable file
View File

@ -0,0 +1,84 @@
#!/usr/bin/env python3
# Push specified file to specified device and verify that the contents is
# correct by sending a "peek" command afterwards and check the output. Errors
# out if the contents isn't the same. The parameter passed to the "peek"
# command is the length of the uploaded file.
import argparse
import os
import sys
import time
def sendcmd(fd, cmd):
# The serial link echoes back all typed characters and expects us to read
# them. We have to send each char one at a time.
print("Executing {}".format(cmd.decode()))
for c in cmd:
os.write(fd, bytes([c]))
os.read(fd, 1)
os.write(fd, b'\n')
os.read(fd, 2) # sends back \r\n
def main():
parser = argparse.ArgumentParser()
parser.add_argument('device')
parser.add_argument('memptr')
parser.add_argument('filename')
args = parser.parse_args()
try:
memptr = int('0x' + args.memptr, 0)
except ValueError:
print("memptr are has to be hexadecimal without prefix.")
return 1
if memptr >= 0x10000:
print("memptr out of range.")
return 1
maxsize = 0x10000 - memptr
st = os.stat(args.filename)
if st.st_size > maxsize:
print("File too big. 0x{:04x} bytes max".format(maxsize))
return 1
fd = os.open(args.device, os.O_RDWR)
with open(args.filename, 'rb') as fp:
while True:
fcontents = fp.read(0xff)
if not fcontents:
break
print("Seeking...")
sendcmd(fd, 'mptr {:04x}'.format(memptr).encode())
os.read(fd, 9)
sendcmd(fd, 'poke {:x}'.format(len(fcontents)).encode())
print("Poking...")
for c in fcontents:
os.write(fd, bytes([c]))
# Let's give the machine a bit of time to breathe. We ain't in a
# hurry now, are we?
time.sleep(0.0001)
print("Poked")
os.read(fd, 5)
print("Peeking back...")
sendcmd(fd, 'peek {:x}'.format(len(fcontents)).encode())
peek = b''
while len(peek) < len(fcontents) * 2:
peek += os.read(fd, 1)
time.sleep(0.0001)
os.read(fd, 5)
print("Got {}".format(peek.decode()))
print("Comparing...")
for i, c in enumerate(fcontents):
hexfmt = '{:02X}'.format(c).encode()
if hexfmt != peek[:2]:
print("Mismatch at byte {}! {} != {}".format(i, peek[:2], hexfmt))
return 1
peek = peek[2:]
print("All good!")
memptr += len(fcontents)
print("Done!")
os.close(fd)
return 0
if __name__ == '__main__':
sys.exit(main())