| /* | |
| * 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; | |
| case -ENOBUFS: | |
| return WIFI_ERROR_TOO_MANY_REQUESTS; | |
| } | |
| return WIFI_ERROR_UNKNOWN; | |
| } |