diff --git a/docs/02_usb_commands.md b/docs/02_usb_commands.md index 0e26692..56d6cf3 100644 --- a/docs/02_usb_commands.md +++ b/docs/02_usb_commands.md @@ -20,6 +20,7 @@ | `M` | **MEMORY_WRITE** | address | length | data | --- | Write data to specified memory address | | `U` | **USB_WRITE** | type | length | data | N/A | Send data to be received by app running on N64 (no response!) | | `D` | **DD_SET_BLOCK_READY** | success | --- | --- | --- | Notify flashcart about 64DD block readiness | +| `W` | **WRITEBACK_ENABLE** | --- | --- | --- | --- | Enable save writeback through USB packet | | `p` | **FLASH_WAIT_BUSY** | wait | --- | --- | erase_block_size | Wait until flash ready / Get flash block erase size | | `P` | **FLASH_ERASE_BLOCK** | address | --- | --- | --- | Start flash block erase | | `f` | **FIRMWARE_BACKUP** | address | --- | --- | status/length | Backup firmware to specified memory address | diff --git a/sw/controller/src/cfg.c b/sw/controller/src/cfg.c index dcc1939..9806694 100644 --- a/sw/controller/src/cfg.c +++ b/sw/controller/src/cfg.c @@ -628,7 +628,7 @@ void cfg_process (void) { return; } writeback_load_sector_table(args[0]); - writeback_enable(); + writeback_enable(WRITEBACK_SD); break; case 'K': diff --git a/sw/controller/src/usb.c b/sw/controller/src/usb.c index 52d531c..e301056 100644 --- a/sw/controller/src/usb.c +++ b/sw/controller/src/usb.c @@ -8,6 +8,7 @@ #include "update.h" #include "usb.h" #include "version.h" +#include "writeback.h" #define BOOTLOADER_ADDRESS (0x04E00000UL) @@ -307,6 +308,12 @@ static void usb_rx_process (void) { 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(); diff --git a/sw/controller/src/usb.h b/sw/controller/src/usb.h index 2e5273b..0819a46 100644 --- a/sw/controller/src/usb.h +++ b/sw/controller/src/usb.h @@ -11,6 +11,7 @@ typedef enum packet_cmd { PACKET_CMD_DD_REQUEST = 'D', PACKET_CMD_DEBUG_OUTPUT = 'U', PACKET_CMD_ISV_OUTPUT = 'I', + PACKET_CMD_SAVE_WRITEBACK = 'S', PACKET_CMD_UPDATE_STATUS = 'F', } usb_packet_cmd_e; diff --git a/sw/controller/src/writeback.c b/sw/controller/src/writeback.c index 0fd9485..86c91c4 100644 --- a/sw/controller/src/writeback.c +++ b/sw/controller/src/writeback.c @@ -2,23 +2,25 @@ #include "fpga.h" #include "sd.h" #include "timer.h" +#include "usb.h" #include "writeback.h" #define SAVE_MAX_SECTOR_COUNT (256) #define EEPROM_ADDRESS (0x05002000) #define SRAM_FLASHRAM_ADDRESS (0x03FE0000) -#define EEPROM_4K_SECTOR_COUNT (1) -#define EEPROM_16K_SECTOR_COUNT (4) -#define SRAM_SECTOR_COUNT (64) -#define FLASHRAM_SECTOR_COUNT (256) -#define SRAM_BANKED_SECTOR_COUNT (192) +#define EEPROM_4K_LENGTH (512) +#define EEPROM_16K_LENGTH (2048) +#define SRAM_LENGTH (32 * 1024) +#define FLASHRAM_LENGTH (128 * 1024) +#define SRAM_BANKED_LENGTH (3 * 32 * 1024) #define WRITEBACK_DELAY_TICKS (100) struct process { bool enabled; bool pending; + writeback_mode_t mode; uint16_t last_save_count; uint32_t sectors[SAVE_MAX_SECTOR_COUNT]; }; @@ -27,41 +29,75 @@ struct process { static struct process p; -static void writeback_save_to_sd (void) { - uint32_t address; - uint32_t count; +static save_type_t writeback_get_address_length (uint32_t *address, uint32_t *length) { + save_type_t save = cfg_get_save_type(); - switch (cfg_get_save_type()) { + switch (save) { case SAVE_TYPE_EEPROM_4K: - address = EEPROM_ADDRESS; - count = EEPROM_4K_SECTOR_COUNT; + *address = EEPROM_ADDRESS; + *length = EEPROM_4K_LENGTH; break; case SAVE_TYPE_EEPROM_16K: - address = EEPROM_ADDRESS; - count = EEPROM_16K_SECTOR_COUNT; + *address = EEPROM_ADDRESS; + *length = EEPROM_16K_LENGTH; break; case SAVE_TYPE_SRAM: - address = SRAM_FLASHRAM_ADDRESS; - count = SRAM_SECTOR_COUNT; + *address = SRAM_FLASHRAM_ADDRESS; + *length = SRAM_LENGTH; break; case SAVE_TYPE_FLASHRAM: - address = SRAM_FLASHRAM_ADDRESS; - count = FLASHRAM_SECTOR_COUNT; + *address = SRAM_FLASHRAM_ADDRESS; + *length = FLASHRAM_LENGTH; break; case SAVE_TYPE_SRAM_BANKED: - address = SRAM_FLASHRAM_ADDRESS; - count = SRAM_BANKED_SECTOR_COUNT; + *address = SRAM_FLASHRAM_ADDRESS; + *length = SRAM_BANKED_LENGTH; break; default: - writeback_disable(); - return; + *address = 0; + *length = 0; + break; } - if(sd_optimize_sectors(address, p.sectors, count, sd_write_sectors)) { + return save; +} + +static void writeback_save_to_sd (void) { + save_type_t save; + uint32_t address; + uint32_t length; + + save = writeback_get_address_length(&address, &length); + if (save == SAVE_TYPE_NONE) { + writeback_disable(); + return; + } + + if(sd_optimize_sectors(address, p.sectors, length / SD_SECTOR_SIZE, sd_write_sectors)) { writeback_disable(); } } +static bool writeback_save_to_usb (void) { + save_type_t save; + uint32_t address; + uint32_t length; + + save = writeback_get_address_length(&address, &length); + if (save == SAVE_TYPE_NONE) { + writeback_disable(); + return true; + } + + usb_tx_info_t packet_info; + usb_create_packet(&packet_info, PACKET_CMD_SAVE_WRITEBACK); + packet_info.data_length = 4; + packet_info.data[0] = save; + packet_info.dma_length = length; + packet_info.dma_address = address; + return usb_enqueue_packet(&packet_info); +} + void writeback_load_sector_table (uint32_t address) { fpga_mem_read(address, sizeof(p.sectors), (uint8_t *) (p.sectors)); @@ -70,9 +106,10 @@ void writeback_load_sector_table (uint32_t address) { } } -void writeback_enable (void) { +void writeback_enable (writeback_mode_t mode) { p.enabled = true; p.pending = false; + p.mode = mode; p.last_save_count = fpga_reg_get(REG_SAVE_COUNT); } @@ -85,13 +122,14 @@ void writeback_disable (void) { void writeback_init (void) { p.enabled = false; p.pending = false; + p.mode = WRITEBACK_SD; for (int i = 0; i < SAVE_MAX_SECTOR_COUNT; i++) { p.sectors[i] = 0; } } void writeback_process (void) { - if (p.enabled && !sd_card_is_inserted()) { + if (p.enabled && (p.mode == WRITEBACK_SD) && !sd_card_is_inserted()) { writeback_disable(); } @@ -105,7 +143,19 @@ void writeback_process (void) { } if (p.pending && (timer_get(TIMER_ID_WRITEBACK) == 0)) { - p.pending = false; - writeback_save_to_sd(); + switch (p.mode) { + case WRITEBACK_SD: + writeback_save_to_sd(); + p.pending = false; + break; + case WRITEBACK_USB: + if (writeback_save_to_usb()) { + p.pending = false; + } + break; + default: + writeback_disable(); + break; + } } } diff --git a/sw/controller/src/writeback.h b/sw/controller/src/writeback.h index 41e038d..f1bff21 100644 --- a/sw/controller/src/writeback.h +++ b/sw/controller/src/writeback.h @@ -9,8 +9,14 @@ #define WRITEBACK_SECTOR_TABLE_SIZE (1024) +typedef enum { + WRITEBACK_SD, + WRITEBACK_USB, +} writeback_mode_t; + + void writeback_load_sector_table (uint32_t address); -void writeback_enable (void); +void writeback_enable (writeback_mode_t mode); void writeback_disable (void); void writeback_init (void); void writeback_process (void); diff --git a/sw/deployer/src/debug.rs b/sw/deployer/src/debug.rs index cfa69d1..540a9bf 100644 --- a/sw/deployer/src/debug.rs +++ b/sw/deployer/src/debug.rs @@ -115,6 +115,8 @@ impl TryFrom> for ScreenshotMetadata { } } +const MAX_FILE_LENGTH: u64 = 8 * 1024 * 1024; + impl Handler { pub fn process_user_input(&self) -> Option { if let Ok(line) = self.line_rx.try_recv() { @@ -151,6 +153,10 @@ impl Handler { return None; } }; + if length > MAX_FILE_LENGTH { + println!("File too big ({length} bytes), exceeding max size of {MAX_FILE_LENGTH} bytes"); + return None; + } let mut data = vec![0u8; length as usize]; if let Err(error) = file.read_exact(&mut data) { println!("Couldn't read file contents: {error}"); @@ -202,9 +208,9 @@ impl Handler { match File::create(filename) { Ok(mut file) => { if let Err(error) = file.write_all(data) { - println!("Error during raw binary save: {error}"); + println!("Error during raw binary write: {error}"); } - println!("Wrote {} bytes to [{}]", data.len(), filename); + println!("Wrote [{}] bytes to [{}]", data.len(), filename); } Err(error) => { println!("Error during raw binary file creation: {error}"); @@ -267,6 +273,37 @@ impl Handler { self.gdb_tx.send(data.to_vec()).ok(); } + pub fn handle_save_writeback( + &self, + save_writeback: sc64::SaveWriteback, + path: &Option, + ) { + let filename = &if let Some(path) = path { + path.to_string_lossy().to_string() + } else { + self.generate_filename( + "save", + match save_writeback.save { + sc64::SaveType::Eeprom4k | sc64::SaveType::Eeprom16k => "eep", + sc64::SaveType::Sram | sc64::SaveType::SramBanked => "srm", + sc64::SaveType::Flashram => "fla", + _ => "sav", + }, + ) + }; + match File::create(filename) { + Ok(mut file) => { + if let Err(error) = file.write_all(&save_writeback.data) { + println!("Error during save write: {error}"); + } + println!("Wrote [{}] save to [{}]", save_writeback.save, filename); + } + Err(error) => { + println!("Error during save writeback file creation: {error}"); + } + } + } + fn generate_filename(&self, prefix: &str, extension: &str) -> String { format!( "{prefix}-{}.{extension}", @@ -314,7 +351,7 @@ fn stdin_thread(line_tx: Sender) { loop { let mut line = String::new(); if stdin().read_line(&mut line).is_ok() { - if line_tx.send(line).is_err() { + if line_tx.send(line.trim_end().to_string()).is_err() { return; } } diff --git a/sw/deployer/src/main.rs b/sw/deployer/src/main.rs index 88a38bf..a624455 100644 --- a/sw/deployer/src/main.rs +++ b/sw/deployer/src/main.rs @@ -146,6 +146,10 @@ struct _64DDArgs { #[derive(Args)] struct DebugArgs { + /// Path to the save file to use by the save writeback mechanism + #[arg(short, long)] + save: Option, + /// Enable IS-Viewer64 and set listening address at ROM offset (in most cases it's fixed at 0x03FF0000) #[arg(long, value_name = "offset", value_parser = |s: &str| maybe_hex_range::(s, 0x00000004, 0x03FF0000))] isv: Option, @@ -581,6 +585,7 @@ fn handle_debug_command(connection: Connection, args: &DebugArgs) -> Result<(), .bright_blue() ); } + sc64.set_save_writeback(true)?; println!("{}: Started", "[Debug]".bold()); @@ -594,6 +599,9 @@ fn handle_debug_command(connection: Connection, args: &DebugArgs) -> Result<(), sc64::DataPacket::Debug(debug_packet) => { debug_handler.handle_debug_packet(debug_packet) } + sc64::DataPacket::SaveWriteback(save_writeback) => { + debug_handler.handle_save_writeback(save_writeback, &args.save); + } _ => {} } } else if let Some(gdb_packet) = debug_handler.receive_gdb_packet() { @@ -603,6 +611,7 @@ fn handle_debug_command(connection: Connection, args: &DebugArgs) -> Result<(), } } + sc64.set_save_writeback(false)?; if args.isv.is_some() { sc64.configure_is_viewer_64(None)?; println!("{}: Stopped listening", "[IS-Viewer 64]".bold()); diff --git a/sw/deployer/src/sc64/mod.rs b/sw/deployer/src/sc64/mod.rs index 40aa4f7..4353240 100644 --- a/sw/deployer/src/sc64/mod.rs +++ b/sw/deployer/src/sc64/mod.rs @@ -12,8 +12,8 @@ pub use self::{ server::ServerEvent, types::{ BootMode, ButtonMode, ButtonState, CicSeed, DataPacket, DdDiskState, DdDriveType, DdMode, - DebugPacket, DiskPacket, DiskPacketKind, FpgaDebugData, McuStackUsage, SaveType, Switch, - TvType, + DebugPacket, DiskPacket, DiskPacketKind, FpgaDebugData, McuStackUsage, SaveType, + SaveWriteback, Switch, TvType, }, }; @@ -272,6 +272,15 @@ impl SC64 { Ok(()) } + fn command_writeback_enable(&mut self) -> Result<(), Error> { + self.link.execute_command(&Command { + id: b'W', + args: [0, 0], + data: &[], + })?; + Ok(()) + } + fn command_flash_wait_busy(&mut self, wait: bool) -> Result { let data = self.link.execute_command(&Command { id: b'p', @@ -582,6 +591,16 @@ impl SC64 { Ok(()) } + pub fn set_save_writeback(&mut self, enabled: bool) -> Result<(), Error> { + if enabled { + self.command_writeback_enable()?; + } else { + let save_type = get_config!(self, SaveType)?; + self.set_save_type(save_type)?; + } + Ok(()) + } + pub fn receive_data_packet(&mut self) -> Result, Error> { if let Some(packet) = self.link.receive_packet()? { return Ok(Some(packet.try_into()?)); diff --git a/sw/deployer/src/sc64/types.rs b/sw/deployer/src/sc64/types.rs index 080a92d..22fd852 100644 --- a/sw/deployer/src/sc64/types.rs +++ b/sw/deployer/src/sc64/types.rs @@ -580,6 +580,7 @@ pub enum DataPacket { Debug(DebugPacket), Disk(DiskPacket), IsViewer64(String), + SaveWriteback(SaveWriteback), UpdateStatus(UpdateStatus), } @@ -591,6 +592,7 @@ impl TryFrom for DataPacket { b'U' => Self::Debug(value.data.try_into()?), b'D' => Self::Disk(value.data.try_into()?), b'I' => Self::IsViewer64(EUC_JP.decode(&value.data).0.into()), + b'S' => Self::SaveWriteback(value.data.try_into()?), b'F' => { if value.data.len() != 4 { return Err(Error::new( @@ -682,6 +684,25 @@ impl DiskBlock { } } +pub struct SaveWriteback { + pub save: SaveType, + pub data: Vec, +} + +impl TryFrom> for SaveWriteback { + type Error = Error; + fn try_from(value: Vec) -> Result { + if value.len() < 4 { + return Err(Error::new( + "Couldn't extract save info from save writeback packet", + )); + } + let save: SaveType = u32::from_be_bytes(value[0..4].try_into().unwrap()).try_into()?; + let data = value[4..].to_vec(); + Ok(SaveWriteback { save, data }) + } +} + pub enum FirmwareStatus { Ok, ErrToken, @@ -782,7 +803,10 @@ impl TryFrom> for FpgaDebugData { impl Display for FpgaDebugData { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.write_fmt(format_args!("Last PI address: 0x{:08X}", self.last_pi_address))?; + f.write_fmt(format_args!( + "Last PI address: 0x{:08X}", + self.last_pi_address + ))?; if self.read_fifo_wait { f.write_str(" RW")?; }