blob: 53475de8b396872c194c763fea00bb6b9e2323bf [file] [log] [blame]
// 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 "src/devices/usb/drivers/usb-composite/usb-interface.h"
#include <fuchsia/hardware/usb/c/banjo.h>
#include <fuchsia/hardware/usb/composite/c/banjo.h>
#include <lib/ddk/debug.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <usb/usb-request.h>
#include "src/devices/usb/drivers/usb-composite/usb-composite.h"
namespace usb_composite {
zx::result<fidl::ClientEnd<fuchsia_io::Directory>> UsbInterface::Create(
zx_device_t* parent, UsbComposite* composite, const ddk::UsbProtocolClient& usb,
fidl::ClientEnd<fuchsia_hardware_usb::Usb> usb_new,
const usb_interface_descriptor_t* interface_desc, size_t desc_length,
async_dispatcher_t* dispatcher, std::unique_ptr<UsbInterface>* out_interface) {
fbl::AllocChecker ac;
auto interface = std::make_unique<UsbInterface>(composite->zxdev(), composite, usb,
std::move(usb_new), dispatcher);
if (!interface) {
return zx::error(ZX_ERR_NO_MEMORY);
}
auto* device_desc = composite->device_descriptor();
uint8_t usb_class, usb_subclass, usb_protocol;
if (interface_desc->b_interface_class == 0) {
usb_class = device_desc->b_device_class;
usb_subclass = device_desc->b_device_sub_class;
usb_protocol = device_desc->b_device_protocol;
} else {
// class/subclass/protocol defined per-interface
usb_class = interface_desc->b_interface_class;
usb_subclass = interface_desc->b_interface_sub_class;
usb_protocol = interface_desc->b_interface_protocol;
}
auto status = interface->Init(interface_desc, desc_length, interface_desc->b_interface_number,
usb_class, usb_subclass, usb_protocol);
if (status != ZX_OK) {
return zx::error(status);
}
status = interface->ConfigureEndpoints(interface_desc->b_interface_number, 0);
if (status != ZX_OK) {
return zx::error(status);
}
auto result = interface->ServeOutgoing(dispatcher);
if (result.is_ok()) {
*out_interface = std::move(interface);
}
return result;
}
zx::result<fidl::ClientEnd<fuchsia_io::Directory>> UsbInterface::Create(
zx_device_t* parent, UsbComposite* composite, const ddk::UsbProtocolClient& usb,
fidl::ClientEnd<fuchsia_hardware_usb::Usb> usb_new,
const usb_interface_assoc_descriptor_t* assoc_desc, size_t desc_length,
async_dispatcher_t* dispatcher, std::unique_ptr<UsbInterface>* out_interface) {
fbl::AllocChecker ac;
auto interface = std::make_unique<UsbInterface>(composite->zxdev(), composite, usb,
std::move(usb_new), dispatcher);
if (!interface) {
return zx::error(ZX_ERR_NO_MEMORY);
}
auto* device_desc = composite->device_descriptor();
uint8_t usb_class, usb_subclass, usb_protocol;
if (assoc_desc->b_function_class == 0) {
usb_class = device_desc->b_device_class;
usb_subclass = device_desc->b_device_sub_class;
usb_protocol = device_desc->b_device_protocol;
} else {
// class/subclass/protocol defined per-interface
usb_class = assoc_desc->b_function_class;
usb_subclass = assoc_desc->b_function_sub_class;
usb_protocol = assoc_desc->b_function_protocol;
}
// Interfaces in an IAD interface collection must be contiguous.
auto last_interface_id = assoc_desc->b_first_interface + assoc_desc->b_interface_count - 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 zx::error(status);
}
usb::UnownedInterfaceList assoc_list(const_cast<usb_interface_assoc_descriptor_t*>(assoc_desc),
desc_length, true);
for (const auto& intf : assoc_list) {
if (!intf.association() || intf.association() != assoc_desc) {
break;
}
if (!intf.descriptor()) {
zxlogf(ERROR, "Malformed USB descriptor detected!");
return zx::error(ZX_ERR_INVALID_ARGS);
}
zx_status_t status = interface->ConfigureEndpoints(intf.descriptor()->b_interface_number, 0);
if (status != ZX_OK) {
return zx::error(status);
}
}
auto result = interface->ServeOutgoing(dispatcher);
if (result.is_ok()) {
*out_interface = std::move(interface);
}
return result;
}
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) {
composite_->RemoveInterface(this);
txn.Reply();
}
void UsbInterface::DdkRelease() { delete this; }
// for determining index into active_endpoints[]
// b_endpoint_address 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)->b_endpoint_address & 0x0F) | ((ep)->b_endpoint_address >> 3));
}
zx_status_t UsbInterface::ConfigureEndpoints(uint8_t interface_id, uint8_t alt_setting) {
std::optional<usb::Endpoint> 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
usb::UnownedInterfaceList intf_list(reinterpret_cast<void*>(descriptors_.data()),
descriptors_.size(), false);
bool enable_endpoints = false;
for (const auto& intf : intf_list) {
if (!intf.descriptor()) {
zxlogf(ERROR, "Malformed USB descriptor detected!");
return false;
}
if (intf.descriptor()->b_interface_number != interface_id) {
continue;
}
enable_endpoints = (intf.descriptor()->b_alternate_setting == alt_setting);
for (const auto& ep : intf.GetEndpointList()) {
auto ep_index = GetEndpointIndex(ep.descriptor());
interface_endpoints[ep_index] = true;
if (enable_endpoints) {
new_endpoints[ep_index] = ep;
}
}
}
// 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 < std::size(new_endpoints); i++) {
if (interface_endpoints[i]) {
auto* old_ep = active_endpoints_[i];
auto new_ep = new_endpoints[i];
if (old_ep != (new_ep.has_value() ? new_ep->descriptor() : nullptr)) {
if (old_ep) {
zx_status_t ret = usb_.EnableEndpoint(old_ep, nullptr, false);
if (ret != ZX_OK) {
zxlogf(ERROR, "EnableEndpoint failed: %s", zx_status_get_string(ret));
status = ret;
}
}
if (new_ep) {
zx_status_t ret = usb_.EnableEndpoint(new_ep->descriptor(),
new_ep->ss_companion().value_or(nullptr), true);
if (ret != ZX_OK) {
zxlogf(ERROR, "EnableEndpoint failed: %s", zx_status_get_string(ret));
status = ret;
}
}
active_endpoints_[i] = new_ep->descriptor();
}
}
}
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 uint8_t* 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, uint8_t* 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_callback_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,
uint8_t* 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(uint8_t* 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();
usb::UnownedInterfaceList intf_list(const_cast<usb_configuration_descriptor_t*>(config),
le16toh(config->w_total_length), true);
for (const auto& intf : intf_list) {
if (!intf.descriptor()) {
zxlogf(ERROR, "Malformed USB descriptor detected!");
return false;
}
// We are only interested in descriptors past the last stored descriptor
// for the current interface.
if (intf.descriptor()->b_interface_number > last_interface_id_) {
return le16toh(config->w_total_length) -
(reinterpret_cast<uintptr_t>(intf.descriptor()) - reinterpret_cast<uintptr_t>(config));
}
}
return 0;
}
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, uint8_t* 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->b_interface_number);
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->b_interface_number > last_interface_id_) {
last_interface_id_ = desc->b_interface_number;
}
return ZX_OK;
}
bool UsbInterface::ContainsInterface(uint8_t interface_id) {
usb::UnownedInterfaceList intf_list(reinterpret_cast<void*>(descriptors_.data()),
descriptors_.size(), true);
for (const auto& intf : intf_list) {
if (!intf.descriptor()) {
zxlogf(ERROR, "Malformed USB descriptor detected!");
return false;
}
if (intf.descriptor()->b_interface_number == interface_id) {
return true;
}
}
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