DM6446的视频前端VPFE驱动之ioctl控制(视频缓存区,CCDC,decoder)解析之一

来源:互联网 发布:重生之星际淘宝主百度 编辑:程序博客网 时间:2024/04/30 06:48

本文均属自己阅读源码的点滴总结,转账请注明出处谢谢。http://blog.csdn.net/gzzaigcn/article/details/7750509

欢迎和大家交流。qq:1037701636 email:200803090209@zjut.comgzzaigcn2012@gmail.com

  在这里分析驱动的ioctl的内容时,需要结合相关的应用层的操作,之前我已经说过,这块V4L2的控制都是Ioclt实现的,在完成前期的驱动后,后续的系统调用都由他来完成,主要通过应用层发送一定的命令来完成调用。之前看过很多V4L2的内容,都会涉及到ioctl的内容,在这里我不再介绍。只解析几个核心的控制命令,来实现一个简单的视频采集。

        先简单的说下视频ioctl系统调用的流程如下:

 
 
 
以上的流程图是在应用程序的ioctl和mmap的核心调用过程。每一个ioctl命令代表着对视频设备的控制。下面我选择涉及到缓存区相关操作的命令进行展开:
1.VIDIOC_REQBUFS申请视频缓存区,对应的源码位于davinci_vpfe.c的doioctl函数中,部分代码人如下:
[plain] view plaincopy
  1. case VIDIOC_REQBUFS:  //Initiates memory mapping or user pointer I/O,申请内存  
  2.     dev_dbg(vpfe_dev, "\nEnd of VIDIOC_REQBUFS ioctl");  
  3.     if (vpfe->io_usrs != 0) {  
  4.         ret = -EBUSY;  
  5.         break;  
  6.     }  
  7.     down_interruptible(&vpfe->lock);  
  8.     videobuf_queue_init(&vpfe->bufqueue, &video_qops, NULL,    
  9.                 &vpfe->irqlock,  
  10.                 V4L2_BUF_TYPE_VIDEO_CAPTURE,  
  11.                 vpfe->field,  //filed=V4L2_FIELD_INTERLACED  
  12.                 sizeof(struct videobuf_buffer), fh);//主要完成vpfe_obj中的成员变量videobuf_queue的初始化  
  13.   
  14.     videobuf_set_buftype(&vpfe->bufqueue, VIDEOBUF_BUF_LINEAR);//buf_type=VIDEOBUF_BUF_LINEAR  
  15.   
  16.     fh->io_allowed = TRUE;  
  17.     vpfe->io_usrs = 1;  
  18.     INIT_LIST_HEAD(&vpfe->dma_queue);//初始化DMA链表头  
  19.     ret = videobuf_reqbufs(&vpfe->bufqueue, arg);//获取内存分配,完成相关初始化  
  20.     up(&vpfe->lock);  
  21.     dev_dbg(vpfe_dev, "\nEnd of VIDIOC_REQBUFS ioctl");  
  22.     break;  
在这个case中,可以看到主要调用了videobuf_queue_init,在这个函数里主要完成了videobuf_queue这个缓存区队列的初始化,填充了其相关的内容,核心包括video_qops等。随后调用videobuf_reqbufs完成缓存区的真正申请。当然分析源码后核心的其实调用的是 q->ops->buf_setup(q,&count,&size); 函数,也就是队列初始化后的video_qops的buf_setup(所谓的缓存区真正的申请),下面我们来看这块的内容:
[plain] view plaincopy
  1. buffer_setup(struct videobuf_queue *q, unsigned int *count, unsigned int *size)  
  2. {  
  3.     vpfe_obj *vpfe = &vpfe_device;  
  4.     int i;  
  5.     unsigned int buf_size;  
  6.     dev_dbg(vpfe_dev, "\nstarting buffer_setup");  
  7.     if (device_type == TVP5146) {  
  8.         *size = buf_size = VPFE_TVP5146_MAX_FBUF_SIZE; //最大缓冲区768*576*2  
  9.     } else {  
  10.         *size = buf_size = VPFE_MT9T001_MAX_FBUF_SIZE;  
  11.     }  
  12.   
  13.     for (i = VPFE_DEFNUM_FBUFS; i < *count; i++) {  //VPFE_DEFNUM_FBUFS=3,如果多余3个再申请,否则跳过  
  14.         u32 size = PAGE_SIZE << (get_order(buf_size));  
  15.         void *mem = (void *)__get_free_pages(GFP_KERNEL | GFP_DMA, //DMA内存申请  
  16.                              get_order(buf_size));  
  17.         if (mem) {  
  18.             unsigned long adr = (unsigned long)mem;  
  19.             while (size > 0) {  
  20.                 /* make sure the frame buffers are never   
  21.                    swapped out of memory *///交换内存  
  22.                 SetPageReserved(virt_to_page(adr));  
  23.                 adr += PAGE_SIZE;  
  24.                 size -= PAGE_SIZE;  
  25.             }  
  26.             vpfe->fbuffers[i] = mem;  
  27.         } else {  
  28.             break;  
  29.         }  
  30.     }  
  31.     *count = vpfe->numbuffers = i;     //3      
  32.     dev_dbg(vpfe_dev, "\nEnd of buffer_setup");  
  33.     return 0;  
  34. }  
