在数据传输协议中,稳定可靠的传输层(带流空、分包组包、重传等功能)是必不可少的。通过分析蓝牙中的L2CAP层,可以借鉴里面的数据传输和链路管理的设计思想,之后根据需要设计适合自己系统的传输层。
本文介绍蓝牙L2CAP相关内容,主要分为两个部分:一是对 core spec 中L2CAP部分的解读,二是对Bluedroid L2CAP源码的简单分析。
Spec解读 L2CAP的作用 L2CAP是Logic and Link Control and Adapter Protocol的缩写,即逻辑链路控制和适配协议。
L2CAP向上层提供面向连接和无连接的数据服务,并提供多协议功能和分割重组操作。
L2CAP允许上层协议和应用软件传输和接收最大长度为64KB的L2CAP数据包。
L2CAP架构
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)
实际上用得最多的就是这种格式。
Retransmission/FlowControl/Streaming Mode 面向连接的数据包格式 有两种帧:I-frame和S-frame
I-frame,用来传信息的帧,叫做Information frame
S-frame,用来应答和请求重传I-frame的帧,叫做Supervisor-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。
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
L2 Credit Base Flow Control Mode和Enhanced Credit Base Flow Control Mode 面向连接的数据包格式(K-frame)
SDU的两字节 + Information Payload Length
第一包才有SDU Length字段。所以第一个K-frame时,PDU Length = 2 + Information Payload Length;后续的 PDU Length = Information Payload Length
表示总的数据长度。
信令包格式(C-frame)
注意,信令包是在特定的CID上传输的。
信令包的命令号:
状态机 这里有两层状态机,其中CONFIG STATE有很多个子状态。
一般流程 配置流程 主要是配置MTU, Flush Timeout,QoS,流控和重传,FCS(Frame Check Sequence)、扩展流控选项、扩展Window Size。
拆包和组包 这里指的是,一个L2CAP frame怎么拆成多个HCI 的 ACL Data Packet。这里其实是HCI层的工作。
组包是拆包的逆过程。
SDU封装 这里才是把上层传下来的L2CAP SDU拆成多个frame。一个SDU可能需要多个I-frame才装得下,所以得拆开。
第一包带SDU Length字段,这样就可以知道整个SDU的长度,后面跟多个I-frame,把payload组起来可以得到完整的SDU。
流控和重传 首先看两个图:
发送端:
这里涉及了一些控制变量:
TxSeq
发送帧序号
NextTxSeq
即将发送的帧序号。每发送一帧,NextTxSeq自加1.
不能超过ExpectedAckSeq+TxWindow。因为TxWindow是接收端容许发送端最多能发送的还没有收到ack的帧的数目。
在重传模式和流控模式中,TxWindow在132之间,Enhanced重传模式为163
ExpectedAckSeq
就是最后收到ACK的TxSeq+1。
接收端:
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。
Link Setup 当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 { 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; p_lcb = l2cu_find_lcb_by_bd_addr(bda, BT_TRANSPORT_LE); if (!p_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 { 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 ; } } } 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; 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; l2cu_process_fixed_chnl_resp(p_lcb); } 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; for (int xx = 0 ; xx < L2CAP_NUM_FIXED_CHNLS; xx++) { 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]) 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。
而它们的回调函数分别是哪里注册的呢?是通过 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; fixed_reg.default_idle_tout = 0xffff ; 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) { if (p_tcb) { ... } else { 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; 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); 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) { conn_id = GATT_CREATE_CONN_ID(p_tcb->tcb_idx, p_reg->gatt_if); (*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: 53f 9...3f 1c is conn, res:0x00 , trans:2 [BT_APP] gatt_alloc_tcb,bda:53f 9...3f 1c, 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:53f 9...3f 1c, 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:53f 9...3f 1c [BT_APP] btif_gs: GATTS_CONNECT, ci:6 , sif:6 , bda:53f 9...3f 1c [BT_APP] btif_gs: GATTS_CONNECT, ci:7 , sif:7 , bda:53f 9...3f 1c
具体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) { ... 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); if (st == GATT_CH_OPEN && p_tcb->app_hold_link.empty() && transport == BT_TRANSPORT_LE) { if (!gatt_connect(bd_addr, p_tcb, transport, initiating_phys)) ret = false ; } } else { 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); } 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) { ... 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; p_lcb = l2cu_find_lcb_by_bd_addr(rem_bda, transport); if (p_lcb != NULL ) { if (transport == BT_TRANSPORT_LE) peer_channel_mask = l2cb.l2c_ble_fixed_chnls_mask; else peer_channel_mask = p_lcb->peer_chnl_mask[0 ]; if (!(peer_channel_mask & (1 << fixed_cid))) { VLOG(2 ) << __func__ << " BDA " << rem_bda << StringPrintf(" CID:0x%04x not supported" , fixed_cid); return false ; } 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 (p_lcb->link_state == LST_DISCONNECTING) { L2CAP_TRACE_DEBUG("$s() - link disconnecting: RETRY LATER" , __func__); p_lcb->p_pending_ccb = p_lcb->p_fixed_ccbs[fixed_cid - L2CAP_FIRST_FIXED_CHNL]; return 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); return true ; } 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 ; } 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 ; }
检查通道合法性,以及蓝牙是否打开(对于LE设备,fixed channel是强制的)
如果已经连接到对端设备
2.1. 检查channel是否支持
2.2. 获取一个ccb,绑定到lcb上
2.3. 如果链路处于disconnecting状态,则保存ccb到pending list,后面再连
2.4. 否则回调给上层connected(?)
如果还没有连接,则申请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; p_ccb = p_lcb->p_fixed_ccbs[fixed_cid - L2CAP_FIRST_FIXED_CHNL]; if ((p_ccb != NULL ) && p_ccb->in_use) { return (true ); } 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) { 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 ; } 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关联起来,就可以视为通道建立起来了。
数据发送
前面的函数调用比较简单,我们从l2c_csm_open()这个状态机处理函数开始分析。
这里主要是两个动作:1. 把数据入队;2. 发送数据。
1 2 3 4 case L2CEVT_L2CA_DATA_WRITE: 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()这个函数的主要工作:
把组装好的p_buf,放到当前ccb的xmit_hold_q队列
检查当前channel的拥堵情况
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 { p_buf->event = p_ccb->local_cid; p_buf->offset -= L2CAP_PKT_OVERHEAD; p_buf->len += L2CAP_PKT_OVERHEAD; p = (uint8_t *)(p_buf + 1 ) + p_buf->offset; UINT16_TO_STREAM(p, p_buf->len - L2CAP_PKT_OVERHEAD); UINT16_TO_STREAM(p, p_ccb->remote_cid); } fixed_queue_enqueue(p_ccb->xmit_hold_q, p_buf); l2cu_check_channel_congestion(p_ccb); #if (L2CAP_ROUND_ROBIN_CHANNEL_SERVICE == TRUE) if ((p_ccb->p_lcb->rr_pri > p_ccb->ccb_priority) && (p_ccb->p_lcb->rr_serv[p_ccb->ccb_priority].quota > 0 )) { p_ccb->p_lcb->rr_pri = p_ccb->ccb_priority; } #endif 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) { size_t q_count = fixed_queue_length(p_ccb->xmit_hold_q); if (p_ccb->buff_quota != 0 ) { 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) { 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 ) (*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 ; 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 ; list_append(p_lcb->link_xmit_data_q, p_buf); 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++; 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 ; 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) { break ; } 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); } } } 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 ((p_lcb->partial_segment_being_sent) || (p_lcb->link_state != LST_CONNECTED) || (L2C_LINK_CHECK_POWER_MODE(p_lcb))) return ; 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 ; 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) { 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; 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 ; } } 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); } } }
这个函数比较复杂,总结一下:
优先发送link上的数据包,然后发送ccb上的数据包
round-robin是指轮询各个lcb,如果还有窗口可以发送,则发送数据
quota可以理解成TxWindow之类的一个参数,表示i可以发送的没有收到ack的数据包个数
最终调到l2c_link_send_to_lower()
l2c_link_send_to_lower()主要工作:
假设当前数据包p_buf小于acl包的最大值,sent_not_acked加1,整个L2CAP的 controller_xmit_window减1.然后通过L2C_LINK_SEND_ACL_DATA将此数据包发送出去。
假设当前数据包p_buf的长度大于ACL包的最大值,先看能分成几个分包(为了求得几个窗体能容下),然后窗体值减掉这些分包个数。然后将整个数据包交给L2C_LINK_SEND_ACL_DATA(大于ACL包长度),详细分包发送由H5(串口)部分来负责。