| // 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); |
| } |