emul/8086: add PC/AT emulator

This commit is contained in:
Virgil Dupras 2020-10-25 10:27:52 -04:00
parent c912158744
commit ba21b6a9f5
10 changed files with 175 additions and 16 deletions

View File

@ -1,2 +1,3 @@
/forth /forth
/forth.bin /forth.bin
/pcat

View File

@ -1,4 +1,4 @@
TARGETS = forth TARGETS = forth pcat
OBJS = cpu.o OBJS = cpu.o
CDIR = ../../cvm CDIR = ../../cvm
STAGE = $(CDIR)/stage STAGE = $(CDIR)/stage
@ -13,6 +13,9 @@ forth: forth.c forth.bin $(OBJS) $(BLKFS)
forth.bin: xcomp.fs $(STAGE) forth.bin: xcomp.fs $(STAGE)
$(CDIR)/stage < xcomp.fs > $@ $(CDIR)/stage < xcomp.fs > $@
pcat: pcat.c $(OBJS) $(BLKFS)
$(CC) -DBLKFS_PATH=\"`pwd`/$(BLKFS)\" pcat.c $(OBJS) -lncurses -o $@
$(BLKFS): $(STAGE) $(BLKFS): $(STAGE)
$(STAGE): $(STAGE):

View File

@ -1,8 +1,28 @@
# 8086 emulator # 8086 emulator
This is a work in progress. The goal is to have something lighter than QEMU to This folder contains emulator for 8086 binaries of Collapse OS. The bulk of
run Collapse OS on and also something that is easier to plug with my stuff. it is a fork of Fake86 by Mike Chambers.
Something I could run without BIOS (have my own studs for INT 10 and INT 13).
My first try was with 8086tiny, but this code is messy. Fake86 is a bit bigger, `forth` is an imaginary hardware used for userspace development and testing.
but also cleaner. This machine has an imaginary interrupt API and does not conform to PC/AT.
`pcat` is a very simple PC/AT emulator. The BIOS interrupt hooks implemented in
it only cover Collapse OS' own needs.
## Requirements
You need `ncurses` to build the `forth` executable. In debian-based distros,
it's `libncurses5-dev`.
## Build
Run `make` and it builds the `forth` and `pcat` interpreters.
## Usage
The `./forth` executable here works like the one in `/cvm`, except that it runs
under an emulated 8086 machine instead of running natively. Refer to
`/cvm/README.md` for details.
`pcat` needs to be suppied a path to a floppy disk image with a proper MBR.
`disk.bin` provided by the `pcat` recipe is sufficient.

View File

@ -3399,8 +3399,8 @@ int exec86(int execloops) {
return 1; return 1;
} }
void reset86() { void reset86(uint16_t startip) {
segregs[regcs] = 0; segregs[regcs] = 0;
ip = 0; ip = startip;
hltstate = 0; hltstate = 0;
} }

View File

@ -52,4 +52,4 @@ void writew86 (uint32_t addr32, uint16_t value);
uint8_t read86 (uint32_t addr32); uint8_t read86 (uint32_t addr32);
uint16_t readw86 (uint32_t addr32); uint16_t readw86 (uint32_t addr32);
int exec86(int execloops); // returns 0 if halted int exec86(int execloops); // returns 0 if halted
void reset86(); void reset86(uint16_t startip);

View File

