mirror of
https://github.com/Polprzewodnikowy/SummerCart64.git
synced 2024-11-29 00:44:13 +01:00
0dbec80183
- N64 -> PC heartbeat datatype support - PC -> N64 debug write timeout implementation (1 second) - PC -> N64 text datatype bug fix (added null byte at the end) - 64DD disk insertion by default
585 lines
18 KiB
C
585 lines
18 KiB
C
#include "app.h"
|
|
#include "cfg.h"
|
|
#include "cic.h"
|
|
#include "dd.h"
|
|
#include "flash.h"
|
|
#include "fpga.h"
|
|
#include "rtc.h"
|
|
#include "timer.h"
|
|
#include "update.h"
|
|
#include "usb.h"
|
|
#include "version.h"
|
|
#include "writeback.h"
|
|
|
|
|
|
#define BOOTLOADER_ADDRESS (0x04E00000UL)
|
|
#define BOOTLOADER_LENGTH (1920 * 1024)
|
|
|
|
#define MEMORY_LENGTH (0x05002980UL)
|
|
|
|
#define RX_FLUSH_ADDRESS (0x07F00000UL)
|
|
#define RX_FLUSH_LENGTH (1 * 1024 * 1024)
|
|
|
|
#define DEBUG_WRITE_TIMEOUT_TICKS (100)
|
|
|
|
|
|
enum rx_state {
|
|
RX_STATE_IDLE,
|
|
RX_STATE_ARGS,
|
|
RX_STATE_DATA,
|
|
RX_STATE_FLUSH,
|
|
};
|
|
|
|
enum tx_state {
|
|
TX_STATE_IDLE,
|
|
TX_STATE_TOKEN,
|
|
TX_STATE_DATA,
|
|
TX_STATE_DMA,
|
|
TX_STATE_FLUSH,
|
|
};
|
|
|
|
|
|
struct process {
|
|
enum rx_state rx_state;
|
|
uint8_t rx_counter;
|
|
uint8_t rx_cmd;
|
|
uint32_t rx_args[2];
|
|
bool rx_dma_running;
|
|
|
|
enum tx_state tx_state;
|
|
uint8_t tx_counter;
|
|
usb_tx_info_t tx_info;
|
|
uint32_t tx_token;
|
|
bool tx_dma_running;
|
|
|
|
bool flush_response;
|
|
bool flush_packet;
|
|
|
|
bool response_pending;
|
|
bool response_error;
|
|
usb_tx_info_t response_info;
|
|
|
|
bool packet_pending;
|
|
usb_tx_info_t packet_info;
|
|
|
|
bool read_ready;
|
|
uint32_t read_length;
|
|
uint32_t read_address;
|
|
};
|
|
|
|
|
|
static struct process p;
|
|
|
|
|
|
static const char CMD_TOKEN[3] = { 'C', 'M', 'D' };
|
|
static const uint32_t CMP_TOKEN = (0x434D5000UL);
|
|
static const uint32_t ERR_TOKEN = (0x45525200UL);
|
|
static const uint32_t PKT_TOKEN = (0x504B5400UL);
|
|
|
|
|
|
static bool usb_dma_ready (void) {
|
|
return !((fpga_reg_get(REG_USB_DMA_SCR) & DMA_SCR_BUSY));
|
|
}
|
|
|
|
static bool usb_rx_byte (uint8_t *data) {
|
|
if (fpga_usb_status_get() & USB_STATUS_RXNE) {
|
|
*data = fpga_usb_pop();
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static bool usb_tx_byte (uint8_t data) {
|
|
if (fpga_usb_status_get() & USB_STATUS_TXE) {
|
|
fpga_usb_push(data);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static uint8_t usb_rx_word_counter = 0;
|
|
static uint32_t usb_rx_word_buffer = 0;
|
|
|
|
static bool usb_rx_word (uint32_t *data) {
|
|
uint8_t tmp;
|
|
while (usb_rx_byte(&tmp)) {
|
|
usb_rx_word_buffer = (usb_rx_word_buffer << 8) | tmp;
|
|
usb_rx_word_counter += 1;
|
|
if (usb_rx_word_counter == 4) {
|
|
usb_rx_word_counter = 0;
|
|
*data = usb_rx_word_buffer;
|
|
usb_rx_word_buffer = 0;
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static uint8_t usb_tx_word_counter = 0;
|
|
|
|
static bool usb_tx_word (uint32_t data) {
|
|
while (usb_tx_byte(data >> ((3 - usb_tx_word_counter) * 8))) {
|
|
usb_tx_word_counter += 1;
|
|
if (usb_tx_word_counter == 4) {
|
|
usb_tx_word_counter = 0;
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static uint8_t usb_rx_cmd_counter = 0;
|
|
|
|
static bool usb_rx_cmd (uint8_t *cmd) {
|
|
uint8_t data;
|
|
while (usb_rx_byte(&data)) {
|
|
if (usb_rx_cmd_counter == 3) {
|
|
*cmd = data;
|
|
usb_rx_cmd_counter = 0;
|
|
return true;
|
|
}
|
|
if (data != CMD_TOKEN[usb_rx_cmd_counter++]) {
|
|
usb_rx_cmd_counter = 0;
|
|
return false;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static bool usb_validate_address_length (uint32_t address, uint32_t length, bool exclude_bootloader) {
|
|
if ((address >= MEMORY_LENGTH) || (length > MEMORY_LENGTH)) {
|
|
return true;
|
|
}
|
|
if ((address + length) > MEMORY_LENGTH) {
|
|
return true;
|
|
}
|
|
if (exclude_bootloader) {
|
|
if (((address + length) > BOOTLOADER_ADDRESS) && (address < (BOOTLOADER_ADDRESS + BOOTLOADER_LENGTH))) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static void usb_rx_process (void) {
|
|
if (p.rx_state == RX_STATE_IDLE) {
|
|
if (!p.response_pending && usb_rx_cmd(&p.rx_cmd)) {
|
|
p.rx_state = RX_STATE_ARGS;
|
|
p.rx_counter = 0;
|
|
p.rx_dma_running = false;
|
|
p.flush_response = false;
|
|
p.flush_packet = false;
|
|
p.response_error = false;
|
|
p.response_info.cmd = p.rx_cmd;
|
|
p.response_info.data_length = 0;
|
|
p.response_info.dma_length = 0;
|
|
p.response_info.done_callback = NULL;
|
|
if (p.rx_cmd == 'U') {
|
|
timer_set(TIMER_ID_USB, DEBUG_WRITE_TIMEOUT_TICKS);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (p.rx_state == RX_STATE_ARGS) {
|
|
while (usb_rx_word(&p.rx_args[p.rx_counter])) {
|
|
p.rx_counter += 1;
|
|
if (p.rx_counter == 2) {
|
|
p.rx_counter = 0;
|
|
p.rx_state = RX_STATE_DATA;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (p.rx_state == RX_STATE_DATA) {
|
|
switch (p.rx_cmd) {
|
|
case 'v':
|
|
p.rx_state = RX_STATE_IDLE;
|
|
p.response_pending = true;
|
|
p.response_info.data_length = 4;
|
|
p.response_info.data[0] = cfg_get_identifier();
|
|
break;
|
|
|
|
case 'V':
|
|
p.rx_state = RX_STATE_IDLE;
|
|
p.response_pending = true;
|
|
p.response_info.data_length = 8;
|
|
version_firmware(&p.response_info.data[0], &p.response_info.data[1]);
|
|
break;
|
|
|
|
case 'R':
|
|
cfg_reset_state();
|
|
cic_reset_parameters();
|
|
p.rx_state = RX_STATE_IDLE;
|
|
p.response_pending = true;
|
|
break;
|
|
|
|
case 'B':
|
|
cic_set_parameters(p.rx_args);
|
|
p.rx_state = RX_STATE_IDLE;
|
|
p.response_pending = true;
|
|
break;
|
|
|
|
case 'c':
|
|
p.response_error = cfg_query(p.rx_args);
|
|
p.rx_state = RX_STATE_IDLE;
|
|
p.response_pending = true;
|
|
p.response_info.data_length = 4;
|
|
p.response_info.data[0] = p.rx_args[1];
|
|
break;
|
|
|
|
case 'C':
|
|
p.response_error = cfg_update(p.rx_args);
|
|
p.rx_state = RX_STATE_IDLE;
|
|
p.response_pending = true;
|
|
break;
|
|
|
|
case 'a':
|
|
p.response_error = cfg_query_setting(p.rx_args);
|
|
p.rx_state = RX_STATE_IDLE;
|
|
p.response_pending = true;
|
|
p.response_info.data_length = 4;
|
|
p.response_info.data[0] = p.rx_args[1];
|
|
break;
|
|
|
|
case 'A':
|
|
p.response_error = cfg_update_setting(p.rx_args);
|
|
p.rx_state = RX_STATE_IDLE;
|
|
p.response_pending = true;
|
|
break;
|
|
|
|
case 't':
|
|
cfg_get_time(p.rx_args);
|
|
p.rx_state = RX_STATE_IDLE;
|
|
p.response_pending = true;
|
|
p.response_info.data_length = 8;
|
|
p.response_info.data[0] = p.rx_args[0];
|
|
p.response_info.data[1] = p.rx_args[1];
|
|
break;
|
|
|
|
case 'T':
|
|
cfg_set_time(p.rx_args);
|
|
p.rx_state = RX_STATE_IDLE;
|
|
p.response_pending = true;
|
|
break;
|
|
|
|
case 'm':
|
|
p.rx_state = RX_STATE_IDLE;
|
|
p.response_pending = true;
|
|
if (usb_validate_address_length(p.rx_args[0], p.rx_args[1], false)) {
|
|
p.response_error = true;
|
|
} else {
|
|
p.response_info.dma_address = p.rx_args[0];
|
|
p.response_info.dma_length = p.rx_args[1];
|
|
}
|
|
break;
|
|
|
|
case 'M':
|
|
if (usb_dma_ready()) {
|
|
if (!p.rx_dma_running) {
|
|
if (usb_validate_address_length(p.rx_args[0], p.rx_args[1], true)) {
|
|
p.rx_state = RX_STATE_FLUSH;
|
|
p.flush_response = true;
|
|
} else {
|
|
fpga_reg_set(REG_USB_DMA_ADDRESS, p.rx_args[0]);
|
|
fpga_reg_set(REG_USB_DMA_LENGTH, p.rx_args[1]);
|
|
fpga_reg_set(REG_USB_DMA_SCR, DMA_SCR_DIRECTION | DMA_SCR_START);
|
|
p.rx_dma_running = true;
|
|
}
|
|
} else {
|
|
p.rx_state = RX_STATE_IDLE;
|
|
p.response_pending = true;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case 'U':
|
|
if (p.rx_args[1] == 0) {
|
|
p.rx_state = RX_STATE_IDLE;
|
|
} else if (usb_dma_ready()) {
|
|
if (p.read_length > 0) {
|
|
uint32_t length = (p.read_length > p.rx_args[1]) ? p.rx_args[1] : p.read_length;
|
|
if (!p.rx_dma_running) {
|
|
fpga_reg_set(REG_USB_DMA_ADDRESS, p.read_address);
|
|
fpga_reg_set(REG_USB_DMA_LENGTH, length);
|
|
fpga_reg_set(REG_USB_DMA_SCR, DMA_SCR_DIRECTION | DMA_SCR_START);
|
|
p.rx_dma_running = true;
|
|
p.read_ready = false;
|
|
} else {
|
|
p.rx_args[1] -= length;
|
|
p.rx_dma_running = false;
|
|
p.read_length -= length;
|
|
p.read_address += length;
|
|
p.read_ready = true;
|
|
timer_set(TIMER_ID_USB, DEBUG_WRITE_TIMEOUT_TICKS);
|
|
}
|
|
} else if (timer_get(TIMER_ID_USB) == 0) {
|
|
p.rx_state = RX_STATE_FLUSH;
|
|
p.flush_packet = true;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case 'D':
|
|
dd_set_block_ready(p.rx_args[0] == 0);
|
|
p.rx_state = RX_STATE_IDLE;
|
|
p.response_pending = true;
|
|
break;
|
|
|
|
case 'W':
|
|
writeback_enable(WRITEBACK_USB);
|
|
p.rx_state = RX_STATE_IDLE;
|
|
p.response_pending = true;
|
|
break;
|
|
|
|
case 'p':
|
|
if (p.rx_args[0]) {
|
|
flash_wait_busy();
|
|
}
|
|
p.rx_state = RX_STATE_IDLE;
|
|
p.response_pending = true;
|
|
p.response_info.data_length = 4;
|
|
p.response_info.data[0] = FLASH_ERASE_BLOCK_SIZE;
|
|
break;
|
|
|
|
case 'P':
|
|
if (usb_validate_address_length(p.rx_args[0], FLASH_ERASE_BLOCK_SIZE, true)) {
|
|
p.response_error = true;
|
|
} else {
|
|
p.response_error = flash_erase_block(p.rx_args[0]);
|
|
}
|
|
p.rx_state = RX_STATE_IDLE;
|
|
p.response_pending = true;
|
|
break;
|
|
|
|
case 'f':
|
|
cfg_set_rom_write_enable(false);
|
|
p.response_info.data[0] = update_backup(p.rx_args[0], &p.response_info.data[1]);
|
|
p.rx_state = RX_STATE_IDLE;
|
|
p.response_pending = true;
|
|
p.response_error = (p.response_info.data[0] != UPDATE_OK);
|
|
p.response_info.data_length = 8;
|
|
break;
|
|
|
|
case 'F':
|
|
cfg_set_rom_write_enable(false);
|
|
p.response_info.data[0] = update_prepare(p.rx_args[0], p.rx_args[1]);
|
|
p.rx_state = RX_STATE_IDLE;
|
|
p.response_pending = true;
|
|
p.response_info.data_length = 4;
|
|
if (p.response_info.data[0] == UPDATE_OK) {
|
|
p.response_info.done_callback = update_start;
|
|
} else {
|
|
p.response_error = true;
|
|
}
|
|
break;
|
|
|
|
case '?':
|
|
p.rx_state = RX_STATE_IDLE;
|
|
p.response_pending = true;
|
|
p.response_info.data_length = 8;
|
|
p.response_info.data[0] = fpga_reg_get(REG_DEBUG_0);
|
|
p.response_info.data[1] = fpga_reg_get(REG_DEBUG_1);
|
|
break;
|
|
|
|
case '%':
|
|
p.rx_state = RX_STATE_IDLE;
|
|
p.response_pending = true;
|
|
p.response_info.data_length = 16;
|
|
app_get_stack_usage(p.response_info.data);
|
|
break;
|
|
|
|
default:
|
|
p.rx_state = RX_STATE_IDLE;
|
|
p.response_pending = true;
|
|
p.response_error = true;
|
|
p.response_info.data_length = 4;
|
|
p.response_info.data[0] = 0xFFFFFFFF;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (p.rx_state == RX_STATE_FLUSH) {
|
|
if (usb_dma_ready()) {
|
|
if (p.rx_args[1] != 0) {
|
|
uint32_t length = (p.rx_args[1] > RX_FLUSH_LENGTH) ? RX_FLUSH_LENGTH : p.rx_args[1];
|
|
fpga_reg_set(REG_USB_DMA_ADDRESS, RX_FLUSH_ADDRESS);
|
|
fpga_reg_set(REG_USB_DMA_LENGTH, length);
|
|
fpga_reg_set(REG_USB_DMA_SCR, DMA_SCR_DIRECTION | DMA_SCR_START);
|
|
p.rx_args[1] -= length;
|
|
} else {
|
|
if (p.flush_response) {
|
|
p.rx_state = RX_STATE_IDLE;
|
|
p.response_pending = true;
|
|
p.response_error = true;
|
|
} else if (p.flush_packet) {
|
|
usb_tx_info_t packet_info;
|
|
usb_create_packet(&packet_info, PACKET_CMD_DATA_FLUSHED);
|
|
if (usb_enqueue_packet(&packet_info)) {
|
|
p.rx_state = RX_STATE_IDLE;
|
|
}
|
|
} else {
|
|
p.rx_state = RX_STATE_IDLE;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static void usb_tx_process (void) {
|
|
if (p.tx_state == TX_STATE_IDLE) {
|
|
if (p.response_pending) {
|
|
p.response_pending = false;
|
|
p.tx_state = TX_STATE_TOKEN;
|
|
p.tx_counter = 0;
|
|
p.tx_info = p.response_info;
|
|
p.tx_token = p.response_error ? ERR_TOKEN : CMP_TOKEN;
|
|
p.tx_dma_running = false;
|
|
} else if (p.packet_pending) {
|
|
p.packet_pending = false;
|
|
p.tx_state = TX_STATE_TOKEN;
|
|
p.tx_counter = 0;
|
|
p.tx_info = p.packet_info;
|
|
p.tx_token = PKT_TOKEN;
|
|
p.tx_dma_running = false;
|
|
}
|
|
}
|
|
|
|
if (p.tx_state == TX_STATE_TOKEN) {
|
|
if (p.tx_counter == 0) {
|
|
if (usb_tx_word(p.tx_token | p.tx_info.cmd)) {
|
|
p.tx_counter += 1;
|
|
}
|
|
}
|
|
if (p.tx_counter == 1) {
|
|
if (usb_tx_word(p.tx_info.data_length + p.tx_info.dma_length)) {
|
|
p.tx_state = TX_STATE_DATA;
|
|
p.tx_counter = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (p.tx_state == TX_STATE_DATA) {
|
|
if (p.tx_info.data_length > 0) {
|
|
while (usb_tx_word(p.tx_info.data[p.tx_counter])) {
|
|
p.tx_counter += 1;
|
|
if (p.tx_counter == (p.tx_info.data_length / 4)) {
|
|
p.tx_state = TX_STATE_DMA;
|
|
p.tx_counter = 0;
|
|
break;
|
|
}
|
|
}
|
|
} else {
|
|
p.tx_state = TX_STATE_DMA;
|
|
}
|
|
}
|
|
|
|
if (p.tx_state == TX_STATE_DMA) {
|
|
if (p.tx_info.dma_length > 0) {
|
|
if (usb_dma_ready()) {
|
|
if (!p.tx_dma_running) {
|
|
p.tx_dma_running = true;
|
|
fpga_reg_set(REG_USB_DMA_ADDRESS, p.tx_info.dma_address);
|
|
fpga_reg_set(REG_USB_DMA_LENGTH, p.tx_info.dma_length);
|
|
fpga_reg_set(REG_USB_DMA_SCR, DMA_SCR_START);
|
|
} else {
|
|
p.tx_state = TX_STATE_FLUSH;
|
|
}
|
|
}
|
|
} else {
|
|
p.tx_state = TX_STATE_FLUSH;
|
|
}
|
|
}
|
|
|
|
if (p.tx_state == TX_STATE_FLUSH) {
|
|
fpga_reg_set(REG_USB_SCR, USB_SCR_WRITE_FLUSH);
|
|
if (p.tx_info.done_callback) {
|
|
p.tx_info.done_callback();
|
|
}
|
|
p.tx_state = TX_STATE_IDLE;
|
|
}
|
|
}
|
|
|
|
|
|
void usb_create_packet (usb_tx_info_t *info, usb_packet_cmd_e cmd) {
|
|
info->cmd = (uint8_t) (cmd);
|
|
info->data_length = 0;
|
|
for (int i = 0; i < 4; i++) {
|
|
info->data[i] = 0;
|
|
}
|
|
info->dma_length = 0;
|
|
info->dma_address = 0;
|
|
info->done_callback = NULL;
|
|
}
|
|
|
|
bool usb_enqueue_packet (usb_tx_info_t *info) {
|
|
if (p.packet_pending) {
|
|
return false;
|
|
}
|
|
p.packet_pending = true;
|
|
p.packet_info = *info;
|
|
return true;
|
|
}
|
|
|
|
bool usb_prepare_read (uint32_t *args) {
|
|
if (!p.read_ready) {
|
|
return false;
|
|
}
|
|
p.read_length = args[1];
|
|
p.read_address = args[0];
|
|
return true;
|
|
}
|
|
|
|
void usb_get_read_info (uint32_t *args) {
|
|
uint32_t scr = fpga_reg_get(REG_USB_SCR);
|
|
args[0] = 0;
|
|
args[1] = 0;
|
|
if (p.rx_state == RX_STATE_DATA && p.rx_cmd == 'U') {
|
|
args[0] = p.rx_args[0] & 0xFF;
|
|
args[1] = p.rx_args[1];
|
|
}
|
|
args[0] |= (p.read_length > 0) ? (1 << 31) : 0;
|
|
args[0] |= (scr & USB_SCR_RESET_STATE) ? (1 << 30) : 0;
|
|
args[0] |= (scr & USB_SCR_PWRSAV) ? (1 << 29) : 0;
|
|
}
|
|
|
|
void usb_init (void) {
|
|
fpga_reg_set(REG_USB_DMA_SCR, DMA_SCR_STOP);
|
|
fpga_reg_set(REG_USB_SCR, USB_SCR_FIFO_FLUSH);
|
|
|
|
p.rx_state = RX_STATE_IDLE;
|
|
p.tx_state = TX_STATE_IDLE;
|
|
|
|
p.response_pending = false;
|
|
p.packet_pending = false;
|
|
|
|
p.read_ready = true;
|
|
p.read_length = 0;
|
|
p.read_address = 0;
|
|
|
|
usb_rx_word_counter = 0;
|
|
usb_rx_word_buffer = 0;
|
|
usb_tx_word_counter = 0;
|
|
usb_rx_cmd_counter = 0;
|
|
}
|
|
|
|
void usb_process (void) {
|
|
uint32_t scr = fpga_reg_get(REG_USB_SCR);
|
|
if (scr & (USB_SCR_PWRSAV | USB_SCR_RESET_STATE | USB_SCR_RESET_PENDING)) {
|
|
if (p.packet_pending && p.packet_info.done_callback) {
|
|
p.packet_pending = false;
|
|
p.packet_info.done_callback();
|
|
}
|
|
if (scr & USB_SCR_RESET_PENDING) {
|
|
if (p.tx_state != TX_STATE_IDLE && p.tx_info.done_callback) {
|
|
p.tx_info.done_callback();
|
|
}
|
|
usb_init();
|
|
fpga_reg_set(REG_USB_SCR, USB_SCR_RESET_ACK);
|
|
}
|
|
} else {
|
|
usb_rx_process();
|
|
usb_tx_process();
|
|
}
|
|
}
|