| // 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 <stddef.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <thread> |
| |
| #include <fbl/auto_call.h> |
| #include <usbhost/usbhost.h> |
| #include <unittest/unittest.h> |
| #include <zircon/device/usb-peripheral-test.h> |
| #include <zircon/device/usb-peripheral.h> |
| |
| namespace { |
| |
| struct usb_device* dev = nullptr; |
| struct usb_endpoint_descriptor* bulk_out_ep = nullptr; |
| struct usb_endpoint_descriptor* bulk_in_ep = nullptr; |
| struct usb_endpoint_descriptor* intr_ep = nullptr; |
| |
| constexpr size_t BUFFER_SIZE = 4096; |
| |
| // Data to send to the device |
| uint8_t send_buf[BUFFER_SIZE] = {}; |
| // Buffer for receiving data from the device |
| int receive_buf[BUFFER_SIZE] = {}; |
| |
| constexpr int TIMEOUT = 1000; // 1 seecond |
| |
| |
| // Fill send_buf with random values. |
| void randomize() { |
| // Generate some random data. |
| for (size_t i = 0; i < sizeof(send_buf); i++) { |
| send_buf[i] = static_cast<uint8_t>(random()); |
| } |
| } |
| |
| // Tests control and interrupt transfers with specified transfer size. |
| bool control_interrupt_test(size_t transfer_size) { |
| BEGIN_TEST; |
| |
| randomize(); |
| |
| // Send data to device via OUT control request. |
| int ret = usb_device_control_transfer(dev, |
| USB_DIR_OUT | USB_TYPE_VENDOR | USB_TYPE_VENDOR | USB_RECIP_INTERFACE, |
| USB_PERIPHERAL_TEST_SET_DATA, 0, 0, send_buf, transfer_size, TIMEOUT); |
| EXPECT_EQ(ret, transfer_size); |
| |
| // Receive data back from device via IN control request. |
| ret = usb_device_control_transfer(dev, |
| USB_DIR_IN | USB_TYPE_VENDOR | USB_TYPE_VENDOR | USB_RECIP_INTERFACE, |
| USB_PERIPHERAL_TEST_GET_DATA, 0, 0, receive_buf, transfer_size, |
| TIMEOUT); |
| EXPECT_EQ(ret, transfer_size); |
| |
| // Sent and received data should match. |
| EXPECT_EQ(memcmp(send_buf, receive_buf, transfer_size), 0); |
| |
| // Create a thread to wait for interrupt request. |
| auto thread_func = [](struct usb_request** req) -> void { |
| *req = usb_request_wait(dev, TIMEOUT); |
| }; |
| struct usb_request* complete_req = nullptr; |
| std::thread wait_thread (thread_func, &complete_req); |
| |
| // Queue read for interrupt request |
| auto* req = usb_request_new(dev, intr_ep); |
| EXPECT_NE(req, nullptr); |
| req->buffer = receive_buf; |
| req->buffer_length = static_cast<int>(transfer_size); |
| ret = usb_request_queue(req); |
| EXPECT_EQ(ret, 0); |
| |
| // Ask the device to send us an interrupt request containing the data we sent earlier. |
| ret = usb_device_control_transfer(dev, |
| USB_DIR_OUT | USB_TYPE_VENDOR | USB_TYPE_VENDOR | USB_RECIP_INTERFACE, |
| USB_PERIPHERAL_TEST_SEND_INTERUPT, 0, 0, nullptr, 0, TIMEOUT); |
| EXPECT_EQ(ret, 0); |
| |
| wait_thread.join(); |
| |
| EXPECT_EQ(complete_req, req); |
| |
| // Sent data should match payload of interrupt request. |
| EXPECT_EQ(memcmp(send_buf, receive_buf, transfer_size), 0); |
| |
| usb_request_free(req); |
| |
| END_TEST; |
| } |
| |
| // Test control and interrupt requests with 8 byte transfer size. |
| bool control_interrupt_test_8() { |
| return control_interrupt_test(8); |
| } |
| |
| // Test control and interrupt requests with 64 byte transfer size. |
| bool control_interrupt_test_64() { |
| return control_interrupt_test(64); |
| } |
| |
| // Test control and interrupt requests with 100 byte transfer size. |
| bool control_interrupt_test_100() { |
| return control_interrupt_test(100); |
| } |
| |
| // Test control and interrupt requests with 256 byte transfer size. |
| bool control_interrupt_test_256() { |
| return control_interrupt_test(256); |
| } |
| |
| // Test control and interrupt requests with 1000 byte transfer size. |
| bool control_interrupt_test_1000() { |
| return control_interrupt_test(1000); |
| } |
| |
| // Tests bulk OUT and IN transfers. |
| // Send BUFFER_SIZE bytes to device, read back and compare. |
| bool bulk_test() { |
| BEGIN_TEST; |
| |
| auto* send_req = usb_request_new(dev, bulk_out_ep); |
| EXPECT_NE(send_req, nullptr); |
| send_req->buffer = send_buf; |
| send_req->buffer_length = static_cast<int>(BUFFER_SIZE); |
| |
| auto* receive_req = usb_request_new(dev, bulk_in_ep); |
| EXPECT_NE(send_req, nullptr); |
| receive_req->buffer = receive_buf; |
| receive_req->buffer_length = static_cast<int>(BUFFER_SIZE); |
| |
| for (int i = 0; i < 10; i++) { |
| randomize(); |
| |
| // Create a thread to wait for request completions. |
| auto thread_func = [](struct usb_request** reqs) -> void { |
| *reqs++ = usb_request_wait(dev, TIMEOUT); |
| *reqs = usb_request_wait(dev, TIMEOUT); |
| }; |
| struct usb_request* complete_reqs[2] = {}; |
| std::thread wait_thread (thread_func, complete_reqs); |
| |
| // Queue requests in both directions |
| int ret = usb_request_queue(receive_req); |
| EXPECT_EQ(ret, 0); |
| ret = usb_request_queue(send_req); |
| EXPECT_EQ(ret, 0); |
| |
| wait_thread.join(); |
| |
| EXPECT_NE(complete_reqs[0], nullptr); |
| EXPECT_NE(complete_reqs[1], nullptr); |
| |
| // Sent and received data should match. |
| EXPECT_EQ(memcmp(send_buf, receive_buf, BUFFER_SIZE), 0); |
| } |
| |
| usb_request_free(send_req); |
| usb_request_free(receive_req); |
| |
| END_TEST; |
| } |
| |
| // usb_host_load() will call this for all connected USB devices. |
| int usb_device_added(const char *dev_name, void *client_data) { |
| usb_descriptor_iter iter; |
| struct usb_descriptor_header* header; |
| struct usb_interface_descriptor* intf = nullptr; |
| int ret; |
| |
| dev = usb_device_open(dev_name); |
| if (!dev) { |
| fprintf(stderr, "usb_device_open failed for %s\n", dev_name); |
| return 0; |
| } |
| |
| uint16_t vid = usb_device_get_vendor_id(dev); |
| uint16_t pid = usb_device_get_product_id(dev); |
| |
| if (vid != GOOGLE_USB_VID || pid != GOOGLE_USB_PERIPHERAL_TEST_PID) { |
| // Device doesn't match, so keep looking. |
| usb_device_close(dev); |
| dev = nullptr; |
| return 0; |
| } |
| |
| usb_descriptor_iter_init(dev, &iter); |
| |
| while ((header = usb_descriptor_iter_next(&iter)) != nullptr) { |
| if (header->bDescriptorType == USB_DT_INTERFACE) { |
| intf = reinterpret_cast<struct usb_interface_descriptor*>(header); |
| } else if (header->bDescriptorType == USB_DT_ENDPOINT) { |
| auto* ep = reinterpret_cast<struct usb_endpoint_descriptor*>(header); |
| if (usb_endpoint_type(ep) == USB_ENDPOINT_XFER_BULK) { |
| if (usb_endpoint_dir_in(ep)) { |
| bulk_in_ep = ep; |
| } else { |
| bulk_out_ep = ep; |
| } |
| } else if (usb_endpoint_type(ep) == USB_ENDPOINT_XFER_INT) { |
| intr_ep = ep; |
| } |
| } |
| } |
| |
| if (!intf || !bulk_out_ep || !bulk_in_ep || !intr_ep) { |
| fprintf(stderr, "could not find all our endpoints\n"); |
| goto fail; |
| } |
| |
| ret = usb_device_claim_interface(dev, intf->bInterfaceNumber); |
| if (ret < 0) { |
| fprintf(stderr, "usb_device_claim_interface failed\n"); |
| goto fail; |
| } |
| |
| // Device found, exit from usb_host_load(). |
| return 1; |
| |
| fail: |
| usb_device_close(dev); |
| dev = nullptr; |
| // Test done, exit from usb_host_load(). |
| return 1; |
| } |
| |
| int usb_device_removed(const char *dev_name, void *client_data) { |
| return 0; |
| } |
| |
| int usb_discovery_done(void *client_data) { |
| return 0; |
| } |
| |
| } // anonymous namespace |
| |
| BEGIN_TEST_CASE(usb_peripheral_tests) |
| RUN_TEST(control_interrupt_test_8); |
| RUN_TEST(control_interrupt_test_64); |
| RUN_TEST(control_interrupt_test_100); |
| RUN_TEST(control_interrupt_test_256); |
| RUN_TEST(control_interrupt_test_1000); |
| RUN_TEST(bulk_test); |
| END_TEST_CASE(usb_peripheral_tests) |
| |
| int main(int argc, char** argv) { |
| struct usb_host_context* context = usb_host_init(); |
| if (!context) { |
| fprintf(stderr, "usb_host_context failed\n"); |
| return -1; |
| } |
| auto cleanup = fbl::MakeAutoCall([&]() { |
| usb_host_cleanup(context); |
| }); |
| |
| auto ret = usb_host_load(context, usb_device_added, usb_device_removed, usb_discovery_done, |
| nullptr); |
| if (ret < 0) { |
| fprintf(stderr, "usb_host_load failed!\n"); |
| goto fail; |
| } |
| if (!dev) { |
| fprintf(stderr, "No device found, skipping tests.\n"); |
| goto fail; |
| } |
| |
| ret = unittest_run_all_tests(argc, argv) ? 0 : -1; |
| |
| fail: |
| if (dev) { |
| usb_device_close(dev); |
| } |
| |
| return ret; |
| } |