blob: 2cdf130846d515a1cd7a1e6e7f74d8dd4d9e840e [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_include.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;
for (auto& key_conf : ieee80211_key_confs_) {
if (key_conf == nullptr) {
continue;
}
if ((status = iwl_mvm_mac_remove_key(iwl_mvm_sta_->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));
}
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) {
mtx_unlock(&iwl_mvm->mutex);
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::SetKey(const struct wlan_key_config* key_config) {
zx_status_t status = ZX_OK;
struct iwl_mvm* const mvm = iwl_mvm_sta_->mvmvif->mvm;
if (mvm->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.
return ZX_ERR_NOT_SUPPORTED;
}
if (!std::equal(key_config->cipher_oui,
key_config->cipher_oui + std::size(key_config->cipher_oui), kIeeeOui,
kIeeeOui + std::size(kIeeeOui))) {
// IEEE 802.11-2016 9.4.2.25.2
// The standard ciphers all live in the IEEE space.
return ZX_ERR_NOT_SUPPORTED;
}
if (key_config->key_type < 0 || key_config->key_type >= ieee80211_key_confs_.size()) {
return ZX_ERR_INVALID_ARGS;
}
// Remove any existing key in this slot.
if (ieee80211_key_confs_[key_config->key_type] != nullptr) {
if ((status = iwl_mvm_mac_remove_key(iwl_mvm_sta_->mvmvif, iwl_mvm_sta_.get(),
ieee80211_key_confs_[key_config->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_config->key_type].reset();
}
unique_free_ptr<struct ieee80211_key_conf> key_conf(reinterpret_cast<struct ieee80211_key_conf*>(
malloc(sizeof(ieee80211_key_conf) + key_config->key_len)));
memset(key_conf.get(), 0, sizeof(*key_conf) + key_config->key_len);
key_conf->cipher = key_config->cipher_type;
key_conf->keyidx = key_config->key_idx;
key_conf->keylen = key_config->key_len;
key_conf->rx_seq = key_config->rsc;
memcpy(key_conf->key, key_config->key, 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 %d, cipher_type %d, key_idx %d) failed: %s\n",
key_config->key_type, key_config->cipher_type, key_config->key_idx,
zx_status_get_string(status));
return status;
}
ieee80211_key_confs_[key_config->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 sta_state_; }
zx_status_t MvmSta::ChangeState(enum iwl_sta_state state) {
zx_status_t status = ZX_OK;
while (state > sta_state_) {
if ((status = ChangeStateUp()) != ZX_OK) {
return status;
}
}
while (state < sta_state_) {
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 (sta_state_) {
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", sta_state_);
return ZX_ERR_BAD_STATE;
}
}
if ((status = iwl_mvm_mac_sta_state(iwl_mvm_vif_, iwl_mvm_sta_.get(), sta_state_, new_state)) !=
ZX_OK) {
IWL_ERR(iwl_mvm_vif_, "iwl_mvm_mac_sta_state() failed for %d -> %d: %s\n", sta_state_,
new_state, zx_status_get_string(status));
return status;
}
sta_state_ = new_state;
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 (sta_state_) {
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", sta_state_);
return ZX_ERR_BAD_STATE;
}
}
if ((status = iwl_mvm_mac_sta_state(iwl_mvm_vif_, iwl_mvm_sta_.get(), sta_state_, new_state)) !=
ZX_OK) {
IWL_ERR(iwl_mvm_vif_, "iwl_mvm_mac_sta_state() failed for %d -> %d: %s\n", sta_state_,
new_state, zx_status_get_string(status));
return status;
}
sta_state_ = new_state;
return ZX_OK;
}
} // namespace wlan::iwlwifi