blob: c2c21e9abba9addf9cbead4f1ac84738488f09a7 [file] [log] [blame]
/* 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 <utils/Log.h>
#include "wifi_hal.h"
#include "common.h"
#include "cpp_bindings.h"
#include "vendor_definitions.h"
#include <netlink/genl/genl.h>
#include <string.h>
#include <net/if.h>
#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);
}