From patchwork Mon Jun 16 18:06:04 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jason Gunthorpe X-Patchwork-Id: 897131 Received: from NAM12-BN8-obe.outbound.protection.outlook.com (mail-bn8nam12on2043.outbound.protection.outlook.com [40.107.237.43]) (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 181B7289803; Mon, 16 Jun 2025 18:06:48 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=fail smtp.client-ip=40.107.237.43 ARC-Seal: i=2; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1750097211; cv=fail; b=iC47KYcpuIyY4KeHmxsr+NTvrM4BtnW4uVbranGhRjuUCC2z9eGrb4j1E8F+SI8dLM9H4xLdbP8+3OhrIb1CaqNjGbOgj64CtKPShC6BTttWoOxUfScCyvI6fNihNuB226Y7KPI2cWDxbLCNSefwBY4Fn2Rs/fiK9EZ8ACOwZ+Y= ARC-Message-Signature: i=2; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1750097211; c=relaxed/simple; bh=+gTKLac0qZ1pEYftv81ZIQZAz2OgnKszaY7Hve6KlKQ=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: Content-Type:MIME-Version; b=Z4b6LlRtYOGAw4BKSZ7U+FFnPg1XpG1uV3BcKW/J+YkNluyE8F1dYlqF5mzKhzIL69zek60fU4gmN30kgJniCuROJGFeyUIcSYmPjcRKd9W1sKSbiYbCwWyiJCFfYF4+KKCq3uvWcoGGUaqcdAte5oTrIfuJ/iwFQAGdM+9oA6w= 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=XHbc+5me; arc=fail smtp.client-ip=40.107.237.43 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="XHbc+5me" ARC-Seal: i=1; a=rsa-sha256; s=arcselector10001; d=microsoft.com; cv=none; b=YPP5ws39Ktgf1ZCoiAD1A3VVysOci6HIT0NRoOceoFw2q+0wZ4nmS/26b18+tBtwzRZgEwAGsvBMpcpzPfX8QCvD/IgFMWXvhZdzOb1m3FCGH6T+hs0rqxNZ0n5AAnf9s/LYXghMPOxbqo00djJWvzv3yg4nxcgoNfUeUiq/0CsGk5Lpgv3dagOayuuvtVUQ+KBkAWOOfHZfiFJyR5ER8tBI2+QPHYJDd1ZpIkNqpq/bui2AQuyGaaRp+/HW5Jb4K5R3fw9OLzInR3CJCBAusqA5wtEP9oBZuamOZ3Vg2tFDVFBFf6oKQVs+pyWg+Ag2J658RuhaXV99Cz2GV4JVeg== 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=/4k8VAh4On/H92JQIQbbmDRCLCBhQY7gefZRMqb3MX4=; b=WZatW9px5yo9d3aU//46jbk36qUFM36UxtoC9P5X/fok0j0f0kU3k6Wbn9EJ950tIF/S+rIamMTyYK0uWs896G2ufvhQHJAgqZRd9Azq9APWBkmgm3BTzzjVaLse6RHJ4GW8cxuANPBLMf8b904Hz/sllfzCdBMlDX3yOugOfy1fIOGpOFH8QL7O5ZO3o9LqPwXW3HQ3xto9O844CwJuOz8jhGyADNIGUutfGqUg4mkjRyW8lPiS0tzF2iR/ntVuEMc14aJVbv3zXjy/ooNOGloVrFRwrafyrYlO65/27OUh3qEBf0pUuBV9Sk4pqq5pfw9BAbE33kE4siNSc1I/tA== 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=/4k8VAh4On/H92JQIQbbmDRCLCBhQY7gefZRMqb3MX4=; b=XHbc+5me/WYRtTOlWId+eqgitZq1SgM4w/aWBx/v3cc+WjpytVOwmUTcTmF4yIdCg+Mkh6hKyn9aYutiouUub4Y3rwHzrTrLq7kxyMYA4jEtGbBhfzmz71zbWLkyXkn+pEUN3F0zVI4JWrllBZk+ZdhsFTWm1VV6MfUEyhu9E5ZEh/dwDGdSho/a5T+mRhtQnTyKSfTdpSbDNA/PC5Ahpju5Z2JGcEUpnerQPul7gW3JZrxuF/uwRPB0fU6HWeWRIhHMnI/krIjNAkjPPpfQauOIbROa4CnTnoNEEvpO7+XnmzBJrAs6CJXnuKek7NmsSaE40f5SJRPpgL3+Mhe4lg== Authentication-Results: dkim=none (message not signed) header.d=none;dmarc=none action=none header.from=nvidia.com; Received: from CH3PR12MB8659.namprd12.prod.outlook.com (2603:10b6:610:17c::13) by SJ1PR12MB6267.namprd12.prod.outlook.com (2603:10b6:a03:456::10) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.20.8835.28; Mon, 16 Jun 2025 18:06:26 +0000 Received: from CH3PR12MB8659.namprd12.prod.outlook.com ([fe80::6eb6:7d37:7b4b:1732]) by CH3PR12MB8659.namprd12.prod.outlook.com ([fe80::6eb6:7d37:7b4b:1732%7]) with mapi id 15.20.8835.023; Mon, 16 Jun 2025 18:06:26 +0000 From: Jason Gunthorpe To: Jonathan Corbet , iommu@lists.linux.dev, Joerg Roedel , Justin Stitt , Kevin Tian , linux-doc@vger.kernel.org, linux-kselftest@vger.kernel.org, llvm@lists.linux.dev, Bill Wendling , Nathan Chancellor , Nick Desaulniers , Miguel Ojeda , Robin Murphy , Shuah Khan , Suravee Suthikulpanit , Will Deacon Cc: Alexey Kardashevskiy , Alejandro Jimenez , James Gowans , Michael Roth , Pasha Tatashin , patches@lists.linux.dev Subject: [PATCH v3 01/15] genpt: Generic Page Table base API Date: Mon, 16 Jun 2025 15:06:04 -0300 Message-ID: <1-v3-a93aab628dbc+521-iommu_pt_jgg@nvidia.com> In-Reply-To: <0-v3-a93aab628dbc+521-iommu_pt_jgg@nvidia.com> References: X-ClientProxiedBy: BL1PR13CA0072.namprd13.prod.outlook.com (2603:10b6:208:2b8::17) To CH3PR12MB8659.namprd12.prod.outlook.com (2603:10b6:610:17c::13) Precedence: bulk X-Mailing-List: linux-kselftest@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 X-MS-PublicTrafficType: Email X-MS-TrafficTypeDiagnostic: CH3PR12MB8659:EE_|SJ1PR12MB6267:EE_ X-MS-Office365-Filtering-Correlation-Id: bcae3ff9-0fab-4e44-2666-08ddad0080aa X-MS-Exchange-SenderADCheck: 1 X-MS-Exchange-AntiSpam-Relay: 0 X-Microsoft-Antispam: BCL:0; ARA:13230040|366016|1800799024|7416014|376014|921020; X-Microsoft-Antispam-Message-Info: aCMS02z3tgKyqzaUrUTO67TkMET5XvVwWeMcgo6WUPkTOd8RDUIMtHsiqEDP6QGIE7uQOrYKfJG3qWZsY8DdluK6fksc07Wlj4pKVZIKipJNpRk92TK0E2JxeRU6Au0HhxrvkSecbKpMgUBEDxLrB2I2lSvKZi821y3vv2S/7PQcLfoEesRQaoe8bdPn+ONTLb6T7DZCxXuVVCMzw850d7m6JBWcSEXUE3yPW+ZtvzSMMihOgs0fX7mNzGT9CF1r0eZRQfORsok3S2habR93IgJ4mVvGDjLc+WkKE9zfLO+OgaAZSoKRZA7C1TGtcXlCKrh/6erUFgvHPLH+IsBlbT51sBjRluJGLQtzfnZgerJ4Wu9HE5F67dRaNNOsI0MyB4KtF3TjM9r6qen+5Ol72jHKLpYuJdvRAoVJIVWhklj8XUB2xfGhgGl0iXsc2tsvfjjU9sC3tEIsf0gqmmo+Vc2gUulJAlxY3ReVt6D+B2PALKEMn7O6H6nrmOu1WTcBO+WXk5HxC47jQlnrTTP9mQNcEskuFdvy2885vR0iAYPcuVcCyi4ydnDSTDllr9k30KKzZtf+CsE9/RYO8n3JOgu1UDohio6mIZBNp1qjVCJ367HBeIrqJ19sFoapXE7NdD01C+IJjTGxX+Dv7LnGmFyWrj710JfhePWciOU/6RQDAt58OTvbklnevtnoOlM4sFQf4+LnCAKaiKzKv4gOSUN1B53tPeQ9B9ou1EphU74XlUXEec2KYMntV5BlySBEe09w/esReRvfe8JFpk2wDXqsqMsQD4SNDw3tt3Yg4HK9jHzmDA77Zv4eakRoPf/u66Ikc7IfcHURCwQzNho7CynYyRbgJlYUWckEdGXWP9UA6V0lxresc1ghgcY5x/Ban1DhSj68Y7SSNfhrYqJIsnFFPfR2G+oBMDa8ZJRbyYuhkOgIjoZAuPD/rMKDxy/JOyca6idR326X5tg/oQMgxOM/b+mzTwZPe/xWrVUiSDhh41uT9oqPUeaqDB0usKXVZrIxEweG6y2WUSWWq7RSKhxdsYzatn3AuMu6iSvkkDNfW85XC9vJr5G3xxvA40HkReR8GeqqwgpYBIOG01N8JZ5QqRGcjl0SHQWs3E0Pt30UoRSG0/N18YkvrUTzRrtXCPCfkcf5X9CBKdg8E6VbZ02lJ8DE+S1pKyXVn7AnSOHx3yyCwbP9dVsJ6qC2LPPiAR1J2S34ApnOjOF2Jv8xKDREmCgPP/mcQ3+JEliw5GHV8O9wkHI2HiULk8LA2l4JlbmY44IYNOli8OWQmDBhsNg3DYlgjqP3cqv/2XTTMhR3KTrrnQa7sWPRnOdPUrK6PDD/vLsHHt7ItEDpKK8vlfK0TlK0wU/+nPrWlFICF+4dPDE5R0c3/EbScRGpSqvQsG1cVdDlzlfrismBOad4/lmK2atc5gssLyRYhOE8pqqXtfxiZp+tgb3mVOLpYkoPFUp+BwG02FwnBjynhk2i4Q== X-Forefront-Antispam-Report: CIP:255.255.255.255; CTRY:; LANG:en; SCL:1; SRV:; IPV:NLI; SFV:NSPM; H:CH3PR12MB8659.namprd12.prod.outlook.com; PTR:; CAT:NONE; SFS:(13230040)(366016)(1800799024)(7416014)(376014)(921020); DIR:OUT; SFP:1101; X-MS-Exchange-AntiSpam-MessageData-ChunkCount: 1 X-MS-Exchange-AntiSpam-MessageData-0: 363uiRwoLMXRUlcX+MVFdyqD4XnurxBQGfVBsXfqj9tmGKqqkg6dZ3hMe8QNUrz9ZYvphU0A5yc3sgQ/q8N8nveutv1Qir4A1taLfETR/uSFdx2DkHfPt9xG9CYNeZ7rykht/KQ6kbbSTopK4i0HYfI0BUdF3JnAU2nKEKZ7VkuwmhtIAn/Ku7CB4NUKnw+j9DX8ZUuBBG8h7szTDp4KuZ4IWg5V60PqA4GeBQJqDvQngXKpo6z67Ep+zXG2ipQEvn/62sSLxLSNlUnAofZ4zBk8augGBGj4hVKvYndWrLDncx+yUCEjx0EeUOuv+Q56lpQHK0hcEwjmwe9++DfUT/H2mgUh9tQlMAH6YqRIvNFvxj8ekoeKLOKa4ThTIA+Hz6PTmXIkGTOggr8B/CDVq37+4AgxtwObF80MCe7h9xNqKoAZPmlhZQ9XB3m8AokDmmO4zhwj5TdfCLZR9UPt2jqknm+fPhjf5eKa3Fgvs7uY536DnghNF2w0dVuNcIAy4VZF6+f5J5chkHjI/ofCRGSU9j+wSOso2IP64NW74LMa3TWVH8W8fyr+YWtnVOv39AhRtx3OK7nlUcnUIEFN/m4VRksbcWZN9YjaYe5OuIAWBP73vJ3Nf5GFVGJb47VtEk6Zo1CuZWStjeixBEb7F1KE4R5gqcv0/yP4ryu/ln4QAlPvYFuKLQoASWKviv5wJISafkBxHO3uZ1j6Dm6+Re3U41zH/czxRcApLadTYnA3QQb+XXYGVBxoesacdPgIAcoar548KbgNk01AHtztqGdv1N+dpdjQpfI0pgBRutEoRKr7VC0XxdvDZ5Ggv2SbsCoz67TNUvcQXPxl8u+2l0c6exglO9oHVgSOeTkbp1RbUkw7rhyv6bvSoaxF2h0IzQH4SAAMfFYI6IcBKdskEW1a3Wh67GujcV/rIzmI8OzSLibYIvTAl9qUqDtiblNwIIQijBTn/5mytnQwYOP7p2MNJfzp+4aY7Not0tTcZYaD6w8S3GPhTQ/AkpGL5G01QsSFUqcepUZ3EMUgBhsl2iZ6zJzbbDG4iobeRVEJ+N4mxlPJqkA7EfNnNiX588eUJXxKkReI5BzJneKw/g6kLV+9FRgE2PQniTkjchs/smewYbB1NciBRxQ6aKnRgYzbBiM5O8/b3JFDj4OBRQgWNKrwFZlLzEPF8MVZQyfJ1mZRJOnCJ9c/hMjuaU5b/r7CrYr4wexCKixC6ttFUEuiEdjnPEnmwTR3DA8NLQHychWehweROyb+g2OiE5p0xoyhsOkMZz+kFp9wXHk+ICj/ESeYmOBh6cn6T/EAMLIFz6fxWTJhXRd4xM65NkPC7vg/zPVHSKNaeb3gM5vSTvkz7VGkFKvJAlVdjyImz3A98xfgkQKyZFV6hHQRl3598AHsGCLfm0Td46g5/D37t6C0PrsLHb6BZC96TGh5akEBqrdRoXs5hZjYnSRnHPFTGBqflTm/iX8MjIf7+08PVtyq7vaqeaDkEs7jrLjCZZHb5R+xhgY/oXZvkLBxvVHMtEqfthbOu/IE25+hZgRwB7g1qnx/2Zf0WgZNNl+0stWKiHfbtBWs4F7kCbrm7hcFWlr2 X-OriginatorOrg: Nvidia.com X-MS-Exchange-CrossTenant-Network-Message-Id: bcae3ff9-0fab-4e44-2666-08ddad0080aa X-MS-Exchange-CrossTenant-AuthSource: CH3PR12MB8659.namprd12.prod.outlook.com X-MS-Exchange-CrossTenant-AuthAs: Internal X-MS-Exchange-CrossTenant-OriginalArrivalTime: 16 Jun 2025 18:06:23.4295 (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: DzpSoF+Y8zDinjnWpy9H5t2d6hS7e6s0J9j1x4Q+QGWY+coQ+MCgbKAvrT0QlLEz X-MS-Exchange-Transport-CrossTenantHeadersStamped: SJ1PR12MB6267 The generic API is intended to be separated from the implementation of page table algorithms. It contains only accessors for walking and manipulating the table and helpers that are useful for building an implementation. Memory management is not in the generic API, but part of the implementation. Using a multi-compilation approach the implementation module would include headers in this order: common.h defs_FMT.h pt_defs.h FMT.h pt_common.h IMPLEMENTATION.h Where each compilation unit would have a combination of FMT and IMPLEMENTATION to produce a per-format per-implementation module. The API is designed so that the format headers have minimal logic, and default implementations are provided if the format doesn't include one. Generally formats provide their code via an inline function using the pattern: static inline FMTpt_XX(..) {} #define pt_XX FMTpt_XX The common code then enforces a function signature so that there is no drift in function arguments, or accidental polymorphic functions (as has been slightly troublesome in mm). Use of function-like #defines are avoided in the format even though many of the functions are small enough. Provide kdocs for the API surface. This is enough to implement the 8 initial format variations with all of their features: * Entries comprised of contiguous blocks of IO PTEs for larger page sizes (AMDv1, ARMv8) * Multi-level tables, up to 6 levels. Runtime selected top level * Runtime variable table level size (ARM's concatenated tables) * Expandable top level (AMDv1) * Optional leaf entries at any level * 32 bit/64 bit virtual and output addresses, using every bit * Sign extended addressing (x86) * Dirty tracking A basic simple format takes about 200 lines to declare the require inline functions. Tested-by: Alejandro Jimenez Signed-off-by: Jason Gunthorpe --- .clang-format | 1 + drivers/iommu/Kconfig | 2 + drivers/iommu/generic_pt/Kconfig | 22 + drivers/iommu/generic_pt/pt_common.h | 354 ++++++++++++ drivers/iommu/generic_pt/pt_defs.h | 323 +++++++++++ drivers/iommu/generic_pt/pt_fmt_defaults.h | 193 +++++++ drivers/iommu/generic_pt/pt_iter.h | 640 +++++++++++++++++++++ drivers/iommu/generic_pt/pt_log2.h | 130 +++++ include/linux/generic_pt/common.h | 134 +++++ 9 files changed, 1799 insertions(+) create mode 100644 drivers/iommu/generic_pt/Kconfig create mode 100644 drivers/iommu/generic_pt/pt_common.h create mode 100644 drivers/iommu/generic_pt/pt_defs.h create mode 100644 drivers/iommu/generic_pt/pt_fmt_defaults.h create mode 100644 drivers/iommu/generic_pt/pt_iter.h create mode 100644 drivers/iommu/generic_pt/pt_log2.h create mode 100644 include/linux/generic_pt/common.h diff --git a/.clang-format b/.clang-format index 48405c54ef271e..43fd61dd2e2092 100644 --- a/.clang-format +++ b/.clang-format @@ -416,6 +416,7 @@ ForEachMacros: - 'for_each_prop_dlc_cpus' - 'for_each_prop_dlc_platforms' - 'for_each_property_of_node' + - 'for_each_pt_level_entry' - 'for_each_rdt_resource' - 'for_each_reg' - 'for_each_reg_filtered' diff --git a/drivers/iommu/Kconfig b/drivers/iommu/Kconfig index 0a33d995d15dd7..d095b755a2ca96 100644 --- a/drivers/iommu/Kconfig +++ b/drivers/iommu/Kconfig @@ -384,3 +384,5 @@ config SPRD_IOMMU Say Y here if you want to use the multimedia devices listed above. endif # IOMMU_SUPPORT + +source "drivers/iommu/generic_pt/Kconfig" diff --git a/drivers/iommu/generic_pt/Kconfig b/drivers/iommu/generic_pt/Kconfig new file mode 100644 index 00000000000000..775a3afb563f72 --- /dev/null +++ b/drivers/iommu/generic_pt/Kconfig @@ -0,0 +1,22 @@ +# SPDX-License-Identifier: GPL-2.0-only + +menuconfig GENERIC_PT + bool "Generic Radix Page Table" + default n + help + Generic library for building radix tree page tables. + + Generic PT provides a set of HW page table formats and a common + set of APIs to work with them. + +if GENERIC_PT +config DEBUG_GENERIC_PT + bool "Extra debugging checks for GENERIC_PT" + default n + help + Enable extra run time debugging checks for GENERIC_PT code. This + incurs a runtime cost and should not be enabled for production + kernels. + + The kunit tests require this to be enabled to get full coverage. +endif diff --git a/drivers/iommu/generic_pt/pt_common.h b/drivers/iommu/generic_pt/pt_common.h new file mode 100644 index 00000000000000..5ed06104d38b45 --- /dev/null +++ b/drivers/iommu/generic_pt/pt_common.h @@ -0,0 +1,354 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2024-2025, NVIDIA CORPORATION & AFFILIATES + * + * This header is included after the format. It contains definitions + * that build on the format definitions to create the basic format API. + * + * The format API is listed here, with kdocs, in alphabetical order. The + * functions without bodies are implemented in the format using the pattern: + * static inline FMTpt_XXX(..) {..} + * #define pt_XXX FMTpt_XXX + * + * The routines marked "@pts: Entry to query" operate on the entire contiguous + * entry and can be called with a pts->index pointing to any sub item that makes + * up that entry. + * + * The header order is: + * pt_defs.h + * fmt_XX.h + * pt_common.h + */ +#ifndef __GENERIC_PT_PT_COMMON_H +#define __GENERIC_PT_PT_COMMON_H + +#include "pt_defs.h" +#include "pt_fmt_defaults.h" + +/** + * pt_attr_from_entry() - Convert the permission bits back to attrs + * @pts: Entry to convert from + * @attrs: Resulting attrs + * + * Fill in the attrs with the permission bits encoded in the current leaf entry. + * The attrs should be usable with pt_install_leaf_entry() to reconstruct the + * same entry. + */ +static inline void pt_attr_from_entry(const struct pt_state *pts, + struct pt_write_attrs *attrs); + +/** + * pt_can_have_leaf() - True if the current level can have an OA entry + * @pts: The current level + * + * True if the current level can support pt_install_leaf_entry(). A leaf + * entry produce an OA. + */ +static inline bool pt_can_have_leaf(const struct pt_state *pts); + +/** + * pt_can_have_table() - True if the current level can have a lower table + * @pts: The current level + * + * Every level except 0 is allowed to have a lower table. + */ +static inline bool pt_can_have_table(const struct pt_state *pts) +{ + /* No further tables at level 0 */ + return pts->level > 0; +} + +/** + * pt_clear_entry() - Make entries empty (non-present) + * @pts: Starting table index + * @num_contig_lg2: Number of contiguous items to clear + * + * Clear a run of entries. A cleared entry will load back as PT_ENTRY_EMPTY + * and does not have any effect on table walking. The starting index must be + * aligned to num_contig_lg2. + */ +static inline void pt_clear_entry(struct pt_state *pts, + unsigned int num_contig_lg2); + +/** + * pt_entry_make_write_dirty() - Make an entry dirty + * @pts: Table index to change + * + * Make pt_entry_write_is_dirty() return true for this entry. This can be called + * asynchronously with any other table manipulation under a RCU lock and must + * not corrupt the table. + */ +static inline bool pt_entry_make_write_dirty(struct pt_state *pts); + +/** + * pt_dirty_supported() - True if the page table supports dirty tracking + * @common: Page table to query + */ +static inline bool pt_dirty_supported(struct pt_common *common); + +/** + * pt_entry_num_contig_lg2() - Number of contiguous items for this leaf entry + * @pts: Entry to query + * + * Returns the number of contiguous items this leaf entry spans. If the entry is + * single item it returns ilog2(1). + */ +static inline unsigned int pt_entry_num_contig_lg2(const struct pt_state *pts); + +/** + * pt_entry_oa() - Output Address for this leaf entry + * @pts: Entry to query + * + * Return the output address for the start of the entry. If the entry + * is contigous this returns the same value for each sub-item. Ie:: + * + * log2_mod(pt_entry_oa(), pt_entry_oa_lg2sz()) == 0 + * + * See pt_item_oa(). The format should implement one of these two functions + * depending on how it stores the OA's in the table. + */ +static inline pt_oaddr_t pt_entry_oa(const struct pt_state *pts); + +/** + * pt_entry_oa_lg2sz() - Return the size of a OA entry + * @pts: Entry to query + * + * If the entry is not contigous this returns pt_table_item_lg2sz(), otherwise + * it returns the total VA/OA size of the entire contiguous entry. + */ +static inline unsigned int pt_entry_oa_lg2sz(const struct pt_state *pts) +{ + return pt_entry_num_contig_lg2(pts) + pt_table_item_lg2sz(pts); +} + +/** + * pt_entry_oa_full() - Return the full OA for an entry + * @pts: Entry to query + * + * During iteration the first entry could have a VA with an offset from the + * natural start of the entry. Return the true full OA considering the pts's VA + * offset. + */ +static inline pt_oaddr_t pt_entry_oa_full(const struct pt_state *pts) +{ + return _pt_entry_oa_fast(pts) | + log2_mod(pts->range->va, pt_entry_oa_lg2sz(pts)); +} + +/** + * pt_entry_set_write_clean() - Make the entry write clean + * @pts: Table index to change + * + * Modify the entry so that pt_entry_write_is_dirty() == false. The HW will + * eventually be notified of this change via a TLB flush, which is the point + * that the HW must become synchronized. Any "write dirty" prior to the TLB + * flush can be lost, but once the TLB flush completes all writes must make + * their entries write dirty. + * + * The format should alter the entry in a way that is compatible with any + * concurrent update from HW. The entire contiguous entry is changed. + */ +static inline void pt_entry_set_write_clean(struct pt_state *pts); + +/** + * pt_entry_write_is_dirty() - True if the entry has been written to + * @pts: Entry to query + * + * "write dirty" means that the HW has written to the OA translated + * by this entry. If the entry is contiguous then the consolidated + * "write dirty" for all the items must be returned. + */ +static inline bool pt_entry_write_is_dirty(const struct pt_state *pts); + +/** + * pt_full_va_prefix() - The top bits of the VA + * @common: Page table to query + * + * This is usually 0, but some formats have their VA space going downward from + * PT_VADDR_MAX, and will return that instead. This value must always be + * adjusted by struct pt_common max_vasz_lg2. + */ +static inline pt_vaddr_t pt_full_va_prefix(const struct pt_common *common); + +/** + * pt_has_system_page() - True if level 0 can install a PAGE_SHIFT entry + * @common: Page table to query + * + * If true the caller use at level 0 pt_install_leaf_entry(PAGE_SHIFT). This is + * useful to create optimized paths for common cases of PAGE_SIZE mappings. + */ +static inline bool pt_has_system_page(const struct pt_common *common); + +/** + * pt_install_leaf_entry() - Write a leaf entry to the table + * @pts: Table index to change + * @oa: Output Address for this leaf + * @oasz_lg2: Size in VA for this leaf + * @attrs: Attributes to modify the entry + * + * A leaf OA entry will return PT_ENTRY_OA from pt_load_entry(). It translates + * the VA indicated by pts to the given OA. + * + * For a single item non-contiguous entry oasz_lg2 is pt_table_item_lg2sz(). + * For contiguous it is pt_table_item_lg2sz() + num_contig_lg2. + * + * This must not be called if pt_can_have_leaf() == false. Contigous sizes + * not indicated by pt_possible_sizes() must not be specified. + */ +static inline void pt_install_leaf_entry(struct pt_state *pts, pt_oaddr_t oa, + unsigned int oasz_lg2, + const struct pt_write_attrs *attrs); + +/** + * pt_install_table() - Write a table entry to the table + * @pts: Table index to change + * @table_pa: CPU physical address of the lower table's memory + * @attrs: Attributes to modify the table index + * + * A table entry will return PT_ENTRY_TABLE from pt_load_entry(). The table_pa + * is the table at pts->level - 1. This is done by cmpxchg so pts must have the + * current entry loaded. The pts is updated with the installed entry. + * + * This must not be called if pt_can_have_table() == false. + * + * Returns true if the table was installed successfully. + */ +static inline bool pt_install_table(struct pt_state *pts, pt_oaddr_t table_pa, + const struct pt_write_attrs *attrs); + +/** + * pt_item_oa() - Output Address for this leaf item + * @pts: Item to query + * + * Return the output address for this item. If the item is part of a contiguous + * entry it returns the value of the OA for this individual sub item. + * + * See pt_entry_oa(). The format should implement one of these two functions + * depending on how it stores the OA's in the table. + */ +static inline pt_oaddr_t pt_item_oa(const struct pt_state *pts); + +/** + * pt_load_entry_raw() - Read from the location pts points at into the pts + * @pts: Table index to load + * + * Return the type of entry that was loaded. pts->entry will be filled in with + * the entry's content. See pt_load_entry() + */ +static inline enum pt_entry_type pt_load_entry_raw(struct pt_state *pts); + +/** + * pt_max_output_address_lg2() - Return the maximum OA the table format can hold + * @common: Page table to query + * + * The value oalog2_to_max_int(pt_max_output_address_lg2()) is the MAX for the + * OA. This is the absolute maximum address the table can hold. struct pt_common + * max_oasz_lg2 sets a lower dynamic maximum based on HW capability. + */ +static inline unsigned int +pt_max_output_address_lg2(const struct pt_common *common); + +/** + * pt_num_items_lg2() - Return the number of items in this table level + * @pts: The current level + * + * The number of items in a table level defines the number of bits this level + * decodes from the VA. This function is not called for the top level, + * so it does not need to compute a special value for the top case. The + * result for the top is based on pt_common max_vasz_lg2. + * + * The value is used as part if determining the table indexes via the + * equation:: + * + * log2_mod(log2_div(VA, pt_table_item_lg2sz()), pt_num_items_lg2()) + */ +static inline unsigned int pt_num_items_lg2(const struct pt_state *pts); + +/** + * pt_pgsz_lg2_to_level - Return the level that maps the page size + * @common: Page table to query + * @pgsize_lg2: Log2 page size + * + * Returns the table level that will map the given page size. The page + * size must be part of the pt_possible_sizes() for some level. + */ +static inline unsigned int pt_pgsz_lg2_to_level(struct pt_common *common, + unsigned int pgsize_lg2); + +/** + * pt_possible_sizes() - Return a bitmap of possible output sizes at this level + * @pts: The current level + * + * Each level has a list of possible output sizes that can be installed as + * leaf entries. If pt_can_have_leaf() is false returns zero. + * + * Otherwise the bit in position pt_table_item_lg2sz() should be set indicating + * that a non-contigous singe item leaf entry is supported. The following + * pt_num_items_lg2() number of bits can be set indicating contiguous entries + * are supported. Bit pt_table_item_lg2sz() + pt_num_items_lg2() must not be + * set, contiguous entries cannot span the entire table. + * + * The OR of pt_possible_sizes() of all levels is the typical bitmask of all + * supported sizes in the entire table. + */ +static inline pt_vaddr_t pt_possible_sizes(const struct pt_state *pts); + +/** + * pt_table_item_lg2sz() - Size of a single item entry in this table level + * @pts: The current level + * + * The size of the item specifies how much VA and OA a single item occupies. + * + * See pt_entry_oa_lg2sz() for the same value including the effect of contiguous + * entries. + */ +static inline unsigned int pt_table_item_lg2sz(const struct pt_state *pts); + +/** + * pt_table_oa_lg2sz() - Return the VA/OA size of the entire table + * @pts: The current level + * + * Return the size of VA decoded by the entire table level. + */ +static inline unsigned int pt_table_oa_lg2sz(const struct pt_state *pts) +{ + if (pts->range->top_level == pts->level) + return pts->range->max_vasz_lg2; + return min_t(unsigned int, pts->range->common->max_vasz_lg2, + pt_num_items_lg2(pts) + pt_table_item_lg2sz(pts)); +} + +/** + * pt_table_pa() - Return the CPU physical address of the table entry + * @pts: Entry to query + * + * This is only ever called on PT_ENTRY_TABLE entries. Must return the same + * value passed to pt_install_table(). + */ +static inline pt_oaddr_t pt_table_pa(const struct pt_state *pts); + +/** + * pt_table_ptr() - Return a CPU pointer for a table item + * @pts: Entry to query + * + * Same as pt_table_pa() but returns a CPU pointer. + */ +static inline struct pt_table_p *pt_table_ptr(const struct pt_state *pts) +{ + return __va(pt_table_pa(pts)); +} + +/** + * pt_load_entry() - Read from the location pts points at into the pts + * @pts: Table index to load + * + * Set the type of entry that was loaded. pts->entry and pts->table_lower + * will be filled in with the entry's content. + */ +static inline void pt_load_entry(struct pt_state *pts) +{ + pts->type = pt_load_entry_raw(pts); + if (pts->type == PT_ENTRY_TABLE) + pts->table_lower = pt_table_ptr(pts); +} +#endif diff --git a/drivers/iommu/generic_pt/pt_defs.h b/drivers/iommu/generic_pt/pt_defs.h new file mode 100644 index 00000000000000..3673566708495d --- /dev/null +++ b/drivers/iommu/generic_pt/pt_defs.h @@ -0,0 +1,323 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2024-2025, NVIDIA CORPORATION & AFFILIATES + * + * This header is included before the format. It contains definitions + * that are required to compile the format. The header order is: + * pt_defs.h + * fmt_XX.h + * pt_common.h + */ +#ifndef __GENERIC_PT_DEFS_H +#define __GENERIC_PT_DEFS_H + +#include + +#include +#include +#include +#include +#include +#include +#include "pt_log2.h" + +/* Header self-compile default defines */ +#ifndef pt_write_attrs +typedef u64 pt_vaddr_t; +typedef u64 pt_oaddr_t; +#endif + +struct pt_table_p; + +enum { + PT_VADDR_MAX = sizeof(pt_vaddr_t) == 8 ? U64_MAX : U32_MAX, + PT_VADDR_MAX_LG2 = sizeof(pt_vaddr_t) == 8 ? 64 : 32, + PT_OADDR_MAX = sizeof(pt_oaddr_t) == 8 ? U64_MAX : U32_MAX, + PT_OADDR_MAX_LG2 = sizeof(pt_oaddr_t) == 8 ? 64 : 32, +}; + +/* + * The format instantiation can have features wired off or on to optimize the + * code gen. Supported features are just a reflection of what the current set of + * kernel users want to use. + */ +#ifndef PT_SUPPORTED_FEATURES +#define PT_SUPPORTED_FEATURES 0 +#endif + +/* + * When in debug mode we compile all formats with all features. This allows the + * kunit to test the full matrix. SIGN_EXTEND can't co-exist with DYNAMIC_TOP or + * FULL_VA. + */ +#if IS_ENABLED(CONFIG_DEBUG_GENERIC_PT) +enum { + PT_ORIG_SUPPORTED_FEATURES = PT_SUPPORTED_FEATURES, + PT_DEBUG_SUPPORTED_FEATURES = + UINT_MAX & + ~((PT_ORIG_SUPPORTED_FEATURES & BIT(PT_FEAT_SIGN_EXTEND)) ? + BIT(PT_FEAT_DYNAMIC_TOP) | BIT(PT_FEAT_FULL_VA) : + BIT(PT_FEAT_SIGN_EXTEND)), +}; +#undef PT_SUPPORTED_FEATURES +#define PT_SUPPORTED_FEATURES PT_DEBUG_SUPPORTED_FEATURES +#endif + +#ifndef PT_FORCE_ENABLED_FEATURES +#define PT_FORCE_ENABLED_FEATURES 0 +#endif + +/** + * DOC: Generic Page Table Language + * + * Language used in Generic Page Table + * va + * The input address to the page table, often the virtual address. + * oa + * The output address from the page table, often the physical address. + * leaf + * An entry that results in an output address. Ie a physical memory addr + * start/end + * An open range, eg [0,0) refers to no VA. + * start/last + * An inclusive closed range, eg [0,0] refers to the VA 0 + * common + * The generic page table container struct pt_common + * level + * The number of table hops from the lowest leaf. Level 0 + * is always a table of only leaves of the least significant VA bits. The + * labels used by HW descriptions are never used. + * top_level + * The inclusive highest level of the table. A two level table + * has a top level of 1. + * table + * A linear array of entries representing the translation items for that + * level. + * index + * The position in a table of an element: item = table[index] + * item + * A single position in a table + * entry + * A single logical element in a table. If contiguous pages are not + * supported then item and entry are the same thing, otherwise entry refers + * to the all the items that comprise a single contiguous translation. + * item/entry_size + * The number of bytes of VA the table translates for. + * If the item is a table entry then the next table covers + * this size. If the entry is an output address then the + * full OA is: OA | (VA % entry_size) + * contig_count + * The number of consecutive items fused into a single entry. + * item_size * contig_count is the size of that entry's translation. + * lg2 + * Indicates the value is encoded as log2, ie 1<table)) + +/* + * Try to install a new table pointer. The locking methodology requires this to + * be atomic, multiple threads can race to install a pointer, the losing threads + * will fail the atomic and return false. They should free any memory and + * reparse the table level again. + */ +#if !IS_ENABLED(CONFIG_GENERIC_ATOMIC64) +static inline bool pt_table_install64(struct pt_state *pts, u64 table_entry) +{ + u64 *entryp = pt_cur_table(pts, u64) + pts->index; + u64 old_entry = pts->entry; + bool ret; + + /* + * Ensure the zero'd table content itself is visible before its PTE can + * be. release is a NOP on !SMP, but the HW is still doing an acquire. + */ + if (!IS_ENABLED(CONFIG_SMP)) + dma_wmb(); + ret = try_cmpxchg64_release(entryp, &old_entry, table_entry); + if (ret) + pts->entry = table_entry; + return ret; +} +#endif + +static inline bool pt_table_install32(struct pt_state *pts, u32 table_entry) +{ + u32 *entryp = pt_cur_table(pts, u32) + pts->index; + u32 old_entry = pts->entry; + bool ret; + + /* + * Ensure the zero'd table content itself is visible before its PTE can + * be. release is a NOP on !SMP, but the HW is still doing an acquire. + */ + if (!IS_ENABLED(CONFIG_SMP)) + dma_wmb(); + ret = try_cmpxchg_release(entryp, &old_entry, table_entry); + if (ret) + pts->entry = table_entry; + return ret; +} + +#define PT_SUPPORTED_FEATURE(feature_nr) (PT_SUPPORTED_FEATURES & BIT(feature_nr)) + +static inline bool pt_feature(const struct pt_common *common, + unsigned int feature_nr) +{ + if (PT_FORCE_ENABLED_FEATURES & BIT(feature_nr)) + return true; + if (!PT_SUPPORTED_FEATURE(feature_nr)) + return false; + return common->features & BIT(feature_nr); +} + +static inline bool pts_feature(const struct pt_state *pts, + unsigned int feature_nr) +{ + return pt_feature(pts->range->common, feature_nr); +} + +/* + * PT_WARN_ON is used for invariants that the kunit should be checking can't + * happen. + */ +#if IS_ENABLED(CONFIG_DEBUG_GENERIC_PT) +#define PT_WARN_ON WARN_ON +#else +static inline bool PT_WARN_ON(bool condition) +{ + return false; +} +#endif + +/* These all work on the VA type */ +#define log2_to_int(a_lg2) log2_to_int_t(pt_vaddr_t, a_lg2) +#define log2_to_max_int(a_lg2) log2_to_max_int_t(pt_vaddr_t, a_lg2) +#define log2_div(a, b_lg2) log2_div_t(pt_vaddr_t, a, b_lg2) +#define log2_div_eq(a, b, c_lg2) log2_div_eq_t(pt_vaddr_t, a, b, c_lg2) +#define log2_mod(a, b_lg2) log2_mod_t(pt_vaddr_t, a, b_lg2) +#define log2_mod_eq_max(a, b_lg2) log2_mod_eq_max_t(pt_vaddr_t, a, b_lg2) +#define log2_set_mod(a, val, b_lg2) log2_set_mod_t(pt_vaddr_t, a, val, b_lg2) +#define log2_set_mod_max(a, b_lg2) log2_set_mod_max_t(pt_vaddr_t, a, b_lg2) +#define log2_mul(a, b_lg2) log2_mul_t(pt_vaddr_t, a, b_lg2) +#define log2_ffs(a) log2_ffs_t(pt_vaddr_t, a) +#define log2_fls(a) log2_fls_t(pt_vaddr_t, a) +#define log2_ffz(a) log2_ffz_t(pt_vaddr_t, a) + +/* + * The full va (fva) versions permit the lg2 value to be == PT_VADDR_MAX_LG2 and + * generate a useful defined result. The non fva versions will malfunction at + * this extreme. + */ +static inline pt_vaddr_t fvalog2_div(pt_vaddr_t a, unsigned int b_lg2) +{ + if (PT_SUPPORTED_FEATURE(PT_FEAT_FULL_VA) && b_lg2 == PT_VADDR_MAX_LG2) + return 0; + return log2_div_t(pt_vaddr_t, a, b_lg2); +} + +static inline pt_vaddr_t fvalog2_mod(pt_vaddr_t a, unsigned int b_lg2) +{ + if (PT_SUPPORTED_FEATURE(PT_FEAT_FULL_VA) && b_lg2 == PT_VADDR_MAX_LG2) + return a; + return log2_mod_t(pt_vaddr_t, a, b_lg2); +} + +static inline bool fvalog2_div_eq(pt_vaddr_t a, pt_vaddr_t b, + unsigned int c_lg2) +{ + if (PT_SUPPORTED_FEATURE(PT_FEAT_FULL_VA) && c_lg2 == PT_VADDR_MAX_LG2) + return true; + return log2_div_eq_t(pt_vaddr_t, a, b, c_lg2); +} + +static inline pt_vaddr_t fvalog2_set_mod(pt_vaddr_t a, pt_vaddr_t val, + unsigned int b_lg2) +{ + if (PT_SUPPORTED_FEATURE(PT_FEAT_FULL_VA) && b_lg2 == PT_VADDR_MAX_LG2) + return val; + return log2_set_mod_t(pt_vaddr_t, a, val, b_lg2); +} + +static inline pt_vaddr_t fvalog2_set_mod_max(pt_vaddr_t a, unsigned int b_lg2) +{ + if (PT_SUPPORTED_FEATURE(PT_FEAT_FULL_VA) && b_lg2 == PT_VADDR_MAX_LG2) + return PT_VADDR_MAX; + return log2_set_mod_max_t(pt_vaddr_t, a, b_lg2); +} + +/* These all work on the OA type */ +#define oalog2_to_int(a_lg2) log2_to_int_t(pt_oaddr_t, a_lg2) +#define oalog2_to_max_int(a_lg2) log2_to_max_int_t(pt_oaddr_t, a_lg2) +#define oalog2_div(a, b_lg2) log2_div_t(pt_oaddr_t, a, b_lg2) +#define oalog2_div_eq(a, b, c_lg2) log2_div_eq_t(pt_oaddr_t, a, b, c_lg2) +#define oalog2_mod(a, b_lg2) log2_mod_t(pt_oaddr_t, a, b_lg2) +#define oalog2_mod_eq_max(a, b_lg2) log2_mod_eq_max_t(pt_oaddr_t, a, b_lg2) +#define oalog2_set_mod(a, val, b_lg2) log2_set_mod_t(pt_oaddr_t, a, val, b_lg2) +#define oalog2_set_mod_max(a, b_lg2) log2_set_mod_max_t(pt_oaddr_t, a, b_lg2) +#define oalog2_mul(a, b_lg2) log2_mul_t(pt_oaddr_t, a, b_lg2) +#define oalog2_ffs(a) log2_ffs_t(pt_oaddr_t, a) +#define oalog2_fls(a) log2_fls_t(pt_oaddr_t, a) +#define oalog2_ffz(a) log2_ffz_t(pt_oaddr_t, a) + +static inline uintptr_t _pt_top_set(struct pt_table_p *table_mem, + unsigned int top_level) +{ + return top_level | (uintptr_t)table_mem; +} + +static inline void pt_top_set(struct pt_common *common, + struct pt_table_p *table_mem, + unsigned int top_level) +{ + WRITE_ONCE(common->top_of_table, _pt_top_set(table_mem, top_level)); +} + +static inline void pt_top_set_level(struct pt_common *common, + unsigned int top_level) +{ + pt_top_set(common, NULL, top_level); +} + +static inline unsigned int pt_top_get_level(const struct pt_common *common) +{ + return READ_ONCE(common->top_of_table) % (1 << PT_TOP_LEVEL_BITS); +} + +#endif diff --git a/drivers/iommu/generic_pt/pt_fmt_defaults.h b/drivers/iommu/generic_pt/pt_fmt_defaults.h new file mode 100644 index 00000000000000..8738008d024b0b --- /dev/null +++ b/drivers/iommu/generic_pt/pt_fmt_defaults.h @@ -0,0 +1,193 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2024-2025, NVIDIA CORPORATION & AFFILIATES + * + * Default definitions for formats that don't define these functions. + */ +#ifndef __GENERIC_PT_PT_FMT_DEFAULTS_H +#define __GENERIC_PT_PT_FMT_DEFAULTS_H + +#include "pt_defs.h" +#include + +/* Header self-compile default defines */ +#ifndef pt_load_entry_raw +#include "fmt/amdv1.h" +#endif + +/* + * The format must provide PT_GRANULE_LG2SZ, PT_TABLEMEM_LG2SZ, and + * PT_ITEM_WORD_SIZE. The must be the same at every level excluding the top. + */ +#ifndef pt_table_item_lg2sz +static inline unsigned int pt_table_item_lg2sz(const struct pt_state *pts) +{ + return PT_GRANULE_LG2SZ + + (PT_TABLEMEM_LG2SZ - ilog2(PT_ITEM_WORD_SIZE)) * pts->level; +} +#endif + +#ifndef pt_pgsz_lg2_to_level +static inline unsigned int pt_pgsz_lg2_to_level(struct pt_common *common, + unsigned int pgsize_lg2) +{ + return (pgsize_lg2 - PT_GRANULE_LG2SZ) / + (PT_TABLEMEM_LG2SZ - ilog2(PT_ITEM_WORD_SIZE)); + return 0; +} +#endif + +/* If not supplied by the format then contiguous pages are not supported */ +#ifndef pt_entry_num_contig_lg2 +static inline unsigned int pt_entry_num_contig_lg2(const struct pt_state *pts) +{ + return ilog2(1); +} + +static inline unsigned short pt_contig_count_lg2(const struct pt_state *pts) +{ + return ilog2(1); +} +#endif + +/* If not supplied by the format then dirty tracking is not supported */ +#ifndef pt_entry_write_is_dirty +static inline bool pt_entry_write_is_dirty(const struct pt_state *pts) +{ + return false; +} + +static inline void pt_entry_set_write_clean(struct pt_state *pts) +{ +} + +static inline bool pt_dirty_supported(struct pt_common *common) +{ + return true; +} +#else +/* If not supplied then dirty tracking is always enabled */ +#ifndef pt_dirty_supported +static inline bool pt_dirty_supported(struct pt_common *common) +{ + return true; +} +#endif +#endif + +#ifndef pt_entry_make_write_dirty +static inline bool pt_entry_make_write_dirty(struct pt_state *pts) +{ + return false; +} +#endif + +/* + * Format supplies either: + * pt_entry_oa - OA is at the start of a contiguous entry + * or + * pt_item_oa - OA is correct for every item in a contiguous entry + * + * Build the missing one + */ +#ifdef pt_entry_oa +static inline pt_oaddr_t pt_item_oa(const struct pt_state *pts) +{ + return pt_entry_oa(pts) | + log2_mul(pts->index, pt_table_item_lg2sz(pts)); +} +#define _pt_entry_oa_fast pt_entry_oa +#endif + +#ifdef pt_item_oa +static inline pt_oaddr_t pt_entry_oa(const struct pt_state *pts) +{ + return log2_set_mod(pt_item_oa(pts), 0, + pt_entry_num_contig_lg2(pts) + + pt_table_item_lg2sz(pts)); +} +#define _pt_entry_oa_fast pt_item_oa +#endif + +/* + * If not supplied by the format then use the constant + * PT_MAX_OUTPUT_ADDRESS_LG2. + */ +#ifndef pt_max_output_address_lg2 +static inline unsigned int +pt_max_output_address_lg2(const struct pt_common *common) +{ + return PT_MAX_OUTPUT_ADDRESS_LG2; +} +#endif + +#ifndef pt_has_system_page +static inline bool pt_has_system_page(const struct pt_common *common) +{ + return PT_GRANULE_LG2SZ == PAGE_SHIFT; +} +#endif + +/* + * If not supplied by the format then assume only one contiguous size determined + * by pt_contig_count_lg2() + */ +#ifndef pt_possible_sizes +static inline unsigned short pt_contig_count_lg2(const struct pt_state *pts); + +/* Return a bitmap of possible leaf page sizes at this level */ +static inline pt_vaddr_t pt_possible_sizes(const struct pt_state *pts) +{ + unsigned int isz_lg2 = pt_table_item_lg2sz(pts); + + if (!pt_can_have_leaf(pts)) + return 0; + return log2_to_int(isz_lg2) | + log2_to_int(pt_contig_count_lg2(pts) + isz_lg2); +} +#endif + +/* If not supplied by the format then use 0. */ +#ifndef pt_full_va_prefix +static inline pt_vaddr_t pt_full_va_prefix(const struct pt_common *common) +{ + return 0; +} +#endif + +/* If not supplied by the format then zero fill using PT_ITEM_WORD_SIZE */ +#ifndef pt_clear_entry +static inline void pt_clear_entry64(struct pt_state *pts, + unsigned int num_contig_lg2) +{ + u64 *tablep = pt_cur_table(pts, u64) + pts->index; + u64 *end = tablep + log2_to_int(num_contig_lg2); + + PT_WARN_ON(log2_mod(pts->index, num_contig_lg2)); + for (; tablep != end; tablep++) + WRITE_ONCE(*tablep, 0); +} + +static inline void pt_clear_entry32(struct pt_state *pts, + unsigned int num_contig_lg2) +{ + u32 *tablep = pt_cur_table(pts, u32) + pts->index; + u32 *end = tablep + log2_to_int(num_contig_lg2); + + PT_WARN_ON(log2_mod(pts->index, num_contig_lg2)); + for (; tablep != end; tablep++) + WRITE_ONCE(*tablep, 0); +} + +static inline void pt_clear_entry(struct pt_state *pts, + unsigned int num_contig_lg2) +{ + if (PT_ITEM_WORD_SIZE == sizeof(u32)) + pt_clear_entry32(pts, num_contig_lg2); + else + pt_clear_entry64(pts, num_contig_lg2); +} +#define pt_clear_entry pt_clear_entry +#endif + +#endif diff --git a/drivers/iommu/generic_pt/pt_iter.h b/drivers/iommu/generic_pt/pt_iter.h new file mode 100644 index 00000000000000..687bb1eb88f950 --- /dev/null +++ b/drivers/iommu/generic_pt/pt_iter.h @@ -0,0 +1,640 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2024-2025, NVIDIA CORPORATION & AFFILIATES + * + * Iterators for Generic Page Table + */ +#ifndef __GENERIC_PT_PT_ITER_H +#define __GENERIC_PT_PT_ITER_H + +#include "pt_common.h" + +#include + +/* + * Use to mangle symbols so that backtraces and the symbol table are + * understandable. Any non-inlined function should get mangled like this. + */ +#define NS(fn) CONCATENATE(PTPFX, fn) + +/** + * pt_check_range() - Validate the range can be iterated + * @range: Range to validate + * + * Check that va and last_va fall within the permitted range of VAs. If the + * format is using PT_FEAT_SIGN_EXTEND then this also checks the sign extension + * is correct. + */ +static inline int pt_check_range(struct pt_range *range) +{ + pt_vaddr_t prefix; + + PT_WARN_ON(!range->max_vasz_lg2); + + if (pt_feature(range->common, PT_FEAT_SIGN_EXTEND)) { + PT_WARN_ON(range->common->max_vasz_lg2 != range->max_vasz_lg2); + prefix = fvalog2_div(range->va, range->max_vasz_lg2 - 1) ? + PT_VADDR_MAX : + 0; + } else { + prefix = pt_full_va_prefix(range->common); + } + + if (!fvalog2_div_eq(range->va, prefix, range->max_vasz_lg2) || + !fvalog2_div_eq(range->last_va, prefix, range->max_vasz_lg2)) + return -ERANGE; + return 0; +} + +/** + * pt_index_to_va() - Update range->va to the current pts->index + * @pts: Iteration State + * + * Adjust range->va to match the current index. This is done in a lazy manner + * since computing the VA takes several instructions and is rarely required. + */ +static inline void pt_index_to_va(struct pt_state *pts) +{ + pt_vaddr_t lower_va; + + lower_va = log2_mul(pts->index, pt_table_item_lg2sz(pts)); + pts->range->va = fvalog2_set_mod(pts->range->va, lower_va, + pt_table_oa_lg2sz(pts)); +} + +/* + * Add index_count_lg2 number of entries to pts's VA and index. The va will be + * adjusted to the end of the contiguous block if it is currently in the middle. + */ +static inline void _pt_advance(struct pt_state *pts, + unsigned int index_count_lg2) +{ + pts->index = log2_set_mod(pts->index + log2_to_int(index_count_lg2), 0, + index_count_lg2); +} + +/** + * pt_item_fully_covered() - Check if the item or entry is entirely contained + * within pts->range + * @pts: Iteration State + * @oasz_lg2: The size of the item to check, pt_table_item_lg2sz() or + * pt_entry_oa_lg2sz() + * + * True if the item is fully enclosed by the pts->range. + */ +static inline bool pt_item_fully_covered(const struct pt_state *pts, + unsigned int oasz_lg2) +{ + struct pt_range *range = pts->range; + + /* Range begins at the start of the entry */ + if (log2_mod(pts->range->va, oasz_lg2)) + return false; + + /* Range ends past the end of the entry */ + if (!log2_div_eq(range->va, range->last_va, oasz_lg2)) + return true; + + /* Range ends at the end of the entry */ + return log2_mod_eq_max(range->last_va, oasz_lg2); +} + +/** + * pt_range_to_index() - Starting index for an iteration + * @pts: Iteration State + * + * Return the starting index for the iteration in pts. + */ +static inline unsigned int pt_range_to_index(const struct pt_state *pts) +{ + unsigned int isz_lg2 = pt_table_item_lg2sz(pts); + + PT_WARN_ON(pts->level > pts->range->top_level); + if (pts->range->top_level == pts->level) + return log2_div(fvalog2_mod(pts->range->va, + pts->range->max_vasz_lg2), + isz_lg2); + return log2_mod(log2_div(pts->range->va, isz_lg2), + pt_num_items_lg2(pts)); +} + +/** + * pt_range_to_end_index() - Ending index iteration + * @pts: Iteration State + * + * Return the last index for the iteration in pts. + */ +static inline unsigned int pt_range_to_end_index(const struct pt_state *pts) +{ + unsigned int isz_lg2 = pt_table_item_lg2sz(pts); + struct pt_range *range = pts->range; + unsigned int num_entries_lg2; + + if (range->va == range->last_va) + return pts->index + 1; + + if (pts->range->top_level == pts->level) + return log2_div(fvalog2_mod(pts->range->last_va, + pts->range->max_vasz_lg2), + isz_lg2) + + 1; + + num_entries_lg2 = pt_num_items_lg2(pts); + + /* last_va falls within this table */ + if (log2_div_eq(range->va, range->last_va, num_entries_lg2 + isz_lg2)) + return log2_mod(log2_div(pts->range->last_va, isz_lg2), + num_entries_lg2) + + 1; + + return log2_to_int(num_entries_lg2); +} + +static inline void _pt_iter_first(struct pt_state *pts) +{ + pts->index = pt_range_to_index(pts); + pts->end_index = pt_range_to_end_index(pts); + PT_WARN_ON(pts->index > pts->end_index); +} + +static inline bool _pt_iter_load(struct pt_state *pts) +{ + if (pts->index >= pts->end_index) + return false; + pt_load_entry(pts); + return true; +} + +/** + * pt_next_entry() - Advance pts to the next entry + * @pts: Iteration State + * + * Update pts to go to the next index at this level. If pts is pointing at a + * contiguous entry then the index may advance my more than one. + */ +static inline void pt_next_entry(struct pt_state *pts) +{ + if (pts->type == PT_ENTRY_OA && + !__builtin_constant_p(pt_entry_num_contig_lg2(pts) == 0)) + _pt_advance(pts, pt_entry_num_contig_lg2(pts)); + else + pts->index++; + pt_index_to_va(pts); +} + +/** + * for_each_pt_level_entry() - For loop wrapper over entries in the range + * @pts: Iteration State + * + * This is the basic iteration primitive, it iterates over all the entries in + * pts->range that fall within the pts's current table level. Each step does + * pt_load_entry(pts). + */ +#define for_each_pt_level_entry(pts) \ + for (_pt_iter_first(pts); _pt_iter_load(pts); pt_next_entry(pts)) + +/** + * pt_load_single_entry() - Version of pt_load_entry() usable within a walker + * @pts: Iteration State + * + * Alternative to for_each_pt_level_entry() if the walker function uses only a + * single entry. + */ +static inline enum pt_entry_type pt_load_single_entry(struct pt_state *pts) +{ + pts->index = pt_range_to_index(pts); + pt_load_entry(pts); + return pts->type; +} + +static __always_inline struct pt_range _pt_top_range(struct pt_common *common, + uintptr_t top_of_table) +{ + struct pt_range range = { + .common = common, + .top_table = + (struct pt_table_p *)(top_of_table & + ~(uintptr_t)PT_TOP_LEVEL_MASK), +#ifdef PT_FIXED_TOP_LEVEL + .top_level = PT_FIXED_TOP_LEVEL, +#else + .top_level = top_of_table % (1 << PT_TOP_LEVEL_BITS), +#endif + }; + struct pt_state pts = { .range = &range, .level = range.top_level }; + unsigned int max_vasz_lg2; + + max_vasz_lg2 = common->max_vasz_lg2; + if (pt_feature(common, PT_FEAT_DYNAMIC_TOP) && + pts.level != PT_MAX_TOP_LEVEL) + max_vasz_lg2 = min_t(unsigned int, common->max_vasz_lg2, + pt_num_items_lg2(&pts) + + pt_table_item_lg2sz(&pts)); + + /* + * The top range will default to the lower region only with sign extend. + */ + range.max_vasz_lg2 = max_vasz_lg2; + if (pt_feature(common, PT_FEAT_SIGN_EXTEND)) + max_vasz_lg2--; + + range.va = fvalog2_set_mod(pt_full_va_prefix(common), 0, max_vasz_lg2); + range.last_va = + fvalog2_set_mod_max(pt_full_va_prefix(common), max_vasz_lg2); + return range; +} + +/** + * pt_top_range() - Return a range that spans part of the top level + * @common: Table + * + * For PT_FEAT_SIGN_EXTEND this will return the lower range, and cover half the + * total page table. Otherwise it returns the entire page table. + */ +static __always_inline struct pt_range pt_top_range(struct pt_common *common) +{ + /* + * The top pointer can change without locking. We capture the value and + * it's level here and are safe to walk it so long as both values are + * captured without tearing. + */ + return _pt_top_range(common, READ_ONCE(common->top_of_table)); +} + +/** + * pt_all_range() - Return a range that spans the entire page table + * @common: Table + * + * The returned range spans the whole page table. Due to how PT_FEAT_SIGN_EXTEND + * is supported range->va and range->last_va will be incorrect during the + * iteration and must not be accessed. + */ +static inline struct pt_range pt_all_range(struct pt_common *common) +{ + struct pt_range range = pt_top_range(common); + + if (!pt_feature(common, PT_FEAT_SIGN_EXTEND)) + return range; + + /* + * Pretend the table is linear from 0 without a sign extension. This + * generates the correct indexes for iteration. + */ + range.last_va = fvalog2_set_mod_max(0, range.max_vasz_lg2); + return range; +} + +/** + * pt_upper_range() - Return a range that spans part of the top level + * @common: Table + * + * For PT_FEAT_SIGN_EXTEND this will return the upper range, and cover half the + * total page table. Otherwise it returns the entire page table. + */ +static inline struct pt_range pt_upper_range(struct pt_common *common) +{ + struct pt_range range = pt_top_range(common); + + if (!pt_feature(common, PT_FEAT_SIGN_EXTEND)) + return range; + + range.va = fvalog2_set_mod(PT_VADDR_MAX, 0, range.max_vasz_lg2 - 1); + range.last_va = PT_VADDR_MAX; + return range; +} + +/** + * pt_make_range() - Return a range that spans part of the table + * @common: Table + * @va: Start address + * @last_va: Last address + * + * The caller must validate the range with pt_check_range() before using it. + */ +static __always_inline struct pt_range +pt_make_range(struct pt_common *common, pt_vaddr_t va, pt_vaddr_t last_va) +{ + struct pt_range range = + _pt_top_range(common, READ_ONCE(common->top_of_table)); + + range.va = va; + range.last_va = last_va; + + return range; +} + +/* + * Span a slice of the table starting at a lower table level from an active + * walk. + */ +static __always_inline struct pt_range +pt_make_child_range(const struct pt_range *parent, pt_vaddr_t va, + pt_vaddr_t last_va) +{ + struct pt_range range = *parent; + + range.va = va; + range.last_va = last_va; + + PT_WARN_ON(last_va < va); + PT_WARN_ON(pt_check_range(&range)); + + return range; +} + +/** + * pt_init() - Initialize a pt_state on the stack + * @range: Range pointer to embed in the state + * @level: Table level for the state + * @table: Pointer to the table memory at level + * + * Helper to initialize the on-stack pt_state from walker arguments. + */ +static __always_inline struct pt_state +pt_init(struct pt_range *range, unsigned int level, struct pt_table_p *table) +{ + struct pt_state pts = { + .range = range, + .table = table, + .level = level, + }; + return pts; +} + +/** + * pt_init_top() - Initialize a pt_state on the stack + * @range: Range pointer to embed in the state + * + * The pt_state points to the top most level. + */ +static __always_inline struct pt_state pt_init_top(struct pt_range *range) +{ + return pt_init(range, range->top_level, range->top_table); +} + +typedef int (*pt_level_fn_t)(struct pt_range *range, void *arg, + unsigned int level, struct pt_table_p *table); + +/** + * pt_descend() - Recursively invoke the walker for the lower level + * @pts: Iteration State + * @arg: Value to pass to the function + * @fn: Walker function to call + * + * pts must point to a table item. Invoke fn as a walker on the table + * pts points to. + */ +static __always_inline int pt_descend(struct pt_state *pts, void *arg, + pt_level_fn_t fn) +{ + int ret; + + if (PT_WARN_ON(!pts->table_lower)) + return -EINVAL; + + ret = (*fn)(pts->range, arg, pts->level - 1, pts->table_lower); + return ret; +} + +/** + * pt_walk_range() - Walk over a VA range + * @range: Range pointer + * @fn: Walker function to call + * @arg: Value to pass to the function + * + * Walk over a VA range. The caller should have done a validity check, at + * least calling pt_check_range(), when building range. The walk will + * start at the top most table. + */ +static __always_inline int pt_walk_range(struct pt_range *range, + pt_level_fn_t fn, void *arg) +{ + return fn(range, arg, range->top_level, range->top_table); +} + +/* + * pt_walk_descend() - Recursively invoke the walker for a slice of a lower + * level + * @pts: Iteration State + * @va: Start address + * @last_va: Last address + * @fn: Walker function to call + * @arg: Value to pass to the function + * + * With pts pointing at a table item this will descend and over a slice of the + * lower table. The caller must ensure that va/last_va are within the table + * item. This creates a new walk and does not alter pts or pts->range. + */ +static __always_inline int pt_walk_descend(const struct pt_state *pts, + pt_vaddr_t va, pt_vaddr_t last_va, + pt_level_fn_t fn, void *arg) +{ + struct pt_range range = pt_make_child_range(pts->range, va, last_va); + + if (PT_WARN_ON(!pt_can_have_table(pts)) || + PT_WARN_ON(!pts->table_lower)) + return -EINVAL; + + return fn(&range, arg, pts->level - 1, pts->table_lower); +} + +/* + * pt_walk_descend_all() - Recursively invoke the walker for a table item + * @pts: Iteration State + * @fn: Walker function to call + * @arg: Value to pass to the function + * + * With pts pointing at a table item this will descend and over the entire lower + * table. This creates a new walk and does not alter pts or pts->range. + */ +static __always_inline int +pt_walk_descend_all(const struct pt_state *parent_pts, pt_level_fn_t fn, + void *arg) +{ + unsigned int isz_lg2 = pt_table_item_lg2sz(parent_pts); + + return pt_walk_descend(parent_pts, + log2_set_mod(parent_pts->range->va, 0, isz_lg2), + log2_set_mod_max(parent_pts->range->va, isz_lg2), + fn, arg); +} + +/** + * pt_range_slice() - Return a range that spans indexes + * @pts: Iteration State + * @start_index: Starting index within pts + * @end_index: Ending index within pts + * + * Create a range than spans an index range of the current table level + * pt_state points at. + */ +static inline struct pt_range pt_range_slice(const struct pt_state *pts, + unsigned int start_index, + unsigned int end_index) +{ + unsigned int table_lg2sz = pt_table_oa_lg2sz(pts); + pt_vaddr_t last_va; + pt_vaddr_t va; + + va = fvalog2_set_mod(pts->range->va, + log2_mul(start_index, pt_table_item_lg2sz(pts)), + table_lg2sz); + last_va = fvalog2_set_mod( + pts->range->va, + log2_mul(end_index, pt_table_item_lg2sz(pts)) - 1, table_lg2sz); + return pt_make_child_range(pts->range, va, last_va); +} + +/** + * pt_top_memsize_lg2() + * @common: Table + * @top_of_table: Top of table value from _pt_top_set() + * + * Compute the allocation size of the top table. For PT_FEAT_DYNAMIC_TOP this + * will compute the top size assuming the table will grow. + */ +static inline unsigned int pt_top_memsize_lg2(struct pt_common *common, + uintptr_t top_of_table) +{ + struct pt_range range = _pt_top_range(common, top_of_table); + struct pt_state pts = pt_init_top(&range); + unsigned int num_items_lg2; + + num_items_lg2 = common->max_vasz_lg2 - pt_table_item_lg2sz(&pts); + if (range.top_level != PT_MAX_TOP_LEVEL && + pt_feature(common, PT_FEAT_DYNAMIC_TOP)) + num_items_lg2 = min(num_items_lg2, pt_num_items_lg2(&pts)); + + /* Round up the allocation size to the minimum alignment */ + return max(log2_ffs_t(u64, PT_TOP_PHYS_MASK), + num_items_lg2 + ilog2(PT_ITEM_WORD_SIZE)); +} + +/** + * pt_compute_best_pgsize() - Determine the best page size for leaf entries + * @pgsz_bitmap: Permitted page sizes + * @va: Starting virtual address for the leaf entry + * @last_va: Last virtual address for the leaf entry, sets the max page size + * @oa: Starting output address for the leaf entry + * + * Compute the largest page size for va, last_va, and oa together and return it + * in lg2. The largest page size depends on the format's supported page sizes at + * this level, and the relative alignment of the VA and OA addresses. 0 means + * the OA cannot be stored with the provided pgsz_bitmap. + */ +static inline unsigned int pt_compute_best_pgsize(pt_vaddr_t pgsz_bitmap, + pt_vaddr_t va, + pt_vaddr_t last_va, + pt_oaddr_t oa) +{ + unsigned int best_pgsz_lg2; + unsigned int pgsz_lg2; + pt_vaddr_t len = last_va - va + 1; + pt_vaddr_t mask; + + if (PT_WARN_ON(va >= last_va)) + return 0; + + /* + * Given a VA/OA pair the best page size is the largest page side + * where: + * + * 1) VA and OA start at the page. Bitwise this is the count of least + * significant 0 bits. + * This also implies that last_va/oa has the same prefix as va/oa. + */ + mask = va | oa; + + /* + * 2) The page size is not larger than the last_va (length). Since page + * sizes are always power of two this can't be larger than the + * largest power of two factor of the length. + */ + mask |= log2_to_int(log2_fls(len) - 1); + + best_pgsz_lg2 = log2_ffs(mask); + + /* Choose the higest bit <= best_pgsz_lg2 */ + if (best_pgsz_lg2 < PT_VADDR_MAX_LG2 - 1) + pgsz_bitmap = log2_mod(pgsz_bitmap, best_pgsz_lg2 + 1); + + pgsz_lg2 = log2_fls(pgsz_bitmap); + if (!pgsz_lg2) + return 0; + + pgsz_lg2--; + + PT_WARN_ON(log2_mod(va, pgsz_lg2) != 0); + PT_WARN_ON(oalog2_mod(oa, pgsz_lg2) != 0); + PT_WARN_ON(va + log2_to_int(pgsz_lg2) - 1 > last_va); + PT_WARN_ON(!log2_div_eq(va, va + log2_to_int(pgsz_lg2) - 1, pgsz_lg2)); + PT_WARN_ON( + !oalog2_div_eq(oa, oa + log2_to_int(pgsz_lg2) - 1, pgsz_lg2)); + return pgsz_lg2; +} + +#define _PT_MAKE_CALL_LEVEL(fn) \ + static __always_inline int fn(struct pt_range *range, void *arg, \ + unsigned int level, \ + struct pt_table_p *table) \ + { \ + static_assert(PT_MAX_TOP_LEVEL <= 5); \ + if (level == 0) \ + return CONCATENATE(fn, 0)(range, arg, 0, table); \ + if (level == 1 || PT_MAX_TOP_LEVEL == 1) \ + return CONCATENATE(fn, 1)(range, arg, 1, table); \ + if (level == 2 || PT_MAX_TOP_LEVEL == 2) \ + return CONCATENATE(fn, 2)(range, arg, 2, table); \ + if (level == 3 || PT_MAX_TOP_LEVEL == 3) \ + return CONCATENATE(fn, 3)(range, arg, 3, table); \ + if (level == 4 || PT_MAX_TOP_LEVEL == 4) \ + return CONCATENATE(fn, 4)(range, arg, 4, table); \ + return CONCATENATE(fn, 5)(range, arg, 5, table); \ + } + +static inline int __pt_make_level_fn_err(struct pt_range *range, void *arg, + unsigned int unused_level, + struct pt_table_p *table) +{ + static_assert(PT_MAX_TOP_LEVEL <= 5); + return -EPROTOTYPE; +} + +#define __PT_MAKE_LEVEL_FN(fn, level, descend_fn, do_fn) \ + static inline int fn(struct pt_range *range, void *arg, \ + unsigned int unused_level, \ + struct pt_table_p *table) \ + { \ + return do_fn(range, arg, level, table, descend_fn); \ + } + +/** + * PT_MAKE_LEVELS() - Build an unwound walker + * @fn: Name of the walker function + * @do_fn: Function to call at each level + * + * This builds a function call tree that can be fully inlined, + * The caller must provide a function body in an __always_inline function:: + * + * static __always_inline int do(struct pt_range *range, void *arg, + * unsigned int level, struct pt_table_p *table, + * pt_level_fn_t descend_fn) + * + * An inline function will be created for each table level that calls do_fn with + * a compile time constant for level and a pointer to the next lower function. + * This generates an optimally inlined walk where each of the functions sees a + * constant level and can codegen the exact constants/etc for that level. + * + * Note this can produce a lot of code! + */ +#define PT_MAKE_LEVELS(fn, do_fn) \ + __PT_MAKE_LEVEL_FN(CONCATENATE(fn, 0), 0, __pt_make_level_fn_err, \ + do_fn); \ + __PT_MAKE_LEVEL_FN(CONCATENATE(fn, 1), 1, CONCATENATE(fn, 0), do_fn); \ + __PT_MAKE_LEVEL_FN(CONCATENATE(fn, 2), 2, CONCATENATE(fn, 1), do_fn); \ + __PT_MAKE_LEVEL_FN(CONCATENATE(fn, 3), 3, CONCATENATE(fn, 2), do_fn); \ + __PT_MAKE_LEVEL_FN(CONCATENATE(fn, 4), 4, CONCATENATE(fn, 3), do_fn); \ + __PT_MAKE_LEVEL_FN(CONCATENATE(fn, 5), 5, CONCATENATE(fn, 4), do_fn); \ + _PT_MAKE_CALL_LEVEL(fn) + +#endif diff --git a/drivers/iommu/generic_pt/pt_log2.h b/drivers/iommu/generic_pt/pt_log2.h new file mode 100644 index 00000000000000..70d40fbfd7a7b6 --- /dev/null +++ b/drivers/iommu/generic_pt/pt_log2.h @@ -0,0 +1,130 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2024-2025, NVIDIA CORPORATION & AFFILIATES + * + * Helper macros for working with log2 values + * + */ +#ifndef __GENERIC_PT_LOG2_H +#define __GENERIC_PT_LOG2_H +#include +#include + +/* Compute a */ +#define log2_to_int_t(type, a_lg2) ((type)(((type)1) << (a_lg2))) +static_assert(log2_to_int_t(unsigned int, 0) == 1); + +/* Compute a - 1 (aka all low bits set) */ +#define log2_to_max_int_t(type, a_lg2) ((type)(log2_to_int_t(type, a_lg2) - 1)) + +/* Compute a / b */ +#define log2_div_t(type, a, b_lg2) ((type)(((type)a) >> (b_lg2))) +static_assert(log2_div_t(unsigned int, 4, 2) == 1); + +/* + * Compute: + * a / c == b / c + * aka the high bits are equal + */ +#define log2_div_eq_t(type, a, b, c_lg2) \ + (log2_div_t(type, (a) ^ (b), c_lg2) == 0) +static_assert(log2_div_eq_t(unsigned int, 1, 1, 2)); + +/* Compute a % b */ +#define log2_mod_t(type, a, b_lg2) \ + ((type)(((type)a) & log2_to_max_int_t(type, b_lg2))) +static_assert(log2_mod_t(unsigned int, 1, 2) == 1); + +/* + * Compute: + * a % b == b - 1 + * aka the low bits are all 1s + */ +#define log2_mod_eq_max_t(type, a, b_lg2) \ + (log2_mod_t(type, a, b_lg2) == log2_to_max_int_t(type, b_lg2)) +static_assert(log2_mod_eq_max_t(unsigned int, 3, 2)); + +/* + * Return a value such that: + * a / b == ret / b + * ret % b == val + * aka set the low bits to val. val must be < b + */ +#define log2_set_mod_t(type, a, val, b_lg2) \ + ((((type)(a)) & (~log2_to_max_int_t(type, b_lg2))) | ((type)(val))) +static_assert(log2_set_mod_t(unsigned int, 3, 1, 2) == 1); + +/* Return a value such that: + * a / b == ret / b + * ret % b == b - 1 + * aka set the low bits to all 1s + */ +#define log2_set_mod_max_t(type, a, b_lg2) \ + (((type)(a)) | log2_to_max_int_t(type, b_lg2)) +static_assert(log2_set_mod_max_t(unsigned int, 2, 2) == 3); + +/* Compute a * b */ +#define log2_mul_t(type, a, b_lg2) ((type)(((type)a) << (b_lg2))) +static_assert(log2_mul_t(unsigned int, 2, 2) == 8); + +#define _dispatch_sz(type, fn, a) \ + (sizeof(type) == 4 ? fn##32((u32)a) : fn##64(a)) + +/* + * Return the highest value such that: + * log2_fls(0) == 0 + * log2_fls(1) == 1 + * a >= log2_to_int(ret - 1) + * aka find last set bit + */ +static inline unsigned int log2_fls32(u32 a) +{ + return fls(a); +} +static inline unsigned int log2_fls64(u64 a) +{ + return fls64(a); +} +#define log2_fls_t(type, a) _dispatch_sz(type, log2_fls, a) + +/* + * Return the highest value such that: + * log2_ffs(0) == UNDEFINED + * log2_ffs(1) == 0 + * log_mod(a, ret) == 0 + * aka find first set bit + */ +static inline unsigned int log2_ffs32(u32 a) +{ + return __ffs(a); +} +static inline unsigned int log2_ffs64(u64 a) +{ + return __ffs64(a); +} +#define log2_ffs_t(type, a) _dispatch_sz(type, log2_ffs, a) + +/* + * Return the highest value such that: + * log2_ffz(MAX) == UNDEFINED + * log2_ffz(0) == 0 + * log2_ffz(1) == 1 + * log_mod(a, ret) == log_to_max_int(ret) + * aka find first zero bit + */ +static inline unsigned int log2_ffz32(u32 a) +{ + return ffz(a); +} +static inline unsigned int log2_ffz64(u64 a) +{ + if (sizeof(u64) == sizeof(unsigned long)) + return ffz(a); + + if ((u32)a == U32_MAX) + return log2_ffz32(a >> 32) + 32; + return log2_ffz32(a); +} +#define log2_ffz_t(type, a) _dispatch_sz(type, log2_ffz, a) + +#endif diff --git a/include/linux/generic_pt/common.h b/include/linux/generic_pt/common.h new file mode 100644 index 00000000000000..91869fad33fbdf --- /dev/null +++ b/include/linux/generic_pt/common.h @@ -0,0 +1,134 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2024-2025, NVIDIA CORPORATION & AFFILIATES + */ +#ifndef __GENERIC_PT_COMMON_H +#define __GENERIC_PT_COMMON_H + +#include +#include +#include + +/** + * DOC: Generic Radix Page Table + * + * Generic Radix Page Table is a set of functions and helpers to efficiently + * parse radix style page tables typically seen in HW implementations. The + * interface is built to deliver similar code generation as the mm's pte/pmd/etc + * system by fully inlining the exact code required to handle each table level. + * + * Like the MM each format contributes its parsing implementation under common + * names and the common code implements the required algorithms. + * + * The system is divided into three logical levels: + * + * - The page table format and its manipulation functions + * - Generic helpers to give a consistent API regardless of underlying format + * - An algorithm implementation (eg IOMMU/DRM/KVM/MM) + * + * Multiple implementations are supported, the intention is to have the generic + * format code be re-usable for whatever specalized implementation is required. + * The generic code is solely about the format of the radix tree, it does not + * include memory allocation or higher level decisions that are left for the + * implementation. + * + * The generic framework supports a superset of functions across many HW + * implementations: + * + * - Entries comprised of contiguous blocks of IO PTEs for larger page sizes + * - Multi-level tables, up to 6 levels. Runtime selected top level + * - Runtime variable table level size (ARM's concatenated tables) + * - Expandable top level allowing dynamic sizing of table levels + * - Optional leaf entries at any level + * - 32 bit/64 bit virtual and output addresses, using every address bit + * - Dirty tracking + */ + +/** + * struct pt_common + */ +struct pt_common { + /** + * @top_of_table: Encodes the table top pointer and the top level in a + * single value. Must use READ_ONCE/WRITE_ONCE to access it. The lower + * bits of the aligned table pointer are used for the level. + */ + uintptr_t top_of_table; + /** + * @max_oasz_lg2: Maximum number of bits the OA can contain. Upper bits + * must be zero. This may be less than what the page table format + * supports, but must not be more. + */ + u8 max_oasz_lg2; + /** + * @max_vasz_lg2: Maximum number of bits the VA can contain. Upper bits + * are 0 or 1 depending on pt_full_va_prefix(). This may be less than + * what the page table format supports, but must not be more. When + * PT_FEAT_DYNAMIC_TOP this reflects the maximum VA capability. + */ + u8 max_vasz_lg2; + /** + * @features: Bitmap of `enum pt_features` + */ + unsigned int features; +}; + +/* Encoding parameters for top_of_table */ +enum { + PT_TOP_LEVEL_BITS = 3, + PT_TOP_LEVEL_MASK = GENMASK(PT_TOP_LEVEL_BITS - 1, 0), +}; + +/** + * enum pt_features - Features turned on in the table. Each symbol is a bit + * position. + */ +enum pt_features { + /** + * @PT_FEAT_FULL_VA: The table can span the full VA range from 0 to + * PT_VADDR_MAX. + */ + PT_FEAT_FULL_VA, + /** + * @PT_FEAT_DYNAMIC_TOP: The table's top level can be increased + * dynamically during map. This requires HW support for atomically + * setting both the table top pointer and the starting table level. + */ + PT_FEAT_DYNAMIC_TOP, + /** + * @PT_FEAT_SIGN_EXTEND: The top most bit of the valid VA range sign + * extends up to the full pt_vaddr_t. This divides the page table into + * three VA ranges:: + * + * 0 -> 2^N - 1 Lower + * 2^N -> (MAX - 2^N - 1) Non-Canonical + * MAX - 2^N -> MAX Upper + * + * In this mode pt_common::max_vasz_lg2 includes the sign bit and the + * upper bits that don't fall within the translation are just validated. + * + * If not set there is no sign extension and valid VA goes from 0 to 2^N + * - 1. + */ + PT_FEAT_SIGN_EXTEND, + /** + * @PT_FEAT_FLUSH_RANGE: IOTLB maintenance is done by flushing IOVA + * ranges which will clean out any walk cache or any IOPTE fully + * contained by the range. The optimization objective is to minimize the + * number of flushes even if ranges include IOVA gaps that do not need + * to be flushed. + */ + PT_FEAT_FLUSH_RANGE, + /** + * @PT_FEAT_FLUSH_RANGE_NO_GAPS: Like PT_FEAT_FLUSH_RANGE except that + * the optimization objective is to only flush IOVA that has been + * changed. This mode is suitable for cases like hypervisor shadowing + * where flushing unchanged ranges may cause the hypervisor to reparse + * significant amount of page table. + */ + PT_FEAT_FLUSH_RANGE_NO_GAPS, + /* private: */ + PT_FEAT_FMT_START, +}; + +#endif From patchwork Mon Jun 16 18:06:06 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jason Gunthorpe X-Patchwork-Id: 897137 Received: from NAM12-BN8-obe.outbound.protection.outlook.com (mail-bn8nam12on2043.outbound.protection.outlook.com [40.107.237.43]) (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 EDD2F288C8E; Mon, 16 Jun 2025 18:06:29 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=fail smtp.client-ip=40.107.237.43 ARC-Seal: i=2; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1750097192; cv=fail; b=TX7Ebm4bdUgTNU5fa5LN+D+/Pxit+kdUTnY0J7LVfOEHpZnl1n0IZs3mCYbU1lysPFu0H2EBEtJiigmFNWEEC1GX32C+CwqHjfFzzFmk5AqkIo3RXAw5XYE7/Oub77E3AaBvwuGc6sJWGYuM2YXXpM3D8XOEKOajes1p5YasTE0= ARC-Message-Signature: i=2; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1750097192; c=relaxed/simple; bh=bJbpGax42cBq250sT2PmiSclQ1VtdoNPjQqnZcMFUwc=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: Content-Type:MIME-Version; b=J8C4Wy8KE1QLeOYUiBk3UsHwB8Hil+bWkala7gWYfj2fYhffDTXjECdUkR0uiRG+qkBmhL+shGJV1d+uawc0/PStwXIvWAxuP5hzq430z+1FvcFsC02dvu8QvKlAJf6BXMqx6uo7IN3R8ZfgBY89bRtAV2FuDSdGT8X8HPMb4Lg= 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=GfM6rCd2; arc=fail smtp.client-ip=40.107.237.43 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="GfM6rCd2" ARC-Seal: i=1; a=rsa-sha256; s=arcselector10001; d=microsoft.com; cv=none; b=CaKWkABICg3goaj9GzgSA0VWQhD8Bx2P2YqXrZMDImgeAGxir3S9U0cX10T08bWtVat1BoDEZDS1ptdq2JIfE+m1J5n6H//54GIwgWQG6uGvnhGRHRV+/yzDL36iDlmqmCKYXr2Teev6IJayDv6IbENawgzFmfJs1ZK3ORFUhARUUW0FIDYGI0Rp8vOERAgWNtdRQ/4+Rnt88c0cUp2sqZlZFeDQ/vj7B2T56/bHATU0dvOjbjP3KYkTen1fK974s3C3ukGDUQjSrjafyndzJGkjhOMHgpmZWwSRc2oPgMTcrrGnodNOlzVwtcuzDVA39BW5Ek+2ZicPjit5S3B6GQ== 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=47FMNxPrO1KBvICCXZFtGsM/0nQ0u69j1QgilH2oirg=; b=Sxa/Eq62XiA5j4z7uZsgl2EoDSOZjyWHoWCshutGKAGDI2Kwqq+kQ96Hj5NHMQvxXE7/+1tZrVqMm+knN3yH2gSDLEXPmwCg3eUzlf5uPk4Y0uy2ObZVK98aJUsI34//wMyyzjioZXYtetrTd3zECJd1P0GtzWC2z64olMEbmcO9qYZbnCQQ84Wht6CXydeTe1rqraKNRKza+JIGrjzy1aBXlSqwEdDR4VpK7xFj/Nbdlip8dfOK3QT/PflF4Izu9uw34Na222atB6hmYXEjjSTAF0xSSP1k9CvrkLAPf2tyVXxOVXsqoAzKqmRZ0Q2sA9BTxfEFoULLkIABYoF4vA== 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=47FMNxPrO1KBvICCXZFtGsM/0nQ0u69j1QgilH2oirg=; b=GfM6rCd2GnOs74YvSQZ9cS5cNu0wRHyDpv0agAQKu045B4HmWjnratYdioUxMqlkCpgVHa7AdDawCtlhicvj59we/+iInw8Rt1QzQlE6vuTjaR1EHrSqVqJ9D5xZj2t0FjofgJCxrn24DhAqde1pIehAPkmwyc0tVB/yJQa2Qxt9DsZNLis1QxKUvl94CGv2wSUZkWqm1D3TNs6GwXes9k30a+VL1Xh9JnFuTmQpPSce+qyrm5H9gW9+IICR5O1shdIsTz75nJ7hPX8d44HtA1nwnRYGUBNyR6vA6p8AGMrp5CR5esrIoNA6qexU224CkqWE7RV26v0BNDxYUF8uaA== Authentication-Results: dkim=none (message not signed) header.d=none;dmarc=none action=none header.from=nvidia.com; Received: from CH3PR12MB8659.namprd12.prod.outlook.com (2603:10b6:610:17c::13) by SJ1PR12MB6267.namprd12.prod.outlook.com (2603:10b6:a03:456::10) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.20.8835.28; Mon, 16 Jun 2025 18:06:21 +0000 Received: from CH3PR12MB8659.namprd12.prod.outlook.com ([fe80::6eb6:7d37:7b4b:1732]) by CH3PR12MB8659.namprd12.prod.outlook.com ([fe80::6eb6:7d37:7b4b:1732%7]) with mapi id 15.20.8835.023; Mon, 16 Jun 2025 18:06:21 +0000 From: Jason Gunthorpe To: Jonathan Corbet , iommu@lists.linux.dev, Joerg Roedel , Justin Stitt , Kevin Tian , linux-doc@vger.kernel.org, linux-kselftest@vger.kernel.org, llvm@lists.linux.dev, Bill Wendling , Nathan Chancellor , Nick Desaulniers , Miguel Ojeda , Robin Murphy , Shuah Khan , Suravee Suthikulpanit , Will Deacon Cc: Alexey Kardashevskiy , Alejandro Jimenez , James Gowans , Michael Roth , Pasha Tatashin , patches@lists.linux.dev Subject: [PATCH v3 03/15] iommupt: Add the basic structure of the iommu implementation Date: Mon, 16 Jun 2025 15:06:06 -0300 Message-ID: <3-v3-a93aab628dbc+521-iommu_pt_jgg@nvidia.com> In-Reply-To: <0-v3-a93aab628dbc+521-iommu_pt_jgg@nvidia.com> References: X-ClientProxiedBy: DS7PR06CA0040.namprd06.prod.outlook.com (2603:10b6:8:54::28) To CH3PR12MB8659.namprd12.prod.outlook.com (2603:10b6:610:17c::13) Precedence: bulk X-Mailing-List: linux-kselftest@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 X-MS-PublicTrafficType: Email X-MS-TrafficTypeDiagnostic: CH3PR12MB8659:EE_|SJ1PR12MB6267:EE_ X-MS-Office365-Filtering-Correlation-Id: c2bbaeca-d5a3-4c9c-f187-08ddad007edd X-MS-Exchange-SenderADCheck: 1 X-MS-Exchange-AntiSpam-Relay: 0 X-Microsoft-Antispam: BCL:0; ARA:13230040|366016|1800799024|7416014|376014|921020; X-Microsoft-Antispam-Message-Info: HvrCvgWtONTqrwFjv2gt3qwX8AN6DAeFUkPadcEYaoiBKJ2Qa0cf+9obodC6TSzhwnLpGkTspW5k1uqv48y02gfyrsoudVrgxPzZHozvDWxRx8IUT44QYt2CMGhExudtD4/+HO1n+t2bolzstV/FhZFuKsrJUv8tS7hzEELgfpw5XiHAs4LMKwLrkBgUqSouWaiODuxuYygKShU8MEFSa6DK0rX/gEwF08sK8183ukZNVaHyvZCeK9SxUJNxhVmSPzCKYKxFWypxMYg/zCg0IfK03zzOfirOuSYucSWUZGAM5h3c1GqXubRV33Lbcn2Q1K54XE3HegWBgG/EGF3p8x6dOXweJdL1IYoq0uHHzAtk2bb4ToX+ayU+sexkwhR+rZc9i2zmly1iYsW3j9MNHoMhi5AOSZjE1WeGuqXWYGQACXK5BhRghcEzV7dq5fH2HqiKFe82arghyCk0vSJDWDDr72LXj5AjMQLPZuLrgvKEjHzANmgtwVTOwRM23UZnTpRxEK+Yrw7nqeota0qY7E/DgIymBOp6RKA/XZ3CasVL+zKSfGvRtzOKyaDiE3R6xpA4mB6MeooA/0gWGFXb0xwnh2KPVFitOr1DV6ckjPt+VsWXjeclefyyLv3qMVBbR6SlHX99BUIYHCeeA2x3urt6X4kiTYwlHWJtPj+NxyRXIdF9i9p8g2Q3X5ZR+lZVO3Kmr4hiVGlh/d4CrC5ORcVyFE7rE49hfJrjlTkhwfP4Tlm1+ycPfkLcaLG4SlJpgVZHgf/R2mrqprN7YeK4dTyxIbVc40+N4EYwMCchSkw7gT81qgmLmDipWFwqc+MSe8urcYc6vg8CciDJ+Fnm0UF07QHsL8lsx9o0INDg/ybtUSMVSXoCtzuOWo13uHP64XZ2voCkMg5xjY9ojX0XEPOPyMjLr7UX3REXPAP5LckCEprKekdQ4CQ2eDhdlyDKANC0D+cFn5kbQNctducTKRBi6oQ9tk/3GlB/xwff3Ld+im7kI4/KAZIkyaERklfSOBrKrz8kvbuzVQG+dWIntP6iTWSB5E/FdLHAb9FgmHMvcTA1LO1FTWxjZGvkLxKR9GjF1KN+S4jRTcX+bzZAJWgKmtLw6Xd5OPNVQGn1rXyeMG/LBaKETfSETnHLbufLXluEC9RNjh8Sq7CTNTVvbVcwd71ybskoZ5o6Lsit8fsQI9VIlrJNqDluFZ25vLT/jAUhGpu7aaVfPesSdkIQsUlAI5BbxKppx7t7vdN3UADikjvzw/6/hKsK9MhHAj1pbyNXin96c8C1v/5oUdSdC1kiRC8LSPwCrljYCu3GDoXL8HRNMtBgUrqphGmIdcmeXhmG1pCaFsBsfGFIGF24d2EI1ugbKl4IYKON0KFePfrMyCj91jXyZZmKC/kA7nAUeDetMLOTfSgZ4F17/gi0qSJvLCcmVRNAAF50U/rGiaJv0tulxkK50U9y56cGTWep X-Forefront-Antispam-Report: CIP:255.255.255.255; CTRY:; LANG:en; SCL:1; SRV:; IPV:NLI; SFV:NSPM; H:CH3PR12MB8659.namprd12.prod.outlook.com; PTR:; CAT:NONE; SFS:(13230040)(366016)(1800799024)(7416014)(376014)(921020); DIR:OUT; SFP:1101; X-MS-Exchange-AntiSpam-MessageData-ChunkCount: 1 X-MS-Exchange-AntiSpam-MessageData-0: WWfGD3wwWyoA8PbxMn2r4m26P38egWElEj9jO+Xd8xT/Y4IweNvxVQK4DBet99TboK9HZSaalDQCzMLxwQX40q2gBq0TyeImKT82cmPr5j5KPVpDrZS+WLuJsJoKZX+TB+l6iMftdmZlqkNLEELNKHH1kNBnRY+qCx5vrABN4tYC5iMPaXm4I9IfQj8u9mtJ1/TpJNZIprCgtUR7Sux8NkZpmAkonmYXhv8ArsNEJ/3KfsAnAxWGX5JyuzrpEA6TvjjDVVnlob8x1UBWnCRfZyUhOjjlTCDip10JZ02C8qpe716u0Nc0LMoYJe19RNkT3+tbWlnvY1Vbmqw4W9uQ6X7hsAMhHds6sD2jQakdt/f1+64l+o9s1InGwPOkGlOgEShfkkc+WRL6gg56DX0sGRK5MXQnmUmMe5r1/HgbboWzeTeTAPn4Fx25lZQ2e2yzghkcCz78BizgRGC1m0RZN8cMjiMpg20Q/lYEPBnGEnu+963mNWtA0Fy6/t06R9vEx3mrsleNLyMA9B6q+zmcze01ZnlA0E8GGwiDOM8e0nBP/BDBz6Oj5PHZV3RAy2KTwPCfHAPV9EoPnigh0Of90U+MH1uuKHUnlSdJpIZYLiFMxtX9UWf7OFrzs+y4es00o6YRuyVyoO9l1ZaalWTUImagZQFtU5a4APSrrSMqFYo9AnEy1fisLuPp4MVrjHL0YhU/0C5UejVOHxIMamYNQWH5HR1xlqV1oJYYldOos4wl6G+C6qB8z9hwJ9Onxg16Rp2JfaGVQgmxhJXvYCK9V6EIbeVFNqAqHN3O3P3g+OHX+9JVZkbo3aEqIMxmTCwlOb1zKGZO0BrWvNlxAF7xq1wZQfIxW3JeN5MakgUQtRw0OXDYL0AwFP/1SYknk3mLhMKiR5m64P2y81+C9FSl3dXaRi5QVEgM+bZLMI6uSC8P+jhFp+PB0qYXQ9oDi/TZW1r79gLS1YOXhJUSd6G7ZcBgjv1N7jnRrk9b5H0BS+89zzh/4/q17RaO8YavU1eWlsL7G5ReUpCqmvdXgXZMzmwMAWBeVT0UyJmdPLdZNRwQrRQFTfCptxNNfQwEmWSoB/jbERb0rBFzBEcP/ZABu2G3E+Ike88+7QodWMKOlDe8Fm3AkJhYnmcF4nU56kn9Xivj7FzfSaayZd+H2UZFBRkMlnz/gjhOPrkmk3GunJVKWlLXiipqn8BjdkVA2OFXCwiaWVdvvRIXf4Ftq/RQAXnm9Y5OUS7+Gm1jnTN9fTX7mucwEOPSX39O/apqkZXQ19MRUTZ68dSc80GR8w9c/bOyQXYJ5KtxoHpxoCcp2fSrk0QhKUFHQqJhyxOgNC4FUGmRvgbgdsZCW5GaIW7GFq9B5trFYHY5xwAM4QRvyd/oCFnG30VuMn3Saf2IweilgQieYA2SN2hieW4VZNCtXXK6enA4Y6pskvM1B0eEP9eNj6GUdbXwhMutqRUhu90unR6STCcYQcNISNF0pgVpKktflsxqJl9moq18frE724PJPQXDFq4w25sB4uPldlVYJIy77oegEJM7xhc4eppucS1QxAGt2fekaqggivt/Uu+VoJ+Ole7x6ikGIRtRlViA X-OriginatorOrg: Nvidia.com X-MS-Exchange-CrossTenant-Network-Message-Id: c2bbaeca-d5a3-4c9c-f187-08ddad007edd X-MS-Exchange-CrossTenant-AuthSource: CH3PR12MB8659.namprd12.prod.outlook.com X-MS-Exchange-CrossTenant-AuthAs: Internal X-MS-Exchange-CrossTenant-OriginalArrivalTime: 16 Jun 2025 18:06:20.2794 (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: FMCGK7RKHw0A3CvpzEegyQ98PaZfToxcQey4tN2wrtxZ/Vz6xWrN6WGgW19ywjx0 X-MS-Exchange-Transport-CrossTenantHeadersStamped: SJ1PR12MB6267 The existing IOMMU page table implementations duplicate all of the working algorithms for each format. By using the generic page table API a single C version of the IOMMU algorithms can be created and re-used for all of the different formats used in the drivers. The implementation will provide a single C version of the iommu domain operations: iova_to_phys, map, unmap, and read_and_clear_dirty. Further, adding new algorithms and techniques becomes easy to do across the entire fleet of drivers and formats. The C functions are drop in compatible with the existing iommu_domain_ops using the IOMMU_PT_DOMAIN_OPS() macro. Each per-format implementation compilation unit will produce exported symbols following the pattern pt_iommu_FMT_map_pages() which the macro directly maps to the iommu_domain_ops members. This avoids the additional function pointer indirection like io-pgtable has. The top level struct used by the drivers is pt_iommu_table_FMT. It contains the other structs to allow container_of() to move between the driver, iommu page table, generic page table, and generic format layers. struct pt_iommu_table_amdv1 { struct pt_iommu { struct iommu_domain domain; } iommu; struct pt_amdv1 { struct pt_common { } common; } amdpt; }; The driver is expected to union the pt_iommu_table_FMT with it's own existing domain struct: struct driver_domain { union { struct iommu_domain domain; struct pt_iommu_table_amdv1 amdv1; }; }; PT_IOMMU_CHECK_DOMAIN(struct driver_domain, amdv1, domain); To create an alias to avoid renaming 'domain' in a lot of driver code. This allows all the layers to access all the necessary functions to implement their different roles with no change to any of the existing iommu core code. Implement the basic starting point: pt_iommu_init(), get_info() and deinit(). Tested-by: Alejandro Jimenez Signed-off-by: Jason Gunthorpe --- drivers/iommu/generic_pt/Kconfig | 13 + drivers/iommu/generic_pt/fmt/iommu_template.h | 39 +++ drivers/iommu/generic_pt/iommu_pt.h | 268 ++++++++++++++++++ include/linux/generic_pt/iommu.h | 118 ++++++++ 4 files changed, 438 insertions(+) create mode 100644 drivers/iommu/generic_pt/fmt/iommu_template.h create mode 100644 drivers/iommu/generic_pt/iommu_pt.h create mode 100644 include/linux/generic_pt/iommu.h diff --git a/drivers/iommu/generic_pt/Kconfig b/drivers/iommu/generic_pt/Kconfig index 775a3afb563f72..73b7a54375f9bd 100644 --- a/drivers/iommu/generic_pt/Kconfig +++ b/drivers/iommu/generic_pt/Kconfig @@ -19,4 +19,17 @@ config DEBUG_GENERIC_PT kernels. The kunit tests require this to be enabled to get full coverage. + +config IOMMU_PT + tristate "IOMMU Page Tables" + select IOMMU_API + depends on IOMMU_SUPPORT + depends on GENERIC_PT + default n + help + Generic library for building IOMMU page tables + + IOMMU_PT provides an implementation of the page table operations + related struct iommu_domain using GENERIC_PT to abstract the page + table format. endif diff --git a/drivers/iommu/generic_pt/fmt/iommu_template.h b/drivers/iommu/generic_pt/fmt/iommu_template.h new file mode 100644 index 00000000000000..5b631bc07cbc16 --- /dev/null +++ b/drivers/iommu/generic_pt/fmt/iommu_template.h @@ -0,0 +1,39 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2024-2025, NVIDIA CORPORATION & AFFILIATES + * + * Template to build the iommu module and kunit from the format and + * implementation headers. + * + * The format should have: + * #define PT_FMT + * #define PT_SUPPORTED_FEATURES (BIT(PT_FEAT_xx) | BIT(PT_FEAT_yy)) + * And optionally: + * #define PT_FORCE_ENABLED_FEATURES .. + * #define PT_FMT_VARIANT + */ +#include +#include + +#ifdef PT_FMT_VARIANT +#define PTPFX_RAW \ + CONCATENATE(CONCATENATE(PT_FMT, _), PT_FMT_VARIANT) +#else +#define PTPFX_RAW PT_FMT +#endif + +#define PTPFX CONCATENATE(PTPFX_RAW, _) + +#define _PT_FMT_H PT_FMT.h +#define PT_FMT_H __stringify(_PT_FMT_H) + +#define _PT_DEFS_H CONCATENATE(defs_, _PT_FMT_H) +#define PT_DEFS_H __stringify(_PT_DEFS_H) + +#include +#include PT_DEFS_H +#include "../pt_defs.h" +#include PT_FMT_H +#include "../pt_common.h" + +#include "../iommu_pt.h" diff --git a/drivers/iommu/generic_pt/iommu_pt.h b/drivers/iommu/generic_pt/iommu_pt.h new file mode 100644 index 00000000000000..205c232bda68b5 --- /dev/null +++ b/drivers/iommu/generic_pt/iommu_pt.h @@ -0,0 +1,268 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2024-2025, NVIDIA CORPORATION & AFFILIATES + * + * "Templated C code" for implementing the iommu operations for page tables. + * This is compiled multiple times, over all the page table formats to pick up + * the per-format definitions. + */ +#ifndef __GENERIC_PT_IOMMU_PT_H +#define __GENERIC_PT_IOMMU_PT_H + +#include "pt_iter.h" + +#include +#include "../iommu-pages.h" +#include + +#define DOMAIN_NS(op) CONCATENATE(CONCATENATE(pt_iommu_, PTPFX), op) + +struct pt_iommu_collect_args { + struct iommu_pages_list free_list; + u8 ignore_mapped : 1; +}; + +static int __collect_tables(struct pt_range *range, void *arg, + unsigned int level, struct pt_table_p *table) +{ + struct pt_state pts = pt_init(range, level, table); + struct pt_iommu_collect_args *collect = arg; + int ret; + + if (collect->ignore_mapped && !pt_can_have_table(&pts)) + return 0; + + for_each_pt_level_entry(&pts) { + if (pts.type == PT_ENTRY_TABLE) { + iommu_pages_list_add(&collect->free_list, pts.table_lower); + ret = pt_descend(&pts, arg, __collect_tables); + if (ret) + return ret; + continue; + } + if (pts.type == PT_ENTRY_OA && !collect->ignore_mapped) + return -EADDRINUSE; + } + return 0; +} + +static inline struct pt_table_p *table_alloc_top(struct pt_common *common, + uintptr_t top_of_table, + gfp_t gfp) +{ + struct pt_iommu *iommu_table = iommu_from_common(common); + + /* + * Top doesn't need the free list or otherwise, so it technically + * doesn't need to use iommu pages. Use the API anyhow as the top is + * usually not smaller than PAGE_SIZE to keep things simple. + */ + return iommu_alloc_pages_node_sz( + iommu_table->nid, gfp, + log2_to_int(pt_top_memsize_lg2(common, top_of_table))); +} + +static void NS(get_info)(struct pt_iommu *iommu_table, + struct pt_iommu_info *info) +{ + struct pt_common *common = common_from_iommu(iommu_table); + struct pt_range range = pt_top_range(common); + struct pt_state pts = pt_init_top(&range); + pt_vaddr_t pgsize_bitmap = 0; + + if (pt_feature(common, PT_FEAT_DYNAMIC_TOP)) { + for (pts.level = 0; pts.level <= PT_MAX_TOP_LEVEL; + pts.level++) { + if (pt_table_item_lg2sz(&pts) >= common->max_vasz_lg2) + break; + pgsize_bitmap |= pt_possible_sizes(&pts); + } + } else { + for (pts.level = 0; pts.level <= range.top_level; pts.level++) + pgsize_bitmap |= pt_possible_sizes(&pts); + } + + /* Hide page sizes larger than the maximum OA */ + info->pgsize_bitmap = oalog2_mod(pgsize_bitmap, common->max_oasz_lg2); +} + +static void NS(deinit)(struct pt_iommu *iommu_table) +{ + struct pt_common *common = common_from_iommu(iommu_table); + struct pt_range range = pt_all_range(common); + struct pt_iommu_collect_args collect = { + .free_list = IOMMU_PAGES_LIST_INIT(collect.free_list), + .ignore_mapped = true, + }; + + iommu_pages_list_add(&collect.free_list, range.top_table); + pt_walk_range(&range, __collect_tables, &collect); + + /* + * The driver has to already have fenced the HW access to the page table + * and invalidated any caching referring to this memory. + */ + iommu_put_pages_list(&collect.free_list); +} + +static const struct pt_iommu_ops NS(ops) = { + .get_info = NS(get_info), + .deinit = NS(deinit), +}; + +static int pt_init_common(struct pt_common *common) +{ + struct pt_range top_range = pt_top_range(common); + + if (PT_WARN_ON(top_range.top_level > PT_MAX_TOP_LEVEL)) + return -EINVAL; + + if (top_range.top_level == PT_MAX_TOP_LEVEL || + common->max_vasz_lg2 == top_range.max_vasz_lg2) + common->features &= ~BIT(PT_FEAT_DYNAMIC_TOP); + + if (top_range.max_vasz_lg2 == PT_VADDR_MAX_LG2) + common->features |= BIT(PT_FEAT_FULL_VA); + + /* Requested features must match features compiled into this format */ + if ((common->features & ~(unsigned int)PT_SUPPORTED_FEATURES) || + (!IS_ENABLED(CONFIG_DEBUG_GENERIC_PT) && + (common->features & PT_FORCE_ENABLED_FEATURES) != + PT_FORCE_ENABLED_FEATURES)) + return -EOPNOTSUPP; + + if (common->max_oasz_lg2 == 0) + common->max_oasz_lg2 = pt_max_output_address_lg2(common); + else + common->max_oasz_lg2 = min(common->max_oasz_lg2, + pt_max_output_address_lg2(common)); + return 0; +} + +static int pt_iommu_init_domain(struct pt_iommu *iommu_table, + struct iommu_domain *domain) +{ + struct pt_common *common = common_from_iommu(iommu_table); + struct pt_iommu_info info; + struct pt_range range; + + NS(get_info)(iommu_table, &info); + + domain->type = __IOMMU_DOMAIN_PAGING; + domain->pgsize_bitmap = info.pgsize_bitmap; + + if (pt_feature(common, PT_FEAT_DYNAMIC_TOP)) + range = _pt_top_range(common, + _pt_top_set(NULL, PT_MAX_TOP_LEVEL)); + else + range = pt_top_range(common); + + /* + * A 64 bit high address space table on a 32 bit system cannot work. + */ + domain->geometry.aperture_start = (unsigned long)range.va; + if ((pt_vaddr_t)domain->geometry.aperture_start != range.va || + range.va > ULONG_MAX) + return -EOVERFLOW; + + /* + * The aperture is limited to what the API can do after considering all + * the different types dma_addr_t/unsigned long/pt_vaddr_t that are used + * to store a VA. Set the aperture to something that is valid for all + * cases. Saturate instead of truncate the end if the types are smaller + * than the top range. aperture_end is a last. + */ + domain->geometry.aperture_end = (unsigned long)range.last_va; + if ((pt_vaddr_t)domain->geometry.aperture_end != range.last_va) { + domain->geometry.aperture_end = ULONG_MAX; + domain->pgsize_bitmap &= ULONG_MAX; + } + domain->geometry.force_aperture = true; + + return 0; +} + +static void pt_iommu_zero(struct pt_iommu_table *fmt_table) +{ + struct pt_iommu *iommu_table = &fmt_table->iommu; + struct pt_iommu cfg = *iommu_table; + + static_assert(offsetof(struct pt_iommu_table, iommu.domain) == 0); + memset_after(fmt_table, 0, iommu.domain); + + /* The caller can initialize some of these values */ + iommu_table->nid = cfg.nid; +} + +#define pt_iommu_table_cfg CONCATENATE(pt_iommu_table, _cfg) +#define pt_iommu_init CONCATENATE(CONCATENATE(pt_iommu_, PTPFX), init) +int pt_iommu_init(struct pt_iommu_table *fmt_table, + const struct pt_iommu_table_cfg *cfg, gfp_t gfp) +{ + struct pt_iommu *iommu_table = &fmt_table->iommu; + struct pt_common *common = common_from_iommu(iommu_table); + struct pt_table_p *table_mem; + int ret; + + if (cfg->common.hw_max_vasz_lg2 > PT_MAX_VA_ADDRESS_LG2 || + !cfg->common.hw_max_vasz_lg2 || !cfg->common.hw_max_oasz_lg2) + return -EINVAL; + + pt_iommu_zero(fmt_table); + common->features = cfg->common.features; + common->max_vasz_lg2 = cfg->common.hw_max_vasz_lg2; + common->max_oasz_lg2 = cfg->common.hw_max_oasz_lg2; +#ifdef PT_FIXED_TOP_LEVEL + pt_top_set_level(common, PT_FIXED_TOP_LEVEL); +#endif + ret = pt_iommu_fmt_init(fmt_table, cfg); + if (ret) + return ret; + + if (cfg->common.hw_max_oasz_lg2 > pt_max_output_address_lg2(common)) + return -EINVAL; + + ret = pt_init_common(common); + if (ret) + return ret; + + if (pt_feature(common, PT_FEAT_SIGN_EXTEND) && + (pt_feature(common, PT_FEAT_FULL_VA) || + pt_feature(common, PT_FEAT_DYNAMIC_TOP))) + return -EINVAL; + + ret = pt_iommu_init_domain(iommu_table, &iommu_table->domain); + if (ret) + return ret; + + table_mem = table_alloc_top(common, common->top_of_table, gfp); + if (IS_ERR(table_mem)) + return PTR_ERR(table_mem); + pt_top_set(common, table_mem, pt_top_get_level(common)); + + /* Must be last, see pt_iommu_deinit() */ + iommu_table->ops = &NS(ops); + return 0; +} +EXPORT_SYMBOL_NS_GPL(pt_iommu_init, "GENERIC_PT_IOMMU"); + +#ifdef pt_iommu_fmt_hw_info +#define pt_iommu_table_hw_info CONCATENATE(pt_iommu_table, _hw_info) +#define pt_iommu_hw_info CONCATENATE(CONCATENATE(pt_iommu_, PTPFX), hw_info) +void pt_iommu_hw_info(struct pt_iommu_table *fmt_table, + struct pt_iommu_table_hw_info *info) +{ + struct pt_iommu *iommu_table = &fmt_table->iommu; + struct pt_common *common = common_from_iommu(iommu_table); + struct pt_range top_range = pt_top_range(common); + + pt_iommu_fmt_hw_info(fmt_table, &top_range, info); +} +EXPORT_SYMBOL_NS_GPL(pt_iommu_hw_info, "GENERIC_PT_IOMMU"); +#endif + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("IOMMU Pagetable implementation for " __stringify(PTPFX_RAW)); +MODULE_IMPORT_NS("GENERIC_PT"); + +#endif diff --git a/include/linux/generic_pt/iommu.h b/include/linux/generic_pt/iommu.h new file mode 100644 index 00000000000000..9d2152bc64c0d6 --- /dev/null +++ b/include/linux/generic_pt/iommu.h @@ -0,0 +1,118 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2024-2025, NVIDIA CORPORATION & AFFILIATES + */ +#ifndef __GENERIC_PT_IOMMU_H +#define __GENERIC_PT_IOMMU_H + +#include +#include +#include + +struct pt_iommu_ops; + +/** + * DOC: IOMMU Radix Page Table + * + * The iommu implementation of the Generic Page Table provides an ops struct + * that is useful to go with an iommu_domain to serve the DMA API, IOMMUFD and + * the generic map/unmap interface. + * + * This interface uses a caller provided locking approach. The caller must have + * a VA range lock concept that prevents concurrent threads from calling ops on + * the same VA. Generally the range lock must be at least as large as a single + * map call. + */ + +/** + * struct pt_iommu - Base structure for iommu page tables + * + * The format specific struct will include this as the first member. + */ +struct pt_iommu { + /** + * @domain - The core iommu domain. The driver should use a union to + * overlay this memory with its previously existing domain struct to + * create an alias. + */ + struct iommu_domain domain; + + /** + * @ops - Function pointers to access the API + */ + const struct pt_iommu_ops *ops; + + /** + * @nid - Node ID to use for table memory allocations. The iommu driver + * may want to set the NID to the device's NID, if there are multiple + * table walkers. + */ + int nid; +}; + +/** + * struct pt_iommu_info - Details about the iommu page table + * + * Returned from pt_iommu_ops->get_info() + */ +struct pt_iommu_info { + /** + * @pgsize_bitmap - A bitmask where each set bit indicates + * a page size that can be natively stored in the page table. + */ + u64 pgsize_bitmap; +}; + +struct pt_iommu_ops { + /** + * get_info() - Return the pt_iommu_info structure + * @iommu_table: Table to query + * + * Return some basic static information about the page table. + */ + void (*get_info)(struct pt_iommu *iommu_table, + struct pt_iommu_info *info); + + /** + * deinit() - Undo a format specific init operation + * @iommu_table: Table to destroy + * + * Release all of the memory. The caller must have already removed the + * table from all HW access and all caches. + */ + void (*deinit)(struct pt_iommu *iommu_table); +}; + +static inline void pt_iommu_deinit(struct pt_iommu *iommu_table) +{ + /* + * It is safe to call pt_iommu_deinit() before an init, or if init + * fails. The ops pointer will only become non-NUL if deinit needs to be + * run. + */ + if (iommu_table->ops) + iommu_table->ops->deinit(iommu_table); +} + +/** + * struct pt_iommu_cfg - Common configuration values for all formats + */ +struct pt_iommu_cfg { + /** + * @features - Features required. Only these features will be turned on. + * The feature list should reflect what the IOMMU HW is capable of. + */ + unsigned int features; + /** + * @hw_max_vasz_lg2 - Maximum VA the IOMMU HW can support. This will + * imply the top level of the table. + */ + u8 hw_max_vasz_lg2; + /** + * @hw_max_oasz_lg2 - Maximum OA the IOMMU HW can support. The format + * might select a lower maximum OA. + */ + u8 hw_max_oasz_lg2; +}; + +#endif From patchwork Mon Jun 16 18:06:07 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jason Gunthorpe X-Patchwork-Id: 897133 Received: from NAM12-BN8-obe.outbound.protection.outlook.com (mail-bn8nam12on2043.outbound.protection.outlook.com [40.107.237.43]) (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 40B8D289349; Mon, 16 Jun 2025 18:06:37 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=fail smtp.client-ip=40.107.237.43 ARC-Seal: i=2; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1750097200; cv=fail; b=l9vSSrc9SrvIPqzQmSJvGdOJ6Ntf8xczHG7wRM3t0VuVmioeuXkboE3+GU8eptUIoEE5fefNjwgrGbYvH+anphi7KXJq7dR/jYes8eicSp72tJi09DKZWGRhMx143qedJmKmZxtYNXj8gBWSFgcdp38uLt32Dau51y+uxsTxjCI= ARC-Message-Signature: i=2; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1750097200; c=relaxed/simple; bh=cCD8QiVcWTSag7s8OHnmOZwA8d6pEGUPgkmyzOqLyGA=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: Content-Type:MIME-Version; b=Ley/+s1n2bB1BOxEA1i0QIWw8G9eR7LDX92MZgZMC3shUZuyGL1T/n6siaz4hJkY9CwQds8GC68ZLSvq+qXCQl5M2fy4xkzeVdzu7+XvaYVvvZo3frHR404f3DOZqy7K44XMtNsEo++cLe3AWPumpDRD/6cUpLJbfx5f6lKfclM= 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=WE3MARsD; arc=fail smtp.client-ip=40.107.237.43 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="WE3MARsD" ARC-Seal: i=1; a=rsa-sha256; s=arcselector10001; d=microsoft.com; cv=none; b=smfoY+pF+sZH83cF16AD/8CvZO7Yfl27/Ec+jNOWYzJlOksvVslH9Z5D1g7RllVmhJMJZtP2gynWL4O81hnY3hWIgke2PkuKSAQ0DqR0TtjmqU2J5DYFBqIpwPMLKIujuoutGwDfvzjXycg8zb7TbVnCmF6vTMmEto+BkAKxEEO7fPvIqwMPEuGJn08RmDkMSvyuw44IMUKkY2DraqyJwf0VTh2cEeFlVFapnW4LPNYB9wPDHZNuQQRXrEVOWXlKKGytHObxjmNDKOT/r/S0LoE+FbbV35T87yy6h1jUM7TbA0oy6QVmt6TDW0HC48NyUYH4G4Tzyun5K3pLqCyc8g== 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=a0A893A1rjih4d31EMWk+QRN02rqEfHHi6remt8e0xY=; b=vaoRLUEKPAwKPZitIIkA9GEGfSktheqcBoIruloVI87hEEnwXWfumppZAcCzMaaCadt4lxmWXZ75+szmrUarbnMbwHVNaLNtohdWJNwpewFsaxif9H3KMNqYw3b85RWC0JdbSAqiNJh59vxYlpKRuNZy4iAUV9OyYuMqMiLuX/llfOR789rOmE8eMS2v6cQTcwOjG0Mgaw19FZNTDR7BNaprZEicO7tyDdEg2+L82zFAFEEJr3gGf4+duCVEknj+Hio1A3EX/hayN7nVjBuNFsf9RfPMpZXbMyKkLr183CdkcKJohJB54S0OWC5gSSdW9mjNQ00n1y3TdbdaHZDSIQ== 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=a0A893A1rjih4d31EMWk+QRN02rqEfHHi6remt8e0xY=; b=WE3MARsDf1/vXuX+ayurvpA+bCMVZZW0CJ6j2jfcmn03g0B4zXUXpFXb0h2NF0rXaHWeZ9evopCzUOHnP1xIQ0j4UDw6ijo1FH1wY/sx2WxYUIVg9gpu1/ys66QrVkghIMd3RMVWPMbix7rEXkxxvPld35vTGEkYQ6ihBce3aI51RE0tPYKf8sqU50RhK0ZDQCSvmIB0MWun+oTkxnTvFvNdeGc99LR56GTHC8luS2cWpZ8fdZqlwn0dS+alrYZn6n1hKp5OMwq0FuTZnHQXaRwc0eaQD45CzrCalgaMNWXvfyXHbn0YJPBUFUlhTxUdvi574o4gurHEvA+NTDL91w== Authentication-Results: dkim=none (message not signed) header.d=none;dmarc=none action=none header.from=nvidia.com; Received: from CH3PR12MB8659.namprd12.prod.outlook.com (2603:10b6:610:17c::13) by SJ1PR12MB6267.namprd12.prod.outlook.com (2603:10b6:a03:456::10) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.20.8835.28; Mon, 16 Jun 2025 18:06:25 +0000 Received: from CH3PR12MB8659.namprd12.prod.outlook.com ([fe80::6eb6:7d37:7b4b:1732]) by CH3PR12MB8659.namprd12.prod.outlook.com ([fe80::6eb6:7d37:7b4b:1732%7]) with mapi id 15.20.8835.023; Mon, 16 Jun 2025 18:06:25 +0000 From: Jason Gunthorpe To: Jonathan Corbet , iommu@lists.linux.dev, Joerg Roedel , Justin Stitt , Kevin Tian , linux-doc@vger.kernel.org, linux-kselftest@vger.kernel.org, llvm@lists.linux.dev, Bill Wendling , Nathan Chancellor , Nick Desaulniers , Miguel Ojeda , Robin Murphy , Shuah Khan , Suravee Suthikulpanit , Will Deacon Cc: Alexey Kardashevskiy , Alejandro Jimenez , James Gowans , Michael Roth , Pasha Tatashin , patches@lists.linux.dev Subject: [PATCH v3 04/15] iommupt: Add the AMD IOMMU v1 page table format Date: Mon, 16 Jun 2025 15:06:07 -0300 Message-ID: <4-v3-a93aab628dbc+521-iommu_pt_jgg@nvidia.com> In-Reply-To: <0-v3-a93aab628dbc+521-iommu_pt_jgg@nvidia.com> References: X-ClientProxiedBy: PH5P222CA0010.NAMP222.PROD.OUTLOOK.COM (2603:10b6:510:34b::11) To CH3PR12MB8659.namprd12.prod.outlook.com (2603:10b6:610:17c::13) Precedence: bulk X-Mailing-List: linux-kselftest@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 X-MS-PublicTrafficType: Email X-MS-TrafficTypeDiagnostic: CH3PR12MB8659:EE_|SJ1PR12MB6267:EE_ X-MS-Office365-Filtering-Correlation-Id: 68fcd62c-aad3-46ca-5e78-08ddad008046 X-MS-Exchange-SenderADCheck: 1 X-MS-Exchange-AntiSpam-Relay: 0 X-Microsoft-Antispam: BCL:0; ARA:13230040|366016|1800799024|7416014|376014|921020; X-Microsoft-Antispam-Message-Info: 5NKK0sDDwys5n1vCgl0cMLXBrjY47GUqjOR905LSM9F28vt7x3GXLQs/tV5oqHjw3gsetmJaWzP2Bq63n/YPy4jcRrSq9hKFDLHGpb8AGz22rvBANv1VtI5JGWMWqYPXEYSXrpPHtTKQ/7Q1CUiQEeTfAwmBQ0IQ5GtZMPEbMWCtpI2SmcZEj9oDcr0mzgofBHuMkgIm7TYlK+mdaa+RCqNmj2sy+zadwUM+RrBUh65+vlymj8oT2NJlQLkk7GygGFkDmk1+KR1peQD7EgHZ9IUJLpbOEWs+oC3H4+IZ4HEWlZD4A1vAshn3Iz5B6gb1l/CGgV2Ct9d+cP2no8qMYLmb/TN9zadPvDpZzVb8c1+MBaTkMJRmgw+P/PiD/eWULlMVZ5sjuCcbW2fb0Q8RIFec54/R1ombq+vvWKBsRLyYNWXfkE7g2rmt0njkDtoEanynMAza8UizEcGoeMqGubHJ8vRjZKM0V6Mwr/UBvUBNojCnOGjh3LNvVIemCAvnsLvdvar0TQZjFntVtGW97iyu1Cs9D1en158QaRt7l9j3d6gSfqji1iBNcQ2GX4wRyxUFLrSs9HLO8uLsC483x39bQjygl5qrG6A5s1ZOscrT2pyQOdNf9oB8wc6hNczPW5wXY/eNk/RR5HURFGNwogMKAY5rlP5Edv1fFb3qvT1ywhbxgzmzXYHafoVwJhQHw86ZhvH7lI0sC1rB4M5tA8cSxKc3pbUp0kcufG2Ay7zDg7/M5S9hAdv3Eyz+3MTiesn4MNzXwaSjWKIs//DHImAWyOpzZWAXhXw+a+TqNIbpmVshke/d3odFt0N8foGRN4PjKaXDZIRn9FVMuKVedX9BX8pNj8sccMwu8pEBYMqz2fZi7iB6zIHC0gNy59IxPmR7W+rYJEieNuO6AQhtJ0fHF/fCxfmevHoXNYWP3qJIFfcNpH2T821JRKp91DgaTGQCV7HAaDgUjKNVI1T/gwB/4K6UBtKetLv88A716QdEc9XdLedXVw988w2OJ5oOTj/56eaNw2yFvZ/nuZVCOhtrZcsa3A9xte6EpxuOLGfZaBD6eTBUp18E/D9cij0TUaz/qfkblq6UFin1/2VnjA8rAkZV/q2EZmBzNG5P+ddSLVncKvKpErqeDQE9C/TfBh3oNkIAk2omdiCJ9KqLe/x6JXW1Uic65RqPunXfZUU9DoMY2TNJ8lRxmhMG6/GxY9/ZSNgaKQPpdgwEZ4VWi+FmYKJQBvGdGLbtC9Dj6JRN+01Gu+hruHu6eTSMArkLtiy3qCA/cREm3NxZnR0rCkZPUPQnwUibPc4e0XPULg6MH8QmMfMMRlMWzW2O2peZN3/tqsOmgmqj4dWdL/bp+A0GHK+n+VgWhXfJgbjPtH7RXxnZjx74tBh8+vMt83uLK1u6LggodoaVL976xmIJk1VlHLWHdf7dixfHWSIwIWFYXhtsKCGe8rq+jQESU+XcEYPhxbb3W1vp0ltH48G+4Q== X-Forefront-Antispam-Report: CIP:255.255.255.255; CTRY:; LANG:en; SCL:1; SRV:; IPV:NLI; SFV:NSPM; H:CH3PR12MB8659.namprd12.prod.outlook.com; PTR:; CAT:NONE; SFS:(13230040)(366016)(1800799024)(7416014)(376014)(921020); DIR:OUT; SFP:1101; X-MS-Exchange-AntiSpam-MessageData-ChunkCount: 1 X-MS-Exchange-AntiSpam-MessageData-0: SRRhBS/91pDKtbTjFKLX8UbRAyw0g/nMCiwcT7GPRIwcbtkqEyWjswKcC+j9d0soBAqp55K3nI3btFBb2Wvg9+7vUvariyVFjiv3mbQ6p4ObFf0JpXmGtcQv/VYU178e9+nSSRMrBwvzVvPipeEZ4vDvT9/OLXYen+bp2siWCmqNnByHayDaMv6l+9Nk3SWZ/bGrMDug4tPywWcxXIM/4zbmcqgXRGwKhq8fCccz1ZXQ25olrw8GFFf7usskYR27BZL+a5joW8l6om3kYQPyR47HwacNTDZf9yp4SiqNY1J1MC0mMpSjQGhtnvIIqqCDDz54qqejFsJnWblZqOtNnRySd1XceNxx6d7aedpSfiPd7o3PYbWbYWvAXZNPVUaXBImC/w2UizaUYu4JFbV7S5/1yklrc1/2jZFijrjUsQV4pcxY0O7Aid6GepBMtkQFaat63p7uigZfcFrRi4MjshUm5dgtDYcqT4CEExBnd5uavE2KuKsZN0qRhEii/c3C0+MQyjRFWB47C/I9xt2VFVuyUI4sE/IaV8w8CYj4KU3EWnW7zouWs73NhkqW1OyCtCXXLnKy7mS9IC27vTI48GALnRVOlWUecbs9CmIbWeRrArHbozXFw7qn6CWK4MKeLDjSGhtfxY84i8Mf2Zb9aYDRCLR4M4SnXzC7wJsaI72dak22UPDBVvoVWbsu4vncq8RNdnKkQ3efiCqVEb4aFHNnqZNw0M/EseiolDqWRUrs9hLfLDkLwyAgLAUO4ce5yNafV4+THrHmUCuaysrpuQQW7VhIY2jYaf1mYycJvTJdpvkuRQ/xnYlD8e+CVhz6aX23ODTbPYyGfrY4GodNT42Ksn5oEuDLrDQ4gwqgoQ4d9tSMpP4PYPxcbD7oV3Dl8ynHTkw6JcN786EKIZTrjIfHwCoOse2iKnrianTaTGGYWmYlZ2tLeyrm5ALqouIpMgfWUT1i7YSFBeP2seHzsn2Zt/KHlgBryfBlgQpe4uaVbLmYrVJgHHUHe1dRZL2XtzgnbnpYjakeXPRpvTVeyLlh4bLTu6FO1n/9+3VhT/WyYiJLCsb12fZUj/oM+u/YcYOnqa8Ya8cRA7N2wVvFqslQpzM8Bmjgj+Dewm3PTAvVtYGWVPZMTykdimI5lCnHUqAjFPWYLjsaUmp3NQYsqYI6/MtkHlNaxpSotrXZ8fcqTjf+vlfhJDGU9rDBPqJGslrOWRZca/6E+P6goomRnv+19g48/OfSD62ZT7BV2xcPZMTBWPoGf2mvyA+A5hnFYQHdlJivXz1EZ666U7i4JwSlmpcJwNhqMBvEwLuBUyKN0qBQbJFXYdPn9K0yzLEEOzg+yULFdUpEWuCv+dvgqu6NL3nRpQU03TYfV6tMMAVsxnm9U92DmCVHPJU8FCagAJLo4HWIXAu1zhHo5Uxh5QFFKwpW+8K83C0mqiunLKY3Me1jRUn3TJjpbJgRPwfrHjKjZ3G6fzo0slIfOn+99/EJJKsMOarw1IYU6sP2pdY3lL9CsbD3KsTahWVaAQyy9r1Kw/zmup2z+YU5cNxb78Tm54Ph5eXVLy7aKhkByl92I6OanyLNETZaF3UxDMg5 X-OriginatorOrg: Nvidia.com X-MS-Exchange-CrossTenant-Network-Message-Id: 68fcd62c-aad3-46ca-5e78-08ddad008046 X-MS-Exchange-CrossTenant-AuthSource: CH3PR12MB8659.namprd12.prod.outlook.com X-MS-Exchange-CrossTenant-AuthAs: Internal X-MS-Exchange-CrossTenant-OriginalArrivalTime: 16 Jun 2025 18:06:22.6274 (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: X3F4N/rOKripv7tiEuaKFyfXYAzNiLb8vS/0mMGgY8VEQv+FqTdQR0AY9t1XuvB0 X-MS-Exchange-Transport-CrossTenantHeadersStamped: SJ1PR12MB6267 AMD IOMMU v1 is unique in supporting contiguous pages with a variable size and it can decode the full 64 bit VA space. Unlike other x86 page tables this explicitly does not do sign extension as part of allowing the entire 64 bit VA space to be supported. The general design is quite similar to the x86 PAE format, except with a 6th level and quite different PTE encoding. This format is the only one that uses the PT_FEAT_DYNAMIC_TOP feature in the existing code as the existing AMDv1 code starts out with a 3 level table and adds levels on the fly if more IOVA is needed. Comparing the performance of several operations to the existing version: iommu_map() pgsz ,avg new,old ns, min new,old ns , min % (+ve is better) 2^12, 65,64 , 62,61 , -1.01 2^13, 70,66 , 67,62 , -8.08 2^14, 73,69 , 71,65 , -9.09 2^15, 78,75 , 75,71 , -5.05 2^16, 89,89 , 86,84 , -2.02 2^17, 128,121 , 124,112 , -10.10 2^18, 175,175 , 170,163 , -4.04 2^19, 264,306 , 261,279 , 6.06 2^20, 444,525 , 438,489 , 10.10 2^21, 60,62 , 58,59 , 1.01 256*2^12, 381,1833 , 367,1795 , 79.79 256*2^21, 375,1623 , 356,1555 , 77.77 256*2^30, 356,1338 , 349,1277 , 72.72 iommu_unmap() pgsz ,avg new,old ns, min new,old ns , min % (+ve is better) 2^12, 76,89 , 71,86 , 17.17 2^13, 79,89 , 75,86 , 12.12 2^14, 78,90 , 74,86 , 13.13 2^15, 82,89 , 74,86 , 13.13 2^16, 79,89 , 74,86 , 13.13 2^17, 81,89 , 77,87 , 11.11 2^18, 90,92 , 87,89 , 2.02 2^19, 91,93 , 88,90 , 2.02 2^20, 96,95 , 91,92 , 1.01 2^21, 72,88 , 68,85 , 20.20 256*2^12, 372,6583 , 364,6251 , 94.94 256*2^21, 398,6032 , 392,5758 , 93.93 256*2^30, 396,5665 , 389,5258 , 92.92 The ~5-17x speedup when working with mutli-PTE map/unmaps is because the AMD implementation rewalks the entire table on every new PTE while this version retains its position. The same speedup will be seen with dirtys as well. The old implementation triggers a compiler optimization that ends up generating a "rep stos" memset for contiguous PTEs. Since AMD can have contiguous PTEs that span 2Kbytes of table this is a huge win compared to a normal movq loop. It is why the unmap side has a fairly flat runtime as the contiguous PTE sides increases. This version makes it explicit with a memset64() call. Tested-by: Alejandro Jimenez Signed-off-by: Jason Gunthorpe --- drivers/iommu/Makefile | 1 + drivers/iommu/generic_pt/Kconfig | 13 + drivers/iommu/generic_pt/fmt/Makefile | 11 + drivers/iommu/generic_pt/fmt/amdv1.h | 385 +++++++++++++++++++++ drivers/iommu/generic_pt/fmt/defs_amdv1.h | 21 ++ drivers/iommu/generic_pt/fmt/iommu_amdv1.c | 15 + include/linux/generic_pt/common.h | 19 + include/linux/generic_pt/iommu.h | 29 ++ 8 files changed, 494 insertions(+) create mode 100644 drivers/iommu/generic_pt/fmt/Makefile create mode 100644 drivers/iommu/generic_pt/fmt/amdv1.h create mode 100644 drivers/iommu/generic_pt/fmt/defs_amdv1.h create mode 100644 drivers/iommu/generic_pt/fmt/iommu_amdv1.c diff --git a/drivers/iommu/Makefile b/drivers/iommu/Makefile index 355294fa9033f3..b17ef9818759be 100644 --- a/drivers/iommu/Makefile +++ b/drivers/iommu/Makefile @@ -3,6 +3,7 @@ obj-y += arm/ iommufd/ obj-$(CONFIG_AMD_IOMMU) += amd/ obj-$(CONFIG_INTEL_IOMMU) += intel/ obj-$(CONFIG_RISCV_IOMMU) += riscv/ +obj-$(CONFIG_GENERIC_PT) += generic_pt/fmt/ obj-$(CONFIG_IOMMU_API) += iommu.o obj-$(CONFIG_IOMMU_SUPPORT) += iommu-pages.o obj-$(CONFIG_IOMMU_API) += iommu-traces.o diff --git a/drivers/iommu/generic_pt/Kconfig b/drivers/iommu/generic_pt/Kconfig index 73b7a54375f9bd..887c585a66699a 100644 --- a/drivers/iommu/generic_pt/Kconfig +++ b/drivers/iommu/generic_pt/Kconfig @@ -32,4 +32,17 @@ config IOMMU_PT IOMMU_PT provides an implementation of the page table operations related struct iommu_domain using GENERIC_PT to abstract the page table format. + +if IOMMU_PT +config IOMMU_PT_AMDV1 + tristate "IOMMU page table for 64 bit AMD IOMMU v1" + depends on !GENERIC_ATOMIC64 # for cmpxchg64 + default n + help + iommu_domain implementation for the AMD v1 page table. AMDv1 is the + "host" page table. It supports granular page sizes of almost every + power of 2 and decodes an full 64 bit IOVA space. + + Selected automatically by an IOMMU driver that uses this format. +endif endif diff --git a/drivers/iommu/generic_pt/fmt/Makefile b/drivers/iommu/generic_pt/fmt/Makefile new file mode 100644 index 00000000000000..a4d83b7e0cf691 --- /dev/null +++ b/drivers/iommu/generic_pt/fmt/Makefile @@ -0,0 +1,11 @@ +# SPDX-License-Identifier: GPL-2.0 + +iommu_pt_fmt-$(CONFIG_IOMMU_PT_AMDV1) += amdv1 + +define create_format +obj-$(2) += iommu_$(1).o + +endef + +$(eval $(foreach fmt,$(iommu_pt_fmt-y),$(call create_format,$(fmt),y))) +$(eval $(foreach fmt,$(iommu_pt_fmt-m),$(call create_format,$(fmt),m))) diff --git a/drivers/iommu/generic_pt/fmt/amdv1.h b/drivers/iommu/generic_pt/fmt/amdv1.h new file mode 100644 index 00000000000000..901fc4a80e9a83 --- /dev/null +++ b/drivers/iommu/generic_pt/fmt/amdv1.h @@ -0,0 +1,385 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2024-2025, NVIDIA CORPORATION & AFFILIATES + * + * AMD IOMMU v1 page table + * + * This is described in Section "2.2.3 I/O Page Tables for Host Translations" + * of the "AMD I/O Virtualization Technology (IOMMU) Specification" + * + * Note the level numbering here matches the core code, so level 0 is the same + * as mode 1. + * + */ +#ifndef __GENERIC_PT_FMT_AMDV1_H +#define __GENERIC_PT_FMT_AMDV1_H + +#include "defs_amdv1.h" +#include "../pt_defs.h" + +#include +#include +#include +#include +#include +#include +#include + +enum { + PT_MAX_OUTPUT_ADDRESS_LG2 = 52, + PT_MAX_VA_ADDRESS_LG2 = 64, + PT_ITEM_WORD_SIZE = sizeof(u64), + PT_MAX_TOP_LEVEL = 5, + PT_GRANULE_LG2SZ = 12, + PT_TABLEMEM_LG2SZ = 12, + + /* The DTE only has these bits for the top phyiscal address */ + PT_TOP_PHYS_MASK = GENMASK_ULL(51, 12), +}; + +/* PTE bits */ +enum { + AMDV1PT_FMT_PR = BIT(0), + AMDV1PT_FMT_D = BIT(6), + AMDV1PT_FMT_NEXT_LEVEL = GENMASK_ULL(11, 9), + AMDV1PT_FMT_OA = GENMASK_ULL(51, 12), + AMDV1PT_FMT_FC = BIT_ULL(60), + AMDV1PT_FMT_IR = BIT_ULL(61), + AMDV1PT_FMT_IW = BIT_ULL(62), +}; + +/* + * gcc 13 has a bug where it thinks the output of FIELD_GET() is an enum, make + * these defines to avoid it. + */ +#define AMDV1PT_FMT_NL_DEFAULT 0 +#define AMDV1PT_FMT_NL_SIZE 7 + +#define common_to_amdv1pt(common_ptr) \ + container_of_const(common_ptr, struct pt_amdv1, common) +#define to_amdv1pt(pts) common_to_amdv1pt((pts)->range->common) + +static inline pt_oaddr_t amdv1pt_table_pa(const struct pt_state *pts) +{ + return oalog2_mul(FIELD_GET(AMDV1PT_FMT_OA, pts->entry), + PT_GRANULE_LG2SZ); +} +#define pt_table_pa amdv1pt_table_pa + +/* Returns the oa for the start of the contiguous entry */ +static inline pt_oaddr_t amdv1pt_entry_oa(const struct pt_state *pts) +{ + pt_oaddr_t oa = FIELD_GET(AMDV1PT_FMT_OA, pts->entry); + + if (FIELD_GET(AMDV1PT_FMT_NEXT_LEVEL, pts->entry) == + AMDV1PT_FMT_NL_SIZE) { + unsigned int sz_bits = oalog2_ffz(oa); + + oa = oalog2_set_mod(oa, 0, sz_bits); + } else if (PT_WARN_ON(FIELD_GET(AMDV1PT_FMT_NEXT_LEVEL, pts->entry) != + AMDV1PT_FMT_NL_DEFAULT)) + return 0; + return oalog2_mul(oa, PT_GRANULE_LG2SZ); +} +#define pt_entry_oa amdv1pt_entry_oa + +static inline bool amdv1pt_can_have_leaf(const struct pt_state *pts) +{ + /* + * Table 15: Page Table Level Parameters + * The top most level cannot have translation entries + */ + return pts->level < PT_MAX_TOP_LEVEL; +} +#define pt_can_have_leaf amdv1pt_can_have_leaf + +static inline unsigned int amdv1pt_table_item_lg2sz(const struct pt_state *pts) +{ + return PT_GRANULE_LG2SZ + + (PT_TABLEMEM_LG2SZ - ilog2(PT_ITEM_WORD_SIZE)) * pts->level; +} +#define pt_table_item_lg2sz amdv1pt_table_item_lg2sz + +static inline unsigned int +amdv1pt_entry_num_contig_lg2(const struct pt_state *pts) +{ + u32 code; + + if (FIELD_GET(AMDV1PT_FMT_NEXT_LEVEL, pts->entry) == + AMDV1PT_FMT_NL_DEFAULT) + return ilog2(1); + + PT_WARN_ON(FIELD_GET(AMDV1PT_FMT_NEXT_LEVEL, pts->entry) != + AMDV1PT_FMT_NL_SIZE); + + /* + * The contiguous size is encoded in the length of a string of 1's in + * the low bits of the OA. Reverse the equation: + * code = log2_to_int(num_contig_lg2 + item_lg2sz - + * PT_GRANULE_LG2SZ - 1) - 1 + * Which can be expressed as: + * num_contig_lg2 = oalog2_ffz(code) + 1 - + * item_lg2sz - PT_GRANULE_LG2SZ + * + * Assume the bit layout is correct and remove the masking. Reorganize + * the equation to move all the arithmetic before the ffz. + */ + code = pts->entry >> (__bf_shf(AMDV1PT_FMT_OA) - 1 + + pt_table_item_lg2sz(pts) - PT_GRANULE_LG2SZ); + return log2_ffz_t(u32, code); +} +#define pt_entry_num_contig_lg2 amdv1pt_entry_num_contig_lg2 + +static inline unsigned int amdv1pt_num_items_lg2(const struct pt_state *pts) +{ + /* + * Top entry covers bits [63:57] only, this is handled through + * max_vasz_lg2. + */ + if (PT_WARN_ON(pts->level == 5)) + return 7; + return PT_TABLEMEM_LG2SZ - ilog2(sizeof(u64)); +} +#define pt_num_items_lg2 amdv1pt_num_items_lg2 + +static inline pt_vaddr_t amdv1pt_possible_sizes(const struct pt_state *pts) +{ + unsigned int isz_lg2 = pt_table_item_lg2sz(pts); + + if (!amdv1pt_can_have_leaf(pts)) + return 0; + + /* + * Table 14: Example Page Size Encodings + * Address bits 51:32 can be used to encode page sizes greater than 4 + * Gbytes. Address bits 63:52 are zero-extended. + * + * 512GB Pages are not supported due to a hardware bug. + * Otherwise every power of two size is supported. + */ + return GENMASK_ULL(min(51, isz_lg2 + amdv1pt_num_items_lg2(pts) - 1), + isz_lg2) & ~SZ_512G; +} +#define pt_possible_sizes amdv1pt_possible_sizes + +static inline enum pt_entry_type amdv1pt_load_entry_raw(struct pt_state *pts) +{ + const u64 *tablep = pt_cur_table(pts, u64) + pts->index; + unsigned int next_level; + u64 entry; + + pts->entry = entry = READ_ONCE(*tablep); + if (!(entry & AMDV1PT_FMT_PR)) + return PT_ENTRY_EMPTY; + + next_level = FIELD_GET(AMDV1PT_FMT_NEXT_LEVEL, pts->entry); + if (pts->level == 0 || next_level == AMDV1PT_FMT_NL_DEFAULT || + next_level == AMDV1PT_FMT_NL_SIZE) + return PT_ENTRY_OA; + return PT_ENTRY_TABLE; +} +#define pt_load_entry_raw amdv1pt_load_entry_raw + +static inline void +amdv1pt_install_leaf_entry(struct pt_state *pts, pt_oaddr_t oa, + unsigned int oasz_lg2, + const struct pt_write_attrs *attrs) +{ + unsigned int isz_lg2 = pt_table_item_lg2sz(pts); + u64 *tablep = pt_cur_table(pts, u64) + pts->index; + u64 entry; + + entry = AMDV1PT_FMT_PR | + FIELD_PREP(AMDV1PT_FMT_OA, log2_div(oa, PT_GRANULE_LG2SZ)) | + attrs->descriptor_bits; + + if (oasz_lg2 == isz_lg2) { + entry |= FIELD_PREP(AMDV1PT_FMT_NEXT_LEVEL, + AMDV1PT_FMT_NL_DEFAULT); + WRITE_ONCE(*tablep, entry); + } else { + unsigned int num_contig_lg2 = oasz_lg2 - isz_lg2; + u64 *end = tablep + log2_to_int(num_contig_lg2); + + entry |= FIELD_PREP(AMDV1PT_FMT_NEXT_LEVEL, + AMDV1PT_FMT_NL_SIZE) | + FIELD_PREP(AMDV1PT_FMT_OA, + oalog2_to_int(oasz_lg2 - PT_GRANULE_LG2SZ - + 1) - + 1); + + /* See amdv1pt_clear_entry() */ + if (num_contig_lg2 <= ilog2(32)) { + for (; tablep != end; tablep++) + WRITE_ONCE(*tablep, entry); + } else { + memset64(tablep, entry, log2_to_int(num_contig_lg2)); + } + } + pts->entry = entry; +} +#define pt_install_leaf_entry amdv1pt_install_leaf_entry + +static inline bool amdv1pt_install_table(struct pt_state *pts, + pt_oaddr_t table_pa, + const struct pt_write_attrs *attrs) +{ + u64 entry; + + /* + * IR and IW are ANDed from the table levels along with the PTE. We + * always control permissions from the PTE, so always set IR and IW for + * tables. + */ + entry = AMDV1PT_FMT_PR | + FIELD_PREP(AMDV1PT_FMT_NEXT_LEVEL, pts->level) | + FIELD_PREP(AMDV1PT_FMT_OA, + log2_div(table_pa, PT_GRANULE_LG2SZ)) | + AMDV1PT_FMT_IR | AMDV1PT_FMT_IW; + if (pts_feature(pts, PT_FEAT_AMDV1_ENCRYPT_TABLES)) + entry = __sme_set(entry); + return pt_table_install64(pts, entry); +} +#define pt_install_table amdv1pt_install_table + +static inline void amdv1pt_attr_from_entry(const struct pt_state *pts, + struct pt_write_attrs *attrs) +{ + attrs->descriptor_bits = + pts->entry & (AMDV1PT_FMT_FC | AMDV1PT_FMT_IR | AMDV1PT_FMT_IW); +} +#define pt_attr_from_entry amdv1pt_attr_from_entry + +static inline void amdv1pt_clear_entry(struct pt_state *pts, + unsigned int num_contig_lg2) +{ + u64 *tablep = pt_cur_table(pts, u64) + pts->index; + u64 *end = tablep + log2_to_int(num_contig_lg2); + + /* + * gcc generates rep stos for the io-pgtable code, and this difference + * can show in microbenchmarks with larger contiguous page sizes. + * rep is slower for small cases. + */ + if (num_contig_lg2 <= ilog2(32)) { + for (; tablep != end; tablep++) + WRITE_ONCE(*tablep, 0); + } else { + memset64(tablep, 0, log2_to_int(num_contig_lg2)); + } +} +#define pt_clear_entry amdv1pt_clear_entry + +static inline bool amdv1pt_entry_write_is_dirty(const struct pt_state *pts) +{ + unsigned int num_contig_lg2 = amdv1pt_entry_num_contig_lg2(pts); + u64 *tablep = pt_cur_table(pts, u64) + + log2_set_mod(pts->index, 0, num_contig_lg2); + u64 *end = tablep + log2_to_int(num_contig_lg2); + + for (; tablep != end; tablep++) + if (READ_ONCE(*tablep) & AMDV1PT_FMT_D) + return true; + return false; +} +#define pt_entry_write_is_dirty amdv1pt_entry_write_is_dirty + +static inline void amdv1pt_entry_set_write_clean(struct pt_state *pts) +{ + unsigned int num_contig_lg2 = amdv1pt_entry_num_contig_lg2(pts); + u64 *tablep = pt_cur_table(pts, u64) + + log2_set_mod(pts->index, 0, num_contig_lg2); + u64 *end = tablep + log2_to_int(num_contig_lg2); + + for (; tablep != end; tablep++) + WRITE_ONCE(*tablep, READ_ONCE(*tablep) & ~(u64)AMDV1PT_FMT_D); +} +#define pt_entry_set_write_clean amdv1pt_entry_set_write_clean + +static inline bool amdv1pt_entry_make_write_dirty(struct pt_state *pts) +{ + u64 *tablep = pt_cur_table(pts, u64) + pts->index; + u64 new = pts->entry | AMDV1PT_FMT_D; + + return try_cmpxchg64(tablep, &pts->entry, new); +} +#define pt_entry_make_write_dirty amdv1pt_entry_make_write_dirty + +/* --- iommu */ +#include +#include + +#define pt_iommu_table pt_iommu_amdv1 + +/* The common struct is in the per-format common struct */ +static inline struct pt_common *common_from_iommu(struct pt_iommu *iommu_table) +{ + return &container_of(iommu_table, struct pt_iommu_amdv1, iommu) + ->amdpt.common; +} + +static inline struct pt_iommu *iommu_from_common(struct pt_common *common) +{ + return &container_of(common, struct pt_iommu_amdv1, amdpt.common)->iommu; +} + +static inline int amdv1pt_iommu_set_prot(struct pt_common *common, + struct pt_write_attrs *attrs, + unsigned int iommu_prot) +{ + u64 pte = 0; + + if (pt_feature(common, PT_FEAT_AMDV1_FORCE_COHERENCE)) + pte |= AMDV1PT_FMT_FC; + if (iommu_prot & IOMMU_READ) + pte |= AMDV1PT_FMT_IR; + if (iommu_prot & IOMMU_WRITE) + pte |= AMDV1PT_FMT_IW; + + /* + * Ideally we'd have an IOMMU_ENCRYPTED flag set by higher levels to + * control this. For now if the tables use sme_set then so do the ptes. + */ + if (pt_feature(common, PT_FEAT_AMDV1_ENCRYPT_TABLES)) + pte = __sme_set(pte); + + attrs->descriptor_bits = pte; + return 0; +} +#define pt_iommu_set_prot amdv1pt_iommu_set_prot + +static inline int amdv1pt_iommu_fmt_init(struct pt_iommu_amdv1 *iommu_table, + const struct pt_iommu_amdv1_cfg *cfg) +{ + struct pt_amdv1 *table = &iommu_table->amdpt; + unsigned int max_vasz_lg2 = PT_MAX_VA_ADDRESS_LG2; + + if (cfg->starting_level == 0 || cfg->starting_level > PT_MAX_TOP_LEVEL) + return -EINVAL; + + if (!pt_feature(&table->common, PT_FEAT_DYNAMIC_TOP) && + cfg->starting_level != PT_MAX_TOP_LEVEL) + max_vasz_lg2 = PT_GRANULE_LG2SZ + + (PT_TABLEMEM_LG2SZ - ilog2(sizeof(u64))) * + (cfg->starting_level + 1); + + table->common.max_vasz_lg2 = + min(max_vasz_lg2, cfg->common.hw_max_vasz_lg2); + table->common.max_oasz_lg2 = + min(PT_MAX_OUTPUT_ADDRESS_LG2, cfg->common.hw_max_oasz_lg2); + pt_top_set_level(&table->common, cfg->starting_level); + return 0; +} +#define pt_iommu_fmt_init amdv1pt_iommu_fmt_init + +static inline void +amdv1pt_iommu_fmt_hw_info(struct pt_iommu_amdv1 *table, + const struct pt_range *top_range, + struct pt_iommu_amdv1_hw_info *info) +{ + info->host_pt_root = virt_to_phys(top_range->top_table); + PT_WARN_ON(info->host_pt_root & ~PT_TOP_PHYS_MASK); + info->mode = top_range->top_level + 1; +} +#define pt_iommu_fmt_hw_info amdv1pt_iommu_fmt_hw_info +#endif diff --git a/drivers/iommu/generic_pt/fmt/defs_amdv1.h b/drivers/iommu/generic_pt/fmt/defs_amdv1.h new file mode 100644 index 00000000000000..0b9614ca6d103c --- /dev/null +++ b/drivers/iommu/generic_pt/fmt/defs_amdv1.h @@ -0,0 +1,21 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2024-2025, NVIDIA CORPORATION & AFFILIATES + * + */ +#ifndef __GENERIC_PT_FMT_DEFS_AMDV1_H +#define __GENERIC_PT_FMT_DEFS_AMDV1_H + +#include +#include + +typedef u64 pt_vaddr_t; +typedef u64 pt_oaddr_t; + +struct amdv1pt_write_attrs { + u64 descriptor_bits; + gfp_t gfp; +}; +#define pt_write_attrs amdv1pt_write_attrs + +#endif diff --git a/drivers/iommu/generic_pt/fmt/iommu_amdv1.c b/drivers/iommu/generic_pt/fmt/iommu_amdv1.c new file mode 100644 index 00000000000000..72a2337d0c5510 --- /dev/null +++ b/drivers/iommu/generic_pt/fmt/iommu_amdv1.c @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2024-2025, NVIDIA CORPORATION & AFFILIATES + */ +#define PT_FMT amdv1 +#define PT_SUPPORTED_FEATURES \ + (BIT(PT_FEAT_FULL_VA) | BIT(PT_FEAT_DYNAMIC_TOP) | \ + BIT(PT_FEAT_FLUSH_RANGE) | BIT(PT_FEAT_FLUSH_RANGE_NO_GAPS) | \ + BIT(PT_FEAT_AMDV1_ENCRYPT_TABLES) | \ + BIT(PT_FEAT_AMDV1_FORCE_COHERENCE)) +#define PT_FORCE_ENABLED_FEATURES \ + (BIT(PT_FEAT_DYNAMIC_TOP) | BIT(PT_FEAT_AMDV1_ENCRYPT_TABLES) | \ + BIT(PT_FEAT_AMDV1_FORCE_COHERENCE)) + +#include "iommu_template.h" diff --git a/include/linux/generic_pt/common.h b/include/linux/generic_pt/common.h index 91869fad33fbdf..b127d8915d48fc 100644 --- a/include/linux/generic_pt/common.h +++ b/include/linux/generic_pt/common.h @@ -131,4 +131,23 @@ enum pt_features { PT_FEAT_FMT_START, }; +struct pt_amdv1 { + struct pt_common common; +}; + +enum { + /* + * The memory backing the tables is encrypted. Use __sme_set() to adjust + * the page table pointers in the tree. This only works with + * CONFIG_AMD_MEM_ENCRYPT. + */ + PT_FEAT_AMDV1_ENCRYPT_TABLES = PT_FEAT_FMT_START, + /* + * The PTEs are set to prevent cache incoherent traffic, such as PCI no + * snoop. This is set either at creation time or before the first map + * operation. + */ + PT_FEAT_AMDV1_FORCE_COHERENCE, +}; + #endif diff --git a/include/linux/generic_pt/iommu.h b/include/linux/generic_pt/iommu.h index 9d2152bc64c0d6..b51de39c03c431 100644 --- a/include/linux/generic_pt/iommu.h +++ b/include/linux/generic_pt/iommu.h @@ -115,4 +115,33 @@ struct pt_iommu_cfg { u8 hw_max_oasz_lg2; }; +/* Generate the exported function signatures from iommu_pt.h */ +#define IOMMU_PROTOTYPES(fmt) \ + int pt_iommu_##fmt##_init(struct pt_iommu_##fmt *table, \ + const struct pt_iommu_##fmt##_cfg *cfg, \ + gfp_t gfp); \ + void pt_iommu_##fmt##_hw_info(struct pt_iommu_##fmt *table, \ + struct pt_iommu_##fmt##_hw_info *info) +#define IOMMU_FORMAT(fmt, member) \ + struct pt_iommu_##fmt { \ + struct pt_iommu iommu; \ + struct pt_##fmt member; \ + }; \ + IOMMU_PROTOTYPES(fmt) + + +struct pt_iommu_amdv1_cfg { + struct pt_iommu_cfg common; + unsigned int starting_level; +}; + +struct pt_iommu_amdv1_hw_info { + u64 host_pt_root; + u8 mode; +}; + +IOMMU_FORMAT(amdv1, amdpt); + +#undef IOMMU_PROTOTYPES +#undef IOMMU_FORMAT #endif From patchwork Mon Jun 16 18:06:08 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jason Gunthorpe X-Patchwork-Id: 897138 Received: from NAM12-BN8-obe.outbound.protection.outlook.com (mail-bn8nam12on2043.outbound.protection.outlook.com [40.107.237.43]) (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 101651DDC23; Mon, 16 Jun 2025 18:06:28 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=fail smtp.client-ip=40.107.237.43 ARC-Seal: i=2; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1750097189; cv=fail; b=tjA+5327GRcStHQDEMGip+eWl1prhbwvC6sg+kGCBAZG40v0ojE7oMgjOJomw9EV0oXoVj/Gkk7fvu4pzvX6PH8zE5+qXvXo7stBNTv6PRkUx9C88MFbbyjkpaKChSrUDm2etEOuTGkWxyTxxbgHmfHIcV0DGbcCZt28T9Gxp3k= ARC-Message-Signature: i=2; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1750097189; c=relaxed/simple; bh=eeRDbU/3fY1tkiQ78NGa8Pks9esi3AF0ocQtO6ULo2w=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: Content-Type:MIME-Version; b=Oyzo/Ds5WRR8bdo2ywkG/+lSEmcQWcKjJbIxei5OWBfJaej1OkpRfpfK6ghVCGiCazqqZrlsJZfQiRzd1xLQECcHrBFWNO5KfuJnPYcTbmx0yKk9ckgG77m2nykGhH8ha+Yaby7S8s6Z/PLMQjKIX4tq1HWaSOk1RQoCxrzNcUE= 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=iTZsVTCt; arc=fail smtp.client-ip=40.107.237.43 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="iTZsVTCt" ARC-Seal: i=1; a=rsa-sha256; s=arcselector10001; d=microsoft.com; cv=none; b=khzYBMe7TAmPAsJkNaEWyzMhDxM5HJcx6EkKYoFt3JXnV3APQ47yh3BOngyZgAOdKfFqnJf07MN8kgQzQaBGVg+kVZe42RL2hTeJZNz/Uu5qT4Z7cslOzcYIZqmG+PiBVe5ND0KGUzmEBnq91yJ2VButSBnQPqDMF8J12xiq+4RcJoKFQ7/6glAqdcTBoHQfW70zDRpZH68tMuJ5gSYxMYEvnwLGM8W3rxdoxNo/OwQYV6YwSPiLvTKIyJAmCTXWsFYunMd3um7w/CYMQVZnKfdBpTJn4393EIv2gyJ8mDqTWCCp1QMW+uDUPDi+LK9Pxf6kRKcr6uXO6DvMQoBR2w== 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=lA596Vm/ZHmNIz+0LwIRqzEPDcGZuT7WHKu+MdE/JJU=; b=mSR08zJ7ldKD2+ennkSdY91d77bmpwn6GnEFWhDnG37DMIJLLLhB2R7JYCBfVipnEOP++lXCUZ2b+vRw0IWqNFk8aaMI1JpAUHNtlc3ut4ghiwX5DNVkijvHDxVnIbQadP8b3NNr+gg6ZdzSEIruhC8zh92Uu5ljRPFJlwhcDtH/iWY9TRxj79qpl6VxfHrMuiubEz8vFBgg6cYb5JSmyxWAggkGk9XA2HIeiblm91F+N8Ip48wyN9RP0rfRL/BSysPJT4wBIZJmhLnnBqSt65HAXvFsZJMt1ynZS07bOYzSFZEQMXXogUNkefPSz6yfoSeEloCC8cXuQkwpGuC0hA== 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=lA596Vm/ZHmNIz+0LwIRqzEPDcGZuT7WHKu+MdE/JJU=; b=iTZsVTCtVJjc4AaYD4uwR6oDRIOd8iIWOb1D6PJSborullXnsVtK5Wxx+SLN1osfFtluU91r0h9V3ThtrXJ+0DoZ2B8rIXU59WgsgYaNcotwW/RO5K6GGb/9kmjaMzcx6XGq0MSJAaut4hFQAmx+7s3/Mp6iXy3ks8Dbh87DsHh5S2q9Ith+lQif5Va036faGKrix5j1EyMBBOguZ0bNDO5cL0YPADVLLoM3a+cCo1G3Cap3giJkcfAEYiR162CFLwF1A/ZhRHV0FHxhaWcyb7fPeTQMhMlWn2hkmJBiapRwZZ2LHPZMOz9KvPXkBCviPjAHOc5irCURBaqXJi6Ikg== Authentication-Results: dkim=none (message not signed) header.d=none;dmarc=none action=none header.from=nvidia.com; Received: from CH3PR12MB8659.namprd12.prod.outlook.com (2603:10b6:610:17c::13) by SJ1PR12MB6267.namprd12.prod.outlook.com (2603:10b6:a03:456::10) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.20.8835.28; Mon, 16 Jun 2025 18:06:22 +0000 Received: from CH3PR12MB8659.namprd12.prod.outlook.com ([fe80::6eb6:7d37:7b4b:1732]) by CH3PR12MB8659.namprd12.prod.outlook.com ([fe80::6eb6:7d37:7b4b:1732%7]) with mapi id 15.20.8835.023; Mon, 16 Jun 2025 18:06:22 +0000 From: Jason Gunthorpe To: Jonathan Corbet , iommu@lists.linux.dev, Joerg Roedel , Justin Stitt , Kevin Tian , linux-doc@vger.kernel.org, linux-kselftest@vger.kernel.org, llvm@lists.linux.dev, Bill Wendling , Nathan Chancellor , Nick Desaulniers , Miguel Ojeda , Robin Murphy , Shuah Khan , Suravee Suthikulpanit , Will Deacon Cc: Alexey Kardashevskiy , Alejandro Jimenez , James Gowans , Michael Roth , Pasha Tatashin , patches@lists.linux.dev Subject: [PATCH v3 05/15] iommupt: Add iova_to_phys op Date: Mon, 16 Jun 2025 15:06:08 -0300 Message-ID: <5-v3-a93aab628dbc+521-iommu_pt_jgg@nvidia.com> In-Reply-To: <0-v3-a93aab628dbc+521-iommu_pt_jgg@nvidia.com> References: X-ClientProxiedBy: DM6PR05CA0063.namprd05.prod.outlook.com (2603:10b6:5:335::32) To CH3PR12MB8659.namprd12.prod.outlook.com (2603:10b6:610:17c::13) Precedence: bulk X-Mailing-List: linux-kselftest@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 X-MS-PublicTrafficType: Email X-MS-TrafficTypeDiagnostic: CH3PR12MB8659:EE_|SJ1PR12MB6267:EE_ X-MS-Office365-Filtering-Correlation-Id: 0e44ca07-5ddf-4cd2-bd2f-08ddad007ede X-MS-Exchange-SenderADCheck: 1 X-MS-Exchange-AntiSpam-Relay: 0 X-Microsoft-Antispam: BCL:0; ARA:13230040|366016|1800799024|7416014|376014|921020; X-Microsoft-Antispam-Message-Info: mfOaDXyqaEEEBm4ochLBwJxwR2rtzhTLgmbuLBfIgin2zXN3z8Q6mcBbjoeKumB4RznokIsduN+EiqGu5HYKy/Qa7h4kszZLBZhIoxAqwmJp8XJQApAAwWQWngnfkUOMKPGsPFTQRecJlqB5OBmVF7t69/gkecBz9HJ94MCJUGEm5zE+KQznPjwmBAVfcdX3ugvnBRSg47XNA7+yeyYiKoyWBXzZWipZ0hG70kGo+dFAONxWAKB9DI4eLCJ3q1sBQGoSpymTqlG+YPiaeebtw9B3m9CRrboUQVXklJVT5enY/7hiJ6X/aB3xpNbjARw/42012HCCjTAFsZWCWZV7c0zzpqmHEeVl6TTvMZsEHvZrkEOBTbGo1lQFrWNVxTzU4sR0284H3xHZYgcYWh2Aa1aY9d8n4gbRJDT1IB7wHivG38jXG1cK1BTGPqWytuZU5NCUTTC+8oJJhygk/oiXp6uyFH/jnvsREL/wtZ2qHK3A9QjcD8RjKpdsGPFTWL1a2ksp0UoCV0gp1X/fom0SybKSbx7GrtYn5VEqmwP/cEWUO3Bjs/aBCpvaLNTdlc0kIwaBJqjQqVlO6r2uXrwmbFvImlnnHb8XC0UTHJ/lEfc/RfVLOLhgjj2szkPu+qSuYbW2JyHGLVb64w9r5BY7dzxEK3ED97i3nFgum2c0o/CKvmhIbzBR1NcSOArHnoywxyf9/xjMg3E4NOlzoMll6cho1CGL/oFE4FaWKmMYfUDniK1R1m/IyYrpmwHwND7ltRJ3EkDLjkN9NvoL27PVos0Ma2+5CZ/gXIBHZCyZs9RCekpNfVaUs+YIvuATXcDIr1kl/QSwVE1rMG54AWKnc7YrISaeVp7DqOyYOm2WhBNNcoPksSwuk3LPYF/Xdy59DESUm8u+NSP/0KRQDgeJ3E2YTqTSmm3/jEsBI62QS7e14qvM7lAXtgMsO1/dMPo4X7tLJR13I66oXIxWpHv/PjVtujVf94OhRP9xleuCMRdOAlcYZ6lME3Zp4/zawuurz2gnzQiCh0NL7DMbpdYJxg6EUnQLI8UR6rJ+4ROiQPeBF0pSzcjZSFVAoJif11bgjrWsaPzzPe/e4/vTDXE1fUcXSFU0WuTjSBB7GLxBCB77xN9/XqlY0W3rB0PRhmKvvL6tgNAl+ojjJj6x2WVcqkHkVlueuBjmt3LXH8Gdas9S/NGSu/KyFvgtfkLmUpeUjS4DzWUhzCWt58WxjaoTBGTANlQ/BRdldIIqQ1EJovtmS/f4XgoEQj2OGnEaupE7oC4WF/m2rBANFyuFZ7yghkH4uCpvVUtDdVwv0AEy/FW8v+pVkyeYf11CBMn+KFzFn9fGt8mimi8Vj68PmvnXITWlmqv1IgSbCu+hcMjXdZhrKctq8ZZZbvceBVjNx9GfI0FSQ/Zlvljf66F+ra9SBMWs0dE5BAhEnsFYpo9a8lx7S3Dgr3Wu2Y3FyF9Qqk8T X-Forefront-Antispam-Report: CIP:255.255.255.255; CTRY:; LANG:en; SCL:1; SRV:; IPV:NLI; SFV:NSPM; H:CH3PR12MB8659.namprd12.prod.outlook.com; PTR:; CAT:NONE; SFS:(13230040)(366016)(1800799024)(7416014)(376014)(921020); DIR:OUT; SFP:1101; X-MS-Exchange-AntiSpam-MessageData-ChunkCount: 1 X-MS-Exchange-AntiSpam-MessageData-0: oNUp5b93vmmboAdi15aWsL2H3FwQN+AcaLS5NurNPICSZfMyDThTzscpGphOFUZmsfc94NgSgRIXL2v6rwPRgp/f0xbqmtTNfTV3++FIBaVBXlbUhyTuH/r0X1rBVk3wVgpOUuw7SRT259tYZSRdwDR8lTNHHQ0RW0t9OpP0IVYKFN7583RsYTEBxUmR163lxtZWspVvL1lDtC7AH5Qzfd+tp4QYJzAsjr4jimfQKgclffJv3BZIHJgsBGQYtOO2zAzwTA1gEJheA44JmEvs+RlrKJ8dJW1Qrjxbm14JqCLfH/jJRda0WjnbhEdg+h4Z6XjWizCyWyJfttGxNqicvuRwbebsyk3ONf5TiIJORaywRISBRilcFHcEq7Lj/yEbj987zCBwaYXX/FdtWdeRkmNAAAGGWExVFUHfLoI5NlPT0uz6uow/PERgZybo0uRDsVy4Eq+dq9Wsux+xiKB2XBIJCIs7eXk6tVMoIR6Dlucy8pwgekG3LM4f3IaOJTvI3jZ+39qi76uITyzbEignG9tYkoM6qt/kW89gsqskwSEwQ5Py6Bd9W9XQzOP1JGGs56VWFUeM+ESAL9sLL2VGbfOK2zlOqzvtNwtVV6AW+VJX0+u5qocm0c6edfb49DRfmJG2PsSBgSow3MTwd151+9OSZFTiLz2Zcu97zf8jtJkD5esp2QRaY5DLPASru2uJO/Cn5xxl24clJoEEmVki+yiZsuGVQq40u+75V17/k+TGPPrLLLLAFp+BP39KC8LbQ5gtNvwQvrAvxYJhZf0xqdw7j5hhdiHVMXx1l+QSen87OT7Gy8MRLdEM6/jOanv9+5KAL0JAuCVE/iklHmr2A4lsqNvlmgbVgWWo1CXzWawzJUK3jIkJiRsAT2igRfM0vGXCipCE03P7h443qpsS5gYICle+BU6va3Bz2b/bOLgmPl9E5EK9RQw8OB/GCPrGI4HYLdNaaTuLysL8QgabFquNmkGZdBGax72pypJRs/0p/1cyM6lXSQlFOrBpt1f3ZdSpf9v5A1b2NN38X4DbBrlnk0LRus45cNEnZge1GIGzD6ou9TFtJgjCXUGEpN5BFQb8UvFs4izlpvaxNZTkHWCbIjxQPKlryfhMCf4gLG10SDeS2TEUJGdlkOuF/i2ocFoyqK1Siy4FHoSW/uvDpDmaGtzg5TWLBs2k38dBansl+kAKudo9Re0UL/PU0GJ4DzVKsjYzJ6pDTXZf2OovFZ/bU2NZWfC4bSPRbEK+zWLWBD4/RGNbtp+WzRGw67/00OibzB5svtCy4MU3q96JxBsU4qFV1iIJ9GmhmrelVzIN2r6aoVoWCciWwWDyMNnFhcmHRoP8oNpa1dpAVyqFeJdgQb25yEsI26HBn8r9DYaRw/WOIk7sEKAHmrvVNyB1h5Rc8iN8dFIXVvWo+4nFKkHFQNe7P0zMTgYWDNdTqjfJBPh4cKFYHfP7RbzXN9hesSD3ypUAUL5R6shjaumOpYTIg1SGxGxXA2rWp8fC4eU/8KMouNRfeUan6z6i1pVrI/kLK3xE6oZlDko9o9b0y4zYq+8e1zO681icRXX3ovX4aHmE8q66qF2/8gazUeBI X-OriginatorOrg: Nvidia.com X-MS-Exchange-CrossTenant-Network-Message-Id: 0e44ca07-5ddf-4cd2-bd2f-08ddad007ede X-MS-Exchange-CrossTenant-AuthSource: CH3PR12MB8659.namprd12.prod.outlook.com X-MS-Exchange-CrossTenant-AuthAs: Internal X-MS-Exchange-CrossTenant-OriginalArrivalTime: 16 Jun 2025 18:06:20.3473 (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: OxeM2KeVo3e2IcbD1HovkLFqrfOsB0GuTBFOmhY/tndErCChBjgTP+Xfo27NbhkU X-MS-Exchange-Transport-CrossTenantHeadersStamped: SJ1PR12MB6267 iova_to_phys is a performance path for the DMA API and iommufd, implement it using an unrolled get_user_pages() like function waterfall scheme. The implementation itself is fairly trivial. Tested-by: Alejandro Jimenez Signed-off-by: Jason Gunthorpe --- drivers/iommu/generic_pt/iommu_pt.h | 105 ++++++++++++++++++++++++++++ include/linux/generic_pt/iommu.h | 34 +++++++-- 2 files changed, 134 insertions(+), 5 deletions(-) diff --git a/drivers/iommu/generic_pt/iommu_pt.h b/drivers/iommu/generic_pt/iommu_pt.h index 205c232bda68b5..bae87ff34b8043 100644 --- a/drivers/iommu/generic_pt/iommu_pt.h +++ b/drivers/iommu/generic_pt/iommu_pt.h @@ -17,6 +17,111 @@ #define DOMAIN_NS(op) CONCATENATE(CONCATENATE(pt_iommu_, PTPFX), op) +static int make_range_ul(struct pt_common *common, struct pt_range *range, + unsigned long iova, unsigned long len) +{ + unsigned long last; + + if (unlikely(len == 0)) + return -EINVAL; + + if (check_add_overflow(iova, len - 1, &last)) + return -EOVERFLOW; + + *range = pt_make_range(common, iova, last); + if (sizeof(iova) > sizeof(range->va)) { + if (unlikely(range->va != iova || range->last_va != last)) + return -EOVERFLOW; + } + return 0; +} + +static __maybe_unused int make_range_u64(struct pt_common *common, + struct pt_range *range, u64 iova, + u64 len) +{ + if (unlikely(iova > ULONG_MAX || len > ULONG_MAX)) + return -EOVERFLOW; + return make_range_ul(common, range, iova, len); +} + +/* + * Some APIs use unsigned long some use dma_addr_t as the type. Dispatch to the + * correct validation based on the type. + */ +#define make_range_no_check(common, range, iova, len) \ + ({ \ + int ret; \ + if (sizeof(iova) > sizeof(unsigned long) || \ + sizeof(len) > sizeof(unsigned long)) \ + ret = make_range_u64(common, range, iova, len); \ + else \ + ret = make_range_ul(common, range, iova, len); \ + ret; \ + }) + +#define make_range(common, range, iova, len) \ + ({ \ + int ret = make_range_no_check(common, range, iova, len); \ + if (!ret) \ + ret = pt_check_range(range); \ + ret; \ + }) + +static __always_inline int __do_iova_to_phys(struct pt_range *range, void *arg, + unsigned int level, + struct pt_table_p *table, + pt_level_fn_t descend_fn) +{ + struct pt_state pts = pt_init(range, level, table); + pt_oaddr_t *res = arg; + + switch (pt_load_single_entry(&pts)) { + case PT_ENTRY_EMPTY: + return -ENOENT; + case PT_ENTRY_TABLE: + return pt_descend(&pts, arg, descend_fn); + case PT_ENTRY_OA: + *res = pt_entry_oa_full(&pts); + return 0; + } + return -ENOENT; +} +PT_MAKE_LEVELS(__iova_to_phys, __do_iova_to_phys); + +/** + * iova_to_phys() - Return the output address for the given IOVA + * @iommu_table: Table to query + * @iova: IO virtual address to query + * + * Determine the output address from the given IOVA. @iova may have any + * alignment, the returned physical will be adjusted with any sub page offset. + * + * Context: The caller must hold a read range lock that includes @iova. + * + * Return: 0 if there is no translation for the given iova. + */ +phys_addr_t DOMAIN_NS(iova_to_phys)(struct iommu_domain *domain, + dma_addr_t iova) +{ + struct pt_iommu *iommu_table = + container_of(domain, struct pt_iommu, domain); + struct pt_range range; + pt_oaddr_t res; + int ret; + + ret = make_range(common_from_iommu(iommu_table), &range, iova, 1); + if (ret) + return ret; + + ret = pt_walk_range(&range, __iova_to_phys, &res); + /* PHYS_ADDR_MAX would be a better error code */ + if (ret) + return 0; + return res; +} +EXPORT_SYMBOL_NS_GPL(DOMAIN_NS(iova_to_phys), "GENERIC_PT_IOMMU"); + struct pt_iommu_collect_args { struct iommu_pages_list free_list; u8 ignore_mapped : 1; diff --git a/include/linux/generic_pt/iommu.h b/include/linux/generic_pt/iommu.h index b51de39c03c431..1fe5106997f833 100644 --- a/include/linux/generic_pt/iommu.h +++ b/include/linux/generic_pt/iommu.h @@ -116,11 +116,13 @@ struct pt_iommu_cfg { }; /* Generate the exported function signatures from iommu_pt.h */ -#define IOMMU_PROTOTYPES(fmt) \ - int pt_iommu_##fmt##_init(struct pt_iommu_##fmt *table, \ - const struct pt_iommu_##fmt##_cfg *cfg, \ - gfp_t gfp); \ - void pt_iommu_##fmt##_hw_info(struct pt_iommu_##fmt *table, \ +#define IOMMU_PROTOTYPES(fmt) \ + phys_addr_t pt_iommu_##fmt##_iova_to_phys(struct iommu_domain *domain, \ + dma_addr_t iova); \ + int pt_iommu_##fmt##_init(struct pt_iommu_##fmt *table, \ + const struct pt_iommu_##fmt##_cfg *cfg, \ + gfp_t gfp); \ + void pt_iommu_##fmt##_hw_info(struct pt_iommu_##fmt *table, \ struct pt_iommu_##fmt##_hw_info *info) #define IOMMU_FORMAT(fmt, member) \ struct pt_iommu_##fmt { \ @@ -129,6 +131,28 @@ struct pt_iommu_cfg { }; \ IOMMU_PROTOTYPES(fmt) +/* + * A driver uses IOMMU_PT_DOMAIN_OPS to populate the iommu_domain_ops for the + * iommu_pt + */ +#define IOMMU_PT_DOMAIN_OPS(fmt) \ + .iova_to_phys = &pt_iommu_##fmt##_iova_to_phys, + +/* + * The driver should setup its domain struct like + * union { + * struct iommu_domain domain; + * struct pt_iommu_xxx xx; + * }; + * PT_IOMMU_CHECK_DOMAIN(struct mock_iommu_domain, xx.iommu, domain); + * + * Which creates an alias between driver_domain.domain and + * driver_domain.xx.iommu.domain. This is to avoid a mass rename of existing + * driver_domain.domain users. + */ +#define PT_IOMMU_CHECK_DOMAIN(s, pt_iommu_memb, domain_memb) \ + static_assert(offsetof(s, pt_iommu_memb.domain) == \ + offsetof(s, domain_memb)) struct pt_iommu_amdv1_cfg { struct pt_iommu_cfg common; From patchwork Mon Jun 16 18:06:09 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jason Gunthorpe X-Patchwork-Id: 897135 Received: from NAM04-DM6-obe.outbound.protection.outlook.com (mail-dm6nam04on2081.outbound.protection.outlook.com [40.107.102.81]) (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 92FDC288514; Mon, 16 Jun 2025 18:06:32 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=fail smtp.client-ip=40.107.102.81 ARC-Seal: i=2; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1750097195; cv=fail; b=VPioxnD+ktP36ESKnNsr4FCW7o/7fY/c3IxHWJKqLdZ9CEgOSNGqGrNRvXlTIC7j5I+uemBMCd0DIYW5Td8vC49L33I8LxnxMBglXEK35D7uKMbveoKxDfhh0MI3vqWQG2Yn6iDUWAmcmxxZubBoAoDwQu5VXEC3DJGzE3eGGhM= ARC-Message-Signature: i=2; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1750097195; c=relaxed/simple; bh=2Cm4vU47Spy4nhg0B8n0SgmFi/+NF9pEj64dlP7CSKw=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: Content-Type:MIME-Version; b=F6pVgMRcanku6MqbUkuk4SGRMenfA33yRgH4vdNfNlpdtTwRGBtqip6pqN5h51RuNeKDH5bfJkgVdxhIBKJtftPS+hKsYcbCxQ3LyUe/XJZ8G5MXygEoF1kD7i6lvPH/LGxKYQ5TzFyl3W3MXe1CAo7R/6ui0vAkAcd0uIvwgfA= 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=DGUU28HH; arc=fail smtp.client-ip=40.107.102.81 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="DGUU28HH" ARC-Seal: i=1; a=rsa-sha256; s=arcselector10001; d=microsoft.com; cv=none; b=HsiBrMRgPyvSw6nmezoI29KvfAr12y9sAM5ejut0ncar5Sr6q0OEGBxTrjS6Gsp4bzBz6YfnNugWVs/yQ+C50oEJQ7NpKj0X6FAm9Na27FhhZqqxvvuTTOk4EDjYHcKDByCY52VrGiwN8y+s1QHURhJx/RfO+W3XbRi3+n8Zuo3uD3jgVjTOEIl3LQAw6Qb9yOyjlK1GHjj8imxDiKUHd1rVcJ1g1AnY3n1nDpzw2IBBxyoTo47YqqvfsqzLxKnq0dLH7jCPn7kCq5TyQDodyy86br7VniC1A8bzJq9FGG8k2fs2SWdmHly+K8DYy+/EnVXv0smX8jZFDdGaZ3GMLg== 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=HHYzF1TfVjUtNy9H9SEGfJ7AGNjegIdlguaGjw9z4qE=; b=f+4UuhH48tiM7qYquUbyq1IIF9S1vGjSjyWLgZvRyr3ETYYgPdEZDlecpckvau7enV/g9I0Di6yiGHd+Ph6lnfGJeGzU/Xt5bX+sSoxpRg1hD30aFPrrVziWa8AaqZJ8ph8XKMumDT79g7Ia1lkDnb/BlE2gIG8v02U9wNSHp5WLFlRNx8IUUZWWh8YDpv/OsjWGRsT8d0SYzfjQzi1eYhsUJtSsZxzPNI7//32ZHhgtKmFMT7LuVrS+w9hB0ghusqEVgcP9KGDzggIRK+kRpuRbPs8Qg89R7LiOntmffWZZ67KJY2AT2qrDLYCrzRor0VCoApNdWgCoINR980weKg== 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=HHYzF1TfVjUtNy9H9SEGfJ7AGNjegIdlguaGjw9z4qE=; b=DGUU28HHwpATyI7F3kqZWspjHBTdol6nTuM0miO7tNhsE9X5+f8KEwuppQXgDlZyIOZxhoNRyMdC9N67c2ETL+9a8L2sWs9sW8KRrzCD3/85nX2o5iDglgDJZKRQPbu9ABl6aFmtfZ+3xJlMzPSLgrgf7SLxIi7e8y3jCCP3n+NQ9pFm/4PblMTFYbu4VaPhVe2QRlGpHRbuyqvBtEA+cWBxL3Na1wVnm/Ss/UbnTWY5QdqXZLliRB2ZKxjMWAaFMPm3vlO6NHnJWdDUpKn+yD/VZikYg9jc6rjB38D4XopaxfYw+8GxzxdFXpHFeVtsmY7R3c05uCzRV7G4BePXSQ== Authentication-Results: dkim=none (message not signed) header.d=none;dmarc=none action=none header.from=nvidia.com; Received: from CH3PR12MB8659.namprd12.prod.outlook.com (2603:10b6:610:17c::13) by SN7PR12MB7321.namprd12.prod.outlook.com (2603:10b6:806:298::14) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.20.8835.26; Mon, 16 Jun 2025 18:06:29 +0000 Received: from CH3PR12MB8659.namprd12.prod.outlook.com ([fe80::6eb6:7d37:7b4b:1732]) by CH3PR12MB8659.namprd12.prod.outlook.com ([fe80::6eb6:7d37:7b4b:1732%7]) with mapi id 15.20.8835.023; Mon, 16 Jun 2025 18:06:29 +0000 From: Jason Gunthorpe To: Jonathan Corbet , iommu@lists.linux.dev, Joerg Roedel , Justin Stitt , Kevin Tian , linux-doc@vger.kernel.org, linux-kselftest@vger.kernel.org, llvm@lists.linux.dev, Bill Wendling , Nathan Chancellor , Nick Desaulniers , Miguel Ojeda , Robin Murphy , Shuah Khan , Suravee Suthikulpanit , Will Deacon Cc: Alexey Kardashevskiy , Alejandro Jimenez , James Gowans , Michael Roth , Pasha Tatashin , patches@lists.linux.dev Subject: [PATCH v3 06/15] iommupt: Add unmap_pages op Date: Mon, 16 Jun 2025 15:06:09 -0300 Message-ID: <6-v3-a93aab628dbc+521-iommu_pt_jgg@nvidia.com> In-Reply-To: <0-v3-a93aab628dbc+521-iommu_pt_jgg@nvidia.com> References: X-ClientProxiedBy: YT4PR01CA0131.CANPRD01.PROD.OUTLOOK.COM (2603:10b6:b01:d5::28) To CH3PR12MB8659.namprd12.prod.outlook.com (2603:10b6:610:17c::13) Precedence: bulk X-Mailing-List: linux-kselftest@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 X-MS-PublicTrafficType: Email X-MS-TrafficTypeDiagnostic: CH3PR12MB8659:EE_|SN7PR12MB7321:EE_ X-MS-Office365-Filtering-Correlation-Id: b88fecfe-0af0-435f-8a42-08ddad008263 X-MS-Exchange-SenderADCheck: 1 X-MS-Exchange-AntiSpam-Relay: 0 X-Microsoft-Antispam: BCL:0; ARA:13230040|366016|1800799024|7416014|376014|921020; X-Microsoft-Antispam-Message-Info: 5CHZX5sQbCPz8KEShNV5OaU9/OAT3n08iaX1dyw7haFqSaMwEC9kLq9DV2s0E8IUhAfUYkz/9pJUIq0q9gZW5mZA1OLWuu1clZ1UwTn9L2I+3XwyQWasNfCwNEnED0vCzDIm61+EoNgBFngYNdZ4HgNJNWMdgnKt2O9BUJkgIiIwk06gprucx1HYBL/4nIyAdHhIULt/uUjaTP3kfE2dxytdmgu+Zy1RIpMbAc4r5Ajw4Tpr/S+Xv+rXHJPmnySrmhrvj4c4a9vNtzTqB4EcjQkouqDVWKvRLle8+L7QOoukGmJoVvvBR5mCXfTPWysr0JPHAfyVD9b0Afa8JCdJRrgY63YwipNqv6oKX7lRCAEXXb4YJw0QcZwoJcM03cCgMBXfgmdtLD45hHk7EMfiLpOuBbBBuxEDVW/3Sf+7G9HlubnEQKPO7ez3XSnSqAu9CjQblFcYsT+altfj0oqhDCGQWuQ9cy9VkOcoAP9JFyKMh1LTlF/D1mL+awcX7ewy8n6yy/h+vVmQ7v08qmztQYXrzzk8OXayPW2dnSb3tUQe4DOyG2wGpK1oH+s7scxzfR/f8G+zYAjArmIqDISBt5GIEjfwIHGEhRZxv5DfaeE6zB91zVS+/GU8qPM5sC9wrAioiofHMkoLidM+gNDmqOUZ4UbAgwZ9FqdV4pxN/dE74lp0j4oJrbif9W06seZteMnTnFih+gEm8leHPIWrkz3aOdLMl61OePM1XwkK6ivjDqvOZ+3OgzNc6Mk8UC3sj10SvIaYJ7yuOriZi3Gn+w/e1g8kvaWKK/aNM0XPZJm88hM5ByS94P63WiQ2lMcJj/vSnGcV2Duy2jQ9PrHMGZh5zWftSphJkvGTpG9aUTwc77oOFLd3WqFIF1koGqxZ1jOD2EPlh3aOSbUqVt4/0g+W4A+LjZnYPpLom92JC/Xgp85G3+UXeNXzg2WdnM+XcZ9iOdzp/gp+AiDmhbyZWgV5MHf+LoBCnb4DK5kgf0tfBIhD3fl9M7kMYlSbyGE4J6YRcWn2R68WytqmFwH4mvZno4u8DopjlEDFcx5mfix9MW4j4XNWfJs3LlyUOv4/TDadYboRId0TxTYYiMKDQvtJj6h8OaSYiloPJTaAxsjSsQ3Ps6C+prCsJgPd+/pD2rNq5/s729QcsiNm2yxWUAWB1zqZPQrq8O7q7uicmp5zKjuC11cpIqiKdulGUhMRcpUuogapwC3FeEHrl9zBTGkPOM11yPq77cHbsYA04vuT60Y9Hjly6ujFgoAbrcN8VQmnZkkB2vmitV7R8gmcGYWD1TqFixD3Eccph9OdKkANdKdZVw/yjG6r35oYSAcT6jPCe4mMYdGdGd9WBaRnHZplpOMp/zS/5wzGkKt+jlN6CixJjj++eWyfIVjCI1rpIhbwgtG6Onw/gq4athDnTelAtbt4doAgUeN3W9hJ72bLP+oyZFcxrIv3fBH4eu719c8dOoZ6qGff8RsmI4OP7Q== X-Forefront-Antispam-Report: CIP:255.255.255.255; CTRY:; LANG:en; SCL:1; SRV:; IPV:NLI; SFV:NSPM; H:CH3PR12MB8659.namprd12.prod.outlook.com; PTR:; CAT:NONE; SFS:(13230040)(366016)(1800799024)(7416014)(376014)(921020); DIR:OUT; SFP:1101; X-MS-Exchange-AntiSpam-MessageData-ChunkCount: 1 X-MS-Exchange-AntiSpam-MessageData-0: B55/JAJhpkPVvQt6TRHkpo/53Iz16O3i84iSAX/6MYx9thlNjxdETCcEjcqWTJuP0hWXOATCNy4qIP/xiLuVTovHVun5SrF2o2lC3YeFRHP2ao4nwhzA5dhatMCiJOb78FG95ZqifZA4N5T6Vb1dQe3yTaJ5aihK16QKe5LLd39cZfYyYO5WpMOtx/DSaRXUCgSce45SfbxRwLsQilCe2HCnRj48KNr/Ukmf5IL3wXv8iK6+R9DC+5YV7zhejHTwobktwmh3P7lI5zUxl8yeuW2zGl0+5XtaqSraVfmOwZOwY5AQN++27oaSv1lXKRkIgLeFpualpD1NQIVLmorEChFyzOXOlTZ4tPxlXPZzVCmgeL5VsSQC/03pkRoV953EydcoYyiKyMDnmKyUmItg9qWt+6qP9dVRWuIcWA+ys9IvlDEPw6nOxOLi7cv/hU7yPutESYqMxe3qtW17UzgXvSxp90IfDgFvwmEyvUx1kSbOLw+RNd9rgnuyOE8vzrHuPVSnw15Uh+qbm7yFOK/m7dfxqY8dOTLQU+PFGASF2Bk0OzB1zgs61MuvfWOWgDHPM51LYKLFNkTzpXFtxsNPvyWbqsJSQA0SVNZoBRfT5zMH1YjP57IoOnwAxtPyMaGmsNhZuVFlPO01Iy5sNv3NpinLzuPr7VvAUsRYWA88BztNcJeLsq3NgAKzdSLAIJYMF8WZHpN75sk7cB2iDeKt4awFcStFUc0GIThY/W5fRbjob7/CUzSDHoyJselCJtqKx75w/oLN19Xa0phnPKoTKMkt8P/UA82uAKo1uQpFfIrMVWxAVw1hI09djK5gItA4UZseORxTcQdgJYAFi/54BoMnCnN2VRq4sZP/wcs1Z5EbugI5Iu7pAa6wGiC3O3I/oJIBKudeboGKwqDLfUWzeCXVdVIzWcvbtzYTqCbFawdevH37ZD3Io5ewqMxTOhyaBpFDgKqRDPWBV4ICfKhKXQb0BPGVN96OCqGiVHoLiz3IqeCksmh/ztWG76poaj9M8oF4pQzCxMy93yB5uCATVyMB0TloTBBM/LkD2IQJCaqPhZxi4HmFNyx/lNV0XKofcl48bBGev/1/EsUY3j1gfq0CBV+/PR1SvwpAVHxzR1v0o7UH3WnwtxtE8B07WOnaphlRwL6SgSgEi1VTBE6zfYdRGK5jCT7uZXBat1ILlrNn7l7McyGqclYZmSOI132leqM0OQWDTemIA5io2TiSKaEL3NpdGA9H2Tr1D8tM8J4qGMcJ3EmNu9tzdekvDTlQRP1BOvDFMo6dRIU+NqIhmqFMLT3Dxddgxas9N62vWB09H4xGWuQHhjXo0i3guMEkOLnoizLJYkJPQZk+DLV6cHSdwxO3oWYP19DWok/bNSOYx62XB3v8gK4Qys0tg+kkCIVSJAydv7txQgZILt5aTkopmtKQ7C7PFbhuSsTP0jPEIBoHz/SzcKJSFQzs4EefWzRNxQkUJGrEwtZKw/ECSemZq4QTxrJCXBZyG3HHEJxbED/lknjaRGWZvGgsYr9zgi7CTtHz9Mn8u5SeqAwZxOo+w47lAh6G2jVGx3uNA0McWay4SI0AHlj07porKPsQ X-OriginatorOrg: Nvidia.com X-MS-Exchange-CrossTenant-Network-Message-Id: b88fecfe-0af0-435f-8a42-08ddad008263 X-MS-Exchange-CrossTenant-AuthSource: CH3PR12MB8659.namprd12.prod.outlook.com X-MS-Exchange-CrossTenant-AuthAs: Internal X-MS-Exchange-CrossTenant-OriginalArrivalTime: 16 Jun 2025 18:06:25.9582 (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: vQeOSjWwwYg1mZBgY6Fr8bfr/I7getGnc/ElGERnGnSWx4pxW50iW9398Mtb/XEs X-MS-Exchange-Transport-CrossTenantHeadersStamped: SN7PR12MB7321 unmap_pages removes mappings and any fully contained interior tables from the given range. This follows the now-standard iommu_domain API definition where it does not split up larger page sizes into smaller. The caller must perform unmap only on ranges created by map or it must have somehow otherwise determined safe cut points (eg iommufd/vfio use iova_to_phys to scan for them) A future work will provide 'cut' which explicitly does the page size split if the HW can support it. unmap is implemented with a recursive descent of the tree. If the caller provides a VA range that spans an entire table item then the table memory can be freed as well. If an entire table item can be freed then this version will also check the leaf-only level of the tree to ensure that all entries are present to generate -EINVAL. Many of the existing drivers don't do this extra check. This version sits under the iommu_domain_ops as unmap_pages() but does not require the external page size calculation. The implementation is actually unmap_range() and can do arbitrary ranges, internally handling all the validation and supporting any arrangment of page sizes. A future series can optimize __iommu_unmap() to take advantage of this. Freed page table memory is batched up in the gather and will be freed in the driver's iotlb_sync() callback after the IOTLB flush completes. Tested-by: Alejandro Jimenez Signed-off-by: Jason Gunthorpe --- drivers/iommu/generic_pt/iommu_pt.h | 155 ++++++++++++++++++++++++++++ include/linux/generic_pt/iommu.h | 10 +- 2 files changed, 163 insertions(+), 2 deletions(-) diff --git a/drivers/iommu/generic_pt/iommu_pt.h b/drivers/iommu/generic_pt/iommu_pt.h index bae87ff34b8043..2729422d12dfcd 100644 --- a/drivers/iommu/generic_pt/iommu_pt.h +++ b/drivers/iommu/generic_pt/iommu_pt.h @@ -14,6 +14,29 @@ #include #include "../iommu-pages.h" #include +#include +#include + +static void gather_range_pages(struct iommu_iotlb_gather *iotlb_gather, + struct pt_iommu *iommu_table, pt_vaddr_t iova, + pt_vaddr_t len, + struct iommu_pages_list *free_list) +{ + struct pt_common *common = common_from_iommu(iommu_table); + + if (pt_feature(common, PT_FEAT_FLUSH_RANGE_NO_GAPS) && + iommu_iotlb_gather_is_disjoint(iotlb_gather, iova, len)) { + iommu_iotlb_sync(&iommu_table->domain, iotlb_gather); + /* + * Note that the sync frees the gather's free list, so we must + * not have any pages on that list that are covered by iova/len + */ + } else if (pt_feature(common, PT_FEAT_FLUSH_RANGE)) { + iommu_iotlb_gather_add_range(iotlb_gather, iova, len); + } + + iommu_pages_list_splice(free_list, &iotlb_gather->freelist); +} #define DOMAIN_NS(op) CONCATENATE(CONCATENATE(pt_iommu_, PTPFX), op) @@ -167,6 +190,138 @@ static inline struct pt_table_p *table_alloc_top(struct pt_common *common, log2_to_int(pt_top_memsize_lg2(common, top_of_table))); } +struct pt_unmap_args { + struct iommu_pages_list free_list; + pt_vaddr_t unmapped; +}; + +static __maybe_unused int __unmap_range(struct pt_range *range, void *arg, + unsigned int level, + struct pt_table_p *table) +{ + struct pt_state pts = pt_init(range, level, table); + struct pt_unmap_args *unmap = arg; + unsigned int num_oas = 0; + unsigned int start_index; + int ret = 0; + + _pt_iter_first(&pts); + start_index = pts.index; + pts.type = pt_load_entry_raw(&pts); + /* + * A starting index is in the middle of a contiguous entry + * + * The IOMMU API does not require drivers to support unmapping parts of + * large pages. Long ago VFIO would try to split maps but the current + * version never does. + * + * Instead when unmap reaches a partial unmap of the start of a large + * IOPTE it should remove the entire IOPTE and return that size to the + * caller. + */ + if (pts.type == PT_ENTRY_OA) { + if (log2_mod(range->va, pt_entry_oa_lg2sz(&pts))) + return -EINVAL; + goto start_oa; + } + + do { + if (pts.type != PT_ENTRY_OA) { + bool fully_covered; + + if (pts.type != PT_ENTRY_TABLE) { + ret = -EINVAL; + break; + } + + if (pts.index != start_index) + pt_index_to_va(&pts); + pts.table_lower = pt_table_ptr(&pts); + + fully_covered = pt_item_fully_covered( + &pts, pt_table_item_lg2sz(&pts)); + + ret = pt_descend(&pts, arg, __unmap_range); + if (ret) + break; + + /* + * If the unmapping range fully covers the table then we + * can free it as well. The clear is delayed until we + * succeed in clearing the lower table levels. + */ + if (fully_covered) { + iommu_pages_list_add(&unmap->free_list, + pts.table_lower); + pt_clear_entry(&pts, ilog2(1)); + } + pts.index++; + } else { + unsigned int num_contig_lg2; +start_oa: + /* + * If the caller requested an last that falls within a + * single entry then the entire entry is unmapped and + * the length returned will be larger than requested. + */ + num_contig_lg2 = pt_entry_num_contig_lg2(&pts); + pt_clear_entry(&pts, num_contig_lg2); + num_oas += log2_to_int(num_contig_lg2); + pts.index += log2_to_int(num_contig_lg2); + } + if (pts.index >= pts.end_index) + break; + pts.type = pt_load_entry_raw(&pts); + } while (true); + + unmap->unmapped += log2_mul(num_oas, pt_table_item_lg2sz(&pts)); + return ret; +} + +/** + * unmap_pages() - Make a range of IOVA empty/not present + * @iommu_table: Table to manipulate + * @iova: IO virtual address to start + * @pgsize: Length of each page + * @pgcount: Length of the range in pgsize units starting from @iova + * @gather: Gather struct that must be flushed on return + * + * unmap_pages() will remove a translation created by map_pages(). It cannot + * subdivide a mapping created by map_pages(), so it should be called with IOVA + * ranges that match those passed to map_pages(). The IOVA range can aggregate + * contiguous map_pages() calls so long as no individual range is split. + * + * Context: The caller must hold a write range lock that includes + * the whole range. + * + * Returns: Number of bytes of VA unmapped. iova + res will be the point + * unmapping stopped. + */ +size_t DOMAIN_NS(unmap_pages)(struct iommu_domain *domain, unsigned long iova, + size_t pgsize, size_t pgcount, + struct iommu_iotlb_gather *iotlb_gather) +{ + struct pt_iommu *iommu_table = + container_of(domain, struct pt_iommu, domain); + struct pt_unmap_args unmap = { .free_list = IOMMU_PAGES_LIST_INIT( + unmap.free_list) }; + pt_vaddr_t len = pgsize * pgcount; + struct pt_range range; + int ret; + + ret = make_range(common_from_iommu(iommu_table), &range, iova, len); + if (ret) + return 0; + + pt_walk_range(&range, __unmap_range, &unmap); + + gather_range_pages(iotlb_gather, iommu_table, iova, len, + &unmap.free_list); + + return unmap.unmapped; +} +EXPORT_SYMBOL_NS_GPL(DOMAIN_NS(unmap_pages), "GENERIC_PT_IOMMU"); + static void NS(get_info)(struct pt_iommu *iommu_table, struct pt_iommu_info *info) { diff --git a/include/linux/generic_pt/iommu.h b/include/linux/generic_pt/iommu.h index 1fe5106997f833..382596b70e394e 100644 --- a/include/linux/generic_pt/iommu.h +++ b/include/linux/generic_pt/iommu.h @@ -9,6 +9,7 @@ #include #include +struct iommu_iotlb_gather; struct pt_iommu_ops; /** @@ -119,6 +120,10 @@ struct pt_iommu_cfg { #define IOMMU_PROTOTYPES(fmt) \ phys_addr_t pt_iommu_##fmt##_iova_to_phys(struct iommu_domain *domain, \ dma_addr_t iova); \ + size_t pt_iommu_##fmt##_unmap_pages( \ + struct iommu_domain *domain, unsigned long iova, \ + size_t pgsize, size_t pgcount, \ + struct iommu_iotlb_gather *iotlb_gather); \ int pt_iommu_##fmt##_init(struct pt_iommu_##fmt *table, \ const struct pt_iommu_##fmt##_cfg *cfg, \ gfp_t gfp); \ @@ -135,8 +140,9 @@ struct pt_iommu_cfg { * A driver uses IOMMU_PT_DOMAIN_OPS to populate the iommu_domain_ops for the * iommu_pt */ -#define IOMMU_PT_DOMAIN_OPS(fmt) \ - .iova_to_phys = &pt_iommu_##fmt##_iova_to_phys, +#define IOMMU_PT_DOMAIN_OPS(fmt) \ + .iova_to_phys = &pt_iommu_##fmt##_iova_to_phys, \ + .unmap_pages = &pt_iommu_##fmt##_unmap_pages /* * The driver should setup its domain struct like From patchwork Mon Jun 16 18:06:13 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jason Gunthorpe X-Patchwork-Id: 897136 Received: from NAM02-SN1-obe.outbound.protection.outlook.com (mail-sn1nam02on2054.outbound.protection.outlook.com [40.107.96.54]) (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 19C7F19D07A; Mon, 16 Jun 2025 18:06:32 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=fail smtp.client-ip=40.107.96.54 ARC-Seal: i=2; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1750097193; cv=fail; b=bDUd/kwfMHKwqV+xftvHBSJKoXxjmOaYngx09jtomWAV8AeWK2VR0YpJ+ZcrJk24N55c9pdsnEIy2LyGD0aPluk1sSdCqANPo6abijOiE4M3kYJI3Qxjod6jAOmrNEp2CTdQsly2HspmrQMiIdvCjOX9CHlisUSlvjwDgd4TAAU= ARC-Message-Signature: i=2; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1750097193; c=relaxed/simple; bh=T1nYsqQkhuWIaDuwS3g577cxdjwA014VnHa5fvH8OVA=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: Content-Type:MIME-Version; b=bLSOj+kiXeN/81LDMZKOKdvgV/nUXSJl70ClISPoH4cUscNCzXPLPIE/Xg/eg95zk2hq/d6kLL6G4jso+PXhuRrTgWFG5YzS0Auo5OfR19slwGKJCMg6MZjh5AjgvTd5HBLtWFDGo//3WmuqJJIOcyYfmwl3QcnEkIoprCdU8kw= 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=TF+XY7bm; arc=fail smtp.client-ip=40.107.96.54 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="TF+XY7bm" ARC-Seal: i=1; a=rsa-sha256; s=arcselector10001; d=microsoft.com; cv=none; b=NpJTO7K6hlkWdX500+KdCRHjxKy5YHRe5R0XDO3DPB4WIVj4ODjI0OfSgpnZuXwU3hhC6I0CTIDE1LUJqKB6bgus5MagAT8uE9F9lAV0bu56sf+W1nNoDkLyTg0T77n58m89M4XW0EBipeUxoriDQNfNdDyRBxn8lvii3HJRBHmqkjya7/hjxP4WuKFbaAR1lDFHwJQ3pJE5q7ipe0k1lVqmaOyK17xc6nnL7RnrLbXaK/9k5xGbdA+jtx+RGL4T/mWk4Jgioj7mxq2Z6P/h6aZBk99plycThutpoCCp2No95UEm9VUY9IXOc76cZgqxfeoyiEcnxUbc6G9VEf9HCQ== 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=vGDqw59iK58jjiOAqr3xfLKXZDMZDIfyCKziPsfoslc=; b=XY7rc01h82s2yTlTKPp2NWxwZW/hg+1u6KPxezfSW2cc1z4C9dml28sxed9EhmCH3jwThYqI/iiMKjQ0X06uPwTvpy2gGMDKePLEe+sqVgONhPRpTC805T2BuZ2qSD7NiGUkvPRGPyB1yezK9VhbRwxDOD2Jz2s/VIftWGMudVVz4WfrqNJOWKAeeDe4cxqLNlGAAIUspTAT56rbkmSU21edK2pigBsNfh6G3nSBNc6iIdjEWs/cQQ7061imQFzUROJOJlRrkn7xt5m6yptO/dm22yLhmdwH61Is5n5u3GqicBYwkfigKHr/0kANIfXU6aLCm+oEhvbYHtMSzQ1ScA== 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=vGDqw59iK58jjiOAqr3xfLKXZDMZDIfyCKziPsfoslc=; b=TF+XY7bmRx1Q4mWRvWYxKa3/GHRI4H7vphHlMQrjSOyHCcW2hcUAqNpaNBQYi/lPzwAHlKeyDrwnMOE2h1n8REBgPCYZW1uScNqIpx+QaNRl6pxH89JOrEtTK45HQy04lAZDEIegfdjpsPHgODx+eVBAeudlGcWRy+Iqm4yP6OkxXkGIEwcyAAoe9QWnL4AfZZagQ+zRkmF4gWhklPzUukNnNLrvIxIqBb26lI94YJHKPfP3LleNVNQNyX4/8mPtbWe8Daot0o3oc8FIeH2QktHfikO22CyBu2uKEKFonvRwGCZtazKOu57CEDyf0XHIUc2rpYbcuVhDlElUe7q9hg== Authentication-Results: dkim=none (message not signed) header.d=none;dmarc=none action=none header.from=nvidia.com; Received: from CH3PR12MB8659.namprd12.prod.outlook.com (2603:10b6:610:17c::13) by SJ2PR12MB8805.namprd12.prod.outlook.com (2603:10b6:a03:4d0::20) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.20.8835.28; Mon, 16 Jun 2025 18:06:26 +0000 Received: from CH3PR12MB8659.namprd12.prod.outlook.com ([fe80::6eb6:7d37:7b4b:1732]) by CH3PR12MB8659.namprd12.prod.outlook.com ([fe80::6eb6:7d37:7b4b:1732%7]) with mapi id 15.20.8835.023; Mon, 16 Jun 2025 18:06:26 +0000 From: Jason Gunthorpe To: Jonathan Corbet , iommu@lists.linux.dev, Joerg Roedel , Justin Stitt , Kevin Tian , linux-doc@vger.kernel.org, linux-kselftest@vger.kernel.org, llvm@lists.linux.dev, Bill Wendling , Nathan Chancellor , Nick Desaulniers , Miguel Ojeda , Robin Murphy , Shuah Khan , Suravee Suthikulpanit , Will Deacon Cc: Alexey Kardashevskiy , Alejandro Jimenez , James Gowans , Michael Roth , Pasha Tatashin , patches@lists.linux.dev Subject: [PATCH v3 10/15] iommupt: Add a mock pagetable format for iommufd selftest to use Date: Mon, 16 Jun 2025 15:06:13 -0300 Message-ID: <10-v3-a93aab628dbc+521-iommu_pt_jgg@nvidia.com> In-Reply-To: <0-v3-a93aab628dbc+521-iommu_pt_jgg@nvidia.com> References: X-ClientProxiedBy: MW4PR04CA0088.namprd04.prod.outlook.com (2603:10b6:303:6b::33) To CH3PR12MB8659.namprd12.prod.outlook.com (2603:10b6:610:17c::13) Precedence: bulk X-Mailing-List: linux-kselftest@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 X-MS-PublicTrafficType: Email X-MS-TrafficTypeDiagnostic: CH3PR12MB8659:EE_|SJ2PR12MB8805:EE_ X-MS-Office365-Filtering-Correlation-Id: 84a132d6-2915-402e-bd80-08ddad008125 X-MS-Exchange-SenderADCheck: 1 X-MS-Exchange-AntiSpam-Relay: 0 X-Microsoft-Antispam: BCL:0; ARA:13230040|366016|376014|7416014|1800799024|921020; X-Microsoft-Antispam-Message-Info: 3FuyZx4yi/pjiJ+VeIm9nCwfgifMx+mhQnM+GLvd/Ir1gMs2/QF89N60Z0CEREe14Yzfpa+Mxt3IvJZRDLZz6tLZwWKzZIHYB0IjUAZJliXTV/3v82oHBw0M+ZD7+S4/Mef0OCLnPQDHL6xBgvPAE4aNs7wpb55sDq+Vdvw//3kKIC4cJcFRyVaSfvniLyre9jPkj1gaIXmb1Q1dRGpODNzWWtu0+KOQHgFasw0TcA0IUbwf0n4H0bc1LyTAwEftADIPU//cyVFXMp4yiBjXv9KOCRMV24elpBrt0NZJA0L+IiQdLPsZPy2vXsOgrahUDL0xpKbxTsId1H7O7euNa6kHxxbTn4WdleQQEDzZPiwpQK6J73QGZF0ze2QeR6yt2QYuD86pJ9A+UI7ZTfKJONn7L/e9HcMWkgiqHiTHS4BygOcuXejTLUjy54b6A8K48ZNHeEu2jlN8X35p+ZGcuzbehiEChJBipnyJ6NLPj2dNnxJOtFeR+hqH056S5vIcjiAfMIIbHb60LQAyX2KEwHn/SIAehno6GevJ9NaUliyjD0Zmnf/bCqDaVHzx++rJKLdJF/cJ7+BNb7e0DnP5mQkv4SRrl9OYo2Glh87eD656aQU94IAw7HD6shf2rML+upsm+WYQOAev4qzdGzgCVmLkHlyeLy/gg/f5gxZWBIgxoa94aa66iSFxnOKPzL9qNBtSBDJwEa5HuKC45SG/NBJlQdrQcjahX/DOMhQ64Xq9h25WO6uMp49nHktiMOX1+tmAPVAChl8oMrNkEgOXRQrkuTIH6g3Z4cIq7+15sYxIRrX7ft4FXaceNFGz6eVL1BHYoGdAU6/YdFNdkt939+9CF9mw5uXejKAcNFXW8QxBXbiJMTIoZjE/nndqUk8S4EnJxmelNRRpmzeKN5k9EdKzqBwnHSfcZSYIKJH9dGJ698JEqEDp88ZJuMIdRs6T3UvR6GfHkbTdlhLEytbLwMokaPvbZztkj8gI0Ef7BAH/eQ6jqIEddPlazO+XbUsSLd8jOFxf4raXuIWovTd658ip2+knAqjUg794CIWw38P/2I44cYfQecPcvLxkkq5W3ChokNcD/qNCze1VZbtPGEelKcBVOlbWlEUHjTLW8+ZvBtju8mNAgHbml4b+Z2Ye/Fd/Oy8jSMR1t1qRRVKw4IbKcLn96Y2q5iR/YPa6Wh5tL376Y+2jv/a6Zd+PoP4dqsOwHHdAupYWtGY5piKT0EHw5Gq5JyN81PoDInxZpgQQ3feA6rueTF/hmGgPkyxwu1ETtF7ARoL1SAHh/1MJg2yFJGMf90miWVEFFYpPRjpMdf7hZn2lnRj0m7Y+mCxjKs73R7C93Ob5OqISLG/v7Zz3PRRwjEbTvFnP69r0i7g9GEB7AVqfrTvVvLo+tT0aotPY4OHe3rWYBogBtSmjOwea9sGOxttldb8O4akugRkjeayUc7w3B01zgZtYcu90C0VIhdHw8DoSCuyN/XTEKg== X-Forefront-Antispam-Report: CIP:255.255.255.255; CTRY:; LANG:en; SCL:1; SRV:; IPV:NLI; SFV:NSPM; H:CH3PR12MB8659.namprd12.prod.outlook.com; PTR:; CAT:NONE; SFS:(13230040)(366016)(376014)(7416014)(1800799024)(921020); DIR:OUT; SFP:1101; X-MS-Exchange-AntiSpam-MessageData-ChunkCount: 1 X-MS-Exchange-AntiSpam-MessageData-0: Y97SfW5a1+6EBF0JAZ70PhPXWUYFIs/DvjvkHj1QZl4hnCMj7XYIo2kEulA+YQm+6FXzCXyfl4CwmID0n3UoqEnyNApRLzLjQvoFvQAy29PVABqG5CeZO9col4jAOY8z9yymnPwy3rDuJqFZLoPdloutB93ymOR6ivM9sbfkF0OBLjZO+5ZQfqQ9CRPcX/18O6mDDCIOZBPoj/KV9BEQ05BMV5/iAssZ/XCpVeuCu02zteYFmuRjWVr4pCtAeAPCIFSXQ80yz+oI1wBYEt1fy0QH3gmHl/lt22v325qVSV7Q6VgU4KH8rQ8k1ZxRKUN2/ZjSVyzcP9Copgle6l6+7NrbF1qKGIjVaKrjPMa9bdzAUVmyY+MhauYGmDpijV5vhSa4whOHlwp/XFxdYDV0yB21NwM2jnt7LyoAuxd6/J9yFjAH1RtNdkZFM3wFN5sZzhnVlISZxeIK+sPM8lEgQkZntpT7aXPyI7RAy9Kf/LzYwoOMNhFQrDavuv0q/1eatJQRgvEPE9GDUW9XR4Vog0PVBVrT2is7bhB+AEHTAEWCp8ywV2EVpZGiQEUX9jvwGgKitWe6oLrqJPDiM6NJQGOMxZOQxwkd2uNBwjT1Pgom6eNLf/yLYmvrS7aEuKfAOLemGiV2413V90brSqtp1B7Mv4ppmLmuykOI8jyBcGIN793xFw+HW4mg7gzQBtt6tV2N8sYB4M5WoE1M96hRy5J/EQa6/hsrDJsbuoACMkQlwJmmXgLbSlLODHlh+NC540gokwKmt7HCnOMtwQNqcyGQyZJJjiS8LP2t7PBrK5rnJ4DJlRgqUmzUxwoXwEdM84x1kwfRLSlOFuGcRjC+1Fs228DYrszM5k0YEfYvh6ZZxPtgfUePwWx4QGxAEqhO7nXg3jUSaSdpKzpxIE+5Y+DMX51CXf7y0M5CwMF+6AHC7geQZtzR2stlI7Yp06RBIep5PSnWYt/WlNsHy2F+FUChcDRL3Ib8ypn8HYpp7vEXA35sRkqnWcORFRWy4l3AqJju8rYzF3r27dNYtUINlotMj4DLehyNZxr3L291nt/VWp4ur70CcQzOa2GeKK2DTgfOYgKxc9qYfiEu3ir2a1p9gs0rGPY0uSIKpc0iyC6TE7jE6qe8Zpl5XU3/kAiBj8whq/u1KTMKUxzLhRpG4WaOPxVLt1stC7x2ct1LsOW5jkTNTk35m+tRSVlqanPdlj+E83yM3vZCSb+nkdZ3akHyOYzxhC+izvj1uHZWEpi7E+RPQ2i0XjYpIVKDY+3xFcpiY1IlGeLNRCD4Wze+/965qS9KcrEgwIeMNjbImRAQgpXc3KRe7S7D6KFZ0d1Df6hBB94otT8nY/4/HAhpsBcmLIenog9gYdDiyqKmgPrOBD60wCptM+4geI3sfNAz8VCoyLyfTr7MFTzqpvhayvMp6Ezn8oGm6YIfDLkCuch4QkR4TqaDJ89lJKqluNsjFJUV3daxQWEPiAa/IvfFwgvkmRI5GEK4rW5e9PHArZvGWFxY1cUSY6uosuVNd2zSO6HjbQm2UbIy8iXG1KvQpKDeUXnProm8f81Hv6W7Xsw+B2PU0anF46GrOH9Gp12m X-OriginatorOrg: Nvidia.com X-MS-Exchange-CrossTenant-Network-Message-Id: 84a132d6-2915-402e-bd80-08ddad008125 X-MS-Exchange-CrossTenant-AuthSource: CH3PR12MB8659.namprd12.prod.outlook.com X-MS-Exchange-CrossTenant-AuthAs: Internal X-MS-Exchange-CrossTenant-OriginalArrivalTime: 16 Jun 2025 18:06:23.8933 (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: /mM6/yECzgkoYnburzF2RCU4WRXx4Toqsqq4jMd0zRuphDdwZuhGeIXFh1OEvsC3 X-MS-Exchange-Transport-CrossTenantHeadersStamped: SJ2PR12MB8805 The iommufd self test uses an xarray to store the pfns and their orders to emulate a page table. Slightly modify the amdv1 page table to create a real page table that has similar properties: - 2k base granule to simulate something like a 4k page table on a 64K PAGE_SIZE ARM system - Contiguous page support for every PFN order - Dirty tracking AMDv1 is the closest format, as it is the only one that already supports every page size. Tweak it to have only 5 levels and an 11 bit base granule and compile it separately as a format variant. Tested-by: Alejandro Jimenez Signed-off-by: Jason Gunthorpe --- drivers/iommu/generic_pt/fmt/Makefile | 1 + drivers/iommu/generic_pt/fmt/amdv1.h | 18 ++++++++++++++++-- drivers/iommu/generic_pt/fmt/iommu_mock.c | 10 ++++++++++ include/linux/generic_pt/iommu.h | 6 ++++++ 4 files changed, 33 insertions(+), 2 deletions(-) create mode 100644 drivers/iommu/generic_pt/fmt/iommu_mock.c diff --git a/drivers/iommu/generic_pt/fmt/Makefile b/drivers/iommu/generic_pt/fmt/Makefile index 32f3956c7509f8..f0c22cf5f7bee6 100644 --- a/drivers/iommu/generic_pt/fmt/Makefile +++ b/drivers/iommu/generic_pt/fmt/Makefile @@ -1,6 +1,7 @@ # SPDX-License-Identifier: GPL-2.0 iommu_pt_fmt-$(CONFIG_IOMMU_PT_AMDV1) += amdv1 +iommu_pt_fmt-$(CONFIG_IOMMUFD_TEST) += mock IOMMU_PT_KUNIT_TEST := define create_format diff --git a/drivers/iommu/generic_pt/fmt/amdv1.h b/drivers/iommu/generic_pt/fmt/amdv1.h index cd72688322c6bd..c7442d85a919fa 100644 --- a/drivers/iommu/generic_pt/fmt/amdv1.h +++ b/drivers/iommu/generic_pt/fmt/amdv1.h @@ -26,11 +26,23 @@ #include enum { - PT_MAX_OUTPUT_ADDRESS_LG2 = 52, - PT_MAX_VA_ADDRESS_LG2 = 64, PT_ITEM_WORD_SIZE = sizeof(u64), + /* + * The IOMMUFD selftest uses the AMDv1 format with some alterations It + * uses a 2k page size to test cases where the CPU page size is not the + * same. + */ +#ifdef AMDV1_IOMMUFD_SELFTEST + PT_MAX_VA_ADDRESS_LG2 = 56, + PT_MAX_OUTPUT_ADDRESS_LG2 = 51, + PT_MAX_TOP_LEVEL = 4, + PT_GRANULE_LG2SZ = 11, +#else + PT_MAX_VA_ADDRESS_LG2 = 64, + PT_MAX_OUTPUT_ADDRESS_LG2 = 52, PT_MAX_TOP_LEVEL = 5, PT_GRANULE_LG2SZ = 12, +#endif PT_TABLEMEM_LG2SZ = 12, /* The DTE only has these bits for the top phyiscal address */ @@ -372,6 +384,7 @@ static inline int amdv1pt_iommu_fmt_init(struct pt_iommu_amdv1 *iommu_table, } #define pt_iommu_fmt_init amdv1pt_iommu_fmt_init +#ifndef PT_FMT_VARIANT static inline void amdv1pt_iommu_fmt_hw_info(struct pt_iommu_amdv1 *table, const struct pt_range *top_range, @@ -382,6 +395,7 @@ amdv1pt_iommu_fmt_hw_info(struct pt_iommu_amdv1 *table, info->mode = top_range->top_level + 1; } #define pt_iommu_fmt_hw_info amdv1pt_iommu_fmt_hw_info +#endif #if defined(GENERIC_PT_KUNIT) static const struct pt_iommu_amdv1_cfg amdv1_kunit_fmt_cfgs[] = { diff --git a/drivers/iommu/generic_pt/fmt/iommu_mock.c b/drivers/iommu/generic_pt/fmt/iommu_mock.c new file mode 100644 index 00000000000000..74e597cba9d9cd --- /dev/null +++ b/drivers/iommu/generic_pt/fmt/iommu_mock.c @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2024-2025, NVIDIA CORPORATION & AFFILIATES + */ +#define AMDV1_IOMMUFD_SELFTEST 1 +#define PT_FMT amdv1 +#define PT_FMT_VARIANT mock +#define PT_SUPPORTED_FEATURES 0 + +#include "iommu_template.h" diff --git a/include/linux/generic_pt/iommu.h b/include/linux/generic_pt/iommu.h index 658ef69156121f..cbe6433550f380 100644 --- a/include/linux/generic_pt/iommu.h +++ b/include/linux/generic_pt/iommu.h @@ -236,6 +236,12 @@ struct pt_iommu_amdv1_hw_info { IOMMU_FORMAT(amdv1, amdpt); +/* amdv1_mock is used by the iommufd selftest */ +#define pt_iommu_amdv1_mock pt_iommu_amdv1 +#define pt_iommu_amdv1_mock_cfg pt_iommu_amdv1_cfg +struct pt_iommu_amdv1_mock_hw_info; +IOMMU_PROTOTYPES(amdv1_mock); + #undef IOMMU_PROTOTYPES #undef IOMMU_FORMAT #endif From patchwork Mon Jun 16 18:06:17 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jason Gunthorpe X-Patchwork-Id: 897132 Received: from NAM12-BN8-obe.outbound.protection.outlook.com (mail-bn8nam12on2043.outbound.protection.outlook.com [40.107.237.43]) (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 4D10C289810; Mon, 16 Jun 2025 18:06:40 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=fail smtp.client-ip=40.107.237.43 ARC-Seal: i=2; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1750097205; cv=fail; b=LMJ488+5YlqM8G1IcxTyZlTQ9IUIs8Nt5tbYuhqVYY+P0NXWyW0lK3vX+Vp41W2Pmte0JUiqDYnPOR7ayg0HcFYIo+Ev5nDIEWXu4dlkFnVuKuq8MYq1GmvbraWEvpHGqJvPoV4WyoDik3vfMY+KfuRj/mtPjPN6b4oW3EKl350= ARC-Message-Signature: i=2; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1750097205; c=relaxed/simple; bh=5hl6g6TTF0bCJf9pdldcwIQTu0+z0zwIgHHTu0ix7BA=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: Content-Type:MIME-Version; b=Zdg2/AhI326uWXGpbatQZPvCGPKwVsW13HaaS3vqM9gOkunpi84TrXSoGj8Y1qL6rF1E/aedhOo2FF3K8KMuceZbh9zQqDZhIWFkHWEkHZfxohTm0koFRnLTuaWgdZsm+Qh6FlLuaFLZ/hnFINp4yw3F0iIeUA603LLbtGMZfOg= 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=rl0yBx3k; arc=fail smtp.client-ip=40.107.237.43 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="rl0yBx3k" ARC-Seal: i=1; a=rsa-sha256; s=arcselector10001; d=microsoft.com; cv=none; b=pp5Zx2sx4yZeonQiFeHZc3qE0C91ujOTzxRL5ps+Tzt1GQSXpdFLm/mP5/06x+UrGEqzFUFileKy5AqdpwZqOfOk+77mVibPDl+VKl6yeKWf/iTU1EFJtJUKHYi1lCO1CLwyzhAw4cNEpbMsCU72/NxqHZ5eTAtWSez72AO4z6065/grNWQxV6SHDDxIZq85YCcKNiZtXHUYCfHb8tmR90UmtLr3uZkuxA3Ru/SGjTy1jaQliTgtb5ys0o/0iJI93TY2mgf6yWU5d0Bg+uLhSMS6hrju4jiMNsGHSue7Q0ll/kzmYX2VjRE4IfzATx0tCKPyMuFevsug2zRB8KXI5w== 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=scZF/i4RHg+pRROs0N2yOPXKyN+6japTyN4tYFD82yo=; b=TNdXcsQXkwzqiHh4ywZETMEEqYsAWAA3W8d9JAWoOecq3ZSN9LfeoM0lYXhoSCfjG6aPQPBBJvmOIVIa0zwibPeZP1uqG4fz3tQkmtMweZrQRp/HLOUt4fGUHLrLY4ot7zOzOcdpUAhZJD5Jo3sbT3Bgb/IAG6Ogv5h/Dwkn+c7yHjLiETWy8Q+a/u1WFXjBMApHty3yIP8PABK9S1p8mZG4y/kLRLhWLHDyO5/hfHr3LJ43aMWmoUMllFBsH9MYCg/RXqwNRRQqUluxN198WRS0eYCqGOnfLqrMPJoXyRbpvV5+NKSbaeE3V7LwutkX7RjSPvgdgJ9kAZadegLkjA== 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=scZF/i4RHg+pRROs0N2yOPXKyN+6japTyN4tYFD82yo=; b=rl0yBx3kBXmMfMFbfpvp8NIAfi5U33bcD9OO30bJmavbE/Y31c/M0G7csSpjSG0CGhjlu7uOZQi1rhdYeW+aZge92k1t9U/9ZxaDoTQ6PokOtwOAn0jSpL5bCSZep8Fh8Eqbh9w0HFFlyP1hmQQHmQ6eQX6QJQaYWS+YAULoiMRvm0+AqEfZH/w0CYLunZ3OfpP6QqrgtT7iMn9NwK9Bvli7y8I904r1Ti9JM+DClDu0geRHZIJiWOtQRBYfQ2nIvqarrNzBvDYyVG0F2SlOkNLFOQ8wjx7VcrP5vwuSwsmoEPtI27a6bQPi1Pwn0YizNLBwaLn9LzMdNAaSoYbsiA== Authentication-Results: dkim=none (message not signed) header.d=none;dmarc=none action=none header.from=nvidia.com; Received: from CH3PR12MB8659.namprd12.prod.outlook.com (2603:10b6:610:17c::13) by SJ1PR12MB6267.namprd12.prod.outlook.com (2603:10b6:a03:456::10) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.20.8835.28; Mon, 16 Jun 2025 18:06:23 +0000 Received: from CH3PR12MB8659.namprd12.prod.outlook.com ([fe80::6eb6:7d37:7b4b:1732]) by CH3PR12MB8659.namprd12.prod.outlook.com ([fe80::6eb6:7d37:7b4b:1732%7]) with mapi id 15.20.8835.023; Mon, 16 Jun 2025 18:06:23 +0000 From: Jason Gunthorpe To: Jonathan Corbet , iommu@lists.linux.dev, Joerg Roedel , Justin Stitt , Kevin Tian , linux-doc@vger.kernel.org, linux-kselftest@vger.kernel.org, llvm@lists.linux.dev, Bill Wendling , Nathan Chancellor , Nick Desaulniers , Miguel Ojeda , Robin Murphy , Shuah Khan , Suravee Suthikulpanit , Will Deacon Cc: Alexey Kardashevskiy , Alejandro Jimenez , James Gowans , Michael Roth , Pasha Tatashin , patches@lists.linux.dev Subject: [PATCH v3 14/15] iommu/amd: Remove AMD io_pgtable support Date: Mon, 16 Jun 2025 15:06:17 -0300 Message-ID: <14-v3-a93aab628dbc+521-iommu_pt_jgg@nvidia.com> In-Reply-To: <0-v3-a93aab628dbc+521-iommu_pt_jgg@nvidia.com> References: X-ClientProxiedBy: PH5P222CA0012.NAMP222.PROD.OUTLOOK.COM (2603:10b6:510:34b::8) To CH3PR12MB8659.namprd12.prod.outlook.com (2603:10b6:610:17c::13) Precedence: bulk X-Mailing-List: linux-kselftest@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 X-MS-PublicTrafficType: Email X-MS-TrafficTypeDiagnostic: CH3PR12MB8659:EE_|SJ1PR12MB6267:EE_ X-MS-Office365-Filtering-Correlation-Id: 56b9b6f6-289c-4523-da99-08ddad007f79 X-MS-Exchange-SenderADCheck: 1 X-MS-Exchange-AntiSpam-Relay: 0 X-Microsoft-Antispam: BCL:0; ARA:13230040|366016|1800799024|7416014|376014|921020; X-Microsoft-Antispam-Message-Info: qATARa23CV/fNY6c9DdJK9/hAwQVEs8JoaKYr+tC6JXPjaGVl9wv5wKOwN5yasfoMMzCW7Y5DL52Zuej7pLaV7XGz7axL/CUoRORyb7nnzZ2L4r7K7qb7FZGdn9eGaCdLLKv50AbEraTc+vPbHznj3dMPWIye0Pek/bu1hcmhdeEy5zPzizMMt1+eNbhxhrrqzUgTykWr+KFLSB9X82kGdabnEoSPvW8v8hQCTPasR99CbWNJqBLFutwyrom5olrs1PcR0y9ZWK+aye+Vk0yb0/jp0BA8a4ZCCxHKa0SQyvrp7QLyNE1/XFwDXSrla7KQOSrNVyrA+2RblqopUIyLRY/ajZAXpRsFm5SPXuhxDjC4TyaCWHRkOjj47wDY2z9dNLvRPEvoeEEnzSvnwy+WWMHbloPdeAWFVaeHNUI55XWQVuyz7IWAuGn1Hv/0EyaClc9eqTdNCjLbiQ4FZLneNcH9BYSauQTGm7Q3nx4pXSgqd0qO/RVoHfWLNf0aToapEIoMybamYoqpj6TGvJKwvto1WfzGE99+jiOg2Eqby71EVApF5VNGmldhK1B7RWnaF3/5oPABZkdlGjiy7h8UgRuZphASUIk5sl6rK847MhPaELH2U4HT7ga5OxBcf/Oo112NTPZkvALteWscdmHkiYESWXd5ielv3i4njunAtH/4F8Yrtc2lzpYXO5s8XIQ8NjKj/uo8RudkQeiwrHrRqrwyiPWHRr4bpFvRkAv1Ns6K1wXKv6ZBqIjmEtEE2fKyDEkqQtAL3OOWhifx4zziCiM6gjsVwRnao0fb4k4cVb2Fw+8Sblr3TW6r3u0KHgFHww9bQ8DKGqrH7z/OdNVChIzcPDcSXGTuthzNi++OjrAvISeQfqCZUe35gJusw/UOwdr/UIMB/4XLFEdaWLBsYHhVanaOdhDY7G69JOrGTXNG6VtIbej9njJ44L6jGMQ7Ui7TJav9r6WESrSpLcpIzB2LqZLg6jSIqSpRE0zUwg82uVxxQseHZWcgTj48b6VATEaBgkotPEBkDkhmF8WWsuN5K+RTmKXQ2NwoTCTaoEckWzbDtcv/7Twf3GeQ/KOVOPhPopKX44ogZwwkSdmdFi4lVOOZ08D4FjH5zrd7UPUygwdVcPS415+v1Z3D50fFlCTX3yyBrS5Zw6M26UkYS/2zYYIR1WaT5vFxyDdo3c0t7qzn7ai4azp7CCKZPV3ePbqjmB+saxfe9Cp411lElvbpkMGkb0VroFYPEIfs+IuyF7i/GKtVKq96IpW09UL6KtJqMvz1uMmdMXLYONbXsz+0n8cwjkCL73eUmYiYRYLaLNMZ3kjsLThOlZJohGBIkOtnDiN8s5opAGC/7KWq6taxjbxY5OKa9FWGtYuTwSwtCvkquW3u8CJp+XR3o5WP3HjYzAfIHCIFLEem7TQg1fU90tl8rixNLqyvCIy3cEzxavIst/xb4y5Cr5OUxs0l9UEfML0TqJgKLalHwIraQ== X-Forefront-Antispam-Report: CIP:255.255.255.255; CTRY:; LANG:en; SCL:1; SRV:; IPV:NLI; SFV:NSPM; H:CH3PR12MB8659.namprd12.prod.outlook.com; PTR:; CAT:NONE; SFS:(13230040)(366016)(1800799024)(7416014)(376014)(921020); DIR:OUT; SFP:1101; X-MS-Exchange-AntiSpam-MessageData-ChunkCount: 1 X-MS-Exchange-AntiSpam-MessageData-0: 6PXHrMDtLWQwQsz2kGw4DgQ/YllT9/cp3g2/ytjEgKfpFreK4umFDpAQUJwDSMLS+/5+DFAAGpYxLhLGIwKi7jkH3qQv2qx3CmfZb804ddH+FzLO29rK9AmjYWmansdAvJgJeYakl/RX1nL8RahVpO5qWl68q1m06XvYAe73jLrD2ycdG0yUTPIft5MSiSfDqFgZ0HkmWPQy5q4y7L9N1ozLKN48wp5CeTqU269E8dBZquO+rQT6PFuftcwrcoiZVb/FR7oc4IQlSsGFSK12mY2UEPXBC0tDs9FRjPDqy2uVBqwYfGTGWBvAMQ7W4C0nCBGrpU8DzZGTlAtnujmYdAJSO8cO7oPzXv/KELSJ/L9kafbKgiDnaNaQL/zLZP92yrvI4CPcWSaoMcRO0B4yEKFAgEvXPNsh4EUB113YgwtS0wALCuCpjjIJbmzFDwhdUN7yPDNHJj6AFk5D8yfqhSSv3mkjT720hzuz7iMF+o6y7G24NAxrwPkmCViJLD7lJh6hRsVRsrcdLypYfDfKLrYF2b8tMPJtR/ohqPmCka1GsVxKjHAB+SRQ9NkszXG6wzuKpX92WkHp0prByj8bSCN+35bouy6iOTM02P5/0l4jvWoOoE4nXTv/PdUPgj8iKKM3LN2DTrYxqx4CAwmudhIDAeLZ+y0zU1e5Yf2HyUHhJhob3/ENHjFmsJKeF0Frg5zEE3Ez8BUw5zeyuiqCtuJbNinrTjHcCbtClsv76YGwgZAttQp0qGMo6fQcxgL85AuemHKn8kXrF2F8zPCH68DWDgRF9QLMEhZNN5TG4B8tAO630LaCijwEdWrXkmBGs672z9Tm9CLgeMa9t/zRQDfE2/3FzG4N/QlEx3bauwvcN6n9/okrhf/PBdnq4nofH3dfogLvoNCwYD/wqr0/NS14Xef3T0XWiREmMY8CO34R4gUVjDAHnnG8rf7mDmRd2MwP8Bhqlj0pewa3O31n9LbHqfJDl+sD/f3XhkM3dk5JH78vXxtS/7DkszXgLn8qlZ560vd/sBTnsh8nQ/zRBpxGF4yzQsPAotSlN251EZhbplriSmBm3DTguG0v5nBsTMFQvx84J1kAAzhoYaQ9ABOcQMTGnZyj8ocABHeFV/Vi0OT9SDxsUBarEX3CwEx/gEV98Tue0Fc6+sEN8/yZR0i2Q25s+VAWC7OGNsk9GFNNuQB481esQ9BJX0cTY7Dll15Ak2iruCNH+6+vy3EYr3IZ1x4E8SJZsATw6Lq2yHqJOrtI/zfX/ZH51KW+3+mEr9D79x+LCbFsaT6SVpWiM6YxuEUxuySkQMZ5//Lgd6n2t2medXoQz2oN9yZzU8zmR/PNnK7g7RDqbBHukiXZeVXLpDJ0U9zPJ28+db1Rxiq5ya5760bt2q/as2hjhw0JHqr7FyjDO+6mrXKY7V8Ld+zMeec28sz7gSRgQnZIUDXmYU3f7lRhbqZ90/0lw3CCQ3xc370WgjLrlzBEtXTePOQDSJga4bKZkQv2BGnsmNIrxLZJhZn58ymuapH6e+15cQlypfV2QIQ6nFtMsEWluYQGUvgy1mA5YVNKuSexQDB1JaM5OKKhq3zBphE5usTP X-OriginatorOrg: Nvidia.com X-MS-Exchange-CrossTenant-Network-Message-Id: 56b9b6f6-289c-4523-da99-08ddad007f79 X-MS-Exchange-CrossTenant-AuthSource: CH3PR12MB8659.namprd12.prod.outlook.com X-MS-Exchange-CrossTenant-AuthAs: Internal X-MS-Exchange-CrossTenant-OriginalArrivalTime: 16 Jun 2025 18:06:21.3718 (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: OVnEb94KD3BLKpPA97hG8LF4K67oVrQ/+aGPLENv0kxmjJTAv1bXp9DWqTcIIgRq X-MS-Exchange-Transport-CrossTenantHeadersStamped: SJ1PR12MB6267 None of this is used anymore, delete it. Tested-by: Alejandro Jimenez Signed-off-by: Jason Gunthorpe --- drivers/iommu/amd/Makefile | 2 +- drivers/iommu/amd/amd_iommu_types.h | 97 ----- drivers/iommu/amd/io_pgtable.c | 559 ---------------------------- drivers/iommu/amd/io_pgtable_v2.c | 370 ------------------ drivers/iommu/io-pgtable.c | 4 - include/linux/io-pgtable.h | 2 - 6 files changed, 1 insertion(+), 1033 deletions(-) delete mode 100644 drivers/iommu/amd/io_pgtable.c delete mode 100644 drivers/iommu/amd/io_pgtable_v2.c diff --git a/drivers/iommu/amd/Makefile b/drivers/iommu/amd/Makefile index 59c04a67f39825..5412a563c6979c 100644 --- a/drivers/iommu/amd/Makefile +++ b/drivers/iommu/amd/Makefile @@ -1,3 +1,3 @@ # SPDX-License-Identifier: GPL-2.0-only -obj-y += iommu.o init.o quirks.o io_pgtable.o io_pgtable_v2.o ppr.o pasid.o +obj-y += iommu.o init.o quirks.o ppr.o pasid.o obj-$(CONFIG_AMD_IOMMU_DEBUGFS) += debugfs.o diff --git a/drivers/iommu/amd/amd_iommu_types.h b/drivers/iommu/amd/amd_iommu_types.h index 08b105536a8e5e..5cb5312057124b 100644 --- a/drivers/iommu/amd/amd_iommu_types.h +++ b/drivers/iommu/amd/amd_iommu_types.h @@ -18,7 +18,6 @@ #include #include #include -#include #include /* @@ -337,76 +336,7 @@ #define GUEST_PGTABLE_4_LEVEL 0x00 #define GUEST_PGTABLE_5_LEVEL 0x01 -#define PM_LEVEL_SHIFT(x) (12 + ((x) * 9)) -#define PM_LEVEL_SIZE(x) (((x) < 6) ? \ - ((1ULL << PM_LEVEL_SHIFT((x))) - 1): \ - (0xffffffffffffffffULL)) -#define PM_LEVEL_INDEX(x, a) (((a) >> PM_LEVEL_SHIFT((x))) & 0x1ffULL) -#define PM_LEVEL_ENC(x) (((x) << 9) & 0xe00ULL) -#define PM_LEVEL_PDE(x, a) ((a) | PM_LEVEL_ENC((x)) | \ - IOMMU_PTE_PR | IOMMU_PTE_IR | IOMMU_PTE_IW) -#define PM_PTE_LEVEL(pte) (((pte) >> 9) & 0x7ULL) - -#define PM_MAP_4k 0 #define PM_ADDR_MASK 0x000ffffffffff000ULL -#define PM_MAP_MASK(lvl) (PM_ADDR_MASK & \ - (~((1ULL << (12 + ((lvl) * 9))) - 1))) -#define PM_ALIGNED(lvl, addr) ((PM_MAP_MASK(lvl) & (addr)) == (addr)) - -/* - * Returns the page table level to use for a given page size - * Pagesize is expected to be a power-of-two - */ -#define PAGE_SIZE_LEVEL(pagesize) \ - ((__ffs(pagesize) - 12) / 9) -/* - * Returns the number of ptes to use for a given page size - * Pagesize is expected to be a power-of-two - */ -#define PAGE_SIZE_PTE_COUNT(pagesize) \ - (1ULL << ((__ffs(pagesize) - 12) % 9)) - -/* - * Aligns a given io-virtual address to a given page size - * Pagesize is expected to be a power-of-two - */ -#define PAGE_SIZE_ALIGN(address, pagesize) \ - ((address) & ~((pagesize) - 1)) -/* - * Creates an IOMMU PTE for an address and a given pagesize - * The PTE has no permission bits set - * Pagesize is expected to be a power-of-two larger than 4096 - */ -#define PAGE_SIZE_PTE(address, pagesize) \ - (((address) | ((pagesize) - 1)) & \ - (~(pagesize >> 1)) & PM_ADDR_MASK) - -/* - * Takes a PTE value with mode=0x07 and returns the page size it maps - */ -#define PTE_PAGE_SIZE(pte) \ - (1ULL << (1 + ffz(((pte) | 0xfffULL)))) - -/* - * Takes a page-table level and returns the default page-size for this level - */ -#define PTE_LEVEL_PAGE_SIZE(level) \ - (1ULL << (12 + (9 * (level)))) - -/* - * The IOPTE dirty bit - */ -#define IOMMU_PTE_HD_BIT (6) - -/* - * Bit value definition for I/O PTE fields - */ -#define IOMMU_PTE_PR BIT_ULL(0) -#define IOMMU_PTE_HD BIT_ULL(IOMMU_PTE_HD_BIT) -#define IOMMU_PTE_U BIT_ULL(59) -#define IOMMU_PTE_FC BIT_ULL(60) -#define IOMMU_PTE_IR BIT_ULL(61) -#define IOMMU_PTE_IW BIT_ULL(62) /* * Bit value definition for DTE fields @@ -436,12 +366,6 @@ /* DTE[128:179] | DTE[184:191] */ #define DTE_DATA2_INTR_MASK ~GENMASK_ULL(55, 52) -#define IOMMU_PAGE_MASK (((1ULL << 52) - 1) & ~0xfffULL) -#define IOMMU_PTE_PRESENT(pte) ((pte) & IOMMU_PTE_PR) -#define IOMMU_PTE_DIRTY(pte) ((pte) & IOMMU_PTE_HD) -#define IOMMU_PTE_PAGE(pte) (iommu_phys_to_virt((pte) & IOMMU_PAGE_MASK)) -#define IOMMU_PTE_MODE(pte) (((pte) >> 9) & 0x07) - #define IOMMU_PROT_MASK 0x03 #define IOMMU_PROT_IR 0x01 #define IOMMU_PROT_IW 0x02 @@ -531,19 +455,6 @@ struct amd_irte_ops; #define AMD_IOMMU_FLAG_TRANS_PRE_ENABLED (1 << 0) -#define io_pgtable_to_data(x) \ - container_of((x), struct amd_io_pgtable, pgtbl) - -#define io_pgtable_ops_to_data(x) \ - io_pgtable_to_data(io_pgtable_ops_to_pgtable(x)) - -#define io_pgtable_ops_to_domain(x) \ - container_of(io_pgtable_ops_to_data(x), \ - struct protection_domain, iop) - -#define io_pgtable_cfg_to_data(x) \ - container_of((x), struct amd_io_pgtable, pgtbl.cfg) - struct gcr3_tbl_info { u64 *gcr3_tbl; /* Guest CR3 table */ int glx; /* Number of levels for GCR3 table */ @@ -551,13 +462,6 @@ struct gcr3_tbl_info { u16 domid; /* Per device domain ID */ }; -struct amd_io_pgtable { - struct io_pgtable pgtbl; - int mode; - u64 *root; - u64 *pgd; /* v2 pgtable pgd pointer */ -}; - enum protection_domain_mode { PD_MODE_V1 = 1, PD_MODE_V2, @@ -591,7 +495,6 @@ struct protection_domain { struct pt_iommu_x86_64 amdv2; }; struct list_head dev_list; /* List of all devices in this domain */ - struct amd_io_pgtable iop; spinlock_t lock; /* mostly used to lock the page table*/ u16 id; /* the domain id written to the device table */ enum protection_domain_mode pd_mode; /* Track page table type */ diff --git a/drivers/iommu/amd/io_pgtable.c b/drivers/iommu/amd/io_pgtable.c deleted file mode 100644 index e4b33eba554599..00000000000000 --- a/drivers/iommu/amd/io_pgtable.c +++ /dev/null @@ -1,559 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-only -/* - * CPU-agnostic AMD IO page table allocator. - * - * Copyright (C) 2020 Advanced Micro Devices, Inc. - * Author: Suravee Suthikulpanit - */ - -#define pr_fmt(fmt) "AMD-Vi: " fmt -#define dev_fmt(fmt) pr_fmt(fmt) - -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#include "amd_iommu_types.h" -#include "amd_iommu.h" -#include "../iommu-pages.h" - -/* - * Helper function to get the first pte of a large mapping - */ -static u64 *first_pte_l7(u64 *pte, unsigned long *page_size, - unsigned long *count) -{ - unsigned long pte_mask, pg_size, cnt; - u64 *fpte; - - pg_size = PTE_PAGE_SIZE(*pte); - cnt = PAGE_SIZE_PTE_COUNT(pg_size); - pte_mask = ~((cnt << 3) - 1); - fpte = (u64 *)(((unsigned long)pte) & pte_mask); - - if (page_size) - *page_size = pg_size; - - if (count) - *count = cnt; - - return fpte; -} - -static void free_pt_lvl(u64 *pt, struct iommu_pages_list *freelist, int lvl) -{ - u64 *p; - int i; - - for (i = 0; i < 512; ++i) { - /* PTE present? */ - if (!IOMMU_PTE_PRESENT(pt[i])) - continue; - - /* Large PTE? */ - if (PM_PTE_LEVEL(pt[i]) == 0 || - PM_PTE_LEVEL(pt[i]) == 7) - continue; - - /* - * Free the next level. No need to look at l1 tables here since - * they can only contain leaf PTEs; just free them directly. - */ - p = IOMMU_PTE_PAGE(pt[i]); - if (lvl > 2) - free_pt_lvl(p, freelist, lvl - 1); - else - iommu_pages_list_add(freelist, p); - } - - iommu_pages_list_add(freelist, pt); -} - -static void free_sub_pt(u64 *root, int mode, struct iommu_pages_list *freelist) -{ - switch (mode) { - case PAGE_MODE_NONE: - case PAGE_MODE_7_LEVEL: - break; - case PAGE_MODE_1_LEVEL: - iommu_pages_list_add(freelist, root); - break; - case PAGE_MODE_2_LEVEL: - case PAGE_MODE_3_LEVEL: - case PAGE_MODE_4_LEVEL: - case PAGE_MODE_5_LEVEL: - case PAGE_MODE_6_LEVEL: - free_pt_lvl(root, freelist, mode); - break; - default: - BUG(); - } -} - -/* - * This function is used to add another level to an IO page table. Adding - * another level increases the size of the address space by 9 bits to a size up - * to 64 bits. - */ -static bool increase_address_space(struct amd_io_pgtable *pgtable, - unsigned long address, - unsigned int page_size_level, - gfp_t gfp) -{ - struct io_pgtable_cfg *cfg = &pgtable->pgtbl.cfg; - struct protection_domain *domain = - container_of(pgtable, struct protection_domain, iop); - unsigned long flags; - bool ret = true; - u64 *pte; - - pte = iommu_alloc_pages_node_sz(cfg->amd.nid, gfp, SZ_4K); - if (!pte) - return false; - - spin_lock_irqsave(&domain->lock, flags); - - if (address <= PM_LEVEL_SIZE(pgtable->mode) && - pgtable->mode - 1 >= page_size_level) - goto out; - - ret = false; - if (WARN_ON_ONCE(pgtable->mode == PAGE_MODE_6_LEVEL)) - goto out; - - *pte = PM_LEVEL_PDE(pgtable->mode, iommu_virt_to_phys(pgtable->root)); - - pgtable->root = pte; - pgtable->mode += 1; - - pte = NULL; - ret = true; - -out: - spin_unlock_irqrestore(&domain->lock, flags); - iommu_free_pages(pte); - - return ret; -} - -static u64 *alloc_pte(struct amd_io_pgtable *pgtable, - unsigned long address, - unsigned long page_size, - u64 **pte_page, - gfp_t gfp, - bool *updated) -{ - unsigned long last_addr = address + (page_size - 1); - struct io_pgtable_cfg *cfg = &pgtable->pgtbl.cfg; - int level, end_lvl; - u64 *pte, *page; - - BUG_ON(!is_power_of_2(page_size)); - - while (last_addr > PM_LEVEL_SIZE(pgtable->mode) || - pgtable->mode - 1 < PAGE_SIZE_LEVEL(page_size)) { - /* - * Return an error if there is no memory to update the - * page-table. - */ - if (!increase_address_space(pgtable, last_addr, - PAGE_SIZE_LEVEL(page_size), gfp)) - return NULL; - } - - - level = pgtable->mode - 1; - pte = &pgtable->root[PM_LEVEL_INDEX(level, address)]; - address = PAGE_SIZE_ALIGN(address, page_size); - end_lvl = PAGE_SIZE_LEVEL(page_size); - - while (level > end_lvl) { - u64 __pte, __npte; - int pte_level; - - __pte = *pte; - pte_level = PM_PTE_LEVEL(__pte); - - /* - * If we replace a series of large PTEs, we need - * to tear down all of them. - */ - if (IOMMU_PTE_PRESENT(__pte) && - pte_level == PAGE_MODE_7_LEVEL) { - unsigned long count, i; - u64 *lpte; - - lpte = first_pte_l7(pte, NULL, &count); - - /* - * Unmap the replicated PTEs that still match the - * original large mapping - */ - for (i = 0; i < count; ++i) - cmpxchg64(&lpte[i], __pte, 0ULL); - - *updated = true; - continue; - } - - if (!IOMMU_PTE_PRESENT(__pte) || - pte_level == PAGE_MODE_NONE) { - page = iommu_alloc_pages_node_sz(cfg->amd.nid, gfp, - SZ_4K); - - if (!page) - return NULL; - - __npte = PM_LEVEL_PDE(level, iommu_virt_to_phys(page)); - - /* pte could have been changed somewhere. */ - if (!try_cmpxchg64(pte, &__pte, __npte)) - iommu_free_pages(page); - else if (IOMMU_PTE_PRESENT(__pte)) - *updated = true; - - continue; - } - - /* No level skipping support yet */ - if (pte_level != level) - return NULL; - - level -= 1; - - pte = IOMMU_PTE_PAGE(__pte); - - if (pte_page && level == end_lvl) - *pte_page = pte; - - pte = &pte[PM_LEVEL_INDEX(level, address)]; - } - - return pte; -} - -/* - * This function checks if there is a PTE for a given dma address. If - * there is one, it returns the pointer to it. - */ -static u64 *fetch_pte(struct amd_io_pgtable *pgtable, - unsigned long address, - unsigned long *page_size) -{ - int level; - u64 *pte; - - *page_size = 0; - - if (address > PM_LEVEL_SIZE(pgtable->mode)) - return NULL; - - level = pgtable->mode - 1; - pte = &pgtable->root[PM_LEVEL_INDEX(level, address)]; - *page_size = PTE_LEVEL_PAGE_SIZE(level); - - while (level > 0) { - - /* Not Present */ - if (!IOMMU_PTE_PRESENT(*pte)) - return NULL; - - /* Large PTE */ - if (PM_PTE_LEVEL(*pte) == PAGE_MODE_7_LEVEL || - PM_PTE_LEVEL(*pte) == PAGE_MODE_NONE) - break; - - /* No level skipping support yet */ - if (PM_PTE_LEVEL(*pte) != level) - return NULL; - - level -= 1; - - /* Walk to the next level */ - pte = IOMMU_PTE_PAGE(*pte); - pte = &pte[PM_LEVEL_INDEX(level, address)]; - *page_size = PTE_LEVEL_PAGE_SIZE(level); - } - - /* - * If we have a series of large PTEs, make - * sure to return a pointer to the first one. - */ - if (PM_PTE_LEVEL(*pte) == PAGE_MODE_7_LEVEL) - pte = first_pte_l7(pte, page_size, NULL); - - return pte; -} - -static void free_clear_pte(u64 *pte, u64 pteval, - struct iommu_pages_list *freelist) -{ - u64 *pt; - int mode; - - while (!try_cmpxchg64(pte, &pteval, 0)) - pr_warn("AMD-Vi: IOMMU pte changed since we read it\n"); - - if (!IOMMU_PTE_PRESENT(pteval)) - return; - - pt = IOMMU_PTE_PAGE(pteval); - mode = IOMMU_PTE_MODE(pteval); - - free_sub_pt(pt, mode, freelist); -} - -/* - * Generic mapping functions. It maps a physical address into a DMA - * address space. It allocates the page table pages if necessary. - * In the future it can be extended to a generic mapping function - * supporting all features of AMD IOMMU page tables like level skipping - * and full 64 bit address spaces. - */ -static int iommu_v1_map_pages(struct io_pgtable_ops *ops, unsigned long iova, - phys_addr_t paddr, size_t pgsize, size_t pgcount, - int prot, gfp_t gfp, size_t *mapped) -{ - struct amd_io_pgtable *pgtable = io_pgtable_ops_to_data(ops); - struct iommu_pages_list freelist = IOMMU_PAGES_LIST_INIT(freelist); - bool updated = false; - u64 __pte, *pte; - int ret, i, count; - size_t size = pgcount << __ffs(pgsize); - unsigned long o_iova = iova; - - BUG_ON(!IS_ALIGNED(iova, pgsize)); - BUG_ON(!IS_ALIGNED(paddr, pgsize)); - - ret = -EINVAL; - if (!(prot & IOMMU_PROT_MASK)) - goto out; - - while (pgcount > 0) { - count = PAGE_SIZE_PTE_COUNT(pgsize); - pte = alloc_pte(pgtable, iova, pgsize, NULL, gfp, &updated); - - ret = -ENOMEM; - if (!pte) - goto out; - - for (i = 0; i < count; ++i) - free_clear_pte(&pte[i], pte[i], &freelist); - - if (!iommu_pages_list_empty(&freelist)) - updated = true; - - if (count > 1) { - __pte = PAGE_SIZE_PTE(__sme_set(paddr), pgsize); - __pte |= PM_LEVEL_ENC(7) | IOMMU_PTE_PR | IOMMU_PTE_FC; - } else - __pte = __sme_set(paddr) | IOMMU_PTE_PR | IOMMU_PTE_FC; - - if (prot & IOMMU_PROT_IR) - __pte |= IOMMU_PTE_IR; - if (prot & IOMMU_PROT_IW) - __pte |= IOMMU_PTE_IW; - - for (i = 0; i < count; ++i) - pte[i] = __pte; - - iova += pgsize; - paddr += pgsize; - pgcount--; - if (mapped) - *mapped += pgsize; - } - - ret = 0; - -out: - if (updated) { - struct protection_domain *dom = io_pgtable_ops_to_domain(ops); - unsigned long flags; - - spin_lock_irqsave(&dom->lock, flags); - /* - * Flush domain TLB(s) and wait for completion. Any Device-Table - * Updates and flushing already happened in - * increase_address_space(). - */ - amd_iommu_domain_flush_pages(dom, o_iova, size); - spin_unlock_irqrestore(&dom->lock, flags); - } - - /* Everything flushed out, free pages now */ - iommu_put_pages_list(&freelist); - - return ret; -} - -static unsigned long iommu_v1_unmap_pages(struct io_pgtable_ops *ops, - unsigned long iova, - size_t pgsize, size_t pgcount, - struct iommu_iotlb_gather *gather) -{ - struct amd_io_pgtable *pgtable = io_pgtable_ops_to_data(ops); - unsigned long long unmapped; - unsigned long unmap_size; - u64 *pte; - size_t size = pgcount << __ffs(pgsize); - - BUG_ON(!is_power_of_2(pgsize)); - - unmapped = 0; - - while (unmapped < size) { - pte = fetch_pte(pgtable, iova, &unmap_size); - if (pte) { - int i, count; - - count = PAGE_SIZE_PTE_COUNT(unmap_size); - for (i = 0; i < count; i++) - pte[i] = 0ULL; - } else { - return unmapped; - } - - iova = (iova & ~(unmap_size - 1)) + unmap_size; - unmapped += unmap_size; - } - - return unmapped; -} - -static phys_addr_t iommu_v1_iova_to_phys(struct io_pgtable_ops *ops, unsigned long iova) -{ - struct amd_io_pgtable *pgtable = io_pgtable_ops_to_data(ops); - unsigned long offset_mask, pte_pgsize; - u64 *pte, __pte; - - pte = fetch_pte(pgtable, iova, &pte_pgsize); - - if (!pte || !IOMMU_PTE_PRESENT(*pte)) - return 0; - - offset_mask = pte_pgsize - 1; - __pte = __sme_clr(*pte & PM_ADDR_MASK); - - return (__pte & ~offset_mask) | (iova & offset_mask); -} - -static bool pte_test_and_clear_dirty(u64 *ptep, unsigned long size, - unsigned long flags) -{ - bool test_only = flags & IOMMU_DIRTY_NO_CLEAR; - bool dirty = false; - int i, count; - - /* - * 2.2.3.2 Host Dirty Support - * When a non-default page size is used , software must OR the - * Dirty bits in all of the replicated host PTEs used to map - * the page. The IOMMU does not guarantee the Dirty bits are - * set in all of the replicated PTEs. Any portion of the page - * may have been written even if the Dirty bit is set in only - * one of the replicated PTEs. - */ - count = PAGE_SIZE_PTE_COUNT(size); - for (i = 0; i < count && test_only; i++) { - if (test_bit(IOMMU_PTE_HD_BIT, (unsigned long *)&ptep[i])) { - dirty = true; - break; - } - } - - for (i = 0; i < count && !test_only; i++) { - if (test_and_clear_bit(IOMMU_PTE_HD_BIT, - (unsigned long *)&ptep[i])) { - dirty = true; - } - } - - return dirty; -} - -static int iommu_v1_read_and_clear_dirty(struct io_pgtable_ops *ops, - unsigned long iova, size_t size, - unsigned long flags, - struct iommu_dirty_bitmap *dirty) -{ - struct amd_io_pgtable *pgtable = io_pgtable_ops_to_data(ops); - unsigned long end = iova + size - 1; - - do { - unsigned long pgsize = 0; - u64 *ptep, pte; - - ptep = fetch_pte(pgtable, iova, &pgsize); - if (ptep) - pte = READ_ONCE(*ptep); - if (!ptep || !IOMMU_PTE_PRESENT(pte)) { - pgsize = pgsize ?: PTE_LEVEL_PAGE_SIZE(0); - iova += pgsize; - continue; - } - - /* - * Mark the whole IOVA range as dirty even if only one of - * the replicated PTEs were marked dirty. - */ - if (pte_test_and_clear_dirty(ptep, pgsize, flags)) - iommu_dirty_bitmap_record(dirty, iova, pgsize); - iova += pgsize; - } while (iova < end); - - return 0; -} - -/* - * ---------------------------------------------------- - */ -static void v1_free_pgtable(struct io_pgtable *iop) -{ - struct amd_io_pgtable *pgtable = container_of(iop, struct amd_io_pgtable, pgtbl); - struct iommu_pages_list freelist = IOMMU_PAGES_LIST_INIT(freelist); - - if (pgtable->mode == PAGE_MODE_NONE) - return; - - /* Page-table is not visible to IOMMU anymore, so free it */ - BUG_ON(pgtable->mode < PAGE_MODE_NONE || - pgtable->mode > PAGE_MODE_6_LEVEL); - - free_sub_pt(pgtable->root, pgtable->mode, &freelist); - iommu_put_pages_list(&freelist); -} - -static struct io_pgtable *v1_alloc_pgtable(struct io_pgtable_cfg *cfg, void *cookie) -{ - struct amd_io_pgtable *pgtable = io_pgtable_cfg_to_data(cfg); - - pgtable->root = - iommu_alloc_pages_node_sz(cfg->amd.nid, GFP_KERNEL, SZ_4K); - if (!pgtable->root) - return NULL; - pgtable->mode = PAGE_MODE_3_LEVEL; - - cfg->pgsize_bitmap = amd_iommu_pgsize_bitmap; - cfg->ias = IOMMU_IN_ADDR_BIT_SIZE; - cfg->oas = IOMMU_OUT_ADDR_BIT_SIZE; - - pgtable->pgtbl.ops.map_pages = iommu_v1_map_pages; - pgtable->pgtbl.ops.unmap_pages = iommu_v1_unmap_pages; - pgtable->pgtbl.ops.iova_to_phys = iommu_v1_iova_to_phys; - pgtable->pgtbl.ops.read_and_clear_dirty = iommu_v1_read_and_clear_dirty; - - return &pgtable->pgtbl; -} - -struct io_pgtable_init_fns io_pgtable_amd_iommu_v1_init_fns = { - .alloc = v1_alloc_pgtable, - .free = v1_free_pgtable, -}; diff --git a/drivers/iommu/amd/io_pgtable_v2.c b/drivers/iommu/amd/io_pgtable_v2.c deleted file mode 100644 index b47941353ccbb8..00000000000000 --- a/drivers/iommu/amd/io_pgtable_v2.c +++ /dev/null @@ -1,370 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-only -/* - * CPU-agnostic AMD IO page table v2 allocator. - * - * Copyright (C) 2022, 2023 Advanced Micro Devices, Inc. - * Author: Suravee Suthikulpanit - * Author: Vasant Hegde - */ - -#define pr_fmt(fmt) "AMD-Vi: " fmt -#define dev_fmt(fmt) pr_fmt(fmt) - -#include -#include -#include - -#include - -#include "amd_iommu_types.h" -#include "amd_iommu.h" -#include "../iommu-pages.h" - -#define IOMMU_PAGE_PRESENT BIT_ULL(0) /* Is present */ -#define IOMMU_PAGE_RW BIT_ULL(1) /* Writeable */ -#define IOMMU_PAGE_USER BIT_ULL(2) /* Userspace addressable */ -#define IOMMU_PAGE_PWT BIT_ULL(3) /* Page write through */ -#define IOMMU_PAGE_PCD BIT_ULL(4) /* Page cache disabled */ -#define IOMMU_PAGE_ACCESS BIT_ULL(5) /* Was accessed (updated by IOMMU) */ -#define IOMMU_PAGE_DIRTY BIT_ULL(6) /* Was written to (updated by IOMMU) */ -#define IOMMU_PAGE_PSE BIT_ULL(7) /* Page Size Extensions */ -#define IOMMU_PAGE_NX BIT_ULL(63) /* No execute */ - -#define MAX_PTRS_PER_PAGE 512 - -#define IOMMU_PAGE_SIZE_2M BIT_ULL(21) -#define IOMMU_PAGE_SIZE_1G BIT_ULL(30) - - -static inline int get_pgtable_level(void) -{ - return amd_iommu_gpt_level; -} - -static inline bool is_large_pte(u64 pte) -{ - return (pte & IOMMU_PAGE_PSE); -} - -static inline u64 set_pgtable_attr(u64 *page) -{ - u64 prot; - - prot = IOMMU_PAGE_PRESENT | IOMMU_PAGE_RW | IOMMU_PAGE_USER; - prot |= IOMMU_PAGE_ACCESS; - - return (iommu_virt_to_phys(page) | prot); -} - -static inline void *get_pgtable_pte(u64 pte) -{ - return iommu_phys_to_virt(pte & PM_ADDR_MASK); -} - -static u64 set_pte_attr(u64 paddr, u64 pg_size, int prot) -{ - u64 pte; - - pte = __sme_set(paddr & PM_ADDR_MASK); - pte |= IOMMU_PAGE_PRESENT | IOMMU_PAGE_USER; - pte |= IOMMU_PAGE_ACCESS | IOMMU_PAGE_DIRTY; - - if (prot & IOMMU_PROT_IW) - pte |= IOMMU_PAGE_RW; - - /* Large page */ - if (pg_size == IOMMU_PAGE_SIZE_1G || pg_size == IOMMU_PAGE_SIZE_2M) - pte |= IOMMU_PAGE_PSE; - - return pte; -} - -static inline u64 get_alloc_page_size(u64 size) -{ - if (size >= IOMMU_PAGE_SIZE_1G) - return IOMMU_PAGE_SIZE_1G; - - if (size >= IOMMU_PAGE_SIZE_2M) - return IOMMU_PAGE_SIZE_2M; - - return PAGE_SIZE; -} - -static inline int page_size_to_level(u64 pg_size) -{ - if (pg_size == IOMMU_PAGE_SIZE_1G) - return PAGE_MODE_3_LEVEL; - if (pg_size == IOMMU_PAGE_SIZE_2M) - return PAGE_MODE_2_LEVEL; - - return PAGE_MODE_1_LEVEL; -} - -static void free_pgtable(u64 *pt, int level) -{ - u64 *p; - int i; - - for (i = 0; i < MAX_PTRS_PER_PAGE; i++) { - /* PTE present? */ - if (!IOMMU_PTE_PRESENT(pt[i])) - continue; - - if (is_large_pte(pt[i])) - continue; - - /* - * Free the next level. No need to look at l1 tables here since - * they can only contain leaf PTEs; just free them directly. - */ - p = get_pgtable_pte(pt[i]); - if (level > 2) - free_pgtable(p, level - 1); - else - iommu_free_pages(p); - } - - iommu_free_pages(pt); -} - -/* Allocate page table */ -static u64 *v2_alloc_pte(int nid, u64 *pgd, unsigned long iova, - unsigned long pg_size, gfp_t gfp, bool *updated) -{ - u64 *pte, *page; - int level, end_level; - - level = get_pgtable_level() - 1; - end_level = page_size_to_level(pg_size); - pte = &pgd[PM_LEVEL_INDEX(level, iova)]; - iova = PAGE_SIZE_ALIGN(iova, PAGE_SIZE); - - while (level >= end_level) { - u64 __pte, __npte; - - __pte = *pte; - - if (IOMMU_PTE_PRESENT(__pte) && is_large_pte(__pte)) { - /* Unmap large pte */ - cmpxchg64(pte, *pte, 0ULL); - *updated = true; - continue; - } - - if (!IOMMU_PTE_PRESENT(__pte)) { - page = iommu_alloc_pages_node_sz(nid, gfp, SZ_4K); - if (!page) - return NULL; - - __npte = set_pgtable_attr(page); - /* pte could have been changed somewhere. */ - if (!try_cmpxchg64(pte, &__pte, __npte)) - iommu_free_pages(page); - else if (IOMMU_PTE_PRESENT(__pte)) - *updated = true; - - continue; - } - - level -= 1; - pte = get_pgtable_pte(__pte); - pte = &pte[PM_LEVEL_INDEX(level, iova)]; - } - - /* Tear down existing pte entries */ - if (IOMMU_PTE_PRESENT(*pte)) { - u64 *__pte; - - *updated = true; - __pte = get_pgtable_pte(*pte); - cmpxchg64(pte, *pte, 0ULL); - if (pg_size == IOMMU_PAGE_SIZE_1G) - free_pgtable(__pte, end_level - 1); - else if (pg_size == IOMMU_PAGE_SIZE_2M) - iommu_free_pages(__pte); - } - - return pte; -} - -/* - * This function checks if there is a PTE for a given dma address. - * If there is one, it returns the pointer to it. - */ -static u64 *fetch_pte(struct amd_io_pgtable *pgtable, - unsigned long iova, unsigned long *page_size) -{ - u64 *pte; - int level; - - level = get_pgtable_level() - 1; - pte = &pgtable->pgd[PM_LEVEL_INDEX(level, iova)]; - /* Default page size is 4K */ - *page_size = PAGE_SIZE; - - while (level) { - /* Not present */ - if (!IOMMU_PTE_PRESENT(*pte)) - return NULL; - - /* Walk to the next level */ - pte = get_pgtable_pte(*pte); - pte = &pte[PM_LEVEL_INDEX(level - 1, iova)]; - - /* Large page */ - if (is_large_pte(*pte)) { - if (level == PAGE_MODE_3_LEVEL) - *page_size = IOMMU_PAGE_SIZE_1G; - else if (level == PAGE_MODE_2_LEVEL) - *page_size = IOMMU_PAGE_SIZE_2M; - else - return NULL; /* Wrongly set PSE bit in PTE */ - - break; - } - - level -= 1; - } - - return pte; -} - -static int iommu_v2_map_pages(struct io_pgtable_ops *ops, unsigned long iova, - phys_addr_t paddr, size_t pgsize, size_t pgcount, - int prot, gfp_t gfp, size_t *mapped) -{ - struct amd_io_pgtable *pgtable = io_pgtable_ops_to_data(ops); - struct io_pgtable_cfg *cfg = &pgtable->pgtbl.cfg; - u64 *pte; - unsigned long map_size; - unsigned long mapped_size = 0; - unsigned long o_iova = iova; - size_t size = pgcount << __ffs(pgsize); - int ret = 0; - bool updated = false; - - if (WARN_ON(!pgsize || (pgsize & cfg->pgsize_bitmap) != pgsize) || !pgcount) - return -EINVAL; - - if (!(prot & IOMMU_PROT_MASK)) - return -EINVAL; - - while (mapped_size < size) { - map_size = get_alloc_page_size(pgsize); - pte = v2_alloc_pte(cfg->amd.nid, pgtable->pgd, - iova, map_size, gfp, &updated); - if (!pte) { - ret = -ENOMEM; - goto out; - } - - *pte = set_pte_attr(paddr, map_size, prot); - - iova += map_size; - paddr += map_size; - mapped_size += map_size; - } - -out: - if (updated) { - struct protection_domain *pdom = io_pgtable_ops_to_domain(ops); - unsigned long flags; - - spin_lock_irqsave(&pdom->lock, flags); - amd_iommu_domain_flush_pages(pdom, o_iova, size); - spin_unlock_irqrestore(&pdom->lock, flags); - } - - if (mapped) - *mapped += mapped_size; - - return ret; -} - -static unsigned long iommu_v2_unmap_pages(struct io_pgtable_ops *ops, - unsigned long iova, - size_t pgsize, size_t pgcount, - struct iommu_iotlb_gather *gather) -{ - struct amd_io_pgtable *pgtable = io_pgtable_ops_to_data(ops); - struct io_pgtable_cfg *cfg = &pgtable->pgtbl.cfg; - unsigned long unmap_size; - unsigned long unmapped = 0; - size_t size = pgcount << __ffs(pgsize); - u64 *pte; - - if (WARN_ON(!pgsize || (pgsize & cfg->pgsize_bitmap) != pgsize || !pgcount)) - return 0; - - while (unmapped < size) { - pte = fetch_pte(pgtable, iova, &unmap_size); - if (!pte) - return unmapped; - - *pte = 0ULL; - - iova = (iova & ~(unmap_size - 1)) + unmap_size; - unmapped += unmap_size; - } - - return unmapped; -} - -static phys_addr_t iommu_v2_iova_to_phys(struct io_pgtable_ops *ops, unsigned long iova) -{ - struct amd_io_pgtable *pgtable = io_pgtable_ops_to_data(ops); - unsigned long offset_mask, pte_pgsize; - u64 *pte, __pte; - - pte = fetch_pte(pgtable, iova, &pte_pgsize); - if (!pte || !IOMMU_PTE_PRESENT(*pte)) - return 0; - - offset_mask = pte_pgsize - 1; - __pte = __sme_clr(*pte & PM_ADDR_MASK); - - return (__pte & ~offset_mask) | (iova & offset_mask); -} - -/* - * ---------------------------------------------------- - */ -static void v2_free_pgtable(struct io_pgtable *iop) -{ - struct amd_io_pgtable *pgtable = container_of(iop, struct amd_io_pgtable, pgtbl); - - if (!pgtable || !pgtable->pgd) - return; - - /* Free page table */ - free_pgtable(pgtable->pgd, get_pgtable_level()); - pgtable->pgd = NULL; -} - -static struct io_pgtable *v2_alloc_pgtable(struct io_pgtable_cfg *cfg, void *cookie) -{ - struct amd_io_pgtable *pgtable = io_pgtable_cfg_to_data(cfg); - int ias = IOMMU_IN_ADDR_BIT_SIZE; - - pgtable->pgd = iommu_alloc_pages_node_sz(cfg->amd.nid, GFP_KERNEL, SZ_4K); - if (!pgtable->pgd) - return NULL; - - if (get_pgtable_level() == PAGE_MODE_5_LEVEL) - ias = 57; - - pgtable->pgtbl.ops.map_pages = iommu_v2_map_pages; - pgtable->pgtbl.ops.unmap_pages = iommu_v2_unmap_pages; - pgtable->pgtbl.ops.iova_to_phys = iommu_v2_iova_to_phys; - - cfg->pgsize_bitmap = AMD_IOMMU_PGSIZES_V2; - cfg->ias = ias; - cfg->oas = IOMMU_OUT_ADDR_BIT_SIZE; - - return &pgtable->pgtbl; -} - -struct io_pgtable_init_fns io_pgtable_amd_iommu_v2_init_fns = { - .alloc = v2_alloc_pgtable, - .free = v2_free_pgtable, -}; diff --git a/drivers/iommu/io-pgtable.c b/drivers/iommu/io-pgtable.c index 8841c1487f0048..843fec8e8a511c 100644 --- a/drivers/iommu/io-pgtable.c +++ b/drivers/iommu/io-pgtable.c @@ -28,10 +28,6 @@ io_pgtable_init_table[IO_PGTABLE_NUM_FMTS] = { #ifdef CONFIG_IOMMU_IO_PGTABLE_ARMV7S [ARM_V7S] = &io_pgtable_arm_v7s_init_fns, #endif -#ifdef CONFIG_AMD_IOMMU - [AMD_IOMMU_V1] = &io_pgtable_amd_iommu_v1_init_fns, - [AMD_IOMMU_V2] = &io_pgtable_amd_iommu_v2_init_fns, -#endif }; static int check_custom_allocator(enum io_pgtable_fmt fmt, diff --git a/include/linux/io-pgtable.h b/include/linux/io-pgtable.h index 138fbd89b1e633..75fd67cc2b7a46 100644 --- a/include/linux/io-pgtable.h +++ b/include/linux/io-pgtable.h @@ -15,8 +15,6 @@ enum io_pgtable_fmt { ARM_64_LPAE_S2, ARM_V7S, ARM_MALI_LPAE, - AMD_IOMMU_V1, - AMD_IOMMU_V2, APPLE_DART, APPLE_DART2, IO_PGTABLE_NUM_FMTS, From patchwork Mon Jun 16 18:06:18 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jason Gunthorpe X-Patchwork-Id: 897134 Received: from NAM04-DM6-obe.outbound.protection.outlook.com (mail-dm6nam04on2081.outbound.protection.outlook.com [40.107.102.81]) (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 3EE86289808; Mon, 16 Jun 2025 18:06:35 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=fail smtp.client-ip=40.107.102.81 ARC-Seal: i=2; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1750097197; cv=fail; b=s55gS23vKWpS1Cbi0dONfeYEqrcF7ELORRf1r2b2/ok4vhU8Nv6UQqNucl588R0rrGLdimGcu9XRxv1vMD1XMrgZQDU64c1ILIf0VfqmRjHK4pSc0LfU2u1tYQYMGf+nAdwj9/RneciSbvBsUw4d8L5ATWBhJEWfNHymibaLZU4= ARC-Message-Signature: i=2; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1750097197; c=relaxed/simple; bh=ruRt/emJGWyrRlLS/6nYv+8lEf9wxdTBBckc9eIn3Vc=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: Content-Type:MIME-Version; b=oBCNdPar6i4mNRuYf9C51qchCyJ2vOZfcWby1m7fPetPJ1hRDuXz0iqc+I8jabrCD6nf1y9PBdyFaadPJinA6hxUHxDi783SjZ3uhY87KcGChOZHneSwUG5B9Dz/WqtGWGr/Q+/idPC4JKPur4k0u/f3S0bIPhgxz4gJFq707vg= 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=Wv7y5u5T; arc=fail smtp.client-ip=40.107.102.81 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="Wv7y5u5T" ARC-Seal: i=1; a=rsa-sha256; s=arcselector10001; d=microsoft.com; cv=none; b=rrbJMAd4gbT2H1RJtT6iqX+IYI5reZVGX5Uu2zQjwTQ5T36gIZdbJSldtpTeLQbVu9q0EMDT/SyOnTszrW401nJYNco+2D6LjBCom8GyraRpXnCRLO/PE8WW+tuGD1OfqByX5kn5LQoGcDuJBzOtg5u3EPSUgFJyaDMg4gMXeRyk11yJOYWsgNKmpHb6Ras1FvoG1dQ39V4ru8MTCzzpp42edOwHcfNlPluR1j4zjVkVQQYIRGvw5HxTVwbIapf30OHLuS7C96BmDahJ+eEioWZfa6iFFJ3GHZzjlm2g6NgltEPe7ii3P+tr98qjmpPln1h5kRuNnB2Y+7jKia3eWg== 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=H//Pf8eJtBhimNX2BzyuhQ2AelG2OalJ5cJLrOiXX7s=; b=GzoSioejaxPt11R4pw/28MDlW7R7QP8tlmJQ4WViTs42u/DtFBOFdcyawD3cB64pptMG7iKAXawsbXZnoRBIj/pgDUBgK2TnDu1n052i/3bsywpFPnLBRKsXYiNO8eBk1cT2xW7rLExMIxq0/EYhlGEJL70S0G67JvOxoHE/U36KBIB9ECxXDXzJKfEFpqi62M/vjX+P1FsdcDd81J21ukHNYYJQjHd07AhxNKVLxZ6/ckZl/x6g/ZhM4cNSIkXLARBn0CR1QLmmAU/GFgXiokw1n1ysk5O/jmFP0Dq3SqMKb5b7BbO2ciZHNGYwFuhlgdzBV7KaF6dze4XNdJdRuA== 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=H//Pf8eJtBhimNX2BzyuhQ2AelG2OalJ5cJLrOiXX7s=; b=Wv7y5u5TBKtRbbckxj0ZbNpQjAyNGKUwhCYko4f5zltykZEUYTzPFpAkIQI1l3Pw7mrXIJm+jmMTgINTZ+GU7S4AfeaGdo3/GeMhM7iW12kxr1QMQ13TtQRxLxUw5XwJQ9Rh3yD2CnPTz+D3b9s2XLn5t2durOSHM4e+uwDgtP08APr4oHZSTWCY/ikdu1Y1pgTLBdMPl3X+eGVmD0eZ7OQ3A0YlJSO/w+hT1XUYtxCpHlaELyBOmdRmG1FT/rnINU+dVZRlRIjaTlvKxdERIY9JaKmCTEMJAinqdaOJGF+kxca1urdT44SszzJEgNh/ipc+LIK0KkoqXMoX43aTrQ== Authentication-Results: dkim=none (message not signed) header.d=none;dmarc=none action=none header.from=nvidia.com; Received: from CH3PR12MB8659.namprd12.prod.outlook.com (2603:10b6:610:17c::13) by SN7PR12MB7321.namprd12.prod.outlook.com (2603:10b6:806:298::14) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.20.8835.26; Mon, 16 Jun 2025 18:06:28 +0000 Received: from CH3PR12MB8659.namprd12.prod.outlook.com ([fe80::6eb6:7d37:7b4b:1732]) by CH3PR12MB8659.namprd12.prod.outlook.com ([fe80::6eb6:7d37:7b4b:1732%7]) with mapi id 15.20.8835.023; Mon, 16 Jun 2025 18:06:28 +0000 From: Jason Gunthorpe To: Jonathan Corbet , iommu@lists.linux.dev, Joerg Roedel , Justin Stitt , Kevin Tian , linux-doc@vger.kernel.org, linux-kselftest@vger.kernel.org, llvm@lists.linux.dev, Bill Wendling , Nathan Chancellor , Nick Desaulniers , Miguel Ojeda , Robin Murphy , Shuah Khan , Suravee Suthikulpanit , Will Deacon Cc: Alexey Kardashevskiy , Alejandro Jimenez , James Gowans , Michael Roth , Pasha Tatashin , patches@lists.linux.dev Subject: [PATCH v3 15/15] iommupt: Add a kunit test for the IOMMU implementation Date: Mon, 16 Jun 2025 15:06:18 -0300 Message-ID: <15-v3-a93aab628dbc+521-iommu_pt_jgg@nvidia.com> In-Reply-To: <0-v3-a93aab628dbc+521-iommu_pt_jgg@nvidia.com> References: X-ClientProxiedBy: CYXPR02CA0003.namprd02.prod.outlook.com (2603:10b6:930:cf::18) To CH3PR12MB8659.namprd12.prod.outlook.com (2603:10b6:610:17c::13) Precedence: bulk X-Mailing-List: linux-kselftest@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 X-MS-PublicTrafficType: Email X-MS-TrafficTypeDiagnostic: CH3PR12MB8659:EE_|SN7PR12MB7321:EE_ X-MS-Office365-Filtering-Correlation-Id: 74d6f682-fa71-4d26-52ba-08ddad008147 X-MS-Exchange-SenderADCheck: 1 X-MS-Exchange-AntiSpam-Relay: 0 X-Microsoft-Antispam: BCL:0; ARA:13230040|366016|1800799024|7416014|376014|921020; X-Microsoft-Antispam-Message-Info: 4gywHCUG5vQrfTvqibzwgNQ1RvpXmsuZm6g6jsOxScWEZNV3uciEMDF+XiAS73kKgTQt16ANPl/YoF5Qg5z/J1sTeyWWlWlqYMHnIfzeULYfg5vnzSa5ltuZAtWUgVPWUi7Sk4f9Q9qF0qfWS4MXWKR+FtNT5V40N+QoO2GmL/LlBtzNqeZTXl72T6n7DTVfKEDAdD3GmgfOGrqsbT+SbRijaRoRhrkw2kIpl6g++By6KnoeP5+qgRIDqtsMKc/TRcUeOrgj1kM6e71R9utJAEnYD871R4zGob1BK2dkdybpbdM3do06xEtZMi5NONrNOTBYMKEIwlefg+drf+BBAWLsX5L//NEmW7boTlW/xPJYg7ZB3u3HXbGb6u8JthfFUeSZoMxwN2yDl8UByB6sdjWOimeyDNkZogkojcqyLLh4MYlJrQ6u51qsf8bYVZbI9WI0t1CM27hFNBVAoZ51VB0nuA8maABzW3vFn54XC73BxvETfH3aXWiJ57AmL2EglLNz9Wdd+NuEjYyUz7CQaZ5Tz4fTytoQHXNWxHSCPhyWS5bqA7Tob4V+eWRZwOU6JCsjhNo9oX751qwdSqNZFJF8ZjDsKUZ8vHosNVKtIwTV591vGI9pBV61zN89OdEC0aFYUZL3TjRMlohU+jbEbH9GpK8SNici8VTdPw1YtDe44EX0EujoW7wjBs6H8Ve+90ka3ITBUTPm190C2dBle4s1Wq+ESfMO/QOVVyBcRbYlUCd7UDhR1os6MDcY8O+KQG1S03ZIMjgkJ5imx9sr+cl/SbtuHxJ1oDGZKx50RKBUCHm1kqXent2G6XnkqgydcYpTqMc/Z6ToIsvs58bl0nfYh8v90ACCqNCh4uJGJRR4VR/xNPVRaQGnA1yuBoZ1S/8PCfiEiCWBgVeAvTFWlA8tioUYp6Acxs+Ab3dvvy8FopwJlt1Mw2GFIYvaL3T6EkjkEE9b+IrCmnQ3FE4UHCxU9HfcxkC2H/8oUXmgJXdpocBgZG37BID8ZwslZb1CLs/B2gKm6R1L/z3cOElEyC2uCJ8+6vem6J2BTptt1htIXEHy+y36+gJzgidfwOlg3VOn6DY4W2K0UCNHgbLdqTkGvdIdC6if3hFI+xjtmESYrjtY/wT6yDG7t6RGweWrohe4sigWn9kW74LmrUV4UjhdQsbBhtmUJt8PWAOR2ZbeI0Wa2xHc3/7xMz8Uo7r8B4lPkPAw1ul8SM2fadxDbiPaKmyoYHIBO009fo7kJAvDfW2OHFSK5+jaNvQWwJExVwACu8YDkGaXgpthohbtzhMF/c2XArg1uw9bAhlFmgXxzjaezdyOiGwcyFFR27E4AlZWqwwrSXnQfQcvYtu67e2YI6MPwmr6ytAAzzlrSyq3W/usdLxbIGiD9RccVQSbUffPqk4AsGVLIsOIqdbgw+Fb8swnybD17Y8BT1S8cmaJidyUD5rNntqQnI2Gr35LFhBZ7u+kZHfczpmnTtQKdQ== X-Forefront-Antispam-Report: CIP:255.255.255.255; CTRY:; LANG:en; SCL:1; SRV:; IPV:NLI; SFV:NSPM; H:CH3PR12MB8659.namprd12.prod.outlook.com; PTR:; CAT:NONE; SFS:(13230040)(366016)(1800799024)(7416014)(376014)(921020); DIR:OUT; SFP:1101; X-MS-Exchange-AntiSpam-MessageData-ChunkCount: 1 X-MS-Exchange-AntiSpam-MessageData-0: xSYsIeQ/PAQekGKhz16hDvx0b5MZt76KbRz5/4OWtG/01RCo5nszdpWUaqXzbTTN0FLd+U3aEOmViW+TlkUnupheFj7eqIcPm88ti73CX0uWTr2EuWGhxNz371o5+a/8daHA4iWel/f6M1p+0dLsP+QkUmgaCCb9rw7+vXxBLTulnvH1WhWq1q98a7EGly9t2TzxG+D429b7nu5dpsWqI/6rk+cVi6ealPDt/R3eFdUEaMAlKYd7JIlE049AYAkjtKGqr51od17HQaQJatjQ6UH8DXvq5EalELqHchOGIv2mcMHg8bKDXuJgTja7avyJu7qUrg6CaINbUIsWF+HLRI8TPgzoNp9cSVwtuyaCZ7h+doMkHKOG7cXvcoLq73i2uZswdtjyLzyUwwh0/b6gdycZL7Q1/vBboso3dsEENbUpdr4aHAzI/JOhWbT8VkR60U+ezu64GLzX/oiLqiOj2bvIBctDAhZIVyyvaIAMfamSIBlqGBEgf8qHK4RoSZjXhAIGlI4w4hp0Kvo52xe2YVm9a7QrzphPUC9La82oZ1Wp81qF0nK/+rrKf+kfXfkxrCvWVqewur+b0lbjshcrfHkdXXIrADNqKMNAuwYrGd6HrGV/ScarEzSjVYOTqeooUBUt3+XRbyy9xsKYASaXe3o6MKG+A3C86Q1jxdSlJKjpthRU1L6p1J68RZ3CPa6t9WQ/Qzxw3ltOnpcezH27wHSiRzxwgRY7RxSIBavrf/q29f3Mc3hfo7hkuBcaslIDZlncEDIFg0Vok9do/Mn0sNjnmovXWe+zlXmWu3jqt2yd8IwhC6IdG1g8rH//dMHOtJOQ0zEMSaBqFEtpFJKZLeakSYsznNzQGSe9oZI4XxndBMhMwsADWTNVJ9spj2yYvJUdG+PKT1p3qDOtUd+8n+zwmugqRWeCJL9dnAipge4+dD1loj1vM5rFND2Ud/YkWm0ytEkccoTfqyekgcFGolQAn5BIiICpg8ZcU4LvzOCYnjdozI1xvakMQh0RZeKWNHoq6aZ/inrxZhp0trJYH0AfrQfadNSoHVCNBA1cEs51pb51z8vn32Q3h/X2QgE2+cYyZbQ4fi0TunRvjoOzgOl8qQkZCwNcLUg8JhSeG6Bsu+1j18Q4IkotWgy10ypGsrYrUuC5q+KM8zAqU1+zp3YipV8Td0B2gzKEBM0P6BMGpqd6eb573sR5B9XY47/T2X/r5/jO6VuApzjunLfoWKVGB5d8nlBnC7TIpeNiawFxj8Wbkk0nuuPtMaqNG2Lz/i9bHG5nwbgCbMxz08swvgNhawlJCwqQkYDAvgfttCjiKPeR8TWMlMHLi28FYFnuqqdywNlMFkbhYty5++mCR2X1tfunxz1npB0q3el4xGCnmNVoe/+u5yMtMUJHO9bbJ2xMCeCkjiiW158ROfJI54VAoWnsh/QkixOpI5mHRTvECd22QAqu6EsUW4ud1o3ZbbFJxEzPHGKZmG7QjLRgp6EIkLh9OCp9kfk3VWJZsU489YJLk+90s42fGYiDyHDQDj/Cqv1iABgwY/LbR5/iG1N2kbOPANO3xcKFDt6DIYhniYnExonbqk3nvKd6QSk+ X-OriginatorOrg: Nvidia.com X-MS-Exchange-CrossTenant-Network-Message-Id: 74d6f682-fa71-4d26-52ba-08ddad008147 X-MS-Exchange-CrossTenant-AuthSource: CH3PR12MB8659.namprd12.prod.outlook.com X-MS-Exchange-CrossTenant-AuthAs: Internal X-MS-Exchange-CrossTenant-OriginalArrivalTime: 16 Jun 2025 18:06:24.6057 (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: ge0+SbsEpXzPR03lWlsblfJ+meKPSVG61LWpNmUN+5sBYDrEydvFMMigLAVASRAV X-MS-Exchange-Transport-CrossTenantHeadersStamped: SN7PR12MB7321 This intends to have high coverage of the page table format functions and the IOMMU implementation itself, exercising the various corner cases. The kunit can be run in the kunit framework, using commands like: tools/testing/kunit/kunit.py run --build_dir build_kunit_arm64 --arch arm64 --make_options LLVM=-19 --kunitconfig ./drivers/iommu/generic_pt/.kunitconfig tools/testing/kunit/kunit.py run --build_dir build_kunit_uml --kunitconfig ./drivers/iommu/generic_pt/.kunitconfig --kconfig_add CONFIG_WERROR=n --kconfig_add CONFIG_UML_PCI_OVER_VIRTIO_DEVICE_ID=100 tools/testing/kunit/kunit.py run --build_dir build_kunit_x86_64 --arch x86_64 --kunitconfig ./drivers/iommu/generic_pt/.kunitconfig tools/testing/kunit/kunit.py run --build_dir build_kunit_i386 --arch i386 --kunitconfig ./drivers/iommu/generic_pt/.kunitconfig tools/testing/kunit/kunit.py run --build_dir build_kunit_i386pae --arch i386 --kunitconfig ./drivers/iommu/generic_pt/.kunitconfig --kconfig_add CONFIG_X86_PAE=y There are several interesting corner cases on the 32 bit platforms that need checking. Like the generic test they are run on the formats configuration list using kunit "params". This also checks the core iommu parts of the page table code as it enters the logic through a mock iommu_domain. The following are checked: - PT_FEAT_DYNAMIC_TOP properly adds levels one by oen - Evey page size can be iommu_map()'d, and mapping creates that size - iommu_iova_to_phys() works with every page size - Test converting OA -> non present -> OA when the two OAs overlap and free table levels - Test that unmap stops at holes, unmap doesn't split, and unmap returns the right values for partial unmap requests - Randomly map/unmap. Checks map with random sizes, that map fails when hitting collions doing nothing, unmap/map with random intersections and full unmap of random sizes. Also checked iommu_iova_to_phys() with random sizes - Check for memory leaks by monitoring NR_SECONDARY_PAGETABLE Tested-by: Alejandro Jimenez Signed-off-by: Jason Gunthorpe --- drivers/iommu/generic_pt/fmt/iommu_template.h | 1 + drivers/iommu/generic_pt/kunit_iommu.h | 2 + drivers/iommu/generic_pt/kunit_iommu_pt.h | 451 ++++++++++++++++++ 3 files changed, 454 insertions(+) create mode 100644 drivers/iommu/generic_pt/kunit_iommu_pt.h diff --git a/drivers/iommu/generic_pt/fmt/iommu_template.h b/drivers/iommu/generic_pt/fmt/iommu_template.h index 11e85106ae302e..d28e86abdf2e74 100644 --- a/drivers/iommu/generic_pt/fmt/iommu_template.h +++ b/drivers/iommu/generic_pt/fmt/iommu_template.h @@ -44,4 +44,5 @@ * which means we are building the kunit modle. */ #include "../kunit_generic_pt.h" +#include "../kunit_iommu_pt.h" #endif diff --git a/drivers/iommu/generic_pt/kunit_iommu.h b/drivers/iommu/generic_pt/kunit_iommu.h index 8a53b1d772ca9d..cca4e72efcaa04 100644 --- a/drivers/iommu/generic_pt/kunit_iommu.h +++ b/drivers/iommu/generic_pt/kunit_iommu.h @@ -70,6 +70,8 @@ struct kunit_iommu_priv { unsigned int largest_pgsz_lg2; pt_oaddr_t test_oa; pt_vaddr_t safe_pgsize_bitmap; + unsigned long orig_nr_secondary_pagetable; + }; PT_IOMMU_CHECK_DOMAIN(struct kunit_iommu_priv, fmt_table.iommu, domain); diff --git a/drivers/iommu/generic_pt/kunit_iommu_pt.h b/drivers/iommu/generic_pt/kunit_iommu_pt.h new file mode 100644 index 00000000000000..5e25d698450783 --- /dev/null +++ b/drivers/iommu/generic_pt/kunit_iommu_pt.h @@ -0,0 +1,451 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2024, NVIDIA CORPORATION & AFFILIATES + */ +#include "kunit_iommu.h" +#include "pt_iter.h" +#include +#include + +static void do_map(struct kunit *test, pt_vaddr_t va, pt_oaddr_t pa, + pt_vaddr_t len); + +struct count_valids { + u64 per_size[PT_VADDR_MAX_LG2]; +}; + +static int __count_valids(struct pt_range *range, void *arg, unsigned int level, + struct pt_table_p *table) +{ + struct pt_state pts = pt_init(range, level, table); + struct count_valids *valids = arg; + + for_each_pt_level_entry(&pts) { + if (pts.type == PT_ENTRY_TABLE) { + pt_descend(&pts, arg, __count_valids); + continue; + } + if (pts.type == PT_ENTRY_OA) { + valids->per_size[pt_entry_oa_lg2sz(&pts)]++; + continue; + } + } + return 0; +} + +/* + * Number of valid table entries. This counts contiguous entries as a single + * valid. + */ +static unsigned int count_valids(struct kunit *test) +{ + struct kunit_iommu_priv *priv = test->priv; + struct pt_range range = pt_top_range(priv->common); + struct count_valids valids = {}; + u64 total = 0; + unsigned int i; + + KUNIT_ASSERT_NO_ERRNO(test, + pt_walk_range(&range, __count_valids, &valids)); + + for (i = 0; i != ARRAY_SIZE(valids.per_size); i++) + total += valids.per_size[i]; + return total; +} + +/* Only a single page size is present, count the number of valid entries */ +static unsigned int count_valids_single(struct kunit *test, pt_vaddr_t pgsz) +{ + struct kunit_iommu_priv *priv = test->priv; + struct pt_range range = pt_top_range(priv->common); + struct count_valids valids = {}; + u64 total = 0; + unsigned int i; + + KUNIT_ASSERT_NO_ERRNO(test, + pt_walk_range(&range, __count_valids, &valids)); + + for (i = 0; i != ARRAY_SIZE(valids.per_size); i++) { + if ((1ULL << i) == pgsz) + total = valids.per_size[i]; + else + KUNIT_ASSERT_EQ(test, valids.per_size[i], 0); + } + return total; +} + +static void do_unmap(struct kunit *test, pt_vaddr_t va, pt_vaddr_t len) +{ + struct kunit_iommu_priv *priv = test->priv; + size_t ret; + + ret = iommu_unmap(&priv->domain, va, len); + KUNIT_ASSERT_EQ(test, ret, len); +} + +static void check_iova(struct kunit *test, pt_vaddr_t va, pt_oaddr_t pa, + pt_vaddr_t len) +{ + struct kunit_iommu_priv *priv = test->priv; + pt_vaddr_t pfn = log2_div(va, priv->smallest_pgsz_lg2); + pt_vaddr_t end_pfn = pfn + log2_div(len, priv->smallest_pgsz_lg2); + + for (; pfn != end_pfn; pfn++) { + phys_addr_t res = iommu_iova_to_phys(&priv->domain, + pfn * priv->smallest_pgsz); + + KUNIT_ASSERT_EQ(test, res, (phys_addr_t)pa); + if (res != pa) + break; + pa += priv->smallest_pgsz; + } +} + +static void test_increase_level(struct kunit *test) +{ + struct kunit_iommu_priv *priv = test->priv; + struct pt_common *common = priv->common; + + if (!pt_feature(common, PT_FEAT_DYNAMIC_TOP)) + kunit_skip(test, "PT_FEAT_DYNAMIC_TOP not set for this format"); + + if (IS_32BIT) + kunit_skip(test, "Unable to test on 32bit"); + + KUNIT_ASSERT_GT(test, common->max_vasz_lg2, + pt_top_range(common).max_vasz_lg2); + + /* Add every possible level to the max */ + while (common->max_vasz_lg2 != pt_top_range(common).max_vasz_lg2) { + struct pt_range top_range = pt_top_range(common); + + if (top_range.va == 0) + do_map(test, top_range.last_va + 1, 0, + priv->smallest_pgsz); + else + do_map(test, top_range.va - priv->smallest_pgsz, 0, + priv->smallest_pgsz); + + KUNIT_ASSERT_EQ(test, pt_top_range(common).top_level, + top_range.top_level + 1); + KUNIT_ASSERT_GE(test, common->max_vasz_lg2, + pt_top_range(common).max_vasz_lg2); + } +} + +static void test_map_simple(struct kunit *test) +{ + struct kunit_iommu_priv *priv = test->priv; + struct pt_range range = pt_top_range(priv->common); + struct count_valids valids = {}; + pt_vaddr_t pgsize_bitmap = priv->safe_pgsize_bitmap; + unsigned int pgsz_lg2; + pt_vaddr_t cur_va; + + /* Map every reported page size */ + cur_va = range.va + priv->smallest_pgsz * 256; + for (pgsz_lg2 = 0; pgsz_lg2 != PT_VADDR_MAX_LG2; pgsz_lg2++) { + pt_oaddr_t paddr = log2_set_mod(priv->test_oa, 0, pgsz_lg2); + u64 len = log2_to_int(pgsz_lg2); + + if (!(pgsize_bitmap & len)) + continue; + + cur_va = ALIGN(cur_va, len); + do_map(test, cur_va, paddr, len); + if (len <= SZ_2G) + check_iova(test, cur_va, paddr, len); + cur_va += len; + } + + /* The read interface reports that every page size was created */ + range = pt_top_range(priv->common); + KUNIT_ASSERT_NO_ERRNO(test, + pt_walk_range(&range, __count_valids, &valids)); + for (pgsz_lg2 = 0; pgsz_lg2 != PT_VADDR_MAX_LG2; pgsz_lg2++) { + if (pgsize_bitmap & (1ULL << pgsz_lg2)) + KUNIT_ASSERT_EQ(test, valids.per_size[pgsz_lg2], 1); + else + KUNIT_ASSERT_EQ(test, valids.per_size[pgsz_lg2], 0); + } + + /* Unmap works */ + range = pt_top_range(priv->common); + cur_va = range.va + priv->smallest_pgsz * 256; + for (pgsz_lg2 = 0; pgsz_lg2 != PT_VADDR_MAX_LG2; pgsz_lg2++) { + u64 len = log2_to_int(pgsz_lg2); + + if (!(pgsize_bitmap & len)) + continue; + cur_va = ALIGN(cur_va, len); + do_unmap(test, cur_va, len); + cur_va += len; + } + KUNIT_ASSERT_EQ(test, count_valids(test), 0); +} + +/* + * Test to convert a table pointer into an OA by mapping something small, + * unmapping it so as to leave behind a table pointer, then mapping something + * larger that will convert the table into an OA. + */ +static void test_map_table_to_oa(struct kunit *test) +{ + struct kunit_iommu_priv *priv = test->priv; + pt_vaddr_t limited_pgbitmap = + priv->info.pgsize_bitmap % (IS_32BIT ? SZ_2G : SZ_16G); + struct pt_range range = pt_top_range(priv->common); + unsigned int pgsz_lg2; + pt_vaddr_t max_pgsize; + pt_vaddr_t cur_va; + + max_pgsize = 1ULL << (log2_fls(limited_pgbitmap) - 1); + KUNIT_ASSERT_TRUE(test, priv->info.pgsize_bitmap & max_pgsize); + + for (pgsz_lg2 = 0; pgsz_lg2 != PT_VADDR_MAX_LG2; pgsz_lg2++) { + pt_oaddr_t paddr = log2_set_mod(priv->test_oa, 0, pgsz_lg2); + u64 len = log2_to_int(pgsz_lg2); + pt_vaddr_t offset; + + if (!(priv->info.pgsize_bitmap & len)) + continue; + if (len > max_pgsize) + break; + + cur_va = ALIGN(range.va + priv->smallest_pgsz * 256, + max_pgsize); + for (offset = 0; offset != max_pgsize; offset += len) + do_map(test, cur_va + offset, paddr + offset, len); + check_iova(test, cur_va, paddr, max_pgsize); + KUNIT_ASSERT_EQ(test, count_valids_single(test, len), + max_pgsize / len); + + if (len == max_pgsize) { + do_unmap(test, cur_va, max_pgsize); + } else { + do_unmap(test, cur_va, max_pgsize / 2); + for (offset = max_pgsize / 2; offset != max_pgsize; + offset += len) + do_unmap(test, cur_va + offset, len); + } + + KUNIT_ASSERT_EQ(test, count_valids(test), 0); + } +} + +/* + * Test unmapping a small page at the start of a large page. This always unmaps + * the large page. + */ +static void test_unmap_split(struct kunit *test) +{ + struct kunit_iommu_priv *priv = test->priv; + struct pt_range top_range = pt_top_range(priv->common); + pt_vaddr_t pgsize_bitmap = priv->safe_pgsize_bitmap; + unsigned int pgsz_lg2; + unsigned int count = 0; + + for (pgsz_lg2 = 0; pgsz_lg2 != PT_VADDR_MAX_LG2; pgsz_lg2++) { + pt_vaddr_t base_len = log2_to_int(pgsz_lg2); + unsigned int next_pgsz_lg2; + + if (!(pgsize_bitmap & base_len)) + continue; + + for (next_pgsz_lg2 = pgsz_lg2 + 1; + next_pgsz_lg2 != PT_VADDR_MAX_LG2; next_pgsz_lg2++) { + pt_vaddr_t next_len = log2_to_int(next_pgsz_lg2); + pt_vaddr_t vaddr = top_range.va; + pt_oaddr_t paddr = 0; + size_t gnmapped; + + if (!(pgsize_bitmap & next_len)) + continue; + + do_map(test, vaddr, paddr, next_len); + gnmapped = iommu_unmap(&priv->domain, vaddr, base_len); + KUNIT_ASSERT_EQ(test, gnmapped, next_len); + + /* Make sure unmap doesn't keep going */ + do_map(test, vaddr, paddr, next_len); + do_map(test, vaddr + next_len, paddr, next_len); + gnmapped = iommu_unmap(&priv->domain, vaddr, base_len); + KUNIT_ASSERT_EQ(test, gnmapped, next_len); + gnmapped = iommu_unmap(&priv->domain, vaddr + next_len, + next_len); + KUNIT_ASSERT_EQ(test, gnmapped, next_len); + + count++; + } + } + + if (count == 0) + kunit_skip(test, "Test needs two page sizes"); +} + +static void unmap_collisions(struct kunit *test, struct maple_tree *mt, + pt_vaddr_t start, pt_vaddr_t last) +{ + struct kunit_iommu_priv *priv = test->priv; + MA_STATE(mas, mt, start, last); + void *entry; + + mtree_lock(mt); + mas_for_each(&mas, entry, last) { + pt_vaddr_t mas_start = mas.index; + pt_vaddr_t len = (mas.last - mas_start) + 1; + pt_oaddr_t paddr; + + mas_erase(&mas); + mas_pause(&mas); + mtree_unlock(mt); + + paddr = oalog2_mod(mas_start, priv->common->max_oasz_lg2); + check_iova(test, mas_start, paddr, len); + do_unmap(test, mas_start, len); + mtree_lock(mt); + } + mtree_unlock(mt); +} + +static void clamp_range(struct kunit *test, struct pt_range *range) +{ + struct kunit_iommu_priv *priv = test->priv; + + if (range->last_va - range->va > SZ_1G) + range->last_va = range->va + SZ_1G; + KUNIT_ASSERT_NE(test, range->last_va, PT_VADDR_MAX); + if (range->va <= MAPLE_RESERVED_RANGE) + range->va = + ALIGN(MAPLE_RESERVED_RANGE, priv->smallest_pgsz); +} + +/* + * Randomly map and unmap ranges that can large physical pages. If a random + * range overlaps with existing ranges then unmap them. This hits all the + * special cases. + */ +static void test_random_map(struct kunit *test) +{ + struct kunit_iommu_priv *priv = test->priv; + struct pt_range upper_range = pt_upper_range(priv->common); + struct pt_range top_range = pt_top_range(priv->common); + struct maple_tree mt; + unsigned int iter; + + mt_init(&mt); + + /* + * Shrink the range so randomization is more likely to have + * intersections + */ + clamp_range(test, &top_range); + clamp_range(test, &upper_range); + + for (iter = 0; iter != 1000; iter++) { + struct pt_range *range = &top_range; + pt_oaddr_t paddr; + pt_vaddr_t start; + pt_vaddr_t end; + int ret; + + if (pt_feature(priv->common, PT_FEAT_SIGN_EXTEND) && + ULONG_MAX >= PT_VADDR_MAX && get_random_u32_inclusive(0, 1)) + range = &upper_range; + + start = get_random_u32_below( + min(U32_MAX, range->last_va - range->va)); + end = get_random_u32_below( + min(U32_MAX, range->last_va - start)); + + start = ALIGN_DOWN(start, priv->smallest_pgsz); + end = ALIGN(end, priv->smallest_pgsz); + start += range->va; + end += start; + if (start < range->va || end > range->last_va + 1 || + start >= end) + continue; + + /* Try overmapping to test the failure handling */ + paddr = oalog2_mod(start, priv->common->max_oasz_lg2); + ret = iommu_map(&priv->domain, start, paddr, end - start, + IOMMU_READ | IOMMU_WRITE, GFP_KERNEL); + if (ret) { + KUNIT_ASSERT_EQ(test, ret, -EADDRINUSE); + unmap_collisions(test, &mt, start, end - 1); + do_map(test, start, paddr, end - start); + } + + KUNIT_ASSERT_NO_ERRNO_FN(test, "mtree_insert_range", + mtree_insert_range(&mt, start, end - 1, + XA_ZERO_ENTRY, + GFP_KERNEL)); + + check_iova(test, start, paddr, end - start); + if (iter % 100) + cond_resched(); + } + + unmap_collisions(test, &mt, 0, PT_VADDR_MAX); + KUNIT_ASSERT_EQ(test, count_valids(test), 0); + + mtree_destroy(&mt); +} + +static struct kunit_case iommu_test_cases[] = { + KUNIT_CASE_FMT(test_increase_level), + KUNIT_CASE_FMT(test_map_simple), + KUNIT_CASE_FMT(test_map_table_to_oa), + KUNIT_CASE_FMT(test_unmap_split), + KUNIT_CASE_FMT(test_random_map), + {}, +}; + +static int pt_kunit_iommu_init(struct kunit *test) +{ + struct kunit_iommu_priv *priv; + int ret; + + priv = kunit_kzalloc(test, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->orig_nr_secondary_pagetable = + global_node_page_state(NR_SECONDARY_PAGETABLE); + ret = pt_kunit_priv_init(test, priv); + if (ret) { + kunit_kfree(test, priv); + return ret; + } + test->priv = priv; + return 0; +} + +static void pt_kunit_iommu_exit(struct kunit *test) +{ + struct kunit_iommu_priv *priv = test->priv; + + if (!test->priv) + return; + + pt_iommu_deinit(priv->iommu); + /* + * Look for memory leaks, assumes kunit is running isolated and nothing + * else is using secondary page tables. + */ + KUNIT_ASSERT_EQ(test, priv->orig_nr_secondary_pagetable, + global_node_page_state(NR_SECONDARY_PAGETABLE)); + kunit_kfree(test, test->priv); +} + +static struct kunit_suite NS(iommu_suite) = { + .name = __stringify(NS(iommu_test)), + .init = pt_kunit_iommu_init, + .exit = pt_kunit_iommu_exit, + .test_cases = iommu_test_cases, +}; +kunit_test_suites(&NS(iommu_suite)); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Kunit for generic page table"); +MODULE_IMPORT_NS("GENERIC_PT_IOMMU");