blob: f98e7cba09be2b005409b868126aab8cec812b1b [file] [log] [blame]
// Copyright 2022 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/transport/sco_data_channel.h"
#include <lib/fit/defer.h>
namespace bt::hci {
using pw::bluetooth::Controller;
using ScoCodingFormat = pw::bluetooth::Controller::ScoCodingFormat;
using ScoEncoding = pw::bluetooth::Controller::ScoEncoding;
using ScoSampleRate = pw::bluetooth::Controller::ScoSampleRate;
class ScoDataChannelImpl final : public ScoDataChannel {
public:
ScoDataChannelImpl(const DataBufferInfo& buffer_info,
CommandChannel* command_channel,
Controller* hci);
~ScoDataChannelImpl() override;
// ScoDataChannel overrides:
void RegisterConnection(WeakPtr<ConnectionInterface> connection) override;
void UnregisterConnection(hci_spec::ConnectionHandle handle) override;
void ClearControllerPacketCount(hci_spec::ConnectionHandle handle) override;
void OnOutboundPacketReadable() override;
uint16_t max_data_length() const override {
return static_cast<uint16_t>(buffer_info_.max_data_length());
}
private:
enum class HciConfigState {
kPending,
kConfigured,
};
struct ConnectionData {
WeakPtr<ConnectionInterface> connection;
HciConfigState config_state = HciConfigState::kPending;
};
void OnRxPacket(pw::span<const std::byte> buffer);
// Send packets queued on the active channel if the controller has free buffer
// slots.
void TrySendNextPackets();
// The number of free buffer slots in the controller.
size_t GetNumFreePackets();
// Chooses a new connection to be active if one isn't already active.
void MaybeUpdateActiveConnection();
// Configure the HCI driver for the active SCO connection. Must be called
// before sending packets. Called when the active connection is changed.
void ConfigureHci();
// Called when SCO configuration is complete.
void OnHciConfigured(hci_spec::ConnectionHandle conn_handle,
pw::Status status);
// Handler for the HCI Number of Completed Packets Event, used for
// packet-based data flow control.
CommandChannel::EventCallbackResult OnNumberOfCompletedPacketsEvent(
const EventPacket& event);
bool IsActiveConnectionConfigured() {
if (!active_connection_.is_alive()) {
return false;
}
auto iter = connections_.find(active_connection_->handle());
BT_ASSERT(iter != connections_.end());
return (iter->second.config_state == HciConfigState::kConfigured);
}
CommandChannel* command_channel_;
Controller* hci_;
DataBufferInfo buffer_info_;
std::unordered_map<hci_spec::ConnectionHandle, ConnectionData> connections_;
// Only 1 connection may send packets at a time.
WeakPtr<ConnectionInterface> active_connection_;
// Stores per-connection counts of unacknowledged packets sent to the
// controller. Entries are updated/removed on the HCI Number Of Completed
// Packets event and removed when a connection is unregistered (the controller
// does not acknowledge packets of disconnected links).
std::unordered_map<hci_spec::ConnectionHandle, size_t> pending_packet_counts_;
// The event handler ID for the Number Of Completed Packets event.
CommandChannel::EventHandlerId num_completed_packets_event_handler_id_;
WeakSelf<ScoDataChannelImpl> weak_self_{this};
};
ScoDataChannelImpl::ScoDataChannelImpl(const DataBufferInfo& buffer_info,
CommandChannel* command_channel,
Controller* hci)
: command_channel_(command_channel), hci_(hci), buffer_info_(buffer_info) {
// ScoDataChannel shouldn't be used if the buffer is unavailable (implying the
// controller doesn't support SCO).
BT_ASSERT(buffer_info_.IsAvailable());
num_completed_packets_event_handler_id_ = command_channel_->AddEventHandler(
hci_spec::kNumberOfCompletedPacketsEventCode,
fit::bind_member<&ScoDataChannelImpl::OnNumberOfCompletedPacketsEvent>(
this));
BT_ASSERT(num_completed_packets_event_handler_id_);
hci_->SetReceiveScoFunction(
fit::bind_member<&ScoDataChannelImpl::OnRxPacket>(this));
}
ScoDataChannelImpl::~ScoDataChannelImpl() {
command_channel_->RemoveEventHandler(num_completed_packets_event_handler_id_);
}
void ScoDataChannelImpl::RegisterConnection(
WeakPtr<ConnectionInterface> connection) {
BT_ASSERT(connection->parameters().view().output_data_path().Read() ==
pw::bluetooth::emboss::ScoDataPath::HCI);
ConnectionData conn_data{.connection = connection};
auto [_, inserted] =
connections_.emplace(connection->handle(), std::move(conn_data));
BT_ASSERT_MSG(inserted,
"connection with handle %#.4x already registered",
connection->handle());
MaybeUpdateActiveConnection();
}
void ScoDataChannelImpl::UnregisterConnection(
hci_spec::ConnectionHandle handle) {
auto iter = connections_.find(handle);
if (iter == connections_.end()) {
return;
}
connections_.erase(iter);
MaybeUpdateActiveConnection();
}
void ScoDataChannelImpl::ClearControllerPacketCount(
hci_spec::ConnectionHandle handle) {
bt_log(DEBUG, "hci", "clearing pending packets (handle: %#.4x)", handle);
BT_ASSERT(connections_.find(handle) == connections_.end());
auto iter = pending_packet_counts_.find(handle);
if (iter == pending_packet_counts_.end()) {
return;
}
pending_packet_counts_.erase(iter);
TrySendNextPackets();
}
void ScoDataChannelImpl::OnOutboundPacketReadable() { TrySendNextPackets(); }
void ScoDataChannelImpl::OnRxPacket(pw::span<const std::byte> buffer) {
if (buffer.size() < sizeof(hci_spec::SynchronousDataHeader)) {
// TODO(https://fxbug.dev/42179582): Handle these types of errors by
// signaling Transport.
bt_log(ERROR,
"hci",
"malformed packet - expected at least %zu bytes, got %zu",
sizeof(hci_spec::SynchronousDataHeader),
buffer.size());
return;
}
const size_t payload_size =
buffer.size() - sizeof(hci_spec::SynchronousDataHeader);
std::unique_ptr<ScoDataPacket> packet =
ScoDataPacket::New(static_cast<uint8_t>(payload_size));
packet->mutable_view()->mutable_data().Write(
reinterpret_cast<const uint8_t*>(buffer.data()), buffer.size());
packet->InitializeFromBuffer();
if (packet->view().header().data_total_length != payload_size) {
// TODO(https://fxbug.dev/42179582): Handle these types of errors by
// signaling Transport.
bt_log(ERROR,
"hci",
"malformed packet - payload size from header (%hu) does not match"
" received payload size: %zu",
packet->view().header().data_total_length,
payload_size);
return;
}
auto conn_iter = connections_.find(le16toh(packet->connection_handle()));
if (conn_iter == connections_.end()) {
// Ignore inbound packets for connections that aren't registered. Unlike
// ACL, buffering data received before a connection is registered is
// unnecessary for SCO (it's realtime and not expected to be reliable).
bt_log(DEBUG,
"hci",
"ignoring inbound SCO packet for unregistered connection: %#.4x",
packet->connection_handle());
return;
}
conn_iter->second.connection->ReceiveInboundPacket(std::move(packet));
}
CommandChannel::EventCallbackResult
ScoDataChannelImpl::OnNumberOfCompletedPacketsEvent(const EventPacket& event) {
BT_ASSERT(event.event_code() == hci_spec::kNumberOfCompletedPacketsEventCode);
const auto& payload =
event.params<hci_spec::NumberOfCompletedPacketsEventParams>();
const size_t handles_in_packet =
(event.view().payload_size() -
sizeof(hci_spec::NumberOfCompletedPacketsEventParams)) /
sizeof(hci_spec::NumberOfCompletedPacketsEventData);
if (payload.number_of_handles != handles_in_packet) {
bt_log(ERROR,
"hci",
"packets handle count (%d) doesn't match params size (%zu); either "
"the packet was "
"parsed incorrectly or the controller is buggy",
payload.number_of_handles,
handles_in_packet);
}
for (uint8_t i = 0; i < payload.number_of_handles && i < handles_in_packet;
++i) {
const hci_spec::NumberOfCompletedPacketsEventData* data = payload.data + i;
auto iter = pending_packet_counts_.find(le16toh(data->connection_handle));
if (iter == pending_packet_counts_.end()) {
// This is expected if the completed packet is an ACL packet.
bt_log(TRACE,
"hci",
"controller reported completed packets for connection handle "
"without pending packets: "
"%#.4x",
data->connection_handle);
continue;
}
uint16_t comp_packets = le16toh(data->hc_num_of_completed_packets);
if (iter->second < comp_packets) {
// TODO(https://fxbug.dev/42102535): This can be caused by the controller
// reusing the connection handle of a connection that just disconnected.
// We should somehow avoid sending the controller packets for a connection
// that has disconnected. ScoDataChannel already dequeues such packets,
// but this is insufficient: packets can be queued in the channel to the
// transport driver, and possibly in the transport driver or USB/UART
// drivers.
bt_log(ERROR,
"hci",
"SCO packet tx count mismatch! (handle: %#.4x, expected: %zu, "
"actual : %u)",
le16toh(data->connection_handle),
iter->second,
comp_packets);
// This should eventually result in convergence with the correct pending
// packet count. If it undercounts the true number of pending packets,
// this branch will be reached again when the controller sends an updated
// Number of Completed Packets event. However, ScoDataChannel may overflow
// the controller's buffer in the meantime!
comp_packets = static_cast<uint16_t>(iter->second);
}
iter->second -= comp_packets;
if (iter->second == 0u) {
pending_packet_counts_.erase(iter);
}
}
TrySendNextPackets();
return CommandChannel::EventCallbackResult::kContinue;
}
void ScoDataChannelImpl::TrySendNextPackets() {
if (!IsActiveConnectionConfigured()) {
// If there is no active connection configured, then there is probably no
// bandwidth, so we shouldn't send packets.
return;
}
// Even though we only expect to have enough bandwidth for the 1
// active/configured SCO connection (especially for USB, see
// https://fxbug.dev/42173137), try to service all connections.
for (auto& [conn_handle, conn_data] : connections_) {
for (size_t num_free_packets = GetNumFreePackets(); num_free_packets != 0u;
num_free_packets--) {
std::unique_ptr<ScoDataPacket> packet =
conn_data.connection->GetNextOutboundPacket();
if (!packet) {
// This connection has no more packets available.
break;
}
hci_->SendScoData(packet->view().data().subspan());
auto [iter, _] = pending_packet_counts_.try_emplace(conn_handle, 0u);
iter->second++;
}
}
}
size_t ScoDataChannelImpl::GetNumFreePackets() {
size_t pending_packets_sum = 0u;
for (auto& [_, count] : pending_packet_counts_) {
pending_packets_sum += count;
}
return buffer_info_.max_num_packets() - pending_packets_sum;
}
void ScoDataChannelImpl::MaybeUpdateActiveConnection() {
if (active_connection_.is_alive() &&
connections_.count(active_connection_->handle())) {
// Active connection is still registered.
return;
}
if (connections_.empty()) {
active_connection_.reset();
ConfigureHci();
return;
}
active_connection_ = connections_.begin()->second.connection;
ConfigureHci();
}
void ScoDataChannelImpl::ConfigureHci() {
if (!active_connection_.is_alive()) {
hci_->ResetSco([](pw::Status status) {
bt_log(DEBUG,
"hci",
"ResetSco completed with status %s",
pw_StatusString(status));
});
return;
}
bt::StaticPacket<pw::bluetooth::emboss::SynchronousConnectionParametersWriter>
params = active_connection_->parameters();
auto view = params.view();
ScoCodingFormat coding_format;
if (view.output_coding_format().coding_format().Read() ==
pw::bluetooth::emboss::CodingFormat::MSBC) {
coding_format = ScoCodingFormat::kMsbc;
} else if (view.output_coding_format().coding_format().Read() ==
pw::bluetooth::emboss::CodingFormat::CVSD) {
coding_format = ScoCodingFormat::kCvsd;
} else {
bt_log(WARN,
"hci",
"SCO connection has unsupported coding format, treating as CVSD");
coding_format = ScoCodingFormat::kCvsd;
}
ScoSampleRate sample_rate;
const uint16_t bits_per_byte = CHAR_BIT;
uint16_t bytes_per_sample =
view.output_coded_data_size_bits().Read() / bits_per_byte;
if (bytes_per_sample == 0) {
// Err on the side of reserving too much bandwidth in the transport drivers.
bt_log(WARN,
"hci",
"SCO connection has unsupported encoding size, treating as 16-bit");
bytes_per_sample = 2;
}
const uint32_t bytes_per_second = view.output_bandwidth().Read();
const uint32_t samples_per_second = bytes_per_second / bytes_per_sample;
if (samples_per_second == 8000) {
sample_rate = ScoSampleRate::k8Khz;
} else if (samples_per_second == 16000) {
sample_rate = ScoSampleRate::k16Khz;
} else {
// Err on the side of reserving too much bandwidth in the transport drivers.
bt_log(WARN,
"hci",
"SCO connection has unsupported sample rate, treating as 16kHz");
sample_rate = ScoSampleRate::k16Khz;
}
ScoEncoding encoding;
if (view.output_coded_data_size_bits().Read() == 8) {
encoding = ScoEncoding::k8Bits;
} else if (view.output_coded_data_size_bits().Read() == 16) {
encoding = ScoEncoding::k16Bits;
} else {
// Err on the side of reserving too much bandwidth in the transport drivers.
bt_log(WARN,
"hci",
"SCO connection has unsupported sample rate, treating as 16-bit");
encoding = ScoEncoding::k16Bits;
}
auto conn = connections_.find(active_connection_->handle());
BT_ASSERT(conn != connections_.end());
auto callback = [self = weak_self_.GetWeakPtr(),
handle = conn->first](pw::Status status) {
if (self.is_alive()) {
self->OnHciConfigured(handle, status);
}
};
hci_->ConfigureSco(coding_format, encoding, sample_rate, std::move(callback));
}
// NOLINTNEXTLINE(bugprone-easily-swappable-parameters)
void ScoDataChannelImpl::OnHciConfigured(hci_spec::ConnectionHandle conn_handle,
pw::Status status) {
auto iter = connections_.find(conn_handle);
if (iter == connections_.end()) {
// The connection may have been unregistered before the config callback was
// called.
return;
}
if (!status.ok()) {
bt_log(WARN,
"hci",
"ConfigureSco failed with status %s (handle: %#.4x)",
pw_StatusString(status),
conn_handle);
// The error callback may unregister the connection synchronously, so |iter|
// should not be used past this line.
iter->second.connection->OnHciError();
UnregisterConnection(conn_handle);
return;
}
iter->second.config_state = HciConfigState::kConfigured;
TrySendNextPackets();
}
std::unique_ptr<ScoDataChannel> ScoDataChannel::Create(
const DataBufferInfo& buffer_info,
CommandChannel* command_channel,
Controller* hci) {
return std::make_unique<ScoDataChannelImpl>(
buffer_info, command_channel, hci);
}
} // namespace bt::hci