blob: 8cbbab2d8caf2e026734e37d80381a9ef29562b0 [file] [log] [blame]
/* 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);
}