blob: 34f29ae9df2c5d7157a8ca819a15c73e524e5d2e [file] [log] [blame]
// 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.
#pragma once
#include "usb-request.h"
#include <optional>
#include <ddk/debug.h>
#include <ddk/phys-iter.h>
#include <fbl/algorithm.h>
#include <fbl/auto_lock.h>
#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>
#include <zircon/compiler.h>
namespace usb {
// Usage notes:
//
// usb::Request is a c++ wrapper around the usb_request_t object. It provides
// 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
// 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;
//
// const size_t op_size = usb::Request<>::RequestSize(parent_req_size);
// for (int i = 0; i < kNumRequest; i++) {
// std::optional<usb::Request> request;
// request = usb::Request::Alloc(op_size, parent_req_size);
//
// if (!request) return ZX_ERR_NO_MEMORY;
// pool.add(std::move(*request));
// }
//
///////////////////////////////////////////////////////////////////////////////
// Example: Enqueue incoming requests into a usb::RequestQueue:
//
// class Driver {
// public:
// <...>
// private:
// usb::UnownedRequestQueue<> request_;
// const size_t 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;
// <...>
// }
//
class RequestBase {
public:
// 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);
}
// 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);
}
// 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);
}
// 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);
}
// 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);
}
// 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);
}
// 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);
}
// 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());
}
// Initializes a ddk::PhysIter for a usb request.
// |max_length| is the maximum length of a range returned the iterator.
// |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};
return ddk::PhysIter(buf, max_length);
}
size_t alloc_size() const {
return request()->alloc_size;
}
virtual usb_request_t* request() const = 0;
};
struct OperationTraits {
using OperationType = usb_request_t;
static OperationType* Alloc(size_t op_size) {
ZX_ASSERT(false);
return nullptr;
}
static void Free(OperationType* op) {
usb_request_release(op);
}
};
template <typename Storage = void>
class Request : public operation::Operation<Request<Storage>, OperationTraits, Storage>,
public RequestBase {
public:
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,
uint8_t ep_address, size_t req_size,
size_t parent_req_size = sizeof(usb_request_t)) {
usb_request_t* request;
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()) NodeType((*out)->node_offset_);
} else {
*out = std::nullopt;
}
return status;
}
// Creates a new usb request with the given VMO.
static zx_status_t AllocVmo(std::optional<Request>* out, const zx::vmo& vmo,
uint64_t vmo_offset, uint64_t length, uint8_t ep_address,
size_t req_size, size_t parent_req_size = sizeof(usb_request_t)) {
usb_request_t* request;
zx_status_t status = usb_request_alloc_vmo(&request, vmo.get(), vmo_offset, length,
ep_address, req_size);
if (status == ZX_OK) {
*out = Request(request, parent_req_size);
new ((*out)->node()) NodeType((*out)->node_offset_);
} else {
*out = std::nullopt;
}
return status;
}
Request(usb_request_t* request, size_t parent_req_size)
: BaseClass(request, parent_req_size) {}
Request(Request&& other)
: BaseClass(std::move(other)) {}
Request& operator=(Request&& other) {
BaseClass::operator=(std::move(other));
return *this;
}
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);
}
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);
}
};
// Similar to usb::Request, but it doesn't call usb_request_release on delete.
// This should be used to wrap usb_request_t* objects allocated in other
// drivers.
template <typename Storage = void>
class UnownedRequest : public operation::UnownedOperation<UnownedRequest<Storage>, OperationTraits,
CallbackTraits, Storage>,
public RequestBase {
public:
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, 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(std::move(other)) {}
UnownedRequest& operator=(UnownedRequest&& other) {
BaseClass::operator=(std::move(other));
return *this;
}
virtual ~UnownedRequest() = default;
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();
}
};
// A driver may use usb::RequestPool for recycling their own usb requests.
template <typename Storage = void>
class RequestPool : operation::OperationPool<Request<Storage>, OperationTraits, Storage> {
public:
using BaseClass = operation::OperationPool<Request<Storage>, OperationTraits, Storage>;
// Inherit constructors.
using BaseClass::BaseClass;
void Add(Request<Storage> req) {
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(&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->operation();
}
return std::nullopt;
}
using BaseClass::Release;
};
template <typename Storage = void>
using UnownedRequestQueue = operation::UnownedOperationQueue<UnownedRequest<Storage>,
OperationTraits,
CallbackTraits, Storage>;
template <typename Storage = void>
using RequestQueue = operation::OperationQueue<Request<Storage>, OperationTraits, Storage>;
} // namespace usb