[RFC 8/9] drm: track pending user-space actions

David Herrmann dh.herrmann at gmail.com
Wed Jul 24 06:43:57 PDT 2013


If we want to support real hotplugging, we need to block new syscalls from
entering any drm_* fops. We also need to wait for these to finish before
unregistering a device.

This patch introduces drm_dev_get/put_active() which mark a device as
active during file-ops. If a device is unplugged, drm_dev_get_active()
will fail and prevent users from using this device.

Internally, we use rwsems to implement this. It allows simultaneous users
(down_read) and we can block on them (down_write) to wait until they are
done. This way, a drm_dev_unregister() can be called at any time and does
not have to wait for the last drm_release() call.

Note that the current "atomic_t unplugged" approach is not safe. Imagine
an unplugged device but a user-space context which already is beyong the
"drm_device_is_unplugged()" check. We have no way to prevent any following
mmap operation or buffer access. The percpu-rwsem avoids this by
protecting a whole file-op call and waiting with unplugging a device until
all pending calls are finished.

FIXME: We still need to update all the driver's fops in case they don't
use the DRM core stubs. A quick look showed only custom mmap callbacks.

Signed-off-by: David Herrmann <dh.herrmann at gmail.com>
---
 drivers/gpu/drm/Kconfig    |  1 +
 drivers/gpu/drm/drm_drv.c  |  6 +++--
 drivers/gpu/drm/drm_fops.c | 31 +++++++++++++++++-----
 drivers/gpu/drm/drm_stub.c | 64 +++++++++++++++++++++++++++++++++++++++++++---
 include/drm/drmP.h         |  6 +++++
 5 files changed, 96 insertions(+), 12 deletions(-)

diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig
index a7c54c8..e2a399e 100644
--- a/drivers/gpu/drm/Kconfig
+++ b/drivers/gpu/drm/Kconfig
@@ -11,6 +11,7 @@ menuconfig DRM
 	select I2C
 	select I2C_ALGOBIT
 	select DMA_SHARED_BUFFER
+	select PERCPU_RWSEM
 	help
 	  Kernel-level support for the Direct Rendering Infrastructure (DRI)
 	  introduced in XFree86 4.0. If you say Y here, you need to select
diff --git a/drivers/gpu/drm/drm_drv.c b/drivers/gpu/drm/drm_drv.c
index c294e89..db5e57b 100644
--- a/drivers/gpu/drm/drm_drv.c
+++ b/drivers/gpu/drm/drm_drv.c
@@ -318,7 +318,7 @@ long drm_ioctl(struct file *filp,
 
 	dev = file_priv->minor->dev;
 
-	if (drm_device_is_unplugged(dev))
+	if (!drm_dev_get_active(dev))
 		return -ENODEV;
 
 	atomic_inc(&dev->ioctl_count);
@@ -412,7 +412,9 @@ long drm_ioctl(struct file *filp,
 
 	if (kdata != stack_kdata)
 		kfree(kdata);
-	atomic_dec(&dev->ioctl_count);
+
+	drm_dev_put_active(dev);
+
 	if (retcode)
 		DRM_DEBUG("ret = %d\n", retcode);
 	return retcode;
diff --git a/drivers/gpu/drm/drm_fops.c b/drivers/gpu/drm/drm_fops.c
index f8a3ebc..b75af7d 100644
--- a/drivers/gpu/drm/drm_fops.c
+++ b/drivers/gpu/drm/drm_fops.c
@@ -122,7 +122,7 @@ int drm_open(struct inode *inode, struct file *filp)
 	if (!(dev = minor->dev))
 		return -ENODEV;
 
-	if (drm_device_is_unplugged(dev))
+	if (!drm_dev_get_active(dev))
 		return -ENODEV;
 
 	if (!dev->open_count++)
@@ -147,7 +147,9 @@ int drm_open(struct inode *inode, struct file *filp)
 		if (retcode)
 			goto err_undo;
 	}
-	return 0;
+
+	retcode = 0;
+	goto out_active;
 
 err_undo:
 	mutex_lock(&dev->struct_mutex);
@@ -157,6 +159,8 @@ err_undo:
 	dev->dev_mapping = old_mapping;
 	mutex_unlock(&dev->struct_mutex);
 	dev->open_count--;
+out_active:
+	drm_dev_put_active(dev);
 	return retcode;
 }
 EXPORT_SYMBOL(drm_open);
