Training courses

Kernel and Embedded Linux

Bootlin training courses

Embedded Linux, kernel,
Yocto Project, Buildroot, real-time,
graphics, boot time, debugging...

Bootlin logo

Elixir Cross Referencer

/*	$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 <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: drm_fops.c,v 1.14 2018/08/27 15:22:54 riastradh Exp $");

#include <sys/param.h>
#include <sys/select.h>

#include <drm/drmP.h>
#include <drm/drm_internal.h>
#include <drm/drm_legacy.h>
#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);
}