Bluedroid Study Notes - L2CAP

在数据传输协议中,稳定可靠的传输层(带流空、分包组包、重传等功能)是必不可少的。通过分析蓝牙中的L2CAP层,可以借鉴里面的数据传输和链路管理的设计思想,之后根据需要设计适合自己系统的传输层。

本文介绍蓝牙L2CAP相关内容,主要分为两个部分:一是对 core spec 中L2CAP部分的解读,二是对Bluedroid L2CAP源码的简单分析。

Spec解读

L2CAP的作用

L2CAP是Logic and Link Control and Adapter Protocol的缩写,即逻辑链路控制和适配协议。

L2CAP向上层提供面向连接和无连接的数据服务,并提供多协议功能和分割重组操作。

L2CAP允许上层协议和应用软件传输和接收最大长度为64KB的L2CAP数据包。

L2CAP架构

arch

General Operation

Channel identifiers(CID)

CID指代一个逻辑链路。0x0000不允许被使用。0x0001到0x003F是L2CAP专用的通道,也叫做fixed channels。

0x0001,L2CAP Signaling channel

0x0005,L2CAP LE Signaling channel

工作模式

L2CAP的channel可以工作在以下几种模式:

  • Basic L2CAP Mode

    这是默认的模式,也是用得最多的模式

  • Flow Control Mode

    没有重传,但是可以检测丢失的PDU并报告丢失

  • Retransmission Mode

    用timer来保证所有的数据都能到达对端

  • Enhanced Retransmission Mode

    (谨慎选择)

  • Streaming Mode

    没有重传,没有应答,如果接收端buffer满了,新的会覆盖旧的。可以检测丢包。(谨慎选择)

  • L2 Credit Base Flow Control Mode

  • Enhanced Credit Base Flow Control Mode

数据包格式

数据包格式是数据传输的基础,数据包格式定义了数据收发两端如何”理解“这些数据。知道数据包什么时候使用,里面的字段的含义是什么,才能理解L2CAP是怎么传输数据、怎么做分包组包以及流控和重传的。

Base Mode 面向连接的数据包格式(B-frame)

image-20230514163328725

实际上用得最多的就是这种格式。

Retransmission/FlowControl/Streaming Mode 面向连接的数据包格式

有两种帧:I-frame和S-frame

I-frame,用来传信息的帧,叫做Information frame

S-frame,用来应答和请求重传I-frame的帧,叫做Supervisor-frame

i-s-frame

Control field

I-frame和S-frame都有控制域。

并且有三种控制域:

Standard Control Field:用在Retransmission/Flow Control Mode

Enhanced Control Field:用在Enhanced Retransmission Mode 和 Streaming Mode

Extended Control Field:用在Enhanced Retransmission Mode 和 Streaming Mode

由于增强重传和Streaming Mode基本不用,所以暂不介绍。这里介绍下Standard Control Field。

image-20230514164212841

  • I-frame

    I-frame是用来做数据传输的帧,每一个I-frame都有一个TxSeq和ReqSeq,一个重传位,用来决定I-frame是否是重传的。

    SAR主要用来做分包和组包。

image-20230514164317446

  • S-frame

    S-frame用来应答I-frame,并且请求I-frame的重传。

  • Type

    0表示I-frame,1表示S-frame

  • TxSeq

    发送帧的序号,64取模

  • ReqSeq

    接收端用来应答I-frame,当前应答的帧序号是多少。

    当用在S-frame的REJ(reject)和SREJ(选择性拒绝)时,用来指定发送的哪一个I-frame帧需要被重传。

  • Retransmission Disable Bit - R

    R bit用来实现留空,当接收端的buffer满了的时候,设置该标志位。

    当发送端收到帧里面的R被置位时,需要停止重传定时器,然后停止发送。

  • Segmentation and Reassemble - SAR

sar

  • S-bits

    定义了S-frame的类型。

image-20230514165302810

L2 Credit Base Flow Control Mode和Enhanced Credit Base Flow Control Mode 面向连接的数据包格式(K-frame)

image-20230514165516174

  • PDU Length

SDU的两字节 + Information Payload Length

第一包才有SDU Length字段。所以第一个K-frame时,PDU Length = 2 + Information Payload Length;后续的 PDU Length = Information Payload Length

  • SDU Length

表示总的数据长度。

信令包格式(C-frame)

image-20230514165752461

注意,信令包是在特定的CID上传输的。

image-20230514170029361

信令包的命令号:

signaling-cmd

状态机

这里有两层状态机,其中CONFIG STATE有很多个子状态。

image-20230514170309016

image-20230514170347499

一般流程

配置流程

主要是配置MTU, Flush Timeout,QoS,流控和重传,FCS(Frame Check Sequence)、扩展流控选项、扩展Window Size。

拆包和组包

这里指的是,一个L2CAP frame怎么拆成多个HCI 的 ACL Data Packet。这里其实是HCI层的工作。

image-20230514170832392

