From 942bad080bc2733002076e08c2213f88e438bdbb Mon Sep 17 00:00:00 2001 From: Karolis Stasaitis Date: Tue, 9 Jul 2024 21:54:19 +0200 Subject: [PATCH] feature: payload conversion (to jtag and to isp) + flake, rust updates (#82) A convenience function to convert (full) isp payloads for programming via JTAG and vice versa. --- flake.lock | 74 ++++++++++-------------------------- flake.nix | 2 +- rust-toolchain.toml | 2 +- src/isp.rs | 7 ++-- src/main.rs | 82 ++++++++++++++++++++++++++++++++++++--- src/part.rs | 4 ++ src/util.rs | 93 ++++++++++++++++++++++++++++++++++++++++++++- 7 files changed, 198 insertions(+), 66 deletions(-) diff --git a/flake.lock b/flake.lock index 74e9ffe..019ff72 100644 --- a/flake.lock +++ b/flake.lock @@ -1,33 +1,15 @@ { "nodes": { - "flake-utils": { - "inputs": { - "systems": "systems" - }, - "locked": { - "lastModified": 1705309234, - "narHash": "sha256-uNRRNRKmJyCRC/8y1RqBkqWBLM034y4qN7EprSdmgyA=", - "owner": "numtide", - "repo": "flake-utils", - "rev": "1ef2e671c3b0c19053962c07dbda38332dcebf26", - "type": "github" - }, - "original": { - "owner": "numtide", - "repo": "flake-utils", - "type": "github" - } - }, "naersk": { "inputs": { "nixpkgs": "nixpkgs" }, "locked": { - "lastModified": 1698420672, - "narHash": "sha256-/TdeHMPRjjdJub7p7+w55vyABrsJlt5QkznPYy55vKA=", + "lastModified": 1718727675, + "narHash": "sha256-uFsCwWYI2pUpt0awahSBorDUrUfBhaAiyz+BPTS2MHk=", "owner": "nix-community", "repo": "naersk", - "rev": "aeb58d5e8faead8980a807c840232697982d47b9", + "rev": "941ce6dc38762a7cfb90b5add223d584feed299b", "type": "github" }, "original": { @@ -38,11 +20,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1708751719, - "narHash": "sha256-0uWOKSpXJXmXswOvDM5Vk3blB74apFB6rNGWV5IjoN0=", + "lastModified": 1719082008, + "narHash": "sha256-jHJSUH619zBQ6WdC21fFAlDxHErKVDJ5fpN0Hgx4sjs=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "f63ce824cd2f036216eb5f637dfef31e1a03ee89", + "rev": "9693852a2070b398ee123a329e68f0dab5526681", "type": "github" }, "original": { @@ -52,27 +34,27 @@ }, "nixpkgs_2": { "locked": { - "lastModified": 1708702655, - "narHash": "sha256-qxT5jSLhelfLhQ07+AUxSTm1VnVH+hQxDkQSZ/m/Smo=", + "lastModified": 1719145550, + "narHash": "sha256-K0i/coxxTEl30tgt4oALaylQfxqbotTSNb1/+g+mKMQ=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "c5101e457206dd437330d283d6626944e28794b3", + "rev": "e4509b3a560c87a8d4cb6f9992b8915abf9e36d8", "type": "github" }, "original": { "owner": "NixOS", - "ref": "nixos-23.11", + "ref": "nixos-24.05", "repo": "nixpkgs", "type": "github" } }, "nixpkgs_3": { "locked": { - "lastModified": 1706487304, - "narHash": "sha256-LE8lVX28MV2jWJsidW13D2qrHU/RUUONendL2Q/WlJg=", + "lastModified": 1718428119, + "narHash": "sha256-WdWDpNaq6u1IPtxtYHHWpl5BmabtpmLnMAx0RdJ/vo8=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "90f456026d284c22b3e3497be980b2e47d0b28ac", + "rev": "e6cea36f83499eb4e9cd184c8a8e823296b50ad5", "type": "github" }, "original": { @@ -92,15 +74,14 @@ }, "rust-overlay": { "inputs": { - "flake-utils": "flake-utils", "nixpkgs": "nixpkgs_3" }, "locked": { - "lastModified": 1708827164, - "narHash": "sha256-oBNS6pO04Y6gZBLThP3JDDgviex0+WTXz3bVBenyzms=", + "lastModified": 1719195554, + "narHash": "sha256-bFXHMjpYlEERexzXa1gLGJO/1l8dxaAtSNE56YALuTg=", "owner": "oxalica", "repo": "rust-overlay", - "rev": "e0626adabd5ea461f80b1b11390da2a6575adb30", + "rev": "577ee84c69ba89894ac622d71a678a14d746b2f7", "type": "github" }, "original": { @@ -124,31 +105,16 @@ "type": "github" } }, - "systems_2": { - "locked": { - "lastModified": 1681028828, - "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", - "owner": "nix-systems", - "repo": "default", - "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", - "type": "github" - }, - "original": { - "owner": "nix-systems", - "repo": "default", - "type": "github" - } - }, "utils": { "inputs": { - "systems": "systems_2" + "systems": "systems" }, "locked": { - "lastModified": 1705309234, - "narHash": "sha256-uNRRNRKmJyCRC/8y1RqBkqWBLM034y4qN7EprSdmgyA=", + "lastModified": 1710146030, + "narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=", "owner": "numtide", "repo": "flake-utils", - "rev": "1ef2e671c3b0c19053962c07dbda38332dcebf26", + "rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a", "type": "github" }, "original": { diff --git a/flake.nix b/flake.nix index b9cfd12..06771e6 100644 --- a/flake.nix +++ b/flake.nix @@ -1,6 +1,6 @@ { inputs = { - nixpkgs.url = "github:NixOS/nixpkgs/nixos-23.11"; + nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.05"; utils.url = "github:numtide/flake-utils"; naersk.url = "github:nix-community/naersk"; rust-overlay.url = "github:oxalica/rust-overlay"; diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 98d869a..08e95c4 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,5 +1,5 @@ [toolchain] -channel = "1.76" +channel = "1.78" components = [ "rustfmt", "rustc", diff --git a/src/isp.rs b/src/isp.rs index e47227a..e95865f 100644 --- a/src/isp.rs +++ b/src/isp.rs @@ -3,8 +3,7 @@ use std::{thread, time}; use log::{debug, info}; use thiserror::Error; -use super::{part::*, util}; -use crate::VerificationError; +use crate::{part::*, util, VerificationError}; extern crate hidapi; @@ -317,10 +316,10 @@ impl ISPDevice { self.reboot()?; } - return Ok(firmware); + Ok(firmware) } - pub fn write_cycle(&self, firmware: &mut Vec) -> Result<(), ISPError> { + pub fn write_cycle(&self, firmware: &mut [u8]) -> Result<(), ISPError> { // ensure that the address at is the same as the reset vector firmware.copy_within(1..3, self.part.firmware_size - 4); diff --git a/src/main.rs b/src/main.rs index 64669ac..7eb7560 100644 --- a/src/main.rs +++ b/src/main.rs @@ -26,6 +26,8 @@ pub enum CLIError { ISPError(#[from] ISPError), #[error(transparent)] IHEXError(#[from] ConversionError), + #[error(transparent)] + PayloadConversionError(#[from] PayloadConversionError), } fn main() -> ExitCode { @@ -39,7 +41,7 @@ fn main() -> ExitCode { } fn cli() -> Command { - return Command::new("sinowealth-kb-tool") + Command::new("sinowealth-kb-tool") .about("A programming tool for Sinowealth Gaming KB devices") .version(env!("CARGO_PKG_VERSION")) .subcommand_required(true) @@ -50,6 +52,15 @@ fn cli() -> Command { .short_flag('l') .about("List all connected devices and their identifiers. This is useful to find the manufacturer and product id for your device.") ) + .subcommand( + Command::new("convert") + .short_flag('c') + .about("Convert payload from bootloader to JTAG and vice versa.") + .arg(arg!(-d --direction "direction of conversion").value_parser(["to_jtag", "to_isp"]).required(true)) + .part_args() + .arg(arg!(input_file: "file to convert")) + .arg(arg!(output_file: "file to write results to")) + ) .subcommand( Command::new("read") .short_flag('r') @@ -68,11 +79,11 @@ fn cli() -> Command { .about("Write file (Intel HEX) into flash.") .arg(arg!(input_file: "payload to write into flash")) .part_args(), - ); + ) } fn get_log_level() -> log::LevelFilter { - return if let Ok(debug) = env::var("DEBUG") { + if let Ok(debug) = env::var("DEBUG") { if debug == "1" { log::LevelFilter::Debug } else { @@ -83,7 +94,7 @@ fn get_log_level() -> log::LevelFilter { return log::LevelFilter::Debug; #[cfg(not(debug_assertions))] log::LevelFilter::Info - }; + } } fn err_main() -> Result<(), CLIError> { @@ -146,6 +157,67 @@ fn err_main() -> Result<(), CLIError> { Some(("list", _)) => { ISPDevice::print_connected_devices().map_err(CLIError::from)?; } + Some(("convert", sub_matches)) => { + let input_file = sub_matches + .get_one::("input_file") + .map(|s| s.as_str()) + .unwrap(); + + let output_file = sub_matches + .get_one::("output_file") + .map(|s| s.as_str()) + .unwrap(); + + let direction = sub_matches + .get_one::("direction") + .map(|s| s.as_str()) + .unwrap(); + + let part = get_part_from_matches(sub_matches); + + let mut file = fs::File::open(input_file).map_err(CLIError::from)?; + let mut file_buf = Vec::new(); + file.read_to_end(&mut file_buf).map_err(CLIError::from)?; + let file_str = String::from_utf8_lossy(&file_buf[..]); + let mut firmware = from_ihex(&file_str, part.firmware_size + part.bootloader_size) + .map_err(CLIError::from)?; + + if firmware.len() < part.firmware_size { + log::warn!( + "Firmware size is more than expected ({}). Increasing to {}", + firmware.len(), + part.firmware_size + ); + firmware.resize(part.firmware_size, 0); + } + + match direction { + "to_jtag" => { + convert_to_jtag_payload(&mut firmware, part).map_err(CLIError::from)?; + if firmware.len() < part.total_flash_size() { + log::warn!( + "Firmware is smaller ({} bytes) than expected ({} bytes). This payload might not be suitable for JTAG flashing.", + firmware.len(), + part.total_flash_size() + ); + } + } + "to_isp" => { + convert_to_isp_payload(&mut firmware, part).map_err(CLIError::from)?; + if firmware.len() > part.firmware_size { + log::warn!( + "Firmware size is larger ({} bytes) than expected ({} bytes). This payload might not be suitable for ISP flashing.", + firmware.len(), + part.firmware_size + ); + } + } + _ => unreachable!(), + } + + let ihex = to_ihex(firmware).map_err(CLIError::from)?; + fs::write(output_file, ihex).map_err(CLIError::from)?; + } _ => unreachable!(), } Ok(()) @@ -236,5 +308,5 @@ fn get_part_from_matches(sub_matches: &ArgMatches) -> Part { if let Some(reboot) = reboot { part.reboot = *reboot; } - return part; + part } diff --git a/src/part.rs b/src/part.rs index 0592d94..92cce48 100644 --- a/src/part.rs +++ b/src/part.rs @@ -288,6 +288,10 @@ impl Part { pub fn num_pages(&self) -> usize { self.firmware_size / self.page_size } + + pub fn total_flash_size(&self) -> usize { + self.firmware_size + self.bootloader_size + } } #[test] diff --git a/src/util.rs b/src/util.rs index ae0cd3a..5b2b549 100644 --- a/src/util.rs +++ b/src/util.rs @@ -1,5 +1,9 @@ +use crate::part::Part; use thiserror::Error; +#[cfg(test)] +use crate::part::PART_BASE_SH68F90; + #[derive(Debug, Clone, Error, PartialEq)] pub enum VerificationError { #[error("Firmware Mismatch @ {addr:#06x} --- {expected:#04x} != {actual:#04x}")] @@ -12,7 +16,7 @@ pub enum VerificationError { LengthMismatch { expected: usize, actual: usize }, } -pub fn verify(expected: &Vec, actual: &Vec) -> Result<(), VerificationError> { +pub fn verify(expected: &[u8], actual: &[u8]) -> Result<(), VerificationError> { if expected.len() != actual.len() { return Err(VerificationError::LengthMismatch { expected: expected.len(), @@ -33,6 +37,62 @@ pub fn verify(expected: &Vec, actual: &Vec) -> Result<(), VerificationEr Ok(()) } +#[derive(Debug, Error)] +pub enum PayloadConversionError { + #[error("Expected LJMP not found at {addr:#06x}")] + LJMPNotFoundError { addr: u16 }, + #[error("Unexpected addr at {source_addr:#06x} pointing to {target_addr:#06x}")] + UnexpectedAddressError { source_addr: u16, target_addr: u16 }, +} + +pub fn convert_to_jtag_payload(input: &mut [u8], part: Part) -> Result<(), PayloadConversionError> { + if input[0] != 0x02 { + return Err(PayloadConversionError::LJMPNotFoundError { addr: 0x0000 }); + } + + let main_fw_address = u16::from_be_bytes(input[1..3].try_into().unwrap()); + if main_fw_address > 0xefff { + return Err(PayloadConversionError::UnexpectedAddressError { + source_addr: 0x0001, + target_addr: main_fw_address, + }); + } + + let bootloader_ljmp_addr = (part.firmware_size as u16).to_be_bytes(); + let ljmp_addr = part.firmware_size - 5; + + input[1..3].copy_from_slice(&bootloader_ljmp_addr); + input[ljmp_addr] = 0x02; + input[ljmp_addr + 1..ljmp_addr + 3].copy_from_slice(&main_fw_address.to_be_bytes()); + + Ok(()) +} + +pub fn convert_to_isp_payload(input: &mut [u8], part: Part) -> Result<(), PayloadConversionError> { + if input[0] != 0x02 { + return Err(PayloadConversionError::LJMPNotFoundError { addr: 0 }); + } + + let ljmp_addr = part.firmware_size - 5; + if input[ljmp_addr] != 0x02 { + return Err(PayloadConversionError::LJMPNotFoundError { addr: 0x0000 }); + } + + let main_fw_address = + u16::from_be_bytes(input[ljmp_addr + 1..ljmp_addr + 3].try_into().unwrap()); + if main_fw_address > 0xefff { + return Err(PayloadConversionError::UnexpectedAddressError { + source_addr: (ljmp_addr + 1) as u16, + target_addr: main_fw_address, + }); + } + + input[1..3].copy_from_slice(&main_fw_address.to_be_bytes()); + input[ljmp_addr..ljmp_addr + 3].fill(0x00); + + Ok(()) +} + #[test] fn test_verify_success() { assert!(verify(&vec![1, 2, 3, 4], &vec![1, 2, 3, 4]).is_ok()); @@ -60,3 +120,34 @@ fn test_verify_error_byte_mismatch() { }) ); } + +#[test] +fn test_convert_to_jtag_payload() { + let part = PART_BASE_SH68F90; + let mut firmware: [u8; 65536] = [0; 65536]; + firmware[0] = 0x02; + firmware[1] = 0x00; + firmware[2] = 0x66; + + convert_to_jtag_payload(&mut firmware, part).unwrap(); + + assert_eq!(firmware[0..3], [0x02, 0xf0, 0x00]); + assert_eq!(firmware[0xeffb..0xeffe], [0x02, 0x00, 0x66]); +} + +#[test] +fn test_convert_to_isp_payload() { + let part = PART_BASE_SH68F90; + let mut firmware: [u8; 65536] = [0; 65536]; + firmware[0] = 0x02; + firmware[1] = 0xf0; + firmware[2] = 0x00; + firmware[0xeffb] = 0x02; + firmware[0xeffc] = 0x00; + firmware[0xeffd] = 0x66; + + convert_to_isp_payload(&mut firmware, part).unwrap(); + + assert_eq!(firmware[0..3], [0x02, 0x00, 0x66]); + assert_eq!(firmware[0xeffb..0xeffe], [0x00, 0x00, 0x00]); +}