blob: 1f21df93fdb6555e4e84952d56c603f75bd6da32 [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 "pw_assert/check.h"
#include "pw_async2/channel.h"
#include "pw_async2/dispatcher.h"
#include "pw_async2/poll.h"
#include "pw_async2/task.h"
#include "pw_async2/try.h"
#include "pw_bluetooth_proxy/internal/l2cap_channel.h"
#include "pw_bluetooth_proxy/internal/l2cap_channel_async.h"
#include "pw_bluetooth_proxy/internal/l2cap_channel_manager.h"
#include "pw_log/log.h"
namespace pw::bluetooth::proxy::internal {
L2capChannelImpl::~L2capChannelImpl() {
if (request_handle_.has_value()) {
request_handle_->Close();
}
}
Allocator& L2capChannelImpl::allocator() {
return channel_.l2cap_channel_manager_.impl().allocator();
}
Status L2capChannelImpl::Init() {
// One payload is held in `payload_`, the rest in the payload channel.
auto result = async2::CreateMpscChannel<multibuf::MultiBuf>(
allocator(), kQueueCapacity - 1);
if (!result.has_value()) {
return Status::ResourceExhausted();
}
std::tie(payload_handle_, payload_receiver_) = std::move(*result);
payload_sender_ = payload_handle_.CreateSender();
L2capChannelManagerImpl& manager = channel_.l2cap_channel_manager_.impl();
credit_sender_ = manager.CreateCreditSender();
return OkStatus();
}
Status L2capChannelImpl::Connect(
async2::SpscChannelHandle<Request>& request_handle,
async2::Sender<Request>& request_sender) {
auto req_result = async2::CreateSpscChannel<Request>(allocator(), 1);
if (!req_result.has_value()) {
return Status::ResourceExhausted();
}
async2::Receiver<Request> req_receiver;
std::tie(request_handle, request_sender, req_receiver) =
std::move(*req_result);
request_handle_ = request_handle;
task_.set_receiver(std::move(req_receiver));
L2capChannelManagerImpl& manager = channel_.l2cap_channel_manager_.impl();
manager.dispatcher().Post(task_);
return OkStatus();
}
void L2capChannelImpl::Connect(
async2::Sender<multibuf::MultiBuf>& payload_sender) {
payload_sender = payload_handle_.CreateSender();
}
void L2capChannelImpl::Close() {
if (request_handle_.has_value() && request_handle_->is_open()) {
request_handle_->Close();
}
// Always set to default to mark channel as stale, even if it wasn't acquired.
request_handle_ = async2::SpscChannelHandle<Request>();
}
StatusWithMultiBuf L2capChannelImpl::Write(multibuf::MultiBuf&& payload) {
if (channel_.state() != L2capChannel::State::kRunning) {
PW_LOG_WARN(
"btproxy: L2capChannel::Write called when not running. "
"local_cid: %#x, remote_cid: %#x, state: %u",
channel_.local_cid(),
channel_.remote_cid(),
cpp23::to_underlying(channel_.state()));
return {Status::FailedPrecondition(), std::move(payload)};
}
auto send_reservation = payload_sender_.TryReserveSend();
if (!send_reservation.ok()) {
return {send_reservation.status(), std::move(payload)};
}
send_reservation->Commit(std::move(payload));
return {OkStatus(), std::nullopt};
}
void L2capChannelImpl::NotifyOnDequeue() { notify_on_dequeue_ = true; }
async2::Poll<std::optional<H4PacketWithH4>> L2capChannelImpl::DequeuePacket(
async2::Context& context) {
std::optional<H4PacketWithH4> packet;
// First, try to receive a payload if we don't already have one.
if (!payload_.has_value()) {
if (!payload_future_.is_pendable()) {
payload_future_ = payload_receiver_.Receive();
}
PW_TRY_READY_ASSIGN(payload_, payload_future_.Pend(context));
// Either the handle was closed, or the future returned std::nullopt. Either
// way the channel is closed.
if (!payload_.has_value()) {
return async2::Ready(std::move(packet));
}
// If a previous client call to `Write` or `IsWriteAvailable` returned
// UNAVAILABLE, notify them that more space is now available.
if (notify_on_dequeue_) {
notify_on_dequeue_ = false;
SendEvent(L2capChannelEvent::kWriteAvailable);
}
}
// Create a packet from the payload.
bool keep_payload = false;
{
// Fake lock to satisfy annotations.
std::lock_guard lock(channel_.impl_.mutex_);
packet = channel_.GenerateNextTxPacket(*payload_, keep_payload);
}
// If no additional packets can be created from the payload, remove it.
if (!keep_payload) {
payload_.reset();
}
if (!packet.has_value()) {
PW_ASYNC_STORE_WAKER(
context, waker_, "Waiting for new tx packets or credits");
return async2::Pending();
}
return async2::Ready(std::move(packet));
}
void L2capChannelImpl::ReportNewTxPacketsOrCredits() {
waker_.Wake();
channel_.l2cap_channel_manager_.ReportNewTxPacketsOrCredits();
}
void L2capChannelImpl::ClearQueue() {
payload_handle_.Close();
payload_future_ = async2::ReceiveFuture<multibuf::MultiBuf>();
payload_.reset();
}
bool L2capChannelImpl::IsStale() const {
return request_handle_.has_value() && !request_handle_->is_open();
}
async2::Poll<> L2capChannelImpl::Task::DoPend(async2::Context& context) {
while (true) {
if (!future_.is_pendable()) {
future_ = receiver_.Receive();
}
std::optional<Request> request;
PW_TRY_READY_ASSIGN(request, future_.Pend(context));
future_ = async2::ReceiveFuture<Request>();
if (!request.has_value()) {
return async2::Ready();
}
switch (request->type) {
case Request::Type::kSendAdditionalRxCredits: {
std::ignore = impl_.channel().SendAdditionalRxCredits(
request->additional_rx_credits);
break;
}
case Request::Type::kNotifyOnDequeue: {
impl_.NotifyOnDequeue();
break;
}
case Request::Type::kStop: {
impl_.channel().Stop();
break;
}
case Request::Type::kClose: {
impl_.channel().Close();
break;
}
}
}
}
void L2capChannelImpl::SendEvent(L2capChannelEvent event) {
if (!request_handle_.has_value() || !request_handle_->is_open()) {
return;
}
// We don't log kWriteAvailable since they happen often. Optimally we would
// just debug log them also, but one of our downstreams logs all levels.
if (event != L2capChannelEvent::kWriteAvailable) {
PW_LOG_INFO(
"btproxy: L2capChannel::SendEvent - connection: %#x, "
"local_cid: %#x, event: %u,",
channel_.connection_handle(),
channel_.local_cid(),
cpp23::to_underlying(event));
}
if (channel_.event_fn_) {
channel_.event_fn_(event);
}
}
} // namespace pw::bluetooth::proxy::internal
#endif // PW_BLUETOOTH_PROXY_ASYNC