blob: 4dbfff4db299ea8f30f1348a6917f079666cad47 [file] [log] [blame]
/*
* Copyright 2020 The Android Open Source Project
*
* 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
*
* http://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 "hci/acl_manager/round_robin_scheduler.h"
#include "hci/acl_manager/acl_fragmenter.h"
namespace bluetooth {
namespace hci {
namespace acl_manager {
RoundRobinScheduler::RoundRobinScheduler(
os::Handler* handler, Controller* controller, common::BidiQueueEnd<AclBuilder, AclView>* hci_queue_end)
: handler_(handler), controller_(controller), hci_queue_end_(hci_queue_end) {
max_acl_packet_credits_ = controller_->GetNumAclPacketBuffers();
acl_packet_credits_ = max_acl_packet_credits_;
hci_mtu_ = controller_->GetAclPacketLength();
LeBufferSize le_buffer_size = controller_->GetLeBufferSize();
le_max_acl_packet_credits_ = le_buffer_size.total_num_le_packets_;
le_acl_packet_credits_ = le_max_acl_packet_credits_;
le_hci_mtu_ = le_buffer_size.le_data_packet_length_;
controller_->RegisterCompletedAclPacketsCallback(handler->BindOn(this, &RoundRobinScheduler::incoming_acl_credits));
}
RoundRobinScheduler::~RoundRobinScheduler() {
unregister_all_connections();
controller_->UnregisterCompletedAclPacketsCallback();
}
void RoundRobinScheduler::Register(ConnectionType connection_type, uint16_t handle,
std::shared_ptr<acl_manager::AclConnection::Queue> queue) {
acl_queue_handler acl_queue_handler = {connection_type, std::move(queue), false, 0};
acl_queue_handlers_.insert(std::pair<uint16_t, RoundRobinScheduler::acl_queue_handler>(handle, acl_queue_handler));
if (fragments_to_send_.size() == 0) {
start_round_robin();
}
}
void RoundRobinScheduler::Unregister(uint16_t handle) {
ASSERT(acl_queue_handlers_.count(handle) == 1);
auto acl_queue_handler = acl_queue_handlers_.find(handle)->second;
// Reclaim outstanding packets
if (acl_queue_handler.connection_type_ == ConnectionType::CLASSIC) {
acl_packet_credits_ += acl_queue_handler.number_of_sent_packets_;
} else {
le_acl_packet_credits_ += acl_queue_handler.number_of_sent_packets_;
}
acl_queue_handler.number_of_sent_packets_ = 0;
if (acl_queue_handler.dequeue_is_registered_) {
acl_queue_handler.dequeue_is_registered_ = false;
acl_queue_handler.queue_->GetDownEnd()->UnregisterDequeue();
}
acl_queue_handlers_.erase(handle);
starting_point_ = acl_queue_handlers_.begin();
}
void RoundRobinScheduler::SetLinkPriority(uint16_t handle, bool high_priority) {
auto acl_queue_handler = acl_queue_handlers_.find(handle);
if (acl_queue_handler == acl_queue_handlers_.end()) {
LOG_WARN("handle %d is invalid", handle);
return;
}
acl_queue_handler->second.high_priority_ = high_priority;
}
uint16_t RoundRobinScheduler::GetCredits() {
return acl_packet_credits_;
}
uint16_t RoundRobinScheduler::GetLeCredits() {
return le_acl_packet_credits_;
}
void RoundRobinScheduler::start_round_robin() {
if (acl_packet_credits_ == 0 && le_acl_packet_credits_ == 0) {
return;
}
if (!fragments_to_send_.empty()) {
auto connection_type = fragments_to_send_.front().first;
bool classic_buffer_full = acl_packet_credits_ == 0 && connection_type == ConnectionType::CLASSIC;
bool le_buffer_full = le_acl_packet_credits_ == 0 && connection_type == ConnectionType::LE;
if (classic_buffer_full || le_buffer_full) {
LOG_WARN("Buffer of connection_type %d is full", connection_type);
return;
}
send_next_fragment();
return;
}
if (acl_queue_handlers_.empty()) {
LOG_INFO("No any acl connection");
return;
}
if (acl_queue_handlers_.size() == 1 || starting_point_ == acl_queue_handlers_.end()) {
starting_point_ = acl_queue_handlers_.begin();
}
size_t count = acl_queue_handlers_.size();
for (auto acl_queue_handler = starting_point_; count > 0; count--) {
// Prevent registration when credits is zero
bool classic_buffer_full =
acl_packet_credits_ == 0 && acl_queue_handler->second.connection_type_ == ConnectionType::CLASSIC;
bool le_buffer_full =
le_acl_packet_credits_ == 0 && acl_queue_handler->second.connection_type_ == ConnectionType::LE;
if (!acl_queue_handler->second.dequeue_is_registered_ && !classic_buffer_full && !le_buffer_full) {
acl_queue_handler->second.dequeue_is_registered_ = true;
acl_queue_handler->second.queue_->GetDownEnd()->RegisterDequeue(
handler_, common::Bind(&RoundRobinScheduler::buffer_packet, common::Unretained(this), acl_queue_handler));
}
acl_queue_handler = std::next(acl_queue_handler);
if (acl_queue_handler == acl_queue_handlers_.end()) {
acl_queue_handler = acl_queue_handlers_.begin();
}
}
starting_point_ = std::next(starting_point_);
}
void RoundRobinScheduler::buffer_packet(std::map<uint16_t, acl_queue_handler>::iterator acl_queue_handler) {
BroadcastFlag broadcast_flag = BroadcastFlag::POINT_TO_POINT;
// Wrap packet and enqueue it
uint16_t handle = acl_queue_handler->first;
auto packet = acl_queue_handler->second.queue_->GetDownEnd()->TryDequeue();
ASSERT(packet != nullptr);
ConnectionType connection_type = acl_queue_handler->second.connection_type_;
size_t mtu = connection_type == ConnectionType::CLASSIC ? hci_mtu_ : le_hci_mtu_;
PacketBoundaryFlag packet_boundary_flag = (packet->IsFlushable())
? PacketBoundaryFlag::FIRST_AUTOMATICALLY_FLUSHABLE
: PacketBoundaryFlag::FIRST_NON_AUTOMATICALLY_FLUSHABLE;
int acl_priority = acl_queue_handler->second.high_priority_ ? 1 : 0;
if (packet->size() <= mtu) {
fragments_to_send_.push(
std::make_pair(
connection_type, AclBuilder::Create(handle, packet_boundary_flag, broadcast_flag, std::move(packet))),
acl_priority);
} else {
auto fragments = AclFragmenter(mtu, std::move(packet)).GetFragments();
for (size_t i = 0; i < fragments.size(); i++) {
fragments_to_send_.push(
std::make_pair(
connection_type,
AclBuilder::Create(handle, packet_boundary_flag, broadcast_flag, std::move(fragments[i]))),
acl_priority);
packet_boundary_flag = PacketBoundaryFlag::CONTINUING_FRAGMENT;
}
}
ASSERT(fragments_to_send_.size() > 0);
unregister_all_connections();
acl_queue_handler->second.number_of_sent_packets_ += fragments_to_send_.size();
send_next_fragment();
}
void RoundRobinScheduler::unregister_all_connections() {
for (auto acl_queue_handler = acl_queue_handlers_.begin(); acl_queue_handler != acl_queue_handlers_.end();
acl_queue_handler = std::next(acl_queue_handler)) {
if (acl_queue_handler->second.dequeue_is_registered_) {
acl_queue_handler->second.dequeue_is_registered_ = false;
acl_queue_handler->second.queue_->GetDownEnd()->UnregisterDequeue();
}
}
}
void RoundRobinScheduler::send_next_fragment() {
if (!enqueue_registered_.exchange(true)) {
hci_queue_end_->RegisterEnqueue(
handler_, common::Bind(&RoundRobinScheduler::handle_enqueue_next_fragment, common::Unretained(this)));
}
}
// Invoked from some external Queue Reactable context 1
std::unique_ptr<AclBuilder> RoundRobinScheduler::handle_enqueue_next_fragment() {
ConnectionType connection_type = fragments_to_send_.front().first;
if (connection_type == ConnectionType::CLASSIC) {
ASSERT(acl_packet_credits_ > 0);
acl_packet_credits_ -= 1;
} else {
ASSERT(le_acl_packet_credits_ > 0);
le_acl_packet_credits_ -= 1;
}
auto raw_pointer = fragments_to_send_.front().second.release();
fragments_to_send_.pop();
if (fragments_to_send_.empty()) {
if (enqueue_registered_.exchange(false)) {
hci_queue_end_->UnregisterEnqueue();
}
handler_->Post(common::BindOnce(&RoundRobinScheduler::start_round_robin, common::Unretained(this)));
} else {
ConnectionType next_connection_type = fragments_to_send_.front().first;
bool classic_buffer_full = next_connection_type == ConnectionType::CLASSIC && acl_packet_credits_ == 0;
bool le_buffer_full = next_connection_type == ConnectionType::LE && le_acl_packet_credits_ == 0;
if ((classic_buffer_full || le_buffer_full) && enqueue_registered_.exchange(false)) {
hci_queue_end_->UnregisterEnqueue();
}
}
return std::unique_ptr<AclBuilder>(raw_pointer);
}
void RoundRobinScheduler::incoming_acl_credits(uint16_t handle, uint16_t credits) {
auto acl_queue_handler = acl_queue_handlers_.find(handle);
if (acl_queue_handler == acl_queue_handlers_.end()) {
return;
}
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;
}
bool credit_was_zero = false;
if (acl_queue_handler->second.connection_type_ == ConnectionType::CLASSIC) {
if (acl_packet_credits_ == 0) {
credit_was_zero = true;
}
acl_packet_credits_ += credits;
if (acl_packet_credits_ > max_acl_packet_credits_) {
acl_packet_credits_ = max_acl_packet_credits_;
LOG_WARN("acl packet credits overflow due to receive %hx credits", credits);
}
} else {
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_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) {
start_round_robin();
}
}
} // namespace acl_manager
} // namespace hci
} // namespace bluetooth