This introduces `core.asm` which includes routines used by other parts.pull/10/head
@@ -0,0 +1,193 @@ | |||
; core | |||
; | |||
; Routines used by pretty much all parts. You will want to include it first | |||
; in your glue file. | |||
; *** CONSTS *** | |||
ASCII_CR .equ 0x0d | |||
ASCII_LF .equ 0x0a | |||
; *** CODE *** | |||
; add the value of A into DE | |||
addDE: | |||
add a, e | |||
jr nc, .end ; no carry? skip inc | |||
inc d | |||
.end: | |||
ld e, a | |||
ret | |||
; Format the lower nibble of A into a hex char and stores the result in A. | |||
fmtHex: | |||
and a, 0xf | |||
cp 10 | |||
jr nc, .alpha ; if >= 10, we have alpha | |||
add a, '0' | |||
ret | |||
.alpha: | |||
add a, 'A'-10 | |||
ret | |||
; Formats value in A into a string hex pair. Stores it in the memory location | |||
; that HL points to. Does *not* add a null char at the end. | |||
fmtHexPair: | |||
push af | |||
; let's start with the rightmost char | |||
inc hl | |||
call fmtHex | |||
ld (hl), a | |||
; and now with the leftmost | |||
dec hl | |||
pop af | |||
push af | |||
and a, 0xf0 | |||
rra \ rra \ rra \ rra | |||
call fmtHex | |||
ld (hl), a | |||
pop af | |||
ret | |||
; jump to the location pointed to by HL. This allows us to call HL instead of | |||
; just jumping it. | |||
jumpHL: | |||
jp hl | |||
ret | |||
; Parse the hex char at A and extract it's 0-15 numerical value. Put the result | |||
; in A. | |||
; | |||
; On success, the carry flag is reset. On error, it is set. | |||
parseHex: | |||
; First, let's see if we have an easy 0-9 case | |||
cp '0' | |||
jr c, .error ; if < '0', we have a problem | |||
cp '9'+1 | |||
jr nc, .alpha ; if >= '9'+1, we might have alpha | |||
; We are in the 0-9 range | |||
sub a, '0' ; C is clear | |||
ret | |||
.alpha: | |||
call upcase | |||
cp 'A' | |||
jr c, .error ; if < 'A', we have a problem | |||
cp 'F'+1 | |||
jr nc, .error ; if >= 'F', we have a problem | |||
; We have alpha. | |||
sub a, 'A'-10 ; C is clear | |||
ret | |||
.error: | |||
scf | |||
ret | |||
; Parses 2 characters of the string pointed to by HL and returns the numerical | |||
; value in A. If the second character is a "special" character (<0x21) we don't | |||
; error out: the result will be the one from the first char only. | |||
; | |||
; On success, the carry flag is reset. On error, it is set. | |||
parseHexPair: | |||
push bc | |||
push hl | |||
ld a, (hl) | |||
call parseHex | |||
jr c, .end ; error? goto end, keeping the C flag on | |||
rla \ rla \ rla \ rla ; let's push this in MSB | |||
ld b, a | |||
inc hl | |||
ld a, (hl) | |||
;cp 0x21 | |||
;jr c, .single ; special char? single digit | |||
call parseHex | |||
jr c, .end ; error? | |||
or b ; join left-shifted + new. we're done! | |||
; C flag was set on parseHex and is necessarily clear at this point | |||
jr .end | |||
.single: | |||
; If we have a single digit, our result is already stored in B, but | |||
; we have to right-shift it back. | |||
ld a, b | |||
and a, 0xf0 | |||
rra \ rra \ rra \ rra | |||
.end: | |||
pop hl | |||
pop bc | |||
ret | |||
; print null-terminated string pointed to by HL | |||
printstr: | |||
push af | |||
push hl | |||
.loop: | |||
ld a, (hl) ; load character to send | |||
or a ; is it zero? | |||
jr z, .end ; if yes, we're finished | |||
call aciaPutC | |||
inc hl | |||
jr .loop | |||
.end: | |||
pop hl | |||
pop af | |||
ret | |||
; print A characters from string that HL points to | |||
printnstr: | |||
push bc | |||
push hl | |||
ld b, a | |||
.loop: | |||
ld a, (hl) ; load character to send | |||
call aciaPutC | |||
inc hl | |||
djnz .loop | |||
.end: | |||
pop hl | |||
pop bc | |||
ret | |||
; Compares strings pointed to by HL and DE up to A count of characters. If | |||
; equal, Z is set. If not equal, Z is reset. | |||
strncmp: | |||
push bc | |||
push hl | |||
push de | |||
ld b, a | |||
.loop: | |||
ld a, (de) | |||
cp (hl) | |||
jr nz, .end ; not equal? break early | |||
inc hl | |||
inc de | |||
djnz .loop | |||
.end: | |||
pop de | |||
pop hl | |||
pop bc | |||
; Because we don't call anything else than CP that modify the Z flag, | |||
; our Z value will be that of the last cp (reset if we broke the loop | |||
; early, set otherwise) | |||
ret | |||
; Transforms the character in A, if it's in the a-z range, into its upcase | |||
; version. | |||
upcase: | |||
cp 'a' | |||
ret c ; A < 'a'. nothing to do | |||
cp 'z'+1 | |||
ret nc ; A >= 'z'+1. nothing to do | |||
; 'a' - 'A' == 0x20 | |||
sub 0x20 | |||
ret |
@@ -13,8 +13,6 @@ | |||
; See constants below for error codes. | |||
; *** CONSTS *** | |||
CR .equ 0x0d | |||
LF .equ 0x0a | |||
; number of entries in shellCmdTbl | |||
SHELL_CMD_COUNT .equ 2 | |||
@@ -25,8 +23,19 @@ SHELL_ERR_UNKNOWN_CMD .equ 0x01 | |||
; Arguments for the command weren't properly formatted | |||
SHELL_ERR_BAD_ARGS .equ 0x02 | |||
; *** 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 | |||
SHELL_RAMEND .equ SHELL_HEX_FMT+2 | |||
; *** CODE *** | |||
shellInit: | |||
xor a | |||
ld (SHELL_MEM_PTR), a | |||
; print prompt | |||
ld hl, .prompt | |||
call printstr | |||
@@ -40,32 +49,14 @@ shellLoop: | |||
call chkbuf | |||
jr shellLoop | |||
; print null-terminated string pointed to by HL | |||
printstr: | |||
push af | |||
push hl | |||
.loop: | |||
ld a, (hl) ; load character to send | |||
or a ; is it zero? | |||
jr z, .end ; if yes, we're finished | |||
call aciaPutC | |||
inc hl | |||
jr .loop | |||
.end: | |||
pop hl | |||
pop af | |||
ret | |||
printcrlf: | |||
ld a, CR | |||
ld a, ASCII_CR | |||
call aciaPutC | |||
ld a, LF | |||
ld a, ASCII_LF | |||
call aciaPutC | |||
ret | |||
; check if the input buffer is full or ends in CR or LF. If it does, prints it | |||
; back and empty it. | |||
chkbuf: | |||
@@ -81,9 +72,9 @@ chkbuf: | |||
ld a, (hl) ; now, that's our char we have in A | |||
inc hl ; put HL back where it was | |||
cp CR | |||
cp ASCII_CR | |||
jr z, .do ; char is CR? do! | |||
cp LF | |||
cp ASCII_LF | |||
jr z, .do ; char is LF? do! | |||
; nothing matched? don't do anything | |||
@@ -101,51 +92,12 @@ chkbuf: | |||
call shellParse | |||
ret | |||
; Compares strings pointed to by HL and DE up to A count of characters. If | |||
; equal, Z is set. If not equal, Z is reset. | |||
strncmp: | |||
push bc | |||
push hl | |||
push de | |||
ld b, a | |||
.loop: | |||
ld a, (de) | |||
cp (hl) | |||
jr nz, .end ; not equal? break early | |||
inc hl | |||
inc de | |||
djnz .loop | |||
.end: | |||
pop de | |||
pop hl | |||
pop bc | |||
; Because we don't call anything else than CP that modify the Z flag, | |||
; our Z value will be that of the last cp (reset if we broke the loop | |||
; early, set otherwise) | |||
ret | |||
; add the value of A into DE | |||
addDE: | |||
add a, e | |||
jr nc, .end ; no carry? skip inc | |||
inc d | |||
.end: | |||
ld e, a | |||
ret | |||
; jump to the location pointed to by HL. This allows us to call HL instead of | |||
; just jumping it. | |||
jumpHL: | |||
jp hl | |||
ret | |||
; Parse command (null terminated) at HL and calls it | |||
shellParse: | |||
push af | |||
push bc | |||
push de | |||
push hl | |||
ld de, shellCmdTbl | |||
ld a, SHELL_CMD_COUNT | |||
@@ -165,19 +117,34 @@ shellParse: | |||
jr .end | |||
.found: | |||
; all right, we're almost ready to call the cmd. Let's just have DE | |||
; point to the cmd jump line. | |||
ld a, 4 | |||
call addDE | |||
ex hl, de | |||
; 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! | |||
call jumpHL | |||
ex hl, de | |||
.end: | |||
pop hl | |||
pop de | |||
pop bc | |||
pop af | |||
ret | |||
; Print the error code set in A (doesn't work for codes > 9 yet...) | |||
; Print the error code set in A (in hex) | |||
shellPrintErr: | |||
push af | |||
push hl | |||
@@ -185,9 +152,10 @@ shellPrintErr: | |||
ld hl, .str | |||
call printstr | |||
; ascii for '0' is 0x30 | |||
add a, 0x30 | |||
call aciaPutC | |||
ld hl, SHELL_HEX_FMT | |||
call fmtHexPair | |||
ld a, 2 | |||
call printnstr | |||
call printcrlf | |||
pop hl | |||
@@ -198,19 +166,66 @@ shellPrintErr: | |||
.db "ERR ", 0 | |||
; *** COMMANDS *** | |||
; When these commands are called, DE points to the first character of the | |||
; command args. | |||
; Set memory pointer to the specified address. | |||
; Example: seek 01fe | |||
shellSeek: | |||
ld hl, .str | |||
call printstr | |||
push de | |||
push hl | |||
ex de, hl | |||
call parseHexPair | |||
jr c, .error | |||
ld (SHELL_MEM_PTR), a | |||
inc hl | |||
inc hl | |||
call parseHexPair | |||
jr c, .error | |||
ld (SHELL_MEM_PTR+1), a | |||
jr .success | |||
.error: | |||
ld a, SHELL_ERR_BAD_ARGS | |||
call shellPrintErr | |||
jr .end | |||
.success: | |||
ld a, (SHELL_MEM_PTR) | |||
ld hl, SHELL_HEX_FMT | |||
call fmtHexPair | |||
ld a, 2 | |||
call printnstr | |||
ld a, (SHELL_MEM_PTR+1) | |||
call fmtHexPair | |||
ld a, 2 | |||
call printnstr | |||
call printcrlf | |||
.end: | |||
pop hl | |||
pop de | |||
ret | |||
.str: | |||
.db "seek called", CR, LF, 0 | |||
; peek byte where memory pointer points to aby display its value | |||
shellPeek: | |||
ld hl, .str | |||
call printstr | |||
push af | |||
push hl | |||
ld hl, (SHELL_MEM_PTR) | |||
ld a, (hl) | |||
ld hl, SHELL_HEX_FMT | |||
call fmtHexPair | |||
ld a, 2 | |||
call printnstr | |||
call printcrlf | |||
pop hl | |||
pop af | |||
ret | |||
.str: | |||
.db "peek called", CR, LF, 0 | |||
; Format: 4 bytes name followed by 2 bytes jump. fill names with zeroes | |||
shellCmdTbl: | |||
@@ -47,7 +47,7 @@ This is what your glue code would look like: | |||
jr init | |||
.fill 0x38-$ | |||
jr aciaInt | |||
jp aciaInt | |||
init: | |||
di | |||
@@ -63,8 +63,10 @@ init: | |||
ei | |||
call shellLoop | |||
#include "core.asm" | |||
ACIA_RAMSTART .equ RAMSTART | |||
#include "acia.asm" | |||
SHELL_RAMSTART .equ ACIA_RAMEND | |||
#include "shell.asm" | |||
``` | |||
@@ -83,11 +85,9 @@ 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`. | |||
What comes below is actual code include from the acia and shell modules. As you | |||
can see, we need to tell each module where to put their variables. `shell.asm` | |||
doesn't have variables, but if it did, we would have a `SHELL_RAMSTART .equ | |||
ACIA_RAMEND` just below the `acia.asm` include. `ACIA_RAMEND` is defined in | |||
`acia.asm`. | |||
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. | |||
### Build the image | |||