Repository: probe-rs/probe-rs
Pull request #2498 (2024-05-26 11:49:41)

Clean up build script and builtin targets
Simplify registry a tiny bit by always deserializing *something*. The impact should be negligible, and I think the result's simplicity is preferable.

Alternative:
Do some of the cleanups in this PR, but make `bincode` optional via `builtin-targets`. This would not change the library code, but allow stripping out a dependency.

bugadani: I've tried generating rust code from the target descriptions to allow removing `bincode`. Serde is more limited than ideal - it has no knowledge about what type strings and sequences actually are (`&str` vs `String`, slice vs `Vec`, etc.). The result is a 7+ MB rust file that, if incorrect, would probably generate incomprehensible amounts of compile errors, so I've dropped that line of experimentation for now.

Patch:
diff --git a/.github/workflows/changelog.yml b/.github/workflows/changelog.yml
index 382a59a0..91cb8978 100644
--- a/.github/workflows/changelog.yml
+++ b/.github/workflows/changelog.yml
@@ -28,7 +28,6 @@ jobs:
           cache-on-failure: "true"
 
       - name: Check that fragments are valid
-        run:  cargo xtask check-changelog
+        run:  cargo xtask check-changelog --pr ${{ github.event.number }} --comment-error
         env:
           GH_TOKEN: ${{ github.token }}
-          PR: ${{ github.event.number }}
\ No newline at end of file
diff --git a/Cargo.lock b/Cargo.lock
index cc663a40..a032b5ff 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -71,11 +71,11 @@ checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f"
 
 [[package]]
 name = "ansi-parser"
-version = "0.9.0"
+version = "0.9.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fad5bd94a775101bd68c2de2bb28ca2eccd69f395ae3aec4ac4f6da3c1cd2c6a"
+checksum = "c43e7fd8284f025d0bd143c2855618ecdf697db55bde39211e5c9faec7669173"
 dependencies = [
- "heapless 0.6.1",
+ "heapless",
  "nom",
 ]
 
