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:

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.

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.

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