mirror of https://github.com/inolen/redream.git
351 lines
6.8 KiB
C
351 lines
6.8 KiB
C
#include "core/core.h"
|
|
#include "core/filesystem.h"
|
|
#include "core/thread.h"
|
|
#include "guest/dreamcast.h"
|
|
#include "guest/serial/serial.h"
|
|
#include "guest/sh4/sh4.h"
|
|
|
|
enum {
|
|
STATE_LOADING,
|
|
STATE_RUNNING,
|
|
STATE_SHUTDOWN,
|
|
};
|
|
|
|
static void sys_write();
|
|
|
|
typedef void (*syscall_cb)();
|
|
|
|
static syscall_cb syscalls[] = {
|
|
NULL, /* exit */
|
|
NULL, /* fstat */
|
|
sys_write, /* write */
|
|
NULL, /* read */
|
|
NULL, /* open */
|
|
NULL, /* close */
|
|
NULL, /* create */
|
|
NULL, /* link */
|
|
NULL, /* unlink */
|
|
NULL, /* chdir */
|
|
NULL, /* chmod */
|
|
NULL, /* lseek */
|
|
NULL, /* time */
|
|
NULL, /* state */
|
|
NULL, /* utime */
|
|
NULL, /* unknown */
|
|
NULL, /* opendir */
|
|
NULL, /* closedir */
|
|
NULL, /* readdir */
|
|
NULL, /* readsectors */
|
|
NULL, /* gdbpacket */
|
|
NULL, /* rewinddir */
|
|
};
|
|
|
|
/*
|
|
* serial device to communicate with dcload
|
|
*/
|
|
|
|
/* the serial device has an infinitely-large queue for incoming and outgoing
|
|
data, providing a higher-level interface for transmitting data on top of
|
|
the raw putchar and getchar callbacks */
|
|
#define BLOCK_SIZE 4096
|
|
|
|
struct data_block {
|
|
uint8_t data[BLOCK_SIZE];
|
|
int read;
|
|
int write;
|
|
struct list_node it;
|
|
};
|
|
|
|
static mutex_t dev_mutex;
|
|
static struct list dev_readq;
|
|
static struct list dev_writeq;
|
|
|
|
static void queue_putchar(struct list *list, int c) {
|
|
mutex_lock(dev_mutex);
|
|
|
|
struct data_block *block = list_last_entry(list, struct data_block, it);
|
|
|
|
if (!block || block->write >= BLOCK_SIZE) {
|
|
struct data_block *next = calloc(1, sizeof(*block));
|
|
list_add_after_entry(list, block, next, it);
|
|
block = next;
|
|
}
|
|
|
|
block->data[block->write++] = (uint8_t)c;
|
|
|
|
mutex_unlock(dev_mutex);
|
|
}
|
|
|
|
static int queue_getchar(struct list *list) {
|
|
int c = -1;
|
|
|
|
mutex_lock(dev_mutex);
|
|
|
|
struct data_block *block = list_first_entry(list, struct data_block, it);
|
|
|
|
if (block && block->read < block->write) {
|
|
c = (int)block->data[block->read++];
|
|
|
|
if (block->read >= BLOCK_SIZE) {
|
|
list_remove(list, &block->it);
|
|
free(block);
|
|
}
|
|
}
|
|
|
|
mutex_unlock(dev_mutex);
|
|
|
|
return c;
|
|
}
|
|
|
|
/* called on the emulation thread when the scif is ready to receive another
|
|
character */
|
|
static int dev_getchar(void *userdata) {
|
|
return queue_getchar(&dev_writeq);
|
|
}
|
|
|
|
/* called on the emulation thread when the scif is transmitting another
|
|
character */
|
|
static void dev_putchar(void *userdata, int c) {
|
|
queue_putchar(&dev_readq, c);
|
|
}
|
|
|
|
static void dev_read_raw(void *ptr, int size) {
|
|
uint8_t *data = ptr;
|
|
|
|
while (size) {
|
|
int c = queue_getchar(&dev_readq);
|
|
|
|
/* TODO use a condition variable instead of spinning / locking constantly */
|
|
|
|
/* block until a char is available */
|
|
if (c == -1) {
|
|
continue;
|
|
}
|
|
|
|
*(data++) = c;
|
|
size--;
|
|
}
|
|
}
|
|
|
|
static void dev_write_raw(const void *ptr, int size) {
|
|
const uint8_t *data = ptr;
|
|
|
|
while (size--) {
|
|
queue_putchar(&dev_writeq, *(data++));
|
|
}
|
|
}
|
|
|
|
static void dev_read_blob(void *ptr, int size) {
|
|
uint8_t *data = ptr;
|
|
|
|
char type;
|
|
dev_read_raw(&type, 1);
|
|
CHECK_EQ(type, 'U');
|
|
|
|
int n;
|
|
dev_read_raw(&n, 4);
|
|
dev_read_raw(data, n);
|
|
|
|
int sum;
|
|
dev_read_raw(&sum, 1);
|
|
|
|
char ok = 'G';
|
|
dev_write_raw(&ok, 1);
|
|
}
|
|
|
|
static void dev_write_checked(const void *ptr, int size) {
|
|
uint8_t tmp[4];
|
|
CHECK_LE(size, (int)sizeof(tmp));
|
|
|
|
dev_write_raw(ptr, size);
|
|
dev_read_raw(tmp, size);
|
|
|
|
CHECK(memcmp(ptr, tmp, size) == 0);
|
|
}
|
|
|
|
/*
|
|
* dcload syscalls
|
|
*/
|
|
static void sys_write() {
|
|
int fd;
|
|
dev_read_raw(&fd, 4);
|
|
|
|
int n;
|
|
dev_read_raw(&n, 4);
|
|
|
|
char *data = malloc(n);
|
|
dev_read_blob(data, n);
|
|
|
|
int res = write(fd, data, n);
|
|
dev_write_checked(&res, 4);
|
|
|
|
free(data);
|
|
}
|
|
|
|
/*
|
|
* dcload commands
|
|
*/
|
|
char checksum(const void *ptr, int size) {
|
|
const uint8_t *data = ptr;
|
|
char sum = 0;
|
|
while (size--) {
|
|
sum ^= *(data++);
|
|
}
|
|
return sum;
|
|
}
|
|
|
|
static void run_code(uint32_t addr) {
|
|
/* send over serial if */
|
|
{
|
|
char cmd = 'A';
|
|
int console = 1;
|
|
|
|
dev_write_checked(&cmd, 1);
|
|
dev_write_checked(&addr, 4);
|
|
dev_write_checked(&console, 4);
|
|
}
|
|
|
|
/* parse syscall responses */
|
|
{
|
|
while (1) {
|
|
char cmd;
|
|
dev_read_raw(&cmd, 1);
|
|
|
|
if (!cmd) {
|
|
break;
|
|
}
|
|
|
|
CHECK(cmd >= 0 && cmd < (int)ARRAY_SIZE(syscalls));
|
|
|
|
syscall_cb cb = syscalls[(int)cmd];
|
|
CHECK_NOTNULL(cb, "run_code unexpected syscall=%d", cmd);
|
|
cb();
|
|
}
|
|
}
|
|
}
|
|
|
|
static void load_code(uint32_t addr, const char *path) {
|
|
uint8_t *bin = NULL;
|
|
int bin_size = 0;
|
|
|
|
/* load file */
|
|
{
|
|
FILE *fp = fopen(path, "rb");
|
|
CHECK(fp);
|
|
|
|
fseek(fp, 0, SEEK_END);
|
|
bin_size = ftell(fp);
|
|
fseek(fp, 0, SEEK_SET);
|
|
|
|
bin = malloc(bin_size);
|
|
int n = (int)fread(bin, 1, bin_size, fp);
|
|
CHECK_EQ(n, bin_size);
|
|
|
|
fclose(fp);
|
|
}
|
|
|
|
/* send over serial if */
|
|
{
|
|
/* write load binary command */
|
|
char cmd = 'B';
|
|
dev_write_checked(&cmd, 1);
|
|
dev_write_checked(&addr, 4);
|
|
dev_write_checked(&bin_size, 4);
|
|
|
|
/* write payload */
|
|
char type = 'U';
|
|
char sum = checksum(bin, bin_size);
|
|
dev_write_raw(&type, 1);
|
|
dev_write_checked(&bin_size, 4);
|
|
dev_write_raw(bin, bin_size);
|
|
dev_write_raw(&sum, 1);
|
|
dev_read_raw(&type, 1);
|
|
CHECK_EQ(type, 'G');
|
|
}
|
|
|
|
free(bin);
|
|
}
|
|
|
|
/*
|
|
* main program
|
|
*/
|
|
static volatile int state;
|
|
static struct dreamcast *dc;
|
|
static thread_t dc_thread;
|
|
|
|
static void *dc_main(void *data) {
|
|
const char *dcload_path = data;
|
|
struct serial *serial = NULL;
|
|
int res = 0;
|
|
|
|
dc = dc_create(NULL);
|
|
CHECK_NOTNULL(dc);
|
|
|
|
serial = serial_create(dc, NULL, dev_getchar, dev_putchar);
|
|
dc_add_serial_device(dc, serial);
|
|
|
|
res = dc_load(dc, dcload_path);
|
|
CHECK(res);
|
|
|
|
state = STATE_RUNNING;
|
|
|
|
while (state == STATE_RUNNING) {
|
|
dc_tick(dc, 1000);
|
|
}
|
|
|
|
serial_destroy(serial);
|
|
serial = NULL;
|
|
|
|
dc_destroy(dc);
|
|
dc = NULL;
|
|
|
|
return NULL;
|
|
}
|
|
|
|
int main(int argc, char **argv) {
|
|
if (argc < 3) {
|
|
LOG_INFO("reload /path/to/dcload-serial.cdi /path/to/test.bin ...");
|
|
return EXIT_FAILURE;
|
|
}
|
|
|
|
/* set application directory */
|
|
char appdir[PATH_MAX];
|
|
char userdir[PATH_MAX];
|
|
int r = fs_userdir(userdir, sizeof(userdir));
|
|
CHECK(r);
|
|
snprintf(appdir, sizeof(appdir), "%s" PATH_SEPARATOR ".redream", userdir);
|
|
fs_set_appdir(appdir);
|
|
|
|
/* startup machine */
|
|
dev_mutex = mutex_create();
|
|
CHECK_NOTNULL(dev_mutex);
|
|
|
|
dc_thread = thread_create(&dc_main, NULL, argv[1]);
|
|
CHECK_NOTNULL(dc_thread);
|
|
|
|
/* wait for it to initialize */
|
|
while (state == STATE_LOADING) {
|
|
}
|
|
|
|
/* run each binary */
|
|
const uint32_t code_addr = 0x8c010000;
|
|
|
|
for (int i = 2; i < argc; i++) {
|
|
load_code(code_addr, argv[i]);
|
|
run_code(code_addr);
|
|
}
|
|
|
|
/* shutdown machine */
|
|
void *result;
|
|
|
|
state = STATE_SHUTDOWN;
|
|
|
|
thread_join(dc_thread, &result);
|
|
dc_thread = NULL;
|
|
|
|
mutex_destroy(dev_mutex);
|
|
dev_mutex = NULL;
|
|
|
|
return EXIT_SUCCESS;
|
|
}
|