blob: 7c14950438c151ef0162aa7220f1eb550524d01f [file] [log] [blame] [edit]
// 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 kPacketSize. Thus we adjust it to be the smaller between kBulkRequestSize and the round
// up value of `data_size' w.r.t kPacketSize. For example, if we are expecting exactly 512
// bytes of data, the following will give 512 exactly.
return std::min(kBulkRequestSize, ZX_ROUNDUP(data_size, kPacketSize));
}
} // namespace
void UsbFastbootFunction::CleanUpTx(zx_status_t status, usb::FidlRequest req) {
bulk_in_ep_.PutRequest(std::move(req));
if (send_completer_.has_value()) {
send_vmo_.Reset();
if (status == ZX_OK) {
send_completer_->ReplySuccess();
} else {
send_completer_->ReplyError(status);
}
send_completer_.reset();
}
}
void UsbFastbootFunction::QueueTx() {
std::vector<fuchsia_hardware_usb_request::Request> requests;
requests.reserve(kMaxRequestCount);
while (queued_tx_size_ < total_to_send_) {
auto req = bulk_in_ep_.GetRequest();
if (!req) {
break;
}
const size_t tx_size = std::min(kBulkRequestSize, total_to_send_ - queued_tx_size_);
auto actual = req->CopyTo(0, static_cast<uint8_t*>(send_vmo_.start()) + queued_tx_size_,
tx_size, 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);
}
requests.emplace_back(req->take_request());
queued_tx_size_ += tx_size;
}
if (!requests.empty()) {
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::TxBatchComplete(
std::vector<fuchsia_hardware_usb_endpoint::Completion> completions) {
for (auto& completion : completions) {
TxComplete(std::move(completion));
}
if (send_completer_.has_value() && queued_tx_size_ < total_to_send_) {
QueueTx();
}
}
void UsbFastbootFunction::TxComplete(fuchsia_hardware_usb_endpoint::Completion completion) {
usb::FidlRequest req{std::move(completion.request().value())};
if (!send_completer_.has_value()) {
bulk_in_ep_.PutRequest(std::move(req));
return;
}
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;
}
bulk_in_ep_.PutRequest(std::move(req));
}
void UsbFastbootFunction::Send(::fuchsia_hardware_fastboot::wire::FastbootImplSendRequest* request,
SendCompleter::Sync& completer) {
if (!configured_) {
completer.ReplyError(ZX_ERR_UNAVAILABLE);
return;
}
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) {
total_to_send_ = 0;
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;
}
sent_size_ = 0;
queued_tx_size_ = 0;
send_completer_ = completer.ToAsync();
QueueTx();
}
void UsbFastbootFunction::CleanUpRx(zx_status_t status, usb::FidlRequest req) {
bulk_out_ep_.PutRequest(std::move(req));
if (receive_completer_.has_value()) {
if (status == ZX_OK) {
receive_completer_->ReplySuccess(receive_vmo_.Release());
} else {
receive_vmo_.Reset();
receive_completer_->ReplyError(status);
}
receive_completer_.reset();
}
}
void UsbFastbootFunction::QueueRx() {
std::vector<fuchsia_hardware_usb_request::Request> requests;
requests.reserve(kMaxRequestCount);
while (queued_rx_size_ < requested_size_) {
auto req = bulk_out_ep_.GetRequest();
if (!req) {
break;
}
ZX_ASSERT((*req)->data()->size() == 1);
req->reset_buffers(bulk_out_ep_.GetMapped());
const size_t rx_size = CalculateRxHeaderLength(requested_size_ - queued_rx_size_);
(*req)->data()->at(0).size(rx_size); // Each request has one buffer.
if (auto status = req->CacheFlushInvalidate(bulk_out_ep_.GetMapped()); status != ZX_OK) {
ZX_PANIC("Cache flush and invalidate failed %d", status);
}
requests.emplace_back(req->take_request());
queued_rx_size_ += rx_size;
}
if (!requests.empty()) {
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::RxBatchComplete(
std::vector<fuchsia_hardware_usb_endpoint::Completion> completions) {
for (auto& completion : completions) {
RxComplete(std::move(completion));
}
if (receive_completer_.has_value() && queued_rx_size_ < requested_size_) {
QueueRx();
}
}
void UsbFastbootFunction::RxComplete(fuchsia_hardware_usb_endpoint::Completion completion) {
usb::FidlRequest req{std::move(completion.request().value())};
if (!receive_completer_.has_value()) {
bulk_out_ep_.PutRequest(std::move(req));
return;
}
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;
}
bulk_out_ep_.PutRequest(std::move(req));
}
void UsbFastbootFunction::Receive(
::fuchsia_hardware_fastboot::wire::FastbootImplReceiveRequest* request,
ReceiveCompleter::Sync& completer) {
if (!configured_) {
completer.ReplyError(ZX_ERR_UNAVAILABLE);
return;
}
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_, kPacketSize), "usb fastboot receive");
if (status != ZX_OK) {
zxlogf(ERROR, "Failed to create vmo %d.", status);
completer.ReplyError(status);
return;
}
queued_rx_size_ = 0;
receive_completer_ = completer.ToAsync();
QueueRx();
}
void UsbFastbootFunction::Control(ControlRequest& request, ControlCompleter::Sync& completer) {
completer.Reply(zx::ok(std::vector<uint8_t>{}));
}
zx_status_t UsbFastbootFunction::ConfigureEndpoints(bool enable) {
if (enable) {
for (const auto* ep_desc : {&descriptors_.bulk_out_ep, &descriptors_.bulk_in_ep}) {
fuchsia_hardware_usb_function::EndpointConfiguration ep_config;
fuchsia_hardware_usb_function::EndpointDescriptor desc;
desc.bm_attributes(ep_desc->bm_attributes);
desc.w_max_packet_size(le16toh(ep_desc->w_max_packet_size));
desc.b_interval(ep_desc->b_interval);
ep_config.descriptor(std::move(desc));
fidl::Result result =
function_->ConfigureEndpoint({ep_desc->b_endpoint_address, std::move(ep_config)});
if (result.is_error()) {
fdf::error("ConfigureEndpoint failed: {}", result.error_value().FormatDescription());
return result.error_value().is_framework_error()
? result.error_value().framework_error().status()
: result.error_value().domain_error();
}
}
configured_ = true;
} else {
for (const uint8_t ep_addr : {bulk_out_addr(), bulk_in_addr()}) {
fidl::Result result = function_->DisableEndpoint({ep_addr});
if (!result.is_ok()) {
fdf::error("Failed to disable endpoint {}: {}", ep_addr,
result.error_value().FormatDescription());
return result.error_value().is_framework_error()
? result.error_value().framework_error().status()
: result.error_value().domain_error();
}
}
configured_ = false;
}
return ZX_OK;
}
void UsbFastbootFunction::SetConfigured(SetConfiguredRequest& request,
SetConfiguredCompleter::Sync& completer) {
bool configured = request.configured();
fuchsia_hardware_usb_descriptor::UsbSpeed speed = request.speed();
fdf::info("configured? - {} speed - {}.", configured, static_cast<uint32_t>(speed));
completer.Reply(zx::make_result(ConfigureEndpoints(configured)));
}
void UsbFastbootFunction::SetInterface(SetInterfaceRequest& request,
SetInterfaceCompleter::Sync& completer) {
uint8_t interface = request.interface();
uint8_t alt_setting = request.alt_setting();
fdf::info("interface - {} alt_setting - {}.", interface, alt_setting);
if (interface != descriptors_.fastboot_intf.b_interface_number || alt_setting > 1) {
completer.Reply(zx::error(ZX_ERR_INVALID_ARGS));
return;
}
completer.Reply(zx::make_result(ConfigureEndpoints(alt_setting)));
}
void UsbFastbootFunction::handle_unknown_method(
fidl::UnknownMethodMetadata<fuchsia_hardware_usb_function::UsbFunctionInterface> metadata,
fidl::UnknownMethodCompleter::Sync& completer) {
fdf::error("Unknown method {}", metadata.method_ordinal);
}
zx::result<> UsbFastbootFunction::Start() {
auto client = incoming()->Connect<fuchsia_hardware_usb_function::UsbFunctionService::Device>();
if (client.is_error()) {
fdf::error("Failed to connect to UsbFunctionService: {}", client.status_string());
return client.take_error();
}
function_.Bind(std::move(*client));
zx::result bulk_out_endpoints = fidl::CreateEndpoints<fuchsia_hardware_usb_endpoint::Endpoint>();
if (bulk_out_endpoints.is_error()) {
return bulk_out_endpoints.take_error();
}
zx::result bulk_in_endpoints = fidl::CreateEndpoints<fuchsia_hardware_usb_endpoint::Endpoint>();
if (bulk_in_endpoints.is_error()) {
return bulk_in_endpoints.take_error();
}
std::vector<fuchsia_hardware_usb_function::EndpointResource> resources;
fuchsia_hardware_usb_function::EndpointResource out_res;
out_res.direction(fuchsia_hardware_usb_function::EndpointDirection::kOut);
out_res.endpoint(std::move(bulk_out_endpoints->server));
resources.emplace_back(std::move(out_res));
fuchsia_hardware_usb_function::EndpointResource in_res;
in_res.direction(fuchsia_hardware_usb_function::EndpointDirection::kIn);
in_res.endpoint(std::move(bulk_in_endpoints->server));
resources.emplace_back(std::move(in_res));
fidl::Request<fuchsia_hardware_usb_function::UsbFunction::AllocResources> alloc_req;
alloc_req.interface_count(2);
alloc_req.endpoints(std::move(resources));
fidl::Result alloc_result = function_->AllocResources(std::move(alloc_req));
if (alloc_result.is_error()) {
fdf::error("AllocResources failed: {}", alloc_result.error_value().FormatDescription());
return zx::error(alloc_result.error_value().is_framework_error()
? alloc_result.error_value().framework_error().status()
: alloc_result.error_value().domain_error());
}
auto& response = alloc_result.value();
descriptors_.fastboot_intf.b_interface_number = response.interface_nums()[0];
descriptors_.placehodler_intf.b_interface_number = response.interface_nums()[1];
descriptors_.bulk_out_ep.b_endpoint_address = response.endpoint_addrs()[0];
descriptors_.bulk_in_ep.b_endpoint_address = response.endpoint_addrs()[1];
zx_status_t status = bulk_out_ep_.Init(std::move(bulk_out_endpoints->client), dispatcher());
if (status != ZX_OK) {
fdf::error("bulk_out_ep_.Init failed: {}", zx_status_get_string(status));
return zx::error(status);
}
status = bulk_in_ep_.Init(std::move(bulk_in_endpoints->client), dispatcher());
if (status != ZX_OK) {
fdf::error("bulk_in_ep_.Init failed: {}", zx_status_get_string(status));
return zx::error(status);
}
if (bulk_out_ep_.AddRequests(kMaxRequestCount, kBulkRequestSize,
fuchsia_hardware_usb_request::Buffer::Tag::kVmoId) !=
kMaxRequestCount) {
fdf::error("Failed to allocate RX requests");
return zx::error(ZX_ERR_INTERNAL);
}
if (bulk_in_ep_.AddRequests(kMaxRequestCount, kBulkRequestSize,
fuchsia_hardware_usb_request::Buffer::Tag::kVmoId) !=
kMaxRequestCount) {
fdf::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()) {
fdf::error("Failed to add Device service {}", serve_result.status_string());
return serve_result.take_error();
}
zx::result iface_endpoints =
fidl::CreateEndpoints<fuchsia_hardware_usb_function::UsbFunctionInterface>();
if (iface_endpoints.is_error()) {
return iface_endpoints.take_error();
}
fidl::BindServer(dispatcher(), std::move(iface_endpoints->server), this);
std::vector<uint8_t> descriptors_buffer(sizeof(descriptors_));
memcpy(descriptors_buffer.data(), &descriptors_, sizeof(descriptors_));
fidl::Request<fuchsia_hardware_usb_function::UsbFunction::Configure> config_req;
config_req.configuration(std::move(descriptors_buffer));
config_req.iface(std::move(iface_endpoints->client));
fidl::Result config_res = function_->Configure(std::move(config_req));
if (config_res.is_error()) {
fdf::error("Configure failed: {}", config_res.error_value().FormatDescription());
return zx::error(ZX_ERR_INTERNAL);
}
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);