blob: 9125a5044957b8a186a5e8f0d561e14b85e7664a [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 <fuchsia/hardware/usb/dci/c/banjo.h>
#include <fuchsia/hardware/usb/function/c/banjo.h>
#include <lib/ddk/binding_driver.h>
#include <lib/ddk/debug.h>
#include <lib/ddk/device.h>
#include <lib/ddk/driver.h>
#include <lib/ddk/metadata.h>
#include <lib/fit/defer.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 <ddktl/fidl.h>
#include <fbl/alloc_checker.h>
#include <fbl/auto_lock.h>
#include <fbl/ref_ptr.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::Create(void* ctx, zx_device_t* parent) {
zx_handle_t structured_config_vmo;
auto status = device_get_config_vmo(parent, &structured_config_vmo);
if (status != ZX_OK || structured_config_vmo == ZX_HANDLE_INVALID) {
zxlogf(ERROR, "Failed to get usb peripheral config. Status: %d VMO handle: %d", status,
structured_config_vmo);
return ZX_ERR_INTERNAL;
}
auto config = usb_peripheral_config::Config::CreateFromVmo(zx::vmo(structured_config_vmo));
fbl::AllocChecker ac;
auto device = fbl::make_unique_checked<UsbPeripheral>(&ac, parent, config);
if (!ac.check()) {
return ZX_ERR_NO_MEMORY;
}
status = device->Init();
if (status != ZX_OK) {
zxlogf(ERROR, "Failed to initialize usb peripheral - %d", status);
return status;
}
// devmgr is now in charge of the device.
[[maybe_unused]] auto* dummy = device.release();
return ZX_OK;
}
zx_status_t UsbPeripheral::UsbDciCancelAll(uint8_t ep_address) {
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_status_t UsbPeripheral::Init() {
// Parent must support DCI protocol. USB Mode Switch is optional.
if (!dci_.is_valid()) {
return ZX_ERR_NOT_SUPPORTED;
}
auto client = DdkConnectFidlProtocol<fuchsia_hardware_usb_dci::UsbDciService::Device>();
if (client.is_error()) {
zxlogf(ERROR, "Failed to connect fidl protocol");
return client.error_value();
}
dci_new_.Bind(std::move(*client));
// 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.
size_t actual;
auto status = device_get_metadata(parent(), DEVICE_METADATA_USB_MODE, &usb_mode_,
sizeof(usb_mode_), &actual);
if (status == ZX_ERR_NOT_FOUND) {
fbl::AutoLock lock(&lock_);
// Assume peripheral mode by default.
usb_mode_ = USB_MODE_PERIPHERAL;
} else if (status != ZX_OK || actual != sizeof(usb_mode_)) {
zxlogf(ERROR, "%s: DEVICE_METADATA_USB_MODE failed", __func__);
return status;
}
parent_request_size_ = usb::BorrowedRequest<void>::RequestSize(dci_.GetRequestSize());
status = DdkAdd("usb-peripheral", DEVICE_ADD_NON_BINDABLE);
if (status != ZX_OK) {
zxlogf(ERROR, "DdkAdd failed - %s", zx_status_get_string(status));
return status;
}
PeripheralConfigParser peripheral_config = {};
status = peripheral_config.AddFunctions(config_.functions());
if (status != ZX_OK) {
zxlogf(ERROR, "Failed to add usb functions from structured config - %d", status);
return 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) {
zxlogf(ERROR, "Failed to allocate manufacturer string descriptor - %d", status);
return status;
}
status = AllocStringDesc(peripheral_config.product(), &device_desc_.i_product);
if (status != ZX_OK) {
zxlogf(ERROR, "Failed to allocate product string descriptor - %d", status);
return status;
}
auto serial = GetSerialNumber();
status = AllocStringDesc(serial, &device_desc_.i_serial_number);
if (status != ZX_OK) {
zxlogf(ERROR, "Failed to add serial number descriptor - %d", status);
return status;
}
status = SetDefaultConfig(peripheral_config.functions());
if (status != ZX_OK) {
zxlogf(ERROR, "Failed to set default config - %d", status);
return status;
}
{
// Set interface right away if function devices are not added yet. If there are function
// devices, the interface is set once all functions are registered.
fbl::AutoLock lock(&lock_);
if (!function_devs_added_) {
dci_.SetInterface(this, &usb_dci_interface_protocol_ops_);
}
}
usb_monitor_.Start();
return ZX_OK;
}
std::string UsbPeripheral::GetSerialNumber() {
char buffer[256];
size_t actual = 0;
// Return serial number from metadata if present.
auto status =
device_get_metadata(parent(), DEVICE_METADATA_SERIAL_NUMBER, buffer, sizeof(buffer), &actual);
if (status == ZX_OK) {
return {buffer, actual};
}
// Use MAC address as the next option.
uint8_t raw_mac_addr[6];
status = device_get_metadata(parent(), DEVICE_METADATA_MAC_ADDRESS, &raw_mac_addr,
sizeof(raw_mac_addr), &actual);
if (status == ZX_OK && actual == sizeof(raw_mac_addr)) {
snprintf(buffer, sizeof(buffer), "%02X%02X%02X%02X%02X%02X", raw_mac_addr[0], raw_mac_addr[1],
raw_mac_addr[2], raw_mac_addr[3], raw_mac_addr[4], raw_mac_addr[5]);
return {buffer};
}
zxlogf(INFO, "Serial number/MAC address not found. Using generic (non-unique) serial number.\n");
return std::string(kDefaultSerialNumber);
}
zx_status_t UsbPeripheral::AllocStringDesc(std::string desc, uint8_t* out_index) {
fbl::AutoLock lock(&lock_);
if (strings_.size() >= MAX_STRINGS) {
zxlogf(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(fbl::RefPtr<UsbFunction> function, 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)) {
zxlogf(ERROR, "%s: 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)) {
zxlogf(ERROR, "%s: interface association descriptor is invalid", __func__);
return ZX_ERR_INVALID_ARGS;
}
} else {
zxlogf(ERROR, "%s: 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);
ZX_ASSERT(function->configuration() < configurations_.size());
auto configuration = configurations_[function->configuration()];
auto& interface_map = configuration->interface_map;
if (desc->b_interface_number >= std::size(interface_map) ||
interface_map[desc->b_interface_number] != function) {
zxlogf(ERROR, "usb_func_set_interface: bInterfaceNumber %u", 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) {
zxlogf(ERROR, "usb_func_set_interface: bad endpoint address 0x%X",
desc->b_endpoint_address);
return ZX_ERR_INVALID_ARGS;
}
}
if (header->b_length == 0) {
zxlogf(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;
}
zx_status_t UsbPeripheral::FunctionRegistered() {
fbl::AutoLock lock(&lock_);
ZX_ASSERT(configurations_.size() > 0);
if (configurations_[0]->config_desc.size() != 0) {
return ZX_ERR_BAD_STATE;
}
// 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.
fbl::Vector<size_t> lengths;
for (auto& config : configurations_) {
auto& functions = config->functions;
size_t length = sizeof(usb_configuration_descriptor_t);
for (size_t i = 0; i < functions.size(); i++) {
auto* function = functions[i].get();
size_t descriptors_length;
if (function->GetDescriptors(&descriptors_length) != nullptr) {
length += descriptors_length;
} else {
// Need to wait for more functions to register.
return ZX_OK;
}
}
lengths.push_back(length);
}
size_t config_idx = 0;
// build our configuration descriptor
for (auto& config : configurations_) {
auto& functions = config->functions;
size_t length = lengths[config_idx];
fbl::AllocChecker ac;
auto* config_desc_bytes = new (&ac) uint8_t[length];
if (!ac.check()) {
zxlogf(ERROR, "Building configuration descriptor failed due to no memory.");
return ZX_ERR_NO_MEMORY;
}
auto* config_desc = reinterpret_cast<usb_configuration_descriptor_t*>(config_desc_bytes);
config_desc->b_length = sizeof(*config_desc);
config_desc->b_descriptor_type = USB_DT_CONFIG;
config_desc->w_total_length = htole16(length);
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;
uint8_t* dest = reinterpret_cast<uint8_t*>(config_desc + 1);
for (size_t i = 0; i < functions.size(); i++) {
auto* function = functions[i].get();
size_t descriptors_length;
auto* descriptors = function->GetDescriptors(&descriptors_length);
memcpy(dest, descriptors, descriptors_length);
dest += descriptors_length;
config_desc->b_num_interfaces =
static_cast<uint8_t>(config_desc->b_num_interfaces + function->GetNumInterfaces());
}
config->config_desc.reset(config_desc_bytes, length);
config_idx++;
}
zxlogf(DEBUG, "usb_device_function_registered functions_registered = true");
functions_registered_ = true;
auto status = DeviceStateChanged();
if (status != ZX_OK) {
zxlogf(ERROR, "DeviceStateChanged failed %d", status);
return status;
}
lock.release();
status = dci_.SetInterface(this, &usb_dci_interface_protocol_ops_);
if (status != ZX_OK) {
zxlogf(ERROR, "SetInterface failed %d", status);
return status;
}
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.
zxlogf(DEBUG, "FunctionRegistered failed %s", status.error().FormatDescription().c_str());
}
return ZX_OK;
}
void UsbPeripheral::FunctionCleared() {
zxlogf(DEBUG, "%s", __func__);
fbl::AutoLock lock(&lock_);
if (num_functions_to_clear_ == 0 || !shutting_down_) {
zxlogf(ERROR, "unexpected FunctionCleared event, num_functions: %lu is_shutting_down: %d",
num_functions_to_clear_, shutting_down_);
return;
}
num_functions_to_clear_--;
if (num_functions_to_clear_ > 0) {
// Still waiting for more functions to clear.
return;
}
ClearFunctionsComplete();
}
zx_status_t UsbPeripheral::AllocInterface(fbl::RefPtr<UsbFunction> function,
uint8_t* out_intf_num) {
fbl::AutoLock lock(&lock_);
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] == nullptr) {
interface_map[i] = function;
*out_intf_num = i;
return ZX_OK;
}
}
return ZX_ERR_NO_RESOURCES;
}
zx_status_t UsbPeripheral::AllocEndpoint(fbl::RefPtr<UsbFunction> function, 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 {
zxlogf(ERROR, "Invalid direction.");
return ZX_ERR_INVALID_ARGS;
}
fbl::AutoLock lock(&lock_);
for (uint8_t index = start; index <= end; index++) {
if (endpoint_map_[index] == nullptr) {
endpoint_map_[index] = function;
*out_address = EpIndexToAddress(index);
return ZX_OK;
}
}
zxlogf(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) {
zxlogf(DEBUG, "%s unsupported value: %d index: %d", __func__, value, index);
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) {
zxlogf(ERROR, "%s: device descriptor not set", __func__);
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()) {
zxlogf(ERROR, "Invalid configuration index: %d", index);
return ZX_ERR_INVALID_ARGS;
}
auto& config_desc = configurations_[index]->config_desc;
if (config_desc.size() == 0) {
zxlogf(ERROR, "%s: configuration descriptor not set", __func__);
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 (value >> 8 == 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()) {
zxlogf(ERROR, "Invalid string index: %d", 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;
}
zxlogf(DEBUG, "%s unsupported value: %d index: %d", __func__, value, index);
return ZX_ERR_NOT_SUPPORTED;
}
zx_status_t UsbPeripheral::SetConfiguration(uint8_t configuration) {
bool configured = configuration > 0;
fbl::AutoLock lock(&lock_);
for (auto& config : configurations_) {
auto& functions = config->functions;
for (auto& function : functions) {
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) {
auto configuration = configurations_[configuration_ - 1];
if (interface >= std::size(configuration->interface_map)) {
zxlogf(ERROR, "Invalid interface index: %d", interface);
return ZX_ERR_OUT_OF_RANGE;
}
auto function = configuration->interface_map[interface];
if (function != nullptr) {
return function->SetInterface(interface, alt_setting);
}
zxlogf(ERROR, "Function does not exist");
return ZX_ERR_NOT_SUPPORTED;
}
zx_status_t UsbPeripheral::AddFunction(UsbConfiguration& config, FunctionDescriptor desc) {
fbl::AutoLock lock(&lock_);
if (functions_bound_) {
zxlogf(ERROR, "Functions are already bound");
return ZX_ERR_BAD_STATE;
}
fbl::AllocChecker ac;
auto function =
fbl::MakeRefCountedChecked<UsbFunction>(&ac, zxdev(), this, std::move(desc), config.index,
fdf::Dispatcher().GetCurrent()->async_dispatcher());
if (!ac.check()) {
return ZX_ERR_NO_MEMORY;
}
config.functions.push_back(function);
return ZX_OK;
}
zx_status_t UsbPeripheral::BindFunctions() {
fbl::AutoLock lock(&lock_);
if (functions_bound_) {
zxlogf(ERROR, "%s: already bound!", __func__);
return ZX_ERR_BAD_STATE;
}
if (device_desc_.b_length == 0) {
zxlogf(ERROR, "%s: device descriptor not set", __func__);
return ZX_ERR_BAD_STATE;
}
if (!configurations_.size()) {
zxlogf(ERROR, "%s: no configurations found", __func__);
return ZX_ERR_BAD_STATE;
}
if (configurations_[0]->functions.size() == 0) {
zxlogf(ERROR, "%s: no functions to bind", __func__);
return ZX_ERR_BAD_STATE;
}
zxlogf(DEBUG, "%s: functions_bound = true", __func__);
functions_bound_ = true;
return DeviceStateChanged();
}
void UsbPeripheral::ClearFunctions() {
zxlogf(DEBUG, "%s", __func__);
{
fbl::AutoLock lock(&lock_);
if (shutting_down_) {
zxlogf(INFO, "%s: already in process of clearing the functions", __func__);
return;
}
shutting_down_ = true;
for (size_t i = 0; i < 256; i++) {
dci_.CancelAll(static_cast<uint8_t>(i));
}
for (auto& configuration : configurations_) {
auto& functions = configuration->functions;
for (size_t i = 0; i < functions.size(); i++) {
auto* function = functions[i].get();
if (function->zxdev()) {
num_functions_to_clear_++;
}
}
}
zxlogf(DEBUG, "%s: found %lu functions", __func__, num_functions_to_clear_);
if (num_functions_to_clear_ == 0) {
// Don't need to wait for anything to be removed, update our state now.
ClearFunctionsComplete();
return;
}
}
// TODO(jocelyndang): we can call DdkRemove inside the lock above once DdkRemove becomes async.
for (auto& configuration : configurations_) {
auto& functions = configuration->functions;
for (size_t i = 0; i < functions.size(); i++) {
auto* function = functions[i].get();
if (function->zxdev()) {
function->DdkAsyncRemove();
}
}
}
}
void UsbPeripheral::ClearFunctionsComplete() {
zxlogf(DEBUG, "%s", __func__);
shutting_down_ = false;
configurations_.reset();
functions_bound_ = false;
functions_registered_ = false;
function_devs_added_ = false;
configurations_.reset();
for (size_t i = 0; i < std::size(endpoint_map_); i++) {
endpoint_map_[i].reset();
}
strings_.clear();
DeviceStateChanged();
if (listener_.is_valid()) {
if (fidl::Status status = fidl::WireCall(listener_)->FunctionsCleared(); !status.ok()) {
zxlogf(ERROR, "%s: %s", __func__, status.status_string());
}
}
}
zx_status_t UsbPeripheral::AddFunctionDevices() {
zxlogf(DEBUG, "%s", __func__);
if (function_devs_added_) {
return ZX_OK;
}
int func_index = 0;
for (auto& configuration : configurations_) {
auto& functions = configuration->functions;
for (unsigned i = 0; i < functions.size(); i++) {
auto function = functions[i];
char name[16];
snprintf(name, sizeof(name), "function-%03u", func_index);
auto& desc = function->GetFunctionDescriptor();
zx_device_prop_t props[] = {
{BIND_PROTOCOL, 0, ZX_PROTOCOL_USB_FUNCTION},
{BIND_USB_CLASS, 0, desc.interface_class},
{BIND_USB_SUBCLASS, 0, desc.interface_subclass},
{BIND_USB_PROTOCOL, 0, desc.interface_protocol},
{BIND_USB_VID, 0, device_desc_.id_vendor},
{BIND_USB_PID, 0, device_desc_.id_product},
};
auto endpoints = fidl::CreateEndpoints<fuchsia_io::Directory>();
if (endpoints.is_error()) {
return endpoints.status_value();
}
auto status = function->AddService(std::move(endpoints->server));
if (status != ZX_OK) {
zxlogf(ERROR, "Could not add service %d", status);
return status;
}
std::array offers = {
fuchsia_hardware_usb_function::UsbFunctionService::Name,
};
status = function->DdkAdd(ddk::DeviceAddArgs(name)
.set_props(props)
.forward_metadata(parent(), DEVICE_METADATA_MAC_ADDRESS)
.forward_metadata(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 %d", status);
return status;
}
// Hold a reference while devmgr has a pointer to the function.
function->AddRef();
func_index++;
}
}
function_devs_added_ = true;
return ZX_OK;
}
zx_status_t UsbPeripheral::DeviceStateChanged() {
zxlogf(DEBUG, "%s usb_mode: %d dci_usb_mode: %d", __func__, usb_mode_, dci_usb_mode_);
usb_mode_t new_dci_usb_mode = dci_usb_mode_;
bool add_function_devs = (usb_mode_ == USB_MODE_PERIPHERAL && functions_bound_);
zx_status_t status = ZX_OK;
if (usb_mode_ == USB_MODE_PERIPHERAL) {
if (functions_registered_) {
// switch DCI to device mode
new_dci_usb_mode = USB_MODE_PERIPHERAL;
} else {
new_dci_usb_mode = USB_MODE_NONE;
}
} else {
new_dci_usb_mode = usb_mode_;
}
if (add_function_devs) {
// publish child devices if necessary
if (!function_devs_added_) {
status = AddFunctionDevices();
if (status != ZX_OK) {
return status;
}
}
}
if (dci_usb_mode_ != new_dci_usb_mode) {
zxlogf(DEBUG, "%s: set DCI mode %d", __func__, new_dci_usb_mode);
dci_usb_mode_ = new_dci_usb_mode;
}
return status;
}
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) {
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;
}
zxlogf(DEBUG, "usb_dev_control type: 0x%02X req: %d value: %d index: %d length: %d", 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) {
*static_cast<uint8_t*>(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) {
static_cast<uint8_t*>(read_buffer)[1] = 1 << USB_DEVICE_SELF_POWERED;
*out_read_actual = read_size;
} 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());
auto configuration = configurations_[configuration_ - 1];
auto& interface_map = configuration->interface_map;
for (size_t i = 0; i < std::size(interface_map); i++) {
auto function = interface_map[i];
if (function != nullptr) {
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 {
auto configuration = configurations_[configuration_ - 1];
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 = interface_map[index];
if (function != nullptr) {
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 = endpoint_map_[index];
if (function != nullptr) {
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;
}
void UsbPeripheral::UsbDciInterfaceSetConnected(bool connected) {
bool was_connected = connected;
{
fbl::AutoLock lock(&lock_);
std::swap(connected_, was_connected);
}
if (was_connected != connected) {
if (!connected) {
for (auto& configuration : configurations_) {
auto& functions = configuration->functions;
for (size_t i = 0; i < functions.size(); i++) {
auto* function = functions[i].get();
function->SetConfigured(false, USB_SPEED_UNDEFINED);
}
}
}
}
}
void UsbPeripheral::UsbDciInterfaceSetSpeed(usb_speed_t speed) { speed_ = speed; }
void UsbPeripheral::SetConfiguration(SetConfigurationRequestView request,
SetConfigurationCompleter::Sync& completer) {
zxlogf(DEBUG, "%s", __func__);
ZX_ASSERT(!request->config_descriptors.empty());
uint8_t index = 0;
for (auto& func_descs : request->config_descriptors) {
auto descriptor = fbl::MakeRefCounted<UsbConfiguration>();
descriptor->index = index;
configurations_.push_back(descriptor);
{
fbl::AutoLock lock(&lock_);
if (shutting_down_) {
zxlogf(ERROR, "%s: cannot set configuration while clearing functions", __func__);
completer.ReplyError(ZX_ERR_BAD_STATE);
return;
}
}
if (func_descs.count() == 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) {
AddFunction(*descriptor, func_desc);
}
index++;
}
zx_status_t status = BindFunctions();
if (status != ZX_OK) {
completer.ReplyError(status);
return;
}
completer.ReplySuccess();
}
zx_status_t UsbPeripheral::SetDeviceDescriptor(DeviceDescriptor desc) {
if (desc.b_num_configurations == 0) {
zxlogf(ERROR, "usb_device_ioctl: 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) {
zxlogf(DEBUG, "%s", __func__);
ClearFunctions();
completer.Reply();
}
int UsbPeripheral::ListenerCleanupThread() {
zx_signals_t observed = 0;
listener_.channel().wait_one(ZX_CHANNEL_PEER_CLOSED | __ZX_OBJECT_HANDLE_CLOSED,
zx::time::infinite(), &observed);
fbl::AutoLock l(&lock_);
listener_.reset();
return 0;
}
void UsbPeripheral::SetStateChangeListener(SetStateChangeListenerRequestView request,
SetStateChangeListenerCompleter::Sync& completer) {
// This code is wrapped in a loop
// to prevent a race condition in the event that multiple
// clients try to set the handle at once.
while (1) {
fbl::AutoLock lock(&lock_);
if (listener_.is_valid() && thread_) {
thrd_t thread = thread_;
thread_ = 0;
lock.release();
int output;
thrd_join(thread, &output);
continue;
}
if (listener_.is_valid()) {
completer.Close(ZX_ERR_BAD_STATE);
return;
}
if (thread_) {
int output;
thrd_t thread = thread_;
thread_ = 0;
lock.release();
// We now own the thread, but not the listener.
thrd_join(thread, &output);
// Go back and try to re-set the listener_.
// another caller may have tried to do this while we were blocked on thrd_join.
continue;
}
listener_ = std::move(request->listener);
if (thrd_create(
&thread_,
[](void* arg) -> int {
return reinterpret_cast<UsbPeripheral*>(arg)->ListenerCleanupThread();
},
reinterpret_cast<void*>(this)) != thrd_success) {
listener_.reset();
completer.Close(ZX_ERR_INTERNAL);
return;
}
return;
}
}
void UsbPeripheral::DdkUnbind(ddk::UnbindTxn txn) {
zxlogf(DEBUG, "%s", __func__);
ClearFunctions();
txn.Reply();
usb_monitor_.Stop();
}
void UsbPeripheral::DdkChildPreRelease(void* child_ctx) {
for (auto& configuration : configurations_) {
auto& functions = configuration->functions;
for (size_t i = 0; i < functions.size(); i++) {
if (functions[i].get() == child_ctx) {
functions.erase(i);
break;
}
}
}
}
void UsbPeripheral::DdkRelease() {
zxlogf(DEBUG, "%s", __func__);
{
fbl::AutoLock l(&lock_);
if (listener_) {
listener_.reset();
}
}
if (thread_) {
int output;
thrd_join(thread_, &output);
thread_ = 0;
}
delete this;
}
zx_status_t UsbPeripheral::SetDefaultConfig(std::vector<FunctionDescriptor>& functions) {
auto descriptor = fbl::MakeRefCounted<UsbConfiguration>();
configurations_.push_back(descriptor);
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;
zx_status_t status = ZX_OK;
for (auto function : functions) {
status = AddFunction(*descriptor, function);
if (status != ZX_OK) {
zxlogf(ERROR, "Failed to add function: (%d:%d:%d) status: %s", function.interface_class,
function.interface_subclass, function.interface_protocol,
zx_status_get_string(status));
return status;
}
}
if (!functions.empty()) {
status = BindFunctions();
if (status != ZX_OK) {
zxlogf(ERROR, "Failed to bind functions. status: %s", zx_status_get_string(status));
return status;
}
}
return ZX_OK;
}
static constexpr zx_driver_ops_t ops = []() {
zx_driver_ops_t ops = {};
ops.version = DRIVER_OPS_VERSION;
ops.bind = UsbPeripheral::Create;
return ops;
}();
} // namespace usb_peripheral
ZIRCON_DRIVER(usb_device, usb_peripheral::ops, "zircon", "0.1");