Skip to content

Commit 0eafec6

Browse files
committed
drm/i915: Enable lockless lookup of request tracking via RCU
If we enable RCU for the requests (providing a grace period where we can inspect a "dead" request before it is freed), we can allow callers to carefully perform lockless lookup of an active request. However, by enabling deferred freeing of requests, we can potentially hog a lot of memory when dealing with tens of thousands of requests per second - with a quick insertion of a synchronize_rcu() inside our shrinker callback, that issue disappears. v2: Currently, it is our responsibility to handle reclaim i.e. to avoid hogging memory with the delayed slab frees. At the moment, we wait for a grace period in the shrinker, and block for all RCU callbacks on oom. Suggested alternatives focus on flushing our RCU callback when we have a certain number of outstanding request frees, and blocking on that flush after a second high watermark. (So rather than wait for the system to run out of memory, we stop issuing requests - both are nondeterministic.) Paul E. McKenney wrote: Another approach is synchronize_rcu() after some largish number of requests. The advantage of this approach is that it throttles the production of callbacks at the source. The corresponding disadvantage is that it slows things up. Another approach is to use call_rcu(), but if the previous call_rcu() is still in flight, block waiting for it. Yet another approach is the get_state_synchronize_rcu() / cond_synchronize_rcu() pair. The idea is to do something like this: cond_synchronize_rcu(cookie); cookie = get_state_synchronize_rcu(); You would of course do an initial get_state_synchronize_rcu() to get things going. This would not block unless there was less than one grace period's worth of time between invocations. But this assumes a busy system, where there is almost always a grace period in flight. But you can make that happen as follows: cond_synchronize_rcu(cookie); cookie = get_state_synchronize_rcu(); call_rcu(&my_rcu_head, noop_function); Note that you need additional code to make sure that the old callback has completed before doing a new one. Setting and clearing a flag with appropriate memory ordering control suffices (e.g,. smp_load_acquire() and smp_store_release()). v3: More comments on compiler and processor order of operations within the RCU lookup and discover we can use rcu_access_pointer() here instead. v4: Wrap i915_gem_active_get_rcu() to take the rcu_read_lock itself. Signed-off-by: Chris Wilson <[email protected]> Cc: Maarten Lankhorst <[email protected]> Cc: "Goel, Akash" <[email protected]> Cc: Josh Triplett <[email protected]> Cc: Daniel Vetter <[email protected]> Reviewed-by: Daniel Vetter <[email protected]> Link: http://patchwork.freedesktop.org/patch/msgid/[email protected]
1 parent 00e60f2 commit 0eafec6

File tree

4 files changed

+174
-13
lines changed

4 files changed

+174
-13
lines changed

drivers/gpu/drm/i915/i915_gem.c

+6-1
Original file line numberDiff line numberDiff line change
@@ -4431,7 +4431,9 @@ i915_gem_load_init(struct drm_device *dev)
44314431
dev_priv->requests =
44324432
kmem_cache_create("i915_gem_request",
44334433
sizeof(struct drm_i915_gem_request), 0,
4434-
SLAB_HWCACHE_ALIGN,
4434+
SLAB_HWCACHE_ALIGN |
4435+
SLAB_RECLAIM_ACCOUNT |
4436+
SLAB_DESTROY_BY_RCU,
44354437
NULL);
44364438

44374439
INIT_LIST_HEAD(&dev_priv->context_list);
@@ -4467,6 +4469,9 @@ void i915_gem_load_cleanup(struct drm_device *dev)
44674469
kmem_cache_destroy(dev_priv->requests);
44684470
kmem_cache_destroy(dev_priv->vmas);
44694471
kmem_cache_destroy(dev_priv->objects);
4472+
4473+
/* And ensure that our DESTROY_BY_RCU slabs are truly destroyed */
4474+
rcu_barrier();
44704475
}
44714476

44724477
int i915_gem_freeze_late(struct drm_i915_private *dev_priv)

drivers/gpu/drm/i915/i915_gem_request.c

+1-1
Original file line numberDiff line numberDiff line change
@@ -205,7 +205,7 @@ static void i915_gem_request_retire(struct drm_i915_gem_request *request)
205205
prefetchw(next);
206206

207207
INIT_LIST_HEAD(&active->link);
208-
active->request = NULL;
208+
RCU_INIT_POINTER(active->request, NULL);
209209

210210
active->retire(active, request);
211211
}

drivers/gpu/drm/i915/i915_gem_request.h

+156-7
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,12 @@ i915_gem_request_get(struct drm_i915_gem_request *req)
183183
return to_request(fence_get(&req->fence));
184184
}
185185

186+
static inline struct drm_i915_gem_request *
187+
i915_gem_request_get_rcu(struct drm_i915_gem_request *req)
188+
{
189+
return to_request(fence_get_rcu(&req->fence));
190+
}
191+
186192
static inline void
187193
i915_gem_request_put(struct drm_i915_gem_request *req)
188194
{
@@ -286,7 +292,7 @@ typedef void (*i915_gem_retire_fn)(struct i915_gem_active *,
286292
struct drm_i915_gem_request *);
287293

288294
struct i915_gem_active {
289-
struct drm_i915_gem_request *request;
295+
struct drm_i915_gem_request __rcu *request;
290296
struct list_head link;
291297
i915_gem_retire_fn retire;
292298
};
@@ -327,13 +333,19 @@ i915_gem_active_set(struct i915_gem_active *active,
327333
struct drm_i915_gem_request *request)
328334
{
329335
list_move(&active->link, &request->active_list);
330-
active->request = request;
336+
rcu_assign_pointer(active->request, request);
331337
}
332338

333339
static inline struct drm_i915_gem_request *
334340
__i915_gem_active_peek(const struct i915_gem_active *active)
335341
{
336-
return active->request;
342+
/* Inside the error capture (running with the driver in an unknown
343+
* state), we want to bend the rules slightly (a lot).
344+
*
345+
* Work is in progress to make it safer, in the meantime this keeps
346+
* the known issue from spamming the logs.
347+
*/
348+
return rcu_dereference_protected(active->request, 1);
337349
}
338350

339351
/**
@@ -349,7 +361,29 @@ i915_gem_active_peek(const struct i915_gem_active *active, struct mutex *mutex)
349361
{
350362
struct drm_i915_gem_request *request;
351363

352-
request = active->request;
364+
request = rcu_dereference_protected(active->request,
365+
lockdep_is_held(mutex));
366+
if (!request || i915_gem_request_completed(request))
367+
return NULL;
368+
369+
return request;
370+
}
371+
372+
/**
373+
* i915_gem_active_peek_rcu - report the active request being monitored
374+
* @active - the active tracker
375+
*
376+
* i915_gem_active_peek_rcu() returns the current request being tracked if
377+
* still active, or NULL. It does not obtain a reference on the request
378+
* for the caller, and inspection of the request is only valid under
379+
* the RCU lock.
380+
*/
381+
static inline struct drm_i915_gem_request *
382+
i915_gem_active_peek_rcu(const struct i915_gem_active *active)
383+
{
384+
struct drm_i915_gem_request *request;
385+
386+
request = rcu_dereference(active->request);
353387
if (!request || i915_gem_request_completed(request))
354388
return NULL;
355389

@@ -369,6 +403,119 @@ i915_gem_active_get(const struct i915_gem_active *active, struct mutex *mutex)
369403
return i915_gem_request_get(i915_gem_active_peek(active, mutex));
370404
}
371405

406+
/**
407+
* __i915_gem_active_get_rcu - return a reference to the active request
408+
* @active - the active tracker
409+
*
410+
* __i915_gem_active_get() returns a reference to the active request, or NULL
411+
* if the active tracker is idle. The caller must hold the RCU read lock, but
412+
* the returned pointer is safe to use outside of RCU.
413+
*/
414+
static inline struct drm_i915_gem_request *
415+
__i915_gem_active_get_rcu(const struct i915_gem_active *active)
416+
{
417+
/* Performing a lockless retrieval of the active request is super
418+
* tricky. SLAB_DESTROY_BY_RCU merely guarantees that the backing
419+
* slab of request objects will not be freed whilst we hold the
420+
* RCU read lock. It does not guarantee that the request itself
421+
* will not be freed and then *reused*. Viz,
422+
*
423+
* Thread A Thread B
424+
*
425+
* req = active.request
426+
* retire(req) -> free(req);
427+
* (req is now first on the slab freelist)
428+
* active.request = NULL
429+
*
430+
* req = new submission on a new object
431+
* ref(req)
432+
*
433+
* To prevent the request from being reused whilst the caller
434+
* uses it, we take a reference like normal. Whilst acquiring
435+
* the reference we check that it is not in a destroyed state
436+
* (refcnt == 0). That prevents the request being reallocated
437+
* whilst the caller holds on to it. To check that the request
438+
* was not reallocated as we acquired the reference we have to
439+
* check that our request remains the active request across
440+
* the lookup, in the same manner as a seqlock. The visibility
441+
* of the pointer versus the reference counting is controlled
442+
* by using RCU barriers (rcu_dereference and rcu_assign_pointer).
443+
*
444+
* In the middle of all that, we inspect whether the request is
445+
* complete. Retiring is lazy so the request may be completed long
446+
* before the active tracker is updated. Querying whether the
447+
* request is complete is far cheaper (as it involves no locked
448+
* instructions setting cachelines to exclusive) than acquiring
449+
* the reference, so we do it first. The RCU read lock ensures the
450+
* pointer dereference is valid, but does not ensure that the
451+
* seqno nor HWS is the right one! However, if the request was
452+
* reallocated, that means the active tracker's request was complete.
453+
* If the new request is also complete, then both are and we can
454+
* just report the active tracker is idle. If the new request is
455+
* incomplete, then we acquire a reference on it and check that
456+
* it remained the active request.
457+
*/
458+
do {
459+
struct drm_i915_gem_request *request;
460+
461+
request = rcu_dereference(active->request);
462+
if (!request || i915_gem_request_completed(request))
463+
return NULL;
464+
465+
request = i915_gem_request_get_rcu(request);
466+
467+
/* What stops the following rcu_access_pointer() from occurring
468+
* before the above i915_gem_request_get_rcu()? If we were
469+
* to read the value before pausing to get the reference to
470+
* the request, we may not notice a change in the active
471+
* tracker.
472+
*
473+
* The rcu_access_pointer() is a mere compiler barrier, which
474+
* means both the CPU and compiler are free to perform the
475+
* memory read without constraint. The compiler only has to
476+
* ensure that any operations after the rcu_access_pointer()
477+
* occur afterwards in program order. This means the read may
478+
* be performed earlier by an out-of-order CPU, or adventurous
479+
* compiler.
480+
*
481+
* The atomic operation at the heart of
482+
* i915_gem_request_get_rcu(), see fence_get_rcu(), is
483+
* atomic_inc_not_zero() which is only a full memory barrier
484+
* when successful. That is, if i915_gem_request_get_rcu()
485+
* returns the request (and so with the reference counted
486+
* incremented) then the following read for rcu_access_pointer()
487+
* must occur after the atomic operation and so confirm
488+
* that this request is the one currently being tracked.
489+
*/
490+
if (!request || request == rcu_access_pointer(active->request))
491+
return rcu_pointer_handoff(request);
492+
493+
i915_gem_request_put(request);
494+
} while (1);
495+
}
496+
497+
/**
498+
* i915_gem_active_get_unlocked - return a reference to the active request
499+
* @active - the active tracker
500+
*
501+
* i915_gem_active_get_unlocked() returns a reference to the active request,
502+
* or NULL if the active tracker is idle. The reference is obtained under RCU,
503+
* so no locking is required by the caller.
504+
*
505+
* The reference should be freed with i915_gem_request_put().
506+
*/
507+
static inline struct drm_i915_gem_request *
508+
i915_gem_active_get_unlocked(const struct i915_gem_active *active)
509+
{
510+
struct drm_i915_gem_request *request;
511+
512+
rcu_read_lock();
513+
request = __i915_gem_active_get_rcu(active);
514+
rcu_read_unlock();
515+
516+
return request;
517+
}
518+
372519
/**
373520
* i915_gem_active_isset - report whether the active tracker is assigned
374521
* @active - the active tracker
@@ -380,7 +527,7 @@ i915_gem_active_get(const struct i915_gem_active *active, struct mutex *mutex)
380527
static inline bool
381528
i915_gem_active_isset(const struct i915_gem_active *active)
382529
{
383-
return active->request;
530+
return rcu_access_pointer(active->request);
384531
}
385532

386533
/**
@@ -437,7 +584,8 @@ i915_gem_active_retire(struct i915_gem_active *active,
437584
struct drm_i915_gem_request *request;
438585
int ret;
439586

440-
request = active->request;
587+
request = rcu_dereference_protected(active->request,
588+
lockdep_is_held(mutex));
441589
if (!request)
442590
return 0;
443591

@@ -446,7 +594,8 @@ i915_gem_active_retire(struct i915_gem_active *active,
446594
return ret;
447595

448596
list_del_init(&active->link);
449-
active->request = NULL;
597+
RCU_INIT_POINTER(active->request, NULL);
598+
450599
active->retire(active, request);
451600

452601
return 0;

drivers/gpu/drm/i915/i915_gem_shrinker.c

+11-4
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,8 @@ i915_gem_shrink(struct drm_i915_private *dev_priv,
205205
intel_runtime_pm_put(dev_priv);
206206

207207
i915_gem_retire_requests(dev_priv);
208+
/* expedite the RCU grace period to free some request slabs */
209+
synchronize_rcu_expedited();
208210

209211
return count;
210212
}
@@ -225,10 +227,15 @@ i915_gem_shrink(struct drm_i915_private *dev_priv,
225227
*/
226228
unsigned long i915_gem_shrink_all(struct drm_i915_private *dev_priv)
227229
{
228-
return i915_gem_shrink(dev_priv, -1UL,
229-
I915_SHRINK_BOUND |
230-
I915_SHRINK_UNBOUND |
231-
I915_SHRINK_ACTIVE);
230+
unsigned long freed;
231+
232+
freed = i915_gem_shrink(dev_priv, -1UL,
233+
I915_SHRINK_BOUND |
234+
I915_SHRINK_UNBOUND |
235+
I915_SHRINK_ACTIVE);
236+
rcu_barrier(); /* wait until our RCU delayed slab frees are completed */
237+
238+
return freed;
232239
}
233240

234241
static bool i915_gem_shrinker_lock(struct drm_device *dev, bool *unlock)

0 commit comments

Comments
 (0)