drm_irq.c 12.7 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
{
	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;

D
Dave Airlie 已提交
67
	if ((p.busnum >> 8) != drm_get_pci_domain(dev) ||
68 69
	    (p.busnum & 0xff) != dev->pdev->bus->number ||
	    p.devnum != PCI_SLOT(dev->pdev->devfn) || p.funcnum != PCI_FUNC(dev->pdev->devfn))
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
	mutex_lock(&dev->struct_mutex);
L
Linus Torvalds 已提交
102 103

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

D
Dave Airlie 已提交
109
	if (dev->irq_enabled) {
D
Dave Airlie 已提交
110
		mutex_unlock(&dev->struct_mutex);
L
Linus Torvalds 已提交
111 112 113
		return -EBUSY;
	}
	dev->irq_enabled = 1;
D
Dave Airlie 已提交
114
	mutex_unlock(&dev->struct_mutex);
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

		spin_lock_init(&dev->vbl_lock);

		INIT_LIST_HEAD(&dev->vbl_sigs.head);
124
		INIT_LIST_HEAD(&dev->vbl_sigs2.head);
D
Dave Airlie 已提交
125

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

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

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

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

D
Dave Airlie 已提交
145
	/* After installing handler */
L
Linus Torvalds 已提交
146 147 148 149 150 151 152 153 154 155 156 157
	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 已提交
158
int drm_irq_uninstall(drm_device_t * dev)
L
Linus Torvalds 已提交
159 160 161 162 163 164
{
	int irq_enabled;

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

D
Dave Airlie 已提交
165
	mutex_lock(&dev->struct_mutex);
L
Linus Torvalds 已提交
166 167
	irq_enabled = dev->irq_enabled;
	dev->irq_enabled = 0;
D
Dave Airlie 已提交
168
	mutex_unlock(&dev->struct_mutex);
L
Linus Torvalds 已提交
169

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

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

	dev->driver->irq_uninstall(dev);

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

179 180
	dev->locked_tasklet_func = NULL;

L
Linus Torvalds 已提交
181 182
	return 0;
}
D
Dave Airlie 已提交
183

L
Linus Torvalds 已提交
184 185 186 187 188 189 190 191 192 193 194 195 196
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 已提交
197 198
int drm_control(struct inode *inode, struct file *filp,
		unsigned int cmd, unsigned long arg)
L
Linus Torvalds 已提交
199 200 201 202
{
	drm_file_t *priv = filp->private_data;
	drm_device_t *dev = priv->head->dev;
	drm_control_t ctl;
D
Dave Airlie 已提交
203

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

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

D
Dave Airlie 已提交
209
	switch (ctl.func) {
L
Linus Torvalds 已提交
210 211 212 213 214 215
	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 已提交
216
		return drm_irq_install(dev);
L
Linus Torvalds 已提交
217 218 219
	case DRM_UNINST_HANDLER:
		if (!drm_core_check_feature(dev, DRIVER_HAVE_IRQ))
			return 0;
D
Dave Airlie 已提交
220
		return drm_irq_uninstall(dev);
L
Linus Torvalds 已提交
221 222 223 224 225 226 227 228 229 230 231 232 233 234
	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 已提交
235
 * Verifies the IRQ is installed.
L
Linus Torvalds 已提交
236 237 238 239 240 241 242 243 244
 *
 * 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 已提交
245
int drm_wait_vblank(DRM_IOCTL_ARGS)
L
Linus Torvalds 已提交
246 247 248 249 250 251 252
{
	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;
253
	unsigned int flags, seq;
L
Linus Torvalds 已提交
254 255 256 257

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

258 259
	if (copy_from_user(&vblwait, argp, sizeof(vblwait)))
		return -EFAULT;
L
Linus Torvalds 已提交
260

261 262 263 264 265 266 267 268 269 270 271 272 273 274
	if (vblwait.request.type &
	    ~(_DRM_VBLANK_TYPES_MASK | _DRM_VBLANK_FLAGS_MASK)) {
		DRM_ERROR("Unsupported type value 0x%x, supported mask 0x%x\n",
			  vblwait.request.type,
			  (_DRM_VBLANK_TYPES_MASK | _DRM_VBLANK_FLAGS_MASK));
		return -EINVAL;
	}

	flags = vblwait.request.type & _DRM_VBLANK_FLAGS_MASK;

	if (!drm_core_check_feature(dev, (flags & _DRM_VBLANK_SECONDARY) ?
				    DRIVER_IRQ_VBL2 : DRIVER_IRQ_VBL))
		return -EINVAL;

275 276
	seq = atomic_read((flags & _DRM_VBLANK_SECONDARY) ? &dev->vbl_received2
			  : &dev->vbl_received);
277 278

	switch (vblwait.request.type & _DRM_VBLANK_TYPES_MASK) {
L
Linus Torvalds 已提交
279
	case _DRM_VBLANK_RELATIVE:
280
		vblwait.request.sequence += seq;
L
Linus Torvalds 已提交
281 282 283 284 285 286 287
		vblwait.request.type &= ~_DRM_VBLANK_RELATIVE;
	case _DRM_VBLANK_ABSOLUTE:
		break;
	default:
		return -EINVAL;
	}

288 289 290 291 292
	if ((flags & _DRM_VBLANK_NEXTONMISS) &&
	    (seq - vblwait.request.sequence) <= (1<<23)) {
		vblwait.request.sequence = seq + 1;
	}

D
Dave Airlie 已提交
293
	if (flags & _DRM_VBLANK_SIGNAL) {
L
Linus Torvalds 已提交
294
		unsigned long irqflags;
295 296
		drm_vbl_sig_t *vbl_sigs = (flags & _DRM_VBLANK_SECONDARY)
				      ? &dev->vbl_sigs2 : &dev->vbl_sigs;
L
Linus Torvalds 已提交
297 298
		drm_vbl_sig_t *vbl_sig;

299
		vblwait.reply.sequence = seq;
D
Dave Airlie 已提交
300 301

		spin_lock_irqsave(&dev->vbl_lock, irqflags);
L
Linus Torvalds 已提交
302 303 304 305 306

		/* Check if this task has already scheduled the same signal
		 * for the same vblank sequence number; nothing to be done in
		 * that case
		 */
307
		list_for_each_entry(vbl_sig, &vbl_sigs->head, head) {
L
Linus Torvalds 已提交
308 309
			if (vbl_sig->sequence == vblwait.request.sequence
			    && vbl_sig->info.si_signo == vblwait.request.signal
D
Dave Airlie 已提交
310 311 312
			    && vbl_sig->task == current) {
				spin_unlock_irqrestore(&dev->vbl_lock,
						       irqflags);
L
Linus Torvalds 已提交
313 314 315 316
				goto done;
			}
		}

D
Dave Airlie 已提交
317 318
		if (dev->vbl_pending >= 100) {
			spin_unlock_irqrestore(&dev->vbl_lock, irqflags);
L
Linus Torvalds 已提交
319 320 321 322 323
			return -EBUSY;
		}

		dev->vbl_pending++;

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

D
Dave Airlie 已提交
326 327 328
		if (!
		    (vbl_sig =
		     drm_alloc(sizeof(drm_vbl_sig_t), DRM_MEM_DRIVER))) {
L
Linus Torvalds 已提交
329 330 331
			return -ENOMEM;
		}

D
Dave Airlie 已提交
332
		memset((void *)vbl_sig, 0, sizeof(*vbl_sig));
L
Linus Torvalds 已提交
333 334 335 336 337

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

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

340
		list_add_tail((struct list_head *)vbl_sig, &vbl_sigs->head);
L
Linus Torvalds 已提交
341

D
Dave Airlie 已提交
342
		spin_unlock_irqrestore(&dev->vbl_lock, irqflags);
L
Linus Torvalds 已提交
343
	} else {
344 345 346 347
		if (flags & _DRM_VBLANK_SECONDARY) {
			if (dev->driver->vblank_wait2)
				ret = dev->driver->vblank_wait2(dev, &vblwait.request.sequence);
		} else if (dev->driver->vblank_wait)
D
Dave Airlie 已提交
348 349 350
			ret =
			    dev->driver->vblank_wait(dev,
						     &vblwait.request.sequence);
L
Linus Torvalds 已提交
351

D
Dave Airlie 已提交
352
		do_gettimeofday(&now);
L
Linus Torvalds 已提交
353 354 355 356
		vblwait.reply.tval_sec = now.tv_sec;
		vblwait.reply.tval_usec = now.tv_usec;
	}

D
Dave Airlie 已提交
357
      done:
358 359
	if (copy_to_user(argp, &vblwait, sizeof(vblwait)))
		return -EFAULT;
L
Linus Torvalds 已提交
360 361 362 363 364 365 366 367 368 369 370 371 372

	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 已提交
373
void drm_vbl_send_signals(drm_device_t * dev)
L
Linus Torvalds 已提交
374 375
{
	unsigned long flags;
376
	int i;
L
Linus Torvalds 已提交
377

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

380 381 382 383 384 385 386 387 388 389 390 391 392
	for (i = 0; i < 2; i++) {
		struct list_head *list, *tmp;
		drm_vbl_sig_t *vbl_sig;
		drm_vbl_sig_t *vbl_sigs = i ? &dev->vbl_sigs2 : &dev->vbl_sigs;
		unsigned int vbl_seq = atomic_read(i ? &dev->vbl_received2 :
						   &dev->vbl_received);

		list_for_each_safe(list, tmp, &vbl_sigs->head) {
			vbl_sig = list_entry(list, drm_vbl_sig_t, head);
			if ((vbl_seq - vbl_sig->sequence) <= (1 << 23)) {
				vbl_sig->info.si_code = vbl_seq;
				send_sig_info(vbl_sig->info.si_signo,
					      &vbl_sig->info, vbl_sig->task);
L
Linus Torvalds 已提交
393

394
				list_del(list);
L
Linus Torvalds 已提交
395

396 397
				drm_free(vbl_sig, sizeof(*vbl_sig),
					 DRM_MEM_DRIVER);
L
Linus Torvalds 已提交
398

399 400
				dev->vbl_pending--;
			}
L
Linus Torvalds 已提交
401 402 403
		}
	}

D
Dave Airlie 已提交
404
	spin_unlock_irqrestore(&dev->vbl_lock, flags);
L
Linus Torvalds 已提交
405 406
}

D
Dave Airlie 已提交
407
EXPORT_SYMBOL(drm_vbl_send_signals);
408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461

/**
 * Tasklet wrapper function.
 *
 * \param data DRM device in disguise.
 *
 * Attempts to grab the HW lock and calls the driver callback on success. On
 * failure, leave the lock marked as contended so the callback can be called
 * from drm_unlock().
 */
static void drm_locked_tasklet_func(unsigned long data)
{
	drm_device_t *dev = (drm_device_t*)data;
	unsigned long irqflags;

	spin_lock_irqsave(&dev->tasklet_lock, irqflags);

	if (!dev->locked_tasklet_func ||
	    !drm_lock_take(&dev->lock.hw_lock->lock,
			   DRM_KERNEL_CONTEXT)) {
		spin_unlock_irqrestore(&dev->tasklet_lock, irqflags);
		return;
	}

	dev->lock.lock_time = jiffies;
	atomic_inc(&dev->counts[_DRM_STAT_LOCKS]);

	dev->locked_tasklet_func(dev);

	drm_lock_free(dev, &dev->lock.hw_lock->lock,
		      DRM_KERNEL_CONTEXT);

	dev->locked_tasklet_func = NULL;

	spin_unlock_irqrestore(&dev->tasklet_lock, irqflags);
}

/**
 * Schedule a tasklet to call back a driver hook with the HW lock held.
 *
 * \param dev DRM device.
 * \param func Driver callback.
 *
 * This is intended for triggering actions that require the HW lock from an
 * interrupt handler. The lock will be grabbed ASAP after the interrupt handler
 * completes. Note that the callback may be called from interrupt or process
 * context, it must not make any assumptions about this. Also, the HW lock will
 * be held with the kernel context or any client context.
 */
void drm_locked_tasklet(drm_device_t *dev, void (*func)(drm_device_t*))
{
	unsigned long irqflags;
	static DECLARE_TASKLET(drm_tasklet, drm_locked_tasklet_func, 0);

462 463
	if (!drm_core_check_feature(dev, DRIVER_HAVE_IRQ) ||
	    test_bit(TASKLET_STATE_SCHED, &drm_tasklet.state))
464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481
		return;

	spin_lock_irqsave(&dev->tasklet_lock, irqflags);

	if (dev->locked_tasklet_func) {
		spin_unlock_irqrestore(&dev->tasklet_lock, irqflags);
		return;
	}

	dev->locked_tasklet_func = func;

	spin_unlock_irqrestore(&dev->tasklet_lock, irqflags);

	drm_tasklet.data = (unsigned long)dev;

	tasklet_hi_schedule(&drm_tasklet);
}
EXPORT_SYMBOL(drm_locked_tasklet);