@ -93,7 +93,7 @@ int main(int argc, char *argv[])
INTHOOKS[4] = int4; INTHOOKS[4] = int4;
INTHOOKS[5] = int5; INTHOOKS[5] = int5;
INTHOOKS[6] = int6; INTHOOKS[6] = int6;
reset86(); reset86(0);
fprintf(stderr, "Using blkfs %s\n", BLKFS_PATH); fprintf(stderr, "Using blkfs %s\n", BLKFS_PATH);
blkfp = fopen(BLKFS_PATH, "r+"); blkfp = fopen(BLKFS_PATH, "r+");
if (!blkfp) { if (!blkfp) {

127
emul/8086/pcat.c Normal file
View File

@ -0,0 +1,127 @@
#include <stdint.h>
#include <stdio.h>
#include <unistd.h>
#include <curses.h>
#include "cpu.h"
#define WCOLS 80
#define WLINES 25
#define SEC_PER_TRK 0x3f
#define TRK_PER_HEAD 0xff
extern uint8_t byteregtable[8];
extern union _bytewordregs_ regs;
extern uint16_t segregs[4];
extern INTHOOK INTHOOKS[0x100];
static FILE *fp;
WINDOW *bw, *dw, *w;
static void int10() {
uint8_t cmd = regs.byteregs[regah];
uint8_t al = regs.byteregs[regal];
uint16_t dx = regs.wordregs[regdx];
switch (cmd) {
case 0x02: // at-xy
wmove(w, dx&0xff, dx>>8);
break;
case 0x0e: // emit
if (al >= 0x20 || al == '\n') {
wechochar(w, al);
} else if (al == 0x08) {
int y, x; getyx(w, y, x);
wmove(w, y, x-1);
}
break;
}
}
static void int13() {
uint8_t cmd = regs.byteregs[regah];
uint8_t al = regs.byteregs[regal];
uint8_t cl = regs.byteregs[regcl];
uint8_t ch = regs.byteregs[regch];
uint8_t dh = regs.byteregs[regdh];
uint16_t bx = regs.wordregs[regbx];
switch (cmd) {
case 0x02: // read disk sector(s)
case 0x03: // write disk sector(s)
// CL = sector number (1-based), AL = sector count
// DH = head number, CH = track number
// ES:BX = dest addr
fseek(fp, ((((dh*TRK_PER_HEAD)+ch)*SEC_PER_TRK)+cl-1)*512, SEEK_SET);
for (int i=0; i<(al*512); i++) {
if (cmd == 0x03) { // write
fputc(getmem8(segregs[reges], bx+i), fp);
} else { // read
putmem8(segregs[reges], bx+i, fgetc(fp));
}
}
break;
case 0x08: // poll sectors per track / per head
// we just report a lot of them
regs.wordregs[regcx] = SEC_PER_TRK;
regs.byteregs[regdh] = TRK_PER_HEAD-1;
break;
}
}
static void int16() {
int c;
// debug_panel();
c = wgetch(w);
regs.byteregs[regal] = c;
}
static void usage()
{
fprintf(stderr, "Usage: ./pcat /path/to/fd\n");
}
int main(int argc, char *argv[])
{
if (argc < 2) {
usage();
return 1;
}
INTHOOKS[0x10] = int10;
INTHOOKS[0x13] = int13;
INTHOOKS[0x16] = int16;
reset86(0x7c00);
// initialize memory
fp = fopen(argv[1], "r");
if (!fp) {
fprintf(stderr, "Can't open %s\n", argv[1]);
return 1;
}
// read MBR into RAM
for (int i=0; i<512; i++) {
int c = getc(fp);
if (c != EOF) {
write86(0x7c00+i, c);
}
}
uint16_t magic = readw86(0x7dfe);
if (magic != 0xaa55) {
fprintf(stderr, "Invalid MBR magic %x\n", magic);
return 1;
}
initscr(); cbreak(); noecho(); nl(); clear();
// border window
bw = newwin(WLINES+2, WCOLS+2, 0, 0);
wborder(bw, 0, 0, 0, 0, 0, 0, 0, 0);
wrefresh(bw);
// debug panel
dw = newwin(1, 30, LINES-1, COLS-30);
w = newwin(WLINES, WCOLS, 1, 1);
scrollok(w, 1);
while (exec86(100)) {
//debug_panel();
}
nocbreak(); echo(); delwin(w); delwin(bw); delwin(dw); endwin();
printf("\nDone!\n");
//emul_printdebug();
fclose(fp);
return 0;
}

View File

@ -3,6 +3,7 @@ BASE = ../..
CDIR = $(BASE)/cvm CDIR = $(BASE)/cvm
BLKPACK = $(BASE)/tools/blkpack BLKPACK = $(BASE)/tools/blkpack
STAGE = $(CDIR)/stage STAGE = $(CDIR)/stage
EMUL = $(BASE)/emul/8086/pcat
.PHONY: all .PHONY: all
all: $(TARGET) all: $(TARGET)
@ -10,7 +11,7 @@ os.bin: xcomp.fs $(STAGE) blkfs
$(STAGE) blkfs < xcomp.fs > $@ $(STAGE) blkfs < xcomp.fs > $@
$(BLKPACK): $(BLKPACK):
$(MAKE) -C ../tools $(MAKE) -C $(BASE)/tools
blkfs: $(BLKPACK) blkfs: $(BLKPACK)
$(BLKPACK) $(BASE)/blk blk > $@ $(BLKPACK) $(BASE)/blk blk > $@
@ -25,9 +26,12 @@ $(TARGET): mbr.bin os.bin
cat mbr.bin os.bin > $@ cat mbr.bin os.bin > $@
dd if=blkfs of=$@ bs=512 seek=16 dd if=blkfs of=$@ bs=512 seek=16
$(EMUL):
$(MAKE) -C $(BASE)/emul/8086
.PHONY: emul .PHONY: emul
emul: $(TARGET) emul: $(TARGET) $(EMUL)
qemu-system-i386 -drive file=$(TARGET),if=floppy,format=raw $(EMUL) $(TARGET)
.PHONY: clean .PHONY: clean
clean: clean:

View File

@ -12,7 +12,6 @@ is bootable on a modern PC-compatible machine.
* A modern PC-compatible machine that can boot from a USB drive. * A modern PC-compatible machine that can boot from a USB drive.
* A USB drive * A USB drive
* qemu for emulation
## Build the binary ## Build the binary
@ -30,7 +29,12 @@ can run `VE`.
## Emulation ## Emulation
You can run the built binary in qemu using `make emul`. You can run the built binary in Collapse OS' 8086 emulator using `make emul`.
The 8086 emulator is barbone. If you prefer to use it on a more realistic
setting, use QEMU. The command is:
qemu-system-i386 -drive file=disk.bin,if=floppy,format=raw
## Running on a modern PC ## Running on a modern PC

View File

@ -2,7 +2,7 @@
( AH=read sectors, AL=1 sector, BX=dest, ( AH=read sectors, AL=1 sector, BX=dest,
CH=trackno CL=secno DH=head DL=drive ) CH=trackno CL=secno DH=head DL=drive )
FDSPT C@ /MOD ( AX BX sec trk ) FDSPT C@ /MOD ( AX BX sec trk )
FDHEADS C@ /MOD ( AX BX sec head trk ) FDHEADS C@ /MOD SWAP ( AX BX sec head trk )
8 LSHIFT ROT OR 1+ ( AX BX head CX ) 8 LSHIFT ROT OR 1+ ( AX BX head CX )
SWAP 8 LSHIFT 0x03 C@ ( boot drive ) OR ( AX BX CX DX ) SWAP 8 LSHIFT 0x03 C@ ( boot drive ) OR ( AX BX CX DX )
13H 2DROP 2DROP 13H 2DROP 2DROP