diff --git a/protocol.md b/protocol.md index e8f0b96..dba30b3 100644 --- a/protocol.md +++ b/protocol.md @@ -110,8 +110,31 @@ oOp_u8 = 0x02 when failed - 0x02 Connect chip - 0x03 ? stage after connect chip and read riscvchip, for riscvchip 1 - 0x04 get rom ram split, for riscvchip 3, 5, 6, 9 +- 0x17 Get MCU Memory Split (SRAM_CODE_MODE) + - Request: `81 0d 01 17` + - Response payload: one byte mode value +- 0x18 Set MCU Memory Split (SRAM_CODE_MODE) + - Request: `81 0d 02 18 ` + - Response payload: `18` (ACK) - 0xff End process +#### SRAM_CODE_MODE mapping (CH32V2x/V3x USER[7:5]) + +Based on CH32FV2x_V3xRM and USB captures from WCH official tool. + +- `00x` (`0x00` or `0x01`) -> CODE-192KB + RAM-128KB +- `01x` (`0x02` or `0x03`) -> CODE-224KB + RAM-96KB +- `10x` (`0x04` or `0x05`) -> CODE-256KB + RAM-64KB +- `110` (`0x06`) -> CODE-128KB + RAM-192KB +- `111` (`0x07`) -> CODE-288KB + RAM-32KB + +Observed set/read pairs in captures (verified on CH32V30X): + +- set `0x18 0x01` -> read `0x17` returns `0x01` (192/128) +- set `0x18 0x03` -> read `0x17` returns `0x03` (224/96) +- set `0x18 0x05` -> read `0x17` returns `0x05` (256/64) +- set `0x18 0x07` -> read `0x17` returns `0x07` (288/32) + ### 0x0e - 0x01 Disable debug for riscvchip 2, 3 diff --git a/src/commands/control.rs b/src/commands/control.rs index d2987ed..cdd863d 100644 --- a/src/commands/control.rs +++ b/src/commands/control.rs @@ -135,9 +135,22 @@ impl Command for GetChipRomRamSplit { } } +/// Get MCU Memory Split (SRAM_CODE_MODE) +/// Observed in official tool as command sequence: 81 0d 01 17 +/// Response payload is one byte mode value (USER[7:5]-related). +#[derive(Debug)] +pub struct GetMcuMemorySplit; +impl Command for GetMcuMemorySplit { + type Response = u8; + const COMMAND_ID: u8 = 0x0d; + fn payload(&self) -> Vec { + vec![0x17] + } +} + /// 0, 1, 2, 3 #[derive(Debug)] -pub struct SetChipRomRamSplit(u8); +pub struct SetChipRomRamSplit(pub u8); impl Command for SetChipRomRamSplit { type Response = (); const COMMAND_ID: u8 = 0x0d; @@ -146,6 +159,19 @@ impl Command for SetChipRomRamSplit { } } +/// Set MCU Memory Split (SRAM_CODE_MODE) +/// Observed in official tool as command sequence: 81 0d 02 18 +/// Response payload is one byte: 0x18 (subcommand echo/ack). +#[derive(Debug)] +pub struct SetMcuMemorySplit(pub u8); +impl Command for SetMcuMemorySplit { + type Response = u8; + const COMMAND_ID: u8 = 0x0d; + fn payload(&self) -> Vec { + vec![0x18, self.0] + } +} + // ?? close out /// Detach Chip, (0x0d, 0xff) #[derive(Debug)] diff --git a/src/lib.rs b/src/lib.rs index 598f85e..f55b6ac 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -209,6 +209,18 @@ impl RiscvChip { ) } + /// Support observed MCU Memory Split commands (0x0d/0x17, 0x0d/0x18) + /// based on verified CH32V30X captures. + pub(crate) fn support_mcu_mem_split_cmds(&self) -> bool { + matches!(self, RiscvChip::CH32V30X) + } + + /// Support legacy RAM/ROM split commands (0x0d/0x04, 0x0d/0x05) + /// used for CH32V20X memory split configuration. + pub(crate) fn support_mcu_mem_split_legacy_cmds(&self) -> bool { + matches!(self, RiscvChip::CH32V20X) + } + /// Support config registers, query info(UID, etc.) pub fn support_query_info(&self) -> bool { !matches!( diff --git a/src/main.rs b/src/main.rs index 14a79e7..2270c69 100644 --- a/src/main.rs +++ b/src/main.rs @@ -156,6 +156,14 @@ enum Commands { /// SDI virtual serial port, #[command(subcommand)] SdiPrint(SdiPrint), + #[command( + after_help = "Quick mode map:\n CH32V30X:\n 192-128 => 0x01 (also 0x00)\n 224-96 => 0x03 (also 0x02)\n 256-64 => 0x05 (also 0x04)\n 128-192 => 0x06\n 288-32 => 0x07\n\n CH32V20X:\n 128-64 => 0x00 (also 0x01 by bit-pattern)\n 144-48 => 0x02 (also 0x03 by bit-pattern)\n 160-32 => 0x04..0x07 (legacy index commonly 0x02)\n\nFor full details run: wlink help mem-split set" + )] + /// Read or set MCU Memory Split (SRAM_CODE_MODE) + MemSplit { + #[command(subcommand)] + cmd: MemSplit, + }, Dev {}, } @@ -173,6 +181,101 @@ impl SdiPrint { } } +#[derive(clap::Subcommand, PartialEq, Clone, Debug)] +pub enum MemSplit { + /// Read current SRAM_CODE_MODE + Get, + /// Set SRAM_CODE_MODE using hex value or compact profile alias + Set { + /// + /// Accepted values are chip-dependent: + /// - CH32V30X: 192-128, 224-96, 256-64, 128-192, 288-32 + /// - CH32V20X: 128-64, 144-48, 160-32 + /// + /// Raw values are also accepted: 0x00..0x07 + #[arg( + value_name = "MODE", + long_help = "SRAM_CODE_MODE value.\n\nAliases are interpreted using the detected chip family.\n\nProtocol path by chip:\n - CH32V30X read/write: 0x0d/0x17 and 0x0d/0x18\n - CH32V20X read/write: legacy 0x0d/0x04 and 0x0d/0x05\n\nCH32V30X profiles:\n - 192-128 / p0 / 192 -> writes 0x01 (equivalent read: 0x00 or 0x01)\n - 224-96 / p1 / 224 -> writes 0x03 (equivalent read: 0x02 or 0x03)\n - 256-64 / p2 / 256 -> writes 0x05 (equivalent read: 0x04 or 0x05)\n - 128-192 / p3 / 128 -> writes 0x06\n - 288-32 / p4 / 288 -> writes 0x07\n\nCH32V20X profiles:\n - 128-64 / p0 / 128 -> writes 0x00 (equivalent bit-pattern: 0x01)\n - 144-48 / p1 / 144 -> writes 0x02 (equivalent bit-pattern: 0x03)\n - 160-32 / p2 / 160 -> writes 0x04 (equivalent bit-patterns: 0x05..0x07; legacy split index is often read back as 0x02)\n\nRaw input:\n - Hex/raw 0x00..0x07 is always accepted.\n\nExamples:\n - wlink mem-split set 224-96\n - wlink mem-split set p1\n - wlink mem-split set 0x03" + )] + mode: String, + /// Skip read-back verify after write + #[arg(long, default_value = "false")] + no_verify: bool, + }, +} + +fn decode_mcu_mem_split_mode_v30x(mode: u8) -> &'static str { + match mode & 0x07 { + 0b000 | 0b001 => "CODE-192KB + RAM-128KB", + 0b010 | 0b011 => "CODE-224KB + RAM-96KB", + 0b100 | 0b101 => "CODE-256KB + RAM-64KB", + 0b110 => "CODE-128KB + RAM-192KB", + 0b111 => "CODE-288KB + RAM-32KB", + _ => "UNKNOWN", + } +} + +fn decode_mcu_mem_split_mode_v20x(mode: u8) -> &'static str { + if mode <= 2 { + return match mode { + 0 => "CODE-128KB + RAM-64KB", + 1 => "CODE-144KB + RAM-48KB", + 2 => "CODE-160KB + RAM-32KB", + _ => "UNKNOWN", + }; + } + + match mode & 0x07 { + 0b000 | 0b001 => "CODE-128KB + RAM-64KB", + 0b010 | 0b011 => "CODE-144KB + RAM-48KB", + 0b100..=0b111 => "CODE-160KB + RAM-32KB", + _ => "UNKNOWN", + } +} + +fn decode_mcu_mem_split_mode(chip: RiscvChip, mode: u8) -> &'static str { + match chip { + RiscvChip::CH32V20X => decode_mcu_mem_split_mode_v20x(mode), + _ => decode_mcu_mem_split_mode_v30x(mode), + } +} + +fn mcu_mem_split_profile_id_v30x(mode: u8) -> u8 { + match mode & 0x07 { + 0b000 | 0b001 => 0, + 0b010 | 0b011 => 1, + 0b100 | 0b101 => 2, + 0b110 => 3, + 0b111 => 4, + _ => 0xff, + } +} + +fn mcu_mem_split_profile_id_v20x(mode: u8) -> u8 { + // Legacy read often returns 0..2 index; raw bit patterns can still appear. + if mode <= 2 { + return mode; + } + + match mode & 0x07 { + 0b000 | 0b001 => 0, + 0b010 | 0b011 => 1, + 0b100..=0b111 => 2, + _ => 0xff, + } +} + +fn mcu_mem_split_profile_id(chip: RiscvChip, mode: u8) -> u8 { + match chip { + RiscvChip::CH32V20X => mcu_mem_split_profile_id_v20x(mode), + _ => mcu_mem_split_profile_id_v30x(mode), + } +} + +fn is_same_mcu_mem_split_profile(chip: RiscvChip, expected: u8, observed: u8) -> bool { + mcu_mem_split_profile_id(chip, expected) == mcu_mem_split_profile_id(chip, observed) +} + fn main() -> Result<()> { let cli = Cli::parse(); @@ -427,6 +530,43 @@ fn main() -> Result<()> { sess.set_sdi_print_enabled(false)?; } }, + Commands::MemSplit { cmd } => match cmd { + MemSplit::Get => { + let mode = sess.get_mcu_mem_split()?; + let profile = decode_mcu_mem_split_mode(sess.chip_family, mode); + log::info!( + "MCU Memory Split: 0x{:02x} ({})", + mode, + profile + ); + println!("0x{mode:02x} ({profile})"); + } + MemSplit::Set { mode, no_verify } => { + let mode = parse_mem_split_mode_for_chip(&mode, sess.chip_family) + .map_err(wlink::Error::Custom)?; + let profile = decode_mcu_mem_split_mode(sess.chip_family, mode); + log::info!("Set MCU Memory Split to 0x{:02x} ({})", mode, profile); + sess.set_mcu_mem_split(mode)?; + + if !no_verify { + let read_back = sess.get_mcu_mem_split()?; + let read_profile = + decode_mcu_mem_split_mode(sess.chip_family, read_back); + if !is_same_mcu_mem_split_profile(sess.chip_family, mode, read_back) + { + return Err(wlink::Error::Custom(format!( + "MCU Memory Split verify failed: wrote 0x{mode:02x} ({profile}), read back 0x{read_back:02x} ({read_profile})" + )) + .into()); + } + log::info!( + "MCU Memory Split verified: 0x{:02x} ({})", + read_back, + read_profile + ); + } + } + }, _ => unreachable!("unimplemented command"), } if will_detach { @@ -451,3 +591,40 @@ pub fn parse_number(s: &str) -> std::result::Result { Ok(s.parse().expect("must be a number")) } } + +pub fn parse_mem_split_mode_for_chip( + s: &str, + chip: RiscvChip, +) -> std::result::Result { + let norm = s.trim().to_lowercase().replace('_', "-"); + + let canonical = match chip { + RiscvChip::CH32V20X => match norm.as_str() { + "128-64" | "128/64" | "128:64" | "128" | "p0" => Some(0x00), + "144-48" | "144/48" | "144:48" | "144" | "p1" => Some(0x02), + "160-32" | "160/32" | "160:32" | "160" | "p2" => Some(0x04), + _ => None, + }, + _ => match norm.as_str() { + // profile aliases (CODE-RAM) + "192-128" | "192/128" | "192:128" | "192" | "p0" => Some(0x01), + "224-96" | "224/96" | "224:96" | "224" | "p1" => Some(0x03), + "256-64" | "256/64" | "256:64" | "256" | "p2" => Some(0x05), + "128-192" | "128/192" | "128:192" | "128" | "p3" => Some(0x06), + "288-32" | "288/32" | "288:32" | "288" | "p4" => Some(0x07), + _ => None, + }, + }; + + if let Some(mode) = canonical { + return Ok(mode); + } + + let raw = parse_number(s)?; + let raw = u8::try_from(raw).map_err(|_| "mode must be in range 0x00..0x07".to_string())?; + if raw > 0x07 { + return Err("mode must be in range 0x00..0x07".to_string()); + } + + Ok(raw) +} diff --git a/src/operations.rs b/src/operations.rs index 7137a97..9d5cb60 100644 --- a/src/operations.rs +++ b/src/operations.rs @@ -16,6 +16,56 @@ pub struct ProbeSession { pub speed: Speed, } +fn decode_sram_code_mode_v2_v3(mode: u8) -> &'static str { + match mode & 0x07 { + 0b000 | 0b001 => "CODE-192KB + RAM-128KB", + 0b010 | 0b011 => "CODE-224KB + RAM-96KB", + 0b100 | 0b101 => "CODE-256KB + RAM-64KB", + 0b110 => "CODE-128KB + RAM-192KB", + 0b111 => "CODE-288KB + RAM-32KB", + _ => "UNKNOWN", + } +} + +fn decode_sram_code_mode_v20x(mode: u8) -> &'static str { + // Legacy command 0x0d/0x04 often returns compact index 0..2 on CH32V20X. + if mode <= 2 { + return match mode { + 0 => "CODE-128KB + RAM-64KB", + 1 => "CODE-144KB + RAM-48KB", + 2 => "CODE-160KB + RAM-32KB", + _ => "UNKNOWN", + }; + } + + match mode & 0x07 { + 0b000 | 0b001 => "CODE-128KB + RAM-64KB", + 0b010 | 0b011 => "CODE-144KB + RAM-48KB", + 0b100..=0b111 => "CODE-160KB + RAM-32KB", + _ => "UNKNOWN", + } +} + +fn decode_sram_code_mode(chip: RiscvChip, mode: u8) -> &'static str { + match chip { + RiscvChip::CH32V20X => decode_sram_code_mode_v20x(mode), + _ => decode_sram_code_mode_v2_v3(mode), + } +} + +fn v20x_legacy_split_index_from_mode(mode: u8) -> Option { + if mode <= 2 { + return Some(mode); + } + + Some(match mode & 0x07 { + 0b000 | 0b001 => 0, + 0b010 | 0b011 => 1, + 0b100..=0b111 => 2, + _ => return None, + }) +} + impl ProbeSession { /// Attach probe to target chip, start a probe session pub fn attach(probe: WchLink, expected_chip: Option, speed: Speed) -> Result { @@ -119,10 +169,13 @@ impl ProbeSession { } } if self.chip_family.support_ram_rom_mode() { - let sram_code_mode = self - .probe - .send_command(commands::control::GetChipRomRamSplit)?; - log::debug!("SRAM CODE split mode: {}", sram_code_mode); + let sram_code_mode = self.get_mcu_mem_split()?; + log::debug!( + "SRAM CODE split mode: {} (0x{:02x}, {})", + sram_code_mode, + sram_code_mode, + decode_sram_code_mode(self.chip_family, sram_code_mode) + ); } /* if detailed { @@ -132,6 +185,63 @@ impl ProbeSession { Ok(()) } + /// Read MCU memory split mode. + /// + /// For CH32V30X, this uses observed official-tool command `0x0d/0x17`. + /// For other chips that support RAM/ROM mode, it falls back to legacy `0x0d/0x04`. + pub fn get_mcu_mem_split(&mut self) -> Result { + if self.chip_family.support_mcu_mem_split_cmds() { + match self.probe.send_command(commands::control::GetMcuMemorySplit) { + Ok(mode) => return Ok(mode), + Err(err) => { + log::debug!( + "GetMcuMemorySplit (0x0d/0x17) failed: {err:?}; trying legacy 0x0d/0x04" + ); + } + } + } + + self.probe + .send_command(commands::control::GetChipRomRamSplit) + } + + /// Set MCU memory split mode via observed command `0x0d/0x18`. + /// + /// Uses chip-specific protocol: + /// - CH32V30X: `0x0d/0x18` + /// - CH32V20X: legacy `0x0d/0x05` + pub fn set_mcu_mem_split(&mut self, mode: u8) -> Result<()> { + if self.chip_family.support_mcu_mem_split_cmds() { + let ack = self + .probe + .send_command(commands::control::SetMcuMemorySplit(mode))?; + + if ack != 0x18 { + return Err(Error::Custom(format!( + "Unexpected MCU Memory Split ACK: 0x{ack:02x}" + ))); + } + + return Ok(()); + } + + if self.chip_family.support_mcu_mem_split_legacy_cmds() { + let split_idx = v20x_legacy_split_index_from_mode(mode).ok_or_else(|| { + Error::Custom(format!( + "Invalid CH32V20X memory split mode: 0x{mode:02x} (expected profile index 0..2 or raw 0x00..0x07)" + )) + })?; + self.probe + .send_command(commands::control::SetChipRomRamSplit(split_idx))?; + return Ok(()); + } + + Err(Error::Custom(format!( + "MCU Memory Split set is enabled for CH32V30X and CH32V20X only, attached: {:?}", + self.chip_family + ))) + } + pub fn unprotect_flash(&mut self) -> Result<()> { // HACK: requires a fresh attach self.reattach_chip()?;