[usb-request] Rebase on top of operation lib.

Tested: `runtests -t usb-unittest`
Change-Id: I29d86be31be4edfc77ce201cebed1da3882dabcf
diff --git a/system/dev/lib/usb/include/usb/request-cpp.h b/system/dev/lib/usb/include/usb/request-cpp.h
index 686415a..0c1b02a 100644
--- a/system/dev/lib/usb/include/usb/request-cpp.h
+++ b/system/dev/lib/usb/include/usb/request-cpp.h
@@ -15,6 +15,7 @@
 #include <fbl/intrusive_double_list.h>
 #include <fbl/mutex.h>
 #include <fbl/unique_ptr.h>
+#include <lib/operation/operation.h>
 #include <lib/zx/bti.h>
 #include <lib/zx/vmo.h>
 #include <zircon/assert.h>
@@ -25,31 +26,52 @@
 // Usage notes:
 //
 // usb::Request is a c++ wrapper around the usb_request_t object. It provides
-// capabilites to interact with a request buffer which is used to traverse the
-// usb stack. On deletion, it will automatically call |usb_request_release|.
-// Most of it's functionality is defined in usb::RequestBase.
+// capabilites to interact with a usb_req buffer which is used to traverse the
+// usb stack. On deletion, it will automatically free itself.
 //
-// usb::UnownedRequest provides an unowned variant of usb::Request. It adds a
-// wrapper around |usb_request_complete| which isn't present in usb::Request.
-// In addition, it will call the completion on destruction if it wasn't already
-// triggered.
+// usb::UnownedRequest provides an unowned variant of usb::Request. It adds
+// functionality to store and call a complete callback which isn't present in
+// usb::Request.  In addition, it will call the completion on destruction if it
+// wasn't already triggered.
 //
 // usb::RequestPool provides pooling functionality for usb::Request reuse.
 //
 // usb::RequestQueue provides a queue interface for tracking usb::Request and
 // usb::UnownedRequest objects.
 //
+// Available methods for both Request and UnownedRequest include:
+//
+//   usb_request_t* request(); // accessor for inner type.
+//
+//   // Takes ownership of inner type. Should only be used when transferring
+//   // ownership to another driver.
+//   usb_request_t* take();
+//
+//   All methods implemented in RequestBase (scroll below for additional info).
+//
+// Available to Request and UnownedRequest if they templatize of Storage:
+//
+//   Storage* private_storage(); // accessor for private storage.
+//
+// Available to Request:
+//
+//   void Release(); // Frees the inner type.
+//
+// Available to UnownedRequest:
+//
+//   void Complete(zx_status_t); // Completes the Request.
+//
 ///////////////////////////////////////////////////////////////////////////////
 // Example: Basic allocation with a pool:
 //
-// usb::RequestPool pool;
+// usb::RequestPool<> pool;
 //
+// const size_t op_size = usb::Request<>::RequestSize(parent_req_size);
 // for (int i = 0; i < kNumRequest; i++) {
 //     std::optional<usb::Request> request;
-//     status = usb::Request::Alloc(&request, data_size, ep_address, req_size,
-//                                  parent_req_size);
+//     request = usb::Request::Alloc(op_size, parent_req_size);
 //
-//     if (status != ZX_OK) return status;
+//     if (!request) return ZX_ERR_NO_MEMORY;
 //     pool.add(std::move(*request));
 // }
 //
@@ -60,100 +82,79 @@
 // public:
 //     <...>
 // private:
-//     usb::UnownedRequestQueue<void> requests_;
+//     usb::UnownedRequestQueue<> request_;
 //     const size_t parent_req_size_;
 // };
 //
-// void Driver::UsbRequestQueue(usb_request_t* req,
-//                              const usb_request_queue_callback* cb) {
-//     requests_.push(usb::UnownedRequest(request, cb, parent_req_size_));
+// void Driver::UsbRequestQueue(usb_request_t* req, const usb_request_callback_t* completion_cb) {
+//     request_.push(usb::UnownedRequest<>(op, cb, parent_req_size_));
+// }
+//
+///////////////////////////////////////////////////////////////////////////////
+// Example: Using private context only visible to your driver:
+//
+// struct PrivateStorage {
+//     bool valid;
+//     size_t count_metric;
+// }
+//
+// 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_));
+//     ZX_DEBUG_ASSERT(usb_req.request()->command == USB_ERASE);
+//     usb_req.private_storage()->valid = true;
+//     usb_req.private_storage()->count_metric += 1;
+//     <...>
 // }
 //
 
