| // 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 <zircon/status.h> |
| |
| #include "garnet/drivers/bluetooth/lib/common/log.h" |
| #include "garnet/drivers/bluetooth/lib/hci/device_wrapper.h" |
| #include "garnet/lib/bluetooth/c/bt_host.h" |
| |
| #include "host.h" |
| |
| namespace bthost { |
| |
| HostDevice::HostDevice(zx_device_t* device) |
| : dev_(nullptr), parent_(device), loop_(&kAsyncLoopConfigNoAttachToThread) { |
| ZX_DEBUG_ASSERT(parent_); |
| |
| dev_proto_.version = DEVICE_OPS_VERSION; |
| dev_proto_.unbind = &HostDevice::DdkUnbind; |
| dev_proto_.release = &HostDevice::DdkRelease; |
| dev_proto_.ioctl = &HostDevice::DdkIoctl; |
| } |
| |
| zx_status_t HostDevice::Bind() { |
| bt_log(TRACE, "bt-host", "bind"); |
| |
| std::lock_guard<std::mutex> lock(mtx_); |
| |
| bt_hci_protocol_t hci_proto; |
| 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; |
| } |
| |
| // We are required to publish a device before returning from Bind but we |
| // haven't fully initialized the adapter yet. We create the bt-host device as |
| // invisible until initialization completes on the host thread. We also |
| // disallow other drivers from directly binding to it. |
| device_add_args_t args = { |
| .version = DEVICE_ADD_ARGS_VERSION, |
| .name = "bt_host", |
| .ctx = this, |
| .ops = &dev_proto_, |
| .proto_id = ZX_PROTOCOL_BT_HOST, |
| .flags = DEVICE_ADD_NON_BINDABLE | DEVICE_ADD_INVISIBLE, |
| }; |
| |
| status = device_add(parent_, &args, &dev_); |
| if (status != ZX_OK) { |
| bt_log(ERROR, "bt-host", "Failed to publish device: %s", |
| zx_status_get_string(status)); |
| return status; |
| } |
| |
| 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(), [hci_proto, this] { |
| bt_log(SPEW, "bt-host", "host thread start"); |
| |
| std::lock_guard<std::mutex> lock(mtx_); |
| host_ = fxl::MakeRefCounted<Host>(hci_proto); |
| host_->Initialize([host = host_, this](bool success) { |
| { |
| std::lock_guard<std::mutex> lock(mtx_); |
| |
| // Abort if CleanUp has been called. |
| if (!host_) |
| return; |
| |
| if (success) { |
| bt_log(TRACE, "bt-host", "adapter initialized; make device visible"); |
| host_->gatt_host()->SetRemoteServiceWatcher( |
| fit::bind_member(this, &HostDevice::OnRemoteGattServiceAdded)); |
| device_make_visible(dev_); |
| return; |
| } |
| |
| bt_log(ERROR, "bt-host", "failed to initialize adapter"); |
| CleanUp(); |
| } |
| |
| host->ShutDown(); |
| loop_.Shutdown(); |
| }); |
| }); |
| |
| return ZX_OK; |
| } |
| |
| void HostDevice::Unbind() { |
| bt_log(TRACE, "bt-host", "unbind"); |
| |
| std::lock_guard<std::mutex> lock(mtx_); |
| |
| if (!host_) |
| return; |
| |
| // Do this immediately to stop receiving new service callbacks. |
| host_->gatt_host()->SetRemoteServiceWatcher({}); |
| |
| async::PostTask(loop_.dispatcher(), [this, host = host_] { |
| host->ShutDown(); |
| loop_.Quit(); |
| }); |
| |
| // Make sure that the ShutDown task runs before this returns. |
| loop_.JoinThreads(); |
| |
| CleanUp(); |
| } |
| |
| void HostDevice::Release() { |
| bt_log(TRACE, "bt-host", "release"); |
| delete this; |
| } |
| |
| zx_status_t HostDevice::Ioctl(uint32_t op, const void* in_buf, size_t in_len, |
| void* out_buf, size_t out_len, |
| size_t* out_actual) { |
| bt_log(TRACE, "bt-host", "ioctl"); |
| |
| if (!out_buf) |
| return ZX_ERR_INVALID_ARGS; |
| |
| if (out_len < sizeof(zx_handle_t)) |
| return ZX_ERR_BUFFER_TOO_SMALL; |
| |
| if (op != IOCTL_BT_HOST_OPEN_CHANNEL) |
| return ZX_ERR_NOT_SUPPORTED; |
| |
| zx::channel local, remote; |
| zx_status_t status = zx::channel::create(0, &local, &remote); |
| if (status != ZX_OK) |
| return status; |
| |
| ZX_DEBUG_ASSERT(local); |
| ZX_DEBUG_ASSERT(remote); |
| |
| std::lock_guard<std::mutex> lock(mtx_); |
| |
| // Tell Host to start processing messages on this handle. |
| ZX_DEBUG_ASSERT(host_); |
| async::PostTask(loop_.dispatcher(), |
| [host = host_, chan = std::move(local)]() mutable { |
| host->BindHostInterface(std::move(chan)); |
| }); |
| |
| zx_handle_t* reply = static_cast<zx_handle_t*>(out_buf); |
| *reply = remote.release(); |
| *out_actual = sizeof(zx_handle_t); |
| |
| return ZX_OK; |
| } |
| |
| void HostDevice::OnRemoteGattServiceAdded( |
| const std::string& peer_id, |
| fbl::RefPtr<btlib::gatt::RemoteService> service) { |
| auto gatt_device = |
| std::make_unique<GattRemoteServiceDevice>(dev_, peer_id, service); |
| auto gatt_device_ptr = gatt_device.get(); |
| |
| zx_status_t status = gatt_device->Bind(); |
| if (status != ZX_OK) |
| return; |
| |
| gatt_devices_[gatt_device_ptr] = std::move(gatt_device); |
| |
| service->AddRemovedHandler( |
| [this, gatt_device_ptr] { gatt_devices_.erase(gatt_device_ptr); }); |
| } |
| |
| void HostDevice::CleanUp() { |
| host_ = nullptr; |
| |
| // Removing the devices explictly instead of letting unbind handle it for us. |
| gatt_devices_.clear(); |
| device_remove(dev_); |
| |
| dev_ = nullptr; |
| } |
| |
| } // namespace bthost |