blob: 2e3bfa00d68b66aab08cbea17b1413ca7e270571 [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 <string>
#include <vector>
#include <linux/nl80211.h>
#include <net/if.h>
#include <base/at_exit.h>
#include <base/files/file_util.h>
#include <base/logging.h>
#include <base/macros.h>
#include <base/strings/string_util.h>
#include <base/sys_info.h>
#include <brillo/flag_helper.h>
#include <netlink/attr.h>
#include <netlink/genl/ctrl.h>
#include <netlink/genl/family.h>
#include <netlink/genl/genl.h>
#include <netlink/msg.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 VPD.
#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
namespace {
// Kernel module path for Marvell wireless driver.
const char kMwifiexModulePath[] = "/sys/module/mwifiex";
int ErrorHandler(struct sockaddr_nl* nla, struct nlmsgerr* err, void* arg) {
*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 };
// Returns the type of wireless driver that's present on the system.
WirelessDriver GetWirelessDriverType() {
// TODO(crbug.com/782924): Improve wireless driver detection.
return base::PathExists(base::FilePath(kMwifiexModulePath))
? WirelessDriver::MWIFIEX
: WirelessDriver::IWL;
}
// Returns the wireless device index (either wlan0 or mlan0).
// 0 is returned if the device isn't found.
uint32_t GetWirelessDeviceIndex() {
// TODO(crbug.com/782924): Don't hardcode device names.
uint32_t index = if_nametoindex("mlan0");
if (!index)
index = if_nametoindex("wlan0");
return index;
}
// 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);
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 VPD, or an empty vector if VPD should be
// used. VPD limits are expected; this is just a hack for devices (currently
// only cave) that lack limits in VPD. See b:70549692 for details.
std::vector<uint32_t> GetNonVpdIwlPowerTable(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 = GetNonVpdIwlPowerTable(tablet);
const bool use_vpd = table.empty();
CHECK(!nla_put_u32(msg, NL80211_ATTR_VENDOR_SUBCMD,
use_vpd ? 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);
CHECK(limits) << "Failed in nla_nest_start";
if (use_vpd) {
int index = tablet ? IWL_TABLET_PROFILE_INDEX : IWL_CLAMSHELL_PROFILE_INDEX;
CHECK(!nla_put_u32(msg, IWL_MVM_VENDOR_ATTR_SAR_CHAIN_A_PROFILE, index))
<< "Failed to put IWL_MVM_VENDOR_ATTR_SAR_CHAIN_A_PROFILE";
CHECK(!nla_put_u32(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 MWIFIEX_VENDOR_CMD_ATTR_TXP_LIMIT_24";
CHECK(!nla_put_u32(msg, IWL_MVM_VENDOR_ATTR_TXP_LIMIT_52L, table[1] * 8))
<< "Failed to put MWIFIEX_VENDOR_CMD_ATTR_TXP_LIMIT_52L";
CHECK(!nla_put_u32(msg, IWL_MVM_VENDOR_ATTR_TXP_LIMIT_52H, table[2] * 8))
<< "Failed to put MWIFIEX_VENDOR_CMD_ATTR_TXP_LIMIT_52H";
}
CHECK(!nla_nest_end(msg, limits)) << "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() {
nl_socket_free(nl_sock_);
nl_cb_put(cb_);
}
// Sets power mode according to tablet mode state. Returns true on success and
// false on failure.
bool SetPowerMode(bool tablet) {
CHECK(!genl_connect(nl_sock_)) << "Failed to connect to netlink";
int nl_family_id = genl_ctrl_resolve(nl_sock_, "nl80211");
CHECK_GE(nl_family_id, 0) << "family nl80211 not found";
struct nl_msg* msg = nlmsg_alloc();
CHECK(msg);
// Set header.
genlmsg_put(msg, NL_AUTO_PID, NL_AUTO_SEQ, nl_family_id, 0, 0,
NL80211_CMD_VENDOR, 0);
// Set actual message.
const uint32_t index = GetWirelessDeviceIndex();
if (!index) {
LOG(ERROR) << "Failed to find wireless device index";
return false;
}
CHECK(!nla_put_u32(msg, NL80211_ATTR_IFINDEX, index))
<< "Failed to put NL80211_ATTR_IFINDEX";
switch (GetWirelessDriverType()) {
case WirelessDriver::MWIFIEX:
FillMessageMwifiex(msg, tablet);
break;
case WirelessDriver::IWL:
FillMessageIwl(msg, tablet);
break;
case WirelessDriver::NONE:
LOG(ERROR) << "No valid wireless driver found";
return false;
}
CHECK_GE(nl_send_auto(nl_sock_, msg), 0)
<< "nl_send_auto failed: " << nl_geterror(err_);
while (err_ != 0)
nl_recvmsgs(nl_sock_, cb_);
nlmsg_free(msg);
return true;
}
private:
struct nl_sock* nl_sock_;
struct nl_cb* cb_;
int err_ = 0; // Used by |cb_| to store errors.
DISALLOW_COPY_AND_ASSIGN(PowerSetter);
};
} // namespace
int main(int argc, char* argv[]) {
DEFINE_bool(tablet, false, "Set wifi transmit power mode to tablet mode");
brillo::FlagHelper::Init(argc, argv, "Set wifi transmit power mode");
base::AtExitManager at_exit_manager;
return PowerSetter().SetPowerMode(FLAGS_tablet) ? 0 : 1;
}