blob: 60274d27c8d837510222b6f835c2a5bacc35b051 [file] [log] [blame]
/*
* Copyright (c) 2012-2018 The Linux Foundation. All rights reserved.
*
* Permission to use, copy, modify, and/or distribute this software for
* any purpose with or without fee is hereby granted, provided that the
* above copyright notice and this permission notice appear in all
* copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
* WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
* AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
* DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
* PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
* TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
* PERFORMANCE OF THIS SOFTWARE.
*/
/**
* DOC: wlan_hdd_sap_cond_chan_switch.c
*
* WLAN SAP conditional channel switch functions
*
*/
#include <wlan_hdd_includes.h>
#include <linux/netdevice.h>
#include <linux/skbuff.h>
#include <linux/etherdevice.h>
#include <linux/if_ether.h>
#include <cds_utils.h>
#include <qdf_str.h>
#include <wlan_hdd_sap_cond_chan_switch.h>
/**
* wlan_hdd_set_pre_cac_status() - Set the pre cac status
* @pre_cac_adapter: AP adapter used for pre cac
* @status: Status (true or false)
* @handle: Global handle
*
* Sets the status of pre cac i.e., whether the pre cac is active or not
*
* Return: Zero on success, non-zero on failure
*/
static int wlan_hdd_set_pre_cac_status(struct hdd_adapter *pre_cac_adapter,
bool status, mac_handle_t handle)
{
QDF_STATUS ret;
ret = wlan_sap_set_pre_cac_status(
WLAN_HDD_GET_SAP_CTX_PTR(pre_cac_adapter), status, handle);
if (QDF_IS_STATUS_ERROR(ret))
return -EINVAL;
return 0;
}
/**
* wlan_hdd_set_chan_before_pre_cac() - Save the channel before pre cac
* @ap_adapter: AP adapter
* @chan_before_pre_cac: Channel
*
* Saves the channel which the AP was beaconing on before moving to the pre
* cac channel. If radar is detected on the pre cac channel, this saved
* channel will be used for AP operations.
*
* Return: Zero on success, non-zero on failure
*/
static int wlan_hdd_set_chan_before_pre_cac(struct hdd_adapter *ap_adapter,
uint8_t chan_before_pre_cac)
{
QDF_STATUS ret;
ret = wlan_sap_set_chan_before_pre_cac(
WLAN_HDD_GET_SAP_CTX_PTR(ap_adapter), chan_before_pre_cac);
if (QDF_IS_STATUS_ERROR(ret))
return -EINVAL;
return 0;
}
/**
* wlan_hdd_validate_and_get_pre_cac_ch() - Validate and get pre cac channel
* @hdd_ctx: HDD context
* @ap_adapter: AP adapter
* @channel: Channel requested by userspace
* @pre_cac_chan: Pointer to the pre CAC channel
*
* Validates the channel provided by userspace. If user provided channel 0,
* a valid outdoor channel must be selected from the regulatory channel.
*
* Return: Zero on success and non zero value on error
*/
static int wlan_hdd_validate_and_get_pre_cac_ch(struct hdd_context *hdd_ctx,
struct hdd_adapter *ap_adapter,
uint8_t channel,
uint8_t *pre_cac_chan)
{
uint32_t i;
QDF_STATUS status;
uint32_t weight_len = 0;
uint32_t len = WNI_CFG_VALID_CHANNEL_LIST_LEN;
uint8_t channel_list[QDF_MAX_NUM_CHAN] = {0};
uint8_t pcl_weights[QDF_MAX_NUM_CHAN] = {0};
mac_handle_t mac_handle;
if (channel == 0) {
/* Channel is not obtained from PCL because PCL may not have
* the entire channel list. For example: if SAP is up on
* channel 6 and PCL is queried for the next SAP interface,
* if SCC is preferred, the PCL will contain only the channel
* 6. But, we are in need of a DFS channel. So, going with the
* first channel from the valid channel list.
*/
status = policy_mgr_get_valid_chans(hdd_ctx->hdd_psoc,
channel_list, &len);
if (QDF_IS_STATUS_ERROR(status)) {
hdd_err("Failed to get channel list");
return -EINVAL;
}
policy_mgr_update_with_safe_channel_list(hdd_ctx->hdd_psoc,
channel_list, &len,
pcl_weights,
weight_len);
for (i = 0; i < len; i++) {
if (wlan_reg_is_dfs_ch(hdd_ctx->hdd_pdev,
channel_list[i])) {
*pre_cac_chan = channel_list[i];
break;
}
}
if (*pre_cac_chan == 0) {
hdd_err("unable to find outdoor channel");
return -EINVAL;
}
} else {
/* Only when driver selects a channel, check is done for
* unnsafe and NOL channels. When user provides a fixed channel
* the user is expected to take care of this.
*/
mac_handle = hdd_ctx->mac_handle;
if (!sme_is_channel_valid(mac_handle, channel) ||
!wlan_reg_is_dfs_ch(hdd_ctx->hdd_pdev, channel)) {
hdd_err("Invalid channel for pre cac:%d", channel);
return -EINVAL;
}
*pre_cac_chan = channel;
}
hdd_debug("selected pre cac channel:%d", *pre_cac_chan);
return 0;
}
/**
* wlan_hdd_request_pre_cac() - Start pre CAC in the driver
* @channel: Channel option provided by userspace
*
* Sets the driver to the required hardware mode and start an adapter for
* pre CAC which will mimic an AP.
*
* Return: Zero on success, non-zero value on error
*/
int wlan_hdd_request_pre_cac(uint8_t channel)
{
uint8_t pre_cac_chan = 0, *mac_addr;
struct hdd_context *hdd_ctx;
int ret;
struct hdd_adapter *ap_adapter, *pre_cac_adapter;
struct hdd_ap_ctx *hdd_ap_ctx;
QDF_STATUS status;
struct wiphy *wiphy;
struct net_device *dev;
struct cfg80211_chan_def chandef;
enum nl80211_channel_type channel_type;
uint32_t freq;
struct ieee80211_channel *chan;
mac_handle_t mac_handle;
bool val;
hdd_ctx = cds_get_context(QDF_MODULE_ID_HDD);
if (wlan_hdd_validate_context(hdd_ctx) != 0)
return -EINVAL;
if (policy_mgr_get_connection_count(hdd_ctx->hdd_psoc) > 1) {
hdd_err("pre cac not allowed in concurrency");
return -EINVAL;
}
ap_adapter = hdd_get_adapter(hdd_ctx, QDF_SAP_MODE);
if (!ap_adapter) {
hdd_err("unable to get SAP adapter");
return -EINVAL;
}
mac_handle = hdd_ctx->mac_handle;
val = wlan_sap_is_pre_cac_active(mac_handle);
if (val) {
hdd_err("pre cac is already in progress");
return -EINVAL;
}
hdd_ap_ctx = WLAN_HDD_GET_AP_CTX_PTR(ap_adapter);
if (!hdd_ap_ctx) {
hdd_err("SAP context is NULL");
return -EINVAL;
}
if (wlan_reg_is_dfs_ch(hdd_ctx->hdd_pdev,
hdd_ap_ctx->operating_channel)) {
hdd_err("SAP is already on DFS channel:%d",
hdd_ap_ctx->operating_channel);
return -EINVAL;
}
if (!WLAN_REG_IS_24GHZ_CH(hdd_ap_ctx->operating_channel)) {
hdd_err("pre CAC alllowed only when SAP is in 2.4GHz:%d",
hdd_ap_ctx->operating_channel);
return -EINVAL;
}
mac_addr = wlan_hdd_get_intf_addr(hdd_ctx);
if (!mac_addr) {
hdd_err("can't add virtual intf: Not getting valid mac addr");
return -EINVAL;
}
hdd_debug("channel:%d", channel);
ret = wlan_hdd_validate_and_get_pre_cac_ch(hdd_ctx, ap_adapter, channel,
&pre_cac_chan);
if (ret != 0) {
hdd_err("can't validate pre-cac channel");
goto release_intf_addr_and_return_failure;
}
hdd_debug("starting pre cac SAP adapter");
/* Starting a SAP adapter:
* Instead of opening an adapter, we could just do a SME open session
* for AP type. But, start BSS would still need an adapter.
* So, this option is not taken.
*
* hdd open adapter is going to register this precac interface with
* user space. This interface though exposed to user space will be in
* DOWN state. Consideration was done to avoid this registration to the
* user space. But, as part of SAP operations multiple events are sent
* to user space. Some of these events received from unregistered
* interface was causing crashes. So, retaining the registration.
*
* So, this interface would remain registered and will remain in DOWN
* state for the CAC duration. We will add notes in the feature
* announcement to not use this temporary interface for any activity
* from user space.
*/
pre_cac_adapter = hdd_open_adapter(hdd_ctx, QDF_SAP_MODE, "precac%d",
mac_addr, NET_NAME_UNKNOWN, true);
if (!pre_cac_adapter) {
hdd_err("error opening the pre cac adapter");
goto release_intf_addr_and_return_failure;
}
/*
* This interface is internally created by the driver. So, no interface
* up comes for this interface from user space and hence starting
* the adapter internally.
*/
if (hdd_start_adapter(pre_cac_adapter)) {
hdd_err("error starting the pre cac adapter");
goto close_pre_cac_adapter;
}
hdd_debug("preparing for start ap/bss on the pre cac adapter");
wiphy = hdd_ctx->wiphy;
dev = pre_cac_adapter->dev;
/* Since this is only a dummy interface lets us use the IEs from the
* other active SAP interface. In regular scenarios, these IEs would
* come from the user space entity
*/
pre_cac_adapter->session.ap.beacon = qdf_mem_malloc(
sizeof(*ap_adapter->session.ap.beacon));
if (!pre_cac_adapter->session.ap.beacon) {
hdd_err("failed to alloc mem for beacon");
goto stop_close_pre_cac_adapter;
}
qdf_mem_copy(pre_cac_adapter->session.ap.beacon,
ap_adapter->session.ap.beacon,
sizeof(*pre_cac_adapter->session.ap.beacon));
pre_cac_adapter->session.ap.sap_config.ch_width_orig =
ap_adapter->session.ap.sap_config.ch_width_orig;
pre_cac_adapter->session.ap.sap_config.authType =
ap_adapter->session.ap.sap_config.authType;
/* Premise is that on moving from 2.4GHz to 5GHz, the SAP will continue
* to operate on the same bandwidth as that of the 2.4GHz operations.
* Only bandwidths 20MHz/40MHz are possible on 2.4GHz band.
*/
switch (ap_adapter->session.ap.sap_config.ch_width_orig) {
case CH_WIDTH_20MHZ:
channel_type = NL80211_CHAN_HT20;
break;
case CH_WIDTH_40MHZ:
if (ap_adapter->session.ap.sap_config.sec_ch >
ap_adapter->session.ap.sap_config.channel)
channel_type = NL80211_CHAN_HT40PLUS;
else
channel_type = NL80211_CHAN_HT40MINUS;
break;
default:
channel_type = NL80211_CHAN_NO_HT;
break;
}
freq = cds_chan_to_freq(pre_cac_chan);
chan = ieee80211_get_channel(wiphy, freq);
if (!chan) {
hdd_err("channel converion failed");
goto stop_close_pre_cac_adapter;
}
cfg80211_chandef_create(&chandef, chan, channel_type);
hdd_debug("orig width:%d channel_type:%d freq:%d",
ap_adapter->session.ap.sap_config.ch_width_orig,
channel_type, freq);
/*
* Doing update after opening and starting pre-cac adapter will make
* sure that driver won't do hardware mode change if there are any
* initial hick-ups or issues in pre-cac adapter's configuration.
* Since current SAP is in 2.4GHz and pre CAC channel is in 5GHz, this
* connection update should result in DBS mode
*/
status = policy_mgr_update_and_wait_for_connection_update(
hdd_ctx->hdd_psoc,
ap_adapter->session_id,
pre_cac_chan,
POLICY_MGR_UPDATE_REASON_PRE_CAC);
if (QDF_IS_STATUS_ERROR(status)) {
hdd_err("error in moving to DBS mode");
goto stop_close_pre_cac_adapter;
}
ret = wlan_hdd_set_channel(wiphy, dev, &chandef, channel_type);
if (ret != 0) {
hdd_err("failed to set channel");
goto stop_close_pre_cac_adapter;
}
status = wlan_hdd_cfg80211_start_bss(pre_cac_adapter, NULL,
PRE_CAC_SSID, qdf_str_len(PRE_CAC_SSID),
NL80211_HIDDEN_SSID_NOT_IN_USE, false);
if (QDF_IS_STATUS_ERROR(status)) {
hdd_err("start bss failed");
goto stop_close_pre_cac_adapter;
}
/*
* The pre cac status is set here. But, it would not be reset explicitly
* anywhere, since after the pre cac success/failure, the pre cac
* adapter itself would be removed.
*/
ret = wlan_hdd_set_pre_cac_status(pre_cac_adapter, true, mac_handle);
if (ret != 0) {
hdd_err("failed to set pre cac status");
goto stop_close_pre_cac_adapter;
}
ret = wlan_hdd_set_chan_before_pre_cac(ap_adapter,
hdd_ap_ctx->operating_channel);
if (ret != 0) {
hdd_err("failed to set channel before pre cac");
goto stop_close_pre_cac_adapter;
}
ap_adapter->pre_cac_chan = pre_cac_chan;
return 0;
stop_close_pre_cac_adapter:
hdd_stop_adapter(hdd_ctx, pre_cac_adapter);
qdf_mem_free(pre_cac_adapter->session.ap.beacon);
pre_cac_adapter->session.ap.beacon = NULL;
close_pre_cac_adapter:
hdd_close_adapter(hdd_ctx, pre_cac_adapter, false);
release_intf_addr_and_return_failure:
/*
* Release the interface address as the adapter
* failed to start, if you don't release then next
* adapter which is trying to come wouldn't get valid
* mac address. Remember we have limited pool of mac addresses
*/
wlan_hdd_release_intf_addr(hdd_ctx, mac_addr);
return -EINVAL;
}
/**
* __wlan_hdd_cfg80211_conditional_chan_switch() - Conditional channel switch
* @wiphy: Pointer to wireless phy
* @wdev: Pointer to wireless device
* @data: Pointer to data
* @data_len: Data length
*
* Processes the conditional channel switch request and invokes the helper
* APIs to process the channel switch request.
*
* Return: 0 on success, negative errno on failure
*/
static int
__wlan_hdd_cfg80211_conditional_chan_switch(struct wiphy *wiphy,
struct wireless_dev *wdev,
const void *data,
int data_len)
{
int ret;
struct hdd_context *hdd_ctx = wiphy_priv(wiphy);
struct net_device *dev = wdev->netdev;
struct hdd_adapter *adapter;
struct nlattr
*tb[QCA_WLAN_VENDOR_ATTR_SAP_CONDITIONAL_CHAN_SWITCH_MAX + 1];
uint32_t freq_len, i;
uint32_t *freq;
uint8_t chans[QDF_MAX_NUM_CHAN] = {0};
hdd_enter_dev(dev);
ret = wlan_hdd_validate_context(hdd_ctx);
if (ret)
return ret;
if (!hdd_ctx->config->enableDFSMasterCap) {
hdd_err("DFS master capability is not present in the driver");
return -EINVAL;
}
if (hdd_get_conparam() == QDF_GLOBAL_FTM_MODE) {
hdd_err("Command not allowed in FTM mode");
return -EPERM;
}
adapter = WLAN_HDD_GET_PRIV_PTR(dev);
if (adapter->device_mode != QDF_SAP_MODE) {
hdd_err("Invalid device mode %d", adapter->device_mode);
return -EINVAL;
}
/*
* audit note: it is ok to pass a NULL policy here since only
* one attribute is parsed which is array of frequencies and
* it is explicitly validated for both under read and over read
*/
if (wlan_cfg80211_nla_parse(tb,
QCA_WLAN_VENDOR_ATTR_SAP_CONDITIONAL_CHAN_SWITCH_MAX,
data, data_len, NULL)) {
hdd_err("Invalid ATTR");
return -EINVAL;
}
if (!tb[QCA_WLAN_VENDOR_ATTR_SAP_CONDITIONAL_CHAN_SWITCH_FREQ_LIST]) {
hdd_err("Frequency list is missing");
return -EINVAL;
}
freq_len = nla_len(
tb[QCA_WLAN_VENDOR_ATTR_SAP_CONDITIONAL_CHAN_SWITCH_FREQ_LIST])/
sizeof(uint32_t);
if (freq_len > QDF_MAX_NUM_CHAN) {
hdd_err("insufficient space to hold channels");
return -ENOMEM;
}
hdd_debug("freq_len=%d", freq_len);
freq = nla_data(
tb[QCA_WLAN_VENDOR_ATTR_SAP_CONDITIONAL_CHAN_SWITCH_FREQ_LIST]);
for (i = 0; i < freq_len; i++) {
if (freq[i] == 0)
chans[i] = 0;
else
chans[i] = ieee80211_frequency_to_channel(freq[i]);
hdd_debug("freq[%d]=%d", i, freq[i]);
}
/*
* The input frequency list from user space is designed to be a
* priority based frequency list. This is only to accommodate any
* future request. But, current requirement is only to perform CAC
* on a single channel. So, the first entry from the list is picked.
*
* If channel is zero, any channel in the available outdoor regulatory
* domain will be selected.
*/
ret = wlan_hdd_request_pre_cac(chans[0]);
if (ret) {
hdd_err("pre cac request failed with reason:%d", ret);
return ret;
}
return 0;
}
int wlan_hdd_cfg80211_conditional_chan_switch(struct wiphy *wiphy,
struct wireless_dev *wdev,
const void *data,
int data_len)
{
int ret;
cds_ssr_protect(__func__);
ret = __wlan_hdd_cfg80211_conditional_chan_switch(wiphy, wdev,
data, data_len);
cds_ssr_unprotect(__func__);
return ret;
}