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) {
if (((src + length) >= FLASH_ADDRESS) && (src < (FLASH_ADDRESS + FLASH_SIZE))) {
if (((src + length) > FLASH_ADDRESS) && (src < (FLASH_ADDRESS + FLASH_SIZE))) {
return true;
}
if ((dst < FLASH_ADDRESS) || ((dst + length) >= (FLASH_ADDRESS + FLASH_SIZE))) {
if ((dst < FLASH_ADDRESS) || ((dst + length) > (FLASH_ADDRESS + FLASH_SIZE))) {
return true;
}
while (length > 0) {
@ -29,15 +29,18 @@ void flash_wait_busy (void) {
fpga_mem_read(FLASH_ADDRESS, 2, dummy);
}
bool flash_erase_block (uint32_t offset) {
if ((offset % FLASH_ERASE_BLOCK_SIZE) != 0) {
bool flash_erase_block (uint32_t address) {
if ((address % FLASH_ERASE_BLOCK_SIZE) != 0) {
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++) {
fpga_reg_set(REG_FLASH_SCR, offset);
fpga_reg_set(REG_FLASH_SCR, address);
while (fpga_reg_get(REG_FLASH_SCR) & FLASH_SCR_BUSY);
offset += ERASE_BLOCK_SIZE;
address += ERASE_BLOCK_SIZE;
}
flash_wait_busy();
return false;

View File

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

View File

@ -6,6 +6,9 @@
#include "vendor.h"
#define SDRAM_ADDRESS (0x00000000UL)
#define SDRAM_LENGTH (64 * 1024 * 1024)
#define UPDATE_MAGIC_START (0x54535055UL)
#define BOOTLOADER_ADDRESS (0x04E00000UL)
#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 bootloader_length;
if (address >= (SDRAM_ADDRESS + SDRAM_LENGTH)) {
return UPDATE_ERROR_ADDRESS;
}
*length = update_write_token(&address);
*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);
if ((address + *length) > (SDRAM_ADDRESS + SDRAM_LENGTH)) {
return UPDATE_ERROR_ADDRESS;
}
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_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)) {
return UPDATE_ERROR_TOKEN;
}

View File

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

View File

@ -10,10 +10,20 @@
#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 {
RX_STATE_IDLE,
RX_STATE_ARGS,
RX_STATE_DATA,
RX_STATE_FLUSH,
};
enum tx_state {
@ -129,6 +139,21 @@ static bool usb_rx_cmd (uint8_t *cmd) {
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) {
if (p.rx_state == RX_STATE_IDLE) {
if (!p.response_pending && usb_rx_cmd(&p.rx_cmd)) {
@ -229,17 +254,25 @@ static void usb_rx_process (void) {
case 'm':
p.rx_state = RX_STATE_IDLE;
p.response_pending = true;
if (usb_validate_address_length(p.rx_args[0], p.rx_args[1], false)) {
p.response_error = true;
} else {
p.response_info.dma_address = p.rx_args[0];
p.response_info.dma_length = p.rx_args[1];
}
break;
case 'M':
if (usb_dma_ready()) {
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;
} else {
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 {
p.rx_state = RX_STATE_IDLE;
p.response_pending = true;
@ -258,6 +291,7 @@ static void usb_rx_process (void) {
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;
@ -285,7 +319,11 @@ static void usb_rx_process (void) {
break;
case 'P':
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.response_pending = true;
break;
@ -336,10 +374,26 @@ static void usb_rx_process (void) {
p.response_pending = true;
p.response_error = true;
p.response_info.data_length = 4;
p.response_info.data[0] = 0xFF;
p.response_info.data[0] = 0xFFFFFFFF;
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) {

View File

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

View File

@ -18,7 +18,7 @@ pub fn guess_save_type<T: Read + Seek>(
let mut ed64_header = vec![0u8; 4];
reader.seek(SeekFrom::Start(0x3C))?;
reader.read(&mut ed64_header)?;
reader.read_exact(&mut ed64_header)?;
if &ed64_header[0..2] == b"ED" {
return Ok((
@ -38,7 +38,7 @@ pub fn guess_save_type<T: Read + Seek>(
let mut pi_config = vec![0u8; 4];
reader.rewind()?;
reader.read(&mut pi_config)?;
reader.read_exact(&mut pi_config)?;
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)),

View File

@ -29,10 +29,10 @@ enum DataType {
}
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(
&mut self,
command: &mut Command,
command: &Command,
timeout: Duration,
no_response: bool,
ignore_error: bool,
@ -79,20 +79,16 @@ impl SerialLink {
Ok(())
}
fn serial_send_command(
&mut self,
command: &mut Command,
timeout: Duration,
) -> Result<(), Error> {
let mut packet: Vec<u8> = Vec::new();
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);
fn serial_send_command(&mut self, command: &Command, timeout: Duration) -> Result<(), Error> {
let mut header: Vec<u8> = Vec::new();
header.append(&mut b"CMD".to_vec());
header.append(&mut [command.id].to_vec());
header.append(&mut command.args[0].to_be_bytes().to_vec());
header.append(&mut command.args[1].to_be_bytes().to_vec());
self.serial.set_timeout(timeout)?;
self.serial.write_all(&packet)?;
self.serial.write_all(&header)?;
self.serial.write_all(&command.data)?;
self.serial.flush()?;
Ok(())
@ -138,7 +134,7 @@ impl SerialLink {
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)
}
@ -151,13 +147,13 @@ impl 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)
}
fn execute_command_raw(
&mut self,
command: &mut Command,
command: &Command,
timeout: Duration,
no_response: 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 BOOTLOADER_ADDRESS: u32 = 0x04E0_0000;
const BOOTLOADER_LENGTH: usize = 1920 * 1024;
const FIRMWARE_ADDRESS: u32 = 0x0010_0000; // Arbitrary offset in SDRAM memory
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;
const MEMORY_WRITE_CHUNK_LENGTH: usize = 1 * 1024 * 1024;
const MEMORY_CHUNK_LENGTH: usize = 1 * 1024 * 1024;
impl SC64 {
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',
args: [0, 0],
data: vec![],
@ -109,7 +108,7 @@ impl SC64 {
}
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',
args: [0, 0],
data: vec![],
@ -120,7 +119,7 @@ impl SC64 {
}
fn command_state_reset(&mut self) -> Result<(), Error> {
self.link.execute_command(&mut Command {
self.link.execute_command(&Command {
id: b'R',
args: [0, 0],
data: vec![],
@ -137,7 +136,7 @@ impl SC64 {
let mut params: Vec<u8> = vec![];
params.append(&mut [(disable as u8) << 0, seed].to_vec());
params.append(&mut checksum.to_vec());
self.link.execute_command(&mut Command {
self.link.execute_command(&Command {
id: b'B',
args: args_from_vec(&params[0..8])?,
data: vec![],
@ -146,7 +145,7 @@ impl SC64 {
}
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',
args: [config_id.into(), 0],
data: vec![],
@ -156,7 +155,7 @@ impl SC64 {
}
fn command_config_set(&mut self, config: Config) -> Result<(), Error> {
self.link.execute_command(&mut Command {
self.link.execute_command(&Command {
id: b'C',
args: config.into(),
data: vec![],
@ -165,7 +164,7 @@ impl SC64 {
}
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',
args: [setting_id.into(), 0],
data: vec![],
@ -175,7 +174,7 @@ impl SC64 {
}
fn command_setting_set(&mut self, setting: Setting) -> Result<(), Error> {
self.link.execute_command(&mut Command {
self.link.execute_command(&Command {
id: b'A',
args: setting.into(),
data: vec![],
@ -184,7 +183,7 @@ impl SC64 {
}
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',
args: [0, 0],
data: vec![],
@ -193,7 +192,7 @@ impl SC64 {
}
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',
args: args_from_vec(&vec_from_datetime(datetime)?[0..8])?,
data: vec![],
@ -202,7 +201,7 @@ impl SC64 {
}
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',
args: [address, length as u32],
data: vec![],
@ -211,7 +210,7 @@ impl SC64 {
}
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',
args: [address, data.len() as u32],
data: data.to_vec(),
@ -221,7 +220,7 @@ impl SC64 {
fn command_usb_write(&mut self, datatype: u8, data: &[u8]) -> Result<(), Error> {
self.link.execute_command_raw(
&mut Command {
&Command {
id: b'U',
args: [datatype as u32, data.len() as u32],
data: data.to_vec(),
@ -234,7 +233,7 @@ impl SC64 {
}
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',
args: [error as u32, 0],
data: vec![],
@ -243,7 +242,7 @@ impl SC64 {
}
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',
args: [wait as u32, 0],
data: vec![],
@ -252,7 +251,7 @@ impl SC64 {
}
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',
args: [address, 0],
data: vec![],
@ -262,7 +261,7 @@ impl SC64 {
fn command_firmware_backup(&mut self, address: u32) -> Result<(FirmwareStatus, u32), Error> {
let data = self.link.execute_command_raw(
&mut Command {
&Command {
id: b'f',
args: [address, 0],
data: vec![],
@ -282,7 +281,7 @@ impl SC64 {
length: usize,
) -> Result<FirmwareStatus, Error> {
let data = self.link.execute_command_raw(
&mut Command {
&Command {
id: b'F',
args: [address, length as u32],
data: vec![],
@ -296,7 +295,7 @@ impl SC64 {
fn command_debug_get(&mut self) -> Result<FpgaDebugData, Error> {
self.link
.execute_command(&mut Command {
.execute_command(&Command {
id: b'?',
args: [0, 0],
data: vec![],
@ -306,7 +305,7 @@ impl SC64 {
fn command_stack_usage_get(&mut self) -> Result<McuStackUsage, Error> {
self.link
.execute_command(&mut Command {
.execute_command(&Command {
id: b'%',
args: [0, 0],
data: vec![],
@ -329,7 +328,8 @@ impl SC64 {
let mut pi_config = vec![0u8; 4];
reader.rewind()?;
reader.read(&mut pi_config)?;
reader.read_exact(&mut pi_config)?;
reader.rewind()?;
let endian_swapper = match &pi_config[0..4] {
[0x37, 0x80, 0x40, 0x12] => {
@ -353,8 +353,6 @@ impl SC64 {
min(length, SDRAM_LENGTH)
};
reader.rewind()?;
self.memory_write_chunked(reader, SDRAM_ADDRESS, sdram_length, Some(endian_swapper))?;
self.command_config_set(Config::RomShadowEnable(rom_shadow_enabled.into()))?;
@ -427,18 +425,19 @@ impl SC64 {
SaveType::Flashram => (SAVE_ADDRESS, FLASHRAM_LENGTH),
};
let mut data = self.command_memory_read(address, save_length)?;
writer.write_all(&mut data)?;
Ok(())
self.memory_read_chunked(writer, address, save_length)
}
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 {
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> {
@ -637,11 +636,22 @@ impl SC64 {
}
}
pub fn update_bootloader(&mut self, reader: &mut dyn Read, length: usize) -> Result<(), Error> {
if length > BOOTLOADER_LENGTH {
return Err(Error::new("Bootloader length too big"));
fn memory_read_chunked(
&mut self,
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(
@ -651,13 +661,19 @@ impl SC64 {
length: usize,
transform: Option<fn(&mut [u8])>,
) -> Result<(), Error> {
let mut data: Vec<u8> = vec![0u8; MEMORY_WRITE_CHUNK_LENGTH];
for offset in (0..length).step_by(MEMORY_WRITE_CHUNK_LENGTH) {
let chunk = reader.read(&mut data)?;
if let Some(transform) = transform {
transform(&mut data);
let mut limited_reader = reader.take(length as u64);
let mut memory_address = address;
let mut data: Vec<u8> = vec![0u8; MEMORY_CHUNK_LENGTH];
loop {
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(())
}

View File

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