1/****************************************************************************** 2 * 3 * Copyright (C) 2009-2012 Broadcom Corporation 4 * 5 * Licensed under the Apache License, Version 2.0 (the "License"); 6 * you may not use this file except in compliance with the License. 7 * You may obtain a copy of the License at: 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 * 17 ******************************************************************************/ 18 19/****************************************************************************** 20 * 21 * This is the implementation file for the MCAP Control Channel Action 22 * Functions. 23 * 24 ******************************************************************************/ 25#include <string.h> 26#include "bt_common.h" 27#include "bt_target.h" 28#include "bt_utils.h" 29#include "btm_api.h" 30#include "mca_api.h" 31#include "mca_defs.h" 32#include "mca_int.h" 33#include "osi/include/osi.h" 34 35#include "btu.h" 36 37extern fixed_queue_t* btu_general_alarm_queue; 38 39/***************************************************************************** 40 * constants 41 ****************************************************************************/ 42/******************************************************************************* 43 * 44 * Function mca_ccb_rsp_tout 45 * 46 * Description This function processes the response timeout. 47 * 48 * Returns void. 49 * 50 ******************************************************************************/ 51void mca_ccb_rsp_tout(tMCA_CCB* p_ccb, UNUSED_ATTR tMCA_CCB_EVT* p_data) { 52 tMCA_CTRL evt_data; 53 54 mca_ccb_report_event(p_ccb, MCA_RSP_TOUT_IND_EVT, &evt_data); 55} 56 57/******************************************************************************* 58 * 59 * Function mca_ccb_report_event 60 * 61 * Description This function reports the given event. 62 * 63 * Returns void. 64 * 65 ******************************************************************************/ 66void mca_ccb_report_event(tMCA_CCB* p_ccb, uint8_t event, tMCA_CTRL* p_data) { 67 if (p_ccb && p_ccb->p_rcb && p_ccb->p_rcb->p_cback) 68 (*p_ccb->p_rcb->p_cback)(mca_rcb_to_handle(p_ccb->p_rcb), 69 mca_ccb_to_hdl(p_ccb), event, p_data); 70} 71 72/******************************************************************************* 73 * 74 * Function mca_ccb_free_msg 75 * 76 * Description This function frees the received message. 77 * 78 * Returns void. 79 * 80 ******************************************************************************/ 81void mca_ccb_free_msg(UNUSED_ATTR tMCA_CCB* p_ccb, tMCA_CCB_EVT* p_data) { 82 osi_free(p_data); 83} 84 85/******************************************************************************* 86 * 87 * Function mca_ccb_snd_req 88 * 89 * Description This function builds a request and sends it to the peer. 90 * 91 * Returns void. 92 * 93 ******************************************************************************/ 94void mca_ccb_snd_req(tMCA_CCB* p_ccb, tMCA_CCB_EVT* p_data) { 95 tMCA_CCB_MSG* p_msg = (tMCA_CCB_MSG*)p_data; 96 uint8_t *p, *p_start; 97 bool is_abort = false; 98 tMCA_DCB* p_dcb; 99 100 MCA_TRACE_DEBUG("mca_ccb_snd_req cong=%d req=%d", p_ccb->cong, 101 p_msg->op_code); 102 /* check for abort request */ 103 if ((p_ccb->status == MCA_CCB_STAT_PENDING) && 104 (p_msg->op_code == MCA_OP_MDL_ABORT_REQ)) { 105 p_dcb = mca_dcb_by_hdl(p_ccb->p_tx_req->dcb_idx); 106 /* the Abort API does not have the associated mdl_id. 107 * Get the mdl_id in dcb to compose the request */ 108 p_msg->mdl_id = p_dcb->mdl_id; 109 mca_dcb_event(p_dcb, MCA_DCB_API_CLOSE_EVT, NULL); 110 osi_free_and_reset((void**)&p_ccb->p_tx_req); 111 p_ccb->status = MCA_CCB_STAT_NORM; 112 is_abort = true; 113 } 114 115 /* no pending outgoing messages or it's an abort request for a pending data 116 * channel */ 117 if ((!p_ccb->p_tx_req) || is_abort) { 118 p_ccb->p_tx_req = p_msg; 119 if (!p_ccb->cong) { 120 BT_HDR* p_pkt = (BT_HDR*)osi_malloc(MCA_CTRL_MTU); 121 122 p_pkt->offset = L2CAP_MIN_OFFSET; 123 p = p_start = (uint8_t*)(p_pkt + 1) + L2CAP_MIN_OFFSET; 124 *p++ = p_msg->op_code; 125 UINT16_TO_BE_STREAM(p, p_msg->mdl_id); 126 if (p_msg->op_code == MCA_OP_MDL_CREATE_REQ) { 127 *p++ = p_msg->mdep_id; 128 *p++ = p_msg->param; 129 } 130 p_msg->hdr.layer_specific = true; /* mark this message as sent */ 131 p_pkt->len = p - p_start; 132 L2CA_DataWrite(p_ccb->lcid, p_pkt); 133 period_ms_t interval_ms = p_ccb->p_rcb->reg.rsp_tout * 1000; 134 alarm_set_on_queue(p_ccb->mca_ccb_timer, interval_ms, 135 mca_ccb_timer_timeout, p_ccb, btu_general_alarm_queue); 136 } 137 /* else the L2CAP channel is congested. keep the message to be sent later */ 138 } else { 139 MCA_TRACE_WARNING("dropping api req"); 140 osi_free(p_data); 141 } 142} 143 144/******************************************************************************* 145 * 146 * Function mca_ccb_snd_rsp 147 * 148 * Description This function builds a response and sends it to 149 * the peer. 150 * 151 * Returns void. 152 * 153 ******************************************************************************/ 154void mca_ccb_snd_rsp(tMCA_CCB* p_ccb, tMCA_CCB_EVT* p_data) { 155 tMCA_CCB_MSG* p_msg = (tMCA_CCB_MSG*)p_data; 156 uint8_t *p, *p_start; 157 BT_HDR* p_pkt = (BT_HDR*)osi_malloc(MCA_CTRL_MTU); 158 159 MCA_TRACE_DEBUG("%s cong=%d req=%d", __func__, p_ccb->cong, p_msg->op_code); 160 /* assume that API functions verified the parameters */ 161 162 p_pkt->offset = L2CAP_MIN_OFFSET; 163 p = p_start = (uint8_t*)(p_pkt + 1) + L2CAP_MIN_OFFSET; 164 *p++ = p_msg->op_code; 165 *p++ = p_msg->rsp_code; 166 UINT16_TO_BE_STREAM(p, p_msg->mdl_id); 167 // Only add extra parameters for MCA_RSP_SUCCESS message 168 if (p_msg->rsp_code == MCA_RSP_SUCCESS) { 169 // Append MDL configuration parameters 170 if (p_msg->op_code == MCA_OP_MDL_CREATE_RSP) { 171 *p++ = p_msg->param; 172 } 173 // Check MDL 174 if (p_msg->op_code == MCA_OP_MDL_CREATE_RSP || 175 p_msg->op_code == MCA_OP_MDL_RECONNECT_RSP) { 176 mca_dcb_by_hdl(p_msg->dcb_idx); 177 BTM_SetSecurityLevel(false, "", BTM_SEC_SERVICE_MCAP_DATA, 178 p_ccb->sec_mask, p_ccb->p_rcb->reg.data_psm, 179 BTM_SEC_PROTO_MCA, p_msg->dcb_idx); 180 p_ccb->status = MCA_CCB_STAT_PENDING; 181 /* set p_tx_req to block API_REQ/API_RSP before DL is up */ 182 osi_free_and_reset((void**)&p_ccb->p_tx_req); 183 p_ccb->p_tx_req = p_ccb->p_rx_msg; 184 p_ccb->p_rx_msg = NULL; 185 p_ccb->p_tx_req->dcb_idx = p_msg->dcb_idx; 186 } 187 } 188 189 osi_free_and_reset((void**)&p_ccb->p_rx_msg); 190 p_pkt->len = p - p_start; 191 L2CA_DataWrite(p_ccb->lcid, p_pkt); 192} 193 194/******************************************************************************* 195 * 196 * Function mca_ccb_do_disconn 197 * 198 * Description This function closes a control channel. 199 * 200 * Returns void. 201 * 202 ******************************************************************************/ 203void mca_ccb_do_disconn(tMCA_CCB* p_ccb, UNUSED_ATTR tMCA_CCB_EVT* p_data) { 204 mca_dcb_close_by_mdl_id(p_ccb, MCA_ALL_MDL_ID); 205 L2CA_DisconnectReq(p_ccb->lcid); 206} 207 208/******************************************************************************* 209 * 210 * Function mca_ccb_cong 211 * 212 * Description This function sets the congestion state for the CCB. 213 * 214 * Returns void. 215 * 216 ******************************************************************************/ 217void mca_ccb_cong(tMCA_CCB* p_ccb, tMCA_CCB_EVT* p_data) { 218 MCA_TRACE_DEBUG("mca_ccb_cong cong=%d/%d", p_ccb->cong, p_data->llcong); 219 p_ccb->cong = p_data->llcong; 220 if (!p_ccb->cong) { 221 /* if there's a held packet, send it now */ 222 if (p_ccb->p_tx_req && !p_ccb->p_tx_req->hdr.layer_specific) { 223 p_data = (tMCA_CCB_EVT*)p_ccb->p_tx_req; 224 p_ccb->p_tx_req = NULL; 225 mca_ccb_snd_req(p_ccb, p_data); 226 } 227 } 228} 229 230/******************************************************************************* 231 * 232 * Function mca_ccb_hdl_req 233 * 234 * Description This function is called when a MCAP request is received from 235 * the peer. It calls the application callback function to 236 * report the event. 237 * 238 * Returns void. 239 * 240 ******************************************************************************/ 241void mca_ccb_hdl_req(tMCA_CCB* p_ccb, tMCA_CCB_EVT* p_data) { 242 BT_HDR* p_pkt = &p_data->hdr; 243 uint8_t *p, *p_start; 244 tMCA_DCB* p_dcb; 245 tMCA_CTRL evt_data; 246 tMCA_CCB_MSG* p_rx_msg = NULL; 247 uint8_t reject_code = MCA_RSP_NO_RESOURCE; 248 bool send_rsp = false; 249 bool check_req = false; 250 uint8_t reject_opcode; 251 252 MCA_TRACE_DEBUG("mca_ccb_hdl_req status:%d", p_ccb->status); 253 p_rx_msg = (tMCA_CCB_MSG*)p_pkt; 254 p = (uint8_t*)(p_pkt + 1) + p_pkt->offset; 255 evt_data.hdr.op_code = *p++; 256 BE_STREAM_TO_UINT16(evt_data.hdr.mdl_id, p); 257 reject_opcode = evt_data.hdr.op_code + 1; 258 259 MCA_TRACE_DEBUG("received mdl id: %d ", evt_data.hdr.mdl_id); 260 if (p_ccb->status == MCA_CCB_STAT_PENDING) { 261 MCA_TRACE_DEBUG("received req inpending state"); 262 /* allow abort in pending state */ 263 if ((p_ccb->status == MCA_CCB_STAT_PENDING) && 264 (evt_data.hdr.op_code == MCA_OP_MDL_ABORT_REQ)) { 265 reject_code = MCA_RSP_SUCCESS; 266 send_rsp = true; 267 /* clear the pending status */ 268 p_ccb->status = MCA_CCB_STAT_NORM; 269 if (p_ccb->p_tx_req && 270 ((p_dcb = mca_dcb_by_hdl(p_ccb->p_tx_req->dcb_idx)) != NULL)) { 271 mca_dcb_dealloc(p_dcb, NULL); 272 osi_free_and_reset((void**)&p_ccb->p_tx_req); 273 } 274 } else 275 reject_code = MCA_RSP_BAD_OP; 276 } else if (p_ccb->p_rx_msg) { 277 MCA_TRACE_DEBUG("still handling prev req"); 278 /* still holding previous message, reject this new one ?? */ 279 280 } else if (p_ccb->p_tx_req) { 281 MCA_TRACE_DEBUG("still waiting for a response ctrl_vpsm:0x%x", 282 p_ccb->ctrl_vpsm); 283 /* sent a request; waiting for response */ 284 if (p_ccb->ctrl_vpsm == 0) { 285 MCA_TRACE_DEBUG("local is ACP. accept the cmd from INT"); 286 /* local is acceptor, need to handle the request */ 287 check_req = true; 288 reject_code = MCA_RSP_SUCCESS; 289 /* drop the previous request */ 290 if ((p_ccb->p_tx_req->op_code == MCA_OP_MDL_CREATE_REQ) && 291 ((p_dcb = mca_dcb_by_hdl(p_ccb->p_tx_req->dcb_idx)) != NULL)) { 292 mca_dcb_dealloc(p_dcb, NULL); 293 } 294 osi_free_and_reset((void**)&p_ccb->p_tx_req); 295 mca_stop_timer(p_ccb); 296 } else { 297 /* local is initiator, ignore the req */ 298 osi_free(p_pkt); 299 return; 300 } 301 } else if (p_pkt->layer_specific != MCA_RSP_SUCCESS) { 302 reject_code = (uint8_t)p_pkt->layer_specific; 303 if (((evt_data.hdr.op_code >= MCA_NUM_STANDARD_OPCODE) && 304 (evt_data.hdr.op_code < MCA_FIRST_SYNC_OP)) || 305 (evt_data.hdr.op_code > MCA_LAST_SYNC_OP)) { 306 /* invalid op code */ 307 reject_opcode = MCA_OP_ERROR_RSP; 308 evt_data.hdr.mdl_id = 0; 309 } 310 } else { 311 check_req = true; 312 reject_code = MCA_RSP_SUCCESS; 313 } 314 315 if (check_req) { 316 if (reject_code == MCA_RSP_SUCCESS) { 317 reject_code = MCA_RSP_BAD_MDL; 318 if (MCA_IS_VALID_MDL_ID(evt_data.hdr.mdl_id) || 319 ((evt_data.hdr.mdl_id == MCA_ALL_MDL_ID) && 320 (evt_data.hdr.op_code == MCA_OP_MDL_DELETE_REQ))) { 321 reject_code = MCA_RSP_SUCCESS; 322 /* mdl_id is valid according to the spec */ 323 switch (evt_data.hdr.op_code) { 324 case MCA_OP_MDL_CREATE_REQ: 325 evt_data.create_ind.dep_id = *p++; 326 evt_data.create_ind.cfg = *p++; 327 p_rx_msg->mdep_id = evt_data.create_ind.dep_id; 328 if (!mca_is_valid_dep_id(p_ccb->p_rcb, p_rx_msg->mdep_id)) { 329 MCA_TRACE_ERROR("%s: Invalid local MDEP ID %d", __func__, 330 p_rx_msg->mdep_id); 331 reject_code = MCA_RSP_BAD_MDEP; 332 } else if (mca_ccb_uses_mdl_id(p_ccb, evt_data.hdr.mdl_id)) { 333 MCA_TRACE_DEBUG("the mdl_id is currently used in the CL(create)"); 334 mca_dcb_close_by_mdl_id(p_ccb, evt_data.hdr.mdl_id); 335 } else { 336 /* check if this dep still have MDL available */ 337 if (mca_dep_free_mdl(p_ccb, evt_data.create_ind.dep_id) == 0) { 338 MCA_TRACE_ERROR("%s: MAX_MDL is used by MDEP %d", __func__, 339 evt_data.create_ind.dep_id); 340 reject_code = MCA_RSP_MDEP_BUSY; 341 } 342 } 343 break; 344 345 case MCA_OP_MDL_RECONNECT_REQ: 346 if (mca_ccb_uses_mdl_id(p_ccb, evt_data.hdr.mdl_id)) { 347 MCA_TRACE_ERROR("%s: MDL_ID %d busy, in CL(reconn)", __func__, 348 evt_data.hdr.mdl_id); 349 reject_code = MCA_RSP_MDL_BUSY; 350 } 351 break; 352 353 case MCA_OP_MDL_ABORT_REQ: 354 reject_code = MCA_RSP_BAD_OP; 355 break; 356 357 case MCA_OP_MDL_DELETE_REQ: 358 /* delete the associated mdl */ 359 mca_dcb_close_by_mdl_id(p_ccb, evt_data.hdr.mdl_id); 360 send_rsp = true; 361 break; 362 } 363 } 364 } 365 } 366 367 if (((reject_code != MCA_RSP_SUCCESS) && 368 (evt_data.hdr.op_code != MCA_OP_SYNC_INFO_IND)) || 369 send_rsp) { 370 BT_HDR* p_buf = (BT_HDR*)osi_malloc(MCA_CTRL_MTU); 371 p_buf->offset = L2CAP_MIN_OFFSET; 372 p = p_start = (uint8_t*)(p_buf + 1) + L2CAP_MIN_OFFSET; 373 *p++ = reject_opcode; 374 *p++ = reject_code; 375 bool valid_response = true; 376 switch (reject_opcode) { 377 // Fill in the rest of standard opcode response packet with mdl_id 378 case MCA_OP_ERROR_RSP: 379 case MCA_OP_MDL_CREATE_RSP: 380 case MCA_OP_MDL_RECONNECT_RSP: 381 case MCA_OP_MDL_ABORT_RSP: 382 case MCA_OP_MDL_DELETE_RSP: 383 UINT16_TO_BE_STREAM(p, evt_data.hdr.mdl_id); 384 break; 385 // Fill in the rest of clock sync opcode response packet with 0 386 case MCA_OP_SYNC_CAP_RSP: 387 // Page 37/58 MCAP V1.0 Spec: Total length (9) - 2 = 7 388 memset(p, 0, 7); 389 p += 7; 390 break; 391 case MCA_OP_SYNC_SET_RSP: 392 // Page 39/58 MCAP V1.0 Spec: Total length (16) - 2 = 14 393 memset(p, 0, 14); 394 p += 14; 395 break; 396 default: 397 MCA_TRACE_ERROR("%s: reject_opcode 0x%02x not recognized", __func__, 398 reject_opcode); 399 valid_response = false; 400 break; 401 } 402 if (valid_response) { 403 p_buf->len = p - p_start; 404 MCA_TRACE_ERROR("%s: reject_opcode=0x%02x, reject_code=0x%02x, length=%d", 405 __func__, reject_opcode, reject_code, p_buf->len); 406 L2CA_DataWrite(p_ccb->lcid, p_buf); 407 } else { 408 osi_free(p_buf); 409 } 410 } 411 412 if (reject_code == MCA_RSP_SUCCESS) { 413 /* use the received GKI buffer to store information to double check response 414 * API */ 415 p_rx_msg->op_code = evt_data.hdr.op_code; 416 p_rx_msg->mdl_id = evt_data.hdr.mdl_id; 417 p_ccb->p_rx_msg = p_rx_msg; 418 if (send_rsp) { 419 osi_free(p_pkt); 420 p_ccb->p_rx_msg = NULL; 421 } 422 mca_ccb_report_event(p_ccb, evt_data.hdr.op_code, &evt_data); 423 } else 424 osi_free(p_pkt); 425} 426 427/******************************************************************************* 428 * 429 * Function mca_ccb_hdl_rsp 430 * 431 * Description This function is called when a MCAP response is received 432 * from the peer. It calls the application callback function 433 * with the results. 434 * 435 * Returns void. 436 * 437 ******************************************************************************/ 438void mca_ccb_hdl_rsp(tMCA_CCB* p_ccb, tMCA_CCB_EVT* p_data) { 439 BT_HDR* p_pkt = &p_data->hdr; 440 uint8_t* p; 441 tMCA_CTRL evt_data; 442 bool chk_mdl = false; 443 tMCA_DCB* p_dcb; 444 tMCA_RESULT result = MCA_BAD_HANDLE; 445 tMCA_TC_TBL* p_tbl; 446 447 if (p_ccb->p_tx_req) { 448 /* verify that the received response matches the sent request */ 449 p = (uint8_t*)(p_pkt + 1) + p_pkt->offset; 450 evt_data.hdr.op_code = *p++; 451 if ((evt_data.hdr.op_code == 0) || 452 ((p_ccb->p_tx_req->op_code + 1) == evt_data.hdr.op_code)) { 453 evt_data.rsp.rsp_code = *p++; 454 mca_stop_timer(p_ccb); 455 BE_STREAM_TO_UINT16(evt_data.hdr.mdl_id, p); 456 if (evt_data.hdr.op_code == MCA_OP_MDL_CREATE_RSP) { 457 evt_data.create_cfm.cfg = *p++; 458 chk_mdl = true; 459 } else if (evt_data.hdr.op_code == MCA_OP_MDL_RECONNECT_RSP) 460 chk_mdl = true; 461 462 if (chk_mdl) { 463 p_dcb = mca_dcb_by_hdl(p_ccb->p_tx_req->dcb_idx); 464 if (evt_data.rsp.rsp_code == MCA_RSP_SUCCESS) { 465 if (evt_data.hdr.mdl_id != p_dcb->mdl_id) { 466 MCA_TRACE_ERROR("peer's mdl_id=%d != our mdl_id=%d", 467 evt_data.hdr.mdl_id, p_dcb->mdl_id); 468 /* change the response code to be an error */ 469 if (evt_data.rsp.rsp_code == MCA_RSP_SUCCESS) { 470 evt_data.rsp.rsp_code = MCA_RSP_BAD_MDL; 471 /* send Abort */ 472 p_ccb->status = MCA_CCB_STAT_PENDING; 473 MCA_Abort(mca_ccb_to_hdl(p_ccb)); 474 } 475 } else if (p_dcb->p_chnl_cfg) { 476 /* the data channel configuration is known. Proceed with data 477 * channel initiation */ 478 BTM_SetSecurityLevel(true, "", BTM_SEC_SERVICE_MCAP_DATA, 479 p_ccb->sec_mask, p_ccb->data_vpsm, 480 BTM_SEC_PROTO_MCA, p_ccb->p_tx_req->dcb_idx); 481 p_dcb->lcid = mca_l2c_open_req(p_ccb->peer_addr, p_ccb->data_vpsm, 482 p_dcb->p_chnl_cfg); 483 if (p_dcb->lcid) { 484 p_tbl = mca_tc_tbl_dalloc(p_dcb); 485 if (p_tbl) { 486 p_tbl->state = MCA_TC_ST_CONN; 487 p_ccb->status = MCA_CCB_STAT_PENDING; 488 result = MCA_SUCCESS; 489 } 490 } 491 } else { 492 /* mark this MCL as pending and wait for MCA_DataChnlCfg */ 493 p_ccb->status = MCA_CCB_STAT_PENDING; 494 result = MCA_SUCCESS; 495 } 496 } 497 498 if (result != MCA_SUCCESS && p_dcb) { 499 mca_dcb_dealloc(p_dcb, NULL); 500 } 501 } /* end of chk_mdl */ 502 503 if (p_ccb->status != MCA_CCB_STAT_PENDING) 504 osi_free_and_reset((void**)&p_ccb->p_tx_req); 505 mca_ccb_report_event(p_ccb, evt_data.hdr.op_code, &evt_data); 506 } 507 /* else a bad response is received */ 508 } else { 509 /* not expecting any response. drop it */ 510 MCA_TRACE_WARNING("dropping received rsp (not expecting a response)"); 511 } 512 osi_free(p_data); 513} 514 515/******************************************************************************* 516 * 517 * Function mca_ccb_ll_open 518 * 519 * Description This function is called to report MCA_CONNECT_IND_EVT event. 520 * It also clears the congestion flag (ccb.cong). 521 * 522 * Returns void. 523 * 524 ******************************************************************************/ 525void mca_ccb_ll_open(tMCA_CCB* p_ccb, tMCA_CCB_EVT* p_data) { 526 tMCA_CTRL evt_data; 527 p_ccb->cong = false; 528 evt_data.connect_ind.mtu = p_data->open.peer_mtu; 529 memcpy(evt_data.connect_ind.bd_addr, p_ccb->peer_addr, BD_ADDR_LEN); 530 mca_ccb_report_event(p_ccb, MCA_CONNECT_IND_EVT, &evt_data); 531} 532 533/******************************************************************************* 534 * 535 * Function mca_ccb_dl_open 536 * 537 * Description This function is called when data channel is open. It clears 538 * p_tx_req to allow other message exchage on this CL. 539 * 540 * Returns void. 541 * 542 ******************************************************************************/ 543void mca_ccb_dl_open(tMCA_CCB* p_ccb, UNUSED_ATTR tMCA_CCB_EVT* p_data) { 544 osi_free_and_reset((void**)&p_ccb->p_tx_req); 545 osi_free_and_reset((void**)&p_ccb->p_rx_msg); 546 p_ccb->status = MCA_CCB_STAT_NORM; 547} 548