diff options
author | Tyler Hall <tylerwhall@gmail.com> | 2014-06-15 22:23:17 -0400 |
---|---|---|
committer | David S. Miller <davem@davemloft.net> | 2014-06-17 00:29:13 -0400 |
commit | a8e83b17536aad603fbeae4c460f2da0ee9fe6ed (patch) | |
tree | e458410dbe200d0bc843771fa94084c45b08ab97 /drivers/net/can | |
parent | 661f7fda21b15ec52f57fcd397c03370acc28688 (diff) |
slcan: Port write_wakeup deadlock fix from slip
The commit "slip: Fix deadlock in write_wakeup" fixes a deadlock caused
by a change made in both slcan and slip. This is a direct port of that
fix.
Signed-off-by: Tyler Hall <tylerwhall@gmail.com>
Cc: Oliver Hartkopp <socketcan@hartkopp.net>
Cc: Andre Naujoks <nautsch2@gmail.com>
Cc: David S. Miller <davem@davemloft.net>
Cc: linux-kernel@vger.kernel.org
Signed-off-by: David S. Miller <davem@davemloft.net>
Diffstat (limited to 'drivers/net/can')
-rw-r--r-- | drivers/net/can/slcan.c | 37 |
1 files changed, 27 insertions, 10 deletions
diff --git a/drivers/net/can/slcan.c b/drivers/net/can/slcan.c index dcf9196f6316..ea4d4f1a6411 100644 --- a/drivers/net/can/slcan.c +++ b/drivers/net/can/slcan.c | |||
@@ -52,6 +52,7 @@ | |||
52 | #include <linux/delay.h> | 52 | #include <linux/delay.h> |
53 | #include <linux/init.h> | 53 | #include <linux/init.h> |
54 | #include <linux/kernel.h> | 54 | #include <linux/kernel.h> |
55 | #include <linux/workqueue.h> | ||
55 | #include <linux/can.h> | 56 | #include <linux/can.h> |
56 | #include <linux/can/skb.h> | 57 | #include <linux/can/skb.h> |
57 | 58 | ||
@@ -85,6 +86,7 @@ struct slcan { | |||
85 | struct tty_struct *tty; /* ptr to TTY structure */ | 86 | struct tty_struct *tty; /* ptr to TTY structure */ |
86 | struct net_device *dev; /* easy for intr handling */ | 87 | struct net_device *dev; /* easy for intr handling */ |
87 | spinlock_t lock; | 88 | spinlock_t lock; |
89 | struct work_struct tx_work; /* Flushes transmit buffer */ | ||
88 | 90 | ||
89 | /* These are pointers to the malloc()ed frame buffers. */ | 91 | /* These are pointers to the malloc()ed frame buffers. */ |
90 | unsigned char rbuff[SLC_MTU]; /* receiver buffer */ | 92 | unsigned char rbuff[SLC_MTU]; /* receiver buffer */ |
@@ -309,36 +311,46 @@ static void slc_encaps(struct slcan *sl, struct can_frame *cf) | |||
309 | sl->dev->stats.tx_bytes += cf->can_dlc; | 311 | sl->dev->stats.tx_bytes += cf->can_dlc; |
310 | } | 312 | } |
311 | 313 | ||
312 | /* | 314 | /* Write out any remaining transmit buffer. Scheduled when tty is writable */ |
313 | * Called by the driver when there's room for more data. If we have | 315 | static void slcan_transmit(struct work_struct *work) |
314 | * more packets to send, we send them here. | ||
315 | */ | ||
316 | static void slcan_write_wakeup(struct tty_struct *tty) | ||
317 | { | 316 | { |
317 | struct slcan *sl = container_of(work, struct slcan, tx_work); | ||
318 | int actual; | 318 | int actual; |
319 | struct slcan *sl = (struct slcan *) tty->disc_data; | ||
320 | 319 | ||
320 | spin_lock_bh(&sl->lock); | ||
321 | /* First make sure we're connected. */ | 321 | /* First make sure we're connected. */ |
322 | if (!sl || sl->magic != SLCAN_MAGIC || !netif_running(sl->dev)) | 322 | if (!sl->tty || sl->magic != SLCAN_MAGIC || !netif_running(sl->dev)) { |
323 | spin_unlock_bh(&sl->lock); | ||
323 | return; | 324 | return; |
325 | } | ||
324 | 326 | ||
325 | spin_lock_bh(&sl->lock); | ||
326 | if (sl->xleft <= 0) { | 327 | if (sl->xleft <= 0) { |
327 | /* Now serial buffer is almost free & we can start | 328 | /* Now serial buffer is almost free & we can start |
328 | * transmission of another packet */ | 329 | * transmission of another packet */ |
329 | sl->dev->stats.tx_packets++; | 330 | sl->dev->stats.tx_packets++; |
330 | clear_bit(TTY_DO_WRITE_WAKEUP, &tty->flags); | 331 | clear_bit(TTY_DO_WRITE_WAKEUP, &sl->tty->flags); |
331 | spin_unlock_bh(&sl->lock); | 332 | spin_unlock_bh(&sl->lock); |
332 | netif_wake_queue(sl->dev); | 333 | netif_wake_queue(sl->dev); |
333 | return; | 334 | return; |
334 | } | 335 | } |
335 | 336 | ||
336 | actual = tty->ops->write(tty, sl->xhead, sl->xleft); | 337 | actual = sl->tty->ops->write(sl->tty, sl->xhead, sl->xleft); |
337 | sl->xleft -= actual; | 338 | sl->xleft -= actual; |
338 | sl->xhead += actual; | 339 | sl->xhead += actual; |
339 | spin_unlock_bh(&sl->lock); | 340 | spin_unlock_bh(&sl->lock); |
340 | } | 341 | } |
341 | 342 | ||
343 | /* | ||
344 | * Called by the driver when there's room for more data. | ||
345 | * Schedule the transmit. | ||
346 | */ | ||
347 | static void slcan_write_wakeup(struct tty_struct *tty) | ||
348 | { | ||
349 | struct slcan *sl = tty->disc_data; | ||
350 | |||
351 | schedule_work(&sl->tx_work); | ||
352 | } | ||
353 | |||
342 | /* Send a can_frame to a TTY queue. */ | 354 | /* Send a can_frame to a TTY queue. */ |
343 | static netdev_tx_t slc_xmit(struct sk_buff *skb, struct net_device *dev) | 355 | static netdev_tx_t slc_xmit(struct sk_buff *skb, struct net_device *dev) |
344 | { | 356 | { |
@@ -528,6 +540,7 @@ static struct slcan *slc_alloc(dev_t line) | |||
528 | sl->magic = SLCAN_MAGIC; | 540 | sl->magic = SLCAN_MAGIC; |
529 | sl->dev = dev; | 541 | sl->dev = dev; |
530 | spin_lock_init(&sl->lock); | 542 | spin_lock_init(&sl->lock); |
543 | INIT_WORK(&sl->tx_work, slcan_transmit); | ||
531 | slcan_devs[i] = dev; | 544 | slcan_devs[i] = dev; |
532 | 545 | ||
533 | return sl; | 546 | return sl; |
@@ -626,8 +639,12 @@ static void slcan_close(struct tty_struct *tty) | |||
626 | if (!sl || sl->magic != SLCAN_MAGIC || sl->tty != tty) | 639 | if (!sl || sl->magic != SLCAN_MAGIC || sl->tty != tty) |
627 | return; | 640 | return; |
628 | 641 | ||
642 | spin_lock_bh(&sl->lock); | ||
629 | tty->disc_data = NULL; | 643 | tty->disc_data = NULL; |
630 | sl->tty = NULL; | 644 | sl->tty = NULL; |
645 | spin_unlock_bh(&sl->lock); | ||
646 | |||
647 | flush_work(&sl->tx_work); | ||
631 | 648 | ||
632 | /* Flush network side */ | 649 | /* Flush network side */ |
633 | unregister_netdev(sl->dev); | 650 | unregister_netdev(sl->dev); |