组包是拆包的逆过程。

SDU封装

这里才是把上层传下来的L2CAP SDU拆成多个frame。一个SDU可能需要多个I-frame才装得下,所以得拆开。

第一包带SDU Length字段,这样就可以知道整个SDU的长度,后面跟多个I-frame,把payload组起来可以得到完整的SDU。

流控和重传

首先看两个图:

发送端:

image-20230514171253574

这里涉及了一些控制变量:

  • TxSeq

    发送帧序号

  • NextTxSeq

    即将发送的帧序号。每发送一帧,NextTxSeq自加1.

    不能超过ExpectedAckSeq+TxWindow。因为TxWindow是接收端容许发送端最多能发送的还没有收到ack的帧的数目。

    在重传模式和流控模式中,TxWindow在132之间,Enhanced重传模式为163

  • ExpectedAckSeq

    就是最后收到ACK的TxSeq+1。

接收端:

rx

  • ReqSeq

    在I-frame和S-frame中都有ReqSeq。

    ReqSeq是指,前面的包已经正确接收了,我希望下次接收的包的序号。

    ReqSeq应该被设置为BufferSeq或者ExpectedTxSeq。为何设置成这两个值都可以呢?spec给的解释是,ReqSeq设置成BufferSeq,而不是ExpectedTxSeq时,是为了起到流控或者buffer管理的作用,在这种场景下,一般也会把重传位R bit设置为1,以避免不必要的重传。

  • ExpectedTxSeq

    表示最后收到的有效I-frame

  • BufferSeq

    BufferSeq是用来延迟应答帧的,因为接收端收到数据之后,上层把数据读走还需要事件。

    当ExpectedTxSeq - BufferSeq=TxWindows的时候,传输就要先halt住了。

    如果一个frame迟迟没有被读走,那么BufferSeq就会停止增长,接收端回复ReqSeq的时候就使用BufferSeq,然后把Rbit位关闭重传。这样窗口机制就会生效,发送端就会停止发送数据了。

理解了上面这些变量之后,大概就知道重传和流控是怎么生效的了,当然,实际实现比较复杂。

基于Credit的流控

两种基于Credit的流控模式:L2 Credit Base Flow Control Mode和Enhanced Credit Base Flow Control Mode。

L2 Credit Base Flow Control Mode

该模式只能用于LE的面向连接的L2CAP通道中。

  • credit的数量(K-frames)是在connection建立的时候决定的
  • 每发一包K-frame,credir的数量减1
  • 对端可以返回L2CAP_FLOW_CONTROL_CREDIT_IND包,然后本机设备就增加对应数量(包含在credir_ind包中)的credit,表示又可以接收这么多包数据了
  • 最多的credit不超过65535
  • 如果超过,则说明异常了,需要断开channel

Enhanced Credit Base Flow Control Mode

该模式可以用在LE和BR/EDR的面向连接的L2CAP通道中。

代码分析

本节重点不在于介绍L2CAP是怎么实现状态机、通道配置、以及流控重传这些功能(这里面的实现细节太多太复杂了)。

而是从使用的角度,介绍L2CAP是怎么做通道管理、正常流程是如何收发数据的。

Bluedroid的L2CAP使用静态管理的方法,所有的lcb和ccb,都使用静态数据保存在L2CAP这个大的数据结构中。

LCB对应两个设备之间的一个逻辑连接(对应ACL)。

CCB是L2CAP的channel连接,一个逻辑链路可以建立多个channel。

datastruct

当HCI LE连接完成时,会回调到l2cble_conn_comp()这个函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
void l2cble_conn_comp(uint16_t handle, uint8_t role, const RawAddress& bda,
tBLE_ADDR_TYPE type, uint16_t conn_interval,
uint16_t conn_latency, uint16_t conn_timeout) {
btm_ble_update_link_topology_mask(role, true);

if (role == HCI_ROLE_MASTER) {
l2cble_scanner_conn_comp(handle, bda, type, conn_interval, conn_latency,
conn_timeout);
} else {
// 我们作为slave,回调这里
l2cble_advertiser_conn_comp(handle, bda, type, conn_interval, conn_latency,
conn_timeout);
}
}

