// SPDX-License-Identifier: GPL-2.0
|
|
/******************************************************************************
|
*
|
* Copyright (C) 2020 SeekWave Technology Co.,Ltd.
|
*
|
* This program is free software; you can redistribute it and/or modify
|
* it under the terms of version 2 of the GNU General Public License as
|
* published by the Free Software Foundation;
|
*
|
* This program is distributed in the hope that it will be useful,
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
* GNU General Public License for more details.
|
*
|
******************************************************************************/
|
|
#include <linux/skbuff.h>
|
|
#include "skw_core.h"
|
#include "skw_cfg80211.h"
|
#include "skw_iface.h"
|
#include "skw_msg.h"
|
#include "skw_iw.h"
|
#include "skw_calib.h"
|
#include "skw_recovery.h"
|
#include "skw_mlme.h"
|
#include "skw_rx.h"
|
#include "skw_tx.h"
|
|
static inline void
|
skw_recovery_sta_disconnect(struct net_device *ndev, u8 *addr)
|
{
|
struct skw_iface *iface = netdev_priv(ndev);
|
|
if (iface->sta.sme_external)
|
skw_tx_mlme_mgmt(ndev, IEEE80211_STYPE_DEAUTH, addr, addr, 3);
|
else
|
skw_disconnected(ndev, 3, NULL, 0, true, GFP_KERNEL);
|
}
|
|
static int skw_recovery_sta(struct wiphy *wiphy, struct skw_recovery_data *rd,
|
struct skw_iface *iface)
|
{
|
int ret;
|
u32 peer_map = rd->iface[iface->id].peer_map;
|
struct skw_sta_core *core = &iface->sta.core;
|
struct net_device *dev = iface->ndev;
|
|
#ifdef SKW_STATE_RECOVERY
|
// TODO:
|
// recovery peer state
|
|
struct cfg80211_bss *cbss;
|
|
cbss = cfg80211_get_bss(wiphy, core->bss.channel, core->bss.bssid,
|
core->bss.ssid, core->bss.ssid_len,
|
IEEE80211_BSS_TYPE_ANY, IEEE80211_PRIVACY_ANY);
|
if (!cbss) {
|
if (!skw_cmd_unjoin(wiphy, dev, peer->addr, 3, true))
|
peer->flags |= SKW_PEER_FLAG_DEAUTHED;
|
|
skw_recovery_sta_disconnect(dev, core->bss.bssid);
|
return 0;
|
}
|
|
ret = skw_join(wiphy, dev, cbss, false);
|
if (ret) {
|
if (!skw_cmd_unjoin(wiphy, dev, peer->addr, 3, true))
|
peer->flags |= SKW_PEER_FLAG_DEAUTHED;
|
|
skw_recovery_sta_disconnect(dev, core->bss.bssid);
|
return 0;
|
}
|
|
// set key
|
// set ip
|
|
cfg80211_put_bss(wiphy, cbss);
|
#else
|
while (peer_map) {
|
u8 idx = ffs(peer_map) - 1;
|
struct skw_peer *peer = rd->peer[iface->lmac_id][idx];
|
|
SKW_CLEAR(peer_map, BIT(idx));
|
|
if (!peer || peer->flags & SKW_PEER_FLAG_DEAUTHED)
|
continue;
|
|
if (ether_addr_equal(peer->addr, core->bss.bssid)) {
|
del_timer_sync(&core->timer);
|
cancel_work_sync(&iface->sta.work);
|
|
ret = skw_cmd_unjoin(wiphy, dev, peer->addr,
|
SKW_LEAVE, true);
|
if (ret)
|
skw_warn("failed, sta: %pM, ret: %d\n",
|
peer->addr, ret);
|
|
skw_set_state(&core->sm, SKW_STATE_NONE);
|
memset(&core->bss, 0, sizeof(struct skw_bss_cfg));
|
core->bss.ctx_idx = SKW_INVALID_ID;
|
|
skw_recovery_sta_disconnect(dev, peer->addr);
|
peer->flags |= SKW_PEER_FLAG_DEAUTHED;
|
|
} else {
|
/* TDLS */
|
cfg80211_tdls_oper_request(dev, peer->addr,
|
NL80211_TDLS_TEARDOWN,
|
SKW_WLAN_REASON_TDLS_TEARDOWN_UNREACHABLE,
|
GFP_KERNEL);
|
|
peer->flags |= SKW_PEER_FLAG_DEAUTHED;
|
}
|
}
|
|
atomic_set(&iface->actived_ctx, 0);
|
#endif
|
|
return 0;
|
}
|
|
static void
|
skw_recovery_sap_flush_sta(struct wiphy *wiphy, struct skw_recovery_data *rd,
|
struct skw_iface *iface, u8 subtype, u16 reason)
|
{
|
int idx, ret;
|
u8 addr[ETH_ALEN];
|
struct skw_peer *peer;
|
struct skw_core *skw = wiphy_priv(wiphy);
|
u32 peer_map = rd->iface[iface->id].peer_map;
|
u8 lmac_id = iface->lmac_id;
|
|
while (peer_map) {
|
|
if (test_bit(SKW_FLAG_FW_ASSERT, &skw->flags))
|
break;
|
|
idx = ffs(peer_map) - 1;
|
SKW_CLEAR(peer_map, BIT(idx));
|
|
peer = rd->peer[lmac_id][idx];
|
if (!peer || peer->flags & SKW_PEER_FLAG_DEAUTHED)
|
continue;
|
|
peer->flags |= SKW_PEER_FLAG_DEAUTHED;
|
skw_mlme_ap_remove_client(iface, peer->addr);
|
skw_del_sta_event(iface, peer->addr, SKW_LEAVE);
|
}
|
|
memset(addr, 0xff, ETH_ALEN);
|
ret = skw_cmd_del_sta(wiphy, iface->ndev, addr, subtype, reason, true);
|
if (ret)
|
skw_warn("failed, sta: %pM, ret: %d\n", addr, ret);
|
}
|
|
static int skw_recovery_sap(struct wiphy *wiphy, struct skw_recovery_data *rd,
|
struct skw_iface *iface)
|
{
|
int ret, size;
|
struct skw_startap_param *param;
|
struct net_device *ndev = iface->ndev;
|
struct skw_startap_resp resp = {};
|
|
param = rd->iface[iface->id].param;
|
if (!param) {
|
skw_err("invalid param\n");
|
return -EINVAL;
|
}
|
|
ret = skw_sap_set_mib(wiphy, iface->ndev,
|
(u8 *)param + param->beacon_tail_offset,
|
param->beacon_tail_len);
|
if (ret) {
|
skw_err("set tlv failed, ret: %d\n", ret);
|
return ret;
|
}
|
|
size = rd->iface[iface->id].size;
|
|
ret = skw_send_msg(wiphy, ndev, SKW_CMD_START_AP, param, size, &resp, sizeof(resp));
|
if (ret) {
|
skw_err("failed, ret: %d\n", ret);
|
return ret;
|
}
|
|
skw_startap_resp_handler(iface->skw, iface, &resp);
|
|
skw_dpd_set_coeff_params(wiphy, ndev, param->chan, param->center_chn1,
|
param->center_chn2, param->chan_width);
|
|
skw_recovery_sap_flush_sta(wiphy, rd, iface, 12, SKW_LEAVE);
|
|
return 0;
|
}
|
|
static int skw_recovery_ibss(struct wiphy *wiphy, struct skw_iface *iface)
|
{
|
return 0;
|
}
|
|
static int skw_recovery_p2p_dev(struct wiphy *wiphy, struct skw_iface *iface)
|
{
|
skw_dbg("done\n");
|
|
return 0;
|
}
|
|
static void
|
skw_recovery_prepare(struct skw_core *skw, struct skw_recovery_data *rd)
|
{
|
int i, j;
|
struct skw_peer_ctx *ctx;
|
struct skw_iface *iface;
|
|
skw->cmd.seq = 0;
|
skw->skw_event_sn = 0;
|
|
for (i = 0; i < SKW_MAX_LMAC_SUPPORT; i++) {
|
atomic_set(&skw->hw.lmac[i].fw_credit, 0);
|
|
WRITE_ONCE(skw->hw.lmac[i].iface_bitmap, 0);
|
}
|
|
if (test_and_set_bit(SKW_FLAG_FW_CHIP_RECOVERY, &skw->flags)) {
|
skw_info("received recovery event during recovery");
|
|
|
for (i = 0; i < SKW_MAX_LMAC_SUPPORT; i++) {
|
for (j = 0; j < SKW_MAX_PEER_SUPPORT; j++) {
|
ctx = &skw->hw.lmac[i].peer_ctx[j];
|
|
skw_peer_ctx_lock(ctx);
|
|
rcu_assign_pointer(ctx->entry, NULL);
|
ctx->peer = NULL;
|
|
skw_peer_ctx_unlock(ctx);
|
}
|
}
|
|
spin_lock_bh(&skw->vif.lock);
|
|
for (i = 0; i < SKW_NR_IFACE; i++) {
|
iface = skw->vif.iface[i];
|
if (!iface)
|
continue;
|
|
for (j = 0; j <= SKW_WMM_AC_MAX; j++) {
|
skb_queue_purge(&iface->txq[j]);
|
skb_queue_purge(&iface->tx_cache[j]);
|
}
|
|
atomic_set(&iface->peer_map, 0);
|
}
|
|
spin_unlock_bh(&skw->vif.lock);
|
|
return;
|
}
|
|
mutex_lock(&rd->lock);
|
for (i = 0; i < SKW_MAX_LMAC_SUPPORT; i++) {
|
for (j = 0; j < SKW_MAX_PEER_SUPPORT; j++) {
|
ctx = &skw->hw.lmac[i].peer_ctx[j];
|
|
skw_peer_ctx_lock(ctx);
|
|
rcu_assign_pointer(ctx->entry, NULL);
|
rd->peer[i][j] = ctx->peer;
|
ctx->peer = NULL;
|
|
skw_peer_ctx_unlock(ctx);
|
}
|
}
|
|
spin_lock_bh(&skw->vif.lock);
|
|
for (i = 0; i < SKW_NR_IFACE; i++) {
|
iface = skw->vif.iface[i];
|
if (!iface)
|
continue;
|
|
for (j = 0; j <= SKW_WMM_AC_MAX; j++) {
|
skb_queue_purge(&iface->txq[j]);
|
skb_queue_purge(&iface->tx_cache[j]);
|
}
|
|
rd->iface[i].peer_map = atomic_read(&iface->peer_map);
|
atomic_set(&iface->peer_map, 0);
|
}
|
|
spin_unlock_bh(&skw->vif.lock);
|
|
mutex_unlock(&rd->lock);
|
|
skw_dpd_zero(&skw->dpd);
|
}
|
|
static int skw_recovery_iface(struct work_struct *wk, u8 iface_idx)
|
{
|
int ret = 0;
|
struct skw_core *skw = container_of(wk, struct skw_core, recovery_work);
|
struct wiphy *wiphy = priv_to_wiphy(skw);
|
struct skw_recovery_data *rd = &skw->recovery_data;
|
struct skw_iface *iface = skw->vif.iface[iface_idx];
|
|
if (!iface)
|
goto ret;
|
|
if (test_bit(SKW_FLAG_FW_ASSERT, &skw->flags)) {
|
ret = -EAGAIN;
|
goto ret;
|
}
|
|
skw_info("%s: inst: %d\n",
|
skw_iftype_name(iface->wdev.iftype), iface_idx);
|
|
ret = skw_cmd_open_dev(wiphy, iface->id, iface->addr,
|
iface->wdev.iftype, 0);
|
if (ret) {
|
skw_err("open %s failed, inst: %d, ret: %d\n",
|
skw_iftype_name(iface->wdev.iftype),
|
iface->id, ret);
|
|
skw_hw_assert(skw, false);
|
|
goto ret;
|
}
|
|
spin_lock_bh(&skw->vif.lock);
|
SKW_SET(iface->flags, SKW_IFACE_FLAG_OPENED);
|
skw->vif.opened_dev++;
|
spin_unlock_bh(&skw->vif.lock);
|
|
mutex_lock(&rd->lock);
|
|
switch (iface->wdev.iftype) {
|
case NL80211_IFTYPE_STATION:
|
if (iface->flags & SKW_IFACE_FLAG_LEGACY_P2P_DEV) {
|
skw_recovery_p2p_dev(wiphy, iface);
|
break;
|
}
|
|
skw_fallthrough;
|
|
case NL80211_IFTYPE_P2P_CLIENT:
|
skw_recovery_sta(wiphy, rd, iface);
|
break;
|
|
case NL80211_IFTYPE_AP:
|
case NL80211_IFTYPE_P2P_GO:
|
skw_recovery_sap(wiphy, rd, iface);
|
break;
|
|
case NL80211_IFTYPE_ADHOC:
|
skw_recovery_ibss(wiphy, iface);
|
break;
|
|
case NL80211_IFTYPE_P2P_DEVICE:
|
skw_recovery_p2p_dev(wiphy, iface);
|
break;
|
|
default:
|
break;
|
}
|
|
mutex_unlock(&rd->lock);
|
|
ret:
|
return ret;
|
}
|
|
static void skw_recovery_work(struct work_struct *wk)
|
{
|
int i, j, ret;
|
struct skw_chip_info chip;
|
struct skw_core *skw = container_of(wk, struct skw_core, recovery_work);
|
struct wiphy *wiphy = priv_to_wiphy(skw);
|
struct skw_recovery_data *rd = &skw->recovery_data;
|
struct skw_iface *iface;
|
|
skw_info("start\n");
|
|
skw_recovery_prepare(skw, rd);
|
|
spin_lock_bh(&skw->vif.lock);
|
skw->vif.opened_dev = 0;
|
for (i = 0; i < SKW_NR_IFACE; i++) {
|
iface = skw->vif.iface[i];
|
|
if (iface)
|
SKW_CLEAR(iface->flags, SKW_IFACE_FLAG_OPENED);
|
}
|
spin_unlock_bh(&skw->vif.lock);
|
|
skw_wifi_enable(skw->hw_pdata);
|
|
ret = skw_register_rx_callback(skw, skw_rx_cb, skw, skw_rx_cb, skw);
|
if (ret < 0)
|
skw_err("register rx callback failed, ret: %d\n", ret);
|
|
skw_hw_xmit_init(skw, skw->hw.dma);
|
|
clear_bit(SKW_FLAG_FW_ASSERT, &skw->flags);
|
clear_bit(SKW_FLAG_BLOCK_TX, &skw->flags);
|
clear_bit(SKW_FLAG_FW_MAC_RECOVERY, &skw->flags);
|
clear_bit(SKW_FLAG_FW_THERMAL, &skw->flags);
|
|
skw_sync_cmd_event_version(wiphy);
|
|
ret = skw_sync_chip_info(wiphy, &chip);
|
if (ret) {
|
skw_err("sync chip info failed, ret: %d\n", ret);
|
goto ret;
|
}
|
|
ret = skw_calib_download(wiphy, skw->fw.calib_file);
|
if (ret) {
|
skw_err("calib download failed, ret: %d\n", ret);
|
goto ret;
|
}
|
|
for (i = 0; i < SKW_NR_IFACE; i++) {
|
struct skw_iface *iface = skw->vif.iface[i];
|
|
if (!iface)
|
continue;
|
|
if (iface->wdev.iftype == NL80211_IFTYPE_STATION ||
|
iface->wdev.iftype == NL80211_IFTYPE_P2P_DEVICE) {
|
ret = skw_recovery_iface(wk, i);
|
if (ret)
|
goto ret;
|
}
|
}
|
|
for (i = 0; i < SKW_NR_IFACE; i++) {
|
struct skw_iface *iface = skw->vif.iface[i];
|
|
if (!iface)
|
continue;
|
|
if (!(iface->wdev.iftype == NL80211_IFTYPE_STATION ||
|
iface->wdev.iftype == NL80211_IFTYPE_P2P_DEVICE)) {
|
ret = skw_recovery_iface(wk, i);
|
if (ret)
|
goto ret;
|
}
|
}
|
|
if (!ret) {
|
for (i = 0; i < SKW_MAX_LMAC_SUPPORT; i++) {
|
for (j = 0; j < SKW_MAX_PEER_SUPPORT; j++) {
|
skw_peer_free(rd->peer[i][j]);
|
rd->peer[i][j] = NULL;
|
}
|
}
|
|
clear_bit(SKW_FLAG_FW_CHIP_RECOVERY, &skw->flags);
|
|
#ifdef CONFIG_SWT6621S_USB3_WORKAROUND
|
if (test_bit(SKW_FLAG_SWITCHING_USB_MODE, &skw->flags)) {
|
skw_dbg("usb switch done");
|
complete(&skw->usb_switch_done);
|
}
|
#endif
|
}
|
|
ret:
|
skw_dbg("end %d\n", ret);
|
}
|
|
void skw_recovery_del_peer(struct skw_iface *iface, u8 peer_idx)
|
{
|
struct skw_recovery_data *rd = &iface->skw->recovery_data;
|
u8 lmac_id = iface->lmac_id;
|
|
if (!test_bit(SKW_FLAG_FW_CHIP_RECOVERY, &iface->skw->flags))
|
return;
|
|
mutex_lock(&rd->lock);
|
|
if (rd->peer[lmac_id])
|
rd->peer[lmac_id][peer_idx]->flags |= SKW_PEER_FLAG_DEAUTHED;
|
|
mutex_unlock(&rd->lock);
|
}
|
|
int skw_recovery_data_update(struct skw_iface *iface, void *param, int len)
|
{
|
void *data;
|
struct skw_recovery_data *rd = &iface->skw->recovery_data;
|
|
if (!param)
|
return 0;
|
|
data = SKW_ZALLOC(SKW_2K_SIZE, GFP_KERNEL);
|
if (!data)
|
return -ENOMEM;
|
|
memcpy(data, param, len);
|
|
mutex_lock(&rd->lock);
|
|
SKW_KFREE(rd->iface[iface->id].param);
|
|
rd->iface[iface->id].param = data;
|
rd->iface[iface->id].size = len;
|
|
mutex_unlock(&rd->lock);
|
|
return 0;
|
}
|
|
void skw_recovery_data_clear(struct skw_iface *iface)
|
{
|
struct skw_recovery_data *rd = &iface->skw->recovery_data;
|
|
mutex_lock(&rd->lock);
|
|
rd->iface[iface->id].size = 0;
|
rd->iface[iface->id].peer_map = 0;
|
SKW_KFREE(rd->iface[iface->id].param);
|
|
mutex_unlock(&rd->lock);
|
}
|
|
int skw_recovery_init(struct skw_core *skw)
|
{
|
mutex_init(&skw->recovery_data.lock);
|
INIT_WORK(&skw->recovery_work, skw_recovery_work);
|
#ifdef CONFIG_SWT6621S_USB3_WORKAROUND
|
init_completion(&skw->usb_switch_done);
|
#endif
|
|
return 0;
|
}
|
|
void skw_recovery_deinit(struct skw_core *skw)
|
{
|
int i, j;
|
struct skw_recovery_data *rd = &skw->recovery_data;
|
|
mutex_lock(&rd->lock);
|
|
cancel_work_sync(&skw->recovery_work);
|
|
for (i = 0; i < SKW_NR_IFACE; i++)
|
SKW_KFREE(rd->iface[i].param);
|
|
for (i = 0; i < SKW_MAX_LMAC_SUPPORT; i++) {
|
for (j = 0; j < SKW_MAX_PEER_SUPPORT; j++) {
|
skw_peer_free(rd->peer[i][j]);
|
rd->peer[i][j] = NULL;
|
}
|
}
|
|
mutex_unlock(&rd->lock);
|
}
|