[PATCH v4 15/22] lib: rspdm: Support SPDM get_version

From: alistair23

Date: Sun Sep 27 2026 - 21:16:45 EST


From: Alistair Francis <alistair@xxxxxxxxxxxxx>

Support the GET_VERSION SPDM command.

Signed-off-by: Alistair Francis <alistair@xxxxxxxxxxxxx>
Reviewed-by: Jonathan Cameron <jonathan.cameron@xxxxxxxxxxxxxxxx>
---
lib/rspdm/consts.rs | 16 +++++++-
lib/rspdm/lib.rs | 23 ++++++++++-
lib/rspdm/state.rs | 56 ++++++++++++++++++++++++++-
lib/rspdm/validator.rs | 88 +++++++++++++++++++++++++++++++++++++++++-
4 files changed, 176 insertions(+), 7 deletions(-)

diff --git a/lib/rspdm/consts.rs b/lib/rspdm/consts.rs
index 9709f51849d7..cb0fc2b55206 100644
--- a/lib/rspdm/consts.rs
+++ b/lib/rspdm/consts.rs
@@ -7,16 +7,24 @@
//! Rust implementation of the DMTF Security Protocol and Data Model (SPDM)
//! <https://www.dmtf.org/dsp/DSP0274>

+use crate::validator::SpdmHeader;
+use core::mem;
use kernel::error::{code::EINVAL, Error};

// SPDM versions supported by this implementation
pub(crate) const SPDM_VER_10: u8 = 0x10;
+#[expect(dead_code)]
+pub(crate) const SPDM_VER_11: u8 = 0x11;
+#[expect(dead_code)]
+pub(crate) const SPDM_VER_12: u8 = 0x12;
+#[expect(dead_code)]
+pub(crate) const SPDM_VER_13: u8 = 0x13;
+pub(crate) const SPDM_VER_14: u8 = 0x14;

pub(crate) const SPDM_MIN_VER: u8 = SPDM_VER_10;
+pub(crate) const SPDM_MAX_VER: u8 = SPDM_VER_14;

-#[allow(dead_code)]
pub(crate) const SPDM_REQ: u8 = 0x80;
-#[allow(dead_code)]
pub(crate) const SPDM_ERROR: u8 = 0x7f;

