[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
This commit is contained in:
Mateusz Faderewski 2023-04-23 13:07:47 +02:00 committed by GitHub
parent 248feb94e8
commit 0dbec80183
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 141 additions and 36 deletions

View File

@ -6,6 +6,7 @@
typedef enum {
TIMER_ID_USB,
TIMER_ID_WRITEBACK,
__TIMER_ID_COUNT
} timer_id_t;

View File

@ -5,19 +5,22 @@
#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 BOOTLOADER_ADDRESS (0x04E00000UL)
#define BOOTLOADER_LENGTH (1920 * 1024)
#define MEMORY_LENGTH (0x05002980UL)
#define MEMORY_LENGTH (0x05002980UL)
#define RX_FLUSH_ADDRESS (0x07F00000UL)
#define RX_FLUSH_LENGTH (1 * 1024 * 1024)
#define RX_FLUSH_ADDRESS (0x07F00000UL)
#define RX_FLUSH_LENGTH (1 * 1024 * 1024)
#define DEBUG_WRITE_TIMEOUT_TICKS (100)
enum rx_state {
@ -49,6 +52,9 @@ struct process {
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;
@ -161,11 +167,16 @@ static void usb_rx_process (void) {
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);
}
}
}
@ -268,6 +279,7 @@ static void usb_rx_process (void) {
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]);
@ -284,20 +296,26 @@ static void usb_rx_process (void) {
case 'U':
if (p.rx_args[1] == 0) {
p.rx_state = RX_STATE_IDLE;
} else if ((p.read_length > 0) && usb_dma_ready()) {
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;
} 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;
@ -390,9 +408,19 @@ static void usb_rx_process (void) {
fpga_reg_set(REG_USB_DMA_SCR, DMA_SCR_DIRECTION | DMA_SCR_START);
p.rx_args[1] -= length;
} else {
p.rx_state = RX_STATE_IDLE;
p.response_pending = true;
p.response_error = true;
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;
}
}
}
}

View File

