get:
Show a patch.

patch:
Update a patch.

put:
Update a patch.

GET /api/1.0/patches/2197380/?format=api
HTTP 200 OK
Allow: GET, PUT, PATCH, HEAD, OPTIONS
Content-Type: application/json
Vary: Accept

{
    "id": 2197380,
    "url": "http://patchwork.ozlabs.org/api/1.0/patches/2197380/?format=api",
    "project": {
        "id": 28,
        "url": "http://patchwork.ozlabs.org/api/1.0/projects/28/?format=api",
        "name": "Linux PCI development",
        "link_name": "linux-pci",
        "list_id": "linux-pci.vger.kernel.org",
        "list_email": "linux-pci@vger.kernel.org",
        "web_url": null,
        "scm_url": null,
        "webscm_url": null
    },
    "msgid": "<20260217204909.211793-2-zhiw@nvidia.com>",
    "date": "2026-02-17T20:49:06",
    "name": "[v3,1/1] rust: introduce abstractions for fwctl",
    "commit_ref": null,
    "pull_url": null,
    "state": "new",
    "archived": false,
    "hash": "e31277e6607923a9e0699f4561c3c79f95c8e303",
    "submitter": {
        "id": 88076,
        "url": "http://patchwork.ozlabs.org/api/1.0/people/88076/?format=api",
        "name": "Zhi Wang",
        "email": "zhiw@nvidia.com"
    },
    "delegate": null,
    "mbox": "http://patchwork.ozlabs.org/project/linux-pci/patch/20260217204909.211793-2-zhiw@nvidia.com/mbox/",
    "series": [
        {
            "id": 492476,
            "url": "http://patchwork.ozlabs.org/api/1.0/series/492476/?format=api",
            "date": "2026-02-17T20:49:05",
            "name": "rust: introduce abstractions for fwctl",
            "version": 3,
            "mbox": "http://patchwork.ozlabs.org/series/492476/mbox/"
        }
    ],
    "check": "pending",
    "checks": "http://patchwork.ozlabs.org/api/patches/2197380/checks/",
    "tags": {},
    "headers": {
        "Return-Path": "\n <linux-pci+bounces-47505-incoming=patchwork.ozlabs.org@vger.kernel.org>",
        "X-Original-To": [
            "incoming@patchwork.ozlabs.org",
            "linux-pci@vger.kernel.org"
        ],
        "Delivered-To": "patchwork-incoming@legolas.ozlabs.org",
        "Authentication-Results": [
            "legolas.ozlabs.org;\n\tdkim=pass (2048-bit key;\n unprotected) header.d=Nvidia.com header.i=@Nvidia.com header.a=rsa-sha256\n header.s=selector2 header.b=prVxL1Ql;\n\tdkim-atps=neutral",
            "legolas.ozlabs.org;\n spf=pass (sender SPF authorized) smtp.mailfrom=vger.kernel.org\n (client-ip=2600:3c09:e001:a7::12fc:5321; helo=sto.lore.kernel.org;\n envelope-from=linux-pci+bounces-47505-incoming=patchwork.ozlabs.org@vger.kernel.org;\n receiver=patchwork.ozlabs.org)",
            "smtp.subspace.kernel.org;\n\tdkim=pass (2048-bit key) header.d=Nvidia.com header.i=@Nvidia.com\n header.b=\"prVxL1Ql\"",
            "smtp.subspace.kernel.org;\n arc=fail smtp.client-ip=52.101.48.14",
            "smtp.subspace.kernel.org;\n dmarc=pass (p=reject dis=none) header.from=nvidia.com",
            "smtp.subspace.kernel.org;\n spf=fail smtp.mailfrom=nvidia.com"
        ],
        "Received": [
            "from sto.lore.kernel.org (sto.lore.kernel.org\n [IPv6:2600:3c09:e001:a7::12fc:5321])\n\t(using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)\n\t key-exchange x25519 server-signature ECDSA (secp384r1) server-digest SHA384)\n\t(No client certificate requested)\n\tby legolas.ozlabs.org (Postfix) with ESMTPS id 4fFsGT6BKCz1xpl\n\tfor <incoming@patchwork.ozlabs.org>; Wed, 18 Feb 2026 07:50:09 +1100 (AEDT)",
            "from smtp.subspace.kernel.org (conduit.subspace.kernel.org\n [100.90.174.1])\n\tby sto.lore.kernel.org (Postfix) with ESMTP id 565B63017DEF\n\tfor <incoming@patchwork.ozlabs.org>; Tue, 17 Feb 2026 20:50:04 +0000 (UTC)",
            "from localhost.localdomain (localhost.localdomain [127.0.0.1])\n\tby smtp.subspace.kernel.org (Postfix) with ESMTP id 3CF8C36F430;\n\tTue, 17 Feb 2026 20:50:00 +0000 (UTC)",
            "from MW6PR02CU001.outbound.protection.outlook.com\n (mail-westus2azon11012014.outbound.protection.outlook.com [52.101.48.14])\n\t(using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits))\n\t(No client certificate requested)\n\tby smtp.subspace.kernel.org (Postfix) with ESMTPS id 471A1259CBD;\n\tTue, 17 Feb 2026 20:49:58 +0000 (UTC)",
            "from PH7P220CA0022.NAMP220.PROD.OUTLOOK.COM (2603:10b6:510:326::23)\n by SA3PR12MB8440.namprd12.prod.outlook.com (2603:10b6:806:2f8::8) with\n Microsoft SMTP Server (version=TLS1_2,\n cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.20.9632.13; Tue, 17 Feb\n 2026 20:49:49 +0000",
            "from CO1PEPF000044FD.namprd21.prod.outlook.com\n (2603:10b6:510:326:cafe::78) by PH7P220CA0022.outlook.office365.com\n (2603:10b6:510:326::23) with Microsoft SMTP Server (version=TLS1_3,\n cipher=TLS_AES_256_GCM_SHA384) id 15.20.9611.16 via Frontend Transport; Tue,\n 17 Feb 2026 20:49:49 +0000",
            "from mail.nvidia.com (216.228.117.160) by\n CO1PEPF000044FD.mail.protection.outlook.com (10.167.241.203) with Microsoft\n SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id\n 15.20.9654.0 via Frontend Transport; Tue, 17 Feb 2026 20:49:49 +0000",
            "from rnnvmail204.nvidia.com (10.129.68.6) by mail.nvidia.com\n (10.129.200.66) with Microsoft SMTP Server (version=TLS1_2,\n cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.2.2562.20; Tue, 17 Feb\n 2026 12:49:28 -0800",
            "from rnnvmail203.nvidia.com (10.129.68.9) by rnnvmail204.nvidia.com\n (10.129.68.6) with Microsoft SMTP Server (version=TLS1_2,\n cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.2.2562.20; Tue, 17 Feb\n 2026 12:49:27 -0800",
            "from inno-thin-ubuntu.nvidia.com (10.127.8.9) by mail.nvidia.com\n (10.129.68.9) with Microsoft SMTP Server id 15.2.2562.20 via Frontend\n Transport; Tue, 17 Feb 2026 12:49:20 -0800"
        ],
        "ARC-Seal": [
            "i=2; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116;\n\tt=1771361400; cv=fail;\n b=j9UZzPlTVkbP7ybmF+vf8ps94UTmDT/WMS+6pTlecI3Khr6e7URe/vAN3bOmVNLgH7OAztzgvamXhbffffN0gWQomQTABg5XA2pSt1LDIlZYMfdqpWOH0J5lKAk/3AL99dipBhzSPFM8/IB/zMdDNFVcTffyTtFfyCpJ+6Dcm6s=",
            "i=1; a=rsa-sha256; s=arcselector10001; d=microsoft.com; cv=none;\n b=wUP1R+uae/bMbUw7HLlAjKTIjPQlPb5XJvXzY3LRh5gBAw+L7pUqbsraA0j7c3ZAR+LpZWtZJKuuyI/TynCPVgtlA4urKGDLCMa35V2pzm9K9BcElvqgm2gcK9pM5Xy8CG1ht2W8vqDhxu5cUqnrl5U4KgaIEyPQZCiX/J3eMxdBJrTmR0d/syEYL4nw1hH2f+ShWK+DHQEDj+aH3gA6tySFiIwnN6IF2Oa4OiKLk81Fu+oIlNqDEGKswuwd8De0i/yai94p+GbQTUgKqYBDgK8JpYcljduxMSwIFdFKTccGYnBq6h0T/o9IEOKUl+VXCn7wtDUq+q36Iix+YfJKmw=="
        ],
        "ARC-Message-Signature": [
            "i=2; a=rsa-sha256; d=subspace.kernel.org;\n\ts=arc-20240116; t=1771361400; c=relaxed/simple;\n\tbh=ubbnnhPXIfPRsBEglCZxBk4l6d+zYdc3A9GkiIHfogg=;\n\th=From:To:CC:Subject:Date:Message-ID:In-Reply-To:References:\n\t MIME-Version:Content-Type;\n b=nxkz3ZEaw8rqlXvuxljlJXxVH/C2Fi7MRjJ7KTphYaf/HxrKAugJzu4eP5rFHMUfh3iHdWgwdQRvUw030C4hH44oW806zvzfTy0TkW+6pb1o4HgkbzYAy3VqMWNtaVCDewUjRAmc9wqSQbyJ+CC50eFtP98sOw3JPcA5Q7eN8To=",
            "i=1; a=rsa-sha256; c=relaxed/relaxed; d=microsoft.com;\n s=arcselector10001;\n 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;\n bh=WlBiRKaZ8SSuXBodxLOQpqwBMMwIS1MSqb/PlHGGNIw=;\n b=GDMUNqaBvgtz9N5L14/vvUsLOyBNKEYdAO33QfN3YSElI7v7FDBxbaS9C1afm9zdjbrKhX7o17YF0fOMBG6oKH7G+X+sCFTja+ypJLw5sod6vcN7wkKr1ZFB7h7+fG9LS3CF5x5DmZtW2ko4YLSnoLc4eKTYTT8ios9RN3ZCBzZUXJZUtf216erFv/bQaxPde0FTF1wSgWuUoxobjyqvQTv8io1WtC2HNa+5W4hFDoWD8xXdo/wXu8+FBSOtRY5YiIyRnnQkf351aPRbhDuOLWizZILRHYK53JPdgNWoHsmLmT7SHbqNx8fqU1vS0tZDDGSKQIwACsWQuYYdx9bRHA=="
        ],
        "ARC-Authentication-Results": [
            "i=2; smtp.subspace.kernel.org;\n dmarc=pass (p=reject dis=none) header.from=nvidia.com;\n spf=fail smtp.mailfrom=nvidia.com;\n dkim=pass (2048-bit key) header.d=Nvidia.com header.i=@Nvidia.com\n header.b=prVxL1Ql; arc=fail smtp.client-ip=52.101.48.14",
            "i=1; mx.microsoft.com 1; spf=pass (sender ip is\n 216.228.117.160) smtp.rcpttodomain=vger.kernel.org smtp.mailfrom=nvidia.com;\n dmarc=pass (p=reject sp=reject pct=100) action=none header.from=nvidia.com;\n dkim=none (message not signed); arc=none (0)"
        ],
        "DKIM-Signature": "v=1; a=rsa-sha256; c=relaxed/relaxed; d=Nvidia.com;\n s=selector2;\n h=From:Date:Subject:Message-ID:Content-Type:MIME-Version:X-MS-Exchange-SenderADCheck;\n bh=WlBiRKaZ8SSuXBodxLOQpqwBMMwIS1MSqb/PlHGGNIw=;\n b=prVxL1QlX8rw6I8Bu2XmSSieqLfSEjDhQ6gfHP6sMc12MZ6OP2O7j4baj37q/eVLXcO98p/VUNq93Z6LqYh3Zhv1QhUyjrYfc9dX2js67OuUTFuXCJ9V/Qy5Sjhvi56IxorDHx1fqKS+AkcpAOqnuE5wxABHM3oel4Scx03KZQHbRMIiZn10JILqHfzV9DQwiEoap+KRdBb7SC+HNCuWE+g/Wz2fIPNPrnBQtV/6cV34/AaDjWCm7P4ouEZPceJczM3f6ZLyfsqu7py4vcP3qA9GoFGJVPCgckoF6JPg7ma3YNCoJN+lwWflLs0hCR8f00r5gConRrQEzYBjiYE4gQ==",
        "X-MS-Exchange-Authentication-Results": "spf=pass (sender IP is 216.228.117.160)\n smtp.mailfrom=nvidia.com; dkim=none (message not signed)\n header.d=none;dmarc=pass action=none header.from=nvidia.com;",
        "Received-SPF": "Pass (protection.outlook.com: domain of nvidia.com designates\n 216.228.117.160 as permitted sender) receiver=protection.outlook.com;\n client-ip=216.228.117.160; helo=mail.nvidia.com; pr=C",
        "From": "Zhi Wang <zhiw@nvidia.com>",
        "To": "<rust-for-linux@vger.kernel.org>, <linux-pci@vger.kernel.org>,\n\t<linux-kernel@vger.kernel.org>",
        "CC": "<dakr@kernel.org>, <jgg@nvidia.com>, <gary@garyguo.net>,\n\t<joelagnelf@nvidia.com>, <aliceryhl@google.com>, <bhelgaas@google.com>,\n\t<kwilczynski@kernel.org>, <ojeda@kernel.org>, <alex.gaynor@gmail.com>,\n\t<boqun.feng@gmail.com>, <bjorn3_gh@protonmail.com>, <lossin@kernel.org>,\n\t<a.hindborg@kernel.org>, <tmgross@umich.edu>, <markus.probst@posteo.de>,\n\t<helgaas@kernel.org>, <cjia@nvidia.com>, <smitra@nvidia.com>,\n\t<ankita@nvidia.com>, <aniketa@nvidia.com>, <kwankhede@nvidia.com>,\n\t<targupta@nvidia.com>, <acourbot@nvidia.com>, <jhubbard@nvidia.com>,\n\t<zhiwang@kernel.org>, <daniel.almeida@collabora.com>, Zhi Wang\n\t<zhiw@nvidia.com>",
        "Subject": "[PATCH v3 1/1] rust: introduce abstractions for fwctl",
        "Date": "Tue, 17 Feb 2026 22:49:06 +0200",
        "Message-ID": "<20260217204909.211793-2-zhiw@nvidia.com>",
        "X-Mailer": "git-send-email 2.43.0",
        "In-Reply-To": "<20260217204909.211793-1-zhiw@nvidia.com>",
        "References": "<20260217204909.211793-1-zhiw@nvidia.com>",
        "Precedence": "bulk",
        "X-Mailing-List": "linux-pci@vger.kernel.org",
        "List-Id": "<linux-pci.vger.kernel.org>",
        "List-Subscribe": "<mailto:linux-pci+subscribe@vger.kernel.org>",
        "List-Unsubscribe": "<mailto:linux-pci+unsubscribe@vger.kernel.org>",
        "MIME-Version": "1.0",
        "Content-Type": "text/plain; charset=\"UTF-8\"",
        "Content-Transfer-Encoding": "8bit",
        "X-NV-OnPremToCloud": "ExternallySecured",
        "X-EOPAttributedMessage": "0",
        "X-MS-PublicTrafficType": "Email",
        "X-MS-TrafficTypeDiagnostic": "CO1PEPF000044FD:EE_|SA3PR12MB8440:EE_",
        "X-MS-Office365-Filtering-Correlation-Id": "56a73de9-5233-459a-1990-08de6e661769",
        "X-MS-Exchange-SenderADCheck": "1",
        "X-MS-Exchange-AntiSpam-Relay": "0",
        "X-Microsoft-Antispam": "\n\tBCL:0;ARA:13230040|36860700013|82310400026|376014|7416014|1800799024;",
        "X-Microsoft-Antispam-Message-Info": "=?utf-8?q?gz88ERuUaNQZ8KW70jsrb0NeQDM05X/?=\n\t=?utf-8?q?nRZR5RSHLkRffWuJzmV8qGq1WuzUVzfWWjuib1DJQkAGUGzJ3/0XV+CXELqN4iQ3w?=\n\t=?utf-8?q?iZo1yo6W/eu/t3ZuW4uo+hKQ4uCvq7o0QoYVsXm0OumokcyOZAzHbqQU1uzUp7pPZ?=\n\t=?utf-8?q?oXoTRx0fpJJiD2kjTqpaucf9OT8BYQhe24AVbRABpqorsVutct9mlomY3e5H1CxxR?=\n\t=?utf-8?q?SiBhrGf0lTIYwAdZmRrGOWO1MvKaAjkgIFxVLWdqOhFiaAHhgSTv1cU34M7IOk8sG?=\n\t=?utf-8?q?n3xVKfBh9XUUG+2a9mJLUtagtJzCALfgnOpzlX2erAgEfnj5EYXtEX68lwJL1qju/?=\n\t=?utf-8?q?R/WUSCQutYkLG5O84H5OH/PBxnfAgQ99ebHAA5Q2AxfdYAQWuv9ltqRfeJwQGfiIy?=\n\t=?utf-8?q?SLVCYg3iUOrIWg4pfpB69xZGQdjfxtm72tkwQdYfdY0sh1NtDMsYAPHov3wPrvo68?=\n\t=?utf-8?q?CNDt5Ld3DaGnhTvWQCNzFz+VZcs1Pb3kQX0bP9hhRqBSsE3JSxsEqw47BIdIRJxb7?=\n\t=?utf-8?q?/fyfLVWWRl21eB9Trn7FLVTufnFO2M2SFN4C7p364akO35tsMk05U6migsBD3i1O8?=\n\t=?utf-8?q?n+2LCcN6HkJ/+cEtzA1kLnA5mkun4A1+6guRBYtC4/6119NUgEsohSEDo9ybB6rVq?=\n\t=?utf-8?q?h1pCpZiSXHmwGY1d4aVAfZFEFonpm2dDjYqH6uolM2I1Zw3XsQursfzUOaw+1voNG?=\n\t=?utf-8?q?ZXzFmXzN7LDC7rr3b231ARBfUFX/s4l9vMEHIOCWvEceNOQK5xxbL1jb52fCJWZwC?=\n\t=?utf-8?q?lH2cTWklz599V1EbuVJeTeG7/IzegU/ptfWCbpnFmU4JSmV7sToJNCduYsLlbrDIo?=\n\t=?utf-8?q?iPWu4YWPasgef3rsCvYzEIKXKUAErGZAFg2hSP60er1cUgH4a41QRupeM19/nQ5kV?=\n\t=?utf-8?q?gCLcXUdeFiM2dHVk0+pc2WUQSKpDtCgXUgurGoUSoLgptZAz8K3zxi35qucEQWUpf?=\n\t=?utf-8?q?tJ+dmVAKoNrxZHVC/caM73nudd67RQcTR8wdQe4beuHgOVq/5nJtT01jEE5/EyUuD?=\n\t=?utf-8?q?kYWDIzstIbjf67TkxU6wV8EF5jmu4E2KPjEfb3xHbIfsiZYgQ2GxrSoOl+HQX9hf6?=\n\t=?utf-8?q?sf0/+7dm80bZistMDHEtAK2s4sGQV9mwg+R8R9VWJ8AhOKi6Elsm6AYpydUmillDE?=\n\t=?utf-8?q?insiIMUdgglQaOwoh4EoOT/TJk2A0UqeRS8FHkjMiakd8IkRIzC8AIa8YiMekti/P?=\n\t=?utf-8?q?Br42RDnPDsgV1NL8/3n5BhMWA/EYKL40zRAEgu3u0XzqmDzkAI8aPNbvlQdwOHDNJ?=\n\t=?utf-8?q?qCLFbPc98elKeWMs8Rdz7OAxywjAFrhrF4JBFZhkbSKbsH8RWcf7qI+0AymorWLpJ?=\n\t=?utf-8?q?rUZxuI99RCJmwzblsdRXtRJ0J3doW+6KJewGJi70zxvL9g2uFiMVj22EQLL6qMPTp?=\n\t=?utf-8?q?3UMQLTgQwIpOu6wZRQBFqfSFVE3c0RmuBDOBcoJMIYAH83Ry2xO+7cNbZKzM15Mvg?=\n\t=?utf-8?q?B7XzhJiRwScD+BIjaByUdl4k7v2210ztdRMOUF0IBMlZaj9TgntJVas3UTiPj8LIe?=\n\t=?utf-8?q?7ySl6obqdtyfGihtMxQg/WK+oTsBZsfZs5+ORsRh/xg1DwHxfywIcwNGnQVCaCySI?=\n\t=?utf-8?q?+NH+x2/ihgD3vlsPCG3MoxZxnWmDJg=3D=3D?=",
        "X-Forefront-Antispam-Report": "\n\tCIP:216.228.117.160;CTRY:US;LANG:en;SCL:1;SRV:;IPV:NLI;SFV:NSPM;H:mail.nvidia.com;PTR:dc6edge1.nvidia.com;CAT:NONE;SFS:(13230040)(36860700013)(82310400026)(376014)(7416014)(1800799024);DIR:OUT;SFP:1101;",
        "X-MS-Exchange-AntiSpam-MessageData-ChunkCount": "1",
        "X-MS-Exchange-AntiSpam-MessageData-0": "\n\tgLAJ7Gi9QVOWsDxWZCVq4dRhSOVHBBZ3X7PgkdniOkpXbKoOiarfikYWyuewLVWsD10Q7+he3P8jlOKeQhSIO7pfuxgtvuqRjYoQnwq+akwoDbvDj2hTzhmHRD6P8YL/iDhPMZ/ZEQDuHyY6zw70wLlDAzTWeCZpJlr01DuV+KAFulZ8HBvnCC2w5SSkjBFSNRc1Sdj9LYRuswCkf6vv0ae3LQ/5YZmJxjo/Lkj4CNWxkDSQXI0UZDSQltct8v/OeEd/fjt0DbeVLvi0OlqsY8dhYZ6Zz3J5++uQZuTfcT3KiHACrfI99+SNAQ7zSsz+HqVpCi8NWF6t2LQvkCF+O+KgIZ0jCtNTg8PcWbGtmO+qPgK7IIm1+GjJWpb6uUsgK5gIq5vF73mhLokhTEH9TLxsfGP4fQrHeENQhrT+2TinY+5sHHtGVL0eLtDXDYgY",
        "X-OriginatorOrg": "Nvidia.com",
        "X-MS-Exchange-CrossTenant-OriginalArrivalTime": "17 Feb 2026 20:49:49.0029\n (UTC)",
        "X-MS-Exchange-CrossTenant-Network-Message-Id": "\n 56a73de9-5233-459a-1990-08de6e661769",
        "X-MS-Exchange-CrossTenant-Id": "43083d15-7273-40c1-b7db-39efd9ccc17a",
        "X-MS-Exchange-CrossTenant-OriginalAttributedTenantConnectingIp": "\n TenantId=43083d15-7273-40c1-b7db-39efd9ccc17a;Ip=[216.228.117.160];Helo=[mail.nvidia.com]",
        "X-MS-Exchange-CrossTenant-AuthSource": "\n\tCO1PEPF000044FD.namprd21.prod.outlook.com",
        "X-MS-Exchange-CrossTenant-AuthAs": "Anonymous",
        "X-MS-Exchange-CrossTenant-FromEntityHeader": "HybridOnPrem",
        "X-MS-Exchange-Transport-CrossTenantHeadersStamped": "SA3PR12MB8440"
    },
    "content": "Introduce safe wrappers around `struct fwctl_device` and\n`struct fwctl_uctx`, allowing rust drivers to register fwctl devices and\nimplement their control and RPC logic in safe rust.\n\nCc: Danilo Krummrich <dakr@kernel.org>\nCc: Gary Guo <gary@garyguo.net>\nCc: Jason Gunthorpe <jgg@nvidia.com>\nCc: Joel Fernandes <joelagnelf@nvidia.com>\nSigned-off-by: Zhi Wang <zhiw@nvidia.com>\n---\n drivers/fwctl/Kconfig           |  12 +\n rust/bindings/bindings_helper.h |   1 +\n rust/helpers/fwctl.c            |  17 ++\n rust/helpers/helpers.c          |   3 +-\n rust/kernel/fwctl.rs            | 449 ++++++++++++++++++++++++++++++++\n rust/kernel/lib.rs              |   2 +\n 6 files changed, 483 insertions(+), 1 deletion(-)\n create mode 100644 rust/helpers/fwctl.c\n create mode 100644 rust/kernel/fwctl.rs",
    "diff": "diff --git a/drivers/fwctl/Kconfig b/drivers/fwctl/Kconfig\nindex b5583b12a011..ce42c0f52d6d 100644\n--- a/drivers/fwctl/Kconfig\n+++ b/drivers/fwctl/Kconfig\n@@ -9,6 +9,18 @@ menuconfig FWCTL\n \t  fit neatly into an existing subsystem.\n \n if FWCTL\n+\n+config RUST_FWCTL_ABSTRACTIONS\n+\tbool \"Rust fwctl abstractions\"\n+\tdepends on RUST\n+\thelp\n+\t  This enables the Rust abstractions for the fwctl device firmware\n+\t  access framework. It provides safe wrappers around struct fwctl_device\n+\t  and struct fwctl_uctx, allowing Rust drivers to register fwctl devices\n+\t  and implement their control and RPC logic in safe Rust.\n+\n+\t  If unsure, say N.\n+\n config FWCTL_MLX5\n \ttristate \"mlx5 ConnectX control fwctl driver\"\n \tdepends on MLX5_CORE\ndiff --git a/rust/bindings/bindings_helper.h b/rust/bindings/bindings_helper.h\nindex 083cc44aa952..c0a7248989f7 100644\n--- a/rust/bindings/bindings_helper.h\n+++ b/rust/bindings/bindings_helper.h\n@@ -56,6 +56,7 @@\n #include <linux/fdtable.h>\n #include <linux/file.h>\n #include <linux/firmware.h>\n+#include <linux/fwctl.h>\n #include <linux/fs.h>\n #include <linux/i2c.h>\n #include <linux/interrupt.h>\ndiff --git a/rust/helpers/fwctl.c b/rust/helpers/fwctl.c\nnew file mode 100644\nindex 000000000000..c7eecd4336a7\n--- /dev/null\n+++ b/rust/helpers/fwctl.c\n@@ -0,0 +1,17 @@\n+// SPDX-License-Identifier: GPL-2.0\n+\n+#include <linux/fwctl.h>\n+\n+#if IS_ENABLED(CONFIG_RUST_FWCTL_ABSTRACTIONS)\n+\n+__rust_helper struct fwctl_device *rust_helper_fwctl_get(struct fwctl_device *fwctl)\n+{\n+\treturn fwctl_get(fwctl);\n+}\n+\n+__rust_helper void rust_helper_fwctl_put(struct fwctl_device *fwctl)\n+{\n+\tfwctl_put(fwctl);\n+}\n+\n+#endif\ndiff --git a/rust/helpers/helpers.c b/rust/helpers/helpers.c\nindex a3c42e51f00a..38fbae6880a0 100644\n--- a/rust/helpers/helpers.c\n+++ b/rust/helpers/helpers.c\n@@ -30,8 +30,9 @@\n #include \"dma.c\"\n #include \"drm.c\"\n #include \"err.c\"\n-#include \"irq.c\"\n #include \"fs.c\"\n+#include \"fwctl.c\"\n+#include \"irq.c\"\n #include \"io.c\"\n #include \"jump_label.c\"\n #include \"kunit.c\"\ndiff --git a/rust/kernel/fwctl.rs b/rust/kernel/fwctl.rs\nnew file mode 100644\nindex 000000000000..38bb6f9ebca5\n--- /dev/null\n+++ b/rust/kernel/fwctl.rs\n@@ -0,0 +1,449 @@\n+// SPDX-License-Identifier: GPL-2.0-only\n+\n+//! Abstractions for the fwctl subsystem.\n+//!\n+//! C header: [`include/linux/fwctl.h`]\n+\n+use crate::{\n+    bindings,\n+    container_of,\n+    device,\n+    devres::Devres,\n+    prelude::*,\n+    sync::aref::{\n+        ARef,\n+        AlwaysRefCounted, //\n+    },\n+    types::Opaque, //\n+};\n+use core::{\n+    marker::PhantomData,\n+    ptr::NonNull,\n+    slice, //\n+};\n+\n+/// Represents a fwctl device type.\n+///\n+/// Corresponds to the C `enum fwctl_device_type`.\n+#[repr(u32)]\n+#[derive(Copy, Clone, Debug, Eq, PartialEq)]\n+pub enum DeviceType {\n+    /// Mellanox ConnectX (mlx5) device.\n+    Mlx5 = bindings::fwctl_device_type_FWCTL_DEVICE_TYPE_MLX5,\n+    /// CXL (Compute Express Link) device.\n+    Cxl = bindings::fwctl_device_type_FWCTL_DEVICE_TYPE_CXL,\n+    /// AMD/Pensando PDS device.\n+    Pds = bindings::fwctl_device_type_FWCTL_DEVICE_TYPE_PDS,\n+}\n+\n+impl From<DeviceType> for u32 {\n+    fn from(device_type: DeviceType) -> Self {\n+        device_type as u32\n+    }\n+}\n+\n+/// Scope of access for an RPC request.\n+///\n+/// Corresponds to the C `enum fwctl_rpc_scope`.\n+#[repr(u32)]\n+#[derive(Copy, Clone, Debug, Eq, PartialEq)]\n+pub enum RpcScope {\n+    /// Read/write access to device configuration.\n+    Configuration = bindings::fwctl_rpc_scope_FWCTL_RPC_CONFIGURATION,\n+    /// Read-only access to debug information.\n+    DebugReadOnly = bindings::fwctl_rpc_scope_FWCTL_RPC_DEBUG_READ_ONLY,\n+    /// Write access to lockdown-compatible debug information.\n+    DebugWrite = bindings::fwctl_rpc_scope_FWCTL_RPC_DEBUG_WRITE,\n+    /// Full read/write access to all debug information (requires `CAP_SYS_RAWIO`).\n+    DebugWriteFull = bindings::fwctl_rpc_scope_FWCTL_RPC_DEBUG_WRITE_FULL,\n+}\n+\n+impl TryFrom<u32> for RpcScope {\n+    type Error = Error;\n+\n+    fn try_from(value: u32) -> Result<Self, Error> {\n+        match value {\n+            v if v == Self::Configuration as u32 => Ok(Self::Configuration),\n+            v if v == Self::DebugReadOnly as u32 => Ok(Self::DebugReadOnly),\n+            v if v == Self::DebugWrite as u32 => Ok(Self::DebugWrite),\n+            v if v == Self::DebugWriteFull as u32 => Ok(Self::DebugWriteFull),\n+            _ => Err(ENOTSUPP),\n+        }\n+    }\n+}\n+\n+/// Response from a [`Operations::fw_rpc`] call.\n+pub enum FwRpcResponse {\n+    /// Reuse the input buffer as the output, with the given output length.\n+    InPlace(usize),\n+    /// Return a newly allocated buffer as the output.\n+    NewBuffer(KVec<u8>),\n+}\n+\n+/// Trait implemented by each Rust driver that integrates with the fwctl subsystem.\n+///\n+/// The implementing type **is** the per-FD user context: one instance is\n+/// created for each `open()` call and dropped when the FD is closed.\n+///\n+/// Each implementation corresponds to a specific device type and provides the\n+/// vtable used by the core `fwctl` layer to manage per-FD user contexts and\n+/// handle RPC requests.\n+pub trait Operations: Sized {\n+    /// Driver data embedded alongside the `fwctl_device` allocation.\n+    type DeviceData;\n+\n+    /// fwctl device type identifier.\n+    const DEVICE_TYPE: DeviceType;\n+\n+    /// Called when a new user context is opened.\n+    ///\n+    /// Returns a [`PinInit`] initializer for `Self`. The instance is dropped\n+    /// automatically when the FD is closed (after [`close`](Self::close)).\n+    fn open(device: &Device<Self>) -> Result<impl PinInit<Self, Error>, Error>;\n+\n+    /// Called when the user context is closed.\n+    ///\n+    /// The driver may perform additional cleanup here that requires access\n+    /// to the owning [`Device`]. `Self` is dropped automatically after this\n+    /// returns.\n+    fn close(_this: Pin<&mut Self>, _device: &Device<Self>) {}\n+\n+    /// Return device information to userspace.\n+    ///\n+    /// The default implementation returns no device-specific data.\n+    fn info(_this: &Self, _device: &Device<Self>) -> Result<KVec<u8>, Error> {\n+        Ok(KVec::new())\n+    }\n+\n+    /// Handle a userspace RPC request.\n+    fn fw_rpc(\n+        this: &Self,\n+        device: &Device<Self>,\n+        scope: RpcScope,\n+        rpc_in: &mut [u8],\n+    ) -> Result<FwRpcResponse, Error>;\n+}\n+\n+/// A fwctl device with embedded driver data.\n+///\n+/// `#[repr(C)]` with the `fwctl_device` at offset 0, matching the C\n+/// `fwctl_alloc_device()` layout convention.\n+///\n+/// # Invariants\n+///\n+/// The `fwctl_device` portion is initialised by the C subsystem during\n+/// [`Device::new()`]. The `data` field is initialised in-place via\n+/// [`PinInit`] before the struct is exposed.\n+#[repr(C)]\n+pub struct Device<T: Operations> {\n+    dev: Opaque<bindings::fwctl_device>,\n+    data: T::DeviceData,\n+}\n+\n+impl<T: Operations> Device<T> {\n+    /// Allocate a new fwctl device with embedded driver data.\n+    ///\n+    /// Returns an [`ARef`] that can be passed to [`Registration::new()`]\n+    /// to make the device visible to userspace. The caller may inspect or\n+    /// configure the device between allocation and registration.\n+    pub fn new(\n+        parent: &device::Device<device::Bound>,\n+        data: impl PinInit<T::DeviceData, Error>,\n+    ) -> Result<ARef<Self>> {\n+        let ops = core::ptr::from_ref::<bindings::fwctl_ops>(&VTable::<T>::VTABLE).cast_mut();\n+\n+        // SAFETY: `_fwctl_alloc_device` allocates `size` bytes via kzalloc and\n+        // initialises the embedded fwctl_device. `ops` points to a static vtable\n+        // that outlives the device. `parent` is bound.\n+        let raw = unsafe {\n+            bindings::_fwctl_alloc_device(parent.as_raw(), ops, core::mem::size_of::<Self>())\n+        };\n+\n+        if raw.is_null() {\n+            return Err(ENOMEM);\n+        }\n+\n+        // CAST: Device<T> is repr(C) with fwctl_device at offset 0.\n+        let this = raw as *mut Self;\n+\n+        // SAFETY: `data` field is within the kzalloc'd allocation, uninitialised.\n+        let data_ptr = unsafe { core::ptr::addr_of_mut!((*this).data) };\n+        unsafe { data.__pinned_init(data_ptr) }.inspect_err(|_| {\n+            // SAFETY: Init failed; release the allocation.\n+            unsafe { bindings::fwctl_put(raw) };\n+        })?;\n+\n+        // SAFETY: `_fwctl_alloc_device` returned a valid pointer with refcount 1\n+        // and DeviceData is fully initialised.\n+        Ok(unsafe { ARef::from_raw(NonNull::new_unchecked(raw as *mut Self)) })\n+    }\n+\n+    /// Returns a reference to the embedded driver data.\n+    pub fn data(&self) -> &T::DeviceData {\n+        &self.data\n+    }\n+\n+    fn as_raw(&self) -> *mut bindings::fwctl_device {\n+        self.dev.get()\n+    }\n+\n+    /// # Safety\n+    ///\n+    /// `ptr` must point to a valid `fwctl_device` embedded in a `Device<T>`.\n+    unsafe fn from_raw<'a>(ptr: *mut bindings::fwctl_device) -> &'a Self {\n+        unsafe { &*ptr.cast() }\n+    }\n+\n+    /// Returns the parent device.\n+    ///\n+    /// The parent is guaranteed to be bound while any fwctl callback is\n+    /// running (ensured by the `registration_lock` read lock on the ioctl\n+    /// path and by `Devres` on the teardown path).\n+    pub fn parent(&self) -> &device::Device<device::Bound> {\n+        // SAFETY: fwctl_device always has a valid parent.\n+        let parent_dev = unsafe { (*self.as_raw()).dev.parent };\n+        let dev: &device::Device = unsafe { device::Device::from_raw(parent_dev) };\n+        // SAFETY: The parent is guaranteed to be bound while fwctl ops are active.\n+        unsafe { dev.as_bound() }\n+    }\n+}\n+\n+impl<T: Operations> AsRef<device::Device> for Device<T> {\n+    fn as_ref(&self) -> &device::Device {\n+        // SAFETY: self.as_raw() is a valid fwctl_device.\n+        let dev = unsafe { core::ptr::addr_of_mut!((*self.as_raw()).dev) };\n+        // SAFETY: dev points to a valid struct device.\n+        unsafe { device::Device::from_raw(dev) }\n+    }\n+}\n+\n+// SAFETY: `fwctl_get` increments the refcount of a valid fwctl_device.\n+// `fwctl_put` decrements it and frees the device when it reaches zero.\n+unsafe impl<T: Operations> AlwaysRefCounted for Device<T> {\n+    fn inc_ref(&self) {\n+        // SAFETY: The existence of a shared reference guarantees that the refcount is non-zero.\n+        unsafe { bindings::fwctl_get(self.as_raw()) };\n+    }\n+\n+    unsafe fn dec_ref(obj: NonNull<Self>) {\n+        // SAFETY: The safety requirements guarantee that the refcount is non-zero.\n+        unsafe { bindings::fwctl_put(obj.cast().as_ptr()) };\n+    }\n+}\n+\n+/// A registered fwctl device.\n+///\n+/// Must live inside a [`Devres`] to guarantee that [`fwctl_unregister`] runs\n+/// before the parent driver unbinds. `Devres` prevents the `Registration`\n+/// from being moved to a context that could outlive the parent device.\n+///\n+/// On drop the device is unregistered (all user contexts are closed and\n+/// `ops` is set to `NULL`) and the [`ARef`] is released.\n+///\n+/// [`fwctl_unregister`]: srctree/drivers/fwctl/main.c\n+pub struct Registration<T: Operations> {\n+    dev: ARef<Device<T>>,\n+}\n+\n+impl<T: Operations> Registration<T> {\n+    /// Register a previously allocated fwctl device.\n+    pub fn new<'a>(\n+        parent: &'a device::Device<device::Bound>,\n+        dev: &'a Device<T>,\n+    ) -> impl PinInit<Devres<Self>, Error> + 'a {\n+        pin_init::pin_init_scope(move || {\n+            // SAFETY: `dev` is a valid fwctl_device backed by an ARef.\n+            let ret = unsafe { bindings::fwctl_register(dev.as_raw()) };\n+            if ret != 0 {\n+                return Err(Error::from_errno(ret));\n+            }\n+\n+            Ok(Devres::new(parent, Self { dev: dev.into() }))\n+        })\n+    }\n+}\n+\n+impl<T: Operations> Drop for Registration<T> {\n+    fn drop(&mut self) {\n+        // SAFETY: `Registration` lives inside a `Devres`, which guarantees\n+        // that drop runs while the parent device is still bound.\n+        unsafe { bindings::fwctl_unregister(self.dev.as_raw()) };\n+        // ARef<Device<T>> is dropped after this, calling fwctl_put.\n+    }\n+}\n+\n+// SAFETY: `Registration` can be sent between threads; the underlying\n+// fwctl_device uses internal locking.\n+unsafe impl<T: Operations> Send for Registration<T> {}\n+\n+// SAFETY: `Registration` provides no mutable access; the underlying\n+// fwctl_device is protected by internal locking.\n+unsafe impl<T: Operations> Sync for Registration<T> {}\n+\n+/// Internal per-FD user context wrapping `struct fwctl_uctx` and `T`.\n+///\n+/// Not exposed to drivers — they work with `&T` / `Pin<&mut T>` directly.\n+#[repr(C)]\n+#[pin_data]\n+struct UserCtx<T: Operations> {\n+    #[pin]\n+    fwctl_uctx: Opaque<bindings::fwctl_uctx>,\n+    #[pin]\n+    uctx: T,\n+}\n+\n+impl<T: Operations> UserCtx<T> {\n+    /// # Safety\n+    ///\n+    /// `ptr` must point to a `fwctl_uctx` embedded in a live `UserCtx<T>`.\n+    unsafe fn from_raw<'a>(ptr: *mut bindings::fwctl_uctx) -> &'a Self {\n+        unsafe { &*container_of!(Opaque::cast_from(ptr), Self, fwctl_uctx) }\n+    }\n+\n+    /// # Safety\n+    ///\n+    /// `ptr` must point to a `fwctl_uctx` embedded in a live `UserCtx<T>`.\n+    /// The caller must ensure exclusive access to the `UserCtx<T>`.\n+    unsafe fn from_raw_mut<'a>(ptr: *mut bindings::fwctl_uctx) -> &'a mut Self {\n+        unsafe { &mut *container_of!(Opaque::cast_from(ptr), Self, fwctl_uctx).cast_mut() }\n+    }\n+\n+    /// Returns a reference to the fwctl [`Device`] that owns this context.\n+    fn device(&self) -> &Device<T> {\n+        // SAFETY: fwctl_uctx.fwctl is set by the subsystem and always valid.\n+        let raw_fwctl = unsafe { (*self.fwctl_uctx.get()).fwctl };\n+        // SAFETY: The fwctl_device is embedded in a Device<T>.\n+        unsafe { Device::from_raw(raw_fwctl) }\n+    }\n+}\n+\n+/// Static vtable mapping Rust trait methods to C callbacks.\n+pub struct VTable<T: Operations>(PhantomData<T>);\n+\n+impl<T: Operations> VTable<T> {\n+    /// The fwctl operations vtable for this driver type.\n+    pub const VTABLE: bindings::fwctl_ops = bindings::fwctl_ops {\n+        device_type: T::DEVICE_TYPE as u32,\n+        uctx_size: core::mem::size_of::<UserCtx<T>>(),\n+        open_uctx: Some(Self::open_uctx_callback),\n+        close_uctx: Some(Self::close_uctx_callback),\n+        info: Some(Self::info_callback),\n+        fw_rpc: Some(Self::fw_rpc_callback),\n+    };\n+\n+    /// # Safety\n+    ///\n+    /// `uctx` must be a valid `fwctl_uctx` embedded in a `UserCtx<T>` with\n+    /// sufficient allocated space for the uctx field.\n+    unsafe extern \"C\" fn open_uctx_callback(uctx: *mut bindings::fwctl_uctx) -> ffi::c_int {\n+        // SAFETY: `fwctl_uctx.fwctl` is set by the subsystem before calling open.\n+        let raw_fwctl = unsafe { (*uctx).fwctl };\n+        // SAFETY: `raw_fwctl` points to a valid fwctl_device embedded in a Device<T>.\n+        let device = unsafe { Device::<T>::from_raw(raw_fwctl) };\n+\n+        let initializer = match T::open(device) {\n+            Ok(init) => init,\n+            Err(e) => return e.to_errno(),\n+        };\n+\n+        let uctx_offset = core::mem::offset_of!(UserCtx<T>, uctx);\n+        // SAFETY: The C side allocated space for the entire UserCtx<T>.\n+        let uctx_ptr: *mut T = unsafe { uctx.cast::<u8>().add(uctx_offset).cast() };\n+\n+        // SAFETY: uctx_ptr points to uninitialised, properly aligned memory.\n+        match unsafe { initializer.__pinned_init(uctx_ptr.cast()) } {\n+            Ok(()) => 0,\n+            Err(e) => e.to_errno(),\n+        }\n+    }\n+\n+    /// # Safety\n+    ///\n+    /// `uctx` must point to a fully initialised `UserCtx<T>`.\n+    unsafe extern \"C\" fn close_uctx_callback(uctx: *mut bindings::fwctl_uctx) {\n+        // SAFETY: `fwctl_uctx.fwctl` is set by the subsystem and always valid.\n+        let device = unsafe { Device::<T>::from_raw((*uctx).fwctl) };\n+\n+        // SAFETY: uctx is a valid pointer promised by C side.\n+        let ctx = unsafe { UserCtx::<T>::from_raw_mut(uctx) };\n+\n+        {\n+            // SAFETY: driver uctx is pinned in place by the C allocation.\n+            let pinned = unsafe { Pin::new_unchecked(&mut ctx.uctx) };\n+            T::close(pinned, device);\n+        }\n+\n+        // SAFETY: After close returns, no further callbacks will access UserCtx.\n+        // Drop T before the C side kfree's the allocation.\n+        unsafe { core::ptr::drop_in_place(&mut ctx.uctx) };\n+    }\n+\n+    /// # Safety\n+    ///\n+    /// `uctx` must point to a fully initialised `UserCtx<T>`.\n+    /// `length` must be a valid pointer.\n+    unsafe extern \"C\" fn info_callback(\n+        uctx: *mut bindings::fwctl_uctx,\n+        length: *mut usize,\n+    ) -> *mut ffi::c_void {\n+        // SAFETY: uctx is a valid pointer promised by C side.\n+        let ctx = unsafe { UserCtx::<T>::from_raw(uctx) };\n+        let device = ctx.device();\n+\n+        match T::info(&ctx.uctx, device) {\n+            Ok(kvec) if kvec.is_empty() => {\n+                // SAFETY: `length` is a valid out-parameter.\n+                unsafe { *length = 0 };\n+                // Return NULL for empty data; kfree(NULL) is safe.\n+                core::ptr::null_mut()\n+            }\n+            Ok(kvec) => {\n+                let (ptr, len, _cap) = kvec.into_raw_parts();\n+                // SAFETY: `length` is a valid out-parameter.\n+                unsafe { *length = len };\n+                ptr.cast::<ffi::c_void>()\n+            }\n+            Err(e) => Error::to_ptr(e),\n+        }\n+    }\n+\n+    /// # Safety\n+    ///\n+    /// `uctx` must point to a fully initialised `UserCtx<T>`.\n+    /// `rpc_in` must be valid for `in_len` bytes. `out_len` must be valid.\n+    unsafe extern \"C\" fn fw_rpc_callback(\n+        uctx: *mut bindings::fwctl_uctx,\n+        scope: u32,\n+        rpc_in: *mut ffi::c_void,\n+        in_len: usize,\n+        out_len: *mut usize,\n+    ) -> *mut ffi::c_void {\n+        let scope = match RpcScope::try_from(scope) {\n+            Ok(s) => s,\n+            Err(e) => return Error::to_ptr(e),\n+        };\n+\n+        // SAFETY: `uctx` is fully initialised; shared access is sufficient.\n+        let ctx = unsafe { UserCtx::<T>::from_raw(uctx) };\n+        let device = ctx.device();\n+\n+        // SAFETY: `rpc_in` / `in_len` are guaranteed valid by the fwctl subsystem.\n+        let rpc_in_slice: &mut [u8] =\n+            unsafe { slice::from_raw_parts_mut(rpc_in.cast::<u8>(), in_len) };\n+\n+        match T::fw_rpc(&ctx.uctx, device, scope, rpc_in_slice) {\n+            Ok(FwRpcResponse::InPlace(len)) => {\n+                // SAFETY: `out_len` is valid.\n+                unsafe { *out_len = len };\n+                rpc_in\n+            }\n+            Ok(FwRpcResponse::NewBuffer(kvec)) => {\n+                let (ptr, len, _cap) = kvec.into_raw_parts();\n+                // SAFETY: `out_len` is valid.\n+                unsafe { *out_len = len };\n+                ptr.cast::<ffi::c_void>()\n+            }\n+            Err(e) => Error::to_ptr(e),\n+        }\n+    }\n+}\ndiff --git a/rust/kernel/lib.rs b/rust/kernel/lib.rs\nindex 3da92f18f4ee..d658720276f2 100644\n--- a/rust/kernel/lib.rs\n+++ b/rust/kernel/lib.rs\n@@ -97,6 +97,8 @@\n pub mod firmware;\n pub mod fmt;\n pub mod fs;\n+#[cfg(CONFIG_RUST_FWCTL_ABSTRACTIONS)]\n+pub mod fwctl;\n #[cfg(CONFIG_I2C = \"y\")]\n pub mod i2c;\n pub mod id_pool;\n",
    "prefixes": [
        "v3",
        "1/1"
    ]
}