|  | // Copyright 2018 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 "host_device.h" | 
|  |  | 
|  | #include <lib/inspect/cpp/inspect.h> | 
|  | #include <zircon/status.h> | 
|  |  | 
|  | #include "host.h" | 
|  | #include "src/connectivity/bluetooth/core/bt-host/common/log.h" | 
|  | #include "src/connectivity/bluetooth/core/bt-host/hci/device_wrapper.h" | 
|  |  | 
|  | namespace bthost { | 
|  | namespace { | 
|  |  | 
|  | // bt-gatt-svc devices are published for HID-over-GATT only. | 
|  | constexpr bt::UUID kHogUuid(uint16_t(0x1812)); | 
|  |  | 
|  | const char* kDeviceName = "bt_host"; | 
|  |  | 
|  | }  // namespace | 
|  |  | 
|  | HostDevice::HostDevice(zx_device_t* parent) | 
|  | : HostDeviceType(parent), loop_(&kAsyncLoopConfigNoAttachToCurrentThread) { | 
|  | ZX_DEBUG_ASSERT(parent); | 
|  |  | 
|  | inspect_.GetRoot().CreateString("name", kDeviceName, &inspect_); | 
|  | } | 
|  |  | 
|  | zx_status_t HostDevice::Bind() { | 
|  | bt_log(DEBUG, "bt-host", "bind"); | 
|  |  | 
|  | std::lock_guard<std::mutex> lock(mtx_); | 
|  |  | 
|  | zx_status_t status = device_get_protocol(parent_, ZX_PROTOCOL_BT_HCI, &hci_proto_); | 
|  | if (status != ZX_OK) { | 
|  | bt_log(ERROR, "bt-host", "failed to obtain bt-hci protocol ops: %s", | 
|  | zx_status_get_string(status)); | 
|  | return status; | 
|  | } | 
|  |  | 
|  | if (!hci_proto_.ops) { | 
|  | bt_log(ERROR, "bt-host", "bt-hci device ops required!"); | 
|  | return ZX_ERR_NOT_SUPPORTED; | 
|  | } | 
|  |  | 
|  | if (!hci_proto_.ops->open_command_channel) { | 
|  | bt_log(ERROR, "bt-host", "bt-hci op required: open_command_channel"); | 
|  | return ZX_ERR_NOT_SUPPORTED; | 
|  | } | 
|  |  | 
|  | if (!hci_proto_.ops->open_acl_data_channel) { | 
|  | bt_log(ERROR, "bt-host", "bt-hci op required: open_acl_data_channel"); | 
|  | return ZX_ERR_NOT_SUPPORTED; | 
|  | } | 
|  |  | 
|  | if (!hci_proto_.ops->open_snoop_channel) { | 
|  | bt_log(ERROR, "bt-host", "bt-hci op required: open_snoop_channel"); | 
|  | return ZX_ERR_NOT_SUPPORTED; | 
|  | } | 
|  |  | 
|  | device_add_args_t args = { | 
|  | .version = DEVICE_ADD_ARGS_VERSION, | 
|  | .proto_id = ZX_PROTOCOL_BT_HOST, | 
|  | .flags = DEVICE_ADD_NON_BINDABLE, | 
|  | .inspect_vmo = inspect_.DuplicateVmo().release(), | 
|  | }; | 
|  | status = DdkAdd("bt_host", args); | 
|  |  | 
|  | if (status != ZX_OK) { | 
|  | bt_log(ERROR, "bt-host", "Failed to publish device: %s", zx_status_get_string(status)); | 
|  | return status; | 
|  | } | 
|  |  | 
|  | return status; | 
|  | // Since we define an init hook, Init will be called by the DDK after this method finishes. | 
|  | } | 
|  |  | 
|  | void HostDevice::DdkInit(ddk::InitTxn txn) { | 
|  | bt_log(DEBUG, "bt-host", "init"); | 
|  |  | 
|  | std::lock_guard<std::mutex> lock(mtx_); | 
|  |  | 
|  | auto vendor_result = GetVendorProtocol(); | 
|  | if (vendor_result.is_ok()) { | 
|  | vendor_proto_ = vendor_result.value(); | 
|  | } else { | 
|  | bt_log(WARN, "bt-host", "failed to obtain bt-vendor protocol ops: %s", | 
|  | zx_status_get_string(vendor_result.error())); | 
|  | } | 
|  |  | 
|  | loop_.StartThread("bt-host (gap)"); | 
|  |  | 
|  | // Send the bootstrap message to Host. The Host object can only be accessed on | 
|  | // the Host thread. | 
|  | async::PostTask(loop_.dispatcher(), [this, txn{std::move(txn)}]() mutable { | 
|  | bt_log(TRACE, "bt-host", "host thread start"); | 
|  |  | 
|  | std::lock_guard<std::mutex> lock(mtx_); | 
|  | host_ = fxl::MakeRefCounted<Host>(hci_proto_, vendor_proto_); | 
|  | host_->Initialize(inspect_.GetRoot(), [this, txn{std::move(txn)}](bool success) mutable { | 
|  | std::lock_guard<std::mutex> lock(mtx_); | 
|  |  | 
|  | // host_ must be defined here as Bind() must have been called and the runloop has not | 
|  | // yet been been drained in Unbind(). | 
|  | ZX_DEBUG_ASSERT(host_); | 
|  |  | 
|  | if (!success) { | 
|  | bt_log(ERROR, "bt-host", "failed to initialize adapter; cleaning up"); | 
|  | txn.Reply(ZX_ERR_INTERNAL); | 
|  | // DDK will call Unbind here to clean up. | 
|  | } else { | 
|  | bt_log(DEBUG, "bt-host", "adapter initialized; make device visible"); | 
|  | host_->gatt_host()->SetRemoteServiceWatcher( | 
|  | fit::bind_member(this, &HostDevice::OnRemoteGattServiceAdded)); | 
|  | txn.Reply(ZX_OK); | 
|  | return; | 
|  | } | 
|  | }); | 
|  | }); | 
|  | } | 
|  |  | 
|  | void HostDevice::DdkUnbind(ddk::UnbindTxn txn) { | 
|  | bt_log(DEBUG, "bt-host", "unbind"); | 
|  |  | 
|  | { | 
|  | std::lock_guard<std::mutex> lock(mtx_); | 
|  |  | 
|  | // Do this immediately to stop receiving new service callbacks. | 
|  | bt_log(TRACE, "bt-host", "removing GATT service watcher"); | 
|  | host_->gatt_host()->SetRemoteServiceWatcher({}); | 
|  |  | 
|  | async::PostTask(loop_.dispatcher(), [this] { | 
|  | std::lock_guard<std::mutex> lock(mtx_); | 
|  | host_->ShutDown(); | 
|  | host_ = nullptr; | 
|  | loop_.Quit(); | 
|  | }); | 
|  |  | 
|  | // Don't hold lock waiting on the loop to terminate. | 
|  | } | 
|  |  | 
|  | // Make sure that the ShutDown task runs before this returns. | 
|  | bt_log(TRACE, "bt-host", "waiting for shut down tasks to complete"); | 
|  | loop_.JoinThreads(); | 
|  |  | 
|  | txn.Reply(); | 
|  |  | 
|  | bt_log(DEBUG, "bt-host", "GAP has been shut down"); | 
|  | } | 
|  |  | 
|  | void HostDevice::DdkRelease() { | 
|  | bt_log(DEBUG, "bt-host", "release"); | 
|  | delete this; | 
|  | } | 
|  |  | 
|  | // Route ddk fidl messages to the dispatcher function | 
|  | zx_status_t HostDevice::DdkMessage(fidl_incoming_msg_t* msg, fidl_txn_t* txn) { | 
|  | // Struct containing function pointers for all fidl ops to be dispatched on | 
|  | static constexpr fuchsia_hardware_bluetooth_Host_ops_t fidl_ops = { | 
|  | .Open = [](void* ctx, zx_handle_t channel) { | 
|  | return static_cast<HostDevice*>(ctx)->OpenHostChannel(zx::channel(channel)); | 
|  | }}; | 
|  |  | 
|  | bt_log(DEBUG, "bt-host", "fidl message"); | 
|  | return fuchsia_hardware_bluetooth_Host_dispatch(this, txn, msg, &fidl_ops); | 
|  | } | 
|  |  | 
|  | zx_status_t HostDevice::OpenHostChannel(zx::channel channel) { | 
|  | ZX_DEBUG_ASSERT(channel); | 
|  | std::lock_guard<std::mutex> lock(mtx_); | 
|  |  | 
|  | // This is called from the fidl operation OpenChannelOp.  No fidl calls will be delivered to the | 
|  | // driver before host_ is initialized by Init(), and no fidl calls | 
|  | // will be delivered after the DDK calls Unbind() and host_ is removed. | 
|  | ZX_DEBUG_ASSERT(host_); | 
|  |  | 
|  | // Tell Host to start processing messages on this handle. | 
|  | async::PostTask(loop_.dispatcher(), [host = host_, chan = std::move(channel)]() mutable { | 
|  | host->BindHostInterface(std::move(chan)); | 
|  | }); | 
|  |  | 
|  | return ZX_OK; | 
|  | } | 
|  |  | 
|  | void HostDevice::OnRemoteGattServiceAdded(bt::gatt::PeerId peer_id, | 
|  | fbl::RefPtr<bt::gatt::RemoteService> service) { | 
|  | TRACE_DURATION("bluetooth", "HostDevice::OnRemoteGattServiceAdded"); | 
|  |  | 
|  | // Only publish children for HID-over-GATT. | 
|  | if (service->uuid() != kHogUuid) { | 
|  | return; | 
|  | } | 
|  |  | 
|  | std::lock_guard<std::mutex> lock(mtx_); | 
|  |  | 
|  | // This is run on the host event loop. Bind(), Init() and Unbind() should maintain the invariant | 
|  | // that  host_ are initialized when the event loop is running. | 
|  | ZX_DEBUG_ASSERT(host_); | 
|  |  | 
|  | __UNUSED zx_status_t status = GattRemoteServiceDevice::Publish(zxdev(), peer_id, service); | 
|  | } | 
|  |  | 
|  | fit::result<bt_vendor_protocol_t, zx_status_t> HostDevice::GetVendorProtocol() { | 
|  | bt_vendor_protocol_t vendor_proto = {}; | 
|  | zx_status_t status = device_get_protocol(parent_, ZX_PROTOCOL_BT_VENDOR, &vendor_proto); | 
|  | if (status != ZX_OK) { | 
|  | return fit::error(status); | 
|  | } | 
|  |  | 
|  | if (!vendor_proto.ops) { | 
|  | bt_log(WARN, "bt-host", "bt-vendor device ops required"); | 
|  | return fit::error(ZX_ERR_NOT_SUPPORTED); | 
|  | } | 
|  |  | 
|  | if (!vendor_proto.ops->get_features) { | 
|  | bt_log(WARN, "bt-host", "bt-vendor op required: get_features"); | 
|  | return fit::error(ZX_ERR_NOT_SUPPORTED); | 
|  | } | 
|  |  | 
|  | if (!vendor_proto.ops->encode_command) { | 
|  | bt_log(WARN, "bt-host", "bt-vendor op required: encode_command"); | 
|  | return fit::error(ZX_ERR_NOT_SUPPORTED); | 
|  | } | 
|  |  | 
|  | return fit::ok(vendor_proto); | 
|  | } | 
|  |  | 
|  | }  // namespace bthost |