blob: 49c896610a004056231b9d3a4bf6c4f31a63eb31 [file] [log] [blame]
// Copyright 2022 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "third_party/iwlwifi/platform/mvm-sta.h"
#include <fidl/fuchsia.wlan.ieee80211/cpp/wire.h>
#include <threads.h>
#include <zircon/errors.h>
#include <zircon/status.h>
#include <algorithm>
#include <cstring>
extern "C" {
#include "third_party/iwlwifi/mvm/sta.h"
} // extern "C"
#include "third_party/iwlwifi/platform/ieee80211.h"
#include "third_party/iwlwifi/platform/mvm-mlme.h"
#include "third_party/iwlwifi/platform/rcu.h"
namespace wlan::iwlwifi {
namespace {
// IEEE 802.11-2016 3.2 (c.f. "vendor organizationally unique identifier")
constexpr uint8_t kIeeeOui[] = {0x00, 0x0F, 0xAC};
} // namespace
MvmSta::MvmSta(struct iwl_mvm_vif* iwl_mvm_vif, std::unique_ptr<struct iwl_mvm_sta> iwl_mvm_sta)
: iwl_mvm_vif_(iwl_mvm_vif), iwl_mvm_sta_(std::move(iwl_mvm_sta)) {}
MvmSta::~MvmSta() {
if (iwl_mvm_sta_ != nullptr) {
zx_status_t status = ZX_OK;
iwl_mvm_vif* mvmvif = iwl_mvm_sta_->mvmvif;
for (auto& key_conf : ieee80211_key_confs_) {
if (key_conf == nullptr) {
continue;
}
if ((status = iwl_mvm_mac_remove_key(mvmvif, iwl_mvm_sta_.get(), key_conf.get())) != ZX_OK) {
IWL_ERR(iwl_mvm_vif_, "iwl_mvm_mac_remove_key() failed for keyidx %d: %s\n",
key_conf->keyidx, zx_status_get_string(status));
}
key_conf.reset();
}
if ((status = ChangeState(iwl_sta_state::IWL_STA_NOTEXIST)) != ZX_OK) {
IWL_ERR(iwl_mvm_vif_, "ChangeState() failed: %s\n", zx_status_get_string(status));
}
if ((mvmvif) && (mvmvif->ap_sta_id != IWL_MVM_INVALID_STA)) {
// STA is still in a connected state, clean it up.
if (mac_clear_association(mvmvif, mvmvif->addr) != ZX_OK) {
IWL_ERR(mvmvif, "Unable to clear assoc during sta delete");
}
}
iwl_rcu_call_sync(
iwl_mvm_vif_->mvm->dev,
[](void* data) {
auto mvm_sta = reinterpret_cast<struct iwl_mvm_sta*>(data);
for (auto txq : mvm_sta->txq) {
delete txq;
}
delete mvm_sta;
},
iwl_mvm_sta_.release());
}
}
// static
zx_status_t MvmSta::Create(struct iwl_mvm_vif* iwl_mvm_vif, const uint8_t bssid[ETH_ALEN],
std::unique_ptr<MvmSta>* mvm_sta_out) {
zx_status_t status = ZX_OK;
// Initialize the iwl_mvm_sta instance.
auto iwl_mvm_sta = std::make_unique<struct iwl_mvm_sta>();
for (auto& txq_ref : iwl_mvm_sta->txq) {
txq_ref = new struct iwl_mvm_txq();
}
static_assert(sizeof(iwl_mvm_sta->addr) == sizeof(*bssid) * ETH_ALEN);
std::memcpy(iwl_mvm_sta->addr, bssid, sizeof(iwl_mvm_sta->addr));
mtx_init(&iwl_mvm_sta->lock, mtx_plain);
auto mvm_sta = std::unique_ptr<MvmSta>(new MvmSta(iwl_mvm_vif, std::move(iwl_mvm_sta)));
if ((status = mvm_sta->ChangeState(iwl_sta_state::IWL_STA_NONE)) != ZX_OK) {
return status;
}
{
// Allocate a TX queue for this station.
auto iwl_mvm = mvm_sta->iwl_mvm_vif_->mvm;
auto lock = std::lock_guard(iwl_mvm->mutex);
if ((status = iwl_mvm_sta_alloc_queue(iwl_mvm, mvm_sta->iwl_mvm_sta_.get(), IEEE80211_AC_BE,
IWL_MAX_TID_COUNT)) != ZX_OK) {
IWL_ERR(iwl_mvm, "iwl_mvm_sta_alloc_queue() failed: %s\n", zx_status_get_string(status));
return status;
}
}
*mvm_sta_out = std::move(mvm_sta);
return status;
}
zx_status_t MvmSta::InstallKey(const fuchsia_wlan_softmac::wire::WlanKeyConfiguration* key_config) {
zx_status_t status = ZX_OK;
struct iwl_mvm* const mvm = iwl_mvm_sta_->mvmvif->mvm;
if (!(key_config->has_cipher_oui() && key_config->has_key_type() && key_config->has_key() &&
key_config->has_cipher_type() && key_config->has_key_idx() && key_config->has_rsc())) {
IWL_ERR(mvmvif, "WlanKeyConfiguration missing fields: %s %s %s %s %s %s.",
key_config->has_cipher_oui() ? "" : "cipher_oui",
key_config->has_key_type() ? "" : "key_type", key_config->has_key() ? "" : "key",
key_config->has_cipher_type() ? "" : "cipher_type",
key_config->has_key_idx() ? "" : "key_idx", key_config->has_rsc() ? "" : "rsc");
return ZX_ERR_INVALID_ARGS;
}
if (mvm->trans->trans_cfg->gen2 || iwl_mvm_has_new_tx_api(mvm)) {
// The new firmwares (for starting with the 22000 series) have different packet generation
// requirements than mentioned below.
// TODO(b/328494216): remove this check once we fix the WPA2/3 DHCP problem.
return ZX_ERR_NOT_SUPPORTED;
}
uint8_t key_type = static_cast<uint8_t>(key_config->key_type());
if (!std::equal(key_config->cipher_oui().begin(),
key_config->cipher_oui().begin() + key_config->cipher_oui().size(), kIeeeOui,
kIeeeOui + std::size(kIeeeOui))) {
// IEEE 802.11-2016 9.4.2.25.2
// The standard ciphers all live in the IEEE space.
IWL_ERR(mvmvif, "Cipher OUI must be %02X:%02X:%02X. OUI %02X:%02X:%02X is not supported.",
kIeeeOui[0], kIeeeOui[1], kIeeeOui[2], key_config->cipher_oui()[0],
key_config->cipher_oui()[1], key_config->cipher_oui()[2]);
return ZX_ERR_NOT_SUPPORTED;
}
if (key_type >= ieee80211_key_confs_.size()) {
IWL_ERR(mvmvif, "Unknown key type: %hhu.", key_type);
return ZX_ERR_INVALID_ARGS;
}
// Remove any existing key in this slot.
if (ieee80211_key_confs_[key_type] != nullptr) {
if ((status = iwl_mvm_mac_remove_key(iwl_mvm_sta_->mvmvif, iwl_mvm_sta_.get(),
ieee80211_key_confs_[key_type].get())) != ZX_OK) {
IWL_ERR(mvmvif, "iwl_mvm_mac_remove_key() failed: %s\n", zx_status_get_string(status));
return status;
}
ieee80211_key_confs_[key_type].reset();
}
unique_free_ptr<struct ieee80211_key_conf> key_conf(reinterpret_cast<struct ieee80211_key_conf*>(
calloc(1, sizeof(ieee80211_key_conf) + key_config->key().count())));
key_conf->key_type = key_type;
key_conf->cipher = key_config->cipher_type();
key_conf->keyidx = key_config->key_idx();
key_conf->keylen = key_config->key().count();
key_conf->rx_seq = key_config->rsc();
IWL_INFO(mvm, "Setting a new crypto key (type: %d, cipher: %d) request.\n",
key_conf->key_type, // WLAN_KEY_TYPE_*
key_conf->cipher); // CIPHER_SUITE_TYPE_*
if (key_conf->cipher == CIPHER_SUITE_TYPE_TKIP) {
// A special trick for TKIP group key: Swap the latest 2 8-byte (MIC-KEY-TX and MIC-KEY-RX).
//
// MLME copies the whole 32-byte from the AP packet which the "TX" means "AP TX". So when we
// write to the firmware, it is acually the "firmware RX".
//
// IEEE 802.11-2007 8.6.2 Mapping GTK to TKIP keys
//
// See 8.5.1.3 for the definition of the EAPOL temporal key derived from GTK.
// A STA shall use bits 0–127 of the temporal key as the input to the TKIP Phase 1 and Phase 2
// mixing functions.
// A STA shall use bits 128–191 of the temporal key as the Michael key for MSDUs from the
// Authenticator’s STA to the Supplicant’s STA.
// A STA shall use bits 192–255 of the temporal key as the Michael key for MSDUs from the
// Supplicant’s STA to the Authenticator’s STA.
//
ZX_ASSERT_MSG(key_config->key().count() == 32, "TKIP key length must be 32. Found %zu",
key_config->key().count());
memcpy(&key_conf->key[0], &key_config->key().begin()[0], 16); // TK (Temporal Key)
memcpy(&key_conf->key[16], &key_config->key().begin()[24], 8); // AP TX ==> FW RX
memcpy(&key_conf->key[24], &key_config->key().begin()[16], 8); // AP RX ==> FW TX, which is not
// used in TKIP group key.
} else {
memcpy(key_conf->key, key_config->key().begin(), key_conf->keylen);
}
if ((status = iwl_mvm_mac_add_key(iwl_mvm_sta_->mvmvif, iwl_mvm_sta_.get(), key_conf.get())) !=
ZX_OK) {
IWL_ERR(mvmvif, "iwl_mvm_mac_add_key(key_type %hhu, cipher_type %hhu, key_idx %hhu) failed:%s",
key_type, key_config->cipher_type(), key_config->key_idx(),
zx_status_get_string(status));
return status;
}
ieee80211_key_confs_[key_type] = std::move(key_conf);
return ZX_OK;
}
struct ieee80211_key_conf* MvmSta::GetKey(wlan_key_type_t key_type) {
if (key_type < 0 || key_type > ieee80211_key_confs_.size()) {
return nullptr;
}
return ieee80211_key_confs_[key_type].get();
}
const struct ieee80211_key_conf* MvmSta::GetKey(wlan_key_type_t key_type) const {
if (key_type < 0 || key_type > ieee80211_key_confs_.size()) {
return nullptr;
}
return ieee80211_key_confs_[key_type].get();
}
enum iwl_sta_state MvmSta::GetState() const { return iwl_mvm_sta_.get()->sta_state; }
zx_status_t MvmSta::ChangeState(enum iwl_sta_state state) {
zx_status_t status = ZX_OK;
while (state > GetState()) {
if ((status = ChangeStateUp()) != ZX_OK) {
return status;
}
}
while (state < GetState()) {
if ((status = ChangeStateDown()) != ZX_OK) {
return status;
}
}
return ZX_OK;
}
struct iwl_mvm_sta* MvmSta::iwl_mvm_sta() { return iwl_mvm_sta_.get(); }
const struct iwl_mvm_sta* MvmSta::iwl_mvm_sta() const { return iwl_mvm_sta_.get(); }
zx_status_t MvmSta::ChangeStateUp() {
zx_status_t status = ZX_OK;
iwl_sta_state new_state = iwl_sta_state::IWL_STA_NOTEXIST;
switch (GetState()) {
case iwl_sta_state::IWL_STA_NOTEXIST: {
new_state = iwl_sta_state::IWL_STA_NONE;
break;
}
case iwl_sta_state::IWL_STA_NONE: {
new_state = iwl_sta_state::IWL_STA_AUTH;
break;
}
case iwl_sta_state::IWL_STA_AUTH: {
new_state = iwl_sta_state::IWL_STA_ASSOC;
break;
}
case iwl_sta_state::IWL_STA_ASSOC: {
new_state = iwl_sta_state::IWL_STA_AUTHORIZED;
break;
}
default: {
IWL_ERR(iwl_mvm_vif_, "ChangeStateUp() in invalid state %d\n", GetState());
return ZX_ERR_BAD_STATE;
}
}
if ((status = iwl_mvm_mac_sta_state(iwl_mvm_vif_, iwl_mvm_sta_.get(), GetState(), new_state)) !=
ZX_OK) {
IWL_ERR(iwl_mvm_vif_, "iwl_mvm_mac_sta_state() failed for %d -> %d: %s\n", GetState(),
new_state, zx_status_get_string(status));
return status;
}
return ZX_OK;
}
zx_status_t MvmSta::ChangeStateDown() {
zx_status_t status = ZX_OK;
iwl_sta_state new_state = iwl_sta_state::IWL_STA_NOTEXIST;
switch (GetState()) {
case iwl_sta_state::IWL_STA_AUTHORIZED: {
new_state = iwl_sta_state::IWL_STA_ASSOC;
break;
}
case iwl_sta_state::IWL_STA_ASSOC: {
new_state = iwl_sta_state::IWL_STA_AUTH;
break;
}
case iwl_sta_state::IWL_STA_AUTH: {
new_state = iwl_sta_state::IWL_STA_NONE;
break;
}
case iwl_sta_state::IWL_STA_NONE: {
{
// Tell firmware to flush all packets in the Tx queue. This must be done before we remove
// the STA (in the NONE->NOTEXIST transition).
// TODO(79799): understand why we need this.
auto lock = std::lock_guard(iwl_mvm_vif_->mvm->mutex);
iwl_mvm_flush_sta(iwl_mvm_vif_->mvm, iwl_mvm_sta_.get(), false, 0);
}
new_state = iwl_sta_state::IWL_STA_NOTEXIST;
break;
}
default: {
IWL_ERR(iwl_mvm_vif_, "ChangeStateDown() in invalid state %d\n", GetState());
return ZX_ERR_BAD_STATE;
}
}
if ((status = iwl_mvm_mac_sta_state(iwl_mvm_vif_, iwl_mvm_sta_.get(), GetState(), new_state)) !=
ZX_OK) {
IWL_ERR(iwl_mvm_vif_, "iwl_mvm_mac_sta_state() failed for %d -> %d: %s\n", GetState(),
new_state, zx_status_get_string(status));
return status;
}
return ZX_OK;
}
} // namespace wlan::iwlwifi