Переглянути джерело

basic: add fls command

Also, add the new `bshell` emulated tool. BASIC is on its way to replace the
shell.
pull/85/head
Virgil Dupras 4 роки тому
джерело
коміт
13f935aa88
10 змінених файлів з 508 додано та 22 видалено
  1. +21
    -0
      apps/basic/README.md
  2. +15
    -0
      apps/basic/fs.asm
  3. +1
    -0
      apps/basic/glue.asm
  4. +45
    -19
      apps/basic/main.asm
  5. +1
    -0
      tools/emul/.gitignore
  6. +10
    -3
      tools/emul/Makefile
  7. +5
    -0
      tools/emul/README.md
  8. +172
    -0
      tools/emul/bshell/glue.asm
  9. +203
    -0
      tools/emul/bshell/shell.c
  10. +35
    -0
      tools/emul/bshell/user.h

+ 21
- 0
apps/basic/README.md Переглянути файл

@@ -25,6 +25,16 @@ Because the goal is not to provide a foundation for complex programs, I'm
planning on intentionally crippling this BASIC dialect for the sake of
simplicity.

## Glueing

The `glue.asm` file in this folder represents the minimal basic system. There
are additional modules that can be added that aren't added by default, such
as `fs.asm` because they require kernel options that might not be available.

To include these modules, you'll need to write your own glue file and to hook
extra commands through `BAS_FINDHOOK`. Look for examples in `tools/emul` and
in recipes.

## Usage

Upon launch, a prompt is presented, waiting for a command. There are two types
@@ -123,3 +133,14 @@ output I/O on port 42 with value 3.

**sleep**: Sleep a number of "units" specified by the supplied expression. A
"unit" depends on the CPU clock speed. At 4MHz, it is roughly 8 microseconds.

## Optional modules

As explained in "glueing" section abolve, this folder contains optional modules.
Here's the documentation for them.

### fs

`fs.asm` provides those commands:

**fls**: prints the list of files contained in the active filesystem.

+ 15
- 0
apps/basic/fs.asm Переглянути файл

@@ -0,0 +1,15 @@
; FS-related basic commands

basFLS:
ld iy, .iter
jp fsIter
.iter:
ld a, FS_META_FNAME_OFFSET
call addHL
call printstr
jp printcrlf

basFSCmds:
.dw basFLS
.db "fls", 0, 0, 0
.db 0xff, 0xff, 0xff ; end of table

+ 1
- 0
apps/basic/glue.asm Переглянути файл

@@ -7,6 +7,7 @@
.inc "user.h"
.inc "err.h"

call basInit
jp basStart

; RAM space used in different routines for short term processing.


+ 45
- 19
apps/basic/main.asm Переглянути файл

@@ -7,16 +7,24 @@
; Important note: this is **not** a line number, it's a pointer to a line index
; in buffer. If it's not zero, its a valid pointer.
.equ BAS_PNEXTLN @+2
; Points to a routine to call when a command isn't found in the "core" cmd
; table. This gives the opportunity to glue code to configure extra commands.
.equ BAS_FINDHOOK @+2
.equ BAS_RAMEND @+2

; *** Code ***
basStart:
basInit:
ld (BAS_INITSP), sp
call varInit
call bufInit
xor a
ld (BAS_PNEXTLN), a
ld (BAS_PNEXTLN+1), a
ld hl, unsetZ
ld (BAS_FINDHOOK), hl
ret

basStart:
ld hl, .welcome
call printstr
call printcrlf
@@ -48,22 +56,14 @@ basLoop:
.sPrompt:
.db "> ", 0

