blob: be860f85fcadfe0db425364fe4f0bf4521cbff4d [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 "sco_data_channel.h"
#include <lib/async/default.h>
#include <lib/fit/defer.h>
#include <zircon/status.h>
#include "device_wrapper.h"
#include "slab_allocators.h"
#include "src/connectivity/bluetooth/core/bt-host/transport/transport.h"
#include "src/lib/fxl/memory/weak_ptr.h"
namespace bt::hci {
class ScoDataChannelImpl final : public ScoDataChannel {
public:
ScoDataChannelImpl(const DataBufferInfo& buffer_info, CommandChannel* command_channel,
HciWrapper* hci);
~ScoDataChannelImpl() override;
// ScoDataChannel overrides:
void RegisterConnection(fxl::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 buffer_info_.max_data_length(); }
private:
enum class HciConfigState {
kPending,
kConfigured,
};
struct ConnectionData {
fxl::WeakPtr<ConnectionInterface> connection;
HciConfigState config_state = HciConfigState::kPending;
};
void OnRxPacket(std::unique_ptr<ScoDataPacket> packet);
// 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, zx_status_t 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_) {
return false;
}
auto iter = connections_.find(active_connection_->handle());
ZX_ASSERT(iter != connections_.end());
return (iter->second.config_state == HciConfigState::kConfigured);
}
CommandChannel* command_channel_;
HciWrapper* hci_;
DataBufferInfo buffer_info_;
async_dispatcher_t* dispatcher_;
std::unordered_map<hci_spec::ConnectionHandle, ConnectionData> connections_;
// Only 1 connection may send packets at a time.
fxl::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_;
fxl::WeakPtrFactory<ScoDataChannelImpl> weak_ptr_factory_{this};
};
ScoDataChannelImpl::ScoDataChannelImpl(const DataBufferInfo& buffer_info,
CommandChannel* command_channel, HciWrapper* hci)
: command_channel_(command_channel),
hci_(hci),
buffer_info_(buffer_info),
dispatcher_(async_get_default_dispatcher()) {
// ScoDataChannel shouldn't be used if the buffer is unavailable (implying the controller
// doesn't support SCO).
ZX_ASSERT(buffer_info_.IsAvailable());
num_completed_packets_event_handler_id_ = command_channel_->AddEventHandler(
hci_spec::kNumberOfCompletedPacketsEventCode,
fit::bind_member<&ScoDataChannelImpl::OnNumberOfCompletedPacketsEvent>(this));
ZX_ASSERT(num_completed_packets_event_handler_id_);
hci_->SetScoCallback(fit::bind_member<&ScoDataChannelImpl::OnRxPacket>(this));
}
ScoDataChannelImpl::~ScoDataChannelImpl() {
command_channel_->RemoveEventHandler(num_completed_packets_event_handler_id_);
}
void ScoDataChannelImpl::RegisterConnection(fxl::WeakPtr<ConnectionInterface> connection) {
ZX_ASSERT(connection->parameters().output_data_path == hci_spec::ScoDataPath::kHci);
ConnectionData conn_data{.connection = connection};
auto [_, inserted] = connections_.emplace(connection->handle(), std::move(conn_data));
ZX_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);
ZX_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(std::unique_ptr<ScoDataPacket> packet) {
auto conn_iter = connections_.find(letoh16(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) {
ZX_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;
}
const uint16_t comp_packets = le16toh(data->hc_num_of_completed_packets);
ZX_ASSERT(iter->second != 0u);
ZX_ASSERT_MSG(
iter->second >= comp_packets,
"pending/completed packet count mismatch! (handle: %#.4x, pending: %zu, completed : %u)",
le16toh(data->connection_handle), iter->second, comp_packets);
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 fxb/91560), 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;
}
zx_status_t status = hci_->SendScoPacket(std::move(packet));
if (status != ZX_OK) {
bt_log(ERROR, "hci", "failed to send data packet to HCI driver (%s) - dropping packet",
zx_status_get_string(status));
continue;
}
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_ && 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_) {
hci_->ResetSco([](zx_status_t status) {
bt_log(DEBUG, "hci", "ResetSco completed with status %s", zx_status_get_string(status));
});
return;
}
hci_spec::SynchronousConnectionParameters params = active_connection_->parameters();
ScoCodingFormat coding_format;
if (params.output_coding_format.coding_format == hci_spec::CodingFormat::kMSbc) {
coding_format = ScoCodingFormat::kMsbc;
} else if (params.output_coding_format.coding_format == hci_spec::CodingFormat::kCvsd) {
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 = params.output_coded_data_size_bits / 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 = params.output_bandwidth;
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 (params.output_coded_data_size_bits == 8) {
encoding = ScoEncoding::k8Bits;
} else if (params.output_coded_data_size_bits == 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());
ZX_ASSERT(conn != connections_.end());
auto callback = [self = weak_ptr_factory_.GetWeakPtr(),
handle = conn->first](zx_status_t status) {
if (self) {
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,
zx_status_t 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 != ZX_OK) {
bt_log(WARN, "hci", "ConfigureSco failed with status %s (handle: %#.4x)",
zx_status_get_string(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,
HciWrapper* hci) {
return std::make_unique<ScoDataChannelImpl>(buffer_info, command_channel, hci);
}
} // namespace bt::hci