Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 11 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,8 @@ The [examples](examples) begin with a raw SWD debug-port identity read, then
add posted access-port reads and a Cortex-M identity read through a MEM-AP.
They compose the public packages explicitly without duplicating their framing.
The `target/cortexm` package reads and decodes the architectural CPUID value
through any compatible target-word reader.
through any compatible target-word reader. It also provides acquired Cortex-M0
halt/resume control over word memory; see [Cortex-M control](docs/cortexm.md).

The FTDI path uses the standard H-series MPSSE port and endpoint layout.
Descriptor-driven FTDI port binding is not implemented yet. J-Link instead
Expand Down Expand Up @@ -131,16 +132,21 @@ root. See [Linux USB access](docs/linux-usb.md) for udev rules and a bounded

Debug and programming interfaces can reset processors, halt execution, modify
memory, reconfigure programmable logic, and change persistent device state.
The shipped examples and `ost` commands avoid reset, halt, target-memory
writes, and persistent changes. The `dap.MemAP` API does expose effectful
scalar writes; callers choose the addresses and own the consequences.
The SWD inspection examples and `ost` commands request a 1 MHz clock ceiling.
The `arm-info`, `coresight-info`, and `cortexm-control` examples accept
`-clock` in Hz for targets that require another rate.

The inspection examples and `ost` commands avoid reset, halt, target-memory
writes, and persistent changes. The separately gated `cortexm-control` example
enables halting debug and halts and resumes a Cortex-M0. The `dap.MemAP` API
does expose effectful scalar writes; callers choose the addresses and own the consequences.
Establishing an ADIv5 connection also changes volatile debug-port control
state; the connection releases its own power requests before return.

## SWD DPIDR example

The program expects exactly one supported FTDI H-series attachment and uses
MPSSE port A at 400 kHz. Connect it to a powered SWD target as follows:
MPSSE port A at 1 MHz. Connect it to a powered SWD target as follows:

