blob: db595abe274e5edbf7f366a9aa9e524f3390e8e5 [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/debug.h>
#include <fbl/algorithm.h>
#include <fbl/auto_lock.h>
#include <fbl/limits.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 <zircon/device/usb.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)
: UsbVideoStreamBase(parent),
usb_(*usb),
format_list_(fbl::move(format_list)),
streaming_settings_(fbl::move(*settings)) {
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_req_release(&usb_,
list_remove_head_type(&free_reqs_, usb_request_t, node));
}
}
}
// 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) {
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));
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_);
// 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_req_release(&usb_,
list_remove_head_type(&free_reqs_, usb_request_t, node));
}
zxlogf(TRACE, "allocating %d usb requests of size %lu\n",
MAX_OUTSTANDING_REQS, size);
for (uint32_t i = 0; i < MAX_OUTSTANDING_REQS; i++) {
usb_request_t* req;
zx_status_t status = usb_req_alloc(&usb_, &req, size, usb_ep_addr_);
if (status != ZX_OK) {
zxlogf(ERROR, "usb_req_alloc failed: %d\n", status);
return status;
}
req->cookie = this;
req->complete_cb = [](usb_request_t* req, void* cookie) -> void {
ZX_DEBUG_ASSERT(cookie != nullptr);
reinterpret_cast<UsbVideoStream*>(cookie)->RequestComplete(req);
};
list_add_head(&free_reqs_, &req->node);
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::driver::Control> control_handle;
fidl::InterfaceRequest<fuchsia::camera::driver::Control> control_interface =
control_handle.NewRequest();
if (control_interface.is_valid()) {
camera_control_ = fbl::make_unique<camera::ControlImpl>(
this, fbl::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::driver::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::driver::FrameRate frame_rate) {
fbl::AutoLock lock(&lock_);
fuchsia::camera::driver::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 is less than max size.\n");
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 = list_remove_head_type(&free_reqs_, usb_request_t, node);
ZX_DEBUG_ASSERT(req != nullptr);
num_free_reqs_--;
req->header.length = send_req_size_;
usb_request_queue(&usb_, req);
}
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.
list_add_head(&free_reqs_, &req->node);
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);
list_add_head(&free_reqs_, &req->node);
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_req_copy_from(&usb_, 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.
size_t len;
zx_status_t status = device_ioctl(parent_, IOCTL_USB_GET_CURRENT_FRAME, NULL,
0, &cur_frame_state_.host_sof,
sizeof(cur_frame_state_.host_sof), &len);
if (status != ZX_OK) {
zxlogf(ERROR, "could not get host SOF, err: %d\n", status);
return;
}
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::driver::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::driver::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::driver::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_req_copy_from(&usb_, 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_req_copy_from(&usb_, 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