From nobody Mon Sep 28 21:03:44 2026 Received: from BL2PR02CU003.outbound.protection.outlook.com (mail-eastusazon11011032.outbound.protection.outlook.com [52.101.52.32]) (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 3AD5A48034B; Mon, 28 Sep 2026 08:43:05 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=fail smtp.client-ip=52.101.52.32 ARC-Seal: i=2; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1790584987; cv=fail; b=lmHpyAzeoRP10k1RkQYAwvEkFfsiDaONhYT4kiWQJxEUOGeOEqECVrQhgEtM1d1K44DivbLVHjtgws3Ye+JSQMBOGhN1xVzsctdSlmtZh20aB6S0UZUHhd4LwlHkz5fv33AwOF2/kmcnnVbhLBOzShBfsRfdd/bmzy15GAyzJ9I= ARC-Message-Signature: i=2; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1790584987; c=relaxed/simple; bh=P5JggELd2QMIGEGFxvVn46m8Cg5kkAU6V2QXV1bIUW4=; h=From:Date:Subject:Content-Type:Message-Id:References:In-Reply-To: To:Cc:MIME-Version; b=HySxwGPRGTODcDh6CVgk9Nr3FLRSIlwZwc++vfYLuqmQQ/8FaaeUmK7WG18sGS5nwyreHc4DefRwEhODS4kkBRrdWjqsjMCw7fTd0dwSyGva9YCGCi7RtTbiLOhv0WVcILIeX1nWfzDxFv23O5jdfQKaZJuLU2cv8SDQoFyCoQc= 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=sl7oV/f0; arc=fail smtp.client-ip=52.101.52.32 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="sl7oV/f0" ARC-Seal: i=1; a=rsa-sha256; s=arcselector10001; d=microsoft.com; cv=none; b=anX8hgKC9Zhoqs7exqJuTaJ9OzpjVXVi4c0ajX+MZEnUph8CcFHJRHQroWZWZOk6sonTbuQn2Xi98a84CfXHOCsY1GU+8rPVFznZRMUXo2/rwBDR3maTH10xzw6TtOORmVtgfHOuMEjsOxAoaZFlhxvetI8BMbBnxEQjVfsSbMLJ9y2mUpXiFAKa+gUMXJxJRBYfm+oUPsOOe6oXqFtZtn8pEDLvMzTnBhbKvAhxSW6YcYyK0QXCnZS6xQ052bOWhoGGV1s3ShzmWBh0Khyc9YcKsfjxHRndDqMXEh5M4HBgp2nsFj2i7vrl9S3ObJRsngY1HpPNt+tqUbEXjJe/fg== 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=r9Yvk3D6HPkxAyIo+4hQijtWiGKhgZFPeVgxWNDXIFw=; b=aOTFVhqUIq7nv9P28sJuY8rtD5ZQJI/A69ZJgV7PFN5CnDeh4QYtRE1y3OT7Qew8QSv5FUw6WCfkOpMr/dp5oCLBcXLM7X15Au0kv2At4GO7N83A94LXZcxe6RzEgjgtFp2y3mqr2iO5k8cPtlXJb+mw3mLyOrCNfhbzZF+VUeNvStwfdDFo/MAbfOPI4pcyyeg/dSmnOCQs+mXkc96tt42ptyheTOpodnpqf9pqpF5IbhXvbnu4IJBS8Fal9dojdPbuHMKM6pKTxOXs1IvBhr/TwUcx4q5ofIBRU2KFq1gVTsgkGHfN/uN/yhcTeePf/S794EhXHf3CVUgTgGS5yA== 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=r9Yvk3D6HPkxAyIo+4hQijtWiGKhgZFPeVgxWNDXIFw=; b=sl7oV/f0ijqJDpdK+Gu1ei3T68laftx+n8+z+b23T3KgElr03iTuGQq3adx5c605Y9p9uUE1P0vzFIZG3Z7nLwViCZi3WnJgPXBET+BvrAHkrQ0WuqRnHJ4VO0KkugDsYC+QXKjcLZPvtpcaTD+sahcNXlzCF3LS2qEcA+iTvQxceEL0kubijAifVYqnDFU/n7rihqlNBLdnepXCLVorQDCHLFFE9URbOQXyxYdZINMsgzsSefBwIB+pfLpGqUZqQVD+GzCMHw7F1Yv4LbQQ62YTOAR5UV01vugm/MegjmSdN6WE2qQCmgcq538RTiv62e3AeQ+ahvszNj8tubU2PA== Authentication-Results: mx.microsoft.com 1; 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 CY8PR12MB8065.namprd12.prod.outlook.com (2603:10b6:930:73::16) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.21.451.24; Mon, 28 Sep 2026 08:43: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.0451.022; Mon, 28 Sep 2026 08:43:02 +0000 From: Eliot Courtney Date: Mon, 28 Sep 2026 17:42:40 +0900 Subject: [PATCH v3 1/8] rust: alloc: add Vec::try_push_init Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260928-b4-nvkv-v3-1-f04504c262c2@nvidia.com> References: <20260928-b4-nvkv-v3-0-f04504c262c2@nvidia.com> In-Reply-To: <20260928-b4-nvkv-v3-0-f04504c262c2@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: TY6P301CA0030.JPNP301.PROD.OUTLOOK.COM (2603:1096:405:3ba::17) 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_|CY8PR12MB8065:EE_ X-MS-Office365-Filtering-Correlation-Id: 67cfda10-a8f9-485c-e0c9-08df1d3c81a0 X-MS-Exchange-SenderADCheck: 1 X-MS-Exchange-AntiSpam-Relay: 0 X-Microsoft-Antispam: BCL:0;ARA:13230040|10070799003|23010399003|7416014|376014|1800799024|366016|10067099003|56012099006|11063799006|921020|6133799003|18002099003|22082099003; X-Microsoft-Antispam-Message-Info: uhGE7ORx63doXQrsLd2bu1qJ9fkni/BicOv+rr08sB0g6Hobmoy6/UHExSkhTokt/P9xHr5vgBZjkPxsziqvrCexbw3jvS2W35d0RYl6hLhB1BTvwhgSQxTGEpuJSZb5YS+eyOanzrxFuWK5RtId6U+Tu3+ly/81lU1qnKl43dzDlYZ4LjTEpn1JOCMG3ifMjCT5ExodVBY6cdoaJvh1sywuvNmSCf9ABRoHsFWsDOM5wSvEelLyeAPokynsBEAwgfROCOqPGe+7iIs0gADaJVEHBG0eymxcVHOwXKmteT55ndamG20mtHCTAVU9GNhr9SkrIPCIfcndrexqBcXBEoMPFzNbd2UDs/7GobcN+BDEBEh+p+yCOYvS1hHxkha4ZmzzNDKO+ogvG7YMaKR5VS/2/5Hz795Mn7xB4CjP9SqYZQ0ERTkWFNhOEFCKKsXPyDQgLBfYnDESIplns5EgeOlRBjtqyiWIqiatgMiaxmCJrlsbDNNlmtYGF87kTDqcAfLjBlwMvKd1eYHqwjXUH8YQfBJ+yDYm/MzDaBzjpbeks6nkKqytJoGQCP3crLALkVFbP5H6uJxCDcVoLRxIlqLOymPDmLxR+GGes/gtvkPSGriq9uYmLTTRpOF6kW5T8/GPmgmqFKi3CL78mQ8SrVYEKpL7ev+SjujbPvmIlZOfkwLhhVc8Khs9WJVoiBnJagpu4BAJ70bPI7XdSntO3w== 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)(10070799003)(23010399003)(7416014)(376014)(1800799024)(366016)(10067099003)(56012099006)(11063799006)(921020)(6133799003)(18002099003)(22082099003);DIR:OUT;SFP:1101; X-MS-Exchange-AntiSpam-MessageData-ChunkCount: 2 X-MS-Exchange-AntiSpam-MessageData-0: =?utf-8?B?ZDNlODB5U3lXa1dxV3lmWjdHWUNLcFBzQ0JzNVhEaCtTVmdBSTVpaVNISW5a?= =?utf-8?B?YkUwcUwrRWNyL3B0U0dmNUFKcFF1TnBqVHQ3dDJTWUU5NGgyK25Fak5yQ2t5?= =?utf-8?B?ZG9WSHV1YmVpWDFvcmg1RGlxMXIxZk9zdkNrNGVtd0hTWEFCYjA4a00rSXdM?= =?utf-8?B?bVN4cTdyU1U5dk9yc25iNS9weU1vVzNYMjdxOFExUk14VWtrVDlyZ00wYTM5?= =?utf-8?B?d0NITUhQeE5COHJGczdVcndQWmNiNkRMeHNmRytBazZtSE1oNXJ5aXJ1TWlu?= =?utf-8?B?dzUwZHRzNEtVaHBWamZtY0E2VkxIdGJLM21wRmVGRy9JMjBzc1JIWHJhdEx6?= =?utf-8?B?eDU4UGxVVkNESlB1TE92VlNSK2dwSEd2b2hMMVJjS3pVZ3lZa1ZRQWNyR0lj?= =?utf-8?B?WUpHL3VkcnNlUmZGQVRLNjhKMHh1c010L2lMNUNYTG4zM0twQjFCVkdaamEv?= =?utf-8?B?S21aNmNOZ3A1SUUxcVUwWi9kS3htQ3d3S1NIbkYvQjljQXk4cTZxSEZTbHUy?= =?utf-8?B?dEx0a2FsaDdwNGtKSEV4RlN4UUxuYm5ZSDFNK1dDYUhGZ2lhNlN4eDRxSkZW?= =?utf-8?B?eWJaNC83eEtGRUI5SXZZOW1Ock9HQWhNdlNWSHVmdzRhMCtRUG1WMkMwbUZ1?= =?utf-8?B?bVZMQlk4K1RwQzVWcEg3a2F0em1WNUR1eDN0ZFI1RlpWT0x0cnRpSms3aW9o?= =?utf-8?B?bzA4SnFNZDhMVWVyb2lKZGREOUN5d3AvME5ocGxuS3JVNmZPTzV0Vy9IT2dV?= =?utf-8?B?SUVDR2JOTThNcmdPODhOTGh2enVPemlmTWpCT3V2ZlNvYW5OWk02SlRKOE0x?= =?utf-8?B?UVExcTBNeFBSUFV0eUZyZ002aTUzWWxTOCszU0pFSzh3WXU1MUJnY2NDdVBJ?= =?utf-8?B?alJFSVcwVkQ0UkVZYlBET3o1eHF6YkEzdER1RmVMeGtiQ3h4d1lEZlQ3NUdO?= =?utf-8?B?TVVCSm11blBKeGdPOFdyR2pNVllVSi92OFo3SmVoYVpNTFo1N3BaVkloZjNW?= =?utf-8?B?czFueUFUanBCZFNnbUdvL1BsS3pFL2txdDZjd2tMWjJDNnNpUXY0Y0dxcHhX?= =?utf-8?B?ejJDMXplaVk0NlZMU2FOd3JwRkxQQWdVSWVwYUkzWElHYVc0MEFQTmdwcHA5?= =?utf-8?B?NG84YURiTDNqT2prL2l3bytDY1pJOHA5dFFDSlV5K1M2MTFWeXkvamxBalJZ?= =?utf-8?B?bnJvSzRlZ0JFR0Ftb1JsanM4UzJqQTNaeXFhMStSV3dXNnNqZ2NnNko4OEl0?= =?utf-8?B?Umk1dTdYdlRCMHVMK1lYSmJsclVUK3JXaW0wUVhreDJPYk4wcUpnRS9uNlF1?= =?utf-8?B?WWRydHJ5aFBhcW9XZVc5VXR0QS9tQUY3UmF5SjVpZXlQaHl3Qk1GTEt5aHN3?= =?utf-8?B?NDl2NEZ2dVduYlZHYXVQV3I1Mk5NR3BWN0ZaVzg0RUtjV0dqYUpBYTRQaUU0?= =?utf-8?B?amxuWUI3MFRxcXgzWnJzTThyamxDWE9TY3FGaVhZNWxKRXFOQ0txeFVkNllu?= =?utf-8?B?bDJrYmRLdEtvK1o2dS9HZFh6N1IzSGNKa1MrN05BNVZxaitPTWFyL0J5M2d0?= =?utf-8?B?ak1COThCSmZOZ01GOWd5OHZoRTc5WXdNQ1J3QkZRMXArWWJ4OURNTHZPOU9l?= =?utf-8?B?SlR2cVAvcUdZWGM2RTArbzdkQStFU0xERmVWc21BclFnTnhDMFdmTkFXMFF2?= =?utf-8?B?eVdDMUhYZ1RSSC8zcGFCMWVaUDNsQXZTMGs4bVRjQ21CV0dZMWdyT2pHSmNG?= =?utf-8?B?U1k1L1pTZEVHanh6N1EwY2RSNGZuSTYvbGJPNnhJNHFBSi9SQ0R5TUswd2k3?= =?utf-8?B?RVhzTzU4QWJGQ3FiNzhmNjFsRHo3aThHRkJYazEwbzc4elM0TDl5cXRIQUhk?= =?utf-8?B?QVdhRm96cDgrdkJIWkV6OWp3MGRmbFlRRElhQVJycUxITlJGSXVQbldNMmJp?= =?utf-8?B?bWtQNjFDMDdUS0xhL3pMcjlMVXczOUZ4SnpBdWNnenFwK3FsRzAveHZLSG1z?= =?utf-8?B?RUQ0U05EWGlycVg1SEFKcEhwcUxndHZySmlHTUh5MWpXcFdwS1hwYTViSEsv?= =?utf-8?B?c0dHeGFvQWh6RFIvV0gvQnZFK1Fmc1VBbmZoQVpTUWNBS0lTMmtBVlJERmY4?= =?utf-8?B?RTgvS1gxMXE2dnp5VTBRb1NZazVUVnpnRmZpbTZxMmJLVWtsM0VWZkJoaFlj?= =?utf-8?B?NlZhWkhlMkhtT2lZTGg4cXRRT2MzbXlxQ0E5dlhYM1ZpN0NDNGtwNzQ2UXph?= =?utf-8?B?a0VWQjBLMy9GUFBtTk00dHQ0aDZvQXdlQ251MWNJNVhXeE0zYmpPbHMzdTIw?= =?utf-8?B?Rzg3V09NcWsxd3BjSWZRRWNLT2hNWlEzSXVWWlc3TFUyVEN3RWxMS2U5WXBI?= =?utf-8?Q?h98fHXm0oWvLJbCnx1D5Hb5uH3MTKnpzX9NDLxG7gsbwW?= X-MS-Exchange-AntiSpam-MessageData-1: cH4xLo9lKEOLKQ== X-OriginatorOrg: Nvidia.com X-MS-Exchange-CrossTenant-Network-Message-Id: 67cfda10-a8f9-485c-e0c9-08df1d3c81a0 X-MS-Exchange-CrossTenant-AuthSource: DS0PR12MB6413.namprd12.prod.outlook.com X-MS-Exchange-CrossTenant-AuthAs: Internal X-MS-Exchange-CrossTenant-OriginalArrivalTime: 28 Sep 2026 08:43:02.2784 (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: Ofj0hN7OQvSkyO71ivbihAU+TYilwJN6iTpDjGEo6LL9qYxx5ixAMZd6dDsxCmILv7fR0rMLAdt+YivIh1vQ4A== X-MS-Exchange-Transport-CrossTenantHeadersStamped: CY8PR12MB8065 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 | 55 ++++++++++++++++++++++++++++++++++++= ++-- rust/kernel/alloc/kvec/errors.rs | 30 ++++++++++++++++++++++ 2 files changed, 83 insertions(+), 2 deletions(-) diff --git a/rust/kernel/alloc/kvec.rs b/rust/kernel/alloc/kvec.rs index c7546b9da4fa..a2b72a7779d9 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. 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 Mon Sep 28 21:03:44 2026 Received: from MW6PR02CU001.outbound.protection.outlook.com (mail-westus2azon11012067.outbound.protection.outlook.com [52.101.48.67]) (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 1A0234734F3; Mon, 28 Sep 2026 08:43:08 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=fail smtp.client-ip=52.101.48.67 ARC-Seal: i=2; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1790584990; cv=fail; b=cylk40gqvPHxdSeJXDMyTU7DghludFaYu95gHisiX3p9VUdRc3LAtfoyqz6TXlXL40S/yKlZ8vgIGNGWSVxSRDfNKsD/zz6nWikWfPctASmxTlRry74Z+LIBz48rt02wZK16M4pWXOBMN66SlqLEIM7TdWQoodzHVN+Lq1UJm1k= ARC-Message-Signature: i=2; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1790584990; c=relaxed/simple; bh=HrAsVoV3jbHXMN8Du+O3k+uG5XXkHBincCrfN25/Hmk=; h=From:Date:Subject:Content-Type:Message-Id:References:In-Reply-To: To:Cc:MIME-Version; b=fsOBr8xK/gymWZ8koXQCfNeL0EgwKMLjXh4Oc4ToEMmUySaW2ypCt4jsEaG5Pz9pLjyTqt9aEkgJDBBAQ721FO1Zki1uwXWEKBMN5yg34rKIsnV67vo2QSuPPMeGSOuL0JjU+GFcy1zyRhTYWNf33D3UEI2G4Q/bDyXnDl2Fhg4= 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=HzBM7j+A; arc=fail smtp.client-ip=52.101.48.67 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="HzBM7j+A" ARC-Seal: i=1; a=rsa-sha256; s=arcselector10001; d=microsoft.com; cv=none; b=lAKbhcUmb3c9LHvkiTcqJaUMDWsVDZfKTCGG6WXJZ1xMp+nOmhMCcXs2MKaBTbunr+a4LHzHNBqN41zySjQm1ulZfcKOqg9LOfpKQYrNd+uCAb7Zc/ERFQ0O3JlJl3cP4aPRH1kI4VVsXUjA9yFMzMlySvhKzeaYEudVgvX0k34P0DNi018OtYl3eTh2SrZBYyK1m+daStA0D9JKar0kmkoVofx4AJyIIESlV7qUmFpxNHzdgwYLgRo1hIudFsWz/MbjBf7FbSPOZL+8DvNXMzEJQa6NwPHBBo2Bo4dELMO5J2HHy/pgMYsbMfNFg4j4TPySgB861JvKNoH1p7b24Q== 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=JtVpn+OiXrI1znyTYB9URgUkP7WgVNIA3CIbMb84E/s=; b=jY/HLEy9iyfrmqvf2m8Sb/+0Sukav8T2JHpsoXb0GCZtSdtR+g3r2Cq8CHVjZMYLRctt5P3Gv8HrRJS4ClpVMC7r4fo9O5zgT4IK70GyZI2Hkokxy613zpDyvoo2FHHwWHfZlncu0WuVSWqGkMJDIV4u8+2pvLT3YaZbUZSURzjoWgnxU2CUIUpTDgBUcXbztCkVHg/P8V0tqTA9LpVmvUsqRgQh78iTMz2B5xtrQUFi+5eXIlLiBeVZCSNFOdJNh1msH95HFayXFdOBufsHoHhCEAM0IpQVQ4nGwmdGZs6tlgb3T4IHAMOqQ1gwJhjKFG++jeNEI+9FVyJSXnXURA== 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=JtVpn+OiXrI1znyTYB9URgUkP7WgVNIA3CIbMb84E/s=; b=HzBM7j+AXCMAeNUYZZs624pF54hCurMvHD9qB4prYciq4Y5ZGIcPIoomEChGNIr77izItkKCla11+HcTRhjS3Vy18LKzTs6/g+fRNy0Jx/IR2sBfHmN2brwPKBHO75rDUxPvJSzc41cKiNZsDsjPs8LDkAZtwYFK3WliCvyq5JNSs9aXG769tzW5+AgNKYgrC5IiGG23OQOoqu+9OVwnm2VG72XGuwVhWb+dwb+VPzPBVMik7a2sYoIZmFy1HFbzbUwvgAHY7v4JQaBkY0zXDbyegfHzUUQ437K9pBYHeXcYFDWTlO8BmKpfP3D+dYESAJDmGNO0N+nZRK+MUlUB2Q== Authentication-Results: mx.microsoft.com 1; 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 CY8PR12MB8065.namprd12.prod.outlook.com (2603:10b6:930:73::16) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.21.451.24; Mon, 28 Sep 2026 08:43: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.0451.022; Mon, 28 Sep 2026 08:43:05 +0000 From: Eliot Courtney Date: Mon, 28 Sep 2026 17:42:41 +0900 Subject: [PATCH v3 2/8] rust: alloc: add Vec::push_init Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260928-b4-nvkv-v3-2-f04504c262c2@nvidia.com> References: <20260928-b4-nvkv-v3-0-f04504c262c2@nvidia.com> In-Reply-To: <20260928-b4-nvkv-v3-0-f04504c262c2@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: TYCP301CA0052.JPNP301.PROD.OUTLOOK.COM (2603:1096:400:384::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_|CY8PR12MB8065:EE_ X-MS-Office365-Filtering-Correlation-Id: 87ca6b5c-68a4-489f-f418-08df1d3c83d5 X-MS-Exchange-SenderADCheck: 1 X-MS-Exchange-AntiSpam-Relay: 0 X-Microsoft-Antispam: BCL:0;ARA:13230040|10070799003|23010399003|7416014|376014|1800799024|366016|10067099003|56012099006|11063799006|921020|18002099003|22082099003; X-Microsoft-Antispam-Message-Info: S2ktxvX2P/VnA0kI2ysLG2TkwjwNwSdVPaoErn/NldNzfn14lXcfhpFknBjKdCdUBtI7LJojkFBDkmVtw2WdS9LAFNpQfFrafPTfOYPVk9O77K11jtwQ6F6616PtrYfD5ZOkZlCcsi9r43xCRRlhmhIT4YzFlaQKKragoXmGHcvGaCa7pCtmgINR2QK2Lcc9fWkINF3I/NUEv7atQIy5rxCo8d3ReXbcvQiHYRFIOnhAGk6gvMzRg0dvxIPQkLzsurKp+FVS7Uc6BPuDB2j4+e/PPf4sboMAO6GNl88miCKOa/xrEcoFn8VBWGzq8pK+IJLvL/a62YvaJyzFoPQW9+zMpzn9boR6HD/WwcViEcrDbKu4yDnlHCujonH49dBJ2+Zdt5eYR/XcaZG1efzN7TCwLxlvCUaRq2FaxVinR81eENDEVnXAymahCSyl7HlDrHon3cT5AQWr0zqGs3xKB6rmWvb33TMo5ihTnK/WsZlzKxHP47Hrvnd/csb9z5MuSm8cU4P3yFnxwEKJgcQVR4sr4wucS+xQ/xFHJ/Opf/veqogeNRI6Ey/wIzt4ytDpQ6mohP7AaoCWpddrGgHNQ4P2aYVqM+kEP7wZXTxaHYI6XOdZ0zj5ZFbYkLkIGZuuFOxPYNSqdLZrlQG+OMmCIhPY3+qPzZxJwtSzwUWB8qZek2eeVZVAUd/DmiIRCW0wa/nQBl9/GMvy+2nQq4eFrw== 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)(10070799003)(23010399003)(7416014)(376014)(1800799024)(366016)(10067099003)(56012099006)(11063799006)(921020)(18002099003)(22082099003);DIR:OUT;SFP:1101; X-MS-Exchange-AntiSpam-MessageData-ChunkCount: 2 X-MS-Exchange-AntiSpam-MessageData-0: =?utf-8?B?OFNCekR3MmNIUEtlU1BnZlROZGp2OU5McHowY2kzSlNRMFVKVysrOFE1V2hw?= =?utf-8?B?V2lkS3dCTXRtbEhwakcramlsUEI4ZmtFVTI2Qm5MeXRwbzJjbzJyMW9MT1Uw?= =?utf-8?B?U1U2Qnlsb21tNm5pY3pBQ1hhWU1PcldkLzh6enBhc1Jac3Y4U0RLMW9PSWI0?= =?utf-8?B?R1lhZ3NnSldiWm14MW5hQlNXSTBkTWhDVGpvSmo3V0t2RDJzcFQ2Q2d0YXdk?= =?utf-8?B?bFJnZjg5SFAwdWNlVFJXVDJSVC90RXd5djJ1dEhlSHVNRmZXQVk3NjNMN1dV?= =?utf-8?B?RGZTQ0NWL0FTMlpXODFkUzZiQ0pZZURrRFVMM095TnhSMUJVeVlOeTVVelVT?= =?utf-8?B?RG9HeTBlcDdHSm1uMXQvaFZzSi9EdGNOVEVDMFpSZEl1YWRReEdBdHZMNVNK?= =?utf-8?B?dEhTWmVvZkpQelN1eXd3K1JqVlFxWUlmdktGemF6eERtVnhDcnRVNHlpaEJQ?= =?utf-8?B?Z0Zab3hKZmtDVjhLVUZhYmpUNUI2UEJHZWx3STRSVGQxWE9EVkpvSFExRWFX?= =?utf-8?B?bWxsclhWQmpnWEZKeUlWVlFWRDlSYmlRRVVpeFU4aTAwcGlsQXEzU09OZFhH?= =?utf-8?B?M0NNVVo4YS9pc0pMYTNXR0NQREthQmNIelltcFlHZmJQV01xMG5rZW9XZnpr?= =?utf-8?B?Z29iUlRHbis2R2lwTGhORGRvVGhNZUw5VzBkNXVDOVI1eXJVV3ZhSkZYMFpT?= =?utf-8?B?ZU5EdUhVOWJBSXhEMmh5YkpUVllWeFhjQyt3Q3pGWGgrTGg0andmOWQ1S1Ro?= =?utf-8?B?bFIvOTZaRlhzUmI4ak1ObVRYelAzTEdvekxVOUphQ3Nyc1FnaGVwQXUvYnk5?= =?utf-8?B?V2hLanZNQVlPYUY3b3FoaVFiSENsNE82YWZnbE9KWU1DQ2Rwa1daaWdXVWFh?= =?utf-8?B?Qis0STgwNGdKVGc4RFZCUjZGTW85ajhNQTgwUVN6T0Fwem8rVUtDYWN2b1Vv?= =?utf-8?B?dEFhdVh6RHlKS0NUMnlCZ09XWGdKZ2oyK2ZORzZWcW5WcXowWnl6QkdVK0lt?= =?utf-8?B?bVhISkxaa042cFhBTE1Zcmt0R0tZNlZhaDJuVStLS3B0SVNRMlFJK0htMWh2?= =?utf-8?B?ZENyTlJ1L0VGKzJBTytyY2dFL2VoS2FOeXZlbXpnb2JRdEZIMjhMU24xVDlB?= =?utf-8?B?YnY5Wjd0M1hvVS9sNFg0dGZ4UktaMk14dlRrRll2NUNCTDZmZkJXd3lBWXln?= =?utf-8?B?MGZrMWZUeDV1bkpxcnVQVGw2K0pSbXJnVWw4b2xFOEg2WVc0Tys5dXhkWjZw?= =?utf-8?B?ay9uNHBCM0h0Ni9tbkNQbzRpVFBzWlc3WFhhbGVvc0xmcUxrYTF0aTRPTXFN?= =?utf-8?B?YXFESFU2cmhGZHRQQm1sYnZGYlVtQjZiM3NpWGJaUkZ5WHVRTVd5dmR2L25K?= =?utf-8?B?MkhRa1cyUElzMzVZUU9rTkFpQjFZS21yTUJacE44N0lYRHJOY0JsVDVQbmd0?= =?utf-8?B?TDg1dW1OOEFjUm9jc0Nqa0JUVVFsUkpSRWdaSnd5bHpwNHRWRFJRRFplOUhI?= =?utf-8?B?bEZ4dzNSMzVKRnhnUFVJbmZvblo3NkMwSnJWVW9ieEFheGtFenpjd0JoNVh5?= =?utf-8?B?VjhESEE1d3AzdDlrTzVrZy9mOFV4RGthd0huSlRtN1RUV3NaMitWL3dRN0NW?= =?utf-8?B?VW1QN1prazl4TDgzU3ZXeEk3N0hiZkY2RmlRa0F3aHhmbzVtQndlOE1Lanlx?= =?utf-8?B?RDlaSUQxcmgrbnh5Wm5QbS9HanllaWw1ajd4M0tBRUZEZTJNbFA2VkNYYW5S?= =?utf-8?B?bDYyUXB0eUo2TGp5UFk1TE1lWUhZMzZCMkRVNSswQTNwVjNUN3krMThlakxO?= =?utf-8?B?Y2lCczRzaGJZaDhOTi9CSmhwOVBSMXhVa1NweEQvTk1GWlI2RkJpbTc1a1k4?= =?utf-8?B?N2dyemhwN2s2M2F0YlJLOGtRWmpoVWNwSzZyZzVPNEdwYklTNTk4anBpczlR?= =?utf-8?B?M0h3ZVM5Y0hld21QRjdLSkRJeFhKUWlMc0FNR29nMEJJUmxYZVZ0aXlDTkFM?= =?utf-8?B?bUNnVUlVUHFDSUtobFNnY3ZDMzh0Um1pVWNwaXpibWxEM3hCUHd4bXZKaXNG?= =?utf-8?B?Q0R3RjloMWZHb2NIbVU5aEtRUldpVzlMYmw5Q2tjMU9kbllKZWsyR1hKUXJw?= =?utf-8?B?MUZudjNMc1h2WU9pUDFBbFJ3OEs1S29jTWtVckJ0N2FRN1E4L3RaWXdVRW1j?= =?utf-8?B?WFJqOFNTVFpDZFJQdGw1NDJqMmJ2YWdxM3F5NjUvV3BudUNTVGdwTXZBUzhr?= =?utf-8?B?NFhYNHZxZkRuSGN5TTV4VytHUm5wRGtxMjNST1ArSDJZTXJDcHBiclhFTUY5?= =?utf-8?B?VTZjWUV6U0tIZWcvcjRmUUtRM3BDbjVJTUxQMHJZRG9KY3pBZTZicDZET0s0?= =?utf-8?Q?IHkyM0HVyf57zyqJdP0qRgoGg9u1G+QsrjqRDzG0qRFhk?= X-MS-Exchange-AntiSpam-MessageData-1: OOHMkSotlnR0dw== X-OriginatorOrg: Nvidia.com X-MS-Exchange-CrossTenant-Network-Message-Id: 87ca6b5c-68a4-489f-f418-08df1d3c83d5 X-MS-Exchange-CrossTenant-AuthSource: DS0PR12MB6413.namprd12.prod.outlook.com X-MS-Exchange-CrossTenant-AuthAs: Internal X-MS-Exchange-CrossTenant-OriginalArrivalTime: 28 Sep 2026 08:43:05.8428 (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: eRhwzxc3daXbmwVyr781M02OyWEe6nGjUXKraGg2Zka8qazsQmL0PDjP1afahCPKZtXG14ocL1K9KVnzOxJ1SA== X-MS-Exchange-Transport-CrossTenantHeadersStamped: CY8PR12MB8065 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 a2b72a7779d9..1b7b3a1ca770 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 Mon Sep 28 21:03:44 2026 Received: from SJ2PR03CU001.outbound.protection.outlook.com (mail-westusazon11012012.outbound.protection.outlook.com [52.101.43.12]) (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 E5A394349AB; Mon, 28 Sep 2026 08:43:13 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=fail smtp.client-ip=52.101.43.12 ARC-Seal: i=2; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1790584995; cv=fail; b=j7nmVq/G4VjJ2bMrXEtbwBXNSyAGK/ftjTUGrY7x4LMXPh2uGuePYOerPrmUg5ZsQyuheQ8cPOe3Dikm+JLJoSOuGdtQ0gIpqxA6eBZRdb6lL3/q0SqfwE1IsQamKzVvJHCGkruqCOTSIRolMYyKxjyvBUwpgAaBug6/M2lVlwM= ARC-Message-Signature: i=2; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1790584995; c=relaxed/simple; bh=s02IK5D/FSXAEcKZ4It1UhYn2yEyM7+Yd2Z8YOTePLo=; h=From:Date:Subject:Content-Type:Message-Id:References:In-Reply-To: To:Cc:MIME-Version; b=TPQEdPluT95a3t7dumcJigdcuZyQTF3ZMeQPO8CCwlCm9lmvZIG6F0u3GfzMhb1WILEkOla1cR07wTF2QSSjg2mgFrXOFaUqu+tPXiTTcLfT9LVPgmbQ2VxTgouIfRbNRs8PGOPlGFd5u5ICzViYdKGPEpaZVrAJQrSOz0Ger7A= 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=g1+1An0r; arc=fail smtp.client-ip=52.101.43.12 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="g1+1An0r" ARC-Seal: i=1; a=rsa-sha256; s=arcselector10001; d=microsoft.com; cv=none; b=PUKgFKS2vDR7AinY+nCqHKqS2+xDfJmbysV1FA6zHO6KeO0Ewxlb6wRyZICO2LvqEDMSY2oMOsvQu8P53ouAmjkQskeJSGYrivburBIIGcTd4avODbj13b34bmE3uoDa+7pLz98qZE2jAmSh1kQ4yH+RNgQh7m+Vdsp+3QIMGIMpRnnABQoNfKULlYdJoC+06Kar0ZyrAAsAWVt0ryWGfTEvOR9VqyLesQ8IiUqErok8okHXBW7kAYGBAdB49l8Img+2N3UGXGMbuNt0xKeDgVxpy8L4n+c1HcHgteVmgGwa8mugb4CyqET7CZY3gA6YBBH8LmSmZo/6l7fRCW2zpw== 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=R6Zctiz8euiDNmZkseoLVILEwvuTTChwgaa+QYb6TRA=; b=wQlDLfbPo1GMYeG1gK6ELI13rRafOJTHWWZgL6yuOlkzU8caaOpikBnY3k9wOP2mWjM7J+dzua0sEiF9Mr8Mhes2QevFlg6bwhsitTq8nQRWQvocd0jHcuTHEvpkazCkGyZ7vfFtQJlaTFHWBFIcF5+Jq8C5FjHWKGmYI/FNmvfqY7rrMJCXEbxLFhvR+w7E2Pw5/Pe8GTUwfB9JdiKdzQmo2BGil1nW8wA6XhofTnUAx4Rp4VdqqJmadmMAj+8KBqhj4qexOxL/5mthuJuU++m2qx3uY/SG9XDp9f7ImYw9tgJWRVTGZQbEi85Za9Jg7qQ4jfPGODt70k1SYtgjRw== 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=R6Zctiz8euiDNmZkseoLVILEwvuTTChwgaa+QYb6TRA=; b=g1+1An0rGudA9HlC7Csi5sz9UVaYOdqXIMIjVrPJ+a0fqh3o/ziTLAe590KlBzS1Y1M2ydRmgA99DGHjd5osTuWdrthGsWdaWbfKSJ208G/kiV1w9wfX7m5ChOn2I8Mm5dgLsxy3w9Atyvi9Gy3xuiB/gjXCddvSpdPoFrYU6e0XP4u7AXkeWtC+dBAPwAfikLFPYxbL56eGm4I2S5RuyiclcoZT6BIiKL8Oa9AEn4N9EuwjC76wjYK1U4RZ/fanAqsb+tuV4BpXrTjy8yLK6iAQ1Z60JdymvOFJlUFUVqHTCSYiM+MxaYQMzJFKiQ8Rh2JYJ/HGmmTAT3Zzb6St/w== Authentication-Results: mx.microsoft.com 1; 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 CY8PR12MB8065.namprd12.prod.outlook.com (2603:10b6:930:73::16) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.21.451.24; Mon, 28 Sep 2026 08:43:09 +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.0451.022; Mon, 28 Sep 2026 08:43:09 +0000 From: Eliot Courtney Date: Mon, 28 Sep 2026 17:42:42 +0900 Subject: [PATCH v3 3/8] rust: alloc: add ArrayVec Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260928-b4-nvkv-v3-3-f04504c262c2@nvidia.com> References: <20260928-b4-nvkv-v3-0-f04504c262c2@nvidia.com> In-Reply-To: <20260928-b4-nvkv-v3-0-f04504c262c2@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: TYCPR01CA0187.jpnprd01.prod.outlook.com (2603:1096:400:2b0::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_|CY8PR12MB8065:EE_ X-MS-Office365-Filtering-Correlation-Id: 97dc680a-ced9-4b71-04d7-08df1d3c85fd X-MS-Exchange-SenderADCheck: 1 X-MS-Exchange-AntiSpam-Relay: 0 X-Microsoft-Antispam: BCL:0;ARA:13230040|10070799003|23010399003|7416014|376014|1800799024|366016|10067099003|56012099006|11063799006|921020|6133799003|18002099003|22082099003; X-Microsoft-Antispam-Message-Info: xXootskZ6eSFSXD72BE2WmiaEY9NKqg1ROdkDaY4EY6rCFvszgezRzevMJfjatE5GX5VxMmdAW5siUvzxUzpjCxkfWyNAfjQX3ZW3dcFo6BlOYv4iEvvgbFSpLpW+xAoSl0qqadGNa4FliTkhRR48z3pqTLpgpn5yKD89USmMrCKTp1ILww2wFnkZMeELSBuF+Nd4RAT70syBM1g9YI/+uxUQLgI9sazWUrVX0jfe17+U9JHlxxZ+rqW8MuCa3w9CK7NfxD+Q+BCR97Ad6KvhSqfnIr/J35Vtd7fDV20bFGvU6GIdXSTWzPqKKeFPqo1gV4WBxFQ66YuNa+Vb116WQehCzWiR6lNNoxjmm5HODy6VkdcQo/BB+VfdLxohLOZukek+koRpyZUmYfWbmL7MPqolXVSJzKCAzPeVvKh0sHPC8lHK4RPiFD05wQTvERBB8NDoCLcRbhHywVjEItkwTy162wIZGnzAA8MQF20rVILKVzxQUuoPjqZgbqP9w9Zif3tEPj4EsRBHH5mHoqC35I6I09e3/fgqJiycRoOgj9y1GYhwXD7DjgxwuQrs0TeGLoLCzXqmJj8vfAGZDv6kuB+YQ3mpaOH8LRZB3ocfb9JbQi2DR3dyVKiNNV5PHuapfQJe46LesfmMC+dVyohKcru1JVEMCllLZpk2NgeTXieWwNBq1JEQ9F+c8KSw9Z/w3mAdn/fy1hLhCH4gMcTQg== 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)(10070799003)(23010399003)(7416014)(376014)(1800799024)(366016)(10067099003)(56012099006)(11063799006)(921020)(6133799003)(18002099003)(22082099003);DIR:OUT;SFP:1101; X-MS-Exchange-AntiSpam-MessageData-ChunkCount: 2 X-MS-Exchange-AntiSpam-MessageData-0: =?utf-8?B?M2NZRUZLK1ZPRWIzdjhlMDJtR2s1TkZmVDdab3ZDWXl6bEZra3FLWVRqRlBq?= =?utf-8?B?UEQ1b0t6cjZ5MW96NjgzV1lpSTFXdzV6eFJwWUtDa0tTc3lOeFMwVDROblV2?= =?utf-8?B?MVZXNmI5UTQ2UDJZUGpTOTBGcncvRXVGRFB1M0hhVURVRkl3YlJ2SGdSbENy?= =?utf-8?B?L2FYSG13ZlZmNFkxTVdDTXY1M3dpUzh0cndxTU1RTHpSRGNSSWRrYUd4WjU0?= =?utf-8?B?b3pQeTMySyswbzFQUWpaNkFSYjMrbEFpZWtZRForc0JFYXJ2MDdyU2RsQ1lH?= =?utf-8?B?My96NERmOGRxR0ZQVURvSFMwa3hKYktWeFUrODJrWVNSRlRmbk5XSjR0Y2VK?= =?utf-8?B?ek1JaXo2MG1nNlRzU0FJbkJmWHhHNTFlNkQzdWlPcDVmTXFMU1BaYU5Cb2ZV?= =?utf-8?B?b0NtQmpkOVp0R205RFRpQlNoUzhvdlQyWGpDd1NsTFFzUGpGdkpCS1hoaVdV?= =?utf-8?B?elpETlM5aGhrOXFCbGNmeDdhaUd6S0JBSlBPaXlaNnJJSFlLUVBDR1FzSXRi?= =?utf-8?B?ZVBVSEF5SXpFcDBaL3hxZkd4Nm9lRzlEUHNZSTZGYTdOLzhRMkVyRmlQeXE0?= =?utf-8?B?eEJIenFrTGVKbXRHTXdpaUNhUlVUMjZ0TlhuZmFsS3pZOGM0Zm94b0xsWU0r?= =?utf-8?B?bEF1YnZnVEJiTVBXMkRpZGhydnNiamtRQ0VDS1hyZ1d2bWh3dmw5Tnc0NWNz?= =?utf-8?B?ajlWK080SXhNRnNHakRnUlVKZHFTVmZxTGlJTVNvZTJNNWRYVFB2Z1RlZHcy?= =?utf-8?B?MTR4cDEwZXFXUW9EckluUEZ1eXl6YXRiSk1Jb2pGdU9GVVFvY3ZjVjBDL3gx?= =?utf-8?B?TTQvSEZPKzVJa3lPaU0yT0ZsVEpZd3VWRm1HajFwMDYvcUFua3FXOWNYMlVL?= =?utf-8?B?bXJ3T0g1MWtjTjVGSFJka1podlcyenJXU1JzaFlrRjdYWEQzbUZQaTJ5MVR5?= =?utf-8?B?TlI4ZG5PVHdIOHJ0ajJnMWFDYjdJT3U0bmM5eDU2bUYrckFaTnZDQ0xCaTVE?= =?utf-8?B?Uk9ka29qNCtDcU11eXpzUG9rWmVuVkNWanpOSFhJaG5xWko1eFlibjUxKzhM?= =?utf-8?B?UXZRaHdxbXI1RjFVdDk3ODhNWGkyNGZWQVNCMGhNcjY2TTRLK09BaGF1YWN1?= =?utf-8?B?L3F4UHRMd3kzQTJ2SktXTWJYMXRFVnZnQ2UzeWlwWnFqQWk4VU1Wd3NjSDI0?= =?utf-8?B?bS9FbnFCS2FRdm9ncU44YUNIdTdBb3M5S3AwTXF2UVpXWU9qdVBYMG95WXVK?= =?utf-8?B?OXE3ajZyakVlc3F1Z3c0TlFrQlF3YndWVm1tSStnWWVoUGQ2eXhpL0hzSm5t?= =?utf-8?B?ZngxSGlPendZeVA4bjdaeGhEdTB3MXo0Y3p0RGtnWlZKWlRDYk5hUXlTZUpr?= =?utf-8?B?ZzNzOFE0bnNyL3lpK0Rka3NjTUFXVk1SM245Wk4wQ29NcTI5SkpnRkgzK0Q1?= =?utf-8?B?NitHNDdZQ01IR3paNVdCbW9HTEJWK1d3YzBpRjgxQjNJcEhjSUNQMXEzSFhx?= =?utf-8?B?UHRvalRKYS9iZXdnN1gwbllsL1RDOFRwbC9UUkU2dUJDOUQ4MUJrN0xGUk1h?= =?utf-8?B?TG9zdTJhaG81ZXcyTldJanlxY3JXcy90ZEJ6M21qb0Z0ZExFN2lZRFpuS1pT?= =?utf-8?B?cklyaVQ2RmpNNFpKdW9EOUlneGt2UkZsOUVwbmtDUWI4TTJDLy8vdVFSMCtU?= =?utf-8?B?bW1MRitOY2FKN3hkbks3SVpVMjdLUUR0U25YMXpXb1NzR2lWTTZZc3JnemtV?= =?utf-8?B?WHZ4dXdJVGtPS3RkazM5aXA4US9ja085N1dqaUw2YW9Mbm5keFBYNHBiVnJj?= =?utf-8?B?QTI5bTVjdlMyYjJXS3d2elNxc2xPVjQxVUR2cmNLaU5hUDJQYWFSemhabmNV?= =?utf-8?B?bHoyczVvL0JBaXFOamtFWm1DR3ZWdmtwVnEwMDFtWFpnbTh3VzRlaHNueXNE?= =?utf-8?B?NjN4QXJOb3ZoUjM3WXUzNStja3ZFQXI1cDN2aUNnK2g2SzB6OGdzeDBkYWdj?= =?utf-8?B?N3R5Yit1ZmZCYVBac3BLYk9rc2FZZUZmS0NxTEgxSk5Hd2t2bjhOSTJrQ0ZM?= =?utf-8?B?NkhTWHU2Mks3SWJlUXpMOE9hM2x5OEs1d29CRXZ1R2ZnYUNkNFhFd0IyL1pO?= =?utf-8?B?bEZacUlIZkFGajhoWGNEUDNiWVJsYjBrQkgrdDY0TVRNNkNHcE1NOWErOUpL?= =?utf-8?B?ZFphdkNoWGRjNDN6Z2FpT2FDRFUzaVI0VC9kU2FmMGVTRHFuR0U5ZHFYTTlT?= =?utf-8?B?WlpQYnFkUjhOeVNpa29IbEtrOEpzR0FxK3NLcXhQcURkY3MrNEo1V0M1N0xt?= =?utf-8?B?K3RIampMRk9mRnBRS20wb1JmcDdkWktmdU9KTUtURFRzNmc3Nmx1WStTWGZn?= =?utf-8?Q?KOzdKifd7ejTMcqygPj6KgIRyb8MX8ndBAlDEY3HnsbpS?= X-MS-Exchange-AntiSpam-MessageData-1: +NU33D/1w0S3GA== X-OriginatorOrg: Nvidia.com X-MS-Exchange-CrossTenant-Network-Message-Id: 97dc680a-ced9-4b71-04d7-08df1d3c85fd X-MS-Exchange-CrossTenant-AuthSource: DS0PR12MB6413.namprd12.prod.outlook.com X-MS-Exchange-CrossTenant-AuthAs: Internal X-MS-Exchange-CrossTenant-OriginalArrivalTime: 28 Sep 2026 08:43:09.4337 (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: CF6vatCF+9cI2wxd6xbqWQPL1mQyNl/3h2tCK4WkkOWHw7cKogx/v40nYnUTeGEa0nDVElddMagnVgA4Q2xL3A== X-MS-Exchange-Transport-CrossTenantHeadersStamped: CY8PR12MB8065 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 ++++++++++++++++++++++++++++++++++++++= ++++ rust/kernel/alloc/kvec.rs | 1 + 3 files changed, 351 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) + } +} diff --git a/rust/kernel/alloc/kvec.rs b/rust/kernel/alloc/kvec.rs index 1b7b3a1ca770..bb4da220293b 100644 --- a/rust/kernel/alloc/kvec.rs +++ b/rust/kernel/alloc/kvec.rs @@ -1242,6 +1242,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, --=20 2.55.0 From nobody Mon Sep 28 21:03:44 2026 Received: from BL0PR03CU003.outbound.protection.outlook.com (mail-eastusazon11012048.outbound.protection.outlook.com [52.101.53.48]) (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 035D4377ECA; Mon, 28 Sep 2026 08:43:17 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=fail smtp.client-ip=52.101.53.48 ARC-Seal: i=2; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1790585000; cv=fail; b=WaB+EQDFKKZNaf/uGTiqzUiB21qJUyNHkoJpKBL/O1OcfjxmJLSoXKGOErNyBlv26mAKwYl/YC1oT7tcppXnPpRyGSzrZpasCrzw5P9b0g5MHHr/PQTG3TFMiVxuZZFAyzwcy8TGyf6jYzJ+elSq0olC3LsqrWuvRu8VrCOGKDg= ARC-Message-Signature: i=2; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1790585000; c=relaxed/simple; bh=N1+1QdzF0147TR9pjiReVPFo7PeCGIPCUhzl14a/iEw=; h=From:Date:Subject:Content-Type:Message-Id:References:In-Reply-To: To:Cc:MIME-Version; b=q/78ZxW5QuyHCJK0BmQv5Vg6Cj6AJ+Wy36taWIys1pGJkl1bi+3s5Pp7YLXacRW0hPhEXU/J771c96xssfu7QmU5MeurDH2dEsd2MCYFajcm3A6zzdkAcYKatDlxk+xEFzcu8+GhOR6im8wKvOyor9Pp6pS+UXAC3Gi/WUAeDLg= 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=hyuMyI2t; arc=fail smtp.client-ip=52.101.53.48 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="hyuMyI2t" ARC-Seal: i=1; a=rsa-sha256; s=arcselector10001; d=microsoft.com; cv=none; b=vcjpFjuk1heijbN4YPL440zABJWW+8fUib48hlozXuUEkEF/qrQ9/QcKqPYOlw03SMvZJ/3t26gCvSIq2ZSzKYide8cgDiQwqshiDuYMtyJhasrAyoYKx4J4rD7v9/heQ9JlnsNdC01PdSuzKxtOED5RDpGzYpwl4EjSgCjUdoCpXbz50dJkjfBZIHXVADMY/G1x5J65yafuXiLngc72vrMH2DhvJSpFc7XQyj1VT63wMGtND4nOau7C+LP+awA8lHPPmmpb0HhLl70zPwLx5sKv4qxD3Bu2if5azVLvTm09aHRh1Yxg5rE/jUaA9Bpf3HTLX0b4TY9eiP/ybt5RhQ== 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=9bQZ4Pl+C2/kVysjclV8MULoP6A6723vSBikcamVv9Y=; b=aPaYk8koj0EU89ju3BK0ma6Ffn8UiVJk9e3qRj/9D3aZ+TdZEFMFIDe5P9RUuSR7COVrKil9none+/v/zQQd21Ao3P0MWwTLMjZtq4cLOZBNLElKpdi42JvKChq8NU9+P1kdRKxwh0v4RvxFOG6ZftsnT1DrulJvOo7sVptf8TkFOOhq4o0zuF/iVaC7qtVkvK+eewY55BGEeBQ7dd075Hpi+T62FspRpxuGtOQvS+9e/pSAZis/ISiIFH6mEjz/vfEi3knDCS2ckzGjoysgNG2xw1HcLTlxUzhpwWw3S/lMyhhSK6kIEhUIkoCFE8R9Ie0boMMeZc+ygDOp7U0ZSQ== 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=9bQZ4Pl+C2/kVysjclV8MULoP6A6723vSBikcamVv9Y=; b=hyuMyI2t8lAR7kdFMKs2/rw66/dtFW1XERs9byJ1ZqPm5oWUXhTNufeJ+Fy6+dDBZlOlXm2dL2cFfIwZwNJXLPIuRCckrX74oniFzkzJj4YW+QNjEs5EOWYKCNrr/xShH0OZqq2ozP+7dmiP7d8XBtiGGYFM6ArOL2DHJQbeJk0zFtrK/8/EFEFlYTlvYm0DEsiYnDSuxCX8gQ9yc+HtxB8HgO5jMGFcCGWVBh3ksMN1u5sYjnbuqAJ2BQqpmWyf8D5KsdX2YsyPv8jzXNFyH4WTytWM9A1AhEII8JR+8CfqTi+itz6Pg8/aMO4XxFhlcYP3gBTkNO3kgBHjfdjVqg== Authentication-Results: mx.microsoft.com 1; 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 CY8PR12MB8065.namprd12.prod.outlook.com (2603:10b6:930:73::16) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.21.451.24; Mon, 28 Sep 2026 08:43:13 +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.0451.022; Mon, 28 Sep 2026 08:43:13 +0000 From: Eliot Courtney Date: Mon, 28 Sep 2026 17:42:43 +0900 Subject: [PATCH v3 4/8] gpu: nova-core: add NVKV encoder Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260928-b4-nvkv-v3-4-f04504c262c2@nvidia.com> References: <20260928-b4-nvkv-v3-0-f04504c262c2@nvidia.com> In-Reply-To: <20260928-b4-nvkv-v3-0-f04504c262c2@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: TY6P301CA0006.JPNP301.PROD.OUTLOOK.COM (2603:1096:405:3be::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_|CY8PR12MB8065:EE_ X-MS-Office365-Filtering-Correlation-Id: 0780275d-3497-469e-0dce-08df1d3c880e X-MS-Exchange-SenderADCheck: 1 X-MS-Exchange-AntiSpam-Relay: 0 X-Microsoft-Antispam: BCL:0;ARA:13230040|10070799003|23010399003|7416014|376014|1800799024|366016|10067099003|56012099006|11063799006|921020|3023799007|6133799003|18002099003|22082099003; X-Microsoft-Antispam-Message-Info: GJS9DGw/zhovl6BQhtcUwhM3qHVPTw3RKiuqKRGdN1aIuC+oqGPoK/NAk2IfKbGx5ExmrkWBZMp0Qbr5l4aI/a83lKo/RtOK4cxE+Th0zYAB3kvYvIc3wfgpemV3RD+UGsNdzbYUxWuLyC0ch4wJjfY8ops2G4OxJ3ji90l5Bp8ToZU7VbKVjUIIlnqKm+FneKvm6fVCGLH6WYB/yeS2JT9m8L2+txsX3imGHA830Eah6fgpsr2RODg16kqtfYXe5P0Ku4GWMLtCA57yPHMP+wyAA0wOyz26H4sqisjq5Drw8xvlDN3gNAFlVqVc7FK2D1hk3qS0fS22jDCibDsetKdolodhmjhLDeQrx0HT/v+Z1zfZJJzQ6o02Pt6xjSk/TY2F7WCG3JOog0aIOMv67udMmCPbtoUO2fheFPjHYL9vXnl+/ErFhxshnQ1oVgkpGQ3gSmbkJsNl5NIdyerqPqFB7cWXePuOPjXfA/qABpFzJVaOMgtOtfPcj7RmSVF3t1zkhqupNSzkXsAPmHcD48fNKP/0BVbyXaNvztdRDSIrmc1ByTlihyt3LMgTcHOYzrbmANjR2MIjMhpBAYl9O/22p/0vyrjF8dBdtnEMIaqMVJxKGxay1eQjXnWB4+JRbxNjy9BzkoVBA+RuwBcMlrrqHblFExS7tsAegQeoYwiX/YDbYfMSG2f7N6MSzud9IUFWAV3VpYarg0mSPyeUOA== 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)(10070799003)(23010399003)(7416014)(376014)(1800799024)(366016)(10067099003)(56012099006)(11063799006)(921020)(3023799007)(6133799003)(18002099003)(22082099003);DIR:OUT;SFP:1101; X-MS-Exchange-AntiSpam-MessageData-ChunkCount: 2 X-MS-Exchange-AntiSpam-MessageData-0: =?utf-8?B?Ulpxa3pva0hWTHJwWHZzSUZLOVNCem9ha05PemhQdFFveml4STZQTzRGbkhC?= =?utf-8?B?MGx6ZElBYlQ5M0VGN1RKcVFWaGlOTmgrY1JPd1hXbDFIaVh4Nmg2K2lSU01r?= =?utf-8?B?SHNrWnB3ZldadGY0b3BheVBHMTZmVXFqK3F2NTN0ZFlrbGFFcXlIRnljbG8w?= =?utf-8?B?KzkvSTl6YndWOWJvSWpadDhJQi9sVnVIUGFxazFWK2dLWFNIclhQK1JUamRY?= =?utf-8?B?RUZVVzJNUnVJQXRuVXJ0OEd5MXlVeEwrZVlXdkFGWjNDK1NDMEYrV3NIY2lv?= =?utf-8?B?dVRSL1ZEY2hsUHpLa1JjTVhnOXZXTWVxRHNEaUI2MmhUeXBMM0tzK2c4WkM5?= =?utf-8?B?T3FnU3UrVnFiV0YxR3Z6Nk5DU0pleGJML29oY2VkcTQ0d3l2c09Pd25ZRkI3?= =?utf-8?B?OUY3VFNDV0J0UVd3TnlWTkFBd2l1WTNWMG51eUx6cVBDbkFQMENCK0JyYkVo?= =?utf-8?B?NGkxWi92WkpKdlM2Q1RFWWdTbTQvMU5reFZWZWYvNENjZ0ZQbFNGMFlMa1Jm?= =?utf-8?B?OVZHREtWZDNZY0RGTXE3Z3FnVXVBdXh3RHI0czJpWGJRcGY4cmlFa2Zma3Qz?= =?utf-8?B?NnV4a09BQ1JhRnhBZkxIMmtnZ2RudHFoeEhnVnlEdzh0M0NJYWU1VXg5ZllM?= =?utf-8?B?Y2Nic0ovcEtYa3IrN0R5b2xGYThkM01HVk1RNFFwdmNCVmVLZjVIUGV4YmNj?= =?utf-8?B?b05ETEpRWktLM0pEQU9PY3c1amRQa2ZpMUpMR29McHZlMnFyVXFPYnhLbW5B?= =?utf-8?B?OWhCSmdqbjY3OUE4QWpUcnJwTFRqRWo2MEZsUUtTdkpDU1hXU1N6eEpuY2Fl?= =?utf-8?B?OHUzQkZ3Qkl6ZlNPQ29CdVV3NW91YU15d0VIV2prU3lpMGxXOWpVamVWQkNT?= =?utf-8?B?WEJkL3JZWlJWWWJrTXlEcGkzYXBjRmVUSE1ldXFSbXRHUFA4d0U3TXNXdmQ4?= =?utf-8?B?TStZK0lRQTBMTDVVOUo1dU5YQkhqbS95K0dtY01ZelpLbyttcnUzSHptTEZx?= =?utf-8?B?cVpjSXlFOGhwOWtveTIvYVN1eWFXTkNNSlkydjB0S1pINjFXY1Vva25HQ283?= =?utf-8?B?S21BRXBHcS9keFFSTkZhWFpKdXBZdFpWL3h5eVJhZU90UmNZbU1QQ2FqaVhD?= =?utf-8?B?Z2gyRUxPMHE0OEd6ZDlPNjNkdm1uVXhKU29JSWY2UmdRUUt2MkF6bVBHZS8y?= =?utf-8?B?WkZKZUlRQVQ2MTVXRW5qQ2JzQitnWnhjZ2hjOEw0UGZKOHFiZ0E3YlNGbG1q?= =?utf-8?B?RlNpcjhkTFhNZTFrcXJnK2NiOGk2R0FGb0JRYUlzNm9CN1VUL1dhbEEydkVu?= =?utf-8?B?ZjY2bm5uVmpYdFhjZjBrazhneHZaRk9UblgzUFhrZTAyWHN6czIyUTR2bm4z?= =?utf-8?B?dk1VajJuUDE0RXllQmN2VkNQNDU0bGRZUVA1dytzR25hZk1XaWE5RWFrMFB3?= =?utf-8?B?aHhoVjkyK3A4K3p4NUNDakg5MDdBTHpPTXlVUTkxNm1PbWlTSVFNemRueFFk?= =?utf-8?B?NkNqdUtkaUE5K29MS3NCUkRrTVpUMzM4YUpRTEMxZ0J5WkhabVM4eHVRTmFI?= =?utf-8?B?SGZzWEdkcC9zcnhPd3VGS2l1dFZDVzYyaE1NQ0hpK0JpOWR4QUtzcGxRQlV5?= =?utf-8?B?b25wYmxuSFBtUjFBWEE5RC9hV1kvdWJWRzRXWnp5YnY4OTB5aHMxa2I2Vmhs?= =?utf-8?B?UGVDUG1XbVZPUkU0eUptNnF0WFFWb1F4NlE3emdmRENwNTNqNmZ5cHpXQms5?= =?utf-8?B?eC9kUW8rN3ZJb1lxUCtNSXdXM3pCL2VFVFZneGlhM1psQlJIM3UrS0RiMncw?= =?utf-8?B?MWZ4VGhvWU5qeVVWbENoK2IwSzhsaS9mOGFLWDg1bmdlK0U3SDNkc2JSNWNJ?= =?utf-8?B?Z0t5aisrS1cvYUJpTjJ3dVQxK2lXb0drSlMrcVNVTGJmalV0ZldIMjI5UDRr?= =?utf-8?B?RGhmOTBNYmI0TjRFV3JxaDBRVmRsalplblBPYS8zT1lOakVKWFRjUHJSejZR?= =?utf-8?B?NE1IeFFiT2JGYXk4QWl6aWdVeWZJWVJRNzFmL25YSEhTejQzN1RUOEJQMW5U?= =?utf-8?B?MDhzcnBySWF6VUk3QloyWFRjbVZrUHJIRlJNM2R0Szl2dkE3OFkzYkxNSWtw?= =?utf-8?B?TFpaTEZUV3FibnhwcGI4WGZ1R0VOQmYvY3BydFBjcExuNGtYOTk0WjF2dndu?= =?utf-8?B?T01MY1ljeEZzR0ZLNHQzeWJoaUduVDY5dXF0bkpQR3hlb1ZmaXAxclFyaTZB?= =?utf-8?B?MHRaclptQ2QwdjVINy9aWXBGbTAvVGVWVDNPbTBkcHFjRGNjc1JIRmxnMVpa?= =?utf-8?B?Z1B2TXpKcGZjUUdBT2ZxcEJVVTFlQ2NOWTVjeXZjWXJ5V1FscDBIYm9VeWdQ?= =?utf-8?Q?tGwtJ6pHrYIYLnjrvWBi17IqEbtpdGhttRXr6yh7NmEXJ?= X-MS-Exchange-AntiSpam-MessageData-1: j9fl+feSpfKNAA== X-OriginatorOrg: Nvidia.com X-MS-Exchange-CrossTenant-Network-Message-Id: 0780275d-3497-469e-0dce-08df1d3c880e X-MS-Exchange-CrossTenant-AuthSource: DS0PR12MB6413.namprd12.prod.outlook.com X-MS-Exchange-CrossTenant-AuthAs: Internal X-MS-Exchange-CrossTenant-OriginalArrivalTime: 28 Sep 2026 08:43:13.0092 (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: KubIG4GOZPChFQbP8RnGk/Xlb/rvpb9D9U4uMw2gaJz/N+3sQ2u+L/jd51+STwKtUQguWNpchWXuN9iLJErCdQ== X-MS-Exchange-Transport-CrossTenantHeadersStamped: CY8PR12MB8065 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. Co-developed-by: Danilo Krummrich Signed-off-by: Danilo Krummrich Signed-off-by: Eliot Courtney --- drivers/gpu/nova-core/gsp.rs | 1 + drivers/gpu/nova-core/gsp/nvkv.rs | 122 ++++++++++++++++++ drivers/gpu/nova-core/gsp/nvkv/encode.rs | 210 +++++++++++++++++++++++++++= ++++ 3 files changed, 333 insertions(+) diff --git a/drivers/gpu/nova-core/gsp.rs b/drivers/gpu/nova-core/gsp.rs index 25ea43f1cbe9..7b1bf74464e2 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..0957dce92f96 --- /dev/null +++ b/drivers/gpu/nova-core/gsp/nvkv.rs @@ -0,0 +1,122 @@ +// 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; + +use crate::bounded_enum; + +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; + } +} + +bounded_enum! { + /// Describes the format of the following NVKV operation. + #[derive(Debug, Copy, Clone, PartialEq, Eq)] + #[repr(u8)] + enum Opcode with TryFrom> { + /// 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, + } +} 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..8b66ddb15189 --- /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] + 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] + 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] + 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] + 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] + 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] + 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 Mon Sep 28 21:03:44 2026 Received: from BL0PR03CU003.outbound.protection.outlook.com (mail-eastusazon11012058.outbound.protection.outlook.com [52.101.53.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 7609A4734F3; Mon, 28 Sep 2026 08:43:23 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=fail smtp.client-ip=52.101.53.58 ARC-Seal: i=2; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1790585006; cv=fail; b=av0mNrXFH6giN6mpD11VbD/l42dEsN0GPY4TxLl9wld7YksAzQYzXkpLUM07FacLPUnGIOTxToMIexO9CXUYzJHs/N9a7AwYtppMFhowqpuAjO1Mb9t7XUMhiuAlEXbKW6aRSVA3sScQYVSYrLkZArlED3kbF15qXMAEbxw6j5o= ARC-Message-Signature: i=2; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1790585006; c=relaxed/simple; bh=7lMYaxmlA9yRVJ3DnQUcsK6nxX6VMrV3FZJf7l/hCJI=; h=From:Date:Subject:Content-Type:Message-Id:References:In-Reply-To: To:Cc:MIME-Version; b=jPXXZ1j+wPrh2rtY9K16KrUtvcqSbOB7V/+UIhc9Hs2fBmt3t2S7fmBzbpjgyl58LxnXMmaW/+uBvzCKxYUm4NLyJovnwL7QM1NK9iy6uJSur5kkx3ZC4tXPJmYTW2C5PZixz/Pk7ClMtUAPEp9gjUEGmnO+G8OXP/TyrruLm7c= 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=pImOQnUC; arc=fail smtp.client-ip=52.101.53.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="pImOQnUC" ARC-Seal: i=1; a=rsa-sha256; s=arcselector10001; d=microsoft.com; cv=none; b=f4Ke9PPRalbYjWOiDufhmk5Ov8/6hsuVFmSFVrPYDHxgjXHY1TqSFeG6ZR0R08ZYF4ueHDjqU8UuvD331Vt4qpmyxx6Nl/mnGpBbFTnBLmlS6TJhML8aXxNuDnWxFAFcXG5B6UizdRU0c6sLg8R+OpMKF+s3xr6mgUnVtVIA5cv+bSUMs4tIscPYtNsbAkutwGLo7mo0fnoXW6F+Fb5Kit7BAm5lxcy2mXkCYylfOIgfIR4rsX1MdGzqof1PCYcUe2l+IUpIT+5a2REmBc/OAPI+H4PkQ4Yd5/emb/eKiC5Qf36B2kngugvpztrk0/Su5sJQmIaMkQXVXdzshqXjfQ== 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=AJXPfWZO2tnS6TioG9/H0fswKfkJWTXZb/a1ChqVc1s=; b=FLA0TdcuAsxc94c9ZhALJ1hJL6JOO7FySZqCkSO+/J9NLMtti0eQ7r2CdEQmVgu5Z403Xwv546YuYRYNAFMNWf9BBS3LIvlmjvhX8e1aEcQidKUZA4eWqVEGihr2saEf7QA7cA6+9njLCGZfUsS9x9hJF6wf8IEUVRjSerbBbkbJROzcbzk+JqS6x5HB1/JN+DyB5ZyblgRnptr4F/RQunHAdDbvcsAPWTfWX9OZUfoYSynxeT3+p72HBsXbpVmO+G31lLbyEzjC+f+Cr1dhZA9QA/zeTUqdupqymr7xLL6ZJ2byPFiryOzjPo+NPdqbaPTh/1dTRD6fYAd/eOvCQQ== 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=AJXPfWZO2tnS6TioG9/H0fswKfkJWTXZb/a1ChqVc1s=; b=pImOQnUCVLzcxfpqCw1/3ihv7gDzCuR+aZlXpTuab/BBALJOrtXl3TLrbuluDOuo3/eIV/C44G0jKS3EJMBjHPJ6Qu42AcXQkK+ARooZoI0p2DEJ6KnHSLkiiIVIHpLCJPVSbDEsy2ntmXYr+gFkbO81abjLLgcmsE6a1o+ga1zwKafV+jQf7ag9JPXqYeZBmRMrPVIr+L3WL1aJArbgBt7IS148mLgwxm9G+Xxav5vB6SRrMOFgTLr85P/cWqjAY0bmLp59KWSdLZljbBPhwvdb3pdtp5Kk5pFLFjrm4yMASBBWBIkLthJf23azbzi4pADCNoPG4EDaf6MjC5MxZg== Authentication-Results: mx.microsoft.com 1; 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 CY8PR12MB8065.namprd12.prod.outlook.com (2603:10b6:930:73::16) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.21.451.24; Mon, 28 Sep 2026 08:43:17 +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.0451.022; Mon, 28 Sep 2026 08:43:16 +0000 From: Eliot Courtney Date: Mon, 28 Sep 2026 17:42:44 +0900 Subject: [PATCH v3 5/8] gpu: nova-core: add NVKV decoder Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260928-b4-nvkv-v3-5-f04504c262c2@nvidia.com> References: <20260928-b4-nvkv-v3-0-f04504c262c2@nvidia.com> In-Reply-To: <20260928-b4-nvkv-v3-0-f04504c262c2@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: TY4P286CA0050.JPNP286.PROD.OUTLOOK.COM (2603:1096:405:36e::19) 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_|CY8PR12MB8065:EE_ X-MS-Office365-Filtering-Correlation-Id: 4c64b694-5e68-4614-6757-08df1d3c8a71 X-MS-Exchange-SenderADCheck: 1 X-MS-Exchange-AntiSpam-Relay: 0 X-Microsoft-Antispam: BCL:0;ARA:13230040|10070799003|23010399003|7416014|376014|1800799024|366016|10067099003|56012099006|5023799004|11063799006|921020|3023799007|6133799003|18002099003|22082099003; X-Microsoft-Antispam-Message-Info: c8eCo+2T8SvpInwjyMPna0FV4z8a/J76BoWd9kTHWLHNzt3fyF32Y9/z/t7EEfalMNNrlfU5eY3NdQ7eZkvqJY+qH/oS9xT8wfbGNKGp/bq5TSbm084PTGtQU4VNeKD2s3es0KVSsebjGQWmxuSLvB+L5g3Kh11bkJBrmeAN6uVMauxpWZ9W/fAAiu4lwntPFzxlx0qQW4Q9BY2ImaL54Aio012kb7G5Alpqln45smb164riVAqOPD3afnto/ktmB6Ku36ABvITVsSacxazaVIYoeTyDhKq0mjl/5Ri3yfREsiqAWbSf7VArBkvt1qMgn4l+wsy9LXrs5d/pRKwq4VCdfXBzrjbBWCubk939gdZObNQmn/rP2IulLX+pgkGeRuru3qEsqCg50b+z0GIC+eHjAB75rvauM9AVFJ/36coLOb5FhQAnWD/d9y/6nzMQ/TtBPE6rQKFseQJhbp6yWPDFw2zid8T10uo2pMFTYxWJin3faYv0+xk0bWSc9ivTk6Cgqp+ctEiG6u67G0vtroBrPjq7VjGYiiRmCkbPjJO4bmdKGCICzTtCon3SBMpShM1l14jwGmz9/hZfQrSbkwmTYOOSoBdX2H9PiOOKYI+45H/El5yrIuf0OscG0wgK4Ja6uQgzlywsq3RQAyI8OBK8kLYr4jkAw9cfgXT1fcKDK1xs3Toe87WRrcVFowSLPDHze8oZ+LugywJP30FeVg== 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)(10070799003)(23010399003)(7416014)(376014)(1800799024)(366016)(10067099003)(56012099006)(5023799004)(11063799006)(921020)(3023799007)(6133799003)(18002099003)(22082099003);DIR:OUT;SFP:1101; X-MS-Exchange-AntiSpam-MessageData-ChunkCount: 2 X-MS-Exchange-AntiSpam-MessageData-0: =?utf-8?B?WElGV0xKZUpPTk9aTjlEV3Z1T3lkeWlidU81bVZYOFFoYWN6WVB3RlBtZFk0?= =?utf-8?B?NkRRMVMzZitmZHQwWGNzYjVKOUJmU0lVZ0k1VUxyNWdWak1yT2FpcVpGODUw?= =?utf-8?B?YTRJSkRka3puejZsRFdkeDVtaC9VVllrWWJTd3Nla01MRlh6akJmcE5PcnpZ?= =?utf-8?B?VVdUa2hRV0c1dlh1QjlUMnZyWUtteVhPOC9wWTFvZHZmVGkwSUxYRW9hdUZY?= =?utf-8?B?K3N2Y2MvQUVMdTRqNzdBdGcxMklZSUtmSlMvTk1YaytObytha1NwcU41SkVq?= =?utf-8?B?Zk44bTlCdGhQb3NvdnZqTDk5ajIxbzJZWlhKZUxBNmg0ckp0ZGpWTXN0TmlP?= =?utf-8?B?cCt5a25GYi9qUU9ieW9JOThUZXJidjdybHpQYWJpam5DcGJVUDQ1bU1GcVgr?= =?utf-8?B?RmNnbVF4NWYrbG9Yb042NklPRDVudUY5Qzcxd0VoRFJqQTNYOERLNEdYTWdW?= =?utf-8?B?RmdoS081UDlPS0l1KytoVWlVa29pdVJoZ2NTSXFQd2c3RmVUYXZmN2pPVy9J?= =?utf-8?B?NXhrVlpFbHRqVVMvWXMzN0RUZ3VmVkxYdGQra2pjUE5aWmlockFNR0FPQWcr?= =?utf-8?B?Tm4rSGNVanNNbDhaR1MwTjR6WGNwcVVTeFFTSEU0QlRSYnlqQzBnSmNORHFG?= =?utf-8?B?WjI3b1cxek9IV0ZiNXZoSXYxWGRaK2tGcXhFeTV6WmhRNjY3eVFDMElkVkkr?= =?utf-8?B?dHp6ZmZVRlBQUndQOUNHVitnaXV1dmFiTEV1ZHo1cVl5WEtXQXlvTlVlcjhq?= =?utf-8?B?bzBvR28zK09uMm90cWZmSTArUmlSQUZieWZncDlENDlzaEF2QWx5YnNsNXdr?= =?utf-8?B?REdvcmhURTRYMmxIdHRCMXR2TU9GUWF3WXd5UEZFTVZURUJzWmpTRzlWUENz?= =?utf-8?B?RFduWE1lTEZmSHlUalF2TkxtY20xYjRITllYbmhCeUFiMDR0YXUrSVlPRkpk?= =?utf-8?B?dmUrdWwxMGRFTURXMU01RkhGakxWU2hpS2tRZ0FLT0ZRNWQvR2srVmptWkw3?= =?utf-8?B?K2hGbHJxVDdwRVNla0VTMDlidjZ4dTVsMzV2K2o4Y3UzNEhXV0FmaDNPdzc2?= =?utf-8?B?dU96SHJvSGl3YzNQOWZDZW1VZG1BTmZ3RTBFVk5lL21IUEFNT3lIemttc1hN?= =?utf-8?B?cEZLWjJoMHIyTFZzOEY0ZlZRNzM4SEMxRzkyUGIxNzBOQ1Rob1A5NGUyWDJa?= =?utf-8?B?RzYvQ0diWE5ZakRvbHpQZkRZVFl3ZVRXdHphb3BtbzdYRVE2UjdVR3ljUEs1?= =?utf-8?B?MTdoRmwvcDU5ZEdCTXZpaGY5ZGhmd2pYdmdLd28wSVpyTnVwUjZhSHM5a1JH?= =?utf-8?B?NmsyL2trL3lzN285NWR6WEMvN2JYZEQ3ak45OXlNOWdYVEVjZllXTVZnVDNE?= =?utf-8?B?MXhTTXdCQ2dyRUE0SnBodmNEK0Y5NWIyaEFkYVh3V3NUU3hrZnAxNWd6QS92?= =?utf-8?B?RTIzdUFEL3hGYlhPd25Cc0RydVZ3VnNwbm1kQnMxb3lGVGdwT0J5V2dZQXRZ?= =?utf-8?B?ODBYL29oelRSZ3llOVhvZ25XR3c4Qi9oS0s2OVVWWks0QUszRzlFYVBpekZK?= =?utf-8?B?N2E2Vy9ISmk5REpwenVkdHQ0eWl5Q0NqUGVnVzZjNVpBOHFNay9TOTdQNTJW?= =?utf-8?B?SlJ4M0cydFBEQ2ZCcXJ2clRrS2RUMVR1U1U2VmVxRUo4SjlzTGVMaHVkZGw5?= =?utf-8?B?aEVqcnZsek9NODlKUkJVcWdZV3dYN0FMOVV6d3BiOStETmJUUWF6a1pSL1Yx?= =?utf-8?B?NVU2MFUyaXNhM2p1SFlzQnI4TTluZlkvVFRYYWFOUnV4N2h0QWdMTE5NZHNG?= =?utf-8?B?TWEwdGpjejNkMVdDRnFrNzlNKzdFRHl4MTdYWTVHemY5WVc0T2ZUbXFvWHls?= =?utf-8?B?SERrZXUvR0VsaVIyUEdyUlc1MWMxc1RLaGttNXNzdnVuamgvd1ZsSXdReCtI?= =?utf-8?B?NXRMWlhESDg3YVRROTl6WnpVMDdrejlyOFhKVXBlaW8yKzFESnh0RHpEQUxp?= =?utf-8?B?TGVDb2NNWEJKOFlvOHpFc0RPeVpBVGtkbDhIWWN2RjFrNGtQcWlIMVBFdFZq?= =?utf-8?B?UnpkQ3RUT2lDRlRvVit3UUFZZkVBUDRUWG5yaUluYVNueU9rSkZVOStHSExo?= =?utf-8?B?NXpCYzJLMFVZbmVrWGYybHpOSUl0VjFSQzZBSUhnaXc1bHFJSkhqVUpaR0lK?= =?utf-8?B?QURvYmgzQTlTVG1zaVpxVVF4Vi9Oa0EwTFM1TFF5N1NkcUl5UkkzWHVRRTBj?= =?utf-8?B?d0xFUUIvTklYV1E4TVBYUlkzR1ZzMlozZ05ZZnRtZzhNUCtlNmFEcXhyYnNN?= =?utf-8?B?ZzQzT0tSWU1Db3ZBb3JyWUdwd2N6MlA1VldtTnpJWlZCc1dmbkN6MWwzbkpp?= =?utf-8?Q?24BB9G18cesNV6V0IMdwYBvofhaShBSSWDvJ3thhFI7b5?= X-MS-Exchange-AntiSpam-MessageData-1: c8j55njV5znM9A== X-OriginatorOrg: Nvidia.com X-MS-Exchange-CrossTenant-Network-Message-Id: 4c64b694-5e68-4614-6757-08df1d3c8a71 X-MS-Exchange-CrossTenant-AuthSource: DS0PR12MB6413.namprd12.prod.outlook.com X-MS-Exchange-CrossTenant-AuthAs: Internal X-MS-Exchange-CrossTenant-OriginalArrivalTime: 28 Sep 2026 08:43:16.8823 (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: a5fb1r/Dn1WO7A/FKLhkXfG+szjKEb8ll2xsKtFo03a7JJKJlmzOQfT08+P5qfxC6tm8Ee8imn3SMBujPhVuuw== X-MS-Exchange-Transport-CrossTenantHeadersStamped: CY8PR12MB8065 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` and `Visit` trait. The `Visit` 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 | 487 +++++++++++++++++++++++++++= ++++ 2 files changed, 490 insertions(+) diff --git a/drivers/gpu/nova-core/gsp/nvkv.rs b/drivers/gpu/nova-core/gsp/= nvkv.rs index 0957dce92f96..10f7a16ffc23 100644 --- a/drivers/gpu/nova-core/gsp/nvkv.rs +++ b/drivers/gpu/nova-core/gsp/nvkv.rs @@ -29,6 +29,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..c4c24fe1108e --- /dev/null +++ b/drivers/gpu/nova-core/gsp/nvkv/decode.rs @@ -0,0 +1,487 @@ +// 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, // + }, + num, // +}; + +/// A decoded NVKV value. +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +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; + + /// Returns an initializer that creates an empty schema in place. + /// + /// Use [`KBox::init`] for the heap or `stack_pin_init!` for the stack= (if sure that the value + /// is small enough to fit). + fn init() -> impl Init + where + Self: Sized; + + /// 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 visitor that consumes decoded NVKV from a stream. +/// +/// A schema that doesn't need to borrow data from the stream can implemen= t this for all `'data` +/// lifetimes, avoiding having to carry the lifetime parameter. A schema t= hat borrows from the +/// stream directly should implements it for its own lifetime only. +pub(crate) trait Visit<'data> { + /// Visits one decoded pair. Returns `Ok(true)` if the schema consumed= it. + fn visit(&mut self, key: KeyId, index: Index, value: DecoderValue<'dat= a>) -> Result; +} + +/// A read position in an NVKV stream. +struct Cursor<'a> { + data: &'a [u64], +} + +impl<'a> Cursor<'a> { + /// Creates a cursor at the start of `data`. + fn new(data: &'a [u64]) -> Self { + Self { data } + } + + /// Returns `true` if no `u64` values remain. + fn is_empty(&self) -> bool { + self.data.is_empty() + } + + /// Takes the next `u64`. + fn take_u64(&mut self) -> Result { + // PANIC: `take_u64s(1)` returns exactly one element on success. + Ok(self.take_u64s(1)?[0]) + } + + /// Takes `count` bytes. If `count` is not a multiple of 8 (`u64` size= ), bytes are discarded up + /// to the next multiple. + fn take_u8s(&mut self, count: usize) -> Result<&'a [u8]> { + let values =3D self.take_u64s(count.div_ceil(8))?; + values.as_bytes().get(..count).ok_or(EINVAL) + } + + /// Takes `count` 32-bit values. If `count` is not a multiple of 2 (`u= 64` size), bytes are + /// discarded up to the next multiple. + fn take_u32s(&mut self, count: usize) -> Result<&'a [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) + } + + /// Takes `count` `u64` values, or fails with `EINVAL` if fewer remain. + fn take_u64s(&mut self, count: usize) -> Result<&'a [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<'a>, + ) -> 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 + Visit<'a>>( + &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 = can 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 init() -> impl Init { + Self::default() + } + + fn finish(&mut self) -> impl Init + '_ { + Ok(core::mem::take(self)) + } + } + + impl<'d> Visit<'d> for RawSchema { + fn visit(&mut self, key: KeyId, index: Index, value: DecoderVa= lue<'d>) -> 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) + } + } + + 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 KBox::init(RawSchema::init(), GFP_KERNEL)?; + let decoded =3D KBox::try_init(decoder.decode(&mut *schema)?, GFP_= KERNEL)?; + + 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); + let mut schema =3D KBox::init(RawSchema::init(), GFP_KERNEL)?; + assert!(decoder.decode(&mut *schema).is_err()); + + let decoder =3D Decoder::new(&serialized, UnknownKeyPolicy::Ignore= ); + let mut schema =3D KBox::init(RawSchema::init(), GFP_KERNEL)?; + let decoded =3D KBox::try_init(decoder.decode(&mut *schema)?, GFP_= KERNEL)?; + assert_eq!(decoded.scalar32, 0); + + Ok(()) + } + + /// Records each visit as (key, index, value), for tests on hand-built= streams. + #[derive(Default)] + struct Recorder<'d> { + visits: KVVec<(KeyId, u64, DecoderValue<'d>)>, + } + + impl<'d> Schema for Recorder<'d> { + type Target =3D KVVec<(KeyId, u64, DecoderValue<'d>)>; + + fn init() -> impl Init { + Self::default() + } + + fn finish(&mut self) -> impl Init + '_ { + Ok(core::mem::take(&mut self.visits)) + } + } + + impl<'d> Visit<'d> for Recorder<'d> { + fn visit(&mut self, key: KeyId, index: Index, value: DecoderValue<= 'd>) -> Result { + self.visits.push((key, index.get(), value), GFP_KERNEL)?; + Ok(true) + } + } + + // Tests the decoder on hand-built `u64` values that the encoder does = not produce: SEQ32, + // multi-value SEQ64, zero counts, a non-zero index and padded arrays. + #[test] + fn decode_raw_u64s() -> Result { + const SEQ32_KEY: KeyId =3D 0x2000; + const SEQ64_KEY: KeyId =3D 0x2010; + const EMPTY_SEQ64_KEY: KeyId =3D 0x2020; + const EMPTY_SEQ32_KEY: KeyId =3D 0x2021; + const EMPTY_ARRAY8_KEY: KeyId =3D 0x2030; + const EMPTY_ARRAY32_KEY: KeyId =3D 0x2031; + const EMPTY_ARRAY64_KEY: KeyId =3D 0x2032; + const ARRAY8_KEY: KeyId =3D 0x2040; + const ARRAY32_KEY: KeyId =3D 0x2041; + + let index3 =3D Index::new::<3>(); + let data =3D [ + // SEQ32 with three values for three consecutive keys, packed = two per `u64`. + Op::zeroed() + .with_key(SEQ32_KEY) + .with_opcode(Opcode::Seq32) + .with_value(3u32) + .into_raw(), + 0x0000_0002_0000_0001, + 0x0000_0000_0000_0003, + // SEQ64 with two values at a non-zero index. + Op::zeroed() + .with_key(SEQ64_KEY) + .with_index(index3) + .with_opcode(Opcode::Seq64) + .with_value(2u32) + .into_raw(), + 0x1111_1111_1111_1111, + 0x2222_2222_2222_2222, + // A zero-count sequence has no keys and is skipped, as in NVI= DIA's decoder. + Op::zeroed() + .with_key(EMPTY_SEQ64_KEY) + .with_opcode(Opcode::Seq64) + .with_value(0u32) + .into_raw(), + Op::zeroed() + .with_key(EMPTY_SEQ32_KEY) + .with_opcode(Opcode::Seq32) + .with_value(0u32) + .into_raw(), + // A zero-length array is visited with an empty slice. + Op::zeroed() + .with_key(EMPTY_ARRAY8_KEY) + .with_opcode(Opcode::Array8) + .with_value(0u32) + .into_raw(), + Op::zeroed() + .with_key(EMPTY_ARRAY32_KEY) + .with_opcode(Opcode::Array32) + .with_value(0u32) + .into_raw(), + Op::zeroed() + .with_key(EMPTY_ARRAY64_KEY) + .with_opcode(Opcode::Array64) + .with_value(0u32) + .into_raw(), + // Three bytes padded to one `u64`, then three 32-bit values p= added to two `u64` values. + Op::zeroed() + .with_key(ARRAY8_KEY) + .with_opcode(Opcode::Array8) + .with_value(3u32) + .into_raw(), + 0x0000_0000_00cc_bbaa, + Op::zeroed() + .with_key(ARRAY32_KEY) + .with_opcode(Opcode::Array32) + .with_value(3u32) + .into_raw(), + 0x0000_0002_0000_0001, + 0x0000_0000_0000_0003, + ]; + + let decoder =3D Decoder::new(&data, UnknownKeyPolicy::Error); + let mut schema =3D KBox::init(Recorder::init(), GFP_KERNEL)?; + let visits =3D KBox::try_init(decoder.decode(&mut *schema)?, GFP_K= ERNEL)?; + + assert_eq!( + visits.as_slice(), + &[ + (SEQ32_KEY, 0, DecoderValue::Scalar32(1)), + (SEQ32_KEY + 1, 0, DecoderValue::Scalar32(2)), + (SEQ32_KEY + 2, 0, DecoderValue::Scalar32(3)), + (SEQ64_KEY, 3, DecoderValue::Scalar64(0x1111_1111_1111_111= 1)), + ( + SEQ64_KEY + 1, + 3, + DecoderValue::Scalar64(0x2222_2222_2222_2222) + ), + (EMPTY_ARRAY8_KEY, 0, DecoderValue::Array8(&[])), + (EMPTY_ARRAY32_KEY, 0, DecoderValue::Array32(&[])), + (EMPTY_ARRAY64_KEY, 0, DecoderValue::Array64(&[])), + (ARRAY8_KEY, 0, DecoderValue::Array8(&[0xaa, 0xbb, 0xcc])), + (ARRAY32_KEY, 0, DecoderValue::Array32(&[1, 2, 3])), + ] + ); + + Ok(()) + } + + // Tests that decoding a malformed stream fails instead of reading pas= t the payload. + #[test] + fn decode_raw_words_malformed() -> Result { + const KEY: KeyId =3D 0x2100; + + // An `Op` with the reserved opcode 6. + let bad_opcode =3D Op::zeroed().with_key(KEY).into_raw() | (6u64 <= < 28); + let streams: [&[u64]; 6] =3D [ + // 100 bytes need 13 `u64` values, only one follows. + &[ + Op::zeroed() + .with_key(KEY) + .with_opcode(Opcode::Array8) + .with_value(100u32) + .into_raw(), + 0, + ], + // Two 64-bit values, only one follows. + &[ + Op::zeroed() + .with_key(KEY) + .with_opcode(Opcode::Seq64) + .with_value(2u32) + .into_raw(), + 0, + ], + // Three 32-bit values need two `u64` values, only one follows. + &[ + Op::zeroed() + .with_key(KEY) + .with_opcode(Opcode::Array32) + .with_value(3u32) + .into_raw(), + 0, + ], + // A value count with no payload. + &[Op::zeroed() + .with_key(KEY) + .with_opcode(Opcode::Seq32) + .with_value(1u32) + .into_raw()], + &[bad_opcode], + // Consecutive keys that overflow `KeyId`. + &[ + Op::zeroed() + .with_key(KeyId::MAX) + .with_opcode(Opcode::Seq32) + .with_value(2u32) + .into_raw(), + 0, + ], + ]; + + for stream in streams { + let decoder =3D Decoder::new(stream, UnknownKeyPolicy::Ignore); + let mut schema =3D KBox::init(Recorder::init(), GFP_KERNEL)?; + assert!(decoder.decode(&mut *schema).is_err()); + } + + Ok(()) + } +} --=20 2.55.0 From nobody Mon Sep 28 21:03:44 2026 Received: from BL0PR03CU003.outbound.protection.outlook.com (mail-eastusazon11012058.outbound.protection.outlook.com [52.101.53.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 6A0744848AD; Mon, 28 Sep 2026 08:43:26 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=fail smtp.client-ip=52.101.53.58 ARC-Seal: i=2; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1790585008; cv=fail; b=ClIWjaTLRd5LHZlT7Qh8n+pMWnaao+qczFj8JERe2qIRn2o3r7PyEK/gxt9nSF6o5DMPkJLWOtL64Uw+NFH57CpqBdBpv7u6zunEEtl/BEuou58VaHLJTk9LZx74BNi6YGvX+57e0dzZF5kPC2HesN7iwN2OxI/hASIH6OxM2Xc= ARC-Message-Signature: i=2; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1790585008; c=relaxed/simple; bh=PrR7qnLWsTW8Rxq4lz7atZgToDxHIsXpTP/WC158yZQ=; h=From:Date:Subject:Content-Type:Message-Id:References:In-Reply-To: To:Cc:MIME-Version; b=abV5icRrX3RPgNLerRCFBZBqTuNJnb04xwR8IttQN8CpZYWBkCY2RXOj47WbnlHNBJZnGkhmLfbY6uDnDJjo6MsOjxntc/M5VDrg0vfhaP5nIHBKa9MLJqjWMIgT301eJYy4Ex622hlKD1NEUZDRR8UI77r7LTMS4FUwqJZulOw= 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=LjrMkTNm; arc=fail smtp.client-ip=52.101.53.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="LjrMkTNm" ARC-Seal: i=1; a=rsa-sha256; s=arcselector10001; d=microsoft.com; cv=none; b=c1cdMwD56vsYxd3OGmsjbNf5O5TltwNJqmPTcuie9+BPamWn7ob79+eEcV4NWLLFXBSy8wDH1CKledUKRhOygciIHRL3UMEWgsHdtzAYFLDyxzJidCL/K2QNKvZJ6Nz3ZIj4lXQ3AwajoPUcMNYPdv2Cbyzrb3a4ruj60F7Andc8rAHD63i5qCu8CyV8b7oCPrKonOfd94T5zuUlu8R0YeZE3T1q6hPHWFnPbAeVhISIsoEALwsLnh62aPnrlgT/jMcicvIh2/ofr3vIZUqPuUU7uPotRVguWsDZ9yjkt18eU+F8seQn8uBbwplN3GI5hWhKv4KI/5a1h8PjWD513A== 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=+8Qt4tkjb9EowBOopQTF/mGrZ0Ii17ox9dnn6/61w64=; b=DKjczq31HiAyABO0UicyeNor0OWe8qskHEu+xZ0qi8b2DPaZrgYBgKgctVyABbOx4N9o1WvL87LcfeMiTAPt2mlphVoMcXjCVAczy2USWfEe64jJOfUfXhPfk6Nn4ttiEWDJNLgGV1l6Te8BSM0ZnRc1/eZrEXqdCYaqU73YNuGHHJu38m28JmiJUxQzIM27l9vfBC89eicAQ6jqIkT/4c546VctJGi1f9fdkSyApovOi+j9uLMmbzHDZeGuvNX5a82ye7rYn1qrN1itrwNHCqLZGTcrHpE7B8PMsbvhF/N7MWQJ6h4oDY4/lsTcFDFkt7gsC98kkjkCIbVpcMhMFQ== 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=+8Qt4tkjb9EowBOopQTF/mGrZ0Ii17ox9dnn6/61w64=; b=LjrMkTNmpyzoAeP19xyU6bzpxoZd25WiG16zITXg46FaAV5ICdD/1s8K+qAr9tRFPs1GaUTvN5xr93u4xazJ0mVED5ucMxpW7RfzGqmZzAn1j9WzYZOFenUEJ9V06QC/JipTCdusH3jRID3Iwt2WjGFfQoDcsr9Ym80D1GbFPY/5xhQji+kXjLl4RmZpkJICBsT6cXWSjFA+Q8JbB2XHoJmJ19AZAyICYxKSynfbhfOd4jWuXSMueiGIDc1ac8/Uhk91cB52B++Nba5zCPkQR7N7MfHZ/bnGm/0S19WyG6CBFlZpp0Pt+TK+kkHLmY7jjM0PHfnSvaX7jXO39oOu7Q== Authentication-Results: mx.microsoft.com 1; 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 CY8PR12MB8065.namprd12.prod.outlook.com (2603:10b6:930:73::16) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.21.451.24; Mon, 28 Sep 2026 08:43:20 +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.0451.022; Mon, 28 Sep 2026 08:43:20 +0000 From: Eliot Courtney Date: Mon, 28 Sep 2026 17:42:45 +0900 Subject: [PATCH v3 6/8] gpu: nova-core: add NVKV typed encoding Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260928-b4-nvkv-v3-6-f04504c262c2@nvidia.com> References: <20260928-b4-nvkv-v3-0-f04504c262c2@nvidia.com> In-Reply-To: <20260928-b4-nvkv-v3-0-f04504c262c2@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: TY4P301CA0072.JPNP301.PROD.OUTLOOK.COM (2603:1096:405:36f::19) 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_|CY8PR12MB8065:EE_ X-MS-Office365-Filtering-Correlation-Id: 9e880ddb-fb48-4b04-0968-08df1d3c8c58 X-MS-Exchange-SenderADCheck: 1 X-MS-Exchange-AntiSpam-Relay: 0 X-Microsoft-Antispam: BCL:0;ARA:13230040|10070799003|23010399003|7416014|376014|1800799024|366016|10067099003|56012099006|11063799006|921020|3023799007|18002099003|22082099003; X-Microsoft-Antispam-Message-Info: 9hwEPCPpvKWqZEWbL5C0Qj7b1Zfdfj05unLXReOTCBehbevIAEJfSpcOp65Y7TeLdkTyuM3lz68C+IvoWwLhFTURGLhDm2XIACsE0G6Ge+AFvTOscon4kqwMeQobYCDDCJKgOKrKpVCTTHxV7WO0904hhP5qbSP4XgJ5a2kjeyq6XNaYftee/38OTknOCI1WHd5EMEgPIvYpD2Y3N+S1O2gzlu+6UESiLurcK1HllEOvTvtn+vy27VNGSndfZMOPtx3Hw9cGBKFMpQaepX+NFNJuIbMIj8ycnX83K/l9UMCEpmjraPlsCQFXbvKRIpo7YCYCbve/DOn+uexb7IvKYcztKu9msKy9WyC1g19SsrxtszWyBRLGZl3oQgOQk3y2T77rQAIZGTTTaydl0qVp7pHJOZKBSiDpEH/bFTIsibmINsS3k/gQS6jbqlNgXkNCmBu25tvGEiNueWgVWE1dLVHSj+fctziHCrIEb39CB0Uk24yCOOgYiRDm/EV3aIRbxUs0SVT+4lzcX3Y7H1uKl9qQJP+Me/minCJNh4YX/774fHvxAUFgVM6prFyew7F9Xarn6/B/dfvTkNnohYfBa6cPIhKlB2EYE1nSAy42kAjZrdNO8HPB5JOPppn0pgmO8ByhHqdNDDr4C5Ae35St+dJib98/XBV0FPjiwHHndlqP8jHyzHuNN1Xh5JuLLNla9HH+e7vhORjUbNnECVmrcg== 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)(10070799003)(23010399003)(7416014)(376014)(1800799024)(366016)(10067099003)(56012099006)(11063799006)(921020)(3023799007)(18002099003)(22082099003);DIR:OUT;SFP:1101; X-MS-Exchange-AntiSpam-MessageData-ChunkCount: 2 X-MS-Exchange-AntiSpam-MessageData-0: =?utf-8?B?U09zUG1udnRDSDNFYThWOHJnNzNmY09UbVJkWk9tdnVrOW9Nb29VUzdtb3FP?= =?utf-8?B?RGs5WStBYVNZaXErNnVVeEFZa1lXc0hPaTB0bkdpYitLRFcrcWhoYUp4Nko0?= =?utf-8?B?OURBcms1VVd4aVByYXg0aUtSd09mdGVxQy92SlQ1NTVhc0w2UTFNeWltaWhp?= =?utf-8?B?ajhNeFNRb2l4Vm9PK0Mxbi9Kd3phK3pDc3BRQnNWZC90R1dtY2E5eG13NVdO?= =?utf-8?B?bGxSQzFadDZoY01XdEJuVlNXOWJWYjA1M1pOWS8zT3lwMTBiQnpJZWttcExO?= =?utf-8?B?SC8wOEtJc0xJMkRNQ3E1MWYyclBtdUR6RDdVYWEwclhiNGlhTXF6TjdiVXlE?= =?utf-8?B?NFZmLzlMWUp4ZWVGWXhYZFRkOXJuNlpDVUhTZEhCMFZZaGZucnJQbDdna0o3?= =?utf-8?B?OG5Jdis4TmZrQjdKT2x6c1VNdjVpNDJBTjlCVi9NekJZWmFLZUZ0aEg3Kzd6?= =?utf-8?B?dW5zcEd2MWEzNmlVL0E4NjNOcTh0SmVJUm5pdlRCZDY1dkxXMWc4WWZhZHNn?= =?utf-8?B?aHEwWEJwZXcwdXdZUHlwd2FXbjFpWWxaSnpmNGgzSTNDaTJtVEMwVWhKYThq?= =?utf-8?B?SjVuZ1FDQ29xNGFweDliZHhXa3lOdm5kT1hIMWJBUFJZRXJBQ3dnYlpPT2ox?= =?utf-8?B?bC9uOHg3RU8rOERCUStURStHbFNwdk9DSGQxMEJTUGxQeDlkM1VkdVVFRzBt?= =?utf-8?B?Z3ZnNmgyUVR3d3NaL3p5SzY4ZzU4RGhuYTNyenUrSGxySXlaKzVNcmorK05G?= =?utf-8?B?bG9ybG1tWkVwVHNrdUN1dDJuRVNhTzVrOTQ4dHMrVnhKRlE1SWI0WFF1a2l1?= =?utf-8?B?bFBxTjRQeFlyUGo5WmVHc29zOVllVDNZaEdPd2ZjYlBIUjJuQW1ScHJHMmtu?= =?utf-8?B?RlBkY3Q4QThaSlVHYTNXcHppcE1yUVRNeFFzL2xSSEFGN3BqejY3SlpFdlow?= =?utf-8?B?cXozWGNlTmxKOTBLZEhmMUttRUtUY2F2UzA1azRhTTFoM2lVSSt6SDN3dHNn?= =?utf-8?B?Rm9WY0UrWnZzM041UUExT0hoZEVuNlk0bnRuUEJCMTljV0JDU0xSUURVSG5v?= =?utf-8?B?UWxETHJMbS9hVHZqR1cwK3pnVUdqajIyeDNHcEZFSWVFTC9IR0xKUDdHaHlF?= =?utf-8?B?V0FqVWE0UWZxYmFTcFZjZlFsaFhkUHc0R0lUM0IybVJPdGZSbGJIMGVYVnB3?= =?utf-8?B?OGo3Y3RMclZnUnNkdmNidllBR1VLZUxKMWM3S3U1NDRJalNwbkhQNktFUEl5?= =?utf-8?B?eFNWSUpHZHVUYVZCVGNQa3c5aUlLSzRyNmxUSnFBMjBPZ250WWRpUlZYbnUz?= =?utf-8?B?QUwzZXVMTTZMbzFUNHRIc1dEYVJyNjJiSXp3L3doVUhldW51TXVnSGU3TTZ3?= =?utf-8?B?QlBJUXY5WTRGdUxLRWNtUVJGQlRjcUJpOGN5WU1VcFkwOXBBYXVMVmsxbW1w?= =?utf-8?B?Yk52K0h4YVZCTXA0RE1iUnZiYzAxd25KUkhzN0JNaVp0KzFsWS9kT0lDbEFt?= =?utf-8?B?WFl6aVJ5Q3ppMVFOaDNxVTNBK1pxVTdJUzYvMXQ3d2pIeTZyaXo0TXhYOEc5?= =?utf-8?B?ZlRLVzJLcmsxTkhaOEZtbC9lOVdMZk9NWGlpZ3RyUUFaUW9adCs1UXlFcklS?= =?utf-8?B?TkZzdmpzei9HQUlRaHUrUTFRMkswdFo3ZDRPR0ZTOG02clhSK2pMVzh1cVJV?= =?utf-8?B?Y2xjOEtEdzk2K0Z0QUJsWFREc2ZVTlFFWk45enMrb2JIaWR2VUVhSkNIUEJZ?= =?utf-8?B?cm15SE4wZkdkT0pwam9ndjgrblFESWlHQXJrMXVTWnM1NDk5OFNSYlhHYWgv?= =?utf-8?B?RGY2eFpjUGZSOUExREtGUFppa09OcEdWL3BESVRZZlIvNncrOVFhMUIyVkRo?= =?utf-8?B?WEl5MXBNMEV2K2JvbzdrRHR6dUZsVy9vVU9zdHpHVng3QW0yUlpmdzgyQmVx?= =?utf-8?B?YkV0MUdmSk1BZWo0ZmRURUlGWTdxVC9TRXBnWEtkNEJRY2xqczc3SElmVkhq?= =?utf-8?B?ckFOUWpHTFd6aGhZZE1zMFNDU05FRzk2NGs2VUNJQ3o4WXV2MXJ5ZFRES1Rz?= =?utf-8?B?Uy9VZ1Bndi81ZG1obHp1QlhVNk52R2dyUkN3NGozbmR1V21MblpoY2h0anZI?= =?utf-8?B?WTVyNEZlMjd3OGkzeWI1Y1hpbWxNaGlXVTNsVDVsY0R3OTd6SXdWeXJSVHVu?= =?utf-8?B?NnNISWxmRUFxSmdyeE40aGk4Q1VaYVExN1VpNU90OEZpcHNKT3hIQ2VkczY4?= =?utf-8?B?QVZ2R2ZWRFV2OU9tRFBFNTJKZ1RQZDYwTlNIKzZRbmVWeS9FT25GY3RFL2x0?= =?utf-8?B?WHN0cWdSZVRzUGxhNyt2ZElGY1J0ZllCd3lkMVJUOXNIb2lPc3A5VnVXQVZw?= =?utf-8?Q?v4R8qQPUKbgV+zukSXqKkgDNNUGXQrsY5CHU6TOdGM8SR?= X-MS-Exchange-AntiSpam-MessageData-1: kC/0knvNjl6rTQ== X-OriginatorOrg: Nvidia.com X-MS-Exchange-CrossTenant-Network-Message-Id: 9e880ddb-fb48-4b04-0968-08df1d3c8c58 X-MS-Exchange-CrossTenant-AuthSource: DS0PR12MB6413.namprd12.prod.outlook.com X-MS-Exchange-CrossTenant-AuthAs: Internal X-MS-Exchange-CrossTenant-OriginalArrivalTime: 28 Sep 2026 08:43:20.2114 (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: 76c48xtcmeuRJx+vg3BnYDF0+WYEcwV7m+DviOd9j3bnWaEYAtqhueHGEGj04gHrUZCxtyDIDhHZgOPhR6elMg== X-MS-Exchange-Transport-CrossTenantHeadersStamped: CY8PR12MB8065 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 | 56 ++++++- drivers/gpu/nova-core/gsp/nvkv/encode.rs | 241 +++++++++++++++++++++++++++= ++++ 2 files changed, 296 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/nova-core/gsp/nvkv.rs b/drivers/gpu/nova-core/gsp/= nvkv.rs index 10f7a16ffc23..7ac3a459a98b 100644 --- a/drivers/gpu/nova-core/gsp/nvkv.rs +++ b/drivers/gpu/nova-core/gsp/nvkv.rs @@ -10,8 +10,15 @@ //! 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, + ops::{ + Deref, + DerefMut, // + }, // +}; =20 use kernel::{ alloc::{ @@ -94,6 +101,53 @@ 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(T, PhantomData= ); + +impl From for Key { + #[inline] + 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> { + #[inline] + fn from(value: &'a [T; N]) -> Self { + Self(&value[..], PhantomData) + } +} + +impl Deref for Key { + type Target =3D T; + + #[inline] + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl DerefMut for Key { + #[inline] + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +impl Default for Key { + #[inline] + 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 8b66ddb15189..2cc0cfbad814 100644 --- a/drivers/gpu/nova-core/gsp/nvkv/encode.rs +++ b/drivers/gpu/nova-core/gsp/nvkv/encode.rs @@ -8,11 +8,152 @@ 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] + fn encode(&self, encoder: &mut $crate::gsp::nvkv::Encoder) -> = ::kernel::error::Result { + $( $crate::gsp::nvkv::Encodable::encode(&self.$field, enco= der)?; )* + Ok(()) + } + } + }; +} + +/// 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. + fn new(index: Index, value: T) -> Self { + Self { index, value } + } +} + +impl Encodable for IndexedKey { + #[inline] + fn encode(&self, encoder: &mut Encoder) -> Result { + encoder.encode_u32(KEY_ID, self.index, self.value) + } +} + +impl Encodable for IndexedKey { + #[inline] + fn encode(&self, encoder: &mut Encoder) -> Result { + encoder.encode_u64(KEY_ID, self.index, self.value) + } +} + +impl Encodable for IndexedKey<&[u8], KEY_ID> { + #[inline] + fn encode(&self, encoder: &mut Encoder) -> Result { + encoder.encode_array8(KEY_ID, self.index, self.value) + } +} + +impl Encodable for IndexedKey<&[u32], KEY_ID> { + #[inline] + fn encode(&self, encoder: &mut Encoder) -> Result { + encoder.encode_array32(KEY_ID, self.index, self.value) + } +} + +impl Encodable for IndexedKey<&[u64], KEY_ID> { + #[inline] + 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] + 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] + 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] + 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] + fn encode(&self, encoder: &mut Encoder) -> Result { + IndexedKey::new(Index::new::<0>(), As::from(self.0)).encode(encode= r) + } +} + +impl Encodable for Option { + #[inline] + 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 +348,104 @@ 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; + const INDEXED_KEY: KeyId =3D 0x0006; + + nvkv_encode! { + struct TypedRequest { + a: Key, + b: Key, + name: Key<&'static [u8], NAME_KEY>, + fixed: Key<[u8; 4], FIXED_KEY>, + opt: Option>, + indexed: IndexedKey, + } + } + + const U32_VALUE: u32 =3D 0x89ab_cdef; + const U64_VALUE: u64 =3D 0x0123_4567_89ab_cdef; + const INDEXED_VALUE: u32 =3D 0x1234_5678; + + let index5 =3D Index::new::<5>(); + let request =3D TypedRequest { + a: U32_VALUE.into(), + b: U64_VALUE.into(), + name: b"name\0".into(), + fixed: [1u8, 2, 3, 4].into(), + opt: None, + indexed: IndexedKey::new(index5, INDEXED_VALUE), + }; + + let mut encoder =3D Encoder::new(); + request.encode(&mut encoder)?; + let encoded =3D encoder.finish(); + + // `opt` is `None` and is not encoded. + assert_eq!(encoded.len(), 8); + + let index0 =3D Index::new::<0>(); + // `a`: IMM32 with the value in the `Op`. + assert_eq!( + encoded[0], + Op::zeroed() + .with_key(U32_KEY) + .with_index(index0) + .with_opcode(Opcode::Imm32) + .with_value(U32_VALUE) + .into_raw() + ); + // `b`: a single-key SEQ64 followed by the value. + assert_eq!( + encoded[1], + Op::zeroed() + .with_key(U64_KEY) + .with_index(index0) + .with_opcode(Opcode::Seq64) + .with_value(1u32) + .into_raw() + ); + assert_eq!(encoded[2], U64_VALUE); + // `name`: ARRAY8 of 5 bytes, zero-padded to one `u64`. + assert_eq!( + encoded[3], + Op::zeroed() + .with_key(NAME_KEY) + .with_index(index0) + .with_opcode(Opcode::Array8) + .with_value(5u32) + .into_raw() + ); + assert_eq!(encoded[4], u64::from_le_bytes(*b"name\0\0\0\0")); + // `fixed`: ARRAY8 of 4 bytes, zero-padded to one `u64`. + assert_eq!( + encoded[5], + Op::zeroed() + .with_key(FIXED_KEY) + .with_index(index0) + .with_opcode(Opcode::Array8) + .with_value(4u32) + .into_raw() + ); + assert_eq!(encoded[6], u64::from_le_bytes([1, 2, 3, 4, 0, 0, 0, 0]= )); + // `indexed`: IMM32 with a non-zero index in the `Op`. + assert_eq!( + encoded[7], + Op::zeroed() + .with_key(INDEXED_KEY) + .with_index(index5) + .with_opcode(Opcode::Imm32) + .with_value(INDEXED_VALUE) + .into_raw() + ); + + Ok(()) + } } --=20 2.55.0 From nobody Mon Sep 28 21:03:44 2026 Received: from BL0PR03CU003.outbound.protection.outlook.com (mail-eastusazon11012064.outbound.protection.outlook.com [52.101.53.64]) (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 C9BCF483BC6; Mon, 28 Sep 2026 08:43:33 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=fail smtp.client-ip=52.101.53.64 ARC-Seal: i=2; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1790585017; cv=fail; b=m/uOdoT8Gl4d3xEoJqluFgm0exWBzLobIhCM+xcPtta6bUhK98afUTIJ/4eI61O5hIIebeRjNA0/jXLNxLSgQmaO6zYFDSkQGczgUnZp9NuRUpRyKLEdFyfSfxWBLdCdir1h5Bd23HAV2lcJoFI/yCXVIQHM0wJMK8go6BqpL7o= ARC-Message-Signature: i=2; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1790585017; c=relaxed/simple; bh=3ju8bx/d1l9iH71s5Mw9BOd/wsz9KJjHsQvh9KtLa9c=; h=From:Date:Subject:Content-Type:Message-Id:References:In-Reply-To: To:Cc:MIME-Version; b=PJ3WvVrIw5j2eWPTWc7h0i1ESPeoAQdooTRrG8PIykouqbHIya0sCS6cfSImuIumvoQTB6VGEOZnRsWxOcP3mz5x0fO5uchuLCtrlhG9RpjRfYOZ5y+eTosP3ejz/CwgrO8m69sg2AL1G1Z49klB3LtV25WJ1kdGJgaaBy29B84= 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=YgXSH4t+; arc=fail smtp.client-ip=52.101.53.64 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="YgXSH4t+" ARC-Seal: i=1; a=rsa-sha256; s=arcselector10001; d=microsoft.com; cv=none; b=hzbuxHd3l9y6uK/zoG96Hp2/qnRMVoZJNFS5HsJDCGH6phbggo0ThjULwt6verZ7/dJShDVtej3A+cMaMHpoIyV8lJRynpU0odOTJlZvoPctM7LS/ApyxOm9pTj/EX1AlMh0UqQ+6KIZ3O8ZjfKS79enXddkgCGn8O/8g5eltU0gtVR//CBHiBBvQCo84ZqjaXjOoU+YEy+PEWt+cJsz8MUo1kvKfJT9H+tvOtquPbT+sWtwMYutkgKsw+KUR0BycssbR0MPG4osQseIqcY774ivodkUIggXecZYycLs3nT96PdzfWECcGfY+O2fhxNLNUSa0FrwdRJDgCmj+hvG3A== 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=kPvWgihL7fEhHo5k3POKQuKPjUBzSdgtOP/AJiVJhTk=; b=vF9cOYBhJMoY70iGWH1ujZXqZYXQwkcahKJMh9e7rY6eWQB0Di6pQ1V2kBSk9MM42If2Iz+d6fAxrBlVl3Rv4XiBpcu9RErHIQQR8fpu0WovL6awkfAP7CsIxionTPzje3hTCqI8wzPugfNgyX0mo824wex7fR/vcExu9D+wyZcVDMWJIxPLq6gSvVPt04S1eyfLqqcQTxoTAXW3Ip8P9m4VXIBN9+YoH+eHDM6O//AHNRDGb3PbZ3z8I7fo1LGR+DMwhQBJ8JaSf3zR6794eELb4EAexn9eu+Rp9W9XFe47B/l0M830cQaLDF9/EauYvMvvqhftF1fofuMHqdbjLg== 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=kPvWgihL7fEhHo5k3POKQuKPjUBzSdgtOP/AJiVJhTk=; b=YgXSH4t+8Oca/byemLyP2fIDC5dB34WbWFcJGQLZGjG8YbgrvEFkP3yW9L1QfjjBDtG4GV+A36CzkWKGoITEX9Usl4EWC5Q5OmJWH/Gw5KEEj0Ed03B2VQFSIJpFvvJSkigJr0lm1XWATmExW0Vk9/rJImQj3/7FMJpHFHQxJrWwSEcumAh8jnB8ZlEdopjSGULDWtSGHECelYEKlqXLUfEPHXHbflsTgQXnG4ALtRvTOke/9LeZnAZM+oLm6g+UPaffVMLhV78oldDzAu3+he4w0OnJp8rxXUfnvsBalRtpLMlISQipAIhKtEStQrj6HQpApfOnWvIVvzqjwaLoEQ== Authentication-Results: mx.microsoft.com 1; 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 CY8PR12MB8065.namprd12.prod.outlook.com (2603:10b6:930:73::16) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.21.451.24; Mon, 28 Sep 2026 08:43:23 +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.0451.022; Mon, 28 Sep 2026 08:43:23 +0000 From: Eliot Courtney Date: Mon, 28 Sep 2026 17:42:46 +0900 Subject: [PATCH v3 7/8] gpu: nova-core: add NVKV typed decoding Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260928-b4-nvkv-v3-7-f04504c262c2@nvidia.com> References: <20260928-b4-nvkv-v3-0-f04504c262c2@nvidia.com> In-Reply-To: <20260928-b4-nvkv-v3-0-f04504c262c2@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: TYCP286CA0364.JPNP286.PROD.OUTLOOK.COM (2603:1096:405:79::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_|CY8PR12MB8065:EE_ X-MS-Office365-Filtering-Correlation-Id: f56bfd0a-5db4-4978-9c50-08df1d3c8e60 X-MS-Exchange-SenderADCheck: 1 X-MS-Exchange-AntiSpam-Relay: 0 X-Microsoft-Antispam: BCL:0;ARA:13230040|10070799003|23010399003|7416014|376014|1800799024|366016|10067099003|56012099006|5023799004|11063799006|921020|3023799007|10063799003|18002099003|22082099003; X-Microsoft-Antispam-Message-Info: sNYnZBWB3NU6vck3YukWzPErPrddlSYjzWusn1pq5JO+ZZUyr0tSNK52I9b8xVjVxK+T9sLpZBe2FykxrL3IdqhBIlMaVi8EsWWVFRjyvGEF6vjfazi6zQR466QRyCGI4PtCVrjH9ySearCcYUKdgaF2J6LC/dwWRlmIlkI/751+fmihciLHxU75RiFKRnPTkcmwXRurTp+5ZNVD8Em7kx3kpv4y49pUat6rXQl5NmboWHtzWOqp7JQa+isOZdcOnFeQ+0pr4PIRh/cuE3kTyWMC2eahili/t8QbwuV9S59Y93RFAWXT3qjFJ+jyLSXwifd+zTWnCZIs751wZnS9cF4nyt7SYo/fdr/oCIbHw026dDjUTl9fLKtQLMg+279Go/mAeN1Kqqj6N0mQJRA2BWv/nK6G0kddusvsMTdSaT3P3gTUo++6o2iji98CDNYV8PV9ZXq92LPQjLPVyD6NqVMcW5ktS7HmyTsXCZXHps/AgjQ9YZqfao7qrZYZy2UrqTURw3Cy+B6DTMCg3PTWZhA7RLpShhVYwLjyhK6ZF9t28YIEzdeAJajk4nb4NNHhZ14jXV5eQByKYDJSHZy2SD7Nas6OZpiNT8FXhwTsbmfaMZa+8N+9zVFZNprlKjW034bDzy5PKp+6bZhGcTzi2IkDtLYcXp36QHq6j7iwJcWGRrZDfug9sZW0hoaXjT78Sa9nIlRGT9acMkWryMn3kQ== 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)(10070799003)(23010399003)(7416014)(376014)(1800799024)(366016)(10067099003)(56012099006)(5023799004)(11063799006)(921020)(3023799007)(10063799003)(18002099003)(22082099003);DIR:OUT;SFP:1101; X-MS-Exchange-AntiSpam-MessageData-ChunkCount: 2 X-MS-Exchange-AntiSpam-MessageData-0: =?utf-8?B?cDZET0tPZGJnbkMyaEoyazVnUXJ0RkJmOFg0RElkM1JJaGhSN2d1WEV4QlBt?= =?utf-8?B?VE5xa1F4TGZxOWNnMmNTYkJ0ckJONFpDaWpFaVY2c2VuYWFPNFJha3ZHRG51?= =?utf-8?B?QjlOcG01Y2VjQ3o1MEpGRjBtaTY5Q1YrL2NNbjBTdSs4MUtsSW1rcEY1WGJV?= =?utf-8?B?WEludWFicTNzVDdkMXg0WHJyZ1Y2REoyWVE5d25ucnc1TGVreEFLUTBrS3Rj?= =?utf-8?B?bHV0NHo0M3h2KzNVeitPVm8yUXMxWCtMZXU2QUJIdTVyc3ZydksxSWpRWVda?= =?utf-8?B?L01qNm5CeW0rQlBuK3U2K2VXcW4zQWZyREhqZHIxUTI1dFVRVWRLNzNjM282?= =?utf-8?B?bDdjUThRalpJODltRmcyeCtEaTdMbjlabWlVVHpVbmJ2ek1OL1NFWU8rWFhS?= =?utf-8?B?QUpYa3VxV2pkeHJ6RmRYOFlEdEtyUE5EVGgzK2ZQN1F1b1liYUx5YVN6cGk2?= =?utf-8?B?Q2hDdUMvY1VFeDBBZzZodEdHT3hFNk1FNGROcUtQRVhTeThUb29iMjZZV2du?= =?utf-8?B?cHovbWlwbGlZY3hNTmNTRVAxVlQwTDhqRjNua252REJkS2tobFdtWm9TYkpr?= =?utf-8?B?cmlUYytjWHRyQ09rMUN5dk1LcVNwclpXL256OEpkK3E2L2RtU09LTk9FNW0v?= =?utf-8?B?bTNEbHgvTDc0djBtaGwzVlFPOXpOZ05KMXNERFBBYWIvcUFpTDJZd1QxTlpq?= =?utf-8?B?WEV6eWFiVmJvRi96VVIzTU0yQWlzYWJqVzR0YXc2MjV5VkVLL21oWmpOSnlG?= =?utf-8?B?aDVHVEhoTzVseFpJU0hEZjhlenBwa3RiR3lpdmUwM3FEc1FEdGp6cWFRQnJZ?= =?utf-8?B?OWN0emJoMzArOVJSQXptaElIOVJJRzNzREZsTTBieUZiQngyeWJ1ekFhMmgy?= =?utf-8?B?RmJtbUJYNGlsSUZDSnJJOVdpa1BrWkNTMFA4eW0vemZITGlCMlB4VEUzTFlK?= =?utf-8?B?SmpwZGNsRUlBRTlFeDV3S3pPTWFFSlV6MHQ5bWtvMUVOU3QrM1FBcVRiZ3R4?= =?utf-8?B?VFNsYXZDdTJCeXVJSmIvUVV4N3NUbE41T0c2czlQalQrVVlUVVltMmp3YW5X?= =?utf-8?B?VDRvdWxTRHM1V0ljaGtKdi93OU55b2NVWHM3NEVKczBoaGlVRGI5TVJpNmpE?= =?utf-8?B?SmJRQzNtMmQ4RzFJaDJ4SFU4WUhDNjh4aXA0UUYrMllldkkrRmRxeWt5aVRV?= =?utf-8?B?eldBRkY2Uy9sUDRCVmdGei9KM29OMXJMenF0b2RlNTRQRVhUVWp5cFZQZnBP?= =?utf-8?B?NDNrUUVzdnl0cEF2cG5qV1c4YnJ6UU9sNnhUK1ZJSi9hOGtWU0p0L0dDU2Fk?= =?utf-8?B?NTlscERMSUxFbU9SN2lEVU5LcXdDWFlXL1NIRG5OSUZzcm5QTmZDT2g3T0ps?= =?utf-8?B?ZUxuY0lvWmYxMDU1b0FjbWJyZmkzd3VZTXlOc1ZqcVBCc2ZyK3BKSHFUakFa?= =?utf-8?B?bW1CcGJiY0FvNnhBQ2FraGZ3RGo4aGw1Z05xRXh1NWdUdlJ0bjdpMm9XN1Vq?= =?utf-8?B?dzFyeDJEaGxDU2Vob0k2U2l3WnU5M1RvR1VIc1dOcmFkaHZMaWZKaW9XeUN5?= =?utf-8?B?OGJDbjJNbllQRXM0ejNQWDNtTlhUZ09MU3E0S2ZhekJqQzFYbUI2QWc5SnVx?= =?utf-8?B?eGE0VzRjK1dxMXpEbFpHb1psb2d2K3gvK2NsaFN1Q0RqdUJBK0Y4bkQyWDk1?= =?utf-8?B?b2NxOHdzYXVwMklWdVJzS0JHMlpkNE5ocU9EVlRWSHdqNmdoYVdpMWhURDRT?= =?utf-8?B?bmVwWHp5YW5INmFNQVpmcGxEUEpucEp5TGdUWG1HUDZLYU94ck9hNTdFQU5P?= =?utf-8?B?V2M4VURnUFpHMWxjWmFzR3EwdmU4eGQrZVdiU1BuRVRrdHNVNlRqZjZvNFFN?= =?utf-8?B?U3JERnRFbkNPN3dINVo2L1U5YUFXSFRrck13RTllaDFERXhKTWlrcEFSdGRn?= =?utf-8?B?VGU2Nkt4Mng0b3lzQnZPUzMreFRGZTlJYWVFbmxpNUFKMWNBckVOWkFhME9B?= =?utf-8?B?aHBNU2VqNWNFYUNtVW0xT2I1bVhkTEl0NXlMb0RrcS9tbjcyL2lWYVhCdk1l?= =?utf-8?B?c01LNjhoNHB2eVlRYzJJa2FyWTdmeVZrY2ZBUTB2L0RGUUlTcEJ2ZDFKQXFu?= =?utf-8?B?eHBielVPc01rUzU5WFZoZzlkVytabzIvekpsNkNKZ3RnUmF3aUtUWG56R0ZT?= =?utf-8?B?MksyakNvTm9qc2RrZDlnSUhQWWNRaGx5OWFEVFd2aURWbmdtZjdrMldIRER2?= =?utf-8?B?NTBIVWdRMHFsZVVHc0JQeDhRSnlUb0w2V1laNGNEU3c5RTgvcjZqc2hxK0ZM?= =?utf-8?B?YTYrSXN3TXpURW81dEpLNUZmVE1paU5qRkc2U2YzRWFWQ2pxaWFWY3kza2R0?= =?utf-8?Q?FCsgW1n0DPWymJAb4LsUdY8yKYOWXRKb21+U36J+IsA1O?= X-MS-Exchange-AntiSpam-MessageData-1: 1MUAkrmJikU2WA== X-OriginatorOrg: Nvidia.com X-MS-Exchange-CrossTenant-Network-Message-Id: f56bfd0a-5db4-4978-9c50-08df1d3c8e60 X-MS-Exchange-CrossTenant-AuthSource: DS0PR12MB6413.namprd12.prod.outlook.com X-MS-Exchange-CrossTenant-AuthAs: Internal X-MS-Exchange-CrossTenant-OriginalArrivalTime: 28 Sep 2026 08:43:23.6202 (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: Q/uWwwNABmqQjyv/kfMHxUIIZ4mEoVwK1zmD9B2fqXUOo61OoqRYpmkR2V+x6fwtriu88snhJZvUjsIbq1VoSA== X-MS-Exchange-Transport-CrossTenantHeadersStamped: CY8PR12MB8065 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 | 11 +- drivers/gpu/nova-core/gsp/nvkv/decode.rs | 622 +++++++++++++++++++++++++++= +++- 2 files changed, 628 insertions(+), 5 deletions(-) diff --git a/drivers/gpu/nova-core/gsp/nvkv.rs b/drivers/gpu/nova-core/gsp/= nvkv.rs index 7ac3a459a98b..5791df07a7fa 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::{ @@ -23,7 +23,8 @@ use kernel::{ alloc::{ allocator::KVmalloc, - Allocator, // + Allocator, + ArrayVec, // }, bitfield, num::Bounded, @@ -148,6 +149,12 @@ fn default() -> Self { } } =20 +/// A schema field for an array value under the NVKV key `KEY_ID`. +#[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 c4c24fe1108e..24dad31296cb 100644 --- a/drivers/gpu/nova-core/gsp/nvkv/decode.rs +++ b/drivers/gpu/nova-core/gsp/nvkv/decode.rs @@ -3,11 +3,22 @@ =20 #![cfg_attr(not(CONFIG_KUNIT), expect(dead_code))] =20 -use kernel::prelude::*; +use core::{ + convert::Infallible, + 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, // @@ -15,6 +26,353 @@ num, // }; =20 +/// Defines a schema struct together with its [`Schema`] and [`Visit`] imp= lementations that decode +/// into `$target`. +/// +/// Each member of the struct should implement [`Schema`] and [`Visit`]. F= or every (key, index, +/// value) triple decoded from the NVKV stream, the generated parent `Visi= t` implementation will +/// call each member in declaration order with that triple. If a member co= nsumes that triple, it +/// will stop there. Otherwise it will keep going until all members are tr= ied. +/// +/// 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. +/// +/// Use a lifetime on the schema struct if you want to borrow data directl= y from the encoded stream, +/// for example, `Key<&'d [u8], KEY>`. +/// +/// # Examples +/// +/// ``` +/// nvkv_decode! { +/// struct RequestSchema =3D> Request { +/// id: Required, +/// name: Array, +/// } +/// } +/// +/// nvkv_decode! { +/// struct NameSchema<'d> =3D> Name<'d> { +/// name: Key<&'d [u8], 0x0002>, +/// } +/// } +/// ``` +macro_rules! nvkv_decode { + // A schema which doesn't borrow from the stream. The macro uses a gen= eric lifetime for the + // [`Visit`] implementation but not for the struct itself. This allows= omitting the unused + // lifetime on the struct. + ( + $(#[$attr:meta])* + $vis:vis struct $name:ident =3D> $target:ty { $($fields:tt)* } + ) =3D> { + nvkv_decode!( + @impl ['data] [] $(#[$attr])* $vis struct $name =3D> $target {= $($fields)* } + ); + }; + // A schema which borrows from the stream for lifetime `$datalt`. The = macro connects the + // [`Visit`] lifetime with the struct lifetime. + ( + $(#[$attr:meta])* + $vis:vis struct $name:ident<$datalt:lifetime> =3D> $target:ty { $(= $fields:tt)* } + ) =3D> { + nvkv_decode!( + @impl [$datalt] [<$datalt>] $(#[$attr])* $vis struct $name =3D= > $target { $($fields)* } + ); + }; + (@impl [$datalt:lifetime] [$($generics:tt)*] + $(#[$attr:meta])* + $vis:vis struct $name:ident =3D> $target:ty { + $( + $(#[$field_attr:meta])* + $field_vis:vis $field:ident : $ty:ty + ),* $(,)? + } + ) =3D> { + $(#[$attr])* + $vis struct $name $($generics)* { + $( + $(#[$field_attr])* + $field_vis $field: $ty, + )* + } + + impl $($generics)* $crate::gsp::nvkv::Schema for $name $($generics= )* { + type Target =3D $target; + + #[inline] + fn init() -> impl ::kernel::prelude::Init { + ::pin_init::init!(Self { + $( $field <- <$ty as $crate::gsp::nvkv::Schema>::init(= ), )* + }) + } + + #[inline] + 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<$datalt> $crate::gsp::nvkv::Visit<$datalt> for $name $($gener= ics)* { + fn visit( + &mut self, + key: $crate::gsp::nvkv::KeyId, + index: $crate::gsp::nvkv::Index, + value: $crate::gsp::nvkv::DecoderValue<$datalt>, + ) -> ::kernel::error::Result { + // TODO: This performs worst-case O(#fields) visit calls. = Consider optimising this + // if it becomes a problem. + Ok(false + $( || $crate::gsp::nvkv::Visit::visit(&mut self.$field= , key, index, value)? )*) + } + } + }; +} + +impl Schema for Key { + type Target =3D T; + + #[inline] + fn init() -> impl Init { + Self::default() + } + + #[inline] + fn finish(&mut self) -> impl Init + '_ { + Ok(core::mem::take(&mut self.0)) + } +} + +impl<'data, T: TryFrom, Error =3D Error>, const KEY_ID= : KeyId> Visit<'data> + for Key +{ + #[inline] + fn visit(&mut self, key: KeyId, index: Index, value: DecoderValue<'dat= 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) + } + } +} + +impl Schema for Ar= ray { + type Target =3D ArrayVec; + + #[inline] + fn init() -> impl Init { + init!(Self { + vec <- ArrayVec::init_with::(|_| Ok(())), + }) + } + + #[inline] + fn finish(&mut self) -> impl Init + '_ { + ArrayVec::init_with(move |dst| { + dst.extend_from_slice(&self.vec)?; + self.vec.clear(); + Ok(()) + }) + } +} + +impl<'data, T: Default + Copy + 'data, const N: usize, const KEY_ID: KeyId= > Visit<'data> + for Array +where + &'data [T]: TryFrom, Error =3D Error>, +{ + fn visit(&mut self, key: KeyId, index: Index, value: DecoderValue<'dat= 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) + } +} + +/// 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 Schema for Required { + type Target =3D T; + + #[inline] + fn init() -> impl Init { + Self(None.into()) + } + + #[inline] + fn finish(&mut self) -> impl Init + '_ { + (self.0).0.take().ok_or(EINVAL) + } +} + +impl<'data, T: TryFrom, Error =3D Error>, const KEY_ID= : KeyId> Visit<'data> + for Required +{ + #[inline] + fn visit(&mut self, key: KeyId, index: Index, value: DecoderValue<'dat= a>) -> Result { + self.0.visit(key, index, value) + } +} + +/// Expects objects specified sequentially with index starting from zero. +/// +/// This stores two `Schema`s which are initially empty. A `Schema` is exp= ected to be complete when +/// we get the next index. +pub(crate) struct Accumulated { + // Tracks the current index (starting from 0). When this advances, the= schema `current` must be + // finished. + current_index: Index, + + // The current `Schema` being constructed now. + current: S, + + // Tracks empty schemas (one that has never consumed a key via `visit`= ), so we don't call + // `finish` on them. + current_started: bool, + + // The next `Schema` to be constructed. This is required because we mu= st consume a key intended + // for this Schema from the next index to know we can try calling `fin= ish` on `current`. + next: S, + + // The set of results generated by the completed schemas so far. + accumulated: KVVec, +} + +impl Schema for Accumulated { + type Target =3D KVVec; + + #[inline] + fn init() -> impl Init { + init!(Self { + current_index: Index::new::<0>(), + current <- S::init(), + current_started: false, + next <- S::init(), + accumulated: KVVec::new(), + }) + } + + #[inline] + fn finish(&mut self) -> impl Init + '_ { + 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<'data, S: Schema + Visit<'data>> Visit<'data> for Accumulated { + fn visit(&mut self, key: KeyId, index: Index, value: DecoderValue<'dat= 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) + } + } +} + +/// A schema field that scatters indexed values into an array of `N` slots. +/// +/// Values are decoded from the encoded stream using the type `As`, but th= en converted into `T` with +/// [`From`], like the `As` parameter of [`Key`]. +#[repr(transparent)] +pub(crate) struct Indexed { + slots: [T; N], + _as: 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 Index= ed { + type Target =3D [T; N]; + + #[inline] + fn init() -> impl Init { + init!(Self { + slots <- init_array_from_fn(|_| T::default()), + _as: PhantomData, + }) + } + + #[inline] + fn finish(&mut self) -> impl Init + '_ { + init_array_from_fn(|i| Ok::<_, Error>(core::mem::take(&mut self.sl= ots[i]))) + } +} + +impl<'data, T, const N: usize, const KEY_ID: KeyId, As> Visit<'data> for I= ndexed +where + T: From, + As: Copy + TryFrom, Error =3D Error> + 'data, + &'data [As]: TryFrom, Error =3D Error>, +{ + fn visit(&mut self, key: KeyId, index: Index, value: DecoderValue<'dat= 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.slots, start, elems)?, + Err(_) =3D> scatter_window(&mut self.slots, start, &[As::try_f= rom(value)?])?, + } + Ok(true) + } +} + /// A decoded NVKV value. #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub(crate) enum DecoderValue<'a> { @@ -51,7 +409,16 @@ fn try_from(value: DecoderValue<'a>) -> Result { impl_try_from_decoder_value!(&'a [u32], Array32); impl_try_from_decoder_value!(&'a [u64], Array64); =20 -/// A visitor that consumes decoded NVKV and produces a `Target`. +/// Lets `Key, KEY_ID>` accept whatever `Key` accepts. +impl<'a, T: TryFrom, Error =3D Error>> TryFrom> for Option { + type Error =3D Error; + + fn try_from(value: DecoderValue<'a>) -> Result { + T::try_from(value).map(Some) + } +} + +/// The state of one NVKV decode operation which produces a target value `= Target`. pub(crate) trait Schema { type Target; =20 @@ -65,7 +432,12 @@ fn init() -> impl Init =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 must = be empty again. For + /// example, this is required by [`Accumulated`] which finishes one ob= ject and then decodes the + /// next one with the same schema. Implementations generated by `nvkv_= decode!` meet this + /// requirement. If the initializer fails, the `Schema` can be in a va= lid but non-fresh state. + /// Taking `self` instead of `&mut self` would avoid this contract, bu= t it forces a copy of the + /// schema onto the stack. fn finish(&mut self) -> impl Init + '_; } =20 @@ -295,6 +667,133 @@ fn visit(&mut self, key: KeyId, index: Index, value: = DecoderValue<'d>) -> Result Ok(()) } =20 + // 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 index2 =3D Index::new::<2>(); + 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)?; + encoder.encode_array32(SLOT_KEY, index2, &[30, 40])?; + let serialized =3D encoder.finish(); + + let decoder =3D Decoder::new(&serialized, UnknownKeyPolicy::Error); + let mut schema =3D KBox::init(TestSchema::init(), GFP_KERNEL)?; + let decoded =3D KBox::try_init(decoder.decode(&mut *schema)?, GFP_= KERNEL)?; + + 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, 30, 40]); + + 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(()) + } + /// Records each visit as (key, index, value), for tests on hand-built= streams. #[derive(Default)] struct Recorder<'d> { @@ -484,4 +983,121 @@ fn decode_raw_words_malformed() -> Result { =20 Ok(()) } + + // Tests the error paths of the schema kinds. + #[test] + fn decode_typed_struct_errors() -> Result { + const VALUE_KEY: KeyId =3D 0x2200; + const SLOT_KEY: KeyId =3D 0x2201; + const BLOB_KEY: KeyId =3D 0x2202; + const X_KEY: KeyId =3D 0x2203; + const Y_KEY: KeyId =3D 0x2204; + + let index0 =3D Index::new::<0>(); + let index1 =3D Index::new::<1>(); + let index2 =3D Index::new::<2>(); + + nvkv_decode! { + struct ValueSchema =3D> Value { + value: Key, + } + } + + struct Value { + value: u32, + } + + // A single value at a non-zero index. + let mut encoder =3D Encoder::new(); + encoder.encode_u32(VALUE_KEY, index1, 1)?; + let serialized =3D encoder.finish(); + let decoder =3D Decoder::new(&serialized, UnknownKeyPolicy::Error); + let mut schema =3D KBox::init(ValueSchema::init(), GFP_KERNEL)?; + assert!(decoder.decode(&mut *schema).is_err()); + + // A 64-bit value for a 32-bit key. + let mut encoder =3D Encoder::new(); + encoder.encode_u64(VALUE_KEY, index0, 1)?; + let serialized =3D encoder.finish(); + let decoder =3D Decoder::new(&serialized, UnknownKeyPolicy::Error); + let mut schema =3D KBox::init(ValueSchema::init(), GFP_KERNEL)?; + assert!(decoder.decode(&mut *schema).is_err()); + + nvkv_decode! { + struct SlotsSchema =3D> Slots { + slots: Indexed, + } + } + + struct Slots { + slots: [u32; 2], + } + + // An index past the declared slots. + let mut encoder =3D Encoder::new(); + encoder.encode_u32(SLOT_KEY, index2, 1)?; + let serialized =3D encoder.finish(); + let decoder =3D Decoder::new(&serialized, UnknownKeyPolicy::Error); + let mut schema =3D KBox::init(SlotsSchema::init(), GFP_KERNEL)?; + assert!(decoder.decode(&mut *schema).is_err()); + + nvkv_decode! { + struct BlobSchema =3D> Blob { + blob: Array, + } + } + + struct Blob { + blob: ArrayVec, + } + + // An array longer than the declared capacity. + let mut encoder =3D Encoder::new(); + encoder.encode_array8(BLOB_KEY, index0, &[0; 5])?; + let serialized =3D encoder.finish(); + let decoder =3D Decoder::new(&serialized, UnknownKeyPolicy::Error); + let mut schema =3D KBox::init(BlobSchema::init(), GFP_KERNEL)?; + assert!(decoder.decode(&mut *schema).is_err()); + + nvkv_decode! { + struct PairSchema =3D> Pair { + x: Required, + y: Required, + } + } + + struct Pair { + x: u32, + y: u32, + } + + nvkv_decode! { + struct PairsSchema =3D> Pairs { + pairs: Accumulated, + } + } + + struct Pairs { + pairs: KVVec, + } + + // Accumulated objects must start at index 0. + let mut encoder =3D Encoder::new(); + encoder.encode_u32(X_KEY, index1, 1)?; + let serialized =3D encoder.finish(); + let decoder =3D Decoder::new(&serialized, UnknownKeyPolicy::Error); + let mut schema =3D KBox::init(PairsSchema::init(), GFP_KERNEL)?; + assert!(decoder.decode(&mut *schema).is_err()); + + // Accumulated objects must be complete before the next one starts. + let mut encoder =3D Encoder::new(); + encoder.encode_u32(X_KEY, index0, 1)?; + encoder.encode_u32(X_KEY, index1, 2)?; + let serialized =3D encoder.finish(); + let decoder =3D Decoder::new(&serialized, UnknownKeyPolicy::Error); + let mut schema =3D KBox::init(PairsSchema::init(), GFP_KERNEL)?; + assert!(decoder.decode(&mut *schema).is_err()); + + Ok(()) + } } --=20 2.55.0 From nobody Mon Sep 28 21:03:44 2026 Received: from BL0PR03CU003.outbound.protection.outlook.com (mail-eastusazon11012064.outbound.protection.outlook.com [52.101.53.64]) (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 95421484889; Mon, 28 Sep 2026 08:43:37 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=fail smtp.client-ip=52.101.53.64 ARC-Seal: i=2; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1790585019; cv=fail; b=dphQk8jAWwh/mSWqTUxOjvZWlnISISdZOd8gODDqzHz8wF4265BmFbU+Kugc7CQ7m1TT9ZVVpuipSIqF3EKq1tQpPnIHLiWuXZ1eL9OmKm7N2AbCVupHQGHi8N19as9vLjIe99yOvbW3fdsnsiATal90bdYUdC6DXg8lrOYwxlE= ARC-Message-Signature: i=2; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1790585019; c=relaxed/simple; bh=XdgiubZ0DSfRPo25MvaI4vf4EZwma6eXqani2xavYVk=; h=From:Date:Subject:Content-Type:Message-Id:References:In-Reply-To: To:Cc:MIME-Version; b=aT+MyAL7ZyieE99kVZXxm725rWgXMsrZjIlpZUiiwp+g8nBSSR7EfEPntpAmSgFkRF3DzV8gchpvvAjR9zs3w6XvQRyfGflewxHOLO77flpGzD1Jr88t4vSwf8DjPz8EHGeSalKYdm0C8bt+mvspZ4nadFNlMcPCGc4RbrOfMBM= 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=GsU10DFq; arc=fail smtp.client-ip=52.101.53.64 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="GsU10DFq" ARC-Seal: i=1; a=rsa-sha256; s=arcselector10001; d=microsoft.com; cv=none; b=iB0uwznstW6QpIiMC0FD2rMySzauqiMxiNHwdYhMXmbl48gfS71po8T8/dB9VjEO6MH5bOhO/SmcZB+xeMLdlO0psf3aJ8bIBI/Gmcb2Yp9d1QQxGIEloMBBrDpUaalGe9JwDPZilxz97J+Z6agQsvjTnK/6ZtcVb3O40Z2G+SOwLM275ZSH78T9EvofKSJq4MFoxcdZFLRANUulgVzCQqZ18guWBMCdP9ohpXbvdQDFOMpkmkG3yOKQNJ7qzlqJugMbLSXTUqBUxw1xwu8+Czppv/ZFDOBxC0vdcZ2kps2EGBj8ougyCjfauYta8Kzo6UR1e9wPd/aQRpA5P+O0Pw== 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=jS6aTUJsNMCH7fSHKuJFcVVVt9xFqg1HRFc6uBeL9wU=; b=s51NXRF1mTJ7vTGLb3pPbvBRyVUB+Y4IFCAjEkUh6wEzkolAHlwNkALe323a+JSTy6tTVpG8zwd5jyAv5XnQPg7/aYENhpzI1H/WZPDb5tpUE3nRyvGlSrRCIJVvOzPkkhaAlNy7Lxmb0rT1hgVW5prrkpeOHX7yIXRbdnOCE2Dgi0ZgyfUORs1ZyMBYj0XtqsP272W+xk+5EVsY/zU3ZIcs44If5pnOTqaF3Y3hwGupyFb4tf4KpI7+EbvGJi5zxxGy3wI44NbMH7Y0Bxjt6FIPkDvm/Ft3rn+5LdO/J5AijRbOKBk9otk4PLMXVM/ZX0hvASKAgE+cJptGSuqOQQ== 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=jS6aTUJsNMCH7fSHKuJFcVVVt9xFqg1HRFc6uBeL9wU=; b=GsU10DFqznog5L+pyf6XV+MDdNwfe2M1oPEANuH5KVdOjGWeQw+KvHKRqvaaDCVGpDRd7xu/4g7KNUwF56NpIsh5mkWYxniDE8Jlh365Wyvy6swQqGPK6efZdYhYitTspHirThV3uo7Azxf2TQP7euYIY1G2lpE62aGG6qmpHvDq0XlRqKQf6Dr3gWziamGe6Ow+OyAKR72mnfSZAU7Vm48GqdEXUNPnCLdzsrSCSZ3W/2wkF5g5maeGnSGxMGxANRglNTPOfVxjE63lSal6ZTFvxQ3xG990p0JTq1G3K4OiRDwkEneB+2tugkbxAiORS+TAgZmcQWEoE7G0upJAYQ== Authentication-Results: mx.microsoft.com 1; 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 CY8PR12MB8065.namprd12.prod.outlook.com (2603:10b6:930:73::16) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.21.451.24; Mon, 28 Sep 2026 08:43:27 +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.0451.022; Mon, 28 Sep 2026 08:43:27 +0000 From: Eliot Courtney Date: Mon, 28 Sep 2026 17:42:47 +0900 Subject: [PATCH v3 8/8] gpu: nova-core: add NVKV GSP_INIT schemas Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260928-b4-nvkv-v3-8-f04504c262c2@nvidia.com> References: <20260928-b4-nvkv-v3-0-f04504c262c2@nvidia.com> In-Reply-To: <20260928-b4-nvkv-v3-0-f04504c262c2@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: TYCPR01CA0160.jpnprd01.prod.outlook.com (2603:1096:400:2b1::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_|CY8PR12MB8065:EE_ X-MS-Office365-Filtering-Correlation-Id: cefe7ad4-6f37-428b-0d1f-08df1d3c9085 X-MS-Exchange-SenderADCheck: 1 X-MS-Exchange-AntiSpam-Relay: 0 X-Microsoft-Antispam: BCL:0;ARA:13230040|10070799003|23010399003|7416014|376014|1800799024|366016|10067099003|56012099006|11063799006|921020|3023799007|6133799003|18002099003|22082099003|13003099007; X-Microsoft-Antispam-Message-Info: B6wK8/+1VKniXhorw1B25okkq1e5IjF8OR5k593jurvKGCnotzXryxLU9inLbftVhGkMYabyChuBK/7jmu0cKyejaSK+I5zhVAFgT8NBHO/zYS3Kaqf2J5DaPnVLJC6pmAXUcaP57zH3c92RCFE+zXsONAYPyNHJFjwgV3XUYDwlBPzE3EzTtCkOsR8rmow3kmaGuj2zdGeZVlTEjktYWUFOaw3icim99GahNjZMh1h7kajPfNVgUaNr6OApVMNWl3v8hTuXTNZY5icOu4tg5mjD/dBzP1wf5rzqt6YpWmSPBLfExawjlDBC4uXC+laxAkRb80vkPducG+OdS7z6qi9KKoUZmtg3UoOzB5BbgwHmGZ2kbTwWLKhIrIzYG1nSy2sVQBHJHC6xf8bj/t95BQBxm0qzLD61LbUbhSsRTwlU2VUcNMh4QJdpKzKUoxTFm5LLJZuLdoDjZm80tSB270+Mz8vAEXg/KuOVhzGq42sm1vVVwj37i0p4I1DwkFJYUtbhZmGw4X6heCBbpg6oCiOxOTAaQUGmTcubh6wEqCh15/AVbys93Z9daZzgnMs1CwjtHQJ/hn8NdLKAjx7zpZaehphHq6n+XHbOlLNFF/IGBD3o/yJGw/6a7nAQV1EupxZPxdFuvRIRIUHrrBZhOW5hqrWqQql0x5IwdKDVqxtptg1LBcFC81yHUBN4quorr/CbL8nGAfDZVFldLqDIRg== 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)(10070799003)(23010399003)(7416014)(376014)(1800799024)(366016)(10067099003)(56012099006)(11063799006)(921020)(3023799007)(6133799003)(18002099003)(22082099003)(13003099007);DIR:OUT;SFP:1101; X-MS-Exchange-AntiSpam-MessageData-ChunkCount: 2 X-MS-Exchange-AntiSpam-MessageData-0: =?utf-8?B?SU04eFQzYmd3S3lGaXZoY01vSFdSUTE0Z0JSRzNxSUpuNG8rcVk1OVVOdFZ6?= =?utf-8?B?YzIvaWhWd29NcDhZUGFYaXowQVljNjFQa1ROQ0ZqVDk2VEhwRGdpL3cyWU9J?= =?utf-8?B?SWhTaEsyRXJDbm5EUzVXS3NBbFV3Q3VwVlNLVDFXZzA1R1kzN2hoMWxNa0JD?= =?utf-8?B?L2Vwa3NzVUNkQUJsS2ZGcHRBQlpIUXQxank0RXJFVXVnSU0zK2pRbzh2dHV0?= =?utf-8?B?RW1pbDhzbGRmTm1SL3FSV1NyZjA2VlplbWx2QXZ4dEhpdnNER0szemc0VEla?= =?utf-8?B?MFZvckM2MXFQcTlnbmFhdWJlLzNiclhLUFlrMHVmVnBvSUU5NnZKbkdCb1dn?= =?utf-8?B?QUhPcVV6N3FUR3pwVkhHZTg0bzZzNEdyS0REQUVVVlBTQ0xBb3pCdThXOWNt?= =?utf-8?B?dXl3Mk1MWnkwWGpFeVE5bHNZRGNuT3FrbDY2OHhpMVFRZEQ2WU05SUFOUUg0?= =?utf-8?B?b09IU3lSbzQ0VGdFNHJQQkpsdHBHOWNQVE41VjhERFpQRmtOanVBVjhoaG9R?= =?utf-8?B?MlVVblQvSW1oLytZaTNRNVFGS21UQ0gvdldJaHlwOWlYZnlUb2pXL3VZdFgv?= =?utf-8?B?enl6aWNhclkwRC9SMElrRVk5aWp4MUsyMHVERC9kYy9OOEMwaVY2VFpEL08r?= =?utf-8?B?dFpzd0YxUnNuYWJ0QWZHeGdBUlBuWWZyR0szdnRXam5aMEtNczNSdUh0UEUy?= =?utf-8?B?MFU1K2sxMVFGNE9Pc3RjS3FjblRuY2xNZVJVaERLU09CWm9QSUQ3RDdoK3V4?= =?utf-8?B?cWNvVHdDWDBwbWVWM01UWEh6SGVrN0pvTExLV1hBbndybS92UXRGU25kTDh1?= =?utf-8?B?aDVxV2VjNXUwa2xtSWNiU1pId2x3MHdxVHhIR1BtOVMyMU4wbjhUQWN5dk1a?= =?utf-8?B?b0xXdGYxU00xMTlFd0tIdUtaSWwzdm9BdTlENSt3K3V0T3N4SkJiR05kd1VT?= =?utf-8?B?Q0x4ZUF1MlUvT1RDV2hlRk05dituZkEzdFhNekxmd3k5Wi9Bb0h5QUZCcFI3?= =?utf-8?B?L0ZHRHNCM21kUTlUaUplU0tJNTVKMElVaGZvRWFBK0IzdU9iM3A3QTY2MXo1?= =?utf-8?B?blN3czM2ditoVklFbTJ5dHZYN2VhMW44d0xSVWxYUklId3VnNFpTWTA3Vzg0?= =?utf-8?B?bFRSRlRLK1Y1djluUFJuVytSUm1KOXIwQXFlMHRLY0RMQnJKeDVYaW1VNWxD?= =?utf-8?B?TU5aWmpQN1pmbzVGeklUdGpFWDJoUzBDeGsvQ0RXdzhnTWJaRDVZNTBUN0lx?= =?utf-8?B?UXNSWm5MZERrM0hPYkdlSXdQL3p6cUNYY3NqeEVrZVZMTDZ3N0kwcVA5WGhV?= =?utf-8?B?OThwNzQyR2p5UlFtK253SW1CVWo1RDJHMnpBNmVuVHRmak9DblVYSlY4Zk1k?= =?utf-8?B?UE8wa2FIVks2cVlxOW92RllFZlVLdzRYcUlnOWNKM055SDFMbHVZaUN2VUdw?= =?utf-8?B?T1BvY1pnVjZDZHJkZnhER3BMaTA1SGJSKzNDRzUvR3FVQVZhQURFT2g2ZEpF?= =?utf-8?B?cGVyNmlkSUx1QzRIR21QNUxvQS9uMm5rMWVXZWRjOHBlUmZsbXhQbGJXdnc1?= =?utf-8?B?aitiTGdJKzd6QU1ZemRGTWtuUkRHZE1scVNtWVROTXY5a3QvQ3BlYmowbllz?= =?utf-8?B?MTFUN1pTejkvRmcySlgzYTJyc21HcS9IQ21TUEdFVTBadkx0VVZNVEdERkR3?= =?utf-8?B?YkhqWW5oS2laZC9RVElhak53YWlsY01LRzF2cXIwOHluTkxIMWUyS0h5Mnhm?= =?utf-8?B?eXZhUElEZEFmZ1d0eFAzUjN0UDVIa1lUa2V0MGRvY1A1amxoUnpZQjdYUk5a?= =?utf-8?B?ekVBSGhCWnRLVW5NZzVVcHRpelRtVVlGbFF4dXBXRkg1ZTZmc3luNU9tTm1T?= =?utf-8?B?U1lzc3RNRmt6S0VxZWpTNjlqN0pWOFUwaDBiT2YzT29XdnoyWjhLNFVzKzJM?= =?utf-8?B?VkZkMmpteDhaY1o0YjJIdnFWSmhwckdwVFRUUDFtdWdnMUUvV251Sll2Wmhw?= =?utf-8?B?MEVmNW5NZW9tUjNidHlPQmt3Wi92NUVTc3czN0k3Mk9XTjRsbEJDS3lHUWdT?= =?utf-8?B?VHdXUU5PZHQzN3dCc3hIL3AwNTJKOXVIa2YxRlltUEsvQ3d4VTREYmh4c3R3?= =?utf-8?B?SXFscjVDdk9Fa2xIV1dYS1F4ekNuNUtZSlptaFdCVVI2WWZzNlZPSGgvTnVW?= =?utf-8?B?b3gzOVl4NGpRaThiT1JXRGRHeHZWU2lHbUhZTWtoOXdFRVhwNXFzZW9HQTBM?= =?utf-8?B?bkM2bXZnYVJJOC9mbU0rWHltL2dNalZxWktNRGZFSldoWEdzY3IvSnpwWUJC?= =?utf-8?B?MTcwWnBsMm5NaWphb0FVbGVwSzJDQ1JqQ1hIZnBiUG9pcXg0cWRjb3hsdHJH?= =?utf-8?Q?eY744G0vj0gOCSw3F1TIAN90nKQt5+cuEu3eUfCj7JEo3?= X-MS-Exchange-AntiSpam-MessageData-1: e1H3sZ/UnPAT2g== X-OriginatorOrg: Nvidia.com X-MS-Exchange-CrossTenant-Network-Message-Id: cefe7ad4-6f37-428b-0d1f-08df1d3c9085 X-MS-Exchange-CrossTenant-AuthSource: DS0PR12MB6413.namprd12.prod.outlook.com X-MS-Exchange-CrossTenant-AuthAs: Internal X-MS-Exchange-CrossTenant-OriginalArrivalTime: 28 Sep 2026 08:43:27.0794 (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: otfiUwh+422PTHNqKqHTgY65co/IYCxTPUSRLUdoEZnJSfDsrdUPR5GmV/jmF4sdRgmUjtAtA9Bd6IN7PyuWuA== X-MS-Exchange-Transport-CrossTenantHeadersStamped: CY8PR12MB8065 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 | 447 +++++++++++++++++++++++++++= +++- drivers/gpu/nova-core/gsp/nvkv.rs | 3 - drivers/gpu/nova-core/gsp/nvkv/decode.rs | 1 + drivers/gpu/nova-core/gsp/nvkv/encode.rs | 1 + 4 files changed, 448 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/nova-core/gsp/fw/commands.rs b/drivers/gpu/nova-co= re/gsp/fw/commands.rs index 32856ff74183..02de225af917 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::*, @@ -15,7 +17,21 @@ =20 use crate::{ gpu::Chipset, - gsp::GSP_PAGE_SIZE, + gsp::{ + nvkv::{ + nvkv_decode, + nvkv_encode, + Accumulated, + Array, + DecoderValue, + Encodable, + Encoder, + Key, + KeyId, + Required, // + }, + GSP_PAGE_SIZE, // + }, num::IntoSafeCast, // }; =20 @@ -230,3 +246,432 @@ 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, + DecoderValue, + Index, + Schema, + UnknownKeyPolicy, + Visit, // + }; + + use super::*; + + /// Decodes the registry keys of a `GspInitRequest` into (name, value)= pairs. + #[derive(Default)] + struct RegKeysSchema<'d> { + pairs: KVVec<(&'d [u8], u32)>, + // A name whose value has not been decoded yet. + name: Option<&'d [u8]>, + } + + impl<'d> Schema for RegKeysSchema<'d> { + type Target =3D KVVec<(&'d [u8], u32)>; + + fn init() -> impl Init { + Self::default() + } + + fn finish(&mut self) -> impl Init + '_ { + // A name must be followed by its value. + if self.name.take().is_some() { + return Err(EINVAL); + } + Ok(core::mem::take(&mut self.pairs)) + } + } + + impl<'d> Visit<'d> for RegKeysSchema<'d> { + fn visit(&mut self, key: KeyId, index: Index, value: DecoderValue<= 'd>) -> Result { + if key !=3D RegKey::REGKEY_NAME_KEY && key !=3D RegKey::REGKEY= _VALUE_U32_KEY { + return Ok(false); + } + if index !=3D Index::new::<0>() { + return Err(EINVAL); + } + if key =3D=3D RegKey::REGKEY_NAME_KEY { + // A name must be followed by its value before the next na= me. + if self.name.replace(value.try_into()?).is_some() { + return Err(EINVAL); + } + } else { + let name =3D self.name.take().ok_or(EINVAL)?; + self.pairs.push((name, value.try_into()?), GFP_KERNEL)?; + } + Ok(true) + } + } + + // 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"testkey1\0".into(), + key_value: 0xdead_beef.into(), + }, + GFP_KERNEL, + )?; + regkeys.push( + RegKey { + key_name: b"testkey2\0".into(), + key_value: 0x1234_5678.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(), 26); + + type Req =3D GspInitRequest; + let index0 =3D Index::new::<0>(); + let mut expected =3D Encoder::new(); + expected.encode_u32(Req::PCI_DEVICE_ID_KEY, index0, 45)?; + expected.encode_u32(Req::PCI_SUBDEVICE_ID_KEY, index0, 67)?; + expected.encode_u32(Req::PCI_REVISION_ID_KEY, index0, 3)?; + expected.encode_u32(Req::PCI_CONFIG_MIRROR_BASE_KEY, index0, 0x123= 4_5678)?; + expected.encode_u32(Req::PCI_CONFIG_MIRROR_SIZE_KEY, index0, 0x100= 0)?; + expected.encode_u32(Req::HOST_ARCH_KEY, index0, HostArch::Aarch64.= into())?; + expected.encode_u64( + Req::NV_DOMAIN_BUS_DEVICE_FUNC_KEY, + index0, + 0x0001_0203_0405_0607, + )?; + // Each registry key is its name followed by its value, both at in= dex 0. + expected.encode_array8(RegKey::REGKEY_NAME_KEY, index0, b"testkey1= \0")?; + expected.encode_u32(RegKey::REGKEY_VALUE_U32_KEY, index0, 0xdead_b= eef)?; + expected.encode_array8(RegKey::REGKEY_NAME_KEY, index0, b"testkey2= \0")?; + expected.encode_u32(RegKey::REGKEY_VALUE_U32_KEY, index0, 0x1234_5= 678)?; + expected.encode_u32(VfInfo::VF_TOTAL_VFS_KEY, index0, 8)?; + expected.encode_u32(VfInfo::VF_FIRST_VF_OFFSET_KEY, index0, 1)?; + expected.encode_u64(VfInfo::VF_FLAGS_KEY, index0, 0x7)?; + expected.encode_u64(VfInfo::VF_FIRST_BAR0_ADDRESS_KEY, index0, 0x1= 000_0000)?; + expected.encode_u64(VfInfo::VF_FIRST_BAR1_ADDRESS_KEY, index0, 0x2= 000_0000)?; + expected.encode_u64(VfInfo::VF_FIRST_BAR2_ADDRESS_KEY, index0, 0x3= 000_0000)?; + assert_eq!(*encoded, *expected.finish()); + + // Decode the registry keys back out of the request, ignoring the = other keys. + let decoder =3D Decoder::new(&encoded, UnknownKeyPolicy::Ignore); + let mut schema =3D KBox::init(RegKeysSchema::init(), GFP_KERNEL)?; + let decoded =3D KBox::try_init(decoder.decode(&mut *schema)?, GFP_= KERNEL)?; + assert_eq!( + decoded.as_slice(), + &[ + (&b"testkey1\0"[..], 0xdead_beef), + (&b"testkey2\0"[..], 0x1234_5678), + ] + ); + + 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 KBox::init(FbRegionSchema::init(), GFP_KERNEL)?; + 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 KBox::init(Resp::init(), GFP_KERNEL)?; + 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 KBox::init(Resp::init(), GFP_KERNEL)?; + 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 5791df07a7fa..353125e2d321 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, ops::{ diff --git a/drivers/gpu/nova-core/gsp/nvkv/decode.rs b/drivers/gpu/nova-co= re/gsp/nvkv/decode.rs index 24dad31296cb..c592d3098816 100644 --- a/drivers/gpu/nova-core/gsp/nvkv/decode.rs +++ b/drivers/gpu/nova-core/gsp/nvkv/decode.rs @@ -132,6 +132,7 @@ fn visit( } }; } +pub(crate) use nvkv_decode; =20 impl Schema for Key { type Target =3D T; diff --git a/drivers/gpu/nova-core/gsp/nvkv/encode.rs b/drivers/gpu/nova-co= re/gsp/nvkv/encode.rs index 2cc0cfbad814..6d06682fca5f 100644 --- a/drivers/gpu/nova-core/gsp/nvkv/encode.rs +++ b/drivers/gpu/nova-core/gsp/nvkv/encode.rs @@ -62,6 +62,7 @@ fn encode(&self, encoder: &mut $crate::gsp::nvkv::Encoder= ) -> ::kernel::error::R } }; } +pub(crate) use nvkv_encode; =20 /// A value with a specific index that encodes under the NVKV key `KEY_ID`. struct IndexedKey { --=20 2.55.0