Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
228 changes: 199 additions & 29 deletions drivers/gpu/drm/ttm/ttm_bo.c
Original file line number Diff line number Diff line change
Expand Up @@ -489,6 +489,119 @@ int ttm_bo_evict_first(struct ttm_device *bdev, struct ttm_resource_manager *man
return ret;
}

struct ttm_bo_alloc_state {
/** @charge_pool: The memory pool the resource is charged to */
struct dmem_cgroup_pool_state *charge_pool;
/** @limit_pool: Which pool limit we should test against */
struct dmem_cgroup_pool_state *limit_pool;
/** @in_evict: Whether we are currently evicting buffers */
bool in_evict;
/** @may_try_low: If only unprotected BOs, i.e. BOs whose cgroup
* is exceeding its dmem low/min protection, should be considered for eviction
*/
bool may_try_low;
};

/**
* ttm_bo_alloc_at_place - Attempt allocating a BO's backing store in a place
*
* @bo: The buffer to allocate the backing store of
* @place: The place to attempt allocation in
* @ctx: ttm_operation_ctx associated with this allocation
* @force_space: If we should evict buffers to force space
* @res: On allocation success, the resulting struct ttm_resource.
* @alloc_state: Object holding allocation state such as charged cgroups.
*
* Returns:
* -EBUSY: No space available, but allocation should be retried with ttm_bo_evict_alloc.
* -ENOSPC: No space available, allocation should not be retried.
* -ERESTARTSYS: An interruptible sleep was interrupted by a signal.
*
*/
static int ttm_bo_alloc_at_place(struct ttm_buffer_object *bo,
const struct ttm_place *place,
struct ttm_operation_ctx *ctx,
bool force_space,
struct ttm_resource **res,
struct ttm_bo_alloc_state *alloc_state)
{
bool may_evict;
int ret;

may_evict = !alloc_state->in_evict && force_space &&
place->mem_type != TTM_PL_SYSTEM;

if (!alloc_state->charge_pool) {
ret = ttm_resource_try_charge(bo, place, &alloc_state->charge_pool,
force_space ? &alloc_state->limit_pool
: NULL);
if (ret) {
/*
* -EAGAIN means the charge failed, which we treat
* like an allocation failure. Therefore, return an
* error code indicating the allocation failed -
* either -EBUSY if the allocation should be
* retried with eviction, or -ENOSPC if there should
* be no second attempt.
*/
if (ret == -EAGAIN)
ret = may_evict ? -EBUSY : -ENOSPC;
return ret;
}
}

/*
* cgroup protection plays a special role in eviction.
* Conceptually, protection of memory via the dmem cgroup controller
* entitles the protected cgroup to use a certain amount of memory.
* There are two types of protection - the 'low' limit is a
* "best-effort" protection, whereas the 'min' limit provides a hard
* guarantee that memory within the cgroup's allowance will not be
* evicted under any circumstance.
*
* To faithfully model this concept in TTM, we also need to take cgroup
* protection into account when allocating. When allocation in one
* place fails, TTM will default to trying other places first before
* evicting.
* If the allocation is covered by dmem cgroup protection, however,
* this prevents the allocation from using the memory it is "entitled"
* to. To make sure unprotected allocations cannot push new protected
* allocations out of places they are "entitled" to use, we should
* evict buffers not covered by any cgroup protection, if this
* allocation is covered by cgroup protection.
*
* Buffers covered by 'min' protection are a special case - the 'min'
* limit is a stronger guarantee than 'low', and thus buffers protected
* by 'low' but not 'min' should also be considered for eviction.
* Buffers protected by 'min' will never be considered for eviction
* anyway, so the regular eviction path should be triggered here.
* Buffers protected by 'low' but not 'min' will take a special
* eviction path that only evicts buffers covered by neither 'low' or
* 'min' protections.
*/
if (!alloc_state->in_evict) {
if (alloc_state->charge_pool) {
may_evict |= dmem_cgroup_below_min(NULL, alloc_state->charge_pool);
alloc_state->may_try_low = may_evict;

may_evict |= dmem_cgroup_below_low(NULL, alloc_state->charge_pool);
}

ret = ttm_resource_alloc(bo, place, res, alloc_state->charge_pool);
if (ret) {
if (ret == -ENOSPC && may_evict)
ret = -EBUSY;
return ret;
}

/*
* Ownership of charge_pool has been transferred to the TTM resource,
* don't make the caller think we still hold a reference to it.
*/
alloc_state->charge_pool = NULL;
return 0;
}

/**
* struct ttm_bo_evict_walk - Parameters for the evict walk.
*/
Expand All @@ -504,22 +617,66 @@ struct ttm_bo_evict_walk {
/** @evicted: Number of successful evictions. */
unsigned long evicted;

/** @limit_pool: Which pool limit we should test against */
struct dmem_cgroup_pool_state *limit_pool;
/** @try_low: Whether we should attempt to evict BO's with low watermark threshold */
bool try_low;
/** @hit_low: If we cannot evict a bo when @try_low is false (first pass) */
bool hit_low;

/** @alloc_state: State associated with the allocation attempt. */
struct ttm_bo_alloc_state *alloc_state;
};

static s64 ttm_bo_evict_cb(struct ttm_lru_walk *walk, struct ttm_buffer_object *bo)
{
struct ttm_bo_evict_walk *evict_walk =
container_of(walk, typeof(*evict_walk), walk);
struct dmem_cgroup_pool_state *limit_pool, *ancestor = NULL;
bool evict_valuable;
s64 lret;

if (!dmem_cgroup_state_evict_valuable(evict_walk->limit_pool, bo->resource->css,
evict_walk->try_low, &evict_walk->hit_low))
/*
* If may_try_low is not set, then we're trying to evict unprotected
* buffers in favor of a protected allocation for charge_pool. Explicitly skip
* buffers belonging to the same cgroup here - that cgroup is definitely protected,
* even though dmem_cgroup_state_evict_valuable would allow the eviction because a
* cgroup is always allowed to evict from itself even if it is protected.
*/
if (evict_walk->alloc_state->charge_pool &&
!evict_walk->alloc_state->may_try_low &&
bo->resource->css == evict_walk->alloc_state->charge_pool)
return 0;

limit_pool = evict_walk->alloc_state->limit_pool;
/*
* If there is no explicit limit pool, find the root of the shared subtree between
* evictor and evictee. This is important so that recursive protection rules can
* apply properly: Recursive protection distributes cgroup protection afforded
* to a parent cgroup but not used explicitly by a child cgroup between all child
* cgroups (see docs of effective_protection in mm/page_counter.c). However, when
* direct siblings compete for memory, siblings that were explicitly protected
* should get prioritized over siblings that weren't. This only happens correctly
* when the root of the shared subtree is passed to
* dmem_cgroup_state_evict_valuable. Otherwise, the effective-protection
* calculation cannot distinguish direct siblings from unrelated subtrees and the
* calculated protection ends up wrong.
*/
if (!limit_pool) {
ancestor = dmem_cgroup_get_common_ancestor(bo->resource->css,
evict_walk->alloc_state->charge_pool);
limit_pool = ancestor;
}

if (!bo->resource->css) {
evict_valuable = true;
} else {
evict_valuable = dmem_cgroup_state_evict_valuable(limit_pool, bo->resource->css,
evict_walk->try_low,
&evict_walk->hit_low);
}
if (ancestor)
dmem_cgroup_pool_state_put(ancestor);

if (!evict_valuable)
return 0;

if (bo->pin_count || !bo->bdev->funcs->eviction_valuable(bo, evict_walk->place))
Expand All @@ -538,8 +695,9 @@ static s64 ttm_bo_evict_cb(struct ttm_lru_walk *walk, struct ttm_buffer_object *

evict_walk->evicted++;
if (evict_walk->res)
lret = ttm_resource_alloc(evict_walk->evictor, evict_walk->place,
evict_walk->res, NULL);
lret = ttm_bo_alloc_at_place(evict_walk->evictor, evict_walk->place,
walk->arg.ctx, false, evict_walk->res,
evict_walk->alloc_state);
if (lret == 0)
return 1;
out:
Expand All @@ -561,7 +719,7 @@ static int ttm_bo_evict_alloc(struct ttm_device *bdev,
struct ttm_operation_ctx *ctx,
struct ww_acquire_ctx *ticket,
struct ttm_resource **res,
struct dmem_cgroup_pool_state *limit_pool)
struct ttm_bo_alloc_state *state)
{
struct ttm_bo_evict_walk evict_walk = {
.walk = {
Expand All @@ -574,15 +732,21 @@ static int ttm_bo_evict_alloc(struct ttm_device *bdev,
.place = place,
.evictor = evictor,
.res = res,
.limit_pool = limit_pool,
.alloc_state = state,
};
s64 lret;

state->in_evict = true;

evict_walk.walk.arg.trylock_only = true;
lret = ttm_lru_walk_for_evict(&evict_walk.walk, bdev, man, 1);

/* One more attempt if we hit low limit? */
if (!lret && evict_walk.hit_low) {
/* If we failed to find enough BOs to evict, but we skipped over
* some BOs because they were covered by dmem low protection, retry
* evicting these protected BOs too, except if we're told not to
* consider protected BOs at all.
*/
if (!lret && evict_walk.hit_low && state->may_try_low) {
evict_walk.try_low = true;
lret = ttm_lru_walk_for_evict(&evict_walk.walk, bdev, man, 1);
}
Expand All @@ -603,11 +767,13 @@ static int ttm_bo_evict_alloc(struct ttm_device *bdev,
} while (!lret && evict_walk.evicted);

/* We hit the low limit? Try once more */
if (!lret && evict_walk.hit_low && !evict_walk.try_low) {
if (!lret && evict_walk.hit_low && !evict_walk.try_low &&
state->may_try_low) {
evict_walk.try_low = true;
goto retry;
}
out:
state->in_evict = false;
if (lret < 0)
return lret;
if (lret == 0)
Expand Down Expand Up @@ -724,9 +890,8 @@ static int ttm_bo_alloc_resource(struct ttm_buffer_object *bo,

for (i = 0; i < placement->num_placement; ++i) {
const struct ttm_place *place = &placement->placement[i];
struct dmem_cgroup_pool_state *limit_pool = NULL;
struct ttm_bo_alloc_state alloc_state = {};
struct ttm_resource_manager *man;
bool may_evict;

man = ttm_manager_type(bdev, place->mem_type);
if (!man || !ttm_resource_manager_used(man))
Expand All @@ -736,25 +901,30 @@ static int ttm_bo_alloc_resource(struct ttm_buffer_object *bo,
TTM_PL_FLAG_FALLBACK))
continue;

may_evict = (force_space && place->mem_type != TTM_PL_SYSTEM);
ret = ttm_resource_alloc(bo, place, res, force_space ? &limit_pool : NULL);
if (ret) {
if (ret != -ENOSPC) {
dmem_cgroup_pool_state_put(limit_pool);
return ret;
}
if (!may_evict) {
dmem_cgroup_pool_state_put(limit_pool);
continue;
}
ret = ttm_bo_alloc_at_place(bo, place, ctx, force_space,
res, &alloc_state);

if (ret == -ENOSPC) {
dmem_cgroup_uncharge(alloc_state.charge_pool, bo->base.size);
dmem_cgroup_pool_state_put(alloc_state.limit_pool);
continue;
} else if (ret == -EBUSY) {
ret = ttm_bo_evict_alloc(bdev, man, place, bo, ctx,
ticket, res, limit_pool);
dmem_cgroup_pool_state_put(limit_pool);
if (ret == -EBUSY)
continue;
if (ret)
ticket, res, &alloc_state);

dmem_cgroup_pool_state_put(alloc_state.limit_pool);

if (ret) {
dmem_cgroup_uncharge(alloc_state.charge_pool,
bo->base.size);
if (ret == -EBUSY)
continue;
return ret;
}
} else if (ret) {
dmem_cgroup_uncharge(alloc_state.charge_pool, bo->base.size);
dmem_cgroup_pool_state_put(alloc_state.limit_pool);
return ret;
}

ret = ttm_bo_add_move_fence(bo, man, ctx->no_wait_gpu);
Expand Down
51 changes: 35 additions & 16 deletions drivers/gpu/drm/ttm/ttm_resource.c
Original file line number Diff line number Diff line change
Expand Up @@ -372,33 +372,52 @@ void ttm_resource_fini(struct ttm_resource_manager *man,
}
EXPORT_SYMBOL(ttm_resource_fini);

/**
* ttm_resource_try_charge - charge a resource manager's cgroup pool
* @bo: buffer for which an allocation should be charged
* @place: where the allocation is attempted to be placed
* @ret_pool: on charge success, the pool that was charged
* @ret_limit_pool: on charge failure, the pool responsible for the failure
*
* Should be used to charge cgroups before attempting resource allocation.
* When charging succeeds, the value of ret_pool should be passed to
* ttm_resource_alloc.
*
* Returns: 0 on charge success, negative errno on failure.
*/
int ttm_resource_try_charge(struct ttm_buffer_object *bo,
const struct ttm_place *place,
struct dmem_cgroup_pool_state **ret_pool,
struct dmem_cgroup_pool_state **ret_limit_pool)
{
struct ttm_resource_manager *man =
ttm_manager_type(bo->bdev, place->mem_type);

if (!man->cg) {
*ret_pool = NULL;
if (ret_limit_pool)
*ret_limit_pool = NULL;
return 0;
}

return dmem_cgroup_try_charge(man->cg, bo->base.size, ret_pool,
ret_limit_pool);
}

int ttm_resource_alloc(struct ttm_buffer_object *bo,
const struct ttm_place *place,
struct ttm_resource **res_ptr,
struct dmem_cgroup_pool_state **ret_limit_pool)
struct dmem_cgroup_pool_state *charge_pool)
{
struct ttm_resource_manager *man =
ttm_manager_type(bo->bdev, place->mem_type);
struct dmem_cgroup_pool_state *pool = NULL;
int ret;

if (man->cg) {
ret = dmem_cgroup_try_charge(man->cg, bo->base.size, &pool, ret_limit_pool);
if (ret) {
if (ret == -EAGAIN)
ret = -ENOSPC;
return ret;
}
}

ret = man->func->alloc(man, bo, place, res_ptr);
if (ret) {
if (pool)
dmem_cgroup_uncharge(pool, bo->base.size);
if (ret)
return ret;
}

(*res_ptr)->css = pool;
(*res_ptr)->css = charge_pool;

spin_lock(&bo->bdev->lru_lock);
ttm_resource_add_bulk_move(*res_ptr, bo);
Expand Down
6 changes: 5 additions & 1 deletion include/drm/ttm/ttm_resource.h
Original file line number Diff line number Diff line change
Expand Up @@ -442,10 +442,14 @@ void ttm_resource_init(struct ttm_buffer_object *bo,
void ttm_resource_fini(struct ttm_resource_manager *man,
struct ttm_resource *res);

int ttm_resource_try_charge(struct ttm_buffer_object *bo,
const struct ttm_place *place,
struct dmem_cgroup_pool_state **ret_pool,
struct dmem_cgroup_pool_state **ret_limit_pool);
int ttm_resource_alloc(struct ttm_buffer_object *bo,
const struct ttm_place *place,
struct ttm_resource **res,
struct dmem_cgroup_pool_state **ret_limit_pool);
struct dmem_cgroup_pool_state *charge_pool);
void ttm_resource_free(struct ttm_buffer_object *bo, struct ttm_resource **res);
bool ttm_resource_intersects(struct ttm_device *bdev,
struct ttm_resource *res,
Expand Down
Loading
Loading