#include "key.h"
#include "iface.h"
#include "sta.h"
#include "fw/api/datapath.h"
static u32 iwl_mld_get_key_flags(struct iwl_mld *mld,
struct ieee80211_vif *vif,
struct ieee80211_sta *sta,
struct ieee80211_key_conf *key)
{
struct iwl_mld_vif *mld_vif = iwl_mld_vif_from_mac80211(vif);
bool pairwise = key->flags & IEEE80211_KEY_FLAG_PAIRWISE;
bool igtk = key->keyidx == 4 || key->keyidx == 5;
u32 flags = 0;
if (!pairwise)
flags |= IWL_SEC_KEY_FLAG_MCAST_KEY;
switch (key->cipher) {
case WLAN_CIPHER_SUITE_TKIP:
flags |= IWL_SEC_KEY_FLAG_CIPHER_TKIP;
break;
case WLAN_CIPHER_SUITE_AES_CMAC:
case WLAN_CIPHER_SUITE_CCMP:
flags |= IWL_SEC_KEY_FLAG_CIPHER_CCMP;
break;
case WLAN_CIPHER_SUITE_GCMP_256:
case WLAN_CIPHER_SUITE_BIP_GMAC_256:
flags |= IWL_SEC_KEY_FLAG_KEY_SIZE;
fallthrough;
case WLAN_CIPHER_SUITE_GCMP:
case WLAN_CIPHER_SUITE_BIP_GMAC_128:
flags |= IWL_SEC_KEY_FLAG_CIPHER_GCMP;
break;
}
if (!sta && vif->type == NL80211_IFTYPE_STATION)
sta = mld_vif->ap_sta;
if ((sta && sta->mfp && pairwise) || igtk)
flags |= IWL_SEC_KEY_FLAG_MFP;
if (key->flags & IEEE80211_KEY_FLAG_SPP_AMSDU)
flags |= IWL_SEC_KEY_FLAG_SPP_AMSDU;
return flags;
}
static u32 iwl_mld_get_key_sta_mask(struct iwl_mld *mld,
struct ieee80211_vif *vif,
struct ieee80211_sta *sta,
struct ieee80211_key_conf *key)
{
struct iwl_mld_vif *mld_vif = iwl_mld_vif_from_mac80211(vif);
struct ieee80211_link_sta *link_sta;
int sta_id;
lockdep_assert_wiphy(mld->wiphy);
if (vif->type == NL80211_IFTYPE_AP &&
!(key->flags & IEEE80211_KEY_FLAG_PAIRWISE)) {
struct iwl_mld_link *link = NULL;
if (key->link_id >= 0)
link = iwl_mld_link_dereference_check(mld_vif,
key->link_id);
if (WARN_ON(!link))
return 0;
if (WARN_ON(link->bcast_sta.sta_id == IWL_INVALID_STA ||
link->mcast_sta.sta_id == IWL_INVALID_STA))
return 0;
if (key->keyidx >= 4)
return BIT(link->bcast_sta.sta_id);
return BIT(link->mcast_sta.sta_id);
}
if (!sta && vif->type == NL80211_IFTYPE_STATION)
sta = mld_vif->ap_sta;
if (WARN_ON(!sta))
return 0;
if (key->link_id < 0)
return iwl_mld_fw_sta_id_mask(mld, sta);
link_sta = link_sta_dereference_check(sta, key->link_id);
sta_id = iwl_mld_fw_sta_id_from_link_sta(mld, link_sta);
if (sta_id < 0)
return 0;
return BIT(sta_id);
}
static int iwl_mld_add_key_to_fw(struct iwl_mld *mld, u32 sta_mask,
u32 key_flags, struct ieee80211_key_conf *key)
{
struct iwl_sec_key_cmd cmd = {
.action = cpu_to_le32(FW_CTXT_ACTION_ADD),
.u.add.sta_mask = cpu_to_le32(sta_mask),
.u.add.key_id = cpu_to_le32(key->keyidx),
.u.add.key_flags = cpu_to_le32(key_flags),
.u.add.tx_seq = cpu_to_le64(atomic64_read(&key->tx_pn)),
};
bool tkip = key->cipher == WLAN_CIPHER_SUITE_TKIP;
int max_key_len = sizeof(cmd.u.add.key);
#ifdef CONFIG_PM_SLEEP
if (mld->fw_status.resuming)
return 0;
#endif
if (WARN_ON(!sta_mask))
return -EINVAL;
if (WARN_ON(key->keylen > max_key_len))
return -EINVAL;
memcpy(cmd.u.add.key, key->key, key->keylen);
if (tkip) {
memcpy(cmd.u.add.tkip_mic_rx_key,
key->key + NL80211_TKIP_DATA_OFFSET_RX_MIC_KEY,
8);
memcpy(cmd.u.add.tkip_mic_tx_key,
key->key + NL80211_TKIP_DATA_OFFSET_TX_MIC_KEY,
8);
}
return iwl_mld_send_cmd_pdu(mld, WIDE_ID(DATA_PATH_GROUP, SEC_KEY_CMD),
&cmd);
}
static void iwl_mld_remove_key_from_fw(struct iwl_mld *mld, u32 sta_mask,
u32 key_flags, u32 keyidx)
{
struct iwl_sec_key_cmd cmd = {
.action = cpu_to_le32(FW_CTXT_ACTION_REMOVE),
.u.remove.sta_mask = cpu_to_le32(sta_mask),
.u.remove.key_id = cpu_to_le32(keyidx),
.u.remove.key_flags = cpu_to_le32(key_flags),
};
#ifdef CONFIG_PM_SLEEP
if (mld->fw_status.resuming)
return;
#endif
if (WARN_ON(!sta_mask))
return;
iwl_mld_send_cmd_pdu(mld, WIDE_ID(DATA_PATH_GROUP, SEC_KEY_CMD), &cmd);
}
void iwl_mld_remove_key(struct iwl_mld *mld, struct ieee80211_vif *vif,
struct ieee80211_sta *sta,
struct ieee80211_key_conf *key)
{
u32 sta_mask = iwl_mld_get_key_sta_mask(mld, vif, sta, key);
u32 key_flags = iwl_mld_get_key_flags(mld, vif, sta, key);
struct iwl_mld_vif *mld_vif = iwl_mld_vif_from_mac80211(vif);
lockdep_assert_wiphy(mld->wiphy);
if (!sta_mask)
return;
if (key->keyidx == 4 || key->keyidx == 5) {
struct iwl_mld_link *mld_link;
unsigned int link_id = 0;
if (key->link_id >= 0)
link_id = key->link_id;
mld_link = iwl_mld_link_dereference_check(mld_vif, link_id);
if (WARN_ON(!mld_link))
return;
if (mld_link->igtk == key)
mld_link->igtk = NULL;
mld->num_igtks--;
}
iwl_mld_remove_key_from_fw(mld, sta_mask, key_flags, key->keyidx);
key->hw_key_idx = STA_KEY_IDX_INVALID;
}
int iwl_mld_add_key(struct iwl_mld *mld,
struct ieee80211_vif *vif,
struct ieee80211_sta *sta,
struct ieee80211_key_conf *key)
{
u32 sta_mask = iwl_mld_get_key_sta_mask(mld, vif, sta, key);
u32 key_flags = iwl_mld_get_key_flags(mld, vif, sta, key);
struct iwl_mld_vif *mld_vif = iwl_mld_vif_from_mac80211(vif);
struct iwl_mld_link *mld_link = NULL;
bool igtk = key->keyidx == 4 || key->keyidx == 5;
int ret;
lockdep_assert_wiphy(mld->wiphy);
if (!sta_mask)
return -EINVAL;
if (igtk) {
if (mld->num_igtks == IWL_MAX_NUM_IGTKS)
return -EOPNOTSUPP;
u8 link_id = 0;
if (key->link_id >= 0)
link_id = key->link_id;
mld_link = iwl_mld_link_dereference_check(mld_vif, link_id);
if (WARN_ON(!mld_link))
return -EINVAL;
if (mld_link->igtk) {
IWL_DEBUG_MAC80211(mld, "remove old IGTK %d\n",
mld_link->igtk->keyidx);
iwl_mld_remove_key(mld, vif, sta, mld_link->igtk);
}
WARN_ON(mld_link->igtk);
}
ret = iwl_mld_add_key_to_fw(mld, sta_mask, key_flags, key);
if (ret)
return ret;
if (mld_link) {
mld_link->igtk = key;
mld->num_igtks++;
}
key->hw_key_idx = 0;
return 0;
}
struct remove_ap_keys_iter_data {
u8 link_id;
struct ieee80211_sta *sta;
};
static void iwl_mld_remove_ap_keys_iter(struct ieee80211_hw *hw,
struct ieee80211_vif *vif,
struct ieee80211_sta *sta,
struct ieee80211_key_conf *key,
void *_data)
{
struct iwl_mld *mld = IWL_MAC80211_GET_MLD(hw);
struct remove_ap_keys_iter_data *data = _data;
if (key->hw_key_idx == STA_KEY_IDX_INVALID)
return;
if (WARN_ON(sta))
return;
if (key->link_id >= 0 && key->link_id != data->link_id)
return;
iwl_mld_remove_key(mld, vif, data->sta, key);
}
void iwl_mld_remove_ap_keys(struct iwl_mld *mld, struct ieee80211_vif *vif,
struct ieee80211_sta *sta, unsigned int link_id)
{
struct remove_ap_keys_iter_data iter_data = {
.link_id = link_id,
.sta = sta,
};
if (WARN_ON_ONCE(vif->type != NL80211_IFTYPE_STATION))
return;
ieee80211_iter_keys(mld->hw, vif,
iwl_mld_remove_ap_keys_iter,
&iter_data);
}
struct iwl_mvm_sta_key_update_data {
struct ieee80211_sta *sta;
u32 old_sta_mask;
u32 new_sta_mask;
int err;
};
static void iwl_mld_update_sta_key_iter(struct ieee80211_hw *hw,
struct ieee80211_vif *vif,
struct ieee80211_sta *sta,
struct ieee80211_key_conf *key,
void *_data)
{
struct iwl_mvm_sta_key_update_data *data = _data;
struct iwl_mld *mld = IWL_MAC80211_GET_MLD(hw);
struct iwl_sec_key_cmd cmd = {
.action = cpu_to_le32(FW_CTXT_ACTION_MODIFY),
.u.modify.old_sta_mask = cpu_to_le32(data->old_sta_mask),
.u.modify.new_sta_mask = cpu_to_le32(data->new_sta_mask),
.u.modify.key_id = cpu_to_le32(key->keyidx),
.u.modify.key_flags =
cpu_to_le32(iwl_mld_get_key_flags(mld, vif, sta, key)),
};
int err;
if (sta != data->sta || key->link_id >= 0)
return;
err = iwl_mld_send_cmd_pdu(mld, WIDE_ID(DATA_PATH_GROUP, SEC_KEY_CMD),
&cmd);
if (err)
data->err = err;
}
int iwl_mld_update_sta_keys(struct iwl_mld *mld,
struct ieee80211_vif *vif,
struct ieee80211_sta *sta,
u32 old_sta_mask,
u32 new_sta_mask)
{
struct iwl_mvm_sta_key_update_data data = {
.sta = sta,
.old_sta_mask = old_sta_mask,
.new_sta_mask = new_sta_mask,
};
ieee80211_iter_keys(mld->hw, vif, iwl_mld_update_sta_key_iter,
&data);
return data.err;
}