162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * Copyright © 2017 Keith Packard <keithp@keithp.com> 462306a36Sopenharmony_ci */ 562306a36Sopenharmony_ci#include <linux/file.h> 662306a36Sopenharmony_ci#include <linux/uaccess.h> 762306a36Sopenharmony_ci 862306a36Sopenharmony_ci#include <drm/drm_auth.h> 962306a36Sopenharmony_ci#include <drm/drm_crtc.h> 1062306a36Sopenharmony_ci#include <drm/drm_drv.h> 1162306a36Sopenharmony_ci#include <drm/drm_file.h> 1262306a36Sopenharmony_ci#include <drm/drm_lease.h> 1362306a36Sopenharmony_ci#include <drm/drm_print.h> 1462306a36Sopenharmony_ci 1562306a36Sopenharmony_ci#include "drm_crtc_internal.h" 1662306a36Sopenharmony_ci#include "drm_internal.h" 1762306a36Sopenharmony_ci 1862306a36Sopenharmony_ci/** 1962306a36Sopenharmony_ci * DOC: drm leasing 2062306a36Sopenharmony_ci * 2162306a36Sopenharmony_ci * DRM leases provide information about whether a DRM master may control a DRM 2262306a36Sopenharmony_ci * mode setting object. This enables the creation of multiple DRM masters that 2362306a36Sopenharmony_ci * manage subsets of display resources. 2462306a36Sopenharmony_ci * 2562306a36Sopenharmony_ci * The original DRM master of a device 'owns' the available drm resources. It 2662306a36Sopenharmony_ci * may create additional DRM masters and 'lease' resources which it controls 2762306a36Sopenharmony_ci * to the new DRM master. This gives the new DRM master control over the 2862306a36Sopenharmony_ci * leased resources until the owner revokes the lease, or the new DRM master 2962306a36Sopenharmony_ci * is closed. Some helpful terminology: 3062306a36Sopenharmony_ci * 3162306a36Sopenharmony_ci * - An 'owner' is a &struct drm_master that is not leasing objects from 3262306a36Sopenharmony_ci * another &struct drm_master, and hence 'owns' the objects. The owner can be 3362306a36Sopenharmony_ci * identified as the &struct drm_master for which &drm_master.lessor is NULL. 3462306a36Sopenharmony_ci * 3562306a36Sopenharmony_ci * - A 'lessor' is a &struct drm_master which is leasing objects to one or more 3662306a36Sopenharmony_ci * other &struct drm_master. Currently, lessees are not allowed to 3762306a36Sopenharmony_ci * create sub-leases, hence the lessor is the same as the owner. 3862306a36Sopenharmony_ci * 3962306a36Sopenharmony_ci * - A 'lessee' is a &struct drm_master which is leasing objects from some 4062306a36Sopenharmony_ci * other &struct drm_master. Each lessee only leases resources from a single 4162306a36Sopenharmony_ci * lessor recorded in &drm_master.lessor, and holds the set of objects that 4262306a36Sopenharmony_ci * it is leasing in &drm_master.leases. 4362306a36Sopenharmony_ci * 4462306a36Sopenharmony_ci * - A 'lease' is a contract between the lessor and lessee that identifies 4562306a36Sopenharmony_ci * which resources may be controlled by the lessee. All of the resources 4662306a36Sopenharmony_ci * that are leased must be owned by or leased to the lessor, and lessors are 4762306a36Sopenharmony_ci * not permitted to lease the same object to multiple lessees. 4862306a36Sopenharmony_ci * 4962306a36Sopenharmony_ci * The set of objects any &struct drm_master 'controls' is limited to the set 5062306a36Sopenharmony_ci * of objects it leases (for lessees) or all objects (for owners). 5162306a36Sopenharmony_ci * 5262306a36Sopenharmony_ci * Objects not controlled by a &struct drm_master cannot be modified through 5362306a36Sopenharmony_ci * the various state manipulating ioctls, and any state reported back to user 5462306a36Sopenharmony_ci * space will be edited to make them appear idle and/or unusable. For 5562306a36Sopenharmony_ci * instance, connectors always report 'disconnected', while encoders 5662306a36Sopenharmony_ci * report no possible crtcs or clones. 5762306a36Sopenharmony_ci * 5862306a36Sopenharmony_ci * Since each lessee may lease objects from a single lessor, display resource 5962306a36Sopenharmony_ci * leases form a tree of &struct drm_master. As lessees are currently not 6062306a36Sopenharmony_ci * allowed to create sub-leases, the tree depth is limited to 1. All of 6162306a36Sopenharmony_ci * these get activated simultaneously when the top level device owner changes 6262306a36Sopenharmony_ci * through the SETMASTER or DROPMASTER IOCTL, so &drm_device.master points to 6362306a36Sopenharmony_ci * the owner at the top of the lease tree (i.e. the &struct drm_master for which 6462306a36Sopenharmony_ci * &drm_master.lessor is NULL). The full list of lessees that are leasing 6562306a36Sopenharmony_ci * objects from the owner can be searched via the owner's 6662306a36Sopenharmony_ci * &drm_master.lessee_idr. 6762306a36Sopenharmony_ci */ 6862306a36Sopenharmony_ci 6962306a36Sopenharmony_ci#define drm_for_each_lessee(lessee, lessor) \ 7062306a36Sopenharmony_ci list_for_each_entry((lessee), &(lessor)->lessees, lessee_list) 7162306a36Sopenharmony_ci 7262306a36Sopenharmony_cistatic uint64_t drm_lease_idr_object; 7362306a36Sopenharmony_ci 7462306a36Sopenharmony_cistruct drm_master *drm_lease_owner(struct drm_master *master) 7562306a36Sopenharmony_ci{ 7662306a36Sopenharmony_ci while (master->lessor != NULL) 7762306a36Sopenharmony_ci master = master->lessor; 7862306a36Sopenharmony_ci return master; 7962306a36Sopenharmony_ci} 8062306a36Sopenharmony_ci 8162306a36Sopenharmony_cistatic struct drm_master* 8262306a36Sopenharmony_ci_drm_find_lessee(struct drm_master *master, int lessee_id) 8362306a36Sopenharmony_ci{ 8462306a36Sopenharmony_ci lockdep_assert_held(&master->dev->mode_config.idr_mutex); 8562306a36Sopenharmony_ci return idr_find(&drm_lease_owner(master)->lessee_idr, lessee_id); 8662306a36Sopenharmony_ci} 8762306a36Sopenharmony_ci 8862306a36Sopenharmony_cistatic int _drm_lease_held_master(struct drm_master *master, int id) 8962306a36Sopenharmony_ci{ 9062306a36Sopenharmony_ci lockdep_assert_held(&master->dev->mode_config.idr_mutex); 9162306a36Sopenharmony_ci if (master->lessor) 9262306a36Sopenharmony_ci return idr_find(&master->leases, id) != NULL; 9362306a36Sopenharmony_ci return true; 9462306a36Sopenharmony_ci} 9562306a36Sopenharmony_ci 9662306a36Sopenharmony_ci/* Checks if the given object has been leased to some lessee of drm_master */ 9762306a36Sopenharmony_cistatic bool _drm_has_leased(struct drm_master *master, int id) 9862306a36Sopenharmony_ci{ 9962306a36Sopenharmony_ci struct drm_master *lessee; 10062306a36Sopenharmony_ci 10162306a36Sopenharmony_ci lockdep_assert_held(&master->dev->mode_config.idr_mutex); 10262306a36Sopenharmony_ci drm_for_each_lessee(lessee, master) 10362306a36Sopenharmony_ci if (_drm_lease_held_master(lessee, id)) 10462306a36Sopenharmony_ci return true; 10562306a36Sopenharmony_ci return false; 10662306a36Sopenharmony_ci} 10762306a36Sopenharmony_ci 10862306a36Sopenharmony_ci/* Called with idr_mutex held */ 10962306a36Sopenharmony_cibool _drm_lease_held(struct drm_file *file_priv, int id) 11062306a36Sopenharmony_ci{ 11162306a36Sopenharmony_ci bool ret; 11262306a36Sopenharmony_ci struct drm_master *master; 11362306a36Sopenharmony_ci 11462306a36Sopenharmony_ci if (!file_priv) 11562306a36Sopenharmony_ci return true; 11662306a36Sopenharmony_ci 11762306a36Sopenharmony_ci master = drm_file_get_master(file_priv); 11862306a36Sopenharmony_ci if (!master) 11962306a36Sopenharmony_ci return true; 12062306a36Sopenharmony_ci ret = _drm_lease_held_master(master, id); 12162306a36Sopenharmony_ci drm_master_put(&master); 12262306a36Sopenharmony_ci 12362306a36Sopenharmony_ci return ret; 12462306a36Sopenharmony_ci} 12562306a36Sopenharmony_ci 12662306a36Sopenharmony_cibool drm_lease_held(struct drm_file *file_priv, int id) 12762306a36Sopenharmony_ci{ 12862306a36Sopenharmony_ci struct drm_master *master; 12962306a36Sopenharmony_ci bool ret; 13062306a36Sopenharmony_ci 13162306a36Sopenharmony_ci if (!file_priv) 13262306a36Sopenharmony_ci return true; 13362306a36Sopenharmony_ci 13462306a36Sopenharmony_ci master = drm_file_get_master(file_priv); 13562306a36Sopenharmony_ci if (!master) 13662306a36Sopenharmony_ci return true; 13762306a36Sopenharmony_ci if (!master->lessor) { 13862306a36Sopenharmony_ci ret = true; 13962306a36Sopenharmony_ci goto out; 14062306a36Sopenharmony_ci } 14162306a36Sopenharmony_ci mutex_lock(&master->dev->mode_config.idr_mutex); 14262306a36Sopenharmony_ci ret = _drm_lease_held_master(master, id); 14362306a36Sopenharmony_ci mutex_unlock(&master->dev->mode_config.idr_mutex); 14462306a36Sopenharmony_ci 14562306a36Sopenharmony_ciout: 14662306a36Sopenharmony_ci drm_master_put(&master); 14762306a36Sopenharmony_ci return ret; 14862306a36Sopenharmony_ci} 14962306a36Sopenharmony_ci 15062306a36Sopenharmony_ci/* 15162306a36Sopenharmony_ci * Given a bitmask of crtcs to check, reconstructs a crtc mask based on the 15262306a36Sopenharmony_ci * crtcs which are visible through the specified file. 15362306a36Sopenharmony_ci */ 15462306a36Sopenharmony_ciuint32_t drm_lease_filter_crtcs(struct drm_file *file_priv, uint32_t crtcs_in) 15562306a36Sopenharmony_ci{ 15662306a36Sopenharmony_ci struct drm_master *master; 15762306a36Sopenharmony_ci struct drm_device *dev; 15862306a36Sopenharmony_ci struct drm_crtc *crtc; 15962306a36Sopenharmony_ci int count_in, count_out; 16062306a36Sopenharmony_ci uint32_t crtcs_out = 0; 16162306a36Sopenharmony_ci 16262306a36Sopenharmony_ci if (!file_priv) 16362306a36Sopenharmony_ci return crtcs_in; 16462306a36Sopenharmony_ci 16562306a36Sopenharmony_ci master = drm_file_get_master(file_priv); 16662306a36Sopenharmony_ci if (!master) 16762306a36Sopenharmony_ci return crtcs_in; 16862306a36Sopenharmony_ci if (!master->lessor) { 16962306a36Sopenharmony_ci crtcs_out = crtcs_in; 17062306a36Sopenharmony_ci goto out; 17162306a36Sopenharmony_ci } 17262306a36Sopenharmony_ci dev = master->dev; 17362306a36Sopenharmony_ci 17462306a36Sopenharmony_ci count_in = count_out = 0; 17562306a36Sopenharmony_ci mutex_lock(&master->dev->mode_config.idr_mutex); 17662306a36Sopenharmony_ci list_for_each_entry(crtc, &dev->mode_config.crtc_list, head) { 17762306a36Sopenharmony_ci if (_drm_lease_held_master(master, crtc->base.id)) { 17862306a36Sopenharmony_ci uint32_t mask_in = 1ul << count_in; 17962306a36Sopenharmony_ci 18062306a36Sopenharmony_ci if ((crtcs_in & mask_in) != 0) { 18162306a36Sopenharmony_ci uint32_t mask_out = 1ul << count_out; 18262306a36Sopenharmony_ci 18362306a36Sopenharmony_ci crtcs_out |= mask_out; 18462306a36Sopenharmony_ci } 18562306a36Sopenharmony_ci count_out++; 18662306a36Sopenharmony_ci } 18762306a36Sopenharmony_ci count_in++; 18862306a36Sopenharmony_ci } 18962306a36Sopenharmony_ci mutex_unlock(&master->dev->mode_config.idr_mutex); 19062306a36Sopenharmony_ci 19162306a36Sopenharmony_ciout: 19262306a36Sopenharmony_ci drm_master_put(&master); 19362306a36Sopenharmony_ci return crtcs_out; 19462306a36Sopenharmony_ci} 19562306a36Sopenharmony_ci 19662306a36Sopenharmony_ci/* 19762306a36Sopenharmony_ci * Uses drm_master_create to allocate a new drm_master, then checks to 19862306a36Sopenharmony_ci * make sure all of the desired objects can be leased, atomically 19962306a36Sopenharmony_ci * leasing them to the new drmmaster. 20062306a36Sopenharmony_ci * 20162306a36Sopenharmony_ci * ERR_PTR(-EACCES) some other master holds the title to any object 20262306a36Sopenharmony_ci * ERR_PTR(-ENOENT) some object is not a valid DRM object for this device 20362306a36Sopenharmony_ci * ERR_PTR(-EBUSY) some other lessee holds title to this object 20462306a36Sopenharmony_ci * ERR_PTR(-EEXIST) same object specified more than once in the provided list 20562306a36Sopenharmony_ci * ERR_PTR(-ENOMEM) allocation failed 20662306a36Sopenharmony_ci */ 20762306a36Sopenharmony_cistatic struct drm_master *drm_lease_create(struct drm_master *lessor, struct idr *leases) 20862306a36Sopenharmony_ci{ 20962306a36Sopenharmony_ci struct drm_device *dev = lessor->dev; 21062306a36Sopenharmony_ci int error; 21162306a36Sopenharmony_ci struct drm_master *lessee; 21262306a36Sopenharmony_ci int object; 21362306a36Sopenharmony_ci int id; 21462306a36Sopenharmony_ci void *entry; 21562306a36Sopenharmony_ci 21662306a36Sopenharmony_ci drm_dbg_lease(dev, "lessor %d\n", lessor->lessee_id); 21762306a36Sopenharmony_ci 21862306a36Sopenharmony_ci lessee = drm_master_create(lessor->dev); 21962306a36Sopenharmony_ci if (!lessee) { 22062306a36Sopenharmony_ci drm_dbg_lease(dev, "drm_master_create failed\n"); 22162306a36Sopenharmony_ci return ERR_PTR(-ENOMEM); 22262306a36Sopenharmony_ci } 22362306a36Sopenharmony_ci 22462306a36Sopenharmony_ci mutex_lock(&dev->mode_config.idr_mutex); 22562306a36Sopenharmony_ci 22662306a36Sopenharmony_ci idr_for_each_entry(leases, entry, object) { 22762306a36Sopenharmony_ci error = 0; 22862306a36Sopenharmony_ci if (!idr_find(&dev->mode_config.object_idr, object)) 22962306a36Sopenharmony_ci error = -ENOENT; 23062306a36Sopenharmony_ci else if (_drm_has_leased(lessor, object)) 23162306a36Sopenharmony_ci error = -EBUSY; 23262306a36Sopenharmony_ci 23362306a36Sopenharmony_ci if (error != 0) { 23462306a36Sopenharmony_ci drm_dbg_lease(dev, "object %d failed %d\n", object, error); 23562306a36Sopenharmony_ci goto out_lessee; 23662306a36Sopenharmony_ci } 23762306a36Sopenharmony_ci } 23862306a36Sopenharmony_ci 23962306a36Sopenharmony_ci /* Insert the new lessee into the tree */ 24062306a36Sopenharmony_ci id = idr_alloc(&(drm_lease_owner(lessor)->lessee_idr), lessee, 1, 0, GFP_KERNEL); 24162306a36Sopenharmony_ci if (id < 0) { 24262306a36Sopenharmony_ci error = id; 24362306a36Sopenharmony_ci goto out_lessee; 24462306a36Sopenharmony_ci } 24562306a36Sopenharmony_ci 24662306a36Sopenharmony_ci lessee->lessee_id = id; 24762306a36Sopenharmony_ci lessee->lessor = drm_master_get(lessor); 24862306a36Sopenharmony_ci list_add_tail(&lessee->lessee_list, &lessor->lessees); 24962306a36Sopenharmony_ci 25062306a36Sopenharmony_ci /* Move the leases over */ 25162306a36Sopenharmony_ci lessee->leases = *leases; 25262306a36Sopenharmony_ci drm_dbg_lease(dev, "new lessee %d %p, lessor %d %p\n", 25362306a36Sopenharmony_ci lessee->lessee_id, lessee, lessor->lessee_id, lessor); 25462306a36Sopenharmony_ci 25562306a36Sopenharmony_ci mutex_unlock(&dev->mode_config.idr_mutex); 25662306a36Sopenharmony_ci return lessee; 25762306a36Sopenharmony_ci 25862306a36Sopenharmony_ciout_lessee: 25962306a36Sopenharmony_ci mutex_unlock(&dev->mode_config.idr_mutex); 26062306a36Sopenharmony_ci 26162306a36Sopenharmony_ci drm_master_put(&lessee); 26262306a36Sopenharmony_ci 26362306a36Sopenharmony_ci return ERR_PTR(error); 26462306a36Sopenharmony_ci} 26562306a36Sopenharmony_ci 26662306a36Sopenharmony_civoid drm_lease_destroy(struct drm_master *master) 26762306a36Sopenharmony_ci{ 26862306a36Sopenharmony_ci struct drm_device *dev = master->dev; 26962306a36Sopenharmony_ci 27062306a36Sopenharmony_ci mutex_lock(&dev->mode_config.idr_mutex); 27162306a36Sopenharmony_ci 27262306a36Sopenharmony_ci drm_dbg_lease(dev, "drm_lease_destroy %d\n", master->lessee_id); 27362306a36Sopenharmony_ci 27462306a36Sopenharmony_ci /* This master is referenced by all lessees, hence it cannot be destroyed 27562306a36Sopenharmony_ci * until all of them have been 27662306a36Sopenharmony_ci */ 27762306a36Sopenharmony_ci WARN_ON(!list_empty(&master->lessees)); 27862306a36Sopenharmony_ci 27962306a36Sopenharmony_ci /* Remove this master from the lessee idr in the owner */ 28062306a36Sopenharmony_ci if (master->lessee_id != 0) { 28162306a36Sopenharmony_ci drm_dbg_lease(dev, "remove master %d from device list of lessees\n", 28262306a36Sopenharmony_ci master->lessee_id); 28362306a36Sopenharmony_ci idr_remove(&(drm_lease_owner(master)->lessee_idr), master->lessee_id); 28462306a36Sopenharmony_ci } 28562306a36Sopenharmony_ci 28662306a36Sopenharmony_ci /* Remove this master from any lessee list it may be on */ 28762306a36Sopenharmony_ci list_del(&master->lessee_list); 28862306a36Sopenharmony_ci 28962306a36Sopenharmony_ci mutex_unlock(&dev->mode_config.idr_mutex); 29062306a36Sopenharmony_ci 29162306a36Sopenharmony_ci if (master->lessor) { 29262306a36Sopenharmony_ci /* Tell the master to check the lessee list */ 29362306a36Sopenharmony_ci drm_sysfs_lease_event(dev); 29462306a36Sopenharmony_ci drm_master_put(&master->lessor); 29562306a36Sopenharmony_ci } 29662306a36Sopenharmony_ci 29762306a36Sopenharmony_ci drm_dbg_lease(dev, "drm_lease_destroy done %d\n", master->lessee_id); 29862306a36Sopenharmony_ci} 29962306a36Sopenharmony_ci 30062306a36Sopenharmony_cistatic void _drm_lease_revoke(struct drm_master *top) 30162306a36Sopenharmony_ci{ 30262306a36Sopenharmony_ci int object; 30362306a36Sopenharmony_ci void *entry; 30462306a36Sopenharmony_ci struct drm_master *master = top; 30562306a36Sopenharmony_ci 30662306a36Sopenharmony_ci lockdep_assert_held(&top->dev->mode_config.idr_mutex); 30762306a36Sopenharmony_ci 30862306a36Sopenharmony_ci /* 30962306a36Sopenharmony_ci * Walk the tree starting at 'top' emptying all leases. Because 31062306a36Sopenharmony_ci * the tree is fully connected, we can do this without recursing 31162306a36Sopenharmony_ci */ 31262306a36Sopenharmony_ci for (;;) { 31362306a36Sopenharmony_ci drm_dbg_lease(master->dev, "revoke leases for %p %d\n", 31462306a36Sopenharmony_ci master, master->lessee_id); 31562306a36Sopenharmony_ci 31662306a36Sopenharmony_ci /* Evacuate the lease */ 31762306a36Sopenharmony_ci idr_for_each_entry(&master->leases, entry, object) 31862306a36Sopenharmony_ci idr_remove(&master->leases, object); 31962306a36Sopenharmony_ci 32062306a36Sopenharmony_ci /* Depth-first list walk */ 32162306a36Sopenharmony_ci 32262306a36Sopenharmony_ci /* Down */ 32362306a36Sopenharmony_ci if (!list_empty(&master->lessees)) { 32462306a36Sopenharmony_ci master = list_first_entry(&master->lessees, struct drm_master, lessee_list); 32562306a36Sopenharmony_ci } else { 32662306a36Sopenharmony_ci /* Up */ 32762306a36Sopenharmony_ci while (master != top && master == list_last_entry(&master->lessor->lessees, struct drm_master, lessee_list)) 32862306a36Sopenharmony_ci master = master->lessor; 32962306a36Sopenharmony_ci 33062306a36Sopenharmony_ci if (master == top) 33162306a36Sopenharmony_ci break; 33262306a36Sopenharmony_ci 33362306a36Sopenharmony_ci /* Over */ 33462306a36Sopenharmony_ci master = list_next_entry(master, lessee_list); 33562306a36Sopenharmony_ci } 33662306a36Sopenharmony_ci } 33762306a36Sopenharmony_ci} 33862306a36Sopenharmony_ci 33962306a36Sopenharmony_civoid drm_lease_revoke(struct drm_master *top) 34062306a36Sopenharmony_ci{ 34162306a36Sopenharmony_ci mutex_lock(&top->dev->mode_config.idr_mutex); 34262306a36Sopenharmony_ci _drm_lease_revoke(top); 34362306a36Sopenharmony_ci mutex_unlock(&top->dev->mode_config.idr_mutex); 34462306a36Sopenharmony_ci} 34562306a36Sopenharmony_ci 34662306a36Sopenharmony_cistatic int validate_lease(struct drm_device *dev, 34762306a36Sopenharmony_ci int object_count, 34862306a36Sopenharmony_ci struct drm_mode_object **objects, 34962306a36Sopenharmony_ci bool universal_planes) 35062306a36Sopenharmony_ci{ 35162306a36Sopenharmony_ci int o; 35262306a36Sopenharmony_ci int has_crtc = -1; 35362306a36Sopenharmony_ci int has_connector = -1; 35462306a36Sopenharmony_ci int has_plane = -1; 35562306a36Sopenharmony_ci 35662306a36Sopenharmony_ci /* we want to confirm that there is at least one crtc, plane 35762306a36Sopenharmony_ci connector object. */ 35862306a36Sopenharmony_ci 35962306a36Sopenharmony_ci for (o = 0; o < object_count; o++) { 36062306a36Sopenharmony_ci if (objects[o]->type == DRM_MODE_OBJECT_CRTC && has_crtc == -1) { 36162306a36Sopenharmony_ci has_crtc = o; 36262306a36Sopenharmony_ci } 36362306a36Sopenharmony_ci if (objects[o]->type == DRM_MODE_OBJECT_CONNECTOR && has_connector == -1) 36462306a36Sopenharmony_ci has_connector = o; 36562306a36Sopenharmony_ci 36662306a36Sopenharmony_ci if (universal_planes) { 36762306a36Sopenharmony_ci if (objects[o]->type == DRM_MODE_OBJECT_PLANE && has_plane == -1) 36862306a36Sopenharmony_ci has_plane = o; 36962306a36Sopenharmony_ci } 37062306a36Sopenharmony_ci } 37162306a36Sopenharmony_ci if (has_crtc == -1 || has_connector == -1) 37262306a36Sopenharmony_ci return -EINVAL; 37362306a36Sopenharmony_ci if (universal_planes && has_plane == -1) 37462306a36Sopenharmony_ci return -EINVAL; 37562306a36Sopenharmony_ci return 0; 37662306a36Sopenharmony_ci} 37762306a36Sopenharmony_ci 37862306a36Sopenharmony_cistatic int fill_object_idr(struct drm_device *dev, 37962306a36Sopenharmony_ci struct drm_file *lessor_priv, 38062306a36Sopenharmony_ci struct idr *leases, 38162306a36Sopenharmony_ci int object_count, 38262306a36Sopenharmony_ci u32 *object_ids) 38362306a36Sopenharmony_ci{ 38462306a36Sopenharmony_ci struct drm_mode_object **objects; 38562306a36Sopenharmony_ci u32 o; 38662306a36Sopenharmony_ci int ret; 38762306a36Sopenharmony_ci bool universal_planes = READ_ONCE(lessor_priv->universal_planes); 38862306a36Sopenharmony_ci 38962306a36Sopenharmony_ci objects = kcalloc(object_count, sizeof(struct drm_mode_object *), 39062306a36Sopenharmony_ci GFP_KERNEL); 39162306a36Sopenharmony_ci if (!objects) 39262306a36Sopenharmony_ci return -ENOMEM; 39362306a36Sopenharmony_ci 39462306a36Sopenharmony_ci /* step one - get references to all the mode objects 39562306a36Sopenharmony_ci and check for validity. */ 39662306a36Sopenharmony_ci for (o = 0; o < object_count; o++) { 39762306a36Sopenharmony_ci objects[o] = drm_mode_object_find(dev, lessor_priv, 39862306a36Sopenharmony_ci object_ids[o], 39962306a36Sopenharmony_ci DRM_MODE_OBJECT_ANY); 40062306a36Sopenharmony_ci if (!objects[o]) { 40162306a36Sopenharmony_ci ret = -ENOENT; 40262306a36Sopenharmony_ci goto out_free_objects; 40362306a36Sopenharmony_ci } 40462306a36Sopenharmony_ci 40562306a36Sopenharmony_ci if (!drm_mode_object_lease_required(objects[o]->type)) { 40662306a36Sopenharmony_ci DRM_DEBUG_KMS("invalid object for lease\n"); 40762306a36Sopenharmony_ci ret = -EINVAL; 40862306a36Sopenharmony_ci goto out_free_objects; 40962306a36Sopenharmony_ci } 41062306a36Sopenharmony_ci } 41162306a36Sopenharmony_ci 41262306a36Sopenharmony_ci ret = validate_lease(dev, object_count, objects, universal_planes); 41362306a36Sopenharmony_ci if (ret) { 41462306a36Sopenharmony_ci drm_dbg_lease(dev, "lease validation failed\n"); 41562306a36Sopenharmony_ci goto out_free_objects; 41662306a36Sopenharmony_ci } 41762306a36Sopenharmony_ci 41862306a36Sopenharmony_ci /* add their IDs to the lease request - taking into account 41962306a36Sopenharmony_ci universal planes */ 42062306a36Sopenharmony_ci for (o = 0; o < object_count; o++) { 42162306a36Sopenharmony_ci struct drm_mode_object *obj = objects[o]; 42262306a36Sopenharmony_ci u32 object_id = objects[o]->id; 42362306a36Sopenharmony_ci 42462306a36Sopenharmony_ci drm_dbg_lease(dev, "Adding object %d to lease\n", object_id); 42562306a36Sopenharmony_ci 42662306a36Sopenharmony_ci /* 42762306a36Sopenharmony_ci * We're using an IDR to hold the set of leased 42862306a36Sopenharmony_ci * objects, but we don't need to point at the object's 42962306a36Sopenharmony_ci * data structure from the lease as the main object_idr 43062306a36Sopenharmony_ci * will be used to actually find that. Instead, all we 43162306a36Sopenharmony_ci * really want is a 'leased/not-leased' result, for 43262306a36Sopenharmony_ci * which any non-NULL pointer will work fine. 43362306a36Sopenharmony_ci */ 43462306a36Sopenharmony_ci ret = idr_alloc(leases, &drm_lease_idr_object , object_id, object_id + 1, GFP_KERNEL); 43562306a36Sopenharmony_ci if (ret < 0) { 43662306a36Sopenharmony_ci drm_dbg_lease(dev, "Object %d cannot be inserted into leases (%d)\n", 43762306a36Sopenharmony_ci object_id, ret); 43862306a36Sopenharmony_ci goto out_free_objects; 43962306a36Sopenharmony_ci } 44062306a36Sopenharmony_ci if (obj->type == DRM_MODE_OBJECT_CRTC && !universal_planes) { 44162306a36Sopenharmony_ci struct drm_crtc *crtc = obj_to_crtc(obj); 44262306a36Sopenharmony_ci 44362306a36Sopenharmony_ci ret = idr_alloc(leases, &drm_lease_idr_object, crtc->primary->base.id, crtc->primary->base.id + 1, GFP_KERNEL); 44462306a36Sopenharmony_ci if (ret < 0) { 44562306a36Sopenharmony_ci drm_dbg_lease(dev, "Object primary plane %d cannot be inserted into leases (%d)\n", 44662306a36Sopenharmony_ci object_id, ret); 44762306a36Sopenharmony_ci goto out_free_objects; 44862306a36Sopenharmony_ci } 44962306a36Sopenharmony_ci if (crtc->cursor) { 45062306a36Sopenharmony_ci ret = idr_alloc(leases, &drm_lease_idr_object, crtc->cursor->base.id, crtc->cursor->base.id + 1, GFP_KERNEL); 45162306a36Sopenharmony_ci if (ret < 0) { 45262306a36Sopenharmony_ci drm_dbg_lease(dev, "Object cursor plane %d cannot be inserted into leases (%d)\n", 45362306a36Sopenharmony_ci object_id, ret); 45462306a36Sopenharmony_ci goto out_free_objects; 45562306a36Sopenharmony_ci } 45662306a36Sopenharmony_ci } 45762306a36Sopenharmony_ci } 45862306a36Sopenharmony_ci } 45962306a36Sopenharmony_ci 46062306a36Sopenharmony_ci ret = 0; 46162306a36Sopenharmony_ciout_free_objects: 46262306a36Sopenharmony_ci for (o = 0; o < object_count; o++) { 46362306a36Sopenharmony_ci if (objects[o]) 46462306a36Sopenharmony_ci drm_mode_object_put(objects[o]); 46562306a36Sopenharmony_ci } 46662306a36Sopenharmony_ci kfree(objects); 46762306a36Sopenharmony_ci return ret; 46862306a36Sopenharmony_ci} 46962306a36Sopenharmony_ci 47062306a36Sopenharmony_ci/* 47162306a36Sopenharmony_ci * The master associated with the specified file will have a lease 47262306a36Sopenharmony_ci * created containing the objects specified in the ioctl structure. 47362306a36Sopenharmony_ci * A file descriptor will be allocated for that and returned to the 47462306a36Sopenharmony_ci * application. 47562306a36Sopenharmony_ci */ 47662306a36Sopenharmony_ciint drm_mode_create_lease_ioctl(struct drm_device *dev, 47762306a36Sopenharmony_ci void *data, struct drm_file *lessor_priv) 47862306a36Sopenharmony_ci{ 47962306a36Sopenharmony_ci struct drm_mode_create_lease *cl = data; 48062306a36Sopenharmony_ci size_t object_count; 48162306a36Sopenharmony_ci int ret = 0; 48262306a36Sopenharmony_ci struct idr leases; 48362306a36Sopenharmony_ci struct drm_master *lessor; 48462306a36Sopenharmony_ci struct drm_master *lessee = NULL; 48562306a36Sopenharmony_ci struct file *lessee_file = NULL; 48662306a36Sopenharmony_ci struct file *lessor_file = lessor_priv->filp; 48762306a36Sopenharmony_ci struct drm_file *lessee_priv; 48862306a36Sopenharmony_ci int fd = -1; 48962306a36Sopenharmony_ci uint32_t *object_ids; 49062306a36Sopenharmony_ci 49162306a36Sopenharmony_ci /* Can't lease without MODESET */ 49262306a36Sopenharmony_ci if (!drm_core_check_feature(dev, DRIVER_MODESET)) 49362306a36Sopenharmony_ci return -EOPNOTSUPP; 49462306a36Sopenharmony_ci 49562306a36Sopenharmony_ci if (cl->flags && (cl->flags & ~(O_CLOEXEC | O_NONBLOCK))) { 49662306a36Sopenharmony_ci drm_dbg_lease(dev, "invalid flags\n"); 49762306a36Sopenharmony_ci return -EINVAL; 49862306a36Sopenharmony_ci } 49962306a36Sopenharmony_ci 50062306a36Sopenharmony_ci lessor = drm_file_get_master(lessor_priv); 50162306a36Sopenharmony_ci /* Do not allow sub-leases */ 50262306a36Sopenharmony_ci if (lessor->lessor) { 50362306a36Sopenharmony_ci drm_dbg_lease(dev, "recursive leasing not allowed\n"); 50462306a36Sopenharmony_ci ret = -EINVAL; 50562306a36Sopenharmony_ci goto out_lessor; 50662306a36Sopenharmony_ci } 50762306a36Sopenharmony_ci 50862306a36Sopenharmony_ci object_count = cl->object_count; 50962306a36Sopenharmony_ci 51062306a36Sopenharmony_ci /* Handle leased objects, if any */ 51162306a36Sopenharmony_ci idr_init(&leases); 51262306a36Sopenharmony_ci if (object_count != 0) { 51362306a36Sopenharmony_ci object_ids = memdup_array_user(u64_to_user_ptr(cl->object_ids), 51462306a36Sopenharmony_ci object_count, sizeof(__u32)); 51562306a36Sopenharmony_ci if (IS_ERR(object_ids)) { 51662306a36Sopenharmony_ci ret = PTR_ERR(object_ids); 51762306a36Sopenharmony_ci idr_destroy(&leases); 51862306a36Sopenharmony_ci goto out_lessor; 51962306a36Sopenharmony_ci } 52062306a36Sopenharmony_ci 52162306a36Sopenharmony_ci /* fill and validate the object idr */ 52262306a36Sopenharmony_ci ret = fill_object_idr(dev, lessor_priv, &leases, 52362306a36Sopenharmony_ci object_count, object_ids); 52462306a36Sopenharmony_ci kfree(object_ids); 52562306a36Sopenharmony_ci if (ret) { 52662306a36Sopenharmony_ci drm_dbg_lease(dev, "lease object lookup failed: %i\n", ret); 52762306a36Sopenharmony_ci idr_destroy(&leases); 52862306a36Sopenharmony_ci goto out_lessor; 52962306a36Sopenharmony_ci } 53062306a36Sopenharmony_ci } 53162306a36Sopenharmony_ci 53262306a36Sopenharmony_ci /* Allocate a file descriptor for the lease */ 53362306a36Sopenharmony_ci fd = get_unused_fd_flags(cl->flags & (O_CLOEXEC | O_NONBLOCK)); 53462306a36Sopenharmony_ci if (fd < 0) { 53562306a36Sopenharmony_ci idr_destroy(&leases); 53662306a36Sopenharmony_ci ret = fd; 53762306a36Sopenharmony_ci goto out_lessor; 53862306a36Sopenharmony_ci } 53962306a36Sopenharmony_ci 54062306a36Sopenharmony_ci drm_dbg_lease(dev, "Creating lease\n"); 54162306a36Sopenharmony_ci /* lessee will take the ownership of leases */ 54262306a36Sopenharmony_ci lessee = drm_lease_create(lessor, &leases); 54362306a36Sopenharmony_ci 54462306a36Sopenharmony_ci if (IS_ERR(lessee)) { 54562306a36Sopenharmony_ci ret = PTR_ERR(lessee); 54662306a36Sopenharmony_ci idr_destroy(&leases); 54762306a36Sopenharmony_ci goto out_leases; 54862306a36Sopenharmony_ci } 54962306a36Sopenharmony_ci 55062306a36Sopenharmony_ci /* Clone the lessor file to create a new file for us */ 55162306a36Sopenharmony_ci drm_dbg_lease(dev, "Allocating lease file\n"); 55262306a36Sopenharmony_ci lessee_file = file_clone_open(lessor_file); 55362306a36Sopenharmony_ci if (IS_ERR(lessee_file)) { 55462306a36Sopenharmony_ci ret = PTR_ERR(lessee_file); 55562306a36Sopenharmony_ci goto out_lessee; 55662306a36Sopenharmony_ci } 55762306a36Sopenharmony_ci 55862306a36Sopenharmony_ci lessee_priv = lessee_file->private_data; 55962306a36Sopenharmony_ci /* Change the file to a master one */ 56062306a36Sopenharmony_ci drm_master_put(&lessee_priv->master); 56162306a36Sopenharmony_ci lessee_priv->master = lessee; 56262306a36Sopenharmony_ci lessee_priv->is_master = 1; 56362306a36Sopenharmony_ci lessee_priv->authenticated = 1; 56462306a36Sopenharmony_ci 56562306a36Sopenharmony_ci /* Pass fd back to userspace */ 56662306a36Sopenharmony_ci drm_dbg_lease(dev, "Returning fd %d id %d\n", fd, lessee->lessee_id); 56762306a36Sopenharmony_ci cl->fd = fd; 56862306a36Sopenharmony_ci cl->lessee_id = lessee->lessee_id; 56962306a36Sopenharmony_ci 57062306a36Sopenharmony_ci /* Hook up the fd */ 57162306a36Sopenharmony_ci fd_install(fd, lessee_file); 57262306a36Sopenharmony_ci 57362306a36Sopenharmony_ci drm_master_put(&lessor); 57462306a36Sopenharmony_ci drm_dbg_lease(dev, "drm_mode_create_lease_ioctl succeeded\n"); 57562306a36Sopenharmony_ci return 0; 57662306a36Sopenharmony_ci 57762306a36Sopenharmony_ciout_lessee: 57862306a36Sopenharmony_ci drm_master_put(&lessee); 57962306a36Sopenharmony_ci 58062306a36Sopenharmony_ciout_leases: 58162306a36Sopenharmony_ci put_unused_fd(fd); 58262306a36Sopenharmony_ci 58362306a36Sopenharmony_ciout_lessor: 58462306a36Sopenharmony_ci drm_master_put(&lessor); 58562306a36Sopenharmony_ci drm_dbg_lease(dev, "drm_mode_create_lease_ioctl failed: %d\n", ret); 58662306a36Sopenharmony_ci return ret; 58762306a36Sopenharmony_ci} 58862306a36Sopenharmony_ci 58962306a36Sopenharmony_ciint drm_mode_list_lessees_ioctl(struct drm_device *dev, 59062306a36Sopenharmony_ci void *data, struct drm_file *lessor_priv) 59162306a36Sopenharmony_ci{ 59262306a36Sopenharmony_ci struct drm_mode_list_lessees *arg = data; 59362306a36Sopenharmony_ci __u32 __user *lessee_ids = (__u32 __user *) (uintptr_t) (arg->lessees_ptr); 59462306a36Sopenharmony_ci __u32 count_lessees = arg->count_lessees; 59562306a36Sopenharmony_ci struct drm_master *lessor, *lessee; 59662306a36Sopenharmony_ci int count; 59762306a36Sopenharmony_ci int ret = 0; 59862306a36Sopenharmony_ci 59962306a36Sopenharmony_ci if (arg->pad) 60062306a36Sopenharmony_ci return -EINVAL; 60162306a36Sopenharmony_ci 60262306a36Sopenharmony_ci /* Can't lease without MODESET */ 60362306a36Sopenharmony_ci if (!drm_core_check_feature(dev, DRIVER_MODESET)) 60462306a36Sopenharmony_ci return -EOPNOTSUPP; 60562306a36Sopenharmony_ci 60662306a36Sopenharmony_ci lessor = drm_file_get_master(lessor_priv); 60762306a36Sopenharmony_ci drm_dbg_lease(dev, "List lessees for %d\n", lessor->lessee_id); 60862306a36Sopenharmony_ci 60962306a36Sopenharmony_ci mutex_lock(&dev->mode_config.idr_mutex); 61062306a36Sopenharmony_ci 61162306a36Sopenharmony_ci count = 0; 61262306a36Sopenharmony_ci drm_for_each_lessee(lessee, lessor) { 61362306a36Sopenharmony_ci /* Only list un-revoked leases */ 61462306a36Sopenharmony_ci if (!idr_is_empty(&lessee->leases)) { 61562306a36Sopenharmony_ci if (count_lessees > count) { 61662306a36Sopenharmony_ci drm_dbg_lease(dev, "Add lessee %d\n", 61762306a36Sopenharmony_ci lessee->lessee_id); 61862306a36Sopenharmony_ci ret = put_user(lessee->lessee_id, lessee_ids + count); 61962306a36Sopenharmony_ci if (ret) 62062306a36Sopenharmony_ci break; 62162306a36Sopenharmony_ci } 62262306a36Sopenharmony_ci count++; 62362306a36Sopenharmony_ci } 62462306a36Sopenharmony_ci } 62562306a36Sopenharmony_ci 62662306a36Sopenharmony_ci drm_dbg_lease(dev, "Lessor leases to %d\n", count); 62762306a36Sopenharmony_ci if (ret == 0) 62862306a36Sopenharmony_ci arg->count_lessees = count; 62962306a36Sopenharmony_ci 63062306a36Sopenharmony_ci mutex_unlock(&dev->mode_config.idr_mutex); 63162306a36Sopenharmony_ci drm_master_put(&lessor); 63262306a36Sopenharmony_ci 63362306a36Sopenharmony_ci return ret; 63462306a36Sopenharmony_ci} 63562306a36Sopenharmony_ci 63662306a36Sopenharmony_ci/* Return the list of leased objects for the specified lessee */ 63762306a36Sopenharmony_ciint drm_mode_get_lease_ioctl(struct drm_device *dev, 63862306a36Sopenharmony_ci void *data, struct drm_file *lessee_priv) 63962306a36Sopenharmony_ci{ 64062306a36Sopenharmony_ci struct drm_mode_get_lease *arg = data; 64162306a36Sopenharmony_ci __u32 __user *object_ids = (__u32 __user *) (uintptr_t) (arg->objects_ptr); 64262306a36Sopenharmony_ci __u32 count_objects = arg->count_objects; 64362306a36Sopenharmony_ci struct drm_master *lessee; 64462306a36Sopenharmony_ci struct idr *object_idr; 64562306a36Sopenharmony_ci int count; 64662306a36Sopenharmony_ci void *entry; 64762306a36Sopenharmony_ci int object; 64862306a36Sopenharmony_ci int ret = 0; 64962306a36Sopenharmony_ci 65062306a36Sopenharmony_ci if (arg->pad) 65162306a36Sopenharmony_ci return -EINVAL; 65262306a36Sopenharmony_ci 65362306a36Sopenharmony_ci /* Can't lease without MODESET */ 65462306a36Sopenharmony_ci if (!drm_core_check_feature(dev, DRIVER_MODESET)) 65562306a36Sopenharmony_ci return -EOPNOTSUPP; 65662306a36Sopenharmony_ci 65762306a36Sopenharmony_ci lessee = drm_file_get_master(lessee_priv); 65862306a36Sopenharmony_ci drm_dbg_lease(dev, "get lease for %d\n", lessee->lessee_id); 65962306a36Sopenharmony_ci 66062306a36Sopenharmony_ci mutex_lock(&dev->mode_config.idr_mutex); 66162306a36Sopenharmony_ci 66262306a36Sopenharmony_ci if (lessee->lessor == NULL) 66362306a36Sopenharmony_ci /* owner can use all objects */ 66462306a36Sopenharmony_ci object_idr = &lessee->dev->mode_config.object_idr; 66562306a36Sopenharmony_ci else 66662306a36Sopenharmony_ci /* lessee can only use allowed object */ 66762306a36Sopenharmony_ci object_idr = &lessee->leases; 66862306a36Sopenharmony_ci 66962306a36Sopenharmony_ci count = 0; 67062306a36Sopenharmony_ci idr_for_each_entry(object_idr, entry, object) { 67162306a36Sopenharmony_ci if (count_objects > count) { 67262306a36Sopenharmony_ci drm_dbg_lease(dev, "adding object %d\n", object); 67362306a36Sopenharmony_ci ret = put_user(object, object_ids + count); 67462306a36Sopenharmony_ci if (ret) 67562306a36Sopenharmony_ci break; 67662306a36Sopenharmony_ci } 67762306a36Sopenharmony_ci count++; 67862306a36Sopenharmony_ci } 67962306a36Sopenharmony_ci 68062306a36Sopenharmony_ci DRM_DEBUG("lease holds %d objects\n", count); 68162306a36Sopenharmony_ci if (ret == 0) 68262306a36Sopenharmony_ci arg->count_objects = count; 68362306a36Sopenharmony_ci 68462306a36Sopenharmony_ci mutex_unlock(&dev->mode_config.idr_mutex); 68562306a36Sopenharmony_ci drm_master_put(&lessee); 68662306a36Sopenharmony_ci 68762306a36Sopenharmony_ci return ret; 68862306a36Sopenharmony_ci} 68962306a36Sopenharmony_ci 69062306a36Sopenharmony_ci/* 69162306a36Sopenharmony_ci * This removes all of the objects from the lease without 69262306a36Sopenharmony_ci * actually getting rid of the lease itself; that way all 69362306a36Sopenharmony_ci * references to it still work correctly 69462306a36Sopenharmony_ci */ 69562306a36Sopenharmony_ciint drm_mode_revoke_lease_ioctl(struct drm_device *dev, 69662306a36Sopenharmony_ci void *data, struct drm_file *lessor_priv) 69762306a36Sopenharmony_ci{ 69862306a36Sopenharmony_ci struct drm_mode_revoke_lease *arg = data; 69962306a36Sopenharmony_ci struct drm_master *lessor; 70062306a36Sopenharmony_ci struct drm_master *lessee; 70162306a36Sopenharmony_ci int ret = 0; 70262306a36Sopenharmony_ci 70362306a36Sopenharmony_ci drm_dbg_lease(dev, "revoke lease for %d\n", arg->lessee_id); 70462306a36Sopenharmony_ci 70562306a36Sopenharmony_ci /* Can't lease without MODESET */ 70662306a36Sopenharmony_ci if (!drm_core_check_feature(dev, DRIVER_MODESET)) 70762306a36Sopenharmony_ci return -EOPNOTSUPP; 70862306a36Sopenharmony_ci 70962306a36Sopenharmony_ci lessor = drm_file_get_master(lessor_priv); 71062306a36Sopenharmony_ci mutex_lock(&dev->mode_config.idr_mutex); 71162306a36Sopenharmony_ci 71262306a36Sopenharmony_ci lessee = _drm_find_lessee(lessor, arg->lessee_id); 71362306a36Sopenharmony_ci 71462306a36Sopenharmony_ci /* No such lessee */ 71562306a36Sopenharmony_ci if (!lessee) { 71662306a36Sopenharmony_ci ret = -ENOENT; 71762306a36Sopenharmony_ci goto fail; 71862306a36Sopenharmony_ci } 71962306a36Sopenharmony_ci 72062306a36Sopenharmony_ci /* Lease is not held by lessor */ 72162306a36Sopenharmony_ci if (lessee->lessor != lessor) { 72262306a36Sopenharmony_ci ret = -EACCES; 72362306a36Sopenharmony_ci goto fail; 72462306a36Sopenharmony_ci } 72562306a36Sopenharmony_ci 72662306a36Sopenharmony_ci _drm_lease_revoke(lessee); 72762306a36Sopenharmony_ci 72862306a36Sopenharmony_cifail: 72962306a36Sopenharmony_ci mutex_unlock(&dev->mode_config.idr_mutex); 73062306a36Sopenharmony_ci drm_master_put(&lessor); 73162306a36Sopenharmony_ci 73262306a36Sopenharmony_ci return ret; 73362306a36Sopenharmony_ci} 734