/* $NetBSD: drm_fops.c,v 1.14 2018/08/27 15:22:54 riastradh Exp $ */ /*- * Copyright (c) 2013 The NetBSD Foundation, Inc. * All rights reserved. * * This code is derived from software contributed to The NetBSD Foundation * by Taylor R. Campbell. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ #include __KERNEL_RCSID(0, "$NetBSD: drm_fops.c,v 1.14 2018/08/27 15:22:54 riastradh Exp $"); #include #include #include #include #include #include "../dist/drm/drm_legacy.h" static int drm_open_file_master(struct drm_file *); static void drm_master_release(struct drm_file *); static void drm_events_release(struct drm_file *); static void drm_close_file_master(struct drm_file *); int drm_open_file(struct drm_file *file, void *fp, struct drm_minor *minor) { struct drm_device *const dev = minor->dev; int ret; file->authenticated = DRM_SUSER(); /* XXX */ file->is_master = false; file->stereo_allowed = false; file->universal_planes = false; file->atomic = false; file->allowed_master = false; file->magic = 0; INIT_LIST_HEAD(&file->lhead); file->minor = minor; file->lock_count = 0; /* file->object_idr is initialized by drm_gem_open. */ /* file->table_lock is initialized by drm_gem_open. */ file->filp = fp; file->driver_priv = NULL; file->master = NULL; INIT_LIST_HEAD(&file->fbs); linux_mutex_init(&file->fbs_lock); INIT_LIST_HEAD(&file->blobs); DRM_INIT_WAITQUEUE(&file->event_wait, "drmevent"); selinit(&file->event_selq); INIT_LIST_HEAD(&file->event_list); file->event_space = 0x1000; /* XXX cargo-culted from Linux */ /* file->prime is initialized by drm_prime_init_file_private. */ if (drm_core_check_feature(dev, DRIVER_GEM)) drm_gem_open(dev, file); if (drm_core_check_feature(dev, DRIVER_PRIME)) drm_prime_init_file_private(&file->prime); if (dev->driver->open) { ret = (*dev->driver->open)(dev, file); if (ret) goto fail0; } ret = drm_open_file_master(file); if (ret) goto fail1; mutex_lock(&dev->struct_mutex); list_add(&file->lhead, &dev->filelist); mutex_unlock(&dev->struct_mutex); /* Success! */ return 0; fail1: /* * XXX This error branch needs scrutiny, but Linux's error * branches are incomprehensible and look wronger. */ if (dev->driver->preclose) (*dev->driver->preclose)(dev, file); if (dev->driver->postclose) (*dev->driver->postclose)(dev, file); fail0: if (drm_core_check_feature(dev, DRIVER_PRIME)) drm_prime_destroy_file_private(&file->prime); if (drm_core_check_feature(dev, DRIVER_GEM)) drm_gem_release(dev, file); return ret; } int drm_new_set_master(struct drm_device *dev, struct drm_file *file) { struct drm_master *old_master; int ret; KASSERT(mutex_is_locked(&dev->master_mutex)); KASSERT(file->minor->type == DRM_MINOR_LEGACY); KASSERT(file->minor->master == NULL); file->minor->master = drm_master_create(file->minor); if (file->minor->master == NULL) { ret = -ENOMEM; goto fail0; } /* * Save the old master, to drop a reference later if all goes * well, and get a reference to the new one. */ old_master = file->master; file->master = drm_master_get(file->minor->master); /* Invoke the driver callbacks master_create and master_set. */ if (dev->driver->master_create) { ret = (*dev->driver->master_create)(dev, file->minor->master); if (ret) goto fail1; } if (dev->driver->master_set) { ret = (*dev->driver->master_set)(dev, file, true); if (ret) goto fail1; } /* * Mark ourselves as an authenticated master, and allowed to * set a new master. */ file->is_master = 1; file->allowed_master = 1; file->authenticated = 1; /* If there was an old master, release it now. */ if (old_master) drm_master_put(&old_master); /* Success! */ return 0; fail1: /* Release the master we just created. */ drm_master_put(&file->minor->master); KASSERT(file->minor->master == NULL); /* Release the reference we just added in the file. */ drm_master_put(&file->master); KASSERT(file->master == NULL); /* Restore the old master if there was one. */ file->master = old_master; fail0: KASSERT(ret); return ret; } static int drm_open_file_master(struct drm_file *file) { struct drm_device *const dev = file->minor->dev; int ret; /* If this is not the legacy device, there are no masters. */ if (file->minor->type != DRM_MINOR_LEGACY) return 0; mutex_lock(&dev->master_mutex); if (file->minor->master != NULL) { /* * If the minor already has a master, get a reference * to it. */ file->master = drm_master_get(file->minor->master); ret = 0; } else { /* * Otherwise, automatically behave as though we had * just done setmaster. */ ret = drm_new_set_master(dev, file); } mutex_unlock(&dev->master_mutex); return ret; } void drm_close_file(struct drm_file *file) { struct drm_minor *const minor = file->minor; struct drm_device *const dev = minor->dev; mutex_lock(&dev->struct_mutex); list_del(&file->lhead); if (file->magic) idr_remove(&file->master->magic_map, file->magic); mutex_unlock(&dev->struct_mutex); if (dev->driver->preclose) (*dev->driver->preclose)(dev, file); if (minor->master) drm_master_release(file); if (drm_core_check_feature(dev, DRIVER_HAVE_DMA)) drm_legacy_reclaim_buffers(dev, file); drm_events_release(file); if (drm_core_check_feature(dev, DRIVER_MODESET)) { drm_fb_release(file); drm_property_destroy_user_blobs(dev, file); } if (drm_core_check_feature(dev, DRIVER_GEM)) drm_gem_release(dev, file); drm_legacy_ctxbitmap_flush(dev, file); drm_close_file_master(file); if (dev->driver->postclose) (*dev->driver->postclose)(dev, file); if (drm_core_check_feature(dev, DRIVER_PRIME)) drm_prime_destroy_file_private(&file->prime); seldestroy(&file->event_selq); DRM_DESTROY_WAITQUEUE(&file->event_wait); linux_mutex_destroy(&file->fbs_lock); } static void drm_master_release(struct drm_file *file) { /* * XXX I think this locking concept is wrong -- we need to hold * file->master->lock.spinlock across the two calls to * drm_legacy_i_have_hw_lock and drm_legacy_lock_free. */ if (drm_legacy_i_have_hw_lock(file->minor->dev, file)) drm_legacy_lock_free(&file->master->lock, _DRM_LOCKING_CONTEXT(file->master->lock.hw_lock->lock)); } static void drm_events_release(struct drm_file *file) { struct drm_device *const dev = file->minor->dev; struct drm_pending_vblank_event *vblank, *vblank_next; struct drm_pending_event *event, *event_next; unsigned long flags; spin_lock_irqsave(&dev->event_lock, flags); list_for_each_entry_safe(vblank, vblank_next, &dev->vblank_event_list, base.link) { if (vblank->base.file_priv == file) { list_del(&vblank->base.link); drm_vblank_put(dev, vblank->pipe); (*vblank->base.destroy)(&vblank->base); } } list_for_each_entry_safe(event, event_next, &file->event_list, link) { (*event->destroy)(event); } spin_unlock_irqrestore(&dev->event_lock, flags); } static void drm_close_file_master(struct drm_file *file) { struct drm_device *const dev = file->minor->dev; mutex_lock(&dev->master_mutex); if (file->is_master) { if (file->minor->master == file->master) { if (dev->driver->master_drop) (*dev->driver->master_drop)(dev, file, true); drm_master_put(&file->minor->master); } } if (file->master != NULL) drm_master_put(&file->master); file->is_master = 0; mutex_unlock(&dev->master_mutex); }