diff mbox series

[for-next,1/3] IB/usnic: fix deadlock

Message ID 1544566543-3395-2-git-send-email-pkaustub@cisco.com (mailing list archive)
State Changes Requested
Delegated to: Jason Gunthorpe
Headers show
Series IB/usnic: usnic bug fixes | expand

Commit Message

Parvi Kaustubhi (pkaustub) Dec. 11, 2018, 10:15 p.m. UTC
From: Govindarajulu Varadarajan <gvaradar@cisco.com>

There is a dead lock in usnic ib_register() and netdev_notify() path.

usnic_ib_discover_pf()
| mutex_lock(&usnic_ib_ibdev_list_lock);
 | usnic_ib_device_add();
  | ib_register_device()
   | usnic_ib_query_port()
    | mutex_lock(&us_ibdev->usdev_lock);
     | ib_get_eth_speed()
      | rtnl_lock()

Order of locking: &usnic_ib_ibdev_list_lock -> usdev_lock -> rtnl_lock

rtnl_lock()
| usnic_ib_netdevice_event()
 | mutex_lock(&usnic_ib_ibdev_list_lock);

Order of locking: rtnl_lock -> &usnic_ib_ibdev_list_lock

The solution is to not handle usnic_ib_netdevice_event() with the rtnl
lock held.
This commit creates a single threaded workqueue to defer the handling of
netdev/inet events.

Signed-off-by: Govindarajulu Varadarajan <gvaradar@cisco.com>
Signed-off-by: Parvi Kaustubhi <pkaustub@cisco.com>
---
 drivers/infiniband/hw/usnic/usnic_ib.h      |  6 +++
 drivers/infiniband/hw/usnic/usnic_ib_main.c | 60 ++++++++++++++++++++++++-----
 2 files changed, 56 insertions(+), 10 deletions(-)

Comments

Jason Gunthorpe Dec. 11, 2018, 11:06 p.m. UTC | #1
On Tue, Dec 11, 2018 at 02:15:41PM -0800, Parvi Kaustubhi wrote:
> From: Govindarajulu Varadarajan <gvaradar@cisco.com>
> 
> There is a dead lock in usnic ib_register() and netdev_notify() path.
> 
> usnic_ib_discover_pf()
> | mutex_lock(&usnic_ib_ibdev_list_lock);
>  | usnic_ib_device_add();
>   | ib_register_device()
>    | usnic_ib_query_port()
>     | mutex_lock(&us_ibdev->usdev_lock);
>      | ib_get_eth_speed()
>       | rtnl_lock()
> 
> Order of locking: &usnic_ib_ibdev_list_lock -> usdev_lock -> rtnl_lock
> 
> rtnl_lock()
> | usnic_ib_netdevice_event()
>  | mutex_lock(&usnic_ib_ibdev_list_lock);
> 
> Order of locking: rtnl_lock -> &usnic_ib_ibdev_list_lock
> 
> The solution is to not handle usnic_ib_netdevice_event() with the rtnl
> lock held.
> This commit creates a single threaded workqueue to defer the handling of
> netdev/inet events.
> 
> Signed-off-by: Govindarajulu Varadarajan <gvaradar@cisco.com>
> Signed-off-by: Parvi Kaustubhi <pkaustub@cisco.com>
> ---
>  drivers/infiniband/hw/usnic/usnic_ib.h      |  6 +++
>  drivers/infiniband/hw/usnic/usnic_ib_main.c | 60 ++++++++++++++++++++++++-----
>  2 files changed, 56 insertions(+), 10 deletions(-)

I'm really getting sick of seeing this same code pattern in the
ethernet connecting drivers..  I think this is the 3rd time now I've
seen a fix for this same rtnl locking bug in a driver.

Does anyone want to make a proper driver core based struct
device_driver for this discovery model???

Also, this surely has the same bugs as rxe and bnxt, see those recent
threads too.

