mirror of
https://github.com/hsoft/collapseos.git
synced 2024-11-26 09:18:05 +11:00
Compare commits
No commits in common. "888395d496b52b6114e4aefafacb1451780d43a9" and "b40b39f45c4ef400d8ec41563dd28b68a1501ac5" have entirely different histories.
888395d496
...
b40b39f45c
@ -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:
|
||||
|
@ -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.
|
||||
|
||||
|
@ -137,6 +137,4 @@ basFSCmds:
|
||||
.dw basFNEW
|
||||
.db "fdel", 0
|
||||
.dw basFDEL
|
||||
.db "fson", 0
|
||||
.dw fsOn
|
||||
.db 0xff ; end of table
|
||||
|
@ -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
|
||||
|
@ -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
|
@ -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.
|
||||
|
@ -1,6 +1,5 @@
|
||||
; *** Requirements ***
|
||||
; stdioPutC
|
||||
; divide
|
||||
;
|
||||
|
||||
; Same as fmtDecimal, but DE is considered a signed number
|
||||
|
@ -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
|
||||
|
@ -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:
|
||||
|
@ -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
98
apps/shell/README.md
Normal 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
118
apps/shell/blkdev.asm
Normal 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
75
apps/shell/fs.asm
Normal 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
35
apps/shell/glue.asm
Normal 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
22
apps/shell/gluem.asm
Normal 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
361
apps/shell/main.asm
Normal 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
51
apps/shell/pgm.asm
Normal 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
|
@ -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)
|
||||
|
@ -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?
|
||||
|
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 `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
|
||||
|
@ -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
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
|
@ -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:
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
10
recipes/rc2014/basic/Makefile
Normal file
10
recipes/rc2014/basic/Makefile
Normal 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) < $< > $@
|
||||
|
46
recipes/rc2014/basic/README.md
Normal file
46
recipes/rc2014/basic/README.md
Normal 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?
|
57
recipes/rc2014/basic/glue.asm
Normal file
57
recipes/rc2014/basic/glue.asm
Normal 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
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
@ -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 ***
|
||||
|
||||
|
@ -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 $< $@
|
||||
|
@ -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.
|
||||
|
||||
|
@ -1,4 +1,5 @@
|
||||
.inc "user.h"
|
||||
.org USER_CODE
|
||||
|
||||
ld hl, sAwesome
|
||||
call printstr
|
||||
|
@ -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 ***
|
||||
|
||||
|
@ -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
6
tools/.gitignore
vendored
@ -1,4 +1,4 @@
|
||||
*.o
|
||||
/memdump
|
||||
/blkdump
|
||||
/upload
|
||||
/memdumpb
|
||||
/blkdumpb
|
||||
/uploadb
|
||||
|
@ -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
59
tools/blkdump.py
Executable 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())
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -1,3 +1,2 @@
|
||||
void sendcmd(int fd, char *cmd);
|
||||
void sendcmdp(int fd, char *cmd);
|
||||
|
||||
|
@ -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 $@
|
||||
|
@ -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
178
tools/emul/bshell/glue.asm
Normal 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
191
tools/emul/bshell/shell.c
Normal 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
35
tools/emul/bshell/user.h
Normal 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
|
||||
|
@ -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"
|
||||
|
@ -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,
|
||||
|
@ -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
66
tools/memdump.py
Executable 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())
|
||||
|
@ -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
84
tools/upload.py
Executable 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())
|
Loading…
Reference in New Issue
Block a user