| /****************************************************************************** |
| * |
| * Copyright (C) 2009-2012 Broadcom Corporation |
| * |
| * 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. |
| * |
| ******************************************************************************/ |
| |
| /****************************************************************************** |
| * |
| * This is the implementation file for the MCAP Control Channel Action |
| * Functions. |
| * |
| ******************************************************************************/ |
| #include <string.h> |
| #include "bt_common.h" |
| #include "bt_target.h" |
| #include "bt_utils.h" |
| #include "btm_api.h" |
| #include "mca_api.h" |
| #include "mca_defs.h" |
| #include "mca_int.h" |
| #include "osi/include/osi.h" |
| |
| #include "btu.h" |
| |
| /***************************************************************************** |
| * constants |
| ****************************************************************************/ |
| /******************************************************************************* |
| * |
| * Function mca_ccb_rsp_tout |
| * |
| * Description This function processes the response timeout. |
| * |
| * Returns void. |
| * |
| ******************************************************************************/ |
| void mca_ccb_rsp_tout(tMCA_CCB* p_ccb, UNUSED_ATTR tMCA_CCB_EVT* p_data) { |
| tMCA_CTRL evt_data; |
| |
| mca_ccb_report_event(p_ccb, MCA_RSP_TOUT_IND_EVT, &evt_data); |
| } |
| |
| /******************************************************************************* |
| * |
| * Function mca_ccb_report_event |
| * |
| * Description This function reports the given event. |
| * |
| * Returns void. |
| * |
| ******************************************************************************/ |
| void mca_ccb_report_event(tMCA_CCB* p_ccb, uint8_t event, tMCA_CTRL* p_data) { |
| if (p_ccb && p_ccb->p_rcb && p_ccb->p_rcb->p_cback) |
| (*p_ccb->p_rcb->p_cback)(mca_rcb_to_handle(p_ccb->p_rcb), |
| mca_ccb_to_hdl(p_ccb), event, p_data); |
| } |
| |
| /******************************************************************************* |
| * |
| * Function mca_ccb_free_msg |
| * |
| * Description This function frees the received message. |
| * |
| * Returns void. |
| * |
| ******************************************************************************/ |
| void mca_ccb_free_msg(UNUSED_ATTR tMCA_CCB* p_ccb, tMCA_CCB_EVT* p_data) { |
| osi_free(p_data); |
| } |
| |
| /******************************************************************************* |
| * |
| * Function mca_ccb_snd_req |
| * |
| * Description This function builds a request and sends it to the peer. |
| * |
| * Returns void. |
| * |
| ******************************************************************************/ |
| void mca_ccb_snd_req(tMCA_CCB* p_ccb, tMCA_CCB_EVT* p_data) { |
| tMCA_CCB_MSG* p_msg = (tMCA_CCB_MSG*)p_data; |
| uint8_t *p, *p_start; |
| bool is_abort = false; |
| tMCA_DCB* p_dcb; |
| |
| MCA_TRACE_DEBUG("mca_ccb_snd_req cong=%d req=%d", p_ccb->cong, |
| p_msg->op_code); |
| /* check for abort request */ |
| if ((p_ccb->status == MCA_CCB_STAT_PENDING) && |
| (p_msg->op_code == MCA_OP_MDL_ABORT_REQ)) { |
| p_dcb = mca_dcb_by_hdl(p_ccb->p_tx_req->dcb_idx); |
| /* the Abort API does not have the associated mdl_id. |
| * Get the mdl_id in dcb to compose the request */ |
| p_msg->mdl_id = p_dcb->mdl_id; |
| mca_dcb_event(p_dcb, MCA_DCB_API_CLOSE_EVT, NULL); |
| osi_free_and_reset((void**)&p_ccb->p_tx_req); |
| p_ccb->status = MCA_CCB_STAT_NORM; |
| is_abort = true; |
| } |
| |
| /* no pending outgoing messages or it's an abort request for a pending data |
| * channel */ |
| if ((!p_ccb->p_tx_req) || is_abort) { |
| p_ccb->p_tx_req = p_msg; |
| if (!p_ccb->cong) { |
| BT_HDR* p_pkt = (BT_HDR*)osi_malloc(MCA_CTRL_MTU); |
| |
| p_pkt->offset = L2CAP_MIN_OFFSET; |
| p = p_start = (uint8_t*)(p_pkt + 1) + L2CAP_MIN_OFFSET; |
| *p++ = p_msg->op_code; |
| UINT16_TO_BE_STREAM(p, p_msg->mdl_id); |
| if (p_msg->op_code == MCA_OP_MDL_CREATE_REQ) { |
| *p++ = p_msg->mdep_id; |
| *p++ = p_msg->param; |
| } |
| p_msg->hdr.layer_specific = true; /* mark this message as sent */ |
| p_pkt->len = p - p_start; |
| L2CA_DataWrite(p_ccb->lcid, p_pkt); |
| period_ms_t interval_ms = p_ccb->p_rcb->reg.rsp_tout * 1000; |
| alarm_set_on_mloop(p_ccb->mca_ccb_timer, interval_ms, |
| mca_ccb_timer_timeout, p_ccb); |
| } |
| /* else the L2CAP channel is congested. keep the message to be sent later */ |
| } else { |
| MCA_TRACE_WARNING("dropping api req"); |
| osi_free(p_data); |
| } |
| } |
| |
| /******************************************************************************* |
| * |
| * Function mca_ccb_snd_rsp |
| * |
| * Description This function builds a response and sends it to |
| * the peer. |
| * |
| * Returns void. |
| * |
| ******************************************************************************/ |
| void mca_ccb_snd_rsp(tMCA_CCB* p_ccb, tMCA_CCB_EVT* p_data) { |
| tMCA_CCB_MSG* p_msg = (tMCA_CCB_MSG*)p_data; |
| uint8_t *p, *p_start; |
| BT_HDR* p_pkt = (BT_HDR*)osi_malloc(MCA_CTRL_MTU); |
| |
| MCA_TRACE_DEBUG("%s cong=%d req=%d", __func__, p_ccb->cong, p_msg->op_code); |
| /* assume that API functions verified the parameters */ |
| |
| p_pkt->offset = L2CAP_MIN_OFFSET; |
| p = p_start = (uint8_t*)(p_pkt + 1) + L2CAP_MIN_OFFSET; |
| *p++ = p_msg->op_code; |
| *p++ = p_msg->rsp_code; |
| UINT16_TO_BE_STREAM(p, p_msg->mdl_id); |
| // Only add extra parameters for MCA_RSP_SUCCESS message |
| if (p_msg->rsp_code == MCA_RSP_SUCCESS) { |
| // Append MDL configuration parameters |
| if (p_msg->op_code == MCA_OP_MDL_CREATE_RSP) { |
| *p++ = p_msg->param; |
| } |
| // Check MDL |
| if (p_msg->op_code == MCA_OP_MDL_CREATE_RSP || |
| p_msg->op_code == MCA_OP_MDL_RECONNECT_RSP) { |
| mca_dcb_by_hdl(p_msg->dcb_idx); |
| BTM_SetSecurityLevel(false, "", BTM_SEC_SERVICE_MCAP_DATA, |
| p_ccb->sec_mask, p_ccb->p_rcb->reg.data_psm, |
| BTM_SEC_PROTO_MCA, p_msg->dcb_idx); |
| p_ccb->status = MCA_CCB_STAT_PENDING; |
| /* set p_tx_req to block API_REQ/API_RSP before DL is up */ |
| osi_free_and_reset((void**)&p_ccb->p_tx_req); |
| p_ccb->p_tx_req = p_ccb->p_rx_msg; |
| p_ccb->p_rx_msg = NULL; |
| p_ccb->p_tx_req->dcb_idx = p_msg->dcb_idx; |
| } |
| } |
| |
| osi_free_and_reset((void**)&p_ccb->p_rx_msg); |
| p_pkt->len = p - p_start; |
| L2CA_DataWrite(p_ccb->lcid, p_pkt); |
| } |
| |
| /******************************************************************************* |
| * |
| * Function mca_ccb_do_disconn |
| * |
| * Description This function closes a control channel. |
| * |
| * Returns void. |
| * |
| ******************************************************************************/ |
| void mca_ccb_do_disconn(tMCA_CCB* p_ccb, UNUSED_ATTR tMCA_CCB_EVT* p_data) { |
| mca_dcb_close_by_mdl_id(p_ccb, MCA_ALL_MDL_ID); |
| L2CA_DisconnectReq(p_ccb->lcid); |
| } |
| |
| /******************************************************************************* |
| * |
| * Function mca_ccb_cong |
| * |
| * Description This function sets the congestion state for the CCB. |
| * |
| * Returns void. |
| * |
| ******************************************************************************/ |
| void mca_ccb_cong(tMCA_CCB* p_ccb, tMCA_CCB_EVT* p_data) { |
| MCA_TRACE_DEBUG("mca_ccb_cong cong=%d/%d", p_ccb->cong, p_data->llcong); |
| p_ccb->cong = p_data->llcong; |
| if (!p_ccb->cong) { |
| /* if there's a held packet, send it now */ |
| if (p_ccb->p_tx_req && !p_ccb->p_tx_req->hdr.layer_specific) { |
| p_data = (tMCA_CCB_EVT*)p_ccb->p_tx_req; |
| p_ccb->p_tx_req = NULL; |
| mca_ccb_snd_req(p_ccb, p_data); |
| } |
| } |
| } |
| |
| /******************************************************************************* |
| * |
| * Function mca_ccb_hdl_req |
| * |
| * Description This function is called when a MCAP request is received from |
| * the peer. It calls the application callback function to |
| * report the event. |
| * |
| * Returns void. |
| * |
| ******************************************************************************/ |
| void mca_ccb_hdl_req(tMCA_CCB* p_ccb, tMCA_CCB_EVT* p_data) { |
| BT_HDR* p_pkt = &p_data->hdr; |
| uint8_t *p, *p_start; |
| tMCA_DCB* p_dcb; |
| tMCA_CTRL evt_data; |
| tMCA_CCB_MSG* p_rx_msg = NULL; |
| uint8_t reject_code = MCA_RSP_NO_RESOURCE; |
| bool send_rsp = false; |
| bool check_req = false; |
| uint8_t reject_opcode; |
| |
| MCA_TRACE_DEBUG("mca_ccb_hdl_req status:%d", p_ccb->status); |
| p_rx_msg = (tMCA_CCB_MSG*)p_pkt; |
| p = (uint8_t*)(p_pkt + 1) + p_pkt->offset; |
| evt_data.hdr.op_code = *p++; |
| BE_STREAM_TO_UINT16(evt_data.hdr.mdl_id, p); |
| reject_opcode = evt_data.hdr.op_code + 1; |
| |
| MCA_TRACE_DEBUG("received mdl id: %d ", evt_data.hdr.mdl_id); |
| if (p_ccb->status == MCA_CCB_STAT_PENDING) { |
| MCA_TRACE_DEBUG("received req inpending state"); |
| /* allow abort in pending state */ |
| if ((p_ccb->status == MCA_CCB_STAT_PENDING) && |
| (evt_data.hdr.op_code == MCA_OP_MDL_ABORT_REQ)) { |
| reject_code = MCA_RSP_SUCCESS; |
| send_rsp = true; |
| /* clear the pending status */ |
| p_ccb->status = MCA_CCB_STAT_NORM; |
| if (p_ccb->p_tx_req && |
| ((p_dcb = mca_dcb_by_hdl(p_ccb->p_tx_req->dcb_idx)) != NULL)) { |
| mca_dcb_dealloc(p_dcb, NULL); |
| osi_free_and_reset((void**)&p_ccb->p_tx_req); |
| } |
| } else |
| reject_code = MCA_RSP_BAD_OP; |
| } else if (p_ccb->p_rx_msg) { |
| MCA_TRACE_DEBUG("still handling prev req"); |
| /* still holding previous message, reject this new one ?? */ |
| |
| } else if (p_ccb->p_tx_req) { |
| MCA_TRACE_DEBUG("still waiting for a response ctrl_vpsm:0x%x", |
| p_ccb->ctrl_vpsm); |
| /* sent a request; waiting for response */ |
| if (p_ccb->ctrl_vpsm == 0) { |
| MCA_TRACE_DEBUG("local is ACP. accept the cmd from INT"); |
| /* local is acceptor, need to handle the request */ |
| check_req = true; |
| reject_code = MCA_RSP_SUCCESS; |
| /* drop the previous request */ |
| if ((p_ccb->p_tx_req->op_code == MCA_OP_MDL_CREATE_REQ) && |
| ((p_dcb = mca_dcb_by_hdl(p_ccb->p_tx_req->dcb_idx)) != NULL)) { |
| mca_dcb_dealloc(p_dcb, NULL); |
| } |
| osi_free_and_reset((void**)&p_ccb->p_tx_req); |
| mca_stop_timer(p_ccb); |
| } else { |
| /* local is initiator, ignore the req */ |
| osi_free(p_pkt); |
| return; |
| } |
| } else if (p_pkt->layer_specific != MCA_RSP_SUCCESS) { |
| reject_code = (uint8_t)p_pkt->layer_specific; |
| if (((evt_data.hdr.op_code >= MCA_NUM_STANDARD_OPCODE) && |
| (evt_data.hdr.op_code < MCA_FIRST_SYNC_OP)) || |
| (evt_data.hdr.op_code > MCA_LAST_SYNC_OP)) { |
| /* invalid op code */ |
| reject_opcode = MCA_OP_ERROR_RSP; |
| evt_data.hdr.mdl_id = 0; |
| } |
| } else { |
| check_req = true; |
| reject_code = MCA_RSP_SUCCESS; |
| } |
| |
| if (check_req) { |
| if (reject_code == MCA_RSP_SUCCESS) { |
| reject_code = MCA_RSP_BAD_MDL; |
| if (MCA_IS_VALID_MDL_ID(evt_data.hdr.mdl_id) || |
| ((evt_data.hdr.mdl_id == MCA_ALL_MDL_ID) && |
| (evt_data.hdr.op_code == MCA_OP_MDL_DELETE_REQ))) { |
| reject_code = MCA_RSP_SUCCESS; |
| /* mdl_id is valid according to the spec */ |
| switch (evt_data.hdr.op_code) { |
| case MCA_OP_MDL_CREATE_REQ: |
| evt_data.create_ind.dep_id = *p++; |
| evt_data.create_ind.cfg = *p++; |
| p_rx_msg->mdep_id = evt_data.create_ind.dep_id; |
| if (!mca_is_valid_dep_id(p_ccb->p_rcb, p_rx_msg->mdep_id)) { |
| MCA_TRACE_ERROR("%s: Invalid local MDEP ID %d", __func__, |
| p_rx_msg->mdep_id); |
| reject_code = MCA_RSP_BAD_MDEP; |
| } else if (mca_ccb_uses_mdl_id(p_ccb, evt_data.hdr.mdl_id)) { |
| MCA_TRACE_DEBUG("the mdl_id is currently used in the CL(create)"); |
| mca_dcb_close_by_mdl_id(p_ccb, evt_data.hdr.mdl_id); |
| } else { |
| /* check if this dep still have MDL available */ |
| if (mca_dep_free_mdl(p_ccb, evt_data.create_ind.dep_id) == 0) { |
| MCA_TRACE_ERROR("%s: MAX_MDL is used by MDEP %d", __func__, |
| evt_data.create_ind.dep_id); |
| reject_code = MCA_RSP_MDEP_BUSY; |
| } |
| } |
| break; |
| |
| case MCA_OP_MDL_RECONNECT_REQ: |
| if (mca_ccb_uses_mdl_id(p_ccb, evt_data.hdr.mdl_id)) { |
| MCA_TRACE_ERROR("%s: MDL_ID %d busy, in CL(reconn)", __func__, |
| evt_data.hdr.mdl_id); |
| reject_code = MCA_RSP_MDL_BUSY; |
| } |
| break; |
| |
| case MCA_OP_MDL_ABORT_REQ: |
| reject_code = MCA_RSP_BAD_OP; |
| break; |
| |
| case MCA_OP_MDL_DELETE_REQ: |
| /* delete the associated mdl */ |
| mca_dcb_close_by_mdl_id(p_ccb, evt_data.hdr.mdl_id); |
| send_rsp = true; |
| break; |
| } |
| } |
| } |
| } |
| |
| if (((reject_code != MCA_RSP_SUCCESS) && |
| (evt_data.hdr.op_code != MCA_OP_SYNC_INFO_IND)) || |
| send_rsp) { |
| BT_HDR* p_buf = (BT_HDR*)osi_malloc(MCA_CTRL_MTU); |
| p_buf->offset = L2CAP_MIN_OFFSET; |
| p = p_start = (uint8_t*)(p_buf + 1) + L2CAP_MIN_OFFSET; |
| *p++ = reject_opcode; |
| *p++ = reject_code; |
| bool valid_response = true; |
| switch (reject_opcode) { |
| // Fill in the rest of standard opcode response packet with mdl_id |
| case MCA_OP_ERROR_RSP: |
| case MCA_OP_MDL_CREATE_RSP: |
| case MCA_OP_MDL_RECONNECT_RSP: |
| case MCA_OP_MDL_ABORT_RSP: |
| case MCA_OP_MDL_DELETE_RSP: |
| UINT16_TO_BE_STREAM(p, evt_data.hdr.mdl_id); |
| break; |
| // Fill in the rest of clock sync opcode response packet with 0 |
| case MCA_OP_SYNC_CAP_RSP: |
| // Page 37/58 MCAP V1.0 Spec: Total length (9) - 2 = 7 |
| memset(p, 0, 7); |
| p += 7; |
| break; |
| case MCA_OP_SYNC_SET_RSP: |
| // Page 39/58 MCAP V1.0 Spec: Total length (16) - 2 = 14 |
| memset(p, 0, 14); |
| p += 14; |
| break; |
| default: |
| MCA_TRACE_ERROR("%s: reject_opcode 0x%02x not recognized", __func__, |
| reject_opcode); |
| valid_response = false; |
| break; |
| } |
| if (valid_response) { |
| p_buf->len = p - p_start; |
| MCA_TRACE_ERROR("%s: reject_opcode=0x%02x, reject_code=0x%02x, length=%d", |
| __func__, reject_opcode, reject_code, p_buf->len); |
| L2CA_DataWrite(p_ccb->lcid, p_buf); |
| } else { |
| osi_free(p_buf); |
| } |
| } |
| |
| if (reject_code == MCA_RSP_SUCCESS) { |
| /* use the received GKI buffer to store information to double check response |
| * API */ |
| p_rx_msg->op_code = evt_data.hdr.op_code; |
| p_rx_msg->mdl_id = evt_data.hdr.mdl_id; |
| p_ccb->p_rx_msg = p_rx_msg; |
| if (send_rsp) { |
| osi_free(p_pkt); |
| p_ccb->p_rx_msg = NULL; |
| } |
| mca_ccb_report_event(p_ccb, evt_data.hdr.op_code, &evt_data); |
| } else |
| osi_free(p_pkt); |
| } |
| |
| /******************************************************************************* |
| * |
| * Function mca_ccb_hdl_rsp |
| * |
| * Description This function is called when a MCAP response is received |
| * from the peer. It calls the application callback function |
| * with the results. |
| * |
| * Returns void. |
| * |
| ******************************************************************************/ |
| void mca_ccb_hdl_rsp(tMCA_CCB* p_ccb, tMCA_CCB_EVT* p_data) { |
| BT_HDR* p_pkt = &p_data->hdr; |
| uint8_t* p; |
| tMCA_CTRL evt_data; |
| bool chk_mdl = false; |
| tMCA_DCB* p_dcb; |
| tMCA_RESULT result = MCA_BAD_HANDLE; |
| tMCA_TC_TBL* p_tbl; |
| |
| if (p_ccb->p_tx_req) { |
| /* verify that the received response matches the sent request */ |
| p = (uint8_t*)(p_pkt + 1) + p_pkt->offset; |
| evt_data.hdr.op_code = *p++; |
| if ((evt_data.hdr.op_code == 0) || |
| ((p_ccb->p_tx_req->op_code + 1) == evt_data.hdr.op_code)) { |
| evt_data.rsp.rsp_code = *p++; |
| mca_stop_timer(p_ccb); |
| BE_STREAM_TO_UINT16(evt_data.hdr.mdl_id, p); |
| if (evt_data.hdr.op_code == MCA_OP_MDL_CREATE_RSP) { |
| evt_data.create_cfm.cfg = *p++; |
| chk_mdl = true; |
| } else if (evt_data.hdr.op_code == MCA_OP_MDL_RECONNECT_RSP) |
| chk_mdl = true; |
| |
| if (chk_mdl) { |
| p_dcb = mca_dcb_by_hdl(p_ccb->p_tx_req->dcb_idx); |
| if (evt_data.rsp.rsp_code == MCA_RSP_SUCCESS) { |
| if (evt_data.hdr.mdl_id != p_dcb->mdl_id) { |
| MCA_TRACE_ERROR("peer's mdl_id=%d != our mdl_id=%d", |
| evt_data.hdr.mdl_id, p_dcb->mdl_id); |
| /* change the response code to be an error */ |
| if (evt_data.rsp.rsp_code == MCA_RSP_SUCCESS) { |
| evt_data.rsp.rsp_code = MCA_RSP_BAD_MDL; |
| /* send Abort */ |
| p_ccb->status = MCA_CCB_STAT_PENDING; |
| MCA_Abort(mca_ccb_to_hdl(p_ccb)); |
| } |
| } else if (p_dcb->p_chnl_cfg) { |
| /* the data channel configuration is known. Proceed with data |
| * channel initiation */ |
| BTM_SetSecurityLevel(true, "", BTM_SEC_SERVICE_MCAP_DATA, |
| p_ccb->sec_mask, p_ccb->data_vpsm, |
| BTM_SEC_PROTO_MCA, p_ccb->p_tx_req->dcb_idx); |
| p_dcb->lcid = mca_l2c_open_req(p_ccb->peer_addr, p_ccb->data_vpsm, |
| p_dcb->p_chnl_cfg); |
| if (p_dcb->lcid) { |
| p_tbl = mca_tc_tbl_dalloc(p_dcb); |
| if (p_tbl) { |
| p_tbl->state = MCA_TC_ST_CONN; |
| p_ccb->status = MCA_CCB_STAT_PENDING; |
| result = MCA_SUCCESS; |
| } |
| } |
| } else { |
| /* mark this MCL as pending and wait for MCA_DataChnlCfg */ |
| p_ccb->status = MCA_CCB_STAT_PENDING; |
| result = MCA_SUCCESS; |
| } |
| } |
| |
| if (result != MCA_SUCCESS && p_dcb) { |
| mca_dcb_dealloc(p_dcb, NULL); |
| } |
| } /* end of chk_mdl */ |
| |
| if (p_ccb->status != MCA_CCB_STAT_PENDING) |
| osi_free_and_reset((void**)&p_ccb->p_tx_req); |
| mca_ccb_report_event(p_ccb, evt_data.hdr.op_code, &evt_data); |
| } |
| /* else a bad response is received */ |
| } else { |
| /* not expecting any response. drop it */ |
| MCA_TRACE_WARNING("dropping received rsp (not expecting a response)"); |
| } |
| osi_free(p_data); |
| } |
| |
| /******************************************************************************* |
| * |
| * Function mca_ccb_ll_open |
| * |
| * Description This function is called to report MCA_CONNECT_IND_EVT event. |
| * It also clears the congestion flag (ccb.cong). |
| * |
| * Returns void. |
| * |
| ******************************************************************************/ |
| void mca_ccb_ll_open(tMCA_CCB* p_ccb, tMCA_CCB_EVT* p_data) { |
| tMCA_CTRL evt_data; |
| p_ccb->cong = false; |
| evt_data.connect_ind.mtu = p_data->open.peer_mtu; |
| evt_data.connect_ind.bd_addr = p_ccb->peer_addr; |
| mca_ccb_report_event(p_ccb, MCA_CONNECT_IND_EVT, &evt_data); |
| } |
| |
| /******************************************************************************* |
| * |
| * Function mca_ccb_dl_open |
| * |
| * Description This function is called when data channel is open. It clears |
| * p_tx_req to allow other message exchage on this CL. |
| * |
| * Returns void. |
| * |
| ******************************************************************************/ |
| void mca_ccb_dl_open(tMCA_CCB* p_ccb, UNUSED_ATTR tMCA_CCB_EVT* p_data) { |
| osi_free_and_reset((void**)&p_ccb->p_tx_req); |
| osi_free_and_reset((void**)&p_ccb->p_rx_msg); |
| p_ccb->status = MCA_CCB_STAT_NORM; |
| } |