SummerCart64/sw/controller/src/usb.c
Mateusz Faderewski 0dbec80183
[SC64][SW] USB debug improvements (#38)
- 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
2023-04-23 13:07:47 +02:00

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();
}
}