@@ -188,9 +192,6 @@ int drm_stub_open(struct inode *inode, struct file *filp)
 	if (!(dev = minor->dev))
 		goto out;
 
-	if (drm_device_is_unplugged(dev))
-		goto out;
-
 	old_fops = filp->f_op;
 	filp->f_op = fops_get(dev->driver->fops);
 	if (filp->f_op == NULL) {
@@ -654,14 +655,24 @@ ssize_t drm_read(struct file *filp, char __user *buffer,
 		 size_t count, loff_t *offset)
 {
 	struct drm_file *file_priv = filp->private_data;
+	struct drm_device *dev = file_priv->minor->dev;
 	struct drm_pending_event *e;
 	size_t total;
 	ssize_t ret;
 
+	/* No locking needed around "unplugged" as we sleep during wait_event()
+	 * below, anyway. We acquire the active device after we woke up so we
+	 * never use unplugged devices. */
+	if (dev->unplugged)
+		return -ENODEV;
+
 	ret = wait_event_interruptible(file_priv->event_wait,
-				       !list_empty(&file_priv->event_list));
+				       !list_empty(&file_priv->event_list) ||
+				       dev->unplugged);
 	if (ret < 0)
 		return ret;
+	if (!drm_dev_get_active(dev))
+		return -ENODEV;
 
 	total = 0;
 	while (drm_dequeue_event(file_priv, total, count, &e)) {
@@ -675,6 +686,7 @@ ssize_t drm_read(struct file *filp, char __user *buffer,
 		e->destroy(e);
 	}
 
+	drm_dev_put_active(dev);
 	return total;
 }
 EXPORT_SYMBOL(drm_read);
@@ -682,13 +694,20 @@ EXPORT_SYMBOL(drm_read);
 unsigned int drm_poll(struct file *filp, struct poll_table_struct *wait)
 {
 	struct drm_file *file_priv = filp->private_data;
+	struct drm_device *dev = file_priv->minor->dev;
 	unsigned int mask = 0;
 
+	/* signal HUP/IN on device removal */
+	if (!drm_dev_get_active(dev))
+		return POLLHUP | POLLIN | POLLRDNORM;
+
 	poll_wait(filp, &file_priv->event_wait, wait);
 
 	if (!list_empty(&file_priv->event_list))
 		mask |= POLLIN | POLLRDNORM;
 
+	drm_dev_put_active(dev);
+
 	return mask;
 }
 EXPORT_SYMBOL(drm_poll);
diff --git a/drivers/gpu/drm/drm_stub.c b/drivers/gpu/drm/drm_stub.c
index 4ff1227..c0e76c0 100644
--- a/drivers/gpu/drm/drm_stub.c
+++ b/drivers/gpu/drm/drm_stub.c
@@ -449,9 +449,12 @@ struct drm_device *drm_dev_alloc(struct drm_driver *driver,
 	dev->types[4] = _DRM_STAT_LOCKS;
 	dev->types[5] = _DRM_STAT_UNLOCKS;
 
-	if (drm_ht_create(&dev->map_hash, 12))
+	if (percpu_init_rwsem(&dev->active))
 		goto err_free;
 
+	if (drm_ht_create(&dev->map_hash, 12))
+		goto err_rwsem;
+
 	ret = drm_ctxbitmap_init(dev);
 	if (ret) {
 		DRM_ERROR("Cannot allocate memory for context bitmap.\n");
@@ -472,6 +475,8 @@ err_ctxbitmap:
 	drm_ctxbitmap_cleanup(dev);
 err_map_hash:
 	drm_ht_remove(&dev->map_hash);
+err_rwsem:
+	percpu_free_rwsem(&dev->active);
 err_free:
 	kfree(dev);
 	return NULL;
@@ -496,6 +501,7 @@ void drm_dev_free(struct drm_device *dev)
 	drm_ctxbitmap_cleanup(dev);
 	drm_ht_remove(&dev->map_hash);
 
+	percpu_free_rwsem(&dev->active);
 	kfree(dev->devname);
 	kfree(dev);
 }
@@ -576,14 +582,21 @@ EXPORT_SYMBOL(drm_dev_register);
  * drm_dev_unregister - Unregister DRM device
  * @dev: Device to unregister
  *
- * Unregister the DRM device from the system. This does the reverse of
- * drm_dev_register() but does not deallocate the device. The caller must call
- * drm_dev_free() to free all resources.
+ * Mark DRM device as unplugged, wait for any pending user request and then
+ * unregister the DRM device from the system. This does the reverse of
+ * drm_dev_register().
  */
 void drm_dev_unregister(struct drm_device *dev)
 {
 	struct drm_map_list *r_list, *list_temp;
 
+	/* Wait for pending users and then mark the device as unplugged. We
+	 * must not hold the global-mutex while doing this. Otherwise, calls
+	 * like drm_ioctl() or drm_lock() will dead-lock. */
+	percpu_down_write(&dev->active);
+	dev->unplugged = true;
+	percpu_up_write(&dev->active);
+
 	drm_lastclose(dev);
 
 	if (dev->driver->unload)
@@ -605,3 +618,46 @@ void drm_dev_unregister(struct drm_device *dev)
 	list_del(&dev->driver_item);
 }
 EXPORT_SYMBOL(drm_dev_unregister);
+
+/**
+ * drm_dev_get_active - Mark device as active
+ * @dev: Device to mark
+ *
+ * Whenever a DRM driver performs an action on behalf of user-space, it should
+ * mark the DRM device as active. Once it is done, call drm_dev_put_active() to
+ * release that mark. This allows DRM core to wait for pending user-space
+ * actions before unplugging a device. But this also means, user-space must
+ * not sleep for an indefinite period while a device is marked active.
+ * If you have to sleep for an indefinite period, call drm_dev_put_active() and
+ * try to reacquire the device once you wake up.
+ *
+ * Recursive calls are not allowed! They will dead-lock!
+ *
+ * RETURNS:
+ * True iff the device was marked active and can be used. False if the device
+ * was unplugged and must not be used.
+ */
+bool drm_dev_get_active(struct drm_device *dev)
+{
+	percpu_down_read(&dev->active);
+	if (!dev->unplugged)
+		return true;
+	percpu_up_read(&dev->active);
+	return false;
+}
+EXPORT_SYMBOL(drm_dev_get_active);
+
+/**
+ * drm_dev_put_active - Unmark active device
+ * @dev: Active device to unmark
+ *
+ * This finished a call to drm_dev_get_active(). You must not call it if
+ * drm_dev_get_active() failed.
+ * This marks the device as inactive again, iff no other user currently has the
+ * device marked as active. See drm_dev_get_active().
+ */
+void drm_dev_put_active(struct drm_device *dev)
+{
+	percpu_up_read(&dev->active);
+}
+EXPORT_SYMBOL(drm_dev_put_active);
diff --git a/include/drm/drmP.h b/include/drm/drmP.h
index 381679e..9689173 100644
--- a/include/drm/drmP.h
+++ b/include/drm/drmP.h
@@ -1209,7 +1209,11 @@ struct drm_device {
 	/*@} */
 	int switch_power_state;
 
+	/** \name Hotplug Management */
+	/*@{ */
+	struct percpu_rw_semaphore active;	/**< protect active users */
 	atomic_t unplugged; /* device has been unplugged or gone away */
+	/*@} */
 };
 
 #define DRM_SWITCH_POWER_ON 0
@@ -1710,6 +1714,8 @@ struct drm_device *drm_dev_alloc(struct drm_driver *driver,
 void drm_dev_free(struct drm_device *dev);
 int drm_dev_register(struct drm_device *dev);
 void drm_dev_unregister(struct drm_device *dev);
+bool drm_dev_get_active(struct drm_device *dev);
+void drm_dev_put_active(struct drm_device *dev);
 int drm_get_minor(struct drm_device *dev, struct drm_minor **minor, int type);
 /*@}*/
 
-- 
1.8.3.3



More information about the dri-devel mailing list