赞
踩
首先,因为要用USB传数据,入口函数中注册usb_driver结构体,出口函数反之
- static int myuvc_init(void)
- {
- usb_register(&myuvc_driver);
- return 0;
- }
-
- static void myuvc_exit(void)
- {
- usb_deregister(&myuvc_driver);
- }
- static struct usb_driver myuvc_driver = {
- .name = "myuvc",
- .probe = myuvc_probe,
- .disconnect = myuvc_disconnect,
- .id_table = myuvc_ids,
- };
- static struct usb_device_id myuvc_ids[] = {
- /* Generic USB Video Class */
- { USB_INTERFACE_INFO(USB_CLASS_VIDEO, 1, 0) }, /* VideoControl Interface */
- { USB_INTERFACE_INFO(USB_CLASS_VIDEO, 2, 0) }, /* VideoStreaming Interface */
- {}
- };
UVC的probe函数主要做这几件事:
1.分配video_device结构体
video_device_alloc()
2. 设置video_device结构体
3. 注册video_device结构体
video_register_device(myuvc_vdev, VFL_TYPE_GRABBER, -1)
这里UVC驱动程序把video_device结构体注册给上一层的v4l2-dev.c文件,在v4l2-dev.c中会注册字符设备等一些操作,上一层的操作我们不关心,我们只需要在UVC设备驱动层提供一个video_device结构体就行了
我们来看看需要设置video_device结构体的哪些内容
- struct video_device
- {
- /* device ops */
- const struct v4l2_file_operations *fops;
-
- /* sysfs */
- struct device dev; /* v4l device */
- struct cdev *cdev; /* character device */
-
- /* Set either parent or v4l2_dev if your driver uses v4l2_device */
- struct device *parent; /* device parent */
- struct v4l2_device *v4l2_dev; /* v4l2_device parent */
-
- /* device info */
- char name[32];
- int vfl_type;
- /* 'minor' is set to -1 if the registration failed */
- int minor;
- u16 num;
- /* use bitops to set/clear/test flags */
- unsigned long flags;
- /* attribute to differentiate multiple indices on one physical device */
- int index;
-
- int debug; /* Activates debug level*/
-
- /* Video standard vars */
- v4l2_std_id tvnorms; /* Supported tv norms */
- v4l2_std_id current_norm; /* Current tvnorm */
-
- /* callbacks */
- void (*release)(struct video_device *vdev);
-
- /* ioctl callbacks */
- const struct v4l2_ioctl_ops *ioctl_ops;
- };

别惊讶video_device有这么多内容,其实只有设置其中几项就可以工作了
1.
void (*release)(struct video_device *vdev)
这项要设置,不然装载驱动的时候会报错,写个空函数就行了
2.
const struct v4l2_file_operations *fops;
应用程序open等,会直接调用fops中的open
3.
const struct v4l2_ioctl_ops *ioctl_ops
应用程序ioctl,会直接调用这里的ioctl
很明显,我们只要把fops和ioctl_ops这两个结构体设置好就OK了
ioctl_ops的定义里有好多项,但是必须的只有以下11项
- static const struct v4l2_ioctl_ops myuvc_ioctl_ops = {
- // 表示它是一个摄像头设备
- .vidioc_querycap = myuvc_vidioc_querycap,
-
- /* 用于列举、获得、测试、设置摄像头的数据的格式 */
- .vidioc_enum_fmt_vid_cap = myuvc_vidioc_enum_fmt_vid_cap,
- .vidioc_g_fmt_vid_cap = myuvc_vidioc_g_fmt_vid_cap,
- .vidioc_try_fmt_vid_cap = myuvc_vidioc_try_fmt_vid_cap,
- .vidioc_s_fmt_vid_cap = myuvc_vidioc_s_fmt_vid_cap,
-
- /* 缓冲区操作: 申请/查询/放入队列/取出队列 */
- .vidioc_reqbufs = myuvc_vidioc_reqbufs,
- .vidioc_querybuf = myuvc_vidioc_querybuf,
- .vidioc_qbuf = myuvc_vidioc_qbuf,
- .vidioc_dqbuf = myuvc_vidioc_dqbuf,
-
- // 启动/停止
- .vidioc_streamon = myuvc_vidioc_streamon,
- .vidioc_streamoff = myuvc_vidioc_streamoff,
- };

