4339 lines
142 KiB
C
4339 lines
142 KiB
C
/*
|
|
* Copyright (c) 2015 iComm-semi Ltd.
|
|
*
|
|
* 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.
|
|
*/
|
|
|
|
#include <linux/nl80211.h>
|
|
#include <linux/etherdevice.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/version.h>
|
|
#include <linux/time.h>
|
|
#include <linux/kthread.h>
|
|
#include <linux/ip.h>
|
|
#include <net/udp.h>
|
|
#ifdef SSV_MAC80211
|
|
#include "ssv_mac80211.h"
|
|
#else
|
|
#include <net/mac80211.h>
|
|
#endif
|
|
#include <net/cfg80211.h>
|
|
#include <ssv6200.h>
|
|
#include <hci/hctrl.h>
|
|
|
|
#include "lib.h"
|
|
#include "wow.h"
|
|
#include "dev.h"
|
|
#include "ap.h"
|
|
#include "init.h"
|
|
#include "ssv_skb.h"
|
|
#include "hw_scan.h"
|
|
#include <hal.h>
|
|
#include <linux_80211.h>
|
|
|
|
#ifdef CONFIG_SSV_SUPPORT_ANDROID
|
|
#include "ssv_pm.h"
|
|
#endif
|
|
|
|
#if LINUX_VERSION_CODE < KERNEL_VERSION(3,2,0) && LINUX_VERSION_CODE >= KERNEL_VERSION(3,0,0)
|
|
#include "linux_3_0_0.h"
|
|
#endif
|
|
|
|
#ifdef CONFIG_SSV6XXX_DEBUGFS
|
|
#include "ssv6xxx_debugfs.h"
|
|
#endif // CONFIG_SSV6XXX_DEBUGFS
|
|
|
|
//#define IRQ_PROC_RX_DATA
|
|
|
|
static u16 bits_per_symbol[][2] =
|
|
{
|
|
/* 20MHz 40MHz */
|
|
{ 26, 54 }, /* 0: BPSK */
|
|
{ 52, 108 }, /* 1: QPSK 1/2 */
|
|
{ 78, 162 }, /* 2: QPSK 3/4 */
|
|
{ 104, 216 }, /* 3: 16-QAM 1/2 */
|
|
{ 156, 324 }, /* 4: 16-QAM 3/4 */
|
|
{ 208, 432 }, /* 5: 64-QAM 2/3 */
|
|
{ 234, 486 }, /* 6: 64-QAM 3/4 */
|
|
{ 260, 540 }, /* 7: 64-QAM 5/6 */
|
|
};
|
|
|
|
static void ssv6xxx_process_rx_q(struct ssv_softc *sc, struct sk_buff_head *rx_q);
|
|
|
|
static int ssv6xxx_push_log_to_circbuf(struct ssv_dbg_log *dbg_log, const char *src, unsigned int len)
|
|
{
|
|
int i;
|
|
char *p = dbg_log->tail;
|
|
|
|
for (i = 0; i < len; i++) {
|
|
*p++ = src[i];
|
|
if (p == dbg_log->end) {
|
|
p = dbg_log->data;
|
|
}
|
|
/* Make sure pushing too much data just replaces old data */
|
|
if (dbg_log->size < dbg_log->totalsize) {
|
|
dbg_log->size++;
|
|
} else {
|
|
dbg_log->top++;
|
|
if (dbg_log->top == dbg_log->end) {
|
|
dbg_log->top = dbg_log->data;
|
|
}
|
|
}
|
|
}
|
|
|
|
dbg_log->tail = p;
|
|
return len;
|
|
}
|
|
|
|
void dbgprint(struct ssv_cmd_data *cmd_data, u32 log_ctrl, u32 log_id, const char *fmt,...)
|
|
{
|
|
char tbuf[32], log[256], buf[300];
|
|
va_list args;
|
|
int buf_len = 0, log_len = 0;
|
|
unsigned long long t;
|
|
unsigned long nanosec_rem;
|
|
|
|
if (log_ctrl & log_id){
|
|
if (!cmd_data->log_to_ram) {
|
|
va_start(args, fmt);
|
|
vprintk(fmt, args);
|
|
va_end(args);
|
|
} else {
|
|
va_start(args, fmt);
|
|
log_len = vsnprintf(log, sizeof(log)-1, fmt, args);
|
|
va_end(args);
|
|
if (log_len == (sizeof(log) - 1))
|
|
printk("%s(): log message is too long\n", __FUNCTION__);
|
|
log[log_len] = '\0';
|
|
|
|
/* add current time stamp */
|
|
t = cpu_clock(UINT_MAX);
|
|
nanosec_rem = do_div(t, 1000000000);
|
|
sprintf(tbuf, "[%5lu.%06lu]", (unsigned long)t, nanosec_rem / 1000);
|
|
buf_len = sprintf(buf, "%s %s", tbuf, log);
|
|
if ((cmd_data->dbg_log.data != NULL) && (cmd_data->dbg_log.totalsize != 0)) {
|
|
ssv6xxx_push_log_to_circbuf(&cmd_data->dbg_log, buf, buf_len);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void ssv6xxx_hci_dbgprint(void *argc, u32 log_id, const char *fmt,...)
|
|
{
|
|
|
|
struct ssv_softc *sc = (struct ssv_softc *)argc;
|
|
struct ssv_cmd_data *cmd_data = &sc->cmd_data;
|
|
char log[256];
|
|
va_list args;
|
|
int log_len = 0;
|
|
|
|
if (sc->log_ctrl & log_id) {
|
|
va_start(args, fmt);
|
|
log_len = vsnprintf(log, sizeof(log)-1, fmt, args);
|
|
va_end(args);
|
|
if (log_len == (sizeof(log) - 1))
|
|
printk("%s(): log message is too long\n", __FUNCTION__);
|
|
log[log_len] = '\0';
|
|
dbgprint(cmd_data, sc->log_ctrl, log_id, "%s", log);
|
|
|
|
}
|
|
}
|
|
|
|
#if 1
|
|
void _ssv6xxx_hexdump(const char *title, const u8 *buf,
|
|
size_t len)
|
|
{
|
|
size_t i;
|
|
if (buf == NULL) {
|
|
printk(" [NULL]");
|
|
}else{
|
|
printk(KERN_CONT"%s - hexdump(len=%lu):\n", title, (unsigned long) len);
|
|
for (i = 0; i < len; i++){
|
|
|
|
printk(KERN_CONT" %02x", buf[i]);
|
|
if(((i+1)%16) ==0)
|
|
printk(KERN_CONT"\n");
|
|
}
|
|
}
|
|
printk(KERN_CONT"\n-----------------------------\n");
|
|
}
|
|
#endif
|
|
|
|
|
|
void ssv6xxx_txbuf_free_skb(struct sk_buff *skb, void *args)
|
|
{
|
|
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3,4,0)
|
|
struct ssv_softc *sc = (struct ssv_softc *)args;
|
|
#endif
|
|
|
|
if (!skb)
|
|
return;
|
|
|
|
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3,4,0)
|
|
ieee80211_free_txskb(sc->hw, skb);
|
|
#else
|
|
dev_kfree_skb_any(skb);
|
|
#endif
|
|
|
|
}
|
|
|
|
int ssv6xxx_frame_hdrlen(struct ieee80211_hdr *hdr, bool is_ht)
|
|
{
|
|
#define CTRL_FRAME_INDEX(fc) ((hdr->frame_control-IEEE80211_STYPE_BACK_REQ)>>4)
|
|
/* Control Length: BAR, BA, PS-Poll, RTS, CTS, ACK, CF-End, CF-End+CF-Ack */
|
|
u16 fc, CTRL_FLEN[]= { 16, 16, 16, 16, 10, 10, 16, 16 };
|
|
int hdr_len = 24;
|
|
|
|
fc = hdr->frame_control;
|
|
if (ieee80211_is_ctl(fc))
|
|
hdr_len = CTRL_FLEN[CTRL_FRAME_INDEX(fc)];
|
|
else if (ieee80211_is_mgmt(fc)) {
|
|
if (ieee80211_has_order(fc))
|
|
hdr_len += ((is_ht==1)? 4: 0);
|
|
}
|
|
else {
|
|
if (ieee80211_has_a4(fc))
|
|
hdr_len += 6;
|
|
if (ieee80211_is_data_qos(fc)) {
|
|
hdr_len += 2;
|
|
if (ieee80211_has_order(hdr->frame_control) &&
|
|
is_ht==true)
|
|
hdr_len += 4;
|
|
}
|
|
}
|
|
|
|
|
|
return hdr_len;
|
|
}
|
|
|
|
/*
|
|
* rix - rate index
|
|
* pktlen - total bytes (delims + data + fcs + pads + pad delims)
|
|
* width - 0 for 20 MHz, 1 for 40 MHz
|
|
* half_gi - to use 4us v/s 3.6 us for symbol time
|
|
*/
|
|
u32 ssv6xxx_ht_txtime(u8 rix, int pktlen, int width,
|
|
int half_gi, bool is_gf)
|
|
{
|
|
u32 nbits, nsymbits, duration, nsymbols;
|
|
int streams;
|
|
|
|
/* find number of symbols: PLCP + data */
|
|
streams = 1; /* we only support 1 spatial stream */
|
|
nbits = (pktlen << 3) + OFDM_PLCP_BITS;
|
|
nsymbits = bits_per_symbol[rix % 8][width] * streams;
|
|
nsymbols = (nbits + nsymbits - 1) / nsymbits;
|
|
|
|
if (!half_gi)
|
|
duration = SYMBOL_TIME(nsymbols);
|
|
else
|
|
{
|
|
if (!is_gf)
|
|
duration = DIV_ROUND_UP(SYMBOL_TIME_HALFGI(nsymbols), 4)<<2;
|
|
else
|
|
duration = SYMBOL_TIME_HALFGI(nsymbols);
|
|
}
|
|
|
|
/* addup duration for legacy/ht training and signal fields */
|
|
duration += L_STF + L_LTF + L_SIG + HT_SIG + HT_STF + HT_LTF(streams)+HT_SIGNAL_EXT;
|
|
|
|
if (is_gf)
|
|
duration -=12;
|
|
|
|
duration += HT_SIFS_TIME;
|
|
|
|
return duration;
|
|
}
|
|
|
|
|
|
|
|
u32 ssv6xxx_non_ht_txtime(u8 phy, int kbps,
|
|
u32 frameLen, bool shortPreamble)
|
|
{
|
|
u32 bits_per_symbol, num_bits, num_symbols;
|
|
u32 phy_time, tx_time;
|
|
|
|
if (kbps == 0)
|
|
return 0;
|
|
|
|
switch (phy) {
|
|
case WLAN_RC_PHY_CCK:
|
|
phy_time = CCK_PREAMBLE_BITS + CCK_PLCP_BITS;
|
|
if (shortPreamble)
|
|
phy_time >>= 1;
|
|
num_bits = frameLen << 3;
|
|
tx_time = CCK_SIFS_TIME + phy_time + ((num_bits * 1000) / kbps);
|
|
break;
|
|
case WLAN_RC_PHY_OFDM:
|
|
bits_per_symbol = (kbps * OFDM_SYMBOL_TIME) / 1000;
|
|
num_bits = OFDM_PLCP_BITS + (frameLen << 3);
|
|
num_symbols = DIV_ROUND_UP(num_bits, bits_per_symbol);
|
|
tx_time = OFDM_SIFS_TIME + OFDM_PREAMBLE_TIME
|
|
+ (num_symbols * OFDM_SYMBOL_TIME);
|
|
break;
|
|
default:
|
|
printk("Unknown phy %u\n", phy);
|
|
BUG_ON(1);
|
|
tx_time = 0;
|
|
break;
|
|
}
|
|
|
|
return tx_time;
|
|
}
|
|
|
|
static void hw_crypto_key_clear(struct ieee80211_hw *hw, int index, struct ieee80211_key_conf *key,
|
|
struct ssv_vif_priv_data *vif_priv, struct ssv_sta_priv_data *sta_priv)
|
|
{
|
|
if ((index < 0) || (index >= 4))
|
|
return;
|
|
|
|
/*reset group key index*/
|
|
if (index > 0)
|
|
{
|
|
if (vif_priv)
|
|
vif_priv->group_key_idx = 0;
|
|
if (sta_priv)
|
|
sta_priv->group_key_idx = 0;
|
|
}
|
|
|
|
}
|
|
|
|
void _set_wep_sw_crypto_key (struct ssv_softc *sc,
|
|
struct ssv_vif_info *vif_info,
|
|
struct ssv_sta_info *sta_info,
|
|
void *param)
|
|
{
|
|
struct ssv_sta_priv_data *sta_priv = (struct ssv_sta_priv_data *)sta_info->sta->drv_priv;
|
|
struct ssv_vif_priv_data *vif_priv = (struct ssv_vif_priv_data *)vif_info->vif->drv_priv;
|
|
|
|
sta_priv->has_hw_encrypt = vif_priv->has_hw_encrypt;
|
|
sta_priv->has_hw_decrypt = vif_priv->has_hw_decrypt;
|
|
sta_priv->need_sw_encrypt = vif_priv->need_sw_encrypt;
|
|
sta_priv->need_sw_decrypt = vif_priv->need_sw_decrypt;
|
|
} // end of - _set_wep_sw_crypto_key -
|
|
|
|
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3,0,0)
|
|
static enum sec_type_en _prepare_key (struct ieee80211_key_conf *key, struct ssv_softc *sc)
|
|
{
|
|
enum sec_type_en cipher;
|
|
|
|
switch (key->cipher) {
|
|
case WLAN_CIPHER_SUITE_WEP40:
|
|
cipher = SECURITY_WEP40;
|
|
break;
|
|
case WLAN_CIPHER_SUITE_WEP104:
|
|
cipher = SECURITY_WEP104;
|
|
break;
|
|
case WLAN_CIPHER_SUITE_TKIP:
|
|
if (USE_MAC80211_CIPHER(sc->sh))
|
|
key->flags |= IEEE80211_KEY_FLAG_GENERATE_MMIC;
|
|
cipher = SECURITY_TKIP;
|
|
break;
|
|
case WLAN_CIPHER_SUITE_CCMP:
|
|
#if LINUX_VERSION_CODE < KERNEL_VERSION(3,7,0)
|
|
if(sc->ccmp_h_sel)
|
|
key->flags |= (IEEE80211_KEY_FLAG_SW_MGMT | IEEE80211_KEY_FLAG_GENERATE_IV);
|
|
else
|
|
key->flags |= IEEE80211_KEY_FLAG_SW_MGMT;
|
|
#else
|
|
if(sc->ccmp_h_sel)
|
|
key->flags |= (IEEE80211_KEY_FLAG_SW_MGMT_TX | IEEE80211_KEY_FLAG_RX_MGMT | IEEE80211_KEY_FLAG_GENERATE_IV);
|
|
else
|
|
key->flags |= (IEEE80211_KEY_FLAG_SW_MGMT_TX | IEEE80211_KEY_FLAG_RX_MGMT);
|
|
#endif
|
|
if (USE_MAC80211_CIPHER(sc->sh))
|
|
key->flags |= IEEE80211_KEY_FLAG_GENERATE_MMIC;
|
|
cipher = SECURITY_CCMP;
|
|
break;
|
|
default:
|
|
cipher = SECURITY_CIPHER_INVALID;
|
|
break;
|
|
}
|
|
|
|
return cipher;
|
|
} // end of - _prepare_key -
|
|
#else
|
|
static enum sec_type_en _prepare_key (struct ieee80211_key_conf *key, struct ssv_softc *sc)
|
|
{
|
|
enum sec_type_en cipher;
|
|
|
|
switch (key->alg) {
|
|
case ALG_WEP:
|
|
if(key->keylen == 5) // 40 bits / 8 bit_per_byte = 5 bytes
|
|
cipher = SECURITY_WEP40;
|
|
else
|
|
cipher = SECURITY_WEP104;
|
|
break;
|
|
case ALG_TKIP:
|
|
cipher = SECURITY_TKIP;
|
|
key->flags |= IEEE80211_KEY_FLAG_GENERATE_MMIC;
|
|
break;
|
|
case ALG_CCMP:
|
|
cipher = SECURITY_CCMP;
|
|
key->flags |= IEEE80211_KEY_FLAG_SW_MGMT;
|
|
break;
|
|
default:
|
|
cipher = SECURITY_CIPHER_INVALID;
|
|
break;
|
|
}
|
|
|
|
return cipher;
|
|
} // end of - _prepare_key -
|
|
#endif // LINUX_VERSION_CODE >= KERNEL_VERSION(3,0,0)
|
|
|
|
int _set_key_wep (struct ssv_softc *sc, struct ssv_vif_priv_data *vif_priv,
|
|
struct ssv_sta_priv_data *sta_priv, enum sec_type_en cipher,
|
|
struct ieee80211_key_conf *key)
|
|
{
|
|
int ret = 0;
|
|
struct ssv_vif_info *vif_info = &sc->vif_info[vif_priv->vif_idx];
|
|
|
|
printk(KERN_ERR "Set WEP %02X %02X %02X %02X %02X %02X %02X %02X... (%d %d)\n",
|
|
key->key[0], key->key[1], key->key[2], key->key[3],
|
|
key->key[4], key->key[5], key->key[6], key->key[7],
|
|
key->keyidx, key->keylen);
|
|
|
|
if ( SSV_WEP_USE_HW_CIPHER(sc, vif_priv))
|
|
{
|
|
vif_priv->has_hw_decrypt = true;
|
|
vif_priv->has_hw_encrypt = true;//(vif_priv->force_sw_encrypt == false);
|
|
vif_priv->need_sw_decrypt = false;
|
|
vif_priv->need_sw_encrypt = false;
|
|
vif_priv->use_mac80211_decrypt = false;
|
|
} else {
|
|
if (USE_MAC80211_CIPHER(sc->sh)){
|
|
|
|
vif_priv->need_sw_encrypt = false;
|
|
vif_priv->need_sw_decrypt = false;
|
|
|
|
if (USE_MAC80211_RX(sc->sh)) {
|
|
vif_priv->has_hw_decrypt = false;
|
|
vif_priv->use_mac80211_decrypt = true;
|
|
ssv6xxx_foreach_vif_sta(sc, vif_info, _set_wep_sw_crypto_key, NULL);
|
|
} else {
|
|
vif_priv->has_hw_decrypt = true;
|
|
vif_priv->use_mac80211_decrypt = false;
|
|
}
|
|
vif_priv->has_hw_encrypt = false;
|
|
dev_info(sc->dev, "[Local Crypto]: Use MAC80211's encrypter.\n");
|
|
ret = -EOPNOTSUPP;
|
|
} else {
|
|
vif_priv->has_hw_decrypt = false;
|
|
vif_priv->has_hw_encrypt = false;
|
|
vif_priv->need_sw_decrypt = false;
|
|
vif_priv->need_sw_encrypt = false;
|
|
vif_priv->use_mac80211_decrypt = true;
|
|
|
|
ssv6xxx_foreach_vif_sta(sc, vif_info, _set_wep_sw_crypto_key, NULL);
|
|
|
|
ret = -EOPNOTSUPP;
|
|
}
|
|
}
|
|
|
|
vif_priv->pair_cipher = vif_priv->group_cipher = cipher;
|
|
vif_priv->wep_cipher = cipher;
|
|
vif_priv->wep_idx = key->keyidx;
|
|
SSV_SET_WEP_KEY(sc, vif_priv, sta_priv, cipher, key);
|
|
|
|
vif_priv->is_security_valid = true;
|
|
|
|
return ret;
|
|
} // end of - _set_key_wep -
|
|
|
|
static int _set_pairwise_key_tkip_ccmp (struct ssv_softc *sc, struct ssv_vif_priv_data *vif_priv,
|
|
struct ssv_sta_priv_data *sta_priv, enum sec_type_en cipher,
|
|
struct ieee80211_key_conf *key)
|
|
{
|
|
int ret = 0;
|
|
const char *cipher_name = (cipher == SECURITY_CCMP) ? "CCMP" : "TKIP";
|
|
|
|
if (sta_priv == NULL) {
|
|
dev_err(sc->dev, "Setting pairwise TKIP/CCMP key to NULL STA.\n");
|
|
return -EOPNOTSUPP;
|
|
}
|
|
|
|
// this is a routine for judge hw can use hw cipher for wpa.
|
|
if (SSV_PAIRWISE_WPA_USE_HW_CIPHER( sc, vif_priv, cipher, sta_priv))
|
|
{
|
|
sta_priv->has_hw_decrypt = true;
|
|
sta_priv->need_sw_decrypt = false;
|
|
sta_priv->use_mac80211_decrypt = false;
|
|
|
|
if (SSV_USE_HW_ENCRYPT(cipher, sc, sta_priv, vif_priv))
|
|
{
|
|
dev_info(sc->dev, "STA %d uses HW encrypter for pairwise.\n", sta_priv->sta_idx);
|
|
sta_priv->has_hw_encrypt = true;
|
|
sta_priv->need_sw_encrypt = false;
|
|
sta_priv->use_mac80211_decrypt = false;
|
|
ret = 0;
|
|
}
|
|
else
|
|
{
|
|
sta_priv->has_hw_encrypt = false;
|
|
sta_priv->need_sw_encrypt = false;
|
|
sta_priv->use_mac80211_decrypt = true;
|
|
ret = -EOPNOTSUPP;
|
|
}
|
|
} else {
|
|
if ( USE_MAC80211_CIPHER(sc->sh) ){
|
|
vif_priv->need_sw_encrypt = false;
|
|
vif_priv->need_sw_decrypt = false;
|
|
|
|
if (USE_MAC80211_RX(sc->sh)) {
|
|
vif_priv->has_hw_decrypt = false;
|
|
vif_priv->use_mac80211_decrypt = true;
|
|
|
|
} else {
|
|
vif_priv->has_hw_decrypt = true;
|
|
vif_priv->use_mac80211_decrypt = false;
|
|
}
|
|
vif_priv->has_hw_encrypt = false;
|
|
dev_info(sc->dev, "[Local Crypto]: Use MAC80211's encrypter.\n");
|
|
ret = -EOPNOTSUPP;
|
|
|
|
} else
|
|
{
|
|
sta_priv->has_hw_encrypt = false;
|
|
sta_priv->has_hw_decrypt = false;
|
|
dev_err(sc->dev, "STA %d MAC80211's %s cipher.\n", sta_priv->sta_idx, cipher_name);
|
|
sta_priv->need_sw_encrypt = false;
|
|
sta_priv->need_sw_decrypt = false;
|
|
sta_priv->use_mac80211_decrypt = true;
|
|
ret = -EOPNOTSUPP;
|
|
}
|
|
}
|
|
|
|
if (sta_priv->has_hw_encrypt || sta_priv->has_hw_decrypt)
|
|
{
|
|
SSV_SET_PAIRWISE_CIPHER_TYPE(sc->sh, cipher, sc->sta_info[sta_priv->sta_idx].hw_wsid);/* sta_info is already protected by ssv6200_set_key(). */
|
|
SSV_WRITE_PAIRWISE_KEY_TO_HW(sc, key->keyidx, cipher,
|
|
key->key, key->keylen, key,
|
|
vif_priv, sta_priv);
|
|
}
|
|
|
|
if ( (vif_priv->has_hw_encrypt || vif_priv->has_hw_decrypt)
|
|
&& (vif_priv->group_key_idx > 0))
|
|
{
|
|
SSV_SET_AES_TKIP_HW_CRYPTO_GROUP_KEY(sc, &sc->vif_info[vif_priv->vif_idx],
|
|
&sc->sta_info[sta_priv->sta_idx], &vif_priv->group_key_idx);
|
|
}
|
|
return ret;
|
|
} // end of - _set_pairwise_key_tkip_ccmp -
|
|
|
|
static int _set_group_key_tkip_ccmp (struct ssv_softc *sc, struct ssv_vif_priv_data *vif_priv,
|
|
struct ssv_sta_priv_data *sta_priv, enum sec_type_en cipher,
|
|
struct ieee80211_key_conf *key)
|
|
{
|
|
int ret = 0;
|
|
const char *cipher_name = (cipher == SECURITY_CCMP) ? "CCMP" : "TKIP";
|
|
|
|
vif_priv->group_cipher = cipher;
|
|
|
|
if (SSV_GROUP_WPA_USE_HW_CIPHER( sc, vif_priv, cipher))
|
|
{
|
|
// Hardware encryption is only supported for MPDU. AMPDU connection requires
|
|
// software encryption solution.
|
|
dev_info(sc->dev, "VIF %d uses HW %s cipher for group.\n", vif_priv->vif_idx, cipher_name);
|
|
#ifdef USE_MAC80211_DECRYPT_BROADCAST
|
|
vif_priv->has_hw_decrypt = false;
|
|
ret = -EOPNOTSUPP;
|
|
#else
|
|
vif_priv->has_hw_decrypt = true;
|
|
#endif // USE_MAC80211_DECRYPT_BROADCAST
|
|
vif_priv->has_hw_encrypt = true;
|
|
vif_priv->need_sw_decrypt = false;
|
|
vif_priv->need_sw_encrypt = false;
|
|
vif_priv->use_mac80211_decrypt = false;
|
|
}
|
|
else {
|
|
|
|
if (USE_MAC80211_CIPHER(sc->sh) ){
|
|
vif_priv->need_sw_encrypt = false;
|
|
vif_priv->need_sw_decrypt = false;
|
|
if (USE_MAC80211_RX(sc->sh)) {
|
|
vif_priv->has_hw_decrypt = false;
|
|
vif_priv->use_mac80211_decrypt = true;
|
|
|
|
} else {
|
|
vif_priv->has_hw_decrypt = true;
|
|
vif_priv->use_mac80211_decrypt = false;
|
|
}
|
|
vif_priv->has_hw_encrypt = false;
|
|
dev_info(sc->dev, "[Local Crypto]: Use MAC80211's encrypter.\n");
|
|
ret = -EOPNOTSUPP;
|
|
|
|
} else
|
|
{
|
|
vif_priv->has_hw_decrypt = false;
|
|
vif_priv->has_hw_encrypt = false;
|
|
dev_err(sc->dev, "VIF %d uses MAC80211's %s cipher.\n", vif_priv->vif_idx, cipher_name);
|
|
vif_priv->need_sw_encrypt = false;
|
|
vif_priv->need_sw_decrypt = false;
|
|
vif_priv->use_mac80211_decrypt = true;
|
|
ret = -EOPNOTSUPP;
|
|
}
|
|
}
|
|
|
|
if (vif_priv->has_hw_encrypt || vif_priv->has_hw_decrypt)
|
|
{
|
|
int cipher_type;
|
|
|
|
#ifdef USE_MAC80211_DECRYPT_BROADCAST
|
|
cipher_type = SECURITY_NONE;
|
|
#else
|
|
cipher_type = cipher;
|
|
#endif // USE_MAC80211_DECRYPT_BROADCAST
|
|
|
|
SSV_SET_GROUP_CIPHER_TYPE(sc->sh, vif_priv, cipher_type);
|
|
|
|
key->hw_key_idx = key->keyidx;
|
|
|
|
SSV_WRITE_GROUP_KEY_TO_HW(sc, key->keyidx, cipher,
|
|
key->key, key->keylen, key,
|
|
vif_priv, sta_priv);
|
|
}
|
|
|
|
vif_priv->is_security_valid = true;
|
|
|
|
SSV_CHK_DUAL_VIF_CHG_RX_FLOW( sc, vif_priv);
|
|
return ret;
|
|
} // end of - _set_group_key_tkip_ccmp -
|
|
|
|
static int _set_key_tkip_ccmp (struct ssv_softc *sc, struct ssv_vif_priv_data *vif_priv,
|
|
struct ssv_sta_priv_data *sta_priv, enum sec_type_en cipher,
|
|
struct ieee80211_key_conf *key)
|
|
{
|
|
if (key->keyidx == 0) {
|
|
if (sta_priv->last_pn != 0)
|
|
sta_priv->unicast_key_changed = true;
|
|
return _set_pairwise_key_tkip_ccmp(sc, vif_priv, sta_priv, cipher, key);
|
|
} else {
|
|
if (vif_priv->last_pn_mcast != 0)
|
|
vif_priv->group_key_changed = true;
|
|
return _set_group_key_tkip_ccmp(sc, vif_priv, sta_priv, cipher, key);
|
|
}
|
|
} // end of - _set_key_tkip_ccmp -
|
|
|
|
/*
|
|
* The difference of Set key flow between STA and AP mode:
|
|
* STA: AP:
|
|
* add_vif add_vif
|
|
* add_sta set_key (group)
|
|
* set_key(pairwise) add_sta
|
|
* set_key(group) set_key (pairwise)
|
|
*/
|
|
static int ssv6200_set_key(struct ieee80211_hw *hw,
|
|
enum set_key_cmd cmd,
|
|
struct ieee80211_vif *vif,
|
|
struct ieee80211_sta *sta,
|
|
struct ieee80211_key_conf *key)
|
|
{
|
|
struct ssv_softc *sc = hw->priv;
|
|
int ret = 0;
|
|
enum sec_type_en cipher = SECURITY_NONE;
|
|
int sta_idx = (-1);
|
|
struct ssv_sta_info *sta_info = NULL;/* For mac80211 cb, we use sc->mutex to protect sta_info instead of sta_info_sem. */
|
|
struct ssv_sta_priv_data *sta_priv = NULL;
|
|
struct ssv_vif_priv_data *vif_priv = (struct ssv_vif_priv_data *)vif->drv_priv;
|
|
struct ssv_vif_info *vif_info = &sc->vif_info[vif_priv->vif_idx];
|
|
|
|
mutex_lock(&sc->mutex);
|
|
|
|
if ((vif_info->if_type != NL80211_IFTYPE_AP) && (vif_info->if_type != NL80211_IFTYPE_P2P_GO) && (NULL==sta) &&
|
|
((key->keyidx == 0) && (key->cipher != WLAN_CIPHER_SUITE_WEP40) && (key->cipher != WLAN_CIPHER_SUITE_WEP104)))
|
|
{
|
|
printk("Warning: ssv6200_set_key return; key->cipher=0x%x\r\n", key->cipher);
|
|
mutex_unlock(&sc->mutex);
|
|
return 0;
|
|
}
|
|
|
|
if (sta)
|
|
{
|
|
sta_priv = (struct ssv_sta_priv_data *)sta->drv_priv;
|
|
if(sta_priv->sta_idx == -1)
|
|
{
|
|
dev_warn(sc->dev, "%s(): sta_info is gone.\n", __func__);
|
|
if(!(sc->sc_flags & SC_OP_HW_RESET))
|
|
{
|
|
ret = -ENODATA;
|
|
}
|
|
goto out;
|
|
}
|
|
sta_idx = sta_priv->sta_idx;
|
|
//down_read(&sc->sta_info_sem);/* For mac80211 cb, we use sc->mutex to protect sta_info instead of sta_info_sem. */
|
|
sta_info = &sc->sta_info[sta_idx];
|
|
if ((sta_info->s_flags & STA_FLAG_VALID) == 0) {
|
|
dev_warn(sc->dev, "%s(): sta_info is gone.\n", __func__);
|
|
ret = -ENODATA;
|
|
goto out;
|
|
}
|
|
}
|
|
|
|
BUG_ON((cmd!=SET_KEY) && (cmd!=DISABLE_KEY));
|
|
|
|
if (!(sc->sh->cfg.hw_caps & SSV6200_HW_CAP_SECURITY))
|
|
{
|
|
dev_warn(sc->dev, "HW does not support security.\n");
|
|
ret = -EOPNOTSUPP;
|
|
goto out;
|
|
}
|
|
|
|
/* hw security just support in valid wsid */
|
|
if (sta_info && (sta_info->hw_wsid == (-1)))
|
|
{
|
|
dev_warn(sc->dev, "Add STA without HW resource. Use MAC80211's solution.\n");
|
|
ret = -EOPNOTSUPP;
|
|
goto out;
|
|
}
|
|
|
|
cipher = _prepare_key(key, sc);
|
|
|
|
dev_err(sc->dev,"Set key VIF %d VIF type %d STA %d algorithm = %d, key->keyidx = %d, cmd = %d\n",
|
|
vif_priv->vif_idx, vif->type, sta_idx, cipher, key->keyidx, cmd);
|
|
|
|
if (cipher == SECURITY_CIPHER_INVALID)
|
|
{
|
|
dev_warn(sc->dev, "Unsupported cipher type.\n");
|
|
ret = -EOPNOTSUPP;
|
|
goto out;
|
|
}
|
|
|
|
switch (cmd)
|
|
{
|
|
case SET_KEY:
|
|
{
|
|
// Debug code
|
|
#if 0
|
|
int i;
|
|
printk("================================SET KEY=======================================\n");
|
|
|
|
if (sta_info == NULL)
|
|
{
|
|
printk("NULL STA cmd[%d] alg[%d] keyidx[%d] ", cmd, algorithm, key->keyidx);
|
|
}
|
|
else
|
|
{
|
|
printk("STA WSID[%d] cmd[%d] alg[%d] keyidx[%d] ", sta_info->hw_wsid, cmd, algorithm, key->keyidx);
|
|
}
|
|
|
|
printk("SET_KEY index[%d] flags[0x%x] algorithm[%d] key->keylen[%d]\n",
|
|
key->keyidx, key->flags, algorithm, key->keylen);
|
|
for(i = 0; i < key->keylen; i++)
|
|
{
|
|
printk("[%02x]", key->key[i]);
|
|
}
|
|
printk("\n");
|
|
printk("===============================================================================\n");
|
|
#endif // 0
|
|
// dev_info(sc->dev, "Set key %d VIF %p, STA %p\n", key->keyidx, vif, sta);
|
|
switch (cipher)
|
|
{
|
|
case SECURITY_WEP40:
|
|
case SECURITY_WEP104:
|
|
ret = _set_key_wep(sc, vif_priv, sta_priv, cipher, key);
|
|
key->hw_key_idx = key->keyidx;
|
|
break;
|
|
case SECURITY_TKIP:
|
|
case SECURITY_CCMP:
|
|
ret = _set_key_tkip_ccmp(sc, vif_priv, sta_priv, cipher, key);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
if (sta){
|
|
struct ssv_sta_priv_data *first_sta_priv =
|
|
list_first_entry(&vif_priv->sta_list, struct ssv_sta_priv_data, list);
|
|
if (first_sta_priv->sta_idx == sta_priv->sta_idx){
|
|
vif_priv->pair_cipher = cipher; // keep first station cipher mode for TDLS check
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
case DISABLE_KEY:
|
|
{
|
|
int another_vif_idx = ((vif_priv->vif_idx + 1) % 2);
|
|
struct ssv_vif_priv_data *another_vif_priv =
|
|
(struct ssv_vif_priv_data *)sc->vif_info[another_vif_idx].vif_priv;
|
|
#if 0
|
|
printk("================================DEL KEY=======================================\n");
|
|
if(sta_info == NULL){
|
|
printk("NULL STA cmd[%d] alg[%d] keyidx[%d] ", cmd, cipher, key->keyidx);
|
|
}
|
|
else{
|
|
printk("STA WSID[%d] cmd[%d] alg[%d] keyidx[%d] ", sta_info->hw_wsid, cmd, cipher, key->keyidx);
|
|
}
|
|
|
|
printk("DISABLE_KEY index[%d]\n",key->keyidx);
|
|
printk("==============================================================================\n");
|
|
#endif
|
|
#if 0
|
|
if(key->keyidx == 0)
|
|
{
|
|
sta_info->ampdu_ccmp_encrypt = false;
|
|
}
|
|
#endif // 0
|
|
|
|
|
|
if (another_vif_priv != NULL) { // for dual vif
|
|
SSV_RESTORE_RX_FLOW(sc, vif_priv, sta);
|
|
}
|
|
|
|
// clear cipher type.
|
|
if ( sta == NULL){
|
|
vif_priv->group_cipher = SECURITY_NONE;
|
|
if ((another_vif_priv == NULL)
|
|
|| ((another_vif_priv != NULL) && (!SSV6XXX_USE_HW_DECRYPT(another_vif_priv)))){
|
|
SSV_SET_GROUP_CIPHER_TYPE(sc->sh, vif_priv, SECURITY_NONE);
|
|
}
|
|
} else {
|
|
|
|
if ((vif_info->if_type != NL80211_IFTYPE_AP) && (vif_info->if_type != NL80211_IFTYPE_P2P_GO) && (another_vif_priv == NULL)){
|
|
struct ssv_sta_priv_data *first_sta_priv =
|
|
list_first_entry(&vif_priv->sta_list, struct ssv_sta_priv_data, list);
|
|
if (sta_priv == first_sta_priv){
|
|
SSV_SET_PAIRWISE_CIPHER_TYPE(sc->sh, SECURITY_NONE, sta_info->hw_wsid);
|
|
}
|
|
}
|
|
//vif_priv->pair_cipher = SECURITY_NONE; // should not clear cipher type, since other sta may stay in security mode
|
|
}
|
|
|
|
if ((cipher == SECURITY_WEP40) || (cipher == SECURITY_WEP104))
|
|
{
|
|
vif_priv->wep_idx = -1;
|
|
vif_priv->wep_cipher = -1;
|
|
}
|
|
|
|
if ((cipher == SECURITY_TKIP) || (cipher == SECURITY_CCMP))
|
|
{
|
|
printk(KERN_ERR "Clear key %d VIF %d, STA %d\n",
|
|
key->keyidx, (vif != NULL), (sta != NULL));
|
|
hw_crypto_key_clear(hw, key->keyidx, key, vif_priv, sta_priv);
|
|
}
|
|
// if (key->keyidx == 0)
|
|
{
|
|
if ((key->keyidx == 0) && (sta_priv != NULL))
|
|
{
|
|
sta_priv->has_hw_decrypt = false;
|
|
sta_priv->has_hw_encrypt = false;
|
|
sta_priv->need_sw_encrypt = false;
|
|
sta_priv->use_mac80211_decrypt = false;
|
|
|
|
}
|
|
|
|
if ((vif_priv->is_security_valid) && (key->keyidx != 0))
|
|
{
|
|
vif_priv->is_security_valid = false;
|
|
}
|
|
}
|
|
ret = 0;
|
|
}
|
|
break;
|
|
default:
|
|
ret = -EINVAL;
|
|
}
|
|
|
|
if(sta_priv != NULL)
|
|
{
|
|
printk("sta: hw_en:%d, sw_en:%d, hw_de:%d, sw_de:%d,\n",
|
|
(sta_priv->has_hw_encrypt==true),(sta_priv->need_sw_encrypt==true),
|
|
(sta_priv->has_hw_decrypt==true),(sta_priv->need_sw_decrypt==true));
|
|
}
|
|
|
|
if(vif_priv)
|
|
{
|
|
printk("vif: hw_en:%d, sw_en:%d, hw_de:%d, sw_de:%d, valid:%d\n",
|
|
(vif_priv->has_hw_encrypt==true),(vif_priv->need_sw_encrypt==true),
|
|
(vif_priv->has_hw_decrypt==true),(vif_priv->need_sw_decrypt==true), (vif_priv->is_security_valid==true));
|
|
}
|
|
|
|
|
|
out:
|
|
//if (sta) {
|
|
//up_read(&sc->sta_info_sem);/* For mac80211 cb, we use sc->mutex to protect sta_info instead of sta_info_sem. */
|
|
//}
|
|
mutex_unlock(&sc->mutex);
|
|
printk(KERN_ERR "SET KEY %d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
static void _ssv6xxx_get_current_rate(struct ssv_softc *sc, struct ieee80211_tx_info *tx_info,
|
|
struct sk_buff *skb, int wsid)
|
|
{
|
|
if (wsid >= SSV_NUM_STA) {
|
|
return;
|
|
}
|
|
|
|
if (sc->sta_info[wsid].s_flags & STA_FLAG_VALID)
|
|
SSV_RC_MAC80211_TX_RATE_IDX(sc, (int)sc->hw_cur_rate[wsid], tx_info);
|
|
}
|
|
|
|
void ssvxxx_complete_tx_skb(struct ssv_softc *sc, struct ieee80211_tx_info *tx_info,
|
|
struct sk_buff *skb, int wsid)
|
|
{
|
|
ieee80211_tx_info_clear_status(tx_info);
|
|
tx_info->flags |= IEEE80211_TX_STAT_ACK;
|
|
tx_info->status.ack_signal = 100; /* ???? */
|
|
tx_info->status.rates[1].idx = -1;
|
|
_ssv6xxx_get_current_rate(sc, tx_info, skb, wsid);
|
|
ieee80211_tx_status(sc->hw, skb);
|
|
}
|
|
|
|
static void ssv6xxx_setup_ampdu_session(void *priv, struct ieee80211_sta *sta, struct sk_buff *skb)
|
|
{
|
|
struct ssv_softc *sc = (struct ssv_softc *)priv;
|
|
struct ssv_sta_priv_data *ssv_sta_priv = NULL;
|
|
struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb);
|
|
struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)skb->data;
|
|
u8 *skb_qos_ctl = ieee80211_get_qos_ctl(hdr);
|
|
u8 tid_no = skb_qos_ctl[0] & 0xf;
|
|
|
|
ssv_sta_priv = (struct ssv_sta_priv_data *)sta->drv_priv;
|
|
if (ssv_sta_priv == NULL)
|
|
return;
|
|
|
|
if (!ieee80211_is_data_qos(hdr->frame_control))
|
|
return;
|
|
|
|
if ((sta->ht_cap.ht_supported == false) || (info->flags & IEEE80211_TX_CTL_AMPDU))
|
|
return;
|
|
|
|
if ((!(skb->protocol == cpu_to_be16(ETH_P_PAE))) &&
|
|
conf_is_ht(&sc->hw->conf)) {
|
|
|
|
|
|
if (skb_get_queue_mapping(skb) != IEEE80211_AC_VO) { // Not VO AC
|
|
if ((sta->ht_cap.ht_supported == true) &&
|
|
(ssv_sta_priv->ampdu_tid_state[tid_no] == SSV_AMPDU_1_3_TX_STOP)) {
|
|
|
|
//spin_lock_bh(&ssv_sta_priv->ampdu_ctrl_lock);
|
|
ssv_sta_priv->ampdu_tid_state[tid_no] = SSV_AMPDU_1_3_TX_PROGRESS;
|
|
//spin_unlock_bh(&ssv_sta_priv->ampdu_ctrl_lock);
|
|
#if LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,32)
|
|
ieee80211_start_tx_ba_session(sc->hw, (u8*)(sta->addr), (u16)tid_no);
|
|
#elif LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,37)
|
|
ieee80211_start_tx_ba_session(sta, tid_no);
|
|
#else
|
|
ieee80211_start_tx_ba_session(sta, tid_no, 0);
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void ssv6xxx_tx_done_process(struct work_struct *work)
|
|
{
|
|
struct ssv_softc *sc = container_of(work, struct ssv_softc, tx_done_work);
|
|
struct ieee80211_tx_info *tx_info;
|
|
struct sk_buff *skb;
|
|
struct ieee80211_hdr *hdr;
|
|
struct ieee80211_sta *sta = NULL;
|
|
int reason = -1;
|
|
int wsid = -1;
|
|
|
|
while ((skb = skb_dequeue(&sc->tx_done_q)))
|
|
{
|
|
struct SKB_info_st *mpdu_skb_info_p = (SKB_info *)(skb->head);
|
|
|
|
if (SSV_GET_TX_DESC_CTYPE(sc->sh, skb) != M2_TXREQ)
|
|
{
|
|
ssv_skb_free(sc, skb);
|
|
continue;
|
|
}
|
|
|
|
/* filter out self-generate nullfun frame */
|
|
if (SSV_NULLFUN_FRAME_FILTER(sc->sh, skb)) {
|
|
ssv_skb_free(sc, skb);
|
|
continue;
|
|
}
|
|
|
|
if (!(sc->sc_flags & SC_OP_IF_UP))
|
|
{
|
|
ssv_skb_free(sc, skb);
|
|
continue;
|
|
}
|
|
|
|
|
|
if (true == mpdu_skb_info_p->raw_data)
|
|
{
|
|
ssv_skb_free(sc, skb);
|
|
continue;
|
|
}
|
|
|
|
reason = HAL_GET_TX_DESC_REASON(sc->sh, skb);
|
|
if (reason == ID_TRAP_SW_TXTPUT)
|
|
{
|
|
ssv_skb_free(sc, skb);
|
|
continue;
|
|
}
|
|
|
|
wsid = HAL_GET_TX_DESC_WSID(sc->sh, skb);
|
|
tx_info = IEEE80211_SKB_CB(skb);
|
|
skb_pull(skb, SSV_GET_TX_DESC_SIZE(sc->sh));
|
|
|
|
/* update ampdu state */
|
|
if (wsid <= SSV_SW_TXQ_ID_STAMAX)
|
|
HCI_TXQ_LOCK_BY_STA(sc->sh, wsid);
|
|
|
|
sta = (struct ieee80211_sta *)mpdu_skb_info_p->sta;
|
|
hdr = (struct ieee80211_hdr *)skb->data;
|
|
if (sta) {
|
|
if (ssv6xxx_compare_ether_addr(hdr->addr1, sta->addr)) {
|
|
if (wsid <= SSV_SW_TXQ_ID_STAMAX){
|
|
HCI_TXQ_UNLOCK_BY_STA(sc->sh, wsid);}
|
|
ssv_skb_free(sc, skb);
|
|
continue;
|
|
} else {
|
|
ssv6xxx_setup_ampdu_session((void *)sc, sta, skb);
|
|
}
|
|
}
|
|
if (wsid <= SSV_SW_TXQ_ID_STAMAX)
|
|
HCI_TXQ_UNLOCK_BY_STA(sc->sh, wsid);
|
|
|
|
if (mpdu_skb_info_p->directly_ack) {
|
|
ssv_skb_free(sc, skb);
|
|
} else {
|
|
ssvxxx_complete_tx_skb(sc, tx_info, skb, wsid);
|
|
}
|
|
ssv6200_tx_flow_control(sc, false, false);
|
|
}
|
|
}
|
|
|
|
void ssv6xxx_post_tx_cb(struct sk_buff_head *skb_head, void *args)
|
|
{
|
|
struct ssv_softc *sc=(struct ssv_softc *)args;
|
|
struct ssv6xxx_hci_ctrl *hci_ctrl = sc->sh->hci.hci_ctrl;
|
|
struct sk_buff *skb;
|
|
bool tx_done = false;
|
|
|
|
/* the function should not drop frame
|
|
* it just distinguish frame type to wakeup different task*/
|
|
while ((skb=skb_dequeue(skb_head)))
|
|
{
|
|
atomic_dec(&hci_ctrl->sw_txq_cnt);
|
|
if (SSV_GET_SW_ACK_CTL(sc->sh, skb)) {
|
|
skb_queue_tail(&sc->tx_ack_ctl_q, skb);
|
|
} else {
|
|
skb_queue_tail(&sc->tx_done_q, skb);
|
|
tx_done = true;
|
|
}
|
|
}
|
|
// start workqueue to handle tx done
|
|
if (tx_done)
|
|
queue_work(sc->tx_done_wq, &sc->tx_done_work);
|
|
}
|
|
|
|
void ssv6xxx_hci_update_flowctl_cb(void *args)
|
|
{
|
|
struct ssv_softc *sc=(struct ssv_softc *)args;
|
|
ssv6200_tx_flow_control(sc, false, false);
|
|
}
|
|
|
|
static bool ssv6xxx_special_tx_frame(struct sk_buff *skb)
|
|
{
|
|
#define SSV_SNAP_SIZE 6
|
|
#define SSV_PROTOC_TYPE_SIZE 2
|
|
|
|
u8 mac_hdr_len = ieee80211_get_hdrlen_from_skb(skb);
|
|
u8 offset;
|
|
u16 ether_type;
|
|
const struct iphdr *ip;
|
|
struct udphdr *udp;
|
|
bool retval = false;
|
|
|
|
offset = mac_hdr_len + SSV_SNAP_SIZE;
|
|
ether_type = be16_to_cpup((__be16 *)(skb->data + offset));
|
|
|
|
switch (ether_type) {
|
|
case ETH_P_PAE:
|
|
case ETH_P_ARP:
|
|
retval = true;
|
|
break;
|
|
case ETH_P_IP:
|
|
ip = (struct iphdr *)((u8 *)skb->data + offset + SSV_PROTOC_TYPE_SIZE);
|
|
if (IPPROTO_UDP == ip->protocol) {
|
|
udp = (struct udphdr *)((u8 *)ip + (ip->ihl << 2));
|
|
//DHCP, source port = 68(bootpc) & dest. port = 67(bootps)
|
|
// source port = 67(bootps) & dest. port = 68(bootpc)
|
|
if (((ntohs(udp->dest) == 67) && (ntohs(udp->source) == 68)) ||
|
|
((ntohs(udp->source) == 67) && (ntohs(udp->dest) == 68))) {
|
|
retval = true;
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return retval;
|
|
}
|
|
|
|
static int ssv6xxx_txq_id_mapping(struct ssv_softc *sc, struct sk_buff *skb)
|
|
{
|
|
struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)skb->data;
|
|
//struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb);
|
|
struct SKB_info_st *skb_info = (struct SKB_info_st *)skb->head;
|
|
struct ieee80211_sta *sta = NULL;
|
|
struct ssv_sta_priv_data *ssv_sta_priv = NULL;
|
|
int txq_idx = SSV_SW_TXQ_ID_MNG;
|
|
|
|
/**
|
|
* Decide frame tid & hardware output queue for outgoing
|
|
* frames. Management frames have a dedicate output queue
|
|
* with higher priority in station mode.
|
|
*/
|
|
if (ieee80211_is_mgmt(hdr->frame_control) ||
|
|
ieee80211_is_nullfunc(hdr->frame_control) ||
|
|
ieee80211_is_qos_nullfunc(hdr->frame_control)) {
|
|
txq_idx = SSV_SW_TXQ_ID_MNG;
|
|
} else {
|
|
sta = skb_info->sta;
|
|
if (sta == NULL) {
|
|
txq_idx = SSV_SW_TXQ_ID_MNG;
|
|
} else {
|
|
ssv_sta_priv = (struct ssv_sta_priv_data *)sta->drv_priv;
|
|
if (ssv_sta_priv->sta_idx <= SSV_SW_TXQ_ID_STAMAX)
|
|
txq_idx = ssv_sta_priv->sta_idx;
|
|
else
|
|
txq_idx = SSV_SW_TXQ_ID_MNG;
|
|
}
|
|
}
|
|
|
|
return txq_idx;
|
|
}
|
|
|
|
static bool ssv6xxx_clear_ampdu_ctl_frame(struct sk_buff *skb)
|
|
{
|
|
return ssv6xxx_special_tx_frame(skb);
|
|
}
|
|
|
|
static void ssv6xxx_tx_mib(struct ssv_softc *sc, struct sk_buff *skb)
|
|
{
|
|
struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)skb->data;
|
|
|
|
sc->tx.tx_count++;
|
|
if (ieee80211_is_data(hdr->frame_control)) {
|
|
sc->tx.data_count++;
|
|
} else if (ieee80211_is_mgmt(hdr->frame_control)) {
|
|
sc->tx.mgmt_count++;
|
|
|
|
if (ieee80211_is_auth(hdr->frame_control)) {
|
|
sc->tx.auth_count++;
|
|
} else if (ieee80211_is_deauth(hdr->frame_control)) {
|
|
sc->tx.deauth_count++;
|
|
} else if (ieee80211_is_assoc_req(hdr->frame_control)) {
|
|
sc->tx.assoc_req_count++;
|
|
} else if (ieee80211_is_assoc_resp(hdr->frame_control)) {
|
|
sc->tx.assoc_resp_count++;
|
|
} else if (ieee80211_is_probe_req(hdr->frame_control)) {
|
|
sc->tx.probe_req_count++;
|
|
} else if (ieee80211_is_probe_resp(hdr->frame_control)) {
|
|
sc->tx.probe_resp_count++;
|
|
}
|
|
}
|
|
}
|
|
|
|
#if LINUX_VERSION_CODE < KERNEL_VERSION(3,0,0)
|
|
static int ssv6200_tx(struct ieee80211_hw *hw, struct sk_buff *skb)
|
|
#elif LINUX_VERSION_CODE < KERNEL_VERSION(3,7,0)
|
|
static void ssv6200_tx(struct ieee80211_hw *hw, struct sk_buff *skb)
|
|
#else
|
|
static void ssv6200_tx(struct ieee80211_hw *hw, struct ieee80211_tx_control *control, struct sk_buff *skb)
|
|
#endif
|
|
{
|
|
struct ssv_softc *sc = (struct ssv_softc *)hw->priv;
|
|
struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb);
|
|
struct SKB_info_st *skb_info = (struct SKB_info_st *)skb->head;
|
|
struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)skb->data;
|
|
int txq_idx = 0;
|
|
bool unicast = false, force_trigger = false;
|
|
int wsid = 0;
|
|
|
|
#if LINUX_VERSION_CODE < KERNEL_VERSION(3,7,0)
|
|
memset(skb_info, 0, sizeof (struct SKB_info_st));
|
|
skb_info->sta = info->control.sta;
|
|
#else
|
|
memset(skb_info, 0, sizeof (struct SKB_info_st));
|
|
skb_info->sta = control ? control->sta : NULL;
|
|
#endif
|
|
|
|
ssv6xxx_tx_mib(sc, skb);
|
|
ssv6200_tx_flow_control(sc, true, false);
|
|
|
|
if ((sc->sc_flags & SC_OP_HW_RESET) ||
|
|
(!(sc->sc_flags & SC_OP_IF_UP)) ||
|
|
(sc->sh->cfg.flowctl && ((sc->flowctl_frame_cnt) > (sc->sh->cfg.flowctl_high_threshold << 3)))) {
|
|
//printk(KERN_ERR "******** drop packet in driver **********\n");
|
|
dev_kfree_skb_any(skb);
|
|
} else {
|
|
|
|
if (info->flags & IEEE80211_TX_CTL_ASSIGN_SEQ) {
|
|
if (info->flags & IEEE80211_TX_CTL_FIRST_FRAGMENT)
|
|
sc->tx.seq_no += 0x10;
|
|
hdr->seq_ctrl &= cpu_to_le16(IEEE80211_SCTL_FRAG);
|
|
hdr->seq_ctrl |= cpu_to_le16(sc->tx.seq_no);
|
|
}
|
|
|
|
if (info->flags & IEEE80211_TX_CTL_AMPDU) {
|
|
if (ssv6xxx_clear_ampdu_ctl_frame(skb))
|
|
info->flags &= (~IEEE80211_TX_CTL_AMPDU);
|
|
}
|
|
|
|
txq_idx = ssv6xxx_txq_id_mapping(sc, skb);
|
|
|
|
if (true == ssv6xxx_special_tx_frame(skb)) {
|
|
force_trigger = true; // force trigger
|
|
}
|
|
|
|
// update tx desc
|
|
SSV_ADD_TXINFO(sc, skb);
|
|
|
|
skb_info->directly_ack = false;
|
|
if (sc->sh->cfg.directly_ack_ctrl) {
|
|
|
|
unicast = (is_multicast_ether_addr(hdr->addr1)) ? 0: 1;
|
|
if (unicast &&
|
|
ieee80211_is_data(hdr->frame_control) &&
|
|
(!(ieee80211_is_nullfunc(hdr->frame_control)||ieee80211_is_qos_nullfunc(hdr->frame_control)))) {
|
|
|
|
struct sk_buff *copy_skb = NULL;
|
|
|
|
// 1. create a copy of tx skb
|
|
copy_skb = skb_copy(skb, GFP_ATOMIC);
|
|
if (!copy_skb) {
|
|
printk("create TX skb copy failed!\n");
|
|
HCI_SEND(sc->sh, skb, txq_idx, force_trigger);
|
|
} else {
|
|
skb_info = (struct SKB_info_st *)copy_skb->head;
|
|
|
|
info = IEEE80211_SKB_CB(skb);
|
|
wsid = HAL_GET_TX_DESC_WSID(sc->sh, skb);
|
|
skb_pull(skb, SSV_GET_TX_DESC_SIZE(sc->sh));
|
|
ssvxxx_complete_tx_skb(sc, info, skb, wsid);
|
|
skb_info->directly_ack = true;
|
|
HCI_SEND(sc->sh, copy_skb, txq_idx, force_trigger);
|
|
}
|
|
} else {
|
|
HCI_SEND(sc->sh, skb, txq_idx, force_trigger);
|
|
}
|
|
|
|
} else {
|
|
HCI_SEND(sc->sh, skb, txq_idx, force_trigger);
|
|
}
|
|
}
|
|
|
|
#if LINUX_VERSION_CODE < KERNEL_VERSION(3,0,0)
|
|
//If kernel version less than 2.6.38. and got error. no need to free sk_buff in this function
|
|
return NETDEV_TX_OK;
|
|
#endif
|
|
|
|
}
|
|
|
|
int ssv6xxx_rx_task (void *data)
|
|
{
|
|
struct ssv_softc *sc = (struct ssv_softc *)data;
|
|
unsigned long wait_period = msecs_to_jiffies(50);
|
|
|
|
printk("SSV6XXX RX Task started.\n");
|
|
while (!kthread_should_stop())
|
|
{
|
|
u32 before_timeout = (-1);
|
|
|
|
before_timeout = wait_event_interruptible_timeout(sc->rx_wait_q,
|
|
( skb_queue_len(&sc->rx_skb_q)
|
|
|| kthread_should_stop()),
|
|
wait_period);
|
|
|
|
if (kthread_should_stop())
|
|
{
|
|
printk("Quit RX task loop...\n");
|
|
break;
|
|
}
|
|
|
|
// Take out RX skb from RX Q and process it.
|
|
if (skb_queue_len(&sc->rx_skb_q)) {
|
|
ssv6xxx_process_rx_q(sc, &sc->rx_skb_q);
|
|
}
|
|
}
|
|
|
|
sc->rx_task = NULL;
|
|
|
|
return 0;
|
|
} // end of - ssv6xxx_rx_task -
|
|
|
|
void ssv6xxx_house_keeping(unsigned long argv)
|
|
{
|
|
struct ssv_softc *sc = (struct ssv_softc *)argv;
|
|
|
|
/*
|
|
* It is necessary to wait for HW ready.
|
|
* Otherwise, there is the conflict.
|
|
* Ex: Register read/write is conflict with firmware download by USB.
|
|
*/
|
|
if (!sc->mac80211_dev_started ||
|
|
(sc->sc_flags & SC_OP_HW_RESET) ||
|
|
(sc->sc_flags & SC_OP_BLOCK_CNTL))
|
|
goto next_round;
|
|
|
|
// edca wq
|
|
if((sc->bScanning == false) && (sc->bAdapt == false))
|
|
{
|
|
queue_work(sc->house_keeping_wq, &sc->mib_edca_work);
|
|
}
|
|
|
|
// tx poll
|
|
if (sc->sh->cfg.tx_stuck_detect)
|
|
queue_work(sc->house_keeping_wq, &sc->tx_poll_work);
|
|
|
|
// flow control
|
|
if (sc->sh->cfg.flowctl && (sc->flowctl_frame_cnt == 0))
|
|
queue_work(sc->house_keeping_wq, &sc->flowctl_work);
|
|
|
|
if (sc->sh->cfg.fw_reset) {
|
|
queue_work(sc->house_keeping_wq, &sc->check_fw_status_work);
|
|
}
|
|
|
|
next_round:
|
|
#ifdef CONFIG_ENABLE_HOST_THERMAL
|
|
if (sc->thermal_monitor_enable) {
|
|
sc->thermal_monitor_counter ++;
|
|
if (sc->thermal_monitor_counter > HOUSE_KEEPING_10_SEC){
|
|
queue_work(sc->house_keeping_wq, &sc->thermal_monitor_work);
|
|
sc->thermal_monitor_counter = 0;
|
|
}
|
|
}
|
|
#endif
|
|
mod_timer(&sc->house_keeping.timer, jiffies + msecs_to_jiffies(HOUSE_KEEPING_TIMEOUT));
|
|
} // end of - ssv6xxx_house_keeping -
|
|
|
|
int ssv6xxx_get_channel(struct ssv_softc *sc, int *pch)
|
|
{
|
|
*pch = sc->hw_chan;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int ssv6xxx_set_promisc(struct ssv_softc *sc, int accept)
|
|
{
|
|
u32 val=0;
|
|
|
|
if (accept) //promiscuous mode
|
|
val = MRX_MODE_PROMISCUOUS;
|
|
else //normal mode
|
|
val = MRX_MODE_NORMAL;
|
|
|
|
HAL_SET_MRX_MODE(sc->sh, val);
|
|
return 0;
|
|
}
|
|
|
|
int ssv6xxx_get_promisc(struct ssv_softc *sc, int *paccept)
|
|
{
|
|
u32 val=0;
|
|
|
|
HAL_GET_MRX_MODE(sc->sh, &val);
|
|
|
|
if (val == MRX_MODE_PROMISCUOUS) //promiscuous mode
|
|
*paccept = 1;
|
|
else //normal mode
|
|
*paccept = 0;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int ssv6200_start(struct ieee80211_hw *hw)
|
|
{
|
|
struct ssv_softc *sc = hw->priv;
|
|
struct ssv_hw *sh = sc->sh;
|
|
struct ieee80211_channel *chan;
|
|
enum nl80211_channel_type channel_type;
|
|
|
|
mutex_lock(&sc->mutex);
|
|
|
|
/* Reset MAC & Re-Init */
|
|
/* Initialize ssv6200 mac */
|
|
if (ssv6xxx_init_mac(sc->sh) != 0) {
|
|
mutex_unlock(&sc->mutex);
|
|
printk("Initialize ssv6200 mac fail!!\n");
|
|
// ssv6xxx_deinit_mac(sc);
|
|
return -1;
|
|
}
|
|
|
|
HCI_START(sh);
|
|
|
|
#ifndef SSV_SUPPORT_USB_LPM
|
|
// Do not support USB LPM, so disable it
|
|
SSV_SET_USB_LPM(sc, 0);
|
|
#endif
|
|
|
|
// edca enable
|
|
SSV_EDCA_ENABLE(sc->sh, true);
|
|
|
|
/* get the current channel */
|
|
#if LINUX_VERSION_CODE < KERNEL_VERSION(3,7,0)
|
|
chan = hw->conf.channel;
|
|
channel_type = hw->conf.channel_type;
|
|
#else
|
|
chan = hw->conf.chandef.chan;
|
|
channel_type = cfg80211_get_chandef_type(&hw->conf.chandef);
|
|
#endif
|
|
sc->cur_channel = chan;
|
|
printk("%s(): current channel: %d,sc->ps_status=%d\n", __FUNCTION__, sc->cur_channel->hw_value,sc->ps_status);
|
|
|
|
HAL_SET_CHANNEL(sc, chan, channel_type, false);
|
|
|
|
/* reset hardware to apply the configuration from mac80211 */
|
|
|
|
|
|
/* setup interrupt mask of hardware interface (SDIO/SPI) */
|
|
|
|
ieee80211_wake_queues(hw);
|
|
|
|
SSV_AMPDU_AUTO_CRC_EN(sc->sh);
|
|
SSV_SET_RF_ENABLE(sh);
|
|
|
|
// start house keeping & tx stuck detection
|
|
sc->mac80211_dev_started = true;
|
|
SSV_SEND_TX_POLL_CMD(sc->sh, SSV6XXX_TX_POLL_START);
|
|
|
|
sc->sc_flags |= SC_OP_IF_UP;
|
|
mutex_unlock(&sc->mutex);
|
|
return 0;
|
|
}
|
|
|
|
|
|
static void ssv6200_stop(struct ieee80211_hw *hw)
|
|
{
|
|
struct ssv_softc *sc=hw->priv;
|
|
|
|
printk(KERN_INFO "%s(): sc->ps_status=%d\n", __FUNCTION__,sc->ps_status);
|
|
mutex_lock(&sc->mutex);
|
|
|
|
sc->mac80211_dev_started = false;
|
|
sc->sc_flags &= ~SC_OP_IF_UP;
|
|
|
|
// stop tx stuck detection for fw
|
|
SSV_SEND_TX_POLL_CMD(sc->sh, SSV6XXX_TX_POLL_STOP);
|
|
|
|
SSV_SET_RF_DISABLE(sc->sh);
|
|
|
|
HCI_STOP(sc->sh);
|
|
/* flush tx queue */
|
|
HCI_TXQ_FLUSH(sc->sh);
|
|
|
|
// Fredie ToDo: Remove RX worker? Notify RX thread to flush out queued packets?
|
|
#if 0
|
|
//#ifndef DCONFIG_SSV_RX_NO_WORKER
|
|
cancel_work_sync(&sc->rx_work);
|
|
#endif
|
|
|
|
if((sc->ps_status == PWRSV_PREPARE)||(sc->ps_status == PWRSV_ENABLE)){
|
|
ssv6xxx_enable_ps(sc);
|
|
SSV_SET_RF_ENABLE(sc->sh);
|
|
}
|
|
|
|
ssv6xxx_beacon_release(sc);
|
|
mutex_unlock(&sc->mutex);
|
|
|
|
printk("%s(): leave, hci_txq_len %d\n", __FUNCTION__, HCI_TXQ_LEN(sc->sh));
|
|
}
|
|
|
|
static int ssv6xxx_interface_opertaion(struct ssv_softc *sc, ssv6xxx_vif_ops ops,
|
|
u8 vif_idx, u8 *mac, u8 type, bool p2p, bool assoc)
|
|
{
|
|
struct sk_buff *skb = NULL;
|
|
struct cfg_host_cmd *host_cmd = NULL;
|
|
struct ssv_vif_param *ptr = NULL;
|
|
int ret = 0;
|
|
u8 vif_type = 0;
|
|
|
|
skb = ssv_skb_alloc(sc, HOST_CMD_HDR_LEN + sizeof(struct ssv_vif_param));
|
|
if (skb == NULL) {
|
|
printk("%s(): Fail to alloc cmd buffer.\n", __FUNCTION__);
|
|
return -1;
|
|
}
|
|
|
|
skb_put(skb, HOST_CMD_HDR_LEN + sizeof(struct ssv_vif_param));
|
|
host_cmd = (struct cfg_host_cmd *)skb->data;
|
|
memset(host_cmd, 0x0, sizeof(struct cfg_host_cmd));
|
|
host_cmd->c_type = HOST_CMD;
|
|
host_cmd->h_cmd = (u8)SSV6XXX_HOST_CMD_VIF_OPS;
|
|
host_cmd->sub_h_cmd = (u32)ops;
|
|
host_cmd->blocking_seq_no = (((u16)SSV6XXX_HOST_CMD_VIF_OPS << 16)|(u16)ops);
|
|
host_cmd->len = HOST_CMD_HDR_LEN + sizeof(struct ssv_vif_param);
|
|
|
|
ptr = (struct ssv_vif_param *)host_cmd->un.dat8;
|
|
memset(ptr, 0x0, sizeof(struct ssv_vif_param));
|
|
if ((type == NL80211_IFTYPE_AP) || (type == NL80211_IFTYPE_P2P_GO) || (type == NL80211_IFTYPE_ADHOC))
|
|
vif_type = SSV6XXX_VIF_TYPE_AP;
|
|
else if ((type == NL80211_IFTYPE_STATION) || (type == NL80211_IFTYPE_P2P_CLIENT))
|
|
vif_type = SSV6XXX_VIF_TYPE_STA;
|
|
else
|
|
vif_type = SSV6XXX_VIF_TYPE_NONE;
|
|
|
|
switch (ops) {
|
|
case SSV6XXX_VIF_CMD_ADD:
|
|
ptr->vif_idx = vif_idx;
|
|
memcpy(ptr->mac, mac, ETH_ALEN);
|
|
ptr->type = vif_type;
|
|
break;
|
|
case SSV6XXX_VIF_CMD_DEL:
|
|
ptr->vif_idx = vif_idx;
|
|
break;
|
|
case SSV6XXX_VIF_CMD_CHG:
|
|
ptr->vif_idx = vif_idx;
|
|
ptr->type = vif_type;
|
|
ptr->assoc = assoc;
|
|
ptr->p2p = p2p;
|
|
break;
|
|
}
|
|
ret = HCI_SEND_CMD(sc->sh, skb);
|
|
|
|
return 0;
|
|
}
|
|
|
|
struct ssv_vif_priv_data * ssv6xxx_config_vif_res(struct ssv_softc *sc,
|
|
struct ieee80211_vif *vif)
|
|
{
|
|
int i, vif_idx = -1;
|
|
struct ssv_vif_priv_data *priv_vif;
|
|
struct ssv_vif_info *vif_info;
|
|
bool find_empty_vif_idx = false;
|
|
|
|
lockdep_assert_held(&sc->mutex);
|
|
|
|
//find vif
|
|
for (i=0 ; i<SSV6200_MAX_VIF ;i++) {
|
|
if ((sc->vif_info[i].vif != NULL) && (!memcmp(vif->addr, sc->vif_info[i].vif->addr, ETH_ALEN))) {
|
|
vif_idx = i;
|
|
break;
|
|
}
|
|
|
|
if ((sc->vif_info[i].vif == NULL) && !find_empty_vif_idx) {
|
|
find_empty_vif_idx = true;
|
|
vif_idx = i;
|
|
sc->nvif++;
|
|
}
|
|
}
|
|
|
|
BUG_ON(vif_idx < 0);
|
|
|
|
printk("ssv6xxx_config_vif_res id[%d].\n", vif_idx);
|
|
|
|
priv_vif = (struct ssv_vif_priv_data *)vif->drv_priv;
|
|
memset(priv_vif, 0, sizeof(struct ssv_vif_priv_data));
|
|
|
|
priv_vif->vif_idx = vif_idx;
|
|
|
|
memset(&sc->vif_info[vif_idx], 0, sizeof(sc->vif_info[0]));
|
|
|
|
sc->vif_info[vif_idx].vif = vif;
|
|
sc->vif_info[vif_idx].vif_priv = priv_vif;
|
|
|
|
INIT_LIST_HEAD(&priv_vif->sta_list);
|
|
|
|
priv_vif->pair_cipher = SECURITY_NONE; // Security type for unicast data of this VIF.
|
|
priv_vif->group_cipher = SECURITY_NONE; // Security type for multicast data of this VIF.
|
|
|
|
priv_vif->has_hw_decrypt = false;
|
|
priv_vif->has_hw_encrypt = false;
|
|
priv_vif->need_sw_encrypt = false;
|
|
priv_vif->need_sw_decrypt = false;
|
|
priv_vif->use_mac80211_decrypt = false;
|
|
priv_vif->is_security_valid = false;
|
|
priv_vif->force_sw_encrypt = (vif->type == NL80211_IFTYPE_AP);
|
|
|
|
priv_vif->wep_idx = -1;
|
|
|
|
vif_info = &sc->vif_info[priv_vif->vif_idx];
|
|
vif_info->if_type = vif->type;
|
|
vif_info->vif = vif;
|
|
|
|
return priv_vif;
|
|
}
|
|
|
|
static void _if_set_apmode(struct ieee80211_hw *hw,
|
|
struct ieee80211_vif *vif)
|
|
{
|
|
struct ssv_softc *sc=hw->priv;
|
|
struct ieee80211_channel *chan;
|
|
struct ssv_vif_priv_data *vif_priv = (struct ssv_vif_priv_data *)vif->drv_priv;
|
|
|
|
SSV_SET_BSSID(sc->sh, vif->addr, vif_priv->vif_idx);
|
|
SSV_SET_OP_MODE(sc->sh, SSV6XXX_OPMODE_AP, vif_priv->vif_idx);
|
|
|
|
BUG_ON(sc->ap_vif != NULL);
|
|
sc->ap_vif = vif;
|
|
|
|
#if LINUX_VERSION_CODE < KERNEL_VERSION(3,7,0)
|
|
chan = hw->conf.channel;
|
|
#else
|
|
chan = hw->conf.chandef.chan;
|
|
#endif
|
|
printk("AP created at ch %d \n", chan->hw_value);
|
|
|
|
#ifdef CONFIG_SSV_SUPPORT_ANDROID
|
|
if(vif->p2p == 0)
|
|
{
|
|
printk(KERN_INFO "AP mode init wifi_alive_lock\n");
|
|
ssv_wake_lock(sc);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
static int ssv6200_add_interface(struct ieee80211_hw *hw,
|
|
struct ieee80211_vif *vif)
|
|
{
|
|
struct ssv_softc *sc=hw->priv;
|
|
int ret=0;
|
|
struct ssv_vif_priv_data *vif_priv = NULL;
|
|
|
|
printk("[I] %s(): \n", __FUNCTION__);
|
|
|
|
// AP mode can only exist with managed(station) mode.
|
|
if ( (sc->nvif >= SSV6200_MAX_VIF)
|
|
|| ( ( (vif->type == NL80211_IFTYPE_AP)
|
|
|| (vif->p2p))
|
|
&& (sc->ap_vif != NULL)))
|
|
{
|
|
dev_err(sc->dev, "Add interface of type %d (p2p: %d) failed.\n", vif->type, vif->p2p);
|
|
return -EOPNOTSUPP;
|
|
}
|
|
|
|
mutex_lock(&sc->mutex);
|
|
|
|
vif_priv = ssv6xxx_config_vif_res(sc, vif);
|
|
|
|
if ((vif->addr[0] == 0) && (vif->addr[1] == 0) && (vif->addr[2] == 0) &&
|
|
(vif->addr[3] == 0) && (vif->addr[4] == 0) && (vif->addr[5] == 0))
|
|
{
|
|
SSV_SET_MACADDR(sc->sh, vif_priv->vif_idx);
|
|
}
|
|
else
|
|
{
|
|
dev_err(sc->dev, "Set new macaddr\n");
|
|
SSV_SET_MACADDR_2(sc->sh, vif_priv->vif_idx, vif->addr);
|
|
}
|
|
/*it is first entity, and in ap mode*/
|
|
|
|
if ((vif->type == NL80211_IFTYPE_AP) || (vif->type == NL80211_IFTYPE_P2P_GO) || (vif->type == NL80211_IFTYPE_ADHOC))
|
|
_if_set_apmode(hw, vif);
|
|
|
|
/* Send Host Cmd to notify interface information */
|
|
ssv6xxx_interface_opertaion(sc, SSV6XXX_VIF_CMD_ADD, vif_priv->vif_idx, vif->addr, vif->type, false, false);
|
|
|
|
dev_err(sc->dev, "VIF %02x:%02x:%02x:%02x:%02x:%02x of type %d is added.\n",
|
|
vif->addr[0], vif->addr[1], vif->addr[2],
|
|
vif->addr[3], vif->addr[4], vif->addr[5], vif->type);
|
|
|
|
#ifdef CONFIG_SSV6XXX_DEBUGFS
|
|
ssv6xxx_debugfs_add_interface(sc, vif);
|
|
#endif
|
|
#ifdef CONFIG_STA_BCN_FILTER
|
|
if (1 < sc->nvif) {
|
|
//disable filter for beacon and probe request
|
|
HAL_SET_MRX_FILTER(sc->sh, 3, false, BIT(4)|BIT(5));
|
|
sc->sta_bcn_filter = false;
|
|
}
|
|
#endif
|
|
|
|
mutex_unlock(&sc->mutex);
|
|
return ret;
|
|
}
|
|
|
|
static int ssv6200_change_interface(struct ieee80211_hw *hw,
|
|
struct ieee80211_vif *vif,
|
|
enum nl80211_iftype new_type,
|
|
bool p2p)
|
|
{
|
|
struct ssv_softc *sc = hw->priv;
|
|
struct ssv_vif_priv_data *vif_priv = (struct ssv_vif_priv_data *)vif->drv_priv;
|
|
int vif_idx = vif_priv->vif_idx;
|
|
|
|
mutex_lock(&sc->mutex);
|
|
printk("@@@@@@ change id[%d] type %d to %d, p2p=%d\n", vif_idx, sc->vif_info[vif_idx].if_type, new_type, p2p);
|
|
sc->vif_info[vif_idx].if_type = new_type;
|
|
sc->force_disable_directly_ack_tx = p2p;
|
|
|
|
/* Add new settings */
|
|
vif->type = new_type;
|
|
if ((vif->type == NL80211_IFTYPE_AP) || (vif->type == NL80211_IFTYPE_P2P_GO) || (vif->type == NL80211_IFTYPE_ADHOC))
|
|
_if_set_apmode(hw, vif);
|
|
else {
|
|
u8 null_addr[6]={0, 0, 0, 0, 0, 0};
|
|
sc->ap_vif = NULL;
|
|
SSV_SET_BSSID(sc->sh, null_addr, vif_priv->vif_idx);
|
|
SSV_SET_OP_MODE(sc->sh, SSV6XXX_OPMODE_STA, vif_priv->vif_idx);
|
|
}
|
|
|
|
vif->p2p = p2p;
|
|
|
|
/* Send Host Cmd to notify interface information
|
|
* if p2p is true, it should be association state
|
|
*/
|
|
ssv6xxx_interface_opertaion(sc, SSV6XXX_VIF_CMD_CHG, vif_priv->vif_idx, NULL, vif->type, vif->p2p, vif->p2p);
|
|
mutex_unlock(&sc->mutex);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void ssv6200_remove_interface(struct ieee80211_hw *hw,
|
|
struct ieee80211_vif *vif)
|
|
{
|
|
struct ssv_softc *sc = hw->priv;
|
|
struct ssv_vif_priv_data *vif_priv = (struct ssv_vif_priv_data *)vif->drv_priv;
|
|
int vif_idx = 0;
|
|
|
|
dev_err(sc->dev,
|
|
"Removing interface %02x:%02x:%02x:%02x:%02x:%02x. PS=%d\n",
|
|
vif->addr[0], vif->addr[1], vif->addr[2],
|
|
vif->addr[3], vif->addr[4], vif->addr[5], sc->ps_status);
|
|
/**
|
|
* STA mode decision table is the default table for ssv6xxx.
|
|
* Set the table to default when interface is removed.
|
|
*/
|
|
mutex_lock(&sc->mutex);
|
|
|
|
if (sc->nvif == 0)
|
|
{
|
|
mutex_unlock(&sc->mutex);
|
|
return;
|
|
}
|
|
|
|
/* Send Host Cmd to notify interface information */
|
|
vif_idx = vif_priv->vif_idx;
|
|
ssv6xxx_interface_opertaion(sc, SSV6XXX_VIF_CMD_DEL, vif_priv->vif_idx, NULL, 0, false, false);
|
|
|
|
#ifdef CONFIG_SSV6XXX_DEBUGFS
|
|
ssv6xxx_debugfs_remove_interface(sc, vif);
|
|
#endif
|
|
|
|
if ((vif->type == NL80211_IFTYPE_AP) || (vif->type == NL80211_IFTYPE_ADHOC))
|
|
{
|
|
/* In normal ap mode, release bcast frame and stop worker */
|
|
//ssv6200_release_bcast_frame_res(sc, vif);
|
|
//printk("Config Q4 to normal Q \n");
|
|
|
|
/* Relase skb of beacon frame */
|
|
ssv6xxx_beacon_release(sc);
|
|
sc->ap_vif = NULL;
|
|
|
|
#ifdef CONFIG_SSV_SUPPORT_ANDROID
|
|
if(vif->p2p == 0)
|
|
{
|
|
ssv_wake_unlock(sc);
|
|
printk(KERN_INFO "AP mode destroy wifi_alive_lock\n");
|
|
}
|
|
#endif
|
|
|
|
}
|
|
|
|
//disable beacon loss
|
|
if (vif->type == NL80211_IFTYPE_STATION)
|
|
SSV_BEACON_LOSS_DISABLE(sc->sh);
|
|
|
|
memset(&sc->vif_info[vif_priv->vif_idx], 0, sizeof(struct ssv_vif_info));
|
|
//sc->vif[vif_priv->vif_idx] = NULL;
|
|
sc->nvif--;
|
|
|
|
#ifdef CONFIG_STA_BCN_FILTER
|
|
if (1 == sc->nvif) {
|
|
if (vif_idx == 0) { // check station and enable beacon filter for vif_idx 1
|
|
if (NULL != sc->vif_info[1].vif) {
|
|
if ((NL80211_IFTYPE_STATION == (sc->vif_info[1].vif)->type) &&
|
|
(true == (sc->vif_info[1].vif)->bss_conf.assoc) &&
|
|
(false == (sc->vif_info[1].vif)->p2p)) {
|
|
|
|
//enable filter for beacon and probe request
|
|
HAL_SET_MRX_FILTER(sc->sh, 3, true, BIT(4)|BIT(5));
|
|
sc->sta_bcn_filter = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (vif_idx == 1) { // check station and enable beacon filter for vif_idx 0
|
|
if (NULL != sc->vif_info[1].vif) {
|
|
if ((NL80211_IFTYPE_STATION == (sc->vif_info[0].vif)->type) &&
|
|
(true == (sc->vif_info[0].vif)->bss_conf.assoc) &&
|
|
(false == (sc->vif_info[0].vif)->p2p)) {
|
|
|
|
//enable filter for beacon and probe request
|
|
HAL_SET_MRX_FILTER(sc->sh, 3, true, BIT(4)|BIT(5));
|
|
sc->sta_bcn_filter = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
mutex_unlock(&sc->mutex);
|
|
}
|
|
|
|
void ssv6xxx_enable_ps(struct ssv_softc *sc)
|
|
{
|
|
sc->ps_status = PWRSV_ENABLE;
|
|
//printk(KERN_INFO "PowerSave enabled\n");
|
|
}
|
|
|
|
void ssv6xxx_disable_ps(struct ssv_softc *sc)
|
|
{
|
|
sc->ps_status = PWRSV_DISABLE;
|
|
printk(KERN_INFO "PowerSave disabled\n");
|
|
}
|
|
|
|
/* Return false, it match the following condition
|
|
* 1. P2P_GO and station connection
|
|
* 2. dual interface and STATION/P2P_CLINET association
|
|
*/
|
|
bool ssv6200_not_dual_intf_on_line(struct ssv_softc *sc)
|
|
{
|
|
struct ieee80211_vif *vif;
|
|
int i = 0, assoc = 0, p2p = 0;
|
|
|
|
for (i = 0; i < SSV6200_MAX_VIF; i++) {
|
|
if (sc->vif_info[i].vif != NULL) {
|
|
vif = sc->vif_info[i].vif;
|
|
|
|
if ((vif->type == NL80211_IFTYPE_STATION) || (vif->type == NL80211_IFTYPE_P2P_CLIENT)) {
|
|
if (vif->bss_conf.assoc)
|
|
assoc++;
|
|
if (vif->p2p)
|
|
p2p++;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ((p2p > 0) || (assoc == 2))
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
//
|
|
// return sta or GC associate count
|
|
int ssvxxx_get_sta_assco_cnt(struct ssv_softc *sc)
|
|
{
|
|
struct ieee80211_vif *vif;
|
|
int i = 0, assoc = 0;
|
|
|
|
for (i = 0; i < SSV6200_MAX_VIF; i++) {
|
|
if (sc->vif_info[i].vif != NULL) {
|
|
vif = sc->vif_info[i].vif;
|
|
|
|
if ((vif->type == NL80211_IFTYPE_STATION) || (vif->type == NL80211_IFTYPE_P2P_CLIENT)) {
|
|
if (vif->bss_conf.assoc)
|
|
assoc++;
|
|
}
|
|
}
|
|
}
|
|
|
|
return assoc;
|
|
}
|
|
|
|
static int ssv6200_config(struct ieee80211_hw *hw, u32 changed)
|
|
{
|
|
struct ssv_softc *sc=hw->priv;
|
|
int ret=0;
|
|
|
|
// printk("%s(): changed: 0x%08x\n", __FUNCTION__, changed);
|
|
mutex_lock(&sc->mutex);
|
|
|
|
if (changed & IEEE80211_CONF_CHANGE_POWER) {
|
|
struct ieee80211_conf *conf = &hw->conf;
|
|
printk("IEEE80211_CONF_CHANGE_POWER change power level to %d\n", conf->power_level);
|
|
}
|
|
|
|
|
|
if (changed & IEEE80211_CONF_CHANGE_PS) {
|
|
struct ieee80211_conf *conf = &hw->conf;
|
|
if (conf->flags & IEEE80211_CONF_PS) {
|
|
printk("Enable IEEE80211_CONF_PS ps_aid=%d\n",sc->ps_aid);
|
|
}else{
|
|
printk("Disable IEEE80211_CONF_PS ps_aid=%d\n",sc->ps_aid);
|
|
}
|
|
}
|
|
|
|
if (changed & IEEE80211_CONF_CHANGE_MONITOR) {
|
|
struct ieee80211_conf *conf = &hw->conf;
|
|
if (conf->flags & IEEE80211_CONF_MONITOR) {
|
|
printk("Enable IEEE80211_CONF_MONITOR\n");
|
|
ret=ssv6xxx_set_promisc(sc,1);
|
|
sc->sc_flags |= SC_OP_MONITOR;
|
|
}else{
|
|
printk("Disable IEEE80211_CONF_MONITOR\n");
|
|
ret=ssv6xxx_set_promisc(sc,0);
|
|
sc->sc_flags &= ~SC_OP_MONITOR;
|
|
}
|
|
}
|
|
|
|
#if LINUX_VERSION_CODE < KERNEL_VERSION(3,0,0)
|
|
if (changed & IEEE80211_CONF_CHANGE_QOS) {
|
|
struct ieee80211_conf *conf = &hw->conf;
|
|
bool qos_active = !!(conf->flags & IEEE80211_CONF_QOS);
|
|
|
|
//set QoS status
|
|
SMAC_REG_SET_BITS(sc->sh, ADR_GLBLE_SET,
|
|
(qos_active<<QOS_EN_SFT), QOS_EN_MSK);
|
|
}
|
|
#endif
|
|
|
|
if (changed & IEEE80211_CONF_CHANGE_CHANNEL) {
|
|
struct ieee80211_channel *chan;
|
|
enum nl80211_channel_type channel_type;
|
|
|
|
/*
|
|
* 1. If hw reset, channel setting may cause that IO fail to read/write register
|
|
* 2. fixed channel for debug command
|
|
*/
|
|
if ((sc->sc_flags & SC_OP_HW_RESET) ||
|
|
(sc->sc_flags & SC_OP_CHAN_FIXED))
|
|
goto out;
|
|
|
|
#if LINUX_VERSION_CODE < KERNEL_VERSION(3,7,0)
|
|
chan = hw->conf.channel;
|
|
channel_type = hw->conf.channel_type;
|
|
#else
|
|
chan = hw->conf.chandef.chan;
|
|
channel_type = cfg80211_get_chandef_type(&hw->conf.chandef);
|
|
#endif
|
|
|
|
#if defined (CONFIG_SSV_CTL)
|
|
//printk("@_@ %d\n",sc->ssv_smartlink_status);
|
|
if (sc->ssv_smartlink_status)
|
|
{
|
|
printk("@@ %d\n",sc->ssv_smartlink_status);
|
|
goto out;
|
|
}
|
|
#endif
|
|
|
|
// struct ieee80211_channel *curchan = hw->conf.channel;
|
|
// printk("%s(): Set channel to %d (%d MHz), sc->ch=%d, (%s)\n", __FUNCTION__, curchan->hw_value+1,
|
|
// curchan->center_freq, sc->cur_channel->hw_value+1,
|
|
// ((hw->conf.flags&IEEE80211_CONF_OFFCHANNEL)? "off channel": "on channel"));
|
|
|
|
#if LINUX_VERSION_CODE < KERNEL_VERSION(3,1,0)
|
|
{
|
|
struct ieee80211_channel *curchan = hw->conf.channel;
|
|
|
|
if(sc->bScanning == true &&
|
|
sc->channel_center_freq != curchan->center_freq && sc->isAssoc){
|
|
|
|
hw->conf.flags |= IEEE80211_CONF_OFFCHANNEL;
|
|
}
|
|
else{
|
|
|
|
hw->conf.flags &= ~IEEE80211_CONF_OFFCHANNEL;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
|
|
/**
|
|
* If the current channel is off channel, pause all tx queue except
|
|
* management queue.
|
|
*/
|
|
if (hw->conf.flags & IEEE80211_CONF_OFFCHANNEL)
|
|
{
|
|
// If AP mode is enabled, ignore off channel config.
|
|
if (/* (IS_ALLOW_SCAN || IS_NON_AP_MODE || IS_NONE_STA_CONNECTED_IN_AP_MODE)
|
|
&& ssv6200_not_dual_intf_on_line(sc)
|
|
&& */((sc->hw_chan != chan->hw_value) || (sc->hw_chan_type != channel_type)) )
|
|
{
|
|
int i = 1;
|
|
|
|
//printk("off channel setting %p!\n", sc->ap_vif);
|
|
sc->sc_flags |= SC_OP_OFFCHAN;
|
|
HCI_PAUSE_HWSWQ(sc->sh, (TXQ_EDCA_0|TXQ_EDCA_1|TXQ_EDCA_2|TXQ_EDCA_3));
|
|
|
|
sc->rx_data_exist = false;
|
|
mdelay(1);
|
|
while ((sc->rx_data_exist == true) && (i < 200)){
|
|
i++;
|
|
sc->rx_data_exist = false;
|
|
mdelay(1);
|
|
}
|
|
sc->boffchan = true;
|
|
HAL_SET_CHANNEL(sc, chan, channel_type, true);
|
|
sc->boffchan = false;
|
|
}
|
|
else
|
|
{
|
|
dev_dbg(sc->dev, "Off-channel to %d is ignored\n", chan->hw_value);
|
|
}
|
|
/* if it is not connect to any device, let it takes more time to stay in a channel */
|
|
// if(!sc->isAssoc)
|
|
// msleep(800);
|
|
}
|
|
else {
|
|
if ( ((sc->cur_channel == NULL)
|
|
|| (sc->sc_flags & SC_OP_OFFCHAN)
|
|
|| (sc->hw_chan != chan->hw_value)
|
|
|| (sc->hw_chan_type != channel_type)) )
|
|
{
|
|
HAL_SET_CHANNEL(sc, chan, channel_type, false);
|
|
sc->cur_channel = chan;
|
|
HCI_RESUME_HWSWQ(sc->sh, (TXQ_EDCA_0|TXQ_EDCA_1|TXQ_EDCA_2|TXQ_EDCA_3| TXQ_MGMT));
|
|
sc->sc_flags &= ~SC_OP_OFFCHAN;
|
|
}
|
|
else
|
|
{
|
|
dev_dbg(sc->dev, "Change to the same channel %d\n", chan->hw_value);
|
|
}
|
|
// printk("on channel setting !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!: ch=%d\n", sc->cur_channel->hw_value);
|
|
}
|
|
}
|
|
|
|
out:
|
|
mutex_unlock(&sc->mutex);
|
|
return ret;
|
|
}
|
|
|
|
|
|
#if 0
|
|
static int sv6200_conf_tx(struct ieee80211_hw *hw,
|
|
struct ieee80211_vif *vif, u16 queue,
|
|
const struct ieee80211_tx_queue_params *params)
|
|
{
|
|
struct ssv_softc *sc = hw->priv;
|
|
u32 cw;
|
|
u8 hw_txqid = sc->tx.hw_txqid[queue];
|
|
|
|
printk("[I] sv6200_conf_tx qos[%d] queue[%d] aifsn[%d] cwmin[%d] cwmax[%d] txop[%d] \n",
|
|
vif->bss_conf.qos, queue, params->aifs, params->cw_min, params->cw_max, params->txop);
|
|
|
|
if (queue > NL80211_TXQ_Q_BK)
|
|
return 1;
|
|
mutex_lock(&sc->mutex);
|
|
|
|
|
|
//set QoS status
|
|
#define QOS_EN_MSK 0x00000010
|
|
#define QOS_EN_I_MSK 0xffffffef
|
|
#define QOS_EN_SFT 4
|
|
#define QOS_EN_HI 4
|
|
#define QOS_EN_SZ 1
|
|
SMAC_REG_SET_BITS(sc->sh, ADR_GLBLE_SET, (vif->bss_conf.qos<<QOS_EN_SFT), QOS_EN_MSK);
|
|
|
|
|
|
//set wmm parameter
|
|
cw = params->aifs&0xf;
|
|
cw|= ((ilog2(params->cw_min+1))&0xf)<<8;
|
|
cw|= ((ilog2(params->cw_max+1))&0xf)<<12;
|
|
cw|= ((params->txop)&0xff)<<16;
|
|
|
|
SMAC_REG_WRITE(sc->sh, ADR_TXQ0_MTX_Q_AIFSN+0x100*hw_txqid, cw);
|
|
|
|
mutex_unlock(&sc->mutex);
|
|
return 0;
|
|
}
|
|
|
|
#endif
|
|
|
|
#if LINUX_VERSION_CODE < KERNEL_VERSION(4,2,0)
|
|
#define SUPPORTED_FILTERS \
|
|
(FIF_PROMISC_IN_BSS | \
|
|
FIF_ALLMULTI | \
|
|
FIF_CONTROL | \
|
|
FIF_PSPOLL | \
|
|
FIF_OTHER_BSS | \
|
|
FIF_BCN_PRBRESP_PROMISC | \
|
|
FIF_PROBE_REQ | \
|
|
FIF_FCSFAIL)
|
|
#else
|
|
#define SUPPORTED_FILTERS \
|
|
(FIF_ALLMULTI | \
|
|
FIF_CONTROL | \
|
|
FIF_PSPOLL | \
|
|
FIF_OTHER_BSS | \
|
|
FIF_BCN_PRBRESP_PROMISC | \
|
|
FIF_PROBE_REQ | \
|
|
FIF_FCSFAIL)
|
|
#endif
|
|
|
|
static void ssv6200_config_filter(struct ieee80211_hw *hw,
|
|
unsigned int changed_flags,
|
|
unsigned int *total_flags,
|
|
u64 multicast)
|
|
{
|
|
// struct ssv_softc *sc=hw->priv;
|
|
//
|
|
// printk("%s(): changed_flags: 0x%08x, total_flags: 0x%08x\n",
|
|
// __FUNCTION__, changed_flags, *total_flags);
|
|
//
|
|
// mutex_lock(&sc->mutex);
|
|
|
|
/**
|
|
* Note ??????????????
|
|
* Modify this flag for AP mode ?????????
|
|
*/
|
|
changed_flags &= SUPPORTED_FILTERS;
|
|
*total_flags &= SUPPORTED_FILTERS;
|
|
|
|
|
|
// mutex_unlock(&sc->mutex);
|
|
|
|
}
|
|
|
|
|
|
static void ssv6200_bss_info_changed(struct ieee80211_hw *hw,
|
|
struct ieee80211_vif *vif, struct ieee80211_bss_conf *info,
|
|
u32 changed)
|
|
{
|
|
struct ssv_vif_priv_data *priv_vif = (struct ssv_vif_priv_data *)vif->drv_priv;
|
|
struct ssv_softc *sc = hw->priv;
|
|
int i = 0;
|
|
|
|
// printk("[I] %s(): VIF[%d] changed 0x[%08x]\n", __FUNCTION__, priv_vif->vif_idx, changed);
|
|
mutex_lock(&sc->mutex);
|
|
|
|
//temp mark: RATE CTRL
|
|
if (changed & BSS_CHANGED_ERP_PREAMBLE) {
|
|
printk("BSS Changed use_short_preamble[%d]\n", info->use_short_preamble);
|
|
if (info->use_short_preamble)
|
|
sc->sc_flags |= SC_OP_SHORT_PREAMBLE;
|
|
else
|
|
sc->sc_flags &= ~SC_OP_SHORT_PREAMBLE;
|
|
}
|
|
|
|
if (changed & BSS_CHANGED_ERP_CTS_PROT) {
|
|
printk("BSS Changed use_cts_prot[%d]\n", info->use_cts_prot);
|
|
if (info->use_cts_prot)
|
|
sc->sc_flags |= SC_OP_CTS_PROT;
|
|
else
|
|
sc->sc_flags &= ~SC_OP_CTS_PROT;
|
|
}
|
|
|
|
if (SSV_CHK_IF_SUPPORT_HW_BSSID(sc, priv_vif->vif_idx))
|
|
{
|
|
if (changed & BSS_CHANGED_BSSID)
|
|
{
|
|
/* Set BSSID to hardware and enable WSID entry 0 */
|
|
SSV_SET_BSSID(sc->sh, (u8*)info->bssid, priv_vif->vif_idx);
|
|
|
|
printk("BSS_CHANGED_BSSID: %02x:%02x:%02x:%02x:%02x:%02x\n",
|
|
info->bssid[0], info->bssid[1], info->bssid[2],
|
|
info->bssid[3], info->bssid[4], info->bssid[5]);
|
|
}
|
|
|
|
if (changed & BSS_CHANGED_ERP_SLOT)
|
|
{
|
|
printk("BSS_CHANGED_ERP_SLOT: use_short_slot[%d]\n", info->use_short_slot);
|
|
|
|
/*
|
|
Fix MAC TX backoff issue.
|
|
http://192.168.1.30/mantis/view.php?id=36
|
|
*/
|
|
SSV_SET_DUR_BURST_SIFS_G(sc->sh, 0xa);
|
|
|
|
if (info->use_short_slot) {
|
|
SSV_SET_DUR_SLOT(sc->sh, 0x9);
|
|
//slottime = 9;
|
|
} else {
|
|
SSV_SET_DUR_SLOT(sc->sh, 20);
|
|
//slottime = 20;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (changed & BSS_CHANGED_HT) {
|
|
printk("BSS_CHANGED_HT: Untreated!!\n");
|
|
}
|
|
|
|
|
|
if (changed & BSS_CHANGED_BASIC_RATES)
|
|
{
|
|
printk("ssv6xxx_rc_update_basic_rate!!\n");
|
|
}
|
|
|
|
|
|
if (vif->type == NL80211_IFTYPE_STATION){
|
|
struct ieee80211_channel *curchan;
|
|
int dual_if_vif_idx = 0;
|
|
|
|
#if LINUX_VERSION_CODE < KERNEL_VERSION(3,7,0)
|
|
curchan = hw->conf.channel;
|
|
#else
|
|
curchan = hw->conf.chandef.chan;
|
|
#endif
|
|
printk("NL80211_IFTYPE_STATION!!\n");
|
|
// find the other interface vif idx
|
|
if (priv_vif->vif_idx == 0)
|
|
dual_if_vif_idx = 1;
|
|
|
|
if (changed & BSS_CHANGED_ASSOC) {
|
|
sc->isAssoc = info->assoc;
|
|
/* Send HostCmd to notify association status */
|
|
ssv6xxx_interface_opertaion(sc, SSV6XXX_VIF_CMD_CHG, priv_vif->vif_idx, NULL, vif->type, false, info->assoc);
|
|
|
|
if (!sc->isAssoc) { // station mode disassociation
|
|
|
|
#ifdef CONFIG_STA_BCN_FILTER
|
|
//disable filter for beacon and probe request
|
|
HAL_SET_MRX_FILTER(sc->sh, 3, false, BIT(4)|BIT(5));
|
|
sc->sta_bcn_filter = false;
|
|
#endif
|
|
|
|
sc->channel_center_freq = 0;
|
|
sc->ps_aid = 0;
|
|
#ifdef SSV_SUPPORT_USB_LPM
|
|
// Enable LPM at disconnection state
|
|
SSV_SET_USB_LPM(sc, 1);
|
|
#endif
|
|
}
|
|
else{
|
|
sc->channel_center_freq = curchan->center_freq;
|
|
printk(KERN_INFO "!!info->aid = %d\n",info->aid);
|
|
for (i = 0; i < SSV_NUM_STA; i++) {
|
|
if (sc->sta_info[i].s_flags & STA_FLAG_VALID) {
|
|
if (vif == sc->sta_info[i].vif) {
|
|
sc->sta_info[i].aid = info->aid;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
sc->ps_aid = info->aid;
|
|
#ifdef SSV_SUPPORT_USB_LPM
|
|
/*
|
|
Enable LPM will frequently swicth LPM power state and generate latency.
|
|
When traffic loading is high, LPM makes throughput low.
|
|
So disable LPM at connection state.
|
|
*/
|
|
SSV_SET_USB_LPM(sc, 0);
|
|
#endif
|
|
#ifdef CONFIG_STA_BCN_FILTER
|
|
if ((sc->vif_info[dual_if_vif_idx].vif == NULL)) {
|
|
//enable filter for beacon and probe request
|
|
HAL_SET_MRX_FILTER(sc->sh, 3, true, BIT(4)|BIT(5));
|
|
sc->sta_bcn_filter = true;
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
|
|
if ((changed & BSS_CHANGED_BEACON_INT) && (info->beacon_int != 0)
|
|
/*&& (curchan->band == INDEX_80211_BAND_2GHZ)*/)
|
|
SSV_BEACON_LOSS_CONFIG(sc->sh, info->beacon_int, info->bssid);
|
|
}
|
|
|
|
//--------------------------------------------------------------
|
|
if ((vif->type == NL80211_IFTYPE_AP) || (vif->type == NL80211_IFTYPE_ADHOC))
|
|
{
|
|
|
|
if (changed & ( BSS_CHANGED_BEACON
|
|
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3,2,0)
|
|
| BSS_CHANGED_SSID
|
|
#endif
|
|
| BSS_CHANGED_BSSID
|
|
| BSS_CHANGED_BASIC_RATES))
|
|
{
|
|
#ifdef BROADCAST_DEBUG
|
|
printk("[A] ssv6200_bss_info_changed:beacon changed\n");
|
|
#endif
|
|
ssv6200_set_tim_work(sc);
|
|
}
|
|
|
|
if (changed & BSS_CHANGED_BEACON_INT)
|
|
{
|
|
printk("[A] BSS_CHANGED_BEACON_INT beacon_interval(%d)\n", info->beacon_int);
|
|
if (sc->beacon_interval != info->beacon_int)
|
|
{
|
|
sc->beacon_interval = info->beacon_int;
|
|
ssv6x5x_beacon_set_interval(sc->sh, sc->beacon_interval, sc->beacon_dtim_cnt);
|
|
}
|
|
|
|
}
|
|
|
|
if (changed & BSS_CHANGED_BEACON_ENABLED)
|
|
{
|
|
dbgprint(&sc->cmd_data, sc->log_ctrl, LOG_BEACON,
|
|
"[A] BSS_CHANGED_BEACON_ENABLED (0x%x)\n", info->enable_beacon);
|
|
|
|
ssv6x5x_beacon_enable(sc->sh, info->enable_beacon);
|
|
}
|
|
}
|
|
|
|
mutex_unlock(&sc->mutex);
|
|
printk("[I] %s(): leave\n", __FUNCTION__);
|
|
}
|
|
|
|
static int _ssv6200_sta_operation(struct ssv_softc *sc, struct ieee80211_sta *sta, int wsid,
|
|
struct ieee80211_supported_band *sband, ssv6xxx_rc_ops ops)
|
|
{
|
|
struct sk_buff *skb = NULL;
|
|
struct cfg_host_cmd *host_cmd = NULL;
|
|
struct ssv_rc_param *ptr = NULL;
|
|
int ret = 0;
|
|
|
|
skb = ssv_skb_alloc(sc, HOST_CMD_HDR_LEN + sizeof(struct ssv_rc_param));
|
|
if (skb == NULL) {
|
|
printk("%s(): Fail to alloc cmd buffer.\n", __FUNCTION__);
|
|
return -1;
|
|
}
|
|
|
|
skb_put(skb, HOST_CMD_HDR_LEN + sizeof(struct ssv_rc_param));
|
|
host_cmd = (struct cfg_host_cmd *)skb->data;
|
|
memset(host_cmd, 0x0, sizeof(struct cfg_host_cmd));
|
|
host_cmd->c_type = HOST_CMD;
|
|
host_cmd->h_cmd = (u8)SSV6XXX_HOST_CMD_RC_OPS;
|
|
host_cmd->sub_h_cmd = (u32)ops;
|
|
host_cmd->blocking_seq_no = (((u16)SSV6XXX_HOST_CMD_RC_OPS << 16)|(u16)ops);
|
|
host_cmd->len = HOST_CMD_HDR_LEN+sizeof(struct ssv_rc_param);
|
|
ptr = (struct ssv_rc_param *)host_cmd->un.dat8;
|
|
memset((void *)ptr, 0x0, sizeof(struct ssv_rc_param));
|
|
|
|
ptr->wsid = wsid;
|
|
if(SSV6XXX_RC_CMD_INIT == ops)
|
|
{
|
|
#if 0 //debug
|
|
printk(KERN_ERR "\n\nsta->supp_rates[%u] = 0x%08x\n", sband->band, sta->supp_rates[sband->band]);
|
|
#if LINUX_VERSION_CODE < KERNEL_VERSION(3,7,0)
|
|
printk(KERN_ERR "sc->hw->conf.channel_type = 0x%02x\n", sc->hw->conf.channel_type);
|
|
#else
|
|
printk(KERN_ERR "cfg80211_get_chandef_type(&(sc->hw->conf.chandef)) = 0x%02x\n", cfg80211_get_chandef_type(&(sc->hw->conf.chandef)));
|
|
#endif
|
|
printk(KERN_ERR "sta->ht_cap.cap = 0x%04x\n", sta->ht_cap.cap);
|
|
printk(KERN_ERR "sta->ht_cap.mcs.rx_mask[0] = 0x%02x\n", sta->ht_cap.mcs.rx_mask[0]);
|
|
printk(KERN_ERR "sta->ht_cap.mcs.rx_mask[1] = 0x%02x\n", sta->ht_cap.mcs.rx_mask[1]);
|
|
printk(KERN_ERR "sta->ht_cap.mcs.rx_mask[2] = 0x%02x\n", sta->ht_cap.mcs.rx_mask[2]);
|
|
printk(KERN_ERR "sta->ht_cap.mcs.rx_mask[3] = 0x%02x\n\n", sta->ht_cap.mcs.rx_mask[3]);
|
|
#endif
|
|
ptr->supp_rates = sta->supp_rates[sband->band];
|
|
if(INDEX_80211_BAND_5GHZ == sband->band)
|
|
{
|
|
ptr->supp_rates = ptr->supp_rates << 4; //5G rate starts from 6Mbps.
|
|
}
|
|
ptr->rc_mask = (u16)sc->sh->cfg.rc_mask;
|
|
ptr->ht_capabilities_info = sta->ht_cap.cap;
|
|
memcpy((void *)&ptr->supported_mcs_set[0], (const void *)&sta->ht_cap.mcs, 16);
|
|
ptr->ht_support = sta->ht_cap.ht_supported;
|
|
ptr->short_preamble = (sc->sc_flags & SC_OP_SHORT_PREAMBLE)?1:0;
|
|
#if LINUX_VERSION_CODE < KERNEL_VERSION(3,7,0)
|
|
ptr->rctype = sc->hw->conf.channel_type;
|
|
#else
|
|
ptr->rctype = cfg80211_get_chandef_type(&(sc->hw->conf.chandef));
|
|
#endif
|
|
}
|
|
ret = HCI_SEND_CMD(sc->sh, skb);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int ssv6200_sta_add(struct ieee80211_hw *hw,
|
|
struct ieee80211_vif *vif,
|
|
struct ieee80211_sta *sta)
|
|
{
|
|
struct ssv_sta_priv_data *sta_priv_dat=NULL;
|
|
struct ssv_softc *sc=hw->priv;
|
|
struct ssv_sta_info *sta_info;
|
|
//u32 reg_val;
|
|
int s;
|
|
unsigned long flags;
|
|
int ret = 0;
|
|
struct ssv_vif_priv_data *vif_priv = (struct ssv_vif_priv_data *)vif->drv_priv;
|
|
struct ieee80211_supported_band *sband;
|
|
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3,13,0)
|
|
struct ieee80211_chanctx_conf *chanctx_conf;
|
|
#endif
|
|
|
|
printk("[I] %s(): vif[%d] ", __FUNCTION__, vif_priv->vif_idx);
|
|
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3,13,0)
|
|
chanctx_conf = vif->chanctx_conf;
|
|
if (chanctx_conf == NULL) {
|
|
WARN_ON(1);
|
|
return -EINVAL;
|
|
}
|
|
#endif
|
|
|
|
mutex_lock(&sc->mutex);
|
|
//down_read(&sc->sta_info_sem);/* For mac80211 cb, we use sc->mutex to protect sta_info instead of sta_info_sem. */
|
|
do {
|
|
spin_lock_irqsave(&sc->ps_state_lock, flags);
|
|
|
|
s = HAL_GET_WSID(sc, vif, sta);
|
|
sta_info = &sc->sta_info[s];
|
|
sta_priv_dat = (struct ssv_sta_priv_data *)sta->drv_priv;
|
|
spin_unlock_irqrestore(&sc->ps_state_lock, flags);
|
|
|
|
if (s >= SSV_NUM_STA)
|
|
{
|
|
dev_err(sc->dev, "Number of STA exceeds driver limitation %d\n.", SSV_NUM_STA);
|
|
ret = -1;
|
|
break;
|
|
}
|
|
|
|
#ifdef CONFIG_SSV6XXX_DEBUGFS
|
|
ssv6xxx_debugfs_add_sta(sc, sta_info);
|
|
#endif // CONFIG_SSV6XXX_DEBUGFS
|
|
|
|
sta_priv_dat->wep_key_update = false;
|
|
/* Set WSID to default*/
|
|
sta_info->hw_wsid = -1;
|
|
SSV_SET_HW_WSID(sc, vif, sta, s);
|
|
|
|
if ((sta_priv_dat->has_hw_encrypt || sta_priv_dat->has_hw_decrypt) &&
|
|
((vif_priv->pair_cipher == SECURITY_WEP40) || (vif_priv->pair_cipher == SECURITY_WEP104)))
|
|
{
|
|
HAL_SET_WEP_HW_CRYPTO_KEY(sc, sta_priv_dat, vif_priv);
|
|
}
|
|
|
|
spin_lock_init(&sta_priv_dat->ampdu_ctrl_lock);
|
|
sband = sc->hw->wiphy->bands[sc->cur_channel->band];
|
|
_ssv6200_sta_operation(sc, sta, sta_info->hw_wsid, sband, SSV6XXX_RC_CMD_INIT);
|
|
|
|
printk("Add %02x:%02x:%02x:%02x:%02x:%02x to VIF %d sw_idx=%d, wsid=%d\n",
|
|
sta->addr[0], sta->addr[1], sta->addr[2],
|
|
sta->addr[3], sta->addr[4], sta->addr[5],
|
|
vif_priv->vif_idx,
|
|
sta_priv_dat->sta_idx, sta_info->hw_wsid);
|
|
} while (0);
|
|
//up_read(&sc->sta_info_sem);/* For mac80211 cb, we use sc->mutex to protect sta_info instead of sta_info_sem. */
|
|
mutex_unlock(&sc->mutex);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/* Use original Rx flow while wsid 0 and wsid 1 not use SW cipher*/
|
|
void ssv6200_rx_flow_check(struct ssv_sta_priv_data *sta_priv_dat,
|
|
struct ssv_softc *sc)
|
|
{
|
|
if (SSV6200_USE_HW_WSID(sta_priv_dat->sta_idx) && (sta_priv_dat->need_sw_decrypt)){
|
|
int other_hw_wsid = (sta_priv_dat->sta_idx+ 1) & 1;
|
|
struct ssv_sta_info *sta_info = &sc->sta_info[other_hw_wsid];/* sta_info is already protected by ssv6200_sta_remove(). */
|
|
struct ieee80211_sta *sta = sta_info->sta;/* sta_info is already protected by ssv6200_sta_remove(). */
|
|
struct ssv_sta_priv_data *sta_priv = (struct ssv_sta_priv_data *) sta->drv_priv;
|
|
|
|
if ((sta_info-> s_flags == 0)
|
|
|| ((sta_info-> s_flags & STA_FLAG_VALID) && (sta_priv->has_hw_decrypt))){/* sta_info is already protected by ssv6200_sta_remove(). */
|
|
// restore original rx flow at removing sta when other sta not used or use hw cipher
|
|
|
|
if (USE_MAC80211_RX(sc->sh)){
|
|
HAL_SET_RX_FLOW(sc->sh, RX_DATA_FLOW, RX_HCI);
|
|
} else {
|
|
HAL_SET_RX_FLOW(sc->sh, RX_DATA_FLOW, RX_CIPHER_MIC_HCI);
|
|
}
|
|
printk("redirect Rx flow for sta %d disconnect\n",sta_priv_dat->sta_idx);
|
|
}
|
|
}
|
|
}
|
|
static int ssv6200_sta_remove(struct ieee80211_hw *hw,
|
|
struct ieee80211_vif *vif,
|
|
struct ieee80211_sta *sta)
|
|
{
|
|
struct ssv_sta_priv_data *sta_priv_dat = (struct ssv_sta_priv_data *)sta->drv_priv;
|
|
struct ssv_softc *sc = hw->priv;
|
|
struct ssv_sta_info *sta_info = NULL;
|
|
unsigned long flags;
|
|
u32 bit;
|
|
struct ssv_vif_priv_data *priv_vif = (struct ssv_vif_priv_data *)vif->drv_priv;
|
|
u8 hw_wsid = -1;
|
|
|
|
BUG_ON(sta_priv_dat->sta_idx >= SSV_NUM_STA);
|
|
|
|
dev_notice(sc->dev,
|
|
"Removing STA %d (%02X:%02X:%02X:%02X:%02X:%02X) from VIF %d\n.",
|
|
sta_priv_dat->sta_idx, sta->addr[0], sta->addr[1], sta->addr[2],
|
|
sta->addr[3], sta->addr[4], sta->addr[5], priv_vif->vif_idx);
|
|
|
|
#if defined(IRQ_PROC_RX_DATA)
|
|
local_bh_disable();/* Prevent HWIF layer from using tasklet/workqueue to call ssv6200_rx(). */
|
|
#endif
|
|
mutex_lock(&sc->mutex);
|
|
if(sta_priv_dat->sta_idx == -1)
|
|
{
|
|
mutex_unlock(&sc->mutex);
|
|
#if defined(IRQ_PROC_RX_DATA)
|
|
local_bh_enable();
|
|
#endif
|
|
return 0;
|
|
}
|
|
sta_info = &sc->sta_info[sta_priv_dat->sta_idx];
|
|
HCI_TXQ_LOCK_BY_STA(sc->sh, sta_info->hw_wsid);
|
|
|
|
_ssv6200_sta_operation(sc, sta, sta_info->hw_wsid, NULL, SSV6XXX_RC_CMD_DEINIT);
|
|
|
|
down_write(&sc->sta_info_sem);
|
|
|
|
ssv6200_rx_flow_check(sta_priv_dat, sc);
|
|
|
|
spin_lock_irqsave(&sc->ps_state_lock, flags);
|
|
#ifdef CONFIG_SSV6XXX_DEBUGFS
|
|
// Remove STA debugfs during sta remove callback could results in deadlock.
|
|
//ssv6xxx_debugfs_remove_sta(sc, sta_info);
|
|
#endif // CONFIG_SSV6XXX_DEBUGFS
|
|
|
|
#if 0
|
|
// Freddie ToDo: Remove when power saving?
|
|
if ((sc->ps_status == PWRSV_PREPARE)||(sc->ps_status == PWRSV_ENABLE)) {
|
|
//ssv6xxx_enable_ps(sc);
|
|
memset(sta_info, 0, sizeof(*sta_info));
|
|
sta_priv_dat->sta_idx = -1;
|
|
|
|
list_del(&sta_priv_dat->list);
|
|
|
|
spin_unlock_irqrestore(&sc->ps_state_lock, flags);
|
|
return 0;
|
|
}
|
|
#endif
|
|
/*remove this sleep bit*/
|
|
bit = BIT(sta_priv_dat->sta_idx);
|
|
priv_vif->sta_asleep_mask &= ~bit;
|
|
|
|
/* Remove invalid wsid entry */
|
|
spin_unlock_irqrestore(&sc->ps_state_lock, flags);
|
|
HAL_DEL_FW_WSID(sc, sta, sta_info);
|
|
spin_lock_irqsave(&sc->ps_state_lock, flags);
|
|
hw_wsid = sta_info->hw_wsid;
|
|
|
|
#if 0
|
|
printk("%s(): sw_idx=%d, hw_idx=%d sta_asleep_mask[%08x]\n", __FUNCTION__,
|
|
sta_priv_dat->sta_idx , sta_info->hw_wsid, sc->sta_asleep_mask);
|
|
printk("Remove %02x:%02x:%02x:%02x:%02x:%02x to sw_idx=%d, wsid=%d\n",
|
|
sta->addr[0], sta->addr[1], sta->addr[2],
|
|
sta->addr[3], sta->addr[4], sta->addr[5], sta_priv_dat->sta_idx, sta_info->hw_wsid);
|
|
#endif
|
|
|
|
#ifdef CONFIG_SSV6XXX_DEBUGFS
|
|
{
|
|
spin_unlock_irqrestore(&sc->ps_state_lock, flags);
|
|
// Remove STA debugfs during sta remove callback could results in deadlock.
|
|
ssv6xxx_debugfs_remove_sta(sc, sta_info);
|
|
spin_lock_irqsave(&sc->ps_state_lock, flags);
|
|
}
|
|
#endif // CONFIG_SSV6XXX_DEBUGFS
|
|
|
|
memset(sta_info, 0, sizeof(*sta_info));
|
|
sta_priv_dat->sta_idx = -1;
|
|
list_del(&sta_priv_dat->list);
|
|
if (list_empty(&priv_vif->sta_list) && vif->type == NL80211_IFTYPE_STATION)
|
|
{
|
|
priv_vif->pair_cipher = 0;
|
|
priv_vif->group_cipher = 0;
|
|
sc->ps_aid = 0;
|
|
}
|
|
|
|
spin_unlock_irqrestore(&sc->ps_state_lock, flags);
|
|
|
|
#if 0
|
|
/**
|
|
* Remove the specified station from the driver data structure.
|
|
* Driver keeps this information for AMPDU use.
|
|
*/
|
|
sta_info = sc->sta_info;
|
|
for(s=0; s<SSV_NUM_STA; s++, sta_info++) {
|
|
if (sta_info->s_flags & STA_FLAG_VALID)
|
|
continue;
|
|
if (sta_info->sta == sta &&
|
|
sta_info->vif == vif)
|
|
sta_info->s_flags = 0;
|
|
}
|
|
#endif
|
|
|
|
HCI_TX_PAUSE_BY_STA(sc->sh, hw_wsid);
|
|
HCI_TXQ_FLUSH_BY_STA(sc->sh, hw_wsid);
|
|
HCI_TX_RESUME_BY_STA(sc->sh, hw_wsid);
|
|
SSV_DEL_HW_WSID(sc, hw_wsid);
|
|
printk("hw wsid %u is removed.\n", hw_wsid);
|
|
up_write(&sc->sta_info_sem);
|
|
HCI_TXQ_UNLOCK_BY_STA(sc->sh, hw_wsid);
|
|
mutex_unlock(&sc->mutex);
|
|
#if defined(IRQ_PROC_RX_DATA)
|
|
local_bh_enable();
|
|
#endif
|
|
return 0;
|
|
}
|
|
|
|
|
|
static void ssv6200_sta_notify(struct ieee80211_hw *hw,
|
|
struct ieee80211_vif *vif,
|
|
enum sta_notify_cmd cmd,
|
|
struct ieee80211_sta *sta)
|
|
{
|
|
struct ssv_softc *sc = hw->priv;
|
|
struct ssv_sta_priv_data *sta_priv_dat = (struct ssv_sta_priv_data *)sta->drv_priv;
|
|
u32 txqid = 0;
|
|
unsigned long flags;
|
|
|
|
if (vif->type != NL80211_IFTYPE_AP)
|
|
return;
|
|
|
|
spin_lock_irqsave(&sc->ps_state_lock, flags);
|
|
if(sta_priv_dat->sta_idx == -1)
|
|
{
|
|
spin_unlock_irqrestore(&sc->ps_state_lock, flags);
|
|
return;
|
|
}
|
|
txqid = sta_priv_dat->sta_idx;
|
|
|
|
switch (cmd)
|
|
{
|
|
case STA_NOTIFY_SLEEP:
|
|
HCI_TX_PAUSE_BY_STA(sc->sh, txqid);
|
|
break;
|
|
case STA_NOTIFY_AWAKE:
|
|
HCI_TX_RESUME_BY_STA(sc->sh, txqid);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
spin_unlock_irqrestore(&sc->ps_state_lock, flags);
|
|
return;
|
|
}
|
|
|
|
|
|
#if LINUX_VERSION_CODE < KERNEL_VERSION(3,2,0)
|
|
static u64 ssv6200_get_tsf(struct ieee80211_hw *hw)
|
|
#else
|
|
static u64 ssv6200_get_tsf(struct ieee80211_hw *hw,
|
|
struct ieee80211_vif *vif)
|
|
#endif
|
|
{
|
|
printk("%s(): \n", __FUNCTION__);
|
|
return 0;
|
|
}
|
|
|
|
void ssv6xxx_scan_opertaion(struct ssv_softc *sc, bool start)
|
|
{
|
|
struct sk_buff *skb = NULL;
|
|
struct cfg_host_cmd *host_cmd = NULL;
|
|
int ret = 0;
|
|
|
|
skb = ssv_skb_alloc(sc, HOST_CMD_HDR_LEN);
|
|
if (skb == NULL) {
|
|
printk("%s(): Fail to alloc cmd buffer.\n", __FUNCTION__);
|
|
return;
|
|
}
|
|
|
|
skb_put(skb, HOST_CMD_HDR_LEN);
|
|
host_cmd = (struct cfg_host_cmd *)skb->data;
|
|
memset(host_cmd, 0x0, sizeof(struct cfg_host_cmd));
|
|
host_cmd->c_type = HOST_CMD;
|
|
host_cmd->h_cmd = (u8)SSV6XXX_HOST_CMD_RFPHY_OPS;
|
|
host_cmd->sub_h_cmd = ((start == true) ? (u32)SSV6XXX_RFPHY_CMD_SCAN_START : (u32)SSV6XXX_RFPHY_CMD_SCAN_DONE);
|
|
host_cmd->len = HOST_CMD_HDR_LEN;
|
|
ret = HCI_SEND_CMD(sc->sh, skb);
|
|
if (ret)
|
|
printk("Fail to send scan command\n");
|
|
}
|
|
|
|
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3,19,0)
|
|
static void ssv6200_sw_scan_start(struct ieee80211_hw *hw,
|
|
struct ieee80211_vif *vif, const u8 *mac_addr)
|
|
#else
|
|
static void ssv6200_sw_scan_start(struct ieee80211_hw *hw)
|
|
#endif
|
|
{
|
|
struct ssv_softc *sc = hw->priv;
|
|
u32 dev_type = HCI_DEVICE_TYPE(sc->sh->hci.hci_ctrl);
|
|
|
|
ssv6xxx_scan_opertaion(sc, true);
|
|
#ifdef CONFIG_STA_BCN_FILTER
|
|
if (sc->sta_bcn_filter) {
|
|
//disable filter for beacon and probe request
|
|
HAL_SET_MRX_FILTER(sc->sh, 3, false, BIT(4)|BIT(5));
|
|
}
|
|
#endif
|
|
|
|
//disable beacon loss detect in scanning
|
|
SSV_BEACON_LOSS_DISABLE(sc->sh);
|
|
|
|
if (dev_type == SSV_HWIF_INTERFACE_USB)
|
|
sc->sh->cfg.usb_hw_resource |= USB_HW_RESOURCE_CHK_SCAN;
|
|
|
|
#ifdef CONFIG_ENABLE_ACS_FUNC
|
|
//Enable RX fixed gain setting for EDCCA.
|
|
if(!IS_NON_AP_MODE && !sc->ap_vif->p2p && IS_NONE_STA_CONNECTED_IN_AP_MODE)
|
|
{
|
|
SSV_RX_FIXED_GAIN_ENABLE(sc->sh, true);
|
|
}
|
|
|
|
//Disable RX fixed gain setting for EDCCA
|
|
if(!IS_NON_AP_MODE && !sc->ap_vif->p2p && IS_NONE_STA_CONNECTED_IN_AP_MODE)
|
|
{
|
|
SSV_RX_FIXED_GAIN_ENABLE(sc->sh, false);
|
|
}
|
|
#endif
|
|
|
|
sc->bScanning = true;
|
|
|
|
printk("--------------%s(): \n", __FUNCTION__);
|
|
}
|
|
|
|
|
|
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3,19,0)
|
|
static void ssv6200_sw_scan_complete(struct ieee80211_hw *hw,
|
|
struct ieee80211_vif *vif)
|
|
#else
|
|
static void ssv6200_sw_scan_complete(struct ieee80211_hw *hw)
|
|
#endif
|
|
{
|
|
struct ssv_softc *sc = hw->priv;
|
|
u32 dev_type = HCI_DEVICE_TYPE(sc->sh->hci.hci_ctrl);
|
|
|
|
ssv6xxx_scan_opertaion(sc, false);
|
|
#ifdef CONFIG_STA_BCN_FILTER
|
|
if (sc->sta_bcn_filter) {
|
|
//enable filter for beacon and probe request
|
|
HAL_SET_MRX_FILTER(sc->sh, 3, true, BIT(4)|BIT(5));
|
|
}
|
|
#endif
|
|
|
|
//enable beacon loss detect after scanning
|
|
if (sc->isAssoc)
|
|
SSV_BEACON_LOSS_ENABLE(sc->sh);
|
|
|
|
if (dev_type == SSV_HWIF_INTERFACE_USB)
|
|
sc->sh->cfg.usb_hw_resource &= (~USB_HW_RESOURCE_CHK_SCAN);
|
|
|
|
sc->bScanning = false;
|
|
|
|
printk("==============%s(): \n", __FUNCTION__);
|
|
}
|
|
|
|
|
|
static int ssv6200_set_tim(struct ieee80211_hw *hw, struct ieee80211_sta *sta,
|
|
bool set)
|
|
{
|
|
#if 1
|
|
struct ssv_softc *sc = hw->priv;
|
|
|
|
ssv6200_set_tim_work(sc);
|
|
return 0;
|
|
#else
|
|
struct ssv_softc *sc = hw->priv;
|
|
// int sta_idx = 0;
|
|
struct ssv_sta_info *sta_info = NULL;/* For mac80211 cb, we use sc->mutex to protect sta_info instead of sta_info_sem. */
|
|
|
|
mutex_lock(&sc->mutex);
|
|
if (sta) {
|
|
//while (!down_read_trylock(&sc->sta_info_sem));/* For performance concern, don't lock this code. Accessing tim_set is safe enough. */
|
|
if(((struct ssv_sta_priv_data *)sta->drv_priv)->sta_idx == -1)
|
|
{
|
|
printk("%s(): sta_info is gone.\n", __func__);
|
|
goto out;
|
|
}
|
|
sta_info = &sc->sta_info[((struct ssv_sta_priv_data *)sta->drv_priv)->sta_idx];
|
|
if ((sta_info->s_flags & STA_FLAG_VALID) == 0) {
|
|
printk("%s(): sta_info is gone.\n", __func__);
|
|
goto out;
|
|
}
|
|
}
|
|
|
|
/* Call set beacon if tim bit is changed */
|
|
if (sta_info && (sta_info->tim_set^set))
|
|
{
|
|
#ifdef BROADCAST_DEBUG
|
|
printk("[I] [A] ssv6200_set_tim");
|
|
#endif
|
|
sta_info->tim_set = set;
|
|
ssv6200_set_tim_work(sc);
|
|
}
|
|
|
|
out:
|
|
|
|
mutex_unlock(&sc->mutex);
|
|
return 0;
|
|
#endif
|
|
}
|
|
|
|
#if LINUX_VERSION_CODE < KERNEL_VERSION(3,2,0)
|
|
static int ssv6200_conf_tx(struct ieee80211_hw *hw, u16 queue,
|
|
const struct ieee80211_tx_queue_params *params)
|
|
#else
|
|
static int ssv6200_conf_tx(struct ieee80211_hw *hw,
|
|
struct ieee80211_vif *vif, u16 queue,
|
|
const struct ieee80211_tx_queue_params *params)
|
|
#endif
|
|
{
|
|
struct ssv_softc *sc = hw->priv;
|
|
int ret = 0;
|
|
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3,2,0)
|
|
struct ssv_vif_priv_data *priv_vif = (struct ssv_vif_priv_data *)vif->drv_priv;
|
|
|
|
printk("[I] sv6200_conf_tx vif[%d] qos[%d] queue[%d] aifsn[%d] cwmin[%d] cwmax[%d] txop[%d] \n",
|
|
priv_vif->vif_idx ,vif->bss_conf.qos, queue, params->aifs, params->cw_min, params->cw_max, params->txop);
|
|
#else
|
|
printk("[I] sv6200_conf_tx queue[%d] aifsn[%d] cwmin[%d] cwmax[%d] txop[%d] \n",
|
|
queue, params->aifs, params->cw_min, params->cw_max, params->txop);
|
|
#endif
|
|
|
|
if (queue > NL80211_TXQ_Q_BK) {
|
|
ret = 1;
|
|
goto out;
|
|
}
|
|
|
|
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3,2,0)
|
|
if ((priv_vif->vif_idx != 0) && (vif->p2p == 0) ) {
|
|
dev_warn(sc->dev, "WMM setting applicable to primary interface only.\n");
|
|
ret = 1;
|
|
goto out;
|
|
}
|
|
#endif
|
|
|
|
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3,2,0)
|
|
//set QoS status
|
|
SSV_SET_QOS_ENABLE(sc->sh, vif->bss_conf.qos);
|
|
#endif
|
|
|
|
//set wmm parameter
|
|
SSV_SET_WMM_PARAM(sc, params, queue);
|
|
out:
|
|
return ret;
|
|
}
|
|
|
|
|
|
static bool ssv6xxx_is_ampdu_rx_sta(struct ssv_softc *sc, struct ieee80211_sta *sta)
|
|
{
|
|
struct ssv_sta_info *sta_info;
|
|
int i = 0;
|
|
bool find_peer = false;
|
|
|
|
for (i = 0; i < SSV_NUM_STA; i++) {
|
|
sta_info = &sc->sta_info[i];
|
|
if ((sta_info->s_flags & STA_FLAG_VALID) && (sta == sta_info->sta)) {/* sta_info is already protected by ssv6200_ampdu_action(). */
|
|
if (sta_info->s_flags & STA_FLAG_AMPDU_RX) {/* sta_info is already protected by ssv6200_ampdu_action(). */
|
|
find_peer = true;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
return find_peer;
|
|
}
|
|
|
|
static int ssv6xxx_ampdu_opertaion(struct ssv_softc *sc, ssv6xxx_ampdu_ops ops, u16 tid, int wsid)
|
|
{
|
|
struct sk_buff *skb = NULL;
|
|
struct cfg_host_cmd *host_cmd = NULL;
|
|
struct ssv_ampdu_param *ptr = NULL;
|
|
int ret = 0;
|
|
|
|
printk("%s() tid %d, wsid %d\n", __FUNCTION__, tid, wsid);
|
|
skb = ssv_skb_alloc(sc, HOST_CMD_HDR_LEN + sizeof(struct ssv_ampdu_param));
|
|
if (skb == NULL) {
|
|
printk("%s(): Fail to alloc cmd buffer.\n", __FUNCTION__);
|
|
return -1;
|
|
}
|
|
|
|
skb_put(skb, HOST_CMD_HDR_LEN + sizeof(struct ssv_ampdu_param));
|
|
host_cmd = (struct cfg_host_cmd *)skb->data;
|
|
memset(host_cmd, 0x0, sizeof(struct cfg_host_cmd));
|
|
host_cmd->c_type = HOST_CMD;
|
|
host_cmd->h_cmd = (u8)SSV6XXX_HOST_CMD_AMPDU_OPS;
|
|
host_cmd->sub_h_cmd = (u32)ops;
|
|
host_cmd->blocking_seq_no = (((u16)SSV6XXX_HOST_CMD_AMPDU_OPS << 16)|(u16)ops);
|
|
host_cmd->len = HOST_CMD_HDR_LEN + sizeof(struct ssv_ampdu_param);
|
|
ptr = (struct ssv_ampdu_param *)host_cmd->un.dat8;
|
|
memset(ptr, 0x0, sizeof(struct ssv_ampdu_param));
|
|
ptr->tid = tid;
|
|
ptr->wsid = (u8)wsid;
|
|
ret = HCI_SEND_CMD(sc->sh, skb);
|
|
|
|
return 0;
|
|
}
|
|
|
|
void ssv6xxx_set_ampdu_rx_add_work(struct work_struct *work)
|
|
{
|
|
struct ssv_softc *sc = container_of(work, struct ssv_softc, set_ampdu_rx_add_work);
|
|
SSV_SET_RX_BA(sc->sh, true, sc->ba_ra_addr, sc->ba_tid, sc->ba_ssn, 64);
|
|
}
|
|
|
|
void ssv6xxx_set_ampdu_rx_del_work(struct work_struct *work)
|
|
{
|
|
struct ssv_softc*sc = container_of(work, struct ssv_softc, set_ampdu_rx_del_work);
|
|
u8 addr[6] = { 0 };
|
|
|
|
SSV_SET_RX_BA(sc->sh, false, addr, 0, 0, 0);
|
|
}
|
|
|
|
#if LINUX_VERSION_CODE < KERNEL_VERSION(3,0,0)
|
|
static int ssv6200_ampdu_action(struct ieee80211_hw *hw,
|
|
struct ieee80211_vif *vif,
|
|
enum ieee80211_ampdu_mlme_action action,
|
|
struct ieee80211_sta *sta,
|
|
u16 tid, u16 *ssn)
|
|
|
|
#elif LINUX_VERSION_CODE >= KERNEL_VERSION(3,0,0) && LINUX_VERSION_CODE < KERNEL_VERSION(4,4,0)
|
|
static int ssv6200_ampdu_action(struct ieee80211_hw *hw,
|
|
struct ieee80211_vif *vif,
|
|
enum ieee80211_ampdu_mlme_action action,
|
|
struct ieee80211_sta *sta,
|
|
u16 tid, u16 *ssn, u8 buf_size)
|
|
#elif LINUX_VERSION_CODE >= KERNEL_VERSION(4,4,0) && LINUX_VERSION_CODE < KERNEL_VERSION(4,4,69)
|
|
static int ssv6200_ampdu_action(struct ieee80211_hw *hw,
|
|
struct ieee80211_vif *vif,
|
|
enum ieee80211_ampdu_mlme_action action,
|
|
struct ieee80211_sta *sta,
|
|
u16 tid, u16 *ssn, u8 buf_size, bool amsdu)
|
|
#else
|
|
static int ssv6200_ampdu_action(struct ieee80211_hw *hw,
|
|
struct ieee80211_vif *vif,
|
|
struct ieee80211_ampdu_params *params)
|
|
#endif
|
|
|
|
|
|
{
|
|
#if LINUX_VERSION_CODE < KERNEL_VERSION(3,0,0)
|
|
u8 buf_size = 32;
|
|
#endif
|
|
struct ssv_softc *sc = hw->priv;
|
|
struct ssv_sta_priv_data *sta_priv;
|
|
struct ssv_sta_info *sta_info;
|
|
bool find_peer = false;
|
|
int ret = 0;
|
|
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4,4,69)
|
|
struct ieee80211_sta *sta = params->sta;
|
|
enum ieee80211_ampdu_mlme_action action = params->action;
|
|
u16 tid = params->tid;
|
|
u16 *ssn = &(params->ssn);
|
|
u8 buf_size = params->buf_size;
|
|
#endif
|
|
|
|
//printk("[I] %s(): \n", __FUNCTION__);
|
|
|
|
if(sta == NULL)
|
|
return ret;
|
|
|
|
if((action == IEEE80211_AMPDU_RX_START || action == IEEE80211_AMPDU_RX_STOP ) &&
|
|
(!(sc->sh->cfg.hw_caps & SSV6200_HW_CAP_AMPDU_RX)))
|
|
{
|
|
printk("Disable AMPDU_RX(2).\n");
|
|
return -EOPNOTSUPP;
|
|
}
|
|
|
|
if( ( action == IEEE80211_AMPDU_TX_START
|
|
#if LINUX_VERSION_CODE < KERNEL_VERSION(3,7,0)
|
|
|| action == IEEE80211_AMPDU_TX_STOP
|
|
#else
|
|
|| action == IEEE80211_AMPDU_TX_STOP_CONT
|
|
|| action == IEEE80211_AMPDU_TX_STOP_FLUSH
|
|
|| action == IEEE80211_AMPDU_TX_STOP_FLUSH_CONT
|
|
#endif
|
|
|| action == IEEE80211_AMPDU_TX_OPERATIONAL )
|
|
&& (!(sc->sh->cfg.hw_caps & SSV6200_HW_CAP_AMPDU_TX)))
|
|
{
|
|
printk("Disable AMPDU_TX(2).\n");
|
|
return -EOPNOTSUPP;
|
|
}
|
|
|
|
mutex_lock(&sc->mutex);
|
|
//down_read(&sc->sta_info_sem);/* For mac80211 cb, we use sc->mutex to protect sta_info instead of sta_info_sem. */
|
|
if ((((struct ssv_sta_priv_data *)sta->drv_priv)->sta_idx == -1) ||
|
|
((sc->sta_info[((struct ssv_sta_priv_data *)sta->drv_priv)->sta_idx].s_flags & STA_FLAG_VALID) == 0)) {
|
|
mutex_unlock(&sc->mutex);
|
|
//up_read(&sc->sta_info_sem);/* For mac80211 cb, we use sc->mutex to protect sta_info instead of sta_info_sem. */
|
|
if(!(sc->sc_flags & SC_OP_HW_RESET))
|
|
{
|
|
printk(KERN_WARNING "%s(): sta_info is gone.\n", __func__);
|
|
return -ENODATA;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
switch (action)
|
|
{
|
|
case IEEE80211_AMPDU_RX_START:
|
|
ret = SSV_AMPDU_RX_START(sc, hw, vif, sta, tid, ssn, buf_size);
|
|
if (!ret)
|
|
queue_work(sc->config_wq, &sc->set_ampdu_rx_add_work);
|
|
break;
|
|
|
|
case IEEE80211_AMPDU_RX_STOP:
|
|
find_peer = ssv6xxx_is_ampdu_rx_sta(sc, sta);
|
|
if (!find_peer)
|
|
break;
|
|
|
|
sta_priv = (struct ssv_sta_priv_data *)sta->drv_priv;
|
|
sta_info = &sc->sta_info[sta_priv->sta_idx];
|
|
|
|
sta_info->s_flags &= ~(STA_FLAG_AMPDU_RX);
|
|
sc->rx_ba_session_count--;
|
|
if (sc->rx_ba_session_count <= 0) {
|
|
sc->rx_ba_sta = NULL;
|
|
sc->rx_ba_session_count = 0;
|
|
}
|
|
|
|
queue_work(sc->config_wq, &sc->set_ampdu_rx_del_work);
|
|
break;
|
|
|
|
case IEEE80211_AMPDU_TX_START:
|
|
printk(KERN_ERR "AMPDU_TX_START %02X:%02X:%02X:%02X:%02X:%02X %d.\n",
|
|
sta->addr[0], sta->addr[1], sta->addr[2], sta->addr[3],
|
|
sta->addr[4], sta->addr[5], tid);
|
|
|
|
sta_priv = (struct ssv_sta_priv_data *)sta->drv_priv;
|
|
if (sta_priv->ampdu_tid_state[tid] != SSV_AMPDU_1_3_TX_START) {
|
|
//spin_lock_bh(&sta_priv->ampdu_ctrl_lock);
|
|
sta_priv->ampdu_tid_state[tid] = SSV_AMPDU_1_3_TX_START;
|
|
//spin_unlock_bh(&sta_priv->ampdu_ctrl_lock);
|
|
ssv6xxx_ampdu_opertaion(sc, SSV6XXX_AMPDU_CMD_START, tid, sta_priv->sta_info->hw_wsid);
|
|
}
|
|
ieee80211_start_tx_ba_cb_irqsafe(vif, sta->addr, tid);
|
|
break;
|
|
|
|
#if LINUX_VERSION_CODE < KERNEL_VERSION(3,7,0)
|
|
case IEEE80211_AMPDU_TX_STOP:
|
|
#else
|
|
/* @IEEE80211_AMPDU_TX_STOP_CONT: stop TX aggregation but continue transmitting
|
|
* queued packets, now unaggregated. After all packets are transmitted the
|
|
* driver has to call ieee80211_stop_tx_ba_cb_irqsafe().
|
|
* @IEEE80211_AMPDU_TX_STOP_FLUSH: stop TX aggregation and flush all packets,
|
|
* called when the station is removed. There's no need or reason to call
|
|
* ieee80211_stop_tx_ba_cb_irqsafe() in this case as mac80211 assumes the
|
|
* session is gone and removes the station.
|
|
* @IEEE80211_AMPDU_TX_STOP_FLUSH_CONT: called when TX aggregation is stopped
|
|
* but the driver hasn't called ieee80211_stop_tx_ba_cb_irqsafe() yet and
|
|
* now the connection is dropped and the station will be removed. Drivers
|
|
* should clean up and drop remaining packets when this is called.
|
|
*/
|
|
// Freddie ToDo:
|
|
case IEEE80211_AMPDU_TX_STOP_CONT:
|
|
case IEEE80211_AMPDU_TX_STOP_FLUSH:
|
|
case IEEE80211_AMPDU_TX_STOP_FLUSH_CONT:
|
|
#endif
|
|
printk(KERN_ERR "AMPDU_TX_STOP %02X:%02X:%02X:%02X:%02X:%02X %d.\n",
|
|
sta->addr[0], sta->addr[1], sta->addr[2], sta->addr[3],
|
|
sta->addr[4], sta->addr[5], tid);
|
|
sta_priv = (struct ssv_sta_priv_data *)sta->drv_priv;
|
|
//spin_lock_bh(&sta_priv->ampdu_ctrl_lock);
|
|
sta_priv->ampdu_tid_state[tid] = SSV_AMPDU_1_3_TX_STOP;
|
|
//spin_unlock_bh(&sta_priv->ampdu_ctrl_lock);
|
|
ssv6xxx_ampdu_opertaion(sc, SSV6XXX_AMPDU_CMD_STOP, tid, sta_priv->sta_info->hw_wsid);
|
|
ieee80211_stop_tx_ba_cb_irqsafe(vif, sta->addr, tid);
|
|
break;
|
|
|
|
case IEEE80211_AMPDU_TX_OPERATIONAL:
|
|
printk(KERN_ERR "AMPDU_TX_OPERATIONAL %02X:%02X:%02X:%02X:%02X:%02X %d.\n",
|
|
sta->addr[0], sta->addr[1], sta->addr[2], sta->addr[3],
|
|
sta->addr[4], sta->addr[5], tid);
|
|
break;
|
|
|
|
default:
|
|
ret = -EOPNOTSUPP;//not support.
|
|
break;
|
|
}
|
|
|
|
//up_read(&sc->sta_info_sem);/* For mac80211 cb, we use sc->mutex to protect sta_info instead of sta_info_sem. */
|
|
mutex_unlock(&sc->mutex);
|
|
|
|
return ret;
|
|
}
|
|
|
|
#ifdef CONFIG_ENABLE_ACS_FUNC
|
|
static int ssv6200_get_survey(struct ieee80211_hw *hw, int idx, struct survey_info *survey)
|
|
{
|
|
struct ssv_softc *sc = hw->priv;
|
|
struct ieee80211_supported_band *sband = NULL;
|
|
struct survey_info *ssv_survey = &sc->survey[idx];
|
|
int ret = 0;
|
|
|
|
if(idx == 0)
|
|
{
|
|
SSV_EDCA_UPDATE_SURVEY(sc->sh);
|
|
}
|
|
|
|
sband = &sc->sbands[INDEX_80211_BAND_2GHZ]; //2G
|
|
if (idx >= sband->n_channels)
|
|
{
|
|
idx -= sband->n_channels;
|
|
sband = &sc->sbands[INDEX_80211_BAND_5GHZ]; //5G
|
|
}
|
|
|
|
if (idx >= sband->n_channels)
|
|
{
|
|
ret = -ENOENT;
|
|
goto EXIT;
|
|
}
|
|
|
|
/* Fill survey information */
|
|
memcpy((void *)survey, (const void *)ssv_survey, sizeof(struct survey_info));
|
|
|
|
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4,0,0)
|
|
//printk("channel(%u) time = %lld, time_busy = %lld, time_ext_busy = %lld\n", sband->channels[idx].hw_value, survey->time, survey->time_busy, survey->time_ext_busy);
|
|
#else
|
|
//printk("channel(%u) time = %lld, time_busy = %lld, time_ext_busy = %lld\n", sband->channels[idx].hw_value, survey->channel_time, survey->channel_time_busy, survey->channel_time_ext_busy);
|
|
#endif
|
|
//To avoid complains there is insufficient survey data.
|
|
survey->filled |= SURVEY_INFO_NOISE_DBM;
|
|
survey->noise = -92;
|
|
|
|
survey->channel = &sband->channels[idx];
|
|
|
|
if(sc->cur_channel == survey->channel)
|
|
{
|
|
survey->filled |= SURVEY_INFO_IN_USE;
|
|
}
|
|
|
|
|
|
EXIT:
|
|
return ret;
|
|
}
|
|
#endif
|
|
|
|
static int ssv6200_set_bitrate_mask(struct ieee80211_hw *hw,
|
|
struct ieee80211_vif *vif, const struct cfg80211_bitrate_mask *mask)
|
|
{
|
|
printk("%s start\n", __FUNCTION__);
|
|
return 0;
|
|
}
|
|
|
|
static int ssv6200_set_rts_threshold(struct ieee80211_hw *hw, u32 value)
|
|
{
|
|
struct ssv_softc *sc=hw->priv;
|
|
struct sk_buff *skb = NULL;
|
|
struct cfg_host_cmd *host_cmd = NULL;
|
|
struct ssv6xxx_rts_param *ptr = NULL;
|
|
int ret = 0;
|
|
|
|
mutex_lock(&sc->mutex);
|
|
skb = ssv_skb_alloc(sc, HOST_CMD_HDR_LEN + sizeof(struct ssv6xxx_rts_param));
|
|
if (skb == NULL) {
|
|
printk("%s(): Fail to alloc cmd buffer.\n", __FUNCTION__);
|
|
goto out;
|
|
}
|
|
|
|
skb_put(skb, HOST_CMD_HDR_LEN + sizeof(struct ssv6xxx_rts_param));
|
|
host_cmd = (struct cfg_host_cmd *)skb->data;
|
|
memset(host_cmd, 0x0, sizeof(struct cfg_host_cmd));
|
|
host_cmd->c_type = HOST_CMD;
|
|
host_cmd->h_cmd = (u8)SSV6XXX_HOST_CMD_RTS_THRESHOLD;
|
|
host_cmd->len = HOST_CMD_HDR_LEN + sizeof(struct ssv6xxx_rts_param);
|
|
|
|
ptr = (struct ssv6xxx_rts_param *)host_cmd->un.dat8;
|
|
memset(ptr, 0x0, sizeof(struct ssv6xxx_rts_param));
|
|
ptr->rts_threshold = value;
|
|
ret = HCI_SEND_CMD(sc->sh, skb);
|
|
if (ret)
|
|
printk("Fail to send rts command\n");
|
|
|
|
out:
|
|
mutex_unlock(&sc->mutex);
|
|
return 0;
|
|
}
|
|
|
|
int ssv6xxx_send_rawdata_packet(char *data, int len)
|
|
{
|
|
struct ssv_softc *sc = ssv6xxx_driver_attach(SSV_DRVER_NAME);
|
|
struct SKB_info_st *skb_info = NULL;
|
|
struct sk_buff *tx_frame = NULL;
|
|
|
|
if (NULL == sc) {
|
|
printk("%s() Cannot find ssv driver\n", __FUNCTION__);
|
|
return -EINVAL;
|
|
}
|
|
|
|
tx_frame = ssv_skb_alloc_ex(sc, len + TXPB_OFFSET, GFP_ATOMIC);
|
|
if (NULL == tx_frame)
|
|
return -EINVAL;
|
|
|
|
if (skb_headroom(tx_frame) < sizeof(struct SKB_info_st)) {
|
|
skb_reserve(tx_frame, sizeof(struct SKB_info_st));
|
|
}
|
|
|
|
// clear skb->cb, mac80211 store ieee80211_tx_info in skb->cb
|
|
memset(tx_frame->cb, 0, sizeof(tx_frame->cb));
|
|
// set skb info
|
|
skb_info = (struct SKB_info_st *)tx_frame->head;
|
|
memset(skb_info, 0, sizeof (struct SKB_info_st));
|
|
skb_info->sta = NULL;
|
|
skb_info->raw_data = true;
|
|
|
|
skb_put(tx_frame, len + TXPB_OFFSET);
|
|
skb_pull(tx_frame, TXPB_OFFSET);
|
|
memcpy(tx_frame->data, data, len);
|
|
SSV_ADD_TXINFO(sc, tx_frame);
|
|
// tx done task will free tx_frame
|
|
HCI_SEND(sc->sh, tx_frame, SSV_SW_TXQ_ID_MNG, false);
|
|
|
|
return 0;
|
|
}
|
|
//EXPORT_SYMBOL(ssv6xxx_send_rawdata_packet);
|
|
|
|
struct ieee80211_ops ssv6200_ops =
|
|
{
|
|
/* MUST callback function: */
|
|
.tx = ssv6200_tx,
|
|
.start = ssv6200_start,
|
|
.stop = ssv6200_stop,
|
|
.add_interface = ssv6200_add_interface,
|
|
.change_interface = ssv6200_change_interface,
|
|
.remove_interface = ssv6200_remove_interface,
|
|
.config = ssv6200_config,
|
|
.configure_filter = ssv6200_config_filter,
|
|
|
|
|
|
/* OPTIONAL callback function: */
|
|
.bss_info_changed = ssv6200_bss_info_changed,
|
|
.sta_add = ssv6200_sta_add,
|
|
.sta_remove = ssv6200_sta_remove,
|
|
.sta_notify = ssv6200_sta_notify,
|
|
.set_key = ssv6200_set_key,
|
|
.sw_scan_start = ssv6200_sw_scan_start,
|
|
.sw_scan_complete = ssv6200_sw_scan_complete,
|
|
#ifdef CONFIG_SSV_VENDOR_OPS
|
|
.send_rawdata = ssv6xxx_send_rawdata_packet,
|
|
#endif
|
|
#if ((LINUX_VERSION_CODE >= KERNEL_VERSION(3,17,0)) && defined(CONFIG_HW_SCAN))
|
|
.hw_scan = ssv6200_hw_scan,
|
|
.cancel_hw_scan = ssv6200_cancel_hw_scan,
|
|
#endif
|
|
.get_tsf = ssv6200_get_tsf,
|
|
.set_tim = ssv6200_set_tim,
|
|
.conf_tx = ssv6200_conf_tx,
|
|
.ampdu_action = ssv6200_ampdu_action,
|
|
#ifdef CONFIG_ENABLE_ACS_FUNC
|
|
.get_survey = ssv6200_get_survey,
|
|
#endif
|
|
.set_bitrate_mask = ssv6200_set_bitrate_mask,
|
|
.set_rts_threshold = ssv6200_set_rts_threshold,
|
|
#ifdef CONFIG_PM
|
|
.suspend = ssv6xxx_suspend,
|
|
.resume = ssv6xxx_resume,
|
|
#endif
|
|
};
|
|
|
|
void ssv6xxx_enable_usb_acc(void *param, u8 epnum)
|
|
{
|
|
struct ssv_softc *sc = (struct ssv_softc *)param;
|
|
|
|
SSV_ENABLE_USB_ACC(sc, epnum);
|
|
return;
|
|
}
|
|
|
|
void ssv6xxx_disable_usb_acc(void *param, u8 epnum)
|
|
{
|
|
struct ssv_softc *sc = (struct ssv_softc *)param;
|
|
|
|
SSV_DISABLE_USB_ACC(sc, epnum);
|
|
return;
|
|
}
|
|
|
|
void ssv6xxx_jump_to_rom(void *param)
|
|
{
|
|
struct ssv_softc *sc = (struct ssv_softc *)param;
|
|
|
|
SSV_JUMP_TO_ROM(sc);
|
|
return;
|
|
}
|
|
|
|
int ssv6xxx_rx_mode(void *param)
|
|
{
|
|
struct ssv_softc *sc = (struct ssv_softc *)param;
|
|
struct ssv_hw *sh = sc->sh;
|
|
|
|
return sh->rx_mode;
|
|
}
|
|
|
|
void ssv6200_tx_flow_control(struct ssv_softc *sc, bool fc_en, bool force_stop)
|
|
{
|
|
int tx_frame = 0;
|
|
|
|
if (sc->sh->cfg.flowctl) {
|
|
|
|
if (sc->log_ctrl & LOG_FLOWCTL) {
|
|
printk(KERN_ERR "hci_tx_frame = %d\n", HCI_TXQ_LEN(sc->sh));
|
|
}
|
|
|
|
sc->flowctl_hci_cnt = HCI_TXQ_LEN(sc->sh);
|
|
tx_frame = sc->flowctl_hci_cnt;
|
|
sc->flowctl_frame_cnt = tx_frame;
|
|
|
|
if (fc_en) {
|
|
if (!(sc->sc_flags & SC_OP_FLOWCTL)) {
|
|
if (force_stop || (tx_frame > sc->sh->cfg.flowctl_high_threshold)) {
|
|
ieee80211_stop_queues(sc->hw);
|
|
sc->sc_flags |= SC_OP_FLOWCTL;
|
|
//printk("TXQ enable tx flow\n");
|
|
}
|
|
}
|
|
} else {
|
|
if (sc->sc_flags & SC_OP_FLOWCTL) {
|
|
if (tx_frame < sc->sh->cfg.flowctl_low_threshold) {
|
|
ieee80211_wake_queues(sc->hw);
|
|
sc->sc_flags &= ~SC_OP_FLOWCTL;
|
|
//printk("TXQ disable tx flow\n");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#if 1
|
|
static u64 ssv6200_get_systime_us(void)
|
|
{
|
|
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 20, 0))
|
|
return ktime_to_us(ktime_get_boottime());
|
|
#elif (LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 39))
|
|
struct timespec ts;
|
|
get_monotonic_boottime(&ts);
|
|
return ((u64)ts.tv_sec * 1000000) + ts.tv_nsec / 1000;
|
|
#else
|
|
struct timeval tv;
|
|
do_gettimeofday(&tv);
|
|
return ((u64)tv.tv_sec * 1000000) + tv.tv_usec;
|
|
#endif
|
|
}
|
|
|
|
#else
|
|
static u64 ssv6200_get_systime_us(void)
|
|
{
|
|
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,39))
|
|
struct timespec ts;
|
|
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(4,15,0))
|
|
getnstimeofday(&ts);
|
|
#else
|
|
get_monotonic_boottime(&ts);
|
|
#endif
|
|
return ((u64)ts.tv_sec*1000000)+ts.tv_nsec/1000;
|
|
#else
|
|
struct timeval tv;
|
|
do_gettimeofday(&tv);
|
|
return ((u64)tv.tv_sec*1000000)+tv.tv_usec;
|
|
#endif
|
|
}
|
|
#endif
|
|
static void _proc_data_rx_mib(struct ssv_softc *sc, struct sk_buff *skb)
|
|
{
|
|
struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)skb->data;
|
|
|
|
if (ieee80211_is_data(hdr->frame_control))
|
|
sc->rx.rx_data_count++;
|
|
else if (ieee80211_is_mgmt(hdr->frame_control)) {
|
|
sc->rx.rx_mgmt_count++;
|
|
if (ieee80211_is_beacon(hdr->frame_control))
|
|
sc->rx.rx_bcn_count++;
|
|
else if (ieee80211_is_probe_resp(hdr->frame_control))
|
|
sc->rx.rx_proberesp_count++;
|
|
else if (ieee80211_is_probe_req(hdr->frame_control))
|
|
sc->rx.rx_probereq_count++;
|
|
else if (ieee80211_is_assoc_req(hdr->frame_control))
|
|
sc->rx.rx_assoc_req_count++;
|
|
else if (ieee80211_is_assoc_resp(hdr->frame_control))
|
|
sc->rx.rx_assoc_resp_count++;
|
|
else if (ieee80211_is_auth(hdr->frame_control))
|
|
sc->rx.rx_auth_count++;
|
|
else if (ieee80211_is_disassoc(hdr->frame_control))
|
|
sc->rx.rx_disassoc_count++;
|
|
else if (ieee80211_is_deauth(hdr->frame_control))
|
|
sc->rx.rx_deauth_count++;
|
|
}
|
|
}
|
|
|
|
//static int rx_count = 0;
|
|
// Process RX SKB, i.e. strip SSV header and appended data and send it to upper layer
|
|
static void ssv6xxx_proc_data_rx_skb (struct ssv_softc *sc, struct sk_buff *rx_skb)
|
|
{
|
|
struct ieee80211_rx_status *rxs;
|
|
struct ieee80211_hdr *hdr;
|
|
__le16 fc;
|
|
struct ieee80211_vif *vif = NULL;
|
|
struct ieee80211_sta *sta = NULL;
|
|
bool rx_hw_dec = false;
|
|
bool do_sw_dec = false;
|
|
struct ssv_sta_priv_data *sta_priv = NULL;
|
|
struct ssv_vif_priv_data *vif_priv = NULL;
|
|
SKB_info *skb_info = NULL;
|
|
u32 wsid, rate_idx, mng_used;
|
|
bool aggr, tkip_mmic_err;
|
|
|
|
/* extract headers */
|
|
hdr = (struct ieee80211_hdr *)(rx_skb->data + HAL_GET_RX_DESC_SIZE(sc->sh));
|
|
wsid = HAL_GET_RX_DESC_WSID(sc->sh, rx_skb);
|
|
rate_idx= HAL_GET_RX_DESC_RATE_IDX(sc->sh, rx_skb);
|
|
aggr = HAL_IS_RX_AGGR(sc->sh, rx_skb);
|
|
mng_used = HAL_GET_RX_DESC_MNG_USED(sc->sh, rx_skb);
|
|
|
|
fc = hdr->frame_control;
|
|
skb_info = (SKB_info *)rx_skb->head;
|
|
|
|
if(ieee80211_is_beacon(hdr->frame_control))
|
|
{
|
|
struct ieee80211_mgmt *mgmt_tmp = NULL;
|
|
mgmt_tmp = (struct ieee80211_mgmt *)(rx_skb->data+HAL_GET_RX_DESC_SIZE(sc->sh));
|
|
mgmt_tmp->u.beacon.timestamp = cpu_to_le64(ssv6200_get_systime_us()+(10*1000*1000));
|
|
}
|
|
|
|
if(ieee80211_is_probe_resp(hdr->frame_control))
|
|
{
|
|
struct ieee80211_mgmt *mgmt_tmp = NULL;
|
|
mgmt_tmp = (struct ieee80211_mgmt *)(rx_skb->data+HAL_GET_RX_DESC_SIZE(sc->sh));
|
|
mgmt_tmp->u.probe_resp.timestamp = cpu_to_le64(ssv6200_get_systime_us()+(10*1000*1000));
|
|
}
|
|
|
|
/*
|
|
// Parser connected STA data rate(Rate control)
|
|
WSID0 & WSID1 we use rate control alogrithm.
|
|
WSID2 - WSID7 based on the other side of the data rate.
|
|
*/
|
|
HAL_RC_RX_DATA_HANDLER(sc, rx_skb, rate_idx);
|
|
|
|
/* prepare rx status for mac80211 */
|
|
rxs = IEEE80211_SKB_RXCB(rx_skb);
|
|
memset(rxs, 0, sizeof(struct ieee80211_rx_status));
|
|
|
|
SSV_UPDATE_MAC80211_CHAN_INFO(sc, rx_skb, rxs);
|
|
rxs->antenna = 1;
|
|
SSV_RC_MAC80211_RATE_IDX(sc, rate_idx, rxs);
|
|
|
|
#ifdef CONFIG_SSV_CTL
|
|
{
|
|
void ssv_ctl_nl_send_msg(struct sk_buff *skb);
|
|
|
|
if (sc->ssv_smartlink_status)
|
|
{
|
|
/* remove ssv6xxx headers before passing to mac80211 */
|
|
skb_pull(rx_skb, HAL_GET_RX_DESC_SIZE(sc->sh));
|
|
skb_trim(rx_skb, rx_skb->len-sc->sh->rx_pinfo_pad);
|
|
|
|
ssv_ctl_nl_send_msg(rx_skb);
|
|
goto drop_rx;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
if(sc->sc_flags & SC_OP_MONITOR)
|
|
{
|
|
/* remove ssv6xxx headers before passing to mac80211 */
|
|
skb_pull(rx_skb, HAL_GET_RX_DESC_SIZE(sc->sh));
|
|
skb_trim(rx_skb, rx_skb->len-sc->sh->rx_pinfo_pad);
|
|
goto monitor;
|
|
}
|
|
|
|
sta = ssv6xxx_find_sta_by_rx_skb(sc, rx_skb);
|
|
if (sta){
|
|
struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)(rx_skb->data + sc->sh->rx_desc_len);
|
|
if (ieee80211_is_data(hdr->frame_control)){
|
|
sc->rx_data_exist = true;
|
|
}
|
|
}
|
|
if (HAL_UPDATE_RXSTATUS(sc, rx_skb, rxs) == -1){
|
|
printk(" KRACK detected!!\n");
|
|
goto drop_rx;
|
|
}
|
|
|
|
#if LINUX_VERSION_CODE >= 0x030400
|
|
if (aggr)
|
|
rxs->flag |= RX_FLAG_NO_SIGNAL_VAL;
|
|
#endif
|
|
|
|
/* update Mng queue status to let host know*/
|
|
sc->hw_mng_used = mng_used;
|
|
|
|
// Drop frame beacon for disassoc with station info
|
|
if (ieee80211_is_mgmt(fc) && (sta != NULL)) {
|
|
sta_priv = (struct ssv_sta_priv_data *)sta->drv_priv;
|
|
#ifndef IRQ_PROC_RX_DATA
|
|
down_read(&sc->sta_info_sem);/* START protect sta_info */
|
|
#else
|
|
while(!down_read_trylock(&sc->sta_info_sem));/* START protect sta_info */
|
|
#endif
|
|
if ((sta_priv->sta_idx == -1) ||
|
|
((sc->sta_info[sta_priv->sta_idx].s_flags & STA_FLAG_VALID) == 0)) {
|
|
up_read(&sc->sta_info_sem);/* END protect sta_info */
|
|
goto drop_rx;
|
|
}
|
|
vif = sc->sta_info[sta_priv->sta_idx].vif;
|
|
if (vif == NULL) {
|
|
up_read(&sc->sta_info_sem);/* END protect sta_info */
|
|
goto drop_rx;
|
|
}
|
|
if ((vif->type == NL80211_IFTYPE_STATION) && (vif->bss_conf.assoc == false)) {
|
|
up_read(&sc->sta_info_sem);/* END protect sta_info */
|
|
goto drop_rx;
|
|
|
|
}
|
|
up_read(&sc->sta_info_sem);/* END protect sta_info */
|
|
}
|
|
|
|
/*
|
|
* Set protect bit of IEEE 802.11 data frame header to 0 if
|
|
* hardware security offload engine is enabled for security mode.
|
|
*/
|
|
// Check decryption status of the protected data frame
|
|
if ( (ieee80211_is_data(fc) || ieee80211_is_data_qos(fc))
|
|
&& ieee80211_has_protected(fc))
|
|
{
|
|
// for NL80211_IFTYPE_STATION, wsid would be 0, 1, e.
|
|
// for NL80211_IFTYPE_AP, wisd would be 0, 1, f for no WSID WATCH LIST
|
|
// for NL80211_IFTYPE_AP, wisd would be 0~7 for WSID WATCH list_head
|
|
sta = ssv6xxx_find_sta_by_rx_skb(sc, rx_skb);
|
|
|
|
// Encrypted RX packet must be for connected STA.
|
|
if (sta == NULL)
|
|
goto drop_rx;
|
|
|
|
sta_priv = (struct ssv_sta_priv_data *)sta->drv_priv;
|
|
//tx_desc->tx_report = 1;
|
|
|
|
#ifndef IRQ_PROC_RX_DATA
|
|
down_read(&sc->sta_info_sem);/* START protect sta_info */
|
|
#else
|
|
while(!down_read_trylock(&sc->sta_info_sem));/* START protect sta_info */
|
|
#endif
|
|
if ((sta_priv->sta_idx == -1) ||
|
|
((sc->sta_info[sta_priv->sta_idx].s_flags & STA_FLAG_VALID) == 0)) {
|
|
up_read(&sc->sta_info_sem);/* END protect sta_info */
|
|
goto drop_rx;
|
|
}
|
|
vif = sc->sta_info[sta_priv->sta_idx].vif;
|
|
if (vif == NULL) {
|
|
up_read(&sc->sta_info_sem);/* END protect sta_info */
|
|
goto drop_rx;
|
|
}
|
|
if ((vif->type == NL80211_IFTYPE_STATION) && (vif->bss_conf.assoc == false)) {
|
|
up_read(&sc->sta_info_sem);/* END protect sta_info */
|
|
goto drop_rx;
|
|
}
|
|
|
|
if (is_broadcast_ether_addr(hdr->addr1))
|
|
{
|
|
vif_priv = (struct ssv_vif_priv_data *)vif->drv_priv;
|
|
rx_hw_dec = vif_priv->has_hw_decrypt;
|
|
do_sw_dec = vif_priv->need_sw_decrypt;
|
|
}
|
|
else
|
|
{
|
|
rx_hw_dec = sta_priv->has_hw_decrypt;
|
|
do_sw_dec = sta_priv->need_sw_decrypt;
|
|
}
|
|
up_read(&sc->sta_info_sem);/* END protect sta_info */
|
|
#if 0
|
|
if (rx_count++ < 20)
|
|
{
|
|
printk(KERN_ERR "HW DEC (%d - %d) %d %02X:%02X:%02X:%02X:%02X:%02X\n",
|
|
rx_hw_dec, do_sw_dec, rxdesc->wsid,
|
|
hdr->addr1[0], hdr->addr1[1], hdr->addr1[2],
|
|
hdr->addr1[3], hdr->addr1[4], hdr->addr1[5]);
|
|
_ssv6xxx_hexdump("M ", (const u8 *)rx_skb->data, (rx_skb->len > 128) ? 128 : rx_skb->len);
|
|
}
|
|
#endif // 0
|
|
#if 0
|
|
dev_err(sc->dev, "R %02X:%02X:%02X:%02X:%02X:%02X %d %d\n",
|
|
hdr->addr2[0], hdr->addr2[1], hdr->addr2[2],
|
|
hdr->addr2[3], hdr->addr2[4], hdr->addr2[5],
|
|
rx_hw_dec, do_sw_dec);
|
|
_ssv6xxx_hexdump("R ", (const u8 *)rx_skb->data,
|
|
(rx_skb->len > 128) ? 128 : rx_skb->len);
|
|
#endif // 0
|
|
}
|
|
|
|
if (sc->dbg_rx_frame)
|
|
{
|
|
_ssv6xxx_hexdump("================================================================\n"
|
|
"RX frame", (const u8 *)rx_skb->data, rx_skb->len);
|
|
}
|
|
|
|
tkip_mmic_err = SSV_GET_TKIP_MMIC_ERR(sc->sh, rx_skb);
|
|
/* remove ssv6xxx headers before passing to mac80211 */
|
|
skb_pull(rx_skb, sc->sh->rx_desc_len);
|
|
skb_trim(rx_skb, rx_skb->len - sc->sh->rx_pinfo_pad);
|
|
|
|
if (rx_hw_dec || do_sw_dec)
|
|
{
|
|
|
|
hdr = (struct ieee80211_hdr *)rx_skb->data;
|
|
rxs = IEEE80211_SKB_RXCB(rx_skb);
|
|
hdr->frame_control = hdr->frame_control & ~(cpu_to_le16(IEEE80211_FCTL_PROTECTED));
|
|
rxs->flag |= (RX_FLAG_DECRYPTED|RX_FLAG_IV_STRIPPED);
|
|
|
|
if (tkip_mmic_err) {
|
|
rxs->flag |= RX_FLAG_MMIC_ERROR;
|
|
printk("TKIP MMIC error, set RX_FLAG_MMIC_ERROR flag\n");
|
|
}
|
|
}
|
|
|
|
// Debug code to check received broadcast frame.
|
|
#if 0
|
|
if ( is_broadcast_ether_addr(hdr->addr1)
|
|
&& (ieee80211_is_data_qos(fc) || ieee80211_is_data(fc)))
|
|
#endif
|
|
#if 0
|
|
if (ieee80211_is_probe_req(fc))
|
|
{
|
|
#if 0
|
|
printk(KERN_ERR "RX M: 1 %02X:%02X:%02X:%02X:%02X:%02X (%d - %d - %d)\n",
|
|
hdr->addr1[0], hdr->addr1[1], hdr->addr1[2],
|
|
hdr->addr1[3], hdr->addr1[4], hdr->addr1[5],
|
|
(le16_to_cpu(hdr->seq_ctrl) >> 4),
|
|
rxdesc->wsid, ieee80211_has_protected(fc));
|
|
#endif
|
|
//printk(KERN_ERR "RX M: 2 %02X:%02X:%02X:%02X:%02X:%02X\n",
|
|
printk(KERN_ERR "Probe Req: 2 %02X:%02X:%02X:%02X:%02X:%02X\n",
|
|
hdr->addr2[0], hdr->addr2[1], hdr->addr2[2],
|
|
hdr->addr2[3], hdr->addr2[4], hdr->addr2[5]);
|
|
#if 0
|
|
printk(KERN_ERR "RX M: 3 %02X:%02X:%02X:%02X:%02X:%02X\n",
|
|
hdr->addr3[0], hdr->addr3[1], hdr->addr3[2],
|
|
hdr->addr3[3], hdr->addr3[4], hdr->addr3[5]);
|
|
#endif
|
|
_ssv6xxx_hexdump("RX frame", (const u8 *)rx_skb->data,
|
|
(rx_skb->len > 128) ? 128 : rx_skb->len);
|
|
}
|
|
#endif // DEBUG CODE
|
|
|
|
monitor:
|
|
if ((sc->sc_flags & SC_OP_DEV_READY) && (sc->sc_flags & SC_OP_IF_UP))
|
|
{
|
|
if (sc->sh->cfg.rx_ip_csum_off) {
|
|
/* Turn off IP checksum to improve Rx throughput */
|
|
rx_skb->ip_summed = CHECKSUM_UNNECESSARY;
|
|
}
|
|
// rx frame mib
|
|
_proc_data_rx_mib(sc, rx_skb);
|
|
#if defined(USE_THREAD_RX) && !defined(IRQ_PROC_RX_DATA)
|
|
|
|
local_bh_disable();
|
|
ieee80211_rx(sc->hw, rx_skb);
|
|
local_bh_enable();
|
|
#else
|
|
ieee80211_rx_irqsafe(sc->hw, rx_skb);
|
|
#endif // USE_THREAD_RX
|
|
}
|
|
else
|
|
goto drop_rx;
|
|
|
|
return;
|
|
|
|
drop_rx:
|
|
#if 0
|
|
dev_err(sc->dev, "D %02X:%02X:%02X:%02X:%02X:%02X\n",
|
|
hdr->addr2[0], hdr->addr2[1], hdr->addr2[2],
|
|
hdr->addr2[3], hdr->addr2[4], hdr->addr2[5]);
|
|
#endif
|
|
dev_kfree_skb_any(rx_skb);
|
|
} // end of - ssv6xxx_proc_data_rx_skb -
|
|
|
|
|
|
#ifdef IRQ_PROC_RX_DATA
|
|
static int ssv6xxx_proc_rx_skb (struct ssv_softc *sc, struct sk_buff *rx_skb)
|
|
{
|
|
u32 packet_len = 0, c_type = 0, tx_pkt_run_no = 0;
|
|
|
|
/* extract packet_length */
|
|
SSV_GET_RX_DESC_INFO_HDR(sc->sh, rx_skb->data, &packet_len, &c_type, &tx_pkt_run_no);
|
|
|
|
if ((M0_RXEVENT != c_type) && (M2_RXEVENT != c_type)) {
|
|
return EPERM;
|
|
}
|
|
|
|
/* If hwif is 4byte alignment, we must remove the padding */
|
|
if(rx_skb->len != packet_len) {
|
|
if (rx_skb->len < packet_len) {
|
|
dev_warn(sc->dev, "%s(): incorrect frame length %d[%d][%d]\n", __FUNCTION__, rx_skb->len, packet_len, c_type);
|
|
dev_kfree_skb_any(rx_skb);
|
|
return 0;
|
|
}
|
|
skb_trim(rx_skb, packet_len);
|
|
}
|
|
|
|
ssv6xxx_proc_data_rx_skb(sc, rx_skb);
|
|
return 0;
|
|
} // end of - _proc_rx_skb -
|
|
#endif // IRQ_PROC_RX_DATA
|
|
|
|
#define BEACON_MISS_RSSI_THRESHOLD (-80)
|
|
void ssv6xxx_beacon_miss_work(struct work_struct *work)
|
|
{
|
|
struct ssv_softc *sc = container_of(work, struct ssv_softc, beacon_miss_work);
|
|
struct ssv_vif_priv_data *vif_priv = NULL;
|
|
struct sk_buff *skb = NULL;
|
|
struct ieee80211_hdr_3addr *nullfunc = NULL;
|
|
int tx_desc_size = 0, vif_idx = 0, signal = 0;
|
|
int ret = 0;
|
|
int i = 0;
|
|
struct ieee80211_sta *sta = NULL;
|
|
struct ssv_sta_priv_data *sta_priv_iter;
|
|
u8 macaddr[ETH_ALEN];
|
|
struct SKB_info_st *skb_info;
|
|
|
|
memcpy(macaddr, sc->sh->cfg.maddr[0], ETH_ALEN);
|
|
skb = ssv_skb_alloc(sc, sizeof(struct ieee80211_hdr_3addr));
|
|
if (!skb)
|
|
return;
|
|
|
|
down_read(&sc->sta_info_sem);/* START protect sta_info */
|
|
|
|
//It must be station mode, so find the first station info
|
|
for (i = 0; i < SSV6200_MAX_VIF; i++) {
|
|
if (sc->vif_info[i].vif == NULL)
|
|
continue;
|
|
|
|
if ((sc->vif_info[i].vif->type == NL80211_IFTYPE_STATION) &&
|
|
(sc->vif_info[i].vif->p2p == false) &&
|
|
(sc->vif_info[i].vif->bss_conf.assoc == true))
|
|
break;
|
|
}
|
|
if (i == SSV6200_MAX_VIF)
|
|
goto out;
|
|
|
|
vif_priv = (struct ssv_vif_priv_data *)sc->vif_info[i].vif->drv_priv;
|
|
|
|
if (list_empty(&vif_priv->sta_list)) {
|
|
printk("%s(): sta_list is empty.\n", __func__);
|
|
goto out;
|
|
}
|
|
|
|
list_for_each_entry(sta_priv_iter, &vif_priv->sta_list, list)
|
|
{
|
|
if (sta_priv_iter->sta_idx == -1)
|
|
continue;
|
|
if (sc->sta_info[sta_priv_iter->sta_idx].s_flags & STA_FLAG_VALID)
|
|
break;
|
|
}
|
|
if(sta_priv_iter->sta_idx == -1)
|
|
goto out;
|
|
|
|
/* If rssi < threshold, it should not to send fake beacon to mac80211 */
|
|
signal = -(sta_priv_iter->beacon_rssi >> RSSI_DECIMAL_POINT_SHIFT);
|
|
if (signal < BEACON_MISS_RSSI_THRESHOLD)
|
|
goto out;
|
|
|
|
sta = sc->sta_info[sta_priv_iter->sta_idx].sta;
|
|
if (!sta)
|
|
goto out;
|
|
|
|
// should clean skb info to let tx_frame count correct.
|
|
skb_info = (struct SKB_info_st *)skb->head;
|
|
memset(skb_info, 0, sizeof (struct SKB_info_st));
|
|
|
|
vif_idx = sc->vif_info[i].vif_priv->vif_idx;
|
|
tx_desc_size = SSV_GET_TX_DESC_SIZE(sc->sh);
|
|
skb_put(skb, tx_desc_size+sizeof(struct ieee80211_hdr_3addr));
|
|
|
|
//update null data header
|
|
nullfunc = (struct ieee80211_hdr_3addr *)(skb->data + tx_desc_size);
|
|
memset(nullfunc, 0, sizeof(struct ieee80211_hdr_3addr));
|
|
// update sequence
|
|
sc->tx.seq_no += 0x10;
|
|
nullfunc->seq_ctrl &= cpu_to_le16(IEEE80211_SCTL_FRAG);
|
|
nullfunc->seq_ctrl |= cpu_to_le16(sc->tx.seq_no);
|
|
// update frame control & mac addr
|
|
nullfunc->frame_control = cpu_to_le16(IEEE80211_FTYPE_DATA |
|
|
IEEE80211_STYPE_NULLFUNC |
|
|
IEEE80211_FCTL_TODS);
|
|
memcpy(nullfunc->addr1, sc->bssid[vif_idx], ETH_ALEN);
|
|
memcpy(nullfunc->addr2, macaddr, ETH_ALEN);
|
|
memcpy(nullfunc->addr3, sc->bssid[vif_idx], ETH_ALEN);
|
|
|
|
//update tx description
|
|
ret = SSV_UPDATE_NULL_FUNC_TXINFO(sc, sta, skb);
|
|
if (ret < 0)
|
|
goto out;
|
|
|
|
ret = HCI_SEND(sc->sh, skb, SSV_SW_TXQ_ID_MNG, false);
|
|
if (ret < 0)
|
|
goto out;
|
|
|
|
up_read(&sc->sta_info_sem);/* END protect sta_info */
|
|
return;
|
|
|
|
out:
|
|
up_read(&sc->sta_info_sem);/* END protect sta_info */
|
|
dev_kfree_skb_any(skb);
|
|
}
|
|
|
|
static void ssv6xxx_ack_ctl_notify_process(struct ssv_softc *sc, u8 ack, u8 seq_no)
|
|
{
|
|
struct sk_buff *skb = NULL;
|
|
struct ieee80211_tx_info *tx_info;
|
|
struct ieee80211_hdr *hdr = NULL;
|
|
int qlen = 0;
|
|
int i = 0;
|
|
int wsid = 0;
|
|
u8 sw_ack_seq = 0;
|
|
|
|
qlen = (int)skb_queue_len(&sc->tx_ack_ctl_q);
|
|
|
|
for (i = 0; i < qlen; i++) {
|
|
skb = skb_dequeue(&sc->tx_ack_ctl_q);
|
|
if (skb) {
|
|
struct SKB_info_st *mpdu_skb_info_p = (SKB_info *)(skb->head);
|
|
if (mpdu_skb_info_p->directly_ack) {
|
|
printk("sw ack use directack\n");
|
|
ssv_skb_free(sc, skb);
|
|
ssv6200_tx_flow_control(sc, false, false);
|
|
continue;
|
|
}
|
|
sw_ack_seq = SSV_GET_SW_ACK_SEQ(sc->sh, skb);
|
|
if (seq_no == sw_ack_seq) {
|
|
|
|
tx_info = IEEE80211_SKB_CB(skb);
|
|
wsid = HAL_GET_TX_DESC_WSID(sc->sh, skb);
|
|
skb_pull(skb, SSV_GET_TX_DESC_SIZE(sc->sh));
|
|
|
|
if (0 == ack) {
|
|
//ssv_skb_free(sc, skb);
|
|
// show nack eapol frame seqno for debug
|
|
hdr = (struct ieee80211_hdr *)skb->data;
|
|
printk("Ack ctl frame[%d] cannot receive ack\n", (le16_to_cpu(hdr->seq_ctrl) >> 4));
|
|
#if 0
|
|
ssv_skb_free(sc, skb);
|
|
#else
|
|
ieee80211_tx_info_clear_status(tx_info);
|
|
tx_info->flags &= ~IEEE80211_TX_STAT_ACK;
|
|
tx_info->flags |=IEEE80211_TX_CTL_INJECTED;
|
|
tx_info->status.ack_signal = 100; /* ???? */
|
|
tx_info->status.rates[1].idx = -1;
|
|
ieee80211_tx_status(sc->hw, skb);
|
|
#endif
|
|
} else {
|
|
hdr = (struct ieee80211_hdr *)skb->data;
|
|
printk("Ack ctl frame[%d] receive ack\n", (le16_to_cpu(hdr->seq_ctrl) >> 4));
|
|
ssvxxx_complete_tx_skb(sc, tx_info, skb, wsid);
|
|
}
|
|
ssv6200_tx_flow_control(sc, false, false);
|
|
break;
|
|
} else {
|
|
skb_queue_tail(&sc->tx_ack_ctl_q, skb);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (i == qlen)
|
|
printk("Cannot find eapol frame\n");
|
|
}
|
|
|
|
void process_host_cmd_done_event(struct ssv_softc *sc, u8 *data, int len)
|
|
{
|
|
struct cfg_host_event *h_evt = (struct cfg_host_event *)data;
|
|
|
|
switch (h_evt->h_event) {
|
|
case SOC_EVT_RFPHY_OPS:
|
|
HCI_CMD_DONE(sc->sh, data, len);
|
|
break;
|
|
case SOC_EVT_SECURITY:
|
|
HCI_CMD_DONE(sc->sh, data, len);
|
|
break;
|
|
case SOC_EVT_AMPDU_OPS:
|
|
HCI_CMD_DONE(sc->sh, data, len);
|
|
break;
|
|
case SOC_EVT_RC_OPS:
|
|
HCI_CMD_DONE(sc->sh, data, len);
|
|
break;
|
|
case SOC_EVT_TX_OPS:
|
|
HCI_CMD_DONE(sc->sh, data, len);
|
|
break;
|
|
case SOC_EVT_VIF_OPS:
|
|
HCI_CMD_DONE(sc->sh, data, len);
|
|
break;
|
|
case SOC_EVT_ADAPTIVE:
|
|
HCI_CMD_DONE(sc->sh, data, len);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
static bool _process_host_event(struct ssv_softc *sc, struct sk_buff *skb)
|
|
{
|
|
#define MAX_RFPHY_REG_VERSION_LEN 8
|
|
u32 c_type;
|
|
struct cfg_host_event *h_evt;
|
|
int i = 0;
|
|
u8 *ptr = NULL;
|
|
u8 version[MAX_RFPHY_REG_VERSION_LEN];
|
|
u32 addr = 0, value = 0;
|
|
|
|
c_type = HAL_GET_RX_DESC_CTYPE(sc->sh, skb);
|
|
|
|
if (c_type != HOST_EVENT)
|
|
return false;
|
|
|
|
h_evt = (struct cfg_host_event *)skb->data;
|
|
switch (h_evt->h_event) {
|
|
case SOC_EVT_SDIO_TEST_COMMAND:
|
|
//printk("h_evt->evt_seq_no=%d\n", h_evt->evt_seq_no);
|
|
if (h_evt->evt_seq_no == 0) {
|
|
printk("SOC_EVT_SDIO_TEST_COMMAND\n");
|
|
sc->sdio_rx_evt_size = h_evt->len;
|
|
sc->sdio_throughput_timestamp = jiffies;
|
|
} else {
|
|
sc->sdio_rx_evt_size += h_evt->len;
|
|
if (time_after(jiffies,
|
|
( sc->sdio_throughput_timestamp
|
|
+ msecs_to_jiffies(1000)))) {
|
|
printk("data[%ld] SDIO RX throughput %ld Kbps\n",
|
|
sc->sdio_rx_evt_size,
|
|
( (sc->sdio_rx_evt_size << 3)
|
|
/ jiffies_to_msecs( jiffies
|
|
- sc->sdio_throughput_timestamp)));
|
|
sc->sdio_throughput_timestamp = jiffies;
|
|
sc->sdio_rx_evt_size = 0;
|
|
}
|
|
}
|
|
dev_kfree_skb_any(skb);
|
|
break;
|
|
case SOC_EVT_RESET_HOST:
|
|
dev_kfree_skb_any(skb);
|
|
if ((sc->ap_vif == NULL) && (sc->sh->cfg.online_reset & ONLINE_RESET_ENABLE)) {
|
|
queue_work(sc->config_wq, &sc->hw_restart_work);
|
|
} else {
|
|
dev_warn(sc->dev, "Reset event ignored.\n");
|
|
}
|
|
break;
|
|
case SOC_EVT_SDIO_TXTPUT_RESULT:
|
|
printk("data SDIO TX throughput %d Kbps\n", h_evt->evt_seq_no);
|
|
dev_kfree_skb_any(skb);
|
|
break;
|
|
case SOC_EVT_TXLOOPBK_RESULT:
|
|
if (h_evt->evt_seq_no == SSV6XXX_STATE_OK) {
|
|
printk("FW TX LOOPBACK OK\n");
|
|
sc->iq_cali_done = IQ_CALI_OK;
|
|
} else {
|
|
printk(KERN_ERR "FW TX LOOPBACK FAILED\n");
|
|
sc->iq_cali_done = IQ_CALI_FAILED;
|
|
}
|
|
dev_kfree_skb_any(skb);
|
|
wake_up_interruptible(&sc->fw_wait_q);
|
|
break;
|
|
case SOC_EVT_BEACON_LOSS:
|
|
dbgprint(&sc->cmd_data, sc->log_ctrl, LOG_BEACON, "beacon dectection trigger\n");
|
|
dev_kfree_skb_any(skb);
|
|
queue_work(sc->config_wq, &sc->beacon_miss_work);
|
|
|
|
if (sc->isAssoc)
|
|
SSV_BEACON_LOSS_ENABLE(sc->sh);
|
|
|
|
break;
|
|
case SOC_EVT_TX_STUCK_RESP:
|
|
printk("receive event tx_stuck!!\n");
|
|
dev_kfree_skb_any(skb);
|
|
break;
|
|
case SOC_EVT_SW_BEACON_RESP:
|
|
printk("soft-beacon start!!\n");
|
|
dev_kfree_skb_any(skb);
|
|
break;
|
|
case SOC_EVT_INCORRECT_CHAN_BW:
|
|
printk("The chip cannot support HT40.\n");
|
|
dev_kfree_skb_any(skb);
|
|
break;
|
|
case SOC_EVT_PS:
|
|
SSV_PS_EVT_HANDLER(sc, skb);
|
|
dev_kfree_skb_any(skb);
|
|
break;
|
|
case SOC_EVT_DUMP_PHY_REG:
|
|
case SOC_EVT_DUMP_RF_REG:
|
|
ptr = (u8 *)h_evt->dat;
|
|
|
|
memset(version, 0x0, sizeof(version));
|
|
snprintf(version, sizeof(version) - 1, "%s", ptr);
|
|
printk("rf/phy version %s\n", version);
|
|
|
|
ptr += MAX_RFPHY_REG_VERSION_LEN;
|
|
for (i = 0; i < (skb->len - sizeof(struct cfg_host_event) - MAX_RFPHY_REG_VERSION_LEN) / 8; i++) {
|
|
memcpy(&addr, ptr, 4);
|
|
ptr += 4;
|
|
memcpy(&value, ptr, 4);
|
|
ptr += 4;
|
|
printk("0x%08x = 0x%08x\n", addr, value);
|
|
}
|
|
dev_kfree_skb_any(skb);
|
|
break;
|
|
case SOC_EVT_FW_NOTIFY:
|
|
ptr = (u8 *)h_evt->dat;
|
|
if(true == ((struct ssv_fw_notify_param *)ptr)->rate_update)
|
|
{
|
|
memcpy((void *)&(sc->hw_cur_rate[0]), (const void *)&(((struct ssv_fw_notify_param *)ptr)->cur_rate[0]), MAX_STA_NUM);
|
|
}
|
|
if(true == ((struct ssv_fw_notify_param *)ptr)->status_cnt_update)
|
|
{
|
|
sc->fw_cur_status_cnt = ((struct ssv_fw_notify_param *)ptr)->cur_status_cnt;
|
|
}
|
|
dev_kfree_skb_any(skb);
|
|
break;
|
|
case SOC_EVT_SNIFFER_NOTIFY:
|
|
dev_kfree_skb_any(skb);
|
|
break;
|
|
case SOC_EVT_SW_ACK_NOTIFY:
|
|
ptr = (u8 *)h_evt->dat;
|
|
ssv6xxx_ack_ctl_notify_process(sc, ((struct ssv6xxx_sw_ack_evt *)ptr)->success,
|
|
((struct ssv6xxx_sw_ack_evt *)ptr)->seq_no);
|
|
dev_kfree_skb_any(skb);
|
|
break;
|
|
default:
|
|
dev_kfree_skb_any(skb);
|
|
break;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static int ssv6xxx_rx_skb_reserved_room(struct ssv_softc *sc, u8 *pbdata)
|
|
{
|
|
u32 packet_len = 0, c_type = 0, tx_pkt_run_no = 0, hdrlen = 0;
|
|
struct ieee80211_hdr *rhdr = NULL;
|
|
int reserved_room = 0;
|
|
|
|
if (sc->sh->cfg.rx_ip_align) {
|
|
/* Align 802.3 payload to 4n+2 to improve Rx throughput.
|
|
* mac80211 header translate from 802.11 to 802.3(header len is 14)
|
|
* 802.3 packet address will be 4n+2+14 is 4-align to prevent mac80211 from memmove.
|
|
*/
|
|
SSV_GET_RX_DESC_INFO_HDR(sc->sh, pbdata, &packet_len, &c_type, &tx_pkt_run_no);
|
|
if (M0_RXEVENT == c_type) {
|
|
|
|
rhdr = (struct ieee80211_hdr *)(pbdata + RXPB_OFFSET);
|
|
hdrlen = ieee80211_hdrlen(rhdr->frame_control);
|
|
if (0 == (hdrlen & 0x03)) {
|
|
reserved_room = 2;
|
|
}
|
|
}
|
|
}
|
|
|
|
return reserved_room;
|
|
}
|
|
|
|
void ssv6xxx_process_rx_q(struct ssv_softc *sc, struct sk_buff_head *rx_q)
|
|
{
|
|
struct sk_buff *skb, *sskb;
|
|
u32 packet_len, c_type, tx_pkt_run_no;
|
|
int rx_mode = sc->sh->rx_mode;
|
|
int data_offset, data_length, reserved_room;
|
|
unsigned char *pdata, *psdata;
|
|
struct hci_rx_aggr_info *rx_aggr_info;
|
|
#ifdef CONFIG_PREEMPT_NONE
|
|
u32 max_rx_num = sc->sh->cfg.rx_non_preempt_num;
|
|
u32 cur_rx_num = 0;
|
|
#endif //CONFIG_PREEMPT_NONE
|
|
|
|
|
|
while (1) {
|
|
#ifdef CONFIG_PREEMPT_NONE
|
|
if ((cur_rx_num++) > max_rx_num)
|
|
{
|
|
cur_rx_num = 0;
|
|
schedule();
|
|
}
|
|
#endif //CONFIG_PREEMPT_NONE
|
|
|
|
skb = skb_dequeue(rx_q);
|
|
if (!skb) {
|
|
break;
|
|
}
|
|
|
|
sc->rx.rx_count++;
|
|
|
|
data_length = skb->len;
|
|
pdata = skb->data;
|
|
for (data_offset = 0; data_offset < data_length; ) {
|
|
if (rx_mode == RX_NORMAL_MODE) {
|
|
sskb = skb;
|
|
data_offset = skb->len;
|
|
} else {
|
|
|
|
if ((data_offset + sizeof(struct hci_rx_aggr_info)) > data_length) {
|
|
//printk("wrong data length, data_offset %d, data_length %d\n",
|
|
// data_offset, data_length);
|
|
break;
|
|
}
|
|
|
|
rx_aggr_info = (struct hci_rx_aggr_info *)pdata;
|
|
if ((rx_aggr_info->jmp_mpdu_len == 0) ||
|
|
((rx_aggr_info->jmp_mpdu_len - sizeof(struct hci_rx_aggr_info)) > MAX_FRAME_SIZE) ||
|
|
((data_offset + rx_aggr_info->jmp_mpdu_len) > data_length)) {
|
|
|
|
break;
|
|
}
|
|
|
|
sskb = ssv_skb_alloc(sc, rx_aggr_info->jmp_mpdu_len);
|
|
if (!sskb) {
|
|
dev_warn(sc->dev, "%s(): cannot alloc skb buffer\n", __FUNCTION__);
|
|
data_offset += rx_aggr_info->jmp_mpdu_len;
|
|
pdata = (u8 *)skb->data + data_offset;
|
|
continue;
|
|
}
|
|
|
|
reserved_room = ssv6xxx_rx_skb_reserved_room(sc, (u8 *)pdata+sizeof(struct hci_rx_aggr_info));
|
|
if (0 != reserved_room)
|
|
skb_reserve(sskb, reserved_room);
|
|
|
|
psdata = skb_put(sskb, (rx_aggr_info->jmp_mpdu_len - sizeof(struct hci_rx_aggr_info)));
|
|
memcpy(psdata, pdata+sizeof(struct hci_rx_aggr_info),
|
|
(rx_aggr_info->jmp_mpdu_len - sizeof(struct hci_rx_aggr_info)));
|
|
|
|
/* relocate next packet header */
|
|
data_offset += rx_aggr_info->jmp_mpdu_len;
|
|
pdata = (u8 *)skb->data + data_offset;
|
|
}
|
|
|
|
/* extract packet_length */
|
|
SSV_GET_RX_DESC_INFO_HDR(sc->sh, sskb->data, &packet_len, &c_type, &tx_pkt_run_no);
|
|
|
|
/* If hwif is 4byte alignment, we must remove the padding */
|
|
if(sskb->len != packet_len) {
|
|
if (sskb->len < packet_len) {
|
|
dev_warn(sc->dev, "Incorrect frame length %d[%d][%d]\n", sskb->len, packet_len, c_type);
|
|
dev_kfree_skb_any(sskb);
|
|
continue;
|
|
}
|
|
skb_trim(sskb, packet_len);
|
|
}
|
|
|
|
if (c_type == RATE_RPT) {
|
|
dev_kfree_skb_any(sskb);
|
|
continue;
|
|
}
|
|
|
|
if (_process_host_event(sc, sskb))
|
|
continue;
|
|
|
|
ssv6xxx_proc_data_rx_skb(sc, sskb);
|
|
}
|
|
|
|
if (rx_mode != RX_NORMAL_MODE)
|
|
ssv_skb_free((void *)sc, skb);
|
|
}// end of while (1)
|
|
|
|
} // end of - ssv6xxx_process_rx_q -
|
|
|
|
int ssv6200_is_rx_q_full(void *args)
|
|
{
|
|
struct ssv_softc *sc=args;
|
|
|
|
if (skb_queue_len(&sc->rx_skb_q) > sc->sh->cfg.rx_threshold)
|
|
return 1;
|
|
else
|
|
return 0;
|
|
|
|
}
|
|
|
|
int ssv6200_rx(struct sk_buff *rx_skb, void *args)
|
|
{
|
|
struct ssv_softc *sc=args;
|
|
int rx_mode = sc->sh->rx_mode;
|
|
int data_offset, data_length;
|
|
unsigned char *pdata;
|
|
u32 packet_len, c_type, tx_pkt_run_no;
|
|
struct hci_rx_aggr_info *rx_aggr_info;
|
|
|
|
data_length = rx_skb->len;
|
|
pdata = rx_skb->data;
|
|
for (data_offset = 0; data_offset < data_length; ) {
|
|
if (rx_mode == RX_NORMAL_MODE) {
|
|
|
|
SSV_GET_RX_DESC_INFO_HDR(sc->sh, rx_skb->data, &packet_len, &c_type, &tx_pkt_run_no);
|
|
if (HOST_EVENT == c_type) {
|
|
process_host_cmd_done_event(sc, rx_skb->data, rx_skb->len);
|
|
}
|
|
break;
|
|
|
|
} else {
|
|
|
|
if ((data_offset + sizeof(struct hci_rx_aggr_info)) > data_length) {
|
|
//printk("wrong data length, data_offset %d, data_length %d\n",
|
|
// data_offset, data_length);
|
|
break;
|
|
}
|
|
|
|
rx_aggr_info = (struct hci_rx_aggr_info *)pdata;
|
|
if ((rx_aggr_info->jmp_mpdu_len == 0) ||
|
|
((rx_aggr_info->jmp_mpdu_len - sizeof(struct hci_rx_aggr_info)) > MAX_FRAME_SIZE) ||
|
|
((data_offset + rx_aggr_info->jmp_mpdu_len) > data_length)) {
|
|
break;
|
|
}
|
|
|
|
SSV_GET_RX_DESC_INFO_HDR(sc->sh, (u8 *)pdata+sizeof(struct hci_rx_aggr_info),
|
|
&packet_len, &c_type, &tx_pkt_run_no);
|
|
|
|
if (HOST_EVENT == c_type) {
|
|
process_host_cmd_done_event(sc, pdata+sizeof(struct hci_rx_aggr_info),
|
|
(rx_aggr_info->jmp_mpdu_len - sizeof(struct hci_rx_aggr_info)));
|
|
}
|
|
|
|
/* relocate next packet header */
|
|
data_offset += rx_aggr_info->jmp_mpdu_len;
|
|
pdata = (u8 *)rx_skb->data + data_offset;
|
|
}
|
|
}
|
|
|
|
#ifdef IRQ_PROC_RX_DATA
|
|
if (0 == ssv6xxx_proc_rx_skb(sc, rx_skb))
|
|
return 0;
|
|
#endif // IRQ_PROC_TX_DATA
|
|
|
|
skb_queue_tail(&sc->rx_skb_q, rx_skb);
|
|
wake_up_interruptible(&sc->rx_wait_q);
|
|
|
|
return 0;
|
|
}
|
|
|
|
void ssv6xxx_check_fw_status_process(struct work_struct *work)
|
|
{
|
|
struct ssv_softc *sc = container_of(work, struct ssv_softc, check_fw_status_work);
|
|
|
|
if(sc->fw_cur_status_cnt != sc->fw_pre_status_cnt)
|
|
{
|
|
sc->fw_pre_status_cnt = sc->fw_cur_status_cnt;
|
|
sc->fw_cur_status_idle_time = 0;
|
|
}
|
|
else
|
|
{
|
|
if(sc->sh->cfg.fw_status_idle_cnt <= ++sc->fw_cur_status_idle_time)
|
|
{
|
|
printk(KERN_ERR "[%s] Not detect FW status for %d times(over %d ms), the FW will be reloaded.\n", __FUNCTION__, sc->fw_cur_status_idle_time, sc->fw_cur_status_idle_time*HOUSE_KEEPING_TIMEOUT);
|
|
queue_work(sc->config_wq, &sc->hw_restart_work);
|
|
sc->fw_cur_status_idle_time = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
void ssv6xxx_mib_edca_process(struct work_struct *work)
|
|
{
|
|
struct ssv_softc *sc = container_of(work, struct ssv_softc, mib_edca_work);
|
|
|
|
if (!sc->sh)
|
|
return;
|
|
|
|
SSV_EDCA_STAT(sc->sh);
|
|
}
|
|
|
|
void ssv6xxx_tx_poll_process(struct work_struct *work)
|
|
{
|
|
struct ssv_softc *sc = container_of(work, struct ssv_softc, tx_poll_work);
|
|
|
|
if (!sc->sh)
|
|
return;
|
|
|
|
SSV_SEND_TX_POLL_CMD(sc->sh, SSV6XXX_TX_POLL_RESET);
|
|
}
|
|
|
|
void ssv6xxx_flowctl_process(struct work_struct *work)
|
|
{
|
|
struct ssv_softc *sc = container_of(work, struct ssv_softc, flowctl_work);
|
|
ieee80211_wake_queues(sc->hw);
|
|
}
|
|
|
|
#ifdef CONFIG_ENABLE_HOST_THERMAL
|
|
void ssv6xxx_thermal_monitor_process(struct work_struct *work)
|
|
{
|
|
struct ssv_softc *sc = container_of(work, struct ssv_softc, thermal_monitor_work);
|
|
if (!sc->sh)
|
|
return;
|
|
SSV_DO_TEMPERATURE_COMPENSATION(sc->sh);
|
|
}
|
|
#endif
|
|
|
|
struct ieee80211_sta *ssv6xxx_find_sta_by_rx_skb (struct ssv_softc *sc, struct sk_buff *skb)
|
|
{
|
|
struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)(skb->data + sc->sh->rx_desc_len);
|
|
u32 wsid;
|
|
|
|
wsid = HAL_GET_RX_DESC_WSID(sc->sh, skb);
|
|
if ((wsid >= 0) && (wsid < SSV_NUM_STA))
|
|
return sc->sta_info[wsid].sta;
|
|
else
|
|
return ssv6xxx_find_sta_by_addr(sc, hdr->addr2);
|
|
}
|
|
|
|
struct ieee80211_sta *ssv6xxx_find_sta_by_addr (struct ssv_softc *sc, u8 addr[6])
|
|
{
|
|
struct ieee80211_sta *sta;
|
|
int i;
|
|
|
|
for (i = 0; i < SSV6200_MAX_VIF; i++)
|
|
{
|
|
if (sc->vif_info[i].vif == NULL)
|
|
continue;
|
|
|
|
sta = ieee80211_find_sta(sc->vif_info[i].vif, addr);
|
|
if (sta != NULL)
|
|
return sta;
|
|
}
|
|
return NULL;
|
|
} // end of - ssv6xxx_find_sta -
|
|
|
|
|
|
void ssv6xxx_foreach_sta (struct ssv_softc *sc, void (*sta_func)(struct ssv_softc *, struct ssv_sta_info *, void *), void *param)
|
|
{
|
|
int i;
|
|
|
|
BUG_ON(sta_func == NULL);
|
|
|
|
#if 0
|
|
for (i = 0; i < SSV6200_MAX_VIF; i++)
|
|
{
|
|
struct ssv_vif_priv_data *vif_priv;
|
|
int j;
|
|
|
|
if (sc->vif_info[i].vif == NULL)
|
|
continue;
|
|
|
|
vif_priv = (struct ssv_vif_priv_data *)sc->vif[i]->drv_priv;
|
|
for (j = 0; j < SSV_NUM_STA; j++)
|
|
{
|
|
if ((vif_priv->sta_info[j].s_flags & STA_FLAG_VALID) == 0)
|
|
continue;
|
|
(*sta_func)(sc, &vif_priv->sta_info[j], param);
|
|
}
|
|
}
|
|
#else
|
|
down_read(&sc->sta_info_sem);/* START protect sta_info */
|
|
for (i = 0; i < SSV_NUM_STA; i++)
|
|
{
|
|
if ((sc->sta_info[i].s_flags & STA_FLAG_VALID) == 0)
|
|
continue;
|
|
|
|
(*sta_func)(sc, &sc->sta_info[i], param);
|
|
}
|
|
up_read(&sc->sta_info_sem);/* END protect sta_info */
|
|
#endif
|
|
}
|
|
|
|
|
|
void ssv6xxx_foreach_vif_sta (struct ssv_softc *sc,
|
|
struct ssv_vif_info *vif_info,
|
|
void (*sta_func)(struct ssv_softc *,
|
|
struct ssv_vif_info *,
|
|
struct ssv_sta_info *,
|
|
void *),
|
|
void *param)
|
|
{
|
|
struct ssv_vif_priv_data *vif_priv;
|
|
struct ssv_sta_priv_data *sta_priv_iter;
|
|
|
|
BUG_ON(vif_info == NULL);
|
|
BUG_ON((size_t)vif_info < 0x30000);
|
|
|
|
vif_priv = (struct ssv_vif_priv_data *)vif_info->vif->drv_priv;
|
|
BUG_ON((size_t)vif_info->vif < 0x30000);
|
|
BUG_ON((size_t)vif_priv < 0x30000);
|
|
|
|
//down_read(&sc->sta_info_sem);/* Don't protect sta_info, already protected by ssv6200_set_key(). */
|
|
list_for_each_entry(sta_priv_iter, &vif_priv->sta_list, list)
|
|
{
|
|
BUG_ON(sta_priv_iter == NULL);
|
|
BUG_ON((size_t)sta_priv_iter < 0x30000);
|
|
|
|
if ((sta_priv_iter->sta_idx == -1) ||
|
|
((sc->sta_info[sta_priv_iter->sta_idx].s_flags & STA_FLAG_VALID) == 0))
|
|
continue;
|
|
(*sta_func)(sc, vif_info, &sc->sta_info[sta_priv_iter->sta_idx], param);
|
|
}
|
|
//up_read(&sc->sta_info_sem);/* Don't protect sta_info, already protected by ssv6200_set_key(). */
|
|
}
|
|
|
|
#ifdef CONFIG_SSV6XXX_DEBUGFS
|
|
ssize_t ssv6xxx_tx_queue_status_dump (struct ssv_softc *sc, char *status_buf,
|
|
ssize_t length)
|
|
{
|
|
ssize_t buf_size = length;
|
|
ssize_t prt_size;
|
|
|
|
prt_size = snprintf(status_buf, buf_size, "\nSMAC driver queue status:.\n");
|
|
status_buf += prt_size;
|
|
buf_size -= prt_size;
|
|
|
|
return (length - buf_size);
|
|
} // end of - tx_queue_status_dump -
|
|
|
|
#endif // CONFIG_SSV6XXX_DEBUGFS
|