399 lines
10 KiB
C
399 lines
10 KiB
C
|
// Simple emulator program
|
||
|
#include <stdio.h>
|
||
|
#include <stdint.h>
|
||
|
#include <stddef.h>
|
||
|
#include <stdlib.h>
|
||
|
#include <string.h>
|
||
|
#define SRVE_IMPLEMENTATION
|
||
|
#include "srve.h"
|
||
|
#include "../libc/elf.h"
|
||
|
|
||
|
// Some defines for virtio emulation
|
||
|
#define VIOE_UART0 0x10000000ULL
|
||
|
#define VIOE_UART_SIZE 0x1000ULL
|
||
|
#define VIOE_UART_HOLDING 0x0
|
||
|
#define VIOE_UART_LINESTATUS 0x5
|
||
|
#define VIOE_UART_LINESTATUS_RECEIVEABLE 1
|
||
|
#define VIOE_UART_LINESTATUS_TRANSMITTABLE (1<<5)
|
||
|
|
||
|
// Some defines for new BIOS functionality
|
||
|
#define VMRD_BASEADDRESS 0x0000000010001000UL
|
||
|
#define VMRD_SIZE 0x1000ULL
|
||
|
#define VMRD_REG_MAGIC 0
|
||
|
#define VMRD_REG_BLKSIZE 4
|
||
|
#define VMRD_REG_MEMADDR 8
|
||
|
#define VMRD_REG_BLKADDR 16
|
||
|
#define VMRD_REG_ACTION 24
|
||
|
|
||
|
// The magic number is BA5DB105, which drivers can use to distinguish
|
||
|
// it from virtio.
|
||
|
#define VMRD_MAGIC 0xBA5DB105
|
||
|
|
||
|
// This range needs to cover all I/O areas
|
||
|
#define IORANGE_BEGIN 0x10000000ULL
|
||
|
#define IORANGE_END 0x10002000ULL
|
||
|
|
||
|
|
||
|
|
||
|
typedef struct vmrd vmrd_t;
|
||
|
|
||
|
struct vmrd {
|
||
|
uint64_t memoryaddress;
|
||
|
uint64_t blockaddress;
|
||
|
uint32_t blocksize;
|
||
|
uint32_t action;
|
||
|
FILE* file;
|
||
|
uint64_t imagesize;
|
||
|
};
|
||
|
|
||
|
srve_core64_t core;
|
||
|
|
||
|
// Loads binary into memory at address, returning size or -1 on failure.
|
||
|
int64_t loadf(unsigned char* memory, unsigned long long addr, const char* fname) {
|
||
|
fprintf(stderr, "loadf(%p, 0x%llx, \"%s\");\n", memory, addr, fname);
|
||
|
FILE* f = fopen(fname, "r");
|
||
|
if (!f) {
|
||
|
fprintf(stderr, "Failed to open '%s'\n", fname);
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
unsigned long sz = 0;
|
||
|
size_t nread;
|
||
|
while ((nread = fread(memory + addr + sz, 1, 1024*1024, f)) > 0) {
|
||
|
sz += (unsigned long) nread;
|
||
|
}
|
||
|
|
||
|
fclose(f);
|
||
|
}
|
||
|
|
||
|
int uart(int devnum, uint32_t offset, void* buffer, unsigned int storing, unsigned int size) {
|
||
|
//fprintf(stderr, "uart(%i, 0x%x, %p, %d, %d);\n", devnum, offset, buffer, storing, size);
|
||
|
|
||
|
switch (offset) {
|
||
|
case VIOE_UART_HOLDING:
|
||
|
if (storing) {
|
||
|
fflush(stderr);
|
||
|
//printf("char value is 0x%x\n", *((uint8_t*)buffer));
|
||
|
//putc('X',stdout);
|
||
|
putc(*((uint8_t*)buffer), stdout);
|
||
|
fflush(stdout);
|
||
|
//exit(0);
|
||
|
return 0;
|
||
|
} else {
|
||
|
return -1;
|
||
|
}
|
||
|
case VIOE_UART_LINESTATUS:
|
||
|
if (storing) {
|
||
|
return -1;
|
||
|
} else {
|
||
|
*((char*)buffer) = VIOE_UART_LINESTATUS_TRANSMITTABLE;
|
||
|
return 0;
|
||
|
}
|
||
|
default:
|
||
|
fprintf(stderr, "uart: bad offset 0x%x\n", offset);
|
||
|
return 0;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Only allow one disk for now, but design as though there may be more.
|
||
|
#define VMRD_MAX 1
|
||
|
vmrd_t* vmrd_array[VMRD_MAX];
|
||
|
|
||
|
uint32_t vmrd_performaction(void* memory, vmrd_t* device, uint32_t action) {
|
||
|
if (memory == NULL || device == NULL || device->file == NULL || device->imagesize == 0) {
|
||
|
return 'f';
|
||
|
}
|
||
|
|
||
|
char* membytes = memory;
|
||
|
|
||
|
switch (action) {
|
||
|
case 't':
|
||
|
core.tracing = 1;
|
||
|
return 's';
|
||
|
case 'e':
|
||
|
core.tracing = 0;
|
||
|
return 's';
|
||
|
case 'r':
|
||
|
if (fseek(device->file, device->blocksize*device->blockaddress, SEEK_SET) != 0) {
|
||
|
return 'f';
|
||
|
}
|
||
|
// TODO: Validate memory address...
|
||
|
if (fread(membytes+device->memoryaddress, device->blocksize, 1, device->file) != 1) {
|
||
|
return 'f';
|
||
|
}
|
||
|
return 's';
|
||
|
default:
|
||
|
return 'f';
|
||
|
}
|
||
|
}
|
||
|
|
||
|
int vmrd(void* memory, int devnum, uint32_t offset, void* buffer, unsigned int storing, unsigned int size) {
|
||
|
vmrd_t* device = &vmrd_array[devnum];
|
||
|
switch (offset) {
|
||
|
case VMRD_REG_MAGIC:
|
||
|
if (!storing && size == 4) {
|
||
|
*((uint32_t*)buffer) = VMRD_MAGIC;
|
||
|
return 0;
|
||
|
} else {
|
||
|
goto fail;
|
||
|
}
|
||
|
case VMRD_REG_BLKADDR:
|
||
|
if (storing && size == 8) {
|
||
|
device->blockaddress = *((uint64_t*)buffer);
|
||
|
return 0;
|
||
|
} else if (!storing && size == 8) {
|
||
|
*((uint64_t*)buffer) = device->blockaddress;
|
||
|
return 0;
|
||
|
} else {
|
||
|
goto fail;
|
||
|
}
|
||
|
case VMRD_REG_MEMADDR:
|
||
|
if (storing && size == 8) {
|
||
|
device->memoryaddress = *((uint64_t*)buffer);
|
||
|
return 0;
|
||
|
} else if (!storing && size == 8) {
|
||
|
*((uint64_t*)buffer) = device->memoryaddress;
|
||
|
return 0;
|
||
|
} else {
|
||
|
goto fail;
|
||
|
}
|
||
|
case VMRD_REG_BLKSIZE:
|
||
|
if (storing && size == 4) {
|
||
|
device->blocksize = *((uint32_t*)buffer);
|
||
|
return 0;
|
||
|
} else if (!storing && size == 4) {
|
||
|
*((uint32_t*)buffer) = device->blocksize;
|
||
|
return 0;
|
||
|
} else {
|
||
|
goto fail;
|
||
|
}
|
||
|
case VMRD_REG_ACTION:
|
||
|
if (storing && size == 4) {
|
||
|
device->action = '*'; // Asterix action state indicates it's in use
|
||
|
device->action = vmrd_performaction(memory, device, *((uint32_t*)buffer));
|
||
|
return 0;
|
||
|
} else if (!storing && size == 4) {
|
||
|
*((uint32_t*)buffer) = device->action;
|
||
|
return 0;
|
||
|
} else {
|
||
|
goto fail;
|
||
|
}
|
||
|
default:
|
||
|
fail:
|
||
|
device->action = 'f';
|
||
|
fprintf(stderr, "vmrd: bad access at offset 0x%x\n", offset);
|
||
|
return -1;
|
||
|
}
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
int iofunc(void* memory, uint64_t address, void* buffer, unsigned int storing, unsigned int size) {
|
||
|
if (address >= VIOE_UART0 && address+size <= VIOE_UART0+VIOE_UART_SIZE) {
|
||
|
return uart(0, (uint32_t) (address - VIOE_UART0), buffer, storing, size);
|
||
|
}
|
||
|
if (address >= VMRD_BASEADDRESS && address+size <= VMRD_BASEADDRESS+VMRD_SIZE) {
|
||
|
return vmrd(memory, 0, (uint32_t) (address - VMRD_BASEADDRESS), buffer, storing, size);
|
||
|
}
|
||
|
fprintf(stderr, "iofunc: Bad I/O address 0x%llx\n", address);
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
int memfunc(void* memory, uint64_t address, void* buffer, unsigned int storing, unsigned int size) {
|
||
|
if (address >= IORANGE_BEGIN && address+size <= IORANGE_END) {
|
||
|
return iofunc(memory, address, buffer, storing, size);
|
||
|
}
|
||
|
//if (address % size) {
|
||
|
//fprintf(stderr, "memfunc(%p, 0x%lx, %p, %d, %d);\n", memory, address, buffer, storing, size);
|
||
|
//exit(-1);
|
||
|
//}
|
||
|
//address = (address << 32) >> 32;
|
||
|
//fprintf(stderr, "memfunc(%p, 0x%lx, %p, %d, %d);\n", memory, address, buffer, storing, size);
|
||
|
unsigned char* bmemory = memory;
|
||
|
if (size == 4) {
|
||
|
uint32_t* ibuffer = buffer;
|
||
|
uint32_t* istorage = (void*)(bmemory + address);
|
||
|
if (storing) {
|
||
|
*istorage = *ibuffer;
|
||
|
} else {
|
||
|
*ibuffer = *istorage;
|
||
|
}
|
||
|
return 0;
|
||
|
} else if (size == 8) {
|
||
|
uint64_t* ibuffer = buffer;
|
||
|
uint64_t* istorage = (void*)(bmemory + address);
|
||
|
if (storing) {
|
||
|
*istorage = *ibuffer;
|
||
|
} else {
|
||
|
*ibuffer = *istorage;
|
||
|
}
|
||
|
return 0;
|
||
|
} else if (size == 1) {
|
||
|
uint8_t* ibuffer = buffer;
|
||
|
uint8_t* istorage = (void*)(bmemory + address);
|
||
|
if (storing) {
|
||
|
*istorage = *ibuffer;
|
||
|
} else {
|
||
|
*ibuffer = *istorage;
|
||
|
}
|
||
|
return 0;
|
||
|
} else if (size == 2) {
|
||
|
uint16_t* ibuffer = buffer;
|
||
|
uint16_t* istorage = (void*)(bmemory + address);
|
||
|
if (storing) {
|
||
|
*istorage = *ibuffer;
|
||
|
} else {
|
||
|
*ibuffer = *istorage;
|
||
|
}
|
||
|
return 0;
|
||
|
} else {
|
||
|
return -1;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
int hexvalue(char ch) {
|
||
|
switch (ch) {
|
||
|
case '0': return 0;
|
||
|
case '1': return 1;
|
||
|
case '2': return 2;
|
||
|
case '3': return 3;
|
||
|
case '4': return 4;
|
||
|
case '5': return 5;
|
||
|
case '6': return 6;
|
||
|
case '7': return 7;
|
||
|
case '8': return 8;
|
||
|
case '9': return 9;
|
||
|
case 'a': case 'A': return 10;
|
||
|
case 'b': case 'B': return 11;
|
||
|
case 'c': case 'C': return 12;
|
||
|
case 'd': case 'D': return 13;
|
||
|
case 'e': case 'E': return 14;
|
||
|
case 'f': case 'F': return 15;
|
||
|
default: return -1;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
char linebuf[1000];
|
||
|
|
||
|
int insertsymbol(srve_debugsymbol_t** symvar, uint64_t addr, char* namebuffer) {
|
||
|
for (int i = 0; namebuffer[i] != 0; i++) {
|
||
|
if (namebuffer[i] == '.') {
|
||
|
return 0; // Skip adding entries for filenames and sections
|
||
|
}
|
||
|
}
|
||
|
srve_debugsymbol_t* sym = malloc(sizeof(srve_debugsymbol_t));
|
||
|
if (!sym) {
|
||
|
return -1;
|
||
|
}
|
||
|
sym->addr = addr;
|
||
|
sym->name = strdup(namebuffer);
|
||
|
sym->next = NULL;
|
||
|
|
||
|
for (int i = 0; sym->name[i] != 0; i++) {
|
||
|
if (sym->name[i] == '\r' || sym->name[i] == '\n') {
|
||
|
sym->name[i] = 0;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
srve_debugsymbol_t* search = *symvar;
|
||
|
if (search == NULL || addr < search->addr) {
|
||
|
sym->next = search;
|
||
|
*symvar = sym;
|
||
|
return 0;
|
||
|
}
|
||
|
while (search->addr < addr) {
|
||
|
if (search->next == NULL || search->next->addr >= addr) {
|
||
|
sym->next = search->next;
|
||
|
search->next = sym;
|
||
|
return 0;
|
||
|
}
|
||
|
search = search->next;
|
||
|
}
|
||
|
sym->next = search->next;
|
||
|
search->next = sym;
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
int loadsymbols(srve_debugsymbol_t** symvar, char* fname) {
|
||
|
FILE* f = fopen(fname, "r");
|
||
|
|
||
|
while (fgets(linebuf, 1000, f)) {
|
||
|
char* s = linebuf;
|
||
|
uint64_t x = 0;
|
||
|
int digit;
|
||
|
while ((digit = hexvalue(*s)) >= 0) {
|
||
|
x *= 0x10;
|
||
|
x += digit;
|
||
|
s++;
|
||
|
}
|
||
|
while (*s == ' ') {
|
||
|
s++;
|
||
|
}
|
||
|
if (insertsymbol(symvar, x, s) != 0) {
|
||
|
return -1;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
fclose(f);
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
int loaddiskimage(vmrd_t* device, char* fname) {
|
||
|
FILE* f = fopen(fname, "r+");
|
||
|
if (f) {
|
||
|
fseek(f, 0, SEEK_END);
|
||
|
device->file = f;
|
||
|
device->imagesize = ftell(f);
|
||
|
device->action = 'i'; // Initial action state
|
||
|
return 0;
|
||
|
} else {
|
||
|
device->file = NULL;
|
||
|
device->imagesize = 0;
|
||
|
device->action = 'f'; // Failed action state
|
||
|
return -1;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
int main(int argc, char** argv) {
|
||
|
fprintf(stderr, "SecureLang RV64/RV32 emulator, early testing version\n");
|
||
|
|
||
|
char* memory = calloc(4*1024*1024*1024ULL, 1);
|
||
|
if (memory == NULL) {
|
||
|
fprintf(stderr, "Setup failed!\n");
|
||
|
|
||
|
return -1;
|
||
|
}
|
||
|
uint64_t loadaddr = 0x8000000ULL; // TODO: Compiler bug prevents 0x80000000 as a constant, must be an integer conversion somewhere
|
||
|
loadaddr *= 0x10;
|
||
|
//printf("Loading at %dMB\n", loadaddr/(1024*1024));
|
||
|
char* kfname = argv[1];
|
||
|
char* sname = argv[2];
|
||
|
char* fsname = argv[3];
|
||
|
|
||
|
loadf(memory, loadaddr, argv[1]);
|
||
|
|
||
|
srve_debugsymbol_t* syms = NULL;
|
||
|
loadsymbols(&syms, sname);
|
||
|
|
||
|
loaddiskimage(&vmrd_array[0], fsname);
|
||
|
|
||
|
srve_debugsymbol_t* iter = syms;
|
||
|
while (iter) {
|
||
|
fprintf(stderr, "%lx %s\n", iter->addr, iter->name);
|
||
|
iter = iter->next;
|
||
|
}
|
||
|
|
||
|
if (srve_core64_init(&core, &memfunc, memory, loadaddr) != 0) {
|
||
|
return -1;
|
||
|
}
|
||
|
core.symbols = syms;
|
||
|
core.tracing = 0;
|
||
|
|
||
|
int64_t result;
|
||
|
do {
|
||
|
result = srve_core64_doinstr(&core);
|
||
|
} while (result == 0);
|
||
|
|
||
|
return 0;
|
||
|
}
|