在这个函数中,我们可以看到会根据用户应用程序设置的count参数,来判断是否还需要设置额外的缓存区,因为在注册驱动初始化的函数是已经申请了3个缓存区。获取了3个缓存区的虚拟内存地址。这个采用get_free_pages完成一个缓存区的页式申请,以4K一页为单位,完成申请,同时SetPageReserved完成每一页内存的驻留(在未释放前,不允许再申请)。最后将每一个缓存区内存首地址存入fbuffers[]数组之中。
同时在videobuf_reqbufs函数中还会调用videobuf_mmap_setup完成缓存区队列中的缓存区实例完成初始化,包括这个缓存区的相关性质。同时在该函数buffer_config中完成虚拟内存地址到实际物理地址的转换,存入到缓存区实例中。
 
2.VIDIOC_QUERYBUF查询缓存区的信息
执行函数videobuf_querybuf,最终会调用videobuf_status,完成当前缓存区信息的传递到用户层。
 
3.mmap的相关操作
在这里,针对视频数据量较大的内容,用户和内存之间的数据交互,是最为关键的内容。因此不能使用简单的read,write等系统调用来完成数据的读取,合理的方式是采用mmap这个系统调用,直接将物理内存映射到用户空间,不必要再使用read,write等从内核空间将数据进行拷贝,只需获取内存物理映射后的首地址即可。
内核中调用的函数为videobuf_mmap_mapper,代码如下:
 
