| // 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 "usb-peripheral.h" |
| |
| #include <assert.h> |
| #include <fuchsia/hardware/usb/dci/c/banjo.h> |
| #include <fuchsia/hardware/usb/function/c/banjo.h> |
| #include <fuchsia/hardware/usb/modeswitch/c/banjo.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/device/usb-peripheral.h> |
| #include <zircon/errors.h> |
| #include <zircon/hw/usb.h> |
| #include <zircon/hw/usb/cdc.h> |
| #include <zircon/listnode.h> |
| |
| #include <ddk/usb-peripheral-config.h> |
| #include <ddktl/fidl.h> |
| #include <fbl/alloc_checker.h> |
| #include <fbl/auto_lock.h> |
| #include <fbl/ref_ptr.h> |
| |
| #include "src/devices/usb/drivers/usb-peripheral/usb_peripheral-bind.h" |
| #include "usb-function.h" |
| |
| namespace peripheral = fuchsia_hardware_usb_peripheral; |
| |
| namespace usb_peripheral { |
| |
| zx_status_t UsbPeripheral::Create(void* ctx, zx_device_t* parent) { |
| fbl::AllocChecker ac; |
| auto device = fbl::make_unique_checked<UsbPeripheral>(&ac, parent); |
| if (!ac.check()) { |
| return ZX_ERR_NO_MEMORY; |
| } |
| |
| auto status = device->Init(); |
| if (status != ZX_OK) { |
| return status; |
| } |
| |
| // devmgr is now in charge of the device. |
| __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); |
| } |
| |
| 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()); |
| __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(); |
| 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; |
| } |
| // 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; |
| } |
| // Set DCI mode to USB_MODE_NONE until we are ready |
| if (ums_.is_valid()) { |
| ums_.SetMode(USB_MODE_NONE); |
| } |
| parent_request_size_ = usb::BorrowedRequest<void>::RequestSize(dci_.GetRequestSize()); |
| |
| status = DdkAdd("usb-peripheral", DEVICE_ADD_NON_BINDABLE); |
| if (status != ZX_OK) { |
| return status; |
| } |
| |
| dci_.SetInterface(this, &usb_dci_interface_protocol_ops_); |
| size_t metasize = 0; |
| status = device_get_metadata_size(parent(), DEVICE_METADATA_USB_CONFIG, &metasize); |
| if (status != ZX_OK) { |
| return ZX_OK; |
| } |
| constexpr auto alignment = []() { |
| return alignof(UsbConfig) > __STDCPP_DEFAULT_NEW_ALIGNMENT__ ? alignof(UsbConfig) |
| : __STDCPP_DEFAULT_NEW_ALIGNMENT__; |
| }(); |
| fbl::AllocChecker ac; |
| UsbConfig* config = |
| reinterpret_cast<UsbConfig*>(new (std::align_val_t(alignment), &ac) unsigned char[metasize]); |
| if (!ac.check()) { |
| return ZX_ERR_NO_MEMORY; |
| } |
| auto call = fit::defer( |
| [=]() { operator delete[](reinterpret_cast<char*>(config), std::align_val_t(alignment)); }); |
| status = device_get_metadata(parent(), DEVICE_METADATA_USB_CONFIG, config, metasize, &metasize); |
| if (status != ZX_OK) { |
| return ZX_OK; |
| } |
| device_desc_.id_vendor = config->vid; |
| device_desc_.id_product = config->pid; |
| |
| size_t max_str_len = strnlen(config->manufacturer, sizeof(config->manufacturer)); |
| status = |
| AllocStringDesc(fbl::String(config->manufacturer, max_str_len), &device_desc_.i_manufacturer); |
| if (status != ZX_OK) { |
| return status; |
| } |
| |
| max_str_len = strnlen(config->product, sizeof(config->product)); |
| status = AllocStringDesc(fbl::String(config->product, max_str_len), &device_desc_.i_product); |
| if (status != ZX_OK) { |
| return status; |
| } |
| 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)) { |
| zxlogf(INFO, |
| "Serial number/MAC address not found. Using generic (non-unique) serial number.\n"); |
| } else { |
| char buffer[sizeof(raw_mac_addr) * 3]; |
| 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]); |
| memcpy(config->serial, buffer, sizeof(buffer)); |
| } |
| max_str_len = strnlen(config->serial, sizeof(config->serial)); |
| actual = 0; |
| char buffer[256]; |
| size_t metadata_size; |
| status = device_get_metadata_size(parent(), DEVICE_METADATA_SERIAL_NUMBER, &metadata_size); |
| if (status == ZX_OK) { |
| if (metadata_size >= sizeof(buffer)) { |
| return ZX_ERR_OUT_OF_RANGE; |
| } |
| status = device_get_metadata(parent(), DEVICE_METADATA_SERIAL_NUMBER, buffer, sizeof(buffer), |
| &actual); |
| if (actual >= sizeof(buffer)) { |
| return ZX_ERR_OUT_OF_RANGE; |
| } |
| buffer[actual] = 0; |
| } |
| if ((status != ZX_OK) || (actual == 0)) { |
| status = |
| AllocStringDesc(fbl::String(config->serial, max_str_len), &device_desc_.i_serial_number); |
| } else { |
| status = AllocStringDesc(fbl::String(buffer, actual), &device_desc_.i_serial_number); |
| } |
| if (status != ZX_OK) { |
| return status; |
| } |
| SetDefaultConfig(reinterpret_cast<FunctionDescriptor*>(config->functions), |
| (metasize - sizeof(UsbConfig)) / sizeof(FunctionDescriptor)); |
| return ZX_OK; |
| } |
| |
| zx_status_t UsbPeripheral::AllocStringDesc(fbl::String desc, uint8_t* out_index) { |
| fbl::AutoLock lock(&lock_); |
| |
| if (strings_.size() >= MAX_STRINGS) { |
| return ZX_ERR_NO_RESOURCES; |
| } |
| 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()) { |
| 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; |
| if (listener_) { |
| fidl::WireCall<peripheral::Events>(zx::unowned_channel(listener_.get()))->FunctionRegistered(); |
| } |
| return DeviceStateChanged(); |
| } |
| |
| 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 { |
| 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; |
| } |
| } |
| |
| 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; |
| } |
| if (length > sizeof(device_desc_)) |
| 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()) { |
| 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(); |
| if (length > desc_length) { |
| 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()) { |
| 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); |
| } |
| |
| if (header->b_length < length) |
| length = header->b_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)) { |
| return ZX_ERR_OUT_OF_RANGE; |
| } |
| |
| auto function = configuration->interface_map[interface]; |
| if (function != nullptr) { |
| return function->SetInterface(interface, alt_setting); |
| } |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| |
| zx_status_t UsbPeripheral::AddFunction(UsbConfiguration& config, FunctionDescriptor desc) { |
| fbl::AutoLock lock(&lock_); |
| |
| if (functions_bound_) { |
| return ZX_ERR_BAD_STATE; |
| } |
| |
| fbl::AllocChecker ac; |
| auto function = |
| fbl::MakeRefCountedChecked<UsbFunction>(&ac, zxdev(), this, std::move(desc), config.index); |
| 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_.reset(); |
| |
| DeviceStateChanged(); |
| |
| if (listener_) { |
| fidl::WireCall<peripheral::Events>(zx::unowned_channel(listener_.get()))->FunctionsCleared(); |
| } |
| } |
| |
| 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 status = function->DdkAdd(ddk::DeviceAddArgs(name).set_props(props)); |
| 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); |
| if (ums_.is_valid()) { |
| status = ums_.SetMode(new_dci_usb_mode); |
| if (status != ZX_OK) { |
| ums_.SetMode(USB_MODE_NONE); |
| new_dci_usb_mode = USB_MODE_NONE; |
| } |
| } |
| 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(fbl::String(desc.manufacturer.data(), desc.manufacturer.size()), |
| &device_desc_.i_manufacturer); |
| if (status != ZX_OK) { |
| return status; |
| } |
| status = AllocStringDesc(fbl::String(desc.product.data(), desc.product.size()), |
| &device_desc_.i_product); |
| if (status != ZX_OK) { |
| return status; |
| } |
| status = AllocStringDesc(fbl::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(ClearFunctionsRequestView request, |
| ClearFunctionsCompleter::Sync& completer) { |
| zxlogf(DEBUG, "%s", __func__); |
| ClearFunctions(); |
| completer.Reply(); |
| } |
| |
| int UsbPeripheral::ListenerCleanupThread() { |
| zx_signals_t observed = 0; |
| listener_.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_ = request->listener.TakeChannel(); |
| 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(); |
| } |
| |
| 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(FunctionDescriptor* descriptors, size_t length) { |
| 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 (size_t i = 0; i < length; i++) { |
| status = AddFunction(*descriptor, std::move(*(descriptors + i))); |
| if (status != ZX_OK) { |
| return status; |
| } |
| } |
| |
| if (status != ZX_OK) |
| return status; |
| |
| return BindFunctions(); |
| } |
| |
| 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"); |