project:cfg:BoardConfig_IPC: Added fastboot BoardConfig file and firmware post-scripts, distinguishing between the BoardConfigs for Luckfox Pico Pro and Luckfox Pico Max. project:app: Added fastboot_client and rk_smart_door for quick boot applications; updated rkipc app to adapt to the latest media library. media:samples: Added more usage examples. media:rockit: Fixed bugs; removed support for retrieving data frames from VPSS. media:isp: Updated rkaiq library and related tools to support connection to RKISP_Tuner. sysdrv:Makefile: Added support for compiling drv_ko on Luckfox Pico Ultra W using Ubuntu; added support for custom root filesystem. sysdrv:tools:board: Updated Buildroot optional mirror sources, updated some software versions, and stored device tree files and configuration files that undergo multiple modifications for U-Boot and kernel separately. sysdrv:source:mcu: Used RISC-V MCU SDK with RT-Thread system, mainly for initializing camera AE during quick boot. sysdrv:source:uboot: Added support for fastboot; added high baud rate DDR bin for serial firmware upgrades. sysdrv:source:kernel: Upgraded to version 5.10.160; increased NPU frequency for RV1106G3; added support for fastboot. Signed-off-by: luckfox-eng29 <eng29@luckfox.com>
2543 lines
80 KiB
C
2543 lines
80 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/module.h>
|
|
#include <linux/version.h>
|
|
#include <linux/if_arp.h>
|
|
#include <linux/inetdevice.h>
|
|
#include <linux/of.h>
|
|
#include <net/cfg80211.h>
|
|
#include <linux/etherdevice.h>
|
|
|
|
#include "fmac.h"
|
|
#include "hci/drv_hci_ops.h"
|
|
#include "fmac_msg_tx.h"
|
|
#include "fmac_defs.h"
|
|
#include "fmac_tx.h"
|
|
#include "host_msg.h"
|
|
#include "netdev_ops.h"
|
|
#include "ssv_debug.h"
|
|
#include "ssv_cfg.h"
|
|
#include <linux/semaphore.h>
|
|
|
|
#ifdef FMAC_BRIDGE
|
|
#include "fmac_bridge.h"
|
|
#endif
|
|
|
|
extern struct ssv6xxx_cfg ssv_cfg;
|
|
|
|
extern u8 ssv_get_engmode(void);
|
|
|
|
static const int ssv_ac2hwq[1][NL80211_NUM_ACS] = {
|
|
{
|
|
[NL80211_TXQ_Q_VO] = SSV_HWQ_VO,
|
|
[NL80211_TXQ_Q_VI] = SSV_HWQ_VI,
|
|
[NL80211_TXQ_Q_BE] = SSV_HWQ_BE,
|
|
[NL80211_TXQ_Q_BK] = SSV_HWQ_BK
|
|
}
|
|
};
|
|
|
|
const int ssv_tid2hwq[IEEE80211_NUM_TIDS] = {
|
|
SSV_HWQ_BE,
|
|
SSV_HWQ_BK,
|
|
SSV_HWQ_BK,
|
|
SSV_HWQ_BE,
|
|
SSV_HWQ_VI,
|
|
SSV_HWQ_VI,
|
|
SSV_HWQ_VO,
|
|
SSV_HWQ_VO,
|
|
/* TID_8 is used for management frames */
|
|
SSV_HWQ_VO,
|
|
/* At the moment, all others TID are mapped to BE */
|
|
SSV_HWQ_BE,
|
|
SSV_HWQ_BE,
|
|
SSV_HWQ_BE,
|
|
SSV_HWQ_BE,
|
|
SSV_HWQ_BE,
|
|
SSV_HWQ_BE,
|
|
SSV_HWQ_BE,
|
|
};
|
|
|
|
static const int ssv_hwq2uapsd[NL80211_NUM_ACS] = {
|
|
[SSV_HWQ_VO] = IEEE80211_WMM_IE_STA_QOSINFO_AC_VO,
|
|
[SSV_HWQ_VI] = IEEE80211_WMM_IE_STA_QOSINFO_AC_VI,
|
|
[SSV_HWQ_BE] = IEEE80211_WMM_IE_STA_QOSINFO_AC_BE,
|
|
[SSV_HWQ_BK] = IEEE80211_WMM_IE_STA_QOSINFO_AC_BK,
|
|
};
|
|
|
|
#if defined(CONFIG_WPA_SUPPLICANT_CTL)
|
|
struct ssv_softc *g_ssv_sc = NULL;
|
|
struct ssv_vif *g_ssv_vif = NULL;
|
|
#endif
|
|
|
|
#ifdef CONFIG_MIFI_LOWPOWER
|
|
struct ssv_mgmt_tx_params fake_mgmt_tx_params = {0};
|
|
struct probe_resp_info probe_resp_info;
|
|
#endif
|
|
|
|
static bool ssv_port_control(u32 cipher_group)
|
|
{
|
|
if ((0 == cipher_group) ||
|
|
(WLAN_CIPHER_SUITE_WEP40 == cipher_group) ||
|
|
(WLAN_CIPHER_SUITE_WEP104 == cipher_group))
|
|
{
|
|
return false;
|
|
} else {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
u8 *_ssv_build_bcn(struct ssv_bcn *bcn, struct cfg80211_beacon_data *new)
|
|
{
|
|
u8 *buf, *pos;
|
|
|
|
if (new->head) {
|
|
u8 *head = kmalloc(new->head_len, GFP_KERNEL);
|
|
|
|
if (!head)
|
|
return NULL;
|
|
|
|
if (bcn->head)
|
|
kfree(bcn->head);
|
|
|
|
bcn->head = head;
|
|
bcn->head_len = new->head_len;
|
|
memcpy(bcn->head, new->head, new->head_len);
|
|
}
|
|
if (new->tail) {
|
|
u8 *tail = kmalloc(new->tail_len, GFP_KERNEL);
|
|
|
|
if (!tail)
|
|
return NULL;
|
|
|
|
if (bcn->tail)
|
|
kfree(bcn->tail);
|
|
|
|
bcn->tail = tail;
|
|
bcn->tail_len = new->tail_len;
|
|
memcpy(bcn->tail, new->tail, new->tail_len);
|
|
}
|
|
|
|
if (!bcn->head)
|
|
return NULL;
|
|
|
|
bcn->tim_len = 6;
|
|
bcn->len = bcn->head_len + bcn->tail_len + bcn->ies_len + bcn->tim_len;
|
|
|
|
buf = kmalloc(bcn->len, GFP_KERNEL);
|
|
if (!buf)
|
|
return NULL;
|
|
|
|
// Build the beacon buffer
|
|
pos = buf;
|
|
memcpy(pos, bcn->head, bcn->head_len);
|
|
pos += bcn->head_len;
|
|
*pos++ = WLAN_EID_TIM;
|
|
*pos++ = 4;
|
|
*pos++ = 0;
|
|
*pos++ = bcn->dtim;
|
|
*pos++ = 0;
|
|
*pos++ = 0;
|
|
if (bcn->tail) {
|
|
memcpy(pos, bcn->tail, bcn->tail_len);
|
|
pos += bcn->tail_len;
|
|
}
|
|
if (bcn->ies) {
|
|
memcpy(pos, bcn->ies, bcn->ies_len);
|
|
}
|
|
|
|
return buf;
|
|
}
|
|
|
|
static void ssv_del_bcn(struct ssv_bcn *bcn)
|
|
{
|
|
if (bcn->head)
|
|
{
|
|
kfree(bcn->head);
|
|
bcn->head = NULL;
|
|
}
|
|
bcn->head_len = 0;
|
|
|
|
if (bcn->tail)
|
|
{
|
|
kfree(bcn->tail);
|
|
bcn->tail = NULL;
|
|
}
|
|
bcn->tail_len = 0;
|
|
|
|
if (bcn->ies)
|
|
{
|
|
kfree(bcn->ies);
|
|
bcn->ies = NULL;
|
|
}
|
|
bcn->ies_len = 0;
|
|
bcn->tim_len = 0;
|
|
bcn->dtim = 0;
|
|
bcn->len = 0;
|
|
}
|
|
|
|
static void ssv_del_csa(struct ssv_vif *vif)
|
|
{
|
|
struct ssv_csa *csa = vif->ap.csa;
|
|
|
|
if (!csa)
|
|
return;
|
|
|
|
#if 0
|
|
if (csa->dma.buf)
|
|
{
|
|
kfree(csa->dma.buf);
|
|
}
|
|
#endif
|
|
ssv_del_bcn(&csa->bcn);
|
|
kfree(csa);
|
|
vif->ap.csa = NULL;
|
|
}
|
|
|
|
#if 0
|
|
static void ssv_csa_finish(struct work_struct *ws)
|
|
{
|
|
struct ssv_csa *csa = container_of(ws, struct ssv_csa, work);
|
|
struct ssv_vif *vif = csa->vif;
|
|
struct ssv_softc *sc = vif->sc;
|
|
int error = csa->status;
|
|
|
|
if (!error)
|
|
error = ssv_send_bcn_change(sc, vif->drv_vif_index, csa->dma.buf,
|
|
csa->bcn.len, csa->bcn.head_len,
|
|
csa->bcn.tim_len, NULL);
|
|
|
|
if (error)
|
|
cfg80211_stop_iface(sc->wiphy, &vif->wdev, GFP_KERNEL);
|
|
else
|
|
{
|
|
ssv_chanctx_unlink(vif);
|
|
ssv_chanctx_link(vif, csa->ch_idx, &csa->chandef);
|
|
cfg80211_ch_switch_notify(vif->ndev, &csa->chandef);
|
|
}
|
|
ssv_del_csa(vif);
|
|
}
|
|
#endif
|
|
|
|
/**
|
|
* @add_virtual_intf: create a new virtual interface with the given name,
|
|
* must set the struct wireless_dev's iftype. Beware: You must create
|
|
* the new netdev in the wiphy's network namespace! Returns the struct
|
|
* wireless_dev, or an ERR_PTR. For P2P device wdevs, the driver must
|
|
* also set the address member in the wdev.
|
|
*/
|
|
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 12, 0)
|
|
static struct wireless_dev* ssv_cfg80211_add_iface(struct wiphy *wiphy,
|
|
const char *name, unsigned char name_assign_type,
|
|
enum nl80211_iftype type, struct vif_params *params)
|
|
#elif LINUX_VERSION_CODE >= KERNEL_VERSION(4, 1, 0)
|
|
static struct wireless_dev* ssv_cfg80211_add_iface(struct wiphy *wiphy,
|
|
const char *name, unsigned char name_assign_type,
|
|
enum nl80211_iftype type, u32 *flags,
|
|
struct vif_params *params)
|
|
#elif LINUX_VERSION_CODE >= KERNEL_VERSION(3, 7, 0)
|
|
static struct wireless_dev* ssv_cfg80211_add_iface(struct wiphy *wiphy,
|
|
const char *name, enum nl80211_iftype type,
|
|
u32 *flags, struct vif_params *params)
|
|
#elif LINUX_VERSION_CODE >= KERNEL_VERSION(3, 6, 0)
|
|
static struct wireless_dev* ssv_cfg80211_add_iface(struct wiphy *wiphy,
|
|
char *name, enum nl80211_iftype type,
|
|
u32 *flags, struct vif_params *params)
|
|
#else
|
|
static struct net_device* ssv_cfg80211_add_iface(struct wiphy *wiphy,
|
|
char *name, enum nl80211_iftype type,
|
|
u32 *flags, struct vif_params *params)
|
|
#endif
|
|
{
|
|
struct ssv_softc *sc = wiphy_priv(wiphy);
|
|
struct wireless_dev *wdev = NULL;
|
|
struct ssv_vif *ssv_vif = NULL;
|
|
bool use_monitor = false;
|
|
#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 12, 0)
|
|
if(!flags){
|
|
use_monitor = false;
|
|
}
|
|
else{
|
|
use_monitor = (*flags & MONITOR_FLAG_COOK_FRAMES) ? true : false;
|
|
}
|
|
#else
|
|
use_monitor = (params->flags & MONITOR_FLAG_COOK_FRAMES) ? true : false;
|
|
#endif
|
|
|
|
SSV_LOG_DBG("[%s][%d]\n", __FUNCTION__, __LINE__);
|
|
|
|
wdev = ssv_interface_add(sc, name, type, params);
|
|
if (wdev) {
|
|
ssv_vif = container_of(wdev, struct ssv_vif, wdev);
|
|
ssv_vif->use_monitor = use_monitor;
|
|
}
|
|
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 6, 0)
|
|
return wdev;
|
|
#else
|
|
return wdev->netdev;
|
|
#endif
|
|
}
|
|
|
|
/**
|
|
* @del_virtual_intf: remove the virtual interface
|
|
*/
|
|
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 6, 0)
|
|
static int ssv_cfg80211_del_iface(struct wiphy *wiphy, struct wireless_dev *wdev)
|
|
#else
|
|
static int ssv_cfg80211_del_iface(struct wiphy *wiphy, struct net_device *dev)
|
|
#endif
|
|
{
|
|
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 6, 0)
|
|
struct net_device *dev = wdev->netdev;
|
|
#endif
|
|
struct ssv_vif *ssv_vif = netdev_priv(dev);
|
|
struct ssv_softc *sc = wiphy_priv(wiphy);
|
|
|
|
SSV_LOG_DBG("[%s][%d]\n", __FUNCTION__, __LINE__);
|
|
netdev_info(dev, "Remove Interface");
|
|
|
|
if (dev->reg_state == NETREG_REGISTERED) {
|
|
/* Will call ssv_close if interface is UP */
|
|
unregister_netdevice(dev);
|
|
}
|
|
|
|
#ifdef FMAC_BRIDGE
|
|
br0_detach(ssv_vif);
|
|
#endif
|
|
|
|
mutex_lock(&sc->cb_lock);
|
|
list_del(&ssv_vif->list);
|
|
mutex_unlock(&sc->cb_lock);
|
|
sc->avail_idx_map |= BIT(ssv_vif->drv_vif_index);
|
|
ssv_vif->ndev = NULL;
|
|
|
|
/* Clear the priv in adapter */
|
|
dev->ieee80211_ptr = NULL;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* @change_virtual_intf: change type/configuration of virtual interface,
|
|
* keep the struct wireless_dev's iftype updated.
|
|
*/
|
|
static int ssv_cfg80211_change_iface(struct wiphy *wiphy,
|
|
struct net_device *dev,
|
|
enum nl80211_iftype type,
|
|
#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 12, 0)
|
|
u32 *flags,
|
|
#endif
|
|
struct vif_params *params)
|
|
{
|
|
struct ssv_vif *vif = netdev_priv(dev);
|
|
SSV_LOG_DBG("[%s][%d]\n", __FUNCTION__, __LINE__);
|
|
|
|
if (vif->up)
|
|
return (-EBUSY);
|
|
|
|
dev->type = ARPHRD_ETHER;
|
|
switch (type)
|
|
{
|
|
case NL80211_IFTYPE_STATION:
|
|
case NL80211_IFTYPE_P2P_CLIENT:
|
|
vif->sta.ap = NULL;
|
|
vif->sta.tdls_sta = NULL;
|
|
break;
|
|
case NL80211_IFTYPE_MESH_POINT:
|
|
INIT_LIST_HEAD(&vif->ap.mpath_list);
|
|
INIT_LIST_HEAD(&vif->ap.proxy_list);
|
|
vif->ap.create_path = false;
|
|
vif->ap.generation = 0;
|
|
INIT_LIST_HEAD(&vif->ap.sta_list);
|
|
memset(&vif->ap.bcn, 0, sizeof(vif->ap.bcn));
|
|
break;
|
|
case NL80211_IFTYPE_AP:
|
|
case NL80211_IFTYPE_P2P_GO:
|
|
INIT_LIST_HEAD(&vif->ap.sta_list);
|
|
memset(&vif->ap.bcn, 0, sizeof(vif->ap.bcn));
|
|
break;
|
|
case NL80211_IFTYPE_AP_VLAN:
|
|
return -EPERM;
|
|
case NL80211_IFTYPE_MONITOR:
|
|
{
|
|
dev->type = ARPHRD_IEEE80211_RADIOTAP;
|
|
// dev->type = ARPHRD_IEEE80211;
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
|
|
vif->wdev.iftype = type;
|
|
if (params->use_4addr != -1)
|
|
vif->use_4addr = params->use_4addr;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* @scan: Request to do a scan. If returning zero, the scan request is given
|
|
* the driver, and will be valid until passed to cfg80211_scan_done().
|
|
* For scan results, call cfg80211_inform_bss(); you can call this outside
|
|
* the scan/scan_done bracket too.
|
|
*/
|
|
static int ssv_cfg80211_scan(struct wiphy *wiphy,
|
|
#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 6, 0)
|
|
struct net_device *dev,
|
|
#endif
|
|
struct cfg80211_scan_request *request)
|
|
{
|
|
struct ssv_softc *sc = wiphy_priv(wiphy);
|
|
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 6, 0)
|
|
struct ssv_vif *ssv_vif = container_of(request->wdev, struct ssv_vif,
|
|
wdev);
|
|
#else
|
|
struct ssv_vif *ssv_vif = netdev_priv(dev);
|
|
#endif
|
|
int ret = 0;
|
|
|
|
SSV_LOG_DBG("[%s][%d]\n", __FUNCTION__, __LINE__);
|
|
|
|
if(ssv_get_engmode())
|
|
{
|
|
SSV_LOG_DBG("Engineer Mode ignore ssv_cfg80211_scan!\n");
|
|
goto END;
|
|
}
|
|
|
|
if (0 != (ret = ssv_send_scanu_req(sc, ssv_vif, request)))
|
|
{
|
|
goto END;
|
|
}
|
|
|
|
sc->scan_request = request;
|
|
|
|
END:
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* @add_key: add a key with the given parameters. @mac_addr will be %NULL
|
|
* when adding a group key.
|
|
*/
|
|
static int ssv_cfg80211_add_key(struct wiphy *wiphy, struct net_device *netdev,
|
|
u8 key_index, bool pairwise, const u8 *mac_addr,
|
|
struct key_params *params)
|
|
{
|
|
struct ssv_softc *sc = wiphy_priv(wiphy);
|
|
struct ssv_vif *vif = netdev_priv(netdev);
|
|
int i, error = 0;
|
|
struct mm_key_add_cfm key_add_cfm;
|
|
u8_l cipher = 0;
|
|
struct ssv_sta *sta = NULL;
|
|
struct ssv_key *ssv_key;
|
|
|
|
SSV_LOG_DBG("[%s][%d]\n", __FUNCTION__, __LINE__);
|
|
|
|
if (mac_addr) {
|
|
sta = ssv_get_sta(sc, mac_addr);
|
|
if (!sta)
|
|
return -EINVAL;
|
|
ssv_key = &sta->key;
|
|
}
|
|
else
|
|
ssv_key = &vif->key[key_index];
|
|
|
|
/* Retrieve the cipher suite selector */
|
|
switch (params->cipher) {
|
|
case WLAN_CIPHER_SUITE_WEP40:
|
|
cipher = MAC_RSNIE_CIPHER_WEP40;
|
|
break;
|
|
case WLAN_CIPHER_SUITE_WEP104:
|
|
cipher = MAC_RSNIE_CIPHER_WEP104;
|
|
break;
|
|
case WLAN_CIPHER_SUITE_TKIP:
|
|
cipher = MAC_RSNIE_CIPHER_TKIP;
|
|
break;
|
|
case WLAN_CIPHER_SUITE_CCMP:
|
|
cipher = MAC_RSNIE_CIPHER_CCMP;
|
|
break;
|
|
case WLAN_CIPHER_SUITE_AES_CMAC:
|
|
cipher = MAC_RSNIE_CIPHER_AES_CMAC;
|
|
break;
|
|
case WLAN_CIPHER_SUITE_SMS4:
|
|
{
|
|
// Need to reverse key order
|
|
u8 tmp, *key = (u8 *)params->key;
|
|
cipher = MAC_RSNIE_CIPHER_SMS4;
|
|
for (i = 0; i < WPI_SUBKEY_LEN/2; i++) {
|
|
tmp = key[i];
|
|
key[i] = key[WPI_SUBKEY_LEN - 1 - i];
|
|
key[WPI_SUBKEY_LEN - 1 - i] = tmp;
|
|
}
|
|
for (i = 0; i < WPI_SUBKEY_LEN/2; i++) {
|
|
tmp = key[i + WPI_SUBKEY_LEN];
|
|
key[i + WPI_SUBKEY_LEN] = key[WPI_KEY_LEN - 1 - i];
|
|
key[WPI_KEY_LEN - 1 - i] = tmp;
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
|
|
//Fixme: comfirm eapol data packet send before ssv_send_key_add?
|
|
msleep(3);
|
|
|
|
if ((error = ssv_send_key_add(sc, vif->drv_vif_index,
|
|
(sta ? sta->sta_idx : 0xFF), pairwise,
|
|
(u8 *)params->key, params->key_len,
|
|
key_index, cipher, &key_add_cfm)))
|
|
return error;
|
|
|
|
if (key_add_cfm.status != 0) {
|
|
SSV_LOG_DBG(KERN_CRIT "[%s]: Status Error(%d)\n", __FUNCTION__, key_add_cfm.status);
|
|
return -EIO;
|
|
}
|
|
|
|
/* Save the index retrieved from LMAC */
|
|
ssv_key->hw_idx = key_add_cfm.hw_key_idx;
|
|
if(NULL!=sta){
|
|
sta->port_control=false;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* @get_key: get information about the key with the given parameters.
|
|
* @mac_addr will be %NULL when requesting information for a group
|
|
* key. All pointers given to the @callback function need not be valid
|
|
* after it returns. This function should return an error if it is
|
|
* not possible to retrieve the key, -ENOENT if it doesn't exist.
|
|
*
|
|
*/
|
|
static int ssv_cfg80211_get_key(struct wiphy *wiphy, struct net_device *netdev,
|
|
u8 key_index, bool pairwise, const u8 *mac_addr,
|
|
void *cookie,
|
|
void (*callback)(void *cookie, struct key_params*))
|
|
{
|
|
SSV_LOG_DBG("[%s][%d]\n", __FUNCTION__, __LINE__);
|
|
return -1;
|
|
}
|
|
|
|
|
|
/**
|
|
* @del_key: remove a key given the @mac_addr (%NULL for a group key)
|
|
* and @key_index, return -ENOENT if the key doesn't exist.
|
|
*/
|
|
static int ssv_cfg80211_del_key(struct wiphy *wiphy, struct net_device *netdev,
|
|
u8 key_index, bool pairwise, const u8 *mac_addr)
|
|
{
|
|
struct ssv_softc *sc = wiphy_priv(wiphy);
|
|
struct ssv_vif *vif = netdev_priv(netdev);
|
|
int error;
|
|
struct ssv_sta *sta = NULL;
|
|
struct ssv_key *ssv_key;
|
|
|
|
SSV_LOG_DBG("[%s][%d]\n", __FUNCTION__, __LINE__);
|
|
if (mac_addr) {
|
|
sta = ssv_get_sta(sc, mac_addr);
|
|
if (!sta)
|
|
return -EINVAL;
|
|
ssv_key = &sta->key;
|
|
}
|
|
else
|
|
ssv_key = &vif->key[key_index];
|
|
|
|
error = ssv_send_key_del(sc, ssv_key->hw_idx);
|
|
return error;
|
|
}
|
|
|
|
/**
|
|
* @set_default_key: set the default key on an interface
|
|
*/
|
|
static int ssv_cfg80211_set_default_key(struct wiphy *wiphy,
|
|
struct net_device *netdev,
|
|
u8 key_index, bool unicast, bool multicast)
|
|
{
|
|
SSV_LOG_DBG("[%s][%d]\n", __FUNCTION__, __LINE__);
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* @set_default_mgmt_key: set the default management frame key on an interface
|
|
*/
|
|
static int ssv_cfg80211_set_default_mgmt_key(struct wiphy *wiphy,
|
|
struct net_device *netdev,
|
|
u8 key_index)
|
|
{
|
|
SSV_LOG_DBG("[%s][%d]\n", __FUNCTION__, __LINE__);
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* @connect: Connect to the ESS with the specified parameters. When connected,
|
|
* call cfg80211_connect_result() with status code %WLAN_STATUS_SUCCESS.
|
|
* If the connection fails for some reason, call cfg80211_connect_result()
|
|
* with the status from the AP.
|
|
* (invoked with the wireless_dev mutex held)
|
|
*/
|
|
static int ssv_cfg80211_connect(struct wiphy *wiphy, struct net_device *dev,
|
|
struct cfg80211_connect_params *sme)
|
|
{
|
|
struct ssv_softc *sc = wiphy_priv(wiphy);
|
|
struct ssv_vif *ssv_vif = netdev_priv(dev);
|
|
struct sm_connect_cfm sm_connect_cfm;
|
|
int error = 0;
|
|
|
|
RETRY_CONNECT:
|
|
SSV_LOG_DBG("[%s][%d]\n", __FUNCTION__, __LINE__);
|
|
|
|
ssv_vif->need_port_control = ssv_port_control(sme->crypto.cipher_group);
|
|
SSV_LOG_DBG("need port control=%d\r\n",ssv_vif->need_port_control);
|
|
#if defined(CONFIG_WPA_SUPPLICANT_CTL)
|
|
if (sme->crypto.n_akm_suites != 0 &&
|
|
sme->crypto.akm_suites[0] == 0x000FAC08 &&
|
|
sme->auth_type == NL80211_AUTHTYPE_OPEN_SYSTEM)
|
|
{
|
|
SSV_LOG_DBG("%s: the actual auth type is SAE!!!\n", __func__);
|
|
sme->auth_type = SSV_NL80211_AUTHTYPE_SAE;
|
|
g_ssv_sc = sc;
|
|
g_ssv_vif = ssv_vif;
|
|
}
|
|
#endif
|
|
/* For SHARED-KEY authentication, must install key first */
|
|
if (sme->auth_type == NL80211_AUTHTYPE_SHARED_KEY && sme->key)
|
|
{
|
|
struct key_params key_params;
|
|
key_params.key = (u8 *)sme->key;
|
|
key_params.seq = NULL;
|
|
key_params.key_len = sme->key_len;
|
|
key_params.seq_len = 0;
|
|
key_params.cipher = sme->crypto.cipher_group;
|
|
ssv_cfg80211_add_key(wiphy, dev, sme->key_idx, false, NULL, &key_params);
|
|
}
|
|
//SSV_LOG_DBG("auth_type %d\n",sme->auth_type);
|
|
//SSV_LOG_DBG("ciphers_pairwise 0x%x\n",sme->crypto.ciphers_pairwise[0]);
|
|
|
|
|
|
/* Forward the information to the LMAC */
|
|
if ((error = ssv_send_sm_connect_req(sc, ssv_vif, sme, &sm_connect_cfm)))
|
|
return error;
|
|
|
|
// Check the status
|
|
switch (sm_connect_cfm.status)
|
|
{
|
|
case CO_OK:
|
|
error = 0;
|
|
break;
|
|
case CO_BUSY:
|
|
error = -EINPROGRESS;
|
|
break;
|
|
case CO_OP_IN_PROGRESS:
|
|
error = -EALREADY;
|
|
break;
|
|
default:
|
|
error = -EIO;
|
|
break;
|
|
}
|
|
|
|
#ifdef CONFIG_SSV_CHANNEL_FOLLOW
|
|
if(NL80211_IFTYPE_STATION == SSV_VIF_TYPE(ssv_vif) && sme->channel) {
|
|
ssv_vif->ch_hint = sme->channel->center_freq;
|
|
ssv_vif->chtype_hint = NL80211_CHAN_HT20; //default为HT20
|
|
|
|
if(sme->channel->flags & IEEE80211_CHAN_NO_HT40) {
|
|
ssv_vif->chtype_hint = NL80211_CHAN_HT20;
|
|
} else {
|
|
if(sme->channel->flags & IEEE80211_CHAN_NO_HT40PLUS) {
|
|
ssv_vif->chtype_hint = NL80211_CHAN_HT40MINUS;
|
|
}
|
|
if(sme->channel->flags & IEEE80211_CHAN_NO_HT40MINUS) {
|
|
ssv_vif->chtype_hint = NL80211_CHAN_HT40PLUS;
|
|
}
|
|
}
|
|
}
|
|
#endif /*CONFIG_SSV_CHANNEL_FOLLOW */
|
|
|
|
|
|
sc->sta_reconnect_info.auth_type = sme->auth_type;
|
|
|
|
if(ssv_cfg.sta_max_reconnect_times)
|
|
{
|
|
//Retry_Connection doesn't support WPA3
|
|
if(sme->auth_type != NL80211_AUTHTYPE_SAE)
|
|
{
|
|
down(&sc->reconnect_sem);
|
|
|
|
/*If connection failed, retry again */
|
|
if(sc->sta_reconnect_info.connect_retry)
|
|
{
|
|
if (sme->crypto.n_ciphers_pairwise &&
|
|
((sme->crypto.ciphers_pairwise[0] == WLAN_CIPHER_SUITE_WEP40) ||
|
|
(sme->crypto.ciphers_pairwise[0] == WLAN_CIPHER_SUITE_WEP104)))
|
|
{
|
|
if(sme->auth_type == NL80211_AUTHTYPE_OPEN_SYSTEM)
|
|
sme->auth_type = NL80211_AUTHTYPE_SHARED_KEY;
|
|
else if(sme->auth_type == NL80211_AUTHTYPE_SHARED_KEY)
|
|
sme->auth_type = NL80211_AUTHTYPE_OPEN_SYSTEM;
|
|
}
|
|
SSV_LOG_DBG("goto Retry\n");
|
|
goto RETRY_CONNECT;
|
|
}
|
|
}
|
|
}
|
|
|
|
SSV_LOG_DBG("connect end %d\n", error);
|
|
return error;
|
|
}
|
|
|
|
/**
|
|
* @disconnect: Disconnect from the BSS/ESS.
|
|
* (invoked with the wireless_dev mutex held)
|
|
*/
|
|
static int ssv_cfg80211_disconnect(struct wiphy *wiphy, struct net_device *dev,
|
|
u16 reason_code)
|
|
{
|
|
struct ssv_softc *sc = wiphy_priv(wiphy);
|
|
struct ssv_vif *ssv_vif = netdev_priv(dev);
|
|
|
|
SSV_LOG_DBG("[%s] reason_code %d\n", __FUNCTION__, reason_code);
|
|
return(ssv_send_sm_disconnect_req(sc, ssv_vif, reason_code));
|
|
}
|
|
/**
|
|
* @add_station: Add a new station.
|
|
*/
|
|
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 16, 0)
|
|
static int ssv_cfg80211_add_station(struct wiphy *wiphy, struct net_device *dev,
|
|
const u8 *mac, struct station_parameters *params)
|
|
|
|
#else
|
|
static int ssv_cfg80211_add_station(struct wiphy *wiphy, struct net_device *dev,
|
|
u8 *mac, struct station_parameters *params)
|
|
#endif
|
|
{
|
|
struct ssv_softc *sc = wiphy_priv(wiphy);
|
|
struct ssv_vif *ssv_vif = netdev_priv(dev);
|
|
struct me_sta_add_cfm me_sta_add_cfm;
|
|
int error = 0;
|
|
|
|
SSV_LOG_DBG("[%s][%d]\n", __FUNCTION__, __LINE__);
|
|
|
|
WARN_ON(SSV_VIF_TYPE(ssv_vif) == NL80211_IFTYPE_AP_VLAN);
|
|
|
|
/* Do not add TDLS station */
|
|
if (params->sta_flags_set & BIT(NL80211_STA_FLAG_TDLS_PEER))
|
|
return 0;
|
|
|
|
/* Indicate we are in a STA addition process - This will allow handling
|
|
* potential PS mode change indications correctly
|
|
*/
|
|
sc->adding_sta = true;
|
|
|
|
/* Forward the information to the LMAC */
|
|
if ((error = ssv_send_me_sta_add(sc, params, mac, ssv_vif->drv_vif_index,
|
|
&me_sta_add_cfm)))
|
|
return error;
|
|
|
|
// Check the status
|
|
switch (me_sta_add_cfm.status)
|
|
{
|
|
case CO_OK:
|
|
{
|
|
struct ssv_sta *sta = &sc->sta_table[me_sta_add_cfm.sta_idx];
|
|
int tid;
|
|
sta->aid = params->aid;
|
|
|
|
sta->sta_idx = me_sta_add_cfm.sta_idx;
|
|
ssv_push_private_msg_to_host(sc, (u32)E_HOST_PRIV_MSG_TYPE_RX_REORD_CREATE, (u8 *)&sta->sta_idx, (u32)sizeof(u8));
|
|
sta->ch_idx = ssv_vif->ch_index;
|
|
sta->vif_idx = ssv_vif->drv_vif_index;
|
|
sta->vlan_idx = sta->vif_idx;
|
|
sta->qos = (params->sta_flags_set & BIT(NL80211_STA_FLAG_WME)) != 0;
|
|
sta->ht = params->ht_capa ? 1 : 0;
|
|
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 8, 0)
|
|
sta->vht = params->vht_capa ? 1 : 0;
|
|
#endif
|
|
sta->acm = 0;
|
|
sta->probe_timestamp = 0;
|
|
sta->last_rx = jiffies_to_msecs(jiffies);
|
|
|
|
for (tid = 0; tid < NX_NB_TXQ_PER_STA; tid++) {
|
|
int uapsd_bit = ssv_hwq2uapsd[ssv_tid2hwq[tid]];
|
|
if (params->uapsd_queues & uapsd_bit)
|
|
sta->uapsd_tids |= 1 << tid;
|
|
else
|
|
sta->uapsd_tids &= ~(1 << tid);
|
|
}
|
|
memcpy(sta->mac_addr, mac, ETH_ALEN);
|
|
// ssv_dbgfs_register_rc_stat(sc, sta);
|
|
|
|
/* Ensure that we won't process PS change or channel switch ind*/
|
|
mutex_lock(&sc->cb_lock);
|
|
ssv_drv_hci_tx_active_by_sta(sc->hci_priv, sc->hci_ops, sta->sta_idx); //active hci mng. queue
|
|
ssv_drv_hci_tx_resume_by_sta(sc->hci_priv, sc->hci_ops, sta->sta_idx); //resume hci mng. queue
|
|
list_add_tail(&sta->list, &ssv_vif->ap.sta_list);
|
|
sta->valid = true;
|
|
ssv_ps_bh_enable(sc, sta, sta->ps.active || me_sta_add_cfm.pm_state);
|
|
sta->port_control=(true==ssv_vif->need_port_control)?true:false;
|
|
mutex_unlock(&sc->cb_lock);
|
|
|
|
error = 0;
|
|
|
|
#ifdef CONFIG_SSV_BFMER
|
|
#endif /* CONFIG_SSV_BFMER */
|
|
|
|
#define PRINT_STA_FLAG(f) \
|
|
(params->sta_flags_set & BIT(NL80211_STA_FLAG_##f) ? "["#f"]" : "")
|
|
|
|
netdev_info(dev, "Add sta %d (%pM) flags=%s%s%s%s%s%s"
|
|
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 9, 0)
|
|
"%s"
|
|
#endif
|
|
,
|
|
sta->sta_idx, mac,
|
|
PRINT_STA_FLAG(AUTHORIZED),
|
|
PRINT_STA_FLAG(SHORT_PREAMBLE),
|
|
PRINT_STA_FLAG(WME),
|
|
PRINT_STA_FLAG(MFP),
|
|
PRINT_STA_FLAG(AUTHENTICATED),
|
|
PRINT_STA_FLAG(TDLS_PEER)
|
|
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 9, 0)
|
|
,
|
|
PRINT_STA_FLAG(ASSOCIATED)
|
|
#endif
|
|
);
|
|
#undef PRINT_STA_FLAG
|
|
break;
|
|
}
|
|
default:
|
|
error = -EBUSY;
|
|
break;
|
|
}
|
|
|
|
sc->adding_sta = false;
|
|
|
|
return error;
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* @del_station: Remove a station
|
|
*/
|
|
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 19, 0)
|
|
static int ssv_cfg80211_del_station(struct wiphy *wiphy, struct net_device *dev,
|
|
struct station_del_parameters *params)
|
|
#elif LINUX_VERSION_CODE >= KERNEL_VERSION(3, 16, 0)
|
|
static int ssv_cfg80211_del_station(struct wiphy *wiphy, struct net_device *dev,
|
|
const u8 *mac)
|
|
#else
|
|
static int ssv_cfg80211_del_station(struct wiphy *wiphy, struct net_device *dev,
|
|
u8 *mac)
|
|
#endif
|
|
{
|
|
struct ssv_softc *sc = wiphy_priv(wiphy);
|
|
struct ssv_vif *ssv_vif = netdev_priv(dev);
|
|
struct ssv_sta *cur, *tmp;
|
|
int error = 0, found = 0;
|
|
int j = 0;
|
|
|
|
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 19, 0)
|
|
const u8 *mac = params->mac;
|
|
#endif
|
|
|
|
SSV_LOG_DBG("[%s][%d]\n", __FUNCTION__, __LINE__);
|
|
|
|
list_for_each_entry_safe(cur, tmp, &ssv_vif->ap.sta_list, list) {
|
|
if ((!mac) || (!memcmp(cur->mac_addr, mac, ETH_ALEN))) {
|
|
netdev_info(dev, "Del sta %d (%pM)", cur->sta_idx, cur->mac_addr);
|
|
memset(&sc->rx_bysta[cur->sta_idx], 0 , sizeof(struct ssv_rx));
|
|
memset(&sc->tx_bysta[cur->sta_idx], 0 , sizeof(struct ssv_tx));
|
|
/* Ensure that we won't process PS change ind */
|
|
mutex_lock(&sc->cb_lock);
|
|
cur->ps.active = false;
|
|
cur->valid = false;
|
|
cur->port_control = true;
|
|
mutex_unlock(&sc->cb_lock);
|
|
|
|
if (cur->vif_idx != cur->vlan_idx) {
|
|
struct ssv_vif *vlan_vif;
|
|
vlan_vif = sc->vif_table[cur->vlan_idx];
|
|
if (vlan_vif->up) {
|
|
if ((SSV_VIF_TYPE(vlan_vif) == NL80211_IFTYPE_AP_VLAN) &&
|
|
(vlan_vif->use_4addr)) {
|
|
vlan_vif->ap_vlan.sta_4a = NULL;
|
|
} else {
|
|
WARN(1, "Deleting sta belonging to VLAN other than AP_VLAN 4A");
|
|
}
|
|
}
|
|
}
|
|
ssv_drv_hci_tx_pause_by_sta(sc->hci_priv, sc->hci_ops, cur->sta_idx); //pause hci tx queue by sta
|
|
ssv_drv_hci_tx_inactive_by_sta(sc->hci_priv, sc->hci_ops, cur->sta_idx); //inactive hci tx queue by sta
|
|
ssv_push_private_msg_to_host(sc, (u32)E_HOST_PRIV_MSG_TYPE_RX_REORD_DEL, (u8 *)&cur->sta_idx, (u32)sizeof(u8));
|
|
//Reset station last sequence
|
|
for(j = 0; j < TID_MAX ; j++){
|
|
cur->rx_last_seqcntl[j] = 0xFFFF;
|
|
}
|
|
error = ssv_send_me_sta_del(sc, cur->sta_idx, false);
|
|
if ((error != 0) && (error != -EPIPE))
|
|
return error;
|
|
|
|
list_del(&cur->list);
|
|
// ssv_dbgfs_unregister_rc_stat(sc, cur);
|
|
found ++;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (found) {
|
|
SSV_LOG_DBG("Remove old station %d\n", found);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
#ifdef CONFIG_MIFI_LOWPOWER
|
|
#define PB_RESP_TEMPLATE_TYPE_PATTER 0xFF50
|
|
#define PB_RESP_TEMPLATE_NO_HIDDEN_PATTER 0x1234
|
|
#define PB_RESP_TEMPLATE_HIDDEN_PATTER 0xFFFF
|
|
|
|
void ssv_gen_fake_mgmt_tx_params(void)
|
|
{
|
|
fake_mgmt_tx_params.chan = NULL;
|
|
fake_mgmt_tx_params.offchan = false;
|
|
fake_mgmt_tx_params.wait = 0;
|
|
fake_mgmt_tx_params.buf = probe_resp_info.data;
|
|
fake_mgmt_tx_params.len = probe_resp_info.data_len;
|
|
fake_mgmt_tx_params.no_cck = false;
|
|
fake_mgmt_tx_params.dont_wait_for_ack = true;
|
|
fake_mgmt_tx_params.n_csa_offsets = 0;
|
|
fake_mgmt_tx_params.csa_offsets = NULL;
|
|
}
|
|
|
|
void ssv_gen_probe_resp_template(struct cfg80211_ap_settings *settings, struct ssv_bcn *bcn, uint8_t *bcn_buf)
|
|
{
|
|
uint8_t *probe_resp;
|
|
uint8_t probe_resp_len;
|
|
uint8_t *pdata;
|
|
uint8_t *p_skip_tim;
|
|
struct mac_hdr *mac_hdr;
|
|
uint16_t tim_oft;
|
|
uint8_t ssid_oft;
|
|
// struct ieee80211_mgmt *resp;
|
|
// uint8_t *pos;
|
|
SSV_LOG_DBG("ssv_gen_probe_resp\n");
|
|
|
|
if(settings->hidden_ssid == NL80211_HIDDEN_SSID_NOT_IN_USE)
|
|
probe_resp_len = bcn->len - bcn->tim_len;
|
|
else
|
|
probe_resp_len = bcn->len - bcn->tim_len + settings->ssid_len;
|
|
// SSV_LOG_DBG("original len %ld\n",(unsigned long)bcn->len - bcn->tim_len);
|
|
// SSV_LOG_DBG("probe_resp_len %d\n", probe_resp_len);
|
|
// SSV_LOG_DBG_DUMP("becon", bcn_buf, bcn->len);
|
|
|
|
probe_resp = kzalloc(probe_resp_len, GFP_KERNEL);
|
|
if(NULL == probe_resp)
|
|
{
|
|
SSV_LOG_DBG("[%s][%d] kzalloc() for probe_resp failed!!\n", __FUNCTION__, __LINE__);
|
|
return;
|
|
}
|
|
|
|
tim_oft = bcn->head_len;
|
|
|
|
pdata = probe_resp;
|
|
if(settings->hidden_ssid == NL80211_HIDDEN_SSID_NOT_IN_USE)
|
|
{
|
|
//copy mac_header~before TIM
|
|
memcpy(pdata, (uint8_t *)bcn_buf, tim_oft);
|
|
pdata = pdata + tim_oft;
|
|
}
|
|
else
|
|
{
|
|
//check kernel 3.4.110是否成立
|
|
ssid_oft = sizeof(struct mac_hdr) + 12; //bcn timestamp+intval+capinfo = 12
|
|
memcpy(pdata, (uint8_t *)bcn_buf, ssid_oft);
|
|
pdata = pdata + ssid_oft;
|
|
*pdata++ = WLAN_EID_SSID;
|
|
*pdata++ = settings->ssid_len;
|
|
memcpy(pdata, settings->ssid, settings->ssid_len);
|
|
pdata = pdata + settings->ssid_len;
|
|
memcpy(pdata, (uint8_t *)bcn_buf+ssid_oft+2, tim_oft-ssid_oft-2); //ssid element id+length = 2
|
|
pdata = pdata + (tim_oft - ssid_oft - 2) ;
|
|
}
|
|
|
|
//skip TIM
|
|
p_skip_tim = (uint8_t *)bcn_buf + tim_oft + bcn->tim_len;
|
|
|
|
//copy after TIM ~ End
|
|
memcpy(pdata, p_skip_tim, bcn->len - (tim_oft + bcn->tim_len));
|
|
|
|
//update fctl of mac header
|
|
mac_hdr = (struct mac_hdr *)probe_resp;
|
|
mac_hdr->fctl = PB_RESP_TEMPLATE_TYPE_PATTER; //MAC_FCTRL_PROBERSP_ST
|
|
if(settings->hidden_ssid == NL80211_HIDDEN_SSID_NOT_IN_USE)
|
|
mac_hdr->durid = PB_RESP_TEMPLATE_NO_HIDDEN_PATTER;
|
|
else
|
|
mac_hdr->durid = PB_RESP_TEMPLATE_HIDDEN_PATTER;
|
|
|
|
|
|
//SSV_LOG_DBG_DUMP("g_probe_resp", probe_resp, probe_resp_len);
|
|
|
|
//add tail to check send out...
|
|
// *(probe_resp + bcn->len-bcn->tim_len) = 0xAA;
|
|
// *(probe_resp + bcn->len-bcn->tim_len+1) = 0xBB;
|
|
// *(probe_resp + bcn->len-bcn->tim_len+2) = 0xCC;
|
|
// *(probe_resp + bcn->len-bcn->tim_len+3) = 0xDD;
|
|
SSV_LOG_DBG_DUMP("update ssdi g_probe_resp", probe_resp, probe_resp_len);
|
|
|
|
probe_resp_info.data_len = probe_resp_len;
|
|
probe_resp_info.data = probe_resp;
|
|
}
|
|
#endif
|
|
|
|
/**
|
|
* @change_station: Modify a given station. Note that flags changes are not much
|
|
* validated in cfg80211, in particular the auth/assoc/authorized flags
|
|
* might come to the driver in invalid combinations -- make sure to check
|
|
* them, also against the existing state! Drivers must call
|
|
* cfg80211_check_station_change() to validate the information.
|
|
*/
|
|
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 16, 0)
|
|
static int ssv_cfg80211_change_station(struct wiphy *wiphy, struct net_device *dev,
|
|
const u8 *mac, struct station_parameters *params)
|
|
#else
|
|
static int ssv_cfg80211_change_station(struct wiphy *wiphy, struct net_device *dev,
|
|
u8 *mac, struct station_parameters *params)
|
|
#endif
|
|
{
|
|
struct ssv_softc *sc = wiphy_priv(wiphy);
|
|
struct ssv_vif *vif = netdev_priv(dev);
|
|
struct ssv_sta *sta;
|
|
|
|
SSV_LOG_DBG("[%s][%d]\n", __FUNCTION__, __LINE__);
|
|
|
|
sta = ssv_get_sta(sc, mac);
|
|
if (!sta)
|
|
{
|
|
/* Add the TDLS station */
|
|
if (params->sta_flags_set & BIT(NL80211_STA_FLAG_TDLS_PEER))
|
|
{
|
|
struct ssv_vif *ssv_vif = netdev_priv(dev);
|
|
struct me_sta_add_cfm me_sta_add_cfm;
|
|
int error = 0;
|
|
|
|
/* Indicate we are in a STA addition process - This will allow handling
|
|
* potential PS mode change indications correctly
|
|
*/
|
|
sc->adding_sta = true;
|
|
|
|
/* Forward the information to the LMAC */
|
|
if ((error = ssv_send_me_sta_add(sc, params, mac, ssv_vif->drv_vif_index,
|
|
&me_sta_add_cfm)))
|
|
return error;
|
|
|
|
// Check the status
|
|
switch (me_sta_add_cfm.status)
|
|
{
|
|
case CO_OK:
|
|
{
|
|
int tid;
|
|
sta = &sc->sta_table[me_sta_add_cfm.sta_idx];
|
|
sta->aid = params->aid;
|
|
sta->sta_idx = me_sta_add_cfm.sta_idx;
|
|
sta->ch_idx = ssv_vif->ch_index;
|
|
sta->vif_idx = ssv_vif->drv_vif_index;
|
|
sta->vlan_idx = sta->vif_idx;
|
|
sta->qos = (params->sta_flags_set & BIT(NL80211_STA_FLAG_WME)) != 0;
|
|
sta->ht = params->ht_capa ? 1 : 0;
|
|
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 8, 0)
|
|
sta->vht = params->vht_capa ? 1 : 0;
|
|
#endif
|
|
sta->acm = 0;
|
|
for (tid = 0; tid < NX_NB_TXQ_PER_STA; tid++) {
|
|
int uapsd_bit = ssv_hwq2uapsd[ssv_tid2hwq[tid]];
|
|
if (params->uapsd_queues & uapsd_bit)
|
|
sta->uapsd_tids |= 1 << tid;
|
|
else
|
|
sta->uapsd_tids &= ~(1 << tid);
|
|
}
|
|
memcpy(sta->mac_addr, mac, ETH_ALEN);
|
|
// ssv_dbgfs_register_rc_stat(sc, sta);
|
|
|
|
/* Ensure that we won't process PS change or channel switch ind*/
|
|
mutex_lock(&sc->cb_lock);
|
|
sta->valid = true;
|
|
mutex_unlock(&sc->cb_lock);
|
|
|
|
#define PRINT_STA_FLAG(f) \
|
|
(params->sta_flags_set & BIT(NL80211_STA_FLAG_##f) ? "["#f"]" : "")
|
|
|
|
netdev_info(dev, "Add TDLS sta %d (%pM) flags=%s%s%s%s%s%s"
|
|
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 9, 0)
|
|
"%s"
|
|
#endif
|
|
,
|
|
sta->sta_idx, mac,
|
|
PRINT_STA_FLAG(AUTHORIZED),
|
|
PRINT_STA_FLAG(SHORT_PREAMBLE),
|
|
PRINT_STA_FLAG(WME),
|
|
PRINT_STA_FLAG(MFP),
|
|
PRINT_STA_FLAG(AUTHENTICATED),
|
|
PRINT_STA_FLAG(TDLS_PEER)
|
|
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 9, 0)
|
|
,
|
|
PRINT_STA_FLAG(ASSOCIATED)
|
|
#endif
|
|
);
|
|
#undef PRINT_STA_FLAG
|
|
|
|
break;
|
|
}
|
|
default:
|
|
error = -EBUSY;
|
|
break;
|
|
}
|
|
|
|
sc->adding_sta = false;
|
|
} else {
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
|
|
if (params->sta_flags_mask & BIT(NL80211_STA_FLAG_AUTHORIZED))
|
|
ssv_send_me_set_control_port_req(sc,
|
|
(params->sta_flags_set & BIT(NL80211_STA_FLAG_AUTHORIZED)) != 0,
|
|
sta->sta_idx);
|
|
|
|
if (params->vlan) {
|
|
uint8_t vlan_idx;
|
|
|
|
vif = netdev_priv(params->vlan);
|
|
vlan_idx = vif->drv_vif_index;
|
|
|
|
if (sta->vlan_idx != vlan_idx) {
|
|
struct ssv_vif *old_vif;
|
|
old_vif = sc->vif_table[sta->vlan_idx];
|
|
sta->vlan_idx = vlan_idx;
|
|
|
|
if ((SSV_VIF_TYPE(vif) == NL80211_IFTYPE_AP_VLAN) &&
|
|
(vif->use_4addr)) {
|
|
WARN((vif->ap_vlan.sta_4a),
|
|
"4A AP_VLAN interface with more than one sta");
|
|
vif->ap_vlan.sta_4a = sta;
|
|
}
|
|
|
|
if ((SSV_VIF_TYPE(old_vif) == NL80211_IFTYPE_AP_VLAN) &&
|
|
(old_vif->use_4addr)) {
|
|
old_vif->ap_vlan.sta_4a = NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
#ifdef CONFIG_SSV_CHANNEL_FOLLOW
|
|
static void ssv_softap_follow_sta_channel(struct ssv_softc *sc, struct cfg80211_chan_def *chandef)
|
|
{
|
|
u16 freq = chandef->chan->center_freq;
|
|
struct ssv_vif *sta_vif = NULL, *ap_vif = NULL, *ssv_vif = NULL;
|
|
struct ssv_sta *sta_mode = NULL;
|
|
|
|
// Look for VIF entry check sta mode exist or not
|
|
list_for_each_entry(ssv_vif, &sc->vifs, list) {
|
|
if (ssv_vif->up) {
|
|
if (NL80211_IFTYPE_STATION == SSV_VIF_TYPE(ssv_vif))
|
|
sta_vif = ssv_vif;
|
|
}
|
|
if (NL80211_IFTYPE_AP == SSV_VIF_TYPE(ssv_vif))
|
|
ap_vif = ssv_vif;
|
|
}
|
|
|
|
if ((NULL == sta_vif) || (NULL == ap_vif)) {
|
|
SSV_LOG_DBG("Not concurrency mode\n");
|
|
return;
|
|
}
|
|
|
|
if (sta_vif) {
|
|
sta_mode = sta_vif->sta.ap;
|
|
}
|
|
if (sta_mode && freq != sta_mode->center_freq) {
|
|
|
|
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 8, 0)
|
|
chandef->chan = ieee80211_get_channel(sta_vif->wdev.wiphy, sta_mode->center_freq);
|
|
#endif
|
|
chandef->chan->band = sta_mode->band;
|
|
chandef->chan->center_freq = sta_mode->center_freq;
|
|
chandef->center_freq1 = sta_mode->center_freq1;
|
|
chandef->center_freq2 = sta_mode->center_freq2;
|
|
chandef->width = sta_mode->width;
|
|
cfg80211_ch_switch_notify(ap_vif->ndev, chandef);
|
|
SSV_LOG_DBG("SSV notify softap channel switch\n");
|
|
}
|
|
}
|
|
#endif /*CONFIG_SSV_CHANNEL_FOLLOW */
|
|
|
|
/**
|
|
* @start_ap: Start acting in AP mode defined by the parameters.
|
|
*/
|
|
static int ssv_cfg80211_start_ap(struct wiphy *wiphy, struct net_device *dev,
|
|
struct cfg80211_ap_settings *settings)
|
|
{
|
|
struct ssv_softc *sc = wiphy_priv(wiphy);
|
|
struct ssv_vif *ssv_vif = netdev_priv(dev);
|
|
struct apm_start_cfm apm_start_cfm;
|
|
struct ssv_sta *sta;
|
|
int error = 0;
|
|
u8 *buf = NULL;
|
|
#ifdef CONFIG_MIFI_LOWPOWER
|
|
u64 cookie = 0;
|
|
#endif
|
|
|
|
#ifdef CONFIG_SSV_CHANNEL_FOLLOW
|
|
#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 8, 0)
|
|
struct cfg80211_chan_def chandef;
|
|
SSV_LOG_DBG("[%s][%d]\n", __FUNCTION__, __LINE__);
|
|
|
|
chandef.chan = &ssv_vif->chan_setting;
|
|
switch (ssv_vif->chan_type_setting)
|
|
{
|
|
case NL80211_CHAN_NO_HT: chandef.width = NL80211_CHAN_WIDTH_20_NOHT; break;
|
|
case NL80211_CHAN_HT20: chandef.width = NL80211_CHAN_WIDTH_20; break;
|
|
case NL80211_CHAN_HT40MINUS: chandef.width = NL80211_CHAN_WIDTH_40; break;
|
|
case NL80211_CHAN_HT40PLUS: chandef.width = NL80211_CHAN_WIDTH_40; break;
|
|
default: chandef.width = NL80211_CHAN_WIDTH_20; break;
|
|
}
|
|
ssv_softap_follow_sta_channel(sc, &chandef);
|
|
#else
|
|
SSV_LOG_DBG("[%s][%d]\n", __FUNCTION__, __LINE__);
|
|
ssv_softap_follow_sta_channel(sc, &settings->chandef);
|
|
#endif
|
|
#endif /*CONFIG_SSV_CHANNEL_FOLLOW */
|
|
|
|
|
|
/* Forward the information to the LMAC */
|
|
if ((error = ssv_send_apm_start_req(sc, ssv_vif, settings, &apm_start_cfm, &buf)))
|
|
{
|
|
goto err;
|
|
}
|
|
|
|
|
|
// Check the status
|
|
switch (apm_start_cfm.status)
|
|
{
|
|
case CO_OK:
|
|
{
|
|
ssv_vif->ap.bcmc_index = apm_start_cfm.bcmc_idx;
|
|
ssv_vif->ap.flags = 0;
|
|
ssv_vif->need_port_control = ssv_port_control(settings->crypto.cipher_group);
|
|
SSV_LOG_DBG("need port control=%d\r\n",ssv_vif->need_port_control);
|
|
sta = &sc->sta_table[apm_start_cfm.bcmc_idx];
|
|
sta->valid = true;
|
|
sta->aid = 0;
|
|
sta->sta_idx = apm_start_cfm.bcmc_idx;
|
|
sta->ch_idx = apm_start_cfm.ch_idx;
|
|
sta->vif_idx = ssv_vif->drv_vif_index;
|
|
sta->qos = false;
|
|
sta->acm = 0;
|
|
sta->ps.active = false;
|
|
|
|
mutex_lock(&sc->cb_lock);
|
|
ssv_chanctx_link(ssv_vif, apm_start_cfm.ch_idx,
|
|
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 8, 0)
|
|
&settings->chandef
|
|
#elif defined CONFIG_SSV_CHANNEL_FOLLOW
|
|
&chandef
|
|
#else
|
|
NULL
|
|
#endif
|
|
);
|
|
mutex_unlock(&sc->cb_lock);
|
|
|
|
netif_tx_start_all_queues(dev);
|
|
netif_carrier_on(dev);
|
|
{ // turn on traffic for use_monitor interface
|
|
struct ssv_vif *tmp_vif = NULL;
|
|
|
|
list_for_each_entry(tmp_vif, &sc->vifs, list) {
|
|
if (tmp_vif->use_monitor) {
|
|
netif_tx_start_all_queues(tmp_vif->ndev);
|
|
netif_carrier_on(tmp_vif->ndev);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
error = 0;
|
|
#ifdef CONFIG_MIFI_LOWPOWER
|
|
//Send ProbeResp template to FW for MIFI lowpoer
|
|
ssv_gen_probe_resp_template(settings, &ssv_vif->ap.bcn, buf);
|
|
ssv_gen_fake_mgmt_tx_params();
|
|
|
|
if((error = ssv_start_mgmt_xmit(ssv_vif, NULL, &fake_mgmt_tx_params, 0, &cookie)))
|
|
{
|
|
SSV_LOG_DBG("send probe_resp error\n");
|
|
}
|
|
|
|
if(fake_mgmt_tx_params.buf)
|
|
{
|
|
kfree(fake_mgmt_tx_params.buf);
|
|
fake_mgmt_tx_params.buf = NULL;
|
|
}
|
|
#endif
|
|
|
|
break;
|
|
}
|
|
case CO_BUSY:
|
|
error = -EINPROGRESS;
|
|
break;
|
|
case CO_OP_IN_PROGRESS:
|
|
error = -EALREADY;
|
|
break;
|
|
default:
|
|
error = -EIO;
|
|
break;
|
|
}
|
|
|
|
if (error)
|
|
{
|
|
netdev_info(dev, "Failed to start AP (%d)", error);
|
|
}
|
|
else
|
|
{
|
|
netdev_info(dev, "AP started: ch=%d, bcmc_idx=%d",
|
|
ssv_vif->ch_index, ssv_vif->ap.bcmc_index);
|
|
|
|
/* Retrieve the cipher suite selector */
|
|
switch (settings->crypto.cipher_group) {
|
|
case WLAN_CIPHER_SUITE_WEP40:
|
|
SSV_LOG_DBG("WLAN_CIPHER_SUITE_WEP40\n");
|
|
break;
|
|
case WLAN_CIPHER_SUITE_WEP104:
|
|
SSV_LOG_DBG("WLAN_CIPHER_SUITE_WEP104\n");
|
|
break;
|
|
case WLAN_CIPHER_SUITE_TKIP:
|
|
SSV_LOG_DBG("WPA%d WLAN_CIPHER_SUITE_TKIP\n", settings->crypto.wpa_versions);
|
|
break;
|
|
case WLAN_CIPHER_SUITE_CCMP:
|
|
SSV_LOG_DBG("WPA%d WLAN_CIPHER_SUITE_CCMP\n", settings->crypto.wpa_versions);
|
|
break;
|
|
case WLAN_CIPHER_SUITE_AES_CMAC:
|
|
SSV_LOG_DBG("WLAN_CIPHER_SUITE_AES_CMAC\n");
|
|
break;
|
|
case WLAN_CIPHER_SUITE_SMS4:
|
|
SSV_LOG_DBG("WLAN_CIPHER_SUITE_SMS4\n");
|
|
break;
|
|
default:
|
|
SSV_LOG_DBG("%s\n", settings->crypto.cipher_group == 0 ? "Open System":"Unknow type");
|
|
}
|
|
|
|
}
|
|
|
|
err:
|
|
// Free the buffer used to build the beacon
|
|
if (buf)
|
|
kfree(buf);
|
|
|
|
return error;
|
|
}
|
|
|
|
|
|
/**
|
|
* @change_beacon: Change the beacon parameters for an access point mode
|
|
* interface. This should reject the call when AP mode wasn't started.
|
|
*/
|
|
static int ssv_cfg80211_change_beacon(struct wiphy *wiphy, struct net_device *dev,
|
|
struct cfg80211_beacon_data *info)
|
|
{
|
|
struct ssv_softc *sc = wiphy_priv(wiphy);
|
|
struct ssv_vif *vif = netdev_priv(dev);
|
|
struct ssv_bcn *bcn = &vif->ap.bcn;
|
|
u8 *buf;
|
|
int error = 0;
|
|
|
|
SSV_LOG_DBG("[%s][%d]\n", __FUNCTION__, __LINE__);
|
|
|
|
// Build the beacon
|
|
buf = _ssv_build_bcn(bcn, info);
|
|
if (!buf)
|
|
return -ENOMEM;
|
|
|
|
// Forward the information to the LMAC
|
|
error = ssv_send_bcn_change(sc, vif->drv_vif_index, buf,
|
|
bcn->len, bcn->head_len, bcn->tim_len, NULL);
|
|
|
|
kfree(buf);
|
|
|
|
return error;
|
|
}
|
|
|
|
/**
|
|
* * @stop_ap: Stop being an AP, including stopping beaconing.
|
|
*/
|
|
static int ssv_cfg80211_stop_ap(struct wiphy *wiphy, struct net_device *dev)
|
|
{
|
|
struct ssv_softc *sc = wiphy_priv(wiphy);
|
|
struct ssv_vif *ssv_vif = netdev_priv(dev);
|
|
struct ssv_sta *sta;
|
|
int mgmt_txq;
|
|
struct ssv_sta *cur, *tmp;
|
|
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 19, 0)
|
|
struct station_del_parameters params;
|
|
params.mac = NULL;
|
|
#endif
|
|
|
|
SSV_LOG_DBG("[%s][%d]\n", __FUNCTION__, __LINE__);
|
|
sc->is_stoping_apm=true;
|
|
|
|
{ // turn off traffic for use_monitor interface
|
|
struct ssv_vif *tmp_vif = NULL;
|
|
|
|
list_for_each_entry(tmp_vif, &sc->vifs, list) {
|
|
if (tmp_vif->use_monitor) {
|
|
netif_tx_stop_all_queues(tmp_vif->ndev);
|
|
netif_carrier_off(tmp_vif->ndev);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
netif_tx_stop_all_queues(dev);
|
|
netif_carrier_off(dev);
|
|
|
|
mgmt_txq = (0 == ssv_vif->drv_vif_index) ? SSV_SW_TXQ_ID_MNG0 : SSV_SW_TXQ_ID_MNG1;
|
|
ssv_drv_hci_tx_pause_by_sta(sc->hci_priv, sc->hci_ops, mgmt_txq); //pause mgmt txq
|
|
ssv_drv_hci_tx_inactive_by_sta(sc->hci_priv, sc->hci_ops, mgmt_txq); //inactive mgmt txq
|
|
|
|
list_for_each_entry_safe(cur, tmp, &ssv_vif->ap.sta_list, list) {
|
|
ssv_drv_hci_tx_pause_by_sta(sc->hci_priv, sc->hci_ops, cur->sta_idx); //pause hci tx queue by sta
|
|
ssv_drv_hci_tx_inactive_by_sta(sc->hci_priv, sc->hci_ops, cur->sta_idx); //inactive hci tx queue by sta
|
|
}
|
|
|
|
msleep(200);
|
|
#if 0
|
|
ssv_send_apm_stop_req(sc, ssv_vif);
|
|
mutex_lock(&sc->cb_lock);
|
|
ssv_chanctx_unlink(ssv_vif);
|
|
mutex_unlock(&sc->cb_lock);
|
|
#endif
|
|
|
|
#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 8, 0)
|
|
ssv_vif->bchan_setting = false;
|
|
#endif
|
|
|
|
/* delete any remaining STA*/
|
|
while (!list_empty(&ssv_vif->ap.sta_list))
|
|
{
|
|
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 19, 0)
|
|
ssv_cfg80211_del_station(wiphy, dev, ¶ms);
|
|
#else
|
|
ssv_cfg80211_del_station(wiphy, dev, NULL);
|
|
#endif
|
|
}
|
|
#if 1
|
|
//give more times to cousmer the all tx packets
|
|
//msleep(100);
|
|
|
|
ssv_send_apm_stop_req(sc, ssv_vif);
|
|
mutex_lock(&sc->cb_lock);
|
|
ssv_chanctx_unlink(ssv_vif);
|
|
mutex_unlock(&sc->cb_lock);
|
|
#endif
|
|
/* delete BC/MC STA */
|
|
sta = &sc->sta_table[ssv_vif->ap.bcmc_index];
|
|
ssv_del_bcn(&ssv_vif->ap.bcn);
|
|
ssv_del_csa(ssv_vif);
|
|
|
|
|
|
ssv_drv_hci_tx_active_by_sta(sc->hci_priv, sc->hci_ops, mgmt_txq); //active hci mng. queue
|
|
ssv_drv_hci_tx_resume_by_sta(sc->hci_priv, sc->hci_ops, mgmt_txq); //resume hci mng. queue
|
|
SSV_LOG_DBG(KERN_ERR"AP stop!!\r\n");
|
|
sc->is_stoping_apm=false;
|
|
return 0;
|
|
}
|
|
|
|
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 6, 0)
|
|
static struct ssv_vif *ssv_get_sc_to_vif(struct ssv_softc *sc)
|
|
{
|
|
struct ssv_vif *ssv_vif;
|
|
|
|
ssv_vif = list_first_or_null_rcu(&sc->vifs, typeof(*ssv_vif), list);
|
|
if (!ssv_vif)
|
|
return ERR_PTR(-EINVAL);
|
|
|
|
return ssv_vif;
|
|
}
|
|
#endif
|
|
|
|
/**
|
|
* @set_monitor_channel: Set the monitor mode channel for the device. If other
|
|
* interfaces are active this callback should reject the configuration.
|
|
* If no interfaces are active or the device is down, the channel should
|
|
* be stored for when a monitor interface becomes active.
|
|
*/
|
|
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 6, 0)
|
|
static int ssv_cfg80211_set_monitor_channel(struct wiphy *wiphy,
|
|
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 8, 0)
|
|
struct cfg80211_chan_def *chandef
|
|
#else
|
|
struct ieee80211_channel *chan,
|
|
enum nl80211_channel_type channel_type
|
|
#endif
|
|
)
|
|
{
|
|
struct ssv_softc *sc = wiphy_priv(wiphy);
|
|
struct ssv_vif *ssv_vif = ssv_get_sc_to_vif(sc);
|
|
struct me_config_monitor_cfm cfm;
|
|
int ret = 0;
|
|
bool chan_set = true;
|
|
bool uf = false;
|
|
|
|
// SSV_LOG_DBG("[%s][%d]\n", __FUNCTION__, __LINE__);
|
|
#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 8, 0)
|
|
struct cfg80211_chan_def chandef_tmp, *chandef;
|
|
|
|
chandef_tmp.chan = chan;
|
|
chandef = &chandef_tmp;
|
|
#endif
|
|
|
|
if (IS_ERR(ssv_vif)) {
|
|
return PTR_ERR(ssv_vif);
|
|
}
|
|
|
|
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 8, 0)
|
|
if (cfg80211_chandef_identical(&sc->monitor_chandef, chandef))
|
|
#else
|
|
if (sc->monitor_chandef.chan == chan)
|
|
#endif
|
|
{
|
|
goto END;
|
|
}
|
|
|
|
if (0 != (ret = ssv_send_me_config_monitor_req(sc, chandef, chan_set, uf, &cfm)))
|
|
{
|
|
goto END;
|
|
}
|
|
sc->monitor_chandef = *chandef;
|
|
ssv_chanctx_unlink(ssv_vif);
|
|
ssv_chanctx_link(ssv_vif, cfm.chan_index, chandef);
|
|
|
|
END:
|
|
return ret;
|
|
}
|
|
#endif
|
|
|
|
/**
|
|
* @probe_client: probe an associated client, must return a cookie that it
|
|
* later passes to cfg80211_probe_status().
|
|
*/
|
|
int ssv_cfg80211_probe_client(struct wiphy *wiphy, struct net_device *dev,
|
|
const u8 *peer, u64 *cookie)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 8, 0)
|
|
/**
|
|
* @update_mgmt_frame_registrations: Notify the driver that management frame
|
|
* registrations were updated. The callback is allowed to sleep.
|
|
*/
|
|
void ssv_cfg80211_update_mgmt_frame_registrations(struct wiphy *wiphy,
|
|
struct wireless_dev *wdev,
|
|
struct mgmt_frame_regs *upd)
|
|
{
|
|
// SSV_LOG_DBG("[%s][%d]\n", __FUNCTION__, __LINE__);
|
|
}
|
|
#else
|
|
/**
|
|
* @mgmt_frame_register: Notify driver that a management frame type was
|
|
* registered. Note that this callback may not sleep, and cannot run
|
|
* concurrently with itself.
|
|
*/
|
|
void ssv_cfg80211_mgmt_frame_register(struct wiphy *wiphy,
|
|
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 6, 0)
|
|
struct wireless_dev *wdev,
|
|
#else
|
|
struct net_device *dev,
|
|
#endif
|
|
u16 frame_type, bool reg)
|
|
{
|
|
// SSV_LOG_DBG("[%s][%d]\n", __FUNCTION__, __LINE__);
|
|
}
|
|
#endif
|
|
|
|
/**
|
|
* @set_wiphy_params: Notify that wiphy parameters have changed;
|
|
* @changed bitfield (see &enum wiphy_params_flags) describes which values
|
|
* have changed. The actual parameter values are available in
|
|
* struct wiphy. If returning an error, no value should be changed.
|
|
*/
|
|
static int ssv_cfg80211_set_wiphy_params(struct wiphy *wiphy, u32 changed)
|
|
{
|
|
SSV_LOG_DBG("[%s][%d]\n", __FUNCTION__, __LINE__);
|
|
return 0;
|
|
}
|
|
|
|
|
|
/**
|
|
* @set_tx_power: set the transmit power according to the parameters,
|
|
* the power passed is in mBm, to get dBm use MBM_TO_DBM(). The
|
|
* wdev may be %NULL if power was set for the wiphy, and will
|
|
* always be %NULL unless the driver supports per-vif TX power
|
|
* (as advertised by the nl80211 feature flag.)
|
|
*/
|
|
static int ssv_cfg80211_set_tx_power(struct wiphy *wiphy,
|
|
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 8, 0)
|
|
struct wireless_dev *wdev,
|
|
#endif
|
|
enum nl80211_tx_power_setting type, int mbm)
|
|
{
|
|
struct ssv_softc *sc = wiphy_priv(wiphy);
|
|
struct ssv_vif *vif;
|
|
s8 pwr;
|
|
int res = 0;
|
|
|
|
SSV_LOG_DBG("[%s][%d]\n", __FUNCTION__, __LINE__);
|
|
|
|
if (type == NL80211_TX_POWER_AUTOMATIC) {
|
|
pwr = 0x7f;
|
|
} else {
|
|
pwr = MBM_TO_DBM(mbm);
|
|
}
|
|
|
|
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 8, 0)
|
|
if (wdev) {
|
|
vif = container_of(wdev, struct ssv_vif, wdev);
|
|
res = ssv_send_set_power(sc, vif->drv_vif_index, pwr, NULL);
|
|
} else {
|
|
#endif
|
|
list_for_each_entry(vif, &sc->vifs, list) {
|
|
res = ssv_send_set_power(sc, vif->drv_vif_index, pwr, NULL);
|
|
if (res)
|
|
break;
|
|
}
|
|
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 8, 0)
|
|
}
|
|
#endif
|
|
|
|
return res;
|
|
}
|
|
|
|
static int ssv_cfg80211_set_txq_params(struct wiphy *wiphy, struct net_device *dev,
|
|
struct ieee80211_txq_params *params)
|
|
{
|
|
struct ssv_softc *sc = wiphy_priv(wiphy);
|
|
struct ssv_vif *ssv_vif = netdev_priv(dev);
|
|
u8 hw_queue, aifs, cwmin, cwmax;
|
|
u32 param;
|
|
|
|
SSV_LOG_DBG("[%s][%d]\n", __FUNCTION__, __LINE__);
|
|
|
|
// set wmm param
|
|
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 5, 0)
|
|
hw_queue = ssv_ac2hwq[0][params->ac];
|
|
#else
|
|
hw_queue = ssv_ac2hwq[0][params->queue];
|
|
#endif
|
|
|
|
aifs = params->aifs;
|
|
cwmin = fls(params->cwmin);
|
|
cwmax = fls(params->cwmax);
|
|
|
|
/* Store queue information in general structure */
|
|
param = (u32) (aifs << 0);
|
|
param |= (u32) (cwmin << 4);
|
|
param |= (u32) (cwmax << 8);
|
|
param |= (u32) (params->txop) << 12;
|
|
|
|
if (0 == ssv_cfg.wmm_follow_vo) {
|
|
/* Send the MM_SET_EDCA_REQ message to the FW */
|
|
return ssv_send_set_edca(sc, hw_queue, param, false, ssv_vif->drv_vif_index);
|
|
} else {
|
|
int i = 0;
|
|
|
|
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 5, 0)
|
|
if (NL80211_AC_VO != params->ac)
|
|
#else
|
|
if (NL80211_TXQ_Q_VO != params->queue)
|
|
#endif
|
|
{
|
|
return 0;
|
|
} else {
|
|
SSV_LOG_DBG("Set all txq param follow vo\n");
|
|
for (i = 0; i < NL80211_NUM_ACS; i++) {
|
|
hw_queue = ssv_ac2hwq[0][i];
|
|
|
|
/* Send the MM_SET_EDCA_REQ message to the FW */
|
|
if (0 != ssv_send_set_edca(sc, hw_queue, param, false, ssv_vif->drv_vif_index))
|
|
SSV_LOG_DBG("Fail to send wmm msg\n");
|
|
|
|
}
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
|
|
/**
|
|
* @remain_on_channel: Request the driver to remain awake on the specified
|
|
* channel for the specified duration to complete an off-channel
|
|
* operation (e.g., public action frame exchange). When the driver is
|
|
* ready on the requested channel, it must indicate this with an event
|
|
* notification by calling cfg80211_ready_on_channel().
|
|
*/
|
|
static int
|
|
ssv_cfg80211_remain_on_channel(struct wiphy *wiphy,
|
|
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 6, 0)
|
|
struct wireless_dev *wdev,
|
|
#else
|
|
struct net_device *dev,
|
|
#endif
|
|
struct ieee80211_channel *chan,
|
|
#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 8, 0)
|
|
enum nl80211_channel_type channel_type,
|
|
#endif
|
|
unsigned int duration, u64 *cookie)
|
|
{
|
|
struct ssv_softc *sc = wiphy_priv(wiphy);
|
|
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 6, 0)
|
|
struct ssv_vif *ssv_vif = netdev_priv(wdev->netdev);
|
|
#else
|
|
struct ssv_vif *ssv_vif = netdev_priv(dev);
|
|
#endif
|
|
struct ssv_roc_elem *roc_elem;
|
|
int error;
|
|
|
|
SSV_LOG_DBG("[%s][%d]\n", __FUNCTION__, __LINE__);
|
|
|
|
/* Blocked until no other RoC procedure is on-going */
|
|
while (sc->roc_elem) {
|
|
SSV_LOG_DBG("The last RoC procedure is still on-going\n");
|
|
msleep(10);
|
|
}
|
|
|
|
/* Allocate a temporary RoC element */
|
|
roc_elem = kmalloc(sizeof(struct ssv_roc_elem), GFP_KERNEL);
|
|
|
|
/* Verify that element has well been allocated */
|
|
if (!roc_elem) {
|
|
SSV_LOG_DBG("%s() roc_elem alloc failed\n", __FUNCTION__);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
/* Initialize the RoC information element */
|
|
roc_elem->wdev = &ssv_vif->wdev;
|
|
roc_elem->chan = chan;
|
|
roc_elem->duration = duration;
|
|
roc_elem->mgmt_roc = false;
|
|
roc_elem->on_chan = false;
|
|
|
|
sc->roc_elem = roc_elem;
|
|
/* Set the cookie value */
|
|
*cookie = (u64)(sc->roc_cookie_cnt);
|
|
|
|
/* Forward the information to the FMAC */
|
|
error = ssv_send_roc(sc, ssv_vif, chan, duration);
|
|
|
|
/* If no error, keep all the information for handling of end of procedure */
|
|
if (0 != error) {
|
|
/* Free the allocated element */
|
|
kfree(roc_elem);
|
|
sc->roc_elem = NULL;
|
|
SSV_LOG_DBG("%s() roc_elem send failed, error %d\n", __FUNCTION__, error);
|
|
}
|
|
|
|
return error;
|
|
}
|
|
|
|
/**
|
|
* @cancel_remain_on_channel: Cancel an on-going remain-on-channel operation.
|
|
* This allows the operation to be terminated prior to timeout based on
|
|
* the duration value.
|
|
*/
|
|
static int ssv_cfg80211_cancel_remain_on_channel(struct wiphy *wiphy,
|
|
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 6, 0)
|
|
struct wireless_dev *wdev,
|
|
#else
|
|
struct net_device *dev,
|
|
#endif
|
|
u64 cookie)
|
|
{
|
|
struct ssv_softc *sc = wiphy_priv(wiphy);
|
|
int error;
|
|
|
|
SSV_LOG_DBG("[%s][%d]\n", __FUNCTION__, __LINE__);
|
|
|
|
/* Check if a RoC procedure is pending */
|
|
if (!sc->roc_elem) {
|
|
return 0;
|
|
}
|
|
|
|
/* Forward the information to the FMAC */
|
|
error = ssv_send_cancel_roc(sc);
|
|
|
|
return error;
|
|
}
|
|
|
|
/**
|
|
* @dump_survey: get site survey information.
|
|
*/
|
|
static int ssv_cfg80211_dump_survey(struct wiphy *wiphy, struct net_device *netdev,
|
|
int idx, struct survey_info *info)
|
|
{
|
|
struct ssv_softc *sc = wiphy_priv(wiphy);
|
|
struct ieee80211_supported_band *sband;
|
|
struct ssv_survey_info *ssv_survey;
|
|
|
|
if (idx >= ARRAY_SIZE(sc->survey))
|
|
return -ENOENT;
|
|
|
|
ssv_survey = &sc->survey[idx];
|
|
|
|
// Check if provided index matches with a supported 2.4GHz channel
|
|
sband = wiphy->bands[NL80211_BAND_2GHZ];
|
|
if (sband && idx >= sband->n_channels) {
|
|
idx -= sband->n_channels;
|
|
sband = NULL;
|
|
return -ENOENT;
|
|
}
|
|
|
|
// Fill the survey
|
|
info->channel = &sband->channels[idx];
|
|
info->filled = ssv_survey->filled;
|
|
|
|
if (ssv_survey->filled != 0) {
|
|
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 0, 0)
|
|
info->time = (u64)ssv_survey->chan_time_ms;
|
|
info->time_busy = (u64)ssv_survey->chan_time_busy_ms;
|
|
#else
|
|
info->channel_time = (u64)ssv_survey->chan_time_ms;
|
|
info->channel_time_busy = (u64)ssv_survey->chan_time_busy_ms;
|
|
#endif
|
|
info->noise = ssv_survey->noise_dbm;
|
|
|
|
// Set the survey report as not used
|
|
ssv_survey->filled = 0;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* @set_channel: Set the current operating channel for the virtual interface.
|
|
*
|
|
*/
|
|
#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 8, 0)
|
|
static int ssv_cfg80211_set_channel(struct wiphy *wiphy, struct net_device *dev,
|
|
struct ieee80211_channel *chan,
|
|
enum nl80211_channel_type channel_type)
|
|
{
|
|
struct ssv_vif *ssv_vif = netdev_priv(dev);
|
|
int error = 0;
|
|
|
|
SSV_LOG_DBG("[%s][%d] freq %d type %d \n", __FUNCTION__, __LINE__, chan->center_freq, channel_type);
|
|
/* For kernel < 3.8, cfg80211 operation start_ap doesn't contain channel context.
|
|
* CFG80211 will pass channel setting by this operation.
|
|
* So, we must store the setting.
|
|
* When start_ap operation is called, it can get channel param.
|
|
*/
|
|
if (SSV_VIF_TYPE(ssv_vif)) {
|
|
memcpy(&ssv_vif->chan_setting, chan, sizeof(struct ieee80211_channel));
|
|
ssv_vif->chan_type_setting = channel_type;
|
|
ssv_vif->bchan_setting = true;
|
|
}
|
|
|
|
|
|
return error;
|
|
}
|
|
#endif
|
|
|
|
#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 6, 0)
|
|
static struct ssv_vif *ssv_get_first_up_vif(struct ssv_softc *sc)
|
|
{
|
|
struct ssv_vif *ssv_vif = NULL, *tmp_vif = NULL;
|
|
|
|
list_for_each_entry(tmp_vif, &sc->vifs, list) {
|
|
if (tmp_vif->up) {
|
|
ssv_vif = tmp_vif;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return ssv_vif;
|
|
}
|
|
#endif
|
|
|
|
/**
|
|
* @get_channel: Get the current operating channel for the virtual interface.
|
|
* For monitor interfaces, it should return %NULL unless there's a single
|
|
* current monitoring channel.
|
|
*/
|
|
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 8, 0)
|
|
static int ssv_cfg80211_get_channel(struct wiphy *wiphy,
|
|
struct wireless_dev *wdev,
|
|
struct cfg80211_chan_def *chandef)
|
|
#elif LINUX_VERSION_CODE >= KERNEL_VERSION(3, 6, 0)
|
|
static struct ieee80211_channel *ssv_cfg80211_get_channel(struct wiphy *wiphy,
|
|
struct wireless_dev *wdev,
|
|
enum nl80211_channel_type *type)
|
|
#elif LINUX_VERSION_CODE >= KERNEL_VERSION(3, 5, 0)
|
|
static struct ieee80211_channel *ssv_cfg80211_get_channel(struct wiphy *wiphy,
|
|
enum nl80211_channel_type *type)
|
|
#else
|
|
static struct ieee80211_channel *ssv_cfg80211_get_channel(struct wiphy *wiphy)
|
|
#endif
|
|
{
|
|
struct ssv_softc *sc = wiphy_priv(wiphy);
|
|
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 6, 0)
|
|
struct ssv_vif *ssv_vif = container_of(wdev, struct ssv_vif, wdev);
|
|
#else
|
|
struct ssv_vif *ssv_vif = ssv_get_first_up_vif(sc);
|
|
#endif
|
|
struct ssv_chanctx *ctxt;
|
|
//SSV_LOG_DBG("[%s][%d]\n", __FUNCTION__, __LINE__);
|
|
|
|
if ((ssv_vif == NULL) ||
|
|
!ssv_vif->up ||
|
|
!ssv_chanctx_valid(sc, ssv_vif->ch_index)) {
|
|
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 8, 0)
|
|
return -ENODATA;
|
|
#else
|
|
return NULL;
|
|
#endif
|
|
}
|
|
|
|
ctxt = &sc->chanctx_table[ssv_vif->ch_index];
|
|
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 8, 0)
|
|
*chandef = ctxt->chan_def;
|
|
|
|
return 0;
|
|
#else
|
|
return ctxt->chan_def.chan;
|
|
#endif
|
|
}
|
|
|
|
/*
|
|
* @brief Retrieve the ssv_sta object allocated for a given MAC address
|
|
* and a given role.
|
|
*/
|
|
static struct ssv_sta *ssv_retrieve_sta(struct ssv_softc *sc,
|
|
struct ssv_vif *ssv_vif, u8 *addr,
|
|
__le16 fc, bool ap)
|
|
{
|
|
if (ap)
|
|
{
|
|
/* only deauth, disassoc and action are bufferable MMPDUs */
|
|
bool bufferable = ieee80211_is_deauth(fc) ||
|
|
ieee80211_is_disassoc(fc) ||
|
|
ieee80211_is_action(fc);
|
|
|
|
/* Check if the packet is bufferable or not */
|
|
if (bufferable)
|
|
{
|
|
/* Check if address is a broadcast or a multicast address */
|
|
if (is_broadcast_ether_addr(addr) || is_multicast_ether_addr(addr))
|
|
{
|
|
/* Returned STA pointer */
|
|
struct ssv_sta *ssv_sta = &sc->sta_table[ssv_vif->ap.bcmc_index];
|
|
|
|
if (ssv_sta->valid)
|
|
return ssv_sta;
|
|
}
|
|
else
|
|
{
|
|
/* Returned STA pointer */
|
|
struct ssv_sta *ssv_sta;
|
|
|
|
/* Go through list of STAs linked with the provided VIF */
|
|
list_for_each_entry(ssv_sta, &ssv_vif->ap.sta_list, list)
|
|
{
|
|
if (ssv_sta->valid &&
|
|
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 8, 0)
|
|
ether_addr_equal(ssv_sta->mac_addr, addr)
|
|
#else
|
|
!compare_ether_addr(ssv_sta->mac_addr, addr)
|
|
#endif
|
|
) {
|
|
/* Return the found STA */
|
|
return ssv_sta;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
return ssv_vif->sta.ap;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* @mgmt_tx: Transmit a management frame.
|
|
*/
|
|
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 14, 0)
|
|
static int ssv_cfg80211_mgmt_tx(struct wiphy *wiphy, struct wireless_dev *wdev,
|
|
struct cfg80211_mgmt_tx_params *params,
|
|
u64 *cookie)
|
|
#elif LINUX_VERSION_CODE >= KERNEL_VERSION(3, 8, 0)
|
|
static int ssv_cfg80211_mgmt_tx(struct wiphy *wiphy, struct wireless_dev *wdev,
|
|
struct ieee80211_channel *chan, bool offchan,
|
|
unsigned int wait, const u8 *buf, size_t len,
|
|
bool no_cck, bool dont_wait_for_ack, u64 *cookie)
|
|
#elif LINUX_VERSION_CODE >= KERNEL_VERSION(3, 6, 0)
|
|
static int ssv_cfg80211_mgmt_tx(struct wiphy *wiphy, struct wireless_dev *wdev,
|
|
struct ieee80211_channel *chan, bool offchan,
|
|
enum nl80211_channel_type channel_type,
|
|
bool channel_type_valid,
|
|
unsigned int wait, const u8 *buf, size_t len,
|
|
bool no_cck, bool dont_wait_for_ack, u64 *cookie)
|
|
#else
|
|
static int ssv_cfg80211_mgmt_tx(struct wiphy *wiphy, struct net_device *dev,
|
|
struct ieee80211_channel *chan, bool offchan,
|
|
enum nl80211_channel_type channel_type,
|
|
bool channel_type_valid,
|
|
unsigned int wait, const u8 *buf, size_t len,
|
|
bool no_cck, bool dont_wait_for_ack, u64 *cookie)
|
|
#endif
|
|
{
|
|
struct ssv_softc *sc = wiphy_priv(wiphy);
|
|
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 6, 0)
|
|
struct ssv_vif *ssv_vif = netdev_priv(wdev->netdev);
|
|
#else
|
|
struct ssv_vif *ssv_vif = netdev_priv(dev);
|
|
#endif
|
|
struct ssv_sta *ssv_sta;
|
|
struct ssv_mgmt_tx_params ssv_params = {0};
|
|
// struct ieee80211_mgmt *mgmt = (void *)params->buf;
|
|
struct ieee80211_mgmt *mgmt = NULL;
|
|
int error = 0;
|
|
bool ap = false;
|
|
|
|
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 14, 0)
|
|
ssv_params.chan = params->chan;
|
|
ssv_params.offchan = params->offchan;
|
|
ssv_params.wait = params->wait;
|
|
ssv_params.buf = params->buf;
|
|
ssv_params.len = params->len;
|
|
ssv_params.no_cck = params->no_cck;
|
|
ssv_params.dont_wait_for_ack = params->dont_wait_for_ack;
|
|
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 16, 0)
|
|
ssv_params.n_csa_offsets = params->n_csa_offsets;
|
|
ssv_params.csa_offsets = params->csa_offsets;
|
|
#endif
|
|
#else
|
|
ssv_params.chan = chan;
|
|
ssv_params.offchan = offchan;
|
|
ssv_params.wait = wait;
|
|
ssv_params.buf = buf;
|
|
ssv_params.len = len;
|
|
ssv_params.no_cck = no_cck;
|
|
ssv_params.dont_wait_for_ack = dont_wait_for_ack;
|
|
#endif
|
|
mgmt = (struct ieee80211_mgmt *)ssv_params.buf;
|
|
|
|
do
|
|
{
|
|
/* Check if provided VIF is an AP or a STA one */
|
|
switch (SSV_VIF_TYPE(ssv_vif))
|
|
{
|
|
case NL80211_IFTYPE_AP_VLAN:
|
|
ssv_vif = ssv_vif->ap_vlan.master;
|
|
ap = true;
|
|
break;
|
|
case NL80211_IFTYPE_AP:
|
|
case NL80211_IFTYPE_P2P_GO:
|
|
case NL80211_IFTYPE_MESH_POINT:
|
|
ap = true;
|
|
break;
|
|
case NL80211_IFTYPE_STATION:
|
|
case NL80211_IFTYPE_P2P_CLIENT:
|
|
default:
|
|
break;
|
|
}
|
|
|
|
/* Get STA on which management frame has to be sent */
|
|
ssv_sta = ssv_retrieve_sta(sc, ssv_vif, mgmt->da,
|
|
mgmt->frame_control, ap);
|
|
|
|
#if 0
|
|
/* For debug purpose (use ftrace kernel option) */
|
|
trace_mgmt_tx((params->chan) ? params->chan->center_freq : 0,
|
|
ssv_vif->drv_vif_index,
|
|
(ssv_sta) ? ssv_sta->sta_idx : 0xFF,
|
|
mgmt);
|
|
#endif
|
|
/* If AP, STA have to exist */
|
|
if (!ssv_sta)
|
|
{
|
|
if (!ap)
|
|
{
|
|
/* if no chan params, it should send frame to air.
|
|
* Otherwise, wpa_supplicant will get the fail reason.
|
|
* Ex: WPA3 */
|
|
if (!ssv_params.chan)
|
|
{
|
|
error = ssv_start_mgmt_xmit(ssv_vif, ssv_sta, &ssv_params, ssv_params.offchan, cookie);
|
|
return error;
|
|
}
|
|
|
|
/* Check that a RoC is already pending */
|
|
if (sc->roc_elem)
|
|
{
|
|
/* Get VIF used for current ROC */
|
|
struct ssv_vif *ssv_roc_vif = netdev_priv(sc->roc_elem->wdev->netdev);
|
|
|
|
/* Check if RoC channel is the same than the required one */
|
|
if ((sc->roc_elem->chan->center_freq != ssv_params.chan->center_freq) || (ssv_vif->drv_vif_index != ssv_roc_vif->drv_vif_index))
|
|
{
|
|
error = -EINVAL;
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
u64 cookie;
|
|
|
|
/* Start a ROC procedure for 30ms */
|
|
error = ssv_cfg80211_remain_on_channel(wiphy,
|
|
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 6, 0)
|
|
wdev,
|
|
#else
|
|
dev,
|
|
#endif
|
|
ssv_params.chan,
|
|
#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 8, 0)
|
|
NL80211_CHAN_NO_HT,
|
|
#endif
|
|
30, &cookie);
|
|
|
|
if (error)
|
|
{
|
|
break;
|
|
}
|
|
|
|
/*
|
|
* Need to keep in mind that RoC has been launched internally in order to
|
|
* avoid to call the cfg80211 callback once expired
|
|
*/
|
|
if (sc->roc_elem)
|
|
sc->roc_elem->mgmt_roc = true;
|
|
}
|
|
|
|
ssv_params.offchan = true;
|
|
}
|
|
}
|
|
|
|
/* Push the management frame on the TX path */
|
|
error = ssv_start_mgmt_xmit(ssv_vif, ssv_sta, &ssv_params, ssv_params.offchan, cookie);
|
|
} while (0);
|
|
|
|
return error;
|
|
}
|
|
|
|
/**
|
|
* @update_ft_ies: Provide updated Fast BSS Transition information to the
|
|
* driver. If the SME is in the driver/firmware, this information can be
|
|
* used in building Authentication and Reassociation Request frames.
|
|
*/
|
|
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 10, 0)
|
|
static
|
|
int ssv_cfg80211_update_ft_ies(struct wiphy *wiphy,
|
|
struct net_device *dev,
|
|
struct cfg80211_update_ft_ies_params *ftie)
|
|
{
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
/**
|
|
* @set_cqm_rssi_config: Configure connection quality monitor RSSI threshold.
|
|
*/
|
|
static
|
|
int ssv_cfg80211_set_cqm_rssi_config(struct wiphy *wiphy,
|
|
struct net_device *dev,
|
|
int32_t rssi_thold, uint32_t rssi_hyst)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* @change_bss: Modify parameters for a given BSS (mainly for AP mode).
|
|
*/
|
|
int ssv_cfg80211_change_bss(struct wiphy *wiphy, struct net_device *dev,
|
|
struct bss_parameters *params)
|
|
{
|
|
struct ssv_vif *ssv_vif = netdev_priv(dev);
|
|
int res = -EOPNOTSUPP;
|
|
SSV_LOG_DBG("[%s][%d]\n", __FUNCTION__, __LINE__);
|
|
|
|
if (((SSV_VIF_TYPE(ssv_vif) == NL80211_IFTYPE_AP) ||
|
|
(SSV_VIF_TYPE(ssv_vif) == NL80211_IFTYPE_P2P_GO)) &&
|
|
(params->ap_isolate > -1))
|
|
{
|
|
|
|
if (params->ap_isolate)
|
|
ssv_vif->ap.flags |= SSV_AP_ISOLATE;
|
|
else
|
|
ssv_vif->ap.flags &= ~SSV_AP_ISOLATE;
|
|
|
|
res = 0;
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @channel_switch: initiate channel-switch procedure (with CSA). Driver is
|
|
* responsible for veryfing if the switch is possible. Since this is
|
|
* inherently tricky driver may decide to disconnect an interface later
|
|
* with cfg80211_stop_iface(). This doesn't mean driver can accept
|
|
* everything. It should do it's best to verify requests and reject them
|
|
* as soon as possible.
|
|
*/
|
|
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 12, 0)
|
|
int ssv_cfg80211_channel_switch(struct wiphy *wiphy,
|
|
struct net_device *dev,
|
|
struct cfg80211_csa_settings *params)
|
|
{
|
|
#if 1
|
|
SSV_LOG_DBG("[%s][%d]\n", __FUNCTION__, __LINE__);
|
|
return 0;
|
|
#else
|
|
struct ssv_softc *sc = wiphy_priv(wiphy);
|
|
struct ssv_vif *vif = netdev_priv(dev);
|
|
struct ssv_dma_elem elem;
|
|
struct ssv_bcn *bcn, *bcn_after;
|
|
struct ssv_csa *csa;
|
|
u16 csa_oft[BCN_MAX_CSA_CPT];
|
|
u8 *buf;
|
|
int i, error = 0;
|
|
|
|
SSV_LOG_DBG("[%s][%d]\n", __FUNCTION__, __LINE__);
|
|
|
|
if (vif->ap.csa)
|
|
return -EBUSY;
|
|
|
|
if (params->n_counter_offsets_beacon > BCN_MAX_CSA_CPT)
|
|
return -EINVAL;
|
|
|
|
/* Build the new beacon with CSA IE */
|
|
bcn = &vif->ap.bcn;
|
|
buf = _ssv_build_bcn(bcn, ¶ms->beacon_csa);
|
|
if (!buf)
|
|
return -ENOMEM;
|
|
|
|
memset(csa_oft, 0, sizeof(csa_oft));
|
|
for (i = 0; i < params->n_counter_offsets_beacon; i++)
|
|
{
|
|
csa_oft[i] = params->counter_offsets_beacon[i] + bcn->head_len +
|
|
bcn->tim_len;
|
|
}
|
|
|
|
/* If count is set to 0 (i.e anytime after this beacon) force it to 2 */
|
|
if (params->count == 0)
|
|
{
|
|
params->count = 2;
|
|
for (i = 0; i < params->n_counter_offsets_beacon; i++)
|
|
{
|
|
buf[csa_oft[i]] = 2;
|
|
}
|
|
}
|
|
|
|
elem.buf = buf;
|
|
elem.len = bcn->len;
|
|
|
|
/* Build the beacon to use after CSA. It will only be sent to fw once
|
|
CSA is over. */
|
|
csa = kzalloc(sizeof(struct ssv_csa), GFP_KERNEL);
|
|
if (!csa)
|
|
{
|
|
error = -ENOMEM;
|
|
goto end;
|
|
}
|
|
|
|
bcn_after = &csa->bcn;
|
|
buf = _ssv_build_bcn(bcn_after, ¶ms->beacon_after);
|
|
if (!buf)
|
|
{
|
|
error = -ENOMEM;
|
|
ssv_del_csa(vif);
|
|
goto end;
|
|
}
|
|
|
|
vif->ap.csa = csa;
|
|
csa->vif = vif;
|
|
csa->chandef = params->chandef;
|
|
csa->dma.buf = buf;
|
|
csa->dma.len = bcn_after->len;
|
|
|
|
/* Send new Beacon. FW will extract channel and count from the beacon */
|
|
error = ssv_send_bcn_change(sc, vif->drv_vif_index, elem.buf,
|
|
bcn->len, bcn->head_len, bcn->tim_len, csa_oft);
|
|
|
|
if (error)
|
|
{
|
|
ssv_del_csa(vif);
|
|
goto end;
|
|
}
|
|
else
|
|
{
|
|
INIT_WORK(&csa->work, ssv_csa_finish);
|
|
cfg80211_ch_switch_started_notify(dev, &csa->chandef, params->count);
|
|
}
|
|
|
|
end:
|
|
kfree(elem.buf);
|
|
|
|
return error;
|
|
#endif
|
|
}
|
|
#endif //#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 12, 0)
|
|
|
|
/**
|
|
* @get_station: get station information.
|
|
*/
|
|
int ssv_cfg80211_get_station(struct wiphy *wiphy,
|
|
struct net_device *dev,
|
|
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 16, 0)
|
|
const u8 *mac,
|
|
#else
|
|
u8 *mac,
|
|
#endif
|
|
struct station_info *sinfo)
|
|
{
|
|
struct ssv_softc *sc = wiphy_priv(wiphy);
|
|
s8 tmp_averrssi = sc->rssiaccu / RSSI_MAX;
|
|
|
|
SSV_LOG_DBG("[%s][%d]\n", __FUNCTION__, __LINE__);
|
|
sinfo->signal = (s8)(tmp_averrssi);
|
|
|
|
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 0, 0)
|
|
sinfo->filled |= BIT(NL80211_STA_INFO_SIGNAL);
|
|
sinfo->filled |= BIT(NL80211_STA_INFO_INACTIVE_TIME);
|
|
#else
|
|
sinfo->filled |= (STATION_INFO_SIGNAL);
|
|
sinfo->filled |= (STATION_INFO_INACTIVE_TIME);
|
|
#endif
|
|
|
|
if (mac)
|
|
{
|
|
struct ssv_sta *sta = ssv_get_sta(sc, mac);
|
|
if (sta)
|
|
{
|
|
sinfo->inactive_time = jiffies_to_msecs(jiffies) - sta->last_rx;
|
|
|
|
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 0, 0)
|
|
sinfo->filled |= BIT(NL80211_STA_INFO_STA_FLAGS) |
|
|
BIT(NL80211_STA_INFO_BSS_PARAM) |
|
|
BIT(NL80211_STA_INFO_CONNECTED_TIME) |
|
|
BIT(NL80211_STA_INFO_RX_DROP_MISC) |
|
|
BIT(NL80211_STA_INFO_BEACON_LOSS);
|
|
#else
|
|
sinfo->filled |= (STATION_INFO_STA_FLAGS) |
|
|
(STATION_INFO_BSS_PARAM) |
|
|
(STATION_INFO_CONNECTED_TIME) |
|
|
(STATION_INFO_BEACON_LOSS_COUNT);
|
|
#endif
|
|
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 0, 0)
|
|
if (!(sinfo->filled & (BIT(NL80211_STA_INFO_TX_BYTES64) | BIT(NL80211_STA_INFO_TX_BYTES))))
|
|
{
|
|
sinfo->tx_bytes = sc->tx.tx_count*128; //fake fix me
|
|
sinfo->filled |= BIT(NL80211_STA_INFO_TX_BYTES64);
|
|
}
|
|
#else
|
|
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 9, 0)
|
|
if (!(sinfo->filled & ((STATION_INFO_TX_BYTES64) | (STATION_INFO_TX_BYTES))))
|
|
{
|
|
sinfo->tx_bytes = sc->tx.tx_count*128; //fake fix me
|
|
sinfo->filled |= STATION_INFO_TX_BYTES64;
|
|
}
|
|
#else
|
|
if (!(sinfo->filled & STATION_INFO_TX_BYTES))
|
|
{
|
|
sinfo->tx_bytes = sc->tx.tx_count*128; //fake fix me
|
|
sinfo->filled |= STATION_INFO_TX_BYTES;
|
|
}
|
|
#endif
|
|
#endif
|
|
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 0, 0)
|
|
if (!(sinfo->filled & BIT(NL80211_STA_INFO_TX_PACKETS)))
|
|
{
|
|
sinfo->tx_packets = sc->tx.tx_count;
|
|
sinfo->filled |= BIT(NL80211_STA_INFO_TX_PACKETS);
|
|
}
|
|
#else
|
|
if (!(sinfo->filled & STATION_INFO_TX_PACKETS))
|
|
{
|
|
sinfo->tx_packets = sc->tx.tx_count;
|
|
sinfo->filled |= STATION_INFO_TX_PACKETS;
|
|
}
|
|
#endif
|
|
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 0, 0)
|
|
if (!(sinfo->filled & (BIT(NL80211_STA_INFO_RX_BYTES64) | BIT(NL80211_STA_INFO_RX_BYTES))))
|
|
{
|
|
sinfo->rx_bytes = sc->rx.rx_count*512; //fake fix me
|
|
sinfo->filled |= BIT(NL80211_STA_INFO_RX_BYTES64);
|
|
}
|
|
#else
|
|
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 9, 0)
|
|
if (!(sinfo->filled & ((STATION_INFO_RX_BYTES64) | (STATION_INFO_RX_BYTES))))
|
|
{
|
|
sinfo->rx_bytes = sc->rx.rx_count*512; //fake fix me
|
|
sinfo->filled |= STATION_INFO_RX_BYTES64;
|
|
}
|
|
#else
|
|
if (!(sinfo->filled & STATION_INFO_RX_BYTES))
|
|
{
|
|
sinfo->rx_bytes = sc->rx.rx_count*512; //fake fix me
|
|
sinfo->filled |= STATION_INFO_RX_BYTES;
|
|
}
|
|
#endif
|
|
#endif
|
|
|
|
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 0, 0)
|
|
if (!(sinfo->filled & BIT(NL80211_STA_INFO_RX_PACKETS)))
|
|
{
|
|
sinfo->rx_packets = sc->rx.rx_count;
|
|
sinfo->filled |= BIT(NL80211_STA_INFO_RX_PACKETS);
|
|
}
|
|
#else
|
|
if (!(sinfo->filled & (STATION_INFO_RX_PACKETS)))
|
|
{
|
|
sinfo->rx_packets = sc->rx.rx_count;
|
|
sinfo->filled |= STATION_INFO_RX_PACKETS;
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
//SSV_LOG_DBG("inactive_time = %d ms\n", sinfo->inactive_time);
|
|
return 0;
|
|
}
|
|
|
|
void ssv_external_auth_enable(struct ssv_vif *vif)
|
|
{
|
|
vif->sta.flags |= SSV_STA_EXT_AUTH;
|
|
}
|
|
|
|
void ssv_external_auth_disable(struct ssv_vif *vif)
|
|
{
|
|
if (!(vif->sta.flags & SSV_STA_EXT_AUTH))
|
|
return;
|
|
|
|
vif->sta.flags &= ~SSV_STA_EXT_AUTH;
|
|
}
|
|
|
|
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 17, 0) || defined(CONFIG_SUPPORT_WPA3))
|
|
int ssv_cfg80211_external_auth(struct wiphy *wiphy, struct net_device *dev,
|
|
struct cfg80211_external_auth_params *params)
|
|
{
|
|
struct ssv_softc *sc = wiphy_priv(wiphy);
|
|
struct ssv_vif *ssv_vif = netdev_priv(dev);
|
|
|
|
#if defined(CONFIG_WPA_SUPPLICANT_CTL)
|
|
if(params->status == 0xFF)
|
|
return 0;
|
|
#endif
|
|
|
|
if (!(ssv_vif->sta.flags & SSV_STA_EXT_AUTH))
|
|
return -EINVAL;
|
|
|
|
ssv_external_auth_disable(ssv_vif);
|
|
return ssv_send_sm_external_auth_required_rsp(sc, ssv_vif, params->status);
|
|
}
|
|
#endif
|
|
|
|
struct cfg80211_ops ssv_cfg80211_ops = {
|
|
.add_virtual_intf = ssv_cfg80211_add_iface,
|
|
.del_virtual_intf = ssv_cfg80211_del_iface,
|
|
.change_virtual_intf = ssv_cfg80211_change_iface,
|
|
.scan = ssv_cfg80211_scan,
|
|
.connect = ssv_cfg80211_connect,
|
|
.disconnect = ssv_cfg80211_disconnect,
|
|
.add_key = ssv_cfg80211_add_key,
|
|
.get_key = ssv_cfg80211_get_key,
|
|
.del_key = ssv_cfg80211_del_key,
|
|
.set_default_key = ssv_cfg80211_set_default_key,
|
|
.set_default_mgmt_key = ssv_cfg80211_set_default_mgmt_key,
|
|
.add_station = ssv_cfg80211_add_station,
|
|
.del_station = ssv_cfg80211_del_station,
|
|
.change_station = ssv_cfg80211_change_station,
|
|
.mgmt_tx = ssv_cfg80211_mgmt_tx,
|
|
.start_ap = ssv_cfg80211_start_ap,
|
|
.change_beacon = ssv_cfg80211_change_beacon,
|
|
.stop_ap = ssv_cfg80211_stop_ap,
|
|
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 6, 0)
|
|
.set_monitor_channel = ssv_cfg80211_set_monitor_channel,
|
|
#endif
|
|
.probe_client = ssv_cfg80211_probe_client,
|
|
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 8, 0)
|
|
.update_mgmt_frame_registrations = ssv_cfg80211_update_mgmt_frame_registrations,
|
|
#else
|
|
.mgmt_frame_register = ssv_cfg80211_mgmt_frame_register,
|
|
#endif
|
|
.set_wiphy_params = ssv_cfg80211_set_wiphy_params,
|
|
.set_txq_params = ssv_cfg80211_set_txq_params,
|
|
.set_tx_power = ssv_cfg80211_set_tx_power,
|
|
.remain_on_channel = ssv_cfg80211_remain_on_channel,
|
|
.cancel_remain_on_channel = ssv_cfg80211_cancel_remain_on_channel,
|
|
.dump_survey = ssv_cfg80211_dump_survey,
|
|
#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 6, 0)
|
|
.set_channel = ssv_cfg80211_set_channel,
|
|
#endif
|
|
.get_channel = ssv_cfg80211_get_channel,
|
|
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 10, 0)
|
|
.update_ft_ies = ssv_cfg80211_update_ft_ies,
|
|
#endif
|
|
.set_cqm_rssi_config = ssv_cfg80211_set_cqm_rssi_config,
|
|
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 12, 0)
|
|
.channel_switch = ssv_cfg80211_channel_switch,
|
|
#endif
|
|
.change_bss = ssv_cfg80211_change_bss,
|
|
.get_station = ssv_cfg80211_get_station,
|
|
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 17, 0) || defined(CONFIG_SUPPORT_WPA3))
|
|
.external_auth = ssv_cfg80211_external_auth,
|
|
#endif
|
|
};
|
|
|
|
void ssv_wdev_unregister(struct ssv_softc *sc)
|
|
{
|
|
struct ssv_vif *ssv_vif, *tmp;
|
|
|
|
rtnl_lock();
|
|
list_for_each_entry_safe(ssv_vif, tmp, &sc->vifs, list) {
|
|
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 6, 0)
|
|
ssv_cfg80211_del_iface(sc->wiphy, &ssv_vif->wdev);
|
|
#else
|
|
ssv_cfg80211_del_iface(sc->wiphy, ssv_vif->ndev);
|
|
#endif
|
|
|
|
#ifdef FMAC_BRIDGE
|
|
br0_detach(ssv_vif);
|
|
#endif
|
|
}
|
|
rtnl_unlock();
|
|
}
|
|
|
|
#if (LINUX_VERSION_CODE < KERNEL_VERSION(3, 6, 0))
|
|
int ssv_stop_ap(struct wiphy *wiphy, struct net_device *dev)
|
|
{
|
|
int err;
|
|
|
|
err = ssv_cfg80211_stop_ap(wiphy, dev);
|
|
|
|
return err;
|
|
}
|
|
#endif
|