On Tue, Nov 11, 2025 at 05:43:55PM +0100, Thomas Hellström wrote:
> 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 <[email protected]>

Reviewed-by: Matthew Brost <[email protected]>

> ---
>  drivers/gpu/drm/Makefile           |   3 +-
>  drivers/gpu/drm/drm_pagemap.c      |  83 +++++-
>  drivers/gpu/drm/drm_pagemap_util.c | 450 +++++++++++++++++++++++++++++
>  include/drm/drm_pagemap.h          |  53 +++-
>  include/drm/drm_pagemap_util.h     |  42 +++
>  5 files changed, 613 insertions(+), 18 deletions(-)
>  create mode 100644 drivers/gpu/drm/drm_pagemap_util.c
>  create mode 100644 include/drm/drm_pagemap_util.h
> 
> diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile
> index 7789f42027ff..04ff0b3e55b0 100644
> --- a/drivers/gpu/drm/Makefile
> +++ b/drivers/gpu/drm/Makefile
> @@ -107,7 +107,8 @@ obj-$(CONFIG_DRM_GPUVM) += drm_gpuvm.o
>  
>  drm_gpusvm_helper-y := \
>       drm_gpusvm.o\
> -     drm_pagemap.o
> +     drm_pagemap.o\
> +     drm_pagemap_util.o
>  obj-$(CONFIG_DRM_GPUSVM) += drm_gpusvm_helper.o
>  
>  obj-$(CONFIG_DRM_BUDDY) += drm_buddy.o
> diff --git a/drivers/gpu/drm/drm_pagemap.c b/drivers/gpu/drm/drm_pagemap.c
> index fb18a80d6a1c..50d3963ddbbc 100644
> --- a/drivers/gpu/drm/drm_pagemap.c
> +++ b/drivers/gpu/drm/drm_pagemap.c
> @@ -8,6 +8,7 @@
>  #include <linux/pagemap.h>
>  #include <drm/drm_drv.h>
>  #include <drm/drm_pagemap.h>
> +#include <drm/drm_pagemap_util.h>
>  #include <drm/drm_print.h>
>  
>  /**
> @@ -578,7 +579,7 @@ static void drm_pagemap_release(struct kref *ref)
>        * pagemap provider drm_device and its module.
>        */
>       dpagemap->dev_hold = NULL;
> -     kfree(dpagemap);
> +     drm_pagemap_shrinker_add(dpagemap);
>       llist_add(&dev_hold->link, &drm_pagemap_unhold_list);
>       schedule_work(&drm_pagemap_work);
>       /*
> @@ -628,6 +629,58 @@ drm_pagemap_dev_hold(struct drm_pagemap *dpagemap)
>       return dev_hold;
>  }
>  
> +/**
> + * drm_pagemap_reinit() - Reinitialize a drm_pagemap
> + * @dpagemap: The drm_pagemap to reinitialize
> + *
> + * Reinitialize a drm_pagemap, for which drm_pagemap_release
> + * has already been called. This interface is intended for the
> + * situation where the driver caches a destroyed drm_pagemap.
> + *
> + * Return: 0 on success, negative error code on failure.
> + */
> +int drm_pagemap_reinit(struct drm_pagemap *dpagemap)
> +{
> +     dpagemap->dev_hold = drm_pagemap_dev_hold(dpagemap);
> +     if (IS_ERR(dpagemap->dev_hold))
> +             return PTR_ERR(dpagemap->dev_hold);
> +
> +     kref_init(&dpagemap->ref);
> +     return 0;
> +}
> +EXPORT_SYMBOL(drm_pagemap_reinit);
> +
> +/**
> + * drm_pagemap_init() - Initialize a pre-allocated drm_pagemap
> + * @dpagemap: The drm_pagemap to initialize.
> + * @pagemap: The associated dev_pagemap providing the device
> + * private pages.
> + * @drm: The drm device. The drm_pagemap holds a reference on the
> + * drm_device and the module owning the drm_device until
> + * drm_pagemap_release(). This facilitates drm_pagemap exporting.
> + * @ops: The drm_pagemap ops.
> + *
> + * Initialize and take an initial reference on a drm_pagemap.
> + * After successful return, use drm_pagemap_put() to destroy.
> + *
> + ** Return: 0 on success, negative error code on error.
> + */
> +int drm_pagemap_init(struct drm_pagemap *dpagemap,
> +                  struct dev_pagemap *pagemap,
> +                  struct drm_device *drm,
> +                  const struct drm_pagemap_ops *ops)
> +{
> +     kref_init(&dpagemap->ref);
> +     dpagemap->ops = ops;
> +     dpagemap->pagemap = pagemap;
> +     dpagemap->drm = drm;
> +     dpagemap->cache = NULL;
> +     INIT_LIST_HEAD(&dpagemap->shrink_link);
> +
> +     return drm_pagemap_reinit(dpagemap);
> +}
> +EXPORT_SYMBOL(drm_pagemap_init);
> +
>  /**
>   * drm_pagemap_create() - Create a struct drm_pagemap.
>   * @drm: Pointer to a struct drm_device providing the device-private memory.
> @@ -645,22 +698,14 @@ drm_pagemap_create(struct drm_device *drm,
>                  const struct drm_pagemap_ops *ops)
>  {
>       struct drm_pagemap *dpagemap = kzalloc(sizeof(*dpagemap), GFP_KERNEL);
> -     struct drm_pagemap_dev_hold *dev_hold;
> +     int err;
>  
>       if (!dpagemap)
>               return ERR_PTR(-ENOMEM);
>  
> -     kref_init(&dpagemap->ref);
> -     dpagemap->drm = drm;
> -     dpagemap->ops = ops;
> -     dpagemap->pagemap = pagemap;
> -
> -     dev_hold = drm_pagemap_dev_hold(dpagemap);
> -     if (IS_ERR(dev_hold)) {
> -             kfree(dpagemap);
> -             return ERR_CAST(dev_hold);
> -     }
> -     dpagemap->dev_hold = dev_hold;
> +     err = drm_pagemap_init(dpagemap, pagemap, drm, ops);
> +     if (err)
> +             return ERR_PTR(err);
>  
>       return dpagemap;
>  }
> @@ -675,8 +720,10 @@ EXPORT_SYMBOL(drm_pagemap_create);
>   */
>  void drm_pagemap_put(struct drm_pagemap *dpagemap)
>  {
> -     if (likely(dpagemap))
> +     if (likely(dpagemap)) {
> +             drm_pagemap_shrinker_might_lock(dpagemap);
>               kref_put(&dpagemap->ref, drm_pagemap_release);
> +     }
>  }
>  EXPORT_SYMBOL(drm_pagemap_put);
>  
> @@ -1023,6 +1070,14 @@ int drm_pagemap_populate_mm(struct drm_pagemap 
> *dpagemap,
>  }
>  EXPORT_SYMBOL(drm_pagemap_populate_mm);
>  
> +void drm_pagemap_destroy(struct drm_pagemap *dpagemap, bool 
> is_atomic_or_reclaim)
> +{
> +     if (dpagemap->ops->destroy)
> +             dpagemap->ops->destroy(dpagemap, is_atomic_or_reclaim);
> +     else
> +             kfree(dpagemap);
> +}
> +
>  static void drm_pagemap_exit(void)
>  {
>       flush_work(&drm_pagemap_work);
> diff --git a/drivers/gpu/drm/drm_pagemap_util.c 
> b/drivers/gpu/drm/drm_pagemap_util.c
> new file mode 100644
> index 000000000000..84a7a4807bef
> --- /dev/null
> +++ b/drivers/gpu/drm/drm_pagemap_util.c
> @@ -0,0 +1,450 @@
> +// SPDX-License-Identifier: GPL-2.0-only OR MIT
> +/*
> + * Copyright © 2025 Intel Corporation
> + */
> +
> +#include <drm/drm_drv.h>
> +#include <drm/drm_managed.h>
> +#include <drm/drm_pagemap.h>
> +#include <drm/drm_pagemap_util.h>
> +#include <drm/drm_print.h>
> +
> +/**
> + * struct drm_pagemap_cache - Lookup structure for pagemaps
> + *
> + * Structure to keep track of active (refcount > 1) and inactive
> + * (refcount == 0) pagemaps. Inactive pagemaps can be made active
> + * again by waiting for the @queued completion (indicating that the
> + * pagemap has been put on the @shrinker's list of shrinkable
> + * pagemaps, and then successfully removing it from @shrinker's
> + * list. The latter may fail if the shrinker is already in the
> + * process of freeing the pagemap. A struct drm_pagemap_cache can
> + * hold a single struct drm_pagemap.
> + */
> +struct drm_pagemap_cache {
> +     /** @lookup_mutex: Mutex making the lookup process atomic */
> +     struct mutex lookup_mutex;
> +     /** @lock: Lock protecting the @dpagemap pointer */
> +     spinlock_t lock;
> +     /** @shrinker: Pointer to the shrinker used for this cache. Immutable. 
> */
> +     struct drm_pagemap_shrinker *shrinker;
> +     /** @dpagemap: Non-refcounted pointer to the drm_pagemap */
> +     struct drm_pagemap *dpagemap;
> +     /**
> +      * @queued: Signals when an inactive drm_pagemap has been put on
> +      * @shrinker's list.
> +      */
> +     struct completion queued;
> +};
> +
> +/**
> + * struct drm_pagemap_shrinker - Shrinker to remove unused pagemaps
> + */
> +struct drm_pagemap_shrinker {
> +     /** @drm: Pointer to the drm device. */
> +     struct drm_device *drm;
> +     /** @lock: Spinlock to protect the @dpagemaps list. */
> +     spinlock_t lock;
> +     /** @dpagemaps: List of unused dpagemaps. */
> +     struct list_head dpagemaps;
> +     /** @num_dpagemaps: Number of unused dpagemaps in @dpagemaps. */
> +     atomic_t num_dpagemaps;
> +     /** @shrink: Pointer to the struct shrinker. */
> +     struct shrinker *shrink;
> +};
> +
> +static bool drm_pagemap_shrinker_cancel(struct drm_pagemap *dpagemap);
> +
> +static void drm_pagemap_cache_fini(void *arg)
> +{
> +     struct drm_pagemap_cache *cache = arg;
> +     struct drm_pagemap *dpagemap;
> +
> +     drm_dbg(cache->shrinker->drm, "Destroying dpagemap cache.\n");
> +     spin_lock(&cache->lock);
> +     dpagemap = cache->dpagemap;
> +     if (!dpagemap) {
> +             spin_unlock(&cache->lock);
> +             goto out;
> +     }
> +
> +     if (drm_pagemap_shrinker_cancel(dpagemap)) {
> +             cache->dpagemap = NULL;
> +             spin_unlock(&cache->lock);
> +             drm_pagemap_destroy(dpagemap, false);
> +     }
> +
> +out:
> +     mutex_destroy(&cache->lookup_mutex);
> +     kfree(cache);
> +}
> +
> +/**
> + * drm_pagemap_cache_create_devm() - Create a drm_pagemap_cache
> + * @shrinker: Pointer to a struct drm_pagemap_shrinker.
> + *
> + * Create a device-managed drm_pagemap cache. The cache is automatically
> + * destroyed on struct device removal, at which point any *inactive*
> + * drm_pagemap's are destroyed.
> + *
> + * Return: Pointer to a struct drm_pagemap_cache on success. Error pointer
> + * on failure.
> + */
> +struct drm_pagemap_cache *drm_pagemap_cache_create_devm(struct 
> drm_pagemap_shrinker *shrinker)
> +{
> +     struct drm_pagemap_cache *cache = kzalloc(sizeof(*cache), GFP_KERNEL);
> +     int err;
> +
> +     if (!cache)
> +             return ERR_PTR(-ENOMEM);
> +
> +     mutex_init(&cache->lookup_mutex);
> +     spin_lock_init(&cache->lock);
> +     cache->shrinker = shrinker;
> +     init_completion(&cache->queued);
> +     err = devm_add_action_or_reset(shrinker->drm->dev, 
> drm_pagemap_cache_fini, cache);
> +     if (err)
> +             return ERR_PTR(err);
> +
> +     return cache;
> +}
> +EXPORT_SYMBOL(drm_pagemap_cache_create_devm);
> +
> +/**
> + * DOC: Cache lookup
> + *
> + * Cache lookup should be done under a locked mutex, so that a
> + * failed drm_pagemap_get_from_cache() and a following
> + * drm_pagemap_cache_setpagemap() are carried out as an atomic
> + * operation WRT other lookups. Otherwise, racing lookups may
> + * unnecessarily concurrently create pagemaps to fulfill a
> + * failed lookup. The API provides two functions to perform this lock,
> + * drm_pagemap_lock_lookup() and drm_pagemap_unlock_lookup() and they
> + * should be used in the following way:
> + *
> + * .. code-block:: c
> + *
> + *           drm_pagemap_lock_lookup(cache);
> + *           dpagemap = drm_pagemap_get_from_cache(cache);
> + *           if (dpagemap)
> + *                   goto out_unlock;
> + *
> + *           dpagemap = driver_create_new_dpagemap();
> + *           if (!IS_ERR(dpagemap))
> + *                   drm_pagemap_cache_set_pagemap(cache, dpagemap);
> + *
> + *     out_unlock:
> + *           drm_pagemap_unlock_lookup(cache);
> + */
> +
> +/**
> + * drm_pagemap_cache_lock_lookup() Lock a drm_pagemap_cache for lookup
> + * @cache: The drm_pagemap_cache to lock.
> + *
> + * Return: %-EINTR if interrupted while blocking. %0 otherwise.
> + */
> +int drm_pagemap_cache_lock_lookup(struct drm_pagemap_cache *cache)
> +{
> +     return mutex_lock_interruptible(&cache->lookup_mutex);
> +}
> +EXPORT_SYMBOL(drm_pagemap_cache_lock_lookup);
> +
> +/**
> + * drm_pagemap_cache_unlock_lookup() Unlock a drm_pagemap_cache after lookup
> + * @cache: The drm_pagemap_cache to unlock.
> + */
> +void drm_pagemap_cache_unlock_lookup(struct drm_pagemap_cache *cache)
> +{
> +     mutex_unlock(&cache->lookup_mutex);
> +}
> +EXPORT_SYMBOL(drm_pagemap_cache_unlock_lookup);
> +
> +/**
> + * drm_pagemap_get_from_cache() -  Lookup of drm_pagemaps.
> + * @cache: The cache used for lookup.
> + *
> + * If an active pagemap is present in the cache, it is immediately returned.
> + * If an inactive pagemap is present, it's removed from the shrinker list and
> + * an attempt is made to make it active.
> + * If no pagemap present or the attempt to make it active failed, %NULL is 
> returned
> + * to indicate to the caller to create a new drm_pagemap and insert it into
> + * the cache.
> + *
> + * Return: A reference-counted pointer to a drm_pagemap if successful. An 
> error
> + * pointer if an error occurred, or %NULL if no drm_pagemap was found and
> + * the caller should insert a new one.
> + */
> +struct drm_pagemap *drm_pagemap_get_from_cache(struct drm_pagemap_cache 
> *cache)
> +{
> +     struct drm_pagemap *dpagemap;
> +     int err;
> +
> +     lockdep_assert_held(&cache->lookup_mutex);
> +retry:
> +     spin_lock(&cache->lock);
> +     dpagemap = cache->dpagemap;
> +     if (drm_pagemap_get_unless_zero(dpagemap)) {
> +             spin_unlock(&cache->lock);
> +             return dpagemap;
> +     }
> +
> +     if (!dpagemap) {
> +             spin_unlock(&cache->lock);
> +             return NULL;
> +     }
> +
> +     if (!try_wait_for_completion(&cache->queued)) {
> +             spin_unlock(&cache->lock);
> +             err = wait_for_completion_interruptible(&cache->queued);
> +             if (err)
> +                     return ERR_PTR(err);
> +             goto retry;
> +     }
> +
> +     if (drm_pagemap_shrinker_cancel(dpagemap)) {
> +             cache->dpagemap = NULL;
> +             spin_unlock(&cache->lock);
> +             err = drm_pagemap_reinit(dpagemap);
> +             if (err) {
> +                     drm_pagemap_destroy(dpagemap, false);
> +                     return ERR_PTR(err);
> +             }
> +             drm_pagemap_cache_set_pagemap(cache, dpagemap);
> +     } else {
> +             cache->dpagemap = NULL;
> +             spin_unlock(&cache->lock);
> +             dpagemap = NULL;
> +     }
> +
> +     return dpagemap;
> +}
> +EXPORT_SYMBOL(drm_pagemap_get_from_cache);
> +
> +/**
> + * drm_pagemap_cache_set_pagemap() - Assign a drm_pagemap to a 
> drm_pagemap_cache
> + * @cache: The cache to assign the drm_pagemap to.
> + * @dpagemap: The drm_pagemap to assign.
> + *
> + * The function must be called to populate a drm_pagemap_cache only
> + * after a call to drm_pagemap_get_from_cache() returns NULL.
> + */
> +void drm_pagemap_cache_set_pagemap(struct drm_pagemap_cache *cache, struct 
> drm_pagemap *dpagemap)
> +{
> +     struct drm_device *drm = dpagemap->drm;
> +
> +     lockdep_assert_held(&cache->lookup_mutex);
> +     spin_lock(&cache->lock);
> +     dpagemap->cache = cache;
> +     swap(cache->dpagemap, dpagemap);
> +     reinit_completion(&cache->queued);
> +     spin_unlock(&cache->lock);
> +     drm_WARN_ON(drm, !!dpagemap);
> +}
> +EXPORT_SYMBOL(drm_pagemap_cache_set_pagemap);
> +
> +/**
> + * drm_pagemap_get_from_cache_if_active() - Quick lookup of active 
> drm_pagemaps
> + * @cache: The cache to lookup from.
> + *
> + * Function that should be used to lookup a drm_pagemap that is already 
> active.
> + * (refcount > 0).
> + *
> + * Return: A pointer to the cache's drm_pagemap if it's active; %NULL 
> otherwise.
> + */
> +struct drm_pagemap *drm_pagemap_get_from_cache_if_active(struct 
> drm_pagemap_cache *cache)
> +{
> +     struct drm_pagemap *dpagemap;
> +
> +     spin_lock(&cache->lock);
> +     dpagemap = drm_pagemap_get_unless_zero(cache->dpagemap);
> +     spin_unlock(&cache->lock);
> +
> +     return dpagemap;
> +}
> +EXPORT_SYMBOL(drm_pagemap_get_from_cache_if_active);
> +
> +static bool drm_pagemap_shrinker_cancel(struct drm_pagemap *dpagemap)
> +{
> +     struct drm_pagemap_cache *cache = dpagemap->cache;
> +     struct drm_pagemap_shrinker *shrinker = cache->shrinker;
> +
> +     spin_lock(&shrinker->lock);
> +     if (list_empty(&dpagemap->shrink_link)) {
> +             spin_unlock(&shrinker->lock);
> +             return false;
> +     }
> +
> +     list_del_init(&dpagemap->shrink_link);
> +     atomic_dec(&shrinker->num_dpagemaps);
> +     spin_unlock(&shrinker->lock);
> +     return true;
> +}
> +
> +#ifdef CONFIG_PROVE_LOCKING
> +/**
> + * drm_pagemap_shrinker_might_lock() - lockdep test for 
> drm_pagemap_shrinker_add()
> + * @dpagemap: The drm pagemap.
> + *
> + * The drm_pagemap_shrinker_add() function performs some locking.
> + * This function can be called in code-paths that might
> + * call drm_pagemap_shrinker_add() to detect any lockdep problems early.
> + */
> +void drm_pagemap_shrinker_might_lock(struct drm_pagemap *dpagemap)
> +{
> +     int idx;
> +
> +     if (drm_dev_enter(dpagemap->drm, &idx)) {
> +             struct drm_pagemap_cache *cache = dpagemap->cache;
> +
> +             if (cache)
> +                     might_lock(&cache->shrinker->lock);
> +
> +             drm_dev_exit(idx);
> +     }
> +}
> +#endif
> +
> +/**
> + * drm_pagemap_shrinker_add() - Add a drm_pagemap to the shrinker list or 
> destroy
> + * @dpagemap: The drm_pagemap.
> + *
> + * If @dpagemap is associated with a &struct drm_pagemap_cache AND the
> + * struct device backing the drm device is still alive, add @dpagemap to
> + * the &struct drm_pagemap_shrinker list of shrinkable drm_pagemaps.
> + *
> + * Otherwise destroy the pagemap directly using drm_pagemap_destroy().
> + *
> + * This is an internal function which is not intended to be exposed to 
> drivers.
> + */
> +void drm_pagemap_shrinker_add(struct drm_pagemap *dpagemap)
> +{
> +     struct drm_pagemap_cache *cache;
> +     struct drm_pagemap_shrinker *shrinker;
> +     int idx;
> +
> +     /*
> +      * The pagemap cache and shrinker are disabled at
> +      * pci device remove time. After that, dpagemaps
> +      * are freed directly.
> +      */
> +     if (!drm_dev_enter(dpagemap->drm, &idx))
> +             goto out_no_cache;
> +
> +     cache = dpagemap->cache;
> +     if (!cache) {
> +             drm_dev_exit(idx);
> +             goto out_no_cache;
> +     }
> +
> +     shrinker = cache->shrinker;
> +     spin_lock(&shrinker->lock);
> +     list_add_tail(&dpagemap->shrink_link, &shrinker->dpagemaps);
> +     atomic_inc(&shrinker->num_dpagemaps);
> +     spin_unlock(&shrinker->lock);
> +     complete_all(&cache->queued);
> +     drm_dev_exit(idx);
> +     return;
> +
> +out_no_cache:
> +     drm_pagemap_destroy(dpagemap, true);
> +}
> +
> +static unsigned long
> +drm_pagemap_shrinker_count(struct shrinker *shrink, struct shrink_control 
> *sc)
> +{
> +     struct drm_pagemap_shrinker *shrinker = shrink->private_data;
> +     unsigned long count = atomic_read(&shrinker->num_dpagemaps);
> +
> +     return count ? : SHRINK_EMPTY;
> +}
> +
> +static unsigned long
> +drm_pagemap_shrinker_scan(struct shrinker *shrink, struct shrink_control *sc)
> +{
> +     struct drm_pagemap_shrinker *shrinker = shrink->private_data;
> +     struct drm_pagemap *dpagemap;
> +     struct drm_pagemap_cache *cache;
> +     unsigned long nr_freed = 0;
> +
> +     sc->nr_scanned = 0;
> +     spin_lock(&shrinker->lock);
> +     do {
> +             dpagemap = list_first_entry_or_null(&shrinker->dpagemaps, 
> typeof(*dpagemap),
> +                                                 shrink_link);
> +             if (!dpagemap)
> +                     break;
> +
> +             atomic_dec(&shrinker->num_dpagemaps);
> +             list_del_init(&dpagemap->shrink_link);
> +             spin_unlock(&shrinker->lock);
> +
> +             sc->nr_scanned++;
> +             nr_freed++;
> +
> +             cache = dpagemap->cache;
> +             spin_lock(&cache->lock);
> +             cache->dpagemap = NULL;
> +             spin_unlock(&cache->lock);
> +
> +             drm_dbg(dpagemap->drm, "Shrinking dpagemap %p.\n", dpagemap);
> +             drm_pagemap_destroy(dpagemap, true);
> +             spin_lock(&shrinker->lock);
> +     } while (sc->nr_scanned < sc->nr_to_scan);
> +     spin_unlock(&shrinker->lock);
> +
> +     return sc->nr_scanned ? nr_freed : SHRINK_STOP;
> +}
> +
> +static void drm_pagemap_shrinker_fini(void *arg)
> +{
> +     struct drm_pagemap_shrinker *shrinker = arg;
> +
> +     drm_dbg(shrinker->drm, "Destroying dpagemap shrinker.\n");
> +     drm_WARN_ON(shrinker->drm, !!atomic_read(&shrinker->num_dpagemaps));
> +     shrinker_free(shrinker->shrink);
> +     kfree(shrinker);
> +}
> +
> +/**
> + * drm_pagemap_shrinker_create_devm() - Create and register a pagemap 
> shrinker
> + * @drm: The drm device
> + *
> + * Create and register a pagemap shrinker that shrinks unused pagemaps
> + * and thereby reduces memory footprint.
> + * The shrinker is drm_device managed and unregisters itself when
> + * the drm device is removed.
> + *
> + * Return: %0 on success, negative error code on failure.
> + */
> +struct drm_pagemap_shrinker *drm_pagemap_shrinker_create_devm(struct 
> drm_device *drm)
> +{
> +     struct drm_pagemap_shrinker *shrinker;
> +     struct shrinker *shrink;
> +     int err;
> +
> +     shrinker = kzalloc(sizeof(*shrinker), GFP_KERNEL);
> +     if (!shrinker)
> +             return ERR_PTR(-ENOMEM);
> +
> +     shrink = shrinker_alloc(0, "drm-drm_pagemap:%s", drm->unique);
> +     if (!shrink) {
> +             kfree(shrinker);
> +             return ERR_PTR(-ENOMEM);
> +     }
> +
> +     spin_lock_init(&shrinker->lock);
> +     INIT_LIST_HEAD(&shrinker->dpagemaps);
> +     shrinker->drm = drm;
> +     shrinker->shrink = shrink;
> +     shrink->count_objects = drm_pagemap_shrinker_count;
> +     shrink->scan_objects = drm_pagemap_shrinker_scan;
> +     shrink->private_data = shrinker;
> +     shrinker_register(shrink);
> +
> +     err = devm_add_action_or_reset(drm->dev, drm_pagemap_shrinker_fini, 
> shrinker);
> +     if (err)
> +             return ERR_PTR(err);
> +
> +     return shrinker;
> +}
> +EXPORT_SYMBOL(drm_pagemap_shrinker_create_devm);
> diff --git a/include/drm/drm_pagemap.h b/include/drm/drm_pagemap.h
> index 5cfe54331ba7..4b9af5e785c6 100644
> --- a/include/drm/drm_pagemap.h
> +++ b/include/drm/drm_pagemap.h
> @@ -9,6 +9,7 @@
>  #define NR_PAGES(order) (1U << (order))
>  
>  struct drm_pagemap;
> +struct drm_pagemap_cache;
>  struct drm_pagemap_dev_hold;
>  struct drm_pagemap_zdd;
>  struct device;
> @@ -124,6 +125,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);
>  };
>  
>  /**
> @@ -135,6 +155,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;
> @@ -142,6 +166,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;
> @@ -210,6 +236,11 @@ struct drm_pagemap_devmem_ops {
>                          unsigned long npages);
>  };
>  
> +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);
> @@ -228,9 +259,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)
> @@ -241,6 +272,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
>   *
> @@ -284,5 +329,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
> diff --git a/include/drm/drm_pagemap_util.h b/include/drm/drm_pagemap_util.h
> new file mode 100644
> index 000000000000..924244d5b899
> --- /dev/null
> +++ b/include/drm/drm_pagemap_util.h
> @@ -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
> -- 
> 2.51.1
> 

Reply via email to