; Call command in (HL) after having looked for it in cmd table in (DE).
; If found, jump to it. If not found, unset Z. We expect commands to set Z
; on success. Therefore, when calling basCallCmd results in NZ, we're not sure
; where the error come from, but well...
basCallCmd:
; let's see if it's a variable assignment.
call varTryAssign
ret z ; Done!
push de ; --> lvl 1.
ld de, SCRATCHPAD
call rdWord
; cmdname to find in (DE)
; How lucky, we have a legitimate use of "ex (sp), hl"! We have the
; cmd table in the stack, which we want in HL and we have the rest of
; the cmdline in (HL), which we want in the stack!
ex (sp), hl
; Tries to find command specified in (DE) (must be null-terminated) in cmd
; table in (HL). If found, sets IX to point to the associated routine. If
; not found, calls BAS_FINDHOOK so that we look through extra commands
; configured by glue code.
; Destroys HL.
; Z is set if found, unset otherwise.
basFindCmd:
; cmd table starts with routine pointer, skip
inc hl \ inc hl
.loop:
call strcmp
@@ -73,15 +73,41 @@ basCallCmd:
ld a, (hl)
cp 0xff
jr nz, .loop
; not found
pop hl ; <-- lvl 1
jp unsetZ
.found:
dec hl \ dec hl
call intoHL
push hl \ pop ix
ret

; Call command in (HL) after having looked for it in cmd table in (DE).
; If found, jump to it. If not found, try (BAS_FINDHOOK). If still not found,
; unset Z. We expect commands to set Z on success. Therefore, when calling
; basCallCmd results in NZ, we're not sure where the error come from, but
; well...
basCallCmd:
; let's see if it's a variable assignment.
call varTryAssign
ret z ; Done!
push de ; --> lvl 1.
ld de, SCRATCHPAD
call rdWord
; cmdname to find in (DE)
; How lucky, we have a legitimate use of "ex (sp), hl"! We have the
; cmd table in the stack, which we want in HL and we have the rest of
; the cmdline in (HL), which we want in the stack!
ex (sp), hl
call basFindCmd
jr z, .skip
; not found, try BAS_FINDHOOK
ld ix, (BAS_FINDHOOK)
call callIX
.skip:
; regardless of the result, we need to balance the stack.
; Bring back rest of the command string from the stack
pop hl ; <-- lvl 1
ret nz
; cmd found, skip whitespace and then jump!
call rdSep
jp (ix)



+ 1
- 0
tools/emul/.gitignore Переглянути файл

