/* Copyright (c) 2014, 2018 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. */ #include "sync.h" #define LOG_TAG "WifiHAL" #include #include "wifi_hal.h" #include "common.h" #include "cpp_bindings.h" #include "tdlsCommand.h" #include "vendor_definitions.h" /* Singleton Static Instance */ TdlsCommand* TdlsCommand::mTdlsCommandInstance = NULL; TdlsCommand::TdlsCommand(wifi_handle handle, int id, u32 vendor_id, u32 subcmd) : WifiVendorCommand(handle, id, vendor_id, subcmd) { memset(&mHandler, 0, sizeof(mHandler)); memset(&mTDLSgetStatusRspParams, 0, sizeof(wifi_tdls_status)); mRequestId = 0; } TdlsCommand::~TdlsCommand() { mTdlsCommandInstance = NULL; unregisterVendorHandler(mVendor_id, mSubcmd); } TdlsCommand* TdlsCommand::instance(wifi_handle handle) { if (handle == NULL) { ALOGE("Interface Handle is invalid"); return NULL; } if (mTdlsCommandInstance == NULL) { mTdlsCommandInstance = new TdlsCommand(handle, 0, OUI_QCA, QCA_NL80211_VENDOR_SUBCMD_TDLS_ENABLE); ALOGV("TdlsCommand %p created", mTdlsCommandInstance); return mTdlsCommandInstance; } else { if (handle != getWifiHandle(mTdlsCommandInstance->mInfo)) { /* upper layer must have cleaned up the handle and reinitialized, so we need to update the same */ ALOGV("Handle different, update the handle"); mTdlsCommandInstance->mInfo = (hal_info *)handle; } } ALOGV("TdlsCommand %p created already", mTdlsCommandInstance); return mTdlsCommandInstance; } void TdlsCommand::setSubCmd(u32 subcmd) { mSubcmd = subcmd; } /* This function will be the main handler for incoming event SUBCMD_TDLS * Call the appropriate callback handler after parsing the vendor data. */ int TdlsCommand::handleEvent(WifiEvent &event) { ALOGV("Got a TDLS message from Driver"); WifiVendorCommand::handleEvent(event); /* Parse the vendordata and get the attribute */ switch(mSubcmd) { case QCA_NL80211_VENDOR_SUBCMD_TDLS_STATE: { struct nlattr *tb_vendor[QCA_WLAN_VENDOR_ATTR_TDLS_STATE_MAX + 1]; mac_addr addr; wifi_tdls_status status; memset(&addr, 0, sizeof(mac_addr)); memset(&status, 0, sizeof(wifi_tdls_status)); nla_parse(tb_vendor, QCA_WLAN_VENDOR_ATTR_TDLS_STATE_MAX, (struct nlattr *)mVendorData, mDataLen, NULL); ALOGV("QCA_NL80211_VENDOR_SUBCMD_TDLS_STATE Received"); if (!tb_vendor[QCA_WLAN_VENDOR_ATTR_TDLS_MAC_ADDR]) { ALOGE("%s: QCA_WLAN_VENDOR_ATTR_TDLS_MAC_ADDR not found", __FUNCTION__); return WIFI_ERROR_INVALID_ARGS; } memcpy(addr, (u8 *)nla_data(tb_vendor[QCA_WLAN_VENDOR_ATTR_TDLS_MAC_ADDR]), nla_len(tb_vendor[QCA_WLAN_VENDOR_ATTR_TDLS_MAC_ADDR])); ALOGV(MAC_ADDR_STR, MAC_ADDR_ARRAY(addr)); if (!tb_vendor[QCA_WLAN_VENDOR_ATTR_TDLS_STATE]) { ALOGE("%s: QCA_WLAN_VENDOR_ATTR_TDLS_STATE not found", __FUNCTION__); return WIFI_ERROR_INVALID_ARGS; } status.state = (wifi_tdls_state) get_u32(tb_vendor[QCA_WLAN_VENDOR_ATTR_TDLS_STATE]); ALOGV("TDLS: State New : %d ", status.state); if (!tb_vendor[QCA_WLAN_VENDOR_ATTR_TDLS_REASON]) { ALOGE("%s: QCA_WLAN_VENDOR_ATTR_TDLS_REASON not found", __FUNCTION__); return WIFI_ERROR_INVALID_ARGS; } status.reason = (wifi_tdls_reason) get_s32(tb_vendor[QCA_WLAN_VENDOR_ATTR_TDLS_REASON]); ALOGV("TDLS: Reason : %d ", status.reason); if (!tb_vendor[QCA_WLAN_VENDOR_ATTR_TDLS_CHANNEL]) { ALOGE("%s: QCA_WLAN_VENDOR_ATTR_TDLS_CHANNEL not found", __FUNCTION__); return WIFI_ERROR_INVALID_ARGS; } status.channel = get_u32(tb_vendor[QCA_WLAN_VENDOR_ATTR_TDLS_CHANNEL]); ALOGV("TDLS: channel : %d ", status.channel); if (!tb_vendor[ QCA_WLAN_VENDOR_ATTR_TDLS_GLOBAL_OPERATING_CLASS]) { ALOGE("%s: QCA_WLAN_VENDOR_ATTR_TDLS_GLOBAL_OPERATING_CLASS" " not found", __FUNCTION__); return WIFI_ERROR_INVALID_ARGS; } status.global_operating_class = get_u32( tb_vendor[QCA_WLAN_VENDOR_ATTR_TDLS_GLOBAL_OPERATING_CLASS]); ALOGV("TDLS: global_operating_class: %d ", status.global_operating_class); if (mHandler.on_tdls_state_changed) (*mHandler.on_tdls_state_changed)(addr, status); else ALOGE("TDLS: No Callback registered: "); } break; default: /* Error case should not happen print log */ ALOGE("%s: Wrong TDLS subcmd received %d", __FUNCTION__, mSubcmd); } return NL_SKIP; } int TdlsCommand::handleResponse(WifiEvent &reply) { WifiVendorCommand::handleResponse(reply); switch(mSubcmd) { case QCA_NL80211_VENDOR_SUBCMD_TDLS_GET_STATUS: { struct nlattr *tb_vendor[ QCA_WLAN_VENDOR_ATTR_TDLS_GET_STATUS_MAX + 1]; nla_parse(tb_vendor, QCA_WLAN_VENDOR_ATTR_TDLS_GET_STATUS_MAX, (struct nlattr *)mVendorData, mDataLen, NULL); ALOGV("QCA_NL80211_VENDOR_SUBCMD_TDLS_GET_STATUS Received"); memset(&mTDLSgetStatusRspParams, 0, sizeof(wifi_tdls_status)); if (!tb_vendor[QCA_WLAN_VENDOR_ATTR_TDLS_GET_STATUS_STATE]) { ALOGE("%s: QCA_WLAN_VENDOR_ATTR_TDLS_GET_STATUS_STATE" " not found", __FUNCTION__); return WIFI_ERROR_INVALID_ARGS; } mTDLSgetStatusRspParams.state = (wifi_tdls_state)get_u32( tb_vendor[QCA_WLAN_VENDOR_ATTR_TDLS_GET_STATUS_STATE]); ALOGV("TDLS: State : %u ", mTDLSgetStatusRspParams.state); if (!tb_vendor[QCA_WLAN_VENDOR_ATTR_TDLS_GET_STATUS_REASON]) { ALOGE("%s: QCA_WLAN_VENDOR_ATTR_TDLS_GET_STATUS_REASON" " not found", __FUNCTION__); return WIFI_ERROR_INVALID_ARGS; } mTDLSgetStatusRspParams.reason = (wifi_tdls_reason)get_s32( tb_vendor[QCA_WLAN_VENDOR_ATTR_TDLS_GET_STATUS_REASON]); ALOGV("TDLS: Reason : %d ", mTDLSgetStatusRspParams.reason); if (!tb_vendor[QCA_WLAN_VENDOR_ATTR_TDLS_GET_STATUS_CHANNEL]) { ALOGE("%s: QCA_WLAN_VENDOR_ATTR_TDLS_GET_STATUS_CHANNEL" " not found", __FUNCTION__); return WIFI_ERROR_INVALID_ARGS; } mTDLSgetStatusRspParams.channel = get_u32(tb_vendor[ QCA_WLAN_VENDOR_ATTR_TDLS_GET_STATUS_CHANNEL]); ALOGV("TDLS: channel : %d ", mTDLSgetStatusRspParams.channel); if (!tb_vendor[ QCA_WLAN_VENDOR_ATTR_TDLS_GET_STATUS_GLOBAL_OPERATING_CLASS]) { ALOGE("%s:" "QCA_WLAN_VENDOR_ATTR_TDLS_GET_STATUS_GLOBAL_OPERATING_CLASS" " not found", __FUNCTION__); return WIFI_ERROR_INVALID_ARGS; } mTDLSgetStatusRspParams.global_operating_class = get_u32(tb_vendor[ QCA_WLAN_VENDOR_ATTR_TDLS_GET_STATUS_GLOBAL_OPERATING_CLASS]); ALOGV("TDLS: global_operating_class: %d ", mTDLSgetStatusRspParams.global_operating_class); } break; case QCA_NL80211_VENDOR_SUBCMD_TDLS_GET_CAPABILITIES: { struct nlattr *tb_vendor[ QCA_WLAN_VENDOR_ATTR_TDLS_GET_CAPS_MAX + 1]; nla_parse(tb_vendor, QCA_WLAN_VENDOR_ATTR_TDLS_GET_CAPS_MAX, (struct nlattr *)mVendorData, mDataLen, NULL); memset(&mTDLSgetCaps, 0, sizeof(wifiTdlsCapabilities)); if (!tb_vendor[ QCA_WLAN_VENDOR_ATTR_TDLS_GET_CAPS_MAX_CONC_SESSIONS] ) { ALOGE("%s: QCA_WLAN_VENDOR_ATTR_TDLS_GET_CAPS_" "MAX_CONC_SESSIONS not found", __FUNCTION__); return WIFI_ERROR_INVALID_ARGS; } mTDLSgetCaps.maxConcurrentTdlsSessionNum = get_u32(tb_vendor[ QCA_WLAN_VENDOR_ATTR_TDLS_GET_CAPS_MAX_CONC_SESSIONS]); if (!tb_vendor[ QCA_WLAN_VENDOR_ATTR_TDLS_GET_CAPS_FEATURES_SUPPORTED]) { ALOGE("%s: QCA_WLAN_VENDOR_ATTR_TDLS_GET_CAPS_" "FEATURES_SUPPORTED not found", __FUNCTION__); return WIFI_ERROR_INVALID_ARGS; } mTDLSgetCaps.tdlsSupportedFeatures = get_u32(tb_vendor[ QCA_WLAN_VENDOR_ATTR_TDLS_GET_CAPS_FEATURES_SUPPORTED]); } break; default : ALOGE("%s: Wrong TDLS subcmd response received %d", __FUNCTION__, mSubcmd); } return NL_SKIP; } wifi_error TdlsCommand::setCallbackHandler(wifi_tdls_handler nHandler, u32 event) { wifi_error res; mHandler = nHandler; res = registerVendorHandler(mVendor_id, event); if (res != WIFI_SUCCESS) { /* Error case should not happen print log */ ALOGE("%s: Unable to register Vendor Handler Vendor Id=0x%x subcmd=%u", __FUNCTION__, mVendor_id, mSubcmd); } return res; } void TdlsCommand::unregisterHandler(u32 subCmd) { unregisterVendorHandler(mVendor_id, subCmd); } void TdlsCommand::getStatusRspParams(wifi_tdls_status *status) { status->channel = mTDLSgetStatusRspParams.channel; status->global_operating_class = mTDLSgetStatusRspParams.global_operating_class; status->state = mTDLSgetStatusRspParams.state; status->reason = mTDLSgetStatusRspParams.reason; } wifi_error TdlsCommand::requestResponse() { return WifiCommand::requestResponse(mMsg); } void TdlsCommand::getCapsRspParams(wifi_tdls_capabilities *caps) { caps->max_concurrent_tdls_session_num = mTDLSgetCaps.maxConcurrentTdlsSessionNum; caps->is_global_tdls_supported = !!(mTDLSgetCaps.tdlsSupportedFeatures & IS_GLOBAL_TDLS_SUPPORTED); caps->is_per_mac_tdls_supported = !!(mTDLSgetCaps.tdlsSupportedFeatures & IS_PER_MAC_TDLS_SUPPORTED); caps->is_off_channel_tdls_supported = !!(mTDLSgetCaps.tdlsSupportedFeatures & IS_OFF_CHANNEL_TDLS_SUPPORTED); ALOGV("TDLS capabilities:"); ALOGV("max_concurrent_tdls_session_numChannel : %d\n", caps->max_concurrent_tdls_session_num); ALOGV("is_global_tdls_supported : %d\n", caps->is_global_tdls_supported); ALOGV("is_per_mac_tdls_supported : %d\n", caps->is_per_mac_tdls_supported); ALOGV("is_off_channel_tdls_supported : %d \n", caps->is_off_channel_tdls_supported); } /* wifi_enable_tdls - enables TDLS-auto mode for a specific route * * params specifies hints, which provide more information about * why TDLS is being sought. The firmware should do its best to * honor the hints before downgrading regular AP link * * On successful completion, must fire on_tdls_state_changed event * to indicate the status of TDLS operation. */ wifi_error wifi_enable_tdls(wifi_interface_handle iface, mac_addr addr, wifi_tdls_params *params, wifi_tdls_handler handler) { wifi_error ret; TdlsCommand *pTdlsCommand; struct nlattr *nl_data; interface_info *iinfo = getIfaceInfo(iface); wifi_handle handle = getWifiHandle(iface); pTdlsCommand = TdlsCommand::instance(handle); if (pTdlsCommand == NULL) { ALOGE("%s: Error TdlsCommand NULL", __FUNCTION__); return WIFI_ERROR_UNKNOWN; } pTdlsCommand->setSubCmd(QCA_NL80211_VENDOR_SUBCMD_TDLS_ENABLE); /* Create the message */ ret = pTdlsCommand->create(); if (ret != WIFI_SUCCESS) goto cleanup; ret = pTdlsCommand->set_iface_id(iinfo->name); if (ret != WIFI_SUCCESS) goto cleanup; /* Add the attributes */ nl_data = pTdlsCommand->attr_start(NL80211_ATTR_VENDOR_DATA); if (!nl_data) goto cleanup; ALOGV("%s: MAC_ADDR: " MAC_ADDR_STR, __FUNCTION__, MAC_ADDR_ARRAY(addr)); ret = pTdlsCommand->put_bytes(QCA_WLAN_VENDOR_ATTR_TDLS_ENABLE_MAC_ADDR, (char *)addr, 6); if (ret != WIFI_SUCCESS) goto cleanup; if (params != NULL) { ALOGV("%s: Channel: %d, Global operating class: %d, " "Max Latency: %dms, Min Bandwidth: %dKbps", __FUNCTION__, params->channel, params->global_operating_class, params->max_latency_ms, params->min_bandwidth_kbps); ret = pTdlsCommand->put_u32( QCA_WLAN_VENDOR_ATTR_TDLS_ENABLE_CHANNEL, params->channel); if (ret != WIFI_SUCCESS) goto cleanup; ret = pTdlsCommand->put_u32( QCA_WLAN_VENDOR_ATTR_TDLS_ENABLE_GLOBAL_OPERATING_CLASS, params->global_operating_class); if (ret != WIFI_SUCCESS) goto cleanup; ret = pTdlsCommand->put_u32( QCA_WLAN_VENDOR_ATTR_TDLS_ENABLE_MAX_LATENCY_MS, params->max_latency_ms); if (ret != WIFI_SUCCESS) goto cleanup; ret = pTdlsCommand->put_u32( QCA_WLAN_VENDOR_ATTR_TDLS_ENABLE_MIN_BANDWIDTH_KBPS, params->min_bandwidth_kbps); if (ret != WIFI_SUCCESS) goto cleanup; } pTdlsCommand->attr_end(nl_data); ret = pTdlsCommand->setCallbackHandler(handler, QCA_NL80211_VENDOR_SUBCMD_TDLS_STATE); if (ret != WIFI_SUCCESS) goto cleanup; ret = pTdlsCommand->requestResponse(); if (ret != WIFI_SUCCESS) ALOGE("%s: requestResponse Error:%d", __FUNCTION__, ret); cleanup: return ret; } /* wifi_disable_tdls - disables TDLS-auto mode for a specific route * * This terminates any existing TDLS with addr device, and frees the * device resources to make TDLS connections on new routes. * * DON'T fire any more events on 'handler' specified in earlier call to * wifi_enable_tdls after this action. */ wifi_error wifi_disable_tdls(wifi_interface_handle iface, mac_addr addr) { wifi_error ret; TdlsCommand *pTdlsCommand; struct nlattr *nl_data; interface_info *iinfo = getIfaceInfo(iface); wifi_handle handle = getWifiHandle(iface); pTdlsCommand = TdlsCommand::instance(handle); if (pTdlsCommand == NULL) { ALOGE("%s: Error TdlsCommand NULL", __FUNCTION__); return WIFI_ERROR_UNKNOWN; } pTdlsCommand->setSubCmd(QCA_NL80211_VENDOR_SUBCMD_TDLS_DISABLE); /* Create the message */ ret = pTdlsCommand->create(); if (ret != WIFI_SUCCESS) goto cleanup; ret = pTdlsCommand->set_iface_id(iinfo->name); if (ret != WIFI_SUCCESS) goto cleanup; ALOGV("%s: ifindex obtained:%d", __FUNCTION__, ret); ALOGV("%s: MAC_ADDR: " MAC_ADDR_STR, __FUNCTION__, MAC_ADDR_ARRAY(addr)); /* Add the attributes */ nl_data = pTdlsCommand->attr_start(NL80211_ATTR_VENDOR_DATA); if (!nl_data) goto cleanup; ret = pTdlsCommand->put_bytes(QCA_WLAN_VENDOR_ATTR_TDLS_DISABLE_MAC_ADDR, (char *)addr, 6); if (ret != WIFI_SUCCESS) goto cleanup; pTdlsCommand->attr_end(nl_data); ret = pTdlsCommand->requestResponse(); if (ret != WIFI_SUCCESS) ALOGE("%s: requestResponse Error:%d", __FUNCTION__, ret); cleanup: delete pTdlsCommand; return ret; } /* wifi_get_tdls_status - allows getting the status of TDLS for a specific * route */ wifi_error wifi_get_tdls_status(wifi_interface_handle iface, mac_addr addr, wifi_tdls_status *status) { wifi_error ret; TdlsCommand *pTdlsCommand; struct nlattr *nl_data; interface_info *iinfo = getIfaceInfo(iface); wifi_handle handle = getWifiHandle(iface); pTdlsCommand = TdlsCommand::instance(handle); if (pTdlsCommand == NULL) { ALOGE("%s: Error TdlsCommand NULL", __FUNCTION__); return WIFI_ERROR_UNKNOWN; } pTdlsCommand->setSubCmd(QCA_NL80211_VENDOR_SUBCMD_TDLS_GET_STATUS); /* Create the message */ ret = pTdlsCommand->create(); if (ret != WIFI_SUCCESS) goto cleanup; ret = pTdlsCommand->set_iface_id(iinfo->name); if (ret != WIFI_SUCCESS) goto cleanup; ALOGV("%s: ifindex obtained:%d", __FUNCTION__, ret); /* Add the attributes */ nl_data = pTdlsCommand->attr_start(NL80211_ATTR_VENDOR_DATA); if (!nl_data) goto cleanup; ret = pTdlsCommand->put_bytes(QCA_WLAN_VENDOR_ATTR_TDLS_GET_STATUS_MAC_ADDR, (char *)addr, 6); if (ret != WIFI_SUCCESS) goto cleanup; pTdlsCommand->attr_end(nl_data); ret = pTdlsCommand->requestResponse(); if (ret != WIFI_SUCCESS) ALOGE("%s: requestResponse Error:%d", __FUNCTION__, ret); pTdlsCommand->getStatusRspParams(status); cleanup: return ret; } /* return the current HW + Firmware combination's TDLS capabilities */ wifi_error wifi_get_tdls_capabilities(wifi_interface_handle iface, wifi_tdls_capabilities *capabilities) { wifi_error ret; TdlsCommand *pTdlsCommand; if (capabilities == NULL) { ALOGE("%s: capabilities is NULL", __FUNCTION__); return WIFI_ERROR_INVALID_ARGS; } interface_info *iinfo = getIfaceInfo(iface); wifi_handle handle = getWifiHandle(iface); pTdlsCommand = TdlsCommand::instance(handle); if (pTdlsCommand == NULL) { ALOGE("%s: Error TdlsCommand NULL", __FUNCTION__); return WIFI_ERROR_UNKNOWN; } pTdlsCommand->setSubCmd(QCA_NL80211_VENDOR_SUBCMD_TDLS_GET_CAPABILITIES); /* Create the message */ ret = pTdlsCommand->create(); if (ret != WIFI_SUCCESS) goto cleanup; ret = pTdlsCommand->set_iface_id(iinfo->name); if (ret != WIFI_SUCCESS) goto cleanup; ret = pTdlsCommand->requestResponse(); if (ret != WIFI_SUCCESS) { ALOGE("%s: requestResponse Error:%d", __FUNCTION__, ret); goto cleanup; } pTdlsCommand->getCapsRspParams(capabilities); cleanup: if (ret != WIFI_SUCCESS) memset(capabilities, 0, sizeof(wifi_tdls_capabilities)); delete pTdlsCommand; return ret; }