123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729 |
- // SPDX-License-Identifier: GPL-2.0-or-later
- /*
- * Copyright © 2017 Keith Packard <[email protected]>
- */
- #include <linux/file.h>
- #include <linux/uaccess.h>
- #include <drm/drm_auth.h>
- #include <drm/drm_crtc_helper.h>
- #include <drm/drm_drv.h>
- #include <drm/drm_file.h>
- #include <drm/drm_lease.h>
- #include <drm/drm_print.h>
- #include "drm_crtc_internal.h"
- #include "drm_internal.h"
- /**
- * DOC: drm leasing
- *
- * DRM leases provide information about whether a DRM master may control a DRM
- * mode setting object. This enables the creation of multiple DRM masters that
- * manage subsets of display resources.
- *
- * The original DRM master of a device 'owns' the available drm resources. It
- * may create additional DRM masters and 'lease' resources which it controls
- * to the new DRM master. This gives the new DRM master control over the
- * leased resources until the owner revokes the lease, or the new DRM master
- * is closed. Some helpful terminology:
- *
- * - An 'owner' is a &struct drm_master that is not leasing objects from
- * another &struct drm_master, and hence 'owns' the objects. The owner can be
- * identified as the &struct drm_master for which &drm_master.lessor is NULL.
- *
- * - A 'lessor' is a &struct drm_master which is leasing objects to one or more
- * other &struct drm_master. Currently, lessees are not allowed to
- * create sub-leases, hence the lessor is the same as the owner.
- *
- * - A 'lessee' is a &struct drm_master which is leasing objects from some
- * other &struct drm_master. Each lessee only leases resources from a single
- * lessor recorded in &drm_master.lessor, and holds the set of objects that
- * it is leasing in &drm_master.leases.
- *
- * - A 'lease' is a contract between the lessor and lessee that identifies
- * which resources may be controlled by the lessee. All of the resources
- * that are leased must be owned by or leased to the lessor, and lessors are
- * not permitted to lease the same object to multiple lessees.
- *
- * The set of objects any &struct drm_master 'controls' is limited to the set
- * of objects it leases (for lessees) or all objects (for owners).
- *
- * Objects not controlled by a &struct drm_master cannot be modified through
- * the various state manipulating ioctls, and any state reported back to user
- * space will be edited to make them appear idle and/or unusable. For
- * instance, connectors always report 'disconnected', while encoders
- * report no possible crtcs or clones.
- *
- * Since each lessee may lease objects from a single lessor, display resource
- * leases form a tree of &struct drm_master. As lessees are currently not
- * allowed to create sub-leases, the tree depth is limited to 1. All of
- * these get activated simultaneously when the top level device owner changes
- * through the SETMASTER or DROPMASTER IOCTL, so &drm_device.master points to
- * the owner at the top of the lease tree (i.e. the &struct drm_master for which
- * &drm_master.lessor is NULL). The full list of lessees that are leasing
- * objects from the owner can be searched via the owner's
- * &drm_master.lessee_idr.
- */
- #define drm_for_each_lessee(lessee, lessor) \
- list_for_each_entry((lessee), &(lessor)->lessees, lessee_list)
- static uint64_t drm_lease_idr_object;
- struct drm_master *drm_lease_owner(struct drm_master *master)
- {
- while (master->lessor != NULL)
- master = master->lessor;
- return master;
- }
- static struct drm_master*
- _drm_find_lessee(struct drm_master *master, int lessee_id)
- {
- lockdep_assert_held(&master->dev->mode_config.idr_mutex);
- return idr_find(&drm_lease_owner(master)->lessee_idr, lessee_id);
- }
- static int _drm_lease_held_master(struct drm_master *master, int id)
- {
- lockdep_assert_held(&master->dev->mode_config.idr_mutex);
- if (master->lessor)
- return idr_find(&master->leases, id) != NULL;
- return true;
- }
- /* Checks if the given object has been leased to some lessee of drm_master */
- static bool _drm_has_leased(struct drm_master *master, int id)
- {
- struct drm_master *lessee;
- lockdep_assert_held(&master->dev->mode_config.idr_mutex);
- drm_for_each_lessee(lessee, master)
- if (_drm_lease_held_master(lessee, id))
- return true;
- return false;
- }
- /* Called with idr_mutex held */
- bool _drm_lease_held(struct drm_file *file_priv, int id)
- {
- bool ret;
- struct drm_master *master;
- if (!file_priv)
- return true;
- master = drm_file_get_master(file_priv);
- if (!master)
- return true;
- ret = _drm_lease_held_master(master, id);
- drm_master_put(&master);
- return ret;
- }
- bool drm_lease_held(struct drm_file *file_priv, int id)
- {
- struct drm_master *master;
- bool ret;
- if (!file_priv)
- return true;
- master = drm_file_get_master(file_priv);
- if (!master)
- return true;
- if (!master->lessor) {
- ret = true;
- goto out;
- }
- mutex_lock(&master->dev->mode_config.idr_mutex);
- ret = _drm_lease_held_master(master, id);
- mutex_unlock(&master->dev->mode_config.idr_mutex);
- out:
- drm_master_put(&master);
- return ret;
- }
- /*
- * Given a bitmask of crtcs to check, reconstructs a crtc mask based on the
- * crtcs which are visible through the specified file.
- */
- uint32_t drm_lease_filter_crtcs(struct drm_file *file_priv, uint32_t crtcs_in)
- {
- struct drm_master *master;
- struct drm_device *dev;
- struct drm_crtc *crtc;
- int count_in, count_out;
- uint32_t crtcs_out = 0;
- if (!file_priv)
- return crtcs_in;
- master = drm_file_get_master(file_priv);
- if (!master)
- return crtcs_in;
- if (!master->lessor) {
- crtcs_out = crtcs_in;
- goto out;
- }
- dev = master->dev;
- count_in = count_out = 0;
- mutex_lock(&master->dev->mode_config.idr_mutex);
- list_for_each_entry(crtc, &dev->mode_config.crtc_list, head) {
- if (_drm_lease_held_master(master, crtc->base.id)) {
- uint32_t mask_in = 1ul << count_in;
- if ((crtcs_in & mask_in) != 0) {
- uint32_t mask_out = 1ul << count_out;
- crtcs_out |= mask_out;
- }
- count_out++;
- }
- count_in++;
- }
- mutex_unlock(&master->dev->mode_config.idr_mutex);
- out:
- drm_master_put(&master);
- return crtcs_out;
- }
- /*
- * Uses drm_master_create to allocate a new drm_master, then checks to
- * make sure all of the desired objects can be leased, atomically
- * leasing them to the new drmmaster.
- *
- * ERR_PTR(-EACCES) some other master holds the title to any object
- * ERR_PTR(-ENOENT) some object is not a valid DRM object for this device
- * ERR_PTR(-EBUSY) some other lessee holds title to this object
- * ERR_PTR(-EEXIST) same object specified more than once in the provided list
- * ERR_PTR(-ENOMEM) allocation failed
- */
- static struct drm_master *drm_lease_create(struct drm_master *lessor, struct idr *leases)
- {
- struct drm_device *dev = lessor->dev;
- int error;
- struct drm_master *lessee;
- int object;
- int id;
- void *entry;
- DRM_DEBUG_LEASE("lessor %d\n", lessor->lessee_id);
- lessee = drm_master_create(lessor->dev);
- if (!lessee) {
- DRM_DEBUG_LEASE("drm_master_create failed\n");
- return ERR_PTR(-ENOMEM);
- }
- mutex_lock(&dev->mode_config.idr_mutex);
- idr_for_each_entry(leases, entry, object) {
- error = 0;
- if (!idr_find(&dev->mode_config.object_idr, object))
- error = -ENOENT;
- else if (_drm_has_leased(lessor, object))
- error = -EBUSY;
- if (error != 0) {
- DRM_DEBUG_LEASE("object %d failed %d\n", object, error);
- goto out_lessee;
- }
- }
- /* Insert the new lessee into the tree */
- id = idr_alloc(&(drm_lease_owner(lessor)->lessee_idr), lessee, 1, 0, GFP_KERNEL);
- if (id < 0) {
- error = id;
- goto out_lessee;
- }
- lessee->lessee_id = id;
- lessee->lessor = drm_master_get(lessor);
- list_add_tail(&lessee->lessee_list, &lessor->lessees);
- /* Move the leases over */
- lessee->leases = *leases;
- DRM_DEBUG_LEASE("new lessee %d %p, lessor %d %p\n", lessee->lessee_id, lessee, lessor->lessee_id, lessor);
- mutex_unlock(&dev->mode_config.idr_mutex);
- return lessee;
- out_lessee:
- mutex_unlock(&dev->mode_config.idr_mutex);
- drm_master_put(&lessee);
- return ERR_PTR(error);
- }
- void drm_lease_destroy(struct drm_master *master)
- {
- struct drm_device *dev = master->dev;
- mutex_lock(&dev->mode_config.idr_mutex);
- DRM_DEBUG_LEASE("drm_lease_destroy %d\n", master->lessee_id);
- /* This master is referenced by all lessees, hence it cannot be destroyed
- * until all of them have been
- */
- WARN_ON(!list_empty(&master->lessees));
- /* Remove this master from the lessee idr in the owner */
- if (master->lessee_id != 0) {
- DRM_DEBUG_LEASE("remove master %d from device list of lessees\n", master->lessee_id);
- idr_remove(&(drm_lease_owner(master)->lessee_idr), master->lessee_id);
- }
- /* Remove this master from any lessee list it may be on */
- list_del(&master->lessee_list);
- mutex_unlock(&dev->mode_config.idr_mutex);
- if (master->lessor) {
- /* Tell the master to check the lessee list */
- drm_sysfs_lease_event(dev);
- drm_master_put(&master->lessor);
- }
- DRM_DEBUG_LEASE("drm_lease_destroy done %d\n", master->lessee_id);
- }
- static void _drm_lease_revoke(struct drm_master *top)
- {
- int object;
- void *entry;
- struct drm_master *master = top;
- lockdep_assert_held(&top->dev->mode_config.idr_mutex);
- /*
- * Walk the tree starting at 'top' emptying all leases. Because
- * the tree is fully connected, we can do this without recursing
- */
- for (;;) {
- DRM_DEBUG_LEASE("revoke leases for %p %d\n", master, master->lessee_id);
- /* Evacuate the lease */
- idr_for_each_entry(&master->leases, entry, object)
- idr_remove(&master->leases, object);
- /* Depth-first list walk */
- /* Down */
- if (!list_empty(&master->lessees)) {
- master = list_first_entry(&master->lessees, struct drm_master, lessee_list);
- } else {
- /* Up */
- while (master != top && master == list_last_entry(&master->lessor->lessees, struct drm_master, lessee_list))
- master = master->lessor;
- if (master == top)
- break;
- /* Over */
- master = list_next_entry(master, lessee_list);
- }
- }
- }
- void drm_lease_revoke(struct drm_master *top)
- {
- mutex_lock(&top->dev->mode_config.idr_mutex);
- _drm_lease_revoke(top);
- mutex_unlock(&top->dev->mode_config.idr_mutex);
- }
- static int validate_lease(struct drm_device *dev,
- int object_count,
- struct drm_mode_object **objects,
- bool universal_planes)
- {
- int o;
- int has_crtc = -1;
- int has_connector = -1;
- int has_plane = -1;
- /* we want to confirm that there is at least one crtc, plane
- connector object. */
- for (o = 0; o < object_count; o++) {
- if (objects[o]->type == DRM_MODE_OBJECT_CRTC && has_crtc == -1) {
- has_crtc = o;
- }
- if (objects[o]->type == DRM_MODE_OBJECT_CONNECTOR && has_connector == -1)
- has_connector = o;
- if (universal_planes) {
- if (objects[o]->type == DRM_MODE_OBJECT_PLANE && has_plane == -1)
- has_plane = o;
- }
- }
- if (has_crtc == -1 || has_connector == -1)
- return -EINVAL;
- if (universal_planes && has_plane == -1)
- return -EINVAL;
- return 0;
- }
- static int fill_object_idr(struct drm_device *dev,
- struct drm_file *lessor_priv,
- struct idr *leases,
- int object_count,
- u32 *object_ids)
- {
- struct drm_mode_object **objects;
- u32 o;
- int ret;
- bool universal_planes = READ_ONCE(lessor_priv->universal_planes);
- objects = kcalloc(object_count, sizeof(struct drm_mode_object *),
- GFP_KERNEL);
- if (!objects)
- return -ENOMEM;
- /* step one - get references to all the mode objects
- and check for validity. */
- for (o = 0; o < object_count; o++) {
- objects[o] = drm_mode_object_find(dev, lessor_priv,
- object_ids[o],
- DRM_MODE_OBJECT_ANY);
- if (!objects[o]) {
- ret = -ENOENT;
- goto out_free_objects;
- }
- if (!drm_mode_object_lease_required(objects[o]->type)) {
- DRM_DEBUG_KMS("invalid object for lease\n");
- ret = -EINVAL;
- goto out_free_objects;
- }
- }
- ret = validate_lease(dev, object_count, objects, universal_planes);
- if (ret) {
- DRM_DEBUG_LEASE("lease validation failed\n");
- goto out_free_objects;
- }
- /* add their IDs to the lease request - taking into account
- universal planes */
- for (o = 0; o < object_count; o++) {
- struct drm_mode_object *obj = objects[o];
- u32 object_id = objects[o]->id;
- DRM_DEBUG_LEASE("Adding object %d to lease\n", object_id);
- /*
- * We're using an IDR to hold the set of leased
- * objects, but we don't need to point at the object's
- * data structure from the lease as the main object_idr
- * will be used to actually find that. Instead, all we
- * really want is a 'leased/not-leased' result, for
- * which any non-NULL pointer will work fine.
- */
- ret = idr_alloc(leases, &drm_lease_idr_object , object_id, object_id + 1, GFP_KERNEL);
- if (ret < 0) {
- DRM_DEBUG_LEASE("Object %d cannot be inserted into leases (%d)\n",
- object_id, ret);
- goto out_free_objects;
- }
- if (obj->type == DRM_MODE_OBJECT_CRTC && !universal_planes) {
- struct drm_crtc *crtc = obj_to_crtc(obj);
- ret = idr_alloc(leases, &drm_lease_idr_object, crtc->primary->base.id, crtc->primary->base.id + 1, GFP_KERNEL);
- if (ret < 0) {
- DRM_DEBUG_LEASE("Object primary plane %d cannot be inserted into leases (%d)\n",
- object_id, ret);
- goto out_free_objects;
- }
- if (crtc->cursor) {
- ret = idr_alloc(leases, &drm_lease_idr_object, crtc->cursor->base.id, crtc->cursor->base.id + 1, GFP_KERNEL);
- if (ret < 0) {
- DRM_DEBUG_LEASE("Object cursor plane %d cannot be inserted into leases (%d)\n",
- object_id, ret);
- goto out_free_objects;
- }
- }
- }
- }
- ret = 0;
- out_free_objects:
- for (o = 0; o < object_count; o++) {
- if (objects[o])
- drm_mode_object_put(objects[o]);
- }
- kfree(objects);
- return ret;
- }
- /*
- * The master associated with the specified file will have a lease
- * created containing the objects specified in the ioctl structure.
- * A file descriptor will be allocated for that and returned to the
- * application.
- */
- int drm_mode_create_lease_ioctl(struct drm_device *dev,
- void *data, struct drm_file *lessor_priv)
- {
- struct drm_mode_create_lease *cl = data;
- size_t object_count;
- int ret = 0;
- struct idr leases;
- struct drm_master *lessor;
- struct drm_master *lessee = NULL;
- struct file *lessee_file = NULL;
- struct file *lessor_file = lessor_priv->filp;
- struct drm_file *lessee_priv;
- int fd = -1;
- uint32_t *object_ids;
- /* Can't lease without MODESET */
- if (!drm_core_check_feature(dev, DRIVER_MODESET))
- return -EOPNOTSUPP;
- if (cl->flags && (cl->flags & ~(O_CLOEXEC | O_NONBLOCK))) {
- DRM_DEBUG_LEASE("invalid flags\n");
- return -EINVAL;
- }
- lessor = drm_file_get_master(lessor_priv);
- /* Do not allow sub-leases */
- if (lessor->lessor) {
- DRM_DEBUG_LEASE("recursive leasing not allowed\n");
- ret = -EINVAL;
- goto out_lessor;
- }
- object_count = cl->object_count;
- /* Handle leased objects, if any */
- idr_init(&leases);
- if (object_count != 0) {
- object_ids = memdup_array_user(u64_to_user_ptr(cl->object_ids),
- object_count, sizeof(__u32));
- if (IS_ERR(object_ids)) {
- ret = PTR_ERR(object_ids);
- idr_destroy(&leases);
- goto out_lessor;
- }
- /* fill and validate the object idr */
- ret = fill_object_idr(dev, lessor_priv, &leases,
- object_count, object_ids);
- kfree(object_ids);
- if (ret) {
- DRM_DEBUG_LEASE("lease object lookup failed: %i\n", ret);
- idr_destroy(&leases);
- goto out_lessor;
- }
- }
- /* Allocate a file descriptor for the lease */
- fd = get_unused_fd_flags(cl->flags & (O_CLOEXEC | O_NONBLOCK));
- if (fd < 0) {
- idr_destroy(&leases);
- ret = fd;
- goto out_lessor;
- }
- DRM_DEBUG_LEASE("Creating lease\n");
- /* lessee will take the ownership of leases */
- lessee = drm_lease_create(lessor, &leases);
- if (IS_ERR(lessee)) {
- ret = PTR_ERR(lessee);
- idr_destroy(&leases);
- goto out_leases;
- }
- /* Clone the lessor file to create a new file for us */
- DRM_DEBUG_LEASE("Allocating lease file\n");
- lessee_file = file_clone_open(lessor_file);
- if (IS_ERR(lessee_file)) {
- ret = PTR_ERR(lessee_file);
- goto out_lessee;
- }
- lessee_priv = lessee_file->private_data;
- /* Change the file to a master one */
- drm_master_put(&lessee_priv->master);
- lessee_priv->master = lessee;
- lessee_priv->is_master = 1;
- lessee_priv->authenticated = 1;
- /* Pass fd back to userspace */
- DRM_DEBUG_LEASE("Returning fd %d id %d\n", fd, lessee->lessee_id);
- cl->fd = fd;
- cl->lessee_id = lessee->lessee_id;
- /* Hook up the fd */
- fd_install(fd, lessee_file);
- drm_master_put(&lessor);
- DRM_DEBUG_LEASE("drm_mode_create_lease_ioctl succeeded\n");
- return 0;
- out_lessee:
- drm_master_put(&lessee);
- out_leases:
- put_unused_fd(fd);
- out_lessor:
- drm_master_put(&lessor);
- DRM_DEBUG_LEASE("drm_mode_create_lease_ioctl failed: %d\n", ret);
- return ret;
- }
- int drm_mode_list_lessees_ioctl(struct drm_device *dev,
- void *data, struct drm_file *lessor_priv)
- {
- struct drm_mode_list_lessees *arg = data;
- __u32 __user *lessee_ids = (__u32 __user *) (uintptr_t) (arg->lessees_ptr);
- __u32 count_lessees = arg->count_lessees;
- struct drm_master *lessor, *lessee;
- int count;
- int ret = 0;
- if (arg->pad)
- return -EINVAL;
- /* Can't lease without MODESET */
- if (!drm_core_check_feature(dev, DRIVER_MODESET))
- return -EOPNOTSUPP;
- lessor = drm_file_get_master(lessor_priv);
- DRM_DEBUG_LEASE("List lessees for %d\n", lessor->lessee_id);
- mutex_lock(&dev->mode_config.idr_mutex);
- count = 0;
- drm_for_each_lessee(lessee, lessor) {
- /* Only list un-revoked leases */
- if (!idr_is_empty(&lessee->leases)) {
- if (count_lessees > count) {
- DRM_DEBUG_LEASE("Add lessee %d\n", lessee->lessee_id);
- ret = put_user(lessee->lessee_id, lessee_ids + count);
- if (ret)
- break;
- }
- count++;
- }
- }
- DRM_DEBUG_LEASE("Lessor leases to %d\n", count);
- if (ret == 0)
- arg->count_lessees = count;
- mutex_unlock(&dev->mode_config.idr_mutex);
- drm_master_put(&lessor);
- return ret;
- }
- /* Return the list of leased objects for the specified lessee */
- int drm_mode_get_lease_ioctl(struct drm_device *dev,
- void *data, struct drm_file *lessee_priv)
- {
- struct drm_mode_get_lease *arg = data;
- __u32 __user *object_ids = (__u32 __user *) (uintptr_t) (arg->objects_ptr);
- __u32 count_objects = arg->count_objects;
- struct drm_master *lessee;
- struct idr *object_idr;
- int count;
- void *entry;
- int object;
- int ret = 0;
- if (arg->pad)
- return -EINVAL;
- /* Can't lease without MODESET */
- if (!drm_core_check_feature(dev, DRIVER_MODESET))
- return -EOPNOTSUPP;
- lessee = drm_file_get_master(lessee_priv);
- DRM_DEBUG_LEASE("get lease for %d\n", lessee->lessee_id);
- mutex_lock(&dev->mode_config.idr_mutex);
- if (lessee->lessor == NULL)
- /* owner can use all objects */
- object_idr = &lessee->dev->mode_config.object_idr;
- else
- /* lessee can only use allowed object */
- object_idr = &lessee->leases;
- count = 0;
- idr_for_each_entry(object_idr, entry, object) {
- if (count_objects > count) {
- DRM_DEBUG_LEASE("adding object %d\n", object);
- ret = put_user(object, object_ids + count);
- if (ret)
- break;
- }
- count++;
- }
- DRM_DEBUG("lease holds %d objects\n", count);
- if (ret == 0)
- arg->count_objects = count;
- mutex_unlock(&dev->mode_config.idr_mutex);
- drm_master_put(&lessee);
- return ret;
- }
- /*
- * This removes all of the objects from the lease without
- * actually getting rid of the lease itself; that way all
- * references to it still work correctly
- */
- int drm_mode_revoke_lease_ioctl(struct drm_device *dev,
- void *data, struct drm_file *lessor_priv)
- {
- struct drm_mode_revoke_lease *arg = data;
- struct drm_master *lessor;
- struct drm_master *lessee;
- int ret = 0;
- DRM_DEBUG_LEASE("revoke lease for %d\n", arg->lessee_id);
- /* Can't lease without MODESET */
- if (!drm_core_check_feature(dev, DRIVER_MODESET))
- return -EOPNOTSUPP;
- lessor = drm_file_get_master(lessor_priv);
- mutex_lock(&dev->mode_config.idr_mutex);
- lessee = _drm_find_lessee(lessor, arg->lessee_id);
- /* No such lessee */
- if (!lessee) {
- ret = -ENOENT;
- goto fail;
- }
- /* Lease is not held by lessor */
- if (lessee->lessor != lessor) {
- ret = -EACCES;
- goto fail;
- }
- _drm_lease_revoke(lessee);
- fail:
- mutex_unlock(&dev->mode_config.idr_mutex);
- drm_master_put(&lessor);
- return ret;
- }
|