drm_irq.c 9.6 KB
Newer Older
L
Linus Torvalds 已提交
1
/**
D
Dave Airlie 已提交
2
 * \file drm_irq.c
L
Linus Torvalds 已提交
3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
 * IRQ support
 *
 * \author Rickard E. (Rik) Faith <faith@valinux.com>
 * \author Gareth Hughes <gareth@valinux.com>
 */

/*
 * Created: Fri Mar 19 14:30:16 1999 by faith@valinux.com
 *
 * Copyright 1999, 2000 Precision Insight, Inc., Cedar Park, Texas.
 * Copyright 2000 VA Linux Systems, Inc., Sunnyvale, California.
 * All Rights Reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice (including the next
 * paragraph) shall be included in all copies or substantial portions of the
 * Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
 * VA LINUX SYSTEMS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
 * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
 * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 * OTHER DEALINGS IN THE SOFTWARE.
 */

#include "drmP.h"

#include <linux/interrupt.h>	/* For task queue support */

/**
 * Get interrupt from bus id.
D
Dave Airlie 已提交
42
 *
L
Linus Torvalds 已提交
43 44 45 46 47
 * \param inode device inode.
 * \param filp file pointer.
 * \param cmd command.
 * \param arg user argument, pointing to a drm_irq_busid structure.
 * \return zero on success or a negative number on failure.
D
Dave Airlie 已提交
48
 *
L
Linus Torvalds 已提交
49 50 51 52 53
 * Finds the PCI device with the specified bus id and gets its IRQ number.
 * This IOCTL is deprecated, and will now return EINVAL for any busid not equal
 * to that of the device that this DRM instance attached to.
 */
int drm_irq_by_busid(struct inode *inode, struct file *filp,
D
Dave Airlie 已提交
54
		     unsigned int cmd, unsigned long arg)
L
Linus Torvalds 已提交
55 56 57 58 59 60 61 62 63 64 65 66 67 68
{
	drm_file_t *priv = filp->private_data;
	drm_device_t *dev = priv->head->dev;
	drm_irq_busid_t __user *argp = (void __user *)arg;
	drm_irq_busid_t p;

	if (!drm_core_check_feature(dev, DRIVER_HAVE_IRQ))
		return -EINVAL;

	if (copy_from_user(&p, argp, sizeof(p)))
		return -EFAULT;

	if ((p.busnum >> 8) != dev->pci_domain ||
	    (p.busnum & 0xff) != dev->pci_bus ||
D
Dave Airlie 已提交
69
	    p.devnum != dev->pci_slot || p.funcnum != dev->pci_func)
L
Linus Torvalds 已提交
70 71 72 73
		return -EINVAL;

	p.irq = dev->irq;

D
Dave Airlie 已提交
74
	DRM_DEBUG("%d:%d:%d => IRQ %d\n", p.busnum, p.devnum, p.funcnum, p.irq);
L
Linus Torvalds 已提交
75 76 77 78 79 80 81 82 83 84 85 86 87 88 89
	if (copy_to_user(argp, &p, sizeof(p)))
		return -EFAULT;
	return 0;
}

/**
 * Install IRQ handler.
 *
 * \param dev DRM device.
 * \param irq IRQ number.
 *
 * Initializes the IRQ related data, and setups drm_device::vbl_queue. Installs the handler, calling the driver
 * \c drm_driver_irq_preinstall() and \c drm_driver_irq_postinstall() functions
 * before and after the installation.
 */