#[derive(Clone, Copy)]
@@ -90,3 +98,7 @@ fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "{:#x}", *self as u8)
}
}
+
+pub(crate) const SPDM_GET_VERSION: u8 = 0x84;
+pub(crate) const SPDM_GET_VERSION_LEN: usize =
+ mem::size_of::<SpdmHeader>() + 2 + (u8::MAX as usize) * mem::size_of::<u16>();
diff --git a/lib/rspdm/lib.rs b/lib/rspdm/lib.rs
index 1010bd38df6d..df3b97a05c41 100644
--- a/lib/rspdm/lib.rs
+++ b/lib/rspdm/lib.rs
@@ -75,7 +75,28 @@ pub extern "C" fn spdm_create(
/// Return 0 on success or a negative errno. In particular, -EPROTONOSUPPORT
/// indicates authentication is not supported by the device.
#[export]
-pub extern "C" fn spdm_authenticate(_state_ptr: *mut spdm_state) -> c_int {
+pub extern "C" fn spdm_authenticate(state_ptr: *mut spdm_state) -> c_int {
+ if state_ptr.is_null() {
+ return -(bindings::EINVAL as c_int);
+ }
+
+ // SAFETY: `state_ptr` was returned from `spdm_create()` which used `into_foreign()`
+ // to create the pointer, and it remains valid until `spdm_destroy()` is called.
+ // We only borrow here (rather than `from_foreign()`) so that ownership stays
+ // with the foreign (C) caller.
+ // The exclusive `&mut SpdmState` lives entirely inside the lock guard, so
+ // concurrent FFI callers serialize on the mutex and can never form
+ // aliased `&mut SpdmState` references.
+ let mutex = unsafe {
+ <Pin<KBox<Mutex<SpdmState>>> as ForeignOwnable>::borrow(state_ptr as *mut c_void)
+ };
+
+ let mut state = mutex.lock();
+
+ if let Err(e) = state.get_version() {
+ return e.to_errno() as c_int;
+ }
+
-(EPROTONOSUPPORT as i32)
}

diff --git a/lib/rspdm/state.rs b/lib/rspdm/state.rs
index c894cf30bb87..3bbe55d965f7 100644
--- a/lib/rspdm/state.rs
+++ b/lib/rspdm/state.rs
@@ -22,10 +22,14 @@
use crate::consts::{
SpdmErrorCode,
SPDM_ERROR,
+ SPDM_GET_VERSION_LEN,
+ SPDM_MAX_VER,
SPDM_MIN_VER,
SPDM_REQ, //
};
use crate::validator::{
+ GetVersionReq,
+ GetVersionRsp,
SpdmErrorRsp,
SpdmHeader, //
};
@@ -37,6 +41,11 @@
/// run one at a time and the locked `&mut SpdmState` is the only way to
/// reach the inner fields.
///
+/// Concurrent access is serialized by wrapping the whole struct in a
+/// `Mutex<SpdmState>` at the FFI boundary, so `spdm_authenticate()` callers
+/// run one at a time and the locked `&mut SpdmState` is the only way to
+/// reach the inner fields.
+///
/// `dev`: Responder device. Used for error reporting and passed to @transport.
/// `transport`: Transport function to perform one message exchange.
/// `transport_priv`: Transport private data.
@@ -76,7 +85,6 @@ pub(crate) fn new(
}
}

- #[allow(dead_code)]
fn spdm_err(&self, rsp: &SpdmErrorRsp) -> Result<(), Error> {
match rsp.error_code {
SpdmErrorCode::InvalidRequest => {
@@ -188,7 +196,6 @@ fn spdm_err(&self, rsp: &SpdmErrorRsp) -> Result<(), Error> {
///
/// The data in `request_buf` is sent to the device and the response is
/// stored in `response_buf`.
- #[allow(dead_code)]
pub(crate) fn spdm_exchange(
&self,
request_buf: &mut [u8],
@@ -238,4 +245,49 @@ pub(crate) fn spdm_exchange(

Ok(length)
}
+
+ /// Negotiate a supported SPDM version and store the information
+ /// in the `SpdmState`.
+ pub(crate) fn get_version(&mut self) -> Result<(), Error> {
+ let mut request = GetVersionReq::default();
+ request.header.version = SPDM_MIN_VER;
+ self.version = SPDM_MIN_VER;
+
+ let mut request_buf = request.to_bytes()?;
+
+ let mut response_vec: KVec<u8> = KVec::from_elem(0u8, SPDM_GET_VERSION_LEN, GFP_KERNEL)?;
+
+ let rc =
+ self.spdm_exchange(request_buf.as_mut_slice(), response_vec.as_mut_slice())? as usize;
+
+ // The transport must report a length within the buffer we provided.
+ if rc > response_vec.len() {
+ return Err(EINVAL);
+ }
+ response_vec.truncate(rc);
+
+ let response: GetVersionRsp = Untrusted::new(response_vec.as_slice()).validate(&*self)?;
+
+ let mut foundver = false;
+ for &entry in response.version_number_entries.iter() {
+ let alpha_version = (entry & 0xF) as u8;
+ let version = (entry >> 8) as u8;
+
+ if alpha_version != 0 {
+ pr_warn!("Alpha version {alpha_version} is not specifically supported\n");
+ }
+
+ if version >= self.version && version <= SPDM_MAX_VER {
+ self.version = version;
+ foundver = true;
+ }
+ }
+
+ if !foundver {
+ pr_err!("No common supported version\n");
+ return Err(EPROTO);
+ }
+
+ Ok(())
+ }
}
diff --git a/lib/rspdm/validator.rs b/lib/rspdm/validator.rs
index b14c066e6a51..d0b6691365c0 100644
--- a/lib/rspdm/validator.rs
+++ b/lib/rspdm/validator.rs
@@ -11,6 +11,7 @@
consts::SpdmErrorCode,
SpdmState, //
};
+use core::mem;
use kernel::prelude::*;
use kernel::{
error::Error,
@@ -20,6 +21,11 @@
},
};

+use crate::consts::{
+ SPDM_GET_VERSION,
+ SPDM_MIN_VER, //
+};
+
#[repr(C, packed)]
pub(crate) struct SpdmHeader {
pub(crate) version: u8,
@@ -29,7 +35,6 @@ pub(crate) struct SpdmHeader {
}

impl SpdmHeader {
- #[expect(dead_code)]
pub(crate) fn new(code: u8) -> Self {
SpdmHeader {
version: 0,
@@ -39,7 +44,6 @@ pub(crate) fn new(code: u8) -> Self {
}
}

- #[expect(dead_code)]
pub(crate) fn to_bytes(&self) -> Result<KVec<u8>> {
let mut out = KVec::new();

@@ -91,3 +95,83 @@ fn validate(unvalidated: &[u8], _context: &'c SpdmState) -> Result<Self, Self::E
})
}
}
+
+pub(crate) struct GetVersionReq {
+ pub(crate) header: SpdmHeader,
+}
+
+impl GetVersionReq {
+ pub(crate) fn to_bytes(&self) -> Result<KVec<u8>> {
+ self.header.to_bytes()
+ }
+}
+
+impl Default for GetVersionReq {
+ fn default() -> Self {
+ GetVersionReq {
+ header: SpdmHeader::new(SPDM_GET_VERSION),
+ }
+ }
+}
+
+#[expect(dead_code)]
+pub(crate) struct GetVersionRsp {
+ pub(crate) header: SpdmHeader,
+
+ pub(crate) version_number_entry_count: u8,
+ pub(crate) version_number_entries: KVec<u16>,
+
+ /// Size of the response, not public
+ length: usize,
+}
+
+impl GetVersionRsp {
+ #[expect(dead_code)]
+ pub(crate) fn len(&self) -> usize {
+ self.length
+ }
+}
+
+impl<'a, 'c> Validate<'c, Untrusted<&'a [u8]>> for GetVersionRsp {
+ type Err = Error;
+
+ type Context = &'c SpdmState;
+
+ fn validate(unvalidated: &[u8], context: &'c SpdmState) -> Result<Self, Self::Err> {
+ let header: SpdmHeader =
+ Untrusted::new(unvalidated.get(0..4).ok_or(EIO)?).validate(context)?;
+
+ if header.code != SPDM_GET_VERSION - 0x80 {
+ return Err(EINVAL);
+ }
+
+ if header.version != SPDM_MIN_VER {
+ return Err(EINVAL);
+ }
+
+ let version_number_entry_count = *unvalidated.get(5).ok_or(EIO)?;
+
+ // Entries follow header (4) + reserved (1) + count (1) = 6 bytes.
+ let mut offset = mem::size_of::<SpdmHeader>() + 2;
+
+ let mut version_number_entries = KVec::new();
+ for _ in 0..version_number_entry_count {
+ let entry = u16::from_le_bytes(
+ unvalidated
+ .get(offset..offset + mem::size_of::<u16>())
+ .ok_or(EIO)?
+ .try_into()
+ .map_err(|_| EINVAL)?,
+ );
+ version_number_entries.push(entry, GFP_KERNEL)?;
+ offset += mem::size_of::<u16>();
+ }
+
+ Ok(GetVersionRsp {
+ header,
+ version_number_entry_count,
+ version_number_entries,
+ length: offset,
+ })
+ }
+}
--
2.55.0