blob: 16d9de13f87de06f9f16cb9ba0ad4f5c54aa01e7 [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/driver/compat/cpp/compat.h>
#include <lib/driver/logging/cpp/structured_logger.h>
#include <lib/zx/vmar.h>
#include <zircon/assert.h>
#include <cstdint>
#include <mutex>
#include <optional>
#include <usb/peripheral.h>
#include <usb/request-cpp.h>
#include "zircon/status.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.Reply(fit::ok());
} else {
completer.Reply(fit::error(status));
}
}
} // namespace
void UsbAdbDevice::StartAdb(StartAdbRequestView request, StartAdbCompleter::Sync& completer) {
std::lock_guard<async::sequence_checker> _(checker_);
if (adb_binding_.has_value()) {
zxlogf(WARNING, "ADB already connected");
completer.ReplyError(ZX_ERR_ALREADY_BOUND);
return;
}
switch (state_) {
case State::kStoppingUsb:
zxlogf(WARNING, "ADB connected while stopping");
completer.ReplyError(ZX_ERR_BAD_STATE);
return;
case State::kOnline:
// We're already online, so send the status change immediately.
if (auto result =
fidl::WireSendEvent(request->interface)->OnStatusChanged(fadb::StatusFlags::kOnline);
!result.ok()) {
zxlogf(ERROR, "Could not call UsbAdbImpl.OnStatusChanged.");
}
break;
case State::kAwaitingUsbConnection:
break;
}
zxlogf(INFO, "ADB client connected");
adb_binding_.emplace(fdf::Dispatcher::GetCurrent()->async_dispatcher(),
std::move(request->interface), this, [this](fidl::UnbindInfo info) {
std::lock_guard<async::sequence_checker> _(checker_);
zxlogf(INFO, "Device closed with reason '%s'",
info.FormatDescription().c_str());
ResetOrStopUsb();
});
completer.ReplySuccess();
}
void UsbAdbDevice::StopAdb(StopAdbCompleter::Sync& completer) {
zxlogf(INFO, "ADB client requested disconnect.");
std::lock_guard<async::sequence_checker> _(checker_);
stop_completers_.push_back(completer.ToAsync());
ResetOrStopUsb();
}
void UsbAdbDevice::ResetOrStopUsb() {
switch (state_) {
case State::kStoppingUsb:
zxlogf(INFO, "Stop requested, but already stopping");
return;
case State::kOnline:
zxlogf(INFO, "Stopping USB");
break;
case State::kAwaitingUsbConnection:
zxlogf(INFO, "Stop requested during USB startup");
break;
}
// Purge any requests from internal queues.
// TODO(b/417808660): Replace logs with Inspect once the bug is fixed.
zxlogf(INFO, "rx_requests: %ld", rx_requests_.size());
while (!rx_requests_.empty()) {
rx_requests_.front().Reply(fit::error(ZX_ERR_BAD_STATE));
rx_requests_.pop();
}
// TODO(b/417808660): Replace logs with Inspect once the bug is fixed.
zxlogf(INFO, "pending_replies: %ld", pending_replies_.size());
while (!pending_replies_.empty()) {
bulk_out_ep_.PutRequest(
usb::FidlRequest(std::move(pending_replies_.front().request().value())));
pending_replies_.pop();
}
// TODO(b/417808660): Replace logs with Inspect once the bug is fixed.
zxlogf(INFO, "tx_pending_reqs: %ld", tx_pending_reqs_.size());
while (!tx_pending_reqs_.empty()) {
CompleteTxn(tx_pending_reqs_.front().completer, ZX_ERR_CANCELED);
tx_pending_reqs_.pop();
}
// Disconnect USB.
// TODO(b/417808660): Replace logs with Inspect once the bug is fixed.
zxlogf(INFO, "Disconnecting from USB with SetInterface(nullptr, nullptr)");
zx_status_t status = function_.SetInterface(nullptr, nullptr);
if (status != ZX_OK) {
ZX_PANIC("SetInterface failed: %s", zx_status_get_string(status));
}
zxlogf(INFO, "state_ = State::kStoppingUsb");
state_ = State::kStoppingUsb;
CheckUsbStopComplete();
}
void UsbAdbDevice::SendQueued() {
if (state_ != State::kOnline) {
ZX_PANIC("Unexpected state: %d", state_);
}
while (SendQueuedOnce()) {
}
}
// Returns true if any progress was made. Returns false if we didn't send
// anything, and therefore calling this again won't be useful until something
// changes.
bool UsbAdbDevice::SendQueuedOnce() {
if (tx_pending_reqs_.empty()) {
return false;
}
auto& current = tx_pending_reqs_.front();
std::vector<fuchsia_hardware_usb_request::Request> requests;
while (current.start < current.request.data().size()) {
auto req = bulk_in_ep_.GetRequest();
if (!req) {
break;
}
req->clear_buffers();
size_t to_copy = std::min(current.request.data().size() - current.start, kVmoDataSize);
auto actual = req->CopyTo(0, current.request.data().data() + current.start, to_copy,
bulk_in_ep_.GetMapped());
size_t actual_total = 0;
for (size_t i = 0; i < actual.size(); i++) {
// Fill in size of data.
(*req)->data()->at(i).size(actual[i]);
actual_total += actual[i];
}
auto status = req->CacheFlush(bulk_in_ep_.GetMapped());
if (status != ZX_OK) {
zxlogf(ERROR, "Cache flush failed %s", zx_status_get_string(status));
}
requests.emplace_back(req->take_request());
current.start += actual_total;
}
if (requests.empty()) {
return false;
}
auto result = bulk_in_ep_->QueueRequests(std::move(requests));
if (result.is_error()) {
zxlogf(ERROR, "Failed to QueueRequests %s", result.error_value().FormatDescription().c_str());
}
if (current.start == current.request.data().size()) {
CompleteTxn(current.completer, ZX_OK);
tx_pending_reqs_.pop();
}
return true;
}
void UsbAdbDevice::ReceiveQueued() {
if (state_ != State::kOnline) {
ZX_PANIC("Unexpected state: %d", state_);
}
while (ReceiveQueuedOnce()) {
}
}
bool UsbAdbDevice::ReceiveQueuedOnce() {
if (pending_replies_.empty() || rx_requests_.empty()) {
return false;
}
auto completion = std::move(pending_replies_.front());
pending_replies_.pop();
auto req = usb::FidlRequest(std::move(completion.request().value()));
if (*completion.status() != ZX_OK) {
zxlogf(ERROR, "RxComplete called with error %s.", zx_status_get_string(*completion.status()));
rx_requests_.front().Reply(fit::error(ZX_ERR_INTERNAL));
} else {
// This should always be true because when we registered VMOs, we only registered one per
// request.
ZX_ASSERT(req->data()->size() == 1);
auto addr = bulk_out_ep_.GetMappedAddr(req.request(), 0);
if (!addr.has_value()) {
zxlogf(ERROR, "Failed to get mapped");
rx_requests_.front().Reply(fit::error(ZX_ERR_INTERNAL));
} else {
auto status = req.CacheFlushInvalidate(bulk_out_ep_.GetMapped());
if (status != ZX_OK) {
zxlogf(ERROR, "Cache flush and invalidate failed %s", zx_status_get_string(status));
}
rx_requests_.front().Reply(fit::ok(
std::vector<uint8_t>(reinterpret_cast<uint8_t*>(*addr),
reinterpret_cast<uint8_t*>(*addr) + *completion.transfer_size())));
}
}
rx_requests_.pop();
req.reset_buffers(bulk_out_ep_.GetMapped());
std::vector<fuchsia_hardware_usb_request::Request> requests;
requests.emplace_back(req.take_request());
auto result = bulk_out_ep_->QueueRequests(std::move(requests));
if (result.is_error()) {
zxlogf(ERROR, "Failed to QueueRequests %s", result.error_value().FormatDescription().c_str());
}
return true;
}
void UsbAdbDevice::QueueTx(QueueTxRequest& request, QueueTxCompleter::Sync& completer) {
std::lock_guard<async::sequence_checker> _(checker_);
size_t length = request.data().size();
if (length == 0) {
zxlogf(INFO, "Invalid argument - Length = 0");
completer.Reply(fit::error(ZX_ERR_INVALID_ARGS));
return;
}
switch (state_) {
case State::kStoppingUsb:
// Return early during shutdown.
completer.Reply(fit::error(ZX_ERR_BAD_STATE));
return;
case State::kOnline:
case State::kAwaitingUsbConnection:
tx_pending_reqs_.emplace(
txn_req_t{.request = std::move(request), .start = 0, .completer = completer.ToAsync()});
SendQueued();
}
}
void UsbAdbDevice::Receive(ReceiveCompleter::Sync& completer) {
std::lock_guard<async::sequence_checker> _(checker_);
switch (state_) {
case State::kStoppingUsb:
// Return early during shutdown.
completer.Reply(fit::error(ZX_ERR_BAD_STATE));
return;
case State::kAwaitingUsbConnection:
rx_requests_.emplace(completer.ToAsync());
break;
case State::kOnline:
rx_requests_.emplace(completer.ToAsync());
ReceiveQueued();
break;
}
}
void UsbAdbDevice::RxComplete(fendpoint::Completion completion) {
// This should always be true because when we registered VMOs, we only registered one per request.
ZX_ASSERT(completion.request()->data()->size() == 1);
switch (state_) {
case State::kAwaitingUsbConnection:
ZX_PANIC("Completion arrived before we sent any requests?");
case State::kStoppingUsb:
bulk_out_ep_.PutRequest(usb::FidlRequest(std::move(completion.request().value())));
CheckUsbStopComplete();
return;
case State::kOnline:
pending_replies_.push(std::move(completion));
ReceiveQueued();
return;
}
}
void UsbAdbDevice::TxComplete(fendpoint::Completion completion) {
switch (state_) {
case State::kAwaitingUsbConnection:
ZX_PANIC("Completion arrived before we sent any requests?");
case State::kStoppingUsb:
bulk_in_ep_.PutRequest(usb::FidlRequest(std::move(completion.request().value())));
CheckUsbStopComplete();
return;
case State::kOnline:
bulk_in_ep_.PutRequest(usb::FidlRequest(std::move(completion.request().value())));
// 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 https://fxbug.dev/42174506).
if (*completion.status() != ZX_ERR_IO_NOT_PRESENT) {
SendQueued();
}
return;
}
}
void UsbAdbDevice::RxCompleteCallback(fendpoint::Completion completion) {
async::PostTask(dispatcher(), [this, completion = std::move(completion)]() mutable {
std::lock_guard<async::sequence_checker> _(checker_);
RxComplete(std::move(completion));
});
}
void UsbAdbDevice::TxCompleteCallback(fendpoint::Completion completion) {
async::PostTask(dispatcher(), [this, completion = std::move(completion)]() mutable {
std::lock_guard<async::sequence_checker> _(checker_);
TxComplete(std::move(completion));
});
}
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;
}
void UsbAdbDevice::EnableEndpoints() {
switch (state_) {
case State::kOnline:
zxlogf(INFO, "USB endpoints already enabled");
return;
case State::kStoppingUsb:
zxlogf(ERROR, "This is unexpected: UsbFunctionInterface is disconnected while stopping");
return;
case State::kAwaitingUsbConnection:
zxlogf(INFO, "Enabling USB endpoints");
break;
}
zx_status_t status = function_.ConfigEp(&descriptors_.bulk_out_ep, nullptr);
if (status != ZX_OK) {
ZX_PANIC("Failed to Config BULK OUT ep - %d.", status);
}
status = function_.ConfigEp(&descriptors_.bulk_in_ep, nullptr);
if (status != ZX_OK) {
ZX_PANIC("Failed to Config BULK IN ep - %d.", status);
}
// queue RX requests
std::vector<fuchsia_hardware_usb_request::Request> requests;
while (auto req = bulk_out_ep_.GetRequest()) {
req->reset_buffers(bulk_out_ep_.GetMapped());
auto status = req->CacheFlushInvalidate(bulk_out_ep_.GetMapped());
if (status != ZX_OK) {
ZX_PANIC("Cache flush and invalidate failed %d", status);
}
requests.emplace_back(req->take_request());
}
auto result = bulk_out_ep_->QueueRequests(std::move(requests));
if (result.is_error()) {
ZX_PANIC("Failed to QueueRequests %s", result.error_value().FormatDescription().c_str());
}
if (adb_binding_.has_value()) {
auto result = fidl::WireSendEvent(*adb_binding_)->OnStatusChanged(fadb::StatusFlags::kOnline);
if (!result.ok()) {
zxlogf(ERROR, "Could not call UsbAdbImpl.OnStatusChanged.");
}
}
zxlogf(INFO, "state_ = State::kOnline");
state_ = State::kOnline;
}
zx_status_t UsbAdbDevice::UsbFunctionInterfaceSetConfigured(bool configured, usb_speed_t speed) {
zxlogf(INFO, "configured? - %d speed - %d.", configured, speed);
async::PostTask(dispatcher(), [this, configured]() {
std::lock_guard<async::sequence_checker> _(checker_);
if (configured) {
EnableEndpoints();
} else {
switch (state_) {
case State::kAwaitingUsbConnection:
// It's normal to receive SetConfigured(false) while the connection is
// starting up - ignore it.
break;
case State::kOnline:
ResetOrStopUsb();
break;
case State::kStoppingUsb:
zxlogf(
WARNING,
"Received SetConfigured(false) while stopping. This is unexpected, but probably fine.");
break;
}
}
});
return ZX_OK;
}
zx_status_t UsbAdbDevice::UsbFunctionInterfaceSetInterface(uint8_t interface, uint8_t alt_setting) {
zxlogf(INFO, "interface - %d alt_setting - %d.", interface, alt_setting);
// We don't support any alt_settings - just validate that the main setting has
// been requested, and then don't do anything.
if (interface != descriptors_.adb_intf.b_interface_number || alt_setting > 0) {
return ZX_ERR_INVALID_ARGS;
}
return ZX_OK;
}
void UsbAdbDevice::CheckUsbStopComplete() {
if (state_ != State::kStoppingUsb) {
ZX_PANIC("Unexpected state: %d", state_);
}
if (!bulk_in_ep_.RequestsFull() || !bulk_out_ep_.RequestsFull()) {
// Still waiting for outstanding USB requests to return.
// TODO(b/417808660): Replace logs with Inspect once the bug is fixed.
zxlogf(INFO, "Not all USB requests complete (in:%d out:%d)", bulk_in_ep_.RequestsFull(),
bulk_out_ep_.RequestsFull());
return;
}
zxlogf(INFO, "All USB requests complete. Completing USB stop.");
if (adb_binding_.has_value()) {
auto result = fidl::WireSendEvent(*adb_binding_)->OnStatusChanged(fadb::StatusFlags(0));
if (!result.ok()) {
zxlogf(ERROR, "Could not call UsbAdbImpl.OnStatusChanged.");
}
}
adb_binding_.reset();
// TODO(b/417808660): Replace logs with Inspect once the bug is fixed.
zxlogf(INFO, "Calling stop_completers_");
while (!stop_completers_.empty()) {
stop_completers_.back().Reply(zx::ok());
stop_completers_.pop_back();
}
// Is this a proper shutdown, or a restart of USB?
if (shutdown_callback_.has_value()) {
zxlogf(INFO, "Shutting down driver.");
shutdown_callback_.value()(zx::ok());
shutdown_callback_.reset();
} else {
zxlogf(INFO, "Restarting USB connection.");
StartUsb();
}
}
void UsbAdbDevice::PrepareStop(fdf::PrepareStopCompleter completer) {
std::lock_guard<async::sequence_checker> _(checker_);
shutdown_callback_.emplace(std::move(completer));
ResetOrStopUsb();
}
zx_status_t UsbAdbDevice::InitEndpoint(
fidl::ClientEnd<fuchsia_hardware_usb_function::UsbFunction>& client, uint8_t direction,
uint8_t* ep_addrs, usb::EndpointClient<UsbAdbDevice>& ep, uint32_t req_count) {
auto status = function_.AllocEp(direction, ep_addrs);
if (status != ZX_OK) {
zxlogf(ERROR, "usb_function_alloc_ep failed - %s.", zx_status_get_string(status));
return status;
}
status = ep.Init(*ep_addrs, client, usb_dispatcher_);
if (status != ZX_OK) {
zxlogf(ERROR, "Failed to init UsbEndpoint %s", zx_status_get_string(status));
return status;
}
// TODO(127854): When we support active pinning of VMOs, adb may want to use VMOs that are not
// perpetually pinned.
auto actual =
ep.AddRequests(req_count, kVmoDataSize, fuchsia_hardware_usb_request::Buffer::Tag::kVmoId);
if (actual != req_count) {
zxlogf(ERROR, "Wanted %u requests, only got %zu requests", req_count, actual);
}
return actual == 0 ? ZX_ERR_INTERNAL : ZX_OK;
}
zx::result<> UsbAdbDevice::Start() {
std::lock_guard<async::sequence_checker> _(checker_);
zx::result<ddk::UsbFunctionProtocolClient> function =
compat::ConnectBanjo<ddk::UsbFunctionProtocolClient>(incoming());
if (function.is_error()) {
FDF_SLOG(ERROR, "Failed to connect function", KV("status", function.status_string()));
return function.take_error();
}
function_ = *function;
auto client = incoming()->Connect<fuchsia_hardware_usb_function::UsbFunctionService::Device>();
if (client.is_error()) {
zxlogf(ERROR, "Failed to connect fidl protocol");
return client.take_error();
}
auto status = function_.AllocInterface(&descriptors_.adb_intf.b_interface_number);
if (status != ZX_OK) {
zxlogf(ERROR, "usb_function_alloc_interface failed - %s.", zx_status_get_string(status));
return zx::error(status);
}
status = InitEndpoint(*client, USB_DIR_OUT, &descriptors_.bulk_out_ep.b_endpoint_address,
bulk_out_ep_, kBulkRxCount);
if (status != ZX_OK) {
zxlogf(ERROR, "InitEndpoint failed - %s.", zx_status_get_string(status));
return zx::error(status);
}
status = InitEndpoint(*client, USB_DIR_IN, &descriptors_.bulk_in_ep.b_endpoint_address,
bulk_in_ep_, kBulkTxCount);
if (status != ZX_OK) {
zxlogf(ERROR, "InitEndpoint failed - %s.", zx_status_get_string(status));
return zx::error(status);
}
auto serve_result = outgoing()->AddService<fadb::Service>(fadb::Service::InstanceHandler({
.adb = device_bindings_.CreateHandler(this, fdf::Dispatcher::GetCurrent()->async_dispatcher(),
fidl::kIgnoreBindingClosure),
}));
if (serve_result.is_error()) {
zxlogf(ERROR, "Failed to add Device service %s", serve_result.status_string());
return serve_result.take_error();
}
StartUsb();
return zx::ok();
}
void UsbAdbDevice::StartUsb() {
zx_status_t status = function_.SetInterface(this, &usb_function_interface_protocol_ops_);
if (status != ZX_OK) {
ZX_PANIC("SetInterface failed %s", zx_status_get_string(status));
}
zxlogf(INFO, "state_ = State::kAwaitingUsbConnection");
state_ = State::kAwaitingUsbConnection;
}
} // namespace usb_adb_function
FUCHSIA_DRIVER_EXPORT(usb_adb_function::UsbAdbDevice);