blob: 2dadff85be51ed76f2f1e64aac3b54467e9fab56 [file] [view]
# In-Process SPDM Requester — Implementation Plan
Companion to [DESIGN_IN_PROCESS_REQUESTER.md](DESIGN_IN_PROCESS_REQUESTER.md).
Breaks the design into landable phases, each independently compileable and
testable so nothing goes dark on `main` between merges.
## Status
| Phase | Status | Notes |
|---|---|---|
| 0 — baseline build + assumption check | ✅ done | Required two pre-Phase-1 fixes (see Phase 0 Resolution Log) |
| 1 — feature flag restructuring | ✅ done | `direct-client` is now the shared capability; role features `in-process-responder` / `in-process-requester` pick the FSM. Mutual-exclusion `compile_error!` in `main.rs`. |
| 2 — compileable requester stub | ✅ done | Forced creation of a second system_image package `//target/ast1060-evb/mctp-requester` so the standalone `rust_app` target actually compiles (standalone `rust_app` hits a `pw_kernel/userspace` platform-constraint error without a system image). |
| 3 — FSM happy path | ✅ done | Eight-state VCA FSM, one step per polling-loop iteration. |
| 4 — safety net + counters | ✅ done | `AWAIT_STEP_BUDGET = 10_000`, `await_try!` macro, counters `req_send_ok` / `req_recv_ok` / `req_recv_pending`. |
| 5 — Bazel target + system image | ✅ done (merged into Phase 2) | See Phase 2 note above. |
| 6 — two-board integration test | ⏸ blocked | No on-target hardware available in this environment. See IMPLEMENTATION_PLAN.md §A for measurements to collect when hardware is accessible. |
| 6a — QEMU integration test (virt-ast1060-evb) | ✅ done | QEMU patched with ASPEED I2C emulation; `//target/virt-ast1060-evb/mctp-requester:mctp_requester_test` added. No two-board setup needed. |
| 7 — docs + follow-up TODOs | ✅ done | [INITIALIZATION.md](INITIALIZATION.md) updated with requester glossary + setup steps; follow-up design refs annotated at the FSM in `main.rs`. |
Build verification at end of Phase 7:
```
bazel build --config=k_ast1060_evb //target/ast1060-evb/mctp:mctp # responder (hw) — clean
bazel build --config=k_ast1060_evb //target/ast1060-evb/mctp-requester:mctp_requester # requester (hw) — clean
bazel build --config=k_virt_ast1060_evb //target/virt-ast1060-evb/mctp-requester:mctp_requester # requester (QEMU) — clean
bazel test --config=k_virt_ast1060_evb //target/virt-ast1060-evb/mctp-requester:mctp_requester_test # requester QEMU test
```
Legend:
- **Goal** — what this phase must achieve.
- **Deliverables** — concrete artifacts produced.
- **Files** — edited / added in this phase.
- **Exit criteria** — what proves the phase is done.
- **Risks / rollback** — what can go wrong; how to back out.
---
## How To Build Which Role
Role (requester vs. responder) is selected at **build time**, not at
runtime. Same `src/main.rs` source, two binaries, chosen by which Bazel
**system-image** target you build. Standalone `rust_app` targets do not
build in isolation — `pw_kernel/userspace` requires a `target_codegen` →
`kernel_config` chain that only exists inside a `system_image`.
**Developer commands:**
```bash
# Responder image (default, hardware)
bazel build --config=k_ast1060_evb //target/ast1060-evb/mctp:mctp
# Requester image (hardware)
bazel build --config=k_ast1060_evb //target/ast1060-evb/mctp-requester:mctp_requester
# Requester image (QEMU — requires patched QEMU with ASPEED I2C emulation)
bazel build --config=k_virt_ast1060_evb //target/virt-ast1060-evb/mctp-requester:mctp_requester
bazel test --config=k_virt_ast1060_evb //target/virt-ast1060-evb/mctp-requester:mctp_requester_test
```
Running any test or integration probe uses the same prefix
(`bazel test --config=k_ast1060_evb <target>`).
**Selection chain** — each layer picks the next:
```
System image → rust_app label → Cargo features → #[cfg] in main.rs
──────────────────────────────────────────────── ───────────────────────────────────────────── ─────────────────────────────────────────────────────── ─────────────────────────
//target/ast1060-evb/mctp:mctp → //services/mctp/server:mctp_server → ["i2c-polling", "direct-client", "in-process-responder"] → responder setup + step
//target/ast1060-evb/mctp-requester:mctp_requester → //services/mctp/server:mctp_server_requester → ["i2c-polling", "direct-client", "in-process-requester"] → requester setup + FSM
```
Each `rust_app` has its own `codegen_crate_name` — `app_mctp_server` for
the responder, `app_mctp_server_requester` for the requester — because two
`rust_app` rules in the same Bazel package cannot share the codegen name.
`src/main.rs` picks the right one via a cfg-gated `use`.
**Guards:**
- Both roles at once → rejected by `compile_error!` at the top of `main.rs`.
- Neither role + `i2c-polling` → polling loop compiles with no SPDM in-process
(still valid; drains I2C only).
- No `i2c-polling` → the notification (WG + IRQ) loop compiles; role features
are ignored because `in-process-*` code is also gated on `i2c-polling`.
- `direct-client` is listed explicitly in both `crate_features` sets;
Bazel's `rust_binary` does not resolve Cargo-level feature implications
(`in-process-* = ["direct-client"]`), so the transitive dep must be
named directly.
**What flashes:** each system image binds one of the two `rust_app` labels
via its `apps = [...]` list, together with `i2c_server`. The two images
are mutually exclusive — flash one or the other, not both.
**Direct `cargo build` is not a supported path** for the binary — the
package sets `autobins = false` and `main.rs` is a Pigweed binary built
only through Bazel. The Cargo features still matter because Bazel passes
them through; they're just never consumed by a bare `cargo` invocation
of `main.rs`.
---
## Phase 0 — Baseline & Assumption Verification
**Goal**: confirm the starting source tree builds clean before any new
code is written. Source-level only — no on-target execution is required
or assumed at this stage.
**Deliverables**:
- Successful Bazel build of
`//target/ast1060-evb/mctp:mctp` (responder system image) at current
`ocp-emea-demo-spdm-spdm-req-resp-anthony-i2c-polling` HEAD.
- Source-level re-read of the existing polling loop at
[src/main.rs:322-445](src/main.rs#L322-L445) to confirm the interleaving
assumptions in design §6.5 still match the code (in particular, that
`wait_for_messages(..., None)` paired with the `e.is_timeout()` arm at
[src/main.rs:390](src/main.rs#L390) matches constraint §6.5.3).
- **Skipped:** standalone build of
`//target/ast1060-evb/spdm-req-resp-test:spdm_requester_app`. The app is
marked `# DISABLED` in the sibling `system_image` and fails platform
analysis when built alone. The reference constants in
[spdm_requester.rs](../../target/ast1060-evb/spdm-req-resp-test/spdm_requester.rs)
are consumed as source, not as a binary, so skipping this build does
not block later phases.
**Files**: none modified **initially**. See Phase 0 Resolution Log below.
**Exit criteria**: the responder system image builds clean; the §6.5
cross-check is recorded as "verified against HEAD" in the commit
message of Phase 1.
**Risks / rollback**: none — read-only phase by default; any unblocker
applied here gets its own commit (see log below).
### Phase 0 Resolution Log
- **Blocker discovered**: at branch HEAD, commit `023e75e "Update mctp-lib
(to hotfix branch)"` changed `mctp_lib::Router::inbound` to return
`Result<Option<AppCookie>, MctpError>`, but the
[services/mctp/server/src/server.rs:267-271](src/server.rs#L267-L271)
wrapper `Server::inbound` still declared `Result<(), MctpError>`. The
error surfaces as E0308 and fails *every* Bazel target that depends on
`mctp_server_lib` (responder system image, req-resp test image,
etc.).
- **Verification that the break is isolated**: a worktree at
`d4719a0 "Chasing down an MCTP packet issue."` (the commit immediately
preceding the mctp-lib hotfix) built `//target/ast1060-evb/mctp:mctp`
clean. So the break is entirely within `023e75e`.
- **Resolution — Fix A** (discard the new cookie):
```rust
pub fn inbound(&mut self, pkt: &[u8]) -> Result<(), MctpError> {
self.stack
.inbound(pkt)
.map(|_| ())
.map_err(mctp_error_to_server_error)
}
```
Chosen over Fix B (propagate `Option<AppCookie>` to callers) because:
no caller reads the return value today, the propagation case is cheap
to add later if Shape-B from design §9.1 ever lands, and keeping the
unblocker orthogonal to the requester work eases `git blame`.
Landed as its own small commit before any Phase 1 work.
- **Second blocker** (surfaced after Fix A unblocked rustc past the first
error): the same mctp-lib hotfix changed
`MctpI2cReceiver::decode`'s return type from `(&[u8], u8)` to
`(&[u8], MctpI2cHeader)` — a struct
`{ dest: u8, source: u8, byte_count: usize }` — but
[src/main.rs:350-361](src/main.rs#L350-L361) still treated the second
tuple element as a bare `u8` source address. `as u32` on a struct
triggers E0605.
- **Resolution — Fix A′** (match the new type, minimum diff): rename
the binding from `src_addr` to `hdr` and read the source address
field explicitly: `hdr.source as u32`. Landed in the same
"pre-Phase-1 unblocker" commit as Fix A.
**Deferred to on-target bringup**: measuring actual idle-poll rate,
`wait_for_messages` latency, and worst-case fragment count is out of
scope for Phase 0. `AWAIT_STEP_BUDGET` will ship with a generous
compile-time default (§6.4) and be tuned when a runtime becomes
available — see Appendix §A.
---
## Phase 1 — Feature Flag Restructuring
**Goal**: rename the responder role feature and add the requester role
feature, without changing behavior. Pure refactor.
**Deliverables**:
- `services/mctp/server/Cargo.toml`:
- Add `in-process-responder = ["direct-client"]`.
- Add `in-process-requester = ["direct-client"]`.
- Update `default` to `["i2c-polling", "in-process-responder"]`.
- Keep `direct-client` as-is (library-exposed capability).
- `services/mctp/server/src/main.rs`:
- Add at top:
```rust
#[cfg(all(feature = "in-process-requester", feature = "in-process-responder"))]
compile_error!("features `in-process-requester` and `in-process-responder` are mutually exclusive");
```
- Replace every `#[cfg(feature = "direct-client")]` inside the polling
loop and its setup block with
`#[cfg(feature = "in-process-responder")]`.
- Leave the `mod direct_client;` export and `DirectMctpClient` untouched
— those belong to the `direct-client` feature, which still exists.
- `services/mctp/server/BUILD.bazel`:
- `mctp_server` target: replace
`crate_features = ["i2c-polling", "direct-client"]` with
`["i2c-polling", "in-process-responder"]`.
- Keep `rust_library` `mctp_server_lib` with `crate_features = ["direct-client"]`.
**Files**: `Cargo.toml`, `src/main.rs`, `BUILD.bazel`.
**Exit criteria**:
- `bazel build --config=k_ast1060_evb //target/ast1060-evb/mctp:mctp`
succeeds (the standalone `rust_app` target cannot be built in isolation
because `pw_kernel/userspace` needs a system-image codegen context).
- Built binary is byte-identical in behavior to Phase 0 baseline (responder
loop still runs; log lines at startup unchanged).
- Attempting to build with both features enabled emits the
`compile_error!`.
**Risks / rollback**:
- *Risk*: downstream Bazel targets still set `direct-client` directly and
silently lose the responder. *Mitigation*: grep the repo for
`direct-client` in `crate_features`; the only consumer is
`services/mctp/server/BUILD.bazel`.
- *Rollback*: revert the single refactor commit; no logic changed.
---
## Phase 2 — Compileable Requester Stub
**Goal**: add the requester setup skeleton behind
`in-process-requester` as a no-op Phase 2 step. Proves feature gating,
imports, and the init sequence compile — no FSM logic yet.
**Deliverables**:
- `services/mctp/server/src/main.rs`:
- Add constant `const REMOTE_RESPONDER_EID: u8 = 42;` (module-scope, not
cfg-gated — a single `#[allow(dead_code)]` keeps it clean when the
feature is off).
- Add imports gated on
`#[cfg(all(feature = "i2c-polling", feature = "in-process-requester"))]`:
mock platform types, `MctpSpdmTransport`, `SpdmContext`, VCA
`generate_*` functions, `DemoPeerCertStore`.
- Inside `mctp_loop()` under the same cfg, add the full init sequence
from §7 of the design doc:
1. `DirectMctpClient::new(&server)`
2. `MctpSpdmTransport::new_requester(client, REMOTE_RESPONDER_EID)`
3. `transport.init_sequence()` with error log + return
4. Mock platform instances (cert_store, hash×3, rng, evidence,
peer_cert_store)
5. `CapabilityFlags` / `DeviceCapabilities` matching
[spdm_requester.rs](../../target/ast1060-evb/spdm-req-resp-test/spdm_requester.rs)
6. `LocalDeviceAlgorithms` (factor into a helper to avoid duplicating
the responder's inline block)
7. `SpdmContext::new(..., Some(&mut peer_cert_store), ...)`
8. `MessageBuf` on stack buffer
9. `let mut req_state = ReqState::SendVersion;` — **but** Phase 2
step body is still just
`#[allow(unused_variables)] let _ = &mut req_state;` with a log
`"requester FSM stub: state=..."` every 0xfff loops.
- Add the `ReqState` enum (full variant set — §6.1) even though only
one variant is used, so Phase 3 just fills in match arms.
**Files**: `src/main.rs`.
**Exit criteria**:
- `bazel build --config=k_ast1060_evb //target/ast1060-evb/mctp-requester:mctp_requester`
succeeds. (Phase 2 verification required pulling Phase 5's
system-image package forward — a standalone `rust_app` target is not
buildable on its own in this tree; see `mctp-requester/` for the
system image created here.)
- The responder build is unaffected.
- Running the requester build on-target logs the init sequence, then
idle-poll messages with the stub state — no SPDM traffic emitted yet.
**Risks / rollback**:
- *Risk*: Rust borrow checker rejects `SpdmContext::new` arg layout
because `transport` and `server` share a lifetime. *Mitigation*: mirror
the responder's exact let-binding order, which is already known to
compile.
- *Risk*: `DemoPeerCertStore` import path differs from responder
(responder uses `None`). *Mitigation*: path already resolved in
`spdm_requester.rs` — copy.
- *Rollback*: revert commit; responder build unaffected.
---
## Phase 3 — FSM Implementation (Happy Path)
**Goal**: wire the six Send/Await states so a full VCA flow completes on
the wire.
**Deliverables**:
- Replace the Phase-2 stub body with the match from §6.2 of the design
doc. Each Send state:
1. `msg_buf.reset()`
2. `generate_<step>(...)`
3. `ctx.requester_send_request(&mut msg_buf, REMOTE_RESPONDER_EID)`
4. Advance to matching `Await*` state.
- Each Await state:
1. `msg_buf.reset()`
2. `ctx.requester_process_message(&mut msg_buf)`
3. `Ok(_)` → advance; `Err(_)` → stay (counted, not yet budgeted).
- On entry to `ReqState::Done`, log once:
`"SPDM VCA completed: version/caps/algs OK"`.
- `Done` and `Failed` states become no-ops in Phase 2 (Phase 1 keeps
draining I2C unconditionally, so late inbound traffic is still handled).
**Files**: `src/main.rs`.
**Exit criteria**:
- End-to-end test on AST1060-EVB against the existing
`spdm_responder` app running on a second device (or the same device
with the responder built separately) produces three successful
request/response pairs.
- Protocol-analyzer capture (or I2C frame logs already present) shows
GET_VERSION / GET_CAPABILITIES / NEGOTIATE_ALGORITHMS emitted in order
with matching response consumption.
- `spdm_err` / equivalent pending-counter stays bounded (not monotonically
growing past expected poll-while-waiting count).
**Risks / rollback**:
- *Risk*: a protocol error wedges the FSM in an Await state forever
(design §6.3, option 1 not yet implemented). *Mitigation*: Phase 4
adds the step budget; until then, a power-cycle is acceptable for
bringup.
- *Risk*: `include_supported_algorithms = false` / other capability
fields diverge between the in-process requester and the responder's
expectations. *Mitigation*: copy verbatim from `spdm_requester.rs`;
already known to interoperate with `spdm_responder.rs`.
- *Rollback*: revert to Phase 2 stub.
---
## Phase 4 — Safety Net & Observability
**Goal**: bound failure latency and give on-target debugging the same
counter set as the responder.
**Deliverables**:
- `AWAIT_STEP_BUDGET` const + `await_steps` counter per design §6.4,
reset on every state transition, transitions FSM to `Failed` on
exhaustion with an error log naming the state that timed out.
- Counters from design §8, each `u32` wrapping, logged on first event and
every 256th:
- `req_send_ok`, `req_send_err`, `req_recv_ok`, `req_recv_pending`,
`req_recv_err_terminal`.
- State-transition log (one line per transition, not rate-limited — low
volume).
- `Done` transition log augmented with
`"completed in {N} steps, ~{idle_polls} idle polls"`.
**Files**: `src/main.rs`.
**Exit criteria**:
- Forcing a fault (e.g. responder offline) causes `Failed` transition
within the budget and emits the named-state error; device does not
hang.
- Counters visible in logs match observed behavior (send counts == 3 on
success path; pending spikes and settles at each Await entry).
**Risks / rollback**:
- *Risk*: step budget too tight → spurious failures under a legitimately
multi-fragment response (§6.5.2). *Mitigation*: ship with a deliberately
generous default (`AWAIT_STEP_BUDGET = 10_000`) and defer tightening
until on-target measurements exist (see Appendix §A). Erring high
costs only wall-clock on genuine failures; erring low wedges the
happy path.
- *Rollback*: revert budget block, keep counters (useful regardless).
---
## Phase 5 — Bazel Target & System Image
**Goal**: make the requester role selectable as a first-class Bazel build
without touching the responder's target.
**Deliverables**:
- New target in `services/mctp/server/BUILD.bazel`:
```starlark
rust_app(
name = "mctp_server_requester",
codegen_crate_name = "app_mctp_server",
srcs = ["src/main.rs"],
crate_features = ["i2c-polling", "in-process-requester"],
edition = "2024",
system_config = "//target/ast1060-evb/mctp:system_config",
tags = ["kernel"],
visibility = ["//visibility:public"],
deps = [ # identical to mctp_server
":mctp_server_lib",
"//services/i2c/api:i2c_api",
"//services/i2c/client:i2c_client",
"//services/mctp/api:mctp_api",
"//services/mctp/transport-i2c:mctp_transport_i2c",
"//services/spdm/transport-mctp:spdm_transport_mctp",
"//target/ast1060-evb/spdm-req-resp-test:mock_platform",
"@rust_crates//:mctp",
"@rust_crates//:mctp-lib",
"@pigweed//pw_kernel/syscall:syscall_user",
"@pigweed//pw_kernel/userspace",
"@pigweed//pw_log/rust:pw_log",
"@pigweed//pw_status/rust:pw_status",
"@oot_crates_no_std//:spdm-lib",
],
)
```
- Choose whether the default system image
([target/ast1060-evb/mctp/BUILD.bazel](../../target/ast1060-evb/mctp/BUILD.bazel))
wires the requester or the responder. Do **not** change the default in
this phase — add a second system image build instead (e.g.
`system_requester`) so both can be flashed side by side during
bringup.
**Files actually landed**:
- `services/mctp/server/BUILD.bazel` — second `rust_app(name = "mctp_server_requester", codegen_crate_name = "app_mctp_server_requester", ...)` target.
- `target/ast1060-evb/mctp-requester/BUILD.bazel` — new sibling package with its own `system_image`, `target_codegen`, `target_linker_script`, `rust_binary(name="target",...)`, and `uart_boot_image`.
- `target/ast1060-evb/mctp-requester/system.json5` — copy of the mctp image's config with the MCTP app renamed to `mctp_server_requester`.
- `target/ast1060-evb/mctp-requester/target.rs` — copy of the kernel entry shim.
**Exit criteria**:
- Both system images build clean from scratch:
```
bazel build --config=k_ast1060_evb //target/ast1060-evb/mctp:mctp
bazel build --config=k_ast1060_evb //target/ast1060-evb/mctp-requester:mctp_requester
```
- Responder system image continues to boot unchanged.
- Requester system image boots and reaches the FSM entry log within
the expected startup window (deferred to on-target bringup — Phase 6).
**Risks / rollback**:
- *Risk*: `codegen_crate_name` collision when both `rust_app` targets
live in the same Bazel package. *Resolved*: the two targets use
distinct codegen crate names (`app_mctp_server`,
`app_mctp_server_requester`) and `src/main.rs` selects between them
with a cfg-gated `use`.
- *Risk*: platform-constraint analysis error when building
`rust_app` standalone. *Resolved*: the `rust_app` target must be
consumed by a `system_image`, never built directly.
- *Rollback*: delete the new system-image package; responder unaffected.
---
## Phase 6 — Integration Test (two-board, on-target hardware)
**Goal**: a repeatable test that exercises the in-process requester
against the in-process responder on the bench.
**Status**: ⏸ blocked — no on-target hardware available. See §A for
measurements to collect when hardware is accessible.
**Deliverables**:
- Two-board test config: board A flashed with
`mctp_server_requester` system image, board B flashed with
`mctp_server` (responder). Both use current `OWN_EID` / `OWN_I2C_ADDR`
constants, with board A targeting board B at
`REMOTE_RESPONDER_EID = 42` — board B must be re-configured to EID
42 (its current `OWN_EID = 0x08`; this requires a new const
`RESPONDER_OWN_EID` on the responder image, or swapping A↔B roles).
- Documented test recipe in `README.md` (new section) or a dedicated
`tests/ON_TARGET_README.md`: flash steps, expected log lines, expected
duration-to-`Done`.
- Automated log-checking script is out of scope; bringup-level manual
verification is sufficient.
**Files**: docs only, plus any const bump in main.rs if we change
`OWN_EID` on the responder side.
**Exit criteria**:
- Manual test produces a `SPDM VCA completed` line on the requester side
and three matching processed-request lines on the responder side.
**Risks / rollback**:
- *Risk*: EID mismatch between the two images. *Mitigation*: the spec
prescribes explicit named constants for both ends; document the
pairing in the test recipe.
- *Rollback*: none needed — docs only.
---
## Phase 6a — QEMU Integration Test (virt-ast1060-evb)
**Goal**: automated QEMU test that exercises the full I2C + MCTP + SPDM
requester stack without physical hardware, using QEMU's ASPEED I2C
device emulation.
**Prerequisite**: QEMU patched with ASPEED i2c device model (user confirmed
April 2026).
**Deliverables**:
- `target/virt-ast1060-evb/mctp-requester/system.json5` — two-process
(i2c_server + mctp_server_requester) virt image with ASPEED I2C MMIO
mappings at the real hardware addresses (emulated by QEMU).
- `target/virt-ast1060-evb/mctp-requester/target.rs` — semihosting
kernel shim; `shutdown()` calls `exit(EXIT_SUCCESS/FAILURE)` so QEMU
terminates and the test harness can read the exit code.
- `target/virt-ast1060-evb/mctp-requester/BUILD.bazel` — `system_image`,
`system_image_test`, `target_codegen`, `target_linker_script`,
`rust_binary(name="target")` wired to `//target/virt-ast1060-evb`
platform and linker template.
- `services/mctp/server/BUILD.bazel` — `mctp_server_requester_virt`
rust_app, same feature set as `mctp_server_requester` but with
`system_config = "//target/virt-ast1060-evb/mctp-requester:system_config"`
so codegen (handle constants + linker script) is correct for QEMU.
**Files**:
- `target/virt-ast1060-evb/mctp-requester/system.json5` (new)
- `target/virt-ast1060-evb/mctp-requester/target.rs` (new)
- `target/virt-ast1060-evb/mctp-requester/BUILD.bazel` (new)
- `services/mctp/server/BUILD.bazel` (add `mctp_server_requester_virt`)
**Build / test commands**:
```bash
bazel build --config=k_virt_ast1060_evb //target/virt-ast1060-evb/mctp-requester:mctp_requester
bazel test --config=k_virt_ast1060_evb //target/virt-ast1060-evb/mctp-requester:mctp_requester_test
```
**Exit criteria**:
- `bazel test` passes: QEMU starts, the FSM reaches `Done`, semihosting
`exit(EXIT_SUCCESS)` fires, QEMU terminates with code 0, Bazel marks
the test green.
- Responder hardware image (`//target/ast1060-evb/mctp:mctp`) and
hardware requester image (`//target/ast1060-evb/mctp-requester:mctp_requester`)
still build clean.
**Key design notes**:
- `mctp_server_requester_virt` shares `codegen_crate_name =
"app_mctp_server_requester"` with the hardware target — the handle
layout is identical (I2C object is second in the process objects list
in both system configs), so no source changes are needed.
- Memory layout (640KB) mirrors `virt-ast1060-evb/spdm-req-resp-test`;
vector_table_size_bytes = 1280 (matches multi-process virt convention).
- ASPEED I2C MMIO mappings (`0x7e7b0000` / `0x7e6e2000`) are kept so
the i2c_server binary can run unmodified against the QEMU device model.
**Risks / rollback**:
- *Risk*: QEMU's ASPEED I2C model doesn't emulate bus-2 slave-mode
loopback precisely enough for the polling loop. *Mitigation*: QEMU
patch is user-supplied and already tested; fall back to the two-board
(Phase 6) path if emulation fidelity is insufficient.
- *Rollback*: delete the new `target/virt-ast1060-evb/mctp-requester/`
package and remove `mctp_server_requester_virt` from services BUILD.bazel;
hardware targets are unaffected.
---
## Phase 7 — Cleanup & Follow-Up Tickets
**Goal**: close loose ends the design doc called out and make sure
nothing is left under-documented.
**Deliverables**:
- File follow-up issues (or local TODO comments tagged with a tracking
tag) for:
- Design §6.3 option 2 — propagate `TransportError` out of
`requester_process_message` so TimedOut can be discriminated.
- Design §9.1 — pumping-`DirectMctpClient` variant (Shape B).
- Design §9.3 — external trigger mechanism (IPC / GPIO / policy).
- Design §9.4 — dual-role configuration.
- Update [INITIALIZATION.md](INITIALIZATION.md) with a new "Polling mode
(requester)" subsection mirroring the responder's, so the glossary
stays truthful.
- Update the build-modes table in both `main.rs` module docs and
`INITIALIZATION.md` to list the new requester row.
**Files**: `INITIALIZATION.md`, `src/main.rs` (module-level doc comment
only).
**Exit criteria**: follow-up issues filed; docs reviewed; no
undocumented feature flag.
**Risks / rollback**: none — docs and issue filing.
---
## Phase Dependency Graph
```
Phase 0 ──► Phase 1 ──► Phase 2 ──► Phase 3 ──► Phase 4 ──► Phase 5 ──► Phase 6 ──► Phase 7
│ ▲
└────────────────────────────────────┘
(Phase 5 can start after Phase 2 in parallel
with Phase 3/4 if a separate hand is available)
```
Phases 1 and 2 are small (half-day each). Phase 3 is the bulk of the
work — expect a full day including on-target debug. Phases 4–6 are
each half-day. Phase 7 is a loose-ends sweep.
---
## A. Deferred Measurements (on-target bringup)
These values are **not** required to land Phases 0–5. Capture them once
hardware execution is available (Phase 6 onward) and revisit §6.4 of the
design doc if any value contradicts the back-of-envelope `10_000`
default.
| Metric | Measured value | Source |
|---|---|---|
| `idle_polls` increment rate (per wall-clock second) | _deferred_ | responder log, counted over a fixed window |
| `wait_for_messages` average return latency when idle | _deferred_ | derived from idle_polls rate |
| Largest expected VCA message fragment count | _deferred_ | `receiver.decode` SOM/EOM logs |
| Observed peak `await_steps` on happy path | _deferred_ | requester log at `Done` transition |
| Tuned `AWAIT_STEP_BUDGET` (≥ ~3× observed peak) | _deferred_ | computed |
Until these exist, the design ships with the default and relies on the
Phase-4 state-timeout log line to surface a bad choice loudly rather
than silently.