drm: Use srcu to protect drm_device.unplugged

Use srcu to protect drm_device.unplugged in a race free manner.
Drivers can use drm_dev_enter()/drm_dev_exit() to protect and mark
sections preventing access to device resources that are not available
after the device is gone.

Suggested-by: Daniel Vetter <daniel.vetter@ffwll.ch>
Signed-off-by: Noralf Trønnes <noralf@tronnes.org>
Signed-off-by: Oleksandr Andrushchenko <oleksandr_andrushchenko@epam.com>
Reviewed-by: Oleksandr Andrushchenko <oleksandr_andrushchenko@epam.com>
Tested-by: Oleksandr Andrushchenko <oleksandr_andrushchenko@epam.com>
Cc: intel-gfx@lists.freedesktop.org
Reviewed-by: Daniel Vetter <daniel.vetter@ffwll.ch>
Link: https://patchwork.freedesktop.org/patch/msgid/1522222715-11814-1-git-send-email-andr2000@gmail.com
This commit is contained in:
Noralf Trønnes 2018-03-28 10:38:35 +03:00 committed by Oleksandr Andrushchenko
parent 790861cc34
commit bee330f3d6
3 changed files with 68 additions and 10 deletions

View File

@ -32,6 +32,7 @@
#include <linux/moduleparam.h> #include <linux/moduleparam.h>
#include <linux/mount.h> #include <linux/mount.h>
#include <linux/slab.h> #include <linux/slab.h>
#include <linux/srcu.h>
#include <drm/drm_drv.h> #include <drm/drm_drv.h>
#include <drm/drmP.h> #include <drm/drmP.h>
@ -75,6 +76,8 @@ static bool drm_core_init_complete = false;
static struct dentry *drm_debugfs_root; static struct dentry *drm_debugfs_root;
DEFINE_STATIC_SRCU(drm_unplug_srcu);
/* /*
* DRM Minors * DRM Minors
* A DRM device can provide several char-dev interfaces on the DRM-Major. Each * A DRM device can provide several char-dev interfaces on the DRM-Major. Each
@ -318,18 +321,51 @@ void drm_put_dev(struct drm_device *dev)
} }
EXPORT_SYMBOL(drm_put_dev); EXPORT_SYMBOL(drm_put_dev);
static void drm_device_set_unplugged(struct drm_device *dev) /**
* drm_dev_enter - Enter device critical section
* @dev: DRM device
* @idx: Pointer to index that will be passed to the matching drm_dev_exit()
*
* This function marks and protects the beginning of a section that should not
* be entered after the device has been unplugged. The section end is marked
* with drm_dev_exit(). Calls to this function can be nested.
*
* Returns:
* True if it is OK to enter the section, false otherwise.
*/
bool drm_dev_enter(struct drm_device *dev, int *idx)
{ {
smp_wmb(); *idx = srcu_read_lock(&drm_unplug_srcu);
atomic_set(&dev->unplugged, 1);
if (dev->unplugged) {
srcu_read_unlock(&drm_unplug_srcu, *idx);
return false;
}
return true;
} }
EXPORT_SYMBOL(drm_dev_enter);
/**
* drm_dev_exit - Exit device critical section
* @idx: index returned from drm_dev_enter()
*
* This function marks the end of a section that should not be entered after
* the device has been unplugged.
*/
void drm_dev_exit(int idx)
{
srcu_read_unlock(&drm_unplug_srcu, idx);
}
EXPORT_SYMBOL(drm_dev_exit);
/** /**
* drm_dev_unplug - unplug a DRM device * drm_dev_unplug - unplug a DRM device
* @dev: DRM device * @dev: DRM device
* *
* This unplugs a hotpluggable DRM device, which makes it inaccessible to * This unplugs a hotpluggable DRM device, which makes it inaccessible to
* userspace operations. Entry-points can use drm_dev_is_unplugged(). This * userspace operations. Entry-points can use drm_dev_enter() and
* drm_dev_exit() to protect device resources in a race free manner. This
* essentially unregisters the device like drm_dev_unregister(), but can be * essentially unregisters the device like drm_dev_unregister(), but can be
* called while there are still open users of @dev. * called while there are still open users of @dev.
*/ */
@ -338,10 +374,18 @@ void drm_dev_unplug(struct drm_device *dev)
drm_dev_unregister(dev); drm_dev_unregister(dev);
mutex_lock(&drm_global_mutex); mutex_lock(&drm_global_mutex);
drm_device_set_unplugged(dev);
if (dev->open_count == 0) if (dev->open_count == 0)
drm_dev_put(dev); drm_dev_put(dev);
mutex_unlock(&drm_global_mutex); mutex_unlock(&drm_global_mutex);
/*
* After synchronizing any critical read section is guaranteed to see
* the new value of ->unplugged, and any critical section which might
* still have seen the old value of ->unplugged is guaranteed to have
* finished.
*/
dev->unplugged = true;
synchronize_srcu(&drm_unplug_srcu);
} }
EXPORT_SYMBOL(drm_dev_unplug); EXPORT_SYMBOL(drm_dev_unplug);

View File

@ -46,7 +46,14 @@ struct drm_device {
/* currently active master for this device. Protected by master_mutex */ /* currently active master for this device. Protected by master_mutex */
struct drm_master *master; struct drm_master *master;
atomic_t unplugged; /**< Flag whether dev is dead */ /**
* @unplugged:
*
* Flag to tell if the device has been unplugged.
* See drm_dev_enter() and drm_dev_is_unplugged().
*/
bool unplugged;
struct inode *anon_inode; /**< inode for private address-space */ struct inode *anon_inode; /**< inode for private address-space */
char *unique; /**< unique name of the device */ char *unique; /**< unique name of the device */
/*@} */ /*@} */

View File

@ -623,6 +623,8 @@ void drm_dev_get(struct drm_device *dev);
void drm_dev_put(struct drm_device *dev); void drm_dev_put(struct drm_device *dev);
void drm_dev_unref(struct drm_device *dev); void drm_dev_unref(struct drm_device *dev);
void drm_put_dev(struct drm_device *dev); void drm_put_dev(struct drm_device *dev);
bool drm_dev_enter(struct drm_device *dev, int *idx);
void drm_dev_exit(int idx);
void drm_dev_unplug(struct drm_device *dev); void drm_dev_unplug(struct drm_device *dev);
/** /**
@ -634,11 +636,16 @@ void drm_dev_unplug(struct drm_device *dev);
* unplugged, these two functions guarantee that any store before calling * unplugged, these two functions guarantee that any store before calling
* drm_dev_unplug() is visible to callers of this function after it completes * drm_dev_unplug() is visible to callers of this function after it completes
*/ */
static inline int drm_dev_is_unplugged(struct drm_device *dev) static inline bool drm_dev_is_unplugged(struct drm_device *dev)
{ {
int ret = atomic_read(&dev->unplugged); int idx;
smp_rmb();
return ret; if (drm_dev_enter(dev, &idx)) {
drm_dev_exit(idx);
return false;
}
return true;
} }