void l2cble_advertiser_conn_comp(uint16_t handle, const RawAddress& bda,
UNUSED_ATTR tBLE_ADDR_TYPE type,
UNUSED_ATTR uint16_t conn_interval,
UNUSED_ATTR uint16_t conn_latency,
UNUSED_ATTR uint16_t conn_timeout) {
tL2C_LCB* p_lcb;
tBTM_SEC_DEV_REC* p_dev_rec;

// 检查是否已经有lcb
p_lcb = l2cu_find_lcb_by_bd_addr(bda, BT_TRANSPORT_LE);

// 如果没有找到
if (!p_lcb) {
// 则新建一个lcb
p_lcb = l2cu_allocate_lcb(bda, false, BT_TRANSPORT_LE);
if (!p_lcb) {
btm_sec_disconnect(handle, HCI_ERR_NO_CONNECTION);
L2CAP_TRACE_ERROR("l2cble_advertiser_conn_comp - failed to allocate LCB");
return;
} else {
// 初始化fix ccb
if (!l2cu_initialize_fixed_ccb(
p_lcb, L2CAP_ATT_CID,
&l2cb.fixed_reg[L2CAP_ATT_CID - L2CAP_FIRST_FIXED_CHNL]
.fixed_chnl_opts)) {
btm_sec_disconnect(handle, HCI_ERR_NO_CONNECTION);
L2CAP_TRACE_WARNING("l2cble_scanner_conn_comp - LCB but no CCB");
return;
}
}
}

// 保存handle
p_lcb->handle = handle;

p_lcb->link_role = HCI_ROLE_SLAVE;
p_lcb->transport = BT_TRANSPORT_LE;

// 保存连接参数等信息
p_lcb->min_interval = p_lcb->max_interval = conn_interval;
p_lcb->timeout = conn_timeout;
p_lcb->latency = conn_latency;
p_lcb->conn_update_mask = L2C_BLE_NOT_DEFAULT_PARAM;

// 告诉btm,acl已经建立好了
p_dev_rec = btm_find_or_alloc_dev(bda);

btm_acl_created(bda, NULL, p_dev_rec->sec_bd_name, handle, p_lcb->link_role,
BT_TRANSPORT_LE);

#if (BLE_PRIVACY_SPT == TRUE)
btm_ble_disable_resolving_list(BTM_BLE_RL_ADV, true);
#endif

p_lcb->peer_chnl_mask[0] = L2CAP_FIXED_CHNL_ATT_BIT |
L2CAP_FIXED_CHNL_BLE_SIG_BIT |
L2CAP_FIXED_CHNL_SMP_BIT;

if (!HCI_LE_SLAVE_INIT_FEAT_EXC_SUPPORTED(
controller_get_interface()->get_features_ble()->as_array)) {
p_lcb->link_state = LST_CONNECTED;
// 这里会通知一些fix channel已经连上了
l2cu_process_fixed_chnl_resp(p_lcb);
}

/* when adv and initiating are both active, cancel the direct connection */
if (l2cb.is_ble_connecting && bda == l2cb.ble_connecting_bda) {
L2CA_CancelBleConnectReq(bda);
}
}

l2cble_advertiser_conn_comp() 这个函数时作为slave被链接时的处理函数,它从lcb_pool中申请一个lcb,保存了一些跟这个连接相关的信息。然后告诉btm acl已经创建号了,最后调用l2cu_process_fixed_chnl_resp()通知fix channel的上层应用说channel ready。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
void l2cu_process_fixed_chnl_resp(tL2C_LCB* p_lcb) {
if (p_lcb->transport == BT_TRANSPORT_BR_EDR) {
p_lcb->peer_chnl_mask[0] &=
(L2CAP_FIXED_CHNL_SIG_BIT | L2CAP_FIXED_CHNL_CNCTLESS_BIT |
L2CAP_FIXED_CHNL_SMP_BR_BIT);
} else
p_lcb->peer_chnl_mask[0] = l2cb.l2c_ble_fixed_chnls_mask; // 初始化时赋值为了att,ble_sig,smp位

// 告诉所有注册了的fixed channel连接上了
for (int xx = 0; xx < L2CAP_NUM_FIXED_CHNLS; xx++) {
// 如果是BRDER连接,跳过LE的channel
if (p_lcb->transport == BT_TRANSPORT_BR_EDR &&
xx + L2CAP_FIRST_FIXED_CHNL >= L2CAP_ATT_CID &&
xx + L2CAP_FIRST_FIXED_CHNL <= L2CAP_SMP_CID)
continue;
if (l2cb.fixed_reg[xx].pL2CA_FixedConn_Cb != NULL) {
if (p_lcb->peer_chnl_mask[(xx + L2CAP_FIRST_FIXED_CHNL) / 8] &
(1 << ((xx + L2CAP_FIRST_FIXED_CHNL) % 8))) {
if (p_lcb->p_fixed_ccbs[xx])
// channel状态改成open
p_lcb->p_fixed_ccbs[xx]->chnl_state = CST_OPEN;
// 回调给上层
(*l2cb.fixed_reg[xx].pL2CA_FixedConn_Cb)(xx + L2CAP_FIRST_FIXED_CHNL,
p_lcb->remote_bd_addr, true, 0,
p_lcb->transport);
} else {
...
}
}
}
}

值得关注的是:

1
2
3
(*l2cb.fixed_reg[xx].pL2CA_FixedConn_Cb)(xx + L2CAP_FIRST_FIXED_CHNL,
p_lcb->remote_bd_addr, true, 0,
p_lcb->transport);

这里回调给上层应用,具体是什么呢?一个是ATT,一个是SMP。

cid

而它们的回调函数分别是哪里注册的呢?是通过 L2CA_registerFixedChannel()注册的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
void gatt_init(void) {
...
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);

