JZ2440 摄像头驱动之实现数据传输4_URB_学习笔记

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() 来释放 .



        /* 2. 分配urb */  是一个结构体,里面1个成员指向分配的缓冲区
myuvc_queue.urb[i] = usb_alloc_urb(npackets, GFP_KERNEL);

创建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 结构体。


        if (!myuvc_queue.urb_buffer[i] || !myuvc_queue.urb[i]) //如果urb结构体或urb的buffer请求失败就返回错误
        {
            myuvc_uninit_urbs();
            return -ENOMEM;
        }


    }

    /* 3. 设置urb */对每个urb结构体进行设置

    for (i = 0; i < MYUVC_URBS; ++i) {
        urb = myuvc_queue.urb[i];
        
        urb->dev = myuvc_udev; //myuvc_udev结构体
        urb->context = NULL;

        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
    

  • 原型
    #define list_first_entry(ptr, type, member)     
  • 功能
    已知type类型的结构体中member成员的指针后,求得它所在的链表的下一个指针所指的member所在的type类型的结构体的起始地址!
  • 参数
    ptr:type类型的结构体中member成员的指针
    type:结构体类型
    member:结构体中成员
  • 返回
    结构体的起始地址!



    for (i = 0; i < urb->number_of_packets; ++i) {//urb传输包含多个子包,打印每个包的状态,对每个packet进行操作
    if (urb->iso_frame_desc[i].status < 0) {
    printk("USB isochronous frame "
    "lost (%d).\n", urb->iso_frame_desc[i].status);
    continue;
    }


            src  = urb->transfer_buffer + urb->iso_frame_desc[i].offset;//数据源(urb的起始地址+子包的偏移值)


            dest = myuvc_queue.mem + buf->buf.m.offset + buf->buf.bytesused;//目的(目的地址+buf偏移值+buf里已放的数据)


            len = urb->iso_frame_desc[i].actual_length;//urb传输的实际大小
            /* 判断数据是否有效 */
            /* URB数据含义:
             * data[0] : 头部长度
             * data[1] : 错误状态
             */

            if (len < 2 || src[0] < 2 || src[0] > len)//长度len小于2处理下一个小包,头部长度比整个数据还大
                continue;
            
            /* Skip payloads marked with the error bit ("error frames"). */

把错误数据忽略掉
            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

猜你喜欢

转载自blog.csdn.net/weixin_38807927/article/details/87919491