mirror of
git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2026-03-22 07:27:12 +08:00
For the sake of consistency, ensure that the cache lock is always
unlocked after drm_pagemap_cache_fini. Spinlocks typically disable
preemption and if the code-path missing the unlock is hit, preemption
will remain disabled even if the lock is subsequently freed.
Fixes static analysis issue.
v2:
- Use requested code flow (Maarten)
v3:
- Clear cache->dpagemap (Matt Brost, Maarten)
v4:
- Reword commit message (Thomas)
Fixes: 77f14f2f2d ("drm/pagemap: Add a drm_pagemap cache and shrinker")
Signed-off-by: Jonathan Cavitt <jonathan.cavitt@intel.com>
Reviewed-by: Thomas Hellström <thomas.hellstrom@linux.intel.com>
Reviewed-by: Maarten Lankhorst <maarten.lankhorst@linux.intel.com>
Cc: Thomas Hellstrom <thomas.hellstrom@linux.intel.com>
Cc: Matthew Brost <matthew.brost@intel.com>
Cc: Maarten Lankhorst <maarten.lankhorst@linux.intel.com>
Signed-off-by: Thomas Hellström <thomas.hellstrom@linux.intel.com>
Link: https://patch.msgid.link/20260316151555.7553-2-jonathan.cavitt@intel.com
565 lines
16 KiB
C
565 lines
16 KiB
C
// 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);
|