[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 \