应用程序会调用它来判断此设备设否是一个摄像头设备
我们只要把传进来的v4l2_capability设置相应的参数就可以了
- static int myuvc_vidioc_querycap(struct file *file, void *priv,
- struct v4l2_capability *cap)
- {
- memset(cap, 0, sizeof *cap);
- strcpy(cap->driver, "myuvc");
- strcpy(cap->card, "myuvc");
- cap->version = 1;
-
- cap->capabilities = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING;
-
- return 0;
- }
应用程序会调用它来获取摄像头所支持的格式(数据格式),这里我们只返回一种格式
- static int myuvc_vidioc_enum_fmt_vid_cap(struct file *file, void *priv,
- struct v4l2_fmtdesc *f)
- {
- /* 人工查看描述符可知我们用的摄像头只支持1种格式 */
- if (f->index >= 1)
- return -EINVAL;
-
- /* 支持什么格式呢?
- * 查看VideoStreaming Interface的描述符,
- * 得到GUID为"59 55 59 32 00 00 10 00 80 00 00 aa 00 38 9b 71"
- */
- strcpy(f->description, "4:2:2, packed, YUYV");
- f->pixelformat = V4L2_PIX_FMT_YUYV;
-
- return 0;
- }

获取当前格式
驱动程序会把当前的格式存在一个内存的一个v4l2_format结构体中,应用程序要获取当前格式只需要把这个结构体返回给应用程序
- static int myuvc_vidioc_g_fmt_vid_cap(struct file *file, void *priv,
- struct v4l2_format *f)
- {
- memcpy(f, &myuvc_format, sizeof(myuvc_format));
- return (0);
- }
测试是否支持此格式
应用程序有可能会设置某摄像头不支持的格式(分辨率等),所以在设置之前要先测试一下是否支持
此函数中会进行判断,如果某些参数不支持则返回错误,,支持的话会把一些其他参数强制设成摄像头所支持的
- static int myuvc_vidioc_try_fmt_vid_cap(struct file *file, void *priv,
- struct v4l2_format *f)
- {
- if (f->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
- {
- return -EINVAL;
- }
-
- if (f->fmt.pix.pixelformat != V4L2_PIX_FMT_YUYV)
- return -EINVAL;
-
- /* 调整format的width, height,
- * 计算bytesperline, sizeimage
- */
-
- /* 人工查看描述符, 确定支持哪几种分辨率 */
- f->fmt.pix.width = frames[frame_idx].width;
- f->fmt.pix.height = frames[frame_idx].height;
-
- f->fmt.pix.bytesperline =
- (f->fmt.pix.width * bBitsPerPixel) >> 3;
- f->fmt.pix.sizeimage =
- f->fmt.pix.height * f->fmt.pix.bytesperline;
-
- return 0;
- }

设置参数
应用程序设置参数,这里先要测试一下参数是否可用,然后把这些参数保存在内存中,留着以后用
- static int myuvc_vidioc_s_fmt_vid_cap(struct file *file, void *priv,
- struct v4l2_format *f)
- {
- int ret = myuvc_vidioc_try_fmt_vid_cap(file, NULL, f);
- if (ret < 0)
- return ret;
-
- memcpy(&myuvc_format, f, sizeof(myuvc_format));
-
- return 0;
- }
分配缓冲区,并放入队列
1.USB传来的数据会存到这些缓冲区里,应用程序读数据再从这些缓冲区里读
2.缓冲区会分配几个,但是会一次性分配,给出缓冲区的起始地址,不同的缓冲区会有不同的偏移值
3.这些缓冲区的描述在一个结构体中,在myuvc_queue中有结构体myuvc_buffer,是描述这些buffer用的
4.两个队列,队列mainqueue供APP读数据用,队列irqqueue供USB传输数据用,队列的定义也在myuvc_queue中
- struct myuvc_queue {
- void *mem;
- int count;
- int buf_size;
- struct myuvc_buffer buffer[32];
-
- struct urb *urb[32];
- char *urb_buffer[32];
- dma_addr_t urb_dma[32];
- unsigned int urb_size;
-
- struct list_head mainqueue; /* 供APP消费用 */
- struct list_head irqqueue; /* 供底层驱动生产用 */
- };
- struct myuvc_buffer {
- struct v4l2_buffer buf;
- int state;
- int vma_use_count; /* 表示是否已经被mmap */
- wait_queue_head_t wait; /* APP要读某个缓冲区,如果无数据,在此休眠 */
- struct list_head stream;
- struct list_head irq;
- };
- static int myuvc_vidioc_reqbufs(struct file *file, void *priv,
- struct v4l2_requestbuffers *p)
- {
- int nbuffers = p->count;
- int bufsize = PAGE_ALIGN(myuvc_format.fmt.pix.sizeimage);
- unsigned int i;
- void *mem = NULL;
- int ret;
-
- if ((ret = myuvc_free_buffers()) < 0)
- goto done;
-
- /* Bail out if no buffers should be allocated. */
- if (nbuffers == 0)
- goto done;
-
- /* Decrement the number of buffers until allocation succeeds. */
- for (; nbuffers > 0; --nbuffers) {
- mem = vmalloc_32(nbuffers * bufsize);
- if (mem != NULL)
- break;
- }
-
- if (mem == NULL) {
- ret = -ENOMEM;
- goto done;
- }
-
- /* 这些缓存是一次性作为一个整体来分配的 */
- memset(&myuvc_queue, 0, sizeof(myuvc_queue));
-
- INIT_LIST_HEAD(&myuvc_queue.mainqueue);
- INIT_LIST_HEAD(&myuvc_queue.irqqueue);
-
- for (i = 0; i < nbuffers; ++i) {
- myuvc_queue.buffer[i].buf.index = i;
- myuvc_queue.buffer[i].buf.m.offset = i * bufsize;
- myuvc_queue.buffer[i].buf.length = myuvc_format.fmt.pix.sizeimage;
- myuvc_queue.buffer[i].buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
- myuvc_queue.buffer[i].buf.sequence = 0;
- myuvc_queue.buffer[i].buf.field = V4L2_FIELD_NONE;
- myuvc_queue.buffer[i].buf.memory = V4L2_MEMORY_MMAP;
- myuvc_queue.buffer[i].buf.flags = 0;
- myuvc_queue.buffer[i].state = VIDEOBUF_IDLE;
- init_waitqueue_head(&myuvc_queue.buffer[i].wait);
- }
-
- myuvc_queue.mem = mem;
- myuvc_queue.count = nbuffers;
- myuvc_queue.buf_size = bufsize;
- ret = nbuffers;
-
- done:
- return ret;
- }

查询缓存的信息
这很简单只需要把myuvc_queue中相应的buffer中的buf赋给传入的参数就可以了,其余的操作是设置缓冲区状态
- static int myuvc_vidioc_querybuf(struct file *file, void *priv, struct v4l2_buffer *v4l2_buf)
- {
- int ret = 0;
-
- if (v4l2_buf->index >= myuvc_queue.count) {
- ret = -EINVAL;
- goto done;
- }
-
- memcpy(v4l2_buf, &myuvc_queue.buffer[v4l2_buf->index].buf, sizeof(*v4l2_buf));
-
- /* 更新flags */
- if (myuvc_queue.buffer[v4l2_buf->index].vma_use_count)
- v4l2_buf->flags |= V4L2_BUF_FLAG_MAPPED;
-
-
- switch (myuvc_queue.buffer[v4l2_buf->index].state) {
- case VIDEOBUF_ERROR:
- case VIDEOBUF_DONE:
- v4l2_buf->flags |= V4L2_BUF_FLAG_DONE;
- break;
- case VIDEOBUF_QUEUED:
- case VIDEOBUF_ACTIVE:
- v4l2_buf->flags |= V4L2_BUF_FLAG_QUEUED;
- break;
- case VIDEOBUF_IDLE:
- default:
- break;
- }
-
- done:
- return ret;
- }

把缓冲区放入两个队列,主要是
list_add_tail(&buf->stream, &myuvc_queue.mainqueue);
list_add_tail(&buf->irq, &myuvc_queue.irqqueue);
这两个函数,其余的都是判断和修改缓冲区状态
- static int myuvc_vidioc_qbuf(struct file *file, void *priv, struct v4l2_buffer *v4l2_buf)
- {
- struct myuvc_buffer *buf;
- int ret;
-
- /* 0. APP传入的v4l2_buf可能有问题, 要做判断 */
-
- if (v4l2_buf->type != V4L2_BUF_TYPE_VIDEO_CAPTURE ||
- v4l2_buf->memory != V4L2_MEMORY_MMAP) {
- return -EINVAL;
- }
-
- if (v4l2_buf->index >= myuvc_queue.count) {
- return -EINVAL;
- }
-
- buf = &myuvc_queue.buffer[v4l2_buf->index];
-
- if (buf->state != VIDEOBUF_IDLE) {
- return -EINVAL;
- }
-
-
- /* 1. 修改状态 */
- buf->state = VIDEOBUF_QUEUED;
- buf->buf.bytesused = 0;
-
- /* 2. 放入2个队列 */
- /* 队列1: 供APP使用
- * 当缓冲区没有数据时,放入mainqueue队列
- * 当缓冲区有数据时, APP从mainqueue队列中取出
- */
- list_add_tail(&buf->stream, &myuvc_queue.mainqueue);
-
- /* 队列2: 供产生数据的函数使用
- * 当采集到数据时,从irqqueue队列中取出第1个缓冲区,存入数据
- */
- list_add_tail(&buf->irq, &myuvc_queue.irqqueue);
-
- return 0;
- }

取出队列,供APP使用,APP调用此函数把缓冲区从mainqueue队列中取出
- static int myuvc_vidioc_dqbuf(struct file *file, void *priv, struct v4l2_buffer *v4l2_buf)
- {
- /* APP发现数据就绪后, 从mainqueue里取出这个buffer */
-
- struct myuvc_buffer *buf;
- int ret = 0;
-
- if (list_empty(&myuvc_queue.mainqueue)) {
- ret = -EINVAL;
- goto done;
- }
-
- buf = list_first_entry(&myuvc_queue.mainqueue, struct myuvc_buffer, stream);
-
- switch (buf->state) {
- case VIDEOBUF_ERROR:
- ret = -EIO;
- case VIDEOBUF_DONE:
- buf->state = VIDEOBUF_IDLE;
- break;
-
- case VIDEOBUF_IDLE:
- case VIDEOBUF_QUEUED:
- case VIDEOBUF_ACTIVE:
- default:
- ret = -EINVAL;
- goto done;
- }
-
- list_del(&buf->stream);
-
- done:
- return ret;
- }

启动摄像头传输
要做的工作有:
1. 向USB摄像头设置参数: 比如使用哪个format
2. 分配设置URB
3. 提交URB以接收数据
2.9.1 设置参数
设置streaming_control
参数存在intf->cur_altsetting->desc.bInterfaceNumber中,已被USB总线驱动读到内存,可以直接读取
streaming_control不是标准的USB描述符,USB总线驱动里没有定义结构体,我们要先参考uvcvideo.c定义streaming_control结构体
1.测试参数
设置参数前要先把参数发给摄像头,让摄像头判断这些参数可用否
把streaming_control结构体的数据赋值给数组data
用usb_control_msg发送数据测试
- static int myuvc_try_streaming_params(struct myuvc_streaming_control *ctrl)
- {
- __u8 *data;
- __u16 size;
- int ret;
- __u8 type = USB_TYPE_CLASS | USB_RECIP_INTERFACE;
- unsigned int pipe;
-
- memset(ctrl, 0, sizeof *ctrl);
-
- ctrl->bmHint = 1; /* dwFrameInterval */
- ctrl->bFormatIndex = 1;
- ctrl->bFrameIndex = frame_idx + 1;
- ctrl->dwFrameInterval = 333333;
-
-
- size = uvc_version >= 0x0110 ? 34 : 26;
- data = kzalloc(size, GFP_KERNEL);
- if (data == NULL)
- return -ENOMEM;
-
- *(__le16 *)&data[0] = cpu_to_le16(ctrl->bmHint);
- data[2] = ctrl->bFormatIndex;
- data[3] = ctrl->bFrameIndex;
- *(__le32 *)&data[4] = cpu_to_le32(ctrl->dwFrameInterval);
- *(__le16 *)&data[8] = cpu_to_le16(ctrl->wKeyFrameRate);
- *(__le16 *)&data[10] = cpu_to_le16(ctrl->wPFrameRate);
- *(__le16 *)&data[12] = cpu_to_le16(ctrl->wCompQuality);
- *(__le16 *)&data[14] = cpu_to_le16(ctrl->wCompWindowSize);
- *(__le16 *)&data[16] = cpu_to_le16(ctrl->wDelay);
- put_unaligned_le32(ctrl->dwMaxVideoFrameSize, &data[18]);
- put_unaligned_le32(ctrl->dwMaxPayloadTransferSize, &data[22]);
-
- if (size == 34) {
- put_unaligned_le32(ctrl->dwClockFrequency, &data[26]);
- data[30] = ctrl->bmFramingInfo;
- data[31] = ctrl->bPreferedVersion;
- data[32] = ctrl->bMinVersion;
- data[33] = ctrl->bMaxVersion;
- }
-
- pipe = (SET_CUR & 0x80) ? usb_rcvctrlpipe(myuvc_udev, 0)
- : usb_sndctrlpipe(myuvc_udev, 0);
- type |= (SET_CUR & 0x80) ? USB_DIR_IN : USB_DIR_OUT;
-
- ret = usb_control_msg(myuvc_udev, pipe, SET_CUR, type, VS_PROBE_CONTROL << 8,
- 0 << 8 | myuvc_streaming_intf, data, size, 5000);
-
- kfree(data);
-
- return (ret < 0) ? ret : 0;
-
- }

- static int myuvc_get_streaming_params(struct myuvc_streaming_control *ctrl)
- {
- __u8 *data;
- __u16 size;
- int ret;
- __u8 type = USB_TYPE_CLASS | USB_RECIP_INTERFACE;
- unsigned int pipe;
-
- size = uvc_version >= 0x0110 ? 34 : 26;
- data = kmalloc(size, GFP_KERNEL);
- if (data == NULL)
- return -ENOMEM;
-
- pipe = (GET_CUR & 0x80) ? usb_rcvctrlpipe(myuvc_udev, 0)
- : usb_sndctrlpipe(myuvc_udev, 0);
- type |= (GET_CUR & 0x80) ? USB_DIR_IN : USB_DIR_OUT;
-
- ret = usb_control_msg(myuvc_udev, pipe, GET_CUR, type, VS_PROBE_CONTROL << 8,
- 0 << 8 | myuvc_streaming_intf, data, size, 5000);
-
- if (ret < 0)
- goto done;
-
- ctrl->bmHint = le16_to_cpup((__le16 *)&data[0]);
- ctrl->bFormatIndex = data[2];
- ctrl->bFrameIndex = data[3];
- ctrl->dwFrameInterval = le32_to_cpup((__le32 *)&data[4]);
- ctrl->wKeyFrameRate = le16_to_cpup((__le16 *)&data[8]);
- ctrl->wPFrameRate = le16_to_cpup((__le16 *)&data[10]);
- ctrl->wCompQuality = le16_to_cpup((__le16 *)&data[12]);
- ctrl->wCompWindowSize = le16_to_cpup((__le16 *)&data[14]);
- ctrl->wDelay = le16_to_cpup((__le16 *)&data[16]);
- ctrl->dwMaxVideoFrameSize = get_unaligned_le32(&data[18]);
- ctrl->dwMaxPayloadTransferSize = get_unaligned_le32(&data[22]);
-
- if (size == 34) {
- ctrl->dwClockFrequency = get_unaligned_le32(&data[26]);
- ctrl->bmFramingInfo = data[30];
- ctrl->bPreferedVersion = data[31];
- ctrl->bMinVersion = data[32];
- ctrl->bMaxVersion = data[33];
- } else {
- //ctrl->dwClockFrequency = video->dev->clock_frequency;
- ctrl->bmFramingInfo = 0;
- ctrl->bPreferedVersion = 0;
- ctrl->bMinVersion = 0;
- ctrl->bMaxVersion = 0;
- }
-
- done:
- kfree(data);
-
- return (ret < 0) ? ret : 0;
- }

- static int myuvc_set_streaming_params(struct myuvc_streaming_control *ctrl)
- {
- __u8 *data;
- __u16 size;
- int ret;
- __u8 type = USB_TYPE_CLASS | USB_RECIP_INTERFACE;
- unsigned int pipe;
-
- size = uvc_version >= 0x0110 ? 34 : 26;
- data = kzalloc(size, GFP_KERNEL);
- if (data == NULL)
- return -ENOMEM;
-
- *(__le16 *)&data[0] = cpu_to_le16(ctrl->bmHint);
- data[2] = ctrl->bFormatIndex;
- data[3] = ctrl->bFrameIndex;
- *(__le32 *)&data[4] = cpu_to_le32(ctrl->dwFrameInterval);
- *(__le16 *)&data[8] = cpu_to_le16(ctrl->wKeyFrameRate);
- *(__le16 *)&data[10] = cpu_to_le16(ctrl->wPFrameRate);
- *(__le16 *)&data[12] = cpu_to_le16(ctrl->wCompQuality);
- *(__le16 *)&data[14] = cpu_to_le16(ctrl->wCompWindowSize);
- *(__le16 *)&data[16] = cpu_to_le16(ctrl->wDelay);
- put_unaligned_le32(ctrl->dwMaxVideoFrameSize, &data[18]);
- put_unaligned_le32(ctrl->dwMaxPayloadTransferSize, &data[22]);
-
- if (size == 34) {
- put_unaligned_le32(ctrl->dwClockFrequency, &data[26]);
- data[30] = ctrl->bmFramingInfo;
- data[31] = ctrl->bPreferedVersion;
- data[32] = ctrl->bMinVersion;
- data[33] = ctrl->bMaxVersion;
- }
-
- pipe = (SET_CUR & 0x80) ? usb_rcvctrlpipe(myuvc_udev, 0)
- : usb_sndctrlpipe(myuvc_udev, 0);
- type |= (SET_CUR & 0x80) ? USB_DIR_IN : USB_DIR_OUT;
-
- ret = usb_control_msg(myuvc_udev, pipe, SET_CUR, type, VS_COMMIT_CONTROL << 8,
- 0 << 8 | myuvc_streaming_intf, data, size, 5000);
-
- kfree(data);
-
- return (ret < 0) ? ret : 0;
-
- }

2.9.2 URB
1. 分配usb_buffers
2. 分配urb
3. 设置urb
4. 提交urb
- static int myuvc_alloc_init_urbs(void)
- {
- u16 psize;
- u32 size;
- int npackets;
- int i;
- int j;
-
- struct urb *urb;
-
- psize = wMaxPacketSize; /* 实时传输端点一次能传输的最大字节数 */
- size = myuvc_params.dwMaxVideoFrameSize; /* 一帧数据的最大长度 */
- npackets = DIV_ROUND_UP(size, psize);
- if (npackets > 32)
- npackets = 32;
-
- size = myuvc_queue.urb_size = psize * npackets;
-
- for (i = 0; i < MYUVC_URBS; ++i) {
- /* 1. 分配usb_buffers */
-
- myuvc_queue.urb_buffer[i] = usb_buffer_alloc(
- myuvc_udev, size,
- GFP_KERNEL | __GFP_NOWARN, &myuvc_queue.urb_dma[i]);
-
- /* 2. 分配urb */
- myuvc_queue.urb[i] = usb_alloc_urb(npackets, GFP_KERNEL);
-
- if (!myuvc_queue.urb_buffer[i] || !myuvc_queue.urb[i])
- {
- myuvc_uninit_urbs();
- return -ENOMEM;
- }
-
- }
-
-
- /* 3. 设置urb */
- for (i = 0; i < MYUVC_URBS; ++i) {
- urb = myuvc_queue.urb[i];
-
- urb->dev = myuvc_udev;
- urb->context = NULL;
- urb->pipe = usb_rcvisocpipe(myuvc_udev,myuvc_bEndpointAddress);
- urb->transfer_flags = URB_ISO_ASAP | URB_NO_TRANSFER_DMA_MAP;
- urb->interval = 1;
- urb->transfer_buffer = myuvc_queue.urb_buffer[i];
- urb->transfer_dma = myuvc_queue.urb_dma[i];
- urb->complete = myuvc_video_complete;
- urb->number_of_packets = npackets;
- urb->transfer_buffer_length = size;
-
- for (j = 0; j < npackets; ++j) {
- urb->iso_frame_desc[j].offset = j * psize;
- urb->iso_frame_desc[j].length = psize;
- }
-
- }
-
- /* 4. 提交URB以接收数据 */
- for (i = 0; i < MYUVC_URBS; ++i) {
- if ((ret = usb_submit_urb(myuvc_queue.urb[i], GFP_KERNEL)) < 0) {
- printk("Failed to submit URB %u (%d).\n", i, ret);
- myuvc_uninit_urbs();
- return ret;
- }
- }
-
- return 0;
- }

Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。