luckfox-pico-sdk/sysdrv/drv_ko/wifi/ssv6x5x/smac/hw_scan.c
2023-08-08 20:36:47 +08:00

422 lines
12 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/nl80211.h>
#include <net/mac80211.h>
#include <net/cfg80211.h>
#include <linux_80211.h>
#include <ssv6200.h>
#include <hal.h>
#include "dev.h"
#include "ssv_skb.h"
#include "hw_scan.h"
MODULE_AUTHOR("iComm-semi, Ltd");
MODULE_DESCRIPTION("Support for SSV6xxx wireless LAN cards.");
MODULE_SUPPORTED_DEVICE("SSV6xxx 802.11n WLAN cards");
MODULE_LICENSE("Dual BSD/GPL");
#define SSV_SCAN_MAX_TIME (12000)
#define SSV_SCAN_PASSIVE_CHANNEL_TIME (110)
#define SSV_SCAN_NON_ASSOCIATION_TIME (100)
void ssv6xxx_scan_complete(struct ssv_softc *sc, bool abort)
{
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4,8,0)
struct cfg80211_scan_info info = {
.aborted = abort,
};
#endif
// mutex_lock(&sc->mutex);
sc->hw_scan_start = false;
sc->bScanning = false;
sc->hw_scan_cancel = false;
/* set home channel & resume hci txq */
HAL_SET_CHANNEL(sc, sc->hw_scan_restore_channel, sc->hw_scan_restore_chan_type, false);
HCI_TX_RESUME_BY_STA(sc->sh, sc->scan_wsid);
/* cci control & beacon filter */
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
sc->hw_scan_done = true;
if (abort)
printk("HW scan aborted\n");
else
printk("HW scan complete\n");
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4,8,0)
ieee80211_scan_completed(sc->hw, &info);
#else
ieee80211_scan_completed(sc->hw, abort);
#endif
//mutex_unlock(&sc->mutex);
}
static int ssv6xxx_parse_duplicate_ie_elems(const u8 *start, int len)
{
size_t left = len;
const u8 *pos = start;
u8 first_id = *pos;
int ie_offset = 0;
while (left >= 2) {
u8 id, elen;
id = *pos++;
elen = *pos++;
left -= 2;
if (elen > left) {
return -1;
}
if ((id == first_id) && (left < (len - 2))) {
break;
}
left -= elen;
pos += elen;
ie_offset += (2+elen);
}
return ie_offset;
}
static void ssv6xxx_build_hw_scan_param(struct ssv_softc *sc, u8 *src_addr, int vif_idx, const u8 *bssid)
{
struct cfg80211_scan_request *req = sc->scan_req;
struct ssv_scan_param *scan_param = &sc->scan_param;
int i = 0;
memset(scan_param, 0x0, sizeof(struct ssv_scan_param));
memcpy(scan_param->src_addr, src_addr, ETH_ALEN);
memcpy(scan_param->bssid, bssid, ETH_ALEN);
if (req->n_ssids == 0) { // passive scan
sc->scan_is_passive = true;
scan_param->ssid_len = 0;
} else {
sc->scan_is_passive = false;
for(i=0;i<req->n_ssids;i++)
{
if (req->ssids[i].ssid_len==0)
{
continue;
}
scan_param->ssid_len = req->ssids[i].ssid_len;
memcpy(scan_param->ssid, req->ssids[i].ssid, req->ssids[i].ssid_len);
break;
}
}
#if LINUX_VERSION_CODE > KERNEL_VERSION(3,0,8)
scan_param->no_cck = req->no_cck;
#endif
if (req->ie_len > 512) {
printk("Incorrect scan ie len = %d\n", (int)req->ie_len);
WARN_ON(1);
}
scan_param->vif_idx = vif_idx;
scan_param->wsid = sc->scan_wsid;
if (sc->sh->cfg.hw_caps & SSV6200_HW_CAP_5GHZ) {
int ie_offset = ssv6xxx_parse_duplicate_ie_elems(req->ie, req->ie_len);
if (ie_offset > 0) {
sc->scan_ie_len_band2g = ie_offset;
memcpy(sc->scan_ie_band2g, req->ie, sc->scan_ie_len_band2g);
sc->scan_ie_len_band5g = req->ie_len - ie_offset;
memcpy(sc->scan_ie_band5g, (u8 *)req->ie + ie_offset, sc->scan_ie_len_band5g);
} else {
printk("ie_offset %d, use original ie\n", ie_offset);
sc->scan_ie_len_band2g = req->ie_len;
memcpy(sc->scan_ie_band2g, req->ie, req->ie_len);
sc->scan_ie_len_band5g = req->ie_len;
memcpy(sc->scan_ie_band5g, req->ie, req->ie_len);
}
} else {
sc->scan_ie_len_band2g = req->ie_len;
memcpy(sc->scan_ie_band2g, req->ie, req->ie_len);
}
}
static void ssv6xxx_scan_state_decision(struct ssv_softc *sc, unsigned long *next_delay)
{
bool associated = false;
u8 next_scan_state;
if (sc->hw_scan_cancel || time_after(jiffies, (sc->scan_time + sc->scan_wait_period))) {
sc->next_hw_scan_state = SSV_SCAN_ABORT;
*next_delay = 0;
return;
}
associated = sc->scan_vif->bss_conf.assoc;
if (associated)
next_scan_state = SSV_SCAN_SUSPEND;
else
next_scan_state = SSV_SCAN_RESUME;
sc->next_hw_scan_state = next_scan_state;
*next_delay = 0;
}
static void ssv6xxx_scan_state_suspend(struct ssv_softc *sc, unsigned long *next_delay)
{
u8 next_scan_state;
if (sc->hw_scan_cancel || time_after(jiffies, (sc->scan_time + sc->scan_wait_period))) {
sc->next_hw_scan_state = SSV_SCAN_ABORT;
*next_delay = 0;
return;
}
/* set home channel & resume hci txq */
HAL_SET_CHANNEL(sc, sc->hw_scan_restore_channel, sc->hw_scan_restore_chan_type, false);
HCI_TX_RESUME_BY_STA(sc->sh, sc->scan_wsid);
next_scan_state = SSV_SCAN_RESUME;
sc->next_hw_scan_state = next_scan_state;
*next_delay = msecs_to_jiffies(sc->sh->cfg.scan_hc_period);
}
static void ssv6xxx_scan_state_resume(struct ssv_softc *sc, unsigned long *next_delay)
{
u8 next_scan_state;
struct cfg80211_scan_request *req = sc->scan_req;
struct ieee80211_channel *chan;
if (sc->hw_scan_cancel || time_after(jiffies, (sc->scan_time + sc->scan_wait_period))) {
sc->next_hw_scan_state = SSV_SCAN_ABORT;
*next_delay = 0;
return;
}
#if 0
if (!ssv6200_not_dual_intf_on_line(sc)) {
printk("dual interface, don't set offchan\n");
sc->scan_channel_idx++;
sc->next_hw_scan_state = SSV_SCAN_DECISION;
*next_delay = 0;
return;
}
#endif
/* pause hci txq & set off channel */
HCI_TX_PAUSE_BY_STA(sc->sh, sc->scan_wsid);
chan = req->channels[sc->scan_channel_idx];
HAL_SET_CHANNEL(sc, chan, NL80211_CHAN_NO_HT, true);
sc->scan_channel_idx++;
next_scan_state = SSV_SCAN_DECISION;
sc->next_hw_scan_state = next_scan_state;
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3,14,0)
if (chan->flags & (IEEE80211_CHAN_NO_IR | IEEE80211_CHAN_RADAR)) {
#else
if (chan->flags & (IEEE80211_CHAN_RADAR)) {
#endif
*next_delay = msecs_to_jiffies(SSV_SCAN_PASSIVE_CHANNEL_TIME);
} else {
if (sc->scan_vif->bss_conf.assoc) {
*next_delay = msecs_to_jiffies(sc->sh->cfg.scan_oc_period);
} else {
*next_delay = msecs_to_jiffies(SSV_SCAN_NON_ASSOCIATION_TIME);
}
}
}
void ssv6xxx_scan_ignore_process(struct work_struct *work)
{
struct ssv_softc *sc = container_of(work, struct ssv_softc, scan_ignore_work.work);
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4,8,0)
struct cfg80211_scan_info info = {
.aborted = false,
};
#endif
printk("scan_ignore_process done\n");
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4,8,0)
ieee80211_scan_completed(sc->hw, &info);
#else
ieee80211_scan_completed(sc->hw, false);
#endif
}
void ssv6xxx_scan_process(struct work_struct *work)
{
struct ssv_softc *sc = container_of(work, struct ssv_softc, scan_work.work);
struct cfg80211_scan_request *req = sc->scan_req;
unsigned long next_delay = 0;
bool aborted = false;
do {
switch (sc->next_hw_scan_state) {
case SSV_SCAN_DECISION:
if (sc->scan_channel_idx >= req->n_channels) {
aborted = false;
goto out_complete;
}
ssv6xxx_scan_state_decision(sc, &next_delay);
break;
case SSV_SCAN_SUSPEND: // home channel
ssv6xxx_scan_state_suspend(sc, &next_delay);
break;
case SSV_SCAN_RESUME: // off channel
ssv6xxx_scan_state_resume(sc, &next_delay);
break;
case SSV_SCAN_ABORT:
goto out_complete;
}
} while (next_delay == 0);
queue_delayed_work(sc->scan_wq, &sc->scan_work, next_delay);
goto out;
out_complete:
ssv6xxx_scan_complete(sc, aborted);
out:
return;
}
#if 0
static bool ssv6xxx_scan_check_p2p_interface(struct ssv_softc *sc)
{
struct ieee80211_vif *vif;
int i = 0;
for (i = 0; i < SSV6200_MAX_VIF; i++) {
if (sc->vif_info[i].vif != NULL) {
vif = sc->vif_info[i].vif;
if (vif->p2p)
return true;
}
}
return false;
}
#endif
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 17, 0))
int ssv6200_hw_scan(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
struct ieee80211_scan_request *hw_req)
#else
int ssv6200_hw_scan(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
struct cfg80211_scan_request *hw_req)
#endif
{
struct ssv_softc *sc = hw->priv;
struct ssv_vif_priv_data *vif_priv = (struct ssv_vif_priv_data *)vif->drv_priv;
bool assoc = vif->bss_conf.assoc;
int i = 0;
mutex_lock(&sc->mutex);
printk("scan on vif: %pM\n", vif->addr);
if (sc->bScanning == true) {
printk("scan is running, ignore the scan request\n");
goto out;
}
if ((sc->sc_flags & SC_OP_BLOCK_CNTL) && (sc->sc_flags & SC_OP_CHAN_FIXED)) {
printk("rf tool, ignore the scan request\n");
queue_delayed_work(sc->scan_wq, &sc->scan_ignore_work, 0);
goto out;
}
#if 0
// if p2p interface up, ignore non-p2p scan request
if (true == ssv6xxx_scan_check_p2p_interface(sc)) {
if (false == vif->p2p) {
printk("p2p interface up, non-p2p scan request\n");
queue_delayed_work(sc->scan_wq, &sc->scan_ignore_work, 0);
goto out;
}
}
#endif
/* cci control & beacon filter */
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
/* build scan param */
sc->bScanning = true;
sc->hw_scan_start = true;
sc->hw_scan_cancel = false;
sc->hw_scan_done = false;
sc->hw_scan_restore_channel = sc->cur_channel;
sc->hw_scan_restore_chan_type = sc->hw_chan_type;
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 17, 0))
sc->scan_req = &hw_req->req;
#else
sc->scan_req = hw_req;
#endif
sc->scan_vif = vif;
sc->next_hw_scan_state = SSV_SCAN_DECISION;
sc->scan_channel_idx = 0;
sc->scan_wsid = 0xf;
if (assoc == true) {
// find station wsid
for (i = 0; i < SSV_NUM_STA; i++) {
if (sc->sta_info[i].s_flags & STA_FLAG_VALID) {
if (vif->bss_conf.aid == sc->sta_info[i].aid) {
sc->scan_wsid = i;
break;
}
}
}
}
sc->scan_wait_period = msecs_to_jiffies(SSV_SCAN_MAX_TIME);
sc->scan_time = jiffies;
ssv6xxx_build_hw_scan_param(sc, vif->addr, vif_priv->vif_idx, vif->bss_conf.bssid);
queue_delayed_work(sc->scan_wq, &sc->scan_work, 0);
out:
mutex_unlock(&sc->mutex);
return 0;
}
void ssv6xxx_cancel_hw_scan(struct ssv_softc *sc)
{
cancel_delayed_work_sync(&sc->scan_work);
sc->hw_scan_cancel = true;
ssv6xxx_scan_complete(sc, true);
}
void ssv6200_cancel_hw_scan(struct ieee80211_hw *hw, struct ieee80211_vif *vif)
{
struct ssv_softc *sc = hw->priv;
printk("cancel HW scan on vif: %pM\n", vif->addr);
mutex_lock(&sc->mutex);
ssv6xxx_cancel_hw_scan(sc);
mutex_unlock(&sc->mutex);
}