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: 'Breakpoints & Watchpoints', slug: 'guides/breakpoints' },
|
||||
{ label: 'JTAG Operations', slug: 'guides/jtag-operations' },
|
||||
{ label: 'SWD Operations', slug: 'guides/swd-operations' },
|
||||
{ label: 'SVD Register Decoding', slug: 'guides/svd-decoding' },
|
||||
{ label: 'RTT Communication', slug: 'guides/rtt-communication' },
|
||||
{ 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">
|
||||
Full async/await API with sync wrappers. Use `Session.connect()` for async or `Session.connect_sync()` for synchronous code.
|
||||
</Card>
|
||||
<Card title="9 Subsystems" icon="puzzle">
|
||||
Target control, memory, registers, flash, breakpoints, JTAG, SVD, RTT, and transport — all from one session.
|
||||
<Card title="10 Subsystems" icon="puzzle">
|
||||
Target control, memory, registers, flash, breakpoints, JTAG, SWD/DAP, SVD, RTT, and transport — all from one session.
|
||||
</Card>
|
||||
<Card title="Type-Safe" icon="approve-check">
|
||||
Frozen dataclasses for all return types. Explicit exception hierarchy. Full type annotations throughout.
|
||||
|
||||
@ -16,6 +16,7 @@ from openocd import (
|
||||
TargetNotHaltedError,
|
||||
FlashError,
|
||||
JTAGError,
|
||||
SWDError,
|
||||
SVDError,
|
||||
ProcessError,
|
||||
)
|
||||
@ -32,6 +33,7 @@ OpenOCDError
|
||||
| +-- TargetNotHaltedError
|
||||
+-- FlashError
|
||||
+-- JTAGError
|
||||
+-- SWDError
|
||||
+-- SVDError
|
||||
+-- ProcessError
|
||||
+-- BreakpointError
|
||||
@ -204,6 +206,30 @@ except JTAGError as 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
|
||||
|
||||
```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_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
|
||||
|
||||
@ -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 |
|
||||
| `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
|
||||
|
||||
| Method / Property | Return | Description |
|
||||
|
||||
@ -12,6 +12,7 @@ from openocd import (
|
||||
TargetState, Register, FlashSector, FlashBank,
|
||||
TAPInfo, JTAGState, MemoryRegion, BitField,
|
||||
DecodedRegister, Breakpoint, Watchpoint, RTTChannel,
|
||||
DAPInfo, APInfo,
|
||||
)
|
||||
```
|
||||
|
||||
@ -281,6 +282,48 @@ class Watchpoint:
|
||||
| `length` | `int` | Size of watched region in bytes |
|
||||
| `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
|
||||
|
||||
### RTTChannel
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user