brcmfmac: fix lockup when removing P2P interface after event timeout

Removing P2P interface is handled by sending a proper request to the
firmware. On success firmware triggers an event and driver's handler
removes a matching interface.

However on event timeout we remove interface directly from the cfg80211
callback. Current code doesn't handle this case correctly as it always
assumes rtnl to be unlocked.

Fix it by adding an extra rtnl_locked parameter to functions and calling
unregister_netdevice when needed.

Signed-off-by: Rafał Miłecki <zajec5@gmail.com>
Signed-off-by: Kalle Valo <kvalo@codeaurora.org>
This commit is contained in:
Rafał Miłecki 2016-06-17 12:29:21 +02:00 committed by Kalle Valo
parent 6f07e0f12a
commit b50ddfa853
4 changed files with 21 additions and 16 deletions

View File

@ -548,12 +548,16 @@ int brcmf_net_attach(struct brcmf_if *ifp, bool rtnl_locked)
return -EBADE; return -EBADE;
} }
static void brcmf_net_detach(struct net_device *ndev) static void brcmf_net_detach(struct net_device *ndev, bool rtnl_locked)
{ {
if (ndev->reg_state == NETREG_REGISTERED) if (ndev->reg_state == NETREG_REGISTERED) {
unregister_netdev(ndev); if (rtnl_locked)
else unregister_netdevice(ndev);
else
unregister_netdev(ndev);
} else {
brcmf_cfg80211_free_netdev(ndev); brcmf_cfg80211_free_netdev(ndev);
}
} }
void brcmf_net_setcarrier(struct brcmf_if *ifp, bool on) void brcmf_net_setcarrier(struct brcmf_if *ifp, bool on)
@ -651,7 +655,7 @@ struct brcmf_if *brcmf_add_if(struct brcmf_pub *drvr, s32 bsscfgidx, s32 ifidx,
brcmf_err("ERROR: netdev:%s already exists\n", brcmf_err("ERROR: netdev:%s already exists\n",
ifp->ndev->name); ifp->ndev->name);
netif_stop_queue(ifp->ndev); netif_stop_queue(ifp->ndev);
brcmf_net_detach(ifp->ndev); brcmf_net_detach(ifp->ndev, false);
drvr->iflist[bsscfgidx] = NULL; drvr->iflist[bsscfgidx] = NULL;
} else { } else {
brcmf_dbg(INFO, "netdev:%s ignore IF event\n", brcmf_dbg(INFO, "netdev:%s ignore IF event\n",
@ -699,7 +703,8 @@ struct brcmf_if *brcmf_add_if(struct brcmf_pub *drvr, s32 bsscfgidx, s32 ifidx,
return ifp; return ifp;
} }
static void brcmf_del_if(struct brcmf_pub *drvr, s32 bsscfgidx) static void brcmf_del_if(struct brcmf_pub *drvr, s32 bsscfgidx,
bool rtnl_locked)
{ {
struct brcmf_if *ifp; struct brcmf_if *ifp;
@ -729,7 +734,7 @@ static void brcmf_del_if(struct brcmf_pub *drvr, s32 bsscfgidx)
cancel_work_sync(&ifp->multicast_work); cancel_work_sync(&ifp->multicast_work);
cancel_work_sync(&ifp->ndoffload_work); cancel_work_sync(&ifp->ndoffload_work);
} }
brcmf_net_detach(ifp->ndev); brcmf_net_detach(ifp->ndev, rtnl_locked);
} else { } else {
/* Only p2p device interfaces which get dynamically created /* Only p2p device interfaces which get dynamically created
* end up here. In this case the p2p module should be informed * end up here. In this case the p2p module should be informed
@ -743,14 +748,14 @@ static void brcmf_del_if(struct brcmf_pub *drvr, s32 bsscfgidx)
} }
} }
void brcmf_remove_interface(struct brcmf_if *ifp) void brcmf_remove_interface(struct brcmf_if *ifp, bool rtnl_locked)
{ {
if (!ifp || WARN_ON(ifp->drvr->iflist[ifp->bsscfgidx] != ifp)) if (!ifp || WARN_ON(ifp->drvr->iflist[ifp->bsscfgidx] != ifp))
return; return;
brcmf_dbg(TRACE, "Enter, bsscfgidx=%d, ifidx=%d\n", ifp->bsscfgidx, brcmf_dbg(TRACE, "Enter, bsscfgidx=%d, ifidx=%d\n", ifp->bsscfgidx,
ifp->ifidx); ifp->ifidx);
brcmf_fws_del_interface(ifp); brcmf_fws_del_interface(ifp);
brcmf_del_if(ifp->drvr, ifp->bsscfgidx); brcmf_del_if(ifp->drvr, ifp->bsscfgidx, rtnl_locked);
} }
#ifdef CONFIG_INET #ifdef CONFIG_INET
@ -1057,9 +1062,9 @@ int brcmf_bus_start(struct device *dev)
brcmf_fws_deinit(drvr); brcmf_fws_deinit(drvr);
} }
if (ifp) if (ifp)
brcmf_net_detach(ifp->ndev); brcmf_net_detach(ifp->ndev, false);
if (p2p_ifp) if (p2p_ifp)
brcmf_net_detach(p2p_ifp->ndev); brcmf_net_detach(p2p_ifp->ndev, false);
drvr->iflist[0] = NULL; drvr->iflist[0] = NULL;
drvr->iflist[1] = NULL; drvr->iflist[1] = NULL;
if (drvr->settings->ignore_probe_fail) if (drvr->settings->ignore_probe_fail)
@ -1128,7 +1133,7 @@ void brcmf_detach(struct device *dev)
/* make sure primary interface removed last */ /* make sure primary interface removed last */
for (i = BRCMF_MAX_IFS-1; i > -1; i--) for (i = BRCMF_MAX_IFS-1; i > -1; i--)
brcmf_remove_interface(drvr->iflist[i]); brcmf_remove_interface(drvr->iflist[i], false);
brcmf_cfg80211_detach(drvr->config); brcmf_cfg80211_detach(drvr->config);

View File

@ -216,7 +216,7 @@ struct brcmf_if *brcmf_get_ifp(struct brcmf_pub *drvr, int ifidx);
int brcmf_net_attach(struct brcmf_if *ifp, bool rtnl_locked); int brcmf_net_attach(struct brcmf_if *ifp, bool rtnl_locked);
struct brcmf_if *brcmf_add_if(struct brcmf_pub *drvr, s32 bsscfgidx, s32 ifidx, struct brcmf_if *brcmf_add_if(struct brcmf_pub *drvr, s32 bsscfgidx, s32 ifidx,
bool is_p2pdev, char *name, u8 *mac_addr); bool is_p2pdev, char *name, u8 *mac_addr);
void brcmf_remove_interface(struct brcmf_if *ifp); void brcmf_remove_interface(struct brcmf_if *ifp, bool rtnl_locked);
void brcmf_txflowblock_if(struct brcmf_if *ifp, void brcmf_txflowblock_if(struct brcmf_if *ifp,
enum brcmf_netif_stop_reason reason, bool state); enum brcmf_netif_stop_reason reason, bool state);
void brcmf_txfinalize(struct brcmf_if *ifp, struct sk_buff *txp, bool success); void brcmf_txfinalize(struct brcmf_if *ifp, struct sk_buff *txp, bool success);

View File

@ -183,7 +183,7 @@ static void brcmf_fweh_handle_if_event(struct brcmf_pub *drvr,
err = brcmf_fweh_call_event_handler(ifp, emsg->event_code, emsg, data); err = brcmf_fweh_call_event_handler(ifp, emsg->event_code, emsg, data);
if (ifp && ifevent->action == BRCMF_E_IF_DEL) if (ifp && ifevent->action == BRCMF_E_IF_DEL)
brcmf_remove_interface(ifp); brcmf_remove_interface(ifp, false);
} }
/** /**

View File

@ -2287,7 +2287,7 @@ int brcmf_p2p_del_vif(struct wiphy *wiphy, struct wireless_dev *wdev)
err = 0; err = 0;
} }
if (err) if (err)
brcmf_remove_interface(vif->ifp); brcmf_remove_interface(vif->ifp, true);
brcmf_cfg80211_arm_vif_event(cfg, NULL); brcmf_cfg80211_arm_vif_event(cfg, NULL);
if (vif->wdev.iftype != NL80211_IFTYPE_P2P_DEVICE) if (vif->wdev.iftype != NL80211_IFTYPE_P2P_DEVICE)
@ -2393,7 +2393,7 @@ void brcmf_p2p_detach(struct brcmf_p2p_info *p2p)
if (vif != NULL) { if (vif != NULL) {
brcmf_p2p_cancel_remain_on_channel(vif->ifp); brcmf_p2p_cancel_remain_on_channel(vif->ifp);
brcmf_p2p_deinit_discovery(p2p); brcmf_p2p_deinit_discovery(p2p);
brcmf_remove_interface(vif->ifp); brcmf_remove_interface(vif->ifp, false);
} }
/* just set it all to zero */ /* just set it all to zero */
memset(p2p, 0, sizeof(*p2p)); memset(p2p, 0, sizeof(*p2p));