From nobody Thu Apr 2 17:18:52 2026 Received: from mail-pg1-f172.google.com (mail-pg1-f172.google.com [209.85.215.172]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 60B3E346E41 for ; Wed, 11 Feb 2026 03:31:19 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.215.172 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1770780680; cv=none; b=lv3r64o25REbmPP+25G3S0A/4oOrSLar66ilfTuUoregaxqq/ixjLPjlrTS5lUKVlP1jZ6mgnDKqDGzs8Po/wvkJ5kajpyWotiudz0Ih2mA/vSk+MRB7ULV62VPIVgJFrn3V18qj9IqmrYS+CsOD0OARMeyNQnPeegb7I8TV//8= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1770780680; c=relaxed/simple; bh=toTNJudqEcjkoiXvBRgz/9Q2oWXvwkah3SRR3afs6KI=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=SXlgbh6+ANI9UFtE19mdpmC7GYJdad6mfV/IlH4mgmiNOQRQT46bd3Ql4hVJo8OSroRX6W87/D8wmR+89s4Ka9FFYTNm/PpAZE/SOeso2BE9YKjsPMc+uLhRV/k88qIJwZHnlGDH8bmuMR9nLTxshWD46Ob4OiA/7AJMW3PSM+E= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=XUUNuXzp; arc=none smtp.client-ip=209.85.215.172 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="XUUNuXzp" Received: by mail-pg1-f172.google.com with SMTP id 41be03b00d2f7-c636487ccaeso2189136a12.1 for ; Tue, 10 Feb 2026 19:31:19 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1770780679; x=1771385479; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=7fSuzEVVGWU561gVbAJ53yu4VEf4z/D49690CUVMVKw=; b=XUUNuXzp9NUrPSsN3S5OsIVrX4vywggMJ+Ya08MjOEvbrw58iABYnbWmxy/rFqrhup PqX5d977fftNlbb/eXLBi/AaGCzkUPyrmVSe2fUUP0h01/M+sLZNQR89WgPdpu+H67gh O2Zr2hXOZJww4sEstmienYh3cyrN6qipu44o2nFy/8O7KlzEzWxZ5IPS1SF5UiQwPe4K CIiavm3OqLR/jr9LJMkKdnulxC+ENzS0SHVE+iXvYEpDvorsDqnzvn6ahMyb5F+7OGUX 7ikc/g/88W4o8U7L1zsPw37XfN0WDdo7ljFG4MQmR+mibVZE2+R47yaFSkHLlTfOloO/ ZhCQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1770780679; x=1771385479; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to; bh=7fSuzEVVGWU561gVbAJ53yu4VEf4z/D49690CUVMVKw=; b=UZ5IJ1oAf2+JJpXoHz6d8+P8mo/HkuawIypIoACx8pc/PIos6Shq1EtAAblnSmqS8D ol1mWeY7CCtuBDJwAJ+hxuyDI3OhupSsNrHhfW+bbyyn8oOZPKGldYkzwRUHenYVzBHV sqftRGBfeEm8cuv9QaeiVox/+iCMvTz7UDo6J1YX9hU6lDpxH1c6IbXZqy5Vnhjg4fpL bvfdm5RfdSCs+a7W2dU92pGo+IjCWvbWO9aUx/zxMpMzYNndVBcpRLJpxsEcGHINyGs8 5sFKAD6UZSqerLC2r0KQ4y8qjjvNhx/O2a+CjEpiyPWZXEuPx7/8YTHHduJzBzENy3oW rYUg== X-Forwarded-Encrypted: i=1; AJvYcCWYX5xkd3JP1jJHCjQgXZN1F+Gtr8up+ux79JQqOTREP0DJFO5p6V/LdVd4bF+j2kBu/8gND6NirEVK3XA=@vger.kernel.org X-Gm-Message-State: AOJu0YytJJVDtF/EQgFK3pxwn4RNmcMezplvHm9h3B4Biuwm6CVQgcQD yGJHEN1eOjjWTzSte+u4Oe4cRgm2PSgpuRVSuWr9CR6T0CtdLDzGOPYt X-Gm-Gg: AZuq6aJ6iI0a1eQYKD/8NKzar+KeP3wRE3OVTODkpr9U4brkG7eVgXngduFvyUq6ePy rsJ3ETePR8d+eZtqqVvpoqOEDMQZOEJTChXWF4hF5MV3NVhRYczBn1hfHhXSACa2m0AKn2i5Mkr sLdu4ljmL7X5Pm4ionIHMz2tttO35KN6qk06hZGLRrTkw8mFiCcfKXsF1BMmv2K5g7UgQ03w9N3 AgKVOIv2pOoHfoUtt9FsY1ggCCzsENKm8RqH1YDFYJSUsWLA/BHGdvmr6uxzuP8HyRApEhD5IMu 4VGZA5lE73K55PXhB0uriCWnU4TYOd3HOxWX2MnE5iFsRV3dwpHlwRS2OL43TSH6qUG5bWyfrQe xsQ//mhcvG5JpYIATP/BB+I2zaEjI1jW2dT8TRm31+0XJB+5xK9qeV/r8Yljq56qjOPuJrtFYMA nrAjZMZ8L1Ekk5qAsl+z5SgtTfFbbdwQP1rlzxnnokfwSukcjhOik0 X-Received: by 2002:a05:6a21:4d17:b0:392:e5eb:efe with SMTP id adf61e73a8af0-394324b73c1mr799709637.78.1770780678809; Tue, 10 Feb 2026 19:31:18 -0800 (PST) Received: from toolbx.alistair23.me ([2403:581e:fdf9:0:6209:4521:6813:45b7]) by smtp.gmail.com with ESMTPSA id 41be03b00d2f7-c6e197d63c9sm464856a12.20.2026.02.10.19.31.12 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 10 Feb 2026 19:31:18 -0800 (PST) From: alistair23@gmail.com X-Google-Original-From: alistair.francis@wdc.com To: bhelgaas@google.com, lukas@wunner.de, rust-for-linux@vger.kernel.org, akpm@linux-foundation.org, linux-pci@vger.kernel.org, Jonathan.Cameron@huawei.com, linux-cxl@vger.kernel.org, linux-kernel@vger.kernel.org Cc: alex.gaynor@gmail.com, benno.lossin@proton.me, boqun.feng@gmail.com, a.hindborg@kernel.org, gary@garyguo.net, bjorn3_gh@protonmail.com, tmgross@umich.edu, alistair23@gmail.com, ojeda@kernel.org, wilfred.mallawa@wdc.com, aliceryhl@google.com, Alistair Francis Subject: [RFC v3 13/27] lib: rspdm: Support SPDM get_capabilities Date: Wed, 11 Feb 2026 13:29:20 +1000 Message-ID: <20260211032935.2705841-14-alistair.francis@wdc.com> X-Mailer: git-send-email 2.52.0 In-Reply-To: <20260211032935.2705841-1-alistair.francis@wdc.com> References: <20260211032935.2705841-1-alistair.francis@wdc.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" From: Alistair Francis Support the GET_CAPABILITIES SPDM command. Signed-off-by: Alistair Francis --- lib/rspdm/consts.rs | 18 ++++++++-- lib/rspdm/lib.rs | 4 +++ lib/rspdm/state.rs | 68 +++++++++++++++++++++++++++++++++++-- lib/rspdm/validator.rs | 76 +++++++++++++++++++++++++++++++++++++++++- 4 files changed, 161 insertions(+), 5 deletions(-) diff --git a/lib/rspdm/consts.rs b/lib/rspdm/consts.rs index 38f48f0863e2..a6a66e2af1b5 100644 --- a/lib/rspdm/consts.rs +++ b/lib/rspdm/consts.rs @@ -12,9 +12,7 @@ =20 /* SPDM versions supported by this implementation */ pub(crate) const SPDM_VER_10: u8 =3D 0x10; -#[allow(dead_code)] pub(crate) const SPDM_VER_11: u8 =3D 0x11; -#[allow(dead_code)] pub(crate) const SPDM_VER_12: u8 =3D 0x12; pub(crate) const SPDM_VER_13: u8 =3D 0x13; =20 @@ -132,3 +130,19 @@ fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> cor= e::fmt::Result { =20 pub(crate) const SPDM_GET_VERSION: u8 =3D 0x84; pub(crate) const SPDM_GET_VERSION_LEN: usize =3D mem::size_of::() + 255; + +pub(crate) const SPDM_GET_CAPABILITIES: u8 =3D 0xe1; +pub(crate) const SPDM_MIN_DATA_TRANSFER_SIZE: u32 =3D 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 C= MA +// (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 =3D 20; + +pub(crate) const SPDM_CERT_CAP: u32 =3D 1 << 1; +pub(crate) const SPDM_CHAL_CAP: u32 =3D 1 << 2; + +pub(crate) const SPDM_REQ_CAPS: u32 =3D SPDM_CERT_CAP | SPDM_CHAL_CAP; +pub(crate) const SPDM_RSP_MIN_CAPS: u32 =3D SPDM_CERT_CAP | SPDM_CHAL_CAP; diff --git a/lib/rspdm/lib.rs b/lib/rspdm/lib.rs index 08abcbb21247..5f6653ada59d 100644 --- a/lib/rspdm/lib.rs +++ b/lib/rspdm/lib.rs @@ -113,6 +113,10 @@ return e.to_errno() as c_int; } =20 + if let Err(e) =3D state.get_capabilities() { + return e.to_errno() as c_int; + } + 0 } =20 diff --git a/lib/rspdm/state.rs b/lib/rspdm/state.rs index 3ad53ec05044..0efad7f341cd 100644 --- a/lib/rspdm/state.rs +++ b/lib/rspdm/state.rs @@ -17,9 +17,12 @@ }; =20 use crate::consts::{ - SpdmErrorCode, SPDM_ERROR, SPDM_GET_VERSION_LEN, SPDM_MAX_VER, SPDM_MI= N_VER, SPDM_REQ, + SpdmErrorCode, SPDM_ERROR, SPDM_GET_VERSION_LEN, SPDM_MAX_VER, SPDM_MI= N_DATA_TRANSFER_SIZE, + SPDM_MIN_VER, SPDM_REQ, SPDM_RSP_MIN_CAPS, SPDM_VER_10, SPDM_VER_11, S= PDM_VER_12, +}; +use crate::validator::{ + GetCapabilitiesReq, GetCapabilitiesRsp, GetVersionReq, GetVersionRsp, = SpdmErrorRsp, SpdmHeader, }; -use crate::validator::{GetVersionReq, GetVersionRsp, SpdmErrorRsp, SpdmHea= der}; =20 /// The current SPDM session state for a device. Based on the /// C `struct spdm_state`. @@ -35,6 +38,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, @@ -46,6 +51,7 @@ pub struct SpdmState { =20 // Negotiated state pub(crate) version: u8, + pub(crate) rsp_caps: u32, } =20 impl SpdmState { @@ -65,6 +71,7 @@ pub(crate) fn new( keyring, validate, version: SPDM_MIN_VER, + rsp_caps: 0, } } =20 @@ -269,4 +276,61 @@ pub(crate) fn get_version(&mut self) -> Result<(), Err= or> { =20 Ok(()) } + + /// Obtain the supported capabilities from an SPDM session and store t= he + /// information in the `SpdmState`. + pub(crate) fn get_capabilities(&mut self) -> Result<(), Error> { + let mut request =3D GetCapabilitiesReq::default(); + request.version =3D self.version; + + let (req_sz, rsp_sz) =3D match self.version { + SPDM_VER_10 =3D> (4, 8), + SPDM_VER_11 =3D> (8, 8), + _ =3D> { + request.data_transfer_size =3D self.transport_sz.to_le(); + request.max_spdm_msg_size =3D request.data_transfer_size; + + ( + core::mem::size_of::(), + core::mem::size_of::(), + ) + } + }; + + // SAFETY: `request` is repr(C) and packed, so we can convert it t= o a slice + let request_buf =3D unsafe { from_raw_parts_mut(&mut request as *m= ut _ as *mut u8, req_sz) }; + + let mut response_vec: KVec =3D KVec::with_capacity(rsp_sz, GFP= _KERNEL)?; + // SAFETY: `request` is repr(C) and packed, so we can convert it t= o a slice + let response_buf =3D unsafe { from_raw_parts_mut(response_vec.as_m= ut_ptr(), rsp_sz) }; + + let rc =3D 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 + // then the capacity of the vector + unsafe { response_vec.inc_len(rc as usize) }; + + let response: &mut GetCapabilitiesRsp =3D + Untrusted::new_mut(&mut response_vec).validate_mut()?; + + self.rsp_caps =3D u32::from_le(response.flags); + if (self.rsp_caps & SPDM_RSP_MIN_CAPS) !=3D SPDM_RSP_MIN_CAPS { + to_result(-(bindings::EPROTONOSUPPORT as i32))?; + } + + if self.version >=3D 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 =3D self.transport_sz.min(response.data_tran= sfer_size); + } + + Ok(()) + } } diff --git a/lib/rspdm/validator.rs b/lib/rspdm/validator.rs index f69be6aa6280..cd792c46767a 100644 --- a/lib/rspdm/validator.rs +++ b/lib/rspdm/validator.rs @@ -16,7 +16,7 @@ validate::{Unvalidated, Validate}, }; =20 -use crate::consts::SPDM_GET_VERSION; +use crate::consts::{SPDM_CTEXPONENT, SPDM_GET_CAPABILITIES, SPDM_GET_VERSI= ON, SPDM_REQ_CAPS}; =20 #[repr(C, packed)] pub(crate) struct SpdmHeader { @@ -131,3 +131,77 @@ fn validate(unvalidated: &mut Unvalidated>) -= > Result 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: u16, + + 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, + 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, + pub(crate) param2: u8, + + reserved1: u8, + pub(crate) ctexponent: u8, + reserved2: u16, + + 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>> for &mut GetCapabilitiesRsp { + type Err =3D Error; + + fn validate(unvalidated: &mut Unvalidated>) -> Result { + let raw =3D unvalidated.raw_mut(); + if raw.len() < mem::size_of::() { + return Err(EINVAL); + } + + let ptr =3D raw.as_mut_ptr(); + // CAST: `GetCapabilitiesRsp` only contains integers and has `repr= (C)`. + let ptr =3D ptr.cast::(); + // SAFETY: `ptr` came from a reference and the cast above is valid. + let rsp: &mut GetCapabilitiesRsp =3D unsafe { &mut *ptr }; + + Ok(rsp) + } +} --=20 2.52.0