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 | |
| 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>
| -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 | } |
