drm/pagemap: Add a drm_pagemap cache and shrinker

Pagemaps are costly to set up and tear down, and they consume a lot
of system memory for the struct pages. Ideally they should be
created only when needed.

Add a caching mechanism to allow doing just that: Create the drm_pagemaps
when needed for migration. Keep them around to avoid destruction and
re-creation latencies and destroy inactive/unused drm_pagemaps on memory
pressure using a shrinker.

Only add the helper functions. They will be hooked up to the xe driver
in the upcoming patch.

v2:
- Add lockdep checking for drm_pagemap_put(). (Matt Brost)
- Add a copyright notice. (Matt Brost)

Signed-off-by: Thomas Hellström <thomas.hellstrom@linux.intel.com>
Reviewed-by: Matthew Brost <matthew.brost@intel.com>
Acked-by: Maarten Lankhorst <maarten.lankhorst@linux.intel.com> # For merging through drm-xe.
Link: https://patch.msgid.link/20251219113320.183860-8-thomas.hellstrom@linux.intel.com
This commit is contained in:
Thomas Hellström
2025-12-19 12:33:03 +01:00
parent a26084328a
commit 77f14f2f2d
5 changed files with 613 additions and 18 deletions

View File

@@ -10,6 +10,7 @@
struct dma_fence;
struct drm_pagemap;
struct drm_pagemap_cache;
struct drm_pagemap_dev_hold;
struct drm_pagemap_zdd;
struct device;
@@ -125,6 +126,25 @@ struct drm_pagemap_ops {
unsigned long start, unsigned long end,
struct mm_struct *mm,
unsigned long timeslice_ms);
/**
* @destroy: Destroy the drm_pagemap and associated resources.
* @dpagemap: The drm_pagemap to destroy.
* @is_atomic_or_reclaim: The function may be called from
* atomic- or reclaim context.
*
* The implementation should take care not to attempt to
* destroy resources that may already have been destroyed
* using devm_ callbacks, since this function may be called
* after the underlying struct device has been unbound.
* If the implementation defers the execution to a work item
* to avoid locking issues, then it must make sure the work
* items are flushed before module exit. If the destroy call
* happens after the provider's pci_remove() callback has
* been executed, a module reference and drm device reference is
* held across the destroy callback.
*/
void (*destroy)(struct drm_pagemap *dpagemap,
bool is_atomic_or_reclaim);
};
/**
@@ -136,6 +156,10 @@ struct drm_pagemap_ops {
* @pagemap: Pointer to the underlying dev_pagemap.
* @dev_hold: Pointer to a struct drm_pagemap_dev_hold for
* device referencing.
* @cache: Back-pointer to the &struct drm_pagemap_cache used for this
* &struct drm_pagemap. May be NULL if no cache is used.
* @shrink_link: Link into the shrinker's list of drm_pagemaps. Only
* used if also using a pagemap cache.
*/
struct drm_pagemap {
const struct drm_pagemap_ops *ops;
@@ -143,6 +167,8 @@ struct drm_pagemap {
struct drm_device *drm;
struct dev_pagemap *pagemap;
struct drm_pagemap_dev_hold *dev_hold;
struct drm_pagemap_cache *cache;
struct list_head shrink_link;
};
struct drm_pagemap_devmem;
@@ -217,6 +243,11 @@ struct drm_pagemap_devmem_ops {
struct dma_fence *pre_migrate_fence);
};
int drm_pagemap_init(struct drm_pagemap *dpagemap,
struct dev_pagemap *pagemap,
struct drm_device *drm,
const struct drm_pagemap_ops *ops);
struct drm_pagemap *drm_pagemap_create(struct drm_device *drm,
struct dev_pagemap *pagemap,
const struct drm_pagemap_ops *ops);
@@ -235,9 +266,9 @@ static inline void drm_pagemap_put(struct drm_pagemap *dpagemap)
/**
* drm_pagemap_get() - Obtain a reference on a struct drm_pagemap
* @dpagemap: Pointer to the struct drm_pagemap.
* @dpagemap: Pointer to the struct drm_pagemap, or NULL.
*
* Return: Pointer to the struct drm_pagemap.
* Return: Pointer to the struct drm_pagemap, or NULL.
*/
static inline struct drm_pagemap *
drm_pagemap_get(struct drm_pagemap *dpagemap)
@@ -248,6 +279,20 @@ drm_pagemap_get(struct drm_pagemap *dpagemap)
return dpagemap;
}
/**
* drm_pagemap_get_unless_zero() - Obtain a reference on a struct drm_pagemap
* unless the current reference count is zero.
* @dpagemap: Pointer to the drm_pagemap or NULL.
*
* Return: A pointer to @dpagemap if the reference count was successfully
* incremented. NULL if @dpagemap was NULL, or its refcount was 0.
*/
static inline struct drm_pagemap * __must_check
drm_pagemap_get_unless_zero(struct drm_pagemap *dpagemap)
{
return (dpagemap && kref_get_unless_zero(&dpagemap->ref)) ? dpagemap : NULL;
}
/**
* struct drm_pagemap_devmem - Structure representing a GPU SVM device memory allocation
*
@@ -295,5 +340,7 @@ int drm_pagemap_populate_mm(struct drm_pagemap *dpagemap,
struct mm_struct *mm,
unsigned long timeslice_ms);
#endif
void drm_pagemap_destroy(struct drm_pagemap *dpagemap, bool is_atomic_or_reclaim);
int drm_pagemap_reinit(struct drm_pagemap *dpagemap);
#endif

View File

@@ -0,0 +1,42 @@
/* SPDX-License-Identifier: MIT */
/*
* Copyright © 2025 Intel Corporation
*/
#ifndef _DRM_PAGEMAP_UTIL_H_
#define _DRM_PAGEMAP_UTIL_H_
struct drm_device;
struct drm_pagemap;
struct drm_pagemap_cache;
struct drm_pagemap_shrinker;
void drm_pagemap_shrinker_add(struct drm_pagemap *dpagemap);
int drm_pagemap_cache_lock_lookup(struct drm_pagemap_cache *cache);
void drm_pagemap_cache_unlock_lookup(struct drm_pagemap_cache *cache);
struct drm_pagemap_shrinker *drm_pagemap_shrinker_create_devm(struct drm_device *drm);
struct drm_pagemap_cache *drm_pagemap_cache_create_devm(struct drm_pagemap_shrinker *shrinker);
struct drm_pagemap *drm_pagemap_get_from_cache(struct drm_pagemap_cache *cache);
void drm_pagemap_cache_set_pagemap(struct drm_pagemap_cache *cache, struct drm_pagemap *dpagemap);
struct drm_pagemap *drm_pagemap_get_from_cache_if_active(struct drm_pagemap_cache *cache);
#ifdef CONFIG_PROVE_LOCKING
void drm_pagemap_shrinker_might_lock(struct drm_pagemap *dpagemap);
#else
static inline void drm_pagemap_shrinker_might_lock(struct drm_pagemap *dpagemap)
{
}
#endif /* CONFIG_PROVE_LOCKING */
#endif