Skip to content

Commit

Permalink
pw_bluetooth_proxy: Support queueing + L2CAP credits
Browse files Browse the repository at this point in the history
- Support queueing L2CAP packets if ACL send credits are unavailable.
- Support L2CAP CoC credit-based control flow.
- Implement L2CAP signaling channels to handle
  L2CAP_FLOW_CONTROL_CREDIT_IND packets.
- Add basic L2CAP channel.

Bug: 360929142, 360934030
Change-Id: Ib7a2022538f89a579fa096d59f576663c8a8fd18
Reviewed-on: https://pigweed-review.googlesource.com/c/pigweed/pigweed/+/237263
Docs-Not-Needed: Ali Saeed <saeedali@google.com>
Reviewed-by: David Rees <drees@google.com>
Lint: Lint 🤖 <android-build-ayeaye@system.gserviceaccount.com>
Commit-Queue: Ali Saeed <saeedali@google.com>
  • Loading branch information
acsaeed authored and CQ Bot Account committed Nov 16, 2024
1 parent 11ca404 commit 53d5391
Show file tree
Hide file tree
Showing 26 changed files with 1,181 additions and 140 deletions.
5 changes: 4 additions & 1 deletion pw_bluetooth_proxy/Android.bp
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,14 @@ cc_library_static {
host_supported: true,
srcs: [
"acl_data_channel.cc",
"basic_l2cap_channel.cc",
"gatt_notify_channel.cc",
"h4_storage.cc",
"l2cap_coc.cc",
"l2cap_channel_manager.cc",
"l2cap_coc.cc",
"l2cap_leu_signaling_channel.cc",
"l2cap_read_channel.cc",
"l2cap_signaling_channel.cc",
"l2cap_write_channel.cc",
"proxy_host.cc",
],
Expand Down
6 changes: 6 additions & 0 deletions pw_bluetooth_proxy/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,19 @@ cc_library(
name = "pw_bluetooth_proxy",
srcs = [
"acl_data_channel.cc",
"basic_l2cap_channel.cc",
"gatt_notify_channel.cc",
"h4_storage.cc",
"l2cap_channel_manager.cc",
"l2cap_coc.cc",
"l2cap_leu_signaling_channel.cc",
"l2cap_read_channel.cc",
"l2cap_signaling_channel.cc",
"l2cap_write_channel.cc",
"proxy_host.cc",
],
hdrs = [
"public/pw_bluetooth_proxy/basic_l2cap_channel.h",
"public/pw_bluetooth_proxy/gatt_notify_channel.h",
"public/pw_bluetooth_proxy/h4_packet.h",
"public/pw_bluetooth_proxy/internal/acl_data_channel.h",
Expand All @@ -39,7 +43,9 @@ cc_library(
"public/pw_bluetooth_proxy/internal/hci_transport.h",
"public/pw_bluetooth_proxy/internal/l2cap_channel_manager.h",
"public/pw_bluetooth_proxy/internal/l2cap_coc_internal.h",
"public/pw_bluetooth_proxy/internal/l2cap_leu_signaling_channel.h",
"public/pw_bluetooth_proxy/internal/l2cap_read_channel.h",
"public/pw_bluetooth_proxy/internal/l2cap_signaling_channel.h",
"public/pw_bluetooth_proxy/internal/l2cap_write_channel.h",
"public/pw_bluetooth_proxy/l2cap_coc.h",
"public/pw_bluetooth_proxy/proxy_host.h",
Expand Down
6 changes: 6 additions & 0 deletions pw_bluetooth_proxy/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ pw_test_group("tests") {
pw_source_set("pw_bluetooth_proxy") {
public_configs = [ ":public_include_path" ]
public = [
"public/pw_bluetooth_proxy/basic_l2cap_channel.h",
"public/pw_bluetooth_proxy/gatt_notify_channel.h",
"public/pw_bluetooth_proxy/h4_packet.h",
"public/pw_bluetooth_proxy/internal/acl_data_channel.h",
Expand All @@ -48,7 +49,9 @@ pw_source_set("pw_bluetooth_proxy") {
"public/pw_bluetooth_proxy/internal/hci_transport.h",
"public/pw_bluetooth_proxy/internal/l2cap_channel_manager.h",
"public/pw_bluetooth_proxy/internal/l2cap_coc_internal.h",
"public/pw_bluetooth_proxy/internal/l2cap_leu_signaling_channel.h",
"public/pw_bluetooth_proxy/internal/l2cap_read_channel.h",
"public/pw_bluetooth_proxy/internal/l2cap_signaling_channel.h",
"public/pw_bluetooth_proxy/internal/l2cap_write_channel.h",
"public/pw_bluetooth_proxy/l2cap_coc.h",
"public/pw_bluetooth_proxy/proxy_host.h",
Expand All @@ -73,11 +76,14 @@ pw_source_set("pw_bluetooth_proxy") {
deps = [ dir_pw_log ]
sources = [
"acl_data_channel.cc",
"basic_l2cap_channel.cc",
"gatt_notify_channel.cc",
"h4_storage.cc",
"l2cap_channel_manager.cc",
"l2cap_coc.cc",
"l2cap_leu_signaling_channel.cc",
"l2cap_read_channel.cc",
"l2cap_signaling_channel.cc",
"l2cap_write_channel.cc",
"proxy_host.cc",
]
Expand Down
6 changes: 6 additions & 0 deletions pw_bluetooth_proxy/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ endif()

pw_add_library(pw_bluetooth_proxy STATIC
HEADERS
public/pw_bluetooth_proxy/basic_l2cap_channel.h
public/pw_bluetooth_proxy/gatt_notify_channel.h
public/pw_bluetooth_proxy/h4_packet.h
public/pw_bluetooth_proxy/internal/acl_data_channel.h
Expand All @@ -34,7 +35,9 @@ pw_add_library(pw_bluetooth_proxy STATIC
public/pw_bluetooth_proxy/internal/l2cap_channel_manager.h
public/pw_bluetooth_proxy/internal/l2cap_coc_internal.h
public/pw_bluetooth_proxy/internal/l2cap_read_channel.h
public/pw_bluetooth_proxy/internal/l2cap_signaling_channel.h
public/pw_bluetooth_proxy/internal/l2cap_write_channel.h
public/pw_bluetooth_proxy/internal/l2cap_leu_signaling_channel.h
public/pw_bluetooth_proxy/l2cap_coc.h
public/pw_bluetooth_proxy/proxy_host.h
PUBLIC_INCLUDES
Expand All @@ -57,12 +60,15 @@ pw_add_library(pw_bluetooth_proxy STATIC
pw_sync.mutex
SOURCES
acl_data_channel.cc
basic_l2cap_channel.cc
gatt_notify_channel.cc
h4_storage.cc
l2cap_channel_manager.cc
l2cap_coc.cc
l2cap_read_channel.cc
l2cap_signaling_channel.cc
l2cap_write_channel.cc
l2cap_leu_signaling_channel.cc
proxy_host.cc
)

Expand Down
91 changes: 54 additions & 37 deletions pw_bluetooth_proxy/acl_data_channel.cc
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
#include "lib/stdcompat/utility.h"
#include "pw_bluetooth/emboss_util.h"
#include "pw_bluetooth/hci_data.emb.h"
#include "pw_bluetooth_proxy/internal/l2cap_channel_manager.h"
#include "pw_containers/algorithm.h" // IWYU pragma: keep
#include "pw_log/log.h"
#include "pw_status/status.h"
Expand All @@ -30,7 +31,7 @@ void AclDataChannel::Reset() {
initialized_ = false;
proxy_max_le_acl_packets_ = 0;
proxy_pending_le_acl_packets_ = 0;
active_connections_.clear();
active_le_acl_connections_.clear();
}

template <class EventT>
Expand Down Expand Up @@ -66,7 +67,11 @@ void AclDataChannel::ProcessSpecificLEReadBufferSizeCommandCompleteEvent(
le_acl_credits_to_reserve_,
controller_max_le_acl_packets);
}

credit_allocation_mutex_.unlock();

// Send packets that may have queued before we acquired any LE ACL credits.
l2cap_channel_manager_.DrainWriteChannelQueues();
}

template void
Expand All @@ -93,6 +98,7 @@ void AclDataChannel::HandleNumberOfCompletedPacketsEvent(
}
credit_allocation_mutex_.lock();
bool should_send_to_host = false;
bool did_reclaim_credits = false;
for (uint8_t i = 0; i < nocp_event->num_handles().Read(); ++i) {
uint16_t handle = nocp_event->nocp_data()[i].connection_handle().Read();
uint16_t num_completed_packets =
Expand All @@ -102,19 +108,25 @@ void AclDataChannel::HandleNumberOfCompletedPacketsEvent(
continue;
}

AclConnection* connection_ptr = FindConnection(handle);
LeAclConnection* connection_ptr = FindLeAclConnection(handle);
if (!connection_ptr) {
// Credits for connection we are not tracking, so should pass event on to
// host.
// Credits for connection we are not tracking, so should pass event on
// to host.
should_send_to_host = true;
continue;
}

// Reclaim proxy's credits before event is forwarded to host
uint16_t num_pending_packets = connection_ptr->num_pending_packets();
uint16_t num_reclaimed =
std::min(num_completed_packets, connection_ptr->num_pending_packets);
std::min(num_completed_packets, num_pending_packets);
if (num_reclaimed > 0) {
did_reclaim_credits = true;
}
proxy_pending_le_acl_packets_ -= num_reclaimed;
connection_ptr->num_pending_packets -= num_reclaimed;
connection_ptr->set_num_pending_packets(num_pending_packets -
num_reclaimed);

uint16_t credits_remaining = num_completed_packets - num_reclaimed;
nocp_event->nocp_data()[i].num_completed_packets().Write(credits_remaining);
if (credits_remaining > 0) {
Expand All @@ -123,6 +135,10 @@ void AclDataChannel::HandleNumberOfCompletedPacketsEvent(
}
}
credit_allocation_mutex_.unlock();

if (did_reclaim_credits) {
l2cap_channel_manager_.DrainWriteChannelQueues();
}
if (should_send_to_host) {
hci_transport_.SendToHost(std::move(h4_packet));
}
Expand All @@ -140,10 +156,10 @@ void AclDataChannel::HandleDisconnectionCompleteEvent(
hci_transport_.SendToHost(std::move(h4_packet));
return;
}
credit_allocation_mutex_.lock();

credit_allocation_mutex_.lock();
uint16_t conn_handle = dc_event->connection_handle().Read();
AclConnection* connection_ptr = FindConnection(conn_handle);
LeAclConnection* connection_ptr = FindLeAclConnection(conn_handle);
if (!connection_ptr) {
credit_allocation_mutex_.unlock();
hci_transport_.SendToHost(std::move(h4_packet));
Expand All @@ -152,17 +168,17 @@ void AclDataChannel::HandleDisconnectionCompleteEvent(

emboss::StatusCode status = dc_event->status().Read();
if (status == emboss::StatusCode::SUCCESS) {
if (connection_ptr->num_pending_packets > 0) {
if (connection_ptr->num_pending_packets() > 0) {
PW_LOG_WARN(
"Proxy viewed disconnect (reason: %#.2hhx) for connection %#.4hx "
"with packets in flight. Releasing associated credits",
cpp23::to_underlying(dc_event->reason().Read()),
conn_handle);
proxy_pending_le_acl_packets_ -= connection_ptr->num_pending_packets;
proxy_pending_le_acl_packets_ -= connection_ptr->num_pending_packets();
}
active_connections_.erase(connection_ptr);
active_le_acl_connections_.erase(connection_ptr);
} else {
if (connection_ptr->num_pending_packets > 0) {
if (connection_ptr->num_pending_packets() > 0) {
PW_LOG_WARN(
"Proxy viewed failed disconnect (status: %#.2hhx) for connection "
"%#.4hx with packets in flight. Not releasing associated credits.",
Expand Down Expand Up @@ -204,17 +220,14 @@ pw::Status AclDataChannel::SendAcl(H4PacketWithH4&& h4_packet) {
}
uint16_t handle = acl_view->handle().Read();

AclConnection* connection_ptr = FindConnection(handle);
LeAclConnection* connection_ptr = FindLeAclConnection(handle);
if (!connection_ptr) {
if (active_connections_.full()) {
PW_LOG_ERROR("No space left in connections list.");
credit_allocation_mutex_.unlock();
return pw::Status::Unavailable();
}
active_connections_.push_back({handle, /*num_pending_packets=*/1});
} else {
++connection_ptr->num_pending_packets;
PW_LOG_ERROR("Tried to send ACL packet on unregistered connection.");
credit_allocation_mutex_.unlock();
return pw::Status::NotFound();
}
connection_ptr->set_num_pending_packets(
connection_ptr->num_pending_packets() + 1);

hci_transport_.SendToController(std::move(h4_packet));
credit_allocation_mutex_.unlock();
Expand All @@ -223,65 +236,69 @@ pw::Status AclDataChannel::SendAcl(H4PacketWithH4&& h4_packet) {

Status AclDataChannel::CreateLeAclConnection(uint16_t connection_handle) {
credit_allocation_mutex_.lock();
AclConnection* connection_it = FindConnection(connection_handle);
LeAclConnection* connection_it = FindLeAclConnection(connection_handle);
if (connection_it) {
credit_allocation_mutex_.unlock();
return Status::AlreadyExists();
}
if (active_connections_.full()) {
if (active_le_acl_connections_.full()) {
credit_allocation_mutex_.unlock();
return Status::ResourceExhausted();
}
active_connections_.push_back({connection_handle, 0, false});
active_le_acl_connections_.emplace_back(
connection_handle, 0, l2cap_channel_manager_);
credit_allocation_mutex_.unlock();
return OkStatus();
}

pw::Status AclDataChannel::FragmentedPduStarted(uint16_t connection_handle) {
credit_allocation_mutex_.lock();
AclConnection* connection_ptr = FindConnection(connection_handle);
LeAclConnection* connection_ptr = FindLeAclConnection(connection_handle);
credit_allocation_mutex_.unlock();
if (!connection_ptr) {
return Status::NotFound();
}
if (connection_ptr->is_receiving_fragmented_pdu) {
if (connection_ptr->is_receiving_fragmented_pdu()) {
return Status::FailedPrecondition();
}
connection_ptr->is_receiving_fragmented_pdu = true;
connection_ptr->set_is_receiving_fragmented_pdu(true);
return OkStatus();
}

pw::Result<bool> AclDataChannel::IsReceivingFragmentedPdu(
uint16_t connection_handle) {
credit_allocation_mutex_.lock();
AclConnection* connection_ptr = FindConnection(connection_handle);
LeAclConnection* connection_ptr = FindLeAclConnection(connection_handle);
credit_allocation_mutex_.unlock();
if (!connection_ptr) {
return Status::NotFound();
}
return connection_ptr->is_receiving_fragmented_pdu;
return connection_ptr->is_receiving_fragmented_pdu();
}

pw::Status AclDataChannel::FragmentedPduFinished(uint16_t connection_handle) {
credit_allocation_mutex_.lock();
AclConnection* connection_ptr = FindConnection(connection_handle);
LeAclConnection* connection_ptr = FindLeAclConnection(connection_handle);
credit_allocation_mutex_.unlock();
if (!connection_ptr) {
return Status::NotFound();
}
if (!connection_ptr->is_receiving_fragmented_pdu) {
if (!connection_ptr->is_receiving_fragmented_pdu()) {
return Status::FailedPrecondition();
}
connection_ptr->is_receiving_fragmented_pdu = false;
connection_ptr->set_is_receiving_fragmented_pdu(false);
return OkStatus();
}

AclDataChannel::AclConnection* AclDataChannel::FindConnection(uint16_t handle) {
AclConnection* connection_it = containers::FindIf(
active_connections_, [&handle](const AclConnection& connection) {
return connection.handle == handle;
AclDataChannel::LeAclConnection* AclDataChannel::FindLeAclConnection(
uint16_t connection_handle) {
LeAclConnection* connection_it = containers::FindIf(
active_le_acl_connections_,
[&connection_handle](const LeAclConnection& connection) {
return connection.connection_handle() == connection_handle;
});
return connection_it == active_connections_.end() ? nullptr : connection_it;
return connection_it == active_le_acl_connections_.end() ? nullptr
: connection_it;
}

} // namespace pw::bluetooth::proxy
40 changes: 40 additions & 0 deletions pw_bluetooth_proxy/basic_l2cap_channel.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
// Copyright 2024 The Pigweed Authors
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may not
// use this file except in compliance with the License. You may obtain a copy of
// the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations under
// the License.

#include "pw_bluetooth_proxy/basic_l2cap_channel.h"

#include "pw_bluetooth/emboss_util.h"
#include "pw_bluetooth/l2cap_frames.emb.h"

namespace pw::bluetooth::proxy {

BasicL2capChannel::BasicL2capChannel(
L2capChannelManager& l2cap_channel_manager,
uint16_t connection_handle,
uint16_t local_cid,
pw::Function<void(pw::span<uint8_t> payload)>&& receive_fn)
: L2capReadChannel(l2cap_channel_manager,
std::move(receive_fn),
connection_handle,
local_cid) {}

bool BasicL2capChannel::OnPduReceived(pw::span<uint8_t> bframe) {
Result<emboss::BFrameWriter> bframe_view =
MakeEmbossWriter<emboss::BFrameWriter>(bframe);
CallReceiveFn(span(bframe_view->payload().BackingStorage().data(),
bframe_view->BackingStorage().SizeInBytes()));
return true;
}

} // namespace pw::bluetooth::proxy
2 changes: 1 addition & 1 deletion pw_bluetooth_proxy/gatt_notify_channel.cc
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ pw::Status GattNotifyChannel::Write(pw::span<const uint8_t> attribute_value) {
attribute_value.data(),
attribute_value.size());

return SendL2capPacket(std::move(h4_packet));
return QueuePacket(std::move(h4_packet));
}

pw::Result<GattNotifyChannel> GattNotifyChannel::Create(
Expand Down
Loading

0 comments on commit 53d5391

Please sign in to comment.