blob: c60dee2d5c2b80a52118fb68d4ce37627e9846da [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 {
// 8 bits for each RGB.
static constexpr uint32_t MJPEG_BITS_PER_PIXEL = 24;
camera::camera_proto::PixelFormat guid_to_pixel_format(uint8_t guid[GUID_LENGTH]) {
struct {
uint8_t guid[GUID_LENGTH];
camera::camera_proto::PixelFormat pixel_format;
} GUID_LUT[] = {
{ USB_VIDEO_GUID_YUY2_VALUE, YUY2 },
{ USB_VIDEO_GUID_NV12_VALUE, NV12 },
{ USB_VIDEO_GUID_M420_VALUE, M420 },
{ USB_VIDEO_GUID_I420_VALUE, I420 },
};
for (const auto& g : GUID_LUT) {
if (memcmp(g.guid, guid, GUID_LENGTH) == 0) {
return g.pixel_format;
}
}
return INVALID;
}
// 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->pixel_format =
guid_to_pixel_format(uncompressed_desc->guidFormat);
out_format->bits_per_pixel = uncompressed_desc->bBitsPerPixel;
want_num_frame_descs = uncompressed_desc->bNumFrameDescriptors;
out_format->default_frame_index = uncompressed_desc->bDefaultFrameIndex;
break;
}
case USB_VIDEO_VS_FORMAT_MJPEG: {
usb_video_vs_mjpeg_format_desc* mjpeg_desc =
(usb_video_vs_mjpeg_format_desc *)format_desc;
zxlogf(TRACE,
"USB_VIDEO_VS_FORMAT_MJPEG bNumFrameDescriptors %u bmFlags %d\n",
mjpeg_desc->bNumFrameDescriptors, mjpeg_desc->bmFlags);
want_frame_type = USB_VIDEO_VS_FRAME_MJPEG;
out_format->index = mjpeg_desc->bFormatIndex;
out_format->pixel_format = MJPEG;
out_format->bits_per_pixel = MJPEG_BITS_PER_PIXEL;
want_num_frame_descs = mjpeg_desc->bNumFrameDescriptors;
out_format->default_frame_index = mjpeg_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:
case USB_VIDEO_VS_FRAME_MJPEG: {
usb_video_vs_frame_desc* desc = (usb_video_vs_frame_desc *)header;
// Intervals are specified in 100 ns units.
double framesPerSec = 1 / (desc->dwDefaultFrameInterval * 100 / 1e9);
zxlogf(TRACE, "%s (%u x %u) %.2f frames / sec\n",
format_desc->bDescriptorSubtype == USB_VIDEO_VS_FRAME_UNCOMPRESSED ?
"USB_VIDEO_VS_FRAME_UNCOMPRESSED" : "USB_VIDEO_VS_FRAME_MJPEG",
desc->wWidth, desc->wHeight, framesPerSec);
video::usb::UsbVideoFrameDesc frame_desc = {
.index = desc->bFrameIndex,
.capture_type = STREAM,
.default_frame_interval = desc->dwDefaultFrameInterval,
.width = desc->wWidth,
.height = desc->wHeight,
.stride = desc->dwMaxVideoFrameBufferSize / 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 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, &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: {
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);
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);
// 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;
}
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, &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);
}