blob: dc445a867daf26965c13e0dbc6d36cf5c9a95dc4 [file]
// Copyright 2025 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/config.h"
#if PW_BLUETOOTH_PROXY_ASYNC != 0
#include <optional>
#include "pw_assert/check.h"
#include "pw_bluetooth_proxy/internal/acl_data_channel.h"
#include "pw_bluetooth_proxy/internal/l2cap_channel_manager.h"
#include "pw_status/status.h"
namespace pw::bluetooth::proxy {
L2capChannelManager::L2capChannelManager(AclDataChannel& acl_data_channel,
Allocator& allocator)
: acl_data_channel_(acl_data_channel),
channels_by_local_cid_(allocator),
channels_by_remote_cid_(allocator),
stale_(allocator),
allocator_(allocator),
impl_(*this, allocator),
logical_links_(allocator) {}
namespace internal {
L2capChannelManagerImpl::L2capChannelManagerImpl(L2capChannelManager& manager,
Allocator& allocator)
: manager_(manager),
allocator_(allocator),
drain_task_(*this),
lrd_channel_(manager.channels_by_local_cid_.end()),
round_robin_terminus_(manager.channels_by_local_cid_.end()) {}
Status L2capChannelManagerImpl::Init(async2::Dispatcher& dispatcher) {
if (dispatcher_ != nullptr) {
return Status::FailedPrecondition();
}
auto result = async2::CreateMpscChannel<bool>(allocator_, 1);
if (!result.has_value()) {
return Status::ResourceExhausted();
}
std::tie(credit_handle_, credit_receiver_) = std::move(*result);
credit_sender_ = credit_handle_.CreateSender();
dispatcher_ = &dispatcher;
dispatcher_thread_id_ = this_thread::get_id();
dispatcher_->Post(drain_task_);
return OkStatus();
}
async2::Sender<bool> L2capChannelManagerImpl::CreateCreditSender() {
return credit_handle_.CreateSender();
}
bool L2capChannelManagerImpl::IsRunningOnDispatcherThread() const {
return this_thread::get_id() == dispatcher_thread_id_;
}
void L2capChannelManagerImpl::OnRegister() {
if (lrd_channel_ == manager_.channels_by_local_cid_.end()) {
lrd_channel_ = manager_.channels_by_local_cid_.begin();
}
waker_.Wake();
}
// See L2capChannelManager::ReportNewTxPacketsOrCredits
void L2capChannelManagerImpl::ReportNewTxPacketsOrCredits() {
credit_sender_.TrySend(true).IgnoreError();
}
void L2capChannelManagerImpl::DrainChannelQueuesIfNewTx() {
// Actual work is done by the drain task and `DoDrainChannelQueuesIfNewTx`
waker_.Wake();
}
async2::Poll<> L2capChannelManagerImpl::DoDrainChannelQueuesIfNewTx(
async2::Context& context) {
// No-op that satisifes lock annotations.
std::lock_guard lock(channels_mutex_);
if (round_robin_terminus_ == manager_.channels_by_local_cid_.end()) {
round_robin_terminus_ = lrd_channel_;
}
while (true) {
if (!credit_handle_.is_open()) {
// ProxyHost has been reset; end this task.
return async2::Ready();
}
if (!credit_future_.is_pendable()) {
credit_future_ = credit_receiver_.Receive();
round_robin_terminus_ = lrd_channel_;
}
if (credit_future_.Pend(context).IsReady()) {
// We received a notification of additional tx credits. The future is no
// longer pendable, so start a fresh iteration.
continue;
}
// Container is empty, nothing to do.
if (manager_.channels_by_local_cid_.empty()) {
break;
}
L2capChannel& channel = lrd_channel_->second;
manager_.Advance(lrd_channel_);
// If we can reserve a send credit for the channel's type, attempt to
// dequeue a packet from channel. The credit is RAII, and will be returned
// if not used to send a packet.
//
// If we have made it all the way around without dequeueing a packet or
// receiving an notification of new credits, we are done for now.
std::optional<AclDataChannel::SendCredit> credit =
manager_.acl_data_channel_.ReserveSendCredit(channel.transport());
if (!credit.has_value()) {
if (lrd_channel_ == round_robin_terminus_) {
break;
}
continue;
}
async2::Poll<std::optional<H4PacketWithH4>> packet_poll =
channel.impl().DequeuePacket(context);
if (packet_poll.IsReady() && packet_poll->has_value()) {
// If we have a packet, send it and reset the terminus so round robin will
// continue until we have done a full loop with no packets dequeued.
PW_CHECK_OK(manager_.acl_data_channel_.SendAcl(std::move(**packet_poll),
std::move(*credit)));
round_robin_terminus_ = lrd_channel_;
continue;
}
// If no packet was returned and the channel is stale, no further packets
// will be received and the channel can be removed.
if (channel.IsStale()) {
auto node = manager_.DeregisterChannelLocked(channel);
node->mapped().Close();
}
// Done if we have made it all the way around.
if (lrd_channel_ == round_robin_terminus_) {
break;
}
}
// We completed a full round robin without dequeuing any packets. This task
// may be awoken by wakers set by `L2capChannelImpl::DequeuePacket`. It also
// needs to be awoken if new channels are registered, so store that waker now.
PW_ASYNC_STORE_WAKER(
context, waker_, "Waiting for channels to be registered");
return async2::Pending();
}
async2::Poll<> L2capChannelManagerImpl::DrainTask::DoPend(
async2::Context& context) {
return impl_.DoDrainChannelQueuesIfNewTx(context);
}
} // namespace internal
} // namespace pw::bluetooth::proxy
#endif // PW_BLUETOOTH_PROXY_ASYNC