Jason
Leon Romanovsky Dec. 12, 2018, 5:32 a.m. UTC | #2
On Tue, Dec 11, 2018 at 11:06:49PM +0000, Jason Gunthorpe wrote:
> On Tue, Dec 11, 2018 at 02:15:41PM -0800, Parvi Kaustubhi wrote:
> > From: Govindarajulu Varadarajan <gvaradar@cisco.com>
> >
> > There is a dead lock in usnic ib_register() and netdev_notify() path.
> >
> > usnic_ib_discover_pf()
> > | mutex_lock(&usnic_ib_ibdev_list_lock);
> >  | usnic_ib_device_add();
> >   | ib_register_device()
> >    | usnic_ib_query_port()
> >     | mutex_lock(&us_ibdev->usdev_lock);
> >      | ib_get_eth_speed()
> >       | rtnl_lock()
> >
> > Order of locking: &usnic_ib_ibdev_list_lock -> usdev_lock -> rtnl_lock
> >
> > rtnl_lock()
> > | usnic_ib_netdevice_event()
> >  | mutex_lock(&usnic_ib_ibdev_list_lock);
> >
> > Order of locking: rtnl_lock -> &usnic_ib_ibdev_list_lock
> >
> > The solution is to not handle usnic_ib_netdevice_event() with the rtnl
> > lock held.
> > This commit creates a single threaded workqueue to defer the handling of
> > netdev/inet events.
> >
> > Signed-off-by: Govindarajulu Varadarajan <gvaradar@cisco.com>
> > Signed-off-by: Parvi Kaustubhi <pkaustub@cisco.com>
> > ---
> >  drivers/infiniband/hw/usnic/usnic_ib.h      |  6 +++
> >  drivers/infiniband/hw/usnic/usnic_ib_main.c | 60 ++++++++++++++++++++++++-----
> >  2 files changed, 56 insertions(+), 10 deletions(-)
>
> I'm really getting sick of seeing this same code pattern in the
> ethernet connecting drivers..  I think this is the 3rd time now I've
> seen a fix for this same rtnl locking bug in a driver.

I had the same feeling while I read the patch.

Thanks
Govindarajulu Varadarajan (gvaradar) Dec. 14, 2018, 2:53 a.m. UTC | #3
On Tue, 2018-12-11 at 23:06 +0000, Jason Gunthorpe wrote:
> On Tue, Dec 11, 2018 at 02:15:41PM -0800, Parvi Kaustubhi wrote:
> > From: Govindarajulu Varadarajan <gvaradar@cisco.com>
> > 
> > There is a dead lock in usnic ib_register() and netdev_notify() path.
> > 
> > usnic_ib_discover_pf()
> > > mutex_lock(&usnic_ib_ibdev_list_lock);
> >  | usnic_ib_device_add();
> >   | ib_register_device()
> >    | usnic_ib_query_port()
> >     | mutex_lock(&us_ibdev->usdev_lock);
> >      | ib_get_eth_speed()
> >       | rtnl_lock()
> > 
> > Order of locking: &usnic_ib_ibdev_list_lock -> usdev_lock -> rtnl_lock
> > 
> > rtnl_lock()
> > > usnic_ib_netdevice_event()
> >  | mutex_lock(&usnic_ib_ibdev_list_lock);
> > 
> > Order of locking: rtnl_lock -> &usnic_ib_ibdev_list_lock
> > 
> > The solution is to not handle usnic_ib_netdevice_event() with the rtnl
> > lock held.
> > This commit creates a single threaded workqueue to defer the handling of
> > netdev/inet events.
> > 
> > Signed-off-by: Govindarajulu Varadarajan <gvaradar@cisco.com>
> > Signed-off-by: Parvi Kaustubhi <pkaustub@cisco.com>
> > ---
> >  drivers/infiniband/hw/usnic/usnic_ib.h      |  6 +++
> >  drivers/infiniband/hw/usnic/usnic_ib_main.c | 60 ++++++++++++++++++++++++---
> > --
> >  2 files changed, 56 insertions(+), 10 deletions(-)
> 
> I'm really getting sick of seeing this same code pattern in the
> ethernet connecting drivers..  I think this is the 3rd time now I've
> seen a fix for this same rtnl locking bug in a driver.
> 

Is the issue here using a workqueue to handle notify?


> Does anyone want to make a proper driver core based struct
> device_driver for this discovery model???
> 

Can you elaborate on this? I do not follow.

> Also, this surely has the same bugs as rxe and bnxt, see those recent
> threads too.

