redream/tools/reload/main.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;
}