blob: 05da2bd1d231a6268118575fc06a9a092f9e326f [file] [log] [blame]
// Copyright 2016 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//
// Helper program for setting WiFi transmission power.
#include <map>
#include <string>
#include <vector>
#include <linux/nl80211.h>
#include <net/if.h>
#include <base/at_exit.h>
#include <base/check.h>
#include <base/check_op.h>
#include <base/files/file_enumerator.h>
#include <base/files/file_path.h>
#include <base/files/file_util.h>
#include <base/logging.h>
#include <base/macros.h>
#include <base/notreached.h>
#include <base/strings/string_split.h>
#include <base/strings/string_util.h>
#include <base/strings/stringprintf.h>
#include <base/system/sys_info.h>
#include <brillo/flag_helper.h>
#include <chromeos-config/libcros_config/cros_config.h>
#include <netlink/attr.h>
#include <netlink/genl/ctrl.h>
#include <netlink/genl/family.h>
#include <netlink/genl/genl.h>
#include <netlink/msg.h>
#include <power_manager/common/power_constants.h>
// Vendor command definition for marvell mwifiex driver
// Defined in Linux kernel:
// drivers/net/wireless/marvell/mwifiex/main.h
#define MWIFIEX_VENDOR_ID 0x005043
// Vendor sub command
#define MWIFIEX_VENDOR_CMD_SET_TX_POWER_LIMIT 0
#define MWIFIEX_VENDOR_CMD_ATTR_TXP_LIMIT_24 1
#define MWIFIEX_VENDOR_CMD_ATTR_TXP_LIMIT_52 2
// Vendor command definition for intel iwl7000 driver
// Defined in Linux kernel:
// drivers/net/wireless/iwl7000/iwlwifi/mvm/vendor-cmd.h
#define INTEL_OUI 0x001735
// Vendor sub command
#define IWL_MVM_VENDOR_CMD_SET_SAR_PROFILE 28
#define IWL_MVM_VENDOR_ATTR_SAR_CHAIN_A_PROFILE 58
#define IWL_MVM_VENDOR_ATTR_SAR_CHAIN_B_PROFILE 59
#define IWL_TABLET_PROFILE_INDEX 1
#define IWL_CLAMSHELL_PROFILE_INDEX 2
// Legacy vendor subcommand used for devices without limits in ACPI.
#define IWL_MVM_VENDOR_CMD_SET_NIC_TXPOWER_LIMIT 13
#define IWL_MVM_VENDOR_ATTR_TXP_LIMIT_24 13
#define IWL_MVM_VENDOR_ATTR_TXP_LIMIT_52L 14
#define IWL_MVM_VENDOR_ATTR_TXP_LIMIT_52H 15
#define REALTEK_OUI 0x00E04C
#define REALTEK_NL80211_VNDCMD_SET_SAR 0x88
#define REALTEK_VNDCMD_ATTR_SAR_RULES 1
#define REALTEK_VNDCMD_ATTR_SAR_BAND 2
#define REALTEK_VNDCMD_ATTR_SAR_POWER 3
// COMMON SAR API COMMANDS
#define NL80211_CMD_SET_SAR_SPECS 140
#define NL80211_ATTR_SAR_SPEC 300
#define NL80211_SAR_ATTR_TYPE 1
#define NL80211_SAR_TYPE_POWER 0
#define NL80211_SAR_ATTR_SPECS 2
#define NL80211_SAR_ATTR_SPECS_POWER 1
#define NL80211_SAR_ATTR_SPECS_RANGE_INDEX 2
namespace {
int ErrorHandler(struct sockaddr_nl* nla, struct nlmsgerr* err, void* arg) {
// nl80211 error codes must be negative. Zero means success, which should
// land in AckHandler.
CHECK_LE(err->error, 0);
*static_cast<int*>(arg) = err->error;
return NL_STOP;
}
int FinishHandler(struct nl_msg* msg, void* arg) {
*static_cast<int*>(arg) = 0;
return NL_SKIP;
}
int AckHandler(struct nl_msg* msg, void* arg) {
*static_cast<int*>(arg) = 0;
return NL_STOP;
}
int ValidHandler(struct nl_msg* msg, void* arg) {
return NL_OK;
}
enum class WirelessDriver { NONE, MWIFIEX, IWL, ATH10K, RTW, MTK };
enum RealtekVndcmdSARBand {
REALTEK_VNDCMD_ATTR_SAR_BAND_2g = 0,
REALTEK_VNDCMD_ATTR_SAR_BAND_5g_1 = 1,
REALTEK_VNDCMD_ATTR_SAR_BAND_5g_3 = 3,
REALTEK_VNDCMD_ATTR_SAR_BAND_5g_4 = 4,
};
// For ath10k the driver configures index 0 for 2g and index 1 for 5g.
// This dependency is a bit fragile and can break if the underlying
// assumption changes. In the upcoming implementation where the the driver
// capabilities are published, we will use the driver capability to find the
// index and frequency band mapping and can avoid enums like these.
enum Ath10kSARBand {
kAth10kSarBand2g = 0,
kAth10kSarBand5g = 1,
};
std::map<enum Ath10kSARBand, uint8_t> GetAth10kChromeosConfigPowerTable(
bool tablet) {
std::map<enum Ath10kSARBand, uint8_t> power_table = {};
auto config = std::make_unique<brillo::CrosConfig>();
CHECK(config->Init()) << "Could not find config";
std::string wifi_power_table_path =
tablet ? "/wifi/tablet-mode-power-table-ath10k"
: "/wifi/non-tablet-mode-power-table-ath10k";
std::string value;
int power_limit;
CHECK(config->GetString(wifi_power_table_path, "limit-2g", &value))
<< "Could not get ChromeosConfig power table.";
power_limit = std::stoi(value);
CHECK(power_limit <= UINT8_MAX)
<< "Invalid power limit configs. Limit value cannot exceed 255.";
power_table[kAth10kSarBand2g] = power_limit;
CHECK(config->GetString(wifi_power_table_path, "limit-5g", &value))
<< "Could not get ChromeosConfig power table.";
power_limit = std::stoi(value);
CHECK(power_limit <= UINT8_MAX)
<< "Invalid power limit configs. Limit value cannot exceed 255.";
power_table[kAth10kSarBand5g] = power_limit;
return power_table;
}
// Returns the type of wireless driver that's present on the system.
WirelessDriver GetWirelessDriverType(const std::string& device_name) {
const std::map<std::string, WirelessDriver> drivers = {
{"ath10k_pci", WirelessDriver::ATH10K},
{"ath10k_sdio", WirelessDriver::ATH10K},
{"ath10k_snoc", WirelessDriver::ATH10K},
{"iwlwifi", WirelessDriver::IWL},
{"mwifiex_pcie", WirelessDriver::MWIFIEX},
{"mwifiex_sdio", WirelessDriver::MWIFIEX},
{"rtw_pci", WirelessDriver::RTW},
{"rtw_8822ce", WirelessDriver::RTW},
{"mt7921e", WirelessDriver::MTK},
};
// .../device/driver symlink should point at the driver's module.
base::FilePath link_path(base::StringPrintf("/sys/class/net/%s/device/driver",
device_name.c_str()));
base::FilePath driver_path;
CHECK(base::ReadSymbolicLink(link_path, &driver_path));
base::FilePath driver_name = driver_path.BaseName();
const auto driver = drivers.find(driver_name.value());
if (driver != drivers.end())
return driver->second;
return WirelessDriver::NONE;
}
// Returns a vector of wireless device name(s) found on the system. We
// generally should only have 1 internal WiFi device, but it's possible to have
// an external device plugged in (e.g., via USB).
std::vector<std::string> GetWirelessDeviceNames() {
std::vector<std::string> names;
base::FileEnumerator iter(base::FilePath("/sys/class/net"), false,
base::FileEnumerator::FileType::FILES |
base::FileEnumerator::FileType::SHOW_SYM_LINKS,
"*");
for (base::FilePath name = iter.Next(); !name.empty(); name = iter.Next()) {
std::string uevent;
CHECK(base::ReadFileToString(name.Append("uevent"), &uevent));
for (const auto& line : base::SplitString(
uevent, "\n", base::KEEP_WHITESPACE, base::SPLIT_WANT_ALL)) {
if (line == "DEVTYPE=wlan") {
names.push_back(name.BaseName().value());
break;
}
}
}
return names;
}
// Returns a vector of tx power limits for mode |tablet|.
// If the board does not store power limits for rtw driver in chromeos-config,
// the function will fail.
std::map<enum RealtekVndcmdSARBand, uint8_t> GetRtwChromeosConfigPowerTable(
bool tablet, power_manager::WifiRegDomain domain) {
std::map<enum RealtekVndcmdSARBand, uint8_t> power_table = {};
auto config = std::make_unique<brillo::CrosConfig>();
CHECK(config->Init()) << "Could not find config";
std::string wifi_power_table_path =
tablet ? "/wifi/tablet-mode-power-table-rtw"
: "/wifi/non-tablet-mode-power-table-rtw";
std::string wifi_geo_offsets_path;
switch (domain) {
case power_manager::WifiRegDomain::FCC:
wifi_geo_offsets_path = "/wifi/geo-offsets-fcc";
break;
case power_manager::WifiRegDomain::EU:
wifi_geo_offsets_path = "/wifi/geo-offsets-eu";
break;
case power_manager::WifiRegDomain::REST_OF_WORLD:
wifi_geo_offsets_path = "/wifi/geo-offsets-rest-of-world";
break;
case power_manager::WifiRegDomain::NONE:
break;
}
int offset_2g = 0, offset_5g = 0;
if (domain != power_manager::WifiRegDomain::NONE) {
std::string offset_string;
if (config->GetString(wifi_geo_offsets_path, "offset-2g", &offset_string)) {
offset_2g = std::stoi(offset_string);
}
if (config->GetString(wifi_geo_offsets_path, "offset-5g", &offset_string)) {
offset_5g = std::stoi(offset_string);
}
}
std::string value;
int power_limit;
CHECK(config->GetString(wifi_power_table_path, "limit-2g", &value))
<< "Could not get ChromeosConfig power table.";
power_limit = std::stoi(value) + offset_2g;
CHECK(power_limit <= UINT8_MAX) << "Invalid power limit configs. Limit "
"value cannot exceed 255.";
power_table[REALTEK_VNDCMD_ATTR_SAR_BAND_2g] = power_limit;
CHECK(config->GetString(wifi_power_table_path, "limit-5g-1", &value))
<< "Could not get ChromeosConfig power table.";
power_limit = std::stoi(value) + offset_5g;
CHECK(power_limit <= UINT8_MAX) << "Invalid power limit configs. Limit "
"value cannot exceed 255.";
power_table[REALTEK_VNDCMD_ATTR_SAR_BAND_5g_1] = power_limit;
// Rtw driver does not support 5g band 2, so skip it.
CHECK(config->GetString(wifi_power_table_path, "limit-5g-3", &value))
<< "Could not get ChromeosConfig power table.";
power_limit = std::stoi(value) + offset_5g;
CHECK(power_limit <= UINT8_MAX) << "Invalid power limit configs. Limit "
"value cannot exceed 255.";
power_table[REALTEK_VNDCMD_ATTR_SAR_BAND_5g_3] = power_limit;
CHECK(config->GetString(wifi_power_table_path, "limit-5g-4", &value))
<< "Could not get ChromeosConfig power table.";
power_limit = std::stoi(value) + offset_5g;
CHECK(power_limit <= UINT8_MAX) << "Invalid power limit configs. Limit "
"value cannot exceed 255.";
power_table[REALTEK_VNDCMD_ATTR_SAR_BAND_5g_4] = power_limit;
return power_table;
}
// Fill in nl80211 message for the mwifiex driver.
void FillMessageMwifiex(struct nl_msg* msg, bool tablet) {
CHECK(!nla_put_u32(msg, NL80211_ATTR_VENDOR_ID, MWIFIEX_VENDOR_ID))
<< "Failed to put NL80211_ATTR_VENDOR_ID";
CHECK(!nla_put_u32(msg, NL80211_ATTR_VENDOR_SUBCMD,
MWIFIEX_VENDOR_CMD_SET_TX_POWER_LIMIT))
<< "Failed to put NL80211_ATTR_VENDOR_SUBCMD";
struct nlattr* limits =
nla_nest_start(msg, NL80211_ATTR_VENDOR_DATA | NLA_F_NESTED);
CHECK(limits) << "Failed in nla_nest_start";
CHECK(!nla_put_u8(msg, MWIFIEX_VENDOR_CMD_ATTR_TXP_LIMIT_24, tablet))
<< "Failed to put MWIFIEX_VENDOR_CMD_ATTR_TXP_LIMIT_24";
CHECK(!nla_put_u8(msg, MWIFIEX_VENDOR_CMD_ATTR_TXP_LIMIT_52, tablet))
<< "Failed to put MWIFIEX_VENDOR_CMD_ATTR_TXP_LIMIT_52";
CHECK(!nla_nest_end(msg, limits)) << "Failed in nla_nest_end";
}
// Returns a vector of three IWL transmit power limits for mode |tablet| if the
// board doesn't contain limits in ACPI, or an empty vector if ACPI should be
// used. ACPI limits are expected; this is just a hack for devices (currently
// only cave) that lack limits in ACPI. See b:70549692 for details.
std::vector<uint32_t> GetNonAcpiIwlPowerTable(bool tablet) {
// Get the board name minus an e.g. "-signed-mpkeys" suffix.
std::string board = base::SysInfo::GetLsbReleaseBoard();
const size_t index = board.find("-signed-");
if (index != std::string::npos)
board.resize(index);
if (board == "cave") {
return tablet ? std::vector<uint32_t>{13, 9, 9}
: std::vector<uint32_t>{30, 30, 30};
}
return {};
}
// Fill in nl80211 message for the iwl driver.
void FillMessageIwl(struct nl_msg* msg, bool tablet) {
CHECK(!nla_put_u32(msg, NL80211_ATTR_VENDOR_ID, INTEL_OUI))
<< "Failed to put NL80211_ATTR_VENDOR_ID";
const std::vector<uint32_t> table = GetNonAcpiIwlPowerTable(tablet);
const bool use_acpi = table.empty();
CHECK(!nla_put_u32(msg, NL80211_ATTR_VENDOR_SUBCMD,
use_acpi ? IWL_MVM_VENDOR_CMD_SET_SAR_PROFILE
: IWL_MVM_VENDOR_CMD_SET_NIC_TXPOWER_LIMIT))
<< "Failed to put NL80211_ATTR_VENDOR_SUBCMD";
struct nlattr* limits =
nla_nest_start(msg, NL80211_ATTR_VENDOR_DATA | NLA_F_NESTED);
CHECK(limits) << "Failed in nla_nest_start";
if (use_acpi) {
int index = tablet ? IWL_TABLET_PROFILE_INDEX : IWL_CLAMSHELL_PROFILE_INDEX;
CHECK(!nla_put_u8(msg, IWL_MVM_VENDOR_ATTR_SAR_CHAIN_A_PROFILE, index))
<< "Failed to put IWL_MVM_VENDOR_ATTR_SAR_CHAIN_A_PROFILE";
CHECK(!nla_put_u8(msg, IWL_MVM_VENDOR_ATTR_SAR_CHAIN_B_PROFILE, index))
<< "Failed to put IWL_MVM_VENDOR_ATTR_SAR_CHAIN_B_PROFILE";
} else {
DCHECK_EQ(table.size(), 3);
CHECK(!nla_put_u32(msg, IWL_MVM_VENDOR_ATTR_TXP_LIMIT_24, table[0] * 8))
<< "Failed to put IWL_MVM_VENDOR_ATTR_TXP_LIMIT_24";
CHECK(!nla_put_u32(msg, IWL_MVM_VENDOR_ATTR_TXP_LIMIT_52L, table[1] * 8))
<< "Failed to put IWL_MVM_VENDOR_ATTR_TXP_LIMIT_52L";
CHECK(!nla_put_u32(msg, IWL_MVM_VENDOR_ATTR_TXP_LIMIT_52H, table[2] * 8))
<< "Failed to put IWL_MVM_VENDOR_ATTR_TXP_LIMIT_52H";
}
CHECK(!nla_nest_end(msg, limits)) << "Failed in nla_nest_end";
}
// Fill in nl80211 message for the rtw driver.
void FillMessageRtw(struct nl_msg* msg,
bool tablet,
power_manager::WifiRegDomain domain) {
CHECK(!nla_put_u32(msg, NL80211_ATTR_VENDOR_ID, REALTEK_OUI))
<< "Failed to put NL80211_ATTR_VENDOR_ID";
CHECK(!nla_put_u32(msg, NL80211_ATTR_VENDOR_SUBCMD,
REALTEK_NL80211_VNDCMD_SET_SAR))
<< "Failed to put NL80211_ATTR_VENDOR_SUBCMD";
struct nlattr* vendor_cmd =
nla_nest_start(msg, NL80211_ATTR_VENDOR_DATA | NLA_F_NESTED);
struct nlattr* rules =
nla_nest_start(msg, REALTEK_VNDCMD_ATTR_SAR_RULES | NLA_F_NESTED);
for (const auto& limit : GetRtwChromeosConfigPowerTable(tablet, domain)) {
struct nlattr* rule = nla_nest_start(msg, 1 | NLA_F_NESTED);
CHECK(rule) << "Failed in nla_nest_start";
CHECK(!nla_put_u32(msg, REALTEK_VNDCMD_ATTR_SAR_BAND, limit.first))
<< "Failed to put REALTEK_VNDCMD_ATTR_SAR_BAND";
CHECK(!nla_put_u8(msg, REALTEK_VNDCMD_ATTR_SAR_POWER, limit.second))
<< "Failed to put REALTEK_VNDCMD_ATTR_SAR_POWER";
CHECK(!nla_nest_end(msg, rule)) << "Failed in nla_nest_end";
}
CHECK(!nla_nest_end(msg, rules)) << "Failed in nla_nest_end";
CHECK(!nla_nest_end(msg, vendor_cmd)) << "Failed in nla_nest_end";
}
// Fill in nl80211 message for ath10k driver
void FillMessageAth10k(struct nl_msg* msg, bool tablet) {
struct nlattr* sar_capa =
nla_nest_start(msg, NL80211_ATTR_SAR_SPEC | NLA_F_NESTED);
nla_put_u32(msg, NL80211_SAR_ATTR_TYPE, NL80211_SAR_TYPE_POWER);
struct nlattr* specs =
nla_nest_start(msg, NL80211_SAR_ATTR_SPECS | NLA_F_NESTED);
int i = 0;
for (const auto& limit : GetAth10kChromeosConfigPowerTable(tablet)) {
struct nlattr* sub_freq_range = nla_nest_start(msg, ++i | NLA_F_NESTED);
CHECK(sub_freq_range) << "Failed to execute nla_nest_start";
CHECK(!nla_put_u32(msg, NL80211_SAR_ATTR_SPECS_RANGE_INDEX, limit.first))
<< "Failed to put frequency index";
CHECK(!nla_put_s32(msg, NL80211_SAR_ATTR_SPECS_POWER, limit.second))
<< "Failed to put band power";
CHECK(!nla_nest_end(msg, sub_freq_range)) << "Failed in nla_nest_end";
}
CHECK(!nla_nest_end(msg, specs)) << "Failed in nla_nest_end";
CHECK(!nla_nest_end(msg, sar_capa)) << "Failed in nla_nest_end";
}
// The mt7921 driver configures index 0 for 2g and indexes 1-4 for 5g. This
// dependency is a bit fragile and can break if the underlying assumption
// changes. Since the mt7921 driver already publishes its capabilities (see
// crrev.com/c/3009850), this could use the driver capability to find the index
// and frequency band mapping to avoid enums like these (b/172377638).
enum MtkSARBand {
kMtkSarBand2g = 0,
kMtkSarBand5g1 = 1,
kMtkSarBand5g2 = 2,
kMtkSarBand5g3 = 3,
kMtkSarBand5g4 = 4,
};
std::map<enum MtkSARBand, uint8_t> GetMtkChromeosConfigPowerTable(
bool tablet, power_manager::WifiRegDomain domain) {
std::map<enum MtkSARBand, uint8_t> power_table = {};
auto config = std::make_unique<brillo::CrosConfig>();
CHECK(config->Init()) << "Could not find config";
std::string wifi_power_table_path =
tablet ? "/wifi/tablet-mode-power-table-mtk"
: "/wifi/non-tablet-mode-power-table-mtk";
std::string wifi_geo_power_table_path;
switch (domain) {
case power_manager::WifiRegDomain::FCC:
wifi_geo_power_table_path = "/wifi/fcc-power-table-mtk";
break;
case power_manager::WifiRegDomain::EU:
wifi_geo_power_table_path = "/wifi/eu-power-table-mtk";
break;
case power_manager::WifiRegDomain::REST_OF_WORLD:
wifi_geo_power_table_path = "/wifi/rest-of-world-power-table-mtk";
break;
case power_manager::WifiRegDomain::NONE:
break;
}
int limit_2g = UINT8_MAX, limit_5g = UINT8_MAX, offset_2g = 0, offset_5g = 0;
if (domain != power_manager::WifiRegDomain::NONE) {
std::string geo_string;
if (config->GetString(wifi_geo_power_table_path, "limit-2g", &geo_string)) {
limit_2g = std::stoi(geo_string);
}
if (config->GetString(wifi_geo_power_table_path, "limit-5g", &geo_string)) {
limit_5g = std::stoi(geo_string);
}
if (config->GetString(wifi_geo_power_table_path, "offset-2g",
&geo_string)) {
offset_2g = std::stoi(geo_string);
}
if (config->GetString(wifi_geo_power_table_path, "offset-5g",
&geo_string)) {
offset_5g = std::stoi(geo_string);
}
}
std::string value;
int power_limit = UINT8_MAX;
CHECK(config->GetString(wifi_power_table_path, "limit-2g", &value))
<< "Could not get ChromeosConfig power table: " << wifi_power_table_path;
power_limit = std::stoi(value) + offset_2g;
CHECK(power_limit >= 0 && power_limit <= UINT8_MAX)
<< "Invalid power limit configs. Limit value cannot exceed 255.";
power_table[kMtkSarBand2g] = std::min(power_limit, limit_2g);
CHECK(config->GetString(wifi_power_table_path, "limit-5g-1", &value))
<< "Could not get ChromeosConfig power table.";
power_limit = std::stoi(value) + offset_5g;
CHECK(power_limit >= 0 && power_limit <= UINT8_MAX)
<< "Invalid power limit configs. Limit value cannot exceed 255.";
power_table[kMtkSarBand5g1] = std::min(power_limit, limit_5g);
CHECK(config->GetString(wifi_power_table_path, "limit-5g-2", &value))
<< "Could not get ChromeosConfig power table.";
power_limit = std::stoi(value) + offset_5g;
CHECK(power_limit >= 0 && power_limit <= UINT8_MAX)
<< "Invalid power limit configs. Limit value cannot exceed 255.";
power_table[kMtkSarBand5g2] = std::min(power_limit, limit_5g);
CHECK(config->GetString(wifi_power_table_path, "limit-5g-3", &value))
<< "Could not get ChromeosConfig power table.";
power_limit = std::stoi(value) + offset_5g;
CHECK(power_limit >= 0 && power_limit <= UINT8_MAX)
<< "Invalid power limit configs. Limit value cannot exceed 255.";
power_table[kMtkSarBand5g3] = std::min(power_limit, limit_5g);
CHECK(config->GetString(wifi_power_table_path, "limit-5g-4", &value))
<< "Could not get ChromeosConfig power table.";
power_limit = std::stoi(value) + offset_5g;
CHECK(power_limit >= 0 && power_limit <= UINT8_MAX)
<< "Invalid power limit configs. Limit value cannot exceed 255.";
power_table[kMtkSarBand5g4] = std::min(power_limit, limit_5g);
return power_table;
}
// Fill in nl80211 message for the mtk driver.
void FillMessageMTK(struct nl_msg* msg,
bool tablet,
power_manager::WifiRegDomain domain) {
struct nlattr* sar_capa =
nla_nest_start(msg, NL80211_ATTR_SAR_SPEC | NLA_F_NESTED);
nla_put_u32(msg, NL80211_SAR_ATTR_TYPE, NL80211_SAR_TYPE_POWER);
struct nlattr* specs =
nla_nest_start(msg, NL80211_SAR_ATTR_SPECS | NLA_F_NESTED);
int i = 0;
for (const auto& limit : GetMtkChromeosConfigPowerTable(tablet, domain)) {
struct nlattr* sub_freq_range = nla_nest_start(msg, ++i | NLA_F_NESTED);
CHECK(sub_freq_range) << "Failed to execute nla_nest_start";
CHECK(!nla_put_u32(msg, NL80211_SAR_ATTR_SPECS_RANGE_INDEX, limit.first))
<< "Failed to put frequency index";
CHECK(!nla_put_s32(msg, NL80211_SAR_ATTR_SPECS_POWER, limit.second))
<< "Failed to put band power";
CHECK(!nla_nest_end(msg, sub_freq_range)) << "Failed in nla_nest_end";
}
CHECK(!nla_nest_end(msg, specs)) << "Failed in nla_nest_end";
CHECK(!nla_nest_end(msg, sar_capa)) << "Failed in nla_nest_end";
}
class PowerSetter {
public:
PowerSetter() : nl_sock_(nl_socket_alloc()), cb_(nl_cb_alloc(NL_CB_DEFAULT)) {
CHECK(nl_sock_);
CHECK(cb_);
// Register libnl callbacks.
nl_cb_err(cb_, NL_CB_CUSTOM, ErrorHandler, &err_);
nl_cb_set(cb_, NL_CB_FINISH, NL_CB_CUSTOM, FinishHandler, &err_);
nl_cb_set(cb_, NL_CB_ACK, NL_CB_CUSTOM, AckHandler, &err_);
nl_cb_set(cb_, NL_CB_VALID, NL_CB_CUSTOM, ValidHandler, nullptr);
}
PowerSetter(const PowerSetter&) = delete;
PowerSetter& operator=(const PowerSetter&) = delete;
~PowerSetter() {
nl_socket_free(nl_sock_);
nl_cb_put(cb_);
}
bool SendModeSwitch(const std::string& dev_name,
bool tablet,
power_manager::WifiRegDomain domain) {
const uint32_t index = if_nametoindex(dev_name.c_str());
if (!index) {
LOG(ERROR) << "Failed to find wireless device index for " << dev_name;
return false;
}
WirelessDriver driver = GetWirelessDriverType(dev_name);
if (driver == WirelessDriver::NONE) {
LOG(ERROR) << "No valid wireless driver found for " << dev_name;
return false;
}
LOG(INFO) << "Found wireless device " << dev_name << " (index " << index
<< ")";
struct nl_msg* msg = nlmsg_alloc();
CHECK(msg);
// The command set for Ath10k and MTK, other platform (Rtw, Intel) use
// different APIs.
// TODO(b/172377638): Use common API for all platforms and fallback to
// vendor API if common API is not supported.
if (driver == WirelessDriver::ATH10K || driver == WirelessDriver::MTK)
genlmsg_put(msg, NL_AUTO_PID, NL_AUTO_SEQ, nl_family_id_, 0, 0,
NL80211_CMD_SET_SAR_SPECS, 0);
else
genlmsg_put(msg, NL_AUTO_PID, NL_AUTO_SEQ, nl_family_id_, 0, 0,
NL80211_CMD_VENDOR, 0);
// Set actual message.
CHECK(!nla_put_u32(msg, NL80211_ATTR_IFINDEX, index))
<< "Failed to put NL80211_ATTR_IFINDEX";
switch (driver) {
case WirelessDriver::MWIFIEX:
FillMessageMwifiex(msg, tablet);
break;
case WirelessDriver::IWL:
FillMessageIwl(msg, tablet);
break;
case WirelessDriver::RTW:
FillMessageRtw(msg, tablet, domain);
break;
case WirelessDriver::ATH10K:
FillMessageAth10k(msg, tablet);
break;
case WirelessDriver::MTK:
FillMessageMTK(msg, tablet, domain);
break;
case WirelessDriver::NONE:
NOTREACHED() << "No driver found";
}
PCHECK(nl_send_auto(nl_sock_, msg) > 0) << "nl_send_auto failed";
err_ = 1;
while (err_ > 0)
nl_recvmsgs(nl_sock_, cb_);
// TODO(b/175140213): some drivers may hit this a lot. Consider making this
// even louder (CHECK()?) when known driver issues are fixed.
if (err_ != 0)
LOG(ERROR) << "netlink command failed: " << strerror(-err_);
nlmsg_free(msg);
return err_ == 0;
}
// Sets power mode according to tablet mode state. Returns true on success and
// false on failure.
bool SetPowerMode(bool tablet, power_manager::WifiRegDomain domain) {
CHECK(!genl_connect(nl_sock_)) << "Failed to connect to netlink";
nl_family_id_ = genl_ctrl_resolve(nl_sock_, "nl80211");
CHECK_GE(nl_family_id_, 0) << "family nl80211 not found";
const std::vector<std::string> device_names = GetWirelessDeviceNames();
if (device_names.empty()) {
LOG(ERROR) << "No wireless device found";
return false;
}
bool ret = true;
for (const auto& name : device_names)
if (!SendModeSwitch(name, tablet, domain))
ret = false;
return ret;
}
private:
struct nl_sock* nl_sock_;
int nl_family_id_ = 0;
struct nl_cb* cb_;
int err_ = 0; // Used by |cb_| to store errors.
};
} // namespace
int main(int argc, char* argv[]) {
DEFINE_bool(tablet, false, "Set wifi transmit power mode to tablet mode");
DEFINE_string(domain, "none",
"Regulatory domain for wifi transmit power"
"Options: fcc, eu, rest-of-world, none");
brillo::FlagHelper::Init(argc, argv, "Set wifi transmit power mode");
base::AtExitManager at_exit_manager;
power_manager::WifiRegDomain domain = power_manager::WifiRegDomain::NONE;
if (FLAGS_domain == "fcc") {
domain = power_manager::WifiRegDomain::FCC;
} else if (FLAGS_domain == "eu") {
domain = power_manager::WifiRegDomain::EU;
} else if (FLAGS_domain == "rest-of-world") {
domain = power_manager::WifiRegDomain::REST_OF_WORLD;
} else if (FLAGS_domain != "none") {
LOG(ERROR) << "Domain argument \"" << FLAGS_domain
<< "\" is not an "
"accepted value. Options: fcc, eu, rest-of-world, none";
return 1;
}
return PowerSetter().SetPowerMode(FLAGS_tablet, domain) ? 0 : 1;
}