/* Copyright (c) 2018-2019, The Linux Foundation. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * * Neither the name of The Linux Foundation nor the names of its * contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * Changes from Qualcomm Innovation Center are provided under the following license: * Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved. * SPDX-License-Identifier: BSD-3-Clause-Clear */ #include "sync.h" #include #include #include "common.h" #include "cpp_bindings.h" #include "vendor_definitions.h" #include #include #include #include "tcp_params_update.h" TCPParamCommand::TCPParamCommand(wifi_handle handle, int id, u32 vendor_id, u32 subcmd) : WifiVendorCommand(handle, id, vendor_id, subcmd) { if (registerVendorHandler(vendor_id, subcmd)) { /* Error case should not happen print log */ ALOGE("%s: Unable to register Vendor Handler Vendor Id=0x%x subcmd=%u", __FUNCTION__, vendor_id, subcmd); } memset(def_tcp_limit_output_bytes, 0, SIZE_TCP_PARAM); memset(def_tcp_adv_win_scale, 0, SIZE_TCP_PARAM); def_tcp_limit_output_bytes_valid = false; def_tcp_adv_win_scale_valid = false; } TCPParamCommand::~TCPParamCommand() { unregisterVendorHandler(OUI_QCA, QCA_NL80211_VENDOR_SUBCMD_THROUGHPUT_CHANGE_EVENT); } TCPParamCommand *TCPParamCommand::instance(wifi_handle handle, wifi_request_id id) { TCPParamCommand* mTCPParamCommandInstance; mTCPParamCommandInstance = new TCPParamCommand(handle, id, OUI_QCA, QCA_NL80211_VENDOR_SUBCMD_THROUGHPUT_CHANGE_EVENT); return mTCPParamCommandInstance; } /* This function will be the main handler for incoming event. * Call the appropriate callback handler after parsing the vendor data. */ int TCPParamCommand::handleEvent(WifiEvent &event) { wifi_error ret = WIFI_ERROR_UNKNOWN; WifiVendorCommand::handleEvent(event); u8 tpDirection, tpLevel; u32 tcpLimitOutputBytes; u8 tcpLimitOutputBytesFlag = 0; s8 tcpAdvWinScale; u8 tcpAdvWinScaleFlag = 0; u32 tcpDelackSeg; u8 tcpDelackSegFlag = 0; char value_to_str[100]; int ret_val = 0; /* Parse the vendordata and get the attribute */ switch(mSubcmd) { case QCA_NL80211_VENDOR_SUBCMD_THROUGHPUT_CHANGE_EVENT: { struct nlattr *tb[QCA_WLAN_VENDOR_ATTR_THROUGHPUT_CHANGE_MAX + 1]; nla_parse(tb, QCA_WLAN_VENDOR_ATTR_THROUGHPUT_CHANGE_MAX, (struct nlattr *)mVendorData, mDataLen, NULL); if (!tb[QCA_WLAN_VENDOR_ATTR_THROUGHPUT_CHANGE_DIRECTION] || !tb[QCA_WLAN_VENDOR_ATTR_THROUGHPUT_CHANGE_THROUGHPUT_LEVEL]) { ALOGE("Invalid event, didn't receive mandatory attributes"); return WIFI_ERROR_INVALID_ARGS; } tpDirection = nla_get_u8(tb[QCA_WLAN_VENDOR_ATTR_THROUGHPUT_CHANGE_DIRECTION]); tpLevel = nla_get_u8(tb[QCA_WLAN_VENDOR_ATTR_THROUGHPUT_CHANGE_THROUGHPUT_LEVEL]); if (tb[QCA_WLAN_VENDOR_ATTR_THROUGHPUT_CHANGE_TCP_LIMIT_OUTPUT_BYTES]) { tcpLimitOutputBytes = nla_get_u32(tb[ QCA_WLAN_VENDOR_ATTR_THROUGHPUT_CHANGE_TCP_LIMIT_OUTPUT_BYTES]); tcpLimitOutputBytesFlag = 1; } if (tb[QCA_WLAN_VENDOR_ATTR_THROUGHPUT_CHANGE_TCP_ADV_WIN_SCALE]) { tcpAdvWinScale = *(s8 *)nla_data(tb[ QCA_WLAN_VENDOR_ATTR_THROUGHPUT_CHANGE_TCP_ADV_WIN_SCALE]); tcpAdvWinScaleFlag = 1; } if (tb[QCA_WLAN_VENDOR_ATTR_THROUGHPUT_CHANGE_TCP_DELACK_SEG]) { tcpDelackSeg = nla_get_u32(tb[ QCA_WLAN_VENDOR_ATTR_THROUGHPUT_CHANGE_TCP_DELACK_SEG]); tcpDelackSegFlag = 1; } if (tpDirection == TP_CHANGE_RX) { switch(tpLevel) { case QCA_WLAN_THROUGHPUT_LEVEL_LOW: { if (def_tcp_adv_win_scale_valid) wlan_service_set_tcp_adv_win_scale(def_tcp_adv_win_scale); wlan_service_set_tcp_use_userconfig("0"); } break; case QCA_WLAN_THROUGHPUT_LEVEL_MEDIUM: case QCA_WLAN_THROUGHPUT_LEVEL_HIGH: { if (tcpAdvWinScaleFlag) { ret_val = snprintf(value_to_str, sizeof(value_to_str), "%d", tcpAdvWinScale); if (ret_val < 0 || ret_val >= (int)sizeof(value_to_str)) { ALOGE("Error in converting value to string: %d", ret_val); ret = WIFI_ERROR_UNKNOWN; goto cleanup; } wlan_service_set_tcp_adv_win_scale(value_to_str); } if (tcpDelackSegFlag && wlan_service_set_tcp_use_userconfig("1") == 0) { ret_val = snprintf(value_to_str, sizeof(value_to_str), "%d", tcpDelackSeg); if (ret_val < 0 || ret_val >= (int)sizeof(value_to_str)) { ALOGE("Error in converting value to string: %d", ret_val); ret = WIFI_ERROR_UNKNOWN; goto cleanup; } wlan_service_set_tcp_delack_seg(value_to_str); } } break; default: { /* Error case should not happen print log */ ALOGE("%s: Invalid throughput level value", __FUNCTION__); return WIFI_ERROR_INVALID_ARGS; } } } else if (tpDirection == TP_CHANGE_TX) { switch(tpLevel) { case QCA_WLAN_THROUGHPUT_LEVEL_LOW: { if (def_tcp_limit_output_bytes_valid) wlan_service_set_tcp_limit_output_bytes( def_tcp_limit_output_bytes); } break; case QCA_WLAN_THROUGHPUT_LEVEL_MEDIUM: case QCA_WLAN_THROUGHPUT_LEVEL_HIGH: { if (tcpLimitOutputBytesFlag) { ret_val = snprintf(value_to_str, sizeof(value_to_str), "%d", tcpLimitOutputBytes); if (ret_val < 0 || ret_val >= (int)sizeof(value_to_str)) { ALOGE("Error in converting value to string: %d", ret_val); ret = WIFI_ERROR_UNKNOWN; goto cleanup; } wlan_service_set_tcp_limit_output_bytes(value_to_str); } } break; default: { /* Error case should not happen print log */ ALOGE("%s: Invalid throughput level value", __FUNCTION__); return WIFI_ERROR_INVALID_ARGS; } } } else { /* Error case should not happen print log */ ALOGE("%s: Invalid throughput change direction", __FUNCTION__); return ret; } ret = WIFI_SUCCESS; } break; default: /* Error case should not happen print log */ ALOGE("%s: Wrong subcmd received %d", __FUNCTION__, mSubcmd); break; } return ret; cleanup: return ret; } wifi_error wifi_init_tcp_param_change_event_handler(wifi_interface_handle iface) { wifi_handle wifiHandle = getWifiHandle(iface); TCPParamCommand *tcpParamCommand; if (wifiHandle == NULL) { ALOGE("%s: Interface Handle is invalid", __func__); return WIFI_ERROR_UNKNOWN; } hal_info *info = getHalInfo(wifiHandle); if (!info) return WIFI_ERROR_UNKNOWN; tcpParamCommand = TCPParamCommand::instance(wifiHandle, 0); if (tcpParamCommand == NULL) { ALOGE("%s: Error TcpParamCommand NULL", __FUNCTION__); return WIFI_ERROR_OUT_OF_MEMORY; } info->tcp_param_handler = (tcp_param_cmd_handler *)malloc(sizeof(tcp_param_cmd_handler)); if (info->tcp_param_handler == NULL) { ALOGE("%s: Allocation of tcp handler failed",__FUNCTION__); delete tcpParamCommand; return WIFI_ERROR_OUT_OF_MEMORY; } info->tcp_param_handler->tcpParamCommand = tcpParamCommand; if (wlan_service_read_sys_param("/proc/sys/net/ipv4/tcp_limit_output_bytes", tcpParamCommand->def_tcp_limit_output_bytes, SIZE_TCP_PARAM) == 0) { tcpParamCommand->def_tcp_limit_output_bytes_valid = true; } if (wlan_service_read_sys_param("/proc/sys/net/ipv4/tcp_adv_win_scale", tcpParamCommand->def_tcp_adv_win_scale, SIZE_TCP_PARAM) == 0) { tcpParamCommand->def_tcp_adv_win_scale_valid = true; } return WIFI_SUCCESS; } void cleanupTCPParamCommand(hal_info *info) { TCPParamCommand *tcpParamCommand; if (info == NULL || info->tcp_param_handler == NULL) return; tcpParamCommand = info->tcp_param_handler->tcpParamCommand; if (tcpParamCommand) { if (tcpParamCommand->def_tcp_limit_output_bytes_valid) wlan_service_update_sys_param("/proc/sys/net/ipv4/tcp_limit_output_bytes", tcpParamCommand->def_tcp_limit_output_bytes); wlan_service_update_sys_param("/proc/sys/net/ipv4/tcp_use_userconfig", "0"); if (tcpParamCommand->def_tcp_adv_win_scale_valid) wlan_service_update_sys_param("/proc/sys/net/ipv4/tcp_adv_win_scale", tcpParamCommand->def_tcp_adv_win_scale); delete tcpParamCommand; } free(info->tcp_param_handler); return; } /** * wlan_service_update_sys_param() * @path: path on the file system to be modified * @str: value to be written to the path * * Generic function to update a system parameter * Return: WIFI_SUCCESS code if read is successful * Eror code if read is failure */ wifi_error wlan_service_update_sys_param(const char *path, const char *str) { int ret; FILE *fp; fp = fopen(path, "w"); if (fp == NULL) { ALOGE("%s: unable to open %s", __FUNCTION__, path); return WIFI_ERROR_UNKNOWN; } ALOGD("%s: %s %s", __FUNCTION__, path, str); ret = fputs(str, fp); fclose(fp); if (ret < 0) { ALOGE("%s: failed to write %s to %s with err %d", __FUNCTION__, str, path, ret); return mapKernelErrortoWifiHalError(ret); } return WIFI_SUCCESS; } /** * wlan_service_read_sys_param() * @path: path on the file system to be read * @str: value read from the path * * Generic function to read a system parameter * Return: WIFI_SUCCESS code if read is successful * Eror code if read is failure */ wifi_error wlan_service_read_sys_param(const char *path, char *str, size_t max_size) { size_t ret_len; FILE *fp = fopen(path, "r"); if (fp == NULL) { ALOGE("%s: unable to open %s", __FUNCTION__, path); return WIFI_ERROR_UNKNOWN; } ret_len = fread(str, 1, max_size, fp); fclose(fp); if (ret_len == 0 || ret_len == max_size) { ALOGE("Faild to read %s, ret_len = %zu", path, ret_len); return WIFI_ERROR_UNKNOWN; } ALOGD("%s: %s %s", __FUNCTION__, path, str); return WIFI_SUCCESS; } int TCPParamCommand::wlan_service_set_tcp_adv_win_scale(char *str) { return wlan_service_update_sys_param( "/proc/sys/net/ipv4/tcp_adv_win_scale", str); } int TCPParamCommand::wlan_service_set_tcp_use_userconfig(const char *str) { return wlan_service_update_sys_param( "/proc/sys/net/ipv4/tcp_use_userconfig", str); } int TCPParamCommand::wlan_service_set_tcp_delack_seg(char *str) { return wlan_service_update_sys_param( "/proc/sys/net/ipv4/tcp_delack_seg", str); } int TCPParamCommand::wlan_service_set_tcp_limit_output_bytes(char *str) { return wlan_service_update_sys_param ( "/proc/sys/net/ipv4/tcp_limit_output_bytes", str); }