| // 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 <cstdio> |
| #include <future> |
| #include <thread> |
| |
| #include <ddk/protocol/bt/hci.h> |
| #include <lib/async/cpp/task.h> |
| #include <zircon/device/bt-hci.h> |
| #include <zircon/status.h> |
| #include <zircon/types.h> |
| |
| #include "garnet/drivers/bluetooth/lib/testing/fake_device.h" |
| |
| #include "fake-device.h" |
| |
| using ::btlib::common::DeviceAddress; |
| using ::btlib::testing::FakeController; |
| using ::btlib::testing::FakeDevice; |
| |
| namespace bthci_fake { |
| |
| const DeviceAddress kAddress0(DeviceAddress::Type::kLEPublic, |
| "00:00:00:00:00:01"); |
| const DeviceAddress kAddress1(DeviceAddress::Type::kBREDR, "00:00:00:00:00:02"); |
| |
| Device::Device(zx_device_t* device) |
| : loop_(&kAsyncLoopConfigNoAttachToThread), parent_(device) {} |
| |
| #define DEV(c) static_cast<Device*>(c) |
| static zx_protocol_device_t bthci_fake_device_ops = { |
| .version = DEVICE_OPS_VERSION, |
| .get_protocol = [](void* ctx, uint32_t proto_id, void* out_proto) |
| -> zx_status_t { return DEV(ctx)->GetProtocol(proto_id, out_proto); }, |
| .unbind = [](void* ctx) { DEV(ctx)->Unbind(); }, |
| .release = [](void* ctx) { DEV(ctx)->Release(); }, |
| .ioctl = [](void* ctx, uint32_t op, const void* in_buf, size_t in_len, |
| void* out_buf, size_t out_len, |
| size_t* out_actual) -> zx_status_t { |
| return DEV(ctx)->Ioctl(op, in_buf, in_len, out_buf, out_len, out_actual); |
| }, |
| }; |
| |
| static bt_hci_protocol_ops_t hci_protocol_ops = { |
| .open_command_channel = [](void* ctx, zx_handle_t* chan) -> zx_status_t { |
| return DEV(ctx)->OpenChan(Channel::COMMAND, chan); |
| }, |
| .open_acl_data_channel = [](void* ctx, zx_handle_t* chan) -> zx_status_t { |
| return DEV(ctx)->OpenChan(Channel::ACL, chan); |
| }, |
| .open_snoop_channel = [](void* ctx, zx_handle_t* chan) -> zx_status_t { |
| return DEV(ctx)->OpenChan(Channel::SNOOP, chan); |
| }, |
| }; |
| #undef DEV |
| |
| zx_status_t Device::Bind() { |
| device_add_args_t args = {}; |
| args.version = DEVICE_ADD_ARGS_VERSION; |
| args.name = "bt_hci_fake"; |
| args.ctx = this; |
| args.ops = &bthci_fake_device_ops; |
| args.proto_id = ZX_PROTOCOL_BT_HCI; |
| |
| zx_status_t status = device_add(parent_, &args, &zxdev_); |
| if (status != ZX_OK) { |
| printf("bt_hci_fake: could not add device: %d\n", status); |
| return status; |
| } |
| |
| FakeController::Settings settings; |
| settings.ApplyDualModeDefaults(); |
| std::lock_guard<std::mutex> lock(device_lock_); |
| fake_device_ = fbl::AdoptRef(new FakeController()); |
| fake_device_->set_settings(settings); |
| |
| // A Sample LE remote device for le-scan to pick up. |
| // TODO(bwb): add tooling for adding/removing fake devices |
| const auto kAdvData0 = btlib::common::CreateStaticByteBuffer( |
| // Flags |
| 0x02, 0x01, 0x02, |
| |
| // Complete 16-bit service UUIDs |
| 0x05, 0x03, 0x0d, 0x18, 0x0f, 0x18, |
| |
| // Complete local name |
| 0x05, 0x09, 'F', 'a', 'k', 'e'); |
| auto device = std::make_unique<FakeDevice>(kAddress0, true, true); |
| device->SetAdvertisingData(kAdvData0); |
| fake_device_->AddDevice(std::move(device)); |
| |
| // A Sample BR/EDR remote device to interact with. |
| device = std::make_unique<FakeDevice>(kAddress1, false, false); |
| // A Toy Game |
| device->set_class_of_device(btlib::common::DeviceClass({0x14, 0x08, 0x00})); |
| fake_device_->AddDevice(std::move(device)); |
| |
| loop_.StartThread("bt_hci_fake"); |
| |
| return status; |
| } |
| |
| void Device::Release() { delete this; } |
| |
| void Device::Unbind() { |
| std::lock_guard<std::mutex> lock(device_lock_); |
| async::PostTask(loop_.dispatcher(), [loop = &loop_, fake_dev = fake_device_] { |
| fake_dev->Stop(); |
| loop->Quit(); |
| }); |
| |
| loop_.JoinThreads(); |
| |
| device_remove(zxdev_); |
| } |
| |
| zx_status_t Device::Ioctl(uint32_t op, const void* in_buf, size_t in_len, |
| void* out_buf, size_t out_len, size_t* out_actual) { |
| std::printf("%s\n", __func__); |
| zx_handle_t* reply = static_cast<zx_handle_t*>(out_buf); |
| zx_status_t status = ZX_ERR_NOT_SUPPORTED; |
| |
| if (op == IOCTL_BT_HCI_GET_COMMAND_CHANNEL) { |
| status = OpenChan(Channel::COMMAND, reply); |
| } else if (op == IOCTL_BT_HCI_GET_ACL_DATA_CHANNEL) { |
| status = OpenChan(Channel::ACL, reply); |
| } else if (op == IOCTL_BT_HCI_GET_SNOOP_CHANNEL) { |
| status = OpenChan(Channel::SNOOP, reply); |
| } |
| |
| if (status == ZX_OK) { |
| *out_actual = sizeof(*reply); |
| } |
| return status; |
| } |
| |
| zx_status_t Device::OpenChan(Channel chan_type, zx_handle_t* out_channel) { |
| zx::channel in, out; |
| auto status = zx::channel::create(0, &out, &in); |
| if (status != ZX_OK) { |
| printf("bt_hci_fake: could not create channel (type %i): %d\n", chan_type, |
| status); |
| return status; |
| } |
| *out_channel = out.release(); |
| std::lock_guard<std::mutex> lock(device_lock_); |
| |
| if (chan_type == Channel::COMMAND) { |
| async::PostTask(loop_.dispatcher(), |
| [device = fake_device_, in = std::move(in)]() mutable { |
| device->StartCmdChannel(std::move(in)); |
| }); |
| } else if (chan_type == Channel::ACL) { |
| async::PostTask(loop_.dispatcher(), |
| [device = fake_device_, in = std::move(in)]() mutable { |
| device->StartAclChannel(std::move(in)); |
| }); |
| } else if (chan_type == Channel::SNOOP) { |
| async::PostTask(loop_.dispatcher(), |
| [device = fake_device_, in = std::move(in)]() mutable { |
| device->StartSnoopChannel(std::move(in)); |
| }); |
| } else { |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| return ZX_OK; |
| } |
| |
| zx_status_t Device::GetProtocol(uint32_t proto_id, void* out_proto) { |
| if (proto_id != ZX_PROTOCOL_BT_HCI) { |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| |
| bt_hci_protocol_t* hci_proto = static_cast<bt_hci_protocol_t*>(out_proto); |
| hci_proto->ops = &hci_protocol_ops; |
| hci_proto->ctx = this; |
| |
| return ZX_OK; |
| } |
| |
| } // namespace bthci_fake |