msm: kgsl: fix a race condition when calling kref_put
Fix a race condition which can occur if a thread tries to acquire
a reference to a mem_entry or context while another thread has
already decremented the refcount to 0 and is in the process of
destroying said mem_entry or context.
CRs-Fixed: 635747
Change-Id: I6be64ca75f9cb12b03e870b9ca83588197c64e5e
Signed-off-by: Hareesh Gundu <hareeshg@codeaurora.org>
diff --git a/drivers/gpu/msm/kgsl.c b/drivers/gpu/msm/kgsl.c
old mode 100755
new mode 100644
index 9acac1a..2da190a
--- a/drivers/gpu/msm/kgsl.c
+++ b/drivers/gpu/msm/kgsl.c
@@ -1,4 +1,4 @@
-/* Copyright (c) 2008-2012, The Linux Foundation. All rights reserved.
+/* Copyright (c) 2008-2012,2014 The Linux Foundation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
@@ -1180,7 +1180,8 @@
entry = rb_entry(node, struct kgsl_mem_entry, node);
if (kgsl_gpuaddr_in_memdesc(&entry->memdesc, gpuaddr, size)) {
- kgsl_mem_entry_get(entry);
+ if (!kgsl_mem_entry_get(entry))
+ break;
spin_unlock(&private->mem_lock);
return entry;
}
@@ -1280,14 +1281,17 @@
static inline struct kgsl_mem_entry * __must_check
kgsl_sharedmem_find_id(struct kgsl_process_private *process, unsigned int id)
{
+ int result = 0;
struct kgsl_mem_entry *entry;
rcu_read_lock();
entry = idr_find(&process->mem_idr, id);
if (entry)
- kgsl_mem_entry_get(entry);
+ result = kgsl_mem_entry_get(entry);
rcu_read_unlock();
+ if (!result)
+ return NULL;
return entry;
}
@@ -2877,7 +2881,8 @@
static void kgsl_gpumem_vm_open(struct vm_area_struct *vma)
{
struct kgsl_mem_entry *entry = vma->vm_private_data;
- kgsl_mem_entry_get(entry);
+ if (!kgsl_mem_entry_get(entry))
+ vma->vm_private_data = NULL;
}
static int
@@ -2885,6 +2890,8 @@
{
struct kgsl_mem_entry *entry = vma->vm_private_data;
+ if (!entry)
+ return VM_FAULT_SIGBUS;
if (!entry->memdesc.ops || !entry->memdesc.ops->vmfault)
return VM_FAULT_SIGBUS;
@@ -2896,6 +2903,9 @@
{
struct kgsl_mem_entry *entry = vma->vm_private_data;
+ if (!entry)
+ return;
+
entry->memdesc.useraddr = 0;
kgsl_mem_entry_put(entry);
}
diff --git a/drivers/gpu/msm/kgsl.h b/drivers/gpu/msm/kgsl.h
index 17e6fa3..ee76ab8 100644
--- a/drivers/gpu/msm/kgsl.h
+++ b/drivers/gpu/msm/kgsl.h
@@ -1,4 +1,4 @@
-/* Copyright (c) 2008-2013, The Linux Foundation. All rights reserved.
+/* Copyright (c) 2008-2014, The Linux Foundation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
@@ -313,10 +313,10 @@
return ((a > b) && (a - b <= KGSL_TIMESTAMP_WINDOW)) ? 1 : -1;
}
-static inline void
+static inline int
kgsl_mem_entry_get(struct kgsl_mem_entry *entry)
{
- kref_get(&entry->refcount);
+ return kref_get_unless_zero(&entry->refcount);
}
static inline void
diff --git a/drivers/gpu/msm/kgsl_device.h b/drivers/gpu/msm/kgsl_device.h
index 71de4be..140a9a6 100644
--- a/drivers/gpu/msm/kgsl_device.h
+++ b/drivers/gpu/msm/kgsl_device.h
@@ -1,4 +1,4 @@
-/* Copyright (c) 2002,2007-2012, The Linux Foundation. All rights reserved.
+/* Copyright (c) 2002,2007-2012,2014 The Linux Foundation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
@@ -471,10 +471,12 @@
* lightweight way to just increase the refcount on a known context rather then
* walking through kgsl_context_get and searching the iterator
*/
-static inline void _kgsl_context_get(struct kgsl_context *context)
+static inline int _kgsl_context_get(struct kgsl_context *context)
{
+ int ret = 0;
if (context)
- kref_get(&context->refcount);
+ ret = kref_get_unless_zero(&context->refcount);
+ return ret;
}
/**
@@ -491,16 +493,19 @@
static inline struct kgsl_context *kgsl_context_get(struct kgsl_device *device,
uint32_t id)
{
+ int result = 0;
struct kgsl_context *context = NULL;
read_lock(&device->context_lock);
context = idr_find(&device->context_idr, id);
- _kgsl_context_get(context);
+ result = _kgsl_context_get(context);
read_unlock(&device->context_lock);
+ if (!result)
+ return NULL;
return context;
}