@@ -129,9 +129,9 @@ dependencies = [
 
 [[package]]
 name = "anyhow"
-version = "1.0.83"
+version = "1.0.86"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "25bdb32cbbdce2b519a9cd7df3a678443100e265d5e25ca763b7572a5104f5f3"
+checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da"
 
 [[package]]
 name = "arbitrary"
@@ -148,18 +148,6 @@ version = "0.5.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b"
 
-[[package]]
-name = "as-slice"
-version = "0.1.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "45403b49e3954a4b8428a0ac21a4b7afadccf92bfd96273f1a58cd4812496ae0"
-dependencies = [
- "generic-array 0.12.4",
- "generic-array 0.13.3",
- "generic-array 0.14.7",
- "stable_deref_trait",
-]
-
 [[package]]
 name = "assert_cmd"
 version = "2.0.14"
@@ -177,9 +165,9 @@ dependencies = [
 
 [[package]]
 name = "async-io"
-version = "2.3.2"
+version = "2.3.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dcccb0f599cfa2f8ace422d3555572f47424da5648a4382a9dd0310ff8210884"
+checksum = "0d6baa8f0178795da0e71bc42c9e5d13261aac7ee549853162e66a241ba17964"
 dependencies = [
  "async-lock",
  "cfg-if",
@@ -313,7 +301,7 @@ version = "0.10.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
 dependencies = [
- "generic-array 0.14.7",
+ "generic-array",
 ]
 
 [[package]]
@@ -345,9 +333,9 @@ checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c"
 
 [[package]]
 name = "bytemuck"
-version = "1.15.0"
+version = "1.16.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5d6d68c57235a3a081186990eca2867354726650f42f7516ca50c28d6281fd15"
+checksum = "78834c15cb5d5efe3452d58b1e8ba890dd62d21907f867f383358198e56ebca5"
 dependencies = [
  "bytemuck_derive",
 ]
@@ -381,27 +369,6 @@ version = "1.3.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "a3e368af43e418a04d52505cf3dbc23dda4e3407ae2fa99fd0e4f308ce546acc"
 
-[[package]]
-name = "bzip2"
-version = "0.4.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bdb116a6ef3f6c3698828873ad02c3014b3c85cadb88496095628e3ef1e347f8"
-dependencies = [
- "bzip2-sys",
- "libc",
-]
-
-[[package]]
-name = "bzip2-sys"
-version = "0.1.11+1.0.8"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "736a955f3fa7875102d57c82b8cac37ec45224a07fd32d58f9f7a186b6cd4cdc"
-dependencies = [
- "cc",
- "libc",
- "pkg-config",
-]
-
 [[package]]
 name = "camino"
 version = "1.1.6"
@@ -440,7 +407,7 @@ dependencies = [
  "home",
  "serde",
  "serde_derive",
- "toml_edit 0.22.12",
+ "toml_edit 0.22.13",
 ]
 
 [[package]]
@@ -681,9 +648,9 @@ checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5"
 
 [[package]]
 name = "crc32fast"
-version = "1.4.0"
+version = "1.4.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b3855a8a784b474f333699ef2bbca9db2c4a1f6d9088a90a2d25b1eb53111eaa"
+checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3"
 dependencies = [
  "cfg-if",
 ]
@@ -699,9 +666,9 @@ dependencies = [
 
 [[package]]
 name = "crossbeam-utils"
-version = "0.8.19"
+version = "0.8.20"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345"
+checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80"
 
 [[package]]
 name = "crossterm"
@@ -734,7 +701,7 @@ version = "0.1.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
 dependencies = [
- "generic-array 0.14.7",
+ "generic-array",
  "typenum",
 ]
 
@@ -1051,7 +1018,7 @@ checksum = "59f50b6c32370067087b46087cd5333f2dfe678f0b01223fa70fde6f15449844"
 dependencies = [
  "csv",
  "deku",
- "heapless 0.8.0",
+ "heapless",
  "md5",
  "parse_int",
  "regex",
@@ -1063,9 +1030,9 @@ dependencies = [
 
 [[package]]
 name = "espflash"
-version = "3.0.0"
+version = "3.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "37f8412607744257a0acefddb040ba675ffa66530889e318ded82ad61125c024"
+checksum = "0ae94ed72994aee4c41142198ef915f9c35be9915f0b9c057c49b09bd1b4e099"
 dependencies = [
  "base64",
  "bytemuck",
@@ -1128,9 +1095,9 @@ dependencies = [
 
 [[package]]
 name = "figment"
-version = "0.10.18"
+version = "0.10.19"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d032832d74006f99547004d49410a4b4218e4c33382d56ca3ff89df74f86b953"
+checksum = "8cb01cd46b0cf372153850f4c6c272d9cbea2da513e07538405148f95bd789f3"
 dependencies = [
  "atomic",
  "pear",
@@ -1144,9 +1111,9 @@ dependencies = [
 
 [[package]]
 name = "flate2"
-version = "1.0.28"
+version = "1.0.30"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "46303f565772937ffe1d394a4fac6f411c6013172fadde9dcdb1e147a086940e"
+checksum = "5f54427cfd1c7829e2a139fcefea601bf088ebca651d2bf53ebc600eac295dae"
 dependencies = [
  "crc32fast",
  "miniz_oxide",
@@ -1295,24 +1262,6 @@ dependencies = [
  "paste",
 ]
 
-[[package]]
-name = "generic-array"
-version = "0.12.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ffdf9f34f1447443d37393cc6c2b8313aebddcd96906caf34e54c68d8e57d7bd"
-dependencies = [
- "typenum",
-]
-
-[[package]]
-name = "generic-array"
-version = "0.13.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f797e67af32588215eaaab8327027ee8e71b9dd0b2b26996aedf20c030fce309"
-dependencies = [
- "typenum",
-]
-
 [[package]]
 name = "generic-array"
 version = "0.14.7"
@@ -1381,15 +1330,6 @@ dependencies = [
  "scroll",
 ]
 
-[[package]]
-name = "hash32"
-version = "0.1.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d4041af86e63ac4298ce40e5cca669066e75b6f1aa3390fe2561ffa5e1d9f4cc"
-dependencies = [
- "byteorder",
-]
-
 [[package]]
 name = "hash32"
 version = "0.3.1"
@@ -1409,25 +1349,13 @@ dependencies = [
  "allocator-api2",
 ]
 
-[[package]]
-name = "heapless"
-version = "0.6.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "634bd4d29cbf24424d0a4bfcbf80c6960129dc24424752a7d1d1390607023422"
-dependencies = [
- "as-slice",
- "generic-array 0.14.7",
- "hash32 0.1.1",
- "stable_deref_trait",
-]
-
 [[package]]
 name = "heapless"
 version = "0.8.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "0bfb9eb618601c89945a70e254898da93b13be0388091d42117462b265bb3fad"
 dependencies = [
- "hash32 0.3.1",
+ "hash32",
  "serde",
  "stable_deref_trait",
 ]
@@ -1568,9 +1496,9 @@ dependencies = [
 
 [[package]]
 name = "hyper-util"
-version = "0.1.3"
+version = "0.1.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ca38ef113da30126bbff9cd1705f9273e15d45498615d138b0c20279ac7a76aa"
+checksum = "7b875924a60b96e5d7b9ae7b066540b1dd1cbd90d1828f54c92e02a283351c56"
 dependencies = [
  "bytes",
  "futures-channel",
@@ -1631,12 +1559,6 @@ dependencies = [
  "unicode-width",
 ]
 
-[[package]]
-name = "indoc"
-version = "2.0.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b248f5224d1d606005e02c97f5aa4e88eeb230488bcc03bc9ca4d7991399f2b5"
-
 [[package]]
 name = "inlinable_string"
 version = "0.1.15"
@@ -1645,9 +1567,9 @@ checksum = "c8fae54786f62fb2918dcfae3d568594e50eb9b5c25bf04371af6fe7516452fb"
 
 [[package]]
 name = "insta"
-version = "1.38.0"
+version = "1.39.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3eab73f58e59ca6526037208f0e98851159ec1633cf17b6cd2e1f2c3fd5d53cc"
+checksum = "810ae6042d48e2c9e9215043563a58a80b877bc863228a74cf10c49d4620a6f5"
 dependencies = [
  "lazy_static",
  "linked-hash-map",
@@ -1793,9 +1715,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
 
 [[package]]
 name = "libc"
-version = "0.2.153"
+version = "0.2.155"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd"
+checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c"
 
 [[package]]
 name = "libredox"
@@ -1871,6 +1793,12 @@ dependencies = [
  "scopeguard",
 ]
 
+[[package]]
+name = "lockfree-object-pool"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9374ef4228402d4b7e403e5838cb880d9ee663314b0a900d5a6aabf0c213552e"
+
 [[package]]
 name = "log"
 version = "0.4.21"
@@ -2153,6 +2081,15 @@ dependencies = [
  "ruzstd",
 ]
 
+[[package]]
+name = "object"
+version = "0.36.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "576dfe1fc8f9df304abb159d767a29d0476f7750fbf8aa7ad07816004a207434"
+dependencies = [
+ "memchr",
+]
+
 [[package]]
 name = "once_cell"
 version = "1.19.0"
@@ -2191,9 +2128,9 @@ checksum = "bb813b8af86854136c6922af0598d719255ecb2179515e6e7730d468f05c9cae"
 
 [[package]]
 name = "parking_lot"
-version = "0.12.2"
+version = "0.12.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7e4af0ca4f6caed20e900d564c242b8e5d4903fdacf31d3daf527b66fe6f42fb"
+checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27"
 dependencies = [
  "lock_api",
  "parking_lot_core",
@@ -2385,7 +2322,6 @@ dependencies = [
  "async-io",
  "bincode",
  "bitfield",
- "bitflags 2.5.0",
  "bitvec",
  "clap",
  "docsplay",
@@ -2404,7 +2340,7 @@ dependencies = [
  "jep106",
  "miniz_oxide",
  "nusb",
- "object 0.35.0",
+ "object 0.36.0",
  "once_cell",
  "parking_lot",
  "parse_int",
@@ -2477,7 +2413,7 @@ dependencies = [
  "jep106",
  "libtest-mimic",
  "num-traits",
- "object 0.35.0",
+ "object 0.36.0",
  "once_cell",
  "parking_lot",
  "parse_int",
@@ -2566,21 +2502,21 @@ checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09"
 
 [[package]]
 name = "ratatui"
-version = "0.26.2"
+version = "0.26.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a564a852040e82671dc50a37d88f3aa83bbc690dfc6844cfe7a2591620206a80"
+checksum = "f44c9e68fd46eda15c646fbb85e1040b657a58cdc8c98db1d97a55930d991eef"
 dependencies = [
  "bitflags 2.5.0",
  "cassowary",
  "compact_str",
  "crossterm",
- "indoc",
  "itertools 0.12.1",
  "lru",
  "paste",
  "stability",
  "strum",
  "unicode-segmentation",
+ "unicode-truncate",
  "unicode-width",
 ]
 
@@ -2975,27 +2911,27 @@ dependencies = [
 
 [[package]]
 name = "semver"
-version = "1.0.22"
+version = "1.0.23"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "92d43fe69e652f3df9bdc2b85b2854a0825b86e4fb76bc44d945137d053639ca"
+checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b"
 dependencies = [
  "serde",
 ]
 
 [[package]]
 name = "serde"
-version = "1.0.201"
+version = "1.0.203"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "780f1cebed1629e4753a1a38a3c72d30b97ec044f0aef68cb26650a3c5cf363c"
+checksum = "7253ab4de971e72fb7be983802300c30b5a7f0c2e56fab8abfc6a214307c0094"
 dependencies = [
  "serde_derive",
 ]
 
 [[package]]
 name = "serde_derive"
-version = "1.0.201"
+version = "1.0.203"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c5e405930b9796f1c00bee880d03fc7e0bb4b9a11afc776885ffe84320da2865"
+checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -3035,9 +2971,9 @@ dependencies = [
 
 [[package]]
 name = "serde_spanned"
-version = "0.6.5"
+version = "0.6.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "eb3622f419d1296904700073ea6cc23ad690adbd66f13ea683df73298736f0c1"
+checksum = "79e674e01f999af37c49f70a6ede167a8a60b2503e56c5599532a65baa5969a0"
 dependencies = [
  "serde",
 ]
@@ -3117,6 +3053,12 @@ dependencies = [
  "libc",
 ]
 
+[[package]]
+name = "simd-adler32"
+version = "0.3.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe"
+
 [[package]]
 name = "similar"
 version = "2.5.0"
@@ -3432,18 +3374,18 @@ dependencies = [
 
 [[package]]
 name = "thiserror"
-version = "1.0.60"
+version = "1.0.61"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "579e9083ca58dd9dcf91a9923bb9054071b9ebbd800b342194c9feb0ee89fc18"
+checksum = "c546c80d6be4bc6a00c0f01730c08df82eaa7a7a61f11d656526506112cc1709"
 dependencies = [
  "thiserror-impl",
 ]
 
 [[package]]
 name = "thiserror-impl"
-version = "1.0.60"
+version = "1.0.61"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e2470041c06ec3ac1ab38d0356a6119054dedaea53e12fbefc0de730a1c08524"
+checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -3519,9 +3461,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
 
 [[package]]
 name = "tokio"
-version = "1.37.0"
+version = "1.38.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1adbebffeca75fcfd058afa480fb6c0b81e165a0323f9c9d39c9697e37c46787"
+checksum = "ba4f4a02a7a80d6f274636f0aa95c7e383b912d41fe721a31f29e29698585a4a"
 dependencies = [
  "backtrace",
  "bytes",
@@ -3536,9 +3478,9 @@ dependencies = [
 
 [[package]]
 name = "tokio-macros"
-version = "2.2.0"
+version = "2.3.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b"
+checksum = "5f5ae998a069d4b5aba8ee9dad856af7d520c3699e6159b185c2acd48155d39a"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -3572,21 +3514,21 @@ dependencies = [
 
 [[package]]
 name = "toml"
-version = "0.8.12"
+version = "0.8.13"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e9dd1545e8208b4a5af1aa9bbd0b4cf7e9ea08fabc5d0a5c67fcaafa17433aa3"
+checksum = "a4e43f8cc456c9704c851ae29c67e17ef65d2c30017c17a9765b89c382dc8bba"
 dependencies = [
  "serde",
  "serde_spanned",
  "toml_datetime",
- "toml_edit 0.22.12",
+ "toml_edit 0.22.13",
 ]
 
 [[package]]
 name = "toml_datetime"
-version = "0.6.5"
+version = "0.6.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1"
+checksum = "4badfd56924ae69bcc9039335b2e017639ce3f9b001c393c1b2d1ef846ce2cbf"
 dependencies = [
  "serde",
 ]
@@ -3604,9 +3546,9 @@ dependencies = [
 
 [[package]]
 name = "toml_edit"
-version = "0.22.12"
+version = "0.22.13"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d3328d4f68a705b2a4498da1d580585d39a6510f98318a2cec3018a7ec61ddef"
+checksum = "c127785850e8c20836d49732ae6abfa47616e60bf9d9f57c43c250361a9db96c"
 dependencies = [
  "indexmap",
  "serde",
@@ -3628,7 +3570,6 @@ dependencies = [
  "tokio",
  "tower-layer",
  "tower-service",
- "tracing",
 ]
 
 [[package]]
@@ -3649,7 +3590,6 @@ version = "0.1.40"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef"
 dependencies = [
- "log",
  "pin-project-lite",
  "tracing-attributes",
  "tracing-core",
@@ -3818,6 +3758,16 @@ version = "1.11.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202"
 
+[[package]]
+name = "unicode-truncate"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5a5fbabedabe362c618c714dbefda9927b5afc8e2a8102f47f081089a9019226"
+dependencies = [
+ "itertools 0.12.1",
+ "unicode-width",
+]
+
 [[package]]
 name = "unicode-width"
 version = "0.1.11"
@@ -4293,12 +4243,11 @@ checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d"
 
 [[package]]
 name = "zip"
-version = "1.2.3"
+version = "2.1.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c700ea425e148de30c29c580c1f9508b93ca57ad31c9f4e96b83c194c37a7a8f"
+checksum = "098d5d7737fb0b70814faa73c17df84f047d38dd31d13bbf2ec3fb354b5abf45"
 dependencies = [
  "arbitrary",
- "bzip2",
  "crc32fast",
  "crossbeam-utils",
  "deflate64",
@@ -4306,11 +4255,27 @@ dependencies = [
  "flate2",
  "indexmap",
  "lzma-rs",
+ "memchr",
  "thiserror",
  "time",
+ "zopfli",
  "zstd",
 ]
 
+[[package]]
+name = "zopfli"
+version = "0.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e5019f391bac5cf252e93bbcc53d039ffd62c7bfb7c150414d61369afe57e946"
+dependencies = [
+ "bumpalo",
+ "crc32fast",
+ "lockfree-object-pool",
+ "log",
+ "once_cell",
+ "simd-adler32",
+]
+
 [[package]]
 name = "zstd"
 version = "0.13.1"
diff --git a/changelog/added-support-for-cc13x4-cc26x4-devices.md b/changelog/added-support-for-cc13x4-cc26x4-devices.md
new file mode 100644
index 00000000..fd27f05a
--- /dev/null
+++ b/changelog/added-support-for-cc13x4-cc26x4-devices.md
@@ -0,0 +1,1 @@
+TI: Added support for the cc13x4_cc26x4 family of devices
diff --git a/changelog/added-vendor.md b/changelog/added-vendor.md
new file mode 100644
index 00000000..e4be23df
--- /dev/null
+++ b/changelog/added-vendor.md
@@ -0,0 +1,1 @@
+Added the option to register new debug sequences. To do this, users must implement `probe_rs::vendor::Vendor` and register it using `probe_rs::vendor::register_vendor()`.
diff --git a/changelog/changed-debug-sequence-path.md b/changelog/changed-debug-sequence-path.md
new file mode 100644
index 00000000..85c1c4a4
--- /dev/null
+++ b/changelog/changed-debug-sequence-path.md
@@ -0,0 +1,1 @@
+Debug sequences are now available at `probe_rs::vendor::<VENDOR>::sequences`
\ No newline at end of file
diff --git a/changelog/fixed-cargo-embed-log.md b/changelog/fixed-cargo-embed-log.md
new file mode 100644
index 00000000..1960fdde
--- /dev/null
+++ b/changelog/fixed-cargo-embed-log.md
@@ -0,0 +1,1 @@
+Use `RUST_LOG` as the log filter if `Embed.toml` does not specify a level.
\ No newline at end of file
diff --git a/changelog/fixed-multiple-chip-erase.md b/changelog/fixed-multiple-chip-erase.md
new file mode 100644
index 00000000..7efb37fe
--- /dev/null
+++ b/changelog/fixed-multiple-chip-erase.md
@@ -0,0 +1,1 @@
+Fixed an issue that could cause the chip to be erased in the middle of the flashing process.
\ No newline at end of file
diff --git a/changelog/fixed-multiple-progress-bars.md b/changelog/fixed-multiple-progress-bars.md
new file mode 100644
index 00000000..5bfbab42
--- /dev/null
+++ b/changelog/fixed-multiple-progress-bars.md
@@ -0,0 +1,1 @@
+Fixed progress bar display issues when programming multiple memory regions.
\ No newline at end of file
diff --git a/changelog/removed-tracing-log.md b/changelog/removed-tracing-log.md
new file mode 100644
index 00000000..d4d35a97
--- /dev/null
+++ b/changelog/removed-tracing-log.md
@@ -0,0 +1,1 @@
+probe-rs should no longer emit `log` events via `tracing`. You can re-enable this by adding `tracing = { version = "0.1", features = ["log"] }` to your crate's Cargo.toml.
\ No newline at end of file
diff --git a/probe-rs-target/src/memory.rs b/probe-rs-target/src/memory.rs
index defd2092..771a67b6 100644
--- a/probe-rs-target/src/memory.rs
+++ b/probe-rs-target/src/memory.rs
@@ -66,6 +66,13 @@ pub struct SectorInfo {
     pub size: u64,
 }
 
+impl SectorInfo {
+    /// Returns the address range of the sector.
+    pub fn address_range(&self) -> Range<u64> {
+        self.base_address..self.base_address + self.size
+    }
+}
+
 /// Information about a group of flash sectors, which
 /// is used as part of the [`FlashProperties`] struct.
 ///
@@ -96,6 +103,13 @@ pub struct PageInfo {
     pub size: u32,
 }
 
+impl PageInfo {
+    /// Returns the address range of the sector.
+    pub fn address_range(&self) -> Range<u64> {
+        self.base_address..self.base_address + self.size as u64
+    }
+}
+
 /// Holds information about the entire flash.
 #[derive(Debug, Copy, Clone)]
 pub struct NvmInfo {
diff --git a/probe-rs-tools/Cargo.toml b/probe-rs-tools/Cargo.toml
index d1c9331a..f19abcc3 100644
--- a/probe-rs-tools/Cargo.toml
+++ b/probe-rs-tools/Cargo.toml
@@ -26,7 +26,7 @@ gimli = { version = "0.29", default-features = false, features = [
 jep106 = "0.2"
 once_cell = "1"
 num-traits = "0.2"
-object = { version = "0.35", default-features = false, features = [
+object = { version = "0.36", default-features = false, features = [
     "elf",
     "read_core",
     "std",
@@ -34,7 +34,7 @@ object = { version = "0.35", default-features = false, features = [
 scroll = "0.12"
 serde = { version = "1", features = ["derive"] }
 thiserror = { workspace = true }
-tracing = { version = "0.1", features = ["log"] }
+tracing = "0.1"
 typed-path = "0.8"
 # path
 probe-rs-target = { workspace = true }
@@ -91,13 +91,9 @@ cargo-config2 = "0.1.26"
 clap_complete = "4.5.2"
 regex = "1.10.4"
 semver = "1"
-zip = { version = "1.1.4", default-features = false, features = [
-    "bzip2",
-    "deflate64",
+zip = { version = "2.0.0", default-features = false, features = [
     "deflate",
-    "lzma",
     "time",
-    "zstd",
 ] }
 urlencoding = "2"
 
diff --git a/probe-rs-tools/src/bin/probe-rs/cmd/cargo_embed/config/default.toml b/probe-rs-tools/src/bin/probe-rs/cmd/cargo_embed/config/default.toml
index 8aac40bd..43afd745 100644
--- a/probe-rs-tools/src/bin/probe-rs/cmd/cargo_embed/config/default.toml
+++ b/probe-rs-tools/src/bin/probe-rs/cmd/cargo_embed/config/default.toml
@@ -39,7 +39,8 @@ halt_afterwards = false
 chip_descriptions = []
 # The default log level to be used. Possible values are one of:
 #   "OFF", "ERROR", "WARN", "INFO", "DEBUG", "TRACE"
-log_level = "WARN"
+#   If not set, the `RUST_LOG` environment variable will be used.
+# log_level = "WARN"
 # Use this flag to assert the nreset & ntrst pins during attaching the probe to the chip.
 connect_under_reset = false
 
diff --git a/probe-rs-tools/src/bin/probe-rs/cmd/dap_server/server/debugger.rs b/probe-rs-tools/src/bin/probe-rs/cmd/dap_server/server/debugger.rs
index b47ab62f..ff6e6d32 100644
--- a/probe-rs-tools/src/bin/probe-rs/cmd/dap_server/server/debugger.rs
+++ b/probe-rs-tools/src/bin/probe-rs/cmd/dap_server/server/debugger.rs
@@ -679,18 +679,26 @@ impl Debugger {
                 let mut flash_progress = progress_state.borrow_mut();
                 let mut debug_adapter = rc_debug_adapter_clone.borrow_mut();
                 match event {
-                    ProgressEvent::Initialized { flash_layout } => {
-                        flash_progress.total_page_size =
-                            flash_layout.pages().iter().map(|s| s.size() as usize).sum();
-
-                        flash_progress.total_sector_size = flash_layout
-                            .sectors()
-                            .iter()
-                            .map(|s| s.size() as usize)
-                            .sum();
-
-                        flash_progress.total_fill_size =
-                            flash_layout.fills().iter().map(|s| s.size() as usize).sum();
+                    ProgressEvent::Initialized { phases, .. } => {
+                        for phase_layout in phases {
+                            flash_progress.total_page_size += phase_layout
+                                .pages()
+                                .iter()
+                                .map(|s| s.size() as usize)
+                                .sum::<usize>();
+
+                            flash_progress.total_sector_size += phase_layout
+                                .sectors()
+                                .iter()
+                                .map(|s| s.size() as usize)
+                                .sum::<usize>();
+
+                            flash_progress.total_fill_size += phase_layout
+                                .fills()
+                                .iter()
+                                .map(|s| s.size() as usize)
+                                .sum::<usize>();
+                        }
                     }
                     ProgressEvent::StartedFilling => {
                         debug_adapter
diff --git a/probe-rs-tools/src/bin/probe-rs/cmd/erase.rs b/probe-rs-tools/src/bin/probe-rs/cmd/erase.rs
index e7685295..15e8b0b4 100644
--- a/probe-rs-tools/src/bin/probe-rs/cmd/erase.rs
+++ b/probe-rs-tools/src/bin/probe-rs/cmd/erase.rs
@@ -1,4 +1,7 @@
-use probe_rs::{flashing::erase_all, probe::list::Lister};
+use probe_rs::{
+    flashing::{erase_all, FlashProgress},
+    probe::list::Lister,
+};
 
 use crate::util::common_options::ProbeOptions;
 
@@ -12,7 +15,7 @@ impl Cmd {
     pub fn run(self, lister: &Lister) -> anyhow::Result<()> {
         let (mut session, _probe_options) = self.common.simple_attach(lister)?;
 
-        erase_all(&mut session, None)?;
+        erase_all(&mut session, FlashProgress::empty())?;
 
         Ok(())
     }
diff --git a/probe-rs-tools/src/bin/probe-rs/cmd/info.rs b/probe-rs-tools/src/bin/probe-rs/cmd/info.rs
index e1ef58d8..f0094047 100644
--- a/probe-rs-tools/src/bin/probe-rs/cmd/info.rs
+++ b/probe-rs-tools/src/bin/probe-rs/cmd/info.rs
@@ -21,7 +21,6 @@ use probe_rs::{
             XtensaCommunicationInterface, XtensaDebugInterfaceState,
         },
     },
-    config::sequences::atsam,
     probe::{list::Lister, Probe, WireProtocol},
     MemoryMappedRegister,
 };
@@ -467,7 +466,7 @@ fn process_vendor_rom_tables(
     };
 
     if part_info.peripheral_type() == PeripheralType::Custom && part_info.name() == "Atmel DSU" {
-        use atsam::DsuDid;
+        use probe_rs::vendor::microchip::sequences::atsam::DsuDid;
 
         // Read and parse the DID register
         let did = DsuDid(
diff --git a/probe-rs-tools/src/bin/probe-rs/util/flash.rs b/probe-rs-tools/src/bin/probe-rs/util/flash.rs
index fe1e0acb..26bb60d2 100644
--- a/probe-rs-tools/src/bin/probe-rs/util/flash.rs
+++ b/probe-rs-tools/src/bin/probe-rs/util/flash.rs
@@ -3,12 +3,14 @@ use crate::FormatOptions;
 use super::common_options::{BinaryDownloadOptions, LoadedProbeOptions, OperationError};
 use super::logging;
 
+use std::cell::RefCell;
 use std::fs::File;
 use std::time::Duration;
 use std::{path::Path, time::Instant};
 
 use colored::Colorize;
 use indicatif::{MultiProgress, ProgressBar, ProgressStyle};
+use probe_rs::flashing::FlashLayout;
 use probe_rs::InstructionSet;
 use probe_rs::{
     flashing::{DownloadOptions, FileDownloadError, FlashLoader, FlashProgress, ProgressEvent},
@@ -17,13 +19,6 @@ use probe_rs::{
 
 use anyhow::Context;
 
-fn init_progress_bar(bar: &ProgressBar) {
-    let style = bar.style().progress_chars("##-");
-    bar.set_style(style);
-    bar.enable_steady_tick(Duration::from_millis(100));
-    bar.reset_elapsed();
-}
-
 /// Performs the flash download with the given loader. Ensure that the loader has the data to load already stored.
 /// This function also manages the update and display of progress bars.
 pub fn run_flash_download(
@@ -34,9 +29,6 @@ pub fn run_flash_download(
     loader: FlashLoader,
     do_chip_erase: bool,
 ) -> Result<(), OperationError> {
-    // Start timer.
-    let instant = Instant::now();
-
     let mut options = DownloadOptions::default();
     options.keep_unwritten_bytes = download_options.restore_unwritten;
     options.dry_run = probe_options.dry_run();
@@ -49,100 +41,98 @@ pub fn run_flash_download(
         let multi_progress = MultiProgress::new();
         logging::set_progress_bar(multi_progress.clone());
 
-        let style = ProgressStyle::default_bar()
-                    .tick_chars("⠁⠁⠉⠙⠚⠒⠂⠂⠒⠲⠴⠤⠄⠄⠤⠠⠠⠤⠦⠖⠒⠐⠐⠒⠓⠋⠉⠈⠈✔")
-                    .progress_chars("--")
-                    .template("{msg:.green.bold} {spinner} [{elapsed_precise}] [{wide_bar}] {bytes:>8}/{total_bytes:>8} @ {bytes_per_sec:>10} (eta {eta:3})").expect("Error in progress bar creation. This is a bug, please report it.");
-
-        // Create a new progress bar for the fill progress if filling is enabled.
-        let fill_progress = if download_options.restore_unwritten {
-            let fill_progress = multi_progress.add(ProgressBar::new(0));
-            fill_progress.set_style(style.clone());
-            fill_progress.set_message("Reading flash");
-            Some(fill_progress)
-        } else {
-            None
-        };
-
-        // Create a new progress bar for the erase progress.
-        let erase_progress = multi_progress.add(ProgressBar::new(0));
-        erase_progress.set_style(style.clone());
-        erase_progress.set_message("      Erasing");
-
-        // Create a new progress bar for the program progress.
-        let program_progress = multi_progress.add(ProgressBar::new(0));
-        program_progress.set_style(style);
-        program_progress.set_message("  Programming");
+        let progress_bars = RefCell::new(ProgressBars {
+            erase: ProgressBarGroup::new("      Erasing"),
+            fill: ProgressBarGroup::new("Reading flash"),
+            program: ProgressBarGroup::new("  Programming"),
+        });
 
         // Register callback to update the progress.
         let flash_layout_output_path = download_options.flash_layout_output_path.clone();
-        let progress = FlashProgress::new(move |event| match event {
-            ProgressEvent::Initialized { flash_layout } => {
-                if let Some(fp) = fill_progress.as_ref() {
-                    let total_fill_size: u64 = flash_layout.fills().iter().map(|s| s.size()).sum();
-                    fp.set_length(total_fill_size);
-                }
+        let progress = FlashProgress::new(move |event| {
+            let mut progress_bars = progress_bars.borrow_mut();
 
-                let total_sector_size: u64 = flash_layout.sectors().iter().map(|s| s.size()).sum();
-                erase_progress.set_length(total_sector_size);
+            match event {
+                ProgressEvent::Initialized {
+                    chip_erase,
+                    phases,
+                    restore_unwritten,
+                } => {
+                    // Build progress bars.
+                    if chip_erase {
+                        progress_bars
+                            .erase
+                            .add(multi_progress.add(ProgressBar::new(0)));
+                    }
 
-                let visualizer = flash_layout.visualize();
-                flash_layout_output_path
-                    .as_ref()
-                    .map(|path| visualizer.write_svg(path));
-            }
-            ProgressEvent::StartedProgramming { length } => {
-                init_progress_bar(&program_progress);
-                program_progress.set_length(length);
-            }
-            ProgressEvent::StartedErasing => {
-                init_progress_bar(&erase_progress);
-            }
-            ProgressEvent::StartedFilling => {
-                if let Some(fp) = fill_progress.as_ref() {
-                    init_progress_bar(fp);
+                    if phases.len() > 1 {
+                        progress_bars.erase.append_phase();
+                        progress_bars.program.append_phase();
+                        progress_bars.fill.append_phase();
+                    }
+
+                    let mut flash_layout = FlashLayout::default();
+                    for phase_layout in phases {
+                        if restore_unwritten {
+                            let fill_size =
+                                flash_layout.fills().iter().map(|s| s.size()).sum::<u64>();
+                            progress_bars
+                                .fill
+                                .add(multi_progress.add(ProgressBar::new(fill_size)));
+                        }
+
+                        if !chip_erase {
+                            let sector_size =
+                                flash_layout.sectors().iter().map(|s| s.size()).sum::<u64>();
+                            progress_bars
+                                .erase
+                                .add(multi_progress.add(ProgressBar::new(sector_size)));
+                        }
+
+                        progress_bars
+                            .program
+                            .add(multi_progress.add(ProgressBar::new(0)));
+
+                        flash_layout.merge_from(phase_layout);
+                    }
+
+                    // TODO: progress bar for verifying?
+
+                    // Visualise flash layout to file if requested.
+                    let visualizer = flash_layout.visualize();
+                    flash_layout_output_path
+                        .as_ref()
+                        .map(|path| visualizer.write_svg(path));
                 }
-            }
-            ProgressEvent::PageProgrammed { size, .. } => {
-                program_progress.inc(size as u64);
-            }
-            ProgressEvent::SectorErased { size, .. } => {
-                erase_progress.inc(size);
-            }
-            ProgressEvent::PageFilled { size, .. } => {
-                if let Some(fp) = fill_progress.as_ref() {
-                    fp.inc(size);
+                ProgressEvent::StartedProgramming { length } => {
+                    progress_bars.program.set_length(length);
                 }
-            }
-            ProgressEvent::FailedErasing => {
-                erase_progress.abandon();
-                program_progress.abandon();
-            }
-            ProgressEvent::FinishedErasing => {
-                erase_progress.finish();
-            }
-            ProgressEvent::FailedProgramming => {
-                program_progress.abandon();
-            }
-            ProgressEvent::FinishedProgramming => {
-                program_progress.finish();
-            }
-            ProgressEvent::FailedFilling => {
-                if let Some(fp) = fill_progress.as_ref() {
-                    fp.abandon();
+                ProgressEvent::StartedErasing => {}
+                ProgressEvent::StartedFilling => {}
+                ProgressEvent::PageProgrammed { size, .. } => {
+                    progress_bars.program.inc(size as u64);
                 }
-            }
-            ProgressEvent::FinishedFilling => {
-                if let Some(fp) = fill_progress.as_ref() {
-                    fp.finish();
+                ProgressEvent::SectorErased { size, .. } => progress_bars.erase.inc(size),
+                ProgressEvent::PageFilled { size, .. } => progress_bars.fill.inc(size),
+                ProgressEvent::FailedErasing => {
+                    progress_bars.erase.abandon();
+                    progress_bars.program.abandon();
                 }
+                ProgressEvent::FinishedErasing => progress_bars.erase.finish(),
+                ProgressEvent::FailedProgramming => progress_bars.program.abandon(),
+                ProgressEvent::FinishedProgramming => progress_bars.program.finish(),
+                ProgressEvent::FailedFilling => progress_bars.fill.abandon(),
+                ProgressEvent::FinishedFilling => progress_bars.fill.finish(),
+                ProgressEvent::DiagnosticMessage { .. } => {}
             }
-            ProgressEvent::DiagnosticMessage { .. } => (),
         });
 
         options.progress = Some(progress);
     }
 
+    // Start timer.
+    let flash_timer = Instant::now();
+
     loader
         .commit(session, options)
         .map_err(|error| OperationError::FlashingFailed {
@@ -155,12 +145,10 @@ pub fn run_flash_download(
     // If we don't do this, the progress bars disappear.
     logging::clear_progress_bar();
 
-    // Stop timer.
-    let elapsed = instant.elapsed();
     logging::eprintln(format!(
         "    {} in {}s",
         "Finished".green().bold(),
-        elapsed.as_millis() as f32 / 1000.0,
+        flash_timer.elapsed().as_secs_f32(),
     ));
 
     Ok(())
@@ -189,3 +177,83 @@ pub fn build_loader(
 
     Ok(loader)
 }
+
+struct ProgressBars {
+    erase: ProgressBarGroup,
+    fill: ProgressBarGroup,
+    program: ProgressBarGroup,
+}
+
+struct ProgressBarGroup {
+    message: String,
+    bars: Vec<ProgressBar>,
+    selected: usize,
+    append_phase: bool,
+}
+
+impl ProgressBarGroup {
+    fn new(message: &str) -> Self {
+        Self {
+            message: message.to_string(),
+            bars: vec![],
+            selected: 0,
+            append_phase: false,
+        }
+    }
+
+    fn add(&mut self, bar: ProgressBar) {
+        let msg_template = "{msg:.green.bold} {spinner} [{elapsed_precise}] [{wide_bar}] {bytes:>8}/{total_bytes:>8} @ {bytes_per_sec:>10} (eta {eta:3})";
+        let style = ProgressStyle::default_bar()
+            .tick_chars("⠁⠁⠉⠙⠚⠒⠂⠂⠒⠲⠴⠤⠄⠄⠤⠠⠠⠤⠦⠖⠒⠐⠐⠒⠓⠋⠉⠈⠈✔")
+            .progress_chars("--")
+            .template(msg_template)
+            .expect("Error in progress bar creation. This is a bug, please report it.");
+
+        if self.append_phase {
+            bar.set_message(format!("{} {}", self.message, self.bars.len() + 1));
+        } else {
+            bar.set_message(self.message.clone());
+        }
+        bar.set_style(style);
+        bar.enable_steady_tick(Duration::from_millis(100));
+        bar.reset_elapsed();
+
+        self.bars.push(bar);
+    }
+
+    fn set_length(&mut self, length: u64) {
+        if let Some(bar) = self.bars.get(self.selected) {
+            bar.set_length(length);
+        }
+    }
+
+    fn inc(&mut self, size: u64) {
+        if let Some(bar) = self.bars.get(self.selected) {
+            let style = bar.style().progress_chars("##-");
+            bar.set_style(style);
+            bar.inc(size);
+        }
+    }
+
+    fn abandon(&mut self) {
+        if let Some(bar) = self.bars.get(self.selected) {
+            bar.abandon();
+        }
+        self.next();
+    }
+
+    fn finish(&mut self) {
+        if let Some(bar) = self.bars.get(self.selected) {
+            bar.finish();
+        }
+        self.next();
+    }
+
+    fn next(&mut self) {
+        self.selected += 1;
+    }
+
+    fn append_phase(&mut self) {
+        self.append_phase = true;
+    }
+}
diff --git a/probe-rs-tools/src/bin/probe-rs/util/logging.rs b/probe-rs-tools/src/bin/probe-rs/util/logging.rs
index 5574cc98..ae2d009c 100644
--- a/probe-rs-tools/src/bin/probe-rs/util/logging.rs
+++ b/probe-rs-tools/src/bin/probe-rs/util/logging.rs
@@ -1,14 +1,15 @@
 use indicatif::MultiProgress;
 use once_cell::sync::Lazy;
+use parking_lot::Mutex;
 use serde::{Deserialize, Serialize};
-use std::{fs::File, path::Path, sync::RwLock};
+use std::{fs::File, path::Path};
 use tracing_appender::non_blocking::WorkerGuard;
 use tracing_subscriber::{
     fmt::format::FmtSpan, layer::SubscriberExt, util::SubscriberInitExt, EnvFilter, Layer,
 };
 
 /// Stores the progress bar for the logging facility.
-static PROGRESS_BAR: Lazy<RwLock<Option<MultiProgress>>> = Lazy::new(|| RwLock::new(None));
+static PROGRESS_BAR: Lazy<Mutex<Option<MultiProgress>>> = Lazy::new(|| Mutex::new(None));
 
 pub struct FileLoggerGuard<'a> {
     _append_guard: WorkerGuard,
@@ -146,42 +147,40 @@ pub fn setup_logging(
 
 /// Sets the currently displayed progress bar of the CLI.
 pub fn set_progress_bar(progress: MultiProgress) {
-    let mut guard = PROGRESS_BAR.write().unwrap();
-    *guard = Some(progress);
+    *PROGRESS_BAR.lock() = Some(progress);
 }
 
 /// Disables the currently displayed progress bar of the CLI.
 pub fn clear_progress_bar() {
-    let mut guard = PROGRESS_BAR.write().unwrap();
-    *guard = None;
+    *PROGRESS_BAR.lock() = None;
 }
 
 /// Writes an error to stderr.
 /// This function respects the progress bars of the CLI that might be displayed and displays the message above it if any are.
 pub fn eprintln(message: impl AsRef<str>) {
-    if let Ok(guard) = PROGRESS_BAR.try_write() {
-        match guard.as_ref() {
+    fn inner(message: &str) {
+        let locked = PROGRESS_BAR.lock();
+        match locked.as_ref() {
             Some(pb) => {
-                let _ = pb.println(message.as_ref());
+                let _ = pb.println(message);
             }
-            _ => eprintln!("{}", message.as_ref()),
+            None => eprintln!("{message}"),
         }
-    } else {
-        eprintln!("{}", message.as_ref());
     }
+    inner(message.as_ref())
 }
 
 /// Writes a message to stdout with a newline at the end.
 /// This function respects the progress bars of the CLI that might be displayed and displays the message above it if any are.
 pub fn println(message: impl AsRef<str>) {
-    if let Ok(guard) = PROGRESS_BAR.try_write() {
-        match guard.as_ref() {
+    fn inner(message: &str) {
+        let locked = PROGRESS_BAR.lock();
+        match locked.as_ref() {
             Some(pb) => {
-                let _ = pb.println(message.as_ref());
+                let _ = pb.println(message);
             }
-            _ => println!("{}", message.as_ref()),
+            None => println!("{message}"),
         }
-    } else {
-        println!("{}", message.as_ref());
     }
+    inner(message.as_ref())
 }
diff --git a/probe-rs/Cargo.toml b/probe-rs/Cargo.toml
index 9fa907e2..10b20bcf 100644
--- a/probe-rs/Cargo.toml
+++ b/probe-rs/Cargo.toml
@@ -35,9 +35,8 @@ docsplay.workspace = true
 thiserror.workspace = true
 probe-rs-target.workspace = true
 
-bincode = "1.3"
+bincode = "1"
 bitfield = "0.15"
-bitflags = "2"
 bitvec = "1"
 gimli = { version = "0.29", default-features = false, features = [
     "endian-reader",
@@ -52,7 +51,7 @@ itertools = "0.13"
 jep106 = "0.2"
 once_cell = "1"
 miniz_oxide = "0.7"
-object = { version = "0.35", default-features = false, features = [
+object = { version = "0.36", default-features = false, features = [
     "elf",
     "read_core",
     "std",
@@ -62,7 +61,7 @@ futures-lite = { version = "2", default-features = false }
 async-io = "2"
 scroll = "0.12"
 svg = "0.17"
-tracing = { version = "0.1", features = ["log"] }
+tracing = "0.1"
 uf2-decode = "0.2"
 typed-path = "0.8"
 espflash = { version = "3", default-features = false }
@@ -108,8 +107,3 @@ replace = "## [{{version}}]\n\nReleased {{date}}"
 file = "../CHANGELOG.md"
 search = "\\[unreleased\\]: https://github.com/probe-rs/probe-rs/compare/v([a-z0-9.-]+)\\.\\.\\.master"
 replace = "[unreleased]: https://github.com/probe-rs/probe-rs/compare/v{{version}}...master\n[{{version}}]: https://github.com/probe-rs/probe-rs/compare/v$1...v{{version}}"
-
-[package.metadata.binstall]
-pkg-url = "{ repo }/releases/download/v{ version }/probe-rs-tools-{ target }-v{ version }{ archive-suffix }"
-bin-dir = "probe-rs-tools-{ target }-v{ version }/{ bin }{ binary-ext }"
-pkg-fmt = "tgz"
diff --git a/probe-rs/build.rs b/probe-rs/build.rs
index 6fce9a2f..8826c14c 100644
--- a/probe-rs/build.rs
+++ b/probe-rs/build.rs
@@ -1,7 +1,7 @@
 use std::env;
 use std::fs::{read_dir, read_to_string};
 use std::io;
-use std::path::{Path, PathBuf};
+use std::path::Path;
 
 use probe_rs_target::ChipFamily;
 
@@ -15,37 +15,33 @@ fn main() {
     println!("cargo:rerun-if-changed=targets");
     println!("cargo:rerun-if-env-changed=PROBE_RS_TARGETS_DIR");
 
+    let mut families = Vec::new();
+
     // Test if we have to generate built-in targets
-    if env::var("CARGO_FEATURE_BUILTIN_TARGETS").is_err() {
-        return;
-    }
+    if env::var("CARGO_FEATURE_BUILTIN_TARGETS").is_ok() {
+        let mut process_target_yaml = |file: &Path| {
+            let string = read_to_string(file).unwrap_or_else(|error| {
+                panic!(
+                    "Failed to read target file {} because:\n{error}",
+                    file.display()
+                )
+            });
 
-    let mut families: Vec<ChipFamily> = Vec::new();
+            match serde_yaml::from_str::<ChipFamily>(&string) {
+                Ok(family) => families.push(family),
+                Err(error) => panic!(
+                    "Failed to parse target file: {} because:\n{error}",
+                    file.display()
+                ),
+            }
+        };
 
-    let mut files = vec![];
-    visit_dirs(Path::new("targets"), &mut files).unwrap();
+        visit_dirs("targets", &mut process_target_yaml).unwrap();
 
-    // Check if there are any additional targets to generate for
-    match env::var("PROBE_RS_TARGETS_DIR") {
-        Ok(additional_target_dir) => {
+        // Check if there are any additional targets to generate for
+        if let Ok(additional_target_dir) = env::var("PROBE_RS_TARGETS_DIR") {
             println!("cargo:rerun-if-changed={additional_target_dir}");
-            visit_dirs(Path::new(&additional_target_dir), &mut files).unwrap();
-        }
-        Err(_err) => {
-            // Do nothing as you dont have to add any other targets
-        }
-    }
-
-    for file in files {
-        let string = read_to_string(&file).expect(
-            "Algorithm definition file could not be read. This is a bug. Please report it.",
-        );
-
-        let yaml: Result<ChipFamily, _> = serde_yaml::from_str(&string);
-
-        match yaml {
-            Ok(familiy) => families.push(familiy),
-            Err(e) => panic!("Failed to parse target file: {file:?} because:\n{e}"),
+            visit_dirs(additional_target_dir, &mut process_target_yaml).unwrap();
         }
     }
 
@@ -56,28 +52,35 @@ fn main() {
     let dest_path = Path::new(&out_dir).join("targets.bincode");
     std::fs::write(dest_path, &families_bin).unwrap();
 
-    let _: Vec<ChipFamily> = match bincode::deserialize(&families_bin) {
-        Ok(chip_families) => chip_families,
-        Err(deserialize_error) => panic!(
+    // Check if we can deserialize the bincode again, otherwise the binary will not be usable.
+    if let Err(deserialize_error) = bincode::deserialize::<Vec<ChipFamily>>(&families_bin) {
+        panic!(
             "Failed to deserialize supported target definitions from bincode: {deserialize_error:?}"
-        ),
-    };
+        );
+    }
 }
 
-/// One possible implementation of walking a directory only visiting files.
-fn visit_dirs(dir: &Path, targets: &mut Vec<PathBuf>) -> io::Result<()> {
-    if dir.is_dir() {
+/// Call `process` on all files in a directory and its subdirectories.
+fn visit_dirs(dir: impl AsRef<Path>, process: &mut impl FnMut(&Path)) -> io::Result<()> {
+    // Inner function to avoid generating multiple implementations for the different path types.
+    fn visit_dirs_impl(dir: &Path, process: &mut impl FnMut(&Path)) -> io::Result<()> {
         for entry in read_dir(dir)? {
             let entry = entry?;
             let path = entry.path();
             if path.is_dir() {
-                visit_dirs(&path, targets)?;
-            } else if let Some(extension) = path.extension() {
-                if extension.eq_ignore_ascii_case("yaml") {
-                    targets.push(path);
-                }
+                visit_dirs_impl(&path, process)?;
+            } else {
+                process(&path);
             }
         }
+
+        Ok(())
+    }
+
+    let dir = dir.as_ref();
+    if !dir.is_dir() {
+        return Ok(());
     }
-    Ok(())
+
+    visit_dirs_impl(dir, process)
 }
diff --git a/probe-rs/src/architecture/arm/memory/romtable.rs b/probe-rs/src/architecture/arm/memory/romtable.rs
index e568e2e2..7d069d59 100644
--- a/probe-rs/src/architecture/arm/memory/romtable.rs
+++ b/probe-rs/src/architecture/arm/memory/romtable.rs
@@ -77,7 +77,7 @@ impl<'probe, 'memory, 'reader> Iterator for RomTableIterator<'probe, 'memory, 'r
 
     fn next(&mut self) -> Option<Self::Item> {
         let component_address = self.rom_table_reader.base_address + self.offset;
-        tracing::info!("Reading rom table entry at {:08x}", component_address);
+        tracing::debug!("Reading rom table entry at {:#010x}", component_address);
 
         self.offset += 4;
 
@@ -93,14 +93,14 @@ impl<'probe, 'memory, 'reader> Iterator for RomTableIterator<'probe, 'memory, 'r
 
         // End of entries is marked by an all zero entry
         if entry_data[0] == 0 {
-            tracing::info!("Entry consists of all zeroes, stopping.");
+            tracing::debug!("Entry consists of all zeroes, stopping.");
             return None;
         }
 
         let entry_data =
             RomTableEntryRaw::new(self.rom_table_reader.base_address as u32, entry_data[0]);
 
-        tracing::info!("ROM Table Entry: {:#x?}", entry_data);
+        tracing::debug!("ROM Table Entry: {:#x?}", entry_data);
         Some(Ok(entry_data))
     }
 }
@@ -122,7 +122,7 @@ impl RomTable {
         // This is required for the collect down below.
         let mut entries = vec![];
 
-        tracing::info!("Parsing romtable at base_address {:x?}", base_address);
+        tracing::debug!("Parsing romtable at base_address {:#010x}", base_address);
 
         // Read all the raw romtable entries and flatten them.
 
@@ -136,7 +136,7 @@ impl RomTable {
         for raw_entry in reader.into_iter() {
             let entry_base_addr = raw_entry.component_address();
 
-            tracing::info!("Parsing entry at {:x?}", entry_base_addr);
+            tracing::debug!("Parsing entry at {:#010x}", entry_base_addr);
 
             if raw_entry.entry_present {
                 let component = Component::try_parse(memory, u64::from(entry_base_addr))?;
@@ -190,7 +190,7 @@ struct RomTableEntryRaw {
 impl RomTableEntryRaw {
     /// Create a new RomTableEntryRaw from raw ROM table entry data in memory.
     fn new(base_address: u32, raw: u32) -> Self {
-        tracing::debug!("Parsing raw rom table entry: 0x{:05x}", raw);
+        tracing::debug!("Parsing raw rom table entry: {:#07x}", raw);
 
         let address_offset = ((raw >> 12) & 0xf_ff_ff) as i32;
         let power_domain_id = ((raw >> 4) & 0xf) as u8;
@@ -437,21 +437,21 @@ impl Component {
         memory: &'memory mut (dyn ArmProbe + 'probe),
         baseaddr: u64,
     ) -> Result<Component, RomTableError> {
-        tracing::info!("\tReading component data at: {:08x}", baseaddr);
+        tracing::debug!("\tReading component data at: {:#010x}", baseaddr);
 
         let component_id = ComponentInformationReader::new(baseaddr, memory).read_all()?;
 
         // Determine the component class to find out what component we are dealing with.
-        tracing::info!("\tComponent class: {:x?}", component_id.class);
+        tracing::debug!("\tComponent class: {:x?}", component_id.class);
 
         // Determine the peripheral id to find out what peripheral we are dealing with.
-        tracing::info!(
+        tracing::debug!(
             "\tComponent peripheral id: {:x?}",
             component_id.peripheral_id
         );
 
         if let Some(info) = component_id.peripheral_id.determine_part() {
-            tracing::info!("\tComponent is known: {}", info);
+            tracing::debug!("\tComponent is known: {}", info);
         }
 
         let class = match component_id.class {
diff --git a/probe-rs/src/architecture/mod.rs b/probe-rs/src/architecture/mod.rs
index 3032f980..7f019d52 100644
--- a/probe-rs/src/architecture/mod.rs
+++ b/probe-rs/src/architecture/mod.rs
@@ -1,4 +1,4 @@
-#![warn(missing_docs)]
+//! All the interface bits for the different architectures.
 
 pub mod arm;
 pub mod riscv;
diff --git a/probe-rs/src/config/mod.rs b/probe-rs/src/config/mod.rs
index 53de9abe..f870945d 100644
--- a/probe-rs/src/config/mod.rs
+++ b/probe-rs/src/config/mod.rs
@@ -24,7 +24,6 @@
 
 mod chip_info;
 pub(crate) mod registry;
-pub mod sequences;
 mod target;
 
 pub use probe_rs_target::{
diff --git a/probe-rs/src/config/registry.rs b/probe-rs/src/config/registry.rs
index 4ce37bf8..a1ef31e5 100644
--- a/probe-rs/src/config/registry.rs
+++ b/probe-rs/src/config/registry.rs
@@ -123,17 +123,10 @@ struct Registry {
 
 impl Registry {
     fn from_builtin_families() -> Self {
-        #[cfg(feature = "builtin-targets")]
-        let mut families = {
-            const BUILTIN_TARGETS: &[u8] =
-                include_bytes!(concat!(env!("OUT_DIR"), "/targets.bincode"));
+        const BUILTIN_TARGETS: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/targets.bincode"));
 
-            bincode::deserialize(BUILTIN_TARGETS)
-                .expect("Failed to deserialize builtin targets. This is a bug")
-        };
-
-        #[cfg(not(feature = "builtin-targets"))]
-        let mut families = vec![];
+        let mut families = bincode::deserialize(BUILTIN_TARGETS)
+            .expect("Failed to deserialize builtin targets. This is a bug");
 
         add_generic_targets(&mut families);
 
diff --git a/probe-rs/src/config/sequences/mod.rs b/probe-rs/src/config/sequences/mod.rs
deleted file mode 100644
index f34d93ff..00000000
--- a/probe-rs/src/config/sequences/mod.rs
+++ /dev/null
@@ -1,30 +0,0 @@
-//! Target-specific debug sequence implementations.
-
-// Common code for certain chip families
-mod esp;
-mod nrf;
-
-// RISC-V ESP32 devices
-pub mod esp32c2;
-pub mod esp32c3;
-pub mod esp32c6;
-pub mod esp32h2;
-
-// Xtensa ESP32 devices
-pub mod esp32;
-pub mod esp32s2;
-pub mod esp32s3;
-
-// ARM devices
-pub mod atsam;
-pub mod cc13xx_cc26xx;
-pub mod efm32xg2;
-pub mod infineon;
-pub mod nrf52;
-pub mod nrf53;
-pub mod nrf91;
-pub mod nxp_armv7m;
-pub mod nxp_armv8m;
-pub mod stm32_armv6;
-pub mod stm32_armv7;
-pub mod stm32h7;
diff --git a/probe-rs/src/config/target.rs b/probe-rs/src/config/target.rs
index 4d1c6299..b62744ba 100644
--- a/probe-rs/src/config/target.rs
+++ b/probe-rs/src/config/target.rs
@@ -1,27 +1,4 @@
-use super::{
-    sequences::{
-        atsam::AtSAM,
-        cc13xx_cc26xx::CC13xxCC26xx,
-        efm32xg2::EFM32xG2,
-        esp32::ESP32,
-        esp32c2::ESP32C2,
-        esp32c3::ESP32C3,
-        esp32c6::ESP32C6,
-        esp32h2::ESP32H2,
-        esp32s2::ESP32S2,
-        esp32s3::ESP32S3,
-        infineon::XMC4000,
-        nrf52::Nrf52,
-        nrf53::Nrf5340,
-        nrf91::Nrf9160,
-        nxp_armv7m::{MIMXRT10xx, MIMXRT11xx},
-        nxp_armv8m::{LPC55Sxx, MIMXRT5xxS},
-        stm32_armv6::{Stm32Armv6, Stm32Armv6Family},
-        stm32_armv7::Stm32Armv7,
-        stm32h7::Stm32h7,
-    },
-    Core, MemoryRegion, RawFlashAlgorithm, RegistryError, TargetDescriptionSource,
-};
+use super::{Core, MemoryRegion, RawFlashAlgorithm, RegistryError, TargetDescriptionSource};
 use crate::architecture::{
     arm::{
         ap::MemoryAp,
@@ -118,78 +95,7 @@ impl Target {
             flash_algorithms.push(algo.clone());
         }
 
-        let debug_sequence = if chip.name.starts_with("MIMXRT10") {
-            DebugSequence::Arm(MIMXRT10xx::create())
-        } else if chip.name.starts_with("MIMXRT11") {
-            DebugSequence::Arm(MIMXRT11xx::create())
-        } else if chip.name.starts_with("MIMXRT5") {
-            DebugSequence::Arm(MIMXRT5xxS::create())
-        } else if chip.name.starts_with("LPC55S16")
-            || chip.name.starts_with("LPC55S26")
-            || chip.name.starts_with("LPC55S28")
-            || chip.name.starts_with("LPC55S66")
-            || chip.name.starts_with("LPC55S69")
-        {
-            DebugSequence::Arm(LPC55Sxx::create())
-        } else if chip.name.starts_with("EFM32PG2")
-            || chip.name.starts_with("EFR32BG2")
-            || chip.name.starts_with("EFR32FG2")
-            || chip.name.starts_with("EFR32MG2")
-            || chip.name.starts_with("EFR32ZG2")
-        {
-            DebugSequence::Arm(EFM32xG2::create())
-        } else if chip.name.starts_with("esp32-") {
-            DebugSequence::Xtensa(ESP32::create(chip))
-        } else if chip.name.eq_ignore_ascii_case("esp32s2") {
-            DebugSequence::Xtensa(ESP32S2::create(chip))
-        } else if chip.name.eq_ignore_ascii_case("esp32s3") {
-            DebugSequence::Xtensa(ESP32S3::create(chip))
-        } else if chip.name.eq_ignore_ascii_case("esp32c2") {
-            DebugSequence::Riscv(ESP32C2::create(chip))
-        } else if chip.name.eq_ignore_ascii_case("esp32c3") {
-            DebugSequence::Riscv(ESP32C3::create(chip))
-        } else if chip.name.eq_ignore_ascii_case("esp32c6") {
-            DebugSequence::Riscv(ESP32C6::create(chip))
-        } else if chip.name.eq_ignore_ascii_case("esp32h2") {
-            DebugSequence::Riscv(ESP32H2::create(chip))
-        } else if chip.name.starts_with("nRF5340") {
-            DebugSequence::Arm(Nrf5340::create())
-        } else if chip.name.starts_with("nRF52") {
-            DebugSequence::Arm(Nrf52::create())
-        } else if chip.name.starts_with("nRF9160") {
-            DebugSequence::Arm(Nrf9160::create())
-        } else if chip.name.starts_with("STM32F0") {
-            DebugSequence::Arm(Stm32Armv6::create(Stm32Armv6Family::F0))
-        } else if chip.name.starts_with("STM32L0") {
-            DebugSequence::Arm(Stm32Armv6::create(Stm32Armv6Family::L0))
-        } else if chip.name.starts_with("STM32G0") {
-            DebugSequence::Arm(Stm32Armv6::create(Stm32Armv6Family::G0))
-        } else if chip.name.starts_with("STM32F1")
-            || chip.name.starts_with("STM32F2")
-            || chip.name.starts_with("STM32F3")
-            || chip.name.starts_with("STM32F4")
-            || chip.name.starts_with("STM32F7")
-            || chip.name.starts_with("STM32G4")
-            || chip.name.starts_with("STM32L1")
-            || chip.name.starts_with("STM32L4")
-            || chip.name.starts_with("STM32WB")
-            || chip.name.starts_with("STM32WL")
-        {
-            DebugSequence::Arm(Stm32Armv7::create())
-        } else if chip.name.starts_with("STM32H7") {
-            DebugSequence::Arm(Stm32h7::create())
-        } else if chip.name.starts_with("ATSAMD1")
-            || chip.name.starts_with("ATSAMD2")
-            || chip.name.starts_with("ATSAMDA")
-            || chip.name.starts_with("ATSAMD5")
-            || chip.name.starts_with("ATSAME5")
-        {
-            DebugSequence::Arm(AtSAM::create())
-        } else if chip.name.starts_with("XMC4") {
-            DebugSequence::Arm(XMC4000::create())
-        } else if chip.name.starts_with("CC13") || chip.name.starts_with("CC26") {
-            DebugSequence::Arm(CC13xxCC26xx::create())
-        } else {
+        let debug_sequence = crate::vendor::try_create_debug_sequence(chip).unwrap_or_else(|| {
             // Default to the architecture of the first core, which is okay if
             // there is no mixed architectures.
             match chip.cores[0].core_type.architecture() {
@@ -197,7 +103,7 @@ impl Target {
                 Architecture::Riscv => DebugSequence::Riscv(DefaultRiscvSequence::create()),
                 Architecture::Xtensa => DebugSequence::Xtensa(DefaultXtensaSequence::create()),
             }
-        };
+        });
 
         tracing::info!("Using sequence {:?}", debug_sequence);
 
diff --git a/probe-rs/src/flashing/builder.rs b/probe-rs/src/flashing/builder.rs
index a9162f2b..c8807496 100644
--- a/probe-rs/src/flashing/builder.rs
+++ b/probe-rs/src/flashing/builder.rs
@@ -98,7 +98,7 @@ impl FlashFill {
 }
 
 /// The built layout of the data in flash.
-#[derive(Debug, Clone, PartialEq, Eq)]
+#[derive(Debug, Default, Clone, PartialEq, Eq)]
 pub struct FlashLayout {
     sectors: Vec<FlashSector>,
     pages: Vec<FlashPage>,
@@ -107,6 +107,14 @@ pub struct FlashLayout {
 }
 
 impl FlashLayout {
+    /// Merge another flash layout into this one.
+    pub fn merge_from(&mut self, other: FlashLayout) {
+        self.sectors.extend(other.sectors);
+        self.pages.extend(other.pages);
+        self.fills.extend(other.fills);
+        self.data_blocks.extend(other.data_blocks);
+    }
+
     /// List of sectors which are erased during flashing.
     pub fn sectors(&self) -> &[FlashSector] {
         &self.sectors
@@ -271,13 +279,10 @@ impl FlashBuilder {
         flash_algorithm: &FlashAlgorithm,
         include_empty_pages: bool,
     ) -> Result<FlashLayout, FlashError> {
-        let mut sectors: Vec<FlashSector> = Vec::new();
-        let mut pages: Vec<FlashPage> = Vec::new();
-        let mut fills: Vec<FlashFill> = Vec::new();
-        let mut data_blocks: Vec<FlashDataBlockSpan> = Vec::new();
+        let mut layout = FlashLayout::default();
 
         for info in flash_algorithm.iter_sectors() {
-            let range = info.base_address..info.base_address + info.size;
+            let range = info.address_range();
 
             // Ignore the sector if it's outside the NvmRegion.
             if !region.range.contains_range(&range) {
@@ -285,7 +290,7 @@ impl FlashBuilder {
             }
 
             let page = flash_algorithm.page_info(info.base_address).unwrap();
-            let page_range = page.base_address..page.base_address + page.size as u64;
+            let page_range = page.address_range();
             let sector_has_data = self.has_data_in_range(&range);
             let page_has_data = self.has_data_in_range(&page_range);
 
@@ -294,23 +299,22 @@ impl FlashBuilder {
                 continue;
             }
 
-            sectors.push(FlashSector {
+            layout.sectors.push(FlashSector {
                 address: info.base_address,
                 size: info.size,
             })
         }
 
         for info in flash_algorithm.iter_pages() {
-            let page_end = info.base_address + info.size as u64;
-            let range = info.base_address..page_end;
+            let range = info.address_range();
 
             // Ignore the page if it's outside the NvmRegion.
-            if !region.range.contains_range(&range.clone()) {
+            if !region.range.contains_range(&range) {
                 continue;
             }
 
             let sector = flash_algorithm.sector_info(info.base_address).unwrap();
-            let sector_range = sector.base_address..sector.base_address + sector.size;
+            let sector_range = sector.address_range();
             let sector_has_data = self.has_data_in_range(&sector_range);
             let page_has_data = self.has_data_in_range(&range);
 
@@ -332,41 +336,36 @@ impl FlashBuilder {
 
                 // Fill the hole between the previous data block (or page start if there are no blocks) and current block.
                 if address > fill_start_addr {
-                    fills.push(FlashFill {
+                    layout.fills.push(FlashFill {
                         address: fill_start_addr,
                         size: address - fill_start_addr,
-                        page_index: pages.len(),
+                        page_index: layout.pages.len(),
                     });
                 }
                 fill_start_addr = address + data.len() as u64;
             }
 
             // Fill the hole between the last data block (or page start if there are no blocks) and page end.
-            if fill_start_addr < page_end {
-                fills.push(FlashFill {
+            if fill_start_addr < range.end {
+                layout.fills.push(FlashFill {
                     address: fill_start_addr,
-                    size: page_end - fill_start_addr,
-                    page_index: pages.len(),
+                    size: range.end - fill_start_addr,
+                    page_index: layout.pages.len(),
                 });
             }
 
-            pages.push(page);
+            layout.pages.push(page);
         }
 
         for (address, data) in self.data_in_range(&region.range) {
-            data_blocks.push(FlashDataBlockSpan {
+            layout.data_blocks.push(FlashDataBlockSpan {
                 address,
-                size: data.len() as _,
+                size: data.len() as u64,
             });
         }
 
         // Return the finished flash layout.
-        Ok(FlashLayout {
-            sectors,
-            pages,
-            fills,
-            data_blocks,
-        })
+        Ok(layout)
     }
 }
 
diff --git a/probe-rs/src/flashing/encoder.rs b/probe-rs/src/flashing/encoder.rs
index 7c9cadf2..22d2dd5b 100644
--- a/probe-rs/src/flashing/encoder.rs
+++ b/probe-rs/src/flashing/encoder.rs
@@ -140,4 +140,8 @@ impl FlashEncoder {
     pub fn sectors(&self) -> &[FlashSector] {
         self.encoder.sectors()
     }
+
+    pub fn program_size(&self) -> u64 {
+        self.pages().iter().map(|p| p.data().len() as u64).sum()
+    }
 }
diff --git a/probe-rs/src/flashing/erase.rs b/probe-rs/src/flashing/erase.rs
index e867e119..189eb249 100644
--- a/probe-rs/src/flashing/erase.rs
+++ b/probe-rs/src/flashing/erase.rs
@@ -11,7 +11,7 @@ use super::FlashProgress;
 ///
 /// The optional progress will only be used to emit RTT messages.
 /// No actual indication for the state of the erase all operation will be given.
-pub fn erase_all(session: &mut Session, progress: Option<FlashProgress>) -> Result<(), FlashError> {
+pub fn erase_all(session: &mut Session, progress: FlashProgress) -> Result<(), FlashError> {
     tracing::debug!("Erasing all...");
 
     let mut algos: HashMap<(String, String), Vec<NvmRegion>> = HashMap::new();
@@ -95,9 +95,10 @@ pub fn erase_all(session: &mut Session, progress: Option<FlashProgress>) -> Resu
 }
 
 /// Erases `sectors` sectors starting from `start_sector` from flash.
+// TODO: currently no progress is reported by anything in this function.
 pub fn erase_sectors(
     session: &mut Session,
-    progress: Option<FlashProgress>,
+    progress: FlashProgress,
     start_sector: usize,
     sectors: usize,
 ) -> Result<(), FlashError> {
diff --git a/probe-rs/src/flashing/flasher.rs b/probe-rs/src/flashing/flasher.rs
index 94582305..e327a3c1 100644
--- a/probe-rs/src/flashing/flasher.rs
+++ b/probe-rs/src/flashing/flasher.rs
@@ -4,6 +4,7 @@ use tracing::Level;
 use super::{FlashAlgorithm, FlashBuilder, FlashError, FlashFill, FlashPage, FlashProgress};
 use crate::config::NvmRegion;
 use crate::flashing::encoder::FlashEncoder;
+use crate::flashing::FlashLayout;
 use crate::memory::MemoryInterface;
 use crate::{core::CoreRegisters, session::Session, Core, InstructionSet};
 use std::{
@@ -62,7 +63,7 @@ impl<'session> Flasher<'session> {
         session: &'session mut Session,
         core_index: usize,
         raw_flash_algorithm: &RawFlashAlgorithm,
-        progress: Option<FlashProgress>,
+        progress: FlashProgress,
     ) -> Result<Self, FlashError> {
         let target = session.target();
 
@@ -123,7 +124,7 @@ impl<'session> Flasher<'session> {
             session,
             core_index,
             flash_algorithm,
-            progress: progress.unwrap_or(FlashProgress::new(|_| {})),
+            progress,
         };
 
         this.load()?;
@@ -173,19 +174,21 @@ impl<'session> Flasher<'session> {
 
         for (offset, (original, read_back)) in algo.instructions.iter().zip(data.iter()).enumerate()
         {
-            if original != read_back {
-                tracing::error!(
-                    "Failed to verify flash algorithm. Data mismatch at address {:#010x}",
-                    algo.load_address + (4 * offset) as u64
-                );
-                tracing::error!("Original instruction: {:#010x}", original);
-                tracing::error!("Readback instruction: {:#010x}", read_back);
-
-                tracing::error!("Original: {:x?}", &algo.instructions);
-                tracing::error!("Readback: {:x?}", &data);
-
-                return Err(FlashError::FlashAlgorithmNotLoaded);
+            if original == read_back {
+                continue;
             }
+
+            tracing::error!(
+                "Failed to verify flash algorithm. Data mismatch at address {:#010x}",
+                algo.load_address + (4 * offset) as u64
+            );
+            tracing::error!("Original instruction: {:#010x}", original);
+            tracing::error!("Readback instruction: {:#010x}", read_back);
+
+            tracing::error!("Original: {:x?}", &algo.instructions);
+            tracing::error!("Readback: {:x?}", &data);
+
+            return Err(FlashError::FlashAlgorithmNotLoaded);
         }
 
         tracing::debug!("RAM contents match flashing algo blob.");
@@ -300,12 +303,7 @@ impl<'session> Flasher<'session> {
     ) -> Result<(), FlashError> {
         tracing::debug!("Starting program procedure.");
         // Convert the list of flash operations into flash sectors and pages.
-        let mut flash_layout = flash_builder.build_sectors_and_pages(
-            region,
-            &self.flash_algorithm,
-            restore_unwritten_bytes,
-        )?;
-        self.progress.initialized(flash_layout.clone());
+        let mut flash_layout = self.flash_layout(region, flash_builder, restore_unwritten_bytes)?;
 
         tracing::debug!("Double Buffering enabled: {:?}", enable_double_buffering);
         tracing::debug!(
@@ -338,7 +336,7 @@ impl<'session> Flasher<'session> {
 
         let flash_encoder = FlashEncoder::new(self.flash_algorithm.transfer_encoding, flash_layout);
 
-        // Skip erase if necessary
+        // Skip erase if necessary (i.e. chip erase was done before)
         if !skip_erasing {
             // Erase all necessary sectors
             self.sector_erase(&flash_encoder)?;
@@ -376,13 +374,8 @@ impl<'session> Flasher<'session> {
 
     /// Programs the pages given in `flash_layout` into the flash.
     fn program_simple(&mut self, flash_encoder: &FlashEncoder) -> Result<(), FlashError> {
-        self.progress.started_programming(
-            flash_encoder
-                .pages()
-                .iter()
-                .map(|p| p.data().len() as u64)
-                .sum(),
-        );
+        self.progress
+            .started_programming(flash_encoder.program_size());
 
         let mut t = Instant::now();
         let result = self.run_program(|active| {
@@ -449,13 +442,8 @@ impl<'session> Flasher<'session> {
     /// fit at least two page buffers. See [Flasher::double_buffering_supported].
     fn program_double_buffer(&mut self, flash_encoder: &FlashEncoder) -> Result<(), FlashError> {
         let mut current_buf = 0;
-        self.progress.started_programming(
-            flash_encoder
-                .pages()
-                .iter()
-                .map(|p| p.data().len() as u64)
-                .sum(),
-        );
+        self.progress
+            .started_programming(flash_encoder.program_size());
 
         let mut t = Instant::now();
         let result = self.run_program(|active| {
@@ -523,6 +511,19 @@ impl<'session> Flasher<'session> {
 
         Ok(())
     }
+
+    pub(super) fn flash_layout(
+        &self,
+        region: &NvmRegion,
+        flash_builder: &FlashBuilder,
+        restore_unwritten_bytes: bool,
+    ) -> Result<FlashLayout, FlashError> {
+        flash_builder.build_sectors_and_pages(
+            region,
+            &self.flash_algorithm,
+            restore_unwritten_bytes,
+        )
+    }
 }
 
 struct Registers {
diff --git a/probe-rs/src/flashing/loader.rs b/probe-rs/src/flashing/loader.rs
index 1b3fc2de..8a9ad7b7 100644
--- a/probe-rs/src/flashing/loader.rs
+++ b/probe-rs/src/flashing/loader.rs
@@ -16,7 +16,7 @@ use super::{
     IdfOptions,
 };
 use crate::config::DebugSequence;
-use crate::flashing::Format;
+use crate::flashing::{FlashLayout, FlashProgress, Format};
 use crate::memory::MemoryInterface;
 use crate::session::Session;
 use crate::Target;
@@ -363,7 +363,9 @@ impl FlashLoader {
             tracing::warn!("Memory map of flash loader does not match memory map of target!");
         }
 
-        let mut algos: HashMap<(String, String), Vec<NvmRegion>> = HashMap::new();
+        let mut algos: HashMap<(String, usize), Vec<NvmRegion>> = HashMap::new();
+
+        let progress = options.progress.unwrap_or(FlashProgress::new(|_| {}));
 
         // Commit NVM first
 
@@ -397,18 +399,21 @@ impl FlashLoader {
                 continue;
             }
 
-            let algo = Self::get_flash_algorithm_for_region(region, session.target())?;
+            let target = session.target();
+            let algo = Self::get_flash_algorithm_for_region(region, target)?;
+            let core_name = region
+                .cores
+                .first()
+                .ok_or_else(|| FlashError::NoNvmCoreAccess(region.clone()))?
+                .clone();
 
-            let entry = algos
-                .entry((
-                    algo.name.clone(),
-                    region
-                        .cores
-                        .first()
-                        .ok_or_else(|| FlashError::NoNvmCoreAccess(region.clone()))?
-                        .clone(),
-                ))
-                .or_default();
+            let core = target
+                .cores
+                .iter()
+                .position(|c| c.name == core_name)
+                .unwrap();
+
+            let entry = algos.entry((algo.name.clone(), core)).or_default();
             entry.push(region.clone());
 
             tracing::debug!("     -- using algorithm: {}", algo.name);
@@ -417,43 +422,63 @@ impl FlashLoader {
         if options.dry_run {
             tracing::info!("Skipping programming, dry run!");
 
-            if let Some(progress) = options.progress {
-                progress.failed_filling();
-                progress.failed_erasing();
-                progress.failed_programming();
-            }
+            progress.failed_filling();
+            progress.failed_erasing();
+            progress.failed_programming();
 
             return Ok(());
         }
 
-        // Iterate all flash algorithms we need to use.
-        for ((algo_name, core_name), regions) in algos {
-            tracing::debug!("Flashing ranges for algo: {}", algo_name);
+        let mut do_chip_erase = options.do_chip_erase;
+        let mut did_chip_erase = false;
 
-            // This can't fail, algo_name comes from the target.
-            let algo = session.target().flash_algorithm_by_name(&algo_name);
-            let algo = algo.unwrap().clone();
+        // No longer needs to be mutable.
+        let algos = algos;
 
-            let core = session
-                .target()
-                .cores
-                .iter()
-                .position(|c| c.name == core_name)
-                .unwrap();
-            let mut flasher = Flasher::new(session, core, &algo, options.progress.clone())?;
+        let mut phases = vec![];
 
-            let mut do_chip_erase = options.do_chip_erase;
+        // Iterate all flash algorithms to initialize a few things.
+        for ((algo_name, core), regions) in algos.iter() {
+            // This can't fail, algo_name comes from the target.
+            let algo = session.target().flash_algorithm_by_name(algo_name);
+            let algo = algo.unwrap().clone();
 
-            // If the flash algo doesn't support erase all, disable chip erase.
+            let flasher = Flasher::new(session, *core, &algo, progress.clone())?;
+            // If the first flash algo doesn't support erase all, disable chip erase.
+            // TODO: we could sort by support but it's unlikely to make a difference.
             if do_chip_erase && !flasher.is_chip_erase_supported() {
                 do_chip_erase = false;
                 tracing::warn!("Chip erase was the selected method to erase the sectors but this chip does not support chip erases (yet).");
                 tracing::warn!("A manual sector erase will be performed.");
             }
 
+            let mut phase_layout = FlashLayout::default();
+            for region in regions {
+                let layout =
+                    flasher.flash_layout(region, &self.builder, options.keep_unwritten_bytes)?;
+
+                phase_layout.merge_from(layout);
+            }
+            phases.push(phase_layout);
+        }
+
+        progress.initialized(do_chip_erase, options.keep_unwritten_bytes, phases);
+
+        // Iterate all flash algorithms we need to use and do the flashing.
+        for ((algo_name, core), regions) in algos {
+            tracing::debug!("Flashing ranges for algo: {}", algo_name);
+
+            // This can't fail, algo_name comes from the target.
+            let algo = session.target().flash_algorithm_by_name(&algo_name);
+            let algo = algo.unwrap().clone();
+
+            let mut flasher = Flasher::new(session, core, &algo, progress.clone())?;
+
             if do_chip_erase {
                 tracing::debug!("    Doing chip erase...");
                 flasher.run_erase_all()?;
+                do_chip_erase = false;
+                did_chip_erase = true;
             }
 
             let mut do_use_double_buffering = flasher.double_buffering_supported();
@@ -475,7 +500,7 @@ impl FlashLoader {
                     &self.builder,
                     options.keep_unwritten_bytes,
                     do_use_double_buffering,
-                    options.skip_erase || do_chip_erase,
+                    options.skip_erase || did_chip_erase,
                 )?;
             }
         }
diff --git a/probe-rs/src/flashing/progress.rs b/probe-rs/src/flashing/progress.rs
index 30ae4355..f3aec87f 100644
--- a/probe-rs/src/flashing/progress.rs
+++ b/probe-rs/src/flashing/progress.rs
@@ -27,14 +27,30 @@ impl FlashProgress {
         }
     }
 
+    /// Create a new `FlashProgress` structure with an empty handler.
+    pub fn empty() -> Self {
+        Self {
+            handler: Arc::new(|_| {}),
+        }
+    }
+
     /// Emit a flashing progress event.
     fn emit(&self, event: ProgressEvent) {
         (self.handler)(event);
     }
 
     /// Signalize that the flashing algorithm was set up and is initialized.
-    pub(super) fn initialized(&self, flash_layout: FlashLayout) {
-        self.emit(ProgressEvent::Initialized { flash_layout });
+    pub(super) fn initialized(
+        &self,
+        chip_erase: bool,
+        restore_unwritten: bool,
+        phases: Vec<FlashLayout>,
+    ) {
+        self.emit(ProgressEvent::Initialized {
+            chip_erase,
+            restore_unwritten,
+            phases,
+        });
     }
 
     /// Signalize that the erasing procedure started.
@@ -124,9 +140,18 @@ impl FlashProgress {
 pub enum ProgressEvent {
     /// The flash layout has been built and the flashing procedure was initialized.
     Initialized {
-        /// The layout of the flash contents as it will be used by the flash procedure.
+        /// Whether the chip erase feature is enabled.
+        /// If this is true, the chip will be erased before any other operation. No separate erase
+        /// progress bars are necessary in this case.
+        chip_erase: bool,
+
+        /// The layout of the flash contents as it will be used by the flash procedure, grouped by
+        /// phases (fill, erase, program sequences).
         /// This is an exact report of what the flashing procedure will do during the flashing process.
-        flash_layout: FlashLayout,
+        phases: Vec<FlashLayout>,
+
+        /// Whether the unwritten flash contents will be restored after erasing.
+        restore_unwritten: bool,
     },
     /// Filling of flash pages has started.
     StartedFilling,
diff --git a/probe-rs/src/lib.rs b/probe-rs/src/lib.rs
index 68d3e896..c02af351 100644
--- a/probe-rs/src/lib.rs
+++ b/probe-rs/src/lib.rs
@@ -70,9 +70,9 @@
 #[macro_use]
 extern crate serde;
 
-/// All the interface bits for the different architectures.
 pub mod architecture;
 pub mod config;
+pub mod vendor;
 
 mod core;
 pub mod debug;
diff --git a/probe-rs/src/probe/jlink/bits.rs b/probe-rs/src/probe/jlink/bits.rs
index 9d0302cd..a082c976 100644
--- a/probe-rs/src/probe/jlink/bits.rs
+++ b/probe-rs/src/probe/jlink/bits.rs
@@ -4,8 +4,8 @@ use std::fmt;
 #[derive(Clone)]
 pub struct BitIter<'a> {
     buf: &'a [u8],
-    next_bit: u8,
-    bits_left: usize,
+    total_bits: usize,
+    current_bit: usize,
 }
 
 impl<'a> BitIter<'a> {
@@ -19,8 +19,8 @@ impl<'a> BitIter<'a> {
 
         Self {
             buf,
-            next_bit: 0,
-            bits_left: total_bits,
+            total_bits,
+            current_bit: 0,
         }
     }
 }
@@ -29,25 +29,23 @@ impl Iterator for BitIter<'_> {
     type Item = bool;
 
     fn next(&mut self) -> Option<bool> {
-        if self.bits_left > 0 {
-            let byte = self.buf.first().unwrap();
-            let bit = byte & (1 << self.next_bit) != 0;
-            if self.next_bit < 7 {
-                self.next_bit += 1;
-            } else {
-                self.next_bit = 0;
-                self.buf = &self.buf[1..];
-            }
-
-            self.bits_left -= 1;
-            Some(bit)
-        } else {
-            None
+        if self.current_bit == self.total_bits {
+            return None;
         }
+
+        let current_bit = self.current_bit;
+        let byte = current_bit / 8;
+        let bit = current_bit % 8;
+
+        let bit = self.buf[byte] & (1 << bit) != 0;
+        self.current_bit += 1;
+
+        Some(bit)
     }
 
     fn size_hint(&self) -> (usize, Option<usize>) {
-        (self.bits_left, Some(self.bits_left))
+        let remaining = self.total_bits - self.current_bit;
+        (remaining, Some(remaining))
     }
 }
 
@@ -83,23 +81,13 @@ impl<I: Iterator<Item = bool>> Iterator for ByteIter<I> {
     fn next(&mut self) -> Option<u8> {
         // Collapse 8 bits from `inner` into a byte (LSb first).
         let mut byte = 0;
-        let mut empty = true;
-        for pos in 0..8 {
-            let bit = if let Some(bit) = self.inner.next() {
-                bit
-            } else {
-                break;
-            };
-            empty = false;
-            let mask = if bit { 1 } else { 0 } << pos;
-            byte |= mask;
+        let mut has_data = false;
+        for (pos, bit) in self.inner.by_ref().take(8).enumerate() {
+            has_data = true;
+            byte |= (bit as u8) << pos;
         }
 
-        if empty {
-            None
-        } else {
-            Some(byte)
-        }
+        has_data.then_some(byte)
     }
 }
 
@@ -109,40 +97,40 @@ mod tests {
 
     #[test]
     fn collapse_bytes() {
-        fn collapse(v: Vec<bool>) -> Vec<u8> {
+        fn collapse<const N: usize>(v: [bool; N]) -> Vec<u8> {
             v.into_iter().collapse_bytes().collect()
         }
 
-        assert_eq!(collapse(vec![]), vec![]);
-        assert_eq!(collapse(vec![true]), vec![0x01]);
-        assert_eq!(collapse(vec![false, true]), vec![0x02]);
-        assert_eq!(collapse(vec![true, false]), vec![0x01]);
-        assert_eq!(collapse(vec![false]), vec![0x00]);
-        assert_eq!(collapse(vec![false; 8]), vec![0x00]);
-        assert_eq!(collapse(vec![true; 8]), vec![0xFF]);
-        assert_eq!(collapse(vec![true; 7]), vec![0x7F]);
-        assert_eq!(collapse(vec![true; 9]), vec![0xFF, 0x01]);
+        assert_eq!(collapse([]), []);
+        assert_eq!(collapse([true]), [0x01]);
+        assert_eq!(collapse([false, true]), [0x02]);
+        assert_eq!(collapse([true, false]), [0x01]);
+        assert_eq!(collapse([false]), [0x00]);
+        assert_eq!(collapse([false; 8]), [0x00]);
+        assert_eq!(collapse([true; 8]), [0xFF]);
+        assert_eq!(collapse([true; 7]), [0x7F]);
+        assert_eq!(collapse([true; 9]), [0xFF, 0x01]);
     }
 
     #[test]
     fn bit_iter() {
-        fn bit_iter(b: Vec<u8>, num: usize) -> Vec<bool> {
+        fn bit_iter<const N: usize>(b: [u8; N], num: usize) -> Vec<bool> {
             BitIter::new(&b, num).collect()
         }
 
-        assert_eq!(bit_iter(vec![], 0), Vec::<bool>::new());
-        assert_eq!(bit_iter(vec![0xFF], 0), Vec::<bool>::new());
-        assert_eq!(bit_iter(vec![0xFF, 0xFF], 0), Vec::<bool>::new());
-        assert_eq!(bit_iter(vec![0xFF], 1), vec![true]);
-        assert_eq!(bit_iter(vec![0x00], 1), vec![false]);
-        assert_eq!(bit_iter(vec![0x01], 1), vec![true]);
-        assert_eq!(bit_iter(vec![0x01], 2), vec![true, false]);
-        assert_eq!(bit_iter(vec![0x02], 2), vec![false, true]);
-        assert_eq!(bit_iter(vec![0x02], 3), vec![false, true, false]);
+        assert_eq!(bit_iter([], 0), Vec::<bool>::new());
+        assert_eq!(bit_iter([0xFF], 0), Vec::<bool>::new());
+        assert_eq!(bit_iter([0xFF, 0xFF], 0), Vec::<bool>::new());
+        assert_eq!(bit_iter([0xFF], 1), [true]);
+        assert_eq!(bit_iter([0x00], 1), [false]);
+        assert_eq!(bit_iter([0x01], 1), [true]);
+        assert_eq!(bit_iter([0x01], 2), [true, false]);
+        assert_eq!(bit_iter([0x02], 2), [false, true]);
+        assert_eq!(bit_iter([0x02], 3), [false, true, false]);
 
         assert_eq!(
-            bit_iter(vec![0x01, 0x01], 9),
-            vec![true, false, false, false, false, false, false, false, true]
+            bit_iter([0x01, 0x01], 9),
+            [true, false, false, false, false, false, false, false, true]
         );
     }
 }
diff --git a/probe-rs/src/probe/jlink/capabilities.rs b/probe-rs/src/probe/jlink/capabilities.rs
index ab5bbaf1..565c76c8 100644
--- a/probe-rs/src/probe/jlink/capabilities.rs
+++ b/probe-rs/src/probe/jlink/capabilities.rs
@@ -1,87 +1,87 @@
 //! Probe capabilities.
 
-#![allow(non_upper_case_globals)]
-
 use std::fmt;
 
-enum_and_set!(
-    /// List of capabilities that may be advertised by a probe.
-    ///
-    /// Not many of these are actually used, and a lot of these have unknown meaning.
-    #[non_exhaustive]
-    #[derive(Debug, Copy, Clone, PartialEq, Eq)]
-    pub enum Capability {
-        Reserved0 = 0,  // Reserved, seems to be always set
-        GetHwVersion = 1,
-        WriteDcc = 2,
-        AdaptiveClocking = 3,
-        ReadConfig = 4,
-        WriteConfig = 5,
-        Trace = 6,
-        WriteMem = 7,
-        ReadMem = 8,
-        SpeedInfo = 9,
-        ExecCode = 10,
-        GetMaxBlockSize = 11,
-        GetHwInfo = 12,
-        SetKsPower = 13,
-        ResetStopTimed = 14,
-        // 15 = Reserved, seems to never be set
-        MeasureRtckReact = 16,
-        SelectIf = 17,
-        RwMemArm79 = 18,
-        GetCounters = 19,
-        ReadDcc = 20,
-        GetCpuCaps = 21,
-        ExecCpuCmd = 22,
-        Swo = 23,
-        WriteDccEx = 24,
-        UpdateFirmwareEx = 25,
-        FileIo = 26,
-        Register = 27,
-        Indicators = 28,
-        TestNetSpeed = 29,
-        RawTrace = 30,
-        // For the legacy capabilities, bit 31 is documented as reserved, but it must be
-        // GET_CAPS_EX, since there'd be no other way to know if GET_CAPS_EX is supported.
-        GetCapsEx = 31,
-
-        // Extended capabilities
+/// List of capabilities that may be advertised by a probe.
+///
+/// Not many of these are actually used, and a lot of these have unknown meaning.
+#[non_exhaustive]
+#[derive(Debug, Copy, Clone, PartialEq, Eq)]
+#[allow(missing_docs)]
+pub enum Capability {
+    Reserved0 = 0, // Reserved, seems to be always set
+    GetHwVersion = 1,
+    WriteDcc = 2,
+    AdaptiveClocking = 3,
+    ReadConfig = 4,
+    WriteConfig = 5,
+    Trace = 6,
+    WriteMem = 7,
+    ReadMem = 8,
+    SpeedInfo = 9,
+    ExecCode = 10,
+    GetMaxBlockSize = 11,
+    GetHwInfo = 12,
+    SetKsPower = 13,
+    ResetStopTimed = 14,
+    // 15 = Reserved, seems to never be set
+    MeasureRtckReact = 16,
+    SelectIf = 17,
+    RwMemArm79 = 18,
+    GetCounters = 19,
+    ReadDcc = 20,
+    GetCpuCaps = 21,
+    ExecCpuCmd = 22,
+    Swo = 23,
+    WriteDccEx = 24,
+    UpdateFirmwareEx = 25,
+    FileIo = 26,
+    Register = 27,
+    Indicators = 28,
+    TestNetSpeed = 29,
+    RawTrace = 30,
+    // For the legacy capabilities, bit 31 is documented as reserved, but it must be
+    // GET_CAPS_EX, since there'd be no other way to know if GET_CAPS_EX is supported.
+    GetCapsEx = 31,
 
-        HwJtagWrite = 32,
-        Com = 33,
-    }
+    // Extended capabilities
+    HwJtagWrite = 32,
+    Com = 33,
+}
 
-    flags CapabilityFlags: u128;
-);
+impl Capability {
+    const ALL_MASK: u128 = 0x3_ffff_7fff;
 
-impl CapabilityFlags {
-    fn from_capability(cap: Capability) -> Self {
-        Self::from_bits(1 << cap as u32).unwrap()
+    fn mask(self) -> u128 {
+        1 << self as u128
     }
 }
 
 /// A set of capabilities advertised by a probe.
 #[derive(Copy, Clone, PartialEq, Eq)]
-pub struct Capabilities(CapabilityFlags);
+pub struct Capabilities(u128);
 
 impl Capabilities {
-    /// Creates a `Capabilities` instance from 32 raw bits.
-    pub(crate) fn from_raw_legacy(raw: u32) -> Self {
-        let mut capabilities = CapabilityFlags::from_bits_truncate(u128::from(raw));
-        if capabilities.bits() != u128::from(raw) {
+    /// Creates a `Capabilities` instance from 128 raw bits.
+    fn from_raw(raw: u128) -> Self {
+        let mut capabilities = raw & Capability::ALL_MASK;
+        if capabilities != raw {
             tracing::debug!(
-                "unknown capability bits: 0x{:08X} truncated to 0x{:08X} ({:?})",
+                "unknown capability bits: {:#010x} truncated to {:#010x}",
                 raw,
-                capabilities.bits(),
                 capabilities,
             );
         }
         // Hide reserved bits from user-facing output.
-        capabilities.remove(CapabilityFlags::Reserved0);
+        capabilities &= !Capability::Reserved0.mask();
         Self(capabilities)
     }
 
+    /// Creates a `Capabilities` instance from 32 raw bits.
+    pub(crate) fn from_raw_legacy(raw: u32) -> Self {
+        Self::from_raw(raw as u128)
+    }
+
     /// Creates a `Capabilities` instance from a 256-bit bitset.
     pub(crate) fn from_raw_ex(raw: [u8; 32]) -> Self {
         if raw[16..] != [0; 16] {
@@ -90,31 +90,20 @@ impl Capabilities {
                 &raw[16..],
             );
         }
-        let mut bytes = [0; 16];
-        bytes.copy_from_slice(&raw[..16]);
+        let bytes: [u8; 16] = raw[..16].try_into().unwrap();
         let raw = u128::from_le_bytes(bytes);
-        let mut capabilities = CapabilityFlags::from_bits_truncate(raw);
-        if capabilities.bits() != raw {
-            tracing::debug!(
-                "unknown ext. capability bits: 0x{:08X} truncated to 0x{:08X} ({:?})",
-                raw,
-                capabilities.bits(),
-                capabilities,
-            );
-        }
-        // Hide reserved bits from user-facing output.
-        capabilities.remove(CapabilityFlags::Reserved0);
-        Self(capabilities)
+
+        Self::from_raw(raw)
     }
 
     /// Determines whether `self` contains capability `cap`.
     pub fn contains(&self, cap: Capability) -> bool {
-        self.0.contains(CapabilityFlags::from_capability(cap))
+        self.contains_all(Self(cap.mask()))
     }
 
     /// Determines whether `self` contains all capabilities in `caps`.
     pub fn contains_all(&self, caps: Capabilities) -> bool {
-        self.0.contains(caps.0)
+        self.0 & caps.0 == caps.0
     }
 }
 
diff --git a/probe-rs/src/probe/jlink/error.rs b/probe-rs/src/probe/jlink/error.rs
index cb753b1e..e5a4fddf 100644
--- a/probe-rs/src/probe/jlink/error.rs
+++ b/probe-rs/src/probe/jlink/error.rs
@@ -23,7 +23,7 @@ pub enum JlinkError {
     /// operation isn't set when that operation is attempted.
     ///
     /// Capabilities can be read by calling [`JLink::capabilities`], which returns a
-    /// [`Capabilities`] bitflags struct.
+    /// [`Capabilities`] struct.
     MissingCapability(Capability),
 
     #[error("probe does not support target interface {0:?}")]
diff --git a/probe-rs/src/probe/jlink/interface.rs b/probe-rs/src/probe/jlink/interface.rs
index 87fafaa7..a8181155 100644
--- a/probe-rs/src/probe/jlink/interface.rs
+++ b/probe-rs/src/probe/jlink/interface.rs
@@ -1,77 +1,86 @@
-#![allow(non_upper_case_globals)]
-
-use std::fmt;
-
-enum_and_set!(
-    /// List of target interfaces.
+/// List of target interfaces.
+///
+/// Note that this library might not support all of them, despite listing them here.
+#[non_exhaustive]
+#[derive(Debug, Copy, Clone, PartialEq, Eq)]
+pub enum Interface {
+    /// JTAG interface (IEEE 1149.1). Supported by most J-Link probes (some embedded J-Links
+    /// might only support SWD).
+    Jtag = 0,
+    /// SWD interface (Serial Wire Debug), used by most Cortex-M chips, and supported by almost
+    /// all J-Link probes.
+    Swd = 1,
+    /// Background Debug Mode 3, a single-wire debug interface used on some NXP microcontrollers.
+    Bdm3 = 2,
+    /// FINE, a two-wire debugging interface used by Renesas RX MCUs.
     ///
-    /// Note that this library might not support all of them, despite listing them here.
-    #[non_exhaustive]
-    #[derive(Debug, Copy, Clone, PartialEq, Eq)]
-    pub enum Interface {
-        /// JTAG interface (IEEE 1149.1). Supported by most J-Link probes (some embedded J-Links
-        /// might only support SWD).
-        Jtag = 0,
-        /// SWD interface (Serial Wire Debug), used by most Cortex-M chips, and supported by almost
-        /// all J-Link probes.
-        Swd = 1,
-        /// Background Debug Mode 3, a single-wire debug interface used on some NXP microcontrollers.
-        Bdm3 = 2,
-        /// FINE, a two-wire debugging interface used by Renesas RX MCUs.
-        ///
-        /// **Note**: due to a bug, attempting to select FINE with `JLink::select_interface()` will
-        /// currently hang the probe.
-        // FIXME: There's a curious bug that hangs the probe when selecting the FINE interface.
-        // Specifically, the probe never sends back the previous interface after it receives the
-        // `c7 03` SELECT_IF cmd, even though the normal J-Link software also just sends `c7 03`
-        // and gets back the right response.
-        Fine = 3,
-        /// In-Circuit System Programming (ICSP) interface of PIC32 chips.
-        Pic32Icsp = 4,
-        /// Serial Peripheral Interface (for SPI Flash programming).
-        Spi = 5,
-        /// Silicon Labs' 2-wire debug interface.
-        C2 = 6,
-        /// [cJTAG], or compact JTAG, as specified in IEEE 1149.7.
-        ///
-        /// [cJTAG]: https://wiki.segger.com/J-Link_cJTAG_specifics.
-        CJtag = 7,
-        /// 2-wire debugging interface used by Microchip's IS208x MCUs.
-        Mc2WireJtag = 10,
-        // (*)
-        // NOTE: When changing this enum, also change all other places with a (*) in addition to
-        // anything that fails to compile.
-        // NOTE 2: Keep the docs in sync with the bitflags below!
-    }
-
-    flags InterfaceFlags: u32;
-);
+    /// **Note**: due to a bug, attempting to select FINE with `JLink::select_interface()` will
+    /// currently hang the probe.
+    // FIXME: There's a curious bug that hangs the probe when selecting the FINE interface.
+    // Specifically, the probe never sends back the previous interface after it receives the
+    // `c7 03` SELECT_IF cmd, even though the normal J-Link software also just sends `c7 03`
+    // and gets back the right response.
+    Fine = 3,
+    /// In-Circuit System Programming (ICSP) interface of PIC32 chips.
+    Pic32Icsp = 4,
+    /// Serial Peripheral Interface (for SPI Flash programming).
+    Spi = 5,
+    /// Silicon Labs' 2-wire debug interface.
+    C2 = 6,
+    /// [cJTAG], or compact JTAG, as specified in IEEE 1149.7.
+    ///
+    /// [cJTAG]: https://wiki.segger.com/J-Link_cJTAG_specifics.
+    CJtag = 7,
+    /// 2-wire debugging interface used by Microchip's IS208x MCUs.
+    Mc2WireJtag = 10,
+}
 
 impl Interface {
-    pub(crate) fn as_u8(self) -> u8 {
-        self as u8
+    fn mask(self) -> u32 {
+        1 << self as u32
+    }
+
+    fn all_mask() -> u32 {
+        InterfaceIter::new().fold(0, |mask, interface| mask | interface.mask())
     }
 }
 
-impl fmt::Display for Interface {
-    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        f.write_str(match self {
-            Interface::Jtag => "JTAG",
-            Interface::Swd => "SWD",
-            Interface::Bdm3 => "BDM3",
-            Interface::Fine => "FINE",
-            Interface::Pic32Icsp => "PIC32 ICSP",
-            Interface::Spi => "SPI",
-            Interface::C2 => "C2",
-            Interface::CJtag => "cJTAG",
-            Interface::Mc2WireJtag => "Microchip 2-wire JTAG",
-        })
+/// Iterator over supported [`Interface`]s.
+#[derive(Debug)]
+pub struct InterfaceIter {
+    current: Option<Interface>,
+}
+
+impl InterfaceIter {
+    pub fn new() -> Self {
+        Self {
+            current: Some(Interface::Jtag),
+        }
     }
 }
 
-impl InterfaceFlags {
-    fn from_interface(interface: Interface) -> Self {
-        InterfaceFlags::from_bits(1 << interface as u32).unwrap()
+impl Iterator for InterfaceIter {
+    type Item = Interface;
+
+    fn next(&mut self) -> Option<Self::Item> {
+        // We'll return the current value, so let's grab it.
+        // If it's None, we're done, so let's just return None here.
+        let current = self.current?;
+
+        // Update the current value to the next one
+        self.current = match current {
+            Interface::Jtag => Some(Interface::Swd),
+            Interface::Swd => Some(Interface::Bdm3),
+            Interface::Bdm3 => Some(Interface::Fine),
+            Interface::Fine => Some(Interface::Pic32Icsp),
+            Interface::Pic32Icsp => Some(Interface::Spi),
+            Interface::Spi => Some(Interface::C2),
+            Interface::C2 => Some(Interface::CJtag),
+            Interface::CJtag => Some(Interface::Mc2WireJtag),
+            Interface::Mc2WireJtag => None,
+        };
+
+        Some(current)
     }
 }
 
@@ -80,100 +89,53 @@ impl InterfaceFlags {
 /// This implements `IntoIterator`, so you can call `.into_iter()` to iterate over the contained
 /// [`Interface`]s.
 #[derive(Copy, Clone, Eq, PartialEq)]
-pub struct Interfaces(InterfaceFlags);
+pub struct Interfaces(u32);
 
 impl Interfaces {
     pub(crate) fn from_bits_warn(raw: u32) -> Self {
-        let flags = InterfaceFlags::from_bits_truncate(raw);
-        if flags.bits() != raw {
+        let flags = raw & Interface::all_mask();
+        if flags != raw {
             tracing::debug!(
-                "unknown bits in interface mask: 0x{:08X} truncated to 0x{:08X} ({:?})",
-                raw,
-                flags.bits(),
-                flags,
+                "unknown bits in interface mask: {raw:#010x} truncated to {flags:#010x}"
             );
         }
         Self(flags)
     }
 
     pub(crate) fn single(interface: Interface) -> Self {
-        Self(InterfaceFlags::from_interface(interface))
+        Self(interface.mask())
     }
 
     /// Returns whether `interface` is contained in `self`.
     pub fn contains(&self, interface: Interface) -> bool {
-        self.0.contains(InterfaceFlags::from_interface(interface))
-    }
-}
-
-impl fmt::Debug for Interfaces {
-    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        self.0.fmt(f)
+        self.0 & interface.mask() == interface.mask()
     }
 }
 
 impl IntoIterator for Interfaces {
     type Item = Interface;
-    type IntoIter = InterfaceIter;
+    type IntoIter = InterfacesIter;
 
     fn into_iter(self) -> Self::IntoIter {
-        InterfaceIter {
+        InterfacesIter {
             interfaces: self,
-            next: 0,
+            current: InterfaceIter::new(),
         }
     }
 }
 
 /// Iterator over supported [`Interface`]s.
-#[derive(Debug)]
-pub struct InterfaceIter {
+pub struct InterfacesIter {
     interfaces: Interfaces,
-    next: usize,
+    current: InterfaceIter,
 }
 
-impl Iterator for InterfaceIter {
+impl Iterator for InterfacesIter {
     type Item = Interface;
 
     fn next(&mut self) -> Option<Self::Item> {
-        loop {
-            let next = Interface::ALL.get(self.next)?;
-            self.next += 1;
-            if self.interfaces.contains(*next) {
-                return Some(*next);
-            }
-        }
-    }
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-
-    #[test]
-    fn iter() {
-        assert_eq!(
-            Interfaces(InterfaceFlags::empty())
-                .into_iter()
-                .collect::<Vec<_>>(),
-            &[]
-        );
-        assert_eq!(
-            Interfaces(InterfaceFlags::Jtag)
-                .into_iter()
-                .collect::<Vec<_>>(),
-            &[Interface::Jtag]
-        );
-        assert_eq!(
-            Interfaces(InterfaceFlags::Swd)
-                .into_iter()
-                .collect::<Vec<_>>(),
-            &[Interface::Swd]
-        );
-        assert_eq!(
-            Interfaces(InterfaceFlags::Jtag | InterfaceFlags::Swd)
-                .into_iter()
-                .collect::<Vec<_>>(),
-            &[Interface::Jtag, Interface::Swd]
-        );
+        self.current
+            .by_ref()
+            .find(|&current| self.interfaces.contains(current))
     }
 }
diff --git a/probe-rs/src/probe/jlink/macros.rs b/probe-rs/src/probe/jlink/macros.rs
deleted file mode 100644
index b6896ae4..00000000
--- a/probe-rs/src/probe/jlink/macros.rs
+++ /dev/null
@@ -1,38 +0,0 @@
-macro_rules! enum_and_set {
-    (
-        $( #[$enum_attr:meta] )*
-        $enum_vis:vis enum $enum_name:ident {
-            $(
-                $( #[$attr:meta] )*
-                $name:ident = $id:expr,
-            )+
-        }
-
-        flags $flags_name:ident: $flags_ty:ty;
-    ) => {
-        $( #[$enum_attr] )*
-        $enum_vis enum $enum_name {
-            $(
-                #[allow(missing_docs)] // TODO the capabilities should be documented
-                $( #[$attr] )*
-                $name = $id,
-            )+
-        }
-
-        impl $enum_name {
-            #[allow(dead_code)]
-            const ALL: &'static [Self] = &[
-                $( Self::$name ),+
-            ];
-        }
-
-        ::bitflags::bitflags! {
-            #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
-            struct $flags_name: $flags_ty {
-                $(
-                    const $name = 1 << $id;
-                )+
-            }
-        }
-    };
-}
diff --git a/probe-rs/src/probe/jlink/mod.rs b/probe-rs/src/probe/jlink/mod.rs
index c77129fa..65c2ba22 100644
--- a/probe-rs/src/probe/jlink/mod.rs
+++ b/probe-rs/src/probe/jlink/mod.rs
@@ -1,7 +1,5 @@
 //! Support for J-Link Debug probes
 
-#[macro_use]
-mod macros;
 mod bits;
 pub mod capabilities;
 mod error;
@@ -377,10 +375,8 @@ impl JLink {
     fn fill_capabilities(&mut self) -> Result<(), JlinkError> {
         self.write_cmd(&[Command::GetCaps as u8])?;
 
-        let mut buf = [0; 4];
-        self.read(&mut buf)?;
+        let caps = self.read_u32().map(Capabilities::from_raw_legacy)?;
 
-        let mut caps = Capabilities::from_raw_legacy(u32::from_le_bytes(buf));
         debug!("legacy caps: {:?}", caps);
 
         // If the `GET_CAPS_EX` capability is set, use the extended capability command to fetch
@@ -388,9 +384,7 @@ impl JLink {
         if caps.contains(Capability::GetCapsEx) {
             self.write_cmd(&[Command::GetCapsEx as u8])?;
 
-            let mut buf = [0; 32];
-            self.read(&mut buf)?;
-            let real_caps = Capabilities::from_raw_ex(buf);
+            let real_caps = self.read_n::<32>().map(Capabilities::from_raw_ex)?;
             if !real_caps.contains_all(caps) {
                 return Err(JlinkError::Other(format!(
                     "ext. caps are not a superset of legacy caps (legacy: {:?}, ex: {:?})",
@@ -398,12 +392,12 @@ impl JLink {
                 )));
             }
             debug!("extended caps: {:?}", real_caps);
-            caps = real_caps;
+            self.caps = real_caps;
         } else {
             debug!("extended caps not supported");
+            self.caps = caps;
         }
 
-        self.caps = caps;
         Ok(())
     }
 
@@ -418,11 +412,8 @@ impl JLink {
 
         self.write_cmd(&[Command::SelectIf as u8, 0xFF])?;
 
-        let mut buf = [0; 4];
-        self.read(&mut buf)?;
+        self.interfaces = self.read_u32().map(Interfaces::from_bits_warn)?;
 
-        let intfs = Interfaces::from_bits_warn(u32::from_le_bytes(buf));
-        self.interfaces = intfs;
         Ok(())
     }
 
@@ -458,6 +449,20 @@ impl JLink {
         Ok(())
     }
 
+    fn read_n<const N: usize>(&self) -> Result<[u8; N], JlinkError> {
+        let mut buf = [0; N];
+        self.read(&mut buf)?;
+        Ok(buf)
+    }
+
+    fn read_u16(&self) -> Result<u16, JlinkError> {
+        self.read_n::<2>().map(u16::from_le_bytes)
+    }
+
+    fn read_u32(&self) -> Result<u32, JlinkError> {
+        self.read_n::<4>().map(u32::from_le_bytes)
+    }
+
     fn require_capability(&self, cap: Capability) -> Result<(), JlinkError> {
         if self.caps.contains(cap) {
             Ok(())
@@ -495,19 +500,14 @@ impl JLink {
 
         self.write_cmd(&[Command::GetMaxMemBlock as u8])?;
 
-        let mut buf = [0; 4];
-        self.read(&mut buf)?;
-
-        Ok(u32::from_le_bytes(buf))
+        self.read_u32()
     }
 
     /// Reads the firmware version string from the device.
     fn read_firmware_version(&self) -> Result<String, JlinkError> {
         self.write_cmd(&[Command::Version as u8])?;
 
-        let mut buf = [0; 2];
-        self.read(&mut buf)?;
-        let num_bytes = u16::from_le_bytes(buf);
+        let num_bytes = self.read_u16()?;
         let mut buf = vec![0; num_bytes as usize];
         self.read(&mut buf)?;
 
@@ -530,10 +530,7 @@ impl JLink {
 
         self.write_cmd(&[Command::GetHwVersion as u8])?;
 
-        let mut buf = [0; 4];
-        self.read(&mut buf)?;
-
-        Ok(HardwareVersion::from_u32(u32::from_le_bytes(buf)))
+        self.read_u32().map(HardwareVersion::from_u32)
     }
 
     /// Selects the interface to use for talking to the target MCU.
@@ -553,11 +550,10 @@ impl JLink {
 
         self.require_interface_supported(intf)?;
 
-        self.write_cmd(&[Command::SelectIf as u8, intf.as_u8()])?;
+        self.write_cmd(&[Command::SelectIf as u8, intf as u8])?;
 
         // Returns the previous interface, ignore it
-        let mut buf = [0; 4];
-        self.read(&mut buf)?;
+        let _ = self.read_u32()?;
 
         self.interface = intf;
 
@@ -600,8 +596,7 @@ impl JLink {
     fn read_target_voltage(&self) -> Result<u16, JlinkError> {
         self.write_cmd(&[Command::GetState as u8])?;
 
-        let mut buf = [0; 8];
-        self.read(&mut buf)?;
+        let buf = self.read_n::<8>()?;
 
         let voltage = [buf[0], buf[1]];
         Ok(u16::from_le_bytes(voltage))
@@ -779,8 +774,7 @@ impl JLink {
 
     /// Enable/Disable the Target Power Supply of the probe.
     ///
-    /// This is not available on all probes.
-    /// This is avialable on some J-Links
+    /// This is not available on all J-Links.
     pub fn set_kickstart_power(&mut self, enable: bool) -> Result<(), JlinkError> {
         self.require_capability(Capability::SetKsPower)?;
         self.write_cmd(&[Command::SetKsPower as u8, if enable { 1 } else { 0 }])
diff --git a/probe-rs/src/probe/jlink/speed.rs b/probe-rs/src/probe/jlink/speed.rs
index 620b76d6..5ac76ac1 100644
--- a/probe-rs/src/probe/jlink/speed.rs
+++ b/probe-rs/src/probe/jlink/speed.rs
@@ -80,8 +80,7 @@ impl JLink {
 
         self.write_cmd(&[Command::GetSpeeds as u8])?;
 
-        let mut buf = [0; 6];
-        self.read(&mut buf)?;
+        let buf = self.read_n::<6>()?;
 
         let base_freq_bytes = <[u8; 4]>::try_from(&buf[0..4]).unwrap();
         let min_div_bytes = <[u8; 2]>::try_from(&buf[4..6]).unwrap();
diff --git a/probe-rs/src/probe/jlink/swo.rs b/probe-rs/src/probe/jlink/swo.rs
index 6f386655..c1fc89ca 100644
--- a/probe-rs/src/probe/jlink/swo.rs
+++ b/probe-rs/src/probe/jlink/swo.rs
@@ -1,7 +1,5 @@
 //! SWO capture support for J-Link probes.
 
-#![allow(unused)]
-
 use super::Command;
 use super::JLink;
 
@@ -9,8 +7,6 @@ use super::capabilities::Capability;
 use super::error::JlinkError;
 use super::interface::Interface;
 
-use bitflags::bitflags;
-
 use std::{cmp, ops::Deref};
 use tracing::warn;
 
@@ -43,22 +39,26 @@ pub enum SwoMode {
     // FIXME: Manchester encoding?
 }
 
-bitflags! {
-    /// SWO status returned by probe on SWO buffer read.
-    #[derive(Copy, Clone, Debug, Eq, PartialEq)]
-    struct SwoStatus: u32 {
-        /// The on-probe buffer has overflowed. Device data was lost.
-        const OVERRUN = 1 << 0;
+/// SWO status returned by probe on SWO buffer read.
+#[derive(Copy, Clone, Debug, Eq, PartialEq)]
+struct SwoStatus(u32);
+impl SwoStatus {
+    /// The on-probe buffer has overflowed. Device data was lost.
+    const OVERRUN: u32 = 1 << 0;
+    const ALL_MASK: u32 = Self::OVERRUN;
+
+    fn contains(&self, status: u32) -> bool {
+        self.0 & status != 0
     }
 }
 
 impl SwoStatus {
     fn new(bits: u32) -> Self {
-        let flags = SwoStatus::from_bits_truncate(bits);
-        if flags.bits() != bits {
-            warn!("Unknown SWO status flag bits: 0x{:08X}", bits);
+        let flags = bits & Self::ALL_MASK;
+        if flags != bits {
+            warn!("Unknown SWO status flag bits: {:#010x}", bits);
         }
-        flags
+        Self(flags)
     }
 }
 
@@ -128,8 +128,7 @@ impl JLink {
 
         self.write_cmd(&buf)?;
 
-        let mut buf = [0; 28];
-        self.read(&mut buf)?;
+        let buf = self.read_n::<28>()?;
 
         let mut len = [0; 4];
         len.copy_from_slice(&buf[0..4]);
@@ -143,7 +142,7 @@ impl JLink {
 
         // Skip length and reserved word.
         // FIXME: What's the word after the length for?
-        let mut buf = &buf[8..];
+        let buf = &buf[8..];
 
         let base_freq_bytes = <[u8; 4]>::try_from(&buf[0..4]).unwrap();
         let min_div_bytes = <[u8; 4]>::try_from(&buf[4..8]).unwrap();
@@ -194,9 +193,7 @@ impl JLink {
 
         self.write_cmd(&buf)?;
 
-        let mut status = [0; 4];
-        self.read(&mut status)?;
-        let _status = SwoStatus::new(u32::from_le_bytes(status));
+        let _status = self.read_u32().map(SwoStatus::new)?;
 
         Ok(())
     }
@@ -213,9 +210,7 @@ impl JLink {
 
         self.write_cmd(&buf)?;
 
-        let mut status = [0; 4];
-        self.read(&mut status)?;
-        let _status = SwoStatus::new(u32::from_le_bytes(status));
+        let _status = self.read_u32().map(SwoStatus::new)?;
         // FIXME: What to do with the status?
 
         Ok(())
@@ -241,8 +236,7 @@ impl JLink {
 
         self.write_cmd(&cmd)?;
 
-        let mut header = [0; 8];
-        self.read(&mut header)?;
+        let header = self.read_n::<8>()?;
 
         let status = {
             let mut status = [0; 4];
@@ -253,15 +247,14 @@ impl JLink {
         let length = {
             let mut length = [0; 4];
             length.copy_from_slice(&header[4..8]);
-            u32::from_le_bytes(length)
+            u32::from_le_bytes(length) as usize
         };
 
         if status.contains(SwoStatus::OVERRUN) {
             warn!("SWO probe buffer overrun");
         }
 
-        let len = length as usize;
-        let buf = &mut data[..len];
+        let buf = &mut data[..length];
         self.read(buf)?;
 
         Ok(SwoData { data: buf, status })
diff --git a/probe-rs/src/vendor/espressif/mod.rs b/probe-rs/src/vendor/espressif/mod.rs
new file mode 100644
index 00000000..854d22ea
--- /dev/null
+++ b/probe-rs/src/vendor/espressif/mod.rs
@@ -0,0 +1,44 @@
+//! Espressif vendor support.
+
+use probe_rs_target::Chip;
+
+use crate::{
+    config::DebugSequence,
+    vendor::{
+        espressif::sequences::{
+            esp32::ESP32, esp32c2::ESP32C2, esp32c3::ESP32C3, esp32c6::ESP32C6, esp32h2::ESP32H2,
+            esp32s2::ESP32S2, esp32s3::ESP32S3,
+        },
+        Vendor,
+    },
+};
+
+pub mod sequences;
+
+/// Espressif
+#[derive(docsplay::Display)]
+pub struct Espressif;
+
+impl Vendor for Espressif {
+    fn try_create_debug_sequence(&self, chip: &Chip) -> Option<DebugSequence> {
+        let sequence = if chip.name.starts_with("esp32-") {
+            DebugSequence::Xtensa(ESP32::create(chip))
+        } else if chip.name.eq_ignore_ascii_case("esp32s2") {
+            DebugSequence::Xtensa(ESP32S2::create(chip))
+        } else if chip.name.eq_ignore_ascii_case("esp32s3") {
+            DebugSequence::Xtensa(ESP32S3::create(chip))
+        } else if chip.name.eq_ignore_ascii_case("esp32c2") {
+            DebugSequence::Riscv(ESP32C2::create(chip))
+        } else if chip.name.eq_ignore_ascii_case("esp32c3") {
+            DebugSequence::Riscv(ESP32C3::create(chip))
+        } else if chip.name.eq_ignore_ascii_case("esp32c6") {
+            DebugSequence::Riscv(ESP32C6::create(chip))
+        } else if chip.name.eq_ignore_ascii_case("esp32h2") {
+            DebugSequence::Riscv(ESP32H2::create(chip))
+        } else {
+            return None;
+        };
+
+        Some(sequence)
+    }
+}
diff --git a/probe-rs/src/config/sequences/esp.rs b/probe-rs/src/vendor/espressif/sequences/esp.rs
similarity index 100%
rename from probe-rs/src/config/sequences/esp.rs
rename to probe-rs/src/vendor/espressif/sequences/esp.rs
diff --git a/probe-rs/src/config/sequences/esp32.rs b/probe-rs/src/vendor/espressif/sequences/esp32.rs
similarity index 98%
rename from probe-rs/src/config/sequences/esp32.rs
rename to probe-rs/src/vendor/espressif/sequences/esp32.rs
index fba148e3..de712b3c 100644
--- a/probe-rs/src/config/sequences/esp32.rs
+++ b/probe-rs/src/vendor/espressif/sequences/esp32.rs
@@ -4,11 +4,11 @@ use std::sync::Arc;
 
 use probe_rs_target::Chip;
 
+use super::esp::EspFlashSizeDetector;
 use crate::{
     architecture::xtensa::{
         communication_interface::XtensaCommunicationInterface, sequences::XtensaDebugSequence,
     },
-    config::sequences::esp::EspFlashSizeDetector,
     MemoryInterface, Session,
 };
 
diff --git a/probe-rs/src/config/sequences/esp32c2.rs b/probe-rs/src/vendor/espressif/sequences/esp32c2.rs
similarity index 98%
rename from probe-rs/src/config/sequences/esp32c2.rs
rename to probe-rs/src/vendor/espressif/sequences/esp32c2.rs
index c7d71d3f..c081993e 100644
--- a/probe-rs/src/config/sequences/esp32c2.rs
+++ b/probe-rs/src/vendor/espressif/sequences/esp32c2.rs
@@ -4,12 +4,12 @@ use std::{sync::Arc, time::Duration};
 
 use probe_rs_target::Chip;
 
+use super::esp::EspFlashSizeDetector;
 use crate::{
     architecture::riscv::{
         communication_interface::RiscvCommunicationInterface, sequences::RiscvDebugSequence,
         Dmcontrol,
     },
-    config::sequences::esp::EspFlashSizeDetector,
     MemoryInterface, Session,
 };
 
diff --git a/probe-rs/src/config/sequences/esp32c3.rs b/probe-rs/src/vendor/espressif/sequences/esp32c3.rs
similarity index 98%
rename from probe-rs/src/config/sequences/esp32c3.rs
rename to probe-rs/src/vendor/espressif/sequences/esp32c3.rs
index e406d46d..958fe021 100644
--- a/probe-rs/src/config/sequences/esp32c3.rs
+++ b/probe-rs/src/vendor/espressif/sequences/esp32c3.rs
@@ -4,12 +4,12 @@ use std::{sync::Arc, time::Duration};
 
 use probe_rs_target::Chip;
 
+use super::esp::EspFlashSizeDetector;
 use crate::{
     architecture::riscv::{
         communication_interface::RiscvCommunicationInterface, sequences::RiscvDebugSequence,
         Dmcontrol,
     },
-    config::sequences::esp::EspFlashSizeDetector,
     MemoryInterface, Session,
 };
 
diff --git a/probe-rs/src/config/sequences/esp32c6.rs b/probe-rs/src/vendor/espressif/sequences/esp32c6.rs
similarity index 98%
rename from probe-rs/src/config/sequences/esp32c6.rs
rename to probe-rs/src/vendor/espressif/sequences/esp32c6.rs
index 67f8a39e..caeeba1d 100644
--- a/probe-rs/src/config/sequences/esp32c6.rs
+++ b/probe-rs/src/vendor/espressif/sequences/esp32c6.rs
@@ -4,13 +4,13 @@ use std::{sync::Arc, time::Duration};
 
 use probe_rs_target::Chip;
 
+use super::esp::EspFlashSizeDetector;
 use crate::{
     architecture::riscv::{
         communication_interface::{RiscvCommunicationInterface, Sbaddress0, Sbcs, Sbdata0},
         sequences::RiscvDebugSequence,
         Dmcontrol,
     },
-    config::sequences::esp::EspFlashSizeDetector,
     MemoryInterface, Session,
 };
 
diff --git a/probe-rs/src/config/sequences/esp32h2.rs b/probe-rs/src/vendor/espressif/sequences/esp32h2.rs
similarity index 98%
rename from probe-rs/src/config/sequences/esp32h2.rs
rename to probe-rs/src/vendor/espressif/sequences/esp32h2.rs
index 09c94153..905bb8e9 100644
--- a/probe-rs/src/config/sequences/esp32h2.rs
+++ b/probe-rs/src/vendor/espressif/sequences/esp32h2.rs
@@ -4,13 +4,13 @@ use std::{sync::Arc, time::Duration};
 
 use probe_rs_target::Chip;
 
+use super::esp::EspFlashSizeDetector;
 use crate::{
     architecture::riscv::{
         communication_interface::{RiscvCommunicationInterface, Sbaddress0, Sbcs, Sbdata0},
         sequences::RiscvDebugSequence,
         Dmcontrol,
     },
-    config::sequences::esp::EspFlashSizeDetector,
     MemoryInterface, Session,
 };
 
diff --git a/probe-rs/src/config/sequences/esp32s2.rs b/probe-rs/src/vendor/espressif/sequences/esp32s2.rs
similarity index 97%
rename from probe-rs/src/config/sequences/esp32s2.rs
rename to probe-rs/src/vendor/espressif/sequences/esp32s2.rs
index 3b5e6317..88494b3d 100644
--- a/probe-rs/src/config/sequences/esp32s2.rs
+++ b/probe-rs/src/vendor/espressif/sequences/esp32s2.rs
@@ -4,11 +4,11 @@ use std::sync::Arc;
 
 use probe_rs_target::Chip;
 
+use super::esp::EspFlashSizeDetector;
 use crate::{
     architecture::xtensa::{
         communication_interface::XtensaCommunicationInterface, sequences::XtensaDebugSequence,
     },
-    config::sequences::esp::EspFlashSizeDetector,
     MemoryInterface, Session,
 };
 
diff --git a/probe-rs/src/config/sequences/esp32s3.rs b/probe-rs/src/vendor/espressif/sequences/esp32s3.rs
similarity index 97%
rename from probe-rs/src/config/sequences/esp32s3.rs
rename to probe-rs/src/vendor/espressif/sequences/esp32s3.rs
index 9d3aa131..6a2c9d27 100644
--- a/probe-rs/src/config/sequences/esp32s3.rs
+++ b/probe-rs/src/vendor/espressif/sequences/esp32s3.rs
@@ -4,11 +4,11 @@ use std::sync::Arc;
 
 use probe_rs_target::Chip;
 
+use super::esp::EspFlashSizeDetector;
 use crate::{
     architecture::xtensa::{
         communication_interface::XtensaCommunicationInterface, sequences::XtensaDebugSequence,
     },
-    config::sequences::esp::EspFlashSizeDetector,
     MemoryInterface, Session,
 };
 
diff --git a/probe-rs/src/vendor/espressif/sequences/mod.rs b/probe-rs/src/vendor/espressif/sequences/mod.rs
new file mode 100644
index 00000000..345739e5
--- /dev/null
+++ b/probe-rs/src/vendor/espressif/sequences/mod.rs
@@ -0,0 +1,15 @@
+//! Espressif debug sequences.
+
+// Common code for ESP32 devices
+mod esp;
+
+// RISC-V ESP32 devices
+pub mod esp32c2;
+pub mod esp32c3;
+pub mod esp32c6;
+pub mod esp32h2;
+
+// Xtensa ESP32 devices
+pub mod esp32;
+pub mod esp32s2;
+pub mod esp32s3;
diff --git a/probe-rs/src/vendor/infineon/mod.rs b/probe-rs/src/vendor/infineon/mod.rs
new file mode 100644
index 00000000..892d90e2
--- /dev/null
+++ b/probe-rs/src/vendor/infineon/mod.rs
@@ -0,0 +1,26 @@
+//! Infineon vendor support.
+
+use probe_rs_target::Chip;
+
+use crate::{
+    config::DebugSequence,
+    vendor::{infineon::sequences::xmc4000::XMC4000, Vendor},
+};
+
+pub mod sequences;
+
+/// Infineon
+#[derive(docsplay::Display)]
+pub struct Infineon;
+
+impl Vendor for Infineon {
+    fn try_create_debug_sequence(&self, chip: &Chip) -> Option<DebugSequence> {
+        let sequence = if chip.name.starts_with("XMC4") {
+            DebugSequence::Arm(XMC4000::create())
+        } else {
+            return None;
+        };
+
+        Some(sequence)
+    }
+}
diff --git a/probe-rs/src/vendor/infineon/sequences/mod.rs b/probe-rs/src/vendor/infineon/sequences/mod.rs
new file mode 100644
index 00000000..1e51c378
--- /dev/null
+++ b/probe-rs/src/vendor/infineon/sequences/mod.rs
@@ -0,0 +1,3 @@
+//! Infineon debug sequences.
+
+pub mod xmc4000;
diff --git a/probe-rs/src/config/sequences/infineon.rs b/probe-rs/src/vendor/infineon/sequences/xmc4000.rs
similarity index 99%
rename from probe-rs/src/config/sequences/infineon.rs
rename to probe-rs/src/vendor/infineon/sequences/xmc4000.rs
index d28e5862..5d30506d 100644
--- a/probe-rs/src/config/sequences/infineon.rs
+++ b/probe-rs/src/vendor/infineon/sequences/xmc4000.rs
@@ -1,4 +1,4 @@
-//! Sequences for Infineon target families
+//! Sequences for XMC4000
 
 use crate::architecture::arm::armv7m::{Aircr, Dhcsr, FpCtrl, FpRev1CompX, FpRev2CompX};
 use crate::architecture::arm::memory::adi_v5_memory_interface::ArmProbe;
diff --git a/probe-rs/src/vendor/microchip/mod.rs b/probe-rs/src/vendor/microchip/mod.rs
new file mode 100644
index 00000000..0ce17d6e
--- /dev/null
+++ b/probe-rs/src/vendor/microchip/mod.rs
@@ -0,0 +1,31 @@
+//! Microchip vendor support.
+
+use probe_rs_target::Chip;
+
+use crate::{
+    config::DebugSequence,
+    vendor::{microchip::sequences::atsam::AtSAM, Vendor},
+};
+
+pub mod sequences;
+
+/// Microchip
+#[derive(docsplay::Display)]
+pub struct Microchip;
+
+impl Vendor for Microchip {
+    fn try_create_debug_sequence(&self, chip: &Chip) -> Option<DebugSequence> {
+        let sequence = if chip.name.starts_with("ATSAMD1")
+            || chip.name.starts_with("ATSAMD2")
+            || chip.name.starts_with("ATSAMDA")
+            || chip.name.starts_with("ATSAMD5")
+            || chip.name.starts_with("ATSAME5")
+        {
+            DebugSequence::Arm(AtSAM::create())
+        } else {
+            return None;
+        };
+
+        Some(sequence)
+    }
+}
diff --git a/probe-rs/src/config/sequences/atsam.rs b/probe-rs/src/vendor/microchip/sequences/atsam.rs
similarity index 100%
rename from probe-rs/src/config/sequences/atsam.rs
rename to probe-rs/src/vendor/microchip/sequences/atsam.rs
diff --git a/probe-rs/src/vendor/microchip/sequences/mod.rs b/probe-rs/src/vendor/microchip/sequences/mod.rs
new file mode 100644
index 00000000..762ebf75
--- /dev/null
+++ b/probe-rs/src/vendor/microchip/sequences/mod.rs
@@ -0,0 +1,3 @@
+//! Microchip debug sequences.
+
+pub mod atsam;
diff --git a/probe-rs/src/vendor/mod.rs b/probe-rs/src/vendor/mod.rs
new file mode 100644
index 00000000..067143d0
--- /dev/null
+++ b/probe-rs/src/vendor/mod.rs
@@ -0,0 +1,62 @@
+//! Vendor support modules.
+
+use std::ops::Deref;
+
+use once_cell::sync::Lazy;
+use parking_lot::{Mutex, MutexGuard};
+use probe_rs_target::Chip;
+
+use crate::config::DebugSequence;
+
+pub mod espressif;
+pub mod infineon;
+pub mod microchip;
+pub mod nordicsemi;
+pub mod nxp;
+pub mod silabs;
+pub mod st;
+pub mod ti;
+
+/// Vendor support trait.
+pub trait Vendor: Send + Sync + std::fmt::Display {
+    /// Tries to create a debug sequence for the given chip.
+    fn try_create_debug_sequence(&self, chip: &Chip) -> Option<DebugSequence>;
+}
+
+static VENDORS: Lazy<Mutex<Vec<Box<dyn Vendor>>>> = Lazy::new(|| {
+    let vendors: Vec<Box<dyn Vendor>> = vec![
+        Box::new(microchip::Microchip),
+        Box::new(infineon::Infineon),
+        Box::new(silabs::SiliconLabs),
+        Box::new(ti::TexasInstruments),
+        Box::new(espressif::Espressif),
+        Box::new(nordicsemi::NordicSemi),
+        Box::new(nxp::Nxp),
+        Box::new(st::St),
+    ];
+
+    Mutex::new(vendors)
+});
+
+/// Registers a new vendor.
+pub fn register_vendor(vendor: Box<dyn Vendor>) {
+    // Order matters. Prepend to allow users to override the default vendors.
+    VENDORS.lock().insert(0, vendor);
+}
+
+/// Returns a readable view of all known vendors.
+fn vendors<'a>() -> impl Deref<Target = [Box<dyn Vendor>]> + 'a {
+    MutexGuard::map(VENDORS.lock(), |v| v.as_mut_slice())
+}
+
+/// Tries to create a debug sequence for the given chip.
+pub fn try_create_debug_sequence(chip: &Chip) -> Option<DebugSequence> {
+    let vendors = vendors();
+    for vendor in vendors.iter() {
+        if let Some(sequence) = vendor.try_create_debug_sequence(chip) {
+            return Some(sequence);
+        }
+    }
+
+    None
+}
diff --git a/probe-rs/src/vendor/nordicsemi/mod.rs b/probe-rs/src/vendor/nordicsemi/mod.rs
new file mode 100644
index 00000000..53123ed0
--- /dev/null
+++ b/probe-rs/src/vendor/nordicsemi/mod.rs
@@ -0,0 +1,33 @@
+//! Nordic Semiconductor vendor support.
+
+use probe_rs_target::Chip;
+
+use crate::{
+    config::DebugSequence,
+    vendor::{
+        nordicsemi::sequences::{nrf52::Nrf52, nrf53::Nrf5340, nrf91::Nrf9160},
+        Vendor,
+    },
+};
+
+pub mod sequences;
+
+/// Nordic Semiconductor
+#[derive(docsplay::Display)]
+pub struct NordicSemi;
+
+impl Vendor for NordicSemi {
+    fn try_create_debug_sequence(&self, chip: &Chip) -> Option<DebugSequence> {
+        let sequence = if chip.name.starts_with("nRF5340") {
+            DebugSequence::Arm(Nrf5340::create())
+        } else if chip.name.starts_with("nRF52") {
+            DebugSequence::Arm(Nrf52::create())
+        } else if chip.name.starts_with("nRF9160") {
+            DebugSequence::Arm(Nrf9160::create())
+        } else {
+            return None;
+        };
+
+        Some(sequence)
+    }
+}
diff --git a/probe-rs/src/vendor/nordicsemi/sequences/mod.rs b/probe-rs/src/vendor/nordicsemi/sequences/mod.rs
new file mode 100644
index 00000000..a29b9031
--- /dev/null
+++ b/probe-rs/src/vendor/nordicsemi/sequences/mod.rs
@@ -0,0 +1,7 @@
+//! Nordic Semiconductor debug sequences.
+
+mod nrf;
+
+pub mod nrf52;
+pub mod nrf53;
+pub mod nrf91;
diff --git a/probe-rs/src/config/sequences/nrf.rs b/probe-rs/src/vendor/nordicsemi/sequences/nrf.rs
similarity index 100%
rename from probe-rs/src/config/sequences/nrf.rs
rename to probe-rs/src/vendor/nordicsemi/sequences/nrf.rs
diff --git a/probe-rs/src/config/sequences/nrf52.rs b/probe-rs/src/vendor/nordicsemi/sequences/nrf52.rs
similarity index 100%
rename from probe-rs/src/config/sequences/nrf52.rs
rename to probe-rs/src/vendor/nordicsemi/sequences/nrf52.rs
diff --git a/probe-rs/src/config/sequences/nrf53.rs b/probe-rs/src/vendor/nordicsemi/sequences/nrf53.rs
similarity index 100%
rename from probe-rs/src/config/sequences/nrf53.rs
rename to probe-rs/src/vendor/nordicsemi/sequences/nrf53.rs
diff --git a/probe-rs/src/config/sequences/nrf91.rs b/probe-rs/src/vendor/nordicsemi/sequences/nrf91.rs
similarity index 100%
rename from probe-rs/src/config/sequences/nrf91.rs
rename to probe-rs/src/vendor/nordicsemi/sequences/nrf91.rs
diff --git a/probe-rs/src/vendor/nxp/mod.rs b/probe-rs/src/vendor/nxp/mod.rs
new file mode 100644
index 00000000..b7a8ca63
--- /dev/null
+++ b/probe-rs/src/vendor/nxp/mod.rs
@@ -0,0 +1,38 @@
+//! NXP vendor support.
+
+use probe_rs_target::Chip;
+
+use crate::{
+    config::DebugSequence,
+    vendor::{
+        nxp::sequences::{
+            nxp_armv7m::{MIMXRT10xx, MIMXRT11xx},
+            nxp_armv8m::{LPC55Sxx, MIMXRT5xxS},
+        },
+        Vendor,
+    },
+};
+
+pub mod sequences;
+
+/// NXP
+#[derive(docsplay::Display)]
+pub struct Nxp;
+
+impl Vendor for Nxp {
+    fn try_create_debug_sequence(&self, chip: &Chip) -> Option<DebugSequence> {
+        let sequence = if chip.name.starts_with("MIMXRT10") {
+            DebugSequence::Arm(MIMXRT10xx::create())
+        } else if chip.name.starts_with("MIMXRT11") {
+            DebugSequence::Arm(MIMXRT11xx::create())
+        } else if chip.name.starts_with("MIMXRT5") {
+            DebugSequence::Arm(MIMXRT5xxS::create())
+        } else if chip.name.starts_with("LPC55S") {
+            DebugSequence::Arm(LPC55Sxx::create())
+        } else {
+            return None;
+        };
+
+        Some(sequence)
+    }
+}
diff --git a/probe-rs/src/vendor/nxp/sequences/mod.rs b/probe-rs/src/vendor/nxp/sequences/mod.rs
new file mode 100644
index 00000000..2240da06
--- /dev/null
+++ b/probe-rs/src/vendor/nxp/sequences/mod.rs
@@ -0,0 +1,4 @@
+//! NXP debug sequences.
+
+pub mod nxp_armv7m;
+pub mod nxp_armv8m;
diff --git a/probe-rs/src/config/sequences/nxp_armv7m.rs b/probe-rs/src/vendor/nxp/sequences/nxp_armv7m.rs
similarity index 100%
rename from probe-rs/src/config/sequences/nxp_armv7m.rs
rename to probe-rs/src/vendor/nxp/sequences/nxp_armv7m.rs
diff --git a/probe-rs/src/config/sequences/nxp_armv8m.rs b/probe-rs/src/vendor/nxp/sequences/nxp_armv8m.rs
similarity index 100%
rename from probe-rs/src/config/sequences/nxp_armv8m.rs
rename to probe-rs/src/vendor/nxp/sequences/nxp_armv8m.rs
diff --git a/probe-rs/src/vendor/silabs/mod.rs b/probe-rs/src/vendor/silabs/mod.rs
new file mode 100644
index 00000000..0961e19a
--- /dev/null
+++ b/probe-rs/src/vendor/silabs/mod.rs
@@ -0,0 +1,31 @@
+//! Silicon Labs vendor support.
+
+use probe_rs_target::Chip;
+
+use crate::{
+    config::DebugSequence,
+    vendor::{silabs::sequences::efm32xg2::EFM32xG2, Vendor},
+};
+
+pub mod sequences;
+
+/// Silicon Labs
+#[derive(docsplay::Display)]
+pub struct SiliconLabs;
+
+impl Vendor for SiliconLabs {
+    fn try_create_debug_sequence(&self, chip: &Chip) -> Option<DebugSequence> {
+        let sequence = if chip.name.starts_with("EFM32PG2")
+            || chip.name.starts_with("EFR32BG2")
+            || chip.name.starts_with("EFR32FG2")
+            || chip.name.starts_with("EFR32MG2")
+            || chip.name.starts_with("EFR32ZG2")
+        {
+            DebugSequence::Arm(EFM32xG2::create())
+        } else {
+            return None;
+        };
+
+        Some(sequence)
+    }
+}
diff --git a/probe-rs/src/config/sequences/efm32xg2.rs b/probe-rs/src/vendor/silabs/sequences/efm32xg2.rs
similarity index 100%
rename from probe-rs/src/config/sequences/efm32xg2.rs
rename to probe-rs/src/vendor/silabs/sequences/efm32xg2.rs
diff --git a/probe-rs/src/vendor/silabs/sequences/mod.rs b/probe-rs/src/vendor/silabs/sequences/mod.rs
new file mode 100644
index 00000000..b3e70ba6
--- /dev/null
+++ b/probe-rs/src/vendor/silabs/sequences/mod.rs
@@ -0,0 +1,3 @@
+//! Silicon Labs debug sequences.
+
+pub mod efm32xg2;
diff --git a/probe-rs/src/vendor/st/mod.rs b/probe-rs/src/vendor/st/mod.rs
new file mode 100644
index 00000000..7b8a7e41
--- /dev/null
+++ b/probe-rs/src/vendor/st/mod.rs
@@ -0,0 +1,51 @@
+//! STMicroelectronics vendor support.
+
+use probe_rs_target::Chip;
+
+use crate::{
+    config::DebugSequence,
+    vendor::{
+        st::sequences::{
+            stm32_armv6::{Stm32Armv6, Stm32Armv6Family},
+            stm32_armv7::Stm32Armv7,
+            stm32h7::Stm32h7,
+        },
+        Vendor,
+    },
+};
+
+pub mod sequences;
+
+/// STMicroelectronics
+#[derive(docsplay::Display)]
+pub struct St;
+
+impl Vendor for St {
+    fn try_create_debug_sequence(&self, chip: &Chip) -> Option<DebugSequence> {
+        let sequence = if chip.name.starts_with("STM32F0") {
+            DebugSequence::Arm(Stm32Armv6::create(Stm32Armv6Family::F0))
+        } else if chip.name.starts_with("STM32L0") {
+            DebugSequence::Arm(Stm32Armv6::create(Stm32Armv6Family::L0))
+        } else if chip.name.starts_with("STM32G0") {
+            DebugSequence::Arm(Stm32Armv6::create(Stm32Armv6Family::G0))
+        } else if chip.name.starts_with("STM32F1")
+            || chip.name.starts_with("STM32F2")
+            || chip.name.starts_with("STM32F3")
+            || chip.name.starts_with("STM32F4")
+            || chip.name.starts_with("STM32F7")
+            || chip.name.starts_with("STM32G4")
+            || chip.name.starts_with("STM32L1")
+            || chip.name.starts_with("STM32L4")
+            || chip.name.starts_with("STM32WB")
+            || chip.name.starts_with("STM32WL")
+        {
+            DebugSequence::Arm(Stm32Armv7::create())
+        } else if chip.name.starts_with("STM32H7") {
+            DebugSequence::Arm(Stm32h7::create())
+        } else {
+            return None;
+        };
+
+        Some(sequence)
+    }
+}
diff --git a/probe-rs/src/vendor/st/sequences/mod.rs b/probe-rs/src/vendor/st/sequences/mod.rs
new file mode 100644
index 00000000..6856df28
--- /dev/null
+++ b/probe-rs/src/vendor/st/sequences/mod.rs
@@ -0,0 +1,5 @@
+//! STMicroelectronics debug sequences.
+
+pub mod stm32_armv6;
+pub mod stm32_armv7;
+pub mod stm32h7;
diff --git a/probe-rs/src/config/sequences/stm32_armv6.rs b/probe-rs/src/vendor/st/sequences/stm32_armv6.rs
similarity index 100%
rename from probe-rs/src/config/sequences/stm32_armv6.rs
rename to probe-rs/src/vendor/st/sequences/stm32_armv6.rs
diff --git a/probe-rs/src/config/sequences/stm32_armv7.rs b/probe-rs/src/vendor/st/sequences/stm32_armv7.rs
similarity index 100%
rename from probe-rs/src/config/sequences/stm32_armv7.rs
rename to probe-rs/src/vendor/st/sequences/stm32_armv7.rs
diff --git a/probe-rs/src/config/sequences/stm32h7.rs b/probe-rs/src/vendor/st/sequences/stm32h7.rs
similarity index 100%
rename from probe-rs/src/config/sequences/stm32h7.rs
rename to probe-rs/src/vendor/st/sequences/stm32h7.rs
diff --git a/probe-rs/src/vendor/ti/mod.rs b/probe-rs/src/vendor/ti/mod.rs
new file mode 100644
index 00000000..6fdcaa97
--- /dev/null
+++ b/probe-rs/src/vendor/ti/mod.rs
@@ -0,0 +1,26 @@
+//! Texas Instruments vendor support.
+
+use probe_rs_target::Chip;
+
+use crate::{
+    config::DebugSequence,
+    vendor::{ti::sequences::cc13xx_cc26xx::CC13xxCC26xx, Vendor},
+};
+
+pub mod sequences;
+
+/// Texas Instruments
+#[derive(docsplay::Display)]
+pub struct TexasInstruments;
+
+impl Vendor for TexasInstruments {
+    fn try_create_debug_sequence(&self, chip: &Chip) -> Option<DebugSequence> {
+        let sequence = if chip.name.starts_with("CC13") || chip.name.starts_with("CC26") {
+            DebugSequence::Arm(CC13xxCC26xx::create(chip.name.clone()))
+        } else {
+            return None;
+        };
+
+        Some(sequence)
+    }
+}
diff --git a/probe-rs/src/config/sequences/cc13xx_cc26xx.rs b/probe-rs/src/vendor/ti/sequences/cc13xx_cc26xx.rs
similarity index 91%
rename from probe-rs/src/config/sequences/cc13xx_cc26xx.rs
rename to probe-rs/src/vendor/ti/sequences/cc13xx_cc26xx.rs
index c2046841..6133eee0 100644
--- a/probe-rs/src/config/sequences/cc13xx_cc26xx.rs
+++ b/probe-rs/src/vendor/ti/sequences/cc13xx_cc26xx.rs
@@ -12,7 +12,10 @@ use crate::MemoryMappedRegister;
 
 /// Marker struct indicating initialization sequencing for cc13xx_cc26xx family parts.
 #[derive(Debug)]
-pub struct CC13xxCC26xx {}
+pub struct CC13xxCC26xx {
+    // Chip name
+    name: String,
+}
 
 // IR register values, see https://www.ti.com/lit/ug/swcu185f/swcu185f.pdf table 6-7
 const IR_ROUTER: u64 = 0x02;
@@ -37,8 +40,8 @@ fn set_n_bits(x: u32) -> u64 {
 
 impl CC13xxCC26xx {
     /// Create the sequencer for the cc13xx_cc26xx family of parts.
-    pub fn create() -> Arc<Self> {
-        Arc::new(Self {})
+    pub fn create(name: String) -> Arc<Self> {
+        Arc::new(Self { name })
     }
 
     /// This function implements a Zero Bit Scan(ZBS)
@@ -306,6 +309,38 @@ impl CC13xxCC26xx {
     }
 }
 
+/// Do a full system reset (emulated PIN reset)
+///
+/// CPU reset alone is not possible since AIRCR.SYSRESETREQ will be
+/// converted to system reset on these devices.
+///
+/// The below code writes to the following bit
+/// `AON_PMCTL.RESETCTL.SYSRESET=1`d or its equivalent based on family
+fn reset_chip(chip: &str, probe: &mut dyn ArmProbe) {
+    // The CC family of device have a pattern where the 6th character of the device name dictates the family
+    // Use this to determine the correct address to write to
+    match chip.chars().nth(5).unwrap() {
+        // Note that errors are ignored
+        // writing this register will immediately trigger a system reset which causes us to lose the debug interface
+        // We also don't need to worry about preserving register state because we will anyway reset.
+        '0' => {
+            probe.write_word_32(0x4009_0004, 0x8000_0000).ok();
+        }
+        '1' | '2' => {
+            probe.write_word_32(0x4009_0028, 0x8000_0000).ok();
+        }
+        '4' => {
+            probe.write_word_32(0x5809_0028, 0x8000_0000).ok();
+        }
+        _ => {
+            unreachable!(
+                "TI CC13xx/CC26xx debug sequence used on an unsupported chip: {chip}",
+                chip = chip
+            );
+        }
+    }
+}
+
 impl ArmDebugSequence for CC13xxCC26xx {
     fn reset_system(
         &self,
@@ -316,13 +351,8 @@ impl ArmDebugSequence for CC13xxCC26xx {
         // Check if the previous code requested a halt before reset
         let demcr = Demcr(probe.read_word_32(Demcr::get_mmio_address())?);
 
-        // Do a full system reset (emulated PIN reset)
-        // CPU reset alone is not possible since AIRCR.SYSRESETREQ will be
-        // converted to system reset on these devices.
-        //
-        // The below code writes to the following bit
-        // AON_PMCTL.RESETCTL.SYSRESET=1
-        probe.write_word_32(0x4009_0028, 0x8000_0000).ok();
+        // Do target specific reset
+        reset_chip(&self.name, probe);
 
         // Since the system went down, including the debug, we should flush any pending operations
         probe.flush().ok();
diff --git a/probe-rs/src/vendor/ti/sequences/mod.rs b/probe-rs/src/vendor/ti/sequences/mod.rs
new file mode 100644
index 00000000..f25d12b6
--- /dev/null
+++ b/probe-rs/src/vendor/ti/sequences/mod.rs
@@ -0,0 +1,3 @@
+//! Texas Instruments debug sequences.
+
+pub mod cc13xx_cc26xx;
diff --git a/probe-rs/targets/CC13XX_CC26XX_Series.yaml b/probe-rs/targets/CC13XX_CC26XX_Series.yaml
index d7109fcf..fa4f26a9 100644
--- a/probe-rs/targets/CC13XX_CC26XX_Series.yaml
+++ b/probe-rs/targets/CC13XX_CC26XX_Series.yaml
@@ -219,6 +219,202 @@ variants:
       - main
   flash_algorithms:
   - cc13x2-cc26x2-flash
+##################### cc13x4_cc26x4 ############################################
+- name: CC1314R10
+  cores:
+  - name: main
+    type: armv8m
+    core_access_options:
+      !Arm
+        ap: 0
+        psel: 0
+  jtag:
+    scan_chain:
+      - name: core0
+        ir_len: 4
+      - name: ICEPICK
+        ir_len: 6
+  memory_map:
+  - !Ram
+      range:
+        start: 0x20000000
+        end: 0x20010000
+      is_boot_memory: false
+      cores:
+      - main
+  - !Nvm
+      range:
+        start: 0
+        end: 0x100000
+      is_boot_memory: true
+      cores:
+      - main
+  - !Nvm
+      range:
+        start: 0x50000000
+        end: 0x50000800
+      is_boot_memory: false
+      cores:
+      - main
+  flash_algorithms:
+  - cc13x4-cc26x4-flash
+  - cc13x4-cc26x4-flash-ccfg
+- name: CC1354R10
+  cores:
+  - name: main
+    type: armv8m
+    core_access_options:
+      !Arm
+        ap: 0
+        psel: 0
+  jtag:
+    scan_chain:
+      - name: core0
+        ir_len: 4
+      - name: ICEPICK
+        ir_len: 6
+  memory_map:
+  - !Ram
+      range:
+        start: 0x20000000
+        end: 0x20010000
+      is_boot_memory: false
+      cores:
+      - main
+  - !Nvm
+      range:
+        start: 0
+        end: 0x100000
+      is_boot_memory: true
+      cores:
+      - main
+  - !Nvm
+      range:
+        start: 0x50000000
+        end: 0x50000800
+      is_boot_memory: false
+      cores:
+      - main
+  flash_algorithms:
+  - cc13x4-cc26x4-flash
+  - cc13x4-cc26x4-flash-ccfg
+- name: CC1354P10
+  cores:
+  - name: main
+    type: armv8m
+    core_access_options:
+      !Arm
+        ap: 0
+        psel: 0
+  jtag:
+    scan_chain:
+      - name: core0
+        ir_len: 4
+      - name: ICEPICK
+        ir_len: 6
+  memory_map:
+  - !Ram
+      range:
+        start: 0x20000000
+        end: 0x20010000
+      is_boot_memory: false
+      cores:
+      - main
+  - !Nvm
+      range:
+        start: 0
+        end: 0x100000
+      is_boot_memory: true
+      cores:
+      - main
+  - !Nvm
+      range:
+        start: 0x50000000
+        end: 0x50000800
+      is_boot_memory: false
+      cores:
+      - main
+  flash_algorithms:
+  - cc13x4-cc26x4-flash
+  - cc13x4-cc26x4-flash-ccfg
+- name: CC2674R10
+  cores:
+  - name: main
+    type: armv8m
+    core_access_options:
+      !Arm
+        ap: 0
+        psel: 0
+  jtag:
+    scan_chain:
+      - name: core0
+        ir_len: 4
+      - name: ICEPICK
+        ir_len: 6
+  memory_map:
+  - !Ram
+      range:
+        start: 0x20000000
+        end: 0x20010000
+      is_boot_memory: false
+      cores:
+      - main
+  - !Nvm
+      range:
+        start: 0
+        end: 0x100000
+      is_boot_memory: true
+      cores:
+      - main
+  - !Nvm
+      range:
+        start: 0x50000000
+        end: 0x50000800
+      is_boot_memory: false
+      cores:
+      - main
+  flash_algorithms:
+  - cc13x4-cc26x4-flash
+  - cc13x4-cc26x4-flash-ccfg
+- name: CC2674P10
+  cores:
+  - name: main
+    type: armv8m
+    core_access_options:
+      !Arm
+        ap: 0
+        psel: 0
+  jtag:
+    scan_chain:
+      - name: core0
+        ir_len: 4
+      - name: ICEPICK
+        ir_len: 6
+  memory_map:
+  - !Ram
+      range:
+        start: 0x20000000
+        end: 0x20010000
+      is_boot_memory: false
+      cores:
+      - main
+  - !Nvm
+      range:
+        start: 0
+        end: 0x100000
+      is_boot_memory: true
+      cores:
+      - main
+  - !Nvm
+      range:
+        start: 0x50000000
+        end: 0x50000800
+      is_boot_memory: false
+      cores:
+      - main
+  flash_algorithms:
+  - cc13x4-cc26x4-flash
+  - cc13x4-cc26x4-flash-ccfg
 flash_algorithms:
 - name: cc13x2-cc26x2-flash
   description: Flash algorithm for the cc13x2-cc26x2 family of devices
@@ -246,3 +442,55 @@ flash_algorithms:
       address: 0x0
   cores:
   - main
+- name: cc13x4-cc26x4-flash
+  description: Flash algorithm for the cc13x4-cc26x4 family of devices
+  default: true
+  # Generated using https://github.com/seanmlyons22/cc13x4-cc26x4-flash/commit/acfb188a8e27a889d8baafdc8a98381c04efc41b
+  instructions: 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AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA1NTU
+  load_address: 0x20000020
+  pc_init: 0x1
+  pc_uninit: 0x12d
+  pc_program_page: 0x1c7
+  pc_erase_sector: 0x16f
+  pc_erase_all: 0x235
+  data_section_offset: 0x20002264
+  rtt_location: 0x20001e24
+  flash_properties:
+    address_range:
+      start: 0x0
+      end: 0x100000
+    page_size: 0x800
+    erased_byte_value: 0xff
+    program_page_timeout: 1000
+    erase_sector_timeout: 2000
+    sectors:
+    - size: 0x800
+      address: 0x0
+  cores:
+  - main
+- name: cc13x4-cc26x4-flash-ccfg
+  description: Flash algorithm for the cc13x4-cc26x4 family of devices
+  default: false
+  # Generated using https://github.com/seanmlyons22/cc13x4-cc26x4-flash/commit/acfb188a8e27a889d8baafdc8a98381c04efc41b
+  instructions: 8LUDry3pAAuIsEH2IGlE8gAIwvIACRVGmfgAAMTyAwgBKA7RQfawMQAgwvIAAQciAPA3/kBGASEBIgDwRfkA8Iz+aR4BIAMpifgAAFjSQfYkZTAhwvIABShGAfA1/QXxGAZB9uwhQfZUY0/0gGAAkMLyAAHC8gADMEYAIgDwNv0oRgEhACIBJADwGv0wRgDw6v0A8FD+QvJgIcLyAAEKeFKxAPBT/kH29SEAIMLyAAEyIgDw9v0V4AxwAPBH/kH2JzEAIMLyAAEfIgDw6v1C8igAAyHF9ggAwPgQQcD4DEEEYgFgAPA3/kBGACEBIgDw6/iJ+ABAACAIsL3oAAvwvQOQQfYMQMLyAAAAIQKQQfbsIMLyAAAGkQWRQfYUQQSQAqjC8gABAPAx/v7esLUCr0H2IGXC8gAFKHgBKBXRQfawMQAgwvIAAQciACQA8Kn9RPIAAAEhxPIDAAEiAPC0+ADw+/0gRixwsL0BILC9AUZB9iBgwvIAAAJ4ASABKhi/cEeAtW9GirABkQAhBpECIQWQCKgDkUH2cDEEkEHycXDC8gABwvIAAAKRAqkJkAGoCJAAIADwjv0BmADwqfkKsL3ogEBwR7C1Aq+MsAxGQfYgYcLyAAEJeAEpKNEAkAAgBZADIAKQQfaYMMLyAAABqQGQAiAEkAeoA5BB8nFwwvIAABVGCpALqAmQQfJxcMLyAAALlAiQaEYHkAAgAPBa/QCZKEYiRgDwa/gMsLC9ASAMsLC98LUDry3pAA+JsEH2IGABJMLyAAAAeAEoMNFB9kYxACDC8gABCiIA8CL9QfZwO0HycXgHrgGtwvIAC8LyAAjpRgAhQPYAAAH1AGrA8hAAgkUZ0AAgAJEFkAIgzekBsAAgKUbN6QNkzekHmADwGf0AmADwNPkAKFFG49AERiBGCbC96AAP8L0AJCBGCbC96AAP8L3U1ND4AMBf6gxz+tQB8AMBDPADA5lCD9BDaAAqI/ADAxlEQWAIv3BHRPIAAMTyAwAAvwFoCQf81HBHLenwT4ewFEYNRkLyABcRRCwixfYDB8X2AwKCRtf48AISaNf48DIC9GBym7JaQ5FCg7IP2UzyAEC18aBPwPIhAAaQH9MD8aBAgUIb2AAmT/AACSfg1/gAAwAmb/MfMFhDBZCoQkzyAEDX+AAjwPIhAKD1AHCYvwEmb/MfMgaQT/ABCQ/gQPYAAsXyAAKVQk/wBQDA8LqAGkSRQgDytoBP8AAJASbX+PQSACAALPhiAPCsgAWYT/ABCAE4A5DB88QAAZABOAKSAJAEkwC/BSC58QAPeGDH+ACADNC1+/PwHygc2Nf40BAI+gDwIeoAAMf40AAo4AAuD78F8TBAT/YAAMr2/3AoRLD78/HX+BAhCPoB8SLqAQHH+BARFuACmYhCB9IgOMAI1/jUEBAiAvoA8AXgQBrACNf41BAI+gDwIeoAAMf41AADmChAAAk4YhAgBpmwQAhDqQa4YAvRQCwJ01BGKUZAIk/wQAsA8Cf5ISAh4AC/6AYL0SAsCdNQRilGICJP8CALAPAZ+REgE+AAvwCYKUYF6gACAZig6wILX/qL8KBCiL+jRlL6i/BDHlBGAPAg+QEgeGDH+ACA1/jQAsAH+9DX+NACBCEB6kAA0LFf+ovwJBoFmFX6i/WFQk/wAAAovwEgAC4YvzBGufEADxi/BkYEm1r6i/oALE/wAAB/9GSvAeCA8AQAB7C96PCPELVC8gASxfYDAtL48BJP8P8zE/qB8QFCAtAGIQhGEL0sIcX2AwEJaNL48DIB9GBxm7JZQ4FCJ9nS+AAT0vjwMm/zHzGbstL48EID+wH8BSFP8AEOUWDC+ADgo7LS+AATsPvz82/zHzEAJItCKL8BJB8rrPEBDCnY0vjQEA76A/OZQ8L40BBf4LDxoE9O09L48BLF8gABgUIg2dL48BIA8TBOibIFI0/wAQy++/HxU2DC+ADA0vgQMQz6AfEj6gEBwvgQEQAkT/SAY0Pgi0Iv0qPxIAHJCE/wEA4t4EDy/3HF8gABiEIh2dL48DIR+oPxATGBQk/wBQGQ2dL48EKg8aBDo/UAbqOyT/ABDL778/NRYML4AMDS+BARDPoD85lDwvgQEU/0gGMBJBPgBSF151kayQhP8AEO0vjUMA76AfEj6gEBwvjUEAzqAA5P8AAMT/QAc0/qHhERYhAhoUAZQwH14DGRYEIhUWABIRFgAL/S+NASyQf70NL40BIEIgAsjPABAxi/ASQjQyLqQQEIvwApf/RDrwDxKAFB9iRAwvIAABgivegQQP/3Ir4QtULyJBwURsX2AwxP9v9+kwjM+ADgDdAB8DABYUQBMAwxAL9Q+AEsATtB+AQrAPEEAPfRIEYQvS3p8EOEsAAlmkJP8AAOT/AABE/wAAZP8AAMzekCVc3pAFUh2E/qwg5P8AAMT/ABCOlGAL8I+gL2TOoGDCLwAwYQ+AF7DvAYBFn4BlAH+gT0ATIsQ5pCDvEIDkn4BkDp2d3pAGTd6QLlAfAwAELyJBHF9gMBCETGYARhwPgU4IVhwfgAwASwvejwg4C1b0aCsAAigCkBkgPSjfgEEAEjNOCx9QBvCdICIwpGY/OfEo34BSDAIkLqkREl4AIiC0Zi858TsfWAPw3SjfgGMIsJYvOfE434BTDgIkLqETGN+AQQAyMS4I34BzCLCWLznxON+AYwCwuJDGLznxON+AUwHiIEI2Lz3wGN+AQQUPgEHxQx0eivHwHwAwLRHhi/EUYBqgDwevkAIAKwgL0KRkH2PEHC8gABAPDduvC1A69N+AS9DEYFRgDwkfoGRgDwivooRqBH8AcF0V34BLu96PBAAPCCul34BLvwvfC1A68t6QALkrBC8lQogUbC8gAIBnjY+AQAAPEMAgDxEAPS6K8f0+ivX4RooUIxv6VCACHC6K8fw+ivHwAizekJEpj4CBCN+CwgsUIIkDDQ2PgEAETyQ0HE8kVhDKoUMAIj0OivDw+RQ/Y4EcTyQSEA8AMADpFD8jRRw/I2cQ2RQ/IwEcPyMjEMkQbwDwFRXP8ijfgEIMIejfgFEBi/AkZRHhi/EUYIqAGqAPAB+Yj4CGAIqw3xDAw3+ENcD8uM6A4ACPEIAQyq2fgEwBf4QUwBkRFGjfgYYAKQrfgZUI34G0Cc6HlAgeh5QEH2PEEBqMLyAAEA8En6nfgUAAIoB9EBmJ34GBABcBKwvegAC/C93ekCAQwwwOivHxKwvegAC/C98LUDry3pAAuMsELyVCiBRsLyAAgGeNj4BAAA8QwCAPEQA9Lorx/T6K9fhGihQjG/pUIAIcLorx/D6K8fACLN6QgSmPgIEI34KCCxQgeQMdDY+AQARPJDQcTyRWEBqhQwAiPQ6K8PBJFD9jgRxPJBIQDwAwADkUPyNFHD8jZxApFD8jARw/IyMQGRBvAPAVFc/yIH+Boswh4H+BkcGL8CRlEeGL8RRgeop/EaAgDwb/iI+AhgB6sN8QgMnfgeUA/LvfgcQIzoDwAUMNn4BBCN+Bhg0ekAIwjxCAEBka34GUCN+BtQ0OivDwDwAwDBHhi/AUYBqAQwAPBK+J34FAACKAfRAZid+BgQAXAMsL3oAAvwvd3pAgEMMMDorx8MsL3oAAvwvQF8AikBvwFoAH0IcHBH0OkBEgwxweivLwIhAXRwRwFhRPJTUcTyR3FF8kUjAWBfIQFxACFCYUXyVELF8iAjv/Nfj8DpATLBYHBH3fgAwAFgwPgIwADxFAzc6K8fIfADARFDzOivH0NgcEfwtQOvLekAD4Gwg0YAewAob9GZRgArbNBP8AAM2/gAAADxDAQA8RAD1Oivb9Por1+AaIZCOL+FQgzTxOivz8Por8/b+ASgUEVX01bQb+oKA8YYDeDb+ASgVUUD2W/qCgAuGAXgAC3u0FBFUtOg6woGTkUov05GhrkCKQrR2/gAAADxDAPD6K+vm/gMMAArx9Av4FmzASCL+AwA2/gAgAxGFUYRRjJG2PgEAFBEAPCE/xrrBgA50tv4CBDL+AQAiRk/0tj4CCBP8AAMy/gIEJBCKL/L+ATAm/gMAFC5Kka56wYJBesGAiFGmNEC4AIgi/gMAAGwvegAD/C9QfbgQEH2JFLC8gAAwvIAAiEhAPDO+P7eQfbgQEH2NFLC8gAAwvIAAiEhAPDC+P7eQfbAQEH2BFLC8gAAwvIAAhwhAPC2+P7eQfbAQEH2FFLC8gAAwvIAAhwhAPCq+P7e8LUDr034BL0ERgDwdfgFRgDwbvhC8lQmACDC8gAGMHLoB3RgCL8A8GX4QfZEUMLyAADG6K8PXfgEu/C9gLVvRoSwzekAEkLyVCHC8gAB0eivH1GxakaN+AgAA5ICqApoQPafEcLyAAGQRwSwgL1C8lQiwvIAAtLory8AKgi/cEeAtW9GgrABkUD2fQGN+AAAwvIAARJoaEaQRwKwveiAQHBHgLVvRpRGCkZQ+AQfFDHR6K8fAfADA9keGL8ZRmNG//fx/gAggL2AtW9GAN7+3tC1Aq8A8BD4ASEh6gAEAPAH+CBG0L0AKAi/cEcA8AK4crZwR2K2cEfv8xCAcEcAaP7ncEdN9pZQQPYoQUTy5yJD8khjw/YXYMbyTDHG9lBCwPLZA3BHgLVvRoawzekDAUH2SFDC8gAAASICkEH2SFDC8gAArfgUIAGQAaj/97z//t6AtW9GiLCURgEiAZIGqgCSQfZIUgAjzekGAcLyAAJoRmFGBJMDkwKS//fU//7e8LUDry3pAA+NsAMjFmmN+DAwICMIkwAjAC7N6QoTCZAGkwSTb9BQaQAoAPCSgNL4AKBBAdL4CLABOCDweEBP8AAIT/AACQEwApIDkQGQCusJAEJoQrHd6QkDWvgJENtomEcAKEDwhoAG6wgAA2kG64kA0OkCIQV/hGkIk434MFALlHqxASoI0QvrwQJA9oVTwvIAA1JomkIB0AAiA+Bb+DEQCWgBIlb4CDDN6QQhQWh7sQErCNEL68ECQPaFU8LyAANSaJpCAdAAIgPgW/gxEAloASJDaQeRC+vDAVv4MwBLaASpBpKYRwAoQ9EDmAjxIAgJ8QgJQEWp0d3pAaJQaIJFK9M74NBoGLOUaA3xEAgRaJFGBOvABgE4IPBgQA0dAPEBCipoMrHd6QkDVfgEHNtomEfwudTpAAJBRpBHyLkINAg1tELt0UpGUGiCRQXTFeBP8AAKUGiCRRDSEmhWRt3pCQHLaFL4OhAC68oCUmiYRyCxASANsL3oAA/wvQAgDbC96AAP8L3U1PC1A68t6QAPh7D9aBxGFkaBRgApTdDZ+ByAKyEY8AEACL9P9IgRAOsFCgaR1/gIsF/qSHBI1RAsgPB2gAAsAPCggATwAwyiCE/wAAAA8KqAoOuCDnMczekDS6tGb/ADBQWWXhkENZb5A0CW+QYQlvkFIBTxQQ+W+QRgyL8BMBbxQQ/IvwEwEvFBD8i/ATAR8UEPDusFAci/ATAEMeHRKh1dRgWe3ekDS7zxAA9A8H6AlOAtINn4HIAF8QEKBpDX+AiwX+pIcLbUACbZ+AAAACgA8IqAzekEttn4BLDTRRDZX+oIcCXUmfggAKvrCgoDlaNG3+gA8JYCkAJQRk/wAAqP4CBGLkbZ6QVFAJApRt3pBTIgRgDw1PhIs0/wAQhARgewvegAD/C9MEYhRgDw6/hU4Nn4EAAhRgOQMCCZ+CAgT/ABCC5G2ekFRcn4EAACkon4IIAgRgCRKUbd6QUyAPCv+ICxQEYHsL3oAA/wvetoIEYEmTJGB7C96AAPvejwQBhHACAq4KvrCgDd+BCgsEZGHAE+btAqaSBGMCGQRwAo99Bt4AAivPEADxfQsVYR8UEPyL8BMLzxAQ8P0DJEkvkBEBHxQQ/IvwEwvPECDwXQkvkCEBHxQQ/IvwEwgkTZ+AAAACh/9HavIEaoRtnpBUUzRgCQKUYGmiBGAPBh+AAoPtHraCBGWUZCRgewvegAD73o8EAYRwrxAQFP6loAT+pRCtnpBFhEHNn4GGABPAbQMmlARilGkEcAKPfQIODN+ACwQEbd6QUyMUYA8Dn4uLnzaEBG3ekDIZhHiLkAJKJFItAyaUBGKUaQRwE0ACj20GAeGuDraCBGUUZCRphHMLFP8AEIQEYHsL3oAA/wvQKYT/AACIn4IAADmMn4EABARgewvegAD/C9UEZP8AAIUEU4v0/wAQhARgewvegAD/C98LUDr034BI3X+AiAHEYNRgZGsvWIHwjQK2kwRhFGmEcYsQEgXfgEi/C9RLHraDBGIUZCRl34BIu96PBAGEcAIF34BIvwvfC1A68t6QAPgrCERgMwIPADCqrrDAhBRQrToesIDl/qngkF0A7wAwPiRVvRACCh4ImzAfADCIsIT/AAADDQoOuDDgzxAQZv8AMFchkENZL5AxCS+QZAkvkFMBHxQQ+S+QQgyL8BMA7rBQES8UEPyL8BMBPxQQ/IvwEwFPFBD8i/ATAEMeHRKx248QAPDNECsL3oAA/wvQAgArC96AAP8L0AI7jxAA/y0Bz5AxAR8UEPyL8BMLjxAQ/p0APrDAKS+QEQEfFBD8i/ATC48QIP3tCS+QIQEfFBD8i/ATACsL3oAA/wvW/qDABQRAMoAtIAIAAlI+AM8QELACBv8AMGC+sGBZX5A0CV+QYglfkFEBTxQQ+V+QRQyL8BMBXxQQ/IvwEwEfFBD8i/ATAS8UEPyL8BMBbxCAUG8QQG4NHiRRnQHPkFEBHxQQ/IvwEwuPEBDxDQBesMAZH5ASAS8UEPyL8BMLjxAg8F0JH5AhAR8UEPyL8BMAzrCAIAJsOxLvADARFEkfkAUBXxQQ/IvwEmASsN0JH5AVAV8UEPyL8BNgIrBdCR+QIQEfFBD8i/ATYwRE/w/xoR4AAmCuoWIybw/yYzRKnrDAkL64wCHPADAQPrA0MA6xNAQPBMgbnxAA8/9FavufHAD8xGKL9P8MAMk0a88QQP39Nv8A8BASIB64wBMCkC6xESBtIAJl1GEvADDkDwuIDQ5yLwAwoAJl1GAJLN+ASwlegKSLrxBApqaW/qAQhP6tgUROqREW/qAwQh8P4xT+rUFETqkxMxRCPw/jMLRG/qCwFP6tEULmlE6psUqWsk8P401fg8gCNEb+oOBE/q1BRE6p4UJPD+NATrAw5v6gYET+rUFKtpROqWFO5pJPD+NKZEb+oCBE/q1BRE6pISb+oDBCLw/jJP6tQUROqTE2/qBgSWRE/q1BQqakTqlhQj8P4znkQk8P40a2qmRG/qAgSuak/q1BRE6pISb+oDBCLw/jJP6tQUROqTE2/qBgSWRE/q1BTqakTqlhQj8P4znkQk8P40K2umRG/qAgRua0/q1BRE6pISb+oDBCLw/jJP6tQUROqTE3JEI/D+MxpEb+oGA0/q0xMF8UAFQ+qWEyPw/jMaRG/qAQNP6tMTQ+qRESHw/jERRG/qCAJP6tISQuqYEiLw/jIC6wEGf/RZr934BLBP8P8aAJoS8AMOP/Qar5XoHAG+8QEPb+oCAU/q0RFB6pIRb+oDAk/q0hIh8P4xQuqTEjFEIvD+MhFEb+oEAk/q0hJC6pQSIvD+MhFEb+oIAk/q0hJC6pgSIvD+MgLrAQY/9O+uBfEQCL7xAg+Y6A4Bb+oBBE/q1BRE6pERb+oCBCHw/jFP6tQUROqSEjFEIvD+MhFEb+oDAk/q0hJC6pMSIvD+MhFEb+oIAk/q0hJC6pgSIvD+MgLrAQY/9MKuIDUuzcxD5AlE6pERIfD+MTFE1kP2CUbqkhIi8P4yEUTaQ9IJQuqTEiLw/jIRROpD0glC6pUSIvD+MlYYouYM8PwDASlb+CMgb+oCBk/q1hZG6pISIvD+MhXQC+uDAwIpXmhv6gYFT+rVFUXqlhYm8P42MkQH0Jloy0PbCUPqkREh8P4xCkT/IcDyBwEB6hIhIvD/IhFEAesBQQDrEUACsL3oAA/wvfC1A68t6QAPjbAEaEH2WFBC8hB+DUZ0RcLyAAA900HyWXhO8v8Dp/FDCwAhzfK3GEHye0lP8GQKwPL1UwKVpPsIJQvrAQwEOZxCT+pVMgL7HkUURq6yT+qWBgb7CfZP6lZGBvsaVTD4FmCs+CNgrbIw+BVQrPglUOHYAp0B8ScDFEZjLAzYIUYKKRvTMPgRAJoep/FDAYhSGuAnI2Ms8tmhskHye0KJCAI7UUNkIqfxQwZJDAH7EkKSsjD4EiDyUgop49IB8TAAWh6n8UMBiFTC8ScAEURB9khSACPN6QAQwvIAAihGASH/95r7DbC96AAP8L0A8Ey4C0YRRhpGAPDruNKyBCk4v//39r/QtQKvT/ABM6HxBA5TQwEkBOueBBTwAwQK0IRGASxM+AQ7DdFgRnFGvvEMDxnSAuC+8QwPFdK96NBAYEb/99a/AixDYAbRCDkIMIRGvvEMDwfS8OeDYAw5DDCERr7xDA/p0xA5wOkAM8DpAjMQMAMp99iERr3o0EBgRv/3t78AIv/3ub/wtQOvLekABxAqY9NDQhPwAwQA6wQMFtADRg5GNXgD+AFbY0UP0nV4A/gBW2NFPr+1eAP4AVtjRQXS9XgENgP4AVtjRerTousEDgHrBAku8AMIDOsIA1/qiXE+0LjxAQ9U2xghKfADAgHqyQpP6skBAvEIBUlCEmgB8BgGVfgEHCL6CvIB+gb0IkNM+AQrnEU80ipoIfoK8QL6BvQhQ0z4BBucRT+/aWgi+gryAfoG9CJDPL9M+AQrnEUn0qpoIfoK8RA1AvoG9CFDTPgEG5xF1NMb4ANG8rkz4LjxAQ8V20xGIWhM+AQbnEUP0mFoTPgEG5xFPr+haEz4BBucRQXS4WgQNEz4BBucRerTCesIAQ7wAwKqsRpEDngD+AFrk0IP0k54A/gBa5NCPr+OeAP4AWuTQgXSzngEMQP4AWuTQurTvegAB/C90LUCrxAqNdNDQhPwAw4A6w4MD9ADRgP4ARtjRT+/A/gBG2NFA/gBG2NFA9ID+AEbY0Xw06LrDg4u8AMCDOsCAwEqE9vKsk/wATRiQ0z4BCucRQvSTPgEK5xFPL9M+AQrnEUD0kz4BCucRe/TDvADAhK5EuADRoKxGkQD+AEbk0IL0gP4ARuTQjy/A/gBG5NCA9ID+AEbk0Lv09C9//e+vv/3B79UZXJtaW5hbABJbml0OiBQZXJpcGhlcmFscyBhbHJlYWR5IGluaXRpYWxpemVkLCBkbyBub3RoaW5nCkluaXQ6IEluaXRpYWxpemluZyBwZXJpcGhlcmFscwpFcmFzZSBBbGwKc3JjL21haW4ucnNFcmFzZSBzZWN0b3IgYWRkcjoK1NRbGwAgEgAAAG0bACABAAAAUHJvZ3JhbSBQYWdlIGFkZHI6IHNpemU6gBsAIBIAAACSGwAgBgAAAG0bACABAAAARGVpbml0ClRoaXMgYnJhbmNoIGNhbiBvbmx5IGJlIHJlYWNoZWQgaWYgdGhlIGhvc3QgbGlicmFyeSBzZW50IGFuIHVua25vd24gZnVuY3Rpb24gY29kZS7U1NS3GwAgUgAAAFAbACALAAAAEAAAAAEAAAD////F///////////FxcX/xcXF/8XFxf/DCgAgGAAAAAQAAAApDQAgnwcAID8IACAvaG9tZS9zZWFubHlvbnMvLmNhcmdvL3JlZ2lzdHJ5L3NyYy9pbmRleC5jcmF0ZXMuaW8tNmYxN2QyMmJiYTE1MDAxZi9ydHQtdGFyZ2V0LTAuMy4xL3NyYy9ydHQucnPU1NTU1NTU1NTU1NRhdHRlbXB0IHRvIGFkZCB3aXRoIG92ZXJmbG931NTU1GF0dGVtcHQgdG8gc3VidHJhY3Qgd2l0aCBvdmVyZmxvd9TU1FQcACBgAAAA5wAAAA0AAABUHAAgYAAAAOgAAAANAAAAVBwAIGAAAAD6AAAADQAAAFQcACBgAAAA/AAAAA0AAABNCAAgiQ0AIAAAAAABAAAAiw0AIDAwMDEwMjAzMDQwNTA2MDcwODA5MTAxMTEyMTMxNDE1MTYxNzE4MTkyMDIxMjIyMzI0MjUyNjI3MjgyOTMwMzEzMjMzMzQzNTM2MzczODM5NDA0MTQyNDM0NDQ1NDY0NzQ4NDk1MDUxNTI1MzU0NTU1NjU3NTg1OTYwNjE2MjYzNjQ2NTY2Njc2ODY5NzA3MTcyNzM3NDc1NzY3Nzc4Nzk4MDgxODI4Mzg0ODU4Njg3ODg4OTkwOTE5MjkzOTQ5NTk2OTc5ODk5ANTU1AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA1NTU
+  load_address: 0x20000020
+  pc_init: 0x1
+  pc_uninit: 0x12d
+  pc_program_page: 0x1c7
+  pc_erase_sector: 0x16f
+  pc_erase_all: 0x235
+  data_section_offset: 0x20002264
+  rtt_location: 0x20001e24
+  flash_properties:
+    address_range:
+      start: 0x50000000
+      end: 0x50000800
+    page_size: 0x800
+    erased_byte_value: 0xff
+    program_page_timeout: 1000
+    erase_sector_timeout: 2000
+    sectors:
+    - size: 0x800
+      address: 0x0
+  cores:
+  - main
diff --git a/rtthost/Cargo.toml b/rtthost/Cargo.toml
index d6beeec9..68354aa3 100644
--- a/rtthost/Cargo.toml
+++ b/rtthost/Cargo.toml
@@ -9,6 +9,6 @@ repository.workspace = true
 
 [dependencies]
 pretty_env_logger = "0.5"
-probe-rs = { workspace = true }
-anyhow = { workspace = true }
+probe-rs.workspace = true
+anyhow.workspace = true
 clap = { version = "4", features = ["cargo", "derive"] }
diff --git a/target-gen/Cargo.toml b/target-gen/Cargo.toml
index 0f03185e..4c0b8b5f 100644
--- a/target-gen/Cargo.toml
+++ b/target-gen/Cargo.toml
@@ -28,8 +28,7 @@ goblin = { version = "0.8.2", default-features = false, features = [
 scroll = "0.12.0"
 serde_yaml = "0.9"
 log = "0.4.21"
-zip = { version = "1.1.4", default-features = false, features = [
-    "bzip2",
+zip = { version = "2.0.0", default-features = false, features = [
     "deflate64",
     "deflate",
     "lzma",
@@ -38,7 +37,7 @@ zip = { version = "1.1.4", default-features = false, features = [
 ] }
 clap = { version = "4.5", features = ["derive"] }
 colored = "2"
-anyhow = "1.0.82"
+anyhow.workspace = true
 reqwest = { version = "0.12.4", features = [
     "json",
     "rustls-tls",
diff --git a/xtask/src/main.rs b/xtask/src/main.rs
index 1654659b..6a22ba8f 100644
--- a/xtask/src/main.rs
+++ b/xtask/src/main.rs
@@ -1,5 +1,6 @@
 use std::{
     collections::HashMap,
+    fmt::Write as _,
     io::{Cursor, Write},
     path::{Path, PathBuf},
 };
@@ -43,7 +44,15 @@ enum Cli {
         #[arg(long)]
         commit: bool,
     },
-    CheckChangelog,
+    CheckChangelog {
+        /// The number of the PR to check. If set, checks the PR changes, otherwise the local fragments.
+        #[arg(long)]
+        pr: Option<u64>,
+
+        /// Whether to post a comment on the PR if there are issues. Requires `--pr` to be set.
+        #[arg(long, default_value = "false", requires = "pr")]
+        comment_error: bool,
+    },
 }
 
 fn try_main() -> anyhow::Result<()> {
@@ -56,7 +65,7 @@ fn try_main() -> anyhow::Result<()> {
             no_cleanup,
             commit,
         } => assemble_changelog(version, force, no_cleanup, commit)?,
-        Cli::CheckChangelog => check_changelog()?,
+        Cli::CheckChangelog { pr, comment_error } => check_changelog(pr, comment_error)?,
     }
 
     Ok(())
@@ -124,12 +133,78 @@ impl FragmentList {
             invalid_fragments: Vec::new(),
         }
     }
-}
 
-fn get_changelog_fragments(fragments_dir: &Path) -> Result<FragmentList> {
-    let mut list = FragmentList::new();
-    let github_token = std::env::var("GH_TOKEN").context("GH_TOKEN not set")?;
+    fn is_ok(&self) -> bool {
+        self.invalid_fragments.is_empty()
+    }
+
+    fn display(&self) -> String {
+        if self.is_ok() {
+            self.ok_message()
+        } else {
+            self.error_message()
+        }
+    }
+
+    fn ok_message(&self) -> String {
+        let mut message = String::new();
+        writeln!(
+            &mut message,
+            "Found {} valid fragments:",
+            self.fragments.len()
+        )
+        .unwrap();
+
+        for (group, fragments) in self.fragments.iter() {
+            if fragments.is_empty() {
+                continue;
+            }
+
+            writeln!(&mut message, " {group}:").unwrap();
 
+            for fragment in fragments {
+                writeln!(
+                    &mut message,
+                    "  - {} (#{}) by @{}",
+                    fragment.path.display(),
+                    fragment.pr_number.as_deref().unwrap_or("<unknown>"),
+                    fragment.author.as_deref().unwrap_or("<unknown>")
+                )
+                .unwrap();
+            }
+        }
+
+        message
+    }
+
+    fn error_message(&self) -> String {
+        let mut message = String::new();
+        message.push_str(
+            "The following changelog fragments \
+            do not match the expected pattern:\n",
+        );
+
+        for invalid_fragment in self.invalid_fragments.iter() {
+            writeln!(&mut message, " - {}", invalid_fragment.display()).unwrap();
+        }
+        message.push('\n');
+
+        message.push_str(
+            "Files should start with one of the categories followed \
+            by a dash, and end with '.md'\n\
+            For example: 'added-foo-bar.md'\n\
+            \n",
+        );
+
+        message.push_str("Valid categories are:\n");
+        for category in CHANGELOG_CATEGORIES {
+            writeln!(&mut message, " - {}", category.to_lowercase()).unwrap();
+        }
+        message
+    }
+}
+
+fn check_local_changelog_fragments(list: &mut FragmentList, fragments_dir: &Path) -> Result<()> {
     let fragment_files = std::fs::read_dir(fragments_dir)
         .with_context(|| format!("Unable to read fragments from {}", fragments_dir.display()))?;
 
@@ -137,51 +212,103 @@ fn get_changelog_fragments(fragments_dir: &Path) -> Result<FragmentList> {
         let file = file?;
         let path = file.path();
 
-        if path.is_file() {
-            let filename = path
-                .file_name()
-                .expect("All files should have a name")
-                .to_str()
-                .with_context(|| format!("Filename {path:?} is not valid UTF-8"))?;
+        if !path.is_file() {
+            continue;
+        }
 
-            if filename == (".gitkeep") {
-                continue;
-            }
+        let filename = path
+            .file_name()
+            .expect("All files should have a name")
+            .to_str()
+            .with_context(|| format!("Filename {} is not valid UTF-8", path.display()))?;
 
-            let Some((category, _)) = filename.split_once('-') else {
-                // Unable to split filename
-                list.invalid_fragments.push(path);
-                continue;
-            };
+        if filename == ".gitkeep" {
+            continue;
+        }
 
-            if let Some(fragments) = list.fragments.get_mut(category) {
-                let sh = Shell::new()?;
-                let sha = cmd!(sh, "git blame -l -s {path}").read()?;
-                let sha = sha.split(' ').next().unwrap();
-                println!("fetching PR info for sha: {}", sha);
+        let Some((category, _)) = filename.split_once('-') else {
+            // Unable to split filename
+            list.invalid_fragments.push(path);
+            continue;
+        };
 
-                let response = cmd!(sh, "curl -L -H 'Accept: application/vnd.github+json' -H 'Authorization: Bearer '{github_token} https://api.github.com/repos/probe-rs/probe-rs/commits/{sha}/pulls").read()?;
+        let Some(fragments) = list.fragments.get_mut(category) else {
+            // Incorrect caregory
+            list.invalid_fragments.push(path);
+            continue;
+        };
 
-                let json = serde_json::from_str::<serde_json::Value>(&response).unwrap();
+        let sh = Shell::new()?;
+        let sha = cmd!(sh, "git blame -l -s {path}").read()?;
+        let sha = sha.split(' ').next().unwrap();
+        println!("fetching PR info for sha: {sha}");
 
-                fragments.push(Fragment {
-                    pr_number: json[0]["number"].as_i64().map(|n| n.to_string()),
-                    author: json[0]["user"]["login"].as_str().map(|s| s.to_string()),
-                    path: path.clone(),
-                });
-            } else {
-                list.invalid_fragments.push(path);
-            }
-        }
+        let response = cmd!(sh, "gh api -H 'Accept: application/vnd.github+json' -H 'X-GitHub-Api-Version: 2022-11-28' https://api.github.com/repos/probe-rs/probe-rs/commits/{sha}/pulls").read()?;
+
+        let json = serde_json::from_str::<serde_json::Value>(&response).unwrap();
+
+        fragments.push(Fragment {
+            pr_number: json[0]["number"].as_i64().map(|n| n.to_string()),
+            author: json[0]["user"]["login"].as_str().map(|s| s.to_string()),
+            path,
+        });
+    }
+
+    println!();
+
+    Ok(())
+}
+
+/// This function is similar to the above, but only checks for new changelog fragments in the PR.
+fn check_new_changelog_fragments(list: &mut FragmentList, info: &PrInfo) -> Result<()> {
+    for file in info
+        .files
+        .iter()
+        .filter(|f| f.path.starts_with(FRAGMENTS_DIR))
+    {
+        let path = file.path.clone();
+
+        let filename = path
+            .file_name()
+            .expect("All files should have a name")
+            .to_str()
+            .with_context(|| format!("Filename {} is not valid UTF-8", path.display()))?;
+
+        let Some((category, _)) = filename.split_once('-') else {
+            // Unable to split filename
+            list.invalid_fragments.push(path);
+            continue;
+        };
+
+        let Some(fragments) = list.fragments.get_mut(category) else {
+            // Incorrect caregory
+            list.invalid_fragments.push(path);
+            continue;
+        };
+
+        fragments.push(Fragment {
+            pr_number: Some(info.number.to_string()),
+            author: Some(info.author.login.clone()),
+            path,
+        });
     }
 
-    Ok(list)
+    println!();
+
+    Ok(())
 }
 
 #[derive(Debug, serde::Deserialize)]
-struct PrFile {
-    path: PathBuf,
-    additions: usize,
+struct PrInfo {
+    number: u64,
+    author: PrAuthor,
+    labels: Vec<Label>,
+    files: Vec<PrFile>,
+}
+
+#[derive(Debug, serde::Deserialize)]
+struct PrAuthor {
+    login: String,
 }
 
 #[derive(Debug, serde::Deserialize)]
@@ -190,38 +317,52 @@ struct Label {
 }
 
 #[derive(Debug, serde::Deserialize)]
-struct PrInfo {
-    labels: Vec<Label>,
-    files: Vec<PrFile>,
+struct PrFile {
+    path: PathBuf,
+    additions: usize,
 }
 
-fn check_changelog() -> Result<()> {
-    let sh = Shell::new()?;
+impl PrInfo {
+    fn load(pr_number: u64) -> Result<Self> {
+        let sh = Shell::new()?;
 
-    let pr_number = std::env::var("PR").unwrap_or_default();
+        let pr = pr_number.to_string();
+        let info_json = cmd!(sh, "gh pr view {pr} --json labels,files,author,number").read()?;
+        let pr_info = serde_json::from_str::<PrInfo>(&info_json)?;
 
-    if let Ok(info_json) = cmd!(sh, "gh pr view {pr_number} --json labels,files").read() {
-        let info: PrInfo = serde_json::from_str(&info_json)?;
+        Ok(pr_info)
+    }
+}
+
+fn check_changelog(pr_number: Option<u64>, comment_error: bool) -> Result<()> {
+    let mut fragment_list = FragmentList::new();
 
+    if let Some(pr_number) = pr_number {
+        println!("Checking changelog fragments of PR {pr_number}");
+
+        let info = PrInfo::load(pr_number)?;
         if info.labels.iter().any(|l| l.name == "skip-changelog") {
             println!("Skipping changelog check because of 'skip-changelog' label");
             return Ok(());
         }
 
-        disallow_editing_main_changelog(&info, &pr_number)?;
+        disallow_editing_main_changelog(&info)?;
+        check_new_changelog_fragments(&mut fragment_list, &info)?;
+
         require_changelog_fragment(&info)?;
     } else {
-        println!("Unable to fetch PR info, just checking fragments.");
+        println!("No PR number, checking local fragments.");
+        check_local_changelog_fragments(&mut fragment_list, Path::new(FRAGMENTS_DIR))?;
     }
 
-    check_fragments()?;
+    print_fragment_list(fragment_list, if comment_error { pr_number } else { None })?;
 
     println!("Everything looks good 👍");
 
     Ok(())
 }
 
-fn disallow_editing_main_changelog(info: &PrInfo, pr: &str) -> Result<()> {
+fn disallow_editing_main_changelog(info: &PrInfo) -> Result<()> {
     if info.labels.iter().any(|l| l.name == "release") {
         // The release PR is allowed to edit the main changelog.
         return Ok(());
@@ -234,16 +375,23 @@ fn disallow_editing_main_changelog(info: &PrInfo, pr: &str) -> Result<()> {
     {
         let message = format!("Please do not edit {CHANGELOG_FILE} directly. Take a look at [CONTRIBUTING.md](https://github.com/probe-rs/probe-rs/blob/master/CONTRIBUTING.md) for information on changelog fragments instead.");
 
-        let sh = Shell::new()?;
-        cmd!(sh, "gh pr comment {pr} -b {message}")
-            .run()
-            .context("Failed to comment on PR")?;
+        write_comment(info.number, &message)?;
         anyhow::bail!("Please do not edit {CHANGELOG_FILE} directly");
     }
 
     Ok(())
 }
 
+fn write_comment(pr: u64, message: &str) -> Result<()> {
+    let sh = Shell::new()?;
+    let pr = pr.to_string();
+    cmd!(sh, "gh pr comment {pr} -b {message}")
+        .run()
+        .context("Failed to comment on PR")?;
+
+    Ok(())
+}
+
 fn require_changelog_fragment(info: &PrInfo) -> Result<()> {
     if !info
         .files
@@ -258,46 +406,14 @@ fn require_changelog_fragment(info: &PrInfo) -> Result<()> {
     Ok(())
 }
 
-fn check_fragments() -> Result<(), anyhow::Error> {
-    let fragment_list = get_changelog_fragments(Path::new(FRAGMENTS_DIR))?;
-    if !fragment_list.invalid_fragments.is_empty() {
-        println!("The following changelog fragments do not match the expected pattern:");
-        println!();
+/// `pr`: The PR number, if any, to comment on.
+fn print_fragment_list(fragment_list: FragmentList, pr: Option<u64>) -> Result<()> {
+    let message = fragment_list.display();
+    println!("{message}");
 
-        for invalid_fragment in fragment_list.invalid_fragments {
-            println!(" - {}", invalid_fragment.display());
-        }
-
-        println!();
-        println!(
-            "Files should start with one of the categories followed by a dash, and end with '.md'"
-        );
-        println!("For example: 'added-foo-bar.md'");
-        println!();
-        println!("Valid categories are:");
-        for category in CHANGELOG_CATEGORIES {
-            println!(" - {}", category.to_lowercase());
-        }
-        println!();
-
-        anyhow::bail!("Invalid changelog fragments found");
-    } else {
-        println!("Found {} valid fragments:", fragment_list.fragments.len());
-        for (group, fragments) in fragment_list.fragments.iter() {
-            if fragments.is_empty() {
-                continue;
-            }
-
-            println!(" {group}:");
-
-            for fragment in fragments {
-                println!(
-                    "  - {} (#{}) by @{}",
-                    fragment.path.display(),
-                    fragment.pr_number.as_deref().unwrap_or("<unknown>"),
-                    fragment.author.as_deref().unwrap_or("<unknown>")
-                );
-            }
+    if !fragment_list.is_ok() {
+        if let Some(pr) = pr {
+            write_comment(pr, &message)?;
         }
     }
 
@@ -321,7 +437,8 @@ fn assemble_changelog(
         anyhow::bail!("Changelog has local changes, aborting.\nUse --force to override.");
     }
 
-    let fragment_list = get_changelog_fragments(Path::new(FRAGMENTS_DIR))?;
+    let mut fragment_list = FragmentList::new();
+    check_local_changelog_fragments(&mut fragment_list, Path::new(FRAGMENTS_DIR))?;
 
     ensure!(
         fragment_list.invalid_fragments.is_empty(),


Test patch:
diff --git a/.github/workflows/smoketest.yml b/.github/workflows/smoketest.yml
index 1a4f82f8..79835283 100644
--- a/.github/workflows/smoketest.yml
+++ b/.github/workflows/smoketest.yml
@@ -23,7 +23,7 @@ jobs:
         run: rustup target add aarch64-unknown-linux-gnu
 
       - name: Install cross
-        uses: taiki-e/install-action@v2.33.22
+        uses: taiki-e/install-action@v2.34.0
         with:
           tool: cross
 
diff --git a/target-gen/src/commands/test.rs b/target-gen/src/commands/test.rs
index 86212f5e..f86fde84 100644
--- a/target-gen/src/commands/test.rs
+++ b/target-gen/src/commands/test.rs
@@ -261,9 +261,9 @@ pub fn run_flash_erase(
     erase_type: EraseType,
 ) -> Result<()> {
     if let EraseSectors(start_sector, sectors) = erase_type {
-        erase_sectors(session, Some(progress), start_sector, sectors)?;
+        erase_sectors(session, progress, start_sector, sectors)?;
     } else {
-        erase_all(session, Some(progress))?;
+        erase_all(session, progress)?;
     }
 
     Ok(())