]>
Commit | Line | Data |
---|---|---|
1da177e4 | 1 | /** |
b5e89ed5 | 2 | * \file drm_irq.c |
1da177e4 LT |
3 | * IRQ support |
4 | * | |
5 | * \author Rickard E. (Rik) Faith <faith@valinux.com> | |
6 | * \author Gareth Hughes <gareth@valinux.com> | |
7 | */ | |
8 | ||
9 | /* | |
10 | * Created: Fri Mar 19 14:30:16 1999 by faith@valinux.com | |
11 | * | |
12 | * Copyright 1999, 2000 Precision Insight, Inc., Cedar Park, Texas. | |
13 | * Copyright 2000 VA Linux Systems, Inc., Sunnyvale, California. | |
14 | * All Rights Reserved. | |
15 | * | |
16 | * Permission is hereby granted, free of charge, to any person obtaining a | |
17 | * copy of this software and associated documentation files (the "Software"), | |
18 | * to deal in the Software without restriction, including without limitation | |
19 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, | |
20 | * and/or sell copies of the Software, and to permit persons to whom the | |
21 | * Software is furnished to do so, subject to the following conditions: | |
22 | * | |
23 | * The above copyright notice and this permission notice (including the next | |
24 | * paragraph) shall be included in all copies or substantial portions of the | |
25 | * Software. | |
26 | * | |
27 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
28 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
29 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL | |
30 | * VA LINUX SYSTEMS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, DAMAGES OR | |
31 | * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, | |
32 | * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR | |
33 | * OTHER DEALINGS IN THE SOFTWARE. | |
34 | */ | |
35 | ||
36 | #include "drmP.h" | |
37 | ||
38 | #include <linux/interrupt.h> /* For task queue support */ | |
39 | ||
40 | /** | |
41 | * Get interrupt from bus id. | |
b5e89ed5 | 42 | * |
1da177e4 | 43 | * \param inode device inode. |
6c340eac | 44 | * \param file_priv DRM file private. |
1da177e4 LT |
45 | * \param cmd command. |
46 | * \param arg user argument, pointing to a drm_irq_busid structure. | |
47 | * \return zero on success or a negative number on failure. | |
b5e89ed5 | 48 | * |
1da177e4 LT |
49 | * Finds the PCI device with the specified bus id and gets its IRQ number. |
50 | * This IOCTL is deprecated, and will now return EINVAL for any busid not equal | |
51 | * to that of the device that this DRM instance attached to. | |
52 | */ | |
c153f45f EA |
53 | int drm_irq_by_busid(struct drm_device *dev, void *data, |
54 | struct drm_file *file_priv) | |
1da177e4 | 55 | { |
c153f45f | 56 | struct drm_irq_busid *p = data; |
1da177e4 LT |
57 | |
58 | if (!drm_core_check_feature(dev, DRIVER_HAVE_IRQ)) | |
59 | return -EINVAL; | |
60 | ||
c153f45f EA |
61 | if ((p->busnum >> 8) != drm_get_pci_domain(dev) || |
62 | (p->busnum & 0xff) != dev->pdev->bus->number || | |
63 | p->devnum != PCI_SLOT(dev->pdev->devfn) || p->funcnum != PCI_FUNC(dev->pdev->devfn)) | |
1da177e4 LT |
64 | return -EINVAL; |
65 | ||
ed4cb414 | 66 | p->irq = dev->pdev->irq; |
c153f45f EA |
67 | |
68 | DRM_DEBUG("%d:%d:%d => IRQ %d\n", p->busnum, p->devnum, p->funcnum, | |
69 | p->irq); | |
1da177e4 | 70 | |
1da177e4 LT |
71 | return 0; |
72 | } | |
73 | ||
74 | /** | |
75 | * Install IRQ handler. | |
76 | * | |
77 | * \param dev DRM device. | |
78 | * \param irq IRQ number. | |
79 | * | |
80 | * Initializes the IRQ related data, and setups drm_device::vbl_queue. Installs the handler, calling the driver | |
81 | * \c drm_driver_irq_preinstall() and \c drm_driver_irq_postinstall() functions | |
82 | * before and after the installation. | |
83 | */ | |
84b1fd10 | 84 | static int drm_irq_install(struct drm_device * dev) |
1da177e4 LT |
85 | { |
86 | int ret; | |
b5e89ed5 | 87 | unsigned long sh_flags = 0; |
1da177e4 LT |
88 | |
89 | if (!drm_core_check_feature(dev, DRIVER_HAVE_IRQ)) | |
90 | return -EINVAL; | |
91 | ||
ed4cb414 | 92 | if (dev->pdev->irq == 0) |
1da177e4 LT |
93 | return -EINVAL; |
94 | ||
30e2fb18 | 95 | mutex_lock(&dev->struct_mutex); |
1da177e4 LT |
96 | |
97 | /* Driver must have been initialized */ | |
b5e89ed5 | 98 | if (!dev->dev_private) { |
30e2fb18 | 99 | mutex_unlock(&dev->struct_mutex); |
1da177e4 LT |
100 | return -EINVAL; |
101 | } | |
102 | ||
b5e89ed5 | 103 | if (dev->irq_enabled) { |
30e2fb18 | 104 | mutex_unlock(&dev->struct_mutex); |
1da177e4 LT |
105 | return -EBUSY; |
106 | } | |
107 | dev->irq_enabled = 1; | |
30e2fb18 | 108 | mutex_unlock(&dev->struct_mutex); |
1da177e4 | 109 | |
ed4cb414 | 110 | DRM_DEBUG("irq=%d\n", dev->pdev->irq); |
1da177e4 | 111 | |
af6061af DA |
112 | if (drm_core_check_feature(dev, DRIVER_IRQ_VBL)) { |
113 | init_waitqueue_head(&dev->vbl_queue); | |
114 | ||
115 | spin_lock_init(&dev->vbl_lock); | |
116 | ||
117 | INIT_LIST_HEAD(&dev->vbl_sigs); | |
118 | INIT_LIST_HEAD(&dev->vbl_sigs2); | |
119 | ||
120 | dev->vbl_pending = 0; | |
121 | } | |
122 | ||
b5e89ed5 | 123 | /* Before installing handler */ |
1da177e4 LT |
124 | dev->driver->irq_preinstall(dev); |
125 | ||
b5e89ed5 | 126 | /* Install handler */ |
1da177e4 | 127 | if (drm_core_check_feature(dev, DRIVER_IRQ_SHARED)) |
935f6e3a | 128 | sh_flags = IRQF_SHARED; |
b5e89ed5 | 129 | |
ed4cb414 | 130 | ret = request_irq(dev->pdev->irq, dev->driver->irq_handler, |
b5e89ed5 | 131 | sh_flags, dev->devname, dev); |
ed4cb414 EA |
132 | /* Expose the device irq number to drivers that want to export it for |
133 | * whatever reason. | |
134 | */ | |
135 | dev->irq = dev->pdev->irq; | |
b5e89ed5 | 136 | if (ret < 0) { |
30e2fb18 | 137 | mutex_lock(&dev->struct_mutex); |
1da177e4 | 138 | dev->irq_enabled = 0; |
30e2fb18 | 139 | mutex_unlock(&dev->struct_mutex); |
1da177e4 LT |
140 | return ret; |
141 | } | |
142 | ||
b5e89ed5 | 143 | /* After installing handler */ |
af6061af | 144 | dev->driver->irq_postinstall(dev); |
1da177e4 | 145 | |
af6061af | 146 | return 0; |
1da177e4 LT |
147 | } |
148 | ||
149 | /** | |
150 | * Uninstall the IRQ handler. | |
151 | * | |
152 | * \param dev DRM device. | |
153 | * | |
154 | * Calls the driver's \c drm_driver_irq_uninstall() function, and stops the irq. | |
155 | */ | |
84b1fd10 | 156 | int drm_irq_uninstall(struct drm_device * dev) |
1da177e4 LT |
157 | { |
158 | int irq_enabled; | |
159 | ||
160 | if (!drm_core_check_feature(dev, DRIVER_HAVE_IRQ)) | |
161 | return -EINVAL; | |
162 | ||
30e2fb18 | 163 | mutex_lock(&dev->struct_mutex); |
1da177e4 LT |
164 | irq_enabled = dev->irq_enabled; |
165 | dev->irq_enabled = 0; | |
30e2fb18 | 166 | mutex_unlock(&dev->struct_mutex); |
1da177e4 | 167 | |
b5e89ed5 | 168 | if (!irq_enabled) |
1da177e4 LT |
169 | return -EINVAL; |
170 | ||
ed4cb414 | 171 | DRM_DEBUG("irq=%d\n", dev->pdev->irq); |
1da177e4 LT |
172 | |
173 | dev->driver->irq_uninstall(dev); | |
174 | ||
ed4cb414 | 175 | free_irq(dev->pdev->irq, dev); |
1da177e4 | 176 | |
2e54a007 MD |
177 | dev->locked_tasklet_func = NULL; |
178 | ||
1da177e4 LT |
179 | return 0; |
180 | } | |
b5e89ed5 | 181 | |
1da177e4 LT |
182 | EXPORT_SYMBOL(drm_irq_uninstall); |
183 | ||
184 | /** | |
185 | * IRQ control ioctl. | |
186 | * | |
187 | * \param inode device inode. | |
6c340eac | 188 | * \param file_priv DRM file private. |
1da177e4 LT |
189 | * \param cmd command. |
190 | * \param arg user argument, pointing to a drm_control structure. | |
191 | * \return zero on success or a negative number on failure. | |
192 | * | |
193 | * Calls irq_install() or irq_uninstall() according to \p arg. | |
194 | */ | |
c153f45f EA |
195 | int drm_control(struct drm_device *dev, void *data, |
196 | struct drm_file *file_priv) | |
1da177e4 | 197 | { |
c153f45f | 198 | struct drm_control *ctl = data; |
b5e89ed5 | 199 | |
1da177e4 LT |
200 | /* if we haven't irq we fallback for compatibility reasons - this used to be a separate function in drm_dma.h */ |
201 | ||
1da177e4 | 202 | |
c153f45f | 203 | switch (ctl->func) { |
1da177e4 LT |
204 | case DRM_INST_HANDLER: |
205 | if (!drm_core_check_feature(dev, DRIVER_HAVE_IRQ)) | |
206 | return 0; | |
207 | if (dev->if_version < DRM_IF_VERSION(1, 2) && | |
ed4cb414 | 208 | ctl->irq != dev->pdev->irq) |
1da177e4 | 209 | return -EINVAL; |
b5e89ed5 | 210 | return drm_irq_install(dev); |
1da177e4 LT |
211 | case DRM_UNINST_HANDLER: |
212 | if (!drm_core_check_feature(dev, DRIVER_HAVE_IRQ)) | |
213 | return 0; | |
b5e89ed5 | 214 | return drm_irq_uninstall(dev); |
1da177e4 LT |
215 | default: |
216 | return -EINVAL; | |
217 | } | |
218 | } | |
219 | ||
220 | /** | |
221 | * Wait for VBLANK. | |
222 | * | |
223 | * \param inode device inode. | |
6c340eac | 224 | * \param file_priv DRM file private. |
1da177e4 LT |
225 | * \param cmd command. |
226 | * \param data user argument, pointing to a drm_wait_vblank structure. | |
227 | * \return zero on success or a negative number on failure. | |
228 | * | |
b5e89ed5 | 229 | * Verifies the IRQ is installed. |
1da177e4 LT |
230 | * |
231 | * If a signal is requested checks if this task has already scheduled the same signal | |
232 | * for the same vblank sequence number - nothing to be done in | |
233 | * that case. If the number of tasks waiting for the interrupt exceeds 100 the | |
234 | * function fails. Otherwise adds a new entry to drm_device::vbl_sigs for this | |
235 | * task. | |
236 | * | |
237 | * If a signal is not requested, then calls vblank_wait(). | |
238 | */ | |
af6061af | 239 | int drm_wait_vblank(struct drm_device *dev, void *data, struct drm_file *file_priv) |
1da177e4 | 240 | { |
c153f45f | 241 | union drm_wait_vblank *vblwait = data; |
1da177e4 LT |
242 | struct timeval now; |
243 | int ret = 0; | |
af6061af | 244 | unsigned int flags, seq; |
1da177e4 | 245 | |
ed4cb414 | 246 | if ((!dev->pdev->irq) || (!dev->irq_enabled)) |
1da177e4 LT |
247 | return -EINVAL; |
248 | ||
c153f45f | 249 | if (vblwait->request.type & |
776c9443 MD |
250 | ~(_DRM_VBLANK_TYPES_MASK | _DRM_VBLANK_FLAGS_MASK)) { |
251 | DRM_ERROR("Unsupported type value 0x%x, supported mask 0x%x\n", | |
c153f45f | 252 | vblwait->request.type, |
776c9443 MD |
253 | (_DRM_VBLANK_TYPES_MASK | _DRM_VBLANK_FLAGS_MASK)); |
254 | return -EINVAL; | |
255 | } | |
256 | ||
c153f45f | 257 | flags = vblwait->request.type & _DRM_VBLANK_FLAGS_MASK; |
776c9443 | 258 | |
af6061af DA |
259 | if (!drm_core_check_feature(dev, (flags & _DRM_VBLANK_SECONDARY) ? |
260 | DRIVER_IRQ_VBL2 : DRIVER_IRQ_VBL)) | |
776c9443 MD |
261 | return -EINVAL; |
262 | ||
af6061af DA |
263 | seq = atomic_read((flags & _DRM_VBLANK_SECONDARY) ? &dev->vbl_received2 |
264 | : &dev->vbl_received); | |
776c9443 | 265 | |
c153f45f | 266 | switch (vblwait->request.type & _DRM_VBLANK_TYPES_MASK) { |
1da177e4 | 267 | case _DRM_VBLANK_RELATIVE: |
c153f45f EA |
268 | vblwait->request.sequence += seq; |
269 | vblwait->request.type &= ~_DRM_VBLANK_RELATIVE; | |
1da177e4 LT |
270 | case _DRM_VBLANK_ABSOLUTE: |
271 | break; | |
272 | default: | |
273 | return -EINVAL; | |
274 | } | |
275 | ||
ab285d74 | 276 | if ((flags & _DRM_VBLANK_NEXTONMISS) && |
c153f45f EA |
277 | (seq - vblwait->request.sequence) <= (1<<23)) { |
278 | vblwait->request.sequence = seq + 1; | |
ab285d74 MD |
279 | } |
280 | ||
b5e89ed5 | 281 | if (flags & _DRM_VBLANK_SIGNAL) { |
1da177e4 | 282 | unsigned long irqflags; |
af6061af DA |
283 | struct list_head *vbl_sigs = (flags & _DRM_VBLANK_SECONDARY) |
284 | ? &dev->vbl_sigs2 : &dev->vbl_sigs; | |
55910517 | 285 | struct drm_vbl_sig *vbl_sig; |
1da177e4 | 286 | |
b5e89ed5 | 287 | spin_lock_irqsave(&dev->vbl_lock, irqflags); |
1da177e4 LT |
288 | |
289 | /* Check if this task has already scheduled the same signal | |
290 | * for the same vblank sequence number; nothing to be done in | |
291 | * that case | |
292 | */ | |
bd1b331f | 293 | list_for_each_entry(vbl_sig, vbl_sigs, head) { |
c153f45f EA |
294 | if (vbl_sig->sequence == vblwait->request.sequence |
295 | && vbl_sig->info.si_signo == | |
296 | vblwait->request.signal | |
b5e89ed5 DA |
297 | && vbl_sig->task == current) { |
298 | spin_unlock_irqrestore(&dev->vbl_lock, | |
299 | irqflags); | |
c153f45f | 300 | vblwait->reply.sequence = seq; |
1da177e4 LT |
301 | goto done; |
302 | } | |
303 | } | |
304 | ||
af6061af | 305 | if (dev->vbl_pending >= 100) { |
b5e89ed5 | 306 | spin_unlock_irqrestore(&dev->vbl_lock, irqflags); |
1da177e4 LT |
307 | return -EBUSY; |
308 | } | |
309 | ||
af6061af DA |
310 | dev->vbl_pending++; |
311 | ||
b5e89ed5 | 312 | spin_unlock_irqrestore(&dev->vbl_lock, irqflags); |
1da177e4 | 313 | |
af6061af DA |
314 | if (! |
315 | (vbl_sig = | |
316 | drm_alloc(sizeof(struct drm_vbl_sig), DRM_MEM_DRIVER))) { | |
1da177e4 LT |
317 | return -ENOMEM; |
318 | } | |
319 | ||
af6061af | 320 | memset((void *)vbl_sig, 0, sizeof(*vbl_sig)); |
1da177e4 | 321 | |
c153f45f EA |
322 | vbl_sig->sequence = vblwait->request.sequence; |
323 | vbl_sig->info.si_signo = vblwait->request.signal; | |
1da177e4 LT |
324 | vbl_sig->task = current; |
325 | ||
b5e89ed5 | 326 | spin_lock_irqsave(&dev->vbl_lock, irqflags); |
1da177e4 | 327 | |
bd1b331f | 328 | list_add_tail(&vbl_sig->head, vbl_sigs); |
1da177e4 | 329 | |
b5e89ed5 | 330 | spin_unlock_irqrestore(&dev->vbl_lock, irqflags); |
049b3233 | 331 | |
c153f45f | 332 | vblwait->reply.sequence = seq; |
1da177e4 | 333 | } else { |
af6061af DA |
334 | if (flags & _DRM_VBLANK_SECONDARY) { |
335 | if (dev->driver->vblank_wait2) | |
336 | ret = dev->driver->vblank_wait2(dev, &vblwait->request.sequence); | |
337 | } else if (dev->driver->vblank_wait) | |
338 | ret = | |
339 | dev->driver->vblank_wait(dev, | |
340 | &vblwait->request.sequence); | |
ac741ab7 | 341 | |
af6061af | 342 | do_gettimeofday(&now); |
c153f45f EA |
343 | vblwait->reply.tval_sec = now.tv_sec; |
344 | vblwait->reply.tval_usec = now.tv_usec; | |
1da177e4 LT |
345 | } |
346 | ||
b5e89ed5 | 347 | done: |
1da177e4 LT |
348 | return ret; |
349 | } | |
350 | ||
351 | /** | |
352 | * Send the VBLANK signals. | |
353 | * | |
354 | * \param dev DRM device. | |
355 | * | |
356 | * Sends a signal for each task in drm_device::vbl_sigs and empties the list. | |
357 | * | |
358 | * If a signal is not requested, then calls vblank_wait(). | |
359 | */ | |
af6061af | 360 | void drm_vbl_send_signals(struct drm_device * dev) |
1da177e4 | 361 | { |
1da177e4 | 362 | unsigned long flags; |
af6061af | 363 | int i; |
1da177e4 | 364 | |
b5e89ed5 | 365 | spin_lock_irqsave(&dev->vbl_lock, flags); |
1da177e4 | 366 | |
af6061af DA |
367 | for (i = 0; i < 2; i++) { |
368 | struct drm_vbl_sig *vbl_sig, *tmp; | |
369 | struct list_head *vbl_sigs = i ? &dev->vbl_sigs2 : &dev->vbl_sigs; | |
370 | unsigned int vbl_seq = atomic_read(i ? &dev->vbl_received2 : | |
371 | &dev->vbl_received); | |
776c9443 | 372 | |
af6061af DA |
373 | list_for_each_entry_safe(vbl_sig, tmp, vbl_sigs, head) { |
374 | if ((vbl_seq - vbl_sig->sequence) <= (1 << 23)) { | |
375 | vbl_sig->info.si_code = vbl_seq; | |
376 | send_sig_info(vbl_sig->info.si_signo, | |
377 | &vbl_sig->info, vbl_sig->task); | |
1da177e4 | 378 | |
af6061af | 379 | list_del(&vbl_sig->head); |
1da177e4 | 380 | |
af6061af DA |
381 | drm_free(vbl_sig, sizeof(*vbl_sig), |
382 | DRM_MEM_DRIVER); | |
383 | ||
384 | dev->vbl_pending--; | |
385 | } | |
386 | } | |
1da177e4 LT |
387 | } |
388 | ||
b5e89ed5 | 389 | spin_unlock_irqrestore(&dev->vbl_lock, flags); |
1da177e4 | 390 | } |
1da177e4 | 391 | |
af6061af | 392 | EXPORT_SYMBOL(drm_vbl_send_signals); |
2e54a007 MD |
393 | |
394 | /** | |
395 | * Tasklet wrapper function. | |
396 | * | |
397 | * \param data DRM device in disguise. | |
398 | * | |
399 | * Attempts to grab the HW lock and calls the driver callback on success. On | |
400 | * failure, leave the lock marked as contended so the callback can be called | |
401 | * from drm_unlock(). | |
402 | */ | |
403 | static void drm_locked_tasklet_func(unsigned long data) | |
404 | { | |
84b1fd10 | 405 | struct drm_device *dev = (struct drm_device *)data; |
2e54a007 | 406 | unsigned long irqflags; |
e5b4f194 TH |
407 | void (*tasklet_func)(struct drm_device *); |
408 | ||
2e54a007 | 409 | spin_lock_irqsave(&dev->tasklet_lock, irqflags); |
e5b4f194 TH |
410 | tasklet_func = dev->locked_tasklet_func; |
411 | spin_unlock_irqrestore(&dev->tasklet_lock, irqflags); | |
2e54a007 | 412 | |
e5b4f194 | 413 | if (!tasklet_func || |
040ac320 | 414 | !drm_lock_take(&dev->lock, |
2e54a007 | 415 | DRM_KERNEL_CONTEXT)) { |
2e54a007 MD |
416 | return; |
417 | } | |
418 | ||
419 | dev->lock.lock_time = jiffies; | |
420 | atomic_inc(&dev->counts[_DRM_STAT_LOCKS]); | |
421 | ||
e5b4f194 TH |
422 | spin_lock_irqsave(&dev->tasklet_lock, irqflags); |
423 | tasklet_func = dev->locked_tasklet_func; | |
424 | dev->locked_tasklet_func = NULL; | |
425 | spin_unlock_irqrestore(&dev->tasklet_lock, irqflags); | |
426 | ||
427 | if (tasklet_func != NULL) | |
428 | tasklet_func(dev); | |
2e54a007 | 429 | |
040ac320 | 430 | drm_lock_free(&dev->lock, |
2e54a007 | 431 | DRM_KERNEL_CONTEXT); |
2e54a007 MD |
432 | } |
433 | ||
434 | /** | |
435 | * Schedule a tasklet to call back a driver hook with the HW lock held. | |
436 | * | |
437 | * \param dev DRM device. | |
438 | * \param func Driver callback. | |
439 | * | |
440 | * This is intended for triggering actions that require the HW lock from an | |
441 | * interrupt handler. The lock will be grabbed ASAP after the interrupt handler | |
442 | * completes. Note that the callback may be called from interrupt or process | |
443 | * context, it must not make any assumptions about this. Also, the HW lock will | |
444 | * be held with the kernel context or any client context. | |
445 | */ | |
84b1fd10 | 446 | void drm_locked_tasklet(struct drm_device *dev, void (*func)(struct drm_device *)) |
2e54a007 MD |
447 | { |
448 | unsigned long irqflags; | |
449 | static DECLARE_TASKLET(drm_tasklet, drm_locked_tasklet_func, 0); | |
450 | ||
8163e418 MD |
451 | if (!drm_core_check_feature(dev, DRIVER_HAVE_IRQ) || |
452 | test_bit(TASKLET_STATE_SCHED, &drm_tasklet.state)) | |
2e54a007 MD |
453 | return; |
454 | ||
455 | spin_lock_irqsave(&dev->tasklet_lock, irqflags); | |
456 | ||
457 | if (dev->locked_tasklet_func) { | |
458 | spin_unlock_irqrestore(&dev->tasklet_lock, irqflags); | |
459 | return; | |
460 | } | |
461 | ||
462 | dev->locked_tasklet_func = func; | |
463 | ||
464 | spin_unlock_irqrestore(&dev->tasklet_lock, irqflags); | |
465 | ||
466 | drm_tasklet.data = (unsigned long)dev; | |
467 | ||
468 | tasklet_hi_schedule(&drm_tasklet); | |
469 | } | |
470 | EXPORT_SYMBOL(drm_locked_tasklet); |