heartbeat + write timeout + 64dd stuff

This commit is contained in:
Mateusz Faderewski 2023-04-22 12:22:39 +02:00
parent 248feb94e8
commit 0c3213073d
6 changed files with 141 additions and 36 deletions

View File

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

View File

@ -5,6 +5,7 @@
#include "flash.h" #include "flash.h"
#include "fpga.h" #include "fpga.h"
#include "rtc.h" #include "rtc.h"
#include "timer.h"
#include "update.h" #include "update.h"
#include "usb.h" #include "usb.h"
#include "version.h" #include "version.h"
@ -19,6 +20,8 @@
#define RX_FLUSH_ADDRESS (0x07F00000UL) #define RX_FLUSH_ADDRESS (0x07F00000UL)
#define RX_FLUSH_LENGTH (1 * 1024 * 1024) #define RX_FLUSH_LENGTH (1 * 1024 * 1024)
#define DEBUG_WRITE_TIMEOUT_TICKS (100)
enum rx_state { enum rx_state {
RX_STATE_IDLE, RX_STATE_IDLE,
@ -49,6 +52,9 @@ struct process {
uint32_t tx_token; uint32_t tx_token;
bool tx_dma_running; bool tx_dma_running;
bool flush_response;
bool flush_packet;
bool response_pending; bool response_pending;
bool response_error; bool response_error;
usb_tx_info_t response_info; usb_tx_info_t response_info;
@ -161,11 +167,16 @@ static void usb_rx_process (void) {
p.rx_state = RX_STATE_ARGS; p.rx_state = RX_STATE_ARGS;
p.rx_counter = 0; p.rx_counter = 0;
p.rx_dma_running = false; p.rx_dma_running = false;
p.flush_response = false;
p.flush_packet = false;
p.response_error = false; p.response_error = false;
p.response_info.cmd = p.rx_cmd; p.response_info.cmd = p.rx_cmd;
p.response_info.data_length = 0; p.response_info.data_length = 0;
p.response_info.dma_length = 0; p.response_info.dma_length = 0;
p.response_info.done_callback = NULL; 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 (!p.rx_dma_running) {
if (usb_validate_address_length(p.rx_args[0], p.rx_args[1], true)) { if (usb_validate_address_length(p.rx_args[0], p.rx_args[1], true)) {
p.rx_state = RX_STATE_FLUSH; p.rx_state = RX_STATE_FLUSH;
p.flush_response = true;
} else { } else {
fpga_reg_set(REG_USB_DMA_ADDRESS, p.rx_args[0]); 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_LENGTH, p.rx_args[1]);
@ -284,7 +296,8 @@ static void usb_rx_process (void) {
case 'U': case 'U':
if (p.rx_args[1] == 0) { if (p.rx_args[1] == 0) {
p.rx_state = RX_STATE_IDLE; 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; uint32_t length = (p.read_length > p.rx_args[1]) ? p.rx_args[1] : p.read_length;
if (!p.rx_dma_running) { if (!p.rx_dma_running) {
fpga_reg_set(REG_USB_DMA_ADDRESS, p.read_address); 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_length -= length;
p.read_address += length; p.read_address += length;
p.read_ready = true; 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; break;
@ -390,9 +408,19 @@ static void usb_rx_process (void) {
fpga_reg_set(REG_USB_DMA_SCR, DMA_SCR_DIRECTION | DMA_SCR_START); fpga_reg_set(REG_USB_DMA_SCR, DMA_SCR_DIRECTION | DMA_SCR_START);
p.rx_args[1] -= length; p.rx_args[1] -= length;
} else { } else {
if (p.flush_response) {
p.rx_state = RX_STATE_IDLE; p.rx_state = RX_STATE_IDLE;
p.response_pending = true; p.response_pending = true;
p.response_error = 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 { typedef enum packet_cmd {
PACKET_CMD_BUTTON_TRIGGER = 'B', PACKET_CMD_BUTTON_TRIGGER = 'B',
PACKET_CMD_DD_REQUEST = 'D', PACKET_CMD_DATA_FLUSHED = 'G',
PACKET_CMD_DEBUG_OUTPUT = 'U', PACKET_CMD_DEBUG_OUTPUT = 'U',
PACKET_CMD_DD_REQUEST = 'D',
PACKET_CMD_ISV_OUTPUT = 'I', PACKET_CMD_ISV_OUTPUT = 'I',
PACKET_CMD_SAVE_WRITEBACK = 'S', PACKET_CMD_SAVE_WRITEBACK = 'S',
PACKET_CMD_UPDATE_STATUS = 'F', PACKET_CMD_UPDATE_STATUS = 'F',

View File

@ -26,6 +26,7 @@ enum DataType {
RawBinary, RawBinary,
Header, Header,
Screenshot, Screenshot,
Heartbeat,
Unknown, Unknown,
} }
@ -36,6 +37,7 @@ impl From<u8> for DataType {
0x02 => Self::RawBinary, 0x02 => Self::RawBinary,
0x03 => Self::Header, 0x03 => Self::Header,
0x04 => Self::Screenshot, 0x04 => Self::Screenshot,
0x05 => Self::Heartbeat,
_ => Self::Unknown, _ => Self::Unknown,
} }
} }
@ -48,6 +50,7 @@ impl From<DataType> for u8 {
DataType::RawBinary => 0x02, DataType::RawBinary => 0x02,
DataType::Header => 0x03, DataType::Header => 0x03,
DataType::Screenshot => 0x04, DataType::Screenshot => 0x04,
DataType::Heartbeat => 0x05,
DataType::Unknown => 0xFF, 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 { macro_rules! success {
($($a: tt)*) => { ($($a: tt)*) => {
println!("{}", format!($($a)*).bright_blue()); println!("{}", format!($($a)*).bright_blue());
@ -134,6 +157,7 @@ macro_rules! stop {
} }
const MAX_PACKET_LENGTH: usize = 8 * 1024 * 1024; const MAX_PACKET_LENGTH: usize = 8 * 1024 * 1024;
const SUPPORTED_USB_PROTOCOL_VERSION: u16 = 2;
impl Handler { impl Handler {
pub fn set_text_encoding(&mut self, encoding: Encoding) { pub fn set_text_encoding(&mut self, encoding: Encoding) {
@ -195,6 +219,7 @@ impl Handler {
} }
} }
} }
data.append(&mut b"\0".to_vec());
sc64::DebugPacket { sc64::DebugPacket {
datatype: DataType::Text.into(), datatype: DataType::Text.into(),
data, data,
@ -220,6 +245,7 @@ impl Handler {
DataType::RawBinary => self.handle_datatype_raw_binary(&data), DataType::RawBinary => self.handle_datatype_raw_binary(&data),
DataType::Header => self.handle_datatype_header(&data), DataType::Header => self.handle_datatype_header(&data),
DataType::Screenshot => self.handle_datatype_screenshot(&data), DataType::Screenshot => self.handle_datatype_screenshot(&data),
DataType::Heartbeat => self.handle_datatype_heartbeat(&data),
_ => error!("Received unknown debug packet datatype: 0x{datatype:02X}"), _ => error!("Received unknown debug packet datatype: 0x{datatype:02X}"),
} }
} }
@ -252,13 +278,18 @@ impl Handler {
Ok(mut file) => { Ok(mut file) => {
if let Err(error) = file.write_all(&save_writeback.data) { if let Err(error) = file.write_all(&save_writeback.data) {
error!("Couldn't write save [{filename}]: {error}"); 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}"), 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]) { fn handle_datatype_text(&self, data: &[u8]) {
self.print_text(data); self.print_text(data);
} }
@ -269,9 +300,10 @@ impl Handler {
Ok(mut file) => { Ok(mut file) => {
if let Err(error) = file.write_all(data) { if let Err(error) = file.write_all(data) {
error!("Couldn't write raw binary [{filename}]: {error}"); 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}"), 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}]"); 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]) { fn print_text(&self, data: &[u8]) {
match self.encoding { match self.encoding {
Encoding::UTF8 => print!("{}", String::from_utf8_lossy(&data)), Encoding::UTF8 => print!("{}", String::from_utf8_lossy(&data)),

View File

@ -48,7 +48,7 @@ enum Commands {
command: DownloadCommands, 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), _64DD(_64DDArgs),
/// Enter debug mode /// Enter debug mode
@ -127,7 +127,7 @@ struct _64DDArgs {
#[arg(short, long)] #[arg(short, long)]
rom: Option<PathBuf>, rom: Option<PathBuf>,
/// Path to the save file /// Path to the save file (also used by save writeback mechanism)
#[arg(short, long, requires = "rom")] #[arg(short, long, requires = "rom")]
save: Option<PathBuf>, 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 sc64 = init_sc64(connection, true)?;
let mut debug_handler = debug::new();
println!( println!(
"{} {} {}", "{}\n{}\n{}",
"[WARNING]:".bold().bright_yellow(), "========== [WARNING] ==========".bold().bright_yellow(),
"Do not use this mode when real 64DD accessory is connected to the N64.".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() "Doing so might permanently damage either N64, 64DD or SC64".bright_yellow()
); );
sc64.reset_state()?; sc64.reset_state()?;
@ -478,10 +480,7 @@ fn handle_64dd_command(connection: Connection, args: &_64DDArgs) -> Result<(), s
.iter() .iter()
.map(|path| path.file_name().unwrap().to_string_lossy().to_string()) .map(|path| path.file_name().unwrap().to_string_lossy().to_string())
.collect(); .collect();
let mut disks = disk::open_multiple(&disk_paths)?; 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() { let drive_type = match disks[0].get_format() {
disk::Format::Retail => sc64::DdDriveType::Retail, disk::Format::Retail => sc64::DdDriveType::Retail,
@ -495,11 +494,22 @@ fn handle_64dd_command(connection: Connection, args: &_64DDArgs) -> Result<(), s
println!( println!(
"{}: {}", "{}: {}",
"[64DD]".bold(), "[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() .bold()
.bright_green() .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(); let exit = setup_exit_flag();
while !exit.load(Ordering::Relaxed) { while !exit.load(Ordering::Relaxed) {
if let Some(data_packet) = sc64.receive_data_packet()? { 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_index = 0;
} }
selected_disk = Some(&mut disks[selected_disk_index]); selected_disk = Some(&mut disks[selected_disk_index]);
sc64.set_64dd_disk_state(sc64::DdDiskState::Inserted)?;
println!( println!(
"{}: Disk inserted [{}]", "{}: Disk inserted [{}]",
"[64DD]".bold(), "[64DD]".bold(),
disk_names[selected_disk_index].bright_green() 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) => { sc64::DataPacket::SaveWriteback(save_writeback) => {
debug_handler.handle_save_writeback(save_writeback, &args.save); 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() { } 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 { pub enum DataPacket {
Button, Button,
DataFlushed,
DebugData(DebugPacket), DebugData(DebugPacket),
DiskRequest(DiskPacket), DiskRequest(DiskPacket),
IsViewer64(Vec<u8>), IsViewer64(Vec<u8>),
@ -592,6 +593,7 @@ impl TryFrom<Packet> for DataPacket {
fn try_from(value: Packet) -> Result<Self, Self::Error> { fn try_from(value: Packet) -> Result<Self, Self::Error> {
Ok(match value.id { Ok(match value.id {
b'B' => Self::Button, b'B' => Self::Button,
b'G' => Self::DataFlushed,
b'U' => Self::DebugData(value.data.try_into()?), b'U' => Self::DebugData(value.data.try_into()?),
b'D' => Self::DiskRequest(value.data.try_into()?), b'D' => Self::DiskRequest(value.data.try_into()?),
b'I' => Self::IsViewer64(value.data), b'I' => Self::IsViewer64(value.data),