blob: a7714bcb5f4656e33ae11bbcec36a7807dd3a83b [file] [log] [blame]
// 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;
}