blob: 75e0e3815e4136e6c69a8c9357566cb3f5962df8 [file] [log] [blame]
// Copyright 2018 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 <wlan/common/tx_vector.h>
namespace wlan {
//
// For HT:
// Changing CBW from 20 MHz to 40 MHz advances index by 32
// Changing GI from 800 ns to 400 ns advances index by 64
//
// Group tx_vec_idx_t range PHY GI CBW NSS MCS_IDX
// 0 1 - 32 HT 800 20 - 0-31
// 1 33 - 64 HT 800 40 - 0-31
// 2 65 - 96 HT 400 20 - 0-31
// 3 97 - 128 HT 400 40 - 0-31
// 4 129 - 136 ERP - - - 0-7
// 5 137 - 138 DSSS - - - 0-1
// 6 139 - 140 CCK - - - 2-3
//
// Note: MCS_IDX is explained in the definition of TxVector.
//
// TODO(NET-1451) VHT will be inserted between HT and ERP.
zx_status_t TxVector::FromSupportedRate(const SupportedRate& erp_rate, TxVector* tx_vec) {
if (tx_vec == nullptr) {
errorf("nullptr passed to TxVector::FromSupportedRate()\n");
ZX_DEBUG_ASSERT(false);
return ZX_ERR_INVALID_ARGS;
}
*tx_vec = TxVector{
.gi = WLAN_GI_800NS,
.cbw = CBW20,
.nss = 1,
};
PHY phy;
uint8_t mcs_idx;
uint8_t rate_val = erp_rate.rate();
switch (rate_val) {
case 2:
phy = WLAN_PHY_DSSS;
mcs_idx = 0;
break;
case 4:
phy = WLAN_PHY_DSSS;
mcs_idx = 1;
break;
case 11:
phy = WLAN_PHY_CCK;
mcs_idx = 2;
break;
case 22:
phy = WLAN_PHY_CCK;
mcs_idx = 3;
break;
case 12:
phy = WLAN_PHY_ERP;
mcs_idx = 0;
break;
case 18:
phy = WLAN_PHY_ERP;
mcs_idx = 1;
break;
case 24:
phy = WLAN_PHY_ERP;
mcs_idx = 2;
break;
case 36:
phy = WLAN_PHY_ERP;
mcs_idx = 3;
break;
case 48:
phy = WLAN_PHY_ERP;
mcs_idx = 4;
break;
case 72:
phy = WLAN_PHY_ERP;
mcs_idx = 5;
break;
case 96:
phy = WLAN_PHY_ERP;
mcs_idx = 6;
break;
case 108:
phy = WLAN_PHY_ERP;
mcs_idx = 7;
break;
default:
errorf("Invalid rate %u * 0.5 Mbps for 802.11a/b/g.\n", rate_val);
ZX_DEBUG_ASSERT(false);
return ZX_ERR_INVALID_ARGS;
}
tx_vec->phy = phy;
tx_vec->mcs_idx = mcs_idx;
return ZX_OK;
}
// Inverse of FromSupportedRate above, supports ERP rates but not CCK rates.
std::optional<SupportedRate> TxVectorIdxToErpRate(tx_vec_idx_t idx) {
if (idx < kErpStartIdx || idx >= kErpStartIdx + kErpNumTxVector) { return {}; }
constexpr uint16_t erp_rate_list[] = {12, 18, 34, 36, 48, 72, 96, 108};
return SupportedRate(erp_rate_list[idx - kErpStartIdx]);
}
bool IsTxVecIdxValid(tx_vec_idx_t idx) {
return kInvalidTxVectorIdx < idx && idx <= kMaxValidIdx;
}
PHY TxVecIdxToPhy(tx_vec_idx_t idx) {
if (idx < kHtStartIdx + kHtNumTxVector) {
return WLAN_PHY_HT;
} else if (idx < kErpStartIdx + kErpNumTxVector) {
return WLAN_PHY_ERP;
} else if (idx < kDsssCckStartIdx + kDsssCckNumTxVector) {
return idx - kDsssCckStartIdx < 2 ? WLAN_PHY_DSSS : WLAN_PHY_CCK;
}
// caller will always call IsTxVecIdxValie() so that this is never reached.
ZX_DEBUG_ASSERT(false);
return WLAN_PHY_HT;
}
zx_status_t TxVector::FromIdx(tx_vec_idx_t idx, TxVector* tx_vec) {
if (!IsTxVecIdxValid(idx)) {
errorf("Invalid idx for TxVector::FromIdx(): %u\n", idx);
ZX_DEBUG_ASSERT(false);
return ZX_ERR_INVALID_ARGS;
}
if (tx_vec == nullptr) {
errorf("nullptr for TxVector::FromIdx()\n");
ZX_DEBUG_ASSERT(false);
return ZX_ERR_INVALID_ARGS;
}
PHY phy = TxVecIdxToPhy(idx);
switch (phy) {
case WLAN_PHY_HT: {
uint8_t group_idx = (idx - kHtStartIdx) / kHtNumMcs;
GI gi = ((group_idx / kHtNumCbw) % kHtNumGi == 1 ? WLAN_GI_400NS : WLAN_GI_800NS);
CBW cbw = (group_idx % kHtNumCbw == 0 ? CBW20 : CBW40);
uint8_t mcs_idx = (idx - kHtStartIdx) % kHtNumMcs;
*tx_vec = TxVector{
.phy = phy,
.gi = gi,
.cbw = cbw,
.nss = static_cast<uint8_t>(1 + mcs_idx / kHtNumUniqueMcs),
.mcs_idx = mcs_idx,
};
break;
}
case WLAN_PHY_ERP:
*tx_vec = TxVector{
.phy = phy,
.gi = WLAN_GI_800NS,
.cbw = CBW20,
.nss = 1,
.mcs_idx = static_cast<uint8_t>(idx - kErpStartIdx),
};
break;
case WLAN_PHY_DSSS:
case WLAN_PHY_CCK:
*tx_vec = TxVector{
.phy = phy,
.gi = WLAN_GI_800NS,
.cbw = CBW20,
.nss = 1,
.mcs_idx = static_cast<uint8_t>(idx - kDsssCckStartIdx),
};
break;
default:
// Not reachable.
ZX_DEBUG_ASSERT(false);
break;
}
return ZX_OK;
}
bool TxVector::IsValid() const {
if (!(phy == WLAN_PHY_CCK || phy == WLAN_PHY_DSSS || phy == WLAN_PHY_ERP ||
phy == WLAN_PHY_HT)) {
return false;
}
switch (phy) {
case WLAN_PHY_DSSS:
return mcs_idx == 0 || mcs_idx == 1;
case WLAN_PHY_CCK:
return mcs_idx == 2 || mcs_idx == 3;
case WLAN_PHY_HT:
if (!(gi == WLAN_GI_800NS || gi == WLAN_GI_400NS)) { return false; }
if (!(cbw == CBW20 || cbw == CBW40 || cbw == CBW40ABOVE || cbw == CBW40BELOW)) {
return false;
}
return 0 <= mcs_idx && mcs_idx < kHtNumMcs;
case WLAN_PHY_ERP:
return 0 <= mcs_idx && mcs_idx < kErpNumTxVector;
case WLAN_PHY_VHT:
// fall through
// TODO(NET-1541): GI 800ns, 400ns or 200ns, BW any, MCS 0-9
default:
return false;
}
}
zx_status_t TxVector::ToIdx(tx_vec_idx_t* idx) const {
if (!IsValid()) { return ZX_ERR_INVALID_ARGS; }
switch (phy) {
case WLAN_PHY_HT: {
uint8_t group_idx = 0;
if (gi == WLAN_GI_400NS) { group_idx = kHtNumCbw; }
if (cbw == CBW40 || cbw == CBW40ABOVE || cbw == CBW40BELOW) { group_idx++; }
*idx = kHtStartIdx + (group_idx * kHtNumMcs) + mcs_idx;
break;
}
case WLAN_PHY_ERP:
*idx = kErpStartIdx + mcs_idx;
break;
case WLAN_PHY_CCK:
case WLAN_PHY_DSSS:
*idx = kDsssCckStartIdx + mcs_idx;
break;
case WLAN_PHY_VHT:
// fall-through, will never reach because TxVector is always valid.
// TODO(NET-1541)
default:
break;
}
return ZX_OK;
}
bool operator==(const TxVector& lhs, const TxVector& rhs) {
if (lhs.phy != rhs.phy || lhs.mcs_idx != rhs.mcs_idx) { return false; }
switch (lhs.phy) {
case WLAN_PHY_HT:
return lhs.gi == rhs.gi && lhs.cbw == rhs.cbw;
case WLAN_PHY_ERP:
case WLAN_PHY_CCK:
case WLAN_PHY_DSSS:
return true;
default:
return false;
}
}
bool operator!=(const TxVector& lhs, const TxVector& rhs) {
return !(lhs == rhs);
}
bool IsEqualExceptMcs(const ::wlan::TxVector& lhs, const ::wlan::TxVector& rhs) {
::wlan::TxVector temp = lhs;
temp.mcs_idx = rhs.mcs_idx; // Make mcs_idx equal so that we only compare other fields.
return rhs == temp;
}
} // namespace wlan