blob: ba5c51a7f843df8066d32f5fb4baabaa84001d9c [file] [log] [blame]
// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
/*
* Copyright (C) 2012-2014, 2018-2021 Intel Corporation
* Copyright (C) 2013-2015 Intel Mobile Communications GmbH
* Copyright (C) 2016-2017 Intel Deutschland GmbH
*/
#include <linux/etherdevice.h>
#include <net/netlink.h>
#include <net/mac80211.h>
#include "mvm.h"
#include "iwl-vendor-cmd.h"
#include "fw/api/datapath.h"
#include "iwl-io.h"
#include "iwl-prph.h"
static LIST_HEAD(device_list);
static DEFINE_SPINLOCK(device_list_lock);
static int iwl_mvm_netlink_notifier(struct notifier_block *nb,
unsigned long state,
void *_notify)
{
struct netlink_notify *notify = _notify;
struct iwl_mvm *mvm;
if (state != NETLINK_URELEASE || notify->protocol != NETLINK_GENERIC)
return NOTIFY_DONE;
spin_lock_bh(&device_list_lock);
list_for_each_entry(mvm, &device_list, list) {
if (mvm->csi_portid == netlink_notify_portid(notify))
mvm->csi_portid = 0;
}
spin_unlock_bh(&device_list_lock);
return NOTIFY_OK;
}
static struct notifier_block iwl_mvm_netlink_notifier_block = {
.notifier_call = iwl_mvm_netlink_notifier,
};
void iwl_mvm_vendor_cmd_init(void)
{
WARN_ON(netlink_register_notifier(&iwl_mvm_netlink_notifier_block));
spin_lock_init(&device_list_lock);
}
void iwl_mvm_vendor_cmd_exit(void)
{
netlink_unregister_notifier(&iwl_mvm_netlink_notifier_block);
}
static const struct nla_policy
iwl_mvm_vendor_attr_policy[NUM_IWL_MVM_VENDOR_ATTR] = {
[IWL_MVM_VENDOR_ATTR_LOW_LATENCY] = { .type = NLA_FLAG },
[IWL_MVM_VENDOR_ATTR_COUNTRY] = { .type = NLA_STRING, .len = 2 },
[IWL_MVM_VENDOR_ATTR_FILTER_ARP_NA] = { .type = NLA_FLAG },
[IWL_MVM_VENDOR_ATTR_FILTER_GTK] = { .type = NLA_FLAG },
[IWL_MVM_VENDOR_ATTR_ADDR] = { .type = NLA_BINARY, .len = ETH_ALEN },
[IWL_MVM_VENDOR_ATTR_TXP_LIMIT_24] = { .type = NLA_U32 },
[IWL_MVM_VENDOR_ATTR_TXP_LIMIT_52L] = { .type = NLA_U32 },
[IWL_MVM_VENDOR_ATTR_TXP_LIMIT_52H] = { .type = NLA_U32 },
[IWL_MVM_VENDOR_ATTR_OPPPS_WA] = { .type = NLA_FLAG },
[IWL_MVM_VENDOR_ATTR_GSCAN_MAC_ADDR] = { .len = ETH_ALEN },
[IWL_MVM_VENDOR_ATTR_GSCAN_MAC_ADDR_MASK] = { .len = ETH_ALEN },
[IWL_MVM_VENDOR_ATTR_GSCAN_MAX_AP_PER_SCAN] = { .type = NLA_U32 },
[IWL_MVM_VENDOR_ATTR_GSCAN_REPORT_THRESHOLD] = { .type = NLA_U32 },
[IWL_MVM_VENDOR_ATTR_GSCAN_BUCKET_SPECS] = { .type = NLA_NESTED },
[IWL_MVM_VENDOR_ATTR_GSCAN_LOST_AP_SAMPLE_SIZE] = { .type = NLA_U8 },
[IWL_MVM_VENDOR_ATTR_GSCAN_AP_LIST] = { .type = NLA_NESTED },
[IWL_MVM_VENDOR_ATTR_GSCAN_RSSI_SAMPLE_SIZE] = { .type = NLA_U8 },
[IWL_MVM_VENDOR_ATTR_GSCAN_MIN_BREACHING] = { .type = NLA_U8 },
[IWL_MVM_VENDOR_ATTR_RXFILTER] = { .type = NLA_U32 },
[IWL_MVM_VENDOR_ATTR_RXFILTER_OP] = { .type = NLA_U32 },
[IWL_MVM_VENDOR_ATTR_DBG_COLLECT_TRIGGER] = { .type = NLA_STRING },
[IWL_MVM_VENDOR_ATTR_NAN_FAW_FREQ] = { .type = NLA_U32 },
[IWL_MVM_VENDOR_ATTR_NAN_FAW_SLOTS] = { .type = NLA_U8 },
[IWL_MVM_VENDOR_ATTR_GSCAN_REPORT_THRESHOLD_NUM] = { .type = NLA_U32 },
[IWL_MVM_VENDOR_ATTR_SAR_CHAIN_A_PROFILE] = { .type = NLA_U8 },
[IWL_MVM_VENDOR_ATTR_SAR_CHAIN_B_PROFILE] = { .type = NLA_U8 },
[IWL_MVM_VENDOR_ATTR_FIPS_TEST_VECTOR_HW_CCM] = { .type = NLA_NESTED },
[IWL_MVM_VENDOR_ATTR_FIPS_TEST_VECTOR_HW_GCM] = { .type = NLA_NESTED },
[IWL_MVM_VENDOR_ATTR_FIPS_TEST_VECTOR_HW_AES] = { .type = NLA_NESTED },
[IWL_MVM_VENDOR_ATTR_STA_CIPHER] = { .type = NLA_U32 },
[IWL_MVM_VENDOR_ATTR_STA_HLTK] = { .type = NLA_BINARY },
[IWL_MVM_VENDOR_ATTR_STA_TK] = { .type = NLA_BINARY },
[IWL_MVM_VENDOR_ATTR_RFIM_INFO] = { .type = NLA_NESTED },
[IWL_MVM_VENDOR_ATTR_RFIM_FREQ] = { .type = NLA_U32 },
[IWL_MVM_VENDOR_ATTR_RFIM_CHANNELS] = { .type = NLA_U32 },
[IWL_MVM_VENDOR_ATTR_RFIM_BANDS] = { .type = NLA_U32 },
[IWL_MVM_VENDOR_ATTR_TIME_SYNC_PROTOCOL_TYPE] = { .type = NLA_U32 },
[IWL_MVM_VENDOR_ATTR_TIME_SYNC_DIALOG_TOKEN] = { .type = NLA_U32 },
[IWL_MVM_VENDOR_ATTR_TIME_SYNC_T1] = { .type = NLA_U64 },
[IWL_MVM_VENDOR_ATTR_TIME_SYNC_T1_MAX_ERROR] = { .type = NLA_U32 },
[IWL_MVM_VENDOR_ATTR_TIME_SYNC_T4] = { .type = NLA_U64 },
[IWL_MVM_VENDOR_ATTR_TIME_SYNC_T4_MAX_ERROR] = { .type = NLA_U32 },
[IWL_MVM_VENDOR_ATTR_TIME_SYNC_FUP_DIALOG_TOKEN] = { .type = NLA_U32 },
[IWL_MVM_VENDOR_ATTR_TIME_SYNC_T2] = { .type = NLA_U64 },
[IWL_MVM_VENDOR_ATTR_TIME_SYNC_T2_MAX_ERROR] = { .type = NLA_U32 },
[IWL_MVM_VENDOR_ATTR_TIME_SYNC_T3] = { .type = NLA_U64 },
[IWL_MVM_VENDOR_ATTR_TIME_SYNC_T3_MAX_ERROR] = { .type = NLA_U32 },
[IWL_MVM_VENDOR_ATTR_ROAMING_FORBIDDEN] = { .type = NLA_U8 },
[IWL_MVM_VENDOR_ATTR_AUTH_MODE] = { .type = NLA_U32 },
[IWL_MVM_VENDOR_ATTR_CHANNEL_NUM] = { .type = NLA_U8 },
[IWL_MVM_VENDOR_ATTR_SSID] = { .type = NLA_BINARY,
.len = IEEE80211_MAX_SSID_LEN },
[IWL_MVM_VENDOR_ATTR_BAND] = { .type = NLA_U8 },
[IWL_MVM_VENDOR_ATTR_COLLOC_CHANNEL] = { .type = NLA_U8 },
[IWL_MVM_VENDOR_ATTR_COLLOC_ADDR] = { .type = NLA_BINARY, .len = ETH_ALEN },
};
static struct nlattr **iwl_mvm_parse_vendor_data(const void *data, int data_len)
{
struct nlattr **tb;
int err;
if (!data)
return ERR_PTR(-EINVAL);
tb = kcalloc(MAX_IWL_MVM_VENDOR_ATTR + 1, sizeof(*tb), GFP_KERNEL);
if (!tb)
return ERR_PTR(-ENOMEM);
err = nla_parse(tb, MAX_IWL_MVM_VENDOR_ATTR, data, data_len,
iwl_mvm_vendor_attr_policy, NULL);
if (err) {
kfree(tb);
return ERR_PTR(err);
}
return tb;
}
static int iwl_mvm_set_low_latency(struct wiphy *wiphy,
struct wireless_dev *wdev,
const void *data, int data_len)
{
struct ieee80211_hw *hw = wiphy_to_ieee80211_hw(wiphy);
struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw);
struct nlattr **tb;
int err;
struct ieee80211_vif *vif = wdev_to_ieee80211_vif(wdev);
bool low_latency;
if (!vif)
return -ENODEV;
if (data) {
tb = iwl_mvm_parse_vendor_data(data, data_len);
if (IS_ERR(tb))
return PTR_ERR(tb);
low_latency = tb[IWL_MVM_VENDOR_ATTR_LOW_LATENCY];
kfree(tb);
} else {
low_latency = false;
}
mutex_lock(&mvm->mutex);
err = iwl_mvm_update_low_latency(mvm, vif, low_latency,
LOW_LATENCY_VCMD);
mutex_unlock(&mvm->mutex);
return err;
}
static int iwl_mvm_get_low_latency(struct wiphy *wiphy,
struct wireless_dev *wdev,
const void *data, int data_len)
{
struct ieee80211_vif *vif = wdev_to_ieee80211_vif(wdev);
struct iwl_mvm_vif *mvmvif;
struct sk_buff *skb;
if (!vif)
return -ENODEV;
mvmvif = iwl_mvm_vif_from_mac80211(vif);
skb = cfg80211_vendor_cmd_alloc_reply_skb(wiphy, 100);
if (!skb)
return -ENOMEM;
if (iwl_mvm_vif_low_latency(mvmvif) &&
nla_put_flag(skb, IWL_MVM_VENDOR_ATTR_LOW_LATENCY)) {
kfree_skb(skb);
return -ENOBUFS;
}
return cfg80211_vendor_cmd_reply(skb);
}
static int iwl_mvm_set_country(struct wiphy *wiphy,
struct wireless_dev *wdev,
const void *data, int data_len)
{
struct ieee80211_regdomain *regd;
struct nlattr **tb;
struct ieee80211_hw *hw = wiphy_to_ieee80211_hw(wiphy);
struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw);
int retval;
if (!iwl_mvm_is_lar_supported(mvm))
return -EOPNOTSUPP;
tb = iwl_mvm_parse_vendor_data(data, data_len);
if (IS_ERR(tb))
return PTR_ERR(tb);
if (!tb[IWL_MVM_VENDOR_ATTR_COUNTRY]) {
retval = -EINVAL;
goto free;
}
mutex_lock(&mvm->mutex);
/* set regdomain information to FW */
regd = iwl_mvm_get_regdomain(wiphy,
nla_data(tb[IWL_MVM_VENDOR_ATTR_COUNTRY]),
iwl_mvm_is_wifi_mcc_supported(mvm) ?
MCC_SOURCE_3G_LTE_HOST :
MCC_SOURCE_OLD_FW, NULL);
if (IS_ERR_OR_NULL(regd)) {
retval = -EIO;
goto unlock;
}
retval = regulatory_set_wiphy_regd(wiphy, regd);
kfree(regd);
unlock:
mutex_unlock(&mvm->mutex);
free:
kfree(tb);
return retval;
}
#ifdef CPTCFG_IWLMVM_TDLS_PEER_CACHE
static int iwl_vendor_tdls_peer_cache_add(struct wiphy *wiphy,
struct wireless_dev *wdev,
const void *data, int data_len)
{
struct nlattr **tb;
struct ieee80211_hw *hw = wiphy_to_ieee80211_hw(wiphy);
struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw);
struct iwl_mvm_tdls_peer_counter *cnt;
u8 *addr;
struct ieee80211_vif *vif = wdev_to_ieee80211_vif(wdev);
int err;
if (!vif)
return -ENODEV;
tb = iwl_mvm_parse_vendor_data(data, data_len);
if (IS_ERR(tb))
return PTR_ERR(tb);
if (vif->type != NL80211_IFTYPE_STATION ||
!tb[IWL_MVM_VENDOR_ATTR_ADDR]) {
err = -EINVAL;
goto free;
}
mutex_lock(&mvm->mutex);
if (mvm->tdls_peer_cache_cnt >= IWL_MVM_TDLS_CNT_MAX_PEERS) {
err = -ENOSPC;
goto out_unlock;
}
addr = nla_data(tb[IWL_MVM_VENDOR_ATTR_ADDR]);
rcu_read_lock();
cnt = iwl_mvm_tdls_peer_cache_find(mvm, addr);
rcu_read_unlock();
if (cnt) {
err = -EEXIST;
goto out_unlock;
}
cnt = kzalloc(sizeof(*cnt) +
sizeof(cnt->rx[0]) * mvm->trans->num_rx_queues,
GFP_KERNEL);
if (!cnt) {
err = -ENOMEM;
goto out_unlock;
}
IWL_DEBUG_TDLS(mvm, "Adding %pM to TDLS peer cache\n", addr);
ether_addr_copy(cnt->mac.addr, addr);
cnt->vif = vif;
list_add_tail_rcu(&cnt->list, &mvm->tdls_peer_cache_list);
mvm->tdls_peer_cache_cnt++;
err = 0;
out_unlock:
mutex_unlock(&mvm->mutex);
free:
kfree(tb);
return err;
}
static int iwl_vendor_tdls_peer_cache_del(struct wiphy *wiphy,
struct wireless_dev *wdev,
const void *data, int data_len)
{
struct nlattr **tb;
struct ieee80211_hw *hw = wiphy_to_ieee80211_hw(wiphy);
struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw);
struct iwl_mvm_tdls_peer_counter *cnt;
u8 *addr;
int err;
tb = iwl_mvm_parse_vendor_data(data, data_len);
if (IS_ERR(tb))
return PTR_ERR(tb);
if (!tb[IWL_MVM_VENDOR_ATTR_ADDR]) {
err = -EINVAL;
goto free;
}
addr = nla_data(tb[IWL_MVM_VENDOR_ATTR_ADDR]);
mutex_lock(&mvm->mutex);
rcu_read_lock();
cnt = iwl_mvm_tdls_peer_cache_find(mvm, addr);
if (!cnt) {
IWL_DEBUG_TDLS(mvm, "%pM not found in TDLS peer cache\n", addr);
err = -ENOENT;
goto out_unlock;
}
IWL_DEBUG_TDLS(mvm, "Removing %pM from TDLS peer cache\n", addr);
mvm->tdls_peer_cache_cnt--;
list_del_rcu(&cnt->list);
kfree_rcu(cnt, rcu_head);
err = 0;
out_unlock:
rcu_read_unlock();
mutex_unlock(&mvm->mutex);
free:
kfree(tb);
return err;
}
static int iwl_vendor_tdls_peer_cache_query(struct wiphy *wiphy,
struct wireless_dev *wdev,
const void *data, int data_len)
{
struct nlattr **tb;
struct ieee80211_hw *hw = wiphy_to_ieee80211_hw(wiphy);
struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw);
struct iwl_mvm_tdls_peer_counter *cnt;
struct sk_buff *skb;
u32 rx_bytes, tx_bytes;
u8 *addr;
int err;
tb = iwl_mvm_parse_vendor_data(data, data_len);
if (IS_ERR(tb))
return PTR_ERR(tb);
if (!tb[IWL_MVM_VENDOR_ATTR_ADDR]) {
kfree(tb);
return -EINVAL;
}
addr = nla_data(tb[IWL_MVM_VENDOR_ATTR_ADDR]);
/* we can free the tb, the addr pointer is still valid into the msg */
kfree(tb);
rcu_read_lock();
cnt = iwl_mvm_tdls_peer_cache_find(mvm, addr);
if (!cnt) {
IWL_DEBUG_TDLS(mvm, "%pM not found in TDLS peer cache\n",
addr);
err = -ENOENT;
} else {
int q;
tx_bytes = cnt->tx_bytes;
rx_bytes = 0;
for (q = 0; q < mvm->trans->num_rx_queues; q++)
rx_bytes += cnt->rx[q].bytes;
err = 0;
}
rcu_read_unlock();
if (err)
return err;
skb = cfg80211_vendor_cmd_alloc_reply_skb(wiphy, 100);
if (!skb)
return -ENOMEM;
if (nla_put_u32(skb, IWL_MVM_VENDOR_ATTR_TX_BYTES, tx_bytes) ||
nla_put_u32(skb, IWL_MVM_VENDOR_ATTR_RX_BYTES, rx_bytes)) {
kfree_skb(skb);
return -ENOBUFS;
}
return cfg80211_vendor_cmd_reply(skb);
}
#endif /* CPTCFG_IWLMVM_TDLS_PEER_CACHE */
#define IWL_MVM_RFIM_CAPA_CNVI (BIT(2))
#define IWL_MVM_RFIM_CAPA_SCAN (BIT(3))
#define IWL_MVM_RFIM_CAPA_ASSOC (BIT(4))
#define IWL_MVM_RFIM_CAPA_TPT (BIT(5))
#define IWL_MVM_RFIM_CAPA_ALL (IWL_MVM_RFIM_CAPA_CNVI |\
IWL_MVM_RFIM_CAPA_SCAN |\
IWL_MVM_RFIM_CAPA_ASSOC |\
IWL_MVM_RFIM_CAPA_TPT)
static int iwl_vendor_rfim_get_capa(struct wiphy *wiphy,
struct wireless_dev *wdev,
const void *data, int data_len)
{
struct ieee80211_hw *hw = wiphy_to_ieee80211_hw(wiphy);
struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw);
struct sk_buff *skb;
u8 capa = 0;
skb = cfg80211_vendor_cmd_alloc_reply_skb(wiphy, 4);
if (!skb)
return -ENOMEM;
if (mvm->trans->trans_cfg->device_family >= IWL_DEVICE_FAMILY_AX210 &&
mvm->trans->trans_cfg->integrated)
capa = IWL_MVM_RFIM_CAPA_ALL;
if (nla_put_u8(skb, IWL_MVM_VENDOR_ATTR_RFIM_CAPA, capa)) {
kfree_skb(skb);
return -ENOBUFS;
}
return cfg80211_vendor_cmd_reply(skb);
}
static int iwl_vendor_rfim_get_table(struct wiphy *wiphy,
struct wireless_dev *wdev,
const void *data, int data_len)
{
struct ieee80211_hw *hw = wiphy_to_ieee80211_hw(wiphy);
struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw);
struct iwl_rfi_freq_table_resp_cmd *resp;
struct sk_buff *skb = NULL;
struct nlattr *rfim_info;
int i, ret;
resp = iwl_rfi_get_freq_table(mvm);
if (IS_ERR(resp))
return PTR_ERR(resp);
if (resp->status != RFI_FREQ_TABLE_OK) {
ret = -EINVAL;
goto err;
}
skb = cfg80211_vendor_cmd_alloc_reply_skb(wiphy, sizeof(*rfim_info) + 100);
if (!skb) {
ret = -ENOMEM;
goto err;
}
rfim_info = nla_nest_start(skb, IWL_MVM_VENDOR_ATTR_RFIM_INFO |
NLA_F_NESTED);
if (!rfim_info) {
ret = -ENOBUFS;
goto err;
}
for (i = 0; i < 4; i++) {
if (nla_put_u16(skb, IWL_MVM_VENDOR_ATTR_RFIM_FREQ,
le16_to_cpu(resp->table[i].freq)) ||
nla_put(skb, IWL_MVM_VENDOR_ATTR_RFIM_CHANNELS,
sizeof(resp->table[i].channels),
resp->table[i].channels) ||
nla_put(skb, IWL_MVM_VENDOR_ATTR_RFIM_BANDS,
sizeof(resp->table[i].bands),
resp->table[i].bands)) {
ret = -ENOBUFS;
goto err;
}
}
nla_nest_end(skb, rfim_info);
kfree(resp);
return cfg80211_vendor_cmd_reply(skb);
err:
kfree_skb(skb);
kfree(resp);
return ret;
}
static int iwl_vendor_rfim_set_table(struct wiphy *wiphy,
struct wireless_dev *wdev,
const void *data, int data_len)
{
struct ieee80211_hw *hw = wiphy_to_ieee80211_hw(wiphy);
struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw);
struct iwl_rfi_lut_entry rfim_table[IWL_RFI_LUT_SIZE] = {};
struct nlattr **tb;
struct nlattr *attr;
int rem, err = 0;
int row_idx = -1; /* the row is updated only at frequency attr */
tb = iwl_mvm_parse_vendor_data(data, data_len);
if (IS_ERR(tb))
return PTR_ERR(tb);
if (!tb[IWL_MVM_VENDOR_ATTR_RFIM_INFO]) {
err = -EINVAL;
goto out;
}
nla_for_each_nested(attr, tb[IWL_MVM_VENDOR_ATTR_RFIM_INFO], rem) {
switch (nla_type(attr)) {
case IWL_MVM_VENDOR_ATTR_RFIM_FREQ:
row_idx++;
rfim_table[row_idx].freq =
cpu_to_le16(nla_get_u16(attr));
break;
case IWL_MVM_VENDOR_ATTR_RFIM_CHANNELS:
if (row_idx < 0) {
err = -EINVAL;
goto out;
}
memcpy(rfim_table[row_idx].channels, nla_data(attr),
ARRAY_SIZE(rfim_table[row_idx].channels));
break;
case IWL_MVM_VENDOR_ATTR_RFIM_BANDS:
if (row_idx < 0) {
err = -EINVAL;
goto out;
}
memcpy(rfim_table[row_idx].bands, nla_data(attr),
ARRAY_SIZE(rfim_table[row_idx].bands));
break;
default:
IWL_ERR(mvm, "Invalid attribute %d\n", nla_type(attr));
err = -EINVAL;
goto out;
}
}
err = iwl_rfi_send_config_cmd(mvm, rfim_table);
if (err)
IWL_ERR(mvm, "Failed to send rfi table to FW, error %d\n", err);
out:
kfree(tb);
return err;
}
static int iwl_vendor_set_nic_txpower_limit(struct wiphy *wiphy,
struct wireless_dev *wdev,
const void *data, int data_len)
{
struct ieee80211_hw *hw = wiphy_to_ieee80211_hw(wiphy);
struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw);
struct iwl_dev_tx_power_cmd cmd = {
.common.set_mode = cpu_to_le32(IWL_TX_POWER_MODE_SET_DEVICE),
.common.dev_24 = cpu_to_le16(IWL_DEV_MAX_TX_POWER),
.common.dev_52_low = cpu_to_le16(IWL_DEV_MAX_TX_POWER),
.common.dev_52_high = cpu_to_le16(IWL_DEV_MAX_TX_POWER),
};
struct nlattr **tb;
int len;
int err;
u8 cmd_ver = iwl_fw_lookup_cmd_ver(mvm->fw, LONG_GROUP,
REDUCE_TX_POWER_CMD,
IWL_FW_CMD_VER_UNKNOWN);
tb = iwl_mvm_parse_vendor_data(data, data_len);
if (IS_ERR(tb))
return PTR_ERR(tb);
if (tb[IWL_MVM_VENDOR_ATTR_TXP_LIMIT_24]) {
s32 txp = nla_get_u32(tb[IWL_MVM_VENDOR_ATTR_TXP_LIMIT_24]);
if (txp < 0 || txp > IWL_DEV_MAX_TX_POWER) {
err = -EINVAL;
goto free;
}
cmd.common.dev_24 = cpu_to_le16(txp);
}
if (tb[IWL_MVM_VENDOR_ATTR_TXP_LIMIT_52L]) {
s32 txp = nla_get_u32(tb[IWL_MVM_VENDOR_ATTR_TXP_LIMIT_52L]);
if (txp < 0 || txp > IWL_DEV_MAX_TX_POWER) {
err = -EINVAL;
goto free;
}
cmd.common.dev_52_low = cpu_to_le16(txp);
}
if (tb[IWL_MVM_VENDOR_ATTR_TXP_LIMIT_52H]) {
s32 txp = nla_get_u32(tb[IWL_MVM_VENDOR_ATTR_TXP_LIMIT_52H]);
if (txp < 0 || txp > IWL_DEV_MAX_TX_POWER) {
err = -EINVAL;
goto free;
}
cmd.common.dev_52_high = cpu_to_le16(txp);
}
if (cmd_ver == 6)
len = sizeof(mvm->txp_cmd.v6);
else if (fw_has_api(&mvm->fw->ucode_capa,
IWL_UCODE_TLV_API_REDUCE_TX_POWER))
len = sizeof(mvm->txp_cmd.v5);
else if (fw_has_capa(&mvm->fw->ucode_capa,
IWL_UCODE_TLV_CAPA_TX_POWER_ACK))
len = sizeof(mvm->txp_cmd.v4);
else
len = sizeof(mvm->txp_cmd.v3);
/* all structs have the same common part, add it */
len += sizeof(cmd.common);
mutex_lock(&mvm->mutex);
if (iwl_mvm_firmware_running(mvm))
err = iwl_mvm_send_cmd_pdu(mvm, REDUCE_TX_POWER_CMD,
0, len, &cmd);
else
err = 0;
if (err)
IWL_ERR(mvm, "failed to update device TX power: %d\n", err);
else
mvm->txp_cmd = cmd;
mutex_unlock(&mvm->mutex);
err = 0;
free:
kfree(tb);
return err;
}
#ifdef CPTCFG_IWLMVM_P2P_OPPPS_TEST_WA
static int iwl_mvm_oppps_wa_update_quota(struct iwl_mvm *mvm,
struct ieee80211_vif *vif,
bool enable)
{
struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif);
struct ieee80211_p2p_noa_attr *noa = &vif->bss_conf.p2p_noa_attr;
bool force_update = true;
if (enable && noa->oppps_ctwindow & IEEE80211_P2P_OPPPS_ENABLE_BIT)
mvm->p2p_opps_test_wa_vif = mvmvif;
else
mvm->p2p_opps_test_wa_vif = NULL;
if (fw_has_capa(&mvm->fw->ucode_capa,
IWL_UCODE_TLV_CAPA_DYNAMIC_QUOTA)) {
return -EOPNOTSUPP;
}
return iwl_mvm_update_quotas(mvm, force_update, NULL);
}
static int iwl_mvm_oppps_wa(struct wiphy *wiphy,
struct wireless_dev *wdev,
const void *data, int data_len)
{
struct ieee80211_hw *hw = wiphy_to_ieee80211_hw(wiphy);
struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw);
struct nlattr **tb;
int err;
struct ieee80211_vif *vif = wdev_to_ieee80211_vif(wdev);
if (!vif)
return -ENODEV;
tb = iwl_mvm_parse_vendor_data(data, data_len);
if (IS_ERR(tb))
return PTR_ERR(tb);
mutex_lock(&mvm->mutex);
if (vif->type == NL80211_IFTYPE_STATION && vif->p2p) {
bool enable = !!tb[IWL_MVM_VENDOR_ATTR_OPPPS_WA];
err = iwl_mvm_oppps_wa_update_quota(mvm, vif, enable);
} else {
err = -EOPNOTSUPP;
}
mutex_unlock(&mvm->mutex);
kfree(tb);
return err;
}
#endif
void iwl_mvm_active_rx_filters(struct iwl_mvm *mvm)
{
int i, len, total = 0;
struct iwl_mcast_filter_cmd *cmd;
static const u8 ipv4mc[] = {0x01, 0x00, 0x5e};
static const u8 ipv6mc[] = {0x33, 0x33};
static const u8 ipv4_mdns[] = {0x01, 0x00, 0x5e, 0x00, 0x00, 0xfb};
static const u8 ipv6_mdns[] = {0x33, 0x33, 0x00, 0x00, 0x00, 0xfb};
lockdep_assert_held(&mvm->mutex);
if (mvm->rx_filters & IWL_MVM_VENDOR_RXFILTER_EINVAL)
return;
for (i = 0; i < mvm->mcast_filter_cmd->count; i++) {
if (mvm->rx_filters & IWL_MVM_VENDOR_RXFILTER_MCAST4 &&
memcmp(&mvm->mcast_filter_cmd->addr_list[i * ETH_ALEN],
ipv4mc, sizeof(ipv4mc)) == 0)
total++;
else if (memcmp(&mvm->mcast_filter_cmd->addr_list[i * ETH_ALEN],
ipv4_mdns, sizeof(ipv4_mdns)) == 0)
total++;
else if (mvm->rx_filters & IWL_MVM_VENDOR_RXFILTER_MCAST6 &&
memcmp(&mvm->mcast_filter_cmd->addr_list[i * ETH_ALEN],
ipv6mc, sizeof(ipv6mc)) == 0)
total++;
else if (memcmp(&mvm->mcast_filter_cmd->addr_list[i * ETH_ALEN],
ipv6_mdns, sizeof(ipv6_mdns)) == 0)
total++;
}
/* FW expects full words */
len = roundup(sizeof(*cmd) + total * ETH_ALEN, 4);
cmd = kzalloc(len, GFP_KERNEL);
if (!cmd)
return;
memcpy(cmd, mvm->mcast_filter_cmd, sizeof(*cmd));
cmd->count = 0;
for (i = 0; i < mvm->mcast_filter_cmd->count; i++) {
bool copy_filter = false;
if (mvm->rx_filters & IWL_MVM_VENDOR_RXFILTER_MCAST4 &&
memcmp(&mvm->mcast_filter_cmd->addr_list[i * ETH_ALEN],
ipv4mc, sizeof(ipv4mc)) == 0)
copy_filter = true;
else if (memcmp(&mvm->mcast_filter_cmd->addr_list[i * ETH_ALEN],
ipv4_mdns, sizeof(ipv4_mdns)) == 0)
copy_filter = true;
else if (mvm->rx_filters & IWL_MVM_VENDOR_RXFILTER_MCAST6 &&
memcmp(&mvm->mcast_filter_cmd->addr_list[i * ETH_ALEN],
ipv6mc, sizeof(ipv6mc)) == 0)
copy_filter = true;
else if (memcmp(&mvm->mcast_filter_cmd->addr_list[i * ETH_ALEN],
ipv6_mdns, sizeof(ipv6_mdns)) == 0)
copy_filter = true;
if (!copy_filter)
continue;
ether_addr_copy(&cmd->addr_list[cmd->count * ETH_ALEN],
&mvm->mcast_filter_cmd->addr_list[i * ETH_ALEN]);
cmd->count++;
}
kfree(mvm->mcast_active_filter_cmd);
mvm->mcast_active_filter_cmd = cmd;
}
static int iwl_mvm_vendor_rxfilter(struct wiphy *wiphy,
struct wireless_dev *wdev,
const void *data, int data_len)
{
struct nlattr **tb;
struct ieee80211_hw *hw = wiphy_to_ieee80211_hw(wiphy);
struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw);
enum iwl_mvm_vendor_rxfilter_flags filter, rx_filters, old_rx_filters;
enum iwl_mvm_vendor_rxfilter_op op;
bool first_set;
u32 mask;
int err;
tb = iwl_mvm_parse_vendor_data(data, data_len);
if (IS_ERR(tb))
return PTR_ERR(tb);
if (!tb[IWL_MVM_VENDOR_ATTR_RXFILTER]) {
err = -EINVAL;
goto free;
}
if (!tb[IWL_MVM_VENDOR_ATTR_RXFILTER_OP]) {
err = -EINVAL;
goto free;
}
filter = nla_get_u32(tb[IWL_MVM_VENDOR_ATTR_RXFILTER]);
op = nla_get_u32(tb[IWL_MVM_VENDOR_ATTR_RXFILTER_OP]);
if (filter != IWL_MVM_VENDOR_RXFILTER_UNICAST &&
filter != IWL_MVM_VENDOR_RXFILTER_BCAST &&
filter != IWL_MVM_VENDOR_RXFILTER_MCAST4 &&
filter != IWL_MVM_VENDOR_RXFILTER_MCAST6) {
err = -EINVAL;
goto free;
}
rx_filters = mvm->rx_filters & ~IWL_MVM_VENDOR_RXFILTER_EINVAL;
switch (op) {
case IWL_MVM_VENDOR_RXFILTER_OP_DROP:
rx_filters &= ~filter;
break;
case IWL_MVM_VENDOR_RXFILTER_OP_PASS:
rx_filters |= filter;
break;
default:
err = -EINVAL;
goto free;
}
first_set = mvm->rx_filters & IWL_MVM_VENDOR_RXFILTER_EINVAL;
/* If first time set - clear EINVAL value */
mvm->rx_filters &= ~IWL_MVM_VENDOR_RXFILTER_EINVAL;
err = 0;
if (rx_filters == mvm->rx_filters && !first_set)
goto free;
mutex_lock(&mvm->mutex);
old_rx_filters = mvm->rx_filters;
mvm->rx_filters = rx_filters;
mask = IWL_MVM_VENDOR_RXFILTER_MCAST4 | IWL_MVM_VENDOR_RXFILTER_MCAST6;
if ((old_rx_filters & mask) != (rx_filters & mask) || first_set) {
iwl_mvm_active_rx_filters(mvm);
iwl_mvm_recalc_multicast(mvm);
}
mask = IWL_MVM_VENDOR_RXFILTER_BCAST;
if ((old_rx_filters & mask) != (rx_filters & mask) || first_set)
iwl_mvm_configure_bcast_filter(mvm);
mutex_unlock(&mvm->mutex);
free:
kfree(tb);
return err;
}
static int iwl_mvm_vendor_dbg_collect(struct wiphy *wiphy,
struct wireless_dev *wdev,
const void *data, int data_len)
{
struct ieee80211_hw *hw = wiphy_to_ieee80211_hw(wiphy);
struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw);
struct nlattr **tb;
int err, len = 0;
const char *trigger_desc;
tb = iwl_mvm_parse_vendor_data(data, data_len);
if (IS_ERR(tb))
return PTR_ERR(tb);
if (!tb[IWL_MVM_VENDOR_ATTR_DBG_COLLECT_TRIGGER]) {
err = -EINVAL;
goto free;
}
trigger_desc = nla_data(tb[IWL_MVM_VENDOR_ATTR_DBG_COLLECT_TRIGGER]);
len = nla_len(tb[IWL_MVM_VENDOR_ATTR_DBG_COLLECT_TRIGGER]);
iwl_fw_dbg_collect(&mvm->fwrt, FW_DBG_TRIGGER_USER_EXTENDED,
trigger_desc, len, NULL);
err = 0;
free:
kfree(tb);
return err;
}
static int iwl_mvm_vendor_nan_faw_conf(struct wiphy *wiphy,
struct wireless_dev *wdev,
const void *data, int data_len)
{
struct nlattr **tb;
struct ieee80211_hw *hw = wiphy_to_ieee80211_hw(wiphy);
struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw);
struct cfg80211_chan_def def = {};
struct ieee80211_channel *chan;
u32 freq;
u8 slots;
int err;
tb = iwl_mvm_parse_vendor_data(data, data_len);
if (IS_ERR(tb))
return PTR_ERR(tb);
if (!tb[IWL_MVM_VENDOR_ATTR_NAN_FAW_SLOTS]) {
err = -EINVAL;
goto free;
}
if (!tb[IWL_MVM_VENDOR_ATTR_NAN_FAW_FREQ]) {
err = -EINVAL;
goto free;
}
freq = nla_get_u32(tb[IWL_MVM_VENDOR_ATTR_NAN_FAW_FREQ]);
slots = nla_get_u8(tb[IWL_MVM_VENDOR_ATTR_NAN_FAW_SLOTS]);
chan = ieee80211_get_channel(wiphy, freq);
if (!chan) {
err = -EINVAL;
goto free;
}
cfg80211_chandef_create(&def, chan, NL80211_CHAN_NO_HT);
if (!cfg80211_chandef_usable(wiphy, &def, IEEE80211_CHAN_DISABLED)) {
err = -EINVAL;
goto free;
}
err = iwl_mvm_nan_config_nan_faw_cmd(mvm, &def, slots);
free:
kfree(tb);
return err;
}
#ifdef CONFIG_ACPI
static int iwl_mvm_vendor_set_dynamic_txp_profile(struct wiphy *wiphy,
struct wireless_dev *wdev,
const void *data,
int data_len)
{
struct ieee80211_hw *hw = wiphy_to_ieee80211_hw(wiphy);
struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw);
struct nlattr **tb;
u8 chain_a, chain_b;
int err;
tb = iwl_mvm_parse_vendor_data(data, data_len);
if (IS_ERR(tb))
return PTR_ERR(tb);
if (!tb[IWL_MVM_VENDOR_ATTR_SAR_CHAIN_A_PROFILE] ||
!tb[IWL_MVM_VENDOR_ATTR_SAR_CHAIN_B_PROFILE]) {
err = -EINVAL;
goto free;
}
chain_a = nla_get_u8(tb[IWL_MVM_VENDOR_ATTR_SAR_CHAIN_A_PROFILE]);
chain_b = nla_get_u8(tb[IWL_MVM_VENDOR_ATTR_SAR_CHAIN_B_PROFILE]);
if (mvm->fwrt.sar_chain_a_profile == chain_a &&
mvm->fwrt.sar_chain_b_profile == chain_b) {
err = 0;
goto free;
}
mvm->fwrt.sar_chain_a_profile = chain_a;
mvm->fwrt.sar_chain_b_profile = chain_b;
if (!iwl_mvm_firmware_running(mvm)) {
err = 0;
goto free;
}
mutex_lock(&mvm->mutex);
err = iwl_mvm_sar_select_profile(mvm, chain_a, chain_b);
mutex_unlock(&mvm->mutex);
free:
kfree(tb);
if (err > 0)
/*
* For SAR validation purpose we need to track the exact return
* value of iwl_mvm_sar_select_profile, mostly to differentiate
* between general SAR failure and the case of WRDS disable
* (it is illegal if WRDS doesn't exist but WGDS does).
* Since nl80211 forbids a positive number as a return value,
* in case SAR is disabled overwrite it with -ENOENT.
*/
err = -ENOENT;
return err;
}
static int iwl_mvm_vendor_get_sar_profile_info(struct wiphy *wiphy,
struct wireless_dev *wdev,
const void *data,
int data_len)
{
struct ieee80211_hw *hw = wiphy_to_ieee80211_hw(wiphy);
struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw);
struct sk_buff *skb;
int i;
u32 n_profiles = 0;
for (i = 0; i < ACPI_SAR_PROFILE_NUM; i++) {
if (mvm->fwrt.sar_profiles[i].enabled)
n_profiles++;
}
skb = cfg80211_vendor_cmd_alloc_reply_skb(wiphy, 100);
if (!skb)
return -ENOMEM;
if (nla_put_u8(skb, IWL_MVM_VENDOR_ATTR_SAR_ENABLED_PROFILE_NUM,
n_profiles) ||
nla_put_u8(skb, IWL_MVM_VENDOR_ATTR_SAR_CHAIN_A_PROFILE,
mvm->fwrt.sar_chain_a_profile) ||
nla_put_u8(skb, IWL_MVM_VENDOR_ATTR_SAR_CHAIN_B_PROFILE,
mvm->fwrt.sar_chain_b_profile)) {
kfree_skb(skb);
return -ENOBUFS;
}
return cfg80211_vendor_cmd_reply(skb);
}
static int iwl_mvm_vendor_put_geo_profile(struct iwl_mvm *mvm, struct sk_buff *skb, int profile)
{
int i;
for (i = 0; i < ACPI_GEO_NUM_BANDS_REV2; i++) {
struct nlattr *nl_band = nla_nest_start(skb, i + 1);
if (!nl_band)
return -ENOBUFS;
nla_put_u8(skb, IWL_VENDOR_SAR_GEO_MAX_TXP,
mvm->fwrt.geo_profiles[profile - 1].bands[i].max);
nla_put_u8(skb, IWL_VENDOR_SAR_GEO_CHAIN_A_OFFSET,
mvm->fwrt.geo_profiles[profile - 1].bands[i].chains[0]);
nla_put_u8(skb, IWL_VENDOR_SAR_GEO_CHAIN_B_OFFSET,
mvm->fwrt.geo_profiles[profile - 1].bands[i].chains[1]);
nla_nest_end(skb, nl_band);
}
return 0;
}
static int iwl_mvm_vendor_get_geo_profile_info(struct wiphy *wiphy,
struct wireless_dev *wdev,
const void *data,
int data_len)
{
struct ieee80211_hw *hw = wiphy_to_ieee80211_hw(wiphy);
struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw);
struct sk_buff *skb;
struct nlattr *nl_profile;
int tbl_idx, ret;
tbl_idx = iwl_mvm_get_sar_geo_profile(mvm);
if (tbl_idx < 0)
return tbl_idx;
skb = cfg80211_vendor_cmd_alloc_reply_skb(wiphy, 100);
if (!skb)
return -ENOMEM;
nl_profile = nla_nest_start(skb, IWL_MVM_VENDOR_ATTR_SAR_GEO_PROFILE);
if (!nl_profile) {
kfree_skb(skb);
return -ENOBUFS;
}
if (!tbl_idx)
goto out;
/* put into the skb the info for profile tbl_idx */
ret = iwl_mvm_vendor_put_geo_profile(mvm, skb, tbl_idx);
if (ret < 0) {
kfree_skb(skb);
return ret;
}
out:
nla_nest_end(skb, nl_profile);
return cfg80211_vendor_cmd_reply(skb);
}
static int iwl_mvm_vendor_ppag_get_table(struct wiphy *wiphy,
struct wireless_dev *wdev,
const void *data,
int data_len)
{
struct ieee80211_hw *hw = wiphy_to_ieee80211_hw(wiphy);
struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw);
struct sk_buff *skb = NULL;
struct nlattr *nl_table;
int ret, per_chain_size, chain;
s8 *gain;
/* if ppag is disabled */
if (!mvm->fwrt.ppag_table.v1.flags)
return -ENOENT;
skb = cfg80211_vendor_cmd_alloc_reply_skb(wiphy, 180);
if (!skb)
return -ENOMEM;
nl_table = nla_nest_start(skb, IWL_MVM_VENDOR_ATTR_PPAG_TABLE |
NLA_F_NESTED);
if (!nl_table) {
ret = -ENOBUFS;
goto out;
}
if (mvm->fwrt.ppag_ver == 0) {
gain = mvm->fwrt.ppag_table.v1.gain[0];
per_chain_size = IWL_NUM_SUB_BANDS_V1;
} else {
gain = mvm->fwrt.ppag_table.v2.gain[0];
per_chain_size = IWL_NUM_SUB_BANDS_V2;
}
for (chain = 0; chain < IWL_NUM_CHAIN_LIMITS; chain++) {
int idx = chain * per_chain_size;
if (nla_put(skb, chain + 1, per_chain_size, &gain[idx])) {
ret = -ENOBUFS;
goto out;
}
}
nla_nest_end(skb, nl_table);
/* put the ppag version */
if (nla_put_u32(skb, IWL_MVM_VENDOR_ATTR_PPAG_NUM_SUB_BANDS,
per_chain_size)) {
ret = -ENOBUFS;
goto out;
}
return cfg80211_vendor_cmd_reply(skb);
out:
kfree_skb(skb);
return ret;
}
static int iwl_mvm_vendor_sar_get_table(struct wiphy *wiphy,
struct wireless_dev *wdev,
const void *data,
int data_len)
{
struct ieee80211_hw *hw = wiphy_to_ieee80211_hw(wiphy);
struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw);
struct sk_buff *skb = NULL;
struct nlattr *nl_table;
int prof, chain, ret, fw_ver;
/* if wrds is disabled - ewrd must be disabled too */
if (!mvm->fwrt.sar_profiles[0].enabled)
return -ENOENT;
skb = cfg80211_vendor_cmd_alloc_reply_skb(wiphy, 100);
if (!skb)
return -ENOMEM;
nl_table = nla_nest_start(skb, IWL_MVM_VENDOR_ATTR_SAR_TABLE);
if (!nl_table) {
kfree_skb(skb);
return -ENOBUFS;
}
for (prof = 0; prof < ACPI_SAR_PROFILE_NUM; prof++) {
struct nlattr *nl_profile;
if (!mvm->fwrt.sar_profiles[prof].enabled)
break;
nl_profile = nla_nest_start(skb, prof + 1);
if (!nl_profile) {
ret = -ENOBUFS;
goto out;
}
/* put info per chain */
for (chain = 0; chain < ACPI_SAR_NUM_CHAINS_REV2; chain++) {
if (nla_put(skb, chain + 1, ACPI_SAR_NUM_SUB_BANDS_REV2,
mvm->fwrt.sar_profiles[prof].chains[chain].subbands)) {
ret = -ENOBUFS;
goto out;
}
}
nla_nest_end(skb, nl_profile);
}
nla_nest_end(skb, nl_table);
fw_ver = iwl_fw_lookup_cmd_ver(mvm->fw, LONG_GROUP, REDUCE_TX_POWER_CMD,
IWL_FW_CMD_VER_UNKNOWN);
if (nla_put_u32(skb, IWL_MVM_VENDOR_ATTR_SAR_VER, fw_ver)) {
ret = -ENOBUFS;
goto out;
}
return cfg80211_vendor_cmd_reply(skb);
out:
kfree_skb(skb);
return ret;
}
static int iwl_mvm_vendor_geo_sar_get_table(struct wiphy *wiphy,
struct wireless_dev *wdev,
const void *data,
int data_len)
{
struct ieee80211_hw *hw = wiphy_to_ieee80211_hw(wiphy);
struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw);
struct sk_buff *skb = NULL;
struct nlattr *nl_table;
int i, ret;
if (!mvm->fwrt.geo_enabled)
return -ENOENT;
skb = cfg80211_vendor_cmd_alloc_reply_skb(wiphy, 100);
if (!skb)
return -ENOMEM;
nl_table = nla_nest_start(skb, IWL_MVM_VENDOR_ATTR_GEO_SAR_TABLE);
if (!nl_table) {
ret = -ENOBUFS;
goto out;
}
/* get each profile */
for (i = 0; i < ACPI_NUM_GEO_PROFILES; i++) {
struct nlattr *nl_profile = nla_nest_start(skb, i + 1);
if (!nl_profile) {
ret = -ENOBUFS;
goto out;
}
/* put into the skb the info for profile i+1
* (we don't have profile 0) */
ret = iwl_mvm_vendor_put_geo_profile(mvm, skb, i + 1);
if (ret < 0) {
ret = -ENOBUFS;
goto out;
}
nla_nest_end(skb, nl_profile);
}
nla_nest_end(skb, nl_table);
if (nla_put_u32(skb, IWL_MVM_VENDOR_ATTR_GEO_SAR_VER, mvm->fwrt.geo_rev)) {
ret = -ENOBUFS;
goto out;
}
return cfg80211_vendor_cmd_reply(skb);
out:
kfree_skb(skb);
return ret;
}
#endif
static const struct nla_policy
iwl_mvm_vendor_fips_hw_policy[NUM_IWL_VENDOR_FIPS_TEST_VECTOR_HW] = {
[IWL_VENDOR_FIPS_TEST_VECTOR_HW_KEY] = { .type = NLA_BINARY },
[IWL_VENDOR_FIPS_TEST_VECTOR_HW_NONCE] = { .type = NLA_BINARY },
[IWL_VENDOR_FIPS_TEST_VECTOR_HW_AAD] = { .type = NLA_BINARY },
[IWL_VENDOR_FIPS_TEST_VECTOR_HW_PAYLOAD] = { .type = NLA_BINARY },
[IWL_VENDOR_FIPS_TEST_VECTOR_HW_FLAGS] = { .type = NLA_U8 },
};
static int iwl_mvm_vendor_validate_ccm_vector(struct nlattr **tb)
{
if (!tb[IWL_VENDOR_FIPS_TEST_VECTOR_HW_KEY] ||
!tb[IWL_VENDOR_FIPS_TEST_VECTOR_HW_NONCE] ||
!tb[IWL_VENDOR_FIPS_TEST_VECTOR_HW_AAD] ||
nla_len(tb[IWL_VENDOR_FIPS_TEST_VECTOR_HW_KEY]) !=
FIPS_KEY_LEN_128 ||
nla_len(tb[IWL_VENDOR_FIPS_TEST_VECTOR_HW_NONCE]) !=
FIPS_CCM_NONCE_LEN)
return -EINVAL;
return 0;
}
static int iwl_mvm_vendor_validate_gcm_vector(struct nlattr **tb)
{
if (!tb[IWL_VENDOR_FIPS_TEST_VECTOR_HW_KEY] ||
!tb[IWL_VENDOR_FIPS_TEST_VECTOR_HW_NONCE] ||
!tb[IWL_VENDOR_FIPS_TEST_VECTOR_HW_AAD] ||
(nla_len(tb[IWL_VENDOR_FIPS_TEST_VECTOR_HW_KEY]) !=
FIPS_KEY_LEN_128 &&
nla_len(tb[IWL_VENDOR_FIPS_TEST_VECTOR_HW_KEY]) !=
FIPS_KEY_LEN_256) ||
nla_len(tb[IWL_VENDOR_FIPS_TEST_VECTOR_HW_NONCE]) !=
FIPS_GCM_NONCE_LEN)
return -EINVAL;
return 0;
}
static int iwl_mvm_vendor_validate_aes_vector(struct nlattr **tb)
{
if (!tb[IWL_VENDOR_FIPS_TEST_VECTOR_HW_KEY] ||
(nla_len(tb[IWL_VENDOR_FIPS_TEST_VECTOR_HW_KEY]) !=
FIPS_KEY_LEN_128 &&
nla_len(tb[IWL_VENDOR_FIPS_TEST_VECTOR_HW_KEY]) !=
FIPS_KEY_LEN_256))
return -EINVAL;
return 0;
}
/**
* iwl_mvm_vendor_build_vector - build FIPS test vector for AES/CCM/GCM tests
*
* @cmd_buf: the command buffer is returned by this pointer in case of success.
* @vector: test vector attributes.
* @flags: specifies which encryption algorithm to use. One of
* &IWL_FIPS_TEST_VECTOR_FLAGS_CCM, &IWL_FIPS_TEST_VECTOR_FLAGS_GCM and
* &IWL_FIPS_TEST_VECTOR_FLAGS_AES.
*
* This function returns the length of the command buffer (in bytes) in case of
* success, or a negative error code on failure.
*/
static int iwl_mvm_vendor_build_vector(u8 **cmd_buf, struct nlattr *vector,
u8 flags)
{
struct nlattr *tb[NUM_IWL_VENDOR_FIPS_TEST_VECTOR_HW];
struct iwl_fips_test_cmd *cmd;
int err;
int payload_len = 0;
u8 *buf;
err = nla_parse_nested(tb, MAX_IWL_VENDOR_FIPS_TEST_VECTOR_HW,
vector, iwl_mvm_vendor_fips_hw_policy, NULL);
if (err)
return err;
switch (flags) {
case IWL_FIPS_TEST_VECTOR_FLAGS_CCM:
err = iwl_mvm_vendor_validate_ccm_vector(tb);
break;
case IWL_FIPS_TEST_VECTOR_FLAGS_GCM:
err = iwl_mvm_vendor_validate_gcm_vector(tb);
break;
case IWL_FIPS_TEST_VECTOR_FLAGS_AES:
err = iwl_mvm_vendor_validate_aes_vector(tb);
break;
default:
return -EINVAL;
}
if (err)
return err;
if (tb[IWL_VENDOR_FIPS_TEST_VECTOR_HW_AAD] &&
nla_len(tb[IWL_VENDOR_FIPS_TEST_VECTOR_HW_AAD]) > FIPS_MAX_AAD_LEN)
return -EINVAL;
if (tb[IWL_VENDOR_FIPS_TEST_VECTOR_HW_PAYLOAD])
payload_len =
nla_len(tb[IWL_VENDOR_FIPS_TEST_VECTOR_HW_PAYLOAD]);
buf = kzalloc(sizeof(*cmd) + payload_len, GFP_KERNEL);
if (!buf)
return -ENOMEM;
cmd = (void *)buf;
cmd->flags = cpu_to_le32(flags);
memcpy(cmd->key, nla_data(tb[IWL_VENDOR_FIPS_TEST_VECTOR_HW_KEY]),
nla_len(tb[IWL_VENDOR_FIPS_TEST_VECTOR_HW_KEY]));
if (nla_len(tb[IWL_VENDOR_FIPS_TEST_VECTOR_HW_KEY]) == FIPS_KEY_LEN_256)
cmd->flags |= cpu_to_le32(IWL_FIPS_TEST_VECTOR_FLAGS_KEY_256);
if (tb[IWL_VENDOR_FIPS_TEST_VECTOR_HW_NONCE])
memcpy(cmd->nonce,
nla_data(tb[IWL_VENDOR_FIPS_TEST_VECTOR_HW_NONCE]),
nla_len(tb[IWL_VENDOR_FIPS_TEST_VECTOR_HW_NONCE]));
if (tb[IWL_VENDOR_FIPS_TEST_VECTOR_HW_AAD]) {
memcpy(cmd->aad,
nla_data(tb[IWL_VENDOR_FIPS_TEST_VECTOR_HW_AAD]),
nla_len(tb[IWL_VENDOR_FIPS_TEST_VECTOR_HW_AAD]));
cmd->aad_len =
cpu_to_le32(nla_len(tb[IWL_VENDOR_FIPS_TEST_VECTOR_HW_AAD]));
}
if (payload_len) {
memcpy(cmd->payload,
nla_data(tb[IWL_VENDOR_FIPS_TEST_VECTOR_HW_PAYLOAD]),
payload_len);
cmd->payload_len = cpu_to_le32(payload_len);
}
if (tb[IWL_VENDOR_FIPS_TEST_VECTOR_HW_FLAGS]) {
u8 hw_flags =
nla_get_u8(tb[IWL_VENDOR_FIPS_TEST_VECTOR_HW_FLAGS]);
if (hw_flags & IWL_VENDOR_FIPS_TEST_VECTOR_FLAGS_ENCRYPT)
cmd->flags |=
cpu_to_le32(IWL_FIPS_TEST_VECTOR_FLAGS_ENC);
}
*cmd_buf = buf;
return sizeof(*cmd) + payload_len;
}
static int iwl_mvm_vendor_test_fips_send_resp(struct wiphy *wiphy,
struct iwl_fips_test_resp *resp)
{
struct sk_buff *skb;
u32 resp_len = le32_to_cpu(resp->len);
u32 *status = (void *)(resp->payload + resp_len);
skb = cfg80211_vendor_cmd_alloc_reply_skb(wiphy, sizeof(*resp));
if (!skb)
return -ENOMEM;
if ((*status) == IWL_FIPS_TEST_STATUS_SUCCESS &&
nla_put(skb, IWL_MVM_VENDOR_ATTR_FIPS_TEST_RESULT, resp_len,
resp->payload)) {
kfree_skb(skb);
return -ENOBUFS;
}
return cfg80211_vendor_cmd_reply(skb);
}
static int iwl_mvm_vendor_test_fips(struct wiphy *wiphy,
struct wireless_dev *wdev,
const void *data, int data_len)
{
struct nlattr **tb;
struct ieee80211_hw *hw = wiphy_to_ieee80211_hw(wiphy);
struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw);
struct iwl_host_cmd hcmd = {
.id = iwl_cmd_id(FIPS_TEST_VECTOR_CMD, LEGACY_GROUP, 0),
.flags = CMD_WANT_SKB,
.dataflags = { IWL_HCMD_DFL_NOCOPY },
};
struct iwl_rx_packet *pkt;
struct iwl_fips_test_resp *resp;
struct nlattr *vector;
u8 flags;
u8 *buf = NULL;
int ret;
tb = iwl_mvm_parse_vendor_data(data, data_len);
if (IS_ERR(tb))
return PTR_ERR(tb);
if (tb[IWL_MVM_VENDOR_ATTR_FIPS_TEST_VECTOR_HW_CCM]) {
vector = tb[IWL_MVM_VENDOR_ATTR_FIPS_TEST_VECTOR_HW_CCM];
flags = IWL_FIPS_TEST_VECTOR_FLAGS_CCM;
} else if (tb[IWL_MVM_VENDOR_ATTR_FIPS_TEST_VECTOR_HW_GCM]) {
vector = tb[IWL_MVM_VENDOR_ATTR_FIPS_TEST_VECTOR_HW_GCM];
flags = IWL_FIPS_TEST_VECTOR_FLAGS_GCM;
} else if (tb[IWL_MVM_VENDOR_ATTR_FIPS_TEST_VECTOR_HW_AES]) {
vector = tb[IWL_MVM_VENDOR_ATTR_FIPS_TEST_VECTOR_HW_AES];
flags = IWL_FIPS_TEST_VECTOR_FLAGS_AES;
} else {
ret = -EINVAL;
goto free;
}
ret = iwl_mvm_vendor_build_vector(&buf, vector, flags);
if (ret <= 0)
goto free;
hcmd.data[0] = buf;
hcmd.len[0] = ret;
mutex_lock(&mvm->mutex);
ret = iwl_mvm_send_cmd(mvm, &hcmd);
mutex_unlock(&mvm->mutex);
if (ret)
goto free;
pkt = hcmd.resp_pkt;
resp = (void *)pkt->data;
iwl_mvm_vendor_test_fips_send_resp(wiphy, resp);
iwl_free_resp(&hcmd);
free:
kfree(buf);
kfree(tb);
return ret;
}
static int iwl_mvm_vendor_csi_register(struct wiphy *wiphy,
struct wireless_dev *wdev,
const void *data, int data_len)
{
struct ieee80211_hw *hw = wiphy_to_ieee80211_hw(wiphy);
struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw);
mvm->csi_portid = cfg80211_vendor_cmd_get_sender(wiphy);
return 0;
}
static int iwl_mvm_vendor_add_pasn_sta(struct wiphy *wiphy,
struct wireless_dev *wdev,
const void *data, int data_len)
{
struct nlattr **tb;
struct ieee80211_hw *hw = wiphy_to_ieee80211_hw(wiphy);
struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw);
struct ieee80211_vif *vif = wdev_to_ieee80211_vif(wdev);
u8 *addr, *tk = NULL, *hltk;
u32 tk_len = 0, hltk_len, cipher;
int ret = 0;
struct ieee80211_sta *sta;
if (!vif)
return -ENODEV;
tb = iwl_mvm_parse_vendor_data(data, data_len);
if (IS_ERR(tb))
return PTR_ERR(tb);
if (!tb[IWL_MVM_VENDOR_ATTR_ADDR] ||
!tb[IWL_MVM_VENDOR_ATTR_STA_HLTK] ||
!tb[IWL_MVM_VENDOR_ATTR_STA_CIPHER])
return -EINVAL;
addr = nla_data(tb[IWL_MVM_VENDOR_ATTR_ADDR]);
cipher = nla_get_u32(tb[IWL_MVM_VENDOR_ATTR_STA_CIPHER]);
hltk = nla_data(tb[IWL_MVM_VENDOR_ATTR_STA_HLTK]);
hltk_len = nla_len(tb[IWL_MVM_VENDOR_ATTR_STA_HLTK]);
rcu_read_lock();
sta = ieee80211_find_sta(vif, addr);
if ((!tb[IWL_MVM_VENDOR_ATTR_STA_TK] && (!sta || !sta->mfp)) ||
(tb[IWL_MVM_VENDOR_ATTR_STA_TK] && sta && sta->mfp))
ret = -EINVAL;
rcu_read_unlock();
if (ret)
return ret;
if (tb[IWL_MVM_VENDOR_ATTR_STA_TK]) {
tk = nla_data(tb[IWL_MVM_VENDOR_ATTR_STA_TK]);
tk_len = nla_len(tb[IWL_MVM_VENDOR_ATTR_STA_TK]);
}
mutex_lock(&mvm->mutex);
if (vif->bss_conf.ftm_responder)
ret = iwl_mvm_ftm_respoder_add_pasn_sta(mvm, vif, addr, cipher,
tk, tk_len, hltk,
hltk_len);
else
iwl_mvm_ftm_add_pasn_sta(mvm, vif, addr, cipher, tk, tk_len,
hltk, hltk_len);
mutex_unlock(&mvm->mutex);
return ret;
}
static int iwl_mvm_vendor_remove_pasn_sta(struct wiphy *wiphy,
struct wireless_dev *wdev,
const void *data, int data_len)
{
struct nlattr **tb;
struct ieee80211_hw *hw = wiphy_to_ieee80211_hw(wiphy);
struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw);
struct ieee80211_vif *vif = wdev_to_ieee80211_vif(wdev);
u8 *addr;
int ret = 0;
if (!vif)
return -ENODEV;
tb = iwl_mvm_parse_vendor_data(data, data_len);
if (IS_ERR(tb))
return PTR_ERR(tb);
if (!tb[IWL_MVM_VENDOR_ATTR_ADDR])
return -EINVAL;
addr = nla_data(tb[IWL_MVM_VENDOR_ATTR_ADDR]);
mutex_lock(&mvm->mutex);
if (vif->bss_conf.ftm_responder)
ret = iwl_mvm_ftm_resp_remove_pasn_sta(mvm, vif, addr);
else
iwl_mvm_ftm_remove_pasn_sta(mvm, addr);
mutex_unlock(&mvm->mutex);
return ret;
}
static int iwl_mvm_time_sync_measurement_config(struct wiphy *wiphy,
struct wireless_dev *wdev,
const void *data, int data_len)
{
struct nlattr **tb;
struct ieee80211_hw *hw = wiphy_to_ieee80211_hw(wiphy);
struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw);
struct iwl_time_sync_cfg_cmd cmd = {};
u32 protocol_types;
int err;
tb = iwl_mvm_parse_vendor_data(data, data_len);
if (IS_ERR(tb))
return PTR_ERR(tb);
if (!tb[IWL_MVM_VENDOR_ATTR_ADDR] ||
!tb[IWL_MVM_VENDOR_ATTR_TIME_SYNC_PROTOCOL_TYPE])
return -EINVAL;
ether_addr_copy(cmd.peer_addr, nla_data(tb[IWL_MVM_VENDOR_ATTR_ADDR]));
protocol_types = nla_get_u32(tb[IWL_MVM_VENDOR_ATTR_TIME_SYNC_PROTOCOL_TYPE]);
/* Check if the requested configuration was already set earlier */
if (protocol_types == mvm->time_msmt_cfg)
return -EALREADY;
if (protocol_types <= (IWL_MVM_VENDOR_TIME_SYNC_PROTOCOL_TM |
IWL_MVM_VENDOR_TIME_SYNC_PROTOCOL_FTM))
cmd.protocols = cpu_to_le32(protocol_types);
else
return -EINVAL;
mutex_lock(&mvm->mutex);
err = iwl_mvm_send_cmd_pdu(mvm,
iwl_cmd_id(WNM_80211V_TIMING_MEASUREMENT_CONFIG_CMD,
DATA_PATH_GROUP, 0),
0, sizeof(cmd), &cmd);
mutex_unlock(&mvm->mutex);
if (err) {
IWL_ERR(mvm, "Failed to send TM/FTM Measurement cfg cmd: %d\n", err);
return err;
}
/* Save the changed time sync measurement configuration */
mvm->time_msmt_cfg = protocol_types;
ether_addr_copy(mvm->time_msmt_peer_addr, cmd.peer_addr);
mvm->time_sync_wdev = wdev;
return 0;
}
static int iwl_mvm_vendor_get_csme_conn_info(struct wiphy *wiphy,
struct wireless_dev *wdev,
const void *data, int data_len)
{
struct ieee80211_hw *hw = wiphy_to_ieee80211_hw(wiphy);
struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw);
struct iwl_mvm_csme_conn_info *csme_conn_info;
struct sk_buff *skb;
int err = 0;
mutex_lock(&mvm->mutex);
csme_conn_info = iwl_mvm_get_csme_conn_info(mvm);
if (!csme_conn_info) {
err = -EINVAL;
goto out_unlock;
}
skb = cfg80211_vendor_cmd_alloc_reply_skb(wiphy, 200);
if (!skb) {
err = -ENOMEM;
goto out_unlock;
}
if (nla_put_u32(skb, IWL_MVM_VENDOR_ATTR_AUTH_MODE,
csme_conn_info->conn_info.auth_mode) ||
nla_put(skb, IWL_MVM_VENDOR_ATTR_SSID,
csme_conn_info->conn_info.ssid_len,
csme_conn_info->conn_info.ssid) ||
nla_put_u32(skb, IWL_MVM_VENDOR_ATTR_STA_CIPHER,
csme_conn_info->conn_info.pairwise_cipher) ||
nla_put_u8(skb, IWL_MVM_VENDOR_ATTR_CHANNEL_NUM,
csme_conn_info->conn_info.channel) ||
nla_put(skb, IWL_MVM_VENDOR_ATTR_ADDR, ETH_ALEN,
csme_conn_info->conn_info.bssid)) {
kfree_skb(skb);
err = -ENOBUFS;
}
out_unlock:
mutex_unlock(&mvm->mutex);
if (err)
return err;
return cfg80211_vendor_cmd_reply(skb);
}
static int iwl_mvm_vendor_host_get_ownership(struct wiphy *wiphy,
struct wireless_dev *wdev,
const void *data, int data_len)
{
struct ieee80211_hw *hw = wiphy_to_ieee80211_hw(wiphy);
struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw);
mutex_lock(&mvm->mutex);
iwl_mvm_mei_get_ownership(mvm);
mutex_unlock(&mvm->mutex);
return 0;
}
static const struct wiphy_vendor_command iwl_mvm_vendor_commands[] = {
{
.info = {
.vendor_id = INTEL_OUI,
.subcmd = IWL_MVM_VENDOR_CMD_SET_LOW_LATENCY,
},
.flags = WIPHY_VENDOR_CMD_NEED_NETDEV |
WIPHY_VENDOR_CMD_NEED_RUNNING,
.doit = iwl_mvm_set_low_latency,
#if CFG80211_VERSION >= KERNEL_VERSION(5,3,0)
.policy = iwl_mvm_vendor_attr_policy,
#endif
#if CFG80211_VERSION >= KERNEL_VERSION(5,3,0)
.maxattr = MAX_IWL_MVM_VENDOR_ATTR,
#endif
},
{
.info = {
.vendor_id = INTEL_OUI,
.subcmd = IWL_MVM_VENDOR_CMD_GET_LOW_LATENCY,
},
.flags = WIPHY_VENDOR_CMD_NEED_NETDEV |
WIPHY_VENDOR_CMD_NEED_RUNNING,
.doit = iwl_mvm_get_low_latency,
#if CFG80211_VERSION >= KERNEL_VERSION(5,3,0)
.policy = iwl_mvm_vendor_attr_policy,
#endif
#if CFG80211_VERSION >= KERNEL_VERSION(5,3,0)
.maxattr = MAX_IWL_MVM_VENDOR_ATTR,
#endif
},
{
.info = {
.vendor_id = INTEL_OUI,
.subcmd = IWL_MVM_VENDOR_CMD_SET_COUNTRY,
},
.flags = WIPHY_VENDOR_CMD_NEED_NETDEV |
WIPHY_VENDOR_CMD_NEED_RUNNING,
.doit = iwl_mvm_set_country,
#if CFG80211_VERSION >= KERNEL_VERSION(5,3,0)
.policy = iwl_mvm_vendor_attr_policy,
#endif
#if CFG80211_VERSION >= KERNEL_VERSION(5,3,0)
.maxattr = MAX_IWL_MVM_VENDOR_ATTR,
#endif
},
#ifdef CPTCFG_IWLMVM_TDLS_PEER_CACHE
{
.info = {
.vendor_id = INTEL_OUI,
.subcmd = IWL_MVM_VENDOR_CMD_TDLS_PEER_CACHE_ADD,
},
.flags = WIPHY_VENDOR_CMD_NEED_NETDEV |
WIPHY_VENDOR_CMD_NEED_RUNNING,
.doit = iwl_vendor_tdls_peer_cache_add,
#if CFG80211_VERSION >= KERNEL_VERSION(5,3,0)
.policy = iwl_mvm_vendor_attr_policy,
#endif
#if CFG80211_VERSION >= KERNEL_VERSION(5,3,0)
.maxattr = MAX_IWL_MVM_VENDOR_ATTR,
#endif
},
{
.info = {
.vendor_id = INTEL_OUI,
.subcmd = IWL_MVM_VENDOR_CMD_TDLS_PEER_CACHE_DEL,
},
.flags = WIPHY_VENDOR_CMD_NEED_NETDEV |
WIPHY_VENDOR_CMD_NEED_RUNNING,
.doit = iwl_vendor_tdls_peer_cache_del,
#if CFG80211_VERSION >= KERNEL_VERSION(5,3,0)
.policy = iwl_mvm_vendor_attr_policy,
#endif
#if CFG80211_VERSION >= KERNEL_VERSION(5,3,0)
.maxattr = MAX_IWL_MVM_VENDOR_ATTR,
#endif
},
{
.info = {
.vendor_id = INTEL_OUI,
.subcmd = IWL_MVM_VENDOR_CMD_TDLS_PEER_CACHE_QUERY,
},
.flags = WIPHY_VENDOR_CMD_NEED_NETDEV |
WIPHY_VENDOR_CMD_NEED_RUNNING,
.doit = iwl_vendor_tdls_peer_cache_query,
#if CFG80211_VERSION >= KERNEL_VERSION(5,3,0)
.policy = iwl_mvm_vendor_attr_policy,
#endif
#if CFG80211_VERSION >= KERNEL_VERSION(5,3,0)
.maxattr = MAX_IWL_MVM_VENDOR_ATTR,
#endif
},
#endif /* CPTCFG_IWLMVM_TDLS_PEER_CACHE */
{
.info = {
.vendor_id = INTEL_OUI,
.subcmd = IWL_MVM_VENDOR_CMD_SET_NIC_TXPOWER_LIMIT,
},
.flags = WIPHY_VENDOR_CMD_NEED_WDEV,
.doit = iwl_vendor_set_nic_txpower_limit,
#if CFG80211_VERSION >= KERNEL_VERSION(5,3,0)
.policy = iwl_mvm_vendor_attr_policy,
#endif
#if CFG80211_VERSION >= KERNEL_VERSION(5,3,0)
.maxattr = MAX_IWL_MVM_VENDOR_ATTR,
#endif
},
#ifdef CPTCFG_IWLMVM_P2P_OPPPS_TEST_WA
{
.info = {
.vendor_id = INTEL_OUI,
.subcmd = IWL_MVM_VENDOR_CMD_OPPPS_WA,
},
.flags = WIPHY_VENDOR_CMD_NEED_NETDEV |
WIPHY_VENDOR_CMD_NEED_RUNNING,
.doit = iwl_mvm_oppps_wa,
#if CFG80211_VERSION >= KERNEL_VERSION(5,3,0)
.policy = iwl_mvm_vendor_attr_policy,
#endif
#if CFG80211_VERSION >= KERNEL_VERSION(5,3,0)
.maxattr = MAX_IWL_MVM_VENDOR_ATTR,
#endif
},
#endif
{
.info = {
.vendor_id = INTEL_OUI,
.subcmd = IWL_MVM_VENDOR_CMD_RXFILTER,
},
.flags = WIPHY_VENDOR_CMD_NEED_NETDEV |
WIPHY_VENDOR_CMD_NEED_RUNNING,
.doit = iwl_mvm_vendor_rxfilter,
#if CFG80211_VERSION >= KERNEL_VERSION(5,3,0)
.policy = iwl_mvm_vendor_attr_policy,
#endif
#if CFG80211_VERSION >= KERNEL_VERSION(5,3,0)
.maxattr = MAX_IWL_MVM_VENDOR_ATTR,
#endif
},
{
.info = {
.vendor_id = INTEL_OUI,
.subcmd = IWL_MVM_VENDOR_CMD_DBG_COLLECT,
},
.flags = WIPHY_VENDOR_CMD_NEED_WDEV |
WIPHY_VENDOR_CMD_NEED_RUNNING,
.doit = iwl_mvm_vendor_dbg_collect,
#if CFG80211_VERSION >= KERNEL_VERSION(5,3,0)
.policy = iwl_mvm_vendor_attr_policy,
#endif
#if CFG80211_VERSION >= KERNEL_VERSION(5,3,0)
.maxattr = MAX_IWL_MVM_VENDOR_ATTR,
#endif
},
{
.info = {
.vendor_id = INTEL_OUI,
.subcmd = IWL_MVM_VENDOR_CMD_NAN_FAW_CONF,
},
.flags = WIPHY_VENDOR_CMD_NEED_WDEV |
WIPHY_VENDOR_CMD_NEED_RUNNING,
.doit = iwl_mvm_vendor_nan_faw_conf,
#if CFG80211_VERSION >= KERNEL_VERSION(5,3,0)
.policy = iwl_mvm_vendor_attr_policy,
#endif
#if CFG80211_VERSION >= KERNEL_VERSION(5,3,0)
.maxattr = MAX_IWL_MVM_VENDOR_ATTR,
#endif
},
#ifdef CONFIG_ACPI
{
.info = {
.vendor_id = INTEL_OUI,
.subcmd = IWL_MVM_VENDOR_CMD_SET_SAR_PROFILE,
},
.flags = WIPHY_VENDOR_CMD_NEED_WDEV,
.doit = iwl_mvm_vendor_set_dynamic_txp_profile,
#if CFG80211_VERSION >= KERNEL_VERSION(5,3,0)
.policy = iwl_mvm_vendor_attr_policy,
#endif
#if CFG80211_VERSION >= KERNEL_VERSION(5,3,0)
.maxattr = MAX_IWL_MVM_VENDOR_ATTR,
#endif
},
{
.info = {
.vendor_id = INTEL_OUI,
.subcmd = IWL_MVM_VENDOR_CMD_GET_SAR_PROFILE_INFO,
},
.flags = WIPHY_VENDOR_CMD_NEED_WDEV,
.doit = iwl_mvm_vendor_get_sar_profile_info,
#if CFG80211_VERSION >= KERNEL_VERSION(5,3,0)
.policy = iwl_mvm_vendor_attr_policy,
#endif
#if CFG80211_VERSION >= KERNEL_VERSION(5,3,0)
.maxattr = MAX_IWL_MVM_VENDOR_ATTR,
#endif
},
{
.info = {
.vendor_id = INTEL_OUI,
.subcmd = IWL_MVM_VENDOR_CMD_GET_SAR_GEO_PROFILE,
},
.flags = WIPHY_VENDOR_CMD_NEED_WDEV |
WIPHY_VENDOR_CMD_NEED_RUNNING,
.doit = iwl_mvm_vendor_get_geo_profile_info,
#if CFG80211_VERSION >= KERNEL_VERSION(5,3,0)
.policy = iwl_mvm_vendor_attr_policy,
#endif
#if CFG80211_VERSION >= KERNEL_VERSION(5,3,0)
.maxattr = MAX_IWL_MVM_VENDOR_ATTR,
#endif
},
{
.info = {
.vendor_id = INTEL_OUI,
.subcmd = IWL_MVM_VENDOR_CMD_PPAG_GET_TABLE,
},
.flags = WIPHY_VENDOR_CMD_NEED_WDEV,
.doit = iwl_mvm_vendor_ppag_get_table,
#if CFG80211_VERSION >= KERNEL_VERSION(5,3,0)
.policy = iwl_mvm_vendor_attr_policy,
#endif
#if CFG80211_VERSION >= KERNEL_VERSION(5,3,0)
.maxattr = MAX_IWL_MVM_VENDOR_ATTR,
#endif
},
{
.info = {
.vendor_id = INTEL_OUI,
.subcmd = IWL_MVM_VENDOR_CMD_SAR_GET_TABLE,
},
.flags = WIPHY_VENDOR_CMD_NEED_WDEV,
.doit = iwl_mvm_vendor_sar_get_table,
#if CFG80211_VERSION >= KERNEL_VERSION(5,3,0)
.policy = iwl_mvm_vendor_attr_policy,
#endif
#if CFG80211_VERSION >= KERNEL_VERSION(5,3,0)
.maxattr = MAX_IWL_MVM_VENDOR_ATTR,
#endif
},
{
.info = {
.vendor_id = INTEL_OUI,
.subcmd = IWL_MVM_VENDOR_CMD_GEO_SAR_GET_TABLE,
},
.flags = WIPHY_VENDOR_CMD_NEED_WDEV,
.doit = iwl_mvm_vendor_geo_sar_get_table,
#if CFG80211_VERSION >= KERNEL_VERSION(5,3,0)
.policy = iwl_mvm_vendor_attr_policy,
#endif
#if CFG80211_VERSION >= KERNEL_VERSION(5,3,0)
.maxattr = MAX_IWL_MVM_VENDOR_ATTR,
#endif
},
#endif
{
.info = {
.vendor_id = INTEL_OUI,
.subcmd = IWL_MVM_VENDOR_CMD_TEST_FIPS,
},
.flags = WIPHY_VENDOR_CMD_NEED_WDEV |
WIPHY_VENDOR_CMD_NEED_RUNNING,
.doit = iwl_mvm_vendor_test_fips,
#if CFG80211_VERSION >= KERNEL_VERSION(5,3,0)
.policy = iwl_mvm_vendor_fips_hw_policy,
#endif
#if CFG80211_VERSION >= KERNEL_VERSION(5,3,0)
.maxattr = MAX_IWL_VENDOR_FIPS_TEST_VECTOR_HW,
#endif
},
{
.info = {
.vendor_id = INTEL_OUI,
.subcmd = IWL_MVM_VENDOR_CMD_CSI_EVENT,
},
.doit = iwl_mvm_vendor_csi_register,
#if CFG80211_VERSION >= KERNEL_VERSION(5,3,0)
.policy = iwl_mvm_vendor_attr_policy,
#endif
#if CFG80211_VERSION >= KERNEL_VERSION(5,3,0)
.maxattr = MAX_IWL_MVM_VENDOR_ATTR,
#endif
},
{
.info = {
.vendor_id = INTEL_OUI,
.subcmd = IWL_MVM_VENDOR_CMD_ADD_PASN_STA,
},
.flags = WIPHY_VENDOR_CMD_NEED_WDEV |
WIPHY_VENDOR_CMD_NEED_RUNNING,
.doit = iwl_mvm_vendor_add_pasn_sta,
#if CFG80211_VERSION >= KERNEL_VERSION(5,3,0)
.policy = iwl_mvm_vendor_attr_policy,
#endif
#if CFG80211_VERSION >= KERNEL_VERSION(5,3,0)
.maxattr = MAX_IWL_MVM_VENDOR_ATTR,
#endif
},
{
.info = {
.vendor_id = INTEL_OUI,
.subcmd = IWL_MVM_VENDOR_CMD_REMOVE_PASN_STA,
},
.flags = WIPHY_VENDOR_CMD_NEED_WDEV |
WIPHY_VENDOR_CMD_NEED_RUNNING,
.doit = iwl_mvm_vendor_remove_pasn_sta,
#if CFG80211_VERSION >= KERNEL_VERSION(5,3,0)
.policy = iwl_mvm_vendor_attr_policy,
#endif
#if CFG80211_VERSION >= KERNEL_VERSION(5,3,0)
.maxattr = MAX_IWL_MVM_VENDOR_ATTR,
#endif
},
{
.info = {
.vendor_id = INTEL_OUI,
.subcmd = IWL_MVM_VENDOR_CMD_RFIM_SET_TABLE,
},
.flags = WIPHY_VENDOR_CMD_NEED_WDEV |
WIPHY_VENDOR_CMD_NEED_RUNNING,
.doit = iwl_vendor_rfim_set_table,
#if CFG80211_VERSION >= KERNEL_VERSION(5,3,0)
.policy = iwl_mvm_vendor_attr_policy,
#endif
#if CFG80211_VERSION >= KERNEL_VERSION(5,3,0)
.maxattr = MAX_IWL_MVM_VENDOR_ATTR,
#endif
},
{
.info = {
.vendor_id = INTEL_OUI,
.subcmd = IWL_MVM_VENDOR_CMD_RFIM_GET_TABLE,
},
.flags = WIPHY_VENDOR_CMD_NEED_WDEV,
.doit = iwl_vendor_rfim_get_table,
#if CFG80211_VERSION >= KERNEL_VERSION(5,3,0)
.policy = iwl_mvm_vendor_attr_policy,
#endif
#if CFG80211_VERSION >= KERNEL_VERSION(5,3,0)
.maxattr = MAX_IWL_MVM_VENDOR_ATTR,
#endif
},
{
.info = {
.vendor_id = INTEL_OUI,
.subcmd = IWL_MVM_VENDOR_CMD_RFIM_GET_CAPA,
},
.flags = WIPHY_VENDOR_CMD_NEED_WDEV,
.doit = iwl_vendor_rfim_get_capa,
#if CFG80211_VERSION >= KERNEL_VERSION(5,3,0)
.policy = iwl_mvm_vendor_attr_policy,
#endif
#if CFG80211_VERSION >= KERNEL_VERSION(5,3,0)
.maxattr = MAX_IWL_MVM_VENDOR_ATTR,
#endif
},
{
.info = {
.vendor_id = INTEL_OUI,
.subcmd = IWL_MVM_VENDOR_CMD_TIME_SYNC_MEASUREMENT_CONFIG,
},
.flags = WIPHY_VENDOR_CMD_NEED_WDEV |
WIPHY_VENDOR_CMD_NEED_RUNNING,
.doit = iwl_mvm_time_sync_measurement_config,
#if CFG80211_VERSION >= KERNEL_VERSION(5,3,0)
.policy = iwl_mvm_vendor_attr_policy,
#endif
#if CFG80211_VERSION >= KERNEL_VERSION(5,3,0)
.maxattr = MAX_IWL_MVM_VENDOR_ATTR,
#endif
},
{
.info = {
.vendor_id = INTEL_OUI,
.subcmd = IWL_MVM_VENDOR_CMD_GET_CSME_CONN_INFO,
},
.doit = iwl_mvm_vendor_get_csme_conn_info,
.flags = WIPHY_VENDOR_CMD_NEED_WDEV,
#if CFG80211_VERSION >= KERNEL_VERSION(5,3,0)
.policy = iwl_mvm_vendor_attr_policy,
#endif
#if CFG80211_VERSION >= KERNEL_VERSION(5,3,0)
.maxattr = MAX_IWL_MVM_VENDOR_ATTR,
#endif
},
{
.info = {
.vendor_id = INTEL_OUI,
.subcmd = IWL_MVM_VENDOR_CMD_HOST_GET_OWNERSHIP,
},
.doit = iwl_mvm_vendor_host_get_ownership,
.flags = WIPHY_VENDOR_CMD_NEED_WDEV,
#if CFG80211_VERSION >= KERNEL_VERSION(5,3,0)
.policy = iwl_mvm_vendor_attr_policy,
#endif
#if CFG80211_VERSION >= KERNEL_VERSION(5,3,0)
.maxattr = MAX_IWL_MVM_VENDOR_ATTR,
#endif
},
};
enum iwl_mvm_vendor_events_idx {
/* 0x0 is deprecated */
IWL_MVM_VENDOR_EVENT_IDX_CSI = 1,
IWL_MVM_VENDOR_EVENT_IDX_TSM_CFM,
IWL_MVM_VENDOR_EVENT_IDX_TSM_MSMT,
IWL_MVM_VENDOR_EVENT_IDX_ROAMING_FORBIDDEN,
NUM_IWL_MVM_VENDOR_EVENT_IDX
};
static const struct nl80211_vendor_cmd_info
iwl_mvm_vendor_events[NUM_IWL_MVM_VENDOR_EVENT_IDX] = {
[IWL_MVM_VENDOR_EVENT_IDX_CSI] = {
.vendor_id = INTEL_OUI,
.subcmd = IWL_MVM_VENDOR_CMD_CSI_EVENT,
},
[IWL_MVM_VENDOR_EVENT_IDX_TSM_CFM] = {
.vendor_id = INTEL_OUI,
.subcmd = IWL_MVM_VENDOR_CMD_TIME_SYNC_MSMT_CFM_EVENT,
},
[IWL_MVM_VENDOR_EVENT_IDX_TSM_MSMT] = {
.vendor_id = INTEL_OUI,
.subcmd = IWL_MVM_VENDOR_CMD_TIME_SYNC_MSMT_EVENT,
},
[IWL_MVM_VENDOR_EVENT_IDX_ROAMING_FORBIDDEN] = {
.vendor_id = INTEL_OUI,
.subcmd = IWL_MVM_VENDOR_CMD_ROAMING_FORBIDDEN_EVENT,
},
};
void iwl_mvm_vendor_cmds_register(struct iwl_mvm *mvm)
{
mvm->hw->wiphy->vendor_commands = iwl_mvm_vendor_commands;
mvm->hw->wiphy->n_vendor_commands = ARRAY_SIZE(iwl_mvm_vendor_commands);
mvm->hw->wiphy->vendor_events = iwl_mvm_vendor_events;
mvm->hw->wiphy->n_vendor_events = ARRAY_SIZE(iwl_mvm_vendor_events);
spin_lock_bh(&device_list_lock);
list_add_tail(&mvm->list, &device_list);
spin_unlock_bh(&device_list_lock);
}
void iwl_mvm_vendor_cmds_unregister(struct iwl_mvm *mvm)
{
spin_lock_bh(&device_list_lock);
list_del(&mvm->list);
spin_unlock_bh(&device_list_lock);
}
static void
iwl_mvm_send_csi_event(struct iwl_mvm *mvm,
void *hdr, unsigned int hdr_len,
void **data, unsigned int *len)
{
unsigned int data_len = 0;
struct sk_buff *msg;
struct nlattr *dattr;
u8 *pos;
int i;
if (!mvm->csi_portid)
return;
for (i = 0; len[i] && data[i]; i++)
data_len += len[i];
msg = cfg80211_vendor_event_alloc_ucast(mvm->hw->wiphy, NULL,
mvm->csi_portid,
100 + hdr_len + data_len,
IWL_MVM_VENDOR_EVENT_IDX_CSI,
GFP_KERNEL);
if (!msg)
return;
if (nla_put(msg, IWL_MVM_VENDOR_ATTR_CSI_HDR, hdr_len, hdr))
goto nla_put_failure;
dattr = nla_reserve(msg, IWL_MVM_VENDOR_ATTR_CSI_DATA, data_len);
if (!dattr)
goto nla_put_failure;
pos = nla_data(dattr);
for (i = 0; len[i] && data[i]; i++) {
memcpy(pos, data[i], len[i]);
pos += len[i];
}
cfg80211_vendor_event(msg, GFP_KERNEL);
return;
nla_put_failure:
kfree_skb(msg);
}
static inline u64 iwl_mvm_get_64_bit(u32 high, u32 low)
{
return ((u64)high << 32) | low;
}
void iwl_mvm_time_sync_msmt_confirm_event(struct iwl_mvm *mvm,
struct iwl_rx_cmd_buffer *rxb)
{
struct iwl_rx_packet *pkt = rxb_addr(rxb);
struct iwl_time_msmt_cfm_notify *cfm_notify = (void *)pkt->data;
u64 t1;
u64 t4;
struct sk_buff *msg =
cfg80211_vendor_event_alloc(mvm->hw->wiphy, mvm->time_sync_wdev,
200,
IWL_MVM_VENDOR_EVENT_IDX_TSM_CFM,
GFP_ATOMIC);
if (!msg)
return;
t1 = iwl_mvm_get_64_bit(le32_to_cpu(cfm_notify->t1_hi),
le32_to_cpu(cfm_notify->t1_lo));
t4 = iwl_mvm_get_64_bit(le32_to_cpu(cfm_notify->t4_hi),
le32_to_cpu(cfm_notify->t4_lo));
if (!t1 || !t4)
IWL_WARN(mvm, "TSM CFM: Rx'ed zero timestamp(s), t1:%llu, t4:%llu\n",
t1, t4);
if (nla_put(msg, IWL_MVM_VENDOR_ATTR_ADDR,
ETH_ALEN, cfm_notify->peer_addr) ||
nla_put_u32(msg, IWL_MVM_VENDOR_ATTR_TIME_SYNC_DIALOG_TOKEN,
le32_to_cpu(cfm_notify->dialog_token)) ||
nla_put_u64_64bit(msg, IWL_MVM_VENDOR_ATTR_TIME_SYNC_T1, t1,
IWL_MVM_VENDOR_ATTR_PAD) ||
nla_put_u32(msg, IWL_MVM_VENDOR_ATTR_TIME_SYNC_T1_MAX_ERROR,
le32_to_cpu(cfm_notify->t1_max_err)) ||
nla_put_u64_64bit(msg, IWL_MVM_VENDOR_ATTR_TIME_SYNC_T4, t4,
IWL_MVM_VENDOR_ATTR_PAD) ||
nla_put_u32(msg, IWL_MVM_VENDOR_ATTR_TIME_SYNC_T4_MAX_ERROR,
le32_to_cpu(cfm_notify->t4_max_err))) {
goto nla_put_failure;
}
cfg80211_vendor_event(msg, GFP_ATOMIC);
return;
nla_put_failure:
kfree_skb(msg);
}
void iwl_mvm_time_sync_msmt_event(struct iwl_mvm *mvm,
struct iwl_rx_cmd_buffer *rxb)
{
struct iwl_rx_packet *pkt = rxb_addr(rxb);
struct iwl_time_msmt_notify *msmt_notify = (void *)pkt->data;
u64 t1;
u64 t4;
u64 t2;
u64 t3;
struct sk_buff *msg =
cfg80211_vendor_event_alloc(mvm->hw->wiphy, mvm->time_sync_wdev,
200 + PTP_CTX_MAX_DATA_SIZE,
IWL_MVM_VENDOR_EVENT_IDX_TSM_MSMT,
GFP_ATOMIC);
if (!msg)
return;
t1 = iwl_mvm_get_64_bit(le32_to_cpu(msmt_notify->t1_hi),
le32_to_cpu(msmt_notify->t1_lo));
t4 = iwl_mvm_get_64_bit(le32_to_cpu(msmt_notify->t4_hi),
le32_to_cpu(msmt_notify->t4_lo));
t2 = iwl_mvm_get_64_bit(le32_to_cpu(msmt_notify->t2_hi),
le32_to_cpu(msmt_notify->t2_lo));
t3 = iwl_mvm_get_64_bit(le32_to_cpu(msmt_notify->t3_hi),
le32_to_cpu(msmt_notify->t3_lo));
if (!t1 || !t4)
IWL_WARN(mvm, "TSM MSMT: Rx'ed zero timestamps, t1:%llu, t4:%llu\n",
t1, t4);
if (!t2 || !t3)
IWL_WARN(mvm, "TSM MSMT: Rx'ed zero timestamps, t2:%llu, t3:%llu\n",
t2, t3);
if (nla_put(msg, IWL_MVM_VENDOR_ATTR_ADDR,
ETH_ALEN, msmt_notify->peer_addr) ||
nla_put_u32(msg, IWL_MVM_VENDOR_ATTR_TIME_SYNC_DIALOG_TOKEN,
le32_to_cpu(msmt_notify->dialog_token)) ||
nla_put_u32(msg, IWL_MVM_VENDOR_ATTR_TIME_SYNC_FUP_DIALOG_TOKEN,
le32_to_cpu(msmt_notify->followup_dialog_token)) ||
nla_put_u64_64bit(msg, IWL_MVM_VENDOR_ATTR_TIME_SYNC_T1, t1,
IWL_MVM_VENDOR_ATTR_PAD) ||
nla_put_u32(msg, IWL_MVM_VENDOR_ATTR_TIME_SYNC_T1_MAX_ERROR,
le32_to_cpu(msmt_notify->t1_max_err)) ||
nla_put_u64_64bit(msg, IWL_MVM_VENDOR_ATTR_TIME_SYNC_T4, t4,
IWL_MVM_VENDOR_ATTR_PAD) ||
nla_put_u32(msg, IWL_MVM_VENDOR_ATTR_TIME_SYNC_T4_MAX_ERROR,
le32_to_cpu(msmt_notify->t4_max_err)) ||
nla_put_u64_64bit(msg, IWL_MVM_VENDOR_ATTR_TIME_SYNC_T2, t2,
IWL_MVM_VENDOR_ATTR_PAD) ||
nla_put_u32(msg, IWL_MVM_VENDOR_ATTR_TIME_SYNC_T2_MAX_ERROR,
le32_to_cpu(msmt_notify->t2_max_err)) ||
nla_put_u64_64bit(msg, IWL_MVM_VENDOR_ATTR_TIME_SYNC_T3, t3,
IWL_MVM_VENDOR_ATTR_PAD) ||
nla_put_u32(msg, IWL_MVM_VENDOR_ATTR_TIME_SYNC_T3_MAX_ERROR,
le32_to_cpu(msmt_notify->t3_max_err))) {
goto nla_put_failure;
}
if (mvm->time_msmt_cfg == IWL_MVM_VENDOR_TIME_SYNC_PROTOCOL_FTM) {
if (nla_put(msg, IWL_MVM_VENDOR_ATTR_TIME_SYNC_VS_DATA,
msmt_notify->ptp.ftm.length, msmt_notify->ptp.ftm.data))
goto nla_put_failure;
} else if (mvm->time_msmt_cfg == IWL_MVM_VENDOR_TIME_SYNC_PROTOCOL_TM) {
if (nla_put(msg, IWL_MVM_VENDOR_ATTR_TIME_SYNC_VS_DATA,
msmt_notify->ptp.tm.length, msmt_notify->ptp.tm.data))
goto nla_put_failure;
} else {
IWL_WARN(mvm, "TSM MSMT: Unknown protocol config saved %d\n",
mvm->time_msmt_cfg);
goto nla_put_failure;
}
cfg80211_vendor_event(msg, GFP_ATOMIC);
return;
nla_put_failure:
IWL_ERR(mvm, "(%d) TSM MSMT: nla_put failed\n", __LINE__);
kfree_skb(msg);
}
static void iwl_mvm_csi_free_pages(struct iwl_mvm *mvm)
{
int idx;
for (idx = 0; idx < ARRAY_SIZE(mvm->csi_data_entries); idx++) {
if (mvm->csi_data_entries[idx].page) {
__free_pages(mvm->csi_data_entries[idx].page,
mvm->csi_data_entries[idx].page_order);
memset(&mvm->csi_data_entries[idx], 0,
sizeof(mvm->csi_data_entries[idx]));
}
}
}
static void iwl_mvm_csi_steal(struct iwl_mvm *mvm, unsigned int idx,
struct iwl_rx_cmd_buffer *rxb)
{
/* firmware is ... confused */
if (WARN_ON(mvm->csi_data_entries[idx].page)) {
iwl_mvm_csi_free_pages(mvm);
return;
}
mvm->csi_data_entries[idx].page = rxb_steal_page(rxb);
mvm->csi_data_entries[idx].page_order = rxb->_rx_page_order;
mvm->csi_data_entries[idx].offset = rxb->_offset;
}
void iwl_mvm_rx_csi_header(struct iwl_mvm *mvm, struct iwl_rx_cmd_buffer *rxb)
{
iwl_mvm_csi_steal(mvm, 0, rxb);
}
static void iwl_mvm_csi_complete(struct iwl_mvm *mvm)
{
struct iwl_rx_packet *hdr_pkt;
struct iwl_csi_data_buffer *hdr_buf = &mvm->csi_data_entries[0];
void *data[IWL_CSI_MAX_EXPECTED_CHUNKS + 1] = {};
unsigned int len[IWL_CSI_MAX_EXPECTED_CHUNKS + 1] = {};
unsigned int csi_hdr_len;
void *csi_hdr;
int i;
/*
* Ensure we have the right # of entries, the local data/len
* variables include a terminating entry, csi_data_entries
* instead has one place for the header.
*/
BUILD_BUG_ON(ARRAY_SIZE(data) < ARRAY_SIZE(mvm->csi_data_entries));
BUILD_BUG_ON(ARRAY_SIZE(len) < ARRAY_SIZE(mvm->csi_data_entries));
/* need at least the header and first fragment */
if (WARN_ON(!hdr_buf->page || !mvm->csi_data_entries[1].page)) {
iwl_mvm_csi_free_pages(mvm);
return;
}
hdr_pkt = (void *)((unsigned long)page_address(hdr_buf->page) +
hdr_buf->offset);
csi_hdr = (void *)hdr_pkt->data;
csi_hdr_len = iwl_rx_packet_payload_len(hdr_pkt);
for (i = 1; i < ARRAY_SIZE(mvm->csi_data_entries); i++) {
struct iwl_csi_data_buffer *buf = &mvm->csi_data_entries[i];
struct iwl_rx_packet *pkt;
struct iwl_csi_chunk_notification *chunk;
unsigned int chunk_len;
if (!buf->page)
break;
pkt = (void *)((unsigned long)page_address(buf->page) +
buf->offset);
chunk = (void *)pkt->data;
chunk_len = iwl_rx_packet_payload_len(pkt);
if (sizeof(*chunk) + le32_to_cpu(chunk->size) > chunk_len)
goto free;
data[i - 1] = chunk->data;
len[i - 1] = le32_to_cpu(chunk->size);
}
iwl_mvm_send_csi_event(mvm, csi_hdr, csi_hdr_len, data, len);
free:
iwl_mvm_csi_free_pages(mvm);
}
void iwl_mvm_rx_csi_chunk(struct iwl_mvm *mvm, struct iwl_rx_cmd_buffer *rxb)
{
struct iwl_rx_packet *pkt = rxb_addr(rxb);
struct iwl_csi_chunk_notification *chunk = (void *)pkt->data;
int num;
int idx;
switch (mvm->cmd_ver.csi_notif) {
case 1:
num = le16_get_bits(chunk->ctl,
IWL_CSI_CHUNK_CTL_NUM_MASK_VER_1);
idx = le16_get_bits(chunk->ctl,
IWL_CSI_CHUNK_CTL_IDX_MASK_VER_1) + 1;
break;
case 2:
num = le16_get_bits(chunk->ctl,
IWL_CSI_CHUNK_CTL_NUM_MASK_VER_2);
idx = le16_get_bits(chunk->ctl,
IWL_CSI_CHUNK_CTL_IDX_MASK_VER_2) + 1;
break;
default:
WARN_ON(1);
return;
}
/* -1 to account for the header we also store there */
if (WARN_ON_ONCE(idx >= ARRAY_SIZE(mvm->csi_data_entries) - 1))
return;
iwl_mvm_csi_steal(mvm, idx, rxb);
if (num == idx)
iwl_mvm_csi_complete(mvm);
}
void iwl_mvm_send_roaming_forbidden_event(struct iwl_mvm *mvm,
struct ieee80211_vif *vif,
bool forbidden)
{
struct sk_buff *msg =
cfg80211_vendor_event_alloc(mvm->hw->wiphy,
ieee80211_vif_to_wdev(vif),
200, IWL_MVM_VENDOR_EVENT_IDX_ROAMING_FORBIDDEN,
GFP_ATOMIC);
if (!msg)
return;
if (WARN_ON(!vif))
return;
if (nla_put(msg, IWL_MVM_VENDOR_ATTR_VIF_ADDR,
ETH_ALEN, vif->addr) ||
nla_put_u8(msg, IWL_MVM_VENDOR_ATTR_ROAMING_FORBIDDEN, forbidden))
goto nla_put_failure;
cfg80211_vendor_event(msg, GFP_ATOMIC);
return;
nla_put_failure:
kfree_skb(msg);
}