| /* |
| * Copyright 2012-2019, 2022-2023 NXP |
| * |
| * 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 <sys/stat.h> |
| |
| #include <array> |
| #include <functional> |
| #include <string.h> |
| #include <list> |
| #include <map> |
| #include <mutex> |
| #include <unordered_set> |
| #include <vector> |
| |
| #include <android-base/stringprintf.h> |
| #include <cutils/properties.h> |
| #include <log/log.h> |
| |
| #include <phNxpLog.h> |
| #include <phNxpUciHal.h> |
| #include <phNxpUciHal_Adaptation.h> |
| #include <phNxpUciHal_ext.h> |
| #include <phTmlUwb_spi.h> |
| |
| #include "hal_nxpuwb.h" |
| #include "phNxpConfig.h" |
| #include "phNxpUciHal_utils.h" |
| #include "sessionTrack.h" |
| |
| using namespace std; |
| using android::base::StringPrintf; |
| |
| /*********************** Global Variables *************************************/ |
| /* UCI HAL Control structure */ |
| phNxpUciHal_Control_t nxpucihal_ctrl; |
| |
| bool uwb_device_initialized = false; |
| bool uwb_get_platform_id = false; |
| uint32_t timeoutTimerId = 0; |
| char persistant_log_path[120]; |
| constexpr long HAL_WRITE_TIMEOUT_MS = 1000; |
| /**************** local methods used in this file only ************************/ |
| static void phNxpUciHal_write_complete(void* pContext, |
| phTmlUwb_TransactInfo_t* pInfo); |
| extern int phNxpUciHal_fw_download(); |
| static void phNxpUciHal_getVersionInfo(); |
| static tHAL_UWB_STATUS phNxpUciHal_sendCoreConfig(const uint8_t *p_cmd, |
| long buffer_size); |
| |
| /******************************************************************************* |
| * RX packet handler |
| ******************************************************************************/ |
| struct phNxpUciHal_RxHandler { |
| // mt, gid, oid: packet type |
| uint8_t mt; |
| uint8_t gid; |
| uint8_t oid; |
| |
| // skip_reporting: not reports the packet to upper layer if it's true |
| bool skip_reporting; |
| bool run_once; |
| |
| std::function<void(size_t packet_len, const uint8_t *packet)> callback; |
| |
| phNxpUciHal_RxHandler(uint8_t mt, uint8_t gid, uint8_t oid, |
| bool skip_reporting, bool run_once, |
| std::function<void(size_t packet_len, const uint8_t *packet)> callback) : |
| mt(mt), gid(gid), oid(oid), |
| skip_reporting(skip_reporting), |
| run_once(run_once), |
| callback(callback) { } |
| }; |
| |
| static std::list<std::shared_ptr<phNxpUciHal_RxHandler>> rx_handlers; |
| static std::mutex rx_handlers_lock; |
| |
| std::shared_ptr<phNxpUciHal_RxHandler> phNxpUciHal_rx_handler_add( |
| uint8_t mt, uint8_t gid, uint8_t oid, |
| bool skip_reporting, bool run_once, |
| std::function<void(size_t packet_len, const uint8_t *packet)> callback) |
| { |
| auto handler = std::make_shared<phNxpUciHal_RxHandler>(mt, gid, oid, |
| skip_reporting, run_once, callback); |
| std::lock_guard<std::mutex> guard(rx_handlers_lock); |
| rx_handlers.push_back(handler); |
| return handler; |
| } |
| |
| void phNxpUciHal_rx_handler_del(std::shared_ptr<phNxpUciHal_RxHandler> handler) |
| { |
| std::lock_guard<std::mutex> guard(rx_handlers_lock); |
| rx_handlers.remove(handler); |
| } |
| |
| // Returns true when this packet is handled by one of the handler. |
| static bool phNxpUciHal_rx_handler_check(size_t packet_len, const uint8_t *packet) |
| { |
| const uint8_t mt = ((packet[0]) & UCI_MT_MASK) >> UCI_MT_SHIFT; |
| const uint8_t gid = packet[0] & UCI_GID_MASK; |
| const uint8_t oid = packet[1] & UCI_OID_MASK; |
| bool skip_packet = false; |
| |
| std::lock_guard<std::mutex> guard(rx_handlers_lock); |
| |
| for (auto handler : rx_handlers) { |
| if (mt == handler->mt && gid == handler->gid && oid == handler->oid) { |
| handler->callback(packet_len, packet); |
| if (handler->skip_reporting) { |
| skip_packet = true; |
| } |
| } |
| } |
| rx_handlers.remove_if([mt, gid, oid](auto& handler) { |
| return mt == handler->mt && gid == handler->gid && oid == handler->oid && handler->run_once; |
| }); |
| |
| return skip_packet; |
| } |
| |
| static void phNxpUciHal_rx_handler_destroy(void) |
| { |
| std::lock_guard<std::mutex> guard(rx_handlers_lock); |
| rx_handlers.clear(); |
| } |
| |
| /****************************************************************************** |
| * Function phNxpUciHal_client_thread |
| * |
| * Description This function is a thread handler which handles all TML and |
| * UCI messages. |
| * |
| * Returns void |
| * |
| ******************************************************************************/ |
| static void phNxpUciHal_client_thread(phNxpUciHal_Control_t* p_nxpucihal_ctrl) |
| { |
| NXPLOG_UCIHAL_D("thread started"); |
| |
| bool thread_running = true; |
| |
| while (thread_running) { |
| /* Fetch next message from the UWB stack message queue */ |
| auto msg = p_nxpucihal_ctrl->gDrvCfg.pClientMq->recv(); |
| |
| if (!thread_running) { |
| break; |
| } |
| |
| switch (msg->eMsgType) { |
| case PH_LIBUWB_DEFERREDCALL_MSG: { |
| phLibUwb_DeferredCall_t* deferCall = (phLibUwb_DeferredCall_t*)(msg->pMsgData); |
| |
| REENTRANCE_LOCK(); |
| deferCall->pCallback(deferCall->pParameter); |
| REENTRANCE_UNLOCK(); |
| |
| break; |
| } |
| |
| case UCI_HAL_OPEN_CPLT_MSG: { |
| REENTRANCE_LOCK(); |
| if (nxpucihal_ctrl.p_uwb_stack_cback != NULL) { |
| /* Send the event */ |
| (*nxpucihal_ctrl.p_uwb_stack_cback)(HAL_UWB_OPEN_CPLT_EVT, |
| HAL_UWB_STATUS_OK); |
| } |
| REENTRANCE_UNLOCK(); |
| break; |
| } |
| |
| case UCI_HAL_CLOSE_CPLT_MSG: { |
| REENTRANCE_LOCK(); |
| if (nxpucihal_ctrl.p_uwb_stack_cback != NULL) { |
| /* Send the event */ |
| (*nxpucihal_ctrl.p_uwb_stack_cback)(HAL_UWB_CLOSE_CPLT_EVT, |
| HAL_UWB_STATUS_OK); |
| } |
| thread_running = false; |
| REENTRANCE_UNLOCK(); |
| break; |
| } |
| |
| case UCI_HAL_INIT_CPLT_MSG: { |
| REENTRANCE_LOCK(); |
| if (nxpucihal_ctrl.p_uwb_stack_cback != NULL) { |
| /* Send the event */ |
| (*nxpucihal_ctrl.p_uwb_stack_cback)(HAL_UWB_INIT_CPLT_EVT, |
| HAL_UWB_STATUS_OK); |
| } |
| REENTRANCE_UNLOCK(); |
| break; |
| } |
| |
| case UCI_HAL_ERROR_MSG: { |
| REENTRANCE_LOCK(); |
| if (nxpucihal_ctrl.p_uwb_stack_cback != NULL) { |
| /* Send the event */ |
| (*nxpucihal_ctrl.p_uwb_stack_cback)(HAL_UWB_ERROR_EVT, |
| HAL_UWB_ERROR_EVT); |
| } |
| REENTRANCE_UNLOCK(); |
| break; |
| } |
| } |
| } |
| |
| NXPLOG_UCIHAL_D("NxpUciHal thread stopped"); |
| } |
| |
| /****************************************************************************** |
| * Function phNxpUciHal_parse |
| * |
| * Description This function parses all the data passing through the HAL. |
| * |
| * Returns It returns true if the incoming command to be skipped. |
| * |
| ******************************************************************************/ |
| bool phNxpUciHal_parse(uint16_t data_len, const uint8_t *p_data) |
| { |
| bool ret = false; |
| |
| if (data_len < UCI_MSG_HDR_SIZE) |
| return false; |
| |
| const uint8_t mt = (p_data[0] &UCI_MT_MASK) >> UCI_MT_SHIFT; |
| const uint8_t gid = p_data[0] & UCI_GID_MASK; |
| const uint8_t oid = p_data[1] & UCI_OID_MASK; |
| |
| if (mt == UCI_MT_CMD) { |
| if ((gid == UCI_GID_ANDROID) && (oid == UCI_MSG_ANDROID_SET_COUNTRY_CODE)) { |
| char country_code[2]; |
| if (data_len == 6) { |
| country_code[0] = (char)p_data[4]; |
| country_code[1] = (char)p_data[5]; |
| } else { |
| NXPLOG_UCIHAL_E("Unexpected payload length for ANDROID_SET_COUNTRY_CODE, handle this with 00 country code"); |
| country_code[0] = '0'; |
| country_code[1] = '0'; |
| } |
| phNxpUciHal_handle_set_country_code(country_code); |
| return true; |
| } else if ((gid == UCI_GID_PROPRIETARY_0x0F) && (oid == SET_VENDOR_SET_CALIBRATION)) { |
| if (p_data[UCI_MSG_HDR_SIZE + 1] == |
| VENDOR_CALIB_PARAM_TX_POWER_PER_ANTENNA) { |
| phNxpUciHal_handle_set_calibration(p_data, data_len); |
| } |
| } else if ((gid == UCI_GID_SESSION_MANAGE) && (oid == UCI_MSG_SESSION_SET_APP_CONFIG)) { |
| return phNxpUciHal_handle_set_app_config(&nxpucihal_ctrl.cmd_len, nxpucihal_ctrl.p_cmd_data); |
| } else if ((gid == UCI_GID_SESSION_MANAGE) && (oid == UCI_MSG_SESSION_STATE_INIT)) { |
| SessionTrack_onSessionInit(nxpucihal_ctrl.cmd_len, nxpucihal_ctrl.p_cmd_data); |
| } |
| } else { |
| ret = false; |
| } |
| return ret; |
| } |
| |
| /****************************************************************************** |
| * Function phNxpUciHal_open |
| * |
| * Description This function is called by libuwb-uci during the |
| * initialization of the UWBC. It opens the physical connection |
| * with UWBC (SRXXX) and creates required client thread for |
| * operation. |
| * After open is complete, status is informed to libuwb-uci |
| * through callback function. |
| * |
| * Returns This function return UWBSTATUS_SUCCES (0) in case of success |
| * In case of failure returns other failure value. |
| * |
| ******************************************************************************/ |
| tHAL_UWB_STATUS phNxpUciHal_open(uwb_stack_callback_t* p_cback, uwb_stack_data_callback_t* p_data_cback) |
| { |
| static const char uwb_dev_node[256] = "/dev/srxxx"; |
| tHAL_UWB_STATUS wConfigStatus = UWBSTATUS_SUCCESS; |
| |
| if (nxpucihal_ctrl.halStatus == HAL_STATUS_OPEN) { |
| NXPLOG_UCIHAL_E("phNxpUciHal_open already open"); |
| return UWBSTATUS_SUCCESS; |
| } |
| |
| NxpConfig_Init(); |
| |
| /* initialize trace level */ |
| phNxpLog_InitializeLogLevel(); |
| |
| /*Create the timer for extns write response*/ |
| timeoutTimerId = phOsalUwb_Timer_Create(); |
| |
| if (!phNxpUciHal_init_monitor()) { |
| NXPLOG_UCIHAL_E("Init monitor failed"); |
| return UWBSTATUS_FAILED; |
| } |
| |
| CONCURRENCY_LOCK(); |
| |
| NXPLOG_UCIHAL_E("Assigning the default helios Node: %s", uwb_dev_node); |
| /* By default HAL status is HAL_STATUS_OPEN */ |
| nxpucihal_ctrl.halStatus = HAL_STATUS_OPEN; |
| |
| nxpucihal_ctrl.p_uwb_stack_cback = p_cback; |
| nxpucihal_ctrl.p_uwb_stack_data_cback = p_data_cback; |
| nxpucihal_ctrl.fw_dwnld_mode = false; |
| |
| /* Configure hardware link */ |
| nxpucihal_ctrl.gDrvCfg.pClientMq = std::make_shared<MessageQueue<phLibUwb_Message>>("Client"); |
| nxpucihal_ctrl.gDrvCfg.nLinkType = ENUM_LINK_TYPE_SPI; |
| |
| /* Initialize TML layer */ |
| wConfigStatus = phTmlUwb_Init(uwb_dev_node, nxpucihal_ctrl.gDrvCfg.pClientMq); |
| if (wConfigStatus != UWBSTATUS_SUCCESS) { |
| NXPLOG_UCIHAL_E("phTmlUwb_Init Failed"); |
| goto clean_and_return; |
| } |
| |
| /* Create the client thread */ |
| nxpucihal_ctrl.client_thread = |
| std::thread{ &phNxpUciHal_client_thread, &nxpucihal_ctrl }; |
| |
| nxpucihal_ctrl.halStatus = HAL_STATUS_OPEN; |
| |
| CONCURRENCY_UNLOCK(); |
| |
| // Per-chip (SR1XX or SR200) implementation |
| nxpucihal_ctrl.uwb_chip = GetUwbChip(); |
| |
| /* Call open complete */ |
| phTmlUwb_DeferredCall(std::make_shared<phLibUwb_Message>(UCI_HAL_OPEN_CPLT_MSG)); |
| |
| return UWBSTATUS_SUCCESS; |
| |
| clean_and_return: |
| CONCURRENCY_UNLOCK(); |
| |
| /* Report error status */ |
| (*nxpucihal_ctrl.p_uwb_stack_cback)(HAL_UWB_OPEN_CPLT_EVT, HAL_UWB_ERROR_EVT); |
| |
| nxpucihal_ctrl.p_uwb_stack_cback = NULL; |
| nxpucihal_ctrl.p_uwb_stack_data_cback = NULL; |
| phNxpUciHal_cleanup_monitor(); |
| nxpucihal_ctrl.halStatus = HAL_STATUS_CLOSE; |
| return wConfigStatus; |
| } |
| |
| /****************************************************************************** |
| * Function phNxpUciHal_write |
| * |
| * Description This function write the data to UWBC through physical |
| * interface (e.g. SPI) using the driver interface. |
| * Before sending the data to UWBC, phNxpUciHal_write_ext |
| * is called to check if there is any extension processing |
| * is required for the UCI packet being sent out. |
| * |
| * Returns It returns number of bytes successfully written to UWBC. |
| * |
| ******************************************************************************/ |
| int32_t phNxpUciHal_write(size_t data_len, const uint8_t* p_data) { |
| if (nxpucihal_ctrl.halStatus != HAL_STATUS_OPEN) { |
| return UWBSTATUS_FAILED; |
| } |
| SessionTrack_keepAlive(); |
| |
| CONCURRENCY_LOCK(); |
| auto status = phNxpUciHal_process_ext_cmd_rsp(data_len, p_data); |
| CONCURRENCY_UNLOCK(); |
| |
| /* No data written */ |
| return (status == UWBSTATUS_SUCCESS) ? data_len : 0; |
| } |
| |
| /****************************************************************************** |
| * Function phNxpUciHal_write_unlocked |
| * |
| * Description This is the actual function which is being called by |
| * phNxpUciHal_write. This function writes the data to UWBC. |
| * It waits till write callback provide the result of write |
| * process. |
| * Using write buffer nxpucihal_ctrl.p_cmd_data. |
| * |
| * Returns Status code. |
| * |
| ******************************************************************************/ |
| tHAL_UWB_STATUS phNxpUciHal_write_unlocked() { |
| tHAL_UWB_STATUS status; |
| uint16_t data_len = nxpucihal_ctrl.cmd_len; |
| uint8_t* p_data = nxpucihal_ctrl.p_cmd_data; |
| |
| if ((data_len > UCI_MAX_DATA_LEN) || (data_len < UCI_PKT_HDR_LEN)) { |
| NXPLOG_UCIHAL_E("Invalid data_len"); |
| return UWBSTATUS_INVALID_PARAMETER; |
| } |
| |
| /* Create the local semaphore */ |
| UciHalSemaphore cb_data; |
| |
| status = phTmlUwb_Write(p_data, data_len, |
| (pphTmlUwb_TransactCompletionCb_t)&phNxpUciHal_write_complete, |
| (void*)&cb_data); |
| |
| if (status != UWBSTATUS_PENDING) { |
| return UWBSTATUS_FAILED; |
| } |
| |
| /* Wait for callback response */ |
| if (cb_data.wait_timeout_msec(HAL_WRITE_TIMEOUT_MS)) { |
| NXPLOG_UCIHAL_E("write_unlocked semaphore error"); |
| return UWBSTATUS_FAILED; |
| } |
| |
| return UWBSTATUS_SUCCESS; |
| } |
| |
| /****************************************************************************** |
| * Function phNxpUciHal_write_complete |
| * |
| * Description This function handles write callback. |
| * |
| * Returns void. |
| * |
| ******************************************************************************/ |
| static void phNxpUciHal_write_complete(void* pContext, |
| phTmlUwb_TransactInfo_t* pInfo) { |
| UciHalSemaphore* p_cb_data = (UciHalSemaphore*)pContext; |
| |
| if (pInfo->wStatus == UWBSTATUS_SUCCESS) { |
| NXPLOG_UCIHAL_V("write successful status = 0x%x", pInfo->wStatus); |
| } else { |
| NXPLOG_UCIHAL_E("write error status = 0x%x", pInfo->wStatus); |
| } |
| p_cb_data->post(pInfo->wStatus); |
| } |
| |
| static void handle_rx_packet(uint8_t *buffer, size_t length) |
| { |
| phNxpUciHal_print_packet(NXP_TML_UCI_RSP_NTF_UWBS_2_AP, buffer, length); |
| |
| uint8_t mt = ((buffer[0]) & UCI_MT_MASK) >> UCI_MT_SHIFT; |
| uint8_t gid = buffer[0] & UCI_GID_MASK; |
| uint8_t oid = buffer[1] & UCI_OID_MASK; |
| uint8_t pbf = (buffer[0] & UCI_PBF_MASK) >> UCI_PBF_SHIFT; |
| |
| nxpucihal_ctrl.isSkipPacket = 0; |
| |
| if (phNxpUciHal_rx_handler_check(length, buffer)) { |
| nxpucihal_ctrl.isSkipPacket = true; |
| } |
| |
| // mapping device caps according to Fira 2.0 |
| if (mt == UCI_MT_RSP && gid == UCI_GID_CORE && oid == UCI_MSG_CORE_GET_CAPS_INFO) { |
| if (phNxpUciHal_handle_get_caps_info(length, buffer)) { |
| return; |
| } |
| } |
| |
| /* DBG packets not yet supported, just ignore them silently */ |
| if ((mt == UCI_MT_NTF) && (gid == UCI_GID_INTERNAL) && |
| (oid == UCI_EXT_PARAM_DBG_RFRAME_LOG_NTF)) { |
| return; |
| } |
| |
| if (!pbf && mt == UCI_MT_NTF && gid == UCI_GID_CORE && oid == UCI_MSG_CORE_GENERIC_ERROR_NTF) { |
| uint8_t status_code = buffer[UCI_RESPONSE_STATUS_OFFSET]; |
| |
| if (status_code == UCI_STATUS_COMMAND_RETRY || |
| status_code == UCI_STATUS_SYNTAX_ERROR) { |
| // Handle retransmissions |
| // TODO: Do not retransmit it when !nxpucihal_ctrl.hal_ext_enabled, |
| // Upper layer should take care of it. |
| nxpucihal_ctrl.isSkipPacket = 1; |
| nxpucihal_ctrl.cmdrsp.WakeupError(UWBSTATUS_COMMAND_RETRANSMIT); |
| } else if (status_code == UCI_STATUS_BUFFER_UNDERFLOW) { |
| if (nxpucihal_ctrl.hal_ext_enabled) { |
| nxpucihal_ctrl.isSkipPacket = 1; |
| nxpucihal_ctrl.cmdrsp.WakeupError(UWBSTATUS_COMMAND_RETRANSMIT); |
| } else { |
| // uci to handle retransmission |
| buffer[UCI_RESPONSE_STATUS_OFFSET] = UCI_STATUS_COMMAND_RETRY; |
| // TODO: Why this should be treated as fail? once we already patched |
| // the status code here. Write operation should be treated as success. |
| nxpucihal_ctrl.cmdrsp.WakeupError(UWBSTATUS_FAILED); |
| } |
| } else { |
| // TODO: Why should we wake up the user thread here? |
| nxpucihal_ctrl.cmdrsp.WakeupError(UWBSTATUS_FAILED); |
| } |
| } |
| |
| // Check status code only for extension commands |
| if (mt == UCI_MT_RSP) { |
| if (nxpucihal_ctrl.hal_ext_enabled) { |
| nxpucihal_ctrl.isSkipPacket = 1; |
| |
| if (pbf) { |
| /* XXX: fix the whole logic if this really happens */ |
| NXPLOG_UCIHAL_E("FIXME: Fragmented packets received while processing internal commands!"); |
| } |
| |
| uint8_t status_code = (length > UCI_RESPONSE_STATUS_OFFSET) ? |
| buffer[UCI_RESPONSE_STATUS_OFFSET] : UCI_STATUS_UNKNOWN; |
| |
| if (status_code == UCI_STATUS_OK) { |
| nxpucihal_ctrl.cmdrsp.Wakeup(gid, oid); |
| } else if ((gid == UCI_GID_CORE) && (oid == UCI_MSG_CORE_SET_CONFIG)){ |
| /* check if any configurations are not supported then ignore the |
| * UWBSTATUS_FEATURE_NOT_SUPPORTED status code*/ |
| uint8_t status = phNxpUciHal_process_ext_rsp(length, buffer); |
| if (status == UWBSTATUS_SUCCESS) { |
| nxpucihal_ctrl.cmdrsp.Wakeup(gid, oid); |
| } else { |
| nxpucihal_ctrl.cmdrsp.WakeupError(status); |
| } |
| } else { |
| NXPLOG_UCIHAL_E("Got error status code(0x%x) from internal command.", status_code); |
| usleep(1); // XXX: not sure if it's really needed |
| nxpucihal_ctrl.cmdrsp.WakeupError(UWBSTATUS_FAILED); |
| } |
| } else { |
| nxpucihal_ctrl.cmdrsp.Wakeup(gid, oid); |
| } |
| } |
| |
| if (!nxpucihal_ctrl.isSkipPacket) { |
| /* Read successful, send the event to higher layer */ |
| if ((nxpucihal_ctrl.p_uwb_stack_data_cback != NULL) && (length <= UCI_MAX_PAYLOAD_LEN)) { |
| (*nxpucihal_ctrl.p_uwb_stack_data_cback)(length, buffer); |
| } |
| } |
| |
| /* Disable junk data check for each UCI packet*/ |
| if(nxpucihal_ctrl.fw_dwnld_mode) { |
| if((gid == UCI_GID_CORE) && (oid == UCI_MSG_CORE_DEVICE_STATUS_NTF)){ |
| nxpucihal_ctrl.fw_dwnld_mode = false; |
| } |
| } |
| } |
| |
| /****************************************************************************** |
| * Function phNxpUciHal_read_complete |
| * |
| * Description This function is called whenever there is an UCI packet |
| * received from UWBC. It could be RSP or NTF packet. This |
| * function provide the received UCI packet to libuwb-uci |
| * using data callback of libuwb-uci. |
| * There is a pending read called from each |
| * phNxpUciHal_read_complete so each a packet received from |
| * UWBC can be provide to libuwb-uci. |
| * |
| * Returns void. |
| * |
| ******************************************************************************/ |
| void phNxpUciHal_read_complete(void* pContext, phTmlUwb_TransactInfo_t* pInfo) |
| { |
| UNUSED(pContext); |
| |
| if (pInfo->wStatus != UWBSTATUS_SUCCESS) { |
| NXPLOG_UCIHAL_E("read error status = 0x%x", pInfo->wStatus); |
| return; |
| } |
| |
| NXPLOG_UCIHAL_V("read successful status = 0x%x , total len = 0x%x", |
| pInfo->wStatus, pInfo->wLength); |
| |
| for (int32_t index = 0; index < pInfo->wLength; ) |
| { |
| uint8_t extBitSet = (pInfo->pBuff[index + EXTND_LEN_INDICATOR_OFFSET] & EXTND_LEN_INDICATOR_OFFSET_MASK); |
| int32_t length = pInfo->pBuff[index + NORMAL_MODE_LENGTH_OFFSET]; |
| if (extBitSet || ((pInfo->pBuff[index] & UCI_MT_MASK) == 0x00)) { |
| length = (length << EXTENDED_MODE_LEN_SHIFT) | pInfo->pBuff[index + EXTENDED_MODE_LEN_OFFSET] ; |
| } |
| length += UCI_MSG_HDR_SIZE; |
| |
| if ((index + length) > pInfo->wLength) { |
| NXPLOG_UCIHAL_E("RX Packet misaligned! given length=%u, offset=%d, len=%d", |
| pInfo->wLength, index, length); |
| return; |
| } |
| handle_rx_packet(&pInfo->pBuff[index], length); |
| |
| index += length; |
| } //End of loop |
| } |
| |
| /****************************************************************************** |
| * Function phNxpUciHal_close |
| * |
| * Description This function close the UWBC interface and free all |
| * resources.This is called by libuwb-uci on UWB service stop. |
| * |
| * Returns Always return UWBSTATUS_SUCCESS (0). |
| * |
| ******************************************************************************/ |
| tHAL_UWB_STATUS phNxpUciHal_close() { |
| tHAL_UWB_STATUS status; |
| if (nxpucihal_ctrl.halStatus == HAL_STATUS_CLOSE) { |
| NXPLOG_UCIHAL_E("phNxpUciHal_close is already closed, ignoring close"); |
| return UWBSTATUS_FAILED; |
| } |
| |
| uwb_device_initialized = false; |
| |
| CONCURRENCY_LOCK(); |
| |
| SessionTrack_deinit(); |
| |
| NXPLOG_UCIHAL_D("Terminating phNxpUciHal client thread..."); |
| phTmlUwb_DeferredCall(std::make_shared<phLibUwb_Message>(UCI_HAL_CLOSE_CPLT_MSG)); |
| nxpucihal_ctrl.client_thread.join(); |
| |
| status = phTmlUwb_Shutdown(); |
| |
| phNxpUciHal_rx_handler_destroy(); |
| |
| nxpucihal_ctrl.halStatus = HAL_STATUS_CLOSE; |
| |
| CONCURRENCY_UNLOCK(); |
| |
| nxpucihal_ctrl.uwb_chip.reset(); |
| |
| phOsalUwb_Timer_Cleanup(); |
| |
| phNxpUciHal_cleanup_monitor(); |
| |
| NxpConfig_Deinit(); |
| |
| NXPLOG_UCIHAL_D("phNxpUciHal_close completed"); |
| |
| /* Return success always */ |
| return UWBSTATUS_SUCCESS; |
| } |
| |
| /****************************************************************************** |
| * Function parseAntennaConfig |
| * |
| * Description This function parse the antenna config and update required |
| * params |
| * |
| * Returns void |
| * |
| ******************************************************************************/ |
| static void parseAntennaConfig(const char *configName) |
| { |
| std::array<uint8_t, NXP_MAX_CONFIG_STRING_LEN> buffer; |
| long retlen = 0; |
| int gotConfig = NxpConfig_GetByteArray(configName, buffer.data(), buffer.size(), &retlen); |
| if (gotConfig) { |
| if (retlen <= UCI_MSG_HDR_SIZE) { |
| NXPLOG_UCIHAL_E("parseAntennaConfig: %s is too short. Aborting.", configName); |
| return; |
| } |
| } |
| else |
| { |
| NXPLOG_UCIHAL_E("parseAntennaConfig: Failed to get '%s'. Aborting.", configName); |
| return; |
| } |
| |
| const uint16_t dataLength = retlen; |
| const uint8_t *data = buffer.data(); |
| |
| uint8_t index = 1; // Excluding number of params |
| uint8_t tagId, subTagId; |
| int length; |
| while (index < dataLength) { |
| tagId = data[index++]; |
| subTagId = data[index++]; |
| length = data[index++]; |
| if ((ANTENNA_RX_PAIR_DEFINE_TAG_ID == tagId) && |
| (ANTENNA_RX_PAIR_DEFINE_SUB_TAG_ID == subTagId)) { |
| nxpucihal_ctrl.numberOfAntennaPairs = data[index]; |
| NXPLOG_UCIHAL_D("numberOfAntennaPairs:%d", nxpucihal_ctrl.numberOfAntennaPairs); |
| break; |
| } else { |
| index = index + length; |
| } |
| } |
| } |
| |
| /****************************************************************************** |
| * Function phNxpUciHal_applyVendorConfig |
| * |
| * Description This function applies the vendor config from config file |
| * |
| * Returns status |
| * |
| ******************************************************************************/ |
| tHAL_UWB_STATUS phNxpUciHal_applyVendorConfig() |
| { |
| std::vector<const char *> vendorParamNames; |
| std::array<uint8_t, NXP_MAX_CONFIG_STRING_LEN> buffer; |
| long retlen = 0; |
| tHAL_UWB_STATUS status = UWBSTATUS_FAILED; |
| |
| // Base parameter names |
| if (nxpucihal_ctrl.fw_boot_mode == USER_FW_BOOT_MODE) { |
| vendorParamNames.push_back(NAME_UWB_USER_FW_BOOT_MODE_CONFIG); |
| } |
| vendorParamNames.push_back(NAME_NXP_UWB_EXTENDED_NTF_CONFIG); |
| |
| // Chip parameter names |
| const char *per_chip_param = NAME_UWB_CORE_EXT_DEVICE_DEFAULT_CONFIG; |
| if (nxpucihal_ctrl.device_type == DEVICE_TYPE_SR1xxT) { |
| per_chip_param = NAME_UWB_CORE_EXT_DEVICE_SR1XX_T_CONFIG; |
| } else if (nxpucihal_ctrl.device_type == DEVICE_TYPE_SR1xxS) { |
| per_chip_param = NAME_UWB_CORE_EXT_DEVICE_SR1XX_S_CONFIG; |
| } |
| |
| if (NxpConfig_GetByteArray(per_chip_param, buffer.data(), buffer.size(), |
| &retlen)) { |
| if (retlen > 0 && retlen < UCI_MAX_DATA_LEN) { |
| NXPLOG_UCIHAL_D("VendorConfig: apply %s", per_chip_param); |
| status = phNxpUciHal_sendCoreConfig(buffer.data(), retlen); |
| if (status != UWBSTATUS_SUCCESS) { |
| NXPLOG_UCIHAL_E("VendorConfig: failed to apply %s", per_chip_param); |
| return status; |
| } |
| } |
| } |
| |
| // Parse Antenna config from chip-parameter |
| parseAntennaConfig(per_chip_param); |
| |
| // Extra parameter names, XTAL, NXP_CORE_CONF_BLK[1..10] |
| vendorParamNames.push_back(NAME_NXP_UWB_XTAL_38MHZ_CONFIG); |
| vendorParamNames.push_back(NAME_NXP_CORE_CONF_BLK "1"); |
| vendorParamNames.push_back(NAME_NXP_CORE_CONF_BLK "2"); |
| vendorParamNames.push_back(NAME_NXP_CORE_CONF_BLK "3"); |
| vendorParamNames.push_back(NAME_NXP_CORE_CONF_BLK "4"); |
| vendorParamNames.push_back(NAME_NXP_CORE_CONF_BLK "5"); |
| vendorParamNames.push_back(NAME_NXP_CORE_CONF_BLK "6"); |
| vendorParamNames.push_back(NAME_NXP_CORE_CONF_BLK "7"); |
| vendorParamNames.push_back(NAME_NXP_CORE_CONF_BLK "8"); |
| vendorParamNames.push_back(NAME_NXP_CORE_CONF_BLK "9"); |
| vendorParamNames.push_back(NAME_NXP_CORE_CONF_BLK "10"); |
| |
| // Execute |
| for (const auto paramName : vendorParamNames) { |
| if (NxpConfig_GetByteArray(paramName, buffer.data(), buffer.size(), &retlen)) { |
| if (retlen > 0 && retlen < UCI_MAX_DATA_LEN) { |
| NXPLOG_UCIHAL_D("VendorConfig: apply %s", paramName); |
| status = phNxpUciHal_send_ext_cmd(retlen, buffer.data()); |
| if (status != UWBSTATUS_SUCCESS) { |
| NXPLOG_UCIHAL_E("VendorConfig: failed to apply %s", paramName); |
| return status; |
| } |
| } |
| } |
| } |
| |
| // Low Power Mode |
| // TODO: remove this out, this can be move to Chip parameter names |
| uint8_t lowPowerMode = 0; |
| if (NxpConfig_GetNum(NAME_NXP_UWB_LOW_POWER_MODE, &lowPowerMode, sizeof(lowPowerMode))) { |
| NXPLOG_UCIHAL_D("VendorConfig: apply %s", NAME_NXP_UWB_LOW_POWER_MODE); |
| |
| // Core set config packet: GID=0x00 OID=0x04 |
| const std::vector<uint8_t> packet( |
| {((UCI_MT_CMD << UCI_MT_SHIFT) | UCI_GID_CORE), UCI_MSG_CORE_SET_CONFIG, |
| 0x00, 0x04, 0x01, LOW_POWER_MODE_TAG_ID, LOW_POWER_MODE_LENGTH, |
| lowPowerMode}); |
| |
| if (phNxpUciHal_send_ext_cmd(packet.size(), packet.data()) != UWBSTATUS_SUCCESS) { |
| NXPLOG_UCIHAL_E("VendorConfig: failed to apply NAME_NXP_UWB_LOW_POWER_MODE"); |
| } |
| } |
| |
| return UWBSTATUS_SUCCESS; |
| } |
| |
| /****************************************************************************** |
| * Function phNxpUciHal_uwb_reset |
| * |
| * Description This function will send UWB reset command |
| * |
| * Returns status |
| * |
| ******************************************************************************/ |
| tHAL_UWB_STATUS phNxpUciHal_uwb_reset() { |
| tHAL_UWB_STATUS status; |
| uint8_t buffer[] = {0x20, 0x00, 0x00, 0x01, 0x00}; |
| status = phNxpUciHal_send_ext_cmd(sizeof(buffer), buffer); |
| if(status != UWBSTATUS_SUCCESS) { |
| return status; |
| } |
| return UWBSTATUS_SUCCESS; |
| } |
| |
| static bool cacheDevInfoRsp() |
| { |
| auto dev_info_cb = [](size_t packet_len, const uint8_t *packet) mutable { |
| if (packet_len < 5 || packet[UCI_RESPONSE_STATUS_OFFSET] != UWBSTATUS_SUCCESS) { |
| NXPLOG_UCIHAL_E("Failed to get valid CORE_DEVICE_INFO_RSP"); |
| return; |
| } |
| if (packet_len > sizeof(nxpucihal_ctrl.dev_info_resp)) { |
| NXPLOG_UCIHAL_E("FIXME: CORE_DEVICE_INFO_RSP buffer overflow!"); |
| return; |
| } |
| |
| // FIRA UCIv2.0 packet size = 14 |
| // [13] = Vendor Specific Info Length |
| constexpr uint8_t firaDevInfoRspSize = 14; |
| constexpr uint8_t firaDevInfoVendorLenOffset = 13; |
| |
| if (packet_len < firaDevInfoRspSize) { |
| NXPLOG_UCIHAL_E("DEVICE_INFO_RSP packet size mismatched."); |
| return; |
| } |
| |
| const uint8_t vendorSpecificLen = packet[firaDevInfoVendorLenOffset]; |
| if (packet_len != (firaDevInfoRspSize + vendorSpecificLen)) { |
| NXPLOG_UCIHAL_E("DEVICE_INFO_RSP packet size mismatched."); |
| } |
| |
| for (uint8_t i = firaDevInfoRspSize; (i + 2) <= packet_len; ) { |
| uint8_t paramId = packet[i++]; |
| uint8_t length = packet[i++]; |
| |
| if (i + length > packet_len) |
| break; |
| |
| if (paramId == DEVICE_NAME_PARAM_ID && length >= 6) { |
| nxpucihal_ctrl.device_type = nxpucihal_ctrl.uwb_chip->get_device_type(&packet[i], length); |
| } else if (paramId == FW_VERSION_PARAM_ID && length >= 3) { |
| nxpucihal_ctrl.fw_version.major_version = packet[i]; |
| nxpucihal_ctrl.fw_version.minor_version = packet[i + 1]; |
| nxpucihal_ctrl.fw_version.rc_version = packet[i + 2]; |
| } else if (paramId == FW_BOOT_MODE_PARAM_ID && length >= 1) { |
| nxpucihal_ctrl.fw_boot_mode = packet[i]; |
| } |
| i += length; |
| } |
| memcpy(nxpucihal_ctrl.dev_info_resp, packet, packet_len); |
| nxpucihal_ctrl.isDevInfoCached = true; |
| NXPLOG_UCIHAL_D("Device Info cached."); |
| }; |
| |
| nxpucihal_ctrl.isDevInfoCached = false; |
| UciHalRxHandler devInfoRspHandler(UCI_MT_RSP, UCI_GID_CORE, UCI_MSG_CORE_DEVICE_INFO, true, dev_info_cb); |
| |
| const uint8_t CoreGetDevInfoCmd[] = {(UCI_MT_CMD << UCI_MT_SHIFT) | UCI_GID_CORE, UCI_MSG_CORE_DEVICE_INFO, 0, 0}; |
| tHAL_UWB_STATUS status = phNxpUciHal_send_ext_cmd(sizeof(CoreGetDevInfoCmd), CoreGetDevInfoCmd); |
| if (status != UWBSTATUS_SUCCESS) { |
| return false; |
| } |
| return true; |
| } |
| |
| /****************************************************************************** |
| * Function phNxpUciHal_init_hw |
| * |
| * Description Init the chip. |
| * |
| * Returns status |
| * |
| ******************************************************************************/ |
| tHAL_UWB_STATUS phNxpUciHal_init_hw() |
| { |
| tHAL_UWB_STATUS status; |
| |
| if (nxpucihal_ctrl.halStatus != HAL_STATUS_OPEN) { |
| NXPLOG_UCIHAL_E("HAL not initialized"); |
| return UWBSTATUS_FAILED; |
| } |
| |
| uwb_device_initialized = false; |
| |
| // Device Status Notification |
| UciHalSemaphore devStatusNtfWait; |
| uint8_t dev_status = UWB_DEVICE_ERROR; |
| auto dev_status_ntf_cb = [&dev_status, |
| &devStatusNtfWait](size_t packet_len, |
| const uint8_t *packet) mutable { |
| if (packet_len >= 5) { |
| dev_status = packet[UCI_RESPONSE_STATUS_OFFSET]; |
| devStatusNtfWait.post(); |
| } |
| }; |
| UciHalRxHandler devStatusNtfHandler(UCI_MT_NTF, UCI_GID_CORE, UCI_MSG_CORE_DEVICE_STATUS_NTF, |
| true, dev_status_ntf_cb); |
| |
| // FW download and enter UCI operating mode |
| status = nxpucihal_ctrl.uwb_chip->chip_init(); |
| if (status != UWBSTATUS_SUCCESS) { |
| return status; |
| } |
| |
| // Initiate UCI packet read |
| status = phTmlUwb_StartRead(&phNxpUciHal_read_complete, NULL); |
| if (status != UWBSTATUS_SUCCESS) { |
| NXPLOG_UCIHAL_E("read status error status = %x", status); |
| return status; |
| } |
| |
| // Wait for the first Device Status Notification |
| devStatusNtfWait.wait_timeout_msec(3000); |
| if(dev_status != UWB_DEVICE_INIT && dev_status != UWB_DEVICE_READY) { |
| NXPLOG_UCIHAL_E("First Device Status NTF was not received or it's invalid state. 0x%x", dev_status); |
| return UWBSTATUS_FAILED; |
| } |
| |
| // Set board-config and wait for Device Status Notification |
| status = phNxpUciHal_set_board_config(); |
| if (status != UWBSTATUS_SUCCESS) { |
| NXPLOG_UCIHAL_E("%s: Set Board Config Failed", __func__); |
| return status; |
| } |
| devStatusNtfWait.wait_timeout_msec(3000); |
| if (dev_status != UWB_DEVICE_READY) { |
| NXPLOG_UCIHAL_E("Cannot receive UWB_DEVICE_READY"); |
| return UWBSTATUS_FAILED; |
| } |
| |
| // Send SW reset and wait for Device Status Notification |
| dev_status = UWB_DEVICE_ERROR; |
| status = phNxpUciHal_uwb_reset(); |
| if (status != UWBSTATUS_SUCCESS) { |
| NXPLOG_UCIHAL_E("%s: device reset Failed", __func__); |
| return status; |
| } |
| devStatusNtfWait.wait_timeout_msec(3000); |
| if(dev_status != UWB_DEVICE_READY) { |
| NXPLOG_UCIHAL_E("UWB_DEVICE_READY not received uwbc_device_state = %x", dev_status); |
| return UWBSTATUS_FAILED; |
| } |
| |
| // Cache CORE_GET_DEVICE_INFO |
| cacheDevInfoRsp(); |
| |
| status = nxpucihal_ctrl.uwb_chip->core_init(); |
| if (status != UWBSTATUS_SUCCESS) { |
| return status; |
| } |
| |
| status = phNxpUciHal_applyVendorConfig(); |
| if (status != UWBSTATUS_SUCCESS) { |
| NXPLOG_UCIHAL_E("%s: Apply vendor Config Failed", __func__); |
| return status; |
| } |
| phNxpUciHal_extcal_handle_coreinit(); |
| |
| uwb_device_initialized = true; |
| phNxpUciHal_getVersionInfo(); |
| |
| return UWBSTATUS_SUCCESS; |
| } |
| |
| /****************************************************************************** |
| * Function phNxpUciHal_coreInitialization |
| * |
| * Description This function performs core initialization |
| * |
| * Returns status |
| * |
| ******************************************************************************/ |
| tHAL_UWB_STATUS phNxpUciHal_coreInitialization() |
| { |
| tHAL_UWB_STATUS status = phNxpUciHal_init_hw(); |
| if (status != UWBSTATUS_SUCCESS) { |
| phTmlUwb_DeferredCall(std::make_shared<phLibUwb_Message>(UCI_HAL_ERROR_MSG)); |
| return status; |
| } |
| |
| SessionTrack_init(); |
| |
| // report to upper-layer |
| phTmlUwb_DeferredCall(std::make_shared<phLibUwb_Message>(UCI_HAL_INIT_CPLT_MSG)); |
| |
| if (nxpucihal_ctrl.p_uwb_stack_data_cback != NULL) { |
| uint8_t dev_ready_ntf[] = {0x60, 0x01, 0x00, 0x01, 0x01}; |
| (*nxpucihal_ctrl.p_uwb_stack_data_cback)((sizeof(dev_ready_ntf)/sizeof(uint8_t)), dev_ready_ntf); |
| } |
| |
| return UWBSTATUS_SUCCESS; |
| } |
| |
| /****************************************************************************** |
| * Function phNxpUciHal_sessionInitialization |
| * |
| * Description This function performs session initialization |
| * |
| * Returns status |
| * |
| ******************************************************************************/ |
| tHAL_UWB_STATUS phNxpUciHal_sessionInitialization(uint32_t sessionId) { |
| NXPLOG_UCIHAL_D(" %s: Enter", __func__); |
| tHAL_UWB_STATUS status = UWBSTATUS_SUCCESS; |
| |
| if (nxpucihal_ctrl.halStatus != HAL_STATUS_OPEN) { |
| NXPLOG_UCIHAL_E("HAL not initialized"); |
| return UWBSTATUS_FAILED; |
| } |
| return status; |
| } |
| |
| /****************************************************************************** |
| * Function phNxpUciHal_GetMwVersion |
| * |
| * Description This function gets the middleware version |
| * |
| * Returns phNxpUciHal_MW_Version_t |
| * |
| ******************************************************************************/ |
| phNxpUciHal_MW_Version_t phNxpUciHal_GetMwVersion() { |
| phNxpUciHal_MW_Version_t mwVer; |
| mwVer.validation = NXP_CHIP_SR100; |
| mwVer.android_version = NXP_ANDROID_VERSION; |
| NXPLOG_UCIHAL_D("0x%x:UWB MW Major Version:", UWB_NXP_MW_VERSION_MAJ); |
| NXPLOG_UCIHAL_D("0x%x:UWB MW Minor Version:", UWB_NXP_MW_VERSION_MIN); |
| mwVer.major_version = UWB_NXP_MW_VERSION_MAJ; |
| mwVer.minor_version = UWB_NXP_MW_VERSION_MIN; |
| mwVer.rc_version = UWB_NXP_ANDROID_MW_RC_VERSION; |
| mwVer.mw_drop = UWB_NXP_ANDROID_MW_DROP_VERSION; |
| return mwVer; |
| } |
| |
| /****************************************************************************** |
| * Function phNxpUciHal_getVersionInfo |
| * |
| * Description This function request for version information |
| * |
| * Returns void |
| * |
| ******************************************************************************/ |
| void phNxpUciHal_getVersionInfo() { |
| phNxpUciHal_MW_Version_t mwVersion = phNxpUciHal_GetMwVersion(); |
| if (mwVersion.rc_version) { /* for RC release*/ |
| ALOGI("MW Version: UWB_SW_Android_U_HKY_D%02x.%02x_RC%02x", |
| mwVersion.major_version, mwVersion.minor_version, |
| mwVersion.rc_version); |
| } else if (mwVersion.mw_drop) { /* For Drops */ |
| ALOGI("MW Version: UWB_SW_Android_U_HKY_D%02x.%02x_DROP%02x", |
| mwVersion.major_version, mwVersion.minor_version, mwVersion.mw_drop); |
| } else { /* for Major Releases*/ |
| ALOGI("MW Version: UWB_SW_Android_U_HKY_D%02x.%02x", |
| mwVersion.major_version, mwVersion.minor_version); |
| } |
| |
| if (nxpucihal_ctrl.fw_version.rc_version) { |
| ALOGI("FW Version: %02x.%02x_RC%02x", nxpucihal_ctrl.fw_version.major_version, |
| nxpucihal_ctrl.fw_version.minor_version, nxpucihal_ctrl.fw_version.rc_version); |
| } else { |
| ALOGI("FW Version: %02x.%02x", nxpucihal_ctrl.fw_version.major_version, |
| nxpucihal_ctrl.fw_version.minor_version); |
| } |
| } |
| |
| /****************************************************************************** |
| * Function phNxpUciHal_sendCoreConfig |
| * |
| * Description This function send set core config command in chunks when |
| * config size greater than 255 bytes. |
| * |
| * Returns status |
| * |
| ******************************************************************************/ |
| tHAL_UWB_STATUS phNxpUciHal_sendCoreConfig(const uint8_t *p_cmd, |
| long buffer_size) { |
| std::array<uint8_t, NXP_MAX_CONFIG_STRING_LEN> payload_data; |
| tHAL_UWB_STATUS status = UWBSTATUS_FAILED; |
| uint16_t i = 0; |
| |
| while (buffer_size > 0) { |
| uint16_t chunk_size = (buffer_size <= UCI_MAX_CONFIG_PAYLOAD_LEN) |
| ? buffer_size |
| : UCI_MAX_CONFIG_PAYLOAD_LEN; |
| |
| payload_data[0] = (buffer_size <= UCI_MAX_CONFIG_PAYLOAD_LEN) ? 0x20 : 0x30; |
| payload_data[1] = 0x04; |
| payload_data[2] = 0x00; |
| payload_data[3] = chunk_size; |
| |
| std::memcpy(&payload_data[UCI_PKT_HDR_LEN], &p_cmd[i], chunk_size); |
| |
| status = phNxpUciHal_send_ext_cmd(chunk_size + UCI_PKT_HDR_LEN, |
| payload_data.data()); |
| |
| i += chunk_size; |
| buffer_size -= chunk_size; |
| } |
| |
| return status; |
| } |
| |
| /******************************************************************************* |
| * Function phNxpUciHal_send_dev_error_status_ntf |
| * |
| * Description send device status notification. Upper layer might restart |
| * HAL service. |
| * |
| * Returns void |
| * |
| ******************************************* ***********************************/ |
| void phNxpUciHal_send_dev_error_status_ntf() |
| { |
| NXPLOG_UCIHAL_D("phNxpUciHal_send_dev_error_status_ntf "); |
| static uint8_t rsp_data[5] = {0x60, 0x01, 0x00, 0x01, 0xFF}; |
| (*nxpucihal_ctrl.p_uwb_stack_data_cback)(sizeof(rsp_data), rsp_data); |
| } |