From 10d29e493427b2280233a730c137765c607b67f9 Mon Sep 17 00:00:00 2001 From: Myles Watson Date: Wed, 3 Aug 2022 16:59:48 -0700 Subject: [PATCH 1/3] Bluetooth HIDL -> AIDL conversion Bug: 205758693 Test: updated vts tests Change-Id: Iac4d387dba3715d17c63369f392d091e9228333a --- bluetooth/aidl/Android.bp | 27 +++++++ .../hardware/bluetooth/IBluetoothHci.aidl | 43 +++++++++++ .../bluetooth/IBluetoothHciCallbacks.aidl | 42 +++++++++++ .../android/hardware/bluetooth/Status.aidl | 42 +++++++++++ .../hardware/bluetooth/IBluetoothHci.aidl | 73 +++++++++++++++++++ .../bluetooth/IBluetoothHciCallbacks.aidl | 56 ++++++++++++++ .../android/hardware/bluetooth/Status.aidl | 27 +++++++ .../compatibility_matrix.current.xml | 7 ++ 8 files changed, 317 insertions(+) create mode 100644 bluetooth/aidl/Android.bp create mode 100644 bluetooth/aidl/aidl_api/android.hardware.bluetooth/current/android/hardware/bluetooth/IBluetoothHci.aidl create mode 100644 bluetooth/aidl/aidl_api/android.hardware.bluetooth/current/android/hardware/bluetooth/IBluetoothHciCallbacks.aidl create mode 100644 bluetooth/aidl/aidl_api/android.hardware.bluetooth/current/android/hardware/bluetooth/Status.aidl create mode 100644 bluetooth/aidl/android/hardware/bluetooth/IBluetoothHci.aidl create mode 100644 bluetooth/aidl/android/hardware/bluetooth/IBluetoothHciCallbacks.aidl create mode 100644 bluetooth/aidl/android/hardware/bluetooth/Status.aidl diff --git a/bluetooth/aidl/Android.bp b/bluetooth/aidl/Android.bp new file mode 100644 index 0000000000..e5ae0075e8 --- /dev/null +++ b/bluetooth/aidl/Android.bp @@ -0,0 +1,27 @@ +// This is the expected build file, but it may not be right in all cases + +aidl_interface { + name: "android.hardware.bluetooth", + vendor_available: true, + srcs: ["android/hardware/bluetooth/*.aidl"], + stability: "vintf", + backend: { + cpp: { + // FIXME should this be disabled? + // prefer NDK backend which can be used anywhere + // If you disable this, you also need to delete the C++ + // translate code. + enabled: true, + }, + java: { + sdk_version: "module_current", + }, + ndk: { + apex_available: [ + "//apex_available:platform", + "com.android.btservices", + ], + min_sdk_version: "33", + }, + }, +} diff --git a/bluetooth/aidl/aidl_api/android.hardware.bluetooth/current/android/hardware/bluetooth/IBluetoothHci.aidl b/bluetooth/aidl/aidl_api/android.hardware.bluetooth/current/android/hardware/bluetooth/IBluetoothHci.aidl new file mode 100644 index 0000000000..8b1cad2775 --- /dev/null +++ b/bluetooth/aidl/aidl_api/android.hardware.bluetooth/current/android/hardware/bluetooth/IBluetoothHci.aidl @@ -0,0 +1,43 @@ +/* + * Copyright 2022 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. + */ +/////////////////////////////////////////////////////////////////////////////// +// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // +/////////////////////////////////////////////////////////////////////////////// + +// This file is a snapshot of an AIDL file. Do not edit it manually. There are +// two cases: +// 1). this is a frozen version file - do not edit this in any case. +// 2). this is a 'current' file. If you make a backwards compatible change to +// the interface (from the latest frozen version), the build system will +// prompt you to update this file with `m -update-api`. +// +// You must not make a backward incompatible change to any AIDL file built +// with the aidl_interface module type with versions property set. The module +// type is used to build AIDL files in a way that they can be used across +// independently updatable components of the system. If a device is shipped +// with such a backward incompatible change, it has a high risk of breaking +// later when a module using the interface is updated, e.g., Mainline modules. + +package android.hardware.bluetooth; +@VintfStability +interface IBluetoothHci { + void close(); + void initialize(in android.hardware.bluetooth.IBluetoothHciCallbacks callback); + void sendAclData(in byte[] data); + void sendHciCommand(in byte[] command); + void sendIsoData(in byte[] data); + void sendScoData(in byte[] data); +} diff --git a/bluetooth/aidl/aidl_api/android.hardware.bluetooth/current/android/hardware/bluetooth/IBluetoothHciCallbacks.aidl b/bluetooth/aidl/aidl_api/android.hardware.bluetooth/current/android/hardware/bluetooth/IBluetoothHciCallbacks.aidl new file mode 100644 index 0000000000..aecff7f2e3 --- /dev/null +++ b/bluetooth/aidl/aidl_api/android.hardware.bluetooth/current/android/hardware/bluetooth/IBluetoothHciCallbacks.aidl @@ -0,0 +1,42 @@ +/* + * Copyright 2022 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. + */ +/////////////////////////////////////////////////////////////////////////////// +// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // +/////////////////////////////////////////////////////////////////////////////// + +// This file is a snapshot of an AIDL file. Do not edit it manually. There are +// two cases: +// 1). this is a frozen version file - do not edit this in any case. +// 2). this is a 'current' file. If you make a backwards compatible change to +// the interface (from the latest frozen version), the build system will +// prompt you to update this file with `m -update-api`. +// +// You must not make a backward incompatible change to any AIDL file built +// with the aidl_interface module type with versions property set. The module +// type is used to build AIDL files in a way that they can be used across +// independently updatable components of the system. If a device is shipped +// with such a backward incompatible change, it has a high risk of breaking +// later when a module using the interface is updated, e.g., Mainline modules. + +package android.hardware.bluetooth; +@VintfStability +interface IBluetoothHciCallbacks { + void aclDataReceived(in byte[] data); + void hciEventReceived(in byte[] event); + void initializationComplete(in android.hardware.bluetooth.Status status); + void isoDataReceived(in byte[] data); + void scoDataReceived(in byte[] data); +} diff --git a/bluetooth/aidl/aidl_api/android.hardware.bluetooth/current/android/hardware/bluetooth/Status.aidl b/bluetooth/aidl/aidl_api/android.hardware.bluetooth/current/android/hardware/bluetooth/Status.aidl new file mode 100644 index 0000000000..da429b884d --- /dev/null +++ b/bluetooth/aidl/aidl_api/android.hardware.bluetooth/current/android/hardware/bluetooth/Status.aidl @@ -0,0 +1,42 @@ +/* + * Copyright 2022 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. + */ +/////////////////////////////////////////////////////////////////////////////// +// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // +/////////////////////////////////////////////////////////////////////////////// + +// This file is a snapshot of an AIDL file. Do not edit it manually. There are +// two cases: +// 1). this is a frozen version file - do not edit this in any case. +// 2). this is a 'current' file. If you make a backwards compatible change to +// the interface (from the latest frozen version), the build system will +// prompt you to update this file with `m -update-api`. +// +// You must not make a backward incompatible change to any AIDL file built +// with the aidl_interface module type with versions property set. The module +// type is used to build AIDL files in a way that they can be used across +// independently updatable components of the system. If a device is shipped +// with such a backward incompatible change, it has a high risk of breaking +// later when a module using the interface is updated, e.g., Mainline modules. + +package android.hardware.bluetooth; +@Backing(type="int") @VintfStability +enum Status { + SUCCESS = 0, + ALREADY_INITIALIZED = 1, + UNABLE_TO_OPEN_INTERFACE = 2, + HARDWARE_INITIALIZATION_ERROR = 3, + UNKNOWN = 4, +} diff --git a/bluetooth/aidl/android/hardware/bluetooth/IBluetoothHci.aidl b/bluetooth/aidl/android/hardware/bluetooth/IBluetoothHci.aidl new file mode 100644 index 0000000000..db12986b50 --- /dev/null +++ b/bluetooth/aidl/android/hardware/bluetooth/IBluetoothHci.aidl @@ -0,0 +1,73 @@ +/* + * Copyright 2022 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. + */ + +package android.hardware.bluetooth; + +import android.hardware.bluetooth.IBluetoothHciCallbacks; + +/** + * The Host Controller Interface (HCI) is the layer defined by the Bluetooth + * specification between the software that runs on the host and the Bluetooth + * controller chip. This boundary is the natural choice for a Hardware + * Abstraction Layer (HAL). Dealing only in HCI packets and events simplifies + * the stack and abstracts away power management, initialization, and other + * implementation-specific details related to the hardware. + */ +@VintfStability +interface IBluetoothHci { + /** + * Close the HCI interface + */ + void close(); + + /** + * Initialize the Bluetooth interface and set the callbacks. + */ + void initialize(in IBluetoothHciCallbacks callback); + + /** + * Send an HCI ACL data packet (as specified in the Bluetooth Specification + * V4.2, Vol 2, Part 5, Section 5.4.2) to the Bluetooth controller. + * Packets must be processed in order. + * @param data HCI data packet to be sent + */ + void sendAclData(in byte[] data); + + /** + * Send an HCI command (as specified in the Bluetooth Specification + * V4.2, Vol 2, Part 5, Section 5.4.1) to the Bluetooth controller. + * Commands must be executed in order. + * + * @param command is the HCI command to be sent + */ + void sendHciCommand(in byte[] command); + + /** + * Send an ISO data packet (as specified in the Bluetooth Core + * Specification v5.2) to the Bluetooth controller. + * Packets must be processed in order. + * @param data HCI data packet to be sent + */ + void sendIsoData(in byte[] data); + + /** + * Send an SCO data packet (as specified in the Bluetooth Specification + * V4.2, Vol 2, Part 5, Section 5.4.3) to the Bluetooth controller. + * Packets must be processed in order. + * @param data HCI data packet to be sent + */ + void sendScoData(in byte[] data); +} diff --git a/bluetooth/aidl/android/hardware/bluetooth/IBluetoothHciCallbacks.aidl b/bluetooth/aidl/android/hardware/bluetooth/IBluetoothHciCallbacks.aidl new file mode 100644 index 0000000000..000333e2ca --- /dev/null +++ b/bluetooth/aidl/android/hardware/bluetooth/IBluetoothHciCallbacks.aidl @@ -0,0 +1,56 @@ +/* + * Copyright 2022 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. + */ + +package android.hardware.bluetooth; + +import android.hardware.bluetooth.Status; + +/** + * The interface from the Bluetooth Controller to the stack. + */ +@VintfStability +interface IBluetoothHciCallbacks { + /** + * Send an ACL data packet from the controller to the host. + * @param data the ACL HCI packet to be passed to the host stack + */ + void aclDataReceived(in byte[] data); + + /** + * This function is invoked when an HCI event is received from the + * Bluetooth controller to be forwarded to the Bluetooth stack. + * @param event is the HCI event to be sent to the Bluetooth stack. + */ + void hciEventReceived(in byte[] event); + + /** + * Invoked when the Bluetooth controller initialization has been + * completed. + */ + void initializationComplete(in Status status); + + /** + * Send a ISO data packet from the controller to the host. + * @param data the ISO HCI packet to be passed to the host stack + */ + void isoDataReceived(in byte[] data); + + /** + * Send a SCO data packet from the controller to the host. + * @param data the SCO HCI packet to be passed to the host stack + */ + void scoDataReceived(in byte[] data); +} diff --git a/bluetooth/aidl/android/hardware/bluetooth/Status.aidl b/bluetooth/aidl/android/hardware/bluetooth/Status.aidl new file mode 100644 index 0000000000..4ec251a913 --- /dev/null +++ b/bluetooth/aidl/android/hardware/bluetooth/Status.aidl @@ -0,0 +1,27 @@ +/* + * Copyright 2022 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. + */ + +package android.hardware.bluetooth; + +@VintfStability +@Backing(type="int") +enum Status { + SUCCESS, + ALREADY_INITIALIZED, + UNABLE_TO_OPEN_INTERFACE, + HARDWARE_INITIALIZATION_ERROR, + UNKNOWN, +} diff --git a/compatibility_matrices/compatibility_matrix.current.xml b/compatibility_matrices/compatibility_matrix.current.xml index d4be2bf860..59b4b220fb 100644 --- a/compatibility_matrices/compatibility_matrix.current.xml +++ b/compatibility_matrices/compatibility_matrix.current.xml @@ -144,6 +144,13 @@ default + + android.hardware.bluetooth + + IBluetoothHci + default + + android.hardware.bluetooth.audio 2 From e4501e549c52ad96ff3dbb9d8e32856e1a2c8262 Mon Sep 17 00:00:00 2001 From: Myles Watson Date: Fri, 30 Sep 2022 06:20:50 -0700 Subject: [PATCH 2/3] Start with async and HCI Bug: 205758693 Test: unit tests Change-Id: I16a8be44bce5f2d233582ab6db17c30d068fa9c0 --- bluetooth/aidl/Android.bp | 1 + bluetooth/async/Android.bp | 41 ++ bluetooth/async/async_fd_watcher.cc | 176 +++++++ bluetooth/async/async_fd_watcher.h | 60 +++ .../async/test/async_fd_watcher_unittest.cc | 377 +++++++++++++++ bluetooth/hci/Android.bp | 46 ++ bluetooth/hci/h4_protocol.cc | 144 ++++++ bluetooth/hci/h4_protocol.h | 68 +++ bluetooth/hci/hci_internals.h | 55 +++ bluetooth/hci/hci_packetizer.cc | 100 ++++ bluetooth/hci/hci_packetizer.h | 41 ++ bluetooth/hci/test/h4_protocol_unittest.cc | 441 ++++++++++++++++++ 12 files changed, 1550 insertions(+) create mode 100644 bluetooth/async/Android.bp create mode 100644 bluetooth/async/async_fd_watcher.cc create mode 100644 bluetooth/async/async_fd_watcher.h create mode 100644 bluetooth/async/test/async_fd_watcher_unittest.cc create mode 100644 bluetooth/hci/Android.bp create mode 100644 bluetooth/hci/h4_protocol.cc create mode 100644 bluetooth/hci/h4_protocol.h create mode 100644 bluetooth/hci/hci_internals.h create mode 100644 bluetooth/hci/hci_packetizer.cc create mode 100644 bluetooth/hci/hci_packetizer.h create mode 100644 bluetooth/hci/test/h4_protocol_unittest.cc diff --git a/bluetooth/aidl/Android.bp b/bluetooth/aidl/Android.bp index e5ae0075e8..07a4c7f27c 100644 --- a/bluetooth/aidl/Android.bp +++ b/bluetooth/aidl/Android.bp @@ -3,6 +3,7 @@ aidl_interface { name: "android.hardware.bluetooth", vendor_available: true, + host_supported: true, srcs: ["android/hardware/bluetooth/*.aidl"], stability: "vintf", backend: { diff --git a/bluetooth/async/Android.bp b/bluetooth/async/Android.bp new file mode 100644 index 0000000000..bd7d7b8ad0 --- /dev/null +++ b/bluetooth/async/Android.bp @@ -0,0 +1,41 @@ +package { + default_applicable_licenses: ["hardware_interfaces_license"], +} + +cc_library_static { + name: "android.hardware.bluetooth.async", + vendor_available: true, + host_supported: true, + srcs: [ + "async_fd_watcher.cc", + ], + export_include_dirs: ["."], + shared_libs: [ + "liblog", + ], + cflags: [ + "-Wall", + "-Werror", + ], +} + +cc_test { + name: "bluetooth-vendor-interface-async-test", + host_supported: true, + srcs: [ + "test/async_fd_watcher_unittest.cc", + ], + shared_libs: [ + "liblog", + "libutils", + ], + static_libs: [ + "android.hardware.bluetooth.async", + "libgmock", + ], + cflags: [ + "-Wall", + "-Werror", + ], + test_suites: ["general-tests"], +} diff --git a/bluetooth/async/async_fd_watcher.cc b/bluetooth/async/async_fd_watcher.cc new file mode 100644 index 0000000000..fb634ffe84 --- /dev/null +++ b/bluetooth/async/async_fd_watcher.cc @@ -0,0 +1,176 @@ +/* + * Copyright 2022 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 "async_fd_watcher.h" + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "fcntl.h" +#include "log/log.h" +#include "sys/select.h" +#include "unistd.h" + +static const int INVALID_FD = -1; + +namespace android::hardware::bluetooth::async { + +int AsyncFdWatcher::WatchFdForNonBlockingReads( + int file_descriptor, const ReadCallback& on_read_fd_ready_callback) { + // Add file descriptor and callback + { + std::unique_lock guard(internal_mutex_); + watched_fds_[file_descriptor] = on_read_fd_ready_callback; + } + + // Start the thread if not started yet + return tryStartThread(); +} + +int AsyncFdWatcher::ConfigureTimeout( + const std::chrono::milliseconds timeout, + const TimeoutCallback& on_timeout_callback) { + // Add timeout and callback + { + std::unique_lock guard(timeout_mutex_); + timeout_cb_ = on_timeout_callback; + timeout_ms_ = timeout; + } + + notifyThread(); + return 0; +} + +void AsyncFdWatcher::StopWatchingFileDescriptors() { stopThread(); } + +AsyncFdWatcher::~AsyncFdWatcher() {} + +// Make sure to call this with at least one file descriptor ready to be +// watched upon or the thread routine will return immediately +int AsyncFdWatcher::tryStartThread() { + if (std::atomic_exchange(&running_, true)) return 0; + + // Set up the communication channel + int pipe_fds[2]; + if (pipe2(pipe_fds, O_NONBLOCK)) return -1; + + notification_listen_fd_ = pipe_fds[0]; + notification_write_fd_ = pipe_fds[1]; + + thread_ = std::thread([this]() { ThreadRoutine(); }); + if (!thread_.joinable()) return -1; + + return 0; +} + +int AsyncFdWatcher::stopThread() { + if (!std::atomic_exchange(&running_, false)) return 0; + + notifyThread(); + if (std::this_thread::get_id() != thread_.get_id()) { + thread_.join(); + } + + { + std::unique_lock guard(internal_mutex_); + watched_fds_.clear(); + } + + { + std::unique_lock guard(timeout_mutex_); + timeout_cb_ = nullptr; + } + + close(notification_listen_fd_); + close(notification_write_fd_); + + return 0; +} + +int AsyncFdWatcher::notifyThread() { + uint8_t buffer[] = {0}; + if (TEMP_FAILURE_RETRY(write(notification_write_fd_, &buffer, 1)) < 0) { + return -1; + } + return 0; +} + +void AsyncFdWatcher::ThreadRoutine() { + while (running_) { + fd_set read_fds; + FD_ZERO(&read_fds); + FD_SET(notification_listen_fd_, &read_fds); + int max_read_fd = INVALID_FD; + for (auto& it : watched_fds_) { + FD_SET(it.first, &read_fds); + max_read_fd = std::max(max_read_fd, it.first); + } + + struct timeval timeout; + struct timeval* timeout_ptr = NULL; + if (timeout_ms_ > std::chrono::milliseconds(0)) { + timeout.tv_sec = timeout_ms_.count() / 1000; + timeout.tv_usec = (timeout_ms_.count() % 1000) * 1000; + timeout_ptr = &timeout; + } + + // Wait until there is data available to read on some FD. + int nfds = std::max(notification_listen_fd_, max_read_fd); + int retval = select(nfds + 1, &read_fds, NULL, NULL, timeout_ptr); + + // There was some error. + if (retval < 0) continue; + + // Timeout. + if (retval == 0) { + // Allow the timeout callback to modify the timeout. + TimeoutCallback saved_cb; + { + std::unique_lock guard(timeout_mutex_); + if (timeout_ms_ > std::chrono::milliseconds(0)) saved_cb = timeout_cb_; + } + if (saved_cb != nullptr) saved_cb(); + continue; + } + + // Read data from the notification FD. + if (FD_ISSET(notification_listen_fd_, &read_fds)) { + char buffer[] = {0}; + TEMP_FAILURE_RETRY(read(notification_listen_fd_, buffer, 1)); + continue; + } + + // Invoke the data ready callbacks if appropriate. + { + // Hold the mutex to make sure that the callbacks are still valid. + std::unique_lock guard(internal_mutex_); + for (auto& it : watched_fds_) { + if (FD_ISSET(it.first, &read_fds)) { + it.second(it.first); + } + } + } + } +} + +} // namespace android::hardware::bluetooth::async diff --git a/bluetooth/async/async_fd_watcher.h b/bluetooth/async/async_fd_watcher.h new file mode 100644 index 0000000000..b50a7a031f --- /dev/null +++ b/bluetooth/async/async_fd_watcher.h @@ -0,0 +1,60 @@ +/* + * Copyright 2022 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. + */ + +#pragma once + +#include +#include +#include + +namespace android::hardware::bluetooth::async { + +using ReadCallback = std::function; +using TimeoutCallback = std::function; + +class AsyncFdWatcher { + public: + AsyncFdWatcher() = default; + ~AsyncFdWatcher(); + + int WatchFdForNonBlockingReads(int file_descriptor, + const ReadCallback& on_read_fd_ready_callback); + int ConfigureTimeout(const std::chrono::milliseconds timeout, + const TimeoutCallback& on_timeout_callback); + void StopWatchingFileDescriptors(); + + private: + AsyncFdWatcher(const AsyncFdWatcher&) = delete; + AsyncFdWatcher& operator=(const AsyncFdWatcher&) = delete; + + int tryStartThread(); + int stopThread(); + int notifyThread(); + void ThreadRoutine(); + + std::atomic_bool running_{false}; + std::thread thread_; + std::mutex internal_mutex_; + std::mutex timeout_mutex_; + + std::map watched_fds_; + int notification_listen_fd_; + int notification_write_fd_; + TimeoutCallback timeout_cb_; + std::chrono::milliseconds timeout_ms_; +}; + +} // namespace android::hardware::bluetooth::async diff --git a/bluetooth/async/test/async_fd_watcher_unittest.cc b/bluetooth/async/test/async_fd_watcher_unittest.cc new file mode 100644 index 0000000000..6d75f10279 --- /dev/null +++ b/bluetooth/async/test/async_fd_watcher_unittest.cc @@ -0,0 +1,377 @@ +/* + * Copyright 2022 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. + */ + +#define LOG_TAG "async_fd_watcher_unittest" + +#include "async_fd_watcher.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +namespace android::hardware::bluetooth::async_test { + +using android::hardware::bluetooth::async::AsyncFdWatcher; + +class AsyncFdWatcherSocketTest : public ::testing::Test { + public: + static const uint16_t kPort = 6111; + static const size_t kBufferSize = 16; + + bool CheckBufferEquals() { + return strcmp(server_buffer_, client_buffer_) == 0; + } + + protected: + int StartServer() { + ALOGD("%s", __func__); + struct sockaddr_in serv_addr; + int fd = socket(AF_INET, SOCK_STREAM, 0); + EXPECT_FALSE(fd < 0); + + memset(&serv_addr, 0, sizeof(serv_addr)); + serv_addr.sin_family = AF_INET; + serv_addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + serv_addr.sin_port = htons(kPort); + int reuse_flag = 1; + EXPECT_FALSE(setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &reuse_flag, + sizeof(reuse_flag)) < 0); + EXPECT_FALSE(bind(fd, (sockaddr*)&serv_addr, sizeof(serv_addr)) < 0); + + ALOGD("%s before listen", __func__); + listen(fd, 1); + return fd; + } + + int AcceptConnection(int fd) { + ALOGD("%s", __func__); + struct sockaddr_in cli_addr; + memset(&cli_addr, 0, sizeof(cli_addr)); + socklen_t clilen = sizeof(cli_addr); + + int connection_fd = accept(fd, (struct sockaddr*)&cli_addr, &clilen); + EXPECT_FALSE(connection_fd < 0); + + return connection_fd; + } + + void ReadIncomingMessage(int fd) { + ALOGD("%s", __func__); + int n = TEMP_FAILURE_RETRY(read(fd, server_buffer_, kBufferSize - 1)); + EXPECT_FALSE(n < 0); + + if (n == 0) { // got EOF + ALOGD("%s: EOF", __func__); + } else { + ALOGD("%s: Got something", __func__); + n = write(fd, "1", 1); + } + } + + void SetUp() override { + ALOGD("%s", __func__); + memset(server_buffer_, 0, kBufferSize); + memset(client_buffer_, 0, kBufferSize); + } + + void ConfigureServer() { + socket_fd_ = StartServer(); + + conn_watcher_.WatchFdForNonBlockingReads(socket_fd_, [this](int fd) { + int connection_fd = AcceptConnection(fd); + ALOGD("%s: Conn_watcher fd = %d", __func__, fd); + + conn_watcher_.ConfigureTimeout(std::chrono::seconds(0), []() { + bool connection_timeout_cleared = false; + ASSERT_TRUE(connection_timeout_cleared); + }); + + ALOGD("%s: 3", __func__); + async_fd_watcher_.WatchFdForNonBlockingReads( + connection_fd, [this](int fd) { ReadIncomingMessage(fd); }); + + // Time out if it takes longer than a second. + SetTimeout(std::chrono::seconds(1)); + }); + conn_watcher_.ConfigureTimeout(std::chrono::seconds(1), []() { + bool connection_timeout = true; + ASSERT_FALSE(connection_timeout); + }); + } + + void CleanUpServer() { + async_fd_watcher_.StopWatchingFileDescriptors(); + conn_watcher_.StopWatchingFileDescriptors(); + close(socket_fd_); + } + + void TearDown() override { + ALOGD("%s 3", __func__); + EXPECT_TRUE(CheckBufferEquals()); + } + + void OnTimeout() { + ALOGD("%s", __func__); + timed_out_ = true; + } + + void ClearTimeout() { + ALOGD("%s", __func__); + timed_out_ = false; + } + + bool TimedOut() { + ALOGD("%s %d", __func__, timed_out_ ? 1 : 0); + return timed_out_; + } + + void SetTimeout(std::chrono::milliseconds timeout_ms) { + ALOGD("%s", __func__); + async_fd_watcher_.ConfigureTimeout(timeout_ms, [this]() { OnTimeout(); }); + ClearTimeout(); + } + + int ConnectClient() { + ALOGD("%s", __func__); + int socket_cli_fd = socket(AF_INET, SOCK_STREAM, 0); + EXPECT_FALSE(socket_cli_fd < 0); + + struct sockaddr_in serv_addr; + memset((void*)&serv_addr, 0, sizeof(serv_addr)); + serv_addr.sin_family = AF_INET; + serv_addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + serv_addr.sin_port = htons(kPort); + + int result = + connect(socket_cli_fd, (struct sockaddr*)&serv_addr, sizeof(serv_addr)); + EXPECT_FALSE(result < 0); + + return socket_cli_fd; + } + + void WriteFromClient(int socket_cli_fd) { + ALOGD("%s", __func__); + strcpy(client_buffer_, "1"); + int n = write(socket_cli_fd, client_buffer_, strlen(client_buffer_)); + EXPECT_TRUE(n > 0); + } + + void AwaitServerResponse(int socket_cli_fd) { + ALOGD("%s", __func__); + int n = read(socket_cli_fd, client_buffer_, 1); + ALOGD("%s done", __func__); + EXPECT_TRUE(n > 0); + } + + private: + AsyncFdWatcher async_fd_watcher_; + AsyncFdWatcher conn_watcher_; + int socket_fd_; + char server_buffer_[kBufferSize]; + char client_buffer_[kBufferSize]; + bool timed_out_; +}; + +// Use a single AsyncFdWatcher to signal a connection to the server socket. +TEST_F(AsyncFdWatcherSocketTest, Connect) { + int socket_fd = StartServer(); + + AsyncFdWatcher conn_watcher; + conn_watcher.WatchFdForNonBlockingReads(socket_fd, [this](int fd) { + int connection_fd = AcceptConnection(fd); + close(connection_fd); + }); + + // Fail if the client doesn't connect within 1 second. + conn_watcher.ConfigureTimeout(std::chrono::seconds(1), []() { + bool connection_timeout = true; + ASSERT_FALSE(connection_timeout); + }); + + int socket_cli_fd = ConnectClient(); + conn_watcher.StopWatchingFileDescriptors(); + close(socket_fd); + close(socket_cli_fd); +} + +// Use a single AsyncFdWatcher to signal a connection to the server socket. +TEST_F(AsyncFdWatcherSocketTest, TimedOutConnect) { + int socket_fd = StartServer(); + bool timed_out = false; + bool* timeout_ptr = &timed_out; + + AsyncFdWatcher conn_watcher; + conn_watcher.WatchFdForNonBlockingReads(socket_fd, [this](int fd) { + int connection_fd = AcceptConnection(fd); + close(connection_fd); + }); + + // Set the timeout flag after 100ms. + conn_watcher.ConfigureTimeout(std::chrono::milliseconds(100), + [timeout_ptr]() { *timeout_ptr = true; }); + EXPECT_FALSE(timed_out); + sleep(1); + EXPECT_TRUE(timed_out); + conn_watcher.StopWatchingFileDescriptors(); + close(socket_fd); +} + +// Modify the timeout in a timeout callback. +TEST_F(AsyncFdWatcherSocketTest, TimedOutSchedulesTimeout) { + int socket_fd = StartServer(); + bool timed_out = false; + bool timed_out2 = false; + + AsyncFdWatcher conn_watcher; + conn_watcher.WatchFdForNonBlockingReads(socket_fd, [this](int fd) { + int connection_fd = AcceptConnection(fd); + close(connection_fd); + }); + + // Set a timeout flag in each callback. + conn_watcher.ConfigureTimeout(std::chrono::milliseconds(500), + [&conn_watcher, &timed_out, &timed_out2]() { + timed_out = true; + conn_watcher.ConfigureTimeout( + std::chrono::seconds(1), + [&timed_out2]() { timed_out2 = true; }); + }); + EXPECT_FALSE(timed_out); + EXPECT_FALSE(timed_out2); + sleep(1); + EXPECT_TRUE(timed_out); + EXPECT_FALSE(timed_out2); + sleep(1); + EXPECT_TRUE(timed_out); + EXPECT_TRUE(timed_out2); + conn_watcher.StopWatchingFileDescriptors(); + close(socket_fd); +} + +MATCHER_P(ReadAndMatchSingleChar, byte, + "Reads a byte from the file descriptor and matches the value against " + "byte") { + char inbuf[1] = {0}; + + int n = TEMP_FAILURE_RETRY(read(arg, inbuf, 1)); + + TEMP_FAILURE_RETRY(write(arg, inbuf, 1)); + if (n != 1) { + return false; + } + return inbuf[0] == byte; +}; + +// Use a single AsyncFdWatcher to watch two file descriptors. +TEST_F(AsyncFdWatcherSocketTest, WatchTwoFileDescriptors) { + int sockfd1[2]; + int sockfd2[2]; + socketpair(AF_LOCAL, SOCK_STREAM, 0, sockfd1); + socketpair(AF_LOCAL, SOCK_STREAM, 0, sockfd2); + + testing::MockFunction cb1; + testing::MockFunction cb2; + + AsyncFdWatcher watcher; + watcher.WatchFdForNonBlockingReads(sockfd1[0], cb1.AsStdFunction()); + + watcher.WatchFdForNonBlockingReads(sockfd2[0], cb2.AsStdFunction()); + + EXPECT_CALL(cb1, Call(ReadAndMatchSingleChar('1'))); + char one_buf[1] = {'1'}; + TEMP_FAILURE_RETRY(write(sockfd1[1], one_buf, sizeof(one_buf))); + + EXPECT_CALL(cb2, Call(ReadAndMatchSingleChar('2'))); + char two_buf[1] = {'2'}; + TEMP_FAILURE_RETRY(write(sockfd2[1], two_buf, sizeof(two_buf))); + + // Blocking read instead of a flush. + TEMP_FAILURE_RETRY(read(sockfd1[1], one_buf, sizeof(one_buf))); + TEMP_FAILURE_RETRY(read(sockfd2[1], two_buf, sizeof(two_buf))); + + watcher.StopWatchingFileDescriptors(); +} + +// Use two AsyncFdWatchers to set up a server socket. +TEST_F(AsyncFdWatcherSocketTest, ClientServer) { + ConfigureServer(); + int socket_cli_fd = ConnectClient(); + + WriteFromClient(socket_cli_fd); + + AwaitServerResponse(socket_cli_fd); + + close(socket_cli_fd); + CleanUpServer(); +} + +// Use two AsyncFdWatchers to set up a server socket, which times out. +TEST_F(AsyncFdWatcherSocketTest, TimeOutTest) { + ConfigureServer(); + int socket_cli_fd = ConnectClient(); + + while (!TimedOut()) sleep(1); + + close(socket_cli_fd); + CleanUpServer(); +} + +// Use two AsyncFdWatchers to set up a server socket, which times out. +TEST_F(AsyncFdWatcherSocketTest, RepeatedTimeOutTest) { + ConfigureServer(); + int socket_cli_fd = ConnectClient(); + ClearTimeout(); + + // Time out when there are no writes. + EXPECT_FALSE(TimedOut()); + sleep(2); + EXPECT_TRUE(TimedOut()); + ClearTimeout(); + + // Don't time out when there is a write. + WriteFromClient(socket_cli_fd); + AwaitServerResponse(socket_cli_fd); + EXPECT_FALSE(TimedOut()); + ClearTimeout(); + + // Time out when the write is late. + sleep(2); + WriteFromClient(socket_cli_fd); + AwaitServerResponse(socket_cli_fd); + EXPECT_TRUE(TimedOut()); + ClearTimeout(); + + // Time out when there is a pause after a write. + WriteFromClient(socket_cli_fd); + sleep(2); + AwaitServerResponse(socket_cli_fd); + EXPECT_TRUE(TimedOut()); + ClearTimeout(); + + close(socket_cli_fd); + CleanUpServer(); +} + +} // namespace android::hardware::bluetooth::async_test diff --git a/bluetooth/hci/Android.bp b/bluetooth/hci/Android.bp new file mode 100644 index 0000000000..f0f6e8f0ce --- /dev/null +++ b/bluetooth/hci/Android.bp @@ -0,0 +1,46 @@ +package { + default_applicable_licenses: ["hardware_interfaces_license"], +} + +cc_library_static { + name: "android.hardware.bluetooth.hci", + vendor_available: true, + host_supported: true, + defaults: ["hidl_defaults"], + srcs: [ + "hci_packetizer.cc", + "h4_protocol.cc", + ], + export_include_dirs: ["."], + shared_libs: [ + "libbase", + "libhidlbase", + "liblog", + "libutils", + ], +} + +cc_test { + name: "bluetooth-vendor-interface-hci-test", + host_supported: true, + defaults: ["hidl_defaults"], + srcs: [ + "test/h4_protocol_unittest.cc", + ], + shared_libs: [ + "libbase", + "libhidlbase", + "liblog", + "libutils", + ], + static_libs: [ + "android.hardware.bluetooth.async", + "android.hardware.bluetooth.hci", + "libgmock", + ], + sanitize: { + address: true, + cfi: true, + }, + test_suites: ["general-tests"], +} diff --git a/bluetooth/hci/h4_protocol.cc b/bluetooth/hci/h4_protocol.cc new file mode 100644 index 0000000000..97ba7aa76a --- /dev/null +++ b/bluetooth/hci/h4_protocol.cc @@ -0,0 +1,144 @@ +/* + * Copyright 2022 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 "h4_protocol.h" + +#define LOG_TAG "android.hardware.bluetooth.hci-h4" + +#include +#include +#include +#include +#include + +#include "log/log.h" + +namespace android::hardware::bluetooth::hci { + +H4Protocol::H4Protocol(int fd, PacketReadCallback cmd_cb, + PacketReadCallback acl_cb, PacketReadCallback sco_cb, + PacketReadCallback event_cb, PacketReadCallback iso_cb, + DisconnectCallback disconnect_cb) + : uart_fd_(fd), + cmd_cb_(std::move(cmd_cb)), + acl_cb_(std::move(acl_cb)), + sco_cb_(std::move(sco_cb)), + event_cb_(std::move(event_cb)), + iso_cb_(std::move(iso_cb)), + disconnect_cb_(std::move(disconnect_cb)) {} + +size_t H4Protocol::Send(PacketType type, const std::vector& vector) { + return Send(type, vector.data(), vector.size()); +} + +size_t H4Protocol::Send(PacketType type, const uint8_t* data, size_t length) { + /* For HCI communication over USB dongle, multiple write results in + * response timeout as driver expect type + data at once to process + * the command, so using "writev"(for atomicity) here. + */ + struct iovec iov[2]; + ssize_t ret = 0; + iov[0].iov_base = &type; + iov[0].iov_len = sizeof(type); + iov[1].iov_base = (void*)data; + iov[1].iov_len = length; + while (1) { + ret = TEMP_FAILURE_RETRY(writev(uart_fd_, iov, 2)); + if (ret == -1) { + if (errno == EAGAIN) { + ALOGE("%s error writing to UART (%s)", __func__, strerror(errno)); + continue; + } + } else if (ret == 0) { + // Nothing written :( + ALOGE("%s zero bytes written - something went wrong...", __func__); + break; + } + break; + } + return ret; +} + +size_t H4Protocol::OnPacketReady(const std::vector& packet) { + switch (hci_packet_type_) { + case PacketType::COMMAND: + cmd_cb_(packet); + break; + case PacketType::ACL_DATA: + acl_cb_(packet); + break; + case PacketType::SCO_DATA: + sco_cb_(packet); + break; + case PacketType::EVENT: + event_cb_(packet); + break; + case PacketType::ISO_DATA: + iso_cb_(packet); + break; + default: { + LOG_ALWAYS_FATAL("Bad packet type 0x%x", + static_cast(hci_packet_type_)); + } + } + return packet.size(); +} + +void H4Protocol::SendDataToPacketizer(uint8_t* buffer, size_t length) { + std::vector input_buffer{buffer, buffer + length}; + size_t buffer_offset = 0; + while (buffer_offset < input_buffer.size()) { + if (hci_packet_type_ == PacketType::UNKNOWN) { + hci_packet_type_ = + static_cast(input_buffer.data()[buffer_offset]); + buffer_offset += 1; + } else { + bool packet_ready = hci_packetizer_.OnDataReady( + hci_packet_type_, input_buffer, buffer_offset); + if (packet_ready) { + // Call packet callback and move offset. + buffer_offset += OnPacketReady(hci_packetizer_.GetPacket()); + // Get ready for the next type byte. + hci_packet_type_ = PacketType::UNKNOWN; + } else { + // The data was consumed, but there wasn't a packet. + buffer_offset = input_buffer.size(); + } + } + } +} + +void H4Protocol::OnDataReady() { + if (disconnected_) { + return; + } + uint8_t buffer[kMaxPacketLength]; + ssize_t bytes_read = + TEMP_FAILURE_RETRY(read(uart_fd_, buffer, kMaxPacketLength)); + if (bytes_read == 0) { + ALOGI("No bytes read, calling the disconnect callback"); + disconnected_ = true; + disconnect_cb_(); + return; + } + if (bytes_read < 0) { + ALOGW("error reading from UART (%s)", strerror(errno)); + return; + } + SendDataToPacketizer(buffer, bytes_read); +} + +} // namespace android::hardware::bluetooth::hci diff --git a/bluetooth/hci/h4_protocol.h b/bluetooth/hci/h4_protocol.h new file mode 100644 index 0000000000..5daee83756 --- /dev/null +++ b/bluetooth/hci/h4_protocol.h @@ -0,0 +1,68 @@ +/* + * Copyright 2022 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. + */ + +#pragma once + +#include +#include + +#include "hci_internals.h" +#include "hci_packetizer.h" + +namespace android::hardware::bluetooth::hci { + +using PacketReadCallback = std::function&)>; +using DisconnectCallback = std::function; + +class H4Protocol { + public: + H4Protocol(int fd, PacketReadCallback cmd_cb, PacketReadCallback acl_cb, + PacketReadCallback sco_cb, PacketReadCallback event_cb, + PacketReadCallback iso_cb, DisconnectCallback disconnect_cb); + + size_t Send(PacketType type, const uint8_t* data, size_t length); + size_t Send(PacketType type, const std::vector& data); + + void OnDataReady(); + + protected: + size_t OnPacketReady(const std::vector& packet); + void SendDataToPacketizer(uint8_t* buffer, size_t length); + + private: + int uart_fd_; + bool disconnected_{false}; + + PacketReadCallback cmd_cb_; + PacketReadCallback acl_cb_; + PacketReadCallback sco_cb_; + PacketReadCallback event_cb_; + PacketReadCallback iso_cb_; + DisconnectCallback disconnect_cb_; + + PacketType hci_packet_type_{PacketType::UNKNOWN}; + HciPacketizer hci_packetizer_; + + /** + * Question : Why read in single chunk rather than multiple reads? + * Answer: Using multiple reads does not work with some BT USB dongles. + * Reading in single shot gives expected response. + * ACL max length is 2 bytes, so using 64K as the buffer length. + */ + static constexpr size_t kMaxPacketLength = 64 * 1024; +}; + +} // namespace android::hardware::bluetooth::hci diff --git a/bluetooth/hci/hci_internals.h b/bluetooth/hci/hci_internals.h new file mode 100644 index 0000000000..71b619161d --- /dev/null +++ b/bluetooth/hci/hci_internals.h @@ -0,0 +1,55 @@ +/* + * Copyright 2022 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. + */ + +#pragma once + +#include + +namespace android::hardware::bluetooth::hci { + +// HCI UART transport packet types (Volume 4, Part A, 2) +enum class PacketType : uint8_t { + UNKNOWN = 0, + COMMAND = 1, + ACL_DATA = 2, + SCO_DATA = 3, + EVENT = 4, + ISO_DATA = 5, +}; + +// 2 bytes for opcode, 1 byte for parameter length (Volume 4, Part E, 5.4.1) +static constexpr size_t kCommandHeaderSize = 3; +static constexpr size_t kCommandLengthOffset = 2; + +// 2 bytes for handle, 2 bytes for data length (Volume 4, Part E, 5.4.2) +static constexpr size_t kAclHeaderSize = 4; +static constexpr size_t kAclLengthOffset = 2; + +// 2 bytes for handle, 1 byte for data length (Volume 4, Part E, 5.4.3) +static constexpr size_t kScoHeaderSize = 3; +static constexpr size_t kScoLengthOffset = 2; + +// 1 byte for event code, 1 byte for parameter length (Volume 4, Part E, 5.4.4) +static constexpr size_t kEventHeaderSize = 2; +static constexpr size_t kEventLengthOffset = 1; + +// 2 bytes for handle, 2 bytes for data length (Volume 4, Part E, 5.4.5) +static constexpr size_t kIsoHeaderSize = 4; +static constexpr size_t kIsoLengthOffset = 2; + +static constexpr size_t kMaxHeaderSize = kAclHeaderSize; + +} // namespace android::hardware::bluetooth::hci diff --git a/bluetooth/hci/hci_packetizer.cc b/bluetooth/hci/hci_packetizer.cc new file mode 100644 index 0000000000..5b6c4435cc --- /dev/null +++ b/bluetooth/hci/hci_packetizer.cc @@ -0,0 +1,100 @@ +/* + * Copyright 2022 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_packetizer.h" + +#define LOG_TAG "android.hardware.bluetooth.hci-packetizer" +#include "log/log.h" + +namespace android::hardware::bluetooth::hci { + +namespace { + +const size_t header_size_for_type[] = {0, + kCommandHeaderSize, + kAclHeaderSize, + kScoHeaderSize, + kEventHeaderSize, + kIsoHeaderSize}; +const size_t packet_length_offset_for_type[] = {0, + kCommandLengthOffset, + kAclLengthOffset, + kScoLengthOffset, + kEventLengthOffset, + kIsoLengthOffset}; + +size_t HciGetPacketLengthForType(PacketType type, + const std::vector& header) { + size_t offset = packet_length_offset_for_type[static_cast(type)]; + if (type != PacketType::ACL_DATA && type != PacketType::ISO_DATA) { + return header[offset]; + } + return (((header[offset + 1]) << 8) | header[offset]); +} + +} // namespace + +const std::vector& HciPacketizer::GetPacket() const { return packet_; } + +bool HciPacketizer::OnDataReady(PacketType packet_type, + const std::vector& buffer, + size_t offset) { + bool packet_completed = false; + size_t bytes_available = buffer.size() - offset; + switch (state_) { + case HCI_HEADER: { + size_t header_size = + header_size_for_type[static_cast(packet_type)]; + if (bytes_remaining_ == 0) { + bytes_remaining_ = header_size; + packet_.clear(); + } + size_t bytes_to_copy = std::min(bytes_remaining_, bytes_available); + packet_.insert(packet_.end(), buffer.begin() + offset, + buffer.begin() + offset + bytes_to_copy); + bytes_remaining_ -= bytes_to_copy; + bytes_available -= bytes_to_copy; + if (bytes_remaining_ == 0) { + bytes_remaining_ = HciGetPacketLengthForType(packet_type, packet_); + if (bytes_remaining_ > 0) { + state_ = HCI_PAYLOAD; + if (bytes_available > 0) { + packet_completed = + OnDataReady(packet_type, buffer, offset + bytes_to_copy); + } + } else { + packet_completed = true; + } + } + break; + } + + case HCI_PAYLOAD: { + size_t bytes_to_copy = std::min(bytes_remaining_, bytes_available); + packet_.insert(packet_.end(), buffer.begin() + offset, + buffer.begin() + offset + bytes_to_copy); + bytes_remaining_ -= bytes_to_copy; + if (bytes_remaining_ == 0) { + state_ = HCI_HEADER; + packet_completed = true; + } + break; + } + } + return packet_completed; +} + +} // namespace android::hardware::bluetooth::hci diff --git a/bluetooth/hci/hci_packetizer.h b/bluetooth/hci/hci_packetizer.h new file mode 100644 index 0000000000..ba3e8412bf --- /dev/null +++ b/bluetooth/hci/hci_packetizer.h @@ -0,0 +1,41 @@ +/* + * Copyright 2022 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. + */ + +#pragma once + +#include +#include +#include + +#include "hci_internals.h" + +namespace android::hardware::bluetooth::hci { + +class HciPacketizer { + public: + HciPacketizer() = default; + bool OnDataReady(PacketType packet_type, const std::vector& data, + size_t offset); + const std::vector& GetPacket() const; + + protected: + enum State { HCI_HEADER, HCI_PAYLOAD }; + State state_{HCI_HEADER}; + std::vector packet_; + size_t bytes_remaining_{0}; +}; + +} // namespace android::hardware::bluetooth::hci diff --git a/bluetooth/hci/test/h4_protocol_unittest.cc b/bluetooth/hci/test/h4_protocol_unittest.cc new file mode 100644 index 0000000000..d6f74fc485 --- /dev/null +++ b/bluetooth/hci/test/h4_protocol_unittest.cc @@ -0,0 +1,441 @@ +/* + * Copyright 2022 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. + */ + +#define LOG_TAG "bt_h4_unittest" + +#include "h4_protocol.h" + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "async_fd_watcher.h" +#include "log/log.h" + +using android::hardware::bluetooth::async::AsyncFdWatcher; +using namespace android::hardware::bluetooth::hci; +using ::testing::Eq; + +static char sample_data1[100] = "A point is that which has no part."; +static char sample_data2[100] = "A line is breadthless length."; +static char sample_data3[100] = "The ends of a line are points."; +static char sample_data4[100] = + "A plane surface is a surface which lies evenly with the straight ..."; +static char acl_data[100] = + "A straight line is a line which lies evenly with the points on itself."; +static char sco_data[100] = + "A surface is that which has length and breadth only."; +static char event_data[100] = "The edges of a surface are lines."; +static char iso_data[100] = + "A plane angle is the inclination to one another of two lines in a ..."; + +MATCHER_P3(PacketMatches, header_, header_length, payload, + "Match header_length bytes of header and then the payload") { + size_t payload_length = strlen(payload); + if (header_length + payload_length != arg.size()) { + return false; + } + + if (memcmp(header_, arg.data(), header_length) != 0) { + return false; + } + + return memcmp(payload, arg.data() + header_length, payload_length) == 0; +}; + +ACTION_P(Notify, barrier) { + ALOGD("%s", __func__); + barrier->set_value(); +} + +class H4ProtocolTest : public ::testing::Test { + protected: + void SetUp() override { + ALOGD("%s", __func__); + + int sockfd[2]; + socketpair(AF_LOCAL, SOCK_STREAM, 0, sockfd); + chip_uart_fd_ = sockfd[1]; + stack_uart_fd_ = sockfd[0]; + h4_hci_ = std::make_shared( + stack_uart_fd_, cmd_cb_.AsStdFunction(), acl_cb_.AsStdFunction(), + sco_cb_.AsStdFunction(), event_cb_.AsStdFunction(), + iso_cb_.AsStdFunction(), disconnect_cb_.AsStdFunction()); + } + + void TearDown() override { + close(stack_uart_fd_); + close(chip_uart_fd_); + } + + virtual void CallDataReady() { h4_hci_->OnDataReady(); } + + void SendAndReadUartOutbound(PacketType type, char* data) { + ALOGD("%s sending", __func__); + int data_length = strlen(data); + h4_hci_->Send(type, (uint8_t*)data, data_length); + + int uart_length = data_length + 1; // + 1 for data type code + int i; + + ALOGD("%s reading", __func__); + for (i = 0; i < uart_length; i++) { + fd_set read_fds; + FD_ZERO(&read_fds); + FD_SET(chip_uart_fd_, &read_fds); + TEMP_FAILURE_RETRY( + select(chip_uart_fd_ + 1, &read_fds, nullptr, nullptr, nullptr)); + + char byte; + TEMP_FAILURE_RETRY(read(chip_uart_fd_, &byte, 1)); + + EXPECT_EQ(i == 0 ? static_cast(type) : data[i - 1], byte); + } + + EXPECT_EQ(i, uart_length); + } + + void ExpectInboundAclData(char* payload, std::promise* promise) { + // h4 type[1] + handle[2] + size[2] + header_[0] = static_cast(PacketType::ACL_DATA); + header_[1] = 19; + header_[2] = 92; + int length = strlen(payload); + header_[3] = length & 0xFF; + header_[4] = (length >> 8) & 0xFF; + ALOGD("(%d bytes) %s", length, payload); + + EXPECT_CALL(acl_cb_, + Call(PacketMatches(header_ + 1, kAclHeaderSize, payload))) + .WillOnce(Notify(promise)); + } + + void WaitForTimeout(size_t timeout_ms, std::promise* promise) { + auto future = promise->get_future(); + auto status = future.wait_for(std::chrono::milliseconds(timeout_ms)); + EXPECT_EQ(status, std::future_status::ready); + } + + void WriteInboundAclData(char* payload) { + // Use the header_ computed in ExpectInboundAclData + TEMP_FAILURE_RETRY(write(chip_uart_fd_, header_, kAclHeaderSize + 1)); + TEMP_FAILURE_RETRY(write(chip_uart_fd_, payload, strlen(payload))); + } + + void ExpectInboundScoData(char* payload, std::promise* promise) { + // h4 type[1] + handle[2] + size[1] + header_[0] = static_cast(PacketType::SCO_DATA); + header_[1] = 20; + header_[2] = 17; + header_[3] = strlen(payload) & 0xFF; + EXPECT_CALL(sco_cb_, + Call(PacketMatches(header_ + 1, kScoHeaderSize, payload))) + .WillOnce(Notify(promise)); + } + + void WriteInboundScoData(char* payload) { + // Use the header_ computed in ExpectInboundScoData + ALOGD("%s writing", __func__); + TEMP_FAILURE_RETRY(write(chip_uart_fd_, header_, kScoHeaderSize + 1)); + TEMP_FAILURE_RETRY(write(chip_uart_fd_, payload, strlen(payload))); + } + + void ExpectInboundEvent(char* payload, std::promise* promise) { + // h4 type[1] + event_code[1] + size[1] + header_[0] = static_cast(PacketType::EVENT); + header_[1] = 9; + header_[2] = strlen(payload) & 0xFF; + EXPECT_CALL(event_cb_, + Call(PacketMatches(header_ + 1, kEventHeaderSize, payload))) + .WillOnce(Notify(promise)); + } + + void WriteInboundEvent(char* payload) { + // Use the header_ computed in ExpectInboundEvent + char preamble[3] = {static_cast(PacketType::EVENT), 9, 0}; + preamble[2] = strlen(payload) & 0xFF; + ALOGD("%s writing", __func__); + TEMP_FAILURE_RETRY(write(chip_uart_fd_, header_, kEventHeaderSize + 1)); + TEMP_FAILURE_RETRY(write(chip_uart_fd_, payload, strlen(payload))); + } + + void ExpectInboundIsoData(char* payload, std::promise* promise) { + // h4 type[1] + handle[2] + size[1] + header_[0] = static_cast(PacketType::ISO_DATA); + header_[1] = 19; + header_[2] = 92; + int length = strlen(payload); + header_[3] = length & 0xFF; + header_[4] = (length >> 8) & 0x3F; + + EXPECT_CALL(iso_cb_, + Call(PacketMatches(header_ + 1, kIsoHeaderSize, payload))) + .WillOnce(Notify(promise)); + } + + void WriteInboundIsoData(char* payload) { + // Use the header_ computed in ExpectInboundIsoData + ALOGD("%s writing", __func__); + TEMP_FAILURE_RETRY(write(chip_uart_fd_, header_, kIsoHeaderSize + 1)); + TEMP_FAILURE_RETRY(write(chip_uart_fd_, payload, strlen(payload))); + } + + void WriteAndExpectManyInboundAclDataPackets(char* payload) { + size_t kNumPackets = 20; + // h4 type[1] + handle[2] + size[2] + char preamble[5] = {static_cast(PacketType::ACL_DATA), 19, 92, 0, + 0}; + int length = strlen(payload); + preamble[3] = length & 0xFF; + preamble[4] = (length >> 8) & 0xFF; + + EXPECT_CALL(acl_cb_, Call(PacketMatches(preamble + 1, sizeof(preamble) - 1, + payload))) + .Times(kNumPackets); + + for (size_t i = 0; i < kNumPackets; i++) { + TEMP_FAILURE_RETRY(write(chip_uart_fd_, preamble, sizeof(preamble))); + TEMP_FAILURE_RETRY(write(chip_uart_fd_, payload, strlen(payload))); + } + + CallDataReady(); + } + + testing::MockFunction&)> cmd_cb_; + testing::MockFunction&)> event_cb_; + testing::MockFunction&)> acl_cb_; + testing::MockFunction&)> sco_cb_; + testing::MockFunction&)> iso_cb_; + testing::MockFunction disconnect_cb_; + std::shared_ptr h4_hci_; + int chip_uart_fd_; + int stack_uart_fd_; + + char header_[5]; +}; + +// Test sending data sends correct data onto the UART +TEST_F(H4ProtocolTest, TestSends) { + SendAndReadUartOutbound(PacketType::COMMAND, sample_data1); + SendAndReadUartOutbound(PacketType::ACL_DATA, sample_data2); + SendAndReadUartOutbound(PacketType::SCO_DATA, sample_data3); + SendAndReadUartOutbound(PacketType::ISO_DATA, sample_data4); +} + +// Ensure we properly parse data coming from the UART +TEST_F(H4ProtocolTest, TestReads) { + std::promise acl_promise; + std::promise sco_promise; + std::promise event_promise; + std::promise iso_promise; + + ExpectInboundAclData(acl_data, &acl_promise); + WriteInboundAclData(acl_data); + CallDataReady(); + ExpectInboundScoData(sco_data, &sco_promise); + WriteInboundScoData(sco_data); + CallDataReady(); + ExpectInboundEvent(event_data, &event_promise); + WriteInboundEvent(event_data); + CallDataReady(); + ExpectInboundIsoData(iso_data, &iso_promise); + WriteInboundIsoData(iso_data); + CallDataReady(); + + WaitForTimeout(100, &acl_promise); + WaitForTimeout(100, &sco_promise); + WaitForTimeout(100, &event_promise); + WaitForTimeout(100, &iso_promise); +} + +TEST_F(H4ProtocolTest, TestMultiplePackets) { + WriteAndExpectManyInboundAclDataPackets(sco_data); +} + +TEST_F(H4ProtocolTest, TestDisconnect) { + EXPECT_CALL(disconnect_cb_, Call()); + close(chip_uart_fd_); + CallDataReady(); +} + +TEST_F(H4ProtocolTest, TestPartialWrites) { + size_t payload_len = strlen(acl_data); + const size_t kNumIntervals = payload_len + 1; + // h4 type[1] + handle[2] + size[2] + header_[0] = static_cast(PacketType::ACL_DATA); + header_[1] = 19; + header_[2] = 92; + header_[3] = payload_len & 0xFF; + header_[4] = (payload_len >> 8) & 0xFF; + + EXPECT_CALL(acl_cb_, + Call(PacketMatches(header_ + 1, sizeof(header_) - 1, acl_data))) + .Times(kNumIntervals); + + for (size_t interval = 1; interval < kNumIntervals + 1; interval++) { + // Use the header_ data that expect already set up. + if (interval < kAclHeaderSize) { + TEMP_FAILURE_RETRY(write(chip_uart_fd_, header_, interval)); + CallDataReady(); + TEMP_FAILURE_RETRY(write(chip_uart_fd_, header_ + interval, + kAclHeaderSize + 1 - interval)); + CallDataReady(); + } else { + TEMP_FAILURE_RETRY(write(chip_uart_fd_, header_, kAclHeaderSize + 1)); + CallDataReady(); + } + + for (size_t bytes = 0; bytes + interval <= payload_len; bytes += interval) { + TEMP_FAILURE_RETRY(write(chip_uart_fd_, acl_data + bytes, interval)); + CallDataReady(); + } + size_t extra_bytes = payload_len % interval; + if (extra_bytes) { + TEMP_FAILURE_RETRY(write( + chip_uart_fd_, acl_data + payload_len - extra_bytes, extra_bytes)); + CallDataReady(); + } + } +} + +class H4ProtocolAsyncTest : public H4ProtocolTest { + protected: + void SetUp() override { + H4ProtocolTest::SetUp(); + fd_watcher_.WatchFdForNonBlockingReads( + stack_uart_fd_, [this](int) { h4_hci_->OnDataReady(); }); + } + + void TearDown() override { fd_watcher_.StopWatchingFileDescriptors(); } + + void CallDataReady() override { + // The Async test can't call data ready. + FAIL(); + } + + void SendAndReadUartOutbound(PacketType type, char* data) { + ALOGD("%s sending", __func__); + int data_length = strlen(data); + h4_hci_->Send(type, (uint8_t*)data, data_length); + + int uart_length = data_length + 1; // + 1 for data type code + int i; + + ALOGD("%s reading", __func__); + for (i = 0; i < uart_length; i++) { + fd_set read_fds; + FD_ZERO(&read_fds); + FD_SET(chip_uart_fd_, &read_fds); + TEMP_FAILURE_RETRY( + select(chip_uart_fd_ + 1, &read_fds, nullptr, nullptr, nullptr)); + + char byte; + TEMP_FAILURE_RETRY(read(chip_uart_fd_, &byte, 1)); + + EXPECT_EQ(i == 0 ? static_cast(type) : data[i - 1], byte); + } + + EXPECT_EQ(i, uart_length); + } + + void WriteAndExpectInboundAclData(char* payload) { + std::promise promise; + ExpectInboundAclData(payload, &promise); + WriteInboundAclData(payload); + WaitForTimeout(100, &promise); + } + + void WriteAndExpectInboundScoData(char* payload) { + std::promise promise; + ExpectInboundScoData(payload, &promise); + WriteInboundScoData(payload); + WaitForTimeout(100, &promise); + } + + void WriteAndExpectInboundEvent(char* payload) { + std::promise promise; + ExpectInboundEvent(payload, &promise); + WriteInboundEvent(payload); + WaitForTimeout(100, &promise); + } + + void WriteAndExpectInboundIsoData(char* payload) { + std::promise promise; + ExpectInboundIsoData(payload, &promise); + WriteInboundIsoData(payload); + WaitForTimeout(100, &promise); + } + + void WriteAndExpectManyInboundAclDataPackets(char* payload) { + const size_t kNumPackets = 20; + // h4 type[1] + handle[2] + size[2] + char preamble[5] = {static_cast(PacketType::ACL_DATA), 19, 92, 0, + 0}; + int length = strlen(payload); + preamble[3] = length & 0xFF; + preamble[4] = (length >> 8) & 0xFF; + + EXPECT_CALL(acl_cb_, Call(PacketMatches(preamble + 1, sizeof(preamble) - 1, + payload))) + .Times(kNumPackets); + + for (size_t i = 0; i < kNumPackets; i++) { + TEMP_FAILURE_RETRY(write(chip_uart_fd_, preamble, sizeof(preamble))); + TEMP_FAILURE_RETRY(write(chip_uart_fd_, payload, strlen(payload))); + } + + WriteAndExpectInboundEvent(event_data); + } + + AsyncFdWatcher fd_watcher_; +}; + +// Test sending data sends correct data onto the UART +TEST_F(H4ProtocolAsyncTest, TestSends) { + SendAndReadUartOutbound(PacketType::COMMAND, sample_data1); + SendAndReadUartOutbound(PacketType::ACL_DATA, sample_data2); + SendAndReadUartOutbound(PacketType::SCO_DATA, sample_data3); + SendAndReadUartOutbound(PacketType::ISO_DATA, sample_data4); +} + +// Ensure we properly parse data coming from the UART +TEST_F(H4ProtocolAsyncTest, TestReads) { + WriteAndExpectInboundAclData(acl_data); + WriteAndExpectInboundScoData(sco_data); + WriteAndExpectInboundEvent(event_data); + WriteAndExpectInboundIsoData(iso_data); +} + +TEST_F(H4ProtocolAsyncTest, TestMultiplePackets) { + WriteAndExpectManyInboundAclDataPackets(sco_data); +} + +TEST_F(H4ProtocolAsyncTest, TestDisconnect) { + std::promise promise; + EXPECT_CALL(disconnect_cb_, Call()).WillOnce(Notify(&promise)); + close(chip_uart_fd_); + + // Fail if it takes longer than 100 ms. + WaitForTimeout(100, &promise); +} From 6d5e77283dee39803c523e278d366f0f46d0c9b8 Mon Sep 17 00:00:00 2001 From: Myles Watson Date: Fri, 30 Sep 2022 06:22:43 -0700 Subject: [PATCH 3/3] Bluetooth HAL: Add an AIDL default implementation Bug: 205758693 Test: VtsHalBluetoothTargetTest Change-Id: Ie8839e38448a309f2e6616d852026066b6c6c064 --- bluetooth/aidl/default/Android.bp | 79 +++++++ bluetooth/aidl/default/BluetoothHci.cpp | 194 ++++++++++++++++++ bluetooth/aidl/default/BluetoothHci.h | 71 +++++++ .../aidl/default/bluetooth-service-default.rc | 6 + .../default/bluetooth-service-default.xml | 6 + bluetooth/aidl/default/service.cpp | 49 +++++ bluetooth/aidl/default/test/fuzzer.cpp | 32 +++ 7 files changed, 437 insertions(+) create mode 100644 bluetooth/aidl/default/Android.bp create mode 100644 bluetooth/aidl/default/BluetoothHci.cpp create mode 100644 bluetooth/aidl/default/BluetoothHci.h create mode 100644 bluetooth/aidl/default/bluetooth-service-default.rc create mode 100644 bluetooth/aidl/default/bluetooth-service-default.xml create mode 100644 bluetooth/aidl/default/service.cpp create mode 100644 bluetooth/aidl/default/test/fuzzer.cpp diff --git a/bluetooth/aidl/default/Android.bp b/bluetooth/aidl/default/Android.bp new file mode 100644 index 0000000000..d1761f5cae --- /dev/null +++ b/bluetooth/aidl/default/Android.bp @@ -0,0 +1,79 @@ +package { + default_applicable_licenses: ["Android-Apache-2.0"], +} + +cc_defaults { + name: "android.hardware.bluetooth-service-build-defaults", + cflags: [ + "-Wall", + "-Wextra", + ], + shared_libs: [ + "android.hardware.bluetooth-V1-ndk", + "libbase", + "libbinder_ndk", + "libcutils", + "libhidlbase", + "liblog", + "libutils", + ], + static_libs: [ + "android.hardware.bluetooth.async", + "android.hardware.bluetooth.hci", + ], +} + +cc_library_static { + name: "libbluetoothhcihalimpl", + vendor_available: true, + host_supported: true, + defaults: ["android.hardware.bluetooth-service-build-defaults"], + srcs: [ + "BluetoothHci.cpp", + ], +} + +cc_binary { + name: "android.hardware.bluetooth-service.default", + relative_install_path: "hw", + init_rc: ["bluetooth-service-default.rc"], + vintf_fragments: ["bluetooth-service-default.xml"], + vendor: true, + defaults: ["android.hardware.bluetooth-service-build-defaults"], + srcs: [ + "service.cpp", + ], + shared_libs: [ + "android.hardware.bluetooth-V1-ndk", + "libbase", + "libbinder_ndk", + "libhidlbase", + "libutils", + "liblog", + ], + static_libs: [ + "libbluetoothhcihalimpl", + ], +} + +cc_fuzz { + name: "android.hardware.bluetooth-service.default_fuzzer", + host_supported: true, + defaults: ["service_fuzzer_defaults"], + srcs: [ + "test/fuzzer.cpp", + ], + static_libs: [ + "android.hardware.bluetooth.async", + "android.hardware.bluetooth.hci", + "android.hardware.bluetooth-V1-ndk", + "libbluetoothhcihalimpl", + "liblog", + ], + fuzz_config: { + componentid: 27441, + cc: [ + "mylesgw@google.com", + ], + }, +} diff --git a/bluetooth/aidl/default/BluetoothHci.cpp b/bluetooth/aidl/default/BluetoothHci.cpp new file mode 100644 index 0000000000..d0ad4a3ffc --- /dev/null +++ b/bluetooth/aidl/default/BluetoothHci.cpp @@ -0,0 +1,194 @@ +/* + * Copyright 2022 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. + */ + +#define LOG_TAG "android.hardware.bluetooth.service.default" + +#include "BluetoothHci.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "log/log.h" + +namespace { +int SetTerminalRaw(int fd) { + termios terminal_settings; + int rval = tcgetattr(fd, &terminal_settings); + if (rval < 0) { + return rval; + } + cfmakeraw(&terminal_settings); + rval = tcsetattr(fd, TCSANOW, &terminal_settings); + return rval; +} +} // namespace + +using namespace ::android::hardware::bluetooth::hci; +using namespace ::android::hardware::bluetooth::async; + +namespace aidl::android::hardware::bluetooth::impl { + +void OnDeath(void* cookie); + +class BluetoothDeathRecipient { + public: + BluetoothDeathRecipient(BluetoothHci* hci) : mHci(hci) {} + + void LinkToDeath(const std::shared_ptr& cb) { + mCb = cb; + clientDeathRecipient_ = AIBinder_DeathRecipient_new(OnDeath); + auto linkToDeathReturnStatus = AIBinder_linkToDeath( + mCb->asBinder().get(), clientDeathRecipient_, this /* cookie */); + LOG_ALWAYS_FATAL_IF(linkToDeathReturnStatus != STATUS_OK, + "Unable to link to death recipient"); + } + + void UnlinkToDeath(const std::shared_ptr& cb) { + LOG_ALWAYS_FATAL_IF(cb != mCb, "Unable to unlink mismatched pointers"); + } + + void serviceDied() { + if (mCb != nullptr && !AIBinder_isAlive(mCb->asBinder().get())) { + ALOGE("Bluetooth remote service has died"); + } else { + ALOGE("BluetoothDeathRecipient::serviceDied called but service not dead"); + return; + } + has_died_ = true; + mHci->close(); + } + BluetoothHci* mHci; + std::shared_ptr mCb; + AIBinder_DeathRecipient* clientDeathRecipient_; + bool getHasDied() const { return has_died_; } + + private: + bool has_died_; +}; + +void OnDeath(void* cookie) { + auto* death_recipient = static_cast(cookie); + death_recipient->serviceDied(); +} + +BluetoothHci::BluetoothHci(const std::string& dev_path) { + char property_bytes[PROPERTY_VALUE_MAX]; + property_get("vendor.ser.bt-uart", property_bytes, dev_path.c_str()); + mDevPath = std::string(property_bytes); + mDeathRecipient = std::make_shared(this); +} + +ndk::ScopedAStatus BluetoothHci::initialize( + const std::shared_ptr& cb) { + ALOGI(__func__); + + mFd = open(mDevPath.c_str(), O_RDWR); + if (mFd < 0) { + ALOGE("Could not connect to bt: %s (%s)", mDevPath.c_str(), + strerror(errno)); + return ndk::ScopedAStatus::fromServiceSpecificError(STATUS_BAD_VALUE); + } + if (int ret = SetTerminalRaw(mFd) < 0) { + ALOGE("Could not make %s a raw terminal %d(%s)", mDevPath.c_str(), ret, + strerror(errno)); + return ndk::ScopedAStatus::fromServiceSpecificError(STATUS_BAD_VALUE); + } + + mCb = cb; + if (mCb == nullptr) { + ALOGE("cb == nullptr! -> Unable to call initializationComplete(ERR)"); + return ndk::ScopedAStatus::fromServiceSpecificError(STATUS_BAD_VALUE); + } + + mDeathRecipient->LinkToDeath(mCb); + + auto init_ret = cb->initializationComplete(Status::SUCCESS); + if (!init_ret.isOk()) { + if (!mDeathRecipient->getHasDied()) { + ALOGE("Error sending init callback, but no death notification."); + } + return ndk::ScopedAStatus::fromServiceSpecificError( + STATUS_FAILED_TRANSACTION); + } + mH4 = std::make_shared( + mFd, + [](const std::vector& /* raw_command */) { + LOG_ALWAYS_FATAL("Unexpected command!"); + }, + [this](const std::vector& raw_event) { + mCb->hciEventReceived(raw_event); + }, + [this](const std::vector& raw_acl) { + mCb->hciEventReceived(raw_acl); + }, + [this](const std::vector& raw_sco) { + mCb->hciEventReceived(raw_sco); + }, + [this](const std::vector& raw_iso) { + mCb->hciEventReceived(raw_iso); + }, + [this]() { + ALOGI("HCI socket device disconnected"); + mFdWatcher.StopWatchingFileDescriptors(); + }); + mFdWatcher.WatchFdForNonBlockingReads(mFd, + [this](int) { mH4->OnDataReady(); }); + return ndk::ScopedAStatus::ok(); +} + +ndk::ScopedAStatus BluetoothHci::close() { + ALOGI(__func__); + mFdWatcher.StopWatchingFileDescriptors(); + ::close(mFd); + + return ndk::ScopedAStatus::ok(); +} + +ndk::ScopedAStatus BluetoothHci::sendHciCommand( + const std::vector& packet) { + send(PacketType::COMMAND, packet); + return ndk::ScopedAStatus::ok(); +} + +ndk::ScopedAStatus BluetoothHci::sendAclData( + const std::vector& packet) { + send(PacketType::ACL_DATA, packet); + return ndk::ScopedAStatus::ok(); +} + +ndk::ScopedAStatus BluetoothHci::sendScoData( + const std::vector& packet) { + send(PacketType::SCO_DATA, packet); + return ndk::ScopedAStatus::ok(); +} + +ndk::ScopedAStatus BluetoothHci::sendIsoData( + const std::vector& packet) { + send(PacketType::ISO_DATA, packet); + return ndk::ScopedAStatus::ok(); +} + +void BluetoothHci::send(PacketType type, const std::vector& v) { + mH4->Send(type, v); +} + +} // namespace aidl::android::hardware::bluetooth::impl diff --git a/bluetooth/aidl/default/BluetoothHci.h b/bluetooth/aidl/default/BluetoothHci.h new file mode 100644 index 0000000000..0ed0623e47 --- /dev/null +++ b/bluetooth/aidl/default/BluetoothHci.h @@ -0,0 +1,71 @@ +/* + * Copyright 2022 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. + */ + +#pragma once + +#include +#include +#include + +#include + +#include "async_fd_watcher.h" +#include "h4_protocol.h" + +namespace aidl::android::hardware::bluetooth::impl { + +class BluetoothDeathRecipient; + +// This Bluetooth HAL implementation connects with a serial port at dev_path_. +class BluetoothHci : public BnBluetoothHci { + public: + BluetoothHci(const std::string& dev_path = "/dev/hvc5"); + + ndk::ScopedAStatus initialize( + const std::shared_ptr& cb) override; + + ndk::ScopedAStatus sendHciCommand( + const std::vector& packet) override; + + ndk::ScopedAStatus sendAclData(const std::vector& packet) override; + + ndk::ScopedAStatus sendScoData(const std::vector& packet) override; + + ndk::ScopedAStatus sendIsoData(const std::vector& packet) override; + + ndk::ScopedAStatus close() override; + + static void OnPacketReady(); + + static BluetoothHci* get(); + + private: + int mFd{-1}; + std::shared_ptr mCb = nullptr; + + std::shared_ptr<::android::hardware::bluetooth::hci::H4Protocol> mH4; + + std::shared_ptr mDeathRecipient; + + std::string mDevPath; + + ::android::hardware::bluetooth::async::AsyncFdWatcher mFdWatcher; + + void send(::android::hardware::bluetooth::hci::PacketType type, + const std::vector& packet); +}; + +} // namespace aidl::android::hardware::bluetooth::impl diff --git a/bluetooth/aidl/default/bluetooth-service-default.rc b/bluetooth/aidl/default/bluetooth-service-default.rc new file mode 100644 index 0000000000..1841c77750 --- /dev/null +++ b/bluetooth/aidl/default/bluetooth-service-default.rc @@ -0,0 +1,6 @@ +service bluetooth_hal_service /vendor/bin/hw/android.hardware.bluetooth-service.default + class hal + capabilities BLOCK_SUSPEND NET_ADMIN SYS_NICE + user bluetooth + group bluetooth + task_profiles HighPerformance diff --git a/bluetooth/aidl/default/bluetooth-service-default.xml b/bluetooth/aidl/default/bluetooth-service-default.xml new file mode 100644 index 0000000000..bb05995c98 --- /dev/null +++ b/bluetooth/aidl/default/bluetooth-service-default.xml @@ -0,0 +1,6 @@ + + + android.hardware.bluetooth + IBluetoothHci/default + + diff --git a/bluetooth/aidl/default/service.cpp b/bluetooth/aidl/default/service.cpp new file mode 100644 index 0000000000..9af2a08727 --- /dev/null +++ b/bluetooth/aidl/default/service.cpp @@ -0,0 +1,49 @@ +/* + * Copyright 2022 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. + */ + +#define LOG_TAG "aidl.android.hardware.bluetooth.service.default" + +#include +#include +#include +#include +#include + +#include "BluetoothHci.h" + +using ::aidl::android::hardware::bluetooth::impl::BluetoothHci; +using ::android::hardware::configureRpcThreadpool; +using ::android::hardware::joinRpcThreadpool; + +int main(int /* argc */, char** /* argv */) { + ALOGI("Bluetooth HAL starting"); + if (!ABinderProcess_setThreadPoolMaxThreadCount(1)) { + ALOGI("failed to set thread pool max thread count"); + return 1; + } + + std::shared_ptr service = + ndk::SharedRefBase::make(); + std::string instance = std::string() + BluetoothHci::descriptor + "/default"; + auto result = + AServiceManager_addService(service->asBinder().get(), instance.c_str()); + if (result == STATUS_OK) { + ABinderProcess_joinThreadPool(); + } else { + ALOGE("Could not register as a service!"); + } + return 0; +} diff --git a/bluetooth/aidl/default/test/fuzzer.cpp b/bluetooth/aidl/default/test/fuzzer.cpp new file mode 100644 index 0000000000..e7a1eeff72 --- /dev/null +++ b/bluetooth/aidl/default/test/fuzzer.cpp @@ -0,0 +1,32 @@ +/* + * Copyright 2022 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 +#include + +#include "BluetoothHci.h" + +using aidl::android::hardware::bluetooth::impl::BluetoothHci; +using android::fuzzService; +using ndk::SharedRefBase; + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { + auto service = SharedRefBase::make(); + + fuzzService(service->asBinder().get(), FuzzedDataProvider(data, size)); + + return 0; +}