blob: d3d4e64d26ff5db8a3c1afbf192f010bc96b5748 [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 "src/connectivity/overnet/usb/overnet_usb.h"
#include <fidl/fuchsia.hardware.usb.function/cpp/fidl.h>
#include <fuchsia/hardware/usb/function/cpp/banjo.h>
#include <lib/driver/compat/cpp/compat.h>
#include <lib/driver/component/cpp/driver_export.h>
#include <zircon/errors.h>
#include <zircon/status.h>
#include <zircon/types.h>
#include <algorithm>
#include <cstdint>
#include <iterator>
#include <optional>
#include <variant>
#include <fbl/auto_lock.h>
#include <usb/request-cpp.h>
#include "fidl/fuchsia.hardware.overnet/cpp/wire_types.h"
#include "lib/async/cpp/task.h"
#include "lib/async/cpp/wait.h"
#include "lib/fidl/cpp/wire/channel.h"
#include "lib/fidl/cpp/wire/internal/transport.h"
namespace fendpoint = fuchsia_hardware_usb_endpoint;
namespace ffunction = fuchsia_hardware_usb_function;
static constexpr uint8_t kOvernetMagic[] = "OVERNET USB\xff\x00\xff\x00\xff";
static constexpr size_t kOvernetMagicSize = sizeof(kOvernetMagic) - 1;
zx::result<> OvernetUsb::Start() {
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<ffunction::UsbFunctionService::Device>();
if (client.is_error()) {
FDF_SLOG(ERROR, "Failed to connect fidl protocol",
KV("status", zx_status_get_string(client.error_value())));
return zx::error(client.error_value());
}
zx_status_t status =
function_.AllocStringDesc("Overnet USB interface", &descriptors_.data_interface.i_interface);
if (status != ZX_OK) {
FDF_SLOG(ERROR, "Failed to allocate string descriptor",
KV("status", zx_status_get_string(status)));
return zx::error(status);
}
status = function_.AllocInterface(&descriptors_.data_interface.b_interface_number);
if (status != ZX_OK) {
FDF_SLOG(ERROR, "Failed to allocate data interface",
KV("status", zx_status_get_string(status)));
return zx::error(status);
}
status = function_.AllocEp(USB_DIR_OUT, &descriptors_.out_ep.b_endpoint_address);
if (status != ZX_OK) {
FDF_SLOG(ERROR, "Failed to allocate bulk out interface",
KV("status", zx_status_get_string(status)));
return zx::error(status);
}
endpoint_dispatcher_.StartThread("endpoint_thread");
status = bulk_out_ep_.Init(descriptors_.out_ep.b_endpoint_address, *client,
endpoint_dispatcher_.dispatcher());
if (status != ZX_OK) {
FDF_SLOG(ERROR, "Failed to init UsbEndpoint", KV("endpoint", "out"),
KV("status", zx_status_get_string(status)));
return zx::error(status);
}
status = function_.AllocEp(USB_DIR_IN, &descriptors_.in_ep.b_endpoint_address);
if (status != ZX_OK) {
FDF_SLOG(ERROR, "Failed to allocate bulk in interface",
KV("status", zx_status_get_string(status)));
return zx::error(status);
}
status = bulk_in_ep_.Init(descriptors_.in_ep.b_endpoint_address, *client,
endpoint_dispatcher_.dispatcher());
if (status != ZX_OK) {
FDF_SLOG(ERROR, "Failed to init UsbEndpoint", KV("endpoint", "in"),
KV("status", zx_status_get_string(status)));
return zx::error(status);
}
auto actual = bulk_in_ep_.AddRequests(kRequestPoolSize, kMtu,
fuchsia_hardware_usb_request::Buffer::Tag::kVmoId);
if (actual != kRequestPoolSize) {
FDF_SLOG(ERROR, "Could not allocate all requests for IN endpoint",
KV("wanted", kRequestPoolSize), KV("got", actual));
}
actual = bulk_out_ep_.AddRequests(kRequestPoolSize, kMtu,
fuchsia_hardware_usb_request::Buffer::Tag::kVmoId);
if (actual != kRequestPoolSize) {
FDF_SLOG(ERROR, "Could not allocate all requests for OUT endpoint",
KV("wanted", kRequestPoolSize), KV("got", actual));
}
function_.SetInterface(this, &usb_function_interface_protocol_ops_);
auto connector = devfs_connector_.Bind(dispatcher_);
if (connector.is_error()) {
FDF_SLOG(ERROR, "devfs_connector_.Bind() failed", KV("error", connector.status_string()));
return connector.take_error();
}
fdf::DevfsAddArgs devfs;
devfs.connector(std::move(connector.value()));
devfs.class_name("overnet-usb");
fdf::NodeAddArgs args;
args.devfs_args(std::move(devfs));
args.name("overnet_usb");
auto controller_eps = fidl::CreateEndpoints<fdf::NodeController>();
if (controller_eps.is_error()) {
FDF_SLOG(ERROR, "Could not create node controller endpoints",
KV("error", controller_eps.status_string()));
return controller_eps.take_error();
}
node_controller_.Bind(std::move(controller_eps->client));
auto node_eps = fidl::CreateEndpoints<fdf::Node>();
if (node_eps.is_error()) {
FDF_SLOG(ERROR, "Could not create node endpoints", KV("error", node_eps.status_string()));
return node_eps.take_error();
}
node_.Bind(std::move(node_eps->client));
auto result = fidl::Call(node())->AddChild({{
.args = std::move(args),
.controller = std::move(controller_eps->server),
.node = std::move(node_eps->server),
}});
if (result.is_error()) {
FDF_SLOG(ERROR, "Could not add child node",
KV("error", result.error_value().FormatDescription()));
return zx::error(ZX_ERR_INTERNAL);
}
return zx::ok();
}
void OvernetUsb::FidlConnect(fidl::ServerEnd<fuchsia_hardware_overnet::Device> request) {
device_binding_group_.AddBinding(dispatcher_, std::move(request), this,
fidl::kIgnoreBindingClosure);
}
void OvernetUsb::PrepareStop(fdf::PrepareStopCompleter completer) {
Shutdown([completer = std::move(completer)]() mutable { completer(zx::ok()); });
}
size_t OvernetUsb::UsbFunctionInterfaceGetDescriptorsSize() { return sizeof(descriptors_); }
void OvernetUsb::UsbFunctionInterfaceGetDescriptors(uint8_t* out_descriptors_buffer,
size_t descriptors_size,
size_t* out_descriptors_actual) {
memcpy(out_descriptors_buffer, &descriptors_,
std::min(descriptors_size, UsbFunctionInterfaceGetDescriptorsSize()));
*out_descriptors_actual = UsbFunctionInterfaceGetDescriptorsSize();
}
// NOLINTNEXTLINE(readability-convert-member-functions-to-static)
zx_status_t OvernetUsb::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) {
FDF_SLOG(WARNING, "Overnet USB driver received control message");
return ZX_ERR_NOT_SUPPORTED;
}
zx_status_t OvernetUsb::UsbFunctionInterfaceSetConfigured(bool configured, usb_speed_t speed) {
fbl::AutoLock lock(&lock_);
if (!configured) {
if (std::holds_alternative<Unconfigured>(state_)) {
return ZX_OK;
}
function_.CancelAll(BulkInAddress());
function_.CancelAll(BulkOutAddress());
state_ = Unconfigured();
callback_ = std::nullopt;
zx_status_t status = function_.DisableEp(BulkInAddress());
if (status != ZX_OK) {
FDF_SLOG(ERROR, "Failed to disable data in endpoint",
KV("status", zx_status_get_string(status)));
return status;
}
status = function_.DisableEp(BulkOutAddress());
if (status != ZX_OK) {
FDF_SLOG(ERROR, "Failed to disable data out endpoint",
KV("status", zx_status_get_string(status)));
return status;
}
return ZX_OK;
}
if (!std::holds_alternative<Unconfigured>(state_)) {
return ZX_OK;
}
zx_status_t status = function_.ConfigEp(&descriptors_.in_ep, nullptr);
if (status != ZX_OK) {
FDF_SLOG(ERROR, "Failed to configure bulk in endpoint",
KV("status", zx_status_get_string(status)));
return status;
}
status = function_.ConfigEp(&descriptors_.out_ep, nullptr);
if (status != ZX_OK) {
FDF_SLOG(ERROR, "Failed to configure bulk out endpoint",
KV("status", zx_status_get_string(status)));
return status;
}
state_ = Ready();
std::vector<fuchsia_hardware_usb_request::Request> requests;
while (auto req = bulk_out_ep_.GetRequest()) {
req->reset_buffers(bulk_out_ep_.GetMapped);
zx_status_t status = req->CacheFlushInvalidate(bulk_out_ep_.GetMapped);
if (status != ZX_OK) {
FDF_SLOG(ERROR, "Cache flush failed", KV("status", zx_status_get_string(status)));
}
requests.emplace_back(req->take_request());
}
auto result = bulk_out_ep_->QueueRequests(std::move(requests));
if (result.is_error()) {
FDF_SLOG(ERROR, "Failed to QueueRequests",
KV("status", result.error_value().FormatDescription()));
return result.error_value().status();
}
return ZX_OK;
}
// NOLINTNEXTLINE(readability-convert-member-functions-to-static,bugprone-easily-swappable-parameters)
zx_status_t OvernetUsb::UsbFunctionInterfaceSetInterface(uint8_t interface, uint8_t alt_setting) {
return ZX_OK;
}
std::optional<usb::FidlRequest> OvernetUsb::PrepareTx() {
if (!Online()) {
return std::nullopt;
}
auto request = bulk_in_ep_.GetRequest();
if (!request) {
FDF_SLOG(DEBUG, "No available TX requests");
return std::nullopt;
}
request->clear_buffers();
return request;
}
void OvernetUsb::HandleSocketReadable(async_dispatcher_t*, async::WaitBase*, zx_status_t status,
const zx_packet_signal_t*) {
if (status != ZX_OK) {
if (status != ZX_ERR_CANCELED) {
FDF_SLOG(WARNING, "Unexpected error waiting on socket",
KV("status", zx_status_get_string(status)));
}
return;
}
fbl::AutoLock lock(&lock_);
auto request = PrepareTx();
if (!request) {
return;
}
// This should always be true because when we registered VMOs, we only registered one per
// request.
ZX_ASSERT((*request)->data()->size() == 1);
std::optional<zx_vaddr_t> addr = bulk_out_ep_.GetMappedAddr(request->request(), 0);
if (!addr.has_value()) {
FDF_LOG(ERROR, "Failed to map request");
return;
}
size_t actual;
std::visit(
[this, &addr, &actual, &status](auto&& state) __TA_REQUIRES(lock_) {
state_ = std::forward<decltype(state)>(state).SendData(reinterpret_cast<uint8_t*>(*addr),
kMtu, &actual, &status);
},
std::move(state_));
if (status == ZX_OK) {
(*request)->data()->at(0).size(actual);
status = request->CacheFlush(bulk_in_ep_.GetMappedLocked);
if (status != ZX_OK) {
FDF_SLOG(ERROR, "Cache flush failed", KV("status", zx_status_get_string(status)));
}
std::vector<fuchsia_hardware_usb_request::Request> requests;
requests.emplace_back(request->take_request());
auto result = bulk_in_ep_->QueueRequests(std::move(requests));
if (result.is_error()) {
FDF_SLOG(ERROR, "Failed to QueueRequests",
KV("status", result.error_value().FormatDescription()));
}
} else {
bulk_in_ep_.PutRequest(usb::FidlRequest(std::move(*request)));
}
std::visit(
[this](auto& state) __TA_REQUIRES(lock_) {
if (std::forward<decltype(state)>(state).ReadsWaiting()) {
ProcessReadsFromSocket();
}
},
state_);
}
OvernetUsb::State OvernetUsb::Running::SendData(uint8_t* data, size_t len, size_t* actual,
zx_status_t* status) && {
*status = socket_.read(0, data, len, actual);
if (*status != ZX_OK && *status != ZX_ERR_SHOULD_WAIT) {
if (*status != ZX_ERR_PEER_CLOSED) {
FDF_SLOG(ERROR, "Failed to read from socket", KV("status", zx_status_get_string(*status)));
}
return Ready();
}
return std::move(*this);
}
void OvernetUsb::HandleSocketWritable(async_dispatcher_t*, async::WaitBase*, zx_status_t status,
const zx_packet_signal_t*) {
fbl::AutoLock lock(&lock_);
if (status != ZX_OK) {
if (status != ZX_ERR_CANCELED) {
FDF_SLOG(WARNING, "Unexpected error waiting on socket",
KV("status", zx_status_get_string(status)));
}
return;
}
std::visit([this](auto&& state)
__TA_REQUIRES(lock_) { state_ = std::forward<decltype(state)>(state).Writable(); },
std::move(state_));
std::visit(
[this](auto& state) __TA_REQUIRES(lock_) {
if (std::forward<decltype(state)>(state).WritesWaiting()) {
ProcessWritesToSocket();
}
},
state_);
}
OvernetUsb::State OvernetUsb::Running::Writable() && {
if (socket_out_queue_.empty()) {
return std::move(*this);
}
size_t actual;
zx_status_t status =
socket_.write(0, socket_out_queue_.data(), socket_out_queue_.size(), &actual);
if (status == ZX_OK) {
socket_out_queue_.erase(socket_out_queue_.begin(),
socket_out_queue_.begin() + static_cast<ssize_t>(actual));
} else if (status != ZX_ERR_SHOULD_WAIT) {
if (status != ZX_ERR_PEER_CLOSED) {
FDF_SLOG(ERROR, "Failed to read from socket", KV("status", zx_status_get_string(status)));
}
return Ready();
}
return std::move(*this);
}
void OvernetUsb::SetCallback(fuchsia_hardware_overnet::wire::DeviceSetCallbackRequest* request,
SetCallbackCompleter::Sync& completer) {
fbl::AutoLock lock(&lock_);
callback_ = Callback(fidl::WireSharedClient(std::move(request->callback), dispatcher_,
fidl::ObserveTeardown([this]() {
fbl::AutoLock lock(&lock_);
callback_ = std::nullopt;
})));
HandleSocketAvailable();
lock.release();
completer.Reply();
}
void OvernetUsb::HandleSocketAvailable() {
if (!callback_) {
return;
}
if (!peer_socket_) {
return;
}
(*callback_)(std::move(*peer_socket_));
peer_socket_ = std::nullopt;
}
void OvernetUsb::Callback::operator()(zx::socket socket) {
if (!fidl_.is_valid()) {
return;
}
fidl_->NewLink(std::move(socket))
.Then([](fidl::WireUnownedResult<fuchsia_hardware_overnet::Callback::NewLink>& result) {
if (!result.ok()) {
auto res = result.FormatDescription();
FDF_SLOG(ERROR, "Failed to share socket with component", KV("status", res));
}
});
}
OvernetUsb::State OvernetUsb::Unconfigured::ReceiveData(uint8_t*, size_t len,
std::optional<zx::socket>*,
OvernetUsb* owner) && {
FDF_SLOG(WARNING, "Dropped incoming data (device not configured)", KV("bytes", len));
return *this;
}
OvernetUsb::State OvernetUsb::ShuttingDown::ReceiveData(uint8_t*, size_t len,
std::optional<zx::socket>*,
OvernetUsb* owner) && {
FDF_SLOG(WARNING, "Dropped incoming data (device shutting down)", KV("bytes", len));
return std::move(*this);
}
OvernetUsb::State OvernetUsb::Ready::ReceiveData(uint8_t* data, size_t len,
std::optional<zx::socket>* peer_socket,
OvernetUsb* owner) && {
if (len != kOvernetMagicSize ||
!std::equal(kOvernetMagic, kOvernetMagic + kOvernetMagicSize, data)) {
FDF_SLOG(WARNING, "Dropped incoming data (driver not synchronized)", KV("bytes", len));
return *this;
}
zx::socket socket;
*peer_socket = zx::socket();
zx_status_t status = zx::socket::create(ZX_SOCKET_STREAM, &socket, &peer_socket->value());
if (status != ZX_OK) {
FDF_SLOG(ERROR, "Failed to create socket", KV("status", zx_status_get_string(status)));
// There are two errors that can happen here: a kernel out of memory condition which the docs
// say we shouldn't try to handle, and invalid arguments, which should be impossible.
abort();
}
return Running(std::move(socket), owner);
}
OvernetUsb::State OvernetUsb::Running::ReceiveData(uint8_t* data, size_t len,
std::optional<zx::socket>* peer_socket,
OvernetUsb* owner) && {
if (len == kOvernetMagicSize &&
std::equal(kOvernetMagic, kOvernetMagic + kOvernetMagicSize, data)) {
return Ready().ReceiveData(data, len, peer_socket, owner);
}
zx_status_t status;
if (socket_out_queue_.empty()) {
size_t actual = 0;
while (len > 0) {
status = socket_.write(0, data, len, &actual);
if (status != ZX_OK) {
break;
}
len -= actual;
data += actual;
}
if (len == 0) {
return std::move(*this);
}
if (status != ZX_ERR_SHOULD_WAIT) {
if (status != ZX_ERR_PEER_CLOSED) {
FDF_SLOG(ERROR, "Failed to write to socket", KV("status", zx_status_get_string(status)));
}
return Ready();
}
}
if (len != 0) {
std::copy(data, data + len, std::back_inserter(socket_out_queue_));
}
return std::move(*this);
}
void OvernetUsb::SendMagicReply(usb::FidlRequest request) {
auto actual = request.CopyTo(0, kOvernetMagic, kOvernetMagicSize, bulk_in_ep_.GetMappedLocked);
for (size_t i = 0; i < actual.size(); i++) {
request->data()->at(i).size(actual[i]);
}
auto status = request.CacheFlush(bulk_in_ep_.GetMappedLocked);
if (status != ZX_OK) {
FDF_SLOG(ERROR, "Cache flush failed", KV("status", zx_status_get_string(status)));
}
std::vector<fuchsia_hardware_usb_request::Request> requests;
requests.emplace_back(request.take_request());
auto result = bulk_in_ep_->QueueRequests(std::move(requests));
if (result.is_error()) {
FDF_SLOG(ERROR, "Failed to QueueRequests",
KV("status", result.error_value().FormatDescription()));
ResetState();
return;
}
auto& state = std::get<Running>(state_);
state.MagicSent();
ProcessReadsFromSocket();
HandleSocketAvailable();
}
void OvernetUsb::Running::MagicSent() {
socket_is_new_ = false;
async::PostTask(owner_->dispatcher_, [this]() {
fbl::AutoLock lock(&owner_->lock_);
if (std::holds_alternative<ShuttingDown>(owner_->state_)) {
if (!owner_->HasPendingRequests()) {
lock.release();
owner_->ShutdownComplete();
}
return;
}
if (std::get_if<Running>(&owner_->state_) != this) {
return;
}
if (read_waiter_->is_pending()) {
read_waiter_->Cancel();
}
read_waiter_->set_object(socket()->get());
if (write_waiter_->is_pending()) {
write_waiter_->Cancel();
}
write_waiter_->set_object(socket()->get());
});
}
void OvernetUsb::ReadComplete(fendpoint::Completion completion) {
fbl::AutoLock lock(&lock_);
auto request = usb::FidlRequest(std::move(completion.request().value()));
if (*completion.status() == ZX_ERR_IO_NOT_PRESENT) {
// Device disconnected from host.
if (std::holds_alternative<ShuttingDown>(state_)) {
if (!HasPendingRequests()) {
lock.release();
ShutdownComplete();
}
return;
}
bulk_out_ep_.PutRequest(std::move(request));
return;
}
if (*completion.status() == ZX_OK) {
// This should always be true because when we registered VMOs, we only registered one per
// request.
ZX_ASSERT(request->data()->size() == 1);
auto addr = bulk_out_ep_.GetMappedAddr(request.request(), 0);
if (!addr.has_value()) {
FDF_SLOG(ERROR, "Failed to map RX data");
return;
}
uint8_t* data = reinterpret_cast<uint8_t*>(*addr);
size_t data_length = *completion.transfer_size();
std::visit(
[this, data, data_length](auto&& state) __TA_REQUIRES(lock_) {
state_ = std::forward<decltype(state)>(state).ReceiveData(data, data_length,
&peer_socket_, this);
},
std::move(state_));
std::visit(
[this](auto& state) __TA_REQUIRES(lock_) {
if (std::forward<decltype(state)>(state).NewSocket()) {
auto request = PrepareTx();
if (request) {
SendMagicReply(std::move(*request));
}
}
if (std::forward<decltype(state)>(state).WritesWaiting()) {
ProcessWritesToSocket();
}
},
state_);
} else if (*completion.status() != ZX_ERR_CANCELED) {
FDF_SLOG(ERROR, "Read failed", KV("status", zx_status_get_string(*completion.status())));
}
if (Online()) {
std::vector<fuchsia_hardware_usb_request::Request> requests;
requests.emplace_back(request.take_request());
auto result = bulk_out_ep_->QueueRequests(std::move(requests));
if (result.is_error()) {
FDF_SLOG(ERROR, "Failed to QueueRequests",
KV("status", result.error_value().FormatDescription()));
}
} else {
if (std::holds_alternative<ShuttingDown>(state_)) {
if (!HasPendingRequests()) {
lock.release();
ShutdownComplete();
}
return;
}
bulk_out_ep_.PutRequest(std::move(request));
}
}
void OvernetUsb::WriteComplete(fendpoint::Completion completion) {
auto request = usb::FidlRequest(std::move(completion.request().value()));
fbl::AutoLock lock(&lock_);
if (std::holds_alternative<ShuttingDown>(state_)) {
if (!HasPendingRequests()) {
lock.release();
ShutdownComplete();
}
return;
}
if (auto state = std::get_if<Running>(&state_)) {
if (state->NewSocket()) {
SendMagicReply(std::move(request));
return;
}
}
bulk_in_ep_.PutRequest(std::move(request));
ProcessReadsFromSocket();
}
void OvernetUsb::Shutdown(fit::function<void()> callback) {
fbl::AutoLock lock(&lock_);
function_.CancelAll(BulkInAddress());
function_.CancelAll(BulkOutAddress());
state_ = ShuttingDown(std::move(callback));
if (!HasPendingRequests()) {
lock.release();
ShutdownComplete();
}
}
void OvernetUsb::ShutdownComplete() {
if (auto state = std::get_if<ShuttingDown>(&state_)) {
state->FinishWithCallback();
} else {
FDF_SLOG(ERROR, "ShutdownComplete called outside of shutdown path");
}
}
FUCHSIA_DRIVER_EXPORT(OvernetUsb);