|
| 1 | +--- |
| 2 | +id: swd-base |
| 3 | +title: SWD基类 |
| 4 | +sidebar_position: 1 |
| 5 | +--- |
| 6 | + |
| 7 | +# SWD 基类(`LibXR::Debug::Swd`) |
| 8 | + |
| 9 | +本文档描述 `LibXR::Debug::Swd`:一个面向 **SWD 探针/链路后端** 的抽象基类。它提供最小的链路控制接口、一次传输原语(`Transfer()`),以及在其之上构建的 **WAIT 重试封装** 与 **DP/AP 辅助函数**,用于被更上层(例如 CMSIS-DAP、调试器、烧录器)复用。 |
| 10 | + |
| 11 | +--- |
| 12 | + |
| 13 | +## 1. 设计定位 |
| 14 | + |
| 15 | +`Swd` 的定位是“**可移植的 SWD 事务层抽象**”: |
| 16 | + |
| 17 | +- 由派生类实现硬件相关部分:GPIO/USART/SPI/bitbang、时序、端点驱动、并发策略等。 |
| 18 | +- 基类负责通用逻辑: |
| 19 | + - 传输策略(WAIT 重试 + IdleCycles 插入) |
| 20 | + - DP/AP 常用读写封装(含 posted read 的 RDBUFF 处理) |
| 21 | + - SELECT 写缓存(减少冗余写操作) |
| 22 | + |
| 23 | +--- |
| 24 | + |
| 25 | +## 2. 数据结构 |
| 26 | + |
| 27 | +### 2.1 `TransferPolicy` |
| 28 | + |
| 29 | +传输策略用于控制重试与空闲周期插入: |
| 30 | + |
| 31 | +- `idle_cycles`:每次传输尝试后插入的空闲周期数(包含 WAIT 重试)。 |
| 32 | +- `wait_retry`:ACK==WAIT 时的最大重试次数。 |
| 33 | +- `clear_sticky_on_fault`:ACK==FAULT 时是否尽力清除 DP sticky 错误(通过写 DP ABORT)。 |
| 34 | + |
| 35 | +相关接口: |
| 36 | + |
| 37 | +- `SetTransferPolicy(const TransferPolicy&)` |
| 38 | +- `GetTransferPolicy()` |
| 39 | + |
| 40 | +--- |
| 41 | + |
| 42 | +## 3. 派生类必须实现的接口 |
| 43 | + |
| 44 | +以下接口为纯虚函数,必须由具体 SWD 后端实现: |
| 45 | + |
| 46 | +1) 链路控制 |
| 47 | + |
| 48 | +- `ErrorCode SetClockHz(uint32_t hz)` |
| 49 | + 设置 SWCLK 目标频率(是否真正可调由后端决定)。 |
| 50 | + |
| 51 | +- `void Close()` |
| 52 | + 关闭探针并释放资源(可用于断开硬件、停止定时器等)。 |
| 53 | + |
| 54 | +- `ErrorCode LineReset()` |
| 55 | + 执行 SWD line reset(常见为连续输出若干个 1,再进行必要的序列)。 |
| 56 | + |
| 57 | +- `ErrorCode EnterSwd()` |
| 58 | + 进入 SWD 模式(例如从 JTAG 切换到 SWD 的序列)。 |
| 59 | + |
| 60 | +2) 传输原语 |
| 61 | + |
| 62 | +- `ErrorCode Transfer(const SwdProtocol::Request& req, SwdProtocol::Response& resp)` |
| 63 | + 执行“一次 SWD 传输(不含重试)”。该函数应完成: |
| 64 | + - 请求发送、ACK 读取、(可选)读数据与校验 |
| 65 | + - 填充 `resp.ack / resp.rdata / resp.parity_ok` |
| 66 | + |
| 67 | +3) 序列与空闲周期 |
| 68 | + |
| 69 | +- `void IdleClocks(uint32_t cycles)` |
| 70 | + 插入空闲时钟周期(通常表现为产生 SWCLK 脉冲,同时保持 SWDIO 空闲态)。 |
| 71 | + |
| 72 | +- `ErrorCode SeqWriteBits(uint32_t cycles, const uint8_t* data_lsb_first)` |
| 73 | + 在 SWDIO 上按 LSB-first 输出 `cycles` 位并产生 SWCLK 脉冲。 |
| 74 | + |
| 75 | +- `ErrorCode SeqReadBits(uint32_t cycles, uint8_t* out_lsb_first)` |
| 76 | + 产生 SWCLK 脉冲并采样 SWDIO,按 LSB-first 写入输出缓冲。 |
| 77 | + |
| 78 | +--- |
| 79 | + |
| 80 | +## 4. 带重试传输:`TransferWithRetry()` |
| 81 | + |
| 82 | +`TransferWithRetry()` 在 `Transfer()` 之上增加策略控制,用于贴近 CMSIS-DAP 的典型行为。 |
| 83 | + |
| 84 | +核心规则: |
| 85 | + |
| 86 | +- 每次尝试后都插入 `idle_cycles`(包括 WAIT 重试)。 |
| 87 | +- 当 `resp.ack == WAIT` 时,最多重试 `wait_retry` 次。 |
| 88 | +- 若底层 `Transfer()` 返回非 `OK`(总线/实现级错误): |
| 89 | + - 将 `resp.ack` 置为 `PROTOCOL` |
| 90 | + - 失效 SELECT 缓存(避免后续基于缓存做错误判断) |
| 91 | + - 立即返回该错误码 |
| 92 | +- 若最终 ACK 非 OK:失效 SELECT 缓存 |
| 93 | +- 若最终 ACK 为 FAULT 且 `clear_sticky_on_fault=true`:尽力写 ABORT 清 sticky |
| 94 | + |
| 95 | +返回值语义: |
| 96 | + |
| 97 | +- 返回 `ErrorCode` 表示“传输流程级”的结果:通常为 `OK`,除非底层 `Transfer()` 失败。 |
| 98 | +- ACK 状态由 `resp.ack` 给出;上层应同时检查 `resp.ack` 与 `resp.parity_ok`。 |
| 99 | + |
| 100 | +--- |
| 101 | + |
| 102 | +## 5. DP/AP 辅助接口 |
| 103 | + |
| 104 | +基类提供常用 DP/AP 事务封装,减少上层重复代码。 |
| 105 | + |
| 106 | +### 5.1 DP 读写 |
| 107 | + |
| 108 | +- `DpRead(...) / DpWrite(...)`:无重试版本(直接调用 `Transfer()`) |
| 109 | +- `DpReadTxn(...) / DpWriteTxn(...)`:带重试版本(调用 `TransferWithRetry()`) |
| 110 | + |
| 111 | +注意事项: |
| 112 | + |
| 113 | +- 读操作在 `resp.ack==OK` 且 `resp.parity_ok==true` 时才返回成功,并输出 `val`。 |
| 114 | +- 任何 ACK 非 OK 或奇偶校验失败都会返回 `FAILED`(并通过 `ack` 返回 ACK)。 |
| 115 | + |
| 116 | +### 5.2 AP 读写与 posted read |
| 117 | + |
| 118 | +AP 读为 posted read,因此提供两种读法: |
| 119 | + |
| 120 | +- `ApReadTxn(addr2b, val, ack)` |
| 121 | + 执行一次 AP READ(posted),随后读 DP `RDBUFF`,将“本次 AP READ 的真实数据”输出到 `val`。 |
| 122 | + |
| 123 | +- `ApReadPostedTxn(addr2b, posted_val, ack)` |
| 124 | + 只执行 AP READ 并返回 `resp.rdata`(这是“上一笔” AP READ 的 posted 结果)。调用者若要拿到最后一次 AP READ 的真实数据,需要额外调用一次 `DpReadRdbuffTxn()`。 |
| 125 | + |
| 126 | +写操作: |
| 127 | + |
| 128 | +- `ApWriteTxn(addr2b, val, ack)`:带重试 AP WRITE。 |
| 129 | + |
| 130 | +### 5.3 其他常用封装 |
| 131 | + |
| 132 | +- `ReadIdCode(idcode, ack)`:读取 DP `IDCODE`(无重试)。 |
| 133 | +- `WriteAbort(flags, ack)` / `WriteAbortTxn(flags, ack)`:写 DP `ABORT`(无重试/带重试)。 |
| 134 | +- `DpReadRdbuffTxn(val, ack)`:读 DP `RDBUFF`(带重试)。 |
| 135 | + |
| 136 | +--- |
| 137 | + |
| 138 | +## 6. SELECT 写缓存 |
| 139 | + |
| 140 | +为减少冗余 SELECT 写入,基类维护一个简单缓存: |
| 141 | + |
| 142 | +- `SetSelectCached(select, ack)` |
| 143 | + - 若缓存命中(`select_valid_ && select_cache_ == select`),直接返回 `OK` 且 `ack=OK` |
| 144 | + - 否则执行 `DpWriteTxn(SELECT, select, ack)`;写成功后更新缓存 |
| 145 | + |
| 146 | +- `InvalidateSelectCache()` |
| 147 | + 将缓存标记为无效。以下情况会触发失效: |
| 148 | + - `TransferWithRetry()` 中遇到 ACK 非 OK |
| 149 | + - `TransferWithRetry()` 中底层 Transfer 返回非 OK |
| 150 | + - 上层也可以在链路重置、模式切换等场景主动调用 |
| 151 | + |
| 152 | +--- |
| 153 | + |
| 154 | +## 7. 实现建议(派生类) |
| 155 | + |
| 156 | +1) `Transfer()` 的职责边界 |
| 157 | +`Transfer()` 应只做“一次事务”,不要内置 WAIT 重试与 idle 插入(基类已提供策略封装)。如果必须在硬件层做最小化重试(例如采样时序问题),建议明确与 `wait_retry` 的关系,避免双重重试导致延迟放大。 |
| 158 | + |
| 159 | +2) 响应填充 |
| 160 | +即使发生异常,也应尽量填充 `resp.ack` 与 `resp.parity_ok`,并在不可恢复错误时让 `Transfer()` 返回非 `OK`,由 `TransferWithRetry()` 统一处理缓存失效等副作用。 |
| 161 | + |
| 162 | +3) 线路空闲与方向切换 |
| 163 | +`SeqWriteBits/SeqReadBits` 的方向控制与 turnaround 处理应符合 SWD 协议要求;特别是 ACK 阶段与数据阶段的 IO 方向切换。 |
| 164 | + |
| 165 | +--- |
| 166 | + |
| 167 | +## 8. 使用示例(概念性) |
| 168 | + |
| 169 | +```cpp |
| 170 | +class MySwd : public LibXR::Debug::Swd { |
| 171 | + public: |
| 172 | + ErrorCode SetClockHz(uint32_t hz) override; |
| 173 | + void Close() override; |
| 174 | + ErrorCode LineReset() override; |
| 175 | + ErrorCode EnterSwd() override; |
| 176 | + ErrorCode Transfer(const SwdProtocol::Request& req, |
| 177 | + SwdProtocol::Response& resp) override; |
| 178 | + |
| 179 | + void IdleClocks(uint32_t cycles) override; |
| 180 | + ErrorCode SeqWriteBits(uint32_t cycles, const uint8_t* data_lsb_first) override; |
| 181 | + ErrorCode SeqReadBits(uint32_t cycles, uint8_t* out_lsb_first) override; |
| 182 | +}; |
| 183 | + |
| 184 | +MySwd swd; |
| 185 | +LibXR::Debug::Swd::TransferPolicy pol; |
| 186 | +pol.idle_cycles = 0; |
| 187 | +pol.wait_retry = 100; |
| 188 | +swd.SetTransferPolicy(pol); |
| 189 | + |
| 190 | +swd.EnterSwd(); |
| 191 | + |
| 192 | +uint32_t idcode = 0; |
| 193 | +LibXR::Debug::SwdProtocol::Ack ack; |
| 194 | +swd.ReadIdCode(idcode, ack); |
| 195 | +``` |
0 commit comments