2019-04-14 06:01:20 +10:00
|
|
|
; shell
|
|
|
|
;
|
2019-04-15 05:18:41 +10:00
|
|
|
; Runs a shell over a block device interface.
|
2019-04-14 06:01:20 +10:00
|
|
|
|
2019-04-14 12:39:28 +10:00
|
|
|
; Status: incomplete. As it is now, it 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.
|
|
|
|
;
|
2019-04-15 04:24:29 +10:00
|
|
|
; Commands, for now, are partially implemented.
|
2019-04-14 12:39:28 +10:00
|
|
|
;
|
|
|
|
; See constants below for error codes.
|
2019-04-15 01:22:45 +10:00
|
|
|
;
|
|
|
|
; All numerical values in the Collapse OS shell are represented and parsed in
|
|
|
|
; hexadecimal form, without prefix or suffix.
|
2019-04-14 06:01:20 +10:00
|
|
|
|
2019-04-15 04:24:29 +10:00
|
|
|
; *** DEFINES ***
|
|
|
|
; SHELL_GETC: Macro that calls a GetC routine
|
|
|
|
; SHELL_PUTC: Macro that calls a PutC routine
|
|
|
|
; SHELL_RAMSTART
|
|
|
|
|
2019-04-14 06:01:20 +10:00
|
|
|
; *** CONSTS ***
|
|
|
|
|
2019-04-14 12:39:28 +10:00
|
|
|
; number of entries in shellCmdTbl
|
2019-04-15 04:53:16 +10:00
|
|
|
SHELL_CMD_COUNT .equ 3
|
2019-04-14 12:39:28 +10:00
|
|
|
|
|
|
|
; The command that was type isn't known to the shell
|
|
|
|
SHELL_ERR_UNKNOWN_CMD .equ 0x01
|
|
|
|
|
|
|
|
; Arguments for the command weren't properly formatted
|
|
|
|
SHELL_ERR_BAD_ARGS .equ 0x02
|
|
|
|
|
2019-04-15 03:56:04 +10:00
|
|
|
; Size of the shell command buffer. If a typed command reaches this size, the
|
|
|
|
; command is flushed immediately (same as pressing return).
|
|
|
|
SHELL_BUFSIZE .equ 0x20
|
|
|
|
|
2019-04-15 01:11:13 +10:00
|
|
|
; *** VARIABLES ***
|
|
|
|
; Memory address that the shell is currently "pointing at" for peek and deek
|
|
|
|
; operations. Set with seek.
|
|
|
|
SHELL_MEM_PTR .equ SHELL_RAMSTART
|
|
|
|
; Used to store formatted hex values just before printing it.
|
|
|
|
SHELL_HEX_FMT .equ SHELL_MEM_PTR+2
|
2019-04-15 03:56:04 +10:00
|
|
|
|
|
|
|
; 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
|
|
|
|
; for the null-termination every time we write to it. Simpler that way.
|
|
|
|
SHELL_BUF .equ SHELL_HEX_FMT+2
|
|
|
|
|
|
|
|
SHELL_RAMEND .equ SHELL_BUF+SHELL_BUFSIZE
|
2019-04-15 01:11:13 +10:00
|
|
|
|
2019-04-14 12:39:28 +10:00
|
|
|
; *** CODE ***
|
2019-04-14 06:01:20 +10:00
|
|
|
shellInit:
|
2019-04-15 01:11:13 +10:00
|
|
|
xor a
|
|
|
|
ld (SHELL_MEM_PTR), a
|
2019-04-15 03:56:04 +10:00
|
|
|
ld (SHELL_BUF), a
|
2019-04-15 01:11:13 +10:00
|
|
|
|
2019-04-15 04:53:16 +10:00
|
|
|
; print welcome
|
|
|
|
ld hl, .welcome
|
2019-04-14 06:01:20 +10:00
|
|
|
call printstr
|
|
|
|
ret
|
|
|
|
|
2019-04-15 04:53:16 +10:00
|
|
|
.welcome:
|
|
|
|
.db "Collapse OS", ASCII_CR, ASCII_LF, "> ", 0
|
2019-04-14 12:39:28 +10:00
|
|
|
|
2019-04-14 06:01:20 +10:00
|
|
|
shellLoop:
|
2019-04-15 03:56:04 +10:00
|
|
|
; First, let's wait until something is typed.
|
2019-04-15 04:24:29 +10:00
|
|
|
SHELL_GETC
|
2019-04-15 03:56:04 +10:00
|
|
|
; got it. Now, is it a CR or LF?
|
2019-04-15 01:11:13 +10:00
|
|
|
cp ASCII_CR
|
2019-04-14 06:01:20 +10:00
|
|
|
jr z, .do ; char is CR? do!
|
2019-04-15 01:11:13 +10:00
|
|
|
cp ASCII_LF
|
2019-04-14 06:01:20 +10:00
|
|
|
jr z, .do ; char is LF? do!
|
|
|
|
|
2019-04-15 05:18:41 +10:00
|
|
|
; Echo the received character right away so that we see what we type
|
|
|
|
SHELL_PUTC
|
|
|
|
|
2019-04-15 03:56:04 +10:00
|
|
|
; Ok, gotta add it do the buffer
|
|
|
|
; save char for later
|
|
|
|
ex af, af'
|
|
|
|
ld hl, SHELL_BUF
|
|
|
|
call findnull ; HL points to where we need to write
|
|
|
|
; A is the number of chars in the buf
|
|
|
|
cp SHELL_BUFSIZE
|
|
|
|
jr z, .do ; A == bufsize? then our buffer is full. do!
|
|
|
|
|
|
|
|
; bring the char back in A
|
|
|
|
ex af, af'
|
|
|
|
; Buffer not full, not CR or LF. Let's put that char in our buffer and
|
|
|
|
; read again.
|
|
|
|
ld (hl), a
|
|
|
|
; Now, write a zero to the next byte to properly terminate our string.
|
|
|
|
inc hl
|
|
|
|
xor a
|
|
|
|
ld (hl), a
|
|
|
|
|
2019-04-15 01:22:45 +10:00
|
|
|
jr shellLoop
|
2019-04-15 03:56:04 +10:00
|
|
|
|
2019-04-14 06:01:20 +10:00
|
|
|
.do:
|
2019-04-15 04:53:16 +10:00
|
|
|
call printcrlf
|
2019-04-15 03:56:04 +10:00
|
|
|
ld hl, SHELL_BUF
|
|
|
|
call shellParse
|
|
|
|
; empty our buffer by writing a zero to its first char
|
2019-04-14 06:01:20 +10:00
|
|
|
xor a
|
|
|
|
ld (hl), a
|
|
|
|
|
2019-04-15 04:53:16 +10:00
|
|
|
ld hl, .prompt
|
|
|
|
call printstr
|
2019-04-15 01:22:45 +10:00
|
|
|
jr shellLoop
|
|
|
|
|
2019-04-15 04:53:16 +10:00
|
|
|
.prompt:
|
|
|
|
.db "> ", 0
|
|
|
|
|
2019-04-15 01:22:45 +10:00
|
|
|
printcrlf:
|
|
|
|
ld a, ASCII_CR
|
2019-04-15 04:24:29 +10:00
|
|
|
SHELL_PUTC
|
2019-04-15 01:22:45 +10:00
|
|
|
ld a, ASCII_LF
|
2019-04-15 04:24:29 +10:00
|
|
|
SHELL_PUTC
|
2019-04-14 12:39:28 +10:00
|
|
|
ret
|
|
|
|
|
|
|
|
; Parse command (null terminated) at HL and calls it
|
|
|
|
shellParse:
|
|
|
|
push af
|
|
|
|
push bc
|
|
|
|
push de
|
2019-04-15 01:11:13 +10:00
|
|
|
push hl
|
2019-04-14 12:39:28 +10:00
|
|
|
|
|
|
|
ld de, shellCmdTbl
|
|
|
|
ld a, SHELL_CMD_COUNT
|
|
|
|
ld b, a
|
|
|
|
|
|
|
|
.loop:
|
|
|
|
ld a, 4 ; 4 chars to compare
|
|
|
|
call strncmp
|
|
|
|
jr z, .found
|
2019-04-15 04:53:16 +10:00
|
|
|
ld a, 7
|
2019-04-14 12:39:28 +10:00
|
|
|
call addDE
|
|
|
|
djnz .loop
|
|
|
|
|
|
|
|
; exhausted loop? not found
|
|
|
|
ld a, SHELL_ERR_UNKNOWN_CMD
|
|
|
|
call shellPrintErr
|
|
|
|
jr .end
|
|
|
|
|
|
|
|
.found:
|
2019-04-15 01:11:13 +10:00
|
|
|
; all right, we're almost ready to call the cmd. Let's just have DE
|
|
|
|
; point to the cmd jump line.
|
2019-04-14 12:39:28 +10:00
|
|
|
ld a, 4
|
|
|
|
call addDE
|
2019-04-15 01:11:13 +10:00
|
|
|
; Now, let's swap HL and DE because, welll because that's how we're set.
|
|
|
|
ex hl, de ; HL = jump line, DE = cmd str pointer
|
|
|
|
|
|
|
|
; Before we call our command, we want to set up the pointer to the arg
|
|
|
|
; list. Normally, it's DE+5 (DE+4 is the space) unless DE+4 is null,
|
|
|
|
; which means no arg.
|
|
|
|
ld a, 4
|
|
|
|
call addDE
|
|
|
|
ld a, (DE)
|
|
|
|
cp 0
|
|
|
|
jr z, .noarg ; char is null? we have no arg
|
|
|
|
inc de
|
|
|
|
.noarg:
|
|
|
|
; DE points to args, HL points to jump line. Ready to roll!
|
2019-04-14 12:39:28 +10:00
|
|
|
call jumpHL
|
|
|
|
|
|
|
|
.end:
|
2019-04-15 01:11:13 +10:00
|
|
|
pop hl
|
2019-04-14 12:39:28 +10:00
|
|
|
pop de
|
|
|
|
pop bc
|
|
|
|
pop af
|
|
|
|
ret
|
|
|
|
|
2019-04-15 01:11:13 +10:00
|
|
|
; Print the error code set in A (in hex)
|
2019-04-14 12:39:28 +10:00
|
|
|
shellPrintErr:
|
|
|
|
push af
|
|
|
|
push hl
|
|
|
|
|
|
|
|
ld hl, .str
|
2019-04-14 06:01:20 +10:00
|
|
|
call printstr
|
2019-04-14 12:39:28 +10:00
|
|
|
|
2019-04-15 01:11:13 +10:00
|
|
|
ld hl, SHELL_HEX_FMT
|
|
|
|
call fmtHexPair
|
|
|
|
ld a, 2
|
|
|
|
call printnstr
|
2019-04-14 06:01:20 +10:00
|
|
|
call printcrlf
|
2019-04-14 12:39:28 +10:00
|
|
|
|
|
|
|
pop hl
|
|
|
|
pop af
|
|
|
|
ret
|
|
|
|
|
|
|
|
.str:
|
|
|
|
.db "ERR ", 0
|
|
|
|
|
|
|
|
; *** COMMANDS ***
|
2019-04-15 01:11:13 +10:00
|
|
|
; When these commands are called, DE points to the first character of the
|
|
|
|
; command args.
|
|
|
|
|
2019-04-15 01:54:18 +10:00
|
|
|
; Set memory pointer to the specified address (word).
|
2019-04-15 01:11:13 +10:00
|
|
|
; Example: seek 01fe
|
|
|
|
|
2019-04-14 12:39:28 +10:00
|
|
|
shellSeek:
|
2019-04-15 01:54:18 +10:00
|
|
|
push af
|
2019-04-15 01:11:13 +10:00
|
|
|
push de
|
|
|
|
push hl
|
|
|
|
|
|
|
|
ex de, hl
|
|
|
|
call parseHexPair
|
|
|
|
jr c, .error
|
2019-04-15 01:54:18 +10:00
|
|
|
; z80 is little endian. in a "ld hl, (nn)" op, L is loaded from the
|
|
|
|
; first byte, H is loaded from the second
|
|
|
|
ld (SHELL_MEM_PTR+1), a
|
2019-04-15 01:11:13 +10:00
|
|
|
inc hl
|
|
|
|
inc hl
|
|
|
|
call parseHexPair
|
|
|
|
jr c, .error
|
2019-04-15 01:54:18 +10:00
|
|
|
ld (SHELL_MEM_PTR), a
|
2019-04-15 01:11:13 +10:00
|
|
|
jr .success
|
|
|
|
|
|
|
|
.error:
|
|
|
|
ld a, SHELL_ERR_BAD_ARGS
|
|
|
|
call shellPrintErr
|
|
|
|
jr .end
|
|
|
|
|
|
|
|
.success:
|
2019-04-15 01:54:18 +10:00
|
|
|
ld a, (SHELL_MEM_PTR+1)
|
2019-04-15 01:11:13 +10:00
|
|
|
ld hl, SHELL_HEX_FMT
|
|
|
|
call fmtHexPair
|
|
|
|
ld a, 2
|
|
|
|
call printnstr
|
2019-04-15 01:54:18 +10:00
|
|
|
ld a, (SHELL_MEM_PTR)
|
2019-04-15 01:11:13 +10:00
|
|
|
call fmtHexPair
|
|
|
|
ld a, 2
|
|
|
|
call printnstr
|
|
|
|
call printcrlf
|
|
|
|
|
|
|
|
.end:
|
|
|
|
pop hl
|
|
|
|
pop de
|
2019-04-15 01:54:18 +10:00
|
|
|
pop af
|
2019-04-14 06:01:20 +10:00
|
|
|
ret
|
2019-04-14 12:39:28 +10:00
|
|
|
|
2019-04-15 01:11:13 +10:00
|
|
|
|
2019-04-15 01:54:18 +10:00
|
|
|
; peek byte where memory pointer points to any display its value. If the
|
|
|
|
; optional numerical byte arg is supplied, this number of bytes will be printed
|
|
|
|
;
|
|
|
|
; Example: peek 2 (will print 2 bytes)
|
2019-04-14 12:39:28 +10:00
|
|
|
shellPeek:
|
2019-04-15 01:11:13 +10:00
|
|
|
push af
|
2019-04-15 01:54:18 +10:00
|
|
|
push bc
|
|
|
|
push de
|
2019-04-15 01:11:13 +10:00
|
|
|
push hl
|
|
|
|
|
2019-04-15 01:54:18 +10:00
|
|
|
ld b, 1 ; by default, we run the loop once
|
|
|
|
ld a, (de)
|
|
|
|
cp 0
|
|
|
|
jr z, .success ; no arg? don't try to parse
|
|
|
|
|
|
|
|
ex de, hl
|
|
|
|
call parseHexPair
|
|
|
|
jr c, .error
|
|
|
|
cp 0
|
|
|
|
jr z, .error ; zero isn't a good arg, error
|
|
|
|
ld b, a ; loop the number of times specified in arg
|
|
|
|
jr .success
|
|
|
|
|
|
|
|
.error:
|
|
|
|
ld a, SHELL_ERR_BAD_ARGS
|
|
|
|
call shellPrintErr
|
|
|
|
jr .end
|
|
|
|
|
|
|
|
.success:
|
2019-04-15 01:11:13 +10:00
|
|
|
ld hl, (SHELL_MEM_PTR)
|
2019-04-15 01:54:18 +10:00
|
|
|
.loop: ld a, (hl)
|
|
|
|
ex hl, de
|
2019-04-15 01:11:13 +10:00
|
|
|
ld hl, SHELL_HEX_FMT
|
|
|
|
call fmtHexPair
|
|
|
|
ld a, 2
|
|
|
|
call printnstr
|
2019-04-15 01:54:18 +10:00
|
|
|
ex hl, de
|
|
|
|
inc hl
|
|
|
|
djnz .loop
|
2019-04-15 01:11:13 +10:00
|
|
|
call printcrlf
|
|
|
|
|
2019-04-15 01:54:18 +10:00
|
|
|
.end:
|
2019-04-15 01:11:13 +10:00
|
|
|
pop hl
|
2019-04-15 01:54:18 +10:00
|
|
|
pop de
|
|
|
|
pop bc
|
2019-04-15 01:11:13 +10:00
|
|
|
pop af
|
2019-04-14 12:39:28 +10:00
|
|
|
ret
|
|
|
|
|
2019-04-15 04:53:16 +10:00
|
|
|
; Load the specified number of bytes (max 0xff) from IO and write them in the
|
|
|
|
; current memory pointer (which doesn't change). For now, we can only load from
|
|
|
|
; SHELL_GETC, but a method of selecting IO sources is coming, making this
|
|
|
|
; command much more useful.
|
|
|
|
; Control is returned to the shell only after all bytes are read.
|
|
|
|
;
|
|
|
|
; Example: load 42
|
|
|
|
shellLoad:
|
|
|
|
push af
|
|
|
|
push bc
|
|
|
|
push hl
|
|
|
|
|
|
|
|
ld a, (de)
|
|
|
|
call parseHex
|
|
|
|
jr c, .error
|
|
|
|
jr .success
|
|
|
|
|
|
|
|
.error:
|
|
|
|
ld a, SHELL_ERR_BAD_ARGS
|
|
|
|
call shellPrintErr
|
|
|
|
jr .end
|
|
|
|
|
|
|
|
.success:
|
|
|
|
ld b, a
|
|
|
|
ld hl, (SHELL_MEM_PTR)
|
|
|
|
.loop: SHELL_GETC
|
|
|
|
ld (hl), a
|
|
|
|
inc hl
|
|
|
|
djnz .loop
|
|
|
|
|
|
|
|
.end:
|
|
|
|
pop hl
|
|
|
|
pop bc
|
|
|
|
pop af
|
|
|
|
ret
|
|
|
|
|
|
|
|
; Format: 4 bytes name followed by 3 bytes jump. fill names with zeroes
|
2019-04-14 12:39:28 +10:00
|
|
|
shellCmdTbl:
|
|
|
|
.db "seek"
|
2019-04-15 04:53:16 +10:00
|
|
|
jp shellSeek
|
2019-04-14 12:39:28 +10:00
|
|
|
.db "peek"
|
2019-04-15 04:53:16 +10:00
|
|
|
jp shellPeek
|
|
|
|
.db "load"
|
|
|
|
jp shellLoad
|
2019-04-14 06:01:20 +10:00
|
|
|
|