@@ -1,4 +1,5 @@
/shell/shell
/bshell/shell
/zasm/zasm
/runbin/runbin
/*/*-bin.h


+ 10
- 3
tools/emul/Makefile Переглянути файл

@@ -1,10 +1,10 @@
CFSPACK = ../cfspack/cfspack
TARGETS = shell/shell zasm/zasm runbin/runbin
TARGETS = shell/shell bshell/shell zasm/zasm runbin/runbin
KERNEL = ../../kernel
APPS = ../../apps
ZASMBIN = zasm/zasm
ZASMSH = ../zasm.sh
SHELLAPPS = $(addprefix cfsin/, zasm ed basic)
SHELLAPPS = $(addprefix cfsin/, zasm ed)
CFSIN_CONTENTS = $(SHELLAPPS) cfsin/user.h

.PHONY: all
@@ -17,6 +17,12 @@ shell/shell.bin: $(APPS)/shell/glue.asm $(ZASMBIN)
shell/kernel-bin.h: shell/glue.asm shell/shell.bin $(ZASMBIN)
$(ZASMSH) $(KERNEL) shell/shell.bin < $< | ./bin2c.sh KERNEL | tee $@ > /dev/null

bshell/shell.bin: bshell/glue.asm $(ZASMBIN)
$(ZASMSH) $(KERNEL) bshell/user.h $(APPS) < $< | tee $@ > /dev/null

bshell/shell-bin.h: bshell/shell.bin
./bin2c.sh KERNEL < $< | tee $@ > /dev/null

zasm/kernel-bin.h: zasm/kernel.bin
./bin2c.sh KERNEL < $< | tee $@ > /dev/null

@@ -24,6 +30,7 @@ zasm/zasm-bin.h: zasm/zasm.bin
./bin2c.sh USERSPACE < $< | tee $@ > /dev/null

shell/shell: shell/shell.c libz80/libz80.o shell/kernel-bin.h
bshell/shell: bshell/shell.c libz80/libz80.o bshell/shell-bin.h
$(ZASMBIN): zasm/zasm.c libz80/libz80.o zasm/kernel-bin.h zasm/zasm-bin.h $(CFSPACK)
runbin/runbin: runbin/runbin.c libz80/libz80.o
$(TARGETS):
@@ -50,4 +57,4 @@ updatebootstrap: $(ZASMBIN) $(INCCFS)

.PHONY: clean
clean:
rm -f $(TARGETS) $(SHELLAPPS) {zasm,shell}/*-bin.h
rm -f $(TARGETS) $(SHELLAPPS) zasm/*-bin.h shell/*-bin.h

+ 5
- 0
tools/emul/README.md Переглянути файл

@@ -25,6 +25,11 @@ 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


+ 172
- 0
tools/emul/bshell/glue.asm Переглянути файл

@@ -0,0 +1,172 @@
.inc "blkdev.h"
.inc "fs.h"
.inc "err.h"
.inc "ascii.h"
.equ RAMSTART 0x4000
.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 BAS_RAMSTART BUF_RAMEND
.inc "basic/main.asm"

; Extra cmds
.inc "basic/fs.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
jp basFindCmd

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

+ 203
- 0
tools/emul/bshell/shell.c Переглянути файл

@@ -0,0 +1,203 @@
#include <stdint.h>
#include <stdio.h>
#include <termios.h>
#include "../libz80/z80.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 shell.asm
#define RAMSTART 0x4000
#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 Z80Context cpu;
static uint8_t mem[0x10000] = {0};
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 io_read(int unused, uint16_t addr)
{
addr &= 0xff;
if (addr == STDIO_PORT) {
int c = getchar();
if (c == EOF) {
running = 0;
}
return (uint8_t)c;
} else if (addr == FS_DATA_PORT) {
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;
}
} else if (addr == FS_ADDR_PORT) {
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;
}
} else {
fprintf(stderr, "Out of bounds I/O read: %d\n", addr);
return 0;
}
}

static void io_write(int unused, uint16_t addr, uint8_t val)
{
addr &= 0xff;
if (addr == STDIO_PORT) {
if (val == 0x04) { // CTRL+D
running = 0;
} else {
putchar(val);
}
} else if (addr == FS_DATA_PORT) {
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);
}
} else if (addr == FS_ADDR_PORT) {
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;
}
} else {
fprintf(stderr, "Out of bounds I/O write: %d / %d (0x%x)\n", addr, val, val);
}
}

static uint8_t mem_read(int unused, uint16_t addr)
{
return mem[addr];
}

static void mem_write(int unused, uint16_t addr, uint8_t val)
{
if (addr < RAMSTART) {
fprintf(stderr, "Writing to ROM (%d)!\n", addr);
}
mem[addr] = val;
}

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);


// initialize memory
for (int i=0; i<sizeof(KERNEL); i++) {
mem[i] = KERNEL[i];
}
// Run!
running = 1;
Z80RESET(&cpu);
cpu.ioRead = io_read;
cpu.ioWrite = io_write;
cpu.memRead = mem_read;
cpu.memWrite = mem_write;

while (running && !cpu.halted) {
Z80Execute(&cpu);
}

printf("Done!\n");
termInfo.c_lflag |= ECHO;
termInfo.c_lflag |= ICANON;
tcsetattr(0, TCSAFLUSH, &termInfo);
return 0;
}

+ 35
- 0
tools/emul/bshell/user.h Переглянути файл

@@ -0,0 +1,35 @@
.equ SHELL_RAMSTART 0x4100
.equ USER_CODE 0x4200

; *** 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


Завантаження…
Відмінити
Зберегти