蓝牙 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)) != )
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分析的更多相关文章
- Nginx 504 Gateway Time-out分析及解决方法
一.场景还原php程序在执行抓取远程图片库并保存至本地服务器的时候,出现了“504 Gateway Time-out”错误提示. 问题定位:由于图片巨多,所以下载时间很长(10分钟以上),引起网关超时 ...
- Lock wait timeout分析
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction分析 1.4个用户连接数据库(A和D是本地回环登陆, ...
- 低功耗蓝牙4.0BLE编程-nrf51822开发(11)-蓝牙串口代码分析
代码实例:点击打开链接 实现的功能是从uart口发送数据至另一个蓝牙串口,或是从蓝牙读取数据通过uart打印出数据. int main(void) { // Initialize leds_init( ...
- Android4.4 蓝牙源代码段分析
最近GOOGLE发布时间Android4.4,我看了看源代码.4.4蓝牙打开过程或这部分的一些变化,判断蓝牙开关是从接口设置settings在里面switch开关,widget当然,它可以切换,也许启 ...
- 蓝牙spp协议分析
基本概念 蓝牙串口是基于 SPP 协议(Serial Port Profile),能在蓝牙设备之间创建串口进行数据传输的一种设备. 蓝牙串口的目的是针对如何在两个不同设备(通信的两端)上的应用之间保证 ...
- 动态添加LInk的分析
动态创建HyperLink超链接: 1.如果添加HyperLink的代码只写在Button中,则只会显示最后一次添加的内容.所以必须在Pageload中添加. 2.首次载入: PageLoad ...
- 蓝牙speaker配对流程源码分析
这篇文章简单分析一下 蓝牙音箱配对流程.现在的音箱基本都支持security simple pairing.所以这里的流程基本上就是ssp的代码流程. 源码参考的是 Android 6.0 上面的bl ...
- Android上成功实现了蓝牙的一些Profile
前段时间做蓝牙方面的开发,Google的Android只实现了Handset/Handfree和A2DP/AVRCP等Profile,而其 它常用的Profile如HID/DUN/SPP/OPP/FT ...
- 蓝牙disable流程简述
蓝牙关闭的流程比打开流程要简单,主要就是一些profile的断连以及协议栈相关结构的释放. 这里简单说一下其流程,就直接从协议栈的disable的接口说起了. static int disable(v ...
随机推荐
- c#事务处理(sqlTransaction)
事务: /// <summary> /// 删除考勤 /// </summary> /// <param name="dto">Id</p ...
- 总结Hibernate4.1+版本与Hibernate3.3+版本区别
利用休假时间好好学习了当今流行的ORMapping框架-Hibernate,看完了马士兵老师经典的Hibernate视频教程,也算是小小入门了吧. 马老师在讲课中使用的Hibernate版本是3.3. ...
- python第五十天--paramiko
python通过paramiko实现,ssh功能 import paramiko ssh =paramiko.SSHClient()#创建一个SSH连接对象 ssh.set_missing_host_ ...
- shell中>/dev/null 2>&1
本文转自http://www.kissyu.org/ 背景 我们经常能在shell脚本中发现>/dev/null 2>&1这样的语句.以前的我并没有去深入地理解这段命令的作用,照搬 ...
- 10 种机器学习算法的要点(附 Python 和 R 代码)
本文由 伯乐在线 - Agatha 翻译,唐尤华 校稿.未经许可,禁止转载!英文出处:SUNIL RAY.欢迎加入翻译组. 前言 谷歌董事长施密特曾说过:虽然谷歌的无人驾驶汽车和机器人受到了许多媒体关 ...
- 在Emacs中使用git操作
在Emacs中使用git操作 1.安装 magit 插件 2.安装后开始使用 3.使用方法: . 使用 M-x:magit-status 打开当前仓库查看基本信息 .使用 ? 键查看magit命令绑定 ...
- MATLAB线性方程组的迭代求解法
MATLAB线性方程组的迭代求解法 作者:凯鲁嘎吉 - 博客园http://www.cnblogs.com/kailugaji/ 一.实验目的 1. 借助矩阵按模最大特征值,判断解方程组的Jacobi ...
- Django admin 后台管理
在App 文件夹下的 admin.py 文件中写入 from django.contrib import admin from app名 import models admin.site.site_h ...
- python六十一课——高阶函数之reduce
2).reduce(fn,lsd): 参数一:fn --> 函数对象 参数二:lsd --> 序列对象 功能: 先将lsd中的第一和第二个元素去除传入到fn中参与运算, 运算后得到结果,再 ...
- 最近公共祖先(LCA)模板
第一行包含三个正整数N.M.S,分别表示树的结点个数.询问的个数和树根结点的序号. 接下来N-1行每行包含两个正整数x.y,表示x结点和y结点之间有一条直接连接的边(数据保证可以构成树). 接下来M行 ...