From patchwork Sun Jul 12 17:27:20 2009 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Ralf Baechle X-Patchwork-Id: 29711 X-Patchwork-Delegate: davem@davemloft.net Return-Path: X-Original-To: patchwork-incoming@bilbo.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Received: from ozlabs.org (ozlabs.org [203.10.76.45]) (using TLSv1 with cipher DHE-RSA-AES256-SHA (256/256 bits)) (Client CN "mx.ozlabs.org", Issuer "CA Cert Signing Authority" (verified OK)) by bilbo.ozlabs.org (Postfix) with ESMTPS id 9CD9FB7063 for ; Mon, 13 Jul 2009 03:31:28 +1000 (EST) Received: by ozlabs.org (Postfix) id 4EFF7DDDFD; Mon, 13 Jul 2009 03:31:28 +1000 (EST) Delivered-To: patchwork-incoming@ozlabs.org Received: from vger.kernel.org (vger.kernel.org [209.132.176.167]) by ozlabs.org (Postfix) with ESMTP id 8561BDDDFC for ; Mon, 13 Jul 2009 03:31:27 +1000 (EST) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1754426AbZGLRa6 (ORCPT ); Sun, 12 Jul 2009 13:30:58 -0400 Received: (majordomo@vger.kernel.org) by vger.kernel.org id S1754288AbZGLRa5 (ORCPT ); Sun, 12 Jul 2009 13:30:57 -0400 Received: from h5.dl5rb.org.uk ([81.2.74.5]:38271 "EHLO h5.dl5rb.org.uk" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1754320AbZGLRaz (ORCPT ); Sun, 12 Jul 2009 13:30:55 -0400 Received: from h5.dl5rb.org.uk (localhost.localdomain [127.0.0.1]) by h5.dl5rb.org.uk (8.14.3/8.14.3) with ESMTP id n6CHRMtH008051; Sun, 12 Jul 2009 18:27:22 +0100 Received: (from ralf@localhost) by h5.dl5rb.org.uk (8.14.3/8.14.3/Submit) id n6CHRKcG008047; Sun, 12 Jul 2009 18:27:20 +0100 Date: Sun, 12 Jul 2009 18:27:20 +0100 From: Ralf Baechle To: "David S. Miller" , netdev@vger.kernel.org Cc: Guido Trentalancia , Paul Mackerras , Alan Cox , Andreas Koensgen , linux-hams@vger.kernel.org, Hans Albas PE1AYX Subject: NET: Fix locking issues in PPP, 6pack, mkiss and strip line disciplines. Message-ID: <20090712172720.GA7313@linux-mips.org> MIME-Version: 1.0 Content-Disposition: inline User-Agent: Mutt/1.5.18 (2008-05-17) Sender: netdev-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: netdev@vger.kernel.org Guido Trentalancia reports: I am trying to use the kiss driver in the Linux kernel that is being shipped with Fedora 10 but unfortunately I get the following oops: mkiss: AX.25 Multikiss, Hans Albas PE1AYX mkiss: ax0: crc mode is auto. ADDRCONF(NETDEV_CHANGE): ax0: link becomes ready ------------[ cut here ]------------ WARNING: at kernel/softirq.c:77 __local_bh_disable+0x2f/0x83() (Not tainted) [...] unloaded: microcode] Pid: 0, comm: swapper Not tainted 2.6.27.25-170.2.72.fc10.i686 #1 [] warn_on_slowpath+0x65/0x8b [] ? _spin_unlock_irqrestore+0x22/0x38 [] ? __enqueue_entity+0xe3/0xeb [] ? enqueue_entity+0x203/0x20b [] ? enqueue_task_fair+0x3b/0x3f [] ? resched_task+0x3a/0x6e [] ? _spin_unlock_irqrestore+0x22/0x38 [] ? _spin_lock_bh+0xb/0x16 [] __local_bh_disable+0x2f/0x83 [] local_bh_disable+0xb/0xd [] _spin_lock_bh+0xb/0x16 [] mkiss_receive_buf+0x2fb/0x3a6 [mkiss] [] flush_to_ldisc+0xf7/0x198 [] tty_flip_buffer_push+0x41/0x51 [] ftdi_process_read+0x375/0x4ad [ftdi_sio] [] ftdi_read_bulk_callback+0x130/0x138 [ftdi_sio] [] usb_hcd_giveback_urb+0x63/0x93 [] uhci_giveback_urb+0xe5/0x15f [] uhci_scan_schedule+0x52e/0x767 [] ? psmouse_handle_byte+0xc/0xe5 [] ? acpi_ev_gpe_detect+0xd6/0xe1 [] uhci_irq+0x110/0x125 [] usb_hcd_irq+0x40/0xa3 [] handle_IRQ_event+0x2f/0x64 [] handle_level_irq+0x74/0xbe [] ? handle_level_irq+0x0/0xbe [] do_IRQ+0xc7/0xfe [] common_interrupt+0x28/0x30 [] ? acpi_idle_enter_simple+0x162/0x19d [] cpuidle_idle_call+0x60/0x92 [] cpu_idle+0x101/0x134 [] rest_init+0x4e/0x50 ======================= ---[ end trace b7cc8076093467ad ]--- ------------[ cut here ]------------ WARNING: at kernel/softirq.c:136 _local_bh_enable_ip+0x3d/0xc4() [...] Pid: 0, comm: swapper Tainted: G W 2.6.27.25-170.2.72.fc10.i686 [] warn_on_slowpath+0x65/0x8b [] ? _spin_unlock_irqrestore+0x22/0x38 [] ? __enqueue_entity+0xe3/0xeb [] ? enqueue_entity+0x203/0x20b [] ? enqueue_task_fair+0x3b/0x3f [] ? resched_task+0x3a/0x6e [] ? _spin_unlock_irqrestore+0x22/0x38 [] ? _spin_lock_bh+0xb/0x16 [] ? mkiss_receive_buf+0x33d/0x3a6 [mkiss] [] _local_bh_enable_ip+0x3d/0xc4 [] local_bh_enable_ip+0x8/0xa [] _spin_unlock_bh+0x11/0x13 [] mkiss_receive_buf+0x33d/0x3a6 [mkiss] [] flush_to_ldisc+0xf7/0x198 [] tty_flip_buffer_push+0x41/0x51 [] ftdi_process_read+0x375/0x4ad [ftdi_sio] [] ftdi_read_bulk_callback+0x130/0x138 [ftdi_sio] [] usb_hcd_giveback_urb+0x63/0x93 [] uhci_giveback_urb+0xe5/0x15f [] uhci_scan_schedule+0x52e/0x767 [] ? psmouse_handle_byte+0xc/0xe5 [] ? acpi_ev_gpe_detect+0xd6/0xe1 [] uhci_irq+0x110/0x125 [] usb_hcd_irq+0x40/0xa3 [] handle_IRQ_event+0x2f/0x64 [] handle_level_irq+0x74/0xbe [] ? handle_level_irq+0x0/0xbe [] do_IRQ+0xc7/0xfe [] common_interrupt+0x28/0x30 [] ? acpi_idle_enter_simple+0x162/0x19d [] cpuidle_idle_call+0x60/0x92 [] cpu_idle+0x101/0x134 [] rest_init+0x4e/0x50 ======================= ---[ end trace b7cc8076093467ad ]--- mkiss: ax0: Trying crc-smack mkiss: ax0: Trying crc-flexnet The issue was, that the locking code in mkiss was assuming it was only ever being called in process or bh context but some tty drivers such as the USB serial driver may call it in interrupt context. Fixed by converting the involved locking code to use irq-safe locks. Review of similar networking line disciplines shows that 6pack, both sync and async PPP and STRIP have similar issues. The PPP ones are the most interesting ones as half of the issue was sorted out as far back as 2004 in commit http://git.kernel.org/?p=linux/kernel/git/tglx/history.git;a=commitdiff;h=2996d8deaeddd01820691a872550dc0cfba0c37d Signed-off-by: Ralf Baechle Reported-by: Guido Trentalancia Cc: Paul Mackerras Cc: Alan Cox Cc: Andreas Koensgen Cc: Hans Albas PE1AYX --- drivers/net/hamradio/6pack.c | 10 ++++++---- drivers/net/hamradio/mkiss.c | 15 +++++++++------ drivers/net/ppp_async.c | 11 +++++++---- drivers/net/ppp_synctty.c | 11 +++++++---- drivers/net/wireless/strip.c | 39 ++++++++++++++++++++++++--------------- 5 files changed, 53 insertions(+), 33 deletions(-) -- To unsubscribe from this list: send the line "unsubscribe netdev" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html diff --git a/drivers/net/hamradio/6pack.c b/drivers/net/hamradio/6pack.c index 1551600..913a564 100644 --- a/drivers/net/hamradio/6pack.c +++ b/drivers/net/hamradio/6pack.c @@ -398,13 +398,14 @@ static DEFINE_RWLOCK(disc_data_lock); static struct sixpack *sp_get(struct tty_struct *tty) { + unsigned long flags; struct sixpack *sp; - read_lock(&disc_data_lock); + read_lock_irqsave(&disc_data_lock, flags); sp = tty->disc_data; if (sp) atomic_inc(&sp->refcnt); - read_unlock(&disc_data_lock); + read_unlock_irqrestore(&disc_data_lock, flags); return sp; } @@ -688,12 +689,13 @@ out: */ static void sixpack_close(struct tty_struct *tty) { + unsigned long flags; struct sixpack *sp; - write_lock(&disc_data_lock); + write_lock_irqsave(&disc_data_lock, flags); sp = tty->disc_data; tty->disc_data = NULL; - write_unlock(&disc_data_lock); + write_unlock_irqrestore(&disc_data_lock, flags); if (!sp) return; diff --git a/drivers/net/hamradio/mkiss.c b/drivers/net/hamradio/mkiss.c index fda2fc8..671257a 100644 --- a/drivers/net/hamradio/mkiss.c +++ b/drivers/net/hamradio/mkiss.c @@ -367,6 +367,7 @@ static void ax_changedmtu(struct mkiss *ax) { struct net_device *dev = ax->dev; unsigned char *xbuff, *rbuff, *oxbuff, *orbuff; + unsigned long flags; int len; len = dev->mtu * 2; @@ -392,7 +393,7 @@ static void ax_changedmtu(struct mkiss *ax) return; } - spin_lock_bh(&ax->buflock); + spin_lock_irqsave(&ax->buflock, flags); oxbuff = ax->xbuff; ax->xbuff = xbuff; @@ -423,7 +424,7 @@ static void ax_changedmtu(struct mkiss *ax) ax->mtu = dev->mtu + 73; ax->buffsize = len; - spin_unlock_bh(&ax->buflock); + spin_unlock_irqrestore(&ax->buflock, flags); kfree(oxbuff); kfree(orbuff); @@ -704,13 +705,14 @@ static DEFINE_RWLOCK(disc_data_lock); static struct mkiss *mkiss_get(struct tty_struct *tty) { + unsigned long flags; struct mkiss *ax; - read_lock(&disc_data_lock); + read_lock_irqsave(&disc_data_lock, flags); ax = tty->disc_data; if (ax) atomic_inc(&ax->refcnt); - read_unlock(&disc_data_lock); + read_unlock_irqrestore(&disc_data_lock, flags); return ax; } @@ -809,12 +811,13 @@ out: static void mkiss_close(struct tty_struct *tty) { + unsigned long flags; struct mkiss *ax; - write_lock(&disc_data_lock); + write_lock_irqsave(&disc_data_lock, flags); ax = tty->disc_data; tty->disc_data = NULL; - write_unlock(&disc_data_lock); + write_unlock_irqrestore(&disc_data_lock, flags); if (!ax) return; diff --git a/drivers/net/ppp_async.c b/drivers/net/ppp_async.c index 17c116b..1fd319b 100644 --- a/drivers/net/ppp_async.c +++ b/drivers/net/ppp_async.c @@ -132,13 +132,15 @@ static DEFINE_RWLOCK(disc_data_lock); static struct asyncppp *ap_get(struct tty_struct *tty) { + unsigned long flags; struct asyncppp *ap; - read_lock(&disc_data_lock); + read_lock_irqsave(&disc_data_lock, flags); ap = tty->disc_data; if (ap != NULL) atomic_inc(&ap->refcnt); - read_unlock(&disc_data_lock); + read_unlock_irqrestore(&disc_data_lock, flags); + return ap; } @@ -215,12 +217,13 @@ ppp_asynctty_open(struct tty_struct *tty) static void ppp_asynctty_close(struct tty_struct *tty) { + unsigned long flags; struct asyncppp *ap; - write_lock_irq(&disc_data_lock); + write_lock_irqsave(&disc_data_lock, flags); ap = tty->disc_data; tty->disc_data = NULL; - write_unlock_irq(&disc_data_lock); + write_unlock_irqrestore(&disc_data_lock, flags); if (!ap) return; diff --git a/drivers/net/ppp_synctty.c b/drivers/net/ppp_synctty.c index aa3d39f..1b3f75f 100644 --- a/drivers/net/ppp_synctty.c +++ b/drivers/net/ppp_synctty.c @@ -182,13 +182,15 @@ static DEFINE_RWLOCK(disc_data_lock); static struct syncppp *sp_get(struct tty_struct *tty) { + unsigned long flags; struct syncppp *ap; - read_lock(&disc_data_lock); + read_lock_irqsave(&disc_data_lock, flags); ap = tty->disc_data; if (ap != NULL) atomic_inc(&ap->refcnt); - read_unlock(&disc_data_lock); + read_unlock_irqrestore(&disc_data_lock, flags); + return ap; } @@ -262,12 +264,13 @@ ppp_sync_open(struct tty_struct *tty) static void ppp_sync_close(struct tty_struct *tty) { + unsigned long flags; struct syncppp *ap; - write_lock_irq(&disc_data_lock); + write_lock_irqsave(&disc_data_lock, flags); ap = tty->disc_data; tty->disc_data = NULL; - write_unlock_irq(&disc_data_lock); + write_unlock_irqrestore(&disc_data_lock, flags); if (!ap) return; diff --git a/drivers/net/wireless/strip.c b/drivers/net/wireless/strip.c index 38366a5..3d39f65 100644 --- a/drivers/net/wireless/strip.c +++ b/drivers/net/wireless/strip.c @@ -856,6 +856,7 @@ static int strip_change_mtu(struct net_device *dev, int new_mtu) unsigned char *orbuff = strip_info->rx_buff; unsigned char *osbuff = strip_info->sx_buff; unsigned char *otbuff = strip_info->tx_buff; + unsigned long flags; if (new_mtu > MAX_SEND_MTU) { printk(KERN_ERR @@ -864,11 +865,11 @@ static int strip_change_mtu(struct net_device *dev, int new_mtu) return -EINVAL; } - spin_lock_bh(&strip_lock); + spin_lock_irqsave(&strip_lock, flags); if (!allocate_buffers(strip_info, new_mtu)) { printk(KERN_ERR "%s: unable to grow strip buffers, MTU change cancelled.\n", strip_info->dev->name); - spin_unlock_bh(&strip_lock); + spin_unlock_irqrestore(&strip_lock, flags); return -ENOMEM; } @@ -892,7 +893,7 @@ static int strip_change_mtu(struct net_device *dev, int new_mtu) } } strip_info->tx_head = strip_info->tx_buff; - spin_unlock_bh(&strip_lock); + spin_unlock_irqrestore(&strip_lock, flags); printk(KERN_NOTICE "%s: strip MTU changed fom %d to %d.\n", strip_info->dev->name, old_mtu, strip_info->mtu); @@ -983,10 +984,13 @@ static void strip_seq_neighbours(struct seq_file *seq, const MetricomNodeTable * table, const char *title) { - /* We wrap this in a do/while loop, so if the table changes */ - /* while we're reading it, we just go around and try again. */ + unsigned long flags; struct timeval t; + /* + * We wrap this in a do/while loop, so if the table changes + * while we're reading it, we just go around and try again. + */ do { int i; t = table->timestamp; @@ -995,9 +999,9 @@ static void strip_seq_neighbours(struct seq_file *seq, for (i = 0; i < table->num_nodes; i++) { MetricomNode node; - spin_lock_bh(&strip_lock); + spin_lock_irqsave(&strip_lock, flags); node = table->node[i]; - spin_unlock_bh(&strip_lock); + spin_unlock_irqrestore(&strip_lock, flags); seq_printf(seq, " %s\n", node.c); } } while (table->timestamp.tv_sec != t.tv_sec @@ -1536,6 +1540,7 @@ static void strip_send(struct strip *strip_info, struct sk_buff *skb) static int strip_xmit(struct sk_buff *skb, struct net_device *dev) { struct strip *strip_info = netdev_priv(dev); + unsigned long flags; if (!netif_running(dev)) { printk(KERN_ERR "%s: xmit call when iface is down\n", @@ -1574,11 +1579,11 @@ static int strip_xmit(struct sk_buff *skb, struct net_device *dev) strip_info->dev->name, sx_pps_count / 8); } - spin_lock_bh(&strip_lock); + spin_lock_irqsave(&strip_lock, flags); strip_send(strip_info, skb); - spin_unlock_bh(&strip_lock); + spin_unlock_irqrestore(&strip_lock, flags); if (skb) dev_kfree_skb(skb); @@ -2263,12 +2268,13 @@ static void strip_receive_buf(struct tty_struct *tty, const unsigned char *cp, { struct strip *strip_info = tty->disc_data; const unsigned char *end = cp + count; + unsigned long flags; if (!strip_info || strip_info->magic != STRIP_MAGIC || !netif_running(strip_info->dev)) return; - spin_lock_bh(&strip_lock); + spin_lock_irqsave(&strip_lock, flags); #if 0 { struct timeval tv; @@ -2335,7 +2341,7 @@ static void strip_receive_buf(struct tty_struct *tty, const unsigned char *cp, } cp++; } - spin_unlock_bh(&strip_lock); + spin_unlock_irqrestore(&strip_lock, flags); } @@ -2523,9 +2529,11 @@ static void strip_dev_setup(struct net_device *dev) static void strip_free(struct strip *strip_info) { - spin_lock_bh(&strip_lock); + unsigned long flags; + + spin_lock_irqsave(&strip_lock, flags); list_del_rcu(&strip_info->list); - spin_unlock_bh(&strip_lock); + spin_unlock_irqrestore(&strip_lock, flags); strip_info->magic = 0; @@ -2539,6 +2547,7 @@ static void strip_free(struct strip *strip_info) static struct strip *strip_alloc(void) { struct list_head *n; + unsigned long flags; struct net_device *dev; struct strip *strip_info; @@ -2562,7 +2571,7 @@ static struct strip *strip_alloc(void) strip_info->idle_timer.function = strip_IdleTask; - spin_lock_bh(&strip_lock); + spin_lock_irqsave(&strip_lock, flags); rescan: /* * Search the list to find where to put our new entry @@ -2581,7 +2590,7 @@ static struct strip *strip_alloc(void) sprintf(dev->name, "st%ld", dev->base_addr); list_add_tail_rcu(&strip_info->list, &strip_list); - spin_unlock_bh(&strip_lock); + spin_unlock_irqrestore(&strip_lock, flags); return strip_info; }