blob: cea4ed3ce885bee60b28e65d0520e93e91af012b [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 <driver/usb.h>
#include <stdlib.h>
#include <zircon/device/usb.h>
#include <zircon/hw/usb-video.h>
#include <fbl/vector.h>
#include "usb-video.h"
#include "usb-video-stream.h"
namespace {
// Parses the payload format descriptor and any corresponding frame descriptors.
// The result is stored in out_format.
zx_status_t parse_format(usb_video_vc_desc_header* format_desc,
usb_desc_iter_t* iter,
video::usb::UsbVideoFormat* out_format) {
uint8_t want_frame_type = 0;
int want_num_frame_descs = 0;
switch (format_desc->bDescriptorSubtype) {
case USB_VIDEO_VS_FORMAT_UNCOMPRESSED: {
usb_video_vs_uncompressed_format_desc* uncompressed_desc =
(usb_video_vs_uncompressed_format_desc *)format_desc;
zxlogf(TRACE,
"USB_VIDEO_VS_FORMAT_UNCOMPRESSED bNumFrameDescriptors %u bBitsPerPixel %u\n",
uncompressed_desc->bNumFrameDescriptors, uncompressed_desc->bBitsPerPixel);
want_frame_type = USB_VIDEO_VS_FRAME_UNCOMPRESSED;
out_format->index = uncompressed_desc->bFormatIndex;
out_format->bits_per_pixel = uncompressed_desc->bBitsPerPixel;
want_num_frame_descs = uncompressed_desc->bNumFrameDescriptors;
out_format->default_frame_index = uncompressed_desc->bDefaultFrameIndex;
break;
}
// TODO(jocelyndang): handle other formats.
default:
zxlogf(ERROR, "unsupported format bDescriptorSubtype %u\n",
format_desc->bDescriptorSubtype);
return ZX_ERR_NOT_SUPPORTED;
}
fbl::AllocChecker ac;
out_format->frame_descs.reserve(want_num_frame_descs, &ac);
if (!ac.check()) {
return ZX_ERR_NO_MEMORY;
}
// The format descriptor mut be immediately followed by its frame descriptors, if any.
int num_frame_descs_found = 0;
usb_descriptor_header_t* header;
while ((header = usb_desc_iter_peek(iter)) != NULL &&
header->bDescriptorType == USB_VIDEO_CS_INTERFACE &&
num_frame_descs_found < want_num_frame_descs) {
usb_video_vc_desc_header* format_desc = (usb_video_vc_desc_header *)header;
if (format_desc->bDescriptorSubtype != want_frame_type) {
break;
}
switch (format_desc->bDescriptorSubtype) {
case USB_VIDEO_VS_FRAME_UNCOMPRESSED: {
usb_video_vs_uncompressed_video_frame_desc* desc =
(usb_video_vs_uncompressed_video_frame_desc *)header;
// Intervals are specified in 100 ns units.
double framesPerSec = 1 / (desc->dwDefaultFrameInterval * 100 / 1e9);
zxlogf(TRACE, "USB_VIDEO_VS_FRAME_UNCOMPRESSED (%u x %u) %.2f frames / sec\n",
desc->wWidth, desc->wHeight, framesPerSec);
video::usb::UsbVideoFrameDesc frame_desc = {
.index = desc->bFrameIndex,
.default_frame_interval = desc->dwDefaultFrameInterval,
.width = desc->wWidth,
.height = desc->wHeight
};
out_format->frame_descs.push_back(frame_desc);
break;
}
default:
zxlogf(ERROR, "unhandled frame type: %u\n", format_desc->bDescriptorSubtype);
return ZX_ERR_NOT_SUPPORTED;
}
header = usb_desc_iter_next(iter);
num_frame_descs_found++;
}
if (num_frame_descs_found != want_num_frame_descs) {
zxlogf(ERROR, "missing %u frame descriptors\n",
want_num_frame_descs - num_frame_descs_found);
return ZX_ERR_INTERNAL;
}
// TODO(jocelyndang); parse still image frame and color matching descriptors.
return ZX_OK;
}
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;
}
usb_desc_iter_t iter;
status = usb_desc_iter_init(&usb, &iter);
if (status != ZX_OK) {
return status;
}
int video_source_index = 0;
fbl::Vector<video::usb::UsbVideoFormat> 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 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,
input_header, &formats, &streaming_settings);
if (status != ZX_OK) {
zxlogf(ERROR, "UsbVideoStream::Create failed: %d\n", status);
goto error_return;
}
}
formats.reset();
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:
zxlogf(TRACE, "USB_VIDEO_VC_HEADER\n");
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);
formats.reserve(input_header->bNumFormats, &ac);
if (!ac.check()) {
status = ZX_ERR_NO_MEMORY;
goto error_return;
}
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.size() >= 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;
}
video::usb::UsbVideoFormat format;
status = parse_format(vc_header, &iter, &format);
if (status == ZX_OK) {
formats.push_back(fbl::move(format));
}
// parse_format 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);
int per_mf = 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
};
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;
}
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, input_header, &formats, &streaming_settings);
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);
}