@ -8,8 +8,9 @@
typedef enum packet_cmd {
PACKET_CMD_BUTTON_TRIGGER = 'B',
PACKET_CMD_DD_REQUEST = 'D',
PACKET_CMD_DATA_FLUSHED = 'G',
PACKET_CMD_DEBUG_OUTPUT = 'U',
PACKET_CMD_DD_REQUEST = 'D',
PACKET_CMD_ISV_OUTPUT = 'I',
PACKET_CMD_SAVE_WRITEBACK = 'S',
PACKET_CMD_UPDATE_STATUS = 'F',

View File

@ -26,6 +26,7 @@ enum DataType {
RawBinary,
Header,
Screenshot,
Heartbeat,
Unknown,
}
@ -36,6 +37,7 @@ impl From<u8> for DataType {
0x02 => Self::RawBinary,
0x03 => Self::Header,
0x04 => Self::Screenshot,
0x05 => Self::Heartbeat,
_ => Self::Unknown,
}
}
@ -48,6 +50,7 @@ impl From<DataType> for u8 {
DataType::RawBinary => 0x02,
DataType::Header => 0x03,
DataType::Screenshot => 0x04,
DataType::Heartbeat => 0x05,
DataType::Unknown => 0xFF,
}
}
@ -114,6 +117,26 @@ impl TryFrom<Vec<u8>> for ScreenshotMetadata {
}
}
struct Heartbeat {
usb_protocol: u16,
version: u16,
}
impl TryFrom<&[u8]> for Heartbeat {
type Error = String;
fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
if value.len() < 4 {
return Err("Invalid heartbeat data length".into());
}
let usb_protocol = u16::from_be_bytes(value[0..2].try_into().unwrap());
let version = u16::from_be_bytes(value[2..4].try_into().unwrap());
Ok(Heartbeat {
usb_protocol,
version,
})
}
}
macro_rules! success {
($($a: tt)*) => {
println!("{}", format!($($a)*).bright_blue());
@ -134,6 +157,7 @@ macro_rules! stop {
}
const MAX_PACKET_LENGTH: usize = 8 * 1024 * 1024;
const SUPPORTED_USB_PROTOCOL_VERSION: u16 = 2;
impl Handler {
pub fn set_text_encoding(&mut self, encoding: Encoding) {
@ -195,6 +219,7 @@ impl Handler {
}
}
}
data.append(&mut b"\0".to_vec());
sc64::DebugPacket {
datatype: DataType::Text.into(),
data,
@ -220,6 +245,7 @@ impl Handler {
DataType::RawBinary => self.handle_datatype_raw_binary(&data),
DataType::Header => self.handle_datatype_header(&data),
DataType::Screenshot => self.handle_datatype_screenshot(&data),
DataType::Heartbeat => self.handle_datatype_heartbeat(&data),
_ => error!("Received unknown debug packet datatype: 0x{datatype:02X}"),
}
}
@ -252,13 +278,18 @@ impl Handler {
Ok(mut file) => {
if let Err(error) = file.write_all(&save_writeback.data) {
error!("Couldn't write save [{filename}]: {error}");
} else {
success!("Wrote [{}] save to [{filename}]", save_writeback.save);
}
success!("Wrote [{}] save to [{filename}]", save_writeback.save);
}
Err(error) => error!("Couldn't create save writeback file [{filename}]: {error}"),
}
}
pub fn handle_data_flushed(&self) {
error!("Debug data write dropped due to timeout");
}
fn handle_datatype_text(&self, data: &[u8]) {
self.print_text(data);
}
@ -269,8 +300,9 @@ impl Handler {
Ok(mut file) => {
if let Err(error) = file.write_all(data) {
error!("Couldn't write raw binary [{filename}]: {error}");
} else {
success!("Wrote [{}] bytes to [{filename}]", data.len());
}
success!("Wrote [{}] bytes to [{filename}]", data.len());
}
Err(error) => error!("Couldn't create raw binary file [{filename}]: {error}"),
}
@ -319,6 +351,23 @@ impl Handler {
success!("Wrote {width}x{height} pixels to [{filename}]");
}
fn handle_datatype_heartbeat(&mut self, data: &[u8]) {
let Heartbeat {
usb_protocol,
version,
} = match data.try_into() {
Ok(heartbeat) => heartbeat,
Err(error) => return error!("Error while parsing heartbeat datatype: {error}"),
};
if usb_protocol > SUPPORTED_USB_PROTOCOL_VERSION {
return error!("Unsupported USB protocol version: {usb_protocol}");
}
match version {
1 => {}
_ => return error!("Unsupported USB heartbeat version: {version}"),
}
}
fn print_text(&self, data: &[u8]) {
match self.encoding {
Encoding::UTF8 => print!("{}", String::from_utf8_lossy(&data)),

View File

@ -48,7 +48,7 @@ enum Commands {
command: DownloadCommands,
},
/// Upload ROM (and save), 64DD IPL then run disk server
/// Upload ROM (and save), 64DD IPL then run disk/debug server
_64DD(_64DDArgs),
/// Enter debug mode
@ -127,7 +127,7 @@ struct _64DDArgs {
#[arg(short, long)]
rom: Option<PathBuf>,
/// Path to the save file
/// Path to the save file (also used by save writeback mechanism)
#[arg(short, long, requires = "rom")]
save: Option<PathBuf>,
@ -398,11 +398,13 @@ fn handle_64dd_command(connection: Connection, args: &_64DDArgs) -> Result<(), s
let mut sc64 = init_sc64(connection, true)?;
let mut debug_handler = debug::new();
println!(
"{} {} {}",
"[WARNING]:".bold().bright_yellow(),
"Do not use this mode when real 64DD accessory is connected to the N64.".bright_yellow(),
"This might permanently damage either 64DD or SC64.".bright_yellow()
"{}\n{}\n{}",
"========== [WARNING] ==========".bold().bright_yellow(),
"Do not use this mode when real 64DD accessory is connected to the N64".bright_yellow(),
"Doing so might permanently damage either N64, 64DD or SC64".bright_yellow()
);
sc64.reset_state()?;
@ -478,10 +480,7 @@ fn handle_64dd_command(connection: Connection, args: &_64DDArgs) -> Result<(), s
.iter()
.map(|path| path.file_name().unwrap().to_string_lossy().to_string())
.collect();
let mut disks = disk::open_multiple(&disk_paths)?;
let mut selected_disk_index: usize = 0;
let mut selected_disk: Option<&mut disk::Disk> = None;
let drive_type = match disks[0].get_format() {
disk::Format::Retail => sc64::DdDriveType::Retail,
@ -495,11 +494,22 @@ fn handle_64dd_command(connection: Connection, args: &_64DDArgs) -> Result<(), s
println!(
"{}: {}",
"[64DD]".bold(),
"Press button on the SC64 device to cycle through provided disks"
"Press button on the back of SC64 device to cycle through provided disks"
.bold()
.bright_green()
);
let mut selected_disk_index: usize = 0;
let mut selected_disk = Some(&mut disks[selected_disk_index]);
println!(
"{}: Disk inserted [{}]",
"[64DD]".bold(),
disk_names[selected_disk_index].bright_green()
);
sc64.set_64dd_disk_state(sc64::DdDiskState::Inserted)?;
sc64.set_save_writeback(true)?;
let exit = setup_exit_flag();
while !exit.load(Ordering::Relaxed) {
if let Some(data_packet) = sc64.receive_data_packet()? {
@ -554,16 +564,27 @@ fn handle_64dd_command(connection: Connection, args: &_64DDArgs) -> Result<(), s
selected_disk_index = 0;
}
selected_disk = Some(&mut disks[selected_disk_index]);
sc64.set_64dd_disk_state(sc64::DdDiskState::Inserted)?;
println!(
"{}: Disk inserted [{}]",
"[64DD]".bold(),
disk_names[selected_disk_index].bright_green()
);
sc64.set_64dd_disk_state(sc64::DdDiskState::Inserted)?;
}
}
sc64::DataPacket::DebugData(debug_packet) => {
debug_handler.handle_debug_packet(debug_packet);
}
sc64::DataPacket::SaveWriteback(save_writeback) => {
debug_handler.handle_save_writeback(save_writeback, &args.save);
}
sc64::DataPacket::DataFlushed => {
debug_handler.handle_data_flushed();
}
_ => {}
}
} else if let Some(debug_packet) = debug_handler.process_user_input() {
sc64.send_debug_packet(debug_packet)?;
}
}
@ -608,6 +629,9 @@ fn handle_debug_command(connection: Connection, args: &DebugArgs) -> Result<(),
sc64::DataPacket::SaveWriteback(save_writeback) => {
debug_handler.handle_save_writeback(save_writeback, &args.save);
}
sc64::DataPacket::DataFlushed => {
debug_handler.handle_data_flushed();
}
_ => {}
}
} else if let Some(debug_packet) = debug_handler.process_user_input() {

View File

@ -580,6 +580,7 @@ impl From<Setting> for [u32; 2] {
pub enum DataPacket {
Button,
DataFlushed,
DebugData(DebugPacket),
DiskRequest(DiskPacket),
IsViewer64(Vec<u8>),
@ -592,6 +593,7 @@ impl TryFrom<Packet> for DataPacket {
fn try_from(value: Packet) -> Result<Self, Self::Error> {
Ok(match value.id {
b'B' => Self::Button,
b'G' => Self::DataFlushed,
b'U' => Self::DebugData(value.data.try_into()?),
b'D' => Self::DiskRequest(value.data.try_into()?),
b'I' => Self::IsViewer64(value.data),