| // Copyright 2016 The ChromiumOS Authors |
| // 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/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 <base/threading/platform_thread.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 |
| |
| #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 |
| |
| // Implementation constants |
| #define MAX_ATTEMPTS 3 |
| |
| 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, RTW88, RTW89, 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, |
| }; |
| |
| enum Rtw88SARBand { |
| kRtw88SarBand2g = 0, |
| kRtw88SarBand5g1 = 1, |
| kRtw88SarBand5g3 = 2, |
| kRtw88SarBand5g4 = 3, |
| }; |
| |
| enum Rtw89SARBand { |
| kRtw89SarBand2g = 0, |
| kRtw89SarBand5g1 = 1, |
| kRtw89SarBand5g3 = 2, |
| kRtw89SarBand5g4 = 3, |
| kRtw89SarBand6g1 = 4, |
| kRtw89SarBand6g2 = 5, |
| kRtw89SarBand6g3 = 6, |
| kRtw89SarBand6g4 = 7, |
| kRtw89SarBand6g5 = 8, |
| kRtw89SarBand6g6 = 9, |
| }; |
| |
| // 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>(); |
| 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::RTW88}, |
| {"rtw_8822ce", WirelessDriver::RTW88}, |
| {"rtw89_8852ae", WirelessDriver::RTW89}, |
| {"rtw89_8852ce", WirelessDriver::RTW89}, |
| {"rtw89_8852be", WirelessDriver::RTW89}, |
| {"rtw89_8852bte", WirelessDriver::RTW89}, |
| {"mt7921e", WirelessDriver::MTK}, |
| {"mt7921s", WirelessDriver::MTK}, |
| {"mt7925e", 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; |
| if (!base::ReadSymbolicLink(link_path, &driver_path)) { |
| // This can race with device removal, for instance. Just warn and do |
| // nothing. |
| PLOG(ERROR) << "Failed reading symbolic link: " << link_path.value(); |
| return WirelessDriver::NONE; |
| } |
| |
| 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 rtw88 driver in chromeos-config, |
| // the function will fail. |
| std::map<enum RealtekVndcmdSARBand, uint8_t> |
| GetRtw88VndChromeosConfigPowerTable(bool tablet, |
| power_manager::WifiRegDomain domain) { |
| std::map<enum RealtekVndcmdSARBand, uint8_t> power_table = {}; |
| auto config = std::make_unique<brillo::CrosConfig>(); |
| 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; |
| |
| // Rtw88 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; |
| } |
| |
| // Returns a vector of tx power limits for mode |tablet|. |
| // If the board does not store power limits for rtw88 driver in chromeos-config, |
| // the function will fail. |
| std::map<enum Rtw88SARBand, uint8_t> GetRtw88ChromeosConfigPowerTable( |
| bool tablet, power_manager::WifiRegDomain domain) { |
| std::map<enum Rtw88SARBand, uint8_t> power_table = {}; |
| auto config = std::make_unique<brillo::CrosConfig>(); |
| 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[kRtw88SarBand2g] = 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[kRtw88SarBand5g1] = power_limit; |
| |
| // Rtw88 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[kRtw88SarBand5g3] = 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[kRtw88SarBand5g4] = power_limit; |
| |
| // The table values are based on units for the vendor command, which is |
| // 0.125 dBm, while the common SAR API expects 0.25 dBm. Since the tables |
| // likely won't be updated and need to continue to work, we have to scale |
| // the values here. |
| for (auto& [_, value] : power_table) { |
| value /= 2; |
| } |
| |
| return power_table; |
| } |
| |
| // Returns a vector of tx power limits for mode |tablet|. |
| // If the board does not store power limits for rtw89 driver in chromeos-config, |
| // the function will fail. |
| std::map<enum Rtw89SARBand, uint8_t> GetRtw89ChromeosConfigPowerTable( |
| bool tablet, power_manager::WifiRegDomain domain) { |
| std::map<enum Rtw89SARBand, uint8_t> power_table = {}; |
| auto config = std::make_unique<brillo::CrosConfig>(); |
| 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, offset_6g = 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); |
| } |
| if (config->GetString(wifi_geo_offsets_path, "offset-6g", &offset_string)) { |
| offset_6g = 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[kRtw89SarBand2g] = 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[kRtw89SarBand5g1] = power_limit; |
| |
| // Rtw89 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[kRtw89SarBand5g3] = 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[kRtw89SarBand5g4] = power_limit; |
| |
| if (!config->GetString(wifi_power_table_path, "limit-6g-1", &value)) { |
| return power_table; |
| } |
| |
| power_limit = std::stoi(value) + offset_6g; |
| CHECK(power_limit <= UINT8_MAX) << "Invalid power limit configs. Limit " |
| "value cannot exceed 255."; |
| power_table[kRtw89SarBand6g1] = power_limit; |
| |
| CHECK(config->GetString(wifi_power_table_path, "limit-6g-2", &value)) |
| << "Could not get ChromeosConfig power table."; |
| power_limit = std::stoi(value) + offset_6g; |
| CHECK(power_limit <= UINT8_MAX) << "Invalid power limit configs. Limit " |
| "value cannot exceed 255."; |
| power_table[kRtw89SarBand6g2] = power_limit; |
| |
| CHECK(config->GetString(wifi_power_table_path, "limit-6g-3", &value)) |
| << "Could not get ChromeosConfig power table."; |
| power_limit = std::stoi(value) + offset_6g; |
| CHECK(power_limit <= UINT8_MAX) << "Invalid power limit configs. Limit " |
| "value cannot exceed 255."; |
| power_table[kRtw89SarBand6g3] = power_limit; |
| |
| CHECK(config->GetString(wifi_power_table_path, "limit-6g-4", &value)) |
| << "Could not get ChromeosConfig power table."; |
| power_limit = std::stoi(value) + offset_6g; |
| CHECK(power_limit <= UINT8_MAX) << "Invalid power limit configs. Limit " |
| "value cannot exceed 255."; |
| power_table[kRtw89SarBand6g4] = power_limit; |
| |
| CHECK(config->GetString(wifi_power_table_path, "limit-6g-5", &value)) |
| << "Could not get ChromeosConfig power table."; |
| power_limit = std::stoi(value) + offset_6g; |
| CHECK(power_limit <= UINT8_MAX) << "Invalid power limit configs. Limit " |
| "value cannot exceed 255."; |
| power_table[kRtw89SarBand6g5] = power_limit; |
| |
| CHECK(config->GetString(wifi_power_table_path, "limit-6g-6", &value)) |
| << "Could not get ChromeosConfig power table."; |
| power_limit = std::stoi(value) + offset_6g; |
| CHECK(power_limit <= UINT8_MAX) << "Invalid power limit configs. Limit " |
| "value cannot exceed 255."; |
| power_table[kRtw89SarBand6g6] = 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"; |
| } |
| |
| // 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"; |
| |
| CHECK(!nla_put_u32(msg, NL80211_ATTR_VENDOR_SUBCMD, |
| IWL_MVM_VENDOR_CMD_SET_SAR_PROFILE)) |
| << "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"; |
| |
| 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"; |
| |
| CHECK(!nla_nest_end(msg, limits)) << "Failed in nla_nest_end"; |
| } |
| |
| // Fill in nl80211 vendor command message for the rtw88 driver. |
| // This supports the legacy vendor command method. |
| // This is kept around to work with older kernels. |
| void FillMessageRtw88Vnd(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 : |
| GetRtw88VndChromeosConfigPowerTable(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 the rtw88 driver. |
| void FillMessageRtw88(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 : GetRtw88ChromeosConfigPowerTable(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"; |
| } |
| |
| // Fill in nl80211 message for the rtw89 driver. |
| void FillMessageRtw89(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 : GetRtw89ChromeosConfigPowerTable(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"; |
| } |
| |
| // 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). |
| // Note: Enum indices 5-10 were added for the 6GHz bands present in |
| // crrev.com/c/3953998. |
| enum MtkSARBand { |
| kMtkSarBand2g = 0, |
| kMtkSarBand5g1 = 1, |
| kMtkSarBand5g2 = 2, |
| kMtkSarBand5g3 = 3, |
| kMtkSarBand5g4 = 4, |
| kMtkSarBand6g1 = 5, |
| kMtkSarBand6g2 = 6, |
| kMtkSarBand6g3 = 7, |
| kMtkSarBand6g4 = 8, |
| kMtkSarBand6g5 = 9, |
| kMtkSarBand6g6 = 10, |
| }; |
| |
| 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>(); |
| 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, limit_6g = UINT8_MAX, |
| offset_2g = 0, offset_5g = 0, offset_6g = 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, "limit-6g", &geo_string)) { |
| limit_6g = 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); |
| } |
| if (config->GetString(wifi_geo_power_table_path, "offset-6g", |
| &geo_string)) { |
| offset_6g = 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); |
| |
| // See if there's a 6GHz entry. If so, assume that all 6GHz values will |
| // be populated. Otherwise, assume no 6GHz values and don't touch the |
| // 6GHz config. |
| // This is largely done to be backwards compatible with MT7921, which doesn't |
| // require 6GHz config (as it is not 6GHz capable). |
| if (config->GetString(wifi_power_table_path, "limit-6g-1", &value)) { |
| power_limit = std::stoi(value) + offset_6g; |
| CHECK(power_limit >= 0 && power_limit <= UINT8_MAX) |
| << "Invalid power limit configs. Limit value cannot exceed 255."; |
| power_table[kMtkSarBand6g1] = std::min(power_limit, limit_6g); |
| |
| CHECK(config->GetString(wifi_power_table_path, "limit-6g-2", &value)) |
| << "Could not get ChromeosConfig power table."; |
| power_limit = std::stoi(value) + offset_6g; |
| CHECK(power_limit >= 0 && power_limit <= UINT8_MAX) |
| << "Invalid power limit configs. Limit value cannot exceed 255."; |
| power_table[kMtkSarBand6g2] = std::min(power_limit, limit_6g); |
| |
| CHECK(config->GetString(wifi_power_table_path, "limit-6g-3", &value)) |
| << "Could not get ChromeosConfig power table."; |
| power_limit = std::stoi(value) + offset_6g; |
| CHECK(power_limit >= 0 && power_limit <= UINT8_MAX) |
| << "Invalid power limit configs. Limit value cannot exceed 255."; |
| power_table[kMtkSarBand6g3] = std::min(power_limit, limit_6g); |
| |
| CHECK(config->GetString(wifi_power_table_path, "limit-6g-4", &value)) |
| << "Could not get ChromeosConfig power table."; |
| power_limit = std::stoi(value) + offset_6g; |
| CHECK(power_limit >= 0 && power_limit <= UINT8_MAX) |
| << "Invalid power limit configs. Limit value cannot exceed 255."; |
| power_table[kMtkSarBand6g4] = std::min(power_limit, limit_6g); |
| |
| CHECK(config->GetString(wifi_power_table_path, "limit-6g-5", &value)) |
| << "Could not get ChromeosConfig power table."; |
| power_limit = std::stoi(value) + offset_6g; |
| CHECK(power_limit >= 0 && power_limit <= UINT8_MAX) |
| << "Invalid power limit configs. Limit value cannot exceed 255."; |
| power_table[kMtkSarBand6g5] = std::min(power_limit, limit_6g); |
| |
| CHECK(config->GetString(wifi_power_table_path, "limit-6g-6", &value)) |
| << "Could not get ChromeosConfig power table."; |
| power_limit = std::stoi(value) + offset_6g; |
| CHECK(power_limit >= 0 && power_limit <= UINT8_MAX) |
| << "Invalid power limit configs. Limit value cannot exceed 255."; |
| power_table[kMtkSarBand6g6] = std::min(power_limit, limit_6g); |
| } |
| |
| 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, |
| power_manager::TriggerSource tr_source, |
| int attempt = 0, |
| bool rtw88_use_vendor_cmd = false) { |
| 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); |
| |
| // Ath10k, MTK, and rtw89 use the common SAR API. Other drivers use vendor- |
| // specific commands. |
| // 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 || |
| (driver == WirelessDriver::RTW88 && !rtw88_use_vendor_cmd) || |
| driver == WirelessDriver::RTW89) { |
| 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); |
| } |
| |
| // Marvell does not support Tx power change based on reg domain change. |
| if (driver == WirelessDriver::MWIFIEX && |
| tr_source == power_manager::TriggerSource::REG_DOMAIN) { |
| DLOG(WARNING) << "Marvell WiFi does not support Tx power change based on " |
| "reg domain change."; |
| |
| return false; |
| } |
| |
| // 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::RTW88: |
| if (rtw88_use_vendor_cmd) { |
| FillMessageRtw88Vnd(msg, tablet, domain); |
| } else { |
| FillMessageRtw88(msg, tablet, domain); |
| } |
| break; |
| case WirelessDriver::RTW89: |
| FillMessageRtw89(msg, tablet, domain); |
| break; |
| case WirelessDriver::ATH10K: |
| FillMessageAth10k(msg, tablet); |
| break; |
| case WirelessDriver::MTK: |
| FillMessageMTK(msg, tablet, domain); |
| break; |
| case WirelessDriver::NONE: |
| NOTREACHED_IN_MIGRATION() << "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_); |
| } |
| |
| // rtw88 driver on older kernels only support a vendor command, while |
| // on newer kernels support the common SAR API. Fall back to the vendor |
| // command if the common SAR API method failed. |
| if (driver == WirelessDriver::RTW88 && err_ == -EOPNOTSUPP && |
| !rtw88_use_vendor_cmd) { |
| return SendModeSwitch(dev_name, tablet, domain, tr_source, ++attempt, |
| /* rtw88_use_vendor_cmd= */ true); |
| } |
| |
| // Mediatek chips have a command queue. It's possible that this is full |
| // when the request to change modes was sent. This should not be a fatal |
| // error, it should be retried. |
| if (driver == WirelessDriver::MTK && err_ == -ENOMEM && |
| attempt <= MAX_ATTEMPTS) { |
| LOG(WARNING) << "Retrying as a result of ENOMEM error attempt: " |
| << attempt; |
| base::PlatformThread::Sleep(base::Milliseconds(10 << attempt)); |
| return SendModeSwitch(dev_name, tablet, domain, tr_source, ++attempt); |
| } |
| |
| CHECK(err_ == 0) << "netlink command failed: " << strerror(-err_); |
| |
| LOG(INFO) << "Succeeded after " << attempt << " retries"; |
| 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, |
| power_manager::TriggerSource tr_source) { |
| 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, tr_source)) { |
| 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"); |
| DEFINE_string(source, "unknown", |
| "Trigger source for wifi transmit power" |
| "Options: init, tablet_mode, reg_domain," |
| " proximity, udev_event, unknown"); |
| CHECK(brillo::FlagHelper::Init(argc, argv, "Set wifi transmit power mode", |
| brillo::FlagHelper::InitFuncType::kReturn)); |
| |
| base::AtExitManager at_exit_manager; |
| power_manager::WifiRegDomain domain = power_manager::WifiRegDomain::NONE; |
| power_manager::TriggerSource tr_source = |
| power_manager::TriggerSource::UNKNOWN; |
| 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; |
| } |
| |
| if (FLAGS_source == "init") { |
| tr_source = power_manager::TriggerSource::INIT; |
| } else if (FLAGS_source == "tablet_mode") { |
| tr_source = power_manager::TriggerSource::TABLET_MODE; |
| } else if (FLAGS_source == "reg_domain") { |
| tr_source = power_manager::TriggerSource::REG_DOMAIN; |
| } else if (FLAGS_source == "proximity") { |
| tr_source = power_manager::TriggerSource::PROXIMITY; |
| } else if (FLAGS_source == "udev_event") { |
| tr_source = power_manager::TriggerSource::UDEV_EVENT; |
| } else if (FLAGS_source != "unknown") { |
| LOG(ERROR) << "Trigger source argument \"" << FLAGS_source |
| << "\" is not an " |
| "accepted value. Options: init, tablet_mode," |
| " reg_domain, proximity, udev_event, unknown"; |
| return 1; |
| } |
| |
| return PowerSetter().SetPowerMode(FLAGS_tablet, domain, tr_source) ? 0 : 1; |
| } |