| // Copyright 2021 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "bt_transport_usb.h" |
| |
| #include <fuchsia/hardware/bt/hci/cpp/banjo.h> |
| #include <fuchsia/hardware/usb/cpp/banjo.h> |
| #include <lib/async/cpp/task.h> |
| #include <lib/async/cpp/wait.h> |
| #include <lib/async/dispatcher.h> |
| #include <lib/fit/thread_checker.h> |
| #include <lib/sync/condition.h> |
| #include <lib/sync/mutex.h> |
| #include <zircon/device/bt-hci.h> |
| |
| #include <gtest/gtest.h> |
| #include <usb/request-cpp.h> |
| |
| #include "src/devices/testing/mock-ddk/mock-device.h" |
| #include "src/devices/usb/testing/descriptor-builder/descriptor-builder.h" |
| #include "src/lib/testing/loop_fixture/test_loop_fixture.h" |
| |
| namespace { |
| |
| constexpr uint16_t kVendorId = 1; |
| constexpr uint16_t kProductId = 2; |
| constexpr size_t kInterruptPacketSize = 255u; |
| constexpr uint16_t kScoMaxPacketSize = 255 + 3; // payload + 3 bytes header |
| constexpr uint8_t kEventAndAclInterfaceNum = 0u; |
| constexpr uint8_t kIsocInterfaceNum = 1u; |
| |
| using Request = usb::Request<void>; |
| using UnownedRequest = usb::BorrowedRequest<void>; |
| using UnownedRequestQueue = usb::BorrowedRequestQueue<void>; |
| |
| // The test fixture initializes bt-transport-usb as a child device of FakeUsbDevice. |
| // FakeUsbDevice implements the ddk::UsbProtocol template interface. ddk::UsbProtocol forwards USB |
| // static function calls to the methods of this class. |
| class FakeUsbDevice : public ddk::UsbProtocol<FakeUsbDevice> { |
| public: |
| explicit FakeUsbDevice(async_dispatcher_t* dispatcher) : dispatcher_(dispatcher) {} |
| |
| void set_device_descriptor(usb::DeviceDescriptorBuilder& dev_builder) { |
| device_descriptor_data_ = dev_builder.Generate(); |
| } |
| |
| void set_config_descriptor(usb::ConfigurationBuilder& config_builder) { |
| config_descriptor_data_ = config_builder.Generate(); |
| } |
| |
| void ConfigureDefaultDescriptors(bool with_sco = true) { |
| // Configure the USB endpoint configuration from Core Spec v5.3, Vol 4, Part B, Sec 2.1.1. |
| |
| // Interface 0 contains the bulk (ACL) and interrupt (HCI event) endpoints. |
| // Endpoint indices are per direction (in/out). |
| usb::InterfaceBuilder interface_0_builder(/*config_num=*/0); |
| usb::EndpointBuilder bulk_in_endpoint_builder(/*config_num=*/0, USB_ENDPOINT_BULK, |
| /*endpoint_index=*/0, /*in=*/true); |
| interface_0_builder.AddEndpoint(bulk_in_endpoint_builder); |
| bulk_in_addr_ = usb::EpIndexToAddress(usb::kInEndpointStart); |
| |
| usb::EndpointBuilder bulk_out_endpoint_builder(/*config_num=*/0, USB_ENDPOINT_BULK, |
| /*endpoint_index=*/0, /*in=*/false); |
| interface_0_builder.AddEndpoint(bulk_out_endpoint_builder); |
| bulk_out_addr_ = usb::EpIndexToAddress(usb::kOutEndpointStart); |
| |
| usb::EndpointBuilder interrupt_endpoint_builder(/*config_num=*/0, USB_ENDPOINT_INTERRUPT, |
| /*endpoint_index=*/1, /*in=*/true); |
| // The endpoint packet size must be large enough to send test packets. |
| interrupt_endpoint_builder.set_max_packet_size(kInterruptPacketSize); |
| interface_0_builder.AddEndpoint(interrupt_endpoint_builder); |
| interrupt_addr_ = usb::EpIndexToAddress(usb::kInEndpointStart + 1); |
| |
| usb::ConfigurationBuilder config_builder(/*config_num=*/0); |
| config_builder.AddInterface(interface_0_builder); |
| |
| if (with_sco) { |
| for (uint8_t alt_setting = 0; alt_setting < 6; alt_setting++) { |
| usb::InterfaceBuilder interface_1_builder(/*config_num=*/0, alt_setting); |
| usb::EndpointBuilder isoc_out_endpoint_builder(/*config_num=*/0, USB_ENDPOINT_ISOCHRONOUS, |
| /*endpoint_index=*/1, /*in=*/false); |
| isoc_out_endpoint_builder.set_max_packet_size(kScoMaxPacketSize); |
| interface_1_builder.AddEndpoint(isoc_out_endpoint_builder); |
| isoc_out_addr_ = usb::EpIndexToAddress(usb::kOutEndpointStart + 1); |
| |
| usb::EndpointBuilder isoc_in_endpoint_builder(/*config_num=*/0, USB_ENDPOINT_ISOCHRONOUS, |
| /*endpoint_index=*/2, /*in=*/true); |
| isoc_in_endpoint_builder.set_max_packet_size(kScoMaxPacketSize); |
| interface_1_builder.AddEndpoint(isoc_in_endpoint_builder); |
| isoc_in_addr_ = usb::EpIndexToAddress(usb::kInEndpointStart + 2); |
| config_builder.AddInterface(interface_1_builder); |
| } |
| } |
| set_config_descriptor(config_builder); |
| |
| usb::DeviceDescriptorBuilder dev_builder; |
| dev_builder.set_vendor_id(kVendorId); |
| dev_builder.set_product_id(kProductId); |
| dev_builder.AddConfiguration(config_builder); |
| set_device_descriptor(dev_builder); |
| } |
| |
| void Unplug() { |
| unplugged_ = true; |
| |
| // All requests should have completed or been canceled before unplugging the USB device. |
| ZX_ASSERT(bulk_out_requests_.is_empty()); |
| ZX_ASSERT(bulk_in_requests_.is_empty()); |
| ZX_ASSERT(interrupt_requests_.is_empty()); |
| ZX_ASSERT(isoc_in_requests_.is_empty()); |
| } |
| |
| usb_protocol_t proto() const { |
| usb_protocol_t proto; |
| proto.ctx = const_cast<FakeUsbDevice*>(this); |
| proto.ops = const_cast<usb_protocol_ops_t*>(&usb_protocol_ops_); |
| return proto; |
| } |
| |
| bool interrupt_enabled() const { return interrupt_enabled_; } |
| |
| bool bulk_in_enabled() const { return bulk_in_enabled_; } |
| |
| bool bulk_out_enabled() const { return bulk_out_enabled_; } |
| |
| bool isoc_in_enabled() const { return isoc_in_enabled_; } |
| |
| bool isoc_out_enabled() const { return isoc_out_enabled_; } |
| |
| const std::vector<std::vector<uint8_t>>& received_command_packets() const { return cmd_packets_; } |
| |
| const std::vector<std::vector<uint8_t>>& received_acl_packets() const { return acl_packets_; } |
| |
| const std::vector<std::vector<uint8_t>>& received_sco_packets() const { return sco_packets_; } |
| |
| // ddk::UsbProtocol methods: |
| |
| // Called by bt-transport-usb to send command packets. |
| // UsbControlOut may be called by the read thread. |
| zx_status_t UsbControlOut(uint8_t request_type, uint8_t request, uint16_t value, uint16_t index, |
| int64_t timeout, const uint8_t* write_buffer, size_t write_size) { |
| ZX_ASSERT(write_buffer); |
| std::vector<uint8_t> buffer_copy(write_buffer, write_buffer + write_size); |
| cmd_packets_.push_back(std::move(buffer_copy)); |
| return ZX_OK; |
| } |
| |
| zx_status_t UsbControlIn(uint8_t request_type, uint8_t request, uint16_t value, uint16_t index, |
| int64_t timeout, uint8_t* out_read_buffer, size_t read_size, |
| size_t* out_read_actual) { |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| |
| // UsbRequestQueue may be called from the read thread. |
| void UsbRequestQueue(usb_request_t* usb_request, |
| const usb_request_complete_callback_t* complete_cb) { |
| if (unplugged_) { |
| async::PostTask(dispatcher_, [usb_request, complete_cb] { |
| usb_request_complete(usb_request, ZX_ERR_IO_NOT_PRESENT, /*actual=*/0, complete_cb); |
| }); |
| return; |
| } |
| |
| UnownedRequest request(usb_request, *complete_cb, sizeof(usb_request_t)); |
| |
| if (request.request()->header.ep_address == bulk_in_addr_) { |
| ZX_ASSERT(bulk_in_enabled_); |
| bulk_in_requests_.push(std::move(request)); |
| return; |
| } |
| |
| // If the request is for an ACL packet write, copy the data and complete the request. |
| if (request.request()->header.ep_address == bulk_out_addr_) { |
| ZX_ASSERT(bulk_out_enabled_); |
| std::vector<uint8_t> packet(request.request()->header.length); |
| ssize_t actual_bytes_copied = request.CopyFrom(packet.data(), packet.size(), /*offset=*/0); |
| EXPECT_EQ(actual_bytes_copied, static_cast<ssize_t>(packet.size())); |
| acl_packets_.push_back(std::move(packet)); |
| async::PostTask(dispatcher_, [request = std::move(request), actual_bytes_copied]() mutable { |
| request.Complete(ZX_OK, /*actual=*/actual_bytes_copied); |
| }); |
| return; |
| } |
| |
| if (isoc_in_addr_ && request.request()->header.ep_address == *isoc_in_addr_) { |
| ZX_ASSERT(isoc_in_enabled_); |
| ZX_ASSERT_MSG(isoc_interface_alt_ != 0, |
| "requests must not be sent to isoc interface with alt setting 0"); |
| isoc_in_requests_.push(std::move(request)); |
| return; |
| } |
| |
| if (isoc_out_addr_ && request.request()->header.ep_address == *isoc_out_addr_) { |
| ZX_ASSERT(isoc_out_enabled_); |
| ZX_ASSERT_MSG(isoc_interface_alt_ != 0, |
| "requests must not be sent to isoc interface with alt setting 0"); |
| std::vector<uint8_t> packet(request.request()->header.length); |
| ssize_t actual_bytes_copied = request.CopyFrom(packet.data(), packet.size(), /*offset=*/0); |
| EXPECT_EQ(actual_bytes_copied, static_cast<ssize_t>(packet.size())); |
| sco_packets_.push_back(std::move(packet)); |
| async::PostTask(dispatcher_, [request = std::move(request), actual_bytes_copied]() mutable { |
| request.Complete(ZX_OK, /*actual=*/actual_bytes_copied); |
| }); |
| return; |
| } |
| |
| if (request.request()->header.ep_address == interrupt_addr_) { |
| ZX_ASSERT(interrupt_enabled_); |
| interrupt_requests_.push(std::move(request)); |
| return; |
| } |
| |
| zxlogf(ERROR, "FakeUsbDevice: received request for unknown endpoint"); |
| async::PostTask(dispatcher_, [request = std::move(request)]() mutable { |
| request.Complete(ZX_ERR_IO_NOT_PRESENT, /*actual=*/0); |
| }); |
| } |
| |
| usb_speed_t UsbGetSpeed() { return USB_SPEED_FULL; } |
| |
| zx_status_t UsbSetInterface(uint8_t interface_number, uint8_t alt_setting) { |
| if (interface_number == kEventAndAclInterfaceNum) { |
| ZX_ASSERT(alt_setting == 0); |
| event_and_acl_interface_set_count_++; |
| return ZX_OK; |
| } |
| if (interface_number == kIsocInterfaceNum) { |
| // Endpoints must be disabled before changing the interface. |
| ZX_ASSERT(!isoc_in_enabled_); |
| ZX_ASSERT(!isoc_out_enabled_); |
| isoc_interface_alt_ = alt_setting; |
| isoc_interface_set_count_++; |
| return ZX_OK; |
| } |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| |
| uint8_t UsbGetConfiguration() { return 0; } |
| |
| zx_status_t UsbSetConfiguration(uint8_t configuration) { return ZX_ERR_NOT_SUPPORTED; } |
| |
| zx_status_t UsbEnableEndpoint(const usb_endpoint_descriptor_t* ep_desc, |
| const usb_ss_ep_comp_descriptor_t* ss_com_desc, bool enable) { |
| if (interrupt_addr_ && interrupt_addr_.value() == ep_desc->b_endpoint_address) { |
| interrupt_enabled_ = enable; |
| return ZX_OK; |
| } |
| |
| if (bulk_in_addr_ && bulk_in_addr_.value() == ep_desc->b_endpoint_address) { |
| bulk_in_enabled_ = enable; |
| return ZX_OK; |
| } |
| |
| if (bulk_out_addr_ && bulk_out_addr_.value() == ep_desc->b_endpoint_address) { |
| bulk_out_enabled_ = enable; |
| return ZX_OK; |
| } |
| |
| if (isoc_in_addr_ && isoc_in_addr_.value() == ep_desc->b_endpoint_address) { |
| isoc_in_enabled_ = enable; |
| return ZX_OK; |
| } |
| |
| if (isoc_out_addr_ && isoc_out_addr_.value() == ep_desc->b_endpoint_address) { |
| isoc_out_enabled_ = enable; |
| return ZX_OK; |
| } |
| |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| |
| zx_status_t UsbResetEndpoint(uint8_t ep_address) { return ZX_ERR_NOT_SUPPORTED; } |
| |
| zx_status_t UsbResetDevice() { return ZX_ERR_NOT_SUPPORTED; } |
| |
| size_t UsbGetMaxTransferSize(uint8_t ep_address) { return 0; } |
| |
| uint32_t UsbGetDeviceId() { return 0; } |
| void UsbGetDeviceDescriptor(usb_device_descriptor_t* out_desc) { |
| memcpy(out_desc, device_descriptor_data_.data(), sizeof(usb_device_descriptor_t)); |
| } |
| |
| zx_status_t UsbGetConfigurationDescriptorLength(uint8_t configuration, size_t* out_length) { |
| *out_length = config_descriptor_data_.size(); |
| return ZX_OK; |
| } |
| |
| zx_status_t UsbGetConfigurationDescriptor(uint8_t configuration, uint8_t* out_desc_buffer, |
| size_t desc_size, size_t* out_desc_actual) { |
| if (desc_size != config_descriptor_data_.size()) { |
| return ZX_ERR_INVALID_ARGS; |
| } |
| memcpy(out_desc_buffer, config_descriptor_data_.data(), config_descriptor_data_.size()); |
| *out_desc_actual = config_descriptor_data_.size(); |
| return ZX_OK; |
| } |
| |
| size_t UsbGetDescriptorsLength() { return device_descriptor_data_.size(); } |
| |
| void UsbGetDescriptors(uint8_t* out_descs_buffer, size_t descs_size, size_t* out_descs_actual) { |
| ZX_ASSERT(descs_size == device_descriptor_data_.size()); |
| memcpy(out_descs_buffer, device_descriptor_data_.data(), descs_size); |
| } |
| |
| zx_status_t UsbGetStringDescriptor(uint8_t desc_id, uint16_t lang_id, uint16_t* out_lang_id, |
| uint8_t* out_string_buffer, size_t string_size, |
| size_t* out_string_actual) { |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| |
| zx_status_t UsbCancelAll(uint8_t ep_address) { |
| if (bulk_in_addr_ && ep_address == *bulk_in_addr_) { |
| auto requests = std::move(bulk_in_requests_); |
| requests.CompleteAll(ZX_ERR_CANCELED, 0); |
| return ZX_OK; |
| } |
| if (bulk_out_addr_ && ep_address == *bulk_out_addr_) { |
| auto requests = std::move(bulk_out_requests_); |
| requests.CompleteAll(ZX_ERR_CANCELED, 0); |
| return ZX_OK; |
| } |
| if (interrupt_addr_ && ep_address == *interrupt_addr_) { |
| auto requests = std::move(interrupt_requests_); |
| requests.CompleteAll(ZX_ERR_CANCELED, 0); |
| return ZX_OK; |
| } |
| |
| if (isoc_in_addr_ && isoc_in_addr_.value() == ep_address) { |
| isoc_in_canceled_count_++; |
| UnownedRequestQueue isoc_in_reqs = std::move(isoc_in_requests_); |
| isoc_in_reqs.CompleteAll(ZX_ERR_CANCELED, 0); |
| return ZX_OK; |
| } |
| |
| if (isoc_out_addr_ && isoc_out_addr_.value() == ep_address) { |
| isoc_out_canceled_count_++; |
| // There's nothing to cancel because isoc out requests are completed immediately. |
| return ZX_OK; |
| } |
| |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| |
| uint64_t UsbGetCurrentFrame() { return 0; } |
| |
| size_t UsbGetRequestSize() { return UnownedRequest::RequestSize(sizeof(usb_request_t)); } |
| |
| void StallOneBulkInRequest() { |
| std::optional<usb::BorrowedRequest<>> value = bulk_in_requests_.pop(); |
| |
| if (!value) { |
| return; |
| } |
| value->Complete(ZX_ERR_IO_INVALID, /*actual=*/0); |
| } |
| |
| void StallOneIsocInRequest() { |
| std::optional<usb::BorrowedRequest<>> value = isoc_in_requests_.pop(); |
| if (!value) { |
| return; |
| } |
| value->Complete(ZX_ERR_IO_INVALID, /*actual=*/0); |
| } |
| |
| // Sends 1 ACL data packet. |
| // Returns true if a response was sent. |
| bool SendOneBulkInResponse(std::vector<uint8_t> buffer) { |
| std::optional<usb::BorrowedRequest<>> req = bulk_in_requests_.pop(); |
| |
| if (!req) { |
| return false; |
| } |
| |
| // Copy data into the request's VMO. The request must have been allocated with a large enough |
| // VMO (usb_request_alloc's data_size parameter). bt-transport-usb currently uses the max ACL |
| // frame size for the data_size. |
| ssize_t actual_copy_size = req->CopyTo(buffer.data(), buffer.size(), /*offset=*/0); |
| EXPECT_EQ(actual_copy_size, static_cast<ssize_t>(buffer.size())); |
| // This calls the request callback and sets response.status and response.actual. |
| req->Complete(ZX_OK, /*actual=*/actual_copy_size); |
| |
| return true; |
| } |
| |
| // Sends 1 SCO data packet. |
| // Returns true if a response was sent. |
| bool SendOneIsocInResponse(std::vector<uint8_t> buffer) { |
| std::optional<usb::BorrowedRequest<>> req = isoc_in_requests_.pop(); |
| |
| if (!req) { |
| return false; |
| } |
| |
| // Copy data into the request's VMO. The request must have been allocated with a large enough |
| // VMO (usb_request_alloc's data_size parameter). bt-transport-usb currently uses the max SCO |
| // frame size for the data_size. |
| ssize_t actual_copy_size = req->CopyTo(buffer.data(), buffer.size(), /*offset=*/0); |
| EXPECT_EQ(actual_copy_size, static_cast<ssize_t>(buffer.size())); |
| // This calls the request callback and sets response.status and response.actual. |
| req->Complete(ZX_OK, /*actual=*/actual_copy_size); |
| return true; |
| } |
| |
| // The first even chunk buffer should be at least 2 bytes, and must specify an accurate |
| // parameter_total_size (second byte). |
| // Returns true if a response was sent. |
| bool SendHciEvent(const std::vector<uint8_t>& buffer) { |
| std::optional<usb::BorrowedRequest<>> req = interrupt_requests_.pop(); |
| |
| if (!req) { |
| return false; |
| } |
| |
| // Copy data into the request's VMO. The request must have been allocated with a large enough |
| // VMO (usb_request_alloc's data_size parameter). |
| ssize_t actual_copy_size = req->CopyTo(buffer.data(), buffer.size(), /*offset=*/0); |
| EXPECT_EQ(actual_copy_size, static_cast<ssize_t>(buffer.size())); |
| // This calls the request callback and sets response.status and response.actual. |
| req->Complete(ZX_OK, /*actual=*/actual_copy_size); |
| |
| return true; |
| } |
| |
| uint8_t isoc_interface_alt() { return isoc_interface_alt_; } |
| |
| int isoc_interface_set_count() { return isoc_interface_set_count_; } |
| |
| private: |
| async_dispatcher_t* dispatcher_; |
| |
| bool unplugged_ = false; |
| |
| // Command packets received from bt-transport-usb. |
| std::vector<std::vector<uint8_t>> cmd_packets_; |
| |
| // Outbound ACL packets received from bt-transport-usb. |
| std::vector<std::vector<uint8_t>> acl_packets_; |
| |
| // Outbound SCO packets received from bt-transport-usb. |
| std::vector<std::vector<uint8_t>> sco_packets_; |
| |
| // ACL data in/out requests. |
| UnownedRequestQueue bulk_in_requests_; |
| UnownedRequestQueue bulk_out_requests_; |
| |
| // Requests for HCI events |
| UnownedRequestQueue interrupt_requests_; |
| |
| // Inbound SCO requests. |
| UnownedRequestQueue isoc_in_requests_; |
| |
| std::vector<uint8_t> device_descriptor_data_; |
| std::vector<uint8_t> config_descriptor_data_; |
| std::optional<uint8_t> bulk_in_addr_; |
| bool bulk_in_enabled_ = false; |
| std::optional<uint8_t> bulk_out_addr_; |
| bool bulk_out_enabled_ = false; |
| std::optional<uint8_t> isoc_out_addr_; |
| bool isoc_out_enabled_ = false; |
| int isoc_out_canceled_count_ = 0; |
| std::optional<uint8_t> isoc_in_addr_; |
| bool isoc_in_enabled_ = false; |
| int isoc_in_canceled_count_ = 0; |
| std::optional<uint8_t> interrupt_addr_; |
| bool interrupt_enabled_ = false; |
| uint8_t isoc_interface_alt_ = 0; |
| int isoc_interface_set_count_ = 0; |
| int event_and_acl_interface_set_count_ = 0; |
| }; |
| |
| class BtTransportUsbTest : public ::gtest::TestLoopFixture { |
| public: |
| void SetUp() override { |
| fake_usb_device_.emplace(dispatcher()); |
| root_device_ = MockDevice::FakeRootParent(); |
| root_device_->AddProtocol(ZX_PROTOCOL_USB, fake_usb_device_->proto().ops, |
| fake_usb_device_->proto().ctx); |
| |
| fake_usb_device_->ConfigureDefaultDescriptors(); |
| |
| ASSERT_EQ(bt_transport_usb::Device::Create(root_device_.get(), dispatcher()), ZX_OK); |
| ASSERT_EQ(1u, root_device()->child_count()); |
| ASSERT_TRUE(dut()); |
| EXPECT_TRUE(fake_usb_device_->interrupt_enabled()); |
| EXPECT_TRUE(fake_usb_device_->bulk_in_enabled()); |
| EXPECT_TRUE(fake_usb_device_->bulk_out_enabled()); |
| EXPECT_FALSE(fake_usb_device_->isoc_in_enabled()); |
| EXPECT_FALSE(fake_usb_device_->isoc_out_enabled()); |
| } |
| |
| void TearDown() override { |
| RunLoopUntilIdle(); |
| |
| dut()->UnbindOp(); |
| RunLoopUntilIdle(); |
| EXPECT_EQ(dut()->UnbindReplyCallStatus(), ZX_OK); |
| EXPECT_FALSE(fake_usb_device_->interrupt_enabled()); |
| EXPECT_FALSE(fake_usb_device_->bulk_in_enabled()); |
| EXPECT_FALSE(fake_usb_device_->bulk_out_enabled()); |
| EXPECT_FALSE(fake_usb_device_->isoc_in_enabled()); |
| EXPECT_FALSE(fake_usb_device_->isoc_out_enabled()); |
| |
| dut()->ReleaseOp(); |
| |
| fake_usb_device_->Unplug(); |
| } |
| |
| // The root device that bt-transport-usb binds to. |
| MockDevice* root_device() const { return root_device_.get(); } |
| |
| // Returns the MockDevice corresponding to the bt-transport-usb driver. |
| MockDevice* dut() const { return root_device_->GetLatestChild(); } |
| |
| FakeUsbDevice* fake_usb() { return &fake_usb_device_.value(); } |
| |
| private: |
| std::shared_ptr<MockDevice> root_device_; |
| std::optional<FakeUsbDevice> fake_usb_device_; |
| }; |
| |
| class BtTransportUsbHciProtocolTest : public BtTransportUsbTest { |
| public: |
| void SetUp() override { |
| BtTransportUsbTest::SetUp(); |
| |
| bt_transport_usb::Device* dev = dut()->GetDeviceContext<bt_transport_usb::Device>(); |
| ASSERT_NE(dev, nullptr); |
| ASSERT_EQ(dev->DdkGetProtocol(ZX_PROTOCOL_BT_HCI, &hci_proto_), ZX_OK); |
| |
| ASSERT_EQ(zx::channel::create(/*flags=*/0, &cmd_chan_, &cmd_chan_driver_end_), ZX_OK); |
| |
| // Configure wait for readable signal on command channel. |
| cmd_chan_readable_wait_.set_object(cmd_chan_.get()); |
| zx_status_t wait_begin_status = cmd_chan_readable_wait_.Begin(dispatcher()); |
| ASSERT_EQ(wait_begin_status, ZX_OK) << zx_status_get_string(wait_begin_status); |
| |
| ASSERT_EQ(zx::channel::create(/*flags=*/0, &acl_chan_, &acl_chan_driver_end_), ZX_OK); |
| |
| // Configure wait for readable signal on ACL channel. |
| acl_chan_readable_wait_.set_object(acl_chan_.get()); |
| wait_begin_status = acl_chan_readable_wait_.Begin(dispatcher()); |
| ASSERT_EQ(wait_begin_status, ZX_OK) << zx_status_get_string(wait_begin_status); |
| |
| ASSERT_EQ(zx::channel::create(/*flags=*/0, &sco_chan_, &sco_chan_driver_end_), ZX_OK); |
| |
| // Configure wait for readable signal on SCO channel. |
| sco_chan_readable_wait_.set_object(sco_chan_.get()); |
| wait_begin_status = sco_chan_readable_wait_.Begin(dispatcher()); |
| ASSERT_EQ(wait_begin_status, ZX_OK) << zx_status_get_string(wait_begin_status); |
| } |
| |
| void TearDown() override { |
| cmd_chan_readable_wait_.Cancel(); |
| cmd_chan_.reset(); |
| acl_chan_.reset(); |
| snoop_chan_.reset(); |
| |
| BtTransportUsbTest::TearDown(); |
| } |
| |
| // Starts the driver. Must be called before we expect any result from the driver. |
| // Used to queue multiple packets on a channel before the driver read thread starts. |
| void ConnectDriver() { |
| // connect the snoop channel first, as some channels may have packets waiting. |
| configure_snoop_channel(); |
| bt_hci_open_command_channel(&hci_proto_, cmd_chan_driver_end_.release()); |
| bt_hci_open_acl_data_channel(&hci_proto_, acl_chan_driver_end_.release()); |
| zx_status_t sco_status = bt_hci_open_sco_channel(&hci_proto_, sco_chan_driver_end_.release()); |
| ASSERT_EQ(sco_status, ZX_OK); |
| } |
| |
| zx_status_t configure_snoop_channel() { |
| snoop_chan_readable_wait_.Cancel(); |
| zx::channel snoop_chan_driver_end; |
| ZX_ASSERT(zx::channel::create(/*flags=*/0, &snoop_chan_, &snoop_chan_driver_end) == ZX_OK); |
| zx_status_t status = bt_hci_open_snoop_channel(&hci_proto_, snoop_chan_driver_end.release()); |
| if (status == ZX_OK) { |
| // Configure wait for readable signal on snoop channel. |
| snoop_chan_readable_wait_.set_object(snoop_chan_.get()); |
| zx_status_t wait_begin_status = snoop_chan_readable_wait_.Begin(dispatcher()); |
| ZX_ASSERT_MSG(wait_begin_status == ZX_OK, "snoop wait begin failed: %s", |
| zx_status_get_string(wait_begin_status)); |
| } |
| return status; |
| } |
| |
| const std::vector<std::vector<uint8_t>>& hci_events() const { return cmd_chan_received_packets_; } |
| |
| const std::vector<std::vector<uint8_t>>& received_acl_packets() const { |
| return acl_chan_received_packets_; |
| } |
| |
| const std::vector<std::vector<uint8_t>>& received_sco_packets() const { |
| return sco_chan_received_packets_; |
| } |
| |
| const std::vector<std::vector<uint8_t>>& received_snoop_packets() const { |
| return snoop_chan_received_packets_; |
| } |
| |
| zx::channel* cmd_chan() { return &cmd_chan_; } |
| |
| zx::channel* acl_chan() { return &acl_chan_; } |
| |
| zx::channel* sco_chan() { return &sco_chan_; } |
| |
| zx_status_t ConfigureSco(sco_coding_format_t coding_format, sco_encoding_t encoding, |
| sco_sample_rate_t sample_rate) { |
| zx_status_t status = ZX_OK; |
| // The callback is called synchronously. |
| bt_hci_configure_sco( |
| &hci_proto_, coding_format, encoding, sample_rate, |
| [](void* status, zx_status_t s) { *reinterpret_cast<zx_status_t*>(status) = s; }, &status); |
| return status; |
| } |
| |
| zx_status_t ResetSco() { |
| zx_status_t status = ZX_OK; |
| // The callback is called synchronously. |
| bt_hci_reset_sco( |
| &hci_proto_, |
| [](void* status, zx_status_t s) { *reinterpret_cast<zx_status_t*>(status) = s; }, &status); |
| return status; |
| } |
| |
| private: |
| // This method is shared by the waits for all channels. |wait| is used to differentiate which wait |
| // called the method. |
| // Since bt-transport-usb writes to the channel on the same thread as tests, this handler is |
| // dispatched immediately when the channel is written to. |
| void OnChannelReady(async_dispatcher_t*, async::WaitBase* wait, zx_status_t status, |
| const zx_packet_signal_t* signal) { |
| ASSERT_EQ(status, ZX_OK); |
| ASSERT_TRUE(signal->observed & ZX_CHANNEL_READABLE); |
| |
| zx::unowned_channel chan; |
| if (wait == &cmd_chan_readable_wait_) { |
| chan = zx::unowned_channel(cmd_chan_); |
| } else if (wait == &snoop_chan_readable_wait_) { |
| chan = zx::unowned_channel(snoop_chan_); |
| } else if (wait == &acl_chan_readable_wait_) { |
| chan = zx::unowned_channel(acl_chan_); |
| } else if (wait == &sco_chan_readable_wait_) { |
| chan = zx::unowned_channel(sco_chan_); |
| } else { |
| ADD_FAILURE(); |
| return; |
| } |
| |
| for (size_t count = 0; count < signal->count; count++) { |
| // Make byte buffer arbitrarily large to hold test packets. |
| std::vector<uint8_t> bytes(255); |
| uint32_t actual_bytes; |
| zx_status_t read_status = chan->read( |
| /*flags=*/0, bytes.data(), /*handles=*/nullptr, static_cast<uint32_t>(bytes.size()), |
| /*num_handles=*/0, &actual_bytes, /*actual_handles=*/nullptr); |
| ASSERT_EQ(read_status, ZX_OK); |
| bytes.resize(actual_bytes); |
| |
| if (wait == &cmd_chan_readable_wait_) { |
| cmd_chan_received_packets_.push_back(std::move(bytes)); |
| } else if (wait == &snoop_chan_readable_wait_) { |
| snoop_chan_received_packets_.push_back(std::move(bytes)); |
| } else if (wait == &acl_chan_readable_wait_) { |
| acl_chan_received_packets_.push_back(std::move(bytes)); |
| } else if (wait == &sco_chan_readable_wait_) { |
| sco_chan_received_packets_.push_back(std::move(bytes)); |
| } else { |
| ADD_FAILURE(); |
| return; |
| } |
| } |
| |
| // The wait needs to be restarted. |
| zx_status_t wait_begin_status = wait->Begin(dispatcher()); |
| ASSERT_EQ(wait_begin_status, ZX_OK) << zx_status_get_string(wait_begin_status); |
| } |
| |
| bt_hci_protocol_t hci_proto_; |
| |
| zx::channel cmd_chan_; |
| zx::channel acl_chan_; |
| zx::channel sco_chan_; |
| zx::channel snoop_chan_; |
| // The driver ends of the above channels. Passed to the driver on ConnectDriver(), and afterwards |
| // are ZX_HANDLE_INVALID. snoop_chan_ is not included as it's connected with |
| // configure_snoop_channel and we never write to the client end. |
| zx::channel cmd_chan_driver_end_; |
| zx::channel acl_chan_driver_end_; |
| zx::channel sco_chan_driver_end_; |
| |
| async::WaitMethod<BtTransportUsbHciProtocolTest, &BtTransportUsbHciProtocolTest::OnChannelReady> |
| cmd_chan_readable_wait_{this, zx_handle_t(), ZX_CHANNEL_READABLE}; |
| async::WaitMethod<BtTransportUsbHciProtocolTest, &BtTransportUsbHciProtocolTest::OnChannelReady> |
| snoop_chan_readable_wait_{this, zx_handle_t(), ZX_CHANNEL_READABLE}; |
| async::WaitMethod<BtTransportUsbHciProtocolTest, &BtTransportUsbHciProtocolTest::OnChannelReady> |
| acl_chan_readable_wait_{this, zx_handle_t(), ZX_CHANNEL_READABLE}; |
| async::WaitMethod<BtTransportUsbHciProtocolTest, &BtTransportUsbHciProtocolTest::OnChannelReady> |
| sco_chan_readable_wait_{this, zx_handle_t(), ZX_CHANNEL_READABLE}; |
| |
| std::vector<std::vector<uint8_t>> cmd_chan_received_packets_; |
| std::vector<std::vector<uint8_t>> snoop_chan_received_packets_; |
| std::vector<std::vector<uint8_t>> acl_chan_received_packets_; |
| std::vector<std::vector<uint8_t>> sco_chan_received_packets_; |
| }; |
| |
| class BtTransportUsbBindFailureTest : public ::gtest::TestLoopFixture {}; |
| |
| // This tests the test fixture setup and teardown. |
| TEST_F(BtTransportUsbTest, Lifecycle) {} |
| |
| TEST_F(BtTransportUsbTest, IgnoresStalledRequest) { |
| fake_usb()->StallOneBulkInRequest(); |
| EXPECT_FALSE(dut()->RemoveCalled()); |
| } |
| |
| TEST_F(BtTransportUsbTest, Name) { EXPECT_EQ(std::string(dut()->name()), "bt_transport_usb"); } |
| |
| TEST_F(BtTransportUsbTest, Properties) { |
| cpp20::span<const zx_device_prop_t> props = dut()->GetProperties(); |
| ASSERT_EQ(props.size(), 3u); |
| EXPECT_EQ(props[0].id, BIND_PROTOCOL); |
| EXPECT_EQ(props[0].value, ZX_PROTOCOL_BT_TRANSPORT); |
| EXPECT_EQ(props[1].id, BIND_USB_VID); |
| EXPECT_EQ(props[1].value, kVendorId); |
| EXPECT_EQ(props[2].id, BIND_USB_PID); |
| EXPECT_EQ(props[2].value, kProductId); |
| } |
| |
| TEST_F(BtTransportUsbBindFailureTest, NoConfigurationDescriptor) { |
| FakeUsbDevice fake_usb_device(dispatcher()); |
| std::shared_ptr<MockDevice> root_device = MockDevice::FakeRootParent(); |
| root_device->AddProtocol(ZX_PROTOCOL_USB, fake_usb_device.proto().ops, |
| fake_usb_device.proto().ctx); |
| EXPECT_EQ(bt_transport_usb::Device::Create(root_device.get(), dispatcher()), |
| ZX_ERR_NOT_SUPPORTED); |
| } |
| |
| TEST_F(BtTransportUsbBindFailureTest, ConfigurationDescriptorWithoutInterfaces) { |
| FakeUsbDevice fake_usb_device(dispatcher()); |
| std::shared_ptr<MockDevice> root_device = MockDevice::FakeRootParent(); |
| root_device->AddProtocol(ZX_PROTOCOL_USB, fake_usb_device.proto().ops, |
| fake_usb_device.proto().ctx); |
| |
| usb::ConfigurationBuilder config_builder(/*config_num=*/0); |
| usb::DeviceDescriptorBuilder dev_builder; |
| dev_builder.AddConfiguration(config_builder); |
| fake_usb_device.set_device_descriptor(dev_builder); |
| |
| EXPECT_EQ(bt_transport_usb::Device::Create(root_device.get(), dispatcher()), |
| ZX_ERR_NOT_SUPPORTED); |
| } |
| |
| TEST_F(BtTransportUsbBindFailureTest, ConfigurationDescriptorWithIncorrectNumberOfEndpoints) { |
| FakeUsbDevice fake_usb_device(dispatcher()); |
| std::shared_ptr<MockDevice> root_device = MockDevice::FakeRootParent(); |
| root_device->AddProtocol(ZX_PROTOCOL_USB, fake_usb_device.proto().ops, |
| fake_usb_device.proto().ctx); |
| |
| usb::InterfaceBuilder interface_builder(/*config_num=*/0); |
| usb::EndpointBuilder interrupt_endpoint_builder(/*config_num=*/0, USB_ENDPOINT_INTERRUPT, |
| /*endpoint_index=*/1, /*in=*/true); |
| interface_builder.AddEndpoint(interrupt_endpoint_builder); |
| usb::ConfigurationBuilder config_builder(/*config_num=*/0); |
| config_builder.AddInterface(interface_builder); |
| usb::DeviceDescriptorBuilder dev_builder; |
| dev_builder.AddConfiguration(config_builder); |
| fake_usb_device.set_device_descriptor(dev_builder); |
| |
| EXPECT_EQ(bt_transport_usb::Device::Create(root_device.get(), dispatcher()), |
| ZX_ERR_NOT_SUPPORTED); |
| } |
| |
| TEST_F(BtTransportUsbBindFailureTest, |
| ConfigurationDescriptorWithIncorrectEndpointTypesInInterface0) { |
| FakeUsbDevice fake_usb_device(dispatcher()); |
| std::shared_ptr<MockDevice> root_device = MockDevice::FakeRootParent(); |
| root_device->AddProtocol(ZX_PROTOCOL_USB, fake_usb_device.proto().ops, |
| fake_usb_device.proto().ctx); |
| |
| usb::InterfaceBuilder interface_0_builder(/*config_num=*/0); |
| usb::EndpointBuilder interrupt_endpoint_builder(/*config_num=*/0, USB_ENDPOINT_INTERRUPT, |
| /*endpoint_index=*/0, /*in=*/true); |
| interface_0_builder.AddEndpoint(interrupt_endpoint_builder); |
| usb::EndpointBuilder bulk_in_endpoint_builder(/*config_num=*/0, USB_ENDPOINT_BULK, |
| /*endpoint_index=*/1, /*in=*/true); |
| interface_0_builder.AddEndpoint(bulk_in_endpoint_builder); |
| |
| // Add isoc endpoint instead of expected bulk out endpoint. |
| usb::EndpointBuilder bulk_out_endpoint_builder(/*config_num=*/0, USB_ENDPOINT_ISOCHRONOUS, |
| /*endpoint_index=*/0, /*in=*/false); |
| interface_0_builder.AddEndpoint(bulk_out_endpoint_builder); |
| |
| usb::ConfigurationBuilder config_builder(/*config_num=*/0); |
| config_builder.AddInterface(interface_0_builder); |
| |
| usb::DeviceDescriptorBuilder dev_builder; |
| dev_builder.AddConfiguration(config_builder); |
| fake_usb_device.set_device_descriptor(dev_builder); |
| |
| EXPECT_EQ(bt_transport_usb::Device::Create(root_device.get(), dispatcher()), |
| ZX_ERR_NOT_SUPPORTED); |
| } |
| |
| TEST_F(BtTransportUsbHciProtocolTest, ReceiveManySmallHciEvents) { |
| ConnectDriver(); |
| std::vector<uint8_t> kSnoopEventBuffer = { |
| BT_HCI_SNOOP_TYPE_EVT | BT_HCI_SNOOP_FLAG_RECV, // snoop packet flag |
| 0x01, // arbitrary event code |
| 0x01, // parameter_total_size |
| 0x02 // arbitrary parameter |
| }; |
| std::vector<uint8_t> kEventBuffer(kSnoopEventBuffer.begin() + 1, kSnoopEventBuffer.end()); |
| const int kNumEvents = 50; |
| |
| for (int i = 0; i < kNumEvents; i++) { |
| ASSERT_TRUE(fake_usb()->SendHciEvent(kEventBuffer)); |
| RunLoopUntilIdle(); |
| } |
| |
| ASSERT_EQ(hci_events().size(), static_cast<size_t>(kNumEvents)); |
| for (const std::vector<uint8_t>& event : hci_events()) { |
| EXPECT_EQ(event, kEventBuffer); |
| } |
| |
| auto packets = received_snoop_packets(); |
| ASSERT_EQ(packets.size(), static_cast<size_t>(kNumEvents)); |
| for (const std::vector<uint8_t>& packet : packets) { |
| EXPECT_EQ(packet, kSnoopEventBuffer); |
| } |
| } |
| |
| TEST_F(BtTransportUsbHciProtocolTest, ReceiveManyHciEventsSplitIntoTwoResponses) { |
| ConnectDriver(); |
| const std::vector<uint8_t> kSnoopEventBuffer = { |
| BT_HCI_SNOOP_TYPE_EVT | BT_HCI_SNOOP_FLAG_RECV, // Snoop packet flag |
| 0x01, // event code |
| 0x02, // parameter_total_size |
| 0x03, // arbitrary parameter |
| 0x04 // arbitrary parameter |
| }; |
| const std::vector<uint8_t> kEventBuffer(kSnoopEventBuffer.begin() + 1, kSnoopEventBuffer.end()); |
| const std::vector<uint8_t> kPart1(kEventBuffer.begin(), kEventBuffer.begin() + 3); |
| const std::vector<uint8_t> kPart2(kEventBuffer.begin() + 3, kEventBuffer.end()); |
| |
| const int kNumEvents = 50; |
| for (int i = 0; i < kNumEvents; i++) { |
| EXPECT_TRUE(fake_usb()->SendHciEvent(kPart1)); |
| EXPECT_TRUE(fake_usb()->SendHciEvent(kPart2)); |
| RunLoopUntilIdle(); |
| } |
| |
| ASSERT_EQ(hci_events().size(), static_cast<size_t>(kNumEvents)); |
| for (const std::vector<uint8_t>& event : hci_events()) { |
| EXPECT_EQ(event, kEventBuffer); |
| } |
| |
| auto packets = received_snoop_packets(); |
| ASSERT_EQ(packets.size(), static_cast<size_t>(kNumEvents)); |
| for (const std::vector<uint8_t>& packet : packets) { |
| EXPECT_EQ(packet, kSnoopEventBuffer); |
| } |
| } |
| |
| TEST_F(BtTransportUsbHciProtocolTest, SendHciCommands) { |
| const std::vector<uint8_t> kSnoopCmd0 = { |
| BT_HCI_SNOOP_TYPE_CMD, // Snoop packet flag |
| 0x00, // arbitrary payload |
| }; |
| const std::vector<uint8_t> kCmd0(kSnoopCmd0.begin() + 1, kSnoopCmd0.end()); |
| zx_status_t write_status = |
| cmd_chan()->write(/*flags=*/0, kCmd0.data(), static_cast<uint32_t>(kCmd0.size()), |
| /*handles=*/nullptr, |
| /*num_handles=*/0); |
| EXPECT_EQ(write_status, ZX_OK); |
| |
| const std::vector<uint8_t> kSnoopCmd1 = { |
| BT_HCI_SNOOP_TYPE_CMD, // Snoop packet flag |
| 0x01, // arbitrary payload (longer than before) |
| 0xC0, |
| 0xDE, |
| }; |
| const std::vector<uint8_t> kCmd1(kSnoopCmd1.begin() + 1, kSnoopCmd1.end()); |
| write_status = cmd_chan()->write(/*flags=*/0, kCmd1.data(), static_cast<uint32_t>(kCmd1.size()), |
| /*handles=*/nullptr, |
| /*num_handles=*/0); |
| EXPECT_EQ(write_status, ZX_OK); |
| |
| // Delayed connect to the driver, so the HCI CMD read loop must process both of the above commands |
| // at once. |
| ConnectDriver(); |
| RunLoopUntilIdle(); |
| std::vector<std::vector<uint8_t>> cmd_packets = fake_usb()->received_command_packets(); |
| ASSERT_EQ(cmd_packets.size(), 2u); |
| EXPECT_EQ(cmd_packets[0], kCmd0); |
| EXPECT_EQ(cmd_packets[1], kCmd1); |
| |
| std::vector<std::vector<uint8_t>> snoop_packets = received_snoop_packets(); |
| ASSERT_EQ(snoop_packets.size(), 2u); |
| EXPECT_EQ(snoop_packets[0], kSnoopCmd0); |
| EXPECT_EQ(snoop_packets[1], kSnoopCmd1); |
| } |
| |
| TEST_F(BtTransportUsbHciProtocolTest, ReceiveManyAclPackets) { |
| ConnectDriver(); |
| const std::vector<uint8_t> kSnoopAclBuffer = { |
| BT_HCI_SNOOP_TYPE_ACL | BT_HCI_SNOOP_FLAG_RECV, // Snoop packet flag |
| 0x04, 0x05 // arbitrary payload |
| }; |
| const std::vector<uint8_t> kAclBuffer(kSnoopAclBuffer.begin() + 1, kSnoopAclBuffer.end()); |
| |
| const int kNumPackets = 50; |
| for (int i = 0; i < kNumPackets; i++) { |
| EXPECT_TRUE(fake_usb()->SendOneBulkInResponse(kAclBuffer)); |
| RunLoopUntilIdle(); |
| } |
| |
| ASSERT_EQ(received_acl_packets().size(), static_cast<size_t>(kNumPackets)); |
| for (const std::vector<uint8_t>& packet : received_acl_packets()) { |
| EXPECT_EQ(packet.size(), kAclBuffer.size()); |
| EXPECT_EQ(packet, kAclBuffer); |
| } |
| |
| RunLoopUntilIdle(); |
| auto packets = received_snoop_packets(); |
| ASSERT_EQ(packets.size(), static_cast<size_t>(kNumPackets)); |
| for (const std::vector<uint8_t>& packet : packets) { |
| EXPECT_EQ(packet, kSnoopAclBuffer); |
| } |
| } |
| |
| TEST_F(BtTransportUsbHciProtocolTest, SendManyAclPackets) { |
| const uint8_t kNumPackets = 8; |
| for (uint8_t i = 0; i < kNumPackets; i++) { |
| std::vector<uint8_t> packet; |
| // Vary the length of the packets (start small) |
| if (i % 2) { |
| packet = std::vector<uint8_t>(1, i); |
| } else { |
| packet = std::vector<uint8_t>(10, i); |
| } |
| zx_status_t write_status = |
| acl_chan()->write(/*flags=*/0, packet.data(), static_cast<uint32_t>(packet.size()), |
| /*handles=*/nullptr, |
| /*num_handles=*/0); |
| ASSERT_EQ(write_status, ZX_OK); |
| } |
| |
| // Delayed connect to the driver, so that the driver must process many packets at once. |
| ConnectDriver(); |
| RunLoopUntilIdle(); |
| std::vector<std::vector<uint8_t>> packets = fake_usb()->received_acl_packets(); |
| // Ensure completion callbacks are called. |
| RunLoopUntilIdle(); |
| ASSERT_EQ(packets.size(), kNumPackets); |
| for (uint8_t i = 0; i < kNumPackets; i++) { |
| if (i % 2) { |
| EXPECT_EQ(packets[i], std::vector<uint8_t>(1, i)); |
| } else { |
| EXPECT_EQ(packets[i], std::vector<uint8_t>(10, i)); |
| } |
| } |
| |
| std::vector<std::vector<uint8_t>> snoop_packets = received_snoop_packets(); |
| ASSERT_EQ(snoop_packets.size(), kNumPackets); |
| for (uint8_t i = 0; i < kNumPackets; i++) { |
| std::vector<uint8_t> expectedSnoopPacket = {BT_HCI_SNOOP_TYPE_ACL}; |
| if (i % 2) { |
| const std::vector<uint8_t> data(1, i); |
| expectedSnoopPacket.insert(expectedSnoopPacket.end(), data.begin(), data.end()); |
| } else { |
| const std::vector<uint8_t> data(10, i); |
| expectedSnoopPacket.insert(expectedSnoopPacket.end(), data.begin(), data.end()); |
| } |
| EXPECT_EQ(snoop_packets[i], expectedSnoopPacket); |
| } |
| } |
| |
| class BtTransportUsbScoNotSupportedTest : public ::gtest::TestLoopFixture {}; |
| TEST_F(BtTransportUsbScoNotSupportedTest, OpenScoChannel) { |
| // Create USB device without a SCO interface. |
| FakeUsbDevice fake_usb_device(dispatcher()); |
| fake_usb_device.ConfigureDefaultDescriptors(/*with_sco=*/false); |
| |
| std::shared_ptr<MockDevice> root_device = MockDevice::FakeRootParent(); |
| root_device->AddProtocol(ZX_PROTOCOL_USB, fake_usb_device.proto().ops, |
| fake_usb_device.proto().ctx); |
| |
| // Binding should succeed. |
| ASSERT_EQ(bt_transport_usb::Device::Create(root_device.get(), dispatcher()), ZX_OK); |
| MockDevice* mock_dev = root_device->GetLatestChild(); |
| bt_transport_usb::Device* dev = mock_dev->GetDeviceContext<bt_transport_usb::Device>(); |
| ASSERT_NE(dev, nullptr); |
| |
| bt_hci_protocol_t hci_proto; |
| ASSERT_EQ(dev->DdkGetProtocol(ZX_PROTOCOL_BT_HCI, &hci_proto), ZX_OK); |
| |
| zx::channel chan; |
| EXPECT_EQ(bt_hci_open_sco_channel(&hci_proto, chan.get()), ZX_ERR_NOT_SUPPORTED); |
| zx_status_t configure_status = ZX_OK; |
| bt_hci_configure_sco( |
| &hci_proto, SCO_CODING_FORMAT_MSBC, SCO_ENCODING_BITS_16, SCO_SAMPLE_RATE_KHZ_16, |
| [](void* status, zx_status_t s) { *reinterpret_cast<zx_status_t*>(status) = s; }, |
| &configure_status); |
| EXPECT_EQ(configure_status, ZX_ERR_NOT_SUPPORTED); |
| |
| mock_dev->UnbindOp(); |
| RunLoopUntilIdle(); |
| EXPECT_EQ(mock_dev->UnbindReplyCallStatus(), ZX_OK); |
| mock_dev->ReleaseOp(); |
| fake_usb_device.Unplug(); |
| } |
| |
| TEST_F(BtTransportUsbHciProtocolTest, ConfigureScoAltSettings) { |
| ConnectDriver(); |
| EXPECT_EQ(fake_usb()->isoc_interface_set_count(), 1); |
| EXPECT_EQ(ZX_OK, |
| ConfigureSco(SCO_CODING_FORMAT_MSBC, SCO_ENCODING_BITS_16, SCO_SAMPLE_RATE_KHZ_16)); |
| EXPECT_EQ(fake_usb()->isoc_interface_alt(), 1); |
| EXPECT_EQ(fake_usb()->isoc_interface_set_count(), 2); |
| EXPECT_EQ(ZX_OK, |
| ConfigureSco(SCO_CODING_FORMAT_CVSD, SCO_ENCODING_BITS_8, SCO_SAMPLE_RATE_KHZ_8)); |
| EXPECT_EQ(fake_usb()->isoc_interface_alt(), 1); |
| EXPECT_EQ(fake_usb()->isoc_interface_set_count(), 2); |
| EXPECT_EQ(ZX_ERR_NOT_SUPPORTED, |
| ConfigureSco(SCO_CODING_FORMAT_CVSD, SCO_ENCODING_BITS_8, SCO_SAMPLE_RATE_KHZ_16)); |
| EXPECT_EQ(fake_usb()->isoc_interface_set_count(), 2); |
| EXPECT_EQ(ZX_OK, |
| ConfigureSco(SCO_CODING_FORMAT_CVSD, SCO_ENCODING_BITS_16, SCO_SAMPLE_RATE_KHZ_8)); |
| EXPECT_EQ(fake_usb()->isoc_interface_alt(), 2); |
| EXPECT_EQ(fake_usb()->isoc_interface_set_count(), 3); |
| EXPECT_EQ(ZX_OK, |
| ConfigureSco(SCO_CODING_FORMAT_CVSD, SCO_ENCODING_BITS_16, SCO_SAMPLE_RATE_KHZ_16)); |
| EXPECT_EQ(fake_usb()->isoc_interface_alt(), 4); |
| EXPECT_EQ(fake_usb()->isoc_interface_set_count(), 4); |
| } |
| |
| TEST_F(BtTransportUsbHciProtocolTest, SendManyScoPackets) { |
| ConnectDriver(); |
| ConfigureSco(SCO_CODING_FORMAT_CVSD, SCO_ENCODING_BITS_8, SCO_SAMPLE_RATE_KHZ_8); |
| EXPECT_EQ(fake_usb()->isoc_interface_alt(), 1); |
| |
| const uint8_t kNumPackets = 8; |
| for (uint8_t i = 0; i < kNumPackets; i++) { |
| const std::vector<uint8_t> kScoPacket = {i}; |
| zx_status_t write_status = |
| sco_chan()->write(/*flags=*/0, kScoPacket.data(), static_cast<uint32_t>(kScoPacket.size()), |
| /*handles=*/nullptr, |
| /*num_handles=*/0); |
| ASSERT_EQ(write_status, ZX_OK); |
| } |
| |
| // Completes USB requests. |
| RunLoopUntilIdle(); |
| |
| std::vector<std::vector<uint8_t>> sco_packets = fake_usb()->received_sco_packets(); |
| ASSERT_EQ(sco_packets.size(), kNumPackets); |
| for (uint8_t i = 0; i < kNumPackets; i++) { |
| EXPECT_EQ(sco_packets[i], std::vector<uint8_t>{i}); |
| } |
| |
| std::vector<std::vector<uint8_t>> snoop_packets = received_snoop_packets(); |
| ASSERT_EQ(snoop_packets.size(), kNumPackets); |
| for (uint8_t i = 0; i < kNumPackets; i++) { |
| const std::vector<uint8_t> kExpectedSnoopPacket = {BT_HCI_SNOOP_TYPE_SCO, // Snoop packet flag |
| i}; |
| EXPECT_EQ(snoop_packets[i], kExpectedSnoopPacket); |
| } |
| |
| ResetSco(); |
| EXPECT_EQ(fake_usb()->isoc_interface_alt(), 0); |
| } |
| |
| TEST_F(BtTransportUsbHciProtocolTest, QueueManyScoPacketsDueToNoAltSettingSelected) { |
| ConnectDriver(); |
| EXPECT_EQ(fake_usb()->isoc_interface_alt(), 0); |
| |
| const uint8_t kNumPackets = 8; |
| for (uint8_t i = 0; i < kNumPackets; i++) { |
| const std::vector<uint8_t> kScoPacket = {i}; |
| zx_status_t write_status = |
| sco_chan()->write(/*flags=*/0, kScoPacket.data(), static_cast<uint32_t>(kScoPacket.size()), |
| /*handles=*/nullptr, |
| /*num_handles=*/0); |
| ASSERT_EQ(write_status, ZX_OK); |
| } |
| |
| // Complete any requests. |
| RunLoopUntilIdle(); |
| |
| // This wait should time out since no packets should be sent. |
| std::vector<std::vector<uint8_t>> packets = fake_usb()->received_sco_packets(); |
| ASSERT_EQ(packets.size(), 0u); |
| |
| // Set an alt setting. Buffered packets should be sent. |
| ConfigureSco(SCO_CODING_FORMAT_CVSD, SCO_ENCODING_BITS_8, SCO_SAMPLE_RATE_KHZ_8); |
| RunLoopUntilIdle(); |
| packets = fake_usb()->received_sco_packets(); |
| ASSERT_EQ(packets.size(), kNumPackets); |
| } |
| |
| TEST_F(BtTransportUsbHciProtocolTest, ReceiveManyScoPackets) { |
| ConnectDriver(); |
| ConfigureSco(SCO_CODING_FORMAT_CVSD, SCO_ENCODING_BITS_8, SCO_SAMPLE_RATE_KHZ_8); |
| |
| const std::vector<uint8_t> kSnoopScoBuffer = { |
| BT_HCI_SNOOP_TYPE_SCO | BT_HCI_SNOOP_FLAG_RECV, // Snoop packet flag |
| 0x01, // arbitrary header fields |
| 0x02, |
| 0x03, // payload length |
| 0x04, // arbitrary payload |
| 0x05, |
| 0x06, |
| }; |
| const std::vector<uint8_t> kScoBuffer(kSnoopScoBuffer.begin() + 1, kSnoopScoBuffer.end()); |
| // Split the packet into 2 chunks to test recombination. |
| const std::vector<uint8_t> kScoBufferChunk0(kScoBuffer.begin(), kScoBuffer.begin() + 4); |
| const std::vector<uint8_t> kScoBufferChunk1(kScoBuffer.begin() + 4, kScoBuffer.end()); |
| |
| const int kNumPackets = 25; |
| for (int i = 0; i < kNumPackets; i++) { |
| EXPECT_TRUE(fake_usb()->SendOneIsocInResponse(kScoBufferChunk0)); |
| EXPECT_TRUE(fake_usb()->SendOneIsocInResponse(kScoBufferChunk1)); |
| RunLoopUntilIdle(); |
| } |
| |
| ASSERT_EQ(received_sco_packets().size(), static_cast<size_t>(kNumPackets)); |
| for (const std::vector<uint8_t>& packet : received_sco_packets()) { |
| EXPECT_EQ(packet.size(), kScoBuffer.size()); |
| EXPECT_EQ(packet, kScoBuffer); |
| } |
| |
| RunLoopUntilIdle(); |
| auto snoop_packets = received_snoop_packets(); |
| ASSERT_EQ(snoop_packets.size(), static_cast<size_t>(kNumPackets)); |
| for (const std::vector<uint8_t>& packet : snoop_packets) { |
| EXPECT_EQ(packet, kSnoopScoBuffer); |
| } |
| } |
| |
| TEST_F(BtTransportUsbHciProtocolTest, IgnoresStalledScoRequest) { |
| ConnectDriver(); |
| ConfigureSco(SCO_CODING_FORMAT_CVSD, SCO_ENCODING_BITS_8, SCO_SAMPLE_RATE_KHZ_8); |
| fake_usb()->StallOneIsocInRequest(); |
| EXPECT_FALSE(dut()->RemoveCalled()); |
| } |
| |
| } // namespace |