diff --git a/README.md b/README.md index 257b772..e83a9f3 100644 --- a/README.md +++ b/README.md @@ -71,7 +71,10 @@ 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, halted register access, and architectural -stepping over word memory; see [Cortex-M control](docs/cortexm.md). +stepping over word memory; see [Cortex-M control](docs/cortexm.md). Independent +RP2350 targets can share one Arm owner with serialized calls; the +[two-core bench](docs/cortexm.md#rp2350-independent-core-bench) records halt, +step, peer progress, and cleanup observations. 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 diff --git a/docs/architecture.md b/docs/architecture.md index f022fb7..64dbbc8 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -61,6 +61,11 @@ banking, AP identification, raw AP addresses, power ownership, and MEM-AP state stay in `dap`. Higher layers should call these packages rather than reproduce their framing. +A target owns one processor. Several targets may borrow distinct MEM-APs from +one `armdebug.Conn`; all calls over that shared owner remain serialized. +Independent RP2350 core control has [physical evidence][dual-core]. It does not +provide group ownership, CTI routing, or a simultaneous memory snapshot. + ## Discovery and opening `discover.Registry` stores immutable transport providers. Registration is @@ -409,3 +414,5 @@ The layers are not entirely passive: Callers should always complete the documented release sequence, including when the primary operation fails. + +[dual-core]: cortexm.md#rp2350-independent-core-bench diff --git a/docs/capabilities.md b/docs/capabilities.md index 62b1cc0..ed3f9b5 100644 --- a/docs/capabilities.md +++ b/docs/capabilities.md @@ -301,6 +301,7 @@ skips have hardware-independent test coverage. | 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. | +| Independent RP2350 cores | HIL | Two fresh J-Link sessions borrowed core-0/core-1 MEM-APs under one Arm owner. Each core halted and stepped while its peer advanced; both restored disabled debug and running state with inactive CTIs unchanged. See the [independent-core bench](cortexm.md#rp2350-independent-core-bench). This provides serialized per-core control, without group or CTI ownership. | | 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. | @@ -345,9 +346,9 @@ SWD, and use the volatile DAP and MEM-AP state described above. ## Not currently provided There is no CMSIS-DAP HID/v1 transport, automatic probe discovery policy, -multi-core or SoC attachment, general target control, semihosting, trace, -debugger protocol server, firmware flashing, FPGA programming, or Windows host -implementation. +automatic SoC attachment, group or CTI control, general target control, +semihosting, trace, debugger protocol server, firmware flashing, FPGA +programming, or Windows host implementation. Treat an absent capability as an explicit boundary. Do not infer it from the project description or recreate its lower-level protocol inside an application. diff --git a/docs/composition.md b/docs/composition.md index 2a8139d..b47a859 100644 --- a/docs/composition.md +++ b/docs/composition.md @@ -744,6 +744,12 @@ target tracks transfer completion but does not roll back writes. Release it before its memory owner and retain both after failed target restoration. See [Cortex-M control](cortexm.md) for the full composition and effects. +For independent RP2350 processors, borrow both MEM-APs from one Arm owner and +acquire a separate target for each. Serialize calls over that connection and +release both targets before closing their memory owner. The +[two-core composition and bench](cortexm.md#independent-rp2350-cores) show the +per-core ownership boundary and physical observations. + ## Release in reverse order A complete Cortex-M identity composition acquires and releases state in one of diff --git a/docs/cortexm.md b/docs/cortexm.md index 907ca42..491fbee 100644 --- a/docs/cortexm.md +++ b/docs/cortexm.md @@ -246,6 +246,68 @@ Hardware-independent tests model DHCSR control and execution state, including partial writes, canceled operations, ignored writes, failed cleanup, and retry. They do not establish physical halt/resume behavior on a bench program. +## Independent RP2350 cores + +One `armdebug.Conn` can lend core 0's MEM-AP at `0x2000` and core 1's MEM-AP at +`0x4000`. Acquire a separate `cortexm.Target` for each. Serialize all calls over +the shared connection, including memory reads, target operations, and cleanup. +Each target owns only its processor's debug state and halt requests. Halting one +target does not claim ownership of a stop on the other. + +For an already-open Arm owner `c`, the caller supplies operation `ctx` and a +live `cleanupCtx`, including after cancellation: + +```go +var cores [2]*cortexm.Target +var err error +for i, base := range []uint64{0x2000, 0x4000} { + ap, e := dap.APAt(base) + if e != nil { + err = e + break + } + memory, e := c.OpenMemAP(ctx, ap) + if e != nil { + err = e + break + } + cores[i], err = cortexm.Acquire(ctx, memory) + if err != nil { + break + } +} +if err == nil { + err = cores[0].Halt(ctx) +} +if err == nil { + var halted bool + halted, err = cores[1].Halted(ctx) + if err == nil { + fmt.Printf("core 1 halted=%v\n", halted) + } +} +if err == nil { + err = cores[0].Resume(ctx) +} +var releaseErr error +for i := len(cores) - 1; i >= 0; i-- { + releaseErr = errors.Join(releaseErr, cores[i].Release(cleanupCtx)) +} +err = errors.Join(err, releaseErr) +if releaseErr == nil { + err = errors.Join(err, c.Close()) +} +// Retain targets and c if target cleanup fails; retain c if Close fails. +``` + +A failed acquisition can return a non-nil target, so store it before handling +the error. Release every retained target before closing its memory owner; a +failed release leaves that owner live for retry. A target acquired while its +processor is halted cannot resume that inherited halt. Existing cross-trigger +routing can couple stops: inspect the bench's routing before expecting +independent progress. This composition provides per-core control, without group +ownership, coordinated stopping, or a simultaneous snapshot. + ## Hardware procedure The opt-in integration test selects the CMSIS-DAP micro:bit with serial @@ -475,3 +537,45 @@ 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. + +## RP2350 independent-core bench + +`TestHILRP2350IndependentCores` selects J-Link EDU Mini V2 `000802011345` at 1 +MHz, opens one Arm owner, and borrows AP `0x2000` and AP `0x4000`. Prepare the +[separate RAM counters][dual-counter] first. That procedure replaces both cores' +volatile execution state, resets core 1, and disables its Secure MPU for RAM +entry; it does not change flash or CTI routing. + +```sh +OSTIOLE_RP2350_HIL_DUAL_CORE=1 \ +OSTIOLE_RP2350_HIL_PROGRAM=bf878b47815bc5eaf6afb5279efaa6ff163832bd178c5b7b1604f80e6ad6cde9 \ +go test -tags integration ./target/cortexm -run '^TestHILRP2350IndependentCores$' -count=1 -v +``` + +The test requires the known instruction words, running Secure counters, and +inactive CTIs before acquiring either target. It checks CTI architecture and +geometry, disabled control and integration mode, zero application triggers, +output and input-channel status, and all eight input/output routes. It reads and +preserves CTIGATE. It never writes routing or acknowledgements. + +On Nostalgia, two fresh sessions read all 19 registers on each core and checked +one architectural step per core against PC, R0, and its counter word. Core 0's +counter stayed unchanged during its halt and after its step while core 1 +advanced; the reverse held when core 1 was halted and stepped. Both counters +stopped after sequential halt requests. Resuming only core 0 left core 1 +stopped; release from an owned halt restored progress on each core. + +Both sessions restored initially disabled halting debug and running state on +both cores before Arm owner close, with DHCSR `0x01100000` before/after and +DSCSR `0x00030000` unchanged. Both CTIs remained disabled and unrouted, with +zero pending output/input-channel status and CTIGATE `0x0f` unchanged. Both +targets released and the shared owner closed successfully. + +This covers the prepared Secure counter programs and serialized per-core +operations. It does not establish simultaneous stopping, CTI propagation, +Non-secure execution, sleeping instructions, or cross-core failure recovery. +Per-target cleanup failures have behavioral coverage; this bench does not inject +failures. State after Arm owner close was not independently measured. Both loops +remain running; preparation and instruction effects are not undone. + +[dual-counter]: ../target/cortexm/testdata/rp2350-dual-counter/README.md diff --git a/target/cortexm/multicore_integration_test.go b/target/cortexm/multicore_integration_test.go new file mode 100644 index 0000000..e2f0213 --- /dev/null +++ b/target/cortexm/multicore_integration_test.go @@ -0,0 +1,261 @@ +//go:build integration + +package cortexm_test + +import ( + "context" + "os" + "testing" + "time" + + "github.com/jon/ostiole/armdebug" + "github.com/jon/ostiole/dap" + "github.com/jon/ostiole/target/cortexm" +) + +const dualCounterProgram = "bf878b47815bc5eaf6afb5279efaa6ff163832bd178c5b7b1604f80e6ad6cde9" + +func TestHILRP2350IndependentCores(t *testing.T) { + if os.Getenv("OSTIOLE_RP2350_HIL_DUAL_CORE") != "1" { + t.Skip("require OSTIOLE_RP2350_HIL_DUAL_CORE=1") + } + if os.Getenv("OSTIOLE_RP2350_HIL_PROGRAM") != dualCounterProgram { + t.Fatal("require the documented two-core counter binary identity") + } + for range 2 { + if !t.Run("session", dualCoreHIL) { + return + } + } +} + +type dualCoreBench struct { + memory *dap.MemAP + core *cortexm.Target + before uint32 + domain uint32 + cti []uint32 +} + +func dualCoreHIL(t *testing.T) { + t.Helper() + ctx, cancel := context.WithTimeout(t.Context(), 90*time.Second) + defer cancel() + bench := controlBench{name: "RP2350 independent cores", provider: "jlink", serial: "000802011345"} + c := bench.open(t, ctx) + var cores [2]dualCoreBench + t.Cleanup(func() { releaseDualCoreBench(t, &cores, c) }) + for i, base := range []uint64{0x2000, 0x4000} { + ap, err := dap.APAt(base) + if err != nil { + t.Fatal(err) + } + cores[i].memory, err = c.OpenMemAP(ctx, ap) + if err != nil { + t.Fatal(err) + } + checkDualCoreBench(t, ctx, &cores[i], i) + } + checkDualCounters(t, ctx, &cores, "before acquisition", [2]bool{}) + for i := range cores { + var err error + cores[i].core, err = cortexm.Acquire(ctx, cores[i].memory) + if err != nil { + t.Fatal(err) + } + } + exerciseDualCores(t, ctx, &cores) + for i := range cores { + checkDualCoreRestored(t, ctx, &cores[i], i) + } +} + +func checkDualCoreBench(t *testing.T, ctx context.Context, b *dualCoreBench, index int) { + t.Helper() + identity, err := cortexm.Identify(ctx, b.memory) + if err != nil || identity.Raw != 0x411fd210 { + t.Fatalf("core %d CPUID=%#x: %v", index, identity.Raw, err) + } + b.cti = readInactiveCTI(t, ctx, b.memory) + for i, want := range []uint32{0x4902b672, 0x30012000, 0xe7fc6008, 0x20040000 + uint32(index)*4} { + addr := uint32(0x20040020 + index*0x40 + i*4) + got, err := b.memory.ReadWord(ctx, addr) + if err != nil || got != want { + t.Fatalf("core %d counter code %#x=%#x, want %#x: %v", index, addr, got, want, err) + } + } + b.before, err = b.memory.ReadWord(ctx, dhcsr) + if err != nil || b.before&haltStatus != 0 { + t.Fatalf("core %d must be running: DHCSR=%#x: %v", index, b.before, err) + } + b.domain, err = b.memory.ReadWord(ctx, 0xe000ee08) + if err != nil || b.domain&(1<<16) == 0 { + t.Fatalf("core %d requires Secure counter state: DSCSR=%#x: %v", index, b.domain, err) + } +} + +func readInactiveCTI(t *testing.T, ctx context.Context, memory *dap.MemAP) []uint32 { + t.Helper() + var values []uint32 + for _, item := range []struct{ offset, want uint32 }{ + {0xfbc, 0x47701a14}, {0xfc8, 0x00040800}, + {0, 0}, {0x14, 0}, {0x134, 0}, {0x138, 0}, {0xf00, 0}, + } { + got, err := memory.ReadWord(ctx, 0xe0042000+item.offset) + if err != nil || got != item.want { + t.Fatalf("require inactive RP2350 CTI: offset=%#x value=%#x want=%#x: %v", item.offset, got, item.want, err) + } + values = append(values, got) + } + gate, err := memory.ReadWord(ctx, 0xe0042140) + if err != nil { + t.Fatal(err) + } + values = append(values, gate) + for i := uint32(0); i < 8; i++ { + for _, offset := range []uint32{0x20, 0xa0} { + got, err := memory.ReadWord(ctx, 0xe0042000+offset+i*4) + if err != nil || got != 0 { + t.Fatalf("require unrouted CTI: offset=%#x value=%#x: %v", offset+i*4, got, err) + } + values = append(values, got) + } + } + return values +} + +func exerciseDualCores(t *testing.T, ctx context.Context, cores *[2]dualCoreBench) { + t.Helper() + for i := range cores { + if err := cores[i].core.Halt(ctx); err != nil { + t.Fatal(err) + } + stopped := [2]bool{} + stopped[i] = true + checkDualCounters(t, ctx, cores, "one core halted", stopped) + checkDualCoreRegisters(t, ctx, &cores[i], i) + checkCounterStepAtHIL(t, ctx, cores[i].core, cores[i].memory, i, uint32(0x20040026+i*0x40), uint32(0x20040000+i*4)) + checkDualCounters(t, ctx, cores, "one core stepped", stopped) + if err := cores[i].core.Resume(ctx); err != nil { + t.Fatal(err) + } + checkDualCounters(t, ctx, cores, "both resumed", [2]bool{}) + } + for i := range cores { + if err := cores[i].core.Halt(ctx); err != nil { + t.Fatal(err) + } + } + checkDualCounters(t, ctx, cores, "both halted sequentially", [2]bool{true, true}) + if err := cores[0].core.Resume(ctx); err != nil { + t.Fatal(err) + } + checkDualCounters(t, ctx, cores, "only core 0 resumed", [2]bool{false, true}) + if err := cores[1].core.Release(ctx); err != nil { + t.Fatal(err) + } + checkDualCounters(t, ctx, cores, "core 1 released from halt", [2]bool{}) + if err := cores[0].core.Halt(ctx); err != nil { + t.Fatal(err) + } + if err := cores[0].core.Release(ctx); err != nil { + t.Fatal(err) + } + checkDualCounters(t, ctx, cores, "both released", [2]bool{}) +} + +func checkDualCoreRegisters(t *testing.T, ctx context.Context, b *dualCoreBench, index int) { + t.Helper() + saved := readRegistersHIL(t, ctx, b.core) + pc := saved[cortexm.PC] + start := uint32(0x20040026 + index*0x40) + if (pc != start && pc != start+2 && pc != start+4) || saved[cortexm.R1] != uint32(0x20040000+index*4) || saved[cortexm.XPSR]&0x010001ff != 0x01000000 { + t.Fatalf("core %d unexpected counter registers: PC=%#x R1=%#x XPSR=%#x", index, pc, saved[cortexm.R1], saved[cortexm.XPSR]) + } + t.Logf("core %d: all 19 registers read, PC=%#x R1=%#x", index, pc, saved[cortexm.R1]) +} + +func checkDualCounters(t *testing.T, ctx context.Context, cores *[2]dualCoreBench, phase string, stopped [2]bool) { + t.Helper() + var first, last [2]uint32 + var changed [2]bool + for n := range 11 { + if n != 0 { + select { + case <-ctx.Done(): + t.Fatal(ctx.Err()) + case <-time.After(20 * time.Millisecond): + } + } + for i := range cores { + value, err := cores[i].memory.ReadWord(ctx, uint32(0x20040000+i*4)) + if err != nil { + t.Fatal(err) + } + if n == 0 { + first[i] = value + } + last[i] = value + changed[i] = changed[i] || value != first[i] + } + } + t.Logf("%s: first=%#x last=%#x changed=%v stopped=%v", phase, first, last, changed, stopped) + for i := range cores { + if changed[i] == stopped[i] { + t.Fatalf("core %d changed=%v stopped=%v", i, changed[i], stopped[i]) + } + } +} + +func checkDualCoreRestored(t *testing.T, ctx context.Context, b *dualCoreBench, index int) { + t.Helper() + after, err := b.memory.ReadWord(ctx, dhcsr) + mask := debugEnable | haltStatus + if b.before&debugEnable != 0 { + mask |= 12 + } + if err != nil || after&mask != b.before&mask { + t.Fatalf("core %d DHCSR before=%#x after=%#x: %v", index, b.before, after, err) + } + domain, err := b.memory.ReadWord(ctx, 0xe000ee08) + if err != nil || domain != b.domain { + t.Fatalf("core %d DSCSR before=%#x after=%#x: %v", index, b.domain, domain, err) + } + for i, value := range readInactiveCTI(t, ctx, b.memory) { + if value != b.cti[i] { + t.Fatalf("core %d CTI snapshot changed", index) + } + } + t.Logf("core %d AP=%#x CPUID=0x411fd210 DHCSR before=%#x after=%#x DSCSR=%#x CTI unchanged (gate=%#x)", index, 0x2000+index*0x2000, b.before, after, domain, b.cti[7]) +} + +func releaseDualCoreBench(t *testing.T, cores *[2]dualCoreBench, c *armdebug.Conn) { + t.Helper() + pending := false + for i := len(cores) - 1; i >= 0; i-- { + var err error + for range 3 { + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + err = cores[i].core.Release(ctx) + cancel() + if err == nil { + break + } + } + if err != nil { + t.Errorf("core %d cleanup pending; retaining Arm owner: %v", i, err) + pending = true + } + } + if pending { + return + } + var err error + for range 3 { + if err = c.Close(); err == nil { + t.Log("both targets released and shared Arm debug owner closed") + return + } + } + t.Errorf("shared Arm debug owner cleanup remains pending: %v", err) +} diff --git a/target/cortexm/multicore_prepare_test.go b/target/cortexm/multicore_prepare_test.go new file mode 100644 index 0000000..f32596b --- /dev/null +++ b/target/cortexm/multicore_prepare_test.go @@ -0,0 +1,17 @@ +package cortexm_test + +import ( + "os/exec" + "testing" +) + +func TestRP2350PreparationGuards(t *testing.T) { + path, err := exec.LookPath("tclsh") + if err != nil { + t.Skip("tclsh is not installed") + } + cmd := exec.CommandContext(t.Context(), path, "testdata/rp2350-dual-counter/prepare_test.tcl") + if output, err := cmd.CombinedOutput(); err != nil { + t.Fatalf("preparation guard regression: %v\n%s", err, output) + } +} diff --git a/target/cortexm/testdata/rp2350-dual-counter/README.md b/target/cortexm/testdata/rp2350-dual-counter/README.md new file mode 100644 index 0000000..c92bd79 --- /dev/null +++ b/target/cortexm/testdata/rp2350-dual-counter/README.md @@ -0,0 +1,81 @@ +# RP2350 independent RAM counters + +Two Cortex-M33 loops increment separate words in shared SRAM. Core 0 uses +`0x20040000` with code at `0x20040020`; core 1 uses `0x20040004` with code at +`0x20040060`. Each loop adds one to R0, stores through R1, and branches back. +Neither loop uses a stack, peripherals, or DMA. Both run in Secure Thread mode +with configurable interrupts disabled. + +Build from the repository root: + +```sh +clang --target=arm-none-eabi -mcpu=cortex-m33 -mthumb -c \ + target/cortexm/testdata/rp2350-dual-counter/counter.S \ + -o /tmp/ostiole-rp2350-dual-counter.o +ld.lld -T target/cortexm/testdata/rp2350-dual-counter/counter.ld \ + /tmp/ostiole-rp2350-dual-counter.o -o /tmp/ostiole-rp2350-dual-counter.elf +arm-none-eabi-objcopy -O binary /tmp/ostiole-rp2350-dual-counter.elf \ + /tmp/ostiole-rp2350-dual-counter.bin +shasum -a 256 /tmp/ostiole-rp2350-dual-counter.bin +``` + +The binary SHA-256 is +`bf878b47815bc5eaf6afb5279efaa6ff163832bd178c5b7b1604f80e6ad6cde9`. + +## Preparation + +Stop other debuggers. The preparation script selects J-Link EDU Mini V2 +`000802011345` at 1 MHz and creates independent OpenOCD targets at ADIv6 AP +`0x2000` and `0x4000`. It requires both inherited CTIs to be disabled and +unrouted, with no pending output or input channel, integration mode disabled, +and the default channel gate `0x0f`. It rejects core 1 held in inherited reset. +Secure execution, Secure debugger register-bank selection, and Secure invasive +debug permission are required before preparation and again immediately before +the core-1 MPU write. A conflicting override is rejected without changing DSCSR. +No CTI configuration or acknowledgement is written. + +```sh +openocd -f target/cortexm/testdata/rp2350-dual-counter/prepare.cfg +``` + +Preparation resets core 1 through PSM FRCE_OFF.PROC1, using the atomic set/clear +aliases and preserving other reset bits. It then halts both cores, writes +`0x20040000`–`0x2004006f`, and compares all 28 words through both MEM-APs. This +verification reads memory on the host; it runs no checksum algorithm on either +processor. Core 1's boot-ROM MPU configuration prevents direct entry into this +RAM loop, so preparation disables its Secure MPU and sets its Secure MSP to +`0x20043000`. Both PCs, XPSRs, and interrupt masks are set before resume, then +halting debug is disabled. The loops overwrite R0/R1 and counter values. + +Flash, OTP, core-0 reset, and CTI routing are untouched. The previous programs, +RAM contents, core-1 reset effects and MPU control, PCs, XPSRs, registers, stack +pointer, and interrupt masks are not restored. Use a bench where replacing both +cores' volatile execution state is acceptable, with Secure invasive debug +permitted and no other processor or DMA writer using this SRAM. This prepares a +dedicated bench; Ostiole's HIL test does not perform these setup effects. + +## Validation + +Run the [independent-core test][dual-bench] after preparation. It requires the +program identity and explicit effect gate, verifies counter code and inactive +CTIs before acquiring target control, and opens one shared Arm debug owner per +session. Calls remain serialized. Each core can halt and step while its peer +advances. Both targets are released before the memory owner closes. + +The test restores inherited halting debug state. It leaves both RAM loops +running and does not undo their instruction effects or bench preparation. +Sequential stopping does not establish simultaneous halt or an atomic memory +snapshot. CTI propagation and acknowledgement require separate ownership. + +[dual-bench]: ../../../../docs/cortexm.md#rp2350-independent-core-bench + +Preparation guards can also be tested without a probe: + +```sh +tclsh target/cortexm/testdata/rp2350-dual-counter/prepare_test.tcl +``` + +The mocked OpenOCD commands exercise the actual script, including a Non-secure +bank override inherited before preparation or observed after core-1 reset, +permission loss, Non-secure execution, and inherited CTI routing. The Go test +runs this check when `tclsh` is installed; absence skips that fixture check. diff --git a/target/cortexm/testdata/rp2350-dual-counter/counter.S b/target/cortexm/testdata/rp2350-dual-counter/counter.S new file mode 100644 index 0000000..86de19f --- /dev/null +++ b/target/cortexm/testdata/rp2350-dual-counter/counter.S @@ -0,0 +1,24 @@ +.syntax unified +.cpu cortex-m33 +.thumb + +.section .counters, "aw", %progbits +.word 0, 0 + +.macro counter core, address +.section .text.core\core, "ax", %progbits +.thumb_func +.global counter_start\core +counter_start\core: + cpsid i + ldr r1, =\address + movs r0, #0 +counter_loop\core: + adds r0, #1 + str r0, [r1] + b counter_loop\core +.ltorg +.endm + +counter 0, 0x20040000 +counter 1, 0x20040004 diff --git a/target/cortexm/testdata/rp2350-dual-counter/counter.ld b/target/cortexm/testdata/rp2350-dual-counter/counter.ld new file mode 100644 index 0000000..27cab5e --- /dev/null +++ b/target/cortexm/testdata/rp2350-dual-counter/counter.ld @@ -0,0 +1,6 @@ +ENTRY(counter_start0) +SECTIONS { + .counters 0x20040000 : { *(.counters) } + .core0 0x20040020 : { *(.text.core0) } + .core1 0x20040060 : { *(.text.core1) } +} diff --git a/target/cortexm/testdata/rp2350-dual-counter/prepare.cfg b/target/cortexm/testdata/rp2350-dual-counter/prepare.cfg new file mode 100644 index 0000000..018557d --- /dev/null +++ b/target/cortexm/testdata/rp2350-dual-counter/prepare.cfg @@ -0,0 +1,102 @@ +source [find interface/jlink.cfg] +transport select swd +adapter serial 000802011345 +adapter speed 1000 +gdb_port disabled +tcl_port disabled +telnet_port disabled +swd newdap rp2350 cpu -expected-id 0x4c013477 +dap create rp2350.dap -chain-position rp2350.cpu -adiv6 +target create rp2350.core0 cortex_m -dap rp2350.dap -ap-num 0x2000 +target create rp2350.core1 cortex_m -dap rp2350.dap -ap-num 0x4000 + +proc require_inactive_cti {} { + foreach address {0xe0042000 0xe0042014 0xe0042134 0xe0042138 0xe0042f00} { + if {[lindex [read_memory $address 32 1] 0] != 0} { + error "Inherited CTI is active; refusing bench preparation" + } + } + for {set i 0} {$i < 8} {incr i} { + foreach base {0xe0042020 0xe00420a0} { + if {[lindex [read_memory [expr {$base + 4*$i}] 32 1] 0] != 0} { + error "Inherited CTI routing is present; refusing bench preparation" + } + } + } + if {[lindex [read_memory 0xe0042140 32 1] 0] != 15} { + error "Require default inherited CTI gate" + } + echo [mdw 0xe0042000] + echo [mdw 0xe0042140] + echo [mdw 0xe000ee08] +} + +proc require_secure_bank {} { + set domain [lindex [read_memory 0xe000ee08 32 1] 0] + if {($domain & 0x10000) == 0 || ($domain & 3) == 1} { + error "Require Secure execution and debugger bank selection" + } + if {([lindex [read_memory 0xe000edf0 32 1] 0] & 0x00100000) == 0} { + error "Require Secure invasive debug permission" + } +} + +init +foreach core {rp2350.core0 rp2350.core1} { + targets $core + require_inactive_cti + require_secure_bank +} +targets rp2350.core0 +if {([lindex [read_memory 0x40018004 32 1] 0] & 0x01000000) != 0} { + error "Core 1 is held in reset; refusing to release inherited reset" +} +mww 0x4001a004 0x01000000 +if {([lindex [read_memory 0x40018004 32 1] 0] & 0x01000000) == 0} { + error "Core 1 reset assertion unconfirmed" +} +mww 0x4001b004 0x01000000 +sleep 20 +foreach core {rp2350.core0 rp2350.core1} { + targets $core + halt +} +targets rp2350.core0 +load_image /tmp/ostiole-rp2350-dual-counter.bin 0x20040000 bin +set expected { + 0x00000000 0x00000000 0x00000000 0x00000000 + 0x00000000 0x00000000 0x00000000 0x00000000 + 0x4902b672 0x30012000 0xe7fc6008 0x20040000 + 0x00000000 0x00000000 0x00000000 0x00000000 + 0x00000000 0x00000000 0x00000000 0x00000000 + 0x00000000 0x00000000 0x00000000 0x00000000 + 0x4902b672 0x30012000 0xe7fc6008 0x20040004 +} +foreach core {rp2350.core0 rp2350.core1} { + targets $core + foreach got [read_memory 0x20040000 32 28] want $expected { + if {$got != $want} { + error "RAM image verification failed" + } + } +} +echo "Both RAM counter images loaded and verified through both MEM-APs" +foreach {core start} {rp2350.core0 0x20040020 rp2350.core1 0x20040060} { + targets $core + if {$core eq "rp2350.core1"} { + require_secure_bank + echo "Core 1 boot MPU control before RAM entry: [mdw 0xe000ed94]" + mww 0xe000ed94 0 + reg msp_s 0x20043000 + } + reg primask 1 + echo "Configured interrupt mask: [reg primask]" + echo "Inherited security control: [reg control_s]" + reg pc $start + reg xpsr 0x01000000 + resume + mww 0xe000edf0 0xa05f0000 + echo [mdw 0xe000edf0] +} +echo "Both counters running with halting debug disabled; CTI unchanged" +shutdown diff --git a/target/cortexm/testdata/rp2350-dual-counter/prepare_test.tcl b/target/cortexm/testdata/rp2350-dual-counter/prepare_test.tcl new file mode 100644 index 0000000..3c1b05a --- /dev/null +++ b/target/cortexm/testdata/rp2350-dual-counter/prepare_test.tcl @@ -0,0 +1,61 @@ +set fixture [file join [file dirname [info script]] prepare.cfg] + +proc run_case {name before after permission route should_fail} { + set mock [interp create] + $mock eval { + rename source real_source + proc source args {} + proc find args { return interface } + foreach command {transport adapter gdb_port tcl_port telnet_port swd dap target init sleep halt echo reg resume shutdown} { + proc $command args {} + } + set selected rp2350.core0 + set reset 0 + set reset_done 0 + set writes {} + set loaded 0 + proc targets {core} { set ::selected $core } + proc mdw args { return 0 } + proc mww {address value} { + lappend ::writes $address + if {$address == 0x4001a004} { set ::reset 0x01000000 } + if {$address == 0x4001b004} { set ::reset 0; set ::reset_done 1 } + } + proc load_image args { set ::loaded 1 } + proc verify_image args { error "Target checksum algorithms must not run" } + proc read_memory {address width count} { + if {$address == 0x20040000} { return $::expected } + if {$address == 0x40018004} { return [list $::reset] } + if {$address == 0xe000ee08} { + if {$::selected eq "rp2350.core0"} { return {0x30000} } + return [list [expr {$::reset_done ? $::after : $::before}]] + } + if {$address == 0xe000edf0} { return [list $::permission] } + if {$address == 0xe0042140} { return {15} } + if {$address == 0xe0042020 && $::selected eq "rp2350.core1"} { return [list $::route] } + return {0} + } + } + $mock eval [list set before $before] + $mock eval [list set after $after] + $mock eval [list set permission $permission] + $mock eval [list set route $route] + set failed [catch {$mock eval [list real_source $::fixture]} result] + set writes [$mock eval {set writes}] + interp delete $mock + if {$failed != $should_fail} { + error "$name: expected failure=$should_fail, got failure=$failed ($result)" + } + if {$should_fail && [lsearch -exact $writes 0xe000ed94] != -1} { + error "$name: wrote MPU before rejecting the conflicting state" + } + puts "$name: passed" +} + +run_case current-secure 0x30000 0x30000 0x100000 0 0 +run_case selected-secure 0x30003 0x30003 0x100000 0 0 +run_case selected-nonsecure 0x30001 0x30001 0x100000 0 1 +run_case override-after-reset 0x30000 0x30001 0x100000 0 1 +run_case nonsecure-execution 0x20000 0x20000 0x100000 0 1 +run_case permission-lost 0x30000 0x30000 0 0 1 +run_case inherited-route 0x30000 0x30000 0x100000 1 1