【android bluetooth 协议分析 01】【HCI 层介绍 3】【NUMBER_OF_COMPLETED_PACKETS 事件介绍】

发布于:2025-05-19 ⋅ 阅读:(21) ⋅ 点赞:(0)

1. 背景

我们在介绍 【android bluetooth 框架分析 02】【Module详解 8】【Controller 模块介绍】 中介绍 Controller 模块初始化流程时,在 Controller::impl::Start 函数中会看到 我们对 NUMBER_OF_COMPLETED_PACKETS 事件做了 专门的监听:

// system/gd/hci/controller.cc

	/*
	2. 注册 NumberOfCompletedPackets 回调
		1. 注册一个事件处理器,用于接收控制器发送的“已完成的数据包数”通知
		2. 该事件用于流控管理(host 根据完成数量决定是否继续发送数据)。
	*/
    hci_->RegisterEventHandler(
        EventCode::NUMBER_OF_COMPLETED_PACKETS, handler->BindOn(this, &Controller::impl::NumberOfCompletedPackets));

这里就专门介绍一下 NUMBER_OF_COMPLETED_PACKETS 事件在蓝牙协议中的作用。

2. NUMBER_OF_COMPLETED_PACKETS 事件介绍

我们会在 btsnoop 中看到如下 信息:


908	2025-04-24 15:56:16.700694	controller	host	HCI_EVT	8	Rcvd Number of Completed Packets

Bluetooth HCI Event - Number of Completed Packets
    Event Code: Number of Completed Packets (0x13)
    Parameter Total Length: 5
    Number of Connection Handles: 1
    Connection Handle: 0x0001
    Number of Completed Packets: 1

NUMBER_OF_COMPLETED_PACKETS 是一个 标准 HCI 事件,由 Controller(蓝牙芯片)发送给 Host(主机,通常是操作系统或上层协议栈),事件码为:0x13

一个 NUMBER_OF_COMPLETED_PACKETS 事件数据结构中包含:

字段 说明
Num_Handles 有多少个连接要上报完成数量
Connection_Handle[i] 第 i 个连接的句柄
Host_Num_Completed_Packets[i] 第 i 个连接完成的包数量(credits)

这个事件可以一次上报多个连接的完成情况。


1. 作用:

  1. 释放 Host Buffer(流控恢复)

蓝牙数据通过 ACL 数据包传输时,Host 必须跟踪发送出去的每一个包。如果控制器没有 buffer,Host 必须停下来不发。

  • Host 发送一个 ACL 数据包 → credits -1;
  • 当 Controller 成功发出这个包(或者丢弃),它就发 NUMBER_OF_COMPLETED_PACKETS 告诉 Host;
  • Host 收到后,credits +1,就可以继续发送更多的数据。

这种机制是流控的关键。

  1. 节省资源,避免 buffer overflow
  • 控制器中的 buffer 数量是有限的;
  • 如果没有 NUMBER_OF_COMPLETED_PACKETS 通知,Host 会不停地发数据,最终会 冲爆控制器 buffer,导致丢包或者崩溃
  • 所以这个事件起到“释放资源、允许继续发送”的作用。

2. 应用场景举例

场景:手机播放蓝牙音频时

  1. 手机作为 Host,不停地通过 ACL 通道发送 A2DP 音频数据包;
  2. 每发一个包就消耗一个 credit;
  3. 控制器发送完数据后,会定期发 NUMBER_OF_COMPLETED_PACKETS
  4. Host 收到后,就知道可以继续发更多的数据,音频才能持续不断。

可以把它类比为:

  • 快递公司(Controller) 告诉你(Host)哪些包裹已经送达;
  • 你就可以 清空这些包裹在仓库中占的空间(buffer)
  • 如果你之前限流了(怕仓库爆了),现在可以重新发货(继续发数据)。

3. 如果没有这个事件会怎样?

  • Host 无法知道 buffer 是否释放,可能发太多数据 → 崩溃
  • Host 一直等待 → 蓝牙数据传输停滞、卡顿
  • 流控失效 → 丢包、延迟、音频断续

所以它是整个蓝牙传输链路中 不可或缺的一环


4. 协议栈中如何处理它?

在蓝牙协议栈实现中(如 Android、BlueZ、Zephyr 等):

  • 通常会注册对 NUMBER_OF_COMPLETED_PACKETS 的监听;
  • 在事件到来时调用回调,更新 ACL buffer 状态;
  • 对应逻辑函数如:
// system/gd/hci/controller.cc

	/*
	2. 注册 NumberOfCompletedPackets 回调
		1. 注册一个事件处理器,用于接收控制器发送的“已完成的数据包数”通知
		2. 该事件用于流控管理(host 根据完成数量决定是否继续发送数据)。
	*/
    hci_->RegisterEventHandler(
        EventCode::NUMBER_OF_COMPLETED_PACKETS, handler->BindOn(this, &Controller::impl::NumberOfCompletedPackets));

当我们 收到 Controller 发给我们的 NUMBER_OF_COMPLETED_PACKETS 事件时,就会回调 Controller::impl::NumberOfCompletedPackets 方法。

