//
// Copyright (C) 2014 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
#include "shill/wifi/mac80211_monitor.h"
#include <algorithm>
#include <base/files/file_util.h>
#include <base/strings/string_number_conversions.h>
#include <base/strings/string_split.h>
#include <base/strings/stringprintf.h>
#include "shill/logging.h"
#include "shill/metrics.h"
#include "shill/net/shill_time.h"
namespace shill {
using std::string;
using std::vector;
namespace Logging {
static auto kModuleLogScope = ScopeLogger::kWiFi;
static string ObjectID(Mac80211Monitor* m) { return m->link_name(); }
}
// statics
// At 17-25 bytes per queue, this accommodates 80 queues.
// ath9k has 4 queues, and WP2 has 16 queues.
const size_t Mac80211Monitor::kMaxQueueStateSizeBytes = 2048;
const char Mac80211Monitor::kQueueStatusPathFormat[] =
"/sys/kernel/debug/ieee80211/%s/queues";
const char Mac80211Monitor::kWakeQueuesPathFormat[] =
"/sys/kernel/debug/ieee80211/%s/wake_queues";
const time_t Mac80211Monitor::kQueueStatePollIntervalSeconds = 30;
const time_t Mac80211Monitor::kMinimumTimeBetweenWakesSeconds = 60;
Mac80211Monitor::Mac80211Monitor(
EventDispatcher* dispatcher,
const string& link_name,
size_t queue_length_limit,
const base::Closure& on_repair_callback,
Metrics* metrics)
: time_(Time::GetInstance()),
dispatcher_(dispatcher),
link_name_(link_name),
queue_length_limit_(queue_length_limit),
on_repair_callback_(on_repair_callback),
metrics_(metrics),
phy_name_("phy-unknown"),
last_woke_queues_monotonic_seconds_(0),
is_running_(false),
have_ever_read_queue_state_file_(false),
is_device_connected_(false),
weak_ptr_factory_(this) {
CHECK(time_);
CHECK(dispatcher_);
CHECK(metrics_);
}
Mac80211Monitor::~Mac80211Monitor() {
Stop();
}
void Mac80211Monitor::Start(const string& phy_name) {
SLOG(this, 2) << __func__ << " on " << link_name_ << " (" << phy_name << ")";
CHECK(!is_running_);
phy_name_ = phy_name;
queue_state_file_path_ = base::FilePath(
base::StringPrintf(kQueueStatusPathFormat, phy_name.c_str()));
wake_queues_file_path_ = base::FilePath(
base::StringPrintf(kWakeQueuesPathFormat, phy_name.c_str()));
last_woke_queues_monotonic_seconds_ = 0;
StartTimer();
is_running_ = true;
}
void Mac80211Monitor::Stop() {
SLOG(this, 2) << __func__ << " on " << link_name_ << " (" << phy_name_ << ")";
StopTimer();
is_running_ = false;
}
void Mac80211Monitor::UpdateConnectedState(bool new_state) {
SLOG(this, 2) << __func__ << " (new_state=" << new_state << ")";
is_device_connected_ = new_state;
}
void Mac80211Monitor::StartTimer() {
SLOG(this, 2) << __func__;
if (check_queues_callback_.IsCancelled()) {
check_queues_callback_.Reset(
Bind(&Mac80211Monitor::WakeQueuesIfNeeded,
weak_ptr_factory_.GetWeakPtr()));
}
dispatcher_->PostDelayedTask(check_queues_callback_.callback(),
kQueueStatePollIntervalSeconds * 1000);
}
void Mac80211Monitor::StopTimer() {
SLOG(this, 2) << __func__;
check_queues_callback_.Cancel();
}
void Mac80211Monitor::WakeQueuesIfNeeded() {
SLOG(this, 2) << __func__ << " on " << link_name_ << " (" << phy_name_ << ")";
CHECK(is_running_);
StartTimer(); // Always re-arm timer.
if (is_device_connected_) {
SLOG(this, 5) << "Skipping queue check: device is connected.";
return;
}
string queue_state_string;
if (!base::ReadFileToString(queue_state_file_path_, &queue_state_string,
kMaxQueueStateSizeBytes)) {
if (have_ever_read_queue_state_file_) {
LOG(WARNING) << __func__ << ": incomplete read on "
<< queue_state_file_path_.value();
}
return;
}
have_ever_read_queue_state_file_ = true;
uint32_t stuck_flags =
CheckAreQueuesStuck(ParseQueueState(queue_state_string));
SLOG(this, 2) << __func__ << " stuck_flags=" << stuck_flags;
if (!(stuck_flags & kStopFlagPowerSave)) {
if (stuck_flags) {
LOG(INFO) << "Skipping wake: stuck_flags is "
<< std::showbase << std::hex << stuck_flags
<< " (require " << kStopFlagPowerSave << " to wake)."
<< std::dec << std::noshowbase;
}
return;
}
time_t now_monotonic_seconds = 0;
if (!time_->GetSecondsMonotonic(&now_monotonic_seconds)) {
LOG(WARNING) << "Skipping reset: failed to get monotonic time";
return;
}
time_t elapsed = now_monotonic_seconds - last_woke_queues_monotonic_seconds_;
if (elapsed < kMinimumTimeBetweenWakesSeconds) {
LOG(WARNING) << "Skipping reset "
<< "(min interval=" << kMinimumTimeBetweenWakesSeconds
<< ", elapsed=" << elapsed << ")";
return;
}
LOG(WARNING) << "Queues appear stuck; waking.";
if (base::WriteFile(wake_queues_file_path_, "", sizeof("")) < 0) {
PLOG(ERROR) << "Failed to write to " << wake_queues_file_path_.value();
return;
}
if (!on_repair_callback_.is_null()) {
on_repair_callback_.Run();
}
last_woke_queues_monotonic_seconds_ = now_monotonic_seconds;
}
uint32_t Mac80211Monitor::CheckAreQueuesStuck(
const vector<QueueState>& queue_states) {
size_t max_stuck_queue_len = 0;
uint32_t stuck_flags = 0;
for (const auto& queue_state : queue_states) {
if (queue_state.queue_length < queue_length_limit_) {
SLOG(this, 5) << __func__
<< " skipping queue of length " << queue_state.queue_length
<< " (threshold is " << queue_length_limit_ << ")";
continue;
}
if (!queue_state.stop_flags) {
SLOG(this, 5) << __func__
<< " skipping queue of length " << queue_state.queue_length
<< " (not stopped)";
continue;
}
stuck_flags = stuck_flags | queue_state.stop_flags;
max_stuck_queue_len =
std::max(max_stuck_queue_len, queue_state.queue_length);
}
if (max_stuck_queue_len >= queue_length_limit_) {
LOG(WARNING) << "max queue length is " << max_stuck_queue_len;
}
if (stuck_flags) {
for (unsigned int i = 0; i < kStopReasonMax; ++i) {
auto stop_reason = static_cast<QueueStopReason>(i);
if (stuck_flags & GetFlagForReason(stop_reason)) {
metrics_->SendEnumToUMA(Metrics::kMetricWifiStoppedTxQueueReason,
stop_reason,
kStopReasonMax);
}
}
metrics_->SendToUMA(Metrics::kMetricWifiStoppedTxQueueLength,
max_stuck_queue_len,
Metrics::kMetricWifiStoppedTxQueueLengthMin,
Metrics::kMetricWifiStoppedTxQueueLengthMax,
Metrics::kMetricWifiStoppedTxQueueLengthNumBuckets);
}
return stuck_flags;
}
// example input:
// 01: 0x00000000/0
// ...
// 04: 0x00000000/0
//
// static
vector<Mac80211Monitor::QueueState>
Mac80211Monitor::ParseQueueState(const string& state_string) {
vector<QueueState> queue_states;
vector<string> queue_state_strings = base::SplitString(
state_string, "\n", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
if (queue_state_strings.empty()) {
return queue_states;
}
// Trailing newline generates empty string as last element.
// Discard that empty string if present.
if (queue_state_strings.back().empty()) {
queue_state_strings.pop_back();
}
for (const auto& queue_state : queue_state_strings) {
// Example |queue_state|: "00: 0x00000000/10".
vector<string> queuenum_and_state = base::SplitString(
queue_state, ":", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
if (queuenum_and_state.size() != 2) {
LOG(WARNING) << __func__ << ": parse error on " << queue_state;
continue;
}
// Example |queuenum_and_state|: {"00", "0x00000000/10"}.
vector<string> stopflags_and_length = base::SplitString(
queuenum_and_state[1], "/", base::TRIM_WHITESPACE,
base::SPLIT_WANT_ALL);
if (stopflags_and_length.size() != 2) {
LOG(WARNING) << __func__ << ": parse error on " << queue_state;
continue;
}
// Example |stopflags_and_length|: {"0x00000000", "10"}.
size_t queue_number;
uint32_t stop_flags;
size_t queue_length;
if (!base::StringToSizeT(queuenum_and_state[0], &queue_number)) {
LOG(WARNING) << __func__ << ": parse error on " << queue_state;
continue;
}
if (!base::HexStringToUInt(stopflags_and_length[0], &stop_flags)) {
LOG(WARNING) << __func__ << ": parse error on " << queue_state;
continue;
}
if (!base::StringToSizeT(stopflags_and_length[1], &queue_length)) {
LOG(WARNING) << __func__ << ": parse error on " << queue_state;
continue;
}
queue_states.emplace_back(queue_number, stop_flags, queue_length);
}
return queue_states;
}
// static
Mac80211Monitor::QueueStopFlag Mac80211Monitor::GetFlagForReason(
QueueStopReason reason) {
switch (reason) {
case kStopReasonDriver:
return kStopFlagDriver;
case kStopReasonPowerSave:
return kStopFlagPowerSave;
case kStopReasonChannelSwitch:
return kStopFlagChannelSwitch;
case kStopReasonAggregation:
return kStopFlagAggregation;
case kStopReasonSuspend:
return kStopFlagSuspend;
case kStopReasonBufferAdd:
return kStopFlagBufferAdd;
case kStopReasonChannelTypeChange:
return kStopFlagChannelTypeChange;
}
// The switch statement above is exhaustive (-Wswitch will complain
// if it is not). But |reason| might be outside the defined range for
// the enum, so we need this to keep the compiler happy.
return kStopFlagInvalid;
}
} // namespace shill