blob: c4cf303c107391b4a8e5850269561c44ee1531bc [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 <ddk/binding.h>
#include <ddk/debug.h>
#include <ddk/device.h>
#include <ddk/driver.h>
#include <ddk/protocol/usb.h>
#include <usb/usb.h>
#include <fbl/vector.h>
#include <stdlib.h>
#include <zircon/hw/usb/video.h>
#include "garnet/drivers/usb_video/usb-video-stream.h"
#include "garnet/drivers/usb_video/usb-video.h"
#include "garnet/drivers/usb_video/uvc_format.h"
namespace {
// The biggest size we are allowing USB descriptor strings to be.
// Technically, the bLength field for strings is one byte, so the
// max size for any string should be 255.
static constexpr size_t kUsbDescriptorStringSize = 512;
// Extract strings from the device description.
// Strings in the usb device description are represented as indices
// into the 'strings' section at the end. This function unpacks a specific
// string, given its index.
std::string FetchString(const usb_protocol_t& usb_proto,
uint8_t description_index) {
uint8_t str_buf[kUsbDescriptorStringSize];
size_t buflen = sizeof(str_buf);
uint16_t language_id = 0;
zx_status_t res = usb_get_string_descriptor(&usb_proto, description_index,
language_id, &language_id,
str_buf, buflen, &buflen);
if (res != ZX_OK) {
return std::string();
}
buflen = std::min(buflen, sizeof(str_buf));
return std::string(reinterpret_cast<char*>(str_buf), buflen);
}
// Extract vendor and product information from the USB device description.
video::usb::UsbDeviceInfo GetDeviceInfo(const usb_protocol_t& usb_proto) {
usb_device_descriptor_t usb_dev_desc;
video::usb::UsbDeviceInfo device_info;
// Fetch our top level device descriptor, so we know stuff like the values
// of our VID/PID.
usb_get_device_descriptor(&usb_proto, &usb_dev_desc);
device_info.vendor_id = usb_dev_desc.idVendor;
device_info.product_id = usb_dev_desc.idProduct;
// Attempt to fetch the string descriptors for our manufacturer name,
// product name, and serial number.
if (usb_dev_desc.iManufacturer) {
device_info.manufacturer =
FetchString(usb_proto, usb_dev_desc.iManufacturer);
}
if (usb_dev_desc.iProduct) {
device_info.product_name = FetchString(usb_proto, usb_dev_desc.iProduct);
}
if (usb_dev_desc.iSerialNumber) {
device_info.serial_number =
FetchString(usb_proto, usb_dev_desc.iSerialNumber);
}
return device_info;
}
zx_status_t usb_video_parse_descriptors(void* ctx, zx_device_t* device,
void** cookie) {
usb_protocol_t usb;
zx_status_t status = device_get_protocol(device, ZX_PROTOCOL_USB, &usb);
if (status != ZX_OK) {
return status;
}
size_t parent_req_size = usb_get_request_size(&usb);
ZX_DEBUG_ASSERT(parent_req_size != 0);
// Grab the device information, so we can use it when creating the
// UsbVideoStream.
auto device_info = GetDeviceInfo(usb);
usb_desc_iter_t iter;
status = usb_desc_iter_init(&usb, &iter);
if (status != ZX_OK) {
return status;
}
int video_source_index = 0;
video::usb::UvcFormatList formats;
fbl::Vector<video::usb::UsbVideoStreamingSetting> streaming_settings;
fbl::AllocChecker ac;
usb_descriptor_header_t* header;
// Most recent USB interface descriptor.
usb_interface_descriptor_t* intf = NULL;
// Most recent video control header.
usb_video_vc_header_desc* control_header = NULL;
// Most recent video streaming input header.
usb_video_vs_input_header_desc* input_header = NULL;
while ((header = usb_desc_iter_next(&iter)) != NULL) {
switch (header->bDescriptorType) {
case USB_DT_INTERFACE_ASSOCIATION: {
usb_interface_assoc_descriptor_t* assoc_desc =
(usb_interface_assoc_descriptor_t*)header;
zxlogf(TRACE,
"USB_DT_INTERFACE_ASSOCIATION bInterfaceCount: %u "
"bFirstInterface: %u\n",
assoc_desc->bInterfaceCount, assoc_desc->bFirstInterface);
break;
}
case USB_DT_INTERFACE: {
intf = (usb_interface_descriptor_t*)header;
if (intf->bInterfaceClass == USB_CLASS_VIDEO) {
if (intf->bInterfaceSubClass == USB_SUBCLASS_VIDEO_CONTROL) {
zxlogf(TRACE, "interface USB_SUBCLASS_VIDEO_CONTROL\n");
break;
} else if (intf->bInterfaceSubClass == USB_SUBCLASS_VIDEO_STREAMING) {
zxlogf(TRACE,
"interface USB_SUBCLASS_VIDEO_STREAMING bAlternateSetting: "
"%d\n",
intf->bAlternateSetting);
// Encountered a new video streaming interface.
if (intf->bAlternateSetting == 0) {
// Create a video source if we've successfully parsed a VS
// interface.
if (formats.Size() > 0) {
status = video::usb::UsbVideoStream::Create(
device, &usb, video_source_index++, intf, control_header,
input_header, std::move(formats), &streaming_settings,
std::move(device_info), parent_req_size);
if (status != ZX_OK) {
zxlogf(ERROR, "UsbVideoStream::Create failed: %d\n", status);
goto error_return;
}
}
// formats.reset(); //TODO(garratt): what to do here?
streaming_settings.reset();
input_header = NULL;
}
break;
} else if (intf->bInterfaceSubClass ==
USB_SUBCLASS_VIDEO_INTERFACE_COLLECTION) {
zxlogf(TRACE,
"interface USB_SUBCLASS_VIDEO_INTERFACE_COLLECTION "
"bAlternateSetting: %d\n",
intf->bAlternateSetting);
break;
}
}
zxlogf(TRACE, "USB_DT_INTERFACE %d %d %d\n", intf->bInterfaceClass,
intf->bInterfaceSubClass, intf->bInterfaceProtocol);
break;
}
case USB_VIDEO_CS_INTERFACE: {
usb_video_vc_desc_header* vc_header = (usb_video_vc_desc_header*)header;
if (intf->bInterfaceSubClass == USB_SUBCLASS_VIDEO_CONTROL) {
switch (vc_header->bDescriptorSubtype) {
case USB_VIDEO_VC_HEADER: {
control_header = (usb_video_vc_header_desc*)header;
zxlogf(TRACE, "USB_VIDEO_VC_HEADER dwClockFrequency: %u\n",
control_header->dwClockFrequency);
break;
}
case USB_VIDEO_VC_INPUT_TERMINAL: {
usb_video_vc_input_terminal_desc* desc =
(usb_video_vc_input_terminal_desc*)header;
zxlogf(TRACE, "USB_VIDEO_VC_INPUT_TERMINAL wTerminalType: %04X\n",
le16toh(desc->wTerminalType));
break;
}
case USB_VIDEO_VC_OUTPUT_TERMINAL: {
usb_video_vc_output_terminal_desc* desc =
(usb_video_vc_output_terminal_desc*)header;
zxlogf(TRACE,
"USB_VIDEO_VC_OUTPUT_TERMINAL wTerminalType: %04X\n",
le16toh(desc->wTerminalType));
break;
}
case USB_VIDEO_VC_SELECTOR_UNIT:
zxlogf(TRACE, "USB_VIDEO_VC_SELECTOR_UNIT\n");
break;
case USB_VIDEO_VC_PROCESSING_UNIT:
zxlogf(TRACE, "USB_VIDEO_VC_PROCESSING_UNIT\n");
break;
case USB_VIDEO_VC_EXTENSION_UNIT:
zxlogf(TRACE, "USB_VIDEO_VS_EXTENSION_TYPE\n");
break;
case USB_VIDEO_VC_ENCODING_UNIT:
zxlogf(TRACE, "USB_VIDEO_VS_ENCODING_TYPE\n");
break;
}
} else if (intf->bInterfaceSubClass == USB_SUBCLASS_VIDEO_STREAMING) {
switch (vc_header->bDescriptorSubtype) {
case USB_VIDEO_VS_INPUT_HEADER: {
input_header = (usb_video_vs_input_header_desc*)header;
zxlogf(TRACE,
"USB_VIDEO_VS_INPUT_HEADER bNumFormats: %u "
"bEndpointAddress 0x%x\n",
input_header->bNumFormats, input_header->bEndpointAddress);
break;
}
case USB_VIDEO_VS_OUTPUT_HEADER:
zxlogf(TRACE, "USB_VIDEO_VS_OUTPUT_HEADER\n");
break;
case USB_VIDEO_VS_FORMAT_UNCOMPRESSED:
case USB_VIDEO_VS_FORMAT_MJPEG:
case USB_VIDEO_VS_FORMAT_MPEG2TS:
case USB_VIDEO_VS_FORMAT_DV:
case USB_VIDEO_VS_FORMAT_FRAME_BASED:
case USB_VIDEO_VS_FORMAT_STREAM_BASED:
case USB_VIDEO_VS_FORMAT_H264:
case USB_VIDEO_VS_FORMAT_H264_SIMULCAST:
case USB_VIDEO_VS_FORMAT_VP8:
case USB_VIDEO_VS_FORMAT_VP8_SIMULCAST:
if (formats.number_of_formats() >= input_header->bNumFormats) {
// More formats than expected, this should never happen.
zxlogf(
ERROR,
"skipping unexpected format %u, already have %d formats\n",
vc_header->bDescriptorSubtype, input_header->bNumFormats);
break;
}
status = formats.ParseUsbDescriptor(vc_header, &iter);
if (status == ZX_ERR_NO_MEMORY) {
goto error_return;
}
// ParseUsbDescriptor will return an error if it's an unsupported
// format, but we shouldn't return early in case the device has
// other formats we support.
break;
}
} else if (intf->bInterfaceSubClass ==
USB_SUBCLASS_VIDEO_INTERFACE_COLLECTION) {
zxlogf(TRACE, "USB_SUBCLASS_VIDEO_INTERFACE_COLLECTION\n");
}
break;
}
case USB_DT_ENDPOINT: {
usb_endpoint_descriptor_t* endp = (usb_endpoint_descriptor_t*)header;
const char* direction =
(endp->bEndpointAddress & USB_ENDPOINT_DIR_MASK) == USB_ENDPOINT_IN
? "IN"
: "OUT";
uint16_t max_packet_size = usb_ep_max_packet(endp);
// The additional transactions per microframe value is extracted
// from bits 12..11 of wMaxPacketSize, so it fits in a uint8_t.
uint8_t per_mf =
static_cast<uint8_t>(usb_ep_add_mf_transactions(endp) + 1);
zxlogf(TRACE,
"USB_DT_ENDPOINT %s bEndpointAddress 0x%x packet size %d, %d / "
"mf\n",
direction, endp->bEndpointAddress, max_packet_size, per_mf);
// There may be another still image endpoint, so check the address
// matches the input header.
if (input_header &&
endp->bEndpointAddress == input_header->bEndpointAddress) {
video::usb::UsbVideoStreamingSetting setting = {
.alt_setting = intf->bAlternateSetting,
.transactions_per_microframe = per_mf,
.max_packet_size = max_packet_size,
.ep_type = usb_ep_type(endp)};
streaming_settings.push_back(setting, &ac);
if (!ac.check()) {
status = ZX_ERR_NO_MEMORY;
goto error_return;
}
}
break;
}
case USB_VIDEO_CS_ENDPOINT: {
usb_video_vc_interrupt_endpoint_desc* desc =
(usb_video_vc_interrupt_endpoint_desc*)header;
zxlogf(TRACE, "USB_VIDEO_CS_ENDPOINT wMaxTransferSize %u\n",
desc->wMaxTransferSize);
break;
}
case USB_DT_SS_EP_COMPANION: {
if (streaming_settings.is_empty()) {
status = ZX_ERR_BAD_STATE;
goto error_return;
}
auto& settings = streaming_settings[streaming_settings.size() - 1];
if (settings.ep_type == USB_ENDPOINT_ISOCHRONOUS) {
usb_ss_ep_comp_descriptor_t* desc =
(usb_ss_ep_comp_descriptor_t*)header;
if (usb_ss_ep_comp_isoc_comp(desc)) {
auto next = usb_desc_iter_next(&iter);
if (next == nullptr ||
next->bDescriptorType != USB_DT_SS_ISOCH_EP_COMPANION) {
status = ZX_ERR_BAD_STATE;
goto error_return;
}
usb_ss_isoch_ep_comp_descriptor_t* next_desc =
(usb_ss_isoch_ep_comp_descriptor_t*)next;
uint32_t denom = (desc->bMaxBurst + 1) * settings.max_packet_size;
settings.transactions_per_microframe =
(uint8_t)((next_desc->dwBytesPerInterval + denom - 1) / denom);
} else {
settings.transactions_per_microframe = (uint8_t)(
(desc->bMaxBurst + 1) * (usb_ss_ep_comp_isoc_mult(desc) + 1));
}
break;
}
// fall through for unhandled superspeed bulk endpoint
}
default:
zxlogf(TRACE, "unknown DT %d\n", header->bDescriptorType);
break;
}
}
if (formats.Size() > 0) {
status = video::usb::UsbVideoStream::Create(
device, &usb, video_source_index++, intf, control_header, input_header,
std::move(formats), &streaming_settings, std::move(device_info), parent_req_size);
if (status != ZX_OK) {
zxlogf(ERROR, "UsbVideoStream::Create failed: %d\n", status);
}
}
error_return:
usb_desc_iter_release(&iter);
return status;
}
} // namespace
extern "C" zx_status_t usb_video_bind(void* ctx, zx_device_t* device,
void** cookie) {
return usb_video_parse_descriptors(ctx, device, cookie);
}