D
Dave Airlie 已提交
90
static int drm_irq_install(drm_device_t * dev)
L
Linus Torvalds 已提交
91 92
{
	int ret;
D
Dave Airlie 已提交
93
	unsigned long sh_flags = 0;
L
Linus Torvalds 已提交
94 95 96 97

	if (!drm_core_check_feature(dev, DRIVER_HAVE_IRQ))
		return -EINVAL;

D
Dave Airlie 已提交
98
	if (dev->irq == 0)
L
Linus Torvalds 已提交
99 100
		return -EINVAL;

D
Dave Airlie 已提交
101
	down(&dev->struct_sem);
L
Linus Torvalds 已提交
102 103

	/* Driver must have been initialized */
D
Dave Airlie 已提交
104 105
	if (!dev->dev_private) {
		up(&dev->struct_sem);
L
Linus Torvalds 已提交
106 107 108
		return -EINVAL;
	}

D
Dave Airlie 已提交
109 110
	if (dev->irq_enabled) {
		up(&dev->struct_sem);
L
Linus Torvalds 已提交
111 112 113
		return -EBUSY;
	}
	dev->irq_enabled = 1;
D
Dave Airlie 已提交
114
	up(&dev->struct_sem);
L
Linus Torvalds 已提交
115

D
Dave Airlie 已提交
116
	DRM_DEBUG("%s: irq=%d\n", __FUNCTION__, dev->irq);
L
Linus Torvalds 已提交
117 118 119

	if (drm_core_check_feature(dev, DRIVER_IRQ_VBL)) {
		init_waitqueue_head(&dev->vbl_queue);
D
Dave Airlie 已提交
120 121 122 123 124

		spin_lock_init(&dev->vbl_lock);

		INIT_LIST_HEAD(&dev->vbl_sigs.head);

L
Linus Torvalds 已提交
125 126 127
		dev->vbl_pending = 0;
	}

D
Dave Airlie 已提交
128
	/* Before installing handler */
L
Linus Torvalds 已提交
129 130
	dev->driver->irq_preinstall(dev);

D
Dave Airlie 已提交
131
	/* Install handler */
L
Linus Torvalds 已提交
132 133
	if (drm_core_check_feature(dev, DRIVER_IRQ_SHARED))
		sh_flags = SA_SHIRQ;
D
Dave Airlie 已提交
134 135 136 137 138

	ret = request_irq(dev->irq, dev->driver->irq_handler,
			  sh_flags, dev->devname, dev);
	if (ret < 0) {
		down(&dev->struct_sem);
L
Linus Torvalds 已提交
139
		dev->irq_enabled = 0;
D
Dave Airlie 已提交
140
		up(&dev->struct_sem);
L
Linus Torvalds 已提交
141 142 143
		return ret;
	}

D
Dave Airlie 已提交
144
	/* After installing handler */
L
Linus Torvalds 已提交
145 146 147 148 149 150 151 152 153 154 155 156
	dev->driver->irq_postinstall(dev);

	return 0;
}

/**
 * Uninstall the IRQ handler.
 *
 * \param dev DRM device.
 *
 * Calls the driver's \c drm_driver_irq_uninstall() function, and stops the irq.
 */
D
Dave Airlie 已提交
157
int drm_irq_uninstall(drm_device_t * dev)
L
Linus Torvalds 已提交
158 159 160 161 162 163
{
	int irq_enabled;

	if (!drm_core_check_feature(dev, DRIVER_HAVE_IRQ))
		return -EINVAL;

D
Dave Airlie 已提交
164
	down(&dev->struct_sem);
L
Linus Torvalds 已提交
165 166
	irq_enabled = dev->irq_enabled;
	dev->irq_enabled = 0;
D
Dave Airlie 已提交
167
	up(&dev->struct_sem);
L
Linus Torvalds 已提交
168

D
Dave Airlie 已提交
169
	if (!irq_enabled)
L
Linus Torvalds 已提交
170 171
		return -EINVAL;

D
Dave Airlie 已提交
172
	DRM_DEBUG("%s: irq=%d\n", __FUNCTION__, dev->irq);
L
Linus Torvalds 已提交
173 174 175

	dev->driver->irq_uninstall(dev);

D
Dave Airlie 已提交
176
	free_irq(dev->irq, dev);
L
Linus Torvalds 已提交
177 178 179

	return 0;
}
D
Dave Airlie 已提交
180

L
Linus Torvalds 已提交
181 182 183 184 185 186 187 188 189 190 191 192 193
EXPORT_SYMBOL(drm_irq_uninstall);

/**
 * IRQ control ioctl.
 *
 * \param inode device inode.
 * \param filp file pointer.
 * \param cmd command.
 * \param arg user argument, pointing to a drm_control structure.
 * \return zero on success or a negative number on failure.
 *
 * Calls irq_install() or irq_uninstall() according to \p arg.
 */
D
Dave Airlie 已提交
194 195
int drm_control(struct inode *inode, struct file *filp,
		unsigned int cmd, unsigned long arg)
