blob: 45c2f3295a24e1077892d8b54bb48290e888db5a [file] [log] [blame] [edit]
// Copyright 2019 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/devices/usb/drivers/usb-peripheral/usb-function.h"
#include <fidl/fuchsia.hardware.usb.endpoint/cpp/wire.h>
#include <lib/ddk/debug.h>
#include <lib/ddk/metadata.h>
#include <bind/fuchsia/cpp/bind.h>
#include <bind/fuchsia/usb/cpp/bind.h>
#include <fbl/auto_lock.h>
#include "src/devices/usb/drivers/usb-peripheral/usb-peripheral.h"
namespace usb_peripheral {
namespace fdescriptor = fuchsia_hardware_usb_descriptor;
zx_status_t UsbFunction::AddDevice(const std::string& name) {
if (dev_added_) {
return ZX_ERR_ALREADY_BOUND;
}
auto& desc = GetFunctionDescriptor();
zx_device_str_prop_t props[] = {
ddk::MakeStrProperty(bind_fuchsia::PROTOCOL, bind_fuchsia_usb::BIND_PROTOCOL_FUNCTION),
ddk::MakeStrProperty(bind_fuchsia::USB_CLASS, static_cast<uint32_t>(desc.interface_class)),
ddk::MakeStrProperty(bind_fuchsia::USB_SUBCLASS,
static_cast<uint32_t>(desc.interface_subclass)),
ddk::MakeStrProperty(bind_fuchsia::USB_PROTOCOL,
static_cast<uint32_t>(desc.interface_protocol)),
ddk::MakeStrProperty(bind_fuchsia::USB_VID,
static_cast<uint32_t>(peripheral_->device_desc().id_vendor)),
ddk::MakeStrProperty(bind_fuchsia::USB_PID,
static_cast<uint32_t>(peripheral_->device_desc().id_product)),
};
auto endpoints = fidl::CreateEndpoints<fuchsia_io::Directory>();
if (endpoints.is_error()) {
return endpoints.status_value();
}
zx_status_t status = AddService(std::move(endpoints->server));
if (status != ZX_OK) {
zxlogf(ERROR, "Could not add service %s", zx_status_get_string(status));
return status;
}
std::array offers = {
fuchsia_hardware_usb_function::UsbFunctionService::Name,
};
status = DdkAdd(ddk::DeviceAddArgs(name.c_str())
.set_str_props(props)
.forward_metadata(peripheral_->parent(), DEVICE_METADATA_MAC_ADDRESS)
.forward_metadata(peripheral_->parent(), DEVICE_METADATA_SERIAL_NUMBER)
.set_fidl_service_offers(offers)
.set_outgoing_dir(endpoints->client.TakeChannel()));
if (status != ZX_OK) {
zxlogf(ERROR, "usb_dev_bind_functions add_device failed %s", zx_status_get_string(status));
return status;
}
dev_added_ = true;
// Hold a reference while devmgr has a pointer to the function.
AddRef();
return ZX_OK;
}
void UsbFunction::DdkRelease() {
peripheral_->FunctionCleared();
// Release the reference now that devmgr no longer has a pointer to the function.
if (Release()) {
delete this;
}
}
// UsbFunctionProtocol implementation.
zx_status_t UsbFunction::UsbFunctionSetInterface(
const usb_function_interface_protocol_t* function_intf) {
auto func_intf = ddk::UsbFunctionInterfaceProtocolClient(function_intf);
if (!func_intf.is_valid()) {
bool was_valid = function_intf_.is_valid();
function_intf_.clear();
zxlogf(INFO, "Taking peripheral device offline until ready");
return was_valid ? peripheral_->DeviceStateChanged() : ZX_OK;
}
if (function_intf_.is_valid()) {
zxlogf(ERROR, "Function interface already bound");
return ZX_ERR_ALREADY_BOUND;
}
function_intf_ = func_intf;
size_t length = function_intf_.GetDescriptorsSize();
fbl::AllocChecker ac;
auto* descriptors = new (&ac) uint8_t[length];
if (!ac.check()) {
zxlogf(ERROR, "UsbFunctionSetInterface failed due to no memory.");
return ZX_ERR_NO_MEMORY;
}
size_t actual;
function_intf_.GetDescriptors(descriptors, length, &actual);
if (actual != length) {
zxlogf(ERROR, "UsbFunctionInterfaceClient::GetDescriptors() failed");
delete[] descriptors;
return ZX_ERR_INTERNAL;
}
auto status = peripheral_->ValidateFunction(fbl::RefPtr<UsbFunction>(this), descriptors, length,
&num_interfaces_);
if (status != ZX_OK) {
zxlogf(ERROR, "UsbFunctionInterfaceClient::ValidateFunction() failed - %s",
zx_status_get_string(status));
delete[] descriptors;
return status;
}
descriptors_.reset(descriptors, length);
return peripheral_->FunctionRegistered();
}
zx_status_t UsbFunction::UsbFunctionCancelAll(uint8_t ep_address) {
return peripheral_->UsbDciCancelAll(ep_address);
}
zx_status_t UsbFunction::UsbFunctionAllocInterface(uint8_t* out_intf_num) {
return peripheral_->AllocInterface(fbl::RefPtr<UsbFunction>(this), out_intf_num);
}
zx_status_t UsbFunction::UsbFunctionAllocEp(uint8_t direction, uint8_t* out_address) {
return peripheral_->AllocEndpoint(fbl::RefPtr<UsbFunction>(this), direction, out_address);
}
zx_status_t UsbFunction::UsbFunctionConfigEp(const usb_endpoint_descriptor_t* ep_desc,
const usb_ss_ep_comp_descriptor_t* ss_comp_desc) {
fidl::Arena arena;
fdescriptor::wire::UsbEndpointDescriptor fep_desc;
fep_desc.b_length = ep_desc->b_length;
fep_desc.b_descriptor_type = ep_desc->b_descriptor_type;
fep_desc.b_endpoint_address = ep_desc->b_endpoint_address;
fep_desc.bm_attributes = ep_desc->bm_attributes;
fep_desc.w_max_packet_size = ep_desc->w_max_packet_size;
fep_desc.b_interval = ep_desc->b_interval;
fdescriptor::wire::UsbSsEpCompDescriptor fss_comp_desc;
if (ss_comp_desc != nullptr) { // Only applies to 3.x devices.
fss_comp_desc.b_length = ss_comp_desc->b_length;
fss_comp_desc.b_descriptor_type = ss_comp_desc->b_descriptor_type;
fss_comp_desc.b_max_burst = ss_comp_desc->b_max_burst;
fss_comp_desc.bm_attributes = ss_comp_desc->bm_attributes;
fss_comp_desc.w_bytes_per_interval = ss_comp_desc->w_bytes_per_interval;
}
if (peripheral_->dci_new_valid()) {
auto result = peripheral_->dci_new().buffer(arena)->ConfigureEndpoint(fep_desc, fss_comp_desc);
if (!result.ok()) {
zxlogf(DEBUG, "(framework) ConfigureEndpoint(): %s", result.status_string());
} else if (result->is_error() && result->error_value() == ZX_ERR_NOT_SUPPORTED) {
zxlogf(DEBUG, "ConfigureEndpoint(): %s", result.status_string());
} else if (result->is_error() && result->error_value() != ZX_ERR_NOT_SUPPORTED) {
return result->error_value();
} else {
return ZX_OK;
}
}
zxlogf(DEBUG, "could not ConfigureEndpoint() over FIDL, falling back to banjo");
return peripheral_->dci().ConfigEp(ep_desc, ss_comp_desc);
}
zx_status_t UsbFunction::UsbFunctionDisableEp(uint8_t address) {
fidl::Arena arena;
auto result = peripheral_->dci_new().buffer(arena)->DisableEndpoint(address);
if (!result.ok()) {
zxlogf(DEBUG, "(framework) DisableEndpoint(): %s", result.status_string());
} else if (result->is_error() && result->error_value() == ZX_ERR_NOT_SUPPORTED) {
zxlogf(DEBUG, "DisableEndpoint(): %s", result.status_string());
} else if (result->is_error() && result->error_value() != ZX_ERR_NOT_SUPPORTED) {
return result->error_value();
} else {
return ZX_OK;
}
zxlogf(DEBUG, "could not DisableEndpoint() over FIDL, falling back to banjo");
return peripheral_->dci().DisableEp(address);
}
zx_status_t UsbFunction::UsbFunctionAllocStringDesc(const char* str, uint8_t* out_index) {
return peripheral_->AllocStringDesc(str, out_index);
}
void UsbFunction::UsbFunctionRequestQueue(usb_request_t* usb_request,
const usb_request_complete_callback_t* complete_cb) {
peripheral_->UsbPeripheralRequestQueue(usb_request, complete_cb);
}
zx_status_t UsbFunction::UsbFunctionEpSetStall(uint8_t ep_address) {
fidl::Arena arena;
auto result = peripheral_->dci_new().buffer(arena)->EndpointSetStall(ep_address);
if (!result.ok()) {
zxlogf(DEBUG, "(framework) EndpointSetStall(): %s", result.status_string());
} else if (result->is_error() && result->error_value() == ZX_ERR_NOT_SUPPORTED) {
zxlogf(DEBUG, "EndpointSetStall(): %s", result.status_string());
} else if (result->is_error() && result->error_value() != ZX_ERR_NOT_SUPPORTED) {
return result->error_value();
} else {
return ZX_OK;
}
zxlogf(DEBUG, "could not EndointSetStall() over FIDL, falling back to banjo");
return peripheral_->dci().EpSetStall(ep_address);
}
zx_status_t UsbFunction::UsbFunctionEpClearStall(uint8_t ep_address) {
fidl::Arena arena;
auto result = peripheral_->dci_new().buffer(arena)->EndpointClearStall(ep_address);
if (!result.ok()) {
zxlogf(DEBUG, "(framework) EndpointClearStall(): %s", result.status_string());
} else if (result->is_error() && result->error_value() == ZX_ERR_NOT_SUPPORTED) {
zxlogf(DEBUG, "EndpointClearStall(): %s", result.status_string());
} else if (result->is_error() && result->error_value() != ZX_ERR_NOT_SUPPORTED) {
return result->error_value();
} else {
return ZX_OK;
}
zxlogf(DEBUG, "could not EndointClearStall() over FIDL, falling back to banjo");
return peripheral_->dci().EpClearStall(ep_address);
}
size_t UsbFunction::UsbFunctionGetRequestSize() { return peripheral_->ParentRequestSize(); }
void UsbFunction::ConnectToEndpoint(ConnectToEndpointRequest& request,
ConnectToEndpointCompleter::Sync& completer) {
auto status = peripheral_->ConnectToEndpoint(request.ep_addr(), std::move(request.ep()));
if (status != ZX_OK) {
completer.Reply(fit::as_error(status));
return;
}
completer.Reply(fit::ok());
}
zx_status_t UsbFunction::SetConfigured(bool configured, usb_speed_t speed) {
if (function_intf_.is_valid()) {
return function_intf_.SetConfigured(configured, speed);
} else {
zxlogf(ERROR, "SetConfigured failed as the interface is invalid.");
return ZX_ERR_BAD_STATE;
}
}
zx_status_t UsbFunction::SetInterface(uint8_t interface, uint8_t alt_setting) {
if (function_intf_.is_valid()) {
return function_intf_.SetInterface(interface, alt_setting);
} else {
zxlogf(ERROR, "SetInterface failed as the interface is invalid.");
return ZX_ERR_BAD_STATE;
}
}
zx_status_t UsbFunction::Control(const usb_setup_t* setup, const void* write_buffer,
size_t write_size, void* read_buffer, size_t read_size,
size_t* out_read_actual) {
if (function_intf_.is_valid()) {
return function_intf_.Control(setup, reinterpret_cast<const uint8_t*>(write_buffer), write_size,
reinterpret_cast<uint8_t*>(read_buffer), read_size,
out_read_actual);
} else {
zxlogf(ERROR, "Control failed as the interface is invalid.");
return ZX_ERR_BAD_STATE;
}
}
} // namespace usb_peripheral