| // 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/debug.h> |
| #include <usb/usb-request.h> |
| #include <fbl/algorithm.h> |
| #include <fbl/auto_lock.h> |
| #include <fbl/ref_ptr.h> |
| #include <fbl/unique_ptr.h> |
| #include <fbl/vector.h> |
| #include <lib/async/cpp/task.h> |
| #include <lib/fidl/cpp/binding.h> |
| #include <lib/fidl/cpp/binding_set.h> |
| #include <lib/zx/vmar.h> |
| #include <stdlib.h> |
| #include <string.h> |
| |
| #include "garnet/drivers/usb_video/camera_control_impl.h" |
| #include "garnet/drivers/usb_video/usb-video-stream.h" |
| #include "garnet/drivers/usb_video/video-util.h" |
| |
| namespace video { |
| namespace usb { |
| |
| static constexpr uint32_t MAX_OUTSTANDING_REQS = 8; |
| |
| // Only keep the first 11 bits of the USB SOF (Start of Frame) values. |
| // The payload header SOF values only have 11 bits before wrapping around, |
| // whereas the XHCI host returns 64 bits. |
| static constexpr uint16_t USB_SOF_MASK = 0x7FF; |
| |
| fbl::unique_ptr<async::Loop> UsbVideoStream::fidl_dispatch_loop_ = nullptr; |
| |
| UsbVideoStream::UsbVideoStream(zx_device_t* parent, usb_protocol_t* usb, |
| UvcFormatList format_list, |
| fbl::Vector<UsbVideoStreamingSetting>* settings, |
| UsbDeviceInfo device_info, size_t parent_req_size) |
| : UsbVideoStreamBase(parent), |
| usb_(*usb), |
| format_list_(std::move(format_list)), |
| // TODO(CAM-13): Cleanup passing of settings |
| streaming_settings_(std::move(*settings)), |
| parent_req_size_(parent_req_size), |
| device_info_(std::move(device_info)) { |
| if (fidl_dispatch_loop_ == nullptr) { |
| fidl_dispatch_loop_ = |
| fbl::make_unique<async::Loop>(&kAsyncLoopConfigNoAttachToThread); |
| fidl_dispatch_loop_->StartThread(); |
| } |
| } |
| |
| UsbVideoStream::~UsbVideoStream() { |
| // List may not have been initialized. |
| if (free_reqs_.next) { |
| while (!list_is_empty(&free_reqs_)) { |
| usb_request_release(usb_req_list_remove_head(&free_reqs_, parent_req_size_)); |
| } |
| } |
| } |
| |
| void UsbVideoStream::RequestCompleteCallback(void* ctx, usb_request_t* request) { |
| ZX_DEBUG_ASSERT(ctx != nullptr); |
| reinterpret_cast<UsbVideoStream*>(ctx)->RequestComplete(request); |
| } |
| |
| // static |
| zx_status_t UsbVideoStream::Create( |
| zx_device_t* device, usb_protocol_t* usb, int index, |
| usb_interface_descriptor_t* intf, usb_video_vc_header_desc* control_header, |
| usb_video_vs_input_header_desc* input_header, UvcFormatList format_list, |
| fbl::Vector<UsbVideoStreamingSetting>* settings, |
| UsbDeviceInfo device_info, size_t parent_req_size) { |
| if (!usb || !intf || !control_header || !input_header || !settings || |
| settings->size() == 0) { |
| return ZX_ERR_INVALID_ARGS; |
| } |
| |
| auto dev = fbl::unique_ptr<UsbVideoStream>(new UsbVideoStream( |
| device, usb, std::move(format_list), settings, std::move(device_info), parent_req_size)); |
| |
| char name[ZX_DEVICE_NAME_MAX]; |
| snprintf(name, sizeof(name), "usb-video-source-%d", index); |
| |
| auto status = dev->Bind(name, intf, control_header, input_header); |
| if (status == ZX_OK) { |
| // devmgr is now in charge of the memory for dev |
| __UNUSED auto ptr = dev.release(); |
| } |
| return status; |
| } |
| |
| zx_status_t UsbVideoStream::Bind(const char* devname, |
| usb_interface_descriptor_t* intf, |
| usb_video_vc_header_desc* control_header, |
| usb_video_vs_input_header_desc* input_header) { |
| iface_num_ = intf->bInterfaceNumber; |
| clock_frequency_hz_ = control_header->dwClockFrequency; |
| usb_ep_addr_ = input_header->bEndpointAddress; |
| |
| uint32_t max_bandwidth = 0; |
| for (const auto& setting : streaming_settings_) { |
| uint32_t bandwidth = setting_bandwidth(setting); |
| if (bandwidth > max_bandwidth) { |
| max_bandwidth = bandwidth; |
| } |
| |
| // The streaming settings should all be of the same type, |
| // either all USB_ENDPOINT_BULK or all USB_ENDPOINT_ISOCHRONOUS. |
| if (streaming_ep_type_ != USB_ENDPOINT_INVALID && |
| streaming_ep_type_ != setting.ep_type) { |
| zxlogf(ERROR, "mismatched EP types: %u and %u\n", streaming_ep_type_, |
| setting.ep_type); |
| return ZX_ERR_BAD_STATE; |
| } |
| streaming_ep_type_ = setting.ep_type; |
| } |
| |
| // A video streaming interface containing a bulk endpoint for streaming |
| // shall support only alternate setting zero. |
| if (streaming_ep_type_ == USB_ENDPOINT_BULK && |
| (streaming_settings_.size() > 1 || |
| streaming_settings_.get()->alt_setting != 0)) { |
| zxlogf(ERROR, "invalid streaming settings for bulk endpoint\n"); |
| return ZX_ERR_BAD_STATE; |
| } |
| |
| { |
| fbl::AutoLock lock(&lock_); |
| |
| list_initialize(&free_reqs_); |
| num_free_reqs_ = 0; |
| |
| // For isochronous transfers we know the maximum payload size to |
| // use for the usb request size. |
| // |
| // For bulk transfers we can't allocate usb requests until we get |
| // the maximum payload size from stream negotiation. |
| if (streaming_ep_type_ == USB_ENDPOINT_ISOCHRONOUS) { |
| zx_status_t status = AllocUsbRequestsLocked(max_bandwidth); |
| if (status != ZX_OK) { |
| return status; |
| } |
| } |
| } |
| return UsbVideoStreamBase::DdkAdd(devname); |
| } |
| |
| zx_status_t UsbVideoStream::AllocUsbRequestsLocked(uint64_t size) { |
| if (streaming_state_ != StreamingState::STOPPED) { |
| return ZX_ERR_BAD_STATE; |
| } |
| if (size <= allocated_req_size_) { |
| // Can reuse existing usb requests. |
| return ZX_OK; |
| } |
| // Need to allocate new usb requests, release any existing ones. |
| while (!list_is_empty(&free_reqs_)) { |
| usb_request_release(usb_req_list_remove_head(&free_reqs_, parent_req_size_)); |
| } |
| |
| zxlogf(TRACE, "allocating %d usb requests of size %lu\n", |
| MAX_OUTSTANDING_REQS, size); |
| |
| uint64_t req_size = parent_req_size_ + sizeof(usb_req_internal_t); |
| zx_status_t status; |
| for (uint32_t i = 0; i < MAX_OUTSTANDING_REQS; i++) { |
| usb_request_t* req; |
| status = usb_request_alloc(&req, size, usb_ep_addr_, req_size); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "usb_request_alloc failed: %d\n", status); |
| return status; |
| } |
| |
| status = usb_req_list_add_head(&free_reqs_, req, parent_req_size_); |
| ZX_DEBUG_ASSERT(status == ZX_OK); |
| num_free_reqs_++; |
| num_allocated_reqs_++; |
| } |
| allocated_req_size_ = size; |
| return ZX_OK; |
| } |
| |
| zx_status_t UsbVideoStream::TryFormatLocked(uint8_t format_index, |
| uint8_t frame_index, |
| uint32_t default_frame_interval) { |
| zxlogf(INFO, "trying format %u, frame desc %u\n", format_index, frame_index); |
| |
| usb_video_vc_probe_and_commit_controls proposal; |
| memset(&proposal, 0, sizeof(usb_video_vc_probe_and_commit_controls)); |
| proposal.bmHint = USB_VIDEO_BM_HINT_FRAME_INTERVAL; |
| proposal.bFormatIndex = format_index; |
| |
| // TODO(garratt): Some formats do not have frame descriptors. |
| proposal.bFrameIndex = frame_index; |
| proposal.dwFrameInterval = default_frame_interval; |
| |
| usb_video_vc_probe_and_commit_controls result; |
| zx_status_t status = |
| usb_video_negotiate_probe(&usb_, iface_num_, &proposal, &result); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "usb_video_negotiate_probe failed: %d\n", status); |
| return status; |
| } |
| |
| // TODO(jocelyndang): we should calculate this ourselves instead |
| // of reading the reported value, as it is incorrect in some devices. |
| uint32_t required_bandwidth = result.dwMaxPayloadTransferSize; |
| |
| const UsbVideoStreamingSetting* best_setting = nullptr; |
| // Find a setting that supports the required bandwidth. |
| for (const auto& setting : streaming_settings_) { |
| uint32_t bandwidth = setting_bandwidth(setting); |
| // For bulk transfers, we use the first (and only) setting. |
| if (setting.ep_type == USB_ENDPOINT_BULK || |
| bandwidth >= required_bandwidth) { |
| best_setting = &setting; |
| break; |
| } |
| } |
| if (!best_setting) { |
| zxlogf(ERROR, "could not find a setting with bandwidth >= %u\n", |
| required_bandwidth); |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| |
| status = usb_video_negotiate_commit(&usb_, iface_num_, &result); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "usb_video_negotiate_commit failed: %d\n", status); |
| return status; |
| } |
| |
| // Negotiation succeeded, copy the results out. |
| memcpy(&negotiation_result_, &result, |
| sizeof(usb_video_vc_probe_and_commit_controls)); |
| cur_streaming_setting_ = best_setting; |
| |
| // Round frame size up to a whole number of pages, to allow mapping the frames |
| // individually to vmars. |
| max_frame_size_ = ROUNDUP(negotiation_result_.dwMaxVideoFrameSize, PAGE_SIZE); |
| |
| if (negotiation_result_.dwClockFrequency != 0) { |
| // This field is optional. If it isn't present, we instead |
| // would use the default value provided in the video control header. |
| clock_frequency_hz_ = negotiation_result_.dwClockFrequency; |
| } |
| |
| switch (streaming_ep_type_) { |
| case USB_ENDPOINT_ISOCHRONOUS: |
| // Isochronous payloads will always fit within a single usb request. |
| send_req_size_ = setting_bandwidth(*cur_streaming_setting_); |
| break; |
| case USB_ENDPOINT_BULK: { |
| // If the size of a payload is greater than the max usb request size, |
| // we will have to split it up in multiple requests. |
| send_req_size_ = fbl::min( |
| usb_get_max_transfer_size(&usb_, usb_ep_addr_), |
| static_cast<uint64_t>(negotiation_result_.dwMaxPayloadTransferSize)); |
| break; |
| } |
| default: |
| zxlogf(ERROR, "unknown EP type: %d\n", streaming_ep_type_); |
| return ZX_ERR_BAD_STATE; |
| } |
| |
| zxlogf(INFO, "configured video: format index %u frame index %u\n", |
| format_index, frame_index); |
| zxlogf(INFO, "alternate setting %d, packet size %u transactions per mf %u\n", |
| cur_streaming_setting_->alt_setting, |
| cur_streaming_setting_->max_packet_size, |
| cur_streaming_setting_->transactions_per_microframe); |
| |
| return AllocUsbRequestsLocked(send_req_size_); |
| } |
| |
| zx_status_t UsbVideoStream::DdkIoctl(uint32_t op, const void* in_buf, |
| size_t in_len, void* out_buf, |
| size_t out_len, size_t* out_actual) { |
| // The only IOCTL we support is get channel. |
| if (op != CAMERA_IOCTL_GET_CHANNEL) { |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| if ((out_buf == nullptr) || (out_actual == nullptr) || |
| (out_len != sizeof(zx_handle_t))) { |
| return ZX_ERR_INVALID_ARGS; |
| } |
| |
| fbl::AutoLock lock(&lock_); |
| |
| if (camera_control_ != nullptr) { |
| zxlogf(ERROR, "Camera Control already running\n"); |
| // TODO(CAM-XXX): support multiple concurrent clients. |
| return ZX_ERR_ACCESS_DENIED; |
| } |
| |
| fidl::InterfaceHandle<fuchsia::camera::Control> control_handle; |
| fidl::InterfaceRequest<fuchsia::camera::Control> control_interface = |
| control_handle.NewRequest(); |
| |
| if (control_interface.is_valid()) { |
| camera_control_ = fbl::make_unique<camera::ControlImpl>( |
| this, std::move(control_interface), fidl_dispatch_loop_->dispatcher(), |
| [this] { |
| fbl::AutoLock lock(&lock_); |
| |
| camera_control_.reset(); |
| }); |
| |
| *(reinterpret_cast<zx_handle_t*>(out_buf)) = |
| control_handle.TakeChannel().release(); |
| *out_actual = sizeof(zx_handle_t); |
| |
| return ZX_OK; |
| } else { |
| return ZX_ERR_NO_RESOURCES; |
| } |
| } |
| |
| zx_status_t UsbVideoStream::GetFormats( |
| fidl::VectorPtr<fuchsia::camera::VideoFormat>& formats) { |
| fbl::AutoLock lock(&lock_); |
| format_list_.FillFormats(formats); |
| return ZX_OK; |
| } |
| |
| zx_status_t UsbVideoStream::CreateStream( |
| fuchsia::sysmem::BufferCollectionInfo buffer_collection, |
| fuchsia::camera::FrameRate frame_rate) { |
| fbl::AutoLock lock(&lock_); |
| fuchsia::camera::VideoFormat video_format; |
| video_format.format = buffer_collection.format.image(); |
| video_format.rate = frame_rate; |
| // Convert from the client's video format proto to the device driver format |
| // and frame descriptors. |
| uint8_t format_index, frame_index; |
| uint32_t default_frame_interval; |
| bool is_matched = format_list_.MatchFormat( |
| video_format, &format_index, &frame_index, &default_frame_interval); |
| if (!is_matched) { |
| zxlogf(ERROR, "could not find a mapping for the requested format\n"); |
| return ZX_ERR_NOT_FOUND; |
| } |
| |
| if (streaming_state_ != StreamingState::STOPPED) { |
| zxlogf(ERROR, "cannot set video format while streaming is not stopped\n"); |
| return ZX_ERR_BAD_STATE; |
| } |
| |
| // Try setting the format on the device. |
| zx_status_t status = |
| TryFormatLocked(format_index, frame_index, default_frame_interval); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "setting format failed, err: %d\n", status); |
| return status; |
| } |
| |
| if (max_frame_size_ > buffer_collection.vmo_size) { |
| zxlogf(ERROR, "buffer provided %lu is less than max size %u.\n", |
| buffer_collection.vmo_size, max_frame_size_); |
| return ZX_ERR_INVALID_ARGS; |
| } |
| |
| // TODO(garratt): Check if we should clear previous buffers |
| // Now to set the buffers: |
| buffers_.Init(buffer_collection.vmos.data(), buffer_collection.buffer_count); |
| |
| return ZX_OK; |
| } |
| |
| zx_status_t UsbVideoStream::StartStreaming() { |
| fbl::AutoLock lock(&lock_); |
| |
| // Initialize the state. |
| num_frames_ = 0; |
| cur_frame_state_ = {}; |
| // FID of the first seen frame could either be 0 or 1. |
| // Initialize this to -1 so that the first frame will consistently be |
| // detected as a new frame. |
| cur_frame_state_.fid = -1; |
| bulk_payload_bytes_ = 0; |
| buffers_.Reset(); |
| |
| zx_status_t status = |
| usb_set_interface(&usb_, iface_num_, cur_streaming_setting_->alt_setting); |
| if (status != ZX_OK) { |
| return status; |
| } |
| streaming_state_ = StreamingState::STARTED; |
| |
| while (!list_is_empty(&free_reqs_)) { |
| QueueRequestLocked(); |
| } |
| return ZX_OK; |
| } |
| |
| zx_status_t UsbVideoStream::StopStreaming() { |
| fbl::AutoLock lock(&lock_); |
| |
| if (streaming_state_ != StreamingState::STARTED) { |
| return ZX_ERR_BAD_STATE; |
| } |
| // Need to wait for all the in-flight usb requests to complete |
| // before we can be completely stopped. |
| // We won't send the stop response until then. |
| streaming_state_ = StreamingState::STOPPING; |
| |
| // Switch to the zero bandwidth alternate setting. |
| return usb_set_interface(&usb_, iface_num_, 0); |
| } |
| |
| zx_status_t UsbVideoStream::FrameRelease(uint64_t buffer_id) { |
| fbl::AutoLock lock(&lock_); |
| return buffers_.BufferRelease(buffer_id); |
| } |
| |
| void UsbVideoStream::QueueRequestLocked() { |
| auto req = usb_req_list_remove_head(&free_reqs_, parent_req_size_); |
| ZX_DEBUG_ASSERT(req != nullptr); |
| num_free_reqs_--; |
| req->header.length = send_req_size_; |
| usb_request_complete_t complete = { |
| .callback = UsbVideoStream::RequestCompleteCallback, |
| .ctx = this, |
| }; |
| usb_request_queue(&usb_, req, &complete); |
| } |
| |
| void UsbVideoStream::RequestComplete(usb_request_t* req) { |
| fbl::AutoLock lock(&lock_); |
| |
| if (streaming_state_ != StreamingState::STARTED) { |
| // Stopped streaming so don't need to process the result. |
| zx_status_t status = usb_req_list_add_head(&free_reqs_, req, parent_req_size_); |
| ZX_DEBUG_ASSERT(status == ZX_OK); |
| num_free_reqs_++; |
| if (num_free_reqs_ == num_allocated_reqs_) { |
| zxlogf(TRACE, "setting video buffer as stopped, got %u frames\n", |
| num_frames_); |
| streaming_state_ = StreamingState::STOPPED; |
| } |
| return; |
| } |
| ProcessPayloadLocked(req); |
| zx_status_t status = usb_req_list_add_head(&free_reqs_, req, parent_req_size_); |
| ZX_DEBUG_ASSERT(status == ZX_OK); |
| num_free_reqs_++; |
| QueueRequestLocked(); |
| } |
| |
| // Converts from device clock units to milliseconds. |
| static inline double device_clock_to_ms(uint32_t clock_reading, |
| uint32_t clock_frequency_hz) { |
| return clock_frequency_hz != 0 ? clock_reading * 1000.0 / clock_frequency_hz |
| : 0; |
| } |
| |
| void UsbVideoStream::ParseHeaderTimestamps(usb_request_t* req) { |
| // TODO(jocelyndang): handle other formats, the timestamp offset is variable. |
| usb_video_vs_uncompressed_payload_header header = {}; |
| usb_request_copy_from(req, &header, |
| sizeof(usb_video_vs_uncompressed_payload_header), 0); |
| |
| // PTS should stay the same for payloads of the same frame, |
| // but it's probably not a critical error if they're different. |
| if (header.bmHeaderInfo & USB_VIDEO_VS_PAYLOAD_HEADER_PTS) { |
| uint32_t new_pts = header.dwPresentationTime; |
| |
| // Use the first seen PTS value. |
| if (cur_frame_state_.pts == 0) { |
| cur_frame_state_.pts = new_pts; |
| } else if (new_pts != cur_frame_state_.pts) { |
| zxlogf(ERROR, "#%u: PTS changed between payloads, from %u to %u\n", |
| num_frames_, cur_frame_state_.pts, new_pts); |
| } |
| } |
| |
| if (header.bmHeaderInfo & USB_VIDEO_VS_PAYLOAD_HEADER_SCR) { |
| uint32_t new_stc = header.scrSourceTimeClock; |
| uint16_t new_sof = header.scrSourceClockSOFCounter; |
| |
| // The USB Video Class Spec 1.1 suggests that updated SCR values may |
| // be provided per payload of a frame. Only use the first seen value. |
| if (cur_frame_state_.stc == 0) { |
| cur_frame_state_.stc = new_stc; |
| cur_frame_state_.device_sof = new_sof; |
| } |
| } |
| |
| // The device might not support header timestamps. |
| if (cur_frame_state_.pts == 0 || cur_frame_state_.stc == 0) { |
| return; |
| } |
| // Already calculated the capture time for the frame. |
| if (cur_frame_state_.capture_time != 0) { |
| return; |
| } |
| |
| // Calculate the capture time. This uses the method detailed in the |
| // USB Video Class 1.5 FAQ, Section 2.7 Audio and Video Stream |
| // Synchronization. |
| // |
| // Event Available Timestamps |
| // ------------------------ ---------------------------------- |
| // raw frame capture starts - PTS in device clock units |
| // raw frame capture ends - STC in device clock units, device SOF |
| // driver receives frame - host monotonic timestamp, host SOF |
| // |
| // TODO(jocelyndang): revisit this. This may be slightly inaccurate for |
| // devices implementing the 1.1 version of the spec, which states that a |
| // payload's SOF number is not required to match the 'current' frame number. |
| |
| // Get the current host SOF value and host monotonic timestamp. |
| cur_frame_state_.host_sof = usb_get_current_frame(&usb_); |
| zx_time_t host_complete_time_ns = zx_clock_get_monotonic(); |
| |
| // Calculate the difference between when raw frame capture starts and ends. |
| uint32_t device_delay = cur_frame_state_.stc - cur_frame_state_.pts; |
| double device_delay_ms = |
| device_clock_to_ms(device_delay, clock_frequency_hz_); |
| |
| // Calculate the delay caused by USB transport and processing. This will be |
| // the time between raw frame capture ending and the driver receiving the |
| // frame |
| // |
| // SOF (Start of Frame) values are transmitted by the USB host every |
| // millisecond. |
| // We want the difference between the SOF values of when frame capture |
| // completed (device_sof) and when we received the frame (host_sof). |
| // |
| // Since the device SOF value only has 11 bits and wraps around, we should |
| // discard the higher bits of the result. The delay is expected to be |
| // less than 2^11 ms. |
| uint16_t transport_delay_ms = |
| (cur_frame_state_.host_sof - cur_frame_state_.device_sof) & USB_SOF_MASK; |
| |
| // Time between when raw frame capture starts and the driver receiving the |
| // frame. |
| double total_video_delay = device_delay_ms + transport_delay_ms; |
| |
| // Start of raw frame capture as zx_time_t (nanoseconds). |
| zx_time_t capture_start_ns = |
| host_complete_time_ns - ZX_MSEC(total_video_delay); |
| // The capture time is specified in the camera interface as the midpoint of |
| // the capture operation, not including USB transport time. |
| cur_frame_state_.capture_time = |
| capture_start_ns + ZX_MSEC(device_delay_ms) / 2; |
| } |
| |
| zx_status_t UsbVideoStream::FrameNotifyLocked() { |
| if (clock_frequency_hz_ != 0) { |
| zxlogf(TRACE, |
| "#%u: [%ld ns] PTS = %lfs, STC = %lfs, SOF = %u host SOF = %lu\n", |
| num_frames_, cur_frame_state_.capture_time, |
| cur_frame_state_.pts / static_cast<double>(clock_frequency_hz_), |
| cur_frame_state_.stc / static_cast<double>(clock_frequency_hz_), |
| cur_frame_state_.device_sof, cur_frame_state_.host_sof); |
| } |
| |
| if (camera_control_ == nullptr) { |
| // Can't send a notification if there's no channel. |
| return ZX_OK; |
| } |
| |
| fuchsia::camera::FrameAvailableEvent event = {}; |
| event.metadata.timestamp = cur_frame_state_.capture_time; |
| |
| // If we were not even writing to a buffer, return buffer full error: |
| if (!buffers_.HasBufferInProgress()) { |
| event.frame_status = fuchsia::camera::FrameStatus::ERROR_BUFFER_FULL; |
| camera_control_->OnFrameAvailable(event); |
| return ZX_OK; |
| } |
| |
| // If we were writing to a buffer (we have to complete it) |
| // If we had a writing error: |
| if (cur_frame_state_.error) { |
| event.frame_status = fuchsia::camera::FrameStatus::ERROR_FRAME; |
| } |
| |
| zx_status_t status = buffers_.BufferCompleted(&event.buffer_id); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "could not mark frame as complete: %d\n", status); |
| return ZX_ERR_BAD_STATE; |
| } |
| |
| // In the case that we didn't write anything, don't send a notification. |
| // We'll release the buffer just to make things neat though. |
| if (cur_frame_state_.bytes <= 0) { |
| // No bytes were received, so don't send a notification. |
| // release the buffer back: |
| status = buffers_.BufferRelease(event.buffer_id); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "could not release the buffer: %d\n", status); |
| return ZX_ERR_BAD_STATE; |
| } |
| return ZX_OK; |
| } |
| |
| camera_control_->OnFrameAvailable(event); |
| |
| return ZX_OK; |
| } |
| |
| zx_status_t UsbVideoStream::ParsePayloadHeaderLocked( |
| usb_request_t* req, uint32_t* out_header_length) { |
| // Different payload types have different header types but always share |
| // the same first two bytes. |
| usb_video_vs_payload_header header; |
| size_t len = usb_request_copy_from(req, &header, |
| sizeof(usb_video_vs_payload_header), 0); |
| |
| if (len != sizeof(usb_video_vs_payload_header) || |
| header.bHeaderLength > req->response.actual) { |
| zxlogf(ERROR, "got invalid header bHeaderLength %u data length %lu\n", |
| header.bHeaderLength, req->response.actual); |
| return ZX_ERR_INTERNAL; |
| } |
| |
| uint8_t fid = header.bmHeaderInfo & USB_VIDEO_VS_PAYLOAD_HEADER_FID; |
| // We can detect the start of a new frame via FID or EOF. |
| // |
| // FID is toggled when a new frame begins. This means any in progress frame |
| // is now complete, and we are currently parsing the header of a new frame. |
| // |
| // If EOF was set on the previous frame, that means it was also completed, |
| // and this is a new frame. |
| bool new_frame = cur_frame_state_.fid != fid || cur_frame_state_.eof; |
| if (new_frame) { |
| // Notify the client of the completion of the previous frame. |
| // We need to check if the currently stored FID is valid, and we didn't |
| // already send a notification (EOF bit set). |
| if (cur_frame_state_.fid >= 0 && !cur_frame_state_.eof) { |
| zx_status_t status = FrameNotifyLocked(); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "failed to send notification to client, err: %d\n", |
| status); |
| // Even if we failed to send a notification, we should |
| // probably continue processing the new frame. |
| } |
| } |
| |
| // Initialize the frame state for the new frame. |
| |
| cur_frame_state_ = {}; |
| cur_frame_state_.fid = fid; |
| num_frames_++; |
| zx_status_t status = buffers_.GetNewBuffer(); |
| if (status == ZX_ERR_NOT_FOUND) { |
| zxlogf(ERROR, "no available frames, dropping frame #%u\n", num_frames_); |
| } else if (status != ZX_OK) { |
| zxlogf(ERROR, "failed to get new frame, err: %d\n", status); |
| } |
| } |
| cur_frame_state_.eof = header.bmHeaderInfo & USB_VIDEO_VS_PAYLOAD_HEADER_EOF; |
| |
| if (header.bmHeaderInfo & USB_VIDEO_VS_PAYLOAD_HEADER_ERR) { |
| // Only print the error message for the first erroneous payload of the |
| // frame. |
| if (!cur_frame_state_.error) { |
| zxlogf(ERROR, "payload of frame #%u had an error bit set\n", num_frames_); |
| cur_frame_state_.error = true; |
| } |
| return ZX_OK; |
| } |
| |
| ParseHeaderTimestamps(req); |
| |
| *out_header_length = header.bHeaderLength; |
| return ZX_OK; |
| } |
| |
| void UsbVideoStream::ProcessPayloadLocked(usb_request_t* req) { |
| if (req->response.status != ZX_OK) { |
| zxlogf(ERROR, "usb request failed: %d\n", req->response.status); |
| return; |
| } |
| // Empty responses should be ignored. |
| if (req->response.actual == 0) { |
| return; |
| } |
| |
| bool is_bulk = streaming_ep_type_ == USB_ENDPOINT_BULK; |
| uint32_t header_len = 0; |
| // Each isochronous response contains a payload header. |
| // For bulk responses, a payload may be split over several requests, |
| // so only parse the header if it's the first request of the payload. |
| if (!is_bulk || bulk_payload_bytes_ == 0) { |
| zx_status_t status = ParsePayloadHeaderLocked(req, &header_len); |
| if (status != ZX_OK) { |
| return; |
| } |
| } |
| // End of payload detection for bulk transfers. |
| // Unlike isochronous transfers, we aren't guaranteed a payload header |
| // per usb response. To detect the end of a payload, we need to check |
| // whether we've read enough bytes. |
| if (is_bulk) { |
| // We need to update the total bytes counter before checking the error |
| // field, otherwise we might return early and start of payload detection |
| // will be wrong. |
| bulk_payload_bytes_ += static_cast<uint32_t>(req->response.actual); |
| // A payload is complete when we've received enough bytes to reach the max |
| // payload size, or fewer bytes than what we requested. |
| if (bulk_payload_bytes_ >= negotiation_result_.dwMaxPayloadTransferSize || |
| req->response.actual < send_req_size_) { |
| bulk_payload_bytes_ = 0; |
| } |
| } |
| |
| if (cur_frame_state_.error) { |
| zxlogf(TRACE, "skipping payload of invalid frame #%u\n", num_frames_); |
| return; |
| } |
| if (!buffers_.HasBufferInProgress()) { |
| // There was no space in the video buffer when the frame's first payload |
| // header was parsed. |
| return; |
| } |
| |
| // Copy the data into the video buffer. |
| uint32_t data_size = static_cast<uint32_t>(req->response.actual) - header_len; |
| if (cur_frame_state_.bytes + data_size > max_frame_size_) { |
| zxlogf(ERROR, "invalid data size %u, cur frame bytes %u, frame size %u\n", |
| data_size, cur_frame_state_.bytes, max_frame_size_); |
| cur_frame_state_.error = true; |
| return; |
| } |
| |
| // Append the data to the end of the current frame. |
| uint64_t avail = buffers_.CurrentBufferSize() - cur_frame_state_.bytes; |
| ZX_DEBUG_ASSERT(avail >= data_size); |
| |
| uint8_t* dst = reinterpret_cast<uint8_t*>(buffers_.CurrentBufferAddress()) + |
| cur_frame_state_.bytes; |
| usb_request_copy_from(req, dst, data_size, header_len); |
| |
| cur_frame_state_.bytes += data_size; |
| |
| if (cur_frame_state_.eof) { |
| // Send a notification to the client for frame completion now instead of |
| // waiting to parse the next payload header, in case this is the very last |
| // payload. |
| zx_status_t status = FrameNotifyLocked(); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "failed to send notification to client, err: %d\n", status); |
| } |
| } |
| } |
| |
| void UsbVideoStream::DeactivateVideoBuffer() { |
| fbl::AutoLock lock(&lock_); |
| |
| if (streaming_state_ != StreamingState::STOPPED) { |
| streaming_state_ = StreamingState::STOPPING; |
| } |
| } |
| |
| void UsbVideoStream::DdkUnbind() { |
| // Unpublish our device node. |
| DdkRemove(); |
| } |
| |
| void UsbVideoStream::DdkRelease() { delete this; } |
| |
| } // namespace usb |
| } // namespace video |