diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..8b31ef7 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,114 @@ +name: CI + +on: + push: + branches: [main] + pull_request: + +env: + # Bump in lockstep with Cargo.toml when those interfaces change. + CH32_DATA_REPO: ch32-rs/ch32-data + CH32_DATA_REF: 62145f5eef6d68962b6b428ef3a901e5383480e1 + +jobs: + fmt-clippy: + name: fmt + clippy + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + with: + path: flash-algorithms + - uses: actions/checkout@v6 + with: + repository: ${{ env.CH32_DATA_REPO }} + ref: ${{ env.CH32_DATA_REF }} + path: ch32-data + - uses: dtolnay/rust-toolchain@stable + with: + components: rustfmt, clippy + - uses: Swatinem/rust-cache@v2 + with: + workspaces: | + flash-algorithms + ch32-data + shared-key: fmt-clippy + - name: generate ch32-metapac + working-directory: ch32-data + run: ./d gen + - name: cargo fmt --check + working-directory: flash-algorithms + # Explicit `-p` list (not `--all`) so fmt skips path deps like `ch32-metapac`. + run: | + cargo fmt --check \ + -p xtask \ + -p flash-algo-common \ + -p flash-algo-v0 \ + -p flash-algo-v00x \ + -p flash-algo-v1 \ + -p flash-algo-v3 \ + -p flash-algo-l1 \ + -p flash-algo-x0 \ + -p flash-algo-f1 + - name: cargo clippy -p xtask + working-directory: flash-algorithms + run: cargo clippy -p xtask -- -D warnings + + algos: + name: algos/${{ matrix.crate }} + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + crate: [v0, v00x, v1, v3, l1, x0, f1] + steps: + - uses: actions/checkout@v6 + with: + path: flash-algorithms + - uses: actions/checkout@v6 + with: + repository: ${{ env.CH32_DATA_REPO }} + ref: ${{ env.CH32_DATA_REF }} + path: ch32-data + - uses: dtolnay/rust-toolchain@nightly + with: + components: rust-src + - uses: Swatinem/rust-cache@v2 + with: + workspaces: | + flash-algorithms + ch32-data + key: ${{ matrix.crate }} + - name: generate ch32-metapac + working-directory: ch32-data + run: ./d gen + - name: cargo build --release + working-directory: flash-algorithms/algos/${{ matrix.crate }} + run: cargo build --release + + render: + name: render YAMLs + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + with: + path: flash-algorithms + - uses: actions/checkout@v6 + with: + repository: ${{ env.CH32_DATA_REPO }} + ref: ${{ env.CH32_DATA_REF }} + path: ch32-data + - uses: dtolnay/rust-toolchain@nightly + with: + components: rust-src + - uses: Swatinem/rust-cache@v2 + with: + workspaces: | + flash-algorithms + ch32-data + shared-key: render + - name: generate ch32-metapac + working-directory: ch32-data + run: ./d gen + - name: cargo run -p xtask + working-directory: flash-algorithms + run: cargo run -p xtask diff --git a/.gitignore b/.gitignore index b354aec..d37ae81 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,28 @@ +# Generated by Cargo +# will have compiled files and executables +debug +target + +# These are backup files generated by rustfmt +**/*.rs.bk + +# MSVC Windows builds of rustc generate these, which store debugging information +*.pdb + +# Generated by cargo mutants +# Contains mutation testing data +**/mutants.out*/ + +# rustc will dump stack traces when hitting an internal compiler error to PWD +rustc-ice-*.txt + +# RustRover +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +#.idea/ + +# Project-specific Cargo.lock -target/ \ No newline at end of file +generated/ diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..5e0b50e --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,35 @@ +[workspace] +resolver = "2" +members = ["algos/*", "xtask"] +default-members = ["xtask"] + +[workspace.package] +edition = "2024" +license = "MIT OR Apache-2.0" +repository = "https://github.com/ch32-rs/flash-algorithms" +authors = [ + "Andelf ", + "Aaron Qian ", +] + +[workspace.dependencies] +# Local path until ch32-data#36 merges. +ch32-metapac = { path = "../ch32-data/build/ch32-metapac", default-features = false, features = ["metadata"] } +flash-algorithm = { version = "0.7", default-features = false, features = ["erase-chip", "panic-handler"] } +panic-halt = "1" + +[profile.release] +codegen-units = 1 +debug = 2 +debug-assertions = false +incremental = false +lto = "fat" +opt-level = "s" +overflow-checks = false + +[profile.release.build-override] +codegen-units = 8 +debug = false +debug-assertions = false +opt-level = 0 +overflow-checks = false diff --git a/README.md b/README.md index 6b45cb1..a6482b0 100644 --- a/README.md +++ b/README.md @@ -1 +1,46 @@ -# flash-algorithms +# ch32-rs flash-algorithms + +[probe-rs](https://probe.rs) flash algorithms and target YAMLs for WCH's CH32 +MCU families, built on top of [ch32-metapac](https://github.com/ch32-rs/ch32-metapac). + +## Coverage + +One algo crate per (flash IP version, CPU arch) pair. `f1` is a Cortex-M3 +build of the same `flash_v1` logic used by `v1` on Qingke RISC-V — Cargo can't +unify two ch32-metapac chip features, so the lib is duplicated. + +| Crate | Flash peripheral | Target triple | Chip families | +| ------------ | ---------------- | ---------------------------------- | -------------------------- | +| `algos/v0` | `flash_v0` | `riscv32ec-unknown-none-elf` | CH32V003, CH641 | +| `algos/v00x` | `flash_v00x` | `riscv32ec_zmmul-unknown-none-elf` | CH32V002/4/5/6/7, CH32M007 | +| `algos/v1` | `flash_v1` | `riscv32imac-unknown-none-elf` | CH32V103 | +| `algos/v3` | `flash_v3` | `riscv32imac-unknown-none-elf` | CH32V2xx, CH32V3xx | +| `algos/x0` | `flash_x0` | `riscv32imac-unknown-none-elf` | CH32X035/X033, CH643 | +| `algos/l1` | `flash_l1` | `riscv32imac-unknown-none-elf` | CH32L103 | +| `algos/f1` | `flash_v1` | `thumbv7m-none-eabi` | CH32F103 (Cortex-M3) | + +Each crate builds three binaries — `usr`, `sys`, `ob` — one per writable +region. VND (vendor / ESIG) is read-only and not programmed. + +## Building + +Each algo crate's `.cargo/config.toml` pins its target triple, so cargo only +picks it up when run from inside the crate: + +``` +cd algos/v0 +cargo build --release +``` + +## Generating target YAMLs + +``` +cargo run -p xtask +``` + +Walks `../ch32-data/build/data/chips/`, builds every algo crate, emits one +probe-rs YAML per (chip × memory_option) into `generated/` (gitignored). + +## License + +Dual-licensed under MIT or Apache-2.0 at your option. diff --git a/algos/common/Cargo.toml b/algos/common/Cargo.toml new file mode 100644 index 0000000..ee0c5c0 --- /dev/null +++ b/algos/common/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "flash-algo-common" +version = "0.1.0" +edition.workspace = true +license.workspace = true +repository.workspace = true +authors.workspace = true +publish = false + +[lib] +name = "flash_algo_common" + +[dependencies] +ch32-metapac = { workspace = true } diff --git a/algos/common/src/lib.rs b/algos/common/src/lib.rs new file mode 100644 index 0000000..fe602c9 --- /dev/null +++ b/algos/common/src/lib.rs @@ -0,0 +1,69 @@ +#![no_std] + +pub use ch32_metapac::FLASH; +pub use ch32_metapac::metadata::{METADATA, MemoryRegion, Mode}; + +pub const KEY1: u32 = 0x4567_0123; +pub const KEY2: u32 = 0xCDEF_89AB; + +pub const PROGRAM_TIMEOUT_MS: u32 = 1000; +pub const ERASE_TIMEOUT_MS: u32 = 2000; + +pub const ERR_NOT_SUPPORTED: core::num::NonZeroU32 = core::num::NonZeroU32::MIN; + +pub const fn str_eq(a: &str, b: &str) -> bool { + let (a, b) = (a.as_bytes(), b.as_bytes()); + if a.len() != b.len() { + return false; + } + let mut i = 0; + while i < a.len() { + if a[i] != b[i] { + return false; + } + i += 1; + } + true +} + +pub const fn region(name: &str) -> &'static MemoryRegion { + let mem = METADATA.memory; + let mut i = 0; + while i < mem.len() { + if str_eq(mem[i].name, name) { + return &mem[i]; + } + i += 1; + } + panic!("region not found in METADATA.memory") +} + +pub const fn fast(r: &MemoryRegion) -> (u32, u32) { + let mut i = 0; + while i < r.modes.len() { + if let Mode::Fast { + page_size, + load_size, + } = r.modes[i] + { + return (page_size, load_size); + } + i += 1; + } + panic!("region has no Fast programming mode") +} + +pub const fn standard(r: &MemoryRegion) -> (u32, u32) { + let mut i = 0; + while i < r.modes.len() { + if let Mode::Standard { + erase_size, + write_size, + } = r.modes[i] + { + return (erase_size, write_size); + } + i += 1; + } + panic!("region has no Standard programming mode") +} diff --git a/algos/f1/.cargo/config.toml b/algos/f1/.cargo/config.toml new file mode 100644 index 0000000..a1ad903 --- /dev/null +++ b/algos/f1/.cargo/config.toml @@ -0,0 +1,11 @@ +[build] +target = "thumbv7m-none-eabi" + +[unstable] +build-std = ["core", "compiler_builtins"] + +[target.'cfg(all(target_arch = "arm", target_os = "none"))'] +rustflags = [ + "-C", "link-arg=-Tmemory.x", + "-C", "link-arg=--defsym=ALGO_PLACEMENT_START_ADDRESS=0x20000020", +] diff --git a/algos/f1/Cargo.toml b/algos/f1/Cargo.toml new file mode 100644 index 0000000..6515310 --- /dev/null +++ b/algos/f1/Cargo.toml @@ -0,0 +1,37 @@ +[package] +name = "flash-algo-f1" +version = "0.1.0" +edition.workspace = true +license.workspace = true +repository.workspace = true +authors.workspace = true +publish = false + +[lib] +name = "flash_algo_f1" + +[[bin]] +name = "usr" +test = false +doctest = false +bench = false + +[[bin]] +name = "sys" +test = false +doctest = false +bench = false + +[[bin]] +name = "ob" +test = false +doctest = false +bench = false + +# F103 reuses the v1 flash IP but runs on a Cortex-M3 core; metapac can't +# unify two chip features, so this crate duplicates v1's lib instead of +# depending on `flash-algo-v1`. +[dependencies] +ch32-metapac = { workspace = true, features = ["pac", "ch32f103c8t6"] } +flash-algorithm = { workspace = true } +flash-algo-common = { path = "../common" } diff --git a/algos/f1/src/bin/ob.rs b/algos/f1/src/bin/ob.rs new file mode 100644 index 0000000..d12c1ab --- /dev/null +++ b/algos/f1/src/bin/ob.rs @@ -0,0 +1,49 @@ +#![no_std] +#![no_main] + +use flash_algo_f1::*; +use flash_algorithm::{ErrorCode, FlashAlgorithm, Function, algorithm}; + +struct Algo; + +algorithm!(Algo, { + device_name: "CH32 F1 OB", + device_type: DeviceType::Onchip, + flash_address: OPT_BASE, + flash_size: 0, + page_size: OPT_ERASE_SIZE, + empty_value: 0xFF, + program_time_out: PROGRAM_TIMEOUT_MS, + erase_time_out: ERASE_TIMEOUT_MS, + sectors: [{ + size: OPT_ERASE_SIZE, + address: OPT_BASE, + }] +}); + +impl FlashAlgorithm for Algo { + fn new(_addr: u32, _clock: u32, _function: Function) -> Result { + unlock_options(); + Ok(Self) + } + + fn erase_all(&mut self) -> Result<(), ErrorCode> { + Err(ERR_NOT_SUPPORTED) + } + + fn erase_sector(&mut self, _addr: u32) -> Result<(), ErrorCode> { + options_erase(); + Ok(()) + } + + fn program_page(&mut self, addr: u32, data: &[u8]) -> Result<(), ErrorCode> { + options_program(addr, data)?; + Ok(()) + } +} + +impl Drop for Algo { + fn drop(&mut self) { + lock_main(); + } +} diff --git a/algos/f1/src/bin/sys.rs b/algos/f1/src/bin/sys.rs new file mode 100644 index 0000000..11bf56b --- /dev/null +++ b/algos/f1/src/bin/sys.rs @@ -0,0 +1,51 @@ +#![no_std] +#![no_main] + +use flash_algo_f1::*; +use flash_algorithm::{ErrorCode, FlashAlgorithm, Function, algorithm}; + +/// F1 boot is pin-strapped; `erase_all` rejects MER (would target USR +/// regardless of which algo is loaded). +struct Algo; + +algorithm!(Algo, { + device_name: "CH32 F1 SYS", + device_type: DeviceType::Onchip, + flash_address: SYS_BASE, + flash_size: 0, + page_size: SYS_PAGE_SIZE, + empty_value: 0xFF, + program_time_out: PROGRAM_TIMEOUT_MS, + erase_time_out: ERASE_TIMEOUT_MS, + sectors: [{ + size: SYS_PAGE_SIZE, + address: SYS_BASE, + }] +}); + +impl FlashAlgorithm for Algo { + fn new(_addr: u32, _clock: u32, _function: Function) -> Result { + unlock_main(); + Ok(Self) + } + + fn erase_all(&mut self) -> Result<(), ErrorCode> { + Err(ERR_NOT_SUPPORTED) + } + + fn erase_sector(&mut self, addr: u32) -> Result<(), ErrorCode> { + fast_page_erase(addr, SYS_PAGE_SIZE); + Ok(()) + } + + fn program_page(&mut self, addr: u32, data: &[u8]) -> Result<(), ErrorCode> { + fast_page_program(addr, data, SYS_PAGE_SIZE, SYS_LOAD); + Ok(()) + } +} + +impl Drop for Algo { + fn drop(&mut self) { + lock_main(); + } +} diff --git a/algos/f1/src/bin/usr.rs b/algos/f1/src/bin/usr.rs new file mode 100644 index 0000000..7de4391 --- /dev/null +++ b/algos/f1/src/bin/usr.rs @@ -0,0 +1,50 @@ +#![no_std] +#![no_main] + +use flash_algo_f1::*; +use flash_algorithm::{ErrorCode, FlashAlgorithm, Function, algorithm}; + +struct Algo; + +algorithm!(Algo, { + device_name: "CH32 F1 USR", + device_type: DeviceType::Onchip, + flash_address: USR_BASE, + flash_size: 0, + page_size: USR_PAGE_SIZE, + empty_value: 0xFF, + program_time_out: PROGRAM_TIMEOUT_MS, + erase_time_out: ERASE_TIMEOUT_MS, + sectors: [{ + size: USR_PAGE_SIZE, + address: USR_BASE, + }] +}); + +impl FlashAlgorithm for Algo { + fn new(_addr: u32, _clock: u32, _function: Function) -> Result { + unlock_main(); + Ok(Self) + } + + fn erase_all(&mut self) -> Result<(), ErrorCode> { + mass_erase(); + Ok(()) + } + + fn erase_sector(&mut self, addr: u32) -> Result<(), ErrorCode> { + fast_page_erase(addr | USR_BASE, USR_PAGE_SIZE); + Ok(()) + } + + fn program_page(&mut self, addr: u32, data: &[u8]) -> Result<(), ErrorCode> { + fast_page_program(addr | USR_BASE, data, USR_PAGE_SIZE, USR_LOAD); + Ok(()) + } +} + +impl Drop for Algo { + fn drop(&mut self) { + lock_main(); + } +} diff --git a/algos/f1/src/lib.rs b/algos/f1/src/lib.rs new file mode 100644 index 0000000..c3ad21b --- /dev/null +++ b/algos/f1/src/lib.rs @@ -0,0 +1,155 @@ +#![no_std] + +pub use flash_algo_common::*; + +pub const USR_BASE: u32 = region("USR_1").address; +const USR_FAST: (u32, u32) = fast(region("USR_1")); +pub const USR_PAGE_SIZE: u32 = USR_FAST.0; +pub const USR_LOAD: u32 = USR_FAST.1; + +/// F1 has two non-contiguous SYS regions (SYS_1 + SYS_2) sharing one FPEC +/// programming flow; both share SYS_1's geometry. +pub const SYS_BASE: u32 = region("SYS_1").address; +const SYS_FAST: (u32, u32) = fast(region("SYS_1")); +pub const SYS_PAGE_SIZE: u32 = SYS_FAST.0; +pub const SYS_LOAD: u32 = SYS_FAST.1; + +pub const OPT_BASE: u32 = region("OPT").address; +const OPT_STD: (u32, u32) = standard(region("OPT")); +pub const OPT_ERASE_SIZE: u32 = OPT_STD.0; +pub const OPT_WRITE_SIZE: u32 = OPT_STD.1; + +/// Undocumented read-cache register at FLASH+0x34; used by WCH's HAL +/// (openwch/ch32v103 `ch32v10x_flash.c`). Without poking this post-erase or +/// post-program, the prefetch can serve stale data. +const FLASH_RDCACHE_REG: *mut u32 = 0x4002_2034 as *mut u32; +const RDCACHE_XOR_MASK: u32 = 0x0000_1000; + +pub fn wait_busy() { + while FLASH.statr().read().bsy() {} + FLASH.statr().write(|w| w.set_eop(true)); +} + +pub fn unlock_main() { + FLASH.keyr().write(|w| w.set_keyr(KEY1)); + FLASH.keyr().write(|w| w.set_keyr(KEY2)); + FLASH.modekeyr().write(|w| w.set_modekeyr(KEY1)); + FLASH.modekeyr().write(|w| w.set_modekeyr(KEY2)); +} + +pub fn unlock_options() { + FLASH.keyr().write(|w| w.set_keyr(KEY1)); + FLASH.keyr().write(|w| w.set_keyr(KEY2)); + FLASH.obkeyr().write(|w| w.set_obkeyr(KEY1)); + FLASH.obkeyr().write(|w| w.set_obkeyr(KEY2)); +} + +pub fn lock_main() { + FLASH.ctlr().modify(|w| { + w.set_lock(true); + w.set_flock(true); + }); +} + +pub fn invalidate_read_cache(addr: u32) { + let src = addr ^ RDCACHE_XOR_MASK; + let val = unsafe { core::ptr::read_volatile(src as *const u32) }; + unsafe { core::ptr::write_volatile(FLASH_RDCACHE_REG, val) }; +} + +pub fn fast_page_erase(addr: u32, _page_size: u32) { + wait_busy(); + FLASH.ctlr().write(|w| w.set_fter(true)); + FLASH.addr().write(|w| w.set_far(addr)); + FLASH.ctlr().write(|w| { + w.set_fter(true); + w.set_strt(true); + }); + wait_busy(); + FLASH.ctlr().modify(|w| w.set_fter(false)); + invalidate_read_cache(addr); +} + +/// F1 streams 4 words into the FPEC buffer per BUFLOAD strobe (unlike +/// v0/v00x which BUFLOAD every word). +pub fn fast_page_program(addr: u32, data: &[u8], page_size: u32, load_size: u32) { + let page_base = addr & !(page_size - 1); + + wait_busy(); + FLASH.ctlr().write(|w| w.set_ftpg(true)); + FLASH.ctlr().write(|w| { + w.set_ftpg(true); + w.set_bufrst(true); + }); + wait_busy(); + + let mut buf_addr = addr; + let mut src = data.as_ptr() as *const u32; + let mut loaded: u32 = 0; + let words = data.len() as u32 / 4; + for _ in 0..words { + let v = unsafe { src.read() }; + unsafe { core::ptr::write_volatile(buf_addr as *mut u32, v) }; + buf_addr += 4; + src = unsafe { src.add(1) }; + loaded += 4; + if loaded % load_size == 0 { + FLASH.ctlr().write(|w| { + w.set_ftpg(true); + w.set_bufload(true); + }); + wait_busy(); + } + } + + FLASH.addr().write(|w| w.set_far(page_base)); + FLASH.ctlr().write(|w| { + w.set_ftpg(true); + w.set_strt(true); + }); + wait_busy(); + FLASH.ctlr().modify(|w| w.set_ftpg(false)); + invalidate_read_cache(page_base); +} + +pub fn mass_erase() { + wait_busy(); + FLASH.ctlr().modify(|w| w.set_mer(true)); + FLASH.ctlr().modify(|w| w.set_strt(true)); + wait_busy(); + FLASH.ctlr().modify(|w| w.set_mer(false)); + invalidate_read_cache(USR_BASE); +} + +pub fn options_erase() { + wait_busy(); + FLASH.ctlr().modify(|w| w.set_ober(true)); + FLASH.ctlr().modify(|w| w.set_strt(true)); + wait_busy(); + FLASH.ctlr().modify(|w| w.set_ober(false)); +} + +fn options_program_halfword(addr: u32, val: u16) { + wait_busy(); + FLASH.ctlr().modify(|w| w.set_obpg(true)); + unsafe { core::ptr::write_volatile(addr as *mut u16, val) }; + wait_busy(); + FLASH.ctlr().modify(|w| w.set_obpg(false)); +} + +pub fn options_program(addr: u32, data: &[u8]) -> Result<(), core::num::NonZeroU32> { + let step = OPT_WRITE_SIZE as usize; + if (addr as usize % step) != 0 || data.len() % step != 0 { + return Err(ERR_NOT_SUPPORTED); + } + let mut cur = addr; + let mut src = data.as_ptr() as *const u16; + let halfwords = data.len() / step; + for _ in 0..halfwords { + let hw = unsafe { src.read() }; + options_program_halfword(cur, hw); + cur += step as u32; + src = unsafe { src.add(1) }; + } + Ok(()) +} diff --git a/algos/l1/.cargo/config.toml b/algos/l1/.cargo/config.toml new file mode 100644 index 0000000..ed1ea4a --- /dev/null +++ b/algos/l1/.cargo/config.toml @@ -0,0 +1,11 @@ +[build] +target = "riscv32imac-unknown-none-elf" + +[unstable] +build-std = ["core", "compiler_builtins"] + +[target.'cfg(all(target_arch = "riscv32", target_os = "none"))'] +rustflags = [ + "-C", "link-arg=-Tmemory.x", + "-C", "link-arg=--defsym=ALGO_PLACEMENT_START_ADDRESS=0x20000020", +] diff --git a/algos/l1/Cargo.toml b/algos/l1/Cargo.toml new file mode 100644 index 0000000..a4cd3ae --- /dev/null +++ b/algos/l1/Cargo.toml @@ -0,0 +1,34 @@ +[package] +name = "flash-algo-l1" +version = "0.1.0" +edition.workspace = true +license.workspace = true +repository.workspace = true +authors.workspace = true +publish = false + +[lib] +name = "flash_algo_l1" + +[[bin]] +name = "usr" +test = false +doctest = false +bench = false + +[[bin]] +name = "sys" +test = false +doctest = false +bench = false + +[[bin]] +name = "ob" +test = false +doctest = false +bench = false + +[dependencies] +ch32-metapac = { workspace = true, features = ["pac", "ch32l103c8t6"] } +flash-algorithm = { workspace = true } +flash-algo-common = { path = "../common" } diff --git a/algos/l1/src/bin/ob.rs b/algos/l1/src/bin/ob.rs new file mode 100644 index 0000000..334952e --- /dev/null +++ b/algos/l1/src/bin/ob.rs @@ -0,0 +1,49 @@ +#![no_std] +#![no_main] + +use flash_algo_l1::*; +use flash_algorithm::{ErrorCode, FlashAlgorithm, Function, algorithm}; + +struct Algo; + +algorithm!(Algo, { + device_name: "CH32 L1 OB", + device_type: DeviceType::Onchip, + flash_address: OPT_BASE, + flash_size: 0, + page_size: OPT_PAGE_SIZE, + empty_value: 0xFF, + program_time_out: PROGRAM_TIMEOUT_MS, + erase_time_out: ERASE_TIMEOUT_MS, + sectors: [{ + size: OPT_PAGE_SIZE, + address: OPT_BASE, + }] +}); + +impl FlashAlgorithm for Algo { + fn new(_addr: u32, _clock: u32, _function: Function) -> Result { + unlock_options(); + Ok(Self) + } + + fn erase_all(&mut self) -> Result<(), ErrorCode> { + Err(ERR_NOT_SUPPORTED) + } + + fn erase_sector(&mut self, _addr: u32) -> Result<(), ErrorCode> { + options_erase(); + Ok(()) + } + + fn program_page(&mut self, addr: u32, data: &[u8]) -> Result<(), ErrorCode> { + options_program(addr, data)?; + Ok(()) + } +} + +impl Drop for Algo { + fn drop(&mut self) { + lock_main(); + } +} diff --git a/algos/l1/src/bin/sys.rs b/algos/l1/src/bin/sys.rs new file mode 100644 index 0000000..c7d0d64 --- /dev/null +++ b/algos/l1/src/bin/sys.rs @@ -0,0 +1,51 @@ +#![no_std] +#![no_main] + +use flash_algo_l1::*; +use flash_algorithm::{ErrorCode, FlashAlgorithm, Function, algorithm}; + +/// L1 boot is pin-strapped; `erase_all` rejects MER (would target USR +/// regardless of which algo is loaded). +struct Algo; + +algorithm!(Algo, { + device_name: "CH32 L1 SYS", + device_type: DeviceType::Onchip, + flash_address: SYS_BASE, + flash_size: 0, + page_size: SYS_PAGE_SIZE, + empty_value: 0xFF, + program_time_out: PROGRAM_TIMEOUT_MS, + erase_time_out: ERASE_TIMEOUT_MS, + sectors: [{ + size: SYS_PAGE_SIZE, + address: SYS_BASE, + }] +}); + +impl FlashAlgorithm for Algo { + fn new(_addr: u32, _clock: u32, _function: Function) -> Result { + unlock_main(); + Ok(Self) + } + + fn erase_all(&mut self) -> Result<(), ErrorCode> { + Err(ERR_NOT_SUPPORTED) + } + + fn erase_sector(&mut self, addr: u32) -> Result<(), ErrorCode> { + fast_page_erase(addr, SYS_PAGE_SIZE); + Ok(()) + } + + fn program_page(&mut self, addr: u32, data: &[u8]) -> Result<(), ErrorCode> { + fast_page_program(addr, data, SYS_PAGE_SIZE, SYS_LOAD); + Ok(()) + } +} + +impl Drop for Algo { + fn drop(&mut self) { + lock_main(); + } +} diff --git a/algos/l1/src/bin/usr.rs b/algos/l1/src/bin/usr.rs new file mode 100644 index 0000000..efa3013 --- /dev/null +++ b/algos/l1/src/bin/usr.rs @@ -0,0 +1,50 @@ +#![no_std] +#![no_main] + +use flash_algo_l1::*; +use flash_algorithm::{ErrorCode, FlashAlgorithm, Function, algorithm}; + +struct Algo; + +algorithm!(Algo, { + device_name: "CH32 L1 USR", + device_type: DeviceType::Onchip, + flash_address: USR_BASE, + flash_size: 0, + page_size: USR_PAGE_SIZE, + empty_value: 0xFF, + program_time_out: PROGRAM_TIMEOUT_MS, + erase_time_out: ERASE_TIMEOUT_MS, + sectors: [{ + size: USR_PAGE_SIZE, + address: USR_BASE, + }] +}); + +impl FlashAlgorithm for Algo { + fn new(_addr: u32, _clock: u32, _function: Function) -> Result { + unlock_main(); + Ok(Self) + } + + fn erase_all(&mut self) -> Result<(), ErrorCode> { + mass_erase(); + Ok(()) + } + + fn erase_sector(&mut self, addr: u32) -> Result<(), ErrorCode> { + fast_page_erase(addr | USR_BASE, USR_PAGE_SIZE); + Ok(()) + } + + fn program_page(&mut self, addr: u32, data: &[u8]) -> Result<(), ErrorCode> { + fast_page_program(addr | USR_BASE, data, USR_PAGE_SIZE, USR_LOAD); + Ok(()) + } +} + +impl Drop for Algo { + fn drop(&mut self) { + lock_main(); + } +} diff --git a/algos/l1/src/lib.rs b/algos/l1/src/lib.rs new file mode 100644 index 0000000..0b63ef0 --- /dev/null +++ b/algos/l1/src/lib.rs @@ -0,0 +1,104 @@ +#![no_std] + +pub use flash_algo_common::*; + +pub const USR_BASE: u32 = region("USR_1").address; +const USR_FAST: (u32, u32) = fast(region("USR_1")); +pub const USR_PAGE_SIZE: u32 = USR_FAST.0; +pub const USR_LOAD: u32 = USR_FAST.1; + +pub const SYS_BASE: u32 = region("SYS_1").address; +const SYS_FAST: (u32, u32) = fast(region("SYS_1")); +pub const SYS_PAGE_SIZE: u32 = SYS_FAST.0; +pub const SYS_LOAD: u32 = SYS_FAST.1; + +pub const OPT_BASE: u32 = region("OPT").address; +const OPT_FAST: (u32, u32) = fast(region("OPT")); +pub const OPT_PAGE_SIZE: u32 = OPT_FAST.0; +pub const OPT_LOAD: u32 = OPT_FAST.1; + +pub fn wait_busy() { + while FLASH.statr().read().bsy() {} + FLASH.statr().write(|w| w.set_eop(true)); +} + +pub fn unlock_main() { + FLASH.keyr().write(|w| w.set_keyr(KEY1)); + FLASH.keyr().write(|w| w.set_keyr(KEY2)); + FLASH.modekeyr().write(|w| w.set_modekeyr(KEY1)); + FLASH.modekeyr().write(|w| w.set_modekeyr(KEY2)); +} + +pub fn unlock_options() { + FLASH.keyr().write(|w| w.set_keyr(KEY1)); + FLASH.keyr().write(|w| w.set_keyr(KEY2)); + FLASH.modekeyr().write(|w| w.set_modekeyr(KEY1)); + FLASH.modekeyr().write(|w| w.set_modekeyr(KEY2)); + FLASH.obkeyr().write(|w| w.set_obtkey(KEY1)); + FLASH.obkeyr().write(|w| w.set_obtkey(KEY2)); +} + +pub fn lock_main() { + FLASH.ctlr().modify(|w| { + w.set_lock(true); + w.set_flock(true); + }); +} + +pub fn fast_page_erase(addr: u32, _page_size: u32) { + wait_busy(); + FLASH.ctlr().modify(|w| w.set_pter(true)); + FLASH.addr().write(|w| w.set_far(addr)); + FLASH.ctlr().modify(|w| w.set_strt(true)); + wait_busy(); + FLASH.ctlr().modify(|w| w.set_pter(false)); +} + +pub fn fast_page_program(addr: u32, data: &[u8], _page_size: u32, load_size: u32) { + wait_busy(); + FLASH.ctlr().modify(|w| w.set_ptpg(true)); + FLASH.ctlr().modify(|w| w.set_bufrst(true)); + wait_busy(); + + let mut cur = addr; + let mut src = data.as_ptr() as *const u32; + let words = data.len() as u32 / load_size; + for _ in 0..words { + let v = unsafe { src.read() }; + unsafe { core::ptr::write_volatile(cur as *mut u32, v) }; + FLASH.ctlr().modify(|w| w.set_bufload(true)); + wait_busy(); + cur += load_size; + src = unsafe { src.add(1) }; + } + + FLASH.addr().write(|w| w.set_far(addr)); + FLASH.ctlr().modify(|w| w.set_strt(true)); + wait_busy(); + FLASH.ctlr().modify(|w| w.set_ptpg(false)); +} + +pub fn mass_erase() { + wait_busy(); + FLASH.ctlr().modify(|w| w.set_mer(true)); + FLASH.ctlr().modify(|w| w.set_strt(true)); + wait_busy(); + FLASH.ctlr().modify(|w| w.set_mer(false)); +} + +pub fn options_erase() { + wait_busy(); + FLASH.ctlr().modify(|w| w.set_ober(true)); + FLASH.ctlr().modify(|w| w.set_strt(true)); + wait_busy(); + FLASH.ctlr().modify(|w| w.set_ober(false)); +} + +pub fn options_program(addr: u32, data: &[u8]) -> Result<(), core::num::NonZeroU32> { + let step = OPT_LOAD as usize; + if (addr as usize % step) != 0 || data.len() % step != 0 { + return Err(ERR_NOT_SUPPORTED); + } + fast_page_program(addr, data, OPT_PAGE_SIZE, OPT_LOAD); + Ok(()) +} diff --git a/algos/v0/.cargo/config.toml b/algos/v0/.cargo/config.toml new file mode 100644 index 0000000..21ea6cb --- /dev/null +++ b/algos/v0/.cargo/config.toml @@ -0,0 +1,12 @@ +[build] +target = "../../targets/riscv32ec-unknown-none-elf.json" + +[unstable] +build-std = ["core", "compiler_builtins"] +json-target-spec = true + +[target.'cfg(all(target_arch = "riscv32", target_os = "none"))'] +rustflags = [ + "-C", "link-arg=-Tmemory.x", + "-C", "link-arg=--defsym=ALGO_PLACEMENT_START_ADDRESS=0x20000020", +] diff --git a/algos/v0/Cargo.toml b/algos/v0/Cargo.toml new file mode 100644 index 0000000..475caca --- /dev/null +++ b/algos/v0/Cargo.toml @@ -0,0 +1,34 @@ +[package] +name = "flash-algo-v0" +version = "0.1.0" +edition.workspace = true +license.workspace = true +repository.workspace = true +authors.workspace = true +publish = false + +[lib] +name = "flash_algo_v0" + +[[bin]] +name = "usr" +test = false +doctest = false +bench = false + +[[bin]] +name = "sys" +test = false +doctest = false +bench = false + +[[bin]] +name = "ob" +test = false +doctest = false +bench = false + +[dependencies] +ch32-metapac = { workspace = true, features = ["pac", "ch32v003f4p6"] } +flash-algorithm = { workspace = true } +flash-algo-common = { path = "../common" } diff --git a/algos/v0/src/bin/ob.rs b/algos/v0/src/bin/ob.rs new file mode 100644 index 0000000..438dddb --- /dev/null +++ b/algos/v0/src/bin/ob.rs @@ -0,0 +1,49 @@ +#![no_std] +#![no_main] + +use flash_algo_v0::*; +use flash_algorithm::{ErrorCode, FlashAlgorithm, Function, algorithm}; + +struct Algo; + +algorithm!(Algo, { + device_name: "CH32 V0 OB", + device_type: DeviceType::Onchip, + flash_address: OPT_BASE, + flash_size: 0, + page_size: OPT_ERASE_SIZE, + empty_value: 0xFF, + program_time_out: PROGRAM_TIMEOUT_MS, + erase_time_out: ERASE_TIMEOUT_MS, + sectors: [{ + size: OPT_ERASE_SIZE, + address: OPT_BASE, + }] +}); + +impl FlashAlgorithm for Algo { + fn new(_addr: u32, _clock: u32, _function: Function) -> Result { + unlock_options(); + Ok(Self) + } + + fn erase_all(&mut self) -> Result<(), ErrorCode> { + Err(ERR_NOT_SUPPORTED) + } + + fn erase_sector(&mut self, _addr: u32) -> Result<(), ErrorCode> { + options_erase(); + Ok(()) + } + + fn program_page(&mut self, addr: u32, data: &[u8]) -> Result<(), ErrorCode> { + options_program(addr, data)?; + Ok(()) + } +} + +impl Drop for Algo { + fn drop(&mut self) { + lock_main(); + } +} diff --git a/algos/v0/src/bin/sys.rs b/algos/v0/src/bin/sys.rs new file mode 100644 index 0000000..b9225f0 --- /dev/null +++ b/algos/v0/src/bin/sys.rs @@ -0,0 +1,56 @@ +#![no_std] +#![no_main] + +use flash_algo_v0::*; +use flash_algorithm::{ErrorCode, FlashAlgorithm, Function, algorithm}; + +/// `saved_boot` restores `STATR.BOOT_MODE` in `Drop` — without it, flashing +/// SYS would silently redirect the next reset into the bootloader. +struct Algo { + saved_boot: bool, +} + +algorithm!(Algo, { + device_name: "CH32 V0 SYS", + device_type: DeviceType::Onchip, + flash_address: SYS_BASE, + flash_size: 0, + page_size: SYS_PAGE_SIZE, + empty_value: 0xFF, + program_time_out: PROGRAM_TIMEOUT_MS, + erase_time_out: ERASE_TIMEOUT_MS, + sectors: [{ + size: SYS_PAGE_SIZE, + address: SYS_BASE, + }] +}); + +impl FlashAlgorithm for Algo { + fn new(_addr: u32, _clock: u32, _function: Function) -> Result { + let saved_boot = boot_mode(); + unlock_main(); + set_boot_mode(true); + Ok(Self { saved_boot }) + } + + fn erase_all(&mut self) -> Result<(), ErrorCode> { + Err(ERR_NOT_SUPPORTED) + } + + fn erase_sector(&mut self, addr: u32) -> Result<(), ErrorCode> { + fast_page_erase(addr, SYS_PAGE_SIZE); + Ok(()) + } + + fn program_page(&mut self, addr: u32, data: &[u8]) -> Result<(), ErrorCode> { + fast_page_program(addr, data, SYS_PAGE_SIZE, SYS_LOAD); + Ok(()) + } +} + +impl Drop for Algo { + fn drop(&mut self) { + set_boot_mode(self.saved_boot); + lock_main(); + } +} diff --git a/algos/v0/src/bin/usr.rs b/algos/v0/src/bin/usr.rs new file mode 100644 index 0000000..30ca69e --- /dev/null +++ b/algos/v0/src/bin/usr.rs @@ -0,0 +1,50 @@ +#![no_std] +#![no_main] + +use flash_algo_v0::*; +use flash_algorithm::{ErrorCode, FlashAlgorithm, Function, algorithm}; + +struct Algo; + +algorithm!(Algo, { + device_name: "CH32 V0 USR", + device_type: DeviceType::Onchip, + flash_address: USR_BASE, + flash_size: 0, + page_size: USR_PAGE_SIZE, + empty_value: 0xFF, + program_time_out: PROGRAM_TIMEOUT_MS, + erase_time_out: ERASE_TIMEOUT_MS, + sectors: [{ + size: USR_PAGE_SIZE, + address: USR_BASE, + }] +}); + +impl FlashAlgorithm for Algo { + fn new(_addr: u32, _clock: u32, _function: Function) -> Result { + unlock_main(); + Ok(Self) + } + + fn erase_all(&mut self) -> Result<(), ErrorCode> { + mass_erase(); + Ok(()) + } + + fn erase_sector(&mut self, addr: u32) -> Result<(), ErrorCode> { + fast_page_erase(addr | USR_BASE, USR_PAGE_SIZE); + Ok(()) + } + + fn program_page(&mut self, addr: u32, data: &[u8]) -> Result<(), ErrorCode> { + fast_page_program(addr | USR_BASE, data, USR_PAGE_SIZE, USR_LOAD); + Ok(()) + } +} + +impl Drop for Algo { + fn drop(&mut self) { + lock_main(); + } +} diff --git a/algos/v0/src/lib.rs b/algos/v0/src/lib.rs new file mode 100644 index 0000000..90c0acb --- /dev/null +++ b/algos/v0/src/lib.rs @@ -0,0 +1,145 @@ +#![no_std] + +pub use flash_algo_common::*; + +pub const USR_BASE: u32 = region("USR_1").address; +const USR_FAST: (u32, u32) = fast(region("USR_1")); +pub const USR_PAGE_SIZE: u32 = USR_FAST.0; +pub const USR_LOAD: u32 = USR_FAST.1; + +pub const SYS_BASE: u32 = region("SYS_1").address; +const SYS_FAST: (u32, u32) = fast(region("SYS_1")); +pub const SYS_PAGE_SIZE: u32 = SYS_FAST.0; +pub const SYS_LOAD: u32 = SYS_FAST.1; + +pub const OPT_BASE: u32 = region("OPT").address; +const OPT_STD: (u32, u32) = standard(region("OPT")); +pub const OPT_ERASE_SIZE: u32 = OPT_STD.0; +pub const OPT_WRITE_SIZE: u32 = OPT_STD.1; + +pub fn wait_busy() { + while FLASH.statr().read().bsy() {} + FLASH.statr().write(|w| w.set_eop(true)); +} + +pub fn unlock_main() { + FLASH.keyr().write(|w| w.set_keyr(KEY1)); + FLASH.keyr().write(|w| w.set_keyr(KEY2)); + FLASH.modekeyr().write(|w| w.set_modekeyr(KEY1)); + FLASH.modekeyr().write(|w| w.set_modekeyr(KEY2)); +} + +pub fn unlock_options() { + FLASH.keyr().write(|w| w.set_keyr(KEY1)); + FLASH.keyr().write(|w| w.set_keyr(KEY2)); + FLASH.obkeyr().write(|w| w.set_optkey(KEY1)); + FLASH.obkeyr().write(|w| w.set_optkey(KEY2)); +} + +/// `STATR.BOOT_MODE` survives soft reboots and decides the next-reset boot +/// source — snapshot in `Init`, restore in `UnInit`. +pub fn boot_mode() -> bool { + FLASH.statr().read().boot_mode() +} + +pub fn set_boot_mode(mode: bool) { + FLASH.boot_modekeyp().write(|w| w.set_modekeyr(KEY1)); + FLASH.boot_modekeyp().write(|w| w.set_modekeyr(KEY2)); + FLASH.statr().write(|w| w.set_boot_mode(mode)); +} + +pub fn lock_main() { + FLASH.ctlr().write(|w| { + w.set_lock(true); + w.set_flock(true); + }); +} + +pub fn fast_page_erase(addr: u32, _page_size: u32) { + wait_busy(); + FLASH.ctlr().write(|w| w.set_page_er(true)); + FLASH.addr().write(|w| w.set_addr(addr)); + FLASH.ctlr().write(|w| { + w.set_page_er(true); + w.set_strt(true); + }); + wait_busy(); + FLASH.ctlr().modify(|w| w.set_page_er(false)); +} + +pub fn fast_page_program(addr: u32, data: &[u8], page_size: u32, load_size: u32) { + let page_base = addr & !(page_size - 1); + + wait_busy(); + FLASH.ctlr().write(|w| w.set_page_pg(true)); + FLASH.ctlr().write(|w| { + w.set_page_pg(true); + w.set_bufrst(true); + }); + wait_busy(); + + let mut buf_addr = addr; + let mut src = data.as_ptr() as *const u32; + let words = data.len() as u32 / load_size; + for _ in 0..words { + let v = unsafe { src.read() }; + unsafe { core::ptr::write_volatile(buf_addr as *mut u32, v) }; + FLASH.ctlr().write(|w| { + w.set_page_pg(true); + w.set_bufload(true); + }); + wait_busy(); + buf_addr += load_size; + src = unsafe { src.add(1) }; + } + + FLASH.addr().write(|w| w.set_addr(page_base)); + FLASH.ctlr().write(|w| { + w.set_page_pg(true); + w.set_strt(true); + }); + wait_busy(); + FLASH.ctlr().modify(|w| w.set_page_pg(false)); +} + +/// MER targets only USR. Caller must ensure BOOT_MODE=0. +pub fn mass_erase() { + wait_busy(); + FLASH.ctlr().modify(|w| w.set_mer(true)); + FLASH.ctlr().modify(|w| w.set_strt(true)); + wait_busy(); + FLASH.ctlr().modify(|w| w.set_mer(false)); +} + +pub fn options_erase() { + wait_busy(); + FLASH.ctlr().modify(|w| w.set_ober(true)); + FLASH.ctlr().modify(|w| w.set_strt(true)); + wait_busy(); + FLASH.ctlr().modify(|w| w.set_ober(false)); +} + +fn options_program_halfword(addr: u32, val: u16) { + wait_busy(); + FLASH.ctlr().modify(|w| w.set_obpg(true)); + unsafe { core::ptr::write_volatile(addr as *mut u16, val) }; + wait_busy(); + FLASH.ctlr().modify(|w| w.set_obpg(false)); +} + +pub fn options_program(addr: u32, data: &[u8]) -> Result<(), core::num::NonZeroU32> { + let step = OPT_WRITE_SIZE as usize; + if (addr as usize % step) != 0 || data.len() % step != 0 { + return Err(ERR_NOT_SUPPORTED); + } + let mut cur = addr; + let mut src = data.as_ptr() as *const u16; + let halfwords = data.len() / step; + for _ in 0..halfwords { + let hw = unsafe { src.read() }; + options_program_halfword(cur, hw); + cur += step as u32; + src = unsafe { src.add(1) }; + } + Ok(()) +} diff --git a/algos/v00x/.cargo/config.toml b/algos/v00x/.cargo/config.toml new file mode 100644 index 0000000..c4898c5 --- /dev/null +++ b/algos/v00x/.cargo/config.toml @@ -0,0 +1,12 @@ +[build] +target = "../../targets/riscv32ec_zmmul-unknown-none-elf.json" + +[unstable] +build-std = ["core", "compiler_builtins"] +json-target-spec = true + +[target.'cfg(all(target_arch = "riscv32", target_os = "none"))'] +rustflags = [ + "-C", "link-arg=-Tmemory.x", + "-C", "link-arg=--defsym=ALGO_PLACEMENT_START_ADDRESS=0x20000020", +] diff --git a/algos/v00x/Cargo.toml b/algos/v00x/Cargo.toml new file mode 100644 index 0000000..29871f3 --- /dev/null +++ b/algos/v00x/Cargo.toml @@ -0,0 +1,34 @@ +[package] +name = "flash-algo-v00x" +version = "0.1.0" +edition.workspace = true +license.workspace = true +repository.workspace = true +authors.workspace = true +publish = false + +[lib] +name = "flash_algo_v00x" + +[[bin]] +name = "usr" +test = false +doctest = false +bench = false + +[[bin]] +name = "sys" +test = false +doctest = false +bench = false + +[[bin]] +name = "ob" +test = false +doctest = false +bench = false + +[dependencies] +ch32-metapac = { workspace = true, features = ["pac", "ch32v005x6x6"] } +flash-algorithm = { workspace = true } +flash-algo-common = { path = "../common" } diff --git a/algos/v00x/src/bin/ob.rs b/algos/v00x/src/bin/ob.rs new file mode 100644 index 0000000..ac1fd89 --- /dev/null +++ b/algos/v00x/src/bin/ob.rs @@ -0,0 +1,49 @@ +#![no_std] +#![no_main] + +use flash_algo_v00x::*; +use flash_algorithm::{ErrorCode, FlashAlgorithm, Function, algorithm}; + +struct Algo; + +algorithm!(Algo, { + device_name: "CH32 V00X OB", + device_type: DeviceType::Onchip, + flash_address: OPT_BASE, + flash_size: 0, + page_size: OPT_PAGE_SIZE, + empty_value: 0xFF, + program_time_out: PROGRAM_TIMEOUT_MS, + erase_time_out: ERASE_TIMEOUT_MS, + sectors: [{ + size: OPT_PAGE_SIZE, + address: OPT_BASE, + }] +}); + +impl FlashAlgorithm for Algo { + fn new(_addr: u32, _clock: u32, _function: Function) -> Result { + unlock_options(); + Ok(Self) + } + + fn erase_all(&mut self) -> Result<(), ErrorCode> { + Err(ERR_NOT_SUPPORTED) + } + + fn erase_sector(&mut self, _addr: u32) -> Result<(), ErrorCode> { + options_erase(); + Ok(()) + } + + fn program_page(&mut self, addr: u32, data: &[u8]) -> Result<(), ErrorCode> { + options_program(addr, data)?; + Ok(()) + } +} + +impl Drop for Algo { + fn drop(&mut self) { + lock_main(); + } +} diff --git a/algos/v00x/src/bin/sys.rs b/algos/v00x/src/bin/sys.rs new file mode 100644 index 0000000..d02470f --- /dev/null +++ b/algos/v00x/src/bin/sys.rs @@ -0,0 +1,56 @@ +#![no_std] +#![no_main] + +use flash_algo_v00x::*; +use flash_algorithm::{ErrorCode, FlashAlgorithm, Function, algorithm}; + +/// `saved_boot` restores `STATR.BOOT_MODE` in `Drop` — without it, flashing +/// SYS would silently redirect the next reset into the bootloader. +struct Algo { + saved_boot: bool, +} + +algorithm!(Algo, { + device_name: "CH32 V00X SYS", + device_type: DeviceType::Onchip, + flash_address: SYS_BASE, + flash_size: 0, + page_size: SYS_PAGE_SIZE, + empty_value: 0xFF, + program_time_out: PROGRAM_TIMEOUT_MS, + erase_time_out: ERASE_TIMEOUT_MS, + sectors: [{ + size: SYS_PAGE_SIZE, + address: SYS_BASE, + }] +}); + +impl FlashAlgorithm for Algo { + fn new(_addr: u32, _clock: u32, _function: Function) -> Result { + let saved_boot = boot_mode(); + unlock_main(); + set_boot_mode(true); + Ok(Self { saved_boot }) + } + + fn erase_all(&mut self) -> Result<(), ErrorCode> { + Err(ERR_NOT_SUPPORTED) + } + + fn erase_sector(&mut self, addr: u32) -> Result<(), ErrorCode> { + fast_page_erase(addr, SYS_PAGE_SIZE); + Ok(()) + } + + fn program_page(&mut self, addr: u32, data: &[u8]) -> Result<(), ErrorCode> { + fast_page_program(addr, data, SYS_PAGE_SIZE, SYS_LOAD); + Ok(()) + } +} + +impl Drop for Algo { + fn drop(&mut self) { + set_boot_mode(self.saved_boot); + lock_main(); + } +} diff --git a/algos/v00x/src/bin/usr.rs b/algos/v00x/src/bin/usr.rs new file mode 100644 index 0000000..c518133 --- /dev/null +++ b/algos/v00x/src/bin/usr.rs @@ -0,0 +1,50 @@ +#![no_std] +#![no_main] + +use flash_algo_v00x::*; +use flash_algorithm::{ErrorCode, FlashAlgorithm, Function, algorithm}; + +struct Algo; + +algorithm!(Algo, { + device_name: "CH32 V00X USR", + device_type: DeviceType::Onchip, + flash_address: USR_BASE, + flash_size: 0, + page_size: USR_PAGE_SIZE, + empty_value: 0xFF, + program_time_out: PROGRAM_TIMEOUT_MS, + erase_time_out: ERASE_TIMEOUT_MS, + sectors: [{ + size: USR_PAGE_SIZE, + address: USR_BASE, + }] +}); + +impl FlashAlgorithm for Algo { + fn new(_addr: u32, _clock: u32, _function: Function) -> Result { + unlock_main(); + Ok(Self) + } + + fn erase_all(&mut self) -> Result<(), ErrorCode> { + mass_erase(); + Ok(()) + } + + fn erase_sector(&mut self, addr: u32) -> Result<(), ErrorCode> { + fast_page_erase(addr | USR_BASE, USR_PAGE_SIZE); + Ok(()) + } + + fn program_page(&mut self, addr: u32, data: &[u8]) -> Result<(), ErrorCode> { + fast_page_program(addr | USR_BASE, data, USR_PAGE_SIZE, USR_LOAD); + Ok(()) + } +} + +impl Drop for Algo { + fn drop(&mut self) { + lock_main(); + } +} diff --git a/algos/v00x/src/lib.rs b/algos/v00x/src/lib.rs new file mode 100644 index 0000000..cd23094 --- /dev/null +++ b/algos/v00x/src/lib.rs @@ -0,0 +1,131 @@ +#![no_std] + +pub use flash_algo_common::*; + +pub const USR_BASE: u32 = region("USR_1").address; +const USR_FAST: (u32, u32) = fast(region("USR_1")); +pub const USR_PAGE_SIZE: u32 = USR_FAST.0; +pub const USR_LOAD: u32 = USR_FAST.1; + +pub const SYS_BASE: u32 = region("SYS_1").address; +const SYS_FAST: (u32, u32) = fast(region("SYS_1")); +pub const SYS_PAGE_SIZE: u32 = SYS_FAST.0; +pub const SYS_LOAD: u32 = SYS_FAST.1; + +pub const OPT_BASE: u32 = region("OPT").address; +const OPT_FAST: (u32, u32) = fast(region("OPT")); +pub const OPT_PAGE_SIZE: u32 = OPT_FAST.0; +pub const OPT_LOAD: u32 = OPT_FAST.1; + +pub fn wait_busy() { + while FLASH.statr().read().bsy() {} + FLASH.statr().write(|w| w.set_eop(true)); +} + +pub fn unlock_main() { + FLASH.keyr().write(|w| w.set_keyr(KEY1)); + FLASH.keyr().write(|w| w.set_keyr(KEY2)); + FLASH.modekeyr().write(|w| w.set_modekeyr(KEY1)); + FLASH.modekeyr().write(|w| w.set_modekeyr(KEY2)); +} + +pub fn unlock_options() { + FLASH.keyr().write(|w| w.set_keyr(KEY1)); + FLASH.keyr().write(|w| w.set_keyr(KEY2)); + FLASH.modekeyr().write(|w| w.set_modekeyr(KEY1)); + FLASH.modekeyr().write(|w| w.set_modekeyr(KEY2)); + FLASH.obkeyr().write(|w| w.set_optkey(KEY1)); + FLASH.obkeyr().write(|w| w.set_optkey(KEY2)); +} + +/// `STATR.BOOT_MODE` survives soft reboots and decides the next-reset boot +/// source — snapshot in `Init`, restore in `UnInit`. +pub fn boot_mode() -> bool { + FLASH.statr().read().boot_mode() +} + +pub fn set_boot_mode(mode: bool) { + FLASH.boot_modekeyp().write(|w| w.set_modekeyr(KEY1)); + FLASH.boot_modekeyp().write(|w| w.set_modekeyr(KEY2)); + FLASH.statr().write(|w| w.set_boot_mode(mode)); +} + +pub fn lock_main() { + FLASH.ctlr().write(|w| { + w.set_lock(true); + w.set_flock(true); + }); +} + +pub fn fast_page_erase(addr: u32, _page_size: u32) { + wait_busy(); + FLASH.ctlr().write(|w| w.set_fter(true)); + FLASH.addr().write(|w| w.set_far(addr)); + FLASH.ctlr().write(|w| { + w.set_fter(true); + w.set_strt(true); + }); + wait_busy(); + FLASH.ctlr().modify(|w| w.set_fter(false)); +} + +pub fn fast_page_program(addr: u32, data: &[u8], page_size: u32, load_size: u32) { + let page_base = addr & !(page_size - 1); + + wait_busy(); + FLASH.ctlr().write(|w| w.set_ftpg(true)); + FLASH.ctlr().write(|w| { + w.set_ftpg(true); + w.set_bufrst(true); + }); + wait_busy(); + + let mut buf_addr = addr; + let mut src = data.as_ptr() as *const u32; + let words = data.len() as u32 / load_size; + for _ in 0..words { + let v = unsafe { src.read() }; + unsafe { core::ptr::write_volatile(buf_addr as *mut u32, v) }; + FLASH.ctlr().write(|w| { + w.set_ftpg(true); + w.set_bufload(true); + }); + wait_busy(); + buf_addr += load_size; + src = unsafe { src.add(1) }; + } + + FLASH.addr().write(|w| w.set_far(page_base)); + FLASH.ctlr().write(|w| { + w.set_ftpg(true); + w.set_strt(true); + }); + wait_busy(); + FLASH.ctlr().modify(|w| w.set_ftpg(false)); +} + +/// MER targets only USR. Caller must ensure BOOT_MODE=0. +pub fn mass_erase() { + wait_busy(); + FLASH.ctlr().modify(|w| w.set_mer(true)); + FLASH.ctlr().modify(|w| w.set_strt(true)); + wait_busy(); + FLASH.ctlr().modify(|w| w.set_mer(false)); +} + +pub fn options_erase() { + wait_busy(); + FLASH.ctlr().modify(|w| w.set_ober(true)); + FLASH.ctlr().modify(|w| w.set_strt(true)); + wait_busy(); + FLASH.ctlr().modify(|w| w.set_ober(false)); +} + +pub fn options_program(addr: u32, data: &[u8]) -> Result<(), core::num::NonZeroU32> { + let step = OPT_LOAD as usize; + if (addr as usize % step) != 0 || data.len() % step != 0 { + return Err(ERR_NOT_SUPPORTED); + } + fast_page_program(addr, data, OPT_PAGE_SIZE, OPT_LOAD); + Ok(()) +} diff --git a/algos/v1/.cargo/config.toml b/algos/v1/.cargo/config.toml new file mode 100644 index 0000000..ed1ea4a --- /dev/null +++ b/algos/v1/.cargo/config.toml @@ -0,0 +1,11 @@ +[build] +target = "riscv32imac-unknown-none-elf" + +[unstable] +build-std = ["core", "compiler_builtins"] + +[target.'cfg(all(target_arch = "riscv32", target_os = "none"))'] +rustflags = [ + "-C", "link-arg=-Tmemory.x", + "-C", "link-arg=--defsym=ALGO_PLACEMENT_START_ADDRESS=0x20000020", +] diff --git a/algos/v1/Cargo.toml b/algos/v1/Cargo.toml new file mode 100644 index 0000000..32eff22 --- /dev/null +++ b/algos/v1/Cargo.toml @@ -0,0 +1,34 @@ +[package] +name = "flash-algo-v1" +version = "0.1.0" +edition.workspace = true +license.workspace = true +repository.workspace = true +authors.workspace = true +publish = false + +[lib] +name = "flash_algo_v1" + +[[bin]] +name = "usr" +test = false +doctest = false +bench = false + +[[bin]] +name = "sys" +test = false +doctest = false +bench = false + +[[bin]] +name = "ob" +test = false +doctest = false +bench = false + +[dependencies] +ch32-metapac = { workspace = true, features = ["pac", "ch32v103c8t6"] } +flash-algorithm = { workspace = true } +flash-algo-common = { path = "../common" } diff --git a/algos/v1/src/bin/ob.rs b/algos/v1/src/bin/ob.rs new file mode 100644 index 0000000..9cebcc1 --- /dev/null +++ b/algos/v1/src/bin/ob.rs @@ -0,0 +1,49 @@ +#![no_std] +#![no_main] + +use flash_algo_v1::*; +use flash_algorithm::{ErrorCode, FlashAlgorithm, Function, algorithm}; + +struct Algo; + +algorithm!(Algo, { + device_name: "CH32 V1 OB", + device_type: DeviceType::Onchip, + flash_address: OPT_BASE, + flash_size: 0, + page_size: OPT_ERASE_SIZE, + empty_value: 0xFF, + program_time_out: PROGRAM_TIMEOUT_MS, + erase_time_out: ERASE_TIMEOUT_MS, + sectors: [{ + size: OPT_ERASE_SIZE, + address: OPT_BASE, + }] +}); + +impl FlashAlgorithm for Algo { + fn new(_addr: u32, _clock: u32, _function: Function) -> Result { + unlock_options(); + Ok(Self) + } + + fn erase_all(&mut self) -> Result<(), ErrorCode> { + Err(ERR_NOT_SUPPORTED) + } + + fn erase_sector(&mut self, _addr: u32) -> Result<(), ErrorCode> { + options_erase(); + Ok(()) + } + + fn program_page(&mut self, addr: u32, data: &[u8]) -> Result<(), ErrorCode> { + options_program(addr, data)?; + Ok(()) + } +} + +impl Drop for Algo { + fn drop(&mut self) { + lock_main(); + } +} diff --git a/algos/v1/src/bin/sys.rs b/algos/v1/src/bin/sys.rs new file mode 100644 index 0000000..a7e7f5c --- /dev/null +++ b/algos/v1/src/bin/sys.rs @@ -0,0 +1,51 @@ +#![no_std] +#![no_main] + +use flash_algo_v1::*; +use flash_algorithm::{ErrorCode, FlashAlgorithm, Function, algorithm}; + +/// V1 boot is pin-strapped; `erase_all` rejects MER (would target USR +/// regardless of which algo is loaded). +struct Algo; + +algorithm!(Algo, { + device_name: "CH32 V1 SYS", + device_type: DeviceType::Onchip, + flash_address: SYS_BASE, + flash_size: 0, + page_size: SYS_PAGE_SIZE, + empty_value: 0xFF, + program_time_out: PROGRAM_TIMEOUT_MS, + erase_time_out: ERASE_TIMEOUT_MS, + sectors: [{ + size: SYS_PAGE_SIZE, + address: SYS_BASE, + }] +}); + +impl FlashAlgorithm for Algo { + fn new(_addr: u32, _clock: u32, _function: Function) -> Result { + unlock_main(); + Ok(Self) + } + + fn erase_all(&mut self) -> Result<(), ErrorCode> { + Err(ERR_NOT_SUPPORTED) + } + + fn erase_sector(&mut self, addr: u32) -> Result<(), ErrorCode> { + fast_page_erase(addr, SYS_PAGE_SIZE); + Ok(()) + } + + fn program_page(&mut self, addr: u32, data: &[u8]) -> Result<(), ErrorCode> { + fast_page_program(addr, data, SYS_PAGE_SIZE, SYS_LOAD); + Ok(()) + } +} + +impl Drop for Algo { + fn drop(&mut self) { + lock_main(); + } +} diff --git a/algos/v1/src/bin/usr.rs b/algos/v1/src/bin/usr.rs new file mode 100644 index 0000000..3d85ba1 --- /dev/null +++ b/algos/v1/src/bin/usr.rs @@ -0,0 +1,50 @@ +#![no_std] +#![no_main] + +use flash_algo_v1::*; +use flash_algorithm::{ErrorCode, FlashAlgorithm, Function, algorithm}; + +struct Algo; + +algorithm!(Algo, { + device_name: "CH32 V1 USR", + device_type: DeviceType::Onchip, + flash_address: USR_BASE, + flash_size: 0, + page_size: USR_PAGE_SIZE, + empty_value: 0xFF, + program_time_out: PROGRAM_TIMEOUT_MS, + erase_time_out: ERASE_TIMEOUT_MS, + sectors: [{ + size: USR_PAGE_SIZE, + address: USR_BASE, + }] +}); + +impl FlashAlgorithm for Algo { + fn new(_addr: u32, _clock: u32, _function: Function) -> Result { + unlock_main(); + Ok(Self) + } + + fn erase_all(&mut self) -> Result<(), ErrorCode> { + mass_erase(); + Ok(()) + } + + fn erase_sector(&mut self, addr: u32) -> Result<(), ErrorCode> { + fast_page_erase(addr | USR_BASE, USR_PAGE_SIZE); + Ok(()) + } + + fn program_page(&mut self, addr: u32, data: &[u8]) -> Result<(), ErrorCode> { + fast_page_program(addr | USR_BASE, data, USR_PAGE_SIZE, USR_LOAD); + Ok(()) + } +} + +impl Drop for Algo { + fn drop(&mut self) { + lock_main(); + } +} diff --git a/algos/v1/src/lib.rs b/algos/v1/src/lib.rs new file mode 100644 index 0000000..19b7b4e --- /dev/null +++ b/algos/v1/src/lib.rs @@ -0,0 +1,155 @@ +#![no_std] + +pub use flash_algo_common::*; + +pub const USR_BASE: u32 = region("USR_1").address; +const USR_FAST: (u32, u32) = fast(region("USR_1")); +pub const USR_PAGE_SIZE: u32 = USR_FAST.0; +pub const USR_LOAD: u32 = USR_FAST.1; + +/// V1 has two non-contiguous SYS regions (SYS_1 + SYS_2) sharing one FPEC +/// programming flow; both share SYS_1's geometry. +pub const SYS_BASE: u32 = region("SYS_1").address; +const SYS_FAST: (u32, u32) = fast(region("SYS_1")); +pub const SYS_PAGE_SIZE: u32 = SYS_FAST.0; +pub const SYS_LOAD: u32 = SYS_FAST.1; + +pub const OPT_BASE: u32 = region("OPT").address; +const OPT_STD: (u32, u32) = standard(region("OPT")); +pub const OPT_ERASE_SIZE: u32 = OPT_STD.0; +pub const OPT_WRITE_SIZE: u32 = OPT_STD.1; + +/// Undocumented read-cache register at FLASH+0x34; used by WCH's HAL +/// (openwch/ch32v103 `ch32v10x_flash.c`). Without poking this post-erase or +/// post-program, the prefetch can serve stale data. +const FLASH_RDCACHE_REG: *mut u32 = 0x4002_2034 as *mut u32; +const RDCACHE_XOR_MASK: u32 = 0x0000_1000; + +pub fn wait_busy() { + while FLASH.statr().read().bsy() {} + FLASH.statr().write(|w| w.set_eop(true)); +} + +pub fn unlock_main() { + FLASH.keyr().write(|w| w.set_keyr(KEY1)); + FLASH.keyr().write(|w| w.set_keyr(KEY2)); + FLASH.modekeyr().write(|w| w.set_modekeyr(KEY1)); + FLASH.modekeyr().write(|w| w.set_modekeyr(KEY2)); +} + +pub fn unlock_options() { + FLASH.keyr().write(|w| w.set_keyr(KEY1)); + FLASH.keyr().write(|w| w.set_keyr(KEY2)); + FLASH.obkeyr().write(|w| w.set_obkeyr(KEY1)); + FLASH.obkeyr().write(|w| w.set_obkeyr(KEY2)); +} + +pub fn lock_main() { + FLASH.ctlr().modify(|w| { + w.set_lock(true); + w.set_flock(true); + }); +} + +pub fn invalidate_read_cache(addr: u32) { + let src = addr ^ RDCACHE_XOR_MASK; + let val = unsafe { core::ptr::read_volatile(src as *const u32) }; + unsafe { core::ptr::write_volatile(FLASH_RDCACHE_REG, val) }; +} + +pub fn fast_page_erase(addr: u32, _page_size: u32) { + wait_busy(); + FLASH.ctlr().write(|w| w.set_fter(true)); + FLASH.addr().write(|w| w.set_far(addr)); + FLASH.ctlr().write(|w| { + w.set_fter(true); + w.set_strt(true); + }); + wait_busy(); + FLASH.ctlr().modify(|w| w.set_fter(false)); + invalidate_read_cache(addr); +} + +/// V1 streams 4 words into the FPEC buffer per BUFLOAD strobe (unlike +/// v0/v00x which BUFLOAD every word). +pub fn fast_page_program(addr: u32, data: &[u8], page_size: u32, load_size: u32) { + let page_base = addr & !(page_size - 1); + + wait_busy(); + FLASH.ctlr().write(|w| w.set_ftpg(true)); + FLASH.ctlr().write(|w| { + w.set_ftpg(true); + w.set_bufrst(true); + }); + wait_busy(); + + let mut buf_addr = addr; + let mut src = data.as_ptr() as *const u32; + let mut loaded: u32 = 0; + let words = data.len() as u32 / 4; + for _ in 0..words { + let v = unsafe { src.read() }; + unsafe { core::ptr::write_volatile(buf_addr as *mut u32, v) }; + buf_addr += 4; + src = unsafe { src.add(1) }; + loaded += 4; + if loaded % load_size == 0 { + FLASH.ctlr().write(|w| { + w.set_ftpg(true); + w.set_bufload(true); + }); + wait_busy(); + } + } + + FLASH.addr().write(|w| w.set_far(page_base)); + FLASH.ctlr().write(|w| { + w.set_ftpg(true); + w.set_strt(true); + }); + wait_busy(); + FLASH.ctlr().modify(|w| w.set_ftpg(false)); + invalidate_read_cache(page_base); +} + +pub fn mass_erase() { + wait_busy(); + FLASH.ctlr().modify(|w| w.set_mer(true)); + FLASH.ctlr().modify(|w| w.set_strt(true)); + wait_busy(); + FLASH.ctlr().modify(|w| w.set_mer(false)); + invalidate_read_cache(USR_BASE); +} + +pub fn options_erase() { + wait_busy(); + FLASH.ctlr().modify(|w| w.set_ober(true)); + FLASH.ctlr().modify(|w| w.set_strt(true)); + wait_busy(); + FLASH.ctlr().modify(|w| w.set_ober(false)); +} + +fn options_program_halfword(addr: u32, val: u16) { + wait_busy(); + FLASH.ctlr().modify(|w| w.set_obpg(true)); + unsafe { core::ptr::write_volatile(addr as *mut u16, val) }; + wait_busy(); + FLASH.ctlr().modify(|w| w.set_obpg(false)); +} + +pub fn options_program(addr: u32, data: &[u8]) -> Result<(), core::num::NonZeroU32> { + let step = OPT_WRITE_SIZE as usize; + if (addr as usize % step) != 0 || data.len() % step != 0 { + return Err(ERR_NOT_SUPPORTED); + } + let mut cur = addr; + let mut src = data.as_ptr() as *const u16; + let halfwords = data.len() / step; + for _ in 0..halfwords { + let hw = unsafe { src.read() }; + options_program_halfword(cur, hw); + cur += step as u32; + src = unsafe { src.add(1) }; + } + Ok(()) +} diff --git a/algos/v3/.cargo/config.toml b/algos/v3/.cargo/config.toml new file mode 100644 index 0000000..ed1ea4a --- /dev/null +++ b/algos/v3/.cargo/config.toml @@ -0,0 +1,11 @@ +[build] +target = "riscv32imac-unknown-none-elf" + +[unstable] +build-std = ["core", "compiler_builtins"] + +[target.'cfg(all(target_arch = "riscv32", target_os = "none"))'] +rustflags = [ + "-C", "link-arg=-Tmemory.x", + "-C", "link-arg=--defsym=ALGO_PLACEMENT_START_ADDRESS=0x20000020", +] diff --git a/algos/v3/Cargo.toml b/algos/v3/Cargo.toml new file mode 100644 index 0000000..09d6601 --- /dev/null +++ b/algos/v3/Cargo.toml @@ -0,0 +1,34 @@ +[package] +name = "flash-algo-v3" +version = "0.1.0" +edition.workspace = true +license.workspace = true +repository.workspace = true +authors.workspace = true +publish = false + +[lib] +name = "flash_algo_v3" + +[[bin]] +name = "usr" +test = false +doctest = false +bench = false + +[[bin]] +name = "sys" +test = false +doctest = false +bench = false + +[[bin]] +name = "ob" +test = false +doctest = false +bench = false + +[dependencies] +ch32-metapac = { workspace = true, features = ["pac", "ch32v203rbt6"] } +flash-algorithm = { workspace = true } +flash-algo-common = { path = "../common" } diff --git a/algos/v3/src/bin/ob.rs b/algos/v3/src/bin/ob.rs new file mode 100644 index 0000000..2016aea --- /dev/null +++ b/algos/v3/src/bin/ob.rs @@ -0,0 +1,49 @@ +#![no_std] +#![no_main] + +use flash_algo_v3::*; +use flash_algorithm::{ErrorCode, FlashAlgorithm, Function, algorithm}; + +struct Algo; + +algorithm!(Algo, { + device_name: "CH32 V3 OB", + device_type: DeviceType::Onchip, + flash_address: OPT_BASE, + flash_size: 0, + page_size: OPT_ERASE_SIZE, + empty_value: 0xFF, + program_time_out: PROGRAM_TIMEOUT_MS, + erase_time_out: ERASE_TIMEOUT_MS, + sectors: [{ + size: OPT_ERASE_SIZE, + address: OPT_BASE, + }] +}); + +impl FlashAlgorithm for Algo { + fn new(_addr: u32, _clock: u32, _function: Function) -> Result { + unlock_options(); + Ok(Self) + } + + fn erase_all(&mut self) -> Result<(), ErrorCode> { + Err(ERR_NOT_SUPPORTED) + } + + fn erase_sector(&mut self, _addr: u32) -> Result<(), ErrorCode> { + options_erase(); + Ok(()) + } + + fn program_page(&mut self, addr: u32, data: &[u8]) -> Result<(), ErrorCode> { + options_program(addr, data)?; + Ok(()) + } +} + +impl Drop for Algo { + fn drop(&mut self) { + lock_main(); + } +} diff --git a/algos/v3/src/bin/sys.rs b/algos/v3/src/bin/sys.rs new file mode 100644 index 0000000..1f5c4bb --- /dev/null +++ b/algos/v3/src/bin/sys.rs @@ -0,0 +1,51 @@ +#![no_std] +#![no_main] + +use flash_algo_v3::*; +use flash_algorithm::{ErrorCode, FlashAlgorithm, Function, algorithm}; + +/// V3 boot is pin-strapped; `erase_all` rejects MER (would target USR +/// regardless of which algo is loaded). +struct Algo; + +algorithm!(Algo, { + device_name: "CH32 V3 SYS", + device_type: DeviceType::Onchip, + flash_address: SYS_BASE, + flash_size: 0, + page_size: SYS_PAGE_SIZE, + empty_value: 0xFF, + program_time_out: PROGRAM_TIMEOUT_MS, + erase_time_out: ERASE_TIMEOUT_MS, + sectors: [{ + size: SYS_ERASE_SIZE, + address: SYS_BASE, + }] +}); + +impl FlashAlgorithm for Algo { + fn new(_addr: u32, _clock: u32, _function: Function) -> Result { + unlock_main(); + Ok(Self) + } + + fn erase_all(&mut self) -> Result<(), ErrorCode> { + Err(ERR_NOT_SUPPORTED) + } + + fn erase_sector(&mut self, addr: u32) -> Result<(), ErrorCode> { + boot_page_erase(addr); + Ok(()) + } + + fn program_page(&mut self, addr: u32, data: &[u8]) -> Result<(), ErrorCode> { + boot_page_program(addr, data, SYS_LOAD); + Ok(()) + } +} + +impl Drop for Algo { + fn drop(&mut self) { + lock_main(); + } +} diff --git a/algos/v3/src/bin/usr.rs b/algos/v3/src/bin/usr.rs new file mode 100644 index 0000000..c7525cf --- /dev/null +++ b/algos/v3/src/bin/usr.rs @@ -0,0 +1,50 @@ +#![no_std] +#![no_main] + +use flash_algo_v3::*; +use flash_algorithm::{ErrorCode, FlashAlgorithm, Function, algorithm}; + +struct Algo; + +algorithm!(Algo, { + device_name: "CH32 V3 USR", + device_type: DeviceType::Onchip, + flash_address: USR_BASE, + flash_size: 0, + page_size: USR_PAGE_SIZE, + empty_value: 0xFF, + program_time_out: PROGRAM_TIMEOUT_MS, + erase_time_out: ERASE_TIMEOUT_MS, + sectors: [{ + size: USR_PAGE_SIZE, + address: USR_BASE, + }] +}); + +impl FlashAlgorithm for Algo { + fn new(_addr: u32, _clock: u32, _function: Function) -> Result { + unlock_main(); + Ok(Self) + } + + fn erase_all(&mut self) -> Result<(), ErrorCode> { + mass_erase(); + Ok(()) + } + + fn erase_sector(&mut self, addr: u32) -> Result<(), ErrorCode> { + fast_page_erase(addr | USR_BASE, USR_PAGE_SIZE); + Ok(()) + } + + fn program_page(&mut self, addr: u32, data: &[u8]) -> Result<(), ErrorCode> { + fast_page_program(addr | USR_BASE, data, USR_PAGE_SIZE, USR_LOAD); + Ok(()) + } +} + +impl Drop for Algo { + fn drop(&mut self) { + lock_main(); + } +} diff --git a/algos/v3/src/lib.rs b/algos/v3/src/lib.rs new file mode 100644 index 0000000..1ffff46 --- /dev/null +++ b/algos/v3/src/lib.rs @@ -0,0 +1,166 @@ +#![no_std] + +pub use flash_algo_common::*; + +pub const USR_BASE: u32 = region("USR_1").address; +const USR_FAST: (u32, u32) = fast(region("USR_1")); +pub const USR_PAGE_SIZE: u32 = USR_FAST.0; +pub const USR_LOAD: u32 = USR_FAST.1; + +pub const SYS_BASE: u32 = region("SYS_1").address; +const SYS_FAST: (u32, u32) = fast(region("SYS_1")); +pub const SYS_PAGE_SIZE: u32 = SYS_FAST.0; +pub const SYS_LOAD: u32 = SYS_FAST.1; +const SYS_STD: (u32, u32) = standard(region("SYS_1")); +pub const SYS_ERASE_SIZE: u32 = SYS_STD.0; + +pub const OPT_BASE: u32 = region("OPT").address; +const OPT_STD: (u32, u32) = standard(region("OPT")); +pub const OPT_ERASE_SIZE: u32 = OPT_STD.0; +pub const OPT_WRITE_SIZE: u32 = OPT_STD.1; + +pub fn wait_busy() { + while FLASH.statr().read().bsy() {} + FLASH.statr().write(|w| w.set_eop(true)); +} + +pub fn wait_wr_busy() { + while FLASH.statr().read().wr_bsy() {} +} + +pub fn unlock_main() { + FLASH.keyr().write(|w| w.set_keyr(KEY1)); + FLASH.keyr().write(|w| w.set_keyr(KEY2)); + FLASH.modekeyr().write(|w| w.set_modekeyr(KEY1)); + FLASH.modekeyr().write(|w| w.set_modekeyr(KEY2)); +} + +pub fn lock_main() { + FLASH.ctlr().modify(|w| { + w.set_lock(true); + w.set_flock(true); + }); +} + +pub fn unlock_options() { + FLASH.keyr().write(|w| w.set_keyr(KEY1)); + FLASH.keyr().write(|w| w.set_keyr(KEY2)); + FLASH.obkeyr().write(|w| w.set_optkey(KEY1)); + FLASH.obkeyr().write(|w| w.set_optkey(KEY2)); +} + +pub fn fast_page_erase(addr: u32, _page_size: u32) { + wait_busy(); + FLASH.ctlr().modify(|w| w.set_page_er(true)); + FLASH.addr().write(|w| w.set_far(addr)); + FLASH.ctlr().modify(|w| w.set_strt(true)); + wait_busy(); + FLASH.ctlr().modify(|w| w.set_page_er(false)); +} + +/// V3 writes words straight into the mapped flash address (no buffer-load +/// step) and waits WR_BSY each, then commits with PGSTART. +pub fn fast_page_program(addr: u32, data: &[u8], _page_size: u32, load_size: u32) { + wait_busy(); + FLASH.ctlr().modify(|w| w.set_page_pg(true)); + + let mut cur = addr; + let mut src = data.as_ptr() as *const u32; + let words = data.len() as u32 / load_size; + for _ in 0..words { + let v = unsafe { src.read() }; + unsafe { core::ptr::write_volatile(cur as *mut u32, v) }; + wait_wr_busy(); + cur += load_size; + src = unsafe { src.add(1) }; + } + + FLASH.ctlr().modify(|w| w.set_pgstart(true)); + wait_busy(); + FLASH.ctlr().modify(|w| w.set_page_pg(false)); +} + +/// SYS uses standard PER (4 KB) + BTER; FTER silently no-ops on this region. +pub fn boot_page_erase(addr: u32) { + wait_busy(); + FLASH.ctlr().modify(|w| { + w.set_per(true); + w.set_bter(true); + }); + FLASH.addr().write(|w| w.set_far(addr)); + FLASH.ctlr().modify(|w| w.set_strt(true)); + wait_busy(); + FLASH.ctlr().modify(|w| { + w.set_per(false); + w.set_bter(false); + }); +} + +/// `fast_page_program` with BTPG OR-ed in; without it the word write to +/// 0x1FFF8000 faults and WR_BSY never clears. +pub fn boot_page_program(addr: u32, data: &[u8], load_size: u32) { + wait_busy(); + FLASH.ctlr().modify(|w| { + w.set_page_pg(true); + w.set_btpg(true); + }); + + let mut cur = addr; + let mut src = data.as_ptr() as *const u32; + let words = data.len() as u32 / load_size; + for _ in 0..words { + let v = unsafe { src.read() }; + unsafe { core::ptr::write_volatile(cur as *mut u32, v) }; + wait_wr_busy(); + cur += load_size; + src = unsafe { src.add(1) }; + } + + FLASH.ctlr().modify(|w| w.set_pgstart(true)); + wait_busy(); + FLASH.ctlr().modify(|w| { + w.set_page_pg(false); + w.set_btpg(false); + }); +} + +pub fn mass_erase() { + wait_busy(); + FLASH.ctlr().modify(|w| w.set_mer(true)); + FLASH.ctlr().modify(|w| w.set_strt(true)); + wait_busy(); + FLASH.ctlr().modify(|w| w.set_mer(false)); +} + +pub fn options_erase() { + wait_busy(); + FLASH.ctlr().modify(|w| w.set_ober(true)); + FLASH.ctlr().modify(|w| w.set_strt(true)); + wait_busy(); + FLASH.ctlr().modify(|w| w.set_ober(false)); +} + +fn options_program_halfword(addr: u32, val: u16) { + wait_busy(); + FLASH.ctlr().modify(|w| w.set_obpg(true)); + unsafe { core::ptr::write_volatile(addr as *mut u16, val) }; + wait_busy(); + FLASH.ctlr().modify(|w| w.set_obpg(false)); +} + +pub fn options_program(addr: u32, data: &[u8]) -> Result<(), core::num::NonZeroU32> { + let step = OPT_WRITE_SIZE as usize; + if (addr as usize % step) != 0 || data.len() % step != 0 { + return Err(ERR_NOT_SUPPORTED); + } + let mut cur = addr; + let mut src = data.as_ptr() as *const u16; + let halfwords = data.len() / step; + for _ in 0..halfwords { + let hw = unsafe { src.read() }; + options_program_halfword(cur, hw); + cur += step as u32; + src = unsafe { src.add(1) }; + } + Ok(()) +} diff --git a/algos/x0/.cargo/config.toml b/algos/x0/.cargo/config.toml new file mode 100644 index 0000000..ed1ea4a --- /dev/null +++ b/algos/x0/.cargo/config.toml @@ -0,0 +1,11 @@ +[build] +target = "riscv32imac-unknown-none-elf" + +[unstable] +build-std = ["core", "compiler_builtins"] + +[target.'cfg(all(target_arch = "riscv32", target_os = "none"))'] +rustflags = [ + "-C", "link-arg=-Tmemory.x", + "-C", "link-arg=--defsym=ALGO_PLACEMENT_START_ADDRESS=0x20000020", +] diff --git a/algos/x0/Cargo.toml b/algos/x0/Cargo.toml new file mode 100644 index 0000000..127c5e9 --- /dev/null +++ b/algos/x0/Cargo.toml @@ -0,0 +1,34 @@ +[package] +name = "flash-algo-x0" +version = "0.1.0" +edition.workspace = true +license.workspace = true +repository.workspace = true +authors.workspace = true +publish = false + +[lib] +name = "flash_algo_x0" + +[[bin]] +name = "usr" +test = false +doctest = false +bench = false + +[[bin]] +name = "sys" +test = false +doctest = false +bench = false + +[[bin]] +name = "ob" +test = false +doctest = false +bench = false + +[dependencies] +ch32-metapac = { workspace = true, features = ["pac", "ch32x035r8t6"] } +flash-algorithm = { workspace = true } +flash-algo-common = { path = "../common" } diff --git a/algos/x0/src/bin/ob.rs b/algos/x0/src/bin/ob.rs new file mode 100644 index 0000000..20662c4 --- /dev/null +++ b/algos/x0/src/bin/ob.rs @@ -0,0 +1,49 @@ +#![no_std] +#![no_main] + +use flash_algo_x0::*; +use flash_algorithm::{ErrorCode, FlashAlgorithm, Function, algorithm}; + +struct Algo; + +algorithm!(Algo, { + device_name: "CH32 X0 OB", + device_type: DeviceType::Onchip, + flash_address: OPT_BASE, + flash_size: 0, + page_size: OPT_PAGE_SIZE, + empty_value: 0xFF, + program_time_out: PROGRAM_TIMEOUT_MS, + erase_time_out: ERASE_TIMEOUT_MS, + sectors: [{ + size: OPT_PAGE_SIZE, + address: OPT_BASE, + }] +}); + +impl FlashAlgorithm for Algo { + fn new(_addr: u32, _clock: u32, _function: Function) -> Result { + unlock_options(); + Ok(Self) + } + + fn erase_all(&mut self) -> Result<(), ErrorCode> { + Err(ERR_NOT_SUPPORTED) + } + + fn erase_sector(&mut self, _addr: u32) -> Result<(), ErrorCode> { + options_erase(); + Ok(()) + } + + fn program_page(&mut self, addr: u32, data: &[u8]) -> Result<(), ErrorCode> { + options_program(addr, data)?; + Ok(()) + } +} + +impl Drop for Algo { + fn drop(&mut self) { + lock_main(); + } +} diff --git a/algos/x0/src/bin/sys.rs b/algos/x0/src/bin/sys.rs new file mode 100644 index 0000000..97c7f54 --- /dev/null +++ b/algos/x0/src/bin/sys.rs @@ -0,0 +1,56 @@ +#![no_std] +#![no_main] + +use flash_algo_x0::*; +use flash_algorithm::{ErrorCode, FlashAlgorithm, Function, algorithm}; + +/// `saved_boot` restores `STATR.BOOT_MODE` in `Drop` — without it, flashing +/// SYS would silently redirect the next reset into the bootloader. +struct Algo { + saved_boot: bool, +} + +algorithm!(Algo, { + device_name: "CH32 X0 SYS", + device_type: DeviceType::Onchip, + flash_address: SYS_BASE, + flash_size: 0, + page_size: SYS_PAGE_SIZE, + empty_value: 0xFF, + program_time_out: PROGRAM_TIMEOUT_MS, + erase_time_out: ERASE_TIMEOUT_MS, + sectors: [{ + size: SYS_PAGE_SIZE, + address: SYS_BASE, + }] +}); + +impl FlashAlgorithm for Algo { + fn new(_addr: u32, _clock: u32, _function: Function) -> Result { + let saved_boot = boot_mode(); + unlock_main(); + set_boot_mode(true); + Ok(Self { saved_boot }) + } + + fn erase_all(&mut self) -> Result<(), ErrorCode> { + Err(ERR_NOT_SUPPORTED) + } + + fn erase_sector(&mut self, addr: u32) -> Result<(), ErrorCode> { + fast_page_erase(addr, SYS_PAGE_SIZE); + Ok(()) + } + + fn program_page(&mut self, addr: u32, data: &[u8]) -> Result<(), ErrorCode> { + fast_page_program(addr, data, SYS_PAGE_SIZE, SYS_LOAD); + Ok(()) + } +} + +impl Drop for Algo { + fn drop(&mut self) { + set_boot_mode(self.saved_boot); + lock_main(); + } +} diff --git a/algos/x0/src/bin/usr.rs b/algos/x0/src/bin/usr.rs new file mode 100644 index 0000000..3767636 --- /dev/null +++ b/algos/x0/src/bin/usr.rs @@ -0,0 +1,50 @@ +#![no_std] +#![no_main] + +use flash_algo_x0::*; +use flash_algorithm::{ErrorCode, FlashAlgorithm, Function, algorithm}; + +struct Algo; + +algorithm!(Algo, { + device_name: "CH32 X0 USR", + device_type: DeviceType::Onchip, + flash_address: USR_BASE, + flash_size: 0, + page_size: USR_PAGE_SIZE, + empty_value: 0xFF, + program_time_out: PROGRAM_TIMEOUT_MS, + erase_time_out: ERASE_TIMEOUT_MS, + sectors: [{ + size: USR_PAGE_SIZE, + address: USR_BASE, + }] +}); + +impl FlashAlgorithm for Algo { + fn new(_addr: u32, _clock: u32, _function: Function) -> Result { + unlock_main(); + Ok(Self) + } + + fn erase_all(&mut self) -> Result<(), ErrorCode> { + mass_erase(); + Ok(()) + } + + fn erase_sector(&mut self, addr: u32) -> Result<(), ErrorCode> { + fast_page_erase(addr | USR_BASE, USR_PAGE_SIZE); + Ok(()) + } + + fn program_page(&mut self, addr: u32, data: &[u8]) -> Result<(), ErrorCode> { + fast_page_program(addr | USR_BASE, data, USR_PAGE_SIZE, USR_LOAD); + Ok(()) + } +} + +impl Drop for Algo { + fn drop(&mut self) { + lock_main(); + } +} diff --git a/algos/x0/src/lib.rs b/algos/x0/src/lib.rs new file mode 100644 index 0000000..a7c659a --- /dev/null +++ b/algos/x0/src/lib.rs @@ -0,0 +1,133 @@ +#![no_std] + +pub use flash_algo_common::*; + +pub const USR_BASE: u32 = region("USR_1").address; +const USR_FAST: (u32, u32) = fast(region("USR_1")); +pub const USR_PAGE_SIZE: u32 = USR_FAST.0; +pub const USR_LOAD: u32 = USR_FAST.1; + +pub const SYS_BASE: u32 = region("SYS_1").address; +const SYS_FAST: (u32, u32) = fast(region("SYS_1")); +pub const SYS_PAGE_SIZE: u32 = SYS_FAST.0; +pub const SYS_LOAD: u32 = SYS_FAST.1; + +pub const OPT_BASE: u32 = region("OPT").address; +const OPT_FAST: (u32, u32) = fast(region("OPT")); +pub const OPT_PAGE_SIZE: u32 = OPT_FAST.0; +pub const OPT_LOAD: u32 = OPT_FAST.1; + +pub fn wait_busy() { + while FLASH.statr().read().bsy() {} + FLASH.statr().write(|w| w.set_eop(true)); +} + +pub fn unlock_main() { + FLASH.keyr().write(|w| w.set_keyr(KEY1)); + FLASH.keyr().write(|w| w.set_keyr(KEY2)); + FLASH.modekeyr().write(|w| w.set_modekeyr(KEY1)); + FLASH.modekeyr().write(|w| w.set_modekeyr(KEY2)); +} + +/// X0 OB also flows through the fast-mode buffer, so both LOCK and FLOCK +/// must be cleared in addition to OBWRE. +pub fn unlock_options() { + FLASH.keyr().write(|w| w.set_keyr(KEY1)); + FLASH.keyr().write(|w| w.set_keyr(KEY2)); + FLASH.modekeyr().write(|w| w.set_modekeyr(KEY1)); + FLASH.modekeyr().write(|w| w.set_modekeyr(KEY2)); + FLASH.obkeyr().write(|w| w.set_optkey(KEY1)); + FLASH.obkeyr().write(|w| w.set_optkey(KEY2)); +} + +/// `STATR.BOOT_MODE` survives soft reboots and decides the next-reset boot +/// source — snapshot in `Init`, restore in `UnInit`. +pub fn boot_mode() -> bool { + FLASH.statr().read().boot_mode() +} + +pub fn set_boot_mode(mode: bool) { + FLASH.boot_modekeyp().write(|w| w.set_modekeyr(KEY1)); + FLASH.boot_modekeyp().write(|w| w.set_modekeyr(KEY2)); + FLASH.statr().write(|w| w.set_boot_mode(mode)); +} + +pub fn lock_main() { + FLASH.ctlr().write(|w| { + w.set_lock(true); + w.set_flock(true); + }); +} + +pub fn fast_page_erase(addr: u32, _page_size: u32) { + wait_busy(); + FLASH.ctlr().write(|w| w.set_fter(true)); + FLASH.addr().write(|w| w.set_far(addr)); + FLASH.ctlr().write(|w| { + w.set_fter(true); + w.set_strt(true); + }); + wait_busy(); + FLASH.ctlr().modify(|w| w.set_fter(false)); +} + +pub fn fast_page_program(addr: u32, data: &[u8], page_size: u32, load_size: u32) { + let page_base = addr & !(page_size - 1); + + wait_busy(); + FLASH.ctlr().write(|w| w.set_ftpg(true)); + FLASH.ctlr().write(|w| { + w.set_ftpg(true); + w.set_bufrst(true); + }); + wait_busy(); + + let mut buf_addr = addr; + let mut src = data.as_ptr() as *const u32; + let words = data.len() as u32 / load_size; + for _ in 0..words { + let v = unsafe { src.read() }; + unsafe { core::ptr::write_volatile(buf_addr as *mut u32, v) }; + FLASH.ctlr().write(|w| { + w.set_ftpg(true); + w.set_bufload(true); + }); + wait_busy(); + buf_addr += load_size; + src = unsafe { src.add(1) }; + } + + FLASH.addr().write(|w| w.set_far(page_base)); + FLASH.ctlr().write(|w| { + w.set_ftpg(true); + w.set_strt(true); + }); + wait_busy(); + FLASH.ctlr().modify(|w| w.set_ftpg(false)); +} + +/// MER targets only USR. Caller must ensure BOOT_MODE=0. +pub fn mass_erase() { + wait_busy(); + FLASH.ctlr().modify(|w| w.set_mer(true)); + FLASH.ctlr().modify(|w| w.set_strt(true)); + wait_busy(); + FLASH.ctlr().modify(|w| w.set_mer(false)); +} + +pub fn options_erase() { + wait_busy(); + FLASH.ctlr().modify(|w| w.set_ober(true)); + FLASH.ctlr().modify(|w| w.set_strt(true)); + wait_busy(); + FLASH.ctlr().modify(|w| w.set_ober(false)); +} + +pub fn options_program(addr: u32, data: &[u8]) -> Result<(), core::num::NonZeroU32> { + let step = OPT_LOAD as usize; + if (addr as usize % step) != 0 || data.len() % step != 0 { + return Err(ERR_NOT_SUPPORTED); + } + fast_page_program(addr, data, OPT_PAGE_SIZE, OPT_LOAD); + Ok(()) +} diff --git a/ch32v003/.cargo/config.toml b/ch32v003/.cargo/config.toml deleted file mode 100644 index 98b0344..0000000 --- a/ch32v003/.cargo/config.toml +++ /dev/null @@ -1,26 +0,0 @@ -[target.riscv32ec-unknown-none-elf] -rustflags = [ - "-C", - "link-arg=--nmagic", - "-C", - "link-arg=-Tlink.x", - "-C", - "link-arg=-Tmemory.x", - # Code-size optimizations. - # This requires nightly atm. - # "-Z", - # "trap-unreachable=no", - "-C", - "inline-threshold=5", - "-C", - "no-vectorize-loops", - "-C", - "force-frame-pointers=no", -] -runner = "target-gen test template.yaml target/definition.yaml" - -[unstable] -build-std = ["core", "compiler_builtins"] - -[build] -target = "riscv32ec-unknown-none-elf" diff --git a/ch32v003/Cargo.toml b/ch32v003/Cargo.toml deleted file mode 100644 index 1866343..0000000 --- a/ch32v003/Cargo.toml +++ /dev/null @@ -1,52 +0,0 @@ -[package] -authors = ["Andelf "] -edition = "2021" -readme = "README.md" -name = "ch32v003" -version = "0.1.0" - -[dependencies] -ch32v0 = { version = "0.1.7", features = ["ch32v003"] } -flash-algorithm = { version = "0.4.0", default-features = false, features = [ - "erase-chip", -] } -panic-halt = "0.2.0" -# rtt-target = { version = "0.3", features = ["cortex-m"] } - -# this lets you use `cargo fix`! -[[bin]] -name = "ch32v003" -test = false -bench = false - -[profile.dev] -codegen-units = 1 -debug = 2 -debug-assertions = true -incremental = false -opt-level = 3 -overflow-checks = true - -[profile.release] -codegen-units = 1 -debug = 2 -debug-assertions = false -incremental = false -lto = "fat" -opt-level = 's' -overflow-checks = false - -# do not optimize proc-macro crates = faster builds from scratch -[profile.dev.build-override] -codegen-units = 8 -debug = false -debug-assertions = false -opt-level = 0 -overflow-checks = false - -[profile.release.build-override] -codegen-units = 8 -debug = false -debug-assertions = false -opt-level = 0 -overflow-checks = false diff --git a/ch32v003/LICENSE-APACHE b/ch32v003/LICENSE-APACHE deleted file mode 100644 index 16fe87b..0000000 --- a/ch32v003/LICENSE-APACHE +++ /dev/null @@ -1,201 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - -TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - -1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - -2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - -3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - -4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - -5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - -6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - -7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - -8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - -9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - -END OF TERMS AND CONDITIONS - -APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - -Copyright [yyyy] [name of copyright owner] - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. diff --git a/ch32v003/LICENSE-MIT b/ch32v003/LICENSE-MIT deleted file mode 100644 index 31aa793..0000000 --- a/ch32v003/LICENSE-MIT +++ /dev/null @@ -1,23 +0,0 @@ -Permission is hereby granted, free of charge, to any -person obtaining a copy of this software and associated -documentation files (the "Software"), to deal in the -Software without restriction, including without -limitation the rights to use, copy, modify, merge, -publish, distribute, sublicense, and/or sell copies of -the Software, and to permit persons to whom the Software -is furnished to do so, subject to the following -conditions: - -The above copyright notice and this permission notice -shall be included in all copies or substantial portions -of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF -ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED -TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A -PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT -SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR -IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -DEALINGS IN THE SOFTWARE. diff --git a/ch32v003/README.md b/ch32v003/README.md deleted file mode 100644 index 3cb6f88..0000000 --- a/ch32v003/README.md +++ /dev/null @@ -1,54 +0,0 @@ -# Flash Algorithm Template - -This is a flash algorithm template for writing CMSIS-Pack flash algorithms in Rust. -It can be used to generate new flash algoritms for usage with `probe-rs`. - -[![Actions Status](https://img.shields.io/github/actions/workflow/status/probe-rs/flash-algorithm-template/ci.yml?branch=master)](https://github.com/probe-rs/flash-algorithm-template/actions) [![chat](https://img.shields.io/badge/chat-probe--rs%3Amatrix.org-brightgreen)](https://matrix.to/#/#probe-rs:matrix.org) - -## Dependencies - -Run the following requirements: - -```bash -cargo install cargo-generate cargo-binutils target-gen -rustup component add llvm-tools-preview -``` - -## Instantiating the template - -Run - -```bash -cargo generate gh:probe-rs/flash-algorithm-template -``` - -or - -```bash -cargo generate gh:probe-rs/flash-algorithm-template --name=algorithm \ --d target-arch=thumbv7em-none-eabi \ --d ram-start-address=0x20000000 \ --d ram-size=0x4000 \ --d flash-start-address=0x0 \ --d flash-size=0x40000 -``` - -to generate a new project from the template. - -## Developing the algorithm - -Just run `cargo run`. It spits out the flash algo in the probe-rs YAML format and downloads it onto a target and makes a test run. -You will also be able to see RTT messages. - -You can find the generated YAML in `target/definition.yaml`. - -# License - -This thingy is licensed under either of - -- Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or - http://www.apache.org/licenses/LICENSE-2.0) - -- MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) - -at your option. diff --git a/ch32v003/link.x b/ch32v003/link.x deleted file mode 100644 index 8b8639f..0000000 --- a/ch32v003/link.x +++ /dev/null @@ -1 +0,0 @@ -ALGO_PLACEMENT_START_ADDRESS = 0x20000020; diff --git a/ch32v003/src/main.rs b/ch32v003/src/main.rs deleted file mode 100644 index b5509df..0000000 --- a/ch32v003/src/main.rs +++ /dev/null @@ -1,147 +0,0 @@ -#![no_std] -#![no_main] - -use panic_halt as _; - -use ch32v0::ch32v003 as pac; -use flash_algorithm::*; - -const KEY1: u32 = 0x45670123; -const KEY2: u32 = 0xCDEF89AB; - -struct Algorithm; - -algorithm!(Algorithm, { - flash_address: 0x00000000, - flash_size: 0x4000, - page_size: 64, - empty_value: 0xFF, - sectors: [{ - size: 1024, - address: 0x00000000, - }] -}); - -/// Refer: CH32V003RM.PDF -/// Refer: CH32V003EVT.zip:EXAM/FLASH/FLASH_Program -impl FlashAlgorithm for Algorithm { - fn new(_address: u32, _clock: u32, _function: Function) -> Result { - unsafe { - // unlock normal program mode - let rb = &*pac::FLASH::PTR; - rb.keyr.write(|w| w.bits(KEY1)); - rb.keyr.write(|w| w.bits(KEY2)); - // unlock fast program mode - rb.modekeyr.write(|w| w.bits(KEY1)); - rb.modekeyr.write(|w| w.bits(KEY2)); - } - - Ok(Self) - } - - fn erase_all(&mut self) -> Result<(), ErrorCode> { - unsafe { - let rb = &*pac::FLASH::PTR; - - if rb.ctlr.read().lock().bit_is_set() { - return Err(ErrorCode::new(0x1001).unwrap()); // flash is locked - } - - rb.ctlr.modify(|_, w| w.mer().set_bit()); // master erase - rb.ctlr.modify(|_, w| w.strt().set_bit()); // start erase - while rb.statr.read().bsy().bit_is_set() {} // wait for erase done - - rb.statr.modify(|_, w| w.eop().clear_bit()); // clear eop flag - rb.ctlr.modify(|_, w| w.mer().clear_bit()); // clear erase all mode - } - - Ok(()) - } - - fn erase_sector(&mut self, mut addr: u32) -> Result<(), ErrorCode> { - // Address fix, 0x08000000 is remapped to 0x00000000 when running - if addr < 0x08000000 { - addr += 0x08000000; - } - if addr & 0x3FF != 0 { - return Err(ErrorCode::new(0x1000).unwrap()); // invalid address - } - unsafe { - let rb = &*pac::FLASH::PTR; - - if rb.ctlr.read().lock().bit_is_set() { - return Err(ErrorCode::new(0x1001).unwrap()); // flash is locked - } - - rb.ctlr.modify(|_, w| w.per().set_bit()); // sector erase(1K) - rb.addr.write(|w| w.bits(addr)); - rb.ctlr.modify(|_, w| w.strt().set_bit()); - while rb.statr.read().bsy().bit_is_set() {} - - rb.statr.modify(|_, w| w.eop().clear_bit()); - rb.ctlr.modify(|_, w| w.per().clear_bit()); - } - Ok(()) - } - - fn program_page(&mut self, mut addr: u32, data: &[u8]) -> Result<(), ErrorCode> { - // Address fix, 0x08000000 is remapped to 0x00000000 when running - if addr < 0x08000000 { - addr += 0x08000000; - } - - if addr & 0x3F != 0 { - return Err(ErrorCode::new(0x1000).unwrap()); // invalid address - } - unsafe { - let rb = &*pac::FLASH::PTR; - - if rb.ctlr.read().lock().bit_is_set() { - return Err(ErrorCode::new(0x1001).unwrap()); // flash is locked - } - - rb.ctlr.modify(|_, w| w.page_pg().set_bit()); - rb.ctlr.modify(|_, w| w.bufrst().set_bit()); - while rb.statr.read().bsy().bit_is_set() {} // wait for buffer reset done - - let mut start_addr = addr as *mut u32; - let data_word = data.as_ptr() as *const u32; - for i in 0..16 { - start_addr.write_volatile(data_word.offset(i).read_volatile()); - start_addr = start_addr.offset(1); // inc - - rb.ctlr.modify(|_, w| w.bufload().set_bit()); - while rb.statr.read().bsy().bit_is_set() {} // wait for buffer reset done - } - - rb.ctlr.modify(|_, w| w.page_pg().set_bit()); - rb.addr.write(|w| w.bits(addr)); - rb.ctlr.modify(|_, w| w.strt().set_bit()); - while rb.statr.read().bsy().bit_is_set() {} // wait for program done - - rb.ctlr.modify(|_, w| w.page_pg().clear_bit()); - - // verify - let mut start_addr = addr as *mut u32; - for i in 0..16 { - let data = data.as_ptr().offset(i * 4) as *const u32; - if start_addr.read_volatile() != data.read_volatile() { - loop {} // verify failed - } - start_addr = start_addr.offset(1); - } - } - Ok(()) - } -} - -impl Drop for Algorithm { - fn drop(&mut self) { - unsafe { - let rb = &*pac::FLASH::PTR; - - // lock FLASH again - rb.ctlr.modify(|_, w| w.lock().set_bit().flock().set_bit()); - } - } -} diff --git a/ch32v003/template.yaml b/ch32v003/template.yaml deleted file mode 100644 index a76344d..0000000 --- a/ch32v003/template.yaml +++ /dev/null @@ -1,49 +0,0 @@ -name: ch32v003 -variants: - - name: CH32V003 - cores: - - name: main - type: riscv - core_access_options: !Riscv {} - memory_map: - - !Ram - range: - start: 0x20000000 - end: 0x20000800 - is_boot_memory: false - cores: - - main - - !Nvm - range: - start: 0x00000000 - end: 0x00004000 - is_boot_memory: true - cores: - - main - flash_algorithms: - - algorithm-test -flash_algorithms: - - name: algorithm-test - description: A flash algorithm under test - cores: - - main - default: true - instructions: sLUURgxNSEYAIUFRQBlBYIFgwWAQRgDwK/oAKATQSUZJGYhgzGCwvUhGQBkBIUFgAkgBYAAgsL0EAAAACAYAQBC1BEYAIADwE/oNSUpGURgAKALQiGDMYALgiWgAKQ3QCEkBIgpgBkpLRpxYTGCaGFNoi2CTaMtg0mgKYRC9wEYEAAAAAEAAILC1DUhJRgEiClAIGAAkRGCEYMRgBeAgRgDwFvoA8I75BBkA8Iv5BUYA8I75aEOEQvHTACCwvcBGBAAAAPi1BEYXTkhGAieHUYAZACFBYIFgwWAA8HP5AUYgRgDwD/pIRgApAtCAGQMhFeCAGQEhQWAA8GT5BUYA8Gf5aEOgQgfZSEaAGUdgIEYA8N75ACAF4EhGgBkEIYFgxGABIAGw8L0EAAAA/rUORgdGNExIRgMlBVEAGQAhQWCBYMFguAcE0EhGABkDIYFgCuBIRgAZASFBYLAHC9BIRgAZAyGBYDdGSEYAGcdgASYwRgOw8L0CkkhGABkCIUFgAPAg+QGQAPAj+QGZSEO4QhTZSEYAGUVgJUb0GQDwEvkBkADwFfkBmUhDhEIK2UhGQBkEIYFgJ0YsRtXnSEYAGQQhxOdIRkAZBCFBYLAIAZAAJjxGNUYJ4CBoQBwK0QKZAckCkQHEAPBf+W0cAZiFQvLTvedIRgNMABkFIYFgfxmy58BGBAAAAP61FUYERjVKSEYEJ4dQgBgAJkZghmDGYKAHW9GIBwXQSEaAGAMhgWDEYFPgApFIRoAYASFBYADwwfgBkADwxPgBmUhDoEIe2UhGJElAGAIhQWACmAAZAZAA8LD4AJAA8LP4AZkAmlBDgUIU2UhGG0qAGIdgwWAA8KH4BUYA8KT4BEZsQybgSEYUSUAYAyGBYAKZwWAe4EhGEElAGEdg8EOBAGIYApmJCEAcFB0rHYhCCtItaFJoqkIiRh1G9NBIRgZJQBgGIa7nSEYESUAYBSFBYAGcIEYDsPC9wEYEAAAA/rUWRg1GBEYtT0hGBSHBUcAZACFBYIFgwWCgBwTQSEbAGQMhgWAK4EhGwBkBIUFgqAcK0EhGwBkDIYFgLEZIRsAZxGABIAOw8L1IRsAZAyFBYADwQ/gCkADwRvgCmUhDoEIW2UhGwBkEIQCRQWAoGQKQAPAz+AGQAPA2+AKaAZlIQ4JCCdlIRsAZBCGBYBRG1edIRsAZBCHE50hGwBkAmUFgYR4AIAJGqkLM0kwcUhxJeLFCIUb30EhGwBkFIbHnBAAAAA8gAAIDSUpoEgICQAh4EENwR8BG4A8A8AFIAGhwR8BGEAAAEAFIAGhwR8BGFAAAEAZIQWj/IhFCBtAFSQpoACBSHADQCGhwRwBocEcoAAAQABAAEP8gAAIDSQloAUAAIEAaSEFwR8BGBBAAEBC1EUlKaP8jGkIG0A9JCmhSHArQC2gBIQ/gC2gAIQMig0IA2BFGCEYQvQlKE2gDIQhMo0IF0VNoAiEDIoNCANgRRsiyEL3ARigAABAAEAAQBDAAANvlsVEBRgEgAykJ2IgABaEIWAhJCGAISQpoACAAKvvQcEfARgAAAAACAAAAAQAAAAAAAAAE5QFAAOQBQAJIAWgAKfzQcEfARgDkAUADSAEhAWADSAFoACn80HBHDOUBQADkAUADSQhgA0gBaAAp/NBwR8BGCOUBQADkAUAESAEhAWAESAFoACn80AAgcEfARhTlAUAA5AFAACIDCYtCLNMDCotCEdMAI5xGTuADRgtDPNQAIkMIi0Ix0wMJi0Ic0wMKi0IB05RGP+DDCYtCAdPLAcAaUkGDCYtCAdOLAcAaUkFDCYtCAdNLAcAaUkEDCYtCAdMLAcAaUkHDCItCAdPLAMAaUkGDCItCAdOLAMAaUkFDCItCAdNLAMAaUkFBGgDSAUZSQRBGcEdd4MoPANBJQgMQANNAQlNAnEYAIgMJi0It0wMKi0IS04kB/CISugMKi0IM04kBkhGLQgjTiQGSEYtCBNOJATrQkhEA4IkJwwmLQgHTywHAGlJBgwmLQgHTiwHAGlJBQwmLQgHTSwHAGlJBAwmLQgHTCwHAGlJBwwiLQgHTywDAGlJBgwiLQgHTiwDAGlJB2dJDCItCAdNLAMAaUkFBGgDSAUZSQRBGY0ZbEAHTQEIAKwDVSUJwR2NGWxAA00BCAbUAIMBGwEYCvQAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= - pc_init: 0x0 - pc_uninit: 0x0 - pc_program_page: 0x0 - pc_erase_sector: 0x0 - pc_erase_all: 0x0 - data_section_offset: 0x0 - # load_address: 0x20000048 - flash_properties: - address_range: - start: 0x0 - end: 0x0 - page_size: 0x0 - erased_byte_value: 0x0 - program_page_timeout: 0x0 - erase_sector_timeout: 0x0 - sectors: - - size: 0x0 - address: 0x0 diff --git a/ch32v307/.cargo/config.toml b/ch32v307/.cargo/config.toml deleted file mode 100644 index c1e1eb4..0000000 --- a/ch32v307/.cargo/config.toml +++ /dev/null @@ -1,23 +0,0 @@ -[target.riscv32imac-unknown-none-elf] -rustflags = [ - "-C", - "link-arg=--nmagic", - "-C", - "link-arg=-Tlink.x", - "-C", - "link-arg=-Tmemory.x", - # Code-size optimizations. - # This requires nightly atm. - # "-Z", - # "trap-unreachable=no", - "-C", - "inline-threshold=5", - "-C", - "no-vectorize-loops", - "-C", - "force-frame-pointers=no", -] -runner = "target-gen test template.yaml target/definition.yaml" - -[build] -target = "riscv32imac-unknown-none-elf" diff --git a/ch32v307/Cargo.toml b/ch32v307/Cargo.toml deleted file mode 100644 index 89e351c..0000000 --- a/ch32v307/Cargo.toml +++ /dev/null @@ -1,49 +0,0 @@ -[package] -authors = ["Marek Vrbka "] -edition = "2021" -readme = "README.md" -name = "ch32v307" -version = "0.1.0" - -[dependencies] -ch32v3 = { version = "0.1.6", features = ["ch32v30x"] } -flash-algorithm = { version = "0.4.0", default-features = false } -panic-halt = "0.2.0" - -# this lets you use `cargo fix`! -[[bin]] -name = "ch32v307" -test = false -bench = false - -[profile.dev] -codegen-units = 1 -debug = 2 -debug-assertions = true -incremental = false -opt-level = 3 -overflow-checks = true - -[profile.release] -codegen-units = 1 -debug = 2 -debug-assertions = false -incremental = false -lto = "fat" -opt-level = 's' -overflow-checks = false - -# do not optimize proc-macro crates = faster builds from scratch -[profile.dev.build-override] -codegen-units = 8 -debug = false -debug-assertions = false -opt-level = 0 -overflow-checks = false - -[profile.release.build-override] -codegen-units = 8 -debug = false -debug-assertions = false -opt-level = 0 -overflow-checks = false diff --git a/ch32v307/LICENSE-APACHE b/ch32v307/LICENSE-APACHE deleted file mode 100644 index 16fe87b..0000000 --- a/ch32v307/LICENSE-APACHE +++ /dev/null @@ -1,201 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - -TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - -1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - -2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - -3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - -4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - -5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - -6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - -7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - -8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - -9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - -END OF TERMS AND CONDITIONS - -APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - -Copyright [yyyy] [name of copyright owner] - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. diff --git a/ch32v307/LICENSE-MIT b/ch32v307/LICENSE-MIT deleted file mode 100644 index 31aa793..0000000 --- a/ch32v307/LICENSE-MIT +++ /dev/null @@ -1,23 +0,0 @@ -Permission is hereby granted, free of charge, to any -person obtaining a copy of this software and associated -documentation files (the "Software"), to deal in the -Software without restriction, including without -limitation the rights to use, copy, modify, merge, -publish, distribute, sublicense, and/or sell copies of -the Software, and to permit persons to whom the Software -is furnished to do so, subject to the following -conditions: - -The above copyright notice and this permission notice -shall be included in all copies or substantial portions -of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF -ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED -TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A -PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT -SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR -IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -DEALINGS IN THE SOFTWARE. diff --git a/ch32v307/README.md b/ch32v307/README.md deleted file mode 100644 index 3cb6f88..0000000 --- a/ch32v307/README.md +++ /dev/null @@ -1,54 +0,0 @@ -# Flash Algorithm Template - -This is a flash algorithm template for writing CMSIS-Pack flash algorithms in Rust. -It can be used to generate new flash algoritms for usage with `probe-rs`. - -[![Actions Status](https://img.shields.io/github/actions/workflow/status/probe-rs/flash-algorithm-template/ci.yml?branch=master)](https://github.com/probe-rs/flash-algorithm-template/actions) [![chat](https://img.shields.io/badge/chat-probe--rs%3Amatrix.org-brightgreen)](https://matrix.to/#/#probe-rs:matrix.org) - -## Dependencies - -Run the following requirements: - -```bash -cargo install cargo-generate cargo-binutils target-gen -rustup component add llvm-tools-preview -``` - -## Instantiating the template - -Run - -```bash -cargo generate gh:probe-rs/flash-algorithm-template -``` - -or - -```bash -cargo generate gh:probe-rs/flash-algorithm-template --name=algorithm \ --d target-arch=thumbv7em-none-eabi \ --d ram-start-address=0x20000000 \ --d ram-size=0x4000 \ --d flash-start-address=0x0 \ --d flash-size=0x40000 -``` - -to generate a new project from the template. - -## Developing the algorithm - -Just run `cargo run`. It spits out the flash algo in the probe-rs YAML format and downloads it onto a target and makes a test run. -You will also be able to see RTT messages. - -You can find the generated YAML in `target/definition.yaml`. - -# License - -This thingy is licensed under either of - -- Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or - http://www.apache.org/licenses/LICENSE-2.0) - -- MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) - -at your option. diff --git a/ch32v307/link.x b/ch32v307/link.x deleted file mode 100644 index 8b8639f..0000000 --- a/ch32v307/link.x +++ /dev/null @@ -1 +0,0 @@ -ALGO_PLACEMENT_START_ADDRESS = 0x20000020; diff --git a/ch32v307/src/main.rs b/ch32v307/src/main.rs deleted file mode 100644 index 6c00697..0000000 --- a/ch32v307/src/main.rs +++ /dev/null @@ -1,136 +0,0 @@ -#![no_std] -#![no_main] - -use core::num::NonZeroU32; -use panic_halt as _; - -use flash_algorithm::*; - -use ch32v3::ch32v30x as pac; - -struct Algorithm; - -const FLASH_KEY1: u32 = 0x45670123; -const FLASH_KEY2: u32 = 0xCDEF89AB; - -const ERASE_TIMEOUT: u32 = 0xF00000; - -algorithm!(Algorithm, { - flash_address: 0x0000000, - flash_size: 0x40000, - page_size: 0x100, - // Note: This is not correct, each erased word looks like: 0xe339e339 - empty_value: 0x39, - sectors: [{ - size: 0x8000, - address: 0x0000000, - }] -}); - -unsafe fn wait_until_not_write_busy() -> Result<(), ErrorCode> { - let flash = &*pac::FLASH::ptr(); - for _ in 0..ERASE_TIMEOUT { - let status = flash.statr.read(); - if status.wr_bsy().bit_is_set() { - continue; - } - if status.wrprterr().bit() { - return Err(ErrorCode::new(status.bits()).unwrap()); - } - return Ok(()); - } - return Err(NonZeroU32::new(2_u32).unwrap()) -} - -unsafe fn wait_until_not_busy() -> Result<(), ErrorCode> { - let flash = &*pac::FLASH::ptr(); - for _ in 0..ERASE_TIMEOUT { - let status = flash.statr.read(); - if status.bsy().bit_is_set() && status.eop().bit_is_clear() { - continue; - } - if status.wrprterr().bit() { - return Err(ErrorCode::new(status.bits()).unwrap()); - } - flash.statr.modify(|_, w| w.eop().clear_bit()); - return Ok(()); - } - return Err(NonZeroU32::new(1_u32).unwrap()) -} - -impl FlashAlgorithm for Algorithm { - fn new(_address: u32, _clock: u32, _function: Function) -> Result { - // Unlock the flash - unsafe { - let flash = &*pac::FLASH::ptr(); - flash.keyr.write(|w| w.bits(FLASH_KEY1)); - flash.keyr.write(|w| w.bits(FLASH_KEY2)); - - flash.modekeyr.write(|w| w.bits(FLASH_KEY1)); - flash.modekeyr.write(|w| w.bits(FLASH_KEY2)); - } - - Ok(Self) - } - - fn erase_sector(&mut self, addr: u32) -> Result<(), ErrorCode> { - let addr = addr + 0x8000000; - if addr & 0x7FFF != 0 { - return Err(ErrorCode::from(NonZeroU32::new(addr).unwrap())); - } - unsafe { wait_until_not_busy()?; } - - unsafe { - let flash = &*pac::FLASH::ptr(); - flash.ctlr.modify(|_, w| w.ber32().set_bit()); - flash.addr.write(|w| w.bits(addr)); - flash.ctlr.modify(|_, w| w.strt().set_bit()); - - wait_until_not_busy()?; - - flash.ctlr.modify(|_, w| w.ber32().clear_bit()); - } - Ok(()) - } - - fn program_page(&mut self, addr: u32, data: &[u8]) -> Result<(), ErrorCode> { - let flash = unsafe { &*pac::FLASH::ptr() }; - if flash.ctlr.read().lock().bit_is_set() || flash.ctlr.read().flock().bit_is_set() { - return Err(ErrorCode::from(NonZeroU32::new(3).unwrap())); - } - let addr = (addr + 0x8000000) as usize; - if addr & 0xFF != 0 { - return Err(ErrorCode::from(NonZeroU32::new(addr as u32).unwrap())); - } - unsafe { - flash.ctlr.modify(|_, w| w.page_pg().set_bit()); - wait_until_not_busy().map_err(|e| e.checked_add(10).unwrap())?; - flash - }; - - for (word, addr) in data.chunks_exact(4).zip((addr..).step_by(4)) { - let word = u32::from_le_bytes(word.try_into().unwrap()); - unsafe { - (addr as *mut u32).write_volatile(word); - wait_until_not_write_busy()?; - }; - } - - unsafe { - flash.ctlr.modify(|_, w| w.pgstart().set_bit()); - wait_until_not_busy()?; - flash.ctlr.modify(|_, w| w.page_pg().clear_bit()); - } - Ok(()) - } -} - -impl Drop for Algorithm { - fn drop(&mut self) { - unsafe { - let flash = &*pac::FLASH::ptr(); - // Lock the flash - flash.ctlr.modify(|_, w| w.lock().set_bit()); - } - } -} diff --git a/ch32v307/template.yaml b/ch32v307/template.yaml deleted file mode 100644 index f7db8d8..0000000 --- a/ch32v307/template.yaml +++ /dev/null @@ -1,49 +0,0 @@ -name: ch32v3 Series -variants: - - name: ch32v307 - cores: - - name: main - type: riscv - core_access_options: !Riscv {} - memory_map: - - !Ram - range: - start: 0x20000000 - end: 0x20010000 - is_boot_memory: false - cores: - - main - - !Nvm - range: - start: 0x0000000 - end: 0x0040000 - is_boot_memory: true - cores: - - main - flash_algorithms: - - ch32v3xx-flash-algo -flash_algorithms: - - name: ch32v3xx-flash-algo - description: Flash algoritm for the ch32v3xx series - cores: - - main - default: true - instructions: twUAIAPFBU8ZxTclAkAUSZPmBggUyQVFfRaNRiOIpU5jdtYCtwVnRZOFNRI3JgJATMK3lu/Nk4a2mlTCTNJU0rcFACAjiKVOAUWCgAERtwUAIJOFhUwuxCrGAsw3BQAgEwXFQCrIAso3BQAgkwUFTSgAlwAAAOeAgCkAALcFACADxgVPBUURyjcmAkAUSgFFk+YGCBTKI4gFToKAtwUAIAPGBU+qhQVFUcY3BgAILpZjZbYIExUWARnBMoWCgLcF8ACFBbcmAkAFR/0VrcXIRpN3FQLji+f+k3UFAaHttyUCQMhFE3X1/cjFiEm3BgQAVY2IydDJiEkTZQUEiMk3BvAABQaFRn0WHcbIRRN3FQLjC9f+k3UFAZHttyUCQMhFE3X1/cjFkEkBRbcG/P/9FnWOkMmCgAVFgoA3BQAgEwUFP7cFACAThkVE8UWXAAAA54DAHgAAtwYAIAPHBk+qhgVFecs3JwJACEuTdwUIDUXh5xhLQgdjQQcMtwIACLaSY+PSEBP18g8ZwRaFgoA3KAJAgyYIAUFn2Y4jKNgAtwfwAIUHBUf9F73PgybIABP1FgLjCuX+E/UGAT3ltyYCQMhGgUcTdfX9yMYT88X/NwjwAAUIYwUDBjOF8gBjY1UMkwIVAGOPAgqTCEYAcRMDRxYAg0cGAINFJgADRjYAIgddj8IFYgbRjdmNDMFChn0WDcbIRpN1JQD9+ZN1BQGNR0aGzdkRqIVGE4WmALM11QATNhUA0Y3B5YKACUWCgLclAkCISTcGIABRjYjJNwbwAAUGhUZ9FhXGyEUTdxUC4wvX/pN1BQHp+bclAkDIRRN19f3IxZBJAUXBdv0WdY6QyYKABUWCgDcFACATBQU/twUAIBOGRUXxRZcAAADngAAKAAA3BQAgEwUFP7cFACAThkU98UWXAAAA54BACAAANwUAIBMFxUC3BQAgE4ZFRpMFsAKXAAAA54BgBgAAAaCCgLfFuACThfXzTMW3JcfQk4UlSAzFtzU1C5OFRUNMwbd1cg+TheX6DMGCgAERBs43BgAgEwYGTjLENwYAIBMGBk4yxirILsoFRSMcoQAoAJcAAADngOD6AAB5cQbWKtIu1EgQKsYFRSrIAs43BQAgEwUFTirKAsxoALKFlwAAAOeAAPsAAC9ydXN0Yy84MmUxNjA4ZGZhNmUwYjU1NjkyMzI1NTllM2QzODVmZWE1YTkzMTEyL2xpYnJhcnkvY29yZS9zcmMvaXRlci9yYW5nZS5ycwAAhAMAIE4AAACPAQAAAQAAAAAAAAAAAAAAAAAAAGF0dGVtcHQgdG8gYWRkIHdpdGggb3ZlcmZsb3djYWxsZWQgYE9wdGlvbjo6dW53cmFwKClgIG9uIGEgYE5vbmVgIHZhbHVlc3JjL21haW4ucnMAADcEACALAAAATQAAABQAAAA3BAAgCwAAAGUAAAAUAAAANwQAIAsAAABrAAAAQQAAAFRoaXMgYnJhbmNoIGNhbiBvbmx5IGJlIHJlYWNoZWQgaWYgdGhlIGhvc3QgbGlicmFyeSBzZW50IGFuIHVua25vd24gZnVuY3Rpb24gY29kZS4AAHQEACBSAAAANwQAIAsAAAASAAAAAQAAAP4CACAAAAAAAQAAAAADACAA - pc_init: 0x0 - pc_uninit: 0x0 - pc_program_page: 0x0 - pc_erase_sector: 0x0 - pc_erase_all: 0x0 - data_section_offset: 0x0 - # load_address: 0x20000048 - flash_properties: - address_range: - start: 0x0 - end: 0x0040000 - page_size: 0x0 - erased_byte_value: 0x0 - program_page_timeout: 0x1000 - erase_sector_timeout: 0x1000 - sectors: - - size: 0x0 - address: 0x0 \ No newline at end of file diff --git a/targets/riscv32ec-unknown-none-elf.json b/targets/riscv32ec-unknown-none-elf.json new file mode 100644 index 0000000..630b8a0 --- /dev/null +++ b/targets/riscv32ec-unknown-none-elf.json @@ -0,0 +1,19 @@ +{ + "arch": "riscv32", + "atomic-cas": false, + "cpu": "generic-rv32", + "crt-objects-fallback": "false", + "data-layout": "e-m:e-p:32:32-i64:64-n32-S32", + "eh-frame-header": false, + "emit-debug-gdb-scripts": false, + "features": "+e,+c,+forced-atomics", + "linker": "rust-lld", + "linker-flavor": "gnu-lld", + "llvm-target": "riscv32", + "llvm-abiname": "ilp32e", + "abi": "ilp32e", + "max-atomic-width": 32, + "panic-strategy": "abort", + "relocation-model": "static", + "target-pointer-width": 32 +} diff --git a/targets/riscv32ec_zmmul-unknown-none-elf.json b/targets/riscv32ec_zmmul-unknown-none-elf.json new file mode 100644 index 0000000..ec70984 --- /dev/null +++ b/targets/riscv32ec_zmmul-unknown-none-elf.json @@ -0,0 +1,19 @@ +{ + "arch": "riscv32", + "atomic-cas": false, + "cpu": "generic-rv32", + "crt-objects-fallback": "false", + "data-layout": "e-m:e-p:32:32-i64:64-n32-S32", + "eh-frame-header": false, + "emit-debug-gdb-scripts": false, + "features": "+e,+c,+zmmul,+forced-atomics", + "linker": "rust-lld", + "linker-flavor": "gnu-lld", + "llvm-target": "riscv32", + "llvm-abiname": "ilp32e", + "abi": "ilp32e", + "max-atomic-width": 32, + "panic-strategy": "abort", + "relocation-model": "static", + "target-pointer-width": 32 +} diff --git a/xtask/Cargo.toml b/xtask/Cargo.toml new file mode 100644 index 0000000..5723f1d --- /dev/null +++ b/xtask/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "xtask" +version = "0.1.0" +edition.workspace = true +license.workspace = true +repository.workspace = true +authors.workspace = true +publish = false + +[dependencies] +anyhow = "1" +goblin = "0.10" +indexmap = "2" +probe-rs-target = { git = "https://github.com/ch32-rs/probe-rs", rev = "8bfc5b87e356934ef0af6ad3ee8f3f6162089327" } +scroll = "0.13" +serde = { version = "1", features = ["derive"] } +serde_json = "1" +serde_yaml = "0.9" diff --git a/xtask/src/algo.rs b/xtask/src/algo.rs new file mode 100644 index 0000000..abc3cc7 --- /dev/null +++ b/xtask/src/algo.rs @@ -0,0 +1,323 @@ +use anyhow::{Context, Result, anyhow, bail}; +use probe_rs_target::{FlashProperties, RawFlashAlgorithm, SectorDescription}; +use std::collections::HashMap; +use std::path::Path; +use std::process::Command; + +pub struct AlgoBlob { + /// Flash IP version (matches `FLASH.registers.version`). + pub silicon: String, + pub arch: String, + /// "usr" | "sys" | "ob". + pub region_kind: String, + pub template: RawFlashAlgorithm, +} + +pub fn build_all(workspace_root: &Path) -> Result> { + let algos_dir = workspace_root.join("algos"); + let mut out = Vec::new(); + + for entry in std::fs::read_dir(&algos_dir)? { + let entry = entry?; + let dir = entry.path(); + if !dir.is_dir() { + continue; + } + let name = entry.file_name().to_string_lossy().to_string(); + if name == "common" { + continue; + } + out.extend(build_crate(&dir, &name)?); + } + + if out.is_empty() { + bail!("no algo crates found under algos/"); + } + Ok(out) +} + +fn build_crate(crate_dir: &Path, crate_name: &str) -> Result> { + eprintln!(" cargo build --release ({})", crate_dir.display()); + let status = Command::new("cargo") + .arg("+nightly") + .arg("build") + .arg("--release") + .current_dir(crate_dir) + .status() + .with_context(|| format!("failed to spawn cargo for {}", crate_dir.display()))?; + if !status.success() { + bail!("cargo build failed in {}", crate_dir.display()); + } + + let triple = read_target_triple(crate_dir)?; + let arch = arch_from_triple(&triple) + .ok_or_else(|| anyhow!("unknown CPU arch in triple `{}`", triple))?; + let silicon = silicon_for_crate(crate_name); + let bin_dir = crate_dir.join("../../target").join(&triple).join("release"); + + let mut out = Vec::new(); + let bin_src = crate_dir.join("src/bin"); + for entry in + std::fs::read_dir(&bin_src).with_context(|| format!("reading {}", bin_src.display()))? + { + let entry = entry?; + let stem = entry + .path() + .file_stem() + .and_then(|s| s.to_str()) + .map(|s| s.to_string()) + .ok_or_else(|| anyhow!("bad bin filename"))?; + let elf_path = bin_dir.join(&stem); + let elf = std::fs::read(&elf_path) + .with_context(|| format!("reading ELF {}", elf_path.display()))?; + let template = + extract_algo(&elf).with_context(|| format!("parsing ELF {}", elf_path.display()))?; + out.push(AlgoBlob { + silicon: silicon.to_string(), + arch: arch.to_string(), + region_kind: stem, + template, + }); + } + Ok(out) +} + +fn silicon_for_crate(name: &str) -> &str { + match name { + "f1" => "v1", + n => n, + } +} + +fn arch_from_triple(triple: &str) -> Option<&'static str> { + if triple.starts_with("riscv") { + Some("riscv") + } else if triple.starts_with("thumb") || triple.starts_with("arm") { + Some("arm") + } else { + None + } +} + +fn read_target_triple(crate_dir: &Path) -> Result { + let cfg = crate_dir.join(".cargo/config.toml"); + let text = + std::fs::read_to_string(&cfg).with_context(|| format!("reading {}", cfg.display()))?; + for line in text.lines() { + let l = line.trim(); + if let Some(rest) = l.strip_prefix("target") { + let rest = rest.trim_start_matches([' ', '=']).trim(); + let raw = rest.trim_matches('"'); + if raw.is_empty() { + continue; + } + // Custom JSON target specs build into a dir named after the + // file stem, not the path literal. + let triple = if raw.ends_with(".json") { + Path::new(raw) + .file_stem() + .and_then(|s| s.to_str()) + .ok_or_else(|| anyhow!("bad target json path {raw} in {}", cfg.display()))? + .to_string() + } else { + raw.to_string() + }; + return Ok(triple); + } + } + bail!("no `target = \"…\"` in {}", cfg.display()); +} + +// ELF extraction ported from probe-rs target-gen (binary-only dependency). + +const CODE_SECTION_KEY: (&str, u32) = ("PrgCode", goblin::elf64::section_header::SHT_PROGBITS); +const DATA_SECTION_KEY: (&str, u32) = ("PrgData", goblin::elf64::section_header::SHT_PROGBITS); +const BSS_SECTION_KEY: (&str, u32) = ("PrgData", goblin::elf64::section_header::SHT_NOBITS); + +struct Section { + start: u32, + length: u32, + data: Vec, + load_address: u32, +} + +struct AlgoElf { + code: Section, + data: Section, + bss: Section, +} + +impl AlgoElf { + fn parse(elf: &goblin::elf::Elf<'_>, buf: &[u8]) -> Result { + let mut code = None; + let mut data = None; + let mut bss = None; + for ph in &elf.program_headers { + if ph.p_type != goblin::elf::program_header::PT_LOAD || ph.p_memsz == 0 { + continue; + } + let seg = ph.p_offset..ph.p_offset + ph.p_memsz; + for sh in &elf.section_headers { + let r = sh.sh_offset..sh.sh_offset + sh.sh_size; + if seg.start <= r.start && r.end <= seg.end { + let bytes = if sh.sh_type == goblin::elf64::section_header::SHT_NOBITS { + Vec::new() + } else { + buf[sh.sh_offset as usize..][..sh.sh_size as usize].to_vec() + }; + let s = Section { + start: sh.sh_addr as u32, + length: sh.sh_size as u32, + data: bytes, + load_address: (ph.p_vaddr + sh.sh_offset - ph.p_offset) as u32, + }; + match (&elf.shdr_strtab[sh.sh_name], sh.sh_type) { + CODE_SECTION_KEY => code = Some(s), + DATA_SECTION_KEY => data = Some(s), + BSS_SECTION_KEY => bss = Some(s), + _ => {} + } + } + } + } + let code = code.ok_or_else(|| anyhow!("PrgCode section missing"))?; + let data = data.unwrap_or_else(|| Section { + start: code.start + code.length, + length: 0, + data: Vec::new(), + load_address: code.load_address + code.length, + }); + let bss = bss.unwrap_or_else(|| Section { + start: data.start + data.length, + length: 0, + data: Vec::new(), + load_address: data.load_address + data.length, + }); + Ok(Self { code, data, bss }) + } + + fn blob(&self) -> Vec { + let mut b = + Vec::with_capacity((self.code.length + self.data.length + self.bss.length) as usize); + b.extend(&self.code.data); + b.extend(&self.data.data); + b.extend(std::iter::repeat_n(0u8, self.bss.length as usize)); + b + } +} + +fn extract_algo(buf: &[u8]) -> Result { + let elf = goblin::elf::Elf::parse(buf)?; + let parsed = AlgoElf::parse(&elf, buf)?; + let dev = read_flash_device(&elf, buf)?; + + let mut algo = RawFlashAlgorithm::default(); + let code_off = parsed.code.start as u64; + let mut syms: HashMap<&str, u64> = HashMap::new(); + for s in elf.syms.iter() { + let name = &elf.strtab[s.st_name]; + if !name.is_empty() { + syms.insert(name, s.st_value); + } + } + let need = |k: &str| -> Result { + syms.get(k) + .copied() + .map(|v| v - code_off) + .ok_or_else(|| anyhow!("ELF missing required symbol {k}")) + }; + algo.pc_init = Some(need("Init")?); + algo.pc_uninit = syms.get("UnInit").copied().map(|v| v - code_off); + algo.pc_program_page = need("ProgramPage")?; + algo.pc_erase_sector = need("EraseSector")?; + algo.pc_erase_all = syms.get("EraseChip").copied().map(|v| v - code_off); + + algo.instructions = parsed.blob(); + algo.load_address = Some(parsed.code.load_address as u64); + algo.data_section_offset = (parsed.data.start - parsed.code.load_address) as u64; + algo.big_endian = !elf.little_endian; + + algo.flash_properties = FlashProperties { + address_range: dev.start..(dev.start + dev.size), + page_size: dev.page_size, + erased_byte_value: dev.erased, + program_page_timeout: dev.program_to, + erase_sector_timeout: dev.erase_to, + sectors: dev + .sectors + .iter() + .map(|(size, address)| SectorDescription { + size: *size as u64, + address: *address as u64, + }) + .collect(), + }; + // probe-rs schema default (Rust default for u64 is 0). + algo.rtt_poll_interval = 20; + Ok(algo) +} + +struct FlashDevice { + start: u64, + size: u64, + page_size: u32, + erased: u8, + program_to: u32, + erase_to: u32, + sectors: Vec<(u32, u32)>, +} + +fn read_flash_device(elf: &goblin::elf::Elf<'_>, buf: &[u8]) -> Result { + use scroll::Pread; + let mut addr = None; + let mut sym_size = 0u32; + for s in elf.syms.iter() { + if &elf.strtab[s.st_name] == "FlashDevice" { + addr = Some(s.st_value as u32); + sym_size = s.st_size as u32; + break; + } + } + let addr = addr.ok_or_else(|| anyhow!("ELF missing FlashDevice symbol"))?; + + let want = sym_size.max(160); + let bytes = read_at(elf, buf, addr, want) + .ok_or_else(|| anyhow!("FlashDevice not in any LOAD segment"))?; + let mut sectors = Vec::new(); + let mut off = 160; + while off + 8 <= bytes.len() { + let size: u32 = bytes.pread(off).unwrap(); + let address: u32 = bytes.pread(off + 4).unwrap(); + if size == u32::MAX && address == u32::MAX { + break; + } + sectors.push((size, address)); + off += 8; + } + Ok(FlashDevice { + start: bytes.pread::(132).unwrap() as u64, + size: bytes.pread::(136).unwrap() as u64, + page_size: bytes.pread(140).unwrap(), + erased: bytes.pread(148).unwrap(), + program_to: bytes.pread(152).unwrap(), + erase_to: bytes.pread(156).unwrap(), + sectors, + }) +} + +fn read_at<'a>( + elf: &goblin::elf::Elf<'_>, + buf: &'a [u8], + addr: u32, + size: u32, +) -> Option<&'a [u8]> { + let want = addr as u64..(addr as u64 + size as u64); + for ph in &elf.program_headers { + let seg = ph.p_paddr..(ph.p_paddr + ph.p_memsz.min(ph.p_filesz)); + if seg.start <= want.start && want.end <= seg.end { + let off = (ph.p_offset + addr as u64 - seg.start) as usize; + return Some(&buf[off..][..size as usize]); + } + } + None +} diff --git a/xtask/src/chip.rs b/xtask/src/chip.rs new file mode 100644 index 0000000..5d5d738 --- /dev/null +++ b/xtask/src/chip.rs @@ -0,0 +1,231 @@ +use anyhow::{Context, Result}; +use serde::Deserialize; +use std::collections::HashSet; +use std::path::Path; + +#[derive(Debug, Clone, Deserialize)] +pub struct Chip { + pub name: String, + #[serde(default)] + pub packages: Vec, + pub memory: Vec, + pub cores: Vec, + #[serde(default)] + pub memory_ram_code_config: Option, +} + +#[derive(Debug, Clone, Deserialize)] +pub struct Package { + pub name: String, + /// chip_id reported by WCH-Link `AttachChip`; drives `chip_detection`. + /// `0` means unknown — emitted by ch32-data when no ID is on record. + #[serde(default)] + pub device_id: u32, +} + +#[derive(Debug, Clone, Deserialize)] +pub struct MemoryRegion { + pub name: String, + pub kind: String, + pub address: u64, + pub size: u64, + #[serde(default)] + pub modes: Vec, + #[serde(default)] + pub access: Option, + #[serde(default)] + pub structs: Vec, +} + +#[derive(Debug, Clone, Deserialize)] +pub struct MemoryStruct { + #[serde(default)] + pub kind: Option, + #[serde(default)] + pub version: Option, +} + +#[derive(Debug, Clone, Deserialize)] +#[serde(tag = "type", rename_all = "snake_case")] +pub enum Mode { + Fast { page_size: u32, load_size: u32 }, + Standard { erase_size: u32, write_size: u32 }, +} + +impl Mode { + pub fn fast(&self) -> Option<(u32, u32)> { + if let Mode::Fast { + page_size, + load_size, + } = self + { + Some((*page_size, *load_size)) + } else { + None + } + } + pub fn standard(&self) -> Option<(u32, u32)> { + if let Mode::Standard { + erase_size, + write_size, + } = self + { + Some((*erase_size, *write_size)) + } else { + None + } + } +} + +#[derive(Debug, Clone, Deserialize, Default)] +pub struct Access { + #[serde(default)] + pub read: bool, + #[serde(default)] + pub write: bool, + #[serde(default)] + pub execute: bool, +} + +#[derive(Debug, Clone, Deserialize)] +pub struct Core { + #[serde(default)] + pub arch: Option, + #[serde(default)] + pub peripherals: Vec, +} + +#[derive(Debug, Clone, Deserialize)] +pub struct Peripheral { + pub name: String, + #[serde(default)] + pub registers: Option, +} + +#[derive(Debug, Clone, Deserialize)] +pub struct PeripheralRegisters { + pub version: String, +} + +#[derive(Debug, Clone, Deserialize)] +pub struct RamCodeConfig { + pub default: String, + pub configs: Vec, +} + +#[derive(Debug, Clone, Deserialize)] +pub struct RamCodeOption { + pub name: String, + pub code: u64, + pub ram: u64, +} + +impl Chip { + pub fn flash_version(&self) -> Option<&str> { + for c in &self.cores { + for p in &c.peripherals { + if p.name == "FLASH" + && let Some(r) = &p.registers + { + return Some(r.version.as_str()); + } + } + } + None + } + + pub fn arch(&self) -> Option<&str> { + self.cores.first().and_then(|c| c.arch.as_deref()) + } + + /// `_ram_code` suffix indicates `OB.USER` carries an SRAM/CODE split + /// field (bits 5-7). + pub fn ob_version(&self) -> Option<&str> { + for region in &self.memory { + for s in ®ion.structs { + if s.kind.as_deref() == Some("ob") { + return s.version.as_deref(); + } + } + } + None + } + + pub fn variants(&self) -> Vec { + let Some(cfg) = &self.memory_ram_code_config else { + return vec![Variant { + option: None, + memory: self.memory.clone(), + }]; + }; + let usr1_default = self.memory.iter().find(|r| r.name == "USR_1"); + let usr2_default = self.memory.iter().find(|r| r.name == "USR_2"); + let total_flash = match (usr1_default, usr2_default) { + (Some(u1), Some(u2)) => u1.size + u2.size, + _ => return Vec::new(), + }; + let usr1_addr = usr1_default.unwrap().address; + + cfg.configs + .iter() + .map(|opt| { + let usr2_addr = usr1_addr + opt.code; + let usr2_size = total_flash - opt.code; + let mut memory = self.memory.clone(); + for r in &mut memory { + match r.name.as_str() { + "USR_1" => r.size = opt.code, + "USR_2" => { + r.address = usr2_addr; + r.size = usr2_size; + } + "RAM" => r.size = opt.ram, + _ => {} + } + } + Variant { + option: Some(opt.name.clone()), + memory, + } + }) + .collect() + } +} + +pub struct Variant { + /// `None` when chip has no `memory_ram_code_config`; else the option name. + pub option: Option, + pub memory: Vec, +} + +pub fn load_all(dir: &Path) -> Result> { + let mut out = Vec::new(); + for entry in std::fs::read_dir(dir)? { + let entry = entry?; + let path = entry.path(); + if path.extension().and_then(|e| e.to_str()) != Some("json") { + continue; + } + let text = std::fs::read_to_string(&path) + .with_context(|| format!("reading {}", path.display()))?; + let chip: Chip = + serde_json::from_str(&text).with_context(|| format!("parsing {}", path.display()))?; + out.push(chip); + } + out.sort_by(|a, b| a.name.cmp(&b.name)); + + // Drop per-package JSONs that duplicate a container chip's package entry. + // Container = any chip whose `packages` doesn't reduce to a single self-named + // entry (multi-package, or a single package with a different name). + let owned_by_container: HashSet = out + .iter() + .filter(|c| c.packages.len() > 1 || c.packages.iter().any(|p| p.name != c.name)) + .flat_map(|c| c.packages.iter().map(|p| p.name.clone())) + .collect(); + out.retain(|c| { + let self_named_single = c.packages.len() == 1 && c.packages[0].name == c.name; + !(self_named_single && owned_by_container.contains(&c.name)) + }); + + Ok(out) +} diff --git a/xtask/src/main.rs b/xtask/src/main.rs new file mode 100644 index 0000000..8a7c116 --- /dev/null +++ b/xtask/src/main.rs @@ -0,0 +1,51 @@ +mod algo; +mod chip; +mod render; + +use anyhow::{Context, Result, bail}; +use std::path::PathBuf; + +fn workspace_root() -> PathBuf { + PathBuf::from(env!("CARGO_MANIFEST_DIR")) + .parent() + .unwrap() + .to_path_buf() +} + +fn ch32_data_chip_dir() -> PathBuf { + workspace_root().join("../ch32-data/build/data/chips") +} + +fn output_dir() -> PathBuf { + workspace_root().join("generated") +} + +fn main() -> Result<()> { + let root = workspace_root(); + let out = output_dir(); + std::fs::create_dir_all(&out)?; + + eprintln!("==> building algo crates"); + let algos = algo::build_all(&root)?; + eprintln!(" {} algo blobs built", algos.len()); + + let chip_dir = ch32_data_chip_dir(); + if !chip_dir.exists() { + bail!( + "ch32-data chip JSON dir not found at {} — run `cd ../ch32-data && ./d gen` first", + chip_dir.display(), + ); + } + + eprintln!("==> reading chip JSONs from {}", chip_dir.display()); + let chips = chip::load_all(&chip_dir).context("loading chip JSONs")?; + eprintln!(" {} chips parsed", chips.len()); + + eprintln!("==> emitting target YAMLs to {}", out.display()); + let stats = render::emit_all(&chips, &algos, &out)?; + eprintln!( + "==> done: {} family YAMLs written ({} variants total), {} chips skipped", + stats.families_written, stats.variants_written, stats.chips_skipped, + ); + Ok(()) +} diff --git a/xtask/src/render.rs b/xtask/src/render.rs new file mode 100644 index 0000000..4ce611e --- /dev/null +++ b/xtask/src/render.rs @@ -0,0 +1,555 @@ +use crate::algo::AlgoBlob; +use crate::chip::{Access, Chip, MemoryRegion, Variant}; +use anyhow::{Context, Result, anyhow, bail}; +use probe_rs_target::{ + ArmCoreAccessOptions, Chip as PrChip, ChipFamily, Core as PrCore, CoreAccessOptions, CoreType, + MemoryAccess, MemoryRegion as PrMemoryRegion, NvmRegion, RamRegion, RiscvCoreAccessOptions, + SectorDescription, TargetDescriptionSource, + chip_detection::{ChipDetectionMethod, ObRefinement, WchLinkDetection}, +}; +use std::collections::{BTreeMap, HashMap}; +use std::ops::Range; +use std::path::Path; + +#[derive(Default)] +pub struct EmitStats { + pub families_written: usize, + pub variants_written: usize, + pub chips_skipped: usize, +} + +/// Group chips by (silicon, arch) and emit one YAML per group. Splitting by +/// arch separates `flash_v1` consumers into V1 (RISC-V) and F1 (Cortex-M3) +/// families, each with the right CPU-built algo blob. +pub fn emit_all(chips: &[Chip], algos: &[AlgoBlob], out_dir: &Path) -> Result { + let mut groups: BTreeMap<(String, String), Vec<&Chip>> = BTreeMap::new(); + let mut stats = EmitStats::default(); + + for chip in chips { + match (chip.flash_version(), chip.arch()) { + (Some(silicon), Some(arch)) => { + groups + .entry((silicon.to_string(), arch.to_string())) + .or_default() + .push(chip); + } + (None, _) => { + eprintln!(" skip {} (no FLASH peripheral version)", chip.name); + stats.chips_skipped += 1; + } + (_, None) => { + eprintln!(" skip {} (no `arch` on first core)", chip.name); + stats.chips_skipped += 1; + } + } + } + + for ((silicon, arch), silicon_chips) in groups { + if !algos.iter().any(|a| a.silicon == silicon && a.arch == arch) { + eprintln!( + " skip silicon {}/{} ({} chips: no algo blob built)", + silicon, + arch, + silicon_chips.len() + ); + stats.chips_skipped += silicon_chips.len(); + continue; + } + + // Roll up marketing-series prefixes (e.g. v0 → "CH32V003_CH641"). + // A `series:` field in ch32-data would replace `series_for`. + let mut prefixes: Vec = silicon_chips.iter().map(|c| series_for(&c.name)).collect(); + prefixes.sort(); + prefixes.dedup(); + let joined = prefixes.join("_"); + let display_name = format!("{} Series", joined.replace('_', " / ")); + let file_stem = format!("{}_Series", joined); + + let (chip_family, variant_count) = + build_family(&display_name, &silicon_chips, &silicon, &arch, algos)?; + let yaml = serialize_yaml(&chip_family)?; + let path = out_dir.join(format!("{}.yaml", file_stem)); + std::fs::write(&path, yaml).with_context(|| format!("writing {}", path.display()))?; + eprintln!( + " wrote {} ({} variants, silicon {}/{})", + path.file_name().unwrap().to_string_lossy(), + variant_count, + silicon, + arch, + ); + stats.families_written += 1; + stats.variants_written += variant_count; + } + + Ok(stats) +} + +/// Heuristic — ch32-data has no explicit `series` field yet. +fn series_for(chip_name: &str) -> String { + // V003 is silicon v0 alone; all other V0xx + M0xx are silicon v00x. + if chip_name.starts_with("CH32V003") { + return "CH32V003".to_string(); + } + if chip_name.starts_with("CH32V0") || chip_name.starts_with("CH32M0") { + return "CH32V00X".to_string(); + } + if let Some(rest) = chip_name.strip_prefix("CH32") { + let prefix: String = rest.chars().take(2).collect(); + if prefix.len() == 2 { + return format!("CH32{}", prefix); + } + } + if chip_name.starts_with("CH6") { + let prefix: String = chip_name.chars().take(5).collect(); + if prefix.len() == 5 { + return prefix; + } + } + chip_name.to_string() +} + +fn build_family( + display_name: &str, + chips: &[&Chip], + silicon: &str, + arch: &str, + algos: &[AlgoBlob], +) -> Result<(ChipFamily, usize)> { + let core_name = "main".to_string(); + let (core_type, core_access_options) = match arch { + "riscv" => ( + CoreType::Riscv, + CoreAccessOptions::Riscv(RiscvCoreAccessOptions { + hart_id: Some(0), + jtag_tap: None, + mem_ap: None, + }), + ), + "arm" => ( + CoreType::Armv7m, + CoreAccessOptions::Arm(ArmCoreAccessOptions::default()), + ), + other => bail!("unsupported arch `{}`", other), + }; + let core = PrCore { + name: core_name.clone(), + core_type, + core_access_options, + }; + + let mut variants: Vec = Vec::new(); + let mut variant_algo_uses: BTreeMap>> = BTreeMap::new(); + let mut algo_kind: BTreeMap = BTreeMap::new(); + let mut detection_entries: BTreeMap = BTreeMap::new(); + + for chip in chips { + let default_opt = chip + .memory_ram_code_config + .as_ref() + .map(|c| c.default.clone()); + + for package in &chip.packages { + let refinement = build_ob_refinement(chip, &package.name); + + for variant in chip.variants() { + let is_default_variant = match (&variant.option, &default_opt) { + (Some(opt), Some(def)) => opt == def, + _ => true, + }; + let suffix = if is_default_variant { + String::new() + } else { + format!("_{}", variant.option.as_deref().unwrap_or("")) + }; + let target_name = format!("{}{}", package.name, suffix); + + let (memory_map, variant_algo_names) = build_variant( + &variant, + &core_name, + silicon, + arch, + algos, + &mut variant_algo_uses, + &mut algo_kind, + ); + + // Non-default splits share the same chip_id; OB refinement maps + // OB.USER bits 5-7 to the right variant post-attach. device_id=0 + // is ch32-data's "unknown" sentinel — drop those so multiple + // unknown-ID packages don't collide on key 0x0 in the variants + // IndexMap (last-write-wins would otherwise hide all but one). + if is_default_variant && package.device_id != 0 { + let id = package.device_id; + let mask = mask_for(id); + let group = detection_entries.entry(mask).or_default(); + group.variants.insert(id & mask, target_name.clone()); + if let Some(r) = &refinement { + group.ob_refinement.insert(id & mask, r.clone()); + } + } + + variants.push(PrChip { + name: target_name, + part: None, + svd: None, + documentation: Default::default(), + package_variants: vec![], + cores: vec![core.clone()], + memory_map, + flash_algorithms: variant_algo_names, + rtt_scan_ranges: None, + jtag: None, + default_binary_format: None, + }); + } + } + } + + let chip_detection: Vec = detection_entries + .into_iter() + .map(|(mask, group)| { + ChipDetectionMethod::WchLink(WchLinkDetection { + mask, + variants: group.variants, + ob_refinement: group.ob_refinement, + }) + }) + .collect(); + + let mut flash_algorithms = Vec::new(); + for (algo_name, ranges) in variant_algo_uses { + let kind = algo_kind + .get(&algo_name) + .ok_or_else(|| anyhow!("missing kind for algo {}", algo_name))?; + let start = ranges.iter().map(|r| r.start).min().unwrap(); + let end = ranges.iter().map(|r| r.end).max().unwrap(); + let blob_kind = if kind == "usr-legacy" { "usr" } else { kind }; + // Prefer Fast over Standard (v0/v1 OB only has Standard). + let template_region = find_template_region(chips, blob_kind)?; + let (page_size, _) = template_region + .modes + .iter() + .find_map(|m| m.fast().or_else(|| m.standard())) + .ok_or_else(|| { + anyhow!( + "no programming mode on region {} (silicon {})", + template_region.name, + silicon + ) + })?; + + let blob = algos + .iter() + .find(|a| a.silicon == silicon && a.arch == arch && a.region_kind == blob_kind) + .ok_or_else(|| { + anyhow!( + "no algo blob for silicon {} arch {} kind {}", + silicon, + arch, + kind + ) + })?; + let mut algo = blob.template.clone(); + algo.name = algo_name.clone(); + algo.description = algo_name.clone(); + algo.default = true; + algo.flash_properties.address_range = start..end; + algo.flash_properties.page_size = page_size; + // Keep the erase size declared by the `algorithm!` macro (SYS on + // flash_v3 uses 4 KB; USR/OB match page_size). Rebase to 0 — addresses + // in sectors[] are relative to address_range.start. + if algo.flash_properties.sectors.is_empty() { + algo.flash_properties.sectors = vec![SectorDescription { + size: page_size as u64, + address: 0, + }]; + } else { + for s in algo.flash_properties.sectors.iter_mut() { + s.address = 0; + } + } + // Drop EraseChip for SYS — defends against accidental bulk-erase + // routing. USR's MER and OB's page-erase+defaults are fine. + if kind == "sys" { + algo.pc_erase_all = None; + } + flash_algorithms.push(algo); + } + + let variant_count = variants.len(); + Ok(( + ChipFamily { + name: display_name.to_string(), + manufacturer: None, + chip_detection, + generated_from_pack: false, + pack_file_release: None, + variants, + flash_algorithms, + source: TargetDescriptionSource::External, + }, + variant_count, + )) +} + +fn build_variant( + variant: &Variant, + core_name: &str, + silicon: &str, + arch: &str, + algos: &[AlgoBlob], + algo_uses: &mut BTreeMap>>, + algo_kind: &mut BTreeMap, +) -> (Vec, Vec) { + let mut memory_map = Vec::new(); + let mut variant_algos = Vec::new(); + let mut legacy_emitted = false; + let regions = merge_regions(&variant.memory); + for region in ®ions { + let access = MemoryAccess { + read: region.access.as_ref().is_none_or(|a| a.read), + write: region.access.as_ref().is_none_or(|a| a.write), + execute: region.access.as_ref().is_none_or(|a| a.execute), + boot: false, + }; + let range = region.address..(region.address + region.size); + match region.kind.as_str() { + "ram" => { + memory_map.push(PrMemoryRegion::Ram(RamRegion { + name: Some(region.name.clone()), + range, + cores: vec![core_name.to_string()], + is_alias: false, + access: Some(access), + })); + } + "flash" => { + // `is_alias` = "skip in `probe-rs erase` bulk path". Only erase USR_* + let is_alias = !region.name.starts_with("USR"); + memory_map.push(PrMemoryRegion::Nvm(NvmRegion { + name: Some(region.name.clone()), + range: range.clone(), + cores: vec![core_name.to_string()], + is_alias, + access: Some(access), + })); + if access.write + && let Some(kind) = region_kind(®ion.name) + && algos + .iter() + .any(|a| a.silicon == silicon && a.arch == arch && a.region_kind == kind) + { + let suffix = match kind { + "ob" => "opt", + other => other, + }; + let region_idx = split_name(®ion.name) + .1 + .map(|n| format!("-{}", n)) + .unwrap_or_default(); + let algo_name = format!("ch32-{}-{}{}", silicon, suffix, region_idx); + if !variant_algos.contains(&algo_name) { + variant_algos.push(algo_name.clone()); + } + algo_uses + .entry(algo_name.clone()) + .or_default() + .push(range.clone()); + algo_kind.insert(algo_name, kind.to_string()); + + if kind == "usr" && !legacy_emitted { + legacy_emitted = true; + let alias_range = 0u64..(range.end - range.start); + memory_map.push(PrMemoryRegion::Nvm(NvmRegion { + name: Some("USR_LEGACY".to_string()), + range: alias_range.clone(), + cores: vec![core_name.to_string()], + is_alias: true, + access: Some(access), + })); + let legacy_name = format!("ch32-{}-usr-legacy", silicon); + if !variant_algos.contains(&legacy_name) { + variant_algos.push(legacy_name.clone()); + } + algo_uses + .entry(legacy_name.clone()) + .or_default() + .push(alias_range); + algo_kind.insert(legacy_name, "usr-legacy".to_string()); + } + } + } + _ => {} + } + } + (memory_map, variant_algos) +} + +fn region_kind(name: &str) -> Option<&'static str> { + match name { + n if n.starts_with("USR") => Some("usr"), + n if n.starts_with("SYS") => Some("sys"), + "OPT" => Some("ob"), + _ => None, + } +} + +fn find_template_region(chips: &[&Chip], kind: &str) -> Result { + for chip in chips { + for variant in chip.variants() { + for region in &variant.memory { + if region.kind == "flash" && region_kind(®ion.name) == Some(kind) { + return Ok(region.clone()); + } + } + } + } + Err(anyhow!("no region of kind {} in family", kind)) +} + +/// CH643 (0x643xxxxx) needs exact match per wlink's `chips.rs`; everyone +/// else clears the package nibble. +fn mask_for(chip_id: u32) -> u32 { + match chip_id & 0xFFF0_0000 { + 0x6430_0000 => 0xFFFF_FFFF, + _ => 0xFFFF_FF0F, + } +} + +#[derive(Default)] +struct DetectionGroup { + variants: indexmap::IndexMap, + ob_refinement: indexmap::IndexMap, +} + +const OB_USER_ADDRESS: u64 = 0x1FFFF802; +const RAM_CODE_MASK: u8 = 0xE0; + +fn build_ob_refinement(chip: &Chip, package_name: &str) -> Option { + let cfg = chip.memory_ram_code_config.as_ref()?; + let ob_version = chip.ob_version()?; + let encoding = ram_code_encoding(ob_version)?; + + let default_opt = &cfg.default; + let mut variants: indexmap::IndexMap = indexmap::IndexMap::new(); + for raw in 0u8..8 { + let option_name = encoding(raw)?; + let suffix = if option_name == default_opt { + String::new() + } else { + format!("_{}", option_name) + }; + let variant_name = format!("{}{}", package_name, suffix); + variants.insert(raw << 5, variant_name); + } + + Some(ObRefinement { + address: OB_USER_ADDRESS, + mask: RAM_CODE_MASK, + variants, + }) +} + +/// Encoding follows `ch32-data/data/nv/ob_v{2,3}_ram_code.yaml`. +fn ram_code_encoding(ob_version: &str) -> Option Option<&'static str>> { + match ob_version { + "v2_ram_code" => Some(|raw| match raw { + 0b000 | 0b001 => Some("c128_r64"), + 0b010 | 0b011 => Some("c144_r48"), + 0b100 | 0b101 => Some("c160_r32"), + // 0b11x reserved on v2; fall back to factory default. + 0b110 | 0b111 => Some("c128_r64"), + _ => None, + }), + "v3_ram_code" => Some(|raw| match raw { + 0b000 | 0b001 => Some("c192_r128"), + 0b010 | 0b011 => Some("c224_r96"), + 0b100 | 0b101 => Some("c256_r64"), + 0b110 => Some("c128_r192"), + 0b111 => Some("c288_r32"), + _ => None, + }), + _ => None, + } +} + +/// Collapse contiguous `USR_1`+`USR_2` runs into a single `USR` — the V2/V3 +/// split is an OB-configurable line, not a HW boundary. +fn merge_regions(memory: &[MemoryRegion]) -> Vec { + let mut sorted: Vec = memory.to_vec(); + sorted.sort_by_key(|r| r.address); + + let mut merged: Vec = Vec::with_capacity(sorted.len()); + for r in sorted { + let mergeable = merged.last().is_some_and(|last| can_merge(last, &r)); + if mergeable { + merged.last_mut().unwrap().size += r.size; + } else { + merged.push(r); + } + } + + let mut counts: HashMap = HashMap::new(); + for r in &merged { + let base = base_name(&r.name).to_string(); + *counts.entry(base).or_insert(0) += 1; + } + let mut seen: HashMap = HashMap::new(); + for r in &mut merged { + let base = base_name(&r.name).to_string(); + if counts[&base] <= 1 { + r.name = base; + } else { + let n = seen.entry(base.clone()).or_insert(0); + *n += 1; + r.name = format!("{}_{}", base, n); + } + } + merged +} + +fn can_merge(a: &MemoryRegion, b: &MemoryRegion) -> bool { + let (a_base, a_idx) = split_name(&a.name); + let (b_base, b_idx) = split_name(&b.name); + a_idx.is_some() + && b_idx.is_some() + && a_base == b_base + && a.kind == b.kind + && a.address + a.size == b.address + && access_key(a.access.as_ref()) == access_key(b.access.as_ref()) +} + +fn split_name(name: &str) -> (&str, Option) { + if let Some((base, idx)) = name.rsplit_once('_') + && let Ok(n) = idx.parse::() + { + return (base, Some(n)); + } + (name, None) +} + +fn base_name(name: &str) -> &str { + split_name(name).0 +} + +fn access_key(a: Option<&Access>) -> Option<(bool, bool, bool)> { + a.map(|x| (x.read, x.write, x.execute)) +} + +/// `probe-rs-target` serializes hex as `'0x…'` but deserializes raw numbers +/// — strip the quotes to round-trip (same workaround as probe-rs target-gen). +fn serialize_yaml(family: &ChipFamily) -> Result { + let raw = serde_yaml::to_string(family)?; + let mut out = String::with_capacity(raw.len()); + for line in raw.lines() { + let needs_unquote = (line.contains("'0x") || line.contains("'0X")) + && (line.ends_with('\'') || line.contains("':")); + if needs_unquote { + out.push_str(&line.replace('\'', "")); + } else { + out.push_str(line); + } + out.push('\n'); + } + Ok(out) +}