mirror of
https://git.kernel.org/pub/scm/network/wireless/iwd.git
synced 2024-12-22 21:22:37 +01:00
netdev: use wiphy radio work queue for connections
This adds connection/FT attempts to the radio work queue. This will ensure that connections aren't delayed or done concurrently with scanning.
This commit is contained in:
parent
f85fcf2f21
commit
4165d9414f
191
src/netdev.c
191
src/netdev.c
@ -136,6 +136,9 @@ struct netdev {
|
|||||||
|
|
||||||
struct l_io *pae_io; /* for drivers without EAPoL over NL80211 */
|
struct l_io *pae_io; /* for drivers without EAPoL over NL80211 */
|
||||||
|
|
||||||
|
struct l_genl_msg *connect_cmd;
|
||||||
|
struct wiphy_radio_work_item work;
|
||||||
|
|
||||||
bool connected : 1;
|
bool connected : 1;
|
||||||
bool operational : 1;
|
bool operational : 1;
|
||||||
bool rekey_offload_support : 1;
|
bool rekey_offload_support : 1;
|
||||||
@ -485,6 +488,9 @@ static void netdev_preauth_destroy(void *data)
|
|||||||
|
|
||||||
static void netdev_connect_free(struct netdev *netdev)
|
static void netdev_connect_free(struct netdev *netdev)
|
||||||
{
|
{
|
||||||
|
if (netdev->work.id)
|
||||||
|
wiphy_radio_work_done(netdev->wiphy, netdev->work.id);
|
||||||
|
|
||||||
if (netdev->sm) {
|
if (netdev->sm) {
|
||||||
eapol_sm_free(netdev->sm);
|
eapol_sm_free(netdev->sm);
|
||||||
netdev->sm = NULL;
|
netdev->sm = NULL;
|
||||||
@ -530,6 +536,11 @@ static void netdev_connect_free(struct netdev *netdev)
|
|||||||
netdev->ignore_connect_event = false;
|
netdev->ignore_connect_event = false;
|
||||||
netdev->expect_connect_failure = false;
|
netdev->expect_connect_failure = false;
|
||||||
|
|
||||||
|
if (netdev->connect_cmd) {
|
||||||
|
l_genl_msg_unref(netdev->connect_cmd);
|
||||||
|
netdev->connect_cmd = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
netdev_rssi_polling_update(netdev);
|
netdev_rssi_polling_update(netdev);
|
||||||
|
|
||||||
if (netdev->connect_cmd_id) {
|
if (netdev->connect_cmd_id) {
|
||||||
@ -1019,6 +1030,9 @@ static void netdev_connect_ok(struct netdev *netdev)
|
|||||||
}
|
}
|
||||||
|
|
||||||
netdev_rssi_polling_update(netdev);
|
netdev_rssi_polling_update(netdev);
|
||||||
|
|
||||||
|
if (netdev->work.id)
|
||||||
|
wiphy_radio_work_done(netdev->wiphy, netdev->work.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void netdev_setting_keys_failed(struct netdev_handshake_state *nhs,
|
static void netdev_setting_keys_failed(struct netdev_handshake_state *nhs,
|
||||||
@ -1487,6 +1501,9 @@ void netdev_handshake_failed(struct handshake_state *hs, uint16_t reason_code)
|
|||||||
if (!l_genl_family_send(nl80211, msg, NULL, NULL, NULL))
|
if (!l_genl_family_send(nl80211, msg, NULL, NULL, NULL))
|
||||||
l_error("error sending DEL_STATION");
|
l_error("error sending DEL_STATION");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (netdev->work.id)
|
||||||
|
wiphy_radio_work_done(netdev->wiphy, netdev->work.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void hardware_rekey_cb(struct l_genl_msg *msg, void *data)
|
static void hardware_rekey_cb(struct l_genl_msg *msg, void *data)
|
||||||
@ -2421,32 +2438,48 @@ static struct l_genl_msg *netdev_build_cmd_connect(struct netdev *netdev,
|
|||||||
|
|
||||||
struct rtnl_data {
|
struct rtnl_data {
|
||||||
struct netdev *netdev;
|
struct netdev *netdev;
|
||||||
struct l_genl_msg *cmd_connect;
|
|
||||||
uint8_t addr[ETH_ALEN];
|
uint8_t addr[ETH_ALEN];
|
||||||
int ref;
|
int ref;
|
||||||
};
|
};
|
||||||
|
|
||||||
static int netdev_begin_connection(struct netdev *netdev,
|
static int netdev_begin_connection(struct netdev *netdev)
|
||||||
struct l_genl_msg *cmd_connect)
|
|
||||||
{
|
{
|
||||||
if (cmd_connect) {
|
if (netdev->connect_cmd) {
|
||||||
netdev->connect_cmd_id = l_genl_family_send(nl80211,
|
netdev->connect_cmd_id = l_genl_family_send(nl80211,
|
||||||
cmd_connect, netdev_cmd_connect_cb,
|
netdev->connect_cmd,
|
||||||
netdev, NULL);
|
netdev_cmd_connect_cb, netdev,
|
||||||
|
NULL);
|
||||||
|
|
||||||
if (!netdev->connect_cmd_id) {
|
if (!netdev->connect_cmd_id)
|
||||||
l_genl_msg_unref(cmd_connect);
|
goto failed;
|
||||||
return -EIO;
|
|
||||||
}
|
netdev->connect_cmd = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Set the supplicant address now, this may have already been done for
|
||||||
|
* a non-randomized address connect, but if we are randomizing we need
|
||||||
|
* to set it again as the address should have now changed.
|
||||||
|
*/
|
||||||
handshake_state_set_supplicant_address(netdev->handshake, netdev->addr);
|
handshake_state_set_supplicant_address(netdev->handshake, netdev->addr);
|
||||||
|
|
||||||
/* set connected since the auth protocols cannot do so internally */
|
if (netdev->ap) {
|
||||||
if (netdev->ap && auth_proto_start(netdev->ap))
|
if (!auth_proto_start(netdev->ap))
|
||||||
|
goto failed;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* set connected since the auth protocols cannot do
|
||||||
|
* so internally
|
||||||
|
*/
|
||||||
netdev->connected = true;
|
netdev->connected = true;
|
||||||
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
|
failed:
|
||||||
|
netdev_connect_failed(netdev, NETDEV_RESULT_ABORTED,
|
||||||
|
MMPDU_STATUS_CODE_UNSPECIFIED);
|
||||||
|
return -EIO;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void netdev_mac_change_failed(struct netdev *netdev,
|
static void netdev_mac_change_failed(struct netdev *netdev,
|
||||||
@ -2461,8 +2494,6 @@ static void netdev_mac_change_failed(struct netdev *netdev,
|
|||||||
* mac_change_cmd_id was set.
|
* mac_change_cmd_id was set.
|
||||||
*/
|
*/
|
||||||
if (!netdev_get_is_up(netdev)) {
|
if (!netdev_get_is_up(netdev)) {
|
||||||
l_genl_msg_unref(req->cmd_connect);
|
|
||||||
|
|
||||||
WATCHLIST_NOTIFY(&netdev_watches, netdev_watch_func_t,
|
WATCHLIST_NOTIFY(&netdev_watches, netdev_watch_func_t,
|
||||||
netdev, NETDEV_WATCH_EVENT_DOWN);
|
netdev, NETDEV_WATCH_EVENT_DOWN);
|
||||||
|
|
||||||
@ -2474,7 +2505,7 @@ static void netdev_mac_change_failed(struct netdev *netdev,
|
|||||||
*/
|
*/
|
||||||
l_info("Interface still up after failing to change the MAC, "
|
l_info("Interface still up after failing to change the MAC, "
|
||||||
"continuing with connection");
|
"continuing with connection");
|
||||||
if (netdev_begin_connection(netdev, req->cmd_connect) < 0)
|
if (netdev_begin_connection(netdev) < 0)
|
||||||
goto failed;
|
goto failed;
|
||||||
|
|
||||||
return;
|
return;
|
||||||
@ -2517,7 +2548,7 @@ static void netdev_mac_power_up_cb(int error, uint16_t type,
|
|||||||
/*
|
/*
|
||||||
* Pick up where we left off in netdev_connect_commmon.
|
* Pick up where we left off in netdev_connect_commmon.
|
||||||
*/
|
*/
|
||||||
if (netdev_begin_connection(netdev, req->cmd_connect) < 0) {
|
if (netdev_begin_connection(netdev) < 0) {
|
||||||
l_error("Failed to connect after changing MAC");
|
l_error("Failed to connect after changing MAC");
|
||||||
netdev_connect_failed(netdev, NETDEV_RESULT_ASSOCIATION_FAILED,
|
netdev_connect_failed(netdev, NETDEV_RESULT_ASSOCIATION_FAILED,
|
||||||
MMPDU_STATUS_CODE_UNSPECIFIED);
|
MMPDU_STATUS_CODE_UNSPECIFIED);
|
||||||
@ -2576,9 +2607,7 @@ static void netdev_mac_power_down_cb(int error, uint16_t type,
|
|||||||
* Returns -EALREADY if the requested MAC matched our current MAC
|
* Returns -EALREADY if the requested MAC matched our current MAC
|
||||||
* Returns -EIO if there was an IO error when powering down
|
* Returns -EIO if there was an IO error when powering down
|
||||||
*/
|
*/
|
||||||
static int netdev_start_powered_mac_change(struct netdev *netdev,
|
static int netdev_start_powered_mac_change(struct netdev *netdev)
|
||||||
struct scan_bss *bss,
|
|
||||||
struct l_genl_msg *cmd_connect)
|
|
||||||
{
|
{
|
||||||
struct rtnl_data *req;
|
struct rtnl_data *req;
|
||||||
uint8_t new_addr[6];
|
uint8_t new_addr[6];
|
||||||
@ -2586,8 +2615,8 @@ static int netdev_start_powered_mac_change(struct netdev *netdev,
|
|||||||
/* No address set in handshake, use per-network MAC generation */
|
/* No address set in handshake, use per-network MAC generation */
|
||||||
if (util_mem_is_zero(netdev->handshake->spa, ETH_ALEN))
|
if (util_mem_is_zero(netdev->handshake->spa, ETH_ALEN))
|
||||||
wiphy_generate_address_from_ssid(netdev->wiphy,
|
wiphy_generate_address_from_ssid(netdev->wiphy,
|
||||||
(const char *)bss->ssid,
|
(const char *)netdev->handshake->ssid,
|
||||||
new_addr);
|
new_addr);
|
||||||
else
|
else
|
||||||
memcpy(new_addr, netdev->handshake->spa, ETH_ALEN);
|
memcpy(new_addr, netdev->handshake->spa, ETH_ALEN);
|
||||||
|
|
||||||
@ -2600,7 +2629,6 @@ static int netdev_start_powered_mac_change(struct netdev *netdev,
|
|||||||
req = l_new(struct rtnl_data, 1);
|
req = l_new(struct rtnl_data, 1);
|
||||||
req->netdev = netdev;
|
req->netdev = netdev;
|
||||||
/* This message will need to be unreffed upon any error */
|
/* This message will need to be unreffed upon any error */
|
||||||
req->cmd_connect = cmd_connect;
|
|
||||||
req->ref++;
|
req->ref++;
|
||||||
memcpy(req->addr, new_addr, sizeof(req->addr));
|
memcpy(req->addr, new_addr, sizeof(req->addr));
|
||||||
|
|
||||||
@ -2609,7 +2637,6 @@ static int netdev_start_powered_mac_change(struct netdev *netdev,
|
|||||||
req, netdev_mac_destroy);
|
req, netdev_mac_destroy);
|
||||||
|
|
||||||
if (!netdev->mac_change_cmd_id) {
|
if (!netdev->mac_change_cmd_id) {
|
||||||
l_genl_msg_unref(req->cmd_connect);
|
|
||||||
l_free(req);
|
l_free(req);
|
||||||
|
|
||||||
return -EIO;
|
return -EIO;
|
||||||
@ -2618,6 +2645,35 @@ static int netdev_start_powered_mac_change(struct netdev *netdev,
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool netdev_connection_work_ready(struct wiphy_radio_work_item *item)
|
||||||
|
{
|
||||||
|
struct netdev *netdev = l_container_of(item, struct netdev, work);
|
||||||
|
|
||||||
|
if (mac_per_ssid) {
|
||||||
|
int ret = netdev_start_powered_mac_change(netdev);
|
||||||
|
|
||||||
|
if (!ret)
|
||||||
|
return false;
|
||||||
|
else if (ret != -EALREADY)
|
||||||
|
goto failed;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (netdev_begin_connection(netdev) < 0)
|
||||||
|
goto failed;
|
||||||
|
|
||||||
|
return false;
|
||||||
|
|
||||||
|
failed:
|
||||||
|
netdev_connect_failed(netdev, NETDEV_RESULT_ABORTED,
|
||||||
|
MMPDU_STATUS_CODE_UNSPECIFIED);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct wiphy_radio_work_item_ops connect_work_ops = {
|
||||||
|
.do_work = netdev_connection_work_ready,
|
||||||
|
};
|
||||||
|
|
||||||
static int netdev_connect_common(struct netdev *netdev,
|
static int netdev_connect_common(struct netdev *netdev,
|
||||||
struct l_genl_msg *cmd_connect,
|
struct l_genl_msg *cmd_connect,
|
||||||
struct scan_bss *bss,
|
struct scan_bss *bss,
|
||||||
@ -2626,6 +2682,7 @@ static int netdev_connect_common(struct netdev *netdev,
|
|||||||
netdev_event_func_t event_filter,
|
netdev_event_func_t event_filter,
|
||||||
netdev_connect_cb_t cb, void *user_data)
|
netdev_connect_cb_t cb, void *user_data)
|
||||||
{
|
{
|
||||||
|
netdev->connect_cmd = cmd_connect;
|
||||||
netdev->event_filter = event_filter;
|
netdev->event_filter = event_filter;
|
||||||
netdev->connect_cb = cb;
|
netdev->connect_cb = cb;
|
||||||
netdev->user_data = user_data;
|
netdev->user_data = user_data;
|
||||||
@ -2642,14 +2699,9 @@ static int netdev_connect_common(struct netdev *netdev,
|
|||||||
NL80211_EXT_FEATURE_CAN_REPLACE_PTK0))
|
NL80211_EXT_FEATURE_CAN_REPLACE_PTK0))
|
||||||
handshake_state_set_no_rekey(hs, true);
|
handshake_state_set_no_rekey(hs, true);
|
||||||
|
|
||||||
if (mac_per_ssid) {
|
wiphy_radio_work_insert(netdev->wiphy, &netdev->work, 1,
|
||||||
int ret = netdev_start_powered_mac_change(netdev, bss,
|
&connect_work_ops);
|
||||||
cmd_connect);
|
return 0;
|
||||||
if (ret != -EALREADY)
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
return netdev_begin_connection(netdev, cmd_connect);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int netdev_connect(struct netdev *netdev, struct scan_bss *bss,
|
int netdev_connect(struct netdev *netdev, struct scan_bss *bss,
|
||||||
@ -2667,7 +2719,7 @@ int netdev_connect(struct netdev *netdev, struct scan_bss *bss,
|
|||||||
netdev->type != NL80211_IFTYPE_P2P_CLIENT)
|
netdev->type != NL80211_IFTYPE_P2P_CLIENT)
|
||||||
return -ENOTSUP;
|
return -ENOTSUP;
|
||||||
|
|
||||||
if (netdev->connected || netdev->connect_cmd_id)
|
if (netdev->connected || netdev->connect_cmd_id || netdev->work.id)
|
||||||
return -EISCONN;
|
return -EISCONN;
|
||||||
|
|
||||||
switch (hs->akm_suite) {
|
switch (hs->akm_suite) {
|
||||||
@ -2709,23 +2761,36 @@ int netdev_disconnect(struct netdev *netdev,
|
|||||||
netdev_disconnect_cb_t cb, void *user_data)
|
netdev_disconnect_cb_t cb, void *user_data)
|
||||||
{
|
{
|
||||||
struct l_genl_msg *disconnect;
|
struct l_genl_msg *disconnect;
|
||||||
|
bool send_disconnect = true;
|
||||||
|
|
||||||
if (netdev->type != NL80211_IFTYPE_STATION &&
|
if (netdev->type != NL80211_IFTYPE_STATION &&
|
||||||
netdev->type != NL80211_IFTYPE_P2P_CLIENT)
|
netdev->type != NL80211_IFTYPE_P2P_CLIENT)
|
||||||
return -ENOTSUP;
|
return -ENOTSUP;
|
||||||
|
|
||||||
if (!netdev->connected)
|
|
||||||
return -ENOTCONN;
|
|
||||||
|
|
||||||
if (netdev->disconnect_cmd_id)
|
if (netdev->disconnect_cmd_id)
|
||||||
return -EINPROGRESS;
|
return -EINPROGRESS;
|
||||||
|
|
||||||
/* Only perform this if we haven't successfully fully associated yet */
|
/* Only perform this if we haven't successfully fully associated yet */
|
||||||
if (!netdev->operational) {
|
if (!netdev->operational) {
|
||||||
|
/*
|
||||||
|
* Three possibilities here:
|
||||||
|
* 1. We do not actually have a connect in progress (work.id
|
||||||
|
* is zero), then we can bail out early with an error.
|
||||||
|
* 2. We have sent CMD_CONNECT but not fully connected. The
|
||||||
|
* CMD_CONNECT needs to be canceled and a disconnect should
|
||||||
|
* be sent.
|
||||||
|
* 3. Queued up the connect work, but haven't sent CMD_CONNECT
|
||||||
|
* to the kernel. This case we do not need to send a
|
||||||
|
* disconnect.
|
||||||
|
*/
|
||||||
|
if (!netdev->work.id)
|
||||||
|
return -ENOTCONN;
|
||||||
|
|
||||||
if (netdev->connect_cmd_id) {
|
if (netdev->connect_cmd_id) {
|
||||||
l_genl_family_cancel(nl80211, netdev->connect_cmd_id);
|
l_genl_family_cancel(nl80211, netdev->connect_cmd_id);
|
||||||
netdev->connect_cmd_id = 0;
|
netdev->connect_cmd_id = 0;
|
||||||
}
|
} else
|
||||||
|
send_disconnect = false;
|
||||||
|
|
||||||
netdev_connect_failed(netdev, NETDEV_RESULT_ABORTED,
|
netdev_connect_failed(netdev, NETDEV_RESULT_ABORTED,
|
||||||
MMPDU_REASON_CODE_UNSPECIFIED);
|
MMPDU_REASON_CODE_UNSPECIFIED);
|
||||||
@ -2733,19 +2798,23 @@ int netdev_disconnect(struct netdev *netdev,
|
|||||||
netdev_connect_free(netdev);
|
netdev_connect_free(netdev);
|
||||||
}
|
}
|
||||||
|
|
||||||
disconnect = netdev_build_cmd_disconnect(netdev,
|
if (send_disconnect) {
|
||||||
|
disconnect = netdev_build_cmd_disconnect(netdev,
|
||||||
MMPDU_REASON_CODE_DEAUTH_LEAVING);
|
MMPDU_REASON_CODE_DEAUTH_LEAVING);
|
||||||
netdev->disconnect_cmd_id = l_genl_family_send(nl80211, disconnect,
|
netdev->disconnect_cmd_id = l_genl_family_send(nl80211,
|
||||||
netdev_cmd_disconnect_cb, netdev, NULL);
|
disconnect, netdev_cmd_disconnect_cb,
|
||||||
|
netdev, NULL);
|
||||||
|
|
||||||
if (!netdev->disconnect_cmd_id) {
|
if (!netdev->disconnect_cmd_id) {
|
||||||
l_genl_msg_unref(disconnect);
|
l_genl_msg_unref(disconnect);
|
||||||
return -EIO;
|
return -EIO;
|
||||||
}
|
}
|
||||||
|
|
||||||
netdev->disconnect_cb = cb;
|
netdev->disconnect_cb = cb;
|
||||||
netdev->user_data = user_data;
|
netdev->user_data = user_data;
|
||||||
netdev->aborting = true;
|
netdev->aborting = true;
|
||||||
|
} else if (cb)
|
||||||
|
cb(netdev, true, user_data);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@ -3087,12 +3156,31 @@ static void netdev_ft_over_ds_tx_authenticate(struct iovec *iov,
|
|||||||
netdev_ft_request_cb);
|
netdev_ft_request_cb);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool netdev_ft_work_ready(struct wiphy_radio_work_item *item)
|
||||||
|
{
|
||||||
|
struct netdev *netdev = l_container_of(item, struct netdev, work);
|
||||||
|
|
||||||
|
if (!auth_proto_start(netdev->ap)) {
|
||||||
|
/* Restore original nonce */
|
||||||
|
memcpy(netdev->handshake->snonce, netdev->prev_snonce, 32);
|
||||||
|
|
||||||
|
netdev_connect_failed(netdev, NETDEV_RESULT_ABORTED,
|
||||||
|
MMPDU_STATUS_CODE_UNSPECIFIED);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct wiphy_radio_work_item_ops ft_work_ops = {
|
||||||
|
.do_work = netdev_ft_work_ready,
|
||||||
|
};
|
||||||
|
|
||||||
static int fast_transition(struct netdev *netdev, struct scan_bss *target_bss,
|
static int fast_transition(struct netdev *netdev, struct scan_bss *target_bss,
|
||||||
bool over_air,
|
bool over_air,
|
||||||
netdev_connect_cb_t cb)
|
netdev_connect_cb_t cb)
|
||||||
{
|
{
|
||||||
struct netdev_handshake_state *nhs;
|
struct netdev_handshake_state *nhs;
|
||||||
int err = -EINVAL;
|
|
||||||
|
|
||||||
if (!netdev->operational)
|
if (!netdev->operational)
|
||||||
return -ENOTCONN;
|
return -ENOTCONN;
|
||||||
@ -3173,15 +3261,10 @@ static int fast_transition(struct netdev *netdev, struct scan_bss *target_bss,
|
|||||||
netdev_ft_over_ds_tx_authenticate,
|
netdev_ft_over_ds_tx_authenticate,
|
||||||
netdev_ft_tx_associate, netdev);
|
netdev_ft_tx_associate, netdev);
|
||||||
|
|
||||||
if (!auth_proto_start(netdev->ap))
|
wiphy_radio_work_insert(netdev->wiphy, &netdev->work, 1,
|
||||||
goto restore_snonce;
|
&ft_work_ops);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
restore_snonce:
|
|
||||||
memcpy(netdev->handshake->snonce, netdev->prev_snonce, 32);
|
|
||||||
|
|
||||||
return err;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int netdev_fast_transition(struct netdev *netdev, struct scan_bss *target_bss,
|
int netdev_fast_transition(struct netdev *netdev, struct scan_bss *target_bss,
|
||||||
|
@ -2702,10 +2702,6 @@ int station_disconnect(struct station *station)
|
|||||||
if (!station->connected_bss)
|
if (!station->connected_bss)
|
||||||
return -ENOTCONN;
|
return -ENOTCONN;
|
||||||
|
|
||||||
if (netdev_disconnect(station->netdev,
|
|
||||||
station_disconnect_cb, station) < 0)
|
|
||||||
return -EIO;
|
|
||||||
|
|
||||||
if (station->netconfig)
|
if (station->netconfig)
|
||||||
netconfig_reset(station->netconfig);
|
netconfig_reset(station->netconfig);
|
||||||
|
|
||||||
@ -2718,6 +2714,10 @@ int station_disconnect(struct station *station)
|
|||||||
|
|
||||||
station_enter_state(station, STATION_STATE_DISCONNECTING);
|
station_enter_state(station, STATION_STATE_DISCONNECTING);
|
||||||
|
|
||||||
|
if (netdev_disconnect(station->netdev,
|
||||||
|
station_disconnect_cb, station) < 0)
|
||||||
|
return -EIO;
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user