1/*	$NetBSD: drm_irq.c,v 1.18 2021/12/19 12:05:08 riastradh Exp $	*/
2
3/*
4 * drm_irq.c IRQ and vblank support
5 *
6 * \author Rickard E. (Rik) Faith <faith@valinux.com>
7 * \author Gareth Hughes <gareth@valinux.com>
8 *
9 * Permission is hereby granted, free of charge, to any person obtaining a
10 * copy of this software and associated documentation files (the "Software"),
11 * to deal in the Software without restriction, including without limitation
12 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
13 * and/or sell copies of the Software, and to permit persons to whom the
14 * Software is furnished to do so, subject to the following conditions:
15 *
16 * The above copyright notice and this permission notice (including the next
17 * paragraph) shall be included in all copies or substantial portions of the
18 * Software.
19 *
20 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
23 * VA LINUX SYSTEMS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
24 * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
25 * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
26 * OTHER DEALINGS IN THE SOFTWARE.
27 */
28
29/*
30 * Created: Fri Mar 19 14:30:16 1999 by faith@valinux.com
31 *
32 * Copyright 1999, 2000 Precision Insight, Inc., Cedar Park, Texas.
33 * Copyright 2000 VA Linux Systems, Inc., Sunnyvale, California.
34 * All Rights Reserved.
35 *
36 * Permission is hereby granted, free of charge, to any person obtaining a
37 * copy of this software and associated documentation files (the "Software"),
38 * to deal in the Software without restriction, including without limitation
39 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
40 * and/or sell copies of the Software, and to permit persons to whom the
41 * Software is furnished to do so, subject to the following conditions:
42 *
43 * The above copyright notice and this permission notice (including the next
44 * paragraph) shall be included in all copies or substantial portions of the
45 * Software.
46 *
47 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
48 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
49 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
50 * VA LINUX SYSTEMS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
51 * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
52 * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
53 * OTHER DEALINGS IN THE SOFTWARE.
54 */
55
56
57#include <sys/cdefs.h>
58__KERNEL_RCSID(0, "$NetBSD: drm_irq.c,v 1.18 2021/12/19 12:05:08 riastradh Exp $");
59
60#include <linux/export.h>
61#include <linux/interrupt.h>	/* For task queue support */
62#include <linux/pci.h>
63#include <linux/vgaarb.h>
64
65#include <drm/drm.h>
66#include <drm/drm_device.h>
67#include <drm/drm_drv.h>
68#include <drm/drm_irq.h>
69#include <drm/drm_print.h>
70#include <drm/drm_vblank.h>
71
72#include "drm_internal.h"
73
74#ifdef __NetBSD__		/* XXX hurk -- selnotify &c. */
75#include <sys/poll.h>
76#include <sys/select.h>
77#endif
78
79
80/**
81 * DOC: irq helpers
82 *
83 * The DRM core provides very simple support helpers to enable IRQ handling on a
84 * device through the drm_irq_install() and drm_irq_uninstall() functions. This
85 * only supports devices with a single interrupt on the main device stored in
86 * &drm_device.dev and set as the device paramter in drm_dev_alloc().
87 *
88 * These IRQ helpers are strictly optional. Drivers which roll their own only
89 * need to set &drm_device.irq_enabled to signal the DRM core that vblank
90 * interrupts are working. Since these helpers don't automatically clean up the
91 * requested interrupt like e.g. devm_request_irq() they're not really
92 * recommended.
93 */
94
95/**
96 * drm_irq_install - install IRQ handler
97 * @dev: DRM device
98 * @irq: IRQ number to install the handler for
99 *
100 * Initializes the IRQ related data. Installs the handler, calling the driver
101 * &drm_driver.irq_preinstall and &drm_driver.irq_postinstall functions before
102 * and after the installation.
103 *
104 * This is the simplified helper interface provided for drivers with no special
105 * needs. Drivers which need to install interrupt handlers for multiple
106 * interrupts must instead set &drm_device.irq_enabled to signal the DRM core
107 * that vblank interrupts are available.
108 *
109 * @irq must match the interrupt number that would be passed to request_irq(),
110 * if called directly instead of using this helper function.
111 *
112 * &drm_driver.irq_handler is called to handle the registered interrupt.
113 *
114 * Returns:
115 * Zero on success or a negative error code on failure.
116 */
117#ifdef __NetBSD__
118int drm_irq_install(struct drm_device *dev)
119#else
120int drm_irq_install(struct drm_device *dev, int irq)
121#endif
122{
123	int ret;
124	unsigned long sh_flags = 0;
125
126#ifndef __NetBSD__
127	if (irq == 0)
128		return -EINVAL;
129#endif
130
131	/* Driver must have been initialized */
132	if (!dev->dev_private)
133		return -EINVAL;
134
135	if (dev->irq_enabled)
136		return -EBUSY;
137	dev->irq_enabled = true;
138
139#ifndef __NetBSD__
140	DRM_DEBUG("irq=%d\n", irq);
141#endif
142
143	/* Before installing handler */
144	if (dev->driver->irq_preinstall)
145		dev->driver->irq_preinstall(dev);
146
147	/* PCI devices require shared interrupts. */
148	if (dev->pdev)
149		sh_flags = IRQF_SHARED;
150
151#ifdef __NetBSD__
152	ret = (*dev->driver->request_irq)(dev, sh_flags);
153#else
154	ret = request_irq(irq, dev->driver->irq_handler,
155			  sh_flags, dev->driver->name, dev);
156#endif
157
158	if (ret < 0) {
159		dev->irq_enabled = false;
160		return ret;
161	}
162
163	/* After installing handler */
164	if (dev->driver->irq_postinstall)
165		ret = dev->driver->irq_postinstall(dev);
166
167	if (ret < 0) {
168		dev->irq_enabled = false;
169		if (drm_core_check_feature(dev, DRIVER_LEGACY))
170			vga_client_register(dev->pdev, NULL, NULL, NULL);
171#ifdef __NetBSD__
172		(*dev->driver->free_irq)(dev);
173#else
174		free_irq(irq, dev);
175#endif
176	} else {
177#ifndef __NetBSD__
178		dev->irq = irq;
179#endif
180	}
181
182	return ret;
183}
184EXPORT_SYMBOL(drm_irq_install);
185
186/**
187 * drm_irq_uninstall - uninstall the IRQ handler
188 * @dev: DRM device
189 *
190 * Calls the driver's &drm_driver.irq_uninstall function and unregisters the IRQ
191 * handler.  This should only be called by drivers which used drm_irq_install()
192 * to set up their interrupt handler. Other drivers must only reset
193 * &drm_device.irq_enabled to false.
194 *
195 * Note that for kernel modesetting drivers it is a bug if this function fails.
196 * The sanity checks are only to catch buggy user modesetting drivers which call
197 * the same function through an ioctl.
198 *
199 * Returns:
200 * Zero on success or a negative error code on failure.
201 */
202int drm_irq_uninstall(struct drm_device *dev)
203{
204	unsigned long irqflags;
205	bool irq_enabled;
206	int i;
207
208	irq_enabled = dev->irq_enabled;
209	dev->irq_enabled = false;
210
211	/*
212	 * Wake up any waiters so they don't hang. This is just to paper over
213	 * issues for UMS drivers which aren't in full control of their
214	 * vblank/irq handling. KMS drivers must ensure that vblanks are all
215	 * disabled when uninstalling the irq handler.
216	 */
217	if (dev->num_crtcs) {
218		spin_lock_irqsave(&dev->event_lock, irqflags);
219		for (i = 0; i < dev->num_crtcs; i++) {
220			struct drm_vblank_crtc *vblank = &dev->vblank[i];
221
222			if (!vblank->enabled)
223				continue;
224
225			WARN_ON(drm_core_check_feature(dev, DRIVER_MODESET));
226
227			drm_vblank_disable_and_save(dev, i);
228#ifdef __NetBSD__
229			DRM_SPIN_WAKEUP_ONE(&vblank->queue,
230			    &dev->event_lock);
231#else
232			wake_up(&vblank->queue);
233#endif
234		}
235		spin_unlock_irqrestore(&dev->event_lock, irqflags);
236	}
237
238	if (!irq_enabled)
239		return -EINVAL;
240
241	DRM_DEBUG("irq=%d\n", dev->irq);
242
243	if (drm_core_check_feature(dev, DRIVER_LEGACY))
244		vga_client_register(dev->pdev, NULL, NULL, NULL);
245
246	if (dev->driver->irq_uninstall)
247		dev->driver->irq_uninstall(dev);
248
249#ifdef __NetBSD__
250	(*dev->driver->free_irq)(dev);
251#else
252	free_irq(dev->irq, dev);
253#endif
254
255	return 0;
256}
257EXPORT_SYMBOL(drm_irq_uninstall);
258
259#if IS_ENABLED(CONFIG_DRM_LEGACY)
260int drm_legacy_irq_control(struct drm_device *dev, void *data,
261			   struct drm_file *file_priv)
262{
263	struct drm_control *ctl = data;
264	int ret = 0, irq;
265
266	/* if we haven't irq we fallback for compatibility reasons -
267	 * this used to be a separate function in drm_dma.h
268	 */
269
270	if (!drm_core_check_feature(dev, DRIVER_HAVE_IRQ))
271		return 0;
272	if (!drm_core_check_feature(dev, DRIVER_LEGACY))
273		return 0;
274	/* UMS was only ever supported on pci devices. */
275	if (WARN_ON(!dev->pdev))
276		return -EINVAL;
277
278	switch (ctl->func) {
279	case DRM_INST_HANDLER:
280#ifdef __NetBSD__
281		irq = ctl->irq;
282#else
283		irq = dev->pdev->irq;
284#endif
285
286		if (dev->if_version < DRM_IF_VERSION(1, 2) &&
287		    ctl->irq != irq)
288			return -EINVAL;
289		mutex_lock(&dev->struct_mutex);
290#ifdef __NetBSD__
291		ret = drm_irq_install(dev);
292#else
293		ret = drm_irq_install(dev, irq);
294#endif
295		mutex_unlock(&dev->struct_mutex);
296
297		return ret;
298	case DRM_UNINST_HANDLER:
299		mutex_lock(&dev->struct_mutex);
300		ret = drm_irq_uninstall(dev);
301		mutex_unlock(&dev->struct_mutex);
302
303		return ret;
304	default:
305		return -EINVAL;
306	}
307}
308#endif
309