From 2e8af376e3375c454f36694212e9a9fd99bdd9a3 Mon Sep 17 00:00:00 2001 From: Virgil Dupras Date: Fri, 31 May 2019 14:50:43 -0400 Subject: [PATCH] pgm: new kernel module The pgm module implements a shell hook so that when an unknown command is typed, we look into the mounted filesystem and look for a file with the same name as the command. If we find one, we load it in memory and run it. --- doc/glue-code.md | 22 ++++++++---- kernel/pgm.asm | 61 +++++++++++++++++++++++++++++++++ kernel/shell.asm | 34 +++++++++++++----- recipes/rc2014/glue.asm | 2 +- recipes/rc2014/sdcard/README.md | 31 ++++++++--------- recipes/rc2014/sdcard/glue.asm | 13 ++++--- recipes/rc2014/sdcard/helo.asm | 4 ++- tools/emul/shell/shell_.asm | 13 +++++-- 8 files changed, 141 insertions(+), 39 deletions(-) create mode 100644 kernel/pgm.asm diff --git a/doc/glue-code.md b/doc/glue-code.md index 54404a7..a1498bc 100644 --- a/doc/glue-code.md +++ b/doc/glue-code.md @@ -25,17 +25,27 @@ look like: ld sp, hl im 1 call aciaInit - call shellInit + xor a + ld de, BLOCKDEV_GETC + call blkSel + call stdioInit + call shellInit ei - jp shellLoop + jp shellLoop #include "core.asm" .equ ACIA_RAMSTART RAMSTART #include "acia.asm" - .equ SHELL_RAMSTART ACIA_RAMEND - .define SHELL_GETC call aciaGetC - .define SHELL_PUTC call aciaPutC - .define SHELL_IO_GETC call aciaGetC + .equ BLOCKDEV_RAMSTART ACIA_RAMEND + .equ BLOCKDEV_COUNT 1 + #include "blockdev.asm" + ; List of devices + .dw aciaGetC, aciaPutC, 0, 0 + + .equ STDIO_RAMSTART BLOCKDEV_RAMEND + #include "stdio.asm" + + .equ SHELL_RAMSTART STDIO_RAMEND .equ SHELL_EXTRA_CMD_COUNT 0 #include "shell.asm" diff --git a/kernel/pgm.asm b/kernel/pgm.asm new file mode 100644 index 0000000..ead37ac --- /dev/null +++ b/kernel/pgm.asm @@ -0,0 +1,61 @@ +; 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. +; +; *** Defines *** +; PGM_CODEADDR: Memory address where to place the code we load. + +; Routine suitable to plug into SHELL_CMDHOOK. HL points to the full cmdline. +; We can mutate it because the shell doesn't do anything with it afterwards. +pgmShellHook: + call fsIsOn + jr nz, .noFile + ; first first space and replace it with zero so that we have something + ; suitable for fsFindFN. + push hl ; remember beginning + ld a, ' ' + call findchar + jr nz, .noarg ; if we have no arg, we want DE to point to the + ; null char. Also, we have no replacement to + ; make + ; replace space with nullchar + xor a + ld (hl), a + inc hl ; make HL point to the beginning of args +.noarg: + ex de, hl ; DE now points to the beginning of args or to \0 if + ; no args + pop hl ; HL points to cmdname, properly null-terminated + call fsFindFN + jr nz, .noFile + ; We have a file! Load it and run it. + jp pgmRun +.noFile: + ld a, SHELL_ERR_IO_ERROR + ret + +; Loads code in file that FS_PTR is currently pointing at and place it in +; PGM_CODEADDR. Then, jump to PGM_CODEADDR. +pgmRun: + call fsIsValid + jr nz, .ioError + ld ix, FS_HANDLES + call fsOpen + ld hl, PGM_CODEADDR +.loop: + call fsGetC ; we use Z at end of loop + ld (hl), a ; Z preserved + inc hl ; Z preserved in 16-bit + jr z, .loop + ; ready to jump! + jp PGM_CODEADDR + +.ioError: + ld a, SHELL_ERR_IO_ERROR + ret diff --git a/kernel/shell.asm b/kernel/shell.asm index 2d49f63..3f6c28e 100644 --- a/kernel/shell.asm +++ b/kernel/shell.asm @@ -2,11 +2,17 @@ ; ; Runs a shell over a block device interface. -; 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. +; 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. ; -; Commands, for now, are partially implemented. +; 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. ; @@ -59,7 +65,10 @@ ; 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_RAMEND SHELL_BUF+SHELL_BUFSIZE +; Pointer to a hook to call when a cmd name isn't found +.equ SHELL_CMDHOOK SHELL_BUF+SHELL_BUFSIZE + +.equ SHELL_RAMEND SHELL_CMDHOOK+2 ; *** CODE *** shellInit: @@ -67,11 +76,12 @@ shellInit: ld (SHELL_MEM_PTR), a ld (SHELL_MEM_PTR+1), a ld (SHELL_BUF), a + ld hl, noop + ld (SHELL_CMDHOOK), hl ; print welcome ld hl, .welcome - call printstr - ret + jp printstr ; returns .welcome: .db "Collapse OS", ASCII_CR, ASCII_LF, "> ", 0 @@ -154,6 +164,12 @@ shellParse: ; 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: @@ -171,7 +187,7 @@ shellParse: ; We're ready to parse args call shellParseArgs - cp 0 + or a ; cp 0 jr nz, .parseerror ld hl, SHELL_CMD_ARGS @@ -182,7 +198,7 @@ shellParse: push de \ pop ix ; Ready to roll! call callIX - cp 0 + or a ; cp 0 jr nz, .error ; if A is non-zero, we have an error jr .end diff --git a/recipes/rc2014/glue.asm b/recipes/rc2014/glue.asm index 77fba48..7a55b04 100644 --- a/recipes/rc2014/glue.asm +++ b/recipes/rc2014/glue.asm @@ -26,7 +26,7 @@ jp aciaInt #include "stdio.asm" .equ SHELL_RAMSTART STDIO_RAMEND -.equ SHELL_EXTRA_CMD_COUNT 0 +.equ SHELL_EXTRA_CMD_COUNT 0 #include "shell.asm" init: diff --git a/recipes/rc2014/sdcard/README.md b/recipes/rc2014/sdcard/README.md index fd07d6c..38121d2 100644 --- a/recipes/rc2014/sdcard/README.md +++ b/recipes/rc2014/sdcard/README.md @@ -107,22 +107,21 @@ filesystem into the `sdcard.cfs` file. That can be mounted by Collapse OS! Then, you insert your SD card in your SPI relay and go: - Collapse OS - > mptr 9000 - 9000 - > sdci - > bsel 1 - > fson - > fls - helo - hello.txt - > fopn 0 helo - > load 10 - > peek 10 - 210690C3030048656C6C6F210D0A0000 - > call 00 0000 - Hello! - > + Collapse OS + > sdci + > bsel 1 + > fson + > fls + helo + hello.txt + > helo + Hello! + > + +The `helo` command is a bit magical and is due to the hook implemented in +`pgm.asm`: when an unknown command is typed, it looks in the currently mounted +filesystem for a file with the same name. If it finds it, it loads it in memory +at a predefined place (in our case, `0x9000`) and executes it. Now let that sink in for a minute. You've just mounted a filesystem on a SD card, loaded a file from it in memory and executed that file, all that on a diff --git a/recipes/rc2014/sdcard/glue.asm b/recipes/rc2014/sdcard/glue.asm index 8d613ed..fb3b103 100644 --- a/recipes/rc2014/sdcard/glue.asm +++ b/recipes/rc2014/sdcard/glue.asm @@ -2,6 +2,7 @@ ; The RAM module is selected on A15, so it has the range 0x8000-0xffff .equ RAMSTART 0x8000 .equ RAMEND 0xffff +.equ PGM_CODEADDR 0x9000 .equ ACIA_CTL 0x80 ; Control and status. RS off. .equ ACIA_IO 0x81 ; Transmit. RS on. @@ -46,10 +47,12 @@ jp aciaInt .dw sdcInitializeCmd, blkBselCmd, blkSeekCmd .dw fsOnCmd, flsCmd, fnewCmd, fdelCmd, fopnCmd -.equ SDC_RAMSTART SHELL_RAMEND -.equ SDC_PORT_CSHIGH 6 -.equ SDC_PORT_CSLOW 5 -.equ SDC_PORT_SPI 4 +#include "pgm.asm" + +.equ SDC_RAMSTART SHELL_RAMEND +.equ SDC_PORT_CSHIGH 6 +.equ SDC_PORT_CSLOW 5 +.equ SDC_PORT_SPI 4 #include "sdc.asm" init: @@ -64,6 +67,8 @@ init: call blkSel call stdioInit call shellInit + ld hl, pgmShellHook + ld (SHELL_CMDHOOK), hl ei jp shellLoop diff --git a/recipes/rc2014/sdcard/helo.asm b/recipes/rc2014/sdcard/helo.asm index 0c905b3..58b71c7 100644 --- a/recipes/rc2014/sdcard/helo.asm +++ b/recipes/rc2014/sdcard/helo.asm @@ -4,7 +4,9 @@ .org 0x9000 ld hl, sHello - jp printstr ; return + call printstr + xor a ; success + ret sHello: .db "Hello!", 0x0d, 0x0a, 0 diff --git a/tools/emul/shell/shell_.asm b/tools/emul/shell/shell_.asm index 1dad3d7..26d02c5 100644 --- a/tools/emul/shell/shell_.asm +++ b/tools/emul/shell/shell_.asm @@ -1,6 +1,7 @@ ; named shell_.asm to avoid infinite include loop. .equ RAMSTART 0x4000 -.equ RAMEND 0x5000 +.equ KERNEL_RAMEND 0x5000 +.equ USERCODE 0x9000 .equ STDIO_PORT 0x00 .equ FS_DATA_PORT 0x01 .equ FS_SEEKL_PORT 0x02 @@ -9,6 +10,9 @@ jp init +; *** JUMP TABLE *** + jp printstr + #include "core.asm" #include "parse.asm" @@ -36,10 +40,13 @@ #include "shell.asm" .dw blkBselCmd, blkSeekCmd, fsOnCmd, flsCmd, fnewCmd, fdelCmd, fopnCmd +.equ PGM_CODEADDR USERCODE +#include "pgm.asm" + init: di ; setup stack - ld hl, RAMEND + ld hl, KERNEL_RAMEND ld sp, hl xor a ld de, BLOCKDEV_GETC @@ -54,6 +61,8 @@ init: ld de, BLOCKDEV_GETC call blkSel call shellInit + ld hl, pgmShellHook + ld (SHELL_CMDHOOK), hl jp shellLoop emulGetC: