| /* Copyright (c) 2017-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. |
| |
| * Changes from Qualcomm Innovation Center are provided under the following license: |
| |
| * Copyright (c) 2022-2023 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 "radio_mode.h" |
| #include "vendor_definitions.h" |
| #include <netlink/genl/genl.h> |
| #include <string.h> |
| #include <net/if.h> |
| |
| /* Used to handle radio command events from driver/firmware. */ |
| typedef struct radio_event_handler_s { |
| RADIOModeCommand* mRADIOModeCommandInstance; |
| } radio_event_handlers; |
| |
| wifi_error initializeRadioHandler(hal_info *info) |
| { |
| info->radio_handlers = (radio_event_handlers *)malloc( |
| sizeof(radio_event_handlers)); |
| if (info->radio_handlers) { |
| memset(info->radio_handlers, 0, sizeof(radio_event_handlers)); |
| } else { |
| ALOGE("%s: Allocation of radio event handlers failed", |
| __FUNCTION__); |
| return WIFI_ERROR_OUT_OF_MEMORY; |
| } |
| return WIFI_SUCCESS; |
| } |
| |
| wifi_error cleanupRadioHandler(hal_info *info) { |
| radio_event_handlers* event_handlers; |
| if (info && info->radio_handlers) { |
| event_handlers = (radio_event_handlers*) info->radio_handlers; |
| if (event_handlers->mRADIOModeCommandInstance) { |
| delete event_handlers->mRADIOModeCommandInstance; |
| } |
| memset(event_handlers, 0, sizeof(radio_event_handlers)); |
| free(info->radio_handlers); |
| info->radio_handlers = NULL; |
| return WIFI_SUCCESS; |
| } |
| ALOGE ("%s: info or info->radio_handlers NULL", __FUNCTION__); |
| return WIFI_ERROR_UNKNOWN; |
| } |
| |
| /* Used to handle radio mode command events from driver/firmware.*/ |
| void RADIOModeCommand::setCallbackHandler(wifi_radio_mode_change_handler handler) |
| { |
| mHandler = handler; |
| } |
| |
| void RADIOModeCommand::setReqId(wifi_request_id id) |
| { |
| mreqId = id; |
| } |
| |
| RADIOModeCommand::RADIOModeCommand(wifi_handle handle, int id, |
| u32 vendor_id, u32 subcmd) |
| : WifiVendorCommand(handle, id, vendor_id, subcmd) |
| { |
| memset(&mHandler, 0, sizeof(mHandler)); |
| mwifi_iface_mac_info = NULL; |
| 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); |
| } |
| } |
| |
| RADIOModeCommand::~RADIOModeCommand() |
| { |
| unregisterVendorHandler(mVendor_id, mSubcmd); |
| } |
| |
| |
| RADIOModeCommand* RADIOModeCommand::instance(wifi_handle handle, |
| wifi_request_id id) |
| { |
| if (handle == NULL) { |
| ALOGE("Interface Handle is invalid"); |
| return NULL; |
| } |
| hal_info *info = getHalInfo(handle); |
| if (!info) { |
| ALOGE("hal_info is invalid"); |
| return NULL; |
| } |
| RADIOModeCommand* instance = info->radio_handlers->mRADIOModeCommandInstance; |
| if (instance) { |
| if (handle != getWifiHandle(instance->mInfo)) { |
| ALOGV("%s - Handle different, update the handle", __FUNCTION__); |
| instance->mInfo = (hal_info *)handle; |
| } |
| instance->setReqId(id); |
| } else { |
| info->radio_handlers->mRADIOModeCommandInstance = |
| new RADIOModeCommand(handle, id, OUI_QCA, |
| QCA_NL80211_VENDOR_SUBCMD_WLAN_MAC_INFO); |
| instance = info->radio_handlers->mRADIOModeCommandInstance; |
| } |
| return instance; |
| } |
| |
| /* This function will be the main handler for incoming event. |
| * Call the appropriate callback handler after parsing the vendor data. |
| */ |
| int RADIOModeCommand::handleEvent(WifiEvent &event) |
| { |
| wifi_error ret = WIFI_ERROR_UNKNOWN; |
| int num_of_mac = 0; |
| wifi_mac_info mode_info; |
| memset(&mode_info, 0, sizeof(mode_info)); |
| |
| WifiVendorCommand::handleEvent(event); |
| |
| /* Parse the vendordata and get the attribute */ |
| switch(mSubcmd) |
| { |
| case QCA_NL80211_VENDOR_SUBCMD_WLAN_MAC_INFO: |
| { |
| struct nlattr *mtb_vendor[QCA_WLAN_VENDOR_ATTR_MAC_MAX + 1]; |
| struct nlattr *modeInfo; |
| int rem; |
| |
| nla_parse(mtb_vendor, QCA_WLAN_VENDOR_ATTR_MAC_MAX, |
| (struct nlattr *)mVendorData, |
| mDataLen, NULL); |
| |
| if (mtb_vendor[QCA_WLAN_VENDOR_ATTR_MAC_INFO]) |
| { |
| for (modeInfo = (struct nlattr *) nla_data(mtb_vendor[QCA_WLAN_VENDOR_ATTR_MAC_INFO]), |
| rem = nla_len(mtb_vendor[QCA_WLAN_VENDOR_ATTR_MAC_INFO]); |
| nla_ok(modeInfo, rem);modeInfo = nla_next(modeInfo, &(rem))) { |
| |
| struct nlattr *tb2[QCA_WLAN_VENDOR_ATTR_MAC_INFO_MAX+ 1]; |
| nla_parse(tb2, QCA_WLAN_VENDOR_ATTR_MAC_INFO_MAX, |
| (struct nlattr *) nla_data(modeInfo), nla_len(modeInfo), NULL); |
| if (!tb2[QCA_WLAN_VENDOR_ATTR_MAC_INFO_MAC_ID]) |
| { |
| ALOGE("%s: QCA_WLAN_VENDOR_ATTR_MAC_INFO_MAC_ID" |
| " not found", __FUNCTION__); |
| ret = WIFI_ERROR_INVALID_ARGS; |
| goto cleanup; |
| } |
| mode_info.wlan_mac_id = nla_get_u32(tb2[QCA_WLAN_VENDOR_ATTR_MAC_INFO_MAC_ID]); |
| ALOGV("mac_id[%d]: %d ", num_of_mac, mode_info.wlan_mac_id); |
| |
| if (!tb2[QCA_WLAN_VENDOR_ATTR_MAC_INFO_BAND]) |
| { |
| ALOGE("%s: QCA_WLAN_VENDOR_ATTR_MAC_INFO_BAND" |
| " NOT FOUND", __FUNCTION__); |
| ret = WIFI_ERROR_INVALID_ARGS; |
| goto cleanup; |
| } |
| mode_info.mac_band = (wlan_mac_band) nla_get_u32(tb2[QCA_WLAN_VENDOR_ATTR_MAC_INFO_BAND]); |
| ALOGV("mac_band[%d]: %d ", num_of_mac, mode_info.mac_band); |
| |
| if (tb2[QCA_WLAN_VENDOR_ATTR_MAC_IFACE_INFO]) |
| { |
| int num_of_iface = 0; |
| struct nlattr *tb_iface; |
| int rem_info; |
| |
| for (tb_iface = (struct nlattr *) nla_data(tb2[QCA_WLAN_VENDOR_ATTR_MAC_IFACE_INFO]), |
| rem_info = nla_len(tb2[QCA_WLAN_VENDOR_ATTR_MAC_IFACE_INFO]); |
| nla_ok(tb_iface, rem_info);tb_iface = nla_next(tb_iface, &(rem_info))) { |
| |
| struct nlattr *tb3[QCA_WLAN_VENDOR_ATTR_MAC_IFACE_INFO_MAX+ 1]; |
| wifi_iface_info miface_info; |
| |
| nla_parse(tb3, QCA_WLAN_VENDOR_ATTR_MAC_IFACE_INFO_MAX, |
| (struct nlattr *) nla_data(tb_iface), nla_len(tb_iface), NULL); |
| |
| if (!tb3[QCA_WLAN_VENDOR_ATTR_MAC_IFACE_INFO_IFINDEX]) |
| { |
| ALOGE("%s: QCA_WLAN_VENDOR_ATTR_MAC_IFACE_INFO_IFINDEX" |
| " NOT FOUND", __FUNCTION__); |
| ret = WIFI_ERROR_INVALID_ARGS; |
| goto cleanup; |
| } |
| if (if_indextoname(nla_get_u32(tb3[QCA_WLAN_VENDOR_ATTR_MAC_IFACE_INFO_IFINDEX]), |
| miface_info.iface_name) == NULL) |
| { |
| ALOGE("%s: Failed to convert %d IFINDEX to IFNAME", __FUNCTION__, |
| nla_get_u32(tb3[QCA_WLAN_VENDOR_ATTR_MAC_IFACE_INFO_IFINDEX])); |
| } |
| ALOGV("ifname[%d]: %s ", num_of_iface, miface_info.iface_name); |
| |
| if (!tb3[QCA_WLAN_VENDOR_ATTR_MAC_IFACE_INFO_FREQ]) |
| { |
| ALOGE("%s: QCA_WLAN_VENDOR_ATTR_MAC_IFACE_INFO_FREQ" |
| " NOT FOUND", __FUNCTION__); |
| ret = WIFI_ERROR_INVALID_ARGS; |
| goto cleanup; |
| } |
| miface_info.channel = nla_get_u32(tb3[QCA_WLAN_VENDOR_ATTR_MAC_IFACE_INFO_FREQ]); |
| ALOGV("channel[%d]: %d ", num_of_iface, miface_info.channel); |
| |
| if (!num_of_iface) |
| mode_info.iface_info = (wifi_iface_info *) |
| malloc(sizeof(wifi_iface_info)); |
| else |
| mode_info.iface_info = (wifi_iface_info *) |
| realloc(mode_info.iface_info, (num_of_iface + 1) * sizeof(wifi_iface_info)); |
| |
| if (mode_info.iface_info != NULL) { |
| memcpy(&mode_info.iface_info[num_of_iface], &miface_info, sizeof(wifi_iface_info)); |
| num_of_iface++; |
| mode_info.num_iface = num_of_iface; |
| } |
| } |
| } |
| if (!num_of_mac) |
| mwifi_iface_mac_info = (wifi_mac_info *) |
| malloc(sizeof(wifi_mac_info)); |
| else |
| mwifi_iface_mac_info = (wifi_mac_info *) |
| realloc(mwifi_iface_mac_info, (num_of_mac + 1) * (sizeof(wifi_mac_info))); |
| |
| if (mwifi_iface_mac_info != NULL) { |
| memcpy(&mwifi_iface_mac_info[num_of_mac], &mode_info, sizeof(wifi_mac_info)); |
| num_of_mac++; |
| } |
| } |
| } |
| |
| if (mHandler.on_radio_mode_change && num_of_mac) { |
| (*mHandler.on_radio_mode_change)(mreqId, num_of_mac, mwifi_iface_mac_info); |
| } |
| else { |
| ALOGE("No Callback registered: on radio mode change"); |
| ret = WIFI_ERROR_UNKNOWN; |
| goto cleanup; |
| } |
| ret = WIFI_SUCCESS; |
| } |
| break; |
| |
| default: |
| /* Error case should not happen print log */ |
| ALOGE("%s: Wrong subcmd received %d", __FUNCTION__, mSubcmd); |
| } |
| |
| cleanup: |
| if (mode_info.iface_info != NULL) { |
| free(mode_info.iface_info); |
| mode_info.iface_info = NULL; |
| } |
| if (mwifi_iface_mac_info != NULL) { |
| free(mwifi_iface_mac_info); |
| mwifi_iface_mac_info = NULL; |
| } |
| |
| return ret; |
| } |
| |
| wifi_error wifi_set_radio_mode_change_handler(wifi_request_id id, |
| wifi_interface_handle iface, |
| wifi_radio_mode_change_handler eh) |
| { |
| wifi_error ret; |
| WifiVendorCommand *vCommand = NULL; |
| wifi_handle wifiHandle = getWifiHandle(iface); |
| RADIOModeCommand *radiomodeCommand; |
| |
| ret = initialize_vendor_cmd(iface, id, |
| QCA_NL80211_VENDOR_SUBCMD_WLAN_MAC_INFO, |
| &vCommand); |
| if (ret != WIFI_SUCCESS) { |
| ALOGE("%s: Initialization failed", __FUNCTION__); |
| return ret; |
| } |
| |
| radiomodeCommand = RADIOModeCommand::instance(wifiHandle, id); |
| if (radiomodeCommand == NULL) { |
| ALOGE("%s: Error RadioModeCommand NULL", __FUNCTION__); |
| ret = WIFI_ERROR_OUT_OF_MEMORY; |
| goto cleanup; |
| } |
| radiomodeCommand->setCallbackHandler(eh); |
| radiomodeCommand->setReqId(id); |
| |
| cleanup: |
| delete vCommand; |
| return mapKernelErrortoWifiHalError(ret); |
| } |