这个接口之所以重要,是因为它是对应到发送接口的核心处理逻辑。
void l2c_link_check_send_pkts(tL2C_LCB* p_lcb, uint16_t local_cid, BT_HDR* p_buf)
前提背景知识: 在l2cap 层,会为了每个远程设备的连接,维护一个 tL2C_LCB,里面维护了很多和链路相关的信息,例如地址、transport、acl handle等等信息。
调用该接口的主要有两类,第一类当然是 应用要发送一包新的数据,第二类是controller 反馈host哪些数据已经被成功送出去了时会调用该接口。对于第二类可以理解为一个反馈,这个反馈和第一类组成了一个闭环,同时也为了能够触发送出去之前遗留的包。
先说第一类调用,假设dut 要发送一包数据给remoter dut,会首先调用该接口,例如
void l2cu_send_peer_connect_req(tL2C_CCB* p_ccb) {
BT_HDR* p_buf;
uint8_t* p;
/* Create an identifier for this packet */
p_ccb->p_lcb->signal_id++;
l2cu_adj_id(p_ccb->p_lcb);
p_ccb->local_id = p_ccb->p_lcb->signal_id;
p_buf = l2cu_build_header(p_ccb->p_lcb, L2CAP_CONN_REQ_LEN,
L2CAP_CMD_CONN_REQ, p_ccb->local_id);
if (p_buf == NULL) {
L2CAP_TRACE_WARNING("L2CAP - no buffer for conn_req");
return;
}
p = (uint8_t*)(p_buf + 1) + L2CAP_SEND_CMD_OFFSET + HCI_DATA_PREAMBLE_SIZE +
L2CAP_PKT_OVERHEAD + L2CAP_CMD_OVERHEAD;
UINT16_TO_STREAM(p, p_ccb->p_rcb->real_psm);
UINT16_TO_STREAM(p, p_ccb->local_cid);
l2c_link_check_send_pkts(p_ccb->p_lcb, 0, p_buf);
}
在 l2c_link_check_send_pkts 接口中,首先要做的事情如下:
{
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 (p_lcb->link_xmit_quota != 0), 将该lcb 下的数据包最大量发送出去
{
lcb(link control block)下的 link_xmit_data_q:
这是链路级别的待发数据包队列。
主要用于缓存L2CAP信令信道(如CID=1,CID=5)的数据包,也就是协议栈内部的控制/信令包。
这些包直接与链路相关,不属于某个特定的上层应用信道。
ccb(channel control block)下的 xmit_hold_q:
这是信道级别的待发数据包队列。
用于缓存上层应用(如ATT、RFCOMM、A2DP等)通过L2CAP信道发送的数据包。
每个ccb对应一个L2CAP信道(如ATT、RFCOMM等),xmit_hold_q只缓存该信道的数据。
数据包流转关系:
ccb的xmit_hold_q中的包,发送时会直接通过链路,不会再进入lcb的link_xmit_data_q。
lcb的link_xmit_data_q只用于L2CAP信令信道的数据包。
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)) {
LOG_DEBUG("No transmit data, skipping");
break;
}
LOG_DEBUG("Sending to lower layer");
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);
}
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)) {
p_buf = l2cu_get_next_buffer_to_send(p_lcb);
if (p_buf == NULL) {
LOG_DEBUG("No next buffer, skipping");
break;
}
LOG_DEBUG("Sending to lower layer");
l2c_link_send_to_lower(p_lcb, p_buf);
}
}
接下来对于满足条件 if (p_lcb->link_xmit_quota == 0),对每一个lcb 轮询,争取每个都能发送出去一个包。
{
p_lcb++; // 这里为何要这样操作,可以考虑这样一种极端场景,即 l2cb.round_robin_quota = 1,然而 link_xmit_quota == 0 的 lcb 有 2 个,假设每个lcb 下都有一个待发数据包。有了这行代码就可以让这两个 lcb 中的这包数据都可以发送出去。 过程类似于 lcb1.pkt sent out ==> controller 告诉host 发送完毕 ==> lcb2.pkt sent out ==> controller 告诉host 发送完毕。
for (int xx = 0; xx < MAX_L2CAP_LINKS; xx++, p_lcb++) {
/* Check for wraparound */
if (p_lcb == &l2cb.lcb_pool[MAX_L2CAP_LINKS]) p_lcb = &l2cb.lcb_pool[0];
/* If controller window is full, nothing to do */
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))) {
LOG_DEBUG("Skipping lcb %d due to controller window full", xx);
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))) {
LOG_DEBUG("Skipping lcb %d due to quota", xx);
continue;
}
/* See if we can send anything from the Link Queue */
if (!list_is_empty(p_lcb->link_xmit_data_q)) {
LOG_DEBUG("Sending to lower layer");
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);
} else if (single_write) {
/* If only doing one write, break out */
LOG_DEBUG("single_write is true, skipping");
break;
}
/* If nothing on the link queue, check the channel queue */
else {
LOG_DEBUG("Check next buffer");
p_buf = l2cu_get_next_buffer_to_send(p_lcb);
if (p_buf != NULL) {
LOG_DEBUG("Sending next buffer");
l2c_link_send_to_lower(p_lcb, p_buf);
}
}
}
下面是说当 没有 耗光了所有round-robin的额度,会将 check_round_robin 设定为false
/* If we finished without using up our quota, no need for a safety check */
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;
}
接下来拆解 l2c_link_send_to_lower 接口
static void l2c_link_send_to_lower(tL2C_LCB* p_lcb, BT_HDR* p_buf) {
if (p_lcb->transport == BT_TRANSPORT_BR_EDR) {
l2c_link_send_to_lower_br_edr(p_lcb, p_buf);
} else {
l2c_link_send_to_lower_ble(p_lcb, p_buf);
}
}
因为 ble 和 edr 都有属于各自的 acl buffer 数量监测和 round-robin 监测,非常相似,只需要看懂一个即可对另一处也能理解了,下面以ble 做说明。
static void l2c_link_send_to_lower_ble(tL2C_LCB* p_lcb, BT_HDR* p_buf) {
const uint16_t link_xmit_quota = p_lcb->link_xmit_quota;
if (link_xmit_quota == 0) {
l2cb.ble_round_robin_unacked++;
}
p_lcb->sent_not_acked++;
p_buf->layer_specific = 0;
l2cb.controller_le_xmit_window--;
acl_send_data_packet_ble(p_lcb->remote_bd_addr, p_buf);// 此处不再对此深入,可以简单理解她会送到 hci 层即可完工。
LOG_DEBUG("TotalWin=%d,Hndl=0x%x,Quota=%d,Unack=%d,RRQuota=%d,RRUnack=%d",
l2cb.controller_le_xmit_window, p_lcb->Handle(),
p_lcb->link_xmit_quota, p_lcb->sent_not_acked,
l2cb.ble_round_robin_quota, l2cb.ble_round_robin_unacked);
}
接下来第二类调用,controller 通知host 数据已经真正发送出去。
l2c_link_process_num_completed_pkts
{
首先知道是哪个 acl handle 的几个包已经发送出去了。
p_lcb = l2cu_find_lcb_by_handle(handle); 接下来进行资源回收。
{
if (p_lcb && (p_lcb->transport == BT_TRANSPORT_LE))
l2cb.controller_le_xmit_window += num_sent;
else {
/* Maintain the total window to the controller */
l2cb.controller_xmit_window += num_sent;
}
/* If doing round-robin, adjust communal counts */
if (p_lcb->link_xmit_quota == 0) {
if (p_lcb->transport == BT_TRANSPORT_LE) {
/* Don't go negative */
if (l2cb.ble_round_robin_unacked > num_sent)
l2cb.ble_round_robin_unacked -= num_sent;
else
l2cb.ble_round_robin_unacked = 0;
} else {
/* Don't go negative */
if (l2cb.round_robin_unacked > num_sent)
l2cb.round_robin_unacked -= num_sent;
else
l2cb.round_robin_unacked = 0;
}
}
/* Don't go negative */
if (p_lcb->sent_not_acked > num_sent)
p_lcb->sent_not_acked -= num_sent;
else
p_lcb->sent_not_acked = 0;
l2c_link_check_send_pkts(p_lcb, 0, NULL); // 这里是为了 让 该 lcb 的剩余包尽可能多的发送出去。
如下为何是用 高优先级 来触发呢,主要是考虑到 在上次发送的时刻到这儿的时刻期间,有 link_xmit_quota==0 的lcb 有缓存 新的待发数据包,而且正好 round-robin 又有额度,那么下面的调用会将待发数据包发送出去。
/* If we were doing round-robin for low priority links, check 'em */
if ((p_lcb->acl_priority == L2CAP_PRIORITY_HIGH) &&
(l2cb.check_round_robin) &&
(l2cb.round_robin_unacked < l2cb.round_robin_quota)) {
l2c_link_check_send_pkts(NULL, 0, NULL);
}
if ((p_lcb->transport == BT_TRANSPORT_LE) &&
(p_lcb->acl_priority == L2CAP_PRIORITY_HIGH) &&
((l2cb.ble_check_round_robin) &&
(l2cb.ble_round_robin_unacked < l2cb.ble_round_robin_quota))) {
l2c_link_check_send_pkts(NULL, 0, NULL);
}
}
}