...
}

void smp_l2cap_if_init(void) {
...

L2CA_RegisterFixedChannel(L2CAP_SMP_CID, &fixed_reg);

fixed_reg.pL2CA_FixedConn_Cb = smp_br_connect_callback;
fixed_reg.pL2CA_FixedData_Cb = smp_br_data_received;

L2CA_RegisterFixedChannel(L2CAP_SMP_BR_CID, &fixed_reg);
}

先看下GATT的connection callback:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
static void gatt_le_connect_cback(uint16_t chan, const RawAddress& bd_addr,
bool connected, uint16_t reason,
tBT_TRANSPORT transport) {
...

if (connected) {
/* do we have a channel initiating a connection? */
if (p_tcb) {
...
} else {
// gatt也是申请一个tcb
p_tcb = gatt_allocate_tcb_by_bdaddr(bd_addr, BT_TRANSPORT_LE);
if (p_tcb != NULL) {
p_tcb->att_lcid = L2CAP_ATT_CID;

gatt_set_ch_state(p_tcb, GATT_CH_OPEN);

p_tcb->payload_size = GATT_DEF_BLE_MTU_SIZE;
// 通知app
gatt_send_conn_cback(p_tcb);
if (check_srv_chg) {
gatt_chk_srv_chg(p_srv_chg_clt);
}
}
}
}
}

l2cap,btm,bta,gatt,可以看到这些代码的套路都是一样的:cb控制块+静态管理+状态机+api+callback。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
static void gatt_send_conn_cback(tGATT_TCB* p_tcb) {
uint8_t i;
tGATT_REG* p_reg;
uint16_t conn_id;

tGATT_BG_CONN_DEV* p_bg_dev = gatt_find_bg_dev(p_tcb->peer_bda);

// 通知所有的app,connection is up!
for (i = 0, p_reg = gatt_cb.cl_rcb; i < GATT_MAX_APPS; i++, p_reg++) {
if (p_reg->in_use) {
if (p_bg_dev && gatt_is_bg_dev_for_app(p_bg_dev, p_reg->gatt_if))
gatt_update_app_use_link_flag(p_reg->gatt_if, p_tcb, true, true);

if (p_reg->app_cb.p_conn_cb) {
// gatt的connection id出现了
conn_id = GATT_CREATE_CONN_ID(p_tcb->tcb_idx, p_reg->gatt_if);
// 回调上层APP
(*p_reg->app_cb.p_conn_cb)(p_reg->gatt_if, p_tcb->peer_bda, conn_id,
true, 0, p_tcb->transport);
}
}
}
...
}

结合log也可以看到:

1
2
3
4
5
6
7
8
9
10
11
[BT_APP] gatt_le_cb GATT BDA: 53f9...3f1c is conn, res:0x00, trans:2
[BT_APP] gatt_alloc_tcb,bda:53f9...3f1c, trans:2, idx:0
[BT_APP] btc evt 16, len 36
[BT_APP] btc evt 16, len 36
[BT_APP] btc evt 16, len 36
[BT_APP] SMDBG l2c smp_connect_cback bda:53f9...3f1c, conn:1, res:0x00, trans:2, state:0
[BT_APP] btc evt 5, len 280
[BT_APP] btc evt 8, len 280
[BT_APP] btif_gs: GATTS_CONNECT, ci:4, sif:4, bda:53f9...3f1c
[BT_APP] btif_gs: GATTS_CONNECT, ci:6, sif:6, bda:53f9...3f1c
[BT_APP] btif_gs: GATTS_CONNECT, ci:7, sif:7, bda:53f9...3f1c

具体GATT需要经过消息转发到btif层,这里先不细究。

小结:

ACL连接之后,L2CAP的lcb_pool中申请了一个控制块,设置了相关的一些属性,然后通知btm以及fixed chennel的上层应用(ATT和SMP)连接事件。

Channel Setup

以GATT主动连接来分析channel的建立和连接流程。GATT使用的是fixed channel,不过关系不大,流程是类似的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
bool GATT_Connect(tGATT_IF gatt_if, const RawAddress& bd_addr, bool is_direct,
tBT_TRANSPORT transport, bool opportunistic,
uint8_t initiating_phys) {
...

/* Make sure app is registered */
p_reg = gatt_get_regcb(gatt_if);

if (is_direct)
status = gatt_act_connect(p_reg, bd_addr, transport, opportunistic,
initiating_phys);
else {
....
}

return status;
}

