.. | .. |
---|
| 1 | +// SPDX-License-Identifier: GPL-2.0-or-later |
---|
1 | 2 | /* |
---|
2 | 3 | * imon.c: input and display driver for SoundGraph iMON IR/VFD/LCD |
---|
3 | 4 | * |
---|
.. | .. |
---|
10 | 11 | * which the support for them wouldn't be nearly as good. Thanks |
---|
11 | 12 | * also to the numerous 0xffdc device owners that tested auto-config |
---|
12 | 13 | * support for me and provided debug dumps from their devices. |
---|
13 | | - * |
---|
14 | | - * imon is free software; you can redistribute it and/or modify |
---|
15 | | - * it under the terms of the GNU General Public License as published by |
---|
16 | | - * the Free Software Foundation; either version 2 of the License, or |
---|
17 | | - * (at your option) any later version. |
---|
18 | | - * |
---|
19 | | - * This program is distributed in the hope that it will be useful, |
---|
20 | | - * but WITHOUT ANY WARRANTY; without even the implied warranty of |
---|
21 | | - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
---|
22 | | - * GNU General Public License for more details. |
---|
23 | 14 | */ |
---|
24 | 15 | |
---|
25 | 16 | #define pr_fmt(fmt) KBUILD_MODNAME ":%s: " fmt, __func__ |
---|
.. | .. |
---|
92 | 83 | __u16 flags; |
---|
93 | 84 | #define IMON_NO_FLAGS 0 |
---|
94 | 85 | #define IMON_NEED_20MS_PKT_DELAY 1 |
---|
| 86 | +#define IMON_SUPPRESS_REPEATED_KEYS 2 |
---|
95 | 87 | struct imon_panel_key_table key_table[]; |
---|
96 | 88 | }; |
---|
97 | 89 | |
---|
.. | .. |
---|
158 | 150 | struct timer_list ttimer; /* touch screen timer */ |
---|
159 | 151 | int touch_x; /* x coordinate on touchscreen */ |
---|
160 | 152 | int touch_y; /* y coordinate on touchscreen */ |
---|
161 | | - struct imon_usb_dev_descr *dev_descr; /* device description with key |
---|
162 | | - table for front panels */ |
---|
| 153 | + const struct imon_usb_dev_descr *dev_descr; |
---|
| 154 | + /* device description with key */ |
---|
| 155 | + /* table for front panels */ |
---|
| 156 | + /* |
---|
| 157 | + * Fields for deferring free_imon_context(). |
---|
| 158 | + * |
---|
| 159 | + * Since reference to "struct imon_context" is stored into |
---|
| 160 | + * "struct file"->private_data, we need to remember |
---|
| 161 | + * how many file descriptors might access this "struct imon_context". |
---|
| 162 | + */ |
---|
| 163 | + refcount_t users; |
---|
| 164 | + /* |
---|
| 165 | + * Use a flag for telling display_open()/vfd_write()/lcd_write() that |
---|
| 166 | + * imon_disconnect() was already called. |
---|
| 167 | + */ |
---|
| 168 | + bool disconnected; |
---|
| 169 | + /* |
---|
| 170 | + * We need to wait for RCU grace period in order to allow |
---|
| 171 | + * display_open() to safely check ->disconnected and increment ->users. |
---|
| 172 | + */ |
---|
| 173 | + struct rcu_head rcu; |
---|
163 | 174 | }; |
---|
164 | 175 | |
---|
165 | 176 | #define TOUCH_TIMEOUT (HZ/30) |
---|
.. | .. |
---|
167 | 178 | /* vfd character device file operations */ |
---|
168 | 179 | static const struct file_operations vfd_fops = { |
---|
169 | 180 | .owner = THIS_MODULE, |
---|
170 | | - .open = &display_open, |
---|
171 | | - .write = &vfd_write, |
---|
172 | | - .release = &display_close, |
---|
| 181 | + .open = display_open, |
---|
| 182 | + .write = vfd_write, |
---|
| 183 | + .release = display_close, |
---|
173 | 184 | .llseek = noop_llseek, |
---|
174 | 185 | }; |
---|
175 | 186 | |
---|
176 | 187 | /* lcd character device file operations */ |
---|
177 | 188 | static const struct file_operations lcd_fops = { |
---|
178 | 189 | .owner = THIS_MODULE, |
---|
179 | | - .open = &display_open, |
---|
180 | | - .write = &lcd_write, |
---|
181 | | - .release = &display_close, |
---|
| 190 | + .open = display_open, |
---|
| 191 | + .write = lcd_write, |
---|
| 192 | + .release = display_close, |
---|
182 | 193 | .llseek = noop_llseek, |
---|
183 | 194 | }; |
---|
184 | 195 | |
---|
.. | .. |
---|
324 | 335 | } |
---|
325 | 336 | }; |
---|
326 | 337 | |
---|
| 338 | +/* imon ultrabay front panel key table */ |
---|
| 339 | +static const struct imon_usb_dev_descr ultrabay_table = { |
---|
| 340 | + .flags = IMON_SUPPRESS_REPEATED_KEYS, |
---|
| 341 | + .key_table = { |
---|
| 342 | + { 0x0000000f0000ffeell, KEY_MEDIA }, /* Go */ |
---|
| 343 | + { 0x000000000100ffeell, KEY_UP }, |
---|
| 344 | + { 0x000000000001ffeell, KEY_DOWN }, |
---|
| 345 | + { 0x000000160000ffeell, KEY_ENTER }, |
---|
| 346 | + { 0x0000001f0000ffeell, KEY_AUDIO }, /* Music */ |
---|
| 347 | + { 0x000000200000ffeell, KEY_VIDEO }, /* Movie */ |
---|
| 348 | + { 0x000000210000ffeell, KEY_CAMERA }, /* Photo */ |
---|
| 349 | + { 0x000000270000ffeell, KEY_DVD }, /* DVD */ |
---|
| 350 | + { 0x000000230000ffeell, KEY_TV }, /* TV */ |
---|
| 351 | + { 0x000000050000ffeell, KEY_PREVIOUS }, /* Previous */ |
---|
| 352 | + { 0x000000070000ffeell, KEY_REWIND }, |
---|
| 353 | + { 0x000000040000ffeell, KEY_STOP }, |
---|
| 354 | + { 0x000000020000ffeell, KEY_PLAYPAUSE }, |
---|
| 355 | + { 0x000000080000ffeell, KEY_FASTFORWARD }, |
---|
| 356 | + { 0x000000060000ffeell, KEY_NEXT }, /* Next */ |
---|
| 357 | + { 0x000100000000ffeell, KEY_VOLUMEUP }, |
---|
| 358 | + { 0x010000000000ffeell, KEY_VOLUMEDOWN }, |
---|
| 359 | + { 0x000000010000ffeell, KEY_MUTE }, |
---|
| 360 | + { 0, KEY_RESERVED }, |
---|
| 361 | + } |
---|
| 362 | +}; |
---|
| 363 | + |
---|
327 | 364 | /* |
---|
328 | 365 | * USB Device ID for iMON USB Control Boards |
---|
329 | 366 | * |
---|
.. | .. |
---|
420 | 457 | .id_table = imon_usb_id_table, |
---|
421 | 458 | }; |
---|
422 | 459 | |
---|
423 | | -/* to prevent races between open() and disconnect(), probing, etc */ |
---|
424 | | -static DEFINE_MUTEX(driver_lock); |
---|
425 | | - |
---|
426 | 460 | /* Module bookkeeping bits */ |
---|
427 | 461 | MODULE_AUTHOR(MOD_AUTHOR); |
---|
428 | 462 | MODULE_DESCRIPTION(MOD_DESC); |
---|
.. | .. |
---|
462 | 496 | struct device *dev = ictx->dev; |
---|
463 | 497 | |
---|
464 | 498 | usb_free_urb(ictx->tx_urb); |
---|
| 499 | + WARN_ON(ictx->dev_present_intf0); |
---|
465 | 500 | usb_free_urb(ictx->rx_urb_intf0); |
---|
| 501 | + WARN_ON(ictx->dev_present_intf1); |
---|
466 | 502 | usb_free_urb(ictx->rx_urb_intf1); |
---|
467 | | - kfree(ictx); |
---|
| 503 | + kfree_rcu(ictx, rcu); |
---|
468 | 504 | |
---|
469 | 505 | dev_dbg(dev, "%s: iMON context freed\n", __func__); |
---|
470 | 506 | } |
---|
.. | .. |
---|
480 | 516 | int subminor; |
---|
481 | 517 | int retval = 0; |
---|
482 | 518 | |
---|
483 | | - /* prevent races with disconnect */ |
---|
484 | | - mutex_lock(&driver_lock); |
---|
485 | | - |
---|
486 | 519 | subminor = iminor(inode); |
---|
487 | 520 | interface = usb_find_interface(&imon_driver, subminor); |
---|
488 | 521 | if (!interface) { |
---|
.. | .. |
---|
490 | 523 | retval = -ENODEV; |
---|
491 | 524 | goto exit; |
---|
492 | 525 | } |
---|
493 | | - ictx = usb_get_intfdata(interface); |
---|
494 | 526 | |
---|
495 | | - if (!ictx) { |
---|
| 527 | + rcu_read_lock(); |
---|
| 528 | + ictx = usb_get_intfdata(interface); |
---|
| 529 | + if (!ictx || ictx->disconnected || !refcount_inc_not_zero(&ictx->users)) { |
---|
| 530 | + rcu_read_unlock(); |
---|
496 | 531 | pr_err("no context found for minor %d\n", subminor); |
---|
497 | 532 | retval = -ENODEV; |
---|
498 | 533 | goto exit; |
---|
499 | 534 | } |
---|
| 535 | + rcu_read_unlock(); |
---|
500 | 536 | |
---|
501 | 537 | mutex_lock(&ictx->lock); |
---|
502 | 538 | |
---|
.. | .. |
---|
514 | 550 | |
---|
515 | 551 | mutex_unlock(&ictx->lock); |
---|
516 | 552 | |
---|
| 553 | + if (retval && refcount_dec_and_test(&ictx->users)) |
---|
| 554 | + free_imon_context(ictx); |
---|
| 555 | + |
---|
517 | 556 | exit: |
---|
518 | | - mutex_unlock(&driver_lock); |
---|
519 | 557 | return retval; |
---|
520 | 558 | } |
---|
521 | 559 | |
---|
.. | .. |
---|
525 | 563 | */ |
---|
526 | 564 | static int display_close(struct inode *inode, struct file *file) |
---|
527 | 565 | { |
---|
528 | | - struct imon_context *ictx = NULL; |
---|
| 566 | + struct imon_context *ictx = file->private_data; |
---|
529 | 567 | int retval = 0; |
---|
530 | | - |
---|
531 | | - ictx = file->private_data; |
---|
532 | | - |
---|
533 | | - if (!ictx) { |
---|
534 | | - pr_err("no context for device\n"); |
---|
535 | | - return -ENODEV; |
---|
536 | | - } |
---|
537 | 568 | |
---|
538 | 569 | mutex_lock(&ictx->lock); |
---|
539 | 570 | |
---|
.. | .. |
---|
549 | 580 | } |
---|
550 | 581 | |
---|
551 | 582 | mutex_unlock(&ictx->lock); |
---|
| 583 | + if (refcount_dec_and_test(&ictx->users)) |
---|
| 584 | + free_imon_context(ictx); |
---|
552 | 585 | return retval; |
---|
553 | 586 | } |
---|
554 | 587 | |
---|
.. | .. |
---|
613 | 646 | pr_err_ratelimited("error submitting urb(%d)\n", retval); |
---|
614 | 647 | } else { |
---|
615 | 648 | /* Wait for transmission to complete (or abort) */ |
---|
616 | | - mutex_unlock(&ictx->lock); |
---|
617 | 649 | retval = wait_for_completion_interruptible( |
---|
618 | 650 | &ictx->tx.finished); |
---|
619 | 651 | if (retval) { |
---|
620 | 652 | usb_kill_urb(ictx->tx_urb); |
---|
621 | 653 | pr_err_ratelimited("task interrupted\n"); |
---|
622 | 654 | } |
---|
623 | | - mutex_lock(&ictx->lock); |
---|
624 | 655 | |
---|
| 656 | + ictx->tx.busy = false; |
---|
625 | 657 | retval = ictx->tx.status; |
---|
626 | 658 | if (retval) |
---|
627 | 659 | pr_err_ratelimited("packet tx failed (%d)\n", retval); |
---|
.. | .. |
---|
772 | 804 | |
---|
773 | 805 | mutex_lock(&ictx->lock); |
---|
774 | 806 | if (ictx->rf_isassociating) |
---|
775 | | - strcpy(buf, "associating\n"); |
---|
| 807 | + strscpy(buf, "associating\n", PAGE_SIZE); |
---|
776 | 808 | else |
---|
777 | | - strcpy(buf, "closed\n"); |
---|
| 809 | + strscpy(buf, "closed\n", PAGE_SIZE); |
---|
778 | 810 | |
---|
779 | | - dev_info(d, "Visit http://www.lirc.org/html/imon-24g.html for instructions on how to associate your iMON 2.4G DT/LT remote\n"); |
---|
| 811 | + dev_info(d, "Visit https://www.lirc.org/html/imon-24g.html for instructions on how to associate your iMON 2.4G DT/LT remote\n"); |
---|
780 | 812 | mutex_unlock(&ictx->lock); |
---|
781 | 813 | return strlen(buf); |
---|
782 | 814 | } |
---|
.. | .. |
---|
918 | 950 | int offset; |
---|
919 | 951 | int seq; |
---|
920 | 952 | int retval = 0; |
---|
921 | | - struct imon_context *ictx; |
---|
| 953 | + struct imon_context *ictx = file->private_data; |
---|
922 | 954 | static const unsigned char vfd_packet6[] = { |
---|
923 | 955 | 0x01, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF }; |
---|
924 | 956 | |
---|
925 | | - ictx = file->private_data; |
---|
926 | | - if (!ictx) { |
---|
927 | | - pr_err_ratelimited("no context for device\n"); |
---|
| 957 | + if (ictx->disconnected) |
---|
928 | 958 | return -ENODEV; |
---|
929 | | - } |
---|
930 | 959 | |
---|
931 | | - mutex_lock(&ictx->lock); |
---|
| 960 | + if (mutex_lock_interruptible(&ictx->lock)) |
---|
| 961 | + return -ERESTARTSYS; |
---|
932 | 962 | |
---|
933 | 963 | if (!ictx->dev_present_intf0) { |
---|
934 | 964 | pr_err_ratelimited("no iMON device present\n"); |
---|
.. | .. |
---|
1002 | 1032 | size_t n_bytes, loff_t *pos) |
---|
1003 | 1033 | { |
---|
1004 | 1034 | int retval = 0; |
---|
1005 | | - struct imon_context *ictx; |
---|
| 1035 | + struct imon_context *ictx = file->private_data; |
---|
1006 | 1036 | |
---|
1007 | | - ictx = file->private_data; |
---|
1008 | | - if (!ictx) { |
---|
1009 | | - pr_err_ratelimited("no context for device\n"); |
---|
| 1037 | + if (ictx->disconnected) |
---|
1010 | 1038 | return -ENODEV; |
---|
1011 | | - } |
---|
1012 | 1039 | |
---|
1013 | 1040 | mutex_lock(&ictx->lock); |
---|
1014 | 1041 | |
---|
.. | .. |
---|
1273 | 1300 | |
---|
1274 | 1301 | static u32 imon_panel_key_lookup(struct imon_context *ictx, u64 code) |
---|
1275 | 1302 | { |
---|
1276 | | - int i; |
---|
| 1303 | + const struct imon_panel_key_table *key_table; |
---|
1277 | 1304 | u32 keycode = KEY_RESERVED; |
---|
1278 | | - struct imon_panel_key_table *key_table = ictx->dev_descr->key_table; |
---|
| 1305 | + int i; |
---|
| 1306 | + |
---|
| 1307 | + key_table = ictx->dev_descr->key_table; |
---|
1279 | 1308 | |
---|
1280 | 1309 | for (i = 0; key_table[i].hw_code != 0; i++) { |
---|
1281 | 1310 | if (key_table[i].hw_code == (code | 0xffee)) { |
---|
.. | .. |
---|
1559 | 1588 | u32 kc; |
---|
1560 | 1589 | u64 scancode; |
---|
1561 | 1590 | int press_type = 0; |
---|
1562 | | - long msec; |
---|
1563 | 1591 | ktime_t t; |
---|
1564 | 1592 | static ktime_t prev_time; |
---|
1565 | 1593 | u8 ktype; |
---|
.. | .. |
---|
1661 | 1689 | spin_lock_irqsave(&ictx->kc_lock, flags); |
---|
1662 | 1690 | |
---|
1663 | 1691 | t = ktime_get(); |
---|
1664 | | - /* KEY_MUTE repeats from knob need to be suppressed */ |
---|
1665 | | - if (ictx->kc == KEY_MUTE && ictx->kc == ictx->last_keycode) { |
---|
1666 | | - msec = ktime_ms_delta(t, prev_time); |
---|
1667 | | - if (msec < ictx->idev->rep[REP_DELAY]) { |
---|
| 1692 | + /* KEY repeats from knob and panel that need to be suppressed */ |
---|
| 1693 | + if (ictx->kc == KEY_MUTE || |
---|
| 1694 | + ictx->dev_descr->flags & IMON_SUPPRESS_REPEATED_KEYS) { |
---|
| 1695 | + if (ictx->kc == ictx->last_keycode && |
---|
| 1696 | + ktime_ms_delta(t, prev_time) < ictx->idev->rep[REP_DELAY]) { |
---|
1668 | 1697 | spin_unlock_irqrestore(&ictx->kc_lock, flags); |
---|
1669 | 1698 | return; |
---|
1670 | 1699 | } |
---|
1671 | 1700 | } |
---|
| 1701 | + |
---|
1672 | 1702 | prev_time = t; |
---|
1673 | 1703 | kc = ictx->kc; |
---|
1674 | 1704 | |
---|
.. | .. |
---|
1856 | 1886 | dev_info(ictx->dev, "0xffdc iMON Inside, iMON IR"); |
---|
1857 | 1887 | ictx->display_supported = false; |
---|
1858 | 1888 | break; |
---|
| 1889 | + /* Soundgraph iMON UltraBay */ |
---|
| 1890 | + case 0x98: |
---|
| 1891 | + dev_info(ictx->dev, "0xffdc iMON UltraBay, LCD + IR"); |
---|
| 1892 | + detected_display_type = IMON_DISPLAY_TYPE_LCD; |
---|
| 1893 | + allowed_protos = RC_PROTO_BIT_IMON | RC_PROTO_BIT_RC6_MCE; |
---|
| 1894 | + ictx->dev_descr = &ultrabay_table; |
---|
| 1895 | + break; |
---|
| 1896 | + |
---|
1859 | 1897 | default: |
---|
1860 | 1898 | dev_info(ictx->dev, "Unknown 0xffdc device, defaulting to VFD and iMON IR"); |
---|
1861 | 1899 | detected_display_type = IMON_DISPLAY_TYPE_VFD; |
---|
.. | .. |
---|
1987 | 2025 | |
---|
1988 | 2026 | static struct input_dev *imon_init_idev(struct imon_context *ictx) |
---|
1989 | 2027 | { |
---|
1990 | | - struct imon_panel_key_table *key_table = ictx->dev_descr->key_table; |
---|
| 2028 | + const struct imon_panel_key_table *key_table; |
---|
1991 | 2029 | struct input_dev *idev; |
---|
1992 | 2030 | int ret, i; |
---|
| 2031 | + |
---|
| 2032 | + key_table = ictx->dev_descr->key_table; |
---|
1993 | 2033 | |
---|
1994 | 2034 | idev = input_allocate_device(); |
---|
1995 | 2035 | if (!idev) |
---|
.. | .. |
---|
2373 | 2413 | int ifnum, sysfs_err; |
---|
2374 | 2414 | int ret = 0; |
---|
2375 | 2415 | struct imon_context *ictx = NULL; |
---|
2376 | | - struct imon_context *first_if_ctx = NULL; |
---|
2377 | 2416 | u16 vendor, product; |
---|
2378 | 2417 | |
---|
2379 | 2418 | usbdev = usb_get_dev(interface_to_usbdev(interface)); |
---|
.. | .. |
---|
2385 | 2424 | dev_dbg(dev, "%s: found iMON device (%04x:%04x, intf%d)\n", |
---|
2386 | 2425 | __func__, vendor, product, ifnum); |
---|
2387 | 2426 | |
---|
2388 | | - /* prevent races probing devices w/multiple interfaces */ |
---|
2389 | | - mutex_lock(&driver_lock); |
---|
2390 | | - |
---|
2391 | 2427 | first_if = usb_ifnum_to_if(usbdev, 0); |
---|
2392 | 2428 | if (!first_if) { |
---|
2393 | 2429 | ret = -ENODEV; |
---|
2394 | 2430 | goto fail; |
---|
2395 | 2431 | } |
---|
2396 | | - |
---|
2397 | | - first_if_ctx = usb_get_intfdata(first_if); |
---|
2398 | 2432 | |
---|
2399 | 2433 | if (ifnum == 0) { |
---|
2400 | 2434 | ictx = imon_init_intf0(interface, id); |
---|
.. | .. |
---|
2403 | 2437 | ret = -ENODEV; |
---|
2404 | 2438 | goto fail; |
---|
2405 | 2439 | } |
---|
| 2440 | + refcount_set(&ictx->users, 1); |
---|
2406 | 2441 | |
---|
2407 | 2442 | } else { |
---|
2408 | 2443 | /* this is the secondary interface on the device */ |
---|
| 2444 | + struct imon_context *first_if_ctx = usb_get_intfdata(first_if); |
---|
2409 | 2445 | |
---|
2410 | 2446 | /* fail early if first intf failed to register */ |
---|
2411 | 2447 | if (!first_if_ctx) { |
---|
.. | .. |
---|
2419 | 2455 | ret = -ENODEV; |
---|
2420 | 2456 | goto fail; |
---|
2421 | 2457 | } |
---|
| 2458 | + refcount_inc(&ictx->users); |
---|
2422 | 2459 | |
---|
2423 | 2460 | } |
---|
2424 | 2461 | |
---|
2425 | 2462 | usb_set_intfdata(interface, ictx); |
---|
2426 | 2463 | |
---|
2427 | 2464 | if (ifnum == 0) { |
---|
2428 | | - mutex_lock(&ictx->lock); |
---|
2429 | | - |
---|
2430 | 2465 | if (product == 0xffdc && ictx->rf_device) { |
---|
2431 | 2466 | sysfs_err = sysfs_create_group(&interface->dev.kobj, |
---|
2432 | 2467 | &imon_rf_attr_group); |
---|
.. | .. |
---|
2437 | 2472 | |
---|
2438 | 2473 | if (ictx->display_supported) |
---|
2439 | 2474 | imon_init_display(ictx, interface); |
---|
2440 | | - |
---|
2441 | | - mutex_unlock(&ictx->lock); |
---|
2442 | 2475 | } |
---|
2443 | 2476 | |
---|
2444 | 2477 | dev_info(dev, "iMON device (%04x:%04x, intf%d) on usb<%d:%d> initialized\n", |
---|
2445 | 2478 | vendor, product, ifnum, |
---|
2446 | 2479 | usbdev->bus->busnum, usbdev->devnum); |
---|
2447 | 2480 | |
---|
2448 | | - mutex_unlock(&driver_lock); |
---|
2449 | 2481 | usb_put_dev(usbdev); |
---|
2450 | 2482 | |
---|
2451 | 2483 | return 0; |
---|
2452 | 2484 | |
---|
2453 | 2485 | fail: |
---|
2454 | | - mutex_unlock(&driver_lock); |
---|
2455 | 2486 | usb_put_dev(usbdev); |
---|
2456 | 2487 | dev_err(dev, "unable to register, err %d\n", ret); |
---|
2457 | 2488 | |
---|
.. | .. |
---|
2467 | 2498 | struct device *dev; |
---|
2468 | 2499 | int ifnum; |
---|
2469 | 2500 | |
---|
2470 | | - /* prevent races with multi-interface device probing and display_open */ |
---|
2471 | | - mutex_lock(&driver_lock); |
---|
2472 | | - |
---|
2473 | 2501 | ictx = usb_get_intfdata(interface); |
---|
| 2502 | + ictx->disconnected = true; |
---|
2474 | 2503 | dev = ictx->dev; |
---|
2475 | 2504 | ifnum = interface->cur_altsetting->desc.bInterfaceNumber; |
---|
2476 | 2505 | |
---|
.. | .. |
---|
2511 | 2540 | } |
---|
2512 | 2541 | } |
---|
2513 | 2542 | |
---|
2514 | | - if (!ictx->dev_present_intf0 && !ictx->dev_present_intf1) |
---|
| 2543 | + if (refcount_dec_and_test(&ictx->users)) |
---|
2515 | 2544 | free_imon_context(ictx); |
---|
2516 | | - |
---|
2517 | | - mutex_unlock(&driver_lock); |
---|
2518 | 2545 | |
---|
2519 | 2546 | dev_dbg(dev, "%s: iMON device (intf%d) disconnected\n", |
---|
2520 | 2547 | __func__, ifnum); |
---|