Add tools/emul

This commit is contained in:
Virgil Dupras 2019-05-09 12:58:41 -04:00
parent 02808572e6
commit 193e6e066c
9 changed files with 211 additions and 5 deletions

3
.gitmodules vendored Normal file
View File

@ -0,0 +1,3 @@
[submodule "tools/emul/libz80"]
path = tools/emul/libz80
url = https://github.com/ggambetta/libz80.git

View File

@ -1,9 +1,13 @@
# Running Collapse OS on an emulated RC2014
# Running Collapse OS on an emulator
To give Collapse OS a whirl or to use emulation as a development tool, I
recommend using Alan Cox's [RC2014 emulator][rc2014-emul]. It runs Collapse OS
fine. One caveat, however, is that it requires a ROM image bigger than 8K, so
you have to pad the binary.
The quickest way to give Collapse OS a whirl is to use `tools/emul` which is
built around [libz80][libz80]. Everything is set up, you just have to run
`make`.
To emulate something at a lower level, I recommend using Alan Cox's [RC2014
emulator][rc2014-emul]. It runs Collapse OS fine but you have to write the
glue code yourself. One caveat, also, is that it requires a ROM image bigger
than 8K, so you have to pad the binary.
A working Makefile for a project with a glue code being called `main.asm` could
look like:
@ -27,4 +31,5 @@ look like:
`CTRL+\` stops the emulation.
[libz80]: https://github.com/ggambetta/libz80
[rc2014-emul]: https://github.com/EtchedPixels/RC2014

2
tools/emul/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
/shell
/shell-kernel.h

16
tools/emul/Makefile Normal file
View File

@ -0,0 +1,16 @@
.PHONY: all
all: shell
shell-kernel.h: shell_.asm
scas -o - -I ../../parts/z80 $< | ./bin2c.sh SHELL_KERNEL | tee $@ > /dev/null
shell: shell.c libz80/libz80.o shell-kernel.h
cc $< libz80/libz80.o -o $@
libz80/libz80.o: libz80/z80.c
make -C libz80/codegen opcodes
gcc -Wall -ansi -g -c -o libz80/libz80.o libz80/z80.c
.PHONY: clean
clean:
rm shell shell-kernel.h

26
tools/emul/README.md Normal file
View File

@ -0,0 +1,26 @@
# emul
This is an emulator for a virtual machine that is suitable for running Collapse
OS. The goal of this machine is not to emulate 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.
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.
## Usage
First, make sure that the `libz80` git submodule is checked out. If not, run
`git submodule init && git submodule update`.
The Makefile in this folder has multiple targets that all use libz80 as its
core. For example, `make shell` will build `./shell`, a vanilla Collapse OS
shell. `make zasm` will build a `./zasm` executable, and so on.
See documentation is corresponding source files for usage documentation of
each target.

5
tools/emul/bin2c.sh Executable file
View File

@ -0,0 +1,5 @@
#!/bin/sh
echo "unsigned char $1[] = { "
xxd -i -
echo " };"

1
tools/emul/libz80 Submodule

@ -0,0 +1 @@
Subproject commit 8a1f935daa3c288b68121051e8e45068684f80a4

102
tools/emul/shell.c Normal file
View File

@ -0,0 +1,102 @@
#include <stdint.h>
#include <stdio.h>
#include <termios.h>
#include "libz80/z80.h"
#include "shell-kernel.h"
/* Collapse OS vanilla shell
*
* Memory layout:
*
* 0x0000 - 0x3fff: ROM code from shell.asm
* 0x4000 - 0x4fff: Kernel memory
* 0x5000 - 0xffff: Userspace
*
* I/O Ports:
*
* 0 - stdin / stdout
*/
// in sync with shell.asm
#define RAMSTART 0x4000
#define STDIO_PORT 0x00
#define STDIN_ST_PORT 0x01
static Z80Context cpu;
static uint8_t mem[0xffff];
static int running;
static uint8_t io_read(int unused, uint16_t addr)
{
addr &= 0xff;
if (addr == STDIO_PORT) {
uint8_t c = getchar();
if (c == EOF) {
running = 0;
}
return c;
} 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 {
fprintf(stderr, "Out of bounds I/O write: %d / %d\n", addr, 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)
{
mem[addr] = val;
}
int main()
{
// 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(SHELL_KERNEL); i++) {
mem[i] = SHELL_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) {
Z80Execute(&cpu);
}
printf("Done!\n");
termInfo.c_lflag |= ECHO;
termInfo.c_lflag |= ICANON;
tcsetattr(0, TCSAFLUSH, &termInfo);
return 0;
}

46
tools/emul/shell_.asm Normal file
View File

@ -0,0 +1,46 @@
; named shell_.asm to avoid infinite include loop.
RAMSTART .equ 0x4000
RAMEND .equ 0x5000
STDIO_PORT .equ 0x00
jr init
init:
di
; setup stack
ld hl, RAMEND
ld sp, hl
call shellInit
jp shellLoop
#include "core.asm"
.define STDIO_GETC call emulGetC
.define STDIO_PUTC call emulPutC
STDIO_RAMSTART .equ RAMEND
#include "stdio.asm"
BLOCKDEV_RAMSTART .equ STDIO_RAMEND
BLOCKDEV_COUNT .equ 1
#include "blockdev.asm"
; List of devices
.dw emulGetC, emulPutC, 0, 0
#include "blockdev_cmds.asm"
SHELL_RAMSTART .equ BLOCKDEV_RAMEND
.define SHELL_IO_GETC call blkGetCW
.define SHELL_IO_PUTC call blkPutC
SHELL_EXTRA_CMD_COUNT .equ 2
#include "shell.asm"
.dw blkBselCmd, blkSeekCmd
emulGetC:
; Blocks until a char is returned
in a, (STDIO_PORT)
cp a ; ensure Z
ret
emulPutC:
out (STDIO_PORT), a
ret