]> rtime.felk.cvut.cz Git - sojka/nv-tegra/linux-3.10.git/commitdiff
bcmdhd: ensure work completion before freeing
authorSang-Hun Lee <sanlee@nvidia.com>
Wed, 30 Jul 2014 00:53:48 +0000 (17:53 -0700)
committerTodd Poynter <tpoynter@nvidia.com>
Sat, 2 Aug 2014 04:39:56 +0000 (21:39 -0700)
Problem description:
 - wl_dealloc_netinfo would free allocated wireless_dev structures, which has
   work_struct for wdev_cleanup_work
 - If there is any job scheduled for wdev_cleanup_work and if it
   is scheduled after kfree of its parent struct, the result is undefined
 - But simply waiting for wdev_cleanup_work completion in
   wl_cfg80211_netdev_notifier_call would result in a dead lock, as wdev_cleanup_work
   and cfg80211_event_work both use the same workqueue. cfg80211_event_work
   may ultimately call wl_cfg80211_netdev_notifier_call

Fix description:
 - Separate the detach of an interface from the deallocation of an interface
 - Upon a detach, only remove the interface, and schedule a deallocation of it

Bug 1533639

Change-Id: I865907243ac370ab5932fb1d5ec7ce8c83279baf
Signed-off-by: Sang-Hun Lee <sanlee@nvidia.com>
Reviewed-on: http://git-master/r/448637
Reviewed-by: Vinayak Pane <vpane@nvidia.com>
GVS: Gerrit_Virtual_Submit
Reviewed-by: Mitch Luban <mluban@nvidia.com>
drivers/net/wireless/bcmdhd/wl_cfg80211.c
drivers/net/wireless/bcmdhd/wl_cfg80211.h

index 5baaf63c71476515b6a84de04e0f8253410a8e29..7b6a43243fa2bab2bb8da29ce2ff6e3f5358e30c 100644 (file)
@@ -8742,7 +8742,7 @@ wl_cfg80211_netdev_notifier_call(struct notifier_block * nb,
 
                case NETDEV_UNREGISTER:
                        /* after calling list_del_rcu(&wdev->list) */
-                       wl_dealloc_netinfo(wl, ndev);
+                       wl_remove_netinfo(wl, ndev);
                        break;
                case NETDEV_GOING_DOWN:
                        /* At NETDEV_DOWN state, wdev_cleanup_work work will be called.
@@ -9372,6 +9372,23 @@ static s32 wl_init_scan(struct wl_priv *wl)
        return err;
 }
 
+static void wl_dealloc_netinfo(struct work_struct *work)
+{
+       struct net_info *_net_info, *next;
+       struct wl_priv *wl = container_of(work, struct wl_priv, dealloc_work);
+
+       list_for_each_entry_safe(_net_info, next, &wl->dealloc_list, list) {
+               list_del(&_net_info->list);
+               if (_net_info->wdev) {
+                       flush_work(&_net_info->wdev->cleanup_work);
+                       WARN_ON(work_pending(&_net_info->wdev->cleanup_work));
+                       kfree(_net_info->wdev);
+               }
+               kfree(_net_info);
+       }
+
+}
+
 static s32 wl_init_priv(struct wl_priv *wl)
 {
        struct wiphy *wiphy = wl_to_wiphy(wl);
@@ -9412,6 +9429,8 @@ static s32 wl_init_priv(struct wl_priv *wl)
        wl_init_prof(wl, ndev);
        wl_link_down(wl);
        DNGL_FUNC(dhd_cfg80211_init, (wl));
+       INIT_LIST_HEAD(&wl->dealloc_list);
+       INIT_WORK(&wl->dealloc_work, wl_dealloc_netinfo);
 
        return err;
 }
index 4ea4b8273d3620b2561bf35d29ebbab341f1d7ed..da08c33454f8369f418f4aeaf342f715cc065180 100644 (file)
@@ -32,6 +32,7 @@
 #include <proto/ethernet.h>
 #include <wlioctl.h>
 #include <linux/wireless.h>
+#include <linux/workqueue.h>
 #include <net/cfg80211.h>
 #include <linux/rfkill.h>
 
@@ -502,6 +503,7 @@ struct wl_priv {
        EVENT_HANDLER evt_handler[WLC_E_LAST];
        struct list_head eq_list;       /* used for event queue */
        struct list_head net_list;     /* used for struct net_info */
+       struct list_head dealloc_list;     /* used for struct net_info which can be freed */
        spinlock_t eq_lock;     /* for event queue synchronization */
        spinlock_t cfgdrv_lock; /* to protect scan status (and others if needed) */
        struct completion act_frm_scan;
@@ -587,6 +589,7 @@ struct wl_priv {
        bool scan_suppressed;
        struct timer_list scan_supp_timer;
        struct work_struct wlan_work;
+       struct work_struct dealloc_work;
        struct mutex event_sync;        /* maily for up/down synchronization */
        bool pm_enable_work_on;
        struct delayed_work pm_enable_work;
@@ -626,23 +629,27 @@ wl_alloc_netinfo(struct wl_priv *wl, struct net_device *ndev,
        return err;
 }
 static inline void
-wl_dealloc_netinfo(struct wl_priv *wl, struct net_device *ndev)
+wl_remove_netinfo(struct wl_priv *wl, struct net_device *ndev)
 {
        struct net_info *_net_info, *next;
+       bool dealloc_needed = false;
 
        list_for_each_entry_safe(_net_info, next, &wl->net_list, list) {
                if (ndev && (_net_info->ndev == ndev)) {
                        list_del(&_net_info->list);
                        wl->iface_cnt--;
                        if (_net_info->wdev) {
-                               kfree(_net_info->wdev);
                                ndev->ieee80211_ptr = NULL;
                        }
-                       kfree(_net_info);
+                       INIT_LIST_HEAD(&_net_info->list);
+                       list_add(&_net_info->list, &wl->dealloc_list);
+                       dealloc_needed = true;
                }
        }
-
+       if (dealloc_needed)
+               schedule_work(&wl->dealloc_work);
 }
+
 static inline void
 wl_delete_all_netinfo(struct wl_priv *wl)
 {