bootloader protection

This commit is contained in:
Mateusz Faderewski 2023-03-06 21:08:18 +01:00
parent 33c6e2bbdc
commit aba493dfc9
10 changed files with 201 additions and 95 deletions

View File

@ -8,10 +8,10 @@
bool flash_program (uint32_t src, uint32_t dst, uint32_t length) { bool flash_program (uint32_t src, uint32_t dst, uint32_t length) {
if (((src + length) >= FLASH_ADDRESS) && (src < (FLASH_ADDRESS + FLASH_SIZE))) { if (((src + length) > FLASH_ADDRESS) && (src < (FLASH_ADDRESS + FLASH_SIZE))) {
return true; return true;
} }
if ((dst < FLASH_ADDRESS) || ((dst + length) >= (FLASH_ADDRESS + FLASH_SIZE))) { if ((dst < FLASH_ADDRESS) || ((dst + length) > (FLASH_ADDRESS + FLASH_SIZE))) {
return true; return true;
} }
while (length > 0) { while (length > 0) {
@ -29,15 +29,18 @@ void flash_wait_busy (void) {
fpga_mem_read(FLASH_ADDRESS, 2, dummy); fpga_mem_read(FLASH_ADDRESS, 2, dummy);
} }
bool flash_erase_block (uint32_t offset) { bool flash_erase_block (uint32_t address) {
if ((offset % FLASH_ERASE_BLOCK_SIZE) != 0) { if ((address % FLASH_ERASE_BLOCK_SIZE) != 0) {
return true; return true;
} }
offset &= (FLASH_SIZE - 1); if ((address < FLASH_ADDRESS) || (address >= (FLASH_ADDRESS + FLASH_SIZE))) {
return true;
}
address &= (FLASH_SIZE - 1);
for (int i = 0; i < (FLASH_ERASE_BLOCK_SIZE / ERASE_BLOCK_SIZE); i++) { for (int i = 0; i < (FLASH_ERASE_BLOCK_SIZE / ERASE_BLOCK_SIZE); i++) {
fpga_reg_set(REG_FLASH_SCR, offset); fpga_reg_set(REG_FLASH_SCR, address);
while (fpga_reg_get(REG_FLASH_SCR) & FLASH_SCR_BUSY); while (fpga_reg_get(REG_FLASH_SCR) & FLASH_SCR_BUSY);
offset += ERASE_BLOCK_SIZE; address += ERASE_BLOCK_SIZE;
} }
flash_wait_busy(); flash_wait_busy();
return false; return false;

View File

@ -11,7 +11,7 @@
bool flash_program (uint32_t src, uint32_t dst, uint32_t length); bool flash_program (uint32_t src, uint32_t dst, uint32_t length);
void flash_wait_busy (void); void flash_wait_busy (void);
bool flash_erase_block (uint32_t offset); bool flash_erase_block (uint32_t address);
#endif #endif

View File

@ -6,6 +6,9 @@
#include "vendor.h" #include "vendor.h"
#define SDRAM_ADDRESS (0x00000000UL)
#define SDRAM_LENGTH (64 * 1024 * 1024)
#define UPDATE_MAGIC_START (0x54535055UL) #define UPDATE_MAGIC_START (0x54535055UL)
#define BOOTLOADER_ADDRESS (0x04E00000UL) #define BOOTLOADER_ADDRESS (0x04E00000UL)
#define BOOTLOADER_LENGTH (0x001E0000UL) #define BOOTLOADER_LENGTH (0x001E0000UL)
@ -191,6 +194,10 @@ update_error_t update_backup (uint32_t address, uint32_t *length) {
uint32_t fpga_length; uint32_t fpga_length;
uint32_t bootloader_length; uint32_t bootloader_length;
if (address >= (SDRAM_ADDRESS + SDRAM_LENGTH)) {
return UPDATE_ERROR_ADDRESS;
}
*length = update_write_token(&address); *length = update_write_token(&address);
*length += update_prepare_chunk(&address, CHUNK_ID_MCU_DATA); *length += update_prepare_chunk(&address, CHUNK_ID_MCU_DATA);
@ -214,6 +221,10 @@ update_error_t update_backup (uint32_t address, uint32_t *length) {
} }
*length += update_finalize_chunk(&address, bootloader_length); *length += update_finalize_chunk(&address, bootloader_length);
if ((address + *length) > (SDRAM_ADDRESS + SDRAM_LENGTH)) {
return UPDATE_ERROR_ADDRESS;
}
return UPDATE_OK; return UPDATE_OK;
} }
@ -223,6 +234,13 @@ update_error_t update_prepare (uint32_t address, uint32_t length) {
uint32_t data_address; uint32_t data_address;
uint32_t data_length; uint32_t data_length;
if ((address >= (SDRAM_ADDRESS + SDRAM_LENGTH)) || (length > SDRAM_LENGTH)) {
return UPDATE_ERROR_ADDRESS;
}
if (end_address > (SDRAM_ADDRESS + SDRAM_LENGTH)) {
return UPDATE_ERROR_ADDRESS;
}
if (update_check_token(&address)) { if (update_check_token(&address)) {
return UPDATE_ERROR_TOKEN; return UPDATE_ERROR_TOKEN;
} }

View File

@ -13,6 +13,7 @@ typedef enum {
UPDATE_ERROR_SIZE, UPDATE_ERROR_SIZE,
UPDATE_ERROR_UNKNOWN_CHUNK, UPDATE_ERROR_UNKNOWN_CHUNK,
UPDATE_ERROR_READ, UPDATE_ERROR_READ,
UPDATE_ERROR_ADDRESS,
} update_error_t; } update_error_t;

View File

@ -10,10 +10,20 @@
#include "version.h" #include "version.h"
#define BOOTLOADER_ADDRESS (0x04E00000UL)
#define BOOTLOADER_LENGTH (1920 * 1024)
#define MEMORY_LENGTH (0x05002980UL)
#define RX_FLUSH_ADDRESS (0x07F00000UL)
#define RX_FLUSH_LENGTH (1 * 1024 * 1024)
enum rx_state { enum rx_state {
RX_STATE_IDLE, RX_STATE_IDLE,
RX_STATE_ARGS, RX_STATE_ARGS,
RX_STATE_DATA, RX_STATE_DATA,
RX_STATE_FLUSH,
}; };
enum tx_state { enum tx_state {
@ -129,6 +139,21 @@ static bool usb_rx_cmd (uint8_t *cmd) {
return false; return false;
} }
static bool usb_validate_address_length (uint32_t address, uint32_t length, bool exclude_bootloader) {
if ((address >= MEMORY_LENGTH) || (length > MEMORY_LENGTH)) {
return true;
}
if ((address + length) > MEMORY_LENGTH) {
return true;
}
if (exclude_bootloader) {
if (((address + length) > BOOTLOADER_ADDRESS) && (address < (BOOTLOADER_ADDRESS + BOOTLOADER_LENGTH))) {
return true;
}
}
return false;
}
static void usb_rx_process (void) { static void usb_rx_process (void) {
if (p.rx_state == RX_STATE_IDLE) { if (p.rx_state == RX_STATE_IDLE) {
if (!p.response_pending && usb_rx_cmd(&p.rx_cmd)) { if (!p.response_pending && usb_rx_cmd(&p.rx_cmd)) {
@ -229,17 +254,25 @@ static void usb_rx_process (void) {
case 'm': case 'm':
p.rx_state = RX_STATE_IDLE; p.rx_state = RX_STATE_IDLE;
p.response_pending = true; p.response_pending = true;
p.response_info.dma_address = p.rx_args[0]; if (usb_validate_address_length(p.rx_args[0], p.rx_args[1], false)) {
p.response_info.dma_length = p.rx_args[1]; p.response_error = true;
} else {
p.response_info.dma_address = p.rx_args[0];
p.response_info.dma_length = p.rx_args[1];
}
break; break;
case 'M': case 'M':
if (usb_dma_ready()) { if (usb_dma_ready()) {
if (!p.rx_dma_running) { if (!p.rx_dma_running) {
fpga_reg_set(REG_USB_DMA_ADDRESS, p.rx_args[0]); if (usb_validate_address_length(p.rx_args[0], p.rx_args[1], true)) {
fpga_reg_set(REG_USB_DMA_LENGTH, p.rx_args[1]); p.rx_state = RX_STATE_FLUSH;
fpga_reg_set(REG_USB_DMA_SCR, DMA_SCR_DIRECTION | DMA_SCR_START); } else {
p.rx_dma_running = true; 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_SCR, DMA_SCR_DIRECTION | DMA_SCR_START);
p.rx_dma_running = true;
}
} else { } else {
p.rx_state = RX_STATE_IDLE; p.rx_state = RX_STATE_IDLE;
p.response_pending = true; p.response_pending = true;
@ -258,6 +291,7 @@ static void usb_rx_process (void) {
p.read_ready = false; p.read_ready = false;
} else { } else {
p.rx_args[1] -= length; p.rx_args[1] -= length;
p.rx_dma_running = false;
p.read_length -= length; p.read_length -= length;
p.read_address += length; p.read_address += length;
p.read_ready = true; p.read_ready = true;
@ -285,7 +319,11 @@ static void usb_rx_process (void) {
break; break;
case 'P': case 'P':
p.response_error = flash_erase_block(p.rx_args[0]); if (usb_validate_address_length(p.rx_args[0], FLASH_ERASE_BLOCK_SIZE, true)) {
p.response_error = true;
} else {
p.response_error = flash_erase_block(p.rx_args[0]);
}
p.rx_state = RX_STATE_IDLE; p.rx_state = RX_STATE_IDLE;
p.response_pending = true; p.response_pending = true;
break; break;
@ -336,10 +374,26 @@ static void usb_rx_process (void) {
p.response_pending = true; p.response_pending = true;
p.response_error = true; p.response_error = true;
p.response_info.data_length = 4; p.response_info.data_length = 4;
p.response_info.data[0] = 0xFF; p.response_info.data[0] = 0xFFFFFFFF;
break; break;
} }
} }
if (p.rx_state == RX_STATE_FLUSH) {
if (usb_dma_ready()) {
if (p.rx_args[1] != 0) {
uint32_t length = (p.rx_args[1] > RX_FLUSH_LENGTH) ? RX_FLUSH_LENGTH : p.rx_args[1];
fpga_reg_set(REG_USB_DMA_ADDRESS, RX_FLUSH_ADDRESS);
fpga_reg_set(REG_USB_DMA_LENGTH, length);
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;
}
}
}
} }
static void usb_tx_process (void) { static void usb_tx_process (void) {

View File

@ -42,8 +42,11 @@ enum Commands {
/// Upload ROM (and save) to the SC64 /// Upload ROM (and save) to the SC64
Upload(UploadArgs), Upload(UploadArgs),
/// Download save and write it to file /// Download specific memory region and write it to file
DownloadSave(DownloadSaveArgs), Download {
#[command(subcommand)]
command: DownloadCommands,
},
/// Upload ROM, 64DD IPL and run disk server /// Upload ROM, 64DD IPL and run disk server
_64DD(_64DDArgs), _64DD(_64DDArgs),
@ -99,9 +102,15 @@ struct UploadArgs {
tv: Option<TvType>, tv: Option<TvType>,
} }
#[derive(Subcommand)]
enum DownloadCommands {
/// Download save and write it to file
Save(DownloadArgs),
}
#[derive(Args)] #[derive(Args)]
struct DownloadSaveArgs { struct DownloadArgs {
/// Path to the save file /// Path to the file
path: PathBuf, path: PathBuf,
} }
@ -269,7 +278,7 @@ fn handle_command(command: &Commands, port: Option<String>, remote: Option<Strin
let result = match command { let result = match command {
Commands::List => handle_list_command(), Commands::List => handle_list_command(),
Commands::Upload(args) => handle_upload_command(connection, args), Commands::Upload(args) => handle_upload_command(connection, args),
Commands::DownloadSave(args) => handle_download_save_command(connection, args), Commands::Download { command } => handle_download_command(connection, command),
Commands::_64DD(args) => handle_64dd_command(connection, args), Commands::_64DD(args) => handle_64dd_command(connection, args),
Commands::Debug(args) => handle_debug_command(connection, args), Commands::Debug(args) => handle_debug_command(connection, args),
Commands::Dump(args) => handle_dump_command(connection, args), Commands::Dump(args) => handle_dump_command(connection, args),
@ -352,17 +361,21 @@ fn handle_upload_command(connection: Connection, args: &UploadArgs) -> Result<()
Ok(()) Ok(())
} }
fn handle_download_save_command( fn handle_download_command(
connection: Connection, connection: Connection,
args: &DownloadSaveArgs, command: &DownloadCommands,
) -> Result<(), sc64::Error> { ) -> Result<(), sc64::Error> {
let mut sc64 = init_sc64(connection, true)?; let mut sc64 = init_sc64(connection, true)?;
let (mut file, name) = create_file(&args.path)?; match command {
DownloadCommands::Save(args) => {
let (mut file, name) = create_file(&args.path)?;
log_wait(format!("Downloading save [{name}]"), || { log_wait(format!("Downloading save [{name}]"), || {
sc64.download_save(&mut file) sc64.download_save(&mut file)
})?; })?;
}
}
Ok(()) Ok(())
} }
@ -391,7 +404,7 @@ fn handle_debug_command(connection: Connection, args: &DebugArgs) -> Result<(),
if args.isv.is_some() { if args.isv.is_some() {
sc64.configure_is_viewer_64(args.isv)?; sc64.configure_is_viewer_64(args.isv)?;
println!( println!(
"IS-Viewer configured and listening at offset [0x{:08X}]", "IS-Viewer configured and listening at ROM offset [0x{:08X}]",
args.isv.unwrap() args.isv.unwrap()
); );
} }
@ -433,16 +446,14 @@ fn handle_dump_command(connection: Connection, args: &DumpArgs) -> Result<(), sc
let (mut dump_file, dump_name) = create_file(&args.path)?; let (mut dump_file, dump_name) = create_file(&args.path)?;
let data = log_wait( log_wait(
format!( format!(
"Dumping from [0x{:08X}] length [0x{:X}] to [{dump_name}]", "Dumping from [0x{:08X}] length [0x{:X}] to [{dump_name}]",
args.address, args.length args.address, args.length
), ),
|| sc64.dump_memory(args.address, args.length), || sc64.dump_memory(&mut dump_file, args.address, args.length),
)?; )?;
dump_file.write(&data)?;
Ok(()) Ok(())
} }
@ -542,7 +553,7 @@ fn handle_firmware_command(
println!("{}", "Firmware metadata:".bold()); println!("{}", "Firmware metadata:".bold());
println!("{}", metadata); println!("{}", metadata);
backup_file.write(&firmware)?; backup_file.write_all(&firmware)?;
Ok(()) Ok(())
} }
@ -603,7 +614,11 @@ fn log_wait<F: FnOnce() -> Result<T, E>, T, E>(message: String, operation: F) ->
print!("{}... ", message); print!("{}... ", message);
stdout().flush().unwrap(); stdout().flush().unwrap();
let result = operation(); let result = operation();
println!("done"); if result.is_ok() {
println!("done");
} else {
println!("error!");
}
result result
} }

View File

@ -18,7 +18,7 @@ pub fn guess_save_type<T: Read + Seek>(
let mut ed64_header = vec![0u8; 4]; let mut ed64_header = vec![0u8; 4];
reader.seek(SeekFrom::Start(0x3C))?; reader.seek(SeekFrom::Start(0x3C))?;
reader.read(&mut ed64_header)?; reader.read_exact(&mut ed64_header)?;
if &ed64_header[0..2] == b"ED" { if &ed64_header[0..2] == b"ED" {
return Ok(( return Ok((
@ -38,7 +38,7 @@ pub fn guess_save_type<T: Read + Seek>(
let mut pi_config = vec![0u8; 4]; let mut pi_config = vec![0u8; 4];
reader.rewind()?; reader.rewind()?;
reader.read(&mut pi_config)?; reader.read_exact(&mut pi_config)?;
let endian_swapper = match &pi_config[0..4] { let endian_swapper = match &pi_config[0..4] {
[0x37, 0x80, 0x40, 0x12] => |b: &mut [u8]| b.chunks_exact_mut(2).for_each(|c| c.swap(0, 1)), [0x37, 0x80, 0x40, 0x12] => |b: &mut [u8]| b.chunks_exact_mut(2).for_each(|c| c.swap(0, 1)),

View File

@ -29,10 +29,10 @@ enum DataType {
} }
pub trait Link { pub trait Link {
fn execute_command(&mut self, command: &mut Command) -> Result<Vec<u8>, Error>; fn execute_command(&mut self, command: &Command) -> Result<Vec<u8>, Error>;
fn execute_command_raw( fn execute_command_raw(
&mut self, &mut self,
command: &mut Command, command: &Command,
timeout: Duration, timeout: Duration,
no_response: bool, no_response: bool,
ignore_error: bool, ignore_error: bool,
@ -79,20 +79,16 @@ impl SerialLink {
Ok(()) Ok(())
} }
fn serial_send_command( fn serial_send_command(&mut self, command: &Command, timeout: Duration) -> Result<(), Error> {
&mut self, let mut header: Vec<u8> = Vec::new();
command: &mut Command, header.append(&mut b"CMD".to_vec());
timeout: Duration, header.append(&mut [command.id].to_vec());
) -> Result<(), Error> { header.append(&mut command.args[0].to_be_bytes().to_vec());
let mut packet: Vec<u8> = Vec::new(); header.append(&mut command.args[1].to_be_bytes().to_vec());
packet.append(&mut b"CMD".to_vec());
packet.append(&mut [command.id].to_vec());
packet.append(&mut command.args[0].to_be_bytes().to_vec());
packet.append(&mut command.args[1].to_be_bytes().to_vec());
packet.append(&mut command.data);
self.serial.set_timeout(timeout)?; self.serial.set_timeout(timeout)?;
self.serial.write_all(&packet)?; self.serial.write_all(&header)?;
self.serial.write_all(&command.data)?;
self.serial.flush()?; self.serial.flush()?;
Ok(()) Ok(())
@ -138,7 +134,7 @@ impl SerialLink {
Ok(None) Ok(None)
} }
fn send_command(&mut self, command: &mut Command) -> Result<(), Error> { fn send_command(&mut self, command: &Command) -> Result<(), Error> {
self.serial_send_command(command, COMMAND_TIMEOUT) self.serial_send_command(command, COMMAND_TIMEOUT)
} }
@ -151,13 +147,13 @@ impl SerialLink {
} }
impl Link for SerialLink { impl Link for SerialLink {
fn execute_command(&mut self, command: &mut Command) -> Result<Vec<u8>, Error> { fn execute_command(&mut self, command: &Command) -> Result<Vec<u8>, Error> {
self.execute_command_raw(command, COMMAND_TIMEOUT, false, false) self.execute_command_raw(command, COMMAND_TIMEOUT, false, false)
} }
fn execute_command_raw( fn execute_command_raw(
&mut self, &mut self,
command: &mut Command, command: &Command,
timeout: Duration, timeout: Duration,
no_response: bool, no_response: bool,
ignore_error: bool, ignore_error: bool,

View File

@ -84,7 +84,6 @@ const SRAM_BANKED_LENGTH: usize = 3 * 32 * 1024;
const FLASHRAM_LENGTH: usize = 128 * 1024; const FLASHRAM_LENGTH: usize = 128 * 1024;
const BOOTLOADER_ADDRESS: u32 = 0x04E0_0000; const BOOTLOADER_ADDRESS: u32 = 0x04E0_0000;
const BOOTLOADER_LENGTH: usize = 1920 * 1024;
const FIRMWARE_ADDRESS: u32 = 0x0010_0000; // Arbitrary offset in SDRAM memory const FIRMWARE_ADDRESS: u32 = 0x0010_0000; // Arbitrary offset in SDRAM memory
const FIRMWARE_COMMAND_TIMEOUT: Duration = Duration::from_secs(30); const FIRMWARE_COMMAND_TIMEOUT: Duration = Duration::from_secs(30);
@ -96,11 +95,11 @@ const ISV_BUFFER_LENGTH: usize = 64 * 1024;
pub const MEMORY_LENGTH: usize = 0x0500_2980; pub const MEMORY_LENGTH: usize = 0x0500_2980;
const MEMORY_WRITE_CHUNK_LENGTH: usize = 1 * 1024 * 1024; const MEMORY_CHUNK_LENGTH: usize = 1 * 1024 * 1024;
impl SC64 { impl SC64 {
fn command_identifier_get(&mut self) -> Result<Vec<u8>, Error> { fn command_identifier_get(&mut self) -> Result<Vec<u8>, Error> {
let identifier = self.link.execute_command(&mut Command { let identifier = self.link.execute_command(&Command {
id: b'v', id: b'v',
args: [0, 0], args: [0, 0],
data: vec![], data: vec![],
@ -109,7 +108,7 @@ impl SC64 {
} }
fn command_version_get(&mut self) -> Result<(u16, u16), Error> { fn command_version_get(&mut self) -> Result<(u16, u16), Error> {
let version = self.link.execute_command(&mut Command { let version = self.link.execute_command(&Command {
id: b'V', id: b'V',
args: [0, 0], args: [0, 0],
data: vec![], data: vec![],
@ -120,7 +119,7 @@ impl SC64 {
} }
fn command_state_reset(&mut self) -> Result<(), Error> { fn command_state_reset(&mut self) -> Result<(), Error> {
self.link.execute_command(&mut Command { self.link.execute_command(&Command {
id: b'R', id: b'R',
args: [0, 0], args: [0, 0],
data: vec![], data: vec![],
@ -137,7 +136,7 @@ impl SC64 {
let mut params: Vec<u8> = vec![]; let mut params: Vec<u8> = vec![];
params.append(&mut [(disable as u8) << 0, seed].to_vec()); params.append(&mut [(disable as u8) << 0, seed].to_vec());
params.append(&mut checksum.to_vec()); params.append(&mut checksum.to_vec());
self.link.execute_command(&mut Command { self.link.execute_command(&Command {
id: b'B', id: b'B',
args: args_from_vec(&params[0..8])?, args: args_from_vec(&params[0..8])?,
data: vec![], data: vec![],
@ -146,7 +145,7 @@ impl SC64 {
} }
fn command_config_get(&mut self, config_id: ConfigId) -> Result<Config, Error> { fn command_config_get(&mut self, config_id: ConfigId) -> Result<Config, Error> {
let data = self.link.execute_command(&mut Command { let data = self.link.execute_command(&Command {
id: b'c', id: b'c',
args: [config_id.into(), 0], args: [config_id.into(), 0],
data: vec![], data: vec![],
@ -156,7 +155,7 @@ impl SC64 {
} }
fn command_config_set(&mut self, config: Config) -> Result<(), Error> { fn command_config_set(&mut self, config: Config) -> Result<(), Error> {
self.link.execute_command(&mut Command { self.link.execute_command(&Command {
id: b'C', id: b'C',
args: config.into(), args: config.into(),
data: vec![], data: vec![],
@ -165,7 +164,7 @@ impl SC64 {
} }
fn command_setting_get(&mut self, setting_id: SettingId) -> Result<Setting, Error> { fn command_setting_get(&mut self, setting_id: SettingId) -> Result<Setting, Error> {
let data = self.link.execute_command(&mut Command { let data = self.link.execute_command(&Command {
id: b'a', id: b'a',
args: [setting_id.into(), 0], args: [setting_id.into(), 0],
data: vec![], data: vec![],
@ -175,7 +174,7 @@ impl SC64 {
} }
fn command_setting_set(&mut self, setting: Setting) -> Result<(), Error> { fn command_setting_set(&mut self, setting: Setting) -> Result<(), Error> {
self.link.execute_command(&mut Command { self.link.execute_command(&Command {
id: b'A', id: b'A',
args: setting.into(), args: setting.into(),
data: vec![], data: vec![],
@ -184,7 +183,7 @@ impl SC64 {
} }
fn command_time_get(&mut self) -> Result<DateTime<Local>, Error> { fn command_time_get(&mut self) -> Result<DateTime<Local>, Error> {
let data = self.link.execute_command(&mut Command { let data = self.link.execute_command(&Command {
id: b't', id: b't',
args: [0, 0], args: [0, 0],
data: vec![], data: vec![],
@ -193,7 +192,7 @@ impl SC64 {
} }
fn command_time_set(&mut self, datetime: DateTime<Local>) -> Result<(), Error> { fn command_time_set(&mut self, datetime: DateTime<Local>) -> Result<(), Error> {
self.link.execute_command(&mut Command { self.link.execute_command(&Command {
id: b'T', id: b'T',
args: args_from_vec(&vec_from_datetime(datetime)?[0..8])?, args: args_from_vec(&vec_from_datetime(datetime)?[0..8])?,
data: vec![], data: vec![],
@ -202,7 +201,7 @@ impl SC64 {
} }
fn command_memory_read(&mut self, address: u32, length: usize) -> Result<Vec<u8>, Error> { fn command_memory_read(&mut self, address: u32, length: usize) -> Result<Vec<u8>, Error> {
let data = self.link.execute_command(&mut Command { let data = self.link.execute_command(&Command {
id: b'm', id: b'm',
args: [address, length as u32], args: [address, length as u32],
data: vec![], data: vec![],
@ -211,7 +210,7 @@ impl SC64 {
} }
fn command_memory_write(&mut self, address: u32, data: &[u8]) -> Result<(), Error> { fn command_memory_write(&mut self, address: u32, data: &[u8]) -> Result<(), Error> {
self.link.execute_command(&mut Command { self.link.execute_command(&Command {
id: b'M', id: b'M',
args: [address, data.len() as u32], args: [address, data.len() as u32],
data: data.to_vec(), data: data.to_vec(),
@ -221,7 +220,7 @@ impl SC64 {
fn command_usb_write(&mut self, datatype: u8, data: &[u8]) -> Result<(), Error> { fn command_usb_write(&mut self, datatype: u8, data: &[u8]) -> Result<(), Error> {
self.link.execute_command_raw( self.link.execute_command_raw(
&mut Command { &Command {
id: b'U', id: b'U',
args: [datatype as u32, data.len() as u32], args: [datatype as u32, data.len() as u32],
data: data.to_vec(), data: data.to_vec(),
@ -234,7 +233,7 @@ impl SC64 {
} }
fn command_dd_set_block_ready(&mut self, error: bool) -> Result<(), Error> { fn command_dd_set_block_ready(&mut self, error: bool) -> Result<(), Error> {
self.link.execute_command(&mut Command { self.link.execute_command(&Command {
id: b'D', id: b'D',
args: [error as u32, 0], args: [error as u32, 0],
data: vec![], data: vec![],
@ -243,7 +242,7 @@ impl SC64 {
} }
fn command_flash_wait_busy(&mut self, wait: bool) -> Result<u32, Error> { fn command_flash_wait_busy(&mut self, wait: bool) -> Result<u32, Error> {
let erase_block_size = self.link.execute_command(&mut Command { let erase_block_size = self.link.execute_command(&Command {
id: b'p', id: b'p',
args: [wait as u32, 0], args: [wait as u32, 0],
data: vec![], data: vec![],
@ -252,7 +251,7 @@ impl SC64 {
} }
fn command_flash_erase_block(&mut self, address: u32) -> Result<(), Error> { fn command_flash_erase_block(&mut self, address: u32) -> Result<(), Error> {
self.link.execute_command(&mut Command { self.link.execute_command(&Command {
id: b'P', id: b'P',
args: [address, 0], args: [address, 0],
data: vec![], data: vec![],
@ -262,7 +261,7 @@ impl SC64 {
fn command_firmware_backup(&mut self, address: u32) -> Result<(FirmwareStatus, u32), Error> { fn command_firmware_backup(&mut self, address: u32) -> Result<(FirmwareStatus, u32), Error> {
let data = self.link.execute_command_raw( let data = self.link.execute_command_raw(
&mut Command { &Command {
id: b'f', id: b'f',
args: [address, 0], args: [address, 0],
data: vec![], data: vec![],
@ -282,7 +281,7 @@ impl SC64 {
length: usize, length: usize,
) -> Result<FirmwareStatus, Error> { ) -> Result<FirmwareStatus, Error> {
let data = self.link.execute_command_raw( let data = self.link.execute_command_raw(
&mut Command { &Command {
id: b'F', id: b'F',
args: [address, length as u32], args: [address, length as u32],
data: vec![], data: vec![],
@ -296,7 +295,7 @@ impl SC64 {
fn command_debug_get(&mut self) -> Result<FpgaDebugData, Error> { fn command_debug_get(&mut self) -> Result<FpgaDebugData, Error> {
self.link self.link
.execute_command(&mut Command { .execute_command(&Command {
id: b'?', id: b'?',
args: [0, 0], args: [0, 0],
data: vec![], data: vec![],
@ -306,7 +305,7 @@ impl SC64 {
fn command_stack_usage_get(&mut self) -> Result<McuStackUsage, Error> { fn command_stack_usage_get(&mut self) -> Result<McuStackUsage, Error> {
self.link self.link
.execute_command(&mut Command { .execute_command(&Command {
id: b'%', id: b'%',
args: [0, 0], args: [0, 0],
data: vec![], data: vec![],
@ -329,7 +328,8 @@ impl SC64 {
let mut pi_config = vec![0u8; 4]; let mut pi_config = vec![0u8; 4];
reader.rewind()?; reader.rewind()?;
reader.read(&mut pi_config)?; reader.read_exact(&mut pi_config)?;
reader.rewind()?;
let endian_swapper = match &pi_config[0..4] { let endian_swapper = match &pi_config[0..4] {
[0x37, 0x80, 0x40, 0x12] => { [0x37, 0x80, 0x40, 0x12] => {
@ -353,8 +353,6 @@ impl SC64 {
min(length, SDRAM_LENGTH) min(length, SDRAM_LENGTH)
}; };
reader.rewind()?;
self.memory_write_chunked(reader, SDRAM_ADDRESS, sdram_length, Some(endian_swapper))?; self.memory_write_chunked(reader, SDRAM_ADDRESS, sdram_length, Some(endian_swapper))?;
self.command_config_set(Config::RomShadowEnable(rom_shadow_enabled.into()))?; self.command_config_set(Config::RomShadowEnable(rom_shadow_enabled.into()))?;
@ -427,18 +425,19 @@ impl SC64 {
SaveType::Flashram => (SAVE_ADDRESS, FLASHRAM_LENGTH), SaveType::Flashram => (SAVE_ADDRESS, FLASHRAM_LENGTH),
}; };
let mut data = self.command_memory_read(address, save_length)?; self.memory_read_chunked(writer, address, save_length)
writer.write_all(&mut data)?;
Ok(())
} }
pub fn dump_memory(&mut self, address: u32, length: usize) -> Result<Vec<u8>, Error> { pub fn dump_memory<T: Write>(
&mut self,
writer: &mut T,
address: u32,
length: usize,
) -> Result<(), Error> {
if address + length as u32 > MEMORY_LENGTH as u32 { if address + length as u32 > MEMORY_LENGTH as u32 {
return Err(Error::new("Invalid dump address or length")); return Err(Error::new("Invalid dump address or length"));
} }
self.command_memory_read(address, length) self.memory_read_chunked(writer, address, length)
} }
pub fn calculate_cic_parameters(&mut self) -> Result<(), Error> { pub fn calculate_cic_parameters(&mut self) -> Result<(), Error> {
@ -637,11 +636,22 @@ impl SC64 {
} }
} }
pub fn update_bootloader(&mut self, reader: &mut dyn Read, length: usize) -> Result<(), Error> { fn memory_read_chunked(
if length > BOOTLOADER_LENGTH { &mut self,
return Err(Error::new("Bootloader length too big")); writer: &mut dyn Write,
address: u32,
length: usize,
) -> Result<(), Error> {
let mut memory_address = address;
let mut bytes_left = length;
while bytes_left > 0 {
let bytes = min(MEMORY_CHUNK_LENGTH, bytes_left);
let data = self.command_memory_read(memory_address, bytes)?;
writer.write_all(&data)?;
memory_address += bytes as u32;
bytes_left -= bytes;
} }
self.flash_program(reader, BOOTLOADER_ADDRESS, length, None) Ok(())
} }
fn memory_write_chunked( fn memory_write_chunked(
@ -651,13 +661,19 @@ impl SC64 {
length: usize, length: usize,
transform: Option<fn(&mut [u8])>, transform: Option<fn(&mut [u8])>,
) -> Result<(), Error> { ) -> Result<(), Error> {
let mut data: Vec<u8> = vec![0u8; MEMORY_WRITE_CHUNK_LENGTH]; let mut limited_reader = reader.take(length as u64);
for offset in (0..length).step_by(MEMORY_WRITE_CHUNK_LENGTH) { let mut memory_address = address;
let chunk = reader.read(&mut data)?; let mut data: Vec<u8> = vec![0u8; MEMORY_CHUNK_LENGTH];
if let Some(transform) = transform { loop {
transform(&mut data); let bytes = limited_reader.read(&mut data)?;
if bytes == 0 {
break;
} }
self.command_memory_write(address + offset as u32, &data[0..chunk])?; if let Some(transform) = transform {
transform(&mut data[0..bytes]);
}
self.command_memory_write(memory_address, &data[0..bytes])?;
memory_address += bytes as u32;
} }
Ok(()) Ok(())
} }

View File

@ -669,6 +669,7 @@ pub enum FirmwareStatus {
ErrSize, ErrSize,
ErrUnknownChunk, ErrUnknownChunk,
ErrRead, ErrRead,
ErrAddress,
} }
impl Display for FirmwareStatus { impl Display for FirmwareStatus {
@ -680,6 +681,7 @@ impl Display for FirmwareStatus {
FirmwareStatus::ErrSize => "Invalid firmware size", FirmwareStatus::ErrSize => "Invalid firmware size",
FirmwareStatus::ErrUnknownChunk => "Unknown chunk in firmware", FirmwareStatus::ErrUnknownChunk => "Unknown chunk in firmware",
FirmwareStatus::ErrRead => "Firmware read error", FirmwareStatus::ErrRead => "Firmware read error",
FirmwareStatus::ErrAddress => "Invalid address or length provided",
}) })
} }
} }
@ -694,6 +696,7 @@ impl TryFrom<u32> for FirmwareStatus {
3 => Self::ErrSize, 3 => Self::ErrSize,
4 => Self::ErrUnknownChunk, 4 => Self::ErrUnknownChunk,
5 => Self::ErrRead, 5 => Self::ErrRead,
6 => Self::ErrAddress,
_ => return Err(Error::new("Unknown firmware status code")), _ => return Err(Error::new("Unknown firmware status code")),
}) })
} }