diff --git a/CMakeLists.txt b/CMakeLists.txt index 25f3b3f2..b302e1af 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -208,6 +208,7 @@ set(RELIB_SOURCES src/guest/pvr/tr.c src/guest/rom/boot.c src/guest/rom/flash.c + src/guest/serial/serial.c src/guest/sh4/sh4.c src/guest/sh4/sh4_ccn.c src/guest/sh4/sh4_dbg.c @@ -216,6 +217,7 @@ set(RELIB_SOURCES src/guest/sh4/sh4_mem.c src/guest/sh4/sh4_mmu.c src/guest/sh4/sh4_tmu.c + src/guest/sh4/sh4_scif.c src/guest/debugger.c src/guest/dreamcast.c src/guest/memory.c @@ -402,6 +404,19 @@ target_compile_definitions(recc PRIVATE ${RELIB_DEFS}) target_compile_options(recc PRIVATE ${RELIB_FLAGS}) endif() +# reload +set(RELOAD_SOURCES + ${RELIB_SOURCES} + src/host/null_host.c + tools/reload/main.c) +source_group_by_dir(RELOAD_SOURCES) + +add_executable(reload ${RELOAD_SOURCES}) +target_include_directories(reload PUBLIC ${RELIB_INCLUDES} ${CMAKE_CURRENT_SOURCE_DIR}/src) +target_link_libraries(reload ${RELIB_LIBS}) +target_compile_definitions(reload PRIVATE ${RELIB_DEFS}) +target_compile_options(reload PRIVATE ${RELIB_FLAGS}) + # retex set(RETEX_SOURCES ${RELIB_SOURCES} diff --git a/deps/libretro/Makefile.common b/deps/libretro/Makefile.common index 78432e99..c5cb0895 100644 --- a/deps/libretro/Makefile.common +++ b/deps/libretro/Makefile.common @@ -40,6 +40,7 @@ SOURCES_C := $(CORE_DIR)/src/core/assert.c \ $(CORE_DIR)/src/guest/arm7/arm7.c \ $(CORE_DIR)/src/guest/rom/boot.c \ $(CORE_DIR)/src/guest/rom/flash.c \ + $(CORE_DIR)/src/guest/serial/serial.c \ $(CORE_DIR)/src/guest/sh4/sh4.c \ $(CORE_DIR)/src/guest/sh4/sh4_ccn.c \ $(CORE_DIR)/src/guest/sh4/sh4_dbg.c \ @@ -48,6 +49,7 @@ SOURCES_C := $(CORE_DIR)/src/core/assert.c \ $(CORE_DIR)/src/guest/sh4/sh4_mem.c \ $(CORE_DIR)/src/guest/sh4/sh4_mmu.c \ $(CORE_DIR)/src/guest/sh4/sh4_tmu.c \ + $(CORE_DIR)/src/guest/sh4/sh4_scif.c \ $(CORE_DIR)/src/guest/debugger.c \ $(CORE_DIR)/src/guest/dreamcast.c \ $(CORE_DIR)/src/guest/memory.c \ diff --git a/src/guest/dreamcast.c b/src/guest/dreamcast.c index ae9a5bb8..261ed1f7 100644 --- a/src/guest/dreamcast.c +++ b/src/guest/dreamcast.c @@ -92,6 +92,14 @@ void *dc_create_device(struct dreamcast *dc, size_t size, const char *name, return dev; } +void dc_remove_serial_device(struct dreamcast *dc) { + dc->serial = NULL; +} + +void dc_add_serial_device(struct dreamcast *dc, struct serial *serial) { + dc->serial = serial; +} + void dc_input(struct dreamcast *dc, int port, int button, uint16_t value) { maple_handle_input(dc->maple, port, button, value); } diff --git a/src/guest/dreamcast.h b/src/guest/dreamcast.h index a9a57dc5..1174712b 100644 --- a/src/guest/dreamcast.h +++ b/src/guest/dreamcast.h @@ -137,6 +137,7 @@ struct dreamcast { struct maple *maple; struct pvr *pvr; struct ta *ta; + struct serial *serial; struct list devices; /* client callbacks */ @@ -159,6 +160,8 @@ void dc_suspend(struct dreamcast *dc); void dc_resume(struct dreamcast *dc); void dc_tick(struct dreamcast *dc, int64_t ns); void dc_input(struct dreamcast *dc, int port, int button, uint16_t value); +void dc_add_serial_device(struct dreamcast *dc, struct serial *serial); +void dc_remove_serial_device(struct dreamcast *dc); /* device registration */ void *dc_create_device(struct dreamcast *dc, size_t size, const char *name, diff --git a/src/guest/serial/serial.c b/src/guest/serial/serial.c new file mode 100644 index 00000000..e242801d --- /dev/null +++ b/src/guest/serial/serial.c @@ -0,0 +1,36 @@ +#include "guest/serial/serial.h" +#include "guest/dreamcast.h" + +struct serial { + struct device; + void *userdata; + getchar_cb getchar; + putchar_cb putchar; +}; + +static int serial_init(struct device *dev) { + struct serial *serial = (struct serial *)dev; + return 1; +} + +void serial_putchar(struct serial *serial, int c) { + serial->putchar(serial->userdata, c); +} + +int serial_getchar(struct serial *serial) { + return serial->getchar(serial->userdata); +} + +void serial_destroy(struct serial *serial) { + dc_destroy_device((struct device *)serial); +} + +struct serial *serial_create(struct dreamcast *dc, void *userdata, + getchar_cb getchar, putchar_cb putchar) { + struct serial *serial = + dc_create_device(dc, sizeof(struct serial), "serial", &serial_init, NULL); + serial->userdata = userdata; + serial->getchar = getchar; + serial->putchar = putchar; + return serial; +} diff --git a/src/guest/serial/serial.h b/src/guest/serial/serial.h new file mode 100644 index 00000000..46c44c11 --- /dev/null +++ b/src/guest/serial/serial.h @@ -0,0 +1,18 @@ +#ifndef SERIAL_H +#define SERIAL_H + +#include + +struct dreamcast; + +typedef int (*getchar_cb)(void *); +typedef void (*putchar_cb)(void *, int); + +struct serial *serial_create(struct dreamcast *dc, void *userdata, + getchar_cb getchar, putchar_cb putchar); +void serial_destroy(struct serial *serial); + +int serial_getchar(struct serial *serial); +void serial_putchar(struct serial *serial, int c); + +#endif diff --git a/src/guest/sh4/sh4.h b/src/guest/sh4/sh4.h index e52700dc..80ffcbe6 100644 --- a/src/guest/sh4/sh4.h +++ b/src/guest/sh4/sh4.h @@ -8,6 +8,7 @@ #include "guest/sh4/sh4_intc.h" #include "guest/sh4/sh4_mem.h" #include "guest/sh4/sh4_mmu.h" +#include "guest/sh4/sh4_scif.h" #include "guest/sh4/sh4_types.h" #include "jit/frontend/sh4/sh4_guest.h" #include "jit/jit.h" @@ -56,6 +57,11 @@ struct sh4 { uint32_t utlb_sq_map[64]; struct sh4_tlb_entry utlb[64]; + /* scif */ + uint32_t SCFSR2_last_read; + struct sh4_scif_fifo receive_fifo; + struct sh4_scif_fifo transmit_fifo; + /* tmu */ struct timer *tmu_timers[3]; }; diff --git a/src/guest/sh4/sh4_dmac.c b/src/guest/sh4/sh4_dmac.c index 0101c215..c3df60a5 100644 --- a/src/guest/sh4/sh4_dmac.c +++ b/src/guest/sh4/sh4_dmac.c @@ -18,12 +18,12 @@ static void sh4_dmac_check(struct sh4 *sh4, int channel) { chcr = sh4->CHCR3; break; default: - LOG_FATAL("Unexpected DMA channel"); + LOG_FATAL("sh4_dmac_check unexpected channel %d", channel); break; } CHECK(sh4->DMAOR->DDT || !sh4->DMAOR->DME || !chcr->DE, - "Non-DDT DMA not supported"); + "sh4_dmac_check only DDT DMA unsupported"); } void sh4_dmac_ddt(struct sh4 *sh4, struct sh4_dtr *dtr) { diff --git a/src/guest/sh4/sh4_regs.inc b/src/guest/sh4/sh4_regs.inc index 12a1ff36..03e78a71 100644 --- a/src/guest/sh4/sh4_regs.inc +++ b/src/guest/sh4/sh4_regs.inc @@ -145,7 +145,7 @@ SH4_REG(0xffe8000c, SCFTDR2, 0x00000000, uint32_t) SH4_REG(0xffe80010, SCFSR2, 0x00000060, union scfsr2) SH4_REG(0xffe80014, SCFRDR2, 0x00000000, uint32_t) SH4_REG(0xffe80018, SCFCR2, 0x00000000, union scfcr2) -SH4_REG(0xffe8001c, SCFDR2, 0x00000000, uint32_t) +SH4_REG(0xffe8001c, SCFDR2, 0x00000000, union scfdr2) SH4_REG(0xffe80020, SCSPTR2, 0x00000000, uint32_t) SH4_REG(0xffe80024, SCLSR2, 0x00000000, union sclsr2) SH4_REG(0xfff00000, SDIR, 0x0000ffff, uint32_t) diff --git a/src/guest/sh4/sh4_scif.c b/src/guest/sh4/sh4_scif.c new file mode 100644 index 00000000..9feda59f --- /dev/null +++ b/src/guest/sh4/sh4_scif.c @@ -0,0 +1,311 @@ +/* + * serial communication interface implementation + * + * this implementation is very incomplete. primarily, the serial port's transfer + * rate is not emulated, transfers are instead pumped when the status register + * is polled. due to this, features like overrun are also not emulated, it's + * just made to never occur + * + * with that said, the implemntation is complete enough to communicate with + * dcload, which is the primary use case + */ + +#include "guest/serial/serial.h" +#include "guest/sh4/sh4.h" + +static int fifo_size(struct sh4_scif_fifo *q) { + int size = q->head - q->tail; + + /* check if the head pointer has wrapped around */ + if (size < 0) { + size = sizeof(q->data) - (q->tail - q->head); + } + + return size; +} + +static int fifo_dequeue(struct sh4_scif_fifo *q) { + if (q->tail == q->head) { + return -1; + } + + int data = q->data[q->tail]; + q->tail = (q->tail + 1) % sizeof(q->data); + return data; +} + +static int fifo_enqueue(struct sh4_scif_fifo *q, int data) { + int size = fifo_size(q); + + /* never let the fifo completely fill up (see sh4_scif.h) */ + if (size == SCIF_FIFO_SIZE) { + return 0; + } + + q->data[q->head] = data; + q->head = (q->head + 1) % sizeof(q->data); + return 1; +} + +static int receive_triggered(struct sh4 *sh4) { + static uint32_t receive_triggers[] = {1, 4, 8, 14}; + return sh4->SCFDR2->R >= receive_triggers[sh4->SCFCR2->RTRG]; +} + +static int receive_dequeue(struct sh4 *sh4) { + struct sh4_scif_fifo *q = &sh4->receive_fifo; + + int data = fifo_dequeue(q); + if (data == -1) { + return -1; + } + + sh4->SCFDR2->R = fifo_size(q); + + /* RDF isn't cleared when reading from SCFRDR2, it must be explicitly cleared + by writing to SCFSR2 */ + + return data; +} + +static int receive_enqueue(struct sh4 *sh4, int data) { + struct sh4_scif_fifo *q = &sh4->receive_fifo; + + /* TODO raise ORER on overflow */ + int res = fifo_enqueue(q, data); + CHECK(res); + + sh4->SCFDR2->R = fifo_size(q); + sh4->SCFSR2->RDF = receive_triggered(sh4); + + /* raise interrupt if enabled and triggered */ + if (sh4->SCSCR2->RIE && sh4->SCFSR2->RDF) { + sh4_raise_interrupt(sh4, SH4_INT_SCIFRXI); + } + + return 1; +} + +static void receive_reset(struct sh4 *sh4) { + struct sh4_scif_fifo *q = &sh4->receive_fifo; + q->head = q->tail = 0; + sh4->SCFDR2->R = 0; + sh4->SCFSR2->RDF = 0; + sh4_clear_interrupt(sh4, SH4_INT_SCIFRXI); +} + +static int transmit_triggered(struct sh4 *sh4) { + static uint32_t transmit_triggers[] = {8, 4, 2, 1}; + return sh4->SCFDR2->T <= transmit_triggers[sh4->SCFCR2->TTRG]; +} + +static int transmit_ended(struct sh4 *sh4) { + return sh4->SCFDR2->T == 0; +} + +static int transmit_dequeue(struct sh4 *sh4) { + struct sh4_scif_fifo *q = &sh4->transmit_fifo; + + int data = fifo_dequeue(q); + if (data == -1) { + return -1; + } + + sh4->SCFDR2->T = fifo_size(q); + sh4->SCFSR2->TDFE = transmit_triggered(sh4); + sh4->SCFSR2->TEND = transmit_ended(sh4); + + /* raise interrupt if enabled and triggered */ + if (sh4->SCSCR2->TIE && sh4->SCFSR2->TDFE) { + sh4_raise_interrupt(sh4, SH4_INT_SCIFTXI); + } + + return data; +} + +static int transmit_enqueue(struct sh4 *sh4, int data) { + struct sh4_scif_fifo *q = &sh4->transmit_fifo; + + /* TODO discard when full */ + int res = fifo_enqueue(q, data); + CHECK(res); + + sh4->SCFDR2->T = fifo_size(q); + + /* TDFE isn't cleared when writing SCFTDR2, it must be explicitly cleared + by writing to SCFSR2 */ + + return 1; +} + +static void transmit_reset(struct sh4 *sh4) { + struct sh4_scif_fifo *q = &sh4->transmit_fifo; + q->head = q->tail = 0; + sh4->SCFDR2->T = 0; + sh4->SCFSR2->TEND = 1; + sh4->SCFSR2->TDFE = 1; + sh4_clear_interrupt(sh4, SH4_INT_SCIFTXI); +} + +static void sh4_scif_run(struct sh4 *sh4) { + struct serial *serial = sh4->dc->serial; + + if (!serial) { + return; + } + + /* transfer rates aren't emulated at all, just completely fill / drain each + queue at this point */ + if (sh4->SCSCR2->RE && !sh4->SCLSR2->ORER) { + while (sh4->SCFDR2->R < SCIF_FIFO_SIZE) { + int data = serial_getchar(serial); + + if (data == -1) { + break; + } + + receive_enqueue(sh4, data); + } + } + + if (sh4->SCSCR2->TE) { + while (sh4->SCFDR2->T > 0) { + int data = transmit_dequeue(sh4); + + CHECK_NE(data, -1); + + serial_putchar(serial, data); + } + } +} + +REG_W32(sh4_cb, SCSMR2) { + struct sh4 *sh4 = dc->sh4; + sh4->SCSMR2->full = value; + + /* none of the fancy transfer modes are supported */ + CHECK_EQ(sh4->SCSMR2->full, 0); +} + +REG_W32(sh4_cb, SCBRR2) { + struct sh4 *sh4 = dc->sh4; + + /* TODO handle transfer rate */ + + *sh4->SCBRR2 = value; +} + +REG_W32(sh4_cb, SCSCR2) { + struct sh4 *sh4 = dc->sh4; + sh4->SCSCR2->full = value; + + CHECK_EQ(sh4->SCSCR2->CKE1, 0); + + /* transmission has ended */ + if (!sh4->SCSCR2->TE) { + sh4->SCFSR2->TEND = 1; + } + + /* clear interrupts if disabled */ + if (!sh4->SCSCR2->REIE && !sh4->SCSCR2->RIE) { + sh4_clear_interrupt(sh4, SH4_INT_SCIFERI); + sh4_clear_interrupt(sh4, SH4_INT_SCIFBRI); + } + if (!sh4->SCSCR2->RIE) { + sh4_clear_interrupt(sh4, SH4_INT_SCIFRXI); + } + if (!sh4->SCSCR2->TIE) { + sh4_clear_interrupt(sh4, SH4_INT_SCIFTXI); + } +} + +REG_R32(sh4_cb, SCFTDR2) { + LOG_FATAL("unexpected read from SCFTDR2"); +} + +REG_W32(sh4_cb, SCFTDR2) { + struct sh4 *sh4 = dc->sh4; + transmit_enqueue(sh4, (int)value); +} + +REG_R32(sh4_cb, SCFSR2) { + struct sh4 *sh4 = dc->sh4; + + sh4_scif_run(sh4); + + /* in order to clear the SCFSR2 bits, they must be read first */ + sh4->SCFSR2_last_read = sh4->SCFSR2->full; + + return sh4->SCFSR2->full; +} + +REG_W32(sh4_cb, SCFSR2) { + struct sh4 *sh4 = dc->sh4; + + /* can only clear ER, TEND, TDFE, BRK, RDF and DR */ + value |= 0xffffff0c; + + /* can only clear if the flag was previously read as 1 */ + value |= ~sh4->SCFSR2_last_read; + + sh4->SCFSR2->full &= value; + + /* RDF / TDFE / TEND aren't cleared if still valid */ + sh4->SCFSR2->RDF = receive_triggered(sh4); + sh4->SCFSR2->TDFE = transmit_triggered(sh4); + sh4->SCFSR2->TEND = transmit_ended(sh4); + + /* clear RXI if RDF is cleared */ + if (sh4->SCSCR2->RIE && !sh4->SCFSR2->RDF) { + sh4_clear_interrupt(sh4, SH4_INT_SCIFRXI); + } + + /* clear TXI if TDFE is cleared */ + if (sh4->SCSCR2->TIE && !sh4->SCFSR2->TDFE) { + sh4_clear_interrupt(sh4, SH4_INT_SCIFTXI); + } +} + +REG_R32(sh4_cb, SCFRDR2) { + struct sh4 *sh4 = dc->sh4; + uint32_t data = receive_dequeue(sh4); + return data; +} + +REG_W32(sh4_cb, SCFRDR2) { + LOG_FATAL("unexpected write to SCFRDR2"); +} + +REG_W32(sh4_cb, SCFCR2) { + struct sh4 *sh4 = dc->sh4; + sh4->SCFCR2->full = value; + + /* unsupported */ + CHECK_EQ(sh4->SCFCR2->LOOP, 0); + + /* reset fifos */ + if (sh4->SCFCR2->RFRST) { + receive_reset(sh4); + } + if (sh4->SCFCR2->TFRST) { + transmit_reset(sh4); + } + + /* TODO handle MCE */ + + /* unsupported */ + CHECK_EQ(sh4->SCFCR2->RSTRG, 0); +} + +REG_R32(sh4_cb, SCLSR2) { + struct sh4 *sh4 = dc->sh4; + return sh4->SCLSR2->full; +} + +REG_W32(sh4_cb, SCLSR2) { + struct sh4 *sh4 = dc->sh4; + + /* TODO ORER can only be cleared if read as 1 first */ + + sh4->SCLSR2->full = value; +} diff --git a/src/guest/sh4/sh4_scif.h b/src/guest/sh4/sh4_scif.h new file mode 100644 index 00000000..2b20e71f --- /dev/null +++ b/src/guest/sh4/sh4_scif.h @@ -0,0 +1,15 @@ +#ifndef SH4_SCIF_H +#define SH4_SCIF_H + +#define SCIF_FIFO_SIZE 16 + +struct sh4_scif_fifo { + int head; + int tail; + + /* ringbuffers have an ambiguous case when the head is equal to the tail - the + queue could be full or empty. add one to the fifo size to avoid this */ + uint8_t data[SCIF_FIFO_SIZE + 1]; +}; + +#endif diff --git a/src/guest/sh4/sh4_types.h b/src/guest/sh4/sh4_types.h index 44804391..9a568b18 100644 --- a/src/guest/sh4/sh4_types.h +++ b/src/guest/sh4/sh4_types.h @@ -191,17 +191,23 @@ union scfcr2 { uint32_t RFRST : 1; uint32_t TFRST : 1; uint32_t MCE : 1; - uint32_t TTRG0 : 1; - uint32_t TTRG1 : 1; - uint32_t RTRG0 : 1; - uint32_t RTRG1 : 1; - uint32_t RSTRG0 : 1; - uint32_t RSTRG1 : 1; - uint32_t RSTRG2 : 1; + uint32_t TTRG : 2; + uint32_t RTRG : 2; + uint32_t RSTRG : 3; uint32_t : 21; }; }; +union scfdr2 { + uint32_t full; + struct { + uint32_t R : 5; + uint32_t : 3; + uint32_t T : 5; + uint32_t : 19; + }; +}; + union sclsr2 { uint32_t full; struct { diff --git a/tools/reload/main.c b/tools/reload/main.c new file mode 100644 index 00000000..7917f68d --- /dev/null +++ b/tools/reload/main.c @@ -0,0 +1,350 @@ +#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; +}