Repository: probe-rs/probe-rs
Pull request #3176 (2025-03-16 12:36:02)

dpv0: add reset sequence for dpv0
Add a reset sequence for devices that are DPv0. These are older parts that are found on devices such as the TMS570 and devices that are JTAG-only.

These devices only have one bit in the `ABORT` register. In order to clear error conditions, bits must be set in the `CTRL` register as well.


Patch:
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 6bd82001..bf7efadb 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -46,7 +46,7 @@ jobs:
         if: (matrix.os == 'ubuntu-latest') || (matrix.os == 'ubuntu-20.04')
 
       - name: Cache Dependencies
-        uses: Swatinem/rust-cache@v2.7.7
+        uses: Swatinem/rust-cache@v2.7.8
 
       - name: cargo check for probe-rs, --no-default-features
         run: cargo check -p probe-rs --no-default-features --locked
@@ -85,7 +85,7 @@ jobs:
         run: rustup target add thumbv7m-none-eabi
 
       - name: Cache Dependencies
-        uses: Swatinem/rust-cache@v2.7.7
+        uses: Swatinem/rust-cache@v2.7.8
 
       - name: Run cargo test
         run: cargo test --all-features --locked
@@ -113,7 +113,7 @@ jobs:
           sudo apt install -y libudev-dev
 
       - name: Cache Dependencies
-        uses: Swatinem/rust-cache@v2.7.7
+        uses: Swatinem/rust-cache@v2.7.8
 
       - name: Run cargo clippy
         run: cargo clippy --all-features --all-targets --locked -- -D warnings
@@ -137,7 +137,7 @@ jobs:
           sudo apt install -y libudev-dev
 
       - name: Cache Dependencies
-        uses: Swatinem/rust-cache@v2.7.7
+        uses: Swatinem/rust-cache@v2.7.8
 
       - name: Run cargo doc
         run: cargo doc -p probe-rs --no-deps --all-features --locked
