[usb-request] Add c++ list wrapper.
TEST= runtests -t usb-unittest
Change-Id: Iced2817e04aafc5cd2ee5ee53452245c20945449
diff --git a/zircon/system/dev/lib/operation/include/lib/operation/operation.h b/zircon/system/dev/lib/operation/include/lib/operation/operation.h
index 378d74e..97088bf 100644
--- a/zircon/system/dev/lib/operation/include/lib/operation/operation.h
+++ b/zircon/system/dev/lib/operation/include/lib/operation/operation.h
@@ -550,6 +550,8 @@
BaseList& operator=(BaseList&& other) {
list_.clear();
list_.swap(other.list_);
+ size_ = other.size_;
+ other.size_ = 0;
return *this;
}
diff --git a/zircon/system/dev/lib/usb/include/usb/request-cpp.h b/zircon/system/dev/lib/usb/include/usb/request-cpp.h
index 92bb61b..6a69981 100644
--- a/zircon/system/dev/lib/usb/include/usb/request-cpp.h
+++ b/zircon/system/dev/lib/usb/include/usb/request-cpp.h
@@ -39,6 +39,12 @@
// usb::RequestQueue provides a queue interface for tracking usb::Request and
// usb::UnownedRequest objects.
//
+// usb::RequestList provides a list interface for tracking usb::Request and
+// usb::UnownedRequest objects.
+//
+// A Request or UnownedRequest cannot be stored simultaneously in both a
+// usb::RequestQueue and usb::RequestList in the same driver layer.
+//
// Available methods for both Request and UnownedRequest include:
//
// usb_request_t* request(); // accessor for inner type.
@@ -91,6 +97,24 @@
// }
//
///////////////////////////////////////////////////////////////////////////////
+// Example: Add incoming requests into a usb::RequestList:
+//
+// class Driver {
+// public:
+// <...>
+// private:
+// usb::UnownedRequestList<> request_;
+// const size_t parent_req_size_;
+// };
+//
+// void Driver::UsbRequestQueue(usb_request_t* req, const usb_request_callback_t* completion_cb) {
+// auto opt_unowned = usb::UnownedRequest<>(op, cb, parent_req_size);
+// auto unowned = *std::move(opt_unowned);
+// request_.push_back(&unowned);
+// // Pass unowned_.take() to next layer.
+// }
+//
+///////////////////////////////////////////////////////////////////////////////
// Example: Using private context only visible to your driver:
//
// struct PrivateStorage {
@@ -100,8 +124,8 @@
//
// using UsbRequest = usb::UnownedRequest<PrivateStorage>;
//
-// void Driver::UsbReqeustQueue(usb_request_t* req, const usb_request_t* completion_cb) {
-// UsbReqeust usb_req(op, cb, parent_req_size_));
+// void Driver::UsbRequestQueue(usb_request_t* req, const usb_request_t* completion_cb) {
+// UsbRequest usb_req(op, cb, parent_req_size_));
// ZX_DEBUG_ASSERT(usb_req.request()->command == USB_ERASE);
// usb_req.private_storage()->valid = true;
// usb_req.private_storage()->count_metric += 1;
@@ -287,8 +311,9 @@
OperationTraits, CallbackTraits, Storage>;
UnownedRequest(usb_request_t* request, const usb_request_complete_t& complete_cb,
- size_t parent_req_size)
- : BaseClass(request, complete_cb.callback, complete_cb.ctx, parent_req_size) {}
+ size_t parent_req_size, bool allow_destruct = true)
+ : BaseClass(request, complete_cb.callback, complete_cb.ctx, parent_req_size,
+ allow_destruct) {}
UnownedRequest(usb_request_t* request, size_t parent_req_size, bool allow_destruct = true)
: BaseClass(request, parent_req_size, allow_destruct) {}
@@ -353,4 +378,12 @@
template <typename Storage = void>
using RequestQueue = operation::OperationQueue<Request<Storage>, OperationTraits, Storage>;
+template <typename Storage = void>
+using UnownedRequestList = operation::UnownedOperationList<UnownedRequest<Storage>,
+ OperationTraits,
+ CallbackTraits, Storage>;
+
+template <typename Storage = void>
+using RequestList = operation::OperationList<Request<Storage>, OperationTraits, Storage>;
+
} // namespace usb
diff --git a/zircon/system/dev/test/usb/rules.mk b/zircon/system/dev/test/usb/rules.mk
index 9ed8c06..47d3780 100644
--- a/zircon/system/dev/test/usb/rules.mk
+++ b/zircon/system/dev/test/usb/rules.mk
@@ -12,6 +12,7 @@
MODULE_SRCS += \
$(LOCAL_DIR)/main.cpp \
+ $(LOCAL_DIR)/usb-request-list-test.cpp \
$(LOCAL_DIR)/usb-request-pool-test.cpp \
$(LOCAL_DIR)/usb-request-queue-test.cpp \
$(LOCAL_DIR)/usb-request-test.cpp \
diff --git a/zircon/system/dev/test/usb/usb-request-list-test.cpp b/zircon/system/dev/test/usb/usb-request-list-test.cpp
new file mode 100644
index 0000000..64ac34c
--- /dev/null
+++ b/zircon/system/dev/test/usb/usb-request-list-test.cpp
@@ -0,0 +1,401 @@
+// 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 <unittest/unittest.h>
+
+namespace {
+
+using Request = usb::Request<void>;
+
+constexpr size_t kParentReqSize = sizeof(usb_request_t);
+constexpr usb_request_complete_t kNoCallback = {};
+
+bool TrivialLifetimeTest() {
+ BEGIN_TEST;
+ usb::RequestList<void> list;
+ usb::UnownedRequestList<void> unowned_list;
+ END_TEST;
+}
+
+bool SingleRequestTest() {
+ BEGIN_TEST;
+ 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());
+ END_TEST;
+}
+
+bool MultipleRequestTest() {
+ BEGIN_TEST;
+ 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.
+ __UNUSED auto req = Request(raw_reqs[i], kParentReqSize);
+ }
+ EXPECT_EQ(list.size(), 0u);
+ EXPECT_FALSE(list.begin().has_value());
+ END_TEST;
+}
+
+bool MoveTest() {
+ BEGIN_TEST;
+ 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.
+ __UNUSED auto req = Request(raw_reqs[count], kParentReqSize);
+
+ count++;
+ opt_request = std::move(next);
+ }
+ EXPECT_EQ(count, 10u);
+ EXPECT_TRUE(list2.begin() == std::nullopt);
+ END_TEST;
+}
+
+bool ReleaseTest() {
+ BEGIN_TEST;
+ 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.
+ __UNUSED auto req = Request(raw_reqs[i], kParentReqSize);
+ }
+
+ END_TEST;
+}
+
+bool MultipleLayerTest() {
+ BEGIN_TEST;
+
+ using FirstLayerReq = usb::UnownedRequest<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::UnownedRequestList<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);
+
+ END_TEST;
+}
+
+bool MultipleLayerWithStorageTest() {
+ BEGIN_TEST;
+
+ using FirstLayerReq = usb::UnownedRequest<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::UnownedRequestList<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.
+ __UNUSED auto req = SecondLayerReq(raw_reqs[i], kFirstLayerReqSize);
+ }
+
+ END_TEST;
+}
+
+bool MultipleLayerWithCallbackTest() {
+ BEGIN_TEST;
+
+ using FirstLayerReq = usb::UnownedRequest<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_t complete_cb = {
+ .callback = callback,
+ .ctx = &num_callbacks,
+ };
+
+ {
+ usb::UnownedRequestList<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.
+ __UNUSED auto req = SecondLayerReq(raw_reqs[i], kFirstLayerReqSize);
+ }
+
+ END_TEST;
+}
+} // namespace
+
+BEGIN_TEST_CASE(UsbRequestListTests)
+RUN_TEST_SMALL(TrivialLifetimeTest)
+RUN_TEST_SMALL(SingleRequestTest)
+RUN_TEST_SMALL(MultipleRequestTest)
+RUN_TEST_SMALL(MoveTest)
+RUN_TEST_SMALL(ReleaseTest)
+RUN_TEST_SMALL(MultipleLayerTest)
+RUN_TEST_SMALL(MultipleLayerWithStorageTest)
+RUN_TEST_SMALL(MultipleLayerWithCallbackTest)
+END_TEST_CASE(UsbRequestListTests);