blob: 91dc73d063aa77b1fc8c9f8a8ab673a50e3ee11e [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/firmware/drivers/usb-fastboot-function/usb_fastboot_function.h"
#include <lib/driver/compat/cpp/compat.h>
#include <lib/zircon-internal/align.h>
namespace usb_fastboot_function {
namespace {
size_t CalculateRxHeaderLength(size_t data_size) {
// Adjusts USB RX request length to bypass zero-length-packet. Upstream fastboot implementation
// doesn't send zero-length packet when download completes. This causes host side driver to stall
// if size of download data happens to be multiples of USB max packet size but not multiples of
// bulk size. For example, suppose bulk request size is 2048, and USB max packet size is 512, and
// host is sending the last 512/1024/1536 bytes during download, this last packet will not reach
// this driver immediately. But if the host sends another 10 bytes of data, the driver will
// receive a single packet of size (512/1024/1536 + 10) bytes. Thus we adjust the value of bulk
// request based on the expected amount of data to receive. The size is required to be multiples
// of kBulkMaxPacketSize. Thus we adjust it to be the smaller between kBulkReqSize and the round
// up value of `data_size' w.r.t kBulkMaxPacketSize. For example, if we are expecting exactly 512
// bytes of data, the following will give 512 exactly.
return std::min(static_cast<size_t>(kBulkReqSize), ZX_ROUNDUP(data_size, kBulkMaxPacketSize));
}
} // namespace
void UsbFastbootFunction::CleanUpTx(zx_status_t status, usb::FidlRequest req) {
send_vmo_.Reset();
bulk_in_ep_.PutRequest(std::move(req));
if (status == ZX_OK) {
send_completer_->ReplySuccess();
} else {
send_completer_->ReplyError(status);
}
send_completer_.reset();
}
void UsbFastbootFunction::QueueTx(usb::FidlRequest req) {
size_t to_send = std::min(static_cast<size_t>(kBulkReqSize), total_to_send_ - sent_size_);
auto actual = req.CopyTo(0, static_cast<uint8_t*>(send_vmo_.start()) + sent_size_, to_send,
bulk_in_ep_.GetMapped());
ZX_ASSERT(actual.size() == 1);
req->data()->at(0).size(actual[0]);
if (auto status = req.CacheFlush(bulk_in_ep_.GetMapped()); status != ZX_OK) {
ZX_PANIC("Cache flush failed %d", status);
}
std::vector<fuchsia_hardware_usb_request::Request> requests;
requests.emplace_back(req.take_request());
auto result = bulk_in_ep_->QueueRequests(std::move(requests));
if (result.is_error()) {
ZX_PANIC("Failed to QueueRequests %s", result.error_value().FormatDescription().c_str());
}
}
void UsbFastbootFunction::TxComplete(fuchsia_hardware_usb_endpoint::Completion completion) {
std::lock_guard<std::mutex> _(send_lock_);
usb::FidlRequest req{std::move(completion.request().value())};
auto status = *completion.status();
// Do not queue request on error.
if (status != ZX_OK) {
zxlogf(ERROR, "tx_completion error: %s", zx_status_get_string(status));
CleanUpTx(status, std::move(req));
return;
}
// If succeeds, update `sent_size_`, otherwise keep it the same to retry.
sent_size_ += *completion.transfer_size();
if (sent_size_ == total_to_send_) {
CleanUpTx(ZX_OK, std::move(req));
return;
}
QueueTx(std::move(req));
}
void UsbFastbootFunction::Send(::fuchsia_hardware_fastboot::wire::FastbootImplSendRequest* request,
SendCompleter::Sync& completer) {
if (!configured_) {
completer.ReplyError(ZX_ERR_UNAVAILABLE);
return;
}
std::lock_guard<std::mutex> _(send_lock_);
if (send_completer_.has_value()) {
// A previous call to Send() is pending
completer.ReplyError(ZX_ERR_UNAVAILABLE);
return;
}
if (zx_status_t status = request->data.get_prop_content_size(&total_to_send_); status != ZX_OK) {
completer.ReplyError(status);
return;
}
if (total_to_send_ == 0) {
completer.ReplyError(ZX_ERR_INVALID_ARGS);
return;
}
if (zx_status_t status = send_vmo_.Map(std::move(request->data), 0, total_to_send_,
ZX_VM_PERM_READ | ZX_VM_PERM_WRITE);
status != ZX_OK) {
zxlogf(ERROR, "Failed to map vmo %d", status);
completer.ReplyError(status);
return;
}
auto req = bulk_in_ep_.GetRequest();
ZX_ASSERT(req);
sent_size_ = 0;
send_completer_ = completer.ToAsync();
QueueTx(std::move(*req));
}
void UsbFastbootFunction::CleanUpRx(zx_status_t status, usb::FidlRequest req) {
bulk_out_ep_.PutRequest(std::move(req));
if (status == ZX_OK) {
receive_completer_->ReplySuccess(receive_vmo_.Release());
} else {
receive_vmo_.Reset();
receive_completer_->ReplyError(status);
}
receive_completer_.reset();
}
void UsbFastbootFunction::QueueRx(usb::FidlRequest req) {
ZX_ASSERT(req->data()->size() == 1);
req.reset_buffers(bulk_out_ep_.GetMapped());
req->data()->at(0).size(CalculateRxHeaderLength(requested_size_ - received_size_));
if (auto status = req.CacheFlushInvalidate(bulk_out_ep_.GetMapped()); status != ZX_OK) {
ZX_PANIC("Cache flush and invalidate failed %d", status);
}
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()) {
ZX_PANIC("Failed to QueueRequests %s", result.error_value().FormatDescription().c_str());
}
}
void UsbFastbootFunction::RxComplete(fuchsia_hardware_usb_endpoint::Completion completion) {
std::lock_guard<std::mutex> _(receive_lock_);
usb::FidlRequest req{std::move(completion.request().value())};
zx_status_t status = *completion.status();
if (status != ZX_OK) {
zxlogf(ERROR, "rx_completion error: %s", zx_status_get_string(status));
CleanUpRx(status, std::move(req));
return;
}
// 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");
CleanUpRx(ZX_ERR_INTERNAL, std::move(req));
return;
}
if (auto status = req.CacheFlushInvalidate(bulk_out_ep_.GetMapped()); status != ZX_OK) {
ZX_PANIC("Cache flush and invalidate failed %d", status);
}
const uint8_t* data = reinterpret_cast<const uint8_t*>(*addr);
memcpy(static_cast<uint8_t*>(receive_vmo_.start()) + received_size_, data,
*completion.transfer_size());
received_size_ += *completion.transfer_size();
if (received_size_ >= requested_size_) {
zx_status_t status = receive_vmo_.vmo().set_prop_content_size(received_size_);
if (status != ZX_OK) {
zxlogf(ERROR, "Failed to set content size %d", status);
}
CleanUpRx(status, std::move(req));
return;
}
QueueRx(std::move(req));
}
void UsbFastbootFunction::Receive(
::fuchsia_hardware_fastboot::wire::FastbootImplReceiveRequest* request,
ReceiveCompleter::Sync& completer) {
if (!configured_) {
completer.ReplyError(ZX_ERR_UNAVAILABLE);
return;
}
std::lock_guard<std::mutex> _(receive_lock_);
if (receive_completer_.has_value()) {
// A previous call to Receive() is pending
completer.ReplyError(ZX_ERR_UNAVAILABLE);
return;
}
received_size_ = 0;
// Minimum set to 1 so that round up works correctly.
requested_size_ = std::max(uint64_t{1}, request->requested);
// Create vmo for receiving data. Roundup by `kBulkMaxPacketSize` since USB transmission is in
// the unit of packet.
zx_status_t status = receive_vmo_.CreateAndMap(ZX_ROUNDUP(requested_size_, kBulkMaxPacketSize),
"usb fastboot receive");
if (status != ZX_OK) {
zxlogf(ERROR, "Failed to create vmo %d.", status);
completer.ReplyError(status);
return;
}
auto req = bulk_out_ep_.GetRequest();
ZX_ASSERT(req);
receive_completer_ = completer.ToAsync();
QueueRx(std::move(*req));
}
size_t UsbFastbootFunction::UsbFunctionInterfaceGetDescriptorsSize() {
return sizeof(descriptors_);
}
void UsbFastbootFunction::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 UsbFastbootFunction::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 UsbFastbootFunction::ConfigureEndpoints(bool enable) {
zx_status_t status;
if (enable) {
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);
return status;
}
configured_ = true;
} 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);
return status;
}
configured_ = false;
}
return ZX_OK;
}
zx_status_t UsbFastbootFunction::UsbFunctionInterfaceSetConfigured(bool configured,
usb_speed_t speed) {
zxlogf(INFO, "configured? - %d speed - %d.", configured, speed);
return ConfigureEndpoints(configured);
}
zx_status_t UsbFastbootFunction::UsbFunctionInterfaceSetInterface(uint8_t interface,
uint8_t alt_setting) {
zxlogf(INFO, "interface - %d alt_setting - %d.", interface, alt_setting);
if (interface != descriptors_.fastboot_intf.b_interface_number || alt_setting > 1) {
return ZX_ERR_INVALID_ARGS;
}
return ConfigureEndpoints(alt_setting);
}
zx::result<> UsbFastbootFunction::Start() {
std::lock_guard<std::mutex> _guard_receive(receive_lock_);
std::lock_guard<std::mutex> _guard_send(send_lock_);
zx::result<ddk::UsbFunctionProtocolClient> function =
compat::ConnectBanjo<ddk::UsbFunctionProtocolClient>(incoming());
if (function.is_error()) {
zxlogf(ERROR, "Failed to connect function %s", function.status_string());
return function.take_error();
}
function_ = *function;
auto client = incoming()->Connect<fuchsia_hardware_usb_function::UsbFunctionService::Device>();
auto status = function_.AllocInterface(&descriptors_.fastboot_intf.b_interface_number);
if (status != ZX_OK) {
zxlogf(ERROR, "Fastboot interface alloc failed - %d.", status);
return zx::error(status);
}
status = function_.AllocInterface(&descriptors_.placehodler_intf.b_interface_number);
if (status != ZX_OK) {
zxlogf(ERROR, "Placeholder interface alloc failed - %d.", status);
return zx::error(status);
}
status = function_.AllocEp(USB_DIR_OUT, &descriptors_.bulk_out_ep.b_endpoint_address);
if (status != ZX_OK) {
zxlogf(ERROR, "Bulk out endpoint alloc failed - %d.", status);
return zx::error(status);
}
status = function_.AllocEp(USB_DIR_IN, &descriptors_.bulk_in_ep.b_endpoint_address);
if (status != ZX_OK) {
zxlogf(ERROR, "Builk in endpoint alloc failed - %d.", status);
return zx::error(status);
}
auto dispatcher =
fdf::SynchronizedDispatcher::Create({}, "fastboot-ep-dispatcher", [](fdf_dispatcher_t*) {});
if (dispatcher.is_error()) {
zxlogf(ERROR, "[bug] fdf::SynchronizedDispatcher::Create(): %s", dispatcher.status_string());
return dispatcher.take_error();
}
dispatcher_ = std::move(dispatcher.value());
// Allocates a bulk out usb request.
status = bulk_out_ep_.Init(descriptors_.bulk_out_ep.b_endpoint_address, *client,
dispatcher_.async_dispatcher());
if (status != ZX_OK) {
zxlogf(ERROR, "[bug] bulk_out_ep_.Init(): %s", zx_status_get_string(status));
return zx::error(status);
}
// Allocates a bulk in usb request.
status = bulk_in_ep_.Init(descriptors_.bulk_in_ep.b_endpoint_address, *client,
dispatcher_.async_dispatcher());
if (status != ZX_OK) {
zxlogf(ERROR, "[bug] bulk_in_ep_.Init(): %s", zx_status_get_string(status));
return zx::error(status);
}
// Allocates RX request
if (auto actual = bulk_out_ep_.AddRequests(1, kBulkReqSize,
fuchsia_hardware_usb_request::Buffer::Tag::kVmoId);
actual != 1) {
zxlogf(ERROR, "Failed to allocate RX requests");
return zx::error(ZX_ERR_INTERNAL);
}
// Allocates TX request
if (auto actual = bulk_in_ep_.AddRequests(1, kBulkReqSize,
fuchsia_hardware_usb_request::Buffer::Tag::kVmoId);
actual != 1) {
zxlogf(ERROR, "Failed to allocate TX requests");
return zx::error(ZX_ERR_INTERNAL);
}
auto serve_result = outgoing()->AddService<fuchsia_hardware_fastboot::Service>(
fuchsia_hardware_fastboot::Service::InstanceHandler({
.fastboot = 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();
}
status = function_.SetInterface(this, &usb_function_interface_protocol_ops_);
if (status != ZX_OK) {
ZX_PANIC("SetInterface failed %s", zx_status_get_string(status));
}
is_bound.Set(true);
return zx::ok();
}
void UsbFastbootFunction::PrepareStop(fdf::PrepareStopCompleter completer) { completer(zx::ok()); }
} // namespace usb_fastboot_function
FUCHSIA_DRIVER_EXPORT(usb_fastboot_function::UsbFastbootFunction);