@@ -7,3 +7,14 @@ be run. | |||||
That doesn't mean that you can't include that code in your kernel though, but | That doesn't mean that you can't include that code in your kernel though, but | ||||
you will typically not want to do that. | you will typically not want to do that. | ||||
## Userspace convention | |||||
We execute a userspace application by calling the address it's loaded into. This | |||||
means: a userspace application is expected to return. | |||||
Whatever calls the userspace app (usually, it will be the shell), should set | |||||
HL to a pointer to unparsed arguments in string form, null terminated. | |||||
The userspace application is expected to set A on return. 0 means success, | |||||
non-zero means error. |
@@ -37,11 +37,13 @@ | |||||
; fsSeek | ; fsSeek | ||||
; fsTell | ; fsTell | ||||
; cpHLDE | ; cpHLDE | ||||
; parseArgs | |||||
; FS_HANDLE_SIZE | ; FS_HANDLE_SIZE | ||||
; *** Variables *** | ; *** Variables *** | ||||
#include "user.h" | #include "user.h" | ||||
#include "err.h" | |||||
.org USER_CODE | .org USER_CODE | ||||
jp zasmMain | jp zasmMain | ||||
@@ -15,15 +15,28 @@ | |||||
.equ ZASM_ORG ZASM_CTX_PC+2 | .equ ZASM_ORG ZASM_CTX_PC+2 | ||||
.equ ZASM_RAMEND ZASM_ORG+2 | .equ ZASM_RAMEND ZASM_ORG+2 | ||||
; Read file through blockdev ID in H and outputs its upcodes through blockdev | |||||
; ID in L. HL is set to the last lineno to be read. | |||||
; Takes 2 byte arguments, blkdev in and blkdev out, expressed as IDs. | |||||
; Read file through blkdev in and outputs its upcodes through blkdev out. | |||||
; HL is set to the last lineno to be read. | |||||
; Sets Z on success, unset on error. On error, A contains an error code (ERR_*) | ; Sets Z on success, unset on error. On error, A contains an error code (ERR_*) | ||||
zasmMain: | zasmMain: | ||||
; Parse args. HL points to string already | |||||
; We don't allocate memory just to hold this. Because this happens | |||||
; before initialization, we don't really care where those args are | |||||
; parsed. | |||||
ld de, .argspecs | |||||
ld ix, ZASM_RAMSTART | |||||
call parseArgs | |||||
ld a, SHELL_ERR_BAD_ARGS | |||||
ret nz | |||||
; HL now points to parsed args | |||||
; Init I/O | ; Init I/O | ||||
ld a, h | |||||
ld a, (ZASM_RAMSTART) ; blkdev in ID | |||||
ld de, IO_IN_GETC | ld de, IO_IN_GETC | ||||
call blkSel | call blkSel | ||||
ld a, l | |||||
inc hl | |||||
ld a, (ZASM_RAMSTART+1) ; blkdev out ID | |||||
ld de, IO_OUT_GETC | ld de, IO_OUT_GETC | ||||
call blkSel | call blkSel | ||||
@@ -47,6 +60,9 @@ zasmMain: | |||||
.end: | .end: | ||||
jp ioLineNo ; --> HL, --> DE, returns | jp ioLineNo ; --> HL, --> DE, returns | ||||
.argspecs: | |||||
.db 0b001, 0b001, 0 | |||||
; Sets Z according to whether we're in first pass. | ; Sets Z according to whether we're in first pass. | ||||
zasmIsFirstPass: | zasmIsFirstPass: | ||||
ld a, (ZASM_FIRST_PASS) | ld a, (ZASM_FIRST_PASS) | ||||
@@ -0,0 +1,11 @@ | |||||
; Error codes used throughout the kernel | |||||
; The command that was type isn't known to the shell | |||||
.equ SHELL_ERR_UNKNOWN_CMD 0x01 | |||||
; Arguments for the command weren't properly formatted | |||||
.equ SHELL_ERR_BAD_ARGS 0x02 | |||||
; IO routines (GetC, PutC) returned an error in a load/save command | |||||
.equ SHELL_ERR_IO_ERROR 0x05 | |||||
@@ -1,3 +1,10 @@ | |||||
; *** Consts *** | |||||
; maximum number of bytes to receive as args in all commands. Determines the | |||||
; size of the args variable. | |||||
.equ PARSE_ARG_MAXCOUNT 3 | |||||
; *** 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 | ||||
; in A. | ; in A. | ||||
; | ; | ||||
@@ -61,3 +68,101 @@ parseHexPair: | |||||
.end: | .end: | ||||
pop bc | pop bc | ||||
ret | ret | ||||
; TODO: make parseArgs not expect a leading space. | |||||
; Parse arguments at (HL) with specifiers at (DE) into (IX). | |||||
; | |||||
; Args specifiers are a series of flag for each arg: | |||||
; Bit 0 - arg present: if unset, we stop parsing there | |||||
; Bit 1 - is word: this arg is a word rather than a byte. Because our | |||||
; destination are bytes anyway, this doesn't change much except | |||||
; for whether we expect a space between the hex pairs. If set, | |||||
; you still need to have a specifier for the second part of | |||||
; the multibyte. | |||||
; Bit 2 - optional: If set and not present during parsing, we don't error out | |||||
; and write zero | |||||
; | |||||
; Bit 3 - String argument: If set, this argument is a string. A pointer to the | |||||
; read string, null terminated (max 0x20 chars) will | |||||
; be placed in the next two bytes. This has to be the | |||||
; last argument of the list and it stops parsing. | |||||
; Sets A to nonzero if there was an error during parsing, zero otherwise. | |||||
parseArgs: | |||||
push bc | |||||
push de | |||||
push hl | |||||
push ix | |||||
ld b, PARSE_ARG_MAXCOUNT | |||||
xor c | |||||
.loop: | |||||
; init the arg value to a default 0 | |||||
xor a | |||||
ld (ix), a | |||||
ld a, (hl) | |||||
; is this the end of the line? | |||||
or a ; cp 0 | |||||
jr z, .endofargs | |||||
; do we have a proper space char? | |||||
cp ' ' | |||||
jr z, .hasspace ; We're fine | |||||
; is our previous arg a multibyte? (argspec still in C) | |||||
bit 1, c | |||||
jr z, .error ; bit not set? error | |||||
dec hl ; offset the "inc hl" below | |||||
.hasspace: | |||||
; Get the specs | |||||
ld a, (de) | |||||
bit 0, a ; do we have an arg? | |||||
jr z, .error ; not set? then we have too many args | |||||
ld c, a ; save the specs for the next loop | |||||
inc hl ; (hl) points to a space, go next | |||||
bit 3, a ; is our arg a string? | |||||
jr z, .notAString | |||||
; our arg is a string. Let's place HL in our next two bytes and call | |||||
; it a day. Little endian, remember | |||||
ld (ix), l | |||||
ld (ix+1), h | |||||
jr .success ; directly to success: skip endofargs checks | |||||
.notAString: | |||||
call parseHexPair | |||||
jr c, .error | |||||
; we have a good arg and we need to write A in (IX). | |||||
ld (ix), a | |||||
; Good! increase counters | |||||
inc de | |||||
inc ix | |||||
inc hl ; get to following char (generally a space) | |||||
djnz .loop | |||||
; If we get here, it means that our next char *has* to be a null char | |||||
ld a, (hl) | |||||
cp 0 | |||||
jr z, .success ; zero? great! | |||||
jr .error | |||||
.endofargs: | |||||
; We encountered our null char. Let's verify that we either have no | |||||
; more args or that they are optional | |||||
ld a, (de) | |||||
cp 0 | |||||
jr z, .success ; no arg? success | |||||
bit 2, a | |||||
jr nz, .success ; if set, arg is optional. success | |||||
jr .error | |||||
.success: | |||||
xor a | |||||
jr .end | |||||
.error: | |||||
inc a | |||||
.end: | |||||
pop ix | |||||
pop hl | |||||
pop de | |||||
pop bc | |||||
ret |
@@ -20,6 +20,8 @@ | |||||
; hexadecimal form, without prefix or suffix. | ; hexadecimal form, without prefix or suffix. | ||||
; *** REQUIREMENTS *** | ; *** REQUIREMENTS *** | ||||
; err | |||||
; core | |||||
; parse | ; parse | ||||
; stdio | ; stdio | ||||
@@ -33,19 +35,6 @@ | |||||
; number of entries in shellCmdTbl | ; number of entries in shellCmdTbl | ||||
.equ SHELL_CMD_COUNT 6+SHELL_EXTRA_CMD_COUNT | .equ SHELL_CMD_COUNT 6+SHELL_EXTRA_CMD_COUNT | ||||
; maximum number of bytes to receive as args in all commands. Determines the | |||||
; size of the args variable. | |||||
.equ SHELL_CMD_ARGS_MAXSIZE 3 | |||||
; The command that was type isn't known to the shell | |||||
.equ SHELL_ERR_UNKNOWN_CMD 0x01 | |||||
; Arguments for the command weren't properly formatted | |||||
.equ SHELL_ERR_BAD_ARGS 0x02 | |||||
; IO routines (GetC, PutC) returned an error in a load/save command | |||||
.equ SHELL_ERR_IO_ERROR 0x05 | |||||
; Size of the shell command buffer. If a typed command reaches this size, the | ; Size of the shell command buffer. If a typed command reaches this size, the | ||||
; command is flushed immediately (same as pressing return). | ; command is flushed immediately (same as pressing return). | ||||
.equ SHELL_BUFSIZE 0x20 | .equ SHELL_BUFSIZE 0x20 | ||||
@@ -62,7 +51,7 @@ | |||||
; Command buffer. We read types chars into this buffer until return is pressed | ; Command buffer. We read types chars into this buffer until return is pressed | ||||
; This buffer is null-terminated and we don't keep an index around: we look | ; This buffer is null-terminated and we don't keep an index around: we look | ||||
; for the null-termination every time we write to it. Simpler that way. | ; for the null-termination every time we write to it. Simpler that way. | ||||
.equ SHELL_BUF SHELL_CMD_ARGS+SHELL_CMD_ARGS_MAXSIZE | |||||
.equ SHELL_BUF SHELL_CMD_ARGS+PARSE_ARG_MAXCOUNT | |||||
; Pointer to a hook to call when a cmd name isn't found | ; Pointer to a hook to call when a cmd name isn't found | ||||
.equ SHELL_CMDHOOK SHELL_BUF+SHELL_BUFSIZE | .equ SHELL_CMDHOOK SHELL_BUF+SHELL_BUFSIZE | ||||
@@ -185,14 +174,15 @@ shellParse: | |||||
call addDE | call addDE | ||||
; We're ready to parse args | ; We're ready to parse args | ||||
call shellParseArgs | |||||
ld ix, SHELL_CMD_ARGS | |||||
call parseArgs | |||||
or a ; cp 0 | or a ; cp 0 | ||||
jr nz, .parseerror | jr nz, .parseerror | ||||
ld hl, SHELL_CMD_ARGS | |||||
; Args parsed, now we can load the routine address and call it. | ; Args parsed, now we can load the routine address and call it. | ||||
; let's have DE point to the jump line | ; let's have DE point to the jump line | ||||
ld a, SHELL_CMD_ARGS_MAXSIZE | |||||
ld hl, SHELL_CMD_ARGS | |||||
ld a, PARSE_ARG_MAXCOUNT | |||||
call addDE | call addDE | ||||
push de \ pop ix | push de \ pop ix | ||||
; Ready to roll! | ; Ready to roll! | ||||
@@ -230,115 +220,13 @@ shellPrintErr: | |||||
.str: | .str: | ||||
.db "ERR ", 0 | .db "ERR ", 0 | ||||
; Parse arguments at (HL) with specifiers at (DE) into (SHELL_CMD_ARGS). | |||||
; (HL) should point to the character *just* after the name of the command | |||||
; because we verify, in the case that we have args, that we have a space there. | |||||
; | |||||
; Args specifiers are a series of flag for each arg: | |||||
; Bit 0 - arg present: if unset, we stop parsing there | |||||
; Bit 1 - is word: this arg is a word rather than a byte. Because our | |||||
; destination are bytes anyway, this doesn't change much except | |||||
; for whether we expect a space between the hex pairs. If set, | |||||
; you still need to have a specifier for the second part of | |||||
; the multibyte. | |||||
; Bit 2 - optional: If set and not present during parsing, we don't error out | |||||
; and write zero | |||||
; | |||||
; Bit 3 - String argument: If set, this argument is a string. A pointer to the | |||||
; read string, null terminated (max 0x20 chars) will | |||||
; be placed in the next two bytes. This has to be the | |||||
; last argument of the list and it stops parsing. | |||||
; Sets A to nonzero if there was an error during parsing, zero otherwise. | |||||
; If there was an error during parsing, carry is set. | |||||
shellParseArgs: | |||||
push bc | |||||
push de | |||||
push hl | |||||
push ix | |||||
ld ix, SHELL_CMD_ARGS | |||||
ld a, SHELL_CMD_ARGS_MAXSIZE | |||||
ld b, a | |||||
xor c | |||||
.loop: | |||||
; init the arg value to a default 0 | |||||
xor a | |||||
ld (ix), a | |||||
ld a, (hl) | |||||
; is this the end of the line? | |||||
cp 0 | |||||
jr z, .endofargs | |||||
; do we have a proper space char? | |||||
cp ' ' | |||||
jr z, .hasspace ; We're fine | |||||
; is our previous arg a multibyte? (argspec still in C) | |||||
bit 1, c | |||||
jr z, .error ; bit not set? error | |||||
dec hl ; offset the "inc hl" below | |||||
.hasspace: | |||||
; Get the specs | |||||
ld a, (de) | |||||
bit 0, a ; do we have an arg? | |||||
jr z, .error ; not set? then we have too many args | |||||
ld c, a ; save the specs for the next loop | |||||
inc hl ; (hl) points to a space, go next | |||||
bit 3, a ; is our arg a string? | |||||
jr z, .notAString | |||||
; our arg is a string. Let's place HL in our next two bytes and call | |||||
; it a day. Little endian, remember | |||||
ld (ix), l | |||||
ld (ix+1), h | |||||
jr .success ; directly to success: skip endofargs checks | |||||
.notAString: | |||||
call parseHexPair | |||||
jr c, .error | |||||
; we have a good arg and we need to write A in (IX). | |||||
ld (ix), a | |||||
; Good! increase counters | |||||
inc de | |||||
inc ix | |||||
inc hl ; get to following char (generally a space) | |||||
djnz .loop | |||||
; If we get here, it means that our next char *has* to be a null char | |||||
ld a, (hl) | |||||
cp 0 | |||||
jr z, .success ; zero? great! | |||||
jr .error | |||||
.endofargs: | |||||
; We encountered our null char. Let's verify that we either have no | |||||
; more args or that they are optional | |||||
ld a, (de) | |||||
cp 0 | |||||
jr z, .success ; no arg? success | |||||
bit 2, a | |||||
jr nz, .success ; if set, arg is optional. success | |||||
jr .error | |||||
.success: | |||||
xor a | |||||
jr .end | |||||
.error: | |||||
inc a | |||||
.end: | |||||
pop ix | |||||
pop hl | |||||
pop de | |||||
pop bc | |||||
ret | |||||
; *** COMMANDS *** | ; *** COMMANDS *** | ||||
; A command is a 4 char names, followed by a SHELL_CMD_ARGS_MAXSIZE bytes of | |||||
; 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 | ; 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 | ; is compiled in a block and this is what is iterated upon when we want all | ||||
; available commands. | ; available commands. | ||||
; | ; | ||||
; Format: 4 bytes name followed by SHELL_CMD_ARGS_MAXSIZE bytes specifiers, | |||||
; Format: 4 bytes name followed by PARSE_ARG_MAXCOUNT bytes specifiers, | |||||
; followed by 3 bytes jump. fill names with zeroes | ; followed by 3 bytes jump. fill names with zeroes | ||||
; | ; | ||||
; When these commands are called, HL points to the first byte of the | ; When these commands are called, HL points to the first byte of the | ||||
@@ -54,7 +54,7 @@ updatebootstrap: $(ZASMBIN) | |||||
.PHONY: rescue | .PHONY: rescue | ||||
rescue: | rescue: | ||||
scas -o zasm/kernel.bin -I $(KERNEL) zasm/glue.asm | scas -o zasm/kernel.bin -I $(KERNEL) zasm/glue.asm | ||||
scas -o zasm/zasm.bin -I $(APPS) $(APPS)/zasm/glue.asm | |||||
scas -o zasm/zasm.bin -I $(APPS) -I $(KERNEL) $(APPS)/zasm/glue.asm | |||||
.PHONY: clean | .PHONY: clean | ||||
clean: | clean: | ||||
@@ -14,6 +14,7 @@ | |||||
jp printstr | jp printstr | ||||
#include "core.asm" | #include "core.asm" | ||||
#include "err.h" | |||||
#include "parse.asm" | #include "parse.asm" | ||||
.equ BLOCKDEV_RAMSTART RAMSTART | .equ BLOCKDEV_RAMSTART RAMSTART | ||||
@@ -27,8 +27,10 @@ jp fsGetC | |||||
jp fsSeek | jp fsSeek | ||||
jp fsTell | jp fsTell | ||||
jp cpHLDE | jp cpHLDE | ||||
jp parseArgs | |||||
#include "core.asm" | #include "core.asm" | ||||
#include "err.h" | |||||
#include "parse.asm" | #include "parse.asm" | ||||
.equ BLOCKDEV_RAMSTART RAMSTART | .equ BLOCKDEV_RAMSTART RAMSTART | ||||
.equ BLOCKDEV_COUNT 3 | .equ BLOCKDEV_COUNT 3 | ||||
@@ -50,12 +52,14 @@ init: | |||||
ld de, BLOCKDEV_GETC | ld de, BLOCKDEV_GETC | ||||
call blkSel | call blkSel | ||||
call fsOn | call fsOn | ||||
ld h, 0 ; input blkdev | |||||
ld l, 1 ; output blkdev | |||||
ld hl, .zasmArgs | |||||
call USER_CODE | call USER_CODE | ||||
; signal the emulator we're done | ; signal the emulator we're done | ||||
halt | halt | ||||
.zasmArgs: | |||||
.db " 0 1", 0 | |||||
; *** I/O *** | ; *** I/O *** | ||||
emulGetC: | emulGetC: | ||||
in a, (STDIO_PORT) | in a, (STDIO_PORT) | ||||
@@ -2,6 +2,7 @@ | |||||
.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. | ||||
#include "err.h" | |||||
#include "core.asm" | #include "core.asm" | ||||
#include "parse.asm" | #include "parse.asm" | ||||
.equ ACIA_RAMSTART RAMSTART | .equ ACIA_RAMSTART RAMSTART | ||||
@@ -21,7 +21,9 @@ | |||||
.equ fsSeek 0x30 | .equ fsSeek 0x30 | ||||
.equ fsTell 0x33 | .equ fsTell 0x33 | ||||
.equ cpHLDE 0x36 | .equ cpHLDE 0x36 | ||||
.equ parseArgs 0x39 | |||||
#include "err.h" | |||||
#include "zasm/const.asm" | #include "zasm/const.asm" | ||||
#include "zasm/util.asm" | #include "zasm/util.asm" | ||||
.equ IO_RAMSTART USER_RAMSTART | .equ IO_RAMSTART USER_RAMSTART | ||||