From d99ed011c546c9b4ef5947b88350159cbafe26d1 Mon Sep 17 00:00:00 2001 From: Jon Olson Date: Sun, 27 Sep 2026 14:21:02 -0700 Subject: [PATCH] Enforce canonical Markdown formatting. A line-length check cannot catch paragraphs stranded across short lines, and MD013's default mode lets a final word cross the configured limit. Require pinned Prettier formatting for tracked Markdown in CI and enable MD013's stern mode to enforce wrappable prose length. Wrap prose at 80 columns and normalize Markdown structure and LF line endings. Leave embedded code formatting disabled and preserve MD013's code-block, table, unbreakable-token, and link-only exemptions. Apply the format and use reference links where inline destinations prevent wrapping. Document the local commands while keeping PR paragraphs unwrapped. Parse reference definitions so wrapped destinations retain the same local-path and anchor checks as single-line definitions. --- .github/AGENTS.md | 4 +- .github/workflows/test.yml | 4 +- .markdownlint-cli2.yaml | 1 + .prettierrc.json | 6 + AGENTS.md | 101 +++-- CODE_OF_CONDUCT.md | 14 +- CONTRIBUTING.md | 229 +++++----- README.md | 184 ++++---- SECURITY.md | 6 +- docs/README.md | 16 +- docs/architecture.md | 523 +++++++++++----------- docs/capabilities.md | 473 ++++++++++--------- docs/composition.md | 493 ++++++++++---------- docs/coresight.md | 212 ++++----- docs/cortexm.md | 216 ++++----- docs/linux-usb.md | 14 +- docs/ports/dap.md | 445 +++++++++--------- docs/protocols/cmsisdap.md | 116 ++--- docs/protocols/jlink.md | 120 ++--- docs/protocols/jtag.md | 216 ++++----- docs/protocols/swd.md | 233 +++++----- examples/README.md | 29 +- ftdi/AGENTS.md | 13 +- internal/ci/checkpr/markdown.go | 13 +- internal/ci/checkpr/markdown_test.go | 37 ++ target/cortexm/testdata/counter/README.md | 26 +- 26 files changed, 1886 insertions(+), 1858 deletions(-) create mode 100644 .prettierrc.json diff --git a/.github/AGENTS.md b/.github/AGENTS.md index 657ae9f..14509ff 100644 --- a/.github/AGENTS.md +++ b/.github/AGENTS.md @@ -5,8 +5,8 @@ changes under `.github/`. ## Code Review Rules -- Grant the smallest explicit token permissions and pin every external Action - to a full commit SHA. +- Grant the smallest explicit token permissions and pin every external Action to + a full commit SHA. - Never expose repository secrets or write-capable tokens to untrusted pull request code. A `pull_request_target` workflow must not check out, source, evaluate, or execute the pull-request head or interpolate untrusted text into diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index a9cbf7c..e1664af 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -18,6 +18,7 @@ env: GOLANGCI_LINT_VERSION: v2.12.2 GOVULNCHECK_VERSION: v1.6.0 MARKDOWNLINT_VERSION: 0.23.2 + PRETTIER_VERSION: 3.9.9 STATICCHECK_VERSION: v0.7.0 jobs: @@ -138,13 +139,14 @@ jobs: golangci-lint run --config .golangci.yml ./... - name: Run test-code lint run: golangci-lint run --config .golangci.tests.yml ./... - - name: Lint tracked Markdown + - name: Check tracked Markdown formatting and lint shell: bash run: | markdown_files=() while IFS= read -r -d '' file; do markdown_files+=("$file") done < <(git ls-files -z '*.md') + npx --yes "prettier@${PRETTIER_VERSION}" --check "${markdown_files[@]}" npx --yes "markdownlint-cli2@${MARKDOWNLINT_VERSION}" -- "${markdown_files[@]}" - name: Test Codex review signal run: bash .github/scripts/codex-review-signal_test.sh diff --git a/.markdownlint-cli2.yaml b/.markdownlint-cli2.yaml index 55ecaf4..479f1b1 100644 --- a/.markdownlint-cli2.yaml +++ b/.markdownlint-cli2.yaml @@ -1,6 +1,7 @@ config: MD013: line_length: 80 + stern: true code_blocks: false tables: false MD041: false diff --git a/.prettierrc.json b/.prettierrc.json new file mode 100644 index 0000000..c1095a6 --- /dev/null +++ b/.prettierrc.json @@ -0,0 +1,6 @@ +{ + "printWidth": 80, + "proseWrap": "always", + "embeddedLanguageFormatting": "off", + "endOfLine": "lf" +} diff --git a/AGENTS.md b/AGENTS.md index dab3d45..2b1f49a 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,7 +1,7 @@ # AI Agent Guidelines -This file applies to coding agents enhancing Ostiole itself. Before changing -the repository, read and follow [`CONTRIBUTING.md`](CONTRIBUTING.md); its +This file applies to coding agents enhancing Ostiole itself. Before changing the +repository, read and follow [`CONTRIBUTING.md`](CONTRIBUTING.md); its contribution, style, testing, documentation, commit, and pull-request rules apply equally to humans and agents. @@ -14,16 +14,16 @@ downstream compositions. - Start by reading `docs/architecture.md` and `docs/capabilities.md` before adding or changing a public package, composition, example, or command. -- Work only within the approved task. Record useful discoveries for later - rather than implementing unrelated changes. -- Work test-first at every behavioral layer: add a test, run it and observe - the intended failure, implement the smallest change that makes it pass, and - then refactor while the test remains green. +- Work only within the approved task. Record useful discoveries for later rather + than implementing unrelated changes. +- Work test-first at every behavioral layer: add a test, run it and observe the + intended failure, implement the smallest change that makes it pass, and then + refactor while the test remains green. - Prefer deterministic behavioral fakes over canned protocol transcripts when testing hardware-independent behavior. -- Treat substantial example or command code as evidence that a reusable - library boundary may be missing. Add and test the smallest appropriate - public API before composing it into an executable. +- Treat substantial example or command code as evidence that a reusable library + boundary may be missing. Add and test the smallest appropriate public API + before composing it into an executable. - Never bypass an existing library layer by reproducing its USB, adapter, wire-protocol, DAP, or target framing in a test or application. - Keep private plans, donor history, agent activity, and unimplemented @@ -32,30 +32,29 @@ downstream compositions. ## Commit-size checkpoint -Approximately 200 added lines of non-test Go is the normal upper target for -one commit. Before forming each commit: +Approximately 200 added lines of non-test Go is the normal upper target for one +commit. Before forming each commit: -1. Format declarations and calls naturally, measure the proposed added - non-test Go, and report the count to the maintainer. +1. Format declarations and calls naturally, measure the proposed added non-test + Go, and report the count to the maintainer. 2. Above 200 lines, pause and present credible splits at independently useful - capability boundaries, including the approximate count and usefulness of - each resulting commit. -3. Keep each behavior together with its error handling, tests, and - documentation so the commit remains coherent, tested, documented, and - bisectable. + capability boundaries, including the approximate count and usefulness of each + resulting commit. +3. Keep each behavior together with its error handling, tests, and documentation + so the commit remains coherent, tested, documented, and bisectable. 4. Do not form a commit above 300 lines without explicit maintainer approval of the proposed unsplit boundary before the commit is created. A later handoff or pull-request explanation is not approval. 5. An exception request must identify the concrete coupling which prevents a coherent split. “The feature is cohesive” is not enough. -Do not manipulate formatting, create dead private seams, separate error -handling from the behavior it protects, or use mechanical movement to disguise -the count. Call out pure movement, generated code, and other unusual cases and -judge them by their review burden; none is an automatic exemption. Repeated -300–1,500-line exceptions indicate inadequate decomposition, not ordinary use -of the exception. In the final handoff, list every commit's added non-test Go -count and any approved exception. +Do not manipulate formatting, create dead private seams, separate error handling +from the behavior it protects, or use mechanical movement to disguise the count. +Call out pure movement, generated code, and other unusual cases and judge them +by their review burden; none is an automatic exemption. Repeated 300–1,500-line +exceptions indicate inadequate decomposition, not ordinary use of the exception. +In the final handoff, list every commit's added non-test Go count and any +approved exception. ## Code Review Rules @@ -64,12 +63,12 @@ count and any approved exception. - Report concrete, consequential defects rather than general praise or style preferences. Explain the failure mode and point to the narrowest relevant code. -- Check behavior, error paths, bounds, timeouts, cancellation, concurrency, - and cleanup. Trace resource ownership from USB through adapters, wire - protocols, DAP, targets, examples, and commands. -- Ensure deadlines and cancellation cover blocking host and protocol - operations without preventing bounded cleanup; cleanup must not depend on - an operation context that is already canceled. +- Check behavior, error paths, bounds, timeouts, cancellation, concurrency, and + cleanup. Trace resource ownership from USB through adapters, wire protocols, + DAP, targets, examples, and commands. +- Ensure deadlines and cancellation cover blocking host and protocol operations + without preventing bounded cleanup; cleanup must not depend on an operation + context that is already canceled. - Flag leaks, double ownership, discarded primary or cleanup errors, unsafe effects, and restoration that cannot be retried. Cleanup that may be retried must retain enough state to do so safely. @@ -87,12 +86,12 @@ count and any approved exception. control flow; do not suggest formatting tricks that influence lint or line counts. - Require focused deterministic tests for success, failure, cleanup, and retry - behavior at the owning package boundary. Keep production packages - independent of simulators and command-internal policy. + behavior at the owning package boundary. Keep production packages independent + of simulators and command-internal policy. - For integration tests, require the `integration` build tag, skip absent or ambiguous hardware before selection, open a selected adapter exactly once, - gate effectful operations explicitly, and restore volatile state with - bounded cleanup. + gate effectful operations explicitly, and restore volatile state with bounded + cleanup. When a change affects a public package, render and review its complete exported API rather than reading only the diff. Check whether distinct operations can @@ -107,15 +106,15 @@ traffic or cleanup, or bad input reaches hardware before it is rejected. Distinguish ordinary tests, behavioral simulation, CI compilation, and physical HIL; none is evidence for another. - Keep architecture ownership, cleanup, safety effects, composition guidance, - examples, commands, and capability tables consistent with code. For - Markdown changes, verify relative links, headings, commands, package names, - examples, and stated limitations against the current tree. + examples, commands, and capability tables consistent with code. For Markdown + changes, verify relative links, headings, commands, package names, examples, + and stated limitations against the current tree. - Require documentation in the same commit as exported API, ownership, lifecycle, safety, platform, composition, or validation-claim changes. -- Reject a pull request which adds an API without representative calls in - its opening description. When an API changes, require representative calls - before and after the change. Verify that every example preserves the real - ownership, cleanup, and safety rules and shows the actual migration. +- Reject a pull request which adds an API without representative calls in its + opening description. When an API changes, require representative calls before + and after the change. Verify that every example preserves the real ownership, + cleanup, and safety rules and shows the actual migration. - Reject pull-request prose paragraphs which are hard-wrapped in the Markdown source. Let GitHub wrap paragraphs for display; use source line breaks for lists, headings, and naturally formatted code blocks. @@ -123,14 +122,14 @@ traffic or cleanup, or bad input reaches hardware before it is rejected. prose. They remain required publication guards, not evidence to advertise. - Require physical HIL claims to identify the exercised path and bench. - When commit context is available, reject a nontrivial commit whose message - leaves the reviewer to reconstruct its purpose from the diff. The subject - must describe the resulting change. The body must say what was wrong or - missing beforehand, what the commit changes, and any important design or - safety choice which is not evident from the code. + leaves the reviewer to reconstruct its purpose from the diff. The subject must + describe the resulting change. The body must say what was wrong or missing + beforehand, what the commit changes, and any important design or safety choice + which is not evident from the code. - Judge each message against that commit, not the pull request as a whole. A - file inventory, list of implementation steps or tests, or review history - does not explain why a commit belongs in the history. + file inventory, list of implementation steps or tests, or review history does + not explain why a commit belongs in the history. - Flag unrelated changes, non-bisectable commits, missing same-commit tests or documentation, and artificial splits made only to influence line counts. -- Treat changes to contribution rules, agent review guidance, CODEOWNERS, - policy tooling, or workflows as security-sensitive review-policy changes. +- Treat changes to contribution rules, agent review guidance, CODEOWNERS, policy + tooling, or workflows as security-sensitive review-policy changes. diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index 6654301..f6248a2 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -1,7 +1,7 @@ # Code of Conduct -Ostiole should be a friendly place to build careful hardware tools together. -Be respectful, assume good faith, and give people room to learn. Disagree with +Ostiole should be a friendly place to build careful hardware tools together. Be +respectful, assume good faith, and give people room to learn. Disagree with ideas and code without attacking the person behind them. Harassment, threats, discrimination, deliberate humiliation, and sustained @@ -9,8 +9,8 @@ disruptive behavior are not welcome. Neither is using technical correctness as an excuse to be cruel. Please use common sense; this is a community standard, not a checklist whose gaps are loopholes. -If a conversation goes wrong, step back or contact -[Jon](mailto:jon@jon.dev) privately. Jon makes the final call about whether -conduct fits this project and may edit or remove comments, close a discussion, -reject a contribution, or limit participation when needed. Context and intent -matter, but maintaining a respectful community comes first. +If a conversation goes wrong, step back or contact [Jon](mailto:jon@jon.dev) +privately. Jon makes the final call about whether conduct fits this project and +may edit or remove comments, close a discussion, reject a contribution, or limit +participation when needed. Context and intent matter, but maintaining a +respectful community comes first. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 3d2896a..0f02326 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -2,16 +2,16 @@ Ostiole welcomes contributions from people and coding agents. Both follow the same repository standards. This document covers changes to Ostiole itself; -programs that consume the library should begin with [`README.md`](README.md) -and the user guides under [`docs/`](docs/). +programs that consume the library should begin with [`README.md`](README.md) and +the user guides under [`docs/`](docs/). Participation is also subject to the short, common-sense [`CODE_OF_CONDUCT.md`](CODE_OF_CONDUCT.md). Report suspected vulnerabilities privately as described in [`SECURITY.md`](SECURITY.md), not in a public issue. -Ostiole is an experimental hardware-access library. Small changes can affect -USB ownership, protocol state, and a connected target, so contributions should -be narrow, explicit, and tested at the layer that owns the behavior. +Ostiole is an experimental hardware-access library. Small changes can affect USB +ownership, protocol state, and a connected target, so contributions should be +narrow, explicit, and tested at the layer that owns the behavior. ## Acceptance criteria @@ -20,60 +20,60 @@ Before acceptance, a pull request must: - tell one coherent story without unrelated changes; - include appropriate tests for its behavior and regression surface; - keep public documentation and capability claims consistent with the code; -- preserve supported Linux and macOS behavior, or introduce a new host with - its implementation, tests, CI coverage, and documentation; +- preserve supported Linux and macOS behavior, or introduce a new host with its + implementation, tests, CI coverage, and documentation; - use idiomatic, maintainable code at the layer that owns the behavior; and - pass the applicable software, simulation, and hardware validation described below without overstating what was exercised. ## Change and commit shape -- Prefer small commits that introduce one coherent capability together with - its tests and applicable documentation. -- Treat roughly 200 added lines of non-test Go as a signal to consider whether - a commit has a natural, independently useful split. It is a guideline, not - an acceptance limit; keep tightly coupled behavior together when splitting - it would make the history less clear. +- Prefer small commits that introduce one coherent capability together with its + tests and applicable documentation. +- Treat roughly 200 added lines of non-test Go as a signal to consider whether a + commit has a natural, independently useful split. It is a guideline, not an + acceptance limit; keep tightly coupled behavior together when splitting it + would make the history less clear. - Pure movement and formatting do not require artificial splits, but must not conceal functional changes. - Keep production functions within the repository lint limits: cognitive - complexity 20, cyclomatic complexity 12, 60 lines, 40 statements, and - nesting depth 5. + complexity 20, cyclomatic complexity 12, 60 lines, 40 statements, and nesting + depth 5. ## Go style - Write idiomatic Go and run `gofmt`. Formatting mechanically accepted by `gofmt` is not automatically readable. - Keep ordinary declarations, signatures, and calls compact when they fit - naturally. Break lines to clarify structure, not to place every parameter - or argument on its own line or to influence a line-count limit. -- Use conventional short receiver names and names proportional to their - scope. Do not lengthen familiar local names merely to make them descriptive - in isolation. -- Use normal multiline composite literals when a literal no longer reads - clearly on one line. + naturally. Break lines to clarify structure, not to place every parameter or + argument on its own line or to influence a line-count limit. +- Use conventional short receiver names and names proportional to their scope. + Do not lengthen familiar local names merely to make them descriptive in + isolation. +- Use normal multiline composite literals when a literal no longer reads clearly + on one line. - Prefer straightforward control flow. If a multiline call makes an `if` initializer awkward, assign its result first and test it separately. - Keep reusable hardware, protocol, lifecycle, restoration, inspection, and target behavior in the appropriate public package. -- Keep `cmd//main.go` thin. Commands own arguments, user-facing - selection and defaults, output, exit status, and composition—not a parallel - hardware stack. -- Keep examples compact demonstrations of public APIs. Do not duplicate - framing already owned by a library package. +- Keep `cmd//main.go` thin. Commands own arguments, user-facing selection + and defaults, output, exit status, and composition—not a parallel hardware + stack. +- Keep examples compact demonstrations of public APIs. Do not duplicate framing + already owned by a library package. ## Public API design - Expose the meaning owned by a package, not an incidental encoding from the layer below it. Distinct operations remain distinct even when they share a wire value. -- Put every fact needed to interpret a value in its type or owning object. - This includes direction, bank, response mode, device or register class, and +- Put every fact needed to interpret a value in its type or owning object. This + includes direction, bank, response mode, device or register class, and resource ownership. - Prefer immutable constants and opaque constructed values for public vocabulary. Exported sentinel errors are the ordinary exception. -- Make zero values harmless or invalid. A zero value must never silently - select an effectful operation. +- Make zero values harmless or invalid. A zero value must never silently select + an effectful operation. - Do not ask callers to repeat state which the callee already owns or can derive. State transitions which must succeed together belong to one owning operation. @@ -82,12 +82,12 @@ Before acceptance, a pull request must: - Name acquisition, effects, restoration, and cleanup plainly. Do not hide target traffic or cleanup obligations behind a constructor which appears to allocate a value. -- Do not export an enum or abstraction with one concrete choice in case a - second choice appears later. +- Do not export an enum or abstraction with one concrete choice in case a second + choice appears later. - Reject invalid input before USB, adapter, wire, debug-port, access-port, or target traffic. -- Before v1, replace a misleading API instead of preserving parallel old and - new vocabularies through aliases or compatibility shims. +- Before v1, replace a misleading API instead of preserving parallel old and new + vocabularies through aliases or compatibility shims. ## Tests and hardware safety @@ -119,49 +119,48 @@ golangci-lint run --config .golangci.yml ./... golangci-lint run --config .golangci.tests.yml ./... ``` -Pull requests divide automated feedback into deterministic failures and -review judgments. The `policy` check rejects merge commits, malformed commit -subjects, Conventional Commit prefixes, subjects longer than 120 columns, -missing body separators, clearly wrappable body prose longer than 72 columns, -incomplete pull-request metadata, and broken local Markdown links or anchors. -The `commits` check runs formatting, build, vet, and race tests independently -at every commit. The `quality` and macOS checks validate the final tip with -the additional linters, vulnerability scan, integration-tag compilation, and -native C checks applicable to their hosts. The `CodeQL` workflow analyzes Go -and native C/C++ with the security-extended query suite on every pull request, -including pull requests from forks. - -Policy annotations remain advisory when judgment is required. These include -73- through 120-column subjects, ambiguous imperative mood, weak bodies, -commits near the 200-line review checkpoint, likely missing tests or docs, -mixed capabilities, and changes to review-policy files. Codex and the -maintainer assess correctness, ownership, cleanup, hardware safety, -architecture, behavioral coverage, and documentation claims. A completed -standard Codex review must match the current pull-request head. Codex must -reject a nontrivial commit whose message does not explain why the commit is -needed and what behavior it establishes at that boundary. Codex must also -reject a pull request which adds an API without representative calls in its -opening description, or changes an API without representative calls before and after -the change. It must also reject examples which hide ownership, cleanup, safety, -or migration details needed to understand ordinary use, and prose paragraphs -which are hard-wrapped in the Markdown source. On the final head, comment -`@codex review`; a clean review may instead complete with a SHA-labeled Codex -result comment. Automatic review begins when a pull request becomes ready, and -the connector reacts to the pull request with a thumbs-up after a clean review. -The gate accepts a SHA-labeled clean result, or a completed Code Review row -whose displayed commit resolves to the exact head together with the -connector's current thumbs-up. After a head change, the thumbs-up must also be -created after the earliest GitHub Actions run for that pull request and head. -A formal review with suggestions does not satisfy the gate. A Codex opinion -does not count as an approval. Resolve its actionable conversations or explain -the disposition before merging. -If Codex completes after the gate's polling window, rerun the failed +Pull requests divide automated feedback into deterministic failures and review +judgments. The `policy` check rejects merge commits, malformed commit subjects, +Conventional Commit prefixes, subjects longer than 120 columns, missing body +separators, clearly wrappable body prose longer than 72 columns, incomplete +pull-request metadata, and broken local Markdown links or anchors. The `commits` +check runs formatting, build, vet, and race tests independently at every commit. +The `quality` and macOS checks validate the final tip with the additional +linters, vulnerability scan, integration-tag compilation, and native C checks +applicable to their hosts. The `CodeQL` workflow analyzes Go and native C/C++ +with the security-extended query suite on every pull request, including pull +requests from forks. + +Policy annotations remain advisory when judgment is required. These include 73- +through 120-column subjects, ambiguous imperative mood, weak bodies, commits +near the 200-line review checkpoint, likely missing tests or docs, mixed +capabilities, and changes to review-policy files. Codex and the maintainer +assess correctness, ownership, cleanup, hardware safety, architecture, +behavioral coverage, and documentation claims. A completed standard Codex review +must match the current pull-request head. Codex must reject a nontrivial commit +whose message does not explain why the commit is needed and what behavior it +establishes at that boundary. Codex must also reject a pull request which adds +an API without representative calls in its opening description, or changes an +API without representative calls before and after the change. It must also +reject examples which hide ownership, cleanup, safety, or migration details +needed to understand ordinary use, and prose paragraphs which are hard-wrapped +in the Markdown source. On the final head, comment `@codex review`; a clean +review may instead complete with a SHA-labeled Codex result comment. Automatic +review begins when a pull request becomes ready, and the connector reacts to the +pull request with a thumbs-up after a clean review. The gate accepts a +SHA-labeled clean result, or a completed Code Review row whose displayed commit +resolves to the exact head together with the connector's current thumbs-up. +After a head change, the thumbs-up must also be created after the earliest +GitHub Actions run for that pull request and head. A formal review with +suggestions does not satisfy the gate. A Codex opinion does not count as an +approval. Resolve its actionable conversations or explain the disposition before +merging. If Codex completes after the gate's polling window, rerun the failed `codex-reviewed` job to evaluate the completed result. Untrusted pull-request workflows receive no secrets or write-capable checkout -credentials and never run HIL. CodeQL uses the ordinary `pull_request` event -and GitHub's built-in token; it does not use `pull_request_target` or a -maintainer credential. +credentials and never run HIL. CodeQL uses the ordinary `pull_request` event and +GitHub's built-in token; it does not use `pull_request_target` or a maintainer +credential. When changing the native macOS USB bridge, also verify its formatting and warning-clean C build with the commands used by `.github/workflows/test.yml`. @@ -174,10 +173,9 @@ Begin with the resulting behavior and scope, without an opening heading. Headings, separators, and empty blocks do not satisfy the description requirement. Existing descriptions may retain “What this does” as the first section heading. For every new API, show representative calls which make -ordinary use concrete. For every changed API, show representative calls -before and after the change so that the migration is visible. The examples -must preserve the same ownership, cleanup, and safety rules as ordinary -code. +ordinary use concrete. For every changed API, show representative calls before +and after the change so that the migration is visible. The examples must +preserve the same ownership, cleanup, and safety rules as ordinary code. Leave each prose paragraph on one line in the Markdown source and let GitHub wrap it for display. Do not insert source line breaks merely to meet a column @@ -189,22 +187,34 @@ public material changed or explain why the existing documentation remains enough. “Hardware evidence” is optional. Include it for physical HIL, a manual bench -experiment, or other evidence which GitHub cannot reproduce. Name the command -or procedure, the bench, what happened, whether state was restored, and what -the result does not establish. Do not list or claim routine format, build, -test, lint, policy, vulnerability, or compilation checks which GitHub reports +experiment, or other evidence which GitHub cannot reproduce. Name the command or +procedure, the bench, what happened, whether state was restored, and what the +result does not establish. Do not list or claim routine format, build, test, +lint, policy, vulnerability, or compilation checks which GitHub reports independently. ## Documentation -Wrap repository Markdown prose at 80 columns. Code blocks and tables keep -their own layout. This source-file rule does not apply to pull-request -descriptions, whose paragraphs stay on one line. +Format tracked Markdown with Prettier 3.9.9 using the repository configuration. +It wraps prose at 80 columns and standardizes headings, lists, tables, spacing, +and LF line endings. Embedded code formatting is disabled; Go remains governed +by `gofmt`. Format and check the tracked Markdown from the repository root: -Every pull request must leave the public documentation consistent with the -code. A pull request that does not change public behavior may need no -documentation edit, but its author must still verify that the existing claims -remain true. +```sh +git ls-files -z '*.md' | xargs -0 npx --yes prettier@3.9.9 --write +git ls-files -z '*.md' | xargs -0 npx --yes prettier@3.9.9 --check +``` + +CI checks this formatting and runs Markdownlint. Its MD013 line-length check +uses `stern` mode, so a final word cannot extend beyond 80 columns. Code blocks +and tables are excluded from that line-length check; unbreakable tokens and +link-only lines retain MD013's exemptions. Use reference links when a long +inline destination prevents wrapping. These source-file rules do not apply to +pull-request descriptions, whose paragraphs stay on one line. + +Every pull request must leave the public documentation consistent with the code. +A pull request that does not change public behavior may need no documentation +edit, but its author must still verify that the existing claims remain true. Update the relevant documentation in the same commit when changing: @@ -238,12 +248,12 @@ Examples: - `Release debug-port power ownership.` After the summary, leave a blank line. Truly trivial commits may stop there. -Otherwise, use a short body which lets someone reviewing that commit -understand why it belongs in the history. Name the previous limitation or -failure, say what a caller or maintainer can rely on afterward, and explain -any important design or safety choice which is not evident from the diff. -Include evidence when it supports a claim; a list of tests does not substitute -for the reason for the change. +Otherwise, use a short body which lets someone reviewing that commit understand +why it belongs in the history. Name the previous limitation or failure, say what +a caller or maintainer can rely on afterward, and explain any important design +or safety choice which is not evident from the diff. Include evidence when it +supports a claim; a list of tests does not substitute for the reason for the +change. Write about the state before and after that commit, not the eventual pull request or the process used to produce it. Do not merely enumerate files, @@ -270,21 +280,20 @@ timeless and omit internal planning, construction history, and agent activity. - Each pull request should tell one coherent functional story and contain only commits that belong to that story. - Treat commits on an open pull-request branch as working review history. - Rewrite them when doing so folds a correction into the commit that - introduced it, restores atomicity, or makes the final sequence easier to - understand. Review feedback does not require a permanent fixup commit. -- Before rewriting a pull-request branch, fetch it and confirm that nobody - else has advanced it. Update the remote with `git push --force-with-lease`, - never an unconditional force push, and coordinate with anyone building on - that branch. -- Once a commit has been merged into `main` or included in a release tag, - treat it as immutable. Never rewrite or force-push merged branches or tags. + Rewrite them when doing so folds a correction into the commit that introduced + it, restores atomicity, or makes the final sequence easier to understand. + Review feedback does not require a permanent fixup commit. +- Before rewriting a pull-request branch, fetch it and confirm that nobody else + has advanced it. Update the remote with `git push --force-with-lease`, never + an unconditional force push, and coordinate with anyone building on that + branch. +- Once a commit has been merged into `main` or included in a release tag, treat + it as immutable. Never rewrite or force-push merged branches or tags. - Make the first pull-request push coherent and ready for review, then refine - its history deliberately as review reveals necessary changes. Avoid - gratuitous churn that invalidates review context without improving the - series. + its history deliberately as review reveals necessary changes. Avoid gratuitous + churn that invalidates review context without improving the series. -Before requesting review, confirm that every commit builds and tests on its -own, the final worktree is clean, the documentation matches the code, and the +Before requesting review, confirm that every commit builds and tests on its own, +the final worktree is clean, the documentation matches the code, and the pull-request description reports any evidence GitHub cannot reproduce without overstating support. diff --git a/README.md b/README.md index 35ad837..4fee1b2 100644 --- a/README.md +++ b/README.md @@ -9,19 +9,19 @@ Applications can use only the layers they need and explicitly assemble them. Ostiole is a library rather than a packaged debugger. A program can embed firmware, FPGA bitstreams, runtime code, or other payloads and communicate with -supported hardware directly, without requiring users to install additional -tools or libraries. +supported hardware directly, without requiring users to install additional tools +or libraries. An ostiole is a small opening or pore. The name reflects the library's intended role as a narrow, controlled way to observe and interact with another system. ## Motivation -Many development boards expose programming or debugging facilities through -USB, whether through an external probe or an adapter built into the board. -Using those facilities often requires users to install a separate suite of -tools, locate the correct adapter configuration, and reproduce a particular -command or script. +Many development boards expose programming or debugging facilities through USB, +whether through an external probe or an adapter built into the board. Using +those facilities often requires users to install a separate suite of tools, +locate the correct adapter configuration, and reproduce a particular command or +script. Ostiole is intended to make another style of tool possible: a Go program that contains the operation, its hardware support, and any payload it needs. Such a @@ -30,8 +30,8 @@ target, inspect a system, or provide a project-specific recovery utility. ## Status -Ostiole is at an early, exploratory stage. The available packages provide -native Linux and macOS USB access, an explicitly configured FTDI MPSSE path, +Ostiole is at an early, exploratory stage. The available packages provide native +Linux and macOS USB access, an explicitly configured FTDI MPSSE path, descriptor-selected J-Link SWD/JTAG and CMSIS-DAP v2 SWD sessions, and conservative raw Serial Wire Debug transactions without automatic retries. @@ -40,37 +40,36 @@ protocol entry and basic DP/AP register transfers against a caller-supplied target. The `dap` package begins the next layer with ADIv5 debug-port identity, raw -SW-DP registers, and an explicit connection lifecycle. `NewAPSel` constructs -an access-port selector whose zero value is invalid. A connection clears -sticky status, selects the base register bank, and restores only the power -requests it acquired. `DebugPort.Connect` performs SWD entry; later DP, AP, -transaction, and MEM-AP operations require that active connection. -`ReadAPIDR` reads and decodes AP identity. For raw access, `APSel.Address` -combines a selector with the complete eight-bit register address. Posted reads -and writes complete through `RDBUFF`. The simulator models the same DP and AP -state changes. Raw access has the effects defined by the selected AP class; -writing a MEM-AP data register can write target memory. Any raw access which -completes or might have completed invalidates existing `MemAP` values. -`dap.DebugPort` retries only the physical request that returned WAIT. The -one-argument `NewDebugPort` keeps retrying while the operation context remains -active; `WithMaxWaits(1)` instead returns the first clean WAIT as `swd.ErrWait`. -`SetMaxWaits` can change the limit while the port is idle. The count does not -bound host I/O, so callers which need that bound still use a context deadline. -A FAULT ends the operation. If a limit or context ends AP waiting, the debug -port issues DAPABORT rather than replaying the whole logical access. -A MEM-AP client and its model can perform arbitrary-range and aligned-scalar -reads and writes, then restore the CSW, TAR, and optional TARHI values changed -by that access. Cleanup terminates an incomplete 64-bit transfer through CSW -before restoring the target address registers. An accepted write is not -replayed; if its RDBUFF completion request returns WAIT, only that request is -retried. If the MEM-AP does not accept single address increment, block access -writes TAR before each word instead. - -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. It also provides acquired Cortex-M0 +SW-DP registers, and an explicit connection lifecycle. `NewAPSel` constructs an +access-port selector whose zero value is invalid. A connection clears sticky +status, selects the base register bank, and restores only the power requests it +acquired. `DebugPort.Connect` performs SWD entry; later DP, AP, transaction, and +MEM-AP operations require that active connection. `ReadAPIDR` reads and decodes +AP identity. For raw access, `APSel.Address` combines a selector with the +complete eight-bit register address. Posted reads and writes complete through +`RDBUFF`. The simulator models the same DP and AP state changes. Raw access has +the effects defined by the selected AP class; writing a MEM-AP data register can +write target memory. Any raw access which completes or might have completed +invalidates existing `MemAP` values. `dap.DebugPort` retries only the physical +request that returned WAIT. The one-argument `NewDebugPort` keeps retrying while +the operation context remains active; `WithMaxWaits(1)` instead returns the +first clean WAIT as `swd.ErrWait`. `SetMaxWaits` can change the limit while the +port is idle. The count does not bound host I/O, so callers which need that +bound still use a context deadline. A FAULT ends the operation. If a limit or +context ends AP waiting, the debug port issues DAPABORT rather than replaying +the whole logical access. A MEM-AP client and its model can perform +arbitrary-range and aligned-scalar reads and writes, then restore the CSW, TAR, +and optional TARHI values changed by that access. Cleanup terminates an +incomplete 64-bit transfer through CSW before restoring the target address +registers. An accepted write is not replayed; if its RDBUFF completion request +returns WAIT, only that request is retried. If the MEM-AP does not accept single +address increment, block access writes TAR before each word instead. + +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. It also provides acquired Cortex-M0 halt/resume control, stepping, and halted register access over word memory; see [Cortex-M control](docs/cortexm.md). @@ -80,18 +79,18 @@ selects its application interface from the active USB descriptors. ## Design direction -Probe discovery and ownership are separate. Generic tools can import -`discover` and blank-import `discover/probes` to enable all bundled providers, -then call `discover.OpenProbe(ctx, selection)`. A tool needing fewer drivers can -import their individual discovery packages instead. `probe` owns the selected +Probe discovery and ownership are separate. Generic tools can import `discover` +and blank-import `discover/probes` to enable all bundled providers, then call +`discover.OpenProbe(ctx, selection)`. A tool needing fewer drivers can import +their individual discovery packages instead. `probe` owns the selected implementation and lends SWD; it does not enumerate hardware or own DAP state. The [composition guide](docs/composition.md#select-and-open-hardware-explicitly) shows the combined and explicit paths, including cleanup obligations. `armdebug.Open(ctx, selection, config)` owns an Arm debug connection through that probe. `Config.Port` selects an SW-DP or an explicit JTAG-DP chain/TAP; -`Conn.OpenMemAP` acquires selected memory APs whose restoration is included -in `Conn.Close`. The generic `examples/simple/arm-info` tool demonstrates it. +`Conn.OpenMemAP` acquires selected memory APs whose restoration is included in +`Conn.Close`. The generic `examples/simple/arm-info` tool demonstrates it. Ostiole keeps the major hardware-access layers separate: @@ -103,64 +102,63 @@ Low-level packages expose mechanisms rather than guessing policy. Hardware is selected explicitly, protocol state belongs to one logical owner, and higher-level code should not duplicate framing implemented by lower layers. -The initial implementation favors a small, understandable path over broad -device support. Additional transports, adapters, protocols, and targets can be +The initial implementation favors a small, understandable path over broad device +support. Additional transports, adapters, protocols, and targets can be introduced as independent pieces once their behavior is specified and tested. ## Documentation -The [documentation](docs) describes the current package architecture, -ownership rules, composition paths, capability boundaries, and safety effects. -It is written for people and tools building applications with Ostiole. +The [documentation](docs) describes the current package architecture, ownership +rules, composition paths, capability boundaries, and safety effects. It is +written for people and tools building applications with Ostiole. People and coding agents changing Ostiole itself should follow the shared [contribution guide](CONTRIBUTING.md). ## Requirements -Ostiole requires Go 1.25.12 or newer. On macOS 12 or newer, install Xcode or -the Xcode command-line tools. The native USB implementation uses cgo to call -the system IOKit and CoreFoundation frameworks; it does not require libusb or -a third-party USB package. +Ostiole requires Go 1.25.12 or newer. On macOS 12 or newer, install Xcode or the +Xcode command-line tools. The native USB implementation uses cgo to call the +system IOKit and CoreFoundation frameworks; it does not require libusb or a +third-party USB package. -On Linux, grant the interactive user access to the exact USB product and -release any kernel driver bound to the interface before running a hardware -command. Never work around device permissions by running repository code as -root. See [Linux USB access](docs/linux-usb.md) for udev rules and a bounded -`ftdi_sio` release and restoration procedure. +On Linux, grant the interactive user access to the exact USB product and release +any kernel driver bound to the interface before running a hardware command. +Never work around device permissions by running repository code as root. See +[Linux USB access](docs/linux-usb.md) for udev rules and a bounded `ftdi_sio` +release and restoration procedure. ## Safety Debug and programming interfaces can reset processors, halt execution, modify -memory, reconfigure programmable logic, and change persistent device state. -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. +memory, reconfigure programmable logic, and change persistent device state. 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, halts a Cortex-M0, reads PC, SP, R0, and R4, then resumes -it. Add `-step` to perform one architectural step before resume. 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. +it. Add `-step` to perform one architectural step before resume. 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 1 MHz. Connect it to a powered SWD target as follows: -| Adapter signal | Target signal | -| --- | --- | -| D0 | SWCLK | -| D1 through a 1 kΩ series resistor | SWDIO | -| D2 | SWDIO | -| GND | GND | +| Adapter signal | Target signal | +| --------------------------------- | ------------- | +| D0 | SWCLK | +| D1 through a 1 kΩ series resistor | SWDIO | +| D2 | SWDIO | +| GND | GND | -The target supplies its own power. No reset or target-power connection is -used. After configuring [Linux USB access](docs/linux-usb.md), when applicable, -run: +The target supplies its own power. No reset or target-power connection is used. +After configuring [Linux USB access](docs/linux-usb.md), when applicable, run: ```sh go run ./examples/trivial/swd-dpidr @@ -168,12 +166,12 @@ go run ./examples/trivial/swd-dpidr This path is validated with an FT232H (`0403:6014`) on Linux and macOS. On Linux, the current implementation requires an explicit driver release and -restoration. On macOS, claiming the device temporarily seizes its USB -interface from the Apple FTDI driver; closing it releases that ownership. +restoration. On macOS, claiming the device temporarily seizes its USB interface +from the Apple FTDI driver; closing it releases that ownership. A successful read prints only the debug-port identity, for example -`DPIDR=0x2ba01477`. The operation does not halt or reset the target and does -not write target memory. +`DPIDR=0x2ba01477`. The operation does not halt or reset the target and does not +write target memory. Maintainers can exercise the same public-library path as an opt-in hardware test: @@ -192,16 +190,15 @@ OSTIOLE_FT232H_DARWIN_HIL=1 \ ``` This test selects exactly one FT232H (`0403:6014`), opens it once, and performs -only MPSSE setup and synchronization on port A at 400 kHz. It does not -construct an SWD connection, clock JTAG, reset a target, or access downstream -devices. +only MPSSE setup and synchronization on port A at 400 kHz. It does not construct +an SWD connection, clock JTAG, reset a target, or access downstream devices. ## Access-port identity example The simple access-port example uses the same wiring and selects AP0 explicitly. -It connects through the DAP layer, reads the access-port identification -register through the posted-read pipeline, and releases the power requests it -acquired. Run: +It connects through the DAP layer, reads the access-port identification register +through the posted-read pipeline, and releases the power requests it acquired. +Run: ```sh go run ./examples/simple/ap-id @@ -227,15 +224,14 @@ go run ./examples/simple/cortexm-info ``` A successful read prints the debug port, access port, and processor identities, -for example `DPIDR=0x2ba01477 AP0_IDR=0x24770011 CPUID=0x410fc241`. -The operation restores CSW, TAR, DP selection, and acquired power state. It -does not reset or halt the target or write target memory. +for example `DPIDR=0x2ba01477 AP0_IDR=0x24770011 CPUID=0x410fc241`. The +operation restores CSW, TAR, DP selection, and acquired power state. It does not +reset or halt the target or write target memory. ## Command -`ost` is a small command-line companion built from the same public packages -used by the examples. It can list supported FTDI attachments without opening -them: +`ost` is a small command-line companion built from the same public packages used +by the examples. It can list supported FTDI attachments without opening them: ```sh go run ./cmd/ost ftdi list diff --git a/SECURITY.md b/SECURITY.md index f3978f6..9618f75 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -7,9 +7,9 @@ private vulnerability-reporting form when it is available. Otherwise, email [jon@jon.dev](mailto:jon@jon.dev) with the affected revision, enough detail to reproduce or understand the issue, and any relevant host or hardware context. -Do not include live credentials, private keys, or other people's sensitive -data in a report. Jon will coordinate disclosure and any necessary fix with -the reporter. +Do not include live credentials, private keys, or other people's sensitive data +in a report. Jon will coordinate disclosure and any necessary fix with the +reporter. ## Supported versions diff --git a/docs/README.md b/docs/README.md index c317672..6cae771 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,11 +1,11 @@ # Documentation Ostiole is a collection of composable Go packages rather than a complete -debugger application. These guides explain the packages that are available -today and how to assemble them without duplicating lower-level behavior. +debugger application. These guides explain the packages that are available today +and how to assemble them without duplicating lower-level behavior. -- [Architecture](architecture.md) describes package responsibilities, - ownership, cleanup, and safety effects. +- [Architecture](architecture.md) describes package responsibilities, ownership, + cleanup, and safety effects. - [Serial Wire Debug](protocols/swd.md) describes the wire transaction, the parts of the specification which are easy to misread, and the current bench result. @@ -25,10 +25,10 @@ today and how to assemble them without duplicating lower-level behavior. - [Capabilities](capabilities.md) distinguishes implemented behavior from simulated, CI-tested, and HIL-validated configurations and explicit limitations. -- [Linux USB access](linux-usb.md) explains unprivileged device permissions - and bounded release and restoration of a bound FTDI kernel interface. +- [Linux USB access](linux-usb.md) explains unprivileged device permissions and + bounded release and restoration of a bound FTDI kernel interface. - [Examples](../examples) contains executable compositions that progress from direct protocol demonstrations to focused inspection tools. -The documentation describes the current tree. It does not promise future -probe, protocol, target, or application support. +The documentation describes the current tree. It does not promise future probe, +protocol, target, or application support. diff --git a/docs/architecture.md b/docs/architecture.md index 7d18883..abc691e 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -1,8 +1,8 @@ # Architecture Ostiole separates host access, adapter behavior, wire protocols, debug-port -policy, and target-specific operations. Each package owns one kind of state -and exposes the smallest useful mechanism to the layer above it. +policy, and target-specific operations. Each package owns one kind of state and +exposes the smallest useful mechanism to the layer above it. The current hardware paths are: @@ -30,42 +30,42 @@ USB host access ``` Programs can stop at any layer. Reading a raw SWD register does not require a -MEM-AP or target package, and identifying a Cortex-M does not require a -debugger service. +MEM-AP or target package, and identifying a Cortex-M does not require a debugger +service. ## Package responsibilities -| Package | Responsibility | -| --- | --- | -| `usb` | Enumerate, open, inspect the active standard configuration, claim, transfer through, and close one host USB attachment, including explicit asynchronous bulk transfers. | -| `probe` | Own one supplied implementation and lend its supported protocol surfaces; the implementation retains its transport and cleanup obligations. | -| `armdebug` | Own a probe, its connected SW-DP or baseline JTAG-DP, and explicitly acquired MEM-APs; restore APs before releasing DAP and its connection or chain, then close the probe. | -| `discover` | Enumerate registered transports, classify probe bindings, and select one candidate without owning an open probe. | -| `discover/probes` | Register all bundled probe providers for generic tools. | -| `ftdi` | Own one explicitly selected FTDI MPSSE port and expose direction-safe SWD bits or packed JTAG clocks. | -| `jlink` | Find one reviewed J-Link USB application interface, own its command session, configure SWD or JTAG, and adapt scan v3 to the selected wire protocol. | -| `cmsisdap` | Shortlist CMSIS-DAP product strings, validate one explicitly selected v2 bulk interface, own its command session, configure SWD, and adapt packet-bounded sequence commands to direction-explicit SWD bits. | -| `swd` | Enter SWD, establish its response grammar, and encode, execute, and validate individual or packed DP/AP register transactions. | -| `jtag` | Track TAP state, scan registers, discover reset entries, measure IR length, and validate explicit chains for selected-TAP access over a caller-supplied wire. | -| `swd/sim` | Model SWD protocol entry, register transfers, fixed-frame packing, and transfer limits without hardware. | -| `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` | Identify Cortex-M processors and own Cortex-M0 halting debug and register access over borrowed word memory. | -| `examples/...` | Demonstrate public package compositions as executable programs. | -| `cmd/ost` | Provide a small command hierarchy over the same public packages. | +| Package | Responsibility | +| ----------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `usb` | Enumerate, open, inspect the active standard configuration, claim, transfer through, and close one host USB attachment, including explicit asynchronous bulk transfers. | +| `probe` | Own one supplied implementation and lend its supported protocol surfaces; the implementation retains its transport and cleanup obligations. | +| `armdebug` | Own a probe, its connected SW-DP or baseline JTAG-DP, and explicitly acquired MEM-APs; restore APs before releasing DAP and its connection or chain, then close the probe. | +| `discover` | Enumerate registered transports, classify probe bindings, and select one candidate without owning an open probe. | +| `discover/probes` | Register all bundled probe providers for generic tools. | +| `ftdi` | Own one explicitly selected FTDI MPSSE port and expose direction-safe SWD bits or packed JTAG clocks. | +| `jlink` | Find one reviewed J-Link USB application interface, own its command session, configure SWD or JTAG, and adapt scan v3 to the selected wire protocol. | +| `cmsisdap` | Shortlist CMSIS-DAP product strings, validate one explicitly selected v2 bulk interface, own its command session, configure SWD, and adapt packet-bounded sequence commands to direction-explicit SWD bits. | +| `swd` | Enter SWD, establish its response grammar, and encode, execute, and validate individual or packed DP/AP register transactions. | +| `jtag` | Track TAP state, scan registers, discover reset entries, measure IR length, and validate explicit chains for selected-TAP access over a caller-supplied wire. | +| `swd/sim` | Model SWD protocol entry, register transfers, fixed-frame packing, and transfer limits without hardware. | +| `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` | Identify Cortex-M processors and own Cortex-M0 halting debug and register access over borrowed word memory. | +| `examples/...` | Demonstrate public package compositions as executable programs. | +| `cmd/ost` | Provide a small command hierarchy over the same public packages. | Raw USB requests stay in `usb` and adapter drivers. MPSSE framing stays in -`ftdi`. SWD request, acknowledgement, and parity rules stay in `swd`. -Debug-port 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. +`ftdi`. SWD request, acknowledgement, and parity rules stay in `swd`. Debug-port +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. ## Discovery and opening `discover.Registry` stores immutable transport providers. Registration is -serialized; enumeration runs outside the lock against a provider snapshot. -Core discovery imports no concrete transport. Applications register providers +serialized; enumeration runs outside the lock against a provider snapshot. Core +discovery imports no concrete transport. Applications register providers explicitly or use the package registry. Each call enumerates each provider once. Import `usb/discovery` to register all-device USB enumeration, or call its @@ -77,21 +77,20 @@ cannot keep physical-device ordering stable when replugging changes addresses. Each concrete driver exposes `OpenProbe` to acquire an exact USB attachment under a generic `probe.Probe`. Interface claim and protocol configuration happen when the owner lends SWD, or JTAG for FTDI and J-Link. The concrete adapters -share the USB-to-session ownership transition and retain either the -attachment or returned session after failed activation, so the generic owner -can retry cleanup. FTDI's ownership adapter retains its channel after failed -setup so a later close can retry the MPSSE reset sequence if the initial bulk -drain failed. - -`usb.New` constructs access to the host USB inventory. `List` returns a -snapshot matching every attachment, one exact vendor/product pair, or every -product from a vendor, without opening a device. Each candidate includes the -host-visible USB product and serial strings when available. The caller selects -exactly one `usb.DeviceInfo` and passes that complete snapshot back to `Open`. +share the USB-to-session ownership transition and retain either the attachment +or returned session after failed activation, so the generic owner can retry +cleanup. FTDI's ownership adapter retains its channel after failed setup so a +later close can retry the MPSSE reset sequence if the initial bulk drain failed. + +`usb.New` constructs access to the host USB inventory. `List` returns a snapshot +matching every attachment, one exact vendor/product pair, or every product from +a vendor, without opening a device. Each candidate includes the host-visible USB +product and serial strings when available. The caller selects exactly one +`usb.DeviceInfo` and passes that complete snapshot back to `Open`. `Open` revalidates the bus address, vendor ID, product ID, product string, and -serial before and after acquiring the attachment, then retains that identity -for the driver. A device that disappeared or changed identity returns +serial before and after acquiring the attachment, then retains that identity for +the driver. A device that disappeared or changed identity returns `usb.ErrStaleCandidate` instead of silently opening a replacement. `ftdi.SupportedDevices` provides the USB identities understood by the FTDI @@ -99,58 +98,57 @@ driver. It does not select a device. `ftdi.Open` derives the product from the opened USB device; `ftdi.Config` selects the MPSSE port and a maximum wire clock. The channel reports the attainable clock it configured. -`ftdi.Open` initializes MPSSE and the clock with target pins as inputs. -The same `Channel` supplies `SWDIO` and `JTAGIO`; each nonempty call establishes -its pin directions before clocking. No protocol or wiring selector is needed. -A non-nil channel owns the attachment even on error; a nil result leaves it -with the caller. Calls are serialized. Do not mix raw traffic underneath a -borrowed SWD or JTAG connection, whose protocol state that traffic invalidates. -Release higher-level state before closing the channel. No reset pin is driven. - -`jlink.SupportedDevices` likewise returns exact candidate identities rather -than a vendor wildcard. `jlink.Open` inspects the active descriptors, rejects -missing or ambiguous application interfaces, selects the descriptor-chosen -alternate, resolves its active bulk endpoints, and reads metadata. With no -options it does not select a target interface. An immediate reopen may briefly -find the probe unconfigured; `jlink.Open` retries only that typed USB state -until configuration appears or the caller's context ends. Without cancellation -or a deadline, it may wait indefinitely. `jlink.WithSWD` selects SWD during open -and requests a whole-kHz clock no greater than the requested ceiling. -`WithJTAG` does the same for the advertised JTAG interface. An open session -can be explicitly reconfigured after releasing its existing protocol owner; -`SWDIO` and `JTAGIO` reject calls for the wrong selected interface. J-Link -probe owners defer session opening and configuration until SWD or JTAG is -requested. +`ftdi.Open` initializes MPSSE and the clock with target pins as inputs. The same +`Channel` supplies `SWDIO` and `JTAGIO`; each nonempty call establishes its pin +directions before clocking. No protocol or wiring selector is needed. A non-nil +channel owns the attachment even on error; a nil result leaves it with the +caller. Calls are serialized. Do not mix raw traffic underneath a borrowed SWD +or JTAG connection, whose protocol state that traffic invalidates. Release +higher-level state before closing the channel. No reset pin is driven. + +`jlink.SupportedDevices` likewise returns exact candidate identities rather than +a vendor wildcard. `jlink.Open` inspects the active descriptors, rejects missing +or ambiguous application interfaces, selects the descriptor-chosen alternate, +resolves its active bulk endpoints, and reads metadata. With no options it does +not select a target interface. An immediate reopen may briefly find the probe +unconfigured; `jlink.Open` retries only that typed USB state until configuration +appears or the caller's context ends. Without cancellation or a deadline, it may +wait indefinitely. `jlink.WithSWD` selects SWD during open and requests a +whole-kHz clock no greater than the requested ceiling. `WithJTAG` does the same +for the advertised JTAG interface. An open session can be explicitly +reconfigured after releasing its existing protocol owner; `SWDIO` and `JTAGIO` +reject calls for the wrong selected interface. J-Link probe owners defer session +opening and configuration until SWD or JTAG is requested. CMSIS-DAP has no equivalent numeric identity catalog. Applications explicitly request `usb.AllDevices`, may use `cmsisdap.Candidates` as a case-sensitive product-string shortlist, and still select one complete attachment before opening it. The shortlist is not protocol evidence. `cmsisdap.Open` validates the exact v2 bulk-interface class and descriptor order. An explicitly selected -composite attachment can be opened even when its device product string lacks -the marker. With no options, open sends `DAP_Info` but no `DAP_Connect` command. +composite attachment can be opened even when its device product string lacks the +marker. With no options, open sends `DAP_Info` but no `DAP_Connect` command. `cmsisdap.WithSWD` connects only the advertised SWD port and requests a maximum target clock; `ConfigureSWD` applies the same configuration to an open session. -This split keeps inventory policy in the application. Listing hardware does -not claim an interface or send adapter or target traffic. +This split keeps inventory policy in the application. Listing hardware does not +claim an interface or send adapter or target traffic. ## Ownership and cleanup -The live path has one logical owner. Resources are acquired from the bottom -up and released in reverse order. - -| Value | Ownership rule | -| --- | --- | -| `*usb.Enumerator` | Holds inventory configuration, not an open attachment. | -| `*usb.Device` | Owns one open attachment. `ClaimInterface` returns the sole owner of one interface; that value reads the selected alternate before its first endpoint lookup, selects later alternates explicitly, submits bulk transfers, and releases the claim. A successful macOS alternate selection retains the active pipe properties IOKit reports. A failed alternate selection invalidates cached endpoint state so the next lookup reads the host state again. A failed release can be retried, and `Device.Close` does not close the attachment while release remains pending. | -| `*usb.BulkTransfer` | Represents one request on one active bulk endpoint. Its buffer length is the requested transfer length; the endpoint address supplies direction. `Wait` reports the exact count for a successful short or zero-length completion, and ending the wait context does not cancel the request. A host-engine failure can end `Wait` before `Done` closes; the buffer remains host-owned until `Done`. `AbortBulk` cancels and performs a bounded drain of every pending request on the named endpoint. Failed cancellation or drain retains the requests and claim for another cleanup attempt. Closing the claim applies the same bound before release. | -| `*ftdi.Channel` | Takes ownership of the USB device whenever `ftdi.Open` returns a non-nil channel, including on error. It keeps enough ordered maximum-packet-sized IN requests armed to cover its largest response and consumes FTDI status-only completions independently of MPSSE writes. An ambiguous transfer or asynchronous receive failure poisons the channel before later traffic can use the command stream. Recovery requires closing it and opening a new one. `Close` drains bulk OUT before resetting bit mode, setting the latency timer to 16 ms, purging the receive and transmit paths, releasing the interface, and closing the device. A failed cancellation or interface release leaves the channel and device open for another `Close`. It does not preserve prior FTDI settings. | -| `*jlink.Session` | Takes ownership of the USB device after `jlink.Open` succeeds. Metadata-only open claims the descriptor-selected application interface, resolves its active endpoint properties, and leaves target configuration unchanged. SWD or JTAG configuration selects that interface and sets volatile probe clock state; `Close` does not restore an unknown prior interface or clock. A complete nonzero scan status requires explicit reconfiguration. An ambiguous bulk exchange or abandoned response poisons the session, and later commands require closing it and explicitly reopening the device. Starting Close blocks configuration and scans; a failed interface release leaves cleanup retryable. Device close runs once, and later calls return its cached result. | -| `*cmsisdap.Session` | Takes ownership of the USB device after `cmsisdap.Open` succeeds. It claims one descriptor-selected v2 command interface and uses the probe's negotiated packet size, with one full response IN request submitted before each command OUT request. Metadata-only open sends no target-port command. `WithSWD` or `ConfigureSWD` connects the SWD port and requests a maximum clock. 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. Device close runs once, and later calls return its cached result. | -| `*swd.Conn` | Owns one logical SWD transaction stream and the ORUNDETECT bit it adds. `Connect` establishes the target's response grammar and `Release` restores the inherited setting. It does not own a separate host resource. Calls must be serialized. | -| `*dap.DebugPort` | Requires exclusive use of its SWD connection or JTAG chain. It owns only the power requests it adds; JTAG also owns its temporary change to inherited ORUNDETECT. It records newly requested power bits before writing them so bounded cleanup can attempt to clear them even when the write's result is ambiguous. `Release` settles outstanding requests, restores owned power/control state, then releases the connection or chain without closing the probe. | -| `*dap.MemAP` | `OpenMemAP` validates the selected AP and saves its CSW, TAR, and optional TARHI. `Release` retries failed restoration; if DAPABORT interrupts cleanup, the next `Release` retries every saved value. Calls sharing the MEM-AP or its debug port must be serialized. | +The live path has one logical owner. Resources are acquired from the bottom up +and released in reverse order. + +| Value | Ownership rule | +| ------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `*usb.Enumerator` | Holds inventory configuration, not an open attachment. | +| `*usb.Device` | Owns one open attachment. `ClaimInterface` returns the sole owner of one interface; that value reads the selected alternate before its first endpoint lookup, selects later alternates explicitly, submits bulk transfers, and releases the claim. A successful macOS alternate selection retains the active pipe properties IOKit reports. A failed alternate selection invalidates cached endpoint state so the next lookup reads the host state again. A failed release can be retried, and `Device.Close` does not close the attachment while release remains pending. | +| `*usb.BulkTransfer` | Represents one request on one active bulk endpoint. Its buffer length is the requested transfer length; the endpoint address supplies direction. `Wait` reports the exact count for a successful short or zero-length completion, and ending the wait context does not cancel the request. A host-engine failure can end `Wait` before `Done` closes; the buffer remains host-owned until `Done`. `AbortBulk` cancels and performs a bounded drain of every pending request on the named endpoint. Failed cancellation or drain retains the requests and claim for another cleanup attempt. Closing the claim applies the same bound before release. | +| `*ftdi.Channel` | Takes ownership of the USB device whenever `ftdi.Open` returns a non-nil channel, including on error. It keeps enough ordered maximum-packet-sized IN requests armed to cover its largest response and consumes FTDI status-only completions independently of MPSSE writes. An ambiguous transfer or asynchronous receive failure poisons the channel before later traffic can use the command stream. Recovery requires closing it and opening a new one. `Close` drains bulk OUT before resetting bit mode, setting the latency timer to 16 ms, purging the receive and transmit paths, releasing the interface, and closing the device. A failed cancellation or interface release leaves the channel and device open for another `Close`. It does not preserve prior FTDI settings. | +| `*jlink.Session` | Takes ownership of the USB device after `jlink.Open` succeeds. Metadata-only open claims the descriptor-selected application interface, resolves its active endpoint properties, and leaves target configuration unchanged. SWD or JTAG configuration selects that interface and sets volatile probe clock state; `Close` does not restore an unknown prior interface or clock. A complete nonzero scan status requires explicit reconfiguration. An ambiguous bulk exchange or abandoned response poisons the session, and later commands require closing it and explicitly reopening the device. Starting Close blocks configuration and scans; a failed interface release leaves cleanup retryable. Device close runs once, and later calls return its cached result. | +| `*cmsisdap.Session` | Takes ownership of the USB device after `cmsisdap.Open` succeeds. It claims one descriptor-selected v2 command interface and uses the probe's negotiated packet size, with one full response IN request submitted before each command OUT request. Metadata-only open sends no target-port command. `WithSWD` or `ConfigureSWD` connects the SWD port and requests a maximum clock. 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. Device close runs once, and later calls return its cached result. | +| `*swd.Conn` | Owns one logical SWD transaction stream and the ORUNDETECT bit it adds. `Connect` establishes the target's response grammar and `Release` restores the inherited setting. It does not own a separate host resource. Calls must be serialized. | +| `*dap.DebugPort` | Requires exclusive use of its SWD connection or JTAG chain. It owns only the power requests it adds; JTAG also owns its temporary change to inherited ORUNDETECT. It records newly requested power bits before writing them so bounded cleanup can attempt to clear them even when the write's result is ambiguous. `Release` settles outstanding requests, restores owned power/control state, then releases the connection or chain without closing the probe. | +| `*dap.MemAP` | `OpenMemAP` validates the selected AP and saves its CSW, TAR, and optional TARHI. `Release` retries failed restoration; if DAPABORT interrupts cleanup, the next `Release` retries every saved value. Calls sharing the MEM-AP or its debug port must be serialized. | An application that reaches the MEM-AP layer releases the MEM-AP before the debug port, then closes its FTDI channel, J-Link session, or CMSIS-DAP session. @@ -158,35 +156,35 @@ A metadata-only CMSIS-DAP composition closes its session directly. Cleanup errors remain meaningful and should be joined with the operation error rather than discarded. -`dap.DebugPort` caches register-selection and AP state. Direct transfers -on its SWD connection or JTAG chain can make that cached state stale, so do not -share either with another transaction owner while the debug port remains in -use. No layer adds a mutex; serialization belongs to the composition. +`dap.DebugPort` caches register-selection and AP state. Direct transfers on its +SWD connection or JTAG chain can make that cached state stale, so do not share +either with another transaction owner while the debug port remains in use. No +layer adds a mutex; serialization belongs to the composition. -Constructors and open operations attempt to clean up resources acquired before -a failed return. A non-nil result from `ftdi.Open` retains cleanup obligations -in the returned channel; close that channel and retain it if cleanup fails. -Only a nil result leaves device cleanup with the caller. A failed `cmsisdap.Open` -can return a non-nil session when synchronized target-port cleanup -remains retryable; the caller closes that session before the device. +Constructors and open operations attempt to clean up resources acquired before a +failed return. A non-nil result from `ftdi.Open` retains cleanup obligations in +the returned channel; close that channel and retain it if cleanup fails. Only a +nil result leaves device cleanup with the caller. A failed `cmsisdap.Open` can +return a non-nil session when synchronized target-port cleanup remains +retryable; the caller closes that session before the device. ## Protocol and policy boundaries -USB owns the v2 interface descriptors and asynchronous bulk-transfer -mechanism. `cmsisdap` owns the product-marker convention, exact interface and -endpoint fingerprint, command and response framing, `DAP_Info` decoding, -negotiated packet limits, target-port connection, maximum-clock request, -packet-bounded `DAP_SWD_Sequence` framing, and the rule that a response request -is submitted before its command. Packet count remains metadata; the driver -does not pipeline commands. +USB owns the v2 interface descriptors and asynchronous bulk-transfer mechanism. +`cmsisdap` owns the product-marker convention, exact interface and endpoint +fingerprint, command and response framing, `DAP_Info` decoding, negotiated +packet limits, target-port connection, maximum-clock request, packet-bounded +`DAP_SWD_Sequence` framing, and the rule that a response request is submitted +before its command. Packet count remains metadata; the driver does not pipeline +commands. The FTDI channel and configured J-Link or CMSIS-DAP session clock direction-explicit bit streams. None interprets SWD requests. FTDI owns MPSSE framing; J-Link owns its scan framing and status; CMSIS-DAP owns its sequence commands and response status. Each adapter owns its clock and transfer limits. -`swd.Conn` owns request framing, turnaround, -acknowledgements, data parity, line reset, the JTAG-to-SWD selection sequence, -and the CTRL/STAT.ORUNDETECT setting which selects the response grammar. +`swd.Conn` owns request framing, turnaround, acknowledgements, data parity, line +reset, the JTAG-to-SWD selection sequence, and the CTRL/STAT.ORUNDETECT setting +which selects the response grammar. `swd.Conn.Connect` tries JTAG-to-SWD first. If the initial DPIDR read has an invalid ACK and its trailing clocks complete, it tries JTAG-to-dormant and @@ -194,16 +192,16 @@ dormant-to-SWD once before reading DPIDR again. Release uses the same fallback when it must repair framing. Release leaves SWD selected rather than restoring the prior interface mode. -The connection reads and validates DPIDR before configuration, clears -supported sticky state, writes zero to SELECT, settles it through RDBUFF, then -reads CTRL/STAT. This establishes which response grammar applies before -ordinary register access. It keeps an inherited ORUNDETECT setting or tries to -enable it; if the bit does not read back as set, the connection remains in -simple mode. `Release` restores the value found by `Connect`. Register methods -separate DP and AP reads from writes and reject an unsupported physical address -before sending traffic. They send the requested transaction once. In overrun -mode a returned WAIT also causes an ABORT write which clears STICKYORUN; -retrying the request remains the caller's decision. +The connection reads and validates DPIDR before configuration, clears supported +sticky state, writes zero to SELECT, settles it through RDBUFF, then reads +CTRL/STAT. This establishes which response grammar applies before ordinary +register access. It keeps an inherited ORUNDETECT setting or tries to enable it; +if the bit does not read back as set, the connection remains in simple mode. +`Release` restores the value found by `Connect`. Register methods separate DP +and AP reads from writes and reject an unsupported physical address before +sending traffic. They send the requested transaction once. In overrun mode a +returned WAIT also causes an ABORT write which clears STICKYORUN; retrying the +request remains the caller's decision. `swd.Batch` validates its complete queue before traffic. It uses the response grammar already established by `Connect`; callers cannot select another one. @@ -212,138 +210,130 @@ fixed frames up to the limit reported by the wire. A failed physical call makes every operation in that chunk indeterminate and leaves later chunks unsent. `Batch` assigns WAIT to the operation which received it after the connection clears STICKYORUN; the connection does not replay that operation or its -abandoned suffix. See -[Serial Wire Debug](protocols/swd.md) for the wire protocol and current bench -notes. +abandoned suffix. See [Serial Wire Debug](protocols/swd.md) for the wire +protocol and current bench notes. `dap.DebugPort.Connect` returns a `dap.Identity`, whose accessors distinguish -DPIDR from IDCODE without treating their encodings as interchangeable. The -SW-DP connection establishes only DPIDR; JTAG-DP establishes only IDCODE. -Construct the opaque binding with `dap.SWDP(conn)` or -`dap.JTAGDP(chain, tapIndex)` and pass it to `dap.NewDebugPort`. The zero -binding is invalid; constructors send no traffic. The debug port enters the -bound protocol before applying DAP policy. -Public DP, AP, transaction, and MEM-AP operations -remain blocked until that connection is active. The debug port validates -register availability and direction for the binding. On SWD it validates -DPIDR, gives each logical DP register its architectural bank, -preserves the AP fields while changing DPBANKSEL, and requests acknowledged -debug and system power. CTRL/STAT writes preserve connection-owned ORUNDETECT, -and a non-default DLCR turnaround remains unsupported. The debug port retries -the exact physical request which returned WAIT and completes posted AP -transactions through RDBUFF. Ordered transactions use `swd.Batch` for ordinary -fixed frames but keep sticky-exempt DPIDR, CTRL/STAT, and ABORT operations at a -physical boundary. A packed WAIT retries the WAITed request and the suffix the -target abandoned; it does not repeat the confirmed prefix. -The private SWD executor owns request retries, sticky-fault recovery, and the -SELECT confirmations that depend on SWD response grammar. It resolves wire -batch results before returning them to the transaction policy. Unsent and -rejected requests remain distinct from confirmed transfers and ambiguous -exchanges; read parity errors still report -that the request was accepted, without claiming that its data is valid. -The executor also completes posted AP operations and reports confirmed block -writes and uncertain effects without making MEM-AP interpret SWD errors. -After interpreting SWD responses, the executor adds DAP error classifications -to completed operations and individual transaction results while retaining -their original causes. - -Connection setup validates the context and options before protocol entry. -Power acquisition starts only after entry establishes the identity and -control state; failed setup and ordinary release use the same link cleanup. - -ADIv6 SW-DP selects AP register addresses through SELECT and SELECT1, using -the width advertised by DPIDR1. Its transactions complete each operation -before sending the next. ADIv6 MEM-APs use the existing restoration and -managed ownership rules. +DPIDR from IDCODE without treating their encodings as interchangeable. The SW-DP +connection establishes only DPIDR; JTAG-DP establishes only IDCODE. Construct +the opaque binding with `dap.SWDP(conn)` or `dap.JTAGDP(chain, tapIndex)` and +pass it to `dap.NewDebugPort`. The zero binding is invalid; constructors send no +traffic. The debug port enters the bound protocol before applying DAP policy. +Public DP, AP, transaction, and MEM-AP operations remain blocked until that +connection is active. The debug port validates register availability and +direction for the binding. On SWD it validates DPIDR, gives each logical DP +register its architectural bank, preserves the AP fields while changing +DPBANKSEL, and requests acknowledged debug and system power. CTRL/STAT writes +preserve connection-owned ORUNDETECT, and a non-default DLCR turnaround remains +unsupported. The debug port retries the exact physical request which returned +WAIT and completes posted AP transactions through RDBUFF. Ordered transactions +use `swd.Batch` for ordinary fixed frames but keep sticky-exempt DPIDR, +CTRL/STAT, and ABORT operations at a physical boundary. A packed WAIT retries +the WAITed request and the suffix the target abandoned; it does not repeat the +confirmed prefix. The private SWD executor owns request retries, sticky-fault +recovery, and the SELECT confirmations that depend on SWD response grammar. It +resolves wire batch results before returning them to the transaction policy. +Unsent and rejected requests remain distinct from confirmed transfers and +ambiguous exchanges; read parity errors still report that the request was +accepted, without claiming that its data is valid. The executor also completes +posted AP operations and reports confirmed block writes and uncertain effects +without making MEM-AP interpret SWD errors. After interpreting SWD responses, +the executor adds DAP error classifications to completed operations and +individual transaction results while retaining their original causes. + +Connection setup validates the context and options before protocol entry. Power +acquisition starts only after entry establishes the identity and control state; +failed setup and ordinary release use the same link cleanup. + +ADIv6 SW-DP selects AP register addresses through SELECT and SELECT1, using the +width advertised by DPIDR1. Its transactions complete each operation before +sending the next. ADIv6 MEM-APs use the existing restoration and managed +ownership rules. The private JTAG executor owns DPACC/APACC framing and its delayed-response -pipeline. It polls an accepted request to completion without replaying it, -then checks CTRL/STAT after each AP operation before another AP operation is -issued. JTAG uses the same `Txn`, AP selection, and `OpenMemAP` APIs with -sequential logical execution. Baseline SELECT is readable, IDCODE is distinct -from DPIDR, and later banked registers are rejected. The binding temporarily -disables inherited ORUNDETECT and rejects active pushed-operation or -transaction-counter modes. Release restores acquired control state and parks -the chain in BYPASS/Idle. After losing scan state, `DebugPort` revalidates the -exact chain and reacquires its TAP before restoring state. `WithCleanupTimeout` -bounds each independent recovery attempt: one second by default for SWD, -thirty for JTAG. -No driver is reopened automatically after a poisoned exchange. +pipeline. It polls an accepted request to completion without replaying it, then +checks CTRL/STAT after each AP operation before another AP operation is issued. +JTAG uses the same `Txn`, AP selection, and `OpenMemAP` APIs with sequential +logical execution. Baseline SELECT is readable, IDCODE is distinct from DPIDR, +and later banked registers are rejected. The binding temporarily disables +inherited ORUNDETECT and rejects active pushed-operation or transaction-counter +modes. Release restores acquired control state and parks the chain in +BYPASS/Idle. After losing scan state, `DebugPort` revalidates the exact chain +and reacquires its TAP before restoring state. `WithCleanupTimeout` bounds each +independent recovery attempt: one second by default for SWD, thirty for JTAG. No +driver is reopened automatically after a poisoned exchange. `NewAPSel` constructs an ADIv5 index; `APAt` constructs an ADIv6 base-address selector. Both return `APSel` values; the zero `APSel` remains invalid. -`APSel.Address` combines a selector with an eight-bit ADIv5 or twelve-bit -ADIv6 register offset; the -resulting `APAddress` also has an invalid zero value. `ReadAPIDR` reads and -decodes the common read-only AP identity. Raw AP access rejects invalid or -unaligned addresses before traffic. Register names and effects remain -specific to the selected AP class. A write to a MEM-AP data register can -write target memory. A raw AP read or write which completes, or whose -completion is uncertain, invalidates existing `MemAP` values. On SWD, -`dap.DebugPort` retries the same physical request after a clean WAIT until -its response-count limit is reached or the operation context ends. The -one-argument constructor uses only the context; `WithMaxWaits` sets a limit, -and `SetMaxWaits` changes it while the port is idle. If either boundary ends -AP waiting, the debug port issues DAPABORT and invalidates AP-derived state. -RDBUFF also settles DP writes, but a stall or FAULT at that barrier does not -trigger AP-only recovery. A FAULT is not retried: the debug port captures -bank-zero CTRL/STAT, clears the sticky conditions reported there, verifies -the clear through CTRL/STAT, and returns a typed error. AP-derived state is -invalidated when the failed sequence might have changed it, but not when a -complete AP-write FAULT or WDATAERR establishes that the write was -abandoned. A SELECT write remains provisional until later traffic -establishes whether its data took effect. WDATAERR invalidates the cached -selection; FAULT handling reads `0x04` only when both possible DP banks are -zero. If FAULT cleanup, WAIT cleanup, or another transfer leaves framing -unknown, `dap.DebugPort` invalidates AP-derived state and blocks every -operation except cleanup. Cleanup re-enters SWD before sending another -framed request and refuses to restore state if DPIDR no longer matches the -connection being cleaned up. Failed setup uses the DPIDR read by that -attempt; cleanup for an established connection uses its last successful +`APSel.Address` combines a selector with an eight-bit ADIv5 or twelve-bit ADIv6 +register offset; the resulting `APAddress` also has an invalid zero value. +`ReadAPIDR` reads and decodes the common read-only AP identity. Raw AP access +rejects invalid or unaligned addresses before traffic. Register names and +effects remain specific to the selected AP class. A write to a MEM-AP data +register can write target memory. A raw AP read or write which completes, or +whose completion is uncertain, invalidates existing `MemAP` values. On SWD, +`dap.DebugPort` retries the same physical request after a clean WAIT until its +response-count limit is reached or the operation context ends. The one-argument +constructor uses only the context; `WithMaxWaits` sets a limit, and +`SetMaxWaits` changes it while the port is idle. If either boundary ends AP +waiting, the debug port issues DAPABORT and invalidates AP-derived state. RDBUFF +also settles DP writes, but a stall or FAULT at that barrier does not trigger +AP-only recovery. A FAULT is not retried: the debug port captures bank-zero +CTRL/STAT, clears the sticky conditions reported there, verifies the clear +through CTRL/STAT, and returns a typed error. AP-derived state is invalidated +when the failed sequence might have changed it, but not when a complete AP-write +FAULT or WDATAERR establishes that the write was abandoned. A SELECT write +remains provisional until later traffic establishes whether its data took +effect. WDATAERR invalidates the cached selection; FAULT handling reads `0x04` +only when both possible DP banks are zero. If FAULT cleanup, WAIT cleanup, or +another transfer leaves framing unknown, `dap.DebugPort` invalidates AP-derived +state and blocks every operation except cleanup. Cleanup re-enters SWD before +sending another framed request and refuses to restore state if DPIDR no longer +matches the connection being cleaned up. Failed setup uses the DPIDR read by +that attempt; cleanup for an established connection uses its last successful DPIDR. `Connect` attempts this cleanup itself when setup fails; a cleanup -failure remains pending for `Release`. Once `Release` starts, a failure -likewise leaves only `MemAP.Release`, `DebugPort.Release`, and the cached -identity available. `dap.MemAP` reads CFG, then uses one access port for -aligned 8-, 16-, and 32-bit target-memory reads and writes when CSW accepts -the selected size. It also permits 64-bit transfers when CFG.LD is set and -CSW accepts Size64, and addresses above 32 bits when CFG.LA is set. If a -Size64 transfer fails after its first DRW access might have started, -ordinary debug-port traffic remains blocked until the MEM-AP and debug port -are released. MEM-AP cleanup terminates an incomplete transfer through CSW -before restoring TAR or TARHI. Arbitrary-range reads and writes use sub-word -edges and bounded word runs. No auto-incrementing word run crosses a 1 KiB -TAR boundary. If CSW does not retain single address increment, block access -writes TAR before each word. Scalar and block memory access use the same -WAIT rule. An accepted write is not replayed; if its RDBUFF completion -request returns WAIT, only that request is retried. If selection, framing, -or cleanup becomes uncertain, the existing repair behavior applies. A FAULT -returns the confirmed prefix instead of retrying the failed request. +failure remains pending for `Release`. Once `Release` starts, a failure likewise +leaves only `MemAP.Release`, `DebugPort.Release`, and the cached identity +available. `dap.MemAP` reads CFG, then uses one access port for aligned 8-, 16-, +and 32-bit target-memory reads and writes when CSW accepts the selected size. It +also permits 64-bit transfers when CFG.LD is set and CSW accepts Size64, and +addresses above 32 bits when CFG.LA is set. If a Size64 transfer fails after its +first DRW access might have started, ordinary debug-port traffic remains blocked +until the MEM-AP and debug port are released. MEM-AP cleanup terminates an +incomplete transfer through CSW before restoring TAR or TARHI. Arbitrary-range +reads and writes use sub-word edges and bounded word runs. No auto-incrementing +word run crosses a 1 KiB TAR boundary. If CSW does not retain single address +increment, block access writes TAR before each word. Scalar and block memory +access use the same WAIT rule. An accepted write is not replayed; if its RDBUFF +completion request returns WAIT, only that request is retried. If selection, +framing, or cleanup becomes uncertain, the existing repair behavior applies. A +FAULT returns the confirmed prefix instead of retrying the failed request. ADIv5 access-port enumeration scans all 256 APSEL values in bounded transactions. IDR zero means absent. The scan does not assume contiguous AP -numbers and reads no class-specific register. -See [Arm Debug Access Ports](ports/dap.md) for the ADIv5 register protocol and -the awkward parts of posted and memory access. +numbers and reads no class-specific register. See +[Arm Debug Access Ports](ports/dap.md) for the ADIv5 register protocol and the +awkward parts of posted and memory access. -`dap.MemAP.ReadDebugBase` reads and decodes the selected AP's advertised -debug entry, including legacy encodings and the optional upper address word. -It preserves the memory client's state on success and does not access target +`dap.MemAP.ReadDebugBase` reads and decodes the selected AP's advertised debug +entry, including legacy encodings and the optional upper address word. It +preserves the memory client's state on success and does not access target memory. `coresight` reads component identification through a scalar-memory reader. It also derives ROM geometry from those identities, reads individual -entries, and walks hierarchies with explicit limits. Traversal skips -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` 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, its halt requests, and pending register and -step operations. Stepping checks DFSR to preserve competing stops. Release -settles pending operations before restoring debug control; -the target must be released 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. +entries, and walks hierarchies with explicit limits. Traversal skips 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` 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, its halt requests, and pending register and step operations. +Stepping checks DFSR to preserve competing stops. Release settles pending +operations before restoring debug control; the target must be released 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. ## Host implementations @@ -352,31 +342,31 @@ The `usb` API is the same on both supported hosts: - Linux uses sysfs for inventory and usbfs for ownership and transfers. Each bulk request is one pinned usbfs URB. One claim-owned worker submits, reaps, cancels, and drains those URBs while a companion waits for completion - readiness on the usbfs descriptor. A terminal readiness or reap failure - stops new submissions and wakes pending waits without releasing buffers - still owned by the kernel. It is implemented in pure Go. + readiness on the usbfs descriptor. A terminal readiness or reap failure stops + new submissions and wakes pending waits without releasing buffers still owned + by the kernel. It is implemented in pure Go. - macOS uses cgo with the system IOKit and CoreFoundation frameworks. Claiming an interface temporarily seizes that interface from its current driver and - closing it releases ownership. A claim-owned worker confines asynchronous - pipe operations and the interface event source to one locked OS thread and - run loop. + closing it releases ownership. A claim-owned worker confines asynchronous pipe + operations and the interface event source to one locked OS thread and run + loop. -The API keeps USB endpoint addresses, directions, maximum packet sizes, -transfer lengths, completion boundaries, short transfers, and zero-length -transfers visible. It does not pair IN with OUT, flatten endpoints into byte -streams, choose buffer sizes, or rearm reads. Adapter drivers submit as many -requests as their protocols require and interpret each completion themselves. -When a driver treats several IN requests as one ordered flow, it consumes their -handles in submission order. +The API keeps USB endpoint addresses, directions, maximum packet sizes, transfer +lengths, completion boundaries, short transfers, and zero-length transfers +visible. It does not pair IN with OUT, flatten endpoints into byte streams, +choose buffer sizes, or rearm reads. Adapter drivers submit as many requests as +their protocols require and interpret each completion themselves. When a driver +treats several IN requests as one ordered flow, it consumes their handles in +submission order. -The native implementation remains inside `usb`; packages above it do not -inspect host-specific state. +The native implementation remains inside `usb`; packages above it do not inspect +host-specific state. ## Simulation boundary The public simulations implement the same boundaries consumed by production -code. `swd/sim` provides a wire, and `dap/sim` provides DP, AP, and MEM-AP -state behind that wire. +code. `swd/sim` provides a wire, and `dap/sim` provides DP, AP, and MEM-AP state +behind that wire. Production packages do not import their simulators. Tests and downstream programs may compose them explicitly, which keeps hardware-free behavior @@ -386,34 +376,33 @@ replaceable while exercising the public protocol and DAP layers. 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, halts Cortex-M0, reads PC, SP, R0, -and R4, then resumes it. Its `-step` option steps once while halted. -The `dap.MemAP` API does expose scalar and block target-memory writes; -applications choose the affected addresses and own the consequences. +`cortexm-control` example enables debug, halts Cortex-M0, reads PC, SP, R0, and +R4, then resumes it. Its `-step` option steps once while halted. 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: -- Opening FTDI claims a USB interface and places the selected function in - MPSSE mode. Closing resets bit mode, sets the latency timer to 16 ms, purges - the receive and transmit paths, releases the interface, and closes the USB - device; it does not restore the function's prior FTDI settings. +- Opening FTDI claims a USB interface and places the selected function in MPSSE + mode. Closing resets bit mode, sets the latency timer to 16 ms, purges the + receive and transmit paths, releases the interface, and closes the USB device; + it does not restore the function's prior FTDI settings. - Configuring J-Link SWD or JTAG selects the probe's target interface and - changes its volatile target clock. Closing releases the application - interface and USB device but does not restore an unknown prior interface - or clock. + changes its volatile target clock. Closing releases the application interface + and USB device but does not restore an unknown prior interface or clock. - Opening a CMSIS-DAP v2 session claims its command interface and exchanges - metadata commands. With SWD configuration, it also initializes the probe's - SWD pins and requests a volatile maximum target clock. Close attempts + metadata commands. With SWD configuration, it also initializes the probe's SWD + pins and requests a volatile maximum target clock. Close attempts `DAP_Disconnect`. The package does not connect JTAG or use the optional SWO endpoint. - Entering SWD clocks line-reset and protocol-selection sequences. -- Connecting a debug port clears sticky status, selects a register bank, and - may request volatile debug and system power. -- Raw AP access has the effects defined by the selected AP class. A raw read - can change class-specific state, and a raw write to a MEM-AP data register - can write target memory. `DebugPort` does not restore either operation. +- Connecting a debug port clears sticky status, selects a register bank, and may + request volatile debug and system power. +- Raw AP access has the effects defined by the selected AP class. A raw read can + change class-specific state, and a raw write to a MEM-AP data register can + write target memory. `DebugPort` does not restore either operation. - Reading or writing through a MEM-AP temporarily changes CSW, TAR, and sometimes TARHI, then restores their prior values. -Callers should always complete the documented release sequence, including -when the primary operation fails. +Callers should always complete the documented release sequence, including when +the primary operation fails. diff --git a/docs/capabilities.md b/docs/capabilities.md index 520be90..3c2e821 100644 --- a/docs/capabilities.md +++ b/docs/capabilities.md @@ -1,47 +1,48 @@ # Capabilities -This guide describes the behavior implemented by the current public packages. -It distinguishes an API from the environments where that API is exercised in -CI or on physical hardware. +This guide describes the behavior implemented by the current public packages. It +distinguishes an API from the environments where that API is exercised in CI or +on physical hardware. “Implemented” means the behavior is present and covered by ordinary tests. -“Simulated” means a public behavioral model exercises the same package -boundary. “HIL” means an opt-in integration test has exercised physical -hardware. None of these labels imply support for every device in a product -family or every feature of a protocol. +“Simulated” means a public behavioral model exercises the same package boundary. +“HIL” means an opt-in integration test has exercised physical hardware. None of +these labels imply support for every device in a product family or every feature +of a protocol. ## Host USB -| Capability | Implemented | Validation and boundary | -| --- | --- | --- | -| Linux host access | Yes | Pure-Go sysfs inventory and usbfs transfers; Linux CI and FT232H HIL. Permission setup and manual release of a bound kernel driver are host prerequisites. | -| macOS host access | Yes | IOKit and IOUSBLib through cgo; macOS 26 arm64 and Intel CI with a macOS 12 deployment target. | -| Enumeration | Yes | Explicit all-device, exact-product, and vendor-only filters, including an exact product ID of zero; deterministic bus/address ordering, optional host-visible USB product and serial strings, and context checks. | -| Exact open | Yes | Revalidates bus, address, vendor ID, product ID, product string, and serial before and after opening, then retains that identity on the device. | -| Active configuration | Yes | Returns a detached snapshot of standard interface, alternate-setting, and endpoint descriptors without claiming or changing the device. `usb.ErrNotConfigured` distinguishes configuration zero, including a transition to zero while the snapshot is read. | -| Interface ownership | Yes | `ClaimedInterface` reads the current alternate setting before its first endpoint lookup, selects later alternates explicitly, and releases the interface. On macOS, a successful selection retains the active IOKit pipe properties instead of reading the configuration again. A failed alternate selection invalidates its endpoint cache; a failed release can be retried. `Device.Close` waits for that release. Linux reports contention rather than detaching a bound kernel driver. | -| Control transfers | Yes | Synchronous, deadline-bounded endpoint-zero transfers. | -| Bulk transfers | Yes | A claimed interface exposes active endpoint descriptors and accepts explicit asynchronous transfers by endpoint address and buffer length. Short and zero-length completions remain visible. Wait cancellation does not cancel a request. A host-engine error ends pending waits without releasing buffers which native requests still own. Endpoint abort and claim close use bounded drains; failed cancellation or drain retains the requests and claim for a retry. Pairing, buffer sizing, ordering across handles, and read scheduling belong to the adapter driver. | -| Linux FT232H ownership | HIL | Manual `ftdi_sio` unbind, unprivileged usbfs claim and MPSSE/SWD traffic, release, and explicit driver rebind. | -| macOS FT232H ownership | HIL | Interface seizure, control/bulk traffic, MPSSE setup, close, and Apple driver rematch. | - -The USB package does not currently expose manufacturer strings, hotplug -events, multiple simultaneous interface claims, interrupt or isochronous -transfers, device reset, or configuration switching. - -Linux is the only pure-Go host. macOS builds require cgo and the Xcode or -Xcode command-line-tool SDK; they do not require libusb or another installed -USB library. Windows is not supported. See [Linux USB access](linux-usb.md) -for the host setup required by physical Linux USB operations. +| Capability | Implemented | Validation and boundary | +| ---------------------- | ----------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Linux host access | Yes | Pure-Go sysfs inventory and usbfs transfers; Linux CI and FT232H HIL. Permission setup and manual release of a bound kernel driver are host prerequisites. | +| macOS host access | Yes | IOKit and IOUSBLib through cgo; macOS 26 arm64 and Intel CI with a macOS 12 deployment target. | +| Enumeration | Yes | Explicit all-device, exact-product, and vendor-only filters, including an exact product ID of zero; deterministic bus/address ordering, optional host-visible USB product and serial strings, and context checks. | +| Exact open | Yes | Revalidates bus, address, vendor ID, product ID, product string, and serial before and after opening, then retains that identity on the device. | +| Active configuration | Yes | Returns a detached snapshot of standard interface, alternate-setting, and endpoint descriptors without claiming or changing the device. `usb.ErrNotConfigured` distinguishes configuration zero, including a transition to zero while the snapshot is read. | +| Interface ownership | Yes | `ClaimedInterface` reads the current alternate setting before its first endpoint lookup, selects later alternates explicitly, and releases the interface. On macOS, a successful selection retains the active IOKit pipe properties instead of reading the configuration again. A failed alternate selection invalidates its endpoint cache; a failed release can be retried. `Device.Close` waits for that release. Linux reports contention rather than detaching a bound kernel driver. | +| Control transfers | Yes | Synchronous, deadline-bounded endpoint-zero transfers. | +| Bulk transfers | Yes | A claimed interface exposes active endpoint descriptors and accepts explicit asynchronous transfers by endpoint address and buffer length. Short and zero-length completions remain visible. Wait cancellation does not cancel a request. A host-engine error ends pending waits without releasing buffers which native requests still own. Endpoint abort and claim close use bounded drains; failed cancellation or drain retains the requests and claim for a retry. Pairing, buffer sizing, ordering across handles, and read scheduling belong to the adapter driver. | +| Linux FT232H ownership | HIL | Manual `ftdi_sio` unbind, unprivileged usbfs claim and MPSSE/SWD traffic, release, and explicit driver rebind. | +| macOS FT232H ownership | HIL | Interface seizure, control/bulk traffic, MPSSE setup, close, and Apple driver rematch. | + +The USB package does not currently expose manufacturer strings, hotplug events, +multiple simultaneous interface claims, interrupt or isochronous transfers, +device reset, or configuration switching. + +Linux is the only pure-Go host. macOS builds require cgo and the Xcode or Xcode +command-line-tool SDK; they do not require libusb or another installed USB +library. Windows is not supported. See [Linux USB access](linux-usb.md) for the +host setup required by physical Linux USB operations. ## Probe ownership Probe classifiers register with `discover.RegisterProbe` or an explicit -`Registry`. Each binding names its transport dependency. `TransportInventory.Probes` -uses the registrations captured during enumeration, returning sorted candidates -alongside attributed classification errors. It does not repeat enumeration or -open hardware. Handle the original transport error separately before classifying -a partial snapshot; the sequence does not retain that error. +`Registry`. Each binding names its transport dependency. +`TransportInventory.Probes` uses the registrations captured during enumeration, +returning sorted candidates alongside attributed classification errors. It does +not repeat enumeration or open hardware. Handle the original transport error +separately before classifying a partial snapshot; the sequence does not retain +that error. `discover.Candidate` captures an exact opening callback and copied display metadata. `ProbeInventory.Select` requires one match; `Open` combines that @@ -56,39 +57,36 @@ return an empty sequence. `probe.New` takes an implementation without I/O. `Probe.SWD` borrows its SWD wire; failed activation attempts cleanup and retains failed cleanup for -`Probe.Close` to retry. The owner imports no concrete driver or transport. -It does not own SWD transactions, DAP, or MEM-AP state. +`Probe.Close` to retry. The owner imports no concrete driver or transport. It +does not own SWD transactions, DAP, or MEM-AP state. `Probe.JTAG` similarly lends packed JTAG clocks. Only one protocol can be -activated per owner. Close invalidates either borrowed surface, including -when cleanup must be retried. Unsupported protocols fail before activation. -FTDI and J-Link owners support SWD or JTAG; CMSIS-DAP owners currently support -SWD only. JTAG TAP and chain state belong to `jtag`. +activated per owner. Close invalidates either borrowed surface, including when +cleanup must be retried. Unsupported protocols fail before activation. FTDI and +J-Link owners support SWD or JTAG; CMSIS-DAP owners currently support SWD only. +JTAG TAP and chain state belong to `jtag`. FTDI, J-Link, and CMSIS-DAP expose exact-attachment `OpenProbe` entry points. -These acquire USB without adapter or target traffic; activating a protocol -opens the concrete session. FTDI also requires one explicit supported MPSSE -port. +These acquire USB without adapter or target traffic; activating a protocol opens +the concrete session. FTDI also requires one explicit supported MPSSE port. ## Arm debug ownership `armdebug.Connect` takes a supplied probe, activates the explicitly configured -SW-DP or baseline JTAG-DP, and lends its connected `dap.DebugPort`. -One owner releases DAP and its connection or chain -before closing the probe. Failed cleanup stops at the failed layer and retains -its dependencies for retry. Setup failures return a cleanup-only owner when -restoration remains outstanding. Behavioral simulation covers setup, single -SWD entry, explicit JTAG chain/TAP selection, cancellation, release ordering, -and retryable cleanup. +SW-DP or baseline JTAG-DP, and lends its connected `dap.DebugPort`. One owner +releases DAP and its connection or chain before closing the probe. Failed +cleanup stops at the failed layer and retains its dependencies for retry. Setup +failures return a cleanup-only owner when restoration remains outstanding. +Behavioral simulation covers setup, single SWD entry, explicit JTAG chain/TAP +selection, cancellation, release ordering, and retryable cleanup. `armdebug.JTAGDP` copies a complete `jtag.Layout` and selects a zero-based, TDO-first TAP with an IDCODE and a four- or eight-bit IR. Invalid static configuration is rejected before discovery or activation; connection setup validates the exact physical chain. Board-specific chain routing remains -external. `Config.CleanupTimeout` bounds each owned release attempt, -defaulting to one second for SWD and thirty seconds for JTAG. DAP's -independent recovery attempts remain separately configurable through -`DAPOptions`. +external. `Config.CleanupTimeout` bounds each owned release attempt, defaulting +to one second for SWD and thirty seconds for JTAG. DAP's independent recovery +attempts remain separately configurable through `DAPOptions`. `armdebug.Open` adds registered discovery and exact selection to that ownership path. It refuses incomplete discovery and never tries another candidate after @@ -103,59 +101,58 @@ releases. ## FTDI MPSSE -`ftdi.Candidates` classifies a detached USB snapshot into supported MPSSE -ports. Import `ftdi/discovery` to enable these bindings in `discover`, or use -its `Register` with an explicit registry. FT2232H and FT4232H retain separate -A/B candidates; selecting only their shared serial is ambiguous. Discovery -does not prove that the board connects those pins to a debug target. - -| Capability | Implemented | Validation and boundary | -| --- | --- | --- | -| 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. 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. | -| Continuous receive | HIL | One FT232H session completed 1,000 consecutive full AP enumerations on each host: 1,024,012 physical OK acknowledgements on macOS and 1,024,022 on Linux, with no WAIT, FAULT, or invalid acknowledgement and one SWD entry per run. The macOS bench had reproduced intermittent OUT completion failures when IN was not kept armed. | -| JTAG | Yes | `Channel.JTAGIO` establishes standard TCK/TDI/TDO/TMS directions before clocking; the same channel also supplies SWD. Packed streams are capped at 8,192 clocks; the worst-case response fits the existing receive window. Other GPIO remain inputs. | -| FT4232H JTAG chain | HIL | On Nostalgia, ZCU104 serial `01691`, port A at 100 kHz: reset discovery found Arm `0x5ba00477` and Xilinx `0x14730093`; explicit IR4/IR12 validation, selected Arm IDCODE read, BYPASS/Idle release, and channel close completed. Board-specific DAP activation had already been performed externally. No DAP register or target-memory access was exercised. | - -The driver binds the standard FTDI H-series interfaces and endpoint numbers. -It does not inspect USB descriptors to verify a different layout. A listed -USB identity is a candidate, not evidence that every board using that identity -wires its MPSSE port for debugging. +`ftdi.Candidates` classifies a detached USB snapshot into supported MPSSE ports. +Import `ftdi/discovery` to enable these bindings in `discover`, or use its +`Register` with an explicit registry. FT2232H and FT4232H retain separate A/B +candidates; selecting only their shared serial is ambiguous. Discovery does not +prove that the board connects those pins to a debug target. + +| Capability | Implemented | Validation and boundary | +| --------------------------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| 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. 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. | +| Continuous receive | HIL | One FT232H session completed 1,000 consecutive full AP enumerations on each host: 1,024,012 physical OK acknowledgements on macOS and 1,024,022 on Linux, with no WAIT, FAULT, or invalid acknowledgement and one SWD entry per run. The macOS bench had reproduced intermittent OUT completion failures when IN was not kept armed. | +| JTAG | Yes | `Channel.JTAGIO` establishes standard TCK/TDI/TDO/TMS directions before clocking; the same channel also supplies SWD. Packed streams are capped at 8,192 clocks; the worst-case response fits the existing receive window. Other GPIO remain inputs. | +| FT4232H JTAG chain | HIL | On Nostalgia, ZCU104 serial `01691`, port A at 100 kHz: reset discovery found Arm `0x5ba00477` and Xilinx `0x14730093`; explicit IR4/IR12 validation, selected Arm IDCODE read, BYPASS/Idle release, and channel close completed. Board-specific DAP activation had already been performed externally. No DAP register or target-memory access was exercised. | + +The driver binds the standard FTDI H-series interfaces and endpoint numbers. It +does not inspect USB descriptors to verify a different layout. A listed USB +identity is a candidate, not evidence that every board using that identity wires +its MPSSE port for debugging. ## J-Link USB session -| Capability | Implemented | Validation and boundary | -| --- | --- | --- | -| Exact discovery catalog | Yes | Reviewed SEGGER application PIDs only; CDC-only `0x0106`, CMSIS-DAP `0x1008`, vendor wildcards, and inferred neighboring products are excluded. | -| Application interface selection | Yes | Requires one unambiguous `ff/ff/ff` alternate setting with exactly one bulk IN and one bulk OUT endpoint. The descriptors select the interface, alternate, and endpoint addresses; after selection, the session resolves the active endpoint properties and uses the active bulk IN maximum packet size. An immediate reopen retries only `usb.ErrNotConfigured`, at 10 ms intervals until configuration appears or the caller's context ends; other inspection errors return immediately. | -| Firmware record | Yes | Retains the complete length-delimited record and exposes its first NUL-delimited field for display. | -| Capabilities | Yes | Preserves the opaque short or long bitset. The long query is gated by short bit 31, and the common prefix must agree. | -| Optional metadata | Yes | Capability-gated hardware version, workspace hint, available target interfaces, and current target interface. A selected interface outside the 0–31 range represented by the availability mask is rejected. | -| Target-interface effects | Optional | Metadata-only `Open` remains passive. `WithSWD`/`ConfigureSWD` or `WithJTAG`/`ConfigureJTAG` selects the advertised interface and requests a whole-kHz target clock no greater than the caller's ceiling. Conflicting open options fail before traffic. The clock command has no application response. Close does not restore an unknown prior interface or clock. | -| SWD adapter | Yes | A configured session implements `swd.Wire` and `swd.TransferLimits` through scan v3. It masks output where the target drives SWDIO and reports the configured clock and conservative scan limit. | -| JTAG adapter | Yes | A configured session implements `jtag.Wire` and `jtag.TransferLimits` through scan v3. TMS and TDI are independent packed streams, and TDO is returned without the SWD sample correction. The conservative 504-bit limit can be lowered by workspace. Wrong-protocol calls fail before traffic. | -| Scan completion | Yes | Samples and the trailing status byte are read separately. Status 6 reports insufficient probe workspace. Any complete nonzero status requires explicit protocol reconfiguration but does not poison the USB session. No scan is replayed. Starting Close blocks configuration and scans, including after a failed interface release. | -| Ambiguous transfer handling | Yes | A failed, invalid, or progress-free bulk exchange poisons the session. Cancellation after a complete command but before its complete response is likewise ambiguous. The first transfer failure remains visible through cancellation cleanup; later commands require an explicit close and reopen. | -| Metadata-only reopen | HIL | A genuine J-Link EDU Mini V2 completed 100 consecutive reopen tests, or 200 fresh sessions, on macOS. Every session returned its full firmware record, 256 capability bits, hardware version, workspace, available interfaces, and current interface. The selected interface remained SWD. No scan or target-control command was sent. | -| Read-only SWD composition | HIL | At a requested 100 kHz, a genuine J-Link EDU Mini V2 reported a 504-bit scan limit and completed ten full restoration runs against a Cortex-M target. Each run used two fresh sessions, read DPIDR `0x2BA01477`, AP0 IDR `0x24770011`, CPUID `0x410FC241` with part `0xC24`, and DHCSR, and matched DPIDR, CPUID, and DHCSR.S_HALT across reopen. The saved AP0 CSW and TAR values were restored before release. An earlier target returned DPIDR `0x0BB11477`, AP0 IDR `0x04770021`, and Cortex-M0 CPUID `0x410CC200`. | +| Capability | Implemented | Validation and boundary | +| ------------------------------- | ----------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Exact discovery catalog | Yes | Reviewed SEGGER application PIDs only; CDC-only `0x0106`, CMSIS-DAP `0x1008`, vendor wildcards, and inferred neighboring products are excluded. | +| Application interface selection | Yes | Requires one unambiguous `ff/ff/ff` alternate setting with exactly one bulk IN and one bulk OUT endpoint. The descriptors select the interface, alternate, and endpoint addresses; after selection, the session resolves the active endpoint properties and uses the active bulk IN maximum packet size. An immediate reopen retries only `usb.ErrNotConfigured`, at 10 ms intervals until configuration appears or the caller's context ends; other inspection errors return immediately. | +| Firmware record | Yes | Retains the complete length-delimited record and exposes its first NUL-delimited field for display. | +| Capabilities | Yes | Preserves the opaque short or long bitset. The long query is gated by short bit 31, and the common prefix must agree. | +| Optional metadata | Yes | Capability-gated hardware version, workspace hint, available target interfaces, and current target interface. A selected interface outside the 0–31 range represented by the availability mask is rejected. | +| Target-interface effects | Optional | Metadata-only `Open` remains passive. `WithSWD`/`ConfigureSWD` or `WithJTAG`/`ConfigureJTAG` selects the advertised interface and requests a whole-kHz target clock no greater than the caller's ceiling. Conflicting open options fail before traffic. The clock command has no application response. Close does not restore an unknown prior interface or clock. | +| SWD adapter | Yes | A configured session implements `swd.Wire` and `swd.TransferLimits` through scan v3. It masks output where the target drives SWDIO and reports the configured clock and conservative scan limit. | +| JTAG adapter | Yes | A configured session implements `jtag.Wire` and `jtag.TransferLimits` through scan v3. TMS and TDI are independent packed streams, and TDO is returned without the SWD sample correction. The conservative 504-bit limit can be lowered by workspace. Wrong-protocol calls fail before traffic. | +| Scan completion | Yes | Samples and the trailing status byte are read separately. Status 6 reports insufficient probe workspace. Any complete nonzero status requires explicit protocol reconfiguration but does not poison the USB session. No scan is replayed. Starting Close blocks configuration and scans, including after a failed interface release. | +| Ambiguous transfer handling | Yes | A failed, invalid, or progress-free bulk exchange poisons the session. Cancellation after a complete command but before its complete response is likewise ambiguous. The first transfer failure remains visible through cancellation cleanup; later commands require an explicit close and reopen. | +| Metadata-only reopen | HIL | A genuine J-Link EDU Mini V2 completed 100 consecutive reopen tests, or 200 fresh sessions, on macOS. Every session returned its full firmware record, 256 capability bits, hardware version, workspace, available interfaces, and current interface. The selected interface remained SWD. No scan or target-control command was sent. | +| Read-only SWD composition | HIL | At a requested 100 kHz, a genuine J-Link EDU Mini V2 reported a 504-bit scan limit and completed ten full restoration runs against a Cortex-M target. Each run used two fresh sessions, read DPIDR `0x2BA01477`, AP0 IDR `0x24770011`, CPUID `0x410FC241` with part `0xC24`, and DHCSR, and matched DPIDR, CPUID, and DHCSR.S_HALT across reopen. The saved AP0 CSW and TAR values were restored before release. An earlier target returned DPIDR `0x0BB11477`, AP0 IDR `0x04770021`, and Cortex-M0 CPUID `0x410CC200`. | `jlink.Candidates` applies the reviewed product catalog to a detached snapshot. -Import `jlink/discovery` to register those bindings with `discover`, or call -its `Register` with an explicit registry. The J-Link session does not depend -on FTDI. The tested EDU Mini returned target-input -samples displaced by one clock. The correction is gated to its USB product and -full firmware record; for other firmware records, the package returns the -samples unchanged. - -The J-Link JTAG bench on Nostalgia uses EDU Mini V2 serial `000802011345` -and a two-TAP ESP32 chain. Direct and registered-probe paths at 100 kHz found -two `0x120034e5` IDCODEs, measured total IR length 10, validated an explicit -5+5 layout, and read each TAP's IDCODE while bypassing the other. Both paths +Import `jlink/discovery` to register those bindings with `discover`, or call its +`Register` with an explicit registry. The J-Link session does not depend on +FTDI. The tested EDU Mini returned target-input samples displaced by one clock. +The correction is gated to its USB product and full firmware record; for other +firmware records, the package returns the samples unchanged. + +The J-Link JTAG bench on Nostalgia uses EDU Mini V2 serial `000802011345` and a +two-TAP ESP32 chain. Direct and registered-probe paths at 100 kHz found two +`0x120034e5` IDCODEs, measured total IR length 10, validated an explicit 5+5 +layout, and read each TAP's IDCODE while bypassing the other. Both paths released the chain to BYPASS/Idle and closed USB. This does not establish target-memory access, run control, or support for other probe firmware. @@ -163,155 +160,151 @@ target-memory access, run control, or support for other probe firmware. Import `cmsisdap/discovery` to register the existing case-sensitive product shortlist with `discover`, or call its `Register` with an explicit registry. -Classification does not validate the protocol; the selected owner still -requires a supported v2 interface when SWD is activated. - -| Capability | Implemented | Validation and boundary | -| --- | --- | --- | -| Candidate shortlist | Yes | Case-sensitive `CMSIS-DAP` match against the host-visible USB product string. The caller enumerates every USB attachment and makes the exact selection; a marker is not proof of protocol support. | -| Command interface selection | Yes | Requires one unambiguous `ff/00/00` alternate whose descriptor-ordered endpoints are bulk OUT, bulk IN, and optionally a distinct bulk IN for SWO. An explicitly selected composite attachment need not have the marker in its device product string. HID/v1 is rejected with `ErrNoV2Interface`. | -| Metadata-only session | Yes | Queries packet geometry, capabilities, protocol, vendor, product, serial, and firmware through `DAP_Info`. Missing product or serial values fall back to USB strings. No `DAP_Connect` or target command is sent. | -| Command scheduling | Yes | Submits one negotiated-packet-sized response IN request before each command OUT request. Packet count is reported but does not enable pipelining. An ambiguous exchange poisons the session and commands are not replayed. | -| SWD configuration | Optional | `WithSWD` or `ConfigureSWD` requires CMSIS-DAP 1.2 or later and the advertised SWD capability, sends `DAP_Connect(SWD)`, and requests a nonzero maximum clock through `DAP_SWJ_Clock`. `MaxClockHz` reports the accepted request, not an attained rate. Reconfiguration disconnects the active port first. | -| SWD adapter | Yes | A configured session implements `swd.Wire` and `swd.TransferLimits` through direction-explicit `DAP_SWD_Sequence`. Runs are at most 64 cycles, commands and responses stay within the negotiated packet size, and one logical call may use several unpipelined command exchanges up to a conservative 16,384-bit limit. Output is omitted while the target owns SWDIO. | -| Sequence failures | Yes | Complete command errors stop at the failing packet without replay and keep the command stream synchronized. Wrong command IDs, short captured data, and ambiguous USB exchanges poison the session. The probe may already have clocked the prefix sent in earlier packets. | -| 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. 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. | +Classification does not validate the protocol; the selected owner still requires +a supported v2 interface when SWD is activated. + +| Capability | Implemented | Validation and boundary | +| --------------------------- | ----------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Candidate shortlist | Yes | Case-sensitive `CMSIS-DAP` match against the host-visible USB product string. The caller enumerates every USB attachment and makes the exact selection; a marker is not proof of protocol support. | +| Command interface selection | Yes | Requires one unambiguous `ff/00/00` alternate whose descriptor-ordered endpoints are bulk OUT, bulk IN, and optionally a distinct bulk IN for SWO. An explicitly selected composite attachment need not have the marker in its device product string. HID/v1 is rejected with `ErrNoV2Interface`. | +| Metadata-only session | Yes | Queries packet geometry, capabilities, protocol, vendor, product, serial, and firmware through `DAP_Info`. Missing product or serial values fall back to USB strings. No `DAP_Connect` or target command is sent. | +| Command scheduling | Yes | Submits one negotiated-packet-sized response IN request before each command OUT request. Packet count is reported but does not enable pipelining. An ambiguous exchange poisons the session and commands are not replayed. | +| SWD configuration | Optional | `WithSWD` or `ConfigureSWD` requires CMSIS-DAP 1.2 or later and the advertised SWD capability, sends `DAP_Connect(SWD)`, and requests a nonzero maximum clock through `DAP_SWJ_Clock`. `MaxClockHz` reports the accepted request, not an attained rate. Reconfiguration disconnects the active port first. | +| SWD adapter | Yes | A configured session implements `swd.Wire` and `swd.TransferLimits` through direction-explicit `DAP_SWD_Sequence`. Runs are at most 64 cycles, commands and responses stay within the negotiated packet size, and one logical call may use several unpipelined command exchanges up to a conservative 16,384-bit limit. Output is omitted while the target owns SWDIO. | +| Sequence failures | Yes | Complete command errors stop at the failing packet without replay and keep the command stream synchronized. Wrong command IDs, short captured data, and ambiguous USB exchanges poison the session. The probe may already have clocked the prefix sent in earlier packets. | +| 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. 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, packet, ownership, and current bench boundaries. ## Serial Wire Debug -| Capability | Implemented | Validation and boundary | -| --- | --- | --- | -| Line reset | Yes | The wire sequence and simulated STICKYORUN and DLCR effects are unit tested. | -| JTAG-to-SWD selection | Yes | Unit tested and used by every SWD HIL path. | -| Connection lifecycle | Yes | `Connect` validates DPIDR before configuration, establishes bank zero and CTRL/STAT framing, and tries to enable ORUNDETECT. It uses overrun framing only if the bit reads back as set. `Release` restores the inherited setting and can be retried after failure. Register access remains blocked before Connect and after Release. | -| DP and AP requests | Yes | Separate `ReadDP`, `WriteDP`, `ReadAP`, and `WriteAP` calls validate the physical register address before sending one request with header, turnaround, data, and idle cycles. | -| ACK classification | Yes | OK, WAIT, FAULT, and invalid acknowledgements are distinguished. | -| Overrun response framing | Yes | A connected target with ORUNDETECT set uses one fixed request, acknowledgement, data, turnaround, and idle frame. WAIT and FAULT include the data phase. | -| Read parity | Yes | Invalid read parity is reported. | -| Automatic retries | No | A raw register call does not replay the requested transaction. In overrun mode it clears STICKYORUN before returning WAIT; retry policy belongs to the caller. | -| Ordered raw queue | Yes | `swd.Batch` validates all queued DP/AP operations before traffic, sends them in order, resolves direction-specific results, and never replays the operation which first fails. | -| Fixed-frame batching | Yes | In overrun mode the ordered queue packs complete 54-bit frames up to an optional wire limit; simple mode remains sequential. Operations in a failed physical chunk are indeterminate; later chunks remain unsent, and requested operations are never replayed. | -| Dormant activation | Yes | `Connect`, and `Release` when repairing framing, try JTAG-to-dormant and dormant-to-SWD once after a completed invalid-ACK response to the initial DPIDR read. Each wire call uses at most 136 clocks. Ordinary register calls are not retried. | -| Multidrop selection | No | The public connection models one entered SWD target. | -| Behavioral simulation | Yes | Protocol entry and line-reset effects, live overrun response grammar, DP/AP register transfers, packed fixed frames, transfer limits, and request-phase WAIT or FAULT injection. | -| Physical DPIDR read | HIL | Opt-in FTDI test and trivial example on Linux and macOS, plus an opt-in J-Link test on macOS. Two fresh RP2350/J-Link sessions read DPIDR `0x4c013477`, reconnected with matching identity, and completed release after line-reset repair; see the SWD guide for preparation and limits. | - -The public `swd.Wire` boundary is implemented by FTDI, J-Link, and CMSIS-DAP -and can be borrowed through a generic `probe.Probe` owner. -The [Serial Wire Debug guide](protocols/swd.md) gives the bit-level protocol, +| Capability | Implemented | Validation and boundary | +| ------------------------ | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| Line reset | Yes | The wire sequence and simulated STICKYORUN and DLCR effects are unit tested. | +| JTAG-to-SWD selection | Yes | Unit tested and used by every SWD HIL path. | +| Connection lifecycle | Yes | `Connect` validates DPIDR before configuration, establishes bank zero and CTRL/STAT framing, and tries to enable ORUNDETECT. It uses overrun framing only if the bit reads back as set. `Release` restores the inherited setting and can be retried after failure. Register access remains blocked before Connect and after Release. | +| DP and AP requests | Yes | Separate `ReadDP`, `WriteDP`, `ReadAP`, and `WriteAP` calls validate the physical register address before sending one request with header, turnaround, data, and idle cycles. | +| ACK classification | Yes | OK, WAIT, FAULT, and invalid acknowledgements are distinguished. | +| Overrun response framing | Yes | A connected target with ORUNDETECT set uses one fixed request, acknowledgement, data, turnaround, and idle frame. WAIT and FAULT include the data phase. | +| Read parity | Yes | Invalid read parity is reported. | +| Automatic retries | No | A raw register call does not replay the requested transaction. In overrun mode it clears STICKYORUN before returning WAIT; retry policy belongs to the caller. | +| Ordered raw queue | Yes | `swd.Batch` validates all queued DP/AP operations before traffic, sends them in order, resolves direction-specific results, and never replays the operation which first fails. | +| Fixed-frame batching | Yes | In overrun mode the ordered queue packs complete 54-bit frames up to an optional wire limit; simple mode remains sequential. Operations in a failed physical chunk are indeterminate; later chunks remain unsent, and requested operations are never replayed. | +| Dormant activation | Yes | `Connect`, and `Release` when repairing framing, try JTAG-to-dormant and dormant-to-SWD once after a completed invalid-ACK response to the initial DPIDR read. Each wire call uses at most 136 clocks. Ordinary register calls are not retried. | +| Multidrop selection | No | The public connection models one entered SWD target. | +| Behavioral simulation | Yes | Protocol entry and line-reset effects, live overrun response grammar, DP/AP register transfers, packed fixed frames, transfer limits, and request-phase WAIT or FAULT injection. | +| Physical DPIDR read | HIL | Opt-in FTDI test and trivial example on Linux and macOS, plus an opt-in J-Link test on macOS. Two fresh RP2350/J-Link sessions read DPIDR `0x4c013477`, reconnected with matching identity, and completed release after line-reset repair; see the SWD guide for preparation and limits. | + +The public `swd.Wire` boundary is implemented by FTDI, J-Link, and CMSIS-DAP and +can be borrowed through a generic `probe.Probe` owner. The +[Serial Wire Debug guide](protocols/swd.md) gives the bit-level protocol, specification notes, and current physical observation. ## JTAG `jtag.Conn` provides explicit TAP reset, state movement, complete IR/DR scans, -and idle clocks over a supplied wire, with bounded transfers and -unknown-state recovery after wire failures. -Hardware-independent tests cover the state graph, reset sequence, transfer -limits, and cancellation. FTDI and J-Link supply bundled `jtag.Wire` -implementations. Bounded discovery distinguishes IDCODE and bypass entries. -IR measurement checks total length without inferring individual boundaries. -Explicit chain layouts validate reset identities, total length, and capture -boundaries; selected-TAP scans own bypass padding and detect stale -instruction selection. -Behavioral tests cover multiple TAPs, dummy DAPs, invalid lengths, borrowed -surface invalidation, and retryable release. The FT4232H bench above exercises -these chain operations; the DAP layer below also supports baseline JTAG-DP. -See [JTAG](protocols/jtag.md) for effects and ownership. +and idle clocks over a supplied wire, with bounded transfers and unknown-state +recovery after wire failures. Hardware-independent tests cover the state graph, +reset sequence, transfer limits, and cancellation. FTDI and J-Link supply +bundled `jtag.Wire` implementations. Bounded discovery distinguishes IDCODE and +bypass entries. IR measurement checks total length without inferring individual +boundaries. Explicit chain layouts validate reset identities, total length, and +capture boundaries; selected-TAP scans own bypass padding and detect stale +instruction selection. Behavioral tests cover multiple TAPs, dummy DAPs, invalid +lengths, borrowed surface invalidation, and retryable release. The FT4232H bench +above exercises these chain operations; the DAP layer below also supports +baseline JTAG-DP. See [JTAG](protocols/jtag.md) for effects and ownership. ## Debug Access Port and MEM-AP -| Capability | Implemented | Validation and boundary | -| --- | --- | --- | -| DPIDR decoding | Yes | Validates the constant bit and exposes raw identity fields. | -| Connection identity | Yes | `Connect` returns an opaque `Identity`; `DPIDR()` and `IDCODE()` distinguish present identification registers. SW-DP supplies only DPIDR; JTAG-DP supplies only IDCODE. The last successful identity remains cached after release or cleanup failure. | -| Port binding | Yes | `NewDebugPort` takes an opaque `Port` constructed with `SWDP(conn)` or `JTAGDP(chain, tapIndex)`. Constructors send no traffic. JTAG requires a complete explicit layout and a zero-based, TDO-first index with a four- or eight-bit IR. The zero binding and invalid inputs fail before hardware access. | -| Baseline JTAG-DP | Yes | Supports IDCODE, CTRL/STAT, readable SELECT, RDBUFF, baseline ABORT, and AP access. The executor tracks delayed responses, polls accepted operations without replay, and checks CTRL/STAT after every AP operation. Inherited ORUNDETECT is temporarily disabled and restored; active pushed or counted modes are rejected. Exact chain revalidation precedes restoration after scan-state loss. Independent cleanup defaults to thirty seconds and can be configured with `WithCleanupTimeout`. | -| SW-DP connection | Yes | `DebugPort.Connect` uses the SWD connection's established DPIDR and response grammar, then requests acknowledged power. It records newly requested power bits before writing them and attempts bounded cleanup after failed setup. Failed cleanup remains retryable through `Release`. | -| SW-DP release | Yes | Restores SELECT to bank zero, settles that write through RDBUFF, clears only power requests acquired by the debug port, then releases the SWD connection. If framing is unknown, cleanup first performs bounded SWD re-entry and verifies DPIDR against the connection being cleaned up. Failed release can be retried and blocks ordinary operations in the meantime. | -| DP register access | Yes | Logical ADIv5 registers distinguish operations which share a wire offset, including DPIDR from ABORT and SELECT from RESEND. Access selects the required bank while preserving known AP fields. CTRL/STAT writes must preserve connection-owned ORUNDETECT. Wrong directions, unknown registers, unavailable DP versions, and writes requiring unsupported turnaround fail before traffic. DAPABORT invalidates AP-derived state. Access is blocked while cleanup is pending. | -| AP identity | Yes | `NewAPSel` constructs an AP selector whose zero value is invalid. `ReadAPIDR` reads the common read-only IDR, and `DecodeAPIDR` exposes its ADIv5 fields. | -| Raw AP access | Yes | `APSel.Address` combines a selector with a complete eight-bit register address; both types have invalid zero values. Immediate and queued operations reject invalid or unaligned addresses before traffic. Reads use RDBUFF and writes use a completion barrier. Either operation invalidates existing MEM-AP handles if it completes or might have completed. A request canceled before it is sent does not invalidate them. Raw access has the effects defined by the selected AP class; a MEM-AP data-register write can write target memory. APIDR writes fail before traffic. | -| Ordered transactions | Yes | A single-use queue validates every DP/AP operation before traffic and settles any earlier immediate DP write before the queue runs. Queued reads expose data through `ReadResult.Value`; queued writes expose completion through `WriteResult.Err`. DP writes and AP operations settle through RDBUFF. Ordinary fixed frames use SWD batches; DPIDR, CTRL/STAT, and ABORT remain physical boundaries. The queue retains a confirmed prefix after failure and distinguishes later unsent work from operations in a failed physical chunk. FT232H HIL completed nine requests in two SWDIO calls. | -| WAIT handling | Yes | SWD retries a rejected physical request after required STICKYORUN cleanup. JTAG polls the preceding accepted request; requests shifted during WAIT were discarded. `WithMaxWaits` bounds WAIT observations; zero uses only the operation context, and `SetMaxWaits` changes the bound while idle. Cancellation supplies the operation error, while independent cleanup failures remain visible. Pending AP work that exhausts its bound requires ABORT and AP-state invalidation, without replay. An accepted JTAG operation remains indeterminate, not unexecuted, when its completion cannot be established. | -| FAULT recovery | Yes | A FAULT is never replayed. With a known response grammar and bank-zero selection, the error includes the captured CTRL/STAT value and DAP clears only the sticky conditions reported there, then verifies that they are clear. A definitely abandoned AP write does not invalidate MEM-AP state; an uncertain effect does. Failed cleanup preserves the FAULT and blocks ordinary traffic until release repairs the port. | -| AP enumeration | Yes | Scans all 256 ADIv5 APSEL values in bounded transactions. IDR zero means absent; a FAULT returns the confirmed discoveries with the error. The current Cortex-M bench reports AP0 as `0x24770011` and AP1 as `0x02880000`; sparse numbering is covered by simulation. The scan used 32 SWDIO calls for 1,022 fixed frames, all with OK acknowledgements. | -| 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` 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. | -| Block writes | Yes | Uses the block-read geometry, bounded chunks, and the binding's WAIT policy. If single address increment is unavailable, `WriteBlock` writes TAR before each word. Accepted writes are never replayed. SWD confirms buffered chunks through RDBUFF; sequential JTAG checks CTRL/STAT after each write and can return a confirmed prefix within a chunk. An uncertain write reports `ErrIndeterminate` and invalidates the MEM-AP without replay. | -| DPv3 discovery registers | Yes | SW-DP supports DPIDR1, BASEPTR0/1, SELECT1, and bank-zero DPIDR reads. Immediate AP access supports ADIv6 base addresses and 4 KiB register windows. Queued AP operations, scalar/block MEM-AP access, and managed ownership support ADIv6 with sequential completion. | -| Later JTAG-DP versions | No | JTAG uses the original ADIv5 register set, without version detection or banked DP registers. | -| Behavioral simulation | Yes | DP identity/power, posted AP access, and byte-addressed MEM-AP reads and writes in either target byte order. AP fixtures require DPv0 through DPv3 and `dap.APSel` values matching the simulated DP architecture. Configure DPIDR1 before adding DPv3 fixtures; their bases must fit its supported address width. All AP fixtures reject mismatched or duplicate selectors, zero APIDRs, non-MEM-AP identities passed to `AddMEMAP`, and unaligned target-word addresses. | -| DAP-composed SWD entry | HIL | The FT232H/Cortex-M AP, transaction, and MEM-AP tests each counted one SWD connection performed by `DebugPort.Connect`; the reconnect test counted two. | -| JTAG-DP and AP1 memory identity | HIL | On Nostalgia, FT4232H `01691`/A at 100 kHz with Arm `0x5ba00477`/IR4 and Xilinx `0x14730093`/IR12: two fresh direct-driver sessions and two fresh discovered-probe sessions passed. AP1 IDR was `0x44770002`; component words at `0x80410ff0` through `0x80410ffc` were `0x0d`, `0x90`, `0x05`, `0xb1`. Every session restored CSW/TAR and owned power state, released the chain, and closed the probe; fresh sessions found the same inherited power/control state. Board activation was external; no halt, target reset, or target-memory write was exercised. See the [DAP bench procedure](ports/dap.md#ftdi-jtag-dp-bench). | -| AP and MEM-AP access | HIL | Opt-in FTDI integration tests against an explicitly selected AP. One transaction clocked nine fixed requests in two SWDIO calls and received nine OK acknowledgements. A 64-byte block read matched scalar byte reads from the same SRAM range and counted 571 OK acknowledgements, no WAIT, FAULT, or invalid acknowledgement, and 563 fixed frames. Separately gated tests preserved that range, exercised 8-, 16-, and 32-bit scalar writes plus aligned 64-byte and unaligned 31-byte block writes, checked neighboring bytes, then restored and verified the original contents. The scalar-write test counted 3,130 OK acknowledgements and 3,122 fixed frames; the block-write test counted 777 OK acknowledgements and 769 fixed frames. Neither returned WAIT, FAULT, or an invalid acknowledgement. The selected range did not cross a TAR boundary, and the target did not advertise CFG.LD. | +| Capability | Implemented | Validation and boundary | +| ------------------------------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| DPIDR decoding | Yes | Validates the constant bit and exposes raw identity fields. | +| Connection identity | Yes | `Connect` returns an opaque `Identity`; `DPIDR()` and `IDCODE()` distinguish present identification registers. SW-DP supplies only DPIDR; JTAG-DP supplies only IDCODE. The last successful identity remains cached after release or cleanup failure. | +| Port binding | Yes | `NewDebugPort` takes an opaque `Port` constructed with `SWDP(conn)` or `JTAGDP(chain, tapIndex)`. Constructors send no traffic. JTAG requires a complete explicit layout and a zero-based, TDO-first index with a four- or eight-bit IR. The zero binding and invalid inputs fail before hardware access. | +| Baseline JTAG-DP | Yes | Supports IDCODE, CTRL/STAT, readable SELECT, RDBUFF, baseline ABORT, and AP access. The executor tracks delayed responses, polls accepted operations without replay, and checks CTRL/STAT after every AP operation. Inherited ORUNDETECT is temporarily disabled and restored; active pushed or counted modes are rejected. Exact chain revalidation precedes restoration after scan-state loss. Independent cleanup defaults to thirty seconds and can be configured with `WithCleanupTimeout`. | +| SW-DP connection | Yes | `DebugPort.Connect` uses the SWD connection's established DPIDR and response grammar, then requests acknowledged power. It records newly requested power bits before writing them and attempts bounded cleanup after failed setup. Failed cleanup remains retryable through `Release`. | +| SW-DP release | Yes | Restores SELECT to bank zero, settles that write through RDBUFF, clears only power requests acquired by the debug port, then releases the SWD connection. If framing is unknown, cleanup first performs bounded SWD re-entry and verifies DPIDR against the connection being cleaned up. Failed release can be retried and blocks ordinary operations in the meantime. | +| DP register access | Yes | Logical ADIv5 registers distinguish operations which share a wire offset, including DPIDR from ABORT and SELECT from RESEND. Access selects the required bank while preserving known AP fields. CTRL/STAT writes must preserve connection-owned ORUNDETECT. Wrong directions, unknown registers, unavailable DP versions, and writes requiring unsupported turnaround fail before traffic. DAPABORT invalidates AP-derived state. Access is blocked while cleanup is pending. | +| AP identity | Yes | `NewAPSel` constructs an AP selector whose zero value is invalid. `ReadAPIDR` reads the common read-only IDR, and `DecodeAPIDR` exposes its ADIv5 fields. | +| Raw AP access | Yes | `APSel.Address` combines a selector with a complete eight-bit register address; both types have invalid zero values. Immediate and queued operations reject invalid or unaligned addresses before traffic. Reads use RDBUFF and writes use a completion barrier. Either operation invalidates existing MEM-AP handles if it completes or might have completed. A request canceled before it is sent does not invalidate them. Raw access has the effects defined by the selected AP class; a MEM-AP data-register write can write target memory. APIDR writes fail before traffic. | +| Ordered transactions | Yes | A single-use queue validates every DP/AP operation before traffic and settles any earlier immediate DP write before the queue runs. Queued reads expose data through `ReadResult.Value`; queued writes expose completion through `WriteResult.Err`. DP writes and AP operations settle through RDBUFF. Ordinary fixed frames use SWD batches; DPIDR, CTRL/STAT, and ABORT remain physical boundaries. The queue retains a confirmed prefix after failure and distinguishes later unsent work from operations in a failed physical chunk. FT232H HIL completed nine requests in two SWDIO calls. | +| WAIT handling | Yes | SWD retries a rejected physical request after required STICKYORUN cleanup. JTAG polls the preceding accepted request; requests shifted during WAIT were discarded. `WithMaxWaits` bounds WAIT observations; zero uses only the operation context, and `SetMaxWaits` changes the bound while idle. Cancellation supplies the operation error, while independent cleanup failures remain visible. Pending AP work that exhausts its bound requires ABORT and AP-state invalidation, without replay. An accepted JTAG operation remains indeterminate, not unexecuted, when its completion cannot be established. | +| FAULT recovery | Yes | A FAULT is never replayed. With a known response grammar and bank-zero selection, the error includes the captured CTRL/STAT value and DAP clears only the sticky conditions reported there, then verifies that they are clear. A definitely abandoned AP write does not invalidate MEM-AP state; an uncertain effect does. Failed cleanup preserves the FAULT and blocks ordinary traffic until release repairs the port. | +| AP enumeration | Yes | Scans all 256 ADIv5 APSEL values in bounded transactions. IDR zero means absent; a FAULT returns the confirmed discoveries with the error. The current Cortex-M bench reports AP0 as `0x24770011` and AP1 as `0x02880000`; sparse numbering is covered by simulation. The scan used 32 SWDIO calls for 1,022 fixed frames, all with OK acknowledgements. | +| 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` 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. | +| Block writes | Yes | Uses the block-read geometry, bounded chunks, and the binding's WAIT policy. If single address increment is unavailable, `WriteBlock` writes TAR before each word. Accepted writes are never replayed. SWD confirms buffered chunks through RDBUFF; sequential JTAG checks CTRL/STAT after each write and can return a confirmed prefix within a chunk. An uncertain write reports `ErrIndeterminate` and invalidates the MEM-AP without replay. | +| DPv3 discovery registers | Yes | SW-DP supports DPIDR1, BASEPTR0/1, SELECT1, and bank-zero DPIDR reads. Immediate AP access supports ADIv6 base addresses and 4 KiB register windows. Queued AP operations, scalar/block MEM-AP access, and managed ownership support ADIv6 with sequential completion. | +| Later JTAG-DP versions | No | JTAG uses the original ADIv5 register set, without version detection or banked DP registers. | +| Behavioral simulation | Yes | DP identity/power, posted AP access, and byte-addressed MEM-AP reads and writes in either target byte order. AP fixtures require DPv0 through DPv3 and `dap.APSel` values matching the simulated DP architecture. Configure DPIDR1 before adding DPv3 fixtures; their bases must fit its supported address width. All AP fixtures reject mismatched or duplicate selectors, zero APIDRs, non-MEM-AP identities passed to `AddMEMAP`, and unaligned target-word addresses. | +| DAP-composed SWD entry | HIL | The FT232H/Cortex-M AP, transaction, and MEM-AP tests each counted one SWD connection performed by `DebugPort.Connect`; the reconnect test counted two. | +| JTAG-DP and AP1 memory identity | HIL | On Nostalgia, FT4232H `01691`/A at 100 kHz with Arm `0x5ba00477`/IR4 and Xilinx `0x14730093`/IR12: two fresh direct-driver sessions and two fresh discovered-probe sessions passed. AP1 IDR was `0x44770002`; component words at `0x80410ff0` through `0x80410ffc` were `0x0d`, `0x90`, `0x05`, `0xb1`. Every session restored CSW/TAR and owned power state, released the chain, and closed the probe; fresh sessions found the same inherited power/control state. Board activation was external; no halt, target reset, or target-memory write was exercised. See the [DAP bench procedure](ports/dap.md#ftdi-jtag-dp-bench). | +| AP and MEM-AP access | HIL | Opt-in FTDI integration tests against an explicitly selected AP. One transaction clocked nine fixed requests in two SWDIO calls and received nine OK acknowledgements. A 64-byte block read matched scalar byte reads from the same SRAM range and counted 571 OK acknowledgements, no WAIT, FAULT, or invalid acknowledgement, and 563 fixed frames. Separately gated tests preserved that range, exercised 8-, 16-, and 32-bit scalar writes plus aligned 64-byte and unaligned 31-byte block writes, checked neighboring bytes, then restored and verified the original contents. The scalar-write test counted 3,130 OK acknowledgements and 3,122 fixed frames; the block-write test counted 777 OK acknowledgements and 769 fixed frames. Neither returned WAIT, FAULT, or an invalid acknowledgement. The selected range did not cross a TAR boundary, and the target did not advertise CFG.LD. | Connecting and using a MEM-AP changes volatile debug state; its write methods -also change target memory. Applications must release the MEM-AP before the -debug port so CSW, TAR, and TARHI when present are restored, bank selection -returns to zero, and acquired power is released. -Calls which share a debug port, MEM-AP, or SWD connection must be serialized; -the packages do not add locking. -The [Arm Debug Access Port guide](ports/dap.md) describes ADIv5 register -access, posted transactions, power handshakes, and the current bench result. +also change target memory. Applications must release the MEM-AP before the debug +port so CSW, TAR, and TARHI when present are restored, bank selection returns to +zero, and acquired power is released. Calls which share a debug port, MEM-AP, or +SWD connection must be serialized; the packages do not add locking. The +[Arm Debug Access Port guide](ports/dap.md) describes ADIv5 register access, +posted transactions, power handshakes, and the current bench result. ## ADIv6 debug-space inspection -`DebugPort.DebugSpace` supplies a borrowed aligned-word reader for DPv3's -debug address space and reads its advertised BASEPTR0/1 root. It composes with -the bounded CoreSight walker to identify AP bases. Perform this inspection -before acquiring MEM-APs; raw debug-space reads invalidate existing clients. +`DebugPort.DebugSpace` supplies a borrowed aligned-word reader for DPv3's debug +address space and reads its advertised BASEPTR0/1 root. It composes with the +bounded CoreSight walker to identify AP bases. Perform this inspection before +acquiring MEM-APs; raw debug-space reads invalidate existing clients. ## CoreSight component inspection -`coresight.Identify` reads CIDR and PIDR at an explicit 4 KiB aligned page, -then DEVARCH, DEVID, and DEVTYPE for class 9. It preserves unknown -identifiers and performs no target-memory writes. Deterministic tests cover -invalid input, malformed preambles, cancellation, every read failure, and -64-bit addresses; MEM-AP simulation covers both byte orders. -`Component.ROMTable` recognizes class 1 and Arm class 9 ROM geometry; -`ReadEntry` decodes one entry, including its table-scoped power metadata, -without accessing the child. `Walk` follows tables with explicit depth, -component, and entry limits, preserves partial results, rejects repeated -tables, and skips power-domain children. It does not unlock components, or -infer the cause of inaccessible memory. See [CoreSight component -identity](coresight.md) for the two-session micro:bit SWD and ZCU104 JTAG -hardware observations and their limits. +`coresight.Identify` reads CIDR and PIDR at an explicit 4 KiB aligned page, then +DEVARCH, DEVID, and DEVTYPE for class 9. It preserves unknown identifiers and +performs no target-memory writes. Deterministic tests cover invalid input, +malformed preambles, cancellation, every read failure, and 64-bit addresses; +MEM-AP simulation covers both byte orders. `Component.ROMTable` recognizes class +1 and Arm class 9 ROM geometry; `ReadEntry` decodes one entry, including its +table-scoped power metadata, without accessing the child. `Walk` follows tables +with explicit depth, component, and entry limits, preserves partial results, +rejects repeated tables, and skips power-domain children. It does not unlock +components, or infer the cause of inaccessible memory. See +[CoreSight component identity](coresight.md) for the two-session micro:bit SWD +and ZCU104 JTAG hardware observations and their limits. ROM traversal HIL completed six identities on the micro:bit. On ZCU104 it returned seventeen identities and a failed visit at `0x803e0000`, then stopped. -Both observations repeated in fresh sessions with successful owner close. -See the [ROM traversal hardware evidence](coresight.md#rom-traversal-hardware-evidence) -for exact selections, bounds, and the incomplete ZCU104 result. Class 9 table -layouts and power-domain skips have hardware-independent test coverage. +Both observations repeated in fresh sessions with successful owner close. See +the [ROM traversal hardware evidence][rom-evidence] for exact selections, +bounds, and the incomplete ZCU104 result. Class 9 table layouts and power-domain +skips have hardware-independent test coverage. ## Cortex-M target operations -| Capability | Implemented | Validation and boundary | -| --- | --- | --- | -| 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-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). | -| Register reads | Yes | Halted Cortex-M0 R0–R12, SP, LR, PC, XPSR, MSP, and PSP through `ReadRegister`. Two fresh CMSIS-DAP micro:bit sessions read all 19 registers; transfer failures and cleanup have behavioral coverage. | -| Register writes | Yes | Halted Cortex-M0 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 micro:bit sessions wrote and restored R4, SP, MSP, PSP, and PC before resuming; see the [register bench](cortexm.md#register-bench). | -| 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 control currently accepts Cortex-M0 only. -See [Cortex-M control](cortexm.md) for its effects and cleanup limits. +| Capability | Implemented | Validation and boundary | +| ------------------------------------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| 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-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). | +| Register reads | Yes | Halted Cortex-M0 R0–R12, SP, LR, PC, XPSR, MSP, and PSP through `ReadRegister`. Two fresh CMSIS-DAP micro:bit sessions read all 19 registers; transfer failures and cleanup have behavioral coverage. | +| Register writes | Yes | Halted Cortex-M0 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 micro:bit sessions wrote and restored R4, SP, MSP, PSP, and PC before resuming; see the [register bench](cortexm.md#register-bench). | +| 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 control currently accepts Cortex-M0 only. See +[Cortex-M control](cortexm.md) for its effects and cleanup limits. ## Executable surfaces @@ -321,15 +314,15 @@ Available examples: - `examples/simple/ap-id` reports DPIDR and one explicitly selected AP IDR. - `examples/simple/cortexm-info` reports DPIDR, AP IDR, and Cortex-M CPUID. - `examples/simple/coresight-info` reads the MEM-AP's advertised component - identity, or an explicitly supplied page, through a managed SWD connection - and selected MEM-AP. Its `-walk` option follows ROM entries with fixed - depth, component, and entry bounds. -- `examples/simple/arm-info` reports the same identities through generic - probe discovery and one Arm debug owner, with explicit AP selection. + identity, or an explicitly supplied page, through a managed SWD connection and + selected MEM-AP. Its `-walk` option follows ROM entries with fixed depth, + component, and entry bounds. +- `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 with PC, SP, R0, and R4 reads, and requires `-allow-control`. -Its optional `-step` performs one architectural step before resume. +halt/resume with PC, SP, R0, and R4 reads, and requires `-allow-control`. Its +optional `-step` performs one architectural step before resume. Available `ost` commands: @@ -341,20 +334,20 @@ ost dap ap id --ap N ost target cortex-m id --ap N ``` -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. +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. ## Not currently provided -There is no CMSIS-DAP HID/v1 transport, automatic probe -discovery policy, +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. +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. See [Composing Ostiole](composition.md) for selecting and -extending the current layers. +project description or recreate its lower-level protocol inside an application. +See [Composing Ostiole](composition.md) for selecting and extending the current +layers. + +[rom-evidence]: coresight.md#rom-traversal-hardware-evidence diff --git a/docs/composition.md b/docs/composition.md index b8d4a0e..d8bc4d6 100644 --- a/docs/composition.md +++ b/docs/composition.md @@ -1,8 +1,8 @@ # Composing Ostiole -Choose the highest-level package that already owns the behavior an -application needs. Drop to a lower layer only when the lower-level operation -is itself the goal. +Choose the highest-level package that already owns the behavior an application +needs. Drop to a lower layer only when the lower-level operation is itself the +goal. For example, a program identifying a Cortex-M should call `cortexm.Identify` rather than read and decode CPUID itself. A program inspecting an access port @@ -14,32 +14,32 @@ data-register write can write target memory. ## Find the right layer -| Task | Public API | Executable reference | -| --- | --- | --- | -| Discover and select among registered probe drivers | `discover.Probes`, `ProbeInventory.Select`, `Candidate.Open`, or `discover.OpenProbe` | `discover/probes` integration tests | -| List every host USB attachment | `usb.New`, `usb.AllDevices`, `Enumerator.List` | Package tests | -| List USB attachments understood by the FTDI driver | `usb.New`, `ftdi.SupportedDevices`, `Enumerator.List` | `ost ftdi list` | -| Read metadata from one J-Link | `usb.New`, `jlink.SupportedDevices`, `Enumerator.Open`, `jlink.Open`, `Session.Info` | Package tests | -| Read metadata from one CMSIS-DAP v2 probe | `usb.New`, `usb.AllDevices`, `cmsisdap.Candidates`, `Enumerator.Open`, `cmsisdap.Open`, `Session.Info` | Package tests | -| Open one FTDI MPSSE SWD port | `Enumerator.Open`, `ftdi.Open` | `examples/trivial/swd-dpidr` | -| Use an FTDI JTAG chain | `ftdi.Open` or `Probe.JTAG`, then `jtag.New` and `jtag.NewChain` | FTDI integration tests | -| Open one J-Link SWD session | `Enumerator.Open`, `jlink.Open`, `jlink.WithSWD` | Package tests | -| Use a J-Link JTAG chain | `jlink.Open` with `WithJTAG` or `Probe.JTAG`, then `jtag.New` and `jtag.NewChain` | J-Link integration tests | -| Open one CMSIS-DAP SWD session | `Enumerator.Open`, `cmsisdap.Open`, `cmsisdap.WithSWD` | Package tests | -| Connect SWD or transfer DP/AP registers | `swd.New`, `Conn.Connect`, `Conn.ReadDP`, `Conn.WriteDP`, `Conn.ReadAP`, `Conn.WriteAP`, `Conn.NewBatch`, `Conn.Release` | `examples/trivial/swd-dpidr` | -| Enter SWD, decode a DPIDR, and manage SW-DP power | `dap.NewDebugPort`, `DebugPort.Connect`, `DebugPort.Release` | `ost dap dp id` | -| Identify one explicitly selected AP | `DebugPort.ReadAPIDR`, `DecodeAPIDR` | `examples/simple/ap-id` | -| Access another AP register by its full register address | `DebugPort.ReadRawAP`, `DebugPort.WriteRawAP` | Package tests | -| Read or write one aligned target scalar through a MEM-AP | `dap.OpenMemAP`, `MemAP.ReadScalar`, `MemAP.WriteScalar`, `MemAP.Release` | `examples/simple/cortexm-info` uses `ReadWord`. | -| Read or write arbitrary target bytes through a MEM-AP | `dap.OpenMemAP`, `MemAP.ReadBlock`, `MemAP.WriteBlock`, `MemAP.Release` | Package tests | -| Obtain a MEM-AP's advertised debug entry | `MemAP.ReadDebugBase` | `examples/simple/coresight-info` | -| Identify one debug component through scalar memory | `coresight.Identify` | `examples/simple/coresight-info` | -| 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` | -| Read or write a halted Cortex-M0 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 | +| Task | Public API | Executable reference | +| -------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------- | +| Discover and select among registered probe drivers | `discover.Probes`, `ProbeInventory.Select`, `Candidate.Open`, or `discover.OpenProbe` | `discover/probes` integration tests | +| List every host USB attachment | `usb.New`, `usb.AllDevices`, `Enumerator.List` | Package tests | +| List USB attachments understood by the FTDI driver | `usb.New`, `ftdi.SupportedDevices`, `Enumerator.List` | `ost ftdi list` | +| Read metadata from one J-Link | `usb.New`, `jlink.SupportedDevices`, `Enumerator.Open`, `jlink.Open`, `Session.Info` | Package tests | +| Read metadata from one CMSIS-DAP v2 probe | `usb.New`, `usb.AllDevices`, `cmsisdap.Candidates`, `Enumerator.Open`, `cmsisdap.Open`, `Session.Info` | Package tests | +| Open one FTDI MPSSE SWD port | `Enumerator.Open`, `ftdi.Open` | `examples/trivial/swd-dpidr` | +| Use an FTDI JTAG chain | `ftdi.Open` or `Probe.JTAG`, then `jtag.New` and `jtag.NewChain` | FTDI integration tests | +| Open one J-Link SWD session | `Enumerator.Open`, `jlink.Open`, `jlink.WithSWD` | Package tests | +| Use a J-Link JTAG chain | `jlink.Open` with `WithJTAG` or `Probe.JTAG`, then `jtag.New` and `jtag.NewChain` | J-Link integration tests | +| Open one CMSIS-DAP SWD session | `Enumerator.Open`, `cmsisdap.Open`, `cmsisdap.WithSWD` | Package tests | +| Connect SWD or transfer DP/AP registers | `swd.New`, `Conn.Connect`, `Conn.ReadDP`, `Conn.WriteDP`, `Conn.ReadAP`, `Conn.WriteAP`, `Conn.NewBatch`, `Conn.Release` | `examples/trivial/swd-dpidr` | +| Enter SWD, decode a DPIDR, and manage SW-DP power | `dap.NewDebugPort`, `DebugPort.Connect`, `DebugPort.Release` | `ost dap dp id` | +| Identify one explicitly selected AP | `DebugPort.ReadAPIDR`, `DecodeAPIDR` | `examples/simple/ap-id` | +| Access another AP register by its full register address | `DebugPort.ReadRawAP`, `DebugPort.WriteRawAP` | Package tests | +| Read or write one aligned target scalar through a MEM-AP | `dap.OpenMemAP`, `MemAP.ReadScalar`, `MemAP.WriteScalar`, `MemAP.Release` | `examples/simple/cortexm-info` uses `ReadWord`. | +| Read or write arbitrary target bytes through a MEM-AP | `dap.OpenMemAP`, `MemAP.ReadBlock`, `MemAP.WriteBlock`, `MemAP.Release` | Package tests | +| Obtain a MEM-AP's advertised debug entry | `MemAP.ReadDebugBase` | `examples/simple/coresight-info` | +| Identify one debug component through scalar memory | `coresight.Identify` | `examples/simple/coresight-info` | +| 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` | +| Read or write a halted Cortex-M0 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 | The examples are intentionally small, executable compositions of public packages. `ost` adds command parsing and output policy, but its internal @@ -47,8 +47,8 @@ packages are not a reusable library surface. ## Select and open hardware explicitly -`armdebug.Connect` owns the connection from an already-open probe through an -Arm SW-DP. Pass an explicit port configuration: +`armdebug.Connect` owns the connection from an already-open probe through an Arm +SW-DP. Pass an explicit port configuration: ```go connected, err := armdebug.Connect(ctx, opened, armdebug.Config{ @@ -63,20 +63,19 @@ if err != nil { port := connected.Port() ``` -The call takes responsibility for `opened` even on invalid input. Stop using -the supplied probe directly. Keep any non-nil returned owner, including on an -error, until `Close` succeeds; the single deferred attempt above reports a -failure but does not replace an application's bounded retry policy. -The borrowed port is for serialized operations, not independent `Connect` or -`Release` calls. Stop using it when owner cleanup begins. A manually acquired -MEM-AP must be released before closing the owner. +The call takes responsibility for `opened` even on invalid input. Stop using the +supplied probe directly. Keep any non-nil returned owner, including on an error, +until `Close` succeeds; the single deferred attempt above reports a failure but +does not replace an application's bounded retry policy. The borrowed port is for +serialized operations, not independent `Connect` or `Release` calls. Stop using +it when owner cleanup begins. A manually acquired MEM-AP must be released before +closing the owner. `Close` releases DAP, which releases SWD or the JTAG chain, before closing the -probe. A failed -release retains the live probe for another attempt. Cleanup uses fresh bounded -contexts, not the operation's possibly canceled context. There is no forced -abandonment. `Port()` returns nil once cleanup starts; previously returned -pointers are governed by the borrowing contract, not forcibly revoked. +probe. A failed release retains the live probe for another attempt. Cleanup uses +fresh bounded contexts, not the operation's possibly canceled context. There is +no forced abandonment. `Port()` returns nil once cleanup starts; previously +returned pointers are governed by the borrowing contract, not forcibly revoked. Use the owner's method to acquire a MEM-AP whose cleanup it will track: @@ -89,13 +88,13 @@ processor, err := cortexm.Identify(ctx, memory) ``` Distinct APs may be acquired and used serially; acquiring the same AP twice -fails before traffic. Do not call `Release` on these borrowed clients. -`Close` releases them in reverse acquisition order before releasing DAP/SWD. -If one release fails, the owner retains that client and all lower dependencies -for retry. An acquisition error preserves existing clients and ownership; -the DAP client's state determines which subsequent operations remain possible. -Clients acquired directly with `dap.OpenMemAP`, rather than this method, are -not tracked and remain the caller's cleanup responsibility. +fails before traffic. Do not call `Release` on these borrowed clients. `Close` +releases them in reverse acquisition order before releasing DAP/SWD. If one +release fails, the owner retains that client and all lower dependencies for +retry. An acquisition error preserves existing clients and ownership; the DAP +client's state determines which subsequent operations remain possible. Clients +acquired directly with `dap.OpenMemAP`, rather than this method, are not tracked +and remain the caller's cleanup responsibility. The generic `examples/simple/arm-info` program uses this ownership path: @@ -103,13 +102,12 @@ The generic `examples/simple/arm-info` program uses this ownership path: go run ./examples/simple/arm-info -provider cmsisdap -serial SERIAL -ap 0 ``` -It defaults to a 1 MHz SW-DP; `-clock` selects the requested ceiling in Hz. -The default meets the micro:bit nRF51's -[startup clock requirement](protocols/cmsisdap.md#nrf51-startup-clock). -It reads DPIDR, AP IDR, and Cortex-M identity, and -attempts owner cleanup up to three times. It does not halt, reset, or write -target memory. Probe filters may be omitted only when selection remains unique; -the AP argument is required. +It defaults to a 1 MHz SW-DP; `-clock` selects the requested ceiling in Hz. The +default meets the micro:bit nRF51's +[startup clock requirement](protocols/cmsisdap.md#nrf51-startup-clock). It reads +DPIDR, AP IDR, and Cortex-M identity, and attempts owner cleanup up to three +times. It does not halt, reset, or write target memory. Probe filters may be +omitted only when selection remains unique; the AP argument is required. A generic tool enables the bundled providers with these imports: @@ -120,9 +118,9 @@ import ( ) ``` -For a smaller binary, blank-import just `jlink/discovery`, `ftdi/discovery`, -or `cmsisdap/discovery`. The core `discover` and `probe` packages import no -USB implementation or concrete driver. Registration performs no hardware I/O. +For a smaller binary, blank-import just `jlink/discovery`, `ftdi/discovery`, or +`cmsisdap/discovery`. The core `discover` and `probe` packages import no USB +implementation or concrete driver. Registration performs no hardware I/O. For an owned Arm debug connection, `armdebug.Open` combines discovery and `Connect`, using the same selection and configuration: @@ -135,16 +133,16 @@ connected, err := armdebug.Open(ctx, selection, armdebug.Config{ Handle the returned owner and error as in the `Connect` example above. Invalid configuration or incomplete discovery prevents opening. For an explicit -registry, use `registry.OpenProbe` and then `armdebug.Connect`; if opening -fails with a probe, close that probe rather than trying to activate it. +registry, use `registry.OpenProbe` and then `armdebug.Connect`; if opening fails +with a probe, close that probe rather than trying to activate it. With providers registered, `discover.OpenProbe(ctx, selection)` combines enumeration, classification, unique selection, and opening. It stops on any discovery error. `discover.Probes(ctx)` returns a partial inventory alongside -errors so an application can inspect those errors before deliberately opening -a surviving candidate. An explicit `discover.Registry` offers the same calls. -All paths leave the same cleanup obligations on the returned owner, including -when opening returns both an owner and an error. +errors so an application can inspect those errors before deliberately opening a +surviving candidate. An explicit `discover.Registry` offers the same calls. All +paths leave the same cleanup obligations on the returned owner, including when +opening returns both an owner and an error. A `discover.ProbeInventory` supports direct iteration and exact selection: @@ -210,9 +208,9 @@ probe invalidates every borrowed wire, including when transport cleanup fails. Providers can instead register on a caller-owned `discover.Registry` with `RegisterTransport`. `EnsureTransport` shares an identical provider dependency -without accepting a different provider under the same ID. Iteration repeats -the detached snapshot without enumerating again. To use `slices.Collect`, -convert the named sequence to `iter.Seq[discover.Transport]` explicitly. +without accepting a different provider under the same ID. Iteration repeats the +detached snapshot without enumerating again. To use `slices.Collect`, convert +the named sequence to `iter.Seq[discover.Transport]` explicitly. Probe order is provider ID, serial, function, location, then product, with binding identity breaking final ties. Transport order is provider ID, serial, @@ -229,10 +227,10 @@ distinguish candidates, and pass it back as an exact filter: selected, err := inventory.Select(discover.Selection{Binding: binding}) ``` -The binding filter combines with every other nonempty filter. An unknown -binding returns not-found; it never falls back to another candidate. Do not -parse the token or assume it survives replugging. An empty `Selection{}` -deliberately selects the sole candidate and reports ambiguity for several. +The binding filter combines with every other nonempty filter. An unknown binding +returns not-found; it never falls back to another candidate. Do not parse the +token or assume it survives replugging. An empty `Selection{}` deliberately +selects the sole candidate and reports ambiguity for several. An application with a `probe.SWDBackend` can transfer it to a generic owner: @@ -246,15 +244,15 @@ if err != nil { connection := swd.New(wire) ``` -Use a named error result for this cleanup pattern. Release any SWD or DAP -state before closing the probe. The caller must stop using the transferred -backend directly; the borrowed wire has no independent close operation. +Use a named error result for this cleanup pattern. Release any SWD or DAP state +before closing the probe. The caller must stop using the transferred backend +directly; the borrowed wire has no independent close operation. FTDI and J-Link owners can instead lend JTAG with `opened.JTAG(ctx, probe.JTAGConfig{MaxClockHz: 100_000})`. Pass the wire to -`jtag.New`, supply an explicit layout, and release the chain before closing -the probe. Retain both after a failed release so cleanup can be retried. -The [JTAG guide](protocols/jtag.md) covers pin wiring and chain ownership. +`jtag.New`, supply an explicit layout, and release the chain before closing the +probe. Retain both after a failed release so cleanup can be retried. The +[JTAG guide](protocols/jtag.md) covers pin wiring and chain ownership. Concrete drivers can open that owner from an exact USB identity: @@ -315,13 +313,12 @@ func openCMSISDAP(ctx context.Context) (_ *cmsisdap.Session, cleanup func() erro On success the session owns the device, and `cleanup` calls `session.Close`. After a failed open whose device cleanup also fails, `cleanup` calls -`device.Close` again. Retain a non-nil cleanup function. Calling it again -either retries a retained interface claim or returns the cached device-close -result. -Product matching is case-sensitive and only shortlists candidates; `Open` -still requires the exact v2 bulk interface. An application which knows a -composite probe by serial or another explicit policy may select it from -`devices` even when its device product string is absent from `Candidates`. +`device.Close` again. Retain a non-nil cleanup function. Calling it again either +retries a retained interface claim or returns the cached device-close result. +Product matching is case-sensitive and only shortlists candidates; `Open` still +requires the exact v2 bulk interface. An application which knows a composite +probe by serial or another explicit policy may select it from `devices` even +when its device product string is absent from `Candidates`. To use that selected v2 probe as an SWD wire, configure it during `Open` and return a cleanup function even when `connection.Connect` fails: @@ -361,17 +358,17 @@ func connectCMSISDAPSWD(ctx context.Context, device *usb.Device) (_ uint32, clea } ``` -`WithSWD` requires the advertised SWD capability, sends `DAP_Connect(SWD)`, -and requests a maximum frequency in hertz through `DAP_SWJ_Clock`. -`MaxClockHz` reports that accepted request because CMSIS-DAP does not report -the attained clock. `SWDIO` converts direction runs to packet-bounded -`DAP_SWD_Sequence` commands; the session remains the USB owner. A -`connection.Release` failure that leaves the session usable, a complete -`DAP_Disconnect` failure, or an interface-release failure leaves cleanup -retryable in reverse order. After a poisoned exchange, the helper records -`ErrSessionPoisoned`, lets `Close` report the abandoned port and finish USB -cleanup without another command, and returns that terminal error. Device close -runs once; later calls to `Session.Close` return its cached result. +`WithSWD` requires the advertised SWD capability, sends `DAP_Connect(SWD)`, and +requests a maximum frequency in hertz through `DAP_SWJ_Clock`. `MaxClockHz` +reports that accepted request because CMSIS-DAP does not report the attained +clock. `SWDIO` converts direction runs to packet-bounded `DAP_SWD_Sequence` +commands; the session remains the USB owner. A `connection.Release` failure that +leaves the session usable, a complete `DAP_Disconnect` failure, or an +interface-release failure leaves cleanup retryable in reverse order. After a +poisoned exchange, the helper records `ErrSessionPoisoned`, lets `Close` report +the abandoned port and finish USB cleanup without another command, and returns +that terminal error. Device close runs once; later calls to `Session.Close` +return its cached result. A metadata-only J-Link session follows the same explicit inventory rule: @@ -405,13 +402,12 @@ func readJLinkInfo(ctx context.Context) (_ jlink.Info, cleanup func() error, err } ``` -Inventory policy still belongs to the application. `jlink.Open` takes -ownership on success. It claims only the J-Link application interface, -resolves the active endpoints after selecting its alternate, and does not -select or configure a target interface. If a close fails, the returned -`cleanup` function keeps the affected device or session reachable. Calling it -again either retries a retained interface claim or returns the cached -device-close result. +Inventory policy still belongs to the application. `jlink.Open` takes ownership +on success. It claims only the J-Link application interface, resolves the active +endpoints after selecting its alternate, and does not select or configure a +target interface. If a close fails, the returned `cleanup` function keeps the +affected device or session reachable. Calling it again either retries a retained +interface claim or returns the cached device-close result. To use the same selected probe as an SWD wire, configure it while opening and pass the session to `swd.New`. Keep the cleanup closure when the operation @@ -461,8 +457,8 @@ func connectJLinkSWD(ctx context.Context, device *usb.Device) (_ uint32, cleanup } ``` -`WithSWD` selects the advertised SWD interface and requests a whole-kHz clock -no greater than its argument. A metadata-only session can instead call +`WithSWD` selects the advertised SWD interface and requests a whole-kHz clock no +greater than its argument. A metadata-only session can instead call `ConfigureSWD` later. Both forms change volatile probe interface and clock state, which `Close` does not restore. A complete nonzero scan status requires another `ConfigureSWD`; an ambiguous transfer requires close and reopen. The @@ -479,13 +475,12 @@ Pass the opened device to `ftdi.Open` with the MPSSE port and maximum requested clock. The driver reads and validates the product from the device identity; discovery does not choose the port or clock. -`ftdi.Open` initializes MPSSE and the clock with target pins as inputs. -The returned channel supplies both `SWDIO` and `JTAGIO`; each call establishes -its own directions. A non-nil channel owns the USB device even on error; -close that channel and retain it if cleanup fails. Only a nil result leaves -`Device.Close` with the caller. Release higher-level protocol state before -closing the channel, and do not mix raw operations underneath a live protocol -connection. +`ftdi.Open` initializes MPSSE and the clock with target pins as inputs. The +returned channel supplies both `SWDIO` and `JTAGIO`; each call establishes its +own directions. A non-nil channel owns the USB device even on error; close that +channel and retain it if cleanup fails. Only a nil result leaves `Device.Close` +with the caller. Release higher-level protocol state before closing the channel, +and do not mix raw operations underneath a live protocol connection. Adapter drivers submit USB transfers through the claimed interface and keep their scheduling policy themselves: @@ -525,27 +520,27 @@ if err := consume(completion); err != nil { return claim.Close() ``` -This example posts one IN request before its OUT request. A protocol which -needs a receive window submits several maximum-packet-sized buffers instead; -one which does not tolerate read-ahead submits its IN request only when the -response is due. Each handle reports its own completion, including a short or -zero-length transfer. Ending a `Wait` context does not cancel the request. -If the host transfer engine fails, `Wait` returns that error while `Done` can -remain open; the caller must not reuse the buffer until `Done` closes. -`AbortBulk` is endpoint-wide and performs a bounded drain of every pending -request on that endpoint. A drain timeout matches `context.DeadlineExceeded`. -If native cancellation or the drain fails, those requests and the claim remain -owned so cleanup can be retried. Closing the claim applies the same bound -across its endpoints before release. The first endpoint lookup reads the -interface's current alternate setting; a new claim does not imply alternate -zero. If alternate selection fails, the next endpoint lookup reads the host -state again instead of retaining descriptors for the previous alternate. +This example posts one IN request before its OUT request. A protocol which needs +a receive window submits several maximum-packet-sized buffers instead; one which +does not tolerate read-ahead submits its IN request only when the response is +due. Each handle reports its own completion, including a short or zero-length +transfer. Ending a `Wait` context does not cancel the request. If the host +transfer engine fails, `Wait` returns that error while `Done` can remain open; +the caller must not reuse the buffer until `Done` closes. `AbortBulk` is +endpoint-wide and performs a bounded drain of every pending request on that +endpoint. A drain timeout matches `context.DeadlineExceeded`. If native +cancellation or the drain fails, those requests and the claim remain owned so +cleanup can be retried. Closing the claim applies the same bound across its +endpoints before release. The first endpoint lookup reads the interface's +current alternate setting; a new claim does not imply alternate zero. If +alternate selection fails, the next endpoint lookup reads the host state again +instead of retaining descriptors for the previous alternate. ## Managed JTAG-DP Select JTAG-DP through the same `Open` or `Connect` calls. The configuration -contains a complete expected layout, a zero-based TDO-first TAP index, and -the probe's clock ceiling: +contains a complete expected layout, a zero-based TDO-first TAP index, and the +probe's clock ceiling: ```go arm, err := jtag.IDCODE(4, 0x5ba00477) @@ -567,30 +562,30 @@ config := armdebug.Config{ connected, err := armdebug.Open(ctx, selection, config) ``` -Store any non-nil `connected` owner before handling `err`, including when -setup failed. For an already-open probe, call -`armdebug.Connect(ctx, opened, config)` instead; ownership of `opened` -transfers even on error. Close any returned owner and retain it until cleanup -succeeds. These are the same ownership rules as the SW-DP path. +Store any non-nil `connected` owner before handling `err`, including when setup +failed. For an already-open probe, call `armdebug.Connect(ctx, opened, config)` +instead; ownership of `opened` transfers even on error. Close any returned owner +and retain it until cleanup succeeds. These are the same ownership rules as the +SW-DP path. The configuration copies the layout without traffic. Static layout, selected -TAP, clock, and cleanup-timeout validation precede discovery or activation. -The selected TAP must have an IDCODE and a four- or eight-bit IR. DAP then -validates the exact physical chain and enters baseline ADIv5 JTAG-DP. -Board-specific routing must already be enabled; the owner does not infer a -layout or activate a hidden DAP. DAP options are checked after probe activation. - -The borrowed `connected.Port()` supplies IDCODE through its cached -`Identity`, and the existing AP and transaction APIs. Acquire memory separately -with `connected.OpenMemAP(ctx, dap.NewAPSel(1))`; the owner tracks that -MEM-AP's restoration. Do not release or reconnect borrowed clients yourself. -`Close` restores owned MEM-APs in reverse acquisition order, releases DAP and -the chain to BYPASS/Idle, then closes the probe. A failure retains the current -owner and its dependencies, and completed releases are not repeated. - -`CleanupTimeout` bounds each owned release attempt. Zero chooses one second -for SWD or thirty seconds for JTAG; a negative value is invalid. Each attempt -uses a fresh context, independent of the operation context. DAP's own recovery +TAP, clock, and cleanup-timeout validation precede discovery or activation. The +selected TAP must have an IDCODE and a four- or eight-bit IR. DAP then validates +the exact physical chain and enters baseline ADIv5 JTAG-DP. Board-specific +routing must already be enabled; the owner does not infer a layout or activate a +hidden DAP. DAP options are checked after probe activation. + +The borrowed `connected.Port()` supplies IDCODE through its cached `Identity`, +and the existing AP and transaction APIs. Acquire memory separately with +`connected.OpenMemAP(ctx, dap.NewAPSel(1))`; the owner tracks that MEM-AP's +restoration. Do not release or reconnect borrowed clients yourself. `Close` +restores owned MEM-APs in reverse acquisition order, releases DAP and the chain +to BYPASS/Idle, then closes the probe. A failure retains the current owner and +its dependencies, and completed releases are not repeated. + +`CleanupTimeout` bounds each owned release attempt. Zero chooses one second for +SWD or thirty seconds for JTAG; a negative value is invalid. Each attempt uses a +fresh context, independent of the operation context. DAP's own recovery attempts, including chain revalidation, have separate bounds configured with `dap.WithCleanupTimeout` in `DAPOptions`; host cleanup also keeps its own limits. `CleanupTimeout` is not a total deadline for `Close`. @@ -602,66 +597,65 @@ OSTIOLE_ARMDEBUG_JTAG_HIL=1 go test -tags=integration ./armdebug -run '^TestHILA ``` On Nostalgia, FT4232H `01691`/A at 100 kHz completed two fresh `Connect` -sessions and two fresh `Open` sessions against the externally activated -Arm `0x5ba00477`/IR4 and Xilinx `0x14730093`/IR12 chain. AP1 IDR was -`0x44770002`; component words at `0x80410ff0` through `0x80410ffc` were -`0x0d`, `0x90`, `0x05`, and `0xb1`. Each owner closed successfully. Fresh -sessions found the same initial AP1 CSW/TAR (`0x80000042`/`0`) and connected -CTRL/STAT (`0xf0000000`). This exercises managed cleanup and repeated-session -AP restoration; it does not independently measure power after closing the -probe. No halt, target reset, or target-memory write was performed. +sessions and two fresh `Open` sessions against the externally activated Arm +`0x5ba00477`/IR4 and Xilinx `0x14730093`/IR12 chain. AP1 IDR was `0x44770002`; +component words at `0x80410ff0` through `0x80410ffc` were `0x0d`, `0x90`, +`0x05`, and `0xb1`. Each owner closed successfully. Fresh sessions found the +same initial AP1 CSW/TAR (`0x80000042`/`0`) and connected CTRL/STAT +(`0xf0000000`). This exercises managed cleanup and repeated-session AP +restoration; it does not independently measure power after closing the probe. No +halt, target reset, or target-memory write was performed. ## Choose between raw SWD and DAP -Use `swd.Conn` when the application needs one explicit wire-protocol -transaction or is bringing up an SWD path. Call `Connect` before register -access and `Release` before closing the wire. `Connect` returns DPIDR, keeps -inherited ORUNDETECT or tries to enable it, and records whether the setting was -inherited. `Release` restores only a change made by that connection and can be -retried. A register operation returns WAIT, FAULT, parity, and protocol errors -without replaying the requested transaction. When a fixed response returns -WAIT, the connection clears STICKYORUN before returning it. -Use `Conn.NewBatch` for an ordered group of raw register operations. Queue each -operation with the direction-specific DP or AP method, call `Commit`, then read -each direction-specific result. The batch uses the connection's established -response grammar. In simple mode it sends one request at a time; in overrun -mode it packs complete fixed frames when the wire reports room for more than -one. A transport failure makes the operations in that physical chunk -indeterminate and leaves later chunks unsent. WAIT and FAULT still stop the -batch, and the connection never replays a requested operation. -The [SWD protocol guide](protocols/swd.md) describes the wire transaction and -the specification details which are easiest to misread. +Use `swd.Conn` when the application needs one explicit wire-protocol transaction +or is bringing up an SWD path. Call `Connect` before register access and +`Release` before closing the wire. `Connect` returns DPIDR, keeps inherited +ORUNDETECT or tries to enable it, and records whether the setting was inherited. +`Release` restores only a change made by that connection and can be retried. A +register operation returns WAIT, FAULT, parity, and protocol errors without +replaying the requested transaction. When a fixed response returns WAIT, the +connection clears STICKYORUN before returning it. Use `Conn.NewBatch` for an +ordered group of raw register operations. Queue each operation with the +direction-specific DP or AP method, call `Commit`, then read each +direction-specific result. The batch uses the connection's established response +grammar. In simple mode it sends one request at a time; in overrun mode it packs +complete fixed frames when the wire reports room for more than one. A transport +failure makes the operations in that physical chunk indeterminate and leaves +later chunks unsent. WAIT and FAULT still stop the batch, and the connection +never replays a requested operation. The [SWD protocol guide](protocols/swd.md) +describes the wire transaction and the specification details which are easiest +to misread. Use `dap.DebugPort` when the application needs debug-port identity, power -ownership, bank selection, or AP access. Call `Connect` before AP operations -and `Release` afterward. `DebugPort.Connect` also connects its underlying -SWD stream, and `DebugPort.Release` releases it after restoring DAP state; -do not connect or release that stream separately. Give the debug port -exclusive, serialized use of its `swd.Conn`; direct transfers on that -connection can invalidate cached DAP state. DP, AP, transaction, and MEM-AP -operations require an active connection. `ReadDP` and `WriteDP` take logical -ADIv5 register names and manage DPBANKSEL without exposing a current-bank -API. `NewAPSel` constructs an ADIv5 index; `APAt` constructs an ADIv6 base-address -selector. Both return `APSel` values; the zero `APSel` remains invalid. -`APSel.Address` combines a selector with an eight-bit ADIv5 or twelve-bit -ADIv6 register offset; the +ownership, bank selection, or AP access. Call `Connect` before AP operations and +`Release` afterward. `DebugPort.Connect` also connects its underlying SWD +stream, and `DebugPort.Release` releases it after restoring DAP state; do not +connect or release that stream separately. Give the debug port exclusive, +serialized use of its `swd.Conn`; direct transfers on that connection can +invalidate cached DAP state. DP, AP, transaction, and MEM-AP operations require +an active connection. `ReadDP` and `WriteDP` take logical ADIv5 register names +and manage DPBANKSEL without exposing a current-bank API. `NewAPSel` constructs +an ADIv5 index; `APAt` constructs an ADIv6 base-address selector. Both return +`APSel` values; the zero `APSel` remains invalid. `APSel.Address` combines a +selector with an eight-bit ADIv5 or twelve-bit ADIv6 register offset; the resulting `APAddress` also has an invalid zero value. `ReadAPIDR` reads and -decodes the common read-only AP identity. `EnumerateAPs` scans every ADIv5 -AP selector without reading class-specific registers. Raw AP access rejects -an invalid or unaligned address before traffic. Use it only when the caller -understands the selected AP class and will restore any state the access -changes. A raw MEM-AP data-register write can write target memory. This -layer owns AP read and write completion. Construct an SWD binding with +decodes the common read-only AP identity. `EnumerateAPs` scans every ADIv5 AP +selector without reading class-specific registers. Raw AP access rejects an +invalid or unaligned address before traffic. Use it only when the caller +understands the selected AP class and will restore any state the access changes. +A raw MEM-AP data-register write can write target memory. This layer owns AP +read and write completion. Construct an SWD binding with `dap.NewDebugPort(dap.SWDP(conn))`; the operation context bounds WAIT retry. Adding `dap.WithMaxWaits(1)` stops at the first clean WAIT, reporting both -`dap.ErrWait` and its underlying `swd.ErrWait`. `SetMaxWaits` changes the -limit before `Connect` or after a successful `Release`; it rejects the -change while the port is connected or cleanup is pending. The count is per -physical request and does not bound host I/O. A raw AP read or write which -completes, or might have completed, invalidates existing `MemAP` values. If -the limit or context ends after an AP WAIT, `dap.DebugPort` issues DAPABORT; -existing `dap.MemAP` values reject further reads, though `dap.MemAP.Release` -still attempts to restore their saved state. +`dap.ErrWait` and its underlying `swd.ErrWait`. `SetMaxWaits` changes the limit +before `Connect` or after a successful `Release`; it rejects the change while +the port is connected or cleanup is pending. The count is per physical request +and does not bound host I/O. A raw AP read or write which completes, or might +have completed, invalidates existing `MemAP` values. If the limit or context +ends after an AP WAIT, `dap.DebugPort` issues DAPABORT; existing `dap.MemAP` +values reject further reads, though `dap.MemAP.Release` still attempts to +restore their saved state. For an explicit JTAG composition, pass `dap.JTAGDP(chain, tapIndex)` instead. The chain supplies the complete expected layout; the index is zero-based and @@ -671,33 +665,31 @@ replaying them and checks CTRL/STAT after each AP operation. It temporarily disables inherited ORUNDETECT and restores it during release. Release MEM-APs, then DAP and its chain, before closing the probe. Independent recovery defaults to thirty seconds for JTAG, versus one second for SWD; use -`dap.WithCleanupTimeout` for slower clocks. The [DAP guide](ports/dap.md) -shows the binding and the read-only FTDI bench procedure. `armdebug.JTAGDP` -composes these same owners through the managed path above. +`dap.WithCleanupTimeout` for slower clocks. The [DAP guide](ports/dap.md) shows +the binding and the read-only FTDI bench procedure. `armdebug.JTAGDP` composes +these same owners through the managed path above. The SWD connection reads DPIDR, clears supported sticky conditions with ABORT, establishes bank zero through RDBUFF, and establishes its response grammar -before DAP requests power. -Debug-port CTRL/STAT writes must preserve ORUNDETECT. DAP settles a new SELECT -through RDBUFF before sending AP traffic. If WAIT cleanup or a later retry -leaves framing unknown, `dap.DebugPort` invalidates those values and later DP -and AP calls stop before sending traffic. -`Connect` performs bounded cleanup after failed setup. When cleanup succeeds, -the debug port can connect again immediately. If cleanup also fails, or if -`Release` fails, ordinary DP, AP, and MEM-AP operations remain blocked. Call +before DAP requests power. Debug-port CTRL/STAT writes must preserve ORUNDETECT. +DAP settles a new SELECT through RDBUFF before sending AP traffic. If WAIT +cleanup or a later retry leaves framing unknown, `dap.DebugPort` invalidates +those values and later DP and AP calls stop before sending traffic. `Connect` +performs bounded cleanup after failed setup. When cleanup succeeds, the debug +port can connect again immediately. If cleanup also fails, or if `Release` +fails, ordinary DP, AP, and MEM-AP operations remain blocked. Call `MemAP.Release` before retrying `DebugPort.Release`; cleanup re-enters SWD when -necessary and verifies that DPIDR still identifies the connection being -cleaned up before restoring state. `DebugPort.Release` settles its final -bank-zero SELECT through RDBUFF, releases power, and restores connection-owned -ORUNDETECT before returning success. -The [DAP guide](ports/dap.md) describes the ADIv5 register protocol behind -that lifecycle. +necessary and verifies that DPIDR still identifies the connection being cleaned +up before restoring state. `DebugPort.Release` settles its final bank-zero +SELECT through RDBUFF, releases power, and restores connection-owned ORUNDETECT +before returning success. The [DAP guide](ports/dap.md) describes the ADIv5 +register protocol behind that lifecycle. Use `DebugPort.NewTxn` when several DP or AP accesses need ordered results. `Commit` validates the complete queue, settles an earlier immediate DP write if -necessary, then lets the SWD connection pack fixed frames within its wire -limit. Sticky-exempt DPIDR, CTRL/STAT, and ABORT operations remain separate so -an earlier WAIT or FAULT cannot hide behind one of them. DP writes and AP +necessary, then lets the SWD connection pack fixed frames within its wire limit. +Sticky-exempt DPIDR, CTRL/STAT, and ABORT operations remain separate so an +earlier WAIT or FAULT cannot hide behind one of them. DP writes and AP operations settle through RDBUFF before reporting success. Queued reads return a `ReadResult`, whose `Value` method returns the data. Queued writes return a `WriteResult`, whose `Err` method reports completion without a placeholder @@ -719,11 +711,10 @@ same configured WAIT policy as the scalar and raw DAP operations. If selection, framing, or cleanup becomes uncertain, repair is required. A FAULT returns the contiguous prefix read before the fault; a configured WAIT limit, cancellation, and transport or protocol failures can also interrupt the read. The rest of the -destination remains unchanged. No auto-incrementing word run crosses a 1 KiB -TAR boundary; unaligned edges still require the MEM-AP to accept byte or -halfword CSW sizes. -If the MEM-AP does not accept single address increment, `ReadBlock` and -`WriteBlock` write TAR before each word. +destination remains unchanged. No auto-incrementing word run crosses a 1 KiB TAR +boundary; unaligned edges still require the MEM-AP to accept byte or halfword +CSW sizes. If the MEM-AP does not accept single address increment, `ReadBlock` +and `WriteBlock` write TAR before each word. `MemAP.WriteBlock` accepts the same ranges and uses the same geometry. Its returned prefix includes only chunks whose RDBUFF completion requests were @@ -746,17 +737,17 @@ CSW, then release and reconnect the debug port. Use `target/cortexm` when the desired result is processor identity. It accepts the word-reader behavior supplied by `dap.MemAP`, so target code remains -independent of the host, adapter, and wire protocol. `cortexm.Acquire` also -uses `WriteWord` to enable Cortex-M0 halting debug. Use `ReadRegister` for -halted core registers and `WriteRegister` for intentional changes. The target -tracks transfer completion but does not roll back writes. Release it before -its memory owner and retain both after failed target restoration. See +independent of the host, adapter, and wire protocol. `cortexm.Acquire` also uses +`WriteWord` to enable Cortex-M0 halting debug. Use `ReadRegister` for halted +core registers and `WriteRegister` for intentional changes. The 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. ## Release in reverse order -A complete Cortex-M identity composition acquires and releases state in one -of these orders: +A complete Cortex-M identity composition acquires and releases state in one of +these orders: ```text acquire: USB device → FTDI channel → debug port (enters SWD) → MEM-AP @@ -770,13 +761,13 @@ release: MEM-AP → debug port → CMSIS-DAP session ``` Use a fresh, bounded cleanup context if the operation context may already be -canceled. Join cleanup errors with the operation error so a restoration or -close failure is not lost. +canceled. Join cleanup errors with the operation error so a restoration or close +failure is not lost. `Enumerator.Open` cleans up native resources before returning an error. `ftdi.Open` attempts cleanup, then leaves the original device with the caller -for the `Device.Close` described above. A successfully returned value belongs -to the caller until its documented release or close method succeeds. +for the `Device.Close` described above. A successfully returned value belongs to +the caller until its documented release or close method succeeds. ## Identify a debug component @@ -801,12 +792,12 @@ fmt.Printf("class=%#x part=%#x\n", component.Class(), component.Part()) This borrows the same memory client and adds no cleanup owner. An advertised address still requires component power and access permissions. A caller that already knows another accessible identification page can pass that address -directly to `coresight.Identify`. -Use `coresight.Walk` with explicit `WalkLimits` to follow ROM entries. Retain -partial visits when it returns an error, and leave power-domain children -skipped until access has been established separately. The same memory owner -retains cleanup responsibility. The [component guide](coresight.md) describes -entry decoding, traversal bounds, power metadata, and incomplete results. +directly to `coresight.Identify`. Use `coresight.Walk` with explicit +`WalkLimits` to follow ROM entries. Retain partial visits when it returns an +error, and leave power-domain children skipped until access has been established +separately. The same memory owner retains cleanup responsibility. The +[component guide](coresight.md) describes entry decoding, traversal bounds, +power metadata, and incomplete results. ## Keep policy at the application edge @@ -823,9 +814,9 @@ Libraries own reusable hardware and protocol behavior. USB requests, MPSSE commands, SWD frames, AP posted reads, MEM-AP register restoration, and CPUID decoding should not be recreated in an application. -If a needed capability is absent, add it at the layer that can express and -test it as a reusable mechanism. Do not hide a second hardware stack in an -example, an `ost` subcommand, or another application's command package. +If a needed capability is absent, add it at the layer that can express and test +it as a reusable mechanism. Do not hide a second hardware stack in an example, +an `ost` subcommand, or another application's command package. ## Guidance for coding agents @@ -841,5 +832,5 @@ Before writing a hardware composition: reimplementing lower-level framing in application code. The [architecture guide](architecture.md) is the authority for current package -ownership. The [examples](../examples) are the authority for compact, -executable composition. +ownership. The [examples](../examples) are the authority for compact, executable +composition. diff --git a/docs/coresight.md b/docs/coresight.md index 84edb55..0aaf9bb 100644 --- a/docs/coresight.md +++ b/docs/coresight.md @@ -1,10 +1,10 @@ # CoreSight component inspection `coresight.Identify` reads one component's identification registers through a -borrowed scalar-memory reader. A `dap.MemAP` implements that interface over -SWD or JTAG. Obtain the advertised identification page from the selected -MEM-AP with `ReadDebugBase`, or supply an explicitly known address. The package -can also read individual ROM entries or walk a hierarchy with explicit limits. +borrowed scalar-memory reader. A `dap.MemAP` implements that interface over SWD +or JTAG. Obtain the advertised identification page from the selected MEM-AP with +`ReadDebugBase`, or supply an explicitly known address. The package can also +read individual ROM entries or walk a hierarchy with explicit limits. ```go base, present, err := memory.ReadDebugBase(ctx) @@ -29,41 +29,40 @@ if architecture, present := component.Architecture(); present { The reader remains borrowed throughout the call. When `memory` comes from `armdebug.Conn.OpenMemAP`, close that connection and retry failed cleanup as -shown in [Composing Ostiole](composition.md#select-and-open-hardware-explicitly). -A directly acquired MEM-AP must be released before its debug port. Reading -BASE does not access target memory or change CSW/TAR; reading the component -identity temporarily changes MEM-AP address and transfer state. An advertised -address does not establish that the component is accessible. See -[MEM-AP debug base](ports/dap.md#mem-ap-debug-base) for presence and format -handling. +shown in [Composing Ostiole][composition]. A directly acquired MEM-AP must be +released before its debug port. Reading BASE does not access target memory or +change CSW/TAR; reading the component identity temporarily changes MEM-AP +address and transfer state. An advertised address does not establish that the +component is accessible. See [MEM-AP debug base](ports/dap.md#mem-ap-debug-base) +for presence and format handling. The register layout follows Arm IHI 0029E, sections B2.2 and B2.3 of the -[CoreSight Architecture Specification v3.0](https://documentation-service.arm.com/static/5f900a19f86e16515cdc041e). -The reader validates the CIDR preamble before reading PIDR. For class 9, it -also reads DEVARCH, DEVID, and DEVTYPE; it skips those registers for other classes. -The snapshot preserves unknown classes and parts. It exposes a DEVARCH -architecture only when PRESENT is set, and marks whether the PIDR designer -uses JEP106. A part number alone does not identify a component architecture. +[CoreSight Architecture Specification v3.0][coresight-spec]. The reader +validates the CIDR preamble before reading PIDR. For class 9, it also reads +DEVARCH, DEVID, and DEVTYPE; it skips those registers for other classes. The +snapshot preserves unknown classes and parts. It exposes a DEVARCH architecture +only when PRESENT is set, and marks whether the PIDR designer uses JEP106. A +part number alone does not identify a component architecture. `CIDR` and `PIDR` pack the low bytes in register-number order. Reserved upper -bits are ignored. `PIDR` retains REVAND, CMOD, and the encoded SIZE field; -SIZE is not treated as a reliable component extent. `Base` names the -identification page, which may differ from the beginning of a larger component. -The API accepts 64-bit addresses and uses numeric 32-bit scalar reads, leaving -byte order to the supplied memory reader. +bits are ignored. `PIDR` retains REVAND, CMOD, and the encoded SIZE field; SIZE +is not treated as a reliable component extent. `Base` names the identification +page, which may differ from the beginning of a larger component. The API accepts +64-bit addresses and uses numeric 32-bit scalar reads, leaving byte order to the +supplied memory reader. Invalid arguments fail before traffic. A read failure returns a zero snapshot with the failing register address and underlying error. Cancellation stops further reads. Callers must not interpret failure as an absent component or a -complete inventory: this API cannot distinguish a lock, a power restriction, -and a transport failure unless the reader's error supplies that information. -A successful identity also does not establish access to functional registers. +complete inventory: this API cannot distinguish a lock, a power restriction, and +a transport failure unless the reader's error supplies that information. A +successful identity also does not establish access to functional registers. -Inspection performs no target-memory writes, unlocks, component power -requests, CTI configuration, halt, or reset. Callers must establish that the -address is safe to inspect and that required board routing and component power -are already available. DAP setup and restoration still have the effects -described in the [architecture guide](architecture.md#safety-effects). +Inspection performs no target-memory writes, unlocks, component power requests, +CTI configuration, halt, or reset. Callers must establish that the address is +safe to inspect and that required board routing and component power are already +available. DAP setup and restoration still have the effects described in the +[architecture guide](architecture.md#safety-effects). Deterministic tests cover malformed identities, unknown classes, optional fields, cancellation, failures at every register read, and the final aligned @@ -98,33 +97,32 @@ for i := 0; i < table.EntryCount(); i++ { } ``` -Class 1 tables hold at most 960 32-bit entries. Class 9 DEVID.FORMAT selects -512 32-bit or 256 64-bit entries. A table that fills every slot needs no -additional terminator. `ReadEntry` reads both words of a 64-bit entry before -interpreting it and returns no partial entry on error. It applies signed -relative offsets without allowing address underflow or overflow. +Class 1 tables hold at most 960 32-bit entries. Class 9 DEVID.FORMAT selects 512 +32-bit or 256 64-bit entries. A table that fills every slot needs no additional +terminator. `ReadEntry` reads both words of a 64-bit entry before interpreting +it and returns no partial entry on error. It applies signed relative offsets +without allowing address underflow or overflow. -The decoder follows IHI 0029E D6.4.4 and D7.5.17. It rejects reserved -formats, nonzero reserved bits, zero offsets in present entries, and nonzero -class 9 terminators. Class 9 absence (`PRESENT=2`) leaves the remaining bits -uninterpreted. Class 1 FORMAT=0 entries are unsupported; all-ones entries -are malformed. `Raw` retains the complete entry value on success. Unknown -class 9 architectures are not interpreted as tables based on their part -number alone. +The decoder follows IHI 0029E D6.4.4 and D7.5.17. It rejects reserved formats, +nonzero reserved bits, zero offsets in present entries, and nonzero class 9 +terminators. Class 9 absence (`PRESENT=2`) leaves the remaining bits +uninterpreted. Class 1 FORMAT=0 entries are unsupported; all-ones entries are +malformed. `Raw` retains the complete entry value on success. Unknown class 9 +architectures are not interpreted as tables based on their part number alone. Entry reads do not access the child. A valid power ID is scoped to the -containing table and does not establish that the child is powered. This API -does not request power. Callers must establish access before identifying a -child in another power domain. Reader ownership and cleanup remain as above. +containing table and does not establish that the child is powered. This API does +not request power. Callers must establish access before identifying a child in +another power domain. Reader ownership and cleanup remain as above. ## Bounded traversal `Walk` identifies a root and follows present entries in depth-first order. A -root that is not a ROM table produces one successful visit. Limits apply to -the entire walk, with root depth zero. The component limit counts the root, -failed identities, and skipped power-domain children. The entry limit counts -absent entries and terminators as well as present entries. Validate limits -before opening hardware when they come from application arguments. +root that is not a ROM table produces one successful visit. Limits apply to the +entire walk, with root depth zero. The component limit counts the root, failed +identities, and skipped power-domain children. The entry limit counts absent +entries and terminators as well as present entries. Validate limits before +opening hardware when they come from application arguments. ```go limits := coresight.WalkLimits{MaxDepth: 8, MaxComponents: 256, MaxEntries: 4096} @@ -153,25 +151,25 @@ was not obtained. Absent entries and terminators have no visits; use individual entry reads when their raw values matter. A power-domain child is recorded with `ErrPowerDomain` and skipped before any -child access. The walk continues through its accessible siblings but returns -a non-nil error, so those results cannot be mistaken for a complete inventory. -It does not test a power-control register, request power, or offer an option -to assume an advertised domain is accessible. +child access. The walk continues through its accessible siblings but returns a +non-nil error, so those results cannot be mistaken for a complete inventory. It +does not test a power-control register, request power, or offer an option to +assume an advertised domain is accessible. Other failures stop the walk immediately, including malformed entries, unsupported ROM formats, repeated tables, exhausted limits, and memory errors. Repeated table references include cycles and duplicate references from separate parents; they fail before another identity read. Ordinary component references may repeat. Unknown component architectures remain leaves. A successful walk -covers the supported tables reached from this root, not every debug component -in the system. +covers the supported tables reached from this root, not every debug component in +the system. The returned error preserves underlying memory errors and matches `ErrWalkLimit`, `ErrRepeatedTable`, or `ErrPowerDomain` when applicable. Earlier visits remain available; an identity failure is recorded on its visit. An entry read failure or exhausted limit is reported in the returned error, without a -child visit. Stop using a failed MEM-AP according to its recovery rules, -then release its owner with bounded, retryable cleanup. +child visit. Stop using a failed MEM-AP according to its recovery rules, then +release its owner with bounded, retryable cleanup. ## Inspection example @@ -192,8 +190,8 @@ The example defaults to a 1 MHz clock, accepts `-clock` in Hz, and applies a ten-second operation deadline. The library also accepts memory clients reached through JTAG; the example configures SWD only. -Add `-walk` to follow the advertised root with depth 8, at most 256 visits, -and at most 4096 entry reads across the hierarchy: +Add `-walk` to follow the advertised root with depth 8, at most 256 visits, and +at most 4096 entry reads across the hierarchy: ```sh go run ./examples/simple/coresight-info \ @@ -203,8 +201,8 @@ go run ./examples/simple/coresight-info \ `-base ADDRESS` also applies to walks. The output includes parent and entry indexes, available identities, per-component errors, and a `complete` field. Incomplete inspection exits unsuccessfully after printing its partial results -and attempting owner cleanup. These fixed bounds keep the example small; -library callers supply their own `WalkLimits`. +and attempting owner cleanup. These fixed bounds keep the example small; library +callers supply their own `WalkLimits`. ## Hardware evidence @@ -215,21 +213,21 @@ OSTIOLE_CORESIGHT_HIL=1 \ go test -tags=integration -run TestHILComponentIdentity -count=1 -v ./coresight ``` -Each path opened two fresh sessions at a requested 100 kHz. The test first -read and identified the MEM-AP's advertised entry, then read the known -component page below through the same client: +Each path opened two fresh sessions at a requested 100 kHz. The test first read +and identified the MEM-AP's advertised entry, then read the known component page +below through the same client: -| Path | Advertised address | Result | -| --- | --- | --- | -| micro:bit SWD AP0 | `0xf0000000` | CIDR `0xb105100d`, PIDR `0x02007c4001`, class 1. | -| ZCU104 JTAG AP1 | `0x80000000` | CIDR `0xb105100d`, PIDR `0x0100193730`, class 1. | +| Path | Advertised address | Result | +| ----------------- | ------------------ | ------------------------------------------------ | +| micro:bit SWD AP0 | `0xf0000000` | CIDR `0xb105100d`, PIDR `0x02007c4001`, class 1. | +| ZCU104 JTAG AP1 | `0x80000000` | CIDR `0xb105100d`, PIDR `0x0100193730`, class 1. | The additional explicitly addressed reads returned: -| Path | Identification page | Result | -| --- | --- | --- | -| CMSIS-DAP v2 micro:bit, serial `9900360140124e4500279015000000360000000097969901`, SWD AP0 | `0xe00ff000` | CIDR `0xb105100d`, PIDR `0x04000bb471`, class 1, Arm part `0x471`. | -| FT4232H `01691`/A, ZCU104 JTAG AP1 | `0x80410000` | CIDR `0xb105900d`, PIDR `0x04004bbd03`, class 9, Arm part `0xd03`; DEVARCH `0x47706a15`, DEVID `3`, DEVTYPE `0x15`. | +| Path | Identification page | Result | +| ------------------------------------------------------------------------------------------ | ------------------- | ------------------------------------------------------------------------------------------------------------------- | +| CMSIS-DAP v2 micro:bit, serial `9900360140124e4500279015000000360000000097969901`, SWD AP0 | `0xe00ff000` | CIDR `0xb105100d`, PIDR `0x04000bb471`, class 1, Arm part `0x471`. | +| FT4232H `01691`/A, ZCU104 JTAG AP1 | `0x80410000` | CIDR `0xb105900d`, PIDR `0x04004bbd03`, class 9, Arm part `0xd03`; DEVARCH `0x47706a15`, DEVID `3`, DEVTYPE `0x15`. | Both sessions on each path returned the same identity. Every Arm debug owner reported successful close, including its MEM-AP and DAP restoration and probe @@ -240,8 +238,8 @@ target-memory write was performed. The example passed on that micro:bit with its exact serial, both with the advertised address and with `-base 0xe00ff000`. These results cover the -advertised entry and one known page on each bench, not ROM traversal, -component register access, or physical large-address and big-endian support. +advertised entry and one known page on each bench, not ROM traversal, component +register access, or physical large-address and big-endian support. ## ROM traversal hardware evidence @@ -253,9 +251,9 @@ OSTIOLE_ROM_HIL=1 \ ``` The test uses the same exact probe selections and externally enabled ZCU104 -chain described above. In that run, each path opened two fresh sessions at -100 kHz, read its MEM-AP's advertised root, and walked with depth 8, 256 -visits, 4096 entry reads, and a 120-second operation deadline. +chain described above. In that run, each path opened two fresh sessions at 100 +kHz, read its MEM-AP's advertised root, and walked with depth 8, 256 visits, +4096 entry reads, and a 120-second operation deadline. The micro:bit SWD AP0 walk completed with six identities. Its root at `0xf0000000` led to the nested table at `0xe00ff000`, components at @@ -266,25 +264,25 @@ using that probe's exact serial and its ten-second deadline. The ZCU104 JTAG AP1 walk was incomplete. From root `0x80000000`, it identified sixteen children at `0x80100000` through `0x801f0000`, then stopped on a DAP FAULT while reading CIDR at `0x803e0ff0`. The result retained those seventeen -identities and a failed eighteenth visit for root entry 16. The test checks -this access boundary; it does not count the inaccessible component as -identified or attempt later entries. The error does not distinguish a power -restriction from another cause of that target access fault. +identities and a failed eighteenth visit for root entry 16. The test checks this +access boundary; it does not count the inaccessible component as identified or +attempt later entries. The error does not distinguish a power restriction from +another cause of that target access fault. Both sessions reproduced each bench's result. Every owner reported successful close, including after the ZCU104 fault. This does not independently measure restored state after close. No target-memory writes, component power requests, unlocks, processor control, or board activation were performed. The observed -tables were class 1. Class 9 layouts and power-domain skips have ordinary -test coverage; large addresses and both memory byte orders also have public -MEM-AP simulation coverage. Those cases were not exercised on hardware. +tables were class 1. Class 9 layouts and power-domain skips have ordinary test +coverage; large addresses and both memory byte orders also have public MEM-AP +simulation coverage. Those cases were not exercised on hardware. ## ADIv6 and the RP2350 -The example accepts `-ap-base` for an ADIv6 MEM-AP, or `-debug-space` to -inspect the DP's own advertised tree. Choose exactly one of `-ap`, -`-ap-base`, and `-debug-space`. `-base` still overrides the root within the -selected address space. +The example accepts `-ap-base` for an ADIv6 MEM-AP, or `-debug-space` to inspect +the DP's own advertised tree. Choose exactly one of `-ap`, `-ap-base`, and +`-debug-space`. `-base` still overrides the root within the selected address +space. ```sh go run ./examples/simple/coresight-info \ @@ -294,9 +292,9 @@ go run ./examples/simple/coresight-info \ -provider jlink -serial 000802011345 -ap-base 0x2000 -walk ``` -On September 20, 2026, Nostalgia exercised the RP2350 through J-Link EDU Mini -V2 serial `000802011345`, with SWD requested at 100 kHz. Two fresh sessions -for each path used: +On September 20, 2026, Nostalgia exercised the RP2350 through J-Link EDU Mini V2 +serial `000802011345`, with SWD requested at 100 kHz. Two fresh sessions for +each path used: ```sh OSTIOLE_ARMDEBUG_HIL=1 \ @@ -315,23 +313,23 @@ for ap_base in 0x2000 0x4000; do done ``` -The integration walks used depth 8, 64 visits, 256 entry reads, and a -10-second deadline. The debug port reported DPIDR `0x4c013477`, DPIDR1 -`0x94` (20 address bits), and a present discovery root at address zero. Its -class 9 ROM table led to six children, including MEM-APs at `0x2000` and -`0x4000` with DEVARCH `0x47700a17`. The walk completed with seven identities. +The integration walks used depth 8, 64 visits, 256 entry reads, and a 10-second +deadline. The debug port reported DPIDR `0x4c013477`, DPIDR1 `0x94` (20 address +bits), and a present discovery root at address zero. Its class 9 ROM table led +to six children, including MEM-APs at `0x2000` and `0x4000` with DEVARCH +`0x47700a17`. The walk completed with seven identities. Both MEM-APs returned IDR `0x34770008`, CPUID `0x411fd210`, and debug base -`0xe00ff000`. Each target-memory walk completed with seven identities. -The example also completed all three paths with its existing depth 8, -256-visit and 4096-entry limits. +`0xe00ff000`. Each target-memory walk completed with seven identities. The +example also completed all three paths with its existing depth 8, 256-visit and +4096-entry limits. -All connection cleanup calls returned successfully. Restored state was -not independently measured after close. These runs performed no target-memory +All connection cleanup calls returned successfully. Restored state was not +independently measured after close. These runs performed no target-memory writes, halt, reset, component unlock, or component power requests. The walks -cover advertised entries, not every component in the RP2350 debug address -space. Addresses above 32 bits, memory writes, and injected failures have -simulation coverage but were not exercised on this bench. +cover advertised entries, not every component in the RP2350 debug address space. +Addresses above 32 bits, memory writes, and injected failures have simulation +coverage but were not exercised on this bench. On September 26, the micro:bit identity and ROM-walk tests repeated both sessions at a requested 1 MHz and reproduced the identities and six visits @@ -344,3 +342,7 @@ OSTIOLE_CORESIGHT_HIL=1 OSTIOLE_ROM_HIL=1 \ go test -tags integration ./coresight \ -run 'TestHIL(ComponentIdentity|ROMWalk)/microbit' -count=1 -v ``` + +[coresight-spec]: + https://documentation-service.arm.com/static/5f900a19f86e16515cdc041e +[composition]: composition.md#select-and-open-hardware-explicitly diff --git a/docs/cortexm.md b/docs/cortexm.md index 5276acb..f29b8ba 100644 --- a/docs/cortexm.md +++ b/docs/cortexm.md @@ -1,9 +1,9 @@ # Cortex-M control -`target/cortexm.Identify` reads CPUID through any aligned-word reader. -`Acquire` additionally enables halting debug on Cortex-M0 through a borrowed -`Memory`, whose `ReadWord` and `WriteWord` methods are supplied by `dap.MemAP`. -Other processor parts are rejected before a debug-register write. +`target/cortexm.Identify` reads CPUID through any aligned-word reader. `Acquire` +additionally enables halting debug on Cortex-M0 through a borrowed `Memory`, +whose `ReadWord` and `WriteWord` methods are supplied by `dap.MemAP`. Other +processor parts are rejected before a debug-register write. ## Ownership @@ -12,23 +12,23 @@ inherited halt and rejects active stepping, interrupt masking, or an unfinished halt transition. When debug is disabled, the other control bits are unknown; acquisition initializes them to zero when enabling debug. -The target requires exclusive control of the processor's debug registers. Do -not use another debugger or write those registers through raw memory while it -is acquired. Serialize the target and its memory connection. `Identity` returns -the cached CPUID after release; the zero target cannot access memory. +The target requires exclusive control of the processor's debug registers. Do not +use another debugger or write those registers through raw memory while it is +acquired. Serialize the target and its memory connection. `Identity` returns the +cached CPUID after release; the zero target cannot access memory. `Halt` waits for Debug state. `Resume` accepts only a halt requested by this target. Observing an already-halted processor does not acquire permission to resume it. `Halted` reads the current status without acquiring halt ownership. -Release the target before its MEM-AP or Arm debug owner. `Release` restores -the debug control changed by the target and leaves an inherited halt alone. -The caller controls operation cancellation and deadlines, including release. -Without either, an operation may wait indefinitely for the processor. -Failed acquisition attempts cleanup with a fresh five-second context; a -non-nil target returned with an error must be retained for release retries. -Once release starts, or a control write fails, ordinary target calls stop. -Failed cleanup retains the restoration state for another `Release`. +Release the target before its MEM-AP or Arm debug owner. `Release` restores the +debug control changed by the target and leaves an inherited halt alone. The +caller controls operation cancellation and deadlines, including release. Without +either, an operation may wait indefinitely for the processor. Failed acquisition +attempts cleanup with a fresh five-second context; a non-nil target returned +with an error must be retained for release retries. Once release starts, or a +control write fails, ordinary target calls stop. Failed cleanup retains the +restoration state for another `Release`. Cleanup needs a usable memory connection. A poisoned transport or invalidated MEM-AP can prevent restoration; retaining the target does not repair either. @@ -38,9 +38,9 @@ restoration can be retried. ## Effects Enabling halting debug changes how the processor handles debug events, even -before an explicit halt. Halting does not stop peripheral clocks. Resuming -can execute instructions before a later failure is reported, and release -cannot undo those instructions or recover elapsed time. +before an explicit halt. Halting does not stop peripheral clocks. Resuming can +execute instructions before a later failure is reported, and release cannot undo +those instructions or recover elapsed time. A successful memory write does not prove that the halt request cleared. If readback never shows it clear, cleanup stays pending without repeating resume: @@ -56,24 +56,24 @@ permits cleanup to continue. The package has no forced-resume escape hatch. Debug events racing with restoration of disabled debug can still affect execution. -If halt readback shows that the request was lost, the target relinquishes -halt ownership. Cleanup leaves an independent stop alone; restoring initially +If halt readback shows that the request was lost, the target relinquishes halt +ownership. Cleanup leaves an independent stop alone; restoring initially disabled debug waits until the processor is running. -DHCSR reads consume the sticky reset and instruction-retirement indicators. -The package does not restore those indicators or clear DFSR event flags. +DHCSR reads consume the sticky reset and instruction-retirement indicators. The +package does not restore those indicators or clear DFSR event flags. The implementation follows Arm DDI 0419E, sections C1.5 and C1.6.3–C1.6.5 of the -[Armv6-M Architecture Reference Manual](https://documentation-service.arm.com/static/5f8ff05ef86e16515cdbf826). -It does not implement reset, breakpoints, or watchpoints. +[Armv6-M Architecture Reference Manual][armv6m]. It does not implement reset, +breakpoints, or watchpoints. ## Stepping -`Step(ctx)` performs one 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 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 { @@ -83,36 +83,36 @@ if err := core.Step(ctx); err != nil { pc, err := core.ReadRegister(ctx, cortexm.PC) ``` -Stepping does not change interrupt masking. An architectural step can enter -an exception handler instead of retiring an instruction. A breakpoint, -watchpoint, vector catch, or external halt can also interrupt it. The target -checks DFSR before launch and rejects any existing flags for those events; -it preserves all DFSR flags. After launch, it requires a fresh halt with the -HALTED reason and no competing event before claiming that stop. A competing -stop returns an error and remains unowned. +Stepping does not change interrupt masking. An architectural step can enter an +exception handler instead of retiring an instruction. A breakpoint, watchpoint, +vector catch, or external halt can also interrupt it. The target checks DFSR +before launch and rejects any existing flags for those events; it preserves all +DFSR flags. After launch, it requires a fresh halt with the HALTED reason and no +competing event before claiming that stop. A competing stop returns an error and +remains unowned. Once launch is attempted, any failure leaves only `Release` available. Release never repeats the step. After a confirmed launch, it waits for a fresh halt -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 +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. 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. +flags, cancellation, ignored writes, partial failures, and cleanup retries. The +[step bench](#step-bench) records physical instruction checks. ## Register reads `ReadRegister` reads R0–R12, SP, LR, PC, XPSR, MSP, or PSP from a halted processor. SP selects the current stack pointer; MSP and PSP select its banks. PC is the debug return address. An inherited halt permits inspection without -acquiring permission to resume. Invalid `Register` identifiers, including -zero, are rejected before memory traffic. +acquiring permission to resume. Invalid `Register` identifiers, including zero, +are rejected before memory traffic. ```go pc, err := core.ReadRegister(ctx, cortexm.PC) @@ -123,9 +123,9 @@ The target waits for S_REGRDY before and after selecting a register, using the caller's context. It does not require observing S_REGRDY clear, since a transfer may finish before the first status read. -A failed transfer leaves only `Release` available. Release waits for any -pending transfer, including one found busy before selection, before resuming -or disabling debug. It never replays a selector write whose completion is +A failed transfer leaves only `Release` available. Release waits for any pending +transfer, including one found busy before selection, before resuming or +disabling debug. It never replays a selector write whose completion is uncertain. A failed precondition or cancellation before selection leaves the target usable when no transfer is pending. An error returns no register value. @@ -139,9 +139,9 @@ not physical failure-injection evidence. `WriteRegister` writes the same register set except XPSR, which is read-only. SP, MSP, and PSP require word-aligned values; PC requires bit zero clear. PC -writes change the debug return address without changing Thumb state. Writing -SP changes whichever stack bank is active. The API rejects invalid identifiers -and values before traffic; it does not check whether an address is mapped or +writes change the debug return address without changing Thumb state. Writing SP +changes whichever stack bank is active. The API rejects invalid identifiers and +values before traffic; it does not check whether an address is mapped or suitable for the program. ```go @@ -152,9 +152,9 @@ A write stages DCRDR, selects the register, and waits for transfer completion. An error after attempting to stage data leaves only `Release` available. If selection was attempted, the register may have changed even when the call returns an error. Release settles a pending transfer without replaying it. -Successful writes are intentional changes to processor state: release does -not roll them back, and resumed execution uses the changed values. An inherited -halt permits writes but still does not grant permission to resume. +Successful writes are intentional changes to processor state: release does not +roll them back, and resumed execution uses the changed values. An inherited halt +permits writes but still does not grant permission to resume. ## Composition @@ -189,24 +189,23 @@ go run ./examples/simple/cortexm-control \ ``` 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. +partial writes, canceled operations, ignored writes, failed cleanup, and retry. +They do not establish physical halt/resume behavior on a bench program. ## Hardware procedure The opt-in integration test selects the CMSIS-DAP micro:bit with serial -`9900360140124e4500279015000000360000000097969901`, AP0, and a requested -1 MHz clock. The nRF51 needs at least 125 kHz during debug activation after -power-on; see the [startup evidence](protocols/cmsisdap.md#nrf51-startup-clock). -It requires a known firmware program with an aligned 32-bit RAM -counter incremented by the CPU at least once per 200 milliseconds. The counter -must not be updated by DMA or another processor. Loading firmware is outside -the test. - -The [counter firmware](../target/cortexm/testdata/counter/README.md) supplies -a loop that increments the counter at `0x20000000`, with build instructions -and a separate programming procedure. Loading it replaces the target program -and resets the processor. +`9900360140124e4500279015000000360000000097969901`, AP0, and a requested 1 MHz +clock. The nRF51 needs at least 125 kHz during debug activation after power-on; +see the [startup evidence](protocols/cmsisdap.md#nrf51-startup-clock). It +requires a known firmware program with an aligned 32-bit RAM counter incremented +by the CPU at least once per 200 milliseconds. The counter must not be updated +by DMA or another processor. Loading firmware is outside the test. + +The [counter firmware](../target/cortexm/testdata/counter/README.md) supplies a +loop that increments the counter at `0x20000000`, with build instructions and a +separate programming procedure. Loading it replaces the target program and +resets the processor. ```sh OSTIOLE_CORTEXM_HIL_CONTROL=1 \ @@ -215,30 +214,29 @@ OSTIOLE_CORTEXM_HIL_COUNTER=0xRAM_ADDRESS \ go test -tags integration ./target/cortexm -run '^TestHILCortexM0Control$' -v ``` -Two fresh sessions check counter progress before control, no progress during -a halt, and renewed progress after resume and after release from a second -halt. The test compares inherited debug-enable and halt status before closing -the Arm debug owner. It refuses an already-halted bench. These observations -do not establish peripheral behavior, register preservation, reset, stepping, -or restoration after a physical transport failure. +Two fresh sessions check counter progress before control, no progress during a +halt, and renewed progress after resume and after release from a second halt. +The test compares inherited debug-enable and halt status before closing the Arm +debug owner. It refuses an already-halted bench. These observations do not +establish peripheral behavior, register preservation, reset, stepping, or +restoration after a physical transport failure. ## Hardware evidence -On September 26, 2026, Nostalgia (macOS) completed two control sessions at -1 MHz on the selected micro:bit, with Cortex-M0 CPUID `0x410cc200`. The -counter image had been programmed and verified with OpenOCD 0.12.0. Its -Intel HEX SHA-256 was -`ee294cc06ab6e8228161b49506675b065c0148b26421cf1f83c8e45e35cd4e5d`. -After a physical replug, Ostiole's read-only test connected first at 1 MHz, -then the control test ran without any intervening OpenOCD session. - -In both control sessions, the CPU counter advanced before acquisition, -remained unchanged across ten samples 20 milliseconds apart while halted, -and advanced after resume and after release from a second halt. The halted -values were `0x0d8abd3d` and `0x0db618ae`. DHCSR was `0x01000000` before -acquisition and after release in each session: debug was initially disabled, -acquisition enabled it, and release restored disabled debug with the processor -running. Both target releases and Arm debug owner closes completed. +On September 26, 2026, Nostalgia (macOS) completed two control sessions at 1 MHz +on the selected micro:bit, with Cortex-M0 CPUID `0x410cc200`. The counter image +had been programmed and verified with OpenOCD 0.12.0. Its Intel HEX SHA-256 was +`ee294cc06ab6e8228161b49506675b065c0148b26421cf1f83c8e45e35cd4e5d`. After a +physical replug, Ostiole's read-only test connected first at 1 MHz, then the +control test ran without any intervening OpenOCD session. + +In both control sessions, the CPU counter advanced before acquisition, remained +unchanged across ten samples 20 milliseconds apart while halted, and advanced +after resume and after release from a second halt. The halted values were +`0x0d8abd3d` and `0x0db618ae`. DHCSR was `0x01000000` before acquisition and +after release in each session: debug was initially disabled, acquisition enabled +it, and release restored disabled debug with the processor running. Both target +releases and Arm debug owner closes completed. An earlier pair of sessions at 100 kHz, after OpenOCD had activated the interface, preserved initially enabled debug. Those sessions do not establish @@ -261,18 +259,18 @@ go test -tags integration ./target/cortexm -run '^TestHILCortexM0Registers$' -co ``` On September 26, 2026, both fresh sessions passed on Nostalgia with CPUID -`0x410cc200`. Each read R0–R12, SP, LR, PC, XPSR, MSP, and PSP while halted. -R4 accepted `0x55aa55aa` and `0xaa55aa55`; SP, MSP, PSP, and PC accepted -temporary aligned values. SP and MSP aliased as expected for this firmware. -The test restored each written value and compared all 19 registers with the -saved snapshot before resuming. +`0x410cc200`. Each read R0–R12, SP, LR, PC, XPSR, MSP, and PSP while halted. R4 +accepted `0x55aa55aa` and `0xaa55aa55`; SP, MSP, PSP, and PC accepted temporary +aligned values. SP and MSP aliased as expected for this firmware. The test +restored each written value and compared all 19 registers with the saved +snapshot before resuming. The CPU counter remained unchanged across ten samples 20 milliseconds apart after register restoration, then advanced after resume and release. DHCSR was `0x01000000` before acquisition and after release in both sessions, with debug disabled and the processor running. Both target releases and Arm owner closes -completed. If register restoration cannot be confirmed, the test retains both owners -without requesting resume. +completed. If register restoration cannot be confirmed, the test retains both +owners without requesting resume. These sessions exercised register transfers while halted, not execution using the temporary PC or stack values. Writes to the other general registers and LR, @@ -292,27 +290,29 @@ OSTIOLE_CORTEXM_HIL_PROGRAM=sha256:ee294cc06ab6e8228161b49506675b065c0148b26421c go test -tags integration ./target/cortexm -run '^TestHILCortexM0Step$' -count=1 -v ``` -On September 26, 2026, two fresh sessions passed on Nostalgia through -CMSIS-DAP, 1 MHz SWD, and AP0, with CPUID `0x410cc200`. Each checked twelve -consecutive steps through the counter loop: +On September 26, 2026, two fresh sessions passed on Nostalgia through CMSIS-DAP, +1 MHz SWD, and AP0, with CPUID `0x410cc200`. Each checked twelve consecutive +steps through the counter loop: -- At PC `0xc6`, `adds r0, #1` advanced PC to `0xc8` and incremented R0, - leaving RAM unchanged. +- At PC `0xc6`, `adds r0, #1` advanced PC to `0xc8` and incremented R0, leaving + RAM unchanged. - At PC `0xc8`, `str r0, [r1]` advanced PC to `0xca` and copied R0 to the counter at `0x20000000`, leaving R0 unchanged. - At PC `0xca`, the branch returned PC to `0xc6`, leaving R0 and RAM unchanged. After every step, `Halted` confirmed Debug state with stepping and interrupt -masking disabled. The counter then remained unchanged across ten samples -20 milliseconds apart while halted, and advanced after resume. Each session -halted again, checked one further step, and released from that halt. The -counter advanced after release. DHCSR was `0x01000000` before acquisition and -after release in both sessions; initially disabled debug and running state -were restored. Both target releases and Arm owner closes completed. The -control example also completed a step with `-allow-control -step`. +masking disabled. The counter then remained unchanged across ten samples 20 +milliseconds apart while halted, and advanced after resume. Each session halted +again, checked one further step, and released from that halt. The counter +advanced after release. DHCSR was `0x01000000` before acquisition and after +release in both sessions; initially disabled debug and running state were +restored. Both target releases and Arm owner closes completed. The control +example also completed a step with `-allow-control -step`. Stepping's register and memory effects were intentional and were not rolled back. The firmware disables configurable interrupts, so these runs do not establish exception entry, competing debug events, sleeping instructions, or failure cleanup on hardware. Those control failures have behavioral coverage; state after Arm owner close was not measured. + +[armv6m]: https://documentation-service.arm.com/static/5f8ff05ef86e16515cdbf826 diff --git a/docs/linux-usb.md b/docs/linux-usb.md index 912eb60..9a193d2 100644 --- a/docs/linux-usb.md +++ b/docs/linux-usb.md @@ -7,9 +7,9 @@ execute repository code as root. ## Grant device access -Give the interactive user permission to open only the intended USB products. -For the FT232H used by the examples, a system using systemd-logind can install -this udev rule as `/etc/udev/rules.d/70-ostiole-ftdi.rules`: +Give the interactive user permission to open only the intended USB products. For +the FT232H used by the examples, a system using systemd-logind can install this +udev rule as `/etc/udev/rules.d/70-ostiole-ftdi.rules`: ```udev SUBSYSTEM=="usb", ATTR{idVendor}=="0403", ATTR{idProduct}=="6014", MODE="0660", TAG+="uaccess" @@ -30,10 +30,10 @@ Add rules only for the exact USB products the bench uses. ## Release a bound FTDI interface -Device-node permission is necessary but not sufficient when a kernel driver -owns the interface. Ostiole does not currently detach kernel drivers, and -`ftdi_sio` normally binds the FT232H. In that state, opening the usbfs node -succeeds but claiming the interface returns `EBUSY`. +Device-node permission is necessary but not sufficient when a kernel driver owns +the interface. Ostiole does not currently detach kernel drivers, and `ftdi_sio` +normally binds the FT232H. In that state, opening the usbfs node succeeds but +claiming the interface returns `EBUSY`. Before releasing a driver, confirm that no serial process is using the exact adapter. `go run ./cmd/ost ftdi list` reports its USB bus and address. Query the diff --git a/docs/ports/dap.md b/docs/ports/dap.md index c80b84c..2641bd2 100644 --- a/docs/ports/dap.md +++ b/docs/ports/dap.md @@ -1,18 +1,17 @@ # Arm Debug Access Port -The Debug Access Port (DAP) is the register fabric behind an Arm debug port. -SWD and JTAG-DP supply an AP/DP selector, two address bits, and a read/write bit; +The Debug Access Port (DAP) is the register fabric behind an Arm debug port. SWD +and JTAG-DP supply an AP/DP selector, two address bits, and a read/write bit; `SELECT` turns that small window into DP register banks and a set of Access -Ports (APs). The surprising parts are the posted AP pipeline, power -handshakes, and the amount of state a supposedly read-only memory inspection -can disturb. +Ports (APs). The surprising parts are the posted AP pipeline, power handshakes, +and the amount of state a supposedly read-only memory inspection can disturb. -Arm [IHI 0031H, _Arm Debug Interface Architecture Specification ADIv5.0 to -ADIv5.2_](https://developer.arm.com/documentation/ihi0031/h) is the normative -ADIv5 specification. Use chapters B2, B4, C1, and C2 for the programmer's -model and requirements. This note keeps only the traps worth having close at -hand and the hardware observations below. “DAP” here means Arm Debug Access -Port, not Microsoft's Debug Adapter Protocol. +Arm +[IHI 0031H, _Arm Debug Interface Architecture Specification ADIv5.0 to ADIv5.2_](https://developer.arm.com/documentation/ihi0031/h) +is the normative ADIv5 specification. Use chapters B2, B4, C1, and C2 for the +programmer's model and requirements. This note keeps only the traps worth having +close at hand and the hardware observations below. “DAP” here means Arm Debug +Access Port, not Microsoft's Debug Adapter Protocol. ## Binding a debug port @@ -23,8 +22,8 @@ access use the same APIs on either link: dp := dap.NewDebugPort(dap.SWDP(swdConn), dap.WithMaxWaits(100)) ``` -For JTAG, supply the complete expected chain and a zero-based TAP index, -nearest TDO first: +For JTAG, supply the complete expected chain and a zero-based TAP index, nearest +TDO first: ```go arm, err := jtag.IDCODE(4, 0x5ba00477) @@ -45,8 +44,8 @@ dp := dap.NewDebugPort(dap.JTAGDP(chain, 0), dap.WithMaxWaits(100)) These constructors send no traffic. The zero `dap.Port` is invalid, and `Connect` rejects nil connections, bad indices, and unsupported instruction widths before touching hardware. JTAG-DP supports architectural four- and -eight-bit instructions. The binding validates the exact complete layout; -it does not discover IR boundaries or activate board-specific chain routing. +eight-bit instructions. The binding validates the exact complete layout; it does +not discover IR boundaries or activate board-specific chain routing. Give `dp` exclusive use of the supplied SWD connection or JTAG chain until `Release` succeeds. `Connect` enters the protocol and acquires missing power @@ -63,11 +62,11 @@ func connectMEMAP(ctx context.Context, dp *dap.DebugPort, ap dap.APSel) (*dap.Me } ``` -Pass an explicit selector such as `dap.NewAPSel(1)`. The caller retains `dp` -and the probe on every return path, and takes responsibility for a returned -MEM-AP. After use or an error, release the MEM-AP if one was returned, then -release `dp`, then close the probe. Stop at the first cleanup failure and keep -that owner and its dependencies for retry. Each cleanup attempt needs a fresh, +Pass an explicit selector such as `dap.NewAPSel(1)`. The caller retains `dp` and +the probe on every return path, and takes responsibility for a returned MEM-AP. +After use or an error, release the MEM-AP if one was returned, then release +`dp`, then close the probe. Stop at the first cleanup failure and keep that +owner and its dependencies for retry. Each cleanup attempt needs a fresh, independent, bounded context. A failed restoration can be retried without repeating successful restoration steps. For managed ownership of this sequence, use `armdebug.Open` or `armdebug.Connect` with `armdebug.JTAGDP`; see the @@ -75,9 +74,9 @@ use `armdebug.Open` or `armdebug.Connect` with `armdebug.JTAGDP`; see the ## Connection identity -`DebugPort.Connect` returns a `dap.Identity`. Its accessors report whether -the corresponding identification register is present. With `dp` retained by -the caller as above: +`DebugPort.Connect` returns a `dap.Identity`. Its accessors report whether the +corresponding identification register is present. With `dp` retained by the +caller as above: ```go identity, err := dp.Connect(ctx) @@ -89,26 +88,24 @@ dpidr, present := identity.DPIDR() The SW-DP path supplies DPIDR; the JTAG-DP path supplies IDCODE. Each accessor reports absence on the other binding rather than reinterpreting its encoding. -The zero identity contains neither register. `DebugPort.Identity()` retains -the last successful identity -after release or a cleanup failure. Release acquired MEM-APs before releasing -the debug port, and keep the wire connection and probe open until that cleanup -succeeds. +The zero identity contains neither register. `DebugPort.Identity()` retains the +last successful identity after release or a cleanup failure. Release acquired +MEM-APs before releasing the debug port, and keep the wire connection and probe +open until that cleanup succeeds. Port operations and queued results support `errors.Is` with `dap.ErrWait`, `dap.ErrFault`, and `dap.ErrProtocol`. These classifications retain the underlying wire and context errors. `FaultError` reports captured CTRL/STAT -without naming a wire protocol; an SWD fault still matches `swd.ErrFault`. -If cancellation stops WAIT retries, the result remains a context error. +without naming a wire protocol; an SWD fault still matches `swd.ErrFault`. If +cancellation stops WAIT retries, the result remains a context error. Independently joined cleanup failures remain visible. `dap.WithCleanupTimeout(3 * time.Second)` gives each independent recovery attempt three seconds. The defaults are one second for SWD and thirty seconds -for JTAG. Full JTAG chain validation alone exceeds one second at 100 kHz; -allow more time at slower clocks. Recovery does not reuse a canceled operation -context. The option does not change the deadline -for ordinary operations, and `Connect` rejects nonpositive durations before -sending traffic. +for JTAG. Full JTAG chain validation alone exceeds one second at 100 kHz; allow +more time at slower clocks. Recovery does not reuse a canceled operation +context. The option does not change the deadline for ordinary operations, and +`Connect` rejects nonpositive durations before sending traffic. Pass a non-nil context to operations, including transaction commits and owner release. Operations reject nil contexts before sending traffic or changing @@ -123,8 +120,8 @@ banked DP registers are unavailable. ABORT accepts only the architectural DAPABORT value, `1`. Later JTAG-DP versions and version detection are not implemented, and IDCODE is not decoded as DPIDR. -The private JTAG executor uses 35-bit DPACC/APACC scans. Each capture belongs -to the preceding accepted request. A WAIT discards the newly shifted request; +The private JTAG executor uses 35-bit DPACC/APACC scans. Each capture belongs to +the preceding accepted request. A WAIT discards the newly shifted request; completion polling therefore does not resend the accepted operation. RDBUFF scans drain responses, while a logical JTAG RDBUFF read returns that register's own zero value. Other TAPs remain in BYPASS, and the generic JTAG layer splits @@ -138,21 +135,20 @@ checking fails, `Release` retries it before releasing the chain, even when the port inherited all power requests. An uncertain write, including immediate `WriteRawAP`, also reports `ErrIndeterminate` and invalidates existing MEM-AP handles; restoration remains available. JTAG transactions execute one logical -operation at a time. -SWD retains its packed physical -requests and confirmed-prefix behavior. Both paths leave the unsent transaction -suffix unexecuted after a failure; MEM-AP does not interpret either wire protocol. +operation at a time. SWD retains its packed physical requests and +confirmed-prefix behavior. Both paths leave the unsent transaction suffix +unexecuted after a failure; MEM-AP does not interpret either wire protocol. Connection setup primes the pipeline, establishes SELECT zero, clears sticky status, and temporarily disables inherited ORUNDETECT. Release restores that -setting. Active pushed-operation or transaction-counter modes are rejected, -and owned CTRL/STAT writes cannot enable them. A pending AP operation that -exhausts its WAIT bound requires ABORT and invalidates AP-derived state; its -error is indeterminate, not “not executed.” Cancellation remains the primary -operation error, and recovery uses the independent cleanup budget. -Abort recovery also checks and clears sticky status left by an operation -finishing during the abort. A failed check blocks further AP access and chain -release until cleanup succeeds. +setting. Active pushed-operation or transaction-counter modes are rejected, and +owned CTRL/STAT writes cannot enable them. A pending AP operation that exhausts +its WAIT bound requires ABORT and invalidates AP-derived state; its error is +indeterminate, not “not executed.” Cancellation remains the primary operation +error, and recovery uses the independent cleanup budget. Abort recovery also +checks and clears sticky status left by an operation finishing during the abort. +A failed check blocks further AP access and chain release until cleanup +succeeds. After scan-state loss, cleanup revalidates the exact chain and reacquires the selected TAP before restoring AP or DAP state. If the identity has changed, @@ -162,12 +158,12 @@ limitation and never reopens it automatically. ### FTDI JTAG-DP bench -On Nostalgia, the test completed two fresh direct-driver sessions and two -fresh discovered-probe sessions using FT4232H serial `01691`, port A at -100 kHz, and the explicit Arm `0x5ba00477`/IR4 and Xilinx `0x14730093`/IR12 -chain. AP1 reported IDR `0x44770002`; reads at -`0x80410ff0`, `0x80410ff4`, `0x80410ff8`, and `0x80410ffc` returned component -identification words `0x0d`, `0x90`, `0x05`, and `0xb1`. +On Nostalgia, the test completed two fresh direct-driver sessions and two fresh +discovered-probe sessions using FT4232H serial `01691`, port A at 100 kHz, and +the explicit Arm `0x5ba00477`/IR4 and Xilinx `0x14730093`/IR12 chain. AP1 +reported IDR `0x44770002`; reads at `0x80410ff0`, `0x80410ff4`, `0x80410ff8`, +and `0x80410ffc` returned component identification words `0x0d`, `0x90`, `0x05`, +and `0xb1`. ```sh OSTIOLE_ZCU104_JTAGDP_HIL=1 go test -tags=integration ./dap \ @@ -176,30 +172,30 @@ OSTIOLE_ZCU104_JTAGDP_HIL=1 go test -tags=integration ./dap \ In each session, AP1 CSW/TAR returned to `0x80000042`/`0x00000000`. Each acquired both power requests (`0x50000000`) with inherited ORUNDETECT clear, -then completed DAP/chain release and probe close. Fresh sessions found the -same inherited power/control state. +then completed DAP/chain release and probe close. Fresh sessions found the same +inherited power/control state. Board-specific chain activation was completed externally after a power cycle. The test did not halt a processor, assert target reset, or write target memory. -It does not establish later JTAG-DP register support or Arm JTAG-DP behavior -on the J-Link ESP32 bench. +It does not establish later JTAG-DP register support or Arm JTAG-DP behavior on +the J-Link ESP32 bench. ## The SW-DP register window -IHI 0031H sections B2.1-B2.2 and C1.2 define DP and AP addressing. These are -the bank-zero DP registers used by an ordinary ADIv5 connection: +IHI 0031H sections B2.1-B2.2 and C1.2 define DP and AP addressing. These are the +bank-zero DP registers used by an ordinary ADIv5 connection: -| Offset | Read | Write | -| ---: | --- | --- | -| `0x00` | DPIDR | ABORT | +| Offset | Read | Write | +| -----: | --------- | --------- | +| `0x00` | DPIDR | ABORT | | `0x04` | CTRL/STAT | CTRL/STAT | -| `0x08` | RESEND | SELECT | -| `0x0c` | RDBUFF | - | +| `0x08` | RESEND | SELECT | +| `0x0c` | RDBUFF | - | -`SELECT.DPBANKSEL` changes the banked DP register at `0x04`. -`SELECT.APSEL` chooses an AP and `SELECT.APBANKSEL` supplies AP address bits -`[7:4]`; the two address bits in the following AP transaction choose one of -four registers in that bank. +`SELECT.DPBANKSEL` changes the banked DP register at `0x04`. `SELECT.APSEL` +chooses an AP and `SELECT.APBANKSEL` supplies AP address bits `[7:4]`; the two +address bits in the following AP transaction choose one of four registers in +that bank. DPIDR bit 0 is always one. Bits `[15:12]` identify the DP architecture version. ADIv5 SW-DP defines DPv1 and DPv2; reading a structurally valid DPIDR does not @@ -221,23 +217,22 @@ make it harmless. IHI 0031H section B2.3 defines power control. The four high bits of CTRL/STAT form two request/acknowledgement pairs: -| Bit | Name | -| ---: | --- | -| 28 | CDBGPWRUPREQ | -| 29 | CDBGPWRUPACK | -| 30 | CSYSPWRUPREQ | -| 31 | CSYSPWRUPACK | +| Bit | Name | +| --: | ------------ | +| 28 | CDBGPWRUPREQ | +| 29 | CDBGPWRUPACK | +| 30 | CSYSPWRUPREQ | +| 31 | CSYSPWRUPACK | If system power is requested, debug power must be requested with it; asserting system power alone is UNPREDICTABLE. A host asserts the request bits, waits for both acknowledgement bits, and only then starts AP transfers. When releasing -power, it clears its requests and waits for the acknowledgements to clear -before asking again. +power, it clears its requests and waits for the acknowledgements to clear before +asking again. -The falling acknowledgement has a narrower meaning than it first appears to: -it says that the power controller accepted the request to remove power. It -does not prove that the domain is now off. Another requester may be keeping it -alive. +The falling acknowledgement has a narrower meaning than it first appears to: it +says that the power controller accepted the request to remove power. It does not +prove that the domain is now off. Another requester may be keeping it alive. Record newly requested power bits as owned before writing them. If the write then fails, the host cannot know whether the DP applied those bits. Bounded @@ -248,37 +243,36 @@ Read CTRL/STAT before the first request which can stall. If ORUNDETECT is already set, WAIT and FAULT have the overrun-detection data phase; use that response grammar until the bit is cleared. There is an annoying bootstrap problem: CTRL/STAT shares offset `0x04` with other DP banks, and a line reset -does not reset every DP register. After inheriting unknown DP state, read -DPIDR, clear the supported sticky conditions with ABORT, write zero to SELECT -once without retrying, read RDBUFF, then read CTRL/STAT at `0x04`. ABORT and -RDBUFF are bank-independent. ABORT first keeps inherited sticky state from -faulting SELECT; RDBUFF then shows whether the SELECT data took effect. If -either SELECT or RDBUFF returns WAIT or FAULT, the host cannot know which -response grammar it just received. Re-enter SWD before trying the bootstrap -again. Once another DP bank is selected, a read at `0x04` is no longer -CTRL/STAT and may legitimately return WAIT. - -The SELECT write's OK acknowledgement comes before its data. There is no -second acknowledgement after the parity bit, so the host cannot trust the new -SELECT value until later traffic shows whether the write data took effect. -DPIDR and ABORT do not settle that question because sticky state cannot make -them return FAULT. RDBUFF is bank-independent and can settle it without relying -on the requested bank. WDATAERR means that the DP might have abandoned the -write and kept the previous bank. Do not use `0x04` until RDBUFF has returned -OK. - -ABORT at DP offset `0x00` clears sticky conditions. On a full DP, writing -`0x1e` clears STICKYCMP, STICKYERR, WDATAERR, and STICKYORUN without setting -bit 0, DAPABORT. A Minimal DP does not implement pushed-compare operations; -its STKCMPCLR bit is reserved and the corresponding clear mask is `0x1c`. -Arm reserves DAPABORT for an AP transaction which has returned WAIT for an -extended period. Clearing all five bits with `0x1f` is not an equivalent -tidying operation. +does not reset every DP register. After inheriting unknown DP state, read DPIDR, +clear the supported sticky conditions with ABORT, write zero to SELECT once +without retrying, read RDBUFF, then read CTRL/STAT at `0x04`. ABORT and RDBUFF +are bank-independent. ABORT first keeps inherited sticky state from faulting +SELECT; RDBUFF then shows whether the SELECT data took effect. If either SELECT +or RDBUFF returns WAIT or FAULT, the host cannot know which response grammar it +just received. Re-enter SWD before trying the bootstrap again. Once another DP +bank is selected, a read at `0x04` is no longer CTRL/STAT and may legitimately +return WAIT. + +The SELECT write's OK acknowledgement comes before its data. There is no second +acknowledgement after the parity bit, so the host cannot trust the new SELECT +value until later traffic shows whether the write data took effect. DPIDR and +ABORT do not settle that question because sticky state cannot make them return +FAULT. RDBUFF is bank-independent and can settle it without relying on the +requested bank. WDATAERR means that the DP might have abandoned the write and +kept the previous bank. Do not use `0x04` until RDBUFF has returned OK. + +ABORT at DP offset `0x00` clears sticky conditions. On a full DP, writing `0x1e` +clears STICKYCMP, STICKYERR, WDATAERR, and STICKYORUN without setting bit 0, +DAPABORT. A Minimal DP does not implement pushed-compare operations; its +STKCMPCLR bit is reserved and the corresponding clear mask is `0x1c`. Arm +reserves DAPABORT for an AP transaction which has returned WAIT for an extended +period. Clearing all five bits with `0x1f` is not an equivalent tidying +operation. ## Posted AP transactions -IHI 0031H sections B4.2.2 and B4.2.7 define posted reads and write buffering. -AP reads are posted. The first AP read starts the access but returns an unknown +IHI 0031H sections B4.2.2 and B4.2.7 define posted reads and write buffering. AP +reads are posted. The first AP read starts the access but returns an unknown data value. A following AP read returns the previous result while starting another access. Reading DP RDBUFF returns the final result without starting a new AP access: @@ -297,24 +291,24 @@ transfer” is too vague to be useful. Writes can also be buffered. An OK acknowledgement means that the DP accepted the write, not that every earlier write has completed. An AP read, or a DP operation which the DP is allowed to stall, drains the write buffer. DPIDR and -CTRL/STAT reads and ABORT writes are exceptions: the DP must not stall them, -and using one too early can abandon buffered writes and set WDATAERR. A -RDBUFF read is therefore a useful completion barrier after AP writes. +CTRL/STAT reads and ABORT writes are exceptions: the DP must not stall them, and +using one too early can abandon buffered writes and set WDATAERR. A RDBUFF read +is therefore a useful completion barrier after AP writes. -WAIT applies to the physical request which received it. If SELECT returns -WAIT, repeat SELECT. If the AP request returns WAIT, repeat that AP request. -Once the AP request returns OK, however, it has been accepted. If the following -RDBUFF read returns WAIT, repeat RDBUFF; repeating the AP request would start -the access twice. The same distinction matters for writes even when writing -the same value twice happens to look harmless. +WAIT applies to the physical request which received it. If SELECT returns WAIT, +repeat SELECT. If the AP request returns WAIT, repeat that AP request. Once the +AP request returns OK, however, it has been accepted. If the following RDBUFF +read returns WAIT, repeat RDBUFF; repeating the AP request would start the +access twice. The same distinction matters for writes even when writing the same +value twice happens to look harmless. With ORUNDETECT set, the WAIT response also sets STICKYORUN. Clear that sticky condition through ABORT before retrying the WAITed request. A later FAULT in a -fixed response frame can mean that the DP abandoned a request after the -overrun; it is not permission to guess that the request ran. If SELECT was still -buffered when the WAIT arrived, the ABORT used to clear STICKYORUN can abandon -that write. Settle SELECT through RDBUFF before sending an AP or banked-DP -request, rather than guessing which selection survived after cleanup. +fixed response frame can mean that the DP abandoned a request after the overrun; +it is not permission to guess that the request ran. If SELECT was still buffered +when the WAIT arrived, the ABORT used to clear STICKYORUN can abandon that +write. Settle SELECT through RDBUFF before sending an AP or banked-DP request, +rather than guessing which selection survived after cleanup. Changing ORUNDETECT has the same write-data boundary as SELECT. Keep using the old response grammar through a following RDBUFF read. An OK acknowledgement @@ -326,10 +320,10 @@ which framing the target expects. That replay rule assumes that each WAIT response finished cleanly. If the wire fails during the following turnaround, the host no longer knows whether the DP -reached the next request header. If a later retry fails, the host might also -not know whether the AP access was accepted. DAPABORT is itself a normally -framed DP write, so it cannot repair unknown SWD framing. Abandon AP-derived -state and re-establish SWD before sending more requests. +reached the next request header. If a later retry fails, the host might also not +know whether the AP access was accepted. DAPABORT is itself a normally framed DP +write, so it cannot repair unknown SWD framing. Abandon AP-derived state and +re-establish SWD before sending more requests. “More requests” includes cleanup. Restoring AP registers or releasing power still uses ordinary framed DP and AP traffic. Re-enter SWD first, or stop @@ -351,12 +345,11 @@ Restore saved AP registers before releasing debug power or disconnecting. Successful cleanup does not make an invalidated AP handle usable again. Post-abort repair is ordered traffic, not a best-effort checklist. If the -CTRL/STAT read or sticky-clear write fails, SWD framing might be unknown; do -not follow it with SELECT. Re-establish framing before sending another -request. +CTRL/STAT read or sticky-clear write fails, SWD framing might be unknown; do not +follow it with SELECT. Re-establish framing before sending another request. -FAULT is different again. It reports sticky state and must not be replayed as -if it were WAIT. CTRL/STAT identifies the recorded conditions; an ABORT write +FAULT is different again. It reports sticky state and must not be replayed as if +it were WAIT. CTRL/STAT identifies the recorded conditions; an ABORT write clears the supported ones. Read CTRL/STAT again before resuming ordinary traffic: ABORT has no acknowledgement after its data phase either. WDATAERR describes a write which the DP abandoned, while STICKYERR records an error @@ -377,49 +370,47 @@ invalidation. IHI 0031H chapter C2 is the MEM-AP programmer's model. An ADIv5 MEM-AP reports class `0b1000` in IDR. A single-word memory access uses three registers: -| Register | Offset | Purpose | -| --- | ---: | --- | -| CSW | `0x00` | Access size, address increment, and bus attributes | -| TAR | `0x04` | Target address | -| DRW | `0x0c` | Data access at TAR | - -CFG at `0xf4` supplies three details needed before widening that operation: -BE selects the legacy big-endian byte-lane mapping, LA adds TARHI at `0x08`, -and LD advertises the Large Data Extension. LA widens the target address; it -does not widen TAR itself. When LA is set, save and restore TARHI along with -TAR. - -IHI 0031H chapter C2 defines the CSW.Size encodings and the DRW byte lanes. -Only 32-bit access is mandatory. When an unsupported size is written, CSW -reads back a size the AP does support; check that value before accessing DRW. -For 8- and 16-bit transfers, the value does not always occupy the low bits of -DRW: the address and CFG.BE select its lane. Sixty-four-bit access also needs -CFG.LD and a CSW.Size readback of `0b011`. It uses two consecutive DRW -accesses, low word first and then high word. Until the second access completes, -only CSW and DRW may be accessed; a CSW access terminates the sequence. An -address above 32 bits needs CFG.LA and TARHI; the two extensions are -independent. +| Register | Offset | Purpose | +| -------- | -----: | -------------------------------------------------- | +| CSW | `0x00` | Access size, address increment, and bus attributes | +| TAR | `0x04` | Target address | +| DRW | `0x0c` | Data access at TAR | + +CFG at `0xf4` supplies three details needed before widening that operation: BE +selects the legacy big-endian byte-lane mapping, LA adds TARHI at `0x08`, and LD +advertises the Large Data Extension. LA widens the target address; it does not +widen TAR itself. When LA is set, save and restore TARHI along with TAR. + +IHI 0031H chapter C2 defines the CSW.Size encodings and the DRW byte lanes. Only +32-bit access is mandatory. When an unsupported size is written, CSW reads back +a size the AP does support; check that value before accessing DRW. For 8- and +16-bit transfers, the value does not always occupy the low bits of DRW: the +address and CFG.BE select its lane. Sixty-four-bit access also needs CFG.LD and +a CSW.Size readback of `0b011`. It uses two consecutive DRW accesses, low word +first and then high word. Until the second access completes, only CSW and DRW +may be accessed; a CSW access terminates the sequence. An address above 32 bits +needs CFG.LA and TARHI; the two extensions are independent. For one scalar, set CSW.Size, disable address increment, write TAR (and TARHI when present), then read or write DRW. A DRW read is still an AP read, so its value comes back through the posted pipeline. A DRW write must still complete through RDBUFF before its effect can be attributed. -Automatic address increment is guaranteed only across TAR bits `[9:0]`. -Whether it crosses a 1 KiB boundary is implementation-defined in ADIv5. A -portable block implementation therefore ends each incrementing run and -reprograms TAR at the boundary. It cannot assume TAR advances linearly across -the boundary because it worked for the first kilobyte. The host also has to -read CSW back after requesting single address increment. If the setting does -not stick, it can disable increment and write TAR before every word. +Automatic address increment is guaranteed only across TAR bits `[9:0]`. Whether +it crosses a 1 KiB boundary is implementation-defined in ADIv5. A portable block +implementation therefore ends each incrementing run and reprograms TAR at the +boundary. It cannot assume TAR advances linearly across the boundary because it +worked for the first kilobyte. The host also has to read CSW back after +requesting single address increment. If the setting does not stick, it can +disable increment and write TAR before every word. Posted reads expose no result until a later AP read or RDBUFF. Buffered writes expose no per-write completion point before a draining operation. A failed completion therefore leaves the last read undelivered and the affected writes ambiguous; replaying those writes can duplicate effects. -Even a memory read can change debug-side state: SELECT, CSW, TAR, TARHI, and -the DAP power requests. Saving and restoring that state matters when another +Even a memory read can change debug-side state: SELECT, CSW, TAR, TARHI, and the +DAP power requests. Saving and restoring that state matters when another debugger, a ROM monitor, or later code expects to find it intact. A write has the additional and much less subtle effect of changing the target memory the caller selected. @@ -450,20 +441,19 @@ after the connection. The DAP test harness counted 85 OK acknowledgements and no WAIT, FAULT, or invalid acknowledgements. The USB round trip between single transfers likely -gave this target ample time to finish ordinary AP work. The absence of WAIT -here is a bench observation, not evidence that replay and abort recovery are -correct. +gave this target ample time to finish ordinary AP work. The absence of WAIT here +is a bench observation, not evidence that replay and abort recovery are correct. On 2026-08-22 I let each SWD connection enable ORUNDETECT, then inverted the data-parity bit of a same-value SELECT write in one test. The target set -WDATAERR and STICKYORUN, then returned FAULT on the next fixed-frame request. -A following CTRL/STAT read showed that WDERRCLR and ORUNERRCLR cleared both -sticky conditions, and the AP access completed. The seven DAP hardware tests -counted 194 OK acknowledgements, one FAULT, no WAIT, and no invalid -acknowledgements. Of those requests, 131 used fixed overrun-response frames. -Every debug-port release completed, including restoration of the inherited -ORUNDETECT setting. This exercises a target-generated sticky fault; it does -not exercise an AP-originated STICKYERR or a naturally occurring WAIT. +WDATAERR and STICKYORUN, then returned FAULT on the next fixed-frame request. A +following CTRL/STAT read showed that WDERRCLR and ORUNERRCLR cleared both sticky +conditions, and the AP access completed. The seven DAP hardware tests counted +194 OK acknowledgements, one FAULT, no WAIT, and no invalid acknowledgements. Of +those requests, 131 used fixed overrun-response frames. Every debug-port release +completed, including restoration of the inherited ORUNDETECT setting. This +exercises a target-generated sticky fault; it does not exercise an AP-originated +STICKYERR or a naturally occurring WAIT. The same bench scanned APSEL 0 through 255 and found two nonzero identities: @@ -476,10 +466,10 @@ AP0 matched a separate identity read. The scan itself used 32 SWDIO calls for 1,022 fixed overrun-response frames, all with OK acknowledgements. It does not demonstrate sparse numbering; the two implemented APs are adjacent. -A separately gated SRAM experiment saved 64 bytes at `0x20000000`, performed -8-, 16-, and 32-bit writes, and read the full range after each write. The -selected bytes changed and every neighboring byte retained its saved value. -The experiment then restored and verified all 64 original bytes. AP0 did not +A separately gated SRAM experiment saved 64 bytes at `0x20000000`, performed 8-, +16-, and 32-bit writes, and read the full range after each write. The selected +bytes changed and every neighboring byte retained its saved value. The +experiment then restored and verified all 64 original bytes. AP0 did not advertise CFG.LD, so this run says nothing about physical 64-bit transfers. It counted 3,130 OK acknowledgements, no WAIT, FAULT, or invalid acknowledgement, and 3,122 fixed overrun-response frames. Cleanup released the MEM-AP and debug @@ -493,11 +483,11 @@ go test -count=1 -p 1 -tags=integration -v ./dap ``` A read-only run compared one 64-byte block read from the same range with 64 -scalar byte reads and got the same bytes. The range was aligned to the start -of a TAR window, so it did not physically exercise boundary splitting. The -test counted 571 OK acknowledgements, no WAIT, FAULT, or invalid -acknowledgement, and 563 fixed overrun-response frames. Cleanup released the -MEM-AP and debug port, then closed the FTDI channel. +scalar byte reads and got the same bytes. The range was aligned to the start of +a TAR window, so it did not physically exercise boundary splitting. The test +counted 571 OK acknowledgements, no WAIT, FAULT, or invalid acknowledgement, and +563 fixed overrun-response frames. Cleanup released the MEM-AP and debug port, +then closed the FTDI channel. ```sh OSTIOLE_FTDI_HIL=1 \ @@ -506,19 +496,19 @@ go test -count=1 -p 1 -tags=integration -v ./dap \ -run '^TestReadMEMAPBlockOverFTDI$' ``` -The effectful run also wrote an aligned 64-byte pattern and an unaligned -31-byte pattern. Both read back exactly; the bytes neighboring the unaligned -range retained their saved values. The test restored and verified the original -64 bytes before releasing the MEM-AP. It counted 777 OK acknowledgements, no -WAIT, FAULT, or invalid acknowledgement, and 769 fixed overrun-response frames -in 207 SWDIO calls. The bench did not exercise indeterminate partial writes. +The effectful run also wrote an aligned 64-byte pattern and an unaligned 31-byte +pattern. Both read back exactly; the bytes neighboring the unaligned range +retained their saved values. The test restored and verified the original 64 +bytes before releasing the MEM-AP. It counted 777 OK acknowledgements, no WAIT, +FAULT, or invalid acknowledgement, and 769 fixed overrun-response frames in 207 +SWDIO calls. The bench did not exercise indeterminate partial writes. That is enough to identify one working DP/AP/MEM-AP path and one physical -WDATAERR recovery path, and to demonstrate reversible scalar and block writes -to one known SRAM range. It says nothing yet about sparse APs, delayed power +WDATAERR recovery path, and to demonstrate reversible scalar and block writes to +one known SRAM range. It says nothing yet about sparse APs, delayed power acknowledgements, physical WAIT responses, auto-increment across 1 KiB, or -64-bit transfers. Those are better experiments than collecting more CPUID -values from the same board. +64-bit transfers. Those are better experiments than collecting more CPUID values +from the same board. ## ADIv6 architecture @@ -545,19 +535,17 @@ if !present { } ``` -The entry may identify one component or a ROM table. Pass a present address -to `coresight.Identify` to determine its class. Address zero can be present; -absence returns `(0, false, nil)`. A failure returns `(0, false, err)` and -must not be treated as absence. The method uses the active MEM-AP's stored AP -selection and CFG.LA value; callers do not supply either again. It changes -DAP register selection but does not access target memory, change CSW/TAR, -request component power, or add cleanup obligations. After a DAP failure, -the existing recovery rules can invalidate the client; release it before -acquiring another. - -The decoder follows BASE in section C2.6.1 of -[Arm IHI 0031G](https://documentation-service.arm.com/static/622222b2e6f58973271ebc21). -It supports ADIv5 presence and legacy 32-bit addresses, including the legacy +The entry may identify one component or a ROM table. Pass a present address to +`coresight.Identify` to determine its class. Address zero can be present; +absence returns `(0, false, nil)`. A failure returns `(0, false, err)` and must +not be treated as absence. The method uses the active MEM-AP's stored AP +selection and CFG.LA value; callers do not supply either again. It changes DAP +register selection but does not access target memory, change CSW/TAR, request +component power, or add cleanup obligations. After a DAP failure, the existing +recovery rules can invalidate the client; release it before acquiring another. + +The decoder follows BASE in section C2.6.1 of [Arm IHI 0031G][adi-v6-spec]. It +supports ADIv5 presence and legacy 32-bit addresses, including the legacy all-ones absence value. CFG.LA enables the upper word at AP offset `0xf0`; without it, only `0xf8` is read. Absent entries do not cause an upper-word read. The decoder rejects legacy encodings with CFG.LA and nonzero reserved bits. @@ -568,9 +556,9 @@ The simulator's `SetMEMAPDebugBase` sets raw low and high words, including malformed values for failure tests. New simulated MEM-APs advertise no entry. BASE writes are ignored, and the high word reads as zero without CFG.LA. Behavioral tests cover both address formats, absence, malformed values, -cancellation, read failures, WAIT retries, and continued memory access. -Shared SWD/JTAG simulations exercise large addresses in both byte orders. -The [CoreSight guide](../coresight.md#hardware-evidence) records the advertised +cancellation, read failures, WAIT retries, and continued memory access. Shared +SWD/JTAG simulations exercise large addresses in both byte orders. The +[CoreSight guide](../coresight.md#hardware-evidence) records the advertised addresses and identity reads observed on the micro:bit and ZCU104 benches. ## DPv3 discovery registers @@ -596,13 +584,13 @@ fmt.Printf("DPIDR1=%#08x BASEPTR0=%#08x\n", width, base) The caller retains the connected debug port and must release it afterward. `dap.APAt(base)` constructs an ADIv6 selector; `NewAPSel(index)` remains the -ADIv5 constructor. `ReadAPIDR` selects the appropriate IDR offset. Raw -register access accepts a twelve-bit ADIv6 offset, and rejects a selector for -the wrong architecture or an address beyond DPIDR1.ASIZE before AP traffic. -Queued AP operations and `OpenMemAP` also accept ADIv6 selectors. ADIv6 -transactions complete each operation before sending the next; ADIv5 SWD -retains its packed execution. The debug port rejects active DP ERRMODE, and -MEM-AP acquisition rejects error modes which can suppress or defer errors. +ADIv5 constructor. `ReadAPIDR` selects the appropriate IDR offset. Raw register +access accepts a twelve-bit ADIv6 offset, and rejects a selector for the wrong +architecture or an address beyond DPIDR1.ASIZE before AP traffic. Queued AP +operations and `OpenMemAP` also accept ADIv6 selectors. ADIv6 transactions +complete each operation before sending the next; ADIv5 SWD retains its packed +execution. The debug port rejects active DP ERRMODE, and MEM-AP acquisition +rejects error modes which can suppress or defer errors. ```go ap, err := dap.APAt(0x2000) @@ -617,9 +605,9 @@ fmt.Printf("AP IDR=%#08x\n", id.Raw) ``` `APSel.Address` now accepts `uint16` rather than `uint8`. Untyped constants -remain unchanged; callers with a typed byte offset change -`ap.Address(offset)` to `ap.Address(uint16(offset))`. `Value` returns only an -ADIv5 index; use `BaseAddress` for an ADIv6 selector. +remain unchanged; callers with a typed byte offset change `ap.Address(offset)` +to `ap.Address(uint16(offset))`. `Value` returns only an ADIv5 index; use +`BaseAddress` for an ADIv6 selector. The same managed owner can acquire an ADIv6 MEM-AP: @@ -646,8 +634,8 @@ attributes. This does not acquire or halt the processor. ## Discovering ADIv6 access ports -`DebugPort.DebugSpace` borrows the DP's debug address space. Its -`ReadDebugBase` reads BASEPTR0/1, and its aligned `Size32` reader composes with +`DebugPort.DebugSpace` borrows the DP's debug address space. Its `ReadDebugBase` +reads BASEPTR0/1, and its aligned `Size32` reader composes with `coresight.Identify` and `coresight.Walk`: ```go @@ -666,11 +654,14 @@ visits, err := coresight.Walk(ctx, space, base, coresight.WalkLimits{ The visits retain component bases and architecture IDs. A present Arm MEM-AP architecture identifies an AP base that can be passed to `dap.APAt`. Keep -partial results and the walk error if inspection stops. The walker follows -only advertised entries within its bounds; it does not scan the address space -or acquire component power. +partial results and the walk error if inspection stops. The walker follows only +advertised entries within its bounds; it does not scan the address space or +acquire component power. Inspect this space before acquiring MEM-APs: raw AP reads invalidate existing MEM-AP clients and can have register-specific effects. The reader owns no -cleanup; the caller still releases the debug port. The DP's discovery base -and a MEM-AP's debug base belong to different address spaces. +cleanup; the caller still releases the debug port. The DP's discovery base and a +MEM-AP's debug base belong to different address spaces. + +[adi-v6-spec]: + https://documentation-service.arm.com/static/622222b2e6f58973271ebc21 diff --git a/docs/protocols/cmsisdap.md b/docs/protocols/cmsisdap.md index a5b151e..b9fefe1 100644 --- a/docs/protocols/cmsisdap.md +++ b/docs/protocols/cmsisdap.md @@ -6,12 +6,18 @@ defines the command protocol, and its [USB firmware guidance][usb] defines the v2 USB interface. This note records the narrower host boundary Ostiole implements. -[commands]: https://arm-software.github.io/CMSIS-DAP/latest/group__DAP__Info.html -[usb]: https://arm-software.github.io/CMSIS-DAP/latest/dap_firmware.html#dap_bulk_usb -[connect]: https://arm-software.github.io/CMSIS-DAP/latest/group__DAP__Connect.html -[clock]: https://arm-software.github.io/CMSIS-DAP/latest/group__DAP__SWJ__Clock.html -[sequence]: https://arm-software.github.io/CMSIS-DAP/latest/group__DAP__SWD__Sequence.html -[disconnect]: https://arm-software.github.io/CMSIS-DAP/latest/group__DAP__Disconnect.html +[commands]: + https://arm-software.github.io/CMSIS-DAP/latest/group__DAP__Info.html +[usb]: + https://arm-software.github.io/CMSIS-DAP/latest/dap_firmware.html#dap_bulk_usb +[connect]: + https://arm-software.github.io/CMSIS-DAP/latest/group__DAP__Connect.html +[clock]: + https://arm-software.github.io/CMSIS-DAP/latest/group__DAP__SWJ__Clock.html +[sequence]: + https://arm-software.github.io/CMSIS-DAP/latest/group__DAP__SWD__Sequence.html +[disconnect]: + https://arm-software.github.io/CMSIS-DAP/latest/group__DAP__Disconnect.html ## Discovery and interface selection @@ -26,9 +32,9 @@ The application still selects one complete `usb.DeviceInfo`. It may explicitly select a known composite attachment which is absent from `Candidates`. `cmsisdap.Open` accepts the selected device only when its active descriptors contain exactly one vendor-specific `ff/00/00` alternate. The endpoints must -appear in the order required by the v2 interface: bulk OUT for commands, bulk -IN for responses, and optionally a distinct bulk IN endpoint for SWO. The -current package records but does not use the optional SWO endpoint. +appear in the order required by the v2 interface: bulk OUT for commands, bulk IN +for responses, and optionally a distinct bulk IN endpoint for SWO. The current +package records but does not use the optional SWO endpoint. An HID interface is CMSIS-DAP v1 rather than v2. `Open` rejects it with `ErrNoV2Interface`; it does not fall back to interrupt transfers. @@ -38,9 +44,9 @@ An HID interface is CMSIS-DAP v1 rather than v2. `Open` rejects it with A successful `Open` claims and selects the command interface, resolves the active endpoints, and sends only `DAP_Info` commands. It reads packet size, packet count, capabilities, protocol version, vendor, product, serial, and -firmware version. A missing product or serial uses the corresponding USB -string. The USB package does not currently expose a manufacturer string, so a -missing CMSIS-DAP vendor remains empty. +firmware version. A missing product or serial uses the corresponding USB string. +The USB package does not currently expose a manufacturer string, so a missing +CMSIS-DAP vendor remains empty. The packet-size query begins with the active bulk IN maximum packet size as a bootstrap response capacity. Once the probe reports its command packet size, @@ -63,30 +69,28 @@ clock, or touch a target. ## SWD connection and sequences -`WithSWD` during open or `ConfigureSWD` afterward first requires protocol 1.2 -or later and the SWD capability reported by `DAP_Info`. It sends +`WithSWD` during open or `ConfigureSWD` afterward first requires protocol 1.2 or +later and the SWD capability reported by `DAP_Info`. It sends [DAP_Connect][connect] with port 1, then sends the caller's requested maximum frequency as the little-endian hertz value in [DAP_SWJ_Clock][clock]. A -successful clock response says that the request was accepted; CMSIS-DAP does -not report the rate the probe attained. Reconfiguring an active session -disconnects it first. - -The configured session implements `swd.Wire` with -[DAP_SWD_Sequence][sequence], which is available in CMSIS-DAP 1.2 and later. -For each set direction bit, the probe drives SWDIO; for each clear bit, it -samples SWDIO. Each run carries at most 64 cycles, with 64 encoded as zero; -data is packed least-significant bit first. Output bytes are absent from input -sequences, so a caller's output bits are never driven while the target owns -SWDIO. +successful clock response says that the request was accepted; CMSIS-DAP does not +report the rate the probe attained. Reconfiguring an active session disconnects +it first. + +The configured session implements `swd.Wire` with [DAP_SWD_Sequence][sequence], +which is available in CMSIS-DAP 1.2 and later. For each set direction bit, the +probe drives SWDIO; for each clear bit, it samples SWDIO. Each run carries at +most 64 cycles, with 64 encoded as zero; data is packed least-significant bit +first. Output bytes are absent from input sequences, so a caller's output bits +are never driven while the target owns SWDIO. One command can contain several runs. The driver keeps both its request and -expected response within the negotiated packet size, then starts another -command when either side is full or the 255-sequence count is exhausted. It -does not use the reported packet count to pipeline commands. One logical -`SWDIO` call may therefore complete several command exchanges, in order, up to -the driver's conservative 16,384-bit limit. A failure stops at that packet and -no command is replayed; the probe may already have clocked the prefix sent in -earlier packets. +expected response within the negotiated packet size, then starts another command +when either side is full or the 255-sequence count is exhausted. It does not use +the reported packet count to pipeline commands. One logical `SWDIO` call may +therefore complete several command exchanges, in order, up to the driver's +conservative 16,384-bit limit. A failure stops at that packet and no command is +replayed; the probe may already have clocked the prefix sent in earlier packets. When the probe returns a complete `DAP_ERROR`, `SWDIO` returns an error naming that packet but leaves the command stream synchronized. A response with the @@ -100,38 +104,38 @@ retains ownership for another attempt. After failed SWD configuration, `Open` makes another bounded disconnect attempt; if synchronized cleanup remains pending, the caller receives the non-nil session with the setup error and retries `Session.Close`. After a poisoned exchange, `Close` reports the -abandoned active port and continues USB cleanup without sending another -command. Device close still runs once and its result is cached. +abandoned active port and continues USB cleanup without sending another command. +Device close still runs once and its result is cached. ## Bench observation The `0d28:0204` BBC micro:bit attached to the macOS bench identifies itself as `BBC micro:bit CMSIS-DAP` and exposes the v2 bulk command interface. The HIL selected it from the product-string shortlist, opened and closed one metadata -session, then opened a second. Both sessions reported protocol `2.1.0`, -firmware `0257`, packet size 64, packet count 5, and capabilities `0x11`. They -sent no `DAP_Connect` or target traffic. +session, then opened a second. Both sessions reported protocol `2.1.0`, firmware +`0257`, packet size 64, packet count 5, and capabilities `0x11`. They sent no +`DAP_Connect` or target traffic. Two fresh read-only sessions selected the same serial and requested a 100 kHz maximum clock. One called `ConfigureSWD` after metadata-only open; the other used `WithSWD`. Both returned DPIDR `0x0bb11477`, AP0 IDR `0x04770021`, CPUID `0x410cc200`, and DHCSR `0x01000001`. Each saved and restored AP0 CSW and TAR, -released the debug port, disconnected the CMSIS-DAP session, and reproduced -the identities and `DHCSR.S_HALT` state after reopen. Each session sent 20 -packed SWD frames in 57 `SWDIO` calls. +released the debug port, disconnected the CMSIS-DAP session, and reproduced the +identities and `DHCSR.S_HALT` state after reopen. Each session sent 20 packed +SWD frames in 57 `SWDIO` calls. OpenOCD 0.12.0 independently selected the same serial and v2 bulk interface at 100 kHz. It returned the same DPIDR and AP0 IDR and identified the target as -Cortex-M0. These observations cover one probe, one target, read-only access, -and restoration of the session and target state. They do not establish the -clock the probe attained or validate JTAG or SWO. +Cortex-M0. These observations cover one probe, one target, read-only access, and +restoration of the session and target state. They do not establish the clock the +probe attained or validate JTAG or SWO. The DAPLink `0d28:0204` attached to the Linux bench identifies itself as -`DAPLink CMSIS-DAP`, but its command interface is HID. Its other -vendor-specific interface has subclass 3 and no endpoints. The product-string -inventory reported its product and serial. The HIL selected it by serial, and -the v2 opener rejected it before claiming an interface. No CMSIS-DAP command -or target traffic was sent. +`DAPLink CMSIS-DAP`, but its command interface is HID. Its other vendor-specific +interface has subclass 3 and no endpoints. The product-string inventory reported +its product and serial. The HIL selected it by serial, and the v2 opener +rejected it before claiming an interface. No CMSIS-DAP command or target traffic +was sent. The Linux bench still exercises only passive v1 rejection. @@ -140,10 +144,9 @@ The Linux bench still exercises only passive v1 rejection. The nRF51 on the micro:bit requires at least 125 kHz when entering debug interface mode after power-on. Nordic also specifies at least 150 SWCLK cycles with SWDIO high to guarantee that the DAP captures 50 cycles while its power -domain starts; see section 11.1.2 of the -[nRF51 reference manual](https://docs-be.nordicsemi.com/bundle/nRF51-Series/raw/resource/enus/nRF51_RM_v3.0.1.pdf). -A slower clock can work once another debugger has activated the interface. -The CMSIS-DAP driver accepts the caller's clock ceiling; it does not know the +domain starts; see section 11.1.2 of the [nRF51 reference manual][nrf51-manual]. +A slower clock can work once another debugger has activated the interface. The +CMSIS-DAP driver accepts the caller's clock ceiling; it does not know the target's startup requirements or silently raise that ceiling. On Nostalgia, Ostiole connected first after a physical micro:bit replug at 1 MHz @@ -166,6 +169,9 @@ go test -tags integration ./cmsisdap \ For a startup check, physically unplug/replug before running the command and leave other debuggers stopped. Reopening a session alone does not reproduce power-on state. The earlier 100 kHz evidence above covers an active interface; -it does not establish startup from power-on. The new result covers one board -and firmware revision, with no measurement of the attained clock or guarantee -of target-specific activation timing on other devices. +it does not establish startup from power-on. The new result covers one board and +firmware revision, with no measurement of the attained clock or guarantee of +target-specific activation timing on other devices. + +[nrf51-manual]: + https://docs-be.nordicsemi.com/bundle/nRF51-Series/raw/resource/enus/nRF51_RM_v3.0.1.pdf diff --git a/docs/protocols/jlink.md b/docs/protocols/jlink.md index 3448926..d1a3a54 100644 --- a/docs/protocols/jlink.md +++ b/docs/protocols/jlink.md @@ -3,10 +3,10 @@ The `jlink` package implements the smallest J-Link application path needed to provide `swd.Wire` and `jtag.Wire`. The command grammar and conservative host rules come from Jon Olson's [independent J-Link over USB reference, edition -1.0][reference]. The reference draws on public sources and bounded -experiments; it is not SEGGER documentation. This note records the -implementation boundary and the physical observations behind host choices -which the reference leaves open. +1.0][reference]. The reference draws on public sources and bounded experiments; +it is not SEGGER documentation. This note records the implementation boundary +and the physical observations behind host choices which the reference leaves +open. [reference]: https://jon.dev/traces/j-link-over-usb/versions/1.0 @@ -14,8 +14,8 @@ Ostiole takes the command and transport rules it implements from `core-host` claims in the reference. Ostiole's exact candidate catalog also includes the additional PIDs recorded from the pinned libjaylink revision; those remain discovery candidates rather than a product-family rule, and `Open` still -requires the active descriptors to match. Ostiole narrows descriptor binding -to `ff/ff/ff` with exactly two bulk endpoints. The firmware-scoped sample +requires the active descriptors to match. Ostiole narrows descriptor binding to +`ff/ff/ff` with exactly two bulk endpoints. The firmware-scoped sample correction described below applies the corresponding `observed-extension` only under its exact product and record guard. The package emits no `research-only` operation. @@ -25,45 +25,45 @@ operation. Discovery uses a reviewed list of SEGGER application PIDs. Opening then finds exactly one active `ff/ff/ff` alternate with one bulk IN and one bulk OUT endpoint. USB owns descriptor parsing and transfers; `jlink` owns the command -stream, capability gates, interface selection, target clock, scan framing, -and probe status. `swd` owns SWD request and response grammar; `jtag` owns TAP +stream, capability gates, interface selection, target clock, scan framing, and +probe status. `swd` owns SWD request and response grammar; `jtag` owns TAP state, chain validation, and selected-TAP scans. A metadata-only open sends version and capability-gated metadata queries but does not select a target interface. `WithSWD` or `ConfigureSWD` selects advertised interface 1; `WithJTAG` or `ConfigureJTAG` selects advertised -interface 0. Both then request the target clock. The package reports -the requested whole-kHz rate. The clock operation has no application response, -so it does not prove that a target can sustain the rate. The package does not -request adaptive clocking. +interface 0. Both then request the target clock. The package reports the +requested whole-kHz rate. The clock operation has no application response, so it +does not prove that a target can sustain the rate. The package does not request +adaptive clocking. Combining SWD and JTAG options in one open fails before traffic. Repeating an option for the same protocol uses the last clock ceiling. `SWDIO` and `JTAGIO` require the corresponding configured interface; neither switches it implicitly. -Release a live protocol connection before reconfiguring the session. The -generic `Probe` owner permits only one activation and requires a fresh owner -to select a different protocol. - -Only one operation may be outstanding. A known-length response may span -several USB completions. Surplus bytes from a completion are retained only for -the following response phase of that operation; bytes left after the final -phase poison the session. One zero-length packet is tolerated while reading a -known response; continued lack of progress, an invalid count, cancellation -after a command is sent, or another USB failure poisons the session. The -returned error preserves the original failure. Recovery is close, reopen, and -explicit reconfiguration; commands are never replayed. +Release a live protocol connection before reconfiguring the session. The generic +`Probe` owner permits only one activation and requires a fresh owner to select a +different protocol. + +Only one operation may be outstanding. A known-length response may span several +USB completions. Surplus bytes from a completion are retained only for the +following response phase of that operation; bytes left after the final phase +poison the session. One zero-length packet is tolerated while reading a known +response; continued lack of progress, an invalid count, cancellation after a +command is sent, or another USB failure poisons the session. The returned error +preserves the original failure. Recovery is close, reopen, and explicit +reconfiguration; commands are never replayed. ## Scan v3 The request is command `0xcf`, reserved byte zero, a little-endian bit count, -then two packed streams. In SWD these are direction and output; in JTAG they -are TMS and TDI. Bits are least-significant first. SWD clears output bits for +then two packed streams. In SWD these are direction and output; in JTAG they are +TMS and TDI. Bits are least-significant first. SWD clears output bits for target-driven cycles; JTAG preserves TDI independently of TMS and returns TDO -without sample shifting. Neither method changes the caller's buffers. -It reads the packed sample bytes and trailing status as distinct response -phases. Status zero succeeds; status 6 reports insufficient probe workspace. -A complete nonzero status leaves USB framing known but clears protocol -configuration, so a caller must configure again. +without sample shifting. Neither method changes the caller's buffers. It reads +the packed sample bytes and trailing status as distinct response phases. Status +zero succeeds; status 6 reports insufficient probe workspace. A complete nonzero +status leaves USB framing known but clears protocol configuration, so a caller +must configure again. The default ceiling is 504 bits, and a reported workspace can lower it. USB packet size does not lower the scan ceiling: the USB layer preserves full, @@ -72,11 +72,11 @@ coalesced status byte for the following response phase. `swd.Batch` can place nine 54-bit overrun frames in one 486-bit scan without teaching `jlink` about SWD transactions. -JTAG accepts an empty call without sending a command. A reported workspace -must accommodate at least eight clocks for JTAG; SWD retains its 136-bit -minimum connection sequence. The JTAG layer splits longer movements and scans -at the supplied limit. No JTAG-to-SWD selection, TAP movement, physical reset, -or target-assist command is hidden in JTAG configuration. +JTAG accepts an empty call without sending a command. A reported workspace must +accommodate at least eight clocks for JTAG; SWD retains its 136-bit minimum +connection sequence. The JTAG layer splits longer movements and scans at the +supplied limit. No JTAG-to-SWD selection, TAP movement, physical reset, or +target-assist command is hidden in JTAG configuration. ## Using JTAG @@ -91,10 +91,10 @@ if err != nil { conn := jtag.New(session) ``` -Alternatively, open without options and call `session.ConfigureJTAG(ctx, -100_000)`. Configuration failure leaves the session available for `Close`. -For registered discovery, import `jlink/discovery` and activate the selected -probe's borrowed wire: +Alternatively, open without options and call +`session.ConfigureJTAG(ctx, 100_000)`. Configuration failure leaves the session +available for `Close`. For registered discovery, import `jlink/discovery` and +activate the selected probe's borrowed wire: ```go owner, err := discover.OpenProbe(ctx, selector) @@ -108,38 +108,38 @@ if err != nil { conn := jtag.New(wire) ``` -Both constructors leave TAP state unknown until explicit reset or discovery. -Use an explicit `jtag.Layout` for selected-TAP operations. Retain the chain and +Both constructors leave TAP state unknown until explicit reset or discovery. Use +an explicit `jtag.Layout` for selected-TAP operations. Retain the chain and release it with an independent bounded cleanup context before closing the session or probe. Failed cleanup remains retryable; do not discard its owner. -Starting session close invalidates scans even if USB release needs a retry. -See [JTAG](jtag.md) for the full chain lifecycle and cleanup example. +Starting session close invalidates scans even if USB release needs a retry. See +[JTAG](jtag.md) for the full chain lifecycle and cleanup example. ## Bench observations A J-Link EDU Mini V2 running firmware -`J-Link EDU Mini V2 compiled Jun 25 2026 10:27:52` was exercised at 100 kHz. -Its raw target-input samples were displaced by one target-driven clock across -scan boundaries. The package corrects that stream only for the exact observed -USB product and full firmware record. For an unrecognized firmware record, the +`J-Link EDU Mini V2 compiled Jun 25 2026 10:27:52` was exercised at 100 kHz. Its +raw target-input samples were displaced by one target-driven clock across scan +boundaries. The package corrects that stream only for the exact observed USB +product and full firmware record. For an unrecognized firmware record, the package returns the protocol sample bytes unchanged. This correction applies only to SWD; JTAG samples are unchanged even on that firmware. The initial target returned DPIDR `0x0BB11477`, SW-DP version 1, designer `0x23B`, and AP0 IDR `0x04770021`. SW-DP version 1 does not use the SW-DPv2 multidrop selection mechanism. AP0 memory access returned CPUID `0x410CC200` -with part `0xC20`, identifying a Cortex-M0. Read-only composition through -`dap` and `MemAP` restored the saved AP0 CSW and TAR values before release. A -fresh J-Link session then returned the same DPIDR and CPUID with DHCSR.S_HALT +with part `0xC20`, identifying a Cortex-M0. Read-only composition through `dap` +and `MemAP` restored the saved AP0 CSW and TAR values before release. A fresh +J-Link session then returned the same DPIDR and CPUID with DHCSR.S_HALT unchanged. A second target on the same probe returned DPIDR `0x2BA01477`, AP0 IDR -`0x24770011`, and CPUID `0x410FC241` with part `0xC24`. Ten complete -restoration runs at a requested 100 kHz used twenty fresh J-Link sessions. In -every run the two sessions agreed on DPIDR and CPUID, DHCSR.S_HALT was -unchanged, and the saved AP0 CSW and TAR values were restored before release. -Each composition used 20 fixed frames in 48 SWDIO calls per session. - -These observations establish the current path only. They do not generalize -the sample correction or transfer limit to other J-Link products, firmware, -USB speeds, or targets. +`0x24770011`, and CPUID `0x410FC241` with part `0xC24`. Ten complete restoration +runs at a requested 100 kHz used twenty fresh J-Link sessions. In every run the +two sessions agreed on DPIDR and CPUID, DHCSR.S_HALT was unchanged, and the +saved AP0 CSW and TAR values were restored before release. Each composition used +20 fixed frames in 48 SWDIO calls per session. + +These observations establish the current path only. They do not generalize the +sample correction or transfer limit to other J-Link products, firmware, USB +speeds, or targets. diff --git a/docs/protocols/jtag.md b/docs/protocols/jtag.md index 46cdaf1..d42670a 100644 --- a/docs/protocols/jtag.md +++ b/docs/protocols/jtag.md @@ -1,9 +1,8 @@ # JTAG -`jtag.Conn` owns the TAP state machine over a caller-supplied `jtag.Wire`. -It does not open or close an adapter. Give the connection exclusive use of -that wire, serialize calls, and keep the wire's owner alive until all calls -finish. +`jtag.Conn` owns the TAP state machine over a caller-supplied `jtag.Wire`. It +does not open or close an adapter. Give the connection exclusive use of that +wire, serialize calls, and keep the wire's owner alive until all calls finish. ```go conn := jtag.New(wire) // No traffic; wire belongs to its caller. @@ -13,17 +12,17 @@ if err := conn.Reset(ctx); err != nil { return conn.Move(ctx, jtag.Idle) ``` -Reset clocks five TMS-high cycles; it does not assert a physical reset pin. -Move follows a shortest path through the IEEE 1149.1 state machine, driving -TDI low. Crossing update states can commit instructions or data, and TAP -reset can change debug state. The caller chooses which transitions are -appropriate for the target. +Reset clocks five TMS-high cycles; it does not assert a physical reset pin. Move +follows a shortest path through the IEEE 1149.1 state machine, driving TDI low. +Crossing update states can commit instructions or data, and TAP reset can change +debug state. The caller chooses which transitions are appropriate for the +target. -`ScanIR` and `ScanDR` capture and update a complete chain, then return to -Idle. They accept a packed input buffer and a positive bit count; unused -buffer bits are ignored. Each call starts a new capture, including when a -previous Move stopped in a shift or pause state. Raw scans must not bypass a -higher-level owner using the connection. +`ScanIR` and `ScanDR` capture and update a complete chain, then return to Idle. +They accept a packed input buffer and a positive bit count; unused buffer bits +are ignored. Each call starts a new capture, including when a previous Move +stopped in a shift or pause state. Raw scans must not bypass a higher-level +owner using the connection. ```go captured, err := conn.ScanIR(ctx, []byte{0xff}, 8) @@ -31,11 +30,11 @@ _ = captured return err ``` -That example shifts eight one bits. The caller must know the chain length -and what instruction those bits select on each device. `Idle(ctx, cycles)` -provides Run-Test/Idle clocks when the selected instruction needs them. A -zero-cycle request sends no traffic. Scans and idle requests are capped by -`MaxScanBits` (1,048,576); wire limits still apply to each physical transfer. +That example shifts eight one bits. The caller must know the chain length and +what instruction those bits select on each device. `Idle(ctx, cycles)` provides +Run-Test/Idle clocks when the selected instruction needs them. A zero-cycle +request sends no traffic. Scans and idle requests are capped by `MaxScanBits` +(1,048,576); wire limits still apply to each physical transfer. `Discover(ctx, maxTAPs)` resets the chain and returns detached reset-register observations, nearest TDO first. Each observation is either an IDCODE or an @@ -45,12 +44,12 @@ recognizes an all-one terminator. An empty path and TDO stuck high both return `ErrNoChain`; an overlong chain and TDO stuck low return `ErrDiscoveryLimit` with the observed prefix. A partial inventory is not a validated chain. -`MeasureIR(ctx, maxBits)` measures the total instruction-chain length, not -the individual TAP lengths. After filling the register with ones, it shifts -a single zero marker through and measures its delay. It finishes in -BYPASS/Idle when the actual length fits the supplied bound (2 through 65,536 -bits). A wrong bound or interrupted transfer can leave other instructions; -the caller must account for that effect when inspecting an unknown chain. +`MeasureIR(ctx, maxBits)` measures the total instruction-chain length, not the +individual TAP lengths. After filling the register with ones, it shifts a single +zero marker through and measures its delay. It finishes in BYPASS/Idle when the +actual length fits the supplied bound (2 through 65,536 bits). A wrong bound or +interrupted transfer can leave other instructions; the caller must account for +that effect when inspecting an unknown chain. ## Explicit layouts @@ -58,12 +57,11 @@ the caller must account for that effect when inspecting an unknown chain. empty, oversized, or invalid specifications without traffic; only `Chain.Connect` compares the expected layout with the physical chain. -`NewChain(conn, layout)` copies a nonempty layout in scan-out order (nearest -TDO first). Build each entry with `IDCODE(irBits, id)` or `Bypass(irBits)`; -the zero specification is invalid. IR lengths must be 2 through 64 bits. -IDCODEs match exactly, including revision bits. Bypass entries carry no -physical identity guarantee, and equal IDCODEs do not identify individual -devices. +`NewChain(conn, layout)` copies a nonempty layout in scan-out order (nearest TDO +first). Build each entry with `IDCODE(irBits, id)` or `Bypass(irBits)`; the zero +specification is invalid. IR lengths must be 2 through 64 bits. IDCODEs match +exactly, including revision bits. Bypass entries carry no physical identity +guarantee, and equal IDCODEs do not identify individual devices. `chain.Layout()` returns a detached copy of that expected layout without traffic. Changing the copy does not change the chain. A nil or uninitialized @@ -90,31 +88,31 @@ defer cancel() return chain.Release(cleanupCtx) // Retain chain and wire if this fails. ``` -Connect resets the chain, validates reset-register observations, measures -the total IR length, then checks the low `01` capture bits at each supplied -IR boundary. It leaves the chain in BYPASS/Idle. It uses the supported maximum +Connect resets the chain, validates reset-register observations, measures the +total IR length, then checks the low `01` capture bits at each supplied IR +boundary. It leaves the chain in BYPASS/Idle. It uses the supported maximum total IR length (65,536 bits) as its measurement bound, so validation clocks -more than 131,072 cycles even on a short chain. Give its context enough time -for that work at the selected adapter clock. +more than 131,072 cycles even on a short chain. Give its context enough time for +that work at the selected adapter clock. -Coincidental `01` bits can satisfy more than one proposed set of IR -boundaries. Obtain individual IR lengths from the device specification, not -from IDCODE enumeration. Connect -does not enable hidden TAPs or perform board-specific configuration. +Coincidental `01` bits can satisfy more than one proposed set of IR boundaries. +Obtain individual IR lengths from the device specification, not from IDCODE +enumeration. Connect does not enable hidden TAPs or perform board-specific +configuration. -Release parks the chain in BYPASS/Idle, without restoring inherited -instructions or closing the wire. After lost state it validates the same -layout before attempting cleanup. Retain the chain and its wire owner after -a release error so cleanup can be retried with a fresh bounded context. -Successful release is idempotent. A failed validation leaves no validated -chain; the raw connection and wire remain the caller's responsibility. +Release parks the chain in BYPASS/Idle, without restoring inherited instructions +or closing the wire. After lost state it validates the same layout before +attempting cleanup. Retain the chain and its wire owner after a release error so +cleanup can be retried with a fresh bounded context. Successful release is +idempotent. A failed validation leaves no validated chain; the raw connection +and wire remain the caller's responsibility. ## Selecting a TAP After Connect, `chain.TAP(index)` lends a zero-based position. Its ScanIR -accepts exactly enough bytes for that TAP's instruction length, with unused -high bits zero, and puts every other TAP in BYPASS. Its ScanDR adds and removes -the other TAPs' bypass bits. Both return only the selected TAP's capture. +accepts exactly enough bytes for that TAP's instruction length, with unused high +bits zero, and puts every other TAP in BYPASS. Its ScanDR adds and removes the +other TAPs' bypass bits. Both return only the selected TAP's capture. ```go tap, err := chain.TAP(0) @@ -127,28 +125,28 @@ if _, err := tap.ScanIR(ctx, []byte{0x0e}); err != nil { id, err := tap.ScanDR(ctx, make([]byte, 4), 32) ``` -This fragment reads the IDCODE instruction of an already-validated four-bit -Arm DAP. The caller still releases the chain using a fresh bounded context, -joins cleanup and operation errors, and keeps the wire's owner alive for any -cleanup retry. Do not assume that another device uses the same instruction. -`tap.Idle` supplies execution clocks without changing instructions. +This fragment reads the IDCODE instruction of an already-validated four-bit Arm +DAP. The caller still releases the chain using a fresh bounded context, joins +cleanup and operation errors, and keeps the wire's owner alive for any cleanup +retry. Do not assume that another device uses the same instruction. `tap.Idle` +supplies execution clocks without changing instructions. -A new Connect attempt or Release invalidates earlier borrowed TAPs. So does -a failed chain operation or raw traffic through the underlying Conn. -Revalidation never revives old TAP values. Selecting an instruction on a -different TAP requires selecting this TAP's instruction again before its -next data scan; `ErrInstructionChanged` reports that condition before traffic. -Keep exclusive use of the Conn and serialize all Chain and TAP calls. +A new Connect attempt or Release invalidates earlier borrowed TAPs. So does a +failed chain operation or raw traffic through the underlying Conn. Revalidation +never revives old TAP values. Selecting an instruction on a different TAP +requires selecting this TAP's instruction again before its next data scan; +`ErrInstructionChanged` reports that condition before traffic. Keep exclusive +use of the Conn and serialize all Chain and TAP calls. ## Wire transfers The wire packs the earliest TMS, TDI, and TDO bit into bit zero of byte zero. -Each call clocks exactly the requested number of cycles. A wire can advertise -a positive `MaxTransferBits` limit; the connection splits longer movements -without inserting cycles. Without that interface, calls are capped at 4,096 -bits. An invalid response length or a wire error loses TAP synchronization. -Only an explicit successful Reset permits movement afterward. Cancellation -before a wire call preserves the last confirmed state. +Each call clocks exactly the requested number of cycles. A wire can advertise a +positive `MaxTransferBits` limit; the connection splits longer movements without +inserting cycles. Without that interface, calls are capped at 4,096 bits. An +invalid response length or a wire error loses TAP synchronization. Only an +explicit successful Reset permits movement afterward. Cancellation before a wire +call preserves the last confirmed state. ## FTDI wire @@ -162,29 +160,28 @@ if err != nil { conn := jtag.New(wire) ``` -Use that connection with the explicit layout above. Release the chain with -a fresh bounded context before calling `opened.Close`; retain both values -if chain release fails. A probe activates only one protocol. Close invalidates -the borrowed wire, even if the implementation still has cleanup to retry. +Use that connection with the explicit layout above. Release the chain with a +fresh bounded context before calling `opened.Close`; retain both values if chain +release fails. A probe activates only one protocol. Close invalidates the +borrowed wire, even if the implementation still has cleanup to retry. `ftdi.Open(ctx, device, ftdi.Config{Port: ftdi.PortA, MaxClockHz: 100_000})` opens one explicitly selected USB attachment as a protocol-neutral MPSSE -channel. Opening leaves target pins as inputs; `JTAGIO` establishes JTAG -pin directions before each nonempty call. `SWDIO` establishes SWD directions. -The config selects the port and clock; the wire operation determines -directions. A non-nil `Channel` owns the attachment even when open returns an -error; close that channel and retain it if cleanup fails. A nil channel -leaves the device with the caller for cleanup. Opening does not preserve -earlier FTDI settings. - -The caller must verify standard MPSSE wiring: pin 0 TCK, pin 1 TDI, pin 2 -TDO, and pin 3 TMS. TCK/TDI/TMS are outputs and the remaining pins are inputs. -No reset pin is driven. The channel drives TMS and TDI on falling TCK edges -and samples TDO on rising edges. Both wire methods accept at most 8,192 -clocks per call; `jtag.Conn` splits longer scans. A failed exchange poisons -the channel rather than replaying clocks. Release the chain before closing -its channel. Do not mix raw SWD and JTAG traffic underneath a protocol -connection; serialize every call on the channel. +channel. Opening leaves target pins as inputs; `JTAGIO` establishes JTAG pin +directions before each nonempty call. `SWDIO` establishes SWD directions. The +config selects the port and clock; the wire operation determines directions. A +non-nil `Channel` owns the attachment even when open returns an error; close +that channel and retain it if cleanup fails. A nil channel leaves the device +with the caller for cleanup. Opening does not preserve earlier FTDI settings. + +The caller must verify standard MPSSE wiring: pin 0 TCK, pin 1 TDI, pin 2 TDO, +and pin 3 TMS. TCK/TDI/TMS are outputs and the remaining pins are inputs. No +reset pin is driven. The channel drives TMS and TDI on falling TCK edges and +samples TDO on rising edges. Both wire methods accept at most 8,192 clocks per +call; `jtag.Conn` splits longer scans. A failed exchange poisons the channel +rather than replaying clocks. Release the chain before closing its channel. Do +not mix raw SWD and JTAG traffic underneath a protocol connection; serialize +every call on the channel. On Nostalgia, the ZCU104 FT4232H (`0403:6011`, serial `01691`, port A) passed: @@ -193,23 +190,23 @@ OSTIOLE_ZCU104_JTAG_HIL=1 OSTIOLE_JTAG_SERIAL=01691 \ go test -tags integration ./ftdi -run '^TestHILFT4232H(Probe)?JTAG$' -count=1 -v ``` -At 100 kHz, reset discovery returned Arm IDCODE `0x5ba00477` and Xilinx -IDCODE `0x14730093`. The test validated the explicit IR4/IR12 layout, read -the Arm IDCODE through selected TAP 0, parked the chain in BYPASS/Idle, and -closed the channel. Both direct opening and registered discovery through -`Probe.JTAG` completed the same sequence. The board's DAP had already been -activated externally; neither path activates it or accesses DAP registers -or target memory. +At 100 kHz, reset discovery returned Arm IDCODE `0x5ba00477` and Xilinx IDCODE +`0x14730093`. The test validated the explicit IR4/IR12 layout, read the Arm +IDCODE through selected TAP 0, parked the chain in BYPASS/Idle, and closed the +channel. Both direct opening and registered discovery through `Probe.JTAG` +completed the same sequence. The board's DAP had already been activated +externally; neither path activates it or accesses DAP registers or target +memory. ## J-Link wire A J-Link probe opened through discovery or `jlink.OpenProbe` lends JTAG through the same `Probe.JTAG` call and cleanup sequence above. Direct users select it with `jlink.Open(ctx, device, jlink.WithJTAG(100_000))` or configure an existing -session with `ConfigureJTAG`. Configuration selects the advertised interface -and clock; it does not move the TAP. `JTAGIO` rejects an SWD-configured session -before sending traffic. Release the current protocol owner before switching. -See [J-Link USB, SWD, and JTAG](jlink.md) for session ownership and scan errors. +session with `ConfigureJTAG`. Configuration selects the advertised interface and +clock; it does not move the TAP. `JTAGIO` rejects an SWD-configured session +before sending traffic. Release the current protocol owner before switching. See +[J-Link USB, SWD, and JTAG](jlink.md) for session ownership and scan errors. On Nostalgia, EDU Mini V2 serial `000802011345` with firmware `J-Link EDU Mini V2 compiled Jun 25 2026 10:27:52` passed: @@ -220,15 +217,20 @@ OSTIOLE_JLINK_JTAG_HIL=1 OSTIOLE_JLINK_HIL_SERIAL=000802011345 \ ``` Both direct and registered-probe paths requested 100 kHz and used the 504-bit -scan limit. Discovery returned two `0x120034e5` TAPs, matching the -[ESP32 configuration](https://github.com/espressif/openocd-esp32/blob/master/tcl/target/esp32.cfg). -The test measured total IR length 10, validated an explicit 5+5 layout, and -read each TAP's IDCODE using instruction `0x1e` while bypassing the other. -Chain release parked both TAPs in BYPASS/Idle before USB close. The probe path -also checked that its borrowed wire was invalid after close. No target memory, -power register, halt, or physical reset operation was exercised. This evidence -covers one probe firmware and one chain, not all J-Link models. +scan limit. Discovery returned two `0x120034e5` TAPs, matching the [ESP32 +configuration][esp32-config]. The test measured total IR length 10, validated an +explicit 5+5 layout, and read each TAP's IDCODE using instruction `0x1e` while +bypassing the other. Chain release parked both TAPs in BYPASS/Idle before USB +close. The probe path also checked that its borrowed wire was invalid after +close. No target memory, power register, halt, or physical reset operation was +exercised. This evidence covers one probe firmware and one chain, not all J-Link +models. The [OpenOCD JTAG primer](https://openocd.org/doc/doxygen/html/primerjtag.html) -describes TAP state and scan mechanics. The reset sequence also appears in -the [Arm Debug Interface specification](https://documentation-service.arm.com/static/5f900a61f86e16515cdc0610). +describes TAP state and scan mechanics. The reset sequence also appears in the +[Arm Debug Interface specification][adi-spec]. + +[esp32-config]: + https://github.com/espressif/openocd-esp32/blob/master/tcl/target/esp32.cfg +[adi-spec]: + https://documentation-service.arm.com/static/5f900a61f86e16515cdc0610 diff --git a/docs/protocols/swd.md b/docs/protocols/swd.md index 71356db..b94b1d0 100644 --- a/docs/protocols/swd.md +++ b/docs/protocols/swd.md @@ -4,47 +4,47 @@ Serial Wire Debug (SWD) uses one clock and one bidirectional data signal. The packet format is small; most of the trouble is knowing who owns SWDIO on each clock and remembering that everything goes least-significant bit first. -Arm [IHI 0031H, _Arm Debug Interface Architecture Specification ADIv5.0 to -ADIv5.2_](https://developer.arm.com/documentation/ihi0031/h) is the normative -SWD specification. Use chapter B4 and section B5.2 for the protocol definition; -this note is not a substitute for them. It is limited to details which are easy -to misread and observations from hardware. It covers the point-to-point -protocol and dormant activation, not SWD protocol version 2 target selection -or multidrop. +Arm +[IHI 0031H, _Arm Debug Interface Architecture Specification ADIv5.0 to ADIv5.2_](https://developer.arm.com/documentation/ihi0031/h) +is the normative SWD specification. Use chapter B4 and section B5.2 for the +protocol definition; this note is not a substitute for them. It is limited to +details which are easy to misread and observations from hardware. It covers the +point-to-point protocol and dormant activation, not SWD protocol version 2 +target selection or multidrop. ## A transfer IHI 0031H sections B4.1 and B4.2 define the complete transfer. Every transfer begins with an eight-bit request sent by the host: -| Wire bit | Name | Value | -| ---: | --- | --- | -| 0 | Start | 1 | -| 1 | APnDP | 0 for DP, 1 for AP | -| 2 | RnW | 0 for write, 1 for read | -| 3 | A2 | Register address bit 2 | -| 4 | A3 | Register address bit 3 | -| 5 | Parity | Even parity over APnDP, RnW, A2, and A3 | -| 6 | Stop | 0 | -| 7 | Park | 1 | +| Wire bit | Name | Value | +| -------: | ------ | --------------------------------------- | +| 0 | Start | 1 | +| 1 | APnDP | 0 for DP, 1 for AP | +| 2 | RnW | 0 for write, 1 for read | +| 3 | A2 | Register address bit 2 | +| 4 | A3 | Register address bit 3 | +| 5 | Parity | Even parity over APnDP, RnW, A2, and A3 | +| 6 | Stop | 0 | +| 7 | Park | 1 | -For example, a DPIDR read is `0xa5` on the host side. It is clocked bit 0 -first, so the wire sees `1 0 1 0 0 1 0 1`. +For example, a DPIDR read is `0xa5` on the host side. It is clocked bit 0 first, +so the wire sees `1 0 1 0 0 1 0 1`. The host releases SWDIO for one turnaround clock and the target returns a -three-bit acknowledgement. The default turnaround is one clock, although -DLCR can describe another value on implementations which support it. +three-bit acknowledgement. The default turnaround is one clock, although DLCR +can describe another value on implementations which support it. -The acknowledgement notation in IHI 0031H is easy to misread. The prose calls -OK `0b001`, but Table B4-1 prints OK as `0b100` under the heading `ACK[0:2]`. -Those are the same three bits viewed in opposite ways: the numeric value is -sent least-significant bit first. +The acknowledgement notation in IHI 0031H is easy to misread. The prose calls OK +`0b001`, but Table B4-1 prints OK as `0b100` under the heading `ACK[0:2]`. Those +are the same three bits viewed in opposite ways: the numeric value is sent +least-significant bit first. | Response | Numeric value | Bits seen on SWDIO | -| --- | ---: | --- | -| OK | `0b001` | `1 0 0` | -| WAIT | `0b010` | `0 1 0` | -| FAULT | `0b100` | `0 0 1` | +| -------- | ------------: | ------------------ | +| OK | `0b001` | `1 0 0` | +| WAIT | `0b010` | `0 1 0` | +| FAULT | `0b100` | `0 0 1` | After OK, a read continues directly into 32 data bits and one parity bit from the target. The target then releases SWDIO for a turnaround clock. A write has @@ -54,9 +54,9 @@ the request. There is no second acknowledgement after write data. If its parity is bad, the DP abandons the write, records WDATAERR, and reports the sticky condition on a later request. -If the host is going to stop SWCLK after a transfer, IHI 0031H requires at -least eight idle clocks while the host drives SWDIO low. Starting the next -request immediately is also valid. +If the host is going to stop SWCLK after a transfer, IHI 0031H requires at least +eight idle clocks while the host drives SWDIO low. Starting the next request +immediately is also valid. ## WAIT, FAULT, and overrun detection @@ -64,9 +64,9 @@ WAIT means that the request was not accepted. IHI 0031H requires the host to repeat the same request; it does not license replaying some larger operation which happens to contain it. The WAIT response still includes its specified turnaround. The host may repeat the request immediately; IHI 0031H does not -require a separate retry delay. A DPIDR read, a bank-zero CTRL/STAT read, and -an ABORT write are the three exceptions which must complete without WAIT or -FAULT. A read from offset `0x04` in another DP bank may return WAIT or FAULT. +require a separate retry delay. A DPIDR read, a bank-zero CTRL/STAT read, and an +ABORT write are the three exceptions which must complete without WAIT or FAULT. +A read from offset `0x04` in another DP bank may return WAIT or FAULT. Immediate replay assumes that the host completed the WAIT response and the target is waiting for another packet header. If the wire fails while clocking @@ -80,25 +80,25 @@ protocol error, not a fourth response code. When no valid response is detected, the host leaves SWDIO undriven for at least the possible data phase before it tries a line reset. -Once the response grammar is known, a complete FAULT response after one or -more WAITs still ends at a request boundary. Return FAULT without DAPABORT; -the earlier WAITs do not make it a framing error. +Once the response grammar is known, a complete FAULT response after one or more +WAITs still ends at a request boundary. Return FAULT without DAPABORT; the +earlier WAITs do not make it a framing error. There is one important change when `CTRL/STAT.ORUNDETECT` is set. With overrun detection disabled, WAIT and FAULT end after the acknowledgement and trailing -turnaround. With it enabled, every response has a data phase, including WAIT -and FAULT. A host cannot turn on ORUNDETECT as a register-level feature and -keep using the simpler transfer grammar; it will lose alignment on the first -non-OK response. Fixed frames avoid that ambiguity by clocking the request, +turnaround. With it enabled, every response has a data phase, including WAIT and +FAULT. A host cannot turn on ORUNDETECT as a register-level feature and keep +using the simpler transfer grammar; it will lose alignment on the first non-OK +response. Fixed frames avoid that ambiguity by clocking the request, acknowledgement, data phase, turnaround, and idle clocks as one unit. With the default one-clock turnaround and eight idle clocks, either fixed -response is 54 clocks. Several complete frames can share one transport -exchange; that changes the host/probe boundary, not the SWD packet format. A -WAIT in such an exchange sets STICKYORUN, so the target abandons later requests -and returns FAULT for them. The host can clear STICKYORUN and resume at the -request which returned WAIT. If a later request instead appears to complete, -its effect is no longer safe to guess. +response is 54 clocks. Several complete frames can share one transport exchange; +that changes the host/probe boundary, not the SWD packet format. A WAIT in such +an exchange sets STICKYORUN, so the target abandons later requests and returns +FAULT for them. The host can clear STICKYORUN and resume at the request which +returned WAIT. If a later request instead appears to complete, its effect is no +longer safe to guess. In overrun mode, WAIT sets STICKYORUN and later requests can be abandoned. The host clears STICKYORUN through ABORT before retrying the exact request which @@ -114,23 +114,22 @@ WDATAERR FAULT means the old ORUNDETECT value still applies. Cancellation or retry exhaustion before one of those outcomes leaves the response grammar unknown. -A host must establish which grammar applies before it starts replaying -requests which return WAIT. CTRL/STAT reads cannot return WAIT or FAULT, but -offset `0x04` names CTRL/STAT only while `SELECT.DPBANKSEL` is zero. A host -which inherits unknown DP state cannot simply read `0x04` and trust bit zero. +A host must establish which grammar applies before it starts replaying requests +which return WAIT. CTRL/STAT reads cannot return WAIT or FAULT, but offset +`0x04` names CTRL/STAT only while `SELECT.DPBANKSEL` is zero. A host which +inherits unknown DP state cannot simply read `0x04` and trust bit zero. One workable bootstrap is to read DPIDR, clear the supported sticky conditions with ABORT, write zero to SELECT once without retrying, read RDBUFF to settle -that write, then read CTRL/STAT. -ABORT is bank-independent and cannot return WAIT or FAULT; clearing sticky -state first prevents an inherited error from faulting SELECT. The host cannot -trust the new SELECT value until later traffic shows whether the write data -took effect. DPIDR and ABORT do not settle that question because sticky state -cannot make them return FAULT. RDBUFF is bank-independent, so it can settle the -write without relying on the requested bank. If it returns FAULT with -WDATAERR, the DP might have abandoned the SELECT data and kept the previous -bank. Re-enter SWD before trying again. Only after RDBUFF returns OK is `0x04` -known to name CTRL/STAT. +that write, then read CTRL/STAT. ABORT is bank-independent and cannot return +WAIT or FAULT; clearing sticky state first prevents an inherited error from +faulting SELECT. The host cannot trust the new SELECT value until later traffic +shows whether the write data took effect. DPIDR and ABORT do not settle that +question because sticky state cannot make them return FAULT. RDBUFF is +bank-independent, so it can settle the write without relying on the requested +bank. If it returns FAULT with WDATAERR, the DP might have abandoned the SELECT +data and kept the previous bank. Re-enter SWD before trying again. Only after +RDBUFF returns OK is `0x04` known to name CTRL/STAT. A WAIT or FAULT during this bootstrap leaves the response grammar unknown: ORUNDETECT might be set, in which case the response has a data phase the host @@ -147,18 +146,17 @@ the Debug Access Port state, not just replay the eight-bit SWD request. IHI 0031H section B4.3.3 defines connection and line reset. A line reset is at least 50 clocks with SWDIO high followed by at least two idle clocks. It puts -the SWD interface into its reset state; it is not a reset of every DP -register. DPIDR is the ordinary transaction which leaves that state. +the SWD interface into its reset state; it is not a reset of every DP register. +DPIDR is the ordinary transaction which leaves that state. Line reset also resets DLCR. If ORUNDETECT was already set, the reset records STICKYORUN, so bootstrap has sticky state to clear before ordinary traffic. There is a slightly nasty qualification in section B4.3.3: detection of the -50-high sequence is guaranteed while the target is waiting for a packet -header, but is implementation-defined at other points in a transfer. If the -first DPIDR read does not answer, the specified recovery is to send the reset -sequence again. One line reset is not proof that a confused target saw it as -one. +50-high sequence is guaranteed while the target is waiting for a packet header, +but is implementation-defined at other points in a transfer. If the first DPIDR +read does not answer, the specified recovery is to send the reset sequence +again. One line reset is not proof that a confused target saw it as one. An SWJ-DP can power up in JTAG mode. The recommended JTAG-to-SWD sequence is: @@ -168,44 +166,43 @@ at least 50 high clocks at least 50 high clocks ``` -The second high run leaves SWD in line-reset state. Arm points out that the -two low idle clocks from a normal line reset are absent from the switching -figure; a host can supply idle clocks before reading DPIDR. +The second high run leaves SWD in line-reset state. Arm points out that the two +low idle clocks from a normal line reset are absent from the switching figure; a +host can supply idle clocks before reading DPIDR. -IHI 0031H section B5.3 defines dormant operation. A dormant interface -ignores ordinary SWD requests until it receives the selection alert and -activation code. Ostiole first tries the JTAG-to-SWD sequence above. If the -initial DPIDR read returns an invalid ACK and the host completes the -undriven data phase and idle clocks, it tries this sequence once: +IHI 0031H section B5.3 defines dormant operation. A dormant interface ignores +ordinary SWD requests until it receives the selection alert and activation code. +Ostiole first tries the JTAG-to-SWD sequence above. If the initial DPIDR read +returns an invalid ACK and the host completes the undriven data phase and idle +clocks, it tries this sequence once: 1. Nine high clocks and the 31-bit JTAG-to-dormant code `0x33bbbbba`, least-significant bit first. 2. Eight high clocks and the 128-bit selection alert - `0x19bc0ea2e3ddafe986852d956209f392`, least-significant bit first across - the whole value: byte `0x92` goes first. -3. Four low clocks, the eight-bit SWD activation code `0x1a` - least-significant bit first, 56 high clocks for line reset, and eight low - idle clocks. + `0x19bc0ea2e3ddafe986852d956209f392`, least-significant bit first across the + whole value: byte `0x92` goes first. +3. Four low clocks, the eight-bit SWD activation code `0x1a` least-significant + bit first, 56 high clocks for line reset, and eight low idle clocks. 4. Another DPIDR read, followed by the ordinary bootstrap only if identity validation succeeds. -The three activation exchanges use 40, 136, and 76 clocks, so the fallback -needs no larger wire transfer than existing JTAG-to-SWD entry. A parity, -WAIT, FAULT, or transport error does not trigger this fallback. Neither does -an invalid ACK whose trailing clocks failed. Each bootstrap has at most two -DPIDR attempts; failed Connect may also run a separate bootstrap during -bounded cleanup. Ordinary register calls still make one attempt. +The three activation exchanges use 40, 136, and 76 clocks, so the fallback needs +no larger wire transfer than existing JTAG-to-SWD entry. A parity, WAIT, FAULT, +or transport error does not trigger this fallback. Neither does an invalid ACK +whose trailing clocks failed. Each bootstrap has at most two DPIDR attempts; +failed Connect may also run a separate bootstrap during bounded cleanup. +Ordinary register calls still make one attempt. Release uses the same activation fallback when framing repair is needed and checks the established identity before restoring owned state. It restores -ORUNDETECT but leaves SWD selected; it does not return the interface to -dormant mode. Activation does not halt or reset the processor, request -system power, or add ADIv6 AP addressing. +ORUNDETECT but leaves SWD selected; it does not return the interface to dormant +mode. Activation does not halt or reset the processor, request system power, or +add ADIv6 AP addressing. Multidrop SWD has another boundary worth stating plainly: there is no generic way to ask an unselected multidrop bus which target IDs are present. The host -must already know which IDs to try. That is a protocol limitation, not a -missing discovery trick. +must already know which IDs to try. That is a protocol limitation, not a missing +discovery trick. ## Bench note, 2026-08-09 @@ -213,16 +210,16 @@ I ran the existing SWD and DAP integration tests on macOS 26.5.2 (arm64), through an FT232H (`0403:6014`) using MPSSE port A at a requested 400 kHz. The adapter was wired as follows: -| FT232H signal | Target signal | -| --- | --- | -| D0 | SWCLK | -| D1 through a 1 kΩ series resistor | SWDIO | -| D2 | SWDIO | -| GND | GND | +| FT232H signal | Target signal | +| --------------------------------- | ------------- | +| D0 | SWCLK | +| D1 through a 1 kΩ series resistor | SWDIO | +| D2 | SWDIO | +| GND | GND | -D1 drove SWDIO and D2 sampled the same line. The target board and SoC names -were not recorded. Its debug fingerprint identifies an ADIv5 MEM-AP and an -otherwise unidentified Cortex-M4: +D1 drove SWDIO and D2 sampled the same line. The target board and SoC names were +not recorded. Its debug fingerprint identifies an ADIv5 MEM-AP and an otherwise +unidentified Cortex-M4: ```text DPIDR = 0x2ba01477 @@ -294,9 +291,9 @@ was `0x23000040`. The transaction completed `DebugPort.Release`, and the test closed the FTDI channel. The target returned no WAIT during this run. Packed WAIT recovery remains simulator evidence. -Measuring turnaround requires a capture of SWCLK, SWDIO, and the FTDI -direction pin. A real WAIT still requires a target which can be made to stall; -the ordinary transfers here did not produce one. +Measuring turnaround requires a capture of SWCLK, SWDIO, and the FTDI direction +pin. A real WAIT still requires a target which can be made to stall; the +ordinary transfers here did not produce one. ## Linux explicit-transfer regression, 2026-08-25 @@ -314,20 +311,20 @@ OSTIOLE_FTDI_HIL=1 OSTIOLE_FTDI_HIL_ENUMERATIONS=1000 \ ``` One session completed all 1,000 AP enumerations with 1,024,022 physical OK -acknowledgements, no WAIT, FAULT, or invalid acknowledgement, and one SWD -entry. It used 32,033 physical SWDIO calls. The experiment exercises Linux -usbfs submission, completion notification and reaping, endpoint cancellation, -and release on this bench; it is not a USB or SWD waveform capture and does -not establish behavior for another host controller or FTDI product. +acknowledgements, no WAIT, FAULT, or invalid acknowledgement, and one SWD entry. +It used 32,033 physical SWDIO calls. The experiment exercises Linux usbfs +submission, completion notification and reaping, endpoint cancellation, and +release on this bench; it is not a USB or SWD waveform capture and does not +establish behavior for another host controller or FTDI product. ## RP2350 dormant activation bench On macOS, a J-Link EDU Mini V2 (serial `000802011345`, firmware -`J-Link EDU Mini V2 compiled Jun 25 2026 10:27:52`) connected to an RP2350 -over SWD at 100 kHz. Before each of two fresh Ostiole sessions, OpenOCD 0.12.0 connected -to the debug port and shut down; its debug log showed SWD-to-dormant -followed by dormant-to-JTAG on shutdown. The preparation used no CPU target -or reset command: +`J-Link EDU Mini V2 compiled Jun 25 2026 10:27:52`) connected to an RP2350 over +SWD at 100 kHz. Before each of two fresh Ostiole sessions, OpenOCD 0.12.0 +connected to the debug port and shut down; its debug log showed SWD-to-dormant +followed by dormant-to-JTAG on shutdown. The preparation used no CPU target or +reset command: ```sh openocd -c 'adapter driver jlink' -c 'adapter serial 000802011345' \ @@ -349,6 +346,6 @@ reproduced that invalid ACK after JTAG-to-SWD and read the correct identity after its dormant fallback. The runs did not independently measure the inherited ORUNDETECT value after -release, exercise an Ostiole ADIv6 AP, or read target memory. No processor -halt or reset was requested. OpenOCD performed its own debug-port -initialization; the bench was not power-cycled to test startup state. +release, exercise an Ostiole ADIv6 AP, or read target memory. No processor halt +or reset was requested. OpenOCD performed its own debug-port initialization; the +bench was not power-cycled to test startup state. diff --git a/examples/README.md b/examples/README.md index 5b6969b..8aec482 100644 --- a/examples/README.md +++ b/examples/README.md @@ -9,25 +9,26 @@ operation visible and are primarily useful for learning or hardware bring-up. identity, CoreSight discovery, and Cortex-M0 halt/resume. `advanced/` is reserved for composed workflows such as loading ELF payloads, -programming firmware or FPGA bitstreams, and extracting data through a -target's flash controller. +programming firmware or FPGA bitstreams, and extracting data through a target's +flash controller. -These categories describe the intended organization; they do not imply that -an example has been implemented. +These categories describe the intended organization; they do not imply that an +example has been implemented. ## Available examples -- [`trivial/swd-dpidr`](trivial/swd-dpidr) reads the identification register - of one SWD debug port through an explicitly selected FTDI attachment. +- [`trivial/swd-dpidr`](trivial/swd-dpidr) reads the identification register of + one SWD debug port through an explicitly selected FTDI attachment. - [`simple/ap-id`](simple/ap-id) reports the debug-port identity and one explicitly selected access-port identity. - [`simple/cortexm-info`](simple/cortexm-info) reads a Cortex-M processor identity through an explicitly selected memory access port. - [`simple/arm-info`](simple/arm-info) discovers an opt-in probe provider and reads DPIDR, AP IDR, and Cortex-M identity through one Arm debug owner. -- [`simple/coresight-info`](simple/coresight-info) reads the advertised debug entry - of a selected MEM-AP, or a known component page, through a managed SWD connection. - Add `-walk` for bounded ROM traversal with partial-result reporting. +- [`simple/coresight-info`](simple/coresight-info) reads the advertised debug + entry of a selected MEM-AP, or a known component page, through a managed SWD + connection. Add `-walk` for bounded ROM traversal with partial-result + reporting. - [`simple/cortexm-control`](simple/cortexm-control) enables Cortex-M0 halting debug, halts and resumes the processor, then restores debug control. It requires `-allow-control`; see [Cortex-M control](../docs/cortexm.md) for @@ -36,9 +37,11 @@ an example has been implemented. For ADIv6 SW-DP targets, `coresight-info -debug-space -walk` inspects the DP's advertised discovery tree. Use `-ap-base ADDRESS` instead of `-ap INDEX` to inspect memory through one ADIv6 MEM-AP. The same probe selection and cleanup -rules apply. See the [RP2350 procedure](../docs/coresight.md#adiv6-and-the-rp2350). +rules apply. See the [RP2350 procedure][rp2350-procedure]. -The `arm-info`, `coresight-info`, and `cortexm-control` examples accept -`-clock` in Hz and default to 1 MHz. This also suits the micro:bit -nRF51: its debug interface needs at least 125 kHz during startup. See the +The `arm-info`, `coresight-info`, and `cortexm-control` examples accept `-clock` +in Hz and default to 1 MHz. This also suits the micro:bit nRF51: its debug +interface needs at least 125 kHz during startup. See the [startup evidence](../docs/protocols/cmsisdap.md#nrf51-startup-clock). + +[rp2350-procedure]: ../docs/coresight.md#adiv6-and-the-rp2350 diff --git a/ftdi/AGENTS.md b/ftdi/AGENTS.md index 7ba0149..8409762 100644 --- a/ftdi/AGENTS.md +++ b/ftdi/AGENTS.md @@ -7,13 +7,12 @@ FTDI adapter package. - Keep raw adapter behavior here while relying on the shared `usb` package for host transport and ownership. -- Check interface ownership, adapter configuration, deadlines, transfer - lengths, close ordering, and restoration across every success and failure - path. -- Do not duplicate host USB mechanisms or move FTDI-specific protocol state - into commands, examples, or tests. +- Check interface ownership, adapter configuration, deadlines, transfer lengths, + close ordering, and restoration across every success and failure path. +- Do not duplicate host USB mechanisms or move FTDI-specific protocol state into + commands, examples, or tests. - For native C or header changes, require format-clean and warning-clean - compilation under the deployment target and verify safe ownership across - the Go and C boundary. + compilation under the deployment target and verify safe ownership across the + Go and C boundary. - Require platform-independent behavioral coverage where possible and matching platform evidence for host-specific behavior. diff --git a/internal/ci/checkpr/markdown.go b/internal/ci/checkpr/markdown.go index bc3960e..7ba745f 100644 --- a/internal/ci/checkpr/markdown.go +++ b/internal/ci/checkpr/markdown.go @@ -9,10 +9,13 @@ import ( "regexp" "strings" "unicode" + + "github.com/yuin/goldmark" + "github.com/yuin/goldmark/parser" + "github.com/yuin/goldmark/text" ) var inlineLink = regexp.MustCompile(`!?\[[^]]*\]\(([^)]+)\)`) -var referenceLink = regexp.MustCompile(`^\s*\[[^]]+\]:\s*(\S+)`) type markdownTree struct { repo string @@ -141,9 +144,11 @@ func markdownLinks(content string) []string { for _, match := range inlineLink.FindAllStringSubmatch(line, -1) { links = append(links, linkDestination(match[1])) } - if match := referenceLink.FindStringSubmatch(line); match != nil { - links = append(links, linkDestination(match[1])) - } + } + context := parser.NewContext() + goldmark.DefaultParser().Parse(text.NewReader([]byte(content)), parser.WithContext(context)) + for _, reference := range context.References() { + links = append(links, string(reference.Destination())) } return links } diff --git a/internal/ci/checkpr/markdown_test.go b/internal/ci/checkpr/markdown_test.go index 3bec61c..6ed26bb 100644 --- a/internal/ci/checkpr/markdown_test.go +++ b/internal/ci/checkpr/markdown_test.go @@ -70,3 +70,40 @@ func TestCheckMarkdownIgnoresCodeExamples(t *testing.T) { t.Fatalf("checkMarkdown() = %#v, want no findings", findings) } } + +func TestCheckMarkdownReferenceDestinations(t *testing.T) { + for _, test := range []struct { + name string + definition string + wantCode string + }{ + {"same line", "[guide]: docs/guide.md#known-heading", ""}, + {"wrapped", "[guide]:\n docs/guide.md#known-heading", ""}, + {"missing path", "[guide]:\n docs/missing.md", "markdown-link"}, + {"missing anchor", "[guide]:\n docs/guide.md#missing-heading", "markdown-anchor"}, + {"angle destination", "[guide]:\n ", "markdown-link"}, + {"external", "[guide]:\n https://example.com/not-checked", ""}, + {"fenced example", "```markdown\n[guide]:\n missing.md\n```", ""}, + {"indented example", " [guide]:\n missing.md", ""}, + {"blank line", "[guide]:\n\n missing.md", ""}, + } { + t.Run(test.name, func(t *testing.T) { + repo := newRepository(t) + head := commitPaths(t, repo, "Document reference links.\n", map[string]string{ + "README.md": "[Guide][guide]\n\n" + test.definition + "\n", + "docs/guide.md": "# Known heading\n", + }) + findings, err := checkMarkdown(repo, head) + if err != nil { + t.Fatalf("checkMarkdown() error = %v", err) + } + if test.wantCode == "" { + if len(findings) != 0 { + t.Fatalf("checkMarkdown() = %#v, want no findings", findings) + } + } else if len(findings) != 1 || !hasFinding(findings, errorLevel, test.wantCode) { + t.Fatalf("checkMarkdown() = %#v, want one %s error", findings, test.wantCode) + } + }) + } +} diff --git a/target/cortexm/testdata/counter/README.md b/target/cortexm/testdata/counter/README.md index 54f93e2..b03f206 100644 --- a/target/cortexm/testdata/counter/README.md +++ b/target/cortexm/testdata/counter/README.md @@ -1,10 +1,10 @@ # Cortex-M0 counter firmware -This micro:bit v1 bench program increments the 32-bit word at `0x20000000` -in a CPU loop. It disables interrupts and uses no peripheral or DMA engine. -The vector table starts at flash address zero and uses the top of the first -16 KiB of RAM for the initial stack pointer. Every unexpected exception loops -without changing the counter. +This micro:bit v1 bench program increments the 32-bit word at `0x20000000` in a +CPU loop. It disables interrupts and uses no peripheral or DMA engine. The +vector table starts at flash address zero and uses the top of the first 16 KiB +of RAM for the initial stack pointer. Every unexpected exception loops without +changing the counter. Build from the repository root with Clang's Arm assembler, LLD, and GNU Arm objcopy: @@ -17,15 +17,15 @@ ld.lld -T target/cortexm/testdata/counter/counter.ld \ arm-none-eabi-objcopy -O ihex /tmp/ostiole-counter.elf /tmp/ostiole-counter.hex ``` -Loading this image replaces the target program and resets the processor. It -does not update the DAPLink interface firmware. Select the exact probe when -using an external programmer. Programming is bench preparation, separate from -the Ostiole [control test](../../../../docs/cortexm.md#hardware-procedure). +Loading this image replaces the target program and resets the processor. It does +not update the DAPLink interface firmware. Select the exact probe when using an +external programmer. Programming is bench preparation, separate from the Ostiole +[control test](../../../../docs/cortexm.md#hardware-procedure). -For the selected micro:bit, use OpenOCD's CMSIS-DAP v2 transport and nRF51 -flash driver at 1 MHz. The nRF51 requires at least 125 kHz when entering -debug interface mode after power-on; 100 kHz can work after another debugger -has already activated it. See the +For the selected micro:bit, use OpenOCD's CMSIS-DAP v2 transport and nRF51 flash +driver at 1 MHz. The nRF51 requires at least 125 kHz when entering debug +interface mode after power-on; 100 kHz can work after another debugger has +already activated it. See the [startup evidence](../../../../docs/protocols/cmsisdap.md#nrf51-startup-clock). ```sh