parent
20151a97f8
commit
25d25d017c
@ -124,13 +124,6 @@ basERR:
|
||||
; 2 - the beginning of the arg, with whitespace properly skipped.
|
||||
;
|
||||
; Commands are expected to set Z on success.
|
||||
basBYE:
|
||||
; To quit the loop, let's return the stack to its initial value and
|
||||
; then return.
|
||||
xor a
|
||||
ld sp, (BAS_INITSP)
|
||||
ret
|
||||
|
||||
basLIST:
|
||||
call bufFirst
|
||||
jr nz, .end
|
||||
@ -449,8 +442,6 @@ basR2Var: ; Just send reg to vars. Used in basPgmHook
|
||||
|
||||
; direct only
|
||||
basCmds1:
|
||||
.db "bye", 0
|
||||
.dw basBYE
|
||||
.db "list", 0
|
||||
.dw basLIST
|
||||
.db "run", 0
|
||||
|
@ -1,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
|
@ -27,11 +27,11 @@ are other recipes related to the RC2014:
|
||||
* [Accessing a MicroSD card](sdcard/README.md)
|
||||
* [Assembling binaries](zasm/README.md)
|
||||
* [Interfacing a PS/2 keyboard](ps2/README.md)
|
||||
* [Replace shell by a BASIC interpreter](basic/README.md)
|
||||
|
||||
## Recipe
|
||||
|
||||
The goal is to have the shell running and accessible through the Serial I/O.
|
||||
To make things fun, we play with I/Os using RC2014's Digital I/O module.
|
||||
|
||||
You'll need specialized tools to write data to the AT28 EEPROM. There seems to
|
||||
be many devices around made to write in flash and EEPROM modules, but being in
|
||||
@ -44,6 +44,7 @@ device I use in this recipe.
|
||||
* [romwrite][romwrite] and its specified dependencies
|
||||
* [GNU screen][screen]
|
||||
* A FTDI-to-TTL cable to connect to the Serial I/O module of the RC2014
|
||||
* (Optional) RC2014's Digital I/O module
|
||||
|
||||
### Write glue.asm
|
||||
|
||||
@ -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.
|
||||
|
||||
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
|
||||
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
|
||||
is decoupled from the ACIA and can get its IO from anything. See
|
||||
`parts/README.md` for details.
|
||||
You can also see from the `STDIO_GETC` and `STDIO_PUTC` macros that the shell
|
||||
is decoupled from the ACIA and can get its IO from anything. See comments in
|
||||
`kernel/stdio.asm` for details.
|
||||
|
||||
### Build the image
|
||||
|
||||
@ -100,6 +101,20 @@ identify the tty bound to it (in my case, `/dev/ttyUSB0`). Then:
|
||||
screen /dev/ttyUSB0 115200
|
||||
|
||||
Press the reset button on the RC2014 and you should see the Collapse OS prompt!
|
||||
See documentation in `apps/basic/README.md` for details.
|
||||
|
||||
For now, let's have some fun with the Digital I/O module. Type this:
|
||||
|
||||
```
|
||||
> a=0
|
||||
> 10 out 0 a
|
||||
> 20 sleep 0xffff
|
||||
> 30 a=a+1
|
||||
> 40 goto 10
|
||||
> run
|
||||
```
|
||||
|
||||
You now have your Digital I/O lights doing a pretty dance, forever.
|
||||
|
||||
[rc2014]: https://rc2014.co.uk
|
||||
[romwrite]: https://github.com/hsoft/romwrite
|
||||
|
@ -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 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
|
||||
|
||||
@ -23,24 +22,36 @@ jp aciaInt
|
||||
.equ STDIO_PUTC aciaPutC
|
||||
.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/ari.asm"
|
||||
.inc "lib/parse.asm"
|
||||
.inc "lib/args.asm"
|
||||
.inc "lib/stdio.asm"
|
||||
.equ SHELL_RAMSTART STDIO_RAMEND
|
||||
.equ SHELL_EXTRA_CMD_COUNT 0
|
||||
.inc "shell/main.asm"
|
||||
.inc "lib/fmt.asm"
|
||||
.equ EXPR_PARSE parseLiteralOrVar
|
||||
.inc "lib/expr.asm"
|
||||
.inc "basic/util.asm"
|
||||
.inc "basic/parse.asm"
|
||||
.inc "basic/tok.asm"
|
||||
.equ VAR_RAMSTART SCRATCHPAD+SCRATCHPAD_SIZE
|
||||
.inc "basic/var.asm"
|
||||
.equ BUF_RAMSTART VAR_RAMEND
|
||||
.inc "basic/buf.asm"
|
||||
.equ BAS_RAMSTART BUF_RAMEND
|
||||
.inc "basic/main.asm"
|
||||
|
||||
init:
|
||||
di
|
||||
; setup stack
|
||||
ld hl, RAMEND
|
||||
ld sp, hl
|
||||
ld sp, RAMEND
|
||||
im 1
|
||||
|
||||
call aciaInit
|
||||
call shellInit
|
||||
ei
|
||||
jp shellLoop
|
||||
call basInit
|
||||
jp basStart
|
||||
|
||||
|
||||
|
@ -48,9 +48,9 @@ Compiling and running `hello.asm` is done very much like in
|
||||
Collapse OS
|
||||
> sdci
|
||||
> fson
|
||||
> fopn 0 hello.asm
|
||||
> fopen 0 hello.asm
|
||||
> fnew 1 dest
|
||||
> fopn 1 dest
|
||||
> fopen 1 dest
|
||||
> zasm 1 2
|
||||
> dest
|
||||
Assembled from a RC2014
|
||||
@ -94,7 +94,7 @@ Now you can write this into your card and boot Collapse OS:
|
||||
> fson
|
||||
> fopn 0 glue.asm
|
||||
> fnew 10 dest
|
||||
> fopn 1 dest
|
||||
> fopen 1 dest
|
||||
> zasm 1 2 # This takes a while. About 7 minutes.
|
||||
> sdcf # success! sdcf flushes SD card buffers to the card.
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
CFSPACK = ../cfspack/cfspack
|
||||
TARGETS = shell/shell bshell/shell zasm/zasm runbin/runbin
|
||||
TARGETS = shell/shell zasm/zasm runbin/runbin
|
||||
KERNEL = ../../kernel
|
||||
APPS = ../../apps
|
||||
ZASMBIN = zasm/zasm
|
||||
@ -13,17 +13,11 @@ OBJS = emul.o libz80/libz80.o
|
||||
all: $(TARGETS) $(CFSIN_CONTENTS)
|
||||
|
||||
# -o in sync with SHELL_CODE in shell/glue.asm
|
||||
shell/shell.bin: $(APPS)/shell/glue.asm $(ZASMBIN)
|
||||
$(ZASMSH) -o 07 $(KERNEL) shell/user.h $(APPS) < $(APPS)/shell/glue.asm | tee $@ > /dev/null
|
||||
shell/shell.bin: shell/glue.asm $(ZASMBIN)
|
||||
$(ZASMSH) $(KERNEL) shell/user.h $(APPS) < shell/glue.asm | tee $@ > /dev/null
|
||||
|
||||
shell/kernel-bin.h: shell/glue.asm shell/shell.bin $(ZASMBIN)
|
||||
$(ZASMSH) $(KERNEL) shell/shell.bin < shell/glue.asm | ./bin2c.sh KERNEL | tee $@ > /dev/null
|
||||
|
||||
bshell/shell.bin: bshell/glue.asm $(ZASMBIN)
|
||||
$(ZASMSH) $(KERNEL) bshell/user.h $(APPS) < bshell/glue.asm | tee $@ > /dev/null
|
||||
|
||||
bshell/shell-bin.h: bshell/shell.bin
|
||||
./bin2c.sh KERNEL < bshell/shell.bin | tee $@ > /dev/null
|
||||
shell/shell-bin.h: shell/shell.bin
|
||||
./bin2c.sh KERNEL < shell/shell.bin | tee $@ > /dev/null
|
||||
|
||||
zasm/kernel-bin.h: zasm/kernel.bin
|
||||
./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
|
||||
./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 $@
|
||||
|
||||
bshell/shell: bshell/shell.c $(OBJS) bshell/shell-bin.h
|
||||
$(CC) bshell/shell.c $(OBJS) -o $@
|
||||
|
||||
$(ZASMBIN): zasm/zasm.c $(OBJS) zasm/kernel-bin.h zasm/zasm-bin.h $(CFSPACK)
|
||||
$(CC) zasm/zasm.c $(OBJS) -o $@
|
||||
|
||||
|
@ -12,11 +12,11 @@ After that, you can run `make` and it builds all tools.
|
||||
|
||||
## shell
|
||||
|
||||
Running `shell/shell` runs the shell in an emulated machine. The goal of this
|
||||
machine is not to simulate real hardware, but rather to serve as a development
|
||||
platform. What we do here is we emulate the z80 part, the 64K memory space and
|
||||
then hook some fake I/Os to stdin, stdout and a small storage device that is
|
||||
suitable for Collapse OS's filesystem to run on.
|
||||
Running `shell/shell` runs the BASIC shell in an emulated machine. The goal of
|
||||
this machine is not to simulate real hardware, but rather to serve as a
|
||||
development platform. What we do here is we emulate the z80 part, the 64K
|
||||
memory space and then hook some fake I/Os to stdin, stdout and a small storage
|
||||
device that is suitable for Collapse OS's filesystem to run on.
|
||||
|
||||
Through that, it becomes easier to develop userspace applications for Collapse
|
||||
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
|
||||
the real thing.
|
||||
|
||||
## bshell
|
||||
|
||||
The `basic` app is on its way to replace the shell. It is wrapped in the z80
|
||||
emulator in the same way that the shell is and interacts with `cfsin` similarly.
|
||||
|
||||
## zasm
|
||||
|
||||
`zasm/zasm` is `apps/zasm` wrapped in an emulator. It is quite central to the
|
||||
|
@ -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 "fs.h"
|
||||
.inc "err.h"
|
||||
.inc "ascii.h"
|
||||
.equ RAMSTART 0x4000
|
||||
; 0x100 - 0x66 gives us a nice space for the stack.
|
||||
.equ KERNEL_RAMEND 0x4100
|
||||
.equ SHELL_CODE 0x0700
|
||||
.equ RAMSTART 0x2000
|
||||
.equ USER_CODE 0x4200
|
||||
.equ STDIO_PORT 0x00
|
||||
.equ FS_DATA_PORT 0x01
|
||||
.equ FS_ADDR_PORT 0x02
|
||||
@ -75,16 +67,52 @@
|
||||
.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, KERNEL_RAMEND
|
||||
ld sp, 0xffff
|
||||
call fsInit
|
||||
ld a, 0 ; select fsdev
|
||||
ld de, BLOCKDEV_SEL
|
||||
call blkSel
|
||||
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:
|
||||
; Blocks until a char is returned
|
||||
@ -148,6 +176,3 @@ stdinGetB:
|
||||
stdinPutB:
|
||||
ld ix, STDIN_HANDLE
|
||||
jp fsPutB
|
||||
|
||||
.fill SHELL_CODE-$
|
||||
.bin "shell.bin"
|
||||
|
@ -2,7 +2,7 @@
|
||||
#include <stdio.h>
|
||||
#include <termios.h>
|
||||
#include "../emul.h"
|
||||
#include "kernel-bin.h"
|
||||
#include "shell-bin.h"
|
||||
|
||||
/* Collapse OS shell with filesystem
|
||||
*
|
||||
@ -26,8 +26,8 @@
|
||||
//#define DEBUG
|
||||
#define MAX_FSDEV_SIZE 0x20000
|
||||
|
||||
// in sync with shell.asm
|
||||
#define RAMSTART 0x4000
|
||||
// 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,
|
||||
|
@ -1,5 +1,5 @@
|
||||
.equ SHELL_RAMSTART 0x4100
|
||||
.equ USER_CODE 0x4200
|
||||
.equ USER_CODE 0x4200 ; in sync with glue.asm
|
||||
|
||||
; *** JUMP TABLE ***
|
||||
.equ strncmp 0x03
|
||||
|
Loading…
Reference in New Issue
Block a user