blob: 2f971cac6dc6e053ef62f87e8f04ae49069926fa [file] [log] [blame]
/*-
* Copyright (c) 2002-2005 Sam Leffler, Errno Consulting
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer,
* without modification.
* 2. Redistributions in binary form must reproduce at minimum a disclaimer
* similar to the "NO WARRANTY" disclaimer below ("Disclaimer") and any
* redistribution must be conditioned upon including a substantially
* similar Disclaimer requirement for further binary redistribution.
* 3. Neither the names of the above-listed copyright holders nor the names
* of any contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* Alternatively, this software may be distributed under the terms of the
* GNU General Public License ("GPL") version 2 as published by the Free
* Software Foundation.
*
* NO WARRANTY
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF NONINFRINGEMENT, MERCHANTIBILITY
* AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
* THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR SPECIAL, EXEMPLARY,
* OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
* IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGES.
*
* $Id: ieee80211_wireless.c 2614 2007-07-26 12:58:47Z mrenzmann $
*/
/*
* Wireless extensions support for 802.11 common code.
*/
#ifndef AUTOCONF_INCLUDED
#include <linux/config.h>
#endif
#include <linux/version.h>
#include <linux/module.h>
#include <linux/netdevice.h>
#include <linux/utsname.h>
#include <linux/if_arp.h> /* XXX for ARPHRD_ETHER */
#include <linux/delay.h>
#include <linux/random.h>
#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 7, 0)
#include <linux/watch64.h>
#endif
#include <qtn/muc_phy_stats.h>
#include <qtn/shared_defs.h>
#include <qtn/skb_recycle.h>
#include <qtn/lhost_muc_comm.h>
#include <qtn/qtn_global.h>
#include <linux/wireless.h>
#include <net/iw_handler.h>
#include <linux/seq_file.h>
#include <linux/jiffies.h>
#include <linux/math64.h>
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 7, 0)
#include <linux/pm_qos.h>
#else
#include <linux/pm_qos_params.h>
#include <asm/uaccess.h>
#endif
#include "net80211/if_media.h"
#include "net80211/ieee80211_var.h"
#include "net80211/ieee80211_linux.h"
#include "net80211/_ieee80211.h"
#include "qdrv_sch_const.h"
#include "net80211/ieee80211_tpc.h"
#include "net80211/ieee80211_tdls.h"
#include "net80211/ieee80211_mlme_statistics.h"
#include "qtn_logging.h"
#include <qdrv/qdrv_vap.h>
#include <qtn/qtn_debug.h>
#include <qtn/shared_params.h>
#include <qtn/qtn_bb_mutex.h>
#include <qtn/qtn_vlan.h>
#include <linux/net/bridge/br_public.h>
#include "qtn/wlan_ioctl.h"
#include "soc.h"
#include <qdrv/qdrv_mac.h>
#include <qtn/txbf_mbox.h>
#include <qtn/topaz_tqe_cpuif.h>
#include <qtn/hardware_revision.h>
#if defined(CONFIG_QTN_BSA_SUPPORT)
#include "net80211/ieee80211_bsa.h"
#endif
#include <asm/board/pm.h>
#define IS_UP(_dev) \
(((_dev)->flags & (IFF_RUNNING|IFF_UP)) == (IFF_RUNNING|IFF_UP))
#define IS_UP_AUTO(_vap) \
(IS_UP((_vap)->iv_dev) && \
(_vap)->iv_ic->ic_roaming == IEEE80211_ROAMING_AUTO)
#define RESCAN 1
#define DBGMAC "%02X:%02X:%02X:%02X:%02X:%02X"
#define ETHERFMT(a) \
(a)[0], (a)[1], (a)[2], (a)[3], (a)[4], (a)[5]
#define STRUCT_MEMBER_SIZEOF(stype, member) sizeof(((stype *)0)->member)
#define RSSI_SWING_RANGE 30
#define RSSI_MAX_SMTHING_FCTR 100
#define RSSI_HIGH_SMTHING_FCTR 99
#define RSSI_MED_SMTHING_FCTR 95
#define QTN_AMPDU_DETECT_PERIOD 1
#define QTN_RSSI_SAMPLE_TH 4
#define SCS_CHAN_POWER_DIFF_SAFE 2
#define SCS_CHAN_POWER_DIFF_MAX 16
#define SCS_MAX_RAW_CHAN_METRIC 0x7FFFFFFF
#define SCS_MAX_RATE_RATIO_CAP 0x7FFFFFFF
#define SCS_PICK_CHAN_MIN_SCALED_TRAFFIC 100 /* ms */
#define MIN_CAC_PERIOD 70 /* seconds */
#define OCAC_MAX_SUPPORTED_VAPS 2
#define DFS_S_DBG_QEVT(qevtdev, ...) do {\
printk(__VA_ARGS__);\
ieee80211_eventf(qevtdev, __VA_ARGS__);\
} while (0)
#define IEEE80211_OBSS_AP_SCAN_INT 25
int wlan_11ac_20M_mcs_nss_tbl[] = {
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, -1,
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, -1,
0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29,
0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, -1
};
#define DM_DEFAULT_TX_POWER_FACTOR 2
#define DM_DEFAULT_DFS_FACTOR 8
#if defined(CONFIG_QTN_80211K_SUPPORT)
const uint8_t ieee80211_meas_sta_qtn_report_subtype_len[RM_QTN_CTRL_END + 1] = {
[RM_QTN_TX_STATS] = sizeof(struct ieee80211_ie_qtn_rm_txstats),
[RM_QTN_RX_STATS] = sizeof(struct ieee80211_ie_qtn_rm_rxstats),
[RM_QTN_MAX_QUEUED] = STRUCT_MEMBER_SIZEOF(struct ieee80211_ie_qtn_rm_sta_all, max_queued),
[RM_QTN_LINK_QUALITY] = STRUCT_MEMBER_SIZEOF(struct ieee80211_ie_qtn_rm_sta_all, link_quality),
[RM_QTN_RSSI_DBM] = STRUCT_MEMBER_SIZEOF(struct ieee80211_ie_qtn_rm_sta_all, rssi_dbm),
[RM_QTN_BANDWIDTH] = STRUCT_MEMBER_SIZEOF(struct ieee80211_ie_qtn_rm_sta_all, bandwidth),
[RM_QTN_SNR] = STRUCT_MEMBER_SIZEOF(struct ieee80211_ie_qtn_rm_sta_all, snr),
[RM_QTN_TX_PHY_RATE] = STRUCT_MEMBER_SIZEOF(struct ieee80211_ie_qtn_rm_sta_all, tx_phy_rate),
[RM_QTN_RX_PHY_RATE] = STRUCT_MEMBER_SIZEOF(struct ieee80211_ie_qtn_rm_sta_all, rx_phy_rate),
[RM_QTN_CCA] = STRUCT_MEMBER_SIZEOF(struct ieee80211_ie_qtn_rm_sta_all, cca),
[RM_QTN_BR_IP] = STRUCT_MEMBER_SIZEOF(struct ieee80211_ie_qtn_rm_sta_all, br_ip),
[RM_QTN_RSSI] = STRUCT_MEMBER_SIZEOF(struct ieee80211_ie_qtn_rm_sta_all, rssi),
[RM_QTN_HW_NOISE] = STRUCT_MEMBER_SIZEOF(struct ieee80211_ie_qtn_rm_sta_all, hw_noise),
[RM_QTN_SOC_MACADDR] = STRUCT_MEMBER_SIZEOF(struct ieee80211_ie_qtn_rm_sta_all, soc_macaddr),
[RM_QTN_SOC_IPADDR] = STRUCT_MEMBER_SIZEOF(struct ieee80211_ie_qtn_rm_sta_all, soc_ipaddr),
[RM_QTN_UNKNOWN] = sizeof(u_int32_t),
[RM_QTN_RESET_CNTS] = sizeof(u_int32_t),
[RM_QTN_RESET_QUEUED] = sizeof(u_int32_t),
};
#endif
struct assoc_info_report {
uint64_t ai_rx_bytes;
uint64_t ai_tx_bytes;
uint32_t ai_rx_packets;
uint32_t ai_tx_packets;
uint32_t ai_rx_errors;
uint32_t ai_tx_errors;
uint32_t ai_rx_dropped;
uint32_t ai_tx_dropped;
uint32_t ai_tx_wifi_sent[WMM_AC_NUM];
uint32_t ai_tx_wifi_drop[WME_AC_NUM];
uint32_t ai_tx_ucast;
uint32_t ai_rx_ucast;
uint32_t ai_tx_mcast;
uint32_t ai_rx_mcast;
uint32_t ai_tx_bcast;
uint32_t ai_rx_bcast;
uint32_t ai_tx_failed;
uint32_t ai_time_associated; /*Unit: seconds*/
uint16_t ai_assoc_id;
uint16_t ai_link_quality;
uint16_t ai_tx_phy_rate;
uint16_t ai_rx_phy_rate;
uint32_t ai_achievable_tx_phy_rate;
uint32_t ai_achievable_rx_phy_rate;
u_int32_t ai_rx_fragment_pkts;
u_int32_t ai_rx_vlan_pkts;
uint8_t ai_mac_addr[IEEE80211_ADDR_LEN];
int32_t ai_rssi;
int32_t ai_smthd_rssi;
int32_t ai_snr;
int32_t ai_max_queued;
uint8_t ai_bw;
uint8_t ai_tx_mcs;
uint8_t ai_rx_mcs;
uint8_t ai_auth;
char ai_ifname[IFNAMSIZ];
uint32_t ai_ip_addr;
int32_t ai_hw_noise;
uint32_t ai_is_qtn_node;
};
struct assoc_info_table {
uint16_t unit_size; /* Size of structure assoc_info_table */
uint16_t cnt; /* Record the number of valid entries */
struct assoc_info_report array[QTN_ASSOC_LIMIT];
};
struct sample_assoc_data {
uint8_t mac_addr[IEEE80211_ADDR_LEN];
uint8_t assoc_id;
uint8_t bw;
uint8_t tx_stream;
uint8_t rx_stream;
uint32_t time_associated; /*Unit: seconds*/
uint32_t achievable_tx_phy_rate;
uint32_t achievable_rx_phy_rate;
uint32_t rx_packets;
uint32_t tx_packets;
uint32_t rx_errors;
uint32_t tx_errors;
uint32_t rx_dropped;
uint32_t tx_dropped;
uint32_t tx_wifi_drop[WME_AC_NUM];
uint32_t rx_ucast;
uint32_t tx_ucast;
uint32_t rx_mcast;
uint32_t tx_mcast;
uint32_t rx_bcast;
uint32_t tx_bcast;
uint16_t link_quality;
uint32_t ip_addr;
uint64_t rx_bytes;
uint64_t tx_bytes;
uint32_t last_rssi_dbm[NUM_ANT + 1];
uint32_t last_rcpi_dbm[NUM_ANT + 1];
uint32_t last_evm_dbm[NUM_ANT + 1];
uint32_t last_hw_noise[NUM_ANT + 1];
uint8_t protocol;
uint8_t vendor;
}__packed;
struct sample_assoc_user_data {
int num_entry;
int offset;
struct sample_assoc_data *data;
};
struct node_client_data {
struct list_head node_list;
struct sample_assoc_data data;
};
struct scs_chan_intf_params {
struct ieee80211_channel *chan;
uint32_t chan_bw;
uint32_t cca_intf;
uint32_t pmbl_err;
uint32_t cca_dur;
uint32_t cca_pri;
uint32_t cca_sec;
uint32_t cca_sec40;
};
#ifdef WLAN_MALLOC_FREE_TOT_DEBUG
int g_wlan_tot_alloc = 0;
int g_wlan_tot_alloc_cnt = 0;
int g_wlan_tot_free = 0;
int g_wlan_tot_free_cnt = 0;
int g_wlan_balance = 0;
int g_wlan_tot_node_alloc = 0;
int g_wlan_tot_node_alloc_tmp = 0;
int g_wlan_tot_node_free = 0;
int g_wlan_tot_node_free_tmp = 0;
EXPORT_SYMBOL(g_wlan_tot_alloc);
EXPORT_SYMBOL(g_wlan_tot_alloc_cnt);
EXPORT_SYMBOL(g_wlan_tot_free);
EXPORT_SYMBOL(g_wlan_tot_free_cnt);
EXPORT_SYMBOL(g_wlan_balance);
EXPORT_SYMBOL(g_wlan_tot_node_alloc);
EXPORT_SYMBOL(g_wlan_tot_node_alloc_tmp);
EXPORT_SYMBOL(g_wlan_tot_node_free);
EXPORT_SYMBOL(g_wlan_tot_node_free_tmp);
#endif
extern uint16_t g_wowlan_host_state;
extern uint16_t g_wowlan_match_type;
extern uint16_t g_wowlan_l2_ether_type;
extern uint16_t g_wowlan_l3_udp_port;
extern int fwt_db_get_macs_behind_node(const uint8_t index, uint32_t *num_entries, uint32_t max_req,
uint32_t *flags, uint8_t *buf);
int ieee80211_send_tuning_data(struct ieee80211_node *);
void topaz_congest_set_unicast_queue_count(uint32_t qnum);
static void get_node_max_rssi (void *arg, struct ieee80211_node *ni);
static void ieee80211_pco_timer_func ( unsigned long arg );
static int ieee80211_ba_setup_detect_set(struct ieee80211vap *vap, int enable);
static int ieee80211_wds_vap_exists(struct ieee80211com *ic);
static struct ieee80211_node *ieee80211_get_vap_node(struct ieee80211vap *vap);
int ieee80211_should_disable_scs(struct ieee80211com *ic);
#if defined(CONFIG_QTN_BSA_SUPPORT)
static int ieee80211_bsa_macfilter_add(struct ieee80211vap *vap, uint8_t *mac);
static int ieee80211_bsa_macfilter_remove(struct ieee80211vap *vap, uint8_t *mac);
#endif
extern u_int8_t g_channel_fixed;
/*
* The RSSI values reported in the TX/RX descriptors in the driver are the SNR
* expressed in dBm. Thus 'rssi' is signal level above the noise floor in dBm.
*
* Noise is measured in dBm and is negative unless there is an unimaginable
* level of RF noise.
*
* The signal level is noise + rssi.
*
* Note that the iw_quality values are 1 byte, and can be signed, unsigned or
* negative depending on context.
*
*/
void
set_quality(struct iw_quality *iq, u_int rssi, int noise)
{
iq->qual = rssi;
iq->noise = noise;
iq->level = ((((int)rssi + noise) <= 0) ? ((int)rssi + noise) : 0);
iq->updated = IW_QUAL_ALL_UPDATED;
iq->updated |= IW_QUAL_DBM;
}
static void
pre_announced_chanswitch(struct net_device *dev, u_int32_t channel, u_int32_t tbtt);
static void
preempt_scan(struct net_device *dev, int max_grace, int max_wait)
{
struct ieee80211vap *vap = netdev_priv(dev);
struct ieee80211com *ic = vap->iv_ic;
int total_delay = 0;
int canceled = 0, ready = 0;
while (!ready && total_delay < max_grace + max_wait) {
if ((ic->ic_flags & IEEE80211_F_SCAN) == 0
#ifdef QTN_BG_SCAN
&& (ic->ic_flags_qtn & IEEE80211_QTN_BGSCAN) == 0
#endif /* QTN_BG_SCAN */
) {
ready = 1;
} else {
if (!canceled && total_delay > max_grace) {
/*
Cancel any existing active scan, so that any new parameters
in this scan ioctl (or the defaults) can be honored, then
wait around a while to see if the scan cancels properly.
*/
IEEE80211_DPRINTF(vap, IEEE80211_MSG_SCAN,
"%s: cancel pending scan request\n", __func__);
(void) ieee80211_cancel_scan(vap);
canceled = 1;
}
mdelay (1);
total_delay += 1;
}
}
if (!ready) {
IEEE80211_DPRINTF(vap, IEEE80211_MSG_SCAN,
"%s: Timeout canceling current scan.\n",
__func__);
}
}
static struct iw_statistics *
ieee80211_iw_getstats(struct net_device *dev)
{
struct ieee80211vap *vap = netdev_priv(dev);
struct ieee80211com *ic = vap->iv_ic;
#ifdef USE_LINUX_FRAMEWORK
struct iw_statistics *is = &vap->iv_iwstats;
set_quality(&is->qual, ieee80211_getrssi(vap->iv_ic),
ic->ic_channoise);
is->status = vap->iv_state;
is->discard.nwid = vap->iv_stats.is_rx_wrongbss +
vap->iv_stats.is_rx_ssidmismatch;
is->discard.code = vap->iv_stats.is_rx_wepfail +
vap->iv_stats.is_rx_decryptcrc;
is->discard.fragment = 0;
is->discard.retries = 0;
is->discard.misc = 0;
is->miss.beacon = 0;
return is;
#else
struct iw_statistics *is = &ic->ic_iwstats;
ic->ic_get_wlanstats(ic, is);
return is;
#endif
}
static int
ieee80211_ioctl_giwname(struct net_device *dev, struct iw_request_info *info,
char *name, char *extra)
{
struct ieee80211vap *vap = netdev_priv(dev);
struct ieee80211_channel *c = vap->iv_ic->ic_curchan;
if (vap->iv_ic->ic_des_mode == IEEE80211_MODE_AUTO &&
vap->iv_ic->ic_rf_chipid == CHIPID_DUAL)
/* Display all the supported modes for RFIC5 */
strncpy(name, "IEEE 802.11gnac", IFNAMSIZ);
else if ((IEEE80211_IS_CHAN_11AC(c) ) &&
(vap->iv_ic->ic_phymode >= IEEE80211_MODE_11AC_VHT20PM))
strncpy(name, "IEEE 802.11ac", IFNAMSIZ);
else if (IEEE80211_IS_CHAN_108G(c))
strncpy(name, "IEEE 802.11Tg", IFNAMSIZ);
else if (IEEE80211_IS_CHAN_108A(c))
strncpy(name, "IEEE 802.11Ta", IFNAMSIZ);
else if (IEEE80211_IS_CHAN_TURBO(c))
strncpy(name, "IEEE 802.11T", IFNAMSIZ);
else if (IEEE80211_IS_CHAN_11NG(c) &&
(vap->iv_ic->ic_phymode == IEEE80211_MODE_11NG_HT40PM))
strncpy(name, "IEEE 802.11ng40", IFNAMSIZ);
else if (IEEE80211_IS_CHAN_11NG(c) &&
(vap->iv_ic->ic_phymode == IEEE80211_MODE_11NG))
strncpy(name, "IEEE 802.11ng", IFNAMSIZ);
else if (IEEE80211_IS_CHAN_11NA(c) &&
(vap->iv_ic->ic_phymode == IEEE80211_MODE_11NA_HT40PM))
strncpy(name, "IEEE 802.11na40", IFNAMSIZ);
else if (IEEE80211_IS_CHAN_11NA(c) &&
(vap->iv_ic->ic_phymode == IEEE80211_MODE_11NA))
strncpy(name, "IEEE 802.11na", IFNAMSIZ);
else if (IEEE80211_IS_CHAN_ANYG(c) &&
(vap->iv_ic->ic_phymode == IEEE80211_MODE_11G))
strncpy(name, "IEEE 802.11g", IFNAMSIZ);
else if (IEEE80211_IS_CHAN_A(c))
strncpy(name, "IEEE 802.11a", IFNAMSIZ);
else if (IEEE80211_IS_CHAN_B(c))
strncpy(name, "IEEE 802.11b", IFNAMSIZ);
else
strncpy(name, "IEEE 802.11", IFNAMSIZ);
/* XXX FHSS */
return 0;
}
/*
* Get a key index from a request. If nothing is
* specified in the request we use the current xmit
* key index. Otherwise we just convert the index
* to be base zero.
*/
static int
getiwkeyix(struct ieee80211vap *vap, const struct iw_point* erq, int *kix)
{
int kid;
kid = erq->flags & IW_ENCODE_INDEX;
if (kid < 1 || kid > IEEE80211_WEP_NKID) {
kid = vap->iv_def_txkey;
if (kid == IEEE80211_KEYIX_NONE)
kid = 0;
} else {
--kid;
}
if (0 <= kid && kid < IEEE80211_WEP_NKID) {
*kix = kid;
return 0;
} else
return -EINVAL;
}
static int
ieee80211_ioctl_siwencode(struct net_device *dev,
struct iw_request_info *info, struct iw_point *erq, char *keybuf)
{
#ifndef IEEE80211_UNUSED_CRYPTO_COMMANDS
return -EOPNOTSUPP;
#else
struct ieee80211vap *vap = netdev_priv(dev);
int kid = 0;
int error = -EINVAL;
int wepchange = 0;
if ((erq->flags & IW_ENCODE_DISABLED) == 0) {
/*
* Enable crypto, set key contents, and
* set the default transmit key.
*/
error = getiwkeyix(vap, erq, &kid);
if (error < 0)
return error;
if (erq->length > IEEE80211_KEYBUF_SIZE)
return -EINVAL;
/* XXX no way to install 0-length key */
if (erq->length > 0) {
struct ieee80211_key *k = &vap->iv_nw_keys[kid];
/*
* Set key contents. This interface only supports WEP.
* Indicate intended key index.
*/
k->wk_keyix = kid;
k->wk_keylen = erq->length;
k->wk_ciphertype = IEEE80211_CIPHER_WEP;
memcpy(k->wk_key, keybuf, erq->length);
memset(k->wk_key + erq->length, 0,
IEEE80211_KEYBUF_SIZE - erq->length);
error = vap->iv_key_set(vap, k, vap->iv_myaddr);
} else
error = -EINVAL;
} else {
/*
* When the length is zero the request only changes
* the default transmit key. Verify the new key has
* a non-zero length.
*/
if (vap->iv_nw_keys[kid].wk_keylen == 0)
error = -EINVAL;
}
if (error == 0) {
/*
* The default transmit key is only changed when:
* 1. Privacy is enabled and no key matter is
* specified.
* 2. Privacy is currently disabled.
* This is deduced from the iwconfig man page.
*/
if (erq->length == 0 ||
(vap->iv_flags & IEEE80211_F_PRIVACY) == 0)
vap->iv_def_txkey = kid;
wepchange = (vap->iv_flags & IEEE80211_F_PRIVACY) == 0;
vap->iv_flags |= IEEE80211_F_PRIVACY;
} else {
if ((vap->iv_flags & IEEE80211_F_PRIVACY) == 0)
return 0;
vap->iv_flags &= ~IEEE80211_F_PRIVACY;
wepchange = 1;
error = 0;
}
if (error == 0) {
/* Set policy for unencrypted frames */
if ((erq->flags & IW_ENCODE_OPEN) &&
(!(erq->flags & IW_ENCODE_RESTRICTED))) {
vap->iv_flags &= ~IEEE80211_F_DROPUNENC;
} else if (!(erq->flags & IW_ENCODE_OPEN) &&
(erq->flags & IW_ENCODE_RESTRICTED)) {
vap->iv_flags |= IEEE80211_F_DROPUNENC;
} else {
/* Default policy */
if (vap->iv_flags & IEEE80211_F_PRIVACY)
vap->iv_flags |= IEEE80211_F_DROPUNENC;
else
vap->iv_flags &= ~IEEE80211_F_DROPUNENC;
}
}
if (error == 0 && IS_UP(vap->iv_dev)) {
/*
* Device is up and running; we must kick it to
* effect the change. If we're enabling/disabling
* crypto use then we must re-initialize the device
* so the 802.11 state machine is reset. Otherwise
* the key state should have been updated above.
*/
if (wepchange && IS_UP_AUTO(vap))
ieee80211_new_state(vap, IEEE80211_S_SCAN, 0);
}
return error;
#endif /* IEEE80211_UNUSED_CRYPTO_COMMANDS */
}
static int
ieee80211_ioctl_giwencode(struct net_device *dev, struct iw_request_info *info,
struct iw_point *erq, char *key)
{
struct ieee80211vap *vap = netdev_priv(dev);
struct ieee80211_key *k;
int error, kid;
if (vap->iv_flags & IEEE80211_F_PRIVACY) {
error = getiwkeyix(vap, erq, &kid);
if (error < 0)
return error;
k = &vap->iv_nw_keys[kid];
/* XXX no way to return cipher/key type */
erq->flags = kid + 1; /* NB: base 1 */
if (erq->length > k->wk_keylen)
erq->length = k->wk_keylen;
memcpy(key, k->wk_key, erq->length);
erq->flags |= IW_ENCODE_ENABLED;
} else {
erq->length = 0;
erq->flags = IW_ENCODE_DISABLED;
}
if (vap->iv_flags & IEEE80211_F_DROPUNENC)
erq->flags |= IW_ENCODE_RESTRICTED;
else
erq->flags |= IW_ENCODE_OPEN;
return 0;
}
#ifndef ifr_media
#define ifr_media ifr_ifru.ifru_ivalue
#endif
static int
ieee80211_ioctl_siwrate(struct net_device *dev, struct iw_request_info *info,
struct iw_param *rrq, char *extra)
{
static const u_int mopts[] = {
IFM_AUTO,
IFM_IEEE80211_11A,
IFM_IEEE80211_11B,
IFM_IEEE80211_11G,
IFM_IEEE80211_FH,
IFM_IEEE80211_11A | IFM_IEEE80211_TURBO,
IFM_IEEE80211_11G | IFM_IEEE80211_TURBO,
IFM_IEEE80211_11NA,
IFM_IEEE80211_11NG,
IFM_IEEE80211_11NG_HT40PM,
IFM_IEEE80211_11NA_HT40PM,
IFM_IEEE80211_11AC_VHT20PM,
IFM_IEEE80211_11AC_VHT40PM,
IFM_IEEE80211_11AC_VHT80PM,
IFM_IEEE80211_11AC_VHT160PM,
};
struct ieee80211vap *vap = netdev_priv(dev);
struct ieee80211com *ic = vap->iv_ic;
struct ifreq ifr;
int rate, retv;
u_int16_t mode = ic->ic_des_mode;
u_int16_t chan_mode = 0;
uint8_t sgi = 0;
if (mode == IEEE80211_MODE_AUTO)
return -EINVAL;
if (vap->iv_media.ifm_cur == NULL)
return -EINVAL;
memset(&ifr, 0, sizeof(ifr));
ifr.ifr_media = vap->iv_media.ifm_cur->ifm_media &~ (IFM_MMASK|IFM_TMASK);
ifr.ifr_media |= mopts[ic->ic_des_mode];
if (rrq->fixed) {
/* XXX fudge checking rates */
if (mode < IEEE80211_MODE_11NA) {
rate = ieee80211_rate2media(ic, 2 * rrq->value / 1000000,
ic->ic_des_mode);
} else {
if (ic->ic_htcap.cap & IEEE80211_HTCAP_C_CHWIDTH40) {
chan_mode = 1;
sgi = ic->ic_htcap.cap & IEEE80211_HTCAP_C_SHORTGI40 ? 1 : 0;
} else {
sgi = ic->ic_htcap.cap & IEEE80211_HTCAP_C_SHORTGI20 ? 1 : 0;
}
rate = ieee80211_rate2mcs(2 * rrq->value / 1000000, chan_mode, sgi);
/* No mcs match found. It can be a legacy rate */
if (rate < 0) {
rate = ieee80211_mcs2media(ic,
(2 * rrq->value / 1000000),
ic->ic_des_mode);
} else {
rate = ieee80211_mcs2media(ic, rate, ic->ic_des_mode);
}
}
if (rate == IFM_AUTO) { /* NB: unknown rate */
return -EINVAL;
}
} else {
rate = IFM_AUTO;
vap->iv_mcs_config = IEEE80211_MCS_AUTO_RATE_ENABLE;
}
ifr.ifr_media |= IFM_SUBTYPE(rate);
/* refresh media capabilities based on channel */
ifmedia_removeall(&vap->iv_media);
(void) ieee80211_media_setup(ic, &vap->iv_media,
vap->iv_caps, vap->iv_media.ifm_change, vap->iv_media.ifm_status);
retv = ifmedia_ioctl(vap->iv_dev, &ifr, &vap->iv_media, SIOCSIFMEDIA);
if (retv == -ENETRESET)
{
#if 0 //No need to restart network after rate change
retv = IS_UP_AUTO(vap) ? ieee80211_open(vap->iv_dev) : 0;
#endif
return 0;
}
return retv;
}
static int
ieee80211_ioctl_giwrate(struct net_device *dev, struct iw_request_info *info,
struct iw_param *rrq, char *extra)
{
struct ieee80211vap *vap = netdev_priv(dev);
struct ieee80211com *ic = vap->iv_ic;
struct ifmediareq imr;
int rate, mcs;
u_int16_t mode, chan_mode = 0;
uint8_t sgi = 0;
mode = ic->ic_des_mode;
memset(&imr, 0, sizeof(imr));
vap->iv_media.ifm_status((void *) vap, &imr);
rrq->fixed = IFM_SUBTYPE(vap->iv_media.ifm_media) != IFM_AUTO;
/* media status will have the current xmit rate if available */
if(mode < IEEE80211_MODE_11NA)
{
rate = ieee80211_media2rate(imr.ifm_active);
if (rate == -1) /* IFM_AUTO */
rate = 0;
rrq->value = 1000000 * (rate / 2);
}
else
{
if (ic->ic_htcap.cap & IEEE80211_HTCAP_C_CHWIDTH40) {
chan_mode = 1;
sgi = ic->ic_htcap.cap & IEEE80211_HTCAP_C_SHORTGI40 ? 1 : 0;
} else {
sgi = ic->ic_htcap.cap & IEEE80211_HTCAP_C_SHORTGI20 ? 1 : 0;
}
rate = ieee80211_media2mcs(imr.ifm_active);
if(rate > 0) //Fixed rate
{
if(rate & 0x80 ) /* if 11n rate is an mcs index */
{
mcs = rate & 0xf;
rate = ieee80211_mcs2rate(mcs, chan_mode, sgi, 0);
}
}
else /* IFM_AUTO */
{
rate = 0;
}
rrq->value = 1000000 * (rate / 2);
}
return 0;
}
static int
ieee80211_ioctl_siwsens(struct net_device *dev, struct iw_request_info *info,
struct iw_param *sens, char *extra)
{
return -EOPNOTSUPP;
}
static int
ieee80211_ioctl_giwsens(struct net_device *dev, struct iw_request_info *info,
struct iw_param *sens, char *extra)
{
sens->value = 1;
sens->fixed = 1;
return 0;
}
static int
ieee80211_ioctl_siwrts(struct net_device *dev, struct iw_request_info *info,
struct iw_param *rts, char *extra)
{
struct ieee80211vap *vap = netdev_priv(dev);
u32 val;
if (rts->disabled)
val = IEEE80211_RTS_THRESH_OFF;
else if (IEEE80211_RTS_MIN <= rts->value &&
rts->value <= IEEE80211_RTS_MAX)
val = rts->value;
else
return -EINVAL;
if (val != vap->iv_rtsthreshold) {
vap->iv_rtsthreshold = val;
ieee80211_param_to_qdrv(vap, IEEE80211_PARAM_RTSTHRESHOLD, val, NULL, 0);
}
return 0;
}
static int
ieee80211_ioctl_giwrts(struct net_device *dev, struct iw_request_info *info,
struct iw_param *rts, char *extra)
{
struct ieee80211vap *vap = netdev_priv(dev);
rts->value = vap->iv_rtsthreshold;
rts->disabled = (rts->value == IEEE80211_RTS_THRESH_OFF);
rts->fixed = 1;
return 0;
}
static int
ieee80211_ioctl_siwfrag(struct net_device *dev, struct iw_request_info *info,
struct iw_param *rts, char *extra)
{
struct ieee80211vap *vap = netdev_priv(dev);
struct ieee80211com *ic = vap->iv_ic;
u16 val;
if (rts->disabled)
val = 2346;
else if (rts->value < 256 || rts->value > 2346)
return -EINVAL;
else
val = (rts->value & ~0x1);
if (val != vap->iv_fragthreshold) {
vap->iv_fragthreshold = val;
return ic->ic_reset(ic);
}
return 0;
}
static int
ieee80211_ioctl_giwfrag(struct net_device *dev, struct iw_request_info *info,
struct iw_param *rts, char *extra)
{
struct ieee80211vap *vap = netdev_priv(dev);
rts->value = vap->iv_fragthreshold;
rts->disabled = (rts->value == 2346);
rts->fixed = 1;
return 0;
}
static int
ieee80211_ioctl_siwap(struct net_device *dev, struct iw_request_info *info,
struct sockaddr *ap_addr, char *extra)
{
struct ieee80211vap *vap = netdev_priv(dev);
/* NB: should not be set when in AP mode */
if (vap->iv_opmode == IEEE80211_M_HOSTAP)
return -EINVAL;
if (vap->iv_opmode == IEEE80211_M_WDS)
IEEE80211_ADDR_COPY(vap->wds_mac, &ap_addr->sa_data);
/*
* zero address corresponds to 'iwconfig ath0 ap off', which means
* enable automatic choice of AP without actually forcing a
* reassociation.
*
* broadcast address corresponds to 'iwconfig ath0 ap any', which
* means scan for the current best AP.
*
* anything else specifies a particular AP.
*/
vap->iv_flags &= ~IEEE80211_F_DESBSSID;
if (!IEEE80211_ADDR_NULL(&ap_addr->sa_data)) {
if (!IEEE80211_ADDR_EQ(vap->iv_des_bssid, (u_int8_t*) "\xff\xff\xff\xff\xff\xff"))
vap->iv_flags |= IEEE80211_F_DESBSSID;
IEEE80211_ADDR_COPY(vap->iv_des_bssid, &ap_addr->sa_data);
if (IS_UP_AUTO(vap))
ieee80211_new_state(vap, IEEE80211_S_SCAN, 0);
}
return 0;
}
static int
ieee80211_ioctl_giwap(struct net_device *dev, struct iw_request_info *info,
struct sockaddr *ap_addr, char *extra)
{
struct ieee80211vap *vap = netdev_priv(dev);
if (vap->iv_flags & IEEE80211_F_DESBSSID) {
IEEE80211_ADDR_COPY(&ap_addr->sa_data, vap->iv_des_bssid);
} else if (vap->iv_opmode == IEEE80211_M_WDS) {
IEEE80211_ADDR_COPY(&ap_addr->sa_data, vap->wds_mac);
} else if ((vap->iv_opmode == IEEE80211_M_HOSTAP && vap->iv_state == IEEE80211_S_SCAN) ||
((vap->iv_state == IEEE80211_S_RUN) && vap->iv_bss)) {
IEEE80211_ADDR_COPY(&ap_addr->sa_data, vap->iv_bss->ni_bssid);
} else {
IEEE80211_ADDR_SET_NULL(&ap_addr->sa_data);
}
ap_addr->sa_family = ARPHRD_ETHER;
return 0;
}
static int
ieee80211_ioctl_siwnickn(struct net_device *dev, struct iw_request_info *info,
struct iw_point *data, char *nickname)
{
struct ieee80211vap *vap = netdev_priv(dev);
if (data->length > IEEE80211_NWID_LEN)
return -EINVAL;
memset(vap->iv_nickname, 0, IEEE80211_NWID_LEN);
memcpy(vap->iv_nickname, nickname, data->length);
vap->iv_nicknamelen = data->length;
return 0;
}
static int
ieee80211_ioctl_giwnickn(struct net_device *dev, struct iw_request_info *info,
struct iw_point *data, char *nickname)
{
struct ieee80211vap *vap = netdev_priv(dev);
if (data->length > vap->iv_nicknamelen + 1)
data->length = vap->iv_nicknamelen + 1;
if (data->length > 0) {
memcpy(nickname, vap->iv_nickname, data->length - 1); /* XXX: strcpy? */
nickname[data->length-1] = '\0';
}
return 0;
}
static int
find11gchannel(struct ieee80211com *ic, int i, int freq)
{
for (; i < ic->ic_nchans; i++) {
const struct ieee80211_channel *c = &ic->ic_channels[i];
if (c->ic_freq == freq && IEEE80211_IS_CHAN_ANYG(c))
return 1;
}
return 0;
}
struct ieee80211_channel *
findchannel(struct ieee80211com *ic, int ieee, int mode)
{
u_int modeflags;
int i;
modeflags = ieee80211_get_chanflags(mode);
for (i = 0; i < ic->ic_nchans; i++) {
struct ieee80211_channel *c = &ic->ic_channels[i];
if (c->ic_ieee != ieee)
continue;
if (mode == IEEE80211_MODE_AUTO) {
/*
* XXX special-case 11b/g channels so we
* always select the g channel if both
* are present.
*/
if (!IEEE80211_IS_CHAN_B(c) ||
!find11gchannel(ic, i + 1, c->ic_freq))
return c;
} else {
if ((c->ic_flags & modeflags) == modeflags)
return c;
}
}
return NULL;
}
EXPORT_SYMBOL(findchannel);
struct ieee80211_channel *
findchannel_any(struct ieee80211com *ic, int ieee, int prefer_mode)
{
struct ieee80211_channel *c;
c = findchannel(ic, ieee, prefer_mode);
if (c == NULL) {
c = findchannel(ic, ieee, IEEE80211_MODE_AUTO);
if (c == NULL) {
printk("Channel %d does not exist\n", ieee);
c = IEEE80211_CHAN_ANYC;
}
}
return c;
}
struct ieee80211_channel *ieee80211_find_channel_by_ieee(struct ieee80211com *ic, int chan_ieee)
{
struct ieee80211_channel *chan;
if (chan_ieee > IEEE80211_CHAN_MAX) {
return NULL;
}
if (isclr(ic->ic_chan_active, chan_ieee)) {
return NULL;
}
chan = findchannel(ic, chan_ieee, ic->ic_des_mode);
if (chan == NULL) {
chan = findchannel(ic, chan_ieee, IEEE80211_MODE_AUTO);
}
return chan;
}
EXPORT_SYMBOL(ieee80211_find_channel_by_ieee);
static char *
ieee80211_wireless_swfeat_desc(const enum swfeat feat)
{
char *desc = "Invalid feature";
switch (feat) {
case SWFEAT_ID_MODE_AP:
desc = "Access Point";
break;
case SWFEAT_ID_MODE_STA:
desc = "Non-AP station";
break;
case SWFEAT_ID_MODE_REPEATER:
desc = "Repeater";
break;
case SWFEAT_ID_PCIE_RC:
desc = "PCIe RC mode";
break;
case SWFEAT_ID_VHT:
desc = "VHT (802.11ac)";
break;
case SWFEAT_ID_2X2:
desc = "802.11ac 2x2";
break;
case SWFEAT_ID_2X4:
desc = "802.11ac 2x4";
break;
case SWFEAT_ID_3X3:
desc = "802.11ac 3x3";
break;
case SWFEAT_ID_4X4:
desc = "802.11ac 4x4";
break;
case SWFEAT_ID_HS20:
desc = "Hotspot 2.0 (802.11u)";
break;
case SWFEAT_ID_WPA2_ENT:
desc = "WPA2 Enterprise";
break;
case SWFEAT_ID_MESH:
desc = "Mesh (802.11s)";
break;
case SWFEAT_ID_TDLS:
desc = "TDLS (802.11z)";
break;
case SWFEAT_ID_OCAC:
desc = "Zero-Second DFS (OCAC)";
break;
case SWFEAT_ID_QHOP:
desc = "QHOP (WDS Extender)";
break;
case SWFEAT_ID_QSV:
desc = "Spectrum View (QSV)";
break;
case SWFEAT_ID_QSV_NEIGH:
desc = "Neighbour Report";
break;
case SWFEAT_ID_MU_MIMO:
desc = "MU-MIMO";
break;
case SWFEAT_ID_DUAL_CHAN_VIRT:
desc = "Dual Channel Virtual Concurrent";
break;
case SWFEAT_ID_DUAL_CHAN:
desc = "Dual Channel Dual Concurrent";
break;
case SWFEAT_ID_DUAL_BAND_VIRT:
desc = "Dual Band Virtual Concurrent";
break;
case SWFEAT_ID_DUAL_BAND:
desc = "Dual Band Dual Concurrent";
break;
case SWFEAT_ID_QTM_PRIO:
desc = "QTM - Per SSID Prioritisation ";
break;
case SWFEAT_ID_QTM:
desc = "QTM - Network Aware";
break;
case SWFEAT_ID_SPEC_ANALYZER:
desc = "Spectrum Analyzer";
break;
case SWFEAT_ID_MAX:
break;
}
return desc;
}
static int
ieee80211_subioctl_print_swfeat_map(struct net_device *dev,
void __user *outbuf, int len)
{
char *buf;
char *bufp;
int i;
int j;
int rem = len;
int rc = 0;
if (!outbuf) {
printk("%s: NULL pointer for user request\n", __FUNCTION__);
return -EFAULT;
}
buf = kzalloc(len, GFP_KERNEL);
if (buf == NULL) {
printk("%s: buffer alloc failed\n", __FUNCTION__);
return -EFAULT;
}
bufp = buf;
for (i = 0; i < SWFEAT_ID_MAX; i++) {
if (isset(soc_shared_params->swfeat_map, i)) {
j = snprintf(bufp, rem, "%s\n", ieee80211_wireless_swfeat_desc(i));
if (j <= 0)
break;
bufp += j;
rem -= j;
if (rem <= 0)
break;
}
}
if (copy_to_user(outbuf, buf, len) != 0) {
printk("%s: copy_to_user failed\n", __FUNCTION__);
rc = -EIO;
}
kfree(buf);
return rc;
}
static int
ieee80211_subioctl_get_swfeat_map(struct net_device *dev,
void __user *swfeat_map, int len)
{
if (!swfeat_map) {
printk("%s: NULL pointer for user request\n", __FUNCTION__);
return -EFAULT;
}
if (len != sizeof(soc_shared_params->swfeat_map)) {
printk("%s: invalid size\n", __FUNCTION__);
return -EINVAL;
}
if (copy_to_user(swfeat_map, &soc_shared_params->swfeat_map, len) != 0) {
printk("%s: copy_to_user failed\n", __FUNCTION__);
return -EIO;
}
return 0;
}
/*
* Feature restrictions are enforced in the MuC firmware. Bypassing this check will
* cause the system to continually reboot.
*/
int ieee80211_swfeat_is_supported(uint16_t feat, uint8_t print_msg)
{
if ((feat < SWFEAT_ID_MAX) && isset(soc_shared_params->swfeat_map, feat))
return 1;
if (print_msg)
printk("%s is not supported on this device\n",
ieee80211_wireless_swfeat_desc(feat));
return 0;
}
EXPORT_SYMBOL(ieee80211_swfeat_is_supported);
static inline int
ieee80211_vht_tx_mcs_is_valid(uint32_t mcs_val, uint32_t mcs_nss)
{
if (mcs_val >= IEEE80211_AC_MCS_MAX || mcs_nss >= IEEE80211_AC_MCS_NSS_MAX)
return 0;
if (ieee80211_swfeat_is_supported(SWFEAT_ID_2X2, 0) ||
ieee80211_swfeat_is_supported(SWFEAT_ID_2X4, 0)) {
if (mcs_nss >= IEEE80211_VHT_NSS2)
return 0;
} else if (ieee80211_swfeat_is_supported(SWFEAT_ID_3X3, 0)) {
if (mcs_nss >= IEEE80211_VHT_NSS3)
return 0;
}
return 1;
}
static inline int
ieee80211_ht_tx_mcs_is_valid(uint32_t mcs)
{
if (mcs < IEEE80211_HT_EQUAL_MCS_START ||
mcs > IEEE80211_UNEQUAL_MCS_MAX ||
mcs == IEEE80211_EQUAL_MCS_32)
return 0;
if (ieee80211_swfeat_is_supported(SWFEAT_ID_2X2, 0) ||
ieee80211_swfeat_is_supported(SWFEAT_ID_2X4, 0)) {
if ((mcs > IEEE80211_HT_EQUAL_MCS_2SS_MAX &&
mcs < IEEE80211_UNEQUAL_MCS_START) ||
mcs > IEEE80211_HT_UNEQUAL_MCS_2SS_MAX) {
return 0;
}
} else if (ieee80211_swfeat_is_supported(SWFEAT_ID_3X3, 0)) {
if ((mcs > IEEE80211_HT_EQUAL_MCS_3SS_MAX &&
mcs < IEEE80211_UNEQUAL_MCS_START) ||
mcs > IEEE80211_HT_UNEQUAL_MCS_3SS_MAX) {
return 0;
}
}
return 1;
}
#define IEEE80211_MODE_TURBO_STATIC_A IEEE80211_MODE_MAX
static int
ieee80211_check_mode_consistency(struct ieee80211com *ic, int mode,
struct ieee80211_channel *c)
{
if (c == IEEE80211_CHAN_ANYC)
return 0;
switch (mode) {
case IEEE80211_MODE_11B:
if (IEEE80211_IS_CHAN_B(c))
return 0;
else
return 1;
break;
case IEEE80211_MODE_11G:
if (IEEE80211_IS_CHAN_ANYG(c)) {
return 0;
} else {
return 1;
}
break;
case IEEE80211_MODE_11NG:
if (IEEE80211_IS_CHAN_11NG(c)) {
return 0;
} else {
return 1;
}
break;
case IEEE80211_MODE_11NG_HT40PM:
if (IEEE80211_IS_CHAN_11NG_HT40PLUS(c) || IEEE80211_IS_CHAN_11NG_HT40MINUS(c))
return 0;
else
return 1;
break;
case IEEE80211_MODE_11NA:
if (IEEE80211_IS_CHAN_11NA(c))
return 0;
else
return 1;
break;
case IEEE80211_MODE_11A:
if (IEEE80211_IS_CHAN_A(c))
return 0;
else
return 1;
break;
case IEEE80211_MODE_11NA_HT40PM:
if (IEEE80211_IS_CHAN_11NA_HT40PLUS(c) || IEEE80211_IS_CHAN_11NA_HT40MINUS(c))
return 0;
else
return 1;
break;
case IEEE80211_MODE_TURBO_STATIC_A:
if (IEEE80211_IS_CHAN_A(c) && IEEE80211_IS_CHAN_STURBO(c))
return -1; /* Mode not supported */
else
return 1;
break;
case IEEE80211_MODE_11AC_VHT20PM:
if (IEEE80211_IS_CHAN_11AC(c))
return 0;
else
return 1;
break;
case IEEE80211_MODE_11AC_VHT40PM:
if (IEEE80211_IS_CHAN_11AC_VHT40PLUS(c) || IEEE80211_IS_CHAN_11AC_VHT40MINUS(c))
return 0;
else
return 1;
break;
case IEEE80211_MODE_11AC_VHT80PM:
if (IEEE80211_IS_CHAN_11AC_VHT80_EDGEPLUS(c) ||
IEEE80211_IS_CHAN_11AC_VHT80_CNTRPLUS(c) ||
IEEE80211_IS_CHAN_11AC_VHT80_CNTRMINUS(c) ||
IEEE80211_IS_CHAN_11AC_VHT80_EDGEMINUS(c))
return 0;
else
return 1;
case IEEE80211_MODE_AUTO:
return 0;
break;
}
return -1;
}
#undef IEEE80211_MODE_TURBO_STATIC_A
static inline int ieee80211_chan_allowed_in_band(struct ieee80211com *ic,
struct ieee80211_channel *c, enum ieee80211_opmode opmode)
{
if (opmode != IEEE80211_M_STA &&
ieee80211_check_mode_consistency(ic, ic->ic_des_mode, c))
return 0;
return 1;
}
void
ieee80211_initiate_scan(struct ieee80211vap *vap)
{
struct ieee80211com *ic = vap->iv_ic;
ic->ic_des_chan = IEEE80211_CHAN_ANYC;
if (IS_UP(vap->iv_dev) && (vap->iv_opmode == IEEE80211_M_HOSTAP)) {
pre_announced_chanswitch(vap->iv_dev,
ieee80211_chan2ieee(ic, ic->ic_des_chan),
IEEE80211_DEFAULT_CHANCHANGE_TBTT_COUNT);
ic->ic_curchan = ic->ic_des_chan;
ieee80211_new_state(vap, IEEE80211_S_SCAN, 0);
}
}
EXPORT_SYMBOL(ieee80211_initiate_scan);
static int
ieee80211_ioctl_siwfreq(struct net_device *dev, struct iw_request_info *info,
struct iw_freq *freq, char *extra)
{
struct ieee80211vap *vap = netdev_priv(dev);
struct ieee80211vap *canceled_scan_vap = NULL;
struct ieee80211com *ic = vap->iv_ic;
struct ieee80211_channel *c, *c2;
int i;
if (freq->e > 1) {
return -EINVAL;
}
if (freq->e == 1) {
i = (ic->ic_mhz2ieee)(ic, freq->m / 100000, 0);
} else {
i = freq->m;
}
if (i != 0) {
if (i > IEEE80211_CHAN_MAX) {
printk("Channel %d is invalid\n", i);
return -EINVAL;
}
c = findchannel(ic, i, ic->ic_des_mode);
if (c == NULL) {
printk("Channel %d does not exist\n", i);
return -EINVAL;
}
if (vap->iv_opmode == IEEE80211_M_HOSTAP) {
if ((c->ic_freq == ic->ic_curchan->ic_freq) && ic->ic_chan_is_set) {
if (ic->ic_get_init_cac_duration(ic) > 0) {
ic->ic_stop_icac_procedure(ic);
printk(KERN_DEBUG "ICAC: Aborted ICAC due to set channel request\n");
}
return 0;
}
if (!ic->ic_check_channel(ic, c, 0, 1)) {
printk("Channel %d (%d MHz) cannot be selected\n", i, c->ic_freq);
return -EINVAL;
}
}
if (!ic->ic_weachan_cac_allowed &&
(!ieee80211_is_chan_available(c)) &&
ieee80211_is_on_weather_channel(ic, c)) {
printk("Weather channel %d (%d MHz) cannot be selected\n", i, c->ic_freq);
return -EINVAL;
}
c = ieee80211_chk_update_pri_chan(ic, c, 0, "iwconfig", 1);
if (ic->ic_opmode == IEEE80211_M_HOSTAP &&
isset(ic->ic_chan_pri_inactive, c->ic_ieee) &&
isclr(ic->ic_is_inactive_autochan_only, c->ic_ieee)) {
return -EINVAL;
}
i = c->ic_ieee;
/*
* Fine tune channel selection based on desired mode:
* if 11b is requested, find the 11b version of any
* 11g channel returned,
* if static turbo, find the turbo version of any
* 11a channel return,
* otherwise we should be ok with what we've got.
*/
switch (ic->ic_des_mode) {
case IEEE80211_MODE_11B:
if (IEEE80211_IS_CHAN_ANYG(c)) {
c2 = findchannel(ic, i, IEEE80211_MODE_11B);
/* NB: should not happen, =>'s 11g w/o 11b */
if (c2 != NULL)
c = c2;
}
break;
case IEEE80211_MODE_TURBO_A:
if (IEEE80211_IS_CHAN_A(c)) {
c2 = findchannel(ic, i, IEEE80211_MODE_TURBO_A);
if (c2 != NULL)
c = c2;
}
break;
default: /* NB: no static turboG */
break;
}
if (ieee80211_check_mode_consistency(ic, ic->ic_des_mode, c)) {
if (vap->iv_opmode == IEEE80211_M_HOSTAP)
return -EINVAL;
}
/*
* Cancel scan before setting desired channel or before return when the channel
* is same as bss channel
*/
if ((vap->iv_opmode == IEEE80211_M_HOSTAP) && ((ic->ic_flags & IEEE80211_F_SCAN)
#ifdef QTN_BG_SCAN
|| (ic->ic_flags_qtn & IEEE80211_QTN_BGSCAN)
#endif /* QTN_BG_SCAN */
)) {
/*
* Find which vap is in SCAN state(Only one can be in SCAN state at the same time, other
* is pending for scan done on this vap)
* For MBSS, it may be the primary vap, or the last vap whose mode is IEEE80211_M_HOSTAP
*/
TAILQ_FOREACH(canceled_scan_vap, &ic->ic_vaps, iv_next) {
if (canceled_scan_vap->iv_state == IEEE80211_S_SCAN) {
break;
}
}
if (canceled_scan_vap != NULL) {
if (canceled_scan_vap->iv_state != IEEE80211_S_SCAN) {
canceled_scan_vap = NULL;
}
}
if (canceled_scan_vap) {
/*
* Cancel channel scan on vap which is SCAN state
* For example: scan triggered by freq ioctl or channel auto when boot up
*/
ieee80211_cancel_scan_no_wait(canceled_scan_vap);
} else {
/*
* Cancel channel scan(vap is not in SCAN state)
* For example: scan triggered by scan ioctl
*/
ieee80211_cancel_scan_no_wait(vap);
}
}
if (vap->iv_state == IEEE80211_S_RUN && c == ic->ic_bsschan)
return 0; /* no change, return */
ic->ic_des_chan = c;
} else {
/*
* Intepret channel 0 to mean "no desired channel";
* otherwise there's no way to undo fixing the desired
* channel.
*/
if (ic->ic_des_chan == IEEE80211_CHAN_ANYC)
return 0;
ic->ic_des_chan = IEEE80211_CHAN_ANYC;
}
/* Go out of idle state and delay idle state check.*/
if (ic->ic_pm_state[QTN_PM_CURRENT_LEVEL] >= BOARD_PM_LEVEL_IDLE) {
pm_qos_update_requirement(PM_QOS_POWER_SAVE, BOARD_PM_GOVERNOR_WLAN, BOARD_PM_LEVEL_NO);
ic->ic_pm_reason = IEEE80211_PM_LEVEL_SIWFREQ;
ieee80211_pm_queue_work(ic);
}
if (ic->ic_des_chan == IEEE80211_CHAN_ANYC) {
ic->ic_csw_reason = IEEE80211_CSW_REASON_SCAN;
} else {
ic->ic_csw_reason = IEEE80211_CSW_REASON_MANUAL;
}
if ((vap->iv_opmode == IEEE80211_M_MONITOR ||
vap->iv_opmode == IEEE80211_M_WDS) &&
ic->ic_des_chan != IEEE80211_CHAN_ANYC) {
/* Monitor and wds modes can switch directly. */
ic->ic_curchan = ic->ic_des_chan;
if (vap->iv_state == IEEE80211_S_RUN) {
ic->ic_set_channel(ic);
}
} else if (vap->iv_opmode == IEEE80211_M_HOSTAP) {
/*
* Use channel switch announcement on beacon if possible.
* Otherwise, ic_des_chan will take effect when we are transitioned
* to RUN state later.
* We use ic_set_channel directly if we are "running" but not "up".
*/
if (IS_UP(vap->iv_dev)) {
if ((ic->ic_des_chan != IEEE80211_CHAN_ANYC) &&
(vap->iv_state == IEEE80211_S_RUN)) {
ieee80211_enter_csa(ic, ic->ic_des_chan, NULL,
IEEE80211_CSW_REASON_MANUAL,
IEEE80211_DEFAULT_CHANCHANGE_TBTT_COUNT,
IEEE80211_CSA_MUST_STOP_TX,
IEEE80211_CSA_F_BEACON | IEEE80211_CSA_F_ACTION);
} else {
if (canceled_scan_vap) {
/*
* Scan is canceled on vap which is in SCAN state,
* do SCAN -> SCAN on vap of scan canceled
*/
ieee80211_new_state(canceled_scan_vap, IEEE80211_S_SCAN, 0);
} else {
ieee80211_new_state(vap, IEEE80211_S_SCAN, 0);
}
}
} else if (ic->ic_des_chan != IEEE80211_CHAN_ANYC) {
ic->ic_curchan = ic->ic_des_chan;
ic->ic_set_channel(ic);
}
} else {
/* Need to go through the state machine in case we need
* to reassociate or the like. The state machine will
* pickup the desired channel and avoid scanning. */
if (IS_UP_AUTO(vap)) {
ic->ic_curchan = ic->ic_des_chan;
ieee80211_new_state(vap, IEEE80211_S_SCAN, 0);
/* In case of no channel change, Don't Scan. Only VCO cal is required */
ic->ic_set_channel(ic);
} else {
/* STA doesn't support auto channel */
if ((vap->iv_opmode == IEEE80211_M_STA) && (ic->ic_des_chan == IEEE80211_CHAN_ANYC)) {
ic->ic_des_chan = ic->ic_curchan;
return -EINVAL;
} else {
ic->ic_curchan = ic->ic_des_chan;
ic->ic_set_channel(ic);
}
}
}
if (ic->ic_get_init_cac_duration(ic) > 0) {
ic->ic_stop_icac_procedure(ic);
printk(KERN_DEBUG "ICAC: Aborted ICAC due to set channel request\n");
}
ic->ic_chan_switch_reason_record(ic, IEEE80211_CSW_REASON_MANUAL);
return 0;
}
static int
ieee80211_ioctl_giwfreq(struct net_device *dev, struct iw_request_info *info,
struct iw_freq *freq, char *extra)
{
struct ieee80211vap *vap = netdev_priv(dev);
struct ieee80211com *ic = vap->iv_ic;
if (vap->iv_state == IEEE80211_S_RUN &&
vap->iv_opmode != IEEE80211_M_MONITOR) {
/*
* NB: use curchan for monitor mode so you can see
* manual scanning by apps like kismet.
*/
KASSERT(ic->ic_bsschan != IEEE80211_CHAN_ANYC,
("bss channel not set"));
freq->m = ic->ic_curchan->ic_freq;
} else if (vap->iv_state != IEEE80211_S_INIT) { /* e.g. when scanning */
if (ic->ic_curchan != IEEE80211_CHAN_ANYC)
freq->m = ic->ic_curchan->ic_freq;
else
freq->m = 0;
} else if (ic->ic_des_chan != IEEE80211_CHAN_ANYC) {
freq->m = ic->ic_des_chan->ic_freq;
} else {
freq->m = 0;
}
freq->m *= 100000;
freq->e = 1;
return 0;
}
static int
ieee80211_ioctl_siwessid(struct net_device *dev, struct iw_request_info *info,
struct iw_point *data, char *ssid)
{
struct ieee80211vap *vap = netdev_priv(dev);
if (vap->iv_opmode == IEEE80211_M_WDS)
return -EOPNOTSUPP;
if (data->flags == 0) /* ANY */
vap->iv_des_nssid = 0;
else {
if (data->length > IEEE80211_NWID_LEN)
data->length = IEEE80211_NWID_LEN;
/* NB: always use entry 0 */
memcpy(vap->iv_des_ssid[0].ssid, ssid, data->length);
vap->iv_des_ssid[0].len = data->length;
vap->iv_des_nssid = 1;
/*
* Deduct a trailing \0 since iwconfig passes a string
* length that includes this. Unfortunately this means
* that specifying a string with multiple trailing \0's
* won't be handled correctly. Not sure there's a good
* solution; the API is botched (the length should be
* exactly those bytes that are meaningful and not include
* extraneous stuff).
*/
if (data->length > 0 &&
vap->iv_des_ssid[0].ssid[data->length - 1] == '\0')
vap->iv_des_ssid[0].len--;
}
if (vap->iv_opmode == IEEE80211_M_STA)
return IS_UP_AUTO(vap) ? ieee80211_init(vap->iv_dev, RESCAN) : 0;
else
return IS_UP(vap->iv_dev) ? ieee80211_init(vap->iv_dev, RESCAN) : 0;
}
static int
ieee80211_ioctl_giwessid(struct net_device *dev, struct iw_request_info *info,
struct iw_point *data, char *essid)
{
struct ieee80211vap *vap = netdev_priv(dev);
if (vap->iv_opmode == IEEE80211_M_WDS)
return -EOPNOTSUPP;
data->flags = 1; /* active */
if (vap->iv_opmode == IEEE80211_M_HOSTAP) {
if (vap->iv_des_nssid > 0) {
if (data->length > vap->iv_des_ssid[0].len)
data->length = vap->iv_des_ssid[0].len;
memcpy(essid, vap->iv_des_ssid[0].ssid, data->length);
} else
data->length = 0;
} else {
if (vap->iv_des_nssid == 0 && vap->iv_bss) {
if (data->length > vap->iv_bss->ni_esslen)
data->length = vap->iv_bss->ni_esslen;
memcpy(essid, vap->iv_bss->ni_essid, data->length);
} else {
if (data->length > vap->iv_des_ssid[0].len)
data->length = vap->iv_des_ssid[0].len;
memcpy(essid, vap->iv_des_ssid[0].ssid, data->length);
}
}
return 0;
}
static int
ieee80211_ioctl_giwrange(struct net_device *dev, struct iw_request_info *info,
struct iw_point *data, char *extra)
{
struct ieee80211vap *vap = netdev_priv(dev);
struct ieee80211com *ic = vap->iv_ic;
//struct ieee80211_node *ni = vap->iv_bss;
struct iw_range *range = (struct iw_range *) extra;
struct ieee80211_rateset *rs;
uint8_t reported[IEEE80211_CHAN_BYTES]; /* XXX stack usage? */
uint8_t *chan_active;
int i, r, chan_mode = 0;
int step = 0;
uint8_t sgi = 0;
data->length = sizeof(struct iw_range);
memset(range, 0, sizeof(struct iw_range));
/* txpower (128 values, but will print out only IW_MAX_TXPOWER) */
range->num_txpower = (ic->ic_txpowlimit >= 8) ? IW_MAX_TXPOWER : ic->ic_txpowlimit;
step = ic->ic_txpowlimit / (2 * (IW_MAX_TXPOWER - 1));
range->txpower[0] = 0;
for (i = 1; i < IW_MAX_TXPOWER; i++)
range->txpower[i] = (ic->ic_txpowlimit/2)
- (IW_MAX_TXPOWER - i - 1) * step;
range->txpower_capa = IW_TXPOW_DBM;
if (vap->iv_opmode == IEEE80211_M_STA ||
vap->iv_opmode == IEEE80211_M_IBSS) {
range->min_pmp = 1 * 1024;
range->max_pmp = 65535 * 1024;
range->min_pmt = 1 * 1024;
range->max_pmt = 1000 * 1024;
range->pmp_flags = IW_POWER_PERIOD;
range->pmt_flags = IW_POWER_TIMEOUT;
range->pm_capa = IW_POWER_PERIOD | IW_POWER_TIMEOUT |
IW_POWER_UNICAST_R | IW_POWER_ALL_R;
}
range->we_version_compiled = WIRELESS_EXT;
range->we_version_source = 13;
range->retry_capa = IW_RETRY_LIMIT;
range->retry_flags = IW_RETRY_LIMIT;
range->min_retry = 0;
range->max_retry = 255;
if (vap->iv_opmode == IEEE80211_M_STA)
chan_active = ic->ic_chan_active_20;
else
chan_active = ic->ic_chan_active;
range->num_frequency = 0;
memset(reported, 0, sizeof(reported));
for (i = 0; i < ic->ic_nchans; i++) {
struct ieee80211_channel *c = &ic->ic_channels[i];
/* discard if previously reported (e.g. b/g) */
if (isclr(reported, c->ic_ieee) &&
isset(chan_active, c->ic_ieee) &&
(ieee80211_chan_allowed_in_band(ic, c, vap->iv_opmode))) {
setbit(reported, c->ic_ieee);
range->freq[range->num_frequency].i = c->ic_ieee;
range->freq[range->num_frequency].m =
ic->ic_channels[i].ic_freq * 100000;
range->freq[range->num_frequency].e = 1;
if (++range->num_frequency == IW_MAX_FREQUENCIES)
break;
}
}
/* Supported channels count */
range->num_channels = range->num_frequency;
/* Atheros' RSSI value is SNR: 0 -> 60 for old chipsets. Range
* for newer chipsets is unknown. This value is arbitarily chosen
* to give an indication that full rate will be available and to be
* a practicable maximum. */
range->max_qual.qual = 70;
/* XXX: This should be updated to use the current noise floor. */
/* These are negative full bytes.
* Min. quality is noise + 1 */
#define QNT_DEFAULT_NOISE 0
range->max_qual.updated |= IW_QUAL_DBM;
range->max_qual.level = QNT_DEFAULT_NOISE + 1;
range->max_qual.noise = QNT_DEFAULT_NOISE;
range->sensitivity = 1;
range->max_encoding_tokens = IEEE80211_WEP_NKID;
/* XXX query driver to find out supported key sizes */
range->num_encoding_sizes = 3;
range->encoding_size[0] = 5; /* 40-bit */
range->encoding_size[1] = 13; /* 104-bit */
range->encoding_size[2] = 16; /* 128-bit */
if (ic->ic_htcap.cap & IEEE80211_HTCAP_C_CHWIDTH40) {
chan_mode = 1;
sgi = ic->ic_htcap.cap & IEEE80211_HTCAP_C_SHORTGI40 ? 1 : 0;
} else {
sgi = ic->ic_htcap.cap & IEEE80211_HTCAP_C_SHORTGI20 ? 1 : 0;
}
rs = &ic->ic_sup_rates[ic->ic_des_mode];
range->num_bitrates = rs->rs_nrates;
if (range->num_bitrates > MIN(IEEE80211_RATE_MAXSIZE, IW_MAX_BITRATES))
range->num_bitrates = MIN(IEEE80211_RATE_MAXSIZE, IW_MAX_BITRATES);
for (i = 0; i < range->num_bitrates; i++) {
r = rs->rs_rates[i] & IEEE80211_RATE_VAL;
/* Skip legacy rates */
if(i >= (rs->rs_legacy_nrates))
{
r = ieee80211_mcs2rate(r, chan_mode, sgi, 0);
}
range->bitrate[i] = (r * 1000000) / 2;
}
/* estimated maximum TCP throughput values (bps) */
range->throughput = 5500000;
range->min_rts = 0;
range->max_rts = 2347;
range->min_frag = 256;
range->max_frag = 2346;
/* Event capability (kernel) */
IW_EVENT_CAPA_SET_KERNEL(range->event_capa);
/* Event capability (driver) */
if (vap->iv_opmode == IEEE80211_M_STA ||
vap->iv_opmode == IEEE80211_M_IBSS ||
vap->iv_opmode == IEEE80211_M_AHDEMO) {
/* for now, only ibss, ahdemo, sta has this cap */
IW_EVENT_CAPA_SET(range->event_capa, SIOCGIWSCAN);
}
if (vap->iv_opmode == IEEE80211_M_STA) {
/* for sta only */
IW_EVENT_CAPA_SET(range->event_capa, SIOCGIWAP);
IW_EVENT_CAPA_SET(range->event_capa, IWEVREGISTERED);
IW_EVENT_CAPA_SET(range->event_capa, IWEVEXPIRED);
}
/* this is used for reporting replay failure, which is used by the different encoding schemes */
IW_EVENT_CAPA_SET(range->event_capa, IWEVCUSTOM);
/* report supported WPA/WPA2 capabilities to userspace */
range->enc_capa = IW_ENC_CAPA_WPA | IW_ENC_CAPA_WPA2 |
IW_ENC_CAPA_CIPHER_TKIP | IW_ENC_CAPA_CIPHER_CCMP;
return 0;
}
static int
ieee80211_ioctl_setspy(struct net_device *dev, struct iw_request_info *info,
struct iw_point *data, char *extra)
{
/* save the list of node addresses */
struct ieee80211vap *vap = netdev_priv(dev);
struct sockaddr address[IW_MAX_SPY];
unsigned int number = data->length;
int i;
if (number > IW_MAX_SPY)
return -E2BIG;
/* get the addresses into the driver */
if (data->pointer) {
if (copy_from_user(address, data->pointer,
sizeof(struct sockaddr) * number))
return -EFAULT;
} else {
return -EFAULT;
}
/* copy the MAC addresses into a list */
if (number > 0) {
/* extract the MAC addresses */
for (i = 0; i < number; i++)
memcpy(&vap->iv_spy.mac[i * IEEE80211_ADDR_LEN],
address[i].sa_data, IEEE80211_ADDR_LEN);
/* init rssi timestamps */
memset(vap->iv_spy.ts_rssi, 0, IW_MAX_SPY * sizeof(u_int32_t));
}
vap->iv_spy.num = number;
return 0;
}
static int
ieee80211_ioctl_getspy(struct net_device *dev, struct iw_request_info *info,
struct iw_point *data, char *extra)
{
/*
* locate nodes by mac (ieee80211_find_node()),
* copy out rssi, set updated flag appropriately
*/
struct ieee80211vap *vap = netdev_priv(dev);
struct ieee80211_node_table *nt = &vap->iv_ic->ic_sta;
struct ieee80211_node *ni;
struct ieee80211com *ic = vap->iv_ic;
struct sockaddr *address;
struct iw_quality *spy_stat;
unsigned int number = vap->iv_spy.num;
int i;
address = (struct sockaddr *) extra;
spy_stat = (struct iw_quality *) (extra + number * sizeof(struct sockaddr));
for (i = 0; i < number; i++) {
memcpy(address[i].sa_data, &vap->iv_spy.mac[i * IEEE80211_ADDR_LEN],
IEEE80211_ADDR_LEN);
address[i].sa_family = AF_PACKET;
}
/* locate a node, read its rssi, check if updated, convert to dBm */
for (i = 0; i < number; i++) {
ni = ieee80211_find_node(nt, &vap->iv_spy.mac[i * IEEE80211_ADDR_LEN]);
/* check we are associated w/ this vap */
if (ni) {
if (ni->ni_vap == vap) {
set_quality(&spy_stat[i], ni->ni_rssi, ic->ic_channoise);
if (ni->ni_rstamp != vap->iv_spy.ts_rssi[i]) {
vap->iv_spy.ts_rssi[i] = ni->ni_rstamp;
} else {
spy_stat[i].updated = 0;
}
}
ieee80211_free_node(ni);
} else {
spy_stat[i].updated = IW_QUAL_ALL_INVALID;
}
}
/* copy results to userspace */
data->length = number;
return 0;
}
/* Enhanced iwspy support */
static int
ieee80211_ioctl_setthrspy(struct net_device *dev, struct iw_request_info *info,
struct iw_point *data, char *extra)
{
struct ieee80211vap *vap = netdev_priv(dev);
struct iw_thrspy threshold;
if (data->length != 1)
return -EINVAL;
/* get the threshold values into the driver */
if (data->pointer) {
if (copy_from_user(&threshold, data->pointer,
sizeof(struct iw_thrspy)))
return -EFAULT;
} else
return -EINVAL;
if (threshold.low.level == 0) {
/* disable threshold */
vap->iv_spy.thr_low = 0;
vap->iv_spy.thr_high = 0;
IEEE80211_DPRINTF(vap, IEEE80211_MSG_DEBUG,
"%s: disabled iw_spy threshold\n", __func__);
} else {
/* We are passed a signal level/strength - calculate
* corresponding RSSI values */
/* XXX: We should use current noise value. */
vap->iv_spy.thr_low = threshold.low.level + QNT_DEFAULT_NOISE;
vap->iv_spy.thr_high = threshold.high.level + QNT_DEFAULT_NOISE;
IEEE80211_DPRINTF(vap, IEEE80211_MSG_DEBUG,
"%s: enabled iw_spy threshold\n", __func__);
}
return 0;
}
static int
ieee80211_ioctl_getthrspy(struct net_device *dev, struct iw_request_info *info,
struct iw_point *data, char *extra)
{
struct ieee80211vap *vap = netdev_priv(dev);
struct ieee80211com *ic = vap->iv_ic;
struct iw_thrspy *threshold;
threshold = (struct iw_thrspy *) extra;
/* set threshold values */
set_quality(&(threshold->low), vap->iv_spy.thr_low, ic->ic_channoise);
set_quality(&(threshold->high), vap->iv_spy.thr_high, ic->ic_channoise);
/* copy results to userspace */
data->length = 1;
return 0;
}
static int
ieee80211_ioctl_siwmode(struct net_device *dev, struct iw_request_info *info,
__u32 *mode, char *extra)
{
struct ieee80211vap *vap = netdev_priv(dev);
struct ifmediareq imr;
int valid = 0;
memset(&imr, 0, sizeof(imr));
vap->iv_media.ifm_status((void *) vap, &imr);
if (imr.ifm_active & IFM_IEEE80211_HOSTAP)
valid = (*mode == IW_MODE_MASTER);
else if (imr.ifm_active & IFM_IEEE80211_MONITOR)
valid = (*mode == IW_MODE_MONITOR);
else if (imr.ifm_active & IFM_IEEE80211_ADHOC)
valid = (*mode == IW_MODE_ADHOC);
else if (imr.ifm_active & IFM_IEEE80211_WDS)
valid = (*mode == IW_MODE_REPEAT);
else
valid = (*mode == IW_MODE_INFRA);
return valid ? 0 : -EINVAL;
}
static int
ieee80211_ioctl_giwmode(struct net_device *dev, struct iw_request_info *info,
__u32 *mode, char *extra)
{
struct ieee80211vap *vap = netdev_priv(dev);
struct ifmediareq imr;
memset(&imr, 0, sizeof(imr));
vap->iv_media.ifm_status((void *) vap, &imr);
if (imr.ifm_active & IFM_IEEE80211_HOSTAP)
*mode = IW_MODE_MASTER;
else if (imr.ifm_active & IFM_IEEE80211_MONITOR)
*mode = IW_MODE_MONITOR;
else if (imr.ifm_active & IFM_IEEE80211_ADHOC)
*mode = IW_MODE_ADHOC;
else if (imr.ifm_active & IFM_IEEE80211_WDS)
*mode = IW_MODE_REPEAT;
else
*mode = IW_MODE_INFRA;
return 0;
}
static int
ieee80211_ioctl_siwpower(struct net_device *dev, struct iw_request_info *info,
struct iw_param *wrq, char *extra)
{
struct ieee80211vap *vap = netdev_priv(dev);
struct ieee80211com *ic = vap->iv_ic;
/* XXX: These values, flags, and caps do not seem to be used elsewhere
* at all? */
if ((ic->ic_caps & IEEE80211_C_PMGT) == 0)
return -EOPNOTSUPP;
if (wrq->disabled) {
if (ic->ic_flags & IEEE80211_F_PMGTON)
ic->ic_flags &= ~IEEE80211_F_PMGTON;
} else {
switch (wrq->flags & IW_POWER_MODE) {
case IW_POWER_UNICAST_R:
case IW_POWER_ALL_R:
case IW_POWER_ON:
if (wrq->flags & IW_POWER_PERIOD) {
if (IEEE80211_BINTVAL_VALID(wrq->value))
ic->ic_lintval = IEEE80211_MS_TO_TU(wrq->value);
else
return -EINVAL;
}
if (wrq->flags & IW_POWER_TIMEOUT)
ic->ic_holdover = IEEE80211_MS_TO_TU(wrq->value);
ic->ic_flags |= IEEE80211_F_PMGTON;
break;
default:
return -EINVAL;
}
}
return ic->ic_reset(ic);
}
static int
ieee80211_ioctl_giwpower(struct net_device *dev, struct iw_request_info *info,
struct iw_param *rrq, char *extra)
{
struct ieee80211vap *vap = netdev_priv(dev);
struct ieee80211com *ic = vap->iv_ic;
rrq->disabled = (ic->ic_flags & IEEE80211_F_PMGTON) == 0;
if (!rrq->disabled) {
switch (rrq->flags & IW_POWER_TYPE) {
case IW_POWER_TIMEOUT:
rrq->flags = IW_POWER_TIMEOUT;
rrq->value = IEEE80211_TU_TO_MS(ic->ic_holdover);
break;
case IW_POWER_PERIOD:
rrq->flags = IW_POWER_PERIOD;
rrq->value = IEEE80211_TU_TO_MS(ic->ic_lintval);
break;
}
rrq->flags |= IW_POWER_ALL_R;
}
return 0;
}
static int
ieee80211_ioctl_siwretry(struct net_device *dev, struct iw_request_info *info,
struct iw_param *rrq, char *extra)
{
struct ieee80211vap *vap = netdev_priv(dev);
struct ieee80211com *ic = vap->iv_ic;
if (rrq->disabled) {
if (vap->iv_flags & IEEE80211_F_SWRETRY) {
vap->iv_flags &= ~IEEE80211_F_SWRETRY;
goto done;
}
return 0;
}
if ((vap->iv_caps & IEEE80211_C_SWRETRY) == 0)
return -EOPNOTSUPP;
if (rrq->flags == IW_RETRY_LIMIT) {
if (rrq->value >= 0) {
vap->iv_txmin = rrq->value;
vap->iv_txmax = rrq->value; /* XXX */
vap->iv_txlifetime = 0; /* XXX */
vap->iv_flags |= IEEE80211_F_SWRETRY;
} else {
vap->iv_flags &= ~IEEE80211_F_SWRETRY;
}
return 0;
}
done:
return IS_UP(vap->iv_dev) ? ic->ic_reset(ic) : 0;
}
static int
ieee80211_ioctl_giwretry(struct net_device *dev, struct iw_request_info *info,
struct iw_param *rrq, char *extra)
{
struct ieee80211vap *vap = netdev_priv(dev);
rrq->disabled = (vap->iv_flags & IEEE80211_F_SWRETRY) == 0;
if (!rrq->disabled) {
switch (rrq->flags & IW_RETRY_TYPE) {
case IW_RETRY_LIFETIME:
rrq->flags = IW_RETRY_LIFETIME;
rrq->value = IEEE80211_TU_TO_MS(vap->iv_txlifetime);
break;
case IW_RETRY_LIMIT:
rrq->flags = IW_RETRY_LIMIT;
switch (rrq->flags & IW_RETRY_MODIFIER) {
case IW_RETRY_MIN:
rrq->flags |= IW_RETRY_MAX;
rrq->value = vap->iv_txmin;
break;
case IW_RETRY_MAX:
rrq->flags |= IW_RETRY_MAX;
rrq->value = vap->iv_txmax;
break;
}
break;
}
}
return 0;
}
static int
ieee80211_ioctl_siwtxpow(struct net_device *dev, struct iw_request_info *info,
struct iw_param *rrq, char *extra)
{
struct ieee80211vap *vap = netdev_priv(dev);
struct ieee80211com *ic = vap->iv_ic;
int fixed, disabled;
fixed = (ic->ic_flags & IEEE80211_F_TXPOW_FIXED);
if (!vap->iv_bss) {
return 0;
}
disabled = (fixed && vap->iv_bss->ni_txpower == 0);
if (rrq->disabled) {
if (!disabled) {
if ((ic->ic_caps & IEEE80211_C_TXPMGT) == 0)
return -EOPNOTSUPP;
ic->ic_flags |= IEEE80211_F_TXPOW_FIXED;
vap->iv_bss->ni_txpower = 0;
goto done;
}
return 0;
}
if (rrq->fixed) {
if ((ic->ic_caps & IEEE80211_C_TXPMGT) == 0)
return -EOPNOTSUPP;
if (rrq->flags != IW_TXPOW_DBM)
return -EOPNOTSUPP;
if (ic->ic_bsschan != IEEE80211_CHAN_ANYC) {
if (ic->ic_bsschan->ic_maxregpower >= rrq->value &&
ic->ic_txpowlimit/2 >= rrq->value) {
vap->iv_bss->ni_txpower = 2 * rrq->value;
ic->ic_newtxpowlimit = 2 * rrq->value;
ic->ic_flags |= IEEE80211_F_TXPOW_FIXED;
} else
return -EINVAL;
} else {
/*
* No channel set yet
*/
if (ic->ic_txpowlimit/2 >= rrq->value) {
vap->iv_bss->ni_txpower = 2 * rrq->value;
ic->ic_newtxpowlimit = 2 * rrq->value;
ic->ic_flags |= IEEE80211_F_TXPOW_FIXED;
}
else
return -EINVAL;
}
} else {
if (!fixed) /* no change */
return 0;
ic->ic_flags &= ~IEEE80211_F_TXPOW_FIXED;
}
done:
return ic->ic_reset(ic);
}
static int
ieee80211_ioctl_giwtxpow(struct net_device *dev, struct iw_request_info *info,
struct iw_param *rrq, char *extra)
{
struct ieee80211vap *vap = netdev_priv(dev);
struct ieee80211com *ic = vap->iv_ic;
rrq->fixed = (ic->ic_flags & IEEE80211_F_TXPOW_FIXED) != 0;
rrq->disabled = (rrq->fixed && rrq->value == 0);
rrq->flags = IW_TXPOW_DBM;
if (vap->iv_bss) {
/* ni_txpower is stored in 0.5dBm units */
rrq->value = vap->iv_bss->ni_txpower >> 1;
} else {
rrq->value = 0;
}
return 0;
}
struct waplistreq { /* XXX: not the right place for declaration? */
struct ieee80211vap *vap;
struct sockaddr addr[IW_MAX_AP];
struct iw_quality qual[IW_MAX_AP];
int i;
};
static int
waplist_cb(void *arg, const struct ieee80211_scan_entry *se)
{
struct waplistreq *req = arg;
int i = req->i;
if (i >= IW_MAX_AP)
return 0;
req->addr[i].sa_family = ARPHRD_ETHER;
if (req->vap->iv_opmode == IEEE80211_M_HOSTAP)
IEEE80211_ADDR_COPY(req->addr[i].sa_data, se->se_macaddr);
else
IEEE80211_ADDR_COPY(req->addr[i].sa_data, se->se_bssid);
set_quality(&req->qual[i], se->se_rssi, QNT_DEFAULT_NOISE);
req->i = i + 1;
return 0;
}
static int
ieee80211_ioctl_iwaplist(struct net_device *dev, struct iw_request_info *info,
struct iw_point *data, char *extra)
{
struct ieee80211vap *vap = netdev_priv(dev);
struct ieee80211com *ic = vap->iv_ic;
struct waplistreq req; /* XXX off stack */
req.vap = vap;
req.i = 0;
ieee80211_scan_iterate(ic, waplist_cb, &req);
data->length = req.i;
memcpy(extra, &req.addr, req.i * sizeof(req.addr[0]));
data->flags = 1; /* signal quality present (sort of) */
memcpy(extra + req.i * sizeof(req.addr[0]), &req.qual,
req.i * sizeof(req.qual[0]));
return 0;
}
#ifdef SIOCGIWSCAN
static qfdr_remote_siwscan_hook_t qfdr_remote_siwscan_hook = NULL;
void ieee80211_register_qfdr_remote_siwscan_hook(qfdr_remote_siwscan_hook_t hook)
{
qfdr_remote_siwscan_hook = hook;
}
EXPORT_SYMBOL(ieee80211_register_qfdr_remote_siwscan_hook);
static int
ieee80211_ioctl_siwscan(struct net_device *dev, struct iw_request_info *info,
struct iw_point *data, char *extra)
{
struct ieee80211vap *vap = netdev_priv(dev);
uint32_t scan_flags = 0;
uint16_t pick_flags = 0;
struct ieee80211com *ic = vap->iv_ic;
struct ieee80211_scan_state *ss = ic->ic_scan;
int dfs_channel_available = 0;
int is_remote_req = 0;
if (info->cmd & QFDR_REMOTE_CMD) {
is_remote_req = 1;
info->cmd &= ~QFDR_REMOTE_CMD;
}
if (is_remote_req == 0 && qfdr_remote_siwscan_hook != NULL)
qfdr_remote_siwscan_hook(vap->iv_dev->name, data);
/*
* XXX don't permit a scan to be started unless we
* know the device is ready. For the moment this means
* the device is marked up as this is the required to
* initialize the hardware. It would be better to permit
* scanning prior to being up but that'll require some
* changes to the infrastructure.
*/
if (!IS_UP(vap->iv_dev))
return -ENETDOWN; /* XXX */
if ((ic->ic_bsschan != IEEE80211_CHAN_ANYC &&
IEEE80211_IS_CHAN_CAC_IN_PROGRESS(ic->ic_bsschan)) ||
ic->ic_ocac.ocac_running)
return -EBUSY;
if (!ieee80211_should_scan(vap))
return -EAGAIN;
if (ic->ic_get_init_cac_duration(ic) > 0) {
return -EAGAIN;
}
/* XXX always manual... */
IEEE80211_DPRINTF(vap, IEEE80211_MSG_SCAN,
"%s: active scan request\n", __func__);
preempt_scan(dev, 100, 100);
ss->is_scan_valid = 1;
ic->ic_csw_reason = IEEE80211_CSW_REASON_SCAN;
if (data && (data->flags & IW_SCAN_THIS_ESSID)) {
struct iw_scan_req req;
struct ieee80211_scan_ssid ssid;
int copyLength;
IEEE80211_DPRINTF(vap, IEEE80211_MSG_SCAN,
"%s: SCAN_THIS_ESSID requested\n", __func__);
if (data->length > sizeof req) {
copyLength = sizeof req;
} else {
copyLength = data->length;
}
memset(&req, 0, sizeof req);
if (is_remote_req)
memcpy(&req, data->pointer, copyLength);
else {
if (copy_from_user(&req, data->pointer, copyLength))
return -EFAULT;
}
memcpy(&ssid.ssid, req.essid, sizeof ssid.ssid);
ssid.len = req.essid_len;
IEEE80211_DPRINTF(vap, IEEE80211_MSG_SCAN,
"%s: requesting scan of essid '%s'\n", __func__, ssid.ssid);
(void) ieee80211_start_scan(vap,
IEEE80211_SCAN_ACTIVE |
IEEE80211_SCAN_NOPICK |
IEEE80211_SCAN_ONCE |
(IEEE80211_USE_QTN_BGSCAN(vap) ? IEEE80211_SCAN_QTN_BGSCAN: 0) |
((vap->iv_opmode == IEEE80211_M_HOSTAP) ? IEEE80211_SCAN_FLUSH : 0),
IEEE80211_SCAN_FOREVER,
1, &ssid);
return 0;
}
if (data && data->pointer) {
u_int16_t flags_tmp;
u_int16_t flags_bg_scan_mode = 0;
if (is_remote_req)
memcpy(&pick_flags, data->pointer, sizeof(pick_flags));
else {
if (copy_from_user(&pick_flags, data->pointer, sizeof(pick_flags)))
return -EFAULT;
}
flags_tmp = pick_flags & IEEE80211_PICK_ALGORITHM_MASK;
flags_bg_scan_mode = 0;
/*
* For DFS reentry, check if any DFS channel is available.
* If not, skip channel scan and return directly.
*/
if (pick_flags & (IEEE80211_PICK_REENTRY | IEEE80211_PICK_DFS)) {
if (ic->ic_is_dfs_chans_available_for_dfs_reentry) {
if ((dfs_channel_available = ic->ic_is_dfs_chans_available_for_dfs_reentry(ic, vap)) <= 0) {
return dfs_channel_available;
}
} else {
return -EOPNOTSUPP;
}
}
if (flags_tmp == IEEE80211_PICK_REENTRY || flags_tmp == IEEE80211_PICK_CLEAREST) {
scan_flags = IEEE80211_SCAN_FLUSH;
/* Go out of idle state and delay idle state check.*/
if (ic->ic_pm_state[QTN_PM_CURRENT_LEVEL] >= BOARD_PM_LEVEL_IDLE) {
pm_qos_update_requirement(PM_QOS_POWER_SAVE, BOARD_PM_GOVERNOR_WLAN, BOARD_PM_LEVEL_NO);
ic->ic_pm_reason = IEEE80211_PM_LEVEL_SIWSCAN;
ieee80211_pm_queue_work(ic);
}
}
else if (flags_tmp == IEEE80211_PICK_NOPICK) {
scan_flags = IEEE80211_SCAN_NOPICK;
}
#ifdef QTN_BG_SCAN
else if (flags_tmp == IEEE80211_PICK_NOPICK_BG) {
scan_flags = IEEE80211_SCAN_NOPICK;
if ((vap->iv_opmode == IEEE80211_M_HOSTAP && ic->ic_sta_assoc > 0) ||
(vap->iv_opmode == IEEE80211_M_STA && vap->iv_state == IEEE80211_S_RUN)) {
scan_flags |= IEEE80211_SCAN_QTN_BGSCAN;
flags_bg_scan_mode = pick_flags & IEEE80211_PICK_BG_MODE_MASK;
if (IS_MULTIPLE_BITS_SET(flags_bg_scan_mode)) {
/* use auto mode if multiple modes are set */
flags_bg_scan_mode = 0;
}
}
}
#endif /* QTN_BG_SCAN */
if (pick_flags & IEEE80211_PICK_SCAN_FLUSH) {
scan_flags |= IEEE80211_SCAN_FLUSH;
}
/*
* set pick flags before start scanning, and remember to clean it when selection channel done
* only for AP mode
*/
ss->ss_pick_flags = (pick_flags & (~IEEE80211_PICK_CONTROL_MASK)) | flags_bg_scan_mode;
}
if (IEEE80211_USE_QTN_BGSCAN(vap))
scan_flags |= IEEE80211_SCAN_QTN_BGSCAN;
(void) ieee80211_start_scan(vap, IEEE80211_SCAN_ACTIVE |
scan_flags | IEEE80211_SCAN_ONCE,
IEEE80211_SCAN_FOREVER,
/* XXX use ioctl params */
vap->iv_des_nssid, vap->iv_des_ssid);
return 0;
}
int qfdr_siwscan_for_remote(struct qfdr_remote_scan_req *remote_req)
{
struct net_device *dev;
struct iw_point *data;
struct iw_point data_remote;
struct iw_request_info info;
dev = dev_get_by_name(&init_net, remote_req->dev_name);
if (!dev)
return -EINVAL;
memset(&info, 0, sizeof(info));
info.cmd = SIOCSIWSCAN | QFDR_REMOTE_CMD;
if (remote_req->type == QFDR_SIWSCAN_SIMPLE)
data = NULL;
else {
data_remote.length = remote_req->length;
data_remote.flags = remote_req->flags;
data_remote.pointer = remote_req->pointer;
data = &data_remote;
}
ieee80211_ioctl_siwscan(dev, &info, data, NULL);
dev_put(dev);
return 0;
}
EXPORT_SYMBOL(qfdr_siwscan_for_remote);
/*
* Encode a WPA or RSN information element as a custom
* element using the hostap format.
*/
static u_int
encode_ie(void *buf, size_t bufsize, const u_int8_t *ie, size_t ielen,
const char *leader, size_t leader_len)
{
char *p;
int i;
if (bufsize < leader_len)
return 0;
p = buf;
memcpy(p, leader, leader_len);
bufsize -= leader_len;
p += leader_len;
for (i = 0; i < ielen && bufsize > 2; i++) {
p += sprintf(p, "%02x", ie[i]);
bufsize -= 2;
}
return (i == ielen ? p - (char *)buf : 0);
}
/*
* Recalculate the RSSI of MBS/RBS
* Make sure the RSSI has the following priority.
* MBS in best rate range has the highest level RSSI.
* RBS in best rate range has the second high level RSSI.
* MBS not in best rate range has the third high level RSSI.
* RBS not in best rate range has the fourth high level RSSI.
*/
static int8_t
ieee80211_calcu_extwds_node_rssi(struct ieee80211com *ic,
const struct ieee80211_scan_entry *se)
{
int8_t rssi = se->se_rssi;
if (se->se_ext_role == IEEE80211_EXTENDER_ROLE_NONE)
return rssi;
if (se->se_ext_role == IEEE80211_EXTENDER_ROLE_MBS) {
if (rssi >= ic->ic_extender_mbs_best_rssi) {
rssi = IEEE80211_EXTWDS_MBS_BEST_RATE_RSSI;
} else {
rssi = (rssi - IEEE80211_EXTWDS_MIN_PSEUDO_RSSI) *
IEEE80211_EXTWDS_BEST_RATE_BDRY_RSSI /
(ic->ic_extender_mbs_best_rssi -
IEEE80211_EXTWDS_MIN_PSEUDO_RSSI) *
ic->ic_extender_mbs_wgt / 10;
}
} else if (se->se_ext_role == IEEE80211_EXTENDER_ROLE_RBS) {
if (rssi >= ic->ic_extender_rbs_best_rssi) {
rssi = (rssi - ic->ic_extender_rbs_best_rssi) *
(IEEE80211_EXTWDS_MAX_PSEUDO_RSSI -
IEEE80211_EXTWDS_BEST_RATE_BDRY_RSSI) /
(IEEE80211_EXTWDS_MAX_PSEUDO_RSSI -
ic->ic_extender_rbs_best_rssi) +
IEEE80211_EXTWDS_BEST_RATE_BDRY_RSSI;
} else {
rssi = (rssi - IEEE80211_EXTWDS_MIN_PSEUDO_RSSI) *
IEEE80211_EXTWDS_BEST_RATE_BDRY_RSSI /
(ic->ic_extender_rbs_best_rssi -
IEEE80211_EXTWDS_MIN_PSEUDO_RSSI) *
ic->ic_extender_rbs_wgt / 10;
}
}
return rssi;
}
static int
giwscan_cb(void *arg, const struct ieee80211_scan_entry *se)
{
struct iwscanreq *req = arg;
struct ieee80211vap *vap = req->vap;
struct ieee80211com *ic = vap->iv_ic;
struct iw_request_info *info = req->info;
char *current_ev = req->current_ev;
char *end_buf = req->end_buf;
char *last_ev;
#define MAX_IE_LENGTH 257
char buf[MAX_IE_LENGTH];
#ifndef IWEVGENIE
static const char rsn_leader[] = IEEE80211_IE_LEADER_STR_RSN;
static const char wpa_leader[] = IEEE80211_IE_LEADER_STR_WPA;
#endif
struct iw_event iwe;
char *current_val;
int j;
u_int8_t chan_mode = 0;
uint8_t sgi = 0;
u_int8_t k, r;
u_int16_t mask;
struct ieee80211_ie_htcap *htcap;
struct ieee80211_ie_vhtcap *vhtcap;
int rate_ie_exist = 0;
if (current_ev >= end_buf)
return E2BIG;
memset(&iwe, 0, sizeof(iwe));
last_ev = current_ev;
iwe.cmd = SIOCGIWAP;
iwe.u.ap_addr.sa_family = ARPHRD_ETHER;
if (vap->iv_opmode == IEEE80211_M_HOSTAP)
IEEE80211_ADDR_COPY(iwe.u.ap_addr.sa_data, se->se_macaddr);
else
IEEE80211_ADDR_COPY(iwe.u.ap_addr.sa_data, se->se_bssid);
current_ev = iwe_stream_add_event(info, current_ev, end_buf, &iwe, IW_EV_ADDR_LEN);
/* We ran out of space in the buffer. */
if (last_ev == current_ev)
return E2BIG;
memset(&iwe, 0, sizeof(iwe));
last_ev = current_ev;
iwe.cmd = SIOCGIWESSID;
iwe.u.data.flags = 1;
iwe.u.data.length = se->se_ssid[1];
current_ev = iwe_stream_add_point(info, current_ev,
end_buf, &iwe, (char *)se->se_ssid + 2);
/* We ran out of space in the buffer. */
if (last_ev == current_ev)
return E2BIG;
if (se->se_capinfo & (IEEE80211_CAPINFO_ESS|IEEE80211_CAPINFO_IBSS)) {
memset(&iwe, 0, sizeof(iwe));
last_ev = current_ev;
iwe.cmd = SIOCGIWMODE;
iwe.u.mode = se->se_capinfo & IEEE80211_CAPINFO_ESS ?
IW_MODE_MASTER : IW_MODE_ADHOC;
current_ev = iwe_stream_add_event(info, current_ev,
end_buf, &iwe, IW_EV_UINT_LEN);
/* We ran out of space in the buffer. */
if (last_ev == current_ev)
return E2BIG;
}
memset(&iwe, 0, sizeof(iwe));
last_ev = current_ev;
iwe.cmd = SIOCGIWFREQ;
iwe.u.freq.m = se->se_chan->ic_freq * 100000;
iwe.u.freq.e = 1;
current_ev = iwe_stream_add_event(info, current_ev,
end_buf, &iwe, IW_EV_FREQ_LEN);
/* We ran out of space in the buffer. */
if (last_ev == current_ev)
return E2BIG;
memset(&iwe, 0, sizeof(iwe));
last_ev = current_ev;
iwe.cmd = IWEVQUAL;
set_quality(&iwe.u.qual, se->se_rssi, QNT_DEFAULT_NOISE);
/*
* Assign the real RSSI to 'level' for MBS/RBS, so wpa_supplicant
* can run roaming between MBS and RBS base on the 'level' value.
*/