bool gatt_act_connect(tGATT_REG* p_reg, const RawAddress& bd_addr,
tBT_TRANSPORT transport, bool opportunistic,
int8_t initiating_phys) {
...
p_tcb = gatt_find_tcb_by_addr(bd_addr, transport);
if (p_tcb != NULL) {
// 如果已有连接,则直接使用
ret = true;
st = gatt_get_ch_state(p_tcb);

/* before link down, another app try to open a GATT connection */
if (st == GATT_CH_OPEN && p_tcb->app_hold_link.empty() &&
transport == BT_TRANSPORT_LE) {
// 调用gatt_connect
if (!gatt_connect(bd_addr, p_tcb, transport, initiating_phys))
ret = false;
}
} else {
// 申请一个新的tcb
p_tcb = gatt_allocate_tcb_by_bdaddr(bd_addr, transport);
if (p_tcb != NULL) {
if (!gatt_connect(bd_addr, p_tcb, transport, initiating_phys)) {
LOG(ERROR) << "gatt_connect failed";
fixed_queue_free(p_tcb->pending_ind_q, NULL);
*p_tcb = tGATT_TCB();
} else
ret = true;
}
}
...
return ret;
}

bool gatt_connect(const RawAddress& rem_bda, tGATT_TCB* p_tcb,
tBT_TRANSPORT transport, uint8_t initiating_phys) {
bool 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, initiating_phys); //l2cap fixed channel连接
} else {
p_tcb->att_lcid = L2CA_ConnectReq(BT_PSM_ATT, rem_bda);
if (p_tcb->att_lcid != 0) gatt_ret = true;
}

return gatt_ret;
}

前面提到GATT是用的fixed channel,因此会调用到L2CAP的fixed channel 连接。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
bool L2CA_ConnectFixedChnl(uint16_t fixed_cid, const RawAddress& rem_bda,
uint8_t initiating_phys) {
...

// 检查CID合法性
if ((fixed_cid < L2CAP_FIRST_FIXED_CHNL) ||
(fixed_cid > L2CAP_LAST_FIXED_CHNL) ||
(l2cb.fixed_reg[fixed_cid - L2CAP_FIRST_FIXED_CHNL].pL2CA_FixedData_Cb ==
NULL)) {
L2CAP_TRACE_ERROR("%s() Invalid CID: 0x%04x", __func__, fixed_cid);
return (false);
}

// 检查是否蓝牙打开
if (!BTM_IsDeviceUp()) {
L2CAP_TRACE_WARNING("%s(0x%04x) - BTU not ready", __func__, fixed_cid);
return (false);
}

if (fixed_cid >= L2CAP_ATT_CID && fixed_cid <= L2CAP_SMP_CID)
transport = BT_TRANSPORT_LE;

tL2C_BLE_FIXED_CHNLS_MASK peer_channel_mask;

// 如果已经连到对端设备,检查是否支持该CID
p_lcb = l2cu_find_lcb_by_bd_addr(rem_bda, transport);
if (p_lcb != NULL) {
// LE的fixed channels是必选的,因此不需要检查对端的channel mash,直接使用local cached LE channel mask
if (transport == BT_TRANSPORT_LE)
peer_channel_mask = l2cb.l2c_ble_fixed_chnls_mask;
else
peer_channel_mask = p_lcb->peer_chnl_mask[0];

// 检查channel是否支持
if (!(peer_channel_mask & (1 << fixed_cid))) {
VLOG(2) << __func__ << " BDA " << rem_bda
<< StringPrintf(" CID:0x%04x not supported", fixed_cid);
return false;
}

// 获取一个ccb,并绑定到lcb上
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;
}

// 如果正在断开,需要先把ccb pending住,后面再重新启动连接
if (p_lcb->link_state == LST_DISCONNECTING) {
L2CAP_TRACE_DEBUG("$s() - link disconnecting: RETRY LATER", __func__);
/* Save ccb so it can be started after disconnect is finished */
p_lcb->p_pending_ccb =
p_lcb->p_fixed_ccbs[fixed_cid - L2CAP_FIRST_FIXED_CHNL];
return true;
}
// 这里回调给上层connected
(*l2cb.fixed_reg[fixed_cid - L2CAP_FIRST_FIXED_CHNL].pL2CA_FixedConn_Cb)(
fixed_cid, p_lcb->remote_bd_addr, true, 0, p_lcb->transport);
return true;
}

// 如果没有连接,则申请lcb
p_lcb = l2cu_allocate_lcb(rem_bda, false, transport);
if (p_lcb == NULL) {
L2CAP_TRACE_WARNING("%s(0x%04x) - no LCB", __func__, fixed_cid);
return false;
}

// 获取ccb,并绑定到lcb
if (!l2cu_initialize_fixed_ccb(
p_lcb, fixed_cid, &l2cb.fixed_reg[fixed_cid - L2CAP_FIRST_FIXED_CHNL]
.fixed_chnl_opts)) {
p_lcb->disc_reason = L2CAP_CONN_NO_RESOURCES;
L2CAP_TRACE_WARNING("%s(0x%04x) - no CCB", __func__, fixed_cid);
l2cu_release_lcb(p_lcb);
return false;
}

