@@ -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 | |||