drm: Move master pointer from drm_minor to drm_device
[cascardo/linux.git] / drivers / gpu / drm / drm_auth.c
index 50d0baa..6bba6b9 100644 (file)
@@ -30,6 +30,7 @@
 
 #include <drm/drmP.h>
 #include "drm_internal.h"
+#include "drm_legacy.h"
 
 /**
  * drm_getmagic - Get unique magic of a client
@@ -48,7 +49,7 @@ int drm_getmagic(struct drm_device *dev, void *data, struct drm_file *file_priv)
        struct drm_auth *auth = data;
        int ret = 0;
 
-       mutex_lock(&dev->struct_mutex);
+       mutex_lock(&dev->master_mutex);
        if (!file_priv->magic) {
                ret = idr_alloc(&file_priv->master->magic_map, file_priv,
                                1, 0, GFP_KERNEL);
@@ -56,7 +57,7 @@ int drm_getmagic(struct drm_device *dev, void *data, struct drm_file *file_priv)
                        file_priv->magic = ret;
        }
        auth->magic = file_priv->magic;
-       mutex_unlock(&dev->struct_mutex);
+       mutex_unlock(&dev->master_mutex);
 
        DRM_DEBUG("%u\n", auth->magic);
 
@@ -81,13 +82,234 @@ int drm_authmagic(struct drm_device *dev, void *data,
 
        DRM_DEBUG("%u\n", auth->magic);
 
-       mutex_lock(&dev->struct_mutex);
+       mutex_lock(&dev->master_mutex);
        file = idr_find(&file_priv->master->magic_map, auth->magic);
        if (file) {
                file->authenticated = 1;
                idr_replace(&file_priv->master->magic_map, NULL, auth->magic);
        }
-       mutex_unlock(&dev->struct_mutex);
+       mutex_unlock(&dev->master_mutex);
 
        return file ? 0 : -EINVAL;
 }
+
+static struct drm_master *drm_master_create(struct drm_device *dev)
+{
+       struct drm_master *master;
+
+       master = kzalloc(sizeof(*master), GFP_KERNEL);
+       if (!master)
+               return NULL;
+
+       kref_init(&master->refcount);
+       spin_lock_init(&master->lock.spinlock);
+       init_waitqueue_head(&master->lock.lock_queue);
+       idr_init(&master->magic_map);
+       master->dev = dev;
+
+       return master;
+}
+
+/*
+ * drm_new_set_master - Allocate a new master object and become master for the
+ * associated master realm.
+ *
+ * @dev: The associated device.
+ * @fpriv: File private identifying the client.
+ *
+ * This function must be called with dev::master_mutex held.
+ * Returns negative error code on failure. Zero on success.
+ */
+static int drm_new_set_master(struct drm_device *dev, struct drm_file *fpriv)
+{
+       struct drm_master *old_master;
+       int ret;
+
+       lockdep_assert_held_once(&dev->master_mutex);
+
+       /* create a new master */
+       dev->master = drm_master_create(dev);
+       if (!dev->master)
+               return -ENOMEM;
+
+       /* take another reference for the copy in the local file priv */
+       old_master = fpriv->master;
+       fpriv->master = drm_master_get(dev->master);
+
+       if (dev->driver->master_create) {
+               ret = dev->driver->master_create(dev, fpriv->master);
+               if (ret)
+                       goto out_err;
+       }
+       if (dev->driver->master_set) {
+               ret = dev->driver->master_set(dev, fpriv, true);
+               if (ret)
+                       goto out_err;
+       }
+
+       fpriv->is_master = 1;
+       fpriv->allowed_master = 1;
+       fpriv->authenticated = 1;
+       if (old_master)
+               drm_master_put(&old_master);
+
+       return 0;
+
+out_err:
+       /* drop both references and restore old master on failure */
+       drm_master_put(&dev->master);
+       drm_master_put(&fpriv->master);
+       fpriv->master = old_master;
+
+       return ret;
+}
+
+int drm_setmaster_ioctl(struct drm_device *dev, void *data,
+                       struct drm_file *file_priv)
+{
+       int ret = 0;
+
+       mutex_lock(&dev->master_mutex);
+       if (file_priv->is_master)
+               goto out_unlock;
+
+       if (dev->master) {
+               ret = -EINVAL;
+               goto out_unlock;
+       }
+
+       if (!file_priv->master) {
+               ret = -EINVAL;
+               goto out_unlock;
+       }
+
+       if (!file_priv->allowed_master) {
+               ret = drm_new_set_master(dev, file_priv);
+               goto out_unlock;
+       }
+
+       dev->master = drm_master_get(file_priv->master);
+       file_priv->is_master = 1;
+       if (dev->driver->master_set) {
+               ret = dev->driver->master_set(dev, file_priv, false);
+               if (unlikely(ret != 0)) {
+                       file_priv->is_master = 0;
+                       drm_master_put(&dev->master);
+               }
+       }
+
+out_unlock:
+       mutex_unlock(&dev->master_mutex);
+       return ret;
+}
+
+int drm_dropmaster_ioctl(struct drm_device *dev, void *data,
+                        struct drm_file *file_priv)
+{
+       int ret = -EINVAL;
+
+       mutex_lock(&dev->master_mutex);
+       if (!file_priv->is_master)
+               goto out_unlock;
+
+       if (!dev->master)
+               goto out_unlock;
+
+       ret = 0;
+       if (dev->driver->master_drop)
+               dev->driver->master_drop(dev, file_priv, false);
+       drm_master_put(&dev->master);
+       file_priv->is_master = 0;
+
+out_unlock:
+       mutex_unlock(&dev->master_mutex);
+       return ret;
+}
+
+int drm_master_open(struct drm_file *file_priv)
+{
+       struct drm_device *dev = file_priv->minor->dev;
+       int ret = 0;
+
+       /* if there is no current master make this fd it, but do not create
+        * any master object for render clients */
+       mutex_lock(&dev->master_mutex);
+       if (!dev->master)
+               ret = drm_new_set_master(dev, file_priv);
+       else
+               file_priv->master = drm_master_get(dev->master);
+       mutex_unlock(&dev->master_mutex);
+
+       return ret;
+}
+
+void drm_master_release(struct drm_file *file_priv)
+{
+       struct drm_device *dev = file_priv->minor->dev;
+       struct drm_master *master = file_priv->master;
+
+       mutex_lock(&dev->master_mutex);
+       if (file_priv->magic)
+               idr_remove(&file_priv->master->magic_map, file_priv->magic);
+
+       if (!file_priv->is_master)
+               goto out;
+
+       if (!drm_core_check_feature(dev, DRIVER_MODESET)) {
+               /*
+                * Since the master is disappearing, so is the
+                * possibility to lock.
+                */
+               mutex_lock(&dev->struct_mutex);
+               if (master->lock.hw_lock) {
+                       if (dev->sigdata.lock == master->lock.hw_lock)
+                               dev->sigdata.lock = NULL;
+                       master->lock.hw_lock = NULL;
+                       master->lock.file_priv = NULL;
+                       wake_up_interruptible_all(&master->lock.lock_queue);
+               }
+               mutex_unlock(&dev->struct_mutex);
+       }
+
+       if (dev->master == file_priv->master) {
+               /* drop the reference held my the minor */
+               if (dev->driver->master_drop)
+                       dev->driver->master_drop(dev, file_priv, true);
+               drm_master_put(&dev->master);
+       }
+out:
+       /* drop the master reference held by the file priv */
+       if (file_priv->master)
+               drm_master_put(&file_priv->master);
+       file_priv->is_master = 0;
+       mutex_unlock(&dev->master_mutex);
+}
+
+struct drm_master *drm_master_get(struct drm_master *master)
+{
+       kref_get(&master->refcount);
+       return master;
+}
+EXPORT_SYMBOL(drm_master_get);
+
+static void drm_master_destroy(struct kref *kref)
+{
+       struct drm_master *master = container_of(kref, struct drm_master, refcount);
+       struct drm_device *dev = master->dev;
+
+       if (dev->driver->master_destroy)
+               dev->driver->master_destroy(dev, master);
+
+       drm_legacy_master_rmmaps(dev, master);
+
+       idr_destroy(&master->magic_map);
+       kfree(master->unique);
+       kfree(master);
+}
+
+void drm_master_put(struct drm_master **master)
+{
+       kref_put(&(*master)->refcount, drm_master_destroy);
+       *master = NULL;
+}
+EXPORT_SYMBOL(drm_master_put);