| // 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 <mutex> |
| |
| #include "pw_assert/check.h" |
| #include "pw_bluetooth_proxy/internal/generic_l2cap_channel.h" |
| #include "pw_bluetooth_proxy/internal/l2cap_channel.h" |
| #include "pw_bluetooth_proxy/internal/l2cap_channel_manager.h" |
| #include "pw_log/log.h" |
| #include "pw_status/status.h" |
| |
| namespace pw::bluetooth::proxy::internal { |
| |
| sync::Mutex L2capChannelImpl::static_mutex_; |
| |
| BorrowedL2capChannel::BorrowedL2capChannel(L2capChannelImpl& impl) |
| : impl_(&impl) { |
| std::lock_guard lock(impl_->mutex_); |
| ++impl_->num_borrows_; |
| PW_CHECK_UINT_NE(impl_->num_borrows_, 0); |
| } |
| |
| BorrowedL2capChannel& BorrowedL2capChannel::operator=( |
| BorrowedL2capChannel&& other) { |
| if (this != &other) { |
| std::swap(impl_, other.impl_); |
| } |
| return *this; |
| } |
| |
| BorrowedL2capChannel::~BorrowedL2capChannel() { |
| if (impl_ == nullptr) { |
| return; |
| } |
| std::lock_guard lock(impl_->mutex_); |
| PW_CHECK_UINT_NE(impl_->num_borrows_, 0); |
| if (--impl_->num_borrows_ == 0) { |
| impl_->notification_.release(); |
| } |
| } |
| |
| L2capChannel* BorrowedL2capChannel::operator->() { return &impl_->channel_; } |
| |
| L2capChannel& BorrowedL2capChannel::operator*() { return impl_->channel_; } |
| |
| void L2capChannelImpl::BlockWhileBorrowed( |
| std::unique_lock<internal::Mutex>& lock) PW_NO_LOCK_SAFETY_ANALYSIS { |
| while (num_borrows_ != 0) { |
| lock.unlock(); |
| notification_.acquire(); |
| lock.lock(); |
| } |
| } |
| |
| void L2capChannelImpl::Close() { |
| std::lock_guard lock(L2capChannelImpl::mutex()); |
| if (client_.has_value() && *client_ != nullptr) { |
| (*client_)->channel_ = nullptr; |
| } |
| // Always set to null to mark channel as stale, even if it was not acquired. |
| client_ = nullptr; |
| } |
| |
| StatusWithMultiBuf L2capChannelImpl::Write(multibuf::MultiBuf&& payload) |
| PW_NO_LOCK_SAFETY_ANALYSIS { |
| std::lock_guard lock(mutex_); |
| 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)}; |
| } |
| |
| if (payload_queue_.full()) { |
| notify_on_dequeue_ = true; |
| return {Status::Unavailable(), std::move(payload)}; |
| } |
| payload_queue_.push(std::move(payload)); |
| channel_.ReportNewTxPacketsOrCredits(); |
| return {OkStatus(), std::nullopt}; |
| } |
| |
| Status L2capChannelImpl::IsWriteAvailable() PW_NO_LOCK_SAFETY_ANALYSIS { |
| std::lock_guard lock(mutex_); |
| if (channel_.state_ != L2capChannel::State::kRunning) { |
| return Status::FailedPrecondition(); |
| } |
| |
| if (payload_queue_.full()) { |
| notify_on_dequeue_ = true; |
| return Status::Unavailable(); |
| } |
| |
| notify_on_dequeue_ = false; |
| return OkStatus(); |
| } |
| |
| std::optional<H4PacketWithH4> L2capChannelImpl::DequeuePacket() |
| PW_NO_LOCK_SAFETY_ANALYSIS { |
| std::optional<H4PacketWithH4> packet; |
| bool should_notify = false; |
| { |
| // Get a payload. |
| std::lock_guard lock(mutex_); |
| if (channel_.state_ != L2capChannel::State::kRunning) { |
| payload_queue_.clear(); |
| } |
| if (payload_queue_.empty()) { |
| return std::nullopt; |
| } |
| const multibuf::MultiBuf& payload = payload_queue_.front(); |
| |
| // Create a packet from the payload. |
| bool keep_payload = false; |
| packet = channel_.GenerateNextTxPacket(payload, keep_payload); |
| |
| // If no additional packets can be created from the payload, remove it. |
| if (!keep_payload) { |
| payload_queue_.pop(); |
| should_notify = notify_on_dequeue_; |
| notify_on_dequeue_ = false; |
| } |
| } |
| |
| // Notify the client if there is now room in the queue. |
| if (should_notify) { |
| SendEvent(L2capChannelEvent::kWriteAvailable); |
| } |
| |
| return packet; |
| } |
| |
| void L2capChannelImpl::ReportNewTxPacketsOrCredits() { |
| channel_.l2cap_channel_manager_.ReportNewTxPacketsOrCredits(); |
| } |
| |
| void L2capChannelImpl::ClearQueue() { payload_queue_.clear(); } |
| |
| bool L2capChannelImpl::IsStale() const { |
| std::lock_guard lock(L2capChannelImpl::mutex()); |
| return client_.has_value() && *client_ == nullptr; |
| } |
| |
| bool L2capChannelImpl::PayloadQueueEmpty() const { |
| std::lock_guard lock(mutex_); |
| return payload_queue_.empty(); |
| } |
| |
| void L2capChannelImpl::SendEvent(L2capChannelEvent event) { |
| { |
| std::lock_guard lock(L2capChannelImpl::mutex()); |
| if (!client_.has_value() || *client_ == nullptr) { |
| 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 |