mirror of https://github.com/inolen/redream.git
initial sh4 scif support for interacting with dcload
This commit is contained in:
parent
e06ce8fa71
commit
1ce904f5a9
|
@ -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}
|
||||
|
|
|
@ -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 \
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
#ifndef SERIAL_H
|
||||
#define SERIAL_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
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
|
|
@ -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];
|
||||
};
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
}
|
Loading…
Reference in New Issue