-template <typename T, typename Storage = void>
-class RequestNode;
-
-template <typename D, typename Storage>
 class RequestBase {
 public:
-    RequestBase(usb_request_t* request, size_t parent_req_size)
-        : request_(request),
-          node_offset_(fbl::round_up(parent_req_size, kAlignment)) {
-        ZX_DEBUG_ASSERT(request != nullptr);
-    }
-
-    RequestBase(RequestBase&& other)
-        : request_(other.request_), node_offset_(other.node_offset_) {
-        other.request_ = nullptr;
-    }
-
-    RequestBase& operator=(RequestBase&& other) {
-        request_ = other.request_;
-        node_offset_ = other.node_offset_;
-        other.request_ = nullptr;
-        return *this;
-    }
-
-    usb_request_t* take() __WARN_UNUSED_RESULT {
-        auto* tmp = request_;
-        request_ = nullptr;
-        return tmp;
-    }
-
-    const usb_request_t* request() {
-        return request_;
-    }
-
-    // Initializes the statically allocated usb request with the given VMO.
-    // This will free any resources allocated by the usb request but not the usb request itself.
-    zx_status_t Init(const zx::vmo& vmo, uint64_t vmo_offset, uint64_t length, uint8_t ep_address) {
-        return usb_request_init(request_, vmo.get(), vmo_offset, length, ep_address);
-    }
-
     // Copies the scatter gather list to the request.
     // Future transfers using this request will determine where in the VMO to store read/write data.
     // using the scatter gather list.
     // This will free any existing scatter gather list stored in the request.
     zx_status_t SetScatterGatherList(const phys_iter_sg_entry_t* sg_list, size_t sg_count) {
-        return usb_request_set_sg_list(request_, sg_list, sg_count);
+        return usb_request_set_sg_list(request(), sg_list, sg_count);
     }
 
     // Copies data from the Request's vm object.
     // Out of range operations are ignored.
     ssize_t CopyFrom(void* data, size_t length, size_t offset) {
-        return usb_request_copy_from(request_, data, length, offset);
+        return usb_request_copy_from(request(), data, length, offset);
     }
 
     // Copies data into a Request's vm object.
     // Out of range operations are ignored.
     ssize_t CopyTo(const void* data, size_t length, size_t offset) {
-        return usb_request_copy_to(request_, data, length, offset);
+        return usb_request_copy_to(request(), data, length, offset);
     }
 
     // Maps the Request's vm object. The 'data' field is set with the mapped address if this
     // function succeeds.
     zx_status_t Mmap(void** data) {
-        return usb_request_mmap(request_, data);
+        return usb_request_mmap(request(), data);
     }
 
     // Performs a cache maintenance op against the request's internal buffer.
     zx_status_t CacheOp(uint32_t op, size_t offset, size_t length) {
-        return usb_request_cacheop(request_, op, offset, length);
+        return usb_request_cacheop(request(), op, offset, length);
     }
 
     // Performs a cache flush on a range of memory in the request's buffer.
     zx_status_t CacheFlush(zx_off_t offset, size_t length) {
-        return usb_request_cache_flush(request_, offset, length);
+        return usb_request_cache_flush(request(), offset, length);
     }
 
     // Performs a cache flush and invalidate on a range of memory in the request's buffer.
     zx_status_t CacheFlushInvalidate(zx_off_t offset, size_t length) {
-        return usb_request_cache_flush_invalidate(request_, offset, length);
+        return usb_request_cache_flush_invalidate(request(), offset, length);
     }
 
     // Looks up the physical pages backing this request's vm object.
     zx_status_t PhysMap(const zx::bti& bti) {
-        return usb_request_physmap(request_, bti.get());
+        return usb_request_physmap(request(), bti.get());
     }
 
     // Initializes a ddk::PhysIter for a usb request.
@@ -161,53 +162,41 @@
     // |max_length| must be either a positive multiple of PAGE_SIZE, or zero for no limit.
     ddk::PhysIter phys_iter(size_t max_length) {
         phys_iter_buffer_t buf = {
-            .phys = request_->phys_list,
-            .phys_count = request_->phys_count,
-            .length = request_->header.length,
-            .vmo_offset = request_->offset,
-            .sg_list = request_->sg_list,
-            .sg_count = request_->sg_count};
+            .phys = request()->phys_list,
+            .phys_count = request()->phys_count,
+            .length = request()->header.length,
+            .vmo_offset = request()->offset,
+            .sg_list = request()->sg_list,
+            .sg_count = request()->sg_count};
         return ddk::PhysIter(buf, max_length);
     }
 
