| // Copyright 2019 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 "../usb-device.h" |
| |
| #include <fuchsia/hardware/usb/device/llcpp/fidl.h> |
| #include <lib/fake_ddk/fake_ddk.h> |
| #include <lib/fit/function.h> |
| |
| #include <thread> |
| |
| #include <usb/request-cpp.h> |
| #include <utf_conversion/utf_conversion.h> |
| #include <zxtest/zxtest.h> |
| |
| namespace usb_bus { |
| |
| template <typename T, size_t N> |
| constexpr T MakeConstant(const char value[N]) { |
| T retval = 0; |
| for (T i = 0; i < N; i++) { |
| retval = static_cast<T>(retval | (static_cast<T>(value[i]) << (i * 8))); |
| } |
| static_assert(N <= sizeof(T)); |
| return retval; |
| } |
| |
| constexpr uint8_t kVendorId = 81; |
| constexpr uint8_t kProductId = 35; |
| constexpr uint8_t kDeviceClass = 2; |
| constexpr uint8_t kDeviceSubclass = 6; |
| constexpr uint8_t kDeviceProtocol = 250; |
| constexpr uint32_t kDeviceId = 42; |
| constexpr uint32_t kHubId = 32; |
| constexpr uint32_t kMaxTransferSize = 9001; |
| constexpr uint8_t kTransferSizeEndpoint = 5; |
| constexpr uint64_t kCurrentFrame = MakeConstant<uint64_t, 7>("fuchsia"); |
| constexpr size_t kRequestSize = 256; |
| const char16_t* kStringDescriptors[][2] = {{u"Fuchsia", u"Fucsia"}, {u"Device", u"Dispositivo"}}; |
| |
| constexpr usb_speed_t kDeviceSpeed = MakeConstant<usb_speed_t, 4>("slow"); |
| |
| class Binder : public fake_ddk::Bind { |
| public: |
| void* get_context() { return op_ctx_; } |
| |
| bool get_remove_called() { return remove_called_; } |
| }; |
| |
| class FakeHci : public ddk::UsbHciProtocol<FakeHci> { |
| public: |
| FakeHci() { |
| proto_.ops = &usb_hci_protocol_ops_; |
| proto_.ctx = this; |
| } |
| uint64_t UsbHciGetCurrentFrame() { return kCurrentFrame; } |
| |
| zx_status_t UsbHciConfigureHub(uint32_t device_id, usb_speed_t speed, |
| const usb_hub_descriptor_t* desc, bool multi_tt) { |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| |
| zx_status_t UsbHciHubDeviceAdded(uint32_t device_id, uint32_t port, usb_speed_t speed) { |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| |
| zx_status_t UsbHciHubDeviceRemoved(uint32_t device_id, uint32_t port) { |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| |
| zx_status_t UsbHciHubDeviceReset(uint32_t device_id, uint32_t port) { |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| |
| zx_status_t UsbHciResetEndpoint(uint32_t device_id, uint8_t ep_address) { |
| if (device_id == kDeviceId) { |
| reset_endpoint_ = ep_address; |
| } |
| return ZX_OK; |
| } |
| |
| zx_status_t UsbHciResetDevice(uint32_t hub_address, uint32_t device_id) { |
| if (device_id == kDeviceId) { |
| device_reset_ = true; |
| } |
| return ZX_OK; |
| } |
| |
| size_t UsbHciGetMaxTransferSize(uint32_t device_id, uint8_t ep_address) { |
| return ((device_id == kDeviceId) && (ep_address == kTransferSizeEndpoint)) ? kMaxTransferSize |
| : 0; |
| } |
| |
| zx_status_t UsbHciCancelAll(uint32_t device_id, uint8_t ep_address) { |
| auto requests = pending_requests(); |
| requests.CompleteAll(ZX_ERR_CANCELED, 0); |
| return ZX_OK; |
| } |
| |
| void UsbHciSetBusInterface(const usb_bus_interface_protocol_t* bus_intf) {} |
| |
| size_t UsbHciGetMaxDeviceCount() { return 0; } |
| |
| size_t UsbHciGetRequestSize() { |
| return usb::BorrowedRequest<void>::RequestSize(sizeof(usb_request_t)); |
| } |
| |
| void UsbHciRequestQueue(usb_request_t* usb_request_, const usb_request_complete_t* complete_cb_) { |
| usb::BorrowedRequest<void> request(usb_request_, *complete_cb_, sizeof(usb_request_t)); |
| if ((request.request()->header.ep_address == 0) && !custom_control_) { |
| if ((request.request()->setup.bmRequestType == |
| (USB_DIR_IN | USB_TYPE_STANDARD | USB_RECIP_DEVICE)) && |
| (request.request()->setup.bRequest == USB_REQ_GET_DESCRIPTOR)) { |
| uint8_t type = static_cast<uint8_t>(request.request()->setup.wValue >> 8); |
| uint8_t index = static_cast<uint8_t>(request.request()->setup.wValue); |
| switch (type) { |
| case USB_DT_DEVICE: { |
| usb_device_descriptor_t* descriptor; |
| request.Mmap(reinterpret_cast<void**>(&descriptor)); |
| descriptor->bNumConfigurations = 2; |
| descriptor->idVendor = kVendorId; |
| descriptor->idProduct = kProductId; |
| descriptor->bDeviceClass = kDeviceClass; |
| descriptor->bDeviceSubClass = kDeviceSubclass; |
| descriptor->bDeviceProtocol = kDeviceProtocol; |
| request.Complete(ZX_OK, sizeof(*descriptor)); |
| } |
| return; |
| case USB_DT_CONFIG: { |
| usb_configuration_descriptor_t* descriptor; |
| request.Mmap(reinterpret_cast<void**>(&descriptor)); |
| descriptor->wTotalLength = sizeof(*descriptor); |
| descriptor->bConfigurationValue = static_cast<uint8_t>(index + 1); |
| request.Complete(ZX_OK, sizeof(*descriptor)); |
| } |
| return; |
| case USB_DT_STRING: { |
| if (index == 0) { |
| // Fetch language table |
| usb_langid_desc_t* languages; |
| request.Mmap(reinterpret_cast<void**>(&languages)); |
| languages->bLength = 2 + (2 * 2); |
| languages->wLangIds[0] = MakeConstant<uint16_t, 2>("EN"); |
| languages->wLangIds[1] = MakeConstant<uint16_t, 2>("ES"); |
| request.Complete(ZX_OK, languages->bLength); |
| return; |
| } |
| index--; |
| uint16_t lang = request.request()->setup.wIndex; |
| switch (lang) { |
| case MakeConstant<uint16_t, 2>("EN"): |
| lang = 0; |
| break; |
| case MakeConstant<uint16_t, 2>("ES"): |
| lang = 1; |
| break; |
| } |
| if ((index < 2) && (lang < 2)) { |
| usb_string_desc_t* descriptor; |
| request.Mmap(reinterpret_cast<void**>(&descriptor)); |
| descriptor->bLength = static_cast<uint8_t>( |
| 2 + (2 * std::char_traits<char16_t>::length(kStringDescriptors[index][lang]))); |
| memcpy(descriptor->code_points, kStringDescriptors[index][lang], |
| descriptor->bLength - 2); |
| request.Complete(ZX_OK, descriptor->bLength); |
| return; |
| } |
| } |
| } |
| } |
| if ((request.request()->setup.bmRequestType == |
| (USB_DIR_OUT | USB_TYPE_STANDARD | USB_RECIP_DEVICE)) && |
| (request.request()->setup.bRequest == USB_REQ_SET_CONFIGURATION)) { |
| selected_configuration_ = static_cast<uint8_t>(request.request()->setup.wValue); |
| request.Complete(ZX_OK, 0); |
| return; |
| } |
| request.Complete(ZX_ERR_INVALID_ARGS, 0); |
| return; |
| } |
| pending_requests_.push(std::move(request)); |
| } |
| |
| zx_status_t UsbHciEnableEndpoint(uint32_t device_id, const usb_endpoint_descriptor_t* ep_desc, |
| const usb_ss_ep_comp_descriptor_t* ss_com_desc, bool enable) { |
| if (!enable_endpoint_hook_) { |
| return ZX_ERR_BAD_STATE; |
| } |
| return enable_endpoint_hook_(device_id, ep_desc, ss_com_desc, enable); |
| } |
| |
| const usb_hci_protocol_t* proto() { return &proto_; } |
| |
| uint8_t configuration() { return selected_configuration_; } |
| |
| usb::BorrowedRequestQueue<void> pending_requests() { return std::move(pending_requests_); } |
| |
| void set_custom_control_handling(bool enabled) { custom_control_ = enabled; } |
| |
| void set_enable_endpoint_hook( |
| fit::function<zx_status_t(uint32_t device_id, const usb_endpoint_descriptor_t* ep_desc, |
| const usb_ss_ep_comp_descriptor_t* ss_com_desc, bool enable)> |
| hook) { |
| enable_endpoint_hook_ = std::move(hook); |
| } |
| |
| uint8_t reset_endpoint() { return reset_endpoint_; } |
| |
| bool device_reset() { return device_reset_; } |
| |
| private: |
| bool device_reset_ = false; |
| bool custom_control_ = false; |
| uint8_t selected_configuration_ = 0; |
| usb_hci_protocol_t proto_; |
| uint8_t reset_endpoint_ = 0; |
| fit::function<zx_status_t(uint32_t device_id, const usb_endpoint_descriptor_t* ep_desc, |
| const usb_ss_ep_comp_descriptor_t* ss_com_desc, bool enable)> |
| enable_endpoint_hook_; |
| usb::BorrowedRequestQueue<void> pending_requests_; |
| }; |
| |
| class FakeTimer : public UsbWaiterInterface { |
| public: |
| zx_status_t Wait(sync_completion_t* completion, zx_duration_t duration) override { |
| return timeout_handler_(completion, duration); |
| } |
| |
| void set_timeout_handler(fit::function<zx_status_t(sync_completion_t*, zx_duration_t)> handler) { |
| timeout_handler_ = std::move(handler); |
| } |
| |
| private: |
| fit::function<zx_status_t(sync_completion_t*, zx_duration_t)> timeout_handler_; |
| }; |
| |
| class DeviceTest : public zxtest::Test { |
| public: |
| DeviceTest() { |
| timer_ = fbl::MakeRefCounted<FakeTimer>(); |
| timer_->set_timeout_handler([=](sync_completion_t* completion, zx_duration_t duration) { |
| return sync_completion_wait(completion, duration); |
| }); |
| } |
| |
| auto& get_fidl() { |
| if (!fidl_.has_value()) { |
| fidl_.emplace(std::move(ddk_.FidlClient())); |
| } |
| return fidl_.value(); |
| } |
| |
| void SetUp() override { |
| auto device = fbl::MakeRefCounted<UsbDevice>(fake_ddk::kFakeParent, |
| ddk::UsbHciProtocolClient(hci_.proto()), kDeviceId, |
| kHubId, kDeviceSpeed, timer_); |
| ASSERT_OK(device->Init()); |
| device_ = static_cast<UsbDevice*>(ddk_.get_context()); |
| } |
| |
| void TearDown() override { |
| device_->DdkUnbindDeprecated(); |
| ASSERT_TRUE(ddk_.get_remove_called()); |
| device_->DdkRelease(); |
| } |
| |
| void CancelAll() { ASSERT_OK(device_->UsbCancelAll(1)); } |
| |
| size_t get_parent_request_size() { return device_->UsbGetRequestSize(); } |
| |
| void RequestQueue(usb_request_t* request, const usb_request_complete_t* completion) { |
| device_->UsbRequestQueue(request, completion); |
| } |
| |
| ddk::UsbProtocolClient get_usb_protocol() { |
| usb_protocol_t usb; |
| device_->DdkGetProtocol(ZX_PROTOCOL_USB, &usb); |
| return ddk::UsbProtocolClient(&usb); |
| } |
| |
| ddk::UsbBusProtocolClient get_usb_bus_protocol() { |
| usb_bus_protocol_t usb; |
| device_->DdkGetProtocol(ZX_PROTOCOL_USB_BUS, &usb); |
| return ddk::UsbBusProtocolClient(&usb); |
| } |
| |
| void set_custom_control_handling(bool enabled) { hci_.set_custom_control_handling(enabled); } |
| |
| usb::BorrowedRequestQueue<void> get_pending_requests() { return hci_.pending_requests(); } |
| |
| uint8_t get_configuration() { return hci_.configuration(); } |
| |
| void set_enable_endpoint_hook( |
| fit::function<zx_status_t(uint32_t device_id, const usb_endpoint_descriptor_t* ep_desc, |
| const usb_ss_ep_comp_descriptor_t* ss_com_desc, bool enable)> |
| hook) { |
| hci_.set_enable_endpoint_hook(std::move(hook)); |
| } |
| |
| void set_timeout_handler(fit::function<zx_status_t(sync_completion_t*, zx_duration_t)> handler) { |
| timer_->set_timeout_handler(std::move(handler)); |
| } |
| |
| bool get_device_reset() { return hci_.device_reset(); } |
| |
| uint8_t get_reset_endpoint() { return hci_.reset_endpoint(); } |
| |
| private: |
| fbl::RefPtr<FakeTimer> timer_; |
| std::optional<llcpp::fuchsia::hardware::usb::device::Device::SyncClient> fidl_; |
| FakeHci hci_; |
| Binder ddk_; |
| // UsbDevice context pointer owned by us through FakeDDK |
| // This is freed by calling DdkRelease in TearDown. |
| UsbDevice* device_; |
| }; |
| |
| // CancelAll-specific test |
| |
| TEST_F(DeviceTest, CancelAllCancelsAllRequestsThenReturns) { |
| using Request = usb::CallbackRequest<sizeof(max_align_t)>; |
| std::atomic<size_t> completed = 0; |
| for (size_t i = 0; i < 500; i++) { |
| std::optional<Request> request; |
| Request::Alloc(&request, 0, 1, get_parent_request_size(), |
| [&](Request request) { completed++; }); |
| request->Queue(*this); |
| } |
| CancelAll(); |
| ASSERT_EQ(completed.load(), 500); |
| } |
| |
| // USB protocol tests |
| |
| TEST_F(DeviceTest, ControlOut) { |
| auto usb = get_usb_protocol(); |
| uint8_t data[5]; |
| for (size_t i = 0; i < 5; i++) { |
| data[i] = static_cast<uint8_t>(i); |
| } |
| set_custom_control_handling(true); |
| set_timeout_handler([&](sync_completion_t* completion, zx_duration_t duration) { |
| EXPECT_EQ(duration, 9001); |
| auto requests = get_pending_requests(); |
| auto request = requests.pop(); |
| EXPECT_EQ(request->request()->header.length, sizeof(data)); |
| void* mapped_data; |
| request->Mmap(&mapped_data); |
| EXPECT_EQ(0, memcmp(data, data, sizeof(data))); |
| EXPECT_EQ(request->request()->setup.bmRequestType, 5); |
| EXPECT_EQ(request->request()->setup.bRequest, 97); |
| EXPECT_EQ(request->request()->setup.wValue, 8); |
| EXPECT_EQ(request->request()->setup.wIndex, 12); |
| |
| request->Complete(ZX_OK, sizeof(data)); |
| return sync_completion_wait(completion, ZX_TIME_INFINITE); |
| }); |
| ASSERT_OK(usb.ControlOut(5, 97, 8, 12, 9001, data, sizeof(data))); |
| } |
| |
| TEST_F(DeviceTest, ControlIn) { |
| auto usb = get_usb_protocol(); |
| uint8_t data[5]; |
| for (size_t i = 0; i < 5; i++) { |
| data[i] = static_cast<uint8_t>(i); |
| } |
| set_custom_control_handling(true); |
| set_timeout_handler([&](sync_completion_t* completion, zx_duration_t duration) { |
| EXPECT_EQ(duration, 9001); |
| auto requests = get_pending_requests(); |
| auto request = requests.pop(); |
| EXPECT_EQ(request->request()->header.length, sizeof(data)); |
| void* mapped_data; |
| request->Mmap(&mapped_data); |
| memcpy(mapped_data, data, sizeof(data)); |
| EXPECT_EQ(request->request()->setup.bmRequestType, 5 | USB_DIR_IN); |
| EXPECT_EQ(request->request()->setup.bRequest, 97); |
| EXPECT_EQ(request->request()->setup.wValue, 8); |
| EXPECT_EQ(request->request()->setup.wIndex, 12); |
| |
| request->Complete(ZX_OK, sizeof(data)); |
| return sync_completion_wait(completion, ZX_TIME_INFINITE); |
| }); |
| uint8_t buffer[5]; |
| size_t actual; |
| ASSERT_OK(usb.ControlIn(5 | USB_DIR_IN, 97, 8, 12, 9001, buffer, sizeof(buffer), &actual)); |
| ASSERT_EQ(0, memcmp(buffer, data, sizeof(buffer))); |
| } |
| |
| TEST_F(DeviceTest, RequestQueue) { |
| auto usb = get_usb_protocol(); |
| using Request = usb::CallbackRequest<sizeof(max_align_t)>; |
| std::optional<Request> request; |
| sync_completion_t completion; |
| usb_request_t* request_ptr; |
| Request::Alloc(&request, 0, 1, get_parent_request_size(), [&](Request owned_request) { |
| ASSERT_EQ(owned_request.request(), request_ptr); |
| sync_completion_signal(&completion); |
| }); |
| request_ptr = request->request(); |
| request->Queue(usb); |
| auto requests = get_pending_requests(); |
| auto usb_request = requests.pop(); |
| usb_request->Complete(ZX_OK, 0); |
| sync_completion_wait(&completion, ZX_TIME_INFINITE); |
| } |
| |
| TEST_F(DeviceTest, GetSpeed) { |
| auto usb = get_usb_protocol(); |
| ASSERT_EQ(usb.GetSpeed(), kDeviceSpeed); |
| } |
| |
| TEST_F(DeviceTest, SetInterface) { |
| auto usb = get_usb_protocol(); |
| set_custom_control_handling(true); |
| set_timeout_handler([&](sync_completion_t* completion, zx_duration_t duration) { |
| EXPECT_EQ(duration, ZX_TIME_INFINITE); |
| auto requests = get_pending_requests(); |
| auto request = requests.pop(); |
| EXPECT_EQ(request->request()->header.ep_address, 0); |
| EXPECT_EQ(request->request()->setup.bmRequestType, |
| USB_DIR_OUT | USB_TYPE_STANDARD | USB_RECIP_INTERFACE); |
| EXPECT_EQ(request->request()->setup.bRequest, USB_REQ_SET_INTERFACE); |
| EXPECT_EQ(request->request()->setup.wValue, 5); |
| EXPECT_EQ(request->request()->setup.wIndex, 98); |
| request->Complete(ZX_OK, 0); |
| return sync_completion_wait(completion, ZX_TIME_INFINITE); |
| }); |
| usb.SetInterface(98, 5); |
| } |
| |
| TEST_F(DeviceTest, GetConfiguration) { |
| auto usb = get_usb_protocol(); |
| ASSERT_EQ(usb.GetConfiguration(), 1); |
| } |
| |
| TEST_F(DeviceTest, SetConfiguration) { |
| auto usb = get_usb_protocol(); |
| ASSERT_OK(usb.SetConfiguration(2)); |
| ASSERT_EQ(get_configuration(), 2); |
| } |
| |
| TEST_F(DeviceTest, EnableEndpoint) { |
| auto usb = get_usb_protocol(); |
| usb_endpoint_descriptor_t epdesc; |
| usb_ss_ep_comp_descriptor_t ss; |
| set_enable_endpoint_hook([&](uint32_t device_id, const usb_endpoint_descriptor_t* ep_desc, |
| const usb_ss_ep_comp_descriptor_t* ss_com_desc, bool enable) { |
| EXPECT_EQ(device_id, kDeviceId); |
| EXPECT_EQ(ep_desc, &epdesc); |
| EXPECT_EQ(ss_com_desc, &ss); |
| EXPECT_TRUE(enable); |
| return ZX_OK; |
| }); |
| ASSERT_OK(usb.EnableEndpoint(&epdesc, &ss, true)); |
| } |
| |
| TEST_F(DeviceTest, ResetEndpoint) { |
| auto usb = get_usb_protocol(); |
| ASSERT_OK(usb.ResetEndpoint(97)); |
| ASSERT_EQ(get_reset_endpoint(), 97); |
| } |
| |
| TEST_F(DeviceTest, ResetDevice) { |
| auto usb = get_usb_protocol(); |
| ASSERT_OK(usb.ResetDevice()); |
| ASSERT_TRUE(get_device_reset()); |
| } |
| |
| TEST_F(DeviceTest, GetMaxTransferSize) { |
| auto usb = get_usb_protocol(); |
| ASSERT_EQ(usb.GetMaxTransferSize(kTransferSizeEndpoint), kMaxTransferSize); |
| } |
| |
| TEST_F(DeviceTest, GetDeviceId) { |
| auto usb = get_usb_protocol(); |
| ASSERT_EQ(usb.GetDeviceId(), kDeviceId); |
| } |
| |
| TEST_F(DeviceTest, GetDeviceDescriptor) { |
| auto usb = get_usb_protocol(); |
| usb_device_descriptor_t descriptor; |
| usb.GetDeviceDescriptor(&descriptor); |
| ASSERT_EQ(descriptor.idVendor, kVendorId); |
| ASSERT_EQ(descriptor.idProduct, kProductId); |
| ASSERT_EQ(descriptor.bDeviceClass, kDeviceClass); |
| ASSERT_EQ(descriptor.bDeviceSubClass, kDeviceSubclass); |
| ASSERT_EQ(descriptor.bDeviceProtocol, kDeviceProtocol); |
| } |
| |
| TEST_F(DeviceTest, GetConfigurationDescriptorLength) { |
| auto usb = get_usb_protocol(); |
| size_t length; |
| ASSERT_OK(usb.GetConfigurationDescriptorLength(1, &length)); |
| ASSERT_EQ(length, sizeof(usb_configuration_descriptor_t)); |
| } |
| |
| TEST_F(DeviceTest, GetConfigurationDescriptor) { |
| auto usb = get_usb_protocol(); |
| usb_configuration_descriptor_t descriptor; |
| size_t actual; |
| ASSERT_OK(usb.GetConfigurationDescriptor(1, &descriptor, sizeof(descriptor), &actual)); |
| ASSERT_EQ(actual, sizeof(descriptor)); |
| ASSERT_EQ(descriptor.bConfigurationValue, 1); |
| ASSERT_EQ(descriptor.wTotalLength, sizeof(descriptor)); |
| } |
| |
| TEST_F(DeviceTest, GetDescriptorsLength) { |
| auto usb = get_usb_protocol(); |
| ASSERT_EQ(usb.GetDescriptorsLength(), sizeof(usb_configuration_descriptor_t)); |
| } |
| |
| TEST_F(DeviceTest, GetDescriptors) { |
| auto usb = get_usb_protocol(); |
| usb_configuration_descriptor_t descriptor; |
| size_t actual; |
| usb.GetDescriptors(&descriptor, sizeof(descriptor), &actual); |
| ASSERT_EQ(actual, sizeof(descriptor)); |
| ASSERT_EQ(descriptor.bConfigurationValue, 1); |
| ASSERT_EQ(descriptor.wTotalLength, sizeof(descriptor)); |
| } |
| |
| TEST_F(DeviceTest, GetCurrentFrame) { |
| auto usb = get_usb_protocol(); |
| ASSERT_EQ(usb.GetCurrentFrame(), kCurrentFrame); |
| } |
| |
| TEST_F(DeviceTest, GetRequestSize) { |
| auto usb = get_usb_protocol(); |
| ASSERT_EQ(usb.GetRequestSize(), kRequestSize); |
| } |
| |
| TEST_F(DeviceTest, FidlGetSpeed) { |
| auto& fidl = get_fidl(); |
| auto result = fidl.GetDeviceSpeed(); |
| ASSERT_TRUE(result.ok()); |
| ASSERT_EQ(result->speed, kDeviceSpeed); |
| } |
| |
| TEST_F(DeviceTest, FidlGetDescriptor) { |
| auto& fidl = get_fidl(); |
| auto result = fidl.GetDeviceDescriptor(); |
| usb_device_descriptor_t* descriptor = |
| reinterpret_cast<usb_device_descriptor_t*>(result->desc.data()); |
| ASSERT_EQ(descriptor->idVendor, kVendorId); |
| ASSERT_EQ(descriptor->idProduct, kProductId); |
| ASSERT_EQ(descriptor->bDeviceClass, kDeviceClass); |
| ASSERT_EQ(descriptor->bDeviceSubClass, kDeviceSubclass); |
| ASSERT_EQ(descriptor->bDeviceProtocol, kDeviceProtocol); |
| } |
| |
| TEST_F(DeviceTest, FidlGetConfigurationDescriptorSize) { |
| auto& fidl = get_fidl(); |
| auto result = fidl.GetConfigurationDescriptorSize(1); |
| ASSERT_TRUE(result.ok()); |
| ASSERT_OK(result->s); |
| ASSERT_EQ(result->size, sizeof(usb_configuration_descriptor_t)); |
| } |
| |
| TEST_F(DeviceTest, FidlGetConfigurationDescriptor) { |
| auto& fidl = get_fidl(); |
| const usb_configuration_descriptor_t* descriptor; |
| auto result = fidl.GetConfigurationDescriptor(1); |
| ASSERT_TRUE(result.ok()); |
| ASSERT_OK(result->s); |
| ASSERT_EQ(result->desc.count(), sizeof(*descriptor)); |
| descriptor = reinterpret_cast<const usb_configuration_descriptor_t*>(result->desc.data()); |
| ASSERT_EQ(descriptor->bConfigurationValue, 1); |
| ASSERT_EQ(descriptor->wTotalLength, sizeof(*descriptor)); |
| } |
| |
| TEST_F(DeviceTest, FidlGetStringDescriptor) { |
| auto& fidl = get_fidl(); |
| char golden[128]; |
| size_t dest_len = 128; |
| utf16_to_utf8(reinterpret_cast<const uint16_t*>(kStringDescriptors[0][0]), 7, |
| reinterpret_cast<uint8_t*>(golden), &dest_len); |
| auto expected = MakeConstant<uint16_t, 2>("EN"); |
| auto result = fidl.GetStringDescriptor(1, MakeConstant<uint16_t, 2>("EN")); |
| ASSERT_TRUE(result.ok()); |
| ASSERT_OK(result->s); |
| ASSERT_EQ(result->actual_lang_id, expected); |
| ASSERT_EQ(result->desc.size(), dest_len); // this is way off for some reason |
| ASSERT_EQ(memcmp(result->desc.data(), golden, dest_len), 0); |
| |
| dest_len = 128; |
| utf16_to_utf8(reinterpret_cast<const uint16_t*>(kStringDescriptors[0][1]), 6, |
| reinterpret_cast<uint8_t*>(golden), &dest_len); |
| expected = MakeConstant<uint16_t, 2>("ES"); |
| result = fidl.GetStringDescriptor(1, MakeConstant<uint16_t, 2>("ES")); |
| ASSERT_TRUE(result.ok()); |
| ASSERT_OK(result->s); |
| ASSERT_EQ(result->actual_lang_id, expected); |
| ASSERT_EQ(result->desc.size(), dest_len); |
| ASSERT_EQ(memcmp(result->desc.data(), golden, dest_len), 0); |
| |
| dest_len = 128; |
| utf16_to_utf8(reinterpret_cast<const uint16_t*>(kStringDescriptors[1][0]), 6, |
| reinterpret_cast<uint8_t*>(golden), &dest_len); |
| expected = MakeConstant<uint16_t, 2>("EN"); |
| result = fidl.GetStringDescriptor(2, MakeConstant<uint16_t, 2>("EN")); |
| ASSERT_TRUE(result.ok()); |
| ASSERT_OK(result->s); |
| ASSERT_EQ(result->actual_lang_id, expected); |
| ASSERT_EQ(result->desc.size(), dest_len); |
| ASSERT_EQ(memcmp(result->desc.data(), golden, dest_len), 0); |
| |
| dest_len = 128; |
| utf16_to_utf8(reinterpret_cast<const uint16_t*>(kStringDescriptors[1][1]), 11, |
| reinterpret_cast<uint8_t*>(golden), &dest_len); |
| expected = MakeConstant<uint16_t, 2>("ES"); |
| result = fidl.GetStringDescriptor(2, MakeConstant<uint16_t, 2>("ES")); |
| ASSERT_TRUE(result.ok()); |
| ASSERT_OK(result->s); |
| ASSERT_EQ(result->actual_lang_id, expected); |
| ASSERT_EQ(result->desc.size(), dest_len); |
| ASSERT_EQ(memcmp(result->desc.data(), golden, dest_len), 0); |
| } |
| |
| TEST_F(DeviceTest, FidlSetInterface) { |
| auto& fidl = get_fidl(); |
| set_custom_control_handling(true); |
| set_timeout_handler([&](sync_completion_t* completion, zx_duration_t duration) { |
| EXPECT_EQ(duration, ZX_TIME_INFINITE); |
| auto requests = get_pending_requests(); |
| auto request = requests.pop(); |
| EXPECT_EQ(request->request()->header.ep_address, 0); |
| EXPECT_EQ(request->request()->setup.bmRequestType, |
| USB_DIR_OUT | USB_TYPE_STANDARD | USB_RECIP_INTERFACE); |
| EXPECT_EQ(request->request()->setup.bRequest, USB_REQ_SET_INTERFACE); |
| EXPECT_EQ(request->request()->setup.wValue, 5); |
| EXPECT_EQ(request->request()->setup.wIndex, 98); |
| request->Complete(ZX_OK, 0); |
| return sync_completion_wait(completion, ZX_TIME_INFINITE); |
| }); |
| auto result = fidl.SetInterface(98, 5); |
| ASSERT_TRUE(result.ok()); |
| ASSERT_OK(result->s); |
| } |
| |
| TEST_F(DeviceTest, FidlGetDeviceId) { |
| auto& fidl = get_fidl(); |
| auto result = fidl.GetDeviceId(); |
| ASSERT_TRUE(result.ok()); |
| ASSERT_EQ(result->device_id, kDeviceId); |
| } |
| |
| TEST_F(DeviceTest, FidlGetHubDeviceId) { |
| auto& fidl = get_fidl(); |
| auto result = fidl.GetHubDeviceId(); |
| ASSERT_TRUE(result.ok()); |
| ASSERT_EQ(result->hub_device_id, kHubId); |
| } |
| |
| TEST_F(DeviceTest, FidlGetConfiguration) { |
| auto& fidl = get_fidl(); |
| auto result = fidl.GetConfiguration(); |
| ASSERT_TRUE(result.ok()); |
| ASSERT_EQ(result->configuration, 1); |
| } |
| |
| TEST_F(DeviceTest, FidlSetConfiguration) { |
| auto& fidl = get_fidl(); |
| auto result = fidl.SetConfiguration(2); |
| ASSERT_TRUE(result.ok()); |
| ASSERT_OK(result->s); |
| ASSERT_EQ(get_configuration(), 2); |
| } |
| |
| } // namespace usb_bus |