diff --git a/Cargo.lock b/Cargo.lock
index 6329662e..02550b98 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -597,9 +597,9 @@ dependencies = [
 
 [[package]]
 name = "clap_complete"
-version = "4.5.46"
+version = "4.5.47"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f5c5508ea23c5366f77e53f5a0070e5a84e51687ec3ef9e0464c86dc8d13ce98"
+checksum = "c06f5378ea264ad4f82bbc826628b5aad714a75abf6ece087e923010eb937fb6"
 dependencies = [
  "clap",
 ]
@@ -1020,9 +1020,9 @@ dependencies = [
 
 [[package]]
 name = "deranged"
-version = "0.3.11"
+version = "0.4.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4"
+checksum = "28cfac68e08048ae1883171632c2aef3ebc555621ae56fbccce1cbf22dd7f058"
 dependencies = [
  "powerfmt",
 ]
@@ -3235,9 +3235,9 @@ checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
 
 [[package]]
 name = "reqwest"
-version = "0.12.14"
+version = "0.12.15"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "989e327e510263980e231de548a33e63d34962d29ae61b467389a1a09627a254"
+checksum = "d19c46a6fdd48bc4dab94b6103fccc55d34c67cc0ad04653aad4ea2a07cd7bbb"
 dependencies = [
  "base64 0.22.1",
  "bytes",
@@ -3380,9 +3380,9 @@ dependencies = [
 
 [[package]]
 name = "rustls"
-version = "0.23.23"
+version = "0.23.25"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "47796c98c480fce5406ef69d1c76378375492c3b0a0de587be0c1d9feb12f395"
+checksum = "822ee9188ac4ec04a2f0531e55d035fb2de73f18b41a63c70c2712503b6fb13c"
 dependencies = [
  "once_cell",
  "ring",
@@ -3424,9 +3424,9 @@ dependencies = [
 
 [[package]]
 name = "rustls-webpki"
-version = "0.102.8"
+version = "0.103.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9"
+checksum = "0aa4eeac2588ffff23e9d7a7e9b3f971c5fb5b7ebc9452745e0c232c64f83b2f"
 dependencies = [
  "ring",
  "rustls-pki-types",
@@ -4047,9 +4047,9 @@ dependencies = [
 
 [[package]]
 name = "tempfile"
-version = "3.19.0"
+version = "3.19.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "488960f40a3fd53d72c2a29a58722561dee8afdd175bd88e3db4677d7b2ba600"
+checksum = "7437ac7763b9b123ccf33c338a5cc1bac6f69b45a136c19bdd8a65e3916435bf"
 dependencies = [
  "fastrand",
  "getrandom 0.3.1",
@@ -4191,9 +4191,9 @@ dependencies = [
 
 [[package]]
 name = "time"
-version = "0.3.39"
+version = "0.3.41"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dad298b01a40a23aac4580b67e3dbedb7cc8402f3592d7f49469de2ea4aecdd8"
+checksum = "8a7619e19bc266e0f9c5e6686659d394bc57973859340060a69221e57dbc0c40"
 dependencies = [
  "deranged",
  "itoa",
@@ -4208,15 +4208,15 @@ dependencies = [
 
 [[package]]
 name = "time-core"
-version = "0.1.3"
+version = "0.1.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "765c97a5b985b7c11d7bc27fa927dc4fe6af3a6dfb021d28deb60d3bf51e76ef"
+checksum = "c9e9a38711f559d9e3ce1cdb06dd7c5b8ea546bc90052da6d06bb76da74bb07c"
 
 [[package]]
 name = "time-macros"
-version = "0.2.20"
+version = "0.2.22"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e8093bc3e81c3bc5f7879de09619d06c9a5a5e45ca44dfeeb7225bae38005c5c"
+checksum = "3526739392ec93fd8b359c8e98514cb3e8e021beb4e5f597b00a0221f8ed8a49"
 dependencies = [
  "num-conv",
  "time-core",
@@ -5304,18 +5304,18 @@ checksum = "2fe21bcc34ca7fe6dd56cc2cb1261ea59d6b93620215aefb5ea6032265527784"
 
 [[package]]
 name = "zerocopy"
-version = "0.8.23"
+version = "0.8.24"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fd97444d05a4328b90e75e503a34bad781f14e28a823ad3557f0750df1ebcbc6"
+checksum = "2586fea28e186957ef732a5f8b3be2da217d65c5969d4b1e17f973ebbe876879"
 dependencies = [
  "zerocopy-derive",
 ]
 
 [[package]]
 name = "zerocopy-derive"
-version = "0.8.23"
+version = "0.8.24"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6352c01d0edd5db859a63e2605f4ea3183ddbd15e2c4a9e7d32184df75e4f154"
+checksum = "a996a8f63c5c4448cd959ac1bab0aaa3306ccfd060472f85943ee0750f0169be"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -5373,20 +5373,18 @@ dependencies = [
 
 [[package]]
 name = "zip"
-version = "2.3.0"
+version = "2.5.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "84e9a772a54b54236b9b744aaaf8d7be01b4d6e99725523cb82cb32d1c81b1d7"
+checksum = "27c03817464f64e23f6f37574b4fdc8cf65925b5bfd2b0f2aedf959791941f88"
 dependencies = [
  "arbitrary",
  "crc32fast",
  "crossbeam-utils",
  "deflate64",
- "displaydoc",
  "flate2",
  "indexmap",
  "lzma-rs",
  "memchr",
- "thiserror 2.0.12",
  "time",
  "zopfli",
  "zstd",
diff --git a/README.md b/README.md
index 4abd06d6..3148a63f 100644
--- a/README.md
+++ b/README.md
@@ -91,11 +91,20 @@ fn main() -> Result<(), probe_rs::Error> {
 ### Reading from RAM
 
 ```rust,no_run
-use probe_rs::{MemoryInterface, Permissions, Session};
+use probe_rs::{MemoryInterface, Permissions, Session, SessionConfig};
+use probe_rs::probe::WireProtocol;
 
 fn main() -> Result<(), probe_rs::Error> {
     // Attach to a chip.
-    let mut session = Session::auto_attach("nRF52840_xxAA", Permissions::default())?;
+    let speed = Some(5500);
+    let protocol = Some(WireProtocol::Swd);
+    let session_config = SessionConfig {
+      speed,
+      protocol,
+      ..Default::default()
+    };
+
+    let mut session = Session::auto_attach("nRF52840_xxAA", session_config)?;
 
     // Select a core.
     let mut core = session.core(0)?;
diff --git a/changelog/added-cc2340r5.md b/changelog/added-cc2340r5.md
new file mode 100644
index 00000000..7f8ea3a8
--- /dev/null
+++ b/changelog/added-cc2340r5.md
@@ -0,0 +1,3 @@
+Added support for TI's CC2340R5.
+
+- Blank device programming of the CC2340R5 is not yet supported. A valid CCFG must be programmed using TI's uniflash.
diff --git a/changelog/added-reset-for-dpv0.md b/changelog/added-reset-for-dpv0.md
new file mode 100644
index 00000000..5319a120
--- /dev/null
+++ b/changelog/added-reset-for-dpv0.md
@@ -0,0 +1,1 @@
+Added reset support for DPv0, where error conditions must be cleared by writing to Ctrl and not to Abort.
diff --git a/changelog/added-speed-and-protocol-to-utils.md b/changelog/added-speed-and-protocol-to-utils.md
new file mode 100644
index 00000000..481458f9
--- /dev/null
+++ b/changelog/added-speed-and-protocol-to-utils.md
@@ -0,0 +1,1 @@
+Added the ability to set speed and protocol in `smoke-tester` and `target-gen`.
\ No newline at end of file
diff --git a/changelog/changed-auto-attach.md b/changelog/changed-auto-attach.md
new file mode 100644
index 00000000..2bed4040
--- /dev/null
+++ b/changelog/changed-auto-attach.md
@@ -0,0 +1,1 @@
+Changed `auto_attach` to optionally set speed, protocol, and permissions via SessionConfig.
\ No newline at end of file
diff --git a/changelog/changed-debug.md b/changelog/changed-debug.md
new file mode 100644
index 00000000..b63e8525
--- /dev/null
+++ b/changelog/changed-debug.md
@@ -0,0 +1,1 @@
+The `probe-rs debug` command now uses the DAP server internally. The commands have been replaced by those available in the DAP server.
\ No newline at end of file
diff --git a/probe-rs-tools/src/bin/probe-rs/cmd/dap_server/debug_adapter/dap/adapter.rs b/probe-rs-tools/src/bin/probe-rs/cmd/dap_server/debug_adapter/dap/adapter.rs
index ee59b1bc..62474ed8 100644
--- a/probe-rs-tools/src/bin/probe-rs/cmd/dap_server/debug_adapter/dap/adapter.rs
+++ b/probe-rs-tools/src/bin/probe-rs/cmd/dap_server/debug_adapter/dap/adapter.rs
@@ -320,94 +320,57 @@ impl<P: ProtocolAdapter> DebugAdapter<P> {
             if context == "clipboard" {
                 response_body.result = arguments.expression;
             } else if context == "repl" {
-                // While the target is running, we only allow a 'break' command.
-                // Override clippy, because the recommendation would change the logic.
-                #[allow(clippy::nonminimal_bool)]
-                if !target_core.core.core_halted()?
-                    && !(arguments.expression.starts_with("break")
-                        || arguments.expression.starts_with("quit"))
-                {
-                    response_body.result =
-                        "The target is running. Only the 'break' or 'quit' commands are allowed."
-                            .to_string();
-                } else {
-                    // The target is halted, so we can allow any repl command.
-                    //TODO: Do we need to look for '/' in the expression, before we split it?
-                    // Now we can make sure we have a valid expression and evaluate it.
-                    let (command_root, repl_commands) =
-                        build_expanded_commands(arguments.expression.trim());
-                    if let Some(repl_command) = repl_commands.first() {
-                        // We have a valid repl command, so we can evaluate it.
-                        // First, let's extract the remainder of the arguments, so that we can pass them to the handler.
-                        let argument_string = arguments
-                            .expression
-                            .trim_start_matches(&command_root)
-                            .trim_start()
-                            .trim_start_matches(repl_command.command)
-                            .trim_start();
-                        match (repl_command.handler)(target_core, argument_string, &arguments) {
-                            Ok(repl_response) => {
-                                // Perform any special post-processing of the response.
-                                match repl_response.command.as_str() {
-                                    "terminate" => {
-                                        // This is a special case, where a repl command has requested that the debug session be terminated.
-                                        response_body.result = repl_response
-                                            .message
-                                            .unwrap_or_else(|| "Success.".to_string());
-                                        self.send_event(
-                                            "terminated",
-                                            Some(TerminatedEventBody { restart: None }),
-                                        )?;
-                                    }
-                                    "variables" => {
-                                        // This is a special case, where a repl command has requested that the variables be displayed.
-                                        if let Some(repl_response_body) = repl_response.body {
-                                            if let Ok(evaluate_response) =
-                                                serde_json::from_value(repl_response_body.clone())
-                                            {
-                                                response_body = evaluate_response;
-                                            } else {
-                                                response_body.result = format!(
-                                                    "Error: Could not parse response body: {repl_response_body:?}"
-                                                );
-                                            };
-                                        } else {
-                                            response_body.result = repl_response
-                                                .message
-                                                .unwrap_or_else(|| "Success.".to_string());
-                                        };
-                                    }
-                                    "setBreakpoints" => {
-                                        response_body.result = repl_response
-                                            .message
-                                            // This should always have a value, but just in case someone was lazy ...
-                                            .unwrap_or_else(|| "Success.".to_string()); // This is a special case, where we've added a breakpoint, and need to synch the DAP client UI.
-                                        self.send_event("breakpoint", repl_response.body)?;
-                                    }
-                                    _other_commands => {
-                                        // In all other cases, the response would have been updated by the repl command handler.
-                                        if repl_response.success {
-                                            response_body.result = repl_response
-                                                .message
-                                                // This should always have a value, but just in case someone was lazy ...
-                                                .unwrap_or_else(|| "Success.".to_string());
-                                        } else {
-                                            response_body.result = format!(
-                                                "Error: {:?} {:?}",
-                                                repl_response.command, repl_response.message
-                                            );
-                                        }
+                match self.handle_repl(target_core, &arguments) {
+                    Ok(repl_response) => {
+                        // In all other cases, the response would have been updated by the repl command handler.
+                        response_body.result = if repl_response.success {
+                            repl_response
+                                .message
+                                // This should always have a value, but just in case someone was lazy ...
+                                .unwrap_or_else(|| "Success.".to_string())
+                        } else {
+                            format!(
+                                "Error: {:?} {:?}",
+                                repl_response.command, repl_response.message
+                            )
+                        };
+
+                        // Perform any special post-processing of the response.
+                        match repl_response.command.as_str() {
+                            "terminate" => {
+                                // This is a special case, where a repl command has requested that the debug session be terminated.
+                                self.send_event(
+                                    "terminated",
+                                    Some(TerminatedEventBody { restart: None }),
+                                )?;
+                            }
+                            "variables" => {
+                                // This is a special case, where a repl command has requested that the variables be displayed.
+                                if let Some(repl_response_body) = repl_response.body {
+                                    if let Ok(evaluate_response) =
+                                        serde_json::from_value(repl_response_body.clone())
+                                    {
+                                        response_body = evaluate_response;
+                                    } else {
+                                        response_body.result = format!(
+                                            "Error: Could not parse response body: {repl_response_body:?}"
+                                        );
                                     }
                                 }
                             }
-                            Err(error) => match &error {
-                                DebuggerError::UserMessage(repl_message) => {
-                                    repl_message.clone_into(&mut response_body.result)
-                                }
-                                other_error => response_body.result = format!("{other_error:?}",),
-                            },
+                            "setBreakpoints" => {
+                                // This is a special case, where we've added a breakpoint, and need to synch the DAP client UI.
+                                self.send_event("breakpoint", repl_response.body)?;
+                            }
+                            _other_commands => {}
                         }
                     }
+                    Err(error) => {
+                        response_body.result = match error {
+                            DebuggerError::UserMessage(repl_message) => repl_message,
+                            other_error => format!("{other_error:?}"),
+                        };
+                    }
                 }
             } else {
                 // Handle other contexts: 'watch', 'hover', etc.
@@ -532,6 +495,46 @@ impl<P: ProtocolAdapter> DebugAdapter<P> {
         self.send_response(request, Ok(Some(response_body)))
     }
 
+    fn handle_repl(
+        &mut self,
+        target_core: &mut CoreHandle<'_>,
+        arguments: &EvaluateArguments,
+    ) -> Result<Response, DebuggerError> {
+        if !target_core.core.core_halted()?
+            && !arguments.expression.starts_with("break")
+            && !arguments.expression.starts_with("quit")
+            && !arguments.expression.starts_with("help")
+        {
+            return Err(DebuggerError::UserMessage(
+                "The target is running. Only the 'break', 'help' or 'quit' commands are allowed."
+                    .to_string(),
+            ));
+        }
+
+        // The target is halted, so we can allow any repl command.
+        //TODO: Do we need to look for '/' in the expression, before we split it?
+        // Now we can make sure we have a valid expression and evaluate it.
+        let (command_root, repl_commands) = build_expanded_commands(arguments.expression.trim());
+
+        let Some(repl_command) = repl_commands.first() else {
+            return Err(DebuggerError::UserMessage(format!(
+                "Invalid REPL command: {:?}.",
+                command_root
+            )));
+        };
+
+        // We have a valid repl command, so we can evaluate it.
+        // First, let's extract the remainder of the arguments, so that we can pass them to the handler.
+        let argument_string = arguments
+            .expression
+            .trim_start_matches(&command_root)
+            .trim_start()
+            .trim_start_matches(repl_command.command)
+            .trim_start();
+
+        (repl_command.handler)(target_core, argument_string, arguments)
+    }
+
     /// Works in tandem with the `evaluate` request, to provide possible completions in the Debug Console REPL window.
     pub(crate) fn completions(&mut self, _: &mut CoreHandle, request: &Request) -> Result<()> {
         // TODO: When variables appear in the `watch` context, they will not resolve correctly after a 'step' function. Consider doing the lazy load for 'either/or' of Variables vs. Evaluate
diff --git a/probe-rs-tools/src/bin/probe-rs/cmd/dap_server/debug_adapter/dap/core_status.rs b/probe-rs-tools/src/bin/probe-rs/cmd/dap_server/debug_adapter/dap/core_status.rs
index 18b6447a..82068ec4 100644
--- a/probe-rs-tools/src/bin/probe-rs/cmd/dap_server/debug_adapter/dap/core_status.rs
+++ b/probe-rs-tools/src/bin/probe-rs/cmd/dap_server/debug_adapter/dap/core_status.rs
@@ -47,7 +47,14 @@ impl DapStatus for CoreStatus {
                 ),
                 HaltReason::Request => (
                     "pause",
-                    "Core halted due to a user (debugger client) request".to_string(),
+                    format!(
+                        "Core halted due to a user (debugger client) request @{}.",
+                        if let Some(program_counter) = program_counter {
+                            format!("{program_counter:#010x}")
+                        } else {
+                            "(unspecified location)".to_string()
+                        }
+                    ),
                 ),
                 HaltReason::External => (
                     "external",
diff --git a/probe-rs-tools/src/bin/probe-rs/cmd/dap_server/debug_adapter/dap/dap_types.rs b/probe-rs-tools/src/bin/probe-rs/cmd/dap_server/debug_adapter/dap/dap_types.rs
index 9289872b..bf7998d8 100644
--- a/probe-rs-tools/src/bin/probe-rs/cmd/dap_server/debug_adapter/dap/dap_types.rs
+++ b/probe-rs-tools/src/bin/probe-rs/cmd/dap_server/debug_adapter/dap/dap_types.rs
@@ -230,3 +230,17 @@ fn get_string_argument(
         }),
     }
 }
+
+impl Display for Message {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        let mut formatted = self.format.clone();
+
+        if let Some(args) = &self.variables {
+            for (key, value) in args {
+                formatted = formatted.replace(&format!("{{{key}}}"), value);
+            }
+        }
+
+        f.write_str(&formatted)
+    }
+}
diff --git a/probe-rs-tools/src/bin/probe-rs/cmd/dap_server/debug_adapter/dap/repl_commands.rs b/probe-rs-tools/src/bin/probe-rs/cmd/dap_server/debug_adapter/dap/repl_commands.rs
index 9c336199..4750c757 100644
--- a/probe-rs-tools/src/bin/probe-rs/cmd/dap_server/debug_adapter/dap/repl_commands.rs
+++ b/probe-rs-tools/src/bin/probe-rs/cmd/dap_server/debug_adapter/dap/repl_commands.rs
@@ -36,26 +36,24 @@ pub(crate) struct ReplCommand<H: 'static> {
     /// - This is case sensitive.
     pub(crate) command: &'static str,
     pub(crate) help_text: &'static str,
-    pub(crate) sub_commands: Option<&'static [ReplCommand<H>]>,
-    pub(crate) args: Option<&'static [ReplCommandArgs]>,
+    pub(crate) sub_commands: &'static [ReplCommand<H>],
+    pub(crate) args: &'static [ReplCommandArgs],
     pub(crate) handler: H,
 }
 
 impl<H> Display for ReplCommand<H> {
     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        write!(f, "{} ", self.command)?;
-        if self.sub_commands.is_some() {
-            write!(f, "<subcommand> ")?;
+        write!(f, "{}", self.command)?;
+        if !self.sub_commands.is_empty() {
+            write!(f, " <subcommand>")?;
         }
-        if let Some(args) = self.args {
-            for arg in args {
-                write!(f, " {arg} ")?;
-            }
+        for arg in self.args {
+            write!(f, " {arg}")?;
         }
-        write!(f, ": {} ", self.help_text)?;
-        if let Some(sub_commands) = self.sub_commands {
+        write!(f, ": {}", self.help_text)?;
+        if !self.sub_commands.is_empty() {
             write!(f, "\n  Subcommands:")?;
-            for sub_command in sub_commands {
+            for sub_command in self.sub_commands {
                 write!(f, "\n  - {sub_command}")?;
             }
         }
@@ -67,11 +65,11 @@ pub(crate) static REPL_COMMANDS: &[ReplCommand<ReplHandler>] = &[
     ReplCommand {
         command: "help",
         help_text: "Information about available commands and how to use them.",
-        sub_commands: None,
-        args: None,
+        sub_commands: &[],
+        args: &[],
         handler: |_, _, _| {
             let mut help_text =
-                "Usage:\t-Use <Ctrl+Space> to get a list of available commands.".to_string();
+                "Usage:\t- Use <Ctrl+Space> to get a list of available commands.".to_string();
             help_text.push_str("\n\t- Use <Up/DownArrows> to navigate through the command list.");
             help_text.push_str("\n\t- Use <Hab> to insert the currently selected command.");
             help_text.push_str("\n\t- Note: This implementation is a subset of gdb commands, and is intended to behave similarly.");
@@ -93,8 +91,8 @@ pub(crate) static REPL_COMMANDS: &[ReplCommand<ReplHandler>] = &[
     ReplCommand {
         command: "quit",
         help_text: "Disconnect (and suspend) the target.",
-        sub_commands: None,
-        args: None,
+        sub_commands: &[],
+        args: &[],
         handler: |target_core, _, _| {
             target_core.core.halt(Duration::from_millis(500))?;
             Ok(Response {
@@ -111,12 +109,10 @@ pub(crate) static REPL_COMMANDS: &[ReplCommand<ReplHandler>] = &[
     ReplCommand {
         command: "c",
         help_text: "Continue running the program on the target.",
-        sub_commands: None,
-        args: None,
+        sub_commands: &[],
+        args: &[],
         handler: |target_core, _, _| {
             target_core.core.run()?;
-            // Changing the status below will result in the debugger automaticlly synching the client status.
-            target_core.core_data.last_known_status = CoreStatus::Running;
             Ok(Response {
                 command: "continue".to_string(),
                 success: true,
@@ -132,8 +128,8 @@ pub(crate) static REPL_COMMANDS: &[ReplCommand<ReplHandler>] = &[
         command: "break",
         // Stricly speaking, gdb refers to this as an expression, but we only support variables.
         help_text: "Sets a breakpoint specified location, or next instruction if unspecified.",
-        sub_commands: None,
-        args: Some(&[ReplCommandArgs::Optional("*address")]),
+        sub_commands: &[],
+        args: &[ReplCommandArgs::Optional("*address")],
         handler: |target_core, command_arguments, _| {
             if command_arguments.is_empty() {
                 let core_info = target_core.core.halt(Duration::from_millis(500))?;
@@ -193,12 +189,12 @@ pub(crate) static REPL_COMMANDS: &[ReplCommand<ReplHandler>] = &[
     },
     ReplCommand {
         command: "backtrace",
-        sub_commands: None,
+        sub_commands: &[],
         help_text: "Print the backtrace of the current thread to a local file.",
-        args: Some(&[ReplCommandArgs::Optional(
+        args: &[ReplCommandArgs::Optional(
             "path (e.g. my_dir/backtrace.yaml)",
-        )]),
-        handler: |target_core, command_arguments, _request_arguments| {
+        )],
+        handler: |target_core, command_arguments, _| {
             let args = command_arguments.split_whitespace().collect_vec();
 
             let write_to_file = args.first().map(Path::new);
@@ -236,20 +232,20 @@ pub(crate) static REPL_COMMANDS: &[ReplCommand<ReplHandler>] = &[
     ReplCommand {
         command: "info",
         help_text: "Information of specified program data.",
-        sub_commands: Some(&[
+        sub_commands: &[
             ReplCommand {
                 command: "frame",
                 help_text: "Describe the current frame, or the frame at the specified (hex) address.",
-                sub_commands: None,
-                args: Some(&[ReplCommandArgs::Optional("address")]),
+                sub_commands: &[],
+                args: &[ReplCommandArgs::Optional("address")],
                 // TODO: This is easy to implement ... just requires deciding how to format the output.
                 handler: |_, _, _| Err(DebuggerError::Unimplemented),
             },
             ReplCommand {
                 command: "locals",
                 help_text: "List local variables of the selected frame.",
-                sub_commands: None,
-                args: None,
+                sub_commands: &[],
+                args: &[],
                 handler: |target_core, _, evaluate_arguments| {
                     let gdb_nuf = GdbNuf {
                         format_specifier: GdbFormat::Native,
@@ -262,21 +258,21 @@ pub(crate) static REPL_COMMANDS: &[ReplCommand<ReplHandler>] = &[
             ReplCommand {
                 command: "all-reg",
                 help_text: "List all registers of the selected frame.",
-                sub_commands: None,
-                args: None,
+                sub_commands: &[],
+                args: &[],
                 // TODO: This is easy to implement ... just requires deciding how to format the output.
                 handler: |_, _, _| Err(DebuggerError::Unimplemented),
             },
             ReplCommand {
                 command: "var",
                 help_text: "List all static variables.",
-                sub_commands: None,
-                args: None,
+                sub_commands: &[],
+                args: &[],
                 // TODO: This is easy to implement ... just requires deciding how to format the output.
                 handler: |_, _, _| Err(DebuggerError::Unimplemented),
             },
-        ]),
-        args: None,
+        ],
+        args: &[],
         handler: |_, _, _| {
             Err(DebuggerError::UserMessage("Please provide one of the required subcommands. See the `help` command for more information.".to_string()))
         },
@@ -285,11 +281,11 @@ pub(crate) static REPL_COMMANDS: &[ReplCommand<ReplHandler>] = &[
         command: "p",
         // Stricly speaking, gdb refers to this as an expression, but we only support variables.
         help_text: "Print known information about variable.",
-        sub_commands: None,
-        args: Some(&[
+        sub_commands: &[],
+        args: &[
             ReplCommandArgs::Optional("/f (f=format[n|v])"),
             ReplCommandArgs::Required("<local variable name>"),
-        ]),
+        ],
         handler: |target_core, command_arguments, evaluate_arguments| {
             let input_arguments = command_arguments.split_whitespace();
             let mut gdb_nuf = GdbNuf {
@@ -330,11 +326,11 @@ pub(crate) static REPL_COMMANDS: &[ReplCommand<ReplHandler>] = &[
     ReplCommand {
         command: "x",
         help_text: "Examine Memory, using format specifications, at the specified address.",
-        sub_commands: None,
-        args: Some(&[
+        sub_commands: &[],
+        args: &[
             ReplCommandArgs::Optional("/Nuf (N=count, u=unit[b|h|w|g], f=format[t|x|i])"),
             ReplCommandArgs::Optional("address (hex)"),
-        ]),
+        ],
         handler: |target_core, command_arguments, request_arguments| {
             let input_arguments = command_arguments.split_whitespace();
             let mut gdb_nuf = GdbNuf {
@@ -405,13 +401,13 @@ pub(crate) static REPL_COMMANDS: &[ReplCommand<ReplHandler>] = &[
     ReplCommand {
         command: "dump",
         help_text: "Create a core dump at a target location. Specify memory ranges to dump, or leave blank to dump in-scope memory regions.",
-        sub_commands: None,
-        args: Some(&[
+        sub_commands: &[],
+        args: &[
             ReplCommandArgs::Optional("memory start address"),
             ReplCommandArgs::Optional("memory size in bytes"),
             ReplCommandArgs::Optional("path (default: ./coredump)"),
-        ]),
-        handler: |target_core, command_arguments, _request_arguments| {
+        ],
+        handler: |target_core, command_arguments, _| {
             let mut args = command_arguments.split_whitespace().collect_vec();
 
             // If we get an odd number of arguments, treat all n * 2 args at the start as memory blocks
diff --git a/probe-rs-tools/src/bin/probe-rs/cmd/dap_server/debug_adapter/dap/repl_commands_helpers.rs b/probe-rs-tools/src/bin/probe-rs/cmd/dap_server/debug_adapter/dap/repl_commands_helpers.rs
index 82599954..be8d3828 100644
--- a/probe-rs-tools/src/bin/probe-rs/cmd/dap_server/debug_adapter/dap/repl_commands_helpers.rs
+++ b/probe-rs-tools/src/bin/probe-rs/cmd/dap_server/debug_adapter/dap/repl_commands_helpers.rs
@@ -79,10 +79,9 @@ pub(crate) fn get_local_variable(
         type_: None,
         presentation_hint: None,
     };
-    response_body.result = "".to_string();
     for variable in variable_list {
         if gdb_nuf.format_specifier == GdbFormat::DapReference {
-            response_body.memory_reference = Some(format!("{}", variable.memory_location));
+            response_body.memory_reference = Some(variable.memory_location.to_string());
             response_body.result = format!(
                 "{} : {} ",
                 variable.name,
@@ -190,27 +189,25 @@ pub(crate) fn build_expanded_commands(
         let matches = find_commands(&repl_commands, command_piece);
 
         // If there is only one match, and it has sub-commands, then we can continue iterating (implicit recursion with new sub-command).
-        if matches.len() == 1 {
-            if let Some(parent_command) = matches.first() {
-                if let Some(sub_commands) = parent_command.sub_commands {
-                    // Build up the full command as we iterate ...
-                    if !command_root.is_empty() {
-                        command_root.push(' ');
-                    }
-                    command_root.push_str(parent_command.command);
-                    repl_commands = sub_commands.iter().collect();
-                    continue;
-                }
-            }
-        }
+        let Some(parent_command) = matches.first() else {
+            // If there are no matches, then we can keep the matches from the previous
+            // iteration (if there were any) but we can't continue;
+            break;
+        };
 
-        if matches.is_empty() {
-            // If there are no matches, then we can keep the matches from the previous iteration (if there were any).
+        if matches.len() == 1 && !parent_command.sub_commands.is_empty() {
+            // Build up the full command as we iterate ...
+            if !command_root.is_empty() {
+                command_root.push(' ');
+            }
+            command_root.push_str(parent_command.command);
+            repl_commands = parent_command.sub_commands.iter().collect();
         } else {
-            // If there are multiple matches, or there is only one match with no sub-commands, then we can use the matches.
+            // If there are multiple matches, or there is only one match with no
+            // sub-commands, then we can use the matches.
             repl_commands = matches;
+            break;
         }
-        break;
     }
     (command_root, repl_commands)
 }
diff --git a/probe-rs-tools/src/bin/probe-rs/cmd/dap_server/mod.rs b/probe-rs-tools/src/bin/probe-rs/cmd/dap_server/mod.rs
index 3fa9957b..205616b2 100644
--- a/probe-rs-tools/src/bin/probe-rs/cmd/dap_server/mod.rs
+++ b/probe-rs-tools/src/bin/probe-rs/cmd/dap_server/mod.rs
@@ -1,8 +1,8 @@
 // Bad things happen to the VSCode debug extenison and debug_adapter if we panic at the wrong time.
 #![warn(clippy::unwrap_used, clippy::panic, clippy::expect_used)]
-mod debug_adapter;
+pub(crate) mod debug_adapter;
 mod peripherals;
-mod server;
+pub(crate) mod server;
 
 #[cfg(test)]
 mod test;
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 9cb376ea..1e9871dc 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
@@ -47,7 +47,8 @@ use time::UtcOffset;
 /// DAP Client requests such as `Terminate`, `Disconnect`, and `Reset`, as well as how to respond to unrecoverable errors
 /// during a debug session interacting with a target session.
 pub(crate) enum DebugSessionStatus {
-    Continue,
+    /// Continue handling requests after a specified delay.
+    Continue(Duration),
     Terminate,
     Restart(Request),
 }
@@ -112,12 +113,14 @@ impl Debugger {
         self.debug_logger.flush_to_dap(debug_adapter)?;
         let Some(request) = debug_adapter.listen_for_request()? else {
             let _poll_span = tracing::trace_span!("Polling for core status").entered();
+            let mut delay = Duration::ZERO;
             if debug_adapter.all_cores_halted {
                 // Once all cores are halted, then we can skip polling the core for status, and just wait for the next DAP Client request.
                 tracing::trace!(
                     "Sleeping (all cores are halted) for 100ms to reduce polling overheaads."
                 );
-                thread::sleep(Duration::from_millis(100)); // Medium delay to reduce fast looping costs.
+                // Medium delay to reduce fast looping costs.
+                delay = Duration::from_millis(100);
             } else {
                 // Poll ALL target cores for status, which includes synching status with the DAP client, and handling RTT data.
                 let (_, suggest_delay_required) =
@@ -127,7 +130,8 @@ impl Debugger {
                     tracing::trace!(
                         "Sleeping (core is running) for 50ms to reduce polling overheads."
                     );
-                    thread::sleep(Duration::from_millis(50)); // Small delay to reduce fast looping costs.
+                    // Small delay to reduce fast looping costs.
+                    delay = Duration::from_millis(50);
                 } else {
                     tracing::trace!(
                         "Retrieving data from the core, no delay required between iterations of polling the core."
@@ -135,7 +139,7 @@ impl Debugger {
                 };
             }
 
-            return Ok(DebugSessionStatus::Continue);
+            return Ok(DebugSessionStatus::Continue(delay));
         };
 
         let _req_span = tracing::info_span!("Handling request", request = ?request).entered();
@@ -153,7 +157,7 @@ impl Debugger {
             }
 
             // Keep processing "configuration" requests until we've passed `configuration_done` and have a valid `target_core`.
-            return Ok(DebugSessionStatus::Continue);
+            return Ok(DebugSessionStatus::Continue(Duration::ZERO));
         }
 
         // TODO: Currently, we only use `poll_cores()` results from the first core and need to expand to a multi-core implementation that understands which MS DAP requests are core specific.
@@ -205,7 +209,7 @@ impl Debugger {
             _ => {}
         }
 
-        let mut debug_session = DebugSessionStatus::Continue;
+        let mut debug_session = DebugSessionStatus::Continue(Duration::ZERO);
 
         // Now we are ready to execute supported commands, or return an error if it isn't supported.
         let result = match request.command.as_ref() {
@@ -315,8 +319,6 @@ impl Debugger {
         if self.handle_initialize(&mut debug_adapter).is_err() {
             // The request handler has already reported this error to the user.
             return Ok(());
-        } else {
-            self.debug_logger.flush_to_dap(&mut debug_adapter)?;
         }
 
         let launch_attach_request = loop {
@@ -339,7 +341,6 @@ impl Debugger {
                 return Ok(());
             }
         };
-        self.debug_logger.flush_to_dap(&mut debug_adapter)?;
 
         if debug_adapter
             .send_event::<Event>("initialized", None)
@@ -362,8 +363,11 @@ impl Debugger {
                 };
 
             match debug_session_status {
-                DebugSessionStatus::Continue => {
+                DebugSessionStatus::Continue(delay) => {
                     // All is good. We can process the next request.
+                    if !delay.is_zero() {
+                        thread::sleep(delay);
+                    }
                 }
                 DebugSessionStatus::Restart(request) => {
                     if let Err(error) =
@@ -396,7 +400,7 @@ impl Debugger {
 
     /// Process launch or attach request
     #[tracing::instrument(skip_all, name = "Handle Launch/Attach Request")]
-    fn handle_launch_attach<P: ProtocolAdapter + 'static>(
+    pub(crate) fn handle_launch_attach<P: ProtocolAdapter + 'static>(
         &mut self,
         registry: &mut Registry,
         launch_attach_request: &Request,
@@ -488,6 +492,7 @@ impl Debugger {
         drop(target_core);
 
         debug_adapter.send_response::<()>(launch_attach_request, Ok(None))?;
+        self.debug_logger.flush_to_dap(debug_adapter)?;
 
         Ok(session_data)
     }
@@ -804,7 +809,7 @@ impl Debugger {
     }
 
     #[tracing::instrument(skip_all, name = "Handling initialize request")]
-    fn handle_initialize<P: ProtocolAdapter>(
+    pub(crate) fn handle_initialize<P: ProtocolAdapter>(
         &mut self,
         debug_adapter: &mut DebugAdapter<P>,
     ) -> Result<(), DebuggerError> {
@@ -826,15 +831,11 @@ impl Debugger {
         if !(initialize_arguments.columns_start_at_1.unwrap_or(true)
             && initialize_arguments.lines_start_at_1.unwrap_or(true))
         {
-            debug_adapter.send_response::<()>(
-                &initialize_request,
-                Err(&DebuggerError::Other(anyhow!(
-                    "Unsupported Capability: Client requested column and row numbers start at 0."
-                ))),
-            )?;
-            return Err(DebuggerError::Other(anyhow!(
+            let error = DebuggerError::Other(anyhow!(
                 "Unsupported Capability: Client requested column and row numbers start at 0."
-            )));
+            ));
+            debug_adapter.send_response::<()>(&initialize_request, Err(&error))?;
+            return Err(error);
         }
 
         if let Some(progress_support) = initialize_arguments.supports_progress_reporting {
@@ -873,6 +874,8 @@ impl Debugger {
         };
         debug_adapter.send_response(&initialize_request, Ok(Some(capabilities)))?;
 
+        self.debug_logger.flush_to_dap(debug_adapter)?;
+
         Ok(())
     }
 }
@@ -888,26 +891,20 @@ fn expect_request<P: ProtocolAdapter>(
     let next_request = loop {
         if let Some(current_request) = debug_adapter.listen_for_request()? {
             break current_request;
-        };
+        }
     };
 
     if next_request.command == expected_command {
         Ok(next_request)
     } else {
-        debug_adapter.send_response::<()>(
-            &next_request,
-            Err(&DebuggerError::Other(anyhow!(
-                "Initial command was '{}', expected '{}'",
-                next_request.command,
-                expected_command
-            ))),
-        )?;
-
-        Err(DebuggerError::Other(anyhow!(
+        let error = DebuggerError::Other(anyhow!(
             "Initial command was '{}', expected '{}'",
             next_request.command,
             expected_command
-        )))
+        ));
+        debug_adapter.send_response::<()>(&next_request, Err(&error))?;
+
+        Err(error)
     }
 }
 
diff --git a/probe-rs-tools/src/bin/probe-rs/cmd/debug.rs b/probe-rs-tools/src/bin/probe-rs/cmd/debug.rs
index 76dd93fd..8c213200 100644
--- a/probe-rs-tools/src/bin/probe-rs/cmd/debug.rs
+++ b/probe-rs-tools/src/bin/probe-rs/cmd/debug.rs
@@ -1,1041 +1,292 @@
-use std::path::Path;
+use std::cell::RefCell;
 use std::path::PathBuf;
-use std::time::Duration;
+use std::rc::Rc;
 
-use anyhow::anyhow;
-use capstone::{
-    Endian, arch::arm::ArchMode as armArchMode, arch::arm64::ArchMode as aarch64ArchMode,
-    arch::riscv::ArchMode as riscvArchMode, prelude::*,
-};
-use num_traits::Num;
-use parse_int::parse;
-use probe_rs::CoreDump;
-use probe_rs::CoreDumpError;
-use probe_rs::CoreInterface;
-use probe_rs::architecture::arm::ap::AccessPortError;
 use probe_rs::config::Registry;
-use probe_rs::flashing::FileDownloadError;
-use probe_rs::probe::DebugProbeError;
 use probe_rs::probe::list::Lister;
-use probe_rs::{Core, CoreType, InstructionSet, MemoryInterface, RegisterValue};
-use probe_rs_debug::exception_handler_for_core;
-use probe_rs_debug::stack_frame::StackFrameInfo;
-use probe_rs_debug::{debug_info::DebugInfo, registers::DebugRegisters, stack_frame::StackFrame};
 use rustyline::{DefaultEditor, error::ReadlineError};
-
+use time::UtcOffset;
+
+use crate::cmd::dap_server::debug_adapter::dap::adapter::DebugAdapter;
+use crate::cmd::dap_server::debug_adapter::dap::dap_types::ErrorResponseBody;
+use crate::cmd::dap_server::debug_adapter::dap::dap_types::EvaluateArguments;
+use crate::cmd::dap_server::debug_adapter::dap::dap_types::EvaluateResponseBody;
+use crate::cmd::dap_server::debug_adapter::dap::dap_types::InitializeRequestArguments;
+use crate::cmd::dap_server::debug_adapter::dap::dap_types::OutputEventBody;
+use crate::cmd::dap_server::debug_adapter::protocol::ProtocolAdapter;
+use crate::cmd::dap_server::server::configuration::ConsoleLog;
+use crate::cmd::dap_server::server::configuration::CoreConfig;
+use crate::cmd::dap_server::server::configuration::FlashingConfig;
+use crate::cmd::dap_server::server::configuration::SessionConfig;
+use crate::cmd::dap_server::server::debugger::Debugger;
+use crate::util::rtt::RttConfig;
 use crate::{CoreOptions, util::common_options::ProbeOptions};
 
-#[derive(clap::Parser)]
-pub struct Cmd {
-    #[clap(flatten)]
-    shared: CoreOptions,
-
-    #[clap(flatten)]
-    common: ProbeOptions,
+use super::dap_server::debug_adapter::dap::dap_types::Request;
+use super::dap_server::debug_adapter::dap::dap_types::Response;
 
-    #[clap(long, value_parser)]
-    /// Binary to debug
-    exe: Option<PathBuf>,
+struct Shared {
+    stop: bool,
+    next_request: Option<Request>,
 }
 
-impl Cmd {
-    pub fn run(self, registry: &mut Registry, lister: &Lister) -> anyhow::Result<()> {
-        let (mut session, _probe_options) = self.common.simple_attach(registry, lister)?;
-
-        let di = self
-            .exe
-            .as_ref()
-            .and_then(|path| DebugInfo::from_file(path).ok());
-
-        let cli = DebugCli::new();
-
-        let core = session.core(self.shared.core)?;
-
-        let mut cli_data = CliData::new(core, di)?;
-
-        let mut rl = DefaultEditor::new()?;
+/// A barebones adapter for the CLI "client".
+struct CliAdapter {
+    shared: Rc<RefCell<Shared>>,
+    console_log_level: ConsoleLog,
+}
+impl ProtocolAdapter for CliAdapter {
+    fn listen_for_request(&mut self) -> anyhow::Result<Option<Request>> {
+        Ok(self.shared.borrow().next_request.clone())
+    }
 
-        loop {
-            cli_data.print_state()?;
+    fn send_event<S: serde::Serialize>(
+        &mut self,
+        event_type: &str,
+        event_body: Option<S>,
+    ) -> anyhow::Result<()> {
+        let serialized_body = event_body.as_ref().map(serde_json::to_string).transpose()?;
+
+        match event_type {
+            "probe-rs-show-message" | "output" => {
+                let Some(body) = serialized_body else {
+                    return Ok(());
+                };
 
-            match rl.readline(">> ") {
-                Ok(line) => {
-                    let history_entry: &str = line.as_ref();
-                    rl.add_history_entry(history_entry)?;
-                    let cli_state = cli.handle_line(&line, &mut cli_data)?;
+                let output = serde_json::from_str::<OutputEventBody>(&body)?;
 
-                    if cli_state == CliState::Stop {
-                        break;
-                    }
-                }
-                // For end of file and ctrl-c, we just quit
-                Err(ReadlineError::Eof | ReadlineError::Interrupted) => return Ok(()),
-                Err(actual_error) => {
-                    // Show error message and quit
-                    println!("Error handling input: {actual_error:?}");
-                    break;
-                }
+                print!("{}", output.output);
+            }
+            "terminated" => {
+                self.shared.borrow_mut().stop = true;
+            }
+            // Not interesting
+            "memory" => {}
+            "stopped" => {}
+            "breakpoint" => {}
+            // No RTT support (yet)
+            "probe-rs-rtt-channel-config" | "probe-rs-rtt-data" => {}
+            // No flashing support (yet)
+            "progressStart" | "progressEnd" | "progressUpdate" => {}
+            // We can safely ignore "exited"
+            "exited" => {}
+            _ => {
+                tracing::warn!("Unhandled event {event_type}: {serialized_body:?}");
             }
         }
 
         Ok(())
     }
-}
-
-#[derive(Debug, thiserror::Error)]
-enum CliError {
-    #[error(transparent)]
-    DebugProbe(#[from] DebugProbeError),
-    #[error(transparent)]
-    AccessPort(#[from] AccessPortError),
-    #[error(transparent)]
-    StdIO(#[from] std::io::Error),
-    #[error(transparent)]
-    FileDownload(#[from] FileDownloadError),
-    #[error("Command expected more arguments.")]
-    MissingArgument,
-    #[error("Failed to parse argument '{argument}'.")]
-    ArgumentParseError {
-        argument_index: usize,
-        argument: String,
-        source: anyhow::Error,
-    },
-    #[error(transparent)]
-    ProbeRs(#[from] probe_rs::Error),
-    /// Errors related to the handling of core dumps.
-    #[error("An error with a CoreDump occured")]
-    CoreDump(#[from] CoreDumpError),
-    #[error(transparent)]
-    Other(#[from] anyhow::Error),
-}
-
-pub struct DebugCli {
-    commands: Vec<Command>,
-}
-
-/// Parse the argument at the given index.
-fn get_int_argument<T: Num>(args: &[&str], index: usize) -> Result<T, CliError>
-where
-    <T as Num>::FromStrRadixErr: std::error::Error + Send + Sync + 'static,
-{
-    let arg_str = args.get(index).ok_or(CliError::MissingArgument)?;
-
-    parse::<T>(arg_str).map_err(|e| CliError::ArgumentParseError {
-        argument_index: index,
-        argument: arg_str.to_string(),
-        source: e.into(),
-    })
-}
-
-impl DebugCli {
-    fn new() -> DebugCli {
-        let mut cli = DebugCli {
-            commands: Vec::new(),
-        };
-
-        cli.add_command(Command {
-            name: "step",
-            help_text: "Step a single instruction",
-
-            function: |cli_data, _args| {
-                let cpu_info = cli_data.core.step()?;
-                println!("Core stopped at address 0x{:08x}", cpu_info.pc);
-
-                Ok(CliState::Continue)
-            },
-        });
-
-        cli.add_command(Command {
-            name: "halt",
-            help_text: "Stop the CPU",
-
-            function: |cli_data, _args| {
-                let cpu_info = cli_data.core.halt(Duration::from_millis(100))?;
-                println!("Core stopped at address 0x{:08x}", cpu_info.pc);
-
-                let mut code = [0u8; 16 * 2];
-
-                cli_data.core.read(cpu_info.pc, &mut code)?;
-
-                let cs = match cli_data.core.instruction_set()? {
-                    InstructionSet::Thumb2 => Capstone::new()
-                        .arm()
-                        .mode(armArchMode::Thumb)
-                        .endian(Endian::Little)
-                        .build(),
-                    InstructionSet::A32 => {
-                        // We need to inspect the CPSR to determine what mode this is opearting in
-                        Capstone::new()
-                            .arm()
-                            .mode(armArchMode::Arm)
-                            .endian(Endian::Little)
-                            .build()
-                    }
-                    InstructionSet::A64 => {
-                        // We need to inspect the CPSR to determine what mode this is opearting in
-                        Capstone::new()
-                            .arm64()
-                            .mode(aarch64ArchMode::Arm)
-                            .endian(Endian::Little)
-                            .build()
-                    }
-                    InstructionSet::RV32 => Capstone::new()
-                        .riscv()
-                        .mode(riscvArchMode::RiscV32)
-                        .endian(Endian::Little)
-                        .build(),
-                    InstructionSet::RV32C => Capstone::new()
-                        .riscv()
-                        .mode(riscvArchMode::RiscV32)
-                        .endian(Endian::Little)
-                        .extra_mode(std::iter::once(
-                            capstone::arch::riscv::ArchExtraMode::RiscVC,
-                        ))
-                        .build(),
-                    InstructionSet::Xtensa => Err(capstone::Error::UnsupportedArch),
-                }
-                .map_err(|err| anyhow!("Error creating capstone: {:?}", err))?;
-
-                // Attempt to dissassemble
-                match cs.disasm_all(&code, cpu_info.pc) {
-                    Ok(instructions) => {
-                        for i in instructions.iter() {
-                            println!("{i}");
-                        }
-                    }
-                    Err(e) => {
-                        println!("Error disassembling instructions: {e}");
-
-                        // Fallback to raw output
-                        for (offset, instruction) in code.iter().enumerate() {
-                            println!(
-                                "{:#010x}: {:010x}",
-                                cpu_info.pc + offset as u64,
-                                instruction
-                            );
-                        }
-                    }
-                };
-
-                Ok(CliState::Continue)
-            },
-        });
-
-        cli.add_command(Command {
-            name: "status",
-            help_text: "Show current status of CPU",
-
-            function: |cli_data, _args| {
-                let status = cli_data.core.status()?;
-
-                println!("Status: {:?}", &status);
-
-                if status.is_halted() {
-                    let pc_desc = cli_data.core.program_counter();
-                    let pc: u64 = cli_data
-                        .core
-                        .read_core_reg(pc_desc)?;
-                    println!("Core halted at address {:#0width$x}", pc, width = pc_desc.format_hex_width());
-
-                    // determine if the target is handling an interupt
-
-                    if cli_data.core.architecture() == probe_rs::Architecture::Arm {
-                        match cli_data.core.core_type() {
-                            CoreType::Armv6m | CoreType::Armv7em | CoreType::Armv7m | CoreType::Armv8m | CoreType::Armv7a | CoreType::Armv8a => {
-                                // Unwrap is safe here because ARM always defines this register
-                                let psr_desc = cli_data.core.registers().psr().unwrap();
-
-                                let xpsr: u32 = cli_data.core.read_core_reg(
-                                    psr_desc,
-                                )?;
-
-                                println!("XPSR: {:#0width$x}", xpsr, width = psr_desc.format_hex_width());
-
-                                // This is Cortex-M specific interpretation
-                                // It's hard to generally model these concepts for any possible CoreType,
-                                // but it may be worth considering moving this into the CoreInterface somehow
-                                // in the future
-                                if cli_data.core.core_type().is_cortex_m() {
-
-                                    let exception_number = xpsr & 0xff;
-
-                                    if exception_number != 0 {
-                                        println!("Currently handling exception {exception_number}");
-
-                                        if exception_number == 3 {
-                                            println!("Hard Fault!");
-
-
-                                            let return_address: u64 = cli_data.core.read_core_reg(cli_data.core.return_address())?;
-
-                                            println!("Return address (LR): {return_address:#010x}");
-
-                                            // Get reason for hard fault
-                                            let hfsr = cli_data.core.read_word_32(0xE000_ED2C)?;
-
-                                            if hfsr & (1 << 30) == (1 << 30) {
-                                                println!("-> configurable priority exception has been escalated to hard fault!");
-
-
-                                                // read cfsr
-                                                let cfsr = cli_data.core.read_word_32(0xE000_ED28)?;
 
-                                                let ufsr = (cfsr >> 16) & 0xffff;
-                                                let bfsr = (cfsr >> 8) & 0xff;
-                                                let mmfsr = cfsr & 0xff;
-
-
-                                                if ufsr != 0 {
-                                                    println!("\tUsage Fault     - UFSR: {ufsr:#06x}");
-                                                }
-
-                                                if bfsr != 0 {
-                                                    println!("\tBus Fault       - BFSR: {bfsr:#04x}");
-
-                                                    if bfsr & (1 << 7) == (1 << 7) {
-                                                        // Read address from BFAR
-                                                        let bfar = cli_data.core.read_word_32(0xE000_ED38)?;
-                                                        println!("\t Location       - BFAR: {bfar:#010x}");
-                                                    }
-                                                }
-
-                                                if mmfsr != 0 {
-                                                    println!("\tMemManage Fault - BFSR: {bfsr:04x}");
-                                                }
-
-                                            }
-                                        }
-                                    }
-                                }
-                            },
-                            // Nothing extra to log
-                            _ => {},
-                        }
-                    }
-                }
-
-                Ok(CliState::Continue)
-            },
-        });
-
-        cli.add_command(Command {
-            name: "run",
-            help_text: "Resume execution of the CPU",
-
-            function: |cli_data, _args| {
-                cli_data.core.run()?;
-
-                Ok(CliState::Continue)
-            },
-        });
-
-        cli.add_command(Command {
-            name: "quit",
-            help_text: "Exit the program",
-
-            function: |_cli_data, _args| Ok(CliState::Stop),
-        });
-
-        cli.add_command(Command {
-            name: "read8",
-            help_text: "Read 8bit value from memory",
-
-            function: |cli_data, args| {
-                let address = get_int_argument(args, 0)?;
-
-                let num_bytes = if args.len() > 1 {
-                    get_int_argument(args, 1)?
-                } else {
-                    1
-                };
-
-                let mut buff = vec![0u8; num_bytes];
-
-                if num_bytes > 1 {
-                    cli_data.core.read_8(address, &mut buff)?;
-                } else {
-                    buff[0] = cli_data.core.read_word_8(address)?;
-                }
-
-                for (offset, byte) in buff.iter().enumerate() {
-                    println!("0x{:08x} = 0x{:02x}", address + (offset) as u64, byte);
-                }
-
-                Ok(CliState::Continue)
-            },
-        });
-
-        cli.add_command(Command {
-            name: "read16",
-            help_text: "Read 16bit value from memory",
-
-            function: |cli_data, args| {
-                let address = get_int_argument(args, 0)?;
-
-                let num_words = if args.len() > 1 {
-                    get_int_argument(args, 1)?
-                } else {
-                    1
-                };
-
-                let mut buff = vec![0u16; num_words];
-
-                if num_words > 1 {
-                    cli_data.core.read_16(address, &mut buff)?;
-                } else {
-                    buff[0] = cli_data.core.read_word_16(address)?;
-                }
-
-                for (offset, word) in buff.iter().enumerate() {
-                    println!("0x{:08x} = 0x{:04x}", address + (offset * 2) as u64, word);
-                }
-
-                Ok(CliState::Continue)
-            },
-        });
-
-        cli.add_command(Command {
-            name: "read32",
-            help_text: "Read 32bit value from memory",
-
-            function: |cli_data, args| {
-                let address = get_int_argument(args, 0)?;
-
-                let num_words = if args.len() > 1 {
-                    get_int_argument(args, 1)?
-                } else {
-                    1
-                };
-
-                let mut buff = vec![0u32; num_words];
-
-                if num_words > 1 {
-                    cli_data.core.read_32(address, &mut buff)?;
-                } else {
-                    buff[0] = cli_data.core.read_word_32(address)?;
-                }
-
-                for (offset, word) in buff.iter().enumerate() {
-                    println!("0x{:08x} = 0x{:08x}", address + (offset * 4) as u64, word);
-                }
-
-                Ok(CliState::Continue)
-            },
-        });
-
-        cli.add_command(Command {
-            name: "read64",
-            help_text: "Read 64bit value from memory",
-
-            function: |cli_data, args| {
-                let address = get_int_argument(args, 0)?;
+    fn send_raw_response(&mut self, response: &Response) -> anyhow::Result<()> {
+        if !response.success {
+            print_error(response)?;
+            return Ok(());
+        }
 
-                let num_words = if args.len() > 1 {
-                    get_int_argument(args, 1)?
-                } else {
-                    1
+        match response.command.as_str() {
+            "evaluate" => {
+                let Some(body) = &response.body else {
+                    unreachable!();
                 };
+                let body = serde_json::from_value::<EvaluateResponseBody>(body.clone())?;
 
-                let mut buff = vec![0u64; num_words];
-
-                if num_words > 1 {
-                    cli_data.core.read_64(address, &mut buff)?;
-                } else {
-                    buff[0] = cli_data.core.read_word_64(address)?;
-                }
-
-                for (offset, word) in buff.iter().enumerate() {
-                    println!("0x{:08x} = 0x{:02x}", address + (offset * 8) as u64, word);
-                }
-
-                Ok(CliState::Continue)
-            },
-        });
-
-        cli.add_command(Command {
-            name: "write8",
-            help_text: "Write a 8bit value to memory",
-
-            function: |cli_data, args| {
-                let address = get_int_argument(args, 0)?;
-                let data: u8 = get_int_argument(args, 1)?;
-
-                cli_data.core.write_word_8(address, data)?;
-
-                Ok(CliState::Continue)
-            },
-        });
-
-        cli.add_command(Command {
-            name: "write16",
-            help_text: "Write a 16bit value to memory",
-
-            function: |cli_data, args| {
-                let address = get_int_argument(args, 0)?;
-                let data = get_int_argument(args, 1)?;
-
-                cli_data.core.write_word_16(address, data)?;
-
-                Ok(CliState::Continue)
-            },
-        });
-
-        cli.add_command(Command {
-            name: "write32",
-            help_text: "Write a 32bit value to memory",
-
-            function: |cli_data, args| {
-                let address = get_int_argument(args, 0)?;
-                let data = get_int_argument(args, 1)?;
-
-                cli_data.core.write_word_32(address, data)?;
-
-                Ok(CliState::Continue)
-            },
-        });
-
-        cli.add_command(Command {
-            name: "write64",
-            help_text: "Write a 64bit value to memory",
-
-            function: |cli_data, args| {
-                let address = get_int_argument(args, 0)?;
-                let data = get_int_argument(args, 1)?;
-
-                cli_data.core.write_word_64(address, data)?;
-
-                Ok(CliState::Continue)
-            },
-        });
-
-        cli.add_command(Command {
-            name: "break",
-            help_text: "Set a breakpoint at a specific address",
-
-            function: |cli_data, args| {
-                let address = get_int_argument(args, 0)?;
-
-                cli_data.core.set_hw_breakpoint(address)?;
-
-                println!("Set new breakpoint at address {address:#08x}");
-
-                Ok(CliState::Continue)
-            },
-        });
-
-        cli.add_command(Command {
-            name: "clear_break",
-            help_text: "Clear a breakpoint",
-
-            function: |cli_data, args| {
-                let address = get_int_argument(args, 0)?;
-
-                cli_data.core.clear_hw_breakpoint(address)?;
-
-                Ok(CliState::Continue)
-            },
-        });
-
-        cli.add_command(Command {
-            name: "list_break",
-            help_text: "List all set breakpoints",
-            function: |cli_data, _| {
-                cli_data
-                    .core
-                    .hw_breakpoints()?
-                    .into_iter()
-                    .enumerate()
-                    .flat_map(|(idx, bpt)| bpt.map(|bpt| (idx, bpt)))
-                    .for_each(|(idx, bpt)| println!("Breakpoint {idx} - {bpt:#010X}"));
-
-                Ok(CliState::Continue)
-            },
-        });
-
-        cli.add_command(Command {
-            name: "bt",
-            help_text: "Show backtrace",
-
-            function: |cli_data, _args| {
-                match cli_data.state {
-                    DebugState::Halted(ref mut halted_state) => {
-                        if let Some(di) = &mut cli_data.debug_info {
-                            let initial_registers = DebugRegisters::from_core(&mut cli_data.core);
-                            let exception_interface =
-                                exception_handler_for_core(cli_data.core.core_type());
-                            let instruction_set = cli_data.core.instruction_set().ok();
-                            halted_state.stack_frames = di
-                                .unwind(
-                                    &mut cli_data.core,
-                                    initial_registers,
-                                    exception_interface.as_ref(),
-                                    instruction_set,
-                                )
-                                .unwrap();
-
-                            halted_state.frame_indices = halted_state
-                                .stack_frames
-                                .iter()
-                                .map(|sf| sf.id.into())
-                                .collect();
-
-                            for (i, frame) in halted_state.stack_frames.iter().enumerate() {
-                                print!("Frame {}: {} @ {}", i, frame.function_name, frame.pc);
-
-                                if frame.is_inlined {
-                                    print!(" inline");
-                                }
-                                println!();
-
-                                if let Some(location) = &frame.source_location {
-                                    print!("       ");
-
-                                    print!("{}", location.path.to_path().display());
-
-                                    if let Some(line) = location.line {
-                                        print!(":{line}");
-
-                                        if let Some(col) = location.column {
-                                            match col {
-                                                probe_rs_debug::ColumnType::LeftEdge => {
-                                                    print!(":1")
-                                                }
-                                                probe_rs_debug::ColumnType::Column(c) => {
-                                                    print!(":{c}")
-                                                }
-                                            }
-                                        }
-                                    }
-
-                                    println!();
-                                }
-                            }
-
-                            println!();
-                        } else {
-                            println!("No debug information present!");
-                        }
-                    }
-                    DebugState::Running => {
-                        println!("Core must be halted for this command.");
-                    }
-                }
-                Ok(CliState::Continue)
-            },
-        });
-
-        cli.add_command(Command {
-            name: "regs",
-            help_text: "Show CPU register values",
-
-            function: |cli_data, _args| {
-                match &cli_data.state {
-                    DebugState::Running => println!("Core must be halted for this command."),
-                    DebugState::Halted(state) => {
-                        if let Some(current_frame) = state.get_current_frame() {
-                            let registers = &current_frame.registers;
-
-                            for register in &registers.0 {
-                                print!("{:10}: ", register.core_register.name());
-
-                                if let Some(value) = &register.value {
-                                    println!("{:#}", value);
-                                } else {
-                                    println!("{}", "X".repeat(10));
-                                }
-                            }
-                        } else {
-                            let register_file = cli_data.core.registers();
-
-                            for register in register_file.core_registers() {
-                                let value: RegisterValue = cli_data.core.read_core_reg(register)?;
-
-                                println!("{:10}: {:#}", register.name(), value);
-                            }
-
-                            if let Some(psr) = register_file.psr() {
-                                let value: RegisterValue = cli_data.core.read_core_reg(psr)?;
-                                println!("{:10}: {:#}", psr.name(), value);
-                            }
-                        }
-                    }
-                }
-
-                Ok(CliState::Continue)
-            },
-        });
-
-        cli.add_command(Command {
-            name: "fp_regs",
-            help_text: "Show floating point register values",
-
-            function: |cli_data, _args| {
-                if !cli_data.core.fpu_support()? {
-                    println!("Floating point not supported");
-                } else {
-                    let register_file = cli_data.core.registers();
-
-                    let registers = register_file.fpu_registers();
-                    if let Some(registers) = registers {
-                        for register in registers {
-                            let value: RegisterValue = cli_data.core.read_core_reg(register)?;
-
-                            // Print out the register every way it can be interpretted.
-                            // For example, with a u128:
-                            // * Raw value as a u128
-                            // * Value cast as a f64
-                            // * Value cast as a f32
-                            println!("{:10}: {:#}", register.name(), value);
-
-                            if matches!(value, RegisterValue::U128(_) | RegisterValue::U64(_)) {
-                                let data: u128 = value.try_into()?;
-                                let bytes = (data as u64).to_le_bytes();
-                                let fp_data = f64::from_le_bytes(bytes);
-
-                                println!("{:>10}: {:#}", "[as f64]", fp_data);
-                            }
-
-                            if matches!(
-                                value,
-                                RegisterValue::U128(_)
-                                    | RegisterValue::U64(_)
-                                    | RegisterValue::U32(_)
-                            ) {
-                                let data: u128 = value.try_into()?;
-                                let bytes = (data as u32).to_le_bytes();
-                                let fp_data = f32::from_le_bytes(bytes);
-
-                                println!("{:>10}: {:#}", "[as f32]", fp_data);
-                            }
-
-                            println!();
-                        }
-                    } else {
-                        println!("Core has no floating point registers");
-                    }
-                }
-
-                Ok(CliState::Continue)
-            },
-        });
-
-        cli.add_command(Command {
-            name: "locals",
-            help_text: "List local variables",
-
-            function: |cli_data, _args| {
-                match cli_data.state {
-                    DebugState::Halted(ref mut halted_state) => {
-                        let current_frame =
-                            if let Some(current_frame) = halted_state.get_current_frame_mut() {
-                                current_frame
-                            } else {
-                                println!("StackFrame not found.");
-                                return Ok(CliState::Continue);
-                            };
-
-                        let local_variable_cache = if let Some(local_variable_cache) =
-                            &mut current_frame.local_variables
-                        {
-                            local_variable_cache
-                        } else {
-                            print!("No Local variables available");
-                            return Ok(CliState::Continue);
-                        };
-
-                        let mut locals = local_variable_cache.root_variable().clone();
-                        // By default, the first level children are always are lazy loaded, so we will force a load here.
-                        if locals.variable_node_type.is_deferred()
-                            && !local_variable_cache.has_children(&locals)
-                        {
-                            if let Err(error) = cli_data
-                                .debug_info
-                                .as_ref()
-                                .unwrap()
-                                .cache_deferred_variables(
-                                    local_variable_cache,
-                                    &mut cli_data.core,
-                                    &mut locals,
-                                    StackFrameInfo {
-                                        registers: &current_frame.registers,
-                                        frame_base: current_frame.frame_base,
-                                        canonical_frame_address: current_frame
-                                            .canonical_frame_address,
-                                    },
-                                )
-                            {
-                                println!("Failed to cache local variables: {error}");
-                                return Ok(CliState::Continue);
-                            }
-                        }
-                        let children = local_variable_cache.get_children(locals.variable_key());
-
-                        for child in children {
-                            println!(
-                                "{}: {} = {}",
-                                child.name,
-                                child.type_name(),
-                                child.to_string(local_variable_cache)
-                            );
-                        }
-                    }
-                    DebugState::Running => println!("Core must be halted for this command."),
-                }
-
-                Ok(CliState::Continue)
-            },
-        });
-
-        cli.add_command(Command {
-            name: "up",
-            help_text: "Move up a frame",
-
-            function: |cli_data, _args| {
-                match &mut cli_data.state {
-                    DebugState::Running => println!("Core must be halted for this command."),
-                    DebugState::Halted(halted_state) => {
-                        if halted_state.current_frame < halted_state.frame_indices.len() - 1 {
-                            halted_state.current_frame += 1;
-                        } else {
-                            println!(
-                                "Already at top-most frame. current frame: {}, indices: {:?}",
-                                halted_state.current_frame, halted_state.frame_indices
-                            );
-                        }
-                    }
-                }
-
-                Ok(CliState::Continue)
-            },
-        });
-
-        cli.add_command(Command {
-            name: "down",
-            help_text: "Move down a frame",
-
-            function: |cli_data, _args| {
-                match &mut cli_data.state {
-                    DebugState::Running => println!("Core must be halted for this command."),
-                    DebugState::Halted(halted_state) => {
-                        if halted_state.current_frame > 0 {
-                            halted_state.current_frame -= 1;
-                        } else {
-                            println!("Already at bottom-most frame.");
-                        }
-                    }
-                }
-
-                Ok(CliState::Continue)
-            },
-        });
-
-        cli.add_command(Command {
-            name: "reset",
-
-            help_text: "Reset the CPU",
-
-            function: |cli_data, _args| {
-                cli_data.core.halt(Duration::from_millis(100))?;
-                cli_data.core.reset_and_halt(Duration::from_millis(100))?;
-
-                Ok(CliState::Continue)
-            },
-        });
-
-        cli.add_command(Command {
-            name: "dump",
-            help_text: "Dump the core memory & registers",
-
-            function: |cli_data, args| {
-                let mut args = args.to_vec();
-
-                // If we get an odd number of arguments, treat all n * 2 args at the start as memory blocks
-                // and the last argument as the path tho store the coredump at.
-                let location = Path::new(
-                    if args.len() % 2 != 0 {
-                        args.pop()
-                    } else {
-                        None
-                    }
-                    .unwrap_or("./coredump"),
-                );
-
-                let ranges = args
-                    .chunks(2)
-                    .enumerate()
-                    .map(|(i, c)| {
-                        let start = if let Some(start) = c.first() {
-                            parse_int::parse::<u64>(start).map_err(|e| {
-                                CliError::ArgumentParseError {
-                                    argument_index: i,
-                                    argument: start.to_string(),
-                                    source: e.into(),
-                                }
-                            })?
-                        } else {
-                            unreachable!("This should never be reached as there cannot be an odd number of arguments. Please report this as a bug.")
-                        };
-
-                        let size = if let Some(size) = c.get(1) {
-                            parse_int::parse::<u64>(size).map_err(|e| {
-                                CliError::ArgumentParseError {
-                                    argument_index: i,
-                                    argument: size.to_string(),
-                                    source: e.into(),
-                                }
-                            })?
-                        } else {
-                            unreachable!("This should never be reached as there cannot be an odd number of arguments. Please report this as a bug.")
-                        };
-
-                        Ok::<_, CliError>(start..start + size)
-                    })
-                    .collect::<Result<Vec<_>, _>>()?;
-
-                println!("Dumping core");
-
-                CoreDump::dump_core(&mut cli_data.core, ranges)?.store(location)?;
-
-                println!("Done.");
-
-                Ok(CliState::Continue)
-            },
-        });
-
-        cli
-    }
-
-    fn add_command(&mut self, command: Command) {
-        self.commands.push(command)
-    }
-
-    fn handle_line(&self, line: &str, cli_data: &mut CliData) -> Result<CliState, CliError> {
-        let mut command_parts = line.split_whitespace();
-
-        match command_parts.next() {
-            Some("help") => {
-                println!("The following commands are available:");
-
-                for cmd in &self.commands {
-                    println!(" - {}", cmd.name);
-                }
-
-                Ok(CliState::Continue)
+                println!("{}", body.result);
             }
-            Some(command) => {
-                let cmd = self.commands.iter().find(|c| c.name == command);
-
-                if let Some(cmd) = cmd {
-                    let remaining_args: Vec<&str> = command_parts.collect();
+            "initialize" => {}
+            "attach" => {}
+            _ => println!("{response:?}"),
+        }
 
-                    Self::execute_command(cli_data, cmd, &remaining_args)
-                } else {
-                    println!("Unknown command '{command}'");
-                    println!("Enter 'help' for a list of commands");
+        Ok(())
+    }
 
-                    Ok(CliState::Continue)
-                }
+    fn remove_pending_request(&mut self, request_seq: i64) -> Option<String> {
+        self.shared.borrow_mut().next_request.take().and_then(|r| {
+            if request_seq == r.seq {
+                Some(r.command)
+            } else {
+                None
             }
-            _ => Ok(CliState::Continue),
-        }
+        })
     }
 
-    fn execute_command(
-        cli_data: &mut CliData,
-        command: &Command,
-        args: &[&str],
-    ) -> Result<CliState, CliError> {
-        match (command.function)(cli_data, args) {
-            Ok(cli_state) => {
-                // Resync status from core
-                cli_data.update_debug_status_from_core()?;
+    fn set_console_log_level(&mut self, log_level: ConsoleLog) {
+        self.console_log_level = log_level;
+    }
 
-                Ok(cli_state)
-            }
-            Err(CliError::MissingArgument) => {
-                println!("Error: Missing argument\n\n{}", command.help_text);
-                Ok(CliState::Continue)
-            }
-            Err(CliError::ArgumentParseError {
-                argument, source, ..
-            }) => {
-                println!(
-                    "Error parsing argument '{}': {}\n\n{}",
-                    argument, source, command.help_text
-                );
-                Ok(CliState::Continue)
-            }
-            other => other,
-        }
+    fn console_log_level(&self) -> ConsoleLog {
+        self.console_log_level
     }
 }
 
-pub struct CliData<'p> {
-    pub core: Core<'p>,
-    pub debug_info: Option<DebugInfo>,
+fn print_error(response: &Response) -> anyhow::Result<()> {
+    if response.message.as_deref() != Some("cancelled") {
+        // `ProtocolHelper::send_response` sets `cancelled` as the message.
+        println!(
+            "Error while processing {} - unexpected response: {:?}",
+            response.command, response.message
+        );
+        return Ok(());
+    }
 
-    state: DebugState,
+    let Some(body) = response.body.clone() else {
+        // `ProtocolHelper::send_response` sets an error body.
+        println!(
+            "Unspecified error while processing {} - response has no body",
+            response.command
+        );
+        return Ok(());
+    };
+
+    let response_body = serde_json::from_value::<ErrorResponseBody>(body)?;
+
+    let Some(error) = response_body.error else {
+        // `ProtocolHelper::send_response` returns some error to us.
+        println!(
+            "Unspecified error while processing {} - response body has no error.",
+            response.command
+        );
+        return Ok(());
+    };
+
+    println!("Error while processing {}: {}", response.command, error);
+
+    Ok(())
 }
 
-impl<'p> CliData<'p> {
-    fn new(core: Core<'p>, debug_info: Option<DebugInfo>) -> Result<CliData<'p>, CliError> {
-        let mut cli_data = CliData {
-            core,
-            debug_info,
-            state: DebugState::default(),
-        };
+#[derive(clap::Parser)]
+pub struct Cmd {
+    #[clap(flatten)]
+    shared: CoreOptions,
 
-        cli_data.update_debug_status_from_core()?;
+    #[clap(flatten)]
+    common: ProbeOptions,
 
-        Ok(cli_data)
-    }
+    #[clap(long, value_parser)]
+    /// Binary to debug
+    exe: Option<PathBuf>,
+}
 
-    /// Fill out DebugStatus for a given core
-    fn update_debug_status_from_core(&mut self) -> Result<(), CliError> {
-        let status = self.core.status()?;
+impl Cmd {
+    pub fn run(
+        self,
+        registry: &mut Registry,
+        lister: &Lister,
+        utc_offset: UtcOffset,
+    ) -> anyhow::Result<()> {
+        let shared = Rc::new(RefCell::new(Shared {
+            stop: false,
+            next_request: None,
+        }));
+        let mut debug_adapter = DebugAdapter::new(CliAdapter {
+            shared: shared.clone(),
+            console_log_level: ConsoleLog::Console,
+        });
+        let mut debugger = Debugger::new(utc_offset, None)?;
+
+        shared.borrow_mut().next_request = Some(Request {
+            command: "initialize".to_string(),
+            arguments: serde_json::to_value(&InitializeRequestArguments {
+                adapter_id: "probe-rs-cli".to_string(),
+                client_id: Some("probe-rs-cli".to_string()),
+                client_name: Some("probe-rs command-line debugger".to_string()),
+                columns_start_at_1: None,
+                lines_start_at_1: None,
+                locale: None,
+                path_format: None,
+                supports_args_can_be_interpreted_by_shell: None,
+                supports_invalidated_event: None,
+                supports_memory_event: None,
+                supports_memory_references: None,
+                supports_progress_reporting: None,
+                supports_run_in_terminal_request: None,
+                supports_start_debugging_request: None,
+                supports_variable_paging: None,
+                supports_variable_type: None,
+            })
+            .ok(),
+            seq: 0,
+            type_: "request".to_string(),
+        });
+        debugger.handle_initialize(&mut debug_adapter)?;
+
+        let attach_request = Request {
+            command: "attach".to_string(),
+            arguments: serde_json::to_value(&SessionConfig {
+                console_log_level: None,
+                cwd: None,
+                probe: self.common.probe,
+                chip: self.common.chip,
+                chip_description_path: self.common.chip_description_path,
+                connect_under_reset: self.common.connect_under_reset,
+                speed: self.common.speed,
+                wire_protocol: self.common.protocol,
+                allow_erase_all: false,
+                flashing_config: FlashingConfig::default(),
+                core_configs: vec![CoreConfig {
+                    core_index: self.shared.core,
+                    program_binary: self.exe,
+                    svd_file: None,
+                    rtt_config: RttConfig {
+                        enabled: false,
+                        channels: vec![],
+                    },
+                }],
+            })
+            .ok(),
+            seq: 1,
+            type_: "request".to_string(),
+        };
 
-        match status {
-            probe_rs::CoreStatus::Halted(_) => {
-                let registers = DebugRegisters::from_core(&mut self.core);
-                let pc: u64 = registers
-                    .get_program_counter()
-                    .and_then(|reg| reg.value)
-                    .unwrap_or_default()
-                    .try_into()?;
+        // A bit weird since we need the request to be removable,
+        // but we also need to pass it directly.
+        shared.borrow_mut().next_request = Some(attach_request.clone());
+        let mut session_data =
+            debugger.handle_launch_attach(registry, &attach_request, &mut debug_adapter, lister)?;
+
+        shared.borrow_mut().next_request = Some(Request {
+            command: "configurationDone".to_string(),
+            arguments: serde_json::to_value(()).ok(),
+            seq: 2,
+            type_: "request".to_string(),
+        });
+        debugger.process_next_request(&mut session_data, &mut debug_adapter)?;
 
-                // If the core was running before, or the PC changed, we need to update the state
-                let core_state_changed = matches!(self.state, DebugState::Running)
-                    || matches!(self.state, DebugState::Halted(HaltedState { program_counter, .. }) if program_counter != pc);
+        let mut rl = DefaultEditor::new()?;
 
-                // TODO: We should resolve the stack frames here
-                if core_state_changed {
-                    self.state = DebugState::Halted(HaltedState {
-                        program_counter: pc,
-                        current_frame: 0,
-                        frame_indices: vec![1],
-                        stack_frames: vec![],
-                    });
+        let mut seq = 3;
+        while !shared.borrow().stop {
+            match rl.readline(">> ") {
+                Ok(line) => {
+                    rl.add_history_entry(&line)?;
+
+                    let request = Request {
+                        command: "evaluate".to_string(),
+                        arguments: serde_json::to_value(&EvaluateArguments {
+                            context: Some("repl".to_string()),
+                            expression: line,
+                            format: None,
+                            frame_id: None,
+                        })
+                        .ok(),
+                        seq: {
+                            let s = seq;
+                            seq += 1;
+                            s
+                        },
+                        type_: "request".to_string(),
+                    };
+
+                    shared.borrow_mut().next_request = Some(request);
+                    debugger.process_next_request(&mut session_data, &mut debug_adapter)?;
                 }
-            }
-            _other => {
-                self.state = DebugState::Running;
-            }
-        }
-
-        Ok(())
-    }
-
-    fn print_state(&mut self) -> Result<(), CliError> {
-        match self.state {
-            DebugState::Running => println!("Core is running."),
-            DebugState::Halted(ref mut halted_state) => {
-                if let Some(current_stack_frame) = halted_state.get_current_frame() {
-                    let pc = current_stack_frame.pc;
-
-                    println!(
-                        "Frame {}: {} () @ {:#}",
-                        halted_state.current_frame, current_stack_frame.function_name, pc,
-                    );
+                // For end of file and ctrl-c, we just quit
+                Err(ReadlineError::Eof | ReadlineError::Interrupted) => return Ok(()),
+                Err(actual_error) => {
+                    // Show error message and quit
+                    println!("Error handling input: {actual_error:?}");
+                    break;
                 }
             }
         }
@@ -1043,40 +294,3 @@ impl<'p> CliData<'p> {
         Ok(())
     }
 }
-
-#[derive(Default)]
-enum DebugState {
-    #[default]
-    Running,
-    Halted(HaltedState),
-}
-
-struct HaltedState {
-    program_counter: u64,
-    current_frame: usize,
-    frame_indices: Vec<i64>,
-    stack_frames: Vec<StackFrame>,
-}
-
-impl HaltedState {
-    fn get_current_frame(&self) -> Option<&probe_rs_debug::stack_frame::StackFrame> {
-        self.stack_frames.get(self.current_frame)
-    }
-
-    fn get_current_frame_mut(&mut self) -> Option<&mut probe_rs_debug::stack_frame::StackFrame> {
-        self.stack_frames.get_mut(self.current_frame)
-    }
-}
-
-#[derive(PartialEq)]
-pub enum CliState {
-    Continue,
-    Stop,
-}
-
-struct Command {
-    pub name: &'static str,
-    pub help_text: &'static str,
-
-    pub function: fn(&mut CliData, args: &[&str]) -> Result<CliState, CliError>,
-}
diff --git a/probe-rs-tools/src/bin/probe-rs/main.rs b/probe-rs-tools/src/bin/probe-rs/main.rs
index b2ce038a..78adee2f 100644
--- a/probe-rs-tools/src/bin/probe-rs/main.rs
+++ b/probe-rs-tools/src/bin/probe-rs/main.rs
@@ -119,7 +119,7 @@ impl Cli {
             Subcommand::Info(cmd) => cmd.run(client).await,
             Subcommand::Gdb(cmd) => cmd.run(&mut *client.registry().await, &lister),
             Subcommand::Reset(cmd) => cmd.run(client).await,
-            Subcommand::Debug(cmd) => cmd.run(&mut *client.registry().await, &lister),
+            Subcommand::Debug(cmd) => cmd.run(&mut *client.registry().await, &lister, utc_offset),
             Subcommand::Download(cmd) => cmd.run(client).await,
             Subcommand::Run(cmd) => cmd.run(client, utc_offset).await,
             Subcommand::Attach(cmd) => cmd.run(client, utc_offset).await,
diff --git a/probe-rs-tools/src/bin/probe-rs/util/rtt/client.rs b/probe-rs-tools/src/bin/probe-rs/util/rtt/client.rs
index 3152a291..faa22ec1 100644
--- a/probe-rs-tools/src/bin/probe-rs/util/rtt/client.rs
+++ b/probe-rs-tools/src/bin/probe-rs/util/rtt/client.rs
@@ -39,7 +39,7 @@ impl RttClient {
 
             target: None,
             last_control_block_address: None,
-            disallow_clearing_rtt_header: true,
+            disallow_clearing_rtt_header: false,
             try_attaching: true,
             polled_data: false,
             core_id: 0,
diff --git a/probe-rs/src/architecture/arm/sequences.rs b/probe-rs/src/architecture/arm/sequences.rs
index bac605b1..07d255a6 100644
--- a/probe-rs/src/architecture/arm/sequences.rs
+++ b/probe-rs/src/architecture/arm/sequences.rs
@@ -603,23 +603,35 @@ pub trait ArmDebugSequence: Send + Sync + Debug {
         interface: &mut dyn DapAccess,
         dp: DpAddress,
     ) -> Result<(), ArmError> {
-        // Clear all errors.
-        // CMSIS says this is only necessary to do inside the `if powered_down`, but
-        // without it here, nRF52840 faults in the next access.
-        let mut abort = Abort(0);
-        abort.set_dapabort(true);
-        abort.set_orunerrclr(true);
-        abort.set_wderrclr(true);
-        abort.set_stkerrclr(true);
-        abort.set_stkcmpclr(true);
-        interface.write_dp_register(dp, abort)?;
-
         interface.write_dp_register(dp, SelectV1(0))?;
         let dpidr: DPIDR = interface.read_dp_register(dp)?;
 
+        // Clear all errors and see if we need to power up the target
         let ctrl = interface.read_dp_register::<Ctrl>(dp)?;
+        let powered_down = if dpidr.version() == 0 || dpidr.designer() == 0 {
+            // Abort the current transaction. This is the only bit present in `Abort` in `DPv0`.
+            let mut abort = Abort(0);
+            abort.set_dapabort(true);
+            interface.write_dp_register(dp, abort)?;
+
+            // To clear errors on DPv0, we need to set W1C values for STICKYORUN, STICKYCMP, and STICKYERR
+            interface.write_dp_register::<Ctrl>(dp, Ctrl(0x32))?;
 
-        let powered_down = !(ctrl.csyspwrupack() && ctrl.cdbgpwrupack());
+            // DPv0 always needs to be powered up
+            true
+        } else {
+            // CMSIS says this is only necessary to do inside the `if powered_down`, but
+            // without it here, nRF52840 faults in the next access.
+            let mut abort = Abort(0);
+            abort.set_dapabort(true);
+            abort.set_orunerrclr(true);
+            abort.set_wderrclr(true);
+            abort.set_stkerrclr(true);
+            abort.set_stkcmpclr(true);
+            interface.write_dp_register(dp, abort)?;
+
+            !(ctrl.csyspwrupack() && ctrl.cdbgpwrupack())
+        };
 
         if powered_down {
             tracing::info!("Debug port {dp:x?} is powered down, powering up");
diff --git a/probe-rs/src/flashing/mod.rs b/probe-rs/src/flashing/mod.rs
index 29618c09..b2f9fda3 100644
--- a/probe-rs/src/flashing/mod.rs
+++ b/probe-rs/src/flashing/mod.rs
@@ -14,9 +14,10 @@
 //! and looks like this:
 //!
 //! ```no_run
-//! use probe_rs::{Session, flashing, Permissions};
+//! use probe_rs::{Session, SessionConfig, flashing, Permissions};
 //!
-//! let mut session = Session::auto_attach("nrf51822", Permissions::default())?;
+//! let session_config = SessionConfig::default();
+//! let mut session = Session::auto_attach("nrf51822", session_config)?;
 //!
 //! flashing::download_file(&mut session, "binary.hex", flashing::Format::Hex)?;
 //!
@@ -26,10 +27,10 @@
 //! ### Adding data manually
 //!
 //! ```no_run
-//! use probe_rs::{Session, flashing::{FlashLoader, DownloadOptions}, Permissions};
+//! use probe_rs::{Session, SessionConfig, flashing::{FlashLoader, DownloadOptions}, Permissions};
 //!
-//!
-//! let mut session = Session::auto_attach("nrf51822", Permissions::default())?;
+//! let session_config = SessionConfig::default();
+//! let mut session = Session::auto_attach("nrf51822", session_config)?;
 //!
 //! let mut loader = session.target().flash_loader();
 //!
diff --git a/probe-rs/src/lib.rs b/probe-rs/src/lib.rs
index 8a97aed3..8739f4f3 100644
--- a/probe-rs/src/lib.rs
+++ b/probe-rs/src/lib.rs
@@ -37,9 +37,10 @@
 //!
 //! ```no_run
 //! # use probe_rs::Error;
-//! use probe_rs::{Session, Permissions, MemoryInterface};
+//! use probe_rs::{Session, SessionConfig, MemoryInterface};
 //!
-//! let mut session = Session::auto_attach("nrf52", Permissions::default())?;
+//! let session_config = SessionConfig::default();
+//! let mut session = Session::auto_attach("nrf52", session_config)?;
 //! let mut core = session.core(0)?;
 //!
 //! // Read a block of 50 32 bit words.
@@ -98,7 +99,7 @@ pub use crate::core::{
 };
 pub use crate::error::Error;
 pub use crate::memory::MemoryInterface;
-pub use crate::session::{Permissions, Session};
+pub use crate::session::{Permissions, Session, SessionConfig};
 
 #[doc = include_str!("../../README.md")]
 #[cfg(doctest)]
diff --git a/probe-rs/src/session.rs b/probe-rs/src/session.rs
index e3431211..47fe9d68 100644
--- a/probe-rs/src/session.rs
+++ b/probe-rs/src/session.rs
@@ -19,8 +19,8 @@ use crate::{
     config::{CoreExt, DebugSequence, RegistryError, Target, TargetSelector, registry::Registry},
     core::{Architecture, CombinedCoreState},
     probe::{
-        AttachMethod, DebugProbeError, Probe, ProbeCreationError, fake_probe::FakeProbe,
-        list::Lister,
+        AttachMethod, DebugProbeError, Probe, ProbeCreationError, WireProtocol,
+        fake_probe::FakeProbe, list::Lister,
     },
 };
 use std::ops::DerefMut;
@@ -52,6 +52,23 @@ pub struct Session {
     configured_trace_sink: Option<TraceSink>,
 }
 
+/// The `SessionConfig` struct is used to configure a new `Session` during auto-attach.
+///
+/// ## Configuring auto attach
+/// The SessionConfig can be used to control the behavior of the auto-attach function.
+/// It should be used in the [Session::auto_attach()] method.
+/// This includes setting the speed of the probe and the protocol to use, as well as the permissions.
+///
+#[derive(Default, Debug)]
+pub struct SessionConfig {
+    /// Debug permissions
+    pub permissions: Permissions,
+    /// Speed of the WireProtocol in kHz
+    pub speed: Option<u32>,
+    /// WireProtocol to use
+    pub protocol: Option<WireProtocol>,
+}
+
 #[allow(clippy::large_enum_variant)]
 enum JtagInterface {
     Riscv(RiscvDebugInterfaceState),
@@ -410,7 +427,7 @@ impl Session {
     #[tracing::instrument(skip(target))]
     pub fn auto_attach(
         target: impl Into<TargetSelector>,
-        permissions: Permissions,
+        session_config: SessionConfig,
     ) -> Result<Session, Error> {
         // Get a list of all available debug probes.
         let lister = Lister::new();
@@ -418,15 +435,24 @@ impl Session {
         let probes = lister.list_all();
 
         // Use the first probe found.
-        let probe = probes
+        let mut probe = probes
             .first()
             .ok_or(Error::Probe(DebugProbeError::ProbeCouldNotBeCreated(
                 ProbeCreationError::NotFound,
             )))?
             .open()?;
 
+        // If the caller has specified speed or protocol in SessionConfig, set them
+        if let Some(speed) = session_config.speed {
+            probe.set_speed(speed)?;
+        }
+
+        if let Some(protocol) = session_config.protocol {
+            probe.select_protocol(protocol)?;
+        }
+
         // Attach to a chip.
-        probe.attach(target, permissions)
+        probe.attach(target, session_config.permissions)
     }
 
     /// Lists the available cores with their number and their type.
diff --git a/probe-rs/src/vendor/ti/mod.rs b/probe-rs/src/vendor/ti/mod.rs
index f38e5f20..19b6a706 100644
--- a/probe-rs/src/vendor/ti/mod.rs
+++ b/probe-rs/src/vendor/ti/mod.rs
@@ -4,7 +4,10 @@ use probe_rs_target::Chip;
 
 use crate::{
     config::DebugSequence,
-    vendor::{Vendor, ti::sequences::cc13xx_cc26xx::CC13xxCC26xx},
+    vendor::{
+        Vendor,
+        ti::sequences::{cc13xx_cc26xx::CC13xxCC26xx, cc23xx_cc27xx::CC23xxCC27xx},
+    },
 };
 
 pub mod sequences;
@@ -17,6 +20,8 @@ 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 if chip.name.starts_with("CC23") || chip.name.starts_with("CC27") {
+            DebugSequence::Arm(CC23xxCC27xx::create(chip.name.clone()))
         } else {
             return None;
         };
diff --git a/probe-rs/src/vendor/ti/sequences/cc23xx_cc27xx.rs b/probe-rs/src/vendor/ti/sequences/cc23xx_cc27xx.rs
new file mode 100644
index 00000000..480125b8
--- /dev/null
+++ b/probe-rs/src/vendor/ti/sequences/cc23xx_cc27xx.rs
@@ -0,0 +1,377 @@
+//! Sequences for cc23xx_cc27xx devices
+use bitfield::bitfield;
+use std::ops::DerefMut;
+use std::sync::Arc;
+use std::sync::atomic::{AtomicBool, Ordering};
+use std::thread;
+use std::time::{Duration, Instant};
+
+use crate::MemoryMappedRegister;
+use crate::architecture::arm::ArmProbeInterface;
+use crate::architecture::arm::DapAccess;
+use crate::architecture::arm::armv6m::{Aircr, BpCtrl, Demcr, Dhcsr};
+use crate::architecture::arm::core::cortex_m;
+use crate::architecture::arm::dp::{Abort, Ctrl, DebugPortError, DpAccess, DpAddress, SelectV1};
+use crate::architecture::arm::memory::ArmMemoryInterface;
+use crate::architecture::arm::sequences::{ArmDebugSequence, cortex_m_core_start};
+use crate::architecture::arm::{ArmError, FullyQualifiedApAddress};
+use probe_rs_target::CoreType;
+
+/// Marker struct indicating initialization sequencing for cc23xx_cc27xx family parts.
+#[derive(Debug)]
+pub struct CC23xxCC27xx {
+    /// Chip name - this will be used when more targets are added
+    _name: String,
+    /// Flag to indicate if the ROM is in the boot loop
+    boot_loop: AtomicBool,
+}
+
+/// Enum representing the Access Port Select register values
+#[derive(Debug, Clone, Copy)]
+enum ApSel {
+    /// Config-AP: This is the AP used to read device type information
+    CfgAp = 1,
+    /// Sec-AP: This is the AP used to send SACI commands
+    SecAp = 2,
+}
+
+bitfield! {
+    /// Device Status Register, part of CFG-AP.
+    ///
+    /// This register is used to read the device status and boot status.
+    #[derive(Copy, Clone)]
+    pub struct DeviceStatusRegister(u32);
+    impl Debug;
+    ///  Bit describing if the AHB-AP is available
+    ///
+    /// `0`: Device is in SACI mode\
+    /// `1`: Device is not in SACI mode and AHB-AP is available
+    pub ahb_ap_available, _: 24;
+
+    /// Boot Status
+    ///
+    /// This field is used to read the boot status of the device.
+    pub u8, boot_status, _: 15, 8;
+}
+
+impl DeviceStatusRegister {
+    /// Address of the device status register within the CFG-AP.
+    pub const DEVICE_STATUS_REGISTER_ADDRESS: u64 = 0x0C;
+
+    /// Read the device status register from the CFG-AP.
+    pub fn read(interface: &mut dyn DapAccess) -> Result<Self, ArmError> {
+        let cfg_ap: FullyQualifiedApAddress = ApSel::CfgAp.into();
+        let contents =
+            interface.read_raw_ap_register(&cfg_ap, Self::DEVICE_STATUS_REGISTER_ADDRESS)?;
+        Ok(Self(contents))
+    }
+}
+
+const BOOT_STATUS_APP_WAITLOOP_DBGPROBE: u8 = 0xC1;
+const BOOT_STATUS_BLDR_WAITLOOP_DBGPROBE: u8 = 0x81;
+const BOOT_STATUS_BOOT_WAITLOOP_DBGPROBE: u8 = 0x38;
+
+bitfield! {
+    /// TX_CTRL Register, part of SEC-AP.
+    ///
+    /// This register is used to control the transmission of SACI commands.
+    #[derive(Copy, Clone)]
+    pub struct TxCtrlRegister(u32);
+    impl Debug;
+    /// Bit indicating if the TXD register is ready.
+    ///
+    /// Indicates that TXD can be read. Set by hardware when TXD is written, cleared by hardware when TXD is read
+    ///
+    /// `0`: TXD is ready
+    /// `1`: TXD is not ready
+    pub txd_full, _: 0;
+    /// Command Start
+    ///
+    /// This field is used to start a command.
+    pub cmd_start, set_cmd_start: 1;
+}
+
+impl TxCtrlRegister {
+    /// Address of the TX_CTRL register within the SEC-AP.
+    pub const TX_CTRL_REGISTER_ADDRESS: u64 = 4;
+
+    /// Read the TX_CTRL register from the SEC-AP.
+    pub fn read(interface: &mut dyn DapAccess) -> Result<Self, ArmError> {
+        let sec_ap: FullyQualifiedApAddress = ApSel::SecAp.into();
+        let contents = interface.read_raw_ap_register(&sec_ap, Self::TX_CTRL_REGISTER_ADDRESS)?;
+        Ok(Self(contents))
+    }
+
+    /// Write the TX_CTRL register to the SEC-AP.
+    pub fn write(&self, interface: &mut dyn DapAccess) -> Result<(), ArmError> {
+        let sec_ap: FullyQualifiedApAddress = ApSel::SecAp.into();
+        interface.write_raw_ap_register(&sec_ap, Self::TX_CTRL_REGISTER_ADDRESS, self.0)
+    }
+}
+
+impl From<ApSel> for FullyQualifiedApAddress {
+    fn from(apsel: ApSel) -> Self {
+        FullyQualifiedApAddress::v1_with_default_dp(apsel as u8)
+    }
+}
+
+impl CC23xxCC27xx {
+    /// Create the sequencer for the cc23xx_cc27xx family of parts.
+    pub fn create(name: String) -> Arc<Self> {
+        Arc::new(Self {
+            _name: name,
+            boot_loop: AtomicBool::new(false),
+        })
+    }
+
+    /// Check if the ROM is in the boot loop
+    ///
+    /// The boot loop is a state where the ROM is waiting for a debugger to attach and write to R3 to exit the loop.
+    /// This needs to be tracked across multiple debug sequence states so it is stored on the host.
+    fn is_in_boot_loop(&self) -> bool {
+        self.boot_loop.load(Ordering::SeqCst)
+    }
+
+    /// Polls the TX_CTRL register until it is ready or a timeout occurs.
+    ///
+    /// This function reads the TX_CTRL register in a loop until it indicates readiness
+    /// or the specified timeout duration has elapsed.
+    ///
+    /// # Arguments
+    ///
+    /// * `interface` - A mutable reference to the ARM communication interface.
+    /// * `timeout` - The maximum duration to wait for the TX_CTRL register to be ready.
+    ///
+    /// # Returns
+    ///
+    /// * `Result<(), ArmError>` - Returns `Ok(())` if the TX_CTRL register is ready,
+    ///   or an `ArmError` if there was a timeout.
+    fn poll_tx_ctrl(
+        &self,
+        interface: &mut dyn DapAccess,
+        timeout: Duration,
+    ) -> Result<(), ArmError> {
+        let start = Instant::now();
+        let mut tx_ctrl = TxCtrlRegister::read(interface)?;
+        TxCtrlRegister::read(interface)?;
+        while tx_ctrl.txd_full() {
+            if start.elapsed() >= timeout {
+                return Err(ArmError::Timeout);
+            }
+            tx_ctrl = TxCtrlRegister::read(interface)?;
+        }
+        Ok(())
+    }
+
+    /// Sends a SACI command to the device.
+    ///
+    /// This function communicates with the device using the Security Access Port (SEC AP)
+    /// to send a SACI command. It waits for the TX_CTRL register to be ready before sending
+    /// the command and then writes the command to the TX_DATA register. Again waiting for TX_CTRL to be ready.
+    ///
+    /// Implements Section 8.3.1.1 from https://www.ti.com/lit/ug/swcu193/swcu193.pdf
+    ///
+    /// # Arguments
+    ///
+    /// * `interface` - A mutable reference to the ARM communication interface.
+    /// * `command` - The SACI command to be sent.
+    ///
+    /// # Returns
+    ///
+    /// * `Result<(), ArmError>` - Returns `Ok(())` if the command was successfully sent,
+    ///   or an `ArmError` if there was an error during communication.
+    ///
+    fn saci_command(&self, interface: &mut dyn DapAccess, command: u32) -> Result<(), ArmError> {
+        let sec_ap: FullyQualifiedApAddress = ApSel::SecAp.into();
+
+        const TX_DATA_ADDR: u64 = 0;
+
+        // Wait for tx_ctrl to be ready with a timeout of 1 millisecond
+        self.poll_tx_ctrl(interface, Duration::from_millis(1))?;
+
+        // Set Cmd Start
+        let mut tx_ctrl = TxCtrlRegister(0);
+        tx_ctrl.set_cmd_start(true);
+        TxCtrlRegister::write(&tx_ctrl, interface)?;
+
+        // Write parameter word to txd
+        interface.write_raw_ap_register(&sec_ap, TX_DATA_ADDR, command)?;
+
+        self.poll_tx_ctrl(interface, Duration::from_millis(1))?;
+
+        Ok(())
+    }
+}
+
+impl ArmDebugSequence for CC23xxCC27xx {
+    fn reset_system(
+        &self,
+        probe: &mut dyn ArmMemoryInterface,
+        core_type: probe_rs_target::CoreType,
+        debug_base: Option<u64>,
+    ) -> Result<(), ArmError> {
+        // Check if the previous code requested a halt before reset
+        let demcr = Demcr(probe.read_word_32(Demcr::get_mmio_address())?);
+
+        // Read if breakpoints should be enabled after reset
+        let mut bpt_ctrl = BpCtrl(probe.read_word_32(BpCtrl::get_mmio_address())?);
+
+        let mut aircr = Aircr(0);
+        aircr.vectkey();
+        aircr.set_sysresetreq(true);
+
+        // Reset the device, flush all pending writes and wait on the reset to complete
+        probe.write_word_32(Aircr::get_mmio_address(), aircr.into())?;
+        probe.flush().ok();
+        thread::sleep(Duration::from_millis(10));
+
+        // Re-initializing the core(s) is on us.
+        let ap = probe.fully_qualified_address();
+        let interface = probe.get_arm_probe_interface()?;
+
+        interface.reinitialize()?;
+        self.debug_core_start(interface, &ap, core_type, debug_base, None)?;
+
+        // Halt the CPU
+        if demcr.vc_corereset() {
+            let mut value = Dhcsr(0);
+            value.set_c_halt(true);
+            value.set_c_debugen(true);
+            value.enable_write();
+
+            probe.write_word_32(Dhcsr::get_mmio_address(), value.into())?;
+        }
+
+        // Restore the breakpoint control register
+        bpt_ctrl.set_key(true);
+        probe.write_word_32(BpCtrl::get_mmio_address(), bpt_ctrl.into())?;
+
+        Ok(())
+    }
+
+    fn debug_port_start(
+        &self,
+        interface: &mut dyn DapAccess,
+        dp: DpAddress,
+    ) -> Result<(), ArmError> {
+        // TODO:
+        // Copy-pasted from the default Trait implementation, but we need to add
+        // the cc23xx_cc27xx specific parts at the end
+        // This code is from `debug_port_start` in `probe-rs/src/architecture/arm/sequences.rs`
+
+        let mut abort = Abort(0);
+        abort.set_dapabort(true);
+        abort.set_orunerrclr(true);
+        abort.set_wderrclr(true);
+        abort.set_stkerrclr(true);
+        abort.set_stkcmpclr(true);
+        interface.write_dp_register(dp, abort)?;
+
+        interface.write_dp_register(dp, SelectV1(0))?;
+
+        let ctrl = interface.read_dp_register::<Ctrl>(dp)?;
+
+        let powered_down = !(ctrl.csyspwrupack() && ctrl.cdbgpwrupack());
+
+        if powered_down {
+            tracing::info!("Debug port {dp:x?} is powered down, powering up");
+            let mut ctrl = Ctrl(0);
+            ctrl.set_cdbgpwrupreq(true);
+            ctrl.set_csyspwrupreq(true);
+            interface.write_dp_register(dp, ctrl)?;
+
+            let start = Instant::now();
+            loop {
+                let ctrl = interface.read_dp_register::<Ctrl>(dp)?;
+                if ctrl.csyspwrupack() && ctrl.cdbgpwrupack() {
+                    break;
+                }
+                if start.elapsed() >= Duration::from_secs(1) {
+                    return Err(ArmError::Timeout);
+                }
+            }
+
+            // Init AP Transfer Mode, Transaction Counter, and Lane Mask (Normal Transfer Mode, Include all Byte Lanes)
+            let mut ctrl = Ctrl(0);
+            ctrl.set_cdbgpwrupreq(true);
+            ctrl.set_csyspwrupreq(true);
+            ctrl.set_mask_lane(0b1111);
+            interface.write_dp_register(dp, ctrl)?;
+
+            let ctrl_reg: Ctrl = interface.read_dp_register(dp)?;
+            if !(ctrl_reg.csyspwrupack() && ctrl_reg.cdbgpwrupack()) {
+                tracing::error!("Debug power request failed");
+                return Err(DebugPortError::TargetPowerUpFailed.into());
+            }
+
+            // According to CMSIS docs, here's where we would clear errors
+            // in ABORT, but we do that above instead.
+        }
+        // End of copy paste from `debug_port_start` in `probe-rs/src/architecture/arm/sequences.rs`
+
+        // This code is unique to the cc23xx_cc27xx family
+        // First connect to the config AP to read the device status register
+        // This will tell us the state of the boot rom and if SACI is enabled
+
+        // Read the device status register
+        let mut device_status = DeviceStatusRegister::read(interface)?;
+
+        // AHB-AP is not accessible when in SACI mode, so exit SACI
+        if !device_status.ahb_ap_available() {
+            // Send the SACI command to exit SACI
+            self.saci_command(interface, 0x07)?;
+
+            // Read the device status register again to check if boot is completed
+            device_status = DeviceStatusRegister::read(interface)?;
+
+            // Check if the boot rom is waiting for a debugger to attach
+            match device_status.boot_status() {
+                BOOT_STATUS_BOOT_WAITLOOP_DBGPROBE
+                | BOOT_STATUS_BLDR_WAITLOOP_DBGPROBE
+                | BOOT_STATUS_APP_WAITLOOP_DBGPROBE => {
+                    tracing::info!("BOOT_WAITLOOP_DBGPROBE");
+                    self.boot_loop.store(true, Ordering::SeqCst);
+                }
+                _ => tracing::warn!("Expected device to be waiting on debugger, but it is not"),
+            }
+        }
+
+        Ok(())
+    }
+
+    fn debug_core_start(
+        &self,
+        interface: &mut dyn ArmProbeInterface,
+        core_ap: &FullyQualifiedApAddress,
+        _core_type: CoreType,
+        _debug_base: Option<u64>,
+        _cti_base: Option<u64>,
+    ) -> Result<(), ArmError> {
+        if self.is_in_boot_loop() {
+            // Step 1: Halt the CPU
+            let mut dhcsr = Dhcsr(0);
+            dhcsr.set_c_halt(true);
+            dhcsr.set_c_debugen(true);
+            dhcsr.enable_write();
+
+            let mut memory = interface.memory_interface(core_ap)?;
+            memory.write_word_32(Dhcsr::get_mmio_address(), dhcsr.into())?;
+
+            // Step 1.1: Wait for the CPU to halt
+            dhcsr = Dhcsr(memory.read_word_32(Dhcsr::get_mmio_address())?);
+            while !dhcsr.s_halt() {
+                dhcsr = Dhcsr(memory.read_word_32(Dhcsr::get_mmio_address())?);
+            }
+
+            // Step 2: Write R3 to 0 to exit the boot loop
+            cortex_m::write_core_reg(memory.deref_mut(), crate::RegisterId(3), 0x00000000)?;
+
+            // Step 3: Clear the BOOT_LOOP flag
+            self.boot_loop.store(false, Ordering::SeqCst);
+        }
+
+        // Step 4: Start the core like normal
+        let mut core = interface.memory_interface(core_ap)?;
+        cortex_m_core_start(&mut *core)
+    }
+}
diff --git a/probe-rs/src/vendor/ti/sequences/mod.rs b/probe-rs/src/vendor/ti/sequences/mod.rs
index f25d12b6..8d1151f6 100644
--- a/probe-rs/src/vendor/ti/sequences/mod.rs
+++ b/probe-rs/src/vendor/ti/sequences/mod.rs
@@ -1,3 +1,4 @@
 //! Texas Instruments debug sequences.
 
 pub mod cc13xx_cc26xx;
+pub mod cc23xx_cc27xx;
diff --git a/probe-rs/targets/CC23XX_CC27XX_Series.yaml b/probe-rs/targets/CC23XX_CC27XX_Series.yaml
new file mode 100644
index 00000000..7bcb6723
--- /dev/null
+++ b/probe-rs/targets/CC23XX_CC27XX_Series.yaml
@@ -0,0 +1,67 @@
+name: CC23XX CC27XX Series
+manufacturer:
+  id: 0x17
+  cc: 0x0
+variants:
+- name: CC2340R5
+  cores:
+  - name: main
+    type: armv6m
+    core_access_options: !Arm
+      ap: !v1 0
+  memory_map:
+  - !Nvm
+    range:
+      start: 0x0
+      end: 0x80000
+    cores:
+    - main
+    access:
+      boot: true
+  - !Ram
+    range:
+      start: 0x20000000
+      end: 0x20009000
+    cores:
+    - main
+  - !Nvm
+    range:
+      start: 0x4e020000
+      end: 0x4e020800
+    cores:
+    - main
+    access:
+      boot: false
+      execute: false
+  flash_algorithms:
+  - cc23xx-flash
+
+flash_algorithms:
+# Flash algorithm taken from 2dec2a109925c3cad05cbdaf8ce10a76355adcd2 of https://github.com/seanmlyons22/cc23xx-flash
+- name: cc23xx-flash
+  description: A flash algorithm under test
+  default: true
+  instructions: 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IHZpb2xhdGVkOiBzbGljZTo6ZnJvbV9yYXdfcGFydHMgcmVxdWlyZXMgdGhlIHBvaW50ZXIgdG8gYmUgYWxpZ25lZCBhbmQgbm9uLW51bGwsIGFuZCB0aGUgdG90YWwgc2l6ZSBvZiB0aGUgc2xpY2Ugbm90IHRvIGV4Y2VlZCBgaXNpemU6Ok1BWGBzcmMvbWFpbi5yc1Rlcm1pbmFsAEluaXQKRXJhc2UgQWxsCkVyYXNlIHNlY3RvciBhZGRyOgrU1OcVACASAAAA+RUAIAEAAABQcm9nYW0gc2VjdG9yIGFkZHI61AwWACATAAAA+RUAIAEAAABUaGlzIGJyYW5jaCBjYW4gb25seSBiZSByZWFjaGVkIGlmIHRoZSBob3N0IGxpYnJhcnkgc2VudCBhbiB1bmtub3duIGZ1bmN0aW9uIGNvZGUu1NQwFgAgUgAAAMQVACALAAAADgAAAAEAAAB1bnNhZmUgcHJlY29uZGl0aW9uKHMpIHZpb2xhdGVkOiBwdHI6OmNvcHlfbm9ub3ZlcmxhcHBpbmcgcmVxdWlyZXMgdGhhdCBib3RoIHBvaW50ZXIgYXJndW1lbnRzIGFyZSBhbGlnbmVkIGFuZCBub24tbnVsbCBhbmQgdGhlIHNwZWNpZmllZCBtZW1vcnkgcmFuZ2VzIGRvIG5vdCBvdmVybGFw1NS9BAAgGAAAAAQAAABdBwAgFQIAIK0CACBTRUdHX9TU1EVSIFJUVAAAAAAAAC9ob21lL3NlYW5seW9ucy8uY2FyZ28vcmVnaXN0cnkvc3JjL2luZGV4LmNyYXRlcy5pby02ZjE3ZDIyYmJhMTUwMDFmL3J0dC10YXJnZXQtMC4zLjEvc3JjL3J0dC5yc3AXACBgAAAA5wAAAA0AAABwFwAgYAAAAOgAAAANAAAAcBcAIGAAAAD6AAAADQAAAHAXACBgAAAA/AAAAA0AAACtAwAgMDEyMzQ1Njc4OUFCQ0RFRnVuc2FmZSBwcmVjb25kaXRpb24ocykgdmlvbGF0ZWQ6IGhpbnQ6OnVucmVhY2hhYmxlX3VuY2hlY2tlZCBtdXN0IG5ldmVyIGJlIHJlYWNoZWRhdHRlbXB0IHRvIGFkZCB3aXRoIG92ZXJmbG931NR2GAAgHAAAAGF0dGVtcHQgdG8gc3VidHJhY3Qgd2l0aCBvdmVyZmxvd9TU1JwYACAhAAAAY29yZS9zcmMvcGFuaWNraW5nLnJz1NTUyBgAIBUAAADdAAAABQAAADAwMDEwMjAzMDQwNTA2MDcwODA5MTAxMTEyMTMxNDE1MTYxNzE4MTkyMDIxMjIyMzI0MjUyNjI3MjgyOTMwMzEzMjMzMzQzNTM2MzczODM5NDA0MTQyNDM0NDQ1NDY0NzQ4NDk1MDUxNTI1MzU0NTU1NjU3NTg1OTYwNjE2MjYzNjQ2NTY2Njc2ODY5NzA3MTcyNzM3NDc1NzY3Nzc4Nzk4MDgxODI4Mzg0ODU4Njg3ODg4OTkwOTE5MjkzOTQ5NTk2OTc5ODk5AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANTU1A==
+  load_address: 0x20000020
+  pc_init: 0x1
+  pc_uninit: 0xb9
+  pc_program_page: 0x149
+  pc_erase_sector: 0xed
+  pc_erase_all: 0x1c1
+  data_section_offset: 0x20001e00
+  rtt_location: 0x200019b8
+  flash_properties:
+    address_range:
+      start: 0x0
+      end: 0x4E020800
+    page_size: 0x800
+    erased_byte_value: 0xff
+    program_page_timeout: 1000
+    erase_sector_timeout: 2000
+    sectors:
+    - size: 0x800
+      address: 0x0
+    - size: 0x800
+      address: 0x4E020000
+  cores:
+  - main
diff --git a/target-gen/src/main.rs b/target-gen/src/main.rs
index b264b629..30d3b8a7 100644
--- a/target-gen/src/main.rs
+++ b/target-gen/src/main.rs
@@ -1,5 +1,6 @@
 use anyhow::{Context, Result, ensure};
 use clap::Parser;
+use probe_rs::probe::WireProtocol;
 use probe_rs_target::ChipFamily;
 use std::{
     env::current_dir,
@@ -104,6 +105,12 @@ enum TargetGen {
         /// Name of the flash algorithm to test
         #[clap(long = "name", short = 'n')]
         name: Option<String>,
+        /// Speed to run the probe at
+        #[clap(long = "speed", default_value = None)]
+        speed: Option<u32>,
+        /// Protocol to use for the probe
+        #[clap(long = "protocol", default_value = None)]
+        protocol: Option<WireProtocol>,
     },
     /// Loads and updates target description from YAML files.
     Reformat {
@@ -158,6 +165,8 @@ async fn main() -> Result<()> {
             test_start_sector_address,
             chip,
             name,
+            speed,
+            protocol,
         } => cmd_test(
             target_artifact.as_path(),
             template_path.as_path(),
@@ -165,6 +174,8 @@ async fn main() -> Result<()> {
             test_start_sector_address,
             chip,
             name,
+            speed,
+            protocol,
         )?,
         TargetGen::Reformat { yaml_path } => {
             if yaml_path.is_dir() {


Test patch:
diff --git a/.github/workflows/smoketest.yml b/.github/workflows/smoketest.yml
index c140d5c0..266f233d 100644
--- a/.github/workflows/smoketest.yml
+++ b/.github/workflows/smoketest.yml
@@ -30,12 +30,12 @@ jobs:
         run: rustup target add aarch64-unknown-linux-gnu
 
       - name: Install cross
-        uses: taiki-e/install-action@v2.49.27
+        uses: taiki-e/install-action@v2.49.34
         with:
           tool: cross
 
       - name: Cache Dependencies
-        uses: Swatinem/rust-cache@v2.7.7
+        uses: Swatinem/rust-cache@v2.7.8
 
       - name: Build smoke-tester
         run: cross build --release --target aarch64-unknown-linux-gnu -p smoke_tester
diff --git a/smoke-tester/src/dut_definition.rs b/smoke-tester/src/dut_definition.rs
index 950592e0..384347e9 100644
--- a/smoke-tester/src/dut_definition.rs
+++ b/smoke-tester/src/dut_definition.rs
@@ -7,6 +7,7 @@ use miette::IntoDiagnostic;
 use miette::Result;
 use miette::WrapErr;
 use probe_rs::config::Registry;
+use probe_rs::probe::WireProtocol;
 use probe_rs::{
     Target,
     probe::{DebugProbeSelector, Probe, list::Lister},
@@ -24,6 +25,7 @@ struct RawDutDefinition {
     /// See [probe_rs::probe::DebugProbeSelector].
     probe_selector: String,
     probe_speed: Option<u32>,
+    protocol: Option<WireProtocol>,
 
     flash_test_binary: Option<String>,
 
@@ -64,6 +66,11 @@ pub struct DutDefinition {
     /// If not set, the default speed of the probe will be used.
     pub probe_speed: Option<u32>,
 
+    /// Protocol
+    ///
+    /// If not set, the default wire protocol will be used.
+    pub protocol: Option<WireProtocol>,
+
     /// Path to a binary which can be used to test
     /// flashing for the DUT.
     pub flash_test_binary: Option<PathBuf>,
@@ -86,6 +93,7 @@ impl DutDefinition {
             chip: target,
             probe_selector: Some(selector),
             probe_speed: None,
+            protocol: None,
             flash_test_binary: None,
             source: DefinitionSource::Cli,
             reset_connected: false,
@@ -99,6 +107,7 @@ impl DutDefinition {
             chip: target,
             probe_selector: None,
             probe_speed: None,
+            protocol: None,
             flash_test_binary: None,
             source: DefinitionSource::Cli,
             reset_connected: false,
@@ -179,6 +188,10 @@ impl DutDefinition {
             probe.set_speed(probe_speed).into_diagnostic()?;
         }
 
+        if let Some(protocol) = self.protocol {
+            probe.select_protocol(protocol).into_diagnostic()?;
+        }
+
         Ok(probe)
     }
 
@@ -204,6 +217,7 @@ impl DutDefinition {
         Ok(Self {
             chip: target,
             probe_speed: raw_definition.probe_speed,
+            protocol: raw_definition.protocol,
             probe_selector,
             flash_test_binary,
             source: DefinitionSource::File(source_file.to_owned()),
diff --git a/smoke-tester/src/main.rs b/smoke-tester/src/main.rs
index a06bac28..5fb78896 100644
--- a/smoke-tester/src/main.rs
+++ b/smoke-tester/src/main.rs
@@ -11,7 +11,7 @@ use clap::{Parser, Subcommand};
 use colored::Colorize;
 use linkme::distributed_slice;
 use miette::{Context, IntoDiagnostic, Result};
-use probe_rs::Permissions;
+use probe_rs::{Permissions, probe::WireProtocol};
 
 mod dut_definition;
 mod macros;
@@ -93,6 +93,9 @@ struct Opt {
     #[arg(long, global = true, value_name = "PROBE_SPEED")]
     probe_speed: Option<u32>,
 
+    #[arg(long, global = true, value_name = "PROTOCOL")]
+    protocol: Option<WireProtocol>,
+
     #[arg(long, global = true, value_name = "FILE", conflicts_with_all = ["chip", "dut_definitions"])]
     single_dut: Option<PathBuf>,
 }
@@ -123,6 +126,10 @@ fn main() -> Result<ExitCode> {
         if let Some(probe_speed) = opt.probe_speed {
             definition.probe_speed = Some(probe_speed);
         }
+
+        if let Some(protcol) = opt.protocol {
+            definition.protocol = Some(protcol);
+        }
     }
 
     match opt.command {
diff --git a/target-gen/src/commands/test.rs b/target-gen/src/commands/test.rs
index f5169c88..0710bba4 100644
--- a/target-gen/src/commands/test.rs
+++ b/target-gen/src/commands/test.rs
@@ -6,18 +6,20 @@ use std::time::Instant;
 use anyhow::{Context, Result, anyhow};
 use colored::Colorize;
 use probe_rs::{
-    MemoryInterface, Permissions, Session,
+    MemoryInterface, Permissions, Session, SessionConfig,
     config::Registry,
     flashing::{
         DownloadOptions, FlashLoader, FlashProgress, ProgressEvent, ProgressOperation, erase_all,
         erase_sectors,
     },
+    probe::WireProtocol,
 };
 use probe_rs_target::RawFlashAlgorithm;
 use xshell::{Shell, cmd};
 
 use crate::commands::elf::cmd_elf;
 
+#[allow(clippy::too_many_arguments)]
 pub fn cmd_test(
     target_artifact: &Path,
     template_path: &Path,
@@ -25,6 +27,8 @@ pub fn cmd_test(
     test_start_sector_address: Option<u64>,
     chip: Option<String>,
     name: Option<String>,
+    speed: Option<u32>,
+    protocol: Option<WireProtocol>,
 ) -> Result<()> {
     ensure_is_file(target_artifact)?;
     ensure_is_file(template_path)?;
@@ -85,9 +89,16 @@ pub fn cmd_test(
         }
     };
 
+    // Create SessionConfig to steer auto attach
+    let permissions = Permissions::new().allow_erase_all();
+    let session_config = SessionConfig {
+        permissions,
+        speed,
+        protocol,
+    };
+
     // We need to get the chip name so that special startup procedure can be used. (matched on name)
-    let mut session =
-        probe_rs::Session::auto_attach(target_name, Permissions::new().allow_erase_all())?;
+    let mut session = probe_rs::Session::auto_attach(target_name, session_config)?;
 
     // Register callback to update the progress.
     let t = Rc::new(RefCell::new(Instant::now()));