From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from gabe.freedesktop.org (gabe.freedesktop.org [131.252.210.177]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.lore.kernel.org (Postfix) with ESMTPS id 61DDBFF885A for ; Mon, 4 May 2026 04:37:28 +0000 (UTC) Received: from gabe.freedesktop.org (localhost [127.0.0.1]) by gabe.freedesktop.org (Postfix) with ESMTP id EF24F10E0C8; Mon, 4 May 2026 04:37:27 +0000 (UTC) Authentication-Results: gabe.freedesktop.org; dkim=pass (2048-bit key; unprotected) header.d=intel.com header.i=@intel.com header.b="Ub+gOECo"; dkim-atps=neutral Received: from mgamail.intel.com (mgamail.intel.com [192.198.163.11]) by gabe.freedesktop.org (Postfix) with ESMTPS id 7E11F10E0C8 for ; Mon, 4 May 2026 04:37:26 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=intel.com; i=@intel.com; q=dns/txt; s=Intel; t=1777869447; x=1809405447; h=message-id:date:subject:to:cc:references:from: in-reply-to:mime-version; bh=S4XpNsJQA0QxVZ5sPZgI+Dloo79J2dGHhLiUa7MOv7k=; b=Ub+gOECodhZPpIucvXYksCdzJBcbvWoR83GklUccAFNt5gMyheromSFf I6vTzBesRhxZ7Uvcl5J2AHE/HSO/3ajPnjsDAjgkw72qRTx4gfh7J4kQk XEYxB4Hd2fKWaumKed6aNbk22JeguogVFzyijSvZ086d2WbY27cE6mJfB Qo7A9/ESKL/YXt2ZDAWK+8ZEFanBqXgETpUAFBp/lOfYNRWf0I3g9wMBL THKcoXgCGTsywOvRXjvs1IdXmr5vf+GXuybYNaT9lNGZhHHk5FnxfHq00 QwwjpMgtvdDsdbi0AdSmd7Yuv5/itpJyYovy+w3PB0kmkQnT1Dtr+lFbL A==; X-CSE-ConnectionGUID: y587JhhwQYuPRz9v/wKdMg== X-CSE-MsgGUID: P2lUfeE3SbmnW8Ze7hbNiQ== X-IronPort-AV: E=McAfee;i="6800,10657,11775"; a="89310578" X-IronPort-AV: E=Sophos;i="6.23,214,1770624000"; d="scan'208,217";a="89310578" Received: from orviesa004.jf.intel.com ([10.64.159.144]) by fmvoesa105.fm.intel.com with ESMTP/TLS/ECDHE-RSA-AES256-GCM-SHA384; 03 May 2026 21:37:26 -0700 X-CSE-ConnectionGUID: XJEKXfDvQyu6NZpwZGPU4A== X-CSE-MsgGUID: pZUgunb7QXuy7sEBhTRx7g== X-ExtLoop1: 1 X-IronPort-AV: E=Sophos;i="6.23,214,1770624000"; d="scan'208,217";a="239727493" Received: from fmsmsx901.amr.corp.intel.com ([10.18.126.90]) by orviesa004.jf.intel.com with ESMTP/TLS/ECDHE-RSA-AES256-GCM-SHA384; 03 May 2026 21:37:26 -0700 Received: from FMSMSX903.amr.corp.intel.com (10.18.126.92) by fmsmsx901.amr.corp.intel.com (10.18.126.90) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.2.2562.37; Sun, 3 May 2026 21:37:25 -0700 Received: from fmsedg903.ED.cps.intel.com (10.1.192.145) by FMSMSX903.amr.corp.intel.com (10.18.126.92) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.2.2562.37 via Frontend Transport; Sun, 3 May 2026 21:37:25 -0700 Received: from CH5PR02CU005.outbound.protection.outlook.com (40.107.200.50) by edgegateway.intel.com (192.55.55.83) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.2.2562.37; Sun, 3 May 2026 21:37:25 -0700 ARC-Seal: i=1; a=rsa-sha256; s=arcselector10001; d=microsoft.com; cv=none; b=PaaqjPyxYFcxgyyRBylUCXbxGroUq26LH+F6rPGBRSOBswyqAXaucc2+I6Pf45eiIFXjhuLjf3Q1CR+ejlNF7dVCkwH8z4x+QtH2BWaj+j9z9lnEFwQlrySNcIsMYMtDdeGyX2kIzQkrN2PewXsLMjnuYx0n/aeseT2g4odCunAqNRyI865vfS2vJV5yDRgSsDBsfkptr1S1zHW7jaxgTYmzLKyL8dzu9UNivbdgbVwactgybJsOe0tvLChQHkE+v1rOPHm3ALeAC5AxdJcRovfTxVIqbtts8u35F5YzECIKTW+3W8ZyXH+dIcled4v2csn9QxbxJ57qvvrBF42N6g== 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=FWegFN1ZcfEHoHI3PPW+xQmMYojfmntuXxwMNo7PvBs=; b=FQ1J4pqDQgAAc8CVcNzVeVGB6bl3AfSycmJNMb4rdWohanbss6WZ/OyO5UEobwZfWtx/Z/zeIN1wN+xT936w22OOe4/m5TlK/V3fpJ/IqpaF0N6BtBZzyFYWDtavtNQ6etRU0AvrqFtboKv1/sDcLlsyVuqug+HXPHX6P3UlAA+ZLq+/9Tf3+B0hcE8I4C/CfpvZWCrJuJh8Y/O0+9ld8yQWjBoGAVZ+NpnzM/Ox0KNDIfURS3WgAcsjpW+bKA9q7p8wjZxKABx10JjFTA4o4438ReMXnr56M0z2eVeYUMsh00MpKHfG/x07pbonXNpRZI0C+C+YvuPEZI6et/SpLg== ARC-Authentication-Results: i=1; mx.microsoft.com 1; spf=pass smtp.mailfrom=intel.com; dmarc=pass action=none header.from=intel.com; dkim=pass header.d=intel.com; arc=none Authentication-Results: dkim=none (message not signed) header.d=none;dmarc=none action=none header.from=intel.com; Received: from BN0PR11MB5709.namprd11.prod.outlook.com (2603:10b6:408:148::6) by CY8PR11MB7035.namprd11.prod.outlook.com (2603:10b6:930:51::20) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.20.9870.25; Mon, 4 May 2026 04:37:22 +0000 Received: from BN0PR11MB5709.namprd11.prod.outlook.com ([fe80::ad31:3f30:20b8:26c]) by BN0PR11MB5709.namprd11.prod.outlook.com ([fe80::ad31:3f30:20b8:26c%5]) with mapi id 15.20.9870.023; Mon, 4 May 2026 04:37:22 +0000 Content-Type: multipart/alternative; boundary="------------58pSGEOyDNjKcadcyXr9V0aH" Message-ID: <165a529d-e62f-4064-95dd-38304aaec4d8@intel.com> Date: Mon, 4 May 2026 10:07:14 +0530 User-Agent: Mozilla Thunderbird Subject: Re: [PATCH v2] drm/xe/madvise: Track purgeability with BO-local counters To: Matthew Brost CC: , , , References: <20260430101130.1365878-1-arvind.yadav@intel.com> Content-Language: en-US From: "Yadav, Arvind" In-Reply-To: X-ClientProxiedBy: MA5P287CA0054.INDP287.PROD.OUTLOOK.COM (2603:1096:a01:1d3::19) To BN0PR11MB5709.namprd11.prod.outlook.com (2603:10b6:408:148::6) MIME-Version: 1.0 X-MS-PublicTrafficType: Email X-MS-TrafficTypeDiagnostic: BN0PR11MB5709:EE_|CY8PR11MB7035:EE_ X-MS-Office365-Filtering-Correlation-Id: ddaf94f5-c864-4e6a-c106-08dea996d55e X-MS-Exchange-SenderADCheck: 1 X-MS-Exchange-AntiSpam-Relay: 0 X-Microsoft-Antispam: BCL:0; ARA:13230040|366016|376014|1800799024|13003099007|18002099003|8096899003|22082099003|56012099003; X-Microsoft-Antispam-Message-Info: FGNFK19GEKyyKAZh0KirqvIrkDIdjtk8wTCoccEkyd2qNCEiz+KrZflBxye75YAwARcZhhawUH96Hgp68zhC0sqSszjHW3arI+NIaEVYRVhmiTeRaHB+VikEqX8tR2NtlyLG6aO/kh9TOl49vF9inqrhRDT4rCWWEQrIpHTTS8292jZSsSfkTclxDjlx1sgNLqaHhikbfRRF1WzOER1E/ZvN9gDCD4USh1i9aStd6QhKKXcnd+tRGqY2Rp/mgCiFM1hCnFe03GU5WCEP9qSs03tZa7xL6roFMW68VqFbqaHmalUozaLPk+IUdiHN5pQhHBfjepJZ+iU4TJ1tXNspXBZtwN+eQKt7eOxNDvCDRGepABYDm9NyacqHHFLlnJtvMA3Svter02qDL3reTpZrkqTbW8UznCDK3wJ2yIqUf1Qncyz19WM8X76ZhD6iY34JapCr+r1X63jFuyk22l8Kl/U7XPAz8mRDEtn7Rc5HgUFKIywXhjA1XaqFI0p6kvIY5KDXdeacoA0tkATP60tNm7Uo49ykI1aEjku8ntsF6vKKcWtuVZ6n0U++IBLxN1ZP5iG2R6EmJSPmcIEH3LRp2wZsmXjiLkcKMu80zLY8m8NTTe7BWqqJxF4+rDJB7KRbjg276JO7m/SMTmZpxrIS0w== X-Forefront-Antispam-Report: CIP:255.255.255.255; CTRY:; LANG:en; SCL:1; SRV:; IPV:NLI; SFV:NSPM; H:BN0PR11MB5709.namprd11.prod.outlook.com; PTR:; CAT:NONE; SFS:(13230040)(366016)(376014)(1800799024)(13003099007)(18002099003)(8096899003)(22082099003)(56012099003); DIR:OUT; SFP:1101; X-MS-Exchange-AntiSpam-MessageData-ChunkCount: 1 X-MS-Exchange-AntiSpam-MessageData-0: =?utf-8?B?ZU1JM3NaS3BzZTdhbkJ2bzFkNWszUEhBVURJeXRSQmNzM3dlL3UwWnVvQmRs?= =?utf-8?B?cUtHYlE5WWtIbWZtdm1NTjNXOG1qalc3cXRuU1N1ZnJBdE4xOVV1bEsyd1Za?= =?utf-8?B?UEZIK3o0L3orTkRKUTZqZ3IyZ001ejE1OE9PQWdlYUdpa3l2c1Vva2kvYVV6?= =?utf-8?B?OEpSYitKZnozZGJ5ajdlcElERExSZHZnajRyMkhFc21TQldtYitOWENUSGFk?= =?utf-8?B?TkovQnJreDZvdU5MYUhUQUFBdk1qaTljbE01d0VZZzZtZVI0ejNtWG5yNERD?= =?utf-8?B?MjgxdVZ6UngyRFdPWUVEQ1lhU0tLYW1kN1ordk1vTXV1V2FIS2tEQVpMa2U3?= =?utf-8?B?SlpXVWNMSjFtOUNVeVRhZnR3WkxQSm0zWVBLdXBVVllyUW9rbldES2NzRzJI?= =?utf-8?B?bEtXNDNGdGdKWWZmcUdGVTVvOTlnNWI2elJ0SWJiR1BQbG8rOG5YbG9vb0RY?= =?utf-8?B?KzR3MjM5bHB4M1ZkbFpLeEJGTmY1L1JPYk5EYk5Ra2NPTy9OUXhPZjMzaC84?= =?utf-8?B?TkFIMTFiWFlRVHBJczU2MlZYVG9IMzg2c3k0aFlJTEkxaEJSTGo5aGE2ZHN4?= =?utf-8?B?SURsSjU5dW1YZERIYURleXN5NjNTTVpnMVExS3kyeG42QXcvMVJGdVUrWWxE?= =?utf-8?B?eGZKNmg3QVpRdmdwS1FOVWxPZDluR0tzSWh6VXRWNGRWNHRIM1cxR0ZYNnRI?= =?utf-8?B?enRwYm1RV0UreFI3Y1Y5S2VtZUtWcWhIR3MyUFc3aGUzYnJBT2xaTDVaVEJQ?= =?utf-8?B?UXJOSm9nK29xZEVLOS81WWtsOFM5S1lpa2ltQ3ZVNS9SOVlOQ2lYSldrR1BF?= =?utf-8?B?WGxBbFdnSm5OMTVHd25QK3hybEUwMDJjU29iVjBUbUxPaE50amJxdDIraWVk?= =?utf-8?B?S2FqYnBrR3UweUoxRmtyczB1cVVieWZucDhuREdtdVNyZmJEdkhoYXNpNTZx?= =?utf-8?B?WTVzMmRPUHJmOUdQY1lqZ0p6QlpPWVhCcWxxdGVhVzRtd2R5QU00Q1VJZ3Ux?= =?utf-8?B?d21maWpYV21URytYS3k2SUIvTm1PYjFLZTZqZE05QkJKTFl4Q2xCT3RLWjdS?= =?utf-8?B?V2ZvK2QxRlhDSVBZTU5oSlFPMWhUMXV3Ni80VVZTTnhkY2JPeVVuam5YUFZW?= =?utf-8?B?ZEhNSkFOVStYWkhlbUZxZWl5ZU1Qblk1ZXhGb2kwb2FGUDRRRUZnN2d3MFN0?= =?utf-8?B?NDgzbzJERU1RUktSMmFzeFVMcStVbE5HVUtkcjBSQnpWSDRnRzRqSHdZV2R3?= =?utf-8?B?ZzF4L2VGT2dzQWEzSXR0REw0RUR0bXVJcmNaRVlNNkd0dkpkVWI1UGhlZXkv?= =?utf-8?B?TDJORWI5dFliTXd1YUtKcTdTKytqY0dYZEtVSkp4TTRsSFlqQXNOTmtEdS9F?= =?utf-8?B?TG96bENpcEp0NjNjZlFUTGo1ZmVrV3RjK0Nyb2tGTEVabmpITmNabnp5Qk4z?= =?utf-8?B?djNlVUlsbmZCRElpQWZUNlZ3aEJUUnBTZFFRUFo5TWVuYzZHNjRHSmoxcUky?= =?utf-8?B?Mnp4L0pidUl6WlBLSEdGRlBnNjFaaExQOGgrN2VJaVMyTmFENTMxdXRHVHZt?= =?utf-8?B?eHBsRjR6UTFuZHpKQzJscjZxUVM2TUhDOVJEODJCeGQreEZJTlVqQ1ZYLzZk?= =?utf-8?B?cFk3UFJMbTQwMTh0Y1ZuTW4xNnJCN0tQY21aNUc5M2pNd0o2UkY1bVdmQVVq?= =?utf-8?B?TkYxUHdrTllVMjVWZFppVmh6ZlJnR3l2UUFPckpvSGJhNkQ3RmhLUVRBUjYw?= =?utf-8?B?ODlxNmFHbEZCR3NUdlkwaGs1aHhCQnUwQUQzRHNoOHBkQ240MzZYS0xZRzJY?= =?utf-8?B?bkhJNEpDYWptVlJnQ1V5ZjlrR1RGU2JSVUY0ZVdoLzU3VUVnNGtLWVVmL0Z3?= =?utf-8?B?N0RuSjZBcEF2WXZzVXZlSjdHNGVZMkFVNVZCd3NVMHMyeFZURnZkbC8zeVVH?= =?utf-8?B?MTZuUFRjd0RBOUZadUp2QXQyTFFmODROZmdEc3p3cG1jS245SHNYRkkrNWRD?= =?utf-8?B?VEZaNFI4bWIzcktFWSs2MFJlbWg4SmE2RXZPcVZDZHN3OGIxUXFXSnpNellp?= =?utf-8?B?TlFJeTNlYUJXRFNaZ3hwTEFwRFRyanJOb3E4cGlwWCtTczFVNm9qem9CajAy?= =?utf-8?B?RXZCREJYZDdLcWpGaEhjSmhaZUxsbWNiNGF3bFA2QWdoT25GQ3ZUQTZZaEVJ?= =?utf-8?B?STZONy9uR2JJQzVDbGtqbGJ3SVdybnRGU0lHT0pJbjdIQ1pIeDFEZmxKLzR0?= =?utf-8?B?clpnZGc4N2d0NnFSWDBPaTgyVG5JWTgzOWsxOE92WEttcFpIb1d3OW1kcWZV?= =?utf-8?B?MDMzZ3Y5R3AvT2Z1VFZpbEYvenFYbUgwWWR0YkdBUHFqdWNGS2xIZz09?= X-Exchange-RoutingPolicyChecked: Vj1TRVMksITB8RCr3yRImRtQp0JQab1X1XmT/Hadglc44OXZrQiHOc1Tob4enRv3ZVY0d3lwX4p832RLZpfSak+3OuOv++MZ9k4Vh8uN6hHNR8tK7wZFFAw7Y46VIIuZO5BZYK7qlD14lXJ0eAEMSZopTohmgAME0i4y5rlVe5QULa+PAUta4T7Wa/cgFSikPHAYgdyRy9lvCJ4MDhSk1Fngvr8ZfBreGcTwFa4MOnZx/2FLGDGVtnnxqgnHlRlUif+1H3U1kFZ7puFYShOvYmksjHKi0EYfVmDS3ynS9OXthLKYeJutvIPnhhZrB8ZFjo2pwwGx+lk2Ui+I+iCtcQ== X-MS-Exchange-CrossTenant-Network-Message-Id: ddaf94f5-c864-4e6a-c106-08dea996d55e X-MS-Exchange-CrossTenant-AuthSource: BN0PR11MB5709.namprd11.prod.outlook.com X-MS-Exchange-CrossTenant-AuthAs: Internal X-MS-Exchange-CrossTenant-OriginalArrivalTime: 04 May 2026 04:37:22.6747 (UTC) X-MS-Exchange-CrossTenant-FromEntityHeader: Hosted X-MS-Exchange-CrossTenant-Id: 46c98d88-e344-4ed4-8496-4ed7712e255d X-MS-Exchange-CrossTenant-MailboxType: HOSTED X-MS-Exchange-CrossTenant-UserPrincipalName: qCidmghBlaFVo1ubHlTuBPKCN6bytseFKKEnFsATGoiftILIQkZut9EhsuTl3pQDF4NeMqMQRWhZmyXBVr0dOQ== X-MS-Exchange-Transport-CrossTenantHeadersStamped: CY8PR11MB7035 X-OriginatorOrg: intel.com X-BeenThere: intel-xe@lists.freedesktop.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: Intel Xe graphics driver List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: intel-xe-bounces@lists.freedesktop.org Sender: "Intel-xe" --------------58pSGEOyDNjKcadcyXr9V0aH Content-Type: text/plain; charset="UTF-8"; format=flowed Content-Transfer-Encoding: 8bit On 01-05-2026 23:38, Matthew Brost wrote: > On Thu, Apr 30, 2026 at 12:36:41PM -0700, Matthew Brost wrote: >> On Thu, Apr 30, 2026 at 03:41:30PM +0530, Arvind Yadav wrote: >>> xe_bo_recompute_purgeable_state() walks all VMAs of a BO to determine >>> whether the BO can be made purgeable. This makes VMA create/destroy and >>> madvise updates O(n) in the number of mappings. >>> >>> Replace the walk with BO-local counters protected by the BO dma-resv >>> lock: >>> >>> - vma_count tracks the number of VMAs mapping the BO. >>> - willneed_count tracks active WILLNEED holders, including WILLNEED >>> VMAs and active dma-buf exports for non-imported BOs. >>> >>> A DONTNEED BO is promoted back to WILLNEED on a 0->1 transition of >>> willneed_count. A BO is demoted to DONTNEED on a 1->0 transition only >>> when it still has VMAs, preserving the previous behaviour where a BO >>> with no mappings keeps its current madvise state. >>> >>> PURGED remains terminal, preserving the existing "once purged, always >>> purged" rule. >>> >>> v2: >>> - Use early return for imported BOs in all four helpers to avoid >>> nesting (Matt B). >>> - Group purgeability state into a purgeable sub-struct on struct >>> xe_bo (Matt B). >>> - Reword xe_bo_willneed_put_locked() kernel-doc to explain that a 1->0 >>> transition means all remaining active VMAs are DONTNEED (Matt B). >>> >>> Suggested-by: Thomas Hellström >>> Cc: Matthew Brost >> Reviewed-by: Matthew Brost >> > My bad - sashiko flagged a valid issue here [1]. > > So I xe_vma_create need to flags.check_purged check that is currently in > vma_lock_and_validate(). We the dma-resv locks in xe_vma_create so > moving the check to xe_vma_create should be safe. > > More below. > > [1]https://sashiko.dev/#/patchset/20260430101130.1365878-1-arvind.yadav%40intel.com Yes, Good catch. we should add the check in xe_vma_create(), but it also needs to remain in vma_lock_and_validate() because PREFETCH does not go through xe_vma_create(). drm_gpuvm_prefetch_ops_create() generates operations that reference existing VMAs, and the PREFETCH path in op_lock_and_prep() relies solely on vma_lock_and_validate(). > >>> Cc: Thomas Hellström >>> Cc: Himal Prasad Ghimiray >>> Signed-off-by: Arvind Yadav >>> --- >>> drivers/gpu/drm/xe/xe_bo.c | 6 +- >>> drivers/gpu/drm/xe/xe_bo.h | 88 +++++++++++++++- >>> drivers/gpu/drm/xe/xe_bo_types.h | 27 ++++- >>> drivers/gpu/drm/xe/xe_dma_buf.c | 28 ++++- >>> drivers/gpu/drm/xe/xe_vm.c | 9 +- >>> drivers/gpu/drm/xe/xe_vm_madvise.c | 162 ++--------------------------- >>> drivers/gpu/drm/xe/xe_vm_madvise.h | 2 - >>> 7 files changed, 155 insertions(+), 167 deletions(-) >>> >>> diff --git a/drivers/gpu/drm/xe/xe_bo.c b/drivers/gpu/drm/xe/xe_bo.c >>> index 5ce60d161e09..eaa3a4ee9111 100644 >>> --- a/drivers/gpu/drm/xe/xe_bo.c >>> +++ b/drivers/gpu/drm/xe/xe_bo.c >>> @@ -884,10 +884,10 @@ void xe_bo_set_purgeable_state(struct xe_bo *bo, >>> new_state == XE_MADV_PURGEABLE_PURGED); >>> >>> /* Once purged, always purged - cannot transition out */ >>> - xe_assert(xe, !(bo->madv_purgeable == XE_MADV_PURGEABLE_PURGED && >>> + xe_assert(xe, !(bo->purgeable.state == XE_MADV_PURGEABLE_PURGED && >>> new_state != XE_MADV_PURGEABLE_PURGED)); >>> >>> - bo->madv_purgeable = new_state; >>> + bo->purgeable.state = new_state; >>> xe_bo_set_purgeable_shrinker(bo, new_state); >>> } >>> >>> @@ -2355,7 +2355,7 @@ struct xe_bo *xe_bo_init_locked(struct xe_device *xe, struct xe_bo *bo, >>> INIT_LIST_HEAD(&bo->vram_userfault_link); >>> >>> /* Initialize purge advisory state */ >>> - bo->madv_purgeable = XE_MADV_PURGEABLE_WILLNEED; >>> + bo->purgeable.state = XE_MADV_PURGEABLE_WILLNEED; >>> >>> drm_gem_private_object_init(&xe->drm, &bo->ttm.base, size); >>> >>> diff --git a/drivers/gpu/drm/xe/xe_bo.h b/drivers/gpu/drm/xe/xe_bo.h >>> index 68dea7d25a6b..6340317f7d2e 100644 >>> --- a/drivers/gpu/drm/xe/xe_bo.h >>> +++ b/drivers/gpu/drm/xe/xe_bo.h >>> @@ -251,7 +251,7 @@ static inline bool xe_bo_is_protected(const struct xe_bo *bo) >>> static inline bool xe_bo_is_purged(struct xe_bo *bo) >>> { >>> xe_bo_assert_held(bo); >>> - return bo->madv_purgeable == XE_MADV_PURGEABLE_PURGED; >>> + return bo->purgeable.state == XE_MADV_PURGEABLE_PURGED; >>> } >>> >>> /** >>> @@ -268,11 +268,95 @@ static inline bool xe_bo_is_purged(struct xe_bo *bo) >>> static inline bool xe_bo_madv_is_dontneed(struct xe_bo *bo) >>> { >>> xe_bo_assert_held(bo); >>> - return bo->madv_purgeable == XE_MADV_PURGEABLE_DONTNEED; >>> + return bo->purgeable.state == XE_MADV_PURGEABLE_DONTNEED; >>> } >>> >>> void xe_bo_set_purgeable_state(struct xe_bo *bo, enum xe_madv_purgeable_state new_state); >>> >>> +/** >>> + * xe_bo_willneed_get_locked() - Acquire a WILLNEED holder on a BO >>> + * @bo: Buffer object >>> + * >>> + * Increments willneed_count and, on a 0->1 transition, promotes the BO >>> + * from DONTNEED to WILLNEED. PURGED is terminal and is never modified. >>> + * >>> + * Caller must hold the BO's dma-resv lock. >>> + */ >>> +static inline void xe_bo_willneed_get_locked(struct xe_bo *bo) >>> +{ >>> + xe_bo_assert_held(bo); >>> + >>> + /* Imported BOs are owned externally; do not track purgeability. */ >>> + if (drm_gem_is_imported(&bo->ttm.base)) >>> + return; >>> + >>> + if (bo->purgeable.willneed_count++ == 0 && xe_bo_madv_is_dontneed(bo)) >>> + xe_bo_set_purgeable_state(bo, XE_MADV_PURGEABLE_WILLNEED); >>> +} >>> + >>> +/** >>> + * xe_bo_willneed_put_locked() - Release a WILLNEED holder on a BO >>> + * @bo: Buffer object >>> + * >>> + * Decrements willneed_count and, on a 1->0 transition, marks the BO >>> + * DONTNEED only if it still has VMAs (implying all active VMAs are >>> + * DONTNEED). If the last VMA is being removed, preserve the current BO >>> + * state to match the previous VMA-walk semantics. >>> + * >>> + * PURGED is terminal and the BO state is never modified. >>> + * >>> + * Caller must hold the BO's dma-resv lock. >>> + */ >>> +static inline void xe_bo_willneed_put_locked(struct xe_bo *bo) >>> +{ >>> + xe_bo_assert_held(bo); >>> + >>> + if (drm_gem_is_imported(&bo->ttm.base)) >>> + return; >>> + >>> + xe_assert(xe_bo_device(bo), bo->purgeable.willneed_count > 0); >>> + if (--bo->purgeable.willneed_count == 0 && bo->purgeable.vma_count > 0 && >>> + !xe_bo_is_purged(bo)) >>> + xe_bo_set_purgeable_state(bo, XE_MADV_PURGEABLE_DONTNEED); >>> +} >>> + >>> +/** >>> + * xe_bo_vma_count_inc_locked() - Account a new VMA on a BO >>> + * @bo: Buffer object >>> + * >>> + * Increments vma_count. >>> + * >>> + * Caller must hold the BO's dma-resv lock. >>> + */ >>> +static inline void xe_bo_vma_count_inc_locked(struct xe_bo *bo) >>> +{ >>> + xe_bo_assert_held(bo); >>> + >>> + if (drm_gem_is_imported(&bo->ttm.base)) >>> + return; >>> + >>> + bo->purgeable.vma_count++; >>> +} >>> + >>> +/** >>> + * xe_bo_vma_count_dec_locked() - Account a VMA removal on a BO >>> + * @bo: Buffer object >>> + * >>> + * Decrements vma_count. >>> + * >>> + * Caller must hold the BO's dma-resv lock. >>> + */ >>> +static inline void xe_bo_vma_count_dec_locked(struct xe_bo *bo) >>> +{ >>> + xe_bo_assert_held(bo); >>> + >>> + if (drm_gem_is_imported(&bo->ttm.base)) >>> + return; >>> + >>> + xe_assert(xe_bo_device(bo), bo->purgeable.vma_count > 0); >>> + bo->purgeable.vma_count--; >>> +} >>> + >>> static inline void xe_bo_unpin_map_no_vm(struct xe_bo *bo) >>> { >>> if (likely(bo)) { >>> diff --git a/drivers/gpu/drm/xe/xe_bo_types.h b/drivers/gpu/drm/xe/xe_bo_types.h >>> index 9c199badd9b2..6756d7820aca 100644 >>> --- a/drivers/gpu/drm/xe/xe_bo_types.h >>> +++ b/drivers/gpu/drm/xe/xe_bo_types.h >>> @@ -111,10 +111,31 @@ struct xe_bo { >>> u64 min_align; >>> >>> /** >>> - * @madv_purgeable: user space advise on BO purgeability, protected >>> - * by BO's dma-resv lock. >>> + * @purgeable: Purgeability state and accounting. >>> + * >>> + * All fields are protected by the BO's dma-resv lock. >>> */ >>> - u32 madv_purgeable; >>> + struct { >>> + /** >>> + * @purgeable.state: BO purgeability state (WILLNEED/DONTNEED/PURGED). >>> + */ >>> + u32 state; >>> + >>> + /** >>> + * @purgeable.vma_count: Number of VMAs currently mapping this BO. >>> + */ >>> + u32 vma_count; >>> + >>> + /** >>> + * @purgeable.willneed_count: Number of active WILLNEED holders. >>> + * >>> + * Counts WILLNEED VMAs plus active dma-buf exports for >>> + * non-imported BOs. The BO flips to DONTNEED on a 1->0 >>> + * transition only when VMAs still exist; if the last VMA is >>> + * removed, the previous BO state is preserved. >>> + */ >>> + u32 willneed_count; >>> + } purgeable; >>> }; >>> >>> #endif >>> diff --git a/drivers/gpu/drm/xe/xe_dma_buf.c b/drivers/gpu/drm/xe/xe_dma_buf.c >>> index b9828da15897..855d32ba314d 100644 >>> --- a/drivers/gpu/drm/xe/xe_dma_buf.c >>> +++ b/drivers/gpu/drm/xe/xe_dma_buf.c >>> @@ -193,6 +193,18 @@ static int xe_dma_buf_begin_cpu_access(struct dma_buf *dma_buf, >>> return 0; >>> } >>> >>> +static void xe_dma_buf_release(struct dma_buf *dmabuf) >>> +{ >>> + struct drm_gem_object *obj = dmabuf->priv; >>> + struct xe_bo *bo = gem_to_xe_bo(obj); >>> + >>> + xe_bo_lock(bo, false); >>> + xe_bo_willneed_put_locked(bo); >>> + xe_bo_unlock(bo); >>> + >>> + drm_gem_dmabuf_release(dmabuf); >>> +} >>> + >>> static const struct dma_buf_ops xe_dmabuf_ops = { >>> .attach = xe_dma_buf_attach, >>> .detach = xe_dma_buf_detach, >>> @@ -200,7 +212,7 @@ static const struct dma_buf_ops xe_dmabuf_ops = { >>> .unpin = xe_dma_buf_unpin, >>> .map_dma_buf = xe_dma_buf_map, >>> .unmap_dma_buf = xe_dma_buf_unmap, >>> - .release = drm_gem_dmabuf_release, >>> + .release = xe_dma_buf_release, >>> .begin_cpu_access = xe_dma_buf_begin_cpu_access, >>> .mmap = drm_gem_dmabuf_mmap, >>> .vmap = drm_gem_dmabuf_vmap, >>> @@ -241,18 +253,26 @@ struct dma_buf *xe_gem_prime_export(struct drm_gem_object *obj, int flags) >>> ret = -EINVAL; >>> goto out_unlock; >>> } >>> + >>> + xe_bo_willneed_get_locked(bo); >>> xe_bo_unlock(bo); >>> >>> ret = ttm_bo_setup_export(&bo->ttm, &ctx); >>> if (ret) >>> - return ERR_PTR(ret); >>> + goto out_put; >>> >>> buf = drm_gem_prime_export(obj, flags); >>> - if (!IS_ERR(buf)) >>> - buf->ops = &xe_dmabuf_ops; >>> + if (IS_ERR(buf)) { >>> + ret = PTR_ERR(buf); >>> + goto out_put; >>> + } >>> >>> + buf->ops = &xe_dmabuf_ops; >>> return buf; >>> >>> +out_put: >>> + xe_bo_lock(bo, false); >>> + xe_bo_willneed_put_locked(bo); >>> out_unlock: >>> xe_bo_unlock(bo); >>> return ERR_PTR(ret); >>> diff --git a/drivers/gpu/drm/xe/xe_vm.c b/drivers/gpu/drm/xe/xe_vm.c >>> index c3836f6eab35..12457173ba85 100644 >>> --- a/drivers/gpu/drm/xe/xe_vm.c >>> +++ b/drivers/gpu/drm/xe/xe_vm.c >>> @@ -1131,6 +1131,10 @@ static struct xe_vma *xe_vma_create(struct xe_vm *vm, >>> vma->gpuva.gem.offset = bo_offset_or_userptr; >>> drm_gpuva_link(&vma->gpuva, vm_bo); >>> drm_gpuvm_bo_put(vm_bo); >>> + >>> + xe_bo_vma_count_inc_locked(bo); >>> + if (vma->attr.purgeable_state == XE_MADV_PURGEABLE_WILLNEED) >>> + xe_bo_willneed_get_locked(bo); > So at very top of this function I think: > > if (bo && attr->purgeable_state == XE_MADV_PURGEABLE_WILLNEED) { > if (xe_bo_madv_is_dontneed(bo)) > return ERR_PTR(-EBUSY); /* BO marked purgeable */ > else if (xe_bo_is_purged(bo)) > return ERR_PTR(-EINVAL); /* BO already purged */ > } > > Then delete the check in vma_lock_and_validate. I think check in > vma_lock_and_validate is actually wrong for rebinds too - e.g., it is > prefectly to do a partial unbind a dontneed or purged BO and the > existing check I believe would reject this. Agreed. I will dropped .check_purged from MAP and REMAP prev/next in vm_bind_ioctl_op_lock_and_prep() for v3. The check now lives in xe_vma_create() and only triggers when attr->purgeable_state == WILLNEED, so partial unbind/rebind on DONTNEED or PURGED BOs works as expected. and PREFETCH keeps .check_purged as it is a separate check for existing VMAs with no backing store to migrate. > > We should put together a test for for this too. > > addr = bind(2M); > madvise(addr, DONTNEED); > unbind(addr, 1M); > > Assuming the current code fails in this test case and new code works, > I'd suggest making this patch a fixes too. Noted. I will add a test case to cover this and include the appropriate fix tag Thanks, Arvind > > Matt > >>> } else /* userptr or null */ { >>> if (!is_null && !is_cpu_addr_mirror) { >>> struct xe_userptr_vma *uvma = to_userptr_vma(vma); >>> @@ -1208,7 +1212,10 @@ static void xe_vma_destroy(struct xe_vma *vma, struct dma_fence *fence) >>> xe_bo_assert_held(bo); >>> >>> drm_gpuva_unlink(&vma->gpuva); >>> - xe_bo_recompute_purgeable_state(bo); >>> + >>> + xe_bo_vma_count_dec_locked(bo); >>> + if (vma->attr.purgeable_state == XE_MADV_PURGEABLE_WILLNEED) >>> + xe_bo_willneed_put_locked(bo); >>> } >>> >>> xe_vm_assert_held(vm); >>> diff --git a/drivers/gpu/drm/xe/xe_vm_madvise.c b/drivers/gpu/drm/xe/xe_vm_madvise.c >>> index c78906dea82b..c4fb29004195 100644 >>> --- a/drivers/gpu/drm/xe/xe_vm_madvise.c >>> +++ b/drivers/gpu/drm/xe/xe_vm_madvise.c >>> @@ -185,147 +185,6 @@ static void madvise_pat_index(struct xe_device *xe, struct xe_vm *vm, >>> } >>> } >>> >>> -/** >>> - * xe_bo_is_dmabuf_shared() - Check if BO is shared via dma-buf >>> - * @bo: Buffer object >>> - * >>> - * Prevent marking imported or exported dma-bufs as purgeable. >>> - * For imported BOs, Xe doesn't own the backing store and cannot >>> - * safely reclaim pages (exporter or other devices may still be >>> - * using them). For exported BOs, external devices may have active >>> - * mappings we cannot track. >>> - * >>> - * Return: true if BO is imported or exported, false otherwise >>> - */ >>> -static bool xe_bo_is_dmabuf_shared(struct xe_bo *bo) >>> -{ >>> - struct drm_gem_object *obj = &bo->ttm.base; >>> - >>> - /* Imported: exporter owns backing store */ >>> - if (drm_gem_is_imported(obj)) >>> - return true; >>> - >>> - /* Exported: external devices may be accessing */ >>> - if (obj->dma_buf) >>> - return true; >>> - >>> - return false; >>> -} >>> - >>> -/** >>> - * enum xe_bo_vmas_purge_state - VMA purgeable state aggregation >>> - * >>> - * Distinguishes whether a BO's VMAs are all DONTNEED, have at least >>> - * one WILLNEED, or have no VMAs at all. >>> - * >>> - * Enum values align with XE_MADV_PURGEABLE_* states for consistency. >>> - */ >>> -enum xe_bo_vmas_purge_state { >>> - /** @XE_BO_VMAS_STATE_WILLNEED: At least one VMA is WILLNEED */ >>> - XE_BO_VMAS_STATE_WILLNEED = 0, >>> - /** @XE_BO_VMAS_STATE_DONTNEED: All VMAs are DONTNEED */ >>> - XE_BO_VMAS_STATE_DONTNEED = 1, >>> - /** @XE_BO_VMAS_STATE_NO_VMAS: BO has no VMAs */ >>> - XE_BO_VMAS_STATE_NO_VMAS = 2, >>> -}; >>> - >>> -/* >>> - * xe_bo_recompute_purgeable_state() casts between xe_bo_vmas_purge_state and >>> - * xe_madv_purgeable_state. Enforce that WILLNEED=0 and DONTNEED=1 match across >>> - * both enums so the single-line cast is always valid. >>> - */ >>> -static_assert(XE_BO_VMAS_STATE_WILLNEED == (int)XE_MADV_PURGEABLE_WILLNEED, >>> - "VMA purge state WILLNEED must equal madv purgeable WILLNEED"); >>> -static_assert(XE_BO_VMAS_STATE_DONTNEED == (int)XE_MADV_PURGEABLE_DONTNEED, >>> - "VMA purge state DONTNEED must equal madv purgeable DONTNEED"); >>> - >>> -/** >>> - * xe_bo_all_vmas_dontneed() - Determine BO VMA purgeable state >>> - * @bo: Buffer object >>> - * >>> - * Check all VMAs across all VMs to determine aggregate purgeable state. >>> - * Shared BOs require unanimous DONTNEED state from all mappings. >>> - * >>> - * Caller must hold BO dma-resv lock. >>> - * >>> - * Return: XE_BO_VMAS_STATE_DONTNEED if all VMAs are DONTNEED, >>> - * XE_BO_VMAS_STATE_WILLNEED if at least one VMA is not DONTNEED, >>> - * XE_BO_VMAS_STATE_NO_VMAS if BO has no VMAs >>> - */ >>> -static enum xe_bo_vmas_purge_state xe_bo_all_vmas_dontneed(struct xe_bo *bo) >>> -{ >>> - struct drm_gpuvm_bo *vm_bo; >>> - struct drm_gpuva *gpuva; >>> - struct drm_gem_object *obj = &bo->ttm.base; >>> - bool has_vmas = false; >>> - >>> - xe_bo_assert_held(bo); >>> - >>> - /* Shared dma-bufs cannot be purgeable */ >>> - if (xe_bo_is_dmabuf_shared(bo)) >>> - return XE_BO_VMAS_STATE_WILLNEED; >>> - >>> - drm_gem_for_each_gpuvm_bo(vm_bo, obj) { >>> - drm_gpuvm_bo_for_each_va(gpuva, vm_bo) { >>> - struct xe_vma *vma = gpuva_to_vma(gpuva); >>> - >>> - has_vmas = true; >>> - >>> - /* Any non-DONTNEED VMA prevents purging */ >>> - if (vma->attr.purgeable_state != XE_MADV_PURGEABLE_DONTNEED) >>> - return XE_BO_VMAS_STATE_WILLNEED; >>> - } >>> - } >>> - >>> - /* >>> - * No VMAs => preserve existing BO purgeable state. >>> - * Avoids incorrectly flipping DONTNEED -> WILLNEED when last VMA unmapped. >>> - */ >>> - if (!has_vmas) >>> - return XE_BO_VMAS_STATE_NO_VMAS; >>> - >>> - return XE_BO_VMAS_STATE_DONTNEED; >>> -} >>> - >>> -/** >>> - * xe_bo_recompute_purgeable_state() - Recompute BO purgeable state from VMAs >>> - * @bo: Buffer object >>> - * >>> - * Walk all VMAs to determine if BO should be purgeable or not. >>> - * Shared BOs require unanimous DONTNEED state from all mappings. >>> - * If the BO has no VMAs the existing state is preserved. >>> - * >>> - * Locking: Caller must hold BO dma-resv lock. When iterating GPUVM lists, >>> - * VM lock must also be held (write) to prevent concurrent VMA modifications. >>> - * This is satisfied at both call sites: >>> - * - xe_vma_destroy(): holds vm->lock write >>> - * - madvise_purgeable(): holds vm->lock write (from madvise ioctl path) >>> - * >>> - * Return: nothing >>> - */ >>> -void xe_bo_recompute_purgeable_state(struct xe_bo *bo) >>> -{ >>> - enum xe_bo_vmas_purge_state vma_state; >>> - >>> - if (!bo) >>> - return; >>> - >>> - xe_bo_assert_held(bo); >>> - >>> - /* >>> - * Once purged, always purged. Cannot transition back to WILLNEED. >>> - * This matches i915 semantics where purged BOs are permanently invalid. >>> - */ >>> - if (bo->madv_purgeable == XE_MADV_PURGEABLE_PURGED) >>> - return; >>> - >>> - vma_state = xe_bo_all_vmas_dontneed(bo); >>> - >>> - if (vma_state != (enum xe_bo_vmas_purge_state)bo->madv_purgeable && >>> - vma_state != XE_BO_VMAS_STATE_NO_VMAS) >>> - xe_bo_set_purgeable_state(bo, (enum xe_madv_purgeable_state)vma_state); >>> -} >>> - >>> /** >>> * madvise_purgeable - Handle purgeable buffer object advice >>> * @xe: XE device >>> @@ -359,12 +218,6 @@ static void madvise_purgeable(struct xe_device *xe, struct xe_vm *vm, >>> /* BO must be locked before modifying madv state */ >>> xe_bo_assert_held(bo); >>> >>> - /* Skip shared dma-bufs - no PTEs to zap */ >>> - if (xe_bo_is_dmabuf_shared(bo)) { >>> - vmas[i]->skip_invalidation = true; >>> - continue; >>> - } >>> - >>> /* >>> * Once purged, always purged. Cannot transition back to WILLNEED. >>> * This matches i915 semantics where purged BOs are permanently invalid. >>> @@ -377,13 +230,14 @@ static void madvise_purgeable(struct xe_device *xe, struct xe_vm *vm, >>> >>> switch (op->purge_state_val.val) { >>> case DRM_XE_VMA_PURGEABLE_STATE_WILLNEED: >>> - vmas[i]->attr.purgeable_state = XE_MADV_PURGEABLE_WILLNEED; >>> vmas[i]->skip_invalidation = true; >>> - >>> - xe_bo_recompute_purgeable_state(bo); >>> + /* Only act on a real DONTNEED -> WILLNEED transition. */ >>> + if (vmas[i]->attr.purgeable_state == XE_MADV_PURGEABLE_DONTNEED) { >>> + vmas[i]->attr.purgeable_state = XE_MADV_PURGEABLE_WILLNEED; >>> + xe_bo_willneed_get_locked(bo); >>> + } >>> break; >>> case DRM_XE_VMA_PURGEABLE_STATE_DONTNEED: >>> - vmas[i]->attr.purgeable_state = XE_MADV_PURGEABLE_DONTNEED; >>> /* >>> * Don't zap PTEs at DONTNEED time -- pages are still >>> * alive. The zap happens in xe_bo_move_notify() right >>> @@ -391,7 +245,11 @@ static void madvise_purgeable(struct xe_device *xe, struct xe_vm *vm, >>> */ >>> vmas[i]->skip_invalidation = true; >>> >>> - xe_bo_recompute_purgeable_state(bo); >>> + /* Only act on a real WILLNEED -> DONTNEED transition. */ >>> + if (vmas[i]->attr.purgeable_state == XE_MADV_PURGEABLE_WILLNEED) { >>> + vmas[i]->attr.purgeable_state = XE_MADV_PURGEABLE_DONTNEED; >>> + xe_bo_willneed_put_locked(bo); >>> + } >>> break; >>> default: >>> /* Should never hit - values validated in madvise_args_are_sane() */ >>> diff --git a/drivers/gpu/drm/xe/xe_vm_madvise.h b/drivers/gpu/drm/xe/xe_vm_madvise.h >>> index 39acd2689ca0..a3078f634c7e 100644 >>> --- a/drivers/gpu/drm/xe/xe_vm_madvise.h >>> +++ b/drivers/gpu/drm/xe/xe_vm_madvise.h >>> @@ -13,6 +13,4 @@ struct xe_bo; >>> int xe_vm_madvise_ioctl(struct drm_device *dev, void *data, >>> struct drm_file *file); >>> >>> -void xe_bo_recompute_purgeable_state(struct xe_bo *bo); >>> - >>> #endif >>> -- >>> 2.43.0 >>> --------------58pSGEOyDNjKcadcyXr9V0aH Content-Type: text/html; charset="UTF-8" Content-Transfer-Encoding: 8bit


On 01-05-2026 23:38, Matthew Brost wrote:
On Thu, Apr 30, 2026 at 12:36:41PM -0700, Matthew Brost wrote:
On Thu, Apr 30, 2026 at 03:41:30PM +0530, Arvind Yadav wrote:
xe_bo_recompute_purgeable_state() walks all VMAs of a BO to determine
whether the BO can be made purgeable. This makes VMA create/destroy and
madvise updates O(n) in the number of mappings.

Replace the walk with BO-local counters protected by the BO dma-resv
lock:

  - vma_count tracks the number of VMAs mapping the BO.
  - willneed_count tracks active WILLNEED holders, including WILLNEED
    VMAs and active dma-buf exports for non-imported BOs.

A DONTNEED BO is promoted back to WILLNEED on a 0->1 transition of
willneed_count. A BO is demoted to DONTNEED on a 1->0 transition only
when it still has VMAs, preserving the previous behaviour where a BO
with no mappings keeps its current madvise state.

PURGED remains terminal, preserving the existing "once purged, always
purged" rule.

v2:
  - Use early return for imported BOs in all four helpers to avoid
    nesting (Matt B).
  - Group purgeability state into a purgeable sub-struct on struct
    xe_bo (Matt B).
  - Reword xe_bo_willneed_put_locked() kernel-doc to explain that a 1->0
    transition means all remaining active VMAs are DONTNEED (Matt B).

Suggested-by: Thomas Hellström <thomas.hellstrom@linux.intel.com>
Cc: Matthew Brost <matthew.brost@intel.com>
Reviewed-by: Matthew Brost <matthew.brost@intel.com>

My bad - sashiko flagged a valid issue here [1].

So I xe_vma_create need to flags.check_purged check that is currently in
vma_lock_and_validate(). We the dma-resv locks in xe_vma_create so
moving the check to xe_vma_create should be safe. 

More below.

[1] https://sashiko.dev/#/patchset/20260430101130.1365878-1-arvind.yadav%40intel.com


Yes, Good catch. we should add the check in xe_vma_create(), but it also needs to remain in vma_lock_and_validate() because PREFETCH does not go through xe_vma_create(). drm_gpuvm_prefetch_ops_create() generates operations that reference existing VMAs, and the PREFETCH path in op_lock_and_prep() relies solely on vma_lock_and_validate().


Cc: Thomas Hellström <thomas.hellstrom@linux.intel.com>
Cc: Himal Prasad Ghimiray <himal.prasad.ghimiray@intel.com>
Signed-off-by: Arvind Yadav <arvind.yadav@intel.com>
---
 drivers/gpu/drm/xe/xe_bo.c         |   6 +-
 drivers/gpu/drm/xe/xe_bo.h         |  88 +++++++++++++++-
 drivers/gpu/drm/xe/xe_bo_types.h   |  27 ++++-
 drivers/gpu/drm/xe/xe_dma_buf.c    |  28 ++++-
 drivers/gpu/drm/xe/xe_vm.c         |   9 +-
 drivers/gpu/drm/xe/xe_vm_madvise.c | 162 ++---------------------------
 drivers/gpu/drm/xe/xe_vm_madvise.h |   2 -
 7 files changed, 155 insertions(+), 167 deletions(-)

diff --git a/drivers/gpu/drm/xe/xe_bo.c b/drivers/gpu/drm/xe/xe_bo.c
index 5ce60d161e09..eaa3a4ee9111 100644
--- a/drivers/gpu/drm/xe/xe_bo.c
+++ b/drivers/gpu/drm/xe/xe_bo.c
@@ -884,10 +884,10 @@ void xe_bo_set_purgeable_state(struct xe_bo *bo,
 		  new_state == XE_MADV_PURGEABLE_PURGED);
 
 	/* Once purged, always purged - cannot transition out */
-	xe_assert(xe, !(bo->madv_purgeable == XE_MADV_PURGEABLE_PURGED &&
+	xe_assert(xe, !(bo->purgeable.state == XE_MADV_PURGEABLE_PURGED &&
 			new_state != XE_MADV_PURGEABLE_PURGED));
 
-	bo->madv_purgeable = new_state;
+	bo->purgeable.state = new_state;
 	xe_bo_set_purgeable_shrinker(bo, new_state);
 }
 
@@ -2355,7 +2355,7 @@ struct xe_bo *xe_bo_init_locked(struct xe_device *xe, struct xe_bo *bo,
 	INIT_LIST_HEAD(&bo->vram_userfault_link);
 
 	/* Initialize purge advisory state */
-	bo->madv_purgeable = XE_MADV_PURGEABLE_WILLNEED;
+	bo->purgeable.state = XE_MADV_PURGEABLE_WILLNEED;
 
 	drm_gem_private_object_init(&xe->drm, &bo->ttm.base, size);
 
diff --git a/drivers/gpu/drm/xe/xe_bo.h b/drivers/gpu/drm/xe/xe_bo.h
index 68dea7d25a6b..6340317f7d2e 100644
--- a/drivers/gpu/drm/xe/xe_bo.h
+++ b/drivers/gpu/drm/xe/xe_bo.h
@@ -251,7 +251,7 @@ static inline bool xe_bo_is_protected(const struct xe_bo *bo)
 static inline bool xe_bo_is_purged(struct xe_bo *bo)
 {
 	xe_bo_assert_held(bo);
-	return bo->madv_purgeable == XE_MADV_PURGEABLE_PURGED;
+	return bo->purgeable.state == XE_MADV_PURGEABLE_PURGED;
 }
 
 /**
@@ -268,11 +268,95 @@ static inline bool xe_bo_is_purged(struct xe_bo *bo)
 static inline bool xe_bo_madv_is_dontneed(struct xe_bo *bo)
 {
 	xe_bo_assert_held(bo);
-	return bo->madv_purgeable == XE_MADV_PURGEABLE_DONTNEED;
+	return bo->purgeable.state == XE_MADV_PURGEABLE_DONTNEED;
 }
 
 void xe_bo_set_purgeable_state(struct xe_bo *bo, enum xe_madv_purgeable_state new_state);
 
+/**
+ * xe_bo_willneed_get_locked() - Acquire a WILLNEED holder on a BO
+ * @bo: Buffer object
+ *
+ * Increments willneed_count and, on a 0->1 transition, promotes the BO
+ * from DONTNEED to WILLNEED. PURGED is terminal and is never modified.
+ *
+ * Caller must hold the BO's dma-resv lock.
+ */
+static inline void xe_bo_willneed_get_locked(struct xe_bo *bo)
+{
+	xe_bo_assert_held(bo);
+
+	/* Imported BOs are owned externally; do not track purgeability. */
+	if (drm_gem_is_imported(&bo->ttm.base))
+		return;
+
+	if (bo->purgeable.willneed_count++ == 0 && xe_bo_madv_is_dontneed(bo))
+		xe_bo_set_purgeable_state(bo, XE_MADV_PURGEABLE_WILLNEED);
+}
+
+/**
+ * xe_bo_willneed_put_locked() - Release a WILLNEED holder on a BO
+ * @bo: Buffer object
+ *
+ * Decrements willneed_count and, on a 1->0 transition, marks the BO
+ * DONTNEED only if it still has VMAs (implying all active VMAs are
+ * DONTNEED). If the last VMA is being removed, preserve the current BO
+ * state to match the previous VMA-walk semantics.
+ *
+ * PURGED is terminal and the BO state is never modified.
+ *
+ * Caller must hold the BO's dma-resv lock.
+ */
+static inline void xe_bo_willneed_put_locked(struct xe_bo *bo)
+{
+	xe_bo_assert_held(bo);
+
+	if (drm_gem_is_imported(&bo->ttm.base))
+		return;
+
+	xe_assert(xe_bo_device(bo), bo->purgeable.willneed_count > 0);
+	if (--bo->purgeable.willneed_count == 0 && bo->purgeable.vma_count > 0 &&
+	    !xe_bo_is_purged(bo))
+		xe_bo_set_purgeable_state(bo, XE_MADV_PURGEABLE_DONTNEED);
+}
+
+/**
+ * xe_bo_vma_count_inc_locked() - Account a new VMA on a BO
+ * @bo: Buffer object
+ *
+ * Increments vma_count.
+ *
+ * Caller must hold the BO's dma-resv lock.
+ */
+static inline void xe_bo_vma_count_inc_locked(struct xe_bo *bo)
+{
+	xe_bo_assert_held(bo);
+
+	if (drm_gem_is_imported(&bo->ttm.base))
+		return;
+
+	bo->purgeable.vma_count++;
+}
+
+/**
+ * xe_bo_vma_count_dec_locked() - Account a VMA removal on a BO
+ * @bo: Buffer object
+ *
+ * Decrements vma_count.
+ *
+ * Caller must hold the BO's dma-resv lock.
+ */
+static inline void xe_bo_vma_count_dec_locked(struct xe_bo *bo)
+{
+	xe_bo_assert_held(bo);
+
+	if (drm_gem_is_imported(&bo->ttm.base))
+		return;
+
+	xe_assert(xe_bo_device(bo), bo->purgeable.vma_count > 0);
+	bo->purgeable.vma_count--;
+}
+
 static inline void xe_bo_unpin_map_no_vm(struct xe_bo *bo)
 {
 	if (likely(bo)) {
diff --git a/drivers/gpu/drm/xe/xe_bo_types.h b/drivers/gpu/drm/xe/xe_bo_types.h
index 9c199badd9b2..6756d7820aca 100644
--- a/drivers/gpu/drm/xe/xe_bo_types.h
+++ b/drivers/gpu/drm/xe/xe_bo_types.h
@@ -111,10 +111,31 @@ struct xe_bo {
 	u64 min_align;
 
 	/**
-	 * @madv_purgeable: user space advise on BO purgeability, protected
-	 * by BO's dma-resv lock.
+	 * @purgeable: Purgeability state and accounting.
+	 *
+	 * All fields are protected by the BO's dma-resv lock.
 	 */
-	u32 madv_purgeable;
+	struct {
+		/**
+		 * @purgeable.state: BO purgeability state (WILLNEED/DONTNEED/PURGED).
+		 */
+		u32 state;
+
+		/**
+		 * @purgeable.vma_count: Number of VMAs currently mapping this BO.
+		 */
+		u32 vma_count;
+
+		/**
+		 * @purgeable.willneed_count: Number of active WILLNEED holders.
+		 *
+		 * Counts WILLNEED VMAs plus active dma-buf exports for
+		 * non-imported BOs. The BO flips to DONTNEED on a 1->0
+		 * transition only when VMAs still exist; if the last VMA is
+		 * removed, the previous BO state is preserved.
+		 */
+		u32 willneed_count;
+	} purgeable;
 };
 
 #endif
diff --git a/drivers/gpu/drm/xe/xe_dma_buf.c b/drivers/gpu/drm/xe/xe_dma_buf.c
index b9828da15897..855d32ba314d 100644
--- a/drivers/gpu/drm/xe/xe_dma_buf.c
+++ b/drivers/gpu/drm/xe/xe_dma_buf.c
@@ -193,6 +193,18 @@ static int xe_dma_buf_begin_cpu_access(struct dma_buf *dma_buf,
 	return 0;
 }
 
+static void xe_dma_buf_release(struct dma_buf *dmabuf)
+{
+	struct drm_gem_object *obj = dmabuf->priv;
+	struct xe_bo *bo = gem_to_xe_bo(obj);
+
+	xe_bo_lock(bo, false);
+	xe_bo_willneed_put_locked(bo);
+	xe_bo_unlock(bo);
+
+	drm_gem_dmabuf_release(dmabuf);
+}
+
 static const struct dma_buf_ops xe_dmabuf_ops = {
 	.attach = xe_dma_buf_attach,
 	.detach = xe_dma_buf_detach,
@@ -200,7 +212,7 @@ static const struct dma_buf_ops xe_dmabuf_ops = {
 	.unpin = xe_dma_buf_unpin,
 	.map_dma_buf = xe_dma_buf_map,
 	.unmap_dma_buf = xe_dma_buf_unmap,
-	.release = drm_gem_dmabuf_release,
+	.release = xe_dma_buf_release,
 	.begin_cpu_access = xe_dma_buf_begin_cpu_access,
 	.mmap = drm_gem_dmabuf_mmap,
 	.vmap = drm_gem_dmabuf_vmap,
@@ -241,18 +253,26 @@ struct dma_buf *xe_gem_prime_export(struct drm_gem_object *obj, int flags)
 		ret = -EINVAL;
 		goto out_unlock;
 	}
+
+	xe_bo_willneed_get_locked(bo);
 	xe_bo_unlock(bo);
 
 	ret = ttm_bo_setup_export(&bo->ttm, &ctx);
 	if (ret)
-		return ERR_PTR(ret);
+		goto out_put;
 
 	buf = drm_gem_prime_export(obj, flags);
-	if (!IS_ERR(buf))
-		buf->ops = &xe_dmabuf_ops;
+	if (IS_ERR(buf)) {
+		ret = PTR_ERR(buf);
+		goto out_put;
+	}
 
+	buf->ops = &xe_dmabuf_ops;
 	return buf;
 
+out_put:
+	xe_bo_lock(bo, false);
+	xe_bo_willneed_put_locked(bo);
 out_unlock:
 	xe_bo_unlock(bo);
 	return ERR_PTR(ret);
diff --git a/drivers/gpu/drm/xe/xe_vm.c b/drivers/gpu/drm/xe/xe_vm.c
index c3836f6eab35..12457173ba85 100644
--- a/drivers/gpu/drm/xe/xe_vm.c
+++ b/drivers/gpu/drm/xe/xe_vm.c
@@ -1131,6 +1131,10 @@ static struct xe_vma *xe_vma_create(struct xe_vm *vm,
 		vma->gpuva.gem.offset = bo_offset_or_userptr;
 		drm_gpuva_link(&vma->gpuva, vm_bo);
 		drm_gpuvm_bo_put(vm_bo);
+
+		xe_bo_vma_count_inc_locked(bo);
+		if (vma->attr.purgeable_state == XE_MADV_PURGEABLE_WILLNEED)
+			xe_bo_willneed_get_locked(bo);
So at very top of this function I think:

if (bo && attr->purgeable_state == XE_MADV_PURGEABLE_WILLNEED) {
	if (xe_bo_madv_is_dontneed(bo))
		return ERR_PTR(-EBUSY);  /* BO marked purgeable */
	else if (xe_bo_is_purged(bo))
		return ERR_PTR(-EINVAL); /* BO already purged */
}

Then delete the check in vma_lock_and_validate. I think check in
vma_lock_and_validate is actually wrong for rebinds too - e.g., it is
prefectly to do a partial unbind a dontneed or purged BO and the
existing check I believe would reject this.


Agreed. I will dropped .check_purged from MAP and REMAP prev/next in vm_bind_ioctl_op_lock_and_prep() for v3. The check now lives in xe_vma_create() and only triggers when attr->purgeable_state == WILLNEED, so partial unbind/rebind on DONTNEED or PURGED BOs works as expected. and PREFETCH keeps .check_purged as it is a separate check for existing VMAs with no backing store to migrate.


We should put together a test for for this too.

addr = bind(2M);
madvise(addr, DONTNEED);
unbind(addr, 1M);

Assuming the current code fails in this test case and new code works,
I'd suggest making this patch a fixes too.


Noted. I will add a test case to cover this and include the appropriate fix tag

Thanks,
Arvind


Matt

 	} else /* userptr or null */ {
 		if (!is_null && !is_cpu_addr_mirror) {
 			struct xe_userptr_vma *uvma = to_userptr_vma(vma);
@@ -1208,7 +1212,10 @@ static void xe_vma_destroy(struct xe_vma *vma, struct dma_fence *fence)
 		xe_bo_assert_held(bo);
 
 		drm_gpuva_unlink(&vma->gpuva);
-		xe_bo_recompute_purgeable_state(bo);
+
+		xe_bo_vma_count_dec_locked(bo);
+		if (vma->attr.purgeable_state == XE_MADV_PURGEABLE_WILLNEED)
+			xe_bo_willneed_put_locked(bo);
 	}
 
 	xe_vm_assert_held(vm);
diff --git a/drivers/gpu/drm/xe/xe_vm_madvise.c b/drivers/gpu/drm/xe/xe_vm_madvise.c
index c78906dea82b..c4fb29004195 100644
--- a/drivers/gpu/drm/xe/xe_vm_madvise.c
+++ b/drivers/gpu/drm/xe/xe_vm_madvise.c
@@ -185,147 +185,6 @@ static void madvise_pat_index(struct xe_device *xe, struct xe_vm *vm,
 	}
 }
 
-/**
- * xe_bo_is_dmabuf_shared() - Check if BO is shared via dma-buf
- * @bo: Buffer object
- *
- * Prevent marking imported or exported dma-bufs as purgeable.
- * For imported BOs, Xe doesn't own the backing store and cannot
- * safely reclaim pages (exporter or other devices may still be
- * using them). For exported BOs, external devices may have active
- * mappings we cannot track.
- *
- * Return: true if BO is imported or exported, false otherwise
- */
-static bool xe_bo_is_dmabuf_shared(struct xe_bo *bo)
-{
-	struct drm_gem_object *obj = &bo->ttm.base;
-
-	/* Imported: exporter owns backing store */
-	if (drm_gem_is_imported(obj))
-		return true;
-
-	/* Exported: external devices may be accessing */
-	if (obj->dma_buf)
-		return true;
-
-	return false;
-}
-
-/**
- * enum xe_bo_vmas_purge_state - VMA purgeable state aggregation
- *
- * Distinguishes whether a BO's VMAs are all DONTNEED, have at least
- * one WILLNEED, or have no VMAs at all.
- *
- * Enum values align with XE_MADV_PURGEABLE_* states for consistency.
- */
-enum xe_bo_vmas_purge_state {
-	/** @XE_BO_VMAS_STATE_WILLNEED: At least one VMA is WILLNEED */
-	XE_BO_VMAS_STATE_WILLNEED = 0,
-	/** @XE_BO_VMAS_STATE_DONTNEED: All VMAs are DONTNEED */
-	XE_BO_VMAS_STATE_DONTNEED = 1,
-	/** @XE_BO_VMAS_STATE_NO_VMAS: BO has no VMAs */
-	XE_BO_VMAS_STATE_NO_VMAS = 2,
-};
-
-/*
- * xe_bo_recompute_purgeable_state() casts between xe_bo_vmas_purge_state and
- * xe_madv_purgeable_state. Enforce that WILLNEED=0 and DONTNEED=1 match across
- * both enums so the single-line cast is always valid.
- */
-static_assert(XE_BO_VMAS_STATE_WILLNEED == (int)XE_MADV_PURGEABLE_WILLNEED,
-	      "VMA purge state WILLNEED must equal madv purgeable WILLNEED");
-static_assert(XE_BO_VMAS_STATE_DONTNEED == (int)XE_MADV_PURGEABLE_DONTNEED,
-	      "VMA purge state DONTNEED must equal madv purgeable DONTNEED");
-
-/**
- * xe_bo_all_vmas_dontneed() - Determine BO VMA purgeable state
- * @bo: Buffer object
- *
- * Check all VMAs across all VMs to determine aggregate purgeable state.
- * Shared BOs require unanimous DONTNEED state from all mappings.
- *
- * Caller must hold BO dma-resv lock.
- *
- * Return: XE_BO_VMAS_STATE_DONTNEED if all VMAs are DONTNEED,
- *         XE_BO_VMAS_STATE_WILLNEED if at least one VMA is not DONTNEED,
- *         XE_BO_VMAS_STATE_NO_VMAS if BO has no VMAs
- */
-static enum xe_bo_vmas_purge_state xe_bo_all_vmas_dontneed(struct xe_bo *bo)
-{
-	struct drm_gpuvm_bo *vm_bo;
-	struct drm_gpuva *gpuva;
-	struct drm_gem_object *obj = &bo->ttm.base;
-	bool has_vmas = false;
-
-	xe_bo_assert_held(bo);
-
-	/* Shared dma-bufs cannot be purgeable */
-	if (xe_bo_is_dmabuf_shared(bo))
-		return XE_BO_VMAS_STATE_WILLNEED;
-
-	drm_gem_for_each_gpuvm_bo(vm_bo, obj) {
-		drm_gpuvm_bo_for_each_va(gpuva, vm_bo) {
-			struct xe_vma *vma = gpuva_to_vma(gpuva);
-
-			has_vmas = true;
-
-			/* Any non-DONTNEED VMA prevents purging */
-			if (vma->attr.purgeable_state != XE_MADV_PURGEABLE_DONTNEED)
-				return XE_BO_VMAS_STATE_WILLNEED;
-		}
-	}
-
-	/*
-	 * No VMAs => preserve existing BO purgeable state.
-	 * Avoids incorrectly flipping DONTNEED -> WILLNEED when last VMA unmapped.
-	 */
-	if (!has_vmas)
-		return XE_BO_VMAS_STATE_NO_VMAS;
-
-	return XE_BO_VMAS_STATE_DONTNEED;
-}
-
-/**
- * xe_bo_recompute_purgeable_state() - Recompute BO purgeable state from VMAs
- * @bo: Buffer object
- *
- * Walk all VMAs to determine if BO should be purgeable or not.
- * Shared BOs require unanimous DONTNEED state from all mappings.
- * If the BO has no VMAs the existing state is preserved.
- *
- * Locking: Caller must hold BO dma-resv lock. When iterating GPUVM lists,
- * VM lock must also be held (write) to prevent concurrent VMA modifications.
- * This is satisfied at both call sites:
- * - xe_vma_destroy(): holds vm->lock write
- * - madvise_purgeable(): holds vm->lock write (from madvise ioctl path)
- *
- * Return: nothing
- */
-void xe_bo_recompute_purgeable_state(struct xe_bo *bo)
-{
-	enum xe_bo_vmas_purge_state vma_state;
-
-	if (!bo)
-		return;
-
-	xe_bo_assert_held(bo);
-
-	/*
-	 * Once purged, always purged. Cannot transition back to WILLNEED.
-	 * This matches i915 semantics where purged BOs are permanently invalid.
-	 */
-	if (bo->madv_purgeable == XE_MADV_PURGEABLE_PURGED)
-		return;
-
-	vma_state = xe_bo_all_vmas_dontneed(bo);
-
-	if (vma_state != (enum xe_bo_vmas_purge_state)bo->madv_purgeable &&
-	    vma_state != XE_BO_VMAS_STATE_NO_VMAS)
-		xe_bo_set_purgeable_state(bo, (enum xe_madv_purgeable_state)vma_state);
-}
-
 /**
  * madvise_purgeable - Handle purgeable buffer object advice
  * @xe: XE device
@@ -359,12 +218,6 @@ static void madvise_purgeable(struct xe_device *xe, struct xe_vm *vm,
 		/* BO must be locked before modifying madv state */
 		xe_bo_assert_held(bo);
 
-		/* Skip shared dma-bufs - no PTEs to zap */
-		if (xe_bo_is_dmabuf_shared(bo)) {
-			vmas[i]->skip_invalidation = true;
-			continue;
-		}
-
 		/*
 		 * Once purged, always purged. Cannot transition back to WILLNEED.
 		 * This matches i915 semantics where purged BOs are permanently invalid.
@@ -377,13 +230,14 @@ static void madvise_purgeable(struct xe_device *xe, struct xe_vm *vm,
 
 		switch (op->purge_state_val.val) {
 		case DRM_XE_VMA_PURGEABLE_STATE_WILLNEED:
-			vmas[i]->attr.purgeable_state = XE_MADV_PURGEABLE_WILLNEED;
 			vmas[i]->skip_invalidation = true;
-
-			xe_bo_recompute_purgeable_state(bo);
+			/* Only act on a real DONTNEED -> WILLNEED transition. */
+			if (vmas[i]->attr.purgeable_state == XE_MADV_PURGEABLE_DONTNEED) {
+				vmas[i]->attr.purgeable_state = XE_MADV_PURGEABLE_WILLNEED;
+				xe_bo_willneed_get_locked(bo);
+			}
 			break;
 		case DRM_XE_VMA_PURGEABLE_STATE_DONTNEED:
-			vmas[i]->attr.purgeable_state = XE_MADV_PURGEABLE_DONTNEED;
 			/*
 			 * Don't zap PTEs at DONTNEED time -- pages are still
 			 * alive. The zap happens in xe_bo_move_notify() right
@@ -391,7 +245,11 @@ static void madvise_purgeable(struct xe_device *xe, struct xe_vm *vm,
 			 */
 			vmas[i]->skip_invalidation = true;
 
-			xe_bo_recompute_purgeable_state(bo);
+			/* Only act on a real WILLNEED -> DONTNEED transition. */
+			if (vmas[i]->attr.purgeable_state == XE_MADV_PURGEABLE_WILLNEED) {
+				vmas[i]->attr.purgeable_state = XE_MADV_PURGEABLE_DONTNEED;
+				xe_bo_willneed_put_locked(bo);
+			}
 			break;
 		default:
 			/* Should never hit - values validated in madvise_args_are_sane() */
diff --git a/drivers/gpu/drm/xe/xe_vm_madvise.h b/drivers/gpu/drm/xe/xe_vm_madvise.h
index 39acd2689ca0..a3078f634c7e 100644
--- a/drivers/gpu/drm/xe/xe_vm_madvise.h
+++ b/drivers/gpu/drm/xe/xe_vm_madvise.h
@@ -13,6 +13,4 @@ struct xe_bo;
 int xe_vm_madvise_ioctl(struct drm_device *dev, void *data,
 			struct drm_file *file);
 
-void xe_bo_recompute_purgeable_state(struct xe_bo *bo);
-
 #endif
-- 
2.43.0

--------------58pSGEOyDNjKcadcyXr9V0aH--