[PATCH v5 14/23] lib: rspdm: Support SPDM get_version

From: alistair23

Date: Tue Oct 27 2026 - 01:29:37 EST


From: Alistair Francis <alistair.francis@xxxxxxx>

Support the GET_VERSION SPDM command.

Signed-off-by: Alistair Francis <alistair.francis@xxxxxxx>
Reviewed-by: Jonathan Cameron <jonathan.cameron@xxxxxxxxxxxxxxxx>
---
lib/rspdm/consts.rs | 17 +++++++-
lib/rspdm/lib.rs | 25 +++++++++++-
lib/rspdm/state.rs | 43 +++++++++++++++++++-
lib/rspdm/validator.rs | 90 +++++++++++++++++++++++++++++++++++++++++-
4 files changed, 169 insertions(+), 6 deletions(-)

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

+use crate::validator::SpdmHeader;
+use core::mem;
+
// 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;
+
+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 27449ac1ca25d..e0fb7b49d4a07 100644
--- a/lib/rspdm/lib.rs
+++ b/lib/rspdm/lib.rs
@@ -238,7 +238,30 @@ fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
///
/// See `include/linux/spdm.h` for the full C API documentation.
#[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<'_, CTransport>>>> as ForeignOwnable>::borrow(
+ state_ptr.cast::<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 f864d869eff0c..22655b2aff258 100644
--- a/lib/rspdm/state.rs
+++ b/lib/rspdm/state.rs
@@ -20,10 +20,14 @@

use crate::consts::{
SPDM_ERROR,
+ SPDM_GET_VERSION_LEN,
+ SPDM_MAX_VER,
SPDM_MIN_VER,
SPDM_REQ, //
};
use crate::validator::{
+ GetVersionReq,
+ GetVersionRsp,
SpdmErrorRsp,
SpdmHeader, //
};
@@ -97,7 +101,6 @@ pub(crate) fn new(
///
/// The data in `request_buf` is sent to the device and the response
/// is returned as untrusted data to be validated by the caller.
- #[allow(dead_code)]
pub(crate) fn spdm_exchange(
&self,
request_buf: &[u8],
@@ -142,4 +145,42 @@ pub(crate) fn spdm_exchange(

Ok(Untrusted::new(response_buf))
}
+
+ /// 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 response_vec: KVec<u8> = KVec::from_elem(0u8, SPDM_GET_VERSION_LEN, GFP_KERNEL)?;
+
+ let response: GetVersionRsp = self
+ .spdm_exchange(request_buf.as_mut_slice(), response_vec)?
+ .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 0fe341b8dce12..98922638a228b 100644
--- a/lib/rspdm/validator.rs
+++ b/lib/rspdm/validator.rs
@@ -7,11 +7,16 @@
//! Rust implementation of the DMTF Security Protocol and Data Model (SPDM)
//! <https://www.dmtf.org/dsp/DSP0274>

+use crate::consts::{
+ SPDM_GET_VERSION,
+ SPDM_MIN_VER, //
+};
use crate::{
SpdmErrorCode,
SpdmState,
Transport, //
};
+use core::mem;
use kernel::prelude::*;
use kernel::{
error::Error,
@@ -31,7 +36,6 @@ pub(crate) struct SpdmHeader {
}

impl SpdmHeader {
- #[expect(dead_code)]
pub(crate) fn new(code: u8) -> Self {
SpdmHeader {
version: 0,
@@ -41,7 +45,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();

@@ -90,3 +93,86 @@ fn validate(unvalidated: &[u8], _context: &'c SpdmState<'c, T>) -> Result<Self,
})
}
}
+
+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>,
+
+ /// The actual length of the SPDM message. `raw_bytes` might be longer
+ /// as it includes transport specific padding.
+ length: usize,
+
+ /// Entire response, used for the transcript hash.
+ raw_bytes: KVec<u8>,
+}
+
+impl GetVersionRsp {
+ #[expect(dead_code)]
+ pub(crate) fn as_bytes(&self) -> Result<&[u8], Error> {
+ self.raw_bytes.get(..self.length).ok_or(EPROTO)
+ }
+}
+
+impl<'c, T: Transport> Validate<'c, Untrusted<KVec<u8>>, &'c SpdmState<'c, T>> for GetVersionRsp {
+ type Err = Error;
+
+ fn validate(unvalidated: KVec<u8>, context: &'c SpdmState<'c, T>) -> Result<Self, Self::Err> {
+ let header: SpdmHeader =
+ Untrusted::new(unvalidated.get(0..4).ok_or(EPROTO)?).validate(context)?;
+
+ if header.code != SPDM_GET_VERSION - 0x80 {
+ return Err(EPROTO);
+ }
+
+ if header.version != SPDM_MIN_VER {
+ return Err(EPROTO);
+ }
+
+ let version_number_entry_count = *unvalidated.get(5).ok_or(EPROTO)?;
+
+ // 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(EPROTO)?
+ .try_into()
+ .map_err(|_| EPROTO)?,
+ );
+ version_number_entries.push(entry, GFP_KERNEL)?;
+ offset += mem::size_of::<u16>();
+ }
+
+ Ok(GetVersionRsp {
+ header,
+ version_number_entry_count,
+ version_number_entries,
+ length: offset,
+ raw_bytes: unvalidated,
+ })
+ }
+}
--
2.56.0