蓝牙主机和蓝牙设备建立连接之后,会在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. (后端)Java新人入职——配置环境及安装开发工具(完全)

    转自csdn:执笔记忆的空白 很多新人对于进入新公司,相关工具的安装和环境变量的设定很苦恼.又苦于没有完整的配置开发环境的资料,我这里写一篇操作步骤的案例, 至少让你能把开发工具安装起来,并实用起来, ...

  2. 13.1、多进程:进程锁Lock、信号量、事件

    进程锁: 为什么要有进程锁:假如现在有一台打印机,qq要使用打印机,word文档也要使用打印机,如果没有使用进程锁,可能会导致一些问题,比如QQ的任务打印到一半,Word插进来,于是打印出来的结果是各 ...

  3. 洗礼灵魂,修炼python(50)--爬虫篇—基础认识

    爬虫 1.什么是爬虫 爬虫就是昆虫一类的其中一个爬行物种,擅长爬行. 哈哈,开玩笑,在编程里,爬虫其实全名叫网络爬虫,网络爬虫,又被称为网页蜘蛛,网络机器人,在FOAF社区中间,更经常的称为网页追逐者 ...

  4. Asp.net Mvc、webApi配置允许跨域

    Web.config 下<system.webServer> 节点下配置 <httpProtocol> <customHeaders> <add name=& ...

  5. EntityFramework Code-First 简易教程(四)-------继承策略

    在前篇CodeFirst类型约定中,我们在数据库中为每一个模型类创建一个表,但是有个问题,我们可以设计出带继承关系的模型类,即面向对象编程既有“has a”(表示类继承)也有“is a”(表示类包含) ...

  6. 对haproxy文件进行增删改查

    1.文件内容 global log 127.0.0.1 local2 daemon maxconn 256 log 127.0.0.1 local2 info defaults log global ...

  7. 8.1Python面向对象编程(一)

    目录 目录 前言 (一)基本概念 ==1.面向过程与面向对象== ==2.类与对象== (二)类属性的相关操作 ==1.定义一个经典类== ==2.对象属性的操作== ==3.类属性的操作== ==4 ...

  8. 4.91Python数据类型之(6)元组

    前言 有时候,我们为了数值的安全性,不许用户修改数据,今天我们就来讲讲关于python不可变的数据类型--- 元组数据 目录 1.元组的基本定义 2.元组的基本操作 (一)元组的基本定义 1.元组的概 ...

  9. Leviticus

    The head is empty and empty. Just practicing English will not have any effect. The best effect is to ...

  10. Django 使用mysql 数据库流程

    创建一个mysql数据库 在settings中配置: DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', 'NAME': ...