蓝牙主机和蓝牙设备建立连接之后,会在l2cap 层面上建立相应的channel,这些channel 基本上是用于各种不同的profile 或者protocol 进行通信用的。
当相应的profile或者protocol 不再被使用的时候,这些建立的channel 都要被清除掉。当一条link上面没有了 相应的channel之后,那么经过一段时间之后,它就会断开,这个时间就是link idle timeout。
这里分析一下LE 设备的link idle timeout
这段逻辑其实是在 建立channel 的过程中完成的,当前Android8.0 的bluedroid 是在link 建立完成,进行完remote feature的交互之后就会设置link idle timeout,这里分析的情况是Android6.0的bluedroid。
其实在 中已经描述了channel open的过程,但是没有讲到 link idle timeout 相关,我们这里从gatt_connect 来分析:
/*********************************************************************************** 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的建立:
/*********************************************************************************** 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 是在哪里注册的呢?
在gatt_init里面:
/*********************************************************************************** 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 结构下:
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);}
我们下面重点 看一下 刚刚的回调:
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 上面的应用有关。