1、控制传输VC
应用层用于控制传输代码,内核中主要通过usb端点0进行通信
int run_uvc_device()
{
fd_set efds;
struct timeval tv;
int r;
tv.tv_sec = 1;
tv.tv_usec = 0;
FD_ZERO(&efds);
FD_SET(__uvc_device->fd, &efds);
r = select(__uvc_device->fd + 1, NULL, NULL, &efds, &tv);
if (r > 0)
{
if (FD_ISSET(__uvc_device->fd, &efds))
{
uvc_events_process(__uvc_device);
}
}
return r;
}
static void uvc_events_process(struct uvc_device* dev)
{
struct v4l2_event v4l2_event;
struct uvc_event* uvc_event = (struct uvc_event*)(void*)&v4l2_event.u.data;
struct uvc_request_data resp;
int ret;
ret = ioctl(dev->fd, VIDIOC_DQEVENT, &v4l2_event);
if (ret < 0)
{
printf("VIDIOC_DQEVENT failed: %s (%d)\n", strerror(errno),
errno);
return;
}
memset(&resp, 0, sizeof resp);
resp.length = -EL2HLT;
switch (v4l2_event.type)
{
//0x08000000
case UVC_EVENT_CONNECT:
printf("handle connect event \n");
return;
//0x08000001
case UVC_EVENT_DISCONNECT:
printf("handle disconnect event\n");
return;
//0x08000004 UVC class
case UVC_EVENT_SETUP:
printf("UVC_EVENT_SETUP\n");
uvc_events_process_setup(dev, &uvc_event->req, &resp);
break;
//0x08000005
case UVC_EVENT_DATA:
printf("UVC_EVENT_DATA\n");
uvc_events_process_data(dev, &uvc_event->data);
return;
//0x08000002
case UVC_EVENT_STREAMON:
printf("UVC_EVENT_STREAMON\n");
if (!dev->bulk)
{
enable_uvc_video(dev);
}
return;
//0x08000003
case UVC_EVENT_STREAMOFF:
printf("UVC_EVENT_STREAMOFF\n");
if (!dev->bulk)
{
disable_uvc_video(dev);
}
return;
}
ret = ioctl(dev->fd, UVCIOC_SEND_RESPONSE, &resp);
if (ret < 0)
{
printf("UVCIOC_S_EVENT failed: %s (%d)\n", strerror(errno),
errno);
return;
}
}
内核代码
内核select是如何知道有数据要处理的呢?如下
#define POLLIN_SET (POLLRDNORM | POLLRDBAND | POLLIN | POLLHUP | POLLERR)
#define POLLOUT_SET (POLLWRBAND | POLLWRNORM | POLLOUT | POLLERR)
#define POLLEX_SET (POLLPRI)
int do_select(int n, fd_set_bits *fds, struct timespec64 *end_time)
{
f_op = f.file->f_op;
mask = DEFAULT_POLLMASK;
if (f_op->poll) {//这里调poll,下面主要说明这个函数
wait_key_set(wait, in, out,
bit, busy_flag);
mask = (*f_op->poll)(f.file, wait);
}
fdput(f);
if ((mask & POLLIN_SET) && (in & bit)) {
res_in |= bit;
retval++;
wait->_qproc = NULL;
}
if ((mask & POLLOUT_SET) && (out & bit)) {//这里是uvc建立通信之后会传输数据
res_out |= bit;
retval++;
wait->_qproc = NULL;
}
if ((mask & POLLEX_SET) && (ex & bit)) {//控制传输的时候按异常处理
res_ex |= bit;
retval++;
wait->_qproc = NULL;
}
}
v4l2_poll--》uvc_v4l2_poll--》uvcg_queue_poll--》vb2_poll
unsigned int vb2_poll(struct vb2_queue *q, struct file *file, poll_table *wait)
{
struct video_device *vfd = video_devdata(file);
unsigned long req_events = poll_requested_events(wait);
unsigned int res = 0;
if (test_bit(V4L2_FL_USES_V4L2_FH, &vfd->flags)) {
struct v4l2_fh *fh = file->private_data;
if (v4l2_event_pending(fh))
res = POLLPRI;//控制传输的时候这里会赋值,#define POLLEX_SET (POLLPRI)上面定义了,select返回的时候会体现在异常描述符集合里面
else if (req_events & POLLPRI)
poll_wait(file, &fh->wait, wait);
}
return res | vb2_core_poll(q, file, wait);
}
int v4l2_event_pending(struct v4l2_fh *fh)
{
return fh->navailable;//这个值在中断处理函数中调用端点0的处理函数赋值
}
static void __v4l2_event_queue_fh(struct v4l2_fh *fh, const struct v4l2_event *ev,
const struct timespec *ts)
{
struct v4l2_subscribed_event *sev;
struct v4l2_kevent *kev;
bool copy_payload = true;
/* Are we subscribed? */
sev = v4l2_event_subscribed(fh, ev->type, ev->id);
if (sev == NULL)
return;
/*
* If the event has been added to the fh->subscribed list, but its
* add op has not completed yet elems will be 0, treat this as
* not being subscribed.
*/
if (!sev->elems)
return;
/* Increase event sequence number on fh. */
fh->sequence++;
/* Do we have any free events? */
if (sev->in_use == sev->elems) {
/* no, remove the oldest one */
kev = sev->events + sev_pos(sev, 0);
list_del(&kev->list);
sev->in_use--;
sev->first = sev_pos(sev, 1);
fh->navailable--;
if (sev->elems == 1) {
if (sev->ops && sev->ops->replace) {
sev->ops->replace(&kev->event, ev);
copy_payload = false;
}
} else if (sev->ops && sev->ops->merge) {
struct v4l2_kevent *second_oldest =
sev->events + sev_pos(sev, 0);
sev->ops->merge(&kev->event, &second_oldest->event);
}
}
/* Take one and fill it. */
//这里开始填充一个 struct v4l2_event结构体类型的事件加到对应的链表,应用会通过ioctl获取对应 struct v4l2_event的事件
kev = sev->events + sev_pos(sev, sev->in_use);
kev->event.type = ev->type;
if (copy_payload)
kev->event.u = ev->u;
kev->event.id = ev->id;
kev->event.timestamp = *ts;
kev->event.sequence = fh->sequence;
sev->in_use++;
list_add_tail(&kev->list, &fh->available);
fh->navailable++;
<....>
}
直白点说明,usb中断来了之后调用usb注册的中断处理底半步函数调用__v4l2_event_queue_fh设置navailable该参数,具体调用流程看下方图片,不细说,然后select的时候通过poll一层层掉v4l2_event_pending会读到该标志增加,select就会检测到有异常事件要处理,应用调用uvc_events_process处理对应事件
2、视频流传输VS
应用层代码,数据用的不再是usb的0端点,而是其他端点
应用代码
int run_uvc_data()
{
fd_set wfds;
struct timeval tv;
int r;
tv.tv_sec = 1;
tv.tv_usec = 0;
FD_ZERO(&wfds);
if (__uvc_device->streaming == 1)
{
FD_SET(__uvc_device->fd, &wfds);
}
r = select(__uvc_device->fd + 1, NULL, &wfds, NULL, &tv);
if (r > 0)
{
if (FD_ISSET(__uvc_device->fd, &wfds))
{
uvc_video_process_userptr(__uvc_device);
}
}
return r;
}
static int uvc_video_process_userptr(struct uvc_device* dev)
{
struct v4l2_buffer buf;
int ret;
memset(&buf, 0, sizeof buf);
buf.type = V4L2_BUF_TYPE_VIDEO_OUTPUT;
buf.memory = V4L2_MEMORY_USERPTR;
if ((ret = ioctl(dev->fd, VIDIOC_DQBUF, &buf)) < 0)
{
return ret;
}
uvc_video_fill_buffer_userptr(dev, &buf);//取视频流数据
if ((ret = ioctl(dev->fd, VIDIOC_QBUF, &buf)) < 0)//发送数据
{
printf("Unable to requeue buffer: %s (%d).\n", strerror(errno), errno);
return ret;
}
return 0;
}
内核代码
内核select处理和上面一样,下面具体说明工作流程
do_select–》v4l2_poll–》uvc_v4l2_poll–》uvcg_queue_poll–》vb2_poll–》vb2_core_poll
unsigned int vb2_core_poll(struct vb2_queue *q, struct file *file, poll_table *wait)
{
struct vb2_buffer *vb = NULL;
<....>
/*
* Take first buffer available for dequeuing.
*/
spin_lock_irqsave(&q->done_lock, flags);
if (!list_empty(&q->done_list)) //在链表中获取vb2_buffer ,这里是获取vb,vb哪里加的呢,接着往下看
vb = list_first_entry(&q->done_list, struct vb2_buffer,
done_entry);
spin_unlock_irqrestore(&q->done_lock, flags);
if (vb && (vb->state == VB2_BUF_STATE_DONE
|| vb->state == VB2_BUF_STATE_ERROR)) {
return (q->is_output) ? //这个output在初始化队列的时候就初始化为1了
POLLOUT | POLLWRNORM :
POLLIN | POLLRDNORM;
}
return 0;
}
//q->is_output在这里初始化vb2_queue_init
int vb2_queue_init(struct vb2_queue *q)
{
q->buf_ops = &v4l2_buf_ops;
q->is_multiplanar = V4L2_TYPE_IS_MULTIPLANAR(q->type);
q->is_output = V4L2_TYPE_IS_OUTPUT(q->type);
}
//这个函数会往链表q->done_list里面加成员,select就知道out的描述符集合里对应的描述符需要写数据
void vb2_buffer_done(struct vb2_buffer *vb, enum vb2_buffer_state state)
{
spin_lock_irqsave(&q->done_lock, flags);
if (state == VB2_BUF_STATE_QUEUED ||
state == VB2_BUF_STATE_REQUEUEING) {
vb->state = VB2_BUF_STATE_QUEUED;
} else {
/* Add the buffer to the done buffers list */
list_add_tail(&vb->done_entry, &q->done_list);
vb->state = state;
}
atomic_dec(&q->owned_by_drv_count);
spin_unlock_irqrestore(&q->done_lock, flags);
}
通过内核调度WORK队列调用uvcg_video_pump函数一步步往下走最后调用vb2_buffer_done,这样子q->done_list这个链表就非空了,select就返回out的描述符,应用层开始传输数据
int uvcg_video_init(struct uvc_video *video, struct uvc_device *uvc)
{
INIT_LIST_HEAD(&video->req_free);
spin_lock_init(&video->req_lock);
INIT_WORK(&video->pump, uvcg_video_pump);//这里将uvcg_video_pump注册给work工作队列
return 0;
}
那么哪里调度了这个uvcg_video_pump函数呢
两个地方
1、stream_on的时候调这个uvcg_video_enable
int uvcg_video_enable(struct uvc_video *video, int enable)
{
unsigned int i;
int ret;
static int pp=0;
if (video->ep == NULL) {
printk(KERN_INFO "Video enable failed, device is "
"uninitialized.\n");
return -ENODEV;
}
if (!enable) {
cancel_work_sync(&video->pump);
uvcg_queue_cancel(&video->queue, 0);
for (i = 0; i < UVC_NUM_REQUESTS; ++i)
if (video->req[i])
usb_ep_dequeue(video->ep, video->req[i]);
uvc_video_free_requests(video);
uvcg_queue_enable(&video->queue, 0);
return 0;
}
if ((ret = uvcg_queue_enable(&video->queue, 1)) < 0)
return ret;
if ((ret = uvc_video_alloc_requests(video)) < 0)
return ret;
if (video->max_payload_size) {
video->encode = uvc_video_encode_bulk;
video->payload_size = 0;
} else
video->encode = uvc_video_encode_isoc;
schedule_work(&video->pump);//这里cpu会调度使能注册的work队列函数
}
2、数据传输的时候调用uvc_video_complete
static void uvc_video_complete(struct usb_ep *ep, struct usb_request *req)
{
struct uvc_video *video = req->context;
struct uvc_video_queue *queue = &video->queue;
unsigned long flags;
static int pp=0;
switch (req->status) {
case 0:
break;
case -ESHUTDOWN: /* disconnect from host. */
printk(KERN_DEBUG "VS request cancelled.\n");
uvcg_queue_cancel(queue, 1);
break;
default:
printk(KERN_INFO "VS request completed with status %d.\n",
req->status);
uvcg_queue_cancel(queue, 0);
}
spin_lock_irqsave(&video->req_lock, flags);
list_add_tail(&req->list, &video->req_free);
spin_unlock_irqrestore(&video->req_lock, flags);
schedule_work(&video->pump);//这里调用
}
应用层收到端点0发来的stream_on指令的时候会调用一次 if ((ret = ioctl(dev->fd, VIDIOC_QBUF, &buf)) < 0)传输视频流
这一次调用的是uvcg_video_enable
数据传输的时候都是通过中断底半步处理函数调用uvc_video_complete
当调用完之后select就知道要写数据,返回给应用
3、实际调用过程
端点0的控制传输
_______________kev->event.type:0x8000004_______func=__v4l2_event_queue_fh,line=156
UVC_EVENT_SETUP
_______________kev->event.type:0x8000004_______func=__v4l2_event_queue_fh,line=156
UVC_EVENT_SETUP
_______________kev->event.type:0x8000005_______func=__v4l2_event_queue_fh,line=156
UVC_EVENT_DATA
_______________kev->event.type:0x8000004_______func=__v4l2_event_queue_fh,line=156
UVC_EVENT_SETUP
_______________kev->event.type:0x8000004_______func=__v4l2_event_queue_fh,line=156
UVC_EVENT_SETUP
_______________kev->event.type:0x8000004_______func=__v4l2_event_queue_fh,line=156
UVC_EVENT_SETUP
_______________kev->event.type:0x8000004_______func=__v4l2_event_queue_fh,line=156
UVC_EVENT_SETUP
_______________kev->event.type:0x8000005_______func=__v4l2_event_queue_fh,line=156
UVC_EVENT_DATA
_______________kev->event.type:0x8000004_______func=__v4l2_event_queue_fh,line=156
UVC_EVENT_SETUP
_______________kev->event.type:0x8000004_______func=__v4l2_event_queue_fh,line=156
UVC_EVENT_SETUP
_______________kev->event.type:0x8000005_______func=__v4l2_event_queue_fh,line=156
UVC_EVENT_DATA
_______________kev->event.type:0x8000002_______func=__v4l2_event_queue_fh,line=156
UVC_EVENT_STREAMON
端点0经过几次setup和data之后stream on,之后就交给usb的其他端点传输数据
中断底半部处理函数中的端点处理函数
static void dwc3_endpoint_interrupt(struct dwc3 *dwc,
const struct dwc3_event_depevt *event)
{
struct dwc3_ep *dep;
u8 epnum = event->endpoint_number;
dep = dwc->eps[epnum];
if (!(dep->flags & DWC3_EP_ENABLED))
return;
if (epnum == 0 || epnum == 1) {
dwc3_ep0_interrupt(dwc, event);
return;
}
switch (event->endpoint_event) {
case DWC3_DEPEVT_XFERCOMPLETE:
dep->resource_index = 0;
if (usb_endpoint_xfer_isoc(dep->endpoint.desc)) {
dev_err(dwc->dev, "XferComplete for Isochronous endpoint\n");
return;
}
dwc3_endpoint_transfer_complete(dep, event);
break;
case DWC3_DEPEVT_XFERINPROGRESS:
dwc3_endpoint_transfer_complete(dep, event);
break;
case DWC3_DEPEVT_XFERNOTREADY:
if (usb_endpoint_xfer_isoc(dep->endpoint.desc)) {
dwc3_gadget_start_isoc(dep, event);
} else {
int ret;
ret = __dwc3_gadget_kick_transfer(dep, 0);
if (!ret || ret == -EBUSY)
return;
}
break;
case DWC3_DEPEVT_STREAMEVT:
if (!usb_endpoint_xfer_bulk(dep->endpoint.desc)) {
dev_err(dwc->dev, "Stream event for non-Bulk %s\n",
dep->name);
return;
}
break;
case DWC3_DEPEVT_RXTXFIFOEVT:
case DWC3_DEPEVT_EPCMDCMPLT:
break;
}
}