use ch32rv_isp::OptionFields;
use ch32rv_target::{OptionSemantic, OptionUserField};
use serde_json::{Map, Value, json};
pub(crate) const RDPR_OFF: u8 = 0xA5;
pub(crate) struct Layout {
pub label: String,
pub semantics: Option<&'static [OptionSemantic]>,
pub user_fields: Vec<OptionUserField>,
}
impl Layout {
pub(crate) fn of(db_family: &str) -> Self {
Self {
label: db_family.to_owned(),
semantics: ch32rv_target::option_semantics(db_family),
user_fields: ch32rv_target::option_user_fields(db_family),
}
}
fn semantic(&self, key: &str) -> Option<&'static OptionSemantic> {
self.semantics?.iter().find(|s| s.key == key)
}
fn raw_fields(&self) -> impl Iterator<Item = &OptionUserField> {
self.user_fields.iter().filter(|f| {
self.semantics
.unwrap_or_default()
.iter()
.all(|s| s.mask() & f.mask() == 0)
})
}
pub(crate) fn keys(&self) -> String {
let mut k = vec![
"read-protect=on|off".to_owned(),
"write-protect=<mask>".to_owned(),
"data0=<byte>".to_owned(),
"data1=<byte>".to_owned(),
];
for s in self.semantics.unwrap_or_default() {
let words: Vec<&str> = s.values.iter().map(|v| v.word).collect();
k.push(format!("{}={}", s.key, words.join("|")));
}
for f in self.raw_fields() {
k.push(format!("user.{}=0..{}", f.field, (1u16 << f.width) - 1));
}
k.join(" ")
}
pub(crate) fn interim_warning(&self) -> Option<String> {
self.semantics.is_none().then(|| {
format!(
"the USER-byte options of {} are not known; only read-protect, write-protect, data0 and data1 are decoded",
self.label
)
})
}
}
pub(crate) fn from_ob16(ob: &[u8; 16]) -> OptionFields {
OptionFields {
rdpr: ob[0],
user: ob[2],
data0: ob[4],
data1: ob[6],
wrpr: u32::from_le_bytes([ob[8], ob[10], ob[12], ob[14]]),
}
}
pub(crate) fn to_ob16(f: OptionFields) -> [u8; 16] {
let w = f.wrpr.to_le_bytes();
let logical = [f.rdpr, f.user, f.data0, f.data1, w[0], w[1], w[2], w[3]];
let mut ob = [0u8; 16];
for (i, b) in logical.into_iter().enumerate() {
ob[2 * i] = b;
ob[2 * i + 1] = !b;
}
ob
}
#[derive(Debug, Default, PartialEq, Eq)]
pub(crate) struct Applied {
pub read_protect: Option<bool>,
pub changes: Vec<String>,
}
fn number(key: &str, v: &str) -> Result<u32, String> {
let r = match v.strip_prefix("0x").or_else(|| v.strip_prefix("0X")) {
Some(h) => u32::from_str_radix(h, 16),
None => v.parse(),
};
r.map_err(|_| format!("{key}: `{v}` is not a number"))
}
pub(crate) fn apply(
layout: &Layout,
f: &mut OptionFields,
kv: &[String],
rdpr_on: u8,
) -> Result<Applied, String> {
let mut out = Applied::default();
for item in kv {
let (k, v) = item
.split_once('=')
.ok_or_else(|| format!("`{item}`: expected key=value"))?;
match k {
"data0" | "data1" => {
let b = u8::try_from(number(k, v)?)
.map_err(|_| format!("{k}: `{v}` is out of 0..=255"))?;
if k == "data0" {
f.data0 = b;
} else {
f.data1 = b;
}
out.changes.push(format!("{k}=0x{b:02x}"));
}
"write-protect" => {
let mask = number(k, v)?;
f.wrpr = !mask;
out.changes.push(format!("{k}=0x{mask:08x}"));
}
"read-protect" => {
let want = match v {
"on" => true,
"off" => false,
_ => return Err(format!("read-protect: expected on|off, got `{v}`")),
};
if want != (f.rdpr != RDPR_OFF) {
f.rdpr = if want { rdpr_on } else { RDPR_OFF };
out.read_protect = Some(want);
}
out.changes.push(format!("{k}={v}"));
}
_ => {
if let Some(field) = k.strip_prefix("user.") {
let bits = user_field(layout, field, v)?;
f.user = bits.0.set(f.user, bits.1);
out.changes
.push(format!("user.{}={}", bits.0.field, bits.1));
} else {
let s = semantic(layout, k)?;
let val = s.values.iter().find(|x| x.word == v).ok_or_else(|| {
let words: Vec<&str> = s.values.iter().map(|x| x.word).collect();
format!("{k}: expected {}, got `{v}`", words.join("|"))
})?;
f.user = s.set(f.user, val.bits);
out.changes.push(format!("{k}={v}"));
}
}
}
}
Ok(out)
}
fn semantic(layout: &Layout, key: &str) -> Result<&'static OptionSemantic, String> {
if let Some(s) = layout.semantic(key) {
return Ok(s);
}
if ch32rv_target::option_semantic_keys().contains(&key) {
return Err(match layout.semantics {
None => format!(
"{key}: the USER-byte options of {} are not known",
layout.label
),
Some(_) => format!(
"{key}: {} has no such option (WCH EVT FLASH_UserOptionByteConfig)",
layout.label
),
});
}
if let Some(s) = by_rm_name(layout, key) {
return Err(format!("unknown key `{key}`: write it as {}", spelled(s)));
}
if let Some(f) = layout
.user_fields
.iter()
.find(|f| f.field.eq_ignore_ascii_case(key))
{
return Err(match covering(layout, f) {
Some(s) => format!("unknown key `{key}`: write it as {}", spelled(s)),
None => format!(
"unknown key `{key}`: write it as user.{}=<bit value>",
f.field
),
});
}
Err(format!("unknown key `{key}`"))
}
fn user_field<'a>(
layout: &'a Layout,
name: &str,
v: &str,
) -> Result<(&'a OptionUserField, u8), String> {
let key = format!("user.{name}");
if let Some(s) = by_rm_name(layout, name) {
return Err(format!(
"{key}: this bit has a WCH EVT name; write it as {}",
spelled(s)
));
}
let Some(f) = layout
.user_fields
.iter()
.find(|f| f.field.eq_ignore_ascii_case(name))
else {
return Err(format!(
"{key}: {} has no USER field {name} in the device DB",
layout.label
));
};
if let Some(s) = covering(layout, f) {
return Err(format!(
"{key}: this bit has a WCH EVT name; write it as {}",
spelled(s)
));
}
let top = (1u16 << f.width) - 1;
let bits = number(&key, v)?;
match u8::try_from(bits) {
Ok(b) if u16::from(b) <= top => Ok((f, b)),
_ => Err(format!("{key}: expected 0..={top}, got `{v}`")),
}
}
fn by_rm_name(layout: &Layout, name: &str) -> Option<&'static OptionSemantic> {
layout
.semantics?
.iter()
.find(|s| s.field.eq_ignore_ascii_case(name))
}
fn covering(layout: &Layout, f: &OptionUserField) -> Option<&'static OptionSemantic> {
layout.semantics?.iter().find(|s| s.mask() & f.mask() != 0)
}
fn spelled(s: &OptionSemantic) -> String {
let words: Vec<String> = s
.values
.iter()
.map(|v| format!("{} ({})", v.word, v.evt))
.collect();
format!("{}={}", s.key, words.join(" | "))
}
fn entry(value: String, raw: u32) -> Value {
json!({ "value": value, "raw": raw })
}
pub(crate) fn decode(layout: &Layout, f: OptionFields) -> Map<String, Value> {
let mut m = Map::new();
let rp = if f.rdpr == RDPR_OFF { "off" } else { "on" };
m.insert("read-protect".into(), entry(rp.into(), f.rdpr.into()));
m.insert(
"write-protect".into(),
entry(format!("0x{:08x}", !f.wrpr), f.wrpr),
);
m.insert(
"data0".into(),
entry(format!("0x{:02x}", f.data0), f.data0.into()),
);
m.insert(
"data1".into(),
entry(format!("0x{:02x}", f.data1), f.data1.into()),
);
for s in layout.semantics.unwrap_or_default() {
let bits = s.get(f.user);
let word = s
.word(bits)
.map_or_else(|| bits.to_string(), |v| v.word.to_owned());
m.insert(s.key.into(), entry(word, bits.into()));
}
for fl in layout.raw_fields() {
let bits = fl.get(f.user);
m.insert(
format!("user.{}", fl.field),
entry(bits.to_string(), bits.into()),
);
}
m
}
pub(crate) fn result(
family: Option<&str>,
db_family: Option<&str>,
raw: &str,
layout: &Layout,
f: OptionFields,
) -> Value {
json!({
"family": family,
"db_family": db_family,
"raw": raw,
"read_protected": f.rdpr != RDPR_OFF,
"write_protected": f.wrpr != 0xFFFF_FFFF,
"options": decode(layout, f),
})
}
pub(crate) fn print(v: &Value) {
if let Some(f) = v["family"].as_str() {
println!("family: {f}");
}
println!("raw: {}", v["raw"].as_str().unwrap_or("-"));
let Some(opts) = v["options"].as_object() else {
return;
};
let order = |k: &str| match k {
"read-protect" => 0,
"write-protect" => 1,
"data0" => 3,
"data1" => 4,
_ => 2,
};
let mut keys: Vec<&String> = opts.keys().collect();
keys.sort_by_key(|k| (order(k), k.starts_with("user."), (*k).clone()));
for k in keys {
let e = &opts[k.as_str()];
let pair = format!("{k}={}", e["value"].as_str().unwrap_or("-"));
let raw = e["raw"].as_u64().unwrap_or(0);
let note = match k.as_str() {
"write-protect" => format!(" (a set bit protects its sectors; WRPR 0x{raw:08x})"),
"read-protect" => format!(" (rdpr 0x{raw:02x})"),
_ if k.starts_with("user.") || k.starts_with("data") => String::new(),
_ => format!(" (raw {raw})"),
};
println!("{}", format!("{pair:<24}{note}").trim_end());
}
}
#[cfg(test)]
mod tests {
#![allow(clippy::unwrap_used, clippy::expect_used)]
use super::*;
fn s(v: &[&str]) -> Vec<String> {
v.iter().map(|x| (*x).to_owned()).collect()
}
fn factory() -> OptionFields {
OptionFields {
rdpr: RDPR_OFF,
user: 0xff,
data0: 0xff,
data1: 0xff,
wrpr: 0xFFFF_FFFF,
}
}
#[test]
fn semantic_words_set_the_evt_bits() {
let l = Layout::of("CH32X035");
let mut f = factory();
let a = apply(
&l,
&mut f,
&s(&[
"iwdg=hw",
"stop-reset=on",
"standby-reset=off",
"reset-pin=12ms",
]),
0x00,
)
.unwrap();
assert_eq!(f.user, 0b1111_0100);
assert_eq!(a.read_protect, None);
let d = decode(&l, f);
assert_eq!(d["iwdg"], json!({"value": "hw", "raw": 0}));
assert_eq!(d["stop-reset"], json!({"value": "on", "raw": 0}));
assert_eq!(d["standby-reset"], json!({"value": "off", "raw": 1}));
assert_eq!(d["reset-pin"], json!({"value": "12ms", "raw": 2}));
}
#[test]
fn fields_without_an_evt_name_go_by_their_rm_name_raw() {
let l = Layout::of("CH32V103");
let mut f = factory();
apply(&l, &mut f, &s(&["user.PORCTR=0"]), 0x00).unwrap();
assert_eq!(f.user, 0xdf);
assert_eq!(
decode(&l, f)["user.PORCTR"],
json!({"value": "0", "raw": 0})
);
let e = apply(&l, &mut f, &s(&["PORCTR=0"]), 0x00).unwrap_err();
assert!(e.contains("user.PORCTR=<bit value>"), "{e}");
assert!(apply(&l, &mut f, &s(&["user.PORCTR=2"]), 0x00).is_err());
let e = apply(&l, &mut f, &s(&["user.STOPRST=0"]), 0x00).unwrap_err();
assert!(
e.contains("stop-reset=off (OB_STOP_NoRST) | on (OB_STOP_RST)"),
"{e}"
);
let e = apply(&l, &mut f, &s(&["STOPRST=0"]), 0x00).unwrap_err();
assert!(e.contains("stop-reset="), "{e}");
assert!(!decode(&l, f).contains_key("user.STOPRST"));
}
#[test]
fn fields_the_db_now_names_go_by_their_evt_name() {
let l = Layout::of("CH32L103");
let mut f = factory();
let e = apply(&l, &mut f, &s(&["user.CFGCANM=0"]), 0x00).unwrap_err();
assert!(e.contains("can-bus-off=mode1 (OB_CAN_BusOff_MODE1)"), "{e}");
apply(&l, &mut f, &s(&["can-bus-off=mode2"]), 0x00).unwrap();
assert_eq!(f.user, 0xdf);
assert_eq!(
decode(&l, f)["can-bus-off"],
json!({"value": "mode2", "raw": 0})
);
assert!(!decode(&l, f).contains_key("user.CFGCANM"));
let l = Layout::of("CH32V006");
let e = apply(&l, &mut f, &s(&["user.RST_MODE=3"]), 0x00).unwrap_err();
assert!(e.contains("reset-pin="), "{e}");
let e = apply(&l, &mut f, &s(&["RST_MODE=3"]), 0x00).unwrap_err();
assert!(e.contains("reset-pin="), "{e}");
let mut f = factory();
apply(
&Layout::of("CH32M030"),
&mut f,
&s(&["reset-pin-select=pc0", "over-voltage=on"]),
0,
)
.unwrap();
assert_eq!(f.user, 0x5f);
let mut f = factory();
apply(
&Layout::of("CH32X315"),
&mut f,
&s(&[
"start-mode=user",
"usbhs-download=off",
"usart-download=off",
]),
0,
)
.unwrap();
assert_eq!(f.user, 0x2f);
let mut f = factory();
apply(&Layout::of("CH32V407"), &mut f, &s(&["ram-split=mod0"]), 0).unwrap();
assert_eq!(f.user, 0xbf);
let e = apply(&Layout::of("CH32V307"), &mut f, &s(&["start-mode=boot"]), 0).unwrap_err();
assert!(e.contains("no such option"), "{e}");
}
#[test]
fn options_a_family_lacks_are_refused() {
let mut f = factory();
let e = apply(&Layout::of("CH32V003"), &mut f, &s(&["stop-reset=on"]), 0).unwrap_err();
assert!(e.contains("no such option"), "{e}");
let l = Layout::of("CH32M030");
assert!(apply(&l, &mut f, &s(&["reset-pin=12ms"]), 0).is_err());
apply(&l, &mut f, &s(&["reset-pin=512us"]), 0).unwrap();
assert_eq!((f.user >> 3) & 3, 1);
let unknown = Layout::of("CH32NOPE");
assert!(unknown.interim_warning().is_some());
assert!(apply(&unknown, &mut f, &s(&["iwdg=sw"]), 0).is_err());
apply(&unknown, &mut f, &s(&["data0=0x5a"]), 0).unwrap();
assert_eq!(f.data0, 0x5a);
}
#[test]
fn read_protect_changes_only_when_the_state_does() {
let l = Layout::of("CH32V307");
let mut f = factory();
let a = apply(&l, &mut f, &s(&["read-protect=off"]), 0xff).unwrap();
assert_eq!(a.read_protect, None);
let a = apply(&l, &mut f, &s(&["read-protect=on"]), 0xff).unwrap();
assert_eq!((a.read_protect, f.rdpr), (Some(true), 0xff));
let a = apply(&l, &mut f, &s(&["read-protect=off"]), 0xff).unwrap();
assert_eq!((a.read_protect, f.rdpr), (Some(false), RDPR_OFF));
}
#[test]
fn write_protect_is_the_evt_mask_and_raw_is_wrpr() {
let l = Layout::of("CH32V307");
let mut f = factory();
apply(&l, &mut f, &s(&["write-protect=0x3"]), 0).unwrap();
assert_eq!(f.wrpr, 0xFFFF_FFFC);
let d = decode(&l, f);
assert_eq!(
d["write-protect"],
json!({"value": "0x00000003", "raw": 0xFFFF_FFFCu32})
);
assert_eq!(result(None, None, "", &l, f)["write_protected"], true);
let d = decode(&l, factory());
assert_eq!(d["write-protect"]["value"], "0x00000000");
}
#[test]
fn ob16_round_trips_with_complements() {
let mut ob = [0u8; 16];
for i in 0..8 {
ob[2 * i] = 0x10 + i as u8;
ob[2 * i + 1] = !(0x10 + i as u8);
}
let f = from_ob16(&ob);
assert_eq!(f.wrpr, 0x1716_1514);
assert_eq!(to_ob16(f), ob);
}
#[test]
fn keys_list_the_family_vocabulary() {
let k = Layout::of("CH32V003").keys();
assert!(k.contains("reset-pin=off|12ms|1ms|128us"), "{k}");
assert!(k.contains("start-mode=boot|user"), "{k}");
assert!(!k.contains("user."), "{k}");
assert!(!k.contains("STANDYRST"), "{k}");
assert!(!k.contains("stop-reset"), "{k}");
}
}