Removing usnic_verbs modules works fine. I do not see issue. I will have to try
this with lockdep on.
Jason Gunthorpe Dec. 18, 2018, 4:17 p.m. UTC | #4
On Fri, Dec 14, 2018 at 02:53:00AM +0000, Govindarajulu Varadarajan (gvaradar) wrote:
> On Tue, 2018-12-11 at 23:06 +0000, Jason Gunthorpe wrote:
> > On Tue, Dec 11, 2018 at 02:15:41PM -0800, Parvi Kaustubhi wrote:
> > > From: Govindarajulu Varadarajan <gvaradar@cisco.com>
> > > 
> > > There is a dead lock in usnic ib_register() and netdev_notify() path.
> > > 
> > > usnic_ib_discover_pf()
> > > > mutex_lock(&usnic_ib_ibdev_list_lock);
> > >  | usnic_ib_device_add();
> > >   | ib_register_device()
> > >    | usnic_ib_query_port()
> > >     | mutex_lock(&us_ibdev->usdev_lock);
> > >      | ib_get_eth_speed()
> > >       | rtnl_lock()
> > > 
> > > Order of locking: &usnic_ib_ibdev_list_lock -> usdev_lock -> rtnl_lock
> > > 
> > > rtnl_lock()
> > > > usnic_ib_netdevice_event()
> > >  | mutex_lock(&usnic_ib_ibdev_list_lock);
> > > 
> > > Order of locking: rtnl_lock -> &usnic_ib_ibdev_list_lock
> > > 
> > > The solution is to not handle usnic_ib_netdevice_event() with the rtnl
> > > lock held.
> > > This commit creates a single threaded workqueue to defer the handling of
> > > netdev/inet events.
> > > 
> > > Signed-off-by: Govindarajulu Varadarajan <gvaradar@cisco.com>
> > > Signed-off-by: Parvi Kaustubhi <pkaustub@cisco.com>
> > >  drivers/infiniband/hw/usnic/usnic_ib.h      |  6 +++
> > >  drivers/infiniband/hw/usnic/usnic_ib_main.c | 60 ++++++++++++++++++++++++---
> > 
> > I'm really getting sick of seeing this same code pattern in the
> > ethernet connecting drivers..  I think this is the 3rd time now I've
> > seen a fix for this same rtnl locking bug in a driver.
> 
> Is the issue here using a workqueue to handle notify?

The issue is all the code duplication..

Jason
diff mbox series

Patch

diff --git a/drivers/infiniband/hw/usnic/usnic_ib.h b/drivers/infiniband/hw/usnic/usnic_ib.h
index 525bf27..aff4eb4 100644
--- a/drivers/infiniband/hw/usnic/usnic_ib.h
+++ b/drivers/infiniband/hw/usnic/usnic_ib.h
@@ -93,6 +93,12 @@  struct usnic_ib_vf {
 	struct list_head		link;
 };
 
