diff --git a/sw/deployer/Cargo.lock b/sw/deployer/Cargo.lock index c09ffc7..29aa09d 100644 --- a/sw/deployer/Cargo.lock +++ b/sw/deployer/Cargo.lock @@ -696,6 +696,12 @@ dependencies = [ "miniz_oxide", ] +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + [[package]] name = "proc-macro-hack" version = "0.5.20+deprecated" @@ -729,6 +735,36 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + [[package]] name = "rayon" version = "1.9.0" @@ -810,6 +846,7 @@ dependencies = [ "include-flate", "md5", "panic-message", + "rand", "rust-ini", "serial2", "serialport", diff --git a/sw/deployer/Cargo.toml b/sw/deployer/Cargo.toml index 749b0af..7eac40d 100644 --- a/sw/deployer/Cargo.toml +++ b/sw/deployer/Cargo.toml @@ -19,6 +19,7 @@ image = "0.24.5" include-flate = { version = "0.2.0", features = ["stable"] } md5 = "0.7.0" panic-message = "0.3.0" +rand = "0.8.5" rust-ini = "0.18.0" serial2 = "0.2.20" serialport = "4.3.0" diff --git a/sw/deployer/src/main.rs b/sw/deployer/src/main.rs index 3de91a0..c32284f 100644 --- a/sw/deployer/src/main.rs +++ b/sw/deployer/src/main.rs @@ -75,6 +75,9 @@ enum Commands { command: FirmwareCommands, }, + /// Test SC64 hardware + Test, + /// Expose SC64 device over network Server(ServerArgs), } @@ -333,6 +336,7 @@ fn handle_command(command: &Commands, port: Option, remote: Option handle_reset_command(connection), Commands::Set { command } => handle_set_command(connection, command), Commands::Firmware { command } => handle_firmware_command(connection, command), + Commands::Test => handle_test_command(connection), Commands::Server(args) => handle_server_command(connection, args), }; match result { @@ -781,7 +785,7 @@ fn handle_set_command(connection: Connection, command: &SetCommands) -> Result<( sc64.set_datetime(datetime)?; println!( "SC64 RTC datetime synchronized to: {}", - datetime.format("%Y-%m-%d %H:%M:%S %Z").to_string().green() + datetime.format("%Y-%m-%d %H:%M:%S").to_string().green() ); } @@ -877,6 +881,96 @@ fn handle_firmware_command( } } +fn handle_test_command(connection: Connection) -> Result<(), sc64::Error> { + let mut sc64 = init_sc64(connection, false)?; + + println!("{}: SDRAM", "[SC64 Tests]".bold()); + + let sdram_tests = [ + (sc64::MemoryTestType::OwnAddress, None), + (sc64::MemoryTestType::AllZeros, None), + (sc64::MemoryTestType::AllOnes, None), + (sc64::MemoryTestType::Random, None), + (sc64::MemoryTestType::Random, None), + (sc64::MemoryTestType::Random, None), + (sc64::MemoryTestType::AllZeros, Some(60)), + (sc64::MemoryTestType::AllOnes, Some(60)), + (sc64::MemoryTestType::Pattern(0x00010001), None), + (sc64::MemoryTestType::Pattern(0xFFFEFFFE), None), + (sc64::MemoryTestType::Pattern(0x00020002), None), + (sc64::MemoryTestType::Pattern(0xFFFDFFFD), None), + (sc64::MemoryTestType::Pattern(0x00040004), None), + (sc64::MemoryTestType::Pattern(0xFFFBFFFB), None), + (sc64::MemoryTestType::Pattern(0x00080008), None), + (sc64::MemoryTestType::Pattern(0xFFF7FFF7), None), + (sc64::MemoryTestType::Pattern(0x00100010), None), + (sc64::MemoryTestType::Pattern(0xFFEFFFEF), None), + (sc64::MemoryTestType::Pattern(0x00200020), None), + (sc64::MemoryTestType::Pattern(0xFFDFFFDF), None), + (sc64::MemoryTestType::Pattern(0x00400040), None), + (sc64::MemoryTestType::Pattern(0xFFBFFFBF), None), + (sc64::MemoryTestType::Pattern(0x00800080), None), + (sc64::MemoryTestType::Pattern(0xFF7FFF7F), None), + (sc64::MemoryTestType::Pattern(0x01000100), None), + (sc64::MemoryTestType::Pattern(0xFEFFFEFF), None), + (sc64::MemoryTestType::Pattern(0x02000200), None), + (sc64::MemoryTestType::Pattern(0xFDFFFDFF), None), + (sc64::MemoryTestType::Pattern(0x04000400), None), + (sc64::MemoryTestType::Pattern(0xFBFFFBFF), None), + (sc64::MemoryTestType::Pattern(0x08000800), None), + (sc64::MemoryTestType::Pattern(0xF7FFF7FF), None), + (sc64::MemoryTestType::Pattern(0x10001000), None), + (sc64::MemoryTestType::Pattern(0xEFFFEFFF), None), + (sc64::MemoryTestType::Pattern(0x20002000), None), + (sc64::MemoryTestType::Pattern(0xDFFFDFFF), None), + (sc64::MemoryTestType::Pattern(0x40004000), None), + (sc64::MemoryTestType::Pattern(0xBFFFBFFF), None), + (sc64::MemoryTestType::Pattern(0x80008000), None), + (sc64::MemoryTestType::Pattern(0x7FFF7FFF), None), + (sc64::MemoryTestType::AllZeros, Some(300)), + (sc64::MemoryTestType::AllOnes, Some(300)), + ]; + let sdram_tests_count = sdram_tests.len(); + + let mut sdram_tests_failed = false; + + for (i, (test_type, fade)) in sdram_tests.into_iter().enumerate() { + let fadeout_text = if let Some(fade) = fade { + format!(", fadeout {fade} seconds") + } else { + "".to_string() + }; + print!( + " ({} / {sdram_tests_count}) Testing {test_type}{fadeout_text}... ", + i + 1 + ); + stdout().flush().unwrap(); + + let result = sc64.test_sdram(test_type, fade)?; + + if let Some((address, (written, read))) = result.first_error { + sdram_tests_failed = true; + println!("{}", "error!".bright_red()); + println!(" Found a mismatch at address 0x{address:08X}",); + println!(" 0x{written:08X} (W) != 0x{read:08X} (R)"); + println!(" Total errors found: {}", result.all_errors.len()); + } else { + println!("{}", "ok".bright_green()); + } + } + + if sdram_tests_failed { + println!( + "{}", + "Some SDRAM tests failed, SDRAM chip might be defective".bright_red() + ); + } else { + println!("{}", "All SDRAM tests passed without error".bright_green()); + } + + Ok(()) +} + fn handle_server_command(connection: Connection, args: &ServerArgs) -> Result<(), sc64::Error> { let port = if let Connection::Local(port) = connection { port diff --git a/sw/deployer/src/sc64/mod.rs b/sw/deployer/src/sc64/mod.rs index b0179f3..ff5c37b 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, DiagnosticData, DiskPacket, DiskPacketKind, FpgaDebugData, SaveType, - SaveWriteback, Switch, TvType, + DebugPacket, DiagnosticData, DiskPacket, DiskPacketKind, FpgaDebugData, MemoryTestResult, + MemoryTestType, SaveType, SaveWriteback, Switch, TvType, }, }; @@ -26,10 +26,12 @@ use self::{ }, }; use chrono::{DateTime, Local}; +use rand::Rng; use std::{ + cmp::min, io::{Read, Seek, Write}, - time::Instant, - {cmp::min, time::Duration}, + thread::sleep, + time::{Duration, Instant}, }; pub struct SC64 { @@ -748,6 +750,58 @@ impl SC64 { } } + pub fn test_sdram( + &mut self, + test_type: MemoryTestType, + fade: Option, + ) -> Result { + let item_size = std::mem::size_of::(); + let mut test_data = vec![0u32; SDRAM_LENGTH / item_size]; + + match test_type { + MemoryTestType::OwnAddress => { + for (index, item) in test_data.iter_mut().enumerate() { + *item = (index * item_size) as u32; + } + } + MemoryTestType::AllZeros => test_data.fill(0x00000000u32), + MemoryTestType::AllOnes => test_data.fill(0xFFFFFFFFu32), + MemoryTestType::Pattern(pattern) => test_data.fill(pattern), + MemoryTestType::Random => rand::thread_rng().fill(&mut test_data[..]), + }; + + let raw_test_data: Vec = test_data.iter().flat_map(|v| v.to_be_bytes()).collect(); + self.command_memory_write(SDRAM_ADDRESS, &raw_test_data)?; + + if let Some(fade) = fade { + sleep(Duration::from_secs(fade)); + } + + let raw_check_data = self.command_memory_read(SDRAM_ADDRESS, SDRAM_LENGTH)?; + let check_data = raw_check_data + .chunks(4) + .map(|a| u32::from_be_bytes(a[0..4].try_into().unwrap())); + + let all_errors: Vec<(usize, (u32, u32))> = test_data + .into_iter() + .zip(check_data) + .enumerate() + .filter(|(_, (a, b))| a != b) + .map(|(i, (a, b))| (i * item_size, (a, b))) + .collect(); + + let first_error = if all_errors.len() > 0 { + Some(all_errors.get(0).copied().unwrap()) + } else { + None + }; + + return Ok(MemoryTestResult { + first_error, + all_errors, + }); + } + fn memory_read_chunked( &mut self, writer: &mut dyn Write, diff --git a/sw/deployer/src/sc64/types.rs b/sw/deployer/src/sc64/types.rs index 331df85..de4419e 100644 --- a/sw/deployer/src/sc64/types.rs +++ b/sw/deployer/src/sc64/types.rs @@ -983,6 +983,33 @@ impl Display for DiagnosticData { } } +pub enum MemoryTestType { + OwnAddress, + AllZeros, + AllOnes, + Pattern(u32), + Random, +} + +pub struct MemoryTestResult { + pub first_error: Option<(usize, (u32, u32))>, + pub all_errors: Vec<(usize, (u32, u32))>, +} + +impl Display for MemoryTestType { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + MemoryTestType::OwnAddress => f.write_str("Own address"), + MemoryTestType::AllZeros => f.write_str("All zeros"), + MemoryTestType::AllOnes => f.write_str("All ones"), + MemoryTestType::Random => f.write_str("Random"), + MemoryTestType::Pattern(pattern) => { + f.write_fmt(format_args!("Pattern 0x{pattern:08X}")) + } + } + } +} + macro_rules! get_config { ($sc64:ident, $config:ident) => {{ if let Config::$config(value) = $sc64.command_config_get(ConfigId::$config)? {