From: Sang-Hun Lee Date: Wed, 30 Jul 2014 00:53:48 +0000 (-0700) Subject: bcmdhd: ensure work completion before freeing X-Git-Tag: daily-2014.08.28.0_rel-st8-r2.4-partner~33 X-Git-Url: http://rtime.felk.cvut.cz/gitweb/sojka/nv-tegra/linux-3.10.git/commitdiff_plain/1fea2335204fe44e5a5637684022d12290353f26 bcmdhd: ensure work completion before freeing 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 Reviewed-on: http://git-master/r/448637 Reviewed-by: Vinayak Pane GVS: Gerrit_Virtual_Submit Reviewed-by: Mitch Luban --- diff --git a/drivers/net/wireless/bcmdhd/wl_cfg80211.c b/drivers/net/wireless/bcmdhd/wl_cfg80211.c index 5baaf63c714..7b6a43243fa 100644 --- a/drivers/net/wireless/bcmdhd/wl_cfg80211.c +++ b/drivers/net/wireless/bcmdhd/wl_cfg80211.c @@ -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; } diff --git a/drivers/net/wireless/bcmdhd/wl_cfg80211.h b/drivers/net/wireless/bcmdhd/wl_cfg80211.h index 4ea4b8273d3..da08c33454f 100644 --- a/drivers/net/wireless/bcmdhd/wl_cfg80211.h +++ b/drivers/net/wireless/bcmdhd/wl_cfg80211.h @@ -32,6 +32,7 @@ #include #include #include +#include #include #include @@ -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) {