bt-l2cap 深入理解重点接口 l2c_link_check_send_pkts

这个接口之所以重要,是因为它是对应到发送接口的核心处理逻辑。

复制代码
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);
      }
    }
}
相关推荐
babytiger3 小时前
ble扫描相关的问题,蓝牙 MAC 是否可以确定厂商?
蓝牙·ble
summerkissyou19871 天前
android -wifi/蓝牙-常见面试题
android·wifi·bluetooth
whik119414 天前
ESP32-C3-DevKitM-1开发板深度上手评测
wifi·嵌入式·esp32·arduino·蓝牙·开发板·乐鑫
Darkershadow15 天前
蓝牙学习之发送 Mesh Provisioning Service advertising
学习·蓝牙·ble·mesh
byte轻骑兵18 天前
从HCI报文透视LE Audio重连流程(3):音频流建立、同步与终止
音视频·蓝牙·le audio·cig/cis·广播音频
summerkissyou198723 天前
android-蓝牙-广播启动-startAdvertising和startAdvertisingSet区别
android·蓝牙
Darkershadow1 个月前
蓝牙学习之Time Set
python·学习·蓝牙·ble·mesh
榕树子1 个月前
【蓝牙】安全密钥如何生成:蓝牙Mesh网络的安全基石
安全·蓝牙
Ar呐1 个月前
软考网规篇之无线通信网——无线个域网蓝牙和Zigbee、移动通信和5G
5g·蓝牙·zigbee·高级软考·网络规划设计师