3. NumberOfCompletedPackets

这个函数是用来处理 HCI 事件 的,事件的类型是 NUMBER_OF_COMPLETED_PACKETS —— 即:控制器(Controller)告诉主机(Host)某些 ACL 数据包已经传输完成,可以释放 buffer 了


  void NumberOfCompletedPackets(EventView event) {
    /*
    1. 检查回调是否存在
	    1. acl_credits_callback_ 是一个回调函数对象,代表着 ACL 管理器在监听 Controller 反馈的传输完成信息。
	    2. 如果这个回调是空的(即没有注册),说明此时没人关心 ACL buffer 的返回情况。
	    3. 打个警告日志,然后直接返回,不再处理事件。
    */
    if (acl_credits_callback_.IsEmpty()) {
      LOG_WARN("Received event when AclManager is not listening");
      return;
    }

    /*
    2. 解析事件数据结构
	    1. 使用静态工厂函数 NumberOfCompletedPacketsView::Create(event) 从原始 EventView 对象中构造出更专门的解析类 NumberOfCompletedPacketsView。
	    2. NumberOfCompletedPacketsView 是对原始 HCI Event 的封装,内部封装了解析逻辑,比如提取连接句柄、完成的数据包数量等
	    3. ASSERT(complete_view.IsValid()); 确保事件结构合法(比如长度、格式等没有问题)。如果不合法,直接 crash(这是调试期行为,正式版一般不会 ASSERT)
    */
    auto complete_view = NumberOfCompletedPacketsView::Create(event);
    ASSERT(complete_view.IsValid());


	/*
	3.遍历完成的连接句柄与 packet 数量
		1. connection_handle_:哪个连接的包完成了。
		2. host_num_of_completed_packets_:有多少个包完成了(主机现在可以回收 buffer)。
		3. 对每个连接调用回调函数
			1. 把 handle 和 credit 数量传给 acl_credits_callback_
			2. 这个回调函数由上层的 AclManager 注册,它的作用是:通知 buffer 管理器或流控管理器:可以释放 credits,继续发数据了。
			3. acl_monitor_credits_callback_ 是另一个可选的回调,可能用于监控、日志记录或调试。
				1. 如果存在,也会调用,传入相同的 handle 和 credits
	*/
    for (auto completed_packets : complete_view.GetCompletedPackets()) {
      uint16_t handle = completed_packets.connection_handle_;
      uint16_t credits = completed_packets.host_num_of_completed_packets_;
      acl_credits_callback_.Invoke(handle, credits);

	  
      if (!acl_monitor_credits_callback_.IsEmpty()) {
        acl_monitor_credits_callback_.Invoke(handle, credits);
      }
    }
  }

这个函数负责处理 HCI 层的 Number Of Completed Packets 事件。该事件是 Host Controller Interface(HCI)标准规定的一种机制,用于告诉 Host 哪些连接上的数据包已成功完成,可以释放对应的 buffer。

在 Bluetooth HCI 层,每个 ACL 数据包发送出去之前,Host 需要确认 Controller 是否还有空余 buffer。

  • Host 维护一个 credits(信用额度)计数器;
  • 每发送一个 ACL 包,credits -1;
  • 一旦收到 Controller 发来的 Number Of Completed Packets 事件,表示有包成功发出,credits +N;
  • 如果 credits 用完了,Host 就不能再发数据了(除非收到回报);
  • 这个机制就叫做 flow control(流控)

所以本函数的实质意义是:恢复 Host 的发送能力

1. 回调介绍

NumberOfCompletedPackets 中总共有 两个回调:

  1. acl_credits_callback_
  2. acl_monitor_credits_callback_

下面来看一下这两个回调 是在核实注册的

1. acl_credits_callback_ 回调

// system/gd/hci/controller.cc

  void register_completed_acl_packets_callback(CompletedAclPacketsCallback callback) {
    ASSERT(acl_credits_callback_.IsEmpty());
    acl_credits_callback_ = callback;
  }

void Controller::RegisterCompletedAclPacketsCallback(CompletedAclPacketsCallback cb) {
  CallOn(impl_.get(), &impl::register_completed_acl_packets_callback, cb);
}

acl_credits_callback_ 最终是通过调用 RegisterCompletedAclPacketsCallback 还设置的。

RegisterCompletedAclPacketsCallback 是谁调用触发的?

  • 答案是 AclManager

// system/gd/hci/acl_manager.cc

struct AclManager::impl {
  impl(const AclManager& acl_manager) : acl_manager_(acl_manager) {}

