Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions protocol.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 <mode>`
- 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
Expand Down
28 changes: 27 additions & 1 deletion src/commands/control.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<u8> {
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;
Expand All @@ -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 <mode>
/// 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<u8> {
vec![0x18, self.0]
}
}

// ?? close out
/// Detach Chip, (0x0d, 0xff)
#[derive(Debug)]
Expand Down
12 changes: 12 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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!(
Expand Down
177 changes: 177 additions & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {},
}

Expand All @@ -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();

Expand Down Expand Up @@ -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 {
Expand All @@ -451,3 +591,40 @@ pub fn parse_number(s: &str) -> std::result::Result<u32, String> {
Ok(s.parse().expect("must be a number"))
}
}

pub fn parse_mem_split_mode_for_chip(
s: &str,
chip: RiscvChip,
) -> std::result::Result<u8, String> {
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)
}
Loading