add initial test for spdm
diff --git a/target/ast10x0/tests/spdm/mctp_server/BUILD.bazel b/target/ast10x0/tests/spdm/mctp_server/BUILD.bazel
new file mode 100644
index 0000000..f88470f
--- /dev/null
+++ b/target/ast10x0/tests/spdm/mctp_server/BUILD.bazel
@@ -0,0 +1,31 @@
+# Licensed under the Apache-2.0 license
+# SPDX-License-Identifier: Apache-2.0
+#
+# MCTP Server for SPDM testing
+#
+# STUB IMPLEMENTATION — This is a minimal MCTP server that speaks the
+# openprot_mctp_api::wire protocol with deterministic responses for testing.
+#
+# TODO: Replace this stub with a real MCTP server implementation when
+# hardware transport is integrated. The rust_app target in the test
+# BUILD.bazel should only need to change its dependency from this
+# package to the real implementation.
+
+load("@pigweed//pw_kernel/tooling:rust_app.bzl", "rust_app")
+load("//target/ast10x0:defs.bzl", "TARGET_COMPATIBLE_WITH")
+
+rust_app(
+    name = "mctp_server",
+    srcs = ["src/main.rs"],
+    codegen_crate_name = "app_mctp_server",
+    edition = "2024",
+    system_config = "//target/ast10x0/tests/spdm/responder:system_config",
+    tags = ["kernel"],
+    target_compatible_with = TARGET_COMPATIBLE_WITH,
+    visibility = ["//target/ast10x0/tests/spdm:__subpackages__"],
+    deps = [
+        "//services/mctp/api:mctp_api",
+        "@pigweed//pw_kernel/userspace",
+        "@pigweed//pw_log/rust:pw_log",
+    ],
+)
diff --git a/target/ast10x0/tests/spdm/mctp_server/src/main.rs b/target/ast10x0/tests/spdm/mctp_server/src/main.rs
new file mode 100644
index 0000000..e45af07
--- /dev/null
+++ b/target/ast10x0/tests/spdm/mctp_server/src/main.rs
@@ -0,0 +1,130 @@
+// Licensed under the Apache-2.0 license
+// SPDX-License-Identifier: Apache-2.0
+
+//! MCTP Server stub for SPDM Responder testing.
+//!
+//! STUB IMPLEMENTATION — This is a minimal MCTP server that speaks the
+//! `openprot_mctp_api::wire` protocol over a Pigweed IPC channel.
+//! It provides deterministic responses for testing SPDM over MCTP.
+//!
+//! TODO: Replace with real MCTP server when hardware transport is integrated.
+//!
+//! ## Supported Operations
+//!
+//! | Op        | Response                                           |
+//! |-----------|----------------------------------------------------|
+//! | SetEid    | Success                                            |
+//! | GetEid    | Success, eid = 8                                   |
+//! | Listener  | Success, handle = 1                                |
+//! | Req       | Success, handle = 2                                |
+//! | Recv      | Success, returns pending SPDM request (if any)     |
+//! | Send      | Success, buffers response for next recv            |
+//! | Unbind    | Success                                            |
+
+#![no_main]
+#![no_std]
+
+use app_mctp_server::handle;
+use openprot_mctp_api::wire::{
+    self, MctpOp, MctpRequestHeader, MAX_REQUEST_SIZE, MAX_RESPONSE_SIZE,
+};
+use openprot_mctp_api::ResponseCode;
+use userspace::syscall::Signals;
+use userspace::time::Instant;
+use userspace::{entry, syscall};
+
+const LISTENER_HANDLE: u32 = 1;
+const REQ_HANDLE: u32 = 2;
+const LOCAL_EID: u8 = 8;
+
+#[entry]
+fn entry() {
+    pw_log::info!("MCTP server stub starting");
+
+    let mut req_buf = [0u8; MAX_REQUEST_SIZE];
+    let mut resp_buf = [0u8; MAX_RESPONSE_SIZE];
+
+    if syscall::wait_group_add(handle::WG, handle::MCTP, Signals::READABLE, 0usize).is_err() {
+        pw_log::error!("Failed to add MCTP channel to wait group");
+        loop {}
+    }
+
+    loop {
+        let _ = syscall::object_wait(handle::WG, Signals::READABLE, Instant::MAX);
+
+        let len = match syscall::channel_read(handle::MCTP, 0, &mut req_buf) {
+            Ok(n) => n,
+            Err(_) => continue,
+        };
+
+        let header = match MctpRequestHeader::from_bytes(&req_buf[..len]) {
+            Some(h) => h,
+            None => {
+                let rlen =
+                    wire::encode_error_response(&mut resp_buf, ResponseCode::BadArgument)
+                        .unwrap_or(0);
+                let _ = syscall::channel_respond(handle::MCTP, &resp_buf[..rlen]);
+                continue;
+            }
+        };
+
+        let resp_len = dispatch(&header, &req_buf[..len], &mut resp_buf);
+        let _ = syscall::channel_respond(handle::MCTP, &resp_buf[..resp_len]);
+    }
+}
+
+fn dispatch(header: &MctpRequestHeader, _req: &[u8], resp: &mut [u8]) -> usize {
+    let op = match header.operation() {
+        Some(op) => op,
+        None => {
+            return wire::encode_error_response(resp, ResponseCode::BadArgument).unwrap_or(0);
+        }
+    };
+
+    match op {
+        MctpOp::SetEid => {
+            pw_log::debug!("SetEid: {}", header.eid as u32);
+            wire::encode_success_response(resp).unwrap_or(0)
+        }
+
+        MctpOp::GetEid => {
+            pw_log::debug!("GetEid -> {}", LOCAL_EID as u32);
+            wire::encode_get_eid_response(resp, LOCAL_EID).unwrap_or(0)
+        }
+
+        MctpOp::Listener => {
+            pw_log::debug!("Listener: msg_type={}", header.msg_type as u32);
+            wire::encode_handle_response(resp, LISTENER_HANDLE).unwrap_or(0)
+        }
+
+        MctpOp::Req => {
+            pw_log::debug!("Req: eid={}", header.eid as u32);
+            wire::encode_handle_response(resp, REQ_HANDLE).unwrap_or(0)
+        }
+
+        MctpOp::Recv => {
+            pw_log::debug!("Recv: handle={}", header.handle as u32);
+            // Return a minimal SPDM GET_VERSION request for testing
+            // SPDM message type = 0x05, SPDMVersion=0x10, RequestResponseCode=0x84 (GET_VERSION)
+            let spdm_get_version: [u8; 4] = [0x10, 0x84, 0x00, 0x00];
+            wire::encode_recv_response(resp, 0x05, false, LOCAL_EID, 0, &spdm_get_version)
+                .unwrap_or(0)
+        }
+
+        MctpOp::Send => {
+            pw_log::debug!("Send: msg_type={}", header.msg_type as u32);
+            wire::encode_send_response(resp, 0).unwrap_or(0)
+        }
+
+        MctpOp::Unbind => {
+            pw_log::debug!("Unbind: handle={}", header.handle as u32);
+            wire::encode_success_response(resp).unwrap_or(0)
+        }
+    }
+}
+
+#[panic_handler]
+fn panic(_info: &core::panic::PanicInfo) -> ! {
+    pw_log::error!("MCTP server panic");
+    loop {}
+}
diff --git a/target/ast10x0/tests/spdm/responder/BUILD.bazel b/target/ast10x0/tests/spdm/responder/BUILD.bazel
new file mode 100644
index 0000000..1fe08fc
--- /dev/null
+++ b/target/ast10x0/tests/spdm/responder/BUILD.bazel
@@ -0,0 +1,100 @@
+# Licensed under the Apache-2.0 license
+# SPDX-License-Identifier: Apache-2.0
+
+load("@pigweed//pw_kernel/tooling:rust_app.bzl", "rust_app")
+load("@pigweed//pw_kernel/tooling:system_image.bzl", "system_image")
+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/panic_detector:rust_binary_no_panics_test.bzl", "rust_binary_no_panics_test")
+load("@rules_rust//rust:defs.bzl", "rust_binary")
+load("//target/ast10x0:defs.bzl", "TARGET_COMPATIBLE_WITH")
+
+# ── System configuration ───────────────────────────────────────────────────────
+
+filegroup(
+    name = "system_config",
+    srcs = ["system.json5"],
+    visibility = ["//target/ast10x0/tests/spdm:__subpackages__"],
+)
+
+# ── Kernel image ───────────────────────────────────────────────────────────────
+
+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/ast10x0:linker_script_template",
+)
+
+rust_binary(
+    name = "target",
+    srcs = ["target.rs"],
+    edition = "2024",
+    tags = ["kernel"],
+    target_compatible_with = TARGET_COMPATIBLE_WITH,
+    deps = [
+        ":codegen",
+        ":linker_script",
+        "//target/ast10x0:entry",
+        "@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",
+        "@pigweed//pw_kernel/userspace",
+        "@pigweed//pw_log/rust:pw_log",
+    ],
+)
+
+# ── SPDM Responder app ─────────────────────────────────────────────────────────
+# SPDM responder using spdm-lib; listens for SPDM requests over MCTP.
+
+rust_app(
+    name = "spdm_responder",
+    srcs = ["spdm_responder_main.rs"],
+    codegen_crate_name = "app_spdm_responder",
+    edition = "2024",
+    system_config = ":system_config",
+    tags = ["kernel"],
+    target_compatible_with = TARGET_COMPATIBLE_WITH,
+    deps = [
+        "//services/mctp/api:mctp_api",
+        "//services/mctp/client-ipc:mctp_client_ipc",
+        "//services/spdm/responder:spdm_responder_lib",
+        "//services/spdm/transport-mctp:spdm_transport_mctp",
+        "@pigweed//pw_kernel/userspace",
+        "@pigweed//pw_log/rust:pw_log",
+        "@pigweed//pw_status/rust:pw_status",
+        "@rust_crates//:spdm-lib",
+    ],
+)
+
+# ── System image ───────────────────────────────────────────────────────────────
+
+system_image(
+    name = "spdm_responder_image",
+    apps = [
+        # TODO: Replace with real MCTP server when hardware transport is integrated
+        "//target/ast10x0/tests/spdm/mctp_server",
+        ":spdm_responder",
+    ],
+    kernel = ":target",
+    platform = "//target/ast10x0",
+    system_config = ":system_config",
+    tags = ["kernel"],
+    target_compatible_with = TARGET_COMPATIBLE_WITH,
+    visibility = ["//visibility:public"],
+)
+
+rust_binary_no_panics_test(
+    name = "no_panics_test",
+    binary = ":spdm_responder_image",
+    tags = ["kernel"],
+)
diff --git a/target/ast10x0/tests/spdm/responder/spdm_responder_main.rs b/target/ast10x0/tests/spdm/responder/spdm_responder_main.rs
new file mode 100644
index 0000000..0021117
--- /dev/null
+++ b/target/ast10x0/tests/spdm/responder/spdm_responder_main.rs
@@ -0,0 +1,87 @@
+// Licensed under the Apache-2.0 license
+// SPDX-License-Identifier: Apache-2.0
+
+//! SPDM Responder test application.
+//!
+//! This application exercises the SPDM responder implementation using
+//! spdm-lib over the MCTP transport layer.
+//!
+//! ## Test Flow
+//!
+//! 1. Initialize MCTP transport via IPC to mctp_server
+//! 2. Create SPDM responder with platform implementations
+//! 3. Process incoming SPDM messages
+//! 4. Report pass/fail via debug_shutdown
+
+#![no_main]
+#![no_std]
+
+use app_spdm_responder::handle;
+use openprot_mctp_api::stack::Stack;
+use openprot_mctp_client_ipc::IpcMctpClient;
+use openprot_spdm_transport_mctp::MctpSpdmTransport;
+use pw_status::Error;
+use spdm_lib::platform::transport::SpdmTransport;
+use userspace::{entry, syscall};
+
+#[entry]
+fn entry() {
+    match run() {
+        Ok(()) => {
+            pw_log::info!("SPDM responder test PASSED");
+            let _ = syscall::debug_shutdown(Ok(()));
+        }
+        Err(e) => {
+            pw_log::error!("SPDM responder test FAILED: {}", e as u32);
+            let _ = syscall::debug_shutdown(Err(Error::Internal));
+        }
+    }
+    loop {}
+}
+
+fn run() -> Result<(), u32> {
+    pw_log::info!("SPDM responder test starting");
+
+    // Create MCTP client over IPC
+    let mctp_client = IpcMctpClient::new(handle::MCTP);
+    let stack = Stack::new(mctp_client);
+
+    // Set local EID
+    stack.set_eid(8).map_err(|e| {
+        pw_log::error!("Stack set_eid failed: {}", e.code as u32);
+        1u32
+    })?;
+
+    pw_log::info!("MCTP stack initialized with EID 8");
+
+    // Create SPDM transport in responder mode
+    let mut transport = MctpSpdmTransport::new_responder(&stack);
+
+    // Initialize transport (registers listener for SPDM message type)
+    transport.init_sequence().map_err(|_| {
+        pw_log::error!("Transport init failed");
+        2u32
+    })?;
+
+    pw_log::info!("SPDM transport initialized");
+
+    // For now, just verify we can initialize the transport.
+    // Full responder testing requires platform implementations for:
+    // - CertStore (certificate management)
+    // - Hash (SHA-384/512)
+    // - RNG (random number generation)
+    // - Evidence (measurements)
+    //
+    // TODO: Add stub implementations and exercise full SPDM flow.
+
+    pw_log::info!("SPDM responder initialization complete");
+
+    Ok(())
+}
+
+#[panic_handler]
+fn panic(_info: &core::panic::PanicInfo) -> ! {
+    pw_log::error!("SPDM responder panic");
+    let _ = syscall::debug_shutdown(Err(Error::Internal));
+    loop {}
+}
diff --git a/target/ast10x0/tests/spdm/responder/system.json5 b/target/ast10x0/tests/spdm/responder/system.json5
new file mode 100644
index 0000000..3314a1c
--- /dev/null
+++ b/target/ast10x0/tests/spdm/responder/system.json5
@@ -0,0 +1,81 @@
+// Licensed under the Apache-2.0 license
+// SPDX-License-Identifier: Apache-2.0
+
+// AST10x0 SPDM Responder test system image.
+//
+// Two-process layout:
+//
+//   mctp_server     — MCTP channel handler (stub for testing)
+//   spdm_responder  — SPDM responder using spdm-lib
+//
+// Memory map (AST10x0: 768 KB SRAM, no XIP):
+//   0x00000000 - 0x00000500  vector table (1280 B)
+//   0x00000500 - 0x00020500  kernel flash (~128 KB)
+//   0x00020500 - 0x00060500  app flash (256 KB total; 128 KB per app)
+//   0x00060000 - 0x00080000  kernel RAM (128 KB)
+//   0x00080000 - 0x000C0000  app RAM (256 KB total)
+{
+    arch: {
+        type: "armv7m",
+        vector_table_start_address: 0x00000000,
+        vector_table_size_bytes: 1280,
+    },
+    kernel: {
+        flash_start_address: 0x00000500,
+        flash_size_bytes: 129792,
+        ram_start_address: 0x00060000,
+        ram_size_bytes: 131072,
+    },
+    apps: [
+        {
+            name: "mctp_server",
+            flash_size_bytes: 131072,
+            processes: [
+                {
+                    name: "mctp_server_process",
+                    ram_size_bytes: 32768,
+                    objects: [
+                        {
+                            name: "wg",
+                            type: "wait_group",
+                        },
+                        {
+                            name: "mctp",
+                            type: "channel_handler",
+                        },
+                    ],
+                    threads: [
+                        {
+                            name: "mctp_server_thread",
+                            kernel_stack_size_bytes: 4096,
+                        },
+                    ],
+                },
+            ],
+        },
+        {
+            name: "spdm_responder",
+            flash_size_bytes: 131072,
+            processes: [
+                {
+                    name: "spdm_responder_process",
+                    ram_size_bytes: 65536,
+                    objects: [
+                        {
+                            name: "mctp",
+                            type: "channel_initiator",
+                            handler_process: "mctp_server_process",
+                            handler_object_name: "mctp",
+                        },
+                    ],
+                    threads: [
+                        {
+                            name: "spdm_responder_thread",
+                            kernel_stack_size_bytes: 4096,
+                        },
+                    ],
+                },
+            ],
+        },
+    ],
+}
diff --git a/target/ast10x0/tests/spdm/responder/target.rs b/target/ast10x0/tests/spdm/responder/target.rs
new file mode 100644
index 0000000..b7b25c9
--- /dev/null
+++ b/target/ast10x0/tests/spdm/responder/target.rs
@@ -0,0 +1,36 @@
+// Licensed under the Apache-2.0 license
+// SPDX-License-Identifier: Apache-2.0
+
+//! Kernel target for the SPDM Responder test.
+
+#![no_std]
+#![no_main]
+
+use console_backend::console_backend_write_all;
+use entry as _;
+use target_common::{declare_target, TargetInterface};
+
+pub struct Target {}
+
+impl TargetInterface for Target {
+    const NAME: &'static str = "AST10x0 SPDM Responder test";
+
+    fn main() -> ! {
+        codegen::start();
+        #[expect(clippy::empty_loop)]
+        loop {}
+    }
+
+    fn shutdown(code: u32) -> ! {
+        let sentinel: &[u8] = if code == 0 {
+            b"TEST_RESULT:PASS\n"
+        } else {
+            b"TEST_RESULT:FAIL\n"
+        };
+        let _ = console_backend_write_all(sentinel);
+        #[expect(clippy::empty_loop)]
+        loop {}
+    }
+}
+
+declare_target!(Target);