+struct usnic_work {
+	struct work_struct	work;
+	unsigned long		event;
+	void			*ptr;
+};
+
 static inline
 struct usnic_ib_dev *to_usdev(struct ib_device *ibdev)
 {
diff --git a/drivers/infiniband/hw/usnic/usnic_ib_main.c b/drivers/infiniband/hw/usnic/usnic_ib_main.c
index 413fa57..dd28442 100644
--- a/drivers/infiniband/hw/usnic/usnic_ib_main.c
+++ b/drivers/infiniband/hw/usnic/usnic_ib_main.c
@@ -71,6 +71,7 @@  static const char usnic_version[] =
 
 static DEFINE_MUTEX(usnic_ib_ibdev_list_lock);
 static LIST_HEAD(usnic_ib_ibdev_list);
+static struct workqueue_struct *usnic_notify_work;
 
 /* Callback dump funcs */
 static int usnic_ib_dump_vf_hdr(void *obj, char *buf, int buf_sz)
@@ -212,21 +213,35 @@  static void usnic_ib_handle_usdev_event(struct usnic_ib_dev *us_ibdev,
 	mutex_unlock(&us_ibdev->usdev_lock);
 }
 
-static int usnic_ib_netdevice_event(struct notifier_block *notifier,
-					unsigned long event, void *ptr)
+static void usnic_ib_netdevice_work(struct work_struct *work)
 {
+	struct usnic_work *uswork = container_of(work, struct usnic_work, work);
+	struct net_device *netdev = netdev_notifier_info_to_dev(uswork->ptr);
 	struct usnic_ib_dev *us_ibdev;
 
-	struct net_device *netdev = netdev_notifier_info_to_dev(ptr);
-
 	mutex_lock(&usnic_ib_ibdev_list_lock);
 	list_for_each_entry(us_ibdev, &usnic_ib_ibdev_list, ib_dev_link) {
 		if (us_ibdev->netdev == netdev) {
-			usnic_ib_handle_usdev_event(us_ibdev, event);
+			usnic_ib_handle_usdev_event(us_ibdev, uswork->event);
 			break;
 		}
 	}
 	mutex_unlock(&usnic_ib_ibdev_list_lock);
+	kfree(uswork);
+}
+
+static int usnic_ib_netdevice_event(struct notifier_block *nblock,
+				    unsigned long event, void *ptr)
+{
+	struct usnic_work *uswork;
+
+	uswork = kzalloc(sizeof(*uswork), GFP_ATOMIC);
+	if (uswork) {
+		uswork->event = event;
+		uswork->ptr = ptr;
+		INIT_WORK(&uswork->work, usnic_ib_netdevice_work);
+		queue_work(usnic_notify_work, &uswork->work);
+	}
 
 	return NOTIFY_DONE;
 }
@@ -276,24 +291,42 @@  static int usnic_ib_handle_inet_event(struct usnic_ib_dev *us_ibdev,
 	return NOTIFY_DONE;
 }
 
-static int usnic_ib_inetaddr_event(struct notifier_block *notifier,
-					unsigned long event, void *ptr)
+static void usnic_ib_inetaddr_work(struct work_struct *work)
 {
+	struct usnic_work *uswork = container_of(work, struct usnic_work, work);
 	struct usnic_ib_dev *us_ibdev;
-	struct in_ifaddr *ifa = ptr;
+	struct in_ifaddr *ifa = uswork->ptr;
 	struct net_device *netdev = ifa->ifa_dev->dev;
 
 	mutex_lock(&usnic_ib_ibdev_list_lock);
 	list_for_each_entry(us_ibdev, &usnic_ib_ibdev_list, ib_dev_link) {
 		if (us_ibdev->netdev == netdev) {
-			usnic_ib_handle_inet_event(us_ibdev, event, ptr);
+			usnic_ib_handle_inet_event(us_ibdev, uswork->event,
+						   uswork->ptr);
 			break;
 		}
 	}
 	mutex_unlock(&usnic_ib_ibdev_list_lock);
+	kfree(uswork);
+}
+
+static int usnic_ib_inetaddr_event(struct notifier_block *nblock,
+				   unsigned long event, void *ptr)
+{
+	struct usnic_work *uswork;
+
+	uswork = kzalloc(sizeof(*uswork), GFP_ATOMIC);
+
+	if (uswork) {
+		uswork->ptr = ptr;
+		uswork->event = event;
+		INIT_WORK(&uswork->work, usnic_ib_inetaddr_work);
+		queue_work(usnic_notify_work, &uswork->work);
+	}
 
 	return NOTIFY_DONE;
 }
+
 static struct notifier_block usnic_ib_inetaddr_notifier = {
 	.notifier_call = usnic_ib_inetaddr_event
 };
@@ -653,10 +686,15 @@  static int __init usnic_ib_init(void)
 		return err;
 	}
 
+	usnic_notify_work = create_singlethread_workqueue("usnic_notify_work");
+	if (!usnic_notify_work) {
+		usnic_err("Failed to create notify workqueue");
+		goto out_umem_fini;
+	}
 	err = pci_register_driver(&usnic_ib_pci_driver);
 	if (err) {
 		usnic_err("Unable to register with PCI\n");
-		goto out_umem_fini;
+		goto out_notify_work;
 	}
 
 	err = register_netdevice_notifier(&usnic_ib_netdevice_notifier);
@@ -687,6 +725,8 @@  static int __init usnic_ib_init(void)
 	unregister_netdevice_notifier(&usnic_ib_netdevice_notifier);
 out_pci_unreg:
 	pci_unregister_driver(&usnic_ib_pci_driver);
+out_notify_work:
+	destroy_workqueue(usnic_notify_work);
 out_umem_fini:
 	usnic_uiom_fini();