Skip to content

Instantly share code, notes, and snippets.

@Manawyrm
Created September 2, 2020 10:08
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Manawyrm/d5d9c6d5d7fcd00ce20ac755aaffdbf6 to your computer and use it in GitHub Desktop.
Save Manawyrm/d5d9c6d5d7fcd00ce20ac755aaffdbf6 to your computer and use it in GitHub Desktop.
#include <stdio.h>
#include <stdlib.h>
#include <getopt.h>
#include <x86emu.h>
#include <string.h>
#include <stdint.h>
#include <fcntl.h> // Contains file controls like O_RDWR
#include <errno.h> // Error integer and strerror() function
#include <termios.h> // Contains POSIX terminal control definitions
#include <unistd.h> // write(), read(), close()
#include <assert.h>
#include <signal.h>
void flush_log(x86emu_t *emu, char *buf, unsigned size);
x86emu_t* emu_new(void);
int emu_init(x86emu_t *emu, char *file);
void emu_run(char *file);
#define VBIOS_ROM 0xc0000
#define VBIOS_ROM_SIZE 0x10000
#define VBIOS_MEM 0xa0000
#define VBIOS_MEM_SIZE 0x10000
#define VBE_BUF 0x8000
uint8_t loaded_rom = 0;
static void vm_write_byte(x86emu_t *emu, unsigned addr, unsigned val, unsigned perm);
static void vm_write_word(x86emu_t *emu, unsigned addr, unsigned val, unsigned perm);
void vm_write_byte(x86emu_t *emu, unsigned addr, unsigned val, unsigned perm)
{
x86emu_write_byte_noperm(emu, addr, val);
x86emu_set_perm(emu, addr, addr, perm | X86EMU_PERM_VALID);
}
void vm_write_word(x86emu_t *emu, unsigned addr, unsigned val, unsigned perm)
{
x86emu_write_byte_noperm(emu, addr, val);
x86emu_write_byte_noperm(emu, addr + 1, val>> 8);
x86emu_set_perm(emu, addr, addr + 1, perm | X86EMU_PERM_VALID);
}
int serial;
void init_serial()
{
serial = open("/dev/ttyUSB0", O_RDWR);
if (serial < 0)
{
printf("Error %i from open: %s\n", errno, strerror(errno));
}
struct termios tty;
// Read in existing settings, and handle any error
if (tcgetattr(serial, &tty) != 0)
{
printf("Error %i from tcgetattr: %s\n", errno, strerror(errno));
}
tty.c_cflag &= ~PARENB; // Clear parity bit, disabling parity (most common)
tty.c_cflag &= ~CSTOPB; // Clear stop field, only one stop bit used in communication (most common)
tty.c_cflag |= CS8; // 8 bits per byte (most common)
tty.c_cflag &= ~CRTSCTS; // Disable RTS/CTS hardware flow control (most common)
tty.c_cflag |= CREAD | CLOCAL; // Turn on READ &ignore ctrl lines (CLOCAL = 1)
tty.c_lflag &= ~ICANON;
tty.c_lflag &= ~ECHO; // Disable echo
tty.c_lflag &= ~ECHOE; // Disable erasure
tty.c_lflag &= ~ECHONL; // Disable new-line echo
tty.c_lflag &= ~ISIG; // Disable interpretation of INTR, QUIT and SUSP
tty.c_iflag &= ~(IXON | IXOFF | IXANY); // Turn off s/w flow ctrl
tty.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL); // Disable any special handling of received bytes
tty.c_oflag &= ~OPOST; // Prevent special interpretation of output bytes (e.g. newline chars)
tty.c_oflag &= ~ONLCR; // Prevent conversion of newline to carriage return/line feed
tty.c_cc[VTIME] = 0; // Wait for up to 1s (10 deciseconds), returning as soon as any data is received.
tty.c_cc[VMIN] = 0;
cfsetispeed(&tty, B115200);
cfsetospeed(&tty, B115200);
if (tcsetattr(serial, TCSANOW, &tty) != 0)
{
printf("Error %i from tcsetattr: %s\n", errno, strerror(errno));
}
// wait for readyness
char read_buf[256];
while (1)
{
int n = read(serial, &read_buf, sizeof(read_buf));
for (int i = 0; i < n; ++i)
{
if (read_buf[i] == 'R')
{
//printf("%s\n", read_buf);
printf("hardware init done.\n");
return;
}
}
}
}
void outb(uint32_t address, uint8_t data)
{
uint8_t buffer[100];
uint16_t length = snprintf(buffer, 100, "o%04X%02X\n", address, data);
write(serial, buffer, length);
//printf("%s\n", buffer);
// wait for return
char read_buf[256];
while (1)
{
int n = read(serial, &read_buf, sizeof(read_buf));
for (int i = 0; i < n; ++i)
{
if (read_buf[i] == 'o')
{
return;
}
}
}
}
uint8_t inb(uint32_t address)
{
uint8_t buffer[100];
uint16_t length = snprintf(buffer, 100, "i%04X\n", address);
write(serial, buffer, length);
//printf("%s\n", buffer);
// wait for return
char read_buf[256];
uint16_t offset = 0;
while (1)
{
int n = read(serial, read_buf + offset, (sizeof(read_buf) - offset));
for (int i = 0; i < (offset + n); ++i)
{
if (read_buf[i] == '\n')
{
read_buf[i + 1] = 0x00;
//printf("%s\n", read_buf);
return strtol(read_buf + 1, NULL, 16);
}
}
offset += n;
}
}
void memoutb(uint32_t address, uint8_t data)
{
uint8_t buffer[100];
uint16_t length = snprintf(buffer, 100, "w%06X%02X\n", address, data);
write(serial, buffer, length);
//printf("%s\n", buffer);
// wait for return
char read_buf[256];
while (1)
{
int n = read(serial, &read_buf, sizeof(read_buf));
for (int i = 0; i < n; ++i)
{
if (read_buf[i] == 'w')
{
return;
}
}
}
}
uint8_t meminb(uint32_t address)
{
uint8_t buffer[100];
uint16_t length = snprintf(buffer, 100, "r%06X\n", address);
write(serial, buffer, length);
//printf("%s\n", buffer);
// wait for return
char read_buf[256];
uint16_t offset = 0;
while (1)
{
int n = read(serial, read_buf + offset, (sizeof(read_buf) - offset));
for (int i = 0; i < (offset + n); ++i)
{
if (read_buf[i] == '\n')
{
read_buf[i + 1] = 0x00;
//printf("%s\n", read_buf);
return strtol(read_buf + 1, NULL, 16);
}
}
offset += n;
}
}
struct
{
struct
{
unsigned segment;
unsigned offset;
}
start;
unsigned load;
unsigned max_instructions;
unsigned bits_32: 1;
char *file;
}
opt;
/*
*Parse options, then run emulation.
*/
int main(int argc, char **argv)
{
int i;
char *str;
opt.start.segment = 0;
opt.start.offset = opt.load = 0x7c00;
opt.max_instructions = 0;
emu_run("");
return 0;
}
/*
*Write emulation log to console.
*/
void flush_log(x86emu_t *emu, char *buf, unsigned size)
{
if (!buf || !size) return;
fwrite(buf, size, 1, stdout);
}
x86emu_t * globalemu;
void sigfunc(int sig)
{
int c;
if (sig != SIGINT)
return;
else
{
//unsigned flags = X86EMU_RUN_NO_CODE | X86EMU_RUN_TIMEOUT;
//x86emu_stop(globalemu);
/*x86emu_dump(globalemu, X86EMU_DUMP_DEFAULT | X86EMU_DUMP_ACC_MEM);
x86emu_clear_log(globalemu, 1);
x86emu_run(globalemu, flags);
*/
}
}
/*
*Create new emulation object.
*/
x86emu_t* emu_new()
{
x86emu_t *emu = x86emu_new(X86EMU_PERM_RWX, X86EMU_PERM_RWX);
/*log buf size of 1000000 is purely arbitrary */
x86emu_set_log(emu, 10000000, flush_log);
emu->log.trace = X86EMU_TRACE_DEFAULT;
return emu;
}
x86emu_memio_handler_t old_memio;
unsigned emu_memio_handler(x86emu_t *emu, u32 addr, u32 *val, unsigned type)
{
unsigned bits = type &0xff;
unsigned mytype = type &~0xff;
if (mytype == X86EMU_MEMIO_O)
{
printf("[memio] output! Addr: %08lx Val: %08lx\n", addr, *val);
outb(addr, *val);
return 0;
}
if (mytype == X86EMU_MEMIO_I)
{
uint8_t data = inb(addr);
printf("[memio] input! Addr: %08lx Read value: %02x\n", addr, data);
*val = data;
return 0;
}
if (mytype == X86EMU_MEMIO_R || mytype == X86EMU_MEMIO_W || mytype == X86EMU_MEMIO_X)
{
if ((addr >= 0x000A0000 && addr <= 0x000BFFFF) || (addr >= 0x000C0000 && addr <= 0x000C7FFF && loaded_rom == 0))
{
//printf("[memiorw] debug addr: %08lx Data:%02x\n", addr,*val);
//x86emu_set_perm(emu, addr, addr, X86EMU_PERM_RWX | X86EMU_PERM_VALID);
switch (bits)
{
case X86EMU_MEMIO_8_NOPERM:
case X86EMU_MEMIO_8:
if (mytype == X86EMU_MEMIO_R || mytype == X86EMU_MEMIO_X)
{
uint8_t data = meminb(addr);
*val = data;
printf("[memiorw] 8 RX addr: %08lx Data:%02x\n", addr, data);
}
if (mytype == X86EMU_MEMIO_W)
{
memoutb(addr, *val);
printf("[memiorw] 8 W addr: %08lx Data:%02x\n", addr, *val);
}
return 0;
break;
case X86EMU_MEMIO_16:
if (mytype == X86EMU_MEMIO_R || mytype == X86EMU_MEMIO_X)
{
uint16_t data = meminb(addr);
data |= meminb(addr + 1) << 8;
*val = data;
printf("[memiorw] 16 RX addr: %08lx Data:%04x\n", addr, data);
}
if (mytype == X86EMU_MEMIO_W)
{
memoutb(addr, *val &0xFF);
memoutb(addr + 1, (*val << 8) &0xFF);
printf("[memiorw] 16 W addr: %08lx Data:%04x\n", addr, *val);
}
return 0;
break;
case X86EMU_MEMIO_32:
printf("[memiorw] 32 addr: %08lx\n", addr);
assert(0);
break;
}
}
}
return old_memio(emu, addr, val, type);
}
int emu_int_handler(x86emu_t *emu, u8 num, unsigned type)
{
// Int 10h, BIOS Video interrupt
if (num == 0x10)
{
// if there's no interrupt in the IVT,
if (x86emu_read_byte(emu, 0x40) == 0x00)
{
// we'll just set AL to 12 as a "valid" return for each function.
// this is expected by Trident video ROMs.
printf("[int] a: %08lx b: %08lx\n", emu->x86.gen.A.I32_reg.e_reg, emu->x86.gen.B.I32_reg.e_reg);
//[int] a: 00001201 b: 00000032
// AL=12 Function supported
emu->x86.gen.A.I32_reg.e_reg &= 0xFFFFFF00UL;
emu->x86.gen.A.I32_reg.e_reg |= 0x12UL;
return 1;
}
}
return 0;
}
/*
* Setup registers and memory.
*/
int emu_init(x86emu_t *emu, char *file)
{
FILE * f;
unsigned addr;
int i;
//signal(SIGINT, sigfunc);
globalemu = emu;
addr = opt.load;
init_serial();
printf("serial ISA init done, giving the ISA card 5 seconds time after reset...");
sleep(5);
old_memio = x86emu_set_memio_handler(emu, emu_memio_handler);
x86emu_set_intr_handler(emu, emu_int_handler);
// Optional: Load a VGA BIOS from a file and write it to 0xC0000
/*if(!(f = fopen(file, "r"))) return 0;
loaded_rom = 1;
addr = VBIOS_ROM;
while((i = fgetc(f)) != EOF) {
x86emu_write_byte(emu, addr, i);
x86emu_set_perm(emu, addr, addr++, X86EMU_PERM_RWX | X86EMU_PERM_VALID);
}
fclose(f);
*/
printf("video bios: size 0x%04x\n", x86emu_read_byte(emu, VBIOS_ROM + 2) * 0x200);
printf("video bios: entry 0x%04x:0x%04x\n",
x86emu_read_word(emu, 0x10 * 4 + 2),
x86emu_read_word(emu, 0x10 * 4)
);
return 1;
}
/*
* Run emulation.
*/
void emu_run(char *file)
{
x86emu_t *emu = emu_new();
unsigned flags = X86EMU_RUN_NO_CODE | X86EMU_RUN_TIMEOUT;
int ok = 0;
if (!file) return;
ok = emu_init(emu, file);
if (ok)
{
printf("*** running %s ***\n\n", file);
// stack &buffer space
x86emu_set_perm(emu, VBE_BUF, 0xffff, X86EMU_PERM_RW);
// start address 0:0x7c00 (where the MBR would normally be executed)
// we'll do a far call to 0x0xC0003 (aka 0C00:0003) here.
x86emu_set_seg_register(emu, emu->x86.R_CS_SEL, 0);
emu->x86.R_EIP = 0x7c00;
uint8_t code[5] = "\x9a\x03\x00\x00\xc0";
for (int i = 0; i < sizeof(code); ++i)
{
vm_write_byte(emu, 0x7c00 + i, code[i], X86EMU_PERM_RX);
}
// and place a HLT instruction after the far call so the emulator stops after
// we're done executing the video ROM.
vm_write_byte(emu, 0x7c00 + sizeof(code), 0xf4, X86EMU_PERM_RX);
emu->max_instr = opt.max_instructions;
x86emu_run(emu, flags);
x86emu_dump(emu, X86EMU_DUMP_DEFAULT | X86EMU_DUMP_ACC_MEM);
x86emu_clear_log(emu, 1);
// Optional:
printf("setting video mode to 0x03\n");
// set video mode to 0x03 (80x25)
x86emu_set_seg_register(emu, emu->x86.R_CS_SEL, 0);
emu->x86.R_EIP = 0x7c00;
uint8_t code2[6] = "\xb4\x00\xb0\x03\xcd\x10";
for (int i = 0; i < sizeof(code2); ++i)
{
vm_write_byte(emu, 0x7c00 + i, code2[i], X86EMU_PERM_RX);
}
vm_write_byte(emu, 0x7c00 + sizeof(code2), 0xf4, X86EMU_PERM_RX);
emu->max_instr = opt.max_instructions;
x86emu_run(emu, flags);
x86emu_dump(emu, X86EMU_DUMP_DEFAULT | X86EMU_DUMP_ACC_MEM);
x86emu_clear_log(emu, 1);
printf("finished cleanly\n");
}
x86emu_done(emu);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment