drm_irq.c 12.8 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
	struct drm_file *priv = filp->private_data;
	struct drm_device *dev = priv->head->dev;
58 59
	struct drm_irq_busid __user *argp = (void __user *)arg;
	struct drm_irq_busid p;
L
Linus Torvalds 已提交
60 61 62 63 64 65 66

	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.
 */
90
static int drm_irq_install(struct drm_device * 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

		spin_lock_init(&dev->vbl_lock);

123 124
		INIT_LIST_HEAD(&dev->vbl_sigs);
		INIT_LIST_HEAD(&dev->vbl_sigs2);
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.
 */
158
int drm_irq_uninstall(struct drm_device * 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
	struct drm_file *priv = filp->private_data;
	struct drm_device *dev = priv->head->dev;
202
	struct drm_control 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 */

206
	if (copy_from_user(&ctl, (struct drm_control __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
	struct drm_file *priv = filp->private_data;
	struct drm_device *dev = priv->head->dev;
249 250
	union drm_wait_vblank __user *argp = (void __user *)data;
	union drm_wait_vblank vblwait;
L
Linus Torvalds 已提交
251 252
	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
		struct list_head *vbl_sigs = (flags & _DRM_VBLANK_SECONDARY)
296
				      ? &dev->vbl_sigs2 : &dev->vbl_sigs;
D
Dave Airlie 已提交
297
		struct drm_vbl_sig *vbl_sig;
L
Linus Torvalds 已提交
298

D
Dave Airlie 已提交
299
		spin_lock_irqsave(&dev->vbl_lock, irqflags);
L
Linus Torvalds 已提交
300 301 302 303 304

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

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

		dev->vbl_pending++;

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

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

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

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

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

339
		list_add_tail(&vbl_sig->head, vbl_sigs);
L
Linus Torvalds 已提交
340

D
Dave Airlie 已提交
341
		spin_unlock_irqrestore(&dev->vbl_lock, irqflags);
342 343

		vblwait.reply.sequence = seq;
L
Linus Torvalds 已提交
344
	} else {
345 346 347 348
		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 已提交
349 350 351
			ret =
			    dev->driver->vblank_wait(dev,
						     &vblwait.request.sequence);
L
Linus Torvalds 已提交
352

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

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

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

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

381
	for (i = 0; i < 2; i++) {
D
Dave Airlie 已提交
382
		struct drm_vbl_sig *vbl_sig, *tmp;
383
		struct list_head *vbl_sigs = i ? &dev->vbl_sigs2 : &dev->vbl_sigs;
384 385 386
		unsigned int vbl_seq = atomic_read(i ? &dev->vbl_received2 :
						   &dev->vbl_received);

387
		list_for_each_entry_safe(vbl_sig, tmp, vbl_sigs, head) {
388 389 390 391
			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 已提交
392

393
				list_del(&vbl_sig->head);
L
Linus Torvalds 已提交
394

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

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

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

D
Dave Airlie 已提交
406
EXPORT_SYMBOL(drm_vbl_send_signals);
407 408 409 410 411 412 413 414 415 416 417 418

/**
 * 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)
{
419
	struct drm_device *dev = (struct drm_device *)data;
420 421 422 423 424
	unsigned long irqflags;

	spin_lock_irqsave(&dev->tasklet_lock, irqflags);

	if (!dev->locked_tasklet_func ||
425
	    !drm_lock_take(&dev->lock,
426 427 428 429 430 431 432 433 434 435
			   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);

436
	drm_lock_free(&dev->lock,
437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455
		      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.
 */
456
void drm_locked_tasklet(struct drm_device *dev, void (*func)(struct drm_device *))
457 458 459 460
{
	unsigned long irqflags;
	static DECLARE_TASKLET(drm_tasklet, drm_locked_tasklet_func, 0);

461 462
	if (!drm_core_check_feature(dev, DRIVER_HAVE_IRQ) ||
	    test_bit(TASKLET_STATE_SCHED, &drm_tasklet.state))
463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480
		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);