blob: 95840a4f360b478e4ab23e1320753804c150d732 [file] [log] [blame]
// Copyright 2017 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-peripheral.h"
#include <assert.h>
#include <fidl/fuchsia.hardware.usb.phy/cpp/fidl.h>
#include <fuchsia/hardware/usb/dci/c/banjo.h>
#include <fuchsia/hardware/usb/function/c/banjo.h>
#include <lib/driver/component/cpp/driver_export.h>
#include <lib/driver/metadata/cpp/metadata.h>
#include <lib/fit/defer.h>
#include <lib/stdcompat/span.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <threads.h>
#include <zircon/errors.h>
#include <zircon/listnode.h>
#include <zircon/types.h>
#include <algorithm>
#include <memory>
#include <string>
#include <vector>
#include <fbl/auto_lock.h>
#include <usb/cdc.h>
#include <usb/peripheral.h>
#include <usb/usb.h>
#include "src/devices/usb/drivers/usb-peripheral/config-parser.h"
#include "src/devices/usb/drivers/usb-peripheral/usb-function.h"
namespace usb_peripheral {
zx_status_t UsbPeripheral::UsbDciCancelAll(uint8_t ep_address) {
fidl::Arena arena;
auto result = dci_new_.buffer(arena)->CancelAll(ep_address);
if (!result.ok()) {
fdf::debug("Failed to send CancelAll request: {}", result.status_string());
} else if (result->is_error() && result->error_value() == ZX_ERR_NOT_SUPPORTED) {
fdf::debug("Failed to cancel all: {}", zx_status_get_string(result->error_value()));
} else if (result->is_error() && result->error_value() != ZX_ERR_NOT_SUPPORTED) {
return result->error_value();
} else {
return ZX_OK;
}
fdf::debug("could not CancelAll() over FIDL, falling back to banjo");
return dci_.CancelAll(ep_address);
}
void UsbPeripheral::RequestComplete(usb_request_t* req) {
fbl::AutoLock l(&pending_requests_lock_);
usb::BorrowedRequest<void> request(req, dci_.GetRequestSize());
pending_requests_.erase(&request);
l.release();
request.Complete(request.request()->response.status, request.request()->response.actual);
usb_monitor_.AddRecord(req);
}
void UsbPeripheral::UsbPeripheralRequestQueue(usb_request_t* usb_request,
const usb_request_complete_callback_t* complete_cb) {
if (shutting_down_) {
usb_request_complete(usb_request, ZX_ERR_IO_NOT_PRESENT, 0, complete_cb);
return;
}
fbl::AutoLock l(&pending_requests_lock_);
usb::BorrowedRequest<void> request(usb_request, *complete_cb, dci_.GetRequestSize());
[[maybe_unused]] usb_request_complete_callback_t completion;
completion.ctx = this;
completion.callback = [](void* ctx, usb_request_t* req) {
reinterpret_cast<UsbPeripheral*>(ctx)->RequestComplete(req);
};
pending_requests_.push_back(&request);
l.release();
usb_monitor_.AddRecord(usb_request);
dci_.RequestQueue(request.take(), &completion);
}
zx::result<> UsbPeripheral::Start() {
zx::result dci_fidl = incoming()->Connect<fuchsia_hardware_usb_dci::UsbDciService::Device>();
if (dci_fidl.is_error()) {
fdf::error("Failed to connect dci fidl protocol: {}", dci_fidl);
} else {
// Try to set DciIntf over FIDL. We don't do this for Banjo because SetInterface is used to
// indicate that all functions have been attached/un-attached. This is a separate method in
// FIDL, so we set DciIntf here for FIDL.
fidl::Arena arena;
auto client_end = intf_srv_.AddBinding();
auto result = fidl::WireCall(*dci_fidl)->SetInterface(std::move(client_end));
// DCI FIDL is not available. Return OK because we could be using Banjo DCI instead.
// In the future when we remove banjo. This should be an error.
if (!result.ok()) {
fdf::info("Failed to send SetInterface request: {}", result.status_string());
} else if (result->is_error()) {
fdf::error("Failed to set interface: {}", zx_status_get_string(result->error_value()));
} else {
dci_new_.Bind(std::move(*dci_fidl));
}
}
zx::result dci_banjo = compat::ConnectBanjo<ddk::UsbDciProtocolClient>(incoming());
if (dci_banjo.is_error()) {
fdf::info("Failed to connect to dci banjo protocol: {}", dci_banjo);
} else {
dci_ = dci_banjo.value();
parent_request_size_ = usb::BorrowedRequest<void>::RequestSize(dci_.GetRequestSize());
}
if (!dci_.is_valid() && !dci_new_.is_valid()) {
fdf::error("No banjo/FIDL UsbDci protocol served by parent");
return zx::error(ZX_ERR_NOT_SUPPORTED);
}
// Starting USB mode is determined from device metadata.
// We read initial value and store it in dev->usb_mode, but do not actually
// enable it until after all of our functions have bound.
zx::result metadata =
fdf_metadata::GetMetadataIfExists<fuchsia_hardware_usb_phy::Metadata>(incoming());
if (metadata.is_error()) {
fdf::error("Failed to get metadata: {}", metadata);
return metadata.take_error();
}
if (!metadata.value().has_value()) {
fbl::AutoLock lock(&lock_);
// Assume peripheral mode by default.
parent_usb_mode_ = USB_MODE_PERIPHERAL;
}
// Create child.
zx::result<fidl::ClientEnd<fuchsia_device_fs::Connector>> bind_devfs_connector_result =
devfs_connector_.Bind(dispatcher());
if (bind_devfs_connector_result.is_error()) {
fdf::error("Failed to bind devfs connector: {}", bind_devfs_connector_result);
return bind_devfs_connector_result.take_error();
}
fuchsia_driver_framework::DevfsAddArgs devfs_args{{
.connector = std::move(bind_devfs_connector_result).value(),
.connector_supports = fuchsia_device_fs::ConnectionType::kDevice,
}};
zx::result child = AddOwnedChild(kChildNodeName, devfs_args);
if (child.is_error()) {
fdf::error("Failed to add child: {}", child);
return child.take_error();
}
child_ = std::move(child.value());
// Advertise a service:
fuchsia_hardware_usb_peripheral::Service::InstanceHandler handler({
.device = bindings_.CreateHandler(this, dispatcher(), fidl::kIgnoreBindingClosure),
});
zx::result add_result =
outgoing()->AddService<fuchsia_hardware_usb_peripheral::Service>(std::move(handler));
if (add_result.is_error()) {
fdf::error("Failed to add service: {}", add_result);
return add_result.take_error();
}
auto config = take_config<usb_peripheral_config::Config>();
PeripheralConfigParser peripheral_config = {};
zx_status_t status = peripheral_config.AddFunctions(config.functions());
if (status != ZX_OK) {
fdf::error("Failed to add usb functions from structured config: {}",
zx_status_get_string(status));
return zx::error(status);
}
device_desc_.id_vendor = peripheral_config.vid();
device_desc_.id_product = peripheral_config.pid();
status = AllocStringDesc(peripheral_config.manufacturer(), &device_desc_.i_manufacturer);
if (status != ZX_OK) {
fdf::error("Failed to allocate manufacturer string descriptor: {}",
zx_status_get_string(status));
return zx::error(status);
}
status = AllocStringDesc(peripheral_config.product(), &device_desc_.i_product);
if (status != ZX_OK) {
fdf::error("Failed to allocate product string descriptor: {}", zx_status_get_string(status));
return zx::error(status);
}
zx::result serial = GetSerialNumber();
if (serial.is_error()) {
fdf::error("Failed to get serial number: {}", serial);
return serial.take_error();
}
status = AllocStringDesc(std::move(serial.value()), &device_desc_.i_serial_number);
if (status != ZX_OK) {
fdf::error("Failed to add serial number descriptor: {}", zx_status_get_string(status));
return zx::error(status);
}
status = SetDefaultConfig(peripheral_config.functions());
if (status != ZX_OK) {
fdf::error("Failed to set default config: {}", zx_status_get_string(status));
return zx::error(status);
}
usb_monitor_.Start();
return zx::ok();
}
zx::result<std::string> UsbPeripheral::GetSerialNumber() {
zx::result serial_number_result =
fdf_metadata::GetMetadataIfExists<fuchsia_boot_metadata::SerialNumberMetadata>(incoming());
if (serial_number_result.is_error()) {
fdf::error("Failed to get serial number metadata: {}", serial_number_result);
return serial_number_result.take_error();
}
// Return serial number from metadata if present.
if (serial_number_result.value().has_value()) {
auto& metadata = serial_number_result.value().value();
if (!metadata.serial_number().has_value()) {
fdf::error("Serial number metadata missing serial_number field");
return zx::error(ZX_ERR_INTERNAL);
}
return zx::ok(std::move(metadata.serial_number().value()));
}
// Use MAC address as the next option.
zx::result mac_address_result =
fdf_metadata::GetMetadataIfExists<fuchsia_boot_metadata::MacAddressMetadata>(incoming());
if (mac_address_result.is_error()) {
fdf::error("Failed to get MAC address metadata: {}", mac_address_result);
return mac_address_result.take_error();
}
if (mac_address_result.value().has_value()) {
const auto& metadata = mac_address_result.value().value();
if (!metadata.mac_address().has_value()) {
fdf::error("MAC address metadata missing mac_address field");
return zx::error(ZX_ERR_INTERNAL);
}
const auto& octets = metadata.mac_address().value().octets();
char buffer[13];
snprintf(buffer, sizeof(buffer), "%02X%02X%02X%02X%02X%02X", octets[0], octets[1], octets[2],
octets[3], octets[4], octets[5]);
return zx::ok(std::string{buffer});
}
fdf::info("Serial number/MAC address not found. Using generic (non-unique) serial number.\n");
return zx::ok(std::string{kDefaultSerialNumber});
}
zx_status_t UsbPeripheral::AllocStringDesc(std::string desc, uint8_t* out_index) {
fbl::AutoLock lock(&lock_);
if (strings_.size() >= MAX_STRINGS) {
fdf::error("String descriptor limit reached");
return ZX_ERR_NO_RESOURCES;
}
desc.resize(MAX_STRING_LENGTH);
strings_.push_back(std::move(desc));
// String indices are 1-based.
*out_index = static_cast<uint8_t>(strings_.size());
return ZX_OK;
}
zx_status_t UsbPeripheral::ValidateFunction(size_t function_index, void* descriptors, size_t length,
uint8_t* out_num_interfaces) {
auto* intf_desc = static_cast<usb_interface_descriptor_t*>(descriptors);
if (intf_desc->b_descriptor_type == USB_DT_INTERFACE) {
if (intf_desc->b_length != sizeof(usb_interface_descriptor_t)) {
fdf::error("{}: interface descriptor is invalid", __func__);
return ZX_ERR_INVALID_ARGS;
}
} else if (intf_desc->b_descriptor_type == USB_DT_INTERFACE_ASSOCIATION) {
if (intf_desc->b_length != sizeof(usb_interface_assoc_descriptor_t)) {
fdf::error("{}: interface association descriptor is invalid", __func__);
return ZX_ERR_INVALID_ARGS;
}
} else {
fdf::error("{}: first descriptor not an interface descriptor", __func__);
return ZX_ERR_INVALID_ARGS;
}
auto* end =
reinterpret_cast<const usb_descriptor_header_t*>(static_cast<uint8_t*>(descriptors) + length);
auto* header = reinterpret_cast<const usb_descriptor_header_t*>(descriptors);
while (header < end) {
if (header->b_descriptor_type == USB_DT_INTERFACE) {
auto* desc = reinterpret_cast<const usb_interface_descriptor_t*>(header);
auto& function = GetFunction(function_index);
ZX_ASSERT(function.configuration() < configurations_.size());
const auto& configuration = configurations_[function.configuration()];
const auto& interface_map = configuration.interface_map;
if (desc->b_interface_number >= std::size(interface_map) ||
interface_map[desc->b_interface_number] != function_index) {
fdf::error("Function does not exist at interface number {}", desc->b_interface_number);
return ZX_ERR_INVALID_ARGS;
}
if (desc->b_alternate_setting == 0) {
if (*out_num_interfaces == UINT8_MAX) {
return ZX_ERR_INVALID_ARGS;
}
(*out_num_interfaces)++;
}
} else if (header->b_descriptor_type == USB_DT_ENDPOINT) {
auto* desc = reinterpret_cast<const usb_endpoint_descriptor_t*>(header);
auto index = EpAddressToIndex(desc->b_endpoint_address);
if (index == 0 || index >= std::size(endpoint_map_) ||
endpoint_map_[index] != function_index) {
fdf::error("Bad endpoint address {:#x}", desc->b_endpoint_address);
return ZX_ERR_INVALID_ARGS;
}
}
if (header->b_length == 0) {
fdf::error("usb_func_set_interface: zero length descriptor");
return ZX_ERR_INVALID_ARGS;
}
header = reinterpret_cast<const usb_descriptor_header_t*>(
reinterpret_cast<const uint8_t*>(header) + header->b_length);
}
return ZX_OK;
}
bool UsbPeripheral::AllFunctionsRegistered() const {
if (!lock_functions_) {
return false;
}
for (const auto& config : configurations_) {
for (auto function_index : config.functions) {
const auto& function = GetFunction(function_index);
if (!function.registered()) {
return false;
}
}
}
return true;
}
zx_status_t UsbPeripheral::FunctionRegistered() {
fbl::AutoLock lock(&lock_);
// Check to see if we have all our functions registered.
// If so, we can build our configuration descriptor and tell the DCI driver we are ready.
if (!AllFunctionsRegistered()) {
// Need to wait for more functions to register.
return ZX_OK;
}
size_t config_idx = 0;
// build our configuration descriptor
for (auto& config : configurations_) {
std::vector<uint8_t> config_desc_bytes(sizeof(usb_configuration_descriptor_t));
{
auto* config_desc =
reinterpret_cast<usb_configuration_descriptor_t*>(config_desc_bytes.data());
config_desc->b_length = sizeof(*config_desc);
config_desc->b_descriptor_type = USB_DT_CONFIG;
config_desc->b_num_interfaces = 0;
config_desc->b_configuration_value = static_cast<uint8_t>(1 + config_idx);
config_desc->i_configuration = 0;
// TODO(voydanoff) add a way to configure bm_attributes and bMaxPower
config_desc->bm_attributes = USB_CONFIGURATION_SELF_POWERED | USB_CONFIGURATION_RESERVED_7;
config_desc->b_max_power = 0;
}
for (auto function_index : config.functions) {
auto& function = GetFunction(function_index);
size_t descriptors_length;
auto* descriptors = function.GetDescriptors(&descriptors_length);
auto old_size = config_desc_bytes.size();
config_desc_bytes.resize(old_size + descriptors_length);
memcpy(config_desc_bytes.data() + old_size, descriptors, descriptors_length);
reinterpret_cast<usb_configuration_descriptor_t*>(config_desc_bytes.data())
->b_num_interfaces += function.GetNumInterfaces();
}
reinterpret_cast<usb_configuration_descriptor_t*>(config_desc_bytes.data())->w_total_length =
htole16(config_desc_bytes.size());
config.config_desc = std::move(config_desc_bytes);
config_idx++;
}
auto status = DeviceStateChangedLocked();
if (status != ZX_OK) {
fdf::error("DeviceStateChangedLocked failed: {}", status);
return status;
}
lock.release();
if (listener_.is_valid()) {
if (fidl::Status status = fidl::WireCall(listener_)->FunctionRegistered(); !status.ok()) {
// If you expected a call here, the listener_ might have been closed before it got called.
// This shouldn't crash the driver though.
fdf::debug("Failed to send FunctionRegistered request: {}", status);
}
}
return ZX_OK;
}
zx_status_t UsbPeripheral::StartController() {
if (dci_new_.is_valid()) {
fidl::Arena arena;
auto result = dci_new_.buffer(arena)->StartController();
if (!result.ok()) {
fdf::debug("Failed to send StartController request: {}", result.status_string());
return ZX_ERR_INTERNAL;
}
if (result->is_error()) {
fdf::debug("Failed to start controller: {}", zx_status_get_string(result->error_value()));
return result->error_value();
}
return ZX_OK;
}
// Not all DCI drivers have the new FIDL protocols plumbed through. For drivers still in
// migration, fall back to banjo if FIDL fails.
fdf::warn("could not StartController over FIDL, falling back to banjo");
// TODO(b/356940744): Move all DCI drivers over to the UsbDci FIDL protocol.
zx_status_t status = dci_.SetInterface(this, &usb_dci_interface_protocol_ops_);
if (status != ZX_OK) {
fdf::error("SetInterface failed: {}", zx_status_get_string(status));
return status;
}
return ZX_OK;
}
zx_status_t UsbPeripheral::StopController() {
CommonSetConnected(false);
if (dci_new_.is_valid()) {
fidl::Arena arena;
auto result = dci_new_.buffer(arena)->StopController();
if (!result.ok()) {
fdf::debug("Failed to send StopController request: {}", result.status_string());
return ZX_ERR_INTERNAL;
}
if (result->is_error()) {
fdf::debug("Failed to stop controller: {}", zx_status_get_string(result->error_value()));
return result->error_value();
}
return ZX_OK;
}
// Not all DCI drivers have the new FIDL protocols plumbed through. For drivers still in
// migration, fall back to banjo if FIDL fails.
fdf::warn("could not StopController over FIDL, falling back to banjo");
// TODO(b/356940744): Move all DCI drivers over to the UsbDci FIDL protocol.
zx_status_t status = dci_.SetInterface(nullptr, nullptr);
if (status != ZX_OK) {
fdf::error("SetInterface failed: {}", zx_status_get_string(status));
return status;
}
return ZX_OK;
}
zx_status_t UsbPeripheral::AllocInterface(size_t function_index, uint8_t* out_intf_num) {
fbl::AutoLock lock(&lock_);
auto& function = GetFunction(function_index);
ZX_ASSERT(function.configuration() < configurations_.size());
auto& configuration = configurations_[function.configuration()];
auto& interface_map = configuration.interface_map;
for (uint8_t i = 0; i < std::size(interface_map); i++) {
if (!interface_map[i].has_value()) {
interface_map[i] = function_index;
*out_intf_num = i;
return ZX_OK;
}
}
return ZX_ERR_NO_RESOURCES;
}
zx_status_t UsbPeripheral::AllocEndpoint(size_t function_index, uint8_t direction,
uint8_t* out_address) {
uint8_t start, end;
if (direction == USB_DIR_OUT) {
start = OUT_EP_START;
end = OUT_EP_END;
} else if (direction == USB_DIR_IN) {
start = IN_EP_START;
end = IN_EP_END;
} else {
fdf::error("Invalid direction.");
return ZX_ERR_INVALID_ARGS;
}
fbl::AutoLock lock(&lock_);
for (uint8_t endpoint_index = start; endpoint_index <= end; endpoint_index++) {
if (!endpoint_map_[endpoint_index].has_value()) {
endpoint_map_[endpoint_index] = function_index;
*out_address = EpIndexToAddress(endpoint_index);
return ZX_OK;
}
}
fdf::error("Exceeded maximum supported endpoints.");
return ZX_ERR_NO_RESOURCES;
}
zx_status_t UsbPeripheral::GetDescriptor(uint8_t request_type, uint16_t value, uint16_t index,
void* buffer, size_t length, size_t* out_actual) {
uint8_t type = request_type & USB_TYPE_MASK;
if (type != USB_TYPE_STANDARD) {
fdf::debug("Unsupported request type: {}", request_type);
return ZX_ERR_NOT_SUPPORTED;
}
fbl::AutoLock lock(&lock_);
auto desc_type = static_cast<uint8_t>(value >> 8);
if (desc_type == USB_DT_DEVICE && index == 0) {
if (device_desc_.b_length == 0) {
fdf::error("Device descriptor not set");
return ZX_ERR_INTERNAL;
}
length = std::min(length, sizeof(device_desc_));
memcpy(buffer, &device_desc_, length);
*out_actual = length;
return ZX_OK;
} else if (desc_type == USB_DT_CONFIG && index == 0) {
index = value & 0xff;
if (index >= configurations_.size()) {
fdf::error("Invalid configuration index: {}", index);
return ZX_ERR_INVALID_ARGS;
}
auto& config_desc = configurations_[index].config_desc;
if (config_desc.size() == 0) {
fdf::error("Configuration descriptor not set");
return ZX_ERR_INTERNAL;
}
auto desc_length = config_desc.size();
length = std::min(length, desc_length);
memcpy(buffer, config_desc.data(), length);
*out_actual = length;
return ZX_OK;
} else if (desc_type == USB_DT_STRING) {
uint8_t desc[255];
auto* header = reinterpret_cast<usb_descriptor_header_t*>(desc);
header->b_descriptor_type = USB_DT_STRING;
auto string_index = static_cast<uint8_t>(value & 0xFF);
if (string_index == 0) {
// special case - return language list
header->b_length = 4;
desc[2] = 0x09; // language ID
desc[3] = 0x04;
} else {
// String indices are 1-based.
string_index--;
if (string_index >= strings_.size()) {
fdf::error("Invalid string index: {}", string_index);
return ZX_ERR_INVALID_ARGS;
}
const char* string = strings_[string_index].c_str();
unsigned index = 2;
// convert ASCII to UTF16
if (string) {
while (*string && index < sizeof(desc) - 2) {
desc[index++] = *string++;
desc[index++] = 0;
}
}
header->b_length = static_cast<uint8_t>(index);
}
length = std::min<size_t>(header->b_length, length);
memcpy(buffer, desc, length);
*out_actual = length;
return ZX_OK;
} else if (desc_type == USB_DT_DEVICE_QUALIFIER) {
if (device_desc_.b_length == 0) {
fdf::error("Device descriptor not set");
return ZX_ERR_INTERNAL;
}
length = std::min(length, sizeof(usb_device_qualifier_descriptor_t));
memcpy(buffer, &device_desc_, length);
auto* qualifier = static_cast<usb_device_qualifier_descriptor_t*>(buffer);
qualifier->b_descriptor_type = USB_DT_DEVICE_QUALIFIER;
qualifier->b_num_configurations = 0;
qualifier->b_reserved = 0;
*out_actual = length;
return ZX_OK;
} else if (desc_type == USB_DT_BOS) {
usb_bos_descriptor_t bos{
.b_length = sizeof(usb_bos_descriptor_t),
.b_descriptor_type = USB_DT_BOS,
.w_total_length = sizeof(usb_bos_descriptor_t),
.b_num_device_caps = 0, // No device capabilities.
};
length = std::min(length, sizeof(usb_bos_descriptor_t));
memcpy(buffer, &bos, length);
*out_actual = length;
return ZX_OK;
}
fdf::error("Unsupported value: {:#x} index: {}", value, index);
return ZX_ERR_NOT_SUPPORTED;
}
zx_status_t UsbPeripheral::SetConfiguration(uint8_t configuration) {
bool configured = configuration > 0;
// TODO(b/355271738): Logs added to debug b/355271738. Remove when fixed.
fdf::info("Configuration {}", configuration);
fbl::AutoLock lock(&lock_);
for (const auto& config : configurations_) {
for (auto function_index : config.functions) {
auto& function = GetFunction(function_index);
auto status = function.SetConfigured(function.configuration() == (configuration - 1), speed_);
if (status != ZX_OK && configured) {
return status;
}
}
}
configuration_ = configuration;
return ZX_OK;
}
zx_status_t UsbPeripheral::SetInterface(uint8_t interface, uint8_t alt_setting) {
const auto& configuration = configurations_[configuration_ - 1];
if (interface >= std::size(configuration.interface_map)) {
fdf::error("Invalid interface index: {}", interface);
return ZX_ERR_OUT_OF_RANGE;
}
auto function_index = configuration.interface_map[interface];
if (function_index.has_value()) {
auto& function = GetFunction(function_index.value());
return function.SetInterface(interface, alt_setting);
}
fdf::error("Function does not exist");
return ZX_ERR_NOT_SUPPORTED;
}
zx::result<size_t> UsbPeripheral::AddFunction(UsbConfiguration& config, FunctionDescriptor desc) {
fbl::AutoLock lock(&lock_);
if (lock_functions_) {
fdf::error("Functions are already bound");
return zx::error(ZX_ERR_BAD_STATE);
}
auto function_index = functions_.size();
auto function = std::make_unique<UsbFunction>(function_index, this, desc, config.index,
fdf::Dispatcher::GetCurrent()->async_dispatcher());
functions_.emplace_back(std::move(function));
config.functions.push_back(function_index);
return zx::ok(function_index);
}
void UsbPeripheral::ClearFunctions() {
fdf::debug("{}", __func__);
{
fbl::AutoLock lock(&lock_);
if (shutting_down_) {
fdf::info("Already in process of clearing the functions");
return;
}
shutting_down_ = true;
}
auto status = StopController();
if (status != ZX_OK) {
fdf::info("Failed to stop controller: {}", zx_status_get_string(status));
// Continue despite failure.
}
for (size_t i = 0; i < 256; i++) {
UsbDciCancelAll(static_cast<uint8_t>(i));
}
{
fbl::AutoLock lock(&lock_);
shutting_down_ = false;
configurations_.clear();
lock_functions_ = false;
for (size_t i = 0; i < std::size(endpoint_map_); i++) {
endpoint_map_[i].reset();
}
strings_.clear();
functions_.clear();
DeviceStateChangedLocked();
if (listener_.is_valid()) {
if (fidl::Status status = fidl::WireCall(listener_)->FunctionsCleared(); !status.ok()) {
fdf::error("Failed to send FunctionsCleared request: {}", status);
}
}
}
}
zx_status_t UsbPeripheral::AddFunctionDevices() {
fdf::debug("{}", __func__);
int func_index = 0;
for (const auto& configuration : configurations_) {
for (auto function_index : configuration.functions) {
char name[16];
snprintf(name, sizeof(name), "function-%03u", func_index);
auto& function = GetFunction(function_index);
zx::result result = function.AddChild(child_.node_, name, incoming(), outgoing());
if (result.is_error() && result.status_value() != ZX_ERR_ALREADY_BOUND) {
fdf::error("Failed to add child {}: {}; Continuing on to next.", name, result);
}
func_index++;
}
}
return ZX_OK;
}
zx_status_t UsbPeripheral::DeviceStateChanged() {
fbl::AutoLock _(&lock_);
return DeviceStateChangedLocked();
}
zx_status_t UsbPeripheral::DeviceStateChangedLocked() {
fdf::info("cur_usb_mode={}, parent_usb_mode={}", cur_usb_mode_, parent_usb_mode_);
std::optional<bool> stop = std::nullopt;
usb_mode_t new_dci_usb_mode = cur_usb_mode_;
if (parent_usb_mode_ == USB_MODE_PERIPHERAL) {
if (lock_functions_) {
// publish child devices
auto status = AddFunctionDevices();
if (status != ZX_OK) {
return status;
}
}
if (AllFunctionsRegistered()) {
// switch DCI to device mode
new_dci_usb_mode = USB_MODE_PERIPHERAL;
stop = false;
} else {
new_dci_usb_mode = USB_MODE_NONE;
stop = true;
}
} else {
new_dci_usb_mode = parent_usb_mode_;
}
if (cur_usb_mode_ != new_dci_usb_mode) {
fdf::info("Set DCI mode to {}", new_dci_usb_mode);
cur_usb_mode_ = new_dci_usb_mode;
if (stop.has_value()) {
lock_.Release();
auto status = *stop ? StopController() : StartController();
lock_.Acquire();
if (status != ZX_OK) {
return status;
}
}
}
return ZX_OK;
}
zx_status_t UsbPeripheral::CommonControl(const usb_setup_t* setup, const uint8_t* write_buffer,
size_t write_size, uint8_t* read_buffer, size_t read_size,
size_t* out_read_actual) {
uint8_t request_type = setup->bm_request_type;
uint8_t direction = request_type & USB_DIR_MASK;
uint8_t request = setup->b_request;
uint16_t value = le16toh(setup->w_value);
uint16_t index = le16toh(setup->w_index);
uint16_t length = le16toh(setup->w_length);
if (direction == USB_DIR_IN && length > read_size) {
return ZX_ERR_BUFFER_TOO_SMALL;
} else if (direction == USB_DIR_OUT && length > write_size) {
return ZX_ERR_BUFFER_TOO_SMALL;
}
if ((write_size > 0 && write_buffer == NULL) || (read_size > 0 && read_buffer == NULL)) {
return ZX_ERR_INVALID_ARGS;
}
fdf::debug("usb_dev_control type={:#02X}, req={}, value={}, index={}, length={}", request_type,
request, value, index, length);
switch (request_type & USB_RECIP_MASK) {
case USB_RECIP_DEVICE:
// handle standard device requests
if ((request_type & (USB_DIR_MASK | USB_TYPE_MASK)) == (USB_DIR_IN | USB_TYPE_STANDARD) &&
request == USB_REQ_GET_DESCRIPTOR) {
return GetDescriptor(request_type, value, index, read_buffer, length, out_read_actual);
} else if (request_type == (USB_DIR_OUT | USB_TYPE_STANDARD | USB_RECIP_DEVICE) &&
request == USB_REQ_SET_CONFIGURATION && length == 0) {
return SetConfiguration(static_cast<uint8_t>(value));
} else if (request_type == (USB_DIR_IN | USB_TYPE_STANDARD | USB_RECIP_DEVICE) &&
request == USB_REQ_GET_CONFIGURATION && length > 0) {
*read_buffer = configuration_;
*out_read_actual = sizeof(uint8_t);
return ZX_OK;
} else if (request_type == (USB_DIR_IN | USB_TYPE_STANDARD | USB_RECIP_DEVICE) &&
request == USB_REQ_GET_STATUS && length == 2) {
read_buffer[1] = 1 << USB_DEVICE_SELF_POWERED;
*out_read_actual = read_size;
return ZX_OK;
} else {
// Delegate to one of the function drivers.
// USB_RECIP_DEVICE should only be used when there is a single active interface.
// But just to be conservative, try all the available interfaces.
if (configuration_ == 0) {
return ZX_ERR_BAD_STATE;
}
ZX_ASSERT(configuration_ <= configurations_.size());
const auto& configuration = configurations_[configuration_ - 1];
const auto& interface_map = configuration.interface_map;
for (size_t i = 0; i < std::size(interface_map); i++) {
auto function_index = interface_map[i];
if (function_index.has_value()) {
auto& function = GetFunction(function_index.value());
auto status = function.Control(setup, write_buffer, write_size, read_buffer, read_size,
out_read_actual);
if (status == ZX_OK) {
return ZX_OK;
}
}
}
}
break;
case USB_RECIP_INTERFACE: {
if (request_type == (USB_DIR_OUT | USB_TYPE_STANDARD | USB_RECIP_INTERFACE) &&
request == USB_REQ_SET_INTERFACE && length == 0) {
return SetInterface(static_cast<uint8_t>(index), static_cast<uint8_t>(value));
} else {
const auto& configuration = configurations_[configuration_ - 1];
const auto& interface_map = configuration.interface_map;
if (index >= std::size(interface_map)) {
return ZX_ERR_OUT_OF_RANGE;
}
// delegate to the function driver for the interface
auto function_index = interface_map[index];
if (function_index.has_value()) {
auto& function = GetFunction(function_index.value());
return function.Control(setup, write_buffer, write_size, read_buffer, read_size,
out_read_actual);
}
}
break;
}
case USB_RECIP_ENDPOINT: {
// delegate to the function driver for the endpoint
index = EpAddressToIndex(static_cast<uint8_t>(index));
if (index == 0 || index >= USB_MAX_EPS) {
return ZX_ERR_INVALID_ARGS;
}
if (index >= std::size(endpoint_map_)) {
return ZX_ERR_OUT_OF_RANGE;
}
auto function_index = endpoint_map_[index];
if (function_index.has_value()) {
auto& function = GetFunction(function_index.value());
return function.Control(setup, write_buffer, write_size, read_buffer, read_size,
out_read_actual);
}
break;
}
case USB_RECIP_OTHER:
// TODO(voydanoff) - how to handle this?
default:
break;
}
return ZX_ERR_NOT_SUPPORTED;
}
zx_status_t UsbPeripheral::UsbDciInterfaceControl(const usb_setup_t* setup,
const uint8_t* write_buffer, size_t write_size,
uint8_t* read_buffer, size_t read_size,
size_t* out_read_actual) {
return CommonControl(setup, write_buffer, write_size, read_buffer, read_size, out_read_actual);
}
void UsbPeripheral::CommonSetConnected(bool connected) {
bool was_connected = connected;
{
fbl::AutoLock lock(&lock_);
std::swap(connected_, was_connected);
}
// TODO(b/355271738): Logs added to debug b/355271738. Remove when fixed.
fdf::info("connected={}, was_connected={}", connected, was_connected);
if (was_connected != connected) {
if (!connected) {
for (const auto& configuration : configurations_) {
for (const auto function_index : configuration.functions) {
auto& function = GetFunction(function_index);
function.SetConfigured(false, USB_SPEED_UNDEFINED);
}
}
}
}
}
void UsbPeripheral::UsbDciInterfaceSetConnected(bool connected) { CommonSetConnected(connected); }
void UsbPeripheral::UsbDciInterfaceSetSpeed(usb_speed_t speed) { speed_ = speed; }
void UsbPeripheral::SetConfiguration(SetConfigurationRequestView request,
SetConfigurationCompleter::Sync& completer) {
{
fbl::AutoLock _(&lock_);
if (lock_functions_) {
completer.ReplyError(ZX_ERR_ALREADY_BOUND);
return;
}
}
fdf::debug("{}", __func__);
ZX_ASSERT(!request->config_descriptors.empty());
uint8_t index = 0;
for (auto& func_descs : request->config_descriptors) {
auto& descriptor = configurations_.emplace_back(index);
{
fbl::AutoLock lock(&lock_);
if (shutting_down_) {
fdf::error("Cannot set configuration while clearing functions");
completer.ReplyError(ZX_ERR_BAD_STATE);
return;
}
}
if (func_descs.size() == 0) {
completer.ReplyError(ZX_ERR_INVALID_ARGS);
return;
}
zx_status_t status = SetDeviceDescriptor(std::move(request->device_desc));
if (status != ZX_OK) {
completer.ReplyError(status);
return;
}
for (auto func_desc : func_descs) {
auto result = AddFunction(descriptor, func_desc);
if (result.is_error()) {
fdf::error("Failed to add function: {}", result);
}
}
index++;
}
{
fbl::AutoLock _(&lock_);
lock_functions_ = true;
DeviceStateChangedLocked();
}
completer.ReplySuccess();
}
zx_status_t UsbPeripheral::SetDeviceDescriptor(DeviceDescriptor desc) {
if (desc.b_num_configurations == 0) {
fdf::error("bNumConfigurations must be non-zero");
return ZX_ERR_INVALID_ARGS;
} else {
device_desc_.b_length = sizeof(usb_device_descriptor_t);
device_desc_.b_descriptor_type = USB_DT_DEVICE;
device_desc_.bcd_usb = desc.bcd_usb;
device_desc_.b_device_class = desc.b_device_class;
device_desc_.b_device_sub_class = desc.b_device_sub_class;
device_desc_.b_device_protocol = desc.b_device_protocol;
device_desc_.b_max_packet_size0 = desc.b_max_packet_size0;
device_desc_.id_vendor = desc.id_vendor;
device_desc_.id_product = desc.id_product;
device_desc_.bcd_device = desc.bcd_device;
zx_status_t status =
AllocStringDesc(std::string(desc.manufacturer.data(), desc.manufacturer.size()),
&device_desc_.i_manufacturer);
if (status != ZX_OK) {
return status;
}
status = AllocStringDesc(std::string(desc.product.data(), desc.product.size()),
&device_desc_.i_product);
if (status != ZX_OK) {
return status;
}
status = AllocStringDesc(std::string(desc.serial.data(), desc.serial.size()),
&device_desc_.i_serial_number);
if (status != ZX_OK) {
return status;
}
device_desc_.b_num_configurations = desc.b_num_configurations;
return ZX_OK;
}
}
void UsbPeripheral::ClearFunctions(ClearFunctionsCompleter::Sync& completer) {
fdf::debug("{}", __func__);
ClearFunctions();
completer.Reply();
}
void UsbPeripheral::SetStateChangeListener(SetStateChangeListenerRequestView request,
SetStateChangeListenerCompleter::Sync& completer) {
// No safety checks. Test should know when it's safe to set listener.
listener_ = std::move(request->listener);
}
void UsbPeripheral::PrepareStop(fdf::PrepareStopCompleter completer) {
ClearFunctions();
usb_monitor_.Stop();
if (listener_.is_valid()) {
zx_signals_t observed = 0;
listener_.channel().wait_one(ZX_CHANNEL_PEER_CLOSED | __ZX_OBJECT_HANDLE_CLOSED,
zx::time::infinite(), &observed);
}
completer(zx::ok());
}
zx_status_t UsbPeripheral::SetDefaultConfig(std::vector<FunctionDescriptor>& functions) {
{
fbl::AutoLock _(&lock_);
if (lock_functions_) {
return ZX_ERR_ALREADY_BOUND;
}
}
auto& descriptor = configurations_.emplace_back(static_cast<uint8_t>(0));
device_desc_.b_length = sizeof(usb_device_descriptor_t),
device_desc_.b_descriptor_type = USB_DT_DEVICE;
device_desc_.bcd_usb = htole16(0x0200);
device_desc_.b_device_class = 0;
device_desc_.b_device_sub_class = 0;
device_desc_.b_device_protocol = 0;
device_desc_.b_max_packet_size0 = 64;
device_desc_.bcd_device = htole16(0x0100);
device_desc_.b_num_configurations = 1;
for (auto function : functions) {
auto result = AddFunction(descriptor, function);
if (result.is_error()) {
fdf::error("Failed to add function: ({}:{}:{}) status: {}", function.interface_class,
function.interface_subclass, function.interface_protocol, result);
return result.status_value();
}
}
{
fbl::AutoLock _(&lock_);
lock_functions_ = true;
DeviceStateChangedLocked();
}
return ZX_OK;
}
UsbFunction& UsbPeripheral::GetFunction(size_t index) {
ZX_ASSERT_MSG(index < functions_.size(), "Function %lu does not exist", index);
auto& function = functions_[index];
ZX_ASSERT(function != nullptr);
return *function;
}
const UsbFunction& UsbPeripheral::GetFunction(size_t index) const {
ZX_ASSERT_MSG(index < functions_.size(), "Function %lu does not exist", index);
const auto& function = functions_[index];
ZX_ASSERT(function != nullptr);
return *function;
}
} // namespace usb_peripheral
FUCHSIA_DRIVER_EXPORT(usb_peripheral::UsbPeripheral);