L
Linus Torvalds 已提交
196 197 198 199
{
	drm_file_t *priv = filp->private_data;
	drm_device_t *dev = priv->head->dev;
	drm_control_t ctl;
D
Dave Airlie 已提交
200

L
Linus Torvalds 已提交
201 202
	/* if we haven't irq we fallback for compatibility reasons - this used to be a separate function in drm_dma.h */

D
Dave Airlie 已提交
203
	if (copy_from_user(&ctl, (drm_control_t __user *) arg, sizeof(ctl)))
L
Linus Torvalds 已提交
204 205
		return -EFAULT;

D
Dave Airlie 已提交
206
	switch (ctl.func) {
L
Linus Torvalds 已提交
207 208 209 210 211 212
	case DRM_INST_HANDLER:
		if (!drm_core_check_feature(dev, DRIVER_HAVE_IRQ))
			return 0;
		if (dev->if_version < DRM_IF_VERSION(1, 2) &&
		    ctl.irq != dev->irq)
			return -EINVAL;
D
Dave Airlie 已提交
213
		return drm_irq_install(dev);
L
Linus Torvalds 已提交
214 215 216
	case DRM_UNINST_HANDLER:
		if (!drm_core_check_feature(dev, DRIVER_HAVE_IRQ))
			return 0;
D
Dave Airlie 已提交
217
		return drm_irq_uninstall(dev);
L
Linus Torvalds 已提交
218 219 220 221 222 223 224 225 226 227 228 229 230 231
	default:
		return -EINVAL;
	}
}

/**
 * Wait for VBLANK.
 *
 * \param inode device inode.
 * \param filp file pointer.
 * \param cmd command.
 * \param data user argument, pointing to a drm_wait_vblank structure.
 * \return zero on success or a negative number on failure.
 *
D
Dave Airlie 已提交
232
 * Verifies the IRQ is installed.
L
Linus Torvalds 已提交
233 234 235 236 237 238 239 240 241
 *
 * If a signal is requested checks if this task has already scheduled the same signal
 * for the same vblank sequence number - nothing to be done in
 * that case. If the number of tasks waiting for the interrupt exceeds 100 the
 * function fails. Otherwise adds a new entry to drm_device::vbl_sigs for this
 * task.
 *
 * If a signal is not requested, then calls vblank_wait().
 */
D
Dave Airlie 已提交
242
int drm_wait_vblank(DRM_IOCTL_ARGS)
L
Linus Torvalds 已提交
243 244 245 246 247 248 249 250 251 252 253 254 255 256 257
{
	drm_file_t *priv = filp->private_data;
	drm_device_t *dev = priv->head->dev;
	drm_wait_vblank_t __user *argp = (void __user *)data;
	drm_wait_vblank_t vblwait;
	struct timeval now;
	int ret = 0;
	unsigned int flags;

	if (!drm_core_check_feature(dev, DRIVER_IRQ_VBL))
		return -EINVAL;

	if (!dev->irq)
		return -EINVAL;

D
Dave Airlie 已提交
258
	DRM_COPY_FROM_USER_IOCTL(vblwait, argp, sizeof(vblwait));
L
Linus Torvalds 已提交
259

D
Dave Airlie 已提交
260
	switch (vblwait.request.type & ~_DRM_VBLANK_FLAGS_MASK) {
L
Linus Torvalds 已提交
261
	case _DRM_VBLANK_RELATIVE:
D
Dave Airlie 已提交
262
		vblwait.request.sequence += atomic_read(&dev->vbl_received);
L
Linus Torvalds 已提交
263 264 265 266 267 268 269 270
		vblwait.request.type &= ~_DRM_VBLANK_RELATIVE;
	case _DRM_VBLANK_ABSOLUTE:
		break;
	default:
		return -EINVAL;
	}

	flags = vblwait.request.type & _DRM_VBLANK_FLAGS_MASK;
D
Dave Airlie 已提交
271 272

	if (flags & _DRM_VBLANK_SIGNAL) {
L
Linus Torvalds 已提交
273 274 275
		unsigned long irqflags;
		drm_vbl_sig_t *vbl_sig;

D
Dave Airlie 已提交
276 277 278
		vblwait.reply.sequence = atomic_read(&dev->vbl_received);

		spin_lock_irqsave(&dev->vbl_lock, irqflags);
L
Linus Torvalds 已提交
279 280 281 282 283

		/* Check if this task has already scheduled the same signal
		 * for the same vblank sequence number; nothing to be done in
		 * that case
		 */
D
Dave Airlie 已提交
284
		list_for_each_entry(vbl_sig, &dev->vbl_sigs.head, head) {
L
Linus Torvalds 已提交
285 286
			if (vbl_sig->sequence == vblwait.request.sequence
			    && vbl_sig->info.si_signo == vblwait.request.signal
D
Dave Airlie 已提交
287 288 289
			    && vbl_sig->task == current) {
				spin_unlock_irqrestore(&dev->vbl_lock,
						       irqflags);
L
Linus Torvalds 已提交
290 291 292 293
				goto done;
			}
		}

D
Dave Airlie 已提交
294 295
		if (dev->vbl_pending >= 100) {
			spin_unlock_irqrestore(&dev->vbl_lock, irqflags);
L
Linus Torvalds 已提交
296 297 298 299 300
			return -EBUSY;
		}

		dev->vbl_pending++;

D
Dave Airlie 已提交
301
		spin_unlock_irqrestore(&dev->vbl_lock, irqflags);
L
Linus Torvalds 已提交
302

D
Dave Airlie 已提交
303 304 305
		if (!
		    (vbl_sig =
		     drm_alloc(sizeof(drm_vbl_sig_t), DRM_MEM_DRIVER))) {
L
Linus Torvalds 已提交
306 307 308
			return -ENOMEM;
		}

D
Dave Airlie 已提交
309
		memset((void *)vbl_sig, 0, sizeof(*vbl_sig));
L
Linus Torvalds 已提交
310 311 312 313 314

		vbl_sig->sequence = vblwait.request.sequence;
		vbl_sig->info.si_signo = vblwait.request.signal;
		vbl_sig->task = current;

D
Dave Airlie 已提交
315
		spin_lock_irqsave(&dev->vbl_lock, irqflags);
L
Linus Torvalds 已提交
316

D
Dave Airlie 已提交
317
		list_add_tail((struct list_head *)vbl_sig, &dev->vbl_sigs.head);
L
Linus Torvalds 已提交
318

D
Dave Airlie 已提交
319
		spin_unlock_irqrestore(&dev->vbl_lock, irqflags);
L
Linus Torvalds 已提交
320 321
	} else {
		if (dev->driver->vblank_wait)
D
Dave Airlie 已提交
322 323 324
			ret =
			    dev->driver->vblank_wait(dev,
						     &vblwait.request.sequence);
L
Linus Torvalds 已提交
325

D
Dave Airlie 已提交
326
		do_gettimeofday(&now);
L
Linus Torvalds 已提交
327 328 329 330
		vblwait.reply.tval_sec = now.tv_sec;
		vblwait.reply.tval_usec = now.tv_usec;
	}

D
Dave Airlie 已提交
331 332
      done:
	DRM_COPY_TO_USER_IOCTL(argp, vblwait, sizeof(vblwait));
L
Linus Torvalds 已提交
333 334 335 336 337 338 339 340 341 342 343 344 345

	return ret;
}

