| // 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 "bt_transport_uart.h" |
| |
| #include <assert.h> |
| #include <lib/async/default.h> |
| #include <lib/ddk/metadata.h> |
| #include <lib/driver/component/cpp/driver_export.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <threads.h> |
| #include <unistd.h> |
| #include <zircon/assert.h> |
| #include <zircon/status.h> |
| |
| namespace bt_transport_uart { |
| |
| BtTransportUart::BtTransportUart(fuchsia_driver_framework::DriverStartArgs start_args, |
| fdf::UnownedSynchronizedDispatcher driver_dispatcher) |
| : DriverBase("bt-transport-uart", std::move(start_args), std::move(driver_dispatcher)), |
| dispatcher_(dispatcher()), |
| node_(fidl::WireClient(std::move(node()), dispatcher())) {} |
| |
| zx::result<> BtTransportUart::Start() { |
| FDF_LOG(DEBUG, "Start"); |
| |
| zx::result<fdf::ClientEnd<fuchsia_hardware_serialimpl::Device>> client_end = |
| incoming()->Connect<fuchsia_hardware_serialimpl::Service::Device>(); |
| if (client_end.is_error()) { |
| FDF_LOG(ERROR, "Connect to fuchsia_hardware_serialimpl::Device protocol failed: %s", |
| client_end.status_string()); |
| return zx::error(client_end.status_value()); |
| } |
| |
| { |
| std::lock_guard guard(mutex_); |
| |
| serial_client_ = fdf::WireClient<fuchsia_hardware_serialimpl::Device>( |
| std::move(client_end.value()), driver_dispatcher()->get()); |
| if (!serial_client_.is_valid()) { |
| FDF_LOG(ERROR, "fuchsia_hardware_serialimpl::Device Client is not valid"); |
| return zx::error(ZX_ERR_BAD_HANDLE); |
| } |
| |
| // pre-populate event packet indicators |
| event_buffer_[0] = kHciEvent; |
| event_buffer_offset_ = 1; |
| acl_buffer_[0] = kHciAclData; |
| acl_buffer_offset_ = 1; |
| sco_buffer_[0] = kHciSco; |
| sco_buffer_offset_ = 1; |
| } |
| |
| fdf::Arena arena('INIT'); |
| auto info_result = serial_client_.sync().buffer(arena)->GetInfo(); |
| if (!info_result.ok()) { |
| FDF_LOG(ERROR, "hci_start: GetInfo failed with FIDL error %s", info_result.status_string()); |
| return zx::error(info_result.status()); |
| } |
| if (info_result->is_error()) { |
| FDF_LOG(ERROR, "hci_start: GetInfo failed with error %s", |
| zx_status_get_string(info_result->error_value())); |
| return zx::error(info_result->error_value()); |
| } |
| |
| if (info_result.value()->info.serial_class != fuchsia_hardware_serial::Class::kBluetoothHci) { |
| FDF_LOG(ERROR, "hci_start: device class isn't BLUETOOTH_HCI"); |
| return zx::error(ZX_ERR_INTERNAL); |
| } |
| |
| serial_pid_ = info_result.value()->info.serial_pid; |
| |
| auto enable_result = serial_client_.sync().buffer(arena)->Enable(true); |
| if (!enable_result.ok()) { |
| FDF_LOG(ERROR, "hci_start: Enable failed with FIDL error %s", enable_result.status_string()); |
| return zx::error(enable_result.status()); |
| } |
| |
| if (enable_result->is_error()) { |
| FDF_LOG(ERROR, "hci_start: Enable failed with error %s", |
| zx_status_get_string(enable_result->error_value())); |
| return zx::error(enable_result->error_value()); |
| } |
| |
| queue_read_task_.Post(dispatcher_); |
| |
| zx_status_t status = ServeProtocols(); |
| if (status != ZX_OK) { |
| FDF_LOG(ERROR, "Failed to serve protocols: %s", zx_status_get_string(status)); |
| return zx::error(status); |
| } |
| |
| // Start compat device server to forward metadata. |
| zx::result compat_server_result = |
| compat_server_.Initialize(incoming(), outgoing(), node_name(), "bt-transport-uart", |
| compat::ForwardMetadata::Some({DEVICE_METADATA_MAC_ADDRESS})); |
| if (compat_server_result.is_error()) { |
| FDF_LOG(ERROR, "Failed to initialize device server: %s", compat_server_result.status_string()); |
| return compat_server_result.take_error(); |
| } |
| |
| // Add child node for the vendor driver to bind. |
| fidl::Arena args_arena; |
| |
| // Build offers |
| auto offers = compat_server_.CreateOffers2(args_arena); |
| offers.push_back(fdf::MakeOffer2<fuchsia_hardware_bluetooth::HciService>(args_arena)); |
| offers.push_back(fdf::MakeOffer2<fuchsia_hardware_serialimpl::Service>(args_arena)); |
| |
| auto args = fuchsia_driver_framework::wire::NodeAddArgs::Builder(args_arena) |
| .name("bt-transport-uart") |
| .offers2(std::move(offers)) |
| .Build(); |
| |
| auto controller_endpoints = fidl::CreateEndpoints<fuchsia_driver_framework::NodeController>(); |
| if (controller_endpoints.is_error()) { |
| FDF_LOG(ERROR, "Create node controller end points failed: %s", |
| zx_status_get_string(controller_endpoints.error_value())); |
| return zx::error(controller_endpoints.error_value()); |
| } |
| |
| // Add bt-transport-uart child node. |
| auto result = |
| node_.sync()->AddChild(std::move(args), std::move(controller_endpoints->server), {}); |
| if (!result.ok()) { |
| FDF_LOG(ERROR, "Failed to add bt-transport-uart node, FIDL error: %s", result.status_string()); |
| return zx::error(result.status()); |
| } |
| |
| if (result->is_error()) { |
| FDF_LOG(ERROR, "Failed to add bt-transport-uart node: %u", |
| static_cast<uint32_t>(result->error_value())); |
| return zx::error(ZX_ERR_INTERNAL); |
| } |
| |
| node_controller_.Bind(std::move(controller_endpoints->client), dispatcher(), this); |
| |
| return zx::ok(); |
| } |
| |
| void BtTransportUart::PrepareStop(fdf::PrepareStopCompleter completer) { |
| FDF_LOG(TRACE, "Unbind"); |
| |
| // We are now shutting down. Make sure that any pending callbacks in |
| // flight from the serial_impl are nerfed and that our thread is shut down. |
| std::atomic_store_explicit(&shutting_down_, true, std::memory_order_relaxed); |
| |
| { |
| std::lock_guard guard(mutex_); |
| |
| // Close the transport channels so that the host stack is notified of device |
| // removal and tasks aren't posted to work thread. |
| ChannelCleanupLocked(&cmd_channel_); |
| ChannelCleanupLocked(&acl_channel_); |
| ChannelCleanupLocked(&sco_channel_); |
| ChannelCleanupLocked(&snoop_channel_); |
| } |
| |
| // Finish by making sure that all in flight transactions transactions have |
| // been canceled. |
| fdf::Arena arena('CANC'); |
| auto result = serial_client_.sync().buffer(arena)->CancelAll(); |
| if (!result.ok()) { |
| FDF_LOG(ERROR, "hci_bind: CancelAll failed with FIDL error %s", result.status_string()); |
| completer(zx::error(result.status())); |
| } |
| |
| FDF_LOG(TRACE, "PrepareStop complete"); |
| |
| completer(zx::ok()); |
| } |
| |
| // NOLINTNEXTLINE(bugprone-easily-swappable-parameters) |
| BtTransportUart::Wait::Wait(BtTransportUart* uart, zx::channel* channel) { |
| this->state = ASYNC_STATE_INIT; |
| this->handler = Handler; |
| this->object = ZX_HANDLE_INVALID; |
| this->trigger = ZX_SIGNAL_NONE; |
| this->options = 0; |
| this->uart = uart; |
| this->channel = channel; |
| } |
| |
| void BtTransportUart::Wait::Handler(async_dispatcher_t* dispatcher, async_wait_t* async_wait, |
| zx_status_t status, const zx_packet_signal_t* signal) { |
| auto wait = static_cast<Wait*>(async_wait); |
| wait->uart->OnChannelSignal(wait, status, signal); |
| } |
| |
| size_t BtTransportUart::EventPacketLength() { |
| // payload length is in byte 2 of the packet |
| // add 3 bytes for packet indicator, event code and length byte |
| return event_buffer_offset_ > 2 ? event_buffer_[2] + 3 : 0; |
| } |
| |
| size_t BtTransportUart::AclPacketLength() { |
| // length is in bytes 3 and 4 of the packet |
| // add 5 bytes for packet indicator, control info and length fields |
| return acl_buffer_offset_ > 4 ? (acl_buffer_[3] | (acl_buffer_[4] << 8)) + 5 : 0; |
| } |
| |
| size_t BtTransportUart::ScoPacketLength() { |
| // payload length is byte 3 of the packet |
| // add 4 bytes for packet indicator, handle, and length byte |
| return sco_buffer_offset_ > 3 ? (sco_buffer_[3] + 4) : 0; |
| } |
| |
| void BtTransportUart::ChannelCleanupLocked(zx::channel* channel) { |
| if (!channel->is_valid()) { |
| return; |
| } |
| |
| if (channel == &cmd_channel_ && cmd_channel_wait_.pending) { |
| async_cancel_wait(dispatcher_, &cmd_channel_wait_); |
| cmd_channel_wait_.pending = false; |
| } else if (channel == &acl_channel_ && acl_channel_wait_.pending) { |
| async_cancel_wait(dispatcher_, &acl_channel_wait_); |
| acl_channel_wait_.pending = false; |
| } else if (channel == &sco_channel_ && sco_channel_wait_.pending) { |
| async_cancel_wait(dispatcher_, &sco_channel_wait_); |
| sco_channel_wait_.pending = false; |
| } |
| channel->reset(); |
| } |
| |
| void BtTransportUart::SnoopChannelWriteLocked(uint8_t flags, uint8_t* bytes, size_t length) { |
| if (!snoop_channel_.is_valid()) { |
| return; |
| } |
| |
| // We tack on a flags byte to the beginning of the payload. |
| // Use an iovec to avoid a large allocation + copy. |
| zx_channel_iovec_t iovs[2]; |
| iovs[0] = {.buffer = &flags, .capacity = sizeof(flags), .reserved = 0}; |
| iovs[1] = {.buffer = bytes, .capacity = static_cast<uint32_t>(length), .reserved = 0}; |
| |
| zx_status_t status = |
| snoop_channel_.write(/*flags=*/ZX_CHANNEL_WRITE_USE_IOVEC, /*bytes=*/iovs, |
| /*num_bytes=*/std::size(iovs), /*handles=*/nullptr, /*num_handles=*/0); |
| |
| if (status != ZX_OK) { |
| if (status != ZX_ERR_PEER_CLOSED) { |
| FDF_LOG(ERROR, "bt-transport-uart: failed to write to snoop channel: %s", |
| zx_status_get_string(status)); |
| } |
| |
| // It should be safe to clean up the channel right here as the work thread |
| // never waits on this channel from outside of the lock. |
| ChannelCleanupLocked(&snoop_channel_); |
| } |
| } |
| |
| void BtTransportUart::HciBeginShutdown() { |
| bool was_shutting_down = shutting_down_.exchange(true, std::memory_order_relaxed); |
| if (!was_shutting_down) { |
| auto result = node_.UnbindMaybeGetEndpoint(); |
| } |
| } |
| |
| void BtTransportUart::SerialWrite(uint8_t* buffer, size_t length) { |
| { |
| std::lock_guard guard(mutex_); |
| ZX_DEBUG_ASSERT(can_write_); |
| // Clear the can_write flag. The UART can currently only handle one in flight |
| // transaction at a time. |
| can_write_ = false; |
| } |
| |
| fdf::Arena arena('WRIT'); |
| auto data = fidl::VectorView<uint8_t>::FromExternal(buffer, length); |
| serial_client_.buffer(arena)->Write(data).ThenExactlyOnce( |
| [this](fdf::WireUnownedResult<fuchsia_hardware_serialimpl::Device::Write>& result) mutable { |
| if (!result.ok()) { |
| FDF_LOG(ERROR, "hci_bind: Write failed with FIDL error %s", result.status_string()); |
| HciWriteComplete(result.status()); |
| return; |
| } |
| |
| if (result->is_error()) { |
| FDF_LOG(ERROR, "hci_bind: Write failed with error %s", |
| zx_status_get_string(result->error_value())); |
| HciWriteComplete(result->error_value()); |
| return; |
| } |
| HciWriteComplete(ZX_OK); |
| }); |
| } |
| |
| // Returns false if there's an error while sending the packet to the hardware or |
| // if the channel peer closed its endpoint. |
| void BtTransportUart::HciHandleClientChannel(zx::channel* chan, zx_signals_t pending) { |
| // Channel may have been closed since signal was received. |
| if (!chan->is_valid()) { |
| return; |
| } |
| |
| // Figure out which channel we are dealing with and the constants which go |
| // along with it. |
| uint32_t max_buf_size; |
| BtHciPacketIndicator packet_type; |
| bt_hci_snoop_type_t snoop_type; |
| const char* chan_name = nullptr; |
| |
| if (chan == &cmd_channel_) { |
| max_buf_size = kCmdBufSize; |
| packet_type = kHciCommand; |
| snoop_type = BT_HCI_SNOOP_TYPE_CMD; |
| chan_name = "command"; |
| } else if (chan == &acl_channel_) { |
| max_buf_size = kAclMaxFrameSize; |
| packet_type = kHciAclData; |
| snoop_type = BT_HCI_SNOOP_TYPE_ACL; |
| chan_name = "ACL"; |
| } else if (chan == &sco_channel_) { |
| max_buf_size = kScoMaxFrameSize; |
| packet_type = kHciSco; |
| snoop_type = BT_HCI_SNOOP_TYPE_SCO; |
| chan_name = "SCO"; |
| } else { |
| // This should never happen, we only know about three packet types currently. |
| ZX_ASSERT(false); |
| return; |
| } |
| |
| // Handle the read signal first. If we are also peer closed, we want to make |
| // sure that we have processed all of the pending messages before cleaning up. |
| if (pending & ZX_CHANNEL_READABLE) { |
| FDF_LOG(TRACE, "received readable signal for %s channel", chan_name); |
| uint32_t length = max_buf_size - 1; |
| { |
| std::lock_guard guard(mutex_); |
| |
| // Do not proceed if we are not allowed to write. Let the work thread call |
| // us back again when it is safe to write. |
| if (!can_write_) { |
| return; |
| } |
| |
| zx_status_t status; |
| |
| status = |
| zx_channel_read(chan->get(), 0, write_buffer_ + 1, nullptr, length, 0, &length, nullptr); |
| if (status == ZX_ERR_SHOULD_WAIT) { |
| FDF_LOG(WARNING, "ignoring ZX_ERR_SHOULD_WAIT when reading %s channel", chan_name); |
| return; |
| } |
| if (status != ZX_OK) { |
| FDF_LOG(ERROR, "hci_read_thread: failed to read from %s channel %s", chan_name, |
| zx_status_get_string(status)); |
| ChannelCleanupLocked(chan); |
| return; |
| } |
| |
| write_buffer_[0] = packet_type; |
| length++; |
| |
| SnoopChannelWriteLocked(bt_hci_snoop_flags(snoop_type, false), write_buffer_ + 1, length - 1); |
| } |
| |
| SerialWrite(write_buffer_, length); |
| } |
| |
| if (pending & ZX_CHANNEL_PEER_CLOSED) { |
| FDF_LOG(DEBUG, "received closed signal for %s channel", chan_name); |
| std::lock_guard guard(mutex_); |
| ChannelCleanupLocked(chan); |
| } |
| } |
| |
| void BtTransportUart::HciHandleUartReadEvents(const uint8_t* buf, size_t length) { |
| const uint8_t* const end = buf + length; |
| while (buf < end) { |
| if (cur_uart_packet_type_ == kHciNone) { |
| // start of new packet. read packet type |
| cur_uart_packet_type_ = static_cast<BtHciPacketIndicator>(*buf++); |
| } |
| |
| switch (cur_uart_packet_type_) { |
| case kHciEvent: |
| ProcessNextUartPacketFromReadBuffer( |
| event_buffer_, sizeof(event_buffer_), &event_buffer_offset_, &buf, end, |
| &BtTransportUart::EventPacketLength, &cmd_channel_, BT_HCI_SNOOP_TYPE_EVT); |
| break; |
| case kHciAclData: |
| ProcessNextUartPacketFromReadBuffer(acl_buffer_, sizeof(acl_buffer_), &acl_buffer_offset_, |
| &buf, end, &BtTransportUart::AclPacketLength, |
| &acl_channel_, BT_HCI_SNOOP_TYPE_ACL); |
| break; |
| case kHciSco: |
| ProcessNextUartPacketFromReadBuffer(sco_buffer_, sizeof(sco_buffer_), &sco_buffer_offset_, |
| &buf, end, &BtTransportUart::ScoPacketLength, |
| &sco_channel_, BT_HCI_SNOOP_TYPE_SCO); |
| break; |
| default: |
| FDF_LOG(ERROR, "unsupported HCI packet type %u received. We may be out of sync", |
| cur_uart_packet_type_); |
| cur_uart_packet_type_ = kHciNone; |
| return; |
| } |
| } |
| } |
| |
| void BtTransportUart::ProcessNextUartPacketFromReadBuffer( |
| uint8_t* buffer, size_t buffer_size, size_t* buffer_offset, const uint8_t** uart_src, |
| const uint8_t* uart_end, PacketLengthFunction get_packet_length, zx::channel* channel, |
| bt_hci_snoop_type_t snoop_type) { |
| size_t packet_length = (this->*get_packet_length)(); |
| |
| while (!packet_length && *uart_src < uart_end) { |
| // read until we have enough to compute packet length |
| buffer[*buffer_offset] = **uart_src; |
| (*buffer_offset)++; |
| (*uart_src)++; |
| packet_length = (this->*get_packet_length)(); |
| } |
| |
| // Out of bytes, but we still don't know the packet length. Just wait for |
| // the next packet. |
| if (!packet_length) { |
| return; |
| } |
| |
| if (packet_length > buffer_size) { |
| FDF_LOG(ERROR, |
| "packet_length is too large (%zu > %zu) during packet reassembly. Dropping and " |
| "attempting to re-sync.", |
| packet_length, buffer_size); |
| |
| // Reset the reassembly state machine. |
| *buffer_offset = 1; |
| cur_uart_packet_type_ = kHciNone; |
| // Consume the rest of the UART buffer to indicate that it is corrupt. |
| *uart_src = uart_end; |
| return; |
| } |
| |
| size_t remaining = uart_end - *uart_src; |
| size_t copy_size = packet_length - *buffer_offset; |
| if (copy_size > remaining) { |
| copy_size = remaining; |
| } |
| |
| ZX_ASSERT(*buffer_offset + copy_size <= buffer_size); |
| memcpy(buffer + *buffer_offset, *uart_src, copy_size); |
| *uart_src += copy_size; |
| *buffer_offset += copy_size; |
| |
| if (*buffer_offset != packet_length) { |
| // The packet is incomplete, the next chunk should continue the same packet. |
| return; |
| } |
| |
| std::lock_guard guard(mutex_); |
| |
| // Attempt to send this packet to the channel. We are working on the callback thread from the |
| // UART, so we need to do this inside of the lock to make sure that nothing closes the channel |
| // out from under us while we try to write. If something goes wrong here, close the channel. |
| if (channel->is_valid()) { |
| zx_status_t status = channel->write(/*flags=*/0, &buffer[1], packet_length - 1, nullptr, 0); |
| if (status != ZX_OK) { |
| FDF_LOG(ERROR, "failed to write packet: %s", zx_status_get_string(status)); |
| ChannelCleanupLocked(&acl_channel_); |
| } |
| } |
| |
| // If the snoop channel is open then try to write the packet even if |channel| was closed. |
| SnoopChannelWriteLocked(bt_hci_snoop_flags(snoop_type, true), &buffer[1], packet_length - 1); |
| |
| // reset buffer |
| cur_uart_packet_type_ = kHciNone; |
| *buffer_offset = 1; |
| } |
| |
| void BtTransportUart::HciReadComplete(zx_status_t status, const uint8_t* buffer, size_t length) { |
| FDF_LOG(TRACE, "Read complete with status: %s", zx_status_get_string(status)); |
| |
| // If we are in the process of shutting down, we are done. |
| if (atomic_load_explicit(&shutting_down_, std::memory_order_relaxed)) { |
| return; |
| } |
| |
| if (status == ZX_OK) { |
| HciHandleUartReadEvents(buffer, length); |
| queue_read_task_.Post(dispatcher_); |
| } else { |
| // There is not much we can do in the event of a UART read error. Do not |
| // queue a read job and start the process of shutting down. |
| FDF_LOG(ERROR, "Fatal UART read error (%s), shutting down", zx_status_get_string(status)); |
| HciBeginShutdown(); |
| } |
| } |
| |
| void BtTransportUart::HciWriteComplete(zx_status_t status) { |
| FDF_LOG(TRACE, "Write complete with status: %s", zx_status_get_string(status)); |
| |
| // If we are in the process of shutting down, we are done as soon as we |
| // have freed our operation. |
| if (atomic_load_explicit(&shutting_down_, std::memory_order_relaxed)) { |
| return; |
| } |
| |
| if (status != ZX_OK) { |
| HciBeginShutdown(); |
| return; |
| } |
| |
| // We can write now. |
| { |
| std::lock_guard guard(mutex_); |
| can_write_ = true; |
| |
| // Resume waiting for channel signals. If a packet was queued while the write was processing, |
| // it should be immediately signaled. |
| if (cmd_channel_wait_.channel->is_valid() && !cmd_channel_wait_.pending) { |
| ZX_ASSERT(async_begin_wait(dispatcher_, &cmd_channel_wait_) == ZX_OK); |
| cmd_channel_wait_.pending = true; |
| } |
| if (acl_channel_wait_.channel->is_valid() && !acl_channel_wait_.pending) { |
| ZX_ASSERT(async_begin_wait(dispatcher_, &acl_channel_wait_) == ZX_OK); |
| acl_channel_wait_.pending = true; |
| } |
| if (sco_channel_wait_.channel->is_valid() && !sco_channel_wait_.pending) { |
| ZX_ASSERT(async_begin_wait(dispatcher_, &sco_channel_wait_) == ZX_OK); |
| sco_channel_wait_.pending = true; |
| } |
| } |
| } |
| |
| void BtTransportUart::OnChannelSignal(Wait* wait, zx_status_t status, |
| const zx_packet_signal_t* signal) { |
| { |
| std::lock_guard guard(mutex_); |
| wait->pending = false; |
| } |
| |
| HciHandleClientChannel(wait->channel, signal->observed); |
| |
| // The readable signal wait will be re-enabled in the write completion callback. |
| } |
| |
| zx_status_t BtTransportUart::HciOpenChannel(zx::channel* in_channel, zx_handle_t in) { |
| std::lock_guard guard(mutex_); |
| zx_status_t result = ZX_OK; |
| |
| if (in_channel->is_valid()) { |
| FDF_LOG(ERROR, "bt-transport-uart: already bound, failing"); |
| result = ZX_ERR_ALREADY_BOUND; |
| return result; |
| } |
| |
| in_channel->reset(in); |
| |
| Wait* wait = nullptr; |
| if (in_channel == &cmd_channel_) { |
| FDF_LOG(DEBUG, "opening command channel"); |
| wait = &cmd_channel_wait_; |
| } else if (in_channel == &acl_channel_) { |
| FDF_LOG(DEBUG, "opening ACL channel"); |
| wait = &acl_channel_wait_; |
| } else if (in_channel == &sco_channel_) { |
| FDF_LOG(DEBUG, "opening SCO channel"); |
| wait = &sco_channel_wait_; |
| } else if (in_channel == &snoop_channel_) { |
| FDF_LOG(DEBUG, "opening snoop channel"); |
| // TODO(https://fxbug.dev/42172901): Handle snoop channel closed signal. |
| return ZX_OK; |
| } |
| ZX_ASSERT(wait); |
| wait->object = in_channel->get(); |
| wait->trigger = ZX_CHANNEL_READABLE | ZX_CHANNEL_PEER_CLOSED; |
| ZX_ASSERT(async_begin_wait(dispatcher_, wait) == ZX_OK); |
| wait->pending = true; |
| return result; |
| } |
| |
| void BtTransportUart::OpenCommandChannel(OpenCommandChannelRequestView request, |
| OpenCommandChannelCompleter::Sync& completer) { |
| if (zx_status_t status = HciOpenChannel(&cmd_channel_, std::move(request->channel.release())); |
| status != ZX_OK) { |
| FDF_LOG(ERROR, "Failed to open command channel: %s", zx_status_get_string(status)); |
| completer.ReplyError(status); |
| return; |
| } |
| completer.ReplySuccess(); |
| } |
| void BtTransportUart::OpenAclDataChannel(OpenAclDataChannelRequestView request, |
| OpenAclDataChannelCompleter::Sync& completer) { |
| if (zx_status_t status = HciOpenChannel(&acl_channel_, std::move(request->channel.release())); |
| status != ZX_OK) { |
| FDF_LOG(ERROR, "Failed to open acl channel: %s", zx_status_get_string(status)); |
| completer.ReplyError(status); |
| return; |
| } |
| completer.ReplySuccess(); |
| } |
| void BtTransportUart::OpenSnoopChannel(OpenSnoopChannelRequestView request, |
| OpenSnoopChannelCompleter::Sync& completer) { |
| if (zx_status_t status = HciOpenChannel(&snoop_channel_, std::move(request->channel.release())); |
| status != ZX_OK) { |
| FDF_LOG(ERROR, "Failed to open snoop channel: %s", zx_status_get_string(status)); |
| completer.ReplyError(status); |
| return; |
| } |
| completer.ReplySuccess(); |
| } |
| void BtTransportUart::OpenScoDataChannel(OpenScoDataChannelRequestView request, |
| OpenScoDataChannelCompleter::Sync& completer) { |
| if (zx_status_t status = HciOpenChannel(&sco_channel_, std::move(request->channel.release())); |
| status != ZX_OK) { |
| FDF_LOG(ERROR, "Failed to open sco channel: %s", zx_status_get_string(status)); |
| completer.ReplyError(status); |
| return; |
| } |
| completer.ReplySuccess(); |
| } |
| void BtTransportUart::OpenIsoDataChannel(OpenIsoDataChannelRequestView request, |
| OpenIsoDataChannelCompleter::Sync& completer) { |
| completer.ReplyError(ZX_ERR_NOT_SUPPORTED); |
| } |
| void BtTransportUart::ConfigureSco(ConfigureScoRequestView request, |
| ConfigureScoCompleter::Sync& completer) { |
| // UART doesn't require any SCO configuration. |
| completer.ReplySuccess(); |
| } |
| void BtTransportUart::ResetSco(ResetScoCompleter::Sync& completer) { |
| // UART doesn't require any SCO configuration, so there's nothing to do. |
| completer.ReplySuccess(); |
| } |
| void BtTransportUart::handle_unknown_method( |
| fidl::UnknownMethodMetadata<fuchsia_hardware_bluetooth::Hci> metadata, |
| fidl::UnknownMethodCompleter::Sync& completer) { |
| FDF_LOG(ERROR, "Unknown method in Hci protocol, closing with ZX_ERR_NOT_SUPPORTED"); |
| completer.Close(ZX_ERR_NOT_SUPPORTED); |
| } |
| |
| void BtTransportUart::GetInfo(fdf::Arena& arena, GetInfoCompleter::Sync& completer) { |
| fuchsia_hardware_serial::wire::SerialPortInfo info{ |
| .serial_class = fuchsia_hardware_serial::wire::Class::kBluetoothHci, |
| .serial_pid = serial_pid_, |
| }; |
| completer.buffer(arena).ReplySuccess(info); |
| } |
| void BtTransportUart::Config(ConfigRequestView request, fdf::Arena& arena, |
| ConfigCompleter::Sync& completer) { |
| auto result = serial_client_.sync().buffer(arena)->Config( |
| request->baud_rate, fuchsia_hardware_serialimpl::wire::kSerialSetBaudRateOnly); |
| if (!result.ok()) { |
| FDF_LOG(ERROR, "Config request failed with FIDL error %s", result.status_string()); |
| completer.buffer(arena).ReplyError(result.status()); |
| return; |
| } |
| if (result->is_error()) { |
| FDF_LOG(ERROR, "Config request failed with error %s", |
| zx_status_get_string(result->error_value())); |
| completer.buffer(arena).ReplyError(result->error_value()); |
| return; |
| } |
| completer.buffer(arena).ReplySuccess(); |
| } |
| |
| void BtTransportUart::Enable(EnableRequestView request, fdf::Arena& arena, |
| EnableCompleter::Sync& completer) { |
| completer.buffer(arena).ReplyError(ZX_ERR_NOT_SUPPORTED); |
| } |
| |
| void BtTransportUart::Read(fdf::Arena& arena, ReadCompleter::Sync& completer) { |
| completer.buffer(arena).ReplyError(ZX_ERR_NOT_SUPPORTED); |
| } |
| |
| void BtTransportUart::Write(WriteRequestView request, fdf::Arena& arena, |
| WriteCompleter::Sync& completer) { |
| completer.buffer(arena).ReplyError(ZX_ERR_NOT_SUPPORTED); |
| } |
| |
| void BtTransportUart::CancelAll(fdf::Arena& arena, CancelAllCompleter::Sync& completer) { |
| completer.buffer(arena).Reply(); |
| } |
| |
| void BtTransportUart::handle_unknown_method( |
| fidl::UnknownMethodMetadata<fuchsia_hardware_serialimpl::Device> metadata, |
| fidl::UnknownMethodCompleter::Sync& completer) { |
| FDF_LOG( |
| ERROR, |
| "Unknown method in fuchsia_hardware_serialimpl::Device protocol, closing with ZX_ERR_NOT_SUPPORTED"); |
| completer.Close(ZX_ERR_NOT_SUPPORTED); |
| } |
| |
| void BtTransportUart::QueueUartRead() { |
| fdf::Arena arena('READ'); |
| serial_client_.buffer(arena)->Read().ThenExactlyOnce([this](fdf::WireUnownedResult< |
| fuchsia_hardware_serialimpl::Device:: |
| Read>& result) { |
| if (!result.ok()) { |
| FDF_LOG(ERROR, "Read request failed with FIDL error %s", result.status_string()); |
| HciReadComplete(result.status(), nullptr, 0); |
| return; |
| } |
| |
| if (result->is_error()) { |
| if (result->error_value() == ZX_ERR_CANCELED) { |
| FDF_LOG( |
| WARNING, |
| "Read request is cancel by the bus driver, it's likely the serial driver is de-initialized."); |
| } else { |
| FDF_LOG(ERROR, "Read request failed with error %s", |
| zx_status_get_string(result->error_value())); |
| } |
| HciReadComplete(result->error_value(), nullptr, 0); |
| return; |
| } |
| HciReadComplete(ZX_OK, result->value()->data.data(), result->value()->data.count()); |
| }); |
| } |
| |
| zx_status_t BtTransportUart::ServeProtocols() { |
| // Add HCI service to the outgoing directory. |
| auto hci_protocol = [this](fidl::ServerEnd<fuchsia_hardware_bluetooth::Hci> server_end) mutable { |
| fidl::BindServer(dispatcher_, std::move(server_end), this); |
| }; |
| fuchsia_hardware_bluetooth::HciService::InstanceHandler hci_handler( |
| {.hci = std::move(hci_protocol)}); |
| auto status = |
| outgoing()->AddService<fuchsia_hardware_bluetooth::HciService>(std::move(hci_handler)); |
| if (status.is_error()) { |
| FDF_LOG(ERROR, "Failed to add HCI service to outgoing directory: %s\n", status.status_string()); |
| return status.error_value(); |
| } |
| |
| // Add Serial service to the outgoing directory. |
| auto serial_protocol = |
| [this](fdf::ServerEnd<fuchsia_hardware_serialimpl::Device> server_end) mutable { |
| fdf::BindServer(driver_dispatcher()->get(), std::move(server_end), this); |
| }; |
| fuchsia_hardware_serialimpl::Service::InstanceHandler serial_handler( |
| {.device = std::move(serial_protocol)}); |
| status = outgoing()->AddService<fuchsia_hardware_serialimpl::Service>(std::move(serial_handler)); |
| if (status.is_error()) { |
| FDF_LOG(ERROR, "Failed to add Serial service to outgoing directory: %s\n", |
| status.status_string()); |
| return status.error_value(); |
| } |
| |
| return ZX_OK; |
| } |
| |
| } // namespace bt_transport_uart |
| |
| FUCHSIA_DRIVER_EXPORT(bt_transport_uart::BtTransportUart); |