target/virt-ast1060-evb: add QEMU mctp-requester build target

Add a virt-ast1060-evb configuration for the in-process SPDM requester,
enabling integration testing under patched QEMU with Aspeed I2C emulation
without requiring physical AST1060-EVB hardware.

- system.json5: two-process layout (i2c_server + mctp_server_requester),
  640 KB SRAM, ASPEED I2C MMIO at 0x7e7b0000/0x7e6e2000
- target.rs: semihosting shim; shutdown() exits QEMU with pass/fail code
- BUILD.bazel: inline rust_app expansion (rust_app_codegen +
  app_linker_script + rust_binary) to avoid codegen_crate_name collision
  with the hw target in //services/mctp/server
- services/mctp/server/BUILD.bazel: exports_files(["src/main.rs"]) so
  the virt BUILD can reference the shared entry point

Signed-off-by: Anthony Rocha <anthony.rocha@amd.com>
diff --git a/services/mctp/server/BUILD.bazel b/services/mctp/server/BUILD.bazel
index d90e598..384515f 100644
--- a/services/mctp/server/BUILD.bazel
+++ b/services/mctp/server/BUILD.bazel
@@ -1,5 +1,7 @@
 # Licensed under the Apache-2.0 license
 
+exports_files(["src/main.rs"])
+
 load("@rules_rust//rust:defs.bzl", "rust_library", "rust_test")
 load("@pigweed//pw_kernel/tooling:rust_app.bzl", "rust_app")
 
@@ -128,9 +130,12 @@
     ],
 )
 
