dreamcast/serial_load.cpp

166 lines
4.0 KiB
C++

#include <cstdint>
#include "sh7091/serial.hpp"
#include "sh7091/serial_dma.hpp"
#include "serial_load.hpp"
#include "crc32.h"
namespace serial_load {
struct state state;
static void move(void *dst, const void *src, uint32_t n)
{
uint8_t * d = reinterpret_cast<uint8_t *>(dst);
const uint8_t * s = reinterpret_cast<const uint8_t *>(src);
if (d==s) return;
if (d<s) {
for (; n; n--) *d++ = *s++;
} else {
while (n) n--, d[n] = s[n];
}
}
void init()
{
state.len = 0;
state.fsm_state = fsm_state::idle;
sh7091.DMAC.CHCR1 = 0;
}
void jump_to_func(const uint32_t addr)
{
serial::string("jump to: ");
serial::integer<uint32_t>(addr);
// save our stack
asm volatile ("ldc r15, gbr; "
"mov #0, r15; "
"jsr @%0; "
"nop; "
"stc gbr, r15; "
:
: "r"(addr) /* input */
/* clobbered register */
: "r0","r1","r2","r3","r4","r5","r6","r7","r8","r9","r10","r11","r12","macl","mach","gbr","pr"
);
// restore our stack
}
static inline void prestart_write()
{
uint32_t dest = state.buf.arg[0];
uint32_t size = state.buf.arg[1];
serial::recv_dma(dest - 1, size + 1);
state.write_crc.value = 0xffffffff;
state.write_crc.offset = dest;
}
struct state_arglen_reply command_list[] = {
{command::write, reply::write, fsm_state::write},
{command::read , reply::read , fsm_state::read },
{command::jump , reply::jump , fsm_state::jump },
{command::speed, reply::speed, fsm_state::speed},
};
constexpr uint32_t command_list_length = (sizeof (command_list)) / (sizeof (command_list[0]));
void recv(uint8_t c)
{
state.buf.u8[state.len++] = c;
switch (state.fsm_state) {
case fsm_state::idle:
if (state.len == 16) {
for (uint32_t i = 0; i < command_list_length; i++) {
struct state_arglen_reply& sar = command_list[i];
if (state.buf.cmd == sar.command) {
uint32_t crc = crc32(&state.buf.u8[0], 12);
if (crc == state.buf.crc) {
// valid command, do the transition
if (state.buf.cmd == command::write) prestart_write();
state.fsm_state = sar.fsm_state;
state.len = 0;
union command_reply reply = command_reply(sar.reply, state.buf.arg[0], state.buf.arg[1]);
serial::string(reply.u8, 16);
return;
} else {
/*
union command_reply reply = crc_error_reply(crc);
serial::string(reply.u8, 16);
state.len = 0;
return;
*/
}
}
}
// invalid command
move(&state.buf.u8[0], &state.buf.u8[1], state.len - 1);
state.len -= 1;
break;
}
default:
break;
}
}
void tick()
{
switch (state.fsm_state) {
case fsm_state::idle:
break;
case fsm_state::write:
{
// read chcr1 before dar1 to avoid race
uint32_t chcr1 = sh7091.DMAC.CHCR1;
uint32_t dar1 = sh7091.DMAC.DAR1;
if (dar1 > state.write_crc.offset) {
uint32_t len = dar1 - state.write_crc.offset;
const uint8_t * dest = reinterpret_cast<const uint8_t *>(state.write_crc.offset);
state.write_crc.value = crc32_update(state.write_crc.value, dest, len);
state.write_crc.offset += len;
}
if (chcr1 & dmac::chcr::te::transfers_completed) {
state.write_crc.value ^= 0xffffffff;
union command_reply reply = write_crc_reply(state.write_crc.value);
serial::string(reply.u8, 16);
sh7091.DMAC.CHCR1 = 0;
// transition to next state
state.fsm_state = fsm_state::idle;
}
}
break;
case fsm_state::read:
break;
case fsm_state::jump:
{
using namespace scif;
// wait for serial transmission to end
constexpr uint32_t transmission_end = scfsr2::tend::bit_mask | scfsr2::tdfe::bit_mask;
if ((sh7091.SCIF.SCFSR2 & transmission_end) != transmission_end)
return;
const uint32_t dest = state.buf.arg[0];
jump_to_func(dest);
// transition to next state
state.fsm_state = fsm_state::idle;
}
break;
case fsm_state::speed:
{
const uint32_t speed = state.buf.arg[0];
serial::init(speed & 0xff);
// transition to next state
state.fsm_state = fsm_state::idle;
}
break;
default:
break;
}
}
}