[plain] view plaincopy
  1. int videobuf_mmap_mapper(struct videobuf_queue *q,  
  2.              struct vm_area_struct *vma)  
  3. {  
  4.     struct videobuf_mapping *map;  
  5.     unsigned int first,last,size,i;  
  6.     int retval;  
  7.   
  8.     down(&q->lock);  
  9.     retval = -EINVAL;  
  10.     if (!(vma->vm_flags & VM_WRITE)) {  
  11.         dprintk(1,"mmap app bug: PROT_WRITE please\n");  
  12.         goto done;  
  13.     }  
  14.     if (!(vma->vm_flags & VM_SHARED)) {  
  15.         dprintk(1,"mmap app bug: MAP_SHARED please\n");  
  16.         goto done;  
  17.     }  
  18.   
  19.     /* look for first buffer to map */  
  20.     for (first = 0; first < VIDEO_MAX_FRAME; first++) {  
  21.         if (NULL == q->bufs[first])  
  22.             continue;  
  23.         if (V4L2_MEMORY_MMAP != q->bufs[first]->memory)  
  24.             continue;  
  25.         if (q->bufs[first]->boff == (vma->vm_pgoff << PAGE_SHIFT))//在这里boff对应的为缓存区的物理首地址  
  26.             break;  
  27.     }  
  28.     if (VIDEO_MAX_FRAME == first) {  
  29.         dprintk(1,"mmap app bug: offset invalid [offset=0x%lx]\n",  
  30.             (vma->vm_pgoff << PAGE_SHIFT));  
  31.         goto done;  
  32.     }  
  33.   
  34.     /* look for last buffer to map */  
  35.     for (size = 0, last = first; last < VIDEO_MAX_FRAME; last++) {  
  36.         if (NULL == q->bufs[last])  
  37.             continue;  
  38.         if (V4L2_MEMORY_MMAP != q->bufs[last]->memory)  
  39.             continue;  
  40.         if (q->bufs[last]->map) {  
  41.             retval = -EBUSY;  
  42.             goto done;  
  43.         }  
  44.         size += q->bufs[last]->bsize;  
  45.         if (size == (vma->vm_end - vma->vm_start))  
  46.             break;  
  47.     }  
  48.     if (VIDEO_MAX_FRAME == last) {  
  49.         dprintk(1,"mmap app bug: size invalid [size=0x%lx]\n",  
  50.             (vma->vm_end - vma->vm_start));  
  51.         goto done;  
  52.     }  
  53.   
  54.     /* create mapping + update buffer list */  
  55.     retval = -ENOMEM;  
  56.     map = kmalloc(sizeof(struct videobuf_mapping),GFP_KERNEL);  
  57.     if (NULL == map)  
  58.         goto done;  
  59.     for (size = 0, i = first; i <= last; size += q->bufs[i++]->bsize) {  
  60.         q->bufs[i]->map   = map;  
  61.         q->bufs[i]->baddr = vma->vm_start + size;  
  62.     }  
  63.     map->count    = 1;  
  64.     map->start    = vma->vm_start;  
  65.     map->end      = vma->vm_end;  
  66.     map->q        = q;  
  67.     if(q->buf_type == VIDEOBUF_BUF_LINEAR){  
  68.         vma->vm_page_prot = pgprot_writecombine(vma->vm_page_prot);  
  69.         if (io_remap_page_range(vma, vma->vm_start,   //建立页表,完成用户空间和物理内存的直接映射  
  70.             (vma->vm_pgoff << PAGE_SHIFT),//实际所在的需要映射的物理页地址  
  71.             (vma->vm_end - vma->vm_start),  
  72.             vma->vm_page_prot)){  
  73.             return -EAGAIN;  
  74.         }  
  75.         vma->vm_flags |= VM_RESERVED | VM_IO;  
  76.     }else {  
  77.         vma->vm_ops   = &videobuf_vm_ops;  
  78.         vma->vm_flags |= VM_DONTEXPAND | VM_RESERVED;  
  79.         vma->vm_flags &= ~VM_IO; /* using shared anonymous pages */  
  80.     }  
  81.     vma->vm_private_data = map;  
  82.     dprintk(1,"mmap %p: q=%p %08lx-%08lx pgoff %08lx bufs %d-%d\n",  
  83.         map,q,vma->vm_start,vma->vm_end,vma->vm_pgoff,first,last);  
  84.     retval = 0;  
  85.   
  86.  done:  
  87.     up(&q->lock);  
  88.     return retval;  
  89. }  
  90.   
  91. int videobuf_set_buftype(struct videobuf_queue *q, enum videobuf_buf_type type)  
  92. {  
  93.     q->buf_type = type;  
  94.     return 0;  
  95. }  
对于mmap的相关概念理解在这里不做解释,本人也是参考着网上的资料学习而得。整个映射过程简单如图:
 
 
     结合此图和mmap在驱动代码中的实现,我们可以分析出申请的缓存区物理地址的boff偏移量就等于vm_start与文件打开时在用户空间的虚拟首地址的差值,即用户层调用mmap时设置的offset。其实对于普通的文件以及/dev/mem linux内核已经都做好了系统调用的驱动实现。而针对自己需要使用mmap实现设备文件盒物理内存的关联即做映射,则需要自己实现mmap的功能。主要实现的函数io_remap_page_range完成页式的物理内存映射,传入的参数分别为vma这个结构体,文件映射部分在用户空间的首地址addr,以及在物理内存中的实际地址页帧号pfn,即物理地址》4K(一个页的大小),以及整个文件映射部分的大小size。实际上这个pfn参数虽然来自于offset参数,但是整个offset先前已经设置为了我们申请的缓存区在物理内存中的偏移值。换句话说,这个和设备文件做关联的内存并不是凭空产生的,而是先前已经获取。这样的合理设计整个缓存区,是整个V4L2这个视频驱动的重要部分。内存的合理分配也显得格外重要。
      具体后续的缓存区队列的相关操作,我在下一篇博文中进行简单的分析和介绍。
原创粉丝点击