/*
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <stdlib.h>
#include <linux/pkt_sched.h>
#include <netlink/object-api.h>
#include <netlink-private/object-api.h>
#include <netlink-private/types.h>
#include <dlfcn.h>
#include <pthread.h>
#include "wifi_hal.h"
#include "common.h"
#include <errno.h>
interface_info *getIfaceInfo(wifi_interface_handle handle)
{
return (interface_info *)handle;
}
wifi_handle getWifiHandle(wifi_interface_handle handle)
{
return getIfaceInfo(handle)->handle;
}
hal_info *getHalInfo(wifi_handle handle)
{
return (hal_info *)handle;
}
hal_info *getHalInfo(wifi_interface_handle handle)
{
return getHalInfo(getWifiHandle(handle));
}
wifi_handle getWifiHandle(hal_info *info)
{
return (wifi_handle)info;
}
wifi_interface_handle getIfaceHandle(interface_info *info)
{
return (wifi_interface_handle)info;
}
wifi_error wifi_register_handler(wifi_handle handle, int cmd, nl_recvmsg_msg_cb_t func, void *arg)
{
hal_info *info = (hal_info *)handle;
pthread_mutex_lock(&info->cb_lock);
wifi_error result = WIFI_ERROR_OUT_OF_MEMORY;
for (int i = 0; i < info->num_event_cb; i++) {
if(info->event_cb[i].nl_cmd == cmd &&
info->event_cb[i].cb_arg == arg) {
info->event_cb[i].cb_func = func;
ALOGV("Updated event handler %p for nl_cmd 0x%0x"
" and arg %p", func, cmd, arg);
pthread_mutex_unlock(&info->cb_lock);
return WIFI_SUCCESS;
}
}
if (info->num_event_cb < info->alloc_event_cb) {
info->event_cb[info->num_event_cb].nl_cmd = cmd;
info->event_cb[info->num_event_cb].vendor_id = 0;
info->event_cb[info->num_event_cb].vendor_subcmd = 0;
info->event_cb[info->num_event_cb].cb_func = func;
info->event_cb[info->num_event_cb].cb_arg = arg;
info->num_event_cb++;
ALOGV("Successfully added event handler %p for command %d", func, cmd);
result = WIFI_SUCCESS;
} else {
result = WIFI_ERROR_OUT_OF_MEMORY;
}
pthread_mutex_unlock(&info->cb_lock);
return result;
}
wifi_error wifi_register_vendor_handler(wifi_handle handle,
uint32_t id, int subcmd, nl_recvmsg_msg_cb_t func, void *arg)
{
hal_info *info = (hal_info *)handle;
pthread_mutex_lock(&info->cb_lock);
wifi_error result = WIFI_ERROR_OUT_OF_MEMORY;
for (int i = 0; i < info->num_event_cb; i++) {
if(info->event_cb[i].vendor_id == id &&
info->event_cb[i].vendor_subcmd == subcmd)
{
info->event_cb[i].cb_func = func;
info->event_cb[i].cb_arg = arg;
ALOGV("Updated event handler %p for vendor 0x%0x, subcmd 0x%0x"
" and arg %p", func, id, subcmd, arg);
pthread_mutex_unlock(&info->cb_lock);
return WIFI_SUCCESS;
}
}
if (info->num_event_cb < info->alloc_event_cb) {
info->event_cb[info->num_event_cb].nl_cmd = NL80211_CMD_VENDOR;
info->event_cb[info->num_event_cb].vendor_id = id;
info->event_cb[info->num_event_cb].vendor_subcmd = subcmd;
info->event_cb[info->num_event_cb].cb_func = func;
info->event_cb[info->num_event_cb].cb_arg = arg;
info->num_event_cb++;
ALOGV("Added event handler %p for vendor 0x%0x, subcmd 0x%0x and arg"
" %p", func, id, subcmd, arg);
result = WIFI_SUCCESS;
} else {
result = WIFI_ERROR_OUT_OF_MEMORY;
}
pthread_mutex_unlock(&info->cb_lock);
return result;
}
void wifi_unregister_handler(wifi_handle handle, int cmd)
{
hal_info *info = (hal_info *)handle;
if (cmd == NL80211_CMD_VENDOR) {
ALOGE("Must use wifi_unregister_vendor_handler to remove vendor handlers");
return;
}
pthread_mutex_lock(&info->cb_lock);
for (int i = 0; i < info->num_event_cb; i++) {
if (info->event_cb[i].nl_cmd == cmd) {
if(i < info->num_event_cb-1) {
/* No need to memmove if only one entry exist and deleting
* the same, as the num_event_cb will become 0 in this case.
*/
memmove(&info->event_cb[i], &info->event_cb[i+1],
(info->num_event_cb - i) * sizeof(cb_info));
}
info->num_event_cb--;
ALOGV("Successfully removed event handler for command %d", cmd);
break;
}
}
pthread_mutex_unlock(&info->cb_lock);
}
void wifi_unregister_vendor_handler(wifi_handle handle, uint32_t id, int subcmd)
{
hal_info *info = (hal_info *)handle;
pthread_mutex_lock(&info->cb_lock);
for (int i = 0; i < info->num_event_cb; i++) {
if (info->event_cb[i].nl_cmd == NL80211_CMD_VENDOR
&& info->event_cb[i].vendor_id == id
&& info->event_cb[i].vendor_subcmd == subcmd) {
if(i < info->num_event_cb-1) {
/* No need to memmove if only one entry exist and deleting
* the same, as the num_event_cb will become 0 in this case.
*/
memmove(&info->event_cb[i], &info->event_cb[i+1],
(info->num_event_cb - i) * sizeof(cb_info));
}
info->num_event_cb--;
ALOGV("Successfully removed event handler for vendor 0x%0x", id);
break;
}
}
pthread_mutex_unlock(&info->cb_lock);
}
#ifdef __cplusplus
extern "C"
{
#endif /* __cplusplus */
void hexdump(void *buf, u16 len)
{
int i=0;
char *bytes = (char *)buf;
if (len) {
ALOGV("******HexDump len:%d*********", len);
for (i = 0; ((i + 7) < len); i+=8) {
ALOGV("%02x %02x %02x %02x %02x %02x %02x %02x",
bytes[i], bytes[i+1],
bytes[i+2], bytes[i+3],
bytes[i+4], bytes[i+5],
bytes[i+6], bytes[i+7]);
}
if ((len - i) >= 4) {
ALOGV("%02x %02x %02x %02x",
bytes[i], bytes[i+1],
bytes[i+2], bytes[i+3]);
i+=4;
}
for (;i < len;i++) {
ALOGV("%02x", bytes[i]);
}
ALOGV("******HexDump End***********");
} else {
return;
}
}
/* Firmware sends RSSI value without noise floor.
* Add noise floor to the same and return absolute values.
*/
u8 get_rssi(u8 rssi_wo_noise_floor)
{
return abs((int)rssi_wo_noise_floor - 96);
}
#ifdef __cplusplus
}
#endif /* __cplusplus */
/* Pointer to the table of LOWI callback funcs */
lowi_cb_table_t *LowiWifiHalApi = NULL;
/* LowiSupportedCapabilities read */
u32 lowiSupportedCapabilities = 0;
int compareLowiVersion(u16 major, u16 minor, u16 micro)
{
u32 currVersion = 0x10000*(WIFIHAL_LOWI_MAJOR_VERSION) + \
0x100*(WIFIHAL_LOWI_MINOR_VERSION) + \
WIFIHAL_LOWI_MICRO_VERSION;
u32 lowiVersion = 0x10000*(major) + \
0x100*(minor) + \
micro;
return (memcmp(&currVersion, &lowiVersion, sizeof(u32)));
}
/*
* This function will open the lowi shared library and obtain the
* Lowi Callback table and the capabilities supported.
* A version check is also performed in this function and if the version
* check fails then the callback table returned will be NULL.
*/
wifi_error fetchLowiCbTableAndCapabilities(lowi_cb_table_t **lowi_wifihal_api,
bool *lowi_get_capa_supported)
{
getCbTable_t* lowiCbTable = NULL;
int ret = 0;
wifi_error retVal = WIFI_SUCCESS;
*lowi_wifihal_api = NULL;
*lowi_get_capa_supported = false;
#if __WORDSIZE == 64
void* lowi_handle = dlopen("/vendor/lib64/liblowi_wifihal.so", RTLD_NOW);
#else
void* lowi_handle = dlopen("/vendor/lib/liblowi_wifihal.so", RTLD_NOW);
#endif
if (!lowi_handle) {
ALOGE("%s: NULL lowi_handle, err: %s", __FUNCTION__, dlerror());
return WIFI_ERROR_UNKNOWN;
}
lowiCbTable = (getCbTable_t*)dlsym(lowi_handle,
"lowi_wifihal_get_cb_table");
if (!lowiCbTable) {
ALOGE("%s: NULL lowi callback table", __FUNCTION__);
return WIFI_ERROR_UNKNOWN;
}
*lowi_wifihal_api = lowiCbTable();
/* First check whether lowi module implements the get_lowi_version
* function. All the functions in lowi module starts with
* "lowi_wifihal_" prefix thus the below function name.
*/
if ((dlsym(lowi_handle, "lowi_wifihal_get_lowi_version") != NULL) &&
((*lowi_wifihal_api)->get_lowi_version != NULL)) {
u16 lowiMajorVersion = WIFIHAL_LOWI_MAJOR_VERSION;
u16 lowiMinorVersion = WIFIHAL_LOWI_MINOR_VERSION;
u16 lowiMicroVersion = WIFIHAL_LOWI_MICRO_VERSION;
int versionCheck = -1;
ret = (*lowi_wifihal_api)->get_lowi_version(&lowiMajorVersion,
&lowiMinorVersion,
&lowiMicroVersion);
if (ret) {
ALOGE("%s: get_lowi_version returned error:%d",
__FUNCTION__, ret);
retVal = WIFI_ERROR_NOT_SUPPORTED;
goto cleanup;
}
ALOGV("%s: Lowi version:%d.%d.%d", __FUNCTION__,
lowiMajorVersion, lowiMinorVersion,
lowiMicroVersion);
/* Compare the version with version in wifihal_internal.h */
versionCheck = compareLowiVersion(lowiMajorVersion,
lowiMinorVersion,
lowiMicroVersion);
if (versionCheck < 0) {
ALOGE("%s: Version Check failed:%d", __FUNCTION__,
versionCheck);
retVal = WIFI_ERROR_NOT_SUPPORTED;
goto cleanup;
}
}
else {
ALOGV("%s: lowi_wifihal_get_lowi_version not present",
__FUNCTION__);
}
/* Check if get_lowi_capabilities func pointer exists in
* the lowi lib and populate lowi_get_capa_supported
* All the functions in lowi modules starts with
* "lowi_wifihal_ prefix" thus the below function name.
*/
if (dlsym(lowi_handle, "lowi_wifihal_get_lowi_capabilities") != NULL) {
*lowi_get_capa_supported = true;
}
else {
ALOGV("lowi_wifihal_get_lowi_capabilities() is not supported.");
*lowi_get_capa_supported = false;
}
cleanup:
if (retVal) {
*lowi_wifihal_api = NULL;
}
return retVal;
}
lowi_cb_table_t *getLowiCallbackTable(u32 requested_lowi_capabilities)
{
int ret = WIFI_SUCCESS;
bool lowi_get_capabilities_support = false;
if (LowiWifiHalApi == NULL) {
ALOGV("%s: LowiWifiHalApi Null, Initialize Lowi",
__FUNCTION__);
ret = fetchLowiCbTableAndCapabilities(&LowiWifiHalApi,
&lowi_get_capabilities_support);
if (ret != WIFI_SUCCESS || LowiWifiHalApi == NULL ||
LowiWifiHalApi->init == NULL) {
ALOGE("%s: LOWI is not supported.", __FUNCTION__);
goto cleanup;
}
/* Initialize LOWI if it isn't up already. */
ret = LowiWifiHalApi->init();
if (ret) {
ALOGE("%s: failed lowi initialization. "
"Returned error:%d. Exit.", __FUNCTION__, ret);
goto cleanup;
}
if (!lowi_get_capabilities_support ||
LowiWifiHalApi->get_lowi_capabilities == NULL) {
ALOGV("%s: Allow rtt APIs thru LOWI to proceed even though "
"get_lowi_capabilities() is not supported. Returning",
__FUNCTION__);
lowiSupportedCapabilities |=
(ONE_SIDED_RANGING_SUPPORTED|DUAL_SIDED_RANGING_SUPPORED);
return LowiWifiHalApi;
}
ret =
LowiWifiHalApi->get_lowi_capabilities(&lowiSupportedCapabilities);
if (ret) {
ALOGV("%s: failed to get lowi supported capabilities."
"Returned error:%d. Exit.", __FUNCTION__, ret);
goto cleanup;
}
}
if ((lowiSupportedCapabilities & requested_lowi_capabilities) == 0) {
return NULL;
}
return LowiWifiHalApi;
cleanup:
if (LowiWifiHalApi && LowiWifiHalApi->destroy) {
ret = LowiWifiHalApi->destroy();
}
LowiWifiHalApi = NULL;
lowiSupportedCapabilities = 0;
return LowiWifiHalApi;
}
wifi_error mapKernelErrortoWifiHalError(int kern_err)
{
if (kern_err >= 0)
return WIFI_SUCCESS;
switch (kern_err) {
case -EOPNOTSUPP:
return WIFI_ERROR_NOT_SUPPORTED;
case -EAGAIN:
return WIFI_ERROR_NOT_AVAILABLE;
case -EINVAL:
return WIFI_ERROR_INVALID_ARGS;
case -ETIMEDOUT:
return WIFI_ERROR_TIMED_OUT;
case -ENOMEM:
return WIFI_ERROR_OUT_OF_MEMORY;
case -EBUSY:
return WIFI_ERROR_BUSY;
}
return WIFI_ERROR_UNKNOWN;
}