From: Avinash Patil Date: Wed, 28 Jan 2015 10:24:24 +0000 (+0530) Subject: mwifiex: channel switch support for mwifiex X-Git-Url: https://git.karo-electronics.de/?a=commitdiff_plain;h=7d652034d1a08bd240c98727bbd55901a174c245;p=linux-beck.git mwifiex: channel switch support for mwifiex This patch adds cfg80211 channel_switch support for mwifiex. Upon receiving channel switch request, driver would parse channel switch announcement IE from beacon_data. If TX is blocked, netdev queues are stopped. IEs from csa_beacon are then parsed and set to FW. Signed-off-by: Avinash Patil Signed-off-by: Qingshui Gao Signed-off-by: Cathy Luo Signed-off-by: Kalle Valo --- diff --git a/drivers/net/wireless/mwifiex/11h.c b/drivers/net/wireless/mwifiex/11h.c index 08c12aece9ae..d794686e31d6 100644 --- a/drivers/net/wireless/mwifiex/11h.c +++ b/drivers/net/wireless/mwifiex/11h.c @@ -240,3 +240,40 @@ int mwifiex_11h_handle_radar_detected(struct mwifiex_private *priv, return 0; } + +/* This is work queue function for channel switch handling. + * This function takes care of updating new channel definitin to + * bss config structure, restart AP and indicate channel switch success + * to cfg80211. + */ +void mwifiex_dfs_chan_sw_work_queue(struct work_struct *work) +{ + struct mwifiex_uap_bss_param *bss_cfg; + struct delayed_work *delayed_work = + container_of(work, struct delayed_work, work); + struct mwifiex_private *priv = + container_of(delayed_work, struct mwifiex_private, + dfs_chan_sw_work); + + if (WARN_ON(!priv)) + return; + + bss_cfg = &priv->bss_cfg; + if (!bss_cfg->beacon_period) { + dev_err(priv->adapter->dev, + "channel switch: AP already stopped\n"); + return; + } + + mwifiex_uap_set_channel(bss_cfg, priv->dfs_chandef); + + if (mwifiex_config_start_uap(priv, bss_cfg)) { + dev_dbg(priv->adapter->dev, + "Failed to start AP after channel switch\n"); + return; + } + + dev_notice(priv->adapter->dev, + "indicating channel switch completion to kernel\n"); + cfg80211_ch_switch_notify(priv->netdev, &priv->dfs_chandef); +} diff --git a/drivers/net/wireless/mwifiex/cfg80211.c b/drivers/net/wireless/mwifiex/cfg80211.c index 7a969fd2f0cd..2d1ea938e08e 100644 --- a/drivers/net/wireless/mwifiex/cfg80211.c +++ b/drivers/net/wireless/mwifiex/cfg80211.c @@ -2399,7 +2399,7 @@ struct wireless_dev *mwifiex_add_virtual_intf(struct wiphy *wiphy, struct mwifiex_private *priv; struct net_device *dev; void *mdev_priv; - char dfs_cac_str[MWIFIEX_MAX_WQ_LEN]; + char dfs_cac_str[MWIFIEX_MAX_WQ_LEN], dfs_chsw_str[MWIFIEX_MAX_WQ_LEN]; if (!adapter) return ERR_PTR(-EFAULT); @@ -2582,6 +2582,24 @@ struct wireless_dev *mwifiex_add_virtual_intf(struct wiphy *wiphy, INIT_DELAYED_WORK(&priv->dfs_cac_work, mwifiex_dfs_cac_work_queue); + strcpy(dfs_chsw_str, "MWIFIEX_DFS_CHSW"); + strcat(dfs_chsw_str, name); + priv->dfs_chan_sw_workqueue = alloc_workqueue(dfs_chsw_str, + WQ_HIGHPRI | WQ_UNBOUND | + WQ_MEM_RECLAIM, 1); + if (!priv->dfs_chan_sw_workqueue) { + wiphy_err(wiphy, "cannot register virtual network device\n"); + free_netdev(dev); + priv->bss_mode = NL80211_IFTYPE_UNSPECIFIED; + priv->netdev = NULL; + memset(&priv->wdev, 0, sizeof(priv->wdev)); + priv->wdev.iftype = NL80211_IFTYPE_UNSPECIFIED; + return ERR_PTR(-ENOMEM); + } + + INIT_DELAYED_WORK(&priv->dfs_chan_sw_work, + mwifiex_dfs_chan_sw_work_queue); + sema_init(&priv->async_sem, 1); dev_dbg(adapter->dev, "info: %s: Marvell 802.11 Adapter\n", dev->name); @@ -2637,6 +2655,11 @@ int mwifiex_del_virtual_intf(struct wiphy *wiphy, struct wireless_dev *wdev) priv->dfs_cac_workqueue = NULL; } + if (priv->dfs_chan_sw_workqueue) { + flush_workqueue(priv->dfs_chan_sw_workqueue); + destroy_workqueue(priv->dfs_chan_sw_workqueue); + priv->dfs_chan_sw_workqueue = NULL; + } /* Clear the priv in adapter */ priv->netdev->ieee80211_ptr = NULL; priv->netdev = NULL; @@ -3115,6 +3138,62 @@ mwifiex_cfg80211_add_station(struct wiphy *wiphy, struct net_device *dev, return mwifiex_tdls_oper(priv, mac, MWIFIEX_TDLS_CREATE_LINK); } +static int +mwifiex_cfg80211_channel_switch(struct wiphy *wiphy, struct net_device *dev, + struct cfg80211_csa_settings *params) +{ + struct ieee_types_header *chsw_ie; + struct ieee80211_channel_sw_ie *channel_sw; + int chsw_msec; + struct mwifiex_private *priv = mwifiex_netdev_get_priv(dev); + + if (priv->adapter->scan_processing) { + dev_err(priv->adapter->dev, + "radar detection: scan in process...\n"); + return -EBUSY; + } + + if (priv->wdev.cac_started) + return -EBUSY; + + if (cfg80211_chandef_identical(¶ms->chandef, + &priv->dfs_chandef)) + return -EINVAL; + + chsw_ie = (void *)cfg80211_find_ie(WLAN_EID_CHANNEL_SWITCH, + params->beacon_csa.tail, + params->beacon_csa.tail_len); + if (!chsw_ie) { + dev_err(priv->adapter->dev, + "Could not parse channel switch announcement IE\n"); + return -EINVAL; + } + + channel_sw = (void *)(chsw_ie + 1); + if (channel_sw->mode) { + if (netif_carrier_ok(priv->netdev)) + netif_carrier_off(priv->netdev); + mwifiex_stop_net_dev_queue(priv->netdev, priv->adapter); + } + + if (mwifiex_del_mgmt_ies(priv)) + wiphy_err(wiphy, "Failed to delete mgmt IEs!\n"); + + if (mwifiex_set_mgmt_ies(priv, ¶ms->beacon_csa)) { + wiphy_err(wiphy, "%s: setting mgmt ies failed\n", __func__); + return -EFAULT; + } + + memcpy(&priv->dfs_chandef, ¶ms->chandef, sizeof(priv->dfs_chandef)); + memcpy(&priv->beacon_after, ¶ms->beacon_after, + sizeof(priv->beacon_after)); + + chsw_msec = max(channel_sw->count * priv->bss_cfg.beacon_period, 100); + queue_delayed_work(priv->dfs_chan_sw_workqueue, &priv->dfs_chan_sw_work, + msecs_to_jiffies(chsw_msec)); + return 0; +} + static int mwifiex_cfg80211_start_radar_detection(struct wiphy *wiphy, struct net_device *dev, @@ -3210,6 +3289,7 @@ static struct cfg80211_ops mwifiex_cfg80211_ops = { .add_station = mwifiex_cfg80211_add_station, .change_station = mwifiex_cfg80211_change_station, .start_radar_detection = mwifiex_cfg80211_start_radar_detection, + .channel_switch = mwifiex_cfg80211_channel_switch, }; #ifdef CONFIG_PM @@ -3313,7 +3393,8 @@ int mwifiex_register_cfg80211(struct mwifiex_adapter *adapter) wiphy->flags |= WIPHY_FLAG_HAVE_AP_SME | WIPHY_FLAG_AP_PROBE_RESP_OFFLOAD | WIPHY_FLAG_AP_UAPSD | - WIPHY_FLAG_HAS_REMAIN_ON_CHANNEL; + WIPHY_FLAG_HAS_REMAIN_ON_CHANNEL | + WIPHY_FLAG_HAS_CHANNEL_SWITCH; if (ISSUPP_TDLS_ENABLED(adapter->fw_cap_info)) wiphy->flags |= WIPHY_FLAG_SUPPORTS_TDLS | diff --git a/drivers/net/wireless/mwifiex/ie.c b/drivers/net/wireless/mwifiex/ie.c index a6af7b88bf05..f3b6ed249403 100644 --- a/drivers/net/wireless/mwifiex/ie.c +++ b/drivers/net/wireless/mwifiex/ie.c @@ -325,6 +325,7 @@ static int mwifiex_uap_set_head_tail_ies(struct mwifiex_private *priv, { struct mwifiex_ie *gen_ie; struct ieee_types_header *rsn_ie = NULL, *wpa_ie = NULL; + struct ieee_types_header *chsw_ie = NULL; u16 gen_idx = MWIFIEX_AUTO_IDX_MASK, ie_len = 0; const u8 *vendor_ie; @@ -356,9 +357,18 @@ static int mwifiex_uap_set_head_tail_ies(struct mwifiex_private *priv, ie_len += wpa_ie->len + 2; gen_ie->ie_length = cpu_to_le16(ie_len); } + + chsw_ie = (void *)cfg80211_find_ie(WLAN_EID_CHANNEL_SWITCH, + info->tail, info->tail_len); + if (chsw_ie) { + memcpy(gen_ie->ie_buffer + ie_len, + chsw_ie, chsw_ie->len + 2); + ie_len += chsw_ie->len + 2; + gen_ie->ie_length = cpu_to_le16(ie_len); + } } - if (rsn_ie || wpa_ie) { + if (rsn_ie || wpa_ie || chsw_ie) { if (mwifiex_update_uap_custom_ie(priv, gen_ie, &gen_idx, NULL, NULL, NULL, NULL)) { kfree(gen_ie); diff --git a/drivers/net/wireless/mwifiex/main.h b/drivers/net/wireless/mwifiex/main.h index ad9d679c3eed..599698c6b627 100644 --- a/drivers/net/wireless/mwifiex/main.h +++ b/drivers/net/wireless/mwifiex/main.h @@ -591,6 +591,10 @@ struct mwifiex_private { struct cfg80211_chan_def dfs_chandef; struct workqueue_struct *dfs_cac_workqueue; struct delayed_work dfs_cac_work; + struct timer_list dfs_chan_switch_timer; + struct workqueue_struct *dfs_chan_sw_workqueue; + struct delayed_work dfs_chan_sw_work; + struct cfg80211_beacon_data beacon_after; }; enum mwifiex_ba_status { @@ -1394,6 +1398,7 @@ struct sk_buff * mwifiex_clone_skb_for_tx_status(struct mwifiex_private *priv, struct sk_buff *skb, u8 flag, u64 *cookie); void mwifiex_dfs_cac_work_queue(struct work_struct *work); +void mwifiex_dfs_chan_sw_work_queue(struct work_struct *work); void mwifiex_abort_cac(struct mwifiex_private *priv); int mwifiex_11h_handle_radar_detected(struct mwifiex_private *priv, struct sk_buff *skb);