蓝牙主机和蓝牙设备建立连接之后,会在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)) != )
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[]; // Check for supported channel
if (!(peer_channel_mask & ( << fixed_cid)))
{
L2CAP_TRACE_EVENT ("%s() CID:0x%04x BDA: %08x%04x not supported", __func__,
fixed_cid,(rem_bda[]<<)+(rem_bda[]<<)+(rem_bda[]<<)+rem_bda[],
(rem_bda[]<<)+rem_bda[]);
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, , p_lcb->transport);//回调,这里是重点
#else
(*l2cb.fixed_reg[fixed_cid - L2CAP_FIRST_FIXED_CHNL].pL2CA_FixedConn_Cb)
(fixed_cid, p_lcb->remote_bd_addr, TRUE, , 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, , sizeof(tGATT_CB));
memset (&fixed_reg, , 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 = ;
fixed_reg.fixed_chnl_opts.mon_tout = ;
fixed_reg.fixed_chnl_opts.mps = ;
fixed_reg.fixed_chnl_opts.tx_win_sz = ; 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 上面的应用有关。


蓝牙 link timeout分析的更多相关文章

  1. Nginx 504 Gateway Time-out分析及解决方法

    一.场景还原php程序在执行抓取远程图片库并保存至本地服务器的时候,出现了“504 Gateway Time-out”错误提示. 问题定位:由于图片巨多,所以下载时间很长(10分钟以上),引起网关超时 ...

  2. Lock wait timeout分析

    ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction分析 1.4个用户连接数据库(A和D是本地回环登陆, ...

  3. 低功耗蓝牙4.0BLE编程-nrf51822开发(11)-蓝牙串口代码分析

    代码实例:点击打开链接 实现的功能是从uart口发送数据至另一个蓝牙串口,或是从蓝牙读取数据通过uart打印出数据. int main(void) { // Initialize leds_init( ...

  4. Android4.4 蓝牙源代码段分析

    最近GOOGLE发布时间Android4.4,我看了看源代码.4.4蓝牙打开过程或这部分的一些变化,判断蓝牙开关是从接口设置settings在里面switch开关,widget当然,它可以切换,也许启 ...

  5. 蓝牙spp协议分析

    基本概念 蓝牙串口是基于 SPP 协议(Serial Port Profile),能在蓝牙设备之间创建串口进行数据传输的一种设备. 蓝牙串口的目的是针对如何在两个不同设备(通信的两端)上的应用之间保证 ...

  6. 动态添加LInk的分析

    动态创建HyperLink超链接: 1.如果添加HyperLink的代码只写在Button中,则只会显示最后一次添加的内容.所以必须在Pageload中添加. 2.首次载入: PageLoad    ...

  7. 蓝牙speaker配对流程源码分析

    这篇文章简单分析一下 蓝牙音箱配对流程.现在的音箱基本都支持security simple pairing.所以这里的流程基本上就是ssp的代码流程. 源码参考的是 Android 6.0 上面的bl ...

  8. Android上成功实现了蓝牙的一些Profile

    前段时间做蓝牙方面的开发,Google的Android只实现了Handset/Handfree和A2DP/AVRCP等Profile,而其 它常用的Profile如HID/DUN/SPP/OPP/FT ...

  9. 蓝牙disable流程简述

    蓝牙关闭的流程比打开流程要简单,主要就是一些profile的断连以及协议栈相关结构的释放. 这里简单说一下其流程,就直接从协议栈的disable的接口说起了. static int disable(v ...

随机推荐

  1. 利用webpack搭建的前端工程化环境

    随着webpack3.x的发布,其功能也越来越强大,很多的项目的编译打包工具也由gulp逐渐转移到webpack.最近因为项目重构考虑使用使用vue,同时想从原来的gulp切换到webpack,所以搭 ...

  2. oracle order by 排序

    Syntax ORDER BY { column-Name | ColumnPosition | Expression } [ ASC | DESC ] [ NULLS FIRST | NULLS L ...

  3. The Art of Unit Testing With Examples in .NET

    The Art of Unit Testing With Examples in .NET

  4. python基础 - 字符串作

    split(sep=None, maxsplip=-1) 从左到右 sep 指定分隔字符串,缺省情况下空白字符串,指定的字符串会被切掉 maxsplit 指定分隔次数,-1 表示遍历 rsplit(s ...

  5. Windows安装PostgreSQL11.1

    Windows安装PostgreSQL11.1 安装过程如下: 1.下载安装包postgresql-11.1-1-windows-x64.exe 2.点击下一步 3.选择安装位置,默认路径C:\Pro ...

  6. Axure RP Pro7.0的key注册码加汉化非破解

    上次我们刚分享过Axure RP Pro6.5 key注册码加汉化非破解,我还要分享一个Axure RP Pro7.0的key注册码加汉化,非破解哦. 当然方法还是不变,先用下面的密钥激活.用户名就是 ...

  7. Luogu P4707 重返现世

    题目描述 为了打开返回现世的大门,Yopilla 需要制作开启大门的钥匙.Yopilla 所在的迷失大陆有 \(n\) 种原料,只需要集齐任意 \(k\) 种,就可以开始制作. Yopilla 来到了 ...

  8. 【BZOJ2820】YY的GCD

    [BZOJ2820]YY的GCD Description 神犇YY虐完数论后给傻×kAc出了一题 给定N, M,求1<=x<=N, 1<=y<=M且gcd(x, y)为质数的( ...

  9. socket 总结

    网络编程之进程:http://www.cnblogs.com/1a2a/p/7428759.html 网络编程之进阶:http://www.cnblogs.com/1a2a/p/7444446.htm ...

  10. Spark算子讲解(二)

    1:glom def glom(): RDD[Array[T]] 将原RDD的元素收集到一个数组,创建一个数组类型的RDD 2:getNumPartitions final def getNumPar ...