1、分配和初始化URB
static int myuvc_alloc_init_urbs(void)
{
u16 psize;
u32 size;
int npackets;
int i;
int j;
struct urb *urb;
psize = wMaxPacketSize; /* 实时传输端点一次能传输的最大字节数 */ 1024
size = myuvc_params.dwMaxVideoFrameSize; /* 一帧数据的最大长度 */
npackets = DIV_ROUND_UP(size, psize);传输一帧数据需要传输多少次,可计算得到,DIV_ROUND_UP()函数是向上取整,如是2.5,就取3
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,//第二个参数是分配的大小,第3个参数是标志,第4个参数存储分配的物理地址
GFP_KERNEL | __GFP_NOWARN, &myuvc_queue.urb_dma[i]);第一个参数就是 struct usb_device 结构体的指针 , 第二个参数申请的 buffer 的大小 , 第三个参数 ,GFP_KERNEL, 是一个内存申请的 flag, 通常内存申请都用这个 flag, 除非是中断上下文 , 不能睡眠 , 那就得用 GPF_ATOMIC, 这里没那么多要求 .而 usb_buffer_alloc() 的第四个参数 涉及到 dma 传输 .该函数不仅进行内存分配,还会进行DMA映射,这里第四个参数将被设置为DMA地址。
这个地址用于传输DMA缓冲区数据的urb。
用 usb_buffer_alloc 申请的内存空间需要用它的搭档 usb_buffer_free() 来释放 .
创建urb 结构体的函数为:
struct urb *usb_alloc_urb(int iso_packets, int mem_flags);
iso_packets 是这个urb 应当包含的等时数据包的数目,若为0 表示不创建等时数据包。
mem_flags 参数是分配内存的标志,和kmalloc()函数的分配标志参数含义相同。如果分配成功,该函数返回一个urb 结构体指针,否则返回0。
urb 结构体在驱动中不能静态创建,因为这可能破坏USB 核心给urb 使用的引用计数方法。
usb_alloc_urb()的“反函数”为:
void usb_free_urb(struct urb *urb);
该函数用于释放由usb_alloc_urb()分配的urb 结构体。
urb->pipe = usb_rcvisocpipe(myuvc_udev,myuvc_bEndpointAddress);//管道设置(哪一个端点0x81)‘
’unsigned int usb_rcvisocpipe(struct usb_device *dev, unsigned int endpoint)
把指定USB设备指定端点设置为一个等时OUT端点。
urb->transfer_flags = URB_ISO_ASAP | URB_NO_TRANSFER_DMA_MAP;
urb->interval = 1;//端点描述符里面的bInterval
urb->transfer_buffer = myuvc_queue.urb_buffer[i];//分配的是哪一个urb_buffer
urb->transfer_dma = myuvc_queue.urb_dma[i];//buffer对应的物理地址值
urb->complete = myuvc_video_complete;//驱动程序收到一个urb包后,会产生一个中断,这是相应的中断处理函数
urb->number_of_packets = npackets;//urb要传输多少次数据
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;
}
}
return 0;
}
2、中断处理函数
tatic void myuvc_video_complete(struct urb *urb)
{
u8 *src;
u8 *dest;
int ret, i;
int len;
int maxlen;
int nbytes;
struct myuvc_buffer *buf;
switch (urb->status) {
case 0:
break;
default:
printk("Non-zero status (%d) in video "
"completion handler.\n", urb->status);
return;
}
/* 从irqqueue队列中取出第1个缓冲区 */
if (!list_empty(&myuvc_queue.irqqueue))
{
buf = list_first_entry(&myuvc_queue.irqqueue, struct myuvc_buffer, irq);//根据节点irq从队列queue里取出第一个buffer(myuvc_buffer)
把错误数据忽略掉
if (src[1] & UVC_STREAM_ERR) {
printk("Dropping payload (error bit set).\n");
continue;
}
c
/* 除去头部后的数据长度 */
len -= src[0];
/* 缓冲区最多还能存多少数据 */
maxlen = buf->buf.length - buf->buf.bytesused;
nbytes = min(len, maxlen);//所存值是它们俩的最小值
/* 复制数据 */吧urb里面的数据复制到缓存里面去,复制的长度是数据减去头部的大小
memcpy(dest, src + src[0], nbytes);//源(加头部信息)、目的、长度
buf->buf.bytesused += nbytes;
/* 判断一帧数据是否已经全部接收到 */一次传输有多个frame
if (len > maxlen) {
buf->state = VIDEOBUF_DONE;
}
/* Mark the buffer as done if the EOF marker is set. */
收到的数据有eof标志且之前已经接收到数据
if (src[1] & UVC_STREAM_EOF && buf->buf.bytesused != 0) {
printk("Frame complete (EOF found).\n");
//如果除去头部后数据长度为0,接收到空帧,打印错误
if (len == 0)
printk("EOF in empty payload.\n");
buf->state = VIDEOBUF_DONE;//修改状态,表示1帧结束
}
}
/* 当接收完一帧数据,
* 从irqqueue中删除这个缓冲区
* 唤醒等待数据的进程
*/
if (buf->state == VIDEOBUF_DONE ||
buf->state == VIDEOBUF_ERROR)
{
list_del(&buf->irq);
wake_up(&buf->wait);//在buf里面有1个wait队列,如果应用程序在等待队列,在wait队列休眠。得到数据后在wait队列把其唤醒
}
}
/* 再次提交URB */
if ((ret = usb_submit_urb(urb, GFP_ATOMIC)) < 0) {
printk("Failed to resubmit video URB (%d).\n", ret);
}
}
3、mmap函数
static int myuvc_mmap(struct file *file, struct vm_area_struct*vma)
{
struct myuvc_buffer *buffer;
struct page *page;
unsigned long addr, start, size;
unsigned int i;
int ret = 0;
,关于虚存管理的最基本的管理单元应该是struct vm_area_struct了,它描述的是一段连续的、具有相同访问属性的虚存空间,该虚存空间的大小为物理内存页面的整数倍。
start = vma->vm_start;
size = vma->vm_end - vma->vm_start;
linux内核使用vm_area_struct结构来表示一个独立的虚拟内存区域,由于每个不同质的虚拟内存区域功能和内部机制都不同,因此一个进程使用多个vm_area_struct结构来分别表示不同类型的虚拟内存区域。各个vm_area_struct结构使用链表或者树形结构链接,方便进程快速访问,如下图所示:
vm_area_struct结构中包含区域起始和终止地址以及其他相关信息,同时也包含一个vm_ops指针,其内部可引出所有针对这个区域可以使用的系统调用函数。这样,进程对某一虚拟内存区域的任何操作需要用要的信息,都可以从vm_area_struct中获得。mmap函数就是要创建一个新的vm_area_struct结构,并将其与文件的物理磁盘地址相连。
/* 应用程序调用mmap函数时, 会传入offset参数
* 根据这个offset找出指定的缓冲区
*/
for (i = 0; i < myuvc_queue.count; ++i) {
buffer = &myuvc_queue.buffer[i];//轮询所分配的每一个buffer
if ((buffer->buf.m.offset >> PAGE_SHIFT) == vma->vm_pgoff)//偏移值
break;
}
//如果没找到就产生错误
if (i == myuvc_queue.count || size != myuvc_queue.buf_size) {
ret = -EINVAL;
goto done;
}
/*
* VM_IO marks the area as being an mmaped region for I/O to a
* device. It also prevents the region from being core dumped.
*/
vma->vm_flags |= VM_IO;
/* 根据虚拟地址找到缓冲区对应的page构体 */
addr = (unsigned long)myuvc_queue.mem + buffer->buf.m.offset;
while (size > 0) {
page = vmalloc_to_page((void *)addr);
/* 把page和APP传入的虚拟地址挂构 */
if ((ret = vm_insert_page(vma, start, page)) < 0)
goto done;
start += PAGE_SIZE;
addr += PAGE_SIZE;
size -= PAGE_SIZE;
}
vma->vm_ops = &myuvc_vm_ops;//把vma的引用计数增加
vma->vm_private_data = buffer;
myuvc_vm_open(vma);
done:
return ret;
}
4、启动传输
static int myuvc_vidioc_streamon(struct file *file, void *priv, enum v4l2_buf_type i)
{
/* 2. 分配设置URB */
ret = myuvc_alloc_init_urbs();
if (ret)
printk("myuvc_alloc_init_urbs err : ret = %d\n", ret);
/* 3. 提交URB以接收数据 */有多个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;
}
5、总结:
(1)当接收到一个urb包时,会产生一个中断,从而调用myuvc_video_complete函数。
(2)myuvc_video_complete函数:从irqqueue队列中取出第1个缓冲区,缓冲区用于存放urb的数据。
(3)对于实时传输,每一个urb有多个frame,吧一个个frame取出来判断,如果frame有错误,取出下一个frame.
(4)frame的数据存放在src,这些数据将存到dest里面,这些数据有头部信息,我们根据头部信息来判断帧是否有效,如果有效,吧头部数据以外的数据存放到我们的buf里面。根据头部信息的(前含第0个数据)第一个数据来判断是否接收了一个完整的帧。接收完成后修改buf状态。
(5)如果接收完一帧就把在buf的wait队列上的用户程序唤醒。
(6)urb处理完后,重新提交urb