blob: 64446c280f4e89002f166604128fec4beb4804b7 [file] [log] [blame]
// Copyright 2022 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 "adb-function.h"
#include <lib/ddk/debug.h>
#include <zircon/assert.h>
#include <cstdint>
#include <optional>
#include <fbl/auto_lock.h>
#include <usb/peripheral.h>
#include <usb/request-cpp.h>
#include "src/developer/adb/drivers/usb-adb-function/usb_adb-bind.h"
namespace usb_adb_function {
namespace {
// CompleterType follows fidl::internal::WireCompleter<RequestType>::Async
template <typename CompleterType>
void CompleteTxn(CompleterType& completer, zx_status_t status) {
if (status == ZX_OK) {
completer.ReplySuccess();
} else {
completer.ReplyError(status);
}
}
} // namespace
void UsbAdbDevice::Start(StartRequestView request, StartCompleter::Sync& completer) {
zx_status_t status = ZX_OK;
bool enable_ep = false;
{
fbl::AutoLock _(&adb_mutex_);
if (adb_binding_.has_value()) {
status = ZX_ERR_ALREADY_BOUND;
} else {
adb_binding_ = fidl::BindServer<fuchsia_hardware_adb::UsbAdbImpl>(
dispatcher_, std::move(request->interface), this,
[this](auto* server, fidl::UnbindInfo info, auto server_end) {
zxlogf(INFO, "Device closed with reason '%s'", info.FormatDescription().c_str());
Stop();
});
enable_ep = Online();
fbl::AutoLock _(&lock_);
auto result = fidl::WireSendEvent(adb_binding_.value())->OnStatusChanged(status_);
if (!result.ok()) {
zxlogf(ERROR, "Could not call AdbInterface Status %s", result.error().status_string());
status = ZX_ERR_IO;
}
}
}
// Configure endpoints as adb binding is set now.
status = ConfigureEndpoints(enable_ep);
CompleteTxn(completer, status);
}
void UsbAdbDevice::Stop() {
{
fbl::AutoLock _(&adb_mutex_);
adb_binding_.reset();
}
// Disable endpoints.
ConfigureEndpoints(false);
}
zx_status_t UsbAdbDevice::SendLocked(const fidl::VectorView<uint8_t>& buf) {
if (!Online()) {
return ZX_ERR_BAD_STATE;
}
std::optional<usb::Request<>> tx_request = bulk_in_reqs_.Get(usb_request_size_);
if (!tx_request) {
return ZX_ERR_SHOULD_WAIT;
}
// Send data
tx_request->request()->header.length = buf.count();
ssize_t bytes_copied = tx_request->CopyTo(buf.data(), buf.count(), 0);
if (bytes_copied < 0) {
zxlogf(ERROR, "Failed to copy data into send req (error %zd).", bytes_copied);
bulk_in_reqs_.Add(std::move(tx_request.value()));
return ZX_ERR_INTERNAL;
}
{
fbl::AutoLock _(&lock_);
pending_requests_++;
}
function_.RequestQueue(tx_request->take(), &tx_complete_);
return ZX_OK;
}
void UsbAdbDevice::QueueTx(QueueTxRequestView request, QueueTxCompleter::Sync& completer) {
size_t length = request->data.count();
zx_status_t status;
if (!Online() || length == 0) {
zxlogf(INFO, "Invalid state - Online %d Length %zu", Online(), length);
completer.ReplyError(ZX_ERR_INVALID_ARGS);
return;
}
fbl::AutoLock _(&tx_mutex_);
status = SendLocked(request->data);
if (status == ZX_ERR_SHOULD_WAIT) {
// No buffers available, queue it up
tx_pending_infos_.emplace(txn_info_t{.buf = request->data, .completer = completer.ToAsync()});
} else {
CompleteTxn(completer, status);
}
}
void UsbAdbDevice::Receive(ReceiveCompleter::Sync& completer) {
fbl::AutoLock _(&rx_mutex_);
// Return early during shutdown.
if (!Online()) {
completer.ReplyError(ZX_ERR_BAD_STATE);
return;
}
std::optional<usb::Request<>> pending_request = bulk_out_reqs_.Get(usb_request_size_);
if (pending_request.has_value()) {
void* data = NULL;
auto status = pending_request->Mmap(&data);
if (status == ZX_OK) {
auto buffer = fidl::VectorView<uint8_t>::FromExternal(
reinterpret_cast<uint8_t*>(data), pending_request->request()->response.actual);
completer.ReplySuccess(buffer);
} else {
zxlogf(ERROR, "Failed to Mmap pending request - %d.", status);
completer.ReplyError(status);
}
{
fbl::AutoLock _(&lock_);
pending_requests_++;
}
function_.RequestQueue(pending_request->take(), &rx_complete_);
} else {
fbl::AutoLock _(&adb_mutex_);
rx_requests_.emplace(completer.ToAsync());
}
}
zx_status_t UsbAdbDevice::InsertUsbRequest(usb::Request<> request, usb::RequestPool<>& pool) {
{
fbl::AutoLock _(&lock_);
pending_requests_--;
ZX_ASSERT(pending_requests_ >= 0);
// Return without adding the request to the pool during shutdown.
if (shutting_down_) {
request.Release();
if (pending_requests_ == 0) {
ShutdownComplete();
}
return ZX_ERR_CANCELED;
}
}
pool.Add(std::move(request));
return ZX_OK;
}
void UsbAdbDevice::RxComplete(usb_request_t* req) {
usb::Request<> request(req, parent_request_size_);
// Return early during shutdown.
{
fbl::AutoLock _(&lock_);
if (shutting_down_) {
request.Release();
pending_requests_--;
ZX_ASSERT(pending_requests_ >= 0);
if (pending_requests_ == 0) {
ShutdownComplete();
}
return;
}
}
fbl::AutoLock _(&rx_mutex_);
if (req->response.status == ZX_ERR_IO_NOT_PRESENT) {
InsertUsbRequest(std::move(request), bulk_out_reqs_);
return;
}
if (req->response.status != ZX_OK) {
zxlogf(ERROR, "RxComplete called with error %d.", req->response.status);
function_.RequestQueue(request.take(), &rx_complete_);
return;
}
if (req->response.status == ZX_OK) {
fbl::AutoLock _(&adb_mutex_);
if (!rx_requests_.empty()) {
void* data = NULL;
auto status = request.Mmap(&data);
if (status == ZX_OK) {
auto buffer = fidl::VectorView<uint8_t>::FromExternal(reinterpret_cast<uint8_t*>(data),
req->response.actual);
rx_requests_.front().ReplySuccess(buffer);
} else {
zxlogf(ERROR, "Failed to Mmap request - %d.", status);
rx_requests_.front().ReplyError(status);
}
rx_requests_.pop();
function_.RequestQueue(request.take(), &rx_complete_);
} else {
InsertUsbRequest(std::move(request), bulk_out_reqs_);
}
}
}
void UsbAdbDevice::TxComplete(usb_request_t* req) {
usb::Request<> request(req, parent_request_size_);
std::optional<fidl::internal::WireCompleter<::fuchsia_hardware_adb::UsbAdbImpl::QueueTx>::Async>
completer = std::nullopt;
zx_status_t send_status = ZX_OK;
{
fbl::AutoLock _(&tx_mutex_);
if (InsertUsbRequest(std::move(request), bulk_in_reqs_) != ZX_OK) {
return;
}
// Do not queue requests if status is ZX_ERR_IO_NOT_PRESENT, as the underlying connection could
// be disconnected or USB_RESET is being processed. Calling adb_send_locked in such scenario
// will deadlock and crash the driver (see fxbug.dev/92793).
if (req->response.status != ZX_ERR_IO_NOT_PRESENT) {
if (!tx_pending_infos_.empty()) {
if ((send_status = SendLocked(tx_pending_infos_.front().buf)) != ZX_ERR_SHOULD_WAIT) {
completer = std::move(tx_pending_infos_.front().completer);
tx_pending_infos_.pop();
}
}
}
}
if (completer) {
fbl::AutoLock _(&adb_mutex_);
CompleteTxn(completer.value(), send_status);
}
}
size_t UsbAdbDevice::UsbFunctionInterfaceGetDescriptorsSize() { return sizeof(descriptors_); }
void UsbAdbDevice::UsbFunctionInterfaceGetDescriptors(uint8_t* buffer, size_t buffer_size,
size_t* out_actual) {
const size_t length = std::min(sizeof(descriptors_), buffer_size);
std::memcpy(buffer, &descriptors_, length);
*out_actual = length;
}
zx_status_t UsbAdbDevice::UsbFunctionInterfaceControl(const usb_setup_t* setup,
const uint8_t* write_buffer,
size_t write_size, uint8_t* out_read_buffer,
size_t read_size, size_t* out_read_actual) {
if (out_read_actual != NULL) {
*out_read_actual = 0;
}
return ZX_OK;
}
zx_status_t UsbAdbDevice::ConfigureEndpoints(bool enable) {
zx_status_t status;
fbl::AutoLock _(&rx_mutex_);
// Configure endpoint if not already done.
if (enable && !bulk_out_reqs_.is_empty()) {
status = function_.ConfigEp(&descriptors_.bulk_out_ep, nullptr);
if (status != ZX_OK) {
zxlogf(ERROR, "Failed to Config BULK OUT ep - %d.", status);
return status;
}
status = function_.ConfigEp(&descriptors_.bulk_in_ep, nullptr);
if (status != ZX_OK) {
zxlogf(ERROR, "Failed to Config BULK IN ep - %d.", status);
return status;
}
// queue RX requests
std::optional<usb::Request<>> pending_request;
while ((pending_request = bulk_out_reqs_.Get(usb_request_size_))) {
{
fbl::AutoLock _(&lock_);
pending_requests_++;
}
function_.RequestQueue(pending_request->take(), &rx_complete_);
}
zxlogf(INFO, "ADB endpoints configured.");
} else {
status = function_.DisableEp(bulk_out_addr());
if (status != ZX_OK) {
zxlogf(ERROR, "Failed to disable BULK OUT ep - %d.", status);
return status;
}
status = function_.DisableEp(bulk_in_addr());
if (status != ZX_OK) {
zxlogf(ERROR, "Failed to disable BULK IN ep - %d.", status);
return status;
}
}
return ZX_OK;
}
zx_status_t UsbAdbDevice::UsbFunctionInterfaceSetConfigured(bool configured, usb_speed_t speed) {
zxlogf(INFO, "configured? - %d speed - %d.", configured, speed);
bool adb_configured = false;
{
fbl::AutoLock _(&lock_);
status_ = fuchsia_hardware_adb::StatusFlags(configured);
speed_ = speed;
}
{
fbl::AutoLock _(&adb_mutex_);
if (adb_binding_.has_value()) {
fbl::AutoLock _(&lock_);
auto result = fidl::WireSendEvent(adb_binding_.value())->OnStatusChanged(status_);
if (!result.ok()) {
zxlogf(ERROR, "Could not call AdbInterface Status - %d.", result.status());
return ZX_ERR_IO;
}
adb_configured = true;
}
}
// Enable endpoints only when USB is configured and ADB interface is set.
return ConfigureEndpoints(configured && adb_configured);
}
zx_status_t UsbAdbDevice::UsbFunctionInterfaceSetInterface(uint8_t interface, uint8_t alt_setting) {
zxlogf(INFO, "interface - %d alt_setting - %d.", interface, alt_setting);
zx_status_t status;
if (interface != descriptors_.adb_intf.b_interface_number || alt_setting > 1) {
return ZX_ERR_INVALID_ARGS;
}
if (alt_setting) {
if ((status = function_.ConfigEp(&descriptors_.bulk_out_ep, NULL)) != ZX_OK ||
(status = function_.ConfigEp(&descriptors_.bulk_in_ep, NULL)) != ZX_OK) {
zxlogf(ERROR, "usb_function_config_ep failed - %d.", status);
}
} else {
if ((status = function_.DisableEp(bulk_out_addr())) != ZX_OK ||
(status = function_.DisableEp(bulk_in_addr())) != ZX_OK) {
zxlogf(ERROR, "usb_function_disable_ep failed - %d.", status);
}
}
fuchsia_hardware_adb::StatusFlags online;
if (alt_setting && status == ZX_OK) {
online = fuchsia_hardware_adb::StatusFlags::kOnline;
// queue our IN reqs
fbl::AutoLock _(&rx_mutex_);
std::optional<usb::Request<>> pending_request;
while ((pending_request = bulk_out_reqs_.Get(usb_request_size_))) {
{
fbl::AutoLock _(&lock_);
pending_requests_++;
}
function_.RequestQueue(pending_request->take(), &rx_complete_);
}
}
{
fbl::AutoLock _(&lock_);
status_ = online;
}
fbl::AutoLock _(&adb_mutex_);
if (adb_binding_.has_value()) {
fbl::AutoLock _(&lock_);
auto result = fidl::WireSendEvent(adb_binding_.value())->OnStatusChanged(status_);
if (!result.ok()) {
zxlogf(ERROR, "Could not call AdbInterface Status.");
return ZX_ERR_IO;
}
}
return status;
}
void UsbAdbDevice::ShutdownComplete() {
// Multiple threads/callbacks could observe pending_request == 0 and call ShutdownComplete
// multiple times. Only call the callback if not already called.
if (shutdown_callback_) {
shutdown_callback_();
}
}
void UsbAdbDevice::Shutdown() {
// Start the shutdown process by setting the shutdown bool to true.
// When the pipeline tries to submit requests, they will be immediately
// free'd.
{
fbl::AutoLock _(&lock_);
shutting_down_ = true;
}
// Disable endpoints to prevent new requests present in our
// pipeline from getting queued.
function_.DisableEp(bulk_out_addr());
function_.DisableEp(bulk_in_addr());
// Cancel all requests in the pipeline -- the completion handler
// will free these requests as they come in.
// Do not hold locks when calling this method. It might result in deadlock as completion callbacks
// could be invoked during this call.
function_.CancelAll(bulk_out_addr());
function_.CancelAll(bulk_in_addr());
{
fbl::AutoLock _(&adb_mutex_);
if (adb_binding_.has_value()) {
adb_binding_->Unbind();
}
adb_binding_.reset();
while (!rx_requests_.empty()) {
rx_requests_.front().ReplyError(ZX_ERR_BAD_STATE);
rx_requests_.pop();
}
}
// Free all request pools.
std::queue<txn_info_t> queue;
{
fbl::AutoLock _(&tx_mutex_);
bulk_in_reqs_.Release();
std::swap(queue, tx_pending_infos_);
}
while (!queue.empty()) {
CompleteTxn(queue.front().completer, ZX_ERR_PEER_CLOSED);
queue.pop();
}
{
fbl::AutoLock _(&rx_mutex_);
bulk_out_reqs_.Release();
}
// Call shutdown complete if all requests are released. This method will be called in completion
// callbacks if pending requests exists.
{
fbl::AutoLock _(&lock_);
if (pending_requests_ == 0) {
ShutdownComplete();
}
}
}
void UsbAdbDevice::DdkUnbind(ddk::UnbindTxn txn) {
{
fbl::AutoLock _(&lock_);
ZX_ASSERT(!shutdown_callback_);
shutdown_callback_ = [unbind_txn = std::move(txn)]() mutable { unbind_txn.Reply(); };
}
Shutdown();
}
void UsbAdbDevice::DdkRelease() { delete this; }
void UsbAdbDevice::DdkSuspend(ddk::SuspendTxn txn) {
{
fbl::AutoLock _(&lock_);
ZX_ASSERT(!shutdown_callback_);
shutdown_callback_ = [suspend_txn = std::move(txn)]() mutable {
suspend_txn.Reply(ZX_OK, suspend_txn.requested_state());
};
}
Shutdown();
}
zx_status_t UsbAdbDevice::Init() {
parent_request_size_ = function_.GetRequestSize();
usb_request_size_ = usb::Request<>::RequestSize(parent_request_size_);
auto status = function_.AllocInterface(&descriptors_.adb_intf.b_interface_number);
if (status != ZX_OK) {
zxlogf(ERROR, "usb_function_alloc_interface failed - %d.", status);
return status;
}
status = function_.AllocEp(USB_DIR_OUT, &descriptors_.bulk_out_ep.b_endpoint_address);
if (status != ZX_OK) {
zxlogf(ERROR, "usb_function_alloc_ep failed - %d.", status);
return status;
}
status = function_.AllocEp(USB_DIR_IN, &descriptors_.bulk_in_ep.b_endpoint_address);
if (status != ZX_OK) {
zxlogf(ERROR, "usb_function_alloc_ep failed - %d.", status);
return status;
}
// Allocate bulk out usb requests.
for (uint32_t i = 0; i < kBulkRxCount; i++) {
std::optional<usb::Request<>> request;
status = usb::Request<>::Alloc(&request, kBulkReqSize, bulk_out_addr(), parent_request_size_);
if (status != ZX_OK) {
zxlogf(ERROR, "Allocating bulk out request failed - %d.", status);
return status;
}
{
fbl::AutoLock _(&rx_mutex_);
bulk_out_reqs_.Add(*std::move(request));
}
}
// Allocate bulk in usb requests.
for (uint32_t i = 0; i < kBulkTxCount; i++) {
std::optional<usb::Request<>> request;
status = usb::Request<>::Alloc(&request, kBulkReqSize, bulk_in_addr(), parent_request_size_);
if (status != ZX_OK) {
zxlogf(ERROR, "Allocating bulk in request failed %d", status);
return status;
}
{
fbl::AutoLock _(&tx_mutex_);
bulk_in_reqs_.Add(*std::move(request));
}
}
status = DdkAdd("usb-adb-function", DEVICE_ADD_NON_BINDABLE);
if (status != ZX_OK) {
zxlogf(ERROR, "Could not add UsbAdbDevice %d.", status);
return status;
}
function_.SetInterface(this, &usb_function_interface_protocol_ops_);
return ZX_OK;
}
zx_status_t UsbAdbDevice::Bind(void* ctx, zx_device_t* parent) {
auto adb = std::make_unique<UsbAdbDevice>(parent);
if (!adb) {
zxlogf(ERROR, "Could not create UsbAdbDevice.");
return ZX_ERR_NO_MEMORY;
}
auto status = adb->Init();
if (status != ZX_OK) {
zxlogf(ERROR, "Could not init UsbAdbDevice - %d.", status);
adb->DdkRelease();
return status;
}
{
// The DDK now owns this reference.
[[maybe_unused]] auto released = adb.release();
}
return ZX_OK;
}
static constexpr zx_driver_ops_t driver_ops = []() {
zx_driver_ops_t ops{};
ops.version = DRIVER_OPS_VERSION;
ops.bind = UsbAdbDevice::Bind;
return ops;
}();
} // namespace usb_adb_function
// clang-format off
ZIRCON_DRIVER(usb_adb, usb_adb_function::driver_ops, "zircon", "0.1");