  void Start() {
    hci_layer_ = acl_manager_.GetDependency<HciLayer>();
    handler_ = acl_manager_.GetHandler();
    controller_ = acl_manager_.GetDependency<Controller>();
    round_robin_scheduler_ = new RoundRobinScheduler(handler_, controller_, hci_layer_->GetAclQueueEnd());
    ...
  }
  

在 AclManager::impl::Start 函数中,创建了一个 RoundRobinScheduler 对象。

// system/gd/hci/acl_manager/round_robin_scheduler.cc

RoundRobinScheduler::RoundRobinScheduler(
    os::Handler* handler, Controller* controller, common::BidiQueueEnd<AclBuilder, AclView>* hci_queue_end)
    : handler_(handler), controller_(controller), hci_queue_end_(hci_queue_end) {

	...

  controller_->RegisterCompletedAclPacketsCallback(handler->BindOn(this, &RoundRobinScheduler::incoming_acl_credits)); // 这里注册的
}

在 RoundRobinScheduler 构造函数中调用 Controller::RegisterCompletedAclPacketsCallback 将 RoundRobinScheduler::incoming_acl_credits 注册给 acl_credits_callback_

当收到 NUMBER_OF_COMPLETED_PACKETS 事件,就会触发对 incoming_acl_credits 函数的调用

1. incoming_acl_credits

用于处理 控制器通过 NUMBER_OF_COMPLETED_PACKETS 事件上报的 ACL 信道 credits(也就是控制器告诉 Host,哪些 ACL 包已完成,可以继续发更多包了)

// system/gd/hci/acl_manager/round_robin_scheduler.cc

void RoundRobinScheduler::incoming_acl_credits(uint16_t handle, uint16_t credits) {

  // 从 acl_queue_handlers_ 容器中查找对应的连接句柄 handle 的记录。这个容器保存着每个连接的发送状态信息。
  auto acl_queue_handler = acl_queue_handlers_.find(handle);
  if (acl_queue_handler == acl_queue_handlers_.end()) {
    // 如果这个 handle 没有记录,说明是个非法或意外的上报,直接返回,避免空指针操作。
    return;
  }

  /*
  更新发送记录:
	  1. number_of_sent_packets_ 是 Host 记录 “自己发送了多少还未完成的包”
	  2. credits 表示 Controller 告诉 Host 有这么多个包完成了;
	  3. 已发未完成的数量 >= 如果完成的数量,就正常减掉;
	  4. 否则说明 Controller 上报了超额的完成包数,可能是 bug 或同步异常,打印警告并重置为 0
  */
  if (acl_queue_handler->second.number_of_sent_packets_ >= credits) {
    acl_queue_handler->second.number_of_sent_packets_ -= credits;
  } else {
    LOG_WARN("receive more credits than we sent");
    acl_queue_handler->second.number_of_sent_packets_ = 0;
  }

  // 标记变量:记录在增加 credit 前是否为 0,如果是 0,说明之前因为 credit 不足而停发了数据包,后面需要重新启动 round-robin 调度。

  bool credit_was_zero = false;
  if (acl_queue_handler->second.connection_type_ == ConnectionType::CLASSIC) {
    // 是传统 Bluetooth BR/EDR(Classic)连接
    
    if (acl_packet_credits_ == 0) {
      // 如果之前为 0,说明之前已经 卡住 了,要恢复调度
      credit_was_zero = true;
    }
    // 增加 credit,表示可以继续发数据
    
    acl_packet_credits_ += credits;
    if (acl_packet_credits_ > max_acl_packet_credits_) {
      // 上限保护:不要超过最大允许的 credit 数量,避免 credit 超限导致逻辑错误
      acl_packet_credits_ = max_acl_packet_credits_;
      LOG_WARN("acl packet credits overflow due to receive %hx credits", credits);
    }
  } else {
    // 是 Bluetooth Low Energy(LE)连接, 同理,更新 LE ACL credit 数量
    
    if (le_acl_packet_credits_ == 0) {
      credit_was_zero = true;
    }
    le_acl_packet_credits_ += credits;
    if (le_acl_packet_credits_ > le_max_acl_packet_credits_) {
	  // 对 LE 信道也做 credit 溢出保护。
      le_acl_packet_credits_ = le_max_acl_packet_credits_;
      LOG_WARN("le acl packet credits overflow due to receive %hx credits", credits);
    }
  }
  if (credit_was_zero) {
    // 如果之前 credit 为 0(即我们因为没信用值停了发包),现在重新获得了 credit,就要 重启 round-robin 轮询机制,开始发送队列中的数据包。
    start_round_robin();
  }
}

它是 Bluetooth ACL 流控的核心环节之一:

  • 由 Controller 发出 NUMBER_OF_COMPLETED_PACKETS
  • Host 调用此函数更新状态;
  • 恢复调度发送挂起的数据包。

它确保了 Host 和 Controller 之间对 buffer 使用的精确同步,是高效传输和系统稳定运行的关键一环

2. acl_monitor_credits_callback_

// system/gd/hci/controller.cc

  void register_completed_monitor_acl_packets_callback(CompletedAclPacketsCallback callback) {
    ASSERT(acl_monitor_credits_callback_.IsEmpty());
    acl_monitor_credits_callback_ = callback;
  }
void Controller::RegisterCompletedMonitorAclPacketsCallback(CompletedAclPacketsCallback cb) {
  CallOn(impl_.get(), &impl::register_completed_monitor_acl_packets_callback, cb);
}


网站公告

今日签到

点亮在社区的每一天
去签到