blob: 6572e80c1950e0119e778b0d4393f6daa5329288 [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 <getopt.h>
#include <lib/fit/defer.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/time.h>
#include <zircon/status.h>
#include <zircon/types.h>
#include <algorithm>
#include <numeric>
#include <span>
#include <thread>
#include <fbl/algorithm.h>
#include <usb/peripheral-test.h>
#include <usb/peripheral.h>
#include <usbhost/usbhost.h>
#include <zxtest/zxtest.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_out_ep = nullptr;
struct usb_endpoint_descriptor* intr_in_ep = nullptr;
static uint8_t test_interface;
constexpr uint64_t kUsbTimeoutMilliseconds = 1000; // 1 second
constexpr uint64_t kSecondsToNanoseconds = 1'000'000'000;
constexpr uint64_t kMicrosecondsToNanoseconds = 1000;
constexpr uint64_t kNanosecondsToMilliseconds = 1'000'000;
constexpr uint64_t kBulkRequestSize = 512;
constexpr uint64_t kInterruptRequestSize = 64;
constexpr char kUsageMsg[] = R"(
[OPTIONS]
--help -help [-h] Prints this message.
--time -time [-t] Configurable time for tests to run.
--bytes -bytes [-b] Configurable transfer bytes for data transfer.
--bulk-iterations -bulk-iterations [-i] Configurable iteration for bulk transfer.
--buffersize -buffersize [-s] Configurable buffer size. Default buffer size set to 4096 bytes.
--retest -retest [-r] Configurable retest transfer type. Specify one of
{CONTROL, INTERRUPT, BULK} to retest transfer type.
--retest-iterations -retest-iterations [-n] Configurable number of times the transfer type is tested.
)";
class UsbPeripheralConfigurableTests : public ::zxtest::Test {
public:
// User configurable time (in milliseconds) for USB peripheral tests.
inline static int64_t time_ms_ = 100;
// User configurable transfer byte size.
inline static size_t bytes_ = 64;
// User configurable iterations for bulk transfer test.
inline static int bulk_iterations_ = 2;
// User configurable buffer size.
inline static uint64_t buffer_size_ = 4096;
// User configurable to repeat test.
enum class TransferOptions { CONTROL, INTERRUPT, BULK, NONE };
inline static TransferOptions retest_config_ = TransferOptions::BULK;
inline static int retest_iterations_ = 1;
static zx_status_t SetRetestConfigFromString(const std::string& str);
// Help option.
inline static bool help_ = false;
};
zx_status_t UsbPeripheralConfigurableTests::SetRetestConfigFromString(const std::string& str) {
if (str == "CONTROL") {
retest_config_ = UsbPeripheralConfigurableTests::TransferOptions::CONTROL;
return ZX_OK;
} else if (str == "INTERRUPT") {
retest_config_ = UsbPeripheralConfigurableTests::TransferOptions::INTERRUPT;
return ZX_OK;
} else if (str == "BULK") {
retest_config_ = UsbPeripheralConfigurableTests::TransferOptions::BULK;
return ZX_OK;
} else {
retest_config_ = UsbPeripheralConfigurableTests::TransferOptions::NONE;
return ZX_ERR_INVALID_ARGS;
}
}
class Timer {
public:
void Start() { start_ = GetCurrentTimeNanoseconds(); }
int64_t Stop() {
stop_ = GetTime();
return stop_;
}
// Gets the amount of milliseconds since |start_|.
int64_t GetElapsedTimeMilliseconds() const {
return (GetCurrentTimeNanoseconds() - start_) / kNanosecondsToMilliseconds;
}
private:
int64_t start_;
int64_t stop_;
static int64_t GetCurrentTimeNanoseconds() {
struct timeval tv;
if (gettimeofday(&tv, nullptr) < 0) {
return 0;
}
return tv.tv_sec * kSecondsToNanoseconds + tv.tv_usec * kMicrosecondsToNanoseconds;
}
int64_t GetTime() const {
return (GetCurrentTimeNanoseconds() - start_) / kNanosecondsToMilliseconds;
}
};
void pattern_buffer(cpp20::span<uint8_t> container, uint8_t distinct_buffer_number) {
// Generate a known pattern for the buffer.
uint64_t container_size = container.size();
for (size_t i = 0; i < container_size - 1; i += 2) {
container[i] = 0x1;
container[i + 1] = distinct_buffer_number - 1;
}
// Setting buffer borders
container.front() = distinct_buffer_number;
container.back() = distinct_buffer_number;
}
// Tests control and interrupt transfers with specified transfer size.
void control_interrupt_test(size_t transfer_size) {
uint8_t distinct_buffer_number = 3;
std::unique_ptr<uint8_t[]> send_buffer = std::make_unique<uint8_t[]>(transfer_size);
std::unique_ptr<uint8_t[]> receive_buffer = std::make_unique<uint8_t[]>(transfer_size);
pattern_buffer({send_buffer.get(), transfer_size}, distinct_buffer_number);
Timer timer;
timer.Start();
// Send data to device via OUT control request.
int ret = usb_device_control_transfer(
dev, USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_INTERFACE, USB_PERIPHERAL_TEST_SET_DATA, 0,
test_interface, send_buffer.get(), static_cast<int>(transfer_size), kUsbTimeoutMilliseconds);
ASSERT_EQ(ret, static_cast<int>(transfer_size));
// Receive data back from device via IN control request.
ret = usb_device_control_transfer(dev, USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_INTERFACE,
USB_PERIPHERAL_TEST_GET_DATA, 0, test_interface,
receive_buffer.get(), static_cast<int>(transfer_size),
kUsbTimeoutMilliseconds);
ASSERT_EQ(ret, static_cast<int>(transfer_size));
// Sent and received data should match.
EXPECT_EQ(memcmp(send_buffer.get(), receive_buffer.get(), transfer_size), 0);
// Create a thread to wait for interrupt request.
auto thread_func = [](struct usb_request** req) -> void {
*req = usb_request_wait(dev, kUsbTimeoutMilliseconds);
};
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_in_ep);
EXPECT_NE(req, nullptr);
req->buffer = receive_buffer.get();
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_RECIP_INTERFACE,
USB_PERIPHERAL_TEST_SEND_INTERUPT, 0, test_interface, nullptr,
0, kUsbTimeoutMilliseconds);
ASSERT_EQ(ret, 0);
wait_thread.join();
ASSERT_EQ(complete_req, req);
EXPECT_EQ(static_cast<size_t>(req->actual_length), transfer_size);
// Sent data should match payload of interrupt request.
EXPECT_EQ(memcmp(send_buffer.get(), receive_buffer.get(), transfer_size), 0);
usb_request_free(req);
const int64_t elapsed_time = timer.Stop();
printf("[ ] Transferred %zu bytes/%lums\n", transfer_size, elapsed_time);
}
// Tests bulk transfers with specified transfer size.
void bulk_test(uint64_t buffer_size, size_t bulk_iterations) {
buffer_size = fbl::round_up(buffer_size, kBulkRequestSize);
uint8_t distinct_buffer_number = 3;
std::unique_ptr<uint8_t[]> send_buffer = std::make_unique<uint8_t[]>(buffer_size);
std::unique_ptr<uint8_t[]> receive_buffer = std::make_unique<uint8_t[]>(buffer_size);
// Initialize the buffer
memset(send_buffer.get(), 9, buffer_size);
memset(receive_buffer.get(), 9, buffer_size);
auto* send_req = usb_request_new(dev, bulk_out_ep);
EXPECT_NE(send_req, nullptr);
send_req->buffer = send_buffer.get();
send_req->buffer_length = static_cast<int>(buffer_size);
auto* receive_req = usb_request_new(dev, bulk_in_ep);
EXPECT_NE(receive_req, nullptr);
receive_req->buffer = receive_buffer.get();
receive_req->buffer_length = static_cast<int>(buffer_size);
for (size_t i = 0; i < bulk_iterations; i++) {
pattern_buffer({send_buffer.get(), buffer_size}, distinct_buffer_number);
// Create a thread to wait for request completions.
auto thread_func = [](struct usb_request** reqs) -> void {
*reqs++ = usb_request_wait(dev, kUsbTimeoutMilliseconds);
*reqs = usb_request_wait(dev, kUsbTimeoutMilliseconds);
};
struct usb_request* complete_reqs[2] = {};
std::thread wait_thread(thread_func, complete_reqs);
// Queue requests in both directions
int ret = usb_request_queue(send_req);
EXPECT_EQ(ret, 0);
ret = usb_request_queue(receive_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_buffer.get(), receive_buffer.get(), buffer_size), 0);
// Changing the number for the next buffer and bulk iteration. Helps verify that each transfer
// is different.
if (distinct_buffer_number > 9)
distinct_buffer_number = 3;
else
distinct_buffer_number++;
}
usb_request_free(send_req);
usb_request_free(receive_req);
}
void interrupt_test(size_t transfer_bytes) {
transfer_bytes = fbl::round_up(transfer_bytes, kInterruptRequestSize);
uint8_t distinct_buffer_number = 3;
std::unique_ptr<uint8_t[]> send_buffer = std::make_unique<uint8_t[]>(transfer_bytes);
std::unique_ptr<uint8_t[]> receive_buffer = std::make_unique<uint8_t[]>(transfer_bytes);
// Initialize the buffer
memset(send_buffer.get(), 9, transfer_bytes);
memset(receive_buffer.get(), 9, transfer_bytes);
auto* send_req = usb_request_new(dev, intr_out_ep);
ASSERT_NE(send_req, nullptr);
send_req->buffer = send_buffer.get();
send_req->buffer_length = static_cast<int>(transfer_bytes);
auto* receive_req = usb_request_new(dev, intr_in_ep);
ASSERT_NE(receive_req, nullptr);
receive_req->buffer = receive_buffer.get();
receive_req->buffer_length = static_cast<int>(transfer_bytes);
pattern_buffer({send_buffer.get(), transfer_bytes}, distinct_buffer_number);
// Create a thread to wait for request completions.
auto thread_func = [](struct usb_request** reqs) -> void {
*reqs++ = usb_request_wait(dev, kUsbTimeoutMilliseconds);
*reqs = usb_request_wait(dev, kUsbTimeoutMilliseconds);
};
struct usb_request* complete_reqs[2] = {};
std::thread wait_thread(thread_func, complete_reqs);
// Queue requests in both directions
int res = usb_request_queue(send_req);
EXPECT_EQ(res, 0);
res = usb_request_queue(receive_req);
EXPECT_EQ(res, 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_buffer.get(), receive_buffer.get(), transfer_bytes), 0);
usb_request_free(send_req);
usb_request_free(receive_req);
}
// ====================== User configurable tests ===================================
// Tests end to end interrupt transfer.
TEST_F(UsbPeripheralConfigurableTests, interrupt_function) {
ASSERT_NO_FATAL_FAILURE(interrupt_test(bytes_));
}
// Tests bulk transfer for long periods of time. Time is user determined.
TEST_F(UsbPeripheralConfigurableTests, stress_test_configurable) {
int times_ran = 0;
std::vector<int64_t> recorded_times;
Timer timer_loop;
Timer timer_recording;
timer_loop.Start();
while (timer_loop.GetElapsedTimeMilliseconds() < time_ms_) {
timer_recording.Start();
ASSERT_NO_FATAL_FAILURE(bulk_test(buffer_size_, bulk_iterations_));
recorded_times.push_back(timer_recording.Stop());
times_ran++;
}
int64_t elapsed_time = timer_loop.Stop();
uint64_t total_transfer_bytes = times_ran * buffer_size_ * bulk_iterations_;
printf("[ ] Bulk test ran %d times in %lums\n", times_ran, elapsed_time);
printf("[ ] Total bytes transferred: %lu\n", total_transfer_bytes);
if (!recorded_times.empty()) {
uint64_t average =
std::reduce(recorded_times.begin(), recorded_times.end()) / recorded_times.size();
printf("[ ] - Average transfer: %lu/%ldms per bulk test call\n",
buffer_size_ * bulk_iterations_, average);
}
EXPECT_FALSE(recorded_times.empty());
}
// Tests the repetition of a transfer type test.
TEST_F(UsbPeripheralConfigurableTests, retest_transfer_type_test) {
std::vector<int64_t> recorded_times;
Timer timer;
switch (retest_config_) {
case TransferOptions::CONTROL:
for (int i = 0; i < retest_iterations_; i++) {
timer.Start();
ASSERT_NO_FATAL_FAILURE(control_interrupt_test(bytes_));
recorded_times.push_back(timer.Stop());
}
break;
case TransferOptions::INTERRUPT:
for (int i = 0; i < retest_iterations_; i++) {
timer.Start();
ASSERT_NO_FATAL_FAILURE(interrupt_test(bytes_));
recorded_times.push_back(timer.Stop());
}
break;
case TransferOptions::BULK:
for (int i = 0; i < retest_iterations_; i++) {
timer.Start();
ASSERT_NO_FATAL_FAILURE(bulk_test(buffer_size_, bulk_iterations_));
recorded_times.push_back(timer.Stop());
}
break;
default:
fprintf(stderr, "[ ] Transfer type does not exist.\n");
break;
}
if (!recorded_times.empty()) {
uint64_t average =
std::reduce(recorded_times.begin(), recorded_times.end()) / recorded_times.size();
printf("[ ] - Average time for %d iterations is: %ldms\n", retest_iterations_,
average);
}
EXPECT_FALSE(recorded_times.empty());
}
// Tests bulk OUT and IN transfers from user configurable data.
TEST_F(UsbPeripheralConfigurableTests, bulk_test) {
ASSERT_NO_FATAL_FAILURE(bulk_test(buffer_size_, bulk_iterations_));
}
// Test control and interrupt requests from the user configurable data
TEST_F(UsbPeripheralConfigurableTests, control_interrupt_test_configurable) {
ASSERT_NO_FATAL_FAILURE(control_interrupt_test(bytes_));
}
// Tests the buffer size of Bulk transfers.
TEST_F(UsbPeripheralConfigurableTests, size_transfer_bytes) {
// To run this test, first it checks if the control transfer is working. If it succeeds, it goes
// on to test the size transfer.
std::unique_ptr<uint8_t[]> send_buf = std::make_unique<uint8_t[]>(bytes_);
std::unique_ptr<uint8_t[]> receive_buf = std::make_unique<uint8_t[]>(bytes_);
memset(send_buf.get(), 9, bytes_);
int res = usb_device_control_transfer(
dev, USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_INTERFACE, USB_PERIPHERAL_TEST_SET_DATA, 0,
test_interface, send_buf.get(), static_cast<int>(bytes_), kUsbTimeoutMilliseconds);
ASSERT_EQ(res, static_cast<int>(bytes_));
// Receive data back from device via IN control request.
res = usb_device_control_transfer(
dev, USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_INTERFACE, USB_PERIPHERAL_TEST_GET_DATA, 0,
test_interface, receive_buf.get(), static_cast<int>(bytes_), kUsbTimeoutMilliseconds);
ASSERT_EQ(res, static_cast<int>(bytes_));
// Sent and received data should match.
ASSERT_EQ(memcmp(send_buf.get(), receive_buf.get(), bytes_), 0);
// Now the size_test can run. A temporary buffer size that is not a multiple of kBulkRequestSize.
uint64_t temp_buffer_size = 700;
uint8_t distinct_buffer_number = 1;
std::unique_ptr<uint8_t[]> receive_buffer = std::make_unique<uint8_t[]>(temp_buffer_size);
pattern_buffer({receive_buffer.get(), temp_buffer_size}, distinct_buffer_number);
auto* receive_req = usb_request_new(dev, bulk_in_ep);
EXPECT_NE(receive_req, nullptr);
receive_req->buffer = receive_buffer.get();
receive_req->buffer_length = kBulkRequestSize;
int ret =
usb_device_control_transfer(dev, USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_INTERFACE,
USB_PERIPHERAL_TEST_BULK_TRANSFER_SIZE, 0, test_interface,
&temp_buffer_size, sizeof(uint64_t), kUsbTimeoutMilliseconds);
ASSERT_EQ(ret, sizeof(uint64_t));
auto thread_func = [](struct usb_request** req) -> void {
*req = usb_request_wait(dev, kUsbTimeoutMilliseconds);
};
struct usb_request* complete_req = nullptr;
std::thread wait_thread(thread_func, &complete_req);
// Queue read for bulk request
ret = usb_request_queue(receive_req);
EXPECT_EQ(ret, 0);
wait_thread.join();
EXPECT_EQ(receive_req, complete_req);
// Transfer is expected to fail due to incorrect buffer size and the receive buffer will
// be different.
EXPECT_FALSE(std::all_of(
receive_buffer.get(), receive_buffer.get() + temp_buffer_size,
[distinct_buffer_number](uint8_t elem) { return elem == distinct_buffer_number; }));
usb_request_free(receive_req);
}
// =============================== Fixed tests ===================================
// Test control and interrupt requests with 8 byte transfer size.
TEST(UsbPeripheralFixedTests, control_interrupt_test_8) {
ASSERT_NO_FATAL_FAILURE(control_interrupt_test(8));
}
// Test control and interrupt requests with 256 byte transfer size.
TEST(UsbPeripheralFixedTests, control_interrupt_test_256) {
ASSERT_NO_FATAL_FAILURE(control_interrupt_test(256));
}
// Test control and interrupt requests with 1024 byte transfer size.
TEST(UsbPeripheralFixedTests, control_interrupt_test_1024) {
ASSERT_NO_FATAL_FAILURE(control_interrupt_test(1024));
}
// 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;
}
auto cleanup = fit::defer([]() {
usb_device_close(dev);
dev = nullptr;
});
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_FUNCTION_TEST_PID && pid != GOOGLE_USB_CDC_AND_FUNCTION_TEST_PID)) {
// Device doesn't match, so keep looking.
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) {
if (usb_endpoint_dir_in(ep)) {
intr_in_ep = ep;
} else {
intr_out_ep = ep;
}
}
}
}
if (!intf || !bulk_out_ep || !bulk_in_ep || !intr_in_ep || !intr_out_ep) {
fprintf(stderr, "could not find all our endpoints\n");
return 1;
}
ret = usb_device_claim_interface(dev, intf->bInterfaceNumber);
if (ret < 0) {
fprintf(stderr, "usb_device_claim_interface failed\n");
}
test_interface = intf->bInterfaceNumber;
cleanup.cancel();
// Device found, 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
int main(int argc, char** argv) {
// Making command line arguments global to pass to UsbPeripheralConfigurableTests.
static const struct option long_opts[] = {
{"time", required_argument, nullptr, 't'},
{"bytes", required_argument, nullptr, 'b'},
{"bulk-iterations", required_argument, nullptr, 'i'},
{"buffer-size", required_argument, nullptr, 's'},
{"help", no_argument, nullptr, 'h'},
{"retest", required_argument, nullptr, 'r'},
{"retest-iterations", required_argument, nullptr, 'n'},
{0, 0, 0, 0},
};
int opt;
while ((opt = getopt_long_only(argc, argv, "t:b:i:s:r:n:h", long_opts, nullptr)) != -1) {
switch (opt) {
case 't':
UsbPeripheralConfigurableTests::time_ms_ = std::stoi(optarg);
break;
case 'b':
UsbPeripheralConfigurableTests::bytes_ = std::stoi(optarg);
break;
case 'i':
UsbPeripheralConfigurableTests::bulk_iterations_ = std::stoi(optarg);
break;
case 's': {
uint64_t buffer_size = std::stoi(optarg);
if (buffer_size == 0) {
fprintf(stderr, "Buffer size is 0, nothing will be transferred. Choose another size.\n");
return ZX_ERR_INVALID_ARGS;
}
UsbPeripheralConfigurableTests::buffer_size_ = buffer_size;
break;
}
case 'r': {
zx_status_t status = UsbPeripheralConfigurableTests::SetRetestConfigFromString(optarg);
if (status != ZX_OK) {
fprintf(
stderr,
"Invalid transfer type for usb-peripheral-tests, ZX_STATUS: ZX_ERR_INVALID_ARGS. Expected one of: CONTROL, INTERRUPT, BULK\n");
return status;
}
break;
}
case 'n':
UsbPeripheralConfigurableTests::retest_iterations_ = std::stoi(optarg);
break;
case 'h':
UsbPeripheralConfigurableTests::help_ = true;
fprintf(stderr, kUsageMsg);
return 0;
}
}
struct usb_host_context* context = usb_host_init();
if (!context) {
fprintf(stderr, "usb_host_context failed\n");
return -1;
}
auto cleanup = fit::defer([context]() {
usb_host_cleanup(context);
if (dev) {
usb_device_close(dev);
}
});
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");
return -1;
}
if (!dev) {
fprintf(stderr, "No device found, skipping tests.\n");
return 0;
}
// TODO: Add arguments for the zxtest flag options.
return zxtest::RunAllTests(0, nullptr) ? 0 : -1;
}