From nobody Sun Sep 27 00:36:28 2026 Received: from BN1PR04CU002.outbound.protection.outlook.com (mail-eastus2azon11010006.outbound.protection.outlook.com [52.101.56.6]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id DC029474271; Thu, 27 Aug 2026 14:23:00 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=fail smtp.client-ip=52.101.56.6 ARC-Seal: i=2; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1787840582; cv=fail; b=o3a7KMCUgn5hl0us5GVbbSOEosKsKXotFyD0sEBXoEvclTZJyXuMa+cndWHfsQHVS6u1jmjx465QN0hThnf0/ogd50OpSgWmvxx+RQdiIQwSl607y0ULcm8q9yenITwPWotNcU1PMM4RKjf8pdxJdSAhvRNaOHEOM6IFPPmElAg= ARC-Message-Signature: i=2; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1787840582; c=relaxed/simple; bh=Drm44CHVlquKHs5YURoto7JKX69/SzRgKb74EuWKtpk=; h=From:Date:Subject:Content-Type:Message-Id:References:In-Reply-To: To:Cc:MIME-Version; b=Brb9wSAxTNmHkhMbRQhBnOFxy5mkSlmRzSQ3VbWQBpRBOiN5HZtxGweupJeXNQAVa9Ri/9l3SV75heStv6hQq3ac++IfQouR4Eyb1Etj8fiW6fnTcWWOtAIBh1ATLhCwRRjCxVRqWpmQBilY44+PnMqVaidCvhFOrV3zRE+w0o0= ARC-Authentication-Results: i=2; smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=nvidia.com; spf=fail smtp.mailfrom=nvidia.com; dkim=pass (2048-bit key) header.d=Nvidia.com header.i=@Nvidia.com header.b=et905asx; arc=fail smtp.client-ip=52.101.56.6 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=nvidia.com Authentication-Results: smtp.subspace.kernel.org; spf=fail smtp.mailfrom=nvidia.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=Nvidia.com header.i=@Nvidia.com header.b="et905asx" ARC-Seal: i=1; a=rsa-sha256; s=arcselector10001; d=microsoft.com; cv=none; b=hJm2NVh1rrenkqXFdYpmgsnvoZ0c/VXdu5VoHQYeX+BtOHGPpalYs6RSx+kZSq5k38mU243TJDUSaK3VccU8FPfk/h5ZCpLWQvChFK6SZSQ6ZZ0DyO2ia9lC+k+VBXfsunaILyYmiLx2NY4vkxev855xPoPBLch22kgqDadinrxFkluQnE1unWdoPi2lOu3odmYMTUR/K6GguYCrK4F2vWMzsaQL/MhfM3c+Ux0EXqfuIE0eerlZC64q3IzspTJ0+XRnagpJQ4u84C+BlGe8Po8FtnfwK1NDopxtrZBG0z1iY3mBS+nhH0Bxb6MGuwMQrHt/IdDCHbiDc8YElouVnw== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=microsoft.com; s=arcselector10001; h=From:Date:Subject:Message-ID:Content-Type:MIME-Version:X-MS-Exchange-AntiSpam-MessageData-ChunkCount:X-MS-Exchange-AntiSpam-MessageData-0:X-MS-Exchange-AntiSpam-MessageData-1; bh=5YyZsoV/mhRF+IKvx6hVeJJ7wG2dYU0bTgI8klUQ/EM=; b=IQA2ZejzMLF6LHkvxUeJatzv4gwnkFzXcuB+T1BJLYsRdC31ASKGer5fvtRCDZ3AaWPfZi5hDLH/RHKQIMhw2TUdwWLwD6b3cnu6eFf6x9CuGl81HZ072eY8GeHC/IgD1JuGCBzV3ppekERKAde9vz6uIsGfDI/aEULoMPKlFgGggpO6VQN3OKcQCYXkN5IFvWgv/WnI2l9/We+hqMZu2Xe4op00r7P7frtNAzxa+8ScIhmDfyEugYnfwYqNagB9y8Sw6fGApQQ3mjSeW1B/Tw5ZZ17japli0KODck6Tvx7eyUFqBHXxPhPdxgjD9zL7BmS+aH/OaxzM88oer0BXFg== ARC-Authentication-Results: i=1; mx.microsoft.com 1; spf=pass smtp.mailfrom=nvidia.com; dmarc=pass action=none header.from=nvidia.com; dkim=pass header.d=nvidia.com; arc=none DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=Nvidia.com; s=selector2; h=From:Date:Subject:Message-ID:Content-Type:MIME-Version:X-MS-Exchange-SenderADCheck; bh=5YyZsoV/mhRF+IKvx6hVeJJ7wG2dYU0bTgI8klUQ/EM=; b=et905asxkoXP650AoiJccsmABs50qjVmBdDZX9iZJpQYUOII0gYHjgrOB3bOrYbp/0+a6ZrNRE4BT9E1LmT+KUXt8/6kYO09vnkEzTLXrQsedquRyY4/2748EUPznnIy646sqPP1cp0pVcdFxVthk8NAc+C4A4WjdPKhFUKGLqrIPGjk7uyiQ7T75SkTd1S7A0nYAtzZi1xhndUhX9pNTPPQRq8fyrGWll84igNgBfjiqGJp2nSlS50HMcLFUPdSxMK8zZxqn2SubkA1ybmYsAnsDiieooMiLhJG0gs4h49v1ArKDmyINu8T97lH+WEIwBF6wbF8t7Rk6uOYIj0HaA== Authentication-Results: dkim=none (message not signed) header.d=none;dmarc=none action=none header.from=nvidia.com; Received: from DS0PR12MB6413.namprd12.prod.outlook.com (2603:10b6:8:ce::10) by LVUPR12MB999161.namprd12.prod.outlook.com (2603:10b6:408:3a2::8) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.21.360.10; Thu, 27 Aug 2026 14:22:51 +0000 Received: from DS0PR12MB6413.namprd12.prod.outlook.com ([fe80::e82a:6673:4142:37fa]) by DS0PR12MB6413.namprd12.prod.outlook.com ([fe80::e82a:6673:4142:37fa%5]) with mapi id 15.21.0360.008; Thu, 27 Aug 2026 14:22:51 +0000 From: Eliot Courtney Date: Thu, 27 Aug 2026 23:12:50 +0900 Subject: [PATCH v2 1/8] rust: alloc: add Vec::try_push_init Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260827-b4-nvkv-v2-1-0de9d5c8658c@nvidia.com> References: <20260827-b4-nvkv-v2-0-0de9d5c8658c@nvidia.com> In-Reply-To: <20260827-b4-nvkv-v2-0-0de9d5c8658c@nvidia.com> To: Danilo Krummrich , Lorenzo Stoakes , Vlastimil Babka , "Liam R. Howlett" , Uladzislau Rezki , Miguel Ojeda , Boqun Feng , Gary Guo , =?utf-8?q?Bj=C3=B6rn_Roy_Baron?= , Benno Lossin , Andreas Hindborg , Alice Ryhl , Trevor Gross , Daniel Almeida , Tamir Duberstein , Alexandre Courbot , =?utf-8?q?Onur_=C3=96zkan?= , David Airlie , Simona Vetter Cc: John Hubbard , Alistair Popple , Timur Tabi , rust-for-linux@vger.kernel.org, linux-kernel@vger.kernel.org, nova-gpu@lists.linux.dev, dri-devel@lists.freedesktop.org, Eliot Courtney X-Mailer: b4 0.15.2 X-ClientProxiedBy: TY4P301CA0021.JPNP301.PROD.OUTLOOK.COM (2603:1096:405:2b1::8) To DS0PR12MB6413.namprd12.prod.outlook.com (2603:10b6:8:ce::10) Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 X-MS-PublicTrafficType: Email X-MS-TrafficTypeDiagnostic: DS0PR12MB6413:EE_|LVUPR12MB999161:EE_ X-MS-Office365-Filtering-Correlation-Id: 60ad357f-1be3-4df9-d714-08df0446ad8c X-MS-Exchange-SenderADCheck: 1 X-MS-Exchange-AntiSpam-Relay: 0 X-Microsoft-Antispam: BCL:0;ARA:13230040|23010399003|10070799003|366016|376014|7416014|1800799024|10067099003|6133799003|22082099003|18002099003|11063799006|56012099006|921020; X-Microsoft-Antispam-Message-Info: B7K8UBmxVRVKr1dFQYcrsU2HxZxjFe/wZJIes2iwUcjUPCeWe/o4/gmd2va73FN4qT2zLi6IS4pjLowH+Vm7BFxFOE+tjwooGkrAtt4MwabGA6Rsj979Ojmg42Nz09rO4XWxlHYVLQ72R3U9J3WOwJ9TsyeG15L2Yw2Q7YtBgYMU8bK52tJB0JpCi9DxmoXOCMf/hwd1w3wCefBRF/vAkLwUzi/I4gb+eM+tRKsMQHDRz88va2UzBMLd6u4Xb/16tEuD44i25wcBfF1zZSzfjy3yVFNJOtaGFaIEToPf4yC9ccXzIvDGMHMejzSsPz/b92pNUgxfMaAL+/z9QBQrRstjbId+c7HCzaQPnm9+laQSRljwDMDJ1g4y1/pEDw6yEIx7Wa+ppZvaC52EHoYZcQCQ/kowBdyGWBT/RxXrfXFbroOEC+FlBAMS6PJXZKdDYaARR46EFJZjxOywoTLNkkyeXwXiIF1bDJVw+liAfvT6o7NoodfrViT12TsMOQpxPiedJUTawSDDEu72kfIm2B2G80NGcuPg+AOAAQOGEyD66EC9R71fFwBCchs8cHjwqXwC/7buxU+zGvbCemVFVurvSyKcDQzqgw862RkPo/prCGVzglNT47W6FWapL2i+OSF4roqBxfqS8Rqaxxgf3Q+N+cUVYaXbk2puxtGRJA+evcoEeBUUlzjUfbzvZmb/eUCx+ZtFTrR/kuRbIqKQAQ== X-Forefront-Antispam-Report: CIP:255.255.255.255;CTRY:;LANG:en;SCL:1;SRV:;IPV:NLI;SFV:NSPM;H:DS0PR12MB6413.namprd12.prod.outlook.com;PTR:;CAT:NONE;SFS:(13230040)(23010399003)(10070799003)(366016)(376014)(7416014)(1800799024)(10067099003)(6133799003)(22082099003)(18002099003)(11063799006)(56012099006)(921020);DIR:OUT;SFP:1101; X-MS-Exchange-AntiSpam-MessageData-ChunkCount: 2 X-MS-Exchange-AntiSpam-MessageData-0: =?utf-8?B?RVQ5dmdla2Zjd3VrOHhBaEc0amwrd1dQVzJjRjFwWkoyL1JRS0xpUis3d1Nt?= =?utf-8?B?Yjd0Z1YwVzQ0U0FKek45djltSVpYcDcvTjVrUFRUbDJVcHVTQStNMGRqRStC?= =?utf-8?B?bTltcFJEQXhNdUorWmVjUTZZTnlJbVV4NXhzemFBWkVYejJNL0pqZUc2STlK?= =?utf-8?B?N3FZaDl1NkRuRmNqY3hxbGdORVJ0MXRKSC82YXd1OWY5UGhveFVpSGZRYW4r?= =?utf-8?B?WFBIMDcxT3ljSkpGdUlaVWlvYm1pY1VFNFZzRG5EdHRCMTRoN3NyYTJtdS9i?= =?utf-8?B?WWUzSGM2THhRQS81Z2pVNzJ4VmhlRWI0RlBqb0pTb2xlandxakN3SnBqRWpM?= =?utf-8?B?aFF6alRZek16eXF1SHl2Z3JOc2xUbCtSTGkyOEl2NmZJYmJZWHU0eXU5MGdG?= =?utf-8?B?Y0VFQ09MNHRvL0tXNi9pd0FjTUt4MCtqSUR1SitneWtHUjdMTnl3dXBRcUJ2?= =?utf-8?B?N1lFdFdqVmVyeE9pc0wveEdSSTk1ZERpMGdPOGJORVNxYnZtVTBFRnc3b0xB?= =?utf-8?B?dW1NSlVJN3BaamJVdytQYjUvNW9mN00zWjNRYkZpdEF1VDFSQllqYnE4bWxL?= =?utf-8?B?MGxEb3Z5Nllhb0NQYWEzdG1pS0NKRkdUWGVSSUNMSEFBcUZPTVRxdGRpUFRS?= =?utf-8?B?d2FsQjVVSEtIT1lKS0xxSHpKTmVmc1lPTEloU0FBK0F0dmtUWkVqTE5RK0lJ?= =?utf-8?B?Mk93V1kyaTE0WTgrTllraGx6czRpd25FbmFyanFwb0t5WGNndkpUQWpvTVJJ?= =?utf-8?B?OTZvRW9pY1BhMmR2NDlCQUlYWXc2ZnRNdjlGWGdaMnJ0ZU1tNE5rcERIcEVi?= =?utf-8?B?d29hWWJheUhaU0h0U1hZNXJKSXFobUJWK1ZuQXBvMFl4UDJMdDBET1JrNWVy?= =?utf-8?B?aGVrWkxNd3NHdEdGNGxrSkxCalJIalg0NTk0ZWdaZHJoamFHWVRTZmRsS0tE?= =?utf-8?B?bXJJUXAzZXFKKzllL242NDVkTVVVNDF0Mkc0dng1c0ZkcTNKU0VGOFFBbVp4?= =?utf-8?B?MTV6NEswYkJRUENGbm1iVmo1bVBkbHpXbnRDU3dsY095NlNiL1FzOFk4RC8y?= =?utf-8?B?Y2xUa1ZqeHozMDRhY1FaUHFrd2Z1S0lETkF3NHozY2hsbnhtYnZIUndhZUdp?= =?utf-8?B?MHFOb1J6UHRlQk55Vy9XZk0wa0JmSDhiV1Y0aUVyb2dhRjJtVzY4azVwNzY3?= =?utf-8?B?VU8yWU5ESUg3d2FzVzFuUUNnM0VQek5IYVBWWjlDNjhVcmZhOUlnaDMvQy9m?= =?utf-8?B?U3FnUVl4VkdMbVVMV3A0SVZzY3R6Z2lLZmlpMFI5MGU5NFAzMlptelEvdzYw?= =?utf-8?B?eDdzd3BHWUl1bnJxb2FVQVFZSEVxcUFZQmhnN1dhUXhPNW9qTkNueUJBR012?= =?utf-8?B?U3huUmRzU1JUaW4xdUlKL0lSUi9NaDZnOEYzUjZraHJ4UnFUMDVkMk5xYnRC?= =?utf-8?B?eXFGMmFGTWhlejFMWjhlMGpRTzcwL015aDA2bEdHLzNndnFpRUNuR0k4Y2Uy?= =?utf-8?B?SE4ySUtFeWVyb2daZThIKzhvU01NYkVYNzVWaHl2dWIvdFY4UWRsMWNWUkps?= =?utf-8?B?VDcwM1VPQ2V2L3lFSExuVWlSN2Fic2Z5QVJXb2FhdXhRUVFXVFFlQlJkK3RP?= =?utf-8?B?MWhQNzNuaHJYbEdCNUc4WEUvK0s4MnliS2tnSmhNSlAxeWZjOFg3cXgzOFpt?= =?utf-8?B?ejNnYnh3ejhQcE9kRXJTbWJzK1BBWGdQbSs0SGNsQkVTeXQ2U0hvU25BaEZs?= =?utf-8?B?aktTMDVqWVZoTTZmT2paUFhGKzlEaHpyUUZCWStSbHVjcE8vU0FBNnJqQzRO?= =?utf-8?B?WUg3NzVwSWR1ZFRpcVpNV2dNT0h2cUxBS0JxcFFXVUhLdU53ZzJVT0J0SW5X?= =?utf-8?B?ZDYwcmdBNFNrei9oNDd2NDFnVnBoa21ILytsV3NtVXRXVU5LWU1iSzZiUjN4?= =?utf-8?B?YnZ0YTRqS2RxQkpJcHFTOENhTlpLRy9ZU1hzTFlEWHd2ZTB5Zyt2eHFGNWRQ?= =?utf-8?B?ZE5ibUhFanVEZTNOZ3ZiUFRQL0k1OVljeWJGMExycjZ1U0ozTjhSZkV3VW0x?= =?utf-8?B?UGF4V1BNclVhZG1GWmhmbmU5Y2R2Z3drMzdJcmhjT1R2YlNZd2syRllRYkhX?= =?utf-8?B?YTU3R3hzZmJSR2V4QURjNzEwazZZNjQ1U0JwOU01OVBOZUVJd09RQUVWNUpn?= =?utf-8?B?Q0gxaHkwU1ZaeEIxanAyUEh6U0NyZHBLNFFOZEpTWjZET1BtcEZUVDZpVTdt?= =?utf-8?B?SXdVTTZrTnVFVmYvMzhjZmYvejZIeVMzYUZvT0FYdjc5VkNtaDhiQUFrSmk5?= =?utf-8?B?cHNOZHZFSXl6RzZRSEZLYU5qY2lvS3FESDBGS3hjRWVWYk5BdEpDUGZzTmgr?= =?utf-8?Q?a4YsNrs47+/BZMkKgLnPN9qQqnBfUS7pdVGeNhfhVUU4m?= X-MS-Exchange-AntiSpam-MessageData-1: VajJF1I7lGsPoA== X-OriginatorOrg: Nvidia.com X-MS-Exchange-CrossTenant-Network-Message-Id: 60ad357f-1be3-4df9-d714-08df0446ad8c X-MS-Exchange-CrossTenant-AuthSource: DS0PR12MB6413.namprd12.prod.outlook.com X-MS-Exchange-CrossTenant-AuthAs: Internal X-MS-Exchange-CrossTenant-OriginalArrivalTime: 27 Aug 2026 14:22:51.7285 (UTC) X-MS-Exchange-CrossTenant-FromEntityHeader: Hosted X-MS-Exchange-CrossTenant-Id: 43083d15-7273-40c1-b7db-39efd9ccc17a X-MS-Exchange-CrossTenant-MailboxType: HOSTED X-MS-Exchange-CrossTenant-UserPrincipalName: LwtU8q8qwht87x0wwkQNf19V87sXbXke6Extwt/TIWLZp8xYkOsYhdkzMhjnYotZirage8Z/8aUBdPgn8g22MQ== X-MS-Exchange-Transport-CrossTenantHeadersStamped: LVUPR12MB999161 Add `Vec::try_push_init` for fallible initializers (`impl Init`) and a new sum error type `PushInitError` that it returns. If allocation fails, it hands back the original initializer. A From impl for `Error` lets callers decay the `PushInitError` to a regular Error if they want. Signed-off-by: Eliot Courtney --- rust/kernel/alloc/kvec.rs | 56 ++++++++++++++++++++++++++++++++++++= ++-- rust/kernel/alloc/kvec/errors.rs | 30 +++++++++++++++++++++ 2 files changed, 84 insertions(+), 2 deletions(-) diff --git a/rust/kernel/alloc/kvec.rs b/rust/kernel/alloc/kvec.rs index c7546b9da4fa..fe86530624c1 100644 --- a/rust/kernel/alloc/kvec.rs +++ b/rust/kernel/alloc/kvec.rs @@ -52,10 +52,18 @@ }, // }; =20 -use pin_init::Zeroable; +use pin_init::{ + Init, + Zeroable, // +}; =20 mod errors; -pub use self::errors::{InsertError, PushError, RemoveError}; +pub use self::errors::{ + InsertError, + PushError, + PushInitError, + RemoveError, // +}; =20 /// Create a [`KVec`] containing the arguments. /// @@ -359,6 +367,49 @@ pub fn push(&mut self, v: T, flags: Flags) -> Result<(= ), AllocError> { Ok(()) } =20 + /// Appends an element to the back of the [`Vec`] instance by initiali= zing it in place. + /// + /// Unlike [`Vec::push`], the initializer may be fallible. If the allo= cation fails, the + /// original initializer `init` is handed back in [`PushInitError::All= ocError`]. If the + /// initializer itself fails, its error is returned in [`PushInitError= ::InitError`]. + /// + /// # Examples + /// + /// ``` + /// struct Element { + /// buf: KVec, + /// } + /// + /// impl Element { + /// fn new() -> impl Init { + /// try_init!(Element { + /// buf: KVec::with_capacity(16, GFP_KERNEL)?, + /// }? Error) + /// } + /// } + /// + /// let mut v: KVec =3D KVec::new(); + /// v.try_push_init(Element::new(), GFP_KERNEL)?; + /// assert!(v[0].buf.is_empty()); + /// # Ok::<(), Error>(()) + /// ``` + pub fn try_push_init(&mut self, init: I, flags: Flags) -> Result= <(), PushInitError> + where + I: Init, + { + if self.reserve(1, flags).is_err() { + return Err(PushInitError::AllocError(init)); + } + // SAFETY: The call to `reserve` was successful, so there is at le= ast one spare slot. + unsafe { init.__init(self.spare_capacity_mut().as_mut_ptr().cast::= ()) } + .map_err(PushInitError::InitError)?; + // SAFETY: The call to `__init` returned `Ok`, so the first spare = slot now holds an + // initialized `T`. The new length does not exceed the capacity be= cause `reserve` ensured + // the capacity is greater than the length by at least one. + unsafe { self.inc_len(1) }; + Ok(()) + } + /// Appends an element to the back of the [`Vec`] instance without rea= llocating. /// /// Fails if the vector does not have capacity for the new element. @@ -1174,6 +1225,7 @@ fn eq(&self, other: &$rhs) -> bool { self[..] =3D=3D = other[..] } )* } } +pub(super) use impl_slice_eq; =20 impl_slice_eq! { [A1: Allocator, A2: Allocator] Vec, Vec, diff --git a/rust/kernel/alloc/kvec/errors.rs b/rust/kernel/alloc/kvec/erro= rs.rs index aaca6446516a..4e4be9a46d83 100644 --- a/rust/kernel/alloc/kvec/errors.rs +++ b/rust/kernel/alloc/kvec/errors.rs @@ -25,6 +25,36 @@ fn from(_: PushError) -> Error { } } =20 +/// Error type for [`Vec::try_push_init`]. +pub enum PushInitError { + /// The allocation failed. Hand the initializer back. + AllocError(I), + /// The initializer failed. + InitError(E), +} + +impl fmt::Debug for PushInitError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + PushInitError::AllocError(_) =3D> write!(f, "Failed to allocat= e"), + PushInitError::InitError(_) =3D> write!(f, "Initializer failed= "), + } + } +} + +impl From> for Error +where + Error: From, +{ + #[inline] + fn from(e: PushInitError) -> Error { + match e { + PushInitError::AllocError(_) =3D> ENOMEM, + PushInitError::InitError(e) =3D> Error::from(e), + } + } +} + /// Error type for [`Vec::remove`]. pub struct RemoveError; =20 --=20 2.55.0 From nobody Sun Sep 27 00:36:28 2026 Received: from BN1PR04CU002.outbound.protection.outlook.com (mail-eastus2azon11010006.outbound.protection.outlook.com [52.101.56.6]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 7EDEA479880; Thu, 27 Aug 2026 14:23:02 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=fail smtp.client-ip=52.101.56.6 ARC-Seal: i=2; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1787840584; cv=fail; b=okutiQKCLDTG1T0zO9PUT01JzZLX/fflHH+edmBNT7xvDQk3Nf0/1csfGZhc4/f420syunWKTu5h/rpeN3eLPEmGM3WVy/C7OzF7p46GWP3hri+ZzOXL1ORwVuk+53FJg555chUPFbCAsoCFsvduoyd0o5aBddgouDDkBl9XEZU= ARC-Message-Signature: i=2; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1787840584; c=relaxed/simple; bh=dyzkbzeVNTOrnEgzFmuGEOECLzEyMyJhSVvWXhSZR+o=; h=From:Date:Subject:Content-Type:Message-Id:References:In-Reply-To: To:Cc:MIME-Version; b=Fiuoc5ZIG93vQiSCK+yqxa+ghpeSFdS08qN/8LiSk96k6niqLk6ZMO4ZogWBcj9MZRwYw6LKD5kTtklPHcZwMIZG8yGauDi0rBhQMOgaxFdrapnYjxUyMTUArfXgYtYZR5lCZ1O6QSC7kwqpjn4SlJmY5/Kx2HPUEjJxQPeqlf4= ARC-Authentication-Results: i=2; smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=nvidia.com; spf=fail smtp.mailfrom=nvidia.com; dkim=pass (2048-bit key) header.d=Nvidia.com header.i=@Nvidia.com header.b=fFZYLk1k; arc=fail smtp.client-ip=52.101.56.6 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=nvidia.com Authentication-Results: smtp.subspace.kernel.org; spf=fail smtp.mailfrom=nvidia.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=Nvidia.com header.i=@Nvidia.com header.b="fFZYLk1k" ARC-Seal: i=1; a=rsa-sha256; s=arcselector10001; d=microsoft.com; cv=none; b=SSYjRNxQdmzCX2iDkm5NaqM7kSTtMrlbv26TDwAI13JZh+cEUISL016QdIl3zGmxeuH2uD5n00PuGXUXlmOnUkPBECS4uDW18l/Avjk5WnJzatdrtx3dMI8QCb9RcReAN7qc4YjnidXMBz3ykUCVV16EhObZ0jOO2XtuVLJ/56mZUIZcQmBQhq9TEreSeGbyOKkklGeujhIPPgjndxM8jwhp7WdAcB+VKQVmvzQVcgvXX4FyYChm6WoNMfpeKsH0ysLBXeECGyLjWy2PbRE1CgqFboM0MWRLIcaQ+iTx5U7kcUcbsnXp5XyIXiH/SG1ZZGNuEzAEndU/JswupRzEtg== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=microsoft.com; s=arcselector10001; h=From:Date:Subject:Message-ID:Content-Type:MIME-Version:X-MS-Exchange-AntiSpam-MessageData-ChunkCount:X-MS-Exchange-AntiSpam-MessageData-0:X-MS-Exchange-AntiSpam-MessageData-1; bh=KC8ueQBrSt24otLk3wNlkGNbN7djMZYKMzV7Ln5zC6k=; b=uaMGJtWkq9cpyhQTmihnGqaKOTuTU50k0Qb8Wyy4Bb4E+ZEEWSnpl5S2xSyQz4Tp5tGwgV2vI10Uh0qtIIkl77x7nHHbiJ9E6KRgVlzwuHt1X2CGQmdvD/n1qoyrzPTC8yGNraPMDGRtJ2JGfKi0UQTW9NDa7dwdwESLjB5tQzrAW4EcQ78h19HV/8nR/izs1tKQ6ONGEzZ5eHibFub7PPKuqGVCotzM0lq1WKqVQ0UvSIaMjpt4u0iqSvfUzLc4eDf680vka41s/sPl/GAl/6uBfFJ8GA5sSHcIrg/uorFFjPSOTZSwgSbop8Gtrs9Urb/b+KGBsPUmARHmk4KipQ== ARC-Authentication-Results: i=1; mx.microsoft.com 1; spf=pass smtp.mailfrom=nvidia.com; dmarc=pass action=none header.from=nvidia.com; dkim=pass header.d=nvidia.com; arc=none DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=Nvidia.com; s=selector2; h=From:Date:Subject:Message-ID:Content-Type:MIME-Version:X-MS-Exchange-SenderADCheck; bh=KC8ueQBrSt24otLk3wNlkGNbN7djMZYKMzV7Ln5zC6k=; b=fFZYLk1kzT/NJ6y301uao0+PJTs3gSPN+XbeMHX7WVtlDYZ+vsuEFbhYrazaITI1VvShDk2c+YBn8DENYJmQBxxSBShnfZOA3PCgezk3CKcyUKugHJseAJyPp+oMwQaFeLh75amhhH/8iCnMsBDOGjujjV7t2lqG6L6siLGzKJr6YWQXK0ji37tmxlR/6UfTXT/ufM36+EG6ghjrQ0j+K7hTdUlKPU0WhLeBU5Ff2if7xlEyMH0XHz+5OYGixn2q+e63Ff8990DVOk9bBZF2CJjlcFD6WTViggTCB7zyMz4TDLjd4bv7/XkVrlinhJLOcOTMI08qSMI+hj6cHymP1Q== Authentication-Results: dkim=none (message not signed) header.d=none;dmarc=none action=none header.from=nvidia.com; Received: from DS0PR12MB6413.namprd12.prod.outlook.com (2603:10b6:8:ce::10) by LVUPR12MB999161.namprd12.prod.outlook.com (2603:10b6:408:3a2::8) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.21.360.10; Thu, 27 Aug 2026 14:22:55 +0000 Received: from DS0PR12MB6413.namprd12.prod.outlook.com ([fe80::e82a:6673:4142:37fa]) by DS0PR12MB6413.namprd12.prod.outlook.com ([fe80::e82a:6673:4142:37fa%5]) with mapi id 15.21.0360.008; Thu, 27 Aug 2026 14:22:55 +0000 From: Eliot Courtney Date: Thu, 27 Aug 2026 23:12:51 +0900 Subject: [PATCH v2 2/8] rust: alloc: add Vec::push_init Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260827-b4-nvkv-v2-2-0de9d5c8658c@nvidia.com> References: <20260827-b4-nvkv-v2-0-0de9d5c8658c@nvidia.com> In-Reply-To: <20260827-b4-nvkv-v2-0-0de9d5c8658c@nvidia.com> To: Danilo Krummrich , Lorenzo Stoakes , Vlastimil Babka , "Liam R. Howlett" , Uladzislau Rezki , Miguel Ojeda , Boqun Feng , Gary Guo , =?utf-8?q?Bj=C3=B6rn_Roy_Baron?= , Benno Lossin , Andreas Hindborg , Alice Ryhl , Trevor Gross , Daniel Almeida , Tamir Duberstein , Alexandre Courbot , =?utf-8?q?Onur_=C3=96zkan?= , David Airlie , Simona Vetter Cc: John Hubbard , Alistair Popple , Timur Tabi , rust-for-linux@vger.kernel.org, linux-kernel@vger.kernel.org, nova-gpu@lists.linux.dev, dri-devel@lists.freedesktop.org, Eliot Courtney X-Mailer: b4 0.15.2 X-ClientProxiedBy: TYCP301CA0019.JPNP301.PROD.OUTLOOK.COM (2603:1096:400:381::6) To DS0PR12MB6413.namprd12.prod.outlook.com (2603:10b6:8:ce::10) Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 X-MS-PublicTrafficType: Email X-MS-TrafficTypeDiagnostic: DS0PR12MB6413:EE_|LVUPR12MB999161:EE_ X-MS-Office365-Filtering-Correlation-Id: 9ac8f861-706a-41a2-5d2e-08df0446afaa X-MS-Exchange-SenderADCheck: 1 X-MS-Exchange-AntiSpam-Relay: 0 X-Microsoft-Antispam: BCL:0;ARA:13230040|23010399003|10070799003|366016|376014|7416014|1800799024|10067099003|22082099003|18002099003|11063799006|56012099006|921020; X-Microsoft-Antispam-Message-Info: 5NnFYxNo1vwS4xPyMQiRo7wOw+BgQpOERii0RoPjP3pOtt1x5o2IQ3exgItyjGxuSfnhf4bd6VEsFXMbVU6owUzn4Ev/OdcFYuCUzJABRBjev2kBmCZFwtMmulqR/XvPNI2xgXyoCNQU+7tsAhXPAf3w7i/PFBRcRDoLJhTZjHofCp99Ae8atDr9C2op/yRLWxIWyh5urzqFKn5Gh9K7HwXgMgjRInv3d2LOM7iQKke2mUgw4IJ3n4gY8eV/ubYoTrKBqdHCq04Z/8h0OO8HrZqHXENY69kKSzG+osUnmKybdrpmY/OPA8VxKBEOwtPPrWXcs1LtWuInedo+Zwxl0j6ChroFqiCJKPEJBNeZcErrPgPgHjIiRSb+vvM4YFJCwAVpSigUEhshHV+1rukojmY4onvX64KTwA8RseNQh53HerfBw7EAoFoLEEPAaAd8bxliqxe/3iwF7sOcoUSv83LdDue7VQCUR4op+pvdccTQgkKK/9r1eq1H8FBh1DWKDhUaiEM0l5aY7oIWZ8snynfWuBTYvQM1y9nWuJWHE9gp8eAi7lAuQf4uN4mwDJH9rWit8d8V1BY6ys+/HXq4hx5NWF4S3J8JNeYrsiwxQy5ZMRamT62k1nJjX6TpvBxBkcPSnPCt7N5PClgo1r3Q4fLnhtRVOeI8Y7dsurqLx0nNRGLmc+GdtpLykGE+D6O2KtatNKuJDk74/YUqau6tjQ== X-Forefront-Antispam-Report: CIP:255.255.255.255;CTRY:;LANG:en;SCL:1;SRV:;IPV:NLI;SFV:NSPM;H:DS0PR12MB6413.namprd12.prod.outlook.com;PTR:;CAT:NONE;SFS:(13230040)(23010399003)(10070799003)(366016)(376014)(7416014)(1800799024)(10067099003)(22082099003)(18002099003)(11063799006)(56012099006)(921020);DIR:OUT;SFP:1101; X-MS-Exchange-AntiSpam-MessageData-ChunkCount: 2 X-MS-Exchange-AntiSpam-MessageData-0: =?utf-8?B?SmtLVkFZNDNzcHlPRndNd01CNDJvODl2ZmNDRlpNbUdjeVBIckl3eEhBUEVL?= =?utf-8?B?TU9FNkYxOXdSWTFNbWNpOGtScFZKQmNOckN1bnlTN254MFh1UHQzSVREY2Uv?= =?utf-8?B?b251ZjdIWWtJMmlhK1Jycm9IVUw5aDlCU2NLK2lHV2FaT0R1UHZvMWs4b2VX?= =?utf-8?B?SmZWTTJUQk81ajYyZ0g3WDZBdmhMcXhXT2t2bGJaVmRiK1RraDJPdW44aHpn?= =?utf-8?B?R09zRHlLQlBFdDZxVXFrdGdoOU9xZ3lmQ2ZwYU1CQVV4ZStPZGsrYTRFQTdK?= =?utf-8?B?akk2b3ZtS2plbDZicktOd3d0b0ZKVlRGRjcxdEN4VFFLRlFEcmJwaXNIN2V3?= =?utf-8?B?alVkZDM4Z2t4TUFZY01pcVlTMEdFbVJEZlREenVXcGZ1RWlWUk5zNC9pbnNw?= =?utf-8?B?dUk1Vnc1UGZUR0E4bXFvMDhtc0x6RWlWQ01zYWk5c29ndnpHUFpyYjlJSlM4?= =?utf-8?B?M00rcWJ4WjhHcW1hTVR0OFM3K0UwNno4ZWIrY3ZvYS9leTVBdXZMMzRJME9u?= =?utf-8?B?YnpOVHhFam1kVzV0UTg1V1Aya0xid3BwcGllQ0xOMEtuUVB5cWpiVWs1VmYv?= =?utf-8?B?eU9vY05aeTdIUVVIUTV0RVQrZlpxOTBBVG9jdENyQXVUeUg3S1lta0tGRVdL?= =?utf-8?B?dklRenFBRDB3MDZUeXQyY295M1EvRHdlWlQrZi94NzMySWlkSkppVTI4cC9z?= =?utf-8?B?NmlueHl2cXBDamVUMGliMTRiT0ZBQXg4QzYyR3hGZmZuTkxrSlY5ai9tUXMv?= =?utf-8?B?dG9JVjBuOWJ0YzFMOXlPWkU0SFFrMlJZL3ZVK2hnbUtPajBKbDRuU29sRHBO?= =?utf-8?B?eHFaeUJSbGpGbFd1MVAwdE9VMnVPTUErZU50MlNJU1AwU3BZM0gzOVpOS2J3?= =?utf-8?B?MlNidzl0cUo5MzdSWnZqSHJLaE1oMDN6M2VQSVFMZTFTZWtYOHkrWVFrRzht?= =?utf-8?B?Vjk5eDJzakwzbktlSkU4Rit3VWszNWo4Y3Q1UWRqUzVUVVlRcW11emVRditW?= =?utf-8?B?SVR3M3NlMlQwSEhNR2pkNENPUGZLQ0JMZXFQeHFvL21QQzcySStOZU9hNXRC?= =?utf-8?B?UWV3cFp0WXViS1ZBcm1oMVNhWEpKTkE3TVFDZGNhUzBzUU1LVmdkazl4SUVF?= =?utf-8?B?UndFQlFqc3V2clhkT09QdjZFVEg5M1VucEdsU1JaTzFvR2hiZWZmZFpJZ3ZH?= =?utf-8?B?T1kwRFlNTjd0aThqVUlDTExuNmJacTZMNS9SYjdaZk9oelpwdVhGTHp1SUpH?= =?utf-8?B?a3dYaUxVRU9pdStYT20zSlNtUDhzNHFXS0dqb0JmQWRSc0dHR1YvTCtlMFE4?= =?utf-8?B?Wk9PcDdGZlBBVzZBWWs5aVFDcmFlelNTbUtZdEdxZ29vSHhoWjExUVdUUm1K?= =?utf-8?B?OXBudVl6SFE5eWYvc0NDZ1JzUWM4RWkreXNyaXZjYXgvb1JBd1Y3ZTRuSGhJ?= =?utf-8?B?djFWU05NV1QyR3FXbEVVdVh6QlpoOTVpU0NSOG5BcTR5c2g1d2NvL05zQ21K?= =?utf-8?B?d3VoOE9BNFVTZnJUc0MwUnIwcGVCVkx6bkNzcTB3TVVwcTQ4dkhaaXBrK05z?= =?utf-8?B?YURVeW5WN2pka3ZtWDBzL01rM3pXakFHeW9RQVZmbUkzaGZsN3NmNW9iOXBr?= =?utf-8?B?TnVzeFpjY1A3ZnFFMkdCajQrekVXNlpOaEMrdG9TSDdnTlhGclBKbFIxQ2Jy?= =?utf-8?B?VDhiZjJ4UWtpeWNvZGxheFh3OUV6ell0V01WOHJnTjJLMG9NL2FHaFF0VHhX?= =?utf-8?B?VkxGMkhSUU9hL2pDcG1lQ2lzOHBteFZ1aENHOExxV2VaSUMwSXVpeWFDTzVy?= =?utf-8?B?YUxYd0VZQVYzT3lyQzJkMHdJZ29wZk5VN2U1bFZuU1hQUmgva0ZwbEZTUGNI?= =?utf-8?B?YWVsc1dablJOa3JSank3QlRRV1RxMU9nN3VFa0R2aWIrb1lUOEZuODB2bXVm?= =?utf-8?B?TWZHL2p3RThZSWxzaUdZZ2xnQnB6S2Y5STJmZCtURkl4UGRsbW5yaFBHNm9a?= =?utf-8?B?VDZjbFgzbzdDNzNKTktpSVUyUXZHZHZLVkNXeDluNElSc0hSWEoxblZPUFBu?= =?utf-8?B?SkNkZ2FpelYzSmlFeWRkeXBtNmJiV01OOEQyZHRObG1qY0c0SldnRC9RbThr?= =?utf-8?B?bjhBei9qaUVQN04ySmRtNTFMd1BObXFGamEycDF6RUt0TDhhTGVFNDhvcXJV?= =?utf-8?B?MUlkSEVYSitBMTdXWXRXc3FQTEJBdE1nODRzVTg5SXdjZUtOU1lsOWFQa29O?= =?utf-8?B?RCtYMHNONm1xZHhMcnRBZkhUckRqeHlPY2Ezb2MxN0twamVhOXFuaTRmR1Vx?= =?utf-8?B?VjVRU3NzVEJGZDNRMGlRaU4yRlFqam9yNHc2K1lEL3pMeCtFZGpFQml0SXRx?= =?utf-8?Q?yynqHFKr2Es0PHmlP0q+PPCfw8CuGFiYm5SPSbYeJoxUL?= X-MS-Exchange-AntiSpam-MessageData-1: /y70/tyH1XjxNg== X-OriginatorOrg: Nvidia.com X-MS-Exchange-CrossTenant-Network-Message-Id: 9ac8f861-706a-41a2-5d2e-08df0446afaa X-MS-Exchange-CrossTenant-AuthSource: DS0PR12MB6413.namprd12.prod.outlook.com X-MS-Exchange-CrossTenant-AuthAs: Internal X-MS-Exchange-CrossTenant-OriginalArrivalTime: 27 Aug 2026 14:22:55.2376 (UTC) X-MS-Exchange-CrossTenant-FromEntityHeader: Hosted X-MS-Exchange-CrossTenant-Id: 43083d15-7273-40c1-b7db-39efd9ccc17a X-MS-Exchange-CrossTenant-MailboxType: HOSTED X-MS-Exchange-CrossTenant-UserPrincipalName: /SRL+I9zc2/fo64Zr/g7ZpVc/wpOsuyASW8zxZ6JggS4Do/EZKYjC+NeU1ky2Gc6GeVNv2snbBOyxxwZN0DTng== X-MS-Exchange-Transport-CrossTenantHeadersStamped: LVUPR12MB999161 Add `Vec::push_init` which is the init-infallible version of `try_push_init`. Signed-off-by: Eliot Courtney --- rust/kernel/alloc/kvec.rs | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/rust/kernel/alloc/kvec.rs b/rust/kernel/alloc/kvec.rs index fe86530624c1..bb4da220293b 100644 --- a/rust/kernel/alloc/kvec.rs +++ b/rust/kernel/alloc/kvec.rs @@ -369,7 +369,24 @@ pub fn push(&mut self, v: T, flags: Flags) -> Result<(= ), AllocError> { =20 /// Appends an element to the back of the [`Vec`] instance by initiali= zing it in place. /// - /// Unlike [`Vec::push`], the initializer may be fallible. If the allo= cation fails, the + /// # Examples + /// + /// ``` + /// use pin_init::init_zeroed; + /// + /// let mut v =3D KVec::<[u8; 200]>::new(); + /// v.push_init(init_zeroed(), GFP_KERNEL)?; + /// assert_eq!(v[0], [0; 200]); + /// # Ok::<(), Error>(()) + /// ``` + pub fn push_init(&mut self, init: impl Init, flags: Flags) -> Resul= t<(), AllocError> { + self.try_push_init(init, flags) + .map_err(|PushInitError::AllocError(_)| AllocError) + } + + /// Appends an element to the back of the [`Vec`] instance by initiali= zing it in place. + /// + /// Unlike [`Vec::push_init`], the initializer may be fallible. If the= allocation fails, the /// original initializer `init` is handed back in [`PushInitError::All= ocError`]. If the /// initializer itself fails, its error is returned in [`PushInitError= ::InitError`]. /// --=20 2.55.0 From nobody Sun Sep 27 00:36:28 2026 Received: from BN1PR04CU002.outbound.protection.outlook.com (mail-eastus2azon11010006.outbound.protection.outlook.com [52.101.56.6]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 3222747A0D1; Thu, 27 Aug 2026 14:23:04 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=fail smtp.client-ip=52.101.56.6 ARC-Seal: i=2; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1787840586; cv=fail; b=jvwEAWBcEQXvUldjgP0WuNkHdtnErHAB8HgcYl/DkuA8fQxGecJa2WbpBMqqcq7x2Rm3VESR4Wq6KPapKZ36BHUoEzR7U32SkMtC4IWPmVENfgHhoM98drARvnN/7dLVeVQNQdhHSqUTCUafFt9s0kHuJBC3qAYulBFY1yPWvVk= ARC-Message-Signature: i=2; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1787840586; c=relaxed/simple; bh=FI/9Jch4npsIQOiYd2fNzQpD5DycmjCMAeoKnYFFWRg=; h=From:Date:Subject:Content-Type:Message-Id:References:In-Reply-To: To:Cc:MIME-Version; b=sU4cEsuZ79wkEP79sPQMwmy/pzS+aN3X3jW1L6fOw4zYJdj5UlY9qcuOBK+OCZx9WSYCDW7TwSlFQ8uEajlciq0Oorv92reVyekaDZ3kC4TA0YYX0NuKuZGa6LbLwy/HHgMdcEUruDQvX0X+jWtgUqzYrImAIUeVVRtgpn6cnqc= ARC-Authentication-Results: i=2; smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=nvidia.com; spf=fail smtp.mailfrom=nvidia.com; dkim=pass (2048-bit key) header.d=Nvidia.com header.i=@Nvidia.com header.b=b4DTNmya; arc=fail smtp.client-ip=52.101.56.6 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=nvidia.com Authentication-Results: smtp.subspace.kernel.org; spf=fail smtp.mailfrom=nvidia.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=Nvidia.com header.i=@Nvidia.com header.b="b4DTNmya" ARC-Seal: i=1; a=rsa-sha256; s=arcselector10001; d=microsoft.com; cv=none; b=WlyatsuG5w6CYO95uD0oD+438ueNBUwHeHxDqbIvkR808hWNzZbT5Cz4l6MzsFI2HJCXwjGTtOyvDmVuC3Uf4RfnBop1qbk5jZaK/wMM82mTka3Ov6UIOiz9AE7vLOy4wyZsr2szhmeXWq39F6vukQVt4MGwE77wAiaGoCFAIsZXo64v/TeCU6tzv5Hbhyih+ZgCBS0+jeqI7nDugdRp9HTxTFPb795Yd7BsQhW+P8fRtPutNgaw5aCRZHeCIMdjIF2pGGMB40Rh412NVhswSN5OtfzeJC2uFNYIQSy254UJDxreSKLIApO1xkhBe+WT1bTUC81L8DocHfmO7FRfuw== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=microsoft.com; s=arcselector10001; h=From:Date:Subject:Message-ID:Content-Type:MIME-Version:X-MS-Exchange-AntiSpam-MessageData-ChunkCount:X-MS-Exchange-AntiSpam-MessageData-0:X-MS-Exchange-AntiSpam-MessageData-1; bh=PZWIhiqRlTfy1/ciYCOxsocVyXiamOvOQVMuuIn0cB4=; b=ysHDScaQfW+au+/qc2p1xpunWKDhGs6JVgKZX3+EL8bMHZmZBLpQWynV/XKA1DnSJWmnWEnkAYMRvUM7mKDDN/VH8jSNP/hPYgBwpj1Wu/Ie8Eq7yDLcMi0aP6WXb7xmQj532jGpvajiYOLDu6kru1Bz7I0k9a8YI5/KKqYupLsmuQO7xEDg+CLZmMbKbAyhip4fzXqhVvFRJtYAGsdtz6sD+2ky7+YGnm1zZea9Wr/8emjvQ+HpZXEoFys2CsN0XYlf0+AKHrMuLqDbbQvzq2Qd2Dmv/ozvsZXANHrrt8NW+8Iz8oeDVLiZH8NQKjMqy1tmJfmirs6Q9EWTYMpyAA== ARC-Authentication-Results: i=1; mx.microsoft.com 1; spf=pass smtp.mailfrom=nvidia.com; dmarc=pass action=none header.from=nvidia.com; dkim=pass header.d=nvidia.com; arc=none DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=Nvidia.com; s=selector2; h=From:Date:Subject:Message-ID:Content-Type:MIME-Version:X-MS-Exchange-SenderADCheck; bh=PZWIhiqRlTfy1/ciYCOxsocVyXiamOvOQVMuuIn0cB4=; b=b4DTNmya9B54GrF26rjkwE/UE4WuvpHuLUbmT3aG4eFTlA3Jjzx22bcJduxcAy+OWYZ/MGuBLSYjb5gFcMvTafw6sVzhvvGt6gkqhbbZcYgKUMDoi+K2FyVADUhEiciu6/CP6PoX4LZ9uhvmRl62xQzj9hixtN5BC9CtzctPFLXfIQyN06oVOtLq6/dcJ9hIMVP53ccEozUzaCwCJLlDCwUXbi8okr/JK5EF3kpdiFV967raS5xtmeTAgrV7rLUsQAwbEE01VhwthO3/58lZZlVi06DdlkRiwupfaTjJEN5MAeR6pFK575SwIRyGx0VKBKXJ3Fc7GNGjoocIHrutxg== Authentication-Results: dkim=none (message not signed) header.d=none;dmarc=none action=none header.from=nvidia.com; Received: from DS0PR12MB6413.namprd12.prod.outlook.com (2603:10b6:8:ce::10) by LVUPR12MB999161.namprd12.prod.outlook.com (2603:10b6:408:3a2::8) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.21.360.10; Thu, 27 Aug 2026 14:22:58 +0000 Received: from DS0PR12MB6413.namprd12.prod.outlook.com ([fe80::e82a:6673:4142:37fa]) by DS0PR12MB6413.namprd12.prod.outlook.com ([fe80::e82a:6673:4142:37fa%5]) with mapi id 15.21.0360.008; Thu, 27 Aug 2026 14:22:58 +0000 From: Eliot Courtney Date: Thu, 27 Aug 2026 23:12:52 +0900 Subject: [PATCH v2 3/8] rust: alloc: add ArrayVec Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260827-b4-nvkv-v2-3-0de9d5c8658c@nvidia.com> References: <20260827-b4-nvkv-v2-0-0de9d5c8658c@nvidia.com> In-Reply-To: <20260827-b4-nvkv-v2-0-0de9d5c8658c@nvidia.com> To: Danilo Krummrich , Lorenzo Stoakes , Vlastimil Babka , "Liam R. Howlett" , Uladzislau Rezki , Miguel Ojeda , Boqun Feng , Gary Guo , =?utf-8?q?Bj=C3=B6rn_Roy_Baron?= , Benno Lossin , Andreas Hindborg , Alice Ryhl , Trevor Gross , Daniel Almeida , Tamir Duberstein , Alexandre Courbot , =?utf-8?q?Onur_=C3=96zkan?= , David Airlie , Simona Vetter Cc: John Hubbard , Alistair Popple , Timur Tabi , rust-for-linux@vger.kernel.org, linux-kernel@vger.kernel.org, nova-gpu@lists.linux.dev, dri-devel@lists.freedesktop.org, Eliot Courtney X-Mailer: b4 0.15.2 X-ClientProxiedBy: TY4P301CA0018.JPNP301.PROD.OUTLOOK.COM (2603:1096:405:2b1::16) To DS0PR12MB6413.namprd12.prod.outlook.com (2603:10b6:8:ce::10) Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 X-MS-PublicTrafficType: Email X-MS-TrafficTypeDiagnostic: DS0PR12MB6413:EE_|LVUPR12MB999161:EE_ X-MS-Office365-Filtering-Correlation-Id: 07e11613-3a36-4b25-3c0c-08df0446b1a0 X-MS-Exchange-SenderADCheck: 1 X-MS-Exchange-AntiSpam-Relay: 0 X-Microsoft-Antispam: BCL:0;ARA:13230040|23010399003|10070799003|366016|376014|7416014|1800799024|10067099003|6133799003|22082099003|18002099003|11063799006|56012099006|921020; X-Microsoft-Antispam-Message-Info: /2oYsVMeWOtLNbN/J2mQEmSRT6hUI7P5m9ss5uEqVEYdgtxuRj0OwpNObc4UjPqTERuan70aWpBfTynXEOvaoh4bifZSuPw/bcorQkyl6nvktqWoiCOcdwl23/zKSyp369/I/NE6CPuL7droFQoXrz0PDF05vBUXQVTX4Y7yAB7/ch5ewRp2JCO8KnVfrgb/w0B5yuZRko9Q9aEmyCrTFWqk22OKtG/nLRRbSDGGPBjeoQciAnKvfecq/bnQz0XXFBy+pvxNDi+sL4Qaa3Ky0uRe8lEinCu/BQ/cigd6izvKmLLiCoUbOjWV/n0keOIDIoKmYlUH0v9/s16wJTv7VpCpk9z8N1Tx77we6IdmOxe5GplCNgED0s3U6Cu1i9wSrc8oUOnxH4VaI+7JehFQ0mFnGZ45Hp2lQJzIsB46aXdOuOid3uj77j0J8MnKU0al8+7vK5kzYbDbZWcumE44xVgKyWMNWYpbe7lGyPX1JyEYiypvhSd0Ssbnlsf6ZQxXpf9AjQtgP2Wm3ahy0ew/xtfVZH2p3pIWZktIUscfzv/SeGIV9a0S9P2Pw1xJMczMjlxk1/KjKun0ntiWXjMPcEhr1C8rwa6BnsOrn6+xv5YrU1zWOIWyC/S6F+xTKg6xRe/oC+o0Z4GAurUdyBkzb3WvkJSKVl+elnRcUS6VWb1Cd37soHWS2EbXojW/mHGxP6GlsT4aWYH2HDhx2chcDA== X-Forefront-Antispam-Report: CIP:255.255.255.255;CTRY:;LANG:en;SCL:1;SRV:;IPV:NLI;SFV:NSPM;H:DS0PR12MB6413.namprd12.prod.outlook.com;PTR:;CAT:NONE;SFS:(13230040)(23010399003)(10070799003)(366016)(376014)(7416014)(1800799024)(10067099003)(6133799003)(22082099003)(18002099003)(11063799006)(56012099006)(921020);DIR:OUT;SFP:1101; X-MS-Exchange-AntiSpam-MessageData-ChunkCount: 2 X-MS-Exchange-AntiSpam-MessageData-0: =?utf-8?B?d2lZOTB4ZGQvMHZqb3BaZ0RQbVFoMkNUeTlGV2dtemdhL0VTRS9OTGhmWHps?= =?utf-8?B?REVqNEk5aVFZS3V5N1I2cUgwcHp4UlB1R1krSWlkZHRZektEMmlrREsxRkxO?= =?utf-8?B?TFA0VHBITEJCVlRFd0lXN1JHa0hQU05qcVRGMXF4QmlHSVZNVzdqVURYeU9r?= =?utf-8?B?Rys4a0l6UVNYbDZOOEJETkhDT29JOC9DNDZaQ2sxYWk0dXRpQ0ljZXU2YjRl?= =?utf-8?B?ZkRJUlVVVnNGcFFmZjd2SWk5ZDNwVytWODZ3aXJaR3ZuUlZCVUw5VXh3Rmlj?= =?utf-8?B?bGRFcXNwY1Z2N3haSkxmaXJZUHBZZzJwU3JhRG1lT1RLMGRLY09Fc1VwUXMx?= =?utf-8?B?eDdOcFNUaWFtdklVdk1BZ3Q0NDB2KzlPWjduVHNlclBJU09uRC9kc01xTlha?= =?utf-8?B?SjNqL0NOUjNSV0ZUMjV6YUIxZElYRXdrbnBPbGVmbys3RVhsKzZ6d3FYUVZ2?= =?utf-8?B?UEVoZzNCUnpwcGZ5ZFAvaUcyRHJsZmYvNnU4a1ZrWnVMTU0yTGswOEM5eXIr?= =?utf-8?B?YkFNVlAyQ2grZk5KOVVHWm4raXBHOUg0OVR3ZS9UZm9ybXFxWGlUZm1OT3Ji?= =?utf-8?B?aURNMXYwZ1p0SHJtSW9HQ1NyVDd1MGZzWXI4ekFKMG0wdDJLaGg1S0ZsTk9t?= =?utf-8?B?Tm5TUWpyZlJ1YTJra3NXdlNpQWtRT1V0WjBRaEtsazFYcHBlZWhEeWdGL3Vl?= =?utf-8?B?eEcwSHZ0M2gxQnRnL1ROYVVqKy8zMnhpT1MrR2QxYWZhOTJLKzlFamVRL3FQ?= =?utf-8?B?M1dWSm9IT29UQ0c3NVZuU2xCYm52UTlFQTF5TnErRGlXM1MrMjNXdHErQWR1?= =?utf-8?B?OVdqVFM3V1lLSUdjTnFzL1hxN1FhOWhaeEFib0x4dzNEdlYxRnliQnoxNm01?= =?utf-8?B?dHNFZmpnRUlWb0drVzJDR0hrY0dKWjJ2TXp0b3diWHZGTGtQRVB5b083cHFO?= =?utf-8?B?eVNhT0ZHdVMwRDl2L3k2RXBrbStYaG9Vb3RRZDF3cVJXY2lJZUZTUlNsL21s?= =?utf-8?B?eUt5enBGY3llcE1NUy9zZCtGV3VFZFBVSHFSNTJNM3F5YUc1QVZFb2E4czFV?= =?utf-8?B?ZmJmRnUyWkNnejZBZmtRcVo3cTQwSnpUTjhqaUY4MStXMzR6TGt3UUI3azZy?= =?utf-8?B?RkdueUNjOFVYZ1pKRUtBV3FObUEwVDNlU0l6cThjQ3FwL1VkUkJ1VVd3V25w?= =?utf-8?B?VmVidXNqdWxJNnFLZzBtRWF1ak44UWdLQm5udll5dEMweVA1Rml4Q3h6STBl?= =?utf-8?B?aW5WTURRWHo5aVhJRlZ5YXpLN09GRW9RSXhPU01Ba3VFSkZiU0RZYnhhclJX?= =?utf-8?B?S3cvWHQwVC9yTWJObW5BTzZ0MFJGVEVDNVlJeWZkaXFjZlRSdGRQYnppd0Ez?= =?utf-8?B?MWQxTitNdGJBaXh2V2JaQkNubWVXTC9GeEU2dUYxY3BYSGtRbHpQcXlId1VS?= =?utf-8?B?d01EcE0vdkZpNmxZNHkxdW9wQVc3ZnlHZ3NiZVFCNzhxMnF3c3FOZC9kSzF3?= =?utf-8?B?UGpsY09uTENrUCtnWHB6MFpDdWV5OUFYeStadlhlZFk3MjdIQWI5eTh6WC82?= =?utf-8?B?RGdkdFllL0tEd0tqdTI3aWlHYmcvTkNYTkJYZWdvWk0xRGN4eHFtaThlcklZ?= =?utf-8?B?bnZKM29vYmg4V3JvL1hyQi9oTEpFUUJRT3lpRS9ZeTJCN2xuSW9weXJINERC?= =?utf-8?B?VGRNdUpUbERMTzZvK1lMaHdFV0FjU2gzY3UycVpHWGFIT1pVWTVFVHdXeGdJ?= =?utf-8?B?Rit3d0hWVWhBNHM1eTU2UTJwVnhjWFJoaGhOUnVrbXFBTmVyY3Zob01VNU9L?= =?utf-8?B?M1h3anZkK3NTb0p0OHF1R3orRXJ6QjJTSVNyU3NiTXlwbDF0bE8rcFZKTFVR?= =?utf-8?B?aFpmZlFEKy9vUHBwdjhJakdCeDRLbE9ZdEFZV3BXYUptdkFNRVVZdDV6NXM5?= =?utf-8?B?aVpCeHdwU0N5MDZqR1orZS9YeVlLT2lRS1FORHFxSlA5ak9oUU1rVzFUVWY3?= =?utf-8?B?MytUYkpkZWwrV083bzJtVTF5Wjdocm91NlRKNDdKdURBcDU1RnM0OUtLZFVi?= =?utf-8?B?T3BMSzhCUFVKNUtiOVh3TzVYTjNjZVNhcjVoLy95aGxkRmlleTJyYXROYnhq?= =?utf-8?B?R0UvR05jdEdHZWZOWHM4cjNiM3dQaXp0VWxibEFJWkliVTlHQlBQZVp1SzVm?= =?utf-8?B?QXpYT2RIZnhoYjNzam9tOUtoRS9Bak5uK25EK3UycVFyT2hORXc5czFyRDRz?= =?utf-8?B?eFRER09GNkpHcTIwZjFLYnVlODVZNjhhRi9QVStRbG92SkEvZXVVS1BUSzBp?= =?utf-8?B?WlZtYk9JS3NxWDkxekRXTit6OVR1UUx0OWROTWZoUG91cHFWY05QUlgyN0xv?= =?utf-8?Q?mcTSZ0v8Tfz36sHLS9KS56cEHaqPkFGptkmvYiTpYtAo8?= X-MS-Exchange-AntiSpam-MessageData-1: wfo2bXQOn02Waw== X-OriginatorOrg: Nvidia.com X-MS-Exchange-CrossTenant-Network-Message-Id: 07e11613-3a36-4b25-3c0c-08df0446b1a0 X-MS-Exchange-CrossTenant-AuthSource: DS0PR12MB6413.namprd12.prod.outlook.com X-MS-Exchange-CrossTenant-AuthAs: Internal X-MS-Exchange-CrossTenant-OriginalArrivalTime: 27 Aug 2026 14:22:58.6279 (UTC) X-MS-Exchange-CrossTenant-FromEntityHeader: Hosted X-MS-Exchange-CrossTenant-Id: 43083d15-7273-40c1-b7db-39efd9ccc17a X-MS-Exchange-CrossTenant-MailboxType: HOSTED X-MS-Exchange-CrossTenant-UserPrincipalName: TO5QZub7C8p8rCBb3qHrIDQ0CY/n3lw8BHhGbUb60JPMm21tgmungEEE2QTyMkK9H8I+B0J9wo/LbnHejrxnfA== X-MS-Exchange-Transport-CrossTenantHeadersStamped: LVUPR12MB999161 Add a fixed capacity vector backed by [MaybeUninit; N]. The ArrayVec is also initializable with a closure, returning an Init instance, to avoid constructing it on the stack. ArrayVec is useful for small but varying size arrays stored on the stack, to avoid a heap allocation, or, for larger varying size arrays initialized into caller provided memory but not wanting to provide an allocator. Signed-off-by: Eliot Courtney --- rust/kernel/alloc.rs | 3 + rust/kernel/alloc/arrayvec.rs | 347 ++++++++++++++++++++++++++++++++++++++= ++++ 2 files changed, 350 insertions(+) diff --git a/rust/kernel/alloc.rs b/rust/kernel/alloc.rs index 21067bde6860..510e2c7f9f72 100644 --- a/rust/kernel/alloc.rs +++ b/rust/kernel/alloc.rs @@ -3,10 +3,13 @@ //! Implementation of the kernel's memory allocation infrastructure. =20 pub mod allocator; +pub mod arrayvec; pub mod kbox; pub mod kvec; pub mod layout; =20 +pub use self::arrayvec::ArrayVec; + pub use self::kbox::Box; pub use self::kbox::KBox; pub use self::kbox::KVBox; diff --git a/rust/kernel/alloc/arrayvec.rs b/rust/kernel/alloc/arrayvec.rs new file mode 100644 index 000000000000..4172a982e477 --- /dev/null +++ b/rust/kernel/alloc/arrayvec.rs @@ -0,0 +1,347 @@ +// SPDX-License-Identifier: GPL-2.0 + +//! Implementation of [`ArrayVec`]. + +use crate::{ + alloc::kvec::{ + impl_slice_eq, + PushError, // + }, + const_assert, + error::{ + code::EINVAL, + Error, + Result, // + }, + fmt, // +}; + +use core::{ + borrow::{ + Borrow, + BorrowMut, // + }, + mem::MaybeUninit, + ops::{ + Deref, + DerefMut, // + }, + ptr, + slice, // +}; + +use pin_init::{ + init_from_closure, + Init, + Zeroable, // +}; + +/// A fixed capacity vector that holds at most `N` elements. +/// +/// # Invariants +/// +/// - `len` is at most `N`. +/// - The first `len` elements of `data` are initialized. +/// +/// # Examples +/// +/// ``` +/// use kernel::alloc::ArrayVec; +/// +/// let mut v =3D ArrayVec::::new(); +/// v.extend_from_slice(b"abc")?; +/// assert_eq!(*v, *b"abc"); +/// +/// assert!(v.extend_from_slice(b"ab").is_err()); +/// +/// v.push(4u8)?; +/// assert_eq!(*v, *b"abc\x04"); +/// assert!(v.push(5u8).is_err()); +/// +/// v.clear(); +/// assert!(v.is_empty()); +/// # Ok::<(), Error>(()) +/// ``` +#[derive(Zeroable)] +pub struct ArrayVec { + data: [MaybeUninit; N], + len: usize, +} + +impl ArrayVec { + /// Creates an empty [`ArrayVec`]. + #[inline] + pub const fn new() -> Self { + // Clippy triggers this even if the enclosing function is never ca= lled, so skip if clippy is + // on. + const_assert!( + cfg!(clippy) || size_of::() <=3D 512, + "use `init_with` instead of constructing a large ArrayVec on t= he stack" + ); + + // INVARIANT: An empty ArrayVec trivially has all its elements ini= tialized. + Self { + data: [const { MaybeUninit::uninit() }; N], + len: 0, + } + } + + /// Creates an initializer for an [`ArrayVec`] populated by `f`. + /// + /// `f` gets an empty [`ArrayVec`] and can fill it in place. + /// + /// # Examples + /// + /// ``` + /// use kernel::alloc::ArrayVec; + /// + /// let v =3D KBox::init( + /// ArrayVec::::init_with(|v| v.extend_from_slice(b"abc"= )), + /// GFP_KERNEL, + /// )?; + /// assert_eq!(**v, *b"abc"); + /// # Ok::<(), Error>(()) + /// ``` + pub fn init_with(f: impl FnOnce(&mut Self) -> Result<(), E>) -> imp= l Init { + let init =3D move |slot: *mut Self| { + // SAFETY: By the initializer contract `slot` is valid for wri= tes. Once `len` is zero + // the slot holds a valid empty ArrayVec, since `data` require= s no initialization. + // INVARIANT: An empty ArrayVec trivially has all its elements= initialized. + unsafe { ptr::addr_of_mut!((*slot).len).write(0) }; + + // SAFETY: `slot` holds a valid ArrayVec and no other referenc= e to it exists. + let v =3D unsafe { &mut *slot }; + f(v).inspect_err(|_| { + // SAFETY: `slot` holds a valid ArrayVec, and on failure t= he slot is never accessed + // again, so the elements can't be dropped twice. + unsafe { ptr::drop_in_place(slot) } + }) + }; + + // SAFETY: `init` fully initializes the slot on success and drops = the potentially filled + // ArrayVec on failure. + unsafe { init_from_closure(init) } + } + + /// Appends an element to the back of the [`ArrayVec`]. + /// + /// Fails when the [`ArrayVec`] is full, handing the element back in [= `PushError`]. + pub fn push(&mut self, v: T) -> Result<(), PushError> { + self.try_push_init(v) + .map_err(|PushInitError::Full(v)| PushError(v)) + } + + /// Appends an element to the back of the [`ArrayVec`] by initializing= it in place. + /// + /// Fails with [`FullError`] when the [`ArrayVec`] is full. + pub fn push_init(&mut self, init: impl Init) -> Result<(), FullErro= r> { + self.try_push_init(init) + .map_err(|PushInitError::Full(_)| FullError) + } + + /// Appends an element to the back of the [`ArrayVec`] by initializing= it in place. + /// + /// Unlike [`ArrayVec::push_init`], the initializer may be fallible. I= f the [`ArrayVec`] is + /// full, the original initializer `init` is handed back in [`PushInit= Error::Full`]. If the + /// initializer itself fails, its error is returned in [`PushInitError= ::InitError`]. + pub fn try_push_init(&mut self, init: I) -> Result<(), PushInitE= rror> + where + I: Init, + { + let Some(slot) =3D self.spare_capacity_mut().first_mut() else { + return Err(PushInitError::Full(init)); + }; + + // SAFETY: `slot` refers to allocated, aligned memory valid for a = write of one `T`. + unsafe { init.__init(slot.as_mut_ptr()) }.map_err(PushInitError::I= nitError)?; + + // INVARIANT: The element at index `len` was just initialized, and= the new `len` does not + // exceed `N` because a spare slot existed. + self.len +=3D 1; + + Ok(()) + } + + /// Appends a clone of each element in `slice` to the back of the [`Ar= rayVec`]. + /// + /// Fails with [`EINVAL`] if `slice` is longer than the remaining capa= city. + pub fn extend_from_slice(&mut self, slice: &[T]) -> Result + where + T: Clone, + { + let Some(dst) =3D self.spare_capacity_mut().get_mut(..slice.len())= else { + return Err(EINVAL); + }; + + for (d, s) in dst.iter_mut().zip(slice) { + d.write(s.clone()); + } + // INVARIANT: The next `slice.len()` elements after `len` were jus= t initialized, and the + // new `len` does not exceed `N` because the spare capacity was en= ough. + self.len +=3D slice.len(); + + Ok(()) + } + + /// Removes all elements. + #[inline] + pub fn clear(&mut self) { + let elems: *mut [T] =3D self.as_mut_slice(); + // INVARIANT: An empty ArrayVec trivially has all its elements ini= tialized. + self.len =3D 0; + // SAFETY: There are no references to the elements since we hold `= &mut self`. The elements + // can't be dropped again because `len` is already 0. + unsafe { ptr::drop_in_place(elems) }; + } + + /// Returns the initialized elements as a slice. + #[inline] + pub fn as_slice(&self) -> &[T] { + let ptr =3D self.data.as_ptr().cast::(); + // SAFETY: `MaybeUninit` has the same layout as `T`, and by the= type invariants the first + // `len` elements of `data` are initialized. + unsafe { slice::from_raw_parts(ptr, self.len) } + } + + /// Returns the initialized elements as a mutable slice. + #[inline] + pub fn as_mut_slice(&mut self) -> &mut [T] { + let ptr =3D self.data.as_mut_ptr().cast::(); + // SAFETY: `MaybeUninit` has the same layout as `T`, and by the= type invariants the first + // `len` elements of `data` are initialized. + unsafe { slice::from_raw_parts_mut(ptr, self.len) } + } + + /// Returns a slice of `MaybeUninit` for the remaining spare capaci= ty of the [`ArrayVec`]. + fn spare_capacity_mut(&mut self) -> &mut [MaybeUninit] { + // PANIC: `len` never exceeds `N` by the type invariants. + &mut self.data[self.len..] + } +} + +/// Error type for [`ArrayVec::try_push_init`]. +pub enum PushInitError { + /// The [`ArrayVec`] is full. Hand the initializer back. + Full(I), + /// The initializer failed. + InitError(E), +} + +impl fmt::Debug for PushInitError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + PushInitError::Full(_) =3D> write!(f, "Not enough capacity"), + PushInitError::InitError(_) =3D> write!(f, "Initializer failed= "), + } + } +} + +impl From> for Error +where + Error: From, +{ + #[inline] + fn from(e: PushInitError) -> Error { + match e { + PushInitError::Full(_) =3D> EINVAL, + PushInitError::InitError(e) =3D> Error::from(e), + } + } +} + +/// Error type for [`ArrayVec::push_init`]. +pub struct FullError; + +impl fmt::Debug for FullError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "Not enough capacity") + } +} + +impl From for Error { + #[inline] + fn from(_: FullError) -> Error { + EINVAL + } +} + +impl Default for ArrayVec { + #[inline] + fn default() -> Self { + Self::new() + } +} + +impl Drop for ArrayVec { + fn drop(&mut self) { + // SAFETY: The slice holds initialized elements that are never acc= essed again after this + // point. + unsafe { ptr::drop_in_place(self.as_mut_slice()) }; + } +} + +impl Deref for ArrayVec { + type Target =3D [T]; + + #[inline] + fn deref(&self) -> &Self::Target { + self.as_slice() + } +} + +impl DerefMut for ArrayVec { + #[inline] + fn deref_mut(&mut self) -> &mut Self::Target { + self.as_mut_slice() + } +} + +impl Borrow<[T]> for ArrayVec { + fn borrow(&self) -> &[T] { + self.as_slice() + } +} + +impl BorrowMut<[T]> for ArrayVec { + fn borrow_mut(&mut self) -> &mut [T] { + self.as_mut_slice() + } +} + +impl Eq for ArrayVec {} + +impl_slice_eq! { + [const N: usize, const M: usize] ArrayVec, ArrayVec, + [const N: usize] ArrayVec, &[U], + [const N: usize] ArrayVec, &mut [U], + [const N: usize] &[T], ArrayVec, + [const N: usize] &mut [T], ArrayVec, + [const N: usize] ArrayVec, [U], + [const N: usize] [T], ArrayVec, + [const N: usize, const M: usize] ArrayVec, [U; M], + [const N: usize, const M: usize] ArrayVec, &[U; M], +} + +impl<'a, T, const N: usize> IntoIterator for &'a ArrayVec { + type Item =3D &'a T; + type IntoIter =3D slice::Iter<'a, T>; + + fn into_iter(self) -> Self::IntoIter { + self.iter() + } +} + +impl<'a, T, const N: usize> IntoIterator for &'a mut ArrayVec { + type Item =3D &'a mut T; + type IntoIter =3D slice::IterMut<'a, T>; + + fn into_iter(self) -> Self::IntoIter { + self.iter_mut() + } +} + +impl fmt::Debug for ArrayVec { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Debug::fmt(self.as_slice(), f) + } +} --=20 2.55.0 From nobody Sun Sep 27 00:36:29 2026 Received: from PH7PR06CU001.outbound.protection.outlook.com (mail-westus3azon11010058.outbound.protection.outlook.com [52.101.201.58]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 606E647798B; Thu, 27 Aug 2026 14:23:22 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=fail smtp.client-ip=52.101.201.58 ARC-Seal: i=2; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1787840608; cv=fail; b=bAtnzmIYfjF7zxnBoa+v5TxKAYC4DlvL/nI4G4yQ7geuRYVONTXTqNQYLfCUvYP4zE1Jta2K5qHpP/weL395uLT6afjkdxUwvZsr9DAbc/BQCIVwC9TQydBNU0T31MpXtJhk5jO/XnjW9CsI3zq21qqwnL1XoluCSxZRfix7w/M= ARC-Message-Signature: i=2; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1787840608; c=relaxed/simple; bh=32c+wy4/bO+WnuchTqu2i8ezZHO5vYxwfbeaTkYOHcY=; h=From:Date:Subject:Content-Type:Message-Id:References:In-Reply-To: To:Cc:MIME-Version; b=DzAjbCEgZHtsH3B3enp4lu9QHueSFDkh+K8kFToHBGwsE5MqyDJyQi5qa4y7U7w60qxulfygyd4r3HnQcoXcddn7VqiwCWL8DCAuMelQjfWoOxOxRHwxYgLvck/4ukOoU1eIwL6pJ6jwWRgz3wVu/5LpqczcS0qZi8v4+Vh+aKc= ARC-Authentication-Results: i=2; smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=nvidia.com; spf=fail smtp.mailfrom=nvidia.com; dkim=pass (2048-bit key) header.d=Nvidia.com header.i=@Nvidia.com header.b=gf2nQRAS; arc=fail smtp.client-ip=52.101.201.58 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=nvidia.com Authentication-Results: smtp.subspace.kernel.org; spf=fail smtp.mailfrom=nvidia.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=Nvidia.com header.i=@Nvidia.com header.b="gf2nQRAS" ARC-Seal: i=1; a=rsa-sha256; s=arcselector10001; d=microsoft.com; cv=none; b=WXlOVtiZpr120fHvqFMCOLo9KZPNbE2y2K0NmZ79og5n0MauIGY/o7tEgWLOAj140FRL7zebZRGhX03rAJ1ZjY+yjp/gPGKzkDfGLRwYcAs6ZKuyqZaS5A2/dtG4uQImTezrBpxhy+p7alQUUoqVWgk/3/nsb5Meqo05uGbX/v0Eg4sCKfAp+HAjUmrZnX+Ivs/ZVQRtRZFWIBnCbLoJ7PUxmEMsg4OaAgfcojNx07wis1w8GwcAriedchc7s84+nlyQKpldzf8vnEAKAkJCW5s/3qdvJ4JpKZ7Hch6pfM/sYpBu/56Av8LPuEBN0XzGQ7/ZtNf2fra13N7P/2550Q== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=microsoft.com; s=arcselector10001; h=From:Date:Subject:Message-ID:Content-Type:MIME-Version:X-MS-Exchange-AntiSpam-MessageData-ChunkCount:X-MS-Exchange-AntiSpam-MessageData-0:X-MS-Exchange-AntiSpam-MessageData-1; bh=9ZG3Aek4OeMBPrJoALjOWknMykq/G1v4c/nZG/KaiTo=; b=XRjG/Au/ofHsTdtx5vqDR3OIYx0E1MgYicm+DakN/fVY8k1DWjIf1n4kse1wRn0HAuxK8GgqSqvbOy7ApH56Us2wibMSRIvhQm67PR8o0lw+YKxEowSDYMPT3f+tZpB3REQaK+GPK1F7lx+puNNnRrScTm5DF81uiXlhRXSMSh8HMyG0IIo2N2uJE04QfOfgf8tmxKrnDrRHqgVOa1PES6xAJec8MKdQxY93/aPrfdo071I17tTt31hkgJUVfOu2n76k9FXlNpLGYDQoIXHpPgqZkpnINngmI8BWdAc7sTUYSqJr45LlZ96eSzm6YyLZJRsMG0rrjM0R+R0PLxSwZA== ARC-Authentication-Results: i=1; mx.microsoft.com 1; spf=pass smtp.mailfrom=nvidia.com; dmarc=pass action=none header.from=nvidia.com; dkim=pass header.d=nvidia.com; arc=none DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=Nvidia.com; s=selector2; h=From:Date:Subject:Message-ID:Content-Type:MIME-Version:X-MS-Exchange-SenderADCheck; bh=9ZG3Aek4OeMBPrJoALjOWknMykq/G1v4c/nZG/KaiTo=; b=gf2nQRASln/4r2/4mGtegnMDrHCpCYEmStu1B5sAcnVWSeVYwu1r7Cvjb3ZkEAnC2dMfSEGS/cr4m3P5q474lKJ7NNiAnacCPzGJ658kuwrrvc82snBHA5QfekOBdd46Y2sfAs7N/m1Z6rQA1GMZBLrThzoYOYGh5YJwKFkfyLQtGtjwvRdPzGE0IUDauBe1a27L6pvCsB25JRiaZlor2tZYyWRbagqkCKlAszI6BFsOAEtcPL3ozdglQKNSFOdXx0A8MigiGRXxeLABR+DpQ4C2/8hyb8n5UD9Z0601AvUGnVcDY0NCmAMn5vfb/3+pd8pGDp1r62Twi67nZobtJg== Authentication-Results: dkim=none (message not signed) header.d=none;dmarc=none action=none header.from=nvidia.com; Received: from DS0PR12MB6413.namprd12.prod.outlook.com (2603:10b6:8:ce::10) by PH0PR12MB7930.namprd12.prod.outlook.com (2603:10b6:510:283::7) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.21.360.10; Thu, 27 Aug 2026 14:23:02 +0000 Received: from DS0PR12MB6413.namprd12.prod.outlook.com ([fe80::e82a:6673:4142:37fa]) by DS0PR12MB6413.namprd12.prod.outlook.com ([fe80::e82a:6673:4142:37fa%5]) with mapi id 15.21.0360.008; Thu, 27 Aug 2026 14:23:02 +0000 From: Eliot Courtney Date: Thu, 27 Aug 2026 23:12:53 +0900 Subject: [PATCH v2 4/8] gpu: nova-core: add NVKV encoder Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260827-b4-nvkv-v2-4-0de9d5c8658c@nvidia.com> References: <20260827-b4-nvkv-v2-0-0de9d5c8658c@nvidia.com> In-Reply-To: <20260827-b4-nvkv-v2-0-0de9d5c8658c@nvidia.com> To: Danilo Krummrich , Lorenzo Stoakes , Vlastimil Babka , "Liam R. Howlett" , Uladzislau Rezki , Miguel Ojeda , Boqun Feng , Gary Guo , =?utf-8?q?Bj=C3=B6rn_Roy_Baron?= , Benno Lossin , Andreas Hindborg , Alice Ryhl , Trevor Gross , Daniel Almeida , Tamir Duberstein , Alexandre Courbot , =?utf-8?q?Onur_=C3=96zkan?= , David Airlie , Simona Vetter Cc: John Hubbard , Alistair Popple , Timur Tabi , rust-for-linux@vger.kernel.org, linux-kernel@vger.kernel.org, nova-gpu@lists.linux.dev, dri-devel@lists.freedesktop.org, Eliot Courtney X-Mailer: b4 0.15.2 X-ClientProxiedBy: TYCP301CA0020.JPNP301.PROD.OUTLOOK.COM (2603:1096:400:381::13) To DS0PR12MB6413.namprd12.prod.outlook.com (2603:10b6:8:ce::10) Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 X-MS-PublicTrafficType: Email X-MS-TrafficTypeDiagnostic: DS0PR12MB6413:EE_|PH0PR12MB7930:EE_ X-MS-Office365-Filtering-Correlation-Id: 3083706c-9002-4b23-69c0-08df0446b3b3 X-MS-Exchange-SenderADCheck: 1 X-MS-Exchange-AntiSpam-Relay: 0 X-Microsoft-Antispam: BCL:0;ARA:13230040|23010399003|7416014|376014|10070799003|1800799024|366016|18002099003|22082099003|3023799007|6133799003|11063799006|56012099006|10067099003|921020; X-Microsoft-Antispam-Message-Info: qK0XnUzhNAOIL2nqOV49UBzdpsM2iGJxOVz5WbBclCzPTQKBeqgYZa4d3e3BbkxRBpfc0DDCDNP3tzazzGk5KOkHJmExL9csnMrO3djod1aP9bgYvG4Drp1KGcf8/znZtozf+tF7jTESeXNfuikLTsu94JQ0+hXU7m3Zx5aho+cmXaGHQFvj1eqIpCd6/op3K1T/lXt5lQMLbqMQxDB2M83Oyff5yx9Lj+ab6HNohPI1IJ6vt4Fs3xQ+vgDHQp0pe7AiZOnbAFX3HLoR0QMGVRw/61BfyoxRllE0dHTATcKF7bae5CrvusPdk49gyZTxQWQ/a3HE5QZ61qPmyF09xaUNYuvoazpS5+ozN6lVuTwKfi8nBW69/Kq0yd6s5XAT8pfWaUEIoKFA1kdvLZRO/1RIgjz4fQ8APJmu19P4O3XbQ/SpiyLCa9LTdx5071ZWnzVheSr8yy60BmMLgqWTVX3yXmiXVei4vt0Cb66KbEDhNCa0jT0AWoeXcTPQrkyryW53koVGpnMVPM4K4tYFtcm3DtLIFgD+PEPkyUEepMaXHb2eHsB+ayozA4nh9m9b7wk8sNmjvetIEIRbMKl/3RE+rXv4dwSyxjeYvpT2RY90DfPIsqgSOaiDLpaUqb2weY3V6/cL/GGRPYib9nZRZJwSHyRI1OE30tiASxhJtM6Dk/jZptr+v5MfSUoDQY0uqwV7IzUAhJseDl3KMSalyA== X-Forefront-Antispam-Report: CIP:255.255.255.255;CTRY:;LANG:en;SCL:1;SRV:;IPV:NLI;SFV:NSPM;H:DS0PR12MB6413.namprd12.prod.outlook.com;PTR:;CAT:NONE;SFS:(13230040)(23010399003)(7416014)(376014)(10070799003)(1800799024)(366016)(18002099003)(22082099003)(3023799007)(6133799003)(11063799006)(56012099006)(10067099003)(921020);DIR:OUT;SFP:1101; X-MS-Exchange-AntiSpam-MessageData-ChunkCount: 2 X-MS-Exchange-AntiSpam-MessageData-0: =?utf-8?B?VmF4OWxvWFVQcStnYnEyLzFSMUtJbXhjeE1DU3NkWExDSDdTQWtPOFlJSlNP?= =?utf-8?B?SXg3K2dGenFMbmRmV0YyS1JlamEzL3p0c1NSd1c3bURibURYaFJ1VktHSXpl?= =?utf-8?B?NmxoaE15TEdUUjVncTRmZm9KRlZNY21zeEVoVEkvY3JCWFg1bzIzK2huV1lB?= =?utf-8?B?TzU0NTRmckpOSWdHdUVCa2pTV05TU2dDRFgvUHdPV2trRzIzRnFkVkQrNU5F?= =?utf-8?B?V1FXZkxod2o4NGs2VmFDUXZocE0zOFNFTG9HeVRxc1ZzMm9Od2txVkZJU2ZH?= =?utf-8?B?dVIvQU5scTNORWRZUlRpRUNDMVZHQnB5RlBMbEFBQXRwanh5TWkyMkFuaWNU?= =?utf-8?B?RjkvWVVPVFJMWXdDN0Yva2hBK2s0ZldFakVsRUx1ZkpUV2VDZWgrdXpsWUs0?= =?utf-8?B?RWpveHVjQnlTZnVDbWUxYUhiNUZTSlI4RFZDTlk5ajdLYVJ2YVVrRTZDeWJI?= =?utf-8?B?YWc4WkZ5UVlxUHlWRm53UHBNWXY4UnRhUGVkdXQvNUNqNDE4N29ieGMxWEJi?= =?utf-8?B?TlgvVVBIVUIwZys3ZnMzejMwQmdKcjRDc21lODBhSXg2cVlPeVRqdEpoNXp2?= =?utf-8?B?RytqemhIcWVpQXNwS0xtTEk0NVM2WUFPQnNLdGVudXFScDNXY2dFQzY5TkN5?= =?utf-8?B?UlRUNlZrRGViZ1B1OFd0T1Y1VGQvTmRsTS8zbUdCQmM2aFFHSEo2QjFiS3M0?= =?utf-8?B?TS8ySTZqeHMvWHJ6dDRiZXFNeXdRTis2amY5cTBYVC9SUHJWeXFuMTJNcU82?= =?utf-8?B?Q040UkZMTkp1M0Fta0M3Q1BoRk1zbjJIVXpkdlpaczI0SkprR3dSdjJHTFkw?= =?utf-8?B?Z0JtMzRNODdDcThwaXVlbkRtYmUrcDFhd0JYOFJvOWkvRTlVUmNWekdZb3U0?= =?utf-8?B?NEVzcGtNbDZkbGM1eGR4ck5MRVBDbDE5U3cyNU1oN3A3b2FCVy92cHd3YWFl?= =?utf-8?B?VnpLay94NStkRlBVYkhFeWRaMlBjNXNLaUt4bXhEQUhvMkVMTmRVRTVWTXgw?= =?utf-8?B?Vjg4T2s0VzFQeXM1bkRmalFpcVgvazdNL1dpbHVkZVlzWHh5VlpvUi9PNU11?= =?utf-8?B?bXA5SDZEMmEzY3FiRE02VUxvZVVmUDl3OVZtMzRleHlWQWI5ZXhST0kxSXps?= =?utf-8?B?RnZPUVJXYnI3c1RFNG9rc0sxaFdDZ1J0R3RFaG9LM1pkRFEvNFF3MjU3blF6?= =?utf-8?B?Ylc1cVFPRTQ0MzNMYVNOaHlxSHRTcXdnZEFtMEZJV2VxMEJzdGxVY2N0dVVK?= =?utf-8?B?bXYvNTl3WTc5WFB6Zk5uRzI1czBlMmRETjEwY2ZoWW42cUdFZ3dveER1VFJx?= =?utf-8?B?YUNhSjVsVmxQYUJGNnZ1REJNVW9raFBBNDRuNDdGN05naUkyME5saC9pTFJG?= =?utf-8?B?UEhrS3dDOWUyYU9oVGRFR3Jlb3lSVEhwTmxqZzhNVEhFZE41OUZ0V21UazhL?= =?utf-8?B?U0NWS2xYRUFnRlBWeG54Y0dXeWd4YTJPZWI1TTluTmV2S2ZRS3hmbkFpTkV0?= =?utf-8?B?Q1RjV3dTY0pxcGZOTFdTakdHNVFvR1VNdHpVSEdyVytXMjNYYzFRNXZCZXg5?= =?utf-8?B?dWplQ2RIYTFpWkhiOHRuY09hVXZFbGVSOVo5aVlFamtsbnhDbUtOdkt1ZE40?= =?utf-8?B?dUEyRm1WdmVqYTBJRTBaZHRvbXhLdzNtMWVzNVFzdGg0cXdaVU9QL1VyUHho?= =?utf-8?B?Sk4vckx1d0Y1N3lBNXdJNTVZbzVQd0FvS2NDWjJZWWd3eVNubnl4MkJxRzJn?= =?utf-8?B?cFp0eHRjd1B5RVZFaXV2enJXOXc0V3M5clIyQjR1eC9hdFBCYzJkRHJubTl0?= =?utf-8?B?U1U1WGU1Y0ZIempLM1FHQ2tORUdoMmV0U2xnaExVNmtqNTRwb0t3Ykw3M2hX?= =?utf-8?B?VlVOak1EZm11SUFQN2w1NUNVUXp1TkhUOUZhUG5rcmdEOGZuZWtVT3lNZmdr?= =?utf-8?B?NXV6dmthOFlwYnZHOURCbjZWNVp2cUdsdFF2M3o4QlVQMXpYNnVITVlSWmtB?= =?utf-8?B?T0l3WnA1Q01qMXQ3Z0FVejNrbEE4VG9Icy9rbkFjNk9RVXZBMCtpcEVHTGdI?= =?utf-8?B?NldJSjgwL0VhQVliTXBmL1V2ZWRBRHp1UTNyaHBCQTBBRmFLakhubHBIZklJ?= =?utf-8?B?aDRZTFFEUzdSN21Ta0taRjllb05DZWdSbVRReDNMN1dGMDZsZHFhRHJrNkV1?= =?utf-8?B?N2M4ZmFCSlhWQjd4T05aVkRxdVh6U04rWmtLdzhna0cxb2NCUVhmWGJTd2pz?= =?utf-8?B?ZGRDVm05cjFYRmJ5eDQzMHZKRTFNaktxbXFIOEdJVHdRVXFIalhUQzNQOUQr?= =?utf-8?B?dGczWk5hR2hyNTlOSmVEeVFobGozUnZOZVY4dXM3VlNQOGhPekUycVFFenor?= =?utf-8?Q?Ogl0gJ3BwPvmzc9YDefbPkzkFBLoWZQ0dcoqweX4W4Tnk?= X-MS-Exchange-AntiSpam-MessageData-1: lg4KfVxLw1tR+g== X-OriginatorOrg: Nvidia.com X-MS-Exchange-CrossTenant-Network-Message-Id: 3083706c-9002-4b23-69c0-08df0446b3b3 X-MS-Exchange-CrossTenant-AuthSource: DS0PR12MB6413.namprd12.prod.outlook.com X-MS-Exchange-CrossTenant-AuthAs: Internal X-MS-Exchange-CrossTenant-OriginalArrivalTime: 27 Aug 2026 14:23:02.0591 (UTC) X-MS-Exchange-CrossTenant-FromEntityHeader: Hosted X-MS-Exchange-CrossTenant-Id: 43083d15-7273-40c1-b7db-39efd9ccc17a X-MS-Exchange-CrossTenant-MailboxType: HOSTED X-MS-Exchange-CrossTenant-UserPrincipalName: PDkipz4npBNLYKtYlHafefA01pFcozjXAmN65m+naiIeaom2Kb69KnIRrQtw/kbo+r2nai27xtt6AykFx4V7MA== X-MS-Exchange-Transport-CrossTenantHeadersStamped: PH0PR12MB7930 Add an encoder for NVKV, which is the wire format for GMCAPI. The encoded stream is a sequence of 64-bit values. The first 64-bit value encodes an op word which describes the function of the next N values. Essentially, the format encodes a sequence of calls to some function f(key, index, value), where value is a [u8], u32, u64, [u32], or a [u64]. The key is a u16 and the index is a 12 bit integer. The interpretation of these function calls is per GMCAPI. Add tests for the wire encoding for each primitive. Signed-off-by: Eliot Courtney --- drivers/gpu/nova-core/gsp.rs | 1 + drivers/gpu/nova-core/gsp/nvkv.rs | 142 +++++++++++++++++++++ drivers/gpu/nova-core/gsp/nvkv/encode.rs | 210 +++++++++++++++++++++++++++= ++++ 3 files changed, 353 insertions(+) diff --git a/drivers/gpu/nova-core/gsp.rs b/drivers/gpu/nova-core/gsp.rs index 13f361406a6c..84dfe07ae6ba 100644 --- a/drivers/gpu/nova-core/gsp.rs +++ b/drivers/gpu/nova-core/gsp.rs @@ -24,6 +24,7 @@ pub(crate) mod cmdq; pub(crate) mod commands; mod fw; +mod nvkv; mod regs; mod sequencer; =20 diff --git a/drivers/gpu/nova-core/gsp/nvkv.rs b/drivers/gpu/nova-core/gsp/= nvkv.rs new file mode 100644 index 000000000000..a8e16687a134 --- /dev/null +++ b/drivers/gpu/nova-core/gsp/nvkv.rs @@ -0,0 +1,142 @@ +// SPDX-License-Identifier: GPL-2.0 +// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIA= TES. All rights reserved. + +//! Codec for NVKV, the binary key-value format of GMCAPI. +//! +//! Essentially, the format encodes a sequence of calls to some function f= (key, index, value), +//! where value is a [u8], u32, u64, [u32], or a [u64]. The key is a u16 a= nd the index is a 12 bit +//! integer. The interpretation of these function calls is per GMCAPI. Gen= erally speaking, the +//! function calls will map to some struct - for example, f(GPU_NAME_STRIN= G_KEY, 0, b"some gpu") +//! naturally maps to storing a &str with the GPU name. + +#![expect(unused_imports)] + +use core::ops::Deref; + +use kernel::{ + alloc::{ + allocator::KVmalloc, + Allocator, // + }, + bitfield, + num::Bounded, + prelude::*, // +}; +use zerocopy::Immutable; + +mod encode; +pub(crate) use encode::*; + +/// The allocator backing [`EncodedStream`]. +type StreamAllocator =3D KVmalloc; + +/// An encoded NVKV byte stream. +/// +/// # Invariants +/// +/// The byte length is always a multiple of `size_of::()`. +pub(crate) struct EncodedStream(Vec); + +impl EncodedStream { + /// Creates an empty stream. + fn new() -> Self { + // INVARIANT: An empty stream's byte length is 0, a multiple of `s= ize_of::()`. + Self(Vec::new()) + } + + /// Appends a single `u64` to the stream. + fn push_u64(&mut self, value: u64) -> Result { + // INVARIANT: Appending `size_of::()` bytes keeps the byte le= ngth a multiple of + // `size_of::()`. + Ok(self.0.extend_from_slice(&value.to_ne_bytes(), GFP_KERNEL)?) + } + + /// Appends `data` as bytes to the stream, zero-padded to a `u64` boun= dary. + fn extend_with_padding(&mut self, d= ata: &T) -> Result { + let bytes =3D data.as_bytes(); + let padded =3D bytes.len().next_multiple_of(size_of::()); + // Reserve so that a failed allocation can't leave the invariant v= iolated. + self.0.reserve(padded, GFP_KERNEL)?; + self.0.extend_from_slice(bytes, GFP_KERNEL)?; + // INVARIANT: The padding ensures the total length remains a multi= ple of + // `size_of::()`. + Ok(self.0.extend_with(padded - bytes.len(), 0u8, GFP_KERNEL)?) + } +} + +// The Deref to &[u64] relies on this alignment guarantee. +static_assert!(align_of::() <=3D StreamAllocator::MIN_ALIGN); + +impl Deref for EncodedStream { + type Target =3D [u64]; + + fn deref(&self) -> &Self::Target { + // An empty `Vec`'s pointer isn't necessarily aligned by `StreamAl= locator::MIN_ALIGN`. + if self.0.is_empty() { + return &[]; + } + + // PANIC: By the type invariants the byte length is a multiple of = `size_of::()`, and + // the backing buffer of a non-empty vector has at least `u64` ali= gnment per + // `StreamAllocator`'s minimum alignment. + <[u64]>::ref_from_bytes(&self.0).expect("EncodedStream invariant v= iolated") + } +} + +/// The identifier of an NVKV key. +pub(crate) type KeyId =3D u16; + +/// The index of an NVKV value. +pub(crate) type Index =3D Bounded; + +bitfield! { + /// The op word that starts each NVKV operation. + struct Op(u64) { + 15:0 key; + 27:16 index =3D> Index; + 31:28 opcode ?=3D> Opcode; + 63:32 value; + } +} + +/// Describes the format of the following NVKV operation. +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[repr(u8)] +enum Opcode { + /// A 32-bit value in the op word. + Imm32 =3D 0, + /// 32-bit values for consecutive keys, starting at the op word's key. + Seq32 =3D 1, + /// 64-bit values for consecutive keys, starting at the op word's key. + Seq64 =3D 2, + /// An array of bytes. + Array8 =3D 3, + /// An array of 32-bit elements. + Array32 =3D 4, + /// An array of 64-bit elements. + Array64 =3D 5, +} + +// TODO[FPRI]: This is a temporary solution to be replaced with the corres= ponding derive macros once +// they land. +impl TryFrom> for Opcode { + type Error =3D Error; + + fn try_from(value: Bounded) -> Result { + match value.get() { + 0 =3D> Ok(Self::Imm32), + 1 =3D> Ok(Self::Seq32), + 2 =3D> Ok(Self::Seq64), + 3 =3D> Ok(Self::Array8), + 4 =3D> Ok(Self::Array32), + 5 =3D> Ok(Self::Array64), + _ =3D> Err(EINVAL), + } + } +} + +impl From for Bounded { + fn from(value: Opcode) -> Self { + Bounded::from_expr(value as u64) + } +} diff --git a/drivers/gpu/nova-core/gsp/nvkv/encode.rs b/drivers/gpu/nova-co= re/gsp/nvkv/encode.rs new file mode 100644 index 000000000000..6c1a9cbd90e8 --- /dev/null +++ b/drivers/gpu/nova-core/gsp/nvkv/encode.rs @@ -0,0 +1,210 @@ +// SPDX-License-Identifier: GPL-2.0 +// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIA= TES. All rights reserved. + +#![cfg_attr(not(CONFIG_KUNIT), expect(dead_code))] + +use kernel::prelude::*; + +use super::{ + EncodedStream, + Index, + KeyId, + Op, + Opcode, // +}; + +/// An encoder for an NVKV stream. +pub(crate) struct Encoder { + stream: EncodedStream, +} + +impl Encoder { + /// Creates an empty encoder. + pub(crate) fn new() -> Self { + Self { + stream: EncodedStream::new(), + } + } + + /// Returns the encoded data. + #[must_use =3D "encoded stream must be consumed"] + pub(crate) fn finish(self) -> EncodedStream { + self.stream + } + + #[inline(always)] + fn encode_op(&mut self, op: Op) -> Result { + self.stream.push_u64(op.into_raw()) + } + + /// Encodes a 32-bit value as an IMM32 pair, with the value in the op = word. + #[inline(always)] + pub(crate) fn encode_u32(&mut self, key: KeyId, index: Index, value: u= 32) -> Result { + // TODO: Consider automatically merging sequential keys. + self.encode_op( + Op::zeroed() + .with_key(key) + .with_index(index) + .with_opcode(Opcode::Imm32) + .with_value(value), + ) + } + + /// Encodes a 64-bit value as a single-element SEQ64 pair. + #[inline(always)] + pub(crate) fn encode_u64(&mut self, key: KeyId, index: Index, value: u= 64) -> Result { + // TODO: Consider automatically merging sequential keys. + const KEY_COUNT: u32 =3D 1; + self.encode_op( + Op::zeroed() + .with_key(key) + .with_index(index) + .with_opcode(Opcode::Seq64) + .with_value(KEY_COUNT), + )?; + self.stream.push_u64(value) + } + + /// Encodes a byte array as an ARRAY8 pair, zero-padded to a multiple = of 8 bytes. + #[inline(always)] + pub(crate) fn encode_array8(&mut self, key: KeyId, index: Index, array= : &[u8]) -> Result { + let value_count =3D u32::try_from(array.len()).map_err(|_| EMSGSIZ= E)?; + self.encode_op( + Op::zeroed() + .with_key(key) + .with_index(index) + .with_opcode(Opcode::Array8) + .with_value(value_count), + )?; + self.stream.extend_with_padding(array) + } + + /// Encodes a 32-bit array as an ARRAY32 pair, zero-padded to a multip= le of 8 bytes. + #[inline(always)] + pub(crate) fn encode_array32(&mut self, key: KeyId, index: Index, arra= y: &[u32]) -> Result { + let value_count =3D u32::try_from(array.len()).map_err(|_| EMSGSIZ= E)?; + self.encode_op( + Op::zeroed() + .with_key(key) + .with_index(index) + .with_opcode(Opcode::Array32) + .with_value(value_count), + )?; + self.stream.extend_with_padding(array) + } + + /// Encodes a 64-bit array as an ARRAY64 pair. + #[inline(always)] + pub(crate) fn encode_array64(&mut self, key: KeyId, index: Index, arra= y: &[u64]) -> Result { + let value_count =3D u32::try_from(array.len()).map_err(|_| EMSGSIZ= E)?; + self.encode_op( + Op::zeroed() + .with_key(key) + .with_index(index) + .with_opcode(Opcode::Array64) + .with_value(value_count), + )?; + self.stream.extend_with_padding(array) + } +} + +#[kunit_tests(nova_core_nvkv_encode)] +mod tests { + use super::*; + + // Tests that each kind of value is encoded to NVKV wire format proper= ly. + #[test] + fn encode_all_value_kinds() -> Result { + // All keys, indexes, and values are distinct but arbitrary values= to make it easier for the + // test to catch bugs in the encoded output. + const U32_KEY: KeyId =3D 0x1001; + const U64_KEY: KeyId =3D 0x1002; + const ARRAY8_KEY: KeyId =3D 0x1003; + const ARRAY32_KEY: KeyId =3D 0x1004; + const ARRAY64_KEY: KeyId =3D 0x1005; + + const U32_VALUE: u32 =3D 0x1111_2222; + const U64_VALUE: u64 =3D 0x3333_4444_5555_6666; + const ARRAY8_VALUE: &[u8] =3D &[0xaa, 0xbb, 0xcc]; + const ARRAY32_VALUE: &[u32] =3D &[0xbbbb_cccc, 0xdddd_eeee]; + const ARRAY64_VALUE: &[u64] =3D &[0x0123_4567_89ab_cdef, 0xfedc_ba= 98_7654_3210]; + + let mut encoder =3D Encoder::new(); + encoder.encode_u32(U32_KEY, Index::new::<0>(), U32_VALUE)?; + encoder.encode_u64(U64_KEY, Index::new::<1>(), U64_VALUE)?; + encoder.encode_array8(ARRAY8_KEY, Index::new::<2>(), ARRAY8_VALUE)= ?; + encoder.encode_array32(ARRAY32_KEY, Index::new::<3>(), ARRAY32_VAL= UE)?; + encoder.encode_array64(ARRAY64_KEY, Index::new::<4>(), ARRAY64_VAL= UE)?; + + let encoded =3D encoder.finish(); + assert_eq!(encoded.len(), 10); + + // IMM32 has its value in the op word. + assert_eq!( + encoded[0], + Op::zeroed() + .with_key(U32_KEY) + .with_index(Index::new::<0>()) + .with_opcode(Opcode::Imm32) + .with_value(U32_VALUE) + .into_raw() + ); + + // The SEQ64 op word followed by the value. + assert_eq!( + encoded[1], + Op::zeroed() + .with_key(U64_KEY) + .with_index(Index::new::<1>()) + .with_opcode(Opcode::Seq64) + .with_value(1u32) + .into_raw() + ); + assert_eq!(encoded[2], U64_VALUE); + + // The ARRAY8 op word has the byte count. The bytes follow, padded= out to a whole word. + assert_eq!( + encoded[3], + Op::zeroed() + .with_key(ARRAY8_KEY) + .with_index(Index::new::<2>()) + .with_opcode(Opcode::Array8) + .with_value(3u32) + .into_raw() + ); + assert_eq!( + encoded[4], + u64::from_le_bytes([0xaa, 0xbb, 0xcc, 0, 0, 0, 0, 0]) + ); + + // The ARRAY32 op word has the element count. The two elements fol= low in little endian. + assert_eq!( + encoded[5], + Op::zeroed() + .with_key(ARRAY32_KEY) + .with_index(Index::new::<3>()) + .with_opcode(Opcode::Array32) + .with_value(2u32) + .into_raw() + ); + assert_eq!( + encoded[6], + u64::from(ARRAY32_VALUE[1]) << 32 | u64::from(ARRAY32_VALUE[0]) + ); + + // The ARRAY64 op word has the element count with the two elements= after. + assert_eq!( + encoded[7], + Op::zeroed() + .with_key(ARRAY64_KEY) + .with_index(Index::new::<4>()) + .with_opcode(Opcode::Array64) + .with_value(2u32) + .into_raw() + ); + assert_eq!(encoded[8], ARRAY64_VALUE[0]); + assert_eq!(encoded[9], ARRAY64_VALUE[1]); + + Ok(()) + } +} --=20 2.55.0 From nobody Sun Sep 27 00:36:29 2026 Received: from PH7PR06CU001.outbound.protection.outlook.com (mail-westus3azon11010058.outbound.protection.outlook.com [52.101.201.58]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id E5D6A46AA6C; Thu, 27 Aug 2026 14:23:16 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=fail smtp.client-ip=52.101.201.58 ARC-Seal: i=2; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1787840601; cv=fail; b=p4rCgpr22Voo8j/RClGExtV2Iabfg9jQ5adll9xg65m+yF9wnacOo6T5bGRMuCSOuz86sB40jwByitNFY0gsKPN4UKeFwYo1sHLGLoSDM/zk05CXMcmnRcr/5IopaYLODfT/SBUHoCwJiEWRydr2uF2H1Tr/ZpDZ/ifz6/MU3YM= ARC-Message-Signature: i=2; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1787840601; c=relaxed/simple; bh=KMWa9VoQTO5Ly6YpPWNa5mJPdycz3sGHLh1796WZtxc=; h=From:Date:Subject:Content-Type:Message-Id:References:In-Reply-To: To:Cc:MIME-Version; b=b2OdFgRkPfMwAa/HCqsCMzy3yb60qioNiNa2q4Zq/DVYVlrF6gBDonOohw37QZn40++y26/RlexcyfEan6EU7wnRihzwD+qr0GfpfyLaunXGw1Stp4uyri+lAOVWkWdI4mb2LyWIN7pjO2+3Ku8rOU1dGmov38dIDVO2OnOA+hQ= ARC-Authentication-Results: i=2; smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=nvidia.com; spf=fail smtp.mailfrom=nvidia.com; dkim=pass (2048-bit key) header.d=Nvidia.com header.i=@Nvidia.com header.b=RUV7WSvC; arc=fail smtp.client-ip=52.101.201.58 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=nvidia.com Authentication-Results: smtp.subspace.kernel.org; spf=fail smtp.mailfrom=nvidia.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=Nvidia.com header.i=@Nvidia.com header.b="RUV7WSvC" ARC-Seal: i=1; a=rsa-sha256; s=arcselector10001; d=microsoft.com; cv=none; b=Y4xZE+I84aqHx+w3JARO1wj/CeFirgGr9uK5s0GEuK/hZYvjRS/BqbFhIsok5dZ2tKAfnGEvIaitb8Z5IyCQDDT3fa90gZVNjlrctiy0NL0xidnIjY3lkbDtAJU4RPKC/C2kgAcQxp/xxmGI2bKmk6RbRL9kpqj3FDHI5u3dT6i64YrCxR0g8am6pAYhSD+A16KMIsKLvuBs/OuI2bi0g1OhYY4NLuowNreufNCbjLL05p8gxOGJL5BTZWm5HJe+jhw/S0cwmswVsoFmzaGY4KCkRh/OG2U4GuVlzyr31fhoGKdAORS0HUBt6V5W7Gmam47MrxmFQmIT/H/IxW/meg== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=microsoft.com; s=arcselector10001; h=From:Date:Subject:Message-ID:Content-Type:MIME-Version:X-MS-Exchange-AntiSpam-MessageData-ChunkCount:X-MS-Exchange-AntiSpam-MessageData-0:X-MS-Exchange-AntiSpam-MessageData-1; bh=xmxu7D8kQpavwpbhSPKQWnsdXUN85UFYNs4LJz6u/t0=; b=nhr6WE4dOtDrRDYn4pSsfVovD7bH++z/JkzyYsHA98yOXmbatKp2IQ05feJn/jK/4GeYGGYEDbXkEW0wI8sdo1rA6RzArJI7JfQCHvCN8Z+Zt8IfrJSazbOaefII1eK3Kp0y4C/f06ghJuLGQa0jiPuGB3akKXncha88NA1dTxo9hD528j3A7hetRbEjf/pEaFubvnvCRLEa7XahXdwoNaZNgvMzd6Tl6miyl65hSAD1NcnGieACuHOxioejte2dTWT2jTAK3XbEOfCB2nQzM6ueKVeSQbc8/ePLXWbB9CLRGk3oJftZ0lRmuzLpPQwEeFeifFGjpwWzv6BMUzaoVg== ARC-Authentication-Results: i=1; mx.microsoft.com 1; spf=pass smtp.mailfrom=nvidia.com; dmarc=pass action=none header.from=nvidia.com; dkim=pass header.d=nvidia.com; arc=none DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=Nvidia.com; s=selector2; h=From:Date:Subject:Message-ID:Content-Type:MIME-Version:X-MS-Exchange-SenderADCheck; bh=xmxu7D8kQpavwpbhSPKQWnsdXUN85UFYNs4LJz6u/t0=; b=RUV7WSvCDVRLrwTPO2SUnSJbxQjokElymCByFxfY+cAIjqrmflHQOxjsJtXJK76Sga1PdhOoD1sMm8p+6HyyzwIjmyS+YRD8Ex7ahPFLPRtp4UEznc3Te3bPYrnrlFi0XD8FGeOedpZ0RRGJVZ3o6HgKIpLL/5UbwrySWjygwQvHoE1Ie/51pGoUIawN+SZQsWbnRMA4nijjcS3CvelXxgAr2ZOLHpxBSWFi411WbbuwFc8xhHneQJBYAvpRc6uJszDi5aXa0Xtj2crjwIMfuax51/t3jdNpZvY5zDwD7YtcwovBqTpV99gGtYLxxEGxmPKdwM+1IbTk2K8fZXoWlQ== Authentication-Results: dkim=none (message not signed) header.d=none;dmarc=none action=none header.from=nvidia.com; Received: from DS0PR12MB6413.namprd12.prod.outlook.com (2603:10b6:8:ce::10) by PH0PR12MB7930.namprd12.prod.outlook.com (2603:10b6:510:283::7) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.21.360.10; Thu, 27 Aug 2026 14:23:05 +0000 Received: from DS0PR12MB6413.namprd12.prod.outlook.com ([fe80::e82a:6673:4142:37fa]) by DS0PR12MB6413.namprd12.prod.outlook.com ([fe80::e82a:6673:4142:37fa%5]) with mapi id 15.21.0360.008; Thu, 27 Aug 2026 14:23:05 +0000 From: Eliot Courtney Date: Thu, 27 Aug 2026 23:12:54 +0900 Subject: [PATCH v2 5/8] gpu: nova-core: add NVKV decoder Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260827-b4-nvkv-v2-5-0de9d5c8658c@nvidia.com> References: <20260827-b4-nvkv-v2-0-0de9d5c8658c@nvidia.com> In-Reply-To: <20260827-b4-nvkv-v2-0-0de9d5c8658c@nvidia.com> To: Danilo Krummrich , Lorenzo Stoakes , Vlastimil Babka , "Liam R. Howlett" , Uladzislau Rezki , Miguel Ojeda , Boqun Feng , Gary Guo , =?utf-8?q?Bj=C3=B6rn_Roy_Baron?= , Benno Lossin , Andreas Hindborg , Alice Ryhl , Trevor Gross , Daniel Almeida , Tamir Duberstein , Alexandre Courbot , =?utf-8?q?Onur_=C3=96zkan?= , David Airlie , Simona Vetter Cc: John Hubbard , Alistair Popple , Timur Tabi , rust-for-linux@vger.kernel.org, linux-kernel@vger.kernel.org, nova-gpu@lists.linux.dev, dri-devel@lists.freedesktop.org, Eliot Courtney X-Mailer: b4 0.15.2 X-ClientProxiedBy: TY4P301CA0022.JPNP301.PROD.OUTLOOK.COM (2603:1096:405:2b1::14) To DS0PR12MB6413.namprd12.prod.outlook.com (2603:10b6:8:ce::10) Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 X-MS-PublicTrafficType: Email X-MS-TrafficTypeDiagnostic: DS0PR12MB6413:EE_|PH0PR12MB7930:EE_ X-MS-Office365-Filtering-Correlation-Id: 95d7b87c-cc2e-4c3e-a4af-08df0446b5ab X-MS-Exchange-SenderADCheck: 1 X-MS-Exchange-AntiSpam-Relay: 0 X-Microsoft-Antispam: BCL:0;ARA:13230040|23010399003|7416014|376014|10070799003|1800799024|366016|18002099003|22082099003|3023799007|6133799003|5023799004|11063799006|56012099006|10067099003|921020; X-Microsoft-Antispam-Message-Info: hTtYZS9Er0/rmjg6fbaHiyD9MtEF9X0qmwe1FVgwgVLjxodjbJ1w4QC0dsOnoYAcFkeKzdyWLRNHmdx2kUYfd6Y1U+Iudsyzon4k+E8kwPeNbYuxm0kJ0b955mN7hMVee6p+sZU95uBvMkKRhXn+mO7k2Sik7tNMGhQi3l2Jp3+iSX2Xx4XHJ9uS8ockvddcRMCO9/mhy636h75KsUU1jSM+1x9xSjcmQsq9idh5X9SMA1uUuNNxoln0nUb85TKo9O19MgQelSwLixtVkI9ST0Yq58FF4NYgF+qQkKgCgvBpqAkG2g2SZ+J2eyz+dVeok3wS3CV7Wz1OF57ySS6elAgkfezyjuvWPi66hsJfXgTZ27V3qgVvTQxE8e1p83veBFAJCcJSjuwNiV214zNEdLQdHU7evMfHsdqHy6cjtAPvaRcsodIiC65kvoE54xAGiNOc802CxUSkDgwPyG5mZCHEZXuUoAt3w6g+JXupiht9+VwOzj9qPAfkU/rw5Tb+1mIBqSp1QnW30DJLxhNGzqBtFEHkY3YAPFXCd8ImeLae3+AEue3IMNlzmyggY5DQ8VMHsKf2YCkBaGxD3Ni20g2MVwQMGZtr/ScB1KJAwtA554SFVYRiZTN4CKEtwVmqUcENUIaWnZkj+AgOM/MYna/+d4kOquGAjKPSZjYnZLOXy4x2Of2MbeI1FoLDEHqhxhv+COx3iWK4tBnronD9XQ== X-Forefront-Antispam-Report: CIP:255.255.255.255;CTRY:;LANG:en;SCL:1;SRV:;IPV:NLI;SFV:NSPM;H:DS0PR12MB6413.namprd12.prod.outlook.com;PTR:;CAT:NONE;SFS:(13230040)(23010399003)(7416014)(376014)(10070799003)(1800799024)(366016)(18002099003)(22082099003)(3023799007)(6133799003)(5023799004)(11063799006)(56012099006)(10067099003)(921020);DIR:OUT;SFP:1101; X-MS-Exchange-AntiSpam-MessageData-ChunkCount: 2 X-MS-Exchange-AntiSpam-MessageData-0: =?utf-8?B?cVhnSlhVV0JLT0VYUUtmdkJnYTNHV1NnbHNtSnZiYXVxL01ndFg1c1p5SUVI?= =?utf-8?B?ajgvWW80V1B3cXpqdXJRd2FyUVpvOUlFUm82cG9FU3JOZW5IcXRoV0hlM3FR?= =?utf-8?B?WDN4VjBLSkVCdVNtTmZydHd6VFJZL3hWV0drUjN1WnhSVzBWeWRhWTNRUVhV?= =?utf-8?B?NmJKSEkzdkhNSTRrZDR5NlE0SHNuRmcrUDFlWUUrSjJJajNqTlZ4VDM1b3NR?= =?utf-8?B?N1p5L2JlZHJNZEpObzBseEN5cERlUGYrdmYvWkgyMDFUYnM2Rll1aVFITGpN?= =?utf-8?B?VEYyb01MSU93MzBUUC9kRkJLSStQOXZEbitKWkdiQnpaWnhwRWt1UFNSa016?= =?utf-8?B?Z2QvOE0rTjFha3F1K3R4bG9tTThPTFEwRUVteGJYVU1rT2hQZy9zeFg2aFY4?= =?utf-8?B?MEdiTk16a3ZuY3hNS3k0THk2K1NTaS9wNHI0WjJvcXkySXJDZDR1SW56N2tw?= =?utf-8?B?U0RybmVaUnlkaGdLdTVZaUUrQ1FvMnNpVTA1aGJ3d3ZTQkxtbTB4MktnNGtq?= =?utf-8?B?emZRVEZKZmg0QngreldhejJmamIyb2RvdlVOc3NNai9jMHo4M21Zd3pWcDJu?= =?utf-8?B?S09zcEZlMU0xRlNjYlZtaG9IV2ZKaGxWelpHRFd5b0xoeXV2ZUQzTnNNcmtG?= =?utf-8?B?K3ZrNHg2ZzdPOTBxRnBKRmVqVVMzSUN0RjNmZHBFV2FNSVJWQzBSQkRWc0p6?= =?utf-8?B?VTRGZnFmK1Z3VU5EUXdEK3NnMWpiNzZBaGxwSHJSTU1FTnFLanMrR1lYNFlC?= =?utf-8?B?THVhdDhxTTN3bW44VHQ3cE8rcFBxYmlLd21jRFdiUXZqZlJHMGhEUzVid2FB?= =?utf-8?B?L2xIeFBSa2tLSmRYTnVXS3E0Wi9wQnNObENQV2t5ZUdYbXJReVJpV2t4ZVJG?= =?utf-8?B?Q1pzWnVIM0pEaUFWN2FKSS9zWERNV00rRU9Jc09qTXVOWGlXTlYzTFhLRm1s?= =?utf-8?B?NytUeHV5YWJZSzJ2WFB6NnMydFV3OFB6ZENpWTB3RVA3ZkI3aklkd0dwcjJZ?= =?utf-8?B?N2I2UkFBVzlNUmpmeUlKM3VUUXcyN2hvUkZjdVVYVWZKeEtXc05tT3ZIbWxy?= =?utf-8?B?SWU0cXNHcFprbGIyUW1kT1JkQ2Z3TWdtb3FrVW1DSUFGeDhleDI4MnpzVysw?= =?utf-8?B?amxMZGU2VXdjODA1TXhkU093RUlOb0dKNU8zVmhXY2ZhaFk3Q3dkanhVMm92?= =?utf-8?B?UDdRUy9SUStqRGhITjE5WFB4VWtkb0trMll0RTR2MVA0UFhSL0JEdjV3NHRt?= =?utf-8?B?T1VQR3dzZGdUeDNScHlZc2NjYlF1TGVDek9GMUNucFZuUlc4dXd6WU5FWS9I?= =?utf-8?B?S1I2U3NSSFZxcjNrVkxzSGZGUk55cmpYT1RNek9sb3ZVZEMyNnR6cmViTE9S?= =?utf-8?B?NUFEUzRYK1EwS2dlQit3Rm5raXJMS1lZdVZHRHJtbDZTQzNsZ0hpL0JZbzNK?= =?utf-8?B?aDA2SXp1bjBkZWpkUVo3SXQramFqV1VhdnZDZ3U1dFlnVVZKOFVwN09aZVA2?= =?utf-8?B?eFZmODJPZk9YNmw0b0VpZVRTWnRYbytFN0RoK1dWNUkxMUkvZ1l2Sm5BWGtG?= =?utf-8?B?VWlrSnV4UkhCWk16WXZIdHVPNTY3ekZFOWhLdGR3S0tVUC92ZUZPaHd4dkxW?= =?utf-8?B?NW5ueXF1SzYyeFhFTEtsMjQ2a0FWU1hDMEhTVTVBY1ZmWE1Ebk0vdEN0V3VV?= =?utf-8?B?NmlNdzBJOHBGU3VaTzFMYm1wZG02RVhIZDEvanNJMklwaHUxcHpjOHYydGt5?= =?utf-8?B?cmt3cUYxWVpPVDVIWUlwS3BkYmU4SXpkZUhrMEFZeGxPMEh0eXhDMHo2S1hM?= =?utf-8?B?WEpFckNqem9vNWtWQndkSnhhUlBDbEh5aGE4TkpzUXA5cVVkSXNHZVB0ck1C?= =?utf-8?B?Y1M4UWgzMXVjejRWVkYrZ2lsTnUreG1XRTFPRllmYWFUMmYwVXFyR0NMMHRv?= =?utf-8?B?Rm5hQTFUejRFb1lGVkZYMHk5RksxWktTbUlQMkZqVWg4WlkxMFkzUVRmbXRS?= =?utf-8?B?Wm9nSG5meDBicDNyU2Y4R1ZHNHJoMFRFWTBMZmx2bmxQdS94M1kwVzBEWjNr?= =?utf-8?B?ekJqYWpIR2M4cUZ0dGZWeTdKZzF5RTJBTFBWVVRFMlpJcm5tTHJXTW5SVGpD?= =?utf-8?B?S3h3UUdHRWJ3UzYxS1lpYldNOVdEeG1OWFcvNzV2aEJXeVZxNXdvSGkwTWht?= =?utf-8?B?YTdER1ZRKy8rY1ZwL2xtRHZnYU4vYmtsT25ZM1JScXhFQS91bmtibTQxZi9D?= =?utf-8?B?Z3dqK0FRckkzaGhMQWkxS1F0dDg1dkZEMGRqcFRaRWplaDFkV0JTNVdQckxt?= =?utf-8?B?Y3liMm1PZzdUMGxhVDFiUDlyRjNmaEhEVnk3RklFQmFoTC9CYk5NYjloUjJw?= =?utf-8?Q?wUa/rj6842JlQpeFC4s4roO4xhr+MNMbRSy0g1LhTWeKl?= X-MS-Exchange-AntiSpam-MessageData-1: I3cu22TIn17sgg== X-OriginatorOrg: Nvidia.com X-MS-Exchange-CrossTenant-Network-Message-Id: 95d7b87c-cc2e-4c3e-a4af-08df0446b5ab X-MS-Exchange-CrossTenant-AuthSource: DS0PR12MB6413.namprd12.prod.outlook.com X-MS-Exchange-CrossTenant-AuthAs: Internal X-MS-Exchange-CrossTenant-OriginalArrivalTime: 27 Aug 2026 14:23:05.4942 (UTC) X-MS-Exchange-CrossTenant-FromEntityHeader: Hosted X-MS-Exchange-CrossTenant-Id: 43083d15-7273-40c1-b7db-39efd9ccc17a X-MS-Exchange-CrossTenant-MailboxType: HOSTED X-MS-Exchange-CrossTenant-UserPrincipalName: 2eJeyG+aFIvR9jhUfohLF3da192ti7HpXuNrIcJlljwELCTwDwh9IQj3Wz0TjvW8fVx6XrfXBaEkWiqtBgSYbg== X-MS-Exchange-Transport-CrossTenantHeadersStamped: PH0PR12MB7930 Add a decoder for NVKV. This is for receiving messages from GSP for GMCAPI calls. The NVKV format essentially encodes a sequence of function calls f(key, index, value). This decoder reads an encoded stream and invokes a type implementing the new `Schema` visitor trait. The `Schema` trait can either consume the value or not, which is useful for composing Schemas. If a (key, index, value) is not consumed, error out depending on `UnknownKeyPolicy`. Whether ignoring unknown keys is ok or not is per each GMCAPI call. Add kunit tests for the decoder. Signed-off-by: Eliot Courtney --- drivers/gpu/nova-core/gsp/nvkv.rs | 3 + drivers/gpu/nova-core/gsp/nvkv/decode.rs | 265 +++++++++++++++++++++++++++= ++++ 2 files changed, 268 insertions(+) diff --git a/drivers/gpu/nova-core/gsp/nvkv.rs b/drivers/gpu/nova-core/gsp/= nvkv.rs index a8e16687a134..cbeee7f376b6 100644 --- a/drivers/gpu/nova-core/gsp/nvkv.rs +++ b/drivers/gpu/nova-core/gsp/nvkv.rs @@ -27,6 +27,9 @@ mod encode; pub(crate) use encode::*; =20 +mod decode; +pub(crate) use decode::*; + /// The allocator backing [`EncodedStream`]. type StreamAllocator =3D KVmalloc; =20 diff --git a/drivers/gpu/nova-core/gsp/nvkv/decode.rs b/drivers/gpu/nova-co= re/gsp/nvkv/decode.rs new file mode 100644 index 000000000000..ceb97e73e100 --- /dev/null +++ b/drivers/gpu/nova-core/gsp/nvkv/decode.rs @@ -0,0 +1,265 @@ +// SPDX-License-Identifier: GPL-2.0 +// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIA= TES. All rights reserved. + +#![cfg_attr(not(CONFIG_KUNIT), expect(dead_code))] + +use kernel::prelude::*; + +use crate::gsp::nvkv::{ + Index, + KeyId, + Op, + Opcode, // +}; +use crate::num; + +/// A decoded NVKV value. +#[derive(Copy, Clone)] +pub(crate) enum DecoderValue<'a> { + Scalar32(u32), + Scalar64(u64), + Array8(&'a [u8]), + Array32(&'a [u32]), + Array64(&'a [u64]), +} + +/// Implements `TryFrom` from the given `DecoderValue` variant to the give= n type. +/// +/// `TryFrom` is used by the `Schema` implementations in this file to conv= ert from the +/// `DecoderValue`s into the types to store. Provide the implementations f= or basic types here. +macro_rules! impl_try_from_decoder_value { + ($ty:ty, $variant:ident) =3D> { + impl<'a> TryFrom> for $ty { + type Error =3D Error; + + fn try_from(value: DecoderValue<'a>) -> Result { + if let DecoderValue::$variant(v) =3D value { + Ok(v) + } else { + Err(EINVAL) + } + } + } + }; +} + +impl_try_from_decoder_value!(u32, Scalar32); +impl_try_from_decoder_value!(u64, Scalar64); +impl_try_from_decoder_value!(&'a [u8], Array8); +impl_try_from_decoder_value!(&'a [u32], Array32); +impl_try_from_decoder_value!(&'a [u64], Array64); + +/// A visitor that consumes decoded NVKV and produces a `Target`. +pub(crate) trait Schema { + type Target; + + /// Visits one decoded pair. Returns `Ok(true)` if the schema consumed= it. + fn visit<'a>(&mut self, key: KeyId, index: Index, value: DecoderValue<= 'a>) -> Result; + + /// Returns an initializer that makes the decoded `Target`. + /// + /// After the returned initializer runs, the schema should be empty ag= ain. + fn finish(&mut self) -> impl Init + '_; +} + +/// A read position in an NVKV stream. +struct Cursor<'a> { + data: &'a [u64], +} + +impl<'a> Cursor<'a> { + fn new(data: &'a [u64]) -> Self { + Self { data } + } + + fn is_empty(&self) -> bool { + self.data.is_empty() + } + + fn take_u64(&mut self) -> Result { + // PANIC: `take_u64s(1)` returns exactly one element on success. + Ok(self.take_u64s(1)?[0]) + } + + fn take_u8s(&mut self, count: usize) -> Result<&[u8]> { + let values =3D self.take_u64s(count.div_ceil(8))?; + values.as_bytes().get(..count).ok_or(EINVAL) + } + + fn take_u32s(&mut self, count: usize) -> Result<&[u32]> { + let values =3D self.take_u64s(count.div_ceil(2))?; + <[u32]>::ref_from_prefix_with_elems(values.as_bytes(), count) + .map(|(elems, _)| elems) + .map_err(|_| EINVAL) + } + + fn take_u64s(&mut self, count: usize) -> Result<&[u64]> { + let (prefix, suffix) =3D self.data.split_at_checked(count).ok_or(E= INVAL)?; + self.data =3D suffix; + Ok(prefix) + } +} + +/// A decoder for an NVKV stream. +pub(crate) struct Decoder<'a> { + data: &'a [u64], + policy: UnknownKeyPolicy, +} + +impl<'a> Decoder<'a> { + /// Creates a decoder for `data` that handles unknown keys per `policy= `. + pub(crate) fn new(data: &'a [u64], policy: UnknownKeyPolicy) -> Self { + Self { data, policy } + } + + fn visit( + &self, + schema: &mut S, + key: KeyId, + index: Index, + value: DecoderValue<'_>, + ) -> Result { + let consumed =3D schema.visit(key, index, value)?; + if !consumed && self.policy =3D=3D UnknownKeyPolicy::Error { + Err(EINVAL) + } else { + Ok(()) + } + } + + fn seq_key(base: KeyId, offset: usize) -> Result { + base.checked_add(KeyId::try_from(offset)?).ok_or(EINVAL) + } + + /// Decodes every pair into `schema` and returns the result of [`Schem= a::finish`]. + pub(crate) fn decode<'s, S: Schema>( + &self, + schema: &'s mut S, + ) -> Result + 's> { + let mut cursor =3D Cursor::new(self.data); + while !cursor.is_empty() { + let op: Op =3D cursor.take_u64()?.into(); + + let key =3D op.key().into(); + let index =3D op.index(); + let op_value: u32 =3D op.value().into(); + match op.opcode()? { + Opcode::Imm32 =3D> { + self.visit(schema, key, index, DecoderValue::Scalar32(= op_value))?; + } + Opcode::Seq32 =3D> { + let values =3D cursor.take_u32s(num::u32_as_usize(op_v= alue))?; + for (i, &value) in values.iter().enumerate() { + let key =3D Self::seq_key(key, i)?; + self.visit(schema, key, index, DecoderValue::Scala= r32(value))?; + } + } + Opcode::Seq64 =3D> { + let values =3D cursor.take_u64s(num::u32_as_usize(op_v= alue))?; + for (i, &value) in values.iter().enumerate() { + let key =3D Self::seq_key(key, i)?; + self.visit(schema, key, index, DecoderValue::Scala= r64(value))?; + } + } + Opcode::Array8 =3D> { + let value =3D cursor.take_u8s(num::u32_as_usize(op_val= ue))?; + self.visit(schema, key, index, DecoderValue::Array8(va= lue))?; + } + Opcode::Array32 =3D> { + let value =3D cursor.take_u32s(num::u32_as_usize(op_va= lue))?; + self.visit(schema, key, index, DecoderValue::Array32(v= alue))?; + } + Opcode::Array64 =3D> { + let value =3D cursor.take_u64s(num::u32_as_usize(op_va= lue))?; + self.visit(schema, key, index, DecoderValue::Array64(v= alue))?; + } + }; + } + Ok(schema.finish()) + } +} + +/// This is defined per call. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub(crate) enum UnknownKeyPolicy { + Ignore, + Error, +} + +#[kunit_tests(nova_core_nvkv_decode)] +mod tests { + use super::*; + + use crate::gsp::nvkv::Encoder; + + // Tests that basic decoding into a manually implemented `Schema` work= s correctly. + #[test] + fn decode_raw_schema() -> Result { + // Decodes an IMM32 pair and a SEQ64 pair (the encoder emits a u64= as a single-element + // SEQ64) with a hand written `Schema`. Keys and value constants c= hosen to distinguish e.g. + // saving the wrong value to the wrong location. + const SCALAR32_KEY: KeyId =3D 0x1001; + const SCALAR64_KEY: KeyId =3D 0x1002; + const UNKNOWN_KEY: KeyId =3D 0x2001; + + const SCALAR32_VALUE: u32 =3D 0x1111_2222; + const SCALAR64_VALUE: u64 =3D 0x3333_4444_5555_6666; + + // The output type of the hand written Schema. In this case, we ca= n have it also implement + // `Schema` on itself rather than having a separate carrier type, = since the `Schema` + // implementation is completely stateless. + #[derive(Default)] + struct RawSchema { + scalar32: u32, + scalar64: u64, + } + + impl Schema for RawSchema { + type Target =3D Self; + + fn visit(&mut self, key: KeyId, index: Index, value: DecoderVa= lue<'_>) -> Result { + if index !=3D Index::new::<0>() { + return Err(EINVAL); + } + match key { + SCALAR32_KEY =3D> self.scalar32 =3D value.try_into()?, + SCALAR64_KEY =3D> self.scalar64 =3D value.try_into()?, + _ =3D> return Ok(false), + } + Ok(true) + } + + fn finish(&mut self) -> impl Init + '_ { + Ok(core::mem::take(self)) + } + } + + let mut encoder =3D Encoder::new(); + encoder.encode_u32(SCALAR32_KEY, Index::new::<0>(), SCALAR32_VALUE= )?; + encoder.encode_u64(SCALAR64_KEY, Index::new::<0>(), SCALAR64_VALUE= )?; + let serialized =3D encoder.finish(); + + let decoder =3D Decoder::new(&serialized, UnknownKeyPolicy::Error); + let mut schema =3D RawSchema::default(); + let decoded =3D KBox::try_init(decoder.decode(&mut schema)?, GFP_K= ERNEL)?; + + assert_eq!(decoded.scalar32, SCALAR32_VALUE); + assert_eq!(decoded.scalar64, SCALAR64_VALUE); + + // An unknown key should fail with under `UnknownKeyPolicy::Error`= and be skipped under + // `UnknownKeyPolicy::Ignore`. + let mut encoder =3D Encoder::new(); + encoder.encode_u32(UNKNOWN_KEY, Index::new::<0>(), 1)?; + + let serialized =3D encoder.finish(); + let decoder =3D Decoder::new(&serialized, UnknownKeyPolicy::Error); + assert!(decoder.decode(&mut RawSchema::default()).is_err()); + + let decoder =3D Decoder::new(&serialized, UnknownKeyPolicy::Ignore= ); + let mut schema =3D RawSchema::default(); + let decoded =3D KBox::try_init(decoder.decode(&mut schema)?, GFP_K= ERNEL)?; + assert_eq!(decoded.scalar32, 0); + + Ok(()) + } +} --=20 2.55.0 From nobody Sun Sep 27 00:36:29 2026 Received: from PH7PR06CU001.outbound.protection.outlook.com (mail-westus3azon11010058.outbound.protection.outlook.com [52.101.201.58]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 5ECDA47A881; Thu, 27 Aug 2026 14:23:31 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=fail smtp.client-ip=52.101.201.58 ARC-Seal: i=2; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1787840618; cv=fail; b=biFQl4p5Vjn38XYT8oyl3XBTwYK5hMkyBNV7CYNJXryNW/cKcxgtk+S423XDKZGnIJgYDJ3inQt3IRKuNEgDF34Jd548pXeSprBtL25YjV1i93xBhDBk2xKXq3T9PSCvUsDqhN5E49j4mG3E4Rxhrc1R9ZLe0CiRekPXl/L+SqQ= ARC-Message-Signature: i=2; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1787840618; c=relaxed/simple; bh=jkHXh0lTnqsTIIeo2tL9WLo6Zk6bx7K6twisbe5S2v0=; h=From:Date:Subject:Content-Type:Message-Id:References:In-Reply-To: To:Cc:MIME-Version; b=iklNyRp304J6WrrpRi3fv7UJHSdRgSWgKc5a1kwouub/wn4Xlyt0fA4c1m4SZrIf5P1D0FE0UD65ZNupGD5u5626zGZhMCp6fmDvMuGQz20dgc+oxH+M4F9xqUDK+TeewTweTUiqDUDkkqnYsb0ERccV8qik7PXd9sFN23/d7ck= ARC-Authentication-Results: i=2; smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=nvidia.com; spf=fail smtp.mailfrom=nvidia.com; dkim=pass (2048-bit key) header.d=Nvidia.com header.i=@Nvidia.com header.b=CfirnHQl; arc=fail smtp.client-ip=52.101.201.58 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=nvidia.com Authentication-Results: smtp.subspace.kernel.org; spf=fail smtp.mailfrom=nvidia.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=Nvidia.com header.i=@Nvidia.com header.b="CfirnHQl" ARC-Seal: i=1; a=rsa-sha256; s=arcselector10001; d=microsoft.com; cv=none; b=uohBIuKtWq+i3myjuaOuNWaQgHAB4XHvvLZce0L7hShaTJovyiah2vaJR/3ex/IqWfLoZF0D04GkhpwklEi/rpxNOIAh/eoH4fIXaScHILWT4TZZdKE0Cxn3QTn8vvRX+XNPOSwunHXHlG1H+E4kPFzuykyYfoo9N8RylNDIsou7J2G9NzApKduVJvFYpUgS4D8jYNplN9q+PTw2CZkkRgX56i69h8FtHdSZXRG+vWw60qy2HQ29lka89FlrjRY+PhohVSTtV+8z/pkNhzMaO1e1Q76UO/cfc4xizhWrojaDyNqKfyf6PDyBgwEI3ZqJ5tFdBKjIMYIRnc+vLHJatQ== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=microsoft.com; s=arcselector10001; h=From:Date:Subject:Message-ID:Content-Type:MIME-Version:X-MS-Exchange-AntiSpam-MessageData-ChunkCount:X-MS-Exchange-AntiSpam-MessageData-0:X-MS-Exchange-AntiSpam-MessageData-1; bh=1SODgW2UDhmF6w87PmRidCAKOA5siSbqxkN/v1rwYeI=; b=yAZ6Ztiv26bpdKJ0uWFErq6FNn6q07hKSZhEIEXPvQfPHAfw4z/BD14BtlnM9mtLvsr7cEvm/6+zU/aEIorAd8YRdNGE8DmdM5sgcAqqUDIQmfzllgDT5ksWnMv2477zcU0rlZCBII0g5agAYbLzQGotktGdabZQB/lXX4WAOdB9a8d62KqApT7xLJJOi/I/Yg5VO277hN5WS/OVsnQIt9Hi6+d0LgJ0Dfo3Fvqe8Mstye7+6nxPoDqMQuXbFrLZmuTkH6XpeUI2ND840LAropoAWfIZoqgWNDnXcJLBCI/1o0QQV17vOIN/00ICmqeW9raT59j89RNnRk4WU9UGJg== ARC-Authentication-Results: i=1; mx.microsoft.com 1; spf=pass smtp.mailfrom=nvidia.com; dmarc=pass action=none header.from=nvidia.com; dkim=pass header.d=nvidia.com; arc=none DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=Nvidia.com; s=selector2; h=From:Date:Subject:Message-ID:Content-Type:MIME-Version:X-MS-Exchange-SenderADCheck; bh=1SODgW2UDhmF6w87PmRidCAKOA5siSbqxkN/v1rwYeI=; b=CfirnHQlwGkf/1FzG3nGD2y+zYMNj9rMYt+O7QfysgHzkEDayasEX93BUzEDyQaXvi1qDPPlf+bJ3lLf1AEXjqolpPZT7P3uJrLMj0Q5KK6SoZ/rDkA1n9hz+hhGFEc6oNudNT4KDuzbnj13NAq0DPVq6Pj2WIk/ed/tZdnL07he8K/WNq8qPf3ijCzgOgHtFdPKYF3BqLvkoxRL7+WyRwSKSdbOvXcPDPpVzO12I0LfmwjcVYW1ak5xmIiClgNxEBjrcjqyF1jWzu/cOeNcGPutD3l/zeoIA0ojedznjdw2TrQBGEIUM3U0PnyDPH9XyZs6IylvWcb1SJRtEWoWHg== Authentication-Results: dkim=none (message not signed) header.d=none;dmarc=none action=none header.from=nvidia.com; Received: from DS0PR12MB6413.namprd12.prod.outlook.com (2603:10b6:8:ce::10) by PH0PR12MB7930.namprd12.prod.outlook.com (2603:10b6:510:283::7) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.21.360.10; Thu, 27 Aug 2026 14:23:08 +0000 Received: from DS0PR12MB6413.namprd12.prod.outlook.com ([fe80::e82a:6673:4142:37fa]) by DS0PR12MB6413.namprd12.prod.outlook.com ([fe80::e82a:6673:4142:37fa%5]) with mapi id 15.21.0360.008; Thu, 27 Aug 2026 14:23:08 +0000 From: Eliot Courtney Date: Thu, 27 Aug 2026 23:12:55 +0900 Subject: [PATCH v2 6/8] gpu: nova-core: add NVKV typed encoding Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260827-b4-nvkv-v2-6-0de9d5c8658c@nvidia.com> References: <20260827-b4-nvkv-v2-0-0de9d5c8658c@nvidia.com> In-Reply-To: <20260827-b4-nvkv-v2-0-0de9d5c8658c@nvidia.com> To: Danilo Krummrich , Lorenzo Stoakes , Vlastimil Babka , "Liam R. Howlett" , Uladzislau Rezki , Miguel Ojeda , Boqun Feng , Gary Guo , =?utf-8?q?Bj=C3=B6rn_Roy_Baron?= , Benno Lossin , Andreas Hindborg , Alice Ryhl , Trevor Gross , Daniel Almeida , Tamir Duberstein , Alexandre Courbot , =?utf-8?q?Onur_=C3=96zkan?= , David Airlie , Simona Vetter Cc: John Hubbard , Alistair Popple , Timur Tabi , rust-for-linux@vger.kernel.org, linux-kernel@vger.kernel.org, nova-gpu@lists.linux.dev, dri-devel@lists.freedesktop.org, Eliot Courtney X-Mailer: b4 0.15.2 X-ClientProxiedBy: TY4P301CA0025.JPNP301.PROD.OUTLOOK.COM (2603:1096:405:2b1::7) To DS0PR12MB6413.namprd12.prod.outlook.com (2603:10b6:8:ce::10) Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 X-MS-PublicTrafficType: Email X-MS-TrafficTypeDiagnostic: DS0PR12MB6413:EE_|PH0PR12MB7930:EE_ X-MS-Office365-Filtering-Correlation-Id: 6e62cc15-de93-43ab-a7ac-08df0446b7a3 X-MS-Exchange-SenderADCheck: 1 X-MS-Exchange-AntiSpam-Relay: 0 X-Microsoft-Antispam: BCL:0;ARA:13230040|23010399003|7416014|376014|10070799003|1800799024|366016|18002099003|22082099003|3023799007|11063799006|56012099006|10067099003|921020; X-Microsoft-Antispam-Message-Info: 38ZBLmGY8g091MV5SSd9err3CaiZc9lE9zmPxaBhjP5NHu41HgENCZpiCIylGKQuLgt941iHdHgDWTc4qjDKlWuDSI3/kkCmDe3lvEh+9YvvLqcUmLndc3trSUm4f5hGFW8jlkBIwrWN1x9pDjjxlaeRdlsziiKzU0FY2H233dIUef8cG6Al/X743SZMYcd7Wizfd30vVv8blBwgWVh8hoUF5LMAAy0KkcWUualNBBe0sbPDtmiDHzEUcWOSI3vn+NEcyBlb2lvaGP/jVA38j7lv90dmEzkG6484PeCRSTQRntgbYcd57n1/Nc+v1n8Ig/X5w+edrGwh63frm2h/thO6JfZQZnPEhyEw5oAD/pzMqNK5dZJvr3ZULyDhFmgAT9ij/S7VNkM1wz/5D2tIOrYPwPfYjAOL2NmD8PpSd/S+Hh9psr3SlYtc5O7J2kc3KHK2rTXXYADf4I9hvPOnexheug6DXsWGBE5ZV7BZ3PB0jD3nlGbiGRAhqjBw2+hnoUbly2Frzxm3QDooFrlSiQBmjWsDwrc+IdrhVgTZrhJi+C7gOU8WbdnLQ8fhsMT4UHdoVxO9vqzNc/mdBh6u9wtHafCmyjCNdrzaq2t6bW3e3n7uWvn12IKt9hDJKOdgmZI6CsPuZIldcw/PWBxoTg5kChakNeAEOT2eP8PB8fRqgKo7X7iNl0Huw85lGQ208tY/sS1eQ4pa6Ghhun60bA== X-Forefront-Antispam-Report: CIP:255.255.255.255;CTRY:;LANG:en;SCL:1;SRV:;IPV:NLI;SFV:NSPM;H:DS0PR12MB6413.namprd12.prod.outlook.com;PTR:;CAT:NONE;SFS:(13230040)(23010399003)(7416014)(376014)(10070799003)(1800799024)(366016)(18002099003)(22082099003)(3023799007)(11063799006)(56012099006)(10067099003)(921020);DIR:OUT;SFP:1101; X-MS-Exchange-AntiSpam-MessageData-ChunkCount: 2 X-MS-Exchange-AntiSpam-MessageData-0: =?utf-8?B?NmlEOFFuN0Q2TXc2UTYvM3hGVTBnK0RLOHhweDQzYW9CSlpsY3ZSTlAwSDJ4?= =?utf-8?B?MXNVVUVrRUdvTmJnbm1GdGM3dndKWHQ4OW5qR3Bwam1DU0h0ZjVIQ1Q5cDNx?= =?utf-8?B?V3Y3NjI1SExrazZPblZrak8rR1U3SisyT1BHT05BYjYxbTIvWVRha1g1NkRR?= =?utf-8?B?NkI5WDNWbXZ6TWIxNEFpT3NHTU90ZVlsQng5Y0NHZFluRStHaVdoMFFka01o?= =?utf-8?B?b3BXY2RxeGV4ODF3cUVGcE1SZmFKOVdBRWxlMjRsd0NSV08wekVQQjZWd1Fa?= =?utf-8?B?dEgwMyt0c0xnT3ZqcWI0VXE1NEhFL1R4MTFSZXZONFdJOU1jZVl4R2JUWFc1?= =?utf-8?B?bERheHVUVndMdE1nekVvcEg5NDdJVmI1QVNNVTIzYXZ5WkRzT3RRUmZUZlJ5?= =?utf-8?B?WkNQSndTWVlhWVN5cmZqM3h4MjY2Vkx4Y2toOFJJOU81c0N3NHk2aXJsM1JY?= =?utf-8?B?K3hXQ3pyVTZnbEw4S295SFFrQ1dGSXJXYkJGZGRNa3ZaQmt1WTZkUDVDZkZX?= =?utf-8?B?a2tjTzB3eXhPMFc2Sm11VlRoL3VMalFMZHJJalp2cGx6QllHT29EeUVKcVhR?= =?utf-8?B?Q0Q0aUlPOERObjNCZVBpRC80UGt0K1hjck9JalJkSkIvbEJEZkJNakRHb2Uz?= =?utf-8?B?TDhOV3g5VDMzREdWQmJyb0NwOXNJbkFpcy83TlVyOEkyUkdQRE0rNDJUdlVB?= =?utf-8?B?Z0xSelFBYWhwU0M4SGRIYi8vekFVQUFrRm1CSUtnVkNmSlVpSkllekNmTEkr?= =?utf-8?B?dVMzb3libGUyb3U1TzJLNmt5VE5abUVVM3ZXUElvK2YxcG9DaCtMV0szWGNx?= =?utf-8?B?QmVwMWlpOEtVeHRMTHhGdWFETFExSkRuSDE0VC83ckhORmo0NGRuR0d2bzVH?= =?utf-8?B?a1N6WlNwaTVsL2pBTWcwY096ZUYwTCt6aGJIS0ZnYmU1YlhxbGs5dmtGMDQy?= =?utf-8?B?cE1VZlRFZFBuWkh6dDhwNTQyQk94TzQrSUE3RjFCT2NwaEt1WXFKQnRuMTNs?= =?utf-8?B?VzR1TjhKZUwwcGZ6Q0hjOVRpSVZObmtVT1g3NHp0L0pTazJRYzhsaWhSRDQv?= =?utf-8?B?bFFNTDY3Sm1ZeTlyNlhzSk9hV3NuaXBkeU4zYTVVeUgvRktnZ0JWTUVGWVZI?= =?utf-8?B?N2Z1YkVvWXAzOWRMRDRtLzUyTzBaVmtjSVdSNE1ZSVE2bUNvK3NDZEo4Z3ZU?= =?utf-8?B?ZGpzRmp6SkZHRzRETFdJblFkMDNTc0tTanVsMEpyWlljeDladlNweEdsQ2pw?= =?utf-8?B?UVpPTndPZGI3Rm51dkM0UStmdGh5Z2RNbUZ5cjZkUU5iWGs3ZC83OVp2WlZV?= =?utf-8?B?ZWVpWUJMUjRob2JtdHFQcG00ajNLaExncnRvNngwL3J0RnRRNmtVNnZ5RWJ3?= =?utf-8?B?YlJEcUhyajZscWxTSHVjOGkrc05tMXhObmdiTzBoQXkvOGNKT21BMFA2Y2hO?= =?utf-8?B?ZzZzL2FNVnlBWEV5cUNFVGV6RHgyaEUvSGFUWVZDMnk2Z3MzbHFXMDRvb0E5?= =?utf-8?B?a3RRbkFQWXBibS96L2FjQ29YOWdpSUZtUnkrUEtEVTVtVUZsYWxCWVY2K3Jw?= =?utf-8?B?NGw0aWlYYmF5ZmtmR3REakcxeSs4aHhCTVEzYlBtTjAzOCs2M2RhK3pKaHhV?= =?utf-8?B?SllUZWZTejNFMTlINjN0anZrbkV1MlJXVDcya3BNRW9CRmpXcDFpU1Ayc2VJ?= =?utf-8?B?bTRod0hpRlcyTXVDdmI4MjlxNTNvY0ZFalhTN0k1YjdpaUR0cE9lSVRBQWd3?= =?utf-8?B?UDBVNHY3RytnMFcrSjVuQlFwYnJsZUlEZS9GVTMySmUvbFZLMWk1bkE0THIr?= =?utf-8?B?ZFp1em9pR3kvMkJXUXJOMm5nRStNVUpsa01XV2ova0NsTk03Z214bEJDNUoy?= =?utf-8?B?eW1CdSsvZ3lVN1l1S005cDVFYkR3Z25jWUIrckN1QXQ4MTdLTGJvOXVZakFY?= =?utf-8?B?NGtsWFE4SmxjcDJ4Yjdkb0NqSW1ua042YTgvZmxDZXVaQk4yQm1RSGF3Rm9R?= =?utf-8?B?ZExEZko3SllGbkVPbjFCQW9DKzdIaDVpeDJ1aXpSalBkWnhpVkdiQjJsZzln?= =?utf-8?B?T2pBZ3ZOSWp2YWFWemU1YWxqUWUrRWVPNGtUWGl0bU1pNGxNVGxEeE1abEJo?= =?utf-8?B?eXhzczdCVXNiQ29CWDl0SW9xWVI0b1drNkhETDdPUXpnT0JRZkZNdUdFQi9J?= =?utf-8?B?K0lTZ21GWGNQaFJ5TTdvdHY0aUdNWHNuanNBelZrM3hIVFhHR1dmNE0vY0lh?= =?utf-8?B?Z2c1aUVOWlZQZkt2WGNnWlBLUENVNHp3WEUwaDI1UHhabEx4dnRObUJEcGNS?= =?utf-8?B?WWZ2UFNSK1pRYTVxTnpwRzhscnJodDhMWVhVamRua0V3Uis0b2hJZ1B6YUhV?= =?utf-8?Q?pFPYsiTYfUpuYwyu4FFii9Jy3FLQ9V/21vbWmBJ8nV6Pd?= X-MS-Exchange-AntiSpam-MessageData-1: 6QzfDdA60t5nCQ== X-OriginatorOrg: Nvidia.com X-MS-Exchange-CrossTenant-Network-Message-Id: 6e62cc15-de93-43ab-a7ac-08df0446b7a3 X-MS-Exchange-CrossTenant-AuthSource: DS0PR12MB6413.namprd12.prod.outlook.com X-MS-Exchange-CrossTenant-AuthAs: Internal X-MS-Exchange-CrossTenant-OriginalArrivalTime: 27 Aug 2026 14:23:08.6840 (UTC) X-MS-Exchange-CrossTenant-FromEntityHeader: Hosted X-MS-Exchange-CrossTenant-Id: 43083d15-7273-40c1-b7db-39efd9ccc17a X-MS-Exchange-CrossTenant-MailboxType: HOSTED X-MS-Exchange-CrossTenant-UserPrincipalName: sy0U+Xc6ljGaauiQTnK1g5urx5bvFKycxOJP30HzHxrMzlBviALgw2ItaoNT+sfboAdopX0FWIoD9bL9BSWxNQ== X-MS-Exchange-Transport-CrossTenantHeadersStamped: PH0PR12MB7930 For struct-like GMCAPI messages encoding field by field manually is noisy. Add some type machinery and a macro to automate encoding of struct-like messages. The `Encodeable` trait can be implemented by any type to say that it can be encoded into an NVKV `Encoder`. Add a simple `nvkv_encode!` macro that works on structs and encodes each field in order. Provide some base types, such as `Key` which statically associates a NVKV key with some value, to avoid having to make a lot of newtypes and implement `Encodeable` on them. Signed-off-by: Eliot Courtney --- drivers/gpu/nova-core/gsp/nvkv.rs | 49 ++++++++- drivers/gpu/nova-core/gsp/nvkv/encode.rs | 178 +++++++++++++++++++++++++++= ++++ 2 files changed, 226 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/nova-core/gsp/nvkv.rs b/drivers/gpu/nova-core/gsp/= nvkv.rs index cbeee7f376b6..10dcbb9e602c 100644 --- a/drivers/gpu/nova-core/gsp/nvkv.rs +++ b/drivers/gpu/nova-core/gsp/nvkv.rs @@ -10,8 +10,13 @@ //! naturally maps to storing a &str with the GPU name. =20 #![expect(unused_imports)] +#![cfg_attr(not(CONFIG_KUNIT), expect(unused_macros))] =20 -use core::ops::Deref; +use core::marker::PhantomData; +use core::ops::{ + Deref, + DerefMut, // +}; =20 use kernel::{ alloc::{ @@ -92,6 +97,48 @@ fn deref(&self) -> &Self::Target { /// The index of an NVKV value. pub(crate) type Index =3D Bounded; =20 +/// A static association between an NVKV key `KEY_ID` and the storage of i= ts value. +/// +/// Use with the encoder or decoder macros `nvkv_encode!` and `nvkv_decode= !` to let them know how to +/// map the value `Key` to/from encoded data. For brevity, = `As` inserts an additional +/// conversion (`From`) to avoid having to implement [`Encodable`] for man= y types. For example, +/// enums that are easily convertible to a u32 can have `As =3D u32` and r= ely on the existing encoding +/// for u32. +#[repr(transparent)] +pub(crate) struct Key(pub(crate) T, Phan= tomData); + +impl From for Key { + fn from(value: T) -> Self { + Self(value, PhantomData) + } +} + +impl<'a, T, const KEY_ID: KeyId, As, const N: usize> From<&'a [T; N]> for = Key<&'a [T], KEY_ID, As> { + fn from(value: &'a [T; N]) -> Self { + Self(&value[..], PhantomData) + } +} + +impl Deref for Key { + type Target =3D T; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl DerefMut for Key { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +impl Default for Key { + fn default() -> Self { + Self(T::default(), PhantomData) + } +} + bitfield! { /// The op word that starts each NVKV operation. struct Op(u64) { diff --git a/drivers/gpu/nova-core/gsp/nvkv/encode.rs b/drivers/gpu/nova-co= re/gsp/nvkv/encode.rs index 6c1a9cbd90e8..0047be65e8a9 100644 --- a/drivers/gpu/nova-core/gsp/nvkv/encode.rs +++ b/drivers/gpu/nova-core/gsp/nvkv/encode.rs @@ -8,11 +8,153 @@ use super::{ EncodedStream, Index, + Key, KeyId, Op, Opcode, // }; =20 +/// A type that can encode itself into an [`Encoder`]. +pub(crate) trait Encodable { + /// Encodes `self` into `encoder`. + fn encode(&self, encoder: &mut Encoder) -> Result; +} + +/// Defines a struct together with its [`Encodable`] implementation. +/// +/// The implementation encodes each field in declaration order. Each field= type must implement +/// [`Encodable`], which is done already for types like `Key`. +/// +/// # Examples +/// +/// ``` +/// nvkv_encode! { +/// struct Request { +/// id: Key, +/// name: Key<&'static [u8], 0x0002>, +/// } +/// } +/// ``` +macro_rules! nvkv_encode { + ( + $(#[$attr:meta])* + $vis:vis struct $name:ident { + $( + $(#[$field_attr:meta])* + $field_vis:vis $field:ident : $ty:ty + ),* $(,)? + } + ) =3D> { + $(#[$attr])* + $vis struct $name { + $( + $(#[$field_attr])* + $field_vis $field: $ty, + )* + } + + impl $crate::gsp::nvkv::Encodable for $name { + #[inline(always)] + fn encode(&self, encoder: &mut $crate::gsp::nvkv::Encoder) -> = ::kernel::error::Result { + $( $crate::gsp::nvkv::Encodable::encode(&self.$field, enco= der)?; )* + Ok(()) + } + } + }; +} +pub(crate) use nvkv_encode; + +/// A value with a specific index that encodes under the NVKV key `KEY_ID`. +struct IndexedKey { + index: Index, + value: T, +} + +impl IndexedKey { + /// Creates a key with the given index and value. + pub(crate) fn new(index: Index, value: T) -> Self { + Self { index, value } + } +} + +impl Encodable for IndexedKey { + #[inline(always)] + fn encode(&self, encoder: &mut Encoder) -> Result { + encoder.encode_u32(KEY_ID, self.index, self.value) + } +} + +impl Encodable for IndexedKey { + #[inline(always)] + fn encode(&self, encoder: &mut Encoder) -> Result { + encoder.encode_u64(KEY_ID, self.index, self.value) + } +} + +impl Encodable for IndexedKey<&[u8], KEY_ID> { + #[inline(always)] + fn encode(&self, encoder: &mut Encoder) -> Result { + encoder.encode_array8(KEY_ID, self.index, self.value) + } +} + +impl Encodable for IndexedKey<&[u32], KEY_ID> { + #[inline(always)] + fn encode(&self, encoder: &mut Encoder) -> Result { + encoder.encode_array32(KEY_ID, self.index, self.value) + } +} + +impl Encodable for IndexedKey<&[u64], KEY_ID> { + #[inline(always)] + fn encode(&self, encoder: &mut Encoder) -> Result { + encoder.encode_array64(KEY_ID, self.index, self.value) + } +} + +impl Encodable for IndexedKey<[u8; N]= , KEY_ID> { + #[inline(always)] + fn encode(&self, encoder: &mut Encoder) -> Result { + encoder.encode_array8(KEY_ID, self.index, &self.value) + } +} + +impl Encodable for IndexedKey<[u32; N= ], KEY_ID> { + #[inline(always)] + fn encode(&self, encoder: &mut Encoder) -> Result { + encoder.encode_array32(KEY_ID, self.index, &self.value) + } +} + +impl Encodable for IndexedKey<[u64; N= ], KEY_ID> { + #[inline(always)] + fn encode(&self, encoder: &mut Encoder) -> Result { + encoder.encode_array64(KEY_ID, self.index, &self.value) + } +} + +impl Encodable for Key +where + IndexedKey: Encodable, + As: From, + T: Copy, +{ + #[inline(always)] + fn encode(&self, encoder: &mut Encoder) -> Result { + IndexedKey::new(Index::new::<0>(), As::from(self.0)).encode(encode= r) + } +} + +impl Encodable for Option { + #[inline(always)] + fn encode(&self, encoder: &mut Encoder) -> Result { + if let Some(value) =3D self { + value.encode(encoder)?; + } + Ok(()) + } +} + /// An encoder for an NVKV stream. pub(crate) struct Encoder { stream: EncodedStream, @@ -207,4 +349,40 @@ fn encode_all_value_kinds() -> Result { =20 Ok(()) } + + // Tests that encoding via the `nvkv_encode!` macro works correctly. + #[test] + fn encode_typed_struct() -> Result { + const U32_KEY: KeyId =3D 0x0001; + const U64_KEY: KeyId =3D 0x0002; + const NAME_KEY: KeyId =3D 0x0003; + const FIXED_KEY: KeyId =3D 0x0004; + const OPT_KEY: KeyId =3D 0x0005; + + nvkv_encode! { + struct TypedRequest { + a: Key, + b: Key, + name: Key<&'static [u8], { NAME_KEY }>, + fixed: Key<[u8; 4], { FIXED_KEY }>, + opt: Option>, + } + } + + let request =3D TypedRequest { + a: 0x89ab_cdef.into(), + b: 0x0123_4567_89ab_cdef.into(), + name: b"name\0".into(), + fixed: [1u8, 2, 3, 4].into(), + opt: None, + }; + + let mut encoder =3D Encoder::new(); + request.encode(&mut encoder)?; + let encoded =3D encoder.finish(); + + assert_eq!(encoded.len(), 7); + + Ok(()) + } } --=20 2.55.0 From nobody Sun Sep 27 00:36:29 2026 Received: from PH7PR06CU001.outbound.protection.outlook.com (mail-westus3azon11010058.outbound.protection.outlook.com [52.101.201.58]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 73B7247CA73; Thu, 27 Aug 2026 14:23:39 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=fail smtp.client-ip=52.101.201.58 ARC-Seal: i=2; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1787840627; cv=fail; b=hBtaQa/L5t0GZ/+5UGPbq2dKQbrlvLPpSXxHUJUxinoU4CTv94aOvtPsBJbjAXQQKXP8Zmil1H9DvF52VGAjPc0/wfsAzWno6ySYaWdNWGZTNjrjw7UOw+WVyGsrCAJ7A8mkLLFfwJxFIR6Wrrn5uqjM1SMWXVdMgK91kftL98Q= ARC-Message-Signature: i=2; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1787840627; c=relaxed/simple; bh=oBEUvtybD10BHdVAoNTTCrYuY7umHApnm55Oj+cicxk=; h=From:Date:Subject:Content-Type:Message-Id:References:In-Reply-To: To:Cc:MIME-Version; b=T4yP//r4klszW4YQCAp3rw2Ok8R4i1T4b2E9MKUA3kpho1FMRrGdJ/ZJhBl3voJ1OMe3yh6MNcYKwqcPGCo4Z3US0HfwJN2uFMVwFIa1Bb4IlRxsN1gBZWg4dsPndnzAaXisN4wNfL7jCOEBAdOYHbxtP9UxvDLH9smYUxujm6U= ARC-Authentication-Results: i=2; smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=nvidia.com; spf=fail smtp.mailfrom=nvidia.com; dkim=pass (2048-bit key) header.d=Nvidia.com header.i=@Nvidia.com header.b=n8KYf9r7; arc=fail smtp.client-ip=52.101.201.58 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=nvidia.com Authentication-Results: smtp.subspace.kernel.org; spf=fail smtp.mailfrom=nvidia.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=Nvidia.com header.i=@Nvidia.com header.b="n8KYf9r7" ARC-Seal: i=1; a=rsa-sha256; s=arcselector10001; d=microsoft.com; cv=none; b=t8J40GqhExfiv1s5rF5aNllcqa1/aQ5SqfwWeVumoLFxZ3HMc0haaxI0Euzm3xvjaeX1CDAYRhXOz7yEj2+u9bNKw9/41xFDkCxAGIeo3DB5d8HYnwUouWe2xaKXeVK0B+8oemLiRFNVty9njWQ3548O4aWatrn4nR3GcF59e+JLjK7sxC4HsFcmLgwnSoMwXuEQbsdL7YKOJwKY1Z9YgvN5uKWPJLXwg1weCynYRPvMfVCNTUFIWO8QtK1ifaWkRXqTqXpPOIpXYTiMdPXDWnua+Z7y9RE4t859XtO6vNqzxU2TVGsJ44xnBJZrMu675ovMfzARqt1wM8scQBbfKw== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=microsoft.com; s=arcselector10001; h=From:Date:Subject:Message-ID:Content-Type:MIME-Version:X-MS-Exchange-AntiSpam-MessageData-ChunkCount:X-MS-Exchange-AntiSpam-MessageData-0:X-MS-Exchange-AntiSpam-MessageData-1; bh=D983f/t85unLVNPPpCPjE41y8tCVHZPXVxsZ1xeLHag=; b=EqmmTTAdBwmm5osSgr7TVEPusz2UG4k4+EZZRli0onlcVN4IGV0lC7KBj18u3BzBMuKTmqYlMvqcH73VpiWFhvGh+bIg4A5UGZOPk987M0BoH6I31YjYGltxNsf3YEoE2mhQRask9v1UScmpk59eCsjw/YEkE2pD2m7i9VyMzgnozveKOUc4khN1U/PThkAPy/2PAB7CZmdOJfXTLff3+mCKcsenzZnuOaOlCVC22M/konX1bkAkIdwprdTGXOm9JnEdSRdkpkau8U9RzjuJtEj+XiAQRHEvbF9PnkBYHyhjisK4UWxNLqsuBqQrQLhrOaKW/NGNAAmDfGmA4YqxsQ== ARC-Authentication-Results: i=1; mx.microsoft.com 1; spf=pass smtp.mailfrom=nvidia.com; dmarc=pass action=none header.from=nvidia.com; dkim=pass header.d=nvidia.com; arc=none DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=Nvidia.com; s=selector2; h=From:Date:Subject:Message-ID:Content-Type:MIME-Version:X-MS-Exchange-SenderADCheck; bh=D983f/t85unLVNPPpCPjE41y8tCVHZPXVxsZ1xeLHag=; b=n8KYf9r71U78+8G/KWZweMF50T5ByG/PnCu4pDZ/Ajiy3d5IGa8AtWoDt+16ITIpTIjD+le9ChLetl3qXACcccqKbwUroY0eCsuJp2pDf6oPxAkecBJavI1P4n5iXyjgg8Ez6IU1+rhn3EzlWys7Lu2WNpi7qxKj5sTnuwvuRjRE/N49VddrOr6bAQqFoLx3jZgcRi2W+5+U5E8RlqyPPCIOLK2LmiyBCK37XlNJyGvjkYrr7LMA56cOyZFGOJ/HsRuzOUN/HU3PnUqQIBSdTzTaRXM4pmj0Oq7S4XpAmm39hrp9mI89uAvjlMHOuJZoWO7WRBrKwEJdgvi1P3sInA== Authentication-Results: dkim=none (message not signed) header.d=none;dmarc=none action=none header.from=nvidia.com; Received: from DS0PR12MB6413.namprd12.prod.outlook.com (2603:10b6:8:ce::10) by PH0PR12MB7930.namprd12.prod.outlook.com (2603:10b6:510:283::7) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.21.360.10; Thu, 27 Aug 2026 14:23:12 +0000 Received: from DS0PR12MB6413.namprd12.prod.outlook.com ([fe80::e82a:6673:4142:37fa]) by DS0PR12MB6413.namprd12.prod.outlook.com ([fe80::e82a:6673:4142:37fa%5]) with mapi id 15.21.0360.008; Thu, 27 Aug 2026 14:23:12 +0000 From: Eliot Courtney Date: Thu, 27 Aug 2026 23:12:56 +0900 Subject: [PATCH v2 7/8] gpu: nova-core: add NVKV typed decoding Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260827-b4-nvkv-v2-7-0de9d5c8658c@nvidia.com> References: <20260827-b4-nvkv-v2-0-0de9d5c8658c@nvidia.com> In-Reply-To: <20260827-b4-nvkv-v2-0-0de9d5c8658c@nvidia.com> To: Danilo Krummrich , Lorenzo Stoakes , Vlastimil Babka , "Liam R. Howlett" , Uladzislau Rezki , Miguel Ojeda , Boqun Feng , Gary Guo , =?utf-8?q?Bj=C3=B6rn_Roy_Baron?= , Benno Lossin , Andreas Hindborg , Alice Ryhl , Trevor Gross , Daniel Almeida , Tamir Duberstein , Alexandre Courbot , =?utf-8?q?Onur_=C3=96zkan?= , David Airlie , Simona Vetter Cc: John Hubbard , Alistair Popple , Timur Tabi , rust-for-linux@vger.kernel.org, linux-kernel@vger.kernel.org, nova-gpu@lists.linux.dev, dri-devel@lists.freedesktop.org, Eliot Courtney X-Mailer: b4 0.15.2 X-ClientProxiedBy: TY6P301CA0023.JPNP301.PROD.OUTLOOK.COM (2603:1096:405:3bf::15) To DS0PR12MB6413.namprd12.prod.outlook.com (2603:10b6:8:ce::10) Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 X-MS-PublicTrafficType: Email X-MS-TrafficTypeDiagnostic: DS0PR12MB6413:EE_|PH0PR12MB7930:EE_ X-MS-Office365-Filtering-Correlation-Id: 61ccf46a-b536-431c-e6ce-08df0446b9fd X-MS-Exchange-SenderADCheck: 1 X-MS-Exchange-AntiSpam-Relay: 0 X-Microsoft-Antispam: BCL:0;ARA:13230040|23010399003|7416014|376014|10070799003|1800799024|366016|18002099003|22082099003|3023799007|6133799003|11063799006|56012099006|10067099003|921020; X-Microsoft-Antispam-Message-Info: aPT7sso6mW/uQa8Tfz2FMa7K2u2Q6ZdhzTB1wixnlYvVJxcnOaDjNhMECqm0za8+76+3TNaBM6bI6haZZl0LM4qHDFPo7Ikiqv9iGpemWCQ59Qc4w581oM/+hDwAG7YoaTAsyAz3+Hky3yNPH/HsmTc+7KNPok4QuUWKpIZaKVfXt12pnROHTzZGKqKjHmeGZURr/SngUNEybXkZPiW5yQ8SVYKbWqOsVECw/0MpFPj6t6ApJ9oAEi6+yXLZqipGLLGpEyDzMfcXlxJddg2p/8wfQ26m8qYlDm8d+9sIxcSqQ67xjP9Vv+roJw1EUONbmcG1JSHjPf0ZVFTRAAk6qpZ7Mg+lE5B/KbIBT9d6q0N4+SAneueu8zwrGu6ujyeVLr0F/XvecY00Fa4nDb6KW3Sd3ws9lLFfOX8HlfXlhwfZwCSoxWoKSXQ/H1dgRjDhDjvMlNdbztba2ZX/aVkG9YhmMqDZFZSt0k9MXJIf9vBWZUcvDXv1gneUzRRDgrbHESyC0qbSfp0Fm2DNQxFGeLnom7fWQehuJmiFxEWlWJcDBbRevj/YsPRWZlD9XqinSP7R7ll9+s56px5DeH63LWXPbfLRTIV2ZGERIB+t5+k89HdwBHxhixcnNFiiib9MGXSBs7SBBclqdQl4BbUty5AimVCBCWLBoGeP8okzm0fTryE4l/nteA5WTgg8rCZaXjwHcUZA9Z1oDx2ZXS7DWg== X-Forefront-Antispam-Report: CIP:255.255.255.255;CTRY:;LANG:en;SCL:1;SRV:;IPV:NLI;SFV:NSPM;H:DS0PR12MB6413.namprd12.prod.outlook.com;PTR:;CAT:NONE;SFS:(13230040)(23010399003)(7416014)(376014)(10070799003)(1800799024)(366016)(18002099003)(22082099003)(3023799007)(6133799003)(11063799006)(56012099006)(10067099003)(921020);DIR:OUT;SFP:1101; X-MS-Exchange-AntiSpam-MessageData-ChunkCount: 2 X-MS-Exchange-AntiSpam-MessageData-0: =?utf-8?B?N0hEY3NTSGVxUWlabWlRaTFZeDlncXUyRXJTTzVEUWZjTHg2OXhnK21KcU1T?= =?utf-8?B?ais3MkFPMFhWcWJwZlBxeWs3L0EvT0ZwQUhlTElXclNkaElheitPYWwyU3pW?= =?utf-8?B?NXZOc0hDRVA1dTRLZnhadlFGSDhWNlJGZVo3WHpEQXpmUzVCZSt2S3dCZWw4?= =?utf-8?B?L1U5NVVmTDlqQXREVGRZWWJPdWVtbFFaZURYSzU3K1cvNDBleGwrWWw2eFNP?= =?utf-8?B?RzFCanY4dHdhbXpxTm1ubFFyZDNCbjhMZkRJQmxvL2FEa1NLMEpoN1lSM1Fv?= =?utf-8?B?TzBpVWZpMkNlblRnMkNVbjhZWE9LTmRjUDd5RVE3TTljN0ZZSm5nTFhPVlpw?= =?utf-8?B?bThsNlpndjg4TGFKYzhzMTFValh1aFlVdVVVWWovSDJrTklzWXVPS09FeXlh?= =?utf-8?B?bjlRZTdXZ1hXeHJLS2xNcGxCS3JjUWtBSWlORGpVdFl3eGgxbXp2c1R0UGo3?= =?utf-8?B?ZnhDeGdXdldoWlNuMHYvcmI3SVFOcmxnSk1NQXh4ajQ1OVJMZXg5V1lSb281?= =?utf-8?B?SWRXZWxyaW9Deks1aHFyZi9yZkFBcXlxQ2hXU0JzYkV1WlpkMlVaRTdpMHBo?= =?utf-8?B?RlVPcXNOd012amVFalJSZFhGYmIzUko2ZkN3d2FuSjJWbm54ck9aVmlGOUlI?= =?utf-8?B?ZWNCdWVKSWl5U0RKOGxOU0hPRUVRWHhwRGh6K1RXNnY0QzJHSnN2NzFsVjRV?= =?utf-8?B?bTRzb1NhdHpsNEpnNGVzb0doK2xjMThyNUNnTFVUU0tEendPTjNKaXlMWDAy?= =?utf-8?B?QkFBUzcvTzI0T2pyMk9DWHM0YUpiZUI1OU5oQmY1cldaYU0yOU94ZVZobEcw?= =?utf-8?B?eURSOVhONHBORG5tKzYvdldzUndFQ1B6R2Exc3h1UzNHOUY1SjFGVmJUQ04w?= =?utf-8?B?TTdkSStoSHNGaDdMNENsL3EvMUV2bW5mK01jUlpjQ2NGVStGbU5hQWd4R1gr?= =?utf-8?B?NWQ2dWFKN25WcmVtTkRMSWlneGp1NTlkTUMxYWRaV0RlOTUxZW1OaFRxajBW?= =?utf-8?B?VjNiQTA1c0lOUGZhYTF1ZEh3VnVMYXBYeHJ2d2I1U0xoalhBMUd3ajd5VVFS?= =?utf-8?B?US9hUTdZM0dxeTlyaFVBMUtJT2VqTzVlSUhRYWk3TlZDZy9pMUN0V2ZUZFgy?= =?utf-8?B?Rzh6SzViR2NqY01tT1QyNk02ckNlZXNEbDdrMjhKSUdSQlpBdWtTaURQeUZG?= =?utf-8?B?SVZ0VFBSbDdYaURaL0w2YzNXWDZGR0pScG5yVWhQZEo0akhKcEhNOVNXdmZj?= =?utf-8?B?cldEYzhwQUZVaHFJZ2dzWllQSVJjRlRJREpDTkZaUmc5clJlazFzMWNEV0tD?= =?utf-8?B?UE8zYUpHYm1KcllRTXRPaytiMkZVMmk3UTZCdm9hL1dwNlhFUkdzY2hXK1Rw?= =?utf-8?B?ZmpzWmdkd0tPRDIrSXpFbjdzWDQ4bVNmd0RKNE8wOFdMaDd0VEViMDhNZ2VI?= =?utf-8?B?bG9kcW5WU1FWaENUb3ZLK1ZDaHhHL1lYSUZ1akI0RTNtamZBUTcya3dUenpN?= =?utf-8?B?TWVzR29HQnhZNEovSHJ5ajFJSjgyTGFIYjFIWFQ2QmIxczk3SnFyZzU2M0Fs?= =?utf-8?B?RU9KOHZaSzlUT1BITTRhdlUxV1J4QTByaGNESDVKM25pTjRKNnhqSlJQd3pC?= =?utf-8?B?OHFaQ3RSQ1NyQmt0a2g0bDdacitiY3V1VUpBQnAvWHNuTHkvWFJJZEVJK3VX?= =?utf-8?B?SmZ1a3daeG5LdnRuY1daR0JXZlNoRnI2YmM4NE8zcnUycDVJcW9lZ3hXUUJs?= =?utf-8?B?dlI5eUpxa3k4WGpFQXkvYWpUWmhvaDY4RjY4SFRxeHFWbVlDM2wwaTRiWFpq?= =?utf-8?B?RTJBN2FtOFlhS0UxRklLVTBiaGxCNGRxKzJvcHVrR3hqUlN1QnV1cDBCbXB4?= =?utf-8?B?a1RWRTdUK0w2RFZIc2REMk9mQkJlenNvRm45UXFWWE1IUC9tTU4yZTR5bzky?= =?utf-8?B?L3h6OG42bCthdkV3bmJrT0dmdUo1VTN3TXFVQ1RyMDhRZ2JmSThjcGRUK21V?= =?utf-8?B?aFRaT3I1RTBxOUJSN3c1Zis2aFFIenNmalFqZjFCQkJBMGl3TWpQZWNCYlFB?= =?utf-8?B?NWd4MnpFbVFxZnh5N3dUSWtObVg4K3JyaXJyVGdLSzlraTlCaWVyUGhEV1VQ?= =?utf-8?B?b3Y5QVNvYkhSelVWbzJuRFJHcTJxS1FDenJlWWMyc0V2UDFjeXFYZWpjL2dw?= =?utf-8?B?UE96bkphUzdGbUpyZ21MdUFyZy9ncFVHa1ludy9ELzYvK0tBQ1YyV0xhQlFD?= =?utf-8?B?Q1hleWM5MTI2Y1lGQTBNZW1PUnZFTis1ckhtQWZNQ0xScUk2aXJRa3Q5WWhO?= =?utf-8?B?OUVhK1YwVUdUOVJjL1BWVHgrUC9NWGVpczh0ZEtXVlV5MnoxRFpTbjlJajVJ?= =?utf-8?Q?whctKMB823p9vqjPHEYc58aIKfYC8IrKhTXSLzYDvULYS?= X-MS-Exchange-AntiSpam-MessageData-1: 49QzzMO1mbfQjA== X-OriginatorOrg: Nvidia.com X-MS-Exchange-CrossTenant-Network-Message-Id: 61ccf46a-b536-431c-e6ce-08df0446b9fd X-MS-Exchange-CrossTenant-AuthSource: DS0PR12MB6413.namprd12.prod.outlook.com X-MS-Exchange-CrossTenant-AuthAs: Internal X-MS-Exchange-CrossTenant-OriginalArrivalTime: 27 Aug 2026 14:23:12.6309 (UTC) X-MS-Exchange-CrossTenant-FromEntityHeader: Hosted X-MS-Exchange-CrossTenant-Id: 43083d15-7273-40c1-b7db-39efd9ccc17a X-MS-Exchange-CrossTenant-MailboxType: HOSTED X-MS-Exchange-CrossTenant-UserPrincipalName: YpVEHiJq7X/fSYBNW+7oyX5voRxCxOvHQlUTfgl8ljYkemFls6r03i/F2cj4QLHiQD0lHtqyFjhQJQrJ6uaUFw== X-MS-Exchange-Transport-CrossTenantHeadersStamped: PH0PR12MB7930 Similar to the typed encoding layer, add some decoding type machinery. Add a simple macro `nvkv_decode!` which implements `Schema` for a struct by composing visit calls to each member. Add some common `Schema` kinds, such as `Array` which collects an array value into a fixed maximum size array, and `Required` which fails a decode if the value is not sent. Signed-off-by: Eliot Courtney --- drivers/gpu/nova-core/gsp/nvkv.rs | 12 +- drivers/gpu/nova-core/gsp/nvkv/decode.rs | 480 +++++++++++++++++++++++++++= +++- 2 files changed, 488 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/nova-core/gsp/nvkv.rs b/drivers/gpu/nova-core/gsp/= nvkv.rs index 10dcbb9e602c..7d58ca91cbc3 100644 --- a/drivers/gpu/nova-core/gsp/nvkv.rs +++ b/drivers/gpu/nova-core/gsp/nvkv.rs @@ -9,7 +9,7 @@ //! function calls will map to some struct - for example, f(GPU_NAME_STRIN= G_KEY, 0, b"some gpu") //! naturally maps to storing a &str with the GPU name. =20 -#![expect(unused_imports)] +#![cfg_attr(not(CONFIG_KUNIT), expect(unused_imports))] #![cfg_attr(not(CONFIG_KUNIT), expect(unused_macros))] =20 use core::marker::PhantomData; @@ -21,7 +21,8 @@ use kernel::{ alloc::{ allocator::KVmalloc, - Allocator, // + Allocator, + ArrayVec, // }, bitfield, num::Bounded, @@ -139,6 +140,13 @@ fn default() -> Self { } } =20 +/// A schema field for an array value under the NVKV key `KEY_ID`. +#[derive(Default)] +#[repr(transparent)] +pub(crate) struct Array { + vec: ArrayVec, +} + bitfield! { /// The op word that starts each NVKV operation. struct Op(u64) { diff --git a/drivers/gpu/nova-core/gsp/nvkv/decode.rs b/drivers/gpu/nova-co= re/gsp/nvkv/decode.rs index ceb97e73e100..7f5310857764 100644 --- a/drivers/gpu/nova-core/gsp/nvkv/decode.rs +++ b/drivers/gpu/nova-core/gsp/nvkv/decode.rs @@ -3,16 +3,356 @@ =20 #![cfg_attr(not(CONFIG_KUNIT), expect(dead_code))] =20 -use kernel::prelude::*; +use core::convert::Infallible; +use core::marker::PhantomData; + +use kernel::{ + alloc::ArrayVec, + prelude::*, // +}; +use pin_init::init_array_from_fn; =20 use crate::gsp::nvkv::{ + Array, Index, + Key, KeyId, Op, Opcode, // }; use crate::num; =20 +/// Defines a schema struct together with its [`Schema`] implementation th= at decodes into `$target`. +/// +/// Each member of the struct should implement `Schema`. For every (key, i= ndex, value) triple +/// decoded from the NVKV stream, the generated parent `Schema` implementa= tion will call each member +/// in declaration order with that triple. If a member consumes that tripl= e, it will stop there. +/// Otherwise it will keep going until all members are tried. +/// +/// The schema struct holds the state required by the schema implementatio= n to do the decode. It's +/// recommended to use one of the existing Schema kinds (`Required`, `Accu= mulated`, `Key`, `Array`, +/// `Indexed`) for each member. +/// +/// # Examples +/// +/// ``` +/// nvkv_decode! { +/// struct RequestSchema =3D> Request { +/// id: Required, +/// name: Array, +/// } +/// } +/// ``` +macro_rules! nvkv_decode { + ( + $(#[$attr:meta])* + $vis:vis struct $name:ident =3D> $target:ident { + $( + $(#[$field_attr:meta])* + $field_vis:vis $field:ident : $ty:ty + ),* $(,)? + } + ) =3D> { + $(#[$attr])* + $vis struct $name { + $( + $(#[$field_attr])* + $field_vis $field: $ty, + )* + } + + impl $crate::gsp::nvkv::Schema for $name { + type Target =3D $target; + + fn init() -> impl ::kernel::prelude::Init { + ::pin_init::init!(Self { + $( $field <- <$ty as $crate::gsp::nvkv::Schema>::init(= ), )* + }) + } + + fn visit( + &mut self, + key: $crate::gsp::nvkv::KeyId, + index: $crate::gsp::nvkv::Index, + value: $crate::gsp::nvkv::DecoderValue<'_>, + ) -> ::kernel::error::Result { + Ok(false + $( || $crate::gsp::nvkv::Schema::visit(&mut self.$fiel= d, key, index, value)? )*) + } + + #[inline(always)] + fn finish( + &mut self, + ) -> impl ::kernel::prelude::Init + '_ { + let Self { $($field,)* } =3D self; + ::kernel::try_init!(Self::Target { + $( $field <- $crate::gsp::nvkv::Schema::finish($field)= , )* + }? ::kernel::error::Error) + } + } + + impl ::core::default::Default for $name { + fn default() -> Self { + $crate::gsp::nvkv::assert_schema_size_reasonable::(); + Self { + $( $field: ::core::default::Default::default(), )* + } + } + } + }; +} +pub(crate) use nvkv_decode; + +/// Asserts that a schema built by value is small enough. +pub(crate) fn assert_schema_size_reasonable() { + // Clippy triggers this even if the enclosing function is never called= , so skip if clippy is on. + const_assert!( + cfg!(clippy) || size_of::() <=3D 1024, + "construct large schemas in place with `Schema::init` instead of `= Default`" + ); +} + +impl TryFrom, Error =3D Error> + Default, cons= t KEY_ID: KeyId> Schema + for Key +{ + type Target =3D T; + + #[inline(always)] + fn visit<'a>(&mut self, key: KeyId, index: Index, value: DecoderValue<= 'a>) -> Result { + if key !=3D KEY_ID { + Ok(false) + } else if index !=3D Index::new::<0>() { + // Single values being set must be at index 0. + Err(EINVAL) + } else { + // Overwrite and take the latest value here. + self.0 =3D value.try_into()?; + Ok(true) + } + } + + #[inline(always)] + fn finish(&mut self) -> impl Init + '_ { + Ok(core::mem::take(&mut self.0)) + } +} + +impl TryFrom, Error =3D Error>, const KEY_ID: = KeyId> Schema + for Key, KEY_ID> +{ + type Target =3D Option; + + #[inline(always)] + fn visit<'a>(&mut self, key: KeyId, index: Index, value: DecoderValue<= 'a>) -> Result { + if key !=3D KEY_ID { + Ok(false) + } else if index !=3D Index::new::<0>() { + // Single values being set must be at index 0. + Err(EINVAL) + } else { + // Overwrite and take the latest value here. + self.0 =3D Some(value.try_into()?); + Ok(true) + } + } + + #[inline(always)] + fn finish(&mut self) -> impl Init + '_ { + Ok(self.0.take()) + } +} + +impl Schema for Ar= ray +where + for<'a> &'a [T]: TryFrom, Error =3D Error>, +{ + type Target =3D ArrayVec; + + fn init() -> impl Init { + init!(Self { + vec <- ArrayVec::init_with::(|_| Ok(())), + }) + } + + fn visit<'a>(&mut self, key: KeyId, index: Index, value: DecoderValue<= 'a>) -> Result { + if key !=3D KEY_ID { + return Ok(false); + } + // Require to be at index 0 + if index !=3D Index::new::<0>() { + return Err(EINVAL); + } + // Reject oversized and take the latest value. + self.vec.clear(); + self.vec.extend_from_slice(value.try_into()?)?; + Ok(true) + } + + #[inline(always)] + fn finish(&mut self) -> impl Init + '_ { + ArrayVec::init_with(move |dst| { + dst.extend_from_slice(&self.vec)?; + self.vec.clear(); + Ok(()) + }) + } +} + +/// A schema field for a key that must be present. +/// +/// `finish` fails with `EINVAL` if no value arrived for the key. +#[repr(transparent)] +pub(crate) struct Required(Key, KEY_ID>); + +impl TryFrom, Error =3D Error>, const KEY_ID: = KeyId> Schema + for Required +{ + type Target =3D T; + + #[inline(always)] + fn visit<'a>(&mut self, key: KeyId, index: Index, value: DecoderValue<= 'a>) -> Result { + self.0.visit(key, index, value) + } + + #[inline(always)] + fn finish(&mut self) -> impl Init + '_ { + (self.0).0.take().ok_or(EINVAL) + } +} + +impl Default for Required { + fn default() -> Self { + Self(None.into()) + } +} + +/// Expects objects specified sequentially with index starting from zero. +pub(crate) struct Accumulated { + current_index: Index, + current: S, + current_started: bool, + next: S, + accumulated: KVVec, +} + +impl Accumulated { + /// Creates an empty accumulator. + pub(crate) fn new() -> Self { + Self { + current_index: Index::new::<0>(), + current: S::default(), + current_started: false, + next: S::default(), + accumulated: KVVec::new(), + } + } + + fn take_vec(&mut self) -> Result> { + if self.current_started { + self.accumulated + .try_push_init(self.current.finish(), GFP_KERNEL)?; + self.current_started =3D false; + } + self.current_index =3D Index::new::<0>(); + Ok(core::mem::take(&mut self.accumulated)) + } +} + +impl Schema for Accumulated { + type Target =3D KVVec; + + fn visit<'a>(&mut self, key: KeyId, index: Index, value: DecoderValue<= 'a>) -> Result { + if index !=3D self.current_index { + if !self.next.visit(key, Index::new::<0>(), value)? { + // Unrelated key to us. + return Ok(false); + } + + // Require that objects at index k have all their keys sent be= fore the k + 1 th object + // can be completed. Require that objects are sent contiguousl= y in order from index 0. + if !self.current_started || index !=3D self.current_index + 1 { + return Err(EINVAL); + } + + // The current value must be finished. Push it and swap in `ne= xt`. + self.accumulated + .try_push_init(self.current.finish(), GFP_KERNEL)?; + core::mem::swap(&mut self.current, &mut self.next); + self.current_started =3D true; + self.current_index =3D index; + Ok(true) + } else { + let consumed =3D self.current.visit(key, Index::new::<0>(), va= lue)?; + self.current_started |=3D consumed; + Ok(consumed) + } + } + + #[inline(always)] + fn finish(&mut self) -> impl Init + '_ { + self.take_vec() + } +} + +impl Default for Accumulated { + fn default() -> Self { + Self::new() + } +} + +/// A schema field that scatters indexed values into an array of `N` slots. +#[repr(transparent)] +pub(crate) struct Indexed([T; N], PhantomData); + +/// Copies `elems`, converted to `T`, into `slots` at `start`. +/// +/// Fails with `EINVAL` if the window does not fit in `slots`. +fn scatter_window, As: Copy>(slots: &mut [T], start: usize, el= ems: &[As]) -> Result { + let end =3D start.checked_add(elems.len()).ok_or(EINVAL)?; + // Reject indices outside of the declared array size. + let dst =3D slots.get_mut(start..end).ok_or(EINVAL)?; + for (d, &e) in dst.iter_mut().zip(elems) { + *d =3D T::from(e); + } + Ok(()) +} + +impl Schema for Indexed +where + T: From + Default, + As: Copy + for<'a> TryFrom, Error =3D Error>, + for<'a> &'a [As]: TryFrom, Error =3D Error>, +{ + type Target =3D [T; N]; + + fn visit<'a>(&mut self, key: KeyId, index: Index, value: DecoderValue<= 'a>) -> Result { + if key !=3D KEY_ID { + return Ok(false); + } + let start =3D index.cast::().get(); + // Accept both scalar vs scattered array setting for flexibility. + match <&[As]>::try_from(value) { + Ok(elems) =3D> scatter_window(&mut self.0, start, elems)?, + Err(_) =3D> scatter_window(&mut self.0, start, &[As::try_from(= value)?])?, + } + Ok(true) + } + + #[inline(always)] + fn finish(&mut self) -> impl Init + '_ { + init_array_from_fn(|i| Ok::<_, Error>(core::mem::take(&mut self.0[= i]))) + } +} + +impl Default + for Indexed +{ + fn default() -> Self { + assert_schema_size_reasonable::(); + Self([T::default(); N], PhantomData) + } +} + /// A decoded NVKV value. #[derive(Copy, Clone)] pub(crate) enum DecoderValue<'a> { @@ -53,12 +393,23 @@ fn try_from(value: DecoderValue<'a>) -> Result { pub(crate) trait Schema { type Target; =20 + /// Returns an initializer that creates an empty schema in place. + /// + /// Useful if the schema is too large to fit on the stack. + fn init() -> impl Init + where + Self: Sized + Default, + { + Self::default() + } + /// Visits one decoded pair. Returns `Ok(true)` if the schema consumed= it. fn visit<'a>(&mut self, key: KeyId, index: Index, value: DecoderValue<= 'a>) -> Result; =20 /// Returns an initializer that makes the decoded `Target`. /// - /// After the returned initializer runs, the schema should be empty ag= ain. + /// After the returned initializer runs successfully, the schema shoul= d be empty again. If the + /// initializer fails, the schema may hold stale state. fn finish(&mut self) -> impl Init + '_; } =20 @@ -262,4 +613,129 @@ fn finish(&mut self) -> impl Init + '_ { =20 Ok(()) } + + // Tests that decoding via the `nvkv_decode!` macro works correctly. + #[test] + fn decode_typed_struct() -> Result { + const SCALAR32_KEY: KeyId =3D 0x1234; + const SCALAR64_KEY: KeyId =3D 0x1235; + const ARRAY8_KEY: KeyId =3D 0x1236; + const ARRAY32_KEY: KeyId =3D 0x1237; + const ARRAY64_KEY: KeyId =3D 0x1238; + const OPT_PRESENT_KEY: KeyId =3D 0x1239; + const OPT_ABSENT_KEY: KeyId =3D 0x123a; + const X_KEY: KeyId =3D 0x0100; + const Y_KEY: KeyId =3D 0x0101; + const SLOT_KEY: KeyId =3D 0x0200; + + const SCALAR32_VALUE: u32 =3D 0x89ab_cdef; + const SCALAR64_VALUE: u64 =3D 0x0123_4567_89ab_cdef; + const ARRAY8_VALUE: &[u8] =3D &[0x12, 0x34, 0x56]; + const ARRAY32_VALUE: &[u32] =3D &[0x0123_4567, 0x89ab_cdef]; + const ARRAY64_VALUE: &[u64] =3D &[0x0123_4567_89ab_cdef, 0xfedc_ba= 98_7654_3210]; + const OPT_PRESENT_VALUE: u32 =3D 0x55; + + nvkv_decode! { + struct PairSchema =3D> Pair { + x: Required, + y: Required, + } + } + + struct Pair { + x: u32, + y: u32, + } + + nvkv_decode! { + struct TestSchema =3D> TestDecodeable { + scalar32: Required, + scalar64: Required, + array8: Array, + array32: Array, + array64: Array, + opt_present: Key, { OPT_PRESENT_KEY }>, + opt_absent: Key, { OPT_ABSENT_KEY }>, + pairs: Accumulated, + slots: Indexed, + } + } + + struct TestDecodeable { + scalar32: u32, + scalar64: u64, + array8: ArrayVec, + array32: ArrayVec, + array64: ArrayVec, + opt_present: Option, + opt_absent: Option, + pairs: KVVec, + slots: [u32; 4], + } + + let index0 =3D Index::new::<0>(); + let index1 =3D Index::new::<1>(); + let mut encoder =3D Encoder::new(); + encoder.encode_u32(SCALAR32_KEY, index0, SCALAR32_VALUE)?; + encoder.encode_u64(SCALAR64_KEY, index0, SCALAR64_VALUE)?; + encoder.encode_array8(ARRAY8_KEY, index0, ARRAY8_VALUE)?; + encoder.encode_array32(ARRAY32_KEY, index0, ARRAY32_VALUE)?; + encoder.encode_array64(ARRAY64_KEY, index0, ARRAY64_VALUE)?; + encoder.encode_u32(OPT_PRESENT_KEY, index0, OPT_PRESENT_VALUE)?; + encoder.encode_u32(X_KEY, index0, 1)?; + encoder.encode_u32(Y_KEY, index0, 2)?; + encoder.encode_u32(SLOT_KEY, index1, 20)?; + encoder.encode_u32(X_KEY, index1, 3)?; + encoder.encode_u32(Y_KEY, index1, 4)?; + encoder.encode_u32(SLOT_KEY, index0, 10)?; + let serialized =3D encoder.finish(); + + let decoder =3D Decoder::new(&serialized, UnknownKeyPolicy::Error); + let mut schema =3D TestSchema::default(); + let decoded =3D KBox::try_init(decoder.decode(&mut schema)?, GFP_K= ERNEL)?; + + assert_eq!(decoded.scalar32, SCALAR32_VALUE); + assert_eq!(decoded.scalar64, SCALAR64_VALUE); + assert_eq!(*decoded.array8, *ARRAY8_VALUE); + assert_eq!(*decoded.array32, *ARRAY32_VALUE); + assert_eq!(*decoded.array64, *ARRAY64_VALUE); + assert_eq!(decoded.opt_present, Some(OPT_PRESENT_VALUE)); + assert_eq!(decoded.opt_absent, None); + assert_eq!(decoded.pairs.len(), 2); + assert_eq!(decoded.pairs[0].x, 1); + assert_eq!(decoded.pairs[0].y, 2); + assert_eq!(decoded.pairs[1].x, 3); + assert_eq!(decoded.pairs[1].y, 4); + assert_eq!(decoded.slots, [10, 20, 0, 0]); + + Ok(()) + } + + // Tests that a schema too large for the stack decodes on the heap. + #[test] + fn decode_large_schema_on_heap() -> Result { + const BLOB_KEY: KeyId =3D 0x1400; + const BLOB_VALUE: &[u8] =3D &[0xab; 100]; + + nvkv_decode! { + struct BigSchema =3D> BigDecodeable { + blob: Array, + } + } + + struct BigDecodeable { + blob: ArrayVec, + } + + let mut encoder =3D Encoder::new(); + encoder.encode_array8(BLOB_KEY, Index::new::<0>(), BLOB_VALUE)?; + let serialized =3D encoder.finish(); + + let mut schema =3D KBox::init(BigSchema::init(), GFP_KERNEL)?; + let decoder =3D Decoder::new(&serialized, UnknownKeyPolicy::Error); + let decoded =3D KBox::try_init(decoder.decode(&mut *schema)?, GFP_= KERNEL)?; + + assert_eq!(*decoded.blob, *BLOB_VALUE); + Ok(()) + } } --=20 2.55.0 From nobody Sun Sep 27 00:36:29 2026 Received: from PH7PR06CU001.outbound.protection.outlook.com (mail-westus3azon11010058.outbound.protection.outlook.com [52.101.201.58]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id A218D47ACC6; Thu, 27 Aug 2026 14:23:47 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=fail smtp.client-ip=52.101.201.58 ARC-Seal: i=2; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1787840632; cv=fail; b=uSa+0Dwxf3bVbg1dg83fJ894+ojG+jYbQDsFTN4cDMMIHtP3+2Em+xW6UAesm9O1Dqib6XV1psdDPKmUJeTksFTUNU1teSTra0zW+DJnEikcO6CmlNgtvsj45l+eZfsetobeUfeE792UghgKxLGU909kcQ3mw5C/U/aYF5BAYm8= ARC-Message-Signature: i=2; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1787840632; c=relaxed/simple; bh=3O/uTe29algn96dIDVfP9Xrcxu6sxujyQMmTzGVE44g=; h=From:Date:Subject:Content-Type:Message-Id:References:In-Reply-To: To:Cc:MIME-Version; b=qyc+tcGrDYn1UyzcqDq/rqNx+p4ohKDXW3KVfCJAyvcbOpna7YY4QD0JfU37BwYmDnMhTTHHXm/W6EFVW4Ukv+E1uJsb8kMj7gSeWor4kjh9WXfpZkejgUh6vcRo7hkuuwM1zjJNLsFGi/EKgW4uxhR3BFrGebKDpkdA8h85tyw= ARC-Authentication-Results: i=2; smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=nvidia.com; spf=fail smtp.mailfrom=nvidia.com; dkim=pass (2048-bit key) header.d=Nvidia.com header.i=@Nvidia.com header.b=drx9p4j7; arc=fail smtp.client-ip=52.101.201.58 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=nvidia.com Authentication-Results: smtp.subspace.kernel.org; spf=fail smtp.mailfrom=nvidia.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=Nvidia.com header.i=@Nvidia.com header.b="drx9p4j7" ARC-Seal: i=1; a=rsa-sha256; s=arcselector10001; d=microsoft.com; cv=none; b=wtOfl4kSkxObgOAbAookegWT3qu7qM/jcpLhUthFezWxhOTey5aY11Y6A5NB+wyWR2sinbd6Pp9kCrQXQSR5luZOrt8PHhdg66HdipUDusQkPQG20MdMCrH1rj88eHuvGV/GasVAZxuasuqfAqGRbyOx4z0GvZ162BtQIi6zZPEqgHMHcyej8cgVpDr5EvSKJ56EJQ5BuK3ZQE5pAgHBe55daIhUCt3tAWHIXrlhTf/eEksf2gK2yh7oTtAFRypoXCyD3JGVF+Te2jvceBMSE2F8WoBUXYhsylWVufbt53PV/tQeVgxD9MmHKueKsGomPGkv9DVc/mEiCL/gJpr7hw== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=microsoft.com; s=arcselector10001; h=From:Date:Subject:Message-ID:Content-Type:MIME-Version:X-MS-Exchange-AntiSpam-MessageData-ChunkCount:X-MS-Exchange-AntiSpam-MessageData-0:X-MS-Exchange-AntiSpam-MessageData-1; bh=92XB1Q+nIEQR+GxRfpxYE7bMwehcsrctUqKMNXKw8rg=; b=joJNsLNM/qDRzbMWWGoif/LMmz+j5UPdnDehkCUQiPihlJZl496b6md8CWXe7mUwdfSzCwSNvfZhdG4+ndfqV/9Oka/VnlLRY7ydSCSJvnsqpu+5UikRsbI2UhUDjMdzngQy3Fyt4T8StcioDSl6Js8Vcy1oqNJNsOPM9jpuqaGUvviE7ZmB6rJV/+ZyfM/2MPwxwu0eYJEahsEWoS40vP+7oPH/sbbNbkplwg8jW47F7It+KiyUVUy62j7DW5IXPRwVq/hjLi/pYg4wlHzzDYN5HE/TeH6FOdYsHS509vbTAANWaevH/X1CrJUEhBlLbTWjLSG5Xk41rLt254d1dQ== ARC-Authentication-Results: i=1; mx.microsoft.com 1; spf=pass smtp.mailfrom=nvidia.com; dmarc=pass action=none header.from=nvidia.com; dkim=pass header.d=nvidia.com; arc=none DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=Nvidia.com; s=selector2; h=From:Date:Subject:Message-ID:Content-Type:MIME-Version:X-MS-Exchange-SenderADCheck; bh=92XB1Q+nIEQR+GxRfpxYE7bMwehcsrctUqKMNXKw8rg=; b=drx9p4j7E+E328vwCD5eTi2qWpmbKt45ULLsWcZD0v+y9s4rRquLDOvs3yVPqOjxtFabsEU5sHgiJmsysakzAzbBR3HF6IXHQTHInzraQnClSgYAiRLYXd9a3p51QTt1lnYBKAAwtpcvvc6f5EbBcKI1+hrP62FfBHiX8rVoulxKL2I7gFd9WIIcWqO5sOq08Dkl5VTUDwvyPLbs5GlMzj21ckl5yInCqhC4rI8zNo+kT4xwt+m7uOt/jsRHnWk6GFZEmm7Ve3pBZWgmSlpCzbo/4uIk5hjWGUnXKlG86EzzcOlOQVXmRer0RymdqXdGdl9MBJJjgVXzYuG/h6SjZA== Authentication-Results: dkim=none (message not signed) header.d=none;dmarc=none action=none header.from=nvidia.com; Received: from DS0PR12MB6413.namprd12.prod.outlook.com (2603:10b6:8:ce::10) by PH0PR12MB7930.namprd12.prod.outlook.com (2603:10b6:510:283::7) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.21.360.10; Thu, 27 Aug 2026 14:23:16 +0000 Received: from DS0PR12MB6413.namprd12.prod.outlook.com ([fe80::e82a:6673:4142:37fa]) by DS0PR12MB6413.namprd12.prod.outlook.com ([fe80::e82a:6673:4142:37fa%5]) with mapi id 15.21.0360.008; Thu, 27 Aug 2026 14:23:16 +0000 From: Eliot Courtney Date: Thu, 27 Aug 2026 23:12:57 +0900 Subject: [PATCH v2 8/8] gpu: nova-core: add NVKV GSP_INIT schemas Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260827-b4-nvkv-v2-8-0de9d5c8658c@nvidia.com> References: <20260827-b4-nvkv-v2-0-0de9d5c8658c@nvidia.com> In-Reply-To: <20260827-b4-nvkv-v2-0-0de9d5c8658c@nvidia.com> To: Danilo Krummrich , Lorenzo Stoakes , Vlastimil Babka , "Liam R. Howlett" , Uladzislau Rezki , Miguel Ojeda , Boqun Feng , Gary Guo , =?utf-8?q?Bj=C3=B6rn_Roy_Baron?= , Benno Lossin , Andreas Hindborg , Alice Ryhl , Trevor Gross , Daniel Almeida , Tamir Duberstein , Alexandre Courbot , =?utf-8?q?Onur_=C3=96zkan?= , David Airlie , Simona Vetter Cc: John Hubbard , Alistair Popple , Timur Tabi , rust-for-linux@vger.kernel.org, linux-kernel@vger.kernel.org, nova-gpu@lists.linux.dev, dri-devel@lists.freedesktop.org, Eliot Courtney X-Mailer: b4 0.15.2 X-ClientProxiedBy: TYCP286CA0136.JPNP286.PROD.OUTLOOK.COM (2603:1096:400:31b::20) To DS0PR12MB6413.namprd12.prod.outlook.com (2603:10b6:8:ce::10) Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 X-MS-PublicTrafficType: Email X-MS-TrafficTypeDiagnostic: DS0PR12MB6413:EE_|PH0PR12MB7930:EE_ X-MS-Office365-Filtering-Correlation-Id: 2737a562-b14e-422e-d7ef-08df0446bc35 X-MS-Exchange-SenderADCheck: 1 X-MS-Exchange-AntiSpam-Relay: 0 X-Microsoft-Antispam: BCL:0;ARA:13230040|23010399003|7416014|376014|10070799003|1800799024|366016|18002099003|22082099003|3023799007|13003099007|6133799003|11063799006|56012099006|10067099003|921020; X-Microsoft-Antispam-Message-Info: qIe8eKZzCzmxbs+WZft/aMf1lQfef4BwBF3HZOCPJp6m231Kk5cDowbyGpVaum/K3WZ+g+/kOGNyC9PKpuz3Ops+gqvXu4fYV5jd6ikizFHk8MNnBsNgtn+AEunZKtATkqY0mzGPXBPBsXWK4mEHP6zBSrcFYNw8ssw/ptPsyxE3kcGBuGJtjZyjR16HdSmGfJveL9F1fLnRducfJ0CxadeLCDdLgkOXzsGd5et/UB8hNHhN5eMe8wmVKrmbDW5m7KqYMrl6qx+daekX0mHkdunhiGzoMaaSdXcMX0zohcvLzXPSQ/ZCcygb6W+XzwbExw6C2tKxlSY0kpuNXXVnScYGUhFjS1d+uWkGQdNN79PzYFurgBlkX7xhReZS8Gt14YDjAW3K5qOiuueR244mErh12kuiyJr9LzkwYeiEBQWNlIiNEyB0rosU50Hjq1O0X9Im9W3sTewzxrzlQj4p8Hbbs6X3K63u7oQmzcNurgAvAIDb/J0pavJCJzQ1l7wcCIuf7tlK/YEVyig0/11cDlYXqh1SFehq09K8kQf9YAzwCiyzrwwsFmD9gzk8o2/VDKCVwhkvaQROptVqhzOdzGwgmEx+Lqr7zYMP9zr9Amd9xaRbsCMmis9ASJeLNZaJy3P2jyLv2hP3HOIyxmHkoeP4iKuSWCI7MYlla4Tmg4MHfgKSvg4j5fqHsclHILqs2FvSitQuvOh+rgZLbIwFXw== X-Forefront-Antispam-Report: CIP:255.255.255.255;CTRY:;LANG:en;SCL:1;SRV:;IPV:NLI;SFV:NSPM;H:DS0PR12MB6413.namprd12.prod.outlook.com;PTR:;CAT:NONE;SFS:(13230040)(23010399003)(7416014)(376014)(10070799003)(1800799024)(366016)(18002099003)(22082099003)(3023799007)(13003099007)(6133799003)(11063799006)(56012099006)(10067099003)(921020);DIR:OUT;SFP:1101; X-MS-Exchange-AntiSpam-MessageData-ChunkCount: 2 X-MS-Exchange-AntiSpam-MessageData-0: =?utf-8?B?OUlFTmZ3Z25OTGZuUGNqTUFnTXJOMm1tSXVtWjc2aERHRHFGK2ZxdFpXTGpm?= =?utf-8?B?bHduSm0yNDRFTVAvZGxjMmY4dnFicVVYYzdFcEdEdmt0WWFGOGxKMjFUdjc0?= =?utf-8?B?R3J5K3l5TDFyQVhsa0s2enl2YWdyS3MwaTVuOXpVdkw1NUFlT1BmNzZ1aXFW?= =?utf-8?B?V0pYWHlFV25BQzNtVlh0L2ljUUNOQ0F5TTZ5WGp4NUpoY2RDUEhsUE13WnRR?= =?utf-8?B?OTZ5Mm1kRSsxQzJ4QXprRjFPdzNUS0N0NDRPcFUzTU5jZzlWc1BKTG9xd1dv?= =?utf-8?B?SVhLa1VsSjJ1VkhvWVcxU3hBNXBKQ0dqZVFwcDR5ZEpNaUlqSEJhOW5RS2dt?= =?utf-8?B?akMwWklEMDVkaFVSblJ0RE1qUUc3em5yOVVERGZpS2hxMjAvMVRNY3FwWmM0?= =?utf-8?B?Y1ZmaWhXbnZRamxZa005Y09PZEpPWjR2MFdCMXZKSGVWUmtKbjFjenNTK3Ju?= =?utf-8?B?ckFNeWpaQkovdzNlMTlESUZUelN6TGRvUUR5ZHlzclYvL2pZeWIyYlhQQWhl?= =?utf-8?B?SVQ3MG9HY0V6UVhFd2MvZjMxVGNUaW0wa2llS0NZYjlKL2FKeEgySUN3UTBq?= =?utf-8?B?ZURxOEdoTjNETkgxaHlJRFM2NUlrTm03dUNVMGUvY05OTEE2WHZHdnlSVFda?= =?utf-8?B?Y2Rxc29wYTIzTFREUW9UVWFYc1NCMkRvVmdjMTlGbTNYWlB2SGNmYy93Tncx?= =?utf-8?B?VzI1Z0lycFViV3AvNmpVbVJnVjA2ZGRqZ2dFRW5GTlJuK29kMDVaUnVURGhX?= =?utf-8?B?WHpEREJBNDlyTzhoNktEdHhka09Ndi9FaUdsMDhBRG01TDh0NkoybnZ1KzM1?= =?utf-8?B?am1waThlUlE1ZGYxelExZ3RzSXhJa3YrVGM2Vi83NXowOWlySHI5TjlQcm5Q?= =?utf-8?B?MEhzK3lsMEV0dCtYWlN4KzJXWjJ0SS9rWjRCb1haZkkzNWdiVlkwc09UcStN?= =?utf-8?B?OUxMUkhCZVNGZllmZzJjMFNYdmhUT1lNTWJHek1BUHFKNTIrOWxWcUZNenFD?= =?utf-8?B?c1pFanVIMm4wZkw4S3lvaGNOREh1ckhQMFlldHdDOFo1b0JJVG8zMExYYStv?= =?utf-8?B?VmFpa2RaM0RVb0FlSFFjbGtGZEx2eGNTZmloRUhBMkgyT09GMzVGM2JWU3Fo?= =?utf-8?B?NUd1by9yZFVyQzBtSDRLV2ZxaTBHNzFQZENTR2RRc3VDb201K1BXbllwYTZW?= =?utf-8?B?SWNXR2pNSnlKVTRTYUxxbWI5QmVjQ0pTZkt6T1A1T2hHTkxGdFRXSEp6bTBk?= =?utf-8?B?VmhXQTZkZEJFUXl4aWp3MXQ1dEZpUlB5MXpYYnMybGI2ZWlWWitWUG5MNXBt?= =?utf-8?B?aU1seVJPZUFzL2Q2SmtaMFU5aTI4NW5Qd08vbDF4WHByb3JYejM0SmxhWEJu?= =?utf-8?B?dTZsbnNpMjVSWlF5aTYvaUhpT1YzRmdTcU5aYmpmbkUyZUFRb1dyYURuRGty?= =?utf-8?B?UURGWm9BeGI5c1dpTEhwaHphYi85eWw4a1dtbHhrZWNzYkUwNWsrNmF4UTJ1?= =?utf-8?B?OFhxR1NxVkJEWkNud1V3ZU9jWVFHWURLOEJJeERPOTJTNEpiT1YvNXhtMmp0?= =?utf-8?B?elBZSlR0TGw1Y1I2OWxlV0d1dUFRcy9uaGlPTGxwdE1hTXRkbWgxWXAzQnhy?= =?utf-8?B?VDBHYURDTUppSlJxOW1uMnVmeUh5QTVvY0lhSnFGa1Q0REVIeXdRQ09Zb2Vw?= =?utf-8?B?Nmk3T0FRUGxXN1hLL2ZQUDlBTy80OXcyWTlkeXFXNE9KV2RNUUtaWGJkSkp3?= =?utf-8?B?a2xIemRiMU4xbkVQOXYyZTZqcGcxeXNnSW9KemV4OW53U3dibzZHa2xZSVlp?= =?utf-8?B?bGJxc2NxZExSR1pTd2hXTE4zamcxUnlxdWFaSndFdng2amduOHFaSm1Jb2I4?= =?utf-8?B?WVRnYTdZYzZBNWFaQ2dCVFcwSDBPOUFpbzk2S2dQV0ZLSjR4RStMSTlJSnpB?= =?utf-8?B?NVVvSElxdkVQNVdpNHdxcmV3ZjV5ODZ0NU81UmVFQndiQUpaUHdDZk9RaTlz?= =?utf-8?B?VkFrSHZ1SlFNbEU2NHIxQlVQbjFGblJ4YnNtNmtuby9GSkI0Wk4xN1laejJm?= =?utf-8?B?QVU4WU9mZGQrWDdLQ0FQTnQxR0ovVlVaVWpZNGZsd0M0emxmNFU2alpua0Jo?= =?utf-8?B?UTBmNTlhQUN6ZmZITW01SGVOTW9Nb1BCemg4OXF0alVFZis2cDJnejMxK1RO?= =?utf-8?B?Y1EvTGYrWFN4V1lQTWtmS3BlZWxnMHY2MTlPOXloUi9Ic3hpcFUvK1MzN21v?= =?utf-8?B?Qlc1WEQ3ckpHMGtZZVY2RjRyU3l6Q2pBcmtNRzdYK2orcHFFOVg4Z0lKQWk3?= =?utf-8?B?b1FzZTNxU2JMV2wrbHBVTjg5bllXVVE2SDFZRkhDbVhPUXZKTFRHREg5R0Jn?= =?utf-8?Q?gP1konEcA9aPc5gvCg/Kmcjd9f+QRcj54VRJYbmV+4yHf?= X-MS-Exchange-AntiSpam-MessageData-1: pMHn6fWqNaEo1w== X-OriginatorOrg: Nvidia.com X-MS-Exchange-CrossTenant-Network-Message-Id: 2737a562-b14e-422e-d7ef-08df0446bc35 X-MS-Exchange-CrossTenant-AuthSource: DS0PR12MB6413.namprd12.prod.outlook.com X-MS-Exchange-CrossTenant-AuthAs: Internal X-MS-Exchange-CrossTenant-OriginalArrivalTime: 27 Aug 2026 14:23:16.3124 (UTC) X-MS-Exchange-CrossTenant-FromEntityHeader: Hosted X-MS-Exchange-CrossTenant-Id: 43083d15-7273-40c1-b7db-39efd9ccc17a X-MS-Exchange-CrossTenant-MailboxType: HOSTED X-MS-Exchange-CrossTenant-UserPrincipalName: WbwPyx4ji7W7CfZRRZsbY/h3GMKZHyxPW1aOKnFsFYAt55K+ldjuenFapE0mC88TXPYipBrvMqbBG8r2HuRUEA== X-MS-Exchange-Transport-CrossTenantHeadersStamped: PH0PR12MB7930 Add the first user of NVKV encode/decode which is the request and response for GSP init. For now this is exercised via unit tests. Later patches will support GMCAPI in `Cmdq` and use these messages. Signed-off-by: Eliot Courtney --- drivers/gpu/nova-core/gsp/fw/commands.rs | 350 +++++++++++++++++++++++++++= ++++ drivers/gpu/nova-core/gsp/nvkv.rs | 3 - 2 files changed, 350 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/nova-core/gsp/fw/commands.rs b/drivers/gpu/nova-co= re/gsp/fw/commands.rs index 6dc31d1bf5ae..25ac1e8e7785 100644 --- a/drivers/gpu/nova-core/gsp/fw/commands.rs +++ b/drivers/gpu/nova-core/gsp/fw/commands.rs @@ -4,6 +4,8 @@ use core::ops::Range; =20 use kernel::{ + alloc::ArrayVec, + bitfield, device, pci, prelude::*, @@ -19,6 +21,19 @@ num::IntoSafeCast, // }; =20 +use crate::gsp::nvkv::{ + nvkv_decode, + nvkv_encode, + Accumulated, + Array, + DecoderValue, + Encodable, + Encoder, + Key, + KeyId, + Required, // +}; + use super::bindings; =20 /// Payload of the `GspSetSystemInfo` command. @@ -217,3 +232,338 @@ unsafe impl AsBytes for UnloadingGuestDriver {} // SAFETY: This struct only contains integer types for which all bit patte= rns // are valid. unsafe impl FromBytes for UnloadingGuestDriver {} + +/// The host CPU architecture. +#[derive(Clone, Copy)] +pub(crate) enum HostArch { + None =3D 0, + X86_64 =3D 1, + Ppc64le =3D 2, + Arm =3D 3, + Aarch64 =3D 4, + Riscv64 =3D 5, +} + +// TODO[FPRI]: This is a temporary solution to be replaced with the corres= ponding derive macros once +// they land. +impl TryFrom for HostArch { + type Error =3D Error; + + fn try_from(value: u32) -> Result { + match value { + 0 =3D> Ok(Self::None), + 1 =3D> Ok(Self::X86_64), + 2 =3D> Ok(Self::Ppc64le), + 3 =3D> Ok(Self::Arm), + 4 =3D> Ok(Self::Aarch64), + 5 =3D> Ok(Self::Riscv64), + _ =3D> Err(EINVAL), + } + } +} + +impl From for u32 { + fn from(value: HostArch) -> Self { + value as u32 + } +} + +nvkv_encode! { + /// A GSP registry entry. + struct RegKey { + key_name: Key<&'static [u8], { Self::REGKEY_NAME_KEY }>, + key_value: Key, + } +} + +impl RegKey { + // Define the Key IDs read/written by GSP. + const REGKEY_NAME_KEY: KeyId =3D 0x3070; + const REGKEY_VALUE_U32_KEY: KeyId =3D 0x3071; +} + +impl Encodable for KVVec { + fn encode(&self, encoder: &mut Encoder) -> Result { + for regkey in self { + regkey.encode(encoder)?; + } + Ok(()) + } +} + +nvkv_encode! { + /// SR-IOV virtual function information. + struct VfInfo { + total_vfs: Key, + first_vf_offset: Key, + flags: Key, + first_bar0_address: Key, + first_bar1_address: Key, + first_bar2_address: Key, + } +} + +impl VfInfo { + // Define the Key IDs read/written by GSP. + const VF_TOTAL_VFS_KEY: KeyId =3D 0x0080; + const VF_FIRST_VF_OFFSET_KEY: KeyId =3D 0x0081; + const VF_FLAGS_KEY: KeyId =3D 0x1003; + const VF_FIRST_BAR0_ADDRESS_KEY: KeyId =3D 0x1050; + const VF_FIRST_BAR1_ADDRESS_KEY: KeyId =3D 0x1051; + const VF_FIRST_BAR2_ADDRESS_KEY: KeyId =3D 0x1052; +} + +nvkv_encode! { + /// Payload of the `GSP_INIT` command. + // TODO: expect() doesn't work here due to Self:: reference, fixed in = 1.97.0 + // https://github.com/rust-lang/rust/pull/154377 + #[cfg_attr(not(CONFIG_KUNIT), allow(dead_code))] + struct GspInitRequest { + pci_device_id: Key, + pci_sub_device_id: Key, + pci_revision_id: Key, + pci_config_mirror_base: Key, + pci_config_mirror_size: Key, + host_arch: Key, + bus_device_func: Key, + regkeys: KVVec, + vf_info: Option, + } +} + +impl GspInitRequest { + // Define the Key IDs read/written by GSP. + const PCI_DEVICE_ID_KEY: KeyId =3D 0x0001; + const PCI_SUBDEVICE_ID_KEY: KeyId =3D 0x0002; + const PCI_REVISION_ID_KEY: KeyId =3D 0x0003; + const PCI_CONFIG_MIRROR_BASE_KEY: KeyId =3D 0x0010; + const PCI_CONFIG_MIRROR_SIZE_KEY: KeyId =3D 0x0011; + const HOST_ARCH_KEY: KeyId =3D 0x0070; + const NV_DOMAIN_BUS_DEVICE_FUNC_KEY: KeyId =3D 0x1020; +} + +// Decode: + +// Should decode with UnknownKeyPolicy::Ignore. +nvkv_decode! { + /// Schema for the `GSP_INIT` response. + // TODO: expect() doesn't work here due to Self:: reference, fixed in = 1.97.0 + // https://github.com/rust-lang/rust/pull/154377 + #[cfg_attr(not(CONFIG_KUNIT), allow(dead_code))] + struct GspInitResponseSchema =3D> GspInitResponse { + gpu_name: + Array, + fb_regions: Accumulated, + bar1_pde_base: Required, + vmmu_segment_size: Key, + } +} + +impl GspInitResponseSchema { + // Define the Key IDs read/written by GSP. + const GPU_NAME_STRING_KEY: KeyId =3D 0x2000; + const BAR1_PDE_BASE_KEY: KeyId =3D 0x1020; + const VMMU_SEGMENT_SIZE_KEY: KeyId =3D 0x1050; +} + +/// Payload of the `GSP_INIT` response. +struct GspInitResponse { + gpu_name: ArrayVec, + fb_regions: KVVec, + bar1_pde_base: u64, + vmmu_segment_size: u64, +} + +impl GspInitResponse { + const MAX_GPU_NAME_LEN: usize =3D 64; +} + +nvkv_decode! { + /// Schema for one FB region of the `GSP_INIT` response. + struct FbRegionSchema =3D> FbRegion { + base: Required, + limit: Required, + flags: Required, + tag: Required, + } +} + +impl FbRegionSchema { + // Define the Key IDs read/written by GSP. + const BASE_KEY: KeyId =3D 0x1011; + const LIMIT_KEY: KeyId =3D 0x1012; + const FLAGS_KEY: KeyId =3D 0x0012; + const TAG_KEY: KeyId =3D 0x0013; +} + +bitfield! { + /// FB region attribute flags. + struct FbRegionFlags(u32) { + 0:0 support_compressed =3D> bool; + 1:1 support_iso =3D> bool; + 2:2 protected =3D> bool; + } +} + +impl TryFrom> for FbRegionFlags { + type Error =3D Error; + + fn try_from(value: DecoderValue<'_>) -> Result { + if let DecoderValue::Scalar32(v) =3D value { + Ok(v.into()) + } else { + Err(EINVAL) + } + } +} + +/// One FB memory region. +struct FbRegion { + base: u64, + limit: u64, + flags: FbRegionFlags, + tag: u32, +} + +#[kunit_tests(nova_core_fw_commands)] +mod tests { + use crate::gsp::nvkv::{ + Decoder, + Index, + UnknownKeyPolicy, // + }; + + use super::*; + + // Tests that `GspInitRequest` encodes correctly. + #[test] + fn gsp_init_request() -> Result { + let mut encoder =3D Encoder::new(); + + let mut regkeys =3D KVVec::new(); + regkeys.push( + RegKey { + key_name: b"test_key\0".into(), + key_value: 0xdead_beef.into(), + }, + GFP_KERNEL, + )?; + + let gsp_init =3D GspInitRequest { + pci_device_id: 45.into(), + pci_sub_device_id: 67.into(), + pci_revision_id: 3.into(), + pci_config_mirror_base: 0x1234_5678.into(), + pci_config_mirror_size: 0x1000.into(), + host_arch: HostArch::Aarch64.into(), + bus_device_func: 0x0001_0203_0405_0607.into(), + regkeys, + vf_info: Some(VfInfo { + total_vfs: 8.into(), + first_vf_offset: 1.into(), + flags: 0x7.into(), + first_bar0_address: 0x1000_0000.into(), + first_bar1_address: 0x2000_0000.into(), + first_bar2_address: 0x3000_0000.into(), + }), + }; + + gsp_init.encode(&mut encoder)?; + let encoded =3D encoder.finish(); + assert_eq!(encoded.len(), 22); + + Ok(()) + } + + // Tests that FB region decoding fails when required keys are missing. + #[test] + fn decode_fb_region_missing_required_fails() -> Result { + let mut encoder =3D Encoder::new(); + encoder.encode_u64(FbRegionSchema::BASE_KEY, Index::new::<0>(), 0x= 1000_0000)?; + let data =3D encoder.finish(); + + let decoder =3D Decoder::new(&data, UnknownKeyPolicy::Ignore); + let mut schema =3D FbRegionSchema::default(); + let init =3D decoder.decode(&mut schema)?; + assert!(KBox::try_init(init, GFP_KERNEL).is_err()); + + Ok(()) + } + + // Tests that a minimal and a full `GSP_INIT` response decode correctl= y. + #[test] + fn gsp_init_response() -> Result { + let name =3D b"test name\0"; + const BAR1_PDE_BASE: u64 =3D 0xdead_0000; + const FB_REGION0_BASE: u64 =3D 0x1000_0000; + const FB_REGION0_LIMIT: u64 =3D 0x1fff_ffff; + const FB_REGION0_FLAGS: u32 =3D 0x7; + const FB_REGION0_TAG: u32 =3D 0; + const FB_REGION1_BASE: u64 =3D 0x2000_0000; + const FB_REGION1_LIMIT: u64 =3D 0x2fff_ffff; + const FB_REGION1_FLAGS: u32 =3D 0x3; + const FB_REGION1_TAG: u32 =3D 1; + const VMMU_SEGMENT_SIZE: u64 =3D 0x0200_0000; + + type Resp =3D GspInitResponseSchema; + + let index0 =3D Index::new::<0>(); + let index1 =3D Index::new::<1>(); + + // A minimal response: only the BAR1 PDE base, so the FB region li= st stays empty. + let mut encoder =3D Encoder::new(); + encoder.encode_u64(Resp::BAR1_PDE_BASE_KEY, index0, BAR1_PDE_BASE)= ?; + let data =3D encoder.finish(); + + let decoder =3D Decoder::new(&data, UnknownKeyPolicy::Ignore); + let mut schema =3D Resp::default(); + let response =3D KBox::try_init(decoder.decode(&mut schema)?, GFP_= KERNEL)?; + assert_eq!(response.bar1_pde_base, BAR1_PDE_BASE); + assert!(response.fb_regions.is_empty()); + + // A full response. + let mut encoder =3D Encoder::new(); + encoder.encode_array8(Resp::GPU_NAME_STRING_KEY, index0, name)?; + encoder.encode_u64(Resp::BAR1_PDE_BASE_KEY, index0, BAR1_PDE_BASE)= ?; + encoder.encode_u64(FbRegionSchema::BASE_KEY, index0, FB_REGION0_BA= SE)?; + encoder.encode_u64(FbRegionSchema::LIMIT_KEY, index0, FB_REGION0_L= IMIT)?; + encoder.encode_u32(FbRegionSchema::FLAGS_KEY, index0, FB_REGION0_F= LAGS)?; + encoder.encode_u32(FbRegionSchema::TAG_KEY, index0, FB_REGION0_TAG= )?; + + // Test that this unrelated key can safely interleave. + encoder.encode_u64(Resp::VMMU_SEGMENT_SIZE_KEY, index0, VMMU_SEGME= NT_SIZE)?; + + encoder.encode_u64(FbRegionSchema::BASE_KEY, index1, FB_REGION1_BA= SE)?; + encoder.encode_u64(FbRegionSchema::LIMIT_KEY, index1, FB_REGION1_L= IMIT)?; + encoder.encode_u32(FbRegionSchema::FLAGS_KEY, index1, FB_REGION1_F= LAGS)?; + encoder.encode_u32(FbRegionSchema::TAG_KEY, index1, FB_REGION1_TAG= )?; + let data =3D encoder.finish(); + + let decoder =3D Decoder::new(&data, UnknownKeyPolicy::Error); + let mut schema =3D Resp::default(); + let response =3D KBox::try_init(decoder.decode(&mut schema)?, GFP_= KERNEL)?; + + assert_eq!(&*response.gpu_name, &name[..]); + assert_eq!(response.bar1_pde_base, BAR1_PDE_BASE); + assert_eq!(response.fb_regions.len(), 2); + + let fb_region0 =3D &response.fb_regions[0]; + assert_eq!(fb_region0.base, FB_REGION0_BASE); + assert_eq!(fb_region0.limit, FB_REGION0_LIMIT); + assert_eq!(fb_region0.flags.into_raw(), FB_REGION0_FLAGS); + assert!(fb_region0.flags.support_compressed()); + assert!(fb_region0.flags.support_iso()); + assert!(fb_region0.flags.protected()); + assert_eq!(fb_region0.tag, FB_REGION0_TAG); + + let fb_region1 =3D &response.fb_regions[1]; + assert_eq!(fb_region1.base, FB_REGION1_BASE); + assert_eq!(fb_region1.limit, FB_REGION1_LIMIT); + assert_eq!(fb_region1.flags.into_raw(), FB_REGION1_FLAGS); + assert_eq!(fb_region1.tag, FB_REGION1_TAG); + + assert_eq!(response.vmmu_segment_size, VMMU_SEGMENT_SIZE); + + Ok(()) + } +} diff --git a/drivers/gpu/nova-core/gsp/nvkv.rs b/drivers/gpu/nova-core/gsp/= nvkv.rs index 7d58ca91cbc3..e7a9549919fb 100644 --- a/drivers/gpu/nova-core/gsp/nvkv.rs +++ b/drivers/gpu/nova-core/gsp/nvkv.rs @@ -9,9 +9,6 @@ //! function calls will map to some struct - for example, f(GPU_NAME_STRIN= G_KEY, 0, b"some gpu") //! naturally maps to storing a &str with the GPU name. =20 -#![cfg_attr(not(CONFIG_KUNIT), expect(unused_imports))] -#![cfg_attr(not(CONFIG_KUNIT), expect(unused_macros))] - use core::marker::PhantomData; use core::ops::{ Deref, --=20 2.55.0