-# In-process SPDM requester variant.  Same src/main.rs, different feature
-# set.  No separate system_image target yet — use the mctp:mctp image once
-# it is re-wired, or build this target alone to verify compilation.
+# In-process SPDM requester variant (AST1060-EVB hardware target).
+# Same src/main.rs as mctp_server, different feature set.
+# The virt-ast1060-evb variant is expanded inline in
+# //target/virt-ast1060-evb/mctp-requester/BUILD.bazel to avoid a
+# codegen_crate_name collision (two rust_app rules in the same Bazel package
+# cannot share codegen_crate_name).
 rust_app(
     name = "mctp_server_requester",
     codegen_crate_name = "app_mctp_server_requester",
diff --git a/services/mctp/server/IMPLEMENTATION_PLAN.md b/services/mctp/server/IMPLEMENTATION_PLAN.md
index ba935bc..2dadff8 100644
--- a/services/mctp/server/IMPLEMENTATION_PLAN.md
+++ b/services/mctp/server/IMPLEMENTATION_PLAN.md
@@ -15,13 +15,16 @@
 | 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 — clean
-bazel build --config=k_ast1060_evb //target/ast1060-evb/mctp-requester:mctp_requester  # requester — clean
+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:
@@ -44,11 +47,15 @@
 **Developer commands:**
 
 ```bash
-# Responder image (default)
+# Responder image (default, hardware)
 bazel build --config=k_ast1060_evb //target/ast1060-evb/mctp:mctp
 
-# Requester image
+# 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
@@ -437,11 +444,14 @@
 
 ---
 
-## Phase 6 — Integration Test
+## 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
@@ -471,6 +481,73 @@
 
 ---
 
+## 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
diff --git a/target/virt-ast1060-evb/mctp-requester/BUILD.bazel b/target/virt-ast1060-evb/mctp-requester/BUILD.bazel
new file mode 100644
index 0000000..c98f3a8
--- /dev/null
+++ b/target/virt-ast1060-evb/mctp-requester/BUILD.bazel
@@ -0,0 +1,145 @@
+# Licensed under the Apache-2.0 license
+
+load("@pigweed//pw_kernel/tooling:system_image.bzl", "system_image", "system_image_test")
+load("@pigweed//pw_kernel/tooling:target_codegen.bzl", "target_codegen")
+load("@pigweed//pw_kernel/tooling:target_linker_script.bzl", "target_linker_script")
+load("@pigweed//pw_kernel/tooling:app_linker_script.bzl", "app_linker_script")
+load("@pigweed//pw_kernel/tooling:rust_app.bzl", "rust_app_codegen")
+load("@rules_rust//rust:defs.bzl", "rust_binary")
+load("//target/virt-ast1060-evb:defs.bzl", "TARGET_COMPATIBLE_WITH")
+
+# ---------------------------------------------------------------------------
+# System image: I2C server + MCTP server (in-process SPDM requester, QEMU)
+#
+# Requires QEMU patched with ASPEED I2C device emulation.  Mirrors the
+# hardware image at //target/ast1060-evb/mctp-requester:mctp_requester but
+# targets the virt-ast1060-evb platform for automated QEMU testing.
+#
+# The mctp_server_requester app binary is expanded inline here (rather than
+# as a rust_app in //services/mctp/server/BUILD.bazel) because two rust_app
+# rules in the same Bazel package cannot share codegen_crate_name — expansion
+# in the target package places the generated rules in a different namespace.
+# ---------------------------------------------------------------------------
+
+system_image(
+    name = "mctp_requester",
+    apps = [
+        "//services/i2c/server:i2c_server",
+        ":mctp_server_requester_app",
+    ],
+    kernel = ":target",
+    platform = "//target/virt-ast1060-evb",
+    system_config = ":system_config",
+    tags = ["kernel"],
+    visibility = ["//visibility:public"],
+)
+
+system_image_test(
+    name = "mctp_requester_test",
+    image = ":mctp_requester",
+    target_compatible_with = TARGET_COMPATIBLE_WITH,
+)
+
+filegroup(
+    name = "system_config",
+    srcs = ["system.json5"],
+    visibility = ["//visibility:public"],
+)
+
+# ---------------------------------------------------------------------------
+# Kernel target
+# ---------------------------------------------------------------------------
+
+target_codegen(
+    name = "codegen",
+    arch = "@pigweed//pw_kernel/arch/arm_cortex_m:arch_arm_cortex_m",
+    system_config = ":system_config",
+    target_compatible_with = TARGET_COMPATIBLE_WITH,
+)
+
+target_linker_script(
+    name = "linker_script",
+    system_config = ":system_config",
+    tags = ["kernel"],
+    target_compatible_with = TARGET_COMPATIBLE_WITH,
+    template = "//target/virt-ast1060-evb:linker_script_template",
+)
+
+rust_binary(
+    name = "target",
+    srcs = [
+        "target.rs",
+    ],
+    edition = "2024",
+    tags = ["kernel"],
+    target_compatible_with = TARGET_COMPATIBLE_WITH,
+    deps = [
+        ":codegen",
+        ":linker_script",
+        "@pigweed//pw_kernel/arch/arm_cortex_m:arch_arm_cortex_m",
+        "@pigweed//pw_kernel/kernel",
+        "@pigweed//pw_kernel/subsys/console:console_backend",
+        "@pigweed//pw_kernel/target:target_common",
+        "//target/virt-ast1060-evb:entry",
+        "@pigweed//pw_kernel/userspace",
+        "@pigweed//pw_log/rust:pw_log",
+        "@rust_crates//:cortex-m-semihosting",
+    ],
+)
+
+# ---------------------------------------------------------------------------
+# App: MCTP Server (in-process SPDM requester)
+#
+# Expanded from rust_app macro inline so the codegen_crate_name
+# "app_mctp_server_requester" lives in this package, not in
+# //services/mctp/server, avoiding a rule-name collision.
+# ---------------------------------------------------------------------------
+
+rust_app_codegen(
+    name = "app_mctp_server_requester",
+    app_name = "mctp_server_requester",
+    edition = "2024",
+    system_config = ":system_config",
+    tags = ["kernel"],
+    target_compatible_with = TARGET_COMPATIBLE_WITH,
+)
+
+app_linker_script(
+    name = "mctp_server_requester_app.linker_script",
+    app_name = "mctp_server_requester",
+    system_config = ":system_config",
+    tags = ["kernel"],
+)
+
+rust_binary(
+    name = "mctp_server_requester_app",
+    srcs = [
+        "//services/mctp/server:src/main.rs",
+    ],
+    crate_features = [
+        "i2c-polling",
+        "direct-client",
+        "in-process-requester",
+    ],
+    edition = "2024",
+    tags = ["kernel"],
+    target_compatible_with = TARGET_COMPATIBLE_WITH,
+    deps = [
+        ":app_mctp_server_requester",
+        ":mctp_server_requester_app.linker_script",
+        "//services/i2c/api:i2c_api",
+        "//services/i2c/client:i2c_client",
+        "//services/mctp/api:mctp_api",
+        "//services/mctp/server:mctp_server_lib",
+        "//services/mctp/transport-i2c:mctp_transport_i2c",
+        "//services/spdm/transport-mctp:spdm_transport_mctp",
+        "//target/ast1060-evb/spdm-req-resp-test:mock_platform",
+        "@pigweed//pw_kernel/syscall:syscall_user",
+        "@pigweed//pw_kernel/userspace",
+        "@pigweed//pw_log/rust:pw_log",
+        "@pigweed//pw_status/rust:pw_status",
+        "@rust_crates//:mctp",
+        "@rust_crates//:mctp-lib",
+        "@oot_crates_no_std//:spdm-lib",
+    ],
+)
diff --git a/target/virt-ast1060-evb/mctp-requester/system.json5 b/target/virt-ast1060-evb/mctp-requester/system.json5
new file mode 100644
index 0000000..f23df24
--- /dev/null
+++ b/target/virt-ast1060-evb/mctp-requester/system.json5
@@ -0,0 +1,119 @@
+// Licensed under the Apache-2.0 license
+
+// Virtual AST1060-EVB MCTP Requester Configuration (QEMU)
+// ARM Cortex-M4 with semihosting console.
+//
+// Requires QEMU patched with ASPEED I2C device emulation so that the i2c_server
+// can drive bus 2 and the mctp_server_requester can exchange MCTP frames with a
+// peer via the emulated I2C controller.
+//
+// Memory Layout (PMSAv7-friendly, power-of-2 aligned regions):
+//   0x00000000 - 0x00000500: Vector table + kernel annotations (1280 bytes)
+//   0x00000500 - 0x00020000: Kernel flash (~127KB)
+//   0x00020000 - 0x00040000: i2c_server flash (128KB)
+//   0x00040000 - 0x00060000: mctp_server_requester flash (128KB)
+//   0x00060000 - 0x00080000: Kernel RAM (128KB)
+//   0x00080000 - 0x00090000: i2c_server RAM (64KB)
+//   0x00090000 - 0x000A0000: mctp_server_requester RAM (64KB)
+//
+// Total: 640KB (fits within the virtual AST1060's 640KB SRAM window).
+{
+    arch: {
+        type: "armv7m",
+        vector_table_start_address: 0x00000000,
+        vector_table_size_bytes: 1280,
+    },
+    kernel: {
+        flash_start_address: 0x00000500,
+        flash_size_bytes: 129792,         // ~127KB, ends at 0x20000
+        ram_start_address: 0x00060000,
+        ram_size_bytes: 131072,           // 128KB
+    },
+    apps: [
+        // ──── I2C Server ────
+        // Drives the QEMU-emulated ASPEED I2C controller on bus 2.
+        // Object layout intentionally mirrors //target/ast1060-evb/i2c:system_config
+        // so the //services/i2c/server:i2c_server rust_app's handle constants are
+        // consistent across both system images.
+        {
+            name: "i2c_server",
+            flash_size_bytes: 131072,     // 128KB
+            ram_size_bytes: 65536,        // 64KB
+            processes: [
+                {
+                    name: "i2c server process",
+                    objects: [
+                        {
+                            name: "I2C",
+                            type: "channel_handler",
+                        },
+                        {
+                            name: "WG",
+                            type: "wait_group",
+                        },
+                    ],
+                    threads: [
+                        {
+                            name: "i2c server thread",
+                            stack_size_bytes: 4096,
+                        },
+                    ],
+                    memory_mappings: [
+                        {
+                            // ASPEED I2C peripheral block emulated by patched QEMU.
+                            // Covers I2Cglobal through I2cFilter (all 14 controllers + buffers).
+                            name: "i2c_regs",
+                            type: "device",
+                            start_address: 0x7e7b0000,
+                            size_bytes: 0x4000,
+                        },
+                        {
+                            // SCU block — scu310 at +0x310 read by ClockConfig::from_hardware()
+                            // to derive APB clock frequency.
+                            name: "scu",
+                            type: "device",
+                            start_address: 0x7e6e2000,
+                            size_bytes: 0x1000,
+                        },
+                    ],
+                },
+            ],
+        },
+        // ──── MCTP Server (in-process SPDM requester) ────
+        // Object layout mirrors //target/ast1060-evb/mctp-requester:system_config
+        // so the //services/mctp/server:mctp_server_requester_virt rust_app's
+        // handle constants are consistent with the hardware image.
+        {
+            name: "mctp_server_requester",
+            flash_size_bytes: 131072,     // 128KB
+            ram_size_bytes: 65536,        // 64KB (Router state + SPDM context + buffers)
+            processes: [
+                {
+                    name: "mctp server process",
+                    objects: [
+                        {
+                            name: "MCTP",
+                            type: "channel_handler",
+                        },
+                        {
+                            name: "I2C",
+                            type: "channel_initiator",
+                            handler_process: "i2c server process",
+                            handler_object_name: "I2C",
+                        },
+                        {
+                            name: "WG",
+                            type: "wait_group",
+                        },
+                    ],
+                    threads: [
+                        {
+                            name: "mctp server thread",
+                            stack_size_bytes: 32768,
+                        },
+                    ],
+                },
+            ],
+        },
+    ],
+}
diff --git a/target/virt-ast1060-evb/mctp-requester/target.rs b/target/virt-ast1060-evb/mctp-requester/target.rs
new file mode 100644
index 0000000..5261816
--- /dev/null
+++ b/target/virt-ast1060-evb/mctp-requester/target.rs
@@ -0,0 +1,38 @@
+// Licensed under the Apache-2.0 license
+
+//! Virtual AST1060-EVB MCTP Requester Target (QEMU)
+//!
+//! Kernel entry shim for the virt-ast1060-evb mctp-requester system image.
+//! Uses cortex-m-semihosting to signal test pass/fail back to QEMU.
+
+#![no_std]
+#![no_main]
+
+use cortex_m_semihosting::debug::{EXIT_FAILURE, EXIT_SUCCESS, exit};
+use target_common::{TargetInterface, declare_target};
+use {console_backend as _, entry as _};
+
+pub struct Target {}
+
+impl TargetInterface for Target {
+    const NAME: &'static str = "virt-AST1060-EVB MCTP Requester";
+
+    fn main() -> ! {
+        codegen::start();
+        #[expect(clippy::empty_loop)]
+        loop {}
+    }
+
+    fn shutdown(code: u32) -> ! {
+        pw_log::info!("Shutting down with code {}", code as u32);
+        let status = match code {
+            0 => EXIT_SUCCESS,
+            _ => EXIT_FAILURE,
+        };
+        exit(status);
+        #[expect(clippy::empty_loop)]
+        loop {}
+    }
+}
+
+declare_target!(Target);