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
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,8 @@ 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. It also provides acquired Cortex-M0 and
Cortex-M33 halt/resume control and halted register access over word memory, plus
Cortex-M0 stepping; see [Cortex-M control](docs/cortexm.md).
Cortex-M33 halt/resume control, halted register access, and architectural
stepping 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
4 changes: 2 additions & 2 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ and how to assemble them without duplicating lower-level behavior.
- [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/M33 acquisition,
halt/resume, register access, restoration, and the explicitly gated control
example.
halt/resume, register access, stepping, 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
3 changes: 2 additions & 1 deletion docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -335,7 +335,8 @@ before the memory owner. Register writes persist after release. It does not know
about USB, adapters, or wire protocols. See [Cortex-M control](cortexm.md) for
restoration and failure boundaries. Cortex-M33 control requires Secure invasive
debug permission. Its stack register selectors use the halted security state;
register access does not change security state. Stepping remains Cortex-M0-only.
register access does not change security state. Both architectures support
stepping from an owned halt.

## Host implementations

Expand Down
9 changes: 5 additions & 4 deletions docs/capabilities.md
Original file line number Diff line number Diff line change
Expand Up @@ -296,18 +296,19 @@ 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. |
| 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). |
| Cortex-M33 acquisition and halt/resume | HIL | Two RP2350 core-0/J-Link sessions at 1 MHz stopped a RAM counter during halt, observed counter progress after resume and release, and restored disabled debug. Secure invasive debug permission is required; stepping remains M0-only. |
| Cortex-M33 acquisition and halt/resume | HIL | Two RP2350 core-0/J-Link sessions at 1 MHz stopped a RAM counter during halt, observed counter progress after resume and release, and restored disabled debug. Secure invasive debug permission is required. |
| Cortex-M0 step | HIL | `Target.Step` requires an owned halt and returns halted. Two fresh micro:bit sessions checked PC/R0/RAM across 13 steps each, resume, and release with disabled debug restored. Competing events and failure cleanup have behavioral coverage; see the [step bench](cortexm.md#step-bench). |
| Cortex-M33 step | HIL | Two fresh RP2350 core-0/J-Link sessions at 1 MHz checked PC/R0/RAM across 13 steps each, resume, and release with disabled debug restored. Secure counter state and DSCSR were preserved. Permission, snap-stall, restart after completion, and failure cleanup have behavioral coverage; see the [RP2350 step bench](cortexm.md#rp2350-step-bench). |
| Register reads | Yes | Halted Cortex-M0/M33 R0–R12, SP, LR, PC, XPSR, MSP, and PSP through `ReadRegister`. Two fresh sessions each on CMSIS-DAP/micro:bit and J-Link/RP2350 core 0 read all 19 registers; transfer failures and cleanup have behavioral coverage. |
| Register writes | Yes | Halted Cortex-M0/M33 writes except XPSR; aligned SP/MSP/PSP and even PC values. Writes persist after release. Behavioral tests cover staging, uncertain selection, and pending cleanup. Two sessions each on micro:bit and RP2350 core 0 wrote and restored R4, SP, MSP, PSP, and PC before resuming; see the [micro:bit](cortexm.md#register-bench) and [RP2350](cortexm.md#rp2350-register-bench) register benches. |
| 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. |

Identity covers Cortex-M; acquired halt/resume control accepts Cortex-M0 and
Cortex-M33. M33 requires Secure invasive debug permission and excludes stepping.
Register access uses the halted security state. See
[Cortex-M control](cortexm.md) for its effects and cleanup limits.
Cortex-M33. M33 requires Secure invasive debug permission. Register access uses
the halted security state. See [Cortex-M control](cortexm.md) for its effects
and cleanup limits.

## Executable surfaces

Expand Down
2 changes: 1 addition & 1 deletion docs/composition.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ data-register write can write target memory.
| Inspect ROM entries or a bounded component hierarchy | `Component.ROMTable`, `ROMTable.ReadEntry`, `coresight.Walk` | `examples/simple/coresight-info -walk` |
| Identify a Cortex-M through any compatible word reader | `cortexm.Identify` | `examples/simple/cortexm-info` |
| Acquire, halt, inspect registers, and resume a Cortex-M0 | `cortexm.Acquire`, `Target.Halt`, `Target.ReadRegister`, `Target.Resume`, `Target.Release` | `examples/simple/cortexm-control` |
| Step a Cortex-M0 from an owned halt | `Target.Step` | `examples/simple/cortexm-control -step` |
| Step a Cortex-M0/M33 from an owned halt | `Target.Step` | `examples/simple/cortexm-control -step` |
| Read or write a halted Cortex-M0/M33 register | `Target.ReadRegister`, `Target.WriteRegister` | [Register reads](cortexm.md#register-reads), [writes](cortexm.md#register-writes) |
| Test SWD and DAP behavior without hardware | `swd/sim`, `dap/sim` | Package tests |

Expand Down
64 changes: 52 additions & 12 deletions docs/cortexm.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,9 +86,8 @@ can safely resume; clearing the bit is insufficient. Once observed, the target
will not automatically resume the processor. Reset and recovery from that state
remain outside this API.

`Acquire`, `Halt`, `Halted`, `Resume`, `Release`, `ReadRegister`, and
`WriteRegister` support Cortex-M33. `Step` rejects an acquired M33 before
further memory traffic, leaving its other operations available.
`Acquire`, `Halt`, `Halted`, `Resume`, `Release`, `ReadRegister`,
`WriteRegister`, and `Step` support Cortex-M0 and Cortex-M33.

On RP2350, core 0 uses the ADIv6 MEM-AP at `0x2000`. Select it through the
existing Arm debug owner, then use the target composition below:
Expand All @@ -113,11 +112,11 @@ the [RP2350 datasheet][rp2350].

## Stepping

`Step(ctx)` performs one Cortex-M0 architectural step from a halt owned by the
target. It returns halted with stepping disabled, retaining ownership for
another step, register access, or resume. It rejects a running processor or an
inherited halt, settles any pending register transfer before launch, and uses
the caller's context for cancellation and deadlines.
`Step(ctx)` performs one Cortex-M0 or Cortex-M33 architectural step from a halt
owned by the target. It returns halted with stepping disabled, retaining
ownership for another step, register access, or resume. It rejects a running
processor or an inherited halt, settles any pending register transfer before
launch, and uses the caller's context for cancellation and deadlines.

```go
if err := core.Step(ctx); err != nil {
Expand All @@ -141,14 +140,20 @@ before clearing C_STEP; it does not change stepping control while running. A
failed write to clear C_STEP can be retried without restarting execution. An
unconfirmed launch, ignored step request, reset, changed debug control, or loss
of the completed halt can prevent automatic cleanup. A competing stop can
prevent restoring initially disabled debug until the processor runs again.
Retain both owners when release fails; this package provides no forced cleanup
operation.
prevent restoring initially disabled debug until the processor runs again. On
M33, the step's own restart is expected while waiting for completion. After
observing the completed halt, any further restart prevents automatic cleanup,
even if the core has already halted again. Permission and snap-stall checks
apply throughout; before clearing C_STEP, the target checks that the completed
halt is still present. Retain both owners when release fails; this package
provides no forced cleanup operation.

Instructions, exception entry, elapsed time, and peripheral effects cannot be
undone. Behavioral tests cover immediate and delayed completion, competing
flags, cancellation, ignored writes, partial failures, and cleanup retries. The
[step bench](#step-bench) records physical instruction checks.
[micro:bit step bench](#step-bench) and [RP2350 step bench](#rp2350-step-bench)
record physical instruction checks. M33 step semantics follow Arm DDI 0553B.y
B13.4.2 and D1.2.38–D1.2.39.

## Register reads

Expand Down Expand Up @@ -435,3 +440,38 @@ coverage. No security-state switch or core-1 control was performed. Temporary
stack and PC values were not executed. State after Arm owner close was not
independently measured. The previously loaded RAM program remains running; flash
was untouched.

## RP2350 step bench

After preparing the
[core-0 RAM counter](../target/cortexm/testdata/rp2350-counter/README.md), run
the separately gated step test:

```sh
OSTIOLE_RP2350_HIL_CONTROL=1 \
OSTIOLE_RP2350_HIL_STEP=1 \
OSTIOLE_RP2350_HIL_PROGRAM=c20737e61153b272322548e8e6db5c420f0c148d6707ca4412c309f70415065a \
go test -tags integration ./target/cortexm -run '^TestHILRP2350Step$' -count=1 -v
```

The test checks CPUID and counter instructions before acquisition and refuses an
inherited halt. After halting, it checks Secure state, Thread mode, Thumb state,
and R1's counter address. It compares PC, R0, and RAM after each step through
the increment at `0x20040026`, store at `0x20040028`, and branch at
`0x2004002a`. Twelve steps precede resume; a further halt and step exercise
release from an owned stop. DSCSR is compared across the first twelve steps. The
test does not reload firmware or roll back execution.

On Nostalgia, two fresh sessions through J-Link EDU Mini V2 `000802011345` at 1
MHz and AP `0x2000` passed on RP2350 core 0, CPUID `0x411fd210`. Each checked 13
steps, with PC/R0/RAM matching the expected instruction effects. The counter
stayed unchanged while halted and advanced after resume and release. DSCSR
remained `0x00030000`. Initially disabled debug and running state were restored
before Arm owner close; target release and owner close succeeded.

The RAM program disables configurable interrupts and runs in Secure state.
Exception entry, competing debug events, permission loss, snap-stall, restart
after a completed halt, and failure cleanup have behavioral coverage. These
sessions do not establish sleeping-instruction behavior, Non-secure execution,
core-1 control, or cross-core coordination. State after Arm owner close was not
independently measured. Flash was untouched and the counter remains running.
10 changes: 0 additions & 10 deletions target/cortexm/architecture.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,13 +47,3 @@ func (t *Target) readDHCSR(ctx context.Context) (uint32, error) {
}
return value, err
}

func (t *Target) activeM0(ctx context.Context) error {
if err := t.active(ctx); err != nil {
return err
}
if t.identity.Part != 0xc20 {
return errors.New("cortexm: stepping requires Cortex-M0")
}
return nil
}
1 change: 0 additions & 1 deletion target/cortexm/control.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,6 @@ type Target struct {
// Cortex-M33 requires Secure invasive debug permission (S_SDE) and rejects
// snap-stall state. It does not change authentication or security settings.
// DHCSR reads consume sticky reset, retirement, and Cortex-M33 restart status.
// Stepping currently requires Cortex-M0.
//
// The caller controls cancellation and deadlines. Failed setup attempts
// restoration with an independent five-second context. A non-nil target
Expand Down
2 changes: 1 addition & 1 deletion target/cortexm/identity.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// Package cortexm identifies Cortex-M processors and provides Cortex-M0 and
// Cortex-M33 halting debug and register access through target memory.
// Stepping currently requires Cortex-M0.
// Both support architectural stepping from an owned halt.
package cortexm

import (
Expand Down
100 changes: 100 additions & 0 deletions target/cortexm/m33_step_integration_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
//go:build integration

package cortexm_test

import (
"context"
"os"
"testing"
"time"

"github.com/jon/ostiole/dap"
"github.com/jon/ostiole/target/cortexm"
)

func TestHILRP2350Step(t *testing.T) {
if os.Getenv("OSTIOLE_RP2350_HIL_STEP") != "1" || os.Getenv("OSTIOLE_RP2350_HIL_CONTROL") != "1" {
t.Skip("require OSTIOLE_RP2350_HIL_STEP=1 and OSTIOLE_RP2350_HIL_CONTROL=1")
}
if os.Getenv("OSTIOLE_RP2350_HIL_PROGRAM") != "c20737e61153b272322548e8e6db5c420f0c148d6707ca4412c309f70415065a" {
t.Fatal("require the documented RP2350 counter binary identity")
}
for range 2 {
if !t.Run("session", m33StepHIL) {
return
}
}
}

func m33StepHIL(t *testing.T) {
t.Helper()
ctx, cancel := context.WithTimeout(t.Context(), 30*time.Second)
defer cancel()
ap, err := dap.APAt(0x2000)
if err != nil {
t.Fatal(err)
}
bench := controlBench{name: "RP2350 core 0", provider: "jlink", serial: "000802011345", ap: ap, cpuid: 0x411fd210}
c := bench.open(t, ctx)
var core *cortexm.Target
t.Cleanup(func() { releaseControlBench(t, core, c) })
memory, err := c.OpenMemAP(ctx, ap)
if err != nil {
t.Fatal(err)
}
before := checkM33RegisterBench(t, ctx, memory)
checkCounterHIL(t, ctx, memory, 0x20040000, "before acquisition", false)
core, err = cortexm.Acquire(ctx, memory)
if err != nil {
t.Fatal(err)
}
if err := core.Halt(ctx); err != nil {
t.Fatal(err)
}
exerciseM33StepsHIL(t, ctx, core, memory)
checkCounterHIL(t, ctx, memory, 0x20040000, "after steps, halted", true)
if err := core.Resume(ctx); err != nil {
t.Fatal(err)
}
checkCounterHIL(t, ctx, memory, 0x20040000, "resumed", false)
if err := core.Halt(ctx); err != nil {
t.Fatal(err)
}
checkCounterStepAtHIL(t, ctx, core, memory, 12, 0x20040026, 0x20040000)
if err := core.Release(ctx); err != nil {
t.Fatal(err)
}
after, err := memory.ReadWord(ctx, dhcsr)
mask := debugEnable | haltStatus
if before&debugEnable != 0 {
mask |= 12
}
if err != nil || after&mask != before&mask {
t.Fatalf("DHCSR before=%#x after=%#x: %v", before, after, err)
}
checkCounterHIL(t, ctx, memory, 0x20040000, "released after another step", false)
t.Logf("J-Link %s 1 MHz AP 0x2000 CPUID=%#x DHCSR before=%#x after=%#x", bench.serial, core.Identity().Raw, before, after)
}

func exerciseM33StepsHIL(t *testing.T, ctx context.Context, core *cortexm.Target, memory *dap.MemAP) {
t.Helper()
const dscsr = uint32(0xe000ee08)
domain, err := memory.ReadWord(ctx, dscsr)
if err != nil || domain&(1<<16) == 0 {
t.Fatalf("require Secure counter state: DSCSR=%#x: %v", domain, err)
}
if r1 := readRegisterHIL(t, ctx, core, cortexm.R1); r1 != 0x20040000 {
t.Fatalf("counter address in R1=%#x", r1)
}
if xpsr := readRegisterHIL(t, ctx, core, cortexm.XPSR); xpsr&0x010001ff != 0x01000000 {
t.Fatalf("counter state XPSR=%#x", xpsr)
}
for n := range 12 {
checkCounterStepAtHIL(t, ctx, core, memory, n, 0x20040026, 0x20040000)
}
after, err := memory.ReadWord(ctx, dscsr)
if err != nil || after != domain {
t.Fatalf("DSCSR before=%#x after=%#x: %v", domain, after, err)
}
t.Logf("DSCSR preserved at %#x", domain)
}
Loading
Loading