SWD Base Class (LibXR::Debug::Swd)
This document describes LibXR::Debug::Swd: an abstract base class for SWD probes / link backends. It provides the minimal link control interface, a single-transfer primitive (Transfer()), and higher-level utilities built on top of it, including WAIT retry wrapping and DP/AP helper functions, intended for reuse by higher layers (e.g., CMSIS-DAP, debuggers, flash programmers).
1. Design Role
Swd is positioned as a portable SWD transaction-layer abstraction:
- Hardware-specific parts are implemented by derived classes: GPIO/USART/SPI/bit-bang backends, timing, endpoint driving, concurrency strategy, etc.
- The base class provides common logic:
- Transfer policy (WAIT retries + IdleCycles insertion)
- Common DP/AP read/write wrappers (including RDBUFF handling for posted reads)
- SELECT write caching (to reduce redundant writes)
2. Data Structures
2.1 TransferPolicy
The transfer policy controls retries and idle-cycle insertion:
idle_cycles: Number of idle cycles inserted after each transfer attempt (including WAIT retries).wait_retry: Maximum retry count when ACK == WAIT.clear_sticky_on_fault: Whether to attempt to clear DP sticky errors on ACK == FAULT (by writing DP ABORT).
Related APIs:
SetTransferPolicy(const TransferPolicy&)GetTransferPolicy()
3. Interfaces Required by Derived Classes
The following are pure virtual functions and must be implemented by a concrete SWD backend:
- Link control
-
ErrorCode SetClockHz(uint32_t hz)
Set the target SWCLK frequency (whether it is truly adjustable depends on the backend). -
void Close()
Close the probe and release resources (e.g., detach hardware, stop timers, etc.). -
ErrorCode LineReset()
Perform an SWD line reset (commonly: drive a number of 1s, then any required sequences). -
ErrorCode EnterSwd()
Enter SWD mode (e.g., the sequence to switch from JTAG to SWD).
- Transfer primitive
ErrorCode Transfer(const SwdProtocol::Request& req, SwdProtocol::Response& resp)
Perform one SWD transfer attempt (no retries). This function should handle:- Sending the request, reading ACK, (optionally) reading data and checking parity
- Filling
resp.ack / resp.rdata / resp.parity_ok
- Sequences and idle cycles
-
void IdleClocks(uint32_t cycles)
Insert idle clock cycles (typically generate SWCLK pulses while holding SWDIO in idle state). -
ErrorCode SeqWriteBits(uint32_t cycles, const uint8_t* data_lsb_first)
Outputcyclesbits on SWDIO in LSB-first order and generate SWCLK pulses. -
ErrorCode SeqReadBits(uint32_t cycles, uint8_t* out_lsb_first)
Generate SWCLK pulses and sample SWDIO, writing the result to the output buffer in LSB-first order.
4. Retry-Capable Transfer: TransferWithRetry()
TransferWithRetry() adds policy control on top of Transfer() to match typical CMSIS-DAP behavior.
Core rules:
- Insert
idle_cyclesafter every attempt (including WAIT retries). - When
resp.ack == WAIT, retry up towait_retrytimes. - If the underlying
Transfer()returns non-OK (bus/implementation-level error):- Set
resp.acktoPROTOCOL - Invalidate the SELECT cache (avoid incorrect decisions based on cached state)
- Return that error code immediately
- Set
- If the final ACK is not OK: invalidate the SELECT cache
- If the final ACK is FAULT and
clear_sticky_on_fault == true: attempt to write ABORT to clear sticky errors
Return semantics:
- The returned
ErrorCodeindicates the transfer-flow result: usuallyOKunless the underlyingTransfer()fails. - ACK status is reported via
resp.ack; upper layers should check bothresp.ackandresp.parity_ok.
5. DP/AP Helper APIs
The base class provides common DP/AP transaction wrappers to reduce duplication in upper layers.
5.1 DP Read/Write
DpRead(...) / DpWrite(...): no-retry variants (callTransfer()directly)DpReadTxn(...) / DpWriteTxn(...): retry-enabled variants (callTransferWithRetry())
Notes:
- A read is considered successful only when
resp.ack == OKandresp.parity_ok == true, and thenvalis produced. - Any non-OK ACK or parity failure returns
FAILED(and returns the ACK viaack).
5.2 AP Read/Write and Posted Reads
AP reads are posted reads, so two patterns are provided:
-
ApReadTxn(addr2b, val, ack)
Perform one AP READ (posted), then read DPRDBUFFand output the actual data for this AP READ intoval. -
ApReadPostedTxn(addr2b, posted_val, ack)
Perform only the AP READ and returnresp.rdata(this is the posted result of the previous AP READ). To obtain the final AP READ’s actual data, the caller must perform an additionalDpReadRdbuffTxn().
Writes:
ApWriteTxn(addr2b, val, ack): retry-enabled AP WRITE.
5.3 Other Common Wrappers
ReadIdCode(idcode, ack): read DPIDCODE(no retry).WriteAbort(flags, ack)/WriteAbortTxn(flags, ack): write DPABORT(no-retry / retry).DpReadRdbuffTxn(val, ack): read DPRDBUFF(retry).
6. SELECT Write Cache
To reduce redundant SELECT writes, the base class maintains a small cache:
-
SetSelectCached(select, ack)- If cache hits (
select_valid_ && select_cache_ == select), returnOKandack = OK - Otherwise call
DpWriteTxn(SELECT, select, ack); on success update the cache
- If cache hits (
-
InvalidateSelectCache()
Marks the cache invalid. Typical invalidation cases:- In
TransferWithRetry()when ACK is not OK - In
TransferWithRetry()when the underlying Transfer returns non-OK - Upper layers may also call it proactively during link reset, mode switching, etc.
- In
7. Implementation Guidance (Derived Classes)
-
Scope of responsibility for
Transfer()
Transfer()should implement only one transaction attempt and should not embed WAIT retries or idle insertion (the base class already provides policy wrapping). If minimal hardware-layer retries are unavoidable (e.g., sampling/timing edge cases), clearly define how it relates towait_retryto avoid double-retry amplification. -
Response filling
Even on errors, try to fillresp.ackandresp.parity_okas much as possible. For unrecoverable errors, haveTransfer()return non-OK so thatTransferWithRetry()can uniformly handle side effects like cache invalidation. -
Line idle state and direction switching
Direction control and turnaround handling inSeqWriteBits/SeqReadBitsshould follow SWD protocol requirements, especially I/O direction changes across ACK and data phases.
8. Example Usage (Conceptual)
class MySwd : public LibXR::Debug::Swd {
public:
ErrorCode SetClockHz(uint32_t hz) override;
void Close() override;
ErrorCode LineReset() override;
ErrorCode EnterSwd() override;
ErrorCode Transfer(const SwdProtocol::Request& req,
SwdProtocol::Response& resp) override;
void IdleClocks(uint32_t cycles) override;
ErrorCode SeqWriteBits(uint32_t cycles, const uint8_t* data_lsb_first) override;
ErrorCode SeqReadBits(uint32_t cycles, uint8_t* out_lsb_first) override;
};
MySwd swd;
LibXR::Debug::Swd::TransferPolicy pol;
pol.idle_cycles = 0;
pol.wait_retry = 100;
swd.SetTransferPolicy(pol);
swd.EnterSwd();
uint32_t idcode = 0;
LibXR::Debug::SwdProtocol::Ack ack;
swd.ReadIdCode(idcode, ack);