blob: 80e1a52a3b8a5db29a31bbfd4bbeb3bca4df780f [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/ddk/binding_driver.h>
#include <lib/ddk/debug.h>
#include <lib/zircon-internal/align.h>
namespace usb_fastboot_function {
namespace {
size_t CalculateRxHeaderLength(size_t data_size) {
// By default `usb_request_t.header.length` takes the value of `kBulkReqSize` when it is
// allocated with `usb::Request<>::Alloc(...)`. It seems to affect the amount of data to be
// received from the host. One phenomena I observe is that when 1) the host sends less than
// `usb_request_t.header.length` amount of data in a packet and 2) the data size is a multiple of
// kBulkMaxPacketSize, the packet will appear to be "delayed" and combined with the next packet.
// For example, suppose usb_request_t.header.length=2048, kBulkMaxPacketSize=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. This causes hangs during download. Thus we
// adjust the value of `usb_request_t.header.length` based on the expected amount of data to
// receive. usb_request_t.header.length 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::Request<> req) {
send_vmo_.Reset();
bulk_in_reqs_.Add(std::move(req));
if (status == ZX_OK) {
send_completer_->ReplySuccess();
} else {
send_completer_->ReplyError(status);
}
send_completer_.reset();
}
zx_status_t UsbFastbootFunction::PrepareSendRequest(usb::Request<>& request) {
size_t to_send = std::min(static_cast<size_t>(kBulkReqSize), total_to_send_ - sent_size_);
request.request()->header.length = to_send;
ssize_t bytes_copied =
request.CopyTo(static_cast<uint8_t*>(send_vmo_.start()) + sent_size_, to_send, 0);
if (bytes_copied < 0) {
zxlogf(ERROR, "Failed to copy data into send req %zd.", bytes_copied);
return ZX_ERR_INTERNAL;
}
return ZX_OK;
}
void UsbFastbootFunction::TxComplete(usb_request_t* req) {
std::lock_guard<std::mutex> _(send_lock_);
usb::Request<> request(req, parent_request_size_);
// Following the same practice as cdc eth and adb, do not queue request if error is
// ZX_ERR_IO_NOT_PRESENT.
if (req->response.status == ZX_ERR_IO_NOT_PRESENT) {
CleanUpTx(req->response.status, std::move(request));
return;
}
// If succeeds, update `sent_size_`, otherwise keep it the same to retry.
if (req->response.status == ZX_OK) {
sent_size_ += req->header.length;
if (sent_size_ == total_to_send_) {
CleanUpTx(ZX_OK, std::move(request));
return;
}
}
if (zx_status_t status = PrepareSendRequest(request); status != ZX_OK) {
CleanUpTx(ZX_ERR_INTERNAL, std::move(request));
return;
}
function_.RequestQueue(request.take(), &tx_complete_);
}
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), total_to_send_);
status != ZX_OK) {
zxlogf(ERROR, "Failed to map vmo %d", status);
completer.ReplyError(status);
return;
}
std::optional<usb::Request<>> tx_request = bulk_in_reqs_.Get(usb_request_size_);
if (!tx_request) {
zxlogf(ERROR, "Failed to get tx request");
send_vmo_.Reset();
completer.ReplyError(ZX_ERR_UNAVAILABLE);
return;
}
sent_size_ = 0;
send_completer_ = completer.ToAsync();
if (zx_status_t status = PrepareSendRequest(*tx_request); status != ZX_OK) {
CleanUpTx(status, std::move(*tx_request));
return;
}
function_.RequestQueue(tx_request->take(), &tx_complete_);
}
void UsbFastbootFunction::CleanUpRx(zx_status_t status, usb::Request<> req) {
bulk_out_reqs_.Add(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::RxComplete(usb_request_t* req) {
std::lock_guard<std::mutex> _(receive_lock_);
usb::Request<> request(req, parent_request_size_);
// Following the same practice as cdc eth and adb driver, do not queue request if error is
// ZX_ERR_IO_NOT_PRESENT.
if (req->response.status == ZX_ERR_IO_NOT_PRESENT) {
zxlogf(INFO, "IO not present");
CleanUpRx(req->response.status, std::move(request));
return;
}
if (req->response.status != ZX_OK) {
// For all other failures, simply re-queue the request.
function_.RequestQueue(request.take(), &rx_complete_);
return;
}
// Extracts data from the request
void* data;
if (zx_status_t status = request.Mmap(&data); status != ZX_OK) {
zxlogf(ERROR, "Failed to map request data %d", status);
CleanUpRx(status, std::move(request));
return;
}
memcpy(static_cast<uint8_t*>(receive_vmo_.start()) + received_size_, data, req->response.actual);
received_size_ += req->response.actual;
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(request));
return;
}
request.request()->header.length = CalculateRxHeaderLength(requested_size_ - received_size_);
function_.RequestQueue(request.take(), &rx_complete_);
}
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;
}
std::optional<usb::Request<>> rx_request = bulk_out_reqs_.Get(usb_request_size_);
if (!rx_request) {
zxlogf(ERROR, "Failed to get rx request");
receive_vmo_.Reset();
completer.ReplyError(ZX_ERR_UNAVAILABLE);
return;
}
receive_completer_ = completer.ToAsync();
rx_request->request()->header.length = CalculateRxHeaderLength(requested_size_);
function_.RequestQueue(rx_request->take(), &rx_complete_);
}
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_status_t UsbFastbootFunction::Bind(void* ctx, zx_device_t* dev) {
auto driver = std::make_unique<UsbFastbootFunction>(dev);
zx_status_t status = driver->Bind();
if (status != ZX_OK) {
return status;
}
// The DriverFramework now owns driver.
[[maybe_unused]] auto ptr = driver.release();
return ZX_OK;
}
zx_status_t UsbFastbootFunction::Bind() {
is_bound.Set(true);
parent_request_size_ = function_.GetRequestSize();
usb_request_size_ = usb::Request<>::RequestSize(parent_request_size_);
auto status = function_.AllocInterface(&descriptors_.fastboot_intf.b_interface_number);
if (status != ZX_OK) {
zxlogf(ERROR, "Fastboot interface alloc failed - %d.", status);
return status;
}
status = function_.AllocInterface(&descriptors_.placehodler_intf.b_interface_number);
if (status != ZX_OK) {
zxlogf(ERROR, "Placeholder interface alloc failed - %d.", status);
return 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 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 status;
}
// Allocate bulk out usb requests.
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;
}
{
std::lock_guard<std::mutex> _(receive_lock_);
bulk_out_reqs_.Add(*std::move(request));
}
// Allocate bulk in usb requests.
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;
}
{
std::lock_guard<std::mutex> _(send_lock_);
bulk_in_reqs_.Add(*std::move(request));
}
function_.SetInterface(this, &usb_function_interface_protocol_ops_);
return DdkAdd(ddk::DeviceAddArgs("usb_fastboot_function")
.set_flags(DEVICE_ADD_NON_BINDABLE)
.set_inspect_vmo(inspect_.DuplicateVmo()));
}
void UsbFastbootFunction::DdkInit(ddk::InitTxn txn) { txn.Reply(ZX_OK); }
void UsbFastbootFunction::DdkRelease() { delete this; }
static zx_driver_ops_t usb_fastboot_function_driver_ops = []() -> zx_driver_ops_t {
zx_driver_ops_t ops = {};
ops.version = DRIVER_OPS_VERSION;
ops.bind = UsbFastbootFunction::Bind;
return ops;
}();
} // namespace usb_fastboot_function
ZIRCON_DRIVER(UsbFastbootFunction, usb_fastboot_function::usb_fastboot_function_driver_ops,
"zircon", "0.1");