blob: 9fc1dc7bfeabc4acc989a03be869477694e4d0f8 [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 "minstrel.h"
#include <wlan/common/channel.h>
#include <wlan/mlme/debug.h>
#include <wlan/protocol/mac.h>
namespace wlan {
namespace wlan_minstrel = ::fuchsia::wlan::minstrel;
// If the data rate is too low, do not probe more than twice per update interval
static constexpr uint8_t kMaxSlowProbe = 2;
// If success rate is below (1 - kProbabilityThreshold), only probe once every this many cycles.
static constexpr uint8_t kDeadProbeCycleCount = 32;
zx::duration HeaderTxTimeErp() {
// TODO(eyw): Implement Erp preamble and header
return zx::nsec(0);
}
zx::duration PayloadTxTimeErp(SupportedRate rate) {
// D_{bps} as defined in IEEE 802.11-2016 Table 17-4
// Unit: Number of data bits per OFDM symbol
uint16_t bits_per_symbol = rate.rate() * 2;
constexpr int kTxTimePerSymbol = 4000; // nanoseconds.
uint32_t total_time = kTxTimePerSymbol * 8 * kMinstrelFrameLength / bits_per_symbol;
return zx::nsec(total_time);
}
zx::duration TxTimeErp(SupportedRate rate) {
return HeaderTxTimeErp() + PayloadTxTimeErp(rate);
}
void EmplaceErp(std::unordered_map<tx_vec_idx_t, TxStats>* map, tx_vec_idx_t idx,
SupportedRate rate) {
zx::duration time = TxTimeErp(rate);
ZX_DEBUG_ASSERT(time.to_nsecs() != 0);
debugmstl("%s, tx_time %lu nsec\n", debug::Describe(idx).c_str(), time.to_nsecs());
TxStats tx_stats{
.tx_vector_idx = idx,
.perfect_tx_time = time,
};
map->emplace(idx, tx_stats);
}
std::unordered_set<tx_vec_idx_t> AddSupportedErp(
std::unordered_map<tx_vec_idx_t, TxStats>* tx_stats_map,
const std::vector<SupportedRate>& rates) {
size_t tx_stats_added = 0;
std::unordered_set<tx_vec_idx_t> basic_rates;
for (const auto& rate : rates) {
TxVector tx_vector;
zx_status_t status = TxVector::FromSupportedRate(rate, &tx_vector);
ZX_DEBUG_ASSERT(status == ZX_OK);
// Fuchsia only uses 802.11a/g/n and later data rates for transmission.
if (tx_vector.phy != WLAN_PHY_ERP) { continue; }
tx_vec_idx_t tx_vector_idx;
status = tx_vector.ToIdx(&tx_vector_idx);
ZX_DEBUG_ASSERT(status == ZX_OK);
EmplaceErp(tx_stats_map, tx_vector_idx, rate);
++tx_stats_added;
if (rate.is_basic()) {
debugmstl("basic_rate: %s\n", debug::Describe(tx_vector_idx).c_str());
basic_rates.emplace(tx_vector_idx);
}
}
debugmstl("%zu ERP added.\n", tx_stats_added);
if (basic_rates.empty()) { basic_rates.emplace(kErpStartIdx); }
return basic_rates;
}
bool AddMissingErp(std::unordered_map<tx_vec_idx_t, TxStats>* map, tx_vec_idx_t idx) {
auto erp_rate = TxVectorIdxToErpRate(idx);
if (!erp_rate.has_value()) {
ZX_DEBUG_ASSERT(0);
return false;
} else {
EmplaceErp(map, idx, *erp_rate);
return true;
}
}
zx::duration HeaderTxTimeHt() {
// TODO(eyw): Implement Plcp preamble and header
return zx::nsec(0);
}
// relative_mcs_idx is the index for combination of (modulation, coding rate) tuple
// when listed in the same order as MCS Index, without nss. i.e.
// 0: BPSK, 1/2
// 1: QPSK, 1/2
// 2: QPSK, 3/4
// 3: 16-QAM, 1/2
// 4: 16-QAM, 3/4
// 5: 64-QAM, 2/3
// 6: 64-QAM, 3/4
// 7: 64-QAM, 5/6
// 8: 256-QAM, 3/4 (since VHT)
// 9: 256-QAM, 5/6 (since VHT)
zx::duration PayloadTxTimeHt(CBW cbw, GI gi, size_t mcs_idx) {
// D_{bps} as defined in IEEE 802.11-2016 Table 19-26
// Unit: Number of data bits per OFDM symbol (20 MHz channel width)
constexpr uint16_t bits_per_symbol_list[] = {
26, 52, 78, 104, 156, 208, 234, 260, /* since VHT */ 312, 347};
constexpr uint16_t kDataSubCarriers20 = 52;
constexpr uint16_t kDataSubCarriers40 = 108;
// TODO(eyw): VHT would have kDataSubCarriers80 = 234 and kDataSubCarriers160 = 468
ZX_DEBUG_ASSERT(gi == WLAN_GI_400NS || gi == WLAN_GI_800NS);
int nss = 1 + mcs_idx / kHtNumUniqueMcs;
int relative_mcs_idx = mcs_idx % kHtNumUniqueMcs;
uint16_t bits_per_symbol = bits_per_symbol_list[relative_mcs_idx];
if (cbw == CBW40) {
bits_per_symbol = bits_per_symbol * kDataSubCarriers40 / kDataSubCarriers20;
}
constexpr int kTxTimePerSymbolGi800 = 4000; // nanoseconds.
constexpr int kTxTimePerSymbolGi400 = 3600; // nanoseconds.
// Perform multiplication before division to prevent precision loss
uint32_t total_time =
kTxTimePerSymbolGi800 * 8 * kMinstrelFrameLength / (nss * bits_per_symbol);
if (gi == WLAN_GI_400NS) {
total_time =
800 + (kTxTimePerSymbolGi400 * 8 * kMinstrelFrameLength / (nss * bits_per_symbol));
}
return zx::nsec(total_time);
}
zx::duration TxTimeHt(CBW cbw, GI gi, uint8_t relative_mcs_idx) {
return HeaderTxTimeHt() + PayloadTxTimeHt(cbw, gi, relative_mcs_idx);
}
// SupportedMcsRx is 78 bit long in IEEE802.11-2016, Figure 9-334
// In reality, devices implement MCS 0-31, sometimes 32, almost never beyond 32.
void AddSupportedHt(std::unordered_map<tx_vec_idx_t, TxStats>* tx_stats_map, CBW cbw, GI gi,
const SupportedMcsRxMcsHead& mcs_set) {
size_t tx_stats_added = 0;
for (uint8_t mcs_idx = 0; mcs_idx < kHtNumMcs; ++mcs_idx) {
// Skip if this mcs is not supported
if (!mcs_set.Support(mcs_idx)) { continue; }
TxVector tx_vector{
.phy = WLAN_PHY_HT,
.gi = gi,
.cbw = cbw,
.mcs_idx = mcs_idx,
};
tx_vec_idx_t tx_vector_idx;
zx_status_t status = tx_vector.ToIdx(&tx_vector_idx);
ZX_DEBUG_ASSERT(status == ZX_OK);
zx::duration perfect_tx_time = TxTimeHt(cbw, gi, mcs_idx);
ZX_DEBUG_ASSERT(perfect_tx_time.to_nsecs() != 0);
debugmstl("%s, tx_time %lu nsec\n", debug::Describe(tx_vector).c_str(),
perfect_tx_time.to_nsecs());
TxStats tx_stats{
.tx_vector_idx = tx_vector_idx,
.perfect_tx_time = perfect_tx_time,
};
tx_stats_map->emplace(tx_vector_idx, tx_stats);
++tx_stats_added;
}
debugmstl("%zu HT added with cbw=%s, gi=%s\n", tx_stats_added, ::wlan::common::CbwStr(cbw),
debug::Describe(gi).c_str());
}
MinstrelRateSelector::MinstrelRateSelector(fbl::unique_ptr<Timer>&& timer,
ProbeSequence&& probe_sequence,
zx::duration update_interval)
: timer_mgr_(std::move(timer)),
probe_sequence_(std::move(probe_sequence)),
update_interval_(update_interval) {}
std::unordered_set<tx_vec_idx_t> AddErp(std::unordered_map<tx_vec_idx_t, TxStats>* tx_stats_map,
const wlan_assoc_ctx_t& assoc_ctx) {
std::vector<SupportedRate> rates(assoc_ctx.rates_cnt);
std::transform(assoc_ctx.rates, assoc_ctx.rates + assoc_ctx.rates_cnt, rates.begin(),
SupportedRate::raw);
debugmstl("Supported rates: %s\n", debug::Describe(rates).c_str());
return AddSupportedErp(tx_stats_map, rates);
}
void AddHt(std::unordered_map<tx_vec_idx_t, TxStats>* tx_stats_map, const HtCapabilities& ht_cap) {
tx_vec_idx_t max_size = kHtNumMcs;
// TODO(NET-1726): Enable CBW40 support once its information is available from AssocCtx
const CBW assoc_chan_width = CBW20;
const bool sgi_20 = ht_cap.ht_cap_info.short_gi_20() == 1;
const bool sgi_40 = ht_cap.ht_cap_info.short_gi_40() == 1;
if (sgi_20) { max_size += kHtNumMcs; }
if (assoc_chan_width == CBW40) {
max_size += kHtNumMcs;
if (sgi_40) { max_size += kHtNumMcs; }
}
max_size += kErpNumTxVector; // Taking in to account erp_rates.
debugmstl("max_size is %d.\n", max_size);
tx_stats_map->reserve(max_size);
AddSupportedHt(tx_stats_map, CBW20, WLAN_GI_800NS, ht_cap.mcs_set.rx_mcs_head);
if (sgi_20) { AddSupportedHt(tx_stats_map, CBW20, WLAN_GI_400NS, ht_cap.mcs_set.rx_mcs_head); }
if (assoc_chan_width == CBW40) {
AddSupportedHt(tx_stats_map, CBW40, WLAN_GI_800NS, ht_cap.mcs_set.rx_mcs_head);
if (sgi_40) {
AddSupportedHt(tx_stats_map, CBW40, WLAN_GI_400NS, ht_cap.mcs_set.rx_mcs_head);
}
}
debugmstl("tx_stats_map size: %zu.\n", tx_stats_map->size());
}
void MinstrelRateSelector::AddPeer(const wlan_assoc_ctx_t& assoc_ctx) {
auto addr = common::MacAddr(assoc_ctx.bssid);
Peer peer{};
peer.addr = addr;
{
std::lock_guard<std::mutex> guard(*peer.update_lock);
HtCapabilities ht_cap;
constexpr uint32_t kMcsMask0_31 = 0xFFFFFFFF;
if (assoc_ctx.has_ht_cap) {
ht_cap = HtCapabilities::FromDdk(assoc_ctx.ht_cap);
// TODO(eyw): SGI support suppressed. Remove these once they are supported.
ht_cap.ht_cap_info.set_short_gi_20(false);
ht_cap.ht_cap_info.set_short_gi_40(false);
if ((ht_cap.mcs_set.rx_mcs_head.bitmask() & kMcsMask0_31) == 0) {
errorf("Invalid AssocCtx: HT supported but no valid MCS. %s\n",
debug::Describe(ht_cap.mcs_set).c_str());
ZX_DEBUG_ASSERT(false);
} else {
peer.is_ht = true;
AddHt(&peer.tx_stats_map, ht_cap);
}
}
if (assoc_ctx.rates_cnt > 0) {
peer.basic_rates = AddErp(&peer.tx_stats_map, assoc_ctx);
if (peer.basic_rates.size() > 0) {
peer.basic_highest =
*std::max_element(peer.basic_rates.cbegin(), peer.basic_rates.cend());
}
}
debugmstl("tx_stats_map populated. size: %zu.\n", peer.tx_stats_map.size());
if (peer.tx_stats_map.size() == 0) {
errorf("No usable rates for peer %s.\n", addr.ToString().c_str());
ZX_DEBUG_ASSERT(false);
}
}
debugmstl("Minstrel peer added: %s\n", addr.ToString().c_str());
if (peer_map_.empty()) {
ZX_DEBUG_ASSERT(next_update_ == TimeoutId{});
timer_mgr_.Schedule(timer_mgr_.Now() + update_interval_, {}, &next_update_);
} else if (GetPeer(addr) != nullptr) {
warnf("Peer %s already exists. Forgot to clean up?\n", addr.ToString().c_str());
}
peer_map_.emplace(addr, std::move(peer));
outdated_peers_.emplace(addr);
UpdateStats();
// TODO(eyw): RemovePeer() for roles other than client.
}
void MinstrelRateSelector::RemovePeer(const common::MacAddr& addr) {
auto iter = peer_map_.find(addr);
if (iter == peer_map_.end()) {
debugmstl("peer %s not found.\n", addr.ToString().c_str());
return;
}
outdated_peers_.erase(addr);
peer_map_.erase(iter);
if (peer_map_.empty()) {
timer_mgr_.Cancel(next_update_);
next_update_ = {};
}
debugmstl("peer %s removed.\n", addr.ToString().c_str());
}
void MinstrelRateSelector::HandleTxStatusReport(const wlan_tx_status_t& tx_status) {
auto peer_addr = common::MacAddr(tx_status.peer_addr);
auto peer = GetPeer(peer_addr);
if (peer == nullptr) {
debugmstl("Peer [%s] received tx status report after it is removed.\n",
peer_addr.ToString().c_str());
return;
}
std::lock_guard<std::mutex> guard(*peer->update_lock);
auto tx_stats_map = &peer->tx_stats_map;
tx_vec_idx_t last_idx = kInvalidTxVectorIdx;
for (auto entry : tx_status.tx_status_entry) {
if (entry.tx_vector_idx == kInvalidTxVectorIdx) { break; }
last_idx = entry.tx_vector_idx;
if (tx_stats_map->count(last_idx) == 0) {
if (!AddMissingErp(&peer->tx_stats_map, last_idx)) {
debugmstl("error: Invalid tx_vec_idx: %u.\n", last_idx);
last_idx = kInvalidTxVectorIdx;
}
}
if (last_idx != kInvalidTxVectorIdx) {
(*tx_stats_map)[last_idx].attempts_cur += entry.attempts;
}
}
if (tx_status.success && last_idx != kInvalidTxVectorIdx) {
(*tx_stats_map)[last_idx].success_cur++;
}
outdated_peers_.emplace(peer_addr);
}
inline constexpr bool TxStats::PhyPreferredOver(const wlan::TxStats& other) const {
// based on experiment, If HT is supported, it is better not to use ERP for data frames.
// With ralink RT5592 and Netgear Nighthawk X10, approximately 80 feet away,
// HT/ERP tx throughput < 1 Mbps, HT only tx 4-8 Mbps
// TODO(WLAN-868): Revisit with VHT support.
return IsHt(tx_vector_idx) && !IsHt(other.tx_vector_idx);
}
inline constexpr bool TxStats::ThroughputHigherThan(const TxStats& other) const {
return cur_tp > other.cur_tp || (cur_tp == other.cur_tp && probability > other.probability);
}
inline constexpr bool TxStats::ProbabilityHigherThan(const TxStats& other) const {
if (probability >= kMinstrelProbabilityThreshold &&
other.probability >= kMinstrelProbabilityThreshold) {
// When probability is "high enough", consider throughput instead.
return cur_tp > other.cur_tp;
}
return probability > other.probability;
}
inline constexpr bool IsTxUnlikely(const TxStats& ts) {
return ts.probability < 1.0f - kMinstrelProbabilityThreshold;
}
void UpdateStatsPeer(Peer* peer) {
std::lock_guard<std::mutex> guard(*peer->update_lock);
auto& tsm = peer->tx_stats_map;
for (auto& [_, stats] : tsm) {
if (stats.attempts_cur != 0) {
float prob = 1.0 * stats.success_cur / stats.attempts_cur;
if (stats.attempts_total == 0) {
stats.probability = prob;
} else {
stats.probability =
stats.probability * kMinstrelExpWeight + prob * (1 - kMinstrelExpWeight);
}
if (stats.attempts_total + stats.attempts_cur < stats.attempts_total) { // overflow
stats.attempts_total = stats.attempts_cur;
stats.success_total = stats.success_cur;
} else {
stats.attempts_total += stats.attempts_cur;
stats.success_total += stats.success_cur;
}
stats.attempts_cur = 0;
stats.success_cur = 0;
stats.probe_cycles_skipped = 0;
} else {
++stats.probe_cycles_skipped;
}
constexpr float kNanoSecondsPerSecond = 1e9;
// perfect_tx_time is always non-zero as guaranteed by AddSupportedHt and AddSupportedErp
stats.cur_tp = kNanoSecondsPerSecond / stats.perfect_tx_time.to_nsecs() * stats.probability;
}
const auto& ctsm = tsm;
const auto& brs = peer->basic_rates;
// Pick a random tx vector as the starting point because we will go through them all.
tx_vec_idx_t max_tp = ctsm.cbegin()->first;
tx_vec_idx_t max_probability = max_tp;
tx_vec_idx_t basic_max_probability = peer->basic_highest;
for (const auto& [idx, stats] : peer->tx_stats_map) {
if ((!IsTxUnlikely(stats) && stats.PhyPreferredOver(ctsm.at(max_tp))) ||
stats.ThroughputHigherThan(ctsm.at(max_tp))) {
max_tp = idx;
}
if ((!IsTxUnlikely(stats) && stats.PhyPreferredOver(ctsm.at(max_probability))) ||
stats.ProbabilityHigherThan(ctsm.at(max_probability))) {
max_probability = idx;
}
if (brs.count(idx) != 0 && stats.ProbabilityHigherThan(ctsm.at(basic_max_probability))) {
basic_max_probability = idx;
}
}
peer->max_tp = max_tp;
peer->max_probability = max_probability;
peer->basic_max_probability = basic_max_probability;
}
bool MinstrelRateSelector::HandleTimeout() {
bool handled = false;
timer_mgr_.HandleTimeout([&](auto now, auto _event, auto timeout_id) {
if (next_update_ == timeout_id) {
timer_mgr_.Schedule(now + update_interval_, {}, &next_update_);
UpdateStats();
handled = true;
}
});
return handled;
}
tx_vec_idx_t GetNextProbe(Peer* peer, const ProbeSequence& probe_sequence) {
std::lock_guard<std::mutex> gaurd(*peer->update_lock);
tx_vec_idx_t probe_idx = kInvalidTxVectorIdx;
zx::duration baseline_tx_time = peer->tx_stats_map[peer->max_probability].perfect_tx_time;
auto potential_probes = peer->tx_stats_map.size();
if (potential_probes == 1) { return peer->max_tp; }
while (potential_probes > 0) {
--potential_probes;
do {
if (probe_sequence.Next(&peer->probe_entry, &probe_idx)) {
++peer->num_probe_cycles_done;
}
} while (peer->tx_stats_map.count(probe_idx) == 0); // not supported, keep looking
const TxStats& tx_stats = peer->tx_stats_map[probe_idx];
const bool skip =
// These are used by default so no probing needed
probe_idx == peer->basic_max_probability || probe_idx == peer->basic_highest ||
probe_idx == peer->max_tp || probe_idx == peer->max_probability ||
// It has been probed more than anyone else
(tx_stats.attempts_cur > peer->num_probe_cycles_done) ||
// It will not provide higher throughput, minimum probing is enough
((tx_stats.perfect_tx_time > baseline_tx_time) &&
(tx_stats.attempts_cur >= kMaxSlowProbe)) ||
// It is almost guaranteed to fail, defer until enough cycles pass
(IsTxUnlikely(tx_stats) &&
(tx_stats.probe_cycles_skipped < kDeadProbeCycleCount || tx_stats.attempts_cur > 0));
if (!skip) { break; }
}
if (potential_probes == 0) { return peer->max_tp; }
++peer->probes;
++peer->tx_stats_map[probe_idx].probes_total;
return probe_idx;
}
tx_vec_idx_t GetTxVector(Peer* peer, bool needs_reliability, const ProbeSequence& probe_sequence) {
if (needs_reliability) { return peer->max_probability; }
if (peer->num_pkt_until_next_probe > 0) {
--peer->num_pkt_until_next_probe;
return peer->max_tp;
}
peer->num_pkt_until_next_probe = kProbeInterval - 1;
return GetNextProbe(peer, probe_sequence);
}
tx_vec_idx_t MinstrelRateSelector::GetTxVectorIdx(const FrameControl& fc,
const common::MacAddr& peer_addr,
uint32_t flags) {
Peer* peer = GetPeer(peer_addr);
if (peer == nullptr) { return kErpStartIdx + kErpNumTxVector - 1; }
if (!fc.IsData()) { return peer->basic_max_probability; }
const bool needs_reliability = (flags & WLAN_TX_INFO_FLAGS_FAVOR_RELIABILITY) != 0;
return GetTxVector(peer, needs_reliability, probe_sequence_);
}
void MinstrelRateSelector::UpdateStats() {
for (auto peer_addr : outdated_peers_) {
auto* peer = GetPeer(peer_addr);
if (peer != nullptr) {
UpdateStatsPeer(peer);
} else {
ZX_DEBUG_ASSERT(0);
}
}
outdated_peers_.clear();
}
Peer* MinstrelRateSelector::GetPeer(const common::MacAddr& addr) {
auto iter = peer_map_.find(addr);
if (iter != peer_map_.end()) { return &(iter->second); }
return nullptr;
}
const Peer* MinstrelRateSelector::GetPeer(const common::MacAddr& addr) const {
auto iter = peer_map_.find(addr);
if (iter != peer_map_.end()) { return &(iter->second); }
return nullptr;
}
zx_status_t MinstrelRateSelector::GetListToFidl(wlan_minstrel::Peers* peers_fidl) const {
peers_fidl->peers.resize(peer_map_.size());
size_t idx = 0;
for (const auto& iter : peer_map_) {
peers_fidl->peers[idx].resize(common::kMacAddrLen);
iter.first.CopyTo(peers_fidl->peers[idx].data());
++idx;
}
return ZX_OK;
}
wlan_minstrel::StatsEntry TxStats::ToFidl() const {
return wlan_minstrel::StatsEntry{
.tx_vector_idx = tx_vector_idx,
.tx_vec_desc = debug::Describe(tx_vector_idx),
.success_cur = success_cur,
.attempts_cur = attempts_cur,
.probability = probability,
.cur_tp = cur_tp,
.success_total = success_total,
.attempts_total = attempts_total,
.probes_total = probes_total,
.probe_cycles_skipped = probe_cycles_skipped,
};
}
zx_status_t MinstrelRateSelector::GetStatsToFidl(const common::MacAddr& peer_addr,
wlan_minstrel::Peer* peer_fidl) const {
const auto* peer = GetPeer(peer_addr);
if (peer == nullptr) { return ZX_ERR_NOT_FOUND; }
peer_addr.CopyTo(peer_fidl->mac_addr.mutable_data());
std::lock_guard<std::mutex> guard(*peer->update_lock);
peer_fidl->entries.resize(peer->tx_stats_map.size());
size_t idx = 0;
for (const auto& [_, tx_stats] : peer->tx_stats_map) {
peer_fidl->entries[idx++] = tx_stats.ToFidl();
}
peer_fidl->max_tp = peer->max_tp;
peer_fidl->max_probability = peer->max_probability;
peer_fidl->basic_highest = peer->basic_highest;
peer_fidl->basic_max_probability = peer->basic_max_probability;
peer_fidl->probes = peer->probes;
return ZX_OK;
}
bool MinstrelRateSelector::IsActive() const {
return next_update_ != TimeoutId{};
}
namespace debug {
// This macro requires char buf[] and size_t offset variable definitions
// in each function.
#define BUFFER(args...) \
do { \
offset += snprintf(buf + offset, sizeof(buf) - offset, " " args); \
if (offset >= sizeof(buf)) { \
snprintf(buf + sizeof(buf) - 12, 12, " ..(trunc)"); \
offset = sizeof(buf); \
} \
} while (false)
std::string Describe(const TxStats& tx_stats) {
char buf[256];
size_t offset = 0;
BUFFER("%s", Describe(tx_stats.tx_vector_idx).c_str());
BUFFER("succ_c: %zu", tx_stats.success_cur);
BUFFER("att_c: %zu", tx_stats.attempts_cur);
BUFFER("succ_t: %zu", tx_stats.success_total);
BUFFER("att_t: %zu", tx_stats.attempts_total);
BUFFER("prob: %f", tx_stats.probability);
BUFFER("tp: %f", tx_stats.cur_tp);
BUFFER("probes: %zu", tx_stats.probes_total);
BUFFER("probe_cycle_skipped: %hhu", tx_stats.probe_cycles_skipped);
return std::string(buf, buf + offset);
}
#undef BUFFER
} // namespace debug
} // namespace wlan