[PATCH 11/18] lib: rspdm: Support SPDM get_version
From: alistair23
Date: Thu May 07 2026 - 23:22:23 EST
From: Alistair Francis <alistair@xxxxxxxxxxxxx>
Support the GET_VERSION SPDM command.
Signed-off-by: Alistair Francis <alistair@xxxxxxxxxxxxx>
---
lib/rspdm/consts.rs | 14 +++++++++
lib/rspdm/lib.rs | 8 ++++-
lib/rspdm/state.rs | 61 ++++++++++++++++++++++++++++++++++++
lib/rspdm/validator.rs | 70 ++++++++++++++++++++++++++++++++++++++++++
4 files changed, 152 insertions(+), 1 deletion(-)
diff --git a/lib/rspdm/consts.rs b/lib/rspdm/consts.rs
index 2feddde67885..5482a0f6cee0 100644
--- a/lib/rspdm/consts.rs
+++ b/lib/rspdm/consts.rs
@@ -7,10 +7,21 @@
//! 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;
+#[allow(dead_code)]
+pub(crate) const SPDM_VER_11: u8 = 0x11;
+#[allow(dead_code)]
+pub(crate) const SPDM_VER_12: u8 = 0x12;
+#[allow(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;
pub(crate) const SPDM_REQ: u8 = 0x80;
pub(crate) const SPDM_ERROR: u8 = 0x7f;
@@ -54,3 +65,6 @@ fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
Ok(())
}
}
+
+pub(crate) const SPDM_GET_VERSION: u8 = 0x84;
+pub(crate) const SPDM_GET_VERSION_LEN: usize = mem::size_of::<SpdmHeader>() + u8::MAX as usize;
diff --git a/lib/rspdm/lib.rs b/lib/rspdm/lib.rs
index 758d43fba5cb..1cc6c33516fb 100644
--- a/lib/rspdm/lib.rs
+++ b/lib/rspdm/lib.rs
@@ -105,7 +105,13 @@
/// Return 0 on success or a negative errno. In particular, -EPROTONOSUPPORT
/// indicates authentication is not supported by the device.
#[export]
-pub unsafe extern "C" fn spdm_authenticate(_state_ptr: *mut spdm_state) -> c_int {
+pub unsafe extern "C" fn spdm_authenticate(state_ptr: *mut spdm_state) -> c_int {
+ let state: &mut SpdmState = unsafe { &mut (*(state_ptr as *mut SpdmState)) };
+
+ if let Err(e) = state.get_version() {
+ return e.to_errno() as c_int;
+ }
+
0
}
diff --git a/lib/rspdm/state.rs b/lib/rspdm/state.rs
index 18e81f24c724..26b90942b298 100644
--- a/lib/rspdm/state.rs
+++ b/lib/rspdm/state.rs
@@ -8,6 +8,7 @@
//! <https://www.dmtf.org/dsp/DSP0274>
use core::ffi::c_void;
+use core::slice::from_raw_parts_mut;
use kernel::prelude::*;
use kernel::{
bindings,
@@ -22,10 +23,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, //
};
@@ -232,4 +237,60 @@ 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.version = self.version;
+
+ // SAFETY: `request` is repr(C) and packed, so we can convert it to a slice
+ let request_buf = unsafe {
+ from_raw_parts_mut(
+ &mut request as *mut _ as *mut u8,
+ core::mem::size_of::<GetVersionReq>(),
+ )
+ };
+
+ let mut response_vec: KVec<u8> = KVec::with_capacity(SPDM_GET_VERSION_LEN, GFP_KERNEL)?;
+ // SAFETY: `response_vec` is SPDM_GET_VERSION_LEN length, initialised, aligned
+ // and won't be mutated
+ let response_buf =
+ unsafe { from_raw_parts_mut(response_vec.as_mut_ptr(), SPDM_GET_VERSION_LEN) };
+
+ let rc = self.spdm_exchange(request_buf, response_buf)?;
+
+ // SAFETY: `rc` is the length of data read, which will be smaller
+ // than the capacity of the vector
+ unsafe { response_vec.inc_len(rc as usize) };
+
+ let response: &mut GetVersionRsp = Untrusted::new_mut(&mut response_vec).validate_mut()?;
+
+ let mut foundver = false;
+ let unaligned = core::ptr::addr_of_mut!(response.version_number_entries) as *mut u16;
+
+ for i in 0..response.version_number_entry_count {
+ // Creating a reference on a packed struct will result in
+ // undefined behaviour, so we operate on the raw data directly
+ let addr = unaligned.wrapping_add(i as usize);
+ let alpha_version = (unsafe { core::ptr::read_unaligned::<u16>(addr) } & 0xF) as u8;
+ let version = (unsafe { core::ptr::read_unaligned::<u16>(addr) } >> 8) as u8;
+
+ if alpha_version > 0 {
+ pr_warn!("Alpha version {alpha_version} is unsupported\n");
+ }
+
+ if version >= self.version && version <= SPDM_MAX_VER {
+ self.version = version;
+ foundver = true;
+ }
+ }
+
+ if !foundver {
+ pr_err!("No common supported version\n");
+ to_result(-(bindings::EPROTO as i32))?;
+ }
+
+ Ok(())
+ }
}
diff --git a/lib/rspdm/validator.rs b/lib/rspdm/validator.rs
index 58039f532b7d..8f45bafd4d69 100644
--- a/lib/rspdm/validator.rs
+++ b/lib/rspdm/validator.rs
@@ -7,6 +7,10 @@
//! Rust implementation of the DMTF Security Protocol and Data Model (SPDM)
//! <https://www.dmtf.org/dsp/DSP0274>
+use crate::bindings::{
+ __IncompleteArrayField,
+ __le16, //
+};
use crate::consts::SpdmErrorCode;
use core::mem;
use kernel::prelude::*;
@@ -21,6 +25,11 @@
},
};
+use crate::consts::{
+ SPDM_GET_VERSION,
+ SPDM_MIN_VER, //
+};
+
#[repr(C, packed)]
pub(crate) struct SpdmHeader {
pub(crate) version: u8,
@@ -71,3 +80,64 @@ pub(crate) struct SpdmErrorRsp {
pub(crate) error_code: SpdmErrorCode,
pub(crate) error_data: u8,
}
+
+#[repr(C, packed)]
+pub(crate) struct GetVersionReq {
+ pub(crate) version: u8,
+ pub(crate) code: u8,
+ pub(crate) param1: u8,
+ pub(crate) param2: u8,
+}
+
+impl Default for GetVersionReq {
+ fn default() -> Self {
+ GetVersionReq {
+ version: 0,
+ code: SPDM_GET_VERSION,
+ param1: 0,
+ param2: 0,
+ }
+ }
+}
+
+#[repr(C, packed)]
+pub(crate) struct GetVersionRsp {
+ pub(crate) version: u8,
+ pub(crate) code: u8,
+ param1: u8,
+ param2: u8,
+ reserved: u8,
+ pub(crate) version_number_entry_count: u8,
+ pub(crate) version_number_entries: __IncompleteArrayField<__le16>,
+}
+
+impl Validate<&mut Unvalidated<KVec<u8>>> for &mut GetVersionRsp {
+ type Err = Error;
+
+ fn validate(unvalidated: &mut Unvalidated<KVec<u8>>) -> Result<Self, Self::Err> {
+ let raw = unvalidated.raw_mut();
+ if raw.len() < mem::size_of::<GetVersionRsp>() {
+ return Err(EINVAL);
+ }
+
+ let version = *(raw.get(0).ok_or(ENOMEM))? as usize;
+ if version != SPDM_MIN_VER.into() {
+ return Err(EINVAL);
+ }
+
+ let version_number_entries = *(raw.get(5).ok_or(ENOMEM))? as usize;
+ let total_expected_size =
+ version_number_entries * mem::size_of::<__le16>() + mem::size_of::<GetVersionRsp>();
+ if raw.len() < total_expected_size {
+ return Err(EINVAL);
+ }
+
+ let ptr = raw.as_mut_ptr();
+ // CAST: `GetVersionRsp` only contains integers and has `repr(C)`.
+ let ptr = ptr.cast::<GetVersionRsp>();
+ // SAFETY: `ptr` came from a reference and the cast above is valid.
+ let rsp: &mut GetVersionRsp = unsafe { &mut *ptr };
+
+ Ok(rsp)
+ }
+}
--
2.52.0