// 创建连接
if (!l2cu_create_conn(p_lcb, transport, initiating_phys)) {
L2CAP_TRACE_WARNING("%s() - create_conn failed", __func__);
l2cu_release_lcb(p_lcb);
return false;
}
return true;
}
  1. 检查通道合法性,以及蓝牙是否打开(对于LE设备,fixed channel是强制的)

  2. 如果已经连接到对端设备

    2.1. 检查channel是否支持

    2.2. 获取一个ccb,绑定到lcb上

    2.3. 如果链路处于disconnecting状态,则保存ccb到pending list,后面再连

    2.4. 否则回调给上层connected(?)

  3. 如果还没有连接,则申请lcb,申请ccb并绑定到lcb,然后创建连接。

这里面的一个关键函数是l2cu_initialize_fixed_ccb()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
bool l2cu_initialize_fixed_ccb(tL2C_LCB* p_lcb, uint16_t fixed_cid,
tL2CAP_FCR_OPTS* p_fcr) {
#if (L2CAP_NUM_FIXED_CHNLS > 0)
tL2C_CCB* p_ccb;

// 如果已经有ccb,则直接返回
p_ccb = p_lcb->p_fixed_ccbs[fixed_cid - L2CAP_FIRST_FIXED_CHNL];
if ((p_ccb != NULL) && p_ccb->in_use) {
return (true);
}
// 没有就申请一个新的ccb
p_ccb = l2cu_allocate_ccb(NULL, 0);
if (p_ccb == NULL) return (false);

alarm_cancel(p_lcb->l2c_lcb_timer);

// 设置参数
p_ccb->local_cid = fixed_cid;
p_ccb->remote_cid = fixed_cid;

p_ccb->is_flushable = false;

if (p_fcr) {
/* Set the FCR parameters. For now, we will use default pools */
p_ccb->our_cfg.fcr = p_ccb->peer_cfg.fcr = *p_fcr;

p_ccb->ertm_info.fcr_rx_buf_size = L2CAP_FCR_RX_BUF_SIZE;
p_ccb->ertm_info.fcr_tx_buf_size = L2CAP_FCR_TX_BUF_SIZE;
p_ccb->ertm_info.user_rx_buf_size = L2CAP_USER_RX_BUF_SIZE;
p_ccb->ertm_info.user_tx_buf_size = L2CAP_USER_TX_BUF_SIZE;

p_ccb->fcrb.max_held_acks = p_fcr->tx_win_sz / 3;
}

// ccb跟lcb绑定
p_lcb->p_fixed_ccbs[fixed_cid - L2CAP_FIRST_FIXED_CHNL] = p_ccb;
p_ccb->p_lcb = p_lcb;

// 通道就连接起来了
if (p_lcb->link_state == LST_CONNECTED) p_ccb->chnl_state = CST_OPEN;

p_ccb->fixed_chnl_idle_tout =
l2cb.fixed_reg[fixed_cid - L2CAP_FIRST_FIXED_CHNL].default_idle_tout;
#endif
return (true);
}

小结:

  • 为何没有看到channel连接的动作?原因是对于LE设备,fixed channel是强制的,因此,配置好一些参数属性,把lcb和ccb关联起来,就可以视为通道建立起来了。

数据发送

datasend

前面的函数调用比较简单,我们从l2c_csm_open()这个状态机处理函数开始分析。

这里主要是两个动作:1. 把数据入队;2. 发送数据。

1
2
3
4
case L2CEVT_L2CA_DATA_WRITE: /* Upper layer data to send */
l2c_enqueue_peer_data(p_ccb, (BT_HDR*)p_data);
l2c_link_check_send_pkts(p_ccb->p_lcb, NULL, NULL);
break;

