蓝牙 link timeout分析

蓝牙主机和蓝牙设备建立连接之后,会在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。

其实在BTA_GATTC_OPEN 中已经描述了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 上面的应用有关。


 

posted @ 2018-07-27 20:06  雪山飞燕  阅读(1921)  评论(0编辑  收藏  举报