dect
/
linux-2.6
Archived
13
0
Fork 0

mac80211: let cfg80211 manage auth state

mac80211 currently hangs on to the auth state by
keeping it on the work list. That can lead to
confusing behaviour like rejecting scans while
authenticated to any AP (but not yet associated.)
It also means that it needs to keep track of the
work struct while associated for when it gets
disassociated (or disassociates.)

Change this to free the work struct after the
authentication completed successfully and
allocate a new one for associating, thereby
letting cfg80211 manage the auth state. Another
change necessary for this is to tell cfg80211
about all unicast deauth frames sent to mac80211
since now it can no longer check the auth state,
but that check was racy anyway.

Signed-off-by: Johannes Berg <johannes@sipsolutions.net>
Signed-off-by: John W. Linville <linville@tuxdriver.com>
This commit is contained in:
Johannes Berg 2009-12-23 13:15:33 +01:00 committed by John W. Linville
parent a80f7c0b08
commit 63f170e0c8
2 changed files with 73 additions and 103 deletions

View File

@ -228,7 +228,7 @@ struct mesh_preq_queue {
}; };
enum ieee80211_mgd_state { enum ieee80211_mgd_state {
IEEE80211_MGD_STATE_IDLE, IEEE80211_MGD_STATE_INVALID,
IEEE80211_MGD_STATE_PROBE, IEEE80211_MGD_STATE_PROBE,
IEEE80211_MGD_STATE_AUTH, IEEE80211_MGD_STATE_AUTH,
IEEE80211_MGD_STATE_ASSOC, IEEE80211_MGD_STATE_ASSOC,
@ -285,7 +285,6 @@ struct ieee80211_if_managed {
struct mutex mtx; struct mutex mtx;
struct ieee80211_bss *associated; struct ieee80211_bss *associated;
struct ieee80211_mgd_work *old_associate_work;
struct list_head work_list; struct list_head work_list;
u8 bssid[ETH_ALEN]; u8 bssid[ETH_ALEN];

View File

@ -949,11 +949,10 @@ static u32 ieee80211_handle_bss_capability(struct ieee80211_sub_if_data *sdata,
} }
static void ieee80211_set_associated(struct ieee80211_sub_if_data *sdata, static void ieee80211_set_associated(struct ieee80211_sub_if_data *sdata,
struct ieee80211_mgd_work *wk, struct ieee80211_bss *bss,
u32 bss_info_changed) u32 bss_info_changed)
{ {
struct ieee80211_local *local = sdata->local; struct ieee80211_local *local = sdata->local;
struct ieee80211_bss *bss = wk->bss;
bss_info_changed |= BSS_CHANGED_ASSOC; bss_info_changed |= BSS_CHANGED_ASSOC;
/* set timing information */ /* set timing information */
@ -966,7 +965,6 @@ static void ieee80211_set_associated(struct ieee80211_sub_if_data *sdata,
bss->cbss.capability, bss->has_erp_value, bss->erp_value); bss->cbss.capability, bss->has_erp_value, bss->erp_value);
sdata->u.mgd.associated = bss; sdata->u.mgd.associated = bss;
sdata->u.mgd.old_associate_work = wk;
memcpy(sdata->u.mgd.bssid, bss->cbss.bssid, ETH_ALEN); memcpy(sdata->u.mgd.bssid, bss->cbss.bssid, ETH_ALEN);
/* just to be sure */ /* just to be sure */
@ -1090,8 +1088,7 @@ ieee80211_authenticate(struct ieee80211_sub_if_data *sdata,
return RX_MGMT_NONE; return RX_MGMT_NONE;
} }
static void ieee80211_set_disassoc(struct ieee80211_sub_if_data *sdata, static void ieee80211_set_disassoc(struct ieee80211_sub_if_data *sdata)
bool deauth)
{ {
struct ieee80211_if_managed *ifmgd = &sdata->u.mgd; struct ieee80211_if_managed *ifmgd = &sdata->u.mgd;
struct ieee80211_local *local = sdata->local; struct ieee80211_local *local = sdata->local;
@ -1109,16 +1106,6 @@ static void ieee80211_set_disassoc(struct ieee80211_sub_if_data *sdata,
ifmgd->associated = NULL; ifmgd->associated = NULL;
memset(ifmgd->bssid, 0, ETH_ALEN); memset(ifmgd->bssid, 0, ETH_ALEN);
if (deauth) {
kfree(ifmgd->old_associate_work);
ifmgd->old_associate_work = NULL;
} else {
struct ieee80211_mgd_work *wk = ifmgd->old_associate_work;
wk->state = IEEE80211_MGD_STATE_IDLE;
list_add(&wk->list, &ifmgd->work_list);
}
/* /*
* we need to commit the associated = NULL change because the * we need to commit the associated = NULL change because the
* scan code uses that to determine whether this iface should * scan code uses that to determine whether this iface should
@ -1333,7 +1320,8 @@ EXPORT_SYMBOL(ieee80211_beacon_loss);
static void ieee80211_auth_completed(struct ieee80211_sub_if_data *sdata, static void ieee80211_auth_completed(struct ieee80211_sub_if_data *sdata,
struct ieee80211_mgd_work *wk) struct ieee80211_mgd_work *wk)
{ {
wk->state = IEEE80211_MGD_STATE_IDLE; list_del(&wk->list);
kfree(wk);
printk(KERN_DEBUG "%s: authenticated\n", sdata->name); printk(KERN_DEBUG "%s: authenticated\n", sdata->name);
} }
@ -1411,7 +1399,6 @@ ieee80211_rx_mgmt_auth(struct ieee80211_sub_if_data *sdata,
static enum rx_mgmt_action __must_check static enum rx_mgmt_action __must_check
ieee80211_rx_mgmt_deauth(struct ieee80211_sub_if_data *sdata, ieee80211_rx_mgmt_deauth(struct ieee80211_sub_if_data *sdata,
struct ieee80211_mgd_work *wk,
struct ieee80211_mgmt *mgmt, size_t len) struct ieee80211_mgmt *mgmt, size_t len)
{ {
struct ieee80211_if_managed *ifmgd = &sdata->u.mgd; struct ieee80211_if_managed *ifmgd = &sdata->u.mgd;
@ -1423,23 +1410,15 @@ ieee80211_rx_mgmt_deauth(struct ieee80211_sub_if_data *sdata,
ASSERT_MGD_MTX(ifmgd); ASSERT_MGD_MTX(ifmgd);
if (wk) bssid = ifmgd->associated->cbss.bssid;
bssid = wk->bss->cbss.bssid;
else
bssid = ifmgd->associated->cbss.bssid;
reason_code = le16_to_cpu(mgmt->u.deauth.reason_code); reason_code = le16_to_cpu(mgmt->u.deauth.reason_code);
printk(KERN_DEBUG "%s: deauthenticated from %pM (Reason: %u)\n", printk(KERN_DEBUG "%s: deauthenticated from %pM (Reason: %u)\n",
sdata->name, bssid, reason_code); sdata->name, bssid, reason_code);
if (!wk) { ieee80211_set_disassoc(sdata);
ieee80211_set_disassoc(sdata, true); ieee80211_recalc_idle(sdata->local);
ieee80211_recalc_idle(sdata->local);
} else {
list_del(&wk->list);
kfree(wk);
}
return RX_MGMT_CFG80211_DEAUTH; return RX_MGMT_CFG80211_DEAUTH;
} }
@ -1468,7 +1447,7 @@ ieee80211_rx_mgmt_disassoc(struct ieee80211_sub_if_data *sdata,
printk(KERN_DEBUG "%s: disassociated from %pM (Reason: %u)\n", printk(KERN_DEBUG "%s: disassociated from %pM (Reason: %u)\n",
sdata->name, mgmt->sa, reason_code); sdata->name, mgmt->sa, reason_code);
ieee80211_set_disassoc(sdata, false); ieee80211_set_disassoc(sdata);
ieee80211_recalc_idle(sdata->local); ieee80211_recalc_idle(sdata->local);
return RX_MGMT_CFG80211_DISASSOC; return RX_MGMT_CFG80211_DISASSOC;
} }
@ -1484,6 +1463,7 @@ ieee80211_rx_mgmt_assoc_resp(struct ieee80211_sub_if_data *sdata,
struct ieee80211_local *local = sdata->local; struct ieee80211_local *local = sdata->local;
struct ieee80211_supported_band *sband; struct ieee80211_supported_band *sband;
struct sta_info *sta; struct sta_info *sta;
struct ieee80211_bss *bss = wk->bss;
u32 rates, basic_rates; u32 rates, basic_rates;
u16 capab_info, status_code, aid; u16 capab_info, status_code, aid;
struct ieee802_11_elems elems; struct ieee802_11_elems elems;
@ -1502,7 +1482,7 @@ ieee80211_rx_mgmt_assoc_resp(struct ieee80211_sub_if_data *sdata,
if (len < 24 + 6) if (len < 24 + 6)
return RX_MGMT_NONE; return RX_MGMT_NONE;
if (memcmp(wk->bss->cbss.bssid, mgmt->sa, ETH_ALEN) != 0) if (memcmp(bss->cbss.bssid, mgmt->sa, ETH_ALEN) != 0)
return RX_MGMT_NONE; return RX_MGMT_NONE;
capab_info = le16_to_cpu(mgmt->u.assoc_resp.capab_info); capab_info = le16_to_cpu(mgmt->u.assoc_resp.capab_info);
@ -1532,10 +1512,17 @@ ieee80211_rx_mgmt_assoc_resp(struct ieee80211_sub_if_data *sdata,
return RX_MGMT_NONE; return RX_MGMT_NONE;
} }
/*
* Here the association was either successful or not.
*/
/* delete work item -- must be before set_associated for PS */
list_del(&wk->list);
kfree(wk);
if (status_code != WLAN_STATUS_SUCCESS) { if (status_code != WLAN_STATUS_SUCCESS) {
printk(KERN_DEBUG "%s: AP denied association (code=%d)\n", printk(KERN_DEBUG "%s: AP denied association (code=%d)\n",
sdata->name, status_code); sdata->name, status_code);
wk->state = IEEE80211_MGD_STATE_IDLE;
return RX_MGMT_CFG80211_ASSOC; return RX_MGMT_CFG80211_ASSOC;
} }
@ -1553,7 +1540,7 @@ ieee80211_rx_mgmt_assoc_resp(struct ieee80211_sub_if_data *sdata,
printk(KERN_DEBUG "%s: associated\n", sdata->name); printk(KERN_DEBUG "%s: associated\n", sdata->name);
ifmgd->aid = aid; ifmgd->aid = aid;
sta = sta_info_alloc(sdata, wk->bss->cbss.bssid, GFP_KERNEL); sta = sta_info_alloc(sdata, bss->cbss.bssid, GFP_KERNEL);
if (!sta) { if (!sta) {
printk(KERN_DEBUG "%s: failed to alloc STA entry for" printk(KERN_DEBUG "%s: failed to alloc STA entry for"
" the AP\n", sdata->name); " the AP\n", sdata->name);
@ -1645,18 +1632,14 @@ ieee80211_rx_mgmt_assoc_resp(struct ieee80211_sub_if_data *sdata,
(ifmgd->flags & IEEE80211_STA_WMM_ENABLED) && (ifmgd->flags & IEEE80211_STA_WMM_ENABLED) &&
!(ifmgd->flags & IEEE80211_STA_DISABLE_11N)) !(ifmgd->flags & IEEE80211_STA_DISABLE_11N))
changed |= ieee80211_enable_ht(sdata, elems.ht_info_elem, changed |= ieee80211_enable_ht(sdata, elems.ht_info_elem,
wk->bss->cbss.bssid, bss->cbss.bssid,
ap_ht_cap_flags); ap_ht_cap_flags);
/* delete work item -- must be before set_associated for PS */
list_del(&wk->list);
/* set AID and assoc capability, /* set AID and assoc capability,
* ieee80211_set_associated() will tell the driver */ * ieee80211_set_associated() will tell the driver */
bss_conf->aid = aid; bss_conf->aid = aid;
bss_conf->assoc_capability = capab_info; bss_conf->assoc_capability = capab_info;
/* this will take ownership of wk */ ieee80211_set_associated(sdata, bss, changed);
ieee80211_set_associated(sdata, wk, changed);
/* /*
* Start timer to probe the connection to the AP now. * Start timer to probe the connection to the AP now.
@ -1999,8 +1982,7 @@ static void ieee80211_sta_rx_queued_mgmt(struct ieee80211_sub_if_data *sdata,
skb->len, rx_status); skb->len, rx_status);
break; break;
case IEEE80211_STYPE_DEAUTH: case IEEE80211_STYPE_DEAUTH:
rma = ieee80211_rx_mgmt_deauth(sdata, NULL, rma = ieee80211_rx_mgmt_deauth(sdata, mgmt, skb->len);
mgmt, skb->len);
break; break;
case IEEE80211_STYPE_DISASSOC: case IEEE80211_STYPE_DISASSOC:
rma = ieee80211_rx_mgmt_disassoc(sdata, mgmt, skb->len); rma = ieee80211_rx_mgmt_disassoc(sdata, mgmt, skb->len);
@ -2051,8 +2033,15 @@ static void ieee80211_sta_rx_queued_mgmt(struct ieee80211_sub_if_data *sdata,
skb->len, true); skb->len, true);
break; break;
case IEEE80211_STYPE_DEAUTH: case IEEE80211_STYPE_DEAUTH:
rma = ieee80211_rx_mgmt_deauth(sdata, wk, mgmt, if (skb->len >= 24 + 2 /* mgmt + deauth reason */) {
skb->len); /*
* We get here if we get deauth while
* trying to auth/assoc. Telling cfg80211
* is handled below, unconditionally.
*/
list_del(&wk->list);
kfree(wk);
}
break; break;
} }
/* /*
@ -2066,6 +2055,12 @@ static void ieee80211_sta_rx_queued_mgmt(struct ieee80211_sub_if_data *sdata,
mutex_unlock(&ifmgd->mtx); mutex_unlock(&ifmgd->mtx);
if (skb->len >= 24 + 2 /* mgmt + deauth reason */ &&
(fc & IEEE80211_FCTL_STYPE) == IEEE80211_STYPE_DEAUTH) {
WARN_ON(rma != RX_MGMT_NONE);
rma = RX_MGMT_CFG80211_DEAUTH;
}
switch (rma) { switch (rma) {
case RX_MGMT_NONE: case RX_MGMT_NONE:
/* no action */ /* no action */
@ -2116,7 +2111,6 @@ static void ieee80211_sta_work(struct work_struct *work)
struct ieee80211_mgd_work *wk, *tmp; struct ieee80211_mgd_work *wk, *tmp;
LIST_HEAD(free_work); LIST_HEAD(free_work);
enum rx_mgmt_action rma; enum rx_mgmt_action rma;
bool anybusy = false;
if (!ieee80211_sdata_running(sdata)) if (!ieee80211_sdata_running(sdata))
return; return;
@ -2171,7 +2165,7 @@ static void ieee80211_sta_work(struct work_struct *work)
printk(KERN_DEBUG "No probe response from AP %pM" printk(KERN_DEBUG "No probe response from AP %pM"
" after %dms, disconnecting.\n", " after %dms, disconnecting.\n",
bssid, (1000 * IEEE80211_PROBE_WAIT)/HZ); bssid, (1000 * IEEE80211_PROBE_WAIT)/HZ);
ieee80211_set_disassoc(sdata, true); ieee80211_set_disassoc(sdata);
ieee80211_recalc_idle(local); ieee80211_recalc_idle(local);
mutex_unlock(&ifmgd->mtx); mutex_unlock(&ifmgd->mtx);
/* /*
@ -2203,8 +2197,6 @@ static void ieee80211_sta_work(struct work_struct *work)
switch (wk->state) { switch (wk->state) {
default: default:
WARN_ON(1); WARN_ON(1);
/* fall through */
case IEEE80211_MGD_STATE_IDLE:
/* nothing */ /* nothing */
rma = RX_MGMT_NONE; rma = RX_MGMT_NONE;
break; break;
@ -2227,20 +2219,19 @@ static void ieee80211_sta_work(struct work_struct *work)
case RX_MGMT_CFG80211_ASSOC_TO: case RX_MGMT_CFG80211_ASSOC_TO:
list_del(&wk->list); list_del(&wk->list);
list_add(&wk->list, &free_work); list_add(&wk->list, &free_work);
wk->tries = rma; /* small abuse but only local */ /*
* small abuse but only local -- keep the
* action type in wk->timeout while the item
* is on the cleanup list
*/
wk->timeout = rma;
break; break;
default: default:
WARN(1, "unexpected: %d", rma); WARN(1, "unexpected: %d", rma);
} }
} }
list_for_each_entry(wk, &ifmgd->work_list, list) { if (list_empty(&ifmgd->work_list) &&
if (wk->state != IEEE80211_MGD_STATE_IDLE) {
anybusy = true;
break;
}
}
if (!anybusy &&
test_and_clear_bit(IEEE80211_STA_REQ_SCAN, &ifmgd->request)) test_and_clear_bit(IEEE80211_STA_REQ_SCAN, &ifmgd->request))
ieee80211_queue_delayed_work(&local->hw, ieee80211_queue_delayed_work(&local->hw,
&local->scan_work, &local->scan_work,
@ -2249,7 +2240,8 @@ static void ieee80211_sta_work(struct work_struct *work)
mutex_unlock(&ifmgd->mtx); mutex_unlock(&ifmgd->mtx);
list_for_each_entry_safe(wk, tmp, &free_work, list) { list_for_each_entry_safe(wk, tmp, &free_work, list) {
switch (wk->tries) { /* see above how we're using wk->timeout */
switch (wk->timeout) {
case RX_MGMT_CFG80211_AUTH_TO: case RX_MGMT_CFG80211_AUTH_TO:
cfg80211_send_auth_timeout(sdata->dev, cfg80211_send_auth_timeout(sdata->dev,
wk->bss->cbss.bssid); wk->bss->cbss.bssid);
@ -2259,7 +2251,7 @@ static void ieee80211_sta_work(struct work_struct *work)
wk->bss->cbss.bssid); wk->bss->cbss.bssid);
break; break;
default: default:
WARN(1, "unexpected: %d", wk->tries); WARN(1, "unexpected: %lu", wk->timeout);
} }
list_del(&wk->list); list_del(&wk->list);
@ -2487,35 +2479,18 @@ int ieee80211_mgd_assoc(struct ieee80211_sub_if_data *sdata,
struct cfg80211_assoc_request *req) struct cfg80211_assoc_request *req)
{ {
struct ieee80211_if_managed *ifmgd = &sdata->u.mgd; struct ieee80211_if_managed *ifmgd = &sdata->u.mgd;
struct ieee80211_mgd_work *wk, *found = NULL; struct ieee80211_mgd_work *wk;
const u8 *ssid;
int i, err; int i, err;
mutex_lock(&ifmgd->mtx); mutex_lock(&ifmgd->mtx);
list_for_each_entry(wk, &ifmgd->work_list, list) { wk = kzalloc(sizeof(*wk) + req->ie_len, GFP_KERNEL);
if (&wk->bss->cbss == req->bss &&
wk->state == IEEE80211_MGD_STATE_IDLE) {
found = wk;
break;
}
}
if (!found) {
err = -ENOLINK;
goto out;
}
list_del(&found->list);
wk = krealloc(found, sizeof(*wk) + req->ie_len, GFP_KERNEL);
if (!wk) { if (!wk) {
list_add(&found->list, &ifmgd->work_list);
err = -ENOMEM; err = -ENOMEM;
goto out; goto out;
} }
list_add(&wk->list, &ifmgd->work_list);
ifmgd->flags &= ~IEEE80211_STA_DISABLE_11N; ifmgd->flags &= ~IEEE80211_STA_DISABLE_11N;
for (i = 0; i < req->crypto.n_ciphers_pairwise; i++) for (i = 0; i < req->crypto.n_ciphers_pairwise; i++)
@ -2524,8 +2499,6 @@ int ieee80211_mgd_assoc(struct ieee80211_sub_if_data *sdata,
req->crypto.ciphers_pairwise[i] == WLAN_CIPHER_SUITE_WEP104) req->crypto.ciphers_pairwise[i] == WLAN_CIPHER_SUITE_WEP104)
ifmgd->flags |= IEEE80211_STA_DISABLE_11N; ifmgd->flags |= IEEE80211_STA_DISABLE_11N;
sdata->local->oper_channel = req->bss->channel;
ieee80211_hw_config(sdata->local, 0);
if (req->ie && req->ie_len) { if (req->ie && req->ie_len) {
memcpy(wk->ie, req->ie, req->ie_len); memcpy(wk->ie, req->ie, req->ie_len);
@ -2533,11 +2506,16 @@ int ieee80211_mgd_assoc(struct ieee80211_sub_if_data *sdata,
} else } else
wk->ie_len = 0; wk->ie_len = 0;
wk->bss = (void *)req->bss;
ssid = ieee80211_bss_get_ie(req->bss, WLAN_EID_SSID);
memcpy(wk->ssid, ssid + 2, ssid[1]);
wk->ssid_len = ssid[1];
if (req->prev_bssid) if (req->prev_bssid)
memcpy(wk->prev_bssid, req->prev_bssid, ETH_ALEN); memcpy(wk->prev_bssid, req->prev_bssid, ETH_ALEN);
wk->state = IEEE80211_MGD_STATE_ASSOC; wk->state = IEEE80211_MGD_STATE_ASSOC;
wk->tries = 0;
wk->timeout = jiffies; /* run right away */ wk->timeout = jiffies; /* run right away */
if (req->use_mfp) { if (req->use_mfp) {
@ -2553,6 +2531,10 @@ int ieee80211_mgd_assoc(struct ieee80211_sub_if_data *sdata,
else else
ifmgd->flags &= ~IEEE80211_STA_CONTROL_PORT; ifmgd->flags &= ~IEEE80211_STA_CONTROL_PORT;
sdata->local->oper_channel = req->bss->channel;
ieee80211_hw_config(sdata->local, 0);
list_add(&wk->list, &ifmgd->work_list);
ieee80211_queue_work(&sdata->local->hw, &sdata->u.mgd.work); ieee80211_queue_work(&sdata->local->hw, &sdata->u.mgd.work);
err = 0; err = 0;
@ -2568,23 +2550,23 @@ int ieee80211_mgd_deauth(struct ieee80211_sub_if_data *sdata,
{ {
struct ieee80211_if_managed *ifmgd = &sdata->u.mgd; struct ieee80211_if_managed *ifmgd = &sdata->u.mgd;
struct ieee80211_mgd_work *wk; struct ieee80211_mgd_work *wk;
const u8 *bssid = NULL; const u8 *bssid = req->bss->bssid;
bool not_auth_yet = false; bool not_auth_yet = false;
mutex_lock(&ifmgd->mtx); mutex_lock(&ifmgd->mtx);
if (ifmgd->associated && &ifmgd->associated->cbss == req->bss) { if (ifmgd->associated && &ifmgd->associated->cbss == req->bss) {
bssid = req->bss->bssid; bssid = req->bss->bssid;
ieee80211_set_disassoc(sdata, true); ieee80211_set_disassoc(sdata);
} else list_for_each_entry(wk, &ifmgd->work_list, list) { } else list_for_each_entry(wk, &ifmgd->work_list, list) {
if (&wk->bss->cbss == req->bss) { if (wk->state != IEEE80211_MGD_STATE_PROBE)
bssid = req->bss->bssid; continue;
if (wk->state == IEEE80211_MGD_STATE_PROBE) if (req->bss != &wk->bss->cbss)
not_auth_yet = true; continue;
list_del(&wk->list); not_auth_yet = true;
kfree(wk); list_del(&wk->list);
break; kfree(wk);
} break;
} }
/* /*
@ -2601,17 +2583,6 @@ int ieee80211_mgd_deauth(struct ieee80211_sub_if_data *sdata,
return 0; return 0;
} }
/*
* cfg80211 should catch this ... but it's racy since
* we can receive a deauth frame, process it, hand it
* to cfg80211 while that's in a locked section already
* trying to tell us that the user wants to disconnect.
*/
if (!bssid) {
mutex_unlock(&ifmgd->mtx);
return -ENOLINK;
}
mutex_unlock(&ifmgd->mtx); mutex_unlock(&ifmgd->mtx);
printk(KERN_DEBUG "%s: deauthenticating from %pM by local choice (reason=%d)\n", printk(KERN_DEBUG "%s: deauthenticating from %pM by local choice (reason=%d)\n",
@ -2648,7 +2619,7 @@ int ieee80211_mgd_disassoc(struct ieee80211_sub_if_data *sdata,
printk(KERN_DEBUG "%s: disassociating from %pM by local choice (reason=%d)\n", printk(KERN_DEBUG "%s: disassociating from %pM by local choice (reason=%d)\n",
sdata->name, req->bss->bssid, req->reason_code); sdata->name, req->bss->bssid, req->reason_code);
ieee80211_set_disassoc(sdata, false); ieee80211_set_disassoc(sdata);
mutex_unlock(&ifmgd->mtx); mutex_unlock(&ifmgd->mtx);