Browse Source

Replace old shell with BASIC shell

fixes #80
pull/85/head
Virgil Dupras 4 years ago
parent
commit
25d25d017c
22 changed files with 102 additions and 1351 deletions
  1. +0
    -9
      apps/basic/main.asm
  2. +0
    -98
      apps/shell/README.md
  3. +0
    -118
      apps/shell/blkdev.asm
  4. +0
    -75
      apps/shell/fs.asm
  5. +0
    -35
      apps/shell/glue.asm
  6. +0
    -22
      apps/shell/gluem.asm
  7. +0
    -361
      apps/shell/main.asm
  8. +0
    -51
      apps/shell/pgm.asm
  9. +21
    -6
      recipes/rc2014/README.md
  10. +0
    -10
      recipes/rc2014/basic/Makefile
  11. +0
    -46
      recipes/rc2014/basic/README.md
  12. +0
    -57
      recipes/rc2014/basic/glue.asm
  13. +23
    -12
      recipes/rc2014/glue.asm
  14. +3
    -3
      recipes/rc2014/zasm/README.md
  15. +6
    -15
      tools/emul/Makefile
  16. +5
    -10
      tools/emul/README.md
  17. +0
    -178
      tools/emul/bshell/glue.asm
  18. +0
    -191
      tools/emul/bshell/shell.c
  19. +0
    -35
      tools/emul/bshell/user.h
  20. +40
    -15
      tools/emul/shell/glue.asm
  21. +3
    -3
      tools/emul/shell/shell.c
  22. +1
    -1
      tools/emul/shell/user.h

+ 0
- 9
apps/basic/main.asm View File

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


+ 0
- 98
apps/shell/README.md View File

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

+ 0
- 118
apps/shell/blkdev.asm View File

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


+ 0
- 75
apps/shell/fs.asm View File

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

+ 0
- 35
apps/shell/glue.asm View File

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

+ 0
- 22
apps/shell/gluem.asm View File

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


+ 0
- 361
apps/shell/main.asm View File

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


+ 0
- 51
apps/shell/pgm.asm View File

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

+ 21
- 6
recipes/rc2014/README.md View File

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


+ 0
- 10
recipes/rc2014/basic/Makefile View File

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


+ 0
- 46
recipes/rc2014/basic/README.md View File

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

+ 0
- 57
recipes/rc2014/basic/glue.asm View File

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



+ 23
- 12
recipes/rc2014/glue.asm View File

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



+ 3
- 3
recipes/rc2014/zasm/README.md View File

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



+ 6
- 15
tools/emul/Makefile View File

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



+ 5
- 10
tools/emul/README.md View File

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


+ 0
- 178
tools/emul/bshell/glue.asm View File

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

+ 0
- 191
tools/emul/bshell/shell.c View File

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

+ 0
- 35
tools/emul/bshell/user.h View File

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


+ 40
- 15
tools/emul/shell/glue.asm View File

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

+ 3
- 3
tools/emul/shell/shell.c View File

@@ -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
- 1
tools/emul/shell/user.h View File

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


Loading…
Cancel
Save