/**
 * Send the VBLANK signals.
 *
 * \param dev DRM device.
 *
 * Sends a signal for each task in drm_device::vbl_sigs and empties the list.
 *
 * If a signal is not requested, then calls vblank_wait().
 */
D
Dave Airlie 已提交
346
void drm_vbl_send_signals(drm_device_t * dev)
L
Linus Torvalds 已提交
347 348 349
{
	struct list_head *list, *tmp;
	drm_vbl_sig_t *vbl_sig;
D
Dave Airlie 已提交
350
	unsigned int vbl_seq = atomic_read(&dev->vbl_received);
L
Linus Torvalds 已提交
351 352
	unsigned long flags;

D
Dave Airlie 已提交
353
	spin_lock_irqsave(&dev->vbl_lock, flags);
L
Linus Torvalds 已提交
354

D
Dave Airlie 已提交
355 356 357
	list_for_each_safe(list, tmp, &dev->vbl_sigs.head) {
		vbl_sig = list_entry(list, drm_vbl_sig_t, head);
		if ((vbl_seq - vbl_sig->sequence) <= (1 << 23)) {
L
Linus Torvalds 已提交
358
			vbl_sig->info.si_code = vbl_seq;
D
Dave Airlie 已提交
359 360
			send_sig_info(vbl_sig->info.si_signo, &vbl_sig->info,
				      vbl_sig->task);
L
Linus Torvalds 已提交
361

D
Dave Airlie 已提交
362
			list_del(list);
L
Linus Torvalds 已提交
363

D
Dave Airlie 已提交
364
			drm_free(vbl_sig, sizeof(*vbl_sig), DRM_MEM_DRIVER);
L
Linus Torvalds 已提交
365 366 367 368 369

			dev->vbl_pending--;
		}
	}

D
Dave Airlie 已提交
370
	spin_unlock_irqrestore(&dev->vbl_lock, flags);
L
Linus Torvalds 已提交
371 372
}

D
Dave Airlie 已提交
373
EXPORT_SYMBOL(drm_vbl_send_signals);