// SPDX-License-Identifier: GPL-2.0-only
/*
* Implementation of mac80211 API.
*
* Copyright (c) 2017-2019, Silicon Laboratories, Inc.
* Copyright (c) 2010, ST-Ericsson
*/
#include <linux/etherdevice.h>
#include <net/mac80211.h>
#include "sta.h"
#include "wfx.h"
#include "fwio.h"
#include "bh.h"
#include "key.h"
#include "scan.h"
#include "debug.h"
#include "hif_tx.h"
#include "hif_tx_mib.h"
#define HIF_MAX_ARP_IP_ADDRTABLE_ENTRIES 2
u32 wfx_rate_mask_to_hw(struct wfx_dev *wdev, u32 rates)
{
int i;
u32 ret = 0;
// WFx only support 2GHz
struct ieee80211_supported_band *sband = wdev->hw->wiphy->bands[NL80211_BAND_2GHZ];
for (i = 0; i < sband->n_bitrates; i++) {
if (rates & BIT(i)) {
if (i >= sband->n_bitrates)
dev_warn(wdev->dev, "unsupported basic rate\n");
else
ret |= BIT(sband->bitrates[i].hw_value);
}
}
return ret;
}
void wfx_cooling_timeout_work(struct work_struct *work)
{
struct wfx_dev *wdev = container_of(to_delayed_work(work),
struct wfx_dev,
cooling_timeout_work);
wdev->chip_frozen = true;
wfx_tx_unlock(wdev);
}
void wfx_suspend_hot_dev(struct wfx_dev *wdev, enum sta_notify_cmd cmd)
{
if (cmd == STA_NOTIFY_AWAKE) {
// Device recover normal temperature
if (cancel_delayed_work(&wdev->cooling_timeout_work))
wfx_tx_unlock(wdev);
} else {
// Device is too hot
schedule_delayed_work(&wdev->cooling_timeout_work, 10 * HZ);
wfx_tx_lock(wdev);
}
}
static void wfx_filter_beacon(struct wfx_vif *wvif, bool filter_beacon)
{
const struct hif_ie_table_entry filter_ies[] = {
{
.ie_id = WLAN_EID_VENDOR_SPECIFIC,
.has_changed = 1,
.no_longer = 1,
.has_appeared = 1,
.oui = { 0x50, 0x6F, 0x9A },
}, {
.ie_id = WLAN_EID_HT_OPERATION,
.has_changed = 1,
.no_longer = 1,
.has_appeared = 1,
}, {
.ie_id = WLAN_EID_ERP_INFO,
.has_changed = 1,
.no_longer = 1,
.has_appeared = 1,
}
};
if (!filter_beacon) {
hif_beacon_filter_control(wvif, 0, 1);
} else {
hif_set_beacon_filter_table(wvif, 3, filter_ies);
hif_beacon_filter_control(wvif, HIF_BEACON_FILTER_ENABLE, 0);
}
}
static void wfx_filter_mcast(struct wfx_vif *wvif, bool filter_mcast)
{
int i;
// Temporary workaround for filters
hif_set_data_filtering(wvif, false, true);
return;
if (!filter_mcast) {
hif_set_data_filtering(wvif, false, true);
return;
}
for (i = 0; i < wvif->filter_mcast_count; i++)
hif_set_mac_addr_condition(wvif, i, wvif->filter_mcast_addr[i]);
hif_set_uc_mc_bc_condition(wvif, 0,
HIF_FILTER_UNICAST | HIF_FILTER_BROADCAST);
hif_set_config_data_filter(wvif, true, 0, BIT(1),
BIT(wvif->filter_mcast_count) - 1);
hif_set_data_filtering(wvif, true, true);
}
u64 wfx_prepare_multicast(struct ieee80211_hw *hw,
struct netdev_hw_addr_list *mc_list)
{
int i;
struct netdev_hw_addr *ha;
struct wfx_vif *wvif = NULL;
struct wfx_dev *wdev = hw->priv;
int count = netdev_hw_addr_list_count(mc_list);
while ((wvif = wvif_iterate(wdev, wvif)) != NULL) {
if (count > ARRAY_SIZE(wvif->filter_mcast_addr)) {
wvif->filter_mcast_count = 0;
continue;
}
wvif->filter_mcast_count = count;
i = 0;
netdev_hw_addr_list_for_each(ha, mc_list) {
ether_addr_copy(wvif->filter_mcast_addr[i], ha->addr);
i++;
}
}
return 0;
}
void wfx_configure_filter(struct ieee80211_hw *hw, unsigned int changed_flags,
unsigned int *total_flags, u64 unused)
{
struct wfx_vif *wvif = NULL;
struct wfx_dev *wdev = hw->priv;
bool filter_bssid, filter_prbreq, filter_beacon, filter_mcast;
// Notes:
// - Probe responses (FIF_BCN_PRBRESP_PROMISC) are never filtered
// - PS-Poll (FIF_PSPOLL) are never filtered
// - RTS, CTS and Ack (FIF_CONTROL) are always filtered
// - Broken frames (FIF_FCSFAIL and FIF_PLCPFAIL) are always filtered
// - Firmware does (yet) allow to forward unicast traffic sent to
// other stations (aka. promiscuous mode)
*total_flags &= FIF_BCN_PRBRESP_PROMISC | FIF_ALLMULTI | FIF_OTHER_BSS |
FIF_PROBE_REQ | FIF_PSPOLL;
mutex_lock(&wdev->conf_mutex);
while ((wvif = wvif_iterate(wdev, wvif)) != NULL) {
mutex_lock(&wvif->scan_lock);
// Note: FIF_BCN_PRBRESP_PROMISC covers probe response and
// beacons from other BSS
if (*total_flags & FIF_BCN_PRBRESP_PROMISC