Skip to content

Commit

Permalink
[SC64][SW] USB debug improvements (#38)
Browse files Browse the repository at this point in the history
 - 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
  • Loading branch information
Polprzewodnikowy authored Apr 23, 2023
1 parent 248feb9 commit 0dbec80
Show file tree
Hide file tree
Showing 6 changed files with 141 additions and 36 deletions.
1 change: 1 addition & 0 deletions sw/controller/src/timer.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@


typedef enum {
TIMER_ID_USB,
TIMER_ID_WRITEBACK,
__TIMER_ID_COUNT
} timer_id_t;
Expand Down
72 changes: 50 additions & 22 deletions sw/controller/src/usb.c
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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);
}
}
}

Expand Down Expand Up @@ -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]);
Expand All @@ -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;
Expand Down Expand Up @@ -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;
}
}
}
}
Expand Down
3 changes: 2 additions & 1 deletion sw/controller/src/usb.h
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
53 changes: 51 additions & 2 deletions sw/deployer/src/debug.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ enum DataType {
RawBinary,
Header,
Screenshot,
Heartbeat,
Unknown,
}

Expand All @@ -36,6 +37,7 @@ impl From<u8> for DataType {
0x02 => Self::RawBinary,
0x03 => Self::Header,
0x04 => Self::Screenshot,
0x05 => Self::Heartbeat,
_ => Self::Unknown,
}
}
Expand All @@ -48,6 +50,7 @@ impl From<DataType> for u8 {
DataType::RawBinary => 0x02,
DataType::Header => 0x03,
DataType::Screenshot => 0x04,
DataType::Heartbeat => 0x05,
DataType::Unknown => 0xFF,
}
}
Expand Down Expand Up @@ -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());
Expand All @@ -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) {
Expand Down Expand Up @@ -195,6 +219,7 @@ impl Handler {
}
}
}
data.append(&mut b"\0".to_vec());
sc64::DebugPacket {
datatype: DataType::Text.into(),
data,
Expand All @@ -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}"),
}
}
Expand Down Expand Up @@ -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);
}
Expand All @@ -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}"),
}
Expand Down Expand Up @@ -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)),
Expand Down
46 changes: 35 additions & 11 deletions sw/deployer/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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>,

Expand Down Expand Up @@ -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()?;
Expand Down Expand Up @@ -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,
Expand All @@ -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()? {
Expand Down Expand Up @@ -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)?;
}
}

Expand Down Expand Up @@ -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() {
Expand Down
Loading

0 comments on commit 0dbec80

Please sign in to comment.