Add SWD Operations guide and update reference pages for v2026.02.15
- New guide: SWD Operations (DAP discovery, DP/AP register access, AP enumeration) - Reference: add DAPInfo, APInfo types and SWDError exception - Method index: add SWDController section - Homepage: 9 → 10 subsystems - Sidebar: add SWD Operations entry
This commit is contained in:
parent
fe49fff171
commit
17e8070c50
@ -44,6 +44,7 @@ export default defineConfig({
|
|||||||
{ label: 'Flash Programming', slug: 'guides/flash-programming' },
|
{ label: 'Flash Programming', slug: 'guides/flash-programming' },
|
||||||
{ label: 'Breakpoints & Watchpoints', slug: 'guides/breakpoints' },
|
{ label: 'Breakpoints & Watchpoints', slug: 'guides/breakpoints' },
|
||||||
{ label: 'JTAG Operations', slug: 'guides/jtag-operations' },
|
{ label: 'JTAG Operations', slug: 'guides/jtag-operations' },
|
||||||
|
{ label: 'SWD Operations', slug: 'guides/swd-operations' },
|
||||||
{ label: 'SVD Register Decoding', slug: 'guides/svd-decoding' },
|
{ label: 'SVD Register Decoding', slug: 'guides/svd-decoding' },
|
||||||
{ label: 'RTT Communication', slug: 'guides/rtt-communication' },
|
{ label: 'RTT Communication', slug: 'guides/rtt-communication' },
|
||||||
{ label: 'Transport & Adapter', slug: 'guides/transport-adapter' },
|
{ label: 'Transport & Adapter', slug: 'guides/transport-adapter' },
|
||||||
|
|||||||
262
src/content/docs/guides/swd-operations.mdx
Normal file
262
src/content/docs/guides/swd-operations.mdx
Normal file
@ -0,0 +1,262 @@
|
|||||||
|
---
|
||||||
|
title: SWD Operations
|
||||||
|
description: DAP discovery, DP/AP register access, Access Port enumeration, and SWD-specific debugging workflows.
|
||||||
|
---
|
||||||
|
|
||||||
|
import { Tabs, TabItem, Aside } from '@astrojs/starlight/components';
|
||||||
|
|
||||||
|
The `SWDController` provides access to SWD (Serial Wire Debug) operations through the DAP (Debug Access Port) interface: DAP discovery, DP register reads/writes, AP register reads/writes, and AP enumeration. Access it through the session:
|
||||||
|
|
||||||
|
```python
|
||||||
|
# Async
|
||||||
|
session.swd
|
||||||
|
|
||||||
|
# Sync
|
||||||
|
sync_session.swd
|
||||||
|
```
|
||||||
|
|
||||||
|
<Aside type="note">
|
||||||
|
Most users interact with OpenOCD at the target level (halt, resume, memory read/write) and never need raw DAP register commands. This subsystem is for situations where you need direct DP/AP access -- reading device IDs, probing unlisted APs, low-level debug port configuration, or working with custom debug architectures.
|
||||||
|
</Aside>
|
||||||
|
|
||||||
|
## SWD vs JTAG
|
||||||
|
|
||||||
|
SWD and JTAG are the two debug transport protocols supported by OpenOCD. The key architectural difference:
|
||||||
|
|
||||||
|
| | JTAG | SWD |
|
||||||
|
|---|---|---|
|
||||||
|
| **Model** | Scan chain: TAPs, IR/DR shifts | DAP-centric: DP + AP registers |
|
||||||
|
| **Topology** | Daisy-chained TAPs | Point-to-point (one DAP) |
|
||||||
|
| **Pins** | TDI, TDO, TCK, TMS (+ TRST) | SWDIO, SWCLK (2 pins) |
|
||||||
|
| **Subsystem** | `session.jtag` | `session.swd` |
|
||||||
|
| **OpenOCD model** | Global commands (`scan_chain`, `irscan`) | Named DAP objects (`<dap> dpreg`, `<dap> apreg`) |
|
||||||
|
|
||||||
|
Both transports share the higher-level subsystems (target, memory, registers, flash, etc.) -- those work identically regardless of transport. The transport-specific subsystems expose the wire-level protocol details.
|
||||||
|
|
||||||
|
## DAP architecture
|
||||||
|
|
||||||
|
An ARM CoreSight debug system has a **Debug Port** (DP) connected to one or more **Access Ports** (APs):
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────┐
|
||||||
|
│ DP │ ← DPIDR, TARGETID, CTRL/STAT
|
||||||
|
│ (SWDIO) │
|
||||||
|
├─────────────┤
|
||||||
|
│ AP #0 │ ← MEM-AP (AHB/APB/AXI bus access)
|
||||||
|
│ AP #1 │ ← JTAG-AP, additional MEM-AP, etc.
|
||||||
|
│ ... │
|
||||||
|
└─────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
- **DP registers** (address 0x0 - 0x24): Debug port identification and control
|
||||||
|
- **AP registers** (per-AP, address 0x00 - 0xFC): Access port configuration and data transfer
|
||||||
|
|
||||||
|
## DAP discovery
|
||||||
|
|
||||||
|
`info()` queries the DAP for identification and topology. Returns a `DAPInfo` with the DPIDR value and discovered AP count.
|
||||||
|
|
||||||
|
<Tabs>
|
||||||
|
<TabItem label="Async">
|
||||||
|
```python
|
||||||
|
import asyncio
|
||||||
|
from openocd import Session
|
||||||
|
|
||||||
|
async def main():
|
||||||
|
async with await Session.connect() as session:
|
||||||
|
info = await session.swd.info()
|
||||||
|
print(f"DAP: {info.name}")
|
||||||
|
print(f" DPIDR: 0x{info.dpidr:08X}")
|
||||||
|
print(f" Access Ports: {info.ap_count}")
|
||||||
|
|
||||||
|
asyncio.run(main())
|
||||||
|
```
|
||||||
|
</TabItem>
|
||||||
|
<TabItem label="Sync">
|
||||||
|
```python
|
||||||
|
from openocd import Session
|
||||||
|
|
||||||
|
with Session.connect_sync() as session:
|
||||||
|
info = session.swd.info()
|
||||||
|
print(f"DAP: {info.name}")
|
||||||
|
print(f" DPIDR: 0x{info.dpidr:08X}")
|
||||||
|
print(f" Access Ports: {info.ap_count}")
|
||||||
|
```
|
||||||
|
</TabItem>
|
||||||
|
</Tabs>
|
||||||
|
|
||||||
|
## DP register access
|
||||||
|
|
||||||
|
`dpreg(address, value=None)` reads or writes Debug Port registers. When `value` is `None`, it reads. When provided, it writes.
|
||||||
|
|
||||||
|
<Tabs>
|
||||||
|
<TabItem label="Async">
|
||||||
|
```python
|
||||||
|
async with await Session.connect() as session:
|
||||||
|
# Read DPIDR (DP address 0x0)
|
||||||
|
dpidr = await session.swd.dpreg(0x0)
|
||||||
|
print(f"DPIDR: 0x{dpidr:08X}")
|
||||||
|
|
||||||
|
# Read CTRL/STAT (DP address 0x4)
|
||||||
|
ctrl_stat = await session.swd.dpreg(0x4)
|
||||||
|
print(f"CTRL/STAT: 0x{ctrl_stat:08X}")
|
||||||
|
```
|
||||||
|
</TabItem>
|
||||||
|
<TabItem label="Sync">
|
||||||
|
```python
|
||||||
|
with Session.connect_sync() as session:
|
||||||
|
dpidr = session.swd.dpreg(0x0)
|
||||||
|
print(f"DPIDR: 0x{dpidr:08X}")
|
||||||
|
```
|
||||||
|
</TabItem>
|
||||||
|
</Tabs>
|
||||||
|
|
||||||
|
### Convenience methods
|
||||||
|
|
||||||
|
Two commonly-needed DP registers have dedicated methods:
|
||||||
|
|
||||||
|
```python
|
||||||
|
# DP IDR (address 0x0) — identifies the debug port
|
||||||
|
dpidr = await session.swd.dpidr()
|
||||||
|
print(f"DPIDR: 0x{dpidr:08X}")
|
||||||
|
|
||||||
|
# TARGETID (address 0x24, DPv2+) — identifies the target device
|
||||||
|
target_id = await session.swd.target_id()
|
||||||
|
print(f"TARGETID: 0x{target_id:08X}")
|
||||||
|
```
|
||||||
|
|
||||||
|
<Aside type="tip">
|
||||||
|
`dpidr()` is equivalent to `dpreg(0x0)` and `target_id()` is equivalent to `dpreg(0x24)`. The convenience methods exist because these are the two most-queried DP registers in practice.
|
||||||
|
</Aside>
|
||||||
|
|
||||||
|
## AP register access
|
||||||
|
|
||||||
|
`apreg(ap, address, value=None)` reads or writes Access Port registers. The `ap` parameter is the AP index (0, 1, 2...).
|
||||||
|
|
||||||
|
<Tabs>
|
||||||
|
<TabItem label="Async">
|
||||||
|
```python
|
||||||
|
async with await Session.connect() as session:
|
||||||
|
# Read AP #0 IDR (identifies the AP type)
|
||||||
|
ap_idr = await session.swd.apreg(0, 0xFC)
|
||||||
|
print(f"AP #0 IDR: 0x{ap_idr:08X}")
|
||||||
|
|
||||||
|
# Read AP #0 BASE (ROM table address)
|
||||||
|
ap_base = await session.swd.apreg(0, 0xF8)
|
||||||
|
print(f"AP #0 BASE: 0x{ap_base:08X}")
|
||||||
|
|
||||||
|
# Write AP CSW register (configure transfer parameters)
|
||||||
|
await session.swd.apreg(0, 0x00, value=0x23000052)
|
||||||
|
```
|
||||||
|
</TabItem>
|
||||||
|
<TabItem label="Sync">
|
||||||
|
```python
|
||||||
|
with Session.connect_sync() as session:
|
||||||
|
ap_idr = session.swd.apreg(0, 0xFC)
|
||||||
|
print(f"AP #0 IDR: 0x{ap_idr:08X}")
|
||||||
|
```
|
||||||
|
</TabItem>
|
||||||
|
</Tabs>
|
||||||
|
|
||||||
|
## AP enumeration
|
||||||
|
|
||||||
|
`list_aps()` probes the DAP to discover all Access Ports by reading their IDR registers. Returns a list of `APInfo` dataclasses.
|
||||||
|
|
||||||
|
<Tabs>
|
||||||
|
<TabItem label="Async">
|
||||||
|
```python
|
||||||
|
async with await Session.connect() as session:
|
||||||
|
aps = await session.swd.list_aps()
|
||||||
|
for ap in aps:
|
||||||
|
print(f"AP #{ap.index}: {ap.ap_type}")
|
||||||
|
print(f" IDR: 0x{ap.idr:08X}")
|
||||||
|
print(f" BASE: 0x{ap.base:08X}")
|
||||||
|
```
|
||||||
|
</TabItem>
|
||||||
|
<TabItem label="Sync">
|
||||||
|
```python
|
||||||
|
with Session.connect_sync() as session:
|
||||||
|
aps = session.swd.list_aps()
|
||||||
|
for ap in aps:
|
||||||
|
print(f"AP #{ap.index}: {ap.ap_type}")
|
||||||
|
print(f" IDR: 0x{ap.idr:08X}")
|
||||||
|
print(f" BASE: 0x{ap.base:08X}")
|
||||||
|
```
|
||||||
|
</TabItem>
|
||||||
|
</Tabs>
|
||||||
|
|
||||||
|
A typical STM32F1 output:
|
||||||
|
|
||||||
|
```
|
||||||
|
AP #0: MEM-AP
|
||||||
|
IDR: 0x04770031
|
||||||
|
BASE: 0xE00FF003
|
||||||
|
```
|
||||||
|
|
||||||
|
## Multi-DAP boards
|
||||||
|
|
||||||
|
Most boards have a single DAP, and the `SWDController` auto-discovers it via `dap names`. For multi-DAP boards (e.g. STM32H7 dual-core with separate DAPs for M7 and M4), pass the DAP name explicitly:
|
||||||
|
|
||||||
|
```python
|
||||||
|
# Auto-discover (single-DAP boards)
|
||||||
|
dpidr = await session.swd.dpidr()
|
||||||
|
|
||||||
|
# Explicit DAP name (multi-DAP boards)
|
||||||
|
m7_dpidr = await session.swd.dpidr(dap="stm32h7x.m7.dap")
|
||||||
|
m4_info = await session.swd.info(dap="stm32h7x.m4.dap")
|
||||||
|
```
|
||||||
|
|
||||||
|
Every `SWDController` method accepts an optional `dap` keyword argument. When omitted, the controller caches the result of `dap names` and reuses the first discovered DAP.
|
||||||
|
|
||||||
|
## Data types
|
||||||
|
|
||||||
|
### DAPInfo
|
||||||
|
|
||||||
|
Returned by `swd.info()`.
|
||||||
|
|
||||||
|
| Field | Type | Description |
|
||||||
|
|-------|------|-------------|
|
||||||
|
| `name` | `str` | DAP instance name (e.g. `stm32f1x.dap`) |
|
||||||
|
| `dpidr` | `int` | DP ID Register value |
|
||||||
|
| `ap_count` | `int` | Number of APs found in the `dap info` output |
|
||||||
|
| `raw_info` | `str` | Full `dap info` output for detailed parsing |
|
||||||
|
|
||||||
|
### APInfo
|
||||||
|
|
||||||
|
Returned by `swd.list_aps()`.
|
||||||
|
|
||||||
|
| Field | Type | Description |
|
||||||
|
|-------|------|-------------|
|
||||||
|
| `index` | `int` | AP number (0, 1, 2...) |
|
||||||
|
| `idr` | `int` | AP ID Register (from offset 0xFC) |
|
||||||
|
| `base` | `int` | ROM table base address (from offset 0xF8) |
|
||||||
|
| `ap_type` | `str` | `"MEM-AP"`, `"JTAG-AP"`, or `"unknown"` |
|
||||||
|
|
||||||
|
## Error handling
|
||||||
|
|
||||||
|
All SWD/DAP operations raise `SWDError` on failure.
|
||||||
|
|
||||||
|
```python
|
||||||
|
from openocd import SWDError
|
||||||
|
|
||||||
|
try:
|
||||||
|
dpidr = await session.swd.dpidr()
|
||||||
|
except SWDError as e:
|
||||||
|
print(f"SWD error: {e}")
|
||||||
|
```
|
||||||
|
|
||||||
|
Common failure causes:
|
||||||
|
- No DAP found (transport not set to SWD)
|
||||||
|
- Target powered off or not connected
|
||||||
|
- Invalid AP index
|
||||||
|
- DP/AP register read returns no data
|
||||||
|
|
||||||
|
## Method reference
|
||||||
|
|
||||||
|
| Method | Return Type | Description |
|
||||||
|
|--------|-------------|-------------|
|
||||||
|
| `info(dap=None)` | `DAPInfo` | Query DAP identification and topology |
|
||||||
|
| `list_aps(dap=None)` | `list[APInfo]` | Enumerate Access Ports |
|
||||||
|
| `dpreg(address, value=None, *, dap=None)` | `int` | Read or write a DP register |
|
||||||
|
| `apreg(ap, address, value=None, *, dap=None)` | `int` | Read or write an AP register |
|
||||||
|
| `dpidr(dap=None)` | `int` | Read the DP IDR (address 0x0) |
|
||||||
|
| `target_id(dap=None)` | `int` | Read TARGETID (DP address 0x24) |
|
||||||
@ -20,8 +20,8 @@ import { Card, CardGrid } from '@astrojs/starlight/components';
|
|||||||
<Card title="Async-First Design" icon="rocket">
|
<Card title="Async-First Design" icon="rocket">
|
||||||
Full async/await API with sync wrappers. Use `Session.connect()` for async or `Session.connect_sync()` for synchronous code.
|
Full async/await API with sync wrappers. Use `Session.connect()` for async or `Session.connect_sync()` for synchronous code.
|
||||||
</Card>
|
</Card>
|
||||||
<Card title="9 Subsystems" icon="puzzle">
|
<Card title="10 Subsystems" icon="puzzle">
|
||||||
Target control, memory, registers, flash, breakpoints, JTAG, SVD, RTT, and transport — all from one session.
|
Target control, memory, registers, flash, breakpoints, JTAG, SWD/DAP, SVD, RTT, and transport — all from one session.
|
||||||
</Card>
|
</Card>
|
||||||
<Card title="Type-Safe" icon="approve-check">
|
<Card title="Type-Safe" icon="approve-check">
|
||||||
Frozen dataclasses for all return types. Explicit exception hierarchy. Full type annotations throughout.
|
Frozen dataclasses for all return types. Explicit exception hierarchy. Full type annotations throughout.
|
||||||
|
|||||||
@ -16,6 +16,7 @@ from openocd import (
|
|||||||
TargetNotHaltedError,
|
TargetNotHaltedError,
|
||||||
FlashError,
|
FlashError,
|
||||||
JTAGError,
|
JTAGError,
|
||||||
|
SWDError,
|
||||||
SVDError,
|
SVDError,
|
||||||
ProcessError,
|
ProcessError,
|
||||||
)
|
)
|
||||||
@ -32,6 +33,7 @@ OpenOCDError
|
|||||||
| +-- TargetNotHaltedError
|
| +-- TargetNotHaltedError
|
||||||
+-- FlashError
|
+-- FlashError
|
||||||
+-- JTAGError
|
+-- JTAGError
|
||||||
|
+-- SWDError
|
||||||
+-- SVDError
|
+-- SVDError
|
||||||
+-- ProcessError
|
+-- ProcessError
|
||||||
+-- BreakpointError
|
+-- BreakpointError
|
||||||
@ -204,6 +206,30 @@ except JTAGError as e:
|
|||||||
print(f"JTAG chain error: {e}")
|
print(f"JTAG chain error: {e}")
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## SWDError
|
||||||
|
|
||||||
|
```python
|
||||||
|
class SWDError(OpenOCDError)
|
||||||
|
```
|
||||||
|
|
||||||
|
Raised when an SWD/DAP operation fails.
|
||||||
|
|
||||||
|
**When raised:**
|
||||||
|
- `swd.info()` encounters an error querying the DAP
|
||||||
|
- `swd.dpreg()` or `swd.apreg()` fails (e.g. no hex value in response, error keyword in output)
|
||||||
|
- `swd.list_aps()` encounters a probe error
|
||||||
|
- `swd.dpidr()` or `swd.target_id()` fails
|
||||||
|
- No DAP instances found (transport may not be set to SWD)
|
||||||
|
|
||||||
|
```python
|
||||||
|
from openocd import SWDError
|
||||||
|
|
||||||
|
try:
|
||||||
|
dpidr = await session.swd.dpidr()
|
||||||
|
except SWDError as e:
|
||||||
|
print(f"SWD/DAP error: {e}")
|
||||||
|
```
|
||||||
|
|
||||||
## SVDError
|
## SVDError
|
||||||
|
|
||||||
```python
|
```python
|
||||||
|
|||||||
@ -18,7 +18,7 @@ A quick-reference index of every public method in `openocd-python`, organized by
|
|||||||
| `on_halt(callback)` | `None` | Register halt notification callback |
|
| `on_halt(callback)` | `None` | Register halt notification callback |
|
||||||
| `on_reset(callback)` | `None` | Register reset notification callback |
|
| `on_reset(callback)` | `None` | Register reset notification callback |
|
||||||
|
|
||||||
**Properties:** `target`, `memory`, `registers`, `flash`, `jtag`, `breakpoints`, `rtt`, `svd`, `transport`
|
**Properties:** `target`, `memory`, `registers`, `flash`, `jtag`, `swd`, `breakpoints`, `rtt`, `svd`, `transport`
|
||||||
|
|
||||||
## Target
|
## Target
|
||||||
|
|
||||||
@ -100,6 +100,17 @@ A quick-reference index of every public method in `openocd-python`, organized by
|
|||||||
| `svf(path, tap=None, *, quiet=False, progress=True)` | `None` | Execute SVF file |
|
| `svf(path, tap=None, *, quiet=False, progress=True)` | `None` | Execute SVF file |
|
||||||
| `xsvf(tap, path)` | `None` | Execute XSVF file |
|
| `xsvf(tap, path)` | `None` | Execute XSVF file |
|
||||||
|
|
||||||
|
## SWDController
|
||||||
|
|
||||||
|
| Method | Return | Description |
|
||||||
|
|--------|--------|-------------|
|
||||||
|
| `info(dap=None)` | `DAPInfo` | Query DAP identification and topology |
|
||||||
|
| `list_aps(dap=None)` | `list[APInfo]` | Enumerate Access Ports |
|
||||||
|
| `dpreg(address, value=None, *, dap=None)` | `int` | Read or write a DP register |
|
||||||
|
| `apreg(ap, address, value=None, *, dap=None)` | `int` | Read or write an AP register |
|
||||||
|
| `dpidr(dap=None)` | `int` | Read the DP IDR (address 0x0) |
|
||||||
|
| `target_id(dap=None)` | `int` | Read TARGETID (DP address 0x24) |
|
||||||
|
|
||||||
## SVDManager
|
## SVDManager
|
||||||
|
|
||||||
| Method / Property | Return | Description |
|
| Method / Property | Return | Description |
|
||||||
|
|||||||
@ -12,6 +12,7 @@ from openocd import (
|
|||||||
TargetState, Register, FlashSector, FlashBank,
|
TargetState, Register, FlashSector, FlashBank,
|
||||||
TAPInfo, JTAGState, MemoryRegion, BitField,
|
TAPInfo, JTAGState, MemoryRegion, BitField,
|
||||||
DecodedRegister, Breakpoint, Watchpoint, RTTChannel,
|
DecodedRegister, Breakpoint, Watchpoint, RTTChannel,
|
||||||
|
DAPInfo, APInfo,
|
||||||
)
|
)
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -281,6 +282,48 @@ class Watchpoint:
|
|||||||
| `length` | `int` | Size of watched region in bytes |
|
| `length` | `int` | Size of watched region in bytes |
|
||||||
| `access` | `Literal["r", "w", "rw"]` | Access type: read, write, or both |
|
| `access` | `Literal["r", "w", "rw"]` | Access type: read, write, or both |
|
||||||
|
|
||||||
|
## SWD / DAP types
|
||||||
|
|
||||||
|
### DAPInfo
|
||||||
|
|
||||||
|
Debug Access Port information, returned by `swd.info()`.
|
||||||
|
|
||||||
|
```python
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class DAPInfo:
|
||||||
|
name: str
|
||||||
|
dpidr: int
|
||||||
|
ap_count: int
|
||||||
|
raw_info: str
|
||||||
|
```
|
||||||
|
|
||||||
|
| Field | Type | Description |
|
||||||
|
|-------|------|-------------|
|
||||||
|
| `name` | `str` | DAP instance name (e.g. `stm32f1x.dap`) |
|
||||||
|
| `dpidr` | `int` | DP ID Register value |
|
||||||
|
| `ap_count` | `int` | Number of access ports discovered |
|
||||||
|
| `raw_info` | `str` | Full `dap info` output for detailed parsing |
|
||||||
|
|
||||||
|
### APInfo
|
||||||
|
|
||||||
|
Access Port descriptor, returned by `swd.list_aps()`.
|
||||||
|
|
||||||
|
```python
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class APInfo:
|
||||||
|
index: int
|
||||||
|
idr: int
|
||||||
|
base: int
|
||||||
|
ap_type: str
|
||||||
|
```
|
||||||
|
|
||||||
|
| Field | Type | Description |
|
||||||
|
|-------|------|-------------|
|
||||||
|
| `index` | `int` | AP number (0, 1, 2...) |
|
||||||
|
| `idr` | `int` | AP ID Register (from apreg offset 0xFC) |
|
||||||
|
| `base` | `int` | ROM table base address (from apreg offset 0xF8) |
|
||||||
|
| `ap_type` | `str` | `"MEM-AP"`, `"JTAG-AP"`, or `"unknown"` |
|
||||||
|
|
||||||
## RTT types
|
## RTT types
|
||||||
|
|
||||||
### RTTChannel
|
### RTTChannel
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user