l2c_enqueue_peer_data()这个函数的主要工作:

  1. 把组装好的p_buf,放到当前ccb的xmit_hold_q队列
  2. 检查当前channel的拥堵情况
  3. RR相关处理
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
void l2c_enqueue_peer_data(tL2C_CCB* p_ccb, BT_HDR* p_buf) {
uint8_t* p;

if (p_ccb->peer_cfg.fcr.mode != L2CAP_FCR_BASIC_MODE) {
p_buf->event = 0;
} else {
// 保存cid
p_buf->event = p_ccb->local_cid;

// 添加l2cap header
p_buf->offset -= L2CAP_PKT_OVERHEAD;
p_buf->len += L2CAP_PKT_OVERHEAD;

// 设置指向真正的数据
p = (uint8_t*)(p_buf + 1) + p_buf->offset;

// 设置header
UINT16_TO_STREAM(p, p_buf->len - L2CAP_PKT_OVERHEAD);
UINT16_TO_STREAM(p, p_ccb->remote_cid);
}

// 真正将组装好的p_buf入队
fixed_queue_enqueue(p_ccb->xmit_hold_q, p_buf);

// 检查当前的channel的拥堵情况
l2cu_check_channel_congestion(p_ccb);

#if (L2CAP_ROUND_ROBIN_CHANNEL_SERVICE == TRUE)
/* if new packet is higher priority than serving ccb and it is not overrun */
if ((p_ccb->p_lcb->rr_pri > p_ccb->ccb_priority) &&
(p_ccb->p_lcb->rr_serv[p_ccb->ccb_priority].quota > 0)) {
/* send out higher priority packet */
p_ccb->p_lcb->rr_pri = p_ccb->ccb_priority;
}
#endif

/* if we are doing a round robin scheduling, set the flag */
if (p_ccb->p_lcb->link_xmit_quota == 0) l2cb.check_round_robin = true;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
void l2cu_check_channel_congestion(tL2C_CCB* p_ccb) {
// 当前ccb中发送数据的综合
size_t q_count = fixed_queue_length(p_ccb->xmit_hold_q);

if (p_ccb->buff_quota != 0) {
// 当这个字段为TRUE,是否是真正的拥堵,需要继续推测
if (p_ccb->cong_sent) {
// 如果小于限额的一般,则表示不再拥堵
if (q_count <= (p_ccb->buff_quota / 2)) {
p_ccb->cong_sent = false;
if (p_ccb->p_rcb && p_ccb->p_rcb->api.pL2CA_CongestionStatus_Cb) {

// 避免递归调用,回调给上层并没有拥塞
l2cb.is_cong_cback_context = true;
(*p_ccb->p_rcb->api.pL2CA_CongestionStatus_Cb)(p_ccb->local_cid,
false);
l2cb.is_cong_cback_context = false;
}
#if (L2CAP_NUM_FIXED_CHNLS > 0)
else {
uint8_t xx;
for (xx = 0; xx < L2CAP_NUM_FIXED_CHNLS; xx++) {
if (p_ccb->p_lcb->p_fixed_ccbs[xx] == p_ccb) {
// 通知fixed channel
if (l2cb.fixed_reg[xx].pL2CA_FixedCong_Cb != NULL)
(*l2cb.fixed_reg[xx].pL2CA_FixedCong_Cb)(
p_ccb->p_lcb->remote_bd_addr, false);
break;
}
}
}
#endif
}
} else {
// 如果之前没有拥堵,但是现在拥堵了,也告诉应用
if (q_count > p_ccb->buff_quota) {
p_ccb->cong_sent = true;
if (p_ccb->p_rcb && p_ccb->p_rcb->api.pL2CA_CongestionStatus_Cb) {

(*p_ccb->p_rcb->api.pL2CA_CongestionStatus_Cb)(p_ccb->local_cid,
true);
}
#if (L2CAP_NUM_FIXED_CHNLS > 0)
else {
uint8_t xx;
for (xx = 0; xx < L2CAP_NUM_FIXED_CHNLS; xx++) {
if (p_ccb->p_lcb->p_fixed_ccbs[xx] == p_ccb) {
if (l2cb.fixed_reg[xx].pL2CA_FixedCong_Cb != NULL)
// 告诉fixed channel
(*l2cb.fixed_reg[xx].pL2CA_FixedCong_Cb)(
p_ccb->p_lcb->remote_bd_addr, true);
break;
}
}
}
#endif
}
}
}
}

数据入队了,继续看下l2c_link_check_send_pkts()发送数据这个函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
void l2c_link_check_send_pkts(tL2C_LCB* p_lcb, tL2C_CCB* p_ccb, BT_HDR* p_buf) {
int xx;
bool single_write = false;

// 一般是L2CAP的command
if (p_buf) {
if (p_ccb != NULL) {
p_buf->event = p_ccb->local_cid;
single_write = true;
} else
p_buf->event = 0;

p_buf->layer_specific = 0;
// command放到lcb的link_xmit_data_q队列中
list_append(p_lcb->link_xmit_data_q, p_buf);

// 如果发送没有窗体了,需要rr看看别的lcb有没有能发送的
if (p_lcb->link_xmit_quota == 0) {
if (p_lcb->transport == BT_TRANSPORT_LE)
l2cb.ble_check_round_robin = true;
else
l2cb.check_round_robin = true;
}
}

// 如果已经阻塞了,则返回
if (l2cb.is_cong_cback_context) return;

if ((p_lcb == NULL) || (p_lcb->link_xmit_quota == 0)) {
if (p_lcb == NULL)
p_lcb = l2cb.lcb_pool;
else if (!single_write)
p_lcb++;

// 循环lcb
for (xx = 0; xx < MAX_L2CAP_LINKS; xx++, p_lcb++) {
// 边界检查
if (p_lcb == &l2cb.lcb_pool[MAX_L2CAP_LINKS]) p_lcb = &l2cb.lcb_pool[0];

// 如果窗体满了,则啥也不做
if (((l2cb.controller_xmit_window == 0 ||
(l2cb.round_robin_unacked >= l2cb.round_robin_quota)) &&
(p_lcb->transport == BT_TRANSPORT_BR_EDR)) ||
(p_lcb->transport == BT_TRANSPORT_LE &&
(l2cb.ble_round_robin_unacked >= l2cb.ble_round_robin_quota ||
l2cb.controller_le_xmit_window == 0)))
continue;

if ((!p_lcb->in_use) || (p_lcb->partial_segment_being_sent) ||
(p_lcb->link_state != LST_CONNECTED) ||
(p_lcb->link_xmit_quota != 0) || (L2C_LINK_CHECK_POWER_MODE(p_lcb)))
continue;

// 检查是否有能发送的,先从lcb上取数据,如果有,则出队然后调用l2c_link_send_to_lower发送
if (!list_is_empty(p_lcb->link_xmit_data_q)) {
p_buf = (BT_HDR*)list_front(p_lcb->link_xmit_data_q);
list_remove(p_lcb->link_xmit_data_q, p_buf);
l2c_link_send_to_lower(p_lcb, p_buf, NULL);
} else if (single_write) {
// single_write则不循环
break;
}
// 再从ccb上取数据发送
else {
tL2C_TX_COMPLETE_CB_INFO cbi;
p_buf = l2cu_get_next_buffer_to_send(p_lcb, &cbi);
if (p_buf != NULL) {
l2c_link_send_to_lower(p_lcb, p_buf, &cbi);
}
}
}

// 如果窗体用完了,则不需要check rr了
if ((l2cb.controller_xmit_window > 0) &&
(l2cb.round_robin_unacked < l2cb.round_robin_quota) &&
(p_lcb->transport == BT_TRANSPORT_BR_EDR))
l2cb.check_round_robin = false;

if ((l2cb.controller_le_xmit_window > 0) &&
(l2cb.ble_round_robin_unacked < l2cb.ble_round_robin_quota) &&
(p_lcb->transport == BT_TRANSPORT_LE))
l2cb.ble_check_round_robin = false;
} else /* if this is not round-robin service */
{
// 这是正常发送数据的流程,如果有在分段,则本次停止
if ((p_lcb->partial_segment_being_sent) ||
(p_lcb->link_state != LST_CONNECTED) ||
(L2C_LINK_CHECK_POWER_MODE(p_lcb)))
return;

/* See if we can send anything from the link queue */
while (((l2cb.controller_xmit_window != 0 &&
(p_lcb->transport == BT_TRANSPORT_BR_EDR)) ||
(l2cb.controller_le_xmit_window != 0 &&
(p_lcb->transport == BT_TRANSPORT_LE))) &&
(p_lcb->sent_not_acked < p_lcb->link_xmit_quota)) {
if (list_is_empty(p_lcb->link_xmit_data_q)) break;
// 发送link上的包
p_buf = (BT_HDR*)list_front(p_lcb->link_xmit_data_q);
list_remove(p_lcb->link_xmit_data_q, p_buf);
if (!l2c_link_send_to_lower(p_lcb, p_buf, NULL)) break;
}

if (!single_write) {
/* See if we can send anything for any channel */
while (((l2cb.controller_xmit_window != 0 &&
(p_lcb->transport == BT_TRANSPORT_BR_EDR)) ||
(l2cb.controller_le_xmit_window != 0 &&
(p_lcb->transport == BT_TRANSPORT_LE))) &&
(p_lcb->sent_not_acked < p_lcb->link_xmit_quota)) {
tL2C_TX_COMPLETE_CB_INFO cbi;
// 发送ccb上的包
p_buf = l2cu_get_next_buffer_to_send(p_lcb, &cbi);
if (p_buf == NULL) break;

if (!l2c_link_send_to_lower(p_lcb, p_buf, &cbi)) break;
}
}

/* There is a special case where we have readjusted the link quotas and */
/* this link may have sent anything but some other link sent packets so */
/* so we may need a timer to kick off this link's transmissions. */
if ((!list_is_empty(p_lcb->link_xmit_data_q)) &&
(p_lcb->sent_not_acked < p_lcb->link_xmit_quota)) {
alarm_set_on_mloop(p_lcb->l2c_lcb_timer,
L2CAP_LINK_FLOW_CONTROL_TIMEOUT_MS,
l2c_lcb_timer_timeout, p_lcb);
}
}
}

这个函数比较复杂,总结一下:

  1. 优先发送link上的数据包,然后发送ccb上的数据包
  2. round-robin是指轮询各个lcb,如果还有窗口可以发送,则发送数据
  3. quota可以理解成TxWindow之类的一个参数,表示i可以发送的没有收到ack的数据包个数
  4. 最终调到l2c_link_send_to_lower()

l2c_link_send_to_lower()主要工作:

  1. 假设当前数据包p_buf小于acl包的最大值,sent_not_acked加1,整个L2CAP的 controller_xmit_window减1.然后通过L2C_LINK_SEND_ACL_DATA将此数据包发送出去。

  2. 假设当前数据包p_buf的长度大于ACL包的最大值,先看能分成几个分包(为了求得几个窗体能容下),然后窗体值减掉这些分包个数。然后将整个数据包交给L2C_LINK_SEND_ACL_DATA(大于ACL包长度),详细分包发送由H5(串口)部分来负责。