| // Copyright 2018 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-interface.h" |
| |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| |
| #include <ddk/binding.h> |
| #include <ddk/debug.h> |
| #include <ddk/protocol/usb.h> |
| #include <ddk/protocol/usb/composite.h> |
| #include <usb/usb-request.h> |
| |
| #include "usb-composite.h" |
| |
| namespace usb_composite { |
| |
| static inline const usb_descriptor_header_t* NextDescriptor(const usb_descriptor_header_t* header) { |
| return reinterpret_cast<const usb_descriptor_header_t*>(reinterpret_cast<const uint8_t*>(header) + |
| header->bLength); |
| } |
| |
| zx_status_t UsbInterface::Create(zx_device_t* parent, UsbComposite* composite, |
| const ddk::UsbProtocolClient& usb, |
| const usb_interface_descriptor_t* interface_desc, |
| size_t desc_length, fbl::RefPtr<UsbInterface>* out_interface) { |
| fbl::AllocChecker ac; |
| auto interface = |
| fbl::MakeRefCountedChecked<UsbInterface>(&ac, composite->zxdev(), composite, usb); |
| if (!ac.check()) { |
| return ZX_ERR_NO_MEMORY; |
| } |
| |
| auto* device_desc = composite->device_descriptor(); |
| uint8_t usb_class, usb_subclass, usb_protocol; |
| |
| if (interface_desc->bInterfaceClass == 0) { |
| usb_class = device_desc->bDeviceClass; |
| usb_subclass = device_desc->bDeviceSubClass; |
| usb_protocol = device_desc->bDeviceProtocol; |
| } else { |
| // class/subclass/protocol defined per-interface |
| usb_class = interface_desc->bInterfaceClass; |
| usb_subclass = interface_desc->bInterfaceSubClass; |
| usb_protocol = interface_desc->bInterfaceProtocol; |
| } |
| |
| auto status = interface->Init(interface_desc, desc_length, interface_desc->bInterfaceNumber, |
| usb_class, usb_subclass, usb_protocol); |
| if (status != ZX_OK) { |
| return status; |
| } |
| |
| status = interface->ConfigureEndpoints(interface_desc->bInterfaceNumber, 0); |
| if (status != ZX_OK) { |
| return status; |
| } |
| |
| *out_interface = interface; |
| return ZX_OK; |
| } |
| |
| zx_status_t UsbInterface::Create(zx_device_t* parent, UsbComposite* composite, |
| const ddk::UsbProtocolClient& usb, |
| const usb_interface_assoc_descriptor_t* assoc_desc, |
| size_t desc_length, fbl::RefPtr<UsbInterface>* out_interface) { |
| fbl::AllocChecker ac; |
| auto interface = |
| fbl::MakeRefCountedChecked<UsbInterface>(&ac, composite->zxdev(), composite, usb); |
| if (!ac.check()) { |
| return ZX_ERR_NO_MEMORY; |
| } |
| |
| auto* device_desc = composite->device_descriptor(); |
| uint8_t usb_class, usb_subclass, usb_protocol; |
| |
| if (assoc_desc->bFunctionClass == 0) { |
| usb_class = device_desc->bDeviceClass; |
| usb_subclass = device_desc->bDeviceSubClass; |
| usb_protocol = device_desc->bDeviceProtocol; |
| } else { |
| // class/subclass/protocol defined per-interface |
| usb_class = assoc_desc->bFunctionClass; |
| usb_subclass = assoc_desc->bFunctionSubClass; |
| usb_protocol = assoc_desc->bFunctionProtocol; |
| } |
| |
| // Interfaces in an IAD interface collection must be contiguous. |
| auto last_interface_id = assoc_desc->bFirstInterface + assoc_desc->bInterfaceCount - 1; |
| auto status = interface->Init(assoc_desc, desc_length, static_cast<uint8_t>(last_interface_id), |
| usb_class, usb_subclass, usb_protocol); |
| if (status != ZX_OK) { |
| return status; |
| } |
| |
| auto* header = reinterpret_cast<const usb_descriptor_header_t*>(assoc_desc); |
| auto* end = reinterpret_cast<const usb_descriptor_header_t*>( |
| reinterpret_cast<const uint8_t*>(assoc_desc) + desc_length); |
| |
| while (header < end) { |
| if (header->bDescriptorType == USB_DT_INTERFACE) { |
| auto* intf_desc = reinterpret_cast<const usb_interface_descriptor_t*>(header); |
| if (intf_desc->bAlternateSetting == 0) { |
| zx_status_t status = interface->ConfigureEndpoints(intf_desc->bInterfaceNumber, 0); |
| if (status != ZX_OK) { |
| return status; |
| } |
| } |
| } |
| header = NextDescriptor(header); |
| } |
| |
| *out_interface = interface; |
| return ZX_OK; |
| } |
| |
| zx_status_t UsbInterface::Init(const void* descriptors, size_t desc_length, |
| uint8_t last_interface_id, uint8_t usb_class, uint8_t usb_subclass, |
| uint8_t usb_protocol) { |
| fbl::AllocChecker ac; |
| auto desc_bytes = new (&ac) uint8_t[desc_length]; |
| if (!ac.check()) { |
| return ZX_ERR_NO_MEMORY; |
| } |
| memcpy(desc_bytes, descriptors, desc_length); |
| descriptors_.reset(desc_bytes, desc_length); |
| last_interface_id_ = last_interface_id; |
| usb_class_ = usb_class; |
| usb_subclass_ = usb_subclass; |
| usb_protocol_ = usb_protocol; |
| |
| return ZX_OK; |
| } |
| |
| zx_status_t UsbInterface::DdkGetProtocol(uint32_t proto_id, void* protocol) { |
| switch (proto_id) { |
| case ZX_PROTOCOL_USB: { |
| auto* proto = static_cast<usb_protocol_t*>(protocol); |
| proto->ctx = this; |
| proto->ops = &usb_protocol_ops_; |
| return ZX_OK; |
| } |
| case ZX_PROTOCOL_USB_COMPOSITE: { |
| auto* proto = static_cast<usb_composite_protocol_t*>(protocol); |
| proto->ctx = this; |
| proto->ops = &usb_composite_protocol_ops_; |
| return ZX_OK; |
| } |
| default: |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| } |
| |
| void UsbInterface::DdkUnbind(ddk::UnbindTxn txn) { txn.Reply(); } |
| |
| void UsbInterface::DdkRelease() { |
| // Release the reference now that devmgr no longer has a pointer to this object. |
| __UNUSED bool dummy = Release(); |
| } |
| |
| // for determining index into active_endpoints[] |
| // bEndpointAddress has 4 lower order bits, plus high bit to signify direction |
| // shift high bit to bit 4 so index is in range 0 - 31. |
| static inline uint8_t GetEndpointIndex(const usb_endpoint_descriptor_t* ep) { |
| return static_cast<uint8_t>(((ep)->bEndpointAddress & 0x0F) | ((ep)->bEndpointAddress >> 3)); |
| } |
| |
| zx_status_t UsbInterface::ConfigureEndpoints(uint8_t interface_id, uint8_t alt_setting) { |
| usb_endpoint_descriptor_t* new_endpoints[USB_MAX_EPS] = {}; |
| bool interface_endpoints[USB_MAX_EPS] = {}; |
| zx_status_t status = ZX_OK; |
| |
| // iterate through our descriptors to find which endpoints should be active |
| auto* header = reinterpret_cast<const usb_descriptor_header_t*>(descriptors_.data()); |
| auto* end = |
| reinterpret_cast<const usb_descriptor_header_t*>(descriptors_.data() + descriptors_.size()); |
| int cur_interface = -1; |
| |
| bool enable_endpoints = false; |
| while (header < end) { |
| if (header->bDescriptorType == USB_DT_INTERFACE) { |
| auto* intf_desc = reinterpret_cast<const usb_interface_descriptor_t*>(header); |
| cur_interface = intf_desc->bInterfaceNumber; |
| enable_endpoints = (intf_desc->bAlternateSetting == alt_setting); |
| } else if (header->bDescriptorType == USB_DT_ENDPOINT && cur_interface == interface_id) { |
| usb_endpoint_descriptor_t* ep = (usb_endpoint_descriptor_t*)header; |
| auto ep_index = GetEndpointIndex(ep); |
| interface_endpoints[ep_index] = true; |
| if (enable_endpoints) { |
| new_endpoints[ep_index] = ep; |
| } |
| } |
| header = NextDescriptor(header); |
| } |
| |
| // update to new set of endpoints |
| // FIXME - how do we recover if we fail half way through processing the endpoints? |
| for (size_t i = 0; i < countof(new_endpoints); i++) { |
| if (interface_endpoints[i]) { |
| auto* old_ep = active_endpoints_[i]; |
| auto* new_ep = new_endpoints[i]; |
| if (old_ep != new_ep) { |
| if (old_ep) { |
| zx_status_t ret = usb_.EnableEndpoint(old_ep, nullptr, false); |
| if (ret != ZX_OK) |
| status = ret; |
| } |
| if (new_ep) { |
| usb_ss_ep_comp_descriptor_t* ss_comp_desc = nullptr; |
| usb_descriptor_header_t* next = |
| (usb_descriptor_header_t*)((uint8_t*)new_ep + new_ep->bLength); |
| if (next + sizeof(*ss_comp_desc) <= end && |
| next->bDescriptorType == USB_DT_SS_EP_COMPANION) { |
| ss_comp_desc = (usb_ss_ep_comp_descriptor_t*)next; |
| } |
| zx_status_t ret = usb_.EnableEndpoint(new_ep, ss_comp_desc, true); |
| if (ret != ZX_OK) { |
| status = ret; |
| } |
| } |
| active_endpoints_[i] = new_ep; |
| } |
| } |
| } |
| return status; |
| } |
| |
| zx_status_t UsbInterface::UsbControlOut(uint8_t request_type, uint8_t request, uint16_t value, |
| uint16_t index, zx_time_t timeout, const void* write_buffer, |
| size_t write_size) { |
| return usb_.ControlOut(request_type, request, value, index, timeout, write_buffer, write_size); |
| } |
| |
| zx_status_t UsbInterface::UsbControlIn(uint8_t request_type, uint8_t request, uint16_t value, |
| uint16_t index, zx_time_t timeout, void* out_read_buffer, |
| size_t read_size, size_t* out_read_actual) { |
| return usb_.ControlIn(request_type, request, value, index, timeout, out_read_buffer, read_size, |
| out_read_actual); |
| } |
| |
| void UsbInterface::UsbRequestQueue(usb_request_t* usb_request, |
| const usb_request_complete_t* complete_cb) { |
| usb_.RequestQueue(usb_request, complete_cb); |
| } |
| |
| usb_speed_t UsbInterface::UsbGetSpeed() { return usb_.GetSpeed(); } |
| |
| zx_status_t UsbInterface::UsbSetInterface(uint8_t interface_number, uint8_t alt_setting) { |
| return composite_->SetInterface(interface_number, alt_setting); |
| } |
| |
| uint8_t UsbInterface::UsbGetConfiguration() { return usb_.GetConfiguration(); } |
| |
| zx_status_t UsbInterface::UsbSetConfiguration(uint8_t configuration) { |
| return usb_.SetConfiguration(configuration); |
| } |
| |
| zx_status_t UsbInterface::UsbEnableEndpoint(const usb_endpoint_descriptor_t* ep_desc, |
| const usb_ss_ep_comp_descriptor_t* ss_com_desc, |
| bool enable) { |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| |
| zx_status_t UsbInterface::UsbResetEndpoint(uint8_t ep_address) { |
| return usb_.ResetEndpoint(ep_address); |
| } |
| |
| zx_status_t UsbInterface::UsbResetDevice() { return usb_.ResetDevice(); } |
| |
| size_t UsbInterface::UsbGetMaxTransferSize(uint8_t ep_address) { |
| return usb_.GetMaxTransferSize(ep_address); |
| } |
| |
| uint32_t UsbInterface::UsbGetDeviceId() { return usb_.GetDeviceId(); } |
| |
| void UsbInterface::UsbGetDeviceDescriptor(usb_device_descriptor_t* out_desc) { |
| return usb_.GetDeviceDescriptor(out_desc); |
| } |
| |
| zx_status_t UsbInterface::UsbGetConfigurationDescriptorLength(uint8_t configuration, |
| size_t* out_length) { |
| return usb_.GetConfigurationDescriptorLength(configuration, out_length); |
| } |
| |
| zx_status_t UsbInterface::UsbGetConfigurationDescriptor(uint8_t configuration, |
| void* out_desc_buffer, size_t desc_size, |
| size_t* out_desc_actual) { |
| return usb_.GetConfigurationDescriptor(configuration, out_desc_buffer, desc_size, |
| out_desc_actual); |
| } |
| |
| size_t UsbInterface::UsbGetDescriptorsLength() { return descriptors_.size(); } |
| |
| void UsbInterface::UsbGetDescriptors(void* out_descs_buffer, size_t descs_size, |
| size_t* out_descs_actual) { |
| size_t length = descriptors_.size(); |
| if (length > descs_size) { |
| length = descs_size; |
| } |
| memcpy(out_descs_buffer, descriptors_.data(), length); |
| *out_descs_actual = length; |
| } |
| |
| size_t UsbInterface::UsbCompositeGetAdditionalDescriptorLength() { |
| auto* config = composite_->GetConfigurationDescriptor(); |
| auto* header = NextDescriptor(reinterpret_cast<const usb_descriptor_header_t*>(config)); |
| auto* end = reinterpret_cast<const usb_descriptor_header_t*>( |
| reinterpret_cast<const uint8_t*>(config) + le16toh(config->wTotalLength)); |
| |
| const usb_interface_descriptor_t* interface = nullptr; |
| while (header < end) { |
| if (header->bDescriptorType == USB_DT_INTERFACE) { |
| usb_interface_descriptor_t* test_intf = (usb_interface_descriptor_t*)header; |
| // We are only interested in descriptors past the last stored descriptor |
| // for the current interface. |
| if (test_intf->bAlternateSetting == 0 && test_intf->bInterfaceNumber > last_interface_id_) { |
| interface = test_intf; |
| break; |
| } |
| } |
| header = NextDescriptor(header); |
| } |
| if (!interface) { |
| return 0; |
| } |
| return reinterpret_cast<uintptr_t>(end) - reinterpret_cast<uintptr_t>(interface); |
| } |
| |
| zx_status_t UsbInterface::UsbCompositeGetAdditionalDescriptorList(uint8_t* out_desc_list, |
| size_t desc_count, |
| size_t* out_desc_actual) { |
| return composite_->GetAdditionalDescriptorList(last_interface_id_, out_desc_list, desc_count, |
| out_desc_actual); |
| } |
| |
| zx_status_t UsbInterface::UsbGetStringDescriptor(uint8_t desc_id, uint16_t lang_id, |
| uint16_t* out_lang_id, void* out_string_buffer, |
| size_t string_size, size_t* out_string_actual) { |
| return usb_.GetStringDescriptor(desc_id, lang_id, out_lang_id, out_string_buffer, string_size, |
| out_string_actual); |
| } |
| |
| zx_status_t UsbInterface::UsbCancelAll(uint8_t ep_address) { return usb_.CancelAll(ep_address); } |
| |
| uint64_t UsbInterface::UsbGetCurrentFrame() { return usb_.GetCurrentFrame(); } |
| |
| size_t UsbInterface::UsbGetRequestSize() { return usb_.GetRequestSize(); } |
| |
| zx_status_t UsbInterface::UsbCompositeClaimInterface(const usb_interface_descriptor_t* desc, |
| uint32_t length) { |
| auto status = composite_->ClaimInterface(desc->bInterfaceNumber); |
| if (status != ZX_OK) { |
| return status; |
| } |
| // Copy claimed interface descriptors to end of descriptor array. |
| fbl::AllocChecker ac; |
| size_t old_length = descriptors_.size(); |
| size_t new_length = old_length + length; |
| auto* new_descriptors = new (&ac) uint8_t[new_length]; |
| if (!ac.check()) { |
| return ZX_ERR_NO_MEMORY; |
| } |
| memcpy(new_descriptors, descriptors_.data(), old_length); |
| memcpy(new_descriptors + old_length, desc, length); |
| descriptors_.reset(new_descriptors, new_length); |
| |
| if (desc->bInterfaceNumber > last_interface_id_) { |
| last_interface_id_ = desc->bInterfaceNumber; |
| } |
| return ZX_OK; |
| } |
| |
| bool UsbInterface::ContainsInterface(uint8_t interface_id) { |
| auto* header = reinterpret_cast<const usb_descriptor_header_t*>(descriptors_.data()); |
| auto* end = |
| reinterpret_cast<const usb_descriptor_header_t*>(descriptors_.data() + descriptors_.size()); |
| |
| while (header < end) { |
| if (header->bDescriptorType == USB_DT_INTERFACE) { |
| auto* intf_desc = reinterpret_cast<const usb_interface_descriptor_t*>(header); |
| if (intf_desc->bInterfaceNumber == interface_id) { |
| return true; |
| } |
| } |
| header = NextDescriptor(header); |
| } |
| return false; |
| } |
| |
| zx_status_t UsbInterface::SetAltSetting(uint8_t interface_id, uint8_t alt_setting) { |
| zx_status_t status = ConfigureEndpoints(interface_id, alt_setting); |
| if (status != ZX_OK) { |
| return status; |
| } |
| |
| return UsbControlOut(USB_DIR_OUT | USB_TYPE_STANDARD | USB_RECIP_INTERFACE, USB_REQ_SET_INTERFACE, |
| alt_setting, interface_id, ZX_TIME_INFINITE, nullptr, 0); |
| } |
| |
| } // namespace usb_composite |