diff options
author | Konstantin Khlebnikov <khlebnikov@openvz.org> | 2013-07-08 03:23:04 -0400 |
---|---|---|
committer | David S. Miller <davem@davemloft.net> | 2013-07-09 15:55:48 -0400 |
commit | 56e0ef527b184b3de2d7f88c6190812b2b2ac6bf (patch) | |
tree | 3d9bc5ce1b6086554982703f99bc6a0bb9ce05e1 /drivers/net/caif | |
parent | e057590b05bc120732967a04d63c15f2f82cdd2b (diff) |
drivers/net: caif: fix wrong rtnl_is_locked() usage
rtnl_is_locked() doesn't check who holds this lock, it just tells that it's
locked right now. if caif::ldisc_close really can be called under rtrnl_lock
then it should release net device in other context because there is no way
to grab rtnl_lock without deadlock.
This patch adds work which releases these devices. Also this patch fixes calling
dev_close/unregister_netdevice without rtnl_lock from caif_ser_exit().
Signed-off-by: Konstantin Khlebnikov <khlebnikov@openvz.org>
Cc: Dmitry Tarnyagin <dmitry.tarnyagin@lockless.no>
Signed-off-by: David S. Miller <davem@davemloft.net>
Diffstat (limited to 'drivers/net/caif')
-rw-r--r-- | drivers/net/caif/caif_serial.c | 61 |
1 files changed, 39 insertions, 22 deletions
diff --git a/drivers/net/caif/caif_serial.c b/drivers/net/caif/caif_serial.c index 77be3cb0b5fe..34dea95d58db 100644 --- a/drivers/net/caif/caif_serial.c +++ b/drivers/net/caif/caif_serial.c | |||
@@ -35,8 +35,9 @@ MODULE_ALIAS_LDISC(N_CAIF); | |||
35 | #define OFF 0 | 35 | #define OFF 0 |
36 | #define CAIF_MAX_MTU 4096 | 36 | #define CAIF_MAX_MTU 4096 |
37 | 37 | ||
38 | /*This list is protected by the rtnl lock. */ | 38 | static DEFINE_SPINLOCK(ser_lock); |
39 | static LIST_HEAD(ser_list); | 39 | static LIST_HEAD(ser_list); |
40 | static LIST_HEAD(ser_release_list); | ||
40 | 41 | ||
41 | static bool ser_loop; | 42 | static bool ser_loop; |
42 | module_param(ser_loop, bool, S_IRUGO); | 43 | module_param(ser_loop, bool, S_IRUGO); |
@@ -308,6 +309,28 @@ static void ldisc_tx_wakeup(struct tty_struct *tty) | |||
308 | } | 309 | } |
309 | 310 | ||
310 | 311 | ||
312 | static void ser_release(struct work_struct *work) | ||
313 | { | ||
314 | struct list_head list; | ||
315 | struct ser_device *ser, *tmp; | ||
316 | |||
317 | spin_lock(&ser_lock); | ||
318 | list_replace_init(&ser_release_list, &list); | ||
319 | spin_unlock(&ser_lock); | ||
320 | |||
321 | if (!list_empty(&list)) { | ||
322 | rtnl_lock(); | ||
323 | list_for_each_entry_safe(ser, tmp, &list, node) { | ||
324 | dev_close(ser->dev); | ||
325 | unregister_netdevice(ser->dev); | ||
326 | debugfs_deinit(ser); | ||
327 | } | ||
328 | rtnl_unlock(); | ||
329 | } | ||
330 | } | ||
331 | |||
332 | static DECLARE_WORK(ser_release_work, ser_release); | ||
333 | |||
311 | static int ldisc_open(struct tty_struct *tty) | 334 | static int ldisc_open(struct tty_struct *tty) |
312 | { | 335 | { |
313 | struct ser_device *ser; | 336 | struct ser_device *ser; |
@@ -321,6 +344,9 @@ static int ldisc_open(struct tty_struct *tty) | |||
321 | if (!capable(CAP_SYS_ADMIN) && !capable(CAP_SYS_TTY_CONFIG)) | 344 | if (!capable(CAP_SYS_ADMIN) && !capable(CAP_SYS_TTY_CONFIG)) |
322 | return -EPERM; | 345 | return -EPERM; |
323 | 346 | ||
347 | /* release devices to avoid name collision */ | ||
348 | ser_release(NULL); | ||
349 | |||
324 | sprintf(name, "cf%s", tty->name); | 350 | sprintf(name, "cf%s", tty->name); |
325 | dev = alloc_netdev(sizeof(*ser), name, caifdev_setup); | 351 | dev = alloc_netdev(sizeof(*ser), name, caifdev_setup); |
326 | if (!dev) | 352 | if (!dev) |
@@ -341,7 +367,9 @@ static int ldisc_open(struct tty_struct *tty) | |||
341 | return -ENODEV; | 367 | return -ENODEV; |
342 | } | 368 | } |
343 | 369 | ||
370 | spin_lock(&ser_lock); | ||
344 | list_add(&ser->node, &ser_list); | 371 | list_add(&ser->node, &ser_list); |
372 | spin_unlock(&ser_lock); | ||
345 | rtnl_unlock(); | 373 | rtnl_unlock(); |
346 | netif_stop_queue(dev); | 374 | netif_stop_queue(dev); |
347 | update_tty_status(ser); | 375 | update_tty_status(ser); |
@@ -351,19 +379,13 @@ static int ldisc_open(struct tty_struct *tty) | |||
351 | static void ldisc_close(struct tty_struct *tty) | 379 | static void ldisc_close(struct tty_struct *tty) |
352 | { | 380 | { |
353 | struct ser_device *ser = tty->disc_data; | 381 | struct ser_device *ser = tty->disc_data; |
354 | /* Remove may be called inside or outside of rtnl_lock */ | ||
355 | int islocked = rtnl_is_locked(); | ||
356 | 382 | ||
357 | if (!islocked) | ||
358 | rtnl_lock(); | ||
359 | /* device is freed automagically by net-sysfs */ | ||
360 | dev_close(ser->dev); | ||
361 | unregister_netdevice(ser->dev); | ||
362 | list_del(&ser->node); | ||
363 | debugfs_deinit(ser); | ||
364 | tty_kref_put(ser->tty); | 383 | tty_kref_put(ser->tty); |
365 | if (!islocked) | 384 | |
366 | rtnl_unlock(); | 385 | spin_lock(&ser_lock); |
386 | list_move(&ser->node, &ser_release_list); | ||
387 | spin_unlock(&ser_lock); | ||
388 | schedule_work(&ser_release_work); | ||
367 | } | 389 | } |
368 | 390 | ||
369 | /* The line discipline structure. */ | 391 | /* The line discipline structure. */ |
@@ -438,16 +460,11 @@ static int __init caif_ser_init(void) | |||
438 | 460 | ||
439 | static void __exit caif_ser_exit(void) | 461 | static void __exit caif_ser_exit(void) |
440 | { | 462 | { |
441 | struct ser_device *ser = NULL; | 463 | spin_lock(&ser_lock); |
442 | struct list_head *node; | 464 | list_splice(&ser_list, &ser_release_list); |
443 | struct list_head *_tmp; | 465 | spin_unlock(&ser_lock); |
444 | 466 | ser_release(NULL); | |
445 | list_for_each_safe(node, _tmp, &ser_list) { | 467 | cancel_work_sync(&ser_release_work); |
446 | ser = list_entry(node, struct ser_device, node); | ||
447 | dev_close(ser->dev); | ||
448 | unregister_netdevice(ser->dev); | ||
449 | list_del(node); | ||
450 | } | ||
451 | tty_unregister_ldisc(N_CAIF); | 468 | tty_unregister_ldisc(N_CAIF); |
452 | debugfs_remove_recursive(debugfsdir); | 469 | debugfs_remove_recursive(debugfsdir); |
453 | } | 470 | } |