-    static constexpr size_t RequestSize(size_t parent_req_size) {
-        return fbl::round_up(parent_req_size, kAlignment) +
-               fbl::round_up(sizeof(RequestNode<D, Storage>), kAlignment);
-    }
-
-    size_t size() const {
-        return node_offset_ + fbl::round_up(sizeof(RequestNode<D, Storage>), kAlignment);
-    }
-
     size_t alloc_size() const {
-        return request_->alloc_size;
+        return request()->alloc_size;
     }
 
-    // Returns private node stored inline
-    RequestNode<D, Storage>* node() {
-        auto* node = reinterpret_cast<RequestNode<D, Storage>*>(
-            reinterpret_cast<uintptr_t>(request_) + node_offset_);
-        return node;
+    virtual usb_request_t* request() const;
+};
+
+struct OperationTraits {
+    using OperationType = usb_request_t;
+
+    static OperationType* Alloc(size_t op_size) {
+        ZX_ASSERT(false);
+        return nullptr;
     }
 
-    Storage* private_storage() {
-        static_assert(!std::is_same<Storage, void>::value,
-                      "private_storage not available on void type.");
-        return node()->private_storage();
+    static void Free(OperationType* op) {
+        usb_request_release(op);
     }
-
-protected:
-    usb_request_t* request_;
-    zx_off_t node_offset_;
-
-private:
-    static constexpr size_t kAlignment = 8;
 };
 
 template <typename Storage = void>
-class Request : public RequestBase<Request<Storage>, Storage> {
+class Request : public operation::Operation<Request<Storage>, OperationTraits, Storage>,
+                public RequestBase {
 public:
-    using BaseClass = RequestBase<Request<Storage>, Storage>;
+    using BaseClass = operation::Operation<Request<Storage>, OperationTraits, Storage>;
+    using NodeType = operation::OperationNode<Request<Storage>, OperationTraits, void, Storage>;
 
     // Creates a new usb request with payload space of data_size.
     static zx_status_t Alloc(std::optional<Request>* out, uint64_t data_size,
@@ -217,7 +206,7 @@
         zx_status_t status = usb_request_alloc(&request, data_size, ep_address, req_size);
         if (status == ZX_OK) {
             *out = Request(request, parent_req_size);
-            new ((*out)->node()) RequestNode<Request<Storage>, Storage>((*out)->node_offset_);
+            new ((*out)->node()) NodeType((*out)->node_offset_);
         } else {
             *out = std::nullopt;
         }
@@ -233,7 +222,7 @@
                                                    ep_address, req_size);
         if (status == ZX_OK) {
             *out = Request(request, parent_req_size);
-            new ((*out)->node()) RequestNode<Request<Storage>, Storage>((*out)->node_offset_);
+            new ((*out)->node()) NodeType((*out)->node_offset_);
         } else {
             *out = std::nullopt;
         }
@@ -244,25 +233,44 @@
         : BaseClass(request, parent_req_size) {}
 
     Request(Request&& other)
-        : BaseClass(other.request_, other.node_offset_) {
-        other.request_ = nullptr;
-    }
+        : BaseClass(std::move(other)) {}
 
     Request& operator=(Request&& other) {
         BaseClass::operator=(std::move(other));
         return *this;
     }
 
-    ~Request() {
-        Release();
+    virtual ~Request() = default;
+
+    // Initializes the statically allocated usb request with the given VMO.
+    // This will free any resources allocated by the usb request but not the usb request itself.
+    zx_status_t Init(const zx::vmo& vmo, uint64_t vmo_offset, uint64_t length, uint8_t ep_address) {
+        return usb_request_init(BaseClass::operation_, vmo.get(), vmo_offset, length, ep_address);
     }
 
-    void Release() {
-        using NodeType = RequestNode<Request<Storage>, Storage>;
-        if (BaseClass::request_) {
-            BaseClass::node()->NodeType::~NodeType();
-            usb_request_release(BaseClass::take());
-        }
+    static constexpr size_t RequestSize(size_t parent_req_size) {
+        return BaseClass::OperationSize(parent_req_size);
+    }
+
+    usb_request_t* request() const override {
+        return BaseClass::operation();
+    }
+};
+
+struct CallbackTraits {
+    using CallbackType = void(void*, usb_request_t*);
+
+    static std::tuple<zx_status_t, uint32_t> AutoCompleteArgs() {
+        return std::make_tuple(ZX_ERR_INTERNAL, 0);
+    }
+
+    static void Callback(CallbackType* callback, void* cookie, usb_request_t* op,
+                         zx_status_t status, zx_off_t actual) {
+        usb_request_complete_t complete_cb = {
+            .callback = callback,
+            .ctx = cookie,
+        };
+        usb_request_complete(op, status, actual, &complete_cb);
     }
 };
 
@@ -270,287 +278,78 @@
 // This should be used to wrap usb_request_t* objects allocated in other
 // drivers.
 template <typename Storage = void>
-class UnownedRequest : public RequestBase<UnownedRequest<Storage>, Storage> {
+class UnownedRequest : public operation::UnownedOperation<UnownedRequest<Storage>, OperationTraits,
+                                                          CallbackTraits, Storage>,
+                       public RequestBase {
 public:
-    using BaseClass = RequestBase<UnownedRequest<Storage>, Storage>;
+    using BaseClass = operation::UnownedOperation<UnownedRequest<Storage>,
+                                                  OperationTraits, CallbackTraits, Storage>;
 
     UnownedRequest(usb_request_t* request, const usb_request_complete_t& complete_cb,
                    size_t parent_req_size)
-        : BaseClass(request, parent_req_size) {
-        new (BaseClass::node())
-            RequestNode<UnownedRequest<Storage>, Storage>(BaseClass::node_offset_, complete_cb);
-    }
+        : BaseClass(request, complete_cb.callback, complete_cb.ctx, parent_req_size) {}
+
+    UnownedRequest(usb_request_t* request, size_t parent_req_size)
+        : BaseClass(request, parent_req_size) {}
 
     UnownedRequest(UnownedRequest&& other)
-        : BaseClass(other.request_, other.node_offset_) {
-        other.request_ = nullptr;
-    }
+        : BaseClass(std::move(other)) {}
 
     UnownedRequest& operator=(UnownedRequest&& other) {
         BaseClass::operator=(std::move(other));
         return *this;
     }
 
-    ~UnownedRequest() {
-        // Complete should have been called.
-        if (BaseClass::request_ != nullptr)  {
-            zxlogf(WARN, "Auto-completing request.\n");
-        }
-        // Auto-complete if it wasn't.
-        Complete(ZX_ERR_INTERNAL, 0);
+    virtual ~UnownedRequest() = default;
+
+    static constexpr size_t RequestSize(size_t parent_req_size) {
+        return BaseClass::OperationSize(parent_req_size);
     }
 
-    // Must be called by the processor when the request has completed or failed.
-    // The request and any virtual or physical memory obtained from it is no
-    // longer valid after Complete is called.
-    void Complete(zx_status_t status, zx_off_t actual) {
-        using NodeType = RequestNode<UnownedRequest<Storage>, Storage>;
-        if (BaseClass::request_) {
-            auto complete_cb = *BaseClass::node()->complete_cb();
-            BaseClass::node()->NodeType::~NodeType();
-            usb_request_complete(BaseClass::take(), status, actual,
-                                 &complete_cb);
-        }
+    usb_request_t* request() const override {
+        return BaseClass::operation();
     }
-
-    friend class RequestNode<UnownedRequest<Storage>, Storage>;
-
-private:
-    // Special constructor to be used by RequestNode. Skips initializing the
-    // RequestNode.
-    UnownedRequest(usb_request_t* request, size_t parent_req_size)
-        : BaseClass(request, parent_req_size) {}
-};
-
-// Node storage for usb::Request and usb::UnownedRequest. Does not maintain
-// ownership of underlying usb_request_t*. Must be transformed back into
-// appopriate wrapper type to maintain correct ownership.
-// It is strongly recommended to use usb::RequestPool and usb::RequestQueue to
-// avoid ownership pitfalls.
-template <typename T, typename Storage>
-class RequestNode : public fbl::DoublyLinkedListable<RequestNode<T, Storage>*> {
-public:
-    explicit RequestNode(zx_off_t node_offset)
-        : node_offset_(node_offset) {}
-
-    ~RequestNode() = default;
-
-    T request() const {
-        return T(
-            reinterpret_cast<usb_request_t*>(reinterpret_cast<uintptr_t>(this) - node_offset_),
-            node_offset_);
-    }
-
-    Storage* private_storage() {
-        return &private_storage_;
-    }
-
-private:
-    const zx_off_t node_offset_;
-    Storage private_storage_;
-};
-
-// Specialized version for when complete_cb is required.
-template <typename Storage>
-class RequestNode<UnownedRequest<Storage>, Storage> : public fbl::DoublyLinkedListable<
-                                                          RequestNode<UnownedRequest<Storage>,
-                                                                      Storage>*> {
-public:
-    RequestNode(zx_off_t node_offset, const usb_request_complete_t& complete_cb)
-        : node_offset_(node_offset), complete_cb_(complete_cb) {}
-
-    ~RequestNode() = default;
-
-    UnownedRequest<Storage> request() const {
-        return UnownedRequest<Storage>(
-            reinterpret_cast<usb_request_t*>(reinterpret_cast<uintptr_t>(this) - node_offset_),
-            node_offset_);
-    }
-
-    const usb_request_complete_t* complete_cb() {
-        return &complete_cb_;
-    }
-
-    Storage* private_storage() {
-        return &private_storage_;
-    }
-
-private:
-    const zx_off_t node_offset_;
-    const usb_request_complete_t complete_cb_;
-    Storage private_storage_;
-};
-
-// Specialized version for when no additional storage is required.
-template <typename T>
-class RequestNode<T, void> : public fbl::DoublyLinkedListable<RequestNode<T>*> {
-public:
-    RequestNode(zx_off_t node_offset)
-        : node_offset_(node_offset) {}
-
-    ~RequestNode() = default;
-
-    T request() const {
-        return T(
-            reinterpret_cast<usb_request_t*>(reinterpret_cast<uintptr_t>(this) - node_offset_),
-            node_offset_);
-    }
-
-private:
-    const zx_off_t node_offset_;
-};
-
-// Specialized version for when no additional storage is required, but
-// complete_cb is required.
-template <>
-class RequestNode<UnownedRequest<void>, void> : public fbl::DoublyLinkedListable<
-                                                    RequestNode<UnownedRequest<void>>*> {
-public:
-    RequestNode(zx_off_t node_offset, const usb_request_complete_t& complete_cb)
-        : node_offset_(node_offset), complete_cb_(complete_cb) {}
-
-    ~RequestNode() = default;
-
-    UnownedRequest<void> request() const {
-        return UnownedRequest<void>(
-            reinterpret_cast<usb_request_t*>(reinterpret_cast<uintptr_t>(this) - node_offset_),
-            node_offset_);
-    }
-
-    const usb_request_complete_t* complete_cb() {
-        return &complete_cb_;
-    }
-
-private:
-    const zx_off_t node_offset_;
-    const usb_request_complete_t complete_cb_;
 };
 
 // A driver may use usb::RequestPool for recycling their own usb requests.
 template <typename Storage = void>
-class RequestPool {
+class RequestPool : operation::OperationPool<Request<Storage>, OperationTraits, Storage> {
 public:
-    using NodeType = RequestNode<Request<Storage>, Storage>;
+    using BaseClass = operation::OperationPool<Request<Storage>, OperationTraits, Storage>;
 
-    RequestPool() {}
+    // Inherit constructors.
+    using BaseClass::BaseClass;
 
-    ~RequestPool() {
-        Release();
-    }
-
-    // Adds the request to the pool.
     void Add(Request<Storage> req) {
-        fbl::AutoLock al(&lock_);
-        auto* node = req.node();
-        free_reqs_.push_front(node);
-        __UNUSED auto* dummy = req.take();
+        BaseClass::push(std::forward<Request<Storage>>(req));
     }
 
     // Returns a request from the pool that has a buffer of the given length,
     // or null if no such request exists.
     // The request is not re-initialized in any way and should be set accordingly by the user.
     std::optional<Request<Storage>> Get(size_t length) {
-        fbl::AutoLock al(&lock_);
-        auto node = free_reqs_.erase_if([length](const NodeType& node) {
-            auto request = node.request();
+        fbl::AutoLock al(&this->lock_);
+        auto node = this->queue_.erase_if([length](const auto& node) {
+            auto request = node.operation();
             const size_t size = request.alloc_size();
             __UNUSED auto* dummy = request.take(); // Don't free request.
             return size == length;
         });
         if (node) {
-            return node->request();
+            return node->operation();
         }
         return std::nullopt;
     }
 
-    // Releases all usb requests stored in the pool.
-    void Release() {
-        fbl::AutoLock al(&lock_);
-        while (!free_reqs_.is_empty()) {
-            __UNUSED auto req = free_reqs_.pop_front()->request();
-        }
-    }
-
-private:
-    fbl::Mutex lock_;
-    fbl::DoublyLinkedList<NodeType*> free_reqs_ __TA_GUARDED(lock_);
-};
-
-
-// Conveniance queue wrapper around fbl::DoublyLinkedList<T>.
-template <typename ReqType, typename Storage>
-class BaseQueue {
-public:
-    using NodeType = RequestNode<ReqType, Storage>;
-
-    DECLARE_HAS_MEMBER_FN_WITH_SIGNATURE(has_node, node, NodeType* (C::*)());
-    static_assert(has_node<ReqType>::value,
-                  "ReqType must implement RequestNode<ReqType, Storage>* node()");
-
-    BaseQueue() {}
-
-    ~BaseQueue() {
-        release();
-    }
-
-    BaseQueue(BaseQueue&& other) {
-        fbl::AutoLock al(&other.lock_);
-        queue_.swap(other.queue_);
-    }
-
-    BaseQueue& operator=(BaseQueue&& other) {
-        fbl::AutoLock al1(&lock_);
-        fbl::AutoLock al2(&other.lock_);
-        queue_.swap(other.queue_);
-        other.queue_.clear();
-        return *this;
-    }
-
-    void push(ReqType req) {
-        fbl::AutoLock al(&lock_);
-        auto* node = req.node();
-        queue_.push_front(node);
-        __UNUSED auto dummy = req.take();
-    }
-
-    void push_next(ReqType req) {
-        fbl::AutoLock al(&lock_);
-        auto* node = req.node();
-        queue_.push_back(node);
-        __UNUSED auto dummy = req.take();
-    }
-
-    std::optional<ReqType> pop() {
-        fbl::AutoLock al(&lock_);
-        auto* node = queue_.pop_back();
-        if (node) {
-            return std::move(node->request());
-        }
-        return std::nullopt;
-    }
-
-    bool is_empty() {
-        fbl::AutoLock al(&lock_);
-        return queue_.is_empty();
-    }
-
-    // Releases all usb requests stored in the queue.
-    void release() {
-        fbl::AutoLock al(&lock_);
-        while (!queue_.is_empty()) {
-            __UNUSED auto req = queue_.pop_back()->request();
-        }
-    }
-
-private:
-    fbl::Mutex lock_;
-    fbl::DoublyLinkedList<NodeType*> queue_ __TA_GUARDED(lock_);
+    using BaseClass::Release;
 };
 
 template <typename Storage = void>
-using UnownedRequestQueue = BaseQueue<UnownedRequest<Storage>, Storage>;
+using UnownedRequestQueue = operation::UnownedOperationQueue<UnownedRequest<Storage>,
+                                                             OperationTraits,
+                                                             CallbackTraits, Storage>;
 
 template <typename Storage = void>
-using RequestQueue = BaseQueue<Request<Storage>, Storage>;
+using RequestQueue = operation::OperationQueue<Request<Storage>, OperationTraits, Storage>;
 
 } // namespace usb
diff --git a/system/dev/test/usb/rules.mk b/system/dev/test/usb/rules.mk
index cb85e9b..9ed8c06 100644
--- a/system/dev/test/usb/rules.mk
+++ b/system/dev/test/usb/rules.mk
@@ -19,6 +19,7 @@
 MODULE_STATIC_LIBS := \
     system/dev/lib/fake_ddk \
     system/dev/lib/fake-bti \
+    system/dev/lib/operation \
     system/dev/lib/usb \
     system/ulib/ddk \
     system/ulib/fbl \
diff --git a/system/dev/test/usb/usb-request-queue-test.cpp b/system/dev/test/usb/usb-request-queue-test.cpp
index fd3f164..f058a4f 100644
--- a/system/dev/test/usb/usb-request-queue-test.cpp
+++ b/system/dev/test/usb/usb-request-queue-test.cpp
@@ -86,7 +86,7 @@
         queue.push(std::move(*request));
     }
 
-    queue.release();
+    queue.Release();
     EXPECT_TRUE(queue.pop() == std::nullopt);
     END_TEST;
 }
diff --git a/system/dev/usb/usb-virtual-bus/rules.mk b/system/dev/usb/usb-virtual-bus/rules.mk
index 5f96460..6467589 100644
--- a/system/dev/usb/usb-virtual-bus/rules.mk
+++ b/system/dev/usb/usb-virtual-bus/rules.mk
@@ -14,6 +14,7 @@
     $(LOCAL_DIR)/usb-virtual-host.cpp \
 
 MODULE_STATIC_LIBS := \
+    system/dev/lib/operation \
     system/dev/lib/usb \
     system/ulib/ddk \
     system/ulib/ddktl \