藍牙主機和藍牙設備創建鏈接以後,會在l2cap 層面上創建相應的channel,這些channel 基本上是用於各類不一樣的profile 或者protocol 進行通訊用的。html
當相應的profile或者protocol 再也不被使用的時候,這些創建的channel 都要被清除掉。當一條link上面沒有了 相應的channel以後,那麼通過一段時間以後,它就會斷開,這個時間就是link idle timeout。app
這裏分析一下LE 設備的link idle timeout函數
這段邏輯實際上是在 創建channel 的過程當中完成的,當前Android8.0 的bluedroid 是在link 創建完成,進行完remote feature的交互以後就會設置link idle timeout,這裏分析的狀況是Android6.0的bluedroid。this
其實在BTA_GATTC_OPEN 中已經描述了channel open的過程,可是沒有講到 link idle timeout 相關,咱們這裏從gatt_connect 來分析:spa
/******************************************************************************* ** ** Function gatt_connect ** ** Description This function is called to initiate a connection to a peer device. ** ** Parameter rem_bda: remote device address to connect to. ** ** Returns TRUE if connection is started, otherwise return FALSE. ** *******************************************************************************/ BOOLEAN gatt_connect (BD_ADDR rem_bda, tGATT_TCB *p_tcb, tBT_TRANSPORT transport) { BOOLEAN gatt_ret = FALSE; if (gatt_get_ch_state(p_tcb) != GATT_CH_OPEN) gatt_set_ch_state(p_tcb, GATT_CH_CONN); if (transport == BT_TRANSPORT_LE) { p_tcb->att_lcid = L2CAP_ATT_CID; gatt_ret = L2CA_ConnectFixedChnl (L2CAP_ATT_CID, rem_bda);//建立固定的channel } else { if ((p_tcb->att_lcid = L2CA_ConnectReq(BT_PSM_ATT, rem_bda)) != 0) gatt_ret = TRUE; } return gatt_ret; }
LE設備 使用的固定的channel 都是L2CAP_ATT_CID :這裏注意,執行到open channel的時候,通常都已經完成link的創建:code
/******************************************************************************* ** ** Function L2CA_ConnectFixedChnl ** ** Description Connect an fixed signalling channel to a remote device. ** ** Parameters: Fixed CID ** BD Address of remote ** ** Return value: TRUE if connection started ** *******************************************************************************/ BOOLEAN L2CA_ConnectFixedChnl (UINT16 fixed_cid, BD_ADDR rem_bda) { tL2C_LCB *p_lcb; tBT_TRANSPORT transport = BT_TRANSPORT_BR_EDR; ... tL2C_BLE_FIXED_CHNLS_MASK peer_channel_mask; // If we already have a link to the remote, check if it supports that CID if ((p_lcb = l2cu_find_lcb_by_bd_addr (rem_bda, transport)) != NULL) { // Fixed channels are mandatory on LE transports so ignore the received // channel mask and use the locally cached LE channel mask. #if BLE_INCLUDED == TRUE if (transport == BT_TRANSPORT_LE) peer_channel_mask = l2cb.l2c_ble_fixed_chnls_mask; else #endif peer_channel_mask = p_lcb->peer_chnl_mask[0]; // Check for supported channel if (!(peer_channel_mask & (1 << fixed_cid))) { L2CAP_TRACE_EVENT ("%s() CID:0x%04x BDA: %08x%04x not supported", __func__, fixed_cid,(rem_bda[0]<<24)+(rem_bda[1]<<16)+(rem_bda[2]<<8)+rem_bda[3], (rem_bda[4]<<8)+rem_bda[5]); return FALSE; } // Get a CCB and link the lcb to it if (!l2cu_initialize_fixed_ccb (p_lcb, fixed_cid, &l2cb.fixed_reg[fixed_cid - L2CAP_FIRST_FIXED_CHNL].fixed_chnl_opts)) { L2CAP_TRACE_WARNING ("%s(0x%04x) - LCB but no CCB", __func__, fixed_cid); return FALSE; } ... #if BLE_INCLUDED == TRUE (*l2cb.fixed_reg[fixed_cid - L2CAP_FIRST_FIXED_CHNL].pL2CA_FixedConn_Cb) (fixed_cid,p_lcb->remote_bd_addr, TRUE, 0, p_lcb->transport);//回調,這裏是重點 #else (*l2cb.fixed_reg[fixed_cid - L2CAP_FIRST_FIXED_CHNL].pL2CA_FixedConn_Cb) (fixed_cid, p_lcb->remote_bd_addr, TRUE, 0, BT_TRANSPORT_BR_EDR); #endif return TRUE; } // No link. Get an LCB and start link establishment ... return TRUE; }
那這個l2cb.fixed_reg[fixed_cid - L2CAP_FIRST_FIXED_CHNL].pL2CA_FixedConn_Cb 是在哪裏註冊的呢?htm
在gatt_init裏面:blog
/******************************************************************************* ** ** Function gatt_init ** ** Description This function is enable the GATT profile on the device. ** It clears out the control blocks, and registers with L2CAP. ** ** Returns void ** *******************************************************************************/ void gatt_init (void) { tL2CAP_FIXED_CHNL_REG fixed_reg; memset (&gatt_cb, 0, sizeof(tGATT_CB)); memset (&fixed_reg, 0, sizeof(tL2CAP_FIXED_CHNL_REG)); #if defined(GATT_INITIAL_TRACE_LEVEL) gatt_cb.trace_level = GATT_INITIAL_TRACE_LEVEL; #else gatt_cb.trace_level = BT_TRACE_LEVEL_NONE; /* No traces */ #endif gatt_cb.def_mtu_size = GATT_DEF_BLE_MTU_SIZE; GKI_init_q (&gatt_cb.sign_op_queue); GKI_init_q (&gatt_cb.srv_chg_clt_q); GKI_init_q (&gatt_cb.pending_new_srv_start_q); /* First, register fixed L2CAP channel for ATT over BLE */ fixed_reg.fixed_chnl_opts.mode = L2CAP_FCR_BASIC_MODE; fixed_reg.fixed_chnl_opts.max_transmit = 0xFF; fixed_reg.fixed_chnl_opts.rtrans_tout = 2000; fixed_reg.fixed_chnl_opts.mon_tout = 12000; fixed_reg.fixed_chnl_opts.mps = 670; fixed_reg.fixed_chnl_opts.tx_win_sz = 1; fixed_reg.pL2CA_FixedConn_Cb = gatt_le_connect_cback; fixed_reg.pL2CA_FixedData_Cb = gatt_le_data_ind; fixed_reg.pL2CA_FixedCong_Cb = gatt_le_cong_cback; /* congestion callback */ fixed_reg.default_idle_tout = 0xffff; /* 0xffff default idle timeout */ L2CA_RegisterFixedChannel (L2CAP_ATT_CID, &fixed_reg);//把ATT相關的參數和回調 註冊到l2cap ... gatt_cb.hdl_cfg.gatt_start_hdl = GATT_GATT_START_HANDLE; gatt_cb.hdl_cfg.gap_start_hdl = GATT_GAP_START_HANDLE; gatt_cb.hdl_cfg.app_start_hdl = GATT_APP_START_HANDLE; gatt_profile_db_init(); }
註冊的過程很簡單就是 將註冊結構 放置到l2cb 結構下:ip
BOOLEAN L2CA_RegisterFixedChannel (UINT16 fixed_cid, tL2CAP_FIXED_CHNL_REG *p_freg) { if ( (fixed_cid < L2CAP_FIRST_FIXED_CHNL) || (fixed_cid > L2CAP_LAST_FIXED_CHNL) ) { L2CAP_TRACE_ERROR ("L2CA_RegisterFixedChannel() Invalid CID: 0x%04x", fixed_cid); return (FALSE); } l2cb.fixed_reg[fixed_cid - L2CAP_FIRST_FIXED_CHNL] = *p_freg; return (TRUE); }
咱們下面重點 看一下 剛剛的回調:ci
fixed_reg.pL2CA_FixedConn_Cb = gatt_le_connect_cback;
看看這個回調的功能,看註冊其是 當fix channel 創建完成以後纔會調用的:
/******************************************************************************* ** ** Function gatt_le_connect_cback ** ** Description This callback function is called by L2CAP to indicate that ** the ATT fixed channel for LE is ** connected (conn = TRUE)/disconnected (conn = FALSE). ** *******************************************************************************/ static void gatt_le_connect_cback (UINT16 chan, BD_ADDR bd_addr, BOOLEAN connected, UINT16 reason, tBT_TRANSPORT transport) { tGATT_TCB *p_tcb = gatt_find_tcb_by_addr(bd_addr, transport); BOOLEAN check_srv_chg = FALSE; tGATTS_SRV_CHG *p_srv_chg_clt=NULL; /* ignore all fixed channel connect/disconnect on BR/EDR link for GATT */ if (transport == BT_TRANSPORT_BR_EDR) return; if ((p_srv_chg_clt = gatt_is_bda_in_the_srv_chg_clt_list(bd_addr)) != NULL) { check_srv_chg = TRUE; } else { if (btm_sec_is_a_bonded_dev(bd_addr)) gatt_add_a_bonded_dev_for_srv_chg(bd_addr); } if (connected) { /* do we have a channel initiating a connection? */ if (p_tcb) { /* we are initiating connection */ if ( gatt_get_ch_state(p_tcb) == GATT_CH_CONN) { /* send callback */ gatt_set_ch_state(p_tcb, GATT_CH_OPEN); p_tcb->payload_size = GATT_DEF_BLE_MTU_SIZE; gatt_send_conn_cback(p_tcb);//看這裏的回調 } if (check_srv_chg) gatt_chk_srv_chg (p_srv_chg_clt); } /* this is incoming connection or background connection callback */ ... }
這裏咱們關注重點,就是 如何設置 link timeout 的:
/******************************************************************************* ** ** Function gatt_send_conn_cback ** ** Description Callback used to notify layer above about a connection. ** ** ** Returns void ** *******************************************************************************/ static void gatt_send_conn_cback(tGATT_TCB *p_tcb) { UINT8 i; tGATT_REG *p_reg; tGATT_BG_CONN_DEV *p_bg_dev=NULL; UINT16 conn_id; p_bg_dev = gatt_find_bg_dev(p_tcb->peer_bda); ... if (gatt_num_apps_hold_link(p_tcb) && p_tcb->att_lcid == L2CAP_ATT_CID ) { /* disable idle timeout if one or more clients are holding the link disable the idle timer */ GATT_SetIdleTimeout(p_tcb->peer_bda, GATT_LINK_NO_IDLE_TIMEOUT, p_tcb->transport); } }
咱們看到了GATT_SetIdleTimeout ,這個函數從名字上面 看就是設置了GATT所在link的 timeout的時間。
void GATT_SetIdleTimeout (BD_ADDR bd_addr, UINT16 idle_tout, tBT_TRANSPORT transport) { tGATT_TCB *p_tcb; BOOLEAN status = FALSE; if ((p_tcb = gatt_find_tcb_by_addr (bd_addr, transport)) != NULL) { if (p_tcb->att_lcid == L2CAP_ATT_CID) { status = L2CA_SetFixedChannelTout (bd_addr, L2CAP_ATT_CID, idle_tout); if (idle_tout == GATT_LINK_IDLE_TIMEOUT_WHEN_NO_APP) L2CA_SetIdleTimeoutByBdAddr(p_tcb->peer_bda, GATT_LINK_IDLE_TIMEOUT_WHEN_NO_APP, BT_TRANSPORT_LE); } else { status = L2CA_SetIdleTimeout (p_tcb->att_lcid, idle_tout, FALSE); } } }
下面函數的註釋寫的很是好,我就很少加解釋了:
/******************************************************************************* ** ** Function L2CA_SetFixedChannelTout ** ** Description Higher layers call this function to set the idle timeout for ** a fixed channel. The "idle timeout" is the amount of time that ** a connection can remain up with no L2CAP channels on it. ** A timeout of zero means that the connection will be torn ** down immediately when the last channel is removed. ** A timeout of 0xFFFF means no timeout. Values are in seconds. ** A bd_addr is the remote BD address. If bd_addr = BT_BD_ANY, ** then the idle timeouts for all active l2cap links will be ** changed. ** ** Returns TRUE if command succeeded, FALSE if failed ** *******************************************************************************/ BOOLEAN L2CA_SetFixedChannelTout (BD_ADDR rem_bda, UINT16 fixed_cid, UINT16 idle_tout) { tL2C_LCB *p_lcb; tBT_TRANSPORT transport = BT_TRANSPORT_BR_EDR; #if BLE_INCLUDED == TRUE if (fixed_cid >= L2CAP_ATT_CID && fixed_cid <= L2CAP_SMP_CID) transport = BT_TRANSPORT_LE; #endif /* Is a fixed channel connected to the remote BDA ?*/ p_lcb = l2cu_find_lcb_by_bd_addr (rem_bda, transport); ... p_lcb->p_fixed_ccbs[fixed_cid - L2CAP_FIRST_FIXED_CHNL]->fixed_chnl_idle_tout = idle_tout;//設置timeout 時間 if (p_lcb->in_use && p_lcb->link_state == LST_CONNECTED && !p_lcb->ccb_queue.p_first_ccb) { /* If there are no dynamic CCBs, (re)start the idle timer in case we changed it */ l2cu_no_dynamic_ccbs (p_lcb); } return TRUE; }
在l2cu_no_dynamic_ccbs裏面進行 idle timer 的設置。關於link timeout 的設置暫時就講到這裏。咱們可以發現,這個link timeout與link 自己無關,而是和跑在link 上面的應用有關。