| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564 |
- // SPDX-License-Identifier: GPL-2.0-only OR MIT
- /*
- * Copyright © 2025 Intel Corporation
- */
- #include <linux/slab.h>
- #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;
- cache->dpagemap = NULL;
- if (dpagemap && !drm_pagemap_shrinker_cancel(dpagemap))
- dpagemap = NULL;
- spin_unlock(&cache->lock);
- if (dpagemap)
- drm_pagemap_destroy(dpagemap, false);
- 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_obj(*cache);
- 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_obj(*shrinker);
- 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);
- /**
- * struct drm_pagemap_owner - Device interconnect group
- * @kref: Reference count.
- *
- * A struct drm_pagemap_owner identifies a device interconnect group.
- */
- struct drm_pagemap_owner {
- struct kref kref;
- };
- static void drm_pagemap_owner_release(struct kref *kref)
- {
- kfree(container_of(kref, struct drm_pagemap_owner, kref));
- }
- /**
- * drm_pagemap_release_owner() - Stop participating in an interconnect group
- * @peer: Pointer to the struct drm_pagemap_peer used when joining the group
- *
- * Stop participating in an interconnect group. This function is typically
- * called when a pagemap is removed to indicate that it doesn't need to
- * be taken into account.
- */
- void drm_pagemap_release_owner(struct drm_pagemap_peer *peer)
- {
- struct drm_pagemap_owner_list *owner_list = peer->list;
- if (!owner_list)
- return;
- mutex_lock(&owner_list->lock);
- list_del(&peer->link);
- kref_put(&peer->owner->kref, drm_pagemap_owner_release);
- peer->owner = NULL;
- mutex_unlock(&owner_list->lock);
- }
- EXPORT_SYMBOL(drm_pagemap_release_owner);
- /**
- * typedef interconnect_fn - Callback function to identify fast interconnects
- * @peer1: First endpoint.
- * @peer2: Second endpont.
- *
- * The function returns %true iff @peer1 and @peer2 have a fast interconnect.
- * Note that this is symmetrical. The function has no notion of client and provider,
- * which may not be sufficient in some cases. However, since the callback is intended
- * to guide in providing common pagemap owners, the notion of a common owner to
- * indicate fast interconnects would then have to change as well.
- *
- * Return: %true iff @peer1 and @peer2 have a fast interconnect. Otherwise @false.
- */
- typedef bool (*interconnect_fn)(struct drm_pagemap_peer *peer1, struct drm_pagemap_peer *peer2);
- /**
- * drm_pagemap_acquire_owner() - Join an interconnect group
- * @peer: A struct drm_pagemap_peer keeping track of the device interconnect
- * @owner_list: Pointer to the owner_list, keeping track of all interconnects
- * @has_interconnect: Callback function to determine whether two peers have a
- * fast local interconnect.
- *
- * Repeatedly calls @has_interconnect for @peer and other peers on @owner_list to
- * determine a set of peers for which @peer has a fast interconnect. That set will
- * have common &struct drm_pagemap_owner, and upon successful return, @peer::owner
- * will point to that struct, holding a reference, and @peer will be registered in
- * @owner_list. If @peer doesn't have any fast interconnects to other @peers, a
- * new unique &struct drm_pagemap_owner will be allocated for it, and that
- * may be shared with other peers that, at a later point, are determined to have
- * a fast interconnect with @peer.
- *
- * When @peer no longer participates in an interconnect group,
- * drm_pagemap_release_owner() should be called to drop the reference on the
- * struct drm_pagemap_owner.
- *
- * Return: %0 on success, negative error code on failure.
- */
- int drm_pagemap_acquire_owner(struct drm_pagemap_peer *peer,
- struct drm_pagemap_owner_list *owner_list,
- interconnect_fn has_interconnect)
- {
- struct drm_pagemap_peer *cur_peer;
- struct drm_pagemap_owner *owner = NULL;
- bool interconnect = false;
- mutex_lock(&owner_list->lock);
- might_alloc(GFP_KERNEL);
- list_for_each_entry(cur_peer, &owner_list->peers, link) {
- if (cur_peer->owner != owner) {
- if (owner && interconnect)
- break;
- owner = cur_peer->owner;
- interconnect = true;
- }
- if (interconnect && !has_interconnect(peer, cur_peer))
- interconnect = false;
- }
- if (!interconnect) {
- owner = kmalloc_obj(*owner);
- if (!owner) {
- mutex_unlock(&owner_list->lock);
- return -ENOMEM;
- }
- kref_init(&owner->kref);
- list_add_tail(&peer->link, &owner_list->peers);
- } else {
- kref_get(&owner->kref);
- list_add_tail(&peer->link, &cur_peer->link);
- }
- peer->owner = owner;
- peer->list = owner_list;
- mutex_unlock(&owner_list->lock);
- return 0;
- }
- EXPORT_SYMBOL(drm_pagemap_acquire_owner);
|