msm: kgsl: fix race condition setting the event->handle

A race condition existed for setting the event->handle since the
async event can signal at any time after it has been registered.
When the event signals the reference count for the synclist and
the async callback are decremented causing the event to be freed.
The kgsl_cmdbatch_add_sync_fence function needs to take another
reference to ensure that the handle can be set before the event
is freed.

Change-Id: I0d8a02246e42dce014661d6f14c685415f1704cd
Signed-off-by: Jeff Boody <jboody@codeaurora.org>
This commit is contained in:
Jeff Boody 2013-11-07 11:28:06 -07:00 committed by Iliyan Malchev
parent 1b3aadfe27
commit 9c7966d444

View file

@ -1654,26 +1654,30 @@ static int kgsl_cmdbatch_add_sync_fence(struct kgsl_device *device,
event->context = NULL;
/*
* Two krefs are required to support events. The first kref is for
* the synclist which holds the event in the cmdbatch. The second
* kref is for the callback which can be asynchronous and be called
* after kgsl_cmdbatch_destroy. The kref should be put when the event
* is removed from the synclist, if the callback is successfully
* canceled or when the callback is signaled.
* Initial kref is to ensure async callback does not free the
* event before this function sets the event handle
*/
kref_init(&event->refcount);
kref_get(&event->refcount);
/*
* Add it to the list first to account for the possiblity that the
* callback will happen immediately after the call to
* kgsl_sync_fence_async_wait
* kgsl_sync_fence_async_wait. Decrement the event refcount when
* removing from the synclist.
*/
spin_lock(&cmdbatch->lock);
kref_get(&event->refcount);
list_add(&event->node, &cmdbatch->synclist);
spin_unlock(&cmdbatch->lock);
/*
* Increment the reference count for the async callback.
* Decrement when the callback is successfully canceled, when
* the callback is signaled or if the async wait fails.
*/
kref_get(&event->refcount);
event->handle = kgsl_sync_fence_async_wait(sync->fd,
kgsl_cmdbatch_sync_fence_func, event);
@ -1681,16 +1685,27 @@ static int kgsl_cmdbatch_add_sync_fence(struct kgsl_device *device,
if (IS_ERR_OR_NULL(event->handle)) {
int ret = PTR_ERR(event->handle);
/* Failed to add the event to the async callback */
kgsl_cmdbatch_sync_event_put(event);
/* Remove event from the synclist */
spin_lock(&cmdbatch->lock);
list_del(&event->node);
kgsl_cmdbatch_sync_event_put(event);
spin_unlock(&cmdbatch->lock);
kgsl_cmdbatch_put(cmdbatch);
kfree(event);
/* Event no longer needed by this function */
kgsl_cmdbatch_sync_event_put(event);
return ret;
}
/*
* Event was successfully added to the synclist, the async
* callback and handle to cancel event has been set.
*/
kgsl_cmdbatch_sync_event_put(event);
return 0;
}