Commit 1a961ce0 authored by Luca Barbieri's avatar Luca Barbieri Committed by Dave Airlie

drm/ttm: Fix race condition in ttm_bo_delayed_delete (v3, final)

Resending this with Thomas Hellstrom's signoff for merging into 2.6.33

ttm_bo_delayed_delete has a race condition, because after we do:
kref_put(&nentry->list_kref, ttm_bo_release_list);

we are not holding the list lock and not holding any reference to
objects, and thus every bo in the list can be removed and freed at
this point.

However, we then use the next pointer we stored, which is not guaranteed
to be valid.

This was apparently the cause of some Nouveau oopses I experienced.

This patch rewrites the function so that it keeps the reference to nentry
until nentry itself is freed and we already got a reference to nentry->next.

v2 updated by me according to Thomas Hellstrom's feedback.
v3 proposed by Thomas Hellstrom. Commit comment updated by me.

Both updates fixed minor efficiency/style issues only and all three versions
should be correct.
Signed-off-by: default avatarLuca Barbieri <luca@luca-barbieri.com>
Signed-off-by: default avatarThomas Hellstrom <thellstrom@vmware.com>
Signed-off-by: default avatarDave Airlie <airlied@redhat.com>
parent 8471a26b
...@@ -524,52 +524,44 @@ static int ttm_bo_cleanup_refs(struct ttm_buffer_object *bo, bool remove_all) ...@@ -524,52 +524,44 @@ static int ttm_bo_cleanup_refs(struct ttm_buffer_object *bo, bool remove_all)
static int ttm_bo_delayed_delete(struct ttm_bo_device *bdev, bool remove_all) static int ttm_bo_delayed_delete(struct ttm_bo_device *bdev, bool remove_all)
{ {
struct ttm_bo_global *glob = bdev->glob; struct ttm_bo_global *glob = bdev->glob;
struct ttm_buffer_object *entry, *nentry; struct ttm_buffer_object *entry = NULL;
struct list_head *list, *next; int ret = 0;
int ret;
spin_lock(&glob->lru_lock); spin_lock(&glob->lru_lock);
list_for_each_safe(list, next, &bdev->ddestroy) { if (list_empty(&bdev->ddestroy))
entry = list_entry(list, struct ttm_buffer_object, ddestroy); goto out_unlock;
nentry = NULL;
/* entry = list_first_entry(&bdev->ddestroy,
* Protect the next list entry from destruction while we struct ttm_buffer_object, ddestroy);
* unlock the lru_lock. kref_get(&entry->list_kref);
*/
for (;;) {
struct ttm_buffer_object *nentry = NULL;
if (next != &bdev->ddestroy) { if (entry->ddestroy.next != &bdev->ddestroy) {
nentry = list_entry(next, struct ttm_buffer_object, nentry = list_first_entry(&entry->ddestroy,
ddestroy); struct ttm_buffer_object, ddestroy);
kref_get(&nentry->list_kref); kref_get(&nentry->list_kref);
} }
kref_get(&entry->list_kref);
spin_unlock(&glob->lru_lock); spin_unlock(&glob->lru_lock);
ret = ttm_bo_cleanup_refs(entry, remove_all); ret = ttm_bo_cleanup_refs(entry, remove_all);
kref_put(&entry->list_kref, ttm_bo_release_list); kref_put(&entry->list_kref, ttm_bo_release_list);
entry = nentry;
spin_lock(&glob->lru_lock); if (ret || !entry)
if (nentry) { goto out;
bool next_onlist = !list_empty(next);
spin_unlock(&glob->lru_lock);
kref_put(&nentry->list_kref, ttm_bo_release_list);
spin_lock(&glob->lru_lock);
/*
* Someone might have raced us and removed the
* next entry from the list. We don't bother restarting
* list traversal.
*/
if (!next_onlist) spin_lock(&glob->lru_lock);
break; if (list_empty(&entry->ddestroy))
}
if (ret)
break; break;
} }
ret = !list_empty(&bdev->ddestroy);
spin_unlock(&glob->lru_lock);
out_unlock:
spin_unlock(&glob->lru_lock);
out:
if (entry)
kref_put(&entry->list_kref, ttm_bo_release_list);
return ret; return ret;
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment