├── .github └── workflows │ └── ci.yml ├── .gitignore ├── CHANGELOG.md ├── Cargo.lock ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── docs ├── CH32L103.md ├── CH32V003.md ├── CH32V103.md ├── CH32V20x.md ├── CH32V30x.md ├── CH32X035.md ├── CH56X.md ├── CH57x_CH58x_CH59x.md ├── CH641.md ├── CH643.md └── references.md ├── protocol.md └── src ├── chips.rs ├── commands ├── control.rs └── mod.rs ├── dmi.rs ├── error.rs ├── firmware.rs ├── flash_op.rs ├── lib.rs ├── main.rs ├── operations.rs ├── probe.rs ├── regs.rs └── usb_device.rs /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Build and Test 2 | 3 | on: 4 | push: 5 | pull_request: 6 | branches: [ "main" ] 7 | workflow_dispatch: 8 | release: 9 | types: 10 | - created 11 | schedule: # Every day at the 2 P.M. (UTC) we run a scheduled nightly build 12 | - cron: "0 14 * * *" 13 | 14 | env: 15 | CARGO_TERM_COLOR: always 16 | 17 | concurrency: 18 | group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} 19 | cancel-in-progress: true 20 | 21 | jobs: 22 | build: 23 | name: build (${{ matrix.config.arch }}) 24 | strategy: 25 | matrix: 26 | config: 27 | - os: windows-latest 28 | arch: win-x64 29 | - os: windows-latest 30 | arch: win-x86 31 | - os: ubuntu-latest 32 | arch: linux-x64 33 | - os: macos-latest 34 | arch: macos-arm64 35 | - os: macos-13 36 | arch: macos-x64 37 | runs-on: ${{ matrix.config.os }} 38 | 39 | steps: 40 | - uses: actions/checkout@v4 41 | - name: Install libudev for Linux 42 | if: runner.os == 'Linux' 43 | run: sudo apt-get update && sudo apt-get install -y libudev-dev 44 | - name: Set target 45 | run: | 46 | if [[ "${{ matrix.config.arch }}" == "win-x86" ]]; then 47 | echo TARGET="--target i686-pc-windows-msvc" >> $GITHUB_ENV 48 | else 49 | echo TARGET="" >> $GITHUB_ENV 50 | fi 51 | shell: bash 52 | - name: Build 53 | run: cargo build --release ${{ env.TARGET }} 54 | - name: Run tests 55 | run: cargo test --release ${{ env.TARGET }} --verbose 56 | - name: Run help 57 | run: cargo run --release ${{ env.TARGET }} -- --help 58 | 59 | - name: Prepare artifacts 60 | run: | 61 | if [[ "${{ matrix.config.arch }}" == "win-x64" ]]; then 62 | WLINK_EXE="target/release/wlink.exe" 63 | elif [[ "${{ matrix.config.arch }}" == "win-x86" ]]; then 64 | WLINK_EXE="target/i686-pc-windows-msvc/release/wlink.exe" 65 | else 66 | WLINK_EXE="target/release/wlink" 67 | fi 68 | 69 | mkdir wlink-${{ matrix.config.arch }} 70 | cp $WLINK_EXE wlink-${{ matrix.config.arch }} 71 | cp README.md wlink-${{ matrix.config.arch }} 72 | shell: bash 73 | - uses: actions/upload-artifact@v4 74 | with: 75 | name: wlink-${{ matrix.config.arch }} 76 | path: wlink-${{ matrix.config.arch }} 77 | 78 | - name: Prepare Release Asset 79 | if: github.event_name == 'release' 80 | run: | 81 | if [[ "${{ runner.os }}" == "Windows" ]]; then 82 | 7z a -tzip wlink-${{ github.event.release.tag_name }}-${{ matrix.config.arch }}.zip wlink-${{ matrix.config.arch }} 83 | else 84 | tar -czvf wlink-${{ github.event.release.tag_name }}-${{ matrix.config.arch }}.tar.gz wlink-${{ matrix.config.arch }} 85 | fi 86 | shell: bash 87 | - name: Upload Release Asset 88 | uses: softprops/action-gh-release@v2 89 | if: github.event_name == 'release' 90 | with: 91 | fail_on_unmatched_files: false 92 | files: | 93 | wlink-*.tar.gz 94 | wlink-*.zip 95 | 96 | nightly-release: 97 | needs: build 98 | runs-on: ubuntu-latest 99 | if: github.event_name == 'schedule' 100 | steps: 101 | - name: Download Artifacts 102 | uses: actions/download-artifact@v4 103 | with: 104 | path: ./ 105 | 106 | - name: Prepare Nightly Asset 107 | run: | 108 | ls -R ./ 109 | for f in wlink-*; do 110 | echo "Compressing $f" 111 | if [[ $f == wlink-win* ]]; then 112 | zip -r $f.zip $f 113 | else 114 | tar -czvf $f.tar.gz $f 115 | fi 116 | done 117 | ls ./ 118 | 119 | - name: Update Nightly Release 120 | uses: andelf/nightly-release@main 121 | env: 122 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 123 | with: 124 | tag_name: nightly 125 | name: "wlink nightly release $$" 126 | draft: false 127 | prerelease: true 128 | body: | 129 | This is a nightly binary release of the wlink command line tool. 130 | 131 | For Windows users, please use the x86 version since it has the Windows driver support. 132 | files: | 133 | wlink-*.tar.gz 134 | wlink-*.zip 135 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | trash/ 3 | test.py 4 | 5 | firmware.bin 6 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 7 | 8 | ## [Unreleased] 9 | 10 | ## [0.1.1] - 2024-11-15 11 | 12 | ### Fixed 13 | 14 | - Fix ELF firmware parsing error, #77 15 | 16 | ## [0.1.0] - 2024-11-11 17 | 18 | ### Added 19 | 20 | - Support CH585/CH584, CH32V317, CH32V002/4/5/6/7, CH32M007, #76 21 | 22 | ## [0.0.9] - 2024-10-03 23 | 24 | ### Added 25 | 26 | - Add functions to control 3.3V and 5V outputs of probe, #72 27 | 28 | ### Fixed 29 | 30 | - Rename ChipUID response to ESignature, #58 31 | - Ensure consistent nanosecond precision in log timestamps, #64 32 | 33 | ## [0.0.8] - 2024-03-30 34 | 35 | ### Added 36 | 37 | - Add `--watch-serial` for `flash` subcommand, #36 38 | - Add `-o/--out` for `dump` sumcommand, #38 39 | - BREAKING CHANGE: Refactor 40 | - Add Windows native driver support, #39 41 | - Use loaded memory address from ELF file or ihex file 42 | - Add timestamp in serial output 43 | 44 | ### Fixed 45 | 46 | - Merge gaps in firmware sections, #56 47 | 48 | ### Changed 49 | 50 | - No erase by default when flashing 51 | 52 | ## [0.0.7] - 2023-11-04 53 | 54 | ### Added 55 | 56 | - Dump CSRs using `regs` subcommand 57 | - Show firmware size info when flashing 58 | - Add a progress bar when flashing 59 | - New chip: CH641, a RV32EC chip almost the same as CH32V003 60 | - SDI print support, #34 61 | - Add serial number field to probe 62 | 63 | ### Fixed 64 | 65 | - Bypass attach chip when using special erase in #32 66 | 67 | ## [0.0.6] - 2023-10-07 68 | 69 | ### Added 70 | 71 | - Add `--speed` option to specify protocol speed 72 | - Add `erase --method power-off` option to support erase with power off 73 | - Add `erase --method pin-rst` option to support erase with RST pin, close #26 74 | - Add a simple chip ID db, now wlink can identify chip type automatically 75 | 76 | ### Fixed 77 | 78 | - Regression in `flash` command 79 | - Use chip type from the protocol, close #25 80 | - Support 2.10 (aka. v30) firmware, close #27 81 | - Fix `--no-detach` option is not working 82 | - Use DMI to read memory, avoid using probe commands 83 | 84 | ### Changed 85 | 86 | - Allow underscore `_` in number literals in command line 87 | - Refine protocol naming 88 | - Add a simple DMI algorithm skeleton 89 | 90 | ## [0.0.5] - 2023-07-31 91 | 92 | ### Added 93 | 94 | - Support WCH-LinkW, a CH32V208 flasher with wireless connection 95 | - Support WCH-Link firmware 2.9, some raw commands are changed 96 | - Support Flash protect and unprotect (#14) 97 | - Fix stuck for CH5xx devices, due to unsppported read ram rom split command 98 | - Add `--chip` option to specify chip type 99 | - Check probe type when doing mode-switch 100 | - Add support for CH32X035 101 | - Add support for CH59X 102 | 103 | ### Fixed 104 | 105 | - Constraint regs for riscv32ec variant 106 | - Wrong 0x0c command interpretation, this should be a set chip speed command 107 | - Cannot flash CH32V003 (#23). Now wlink won't get info when attaching chip 108 | 109 | ### Changed 110 | 111 | - Refine error messages 112 | - `--address` for flash is now optional, default to device flash start address 113 | 114 | ## [0.0.4] - 2023-07-01 115 | 116 | ### Added 117 | 118 | - Add `mode-switch` subcommand to switch between RV mode and DAP mode (#3) 119 | - Add `hex`, `ihex` and `elf` format support for `flash` subcommand 120 | 121 | ### Fixed 122 | 123 | - Fix communication parity error of abstractcs register (#16) 124 | - Do not halt when read register 125 | 126 | ### Changed 127 | 128 | - Refine attach chip logic, more robust now 129 | - Refine docs 130 | 131 | ## [0.0.3] - 2023-03-01 132 | 133 | ### Added 134 | 135 | - Everything just works 136 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 4 4 | 5 | [[package]] 6 | name = "android-tzdata" 7 | version = "0.1.1" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" 10 | 11 | [[package]] 12 | name = "android_system_properties" 13 | version = "0.1.5" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" 16 | dependencies = [ 17 | "libc", 18 | ] 19 | 20 | [[package]] 21 | name = "anstream" 22 | version = "0.6.18" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" 25 | dependencies = [ 26 | "anstyle", 27 | "anstyle-parse", 28 | "anstyle-query", 29 | "anstyle-wincon", 30 | "colorchoice", 31 | "is_terminal_polyfill", 32 | "utf8parse", 33 | ] 34 | 35 | [[package]] 36 | name = "anstyle" 37 | version = "1.0.10" 38 | source = "registry+https://github.com/rust-lang/crates.io-index" 39 | checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" 40 | 41 | [[package]] 42 | name = "anstyle-parse" 43 | version = "0.2.6" 44 | source = "registry+https://github.com/rust-lang/crates.io-index" 45 | checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" 46 | dependencies = [ 47 | "utf8parse", 48 | ] 49 | 50 | [[package]] 51 | name = "anstyle-query" 52 | version = "1.1.2" 53 | source = "registry+https://github.com/rust-lang/crates.io-index" 54 | checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" 55 | dependencies = [ 56 | "windows-sys 0.59.0", 57 | ] 58 | 59 | [[package]] 60 | name = "anstyle-wincon" 61 | version = "3.0.6" 62 | source = "registry+https://github.com/rust-lang/crates.io-index" 63 | checksum = "2109dbce0e72be3ec00bed26e6a7479ca384ad226efdd66db8fa2e3a38c83125" 64 | dependencies = [ 65 | "anstyle", 66 | "windows-sys 0.59.0", 67 | ] 68 | 69 | [[package]] 70 | name = "anyhow" 71 | version = "1.0.93" 72 | source = "registry+https://github.com/rust-lang/crates.io-index" 73 | checksum = "4c95c10ba0b00a02636238b814946408b1322d5ac4760326e6fb8ec956d85775" 74 | 75 | [[package]] 76 | name = "autocfg" 77 | version = "1.4.0" 78 | source = "registry+https://github.com/rust-lang/crates.io-index" 79 | checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" 80 | 81 | [[package]] 82 | name = "bitfield" 83 | version = "0.17.0" 84 | source = "registry+https://github.com/rust-lang/crates.io-index" 85 | checksum = "f798d2d157e547aa99aab0967df39edd0b70307312b6f8bd2848e6abe40896e0" 86 | 87 | [[package]] 88 | name = "bitflags" 89 | version = "1.3.2" 90 | source = "registry+https://github.com/rust-lang/crates.io-index" 91 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 92 | 93 | [[package]] 94 | name = "bitflags" 95 | version = "2.6.0" 96 | source = "registry+https://github.com/rust-lang/crates.io-index" 97 | checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" 98 | 99 | [[package]] 100 | name = "bumpalo" 101 | version = "3.16.0" 102 | source = "registry+https://github.com/rust-lang/crates.io-index" 103 | checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" 104 | 105 | [[package]] 106 | name = "cc" 107 | version = "1.2.1" 108 | source = "registry+https://github.com/rust-lang/crates.io-index" 109 | checksum = "fd9de9f2205d5ef3fd67e685b0df337994ddd4495e2a28d185500d0e1edfea47" 110 | dependencies = [ 111 | "shlex", 112 | ] 113 | 114 | [[package]] 115 | name = "cfg-if" 116 | version = "1.0.0" 117 | source = "registry+https://github.com/rust-lang/crates.io-index" 118 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 119 | 120 | [[package]] 121 | name = "chrono" 122 | version = "0.4.38" 123 | source = "registry+https://github.com/rust-lang/crates.io-index" 124 | checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" 125 | dependencies = [ 126 | "android-tzdata", 127 | "iana-time-zone", 128 | "js-sys", 129 | "num-traits", 130 | "wasm-bindgen", 131 | "windows-targets", 132 | ] 133 | 134 | [[package]] 135 | name = "clap" 136 | version = "4.5.21" 137 | source = "registry+https://github.com/rust-lang/crates.io-index" 138 | checksum = "fb3b4b9e5a7c7514dfa52869339ee98b3156b0bfb4e8a77c4ff4babb64b1604f" 139 | dependencies = [ 140 | "clap_builder", 141 | "clap_derive", 142 | ] 143 | 144 | [[package]] 145 | name = "clap-verbosity-flag" 146 | version = "2.2.2" 147 | source = "registry+https://github.com/rust-lang/crates.io-index" 148 | checksum = "e099138e1807662ff75e2cebe4ae2287add879245574489f9b1588eb5e5564ed" 149 | dependencies = [ 150 | "clap", 151 | "log", 152 | ] 153 | 154 | [[package]] 155 | name = "clap_builder" 156 | version = "4.5.21" 157 | source = "registry+https://github.com/rust-lang/crates.io-index" 158 | checksum = "b17a95aa67cc7b5ebd32aa5370189aa0d79069ef1c64ce893bd30fb24bff20ec" 159 | dependencies = [ 160 | "anstream", 161 | "anstyle", 162 | "clap_lex", 163 | "strsim", 164 | ] 165 | 166 | [[package]] 167 | name = "clap_derive" 168 | version = "4.5.18" 169 | source = "registry+https://github.com/rust-lang/crates.io-index" 170 | checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab" 171 | dependencies = [ 172 | "heck", 173 | "proc-macro2", 174 | "quote", 175 | "syn", 176 | ] 177 | 178 | [[package]] 179 | name = "clap_lex" 180 | version = "0.7.3" 181 | source = "registry+https://github.com/rust-lang/crates.io-index" 182 | checksum = "afb84c814227b90d6895e01398aee0d8033c00e7466aca416fb6a8e0eb19d8a7" 183 | 184 | [[package]] 185 | name = "colorchoice" 186 | version = "1.0.3" 187 | source = "registry+https://github.com/rust-lang/crates.io-index" 188 | checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" 189 | 190 | [[package]] 191 | name = "console" 192 | version = "0.15.8" 193 | source = "registry+https://github.com/rust-lang/crates.io-index" 194 | checksum = "0e1f83fc076bd6dd27517eacdf25fef6c4dfe5f1d7448bafaaf3a26f13b5e4eb" 195 | dependencies = [ 196 | "encode_unicode", 197 | "lazy_static", 198 | "libc", 199 | "unicode-width 0.1.14", 200 | "windows-sys 0.52.0", 201 | ] 202 | 203 | [[package]] 204 | name = "core-foundation" 205 | version = "0.10.0" 206 | source = "registry+https://github.com/rust-lang/crates.io-index" 207 | checksum = "b55271e5c8c478ad3f38ad24ef34923091e0548492a266d19b3c0b4d82574c63" 208 | dependencies = [ 209 | "core-foundation-sys", 210 | "libc", 211 | ] 212 | 213 | [[package]] 214 | name = "core-foundation-sys" 215 | version = "0.8.7" 216 | source = "registry+https://github.com/rust-lang/crates.io-index" 217 | checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" 218 | 219 | [[package]] 220 | name = "deranged" 221 | version = "0.3.11" 222 | source = "registry+https://github.com/rust-lang/crates.io-index" 223 | checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" 224 | dependencies = [ 225 | "powerfmt", 226 | ] 227 | 228 | [[package]] 229 | name = "encode_unicode" 230 | version = "0.3.6" 231 | source = "registry+https://github.com/rust-lang/crates.io-index" 232 | checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" 233 | 234 | [[package]] 235 | name = "heck" 236 | version = "0.5.0" 237 | source = "registry+https://github.com/rust-lang/crates.io-index" 238 | checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" 239 | 240 | [[package]] 241 | name = "hex" 242 | version = "0.4.3" 243 | source = "registry+https://github.com/rust-lang/crates.io-index" 244 | checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" 245 | 246 | [[package]] 247 | name = "iana-time-zone" 248 | version = "0.1.61" 249 | source = "registry+https://github.com/rust-lang/crates.io-index" 250 | checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220" 251 | dependencies = [ 252 | "android_system_properties", 253 | "core-foundation-sys", 254 | "iana-time-zone-haiku", 255 | "js-sys", 256 | "wasm-bindgen", 257 | "windows-core", 258 | ] 259 | 260 | [[package]] 261 | name = "iana-time-zone-haiku" 262 | version = "0.1.2" 263 | source = "registry+https://github.com/rust-lang/crates.io-index" 264 | checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" 265 | dependencies = [ 266 | "cc", 267 | ] 268 | 269 | [[package]] 270 | name = "ihex" 271 | version = "3.0.0" 272 | source = "registry+https://github.com/rust-lang/crates.io-index" 273 | checksum = "365a784774bb381e8c19edb91190a90d7f2625e057b55de2bc0f6b57bc779ff2" 274 | 275 | [[package]] 276 | name = "indicatif" 277 | version = "0.17.9" 278 | source = "registry+https://github.com/rust-lang/crates.io-index" 279 | checksum = "cbf675b85ed934d3c67b5c5469701eec7db22689d0a2139d856e0925fa28b281" 280 | dependencies = [ 281 | "console", 282 | "number_prefix", 283 | "portable-atomic", 284 | "unicode-width 0.2.0", 285 | "web-time", 286 | ] 287 | 288 | [[package]] 289 | name = "io-kit-sys" 290 | version = "0.4.1" 291 | source = "registry+https://github.com/rust-lang/crates.io-index" 292 | checksum = "617ee6cf8e3f66f3b4ea67a4058564628cde41901316e19f559e14c7c72c5e7b" 293 | dependencies = [ 294 | "core-foundation-sys", 295 | "mach2", 296 | ] 297 | 298 | [[package]] 299 | name = "is_terminal_polyfill" 300 | version = "1.70.1" 301 | source = "registry+https://github.com/rust-lang/crates.io-index" 302 | checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" 303 | 304 | [[package]] 305 | name = "itoa" 306 | version = "1.0.11" 307 | source = "registry+https://github.com/rust-lang/crates.io-index" 308 | checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" 309 | 310 | [[package]] 311 | name = "js-sys" 312 | version = "0.3.72" 313 | source = "registry+https://github.com/rust-lang/crates.io-index" 314 | checksum = "6a88f1bda2bd75b0452a14784937d796722fdebfe50df998aeb3f0b7603019a9" 315 | dependencies = [ 316 | "wasm-bindgen", 317 | ] 318 | 319 | [[package]] 320 | name = "lazy_static" 321 | version = "1.5.0" 322 | source = "registry+https://github.com/rust-lang/crates.io-index" 323 | checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" 324 | 325 | [[package]] 326 | name = "libc" 327 | version = "0.2.162" 328 | source = "registry+https://github.com/rust-lang/crates.io-index" 329 | checksum = "18d287de67fe55fd7e1581fe933d965a5a9477b38e949cfa9f8574ef01506398" 330 | 331 | [[package]] 332 | name = "libloading" 333 | version = "0.8.5" 334 | source = "registry+https://github.com/rust-lang/crates.io-index" 335 | checksum = "4979f22fdb869068da03c9f7528f8297c6fd2606bc3a4affe42e6a823fdb8da4" 336 | dependencies = [ 337 | "cfg-if", 338 | "windows-targets", 339 | ] 340 | 341 | [[package]] 342 | name = "libudev" 343 | version = "0.3.0" 344 | source = "registry+https://github.com/rust-lang/crates.io-index" 345 | checksum = "78b324152da65df7bb95acfcaab55e3097ceaab02fb19b228a9eb74d55f135e0" 346 | dependencies = [ 347 | "libc", 348 | "libudev-sys", 349 | ] 350 | 351 | [[package]] 352 | name = "libudev-sys" 353 | version = "0.1.4" 354 | source = "registry+https://github.com/rust-lang/crates.io-index" 355 | checksum = "3c8469b4a23b962c1396b9b451dda50ef5b283e8dd309d69033475fa9b334324" 356 | dependencies = [ 357 | "libc", 358 | "pkg-config", 359 | ] 360 | 361 | [[package]] 362 | name = "libusb1-sys" 363 | version = "0.7.0" 364 | source = "registry+https://github.com/rust-lang/crates.io-index" 365 | checksum = "da050ade7ac4ff1ba5379af847a10a10a8e284181e060105bf8d86960ce9ce0f" 366 | dependencies = [ 367 | "cc", 368 | "libc", 369 | "pkg-config", 370 | "vcpkg", 371 | ] 372 | 373 | [[package]] 374 | name = "log" 375 | version = "0.4.22" 376 | source = "registry+https://github.com/rust-lang/crates.io-index" 377 | checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" 378 | 379 | [[package]] 380 | name = "mach2" 381 | version = "0.4.2" 382 | source = "registry+https://github.com/rust-lang/crates.io-index" 383 | checksum = "19b955cdeb2a02b9117f121ce63aa52d08ade45de53e48fe6a38b39c10f6f709" 384 | dependencies = [ 385 | "libc", 386 | ] 387 | 388 | [[package]] 389 | name = "memchr" 390 | version = "2.7.4" 391 | source = "registry+https://github.com/rust-lang/crates.io-index" 392 | checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" 393 | 394 | [[package]] 395 | name = "nix" 396 | version = "0.26.4" 397 | source = "registry+https://github.com/rust-lang/crates.io-index" 398 | checksum = "598beaf3cc6fdd9a5dfb1630c2800c7acd31df7aaf0f565796fba2b53ca1af1b" 399 | dependencies = [ 400 | "bitflags 1.3.2", 401 | "cfg-if", 402 | "libc", 403 | ] 404 | 405 | [[package]] 406 | name = "nu-ansi-term" 407 | version = "0.50.1" 408 | source = "registry+https://github.com/rust-lang/crates.io-index" 409 | checksum = "d4a28e057d01f97e61255210fcff094d74ed0466038633e95017f5beb68e4399" 410 | dependencies = [ 411 | "windows-sys 0.52.0", 412 | ] 413 | 414 | [[package]] 415 | name = "nu-pretty-hex" 416 | version = "0.100.0" 417 | source = "registry+https://github.com/rust-lang/crates.io-index" 418 | checksum = "e733db64bb76e1c32a029fa40bbab43ae238125732d3454272afe4307a5398aa" 419 | dependencies = [ 420 | "nu-ansi-term", 421 | ] 422 | 423 | [[package]] 424 | name = "num-conv" 425 | version = "0.1.0" 426 | source = "registry+https://github.com/rust-lang/crates.io-index" 427 | checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" 428 | 429 | [[package]] 430 | name = "num-traits" 431 | version = "0.2.19" 432 | source = "registry+https://github.com/rust-lang/crates.io-index" 433 | checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" 434 | dependencies = [ 435 | "autocfg", 436 | ] 437 | 438 | [[package]] 439 | name = "num_threads" 440 | version = "0.1.7" 441 | source = "registry+https://github.com/rust-lang/crates.io-index" 442 | checksum = "5c7398b9c8b70908f6371f47ed36737907c87c52af34c268fed0bf0ceb92ead9" 443 | dependencies = [ 444 | "libc", 445 | ] 446 | 447 | [[package]] 448 | name = "number_prefix" 449 | version = "0.4.0" 450 | source = "registry+https://github.com/rust-lang/crates.io-index" 451 | checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3" 452 | 453 | [[package]] 454 | name = "object" 455 | version = "0.36.5" 456 | source = "registry+https://github.com/rust-lang/crates.io-index" 457 | checksum = "aedf0a2d09c573ed1d8d85b30c119153926a2b36dce0ab28322c09a117a4683e" 458 | dependencies = [ 459 | "memchr", 460 | ] 461 | 462 | [[package]] 463 | name = "once_cell" 464 | version = "1.20.2" 465 | source = "registry+https://github.com/rust-lang/crates.io-index" 466 | checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" 467 | 468 | [[package]] 469 | name = "pkg-config" 470 | version = "0.3.31" 471 | source = "registry+https://github.com/rust-lang/crates.io-index" 472 | checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" 473 | 474 | [[package]] 475 | name = "portable-atomic" 476 | version = "1.9.0" 477 | source = "registry+https://github.com/rust-lang/crates.io-index" 478 | checksum = "cc9c68a3f6da06753e9335d63e27f6b9754dd1920d941135b7ea8224f141adb2" 479 | 480 | [[package]] 481 | name = "powerfmt" 482 | version = "0.2.0" 483 | source = "registry+https://github.com/rust-lang/crates.io-index" 484 | checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" 485 | 486 | [[package]] 487 | name = "proc-macro2" 488 | version = "1.0.89" 489 | source = "registry+https://github.com/rust-lang/crates.io-index" 490 | checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e" 491 | dependencies = [ 492 | "unicode-ident", 493 | ] 494 | 495 | [[package]] 496 | name = "quote" 497 | version = "1.0.37" 498 | source = "registry+https://github.com/rust-lang/crates.io-index" 499 | checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" 500 | dependencies = [ 501 | "proc-macro2", 502 | ] 503 | 504 | [[package]] 505 | name = "rusb" 506 | version = "0.9.4" 507 | source = "registry+https://github.com/rust-lang/crates.io-index" 508 | checksum = "ab9f9ff05b63a786553a4c02943b74b34a988448671001e9a27e2f0565cc05a4" 509 | dependencies = [ 510 | "libc", 511 | "libusb1-sys", 512 | ] 513 | 514 | [[package]] 515 | name = "scopeguard" 516 | version = "1.2.0" 517 | source = "registry+https://github.com/rust-lang/crates.io-index" 518 | checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" 519 | 520 | [[package]] 521 | name = "serde" 522 | version = "1.0.215" 523 | source = "registry+https://github.com/rust-lang/crates.io-index" 524 | checksum = "6513c1ad0b11a9376da888e3e0baa0077f1aed55c17f50e7b2397136129fb88f" 525 | dependencies = [ 526 | "serde_derive", 527 | ] 528 | 529 | [[package]] 530 | name = "serde_derive" 531 | version = "1.0.215" 532 | source = "registry+https://github.com/rust-lang/crates.io-index" 533 | checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0" 534 | dependencies = [ 535 | "proc-macro2", 536 | "quote", 537 | "syn", 538 | ] 539 | 540 | [[package]] 541 | name = "serialport" 542 | version = "4.6.0" 543 | source = "registry+https://github.com/rust-lang/crates.io-index" 544 | checksum = "7331eefcaafaa382c0df95bcd84068f0b3e3c215c300750dde2316e9b8806ed5" 545 | dependencies = [ 546 | "bitflags 2.6.0", 547 | "cfg-if", 548 | "core-foundation", 549 | "core-foundation-sys", 550 | "io-kit-sys", 551 | "libudev", 552 | "mach2", 553 | "nix", 554 | "scopeguard", 555 | "unescaper", 556 | "winapi", 557 | ] 558 | 559 | [[package]] 560 | name = "shlex" 561 | version = "1.3.0" 562 | source = "registry+https://github.com/rust-lang/crates.io-index" 563 | checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" 564 | 565 | [[package]] 566 | name = "simplelog" 567 | version = "0.12.2" 568 | source = "registry+https://github.com/rust-lang/crates.io-index" 569 | checksum = "16257adbfaef1ee58b1363bdc0664c9b8e1e30aed86049635fb5f147d065a9c0" 570 | dependencies = [ 571 | "log", 572 | "termcolor", 573 | "time", 574 | ] 575 | 576 | [[package]] 577 | name = "strsim" 578 | version = "0.11.1" 579 | source = "registry+https://github.com/rust-lang/crates.io-index" 580 | checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" 581 | 582 | [[package]] 583 | name = "syn" 584 | version = "2.0.87" 585 | source = "registry+https://github.com/rust-lang/crates.io-index" 586 | checksum = "25aa4ce346d03a6dcd68dd8b4010bcb74e54e62c90c573f394c46eae99aba32d" 587 | dependencies = [ 588 | "proc-macro2", 589 | "quote", 590 | "unicode-ident", 591 | ] 592 | 593 | [[package]] 594 | name = "termcolor" 595 | version = "1.4.1" 596 | source = "registry+https://github.com/rust-lang/crates.io-index" 597 | checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" 598 | dependencies = [ 599 | "winapi-util", 600 | ] 601 | 602 | [[package]] 603 | name = "thiserror" 604 | version = "1.0.69" 605 | source = "registry+https://github.com/rust-lang/crates.io-index" 606 | checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" 607 | dependencies = [ 608 | "thiserror-impl 1.0.69", 609 | ] 610 | 611 | [[package]] 612 | name = "thiserror" 613 | version = "2.0.3" 614 | source = "registry+https://github.com/rust-lang/crates.io-index" 615 | checksum = "c006c85c7651b3cf2ada4584faa36773bd07bac24acfb39f3c431b36d7e667aa" 616 | dependencies = [ 617 | "thiserror-impl 2.0.3", 618 | ] 619 | 620 | [[package]] 621 | name = "thiserror-impl" 622 | version = "1.0.69" 623 | source = "registry+https://github.com/rust-lang/crates.io-index" 624 | checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" 625 | dependencies = [ 626 | "proc-macro2", 627 | "quote", 628 | "syn", 629 | ] 630 | 631 | [[package]] 632 | name = "thiserror-impl" 633 | version = "2.0.3" 634 | source = "registry+https://github.com/rust-lang/crates.io-index" 635 | checksum = "f077553d607adc1caf65430528a576c757a71ed73944b66ebb58ef2bbd243568" 636 | dependencies = [ 637 | "proc-macro2", 638 | "quote", 639 | "syn", 640 | ] 641 | 642 | [[package]] 643 | name = "time" 644 | version = "0.3.36" 645 | source = "registry+https://github.com/rust-lang/crates.io-index" 646 | checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" 647 | dependencies = [ 648 | "deranged", 649 | "itoa", 650 | "libc", 651 | "num-conv", 652 | "num_threads", 653 | "powerfmt", 654 | "serde", 655 | "time-core", 656 | "time-macros", 657 | ] 658 | 659 | [[package]] 660 | name = "time-core" 661 | version = "0.1.2" 662 | source = "registry+https://github.com/rust-lang/crates.io-index" 663 | checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" 664 | 665 | [[package]] 666 | name = "time-macros" 667 | version = "0.2.18" 668 | source = "registry+https://github.com/rust-lang/crates.io-index" 669 | checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" 670 | dependencies = [ 671 | "num-conv", 672 | "time-core", 673 | ] 674 | 675 | [[package]] 676 | name = "unescaper" 677 | version = "0.1.5" 678 | source = "registry+https://github.com/rust-lang/crates.io-index" 679 | checksum = "c878a167baa8afd137494101a688ef8c67125089ff2249284bd2b5f9bfedb815" 680 | dependencies = [ 681 | "thiserror 1.0.69", 682 | ] 683 | 684 | [[package]] 685 | name = "unicode-ident" 686 | version = "1.0.13" 687 | source = "registry+https://github.com/rust-lang/crates.io-index" 688 | checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" 689 | 690 | [[package]] 691 | name = "unicode-width" 692 | version = "0.1.14" 693 | source = "registry+https://github.com/rust-lang/crates.io-index" 694 | checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" 695 | 696 | [[package]] 697 | name = "unicode-width" 698 | version = "0.2.0" 699 | source = "registry+https://github.com/rust-lang/crates.io-index" 700 | checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd" 701 | 702 | [[package]] 703 | name = "utf8parse" 704 | version = "0.2.2" 705 | source = "registry+https://github.com/rust-lang/crates.io-index" 706 | checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" 707 | 708 | [[package]] 709 | name = "vcpkg" 710 | version = "0.2.15" 711 | source = "registry+https://github.com/rust-lang/crates.io-index" 712 | checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" 713 | 714 | [[package]] 715 | name = "wasm-bindgen" 716 | version = "0.2.95" 717 | source = "registry+https://github.com/rust-lang/crates.io-index" 718 | checksum = "128d1e363af62632b8eb57219c8fd7877144af57558fb2ef0368d0087bddeb2e" 719 | dependencies = [ 720 | "cfg-if", 721 | "once_cell", 722 | "wasm-bindgen-macro", 723 | ] 724 | 725 | [[package]] 726 | name = "wasm-bindgen-backend" 727 | version = "0.2.95" 728 | source = "registry+https://github.com/rust-lang/crates.io-index" 729 | checksum = "cb6dd4d3ca0ddffd1dd1c9c04f94b868c37ff5fac97c30b97cff2d74fce3a358" 730 | dependencies = [ 731 | "bumpalo", 732 | "log", 733 | "once_cell", 734 | "proc-macro2", 735 | "quote", 736 | "syn", 737 | "wasm-bindgen-shared", 738 | ] 739 | 740 | [[package]] 741 | name = "wasm-bindgen-macro" 742 | version = "0.2.95" 743 | source = "registry+https://github.com/rust-lang/crates.io-index" 744 | checksum = "e79384be7f8f5a9dd5d7167216f022090cf1f9ec128e6e6a482a2cb5c5422c56" 745 | dependencies = [ 746 | "quote", 747 | "wasm-bindgen-macro-support", 748 | ] 749 | 750 | [[package]] 751 | name = "wasm-bindgen-macro-support" 752 | version = "0.2.95" 753 | source = "registry+https://github.com/rust-lang/crates.io-index" 754 | checksum = "26c6ab57572f7a24a4985830b120de1594465e5d500f24afe89e16b4e833ef68" 755 | dependencies = [ 756 | "proc-macro2", 757 | "quote", 758 | "syn", 759 | "wasm-bindgen-backend", 760 | "wasm-bindgen-shared", 761 | ] 762 | 763 | [[package]] 764 | name = "wasm-bindgen-shared" 765 | version = "0.2.95" 766 | source = "registry+https://github.com/rust-lang/crates.io-index" 767 | checksum = "65fc09f10666a9f147042251e0dda9c18f166ff7de300607007e96bdebc1068d" 768 | 769 | [[package]] 770 | name = "web-time" 771 | version = "1.1.0" 772 | source = "registry+https://github.com/rust-lang/crates.io-index" 773 | checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" 774 | dependencies = [ 775 | "js-sys", 776 | "wasm-bindgen", 777 | ] 778 | 779 | [[package]] 780 | name = "winapi" 781 | version = "0.3.9" 782 | source = "registry+https://github.com/rust-lang/crates.io-index" 783 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 784 | dependencies = [ 785 | "winapi-i686-pc-windows-gnu", 786 | "winapi-x86_64-pc-windows-gnu", 787 | ] 788 | 789 | [[package]] 790 | name = "winapi-i686-pc-windows-gnu" 791 | version = "0.4.0" 792 | source = "registry+https://github.com/rust-lang/crates.io-index" 793 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 794 | 795 | [[package]] 796 | name = "winapi-util" 797 | version = "0.1.9" 798 | source = "registry+https://github.com/rust-lang/crates.io-index" 799 | checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" 800 | dependencies = [ 801 | "windows-sys 0.59.0", 802 | ] 803 | 804 | [[package]] 805 | name = "winapi-x86_64-pc-windows-gnu" 806 | version = "0.4.0" 807 | source = "registry+https://github.com/rust-lang/crates.io-index" 808 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 809 | 810 | [[package]] 811 | name = "windows-core" 812 | version = "0.52.0" 813 | source = "registry+https://github.com/rust-lang/crates.io-index" 814 | checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" 815 | dependencies = [ 816 | "windows-targets", 817 | ] 818 | 819 | [[package]] 820 | name = "windows-sys" 821 | version = "0.52.0" 822 | source = "registry+https://github.com/rust-lang/crates.io-index" 823 | checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" 824 | dependencies = [ 825 | "windows-targets", 826 | ] 827 | 828 | [[package]] 829 | name = "windows-sys" 830 | version = "0.59.0" 831 | source = "registry+https://github.com/rust-lang/crates.io-index" 832 | checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" 833 | dependencies = [ 834 | "windows-targets", 835 | ] 836 | 837 | [[package]] 838 | name = "windows-targets" 839 | version = "0.52.6" 840 | source = "registry+https://github.com/rust-lang/crates.io-index" 841 | checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" 842 | dependencies = [ 843 | "windows_aarch64_gnullvm", 844 | "windows_aarch64_msvc", 845 | "windows_i686_gnu", 846 | "windows_i686_gnullvm", 847 | "windows_i686_msvc", 848 | "windows_x86_64_gnu", 849 | "windows_x86_64_gnullvm", 850 | "windows_x86_64_msvc", 851 | ] 852 | 853 | [[package]] 854 | name = "windows_aarch64_gnullvm" 855 | version = "0.52.6" 856 | source = "registry+https://github.com/rust-lang/crates.io-index" 857 | checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" 858 | 859 | [[package]] 860 | name = "windows_aarch64_msvc" 861 | version = "0.52.6" 862 | source = "registry+https://github.com/rust-lang/crates.io-index" 863 | checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" 864 | 865 | [[package]] 866 | name = "windows_i686_gnu" 867 | version = "0.52.6" 868 | source = "registry+https://github.com/rust-lang/crates.io-index" 869 | checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" 870 | 871 | [[package]] 872 | name = "windows_i686_gnullvm" 873 | version = "0.52.6" 874 | source = "registry+https://github.com/rust-lang/crates.io-index" 875 | checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" 876 | 877 | [[package]] 878 | name = "windows_i686_msvc" 879 | version = "0.52.6" 880 | source = "registry+https://github.com/rust-lang/crates.io-index" 881 | checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" 882 | 883 | [[package]] 884 | name = "windows_x86_64_gnu" 885 | version = "0.52.6" 886 | source = "registry+https://github.com/rust-lang/crates.io-index" 887 | checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" 888 | 889 | [[package]] 890 | name = "windows_x86_64_gnullvm" 891 | version = "0.52.6" 892 | source = "registry+https://github.com/rust-lang/crates.io-index" 893 | checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" 894 | 895 | [[package]] 896 | name = "windows_x86_64_msvc" 897 | version = "0.52.6" 898 | source = "registry+https://github.com/rust-lang/crates.io-index" 899 | checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" 900 | 901 | [[package]] 902 | name = "wlink" 903 | version = "0.1.1" 904 | dependencies = [ 905 | "anyhow", 906 | "bitfield", 907 | "chrono", 908 | "clap", 909 | "clap-verbosity-flag", 910 | "hex", 911 | "ihex", 912 | "indicatif", 913 | "libloading", 914 | "log", 915 | "nu-pretty-hex", 916 | "object", 917 | "rusb", 918 | "serialport", 919 | "simplelog", 920 | "thiserror 2.0.3", 921 | ] 922 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "wlink" 3 | version = "0.1.1" 4 | edition = "2021" 5 | authors = ["Andelf "] 6 | repository = "https://github.com/ch32-rs/wlink" 7 | documentation = "https://docs.rs/wlink" 8 | homepage = "https://github.com/ch32-rs/wlink" 9 | categories = ["embedded", "hardware-support", "development-tools::debugging"] 10 | description = "WCH-Link flash tool for WCH's RISC-V MCUs(CH32V, CH56X, CH57X, CH58X, CH59X, CH32L103, CH32X035, CH641, CH643)" 11 | keywords = ["embedded", "WCH", "CH32V", "WCH-Link"] 12 | readme = "README.md" 13 | license = "MIT OR Apache-2.0" 14 | 15 | [features] 16 | default = [] 17 | 18 | [dependencies] 19 | anyhow = "1" 20 | bitfield = "0.17.0" 21 | clap = { version = "4", features = ["derive"] } 22 | hex = "0.4.3" 23 | ihex = "3.0.0" 24 | log = "0.4" 25 | nu-pretty-hex = "0.100.0" 26 | rusb = "0.9.1" 27 | simplelog = "0.12.0" 28 | thiserror = "2" 29 | object = { version = "0.36", default-features = false, features = [ 30 | "elf", 31 | "read_core", 32 | "std", 33 | ] } 34 | indicatif = "0.17.7" 35 | serialport = "4.6" 36 | libloading = "0.8" 37 | chrono = "0.4" 38 | clap-verbosity-flag = "2" 39 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) 2023 Andelf 2 | 3 | Permission is hereby granted, free of charge, to any 4 | person obtaining a copy of this software and associated 5 | documentation files (the "Software"), to deal in the 6 | Software without restriction, including without 7 | limitation the rights to use, copy, modify, merge, 8 | publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software 10 | is furnished to do so, subject to the following 11 | conditions: 12 | 13 | The above copyright notice and this permission notice 14 | shall be included in all copies or substantial portions 15 | of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 18 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 19 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 20 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 21 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 23 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 24 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | DEALINGS IN THE SOFTWARE. 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # wlink - WCH-Link(RV) command line tool 2 | 3 | [![Crates.io][badge-license]][crates] 4 | [![Crates.io][badge-version]][crates] 5 | [![docs.rs][badge-docsrs]][docsrs] 6 | [![GitHub release][badge-release]][nightly] 7 | 8 | [badge-license]: https://img.shields.io/crates/l/wlink?style=for-the-badge 9 | [badge-version]: https://img.shields.io/crates/v/wlink?style=for-the-badge 10 | [badge-docsrs]: https://img.shields.io/docsrs/wlink?style=for-the-badge 11 | [badge-release]: https://img.shields.io/github/v/release/ch32-rs/wlink?include_prereleases&style=for-the-badge 12 | [crates]: https://crates.io/crates/wlink 13 | [docsrs]: https://docs.rs/wlink 14 | [nightly]: https://github.com/ch32-rs/wlink/releases/tag/nightly 15 | 16 | > **Note** 17 | > This tool is still in development and not ready for production use. 18 | 19 | ## Feature Support 20 | 21 | - [x] Flash firmware, support Intel HEX, ELF and raw binary format 22 | - [x] Erase chip 23 | - [x] Halt, resume, reset support 24 | - [x] Read chip info 25 | - [x] Read chip memory(flash) 26 | - [x] Read/write chip register - very handy for debugging 27 | - [x] Code-Protect & Code-Unprotect for supported chips 28 | - [x] Enable or Disable 3.3V, 5V output 29 | - [x] [SDI print](https://www.cnblogs.com/liaigu/p/17628184.html) support, requires 2.10+ firmware 30 | - [x] [Serial port watching](https://github.com/ch32-rs/wlink/pull/36) for a smooth development experience 31 | - [x] Windows native driver support, no need to install libusb manually (requires x86 build) 32 | 33 | ## Tested On 34 | 35 | ### Probes 36 | 37 | Current firmware version: 2.15 (aka. v35). 38 | 39 | > **NOTE**: If you are using newer chips like CH32V317 or CH585, be sure to use the latest firmware. 40 | 41 | > **NOTE**: The firmware version is not the same as the version shown by WCH's toolchain. Because WCH calculates the version number by `major * 10 + minor`, so the firmware version 2.10 is actually v30 `0x020a`. 42 | 43 | - WCH-Link [CH549] - the first version, reflash required when switching mode 44 | - WCH-LinkE [CH32V305][CH32V307] - the recommended debug probe 45 | - WCH-LinkW [CH32V208][CH32V208] - wireless version 46 | - WCH-Link? [CH32V203][CH32V203] 47 | 48 | [CH549]: https://www.wch-ic.com/products/CH549.html 49 | 50 | ### MCU 51 | 52 | > **Note** 53 | > A common misunderstanding is that the CH32V2 and CH32V3 series has fewer flash! No it's not. 54 | > The CH32V203 series has 224K available flash. The CH32V208 and CH32V30x series has 480K available flash. 55 | > The number in the datasheet is the "zero-wait-state" flash size, which is not the same as the "available" flash size. 56 | 57 | - [CH32V003] 58 | - [CH32V103] 59 | - [CH32V203]/[CH32V208] 60 | - [CH32V307] 61 | - [CH569]/CH565 62 | - [CH573]/CH571 63 | - [CH583]/CH582/CH581 64 | - [CH585]/CH584 65 | - [CH592]/CH591 66 | - [ ] [CH643] - I don't have this chip, help wanted 67 | - [ ] [CH32V317] - I don't have this chip, help wanted 68 | - [CH641] 69 | - [CH32X035]/CH32X033 70 | - [CH32L103] 71 | - [ ] [CH8571] - No other source about this chip, help wanted 72 | - ... (Feel free to open an issue if you have tested on other chips) 73 | 74 | [CH32V003]: https://www.wch-ic.com/products/CH32V003.html 75 | [CH32V103]: https://www.wch-ic.com/products/CH32V103.html 76 | [CH32V203]: https://www.wch-ic.com/products/CH32V203.html 77 | [CH32V208]: https://www.wch-ic.com/products/CH32V208.html 78 | [CH32V307]: https://www.wch-ic.com/products/CH32V307.html 79 | [CH32V317]: https://www.wch.cn/products/CH32V317.html 80 | [CH32X035]: https://www.wch-ic.com/products/CH32X035.html 81 | [CH32L103]: https://www.wch-ic.com/products/CH32L103.html 82 | [CH569]: https://www.wch-ic.com/products/CH569.html 83 | [CH573]: https://www.wch-ic.com/products/CH573.html 84 | [CH583]: https://www.wch-ic.com/products/CH583.html 85 | [CH585]: https://www.wch-ic.com/products/CH585.html 86 | [CH592]: https://www.wch-ic.com/products/CH592.html 87 | [CH641]: https://www.wch-ic.com/products/CH641.html 88 | [CH643]: https://www.wch-ic.com/products/CH643.html 89 | [CH8571]: https://www.wch.cn/news/606.html 90 | 91 | ## Install 92 | 93 | `cargo install --git https://github.com/ch32-rs/wlink` or download a binary from the [Nightly Release page](https://github.com/ch32-rs/wlink/releases/tag/nightly). 94 | 95 | > **Note** 96 | > On Linux, you should install libudev and libusb development lib first. 97 | > Like `sudo apt install libudev-dev libusb-1.0-0-dev` on Ubuntu. 98 | 99 | ### Arch Linux 100 | 101 | Arch Linux users can install [wlink-git](https://aur.archlinux.org/packages/wlink-git) via the AUR. 102 | 103 | ```bash 104 | yay -Syu wlink 105 | ``` 106 | 107 | ## Usage 108 | 109 | > **Note** 110 | > For help of wire connection for specific chips, please refer to `docs` subdirectory. 111 | 112 | ```console 113 | > # Flash firmware.bin to Code FLASH at address 0x08000000 114 | > wlink flash --address 0x08000000 ./firmware.bin 115 | 12:10:26 [INFO] WCH-Link v2.10 (WCH-Link-CH549) 116 | 12:10:26 [INFO] Attached chip: CH32V30X(0x30700518) 117 | 12:10:26 [INFO] Flashing 8068 bytes to 0x08000000 118 | 12:10:27 [INFO] Flash done 119 | 12:10:28 [INFO] Now reset... 120 | 12:10:28 [INFO] Resume executing... 121 | 122 | > # Flash firmware.bin to System FLASH, enable SDI print, then watch serial port 123 | > wlink flash --enable-sdi-print --watch-serial firmware.bin 124 | 02:54:34 [INFO] WCH-Link v2.11 (WCH-LinkE-CH32V305) 125 | 02:54:34 [INFO] Attached chip: CH32V003 [CH32V003F4P6] (ChipID: 0x00300500) 126 | 02:54:34 [INFO] Flash already unprotected 127 | 02:54:34 [INFO] Flash protected: false 128 | 02:54:35 [INFO] Flash done 129 | 02:54:35 [INFO] Now reset... 130 | 02:54:35 [INFO] Now connect to the WCH-Link serial port to read SDI print 131 | Hello world from ch32v003 SDI print! 132 | led toggle 133 | led toggle 134 | ... 135 | 136 | 137 | > # Dump Code FLASH, for verification 138 | > # use `-v` or `-vv` for more logs 139 | > wlink -v dump 0x08000000 100 140 | 18:31:18 [DEBUG] (1) wlink::device: Acquired libusb context. 141 | 18:31:18 [DEBUG] (1) wlink::device: Claimed interface 0 of USB device. 142 | 18:31:18 [INFO] WCH-Link v2.8 (WCH-LinkE-CH32V305) 143 | 18:31:18 [DEBUG] (1) wlink::operations: attached chip: ChipInfo { chip_family: CH32V20X, chip_type: "0x20360510" } 144 | 18:31:18 [DEBUG] (1) wlink::operations: Chip UID: cd-ab-b4-ae-45-bc-c6-16 145 | 18:31:18 [DEBUG] (1) wlink::operations: flash protected: false 146 | 18:31:18 [DEBUG] (1) wlink::operations: SRAM CODE mode: 3 147 | 18:31:18 [DEBUG] (1) wlink::operations: RISC-V core version: Some("WCH-V4B") 148 | 18:31:18 [INFO] Read memory from 0x08000000 to 0x08000064 149 | 08000000: b7 00 00 08 67 80 80 00 73 50 40 30 73 50 40 34 ×00•g××0sP@0sP@4 150 | 08000010: 81 40 01 41 81 41 01 42 81 42 01 43 81 43 01 44 ×@•A×A•B×B•C×C•D 151 | 08000020: 81 44 81 46 01 47 81 47 01 48 81 48 01 49 81 49 ×D×F•G×G•H×H•I×I 152 | 08000030: 01 4a 81 4a 01 4b 81 4b 01 4c 81 4c 01 4d 81 4d •J×J•K×K•L×L•M×M 153 | 08000040: 01 4e 81 4e 01 4f 81 4f 97 01 00 18 93 81 81 7b •N×N•O×Oו0•×××{ 154 | 08000050: f3 23 40 f1 b7 02 00 00 93 82 02 00 63 f4 72 00 ×#@×ו00×ו0c×r0 155 | 08000060: 6f 00 c0 29 o0×) 156 | 157 | 158 | > # Dump System FLASH, BOOT_28KB 159 | > wlink dump 0x1FFF8000 0x7000 160 | .... 161 | 162 | 163 | > # Dump all general purpose registers 164 | > wlink regs 165 | 16:24:20 [INFO] Dump GPRs 166 | dpc(pc): 0x2000011a 167 | x0 zero: 0x00000000 168 | x1 ra: 0x49c85c07 169 | x2 sp: 0x20002800 170 | x3 gp: 0x206e24c4 171 | x4 tp: 0x9add07a3 172 | x5 t0: 0xb4a9b38a 173 | .... 174 | 175 | 176 | > # Set dpc(pc) to System Flash 177 | > wlink write-reg 0x7b1 0x000009a8 178 | ``` 179 | 180 | ## References 181 | 182 | - [docs/references.md](docs/references.md) 183 | - WCH's openocd fork: 184 | 185 | ## License 186 | 187 | This project is licensed under the MIT or Apache-2.0 license, at your option. 188 | -------------------------------------------------------------------------------- /docs/CH32L103.md: -------------------------------------------------------------------------------- 1 | # CH32L103 2 | 3 | RISC-V4C, 96MHz, 64KB Flash, 20KB SRAM. 4 | 5 | RTC, ADC/TKey, CAN, OPA, CMP, USB-PD/Type-C. 6 | 7 | ## Chips 8 | 9 | ``` 10 | * CH32L103C8U6-0x103007x0 11 | * CH32L103C8T6-0x103107x0 12 | * CH32L103F8P6-0x103A07x0 13 | * CH32L103G8R6-0x103B07x0 14 | * CH32L103K8U6-0x103207x0 15 | * CH32L103F8U6-0x103D07x0 16 | * CH32L103F7P6-0x103707x0 17 | ``` 18 | -------------------------------------------------------------------------------- /docs/CH32V003.md: -------------------------------------------------------------------------------- 1 | # CH32V003 2 | 3 | RISC-V2A, 16KB Flash, 2KB SRAM, 48MHz. 4 | 5 | > **Note** 6 | > CH32V003 is a riscv32ec core, which is not supported by ofiicial Rust yet. 7 | > ch32-rs team maintains a fork of Rust at . 8 | > You can check [Noxim's Blog](https://noxim.xyz/blog/rust-ch32v003/introduction/) for riscv32ec support. 9 | 10 | ## Debug support 11 | 12 | 1-wire debug. 13 | 14 | ```text 15 | SWDIO <-> D1/DIO@MCU, PD1 16 | GND <-> GND@MCU 17 | 3V3 <-> 3V3@MCU (No need if you connect your MCU board with USB-C power supply) 18 | ``` 19 | 20 | - [x] Flash support 21 | - [x] Memory dump support 22 | - [x] Reset / Resume 23 | - [x] Reg read/write support 24 | 25 | ```console 26 | > wlink -v flash ./firmware.bin 27 | 13:41:49 [INFO] WCH-Link v2.8 (WCH-LinkE-CH32V305) 28 | 13:41:49 [INFO] attached chip: CH32V003(0x00300500) 29 | 13:41:49 [DEBUG] (1) wlink::operations: Chip UID: cd-ab-80-17-48-bc-95-7f 30 | 13:41:49 [DEBUG] (1) wlink::operations: flash protected: false 31 | 13:41:49 [DEBUG] (1) wlink::operations: SRAM CODE mode: 3 32 | 13:41:49 [DEBUG] (1) wlink::operations: already halted 33 | 13:41:49 [DEBUG] (1) wlink::operations: RISC-V core version: Some("WCH-V2A") 34 | 13:41:49 [INFO] flash 860 bytes 35 | 13:41:49 [DEBUG] (1) wlink::operations: Code start address 0x08000000 36 | 13:41:49 [DEBUG] (1) wlink::operations: flash op written 37 | 13:41:49 [DEBUG] (1) wlink::operations: fastprogram done 38 | 13:41:49 [INFO] flash done 39 | 13:41:50 [INFO] now reset... 40 | 13:41:51 [INFO] resume executing... 41 | 13:41:51 [WARN] resume fails 42 | ``` 43 | 44 | ## Chips 45 | 46 | ``` 47 | * CH32V003F4P6-0x003005x0 48 | * CH32V003F4U6-0x003105x0 49 | * CH32V003A4M6-0x003205x0 50 | * CH32V003J4M6-0x003305x0 51 | ``` 52 | -------------------------------------------------------------------------------- /docs/CH32V103.md: -------------------------------------------------------------------------------- 1 | # CH32V103 2 | 3 | SWDIO - PA13 @ MCU 4 | SWCLK - PA14 @ MCU 5 | 6 | "Deprotect" or "接触代码保护" must be run before using wlin.(use WCHISPTool or wchisp). 7 | 8 | ## USBISP 9 | 10 | BOOT0 to VCC 11 | 12 | ## Chips 13 | 14 | ``` 15 | * CH32F103C8T6-0x20004102 16 | * CH32F103R8T6-0x2000410F 17 | ``` 18 | -------------------------------------------------------------------------------- /docs/CH32V20x.md: -------------------------------------------------------------------------------- 1 | # CH32V20x 2 | 3 | - PA13 SWDIO 4 | - PA14 SWCLK 5 | 6 | ## Dev Boards 7 | 8 | - [nanoCH32V203] by Muse Lab - CH32V203C8T6 9 | - [FlappyBoard] - CH32V203G6U6 10 | 11 | [nanoCH32V203]: https://github.com/wuxx/nanoCH32V203 12 | [FlappyBoard]: https://github.com/metro94/FlappyBoard 13 | 14 | ## Notes 15 | 16 | - Erased bytes `39 e3 39 e3` 17 | 18 | ## Chips 19 | 20 | ``` 21 | * CH32V203C8U6-0x203005x0 22 | * CH32V203C8T6-0x203105x0 23 | * CH32V203K8T6-0x203205x0 24 | * CH32V203C6T6-0x203305x0 25 | * CH32V203K6T6-0x203505x0 26 | * CH32V203G6U6-0x203605x0 27 | * CH32V203G8R6-0x203B05x0 28 | * CH32V203F8U6-0x203E05x0 29 | * CH32V203F6P6-0x203705x0-0x203905x0 30 | * CH32V203F8P6-0x203A05x0 31 | * CH32V203RBT6-0x203405xC 32 | * CH32V208WBU6-0x208005xC 33 | * CH32V208RBT6-0x208105xC 34 | * CH32V208CBU6-0x208205xC 35 | * CH32V208GBU6-0x208305xC 36 | ``` 37 | -------------------------------------------------------------------------------- /docs/CH32V30x.md: -------------------------------------------------------------------------------- 1 | # CH32V30x 2 | 3 | - CH32V303 4 | - CH32V305 5 | - CH32V307 6 | 7 | ```console 8 | wlink -v dump 0x08000000 0x10` 9 | 14:40:22 [INFO] WCH-Link v2.8 (WCH-LinkE-CH32V305) 10 | 14:40:22 [INFO] attached chip: CH32V30X(0x30700518) 11 | 14:40:22 [DEBUG] (1) wlink::operations: Chip UID: 30-78-3e-26-3b-38-a9-d6 12 | 14:40:22 [DEBUG] (1) wlink::operations: flash protected: false 13 | 14:40:22 [DEBUG] (1) wlink::operations: SRAM CODE mode: 3 14 | 14:40:22 [DEBUG] (1) wlink::operations: already halted 15 | 14:40:22 [DEBUG] (1) wlink::operations: RISC-V core version: Some("WCH-V4A") 16 | 14:40:22 [INFO] Read memory from 0x08000000 to 0x08000010 17 | 08000000: b7 00 00 08 67 80 80 00 73 50 40 30 73 50 40 34 ×00•g××0sP@0sP@4 18 | ``` 19 | 20 | ## Notes 21 | 22 | - erased flash `39 e3 39 e3` 23 | 24 | ## Chips 25 | 26 | ``` 27 | * CH32V303CBT6-0x303305x4 28 | * CH32V303RBT6-0x303205x4 29 | * CH32V303RCT6-0x303105x4 30 | * CH32V303VCT6-0x303005x4 31 | * CH32V305FBP6-0x305205x8 32 | * CH32V305RBT6-0x305005x8 33 | * CH32V305GBU6-0x305B05x8 34 | * CH32V307WCU6-0x307305x8 35 | * CH32V307FBP6-0x307205x8 36 | * CH32V307RCT6-0x307105x8 37 | * CH32V307VCT6-0x307005x8 38 | ``` 39 | -------------------------------------------------------------------------------- /docs/CH32X035.md: -------------------------------------------------------------------------------- 1 | # CH32X035/CH32X033 2 | 3 | RISC-V4C, 62KB Flash, 20KB SRAM. 4 | 5 | OPA/PGA/CMP, USB-PD/Type-C. 6 | 7 | ## Debug Pins 8 | 9 | - SWDIO - PC18/DIO @ MCU 10 | - SWCLK - PC19/DCK @ MCU 11 | 12 | PC17 to High, enable USB ISP DOWNLOAD MODE 13 | 14 | ## Boards 15 | 16 | Official CH32X035EVT 17 | 18 | - CH32X035F8U6-R0 19 | - CH32X035G8U6-R0 20 | - CH32X035C8T6-R0 21 | 22 | LED1, LED2 blue LED, active low, open, connected to open jumper 23 | 24 | ## Chips 25 | 26 | ``` 27 | CH32X035R8T6-0x035006x1 28 | CH32X035C8T6-0x035106x1 29 | CH32X035F8U6-0x035E06x1 30 | CH32X035G8U6-0x035606x1 31 | CH32X035G8R6-0x035B06x1 32 | CH32X035F7P6-0x035706x1 33 | CH32X033F8P6-0x035A06x1 34 | ``` 35 | -------------------------------------------------------------------------------- /docs/CH56X.md: -------------------------------------------------------------------------------- 1 | # CH569/CH565 2 | 3 | RISC-V3A, 448KB Code Flash, 32KB Data Flash, 32/64/96KB SRAM(RAMX). 4 | 5 | BUS8, USB-SS, ETH, EMMC, SerDes 6 | 7 | - CH569: HSPI 8 | - CH565: DVP 9 | 10 | ## Debug Pins 11 | 12 | - TCK=HTACK=PA10 13 | - TIO=HTCLK=PA11 14 | 15 | ## Boards 16 | 17 | - CH569W-R0-1v0 18 | -------------------------------------------------------------------------------- /docs/CH57x_CH58x_CH59x.md: -------------------------------------------------------------------------------- 1 | # CH57X & CH58X & CH59X 2 | 3 | BLE MCUs. They are sharing almost the same preipheral set 4 | 5 | ## Debug Pins 6 | 7 | - PB15 TCK 8 | - PB14 TIO 9 | 10 | ## Notes 11 | 12 | - Enable debug using WCHISPTool 13 | - After debug enabled, flash should be prgrammed at lease once 14 | - `a9 bd f9 f3` means erased flash or protected flash. The detailed mechanism is unknown 15 | -------------------------------------------------------------------------------- /docs/CH641.md: -------------------------------------------------------------------------------- 1 | # CH641 2 | 3 | RISC-V2A, 48MHz, 16KB Flash, 2KB SRAM. 4 | 5 | USB-PD/Type-C, BC1.2, HV DCP. 6 | ISP/ISN, QII. 7 | 8 | > **Note** 9 | > CH641 is a riscv32ec core, which is not supported by ofiicial Rust yet. 10 | > ch32-rs team maintains a fork of Rust at . 11 | > You can check [Noxim's Blog](https://noxim.xyz/blog/rust-ch32v003/introduction/) for riscv32ec support. 12 | 13 | ## Chips 14 | 15 | ``` 16 | * CH641F-0x641005x0 17 | * CH641D-0x641105x0 18 | * CH641U-0x641505x0 19 | * CH641P-0x641605x0 20 | ``` 21 | -------------------------------------------------------------------------------- /docs/CH643.md: -------------------------------------------------------------------------------- 1 | # CH643 2 | 3 | RISC-V4C, 62KB Flash, 20KB SRAM. 4 | 5 | OPA/PGA/CMP, LEDPWM, USB-PD/Type-C. 6 | 7 | ## Chips 8 | 9 | ``` 10 | * CH643W-0x64300601 11 | * CH643Q-0x64310601 12 | * CH643L-0x64330601 13 | * CH643U-0x64340601 14 | ``` 15 | -------------------------------------------------------------------------------- /docs/references.md: -------------------------------------------------------------------------------- 1 | # References 2 | 3 | ## Real Shot 4 | 5 | ![real shot](https://web.archive.org/web/20230613102346im_/https://www.wch.cn/uploads/image/20221230/1672381416120803.png) 6 | 7 | ## Feature matrix 8 | 9 | | Feature | WCH-Link | WCH-LinkE | WCH-LinkW | WCH-DAPLink | 10 | | ------------------------------- | :--------: | :--------: | :--------: | :---------: | 11 | | RISC-V mode | ✓ | ✓ | ✓ | | 12 | | ARM-SWD mode (HID device) | | | | ✓ | 13 | | ARM-SWD mode (WinUSB device) | ✓ | ✓ | ✓ | ✓ | 14 | | ARM-JTAG mode (HID device) | | | | ✓ | 15 | | ARM-JTAG mode (WinUSB device) | | ✓ | ✓ | ✓ | 16 | | ModeS button | | ✓ | ✓ | ✓ | 17 | | DFU via 2-wire | ✓ | | | | 18 | | DFU via serial-port | ✓ | | | | 19 | | DFU via USB | ✓ | | ✓ | ✓ | 20 | | Power Supply (3.3v) | ✓ | ✓ | ✓ | ✓ | 21 | | Power Supply (5.0v) | ✓ | ✓ | ✓ | ✓ | 22 | | Power Supply (Configurable) | | ✓ | ✓ | ✓ | 23 | | USB 2.0 to JTAG | | ✓ | | | 24 | | Wireless mode | | | ✓ | | 25 | | Download via [MounRiver Studio] | ✓ | ✓ | ✓ | ✓ | 26 | | Download via [WCH-LinkUtility] | ✓ | ✓ | ✓ | | 27 | | Download via [Keil] | ≥ v5.25 | ≥ v5.25 | ≥ v5.25 | ✓ | 28 | 29 | ## Supported Chip matrix 30 | 31 | | Chip | WCH-Link | WCH-LinkE | WCH-LinkW | WCH-DAPLink | 32 | | -------------- | :------: | :-------: | :-------: | :---------: | 33 | | JTAG interface | | ✓ | ✓ | ✓ | 34 | | SWD interface | ✓ | ✓ | ✓ | ✓ | 35 | | CH32F10x | ✓ | ✓ | ✓ | ✓ | 36 | | CH32F20x | ✓ | ✓ | ✓ | ✓ | 37 | | CH32V003 | | ✓ | ✓ | | 38 | | CH32V10x | ✓ | ✓ | ✓ | | 39 | | CH32V20X | ✓ | ✓ | ✓ | | 40 | | CH32V30X | ✓ | ✓ | ✓ | | 41 | | CH569 | ✓ | ✓ | | | 42 | | CH573 | ✓ | ✓ | | | 43 | | CH579 | ✓ | ✓ | ✓ | ✓ | 44 | | CH583 | ✓ | ✓ | | | 45 | 46 | ## Supported Baud matrix 47 | 48 | | Baud | WCH-Link | WCH-LinkE | WCH-LinkW | WCH-DAPLink | 49 | | -----: | :------: | :-------: | :-------: | :---------: | 50 | | 1200 | ✓ | ✓ | ✓ | ✓ | 51 | | 2400 | ✓ | ✓ | ✓ | ✓ | 52 | | 4800 | ✓ | ✓ | ✓ | ✓ | 53 | | 9600 | ✓ | ✓ | ✓ | ✓ | 54 | | 14400 | ✓ | ✓ | ✓ | ✓ | 55 | | 19200 | ✓ | ✓ | ✓ | ✓ | 56 | | 38400 | ✓ | ✓ | ✓ | ✓ | 57 | | 57600 | ✓ | ✓ | ✓ | ✓ | 58 | | 115200 | ✓ | ✓ | ✓ | ✓ | 59 | | 230400 | ✓ | ✓ | ✓ | ✓ | 60 | | 460800 | | ✓ | ✓ | ✓ | 61 | | 921600 | | ✓ | ✓ | ✓ | 62 | 63 | ## SWD PIN matrix 64 | 65 | | Chip | SWDIO | SWCLK | 66 | | -------- | :---: | :---: | 67 | | CH32F10x | PA13 | PA14 | 68 | | CH32F20x | PA13 | PA14 | 69 | | CH32V003 | PD1 | | 70 | | CH32V10x | PA13 | PA14 | 71 | | CH32V20X | PA13 | PA14 | 72 | | CH32V30X | PA13 | PA14 | 73 | | CH32X035 | PC18 | PC19 | 74 | | CH569 | PA11 | PA10 | 75 | | CH573 | PB14 | PB15 | 76 | | CH579 | PB16 | PB17 | 77 | | CH583 | PB14 | PB15 | 78 | | CH59x | PB14 | PB15 | 79 | | CH643 | PC18 | PC19 | 80 | 81 | ## Documentation 82 | 83 | - [WCH-Link 相关资料汇总](https://web.archive.org/web/20230613102346/https://www.wch.cn/bbs/thread-71088-1.html) 84 | - [WCH-Link 使用说明 v1.7](https://web.archive.org/web/20230613114619if_/https://www.wch.cn/downloads/file/417.html?time=2023-06-13%2019:46:05&code=1BaRkx0gWHP7accBAPUtCuJ0dk0emAIzZ85o8UIf) 85 | - [WCH-LinkSCH.pdf](https://web.archive.org/web/20230613133629/https://www.wch.cn/downloads/file/421.html?time=2023-06-13%2021:35:48&code=CA0Mz2JvD7YBhFB9t8jVb3MhgGgZV4fxg23Ku5B6) 86 | - [User Manual (Chinese)](https://web.archive.org/web/20230613102015if_/https://www.wch.cn/downloads/file/417.html?time=2023-06-13%2018:19:04&code=z6nAIBmh1M4Uv64xdbCeAwywfJ9OEPG6OBvdUz1A) 87 | - [User Manual (English)](https://web.archive.org/web/20230613102158if_/http://www.wch-ic.com/downloads/file/372.html?time=2023-06-13%2018:20:36&code=uRfQmamyIynlCZPHO33rloOWiCgb44NLTXxStO8l) 88 | 89 | ## ISP 90 | 91 | - [WCHISPTool_Setup.exe](https://web.archive.org/web/20220811233210if_/https://www.wch.cn/downloads/file/196.html?time=2022-06-30%2014:56:16&code=LS2LHywwDiw3P71gxsM1hfZClwSQlbI4nQga1Kzo) v3.3 92 | 93 | ## Firmware 94 | 95 | - [WCH_RISC-V_MCU_ProgramTool.zip](https://web.archive.org/web/20230613112000if_/https://www.wch.cn/uploads/file/20220628/1656415558432295.zip) 96 | - [WCH-Link v2.3](https://web.archive.org/web/20230613112654if_/https://www.wch.cn/uploads/file/20220718/1658124411917956.zip) 97 | - [WCH-LinkE v1.1](https://web.archive.org/web/20230613112104if_/https://www.wch.cn/uploads/file/20220913/1663036474195451.zip) 98 | 99 | ## Other FOSS implementation 100 | 101 | - RISC-V QingKeV2 Microprocessor Debug Manual 102 | - A miniwchlink implementation 103 | - 104 | - [MounRiver Studio] compatible WCH-Link OpenOCD source code 105 | 106 | [MounRiver Studio]: http://www.mounriver.com "MounRiver Studio" 107 | [WCH-LinkUtility]: https://web.archive.org/web/20230613114515if_/https://www.wch.cn/downloads/file/418.html?time=2023-06-13%2019:44:31&code=z88GXEXY3kNBV9rTwDe0iWerDk5iKHB50lkst8j8 "WCH LinkUtility" 108 | [Keil]: https://www.keil.com "Keil Embedded Development Tools" 109 | -------------------------------------------------------------------------------- /protocol.md: -------------------------------------------------------------------------------- 1 | # WCH-LinkRV Protocol 2 | 3 | WCH-Link uses USB Bulk Transfer. 4 | 5 | ```rust 6 | const VENDOR_ID: u16 = 0x1a86; 7 | const PRODUCT_ID: u16 = 0x8010; 8 | 9 | const ENDPOINT_OUT: u8 = 0x01; 10 | const ENDPOINT_IN: u8 = 0x81; 11 | 12 | // const RAW_ENDPOINT_OUT: u8 = 0x02; 13 | // const RAW_ENDPOINT_IN: u8 = 0x82; 14 | ``` 15 | 16 | ## USB Packet 17 | 18 | Request packet: 19 | 20 | | 0 | 1 | 2 | 3 ... n | 21 | | ---- | --- | --- | ----------- | 22 | | 0x81 | CMD | LEN | PAYLOAD ... | 23 | 24 | Success response packet: 25 | 26 | | 0 | 1 | 2 | 3 ... n | 27 | | ---- | --- | --- | ----------- | 28 | | 0x82 | CMD | LEN | PAYLOAD ... | 29 | 30 | Error response packet: 31 | 32 | | 0 | 1 | 2 | 3 ... n | 33 | | ---- | ------ | --- | ----------- | 34 | | 0x81 | REASON | LEN | PAYLOAD ... | 35 | 36 | where: 37 | 38 | - LEN = PAYLOAD.len() 39 | - PAYLOAD 40 | 41 | ## Command 42 | 43 | - 0x01 - Set address and size 44 | - 0x02 - Program 45 | - 0x03 - Memory read 46 | - 0x06 - Flash Read Protect 47 | - 0x08 - DMI OP 48 | - 0x0b - Reset 49 | - 0x0c - ??? 50 | - 0x0d - Info 51 | - 0x0e - Disable debug for riscvchip 2, 3 52 | - 0x0f - ? beigin verify 53 | 54 | ### 0x01 - Set RAM address and size 55 | 56 | ### 0x02 - Program 57 | 58 | - 0x01 Erase 59 | - 0x03 execute ram? 60 | - 0x05 begin buck transfer? 61 | - 0x06 ?? - wlink_ready_write 62 | - 0x07 Verify 63 | - 0x08 End Program 64 | - 0x09 buck transfer ends 65 | - 0x0a ? Verify 66 | - 0x0b for riscvchip 1, = verify 67 | - 0x0c BeginReadMemory 68 | 69 | ### 0x03 - Memory Read 70 | 71 | - offset: u32 72 | - len: u32 73 | 74 | ### 0x06 - Flash Read Protect 75 | 76 | - 0x01 Check if flash is read-protected 77 | - 0x01 protected, read-memory return random data 78 | - 0x02 not protected 79 | - 0x03 Set flash read-protected 80 | - 0x02 Set flash read-unprotected 81 | 82 | Set subcommand requires quitreset for riscvchip 1 83 | 84 | ### 0x08 - DMI OP 85 | 86 | PAYLOAD 87 | 88 | | iAddr_u8 | iData_u32_be | iOp_u8 | 89 | | -------- | ------------ | ------ | 90 | 91 | Response PAYLOAD 92 | 93 | | oAddr_u8 | oData_u32_be | oOp_u8 | 94 | | -------- | ------------ | ------ | 95 | 96 | where: 97 | 98 | oOp_u8 = 0x02 when failed 99 | 100 | ### 0x0b - Reset 101 | 102 | - 0x01 Quit reset 103 | - 300ms delay after this command 104 | - 0x02 Reset for riscvchip 0x02 105 | - 0x03 Reset, normal? 106 | 107 | ### 0x0d - Control 108 | 109 | - 0x01 Get firmware version 110 | - 0x02 Connect chip 111 | - 0x03 ? stage after connect chip and read riscvchip, for riscvchip 1 112 | - 0x04 get rom ram split, for riscvchip 3, 5, 6, 9 113 | - 0xff End process 114 | 115 | ### 0x0e 116 | 117 | - 0x01 Disable debug for riscvchip 2, 3 118 | 119 | ## Error Reason 120 | 121 | - 0x55: failed to connect with riscvchip 122 | 123 | ## Constants 124 | 125 | ### RiscvChip 126 | 127 | ```rust 128 | /// Currently supported RISC-V chip series 129 | #[repr(u8)] 130 | pub enum RiscvChip { 131 | /// CH32V103 RISC-V3A series 132 | CH32V103 = 0x01, 133 | /// CH571/CH573 RISC-V3A BLE 4.2 series 134 | CH57X = 0x02, 135 | /// CH565/CH569 RISC-V3A series 136 | CH56X = 0x03, 137 | /// CH32V20X RISC-V4B/V4C series 138 | CH32V20X = 0x05, 139 | /// CH32V30X RISC-V4C/V4F series 140 | CH32V30X = 0x06, 141 | /// CH581/CH582/CH583 RISC-V4A BLE 5.3 series 142 | CH58X = 0x07, 143 | /// CH32V003 RISC-V2A series 144 | CH32V003 = 0x09, 145 | } 146 | ``` 147 | 148 | ## Firmware Versions 149 | 150 | WCH-LinkUtility v2.8 151 | 152 | MRS IDE v2.7 153 | 154 | ## Variants 155 | 156 | | MCU | Variant | Description | 157 | | --------- | ------------------ | -------------------------------------- | 158 | | CH549 | WCH-Link-R1-1v1 | Swith mode by changing firmware | 159 | | CH32V305F | WCH-LinkE-R0-1v3 | Swith mode by button or EEPROM setting | 160 | | CH32V203 | WCH-LinkS-CH32V203 | | 161 | | ?? | WCH-LinkB | | 162 | 163 | ## References 164 | 165 | - Official WCH-Link Homepage(English) \ 166 | 167 | - Official WCH-Link Homepage(Chinese) \ 168 | 169 | -------------------------------------------------------------------------------- /src/chips.rs: -------------------------------------------------------------------------------- 1 | //! The chip DB. 2 | //! This numbers are from `GetCHIPID` fn in EVT code. 3 | 4 | pub fn chip_id_to_chip_name(chip_id: u32) -> Option<&'static str> { 5 | match chip_id & 0xFFF00000 { 6 | 0x650_00000 => Some("CH565"), 7 | 0x690_00000 => Some("CH569"), 8 | 0x710_00000 => Some("CH571"), 9 | 0x730_00000 => Some("CH573"), 10 | 0x810_00000 => Some("CH581"), 11 | 0x820_00000 => Some("CH582"), 12 | 0x830_00000 => Some("CH583"), 13 | 0x840_00000 => Some("CH584"), 14 | 0x920_00000 => Some("CH592"), 15 | 0x930_00000 => Some("CH585"), 16 | 0x003_00000 => match chip_id & 0xFFFFFF0F { 17 | 0x003_00500 => Some("CH32V003F4P6"), 18 | 0x003_10500 => Some("CH32V003F4U6"), 19 | 0x003_20500 => Some("CH32V003A4M6"), 20 | 0x003_30500 => Some("CH32V003J4M6"), 21 | _ => None, 22 | }, 23 | // https://github.com/openwch/ch32v002_004_005_006_007/blob/main/EVT/EXAM/SRC/Peripheral/src/ch32v00X_dbgmcu.c 24 | 0x006_00000 => match chip_id & 0xFFFFFF0F { 25 | 0x006_00600 => Some("CH32V006K8U6"), 26 | 0x006_10600 => Some("CH32V006E8R6"), 27 | 0x006_20600 => Some("CH32V006F8U6"), 28 | 0x006_30600 => Some("CH32V006F8P6"), 29 | _ => None, 30 | }, 31 | 0x007_00000 => match chip_id & 0xFFFFFF0F { 32 | 0x007_00800 => Some("CH32M007G8R6"), 33 | 0x007_10600 => Some("CH32V007E8R6"), 34 | 0x007_20600 => Some("CH32V007F8U6"), 35 | _ => None, 36 | }, 37 | 0x005_00000 => match chip_id & 0xFFFFFF0F { 38 | 0x005_00600 => Some("CH32V005E6R6"), 39 | 0x005_10600 => Some("CH32V005F6U6"), 40 | 0x005_20600 => Some("CH32V005F6P6"), 41 | 0x005_30600 => Some("CH32V005D6U6"), 42 | _ => None, 43 | }, 44 | 0x002_00000 => match chip_id & 0xFFFFFF0F { 45 | 0x002_00600 => Some("CH32V002F4P6"), 46 | 0x002_10600 => Some("CH32V002F4U6"), 47 | 0x002_20600 => Some("CH32V002A4M6"), 48 | 0x002_30600 => Some("CH32V002D4U6"), 49 | 0x002_40600 => Some("CH32V002J4M6"), 50 | _ => None, 51 | }, 52 | 0x004_00000 => match chip_id & 0xFFFFFF0F { 53 | 0x004_00600 => Some("CH32V004F6P1"), 54 | 0x004_10600 => Some("CH32V004F6U1"), 55 | _ => None, 56 | }, 57 | 0x035_00000 => match chip_id & 0xFFFFFF0F { 58 | 0x035_00601 => Some("CH32X035R8T6"), 59 | 0x035_10601 => Some("CH32X035C8T6"), 60 | 0x035_E0601 => Some("CH32X035F8U6"), 61 | 0x035_60601 => Some("CH32X035G8U6"), 62 | 0x035_B0601 => Some("CH32X035G8R6"), 63 | 0x035_70601 => Some("CH32X035F7P6"), 64 | 0x035_A0601 => Some("CH32X033F8P6"), 65 | _ => None, 66 | }, 67 | 0x103_00000 => match chip_id & 0xFFFFFF0F { 68 | 0x103_00700 => Some("CH32L103C8U6"), 69 | 0x103_10700 => Some("CH32L103C8T6"), 70 | 0x103_A0700 => Some("CH32L103F8P6"), 71 | 0x103_B0700 => Some("CH32L103G8R6"), 72 | 0x103_20700 => Some("CH32L103K8U6"), 73 | 0x103_D0700 => Some("CH32L103F8U6"), 74 | 0x103_70700 => Some("CH32L103F7P6"), 75 | _ => None, 76 | }, 77 | 0x200_00000 => match chip_id { 78 | 0x200_04102 => Some("CH32F103C8T6"), 79 | 0x200_0410F => Some("CH32F103R8T6"), 80 | _ => None, 81 | }, 82 | 0x203_00000 => match chip_id & 0xFFFFFF0F { 83 | 0x203_00500 => Some("CH32V203C8U6"), 84 | 0x203_10500 => Some("CH32V203C8T6"), 85 | 0x203_20500 => Some("CH32V203K8T6"), 86 | 0x203_30500 => Some("CH32V203C6T6"), 87 | 0x203_50500 => Some("CH32V203K6T6"), 88 | 0x203_60500 => Some("CH32V203G6U6"), 89 | 0x203_B0500 => Some("CH32V203G8R6"), 90 | 0x203_E0500 => Some("CH32V203F8U6"), 91 | 0x203_70500 => Some("CH32V203F6P6"), 92 | 0x203_90500 => Some("CH32V203F6P6"), 93 | 0x203_A0500 => Some("CH32V203F8P6"), 94 | 0x203_4050C => Some("CH32V203RBT6"), 95 | _ => None, 96 | }, 97 | 0x208_00000 => match chip_id & 0xFFFFFF0F { 98 | 0x208_0050C => Some("CH32V208WBU6"), 99 | 0x208_1050C => Some("CH32V208RBT6"), 100 | 0x208_2050C => Some("CH32V208CBU6"), 101 | 0x208_3050C => Some("CH32V208GBU6"), 102 | _ => None, 103 | }, 104 | 0x303_00000 | 0x305_00000 | 0x307_00000 | 0x317_00000 => match chip_id & 0xFFFFFF0F { 105 | 0x303_30504 => Some("CH32V303CBT6"), 106 | 0x303_20504 => Some("CH32V303RBT6"), 107 | 0x303_10504 => Some("CH32V303RCT6"), 108 | 0x303_00504 => Some("CH32V303VCT6"), 109 | 0x305_20508 => Some("CH32V305FBP6"), 110 | 0x305_00508 => Some("CH32V305RBT6"), 111 | 0x305_B0508 => Some("CH32V305GBU6"), 112 | 0x307_30508 => Some("CH32V307WCU6"), 113 | 0x307_20508 => Some("CH32V307FBP6"), 114 | 0x307_10508 => Some("CH32V307RCT6"), 115 | 0x307_00508 => Some("CH32V307VCT6"), 116 | 0x317_0B508 => Some("CH32V317VCT6"), 117 | 0x317_3B508 => Some("CH32V317WCU6"), 118 | 0x317_5B508 => Some("CH32V317TCU6"), 119 | _ => None, 120 | }, 121 | 0x641 => match chip_id & 0xFFFFFF0F { 122 | 0x641_00500 => Some("CH641F"), 123 | 0x641_10500 => Some("CH641D"), 124 | 0x641_50500 => Some("CH641U"), 125 | 0x641_60500 => Some("CH641P"), 126 | _ => None, 127 | }, 128 | 0x643_00000 => match chip_id { 129 | 0x643_00601 => Some("CH643W"), 130 | 0x643_10601 => Some("CH643Q"), 131 | 0x643_30601 => Some("CH643L"), 132 | 0x643_40601 => Some("CH643U"), 133 | _ => None, 134 | }, 135 | _ => None, 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /src/commands/control.rs: -------------------------------------------------------------------------------- 1 | //! Probe control commands. COMMAND_ID = 0x0d 2 | 3 | use crate::{probe::WchLinkVariant, RiscvChip}; 4 | 5 | use super::*; 6 | 7 | /// GetDeviceVersion (0x0d, 0x01) 8 | #[derive(Debug)] 9 | pub struct GetProbeInfo; 10 | impl Command for GetProbeInfo { 11 | type Response = ProbeInfo; 12 | const COMMAND_ID: u8 = 0x0d; 13 | fn payload(&self) -> Vec { 14 | vec![0x01] 15 | } 16 | } 17 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] 18 | pub struct ProbeInfo { 19 | pub major_version: u8, 20 | pub minor_version: u8, 21 | pub variant: WchLinkVariant, 22 | } 23 | impl ProbeInfo { 24 | pub fn version(&self) -> (u8, u8) { 25 | (self.major_version, self.minor_version) 26 | } 27 | } 28 | impl Response for ProbeInfo { 29 | fn from_payload(bytes: &[u8]) -> Result { 30 | if bytes.len() < 3 { 31 | return Err(crate::error::Error::InvalidPayloadLength); 32 | } 33 | Ok(Self { 34 | major_version: bytes[0], 35 | minor_version: bytes[1], 36 | // Only avaliable in newer version of firmware 37 | variant: if bytes.len() == 4 { 38 | WchLinkVariant::try_from_u8(bytes[2])? 39 | } else { 40 | WchLinkVariant::Ch549 41 | }, 42 | }) 43 | } 44 | } 45 | impl fmt::Display for ProbeInfo { 46 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 47 | write!( 48 | f, 49 | "WCH-Link v{}.{}(v{}) ({})", 50 | self.major_version, 51 | self.minor_version, 52 | self.major_version * 10 + self.minor_version, 53 | self.variant 54 | ) 55 | } 56 | } 57 | 58 | /// ?SetChipType (0x0d, 0x02) 59 | #[derive(Debug)] 60 | pub struct AttachChip; 61 | impl Command for AttachChip { 62 | type Response = AttachChipResponse; 63 | const COMMAND_ID: u8 = 0x0d; 64 | fn payload(&self) -> Vec { 65 | vec![0x02] 66 | } 67 | } 68 | 69 | #[derive(Clone, Copy, Debug, PartialEq, Eq)] 70 | pub struct AttachChipResponse { 71 | pub chip_family: RiscvChip, 72 | pub riscvchip: u8, 73 | pub chip_id: u32, 74 | } 75 | impl Response for AttachChipResponse { 76 | fn from_payload(bytes: &[u8]) -> Result { 77 | if bytes.len() != 5 { 78 | return Err(Error::InvalidPayloadLength); 79 | } 80 | Ok(Self { 81 | chip_family: RiscvChip::try_from_u8(bytes[0])?, 82 | riscvchip: bytes[0], 83 | chip_id: u32::from_be_bytes(bytes[1..5].try_into().unwrap()), 84 | }) 85 | } 86 | } 87 | // For logging 88 | impl fmt::Display for AttachChipResponse { 89 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 90 | if self.chip_id == 0 { 91 | write!(f, "{:?}", self.chip_family) 92 | } else if let Some(chip_name) = crate::chips::chip_id_to_chip_name(self.chip_id) { 93 | write!( 94 | f, 95 | "{:?} [{}] (ChipID: 0x{:08x})", 96 | self.chip_family, chip_name, self.chip_id 97 | ) 98 | } else { 99 | write!(f, "{:?} (ChipID: 0x{:08x})", self.chip_family, self.chip_id) 100 | } 101 | } 102 | } 103 | 104 | /// Erase code flash, only supported by WCH-LinkE. 105 | #[derive(Debug)] 106 | pub enum EraseCodeFlash { 107 | ByPinRST(RiscvChip), 108 | ByPowerOff(RiscvChip), 109 | } 110 | impl Command for EraseCodeFlash { 111 | type Response = (); 112 | const COMMAND_ID: u8 = 0x0d; 113 | 114 | fn payload(&self) -> Vec { 115 | match self { 116 | // This is more complex, require RST pin to be connected. 117 | EraseCodeFlash::ByPinRST(c) => vec![0x08, *c as u8], 118 | // NOTE: From the protocol, this command's bytes is wrongly seted 119 | // 81 0d 01 0f 09, note here, the "length" bytes is wrong. 120 | // I guess it is not verified. So here we use `02`. 121 | EraseCodeFlash::ByPowerOff(c) => vec![0x0f, *c as u8], 122 | } 123 | } 124 | } 125 | 126 | /// GetROMRAM, Only avaliable for CH32V2, CH32V3, CH56X 127 | /// 0, 1, 2, 3 128 | #[derive(Debug)] 129 | pub struct GetChipRomRamSplit; 130 | impl Command for GetChipRomRamSplit { 131 | type Response = u8; 132 | const COMMAND_ID: u8 = 0x0d; 133 | fn payload(&self) -> Vec { 134 | vec![0x04] 135 | } 136 | } 137 | 138 | /// 0, 1, 2, 3 139 | #[derive(Debug)] 140 | pub struct SetChipRomRamSplit(u8); 141 | impl Command for SetChipRomRamSplit { 142 | type Response = (); 143 | const COMMAND_ID: u8 = 0x0d; 144 | fn payload(&self) -> Vec { 145 | vec![0x05, self.0] 146 | } 147 | } 148 | 149 | // ?? close out 150 | /// Detach Chip, (0x0d, 0xff) 151 | #[derive(Debug)] 152 | pub struct OptEnd; 153 | impl Command for OptEnd { 154 | type Response = (); 155 | const COMMAND_ID: u8 = 0x0d; 156 | fn payload(&self) -> Vec { 157 | vec![0xff] 158 | } 159 | } 160 | 161 | /// Set Power, from pow3v3, pow5v fn 162 | #[derive(clap::Subcommand, PartialEq, Clone, Copy, Debug)] 163 | pub enum SetPower { 164 | /// Enable 3.3V output 165 | Enable3v3, 166 | /// Disable 3.3V output 167 | Disable3v3, 168 | /// Enable 5V output 169 | Enable5v, 170 | /// Disable 5V output 171 | Disable5v, 172 | } 173 | impl Command for SetPower { 174 | type Response = (); 175 | const COMMAND_ID: u8 = 0x0d; 176 | fn payload(&self) -> Vec { 177 | match self { 178 | SetPower::Enable3v3 => vec![0x09], 179 | SetPower::Disable3v3 => vec![0x0A], 180 | SetPower::Enable5v => vec![0x0B], 181 | SetPower::Disable5v => vec![0x0C], 182 | } 183 | } 184 | } 185 | 186 | /// SDI print support, only available for WCH-LinkE 187 | /// Firmware version >= 2.10 188 | #[derive(Debug)] 189 | pub struct SetSdiPrintEnabled(pub bool); 190 | 191 | impl Command for SetSdiPrintEnabled { 192 | // 0x00 success, 0xff not support 193 | type Response = u8; 194 | const COMMAND_ID: u8 = 0x0d; 195 | fn payload(&self) -> Vec { 196 | if self.0 { 197 | vec![0xee, 0x00] 198 | } else { 199 | vec![0xee, 0x01] 200 | } 201 | } 202 | } 203 | 204 | /// Set RST pin 205 | #[derive(Debug)] 206 | pub enum SetRSTPin { 207 | Low, 208 | High, 209 | Floating, 210 | } 211 | impl Command for SetRSTPin { 212 | type Response = (); 213 | const COMMAND_ID: u8 = 0x0d; 214 | fn payload(&self) -> Vec { 215 | let subcmd = match *self { 216 | SetRSTPin::Low => 0x13, 217 | SetRSTPin::High => 0x14, 218 | SetRSTPin::Floating => 0x15, 219 | }; 220 | vec![0x0e, subcmd] 221 | } 222 | } 223 | -------------------------------------------------------------------------------- /src/commands/mod.rs: -------------------------------------------------------------------------------- 1 | //! WCH-Link commands and response types. 2 | 3 | use std::fmt; 4 | use std::fmt::Debug; 5 | 6 | use crate::error::{Error, Result}; 7 | 8 | // 0x0d subset 9 | pub mod control; 10 | 11 | /// Command to call the WCH-Link 12 | pub trait Command: Debug { 13 | type Response: Response; 14 | const COMMAND_ID: u8; 15 | fn payload(&self) -> Vec; 16 | fn to_raw(&self) -> Vec { 17 | let mut bytes = vec![0x81, Self::COMMAND_ID, 0x00]; 18 | bytes.extend(self.payload()); 19 | bytes[2] = bytes.len() as u8 - 3; 20 | bytes 21 | } 22 | } 23 | 24 | /// Response type of a command call 25 | pub trait Response { 26 | /// parse the PAYLOAD part only 27 | fn from_payload(bytes: &[u8]) -> Result 28 | where 29 | Self: Sized; 30 | /// default implementation for parsing [0x82 CMD LEN PAYLOAD] style response 31 | fn from_raw(resp: &[u8]) -> Result 32 | where 33 | Self: Sized, 34 | { 35 | if resp[0] == 0x81 { 36 | let reason = resp[1]; 37 | let len = resp[2] as usize; 38 | if len != resp[3..].len() { 39 | return Err(Error::InvalidPayloadLength); 40 | } 41 | if reason == 0x55 { 42 | return Err(Error::Protocol(reason, resp.to_vec())); 43 | } 44 | Err(Error::Protocol(reason, resp.to_vec())) 45 | } else if resp[0] == 0x82 { 46 | let len = resp[2] as usize; 47 | if len != resp[3..].len() { 48 | return Err(Error::InvalidPayloadLength); 49 | } 50 | let payload = resp[3..3 + len].to_vec(); 51 | Self::from_payload(&payload) 52 | } else { 53 | Err(Error::InvalidPayload) 54 | } 55 | } 56 | } 57 | 58 | impl Response for () { 59 | fn from_payload(_bytes: &[u8]) -> Result { 60 | Ok(()) 61 | } 62 | } 63 | 64 | impl Response for Vec { 65 | fn from_payload(bytes: &[u8]) -> Result { 66 | Ok(bytes.to_vec()) 67 | } 68 | } 69 | 70 | impl Response for u8 { 71 | fn from_payload(bytes: &[u8]) -> Result { 72 | if bytes.len() != 1 { 73 | return Err(Error::InvalidPayloadLength); 74 | } 75 | Ok(bytes[0]) 76 | } 77 | } 78 | 79 | /// Generic raw command 80 | pub struct RawCommand(pub Vec); 81 | impl Command for RawCommand { 82 | type Response = Vec; 83 | const COMMAND_ID: u8 = N; 84 | fn payload(&self) -> Vec { 85 | self.0.clone() 86 | } 87 | } 88 | impl fmt::Debug for RawCommand { 89 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 90 | write!(f, "RawCommand<0x{:02x}>({})", N, hex::encode(&self.0)) 91 | } 92 | } 93 | 94 | /// 0x01 - Set address and offset of the firmware 95 | #[derive(Debug)] 96 | pub struct SetWriteMemoryRegion { 97 | // 0x08000000 or 0x00000000 98 | pub start_addr: u32, 99 | pub len: u32, 100 | } 101 | impl Command for SetWriteMemoryRegion { 102 | type Response = (); 103 | const COMMAND_ID: u8 = 0x01; 104 | fn payload(&self) -> Vec { 105 | let mut bytes = Vec::with_capacity(8); 106 | bytes.extend_from_slice(&self.start_addr.to_be_bytes()); 107 | bytes.extend_from_slice(&self.len.to_be_bytes()); 108 | bytes 109 | } 110 | } 111 | 112 | /// Read a block of memory from the chip. 113 | #[derive(Debug)] 114 | pub struct SetReadMemoryRegion { 115 | pub start_addr: u32, 116 | pub len: u32, 117 | } 118 | impl Command for SetReadMemoryRegion { 119 | type Response = (); 120 | const COMMAND_ID: u8 = 0x03; 121 | fn payload(&self) -> Vec { 122 | let mut bytes = Vec::with_capacity(8); 123 | bytes.extend_from_slice(&self.start_addr.to_be_bytes()); 124 | bytes.extend_from_slice(&self.len.to_be_bytes()); 125 | bytes 126 | } 127 | } 128 | 129 | /// 0x02 - Flash or Memory operations 130 | #[derive(Copy, Clone, Debug, PartialEq, Eq)] 131 | pub enum Program { 132 | // wlink_erase 133 | EraseFlash = 0x01, 134 | // Before write firmware bytes, choice between 0x02 and 0x04 135 | WriteFlash = 0x02, 136 | // Write flash 137 | WriteFlashAndVerify = 0x04, 138 | /// Write Flash OP 139 | WriteFlashOP = 0x05, 140 | // before SetRamAddress 141 | Prepare = 0x06, 142 | /// Unknown, maybe commit flash op written 143 | Unknown07AfterFlashOPWritten = 0x07, // or 0x0B for riscvchip=1 144 | /// Unknown, maybe commit flash op written, only for riscvchip=1 145 | Unknown0BAfterFlashOPWritten = 0x0B, 146 | // EndProgram 147 | End = 0x08, 148 | /// Read memory section 149 | ReadMemory = 0x0c, 150 | } 151 | impl Command for Program { 152 | type Response = u8; 153 | const COMMAND_ID: u8 = 0x02; 154 | fn payload(&self) -> Vec { 155 | vec![*self as u8] 156 | } 157 | } 158 | 159 | /// 0x06 subset 160 | // query -> check -> set 161 | #[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)] 162 | pub enum ConfigChip { 163 | /// 06, _, 01 164 | CheckReadProtect, // 1 for protected, 2 for unprotected 165 | /// 06, _, 02 166 | Unprotect, 167 | /// 06, _, 03 168 | Protect, 169 | /// 06, _, 04 170 | CheckReadProtectEx, // 1 for protected, 0 for unprotected, 171 | /// bf, or e7 172 | UnprotectEx(u8), // with 0xbf, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 173 | // prefix byte 0xe7 ? for ch32x035 174 | ProtectEx(u8), // with 0xbf, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 175 | /// Config flags 176 | /// 81 06 08 02 3f 00 00 ff ff ff ff 177 | /// __ __ __ ?? ?? [DATA] [WRP ] 178 | Config { 179 | /// User data 180 | data: u16, 181 | /// WRP write protection 182 | wrp: u32, 183 | }, 184 | } 185 | impl ConfigChip { 186 | pub const FLAG_READ_PROTECTED: u8 = 0x01; 187 | pub const FLAG_WRITE_PROTECTED: u8 = 0x11; 188 | } 189 | impl Command for ConfigChip { 190 | type Response = u8; 191 | const COMMAND_ID: u8 = 0x06; 192 | fn payload(&self) -> Vec { 193 | match *self { 194 | ConfigChip::CheckReadProtect => vec![0x01], 195 | ConfigChip::Unprotect => vec![0x02], 196 | ConfigChip::Protect => vec![0x03], 197 | // ret = 0x11 protected 198 | ConfigChip::CheckReadProtectEx => vec![0x04], 199 | // b = 0xff ? 200 | ConfigChip::UnprotectEx(b) => vec![0x02, b, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff], 201 | // [0x03, 0xff, 0xff, 0xff, WPR0, WPR1, WPR2, WPR3] 202 | ConfigChip::ProtectEx(b) => vec![0x03, b, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff], 203 | ConfigChip::Config { data: _, wrp: _ } => todo!("ConfigChip: config flags"), 204 | } 205 | } 206 | } 207 | 208 | /// Get Chip UID, the UID is also available in the `wchisp` command. 209 | // ??? 0x11, 0x01, _ (riscvchip) 210 | #[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)] 211 | pub enum GetChipInfo { 212 | V1 = 0x09, 213 | // spot on WCH-LinkUtility v1.70 214 | V2 = 0x06, 215 | } 216 | impl Command for GetChipInfo { 217 | type Response = ESignature; 218 | const COMMAND_ID: u8 = 0x11; 219 | fn payload(&self) -> Vec { 220 | vec![*self as u8] 221 | } 222 | } 223 | 224 | // See-also: https://github.com/ch32-rs/wlink/issues/58 225 | // This does not use standard response format: 226 | // raw response: ffff0020 aeb4abcd 16c6bc45 e339e339 20360510 227 | // UID in wchisp: cd-ab-b4-ae-45-bc-c6-16 228 | // e339e339 => inital value of erased flash 229 | // 20360510 => chip id 230 | /// Flash size and Chip UID, also reported by wchisp 231 | #[derive(Clone, PartialEq, Debug)] 232 | pub struct ESignature { 233 | /// Non-zero-wait flash size in KB 234 | pub flash_size_kb: u16, 235 | /// UID 236 | pub uid: [u32; 2], 237 | } 238 | 239 | impl Response for ESignature { 240 | fn from_payload(_bytes: &[u8]) -> Result 241 | where 242 | Self: Sized, 243 | { 244 | unreachable!("ESignature is not be parsed from payload; qed") 245 | } 246 | 247 | fn from_raw(resp: &[u8]) -> Result { 248 | if resp.len() < 12 { 249 | return Err(Error::InvalidPayloadLength); 250 | } 251 | let flash_size_kb = u16::from_be_bytes(resp[2..4].try_into().unwrap()); 252 | let uid = [ 253 | u32::from_be_bytes(resp[4..8].try_into().unwrap()), 254 | u32::from_be_bytes(resp[8..12].try_into().unwrap()), 255 | ]; 256 | Ok(Self { flash_size_kb, uid }) 257 | } 258 | } 259 | impl fmt::Display for ESignature { 260 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 261 | // write aa-bb-cc-.. style UID 262 | let bytes: [u8; 8] = unsafe { std::mem::transmute(self.uid) }; 263 | write!( 264 | f, 265 | "FlashSize({}KB) UID({})", 266 | self.flash_size_kb, 267 | &bytes 268 | .iter() 269 | .map(|b| format!("{:02x}", b)) 270 | .collect::>() 271 | .join("-") 272 | ) 273 | } 274 | } 275 | 276 | /// Device reset (0x0b, _) 277 | #[derive(Debug)] 278 | pub enum Reset { 279 | /// wlink_quitreset, reset and run 280 | Soft, // the most common reset 281 | Normal, 282 | /// wlink_chip_reset, chip reset 283 | // The memory is not reset 284 | Chip, 285 | } 286 | impl Command for Reset { 287 | type Response = (); 288 | const COMMAND_ID: u8 = 0x0b; 289 | fn payload(&self) -> Vec { 290 | match self { 291 | Reset::Soft => vec![0x01], 292 | Reset::Normal => vec![0x03], 293 | Reset::Chip => vec![0x02], 294 | } 295 | } 296 | } 297 | 298 | /// Speed settings 299 | #[derive(Debug, Copy, Clone, clap::ValueEnum, Eq, PartialEq, Ord, PartialOrd, Hash, Default)] 300 | pub enum Speed { 301 | /// 400kHz 302 | Low = 0x03, 303 | /// 4000kHz 304 | Medium = 0x02, 305 | /// 6000kHz 306 | #[default] 307 | High = 0x01, 308 | } 309 | 310 | /// Set CLK Speed, 0x0C 311 | #[derive(Debug)] 312 | pub struct SetSpeed { 313 | pub riscvchip: u8, 314 | pub speed: Speed, 315 | } 316 | impl Command for SetSpeed { 317 | type Response = bool; 318 | const COMMAND_ID: u8 = 0x0c; 319 | fn payload(&self) -> Vec { 320 | vec![self.riscvchip, self.speed as u8] 321 | } 322 | } 323 | impl Response for bool { 324 | fn from_payload(resp: &[u8]) -> Result { 325 | if resp.len() != 1 { 326 | return Err(Error::InvalidPayloadLength); 327 | } 328 | Ok(resp[0] == 0x01) // 1 means success 329 | } 330 | } 331 | 332 | /// DMI operations 333 | #[derive(Debug)] 334 | pub enum DmiOp { 335 | Nop, 336 | Read { addr: u8 }, 337 | Write { addr: u8, data: u32 }, 338 | } 339 | impl DmiOp { 340 | pub fn read(addr: u8) -> Self { 341 | DmiOp::Read { addr } 342 | } 343 | pub fn write(addr: u8, data: u32) -> Self { 344 | DmiOp::Write { addr, data } 345 | } 346 | } 347 | impl Command for DmiOp { 348 | type Response = DmiOpResponse; 349 | const COMMAND_ID: u8 = 0x08; 350 | fn payload(&self) -> Vec { 351 | const DMI_OP_NOP: u8 = 0; 352 | const DMI_OP_READ: u8 = 1; 353 | const DMI_OP_WRITE: u8 = 2; 354 | 355 | let mut bytes = vec![0x00, 0x00, 0x00, 0x00, 0x00, 0x00]; 356 | match self { 357 | DmiOp::Nop => { 358 | bytes[5] = DMI_OP_NOP; // :) 359 | } 360 | DmiOp::Read { addr } => { 361 | bytes[0] = *addr; 362 | bytes[5] = DMI_OP_READ; 363 | } 364 | DmiOp::Write { addr, data } => { 365 | bytes[0] = *addr; 366 | bytes[5] = DMI_OP_WRITE; 367 | bytes[1..5].copy_from_slice(&data.to_be_bytes()); 368 | } 369 | } 370 | bytes 371 | } 372 | } 373 | 374 | // DMI_STATUS_SUCCESS = 0, 375 | // DMI_STATUS_FAILED = 2, 376 | // DMI_STATUS_BUSY = 3 377 | #[derive(Debug)] 378 | pub struct DmiOpResponse { 379 | pub addr: u8, 380 | pub data: u32, 381 | pub op: u8, 382 | } 383 | impl DmiOpResponse { 384 | pub fn is_busy(&self) -> bool { 385 | self.op == 0x03 386 | } 387 | 388 | pub fn is_success(&self) -> bool { 389 | self.op == 0x00 390 | } 391 | 392 | // should read mcause to get the reason 393 | pub fn is_failed(&self) -> bool { 394 | self.op == 0x03 || self.op == 0x02 395 | } 396 | } 397 | impl Response for DmiOpResponse { 398 | fn from_payload(bytes: &[u8]) -> Result { 399 | if bytes.len() != 6 { 400 | return Err(Error::InvalidPayloadLength); 401 | } 402 | let addr = bytes[0]; 403 | let op = bytes[5]; 404 | let data = u32::from_be_bytes([bytes[1], bytes[2], bytes[3], bytes[4]]); 405 | Ok(DmiOpResponse { addr, data, op }) 406 | } 407 | } 408 | 409 | #[derive(Debug)] 410 | pub struct DisableDebug; 411 | impl Command for DisableDebug { 412 | type Response = (); 413 | const COMMAND_ID: u8 = 0x0e; 414 | // 0x81, 0x0e, 0x01, 0x01 415 | fn payload(&self) -> Vec { 416 | vec![0x01] 417 | } 418 | } 419 | 420 | // 81 0D 05 11 SetAccessAddress 421 | // 81 0F 01 02 GetDeviceMode 422 | // 81 0D 01 07 EnableQE 423 | // 81 0D 01 06 CheckQE 424 | // 81 FE 01 00 DisEncrypt 425 | // 81 0D 01 0F ClearCodeFlashB 426 | // 81 0D 02 08 xx ClearCodeFlash 427 | // 81 11 01 0D unknown in query info, before GetChipRomRamSplit 428 | // 81 0D 02 EE 00/02/03 SetSDLineMode 429 | // 81 0F 01 01 SetIAPMode 430 | -------------------------------------------------------------------------------- /src/dmi.rs: -------------------------------------------------------------------------------- 1 | /// Access MCU using RISC-V DMI 2 | /// 3 | /// Ref: 4 | /// - RISC-V QingKeV2 Microprocessor Debug Manual. 5 | /// - RISC-V Debug Specification 0.13.2 6 | use crate::{ 7 | commands::DmiOp, 8 | error::{AbstractcsCmdErr, Error, Result}, 9 | operations::ProbeSession, 10 | probe::WchLink, 11 | regs::{self, Abstractcs, DMReg, Dmcontrol, Dmstatus}, 12 | }; 13 | use std::{thread, time::Duration}; 14 | 15 | // FPEC, OPTWRE to unlock, 16 | pub const KEY1: u32 = 0x45670123; 17 | pub const KEY2: u32 = 0xCDEF89AB; 18 | 19 | /// RISC-V DMI 20 | pub trait DebugModuleInterface { 21 | fn dmi_nop(&mut self) -> Result<()>; 22 | fn dmi_read(&mut self, reg: u8) -> Result; 23 | fn dmi_write(&mut self, reg: u8, value: u32) -> Result<()>; 24 | 25 | fn read_dmi_reg(&mut self) -> Result 26 | where 27 | R: DMReg, 28 | { 29 | let val = self.dmi_read(R::ADDR)?; 30 | Ok(R::from(val)) 31 | } 32 | 33 | fn write_dmi_reg(&mut self, reg: R) -> Result<()> 34 | where 35 | R: DMReg, 36 | { 37 | self.dmi_write(R::ADDR, reg.into())?; 38 | Ok(()) 39 | } 40 | } 41 | 42 | impl DebugModuleInterface for WchLink { 43 | fn dmi_nop(&mut self) -> Result<()> { 44 | self.send_command(DmiOp::Nop)?; 45 | Ok(()) 46 | } 47 | 48 | fn dmi_read(&mut self, reg: u8) -> Result { 49 | let mut n = 0; 50 | loop { 51 | let resp = self.send_command(DmiOp::read(reg))?; 52 | if resp.op == 0x03 && resp.data == 0xffffffff && resp.addr == 0x7d { 53 | // special code for NotAttached 54 | return Err(Error::NotAttached); 55 | } 56 | if resp.is_success() { 57 | return Ok(resp.data); 58 | } else if n > 100 { 59 | return Err(Error::Timeout); 60 | } else if resp.is_busy() { 61 | log::warn!("dmi_read: busy, retrying"); 62 | thread::sleep(Duration::from_millis(10)); 63 | n += 1; 64 | } else { 65 | return Err(Error::DmiFailed); 66 | } 67 | } 68 | } 69 | 70 | fn dmi_write(&mut self, reg: u8, value: u32) -> Result<()> { 71 | self.send_command(DmiOp::write(reg, value))?; 72 | Ok(()) 73 | } 74 | } 75 | 76 | impl ProbeSession { 77 | fn clear_abstractcs_cmderr(&mut self) -> Result<()> { 78 | let mut abstractcs = Abstractcs::from(0); 79 | abstractcs.set_cmderr(0b111); 80 | self.probe.write_dmi_reg(abstractcs)?; 81 | Ok(()) 82 | } 83 | fn clear_dmstatus_havereset(&mut self) -> Result<()> { 84 | let mut dmcontrol = self.probe.read_dmi_reg::()?; 85 | dmcontrol.set_ackhavereset(true); 86 | self.probe.write_dmi_reg(dmcontrol)?; 87 | Ok(()) 88 | } 89 | 90 | pub fn ensure_mcu_halt(&mut self) -> Result<()> { 91 | let dmstatus = self.probe.read_dmi_reg::()?; 92 | if dmstatus.allhalted() && dmstatus.anyhalted() { 93 | log::trace!("Already halted, nop"); 94 | } else { 95 | loop { 96 | // Initiate a halt request 97 | self.probe.dmi_write(0x10, 0x80000001)?; 98 | let dmstatus = self.probe.read_dmi_reg::()?; 99 | if dmstatus.anyhalted() && dmstatus.allhalted() { 100 | break; 101 | } else { 102 | log::warn!("Not halt, try send"); 103 | thread::sleep(Duration::from_millis(10)); 104 | } 105 | } 106 | } 107 | 108 | // Clear the halt request bit. 109 | self.probe.dmi_write(0x10, 0x00000001)?; 110 | Ok(()) 111 | } 112 | 113 | // SingleLineExitPauseMode 114 | pub fn ensure_mcu_resume(&mut self) -> Result<()> { 115 | self.clear_dmstatus_havereset()?; 116 | let dmstatus = self.probe.read_dmi_reg::()?; 117 | if dmstatus.allrunning() && dmstatus.anyrunning() { 118 | log::debug!("Already running, nop"); 119 | return Ok(()); 120 | } 121 | 122 | self.probe.send_command(DmiOp::write(0x10, 0x80000001))?; 123 | self.probe.send_command(DmiOp::write(0x10, 0x80000001))?; 124 | self.probe.send_command(DmiOp::write(0x10, 0x00000001))?; 125 | // Initiate a resume request 126 | self.probe.send_command(DmiOp::write(0x10, 0x40000001))?; 127 | 128 | let dmstatus = self.probe.read_dmi_reg::()?; 129 | if dmstatus.allresumeack() && dmstatus.anyresumeack() { 130 | log::debug!("Resumed"); 131 | Ok(()) 132 | } else { 133 | log::warn!("Resume fails"); 134 | Ok(()) 135 | } 136 | } 137 | 138 | pub fn reset_debug_module(&mut self) -> Result<()> { 139 | self.probe.dmi_write(0x10, 0x00000000)?; 140 | self.probe.dmi_write(0x10, 0x00000001)?; 141 | 142 | let dmcontrol = self.probe.read_dmi_reg::()?; 143 | 144 | if dmcontrol.dmactive() { 145 | Ok(()) 146 | } else { 147 | Err(Error::DmiFailed) 148 | } 149 | } 150 | 151 | /// Read register value 152 | /// CSR: 0x0000 - 0x0fff 153 | /// GPR: 0x1000 - 0x101f 154 | /// FPR: 0x1020 - 0x103f 155 | // ref: QingKeV2 Microprocessor Debug Manual 156 | pub fn read_reg(&mut self, regno: u16) -> Result { 157 | self.clear_abstractcs_cmderr()?; 158 | 159 | let reg = regno as u32; 160 | self.probe.dmi_write(0x04, 0x00000000)?; // Clear the Data0 register 161 | self.probe.dmi_write(0x17, 0x00220000 | (reg & 0xFFFF))?; 162 | 163 | let abstractcs = self.probe.read_dmi_reg::()?; 164 | if abstractcs.busy() { 165 | return Err(Error::AbstractCommandError(AbstractcsCmdErr::Busy)); // resue busy 166 | } 167 | if abstractcs.cmderr() != 0 { 168 | AbstractcsCmdErr::try_from_cmderr(abstractcs.cmderr() as _)?; 169 | } 170 | 171 | let resp = self.probe.dmi_read(0x04)?; 172 | 173 | Ok(resp) 174 | } 175 | 176 | pub fn write_reg(&mut self, regno: u16, value: u32) -> Result<()> { 177 | // self.ensure_mcu_halt()?; 178 | 179 | let reg = regno as u32; 180 | self.probe.send_command(DmiOp::write(0x04, value))?; 181 | self.probe 182 | .send_command(DmiOp::write(0x17, 0x00230000 | (reg & 0xFFFF)))?; 183 | 184 | let abstractcs = self.probe.read_dmi_reg::()?; 185 | if abstractcs.busy() { 186 | return Err(Error::AbstractCommandError(AbstractcsCmdErr::Busy)); //resue busy 187 | } 188 | if abstractcs.cmderr() != 0 { 189 | AbstractcsCmdErr::try_from_cmderr(abstractcs.cmderr() as _)?; 190 | } 191 | 192 | Ok(()) 193 | } 194 | 195 | pub fn read_mem32(&mut self, addr: u32) -> Result { 196 | self.probe.dmi_write(0x20, 0x0002a303)?; // lw x6,0(x5) 197 | self.probe.dmi_write(0x21, 0x00100073)?; // ebreak 198 | 199 | self.probe.dmi_write(0x04, addr)?; // data0 <- address 200 | self.clear_abstractcs_cmderr()?; 201 | 202 | self.probe.dmi_write(0x17, 0x00271005)?; 203 | 204 | let abstractcs: Abstractcs = self.probe.read_dmi_reg()?; 205 | if abstractcs.busy() { 206 | return Err(Error::AbstractCommandError(AbstractcsCmdErr::Busy)); //resue busy 207 | } 208 | if abstractcs.cmderr() != 0 { 209 | AbstractcsCmdErr::try_from_cmderr(abstractcs.cmderr() as _)?; 210 | } 211 | 212 | self.probe.dmi_write(0x17, 0x00221006)?; // data0 <- x6 213 | if abstractcs.busy() { 214 | return Err(Error::AbstractCommandError(AbstractcsCmdErr::Busy)); //resue busy 215 | } 216 | if abstractcs.cmderr() != 0 { 217 | AbstractcsCmdErr::try_from_cmderr(abstractcs.cmderr() as _)?; 218 | } 219 | 220 | let data0 = self.probe.dmi_read(0x04)?; 221 | Ok(data0) 222 | } 223 | 224 | pub fn write_mem32(&mut self, addr: u32, data: u32) -> Result<()> { 225 | // rasm2 -a riscv -d 23a07200 226 | // sw t2, 0(t0) 227 | self.probe.dmi_write(0x20, 0x0072a023)?; // sw x7,0(x5) 228 | self.probe.dmi_write(0x21, 0x00100073)?; // ebreak 229 | 230 | self.probe.dmi_write(0x04, addr)?; // data0 <- address 231 | 232 | self.clear_abstractcs_cmderr()?; 233 | self.probe.dmi_write(0x17, 0x00231005)?; // x5 <- data0 234 | 235 | let abstractcs: Abstractcs = self.probe.read_dmi_reg()?; 236 | log::trace!("{:?}", abstractcs); 237 | if abstractcs.busy() { 238 | return Err(Error::AbstractCommandError(AbstractcsCmdErr::Busy)); //resue busy 239 | } 240 | if abstractcs.cmderr() != 0 { 241 | AbstractcsCmdErr::try_from_cmderr(abstractcs.cmderr() as _)?; 242 | } 243 | 244 | self.probe.dmi_write(0x04, data)?; // data0 <- data 245 | self.clear_abstractcs_cmderr()?; 246 | 247 | self.probe.dmi_write(0x17, 0x00271007)?; // x7 <- data0 248 | 249 | let abstractcs: Abstractcs = self.probe.read_dmi_reg()?; 250 | log::trace!("{:?}", abstractcs); 251 | if abstractcs.busy() { 252 | return Err(Error::AbstractCommandError(AbstractcsCmdErr::Busy)); //resue busy 253 | } 254 | if abstractcs.cmderr() != 0 { 255 | AbstractcsCmdErr::try_from_cmderr(abstractcs.cmderr() as _)?; 256 | } 257 | Ok(()) 258 | } 259 | 260 | pub fn write_mem8(&mut self, addr: u32, data: u8) -> Result<()> { 261 | self.probe.dmi_write(0x20, 0x00728023)?; // sb x7,0(x5) 262 | self.probe.dmi_write(0x21, 0x00100073)?; // ebreak 263 | 264 | self.probe.dmi_write(0x04, addr)?; // data0 <- address 265 | 266 | self.clear_abstractcs_cmderr()?; 267 | self.probe.dmi_write(0x17, 0x00231005)?; // x5 <- data0 268 | 269 | let abstractcs: Abstractcs = self.probe.read_dmi_reg()?; 270 | log::trace!("{:?}", abstractcs); 271 | if abstractcs.busy() { 272 | return Err(Error::AbstractCommandError(AbstractcsCmdErr::Busy)); //resue busy 273 | } 274 | if abstractcs.cmderr() != 0 { 275 | AbstractcsCmdErr::try_from_cmderr(abstractcs.cmderr() as _)?; 276 | } 277 | 278 | self.probe.dmi_write(0x04, data as u32)?; // data0 <- data 279 | self.clear_abstractcs_cmderr()?; 280 | 281 | self.probe.dmi_write(0x17, 0x00271007)?; // x7 <- data0 282 | 283 | let abstractcs: Abstractcs = self.probe.read_dmi_reg()?; 284 | log::trace!("{:?}", abstractcs); 285 | if abstractcs.busy() { 286 | return Err(Error::AbstractCommandError(AbstractcsCmdErr::Busy)); //resue busy 287 | } 288 | if abstractcs.cmderr() != 0 { 289 | AbstractcsCmdErr::try_from_cmderr(abstractcs.cmderr() as _)?; 290 | } 291 | Ok(()) 292 | } 293 | 294 | pub fn modify_mem32(&mut self, addr: u32, f: F) -> Result<()> 295 | where 296 | F: FnOnce(u32) -> u32, 297 | { 298 | let data = self.read_mem32(addr)?; 299 | let data = f(data); 300 | self.write_mem32(addr, data)?; 301 | Ok(()) 302 | } 303 | 304 | pub fn wait_mem32(&mut self, addr: u32, until: F) -> Result 305 | where 306 | F: Fn(u32) -> bool, 307 | { 308 | loop { 309 | let data = self.read_mem32(addr)?; 310 | if until(data) { 311 | return Ok(data); 312 | } 313 | thread::sleep(Duration::from_millis(1)); 314 | } 315 | } 316 | 317 | // The same as read_memory, but use DMI 318 | pub fn read_memory_by_dmi(&mut self, addr: u32, len: u32) -> Result> { 319 | if len % 4 != 0 { 320 | return Err(Error::Custom("len must be 4 bytes aligned".to_string())); 321 | } 322 | 323 | let mut ret = Vec::with_capacity(len as usize); 324 | for i in 0..len / 4 { 325 | let data = self.read_mem32(addr + i * 4)?; 326 | ret.extend_from_slice(&data.to_le_bytes()); 327 | } 328 | Ok(ret) 329 | } 330 | } 331 | 332 | impl ProbeSession { 333 | pub fn dump_core_csrs(&mut self) -> Result<()> { 334 | let misa = self.read_reg(regs::MISA)?; 335 | log::trace!("Read csr misa: {misa:08x}"); 336 | let misa = parse_misa(misa); 337 | log::info!("RISC-V ISA(misa): {misa:?}"); 338 | 339 | // detect chip's RISC-V core version, QingKe cores 340 | let marchid = self.read_reg(regs::MARCHID)?; 341 | log::trace!("Read csr marchid: {marchid:08x}"); 342 | let core_type = parse_marchid(marchid); 343 | log::info!("RISC-V arch(marchid): {core_type:?}"); 344 | 345 | // mimpid is always "WCH", skip 346 | Ok(()) 347 | } 348 | 349 | pub fn dump_regs(&mut self) -> Result<()> { 350 | let dpc = self.read_reg(regs::DPC)?; 351 | println!("dpc(pc): 0x{dpc:08x}"); 352 | 353 | let gprs = if self.chip_family.is_rv32ec() { 354 | regs::GPRS_RVE 355 | } else { 356 | regs::GPRS_RVI 357 | }; 358 | 359 | for (reg, name, regno) in gprs { 360 | let val = self.read_reg(*regno)?; 361 | println!("{reg:<4}{name:>5}: 0x{val:08x}"); 362 | } 363 | 364 | for (reg, regno) in regs::CSRS { 365 | let val = self.read_reg(*regno)?; 366 | println!("{reg:<9}: 0x{val:08x}"); 367 | } 368 | 369 | Ok(()) 370 | } 371 | 372 | /// Only for Qingke V4 373 | pub fn dump_pmp_csrs(&mut self) -> Result<()> { 374 | for (name, addr) in regs::PMP_CSRS { 375 | let val = self.read_reg(*addr)?; 376 | log::debug!("{}: 0x{:08x}", name, val); 377 | } 378 | 379 | Ok(()) 380 | } 381 | 382 | pub fn dump_dmi(&mut self) -> Result<()> { 383 | log::warn!("The halt status may be incorrect because detaching might resume the MCU"); 384 | 385 | let dmstatus: regs::Dmstatus = self.probe.read_dmi_reg()?; 386 | log::info!("{dmstatus:#x?}"); 387 | let dmcontrol: regs::Dmcontrol = self.probe.read_dmi_reg()?; 388 | log::info!("{dmcontrol:#x?}"); 389 | let hartinfo: regs::Hartinfo = self.probe.read_dmi_reg()?; 390 | log::info!("{hartinfo:#x?}"); 391 | let abstractcs: regs::Abstractcs = self.probe.read_dmi_reg()?; 392 | log::info!("{abstractcs:#x?}"); 393 | let haltsum0 = self.probe.dmi_read(0x40)?; 394 | log::info!("haltsum0: {:#x?}", haltsum0); 395 | 396 | Ok(()) 397 | } 398 | } 399 | 400 | /* 401 | fn lock_flash(&mut self) -> Result<()> { 402 | const FLASH_CTLR: u32 = 0x40022010; 403 | 404 | self.modify_mem32(FLASH_CTLR, |r| r | 0x00008080)?; 405 | Ok(()) 406 | } 407 | 408 | /// unlock FLASH LOCK and FLOCK 409 | fn unlock_flash(&mut self) -> Result<()> { 410 | const FLASH_CTLR: u32 = 0x40022010; 411 | const FLASH_KEYR: u32 = 0x40022004; 412 | const FLASH_MODEKEYR: u32 = 0x40022024; 413 | const KEY1: u32 = 0x45670123; 414 | const KEY2: u32 = 0xCDEF89AB; 415 | 416 | let flash_ctlr = self.read_mem32(FLASH_CTLR)?; 417 | log::debug!("flash_ctlr: 0x{:08x}", flash_ctlr); 418 | // Test LOCK, FLOCK bits 419 | if flash_ctlr & 0x00008080 == 0 { 420 | // already unlocked 421 | return Ok(()); 422 | } 423 | // unlock LOCK 424 | self.write_mem32(FLASH_KEYR, KEY1)?; 425 | self.write_mem32(FLASH_KEYR, KEY2)?; 426 | 427 | // unlock FLOCK 428 | self.write_mem32(FLASH_MODEKEYR, KEY1)?; 429 | self.write_mem32(FLASH_MODEKEYR, KEY2)?; 430 | 431 | let flash_ctlr = self.read_mem32(FLASH_CTLR)?; 432 | log::debug!("flash_ctlr: 0x{:08x}", flash_ctlr); 433 | 434 | Ok(()) 435 | } 436 | 437 | /// Erase by 256 bytes page 438 | /// address must be 256 bytes aligned 439 | pub fn fast_erase(&mut self, address: u32) -> Result<()> { 440 | // require unlock 441 | self.unlock_flash()?; 442 | 443 | const FLASH_STATR: u32 = 0x4002200C; 444 | const BUSY_MASK: u32 = 0x00000001; 445 | const START_MASK: u32 = 1 << 6; 446 | // const EOP_MASK: u32 = 1 << 5; 447 | const WPROTECT_ERR_MASK: u32 = 1 << 4; 448 | 449 | const FLASH_ADDR: u32 = 0x40022014; 450 | const FLASH_CTLR: u32 = 0x40022010; 451 | 452 | const PAGE_ERASE_MASK: u32 = 1 << 17; 453 | 454 | if address & 0xff != 0 { 455 | return Err(Error::Custom( 456 | "address must be 256 bytes aligned".to_string(), 457 | )); 458 | } 459 | 460 | let statr = self.read_mem32(FLASH_STATR)?; 461 | // check if busy 462 | if statr & BUSY_MASK != 0 { 463 | return Err(Error::Custom("flash busy".to_string())); 464 | } 465 | 466 | self.modify_mem32(FLASH_CTLR, |r| r | PAGE_ERASE_MASK)?; 467 | 468 | self.write_mem32(FLASH_ADDR, address)?; 469 | 470 | self.modify_mem32(FLASH_CTLR, |r| r | START_MASK)?; 471 | 472 | loop { 473 | let statr = self.read_mem32(FLASH_STATR)?; 474 | // check if busy 475 | if statr & BUSY_MASK != 0 { 476 | thread::sleep(Duration::from_millis(1)); 477 | } else { 478 | if statr & WPROTECT_ERR_MASK != 0 { 479 | return Err(Error::Custom("flash write protect error".to_string())); 480 | } 481 | self.write_mem32(FLASH_STATR, statr)?; // write 1 to clear EOP 482 | 483 | break; 484 | } 485 | } 486 | // read 1 word to verify 487 | let word = self.read_mem32(address)?; 488 | println!("=> {:08x}", word); 489 | 490 | // end erase, disable page erase 491 | self.modify_mem32(FLASH_CTLR, |r| r & (!PAGE_ERASE_MASK))?; 492 | 493 | self.lock_flash()?; 494 | 495 | Ok(()) 496 | } 497 | 498 | pub fn fast_erase_32k(&mut self, address: u32) -> Result<()> { 499 | // require unlock 500 | self.unlock_flash()?; 501 | 502 | const FLASH_STATR: u32 = 0x4002200C; 503 | const BUSY_MASK: u32 = 0x00000001; 504 | const START_MASK: u32 = 1 << 6; 505 | const WPROTECT_ERR_MASK: u32 = 1 << 4; 506 | 507 | const FLASH_ADDR: u32 = 0x40022014; 508 | const FLASH_CTLR: u32 = 0x40022010; 509 | 510 | const BLOCK_ERASE_32K_MASK: u32 = 1 << 18; 511 | 512 | if address & 0x7fff != 0 { 513 | return Err(Error::Custom( 514 | "address must be 32k bytes aligned".to_string(), 515 | )); 516 | } 517 | 518 | let statr = self.read_mem32(FLASH_STATR)?; 519 | // check if busy 520 | if statr & BUSY_MASK != 0 { 521 | return Err(Error::Custom("flash busy".to_string())); 522 | } 523 | 524 | self.modify_mem32(FLASH_CTLR, |r| r | BLOCK_ERASE_32K_MASK)?; 525 | 526 | self.write_mem32(FLASH_ADDR, address)?; 527 | 528 | self.modify_mem32(FLASH_CTLR, |r| r | START_MASK)?; 529 | 530 | loop { 531 | let statr = self.read_mem32(FLASH_STATR)?; 532 | // check if busy 533 | if statr & BUSY_MASK != 0 { 534 | thread::sleep(Duration::from_millis(1)); 535 | } else { 536 | if statr & WPROTECT_ERR_MASK != 0 { 537 | return Err(Error::Custom("flash write protect error".to_string())); 538 | } 539 | self.write_mem32(FLASH_STATR, statr)?; // write 1 to clear EOP 540 | 541 | break; 542 | } 543 | } 544 | // read 1 word to verify 545 | let word = self.read_mem32(address)?; 546 | println!("=> {:08x}", word); 547 | 548 | // end erase 549 | // disable page erase 550 | self.modify_mem32(FLASH_CTLR, |r| r & (!BLOCK_ERASE_32K_MASK))?; 551 | 552 | self.lock_flash()?; 553 | 554 | Ok(()) 555 | } 556 | 557 | pub fn erase_all(&mut self) -> Result<()> { 558 | const FLASH_STATR: u32 = 0x4002200C; 559 | const BUSY_MASK: u32 = 0x00000001; 560 | 561 | const FLASH_CTLR: u32 = 0x40022010; 562 | const MASS_ERASE_MASK: u32 = 1 << 2; // MER 563 | const START_MASK: u32 = 1 << 6; 564 | 565 | self.unlock_flash()?; 566 | 567 | self.modify_mem32(FLASH_CTLR, |r| r | MASS_ERASE_MASK)?; 568 | 569 | self.modify_mem32(FLASH_CTLR, |r| r | START_MASK)?; 570 | 571 | let statr = self.wait_mem32(FLASH_STATR, |r| r & BUSY_MASK == 0)?; 572 | self.write_mem32(FLASH_STATR, statr)?; // write 1 to clear EOP 573 | 574 | // clear MER 575 | self.modify_mem32(FLASH_CTLR, |r| r & (!MASS_ERASE_MASK))?; 576 | 577 | Ok(()) 578 | } 579 | 580 | /// Program bytes. 581 | /// 582 | /// # Arguments 583 | /// 584 | /// * `address` - The start address of the flash page to program. 585 | /// * `data` - The data to be written to the page. 586 | /// 587 | /// The page must be erased first 588 | pub fn program_page(&mut self, address: u32, data: &[u8]) -> Result<()> { 589 | // require unlock 590 | self.unlock_flash()?; 591 | 592 | const FLASH_STATR: u32 = 0x4002200C; 593 | const BUSY_MASK: u32 = 0x00000001; 594 | const WRITE_BUSY_MASK: u32 = 1 << 1; 595 | const WPROTECT_ERR_MASK: u32 = 1 << 4; 596 | 597 | const FLASH_CTLR: u32 = 0x40022010; 598 | const PAGE_START_MASK: u32 = 1 << 21; // start page program 599 | const PAGE_PROG_MASK: u32 = 1 << 16; // 600 | 601 | if address & 0xff != 0 { 602 | return Err(Error::Custom( 603 | "address must be 256 bytes aligned".to_string(), 604 | )); 605 | } 606 | 607 | // check if busy 608 | let statr = self.read_mem32(FLASH_STATR)?; 609 | if statr & BUSY_MASK != 0 { 610 | return Err(Error::Custom("flash busy".to_string())); 611 | } 612 | 613 | //let ctlr = self.read_mem32(FLASH_CTLR)?; 614 | //let ctlr = ctlr | PAGE_PROG_MASK; 615 | //self.write_mem32(FLASH_CTLR, ctlr)?; 616 | self.modify_mem32(FLASH_CTLR, |r| r | PAGE_PROG_MASK)?; 617 | 618 | for (i, word) in data.chunks(4).enumerate() { 619 | let word = u32::from_le_bytes(word.try_into().unwrap()); 620 | self.write_mem32(address + (i as u32 * 4), word)?; 621 | 622 | // write busy wait 623 | self.wait_mem32(FLASH_STATR, |r| r & WRITE_BUSY_MASK == 0)?; 624 | } 625 | 626 | // start fast page program 627 | self.modify_mem32(FLASH_CTLR, |r| r | PAGE_START_MASK)?; 628 | 629 | // busy wait 630 | let statr = self.wait_mem32(FLASH_STATR, |r| r & BUSY_MASK == 0)?; 631 | 632 | self.write_mem32(FLASH_STATR, statr)?; // write 1 to clear EOP 633 | if statr & WPROTECT_ERR_MASK != 0 { 634 | return Err(Error::Custom("flash write protect error".to_string())); 635 | } 636 | 637 | // verify 638 | // read 1 word to verify 639 | //let word = self.read_mem32(address)?; 640 | //println!("=> {:08x}", word); 641 | 642 | // end program, clear PAGE_PROG 643 | //let ctlr = self.read_mem32(FLASH_CTLR)?; 644 | //let ctlr = ctlr & (!PAGE_PROG_MASK); // disable page erase 645 | //self.write_mem32(FLASH_CTLR, ctlr)?; 646 | self.modify_mem32(FLASH_CTLR, |r| r & (!PAGE_PROG_MASK))?; 647 | 648 | self.lock_flash()?; 649 | 650 | Ok(()) 651 | } 652 | } 653 | 654 | */ 655 | 656 | // marchid => dc68d882 657 | // Parsed marchid: WCH-V4B 658 | // Ref: QingKe V4 Manual 659 | fn parse_marchid(marchid: u32) -> Option { 660 | if marchid == 0 { 661 | None 662 | } else { 663 | Some(format!( 664 | "{}{}{}-{}{}{}", 665 | (((marchid >> 26) & 0x1F) + 64) as u8 as char, 666 | (((marchid >> 21) & 0x1F) + 64) as u8 as char, 667 | (((marchid >> 16) & 0x1F) + 64) as u8 as char, 668 | (((marchid >> 10) & 0x1F) + 64) as u8 as char, 669 | ((((marchid >> 5) & 0x1F) as u8) + b'0') as char, 670 | ((marchid & 0x1F) + 64) as u8 as char, 671 | )) 672 | } 673 | } 674 | 675 | fn parse_misa(misa: u32) -> Option { 676 | let mut s = String::new(); 677 | let mxl = (misa >> 30) & 0x3; 678 | s.push_str(match mxl { 679 | 1 => "RV32", 680 | 2 => "RV64", 681 | 3 => "RV128", 682 | _ => return None, 683 | }); 684 | for i in 0..26 { 685 | if (misa >> i) & 1 == 1 { 686 | s.push((b'A' + i as u8) as char); 687 | } 688 | } 689 | Some(s) 690 | } 691 | -------------------------------------------------------------------------------- /src/error.rs: -------------------------------------------------------------------------------- 1 | use thiserror::Error; 2 | 3 | use crate::RiscvChip; 4 | 5 | /// Alias for a `Result` with the error type `wlink::Error`. 6 | pub type Result = std::result::Result; 7 | 8 | #[derive(Error, Debug)] 9 | pub enum Error { 10 | #[error("{0}")] 11 | Custom(String), 12 | #[error("USB error: {0}")] 13 | Rusb(#[from] rusb::Error), 14 | #[error("WCH-Link not found, please check your connection")] 15 | ProbeNotFound, 16 | #[error("WCH-Link is connected, but is not in RV mode")] 17 | ProbeModeNotSupported, 18 | #[error("WCH-Link doesn't support current chip: {0:?}")] 19 | UnsupportedChip(RiscvChip), 20 | #[error("Unknown WCH-Link variant: {0}")] 21 | UnknownLinkVariant(u8), 22 | #[error("Unknown RISC-V Chip: 0x{0:02x}")] 23 | UnknownChip(u8), 24 | #[error("Probe is not attached to an MCU, or debug is not enabled. (hint: use wchisp to enable debug)")] 25 | NotAttached, 26 | #[error("Chip mismatch: expected {0:?}, got {1:?}")] 27 | ChipMismatch(RiscvChip, RiscvChip), 28 | #[error("WCH-Link underlying protocol error: {0:#04x} {1:#04x?}")] 29 | Protocol(u8, Vec), 30 | #[error("Invalid payload length")] 31 | InvalidPayloadLength, 32 | #[error("Invalid payload")] 33 | InvalidPayload, 34 | #[error("DM Abstract comand error: {0:?}")] 35 | AbstractCommandError(AbstractcsCmdErr), 36 | #[error("DM is busy")] 37 | Busy, 38 | #[error("DMI Status Failed")] 39 | DmiFailed, 40 | #[error("Operation timeout")] 41 | Timeout, 42 | #[error("Serial port error: {0}")] 43 | Serial(#[from] serialport::Error), 44 | #[error("Io error: {0}")] 45 | Io(#[from] std::io::Error), 46 | #[error("Driver error")] 47 | Driver, 48 | } 49 | 50 | #[derive(Debug, Clone, Copy)] 51 | pub enum AbstractcsCmdErr { 52 | /// Write to the command, abstractcs and abstractauto registers, or read/write to the data 53 | /// and progbuf registers when the abstract command is executed. 54 | Busy = 1, 55 | /// The current abstract command is not supported 56 | NotSupported = 2, 57 | /// error occurs when the abstract command is executed. 58 | Exception = 3, 59 | /// the hart wasn’t in the required state (running/halted), or unavailable 60 | HaltOrResume = 4, 61 | /// bus error (e.g. alignment, access size, or timeout) 62 | Bus = 5, 63 | /// Parity bit error during communication (WCH's extension) 64 | Parity = 6, 65 | /// The command failed for another reason. 66 | Other = 7, 67 | } 68 | 69 | impl AbstractcsCmdErr { 70 | pub(crate) fn try_from_cmderr(value: u8) -> Result<()> { 71 | match value { 72 | 0 => Ok(()), 73 | 1 => Err(Error::AbstractCommandError(AbstractcsCmdErr::Busy)), 74 | 2 => Err(Error::AbstractCommandError(AbstractcsCmdErr::NotSupported)), 75 | 3 => Err(Error::AbstractCommandError(AbstractcsCmdErr::Exception)), 76 | 4 => Err(Error::AbstractCommandError(AbstractcsCmdErr::HaltOrResume)), 77 | 5 => Err(Error::AbstractCommandError(AbstractcsCmdErr::Bus)), 78 | 6 => Err(Error::AbstractCommandError(AbstractcsCmdErr::Parity)), 79 | 7 => Err(Error::AbstractCommandError(AbstractcsCmdErr::Other)), 80 | 81 | _ => unreachable!(), 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/firmware.rs: -------------------------------------------------------------------------------- 1 | //! Firmware file formats 2 | use std::path::Path; 3 | use std::str; 4 | 5 | use anyhow::Result; 6 | use object::{ 7 | elf::FileHeader32, elf::PT_LOAD, read::elf::FileHeader, read::elf::ProgramHeader, Endianness, 8 | Object, ObjectSection, 9 | }; 10 | 11 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] 12 | pub enum FirmwareFormat { 13 | PlainHex, 14 | IntelHex, 15 | ELF, 16 | Binary, 17 | } 18 | 19 | #[derive(Debug, Clone)] 20 | pub struct Section { 21 | /// The start address of the segment, physical address. 22 | pub address: u32, 23 | pub data: Vec, 24 | } 25 | 26 | impl Section { 27 | pub fn end_address(&self) -> u32 { 28 | self.address + self.data.len() as u32 29 | } 30 | } 31 | 32 | /// The abstract representation of a firmware image. 33 | #[derive(Debug, Clone)] 34 | pub enum Firmware { 35 | /// A single section, with address undefined. 36 | Binary(Vec), 37 | /// Multiple sections, with different addresses. 38 | Sections(Vec
), 39 | } 40 | 41 | impl Firmware { 42 | /// Merge sections, and fill gap with 0xff 43 | pub fn merge_sections(self) -> Result { 44 | let Firmware::Sections(mut sections) = self else { 45 | return Ok(self); 46 | }; 47 | sections.sort_by_key(|s| s.address); 48 | let mut merged = vec![]; 49 | 50 | let mut it = sections.drain(0..); 51 | let mut last = it 52 | .next() 53 | .expect("firmware must has at least one section; qed"); 54 | 55 | for sect in it { 56 | if let Some(gap) = sect.address.checked_sub(last.end_address()) { 57 | if gap > 0 { 58 | log::debug!("Merge firmware sections with gap: {}", gap); 59 | } 60 | last.data.resize(last.data.len() + gap as usize, 0xff); // fill gap with 0xff 61 | last.data.extend_from_slice(§.data); 62 | } else { 63 | return Err(anyhow::format_err!( 64 | "section address overflow: {:#010x} + {:#x}", 65 | last.address, 66 | last.data.len() 67 | )); 68 | } 69 | } 70 | merged.push(last); 71 | Ok(Firmware::Sections(merged)) 72 | } 73 | } 74 | 75 | pub fn read_firmware_from_file>(path: P) -> Result { 76 | let p = path.as_ref(); 77 | let raw = std::fs::read(p)?; 78 | 79 | let format = guess_format(p, &raw); 80 | log::info!("Read {} as {:?} format", p.display(), format); 81 | match format { 82 | FirmwareFormat::PlainHex => { 83 | let raw = hex::decode( 84 | raw.into_iter() 85 | .filter(|&c| c != b'\r' || c != b'\n') 86 | .collect::>(), 87 | )?; 88 | Ok(Firmware::Binary(raw)) 89 | } 90 | FirmwareFormat::Binary => Ok(Firmware::Binary(raw)), 91 | FirmwareFormat::IntelHex => { 92 | read_ihex(str::from_utf8(&raw)?).and_then(|f| f.merge_sections()) 93 | } 94 | FirmwareFormat::ELF => read_elf(&raw).and_then(|f| f.merge_sections()), 95 | } 96 | } 97 | 98 | fn guess_format(path: &Path, raw: &[u8]) -> FirmwareFormat { 99 | let ext = path 100 | .extension() 101 | .map(|s| s.to_string_lossy()) 102 | .unwrap_or_default() 103 | .to_lowercase(); 104 | if ["ihex", "ihe", "h86", "hex", "a43", "a90"].contains(&&*ext) { 105 | return FirmwareFormat::IntelHex; 106 | } 107 | 108 | // FIXME: is this 4-byte possible to be some kind of assembly binary? 109 | if raw.starts_with(&[0x7f, b'E', b'L', b'F']) { 110 | FirmwareFormat::ELF 111 | } else if raw[0] == b':' 112 | && raw 113 | .iter() 114 | .all(|&c| (c as char).is_ascii_hexdigit() || c == b':' || c == b'\n' || c == b'\r') 115 | { 116 | FirmwareFormat::IntelHex 117 | } else if raw 118 | .iter() 119 | .all(|&c| (c as char).is_ascii_hexdigit() || c == b'\n' || c == b'\r') 120 | { 121 | FirmwareFormat::PlainHex 122 | } else { 123 | FirmwareFormat::Binary 124 | } 125 | } 126 | 127 | pub fn read_hex(data: &str) -> Result> { 128 | Ok(hex::decode(data)?) 129 | } 130 | 131 | pub fn read_ihex(data: &str) -> Result { 132 | use ihex::Record::*; 133 | 134 | let mut base_address = 0; 135 | 136 | let mut segs: Vec
= vec![]; 137 | let mut last_end_address = 0; 138 | for record in ihex::Reader::new(data) { 139 | let record = record?; 140 | match record { 141 | Data { offset, value } => { 142 | let start_address = base_address + offset as u32; 143 | 144 | if let Some(last) = segs.last_mut() { 145 | if start_address == last_end_address { 146 | // merge to last 147 | last_end_address = start_address + value.len() as u32; 148 | last.data.extend_from_slice(&value); 149 | 150 | continue; 151 | } 152 | } 153 | 154 | last_end_address = start_address + value.len() as u32; 155 | segs.push(Section { 156 | address: start_address, 157 | data: value.to_vec(), 158 | }) 159 | } 160 | ExtendedSegmentAddress(address) => { 161 | base_address = (address as u32) * 16; 162 | } 163 | ExtendedLinearAddress(address) => { 164 | base_address = (address as u32) << 16; 165 | } 166 | StartSegmentAddress { .. } => (), 167 | StartLinearAddress(_) => (), 168 | EndOfFile => (), 169 | }; 170 | } 171 | 172 | Ok(Firmware::Sections(segs)) 173 | } 174 | 175 | /// Simulates `objcopy -O binary`, returns loadable sections 176 | pub fn read_elf(elf_data: &[u8]) -> Result { 177 | let file_kind = object::FileKind::parse(elf_data)?; 178 | 179 | match file_kind { 180 | object::FileKind::Elf32 => (), 181 | _ => anyhow::bail!("cannot read file as ELF32 format"), 182 | } 183 | let elf_header = FileHeader32::::parse(elf_data)?; 184 | let binary = object::read::elf::ElfFile::>::parse(elf_data)?; 185 | 186 | let mut sections = vec![]; 187 | 188 | let endian = elf_header.endian()?; 189 | 190 | // Ref: https://docs.oracle.com/cd/E19683-01/816-1386/chapter6-83432/index.html 191 | for segment in elf_header.program_headers(elf_header.endian()?, elf_data)? { 192 | // Get the physical address of the segment. The data will be programmed to that location. 193 | let p_paddr: u64 = segment.p_paddr(endian).into(); 194 | // Virtual address 195 | let p_vaddr: u64 = segment.p_vaddr(endian).into(); 196 | 197 | let flags = segment.p_flags(endian); 198 | 199 | // The number of bytes in the file image of the segment, which can be zero. 200 | if segment.p_filesz(endian) == 0 { 201 | // skip empty segment 202 | continue; 203 | } 204 | let segment_data = segment 205 | .data(endian, elf_data) 206 | .map_err(|_| anyhow::format_err!("Failed to access data for an ELF segment."))?; 207 | if !segment_data.is_empty() && segment.p_type(endian) == PT_LOAD { 208 | log::debug!( 209 | "Found loadable segment, physical address: {:#010x}, virtual address: {:#010x}, flags: {:#x}", 210 | p_paddr, 211 | p_vaddr, 212 | flags 213 | ); 214 | let (segment_offset, segment_filesize) = segment.file_range(endian); 215 | let mut section_names = vec![]; 216 | for section in binary.sections() { 217 | let (section_offset, section_filesize) = match section.file_range() { 218 | Some(range) => range, 219 | None => continue, 220 | }; 221 | if section_filesize == 0 { 222 | continue; 223 | } 224 | 225 | // contains range 226 | if segment_offset <= section_offset 227 | && segment_offset + segment_filesize >= section_offset + section_filesize 228 | { 229 | log::debug!( 230 | "Matching section: {:?} offset: 0x{:x} size: 0x{:x}", 231 | section.name()?, 232 | section_offset, 233 | section_filesize 234 | ); 235 | for (offset, relocation) in section.relocations() { 236 | log::debug!("Relocation: offset={}, relocation={:?}", offset, relocation); 237 | } 238 | section_names.push(section.name()?.to_owned()); 239 | } 240 | } 241 | let section_data = &elf_data[segment_offset as usize..][..segment_filesize as usize]; 242 | sections.push(Section { 243 | address: p_paddr as u32, 244 | data: section_data.to_vec(), 245 | }); 246 | log::debug!("Section names: {:?}", section_names); 247 | } 248 | } 249 | 250 | if sections.is_empty() { 251 | anyhow::bail!("empty ELF file"); 252 | } 253 | log::debug!("found {} sections", sections.len()); 254 | // merge_sections(sections) 255 | Ok(Firmware::Sections(sections)) 256 | } 257 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! The wlink library. 2 | 3 | pub mod chips; 4 | pub mod commands; 5 | pub mod dmi; 6 | pub mod error; 7 | pub mod firmware; 8 | pub mod flash_op; 9 | pub mod operations; 10 | pub mod probe; 11 | pub mod regs; 12 | pub mod usb_device; 13 | 14 | use clap::{builder::PossibleValue, ValueEnum}; 15 | use probe::WchLink; 16 | 17 | pub use crate::error::{Error, Result}; 18 | 19 | /// Currently supported RISC-V chip series/family 20 | #[derive(Clone, Copy, Debug, PartialEq, Eq)] 21 | #[repr(u8)] 22 | pub enum RiscvChip { 23 | /// CH32V103 RISC-V3A series 24 | CH32V103 = 0x01, 25 | /// CH571/CH573 RISC-V3A BLE 4.2 series 26 | CH57X = 0x02, 27 | /// CH565/CH569 RISC-V3A series 28 | CH56X = 0x03, 29 | /// CH32V20X RISC-V4B/V4C series 30 | CH32V20X = 0x05, 31 | /// CH32V30X RISC-V4C/V4F series, The same as type 5 32 | CH32V30X = 0x06, 33 | /// CH583/CH582/CH581 RISC-V4A BLE 5.3 series 34 | // Use CH582 here as it's the most common one 35 | CH582 = 0x07, 36 | /// CH32V003 RISC-V2A series 37 | CH32V003 = 0x09, 38 | /// RISC-V EC controller, undocumented. 39 | /// The only reference I can find is . 40 | CH8571 = 0x0A, // 10, 41 | /// CH59x RISC-V4C BLE 5.4 series, fallback as CH58X 42 | /// FIXME: CH585 also reported as this 43 | CH59X = 0x0B, // 11 44 | /// CH643 RISC-V4C series, RGB Display Driver MCU 45 | CH643 = 0x0C, // 12 46 | /// CH32X035 RISC-V4C USB-PD series, fallback as CH643 47 | CH32X035 = 0x0D, // 13 48 | /// CH32L103 RISC-V4C low power series, USB-PD 49 | CH32L103 = 0x0E, // 14 50 | /// CH641 RISC-V2A series, USB-PD, fallback as CH32V003 51 | CH641 = 0x49, 52 | // Added at 0.1.0 53 | /// CH585/CH584 RISC-V3C series, BLE 5.4, NFC, USB HS, fallback as CH582 54 | CH585 = 0x4B, 55 | /// CH564 RISC-V4J series 56 | CH564 = 0x0F, 57 | /// CH32V002/4/5/6/7, CH32M007 58 | CH32V007 = 0x4E, 59 | /// CH645, CH653, RISC-V4C 60 | CH645 = 0x46, 61 | /// CH32V317 RISC-V4 series 62 | CH32V317 = 0x86, 63 | // Cortex-M chips 64 | CH32F10X = 0x04, 65 | CH32F20X = 0x08, 66 | } 67 | 68 | impl ValueEnum for RiscvChip { 69 | fn value_variants<'a>() -> &'a [Self] { 70 | &[ 71 | RiscvChip::CH32V103, 72 | RiscvChip::CH57X, 73 | RiscvChip::CH56X, 74 | RiscvChip::CH32V20X, 75 | RiscvChip::CH32V30X, 76 | RiscvChip::CH582, 77 | RiscvChip::CH32V003, 78 | RiscvChip::CH8571, 79 | RiscvChip::CH59X, 80 | RiscvChip::CH643, 81 | RiscvChip::CH32X035, 82 | RiscvChip::CH32L103, 83 | RiscvChip::CH641, 84 | RiscvChip::CH585, 85 | RiscvChip::CH564, 86 | RiscvChip::CH32V007, 87 | RiscvChip::CH645, 88 | RiscvChip::CH32V317, 89 | ] 90 | } 91 | 92 | fn to_possible_value(&self) -> Option { 93 | match self { 94 | RiscvChip::CH32V103 => Some(PossibleValue::new("CH32V103")), 95 | RiscvChip::CH57X => Some(PossibleValue::new("CH57X")), 96 | RiscvChip::CH56X => Some(PossibleValue::new("CH56X")), 97 | RiscvChip::CH32V20X => Some(PossibleValue::new("CH32V20X")), 98 | RiscvChip::CH32V30X => Some(PossibleValue::new("CH32V30X")), 99 | RiscvChip::CH582 => Some(PossibleValue::new("CH582")), 100 | RiscvChip::CH585 => Some(PossibleValue::new("CH585")), 101 | RiscvChip::CH32V003 => Some(PossibleValue::new("CH32V003")), 102 | RiscvChip::CH8571 => Some(PossibleValue::new("CH8571")), 103 | RiscvChip::CH59X => Some(PossibleValue::new("CH59X")), 104 | RiscvChip::CH643 => Some(PossibleValue::new("CH643")), 105 | RiscvChip::CH32X035 => Some(PossibleValue::new("CH32X035")), 106 | RiscvChip::CH32L103 => Some(PossibleValue::new("CH32L103")), 107 | RiscvChip::CH641 => Some(PossibleValue::new("CH641")), 108 | RiscvChip::CH564 => Some(PossibleValue::new("CH564")), 109 | RiscvChip::CH32V007 => Some(PossibleValue::new("CH32V007")), 110 | RiscvChip::CH645 => Some(PossibleValue::new("CH645")), 111 | RiscvChip::CH32V317 => Some(PossibleValue::new("CH32V317")), 112 | _ => None, 113 | } 114 | } 115 | 116 | fn from_str(input: &str, ignore_case: bool) -> std::result::Result { 117 | let s = if ignore_case { 118 | input.to_ascii_uppercase() 119 | } else { 120 | input.to_string() 121 | }; 122 | match &*s { 123 | "CH32V103" => Ok(RiscvChip::CH32V103), 124 | "CH32V20X" | "CH32V203" | "CH32V208" => Ok(RiscvChip::CH32V20X), 125 | "CH32V30X" | "CH32V303" | "CH32V305" | "CH32V307" => Ok(RiscvChip::CH32V30X), 126 | "CH32V317" => Ok(RiscvChip::CH32V317), 127 | "CH32V003" => Ok(RiscvChip::CH32V003), 128 | "CH32L103" => Ok(RiscvChip::CH32L103), 129 | // Note that CH32X034 seems never released 130 | "CH32X0" | "CH32X03X" | "CH32X033" | "CH32X034" | "CH32X035" => Ok(RiscvChip::CH32X035), 131 | "CH32V002" | "CH32V004" | "CH32V005" | "CH32V006" | "CH32V007" | "CH32M007" => { 132 | Ok(RiscvChip::CH32V007) 133 | } 134 | "CH565" | "CH569" => Ok(RiscvChip::CH56X), 135 | "CH57X" | "CH571" | "CH573" => Ok(RiscvChip::CH57X), 136 | "CH581" | "CH582" | "CH583" => Ok(RiscvChip::CH582), 137 | "CH584" | "CH585" => Ok(RiscvChip::CH585), 138 | "CH564" => Ok(RiscvChip::CH564), 139 | "CH59X" | "CH591" | "CH592" => Ok(RiscvChip::CH59X), 140 | "CH641" => Ok(RiscvChip::CH641), 141 | "CH643" => Ok(RiscvChip::CH643), 142 | "CH645" | "CH653" => Ok(RiscvChip::CH645), 143 | "CH8571" => Ok(RiscvChip::CH8571), 144 | "CH56X" => { 145 | log::warn!( 146 | "Ambiguous chip family, assume CH569. use either CH564, CH565 or CH569 instead" 147 | ); 148 | Ok(RiscvChip::CH56X) 149 | } 150 | "CH58X" => { 151 | log::warn!( 152 | "Ambiguous chip family, assume CH582. use either CH582 or CH585 instead" 153 | ); 154 | Ok(RiscvChip::CH582) 155 | } 156 | _ => Err(format!("Unknown chip: {}", s)), 157 | } 158 | } 159 | } 160 | 161 | impl RiscvChip { 162 | /// Support flash protect commands, and info query commands 163 | pub fn support_flash_protect(&self) -> bool { 164 | // 1, 6, 5, 9, 0x49, 0x46, 0x86 165 | matches!( 166 | self, 167 | RiscvChip::CH32V103 168 | | RiscvChip::CH32V20X 169 | | RiscvChip::CH32V30X 170 | | RiscvChip::CH32V003 171 | | RiscvChip::CH32V007 172 | | RiscvChip::CH32L103 173 | | RiscvChip::CH32X035 174 | | RiscvChip::CH641 175 | | RiscvChip::CH645 176 | | RiscvChip::CH32V317 177 | ) 178 | } 179 | 180 | // CH32V208xB, CH32V307, CH32V303RCT6/VCT6 181 | pub(crate) fn support_ram_rom_mode(&self) -> bool { 182 | matches!( 183 | self, 184 | RiscvChip::CH32V20X | RiscvChip::CH32V30X | RiscvChip::CH32V317 185 | ) 186 | } 187 | 188 | /// Support config registers, query info(UID, etc.) 189 | pub fn support_query_info(&self) -> bool { 190 | !matches!( 191 | self, 192 | RiscvChip::CH57X 193 | | RiscvChip::CH56X 194 | | RiscvChip::CH582 195 | | RiscvChip::CH585 196 | | RiscvChip::CH59X 197 | ) 198 | } 199 | 200 | /// Very unsafe. 201 | /// This disables the debug interface of the chip. 202 | /// Command sequence is 810e0101 203 | pub fn support_disable_debug(&self) -> bool { 204 | matches!( 205 | self, 206 | RiscvChip::CH57X 207 | | RiscvChip::CH56X 208 | | RiscvChip::CH582 209 | | RiscvChip::CH585 210 | | RiscvChip::CH59X 211 | ) 212 | } 213 | 214 | /// Erase code flash by RST pin or power-off 215 | pub fn support_special_erase(&self) -> bool { 216 | !matches!( 217 | self, 218 | RiscvChip::CH57X 219 | | RiscvChip::CH56X 220 | | RiscvChip::CH582 221 | | RiscvChip::CH585 222 | | RiscvChip::CH59X 223 | ) 224 | } 225 | 226 | pub fn support_sdi_print(&self) -> bool { 227 | // CH641, CH643, CH32V00x, CH32V103, CH32V20x, CH32V30x, CH32X035, CH32L103 228 | matches!( 229 | self, 230 | RiscvChip::CH32V003 231 | | RiscvChip::CH645 232 | | RiscvChip::CH32V007 233 | | RiscvChip::CH32V103 234 | | RiscvChip::CH32V20X 235 | | RiscvChip::CH32V30X 236 | | RiscvChip::CH32X035 237 | | RiscvChip::CH32L103 238 | | RiscvChip::CH643 239 | | RiscvChip::CH641 240 | | RiscvChip::CH32V317 241 | ) 242 | } 243 | 244 | pub fn is_rv32ec(&self) -> bool { 245 | matches!( 246 | self, 247 | RiscvChip::CH32V003 | RiscvChip::CH641 | RiscvChip::CH32V007 248 | ) 249 | } 250 | 251 | pub fn reset_command(&self) -> crate::commands::Reset { 252 | match self { 253 | RiscvChip::CH57X | RiscvChip::CH582 | RiscvChip::CH59X => crate::commands::Reset::Chip, 254 | _ => crate::commands::Reset::Normal, 255 | } 256 | } 257 | 258 | /// Device-specific post init logic 259 | pub fn do_post_init(&self, probe: &mut WchLink) -> Result<()> { 260 | match self { 261 | RiscvChip::CH32V103 => { 262 | // 81 0d 01 03 263 | // 81 0d 01 10 264 | let _ = probe.send_command(commands::RawCommand::<0x0d>(vec![0x03]))?; 265 | // let _ = probe.send_command(commands::RawCommand::<0x0d>(vec![0x10]))?; 266 | } 267 | RiscvChip::CH32V30X | RiscvChip::CH8571 | RiscvChip::CH32V003 => { 268 | // 81 0d 01 03 269 | // let _ = probe.send_command(commands::RawCommand::<0x0d>(vec![0x03]))?; 270 | } 271 | RiscvChip::CH57X | RiscvChip::CH582 => { 272 | log::warn!("The debug interface has been opened, there is a risk of code leakage."); 273 | log::warn!("Please ensure that the debug interface has been closed before leaving factory!"); 274 | } 275 | RiscvChip::CH56X => { 276 | log::warn!("The debug interface has been opened, there is a risk of code leakage."); 277 | log::warn!("Please ensure that the debug interface has been closed before leaving factory!"); 278 | // 81 0d 01 04 279 | // should test return value 280 | let resp = probe.send_command(commands::RawCommand::<0x0d>(vec![0x04]))?; 281 | log::debug!("TODO, handle CH56X resp {:?}", resp); 282 | } 283 | _ => (), 284 | } 285 | Ok(()) 286 | } 287 | 288 | // TODO: CH32V003 has two flash_op for different flash start address 289 | fn get_flash_op(&self) -> &'static [u8] { 290 | match self { 291 | RiscvChip::CH32V003 | RiscvChip::CH641 => &flash_op::CH32V003, 292 | RiscvChip::CH32V103 => &flash_op::CH32V103, 293 | RiscvChip::CH32V20X | RiscvChip::CH32V30X => &flash_op::CH32V307, 294 | RiscvChip::CH56X => &flash_op::CH569, 295 | RiscvChip::CH57X => &flash_op::CH573, 296 | RiscvChip::CH582 | RiscvChip::CH59X | RiscvChip::CH585 => &flash_op::CH583, 297 | RiscvChip::CH8571 => &flash_op::OP8571, 298 | RiscvChip::CH32X035 | RiscvChip::CH643 => &flash_op::CH643, 299 | RiscvChip::CH32L103 => &flash_op::CH32L103, 300 | RiscvChip::CH564 => &flash_op::CH564, 301 | RiscvChip::CH32V007 => &flash_op::CH32V007, 302 | RiscvChip::CH645 => &flash_op::CH645, 303 | RiscvChip::CH32V317 => &flash_op::CH32V317, 304 | RiscvChip::CH32F10X => todo!(), 305 | RiscvChip::CH32F20X => todo!(), 306 | } 307 | } 308 | fn try_from_u8(value: u8) -> Result { 309 | match value { 310 | 0x01 => Ok(RiscvChip::CH32V103), 311 | 0x02 => Ok(RiscvChip::CH57X), 312 | 0x03 => Ok(RiscvChip::CH56X), 313 | 0x05 => Ok(RiscvChip::CH32V20X), 314 | 0x06 => Ok(RiscvChip::CH32V30X), 315 | 0x07 => Ok(RiscvChip::CH582), 316 | 0x09 => Ok(RiscvChip::CH32V003), 317 | 0x0A => Ok(RiscvChip::CH8571), 318 | 0x0B => Ok(RiscvChip::CH59X), 319 | 0x0C => Ok(RiscvChip::CH643), 320 | 0x0D => Ok(RiscvChip::CH32X035), 321 | 0x0E => Ok(RiscvChip::CH32L103), 322 | 0x49 => Ok(RiscvChip::CH641), 323 | 0x4B => Ok(RiscvChip::CH585), 324 | 0x0F => Ok(RiscvChip::CH564), 325 | 0x4E => Ok(RiscvChip::CH32V007), 326 | 0x46 => Ok(RiscvChip::CH645), 327 | 0x86 => Ok(RiscvChip::CH32V317), 328 | 0x04 => Ok(RiscvChip::CH32F10X), 329 | 0x08 => Ok(RiscvChip::CH32F20X), 330 | _ => Err(Error::UnknownChip(value)), 331 | } 332 | } 333 | 334 | /// Packet data length of data endpoint 335 | pub fn data_packet_size(&self) -> usize { 336 | match self { 337 | RiscvChip::CH32V103 => 128, 338 | RiscvChip::CH32V003 | RiscvChip::CH641 => 64, 339 | _ => 256, 340 | } 341 | } 342 | 343 | pub fn code_flash_start(&self) -> u32 { 344 | match self { 345 | RiscvChip::CH56X 346 | | RiscvChip::CH57X 347 | | RiscvChip::CH582 348 | | RiscvChip::CH585 349 | | RiscvChip::CH59X 350 | | RiscvChip::CH8571 => 0x0000_0000, 351 | _ => 0x0800_0000, 352 | } 353 | } 354 | 355 | // The same as wch-openocd-riscv 356 | pub fn fix_code_flash_start(&self, start_address: u32) -> u32 { 357 | let addr = self.code_flash_start() + start_address; 358 | if addr >= 0x10000000 { 359 | addr - 0x08000000 360 | } else { 361 | addr 362 | } 363 | } 364 | 365 | /// pack size for fastprogram 366 | pub fn write_pack_size(&self) -> u32 { 367 | match self { 368 | RiscvChip::CH32V003 | RiscvChip::CH641 | RiscvChip::CH32V007 => 1024, 369 | _ => 4096, 370 | } 371 | } 372 | } 373 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | use std::{thread::sleep, time::Duration}; 2 | 3 | use anyhow::Result; 4 | use wlink::{ 5 | commands, 6 | dmi::DebugModuleInterface, 7 | firmware::{read_firmware_from_file, Firmware}, 8 | operations::ProbeSession, 9 | probe::WchLink, 10 | regs, RiscvChip, 11 | }; 12 | 13 | use clap::{Parser, Subcommand}; 14 | use clap_verbosity_flag::{InfoLevel, Verbosity}; 15 | 16 | #[derive(clap::Parser)] 17 | #[command(author, version, about, long_about = None)] 18 | struct Cli { 19 | /// Optional device index to operate on 20 | #[arg(long, short = 'd', value_name = "INDEX")] 21 | device: Option, 22 | 23 | #[command(flatten)] 24 | verbose: Verbosity, 25 | 26 | /// Detach chip after operation 27 | #[arg(long, global = true, default_value = "false")] 28 | no_detach: bool, 29 | 30 | /// Specify the chip type 31 | #[arg(long, global = true, ignore_case = true)] 32 | chip: Option, 33 | 34 | /// Connection Speed 35 | #[arg(long, global = true, default_value = "high")] 36 | speed: crate::commands::Speed, 37 | 38 | #[command(subcommand)] 39 | command: Option, 40 | } 41 | 42 | #[derive(Debug, Clone, Copy, PartialEq, Eq, clap::ValueEnum)] 43 | enum EraseMode { 44 | /// Erase code flash by power off, the probe will power off the target chip 45 | PowerOff, 46 | /// Erase code flash by RST pin, the probe will active the nRST line. Requires a RST pin connection 47 | PinRst, 48 | /// Erase code flash by probe command 49 | Default, 50 | } 51 | 52 | #[derive(Debug, Clone, Copy, PartialEq, Eq, clap::ValueEnum)] 53 | enum ResetMode { 54 | /// Quit reset 55 | Quit, 56 | /// Reset and run 57 | Run, 58 | /// Reset and halt 59 | Halt, 60 | /// Reset DM(Debug module) 61 | Dm, 62 | } 63 | 64 | #[derive(Subcommand)] 65 | enum Commands { 66 | /// Dump memory region 67 | Dump { 68 | /// Start address 69 | #[arg(value_parser = parse_number)] 70 | address: u32, 71 | 72 | /// Length in bytes, will be rounded up to the next multiple of 4 73 | #[arg(value_parser = parse_number)] 74 | length: u32, 75 | 76 | /// Write the dumped memory region to a file 77 | #[arg(short = 'o', long = "out")] 78 | filename: Option, 79 | }, 80 | /// Dump registers 81 | Regs {}, 82 | /// Erase flash 83 | Erase { 84 | /// Erase mode 85 | #[arg(long, default_value = "default")] 86 | method: EraseMode, 87 | }, 88 | /// Program the code flash 89 | Flash { 90 | /// Address in u32 91 | #[arg(short, long, value_parser = parse_number)] 92 | address: Option, 93 | /// Erase flash before flashing 94 | #[arg(long, short, default_value = "false")] 95 | erase: bool, 96 | /// Do not reset and run after flashing 97 | #[arg(long, short = 'R', default_value = "false")] 98 | no_run: bool, 99 | /// Enable SDI print after reset 100 | #[arg(long, default_value = "false")] 101 | enable_sdi_print: bool, 102 | /// Open serial port(print only) after reset 103 | #[arg(long, default_value = "false")] 104 | watch_serial: bool, 105 | /// Path to the firmware file to flash 106 | path: String, 107 | }, 108 | /// Unlock flash 109 | Unprotect {}, 110 | /// Protect flash 111 | Protect {}, 112 | /// Force set register 113 | WriteReg { 114 | /// Reg in u16 115 | #[arg(value_parser = parse_number)] 116 | reg: u32, 117 | /// Value in u32 118 | #[arg(value_parser = parse_number)] 119 | value: u32, 120 | }, 121 | /// Force write a memory word 122 | WriteMem { 123 | /// Address in u32 124 | #[arg(value_parser = parse_number)] 125 | address: u32, 126 | /// Value in u32 127 | #[arg(value_parser = parse_number)] 128 | value: u32, 129 | }, 130 | /// Halts the MCU 131 | Halt {}, 132 | /// Resumes the MCU 133 | Resume {}, 134 | /// Reset the MCU 135 | Reset { 136 | /// Reset mode 137 | #[arg(default_value = "quit")] 138 | mode: ResetMode, 139 | }, 140 | /// Debug, check status 141 | Status {}, 142 | /// Switch mode from RV to DAP or vice versa 143 | ModeSwitch { 144 | #[arg(long)] 145 | rv: bool, 146 | #[arg(long)] 147 | dap: bool, 148 | }, 149 | /// List probes 150 | List {}, 151 | /// Enable or disable power output 152 | SetPower { 153 | #[command(subcommand)] 154 | cmd: commands::control::SetPower, 155 | }, 156 | /// SDI virtual serial port, 157 | #[command(subcommand)] 158 | SdiPrint(SdiPrint), 159 | Dev {}, 160 | } 161 | 162 | #[derive(clap::Subcommand, PartialEq, Clone, Copy, Debug)] 163 | pub enum SdiPrint { 164 | /// Enable SDI print, implies --no-detach 165 | Enable, 166 | /// Disable SDI print 167 | Disable, 168 | } 169 | 170 | impl SdiPrint { 171 | pub fn is_enable(&self) -> bool { 172 | *self == SdiPrint::Enable 173 | } 174 | } 175 | 176 | fn main() -> Result<()> { 177 | let cli = Cli::parse(); 178 | 179 | // init simplelogger 180 | simplelog::TermLogger::init( 181 | cli.verbose.log_level_filter(), 182 | simplelog::Config::default(), 183 | simplelog::TerminalMode::Mixed, 184 | simplelog::ColorChoice::Auto, 185 | ) 186 | .expect("initialize simple logger"); 187 | 188 | let device_index = cli.device.unwrap_or(0); 189 | let mut will_detach = !cli.no_detach; 190 | 191 | match cli.command { 192 | None => { 193 | WchLink::list_probes()?; 194 | 195 | println!("No command given, use --help for help."); 196 | println!("hint: use `wlink status` to get started."); 197 | } 198 | Some(Commands::ModeSwitch { rv, dap }) => { 199 | WchLink::list_probes()?; 200 | log::warn!("This is an experimental feature, better use the WCH-LinkUtility!"); 201 | if !(rv ^ dap) { 202 | println!("Please choose one mode to switch, either --rv or --dap"); 203 | } else if dap { 204 | WchLink::switch_from_rv_to_dap(device_index)?; 205 | } else { 206 | WchLink::switch_from_dap_to_rv(device_index)?; 207 | } 208 | } 209 | Some(Commands::List {}) => { 210 | WchLink::list_probes()?; 211 | } 212 | Some(Commands::SetPower { cmd }) => { 213 | WchLink::set_power_output_enabled(device_index, cmd)?; 214 | } 215 | 216 | Some(Commands::Erase { method }) if method != EraseMode::Default => { 217 | // Special handling for non-default erase: bypass attach chip 218 | // So a chip family info is required, no detection 219 | let chip_family = cli.chip.ok_or(wlink::Error::Custom( 220 | "--chip required to do a special erase".into(), 221 | ))?; 222 | 223 | let mut probe = WchLink::open_nth(device_index)?; 224 | log::info!("Erase chip by {:?}", method); 225 | match method { 226 | EraseMode::PowerOff => { 227 | ProbeSession::erase_flash_by_power_off(&mut probe, chip_family)?; 228 | } 229 | EraseMode::PinRst => { 230 | log::warn!("Code flash erase by RST pin requires a RST pin connection"); 231 | ProbeSession::erase_flash_by_rst_pin(&mut probe, chip_family)?; 232 | } 233 | _ => unreachable!(), 234 | } 235 | } 236 | Some(command) => { 237 | let probe = WchLink::open_nth(device_index)?; 238 | let mut sess = ProbeSession::attach(probe, cli.chip, cli.speed)?; 239 | 240 | match command { 241 | Commands::Dev {} => { 242 | // dev only 243 | } 244 | Commands::Dump { 245 | address, 246 | length, 247 | filename, 248 | } => { 249 | log::info!( 250 | "Read memory from 0x{:08x} to 0x{:08x}", 251 | address, 252 | address + length 253 | ); 254 | 255 | let out = sess.read_memory(address, length)?; 256 | 257 | if let Some(fname) = filename { 258 | std::fs::write(&fname, &out)?; 259 | log::info!("{} bytes written to file {}", length, &fname); 260 | } else { 261 | println!( 262 | "{}", 263 | nu_pretty_hex::config_hex( 264 | &out, 265 | nu_pretty_hex::HexConfig { 266 | title: true, 267 | ascii: true, 268 | address_offset: address as _, 269 | ..Default::default() 270 | }, 271 | ) 272 | ); 273 | } 274 | } 275 | Commands::Regs {} => { 276 | log::info!("Dump GPRs"); 277 | sess.dump_regs()?; 278 | sess.dump_pmp_csrs()?; 279 | } 280 | Commands::WriteReg { reg, value } => { 281 | let regno = reg as u16; 282 | log::info!("Set reg 0x{:04x} to 0x{:08x}", regno, value); 283 | sess.write_reg(regno, value)?; 284 | } 285 | Commands::WriteMem { address, value } => { 286 | log::info!("Write memory 0x{:08x} to 0x{:08x}", value, address); 287 | sess.write_mem32(address, value)?; 288 | } 289 | Commands::Halt {} => { 290 | log::info!("Halt MCU"); 291 | sess.reset_debug_module()?; 292 | sess.ensure_mcu_halt()?; 293 | 294 | will_detach = false; // detach will resume the MCU 295 | 296 | let dmstatus: regs::Dmstatus = sess.probe.read_dmi_reg()?; 297 | log::info!("{dmstatus:#x?}"); 298 | } 299 | Commands::Resume {} => { 300 | log::info!("Resume MCU"); 301 | sess.ensure_mcu_resume()?; 302 | 303 | let dmstatus: regs::Dmstatus = sess.probe.read_dmi_reg()?; 304 | log::info!("{dmstatus:#?}"); 305 | } 306 | Commands::Erase { method } => { 307 | log::info!("Erase Flash..."); 308 | match method { 309 | EraseMode::Default => { 310 | sess.erase_flash()?; 311 | } 312 | _ => unreachable!(), 313 | } 314 | log::info!("Erase done"); 315 | } 316 | Commands::Flash { 317 | address, 318 | erase, 319 | no_run, 320 | path, 321 | enable_sdi_print, 322 | watch_serial, 323 | } => { 324 | sess.dump_info()?; 325 | 326 | if erase { 327 | log::info!("Erase Flash"); 328 | sess.erase_flash()?; 329 | } 330 | 331 | let firmware = read_firmware_from_file(path)?; 332 | 333 | match firmware { 334 | Firmware::Binary(data) => { 335 | let start_address = 336 | address.unwrap_or_else(|| sess.chip_family.code_flash_start()); 337 | log::info!("Flashing {} bytes to 0x{:08x}", data.len(), start_address); 338 | sess.write_flash(&data, start_address)?; 339 | } 340 | Firmware::Sections(sections) => { 341 | // Flash section by section 342 | if address.is_some() { 343 | log::warn!("--address is ignored when flashing ELF or ihex"); 344 | } 345 | for section in sections { 346 | let start_address = 347 | sess.chip_family.fix_code_flash_start(section.address); 348 | log::info!( 349 | "Flashing {} bytes to 0x{:08x}", 350 | section.data.len(), 351 | start_address 352 | ); 353 | sess.write_flash(§ion.data, start_address)?; 354 | } 355 | } 356 | } 357 | 358 | log::info!("Flash done"); 359 | 360 | sleep(Duration::from_millis(500)); 361 | 362 | if !no_run { 363 | log::info!("Now reset..."); 364 | sess.soft_reset()?; 365 | if enable_sdi_print { 366 | sess.set_sdi_print_enabled(true)?; 367 | 368 | will_detach = false; 369 | log::info!("Now connect to the WCH-Link serial port to read SDI print"); 370 | } 371 | if watch_serial { 372 | wlink::probe::watch_serial()?; 373 | } else { 374 | sleep(Duration::from_millis(500)); 375 | } 376 | } 377 | } 378 | Commands::Unprotect {} => { 379 | log::info!("Unprotect Flash"); 380 | sess.unprotect_flash()?; 381 | } 382 | Commands::Protect {} => { 383 | log::info!("Protect Flash"); 384 | sess.protect_flash()?; 385 | } 386 | Commands::Reset { mode } => { 387 | log::info!("Reset {:?}", mode); 388 | match mode { 389 | ResetMode::Quit => { 390 | sess.probe.send_command(commands::Reset::Soft)?; 391 | } 392 | ResetMode::Run => { 393 | sess.ensure_mcu_resume()?; 394 | } 395 | ResetMode::Halt => { 396 | sess.ensure_mcu_halt()?; 397 | 398 | will_detach = false; // detach will resume the MCU 399 | } 400 | ResetMode::Dm => { 401 | sess.reset_debug_module()?; 402 | 403 | will_detach = false; // detach will resume the MCU 404 | } 405 | } 406 | sleep(Duration::from_millis(300)); 407 | } 408 | Commands::Status {} => { 409 | sess.dump_info()?; 410 | sess.dump_core_csrs()?; 411 | sess.dump_dmi()?; 412 | } 413 | Commands::SdiPrint(v) => match v { 414 | // By enabling SDI print and modifying the _write function called by printf in the mcu code, 415 | // the WCH-Link can be used to read data from the debug interface of the mcu 416 | // and print it to the serial port of the WCH-Link instead of using its UART peripheral. 417 | // An example can be found here: 418 | // https://github.com/openwch/ch32v003/tree/main/EVT/EXAM/SDI_Printf/SDI_Printf 419 | SdiPrint::Enable => { 420 | log::info!("Enabling SDI print"); 421 | sess.set_sdi_print_enabled(true)?; 422 | will_detach = false; 423 | log::info!("Now you can connect to the WCH-Link serial port"); 424 | } 425 | SdiPrint::Disable => { 426 | log::info!("Disabling SDI print"); 427 | sess.set_sdi_print_enabled(false)?; 428 | } 429 | }, 430 | _ => unreachable!("unimplemented command"), 431 | } 432 | if will_detach { 433 | sess.detach_chip()?; 434 | } 435 | } 436 | } 437 | 438 | Ok(()) 439 | } 440 | 441 | pub fn parse_number(s: &str) -> std::result::Result { 442 | let s = s.replace('_', "").to_lowercase(); 443 | if let Some(hex_str) = s.strip_prefix("0x") { 444 | Ok( 445 | u32::from_str_radix(hex_str, 16) 446 | .unwrap_or_else(|_| panic!("error while parsing {s:?}")), 447 | ) 448 | } else if let Some(bin_str) = s.strip_prefix("0b") { 449 | Ok(u32::from_str_radix(bin_str, 2).unwrap_or_else(|_| panic!("error while parsing {s:?}"))) 450 | } else { 451 | Ok(s.parse().expect("must be a number")) 452 | } 453 | } 454 | -------------------------------------------------------------------------------- /src/operations.rs: -------------------------------------------------------------------------------- 1 | //! Predefined operations for WCH-Link 2 | 3 | use indicatif::ProgressBar; 4 | use std::{thread::sleep, time::Duration}; 5 | 6 | use crate::{ 7 | commands::{self, Speed}, 8 | probe::WchLink, 9 | Error, Result, RiscvChip, 10 | }; 11 | 12 | /// A running probe session, flash, erase, inspect, etc. 13 | pub struct ProbeSession { 14 | pub probe: WchLink, 15 | pub chip_family: RiscvChip, 16 | pub speed: Speed, 17 | } 18 | 19 | impl ProbeSession { 20 | /// Attach probe to target chip, start a probe session 21 | pub fn attach(probe: WchLink, expected_chip: Option, speed: Speed) -> Result { 22 | let mut probe = probe; 23 | 24 | let chip = expected_chip.unwrap_or(RiscvChip::CH32V103); 25 | 26 | if !probe.info.variant.support_chip(chip) { 27 | log::error!( 28 | "Current WCH-Link variant doesn't support the choosen MCU, please use WCH-LinkE!" 29 | ); 30 | return Err(Error::UnsupportedChip(chip)); 31 | } 32 | 33 | let mut chip_info = None; 34 | 35 | for _ in 0..3 { 36 | probe.send_command(commands::SetSpeed { 37 | riscvchip: chip as u8, 38 | speed, 39 | })?; 40 | 41 | if let Ok(resp) = probe.send_command(commands::control::AttachChip) { 42 | log::info!("Attached chip: {}", resp); 43 | chip_info = Some(resp); 44 | 45 | if let Some(expected_chip) = expected_chip { 46 | if resp.chip_family != expected_chip { 47 | log::error!( 48 | "Attached chip type ({:?}) does not match expected chip type ({:?})", 49 | resp.chip_family, 50 | expected_chip 51 | ); 52 | return Err(Error::ChipMismatch(expected_chip, resp.chip_family)); 53 | } 54 | } 55 | // set speed again 56 | if expected_chip.is_none() { 57 | probe.send_command(commands::SetSpeed { 58 | riscvchip: resp.chip_family as u8, 59 | speed, 60 | })?; 61 | } 62 | 63 | break; 64 | } else { 65 | log::debug!("retrying..."); 66 | sleep(Duration::from_millis(100)); 67 | } 68 | } 69 | 70 | let chip_info = chip_info.ok_or(Error::NotAttached)?; 71 | chip_info.chip_family.do_post_init(&mut probe)?; 72 | 73 | //let ret = self.send_command(control::CheckQE)?; 74 | //log::info!("Check QE: {:?}", ret); 75 | // riscvchip = 7 => 2 76 | //let flash_addr = chip_info.chip_family.code_flash_start(); 77 | //let page_size = chip_info.chip_family.data_packet_size(); 78 | 79 | Ok(ProbeSession { 80 | probe, 81 | chip_family: chip_info.chip_family, 82 | speed, 83 | }) 84 | } 85 | 86 | pub fn detach_chip(&mut self) -> Result<()> { 87 | log::trace!("Detach chip"); 88 | self.probe.send_command(commands::control::OptEnd)?; 89 | Ok(()) 90 | } 91 | 92 | fn reattach_chip(&mut self) -> Result<()> { 93 | log::debug!("Reattach chip"); 94 | self.detach_chip()?; 95 | let _ = self.probe.send_command(commands::control::AttachChip)?; 96 | Ok(()) 97 | } 98 | 99 | // NOTE: this halts the MCU 100 | pub fn dump_info(&mut self) -> Result<()> { 101 | if self.chip_family.support_query_info() { 102 | let esig = if self.probe.info.version() >= (2, 9) { 103 | self.probe.send_command(commands::GetChipInfo::V2)? 104 | } else { 105 | self.probe.send_command(commands::GetChipInfo::V1)? 106 | }; 107 | log::info!("Chip ESIG: {esig}"); 108 | 109 | let flash_protected = self 110 | .probe 111 | .send_command(commands::ConfigChip::CheckReadProtect)?; 112 | let protected = flash_protected == commands::ConfigChip::FLAG_READ_PROTECTED; 113 | log::info!("Flash protected: {}", protected); 114 | if protected { 115 | log::warn!("Flash is protected, debug access is not available"); 116 | } 117 | } 118 | if self.chip_family.support_ram_rom_mode() { 119 | let sram_code_mode = self 120 | .probe 121 | .send_command(commands::control::GetChipRomRamSplit)?; 122 | log::debug!("SRAM CODE split mode: {}", sram_code_mode); 123 | } 124 | /* 125 | if detailed { 126 | 127 | } 128 | */ 129 | Ok(()) 130 | } 131 | 132 | pub fn unprotect_flash(&mut self) -> Result<()> { 133 | // HACK: requires a fresh attach 134 | self.reattach_chip()?; 135 | 136 | let read_protected = self 137 | .probe 138 | .send_command(commands::ConfigChip::CheckReadProtect)?; 139 | if read_protected == commands::ConfigChip::FLAG_READ_PROTECTED { 140 | log::info!("Flash already unprotected"); 141 | } 142 | 143 | self.probe.send_command(commands::ConfigChip::Unprotect)?; 144 | 145 | self.reattach_chip()?; 146 | 147 | let read_protected = self 148 | .probe 149 | .send_command(commands::ConfigChip::CheckReadProtect)?; 150 | log::info!( 151 | "Read protected: {}", 152 | read_protected == commands::ConfigChip::FLAG_READ_PROTECTED 153 | ); 154 | 155 | let write_protected = self 156 | .probe 157 | .send_command(commands::ConfigChip::CheckReadProtectEx)?; 158 | if write_protected == commands::ConfigChip::FLAG_WRITE_PROTECTED { 159 | log::warn!("Flash is write protected!"); 160 | log::warn!("try to unprotect..."); 161 | self.probe 162 | .send_command(commands::ConfigChip::UnprotectEx(0xff))?; // FIXME: 0xff or 0xbf 163 | 164 | self.reattach_chip()?; 165 | 166 | let write_protected = self 167 | .probe 168 | .send_command(commands::ConfigChip::CheckReadProtectEx)?; 169 | println!( 170 | "Write protected: {}", 171 | write_protected == commands::ConfigChip::FLAG_WRITE_PROTECTED 172 | ); 173 | } 174 | 175 | Ok(()) 176 | } 177 | 178 | pub fn protect_flash(&mut self) -> Result<()> { 179 | // HACK: requires a fresh attach 180 | self.reattach_chip()?; 181 | 182 | let read_protected = self 183 | .probe 184 | .send_command(commands::ConfigChip::CheckReadProtect)?; 185 | if read_protected == commands::ConfigChip::FLAG_READ_PROTECTED { 186 | log::warn!("Flash already protected"); 187 | } 188 | 189 | self.probe.send_command(commands::ConfigChip::Protect)?; 190 | 191 | self.reattach_chip()?; 192 | 193 | let read_protected = self 194 | .probe 195 | .send_command(commands::ConfigChip::CheckReadProtect)?; 196 | log::info!( 197 | "Read protected: {}", 198 | read_protected == commands::ConfigChip::FLAG_READ_PROTECTED 199 | ); 200 | 201 | Ok(()) 202 | } 203 | 204 | /// Clear cmderror 205 | 206 | /// Erases flash and re-attach 207 | pub fn erase_flash(&mut self) -> Result<()> { 208 | if self.chip_family.support_flash_protect() { 209 | let ret = self 210 | .probe 211 | .send_command(commands::ConfigChip::CheckReadProtect)?; 212 | if ret == commands::ConfigChip::FLAG_READ_PROTECTED { 213 | log::warn!("Flash is protected, unprotecting..."); 214 | self.unprotect_flash()?; 215 | } else if ret == 2 { 216 | self.unprotect_flash()?; // FIXME: 2 is unknown 217 | } else { 218 | log::warn!("Unknown flash protect status: {}", ret); 219 | } 220 | } 221 | self.probe.send_command(commands::Program::EraseFlash)?; 222 | self.probe.send_command(commands::control::AttachChip)?; 223 | 224 | Ok(()) 225 | } 226 | 227 | // wlink_write 228 | pub fn write_flash(&mut self, data: &[u8], address: u32) -> Result<()> { 229 | let chip_family = self.chip_family; 230 | let write_pack_size = chip_family.write_pack_size(); 231 | let data_packet_size = chip_family.data_packet_size(); 232 | 233 | if chip_family.support_flash_protect() { 234 | self.unprotect_flash()?; 235 | } 236 | 237 | let data = data.to_vec(); 238 | 239 | // if data.len() % data_packet_size != 0 { 240 | // data.resize((data.len() / data_packet_size + 1) * data_packet_size, 0xff); 241 | // log::debug!("Data resized to {}", data.len()); 242 | // } 243 | log::debug!( 244 | "Using write pack size {} data pack size {}", 245 | write_pack_size, 246 | data_packet_size 247 | ); 248 | 249 | // wlink_ready_write 250 | // self.send_command(Program::Prepare)?; // no need for CH32V307 251 | self.probe.send_command(commands::SetWriteMemoryRegion { 252 | start_addr: address, 253 | len: data.len() as _, 254 | })?; 255 | 256 | // if self.chip.as_ref().unwrap().chip_family == RiscvChip::CH32V103 {} 257 | self.probe.send_command(commands::Program::WriteFlashOP)?; 258 | // wlink_ramcodewrite 259 | let flash_op = self.chip_family.get_flash_op(); 260 | self.probe.write_data(flash_op, data_packet_size)?; 261 | 262 | log::debug!("Flash OP written"); 263 | 264 | let n = self 265 | .probe 266 | .send_command(commands::Program::Unknown07AfterFlashOPWritten)?; 267 | if n != 0x07 { 268 | return Err(Error::Custom( 269 | "Unknown07AfterFlashOPWritten failed".to_string(), 270 | )); 271 | } 272 | 273 | // wlink_fastprogram 274 | let bar = ProgressBar::new(data.len() as _); 275 | 276 | self.probe.send_command(commands::Program::WriteFlash)?; 277 | for chunk in data.chunks(write_pack_size as usize) { 278 | self.probe 279 | .write_data_with_progress(chunk, data_packet_size, &|nbytes| { 280 | bar.inc(nbytes as _); 281 | })?; 282 | let rxbuf = self.probe.read_data(4)?; 283 | // 41 01 01 04 284 | if rxbuf[3] != 0x04 { 285 | return Err(Error::Custom(format!( 286 | // 0x05, 0x18, 0xff 287 | "Error while fastprogram: {:02x?}", 288 | rxbuf 289 | ))); 290 | } 291 | } 292 | bar.finish(); 293 | 294 | log::debug!("Fastprogram done"); 295 | 296 | // wlink_endprogram 297 | let _ = self.probe.send_command(commands::Program::End)?; 298 | 299 | Ok(()) 300 | } 301 | 302 | pub fn soft_reset(&mut self) -> Result<()> { 303 | self.probe.send_command(commands::Reset::Soft)?; // quit reset 304 | Ok(()) 305 | } 306 | 307 | /// Read a continuous memory region, require MCU to be halted 308 | pub fn read_memory(&mut self, address: u32, length: u32) -> Result> { 309 | let mut length = length; 310 | if length % 4 != 0 { 311 | length = (length / 4 + 1) * 4; 312 | } 313 | self.probe.send_command(commands::SetReadMemoryRegion { 314 | start_addr: address, 315 | len: length, 316 | })?; 317 | self.probe.send_command(commands::Program::ReadMemory)?; 318 | 319 | let mut mem = self.probe.read_data(length as usize)?; 320 | // Fix endian 321 | for chunk in mem.chunks_exact_mut(4) { 322 | chunk.reverse(); 323 | } 324 | 325 | if mem.starts_with(&[0xA9, 0xBD, 0xF9, 0xF3]) { 326 | log::warn!("A9 BD F9 F3 sequence detected!"); 327 | log::warn!("If the chip is just put into debug mode, you should flash the new firmware to the chip first"); 328 | log::warn!("Or else this indicates a reading to invalid location"); 329 | } 330 | 331 | Ok(mem) 332 | } 333 | 334 | pub fn set_sdi_print_enabled(&mut self, enable: bool) -> Result<()> { 335 | if !self.probe.info.variant.support_sdi_print() { 336 | return Err(Error::Custom( 337 | "Probe doesn't support SDI print functionality".to_string(), 338 | )); 339 | } 340 | if !self.chip_family.support_sdi_print() { 341 | return Err(Error::Custom( 342 | "Chip doesn't support SDI print functionality".to_string(), 343 | )); 344 | } 345 | 346 | self.probe 347 | .send_command(commands::control::SetSdiPrintEnabled(enable))?; 348 | Ok(()) 349 | } 350 | 351 | /// Clear All Code Flash - By Power off 352 | pub fn erase_flash_by_power_off(probe: &mut WchLink, chip_family: RiscvChip) -> Result<()> { 353 | if !probe.info.variant.support_power_funcs() { 354 | return Err(Error::Custom( 355 | "Probe doesn't support power off erase".to_string(), 356 | )); 357 | } 358 | if !chip_family.support_special_erase() { 359 | return Err(Error::Custom( 360 | "Chip doesn't support power off erase".to_string(), 361 | )); 362 | } 363 | 364 | probe.send_command(commands::SetSpeed { 365 | riscvchip: chip_family as u8, 366 | speed: Speed::default(), 367 | })?; 368 | probe.send_command(commands::control::EraseCodeFlash::ByPowerOff(chip_family))?; 369 | Ok(()) 370 | } 371 | 372 | /// Clear All Code Flash - By RST pin 373 | pub fn erase_flash_by_rst_pin(probe: &mut WchLink, chip_family: RiscvChip) -> Result<()> { 374 | if !probe.info.variant.support_power_funcs() { 375 | return Err(Error::Custom( 376 | "Probe doesn't support reset pin erase".to_string(), 377 | )); 378 | } 379 | if !chip_family.support_special_erase() { 380 | return Err(Error::Custom( 381 | "Chip doesn't support reset pin erase".to_string(), 382 | )); 383 | } 384 | 385 | probe.send_command(commands::SetSpeed { 386 | riscvchip: chip_family as u8, 387 | speed: Speed::default(), 388 | })?; 389 | probe.send_command(commands::control::EraseCodeFlash::ByPinRST(chip_family))?; 390 | Ok(()) 391 | } 392 | } 393 | 394 | /* 395 | 396 | // NOTE: this halts the MCU, so it's not suitable except for dumping info 397 | pub fn dump_info(&mut self, detailed: bool) -> Result<()> { 398 | let probe_info = self.probe.as_ref().unwrap(); 399 | let chip_family = self.chip.as_ref().unwrap().chip_family; 400 | 401 | if chip_family.support_query_info() { 402 | let chip_id = if probe_info.version() >= (2, 9) { 403 | self.send_command(commands::GetChipInfo::V2)? 404 | } else { 405 | self.send_command(commands::GetChipInfo::V1)? 406 | }; 407 | log::info!("Chip UID: {chip_id}"); 408 | 409 | let flash_protected = self.send_command(commands::ConfigChip::CheckReadProtect)?; 410 | let protected = flash_protected == commands::ConfigChip::FLAG_PROTECTED; 411 | log::info!("Flash protected: {}", protected); 412 | if protected { 413 | log::warn!("Flash is protected, debug access is not available"); 414 | } 415 | } 416 | if chip_family.support_ram_rom_mode() { 417 | let sram_code_mode = self.send_command(commands::control::GetChipRomRamSplit)?; 418 | log::debug!("SRAM CODE split mode: {}", sram_code_mode); 419 | } 420 | 421 | if detailed { 422 | let misa = self.read_reg(regs::MISA)?; 423 | log::trace!("Read csr misa: {misa:08x}"); 424 | let misa = parse_misa(misa); 425 | log::info!("RISC-V ISA: {misa:?}"); 426 | 427 | // detect chip's RISC-V core version, QingKe cores 428 | let marchid = self.read_reg(regs::MARCHID)?; 429 | log::trace!("Read csr marchid: {marchid:08x}"); 430 | let core_type = parse_marchid(marchid); 431 | log::info!("RISC-V arch: {core_type:?}"); 432 | } 433 | Ok(()) 434 | } 435 | 436 | 437 | 438 | 439 | // wlink_endprocess 440 | 441 | pub fn read_flash_size_kb(&mut self) -> Result { 442 | // Ref: (DS) Chapter 31 Electronic Signature (ESIG) 443 | let raw_flash_cap = self.read_memory(0x1FFFF7E0, 4)?; 444 | println!("=> {raw_flash_cap:02x?}"); 445 | let flash_size = u32::from_le_bytes(raw_flash_cap[0..4].try_into().unwrap()); 446 | log::info!("Flash size {}KiB", flash_size); 447 | Ok(flash_size) 448 | } 449 | 450 | /// Read a continuous memory region, require MCU to be halted 451 | pub fn read_memory(&mut self, address: u32, length: u32) -> Result> { 452 | let mut length = length; 453 | if length % 4 != 0 { 454 | length = (length / 4 + 1) * 4; 455 | } 456 | self.send_command(SetReadMemoryRegion { 457 | start_addr: address, 458 | len: length, 459 | })?; 460 | self.send_command(Program::ReadMemory)?; 461 | 462 | let mut mem = self.read_data_ep(length as usize)?; 463 | // Fix endian 464 | for chunk in mem.chunks_exact_mut(4) { 465 | chunk.reverse(); 466 | } 467 | 468 | if mem.starts_with(&[0xA9, 0xBD, 0xF9, 0xF3]) { 469 | log::warn!("A9 BD F9 F3 sequence detected!"); 470 | log::warn!("If the chip is just put into debug mode, you should flash the new firmware to the chip first"); 471 | log::warn!("Or else this indicates a reading to invalid location"); 472 | } 473 | 474 | println!( 475 | "{}", 476 | nu_pretty_hex::config_hex( 477 | &mem, 478 | nu_pretty_hex::HexConfig { 479 | title: false, 480 | ascii: true, 481 | address_offset: address as _, 482 | ..Default::default() 483 | }, 484 | ) 485 | ); 486 | 487 | Ok(mem) 488 | } 489 | 490 | 491 | 492 | 493 | 494 | pub fn ensure_mcu_halt(&mut self) -> Result<()> { 495 | let dmstatus = self.read_dmi_reg::()?; 496 | if dmstatus.allhalted() && dmstatus.anyhalted() { 497 | log::trace!("Already halted, nop"); 498 | } else { 499 | loop { 500 | // Initiate a halt request 501 | self.send_command(DmiOp::write(0x10, 0x80000001))?; 502 | let dmstatus = self.read_dmi_reg::()?; 503 | if dmstatus.anyhalted() && dmstatus.allhalted() { 504 | break; 505 | } else { 506 | log::warn!("Not halt, try send"); 507 | sleep(Duration::from_millis(10)); 508 | } 509 | } 510 | } 511 | 512 | // Clear the halt request bit. 513 | self.send_command(DmiOp::write(0x10, 0x00000001))?; 514 | 515 | Ok(()) 516 | } 517 | 518 | 519 | 520 | /// Write a memory word, require MCU to be halted. 521 | /// 522 | /// V2 microprocessor debug module abstract command only supports the register access mode, 523 | /// So this function will use the register access mode to write a memory word, 524 | /// instead of using the memory access mode. 525 | pub fn write_memory_word(&mut self, address: u32, data: u32) -> Result<()> { 526 | // self.ensure_mcu_halt()?; 527 | 528 | self.send_command(DmiOp::write(0x20, 0x0072a023))?; // sw x7,0(x5) 529 | self.send_command(DmiOp::write(0x21, 0x00100073))?; // ebreak 530 | self.send_command(DmiOp::write(0x04, address))?; // data0 <- address 531 | self.clear_abstractcs_cmderr()?; 532 | self.send_command(DmiOp::write(0x17, 0x00231005))?; // x5 <- data0 533 | 534 | let abstractcs = self.read_dmi_reg::()?; 535 | log::trace!("{:?}", abstractcs); 536 | if abstractcs.busy() { 537 | return Err(Error::AbstractCommandError(AbstractcsCmdErr::Busy)); //resue busy 538 | } 539 | if abstractcs.cmderr() != 0 { 540 | AbstractcsCmdErr::try_from_cmderr(abstractcs.cmderr() as _)?; 541 | } 542 | 543 | self.send_command(DmiOp::write(0x04, data))?; // data0 <- data 544 | self.clear_abstractcs_cmderr()?; 545 | self.send_command(DmiOp::write(0x17, 0x00271007))?; // data0 <- x7 546 | let abstractcs = self.read_dmi_reg::()?; 547 | log::trace!("{:?}", abstractcs); 548 | if abstractcs.busy() { 549 | return Err(Error::AbstractCommandError(AbstractcsCmdErr::Busy)); //resue busy 550 | } 551 | if abstractcs.cmderr() != 0 { 552 | AbstractcsCmdErr::try_from_cmderr(abstractcs.cmderr() as _)?; 553 | } 554 | 555 | Ok(()) 556 | } 557 | 558 | 559 | /// Clear cmderror field of abstractcs register. 560 | /// write 1 to clean the corresponding bit. 561 | fn clear_abstractcs_cmderr(&mut self) -> Result<()> { 562 | let mut abstractcs = self.read_dmi_reg::()?; 563 | abstractcs.set_cmderr(0b111); 564 | self.write_dmi_reg(abstractcs)?; 565 | Ok(()) 566 | } 567 | 568 | /// Soft reset MCU, using PFIC.CFGR.SYSRST 569 | pub fn soft_reset(&mut self) -> Result<()> { 570 | const PFIC_CFGR: u32 = 0xE000E048; 571 | const KEY3: u32 = 0xBEEF; 572 | const KEY_OFFSET: u8 = 16; 573 | const RESETSYS_OFFSET: u8 = 7; 574 | 575 | const RESET_VAL: u32 = KEY3 << KEY_OFFSET | 1 << RESETSYS_OFFSET; 576 | 577 | self.write_memory_word(PFIC_CFGR, RESET_VAL)?; 578 | 579 | Ok(()) 580 | } 581 | 582 | // SingleLineCoreReset 583 | pub fn reset_mcu_and_run(&mut self) -> Result<()> { 584 | self.ensure_mcu_halt()?; 585 | self.clear_dmstatus_havereset()?; 586 | 587 | // Clear the reset signal. 588 | self.send_command(DmiOp::write(0x10, 0x00000001))?; // clear haltreq 589 | 590 | self.send_command(DmiOp::write(0x10, 0x00000003))?; // initiate ndmreset 591 | let dmstatus = self.read_dmi_reg::()?; 592 | println!("{:?}", dmstatus); 593 | if dmstatus.allhavereset() && dmstatus.anyhavereset() { 594 | // reseted 595 | log::debug!("Reseted"); 596 | } else { 597 | log::warn!("Reset failed"); 598 | } 599 | 600 | // Clear the reset status signal 601 | self.send_command(DmiOp::write(0x10, 0x10000001))?; // ackhavereset 602 | let dmstatus = self.read_dmi_reg::()?; 603 | if !dmstatus.allhavereset() && !dmstatus.anyhavereset() { 604 | log::debug!("Reset status cleared"); 605 | } else { 606 | log::warn!("Reset status clear failed"); 607 | } 608 | Ok(()) 609 | } 610 | 611 | /// Microprocessor halted immediately after reset 612 | pub fn reset_mcu_and_halt(&mut self) -> Result<()> { 613 | self.ensure_mcu_halt()?; 614 | 615 | // Initiate a core reset request and hold the halt request. 616 | self.send_command(DmiOp::write(0x10, 0x80000003))?; 617 | let dmstatus = self.read_dmi_reg::()?; 618 | if dmstatus.allhavereset() && dmstatus.anyhavereset() { 619 | log::debug!("Reseted"); 620 | } else { 621 | log::debug!("Reset failed") 622 | } 623 | // Clear the reset status signal and hold the halt request 624 | loop { 625 | self.send_command(DmiOp::write(0x10, 0x90000001))?; 626 | let dmstatus = self.read_dmi_reg::()?; 627 | if !dmstatus.allhavereset() && !dmstatus.anyhavereset() { 628 | log::debug!("Reset status cleared"); 629 | break; 630 | } else { 631 | log::warn!("Reset status clear failed") 632 | } 633 | } 634 | // Clear the halt request when the processor is reset and haltedd again 635 | self.send_command(DmiOp::write(0x10, 0x00000001))?; 636 | 637 | Ok(()) 638 | } 639 | 640 | 641 | 642 | 643 | } 644 | */ 645 | -------------------------------------------------------------------------------- /src/probe.rs: -------------------------------------------------------------------------------- 1 | //! The probe - WCH-Link 2 | 3 | use crate::commands::{self, RawCommand, Response}; 4 | use crate::{commands::control::ProbeInfo, usb_device::USBDeviceBackend}; 5 | use crate::{usb_device, Error, Result, RiscvChip}; 6 | use std::fmt; 7 | 8 | pub const VENDOR_ID: u16 = 0x1a86; 9 | pub const PRODUCT_ID: u16 = 0x8010; 10 | 11 | pub const ENDPOINT_OUT: u8 = 0x01; 12 | pub const ENDPOINT_IN: u8 = 0x81; 13 | 14 | pub const DATA_ENDPOINT_OUT: u8 = 0x02; 15 | pub const DATA_ENDPOINT_IN: u8 = 0x82; 16 | 17 | pub const VENDOR_ID_DAP: u16 = 0x1a86; 18 | pub const PRODUCT_ID_DAP: u16 = 0x8012; 19 | 20 | pub const ENDPOINT_OUT_DAP: u8 = 0x02; 21 | 22 | /// All WCH-Link probe variants, see-also: 23 | #[derive(Clone, Copy, Debug, PartialEq, Eq, Default)] 24 | #[repr(u8)] 25 | pub enum WchLinkVariant { 26 | /// WCH-Link-CH549, does not support CH32V00X 27 | Ch549 = 1, 28 | /// WCH-LinkE-CH32V305 29 | #[default] 30 | ECh32v305 = 2, 31 | /// WCH-LinkS-CH32V203 32 | SCh32v203 = 3, 33 | /// WCH-LinkW-CH32V208 34 | WCh32v208 = 5, 35 | } 36 | 37 | impl WchLinkVariant { 38 | pub fn try_from_u8(value: u8) -> Result { 39 | match value { 40 | 1 => Ok(Self::Ch549), 41 | 2 | 0x12 => Ok(Self::ECh32v305), 42 | 3 => Ok(Self::SCh32v203), 43 | 5 | 0x85 => Ok(Self::WCh32v208), 44 | _ => Err(Error::UnknownLinkVariant(value)), 45 | } 46 | } 47 | 48 | /// CH549 variant does not support mode switch. re-program is needed. 49 | pub fn support_switch_mode(&self) -> bool { 50 | !matches!(self, WchLinkVariant::Ch549) 51 | } 52 | 53 | /// Only W, E mode support this, power functions 54 | pub fn support_power_funcs(&self) -> bool { 55 | matches!(self, WchLinkVariant::WCh32v208 | WchLinkVariant::ECh32v305) 56 | } 57 | 58 | /// Only E mode support SDR print functionality 59 | pub fn support_sdi_print(&self) -> bool { 60 | matches!(self, WchLinkVariant::ECh32v305) 61 | } 62 | 63 | /// Better use E variant, the Old CH549-based variant does not support all chips 64 | pub fn support_chip(&self, chip: RiscvChip) -> bool { 65 | match self { 66 | WchLinkVariant::Ch549 => !matches!( 67 | chip, 68 | RiscvChip::CH32V003 | RiscvChip::CH32X035 | RiscvChip::CH643 69 | ), 70 | WchLinkVariant::WCh32v208 => !matches!( 71 | chip, 72 | RiscvChip::CH56X | RiscvChip::CH57X | RiscvChip::CH582 | RiscvChip::CH59X 73 | ), 74 | _ => true, 75 | } 76 | } 77 | } 78 | 79 | impl fmt::Display for WchLinkVariant { 80 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 81 | match self { 82 | WchLinkVariant::Ch549 => write!(f, "WCH-Link-CH549"), 83 | WchLinkVariant::ECh32v305 => write!(f, "WCH-LinkE-CH32V305"), 84 | WchLinkVariant::SCh32v203 => write!(f, "WCH-LinkS-CH32V203"), 85 | WchLinkVariant::WCh32v208 => write!(f, "WCH-LinkW-CH32V208"), 86 | } 87 | } 88 | } 89 | 90 | /// Abstraction of WchLink probe interface 91 | #[derive(Debug)] 92 | pub struct WchLink { 93 | pub(crate) device: Box, 94 | pub info: ProbeInfo, 95 | } 96 | 97 | impl WchLink { 98 | pub fn open_nth(nth: usize) -> Result { 99 | let device = match crate::usb_device::open_nth(VENDOR_ID, PRODUCT_ID, nth) { 100 | Ok(dev) => dev, 101 | Err(e) => { 102 | // Detect if it is in DAP mode 103 | if crate::usb_device::open_nth(VENDOR_ID_DAP, PRODUCT_ID_DAP, nth).is_ok() { 104 | return Err(Error::ProbeModeNotSupported); 105 | } else { 106 | return Err(e); 107 | } 108 | } 109 | }; 110 | let mut this = WchLink { 111 | device, 112 | info: Default::default(), 113 | }; 114 | let info = this.send_command(commands::control::GetProbeInfo)?; 115 | this.info = info; 116 | 117 | log::info!("Connected to {}", this.info); 118 | 119 | Ok(this) 120 | } 121 | 122 | pub fn probe_info(&mut self) -> Result { 123 | let info = self.send_command(commands::control::GetProbeInfo)?; 124 | log::info!("{}", info); 125 | self.info = info; 126 | Ok(info) 127 | } 128 | 129 | pub fn list_probes() -> Result<()> { 130 | let devs = usb_device::list_devices(VENDOR_ID, PRODUCT_ID)?; 131 | for dev in devs { 132 | println!("{} (RV mode)", dev) 133 | } 134 | let devs = usb_device::list_devices(VENDOR_ID_DAP, PRODUCT_ID_DAP)?; 135 | for dev in devs { 136 | println!("{} (DAP mode)", dev) 137 | } 138 | Ok(()) 139 | } 140 | 141 | /// Switch from DAP mode to RV mode 142 | // ref: https://github.com/cjacker/wchlinke-mode-switch/blob/main/main.c 143 | pub fn switch_from_rv_to_dap(nth: usize) -> Result<()> { 144 | let mut probe = Self::open_nth(nth)?; 145 | 146 | if probe.info.variant.support_switch_mode() { 147 | log::info!("Switch mode for WCH-LinkRV"); 148 | 149 | let _ = probe.send_command(RawCommand::<0xff>(vec![0x41])); 150 | Ok(()) 151 | } else { 152 | log::error!("Cannot switch mode for WCH-LinkRV: not supported"); 153 | Err(crate::Error::Custom(format!( 154 | "The probe {} does not support mode switch", 155 | probe.info.variant 156 | ))) 157 | } 158 | } 159 | 160 | pub fn switch_from_dap_to_rv(nth: usize) -> Result<()> { 161 | let mut dev = crate::usb_device::open_nth(VENDOR_ID_DAP, PRODUCT_ID_DAP, nth)?; 162 | log::info!( 163 | "Switch mode WCH-LinkDAP {:04x}:{:04x} #{}", 164 | VENDOR_ID_DAP, 165 | PRODUCT_ID_DAP, 166 | nth 167 | ); 168 | 169 | let buf = [0x81, 0xff, 0x01, 0x52]; 170 | log::trace!("send {} {}", hex::encode(&buf[..3]), hex::encode(&buf[3..])); 171 | let _ = dev.write_endpoint(ENDPOINT_OUT_DAP, &buf); 172 | 173 | Ok(()) 174 | } 175 | 176 | pub fn set_power_output_enabled(nth: usize, cmd: commands::control::SetPower) -> Result<()> { 177 | let mut probe = Self::open_nth(nth)?; 178 | 179 | if !probe.info.variant.support_power_funcs() { 180 | return Err(Error::Custom( 181 | "Probe doesn't support power control".to_string(), 182 | )); 183 | } 184 | 185 | probe.send_command(cmd)?; 186 | 187 | match cmd { 188 | commands::control::SetPower::Enable3v3 => log::info!("Enable 3.3V Output"), 189 | commands::control::SetPower::Disable3v3 => log::info!("Disable 3.3V Output"), 190 | commands::control::SetPower::Enable5v => log::info!("Enable 5V Output"), 191 | commands::control::SetPower::Disable5v => log::info!("Disable 5V Output"), 192 | } 193 | 194 | Ok(()) 195 | } 196 | 197 | fn write_raw_cmd(&mut self, buf: &[u8]) -> Result<()> { 198 | log::trace!("send {} {}", hex::encode(&buf[..3]), hex::encode(&buf[3..])); 199 | self.device.write_endpoint(ENDPOINT_OUT, buf)?; 200 | Ok(()) 201 | } 202 | 203 | fn read_raw_cmd_resp(&mut self) -> Result> { 204 | let mut buf = [0u8; 64]; 205 | let bytes_read = self.device.read_endpoint(ENDPOINT_IN, &mut buf)?; 206 | 207 | let resp = buf[..bytes_read].to_vec(); 208 | log::trace!( 209 | "recv {} {}", 210 | hex::encode(&resp[..3]), 211 | hex::encode(&resp[3..]) 212 | ); 213 | Ok(resp) 214 | } 215 | 216 | pub fn send_command(&mut self, cmd: C) -> Result { 217 | log::trace!("send command: {:?}", cmd); 218 | let raw = cmd.to_raw(); 219 | self.write_raw_cmd(&raw)?; 220 | let resp = self.read_raw_cmd_resp()?; 221 | 222 | C::Response::from_raw(&resp) 223 | } 224 | 225 | pub(crate) fn read_data(&mut self, n: usize) -> Result> { 226 | let mut buf = Vec::with_capacity(n); 227 | let mut bytes_read = 0; 228 | while bytes_read < n { 229 | let mut chunk = vec![0u8; 64]; 230 | let chunk_read = self.device.read_endpoint(DATA_ENDPOINT_IN, &mut chunk)?; 231 | buf.extend_from_slice(&chunk[..chunk_read]); 232 | bytes_read += chunk_read; 233 | } 234 | if bytes_read != n { 235 | return Err(crate::Error::InvalidPayloadLength); 236 | } 237 | log::trace!("read data ep {} bytes", bytes_read); 238 | if bytes_read <= 10 { 239 | log::trace!("recv data {}", hex::encode(&buf[..bytes_read])); 240 | } 241 | if bytes_read != n { 242 | log::warn!("read data ep {} bytes", bytes_read); 243 | return Err(Error::InvalidPayloadLength); 244 | } 245 | Ok(buf[..n].to_vec()) 246 | } 247 | 248 | pub(crate) fn write_data(&mut self, buf: &[u8], packet_len: usize) -> Result<()> { 249 | self.write_data_with_progress(buf, packet_len, &|_| {}) 250 | } 251 | 252 | pub(crate) fn write_data_with_progress( 253 | &mut self, 254 | buf: &[u8], 255 | packet_len: usize, 256 | progress_callback: &dyn Fn(usize), 257 | ) -> Result<()> { 258 | for chunk in buf.chunks(packet_len) { 259 | let mut chunk = chunk.to_vec(); 260 | progress_callback(chunk.len()); 261 | if chunk.len() < packet_len { 262 | chunk.resize(packet_len, 0xff); 263 | } 264 | log::trace!("write data ep {} bytes", chunk.len()); 265 | self.device.write_endpoint(DATA_ENDPOINT_OUT, &chunk)?; 266 | } 267 | log::trace!("write data ep total {} bytes", buf.len()); 268 | Ok(()) 269 | } 270 | } 271 | 272 | /// Helper for SDI print 273 | pub fn watch_serial() -> Result<()> { 274 | use serialport::SerialPortType; 275 | 276 | let port_info = serialport::available_ports()? 277 | .into_iter() 278 | .find(|port| { 279 | if let SerialPortType::UsbPort(info) = &port.port_type { 280 | info.vid == VENDOR_ID && info.pid == PRODUCT_ID 281 | } else { 282 | false 283 | } 284 | }) 285 | .ok_or_else(|| Error::Custom("No serial port found".to_string()))?; 286 | log::debug!("Opening serial port: {:?}", port_info.port_name); 287 | 288 | let mut port = serialport::new(&port_info.port_name, 115200) 289 | .timeout(std::time::Duration::from_millis(1000)) 290 | .open()?; 291 | 292 | log::trace!("Serial port opened: {:?}", port); 293 | 294 | let mut endl = true; 295 | loop { 296 | let mut buf = [0u8; 1024]; 297 | match port.read(&mut buf) { 298 | Ok(n) => { 299 | let s = String::from_utf8_lossy(&buf[..n]); 300 | for c in s.chars() { 301 | if c == '\r' || c == '\n' { 302 | if endl { 303 | // continous line break 304 | println!("{}:", chrono::Local::now()); 305 | } else { 306 | endl = true; 307 | println!() 308 | } 309 | } else if endl { 310 | print!( 311 | "{}: {}", 312 | chrono::Local::now().format("%Y-%m-%d %H:%M:%S%.3f"), 313 | c 314 | ); 315 | endl = false; 316 | } else { 317 | print!("{}", c); 318 | } 319 | } 320 | } 321 | Err(ref e) if e.kind() == std::io::ErrorKind::TimedOut => (), 322 | Err(e) => return Err(e.into()), 323 | } 324 | } 325 | } 326 | -------------------------------------------------------------------------------- /src/regs.rs: -------------------------------------------------------------------------------- 1 | //! Register definitions 2 | use bitfield::bitfield; 3 | 4 | // Register fields for command.regno (16-bit) 5 | // CSR: 0x0000 - 0x0fff 6 | pub const MARCHID: u16 = 0xF12; 7 | pub const MIMPID: u16 = 0xF13; 8 | pub const MSTATUS: u16 = 0x300; 9 | pub const MISA: u16 = 0x301; 10 | pub const MTVEC: u16 = 0x305; 11 | pub const MSCRATCH: u16 = 0x340; 12 | pub const MEPC: u16 = 0x341; 13 | pub const MCAUSE: u16 = 0x342; 14 | pub const MTVAL: u16 = 0x343; 15 | pub const DPC: u16 = 0x7b1; 16 | 17 | // Debug interface, DMI registers 18 | pub const DMDATA0: u8 = 0x04; 19 | pub const DMDATA1: u8 = 0x05; 20 | pub const DMCONTROL: u8 = 0x10; 21 | pub const DMSTATUS: u8 = 0x11; 22 | pub const DMHARTINFO: u8 = 0x12; 23 | pub const DMABSTRACTCS: u8 = 0x16; 24 | pub const DMCOMMAND: u8 = 0x17; 25 | // Not available for QingkeV2, QingkeV3 26 | pub const DMABSTRACTAUTO: u8 = 0x18; 27 | pub const DMPROGBUF0: u8 = 0x20; 28 | pub const DMPROGBUF1: u8 = 0x21; 29 | pub const DMPROGBUF2: u8 = 0x22; 30 | pub const DMPROGBUF3: u8 = 0x23; 31 | pub const DMPROGBUF4: u8 = 0x24; 32 | pub const DMPROGBUF5: u8 = 0x25; 33 | pub const DMPROGBUF6: u8 = 0x26; 34 | pub const DMPROGBUF7: u8 = 0x27; 35 | pub const DMHALTSUM0: u8 = 0x40; 36 | 37 | // GPR: 0x1000 - 0x101f 38 | pub const GPRS_RVI: &[(&str, &str, u16)] = &[ 39 | ("x0", "zero", 0x1000), 40 | ("x1", "ra", 0x1001), 41 | ("x2", "sp", 0x1002), 42 | ("x3", "gp", 0x1003), 43 | ("x4", "tp", 0x1004), 44 | ("x5", "t0", 0x1005), 45 | ("x6", "t1", 0x1006), 46 | ("x7", "t2", 0x1007), 47 | ("x8", "s0", 0x1008), 48 | ("x9", "s1", 0x1009), 49 | ("x10", "a0", 0x100a), 50 | ("x11", "a1", 0x100b), 51 | ("x12", "a2", 0x100c), 52 | ("x13", "a3", 0x100d), 53 | ("x14", "a4", 0x100e), 54 | ("x15", "a5", 0x100f), 55 | ("x16", "a6", 0x1010), 56 | ("x17", "a7", 0x1011), 57 | ("x18", "s2", 0x1012), 58 | ("x19", "s3", 0x1013), 59 | ("x20", "s4", 0x1014), 60 | ("x21", "s5", 0x1015), 61 | ("x22", "s6", 0x1016), 62 | ("x23", "s7", 0x1017), 63 | ("x24", "s8", 0x1018), 64 | ("x25", "s9", 0x1019), 65 | ("x26", "s10", 0x101a), 66 | ("x27", "s11", 0x101b), 67 | ("x28", "t3", 0x101c), 68 | ("x29", "t4", 0x101d), 69 | ("x30", "t5", 0x101e), 70 | ("x31", "t6", 0x101f), 71 | ]; 72 | 73 | /// Gereral Purpose Register for riscv32ec 74 | pub const GPRS_RVE: &[(&str, &str, u16)] = &[ 75 | ("x0", "zero", 0x1000), 76 | ("x1", "ra", 0x1001), 77 | ("x2", "sp", 0x1002), 78 | ("x3", "gp", 0x1003), 79 | ("x4", "tp", 0x1004), 80 | ("x5", "t0", 0x1005), 81 | ("x6", "t1", 0x1006), 82 | ("x7", "t2", 0x1007), 83 | ("x8", "s0", 0x1008), 84 | ("x9", "s1", 0x1009), 85 | ("x10", "a0", 0x100a), 86 | ("x11", "a1", 0x100b), 87 | ("x12", "a2", 0x100c), 88 | ("x13", "a3", 0x100d), 89 | ("x14", "a4", 0x100e), 90 | ("x15", "a5", 0x100f), 91 | ]; 92 | 93 | pub const CSRS: &[(&str, u16)] = &[ 94 | ("marchid", 0xf12), 95 | ("mimpid", 0xf13), 96 | ("mhartid", 0xf14), 97 | ("misa", 0x301), 98 | ("mtvec", 0x305), 99 | ("mscratch", 0x340), 100 | ("mepc", 0x341), 101 | ("mcause", 0x342), 102 | ("mtval", 0x343), 103 | ("mstatus", 0x300), 104 | ("dcsr", 0x7b0), 105 | ("dpc", 0x7b1), 106 | ("dscratch0", 0x7b2), 107 | ("dscratch1", 0x7b3), 108 | // Qingke-RISCV CSR extensions 109 | ("gintenr", 0x800), 110 | ("intsyscr", 0x804), 111 | ("corecfgr", 0xbc0), 112 | ]; 113 | 114 | /// Physical Memory Protection CSRs, only available for QingkeV4 115 | pub const PMP_CSRS: &[(&str, u16)] = &[ 116 | ("pmpcfg0", 0x3A0), 117 | ("pmpaddr0", 0x3B0), 118 | ("pmpaddr1", 0x3B1), 119 | ("pmpaddr2", 0x3B2), 120 | ("pmpaddr3", 0x3B3), 121 | ]; 122 | 123 | // FPR: 0x1020-0x103f 124 | 125 | /// Debug Module Register 126 | pub trait DMReg: From + Into { 127 | const ADDR: u8; 128 | } 129 | 130 | bitfield! { 131 | /// Debug Module Control, 0x10 132 | pub struct Dmcontrol(u32); 133 | impl Debug; 134 | pub haltreq, set_haltreq: 31; 135 | pub resumereq, set_resumereq: 30; 136 | pub ackhavereset, set_ackhavereset: 29; 137 | pub ndmreset, set_ndmreset: 1; 138 | pub dmactive, set_dmactive: 0; 139 | } 140 | impl From for Dmcontrol { 141 | fn from(value: u32) -> Self { 142 | Self(value) 143 | } 144 | } 145 | impl From for u32 { 146 | fn from(val: Dmcontrol) -> Self { 147 | val.0 148 | } 149 | } 150 | impl DMReg for Dmcontrol { 151 | const ADDR: u8 = 0x10; 152 | } 153 | 154 | bitfield! { 155 | /// Debug Module Status, 0x11 156 | pub struct Dmstatus(u32); 157 | impl Debug; 158 | pub allhavereset, _: 19; 159 | pub anyhavereset, _: 18; 160 | pub allresumeack, _: 17; 161 | pub anyresumeack, _: 16; 162 | pub allunavail, _: 13; // ? allavail 163 | pub anyunavail, _: 12; 164 | pub allrunning, _: 11; 165 | pub anyrunning, _: 10; 166 | pub allhalted, _: 9; 167 | pub anyhalted, _: 8; 168 | pub authenticated, _: 7; 169 | pub version, _: 3, 0; 170 | } 171 | impl From for Dmstatus { 172 | fn from(value: u32) -> Self { 173 | Self(value) 174 | } 175 | } 176 | impl From for u32 { 177 | fn from(val: Dmstatus) -> Self { 178 | val.0 179 | } 180 | } 181 | impl DMReg for Dmstatus { 182 | const ADDR: u8 = 0x11; 183 | } 184 | 185 | bitfield! { 186 | /// Hart information register, Microprocessor status, 0x12 187 | pub struct Hartinfo(u32); 188 | impl Debug; 189 | pub nscratch, _: 23, 20; 190 | pub dataaccess, _: 16; 191 | pub datasize, _: 15, 12; 192 | pub dataaddr, _: 11, 0; 193 | } 194 | impl From for Hartinfo { 195 | fn from(value: u32) -> Self { 196 | Self(value) 197 | } 198 | } 199 | impl From for u32 { 200 | fn from(val: Hartinfo) -> Self { 201 | val.0 202 | } 203 | } 204 | impl DMReg for Hartinfo { 205 | const ADDR: u8 = 0x12; 206 | } 207 | 208 | bitfield! { 209 | /// Abstract command status register, 0x16 210 | #[derive(Clone, Copy)] 211 | pub struct Abstractcs(u32); 212 | impl Debug; 213 | pub progbufsize, _: 28, 24; 214 | pub busy, _: 12; 215 | pub cmderr, set_cmderr: 10, 8; 216 | pub datacount, _: 3, 0; 217 | } 218 | impl From for Abstractcs { 219 | fn from(value: u32) -> Self { 220 | Self(value) 221 | } 222 | } 223 | impl From for u32 { 224 | fn from(val: Abstractcs) -> Self { 225 | val.0 226 | } 227 | } 228 | impl DMReg for Abstractcs { 229 | const ADDR: u8 = 0x16; 230 | } 231 | 232 | bitfield! { 233 | /// Abstract command register 234 | pub struct Command(u32); 235 | impl Debug; 236 | pub cmdtype, set_cmdtype: 31, 24; 237 | pub aarsize, set_aarsize: 22, 20; 238 | pub aarpostincrement, set_aarpostincrement: 19; 239 | pub postexec, set_postexec: 18; 240 | pub transfer, set_transfer: 17; 241 | pub write, set_write: 16; 242 | pub regno, set_regno: 15, 0; 243 | } 244 | impl From for Command { 245 | fn from(value: u32) -> Self { 246 | Self(value) 247 | } 248 | } 249 | impl From for u32 { 250 | fn from(val: Command) -> Self { 251 | val.0 252 | } 253 | } 254 | impl DMReg for Command { 255 | const ADDR: u8 = 0x17; 256 | } 257 | -------------------------------------------------------------------------------- /src/usb_device.rs: -------------------------------------------------------------------------------- 1 | //! USB Device abstraction - The USB Device of WCH-Link. 2 | 3 | use crate::Result; 4 | use std::{ 5 | fmt::{Debug, Display}, 6 | time::Duration, 7 | }; 8 | 9 | pub trait USBDeviceBackend: Debug { 10 | fn set_timeout(&mut self, _timeout: Duration) {} 11 | 12 | fn read_endpoint(&mut self, ep: u8, buf: &mut [u8]) -> Result; 13 | 14 | fn open_nth(vid: u16, pid: u16, nth: usize) -> Result> 15 | where 16 | Self: Sized; 17 | 18 | fn write_endpoint(&mut self, ep: u8, buf: &[u8]) -> Result<()>; 19 | } 20 | 21 | pub fn open_nth(vid: u16, pid: u16, nth: usize) -> Result> { 22 | #[cfg(all(target_os = "windows", target_arch = "x86"))] 23 | { 24 | ch375_driver::CH375USBDevice::open_nth(vid, pid, nth) 25 | .or_else(|_| libusb::LibUSBDevice::open_nth(vid, pid, nth)) 26 | } 27 | #[cfg(not(all(target_os = "windows", target_arch = "x86")))] 28 | { 29 | libusb::LibUSBDevice::open_nth(vid, pid, nth) 30 | } 31 | } 32 | 33 | pub fn list_devices(vid: u16, pid: u16) -> Result> { 34 | let mut ret = vec![]; 35 | #[cfg(all(target_os = "windows", target_arch = "x86"))] 36 | { 37 | ret.extend( 38 | ch375_driver::list_devices(vid, pid)? 39 | .into_iter() 40 | .map(|s| s.to_string()), 41 | ); 42 | } 43 | 44 | ret.extend( 45 | libusb::list_libusb_devices(vid, pid)? 46 | .into_iter() 47 | .map(|s| s.to_string()), 48 | ); 49 | 50 | Ok(ret) 51 | } 52 | 53 | pub mod libusb { 54 | use std::fmt; 55 | 56 | use super::*; 57 | use rusb::{DeviceHandle, Speed, UsbContext}; 58 | 59 | pub fn list_libusb_devices(vid: u16, pid: u16) -> Result> { 60 | let context = rusb::Context::new()?; 61 | let devices = context.devices()?; 62 | let mut result = vec![]; 63 | let mut idx = 0; 64 | 65 | for device in devices.iter() { 66 | let device_desc = device.device_descriptor()?; 67 | if device_desc.vendor_id() == vid && device_desc.product_id() == pid { 68 | result.push(format!( 69 | " Bus {:03} Device {:03} ID {:04x}:{:04x}({})", 70 | idx, 71 | device.bus_number(), 72 | device.address(), 73 | device_desc.vendor_id(), 74 | device_desc.product_id(), 75 | get_speed(device.speed()) 76 | )); 77 | idx += 1; 78 | } 79 | } 80 | Ok(result) 81 | } 82 | 83 | pub struct LibUSBDevice { 84 | handle: DeviceHandle, 85 | timeout: Duration, 86 | } 87 | 88 | impl fmt::Debug for LibUSBDevice { 89 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 90 | f.debug_struct("USBDevice") 91 | .field("provider", &"libusb") 92 | .field("handle", &self.handle.device()) 93 | .finish() 94 | } 95 | } 96 | 97 | impl USBDeviceBackend for LibUSBDevice { 98 | fn set_timeout(&mut self, timeout: Duration) { 99 | self.timeout = timeout; 100 | } 101 | 102 | fn open_nth(vid: u16, pid: u16, nth: usize) -> Result> { 103 | let context = rusb::Context::new()?; 104 | let devices = context.devices()?; 105 | let mut result = vec![]; 106 | for device in devices.iter() { 107 | let device_desc = device.device_descriptor()?; 108 | if device_desc.vendor_id() == vid && device_desc.product_id() == pid { 109 | result.push(device); 110 | } 111 | } 112 | if nth >= result.len() { 113 | return Err(crate::Error::ProbeNotFound); 114 | } 115 | let device = result.remove(nth); 116 | let handle = device.open()?; 117 | 118 | log::trace!("Device: {:?}", &device); 119 | 120 | let desc = device.device_descriptor()?; 121 | let serial_number = handle.read_serial_number_string_ascii(&desc)?; 122 | log::debug!("Serial number: {:?}", serial_number); 123 | 124 | handle.claim_interface(0)?; 125 | 126 | Ok(Box::new(LibUSBDevice { 127 | handle, 128 | timeout: Duration::from_millis(5000), 129 | })) 130 | } 131 | 132 | fn read_endpoint(&mut self, ep: u8, buf: &mut [u8]) -> Result { 133 | let bytes_read = self.handle.read_bulk(ep, buf, self.timeout)?; 134 | Ok(bytes_read) 135 | } 136 | 137 | fn write_endpoint(&mut self, ep: u8, buf: &[u8]) -> Result<()> { 138 | self.handle.write_bulk(ep, buf, self.timeout)?; 139 | Ok(()) 140 | } 141 | } 142 | 143 | impl Drop for LibUSBDevice { 144 | fn drop(&mut self) { 145 | let _ = self.handle.release_interface(0); 146 | } 147 | } 148 | 149 | fn get_speed(speed: Speed) -> &'static str { 150 | match speed { 151 | Speed::SuperPlus => "USB-SS+ 10000 Mbps", 152 | Speed::Super => "USB-SS 5000 Mbps", 153 | Speed::High => "USB-HS 480 Mbps", 154 | Speed::Full => "USB-FS 12 Mbps", 155 | Speed::Low => "USB-LS 1.5 Mbps", 156 | _ => "(unknown)", 157 | } 158 | } 159 | } 160 | 161 | #[cfg(all(target_os = "windows", target_arch = "x86"))] 162 | pub mod ch375_driver { 163 | use libloading::os::windows::*; 164 | use std::fmt; 165 | 166 | use super::*; 167 | use crate::Error; 168 | 169 | static mut CH375_DRIVER: Option = None; 170 | 171 | fn ensure_library_load() -> Result<&'static Library> { 172 | unsafe { 173 | if CH375_DRIVER.is_none() { 174 | CH375_DRIVER = Some( 175 | Library::new("WCHLinkDLL.dll") 176 | .map_err(|_| Error::Custom("WCHLinkDLL.dll not found".to_string()))?, 177 | ); 178 | let lib = CH375_DRIVER.as_ref().unwrap(); 179 | let get_version: Symbol u32> = 180 | { lib.get(b"CH375GetVersion").unwrap() }; 181 | let get_driver_version: Symbol u32> = 182 | { lib.get(b"CH375GetDrvVersion").unwrap() }; 183 | 184 | log::debug!( 185 | "DLL version {}, driver version {}", 186 | get_version(), 187 | get_driver_version() 188 | ); 189 | Ok(lib) 190 | } else { 191 | Ok(CH375_DRIVER.as_ref().unwrap()) 192 | } 193 | } 194 | } 195 | 196 | #[allow(non_snake_case, unused)] 197 | #[derive(Debug)] 198 | #[repr(packed)] 199 | pub struct UsbDeviceDescriptor { 200 | bLength: u8, 201 | bDescriptorType: u8, 202 | bcdUSB: u16, 203 | bDeviceClass: u8, 204 | bDeviceSubClass: u8, 205 | bDeviceProtocol: u8, 206 | bMaxPacketSize0: u8, 207 | idVendor: u16, 208 | idProduct: u16, 209 | bcdDevice: u16, 210 | iManufacturer: u8, 211 | iProduct: u8, 212 | iSerialNumber: u8, 213 | bNumConfigurations: u8, 214 | } 215 | 216 | pub fn list_devices(vid: u16, pid: u16) -> Result> { 217 | let lib = ensure_library_load()?; 218 | let mut ret: Vec = vec![]; 219 | 220 | let open_device: Symbol u32> = 221 | unsafe { lib.get(b"CH375OpenDevice").unwrap() }; 222 | let close_device: Symbol = 223 | unsafe { lib.get(b"CH375CloseDevice").unwrap() }; 224 | let get_device_descriptor: Symbol< 225 | unsafe extern "stdcall" fn(u32, *mut UsbDeviceDescriptor, *mut u32) -> bool, 226 | > = unsafe { lib.get(b"CH375GetDeviceDescr").unwrap() }; 227 | 228 | const INVALID_HANDLE: u32 = 0xffffffff; 229 | 230 | for i in 0..8 { 231 | let h = unsafe { open_device(i) }; 232 | if h != INVALID_HANDLE { 233 | let mut descr = unsafe { core::mem::zeroed() }; 234 | let mut len = core::mem::size_of::() as u32; 235 | let _ = unsafe { get_device_descriptor(i, &mut descr, &mut len) }; 236 | 237 | if descr.idVendor == vid && descr.idProduct == pid { 238 | ret.push(format!( 239 | " CH375Driver Device {:04x}:{:04x}", 240 | i, vid, pid 241 | )); 242 | 243 | log::debug!("Device #{}: {:04x}:{:04x}", i, vid, pid); 244 | } 245 | unsafe { close_device(i) }; 246 | } 247 | } 248 | 249 | Ok(ret) 250 | } 251 | 252 | /// USB Device implementation provided by CH375 Windows driver 253 | pub struct CH375USBDevice { 254 | index: u32, 255 | } 256 | 257 | impl fmt::Debug for CH375USBDevice { 258 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 259 | f.debug_struct("USBDevice") 260 | .field("provider", &"ch375") 261 | .field("device", &self.index) 262 | .finish() 263 | } 264 | } 265 | 266 | impl USBDeviceBackend for CH375USBDevice { 267 | fn open_nth(vid: u16, pid: u16, nth: usize) -> Result> { 268 | let lib = ensure_library_load()?; 269 | /*HANDLE WINAPI CH375OpenDevice( // Open CH375 device, return the handle, invalid if error 270 | ULONG iIndex ); */ 271 | let open_device: Symbol u32> = 272 | unsafe { lib.get(b"CH375OpenDevice").unwrap() }; 273 | /*VOID WINAPI CH375CloseDevice( // Close the CH375 device 274 | ULONG iIndex ); // Specify the serial number of the CH375 device */ 275 | let close_device: Symbol = 276 | unsafe { lib.get(b"CH375CloseDevice").unwrap() }; 277 | let get_device_descriptor: Symbol< 278 | unsafe extern "stdcall" fn(u32, *mut UsbDeviceDescriptor, *mut u32) -> bool, 279 | > = unsafe { lib.get(b"CH375GetDeviceDescr").unwrap() }; 280 | 281 | const INVALID_HANDLE: u32 = 0xffffffff; 282 | 283 | let mut idx = 0; 284 | for i in 0..8 { 285 | let h = unsafe { open_device(i) }; 286 | if h != INVALID_HANDLE { 287 | let mut descr = unsafe { core::mem::zeroed() }; 288 | let mut len = core::mem::size_of::() as u32; 289 | let _ = unsafe { get_device_descriptor(i, &mut descr, &mut len) }; 290 | 291 | if descr.idVendor == vid && descr.idProduct == pid { 292 | if idx == nth { 293 | log::debug!("Device #{}: {:04x}:{:04x}", i, vid, pid); 294 | return Ok(Box::new(CH375USBDevice { index: i })); 295 | } else { 296 | idx += 1; 297 | } 298 | } 299 | unsafe { close_device(i) }; 300 | } 301 | } 302 | 303 | return Err(crate::Error::ProbeNotFound); 304 | } 305 | 306 | fn read_endpoint(&mut self, ep: u8, buf: &mut [u8]) -> Result { 307 | let lib = ensure_library_load()?; 308 | /* 309 | BOOL WINAPI CH375ReadEndP( // read data block 310 | ULONG iIndex, // Specify the serial number of the CH375 device 311 | ULONG iPipeNum, // Endpoint number, valid values are 1 to 8. 312 | PVOID oBuffer, // Point to a buffer large enough to hold the read data 313 | PULONG ioLength); // Point to the length unit, the length to be read when input, and the actual read length after return 314 | */ 315 | let read_end_point: Symbol< 316 | unsafe extern "stdcall" fn(u32, u32, *mut u8, *mut u32) -> bool, 317 | > = unsafe { lib.get(b"CH375ReadEndP").unwrap() }; 318 | 319 | let mut len = buf.len() as u32; 320 | let ep = (ep & 0x7f) as u32; 321 | 322 | let ret = unsafe { read_end_point(self.index, ep, buf.as_mut_ptr(), &mut len) }; 323 | 324 | if ret { 325 | Ok(len as usize) 326 | } else { 327 | Err(Error::Driver) 328 | } 329 | } 330 | 331 | fn write_endpoint(&mut self, ep: u8, buf: &[u8]) -> Result<()> { 332 | let lib = ensure_library_load()?; 333 | /* 334 | BOOL WINAPI CH375WriteEndP( // write out data block 335 | ULONG iIndex, // Specify the serial number of the CH375 device 336 | ULONG iPipeNum, // Endpoint number, valid values are 1 to 8. 337 | PVOID iBuffer, // Point to a buffer where the data to be written is placed 338 | PULONG ioLength); // Point to the length unit, the length to be written out when input, and the length actually written out after returnF */ 339 | let write_end_point: Symbol< 340 | unsafe extern "stdcall" fn(u32, u32, *mut u8, *mut u32) -> bool, 341 | > = unsafe { lib.get(b"CH375WriteEndP").unwrap() }; 342 | 343 | let mut len = buf.len() as u32; 344 | let ret = unsafe { 345 | write_end_point(self.index, ep as u32, buf.as_ptr() as *mut u8, &mut len) 346 | }; 347 | if ret { 348 | Ok(()) 349 | } else { 350 | Err(Error::Driver) 351 | } 352 | } 353 | 354 | fn set_timeout(&mut self, timeout: Duration) { 355 | let lib = ensure_library_load().unwrap(); 356 | 357 | let set_timeout_ex: Symbol< 358 | unsafe extern "stdcall" fn(u32, u32, u32, u32, u32) -> bool, 359 | > = unsafe { lib.get(b"CH375SetTimeoutEx").unwrap() }; 360 | 361 | let ds = timeout.as_millis() as u32; 362 | 363 | unsafe { 364 | set_timeout_ex(self.index, ds, ds, ds, ds); 365 | } 366 | } 367 | } 368 | 369 | impl Drop for CH375USBDevice { 370 | fn drop(&mut self) { 371 | if let Ok(lib) = ensure_library_load() { 372 | let close_device: Symbol = 373 | unsafe { lib.get(b"CH375CloseDevice").unwrap() }; 374 | unsafe { 375 | close_device(self.index); 376 | } 377 | } 378 | } 379 | } 380 | } 381 | --------------------------------------------------------------------------------