| // 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/request-cpp.h" |
| |
| #include <fuchsia/hardware/usb/function/cpp/banjo.h> |
| #include <lib/fake-bti/bti.h> |
| #include <lib/trace-engine/types.h> |
| #include <lib/zx/bti.h> |
| #include <lib/zx/vmo.h> |
| #include <zircon/errors.h> |
| #include <zircon/types.h> |
| |
| #include <array> |
| #include <vector> |
| |
| #include <fbl/algorithm.h> |
| #include <trace-test-utils/fixture_macros.h> |
| #include <zxtest/zxtest.h> |
| |
| #include "usb/usb-request.h" |
| |
| namespace { |
| |
| using Request = usb::Request<void>; |
| |
| constexpr size_t kParentReqSize = sizeof(usb_request_t); |
| constexpr size_t kReqSize = Request::RequestSize(kParentReqSize); |
| constexpr usb_request_complete_callback_t kNoCallback = {}; |
| |
| TEST(UsbRequestListTest, TrivialLifetime) { |
| usb::RequestList<void> list; |
| usb::BorrowedRequestList<void> unowned_list; |
| } |
| |
| TEST(UsbRequestListTest, SingleRequest) { |
| std::optional<Request> opt_request; |
| ASSERT_EQ(Request::Alloc(&opt_request, 0, 0, kParentReqSize), ZX_OK); |
| Request request = *std::move(opt_request); |
| |
| usb::RequestList<void> list; |
| // Empty list. |
| EXPECT_EQ(list.size(), 0u); |
| EXPECT_TRUE(list.begin() == std::nullopt); |
| |
| list.push_back(&request); |
| EXPECT_EQ(list.size(), 1u); |
| |
| // List only has one request. |
| EXPECT_TRUE(list.prev(&request) == std::nullopt); |
| EXPECT_TRUE(list.next(&request) == std::nullopt); |
| |
| std::optional<size_t> idx = list.find(&request); |
| EXPECT_TRUE(idx.has_value()); |
| EXPECT_EQ(idx.value(), 0u); |
| |
| // Delete the request and verify it's no longer in the list. |
| EXPECT_TRUE(list.erase(&request)); |
| EXPECT_EQ(list.size(), 0u); |
| |
| idx = list.find(&request); |
| EXPECT_FALSE(idx.has_value()); |
| } |
| |
| TEST(UsbRequestListTest, MultipleRequest) { |
| usb::RequestList<void> list; |
| // This is for verifying prev / next pointer values when iterating the list. |
| usb_request_t* raw_reqs[10]; |
| |
| for (size_t i = 0; i < 10; i++) { |
| std::optional<Request> opt_request; |
| ASSERT_EQ(Request::Alloc(&opt_request, 0, 0, kParentReqSize), ZX_OK); |
| Request request = *std::move(opt_request); |
| |
| list.push_back(&request); |
| EXPECT_EQ(list.size(), i + 1); |
| |
| raw_reqs[i] = request.take(); |
| } |
| EXPECT_EQ(list.size(), 10u); |
| |
| // Verify iterating in both directions. |
| auto opt_request = list.begin(); |
| for (size_t i = 0; i < 10; i++) { |
| EXPECT_TRUE(opt_request.has_value()); |
| Request request = *std::move(opt_request); |
| |
| std::optional<size_t> idx = list.find(&request); |
| EXPECT_TRUE(idx.has_value()); |
| EXPECT_EQ(idx.value(), i); |
| |
| auto prev = list.prev(&request); |
| if (i == 0) { |
| EXPECT_FALSE(prev.has_value()); |
| } else { |
| EXPECT_TRUE(prev.has_value()); |
| EXPECT_EQ(prev->request(), raw_reqs[i - 1]); |
| } |
| |
| auto next = list.next(&request); |
| if (i == 9) { |
| EXPECT_FALSE(next.has_value()); |
| } else { |
| EXPECT_TRUE(next.has_value()); |
| EXPECT_EQ(next->request(), raw_reqs[i + 1]); |
| } |
| |
| opt_request = std::move(next); |
| } |
| EXPECT_FALSE(opt_request.has_value()); |
| |
| for (size_t i = 0; i < 10; i++) { |
| auto opt_request = list.begin(); |
| EXPECT_TRUE(opt_request.has_value()); |
| Request request = *std::move(opt_request); |
| EXPECT_TRUE(list.erase(&request)); |
| |
| // Force the destructor to run. |
| [[maybe_unused]] auto req = Request(raw_reqs[i], kParentReqSize); |
| } |
| EXPECT_EQ(list.size(), 0u); |
| EXPECT_FALSE(list.begin().has_value()); |
| } |
| |
| TEST(UsbRequestListTest, Move) { |
| usb::RequestList<void> list1; |
| usb::RequestList<void> list2; |
| |
| usb_request_t* raw_reqs[10]; |
| |
| for (size_t i = 0; i < 10; i++) { |
| std::optional<Request> opt_request; |
| ASSERT_EQ(Request::Alloc(&opt_request, 0, 0, kParentReqSize), ZX_OK); |
| Request request = *std::move(opt_request); |
| list1.push_back(&request); |
| raw_reqs[i] = request.take(); |
| } |
| EXPECT_EQ(list1.size(), 10u); |
| EXPECT_EQ(list2.size(), 0u); |
| |
| list2 = std::move(list1); |
| EXPECT_EQ(list1.size(), 0u); |
| EXPECT_EQ(list2.size(), 10u); |
| |
| size_t count = 0; |
| std::optional<Request> opt_request = list2.begin(); |
| while (opt_request) { |
| Request request = *std::move(opt_request); |
| std::optional<Request> next = list2.next(&request); |
| |
| EXPECT_EQ(request.request(), raw_reqs[count]); |
| EXPECT_TRUE(list2.erase(&request)); |
| |
| // Force the destructor to run. |
| [[maybe_unused]] auto req = Request(raw_reqs[count], kParentReqSize); |
| |
| count++; |
| opt_request = std::move(next); |
| } |
| EXPECT_EQ(count, 10u); |
| EXPECT_TRUE(list2.begin() == std::nullopt); |
| } |
| |
| TEST(UsbRequestListTest, Release) { |
| usb::RequestList<void> list; |
| usb_request_t* raw_reqs[10]; |
| |
| for (size_t i = 0; i < 10; i++) { |
| std::optional<Request> opt_request; |
| ASSERT_EQ(Request::Alloc(&opt_request, 0, 0, kParentReqSize), ZX_OK); |
| Request request = *std::move(opt_request); |
| list.push_back(&request); |
| EXPECT_EQ(list.size(), i + 1); |
| |
| raw_reqs[i] = request.take(); |
| } |
| |
| list.Release(); |
| EXPECT_EQ(list.size(), 0u); |
| EXPECT_FALSE(list.begin().has_value()); |
| |
| for (size_t i = 0; i < 10; i++) { |
| // Force the destructor to run. |
| [[maybe_unused]] auto req = Request(raw_reqs[i], kParentReqSize); |
| } |
| } |
| |
| TEST(UsbRequestListTest, MultipleLayer) { |
| using FirstLayerReq = usb::BorrowedRequest<void>; |
| using SecondLayerReq = usb::Request<void>; |
| |
| constexpr size_t kBaseReqSize = sizeof(usb_request_t); |
| constexpr size_t kFirstLayerReqSize = FirstLayerReq::RequestSize(kBaseReqSize); |
| |
| usb_request_t* raw_reqs[10]; |
| |
| usb::RequestList<void> second_layer_list; |
| for (size_t i = 0; i < 10; i++) { |
| std::optional<SecondLayerReq> opt_request; |
| ASSERT_EQ(SecondLayerReq::Alloc(&opt_request, 0, 0, kFirstLayerReqSize), ZX_OK); |
| ASSERT_TRUE(opt_request.has_value()); |
| Request request = *std::move(opt_request); |
| second_layer_list.push_back(&request); |
| raw_reqs[i] = request.take(); |
| } |
| EXPECT_EQ(second_layer_list.size(), 10u); |
| |
| usb::BorrowedRequestList<void> first_layer_list; |
| // Add the requests also into the first layer list. |
| for (size_t i = 0; i < 10; i++) { |
| FirstLayerReq unowned(raw_reqs[i], kNoCallback, kBaseReqSize, /* allow_destruct */ false); |
| first_layer_list.push_back(&unowned); |
| } |
| EXPECT_EQ(first_layer_list.size(), 10u); |
| |
| // Remove the requests from both lists. |
| for (size_t i = 0; i < 10; i++) { |
| FirstLayerReq unowned(raw_reqs[i], kBaseReqSize); |
| std::optional<size_t> idx = first_layer_list.find(&unowned); |
| EXPECT_TRUE(idx.has_value()); |
| EXPECT_EQ(idx.value(), 0u); |
| EXPECT_TRUE(first_layer_list.erase(&unowned)); |
| |
| SecondLayerReq request(unowned.take(), kFirstLayerReqSize); |
| idx = second_layer_list.find(&request); |
| EXPECT_TRUE(idx.has_value()); |
| EXPECT_EQ(idx.value(), 0u); |
| EXPECT_TRUE(second_layer_list.erase(&request)); |
| } |
| EXPECT_EQ(first_layer_list.size(), 0u); |
| EXPECT_EQ(second_layer_list.size(), 0u); |
| } |
| |
| TEST(UsbRequestListTest, MultipleLayerWithStorage) { |
| using FirstLayerReq = usb::BorrowedRequest<char>; |
| using SecondLayerReq = usb::Request<uint64_t>; |
| |
| constexpr size_t kBaseReqSize = sizeof(usb_request_t); |
| constexpr size_t kFirstLayerReqSize = FirstLayerReq::RequestSize(kBaseReqSize); |
| |
| usb_request_t* raw_reqs[10]; |
| |
| usb::RequestList<uint64_t> second_layer_list; |
| for (size_t i = 0; i < 10; i++) { |
| std::optional<SecondLayerReq> opt_request; |
| ASSERT_EQ(SecondLayerReq::Alloc(&opt_request, 0, 0, kFirstLayerReqSize), ZX_OK); |
| SecondLayerReq request = *std::move(opt_request); |
| |
| *request.private_storage() = i; |
| EXPECT_EQ(*request.private_storage(), i); |
| second_layer_list.push_back(&request); |
| raw_reqs[i] = request.take(); |
| } |
| EXPECT_EQ(second_layer_list.size(), 10u); |
| |
| usb::BorrowedRequestList<char> first_layer_list; |
| size_t count = 0; |
| // Add the requests also into the first layer list. |
| for (size_t i = 0; i < 10; i++) { |
| FirstLayerReq unowned(raw_reqs[i], kNoCallback, kBaseReqSize, /* allow_destruct */ false); |
| *unowned.private_storage() = static_cast<char>('a' + first_layer_list.size()); |
| first_layer_list.push_back(&unowned); |
| } |
| EXPECT_EQ(first_layer_list.size(), 10u); |
| |
| // Verify the first layer list node's private storage and also erase them along the way. |
| count = 0; |
| auto opt_unowned = first_layer_list.begin(); |
| while (opt_unowned) { |
| auto unowned = *std::move(opt_unowned); |
| auto next = first_layer_list.next(&unowned); |
| |
| EXPECT_EQ(*unowned.private_storage(), static_cast<char>('a' + count)); |
| EXPECT_TRUE(first_layer_list.erase(&unowned)); |
| |
| ++count; |
| opt_unowned = std::move(next); |
| } |
| EXPECT_EQ(count, 10); |
| EXPECT_EQ(first_layer_list.size(), 0u); |
| |
| // Verify the second layer list node's private storage and also erase them along the way. |
| count = 0; |
| auto opt_request = second_layer_list.begin(); |
| while (opt_request) { |
| auto request = *std::move(opt_request); |
| auto next = second_layer_list.next(&request); |
| |
| EXPECT_EQ(*request.private_storage(), count); |
| EXPECT_TRUE(second_layer_list.erase(&request)); |
| |
| ++count; |
| opt_request = std::move(next); |
| } |
| EXPECT_EQ(count, 10); |
| EXPECT_EQ(second_layer_list.size(), 0u); |
| |
| for (size_t i = 0; i < 10; i++) { |
| // Force the destructor to run. |
| [[maybe_unused]] auto req = SecondLayerReq(raw_reqs[i], kFirstLayerReqSize); |
| } |
| } |
| |
| TEST(UsbRequestListTest, MultipleLayerWithCallback) { |
| using FirstLayerReq = usb::BorrowedRequest<char>; |
| using SecondLayerReq = usb::Request<uint64_t>; |
| |
| constexpr size_t kBaseReqSize = sizeof(usb_request_t); |
| constexpr size_t kFirstLayerReqSize = FirstLayerReq::RequestSize(kBaseReqSize); |
| |
| usb_request_t* raw_reqs[10]; |
| |
| usb::RequestList<uint64_t> second_layer_list; |
| for (size_t i = 0; i < 10; i++) { |
| std::optional<SecondLayerReq> opt_request; |
| ASSERT_EQ(SecondLayerReq::Alloc(&opt_request, 0, 0, kFirstLayerReqSize), ZX_OK); |
| SecondLayerReq request = *std::move(opt_request); |
| |
| *request.private_storage() = i; |
| EXPECT_EQ(*request.private_storage(), i); |
| second_layer_list.push_back(&request); |
| |
| raw_reqs[i] = request.take(); |
| } |
| EXPECT_EQ(second_layer_list.size(), 10u); |
| |
| std::atomic<size_t> num_callbacks{0}; |
| |
| auto callback = [](void* ctx, usb_request_t* request) { |
| auto counter = static_cast<std::atomic<size_t>*>(ctx); |
| ++(*counter); |
| }; |
| |
| usb_request_complete_callback_t complete_cb = { |
| .callback = callback, |
| .ctx = &num_callbacks, |
| }; |
| |
| { |
| usb::BorrowedRequestList<char> first_layer_list; |
| |
| // Store the requests into the first layer list. |
| for (size_t i = 0; i < 10; i++) { |
| FirstLayerReq unowned(raw_reqs[i], complete_cb, kBaseReqSize, |
| /* allow_destruct */ false); |
| first_layer_list.push_back(&unowned); |
| } |
| EXPECT_EQ(first_layer_list.size(), 10u); |
| EXPECT_EQ(second_layer_list.size(), 10u); |
| } |
| // The first layer list destruction should not trigger any callbacks. |
| EXPECT_EQ(num_callbacks.load(), 0u); |
| |
| // Verify the second layer list node's private storage and also erase them along the way. |
| size_t count = 0; |
| auto opt_request = second_layer_list.begin(); |
| while (opt_request) { |
| auto request = *std::move(opt_request); |
| auto next = second_layer_list.next(&request); |
| |
| EXPECT_EQ(*request.private_storage(), count); |
| EXPECT_TRUE(second_layer_list.erase(&request)); |
| |
| ++count; |
| opt_request = std::move(next); |
| } |
| EXPECT_EQ(count, 10); |
| EXPECT_EQ(second_layer_list.size(), 0u); |
| |
| for (size_t i = 0; i < 10; i++) { |
| // Force the destructor to run. |
| [[maybe_unused]] auto req = SecondLayerReq(raw_reqs[i], kFirstLayerReqSize); |
| } |
| } |
| |
| TEST(UsbRequestPoolTest, TrivialLifetime) { usb::RequestPool pool; } |
| |
| TEST(UsbRequestPoolTest, SingleRequest) { |
| std::optional<Request> request; |
| ASSERT_EQ(Request::Alloc(&request, 0, 0, kParentReqSize), ZX_OK); |
| |
| usb::RequestPool pool; |
| EXPECT_TRUE(pool.Get(kReqSize) == std::nullopt); |
| pool.Add(*std::move(request)); |
| EXPECT_TRUE(pool.Get(kReqSize + 1) == std::nullopt); |
| EXPECT_TRUE(pool.Get(kReqSize) != std::nullopt); |
| EXPECT_TRUE(pool.Get(kReqSize) == std::nullopt); |
| } |
| |
| TEST(UsbRequestPoolTest, MultipleRequest) { |
| usb::RequestPool pool; |
| |
| for (size_t i = 0; i < 10; i++) { |
| std::optional<Request> request; |
| ASSERT_EQ(Request::Alloc(&request, 0, 0, kParentReqSize), ZX_OK); |
| pool.Add(*std::move(request)); |
| } |
| |
| for (size_t i = 0; i < 10; i++) { |
| EXPECT_TRUE(pool.Get(kReqSize) != std::nullopt); |
| } |
| EXPECT_TRUE(pool.Get(kReqSize) == std::nullopt); |
| } |
| |
| TEST(UsbRequestPoolTest, MultipleSize) { |
| usb::RequestPool pool; |
| |
| for (size_t i = 0; i < 10; i++) { |
| const size_t size = kParentReqSize + i * 8; |
| std::optional<Request> request; |
| ASSERT_EQ(Request::Alloc(&request, 0, 0, size), ZX_OK); |
| pool.Add(*std::move(request)); |
| } |
| |
| for (size_t i = 0; i < 10; i++) { |
| const size_t size = Request::RequestSize(kParentReqSize + i * 8); |
| EXPECT_TRUE(pool.Get(size) != std::nullopt); |
| EXPECT_TRUE(pool.Get(size) == std::nullopt); |
| } |
| } |
| |
| TEST(UsbRequestPoolTest, Release) { |
| usb::RequestPool pool; |
| |
| for (size_t i = 0; i < 10; i++) { |
| std::optional<Request> request; |
| ASSERT_EQ(Request::Alloc(&request, 0, 0, kParentReqSize), ZX_OK); |
| pool.Add(*std::move(request)); |
| } |
| |
| pool.Release(); |
| EXPECT_TRUE(pool.Get(kReqSize) == std::nullopt); |
| } |
| |
| TEST(UsbRequestQueue, TrivialLifetime) { |
| usb::RequestQueue<void> queue; |
| usb::BorrowedRequestQueue<void> unowned_queue; |
| } |
| |
| TEST(UsbRequestQueue, SingleRequest) { |
| std::optional<Request> request; |
| ASSERT_EQ(Request::Alloc(&request, 0, 0, kParentReqSize), ZX_OK); |
| |
| usb::RequestQueue<void> queue; |
| EXPECT_TRUE(queue.pop() == std::nullopt); |
| queue.push(*std::move(request)); |
| EXPECT_TRUE(queue.pop() != std::nullopt); |
| EXPECT_TRUE(queue.pop() == std::nullopt); |
| } |
| |
| TEST(UsbRequestQueue, MultipleRequest) { |
| usb::RequestQueue<void> queue; |
| |
| for (size_t i = 0; i < 10; i++) { |
| std::optional<Request> request; |
| ASSERT_EQ(Request::Alloc(&request, 0, 0, kParentReqSize), ZX_OK); |
| queue.push(*std::move(request)); |
| } |
| |
| for (size_t i = 0; i < 10; i++) { |
| EXPECT_TRUE(queue.pop() != std::nullopt); |
| } |
| EXPECT_TRUE(queue.pop() == std::nullopt); |
| } |
| |
| TEST(UsbRequestQueue, Move) { |
| usb::RequestQueue<void> queue1; |
| usb::RequestQueue<void> queue2; |
| |
| for (size_t i = 0; i < 10; i++) { |
| std::optional<Request> request; |
| ASSERT_EQ(Request::Alloc(&request, 0, 0, kParentReqSize), ZX_OK); |
| queue1.push(*std::move(request)); |
| } |
| |
| queue2 = std::move(queue1); |
| EXPECT_TRUE(queue1.pop() == std::nullopt); |
| |
| for (size_t i = 0; i < 10; i++) { |
| EXPECT_TRUE(queue2.pop() != std::nullopt); |
| } |
| EXPECT_TRUE(queue2.pop() == std::nullopt); |
| } |
| |
| TEST(UsbRequestQueue, Release) { |
| usb::RequestQueue<void> queue; |
| |
| for (size_t i = 0; i < 10; i++) { |
| std::optional<Request> request; |
| ASSERT_EQ(Request::Alloc(&request, 0, 0, kParentReqSize), ZX_OK); |
| queue.push(*std::move(request)); |
| } |
| |
| queue.Release(); |
| EXPECT_TRUE(queue.pop() == std::nullopt); |
| } |
| |
| TEST(UsbRequestQueue, MultipleLayer) { |
| using FirstLayerReq = usb::BorrowedRequest<void>; |
| using SecondLayerReq = usb::Request<void>; |
| |
| constexpr size_t kBaseReqSize = sizeof(usb_request_t); |
| constexpr size_t kFirstLayerReqSize = FirstLayerReq::RequestSize(kBaseReqSize); |
| |
| usb::RequestQueue<void> queue; |
| for (size_t i = 0; i < 10; i++) { |
| std::optional<SecondLayerReq> request; |
| ASSERT_EQ(SecondLayerReq::Alloc(&request, 0, 0, kFirstLayerReqSize), ZX_OK); |
| queue.push(*std::move(request)); |
| } |
| |
| usb::BorrowedRequestQueue<void> queue2; |
| size_t count = 0; |
| for (auto request = queue.pop(); request; request = queue.pop()) { |
| FirstLayerReq unowned(request->take(), kNoCallback, kBaseReqSize); |
| queue2.push(std::move(unowned)); |
| ++count; |
| } |
| EXPECT_EQ(count, 10); |
| |
| count = 0; |
| for (auto unowned = queue2.pop(); unowned; unowned = queue2.pop()) { |
| SecondLayerReq request(unowned->take(), kFirstLayerReqSize); |
| queue.push(std::move(request)); |
| ++count; |
| } |
| EXPECT_EQ(count, 10); |
| } |
| |
| TEST(UsbRequestQueue, MultipleLayerWithStorage) { |
| using FirstLayerReq = usb::BorrowedRequest<char>; |
| using SecondLayerReq = usb::Request<uint64_t>; |
| |
| constexpr size_t kBaseReqSize = sizeof(usb_request_t); |
| constexpr size_t kFirstLayerReqSize = FirstLayerReq::RequestSize(kBaseReqSize); |
| |
| usb::RequestQueue<uint64_t> queue; |
| for (size_t i = 0; i < 10; i++) { |
| std::optional<SecondLayerReq> request; |
| ASSERT_EQ(SecondLayerReq::Alloc(&request, 0, 0, kFirstLayerReqSize), ZX_OK); |
| *request->private_storage() = i; |
| EXPECT_EQ(*request->private_storage(), i); |
| queue.push(*std::move(request)); |
| } |
| |
| usb::BorrowedRequestQueue<char> queue2; |
| size_t count = 0; |
| for (auto request = queue.pop(); request; request = queue.pop()) { |
| FirstLayerReq unowned(request->take(), kNoCallback, kBaseReqSize); |
| *unowned.private_storage() = static_cast<char>('a' + count); |
| queue2.push(std::move(unowned)); |
| ++count; |
| } |
| EXPECT_EQ(count, 10); |
| |
| count = 0; |
| for (auto unowned = queue2.pop(); unowned; unowned = queue2.pop()) { |
| EXPECT_EQ(*unowned->private_storage(), static_cast<char>('a' + count)); |
| SecondLayerReq request(unowned->take(), kFirstLayerReqSize); |
| EXPECT_EQ(*request.private_storage(), count); |
| queue.push(std::move(request)); |
| ++count; |
| } |
| EXPECT_EQ(count, 10); |
| } |
| |
| TEST(UsbRequestQueue, MultipleLayerWithCallback) { |
| using FirstLayerReq = usb::BorrowedRequest<char>; |
| using SecondLayerReq = usb::Request<uint64_t>; |
| |
| constexpr size_t kBaseReqSize = sizeof(usb_request_t); |
| constexpr size_t kFirstLayerReqSize = FirstLayerReq::RequestSize(kBaseReqSize); |
| constexpr int iter_count = 10; |
| |
| usb::RequestQueue<uint64_t> queue; |
| for (size_t i = 0; i < iter_count; i++) { |
| std::optional<SecondLayerReq> request; |
| ASSERT_EQ(SecondLayerReq::Alloc(&request, 0, 0, kFirstLayerReqSize), ZX_OK); |
| *request->private_storage() = i; |
| EXPECT_EQ(*request->private_storage(), i); |
| queue.push(*std::move(request)); |
| } |
| |
| auto callback = [](void* ctx, usb_request_t* request) { |
| auto* queue = static_cast<usb::RequestQueue<uint64_t>*>(ctx); |
| queue->push(SecondLayerReq(request, kFirstLayerReqSize)); |
| }; |
| usb_request_complete_callback_t complete_cb = { |
| .callback = callback, |
| .ctx = &queue, |
| }; |
| |
| usb::BorrowedRequestQueue<char> queue2; |
| for (auto request = queue.pop(); request; request = queue.pop()) { |
| FirstLayerReq unowned(request->take(), complete_cb, kBaseReqSize); |
| queue2.push(std::move(unowned)); |
| } |
| queue2.CompleteAll(ZX_OK, 0); |
| |
| size_t count = 0; |
| for (auto request = queue.pop(); request; request = queue.pop()) { |
| EXPECT_EQ(*request->private_storage(), count); |
| request->Release(); // Copy elision. |
| ++count; |
| } |
| EXPECT_EQ(count, iter_count); |
| } |
| |
| TEST(UsbRequestTest, Alloc) { |
| std::optional<Request> request; |
| EXPECT_OK(Request::Alloc(&request, 0, 0, kParentReqSize)); |
| } |
| |
| TEST(UsbRequestTest, VmoOffsetBoundsTest) { |
| zx::vmo vmo; |
| ASSERT_OK(zx::vmo::create(zx_system_get_page_size(), 0, &vmo)); |
| std::optional<Request> request; |
| ASSERT_OK(Request::Alloc(&request, 0, 0, kParentReqSize)); |
| |
| uint64_t offset = 0; |
| uint64_t length = zx_system_get_page_size(); |
| EXPECT_OK(request->Init(vmo, offset, 0, 0)); |
| EXPECT_OK(request->Init(vmo, offset, length, 0)); |
| EXPECT_OK(request->Init(vmo, offset, length - 1, 0)); |
| EXPECT_EQ(request->Init(vmo, offset, length + 1, 0), ZX_ERR_INVALID_ARGS); |
| EXPECT_EQ(request->Init(vmo, offset + 1, length, 0), ZX_ERR_INVALID_ARGS); |
| EXPECT_OK(request->Init(vmo, offset + 1, length - 1, 0)); |
| |
| offset = zx_system_get_page_size(); |
| length = 1; |
| EXPECT_EQ(request->Init(vmo, offset, length - 1, 0), ZX_OK); |
| EXPECT_EQ(request->Init(vmo, offset - 1, length, 0), ZX_OK); |
| EXPECT_EQ(request->Init(vmo, offset + 1, length, 0), ZX_ERR_INVALID_ARGS); |
| EXPECT_EQ(request->Init(vmo, offset, length, 0), ZX_ERR_INVALID_ARGS); |
| |
| EXPECT_EQ(request->Init(vmo, zx_system_get_page_size(), zx_system_get_page_size(), 0), |
| ZX_ERR_INVALID_ARGS); |
| free(request->take()); |
| } |
| |
| TEST(UsbRequestTest, AllocVmo) { |
| zx::vmo vmo; |
| ASSERT_OK(zx::vmo::create(zx_system_get_page_size(), 0, &vmo)); |
| std::optional<Request> request; |
| EXPECT_OK(Request::AllocVmo(&request, vmo, 0, 0, 0, kParentReqSize)); |
| } |
| |
| TEST(UsbRequestTest, Copy) { |
| std::optional<Request> request; |
| EXPECT_EQ(Request::Alloc(&request, zx_system_get_page_size(), 0, kParentReqSize), ZX_OK); |
| |
| constexpr uint8_t kSampleData[] = "blahblahblah"; |
| EXPECT_EQ(request->CopyTo(kSampleData, sizeof(kSampleData), 10), sizeof(kSampleData)); |
| uint8_t data[sizeof(kSampleData)] = {}; |
| EXPECT_EQ(request->CopyFrom(data, sizeof(data), 10), sizeof(data)); |
| EXPECT_EQ(memcmp(data, kSampleData, sizeof(kSampleData)), 0); |
| } |
| |
| TEST(UsbRequestTest, Mmap) { |
| std::optional<Request> request; |
| EXPECT_EQ(Request::Alloc(&request, zx_system_get_page_size(), 0, kParentReqSize), ZX_OK); |
| |
| constexpr uint8_t kSampleData[] = "blahblahblah"; |
| EXPECT_EQ(request->CopyTo(kSampleData, sizeof(kSampleData), 10), sizeof(kSampleData)); |
| void* data = nullptr; |
| EXPECT_EQ(request->Mmap(&data), ZX_OK); |
| ASSERT_NE(data, nullptr); |
| EXPECT_EQ(memcmp(&static_cast<uint8_t*>(data)[10], kSampleData, sizeof(kSampleData)), 0); |
| } |
| |
| TEST(UsbRequestTest, CacheFlush) { |
| std::optional<Request> request; |
| EXPECT_EQ(Request::Alloc(&request, zx_system_get_page_size(), 0, kParentReqSize), ZX_OK); |
| |
| EXPECT_EQ(request->CacheFlush(0, 0), ZX_OK); |
| EXPECT_EQ(request->CacheFlush(10, 10), ZX_OK); |
| EXPECT_EQ(request->CacheFlush(0, zx_system_get_page_size() + 1), ZX_ERR_OUT_OF_RANGE); |
| EXPECT_EQ(request->CacheFlush(zx_system_get_page_size() + 1, 0), ZX_ERR_OUT_OF_RANGE); |
| } |
| |
| TEST(UsbRequestTest, CacheInvalidateFlush) { |
| std::optional<Request> request; |
| EXPECT_EQ(Request::Alloc(&request, zx_system_get_page_size(), 0, kParentReqSize), ZX_OK); |
| |
| EXPECT_EQ(request->CacheFlushInvalidate(0, 0), ZX_OK); |
| EXPECT_EQ(request->CacheFlushInvalidate(10, 10), ZX_OK); |
| EXPECT_EQ(request->CacheFlushInvalidate(0, zx_system_get_page_size() + 1), ZX_ERR_OUT_OF_RANGE); |
| EXPECT_EQ(request->CacheFlushInvalidate(zx_system_get_page_size() + 1, 0), ZX_ERR_OUT_OF_RANGE); |
| } |
| |
| TEST(UsbRequestTest, PhysMap) { |
| zx::bti bti; |
| ASSERT_EQ(fake_bti_create(bti.reset_and_get_address()), ZX_OK, ""); |
| |
| std::optional<Request> request; |
| ASSERT_EQ(Request::Alloc(&request, zx_system_get_page_size() * 4, 1, kParentReqSize), ZX_OK); |
| |
| ASSERT_EQ(request->PhysMap(bti), ZX_OK); |
| ASSERT_EQ(request->request()->phys_count, 4u); |
| } |
| |
| TEST(UsbRequestTest, PhysIter) { |
| zx::bti bti; |
| ASSERT_EQ(fake_bti_create(bti.reset_and_get_address()), ZX_OK, ""); |
| |
| std::optional<Request> request; |
| ASSERT_EQ(Request::Alloc(&request, zx_system_get_page_size() * 4, 1, kParentReqSize), ZX_OK); |
| |
| ASSERT_EQ(request->PhysMap(bti), ZX_OK); |
| auto* req = request->take(); |
| for (size_t i = 0; i < req->phys_count; i++) { |
| req->phys_list[i] = zx_system_get_page_size() * i; |
| } |
| request = usb::Request(req, kParentReqSize); |
| |
| size_t count = 0; |
| for (auto [paddr, size] : request->phys_iter(zx_system_get_page_size())) { |
| EXPECT_EQ(paddr, zx_system_get_page_size() * count); |
| EXPECT_EQ(size, zx_system_get_page_size()); |
| ++count; |
| } |
| EXPECT_EQ(count, 4); |
| } |
| |
| TEST(UsbRequestTest, SetScatterGatherList) { |
| std::optional<Request> request; |
| ASSERT_EQ(Request::Alloc(&request, zx_system_get_page_size() * 3, 1, kParentReqSize), ZX_OK); |
| // Wrap around the end of the request. |
| const sg_entry_t kWrapped[] = {{.length = 10, .offset = (3 * zx_system_get_page_size()) - 10}, |
| {.length = 50, .offset = 0}}; |
| EXPECT_EQ(request->SetScatterGatherList(kWrapped, std::size(kWrapped)), ZX_OK); |
| EXPECT_EQ(request->request()->header.length, 60u); |
| |
| const sg_entry_t kUnordered[] = {{.length = 100, .offset = 2 * zx_system_get_page_size()}, |
| {.length = 50, .offset = 500}, |
| {.length = 10, .offset = 2000}}; |
| EXPECT_EQ(request->SetScatterGatherList(kUnordered, std::size(kUnordered)), ZX_OK); |
| EXPECT_EQ(request->request()->header.length, 160u); |
| } |
| |
| TEST(UsbRequestTest, InvalidScatterGatherList) { |
| zx::vmo vmo; |
| ASSERT_EQ(zx::vmo::create(zx_system_get_page_size() * 3, 0, &vmo), ZX_OK); |
| std::optional<Request> request; |
| ASSERT_EQ(Request::AllocVmo(&request, vmo, zx_system_get_page_size(), |
| zx_system_get_page_size() * 3, 0, kParentReqSize), |
| ZX_OK); |
| |
| const sg_entry_t kOutOfBounds[] = { |
| {.length = 10, .offset = zx_system_get_page_size() * 3}, |
| }; |
| EXPECT_NE(request->SetScatterGatherList(kOutOfBounds, std::size(kOutOfBounds)), ZX_OK, |
| "entry ends past end of vmo"); |
| |
| constexpr sg_entry_t kEmpty[] = { |
| {.length = 0, .offset = 0}, |
| }; |
| EXPECT_NE(request->SetScatterGatherList(kEmpty, std::size(kEmpty)), ZX_OK, "empty entry"); |
| } |
| |
| TEST(UsbRequestTest, ScatterGatherPhysIter) { |
| zx::bti bti; |
| ASSERT_EQ(fake_bti_create(bti.reset_and_get_address()), ZX_OK, ""); |
| |
| std::optional<Request> request; |
| ASSERT_EQ(Request::Alloc(&request, zx_system_get_page_size() * 4, 1, kParentReqSize), ZX_OK); |
| |
| ASSERT_EQ(request->PhysMap(bti), ZX_OK); |
| |
| const sg_entry_t kUnordered[] = {{.length = 100, .offset = 2 * zx_system_get_page_size()}, |
| {.length = 50, .offset = 500}, |
| {.length = 10, .offset = 2000}}; |
| EXPECT_EQ(request->SetScatterGatherList(kUnordered, std::size(kUnordered)), ZX_OK); |
| |
| auto* req = request->take(); |
| for (size_t i = 0; i < req->phys_count; i++) { |
| req->phys_list[i] = zx_system_get_page_size() * (i * 2 + 1); |
| } |
| request = usb::Request(req, kParentReqSize); |
| |
| auto phys_iter = request->phys_iter(zx_system_get_page_size()); |
| auto iter = phys_iter.begin(); |
| const auto end = phys_iter.end(); |
| |
| { |
| EXPECT_TRUE(iter != end); |
| auto [paddr, size] = *iter; |
| EXPECT_EQ(paddr, 5 * zx_system_get_page_size()); |
| EXPECT_EQ(size, 100); |
| } |
| |
| { |
| ++iter; |
| EXPECT_TRUE(iter != end); |
| auto [paddr, size] = *iter; |
| EXPECT_EQ(paddr, zx_system_get_page_size() + 500); |
| EXPECT_EQ(size, 50); |
| } |
| |
| { |
| ++iter; |
| EXPECT_TRUE(iter != end); |
| auto [paddr, size] = *iter; |
| EXPECT_EQ(paddr, zx_system_get_page_size() + 2000); |
| EXPECT_EQ(size, 10); |
| } |
| |
| ++iter; |
| EXPECT_TRUE(iter == end); |
| } |
| |
| TEST(UsbRequestTest, MultipleSection) { |
| constexpr size_t kBaseReqSize = sizeof(usb_request_t); |
| constexpr size_t kFirstLayerReqSize = Request::RequestSize(kBaseReqSize); |
| constexpr size_t kSecondLayerReqSize = |
| usb::BorrowedRequest<void>::RequestSize(kFirstLayerReqSize); |
| |
| std::optional<Request> request; |
| ASSERT_EQ(Request::Alloc(&request, 0, 0, kSecondLayerReqSize), ZX_OK); |
| |
| usb::BorrowedRequest request2(request->take(), kNoCallback, kFirstLayerReqSize); |
| usb::BorrowedRequest request3(request2.take(), kNoCallback, kBaseReqSize); |
| request = usb::Request(request3.take(), kSecondLayerReqSize); |
| } |
| |
| TEST(UsbRequestTest, PrivateStorage) { |
| constexpr size_t kRequestSize = usb::Request<uint32_t>::RequestSize(kParentReqSize); |
| std::optional<usb::Request<uint32_t>> request; |
| EXPECT_EQ(usb::Request<uint32_t>::Alloc(&request, 0, 0, kRequestSize), ZX_OK); |
| *request->private_storage() = 1001; |
| ASSERT_EQ(*request->private_storage(), 1001); |
| } |
| |
| TEST(UsbRequestTest, Callback) { |
| constexpr size_t kBaseReqSize = sizeof(usb_request_t); |
| constexpr size_t kFirstLayerReqSize = Request::RequestSize(kBaseReqSize); |
| |
| bool called = false; |
| auto callback = [](void* ctx, usb_request_t* request) { |
| *static_cast<bool*>(ctx) = true; |
| // We take ownership. |
| Request unused(request, kBaseReqSize); |
| }; |
| usb_request_complete_callback_t complete_cb = { |
| .callback = callback, |
| .ctx = &called, |
| }; |
| |
| std::optional<Request> request; |
| ASSERT_EQ(Request::Alloc(&request, 0, 0, kFirstLayerReqSize), ZX_OK); |
| |
| usb::BorrowedRequest<void> request2(request->take(), complete_cb, kBaseReqSize); |
| request2.Complete(ZX_OK, 0); |
| EXPECT_TRUE(called); |
| } |
| |
| TEST(UsbRequestTest, CallbackRequest) { |
| usb_function_protocol_t fake_function = {}; |
| usb_function_protocol_ops_t fake_ops; |
| fake_ops.request_queue = [](void* ctx, usb_request_t* usb_request, |
| const usb_request_complete_callback_t* complete_cb) { |
| usb_request_complete(usb_request, ZX_OK, 0, complete_cb); |
| }; |
| fake_function.ops = &fake_ops; |
| using Request = usb::CallbackRequest<sizeof(std::max_align_t)>; |
| std::optional<Request> req; |
| int invoked = 0; |
| bool invoked_other = false; |
| ddk::UsbFunctionProtocolClient client(&fake_function); |
| ASSERT_EQ(Request::Alloc(&req, 0, 0, sizeof(usb_request_t), |
| [&](Request request) { |
| invoked++; |
| if (invoked == 5) { |
| Request::Queue(std::move(request), client, |
| [&](Request request) { invoked_other = true; }); |
| } else { |
| Request::Queue(std::move(request), client); |
| } |
| }), |
| ZX_OK); |
| |
| Request::Queue(std::move(*req), client); |
| ASSERT_EQ(5, invoked); |
| ASSERT_TRUE(invoked_other); |
| } |
| |
| TEST(UsbRequestTest, TraceRecord) { |
| std::vector<std::string> categories{"USB Request"}; |
| usb_request_t dummy_request{}; |
| BEGIN_TRACE_TEST_WITH_CATEGORIES(categories); |
| fixture_initialize_and_start_tracing(); |
| |
| usb_request_trace_flow(&dummy_request); |
| usb_request_complete_base(&dummy_request, ZX_OK, 0, 0, nullptr); |
| fixture_stop_and_terminate_tracing(); |
| |
| fbl::Vector<trace::Record> records; |
| EXPECT_TRUE(fixture_read_records(&records)); |
| int events = 0; |
| std::array<std::string, 4> names{"USB Trace", "Trace Begin", "USB Trace", "Trace End"}; |
| for (auto& rec : records) { |
| if (rec.type() == trace::RecordType::kEvent) { |
| EXPECT_STREQ(rec.GetEvent().category.c_str(), "USB Request"); |
| EXPECT_STREQ(rec.GetEvent().name.c_str(), names[events].c_str()); |
| ++events; |
| } |
| } |
| EXPECT_EQ(events, 4); |
| } |
| |
| } // namespace |