[PATCH 12/18] lib: rspdm: Support SPDM get_capabilities
From: alistair23
Date: Thu May 07 2026 - 23:22:54 EST
From: Alistair Francis <alistair@xxxxxxxxxxxxx>
Support the GET_CAPABILITIES SPDM command.
Signed-off-by: Alistair Francis <alistair@xxxxxxxxxxxxx>
---
lib/rspdm/consts.rs | 19 ++++++++-
lib/rspdm/lib.rs | 4 ++
lib/rspdm/state.rs | 76 ++++++++++++++++++++++++++++++++-
lib/rspdm/validator.rs | 97 +++++++++++++++++++++++++++++++++++++++++-
4 files changed, 192 insertions(+), 4 deletions(-)
diff --git a/lib/rspdm/consts.rs b/lib/rspdm/consts.rs
index 5482a0f6cee0..ef7d2d1d8e6e 100644
--- a/lib/rspdm/consts.rs
+++ b/lib/rspdm/consts.rs
@@ -9,12 +9,11 @@
use crate::validator::SpdmHeader;
use core::mem;
+use kernel::bits::bit_u32;
/* 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;
@@ -68,3 +67,19 @@ fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
pub(crate) const SPDM_GET_VERSION: u8 = 0x84;
pub(crate) const SPDM_GET_VERSION_LEN: usize = mem::size_of::<SpdmHeader>() + u8::MAX as usize;
+
+pub(crate) const SPDM_GET_CAPABILITIES: u8 = 0xe1;
+pub(crate) const SPDM_MIN_DATA_TRANSFER_SIZE: u32 = 42;
+
+// SPDM cryptographic timeout of this implementation:
+// Assume calculations may take up to 1 sec on a busy machine, which equals
+// roughly 1 << 20. That's within the limits mandated for responders by CMA
+// (1 << 23 usec, PCIe r6.2 sec 6.31.3) and DOE (1 sec, PCIe r6.2 sec 6.30.2).
+// Used in GET_CAPABILITIES exchange.
+pub(crate) const SPDM_CTEXPONENT: u8 = 20;
+
+pub(crate) const SPDM_CERT_CAP: u32 = bit_u32(1);
+pub(crate) const SPDM_CHAL_CAP: u32 = bit_u32(2);
+
+pub(crate) const SPDM_REQ_CAPS: u32 = SPDM_CERT_CAP | SPDM_CHAL_CAP;
+pub(crate) const SPDM_RSP_MIN_CAPS: u32 = SPDM_CERT_CAP | SPDM_CHAL_CAP;
diff --git a/lib/rspdm/lib.rs b/lib/rspdm/lib.rs
index 1cc6c33516fb..9628f258854c 100644
--- a/lib/rspdm/lib.rs
+++ b/lib/rspdm/lib.rs
@@ -112,6 +112,10 @@
return e.to_errno() as c_int;
}
+ if let Err(e) = state.get_capabilities() {
+ return e.to_errno() as c_int;
+ }
+
0
}
diff --git a/lib/rspdm/state.rs b/lib/rspdm/state.rs
index 26b90942b298..e7119ffa9a69 100644
--- a/lib/rspdm/state.rs
+++ b/lib/rspdm/state.rs
@@ -25,10 +25,17 @@
SPDM_ERROR,
SPDM_GET_VERSION_LEN,
SPDM_MAX_VER,
+ SPDM_MIN_DATA_TRANSFER_SIZE,
SPDM_MIN_VER,
- SPDM_REQ, //
+ SPDM_REQ,
+ SPDM_RSP_MIN_CAPS,
+ SPDM_VER_10,
+ SPDM_VER_11,
+ SPDM_VER_12, //
};
use crate::validator::{
+ GetCapabilitiesReq,
+ GetCapabilitiesRsp,
GetVersionReq,
GetVersionRsp,
SpdmErrorRsp,
@@ -47,6 +54,8 @@
///
/// `version`: Maximum common supported version of requester and responder.
/// Negotiated during GET_VERSION exchange.
+/// @rsp_caps: Cached capabilities of responder.
+/// Received during GET_CAPABILITIES exchange.
#[expect(dead_code)]
pub struct SpdmState {
pub(crate) dev: *mut bindings::device,
@@ -57,6 +66,7 @@ pub struct SpdmState {
// Negotiated state
pub(crate) version: u8,
+ pub(crate) rsp_caps: u32,
}
impl SpdmState {
@@ -74,6 +84,7 @@ pub(crate) fn new(
transport_sz,
validate,
version: SPDM_MIN_VER,
+ rsp_caps: 0,
}
}
@@ -293,4 +304,67 @@ pub(crate) fn get_version(&mut self) -> Result<(), Error> {
Ok(())
}
+
+ /// Obtain the supported capabilities from an SPDM session and store the
+ /// information in the `SpdmState`.
+ pub(crate) fn get_capabilities(&mut self) -> Result<(), Error> {
+ let mut request = GetCapabilitiesReq::default();
+ request.version = self.version;
+
+ let (req_sz, rsp_sz) = match self.version {
+ SPDM_VER_10 => (4, 8),
+ SPDM_VER_11 => (8, 8),
+ _ => {
+ request.data_transfer_size = self.transport_sz.to_le();
+ request.max_spdm_msg_size = request.data_transfer_size;
+
+ (
+ core::mem::size_of::<GetCapabilitiesReq>(),
+ core::mem::size_of::<GetCapabilitiesRsp>(),
+ )
+ }
+ };
+
+ // 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, req_sz) };
+
+ let mut response_vec: KVec<u8> = KVec::with_capacity(rsp_sz, GFP_KERNEL)?;
+ // SAFETY: `response_vec` is rsp_sz length, initialised, aligned
+ // and won't be mutated
+ let response_buf = unsafe { from_raw_parts_mut(response_vec.as_mut_ptr(), rsp_sz) };
+
+ let rc = self.spdm_exchange(request_buf, response_buf)?;
+
+ if rc < (rsp_sz as i32) {
+ pr_err!("Truncated capabilities response\n");
+ to_result(-(bindings::EIO as i32))?;
+ }
+
+ // SAFETY: `rc` is the length of data read, which will be smaller
+ // or the same as the capacity of the vector
+ unsafe { response_vec.inc_len(rc as usize) };
+
+ let response: &mut GetCapabilitiesRsp =
+ Untrusted::new_mut(&mut response_vec).validate_mut()?;
+
+ self.rsp_caps = u32::from_le(response.flags);
+ if (self.rsp_caps & SPDM_RSP_MIN_CAPS) != SPDM_RSP_MIN_CAPS {
+ pr_err!(
+ "{:#x} capabilities are supported, which don't meet required {:#x}",
+ self.rsp_caps,
+ SPDM_RSP_MIN_CAPS
+ );
+ to_result(-(bindings::EPROTONOSUPPORT as i32))?;
+ }
+
+ if self.version >= SPDM_VER_12 {
+ if response.data_transfer_size < SPDM_MIN_DATA_TRANSFER_SIZE {
+ pr_err!("Malformed capabilities response\n");
+ to_result(-(bindings::EPROTO as i32))?;
+ }
+ self.transport_sz = self.transport_sz.min(response.data_transfer_size);
+ }
+
+ Ok(())
+ }
}
diff --git a/lib/rspdm/validator.rs b/lib/rspdm/validator.rs
index 8f45bafd4d69..7dc55882c880 100644
--- a/lib/rspdm/validator.rs
+++ b/lib/rspdm/validator.rs
@@ -26,8 +26,12 @@
};
use crate::consts::{
+ SPDM_CTEXPONENT,
+ SPDM_GET_CAPABILITIES,
SPDM_GET_VERSION,
- SPDM_MIN_VER, //
+ SPDM_MIN_VER,
+ SPDM_REQ_CAPS, //
+ SPDM_VER_11,
};
#[repr(C, packed)]
@@ -141,3 +145,94 @@ fn validate(unvalidated: &mut Unvalidated<KVec<u8>>) -> Result<Self, Self::Err>
Ok(rsp)
}
}
+
+#[repr(C, packed)]
+pub(crate) struct GetCapabilitiesReq {
+ pub(crate) version: u8,
+ pub(crate) code: u8,
+ pub(crate) param1: u8,
+ pub(crate) param2: u8,
+
+ reserved1: u8,
+ pub(crate) ctexponent: u8,
+ reserved2: [u8; 2],
+
+ pub(crate) flags: u32,
+
+ /* End of SPDM 1.1 structure */
+ pub(crate) data_transfer_size: u32,
+ pub(crate) max_spdm_msg_size: u32,
+}
+
+impl Default for GetCapabilitiesReq {
+ fn default() -> Self {
+ GetCapabilitiesReq {
+ version: 0,
+ code: SPDM_GET_CAPABILITIES,
+ param1: 0,
+ param2: 0,
+ reserved1: 0,
+ ctexponent: SPDM_CTEXPONENT,
+ reserved2: [0; 2],
+ flags: (SPDM_REQ_CAPS as u32).to_le(),
+ data_transfer_size: 0,
+ max_spdm_msg_size: 0,
+ }
+ }
+}
+
+#[repr(C, packed)]
+pub(crate) struct GetCapabilitiesRsp {
+ pub(crate) version: u8,
+ pub(crate) code: u8,
+ pub(crate) param1: u8,
+ param2: u8,
+
+ reserved1: u8,
+ pub(crate) ctexponent: u8,
+ reserved2: [u8; 2],
+
+ pub(crate) flags: u32,
+
+ /* End of SPDM 1.1 structure */
+ pub(crate) data_transfer_size: u32,
+ pub(crate) max_spdm_msg_size: u32,
+
+ pub(crate) supported_algorithms: __IncompleteArrayField<__le16>,
+}
+
+impl Validate<&mut Unvalidated<KVec<u8>>> for &mut GetCapabilitiesRsp {
+ 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::<GetCapabilitiesRsp>() {
+ let version = *(raw.get(0).ok_or(ENOMEM))?;
+ let version_1_1_len = mem::size_of::<GetCapabilitiesRsp>()
+ - mem::size_of::<__IncompleteArrayField<__le16>>()
+ - mem::size_of::<u32>()
+ - mem::size_of::<u32>();
+
+ if version == SPDM_VER_11 && raw.len() == version_1_1_len {
+ // Version 1.1 of the spec doesn't include all of the fields
+ // So let's extend the KVec with 0s so we can cast the
+ // vector to GetCapabilitiesRsp
+
+ for _i in version_1_1_len..mem::size_of::<GetCapabilitiesRsp>() {
+ raw.push(0, GFP_KERNEL)?;
+ }
+ } else {
+ return Err(EINVAL);
+ }
+ }
+
+ let ptr = raw.as_mut_ptr();
+ // CAST: `GetCapabilitiesRsp` only contains integers and has `repr(C)`.
+ let ptr = ptr.cast::<GetCapabilitiesRsp>();
+ // SAFETY: `ptr` came from a reference and the cast above is valid.
+ let rsp: &mut GetCapabilitiesRsp = unsafe { &mut *ptr };
+
+ Ok(rsp)
+ }
+}
--
2.52.0