mirror of
https://github.com/Polprzewodnikowy/SummerCart64.git
synced 2024-11-21 21:49:15 +01:00
[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:
parent
248feb94e8
commit
0dbec80183
@ -6,6 +6,7 @@
|
||||
|
||||
|
||||
typedef enum {
|
||||
TIMER_ID_USB,
|
||||
TIMER_ID_WRITEBACK,
|
||||
__TIMER_ID_COUNT
|
||||
} timer_id_t;
|
||||
|
@ -5,6 +5,7 @@
|
||||
#include "flash.h"
|
||||
#include "fpga.h"
|
||||
#include "rtc.h"
|
||||
#include "timer.h"
|
||||
#include "update.h"
|
||||
#include "usb.h"
|
||||
#include "version.h"
|
||||
@ -19,6 +20,8 @@
|
||||
#define RX_FLUSH_ADDRESS (0x07F00000UL)
|
||||
#define RX_FLUSH_LENGTH (1 * 1024 * 1024)
|
||||
|
||||
#define DEBUG_WRITE_TIMEOUT_TICKS (100)
|
||||
|
||||
|
||||
enum rx_state {
|
||||
RX_STATE_IDLE,
|
||||
@ -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,7 +296,8 @@ 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()) {
|
||||
} 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);
|
||||
@ -298,6 +311,11 @@ static void usb_rx_process (void) {
|
||||
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 {
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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',
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
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,9 +300,10 @@ 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());
|
||||
}
|
||||
}
|
||||
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)),
|
||||
|
@ -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() {
|
||||
|
@ -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),
|
||||
|
Loading…
Reference in New Issue
Block a user