| Adapter signal | Target signal |
| --- | --- |
Expand Down
2 changes: 1 addition & 1 deletion cmd/ost/internal/app/session.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ func openSWDTransport(ctx context.Context) (*swdSession, error) {
if err != nil {
return nil, err
}
channel, err := ftdi.Open(ctx, device, ftdi.Config{Port: ftdi.PortA, MaxClockHz: 400_000})
channel, err := ftdi.Open(ctx, device, ftdi.Config{Port: ftdi.PortA, MaxClockHz: 1_000_000})
if err != nil {
closeOwner := device.Close
if channel != nil {
Expand Down
4 changes: 2 additions & 2 deletions cmsisdap/session_integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -101,13 +101,13 @@ func observeCMSISDAPTarget(t *testing.T, ctx context.Context, readyOnOpen bool)
t.Helper()
var options []cmsisdap.Option
if readyOnOpen {
options = append(options, cmsisdap.WithSWD(100_000))
options = append(options, cmsisdap.WithSWD(1_000_000))
}
session := openCMSISDAPSession(t, ctx, options...)
cleanup := newCMSISDAPCleanup(t)
cleanup.retain("CMSIS-DAP session", func(context.Context) error { return session.Close() })
if !readyOnOpen {
if err := session.ConfigureSWD(ctx, 100_000); err != nil {
if err := session.ConfigureSWD(ctx, 1_000_000); err != nil {
t.Fatal(err)
}
}
Expand Down
4 changes: 2 additions & 2 deletions coresight/component_integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ func TestHILComponentIdentity(t *testing.T) {
base uint64
class uint8
}{
{"microbit", discover.Selection{Provider: "cmsisdap", Serial: "9900360140124e4500279015000000360000000097969901"}, armdebug.SWDP(probe.SWDConfig{MaxClockHz: 100_000}), 0, 0xe00ff000, 1},
{"microbit", discover.Selection{Provider: "cmsisdap", Serial: "9900360140124e4500279015000000360000000097969901"}, armdebug.SWDP(probe.SWDConfig{MaxClockHz: 1_000_000}), 0, 0xe00ff000, 1},
{"zcu104", discover.Selection{Provider: "ftdi", Serial: "01691", Function: "A"}, armdebug.JTAGDP(probe.JTAGConfig{MaxClockHz: 100_000}, jtag.Layout{arm, xilinx}, 0), 1, 0x80410000, 9},
} {
t.Run(bench.name, func(t *testing.T) {
Expand All @@ -53,7 +53,7 @@ func TestHILComponentIdentity(t *testing.T) {
if got.Class() != bench.class {
t.Fatalf("class=%#x, want %#x", got.Class(), bench.class)
}
t.Logf("session=%d AP%d 100 kHz identity=%+v", session, bench.ap, got)
t.Logf("session=%d AP%d identity=%+v", session, bench.ap, got)
}) {
return
}
Expand Down
4 changes: 2 additions & 2 deletions coresight/walk_integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ func TestHILROMWalk(t *testing.T) {
arm, _ := jtag.IDCODE(4, 0x5ba00477)
xilinx, _ := jtag.IDCODE(12, 0x14730093)
for _, bench := range []romBench{
{"microbit", discover.Selection{Provider: "cmsisdap", Serial: "9900360140124e4500279015000000360000000097969901"}, armdebug.SWDP(probe.SWDConfig{MaxClockHz: 100_000}), 0, 6, 0},
{"microbit", discover.Selection{Provider: "cmsisdap", Serial: "9900360140124e4500279015000000360000000097969901"}, armdebug.SWDP(probe.SWDConfig{MaxClockHz: 1_000_000}), 0, 6, 0},
{"zcu104", discover.Selection{Provider: "ftdi", Serial: "01691", Function: "A"}, armdebug.JTAGDP(probe.JTAGConfig{MaxClockHz: 100_000}, jtag.Layout{arm, xilinx}, 0), 1, 18, 0x803e0000},
} {
t.Run(bench.name, func(t *testing.T) {
Expand Down Expand Up @@ -69,7 +69,7 @@ func observeROMWalk(t *testing.T, bench romBench, session int) {
t.Logf("visit=%d parent=%d entry=%d base=%#x: %v", i, v.Parent, v.Index, v.Entry.Base, v.Err)
}
}
t.Logf("session=%d AP%d 100 kHz root=%#x visits=%d complete=%t error=%v", session, bench.ap, base, len(visits), err == nil, err)
t.Logf("session=%d AP%d root=%#x visits=%d complete=%t error=%v", session, bench.ap, base, len(visits), err == nil, err)
checkROMWalkObservation(t, bench, visits, err)
}

Expand Down
6 changes: 6 additions & 0 deletions dap/memap.go
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,12 @@ func (m *MemAP) ReadWord(ctx context.Context, addr uint32) (uint32, error) {
return uint32(value), err
}

// WriteWord writes one aligned 32-bit target word and waits for AP completion.
// It has the same effects and failure rules as WriteScalar with Size32.
func (m *MemAP) WriteWord(ctx context.Context, addr, value uint32) error {
return m.WriteScalar(ctx, uint64(addr), Size32, uint64(value))
}

// ReadScalar performs one aligned, sized target-memory read. The returned
// value is right-justified regardless of target byte order or address lane.
func (m *MemAP) ReadScalar(ctx context.Context, addr uint64, size TransferSize) (uint64, error) {
Expand Down
26 changes: 26 additions & 0 deletions dap/memap_word_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package dap_test

import (
"testing"

"github.com/jon/ostiole/dap"
"github.com/jon/ostiole/dap/sim"
)

func TestMEMAPWriteWord(t *testing.T) {
target := sim.New(0x2ba01477)
addMEMAP(t, target, 0, 0x00010001, nil)
mem, err := dap.OpenMemAP(t.Context(), enteredDAPClient(t, target), apSel(0))
if err != nil {
t.Fatal(err)
}
if err := mem.WriteWord(t.Context(), 0x100, 0x12345678); err != nil {
t.Fatal(err)
}
if got, err := mem.ReadWord(t.Context(), 0x100); err != nil || got != 0x12345678 {
t.Fatalf("ReadWord = %#x, %v", got, err)
}
if err := mem.WriteWord(t.Context(), 0x101, 0); err == nil {
t.Fatal("unaligned WriteWord succeeded")
}
}
2 changes: 2 additions & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ today and how to assemble them without duplicating lower-level behavior.
posted AP access, power handshakes, and MEM-AP details worth testing.
- [CoreSight component inspection](coresight.md) describes identification
registers, ROM entry decoding, bounded traversal, and the inspection example.
- [Cortex-M control](cortexm.md) describes Cortex-M0 acquisition, halt/resume,
restoration, and the explicitly gated control example.
- [Composition](composition.md) maps common tasks to the narrowest public
package that implements them and gives coding agents a selection checklist.
- [Capabilities](capabilities.md) distinguishes implemented behavior from
Expand Down
21 changes: 13 additions & 8 deletions docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ USB host access
Arm Debug Port and MEM-AP
|
v
Cortex-M identity
Cortex-M identity and Cortex-M0 control
|
v
examples and ost
Expand Down Expand Up @@ -51,7 +51,7 @@ debugger service.
| `dap` | Bind SW-DP or baseline ADIv5 JTAG-DP, manage identity and power, execute ordered DP/AP transactions, and provide scalar or block MEM-AP access. |
| `dap/sim` | Model the DP, AP, and byte-addressed target-memory state consumed by `dap`. |
| `coresight` | Identify debug components and walk ROM tables through borrowed scalar memory, with explicit bounds and no resource acquisition or target-memory writes. |
| `target/cortexm` | Read and decode the architectural Cortex-M CPUID value. |
| `target/cortexm` | Identify Cortex-M processors and own Cortex-M0 halting debug over borrowed word memory. |
| `examples/...` | Demonstrate public package compositions as executable programs. |
| `cmd/ost` | Provide a small command hierarchy over the same public packages. |

Expand Down Expand Up @@ -334,8 +334,12 @@ children with power-domain metadata and reports an incomplete result. It
uses DAP transfer sizes but owns no DAP or MEM-AP state. See [CoreSight
component identity](coresight.md) for its register and failure boundaries.

`target/cortexm` depends only on a compatible word reader. It knows the CPUID
address and encoding, but it does not know about USB, FTDI, or SWD.
`target/cortexm` identifies processors through a word reader. Cortex-M0
control also requires a word writer that waits for each access to complete.
The target owns DHCSR control and its halt requests, and must be released
before the memory owner.
It does not know about USB, adapters, or wire protocols. See
[Cortex-M control](cortexm.md) for restoration and failure boundaries.

## Host implementations

Expand Down Expand Up @@ -376,10 +380,11 @@ replaceable while exercising the public protocol and DAP layers.

## Safety effects

The current examples and `ost` inspection commands do not reset or halt the
target, write target memory, or change persistent state. The `dap.MemAP` API
does expose scalar and block target-memory writes; applications choose the
affected addresses and own the consequences.
The inspection examples and `ost` commands do not reset or halt the target,
write target memory, or change persistent state. The explicitly gated
`cortexm-control` example enables debug and halts and resumes Cortex-M0.
The `dap.MemAP` API does expose scalar and block target-memory writes;
applications choose the affected addresses and own the consequences.

The layers are not entirely passive:

Expand Down
19 changes: 12 additions & 7 deletions docs/capabilities.md
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ does not prove that the board connects those pins to a debug target.
| FT232H | Yes | Port A; full MPSSE and SWD HIL on Linux and macOS. |
| FT2232H | Yes | Ports A and B using the standard H-series interface and endpoint layout. |
| FT4232H | Yes | Ports A and B using the standard H-series interface and endpoint layout. |
| Explicit clock | Yes | `MaxClockHz` is a ceiling; `Channel.ClockHz` reports the attainable configured rate. Examples request 400 kHz. |
| Explicit clock | Yes | `MaxClockHz` is a ceiling; `Channel.ClockHz` reports the attainable configured rate. SWD examples request 1 MHz. |
| MPSSE lifecycle | Yes | Claim, reset bit mode, purge stale traffic, synchronize, and configure the clock with target pins as inputs. Close drains pending bulk OUT work before resetting bit mode, setting the latency timer to 16 ms, purging the receive and transmit paths, releasing, and closing. |
| SWD bit streams | Yes | Direction-safe output and input runs. Enough maximum-packet-sized IN transfers remain posted to cover the worst-case response admitted by the shared 8,192-clock wire limit, including FTDI status bytes. That requires seventeen requests for a 512-byte endpoint and 133 for a 64-byte endpoint. The receive path consumes them in submission order, replenishes each before delivering its payload, and discards status-only packets independently of OUT completion. |
| Ambiguous transfer handling | Yes | A USB error, including an asynchronous receive failure, invalid transfer count, malformed FTDI packet, or surplus payload poisons the channel. A call which observes the poisoned channel returns the first cause and matches `ErrChannelPoisoned`; later SWD traffic requires a fresh channel. `Close` remains available and retryable. |
Expand Down Expand Up @@ -178,7 +178,7 @@ requires a supported v2 interface when SWD is activated.
| Ownership and cleanup | Yes | Successful open owns the USB device. After failed SWD configuration, `Open` makes a bounded cleanup attempt; if a synchronized disconnect remains pending, it returns the session with the error. After a poisoned exchange, `Close` reports the abandoned port and continues USB cleanup without sending another command. Interface release remains retryable, and device close runs once. When failed open returns no session, the caller closes the device to finish or repeat cleanup. |
| Passive v1 rejection | HIL | The Linux all-device inventory reported the `0d28:0204` DAPLink product and serial. HIL selected it by serial, then rejected it from the v2 path before interface claim. Its command interface is HID; no CMSIS-DAP command or target traffic was sent. |
| v2 metadata reopen | HIL | The macOS all-device inventory found a `0d28:0204` micro:bit by its `BBC micro:bit CMSIS-DAP` product string. Two fresh sessions returned protocol `2.1.0`, firmware `0257`, packet size 64, packet count 5, and capabilities `0x11`. No target command was sent. |
| SWD target access | HIL | Two fresh sessions against the same micro:bit used `ConfigureSWD` and `WithSWD` at 100 kHz. Both returned DPIDR `0x0bb11477`, AP0 IDR `0x04770021`, and CPUID `0x410cc200`; `DHCSR.S_HALT` was unchanged. Each restored the saved AP0 CSW and TAR before releasing the debug port and disconnecting. OpenOCD 0.12.0 independently selected the same serial and v2 bulk interface, returned the same DPIDR and AP0 IDR, and identified the target as Cortex-M0. This is read-only evidence from one probe and target; CMSIS-DAP does not report the attained clock. |
| SWD target access | HIL | Two fresh sessions against the same micro:bit used `ConfigureSWD` and `WithSWD` at 100 kHz. Both returned DPIDR `0x0bb11477`, AP0 IDR `0x04770021`, and CPUID `0x410cc200`; `DHCSR.S_HALT` was unchanged. Each restored the saved AP0 CSW and TAR before releasing the debug port and disconnecting. OpenOCD 0.12.0 independently selected the same serial and v2 bulk interface, returned the same DPIDR and AP0 IDR, and identified the target as Cortex-M0. That 100 kHz run used an active debug interface. A later 1 MHz run connected first after physical replug and repeated both sessions; see [nRF51 startup](protocols/cmsisdap.md#nrf51-startup-clock). CMSIS-DAP does not report the attained clock. |
| JTAG or SWO | No | The current session does not connect JTAG or use the optional SWO endpoint. |

The [CMSIS-DAP v2 session guide](protocols/cmsisdap.md) gives the descriptor,
Expand Down Expand Up @@ -245,7 +245,7 @@ See [JTAG](protocols/jtag.md) for effects and ownership.
| MEM-AP acquisition | Yes | `OpenMemAP` performs AP traffic, rejects an absent or non-MEM AP, and snapshots the state which `Release` restores. |
| MEM-AP debug entry | Yes | `ReadDebugBase` decodes ADIv5 and legacy BASE formats, distinguishes absence from address zero, and reads the upper word only for a present entry with CFG.LA. It preserves the memory client on success and does not access target memory. Behavioral tests cover formats, malformed values, cancellation, failure, retry, and shared SWD/JTAG access. |
| MEM-AP configuration | Yes | `OpenMemAP` reads CFG, models BE, LA, and LD, and includes TARHI in retryable restoration when large addresses are available. |
| Scalar target-memory access | Yes | `ReadScalar` and `WriteScalar` support aligned 8-, 16-, and 32-bit values and verify the implementation-defined CSW.Size before using the byte lane selected by CFG.BE. CFG.LA permits addresses above 32 bits; CFG.LD makes 64-bit access eligible for the same CSW check. Oversized write values fail before traffic, and writes finish with an AP completion barrier. If the first DRW access of a failed Size64 transfer might have started, ordinary traffic remains blocked until cleanup. `ReadWord` provides the 32-bit convenience operation. |
| Scalar target-memory access | Yes | `ReadScalar` and `WriteScalar` support aligned 8-, 16-, and 32-bit values and verify the implementation-defined CSW.Size before using the byte lane selected by CFG.BE. CFG.LA permits addresses above 32 bits; CFG.LD makes 64-bit access eligible for the same CSW check. Oversized write values fail before traffic, and writes finish with an AP completion barrier. If the first DRW access of a failed Size64 transfer might have started, ordinary traffic remains blocked until cleanup. `ReadWord` and `WriteWord` provide 32-bit convenience operations. |
| MEM-AP restoration | Yes | Saves and restores CSW, TAR, and TARHI when present; failed restoration remains retryable. MEM-AP restoration remains available while debug-port cleanup is pending. If framing is unknown, `Release` re-enters the bound protocol and verifies identity before restoration. It terminates a possibly incomplete Size64 transfer through CSW before touching TAR or TARHI. If DAPABORT interrupts cleanup, the next `Release` retries every saved value. The invalidated handle remains invalid. |
| Managed target-memory writes | Yes | `WriteScalar` and `WriteBlock` are effectful. The caller selects the address; the API checks alignment and range, not whether that address is safe to modify. `WriteRawAP` remains an unmanaged escape hatch. |
| Block reads | Yes | Accepts empty, unaligned, and mixed-width ranges. No auto-incrementing word run crosses a 1 KiB TAR boundary. If the MEM-AP does not accept single address increment, the reader writes TAR before each word. It uses the ordinary DAP WAIT policy. If selection, framing, or cleanup becomes uncertain, repair is required. A FAULT returns only the confirmed prefix. Cancellation and transport or protocol failures can also interrupt the read. Unread destination bytes remain untouched. |
Expand Down Expand Up @@ -302,14 +302,15 @@ layouts and power-domain skips have hardware-independent test coverage.
| --- | --- | --- |
| CPUID read and decode | Yes | Accepts any aligned-word reader and validates a plausible Arm Cortex-M identity. |
| Physical identity read | HIL | Opt-in FTDI/SWD/DAP/MEM-AP integration test. |
| Halt, resume, or step | No | No target run-control API exists. |
| Cortex-M0 acquisition and halt/resume | HIL | Two CMSIS-DAP micro:bit sessions at a requested 1 MHz stopped a CPU counter during halt and observed progress after resume and release. Both restored initially disabled debug and running state before Arm debug owner close. Earlier sessions preserved initially enabled debug. Cleanup failures remain covered only by behavioral tests; see the [control evidence](cortexm.md#hardware-evidence). |
| Step | No | No single-step API exists. |
| Register access | No | CPUID decoding is not a general core-register interface. |
| Reset | No | No architectural or pin-reset operation exists. |
| Breakpoints or watchpoints | No | No target instrumentation API exists. |
| Firmware or runtime loading | No | No ELF loader, image-placement policy, or flash driver exists. |

The package identifies a processor; it is not yet a complete Cortex-M target
driver.
Identity covers Cortex-M; acquired control currently accepts Cortex-M0 only.
See [Cortex-M control](cortexm.md) for its effects and cleanup limits.

## Executable surfaces

Expand All @@ -325,6 +326,9 @@ Available examples:
- `examples/simple/arm-info` reports the same identities through generic
probe discovery and one Arm debug owner, with explicit AP selection.

`examples/simple/cortexm-control` separately demonstrates effectful Cortex-M0
halt/resume and requires `-allow-control`.

Available `ost` commands:

```text
Expand All @@ -335,7 +339,8 @@ ost dap ap id --ap N
ost target cortex-m id --ap N
```

These hardware operations are read-only with respect to target memory and do
The inspection examples and these commands are read-only with respect to
target memory and do
not halt or reset the target. They still claim the adapter, clock SWD, and use
the volatile DAP and MEM-AP state described above.

Expand Down
Loading
Loading