From 2b5f95ca49f271252215a82bca3f605a3aaa2b40 Mon Sep 17 00:00:00 2001 From: Waldemar Pawlaszek Date: Mon, 25 May 2026 23:38:42 +0200 Subject: [PATCH 1/2] Add CH32V30X memory-assign CLI --- protocol.md | 23 ++++++++ src/commands/control.rs | 26 +++++++++ src/lib.rs | 6 ++ src/main.rs | 122 ++++++++++++++++++++++++++++++++++++++++ src/operations.rs | 66 ++++++++++++++++++++-- 5 files changed, 239 insertions(+), 4 deletions(-) diff --git a/protocol.md b/protocol.md index e8f0b96..67c1a04 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 Assign (SRAM_CODE_MODE) + - Request: `81 0d 01 17` + - Response payload: one byte mode value +- 0x18 Set MCU Memory Assign (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..bcbf86b 100644 --- a/src/commands/control.rs +++ b/src/commands/control.rs @@ -135,6 +135,19 @@ impl Command for GetChipRomRamSplit { } } +/// Get MCU Memory Assign (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 GetMcuMemoryAssign; +impl Command for GetMcuMemoryAssign { + type Response = u8; + const COMMAND_ID: u8 = 0x0d; + fn payload(&self) -> Vec { + vec![0x17] + } +} + /// 0, 1, 2, 3 #[derive(Debug)] pub struct SetChipRomRamSplit(u8); @@ -146,6 +159,19 @@ impl Command for SetChipRomRamSplit { } } +/// Set MCU Memory Assign (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 SetMcuMemoryAssign(pub u8); +impl Command for SetMcuMemoryAssign { + 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..41fdef7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -209,6 +209,12 @@ impl RiscvChip { ) } + /// Support observed MCU Memory Assign commands (0x0d/0x17, 0x0d/0x18) + /// based on verified CH32V30X captures. + pub(crate) fn support_mcu_memory_assign_cmds(&self) -> bool { + matches!(self, RiscvChip::CH32V30X) + } + /// 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..852aead 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 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\nFor full details run: wlink help memory-assign set" + )] + /// Read or set MCU Memory Assign (SRAM_CODE_MODE) + MemoryAssign { + #[command(subcommand)] + cmd: MemoryAssign, + }, Dev {}, } @@ -173,6 +181,61 @@ impl SdiPrint { } } +#[derive(clap::Subcommand, PartialEq, Clone, Copy, Debug)] +pub enum MemoryAssign { + /// Read current SRAM_CODE_MODE + Get, + /// Set SRAM_CODE_MODE using hex value or compact profile alias + Set { + /// + /// Accepted values: + /// - 192-128 => 0x01 (also accepts 0x00) + /// - 224-96 => 0x03 (also accepts 0x02) + /// - 256-64 => 0x05 (also accepts 0x04) + /// - 128-192 => 0x06 + /// - 288-32 => 0x07 + /// + /// Also accepted aliases: + /// - 192, 224, 256, 128, 288 + /// - p0, p1, p2, p3, p4 + #[arg( + value_parser = parse_memory_assign_mode, + value_name = "MODE", + long_help = "SRAM_CODE_MODE value.\n\nAccepted formats:\n - Hex/raw: 0x00..0x07\n - Profile aliases: 192-128, 224-96, 256-64, 128-192, 288-32\n - Compact aliases: 192, 224, 256, 128, 288, p0..p4\n\nProfile to hex mapping:\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" + )] + mode: u8, + /// Skip read-back verify after write + #[arg(long, default_value = "false")] + no_verify: bool, + }, +} + +fn decode_mcu_memory_assign_mode(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 mcu_memory_assign_profile_id(mode: u8) -> u8 { + match mode & 0x07 { + 0b000 | 0b001 => 0, + 0b010 | 0b011 => 1, + 0b100 | 0b101 => 2, + 0b110 => 3, + 0b111 => 4, + _ => 0xff, + } +} + +fn is_same_mcu_memory_assign_profile(expected: u8, observed: u8) -> bool { + mcu_memory_assign_profile_id(expected) == mcu_memory_assign_profile_id(observed) +} + fn main() -> Result<()> { let cli = Cli::parse(); @@ -427,6 +490,39 @@ fn main() -> Result<()> { sess.set_sdi_print_enabled(false)?; } }, + Commands::MemoryAssign { cmd } => match cmd { + MemoryAssign::Get => { + let mode = sess.get_mcu_memory_assign()?; + let profile = decode_mcu_memory_assign_mode(mode); + log::info!( + "MCU Memory Assign: 0x{:02x} ({})", + mode, + profile + ); + println!("0x{mode:02x} ({profile})"); + } + MemoryAssign::Set { mode, no_verify } => { + let profile = decode_mcu_memory_assign_mode(mode); + log::info!("Set MCU Memory Assign to 0x{:02x} ({})", mode, profile); + sess.set_mcu_memory_assign(mode)?; + + if !no_verify { + let read_back = sess.get_mcu_memory_assign()?; + let read_profile = decode_mcu_memory_assign_mode(read_back); + if !is_same_mcu_memory_assign_profile(mode, read_back) { + return Err(wlink::Error::Custom(format!( + "MCU Memory Assign verify failed: wrote 0x{mode:02x} ({profile}), read back 0x{read_back:02x} ({read_profile})" + )) + .into()); + } + log::info!( + "MCU Memory Assign verified: 0x{:02x} ({})", + read_back, + read_profile + ); + } + } + }, _ => unreachable!("unimplemented command"), } if will_detach { @@ -451,3 +547,29 @@ pub fn parse_number(s: &str) -> std::result::Result { Ok(s.parse().expect("must be a number")) } } + +pub fn parse_memory_assign_mode(s: &str) -> std::result::Result { + let norm = s.trim().to_lowercase().replace('_', "-"); + + let canonical = 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..d12b393 100644 --- a/src/operations.rs +++ b/src/operations.rs @@ -16,6 +16,17 @@ 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", + } +} + impl ProbeSession { /// Attach probe to target chip, start a probe session pub fn attach(probe: WchLink, expected_chip: Option, speed: Speed) -> Result { @@ -119,10 +130,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_memory_assign()?; + log::debug!( + "SRAM CODE split mode: {} (0x{:02x}, {})", + sram_code_mode, + sram_code_mode, + decode_sram_code_mode_v2_v3(sram_code_mode) + ); } /* if detailed { @@ -132,6 +146,50 @@ impl ProbeSession { Ok(()) } + /// Read MCU memory assignment 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_memory_assign(&mut self) -> Result { + if self.chip_family.support_mcu_memory_assign_cmds() { + match self.probe.send_command(commands::control::GetMcuMemoryAssign) { + Ok(mode) => return Ok(mode), + Err(err) => { + log::debug!( + "GetMcuMemoryAssign (0x0d/0x17) failed: {err:?}; trying legacy 0x0d/0x04" + ); + } + } + } + + self.probe + .send_command(commands::control::GetChipRomRamSplit) + } + + /// Set MCU memory assignment mode via observed command `0x0d/0x18`. + /// + /// Safety gate: currently enabled only for CH32V30X. + pub fn set_mcu_memory_assign(&mut self, mode: u8) -> Result<()> { + if !self.chip_family.support_mcu_memory_assign_cmds() { + return Err(Error::Custom(format!( + "MCU Memory Assign set (0x0d/0x18) is enabled only for CH32V30X, attached: {:?}", + self.chip_family + ))); + } + + let ack = self + .probe + .send_command(commands::control::SetMcuMemoryAssign(mode))?; + + if ack != 0x18 { + return Err(Error::Custom(format!( + "Unexpected MCU Memory Assign ACK: 0x{ack:02x}" + ))); + } + + Ok(()) + } + pub fn unprotect_flash(&mut self) -> Result<()> { // HACK: requires a fresh attach self.reattach_chip()?; From 40c587c2ad369e11ab021b9f8eab25c092434cbf Mon Sep 17 00:00:00 2001 From: Waldemar Pawlaszek Date: Tue, 26 May 2026 12:05:49 +0200 Subject: [PATCH 2/2] Add CH32V20X mem-split support, rename memory-assign CLI, and update docs/help. --- protocol.md | 4 +- src/commands/control.rs | 14 ++-- src/lib.rs | 10 ++- src/main.rs | 145 +++++++++++++++++++++++++++------------- src/operations.rs | 100 ++++++++++++++++++++------- 5 files changed, 193 insertions(+), 80 deletions(-) diff --git a/protocol.md b/protocol.md index 67c1a04..dba30b3 100644 --- a/protocol.md +++ b/protocol.md @@ -110,10 +110,10 @@ 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 Assign (SRAM_CODE_MODE) +- 0x17 Get MCU Memory Split (SRAM_CODE_MODE) - Request: `81 0d 01 17` - Response payload: one byte mode value -- 0x18 Set MCU Memory Assign (SRAM_CODE_MODE) +- 0x18 Set MCU Memory Split (SRAM_CODE_MODE) - Request: `81 0d 02 18 ` - Response payload: `18` (ACK) - 0xff End process diff --git a/src/commands/control.rs b/src/commands/control.rs index bcbf86b..cdd863d 100644 --- a/src/commands/control.rs +++ b/src/commands/control.rs @@ -135,12 +135,12 @@ impl Command for GetChipRomRamSplit { } } -/// Get MCU Memory Assign (SRAM_CODE_MODE) +/// 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 GetMcuMemoryAssign; -impl Command for GetMcuMemoryAssign { +pub struct GetMcuMemorySplit; +impl Command for GetMcuMemorySplit { type Response = u8; const COMMAND_ID: u8 = 0x0d; fn payload(&self) -> Vec { @@ -150,7 +150,7 @@ impl Command for GetMcuMemoryAssign { /// 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; @@ -159,12 +159,12 @@ impl Command for SetChipRomRamSplit { } } -/// Set MCU Memory Assign (SRAM_CODE_MODE) +/// 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 SetMcuMemoryAssign(pub u8); -impl Command for SetMcuMemoryAssign { +pub struct SetMcuMemorySplit(pub u8); +impl Command for SetMcuMemorySplit { type Response = u8; const COMMAND_ID: u8 = 0x0d; fn payload(&self) -> Vec { diff --git a/src/lib.rs b/src/lib.rs index 41fdef7..f55b6ac 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -209,12 +209,18 @@ impl RiscvChip { ) } - /// Support observed MCU Memory Assign commands (0x0d/0x17, 0x0d/0x18) + /// Support observed MCU Memory Split commands (0x0d/0x17, 0x0d/0x18) /// based on verified CH32V30X captures. - pub(crate) fn support_mcu_memory_assign_cmds(&self) -> bool { + 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 852aead..2270c69 100644 --- a/src/main.rs +++ b/src/main.rs @@ -157,12 +157,12 @@ enum Commands { #[command(subcommand)] SdiPrint(SdiPrint), #[command( - after_help = "Quick mode map:\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\nFor full details run: wlink help memory-assign set" + 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 Assign (SRAM_CODE_MODE) - MemoryAssign { + /// Read or set MCU Memory Split (SRAM_CODE_MODE) + MemSplit { #[command(subcommand)] - cmd: MemoryAssign, + cmd: MemSplit, }, Dev {}, } @@ -181,36 +181,30 @@ impl SdiPrint { } } -#[derive(clap::Subcommand, PartialEq, Clone, Copy, Debug)] -pub enum MemoryAssign { +#[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: - /// - 192-128 => 0x01 (also accepts 0x00) - /// - 224-96 => 0x03 (also accepts 0x02) - /// - 256-64 => 0x05 (also accepts 0x04) - /// - 128-192 => 0x06 - /// - 288-32 => 0x07 + /// Accepted values are chip-dependent: + /// - CH32V30X: 192-128, 224-96, 256-64, 128-192, 288-32 + /// - CH32V20X: 128-64, 144-48, 160-32 /// - /// Also accepted aliases: - /// - 192, 224, 256, 128, 288 - /// - p0, p1, p2, p3, p4 + /// Raw values are also accepted: 0x00..0x07 #[arg( - value_parser = parse_memory_assign_mode, value_name = "MODE", - long_help = "SRAM_CODE_MODE value.\n\nAccepted formats:\n - Hex/raw: 0x00..0x07\n - Profile aliases: 192-128, 224-96, 256-64, 128-192, 288-32\n - Compact aliases: 192, 224, 256, 128, 288, p0..p4\n\nProfile to hex mapping:\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" + 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: u8, + mode: String, /// Skip read-back verify after write #[arg(long, default_value = "false")] no_verify: bool, }, } -fn decode_mcu_memory_assign_mode(mode: u8) -> &'static str { +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", @@ -221,7 +215,32 @@ fn decode_mcu_memory_assign_mode(mode: u8) -> &'static str { } } -fn mcu_memory_assign_profile_id(mode: u8) -> u8 { +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, @@ -232,8 +251,29 @@ fn mcu_memory_assign_profile_id(mode: u8) -> u8 { } } -fn is_same_mcu_memory_assign_profile(expected: u8, observed: u8) -> bool { - mcu_memory_assign_profile_id(expected) == mcu_memory_assign_profile_id(observed) +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<()> { @@ -490,33 +530,37 @@ fn main() -> Result<()> { sess.set_sdi_print_enabled(false)?; } }, - Commands::MemoryAssign { cmd } => match cmd { - MemoryAssign::Get => { - let mode = sess.get_mcu_memory_assign()?; - let profile = decode_mcu_memory_assign_mode(mode); + 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 Assign: 0x{:02x} ({})", + "MCU Memory Split: 0x{:02x} ({})", mode, profile ); println!("0x{mode:02x} ({profile})"); } - MemoryAssign::Set { mode, no_verify } => { - let profile = decode_mcu_memory_assign_mode(mode); - log::info!("Set MCU Memory Assign to 0x{:02x} ({})", mode, profile); - sess.set_mcu_memory_assign(mode)?; + 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_memory_assign()?; - let read_profile = decode_mcu_memory_assign_mode(read_back); - if !is_same_mcu_memory_assign_profile(mode, read_back) { + 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 Assign verify failed: wrote 0x{mode:02x} ({profile}), read back 0x{read_back:02x} ({read_profile})" + "MCU Memory Split verify failed: wrote 0x{mode:02x} ({profile}), read back 0x{read_back:02x} ({read_profile})" )) .into()); } log::info!( - "MCU Memory Assign verified: 0x{:02x} ({})", + "MCU Memory Split verified: 0x{:02x} ({})", read_back, read_profile ); @@ -548,17 +592,28 @@ pub fn parse_number(s: &str) -> std::result::Result { } } -pub fn parse_memory_assign_mode(s: &str) -> std::result::Result { +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 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, + 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 { diff --git a/src/operations.rs b/src/operations.rs index d12b393..9d5cb60 100644 --- a/src/operations.rs +++ b/src/operations.rs @@ -27,6 +27,45 @@ fn decode_sram_code_mode_v2_v3(mode: u8) -> &'static str { } } +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 { @@ -130,12 +169,12 @@ impl ProbeSession { } } if self.chip_family.support_ram_rom_mode() { - let sram_code_mode = self.get_mcu_memory_assign()?; + 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_v2_v3(sram_code_mode) + decode_sram_code_mode(self.chip_family, sram_code_mode) ); } /* @@ -146,17 +185,17 @@ impl ProbeSession { Ok(()) } - /// Read MCU memory assignment mode. + /// 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_memory_assign(&mut self) -> Result { - if self.chip_family.support_mcu_memory_assign_cmds() { - match self.probe.send_command(commands::control::GetMcuMemoryAssign) { + 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!( - "GetMcuMemoryAssign (0x0d/0x17) failed: {err:?}; trying legacy 0x0d/0x04" + "GetMcuMemorySplit (0x0d/0x17) failed: {err:?}; trying legacy 0x0d/0x04" ); } } @@ -166,28 +205,41 @@ impl ProbeSession { .send_command(commands::control::GetChipRomRamSplit) } - /// Set MCU memory assignment mode via observed command `0x0d/0x18`. + /// Set MCU memory split mode via observed command `0x0d/0x18`. /// - /// Safety gate: currently enabled only for CH32V30X. - pub fn set_mcu_memory_assign(&mut self, mode: u8) -> Result<()> { - if !self.chip_family.support_mcu_memory_assign_cmds() { - return Err(Error::Custom(format!( - "MCU Memory Assign set (0x0d/0x18) is enabled only for CH32V30X, attached: {:?}", - self.chip_family - ))); - } + /// 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))?; - let ack = self - .probe - .send_command(commands::control::SetMcuMemoryAssign(mode))?; + if ack != 0x18 { + return Err(Error::Custom(format!( + "Unexpected MCU Memory Split ACK: 0x{ack:02x}" + ))); + } - if ack != 0x18 { - return Err(Error::Custom(format!( - "Unexpected MCU Memory Assign ACK: 0x{ack:02x}" - ))); + return Ok(()); } - 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<()> {