mirror of
https://github.com/hsoft/collapseos.git
synced 2024-11-26 07:18:05 +11:00
Compare commits
6 Commits
b40b39f45c
...
888395d496
Author | SHA1 | Date | |
---|---|---|---|
|
888395d496 | ||
|
1710c865dc | ||
|
25d25d017c | ||
|
20151a97f8 | ||
|
880775ae69 | ||
|
7907687abf |
@ -10,7 +10,6 @@
|
|||||||
|
|
||||||
; *** Requirements ***
|
; *** Requirements ***
|
||||||
; blkGetB
|
; blkGetB
|
||||||
; parseArgs
|
|
||||||
;
|
;
|
||||||
; *** Includes ***
|
; *** Includes ***
|
||||||
|
|
||||||
@ -20,5 +19,9 @@
|
|||||||
|
|
||||||
jp at28wMain
|
jp at28wMain
|
||||||
|
|
||||||
|
.inc "core.asm"
|
||||||
|
.inc "lib/util.asm"
|
||||||
|
.inc "lib/parse.asm"
|
||||||
|
.inc "lib/args.asm"
|
||||||
.inc "at28w/main.asm"
|
.inc "at28w/main.asm"
|
||||||
USER_RAMSTART:
|
USER_RAMSTART:
|
||||||
|
@ -198,7 +198,7 @@ second.
|
|||||||
|
|
||||||
A freshly selected blkdev begins with its "pointer" at 0.
|
A freshly selected blkdev begins with its "pointer" at 0.
|
||||||
|
|
||||||
`seek <lsw> <msw>`: Moves the blkdev "pointer" to the specified offset. The
|
`bseek <lsw> <msw>`: Moves the blkdev "pointer" to the specified offset. The
|
||||||
first argument is the offset's least significant half (blkdev supports 32-bit
|
first argument is the offset's least significant half (blkdev supports 32-bit
|
||||||
addressing). Is is interpreted as an unsigned integer.
|
addressing). Is is interpreted as an unsigned integer.
|
||||||
|
|
||||||
|
@ -137,4 +137,6 @@ basFSCmds:
|
|||||||
.dw basFNEW
|
.dw basFNEW
|
||||||
.db "fdel", 0
|
.db "fdel", 0
|
||||||
.dw basFDEL
|
.dw basFDEL
|
||||||
|
.db "fson", 0
|
||||||
|
.dw fsOn
|
||||||
.db 0xff ; end of table
|
.db 0xff ; end of table
|
||||||
|
@ -124,13 +124,6 @@ basERR:
|
|||||||
; 2 - the beginning of the arg, with whitespace properly skipped.
|
; 2 - the beginning of the arg, with whitespace properly skipped.
|
||||||
;
|
;
|
||||||
; Commands are expected to set Z on success.
|
; 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:
|
basLIST:
|
||||||
call bufFirst
|
call bufFirst
|
||||||
jr nz, .end
|
jr nz, .end
|
||||||
@ -449,8 +442,6 @@ basR2Var: ; Just send reg to vars. Used in basPgmHook
|
|||||||
|
|
||||||
; direct only
|
; direct only
|
||||||
basCmds1:
|
basCmds1:
|
||||||
.db "bye", 0
|
|
||||||
.dw basBYE
|
|
||||||
.db "list", 0
|
.db "list", 0
|
||||||
.dw basLIST
|
.dw basLIST
|
||||||
.db "run", 0
|
.db "run", 0
|
||||||
|
14
apps/basic/sdc.asm
Normal file
14
apps/basic/sdc.asm
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
; 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,3 +1,6 @@
|
|||||||
|
; *** Requirements ***
|
||||||
|
; lib/parse
|
||||||
|
;
|
||||||
; *** Consts ***
|
; *** Consts ***
|
||||||
; maximum number of bytes to receive as args in all commands. Determines the
|
; maximum number of bytes to receive as args in all commands. Determines the
|
||||||
; size of the args variable.
|
; size of the args variable.
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
; *** Requirements ***
|
; *** Requirements ***
|
||||||
; stdioPutC
|
; stdioPutC
|
||||||
|
; divide
|
||||||
;
|
;
|
||||||
|
|
||||||
; Same as fmtDecimal, but DE is considered a signed number
|
; Same as fmtDecimal, but DE is considered a signed number
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
; *** Requirements ***
|
||||||
|
; lib/util
|
||||||
; *** Code ***
|
; *** Code ***
|
||||||
|
|
||||||
; Parse the hex char at A and extract it's 0-15 numerical value. Put the result
|
; Parse the hex char at A and extract it's 0-15 numerical value. Put the result
|
||||||
|
@ -8,12 +8,14 @@
|
|||||||
;
|
;
|
||||||
; *** Requirements ***
|
; *** Requirements ***
|
||||||
; printstr
|
; printstr
|
||||||
; printHexPair
|
; stdioPutC
|
||||||
;
|
;
|
||||||
; *** Includes ***
|
; *** Includes ***
|
||||||
|
|
||||||
.inc "user.h"
|
.inc "user.h"
|
||||||
jp memtMain
|
jp memtMain
|
||||||
|
|
||||||
|
.inc "lib/ari.asm"
|
||||||
|
.inc "lib/fmt.asm"
|
||||||
.inc "memt/main.asm"
|
.inc "memt/main.asm"
|
||||||
USER_RAMSTART:
|
USER_RAMSTART:
|
||||||
|
@ -14,7 +14,7 @@
|
|||||||
; sdcPutB
|
; sdcPutB
|
||||||
; sdcGetB
|
; sdcGetB
|
||||||
; printstr
|
; printstr
|
||||||
; printHexPair
|
; stdioPutC
|
||||||
;
|
;
|
||||||
; *** Includes ***
|
; *** Includes ***
|
||||||
|
|
||||||
@ -23,5 +23,7 @@
|
|||||||
|
|
||||||
jp sdctMain
|
jp sdctMain
|
||||||
|
|
||||||
|
.inc "lib/ari.asm"
|
||||||
|
.inc "lib/fmt.asm"
|
||||||
.inc "sdct/main.asm"
|
.inc "sdct/main.asm"
|
||||||
USER_RAMSTART:
|
USER_RAMSTART:
|
||||||
|
@ -1,98 +0,0 @@
|
|||||||
# 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>
|
|
@ -1,118 +0,0 @@
|
|||||||
; *** 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
|
|
||||||
|
|
@ -1,75 +0,0 @@
|
|||||||
; *** 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
|
|
@ -1,35 +0,0 @@
|
|||||||
; 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:
|
|
@ -1,22 +0,0 @@
|
|||||||
; 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:
|
|
||||||
|
|
@ -1,361 +0,0 @@
|
|||||||
; 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
|
|
||||||
|
|
@ -1,51 +0,0 @@
|
|||||||
; 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,8 +7,7 @@
|
|||||||
|
|
||||||
## User guide
|
## User guide
|
||||||
|
|
||||||
* [The shell](../apps/shell/README.md)
|
* [The shell](../apps/basic/README.md)
|
||||||
* [The BASIC shell](../apps/basic/README.md)
|
|
||||||
* [Load code in RAM and run it](load-run-code.md)
|
* [Load code in RAM and run it](load-run-code.md)
|
||||||
* [Using block devices](blockdev.md)
|
* [Using block devices](blockdev.md)
|
||||||
* [Using the filesystem](fs.md)
|
* [Using the filesystem](fs.md)
|
||||||
|
@ -42,71 +42,31 @@ they should try to adhere to the convention, that is:
|
|||||||
|
|
||||||
## Shell usage
|
## Shell usage
|
||||||
|
|
||||||
`blockdev.asm` supplies 4 shell commands that you can graft to your shell thus:
|
`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).
|
||||||
[...]
|
|
||||||
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
|
### Example
|
||||||
|
|
||||||
Let's try an example: You glue yourself a Collapse OS with ACIA as its first
|
Let's try an example: You glue yourself a Collapse OS with a mmap starting at
|
||||||
device and a mmap starting at `0xd000` as your second device. Here's what you
|
`0xe000` as your 4th device (like it is in the shell emulator). Here's what you
|
||||||
could do to copy memory around:
|
could do to copy memory around:
|
||||||
|
|
||||||
> mptr d000
|
> m=0xe000
|
||||||
D000
|
> 10 getc
|
||||||
> poke 4
|
> 20 poke m a
|
||||||
|
> 30 m=m+1
|
||||||
|
> 40 if m<0xe004 goto 10
|
||||||
|
> run
|
||||||
[enter "abcd"]
|
[enter "abcd"]
|
||||||
> peek 4
|
> bsel 3
|
||||||
61626364
|
> clear
|
||||||
> mptr c000
|
> 10 getb
|
||||||
C000
|
> 20 puth a
|
||||||
> peek 4
|
> run
|
||||||
[RAM garbage]
|
61> run
|
||||||
> bsel 1
|
62> run
|
||||||
> load 4
|
63> run
|
||||||
[returns immediately]
|
64> bseek 2
|
||||||
> peek 4
|
> run
|
||||||
61626364
|
63> run
|
||||||
> seek 00 0002
|
64>
|
||||||
> 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
|
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 `fnew`, mark files as deleted with `fdel` and, more importantly, open files
|
||||||
with `fopn`.
|
with `fopen`.
|
||||||
|
|
||||||
Opened files are accessed a independent block devices. It's the glue code that
|
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
|
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
|
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
|
one for a SD card and one for a file handle. You would mount the filesystem on
|
||||||
block device `1` (the SD card), then open a file on handle `0` with `fopn 0
|
block device `1` (the SD card), then open a file on handle `0` with `fopen 0
|
||||||
filename`. You would then do `bsel 2` to select your third block device which
|
filename`. You would then do `bsel 2` to select your third block device which
|
||||||
is mapped to the file you've just opened.
|
is mapped to the file you've just opened.
|
||||||
|
|
||||||
@ -55,13 +55,23 @@ so it's ready to use:
|
|||||||
> fls
|
> fls
|
||||||
foo
|
foo
|
||||||
bar
|
bar
|
||||||
> mptr 9000
|
> fopen 0 foo
|
||||||
9000
|
|
||||||
> fopn 0 foo
|
|
||||||
> bsel 2
|
> bsel 2
|
||||||
> load 5
|
> getb
|
||||||
> peek 5
|
> puth a
|
||||||
656C6C6F21
|
65
|
||||||
|
> getb
|
||||||
|
> puth a
|
||||||
|
6C
|
||||||
|
> getb
|
||||||
|
> puth a
|
||||||
|
6C
|
||||||
|
> getb
|
||||||
|
> puth a
|
||||||
|
6F
|
||||||
|
> getb
|
||||||
|
> puth a
|
||||||
|
21
|
||||||
> fdel bar
|
> fdel bar
|
||||||
> fls
|
> fls
|
||||||
foo
|
foo
|
||||||
|
@ -31,25 +31,46 @@ look like:
|
|||||||
.equ STDIO_PUTC aciaPutC
|
.equ STDIO_PUTC aciaPutC
|
||||||
.inc "stdio.asm"
|
.inc "stdio.asm"
|
||||||
|
|
||||||
.equ SHELL_RAMSTART STDIO_RAMEND
|
; *** BASIC ***
|
||||||
.equ SHELL_EXTRA_CMD_COUNT 0
|
|
||||||
.inc "shell.asm"
|
; 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:
|
init:
|
||||||
di
|
di
|
||||||
; setup stack
|
; setup stack
|
||||||
ld hl, RAMEND
|
ld sp, RAMEND
|
||||||
ld sp, hl
|
|
||||||
im 1
|
im 1
|
||||||
|
|
||||||
call aciaInit
|
call aciaInit
|
||||||
call shellInit
|
call basInit
|
||||||
ei
|
ei
|
||||||
jp shellLoop
|
jp basStart
|
||||||
|
|
||||||
Once this is written, building it is easy:
|
Once this is written, you can build it with `zasm`, which takes code from stdin
|
||||||
|
and spits binary to stdout. Because out code has includes, however, you need
|
||||||
|
to supply zasm with a block device containing a CFS containing the files to
|
||||||
|
include. This sounds, compicated, but it's managed by the `tools/zasm.sh` shell
|
||||||
|
script. The invocation would look like (it builds a CFS with the contents of
|
||||||
|
both `kernel/` and `apps/` folders):
|
||||||
|
|
||||||
zasm < glue.asm > collapseos.bin
|
tools/zasm.sh kernel/ apps/ < glue.asm > collapseos.bin
|
||||||
|
|
||||||
## Building zasm
|
## Building zasm
|
||||||
|
|
||||||
@ -122,19 +143,23 @@ label at the very end of its source file. This way, it becomes easy for the
|
|||||||
glue code to "graft" entries to the table. This approach, although simple and
|
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.
|
effective, only works for one table per part. But it's often enough.
|
||||||
|
|
||||||
For example, to define extra commands in the shell:
|
For example, to define block devices:
|
||||||
|
|
||||||
[...]
|
[...]
|
||||||
.equ SHELL_EXTRA_CMD_COUNT 2
|
.equ BLOCKDEV_COUNT 4
|
||||||
#include "shell.asm"
|
.inc "blockdev.asm"
|
||||||
.dw myCmd1, myCmd2
|
; List of devices
|
||||||
|
.dw fsdevGetB, fsdevPutB
|
||||||
|
.dw stdoutGetB, stdoutPutB
|
||||||
|
.dw stdinGetB, stdinPutB
|
||||||
|
.dw mmapGetB, mmapPutB
|
||||||
[...]
|
[...]
|
||||||
|
|
||||||
### Initialization
|
### Initialization
|
||||||
|
|
||||||
Then, finally, comes the `init` code. This can be pretty much anything really
|
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
|
and this much depends on the part you select. But if you want a shell, you will
|
||||||
usually end it with `shellLoop`, which never returns.
|
usually end it with `basStart`, which never returns.
|
||||||
|
|
||||||
[rc2014]: https://rc2014.co.uk/
|
[rc2014]: https://rc2014.co.uk/
|
||||||
[zasm]: ../tools/emul/README.md
|
[zasm]: ../tools/emul/README.md
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
Collapse OS likely runs from ROM code. If you need to fiddle with your machine
|
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
|
more deeply, you will want to send arbitrary code to it and run it. You can do
|
||||||
so with the shell's `poke` and `call` commands.
|
so with the shell's `poke` and `usr` commands.
|
||||||
|
|
||||||
For example, let's say that you want to run this simple code that you have
|
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
|
sitting on your "modern" machine and want to execute on your running Collapse OS
|
||||||
@ -13,16 +13,18 @@ machine:
|
|||||||
ld (0xa100), a
|
ld (0xa100), a
|
||||||
ret
|
ret
|
||||||
|
|
||||||
(we must always return at the end of code that we call with `call`). This will
|
(we must always return at the end of code that we call with `usr`). This will
|
||||||
increase a number at memory address `0xa100`. First, compile it:
|
increase a number at memory address `0xa100`. First, compile it:
|
||||||
|
|
||||||
zasm < tosend.asm > tosend.bin
|
zasm < tosend.asm > tosend.bin
|
||||||
|
|
||||||
Now, we'll send that code to address `0xa000`:
|
Now, we'll send that code to address `0xa000`:
|
||||||
|
|
||||||
> mptr a000
|
> m=0xa000
|
||||||
A000
|
> 10 getc
|
||||||
> poke 8 (resulting binary is 8 bytes long)
|
> 20 poke m a
|
||||||
|
> 30 if m<0xa008 goto 10
|
||||||
|
(resulting binary is 8 bytes long)
|
||||||
|
|
||||||
Now, at this point, it's a bit delicate. To pipe your binary to your serial
|
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
|
connection, you have to close `screen` with CTRL+A then `:quit` to free your
|
||||||
@ -35,46 +37,45 @@ but if the number of characters sent corresponds to what you gave `poke`, then
|
|||||||
Collapse OS will be waiting for a new command. Go ahead, verify that the
|
Collapse OS will be waiting for a new command. Go ahead, verify that the
|
||||||
transfer was successful with:
|
transfer was successful with:
|
||||||
|
|
||||||
peek 8
|
> peek 0a000
|
||||||
3A00A13C3200A1C9
|
> puth a
|
||||||
|
3A
|
||||||
|
> peek 0a007
|
||||||
|
> puth a
|
||||||
|
C9
|
||||||
|
|
||||||
Good! Now, we can try to run it. Before we run it, let's peek at the value at
|
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):
|
`0xa100` (being RAM, it's random):
|
||||||
|
|
||||||
> mptr a100
|
> peek 0xa100
|
||||||
A100
|
> puth a
|
||||||
> peek
|
|
||||||
61
|
61
|
||||||
|
|
||||||
So, we'll expect this to become `62` after we run the code. Let's go:
|
So, we'll expect this to become `62` after we run the code. Let's go:
|
||||||
|
|
||||||
> mptr a000
|
> usr 0xa100
|
||||||
A000
|
> peek 0xa100
|
||||||
> call 00 0000
|
> puth a
|
||||||
> mptr a100
|
|
||||||
A100
|
|
||||||
> peek
|
|
||||||
62
|
62
|
||||||
|
|
||||||
Success!
|
Success!
|
||||||
|
|
||||||
## The upload.py tool
|
## The upload tool
|
||||||
|
|
||||||
The serial connection is not always 100% reliable and a bad byte can slip in
|
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
|
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,
|
this bad behavior caused by my logic or by a bad serial upload?). Moreover,
|
||||||
sending contents bigger than `0xff` bytes can be a hassle.
|
sending contents manually can be a hassle.
|
||||||
|
|
||||||
To this end, there is a `upload.py` file in `tools/` that takes care of loading
|
To this end, there is a `upload` file in `tools/` (run `make` to build it) that
|
||||||
the file and verify the contents. So, instead of doing `mptr a000` followed by
|
takes care of loading the file and verify the contents. So, instead of doing
|
||||||
`poke 8` followed by your `cat` above, you would have done:
|
`getc` followed by `poke` followed by your `cat` above, you would have done:
|
||||||
|
|
||||||
./upload.py /dev/ttyUSB0 a000 tosend.bin
|
./upload /dev/ttyUSB0 a000 tosend.bin
|
||||||
|
|
||||||
This emits `mptr`, `poke` and `peek` commands and fail appropriately if the
|
This clears your basic listing and then types in a basic algorithm to receive
|
||||||
`peek` doesn't match sent contents. If the file is larger than `0xff` bytes,
|
and echo and pre-defined number of bytes. The `upload` tool then sends and read
|
||||||
repeat the process until the whole file was sent (file must fit in memory space
|
each byte, verifying that they're the same. Very handy.
|
||||||
though, of course). Very handy.
|
|
||||||
|
|
||||||
## Labels in RAM code
|
## Labels in RAM code
|
||||||
|
|
||||||
@ -126,16 +127,3 @@ You can then include that file in your "user" code, like this:
|
|||||||
|
|
||||||
If you load that code at `0xa000` and call it, it will print "Hello World!" by
|
If you load that code at `0xa000` and call it, it will print "Hello World!" by
|
||||||
using the `printstr` routine from `core.asm`.
|
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
|
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.
|
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,
|
We will open our source file in handle 0 and our dest file in handle 1. Then,
|
||||||
with the power of the `pgm` module, we'll autoload our newly compiled file and
|
with the power of the `fs` module's autoloader, we'll load our newly compiled
|
||||||
execute it!
|
file and execute it!
|
||||||
|
|
||||||
Collapse OS
|
Collapse OS
|
||||||
> fnew 1 dest ; create destination file
|
> fnew 1 dest ; create destination file
|
||||||
> fopn 0 hello.asm ; open source file in handle 0
|
> fopen 0 hello.asm ; open source file in handle 0
|
||||||
> fopn 1 dest ; open dest binary in handle 1
|
> fopen 1 dest ; open dest binary in handle 1
|
||||||
> zasm 1 2 ; assemble source file into binary file
|
> zasm 1 2 ; assemble source file into binary file
|
||||||
> dest ; call newly compiled file
|
> dest ; call newly compiled file
|
||||||
Assembled from the shell
|
Assembled from the shell
|
||||||
|
@ -101,8 +101,7 @@ fsInit:
|
|||||||
xor a
|
xor a
|
||||||
ld hl, FS_BLK
|
ld hl, FS_BLK
|
||||||
ld b, FS_RAMEND-FS_BLK
|
ld b, FS_RAMEND-FS_BLK
|
||||||
call fill
|
jp fill
|
||||||
ret
|
|
||||||
|
|
||||||
; *** Navigation ***
|
; *** Navigation ***
|
||||||
|
|
||||||
@ -286,7 +285,7 @@ fsFindFN:
|
|||||||
call fsNext
|
call fsNext
|
||||||
jr z, .loop
|
jr z, .loop
|
||||||
; End of the chain, not found
|
; End of the chain, not found
|
||||||
call unsetZ
|
; Z already unset
|
||||||
.end:
|
.end:
|
||||||
pop de
|
pop de
|
||||||
ret
|
ret
|
||||||
@ -311,7 +310,7 @@ fsIsValid:
|
|||||||
; Returns whether current block is deleted in Z flag.
|
; Returns whether current block is deleted in Z flag.
|
||||||
fsIsDeleted:
|
fsIsDeleted:
|
||||||
ld a, (FS_META+FS_META_FNAME_OFFSET)
|
ld a, (FS_META+FS_META_FNAME_OFFSET)
|
||||||
cp 0 ; Z flag is our answer
|
or a ; Z flag is our answer
|
||||||
ret
|
ret
|
||||||
|
|
||||||
; *** blkdev methods ***
|
; *** blkdev methods ***
|
||||||
@ -508,12 +507,9 @@ fsOn:
|
|||||||
jr .end
|
jr .end
|
||||||
.error:
|
.error:
|
||||||
; couldn't mount. Let's reset our variables.
|
; couldn't mount. Let's reset our variables.
|
||||||
xor a
|
call fsInit
|
||||||
ld b, FS_META-FS_BLK ; reset routine pointers and FS ptrs
|
|
||||||
ld hl, FS_BLK
|
|
||||||
call fill
|
|
||||||
|
|
||||||
ld a, FS_ERR_NO_FS
|
ld a, FS_ERR_NO_FS
|
||||||
|
or a ; unset Z
|
||||||
.end:
|
.end:
|
||||||
pop bc
|
pop bc
|
||||||
pop de
|
pop de
|
||||||
@ -524,18 +520,16 @@ fsOn:
|
|||||||
fsIsOn:
|
fsIsOn:
|
||||||
; check whether (FS_BLK) is zero
|
; check whether (FS_BLK) is zero
|
||||||
push hl
|
push hl
|
||||||
push de
|
|
||||||
ld hl, (FS_BLK)
|
ld hl, (FS_BLK)
|
||||||
ld de, 0
|
ld a, h
|
||||||
call cpHLDE
|
or l
|
||||||
jr nz, .mounted
|
jr nz, .mounted
|
||||||
; if equal, it means our FS is not mounted
|
; not mounted, unset Z
|
||||||
call unsetZ
|
inc a
|
||||||
jr .end
|
jr .end
|
||||||
.mounted:
|
.mounted:
|
||||||
cp a ; ensure Z
|
cp a ; ensure Z
|
||||||
.end:
|
.end:
|
||||||
pop de
|
|
||||||
pop hl
|
pop hl
|
||||||
ret
|
ret
|
||||||
|
|
||||||
@ -545,8 +539,6 @@ fsIsOn:
|
|||||||
; There are no error condition happening midway. If you get an error, then (IY)
|
; There are no error condition happening midway. If you get an error, then (IY)
|
||||||
; was never called.
|
; was never called.
|
||||||
fsIter:
|
fsIter:
|
||||||
call fsIsOn
|
|
||||||
ret nz
|
|
||||||
call fsBegin
|
call fsBegin
|
||||||
ret nz
|
ret nz
|
||||||
.loop:
|
.loop:
|
||||||
|
@ -621,10 +621,7 @@ sdcCRC:
|
|||||||
pop af
|
pop af
|
||||||
ret
|
ret
|
||||||
|
|
||||||
; *** shell cmds ***
|
|
||||||
|
|
||||||
sdcInitializeCmd:
|
sdcInitializeCmd:
|
||||||
.db "sdci", 0, 0, 0
|
|
||||||
call sdcInitialize
|
call sdcInitialize
|
||||||
ret nz
|
ret nz
|
||||||
call .setBlkSize
|
call .setBlkSize
|
||||||
@ -678,7 +675,6 @@ sdcInitializeCmd:
|
|||||||
|
|
||||||
; Flush the current SDC buffer if dirty
|
; Flush the current SDC buffer if dirty
|
||||||
sdcFlushCmd:
|
sdcFlushCmd:
|
||||||
.db "sdcf", 0, 0, 0
|
|
||||||
ld hl, SDC_BUFSEC1
|
ld hl, SDC_BUFSEC1
|
||||||
ld (SDC_BUFPTR), hl
|
ld (SDC_BUFPTR), hl
|
||||||
call sdcWriteBlk
|
call sdcWriteBlk
|
||||||
@ -724,14 +720,11 @@ _sdcPlaceBuf:
|
|||||||
sdcGetB:
|
sdcGetB:
|
||||||
push hl
|
push hl
|
||||||
call _sdcPlaceBuf
|
call _sdcPlaceBuf
|
||||||
jr nz, .error
|
jr nz, .end ; NZ already set
|
||||||
|
|
||||||
; This is it!
|
; This is it!
|
||||||
ld a, (hl)
|
ld a, (hl)
|
||||||
cp a ; ensure Z
|
cp a ; ensure Z
|
||||||
jr .end
|
|
||||||
.error:
|
|
||||||
call unsetZ
|
|
||||||
.end:
|
.end:
|
||||||
pop hl
|
pop hl
|
||||||
ret
|
ret
|
||||||
|
@ -27,11 +27,11 @@ are other recipes related to the RC2014:
|
|||||||
* [Accessing a MicroSD card](sdcard/README.md)
|
* [Accessing a MicroSD card](sdcard/README.md)
|
||||||
* [Assembling binaries](zasm/README.md)
|
* [Assembling binaries](zasm/README.md)
|
||||||
* [Interfacing a PS/2 keyboard](ps2/README.md)
|
* [Interfacing a PS/2 keyboard](ps2/README.md)
|
||||||
* [Replace shell by a BASIC interpreter](basic/README.md)
|
|
||||||
|
|
||||||
## Recipe
|
## Recipe
|
||||||
|
|
||||||
The goal is to have the shell running and accessible through the Serial I/O.
|
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
|
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
|
be many devices around made to write in flash and EEPROM modules, but being in
|
||||||
@ -44,6 +44,7 @@ device I use in this recipe.
|
|||||||
* [romwrite][romwrite] and its specified dependencies
|
* [romwrite][romwrite] and its specified dependencies
|
||||||
* [GNU screen][screen]
|
* [GNU screen][screen]
|
||||||
* A FTDI-to-TTL cable to connect to the Serial I/O module of the RC2014
|
* 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
|
### Write glue.asm
|
||||||
|
|
||||||
@ -62,15 +63,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.
|
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
|
Then, we init ACIA, shell, enable interrupt and give control of the main loop
|
||||||
to `shell.asm`.
|
to the BASIC shell.
|
||||||
|
|
||||||
What comes below is actual code include from parts we want to include in our
|
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.
|
OS. As you can see, we need to tell each module where to put their variables.
|
||||||
See `parts/README.md` for details.
|
See `apps/README.md` for details.
|
||||||
|
|
||||||
You can also see from the `SHELL_GETC` and `SHELL_PUTC` macros that the shell
|
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
|
is decoupled from the ACIA and can get its IO from anything. See comments in
|
||||||
`parts/README.md` for details.
|
`kernel/stdio.asm` for details.
|
||||||
|
|
||||||
### Build the image
|
### Build the image
|
||||||
|
|
||||||
@ -100,6 +101,20 @@ identify the tty bound to it (in my case, `/dev/ttyUSB0`). Then:
|
|||||||
screen /dev/ttyUSB0 115200
|
screen /dev/ttyUSB0 115200
|
||||||
|
|
||||||
Press the reset button on the RC2014 and you should see the Collapse OS prompt!
|
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
|
[rc2014]: https://rc2014.co.uk
|
||||||
[romwrite]: https://github.com/hsoft/romwrite
|
[romwrite]: https://github.com/hsoft/romwrite
|
||||||
|
@ -1,10 +0,0 @@
|
|||||||
TARGET = os.bin
|
|
||||||
ZASM = ../../../tools/zasm.sh
|
|
||||||
KERNEL = ../../../kernel
|
|
||||||
APPS = ../../../apps
|
|
||||||
|
|
||||||
.PHONY: all
|
|
||||||
all: $(TARGET)
|
|
||||||
$(TARGET): glue.asm
|
|
||||||
$(ZASM) $(KERNEL) $(APPS) < $< > $@
|
|
||||||
|
|
@ -1,46 +0,0 @@
|
|||||||
# 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?
|
|
@ -1,57 +0,0 @@
|
|||||||
.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,9 +1,8 @@
|
|||||||
; 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 RAMSTART 0x8000
|
||||||
.equ RAMEND 0xffff
|
.equ RAMEND 0xffff
|
||||||
.equ ACIA_CTL 0x80 ; Control and status. RS off.
|
.equ ACIA_CTL 0x80 ; Control and status. RS off.
|
||||||
.equ ACIA_IO 0x81 ; Transmit. RS on.
|
.equ ACIA_IO 0x81 ; Transmit. RS on.
|
||||||
|
.equ DIGIT_IO 0x00 ; digital I/O's port
|
||||||
|
|
||||||
jp init
|
jp init
|
||||||
|
|
||||||
@ -23,24 +22,36 @@ jp aciaInt
|
|||||||
.equ STDIO_PUTC aciaPutC
|
.equ STDIO_PUTC aciaPutC
|
||||||
.inc "stdio.asm"
|
.inc "stdio.asm"
|
||||||
|
|
||||||
; *** Shell ***
|
; *** 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/util.asm"
|
||||||
|
.inc "lib/ari.asm"
|
||||||
.inc "lib/parse.asm"
|
.inc "lib/parse.asm"
|
||||||
.inc "lib/args.asm"
|
.inc "lib/fmt.asm"
|
||||||
.inc "lib/stdio.asm"
|
.equ EXPR_PARSE parseLiteralOrVar
|
||||||
.equ SHELL_RAMSTART STDIO_RAMEND
|
.inc "lib/expr.asm"
|
||||||
.equ SHELL_EXTRA_CMD_COUNT 0
|
.inc "basic/util.asm"
|
||||||
.inc "shell/main.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:
|
init:
|
||||||
di
|
di
|
||||||
; setup stack
|
; setup stack
|
||||||
ld hl, RAMEND
|
ld sp, RAMEND
|
||||||
ld sp, hl
|
|
||||||
im 1
|
im 1
|
||||||
|
|
||||||
call aciaInit
|
call aciaInit
|
||||||
call shellInit
|
|
||||||
ei
|
ei
|
||||||
jp shellLoop
|
call basInit
|
||||||
|
jp basStart
|
||||||
|
|
||||||
|
|
||||||
|
@ -2,9 +2,9 @@
|
|||||||
; The RAM module is selected on A15, so it has the range 0x8000-0xffff
|
; The RAM module is selected on A15, so it has the range 0x8000-0xffff
|
||||||
.equ RAMSTART 0x8000
|
.equ RAMSTART 0x8000
|
||||||
.equ RAMEND 0xffff
|
.equ RAMEND 0xffff
|
||||||
.equ PGM_CODEADDR 0x9000
|
|
||||||
.equ ACIA_CTL 0x80 ; Control and status. RS off.
|
.equ ACIA_CTL 0x80 ; Control and status. RS off.
|
||||||
.equ ACIA_IO 0x81 ; Transmit. RS on.
|
.equ ACIA_IO 0x81 ; Transmit. RS on.
|
||||||
|
.equ USER_CODE 0xa000
|
||||||
|
|
||||||
jp init ; 3 bytes
|
jp init ; 3 bytes
|
||||||
|
|
||||||
@ -45,25 +45,32 @@ jp aciaInt
|
|||||||
.equ FS_HANDLE_COUNT 1
|
.equ FS_HANDLE_COUNT 1
|
||||||
.inc "fs.asm"
|
.inc "fs.asm"
|
||||||
|
|
||||||
; *** Shell ***
|
; *** 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/util.asm"
|
||||||
|
.inc "lib/ari.asm"
|
||||||
.inc "lib/parse.asm"
|
.inc "lib/parse.asm"
|
||||||
.inc "lib/args.asm"
|
.inc "lib/fmt.asm"
|
||||||
.inc "lib/stdio.asm"
|
.equ EXPR_PARSE parseLiteralOrVar
|
||||||
.equ SHELL_RAMSTART FS_RAMEND
|
.inc "lib/expr.asm"
|
||||||
.equ SHELL_EXTRA_CMD_COUNT 11
|
.inc "basic/util.asm"
|
||||||
.inc "shell/main.asm"
|
.inc "basic/parse.asm"
|
||||||
.dw sdcInitializeCmd, sdcFlushCmd
|
.inc "basic/tok.asm"
|
||||||
.dw blkBselCmd, blkSeekCmd, blkLoadCmd, blkSaveCmd
|
.equ VAR_RAMSTART SCRATCHPAD+SCRATCHPAD_SIZE
|
||||||
.dw fsOnCmd, flsCmd, fnewCmd, fdelCmd, fopnCmd
|
.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 "shell/blkdev.asm"
|
.equ SDC_RAMSTART BAS_RAMEND
|
||||||
.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_CSHIGH 6
|
||||||
.equ SDC_PORT_CSLOW 5
|
.equ SDC_PORT_CSLOW 5
|
||||||
.equ SDC_PORT_SPI 4
|
.equ SDC_PORT_SPI 4
|
||||||
@ -71,22 +78,30 @@ jp aciaInt
|
|||||||
|
|
||||||
init:
|
init:
|
||||||
di
|
di
|
||||||
; setup stack
|
ld sp, RAMEND
|
||||||
ld hl, RAMEND
|
|
||||||
ld sp, hl
|
|
||||||
im 1
|
im 1
|
||||||
call aciaInit
|
call aciaInit
|
||||||
call fsInit
|
call fsInit
|
||||||
call shellInit
|
call basInit
|
||||||
ld hl, pgmShellHook
|
ld hl, basFindCmdExtra
|
||||||
ld (SHELL_CMDHOOK), hl
|
ld (BAS_FINDHOOK), hl
|
||||||
|
|
||||||
xor a
|
xor a
|
||||||
ld de, BLOCKDEV_SEL
|
ld de, BLOCKDEV_SEL
|
||||||
call blkSel
|
call blkSel
|
||||||
|
|
||||||
ei
|
ei
|
||||||
jp shellLoop
|
jp basStart
|
||||||
|
|
||||||
|
basFindCmdExtra:
|
||||||
|
ld hl, basFSCmds
|
||||||
|
call basFindCmd
|
||||||
|
ret z
|
||||||
|
ld hl, basBLKCmds
|
||||||
|
call basFindCmd
|
||||||
|
ret z
|
||||||
|
ld hl, basSDCCmds
|
||||||
|
jp basFindCmd
|
||||||
|
|
||||||
; *** blkdev 2: file handle 0 ***
|
; *** blkdev 2: file handle 0 ***
|
||||||
|
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
CFSTARGETS = $(addprefix cfsin/, zasm sdct memt at28w user.h)
|
SHELLAPPS = zasm sdct memt at28w
|
||||||
|
APPTARGETS = ${SHELLAPPS:%=cfsin/%}
|
||||||
|
CFSTARGETS = $(APPTARGETS) cfsin/user.h
|
||||||
BASE = ../../..
|
BASE = ../../..
|
||||||
TOOLS = $(BASE)/tools
|
TOOLS = $(BASE)/tools
|
||||||
ZASM = $(TOOLS)/zasm.sh
|
ZASM = $(TOOLS)/zasm.sh
|
||||||
@ -18,17 +20,8 @@ $(CFSPACK):
|
|||||||
sdcard.cfs: $(CFSTARGETS) $(CFSPACK)
|
sdcard.cfs: $(CFSTARGETS) $(CFSPACK)
|
||||||
$(CFSPACK) cfsin > $@
|
$(CFSPACK) cfsin > $@
|
||||||
|
|
||||||
cfsin/zasm: $(ZASMBIN)
|
$(APPTARGETS): $(ZASMBIN)
|
||||||
$(ZASM) $(KERNEL) $(APPS) user.h < $(APPS)/zasm/glue.asm > $@
|
$(ZASM) $(KERNEL) $(APPS) user.h < $(APPS)/${@:cfsin/%=%}/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
|
cfsin/user.h: user.h
|
||||||
cp $< $@
|
cp $< $@
|
||||||
|
@ -48,9 +48,9 @@ Compiling and running `hello.asm` is done very much like in
|
|||||||
Collapse OS
|
Collapse OS
|
||||||
> sdci
|
> sdci
|
||||||
> fson
|
> fson
|
||||||
> fopn 0 hello.asm
|
> fopen 0 hello.asm
|
||||||
> fnew 1 dest
|
> fnew 1 dest
|
||||||
> fopn 1 dest
|
> fopen 1 dest
|
||||||
> zasm 1 2
|
> zasm 1 2
|
||||||
> dest
|
> dest
|
||||||
Assembled from a RC2014
|
Assembled from a RC2014
|
||||||
@ -94,7 +94,7 @@ Now you can write this into your card and boot Collapse OS:
|
|||||||
> fson
|
> fson
|
||||||
> fopn 0 glue.asm
|
> fopn 0 glue.asm
|
||||||
> fnew 10 dest
|
> fnew 10 dest
|
||||||
> fopn 1 dest
|
> fopen 1 dest
|
||||||
> zasm 1 2 # This takes a while. About 7 minutes.
|
> zasm 1 2 # This takes a while. About 7 minutes.
|
||||||
> sdcf # success! sdcf flushes SD card buffers to the card.
|
> sdcf # success! sdcf flushes SD card buffers to the card.
|
||||||
|
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
.inc "user.h"
|
.inc "user.h"
|
||||||
.org USER_CODE
|
|
||||||
|
|
||||||
ld hl, sAwesome
|
ld hl, sAwesome
|
||||||
call printstr
|
call printstr
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
; classic RC2014 setup (8K ROM + 32K RAM) and a stock Serial I/O module
|
; 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
|
; The RAM module is selected on A15, so it has the range 0x8000-0xffff
|
||||||
.equ RAMSTART 0x8000
|
.equ RAMSTART 0x8000
|
||||||
; kernel RAM usage, because of SDC, is a bit high and bring us almost to 0x8500
|
; Kernel RAMEND last check: 0x98f3
|
||||||
; We allocate at least 0x200 bytes for the stack, which is why we have this
|
; We allocate at least 0x100 bytes for the stack, which is why we have this
|
||||||
; threshold.
|
; threshold.
|
||||||
.equ RAMEND 0x8700
|
.equ RAMEND 0x9a00
|
||||||
.equ PGM_CODEADDR RAMEND
|
.equ USER_CODE RAMEND ; in sync with user.h
|
||||||
.equ ACIA_CTL 0x80 ; Control and status. RS off.
|
.equ ACIA_CTL 0x80 ; Control and status. RS off.
|
||||||
.equ ACIA_IO 0x81 ; Transmit. RS on.
|
.equ ACIA_IO 0x81 ; Transmit. RS on.
|
||||||
|
|
||||||
@ -15,30 +15,25 @@
|
|||||||
jp strncmp
|
jp strncmp
|
||||||
jp upcase
|
jp upcase
|
||||||
jp findchar
|
jp findchar
|
||||||
jp parseHex
|
|
||||||
jp parseHexPair
|
|
||||||
jp blkSel
|
jp blkSel
|
||||||
jp blkSet
|
jp blkSet
|
||||||
jp fsFindFN
|
jp fsFindFN
|
||||||
jp fsOpen
|
jp fsOpen
|
||||||
jp fsGetB
|
jp fsGetB
|
||||||
jp parseArgs
|
|
||||||
jp printstr
|
jp printstr
|
||||||
jp _blkGetB
|
jp _blkGetB
|
||||||
jp _blkPutB
|
jp _blkPutB
|
||||||
jp _blkSeek
|
jp _blkSeek
|
||||||
jp _blkTell
|
jp _blkTell
|
||||||
jp printHexPair ; approaching 0x38...
|
jp sdcGetB
|
||||||
|
jp sdcPutB
|
||||||
|
jp blkGetB
|
||||||
|
jp stdioPutC
|
||||||
|
|
||||||
; interrupt hook
|
; interrupt hook
|
||||||
.fill 0x38-$
|
.fill 0x38-$
|
||||||
jp aciaInt
|
jp aciaInt
|
||||||
|
|
||||||
; *** Jump Table (cont.) ***
|
|
||||||
jp sdcGetB
|
|
||||||
jp sdcPutB
|
|
||||||
jp blkGetB
|
|
||||||
|
|
||||||
.inc "err.h"
|
.inc "err.h"
|
||||||
.inc "ascii.h"
|
.inc "ascii.h"
|
||||||
.inc "blkdev.h"
|
.inc "blkdev.h"
|
||||||
@ -69,25 +64,32 @@ jp aciaInt
|
|||||||
.equ FS_HANDLE_COUNT 2
|
.equ FS_HANDLE_COUNT 2
|
||||||
.inc "fs.asm"
|
.inc "fs.asm"
|
||||||
|
|
||||||
; *** Shell ***
|
; *** 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/util.asm"
|
||||||
|
.inc "lib/ari.asm"
|
||||||
.inc "lib/parse.asm"
|
.inc "lib/parse.asm"
|
||||||
.inc "lib/args.asm"
|
.inc "lib/fmt.asm"
|
||||||
.inc "lib/stdio.asm"
|
.equ EXPR_PARSE parseLiteralOrVar
|
||||||
.equ SHELL_RAMSTART FS_RAMEND
|
.inc "lib/expr.asm"
|
||||||
.equ SHELL_EXTRA_CMD_COUNT 11
|
.inc "basic/util.asm"
|
||||||
.inc "shell/main.asm"
|
.inc "basic/parse.asm"
|
||||||
.dw sdcInitializeCmd, sdcFlushCmd
|
.inc "basic/tok.asm"
|
||||||
.dw blkBselCmd, blkSeekCmd, blkLoadCmd, blkSaveCmd
|
.equ VAR_RAMSTART SCRATCHPAD+SCRATCHPAD_SIZE
|
||||||
.dw fsOnCmd, flsCmd, fnewCmd, fdelCmd, fopnCmd
|
.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 "shell/fs.asm"
|
.equ SDC_RAMSTART BAS_RAMEND
|
||||||
.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_CSHIGH 6
|
||||||
.equ SDC_PORT_CSLOW 5
|
.equ SDC_PORT_CSLOW 5
|
||||||
.equ SDC_PORT_SPI 4
|
.equ SDC_PORT_SPI 4
|
||||||
@ -97,22 +99,32 @@ jp aciaInt
|
|||||||
|
|
||||||
init:
|
init:
|
||||||
di
|
di
|
||||||
; setup stack
|
ld sp, RAMEND
|
||||||
ld hl, RAMEND
|
|
||||||
ld sp, hl
|
|
||||||
im 1
|
im 1
|
||||||
call aciaInit
|
call aciaInit
|
||||||
call fsInit
|
call fsInit
|
||||||
call shellInit
|
call basInit
|
||||||
ld hl, pgmShellHook
|
ld hl, basFindCmdExtra
|
||||||
ld (SHELL_CMDHOOK), hl
|
ld (BAS_FINDHOOK), hl
|
||||||
|
|
||||||
xor a
|
xor a
|
||||||
ld de, BLOCKDEV_SEL
|
ld de, BLOCKDEV_SEL
|
||||||
call blkSel
|
call blkSel
|
||||||
|
|
||||||
ei
|
ei
|
||||||
jp shellLoop
|
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
|
||||||
|
|
||||||
; *** blkdev 1: file handle 0 ***
|
; *** blkdev 1: file handle 0 ***
|
||||||
|
|
||||||
|
@ -1,25 +1,20 @@
|
|||||||
.org 0x8700
|
.org 0x9a00
|
||||||
|
|
||||||
; *** JUMP TABLE ***
|
; *** JUMP TABLE ***
|
||||||
.equ strncmp 0x03
|
.equ strncmp 0x03
|
||||||
.equ upcase @+3
|
.equ upcase @+3
|
||||||
.equ findchar @+3
|
.equ findchar @+3
|
||||||
.equ parseHex @+3
|
|
||||||
.equ parseHexPair @+3
|
|
||||||
.equ blkSel @+3
|
.equ blkSel @+3
|
||||||
.equ blkSet @+3
|
.equ blkSet @+3
|
||||||
.equ fsFindFN @+3
|
.equ fsFindFN @+3
|
||||||
.equ fsOpen @+3
|
.equ fsOpen @+3
|
||||||
.equ fsGetB @+3
|
.equ fsGetB @+3
|
||||||
.equ parseArgs @+3
|
|
||||||
.equ printstr @+3
|
.equ printstr @+3
|
||||||
.equ _blkGetB @+3
|
.equ _blkGetB @+3
|
||||||
.equ _blkPutB @+3
|
.equ _blkPutB @+3
|
||||||
.equ _blkSeek @+3
|
.equ _blkSeek @+3
|
||||||
.equ _blkTell @+3
|
.equ _blkTell @+3
|
||||||
.equ printHexPair @+3
|
.equ sdcGetB @+3
|
||||||
; now at 0x36
|
|
||||||
|
|
||||||
.equ sdcGetB 0x3b
|
|
||||||
.equ sdcPutB @+3
|
.equ sdcPutB @+3
|
||||||
.equ blkGetB @+3
|
.equ blkGetB @+3
|
||||||
|
.equ stdioPutC @+3
|
||||||
|
6
tools/.gitignore
vendored
6
tools/.gitignore
vendored
@ -1,4 +1,4 @@
|
|||||||
*.o
|
*.o
|
||||||
/memdumpb
|
/memdump
|
||||||
/blkdumpb
|
/blkdump
|
||||||
/uploadb
|
/upload
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
MEMDUMP_TGT = memdumpb
|
MEMDUMP_TGT = memdump
|
||||||
MEMDUMP_SRC = memdump.c
|
MEMDUMP_SRC = memdump.c
|
||||||
BLKDUMP_TGT = blkdumpb
|
BLKDUMP_TGT = blkdump
|
||||||
BLKDUMP_SRC = blkdump.c
|
BLKDUMP_SRC = blkdump.c
|
||||||
UPLOAD_TGT = uploadb
|
UPLOAD_TGT = upload
|
||||||
UPLOAD_SRC = upload.c
|
UPLOAD_SRC = upload.c
|
||||||
TARGETS = $(MEMDUMP_TGT) $(BLKDUMP_TGT) $(UPLOAD_TGT)
|
TARGETS = $(MEMDUMP_TGT) $(BLKDUMP_TGT) $(UPLOAD_TGT)
|
||||||
OBJS = common.o
|
OBJS = common.o
|
||||||
|
@ -1,59 +0,0 @@
|
|||||||
#!/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,3 +17,10 @@ void sendcmd(int fd, char *cmd)
|
|||||||
usleep(1000);
|
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,2 +1,3 @@
|
|||||||
void sendcmd(int fd, char *cmd);
|
void sendcmd(int fd, char *cmd);
|
||||||
|
void sendcmdp(int fd, char *cmd);
|
||||||
|
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
CFSPACK = ../cfspack/cfspack
|
CFSPACK = ../cfspack/cfspack
|
||||||
TARGETS = shell/shell bshell/shell zasm/zasm runbin/runbin
|
TARGETS = shell/shell zasm/zasm runbin/runbin
|
||||||
KERNEL = ../../kernel
|
KERNEL = ../../kernel
|
||||||
APPS = ../../apps
|
APPS = ../../apps
|
||||||
ZASMBIN = zasm/zasm
|
ZASMBIN = zasm/zasm
|
||||||
ZASMSH = ../zasm.sh
|
ZASMSH = ../zasm.sh
|
||||||
SHELLAPPS = zasm ed
|
SHELLAPPS = zasm ed
|
||||||
SHELLTGTS = ${SHELLAPPS:S/^/cfsin\//}
|
SHELLTGTS = ${SHELLAPPS:%=cfsin/%}
|
||||||
CFSIN_CONTENTS = $(SHELLTGTS) cfsin/user.h
|
CFSIN_CONTENTS = $(SHELLTGTS) cfsin/user.h
|
||||||
OBJS = emul.o libz80/libz80.o
|
OBJS = emul.o libz80/libz80.o
|
||||||
|
|
||||||
@ -13,17 +13,11 @@ OBJS = emul.o libz80/libz80.o
|
|||||||
all: $(TARGETS) $(CFSIN_CONTENTS)
|
all: $(TARGETS) $(CFSIN_CONTENTS)
|
||||||
|
|
||||||
# -o in sync with SHELL_CODE in shell/glue.asm
|
# -o in sync with SHELL_CODE in shell/glue.asm
|
||||||
shell/shell.bin: $(APPS)/shell/glue.asm $(ZASMBIN)
|
shell/shell.bin: shell/glue.asm $(ZASMBIN)
|
||||||
$(ZASMSH) -o 07 $(KERNEL) shell/user.h $(APPS) < $(APPS)/shell/glue.asm | tee $@ > /dev/null
|
$(ZASMSH) $(KERNEL) shell/user.h $(APPS) < shell/glue.asm | tee $@ > /dev/null
|
||||||
|
|
||||||
shell/kernel-bin.h: shell/glue.asm shell/shell.bin $(ZASMBIN)
|
shell/shell-bin.h: shell/shell.bin
|
||||||
$(ZASMSH) $(KERNEL) shell/shell.bin < shell/glue.asm | ./bin2c.sh KERNEL | tee $@ > /dev/null
|
./bin2c.sh KERNEL < shell/shell.bin | 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
|
zasm/kernel-bin.h: zasm/kernel.bin
|
||||||
./bin2c.sh KERNEL < zasm/kernel.bin | tee $@ > /dev/null
|
./bin2c.sh KERNEL < zasm/kernel.bin | tee $@ > /dev/null
|
||||||
@ -31,12 +25,9 @@ zasm/kernel-bin.h: zasm/kernel.bin
|
|||||||
zasm/zasm-bin.h: zasm/zasm.bin
|
zasm/zasm-bin.h: zasm/zasm.bin
|
||||||
./bin2c.sh USERSPACE < zasm/zasm.bin | tee $@ > /dev/null
|
./bin2c.sh USERSPACE < zasm/zasm.bin | tee $@ > /dev/null
|
||||||
|
|
||||||
shell/shell: shell/shell.c $(OBJS) shell/kernel-bin.h
|
shell/shell: shell/shell.c $(OBJS) shell/shell-bin.h
|
||||||
$(CC) shell/shell.c $(OBJS) -o $@
|
$(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)
|
$(ZASMBIN): zasm/zasm.c $(OBJS) zasm/kernel-bin.h zasm/zasm-bin.h $(CFSPACK)
|
||||||
$(CC) zasm/zasm.c $(OBJS) -o $@
|
$(CC) zasm/zasm.c $(OBJS) -o $@
|
||||||
|
|
||||||
@ -55,7 +46,7 @@ $(CFSPACK):
|
|||||||
|
|
||||||
# -o in sync with USER_CODE in shell/user.h
|
# -o in sync with USER_CODE in shell/user.h
|
||||||
$(SHELLTGTS): $(ZASMBIN)
|
$(SHELLTGTS): $(ZASMBIN)
|
||||||
$(ZASMSH) -o 42 $(KERNEL) $(APPS) shell/user.h < $(APPS)/${@:T}/glue.asm > $@
|
$(ZASMSH) -o 42 $(KERNEL) $(APPS) shell/user.h < $(APPS)/${@:cfsin/%=%}/glue.asm > $@
|
||||||
|
|
||||||
cfsin/user.h: shell/user.h
|
cfsin/user.h: shell/user.h
|
||||||
cp shell/user.h $@
|
cp shell/user.h $@
|
||||||
|
@ -12,11 +12,11 @@ After that, you can run `make` and it builds all tools.
|
|||||||
|
|
||||||
## shell
|
## shell
|
||||||
|
|
||||||
Running `shell/shell` runs the shell in an emulated machine. The goal of this
|
Running `shell/shell` runs the BASIC shell in an emulated machine. The goal of
|
||||||
machine is not to simulate real hardware, but rather to serve as a development
|
this machine is not to simulate real hardware, but rather to serve as a
|
||||||
platform. What we do here is we emulate the z80 part, the 64K memory space and
|
development platform. What we do here is we emulate the z80 part, the 64K
|
||||||
then hook some fake I/Os to stdin, stdout and a small storage device that is
|
memory space and then hook some fake I/Os to stdin, stdout and a small storage
|
||||||
suitable for Collapse OS's filesystem to run on.
|
device that is suitable for Collapse OS's filesystem to run on.
|
||||||
|
|
||||||
Through that, it becomes easier to develop userspace applications for Collapse
|
Through that, it becomes easier to develop userspace applications for Collapse
|
||||||
OS.
|
OS.
|
||||||
@ -25,11 +25,6 @@ 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
|
because so far, I don't see the advantage of emulation versus running code on
|
||||||
the real thing.
|
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/zasm` is `apps/zasm` wrapped in an emulator. It is quite central to the
|
`zasm/zasm` is `apps/zasm` wrapped in an emulator. It is quite central to the
|
||||||
|
@ -1,178 +0,0 @@
|
|||||||
.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
|
|
@ -1,191 +0,0 @@
|
|||||||
#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;
|
|
||||||
}
|
|
@ -1,35 +0,0 @@
|
|||||||
.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,17 +1,9 @@
|
|||||||
; Last check:
|
|
||||||
; Kernel size: 0x619
|
|
||||||
; Kernel RAM usage: 0x66
|
|
||||||
; Shell size: 0x411
|
|
||||||
; Shell RAM usage: 0x11
|
|
||||||
|
|
||||||
.inc "blkdev.h"
|
.inc "blkdev.h"
|
||||||
.inc "fs.h"
|
.inc "fs.h"
|
||||||
.inc "err.h"
|
.inc "err.h"
|
||||||
.inc "ascii.h"
|
.inc "ascii.h"
|
||||||
.equ RAMSTART 0x4000
|
.equ RAMSTART 0x2000
|
||||||
; 0x100 - 0x66 gives us a nice space for the stack.
|
.equ USER_CODE 0x4200
|
||||||
.equ KERNEL_RAMEND 0x4100
|
|
||||||
.equ SHELL_CODE 0x0700
|
|
||||||
.equ STDIO_PORT 0x00
|
.equ STDIO_PORT 0x00
|
||||||
.equ FS_DATA_PORT 0x01
|
.equ FS_DATA_PORT 0x01
|
||||||
.equ FS_ADDR_PORT 0x02
|
.equ FS_ADDR_PORT 0x02
|
||||||
@ -75,16 +67,52 @@
|
|||||||
.equ FS_HANDLE_COUNT 2
|
.equ FS_HANDLE_COUNT 2
|
||||||
.inc "fs.asm"
|
.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:
|
init:
|
||||||
di
|
di
|
||||||
; setup stack
|
; setup stack
|
||||||
ld sp, KERNEL_RAMEND
|
ld sp, 0xffff
|
||||||
call fsInit
|
call fsInit
|
||||||
ld a, 0 ; select fsdev
|
ld a, 0 ; select fsdev
|
||||||
ld de, BLOCKDEV_SEL
|
ld de, BLOCKDEV_SEL
|
||||||
call blkSel
|
call blkSel
|
||||||
call fsOn
|
call fsOn
|
||||||
call SHELL_CODE
|
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:
|
emulGetC:
|
||||||
; Blocks until a char is returned
|
; Blocks until a char is returned
|
||||||
@ -148,6 +176,3 @@ stdinGetB:
|
|||||||
stdinPutB:
|
stdinPutB:
|
||||||
ld ix, STDIN_HANDLE
|
ld ix, STDIN_HANDLE
|
||||||
jp fsPutB
|
jp fsPutB
|
||||||
|
|
||||||
.fill SHELL_CODE-$
|
|
||||||
.bin "shell.bin"
|
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <termios.h>
|
#include <termios.h>
|
||||||
#include "../emul.h"
|
#include "../emul.h"
|
||||||
#include "kernel-bin.h"
|
#include "shell-bin.h"
|
||||||
|
|
||||||
/* Collapse OS shell with filesystem
|
/* Collapse OS shell with filesystem
|
||||||
*
|
*
|
||||||
@ -26,8 +26,8 @@
|
|||||||
//#define DEBUG
|
//#define DEBUG
|
||||||
#define MAX_FSDEV_SIZE 0x20000
|
#define MAX_FSDEV_SIZE 0x20000
|
||||||
|
|
||||||
// in sync with shell.asm
|
// in sync with glue.asm
|
||||||
#define RAMSTART 0x4000
|
#define RAMSTART 0x2000
|
||||||
#define STDIO_PORT 0x00
|
#define STDIO_PORT 0x00
|
||||||
#define FS_DATA_PORT 0x01
|
#define FS_DATA_PORT 0x01
|
||||||
// Controls what address (24bit) the data port returns. To select an address,
|
// Controls what address (24bit) the data port returns. To select an address,
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
.equ SHELL_RAMSTART 0x4100
|
.equ SHELL_RAMSTART 0x4100
|
||||||
.equ USER_CODE 0x4200
|
.equ USER_CODE 0x4200 ; in sync with glue.asm
|
||||||
|
|
||||||
; *** JUMP TABLE ***
|
; *** JUMP TABLE ***
|
||||||
.equ strncmp 0x03
|
.equ strncmp 0x03
|
||||||
|
@ -1,66 +0,0 @@
|
|||||||
#!/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,6 +7,8 @@
|
|||||||
|
|
||||||
/* Push specified file to specified device **running the BASIC shell** and verify
|
/* Push specified file to specified device **running the BASIC shell** and verify
|
||||||
* that the sent contents is correct.
|
* 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)
|
int main(int argc, char **argv)
|
||||||
@ -24,6 +26,11 @@ int main(int argc, char **argv)
|
|||||||
fseek(fp, 0, SEEK_END);
|
fseek(fp, 0, SEEK_END);
|
||||||
unsigned int bytecount = ftell(fp);
|
unsigned int bytecount = ftell(fp);
|
||||||
fprintf(stderr, "memptr: 0x%04x bytecount: 0x%04x.\n", memptr, bytecount);
|
fprintf(stderr, "memptr: 0x%04x bytecount: 0x%04x.\n", memptr, bytecount);
|
||||||
|
if (!bytecount) {
|
||||||
|
// Nothing to read
|
||||||
|
fclose(fp);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
if (memptr+bytecount > 0xffff) {
|
if (memptr+bytecount > 0xffff) {
|
||||||
fprintf(stderr, "memptr+bytecount out of range.\n");
|
fprintf(stderr, "memptr+bytecount out of range.\n");
|
||||||
fclose(fp);
|
fclose(fp);
|
||||||
@ -31,35 +38,40 @@ int main(int argc, char **argv)
|
|||||||
}
|
}
|
||||||
rewind(fp);
|
rewind(fp);
|
||||||
int fd = open(argv[1], O_RDWR|O_NOCTTY);
|
int fd = open(argv[1], O_RDWR|O_NOCTTY);
|
||||||
char s[0x10];
|
char s[0x20];
|
||||||
sprintf(s, "m=0x%04x", memptr);
|
sprintf(s, "m=0x%04x", memptr);
|
||||||
sendcmd(fd, s);
|
sendcmdp(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)) {
|
while (fread(s, 1, 1, fp)) {
|
||||||
putchar('.');
|
putchar('.');
|
||||||
fflush(stdout);
|
fflush(stdout);
|
||||||
unsigned char c = s[0];
|
unsigned char c = s[0];
|
||||||
sendcmd(fd, "getc");
|
|
||||||
write(fd, &c, 1);
|
write(fd, &c, 1);
|
||||||
read(fd, s, 2); // read prompt
|
usleep(1000); // let it breathe
|
||||||
sendcmd(fd, "puth a");
|
|
||||||
read(fd, s, 2); // read hex pair
|
read(fd, s, 2); // read hex pair
|
||||||
s[2] = 0; // null terminate
|
s[2] = 0; // null terminate
|
||||||
unsigned char c2 = strtol(s, NULL, 16);
|
unsigned char c2 = strtol(s, NULL, 16);
|
||||||
read(fd, s, 2); // read prompt
|
|
||||||
if (c != c2) {
|
if (c != c2) {
|
||||||
// mismatch!
|
// mismatch!
|
||||||
unsigned int pos = ftell(fp);
|
unsigned int pos = ftell(fp);
|
||||||
fprintf(stderr, "Mismatch at byte %d! %d != %d.\n", pos, c, c2);
|
fprintf(stderr, "Mismatch at byte %d! %d != %d.\n", pos, c, c2);
|
||||||
return 1;
|
// we don't exit now because we need to "consume" our whole program.
|
||||||
|
returncode = 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");
|
printf("Done!\n");
|
||||||
return 0;
|
fclose(fp);
|
||||||
|
return returncode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,84 +0,0 @@
|
|||||||
#!/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