/******************************************************************************* ** ** Function avdt_ad_open_req ** ** Description This function is called by a CCB or SCB to open a transport ** channel. This function allocates and initializes a ** transport channel table entry. The channel can be opened ** in two roles: as an initiator or acceptor. When opened ** as an initiator the function will start an L2CAP connection. ** When opened as an acceptor the function simply configures ** the table entry to listen for an incoming channel. ** ** ** Returns Nothing. ** *******************************************************************************/ void avdt_ad_open_req(UINT8 type, tAVDT_CCB *p_ccb, tAVDT_SCB *p_scb, UINT8 role) { tAVDT_TC_TBL *p_tbl; UINT16 lcid; if ((p_tbl = avdt_ad_tc_tbl_alloc(p_ccb)) == NULL) { AVDT_TRACE_ERROR("avdt_ad_open_req: Cannot allocate p_tbl"); return; } p_tbl->tcid = avdt_ad_type_to_tcid(type, p_scb); AVDT_TRACE_DEBUG("avdt_ad_open_req: type: %d, role: %d, tcid:%d\n", type, role, p_tbl->tcid); if (type == AVDT_CHAN_SIG) { /* if signaling, get mtu from registration control block */ p_tbl->my_mtu = avdt_cb.rcb.ctrl_mtu; p_tbl->my_flush_to = L2CAP_DEFAULT_FLUSH_TO; } else { /* otherwise get mtu from scb */ p_tbl->my_mtu = p_scb->cs.mtu; p_tbl->my_flush_to = p_scb->cs.flush_to; /* also set scb_hdl in rt_tbl */ avdt_cb.ad.rt_tbl[avdt_ccb_to_idx(p_ccb)][p_tbl->tcid].scb_hdl = avdt_scb_to_hdl(p_scb); AVDT_TRACE_DEBUG("avdt_cb.ad.rt_tbl[%d][%d].scb_hdl = %d\n", avdt_ccb_to_idx(p_ccb), p_tbl->tcid, avdt_scb_to_hdl(p_scb)); } /* if we're acceptor, we're done; just sit back and listen */ if (role == AVDT_ACP) { p_tbl->state = AVDT_AD_ST_ACP; } /* else we're inititator, start the L2CAP connection */ else { p_tbl->state = AVDT_AD_ST_CONN; /* call l2cap connect req */ if ((lcid = L2CA_ConnectReq(AVDT_PSM, p_ccb->peer_addr)) != 0) { /* if connect req ok, store tcid in lcid table */ avdt_cb.ad.lcid_tbl[lcid - L2CAP_BASE_APPL_CID] = avdt_ad_tc_tbl_to_idx(p_tbl); AVDT_TRACE_DEBUG("avdt_cb.ad.lcid_tbl[%d] = %d\n", (lcid - L2CAP_BASE_APPL_CID), avdt_ad_tc_tbl_to_idx(p_tbl)); avdt_cb.ad.rt_tbl[avdt_ccb_to_idx(p_ccb)][p_tbl->tcid].lcid = lcid; AVDT_TRACE_DEBUG("avdt_cb.ad.rt_tbl[%d][%d].lcid = 0x%x\n", avdt_ccb_to_idx(p_ccb), p_tbl->tcid, lcid); } else { /* if connect req failed, call avdt_ad_tc_close_ind() */ avdt_ad_tc_close_ind(p_tbl, 0); } } }
/******************************************************************************* ** ** Function avdt_scb_verify ** ** Description Verify the condition of a list of scbs. ** ** ** Returns SEID that failed, or 0 if success. ** *******************************************************************************/ UINT8 avdt_scb_verify(tAVDT_CCB *p_ccb, UINT8 state, UINT8 *p_seid, UINT16 num_seid, UINT8 *p_err_code) { int i; tAVDT_SCB *p_scb; UINT8 nsc_mask; UINT8 chk_state; UINT8 ret = 0; AVDT_TRACE_DEBUG("avdt_scb_verify state %d", state); /* set nonsupported command mask */ /* translate public state into private state */ nsc_mask = 0; chk_state = AVDT_SCB_STREAM_ST; switch(state) { case AVDT_VERIFY_SUSPEND: nsc_mask = AVDT_NSC_SUSPEND; break; case AVDT_VERIFY_OPEN: case AVDT_VERIFY_START: chk_state = AVDT_SCB_OPEN_ST; break; } /* verify every scb */ for (i = 0; i < num_seid; i++) { if ((p_scb = avdt_scb_by_hdl(p_seid[i])) == NULL) { *p_err_code = AVDT_ERR_BAD_STATE; break; } else if ((p_scb->state != chk_state) || (p_scb->p_ccb != p_ccb)) { *p_err_code = AVDT_ERR_BAD_STATE; break; } else if (p_scb->cs.nsc_mask & nsc_mask) { *p_err_code = AVDT_ERR_NSC; break; } } if (i != num_seid) { ret = p_seid[i]; } AVDT_TRACE_DEBUG("avdt_scb_verify state %d, nsc_mask0x%x, ret: %d", chk_state, nsc_mask, ret); return ret; }
/******************************************************************************* ** ** Function avdt_sec_check_complete_orig ** ** Description The function called when Security Manager finishes ** verification of the service side connection ** ** Returns void ** *******************************************************************************/ static void avdt_sec_check_complete_orig (BD_ADDR bd_addr, tBT_TRANSPORT trasnport, void *p_ref_data, UINT8 res) { tAVDT_CCB *p_ccb = NULL; tL2CAP_CFG_INFO cfg; tAVDT_TC_TBL *p_tbl; UNUSED(p_ref_data); AVDT_TRACE_DEBUG("avdt_sec_check_complete_orig res: %d\n", res); if (bd_addr) { p_ccb = avdt_ccb_by_bd(bd_addr); } p_tbl = avdt_ad_tc_tbl_by_st(AVDT_CHAN_SIG, p_ccb, AVDT_AD_ST_SEC_INT); if (p_tbl == NULL) { return; } if ( res == BTM_SUCCESS ) { /* set channel state */ p_tbl->state = AVDT_AD_ST_CFG; /* Send L2CAP config req */ memset(&cfg, 0, sizeof(tL2CAP_CFG_INFO)); cfg.mtu_present = TRUE; cfg.mtu = p_tbl->my_mtu; cfg.flush_to_present = TRUE; cfg.flush_to = p_tbl->my_flush_to; L2CA_ConfigReq(p_tbl->lcid, &cfg); } else { L2CA_DisconnectReq (p_tbl->lcid); avdt_ad_tc_close_ind(p_tbl, L2CAP_CONN_SECURITY_BLOCK); } }
/******************************************************************************* ** ** Function avdt_l2c_config_ind_cback ** ** Description This is the L2CAP config indication callback function. ** ** ** Returns void ** *******************************************************************************/ void avdt_l2c_config_ind_cback(UINT16 lcid, tL2CAP_CFG_INFO *p_cfg) { tAVDT_TC_TBL *p_tbl; /* look up info for this channel */ if ((p_tbl = avdt_ad_tc_tbl_by_lcid(lcid)) != NULL) { /* store the mtu in tbl */ if (p_cfg->mtu_present) { p_tbl->peer_mtu = p_cfg->mtu; } else { p_tbl->peer_mtu = L2CAP_DEFAULT_MTU; } AVDT_TRACE_DEBUG("peer_mtu: %d, lcid: x%x\n", p_tbl->peer_mtu, lcid); /* send L2CAP configure response */ memset(p_cfg, 0, sizeof(tL2CAP_CFG_INFO)); p_cfg->result = L2CAP_CFG_OK; L2CA_ConfigRsp(lcid, p_cfg); /* if first config ind */ if ((p_tbl->cfg_flags & AVDT_L2C_CFG_IND_DONE) == 0) { /* update cfg_flags */ p_tbl->cfg_flags |= AVDT_L2C_CFG_IND_DONE; /* if configuration complete */ if (p_tbl->cfg_flags & AVDT_L2C_CFG_CFM_DONE) { avdt_ad_tc_open_ind(p_tbl); } } } }
/******************************************************************************* ** ** Function avdt_ad_tc_close_ind ** ** Description This function is called by the L2CAP interface when the ** L2CAP channel is closed. It looks up the CCB or SCB for ** the channel and sends it a close event. The reason ** parameter is the same value passed by the L2CAP ** callback function. ** ** ** Returns Nothing. ** *******************************************************************************/ void avdt_ad_tc_close_ind(tAVDT_TC_TBL *p_tbl, UINT16 reason) { tAVDT_CCB *p_ccb; tAVDT_SCB *p_scb; tAVDT_SCB_TC_CLOSE close; // UNUSED(reason); close.old_tc_state = p_tbl->state; /* clear avdt_ad_tc_tbl entry */ p_tbl->state = AVDT_AD_ST_UNUSED; p_tbl->cfg_flags = 0; p_tbl->peer_mtu = L2CAP_DEFAULT_MTU; AVDT_TRACE_DEBUG("avdt_ad_tc_close_ind tcid: %d, old: %d\n", p_tbl->tcid, close.old_tc_state); /* if signaling channel, notify ccb that channel open */ if (p_tbl->tcid == 0) { p_ccb = avdt_ccb_by_idx(p_tbl->ccb_idx); p_ccb->disc_rsn = (reason == AVDT_DISC_RSN_ABNORMAL) ? AVDT_DISC_RSN_ABNORMAL : AVDT_DISC_RSN_NORMAL; avdt_ccb_event(p_ccb, AVDT_CCB_LL_CLOSE_EVT, NULL); } /* if media or other channel, notify scb that channel close */ else { /* look up scb in stream routing table by ccb, tcid */ p_scb = avdt_scb_by_hdl(avdt_cb.ad.rt_tbl[p_tbl->ccb_idx][p_tbl->tcid].scb_hdl); if (p_scb != NULL) { close.tcid = p_tbl->tcid; close.type = avdt_ad_tcid_to_type(p_tbl->tcid); close.disc_rsn = (reason == AVDT_DISC_RSN_ABNORMAL) ? AVDT_DISC_RSN_ABNORMAL : AVDT_DISC_RSN_NORMAL; avdt_scb_event(p_scb, AVDT_SCB_TC_CLOSE_EVT, (tAVDT_SCB_EVT *)&close); } } }
/******************************************************************************* ** ** Function AVDT_SINK_Deactivate ** ** Description Deactivate SEP of A2DP Sink. In Use parameter is adjusted. ** In Use will be made TRUE in case of activation. A2DP SRC ** will receive in_use as true and will not open A2DP Sink ** connection ** ** Returns void. ** *******************************************************************************/ void AVDT_SINK_Deactivate() { tAVDT_SCB *p_scb = &avdt_cb.scb[0]; int i; AVDT_TRACE_DEBUG("AVDT_SINK_Deactivate"); /* for all allocated scbs */ for (i = 0; i < AVDT_NUM_SEPS; i++, p_scb++) { if ((p_scb->allocated) && (p_scb->cs.tsep == AVDT_TSEP_SNK)) { AVDT_TRACE_DEBUG("AVDT_SINK_Deactivate, found scb"); p_scb->sink_activated = FALSE; /* update in_use */ p_scb->in_use = TRUE; break; } } }
/******************************************************************************* ** ** Function avdt_l2c_disconnect_cfm_cback ** ** Description This is the L2CAP disconnect confirm callback function. ** ** ** Returns void ** *******************************************************************************/ void avdt_l2c_disconnect_cfm_cback(UINT16 lcid, UINT16 result) { tAVDT_TC_TBL *p_tbl; AVDT_TRACE_DEBUG("avdt_l2c_disconnect_cfm_cback lcid: %d, result: %d\n", lcid, result); /* look up info for this channel */ if ((p_tbl = avdt_ad_tc_tbl_by_lcid(lcid)) != NULL) { avdt_ad_tc_close_ind(p_tbl, result); } }
/******************************************************************************* ** ** Function avdt_scb_alloc ** ** Description Allocate a stream control block. ** ** ** Returns pointer to the scb, or NULL if none could be allocated. ** *******************************************************************************/ tAVDT_SCB *avdt_scb_alloc(tAVDT_CS *p_cs) { tAVDT_SCB *p_scb = &avdt_cb.scb[0]; int i; /* find available scb */ for (i = 0; i < AVDT_NUM_SEPS; i++, p_scb++) { if (!p_scb->allocated) { memset(p_scb,0,sizeof(tAVDT_SCB)); p_scb->allocated = TRUE; p_scb->p_ccb = NULL; /* initialize sink as activated */ if (p_cs->tsep == AVDT_TSEP_SNK) { p_scb->sink_activated = TRUE; } memcpy(&p_scb->cs, p_cs, sizeof(tAVDT_CS)); #if AVDT_MULTIPLEXING == TRUE /* initialize fragments gueue */ GKI_init_q(&p_scb->frag_q); if(p_cs->cfg.psc_mask & AVDT_PSC_MUX) { p_scb->cs.cfg.mux_tcid_media = avdt_ad_type_to_tcid(AVDT_CHAN_MEDIA, p_scb); #if AVDT_REPORTING == TRUE if(p_cs->cfg.psc_mask & AVDT_PSC_REPORT) { p_scb->cs.cfg.mux_tcid_report = avdt_ad_type_to_tcid(AVDT_CHAN_REPORT, p_scb); } #endif } #endif p_scb->timer_entry.param = (UINT32) p_scb; AVDT_TRACE_DEBUG("avdt_scb_alloc hdl=%d, psc_mask:0x%x", i+1, p_cs->cfg.psc_mask); break; } } if (i == AVDT_NUM_SEPS) { /* out of ccbs */ p_scb = NULL; AVDT_TRACE_WARNING("Out of scbs"); } return p_scb; }
/******************************************************************************* ** ** Function avdt_l2c_connect_cfm_cback ** ** Description This is the L2CAP connect confirm callback function. ** ** ** Returns void ** *******************************************************************************/ void avdt_l2c_connect_cfm_cback(UINT16 lcid, UINT16 result) { tAVDT_TC_TBL *p_tbl; tL2CAP_CFG_INFO cfg; tAVDT_CCB *p_ccb; AVDT_TRACE_DEBUG("avdt_l2c_connect_cfm_cback lcid: %d, result: %d\n", lcid, result); /* look up info for this channel */ if ((p_tbl = avdt_ad_tc_tbl_by_lcid(lcid)) != NULL) { /* if in correct state */ if (p_tbl->state == AVDT_AD_ST_CONN) { /* if result successful */ if (result == L2CAP_CONN_OK) { if (p_tbl->tcid != AVDT_CHAN_SIG) { /* set channel state */ p_tbl->state = AVDT_AD_ST_CFG; /* Send L2CAP config req */ memset(&cfg, 0, sizeof(tL2CAP_CFG_INFO)); cfg.mtu_present = TRUE; cfg.mtu = p_tbl->my_mtu; cfg.flush_to_present = TRUE; cfg.flush_to = p_tbl->my_flush_to; L2CA_ConfigReq(lcid, &cfg); } else { p_ccb = avdt_ccb_by_idx(p_tbl->ccb_idx); if (p_ccb == NULL) { result = L2CAP_CONN_NO_RESOURCES; } else { /* set channel state */ p_tbl->state = AVDT_AD_ST_SEC_INT; p_tbl->lcid = lcid; p_tbl->cfg_flags = AVDT_L2C_CFG_CONN_INT; /* Check the security */ btm_sec_mx_access_request (p_ccb->peer_addr, AVDT_PSM, TRUE, BTM_SEC_PROTO_AVDT, AVDT_CHAN_SIG, &avdt_sec_check_complete_orig, NULL); } } } /* failure; notify adaption that channel closed */ if (result != L2CAP_CONN_OK) { avdt_ad_tc_close_ind(p_tbl, result); } } } }
/******************************************************************************* ** ** Function avdt_scb_dealloc ** ** Description Deallocate a stream control block. ** ** ** Returns void. ** *******************************************************************************/ void avdt_scb_dealloc(tAVDT_SCB *p_scb, tAVDT_SCB_EVT *p_data) { UNUSED(p_data); AVDT_TRACE_DEBUG("avdt_scb_dealloc hdl=%d\n", avdt_scb_to_hdl(p_scb)); btu_stop_timer(&p_scb->timer_entry); #if AVDT_MULTIPLEXING == TRUE /* free fragments we're holding, if any; it shouldn't happen */ fixed_queue_free(p_scb->frag_q, osi_free_func); #endif memset(p_scb, 0, sizeof(tAVDT_SCB)); }
/******************************************************************************* ** ** Function avdt_scb_verify ** ** Description Verify the condition of a list of scbs. ** ** ** Returns SEID that failed, or 0 if success. ** *******************************************************************************/ UINT8 avdt_scb_verify(tAVDT_CCB *p_ccb, UINT8 state, UINT8 *p_seid, UINT16 num_seid, UINT8 *p_err_code) { int i; tAVDT_SCB *p_scb; UINT8 nsc_mask; UINT8 ret = 0; AVDT_TRACE_DEBUG("avdt_scb_verify state %d\n", state); /* set nonsupported command mask */ /* translate public state into private state */ nsc_mask = 0; if (state == AVDT_VERIFY_SUSPEND) { nsc_mask = AVDT_NSC_SUSPEND; } /* verify every scb */ for (i = 0, *p_err_code = 0; (i < num_seid) && (*p_err_code == 0) && (i < AVDT_NUM_SEPS); i++) { if ((p_scb = avdt_scb_by_hdl(p_seid[i])) == NULL) { *p_err_code = AVDT_ERR_BAD_STATE; } else if (p_scb->p_ccb != p_ccb) { *p_err_code = AVDT_ERR_BAD_STATE; } else if (p_scb->cs.nsc_mask & nsc_mask) { *p_err_code = AVDT_ERR_NSC; } switch (state) { case AVDT_VERIFY_OPEN: case AVDT_VERIFY_START: if (p_scb->state != AVDT_SCB_OPEN_ST && p_scb->state != AVDT_SCB_STREAM_ST) { *p_err_code = AVDT_ERR_BAD_STATE; } break; case AVDT_VERIFY_SUSPEND: case AVDT_VERIFY_STREAMING: if (p_scb->state != AVDT_SCB_STREAM_ST) { *p_err_code = AVDT_ERR_BAD_STATE; } break; } } if ((i != num_seid) && (i < AVDT_NUM_SEPS)) { ret = p_seid[i]; } return ret; }
/******************************************************************************* ** ** Function avdt_ad_tcid_to_type ** ** Description Derives the channel type from the TCID. ** ** ** Returns Channel type value. ** *******************************************************************************/ static UINT8 avdt_ad_tcid_to_type(UINT8 tcid) { UINT8 type; if (tcid == 0) { type = AVDT_CHAN_SIG; } else { /* tcid translates to type based on number of channels, as follows: ** only media channel : tcid=1,2,3,4,5,6... type=1,1,1,1,1,1... ** media and report : tcid=1,2,3,4,5,6... type=1,2,1,2,1,2... ** media, report, recov : tcid=1,2,3,4,5,6... type=1,2,3,1,2,3... */ type = ((tcid + AVDT_CHAN_NUM_TYPES - 2) % (AVDT_CHAN_NUM_TYPES - 1)) + 1; } AVDT_TRACE_DEBUG("tcid: %d, type: %d\n", tcid, type); return type; }
/******************************************************************************* ** ** Function avdt_l2c_disconnect_ind_cback ** ** Description This is the L2CAP disconnect indication callback function. ** ** ** Returns void ** *******************************************************************************/ void avdt_l2c_disconnect_ind_cback(UINT16 lcid, BOOLEAN ack_needed) { tAVDT_TC_TBL *p_tbl; UINT16 disc_rsn = AVDT_DISC_RSN_NORMAL; AVDT_TRACE_DEBUG("avdt_l2c_disconnect_ind_cback lcid: %d, ack_needed: %d\n", lcid, ack_needed); /* look up info for this channel */ if ((p_tbl = avdt_ad_tc_tbl_by_lcid(lcid)) != NULL) { if (ack_needed) { /* send L2CAP disconnect response */ L2CA_DisconnectRsp(lcid); } else { disc_rsn = AVDT_DISC_RSN_ABNORMAL; } avdt_ad_tc_close_ind(p_tbl, disc_rsn); } }
/******************************************************************************* ** ** Function avdt_scb_dealloc ** ** Description Deallocate a stream control block. ** ** ** Returns void. ** *******************************************************************************/ void avdt_scb_dealloc(tAVDT_SCB *p_scb, tAVDT_SCB_EVT *p_data) { #if AVDT_MULTIPLEXING == TRUE void *p_buf; #endif UNUSED(p_data); AVDT_TRACE_DEBUG("avdt_scb_dealloc hdl=%d", avdt_scb_to_hdl(p_scb)); btu_stop_timer(&p_scb->timer_entry); #if AVDT_MULTIPLEXING == TRUE /* free fragments we're holding, if any; it shouldn't happen */ while ((p_buf = GKI_dequeue (&p_scb->frag_q)) != NULL) GKI_freebuf(p_buf); #endif memset(p_scb, 0, sizeof(tAVDT_SCB)); }
/******************************************************************************* ** ** Function avdt_sec_check_complete_term ** ** Description The function called when Security Manager finishes ** verification of the service side connection ** ** Returns void ** *******************************************************************************/ static void avdt_sec_check_complete_term (BD_ADDR bd_addr, tBT_TRANSPORT transport, void *p_ref_data, UINT8 res) { tAVDT_CCB *p_ccb = NULL; tL2CAP_CFG_INFO cfg; tAVDT_TC_TBL *p_tbl; UNUSED(p_ref_data); AVDT_TRACE_DEBUG("avdt_sec_check_complete_term res: %d\n", res); if (!bd_addr) { AVDT_TRACE_WARNING("avdt_sec_check_complete_term: NULL BD_ADDR"); return; } p_ccb = avdt_ccb_by_bd(bd_addr); p_tbl = avdt_ad_tc_tbl_by_st(AVDT_CHAN_SIG, p_ccb, AVDT_AD_ST_SEC_ACP); if (p_tbl == NULL) { return; } if (res == BTM_SUCCESS) { /* Send response to the L2CAP layer. */ L2CA_ConnectRsp (bd_addr, p_tbl->id, p_tbl->lcid, L2CAP_CONN_OK, L2CAP_CONN_OK); /* store idx in LCID table, store LCID in routing table */ avdt_cb.ad.lcid_tbl[p_tbl->lcid - L2CAP_BASE_APPL_CID] = avdt_ad_tc_tbl_to_idx(p_tbl); avdt_cb.ad.rt_tbl[avdt_ccb_to_idx(p_ccb)][p_tbl->tcid].lcid = p_tbl->lcid; /* transition to configuration state */ p_tbl->state = AVDT_AD_ST_CFG; /* Send L2CAP config req */ memset(&cfg, 0, sizeof(tL2CAP_CFG_INFO)); cfg.mtu_present = TRUE; cfg.mtu = p_tbl->my_mtu; cfg.flush_to_present = TRUE; cfg.flush_to = p_tbl->my_flush_to; L2CA_ConfigReq(p_tbl->lcid, &cfg); } else { L2CA_ConnectRsp (bd_addr, p_tbl->id, p_tbl->lcid, L2CAP_CONN_SECURITY_BLOCK, L2CAP_CONN_OK); avdt_ad_tc_close_ind(p_tbl, L2CAP_CONN_SECURITY_BLOCK); } }
/******************************************************************************* ** ** Function avdt_ad_close_req ** ** Description This function is called by a CCB or SCB to close a ** transport channel. The function looks up the LCID for the ** channel and calls L2CA_DisconnectReq(). ** ** ** Returns Nothing. ** *******************************************************************************/ void avdt_ad_close_req(UINT8 type, tAVDT_CCB *p_ccb, tAVDT_SCB *p_scb) { UINT8 tcid; tAVDT_TC_TBL *p_tbl; p_tbl = avdt_ad_tc_tbl_by_type(type, p_ccb, p_scb); AVDT_TRACE_DEBUG("avdt_ad_close_req state: %d\n", p_tbl->state); switch (p_tbl->state) { case AVDT_AD_ST_UNUSED: /* probably for reporting */ break; case AVDT_AD_ST_ACP: /* if we're listening on this channel, send ourselves a close ind */ avdt_ad_tc_close_ind(p_tbl, 0); break; default: /* get tcid from type, scb */ tcid = avdt_ad_type_to_tcid(type, p_scb); /* call l2cap disconnect req */ L2CA_DisconnectReq(avdt_cb.ad.rt_tbl[avdt_ccb_to_idx(p_ccb)][tcid].lcid); } }
/******************************************************************************* ** ** Function avdt_ad_tc_tbl_to_idx ** ** Description Convert a transport channel table entry to an index. ** ** ** Returns Index value. ** *******************************************************************************/ UINT8 avdt_ad_tc_tbl_to_idx(tAVDT_TC_TBL *p_tbl) { AVDT_TRACE_DEBUG("avdt_ad_tc_tbl_to_idx: %d\n", (p_tbl - avdt_cb.ad.tc_tbl)); /* use array arithmetic to determine index */ return (UINT8) (p_tbl - avdt_cb.ad.tc_tbl); }