aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/gpu/drm
diff options
context:
space:
mode:
authorDaniel Vetter <daniel.vetter@ffwll.ch>2013-08-14 18:02:37 -0400
committerDave Airlie <airlied@redhat.com>2013-08-20 22:53:45 -0400
commita8e11d1c435f9d185c9f3b1981b9613a579b9999 (patch)
treefc0b5793a69d06045149a47c01edca0ed8be9905 /drivers/gpu/drm
parent9712def2b3e10081b5f7d3c3bddad3126df4f0ba (diff)
drm/gem: fix up flink name create race
This is the 2nd attempt, I've always been a bit dissatisified with the tricky nature of the first one: http://lists.freedesktop.org/archives/dri-devel/2012-July/025451.html The issue is that the flink ioctl can race with calling gem_close on the last gem handle. In that case we'll end up with a zero handle count, but an flink name (and it's corresponding reference). Which results in a neat space leak. In my first attempt I've solved this by rechecking the handle count. But fundamentally the issue is that ->handle_count isn't your usual refcount - it can be resurrected from 0 among other things. For those special beasts atomic_t often suggest way more ordering that it actually guarantees. To prevent being tricked by those hairy semantics take the easy way out and simply protect the handle with the existing dev->object_name_lock. With that change implemented it's dead easy to fix the flink vs. gem close reace: When we try to create the name we simply have to check whether there's still officially a gem handle around and if not refuse to create the flink name. Since the handle count decrement and flink name destruction is now also protected by that lock the reace is gone and we can't ever leak the flink reference again. Outside of the drm core only the exynos driver looks at the handle count, and tbh I have no idea why (it's just for debug dmesg output luckily). I've considered inlining the drm_gem_object_handle_free, but I plan to add more name-like things (like the exported dma_buf) to this scheme, so it's clearer to leave the handle freeing in its own function. This is exercised by the new gem_flink_race i-g-t testcase, which on my snb leaks gem objects at a rate of roughly 1k objects/s. v2: Fix up the error path handling in handle_create and make it more robust by simply calling object_handle_unreference. v3: Fix up the handle_unreference logic bug - atomic_dec_and_test retursn 1 for 0. Oops. v4: Squash in inlining of drm_gem_object_handle_reference as suggested by Dave Airlie and add a note that we now have a testcase. Cc: Dave Airlie <airlied@gmail.com> Cc: Inki Dae <inki.dae@samsung.com> Signed-off-by: Daniel Vetter <daniel.vetter@ffwll.ch> Signed-off-by: Dave Airlie <airlied@redhat.com>
Diffstat (limited to 'drivers/gpu/drm')
-rw-r--r--drivers/gpu/drm/drm_gem.c31
-rw-r--r--drivers/gpu/drm/drm_info.c2
-rw-r--r--drivers/gpu/drm/exynos/exynos_drm_gem.c2
3 files changed, 22 insertions, 13 deletions
diff --git a/drivers/gpu/drm/drm_gem.c b/drivers/gpu/drm/drm_gem.c
index dcbd2f559e39..b8a8132becef 100644
--- a/drivers/gpu/drm/drm_gem.c
+++ b/drivers/gpu/drm/drm_gem.c
@@ -154,7 +154,7 @@ void drm_gem_private_object_init(struct drm_device *dev,
154 obj->filp = NULL; 154 obj->filp = NULL;
155 155
156 kref_init(&obj->refcount); 156 kref_init(&obj->refcount);
157 atomic_set(&obj->handle_count, 0); 157 obj->handle_count = 0;
158 obj->size = size; 158 obj->size = size;
159} 159}
160EXPORT_SYMBOL(drm_gem_private_object_init); 160EXPORT_SYMBOL(drm_gem_private_object_init);
@@ -218,11 +218,9 @@ static void drm_gem_object_handle_free(struct drm_gem_object *obj)
218 struct drm_device *dev = obj->dev; 218 struct drm_device *dev = obj->dev;
219 219
220 /* Remove any name for this object */ 220 /* Remove any name for this object */
221 spin_lock(&dev->object_name_lock);
222 if (obj->name) { 221 if (obj->name) {
223 idr_remove(&dev->object_name_idr, obj->name); 222 idr_remove(&dev->object_name_idr, obj->name);
224 obj->name = 0; 223 obj->name = 0;
225 spin_unlock(&dev->object_name_lock);
226 /* 224 /*
227 * The object name held a reference to this object, drop 225 * The object name held a reference to this object, drop
228 * that now. 226 * that now.
@@ -230,15 +228,13 @@ static void drm_gem_object_handle_free(struct drm_gem_object *obj)
230 * This cannot be the last reference, since the handle holds one too. 228 * This cannot be the last reference, since the handle holds one too.
231 */ 229 */
232 kref_put(&obj->refcount, drm_gem_object_ref_bug); 230 kref_put(&obj->refcount, drm_gem_object_ref_bug);
233 } else 231 }
234 spin_unlock(&dev->object_name_lock);
235
236} 232}
237 233
238void 234void
239drm_gem_object_handle_unreference_unlocked(struct drm_gem_object *obj) 235drm_gem_object_handle_unreference_unlocked(struct drm_gem_object *obj)
240{ 236{
241 if (WARN_ON(atomic_read(&obj->handle_count) == 0)) 237 if (WARN_ON(obj->handle_count == 0))
242 return; 238 return;
243 239
244 /* 240 /*
@@ -247,8 +243,11 @@ drm_gem_object_handle_unreference_unlocked(struct drm_gem_object *obj)
247 * checked for a name 243 * checked for a name
248 */ 244 */
249 245
250 if (atomic_dec_and_test(&obj->handle_count)) 246 spin_lock(&obj->dev->object_name_lock);
247 if (--obj->handle_count == 0)
251 drm_gem_object_handle_free(obj); 248 drm_gem_object_handle_free(obj);
249 spin_unlock(&obj->dev->object_name_lock);
250
252 drm_gem_object_unreference_unlocked(obj); 251 drm_gem_object_unreference_unlocked(obj);
253} 252}
254 253
@@ -326,17 +325,21 @@ drm_gem_handle_create(struct drm_file *file_priv,
326 * allocation under our spinlock. 325 * allocation under our spinlock.
327 */ 326 */
328 idr_preload(GFP_KERNEL); 327 idr_preload(GFP_KERNEL);
328 spin_lock(&dev->object_name_lock);
329 spin_lock(&file_priv->table_lock); 329 spin_lock(&file_priv->table_lock);
330 330
331 ret = idr_alloc(&file_priv->object_idr, obj, 1, 0, GFP_NOWAIT); 331 ret = idr_alloc(&file_priv->object_idr, obj, 1, 0, GFP_NOWAIT);
332 332 drm_gem_object_reference(obj);
333 obj->handle_count++;
333 spin_unlock(&file_priv->table_lock); 334 spin_unlock(&file_priv->table_lock);
335 spin_unlock(&dev->object_name_lock);
334 idr_preload_end(); 336 idr_preload_end();
335 if (ret < 0) 337 if (ret < 0) {
338 drm_gem_object_handle_unreference_unlocked(obj);
336 return ret; 339 return ret;
340 }
337 *handlep = ret; 341 *handlep = ret;
338 342
339 drm_gem_object_handle_reference(obj);
340 343
341 if (dev->driver->gem_open_object) { 344 if (dev->driver->gem_open_object) {
342 ret = dev->driver->gem_open_object(obj, file_priv); 345 ret = dev->driver->gem_open_object(obj, file_priv);
@@ -577,6 +580,12 @@ drm_gem_flink_ioctl(struct drm_device *dev, void *data,
577 580
578 idr_preload(GFP_KERNEL); 581 idr_preload(GFP_KERNEL);
579 spin_lock(&dev->object_name_lock); 582 spin_lock(&dev->object_name_lock);
583 /* prevent races with concurrent gem_close. */
584 if (obj->handle_count == 0) {
585 ret = -ENOENT;
586 goto err;
587 }
588
580 if (!obj->name) { 589 if (!obj->name) {
581 ret = idr_alloc(&dev->object_name_idr, obj, 1, 0, GFP_NOWAIT); 590 ret = idr_alloc(&dev->object_name_idr, obj, 1, 0, GFP_NOWAIT);
582 if (ret < 0) 591 if (ret < 0)
diff --git a/drivers/gpu/drm/drm_info.c b/drivers/gpu/drm/drm_info.c
index 9f8fc4c328c9..5351e811c421 100644
--- a/drivers/gpu/drm/drm_info.c
+++ b/drivers/gpu/drm/drm_info.c
@@ -207,7 +207,7 @@ static int drm_gem_one_name_info(int id, void *ptr, void *data)
207 207
208 seq_printf(m, "%6d %8zd %7d %8d\n", 208 seq_printf(m, "%6d %8zd %7d %8d\n",
209 obj->name, obj->size, 209 obj->name, obj->size,
210 atomic_read(&obj->handle_count), 210 obj->handle_count,
211 atomic_read(&obj->refcount.refcount)); 211 atomic_read(&obj->refcount.refcount));
212 return 0; 212 return 0;
213} 213}
diff --git a/drivers/gpu/drm/exynos/exynos_drm_gem.c b/drivers/gpu/drm/exynos/exynos_drm_gem.c
index b904633863e8..f3c6f40666e1 100644
--- a/drivers/gpu/drm/exynos/exynos_drm_gem.c
+++ b/drivers/gpu/drm/exynos/exynos_drm_gem.c
@@ -136,7 +136,7 @@ void exynos_drm_gem_destroy(struct exynos_drm_gem_obj *exynos_gem_obj)
136 obj = &exynos_gem_obj->base; 136 obj = &exynos_gem_obj->base;
137 buf = exynos_gem_obj->buffer; 137 buf = exynos_gem_obj->buffer;
138 138
139 DRM_DEBUG_KMS("handle count = %d\n", atomic_read(&obj->handle_count)); 139 DRM_DEBUG_KMS("handle count = %d\n", obj->handle_count);
140 140
141 /* 141 /*
142 * do not release memory region from exporter. 142 * do not release memory region from exporter.