Stream On/Off Process for UVC Devices on Linux

1. Introduction

This article shares the execution process of the stream on and off for UVC devices under Linux. It focuses on the differences between isoc and bulk modes.

2. Process

Ioctl Registration and Execution Process

The application layer calls ioctl

int ioctl(int fd, VIDIOC_STREAMON, const int *argp)

Ultimately, it calls the file’s ->unlocked_ioctl

where unlocked_ioctl is specified as video_ioctl2

For example, in drivers/media/usb/uvc/uvc_driver.c, the

uvc_register_video is called.

/* Register the device with V4L. */   return uvc_register_video_device(dev, stream, &stream->vdev,                    &stream->queue, stream->type,                    &uvc_fops, &uvc_ioctl_ops);

Here, uvc_fops is defined in drivers/media/usb/uvc/uvc_v4l2.c.

const struct v4l2_file_operations uvc_fops = {   .owner      = THIS_MODULE,   .open       = uvc_v4l2_open,   .release    = uvc_v4l2_release,   .unlocked_ioctl = video_ioctl2,#ifdef CONFIG_COMPAT   .compat_ioctl32 = uvc_v4l2_compat_ioctl32,#endif   .read       = uvc_v4l2_read,   .mmap       = uvc_v4l2_mmap,   .poll       = uvc_v4l2_poll,#ifndef CONFIG_MMU   .get_unmapped_area = uvc_v4l2_get_unmapped_area,#endif};

Thus, the final call is made to drivers/media/v4l2-core/v4l2-ioctl.c in video_ioctl2

long video_ioctl2(struct file *file,           unsigned int cmd, unsigned long arg){    return video_usercopy(file, cmd, arg, __video_do_ioctl);}

Continuing the call in drivers/media/v4l2-core/v4l2-ioctl.c, to

__video_do_ioctl

This function

if (v4l2_is_known_ioctl(cmd)) {        info = &v4l2_ioctls[_IOC_NR(cmd)];

Queries the array

drivers/media/v4l2-core/v4l2-ioctl.c for the v4l2_ioctls

Based on cmd = VIDIOC_STREAMON, it finds the following line:

IOCTL_INFO(VIDIOC_STREAMON, v4l_streamon, v4l_print_buftype, INFO_FL_PRIO | INFO_FL_QUEUE), ret = info->func(ops, file, fh, arg);

It executes drivers/media/v4l2-core/v4l2-ioctl.c in v4l_streamon

static int v4l_streamon(const struct v4l2_ioctl_ops *ops,                struct file *file, void *fh, void *arg){    return ops->vidioc_streamon(file, fh, *(unsigned int *)arg);}

Where the callback ops->vidioc_streamon

is in

drivers/media/usb/uvc/uvc_driver.c under

uvc_register_video is called.

   /* Register the device with V4L. */    return uvc_register_video_device(dev, stream, &stream->vdev,                     &stream->queue, stream->type,                     &uvc_fops, &uvc_ioctl_ops);

The registered

uvc_ioctl_ops is in drivers/media/usb/uvc/uvc_v4l2.c.

const struct v4l2_ioctl_ops uvc_ioctl_ops = {  ...     .vidioc_streamon = uvc_ioctl_streamon,.vidioc_streamoff = uvc_ioctl_streamoff,...};

Thus, ops->vidioc_streamon calls uvc_ioctl_streamon

static int uvc_ioctl_streamon(struct file *file, void *fh,                  enum v4l2_buf_type type){    struct uvc_fh *handle = fh;    struct uvc_streaming *stream = handle->stream;    int ret;     if (!uvc_has_privileges(handle))        return -EBUSY;    mutex_lock(&stream->mutex);    ret = uvc_queue_streamon(&stream->queue, type);    mutex_unlock(&stream->mutex);    return ret;}

Calls uvc_queue_streamon

int uvc_queue_streamon(struct uvc_video_queue *queue, enum v4l2_buf_type type){    int ret;     mutex_lock(&queue->mutex);    ret = vb2_streamon(&queue->queue, type);    mutex_unlock(&queue->mutex);     return ret;}

Continuing the call in drivers/media/common/videobuf2/videobuf2-v4l2.c in vb2_streamon

int vb2_streamon(struct vb2_queue *q, enum v4l2_buf_type type){    if (vb2_fileio_is_active(q)) {        dprintk(q, 1, "file io in progress\n");        return -EBUSY;    }    return vb2_core_streamon(q, type);}

Continuing the call in drivers/media/common/videobuf2/videobuf2-core.c in vb2_core_streamon

Passing Ioctl Parameters

Parameter checks are performed in vb2_core_streamon

  if (type != q->type) {        dprintk(q, 1, "invalid stream type\n");        return -EINVAL;    }

For UVC cameras, the device is an input device, so type must be

V4L2_BUF_TYPE_VIDEO_CAPTURE

Supported types are seen in argp= enum v4l2_buf_type

See

include/uapi/linux/videodev2.h

for the enumeration

enum v4l2_buf_type {

V4L2_BUF_TYPE_VIDEO_CAPTURE = 1,

V4L2_BUF_TYPE_VIDEO_OUTPUT = 2,

V4L2_BUF_TYPE_VIDEO_OVERLAY = 3,

V4L2_BUF_TYPE_VBI_CAPTURE = 4,

V4L2_BUF_TYPE_VBI_OUTPUT = 5,

V4L2_BUF_TYPE_SLICED_VBI_CAPTURE = 6,

V4L2_BUF_TYPE_SLICED_VBI_OUTPUT = 7,

V4L2_BUF_TYPE_VIDEO_OUTPUT_OVERLAY = 8,

V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE = 9,

V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE = 10,

V4L2_BUF_TYPE_SDR_CAPTURE = 11,

V4L2_BUF_TYPE_SDR_OUTPUT = 12,

V4L2_BUF_TYPE_META_CAPTURE = 13,

V4L2_BUF_TYPE_META_OUTPUT = 14,

/* Deprecated, do not use */

V4L2_BUF_TYPE_PRIVATE = 0x80,

};

Type is the parameter in int ioctl(int fd, VIDIOC_STREAMON, const int *argp)

For example,

where q->type is parsed in uvc_parse_streaming.

/* Parse the header descriptor. */    switch (buffer[2]) {    case UVC_VS_OUTPUT_HEADER:        streaming->type = V4L2_BUF_TYPE_VIDEO_OUTPUT;        size = 9;        break;     case UVC_VS_INPUT_HEADER:        streaming->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;        size = 13;        break;

Parsed from A.6. Video Class-Specific VS Interface Descriptor Subtypes in the

#define UVC_VS_UNDEFINED 0x00#define UVC_VS_INPUT_HEADER 0x013.7.2 Class-Specific VC Interface Descriptor P62 in the third byte buf[2] VC_HEADER=0x01

That is, the following fields in the descriptor:

Stream On/Off Process for UVC Devices on Linux

In vb2_core_streamon

After the above type check, it continues to call ret = vb2_start_streaming(q); located in drivers/media/common/videobuf2/videobuf2-core.c

Callback

  ret = call_qop(q, start_streaming, q,               atomic_read(&q->owned_by_drv_count));

Where

#define call_qop(q, op, args...)                    \	((q)->ops->op ? (q)->ops->op(args) : 0)

That is, it calls q->ops->start_streaming

And the above interface start_streaming is registered in

drivers/media/usb/uvc/uvc_queue.c during

 switch (type) {    case V4L2_BUF_TYPE_META_CAPTURE:        queue->queue.ops = &uvc_meta_queue_qops;        break;    default:        queue->queue.io_modes |= VB2_DMABUF;        queue->queue.ops = &uvc_queue_qops;        break;    }

That is, queue->queue.ops = &uvc_queue_qops;

Where uvc_queue_qops is located in drivers/media/usb/uvc/uvc_queue.c

Its content is as follows:

static const struct vb2_ops uvc_queue_qops = {    .queue_setup = uvc_queue_setup,    .buf_prepare = uvc_buffer_prepare,    .buf_queue = uvc_buffer_queue,    .buf_finish = uvc_buffer_finish,    .wait_prepare = vb2_ops_wait_prepare,    .wait_finish = vb2_ops_wait_finish,    .start_streaming = uvc_start_streaming,    .stop_streaming = uvc_stop_streaming,};

Thus, calling q->ops->start_streaming means calling uvc_start_streaming

Located in drivers/media/usb/uvc/uvc_queue.c

uvc_start_streaming

Essentially, this is the processing of the UVC layer.

Stream On/Off Process for UVC Devices on Linux

Calling uvc_video_start_streaming(stream);

static int uvc_start_streaming(struct vb2_queue *vq, unsigned int count){    struct uvc_video_queue *queue = vb2_get_drv_priv(vq);    struct uvc_streaming *stream = uvc_queue_to_stream(queue);    int ret;     lockdep_assert_irqs_enabled();     queue->buf_used = 0;     ret = uvc_video_start_streaming(stream);    if (ret == 0)        return 0;     spin_lock_irq(&queue->irqlock);    uvc_queue_return_buffers(queue, UVC_BUF_STATE_QUEUED);    spin_unlock_irq(&queue->irqlock);     return ret;}

uvc_video_start_streaming is located in drivers/media/usb/uvc/uvc_video.c

int uvc_video_start_streaming(struct uvc_streaming *stream){    int ret;     ret = uvc_video_clock_init(stream);    if (ret < 0)        return ret;     /* Commit the streaming parameters. */    ret = uvc_commit_video(stream, &stream->ctrl);    if (ret < 0)        goto error_commit;     ret = uvc_video_start_transfer(stream, GFP_KERNEL);    if (ret < 0)        goto error_video;     return 0; error_video:    usb_set_interface(stream->dev->udev, stream->intfnum, 0);error_commit:    uvc_video_clock_cleanup(stream);     return ret;}

First, commit-> then uvc_video_start_transfer

If uvc_video_start_transfer fails, it sets the interface to 0.

Arriving at drivers/media/usb/uvc/uvc_video.c in uvc_video_start_transfer

Here, it checks if if (intf->num_altsetting > 1) meaning there are multiple alt interfaces, then it is isoc, otherwise it is bulk.

It calls

drivers/media/usb/uvc/uvc_video.c in uvc_init_video_isoc

and uvc_init_video_bulk

Both will call

drivers/media/usb/uvc/uvc_video.c in uvc_alloc_urb_buffers

Finally, both will submit the urb request to start the data stream, and the subsequent data stream will continue in interrupts.

Note that under isoc, the interface will be set to 1

usb_set_interface(stream->dev->udev, intfnum, altsetting);

While ISOC has set alt itf 1, bulk does not , so bulk is determined by commit to judge stream on, while isoc is determined by set interface 1.

uvc_stop_streaming

The stream off ioctl is the same as on, ultimately following the path

uvc_stop_streaming->uvc_video_stop_streaming->uvc_video_stop_transfer

In drivers/media/usb/uvc/uvc_video.c, the uvc_video_stop_streaming

If if (stream->intf->num_altsetting > 1) meaning ISOC mode, then set the interface to 0,

Otherwise, for bulk mode, it clears halt.

usb_clear_halt(stream->dev->udev, pipe);

void uvc_video_stop_streaming(struct uvc_streaming *stream){    uvc_video_stop_transfer(stream, 1);     if (stream->intf->num_altsetting > 1) {        usb_set_interface(stream->dev->udev, stream->intfnum, 0);    } else {        /* UVC doesn't specify how to inform a bulk-based device         * when the video stream is stopped. Windows sends a         * CLEAR_FEATURE(HALT) request to the video streaming         * bulk endpoint, mimic the same behaviour.         */        unsigned int epnum = stream->header.bEndpointAddress                   & USB_ENDPOINT_NUMBER_MASK;        unsigned int dir = stream->header.bEndpointAddress                 & USB_ENDPOINT_DIR_MASK;        unsigned int pipe;         pipe = usb_sndbulkpipe(stream->dev->udev, epnum) | dir;        usb_clear_halt(stream->dev->udev, pipe);    }     uvc_video_clock_cleanup(stream);}

3. Conclusion

The process is detailed in the document.

Stream On/Off Process for UVC Devices on Linux

1. Focus on the execution process of ioctl for isoc and bulk, and the registration process of each interface.

2. Pay attention to the differences in handling on and off for isoc and bulk; isoc’s on is determined by set itf 1, while off is determined by set itf 0. Bulk’s on is determined by commit, while off is determined by clear halt.

3. Note the handling during buffer allocation for isoc and bulk, npackets are respectively

dwMaxVideoFrameSize/psize, dwMaxPayloadTransferSize/psize

Leave a Comment