Repository: probe-rs/probe-rs
Pull request #3111 (2025-02-20 06:27:26)

Added support for Texas Instrument's CC2340R5 device
Added support for CC2340R5 and begin to pave the way for supporting TI's LPF3 family (cc23xx_cc27xx) of wireless microcontrollers.

Add protocol selection to smoke-tester and target-gen




Patch:
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-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/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/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()));