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

本文均属自己阅读源码的点滴总结,转账请注明出处谢谢。

欢迎和大家交流。qq:1037701636 email:[email protected][email protected]

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

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

  DM6446的视频前端VPFE驱动之ioctl控制(视频缓存区,CCDC,decoder)解析之一_第1张图片
 
 
以上的流程图是在应用程序的ioctl和mmap的核心调用过程。每一个ioctl命令代表着对视频设备的控制。下面我选择涉及到缓存区相关操作的命令进行展开:
1.VIDIOC_REQBUFS申请视频缓存区,对应的源码位于davinci_vpfe.c的doioctl函数中,部分代码人如下:
	case VIDIOC_REQBUFS:  //Initiates memory mapping or user pointer I/O,申请内存
		dev_dbg(vpfe_dev, "\nEnd of VIDIOC_REQBUFS ioctl");
		if (vpfe->io_usrs != 0) {
			ret = -EBUSY;
			break;
		}
		down_interruptible(&vpfe->lock);
		videobuf_queue_init(&vpfe->bufqueue, &video_qops, NULL,  
				    &vpfe->irqlock,
				    V4L2_BUF_TYPE_VIDEO_CAPTURE,
				    vpfe->field,  //filed=V4L2_FIELD_INTERLACED
				    sizeof(struct videobuf_buffer), fh);//主要完成vpfe_obj中的成员变量videobuf_queue的初始化

		videobuf_set_buftype(&vpfe->bufqueue, VIDEOBUF_BUF_LINEAR);//buf_type=VIDEOBUF_BUF_LINEAR

		fh->io_allowed = TRUE;
		vpfe->io_usrs = 1;
		INIT_LIST_HEAD(&vpfe->dma_queue);//初始化DMA链表头
		ret = videobuf_reqbufs(&vpfe->bufqueue, arg);//获取内存分配,完成相关初始化
		up(&vpfe->lock);
		dev_dbg(vpfe_dev, "\nEnd of VIDIOC_REQBUFS ioctl");
		break;
在这个case中,可以看到主要调用了videobuf_queue_init,在这个函数里主要完成了videobuf_queue这个缓存区队列的初始化,填充了其相关的内容,核心包括video_qops等。随后调用videobuf_reqbufs完成缓存区的真正申请。当然分析源码后核心的其实调用的是 q->ops->buf_setup(q,&count,&size); 函数,也就是队列初始化后的video_qops的buf_setup(所谓的缓存区真正的申请),下面我们来看这块的内容:
buffer_setup(struct videobuf_queue *q, unsigned int *count, unsigned int *size)
{
	vpfe_obj *vpfe = &vpfe_device;
	int i;
	unsigned int buf_size;
	dev_dbg(vpfe_dev, "\nstarting buffer_setup");
	if (device_type == TVP5146) {
		*size = buf_size = VPFE_TVP5146_MAX_FBUF_SIZE; //最大缓冲区768*576*2
	} else {
		*size = buf_size = VPFE_MT9T001_MAX_FBUF_SIZE;
	}

	for (i = VPFE_DEFNUM_FBUFS; i < *count; i++) {  //VPFE_DEFNUM_FBUFS=3,如果多余3个再申请,否则跳过
		u32 size = PAGE_SIZE << (get_order(buf_size));
		void *mem = (void *)__get_free_pages(GFP_KERNEL | GFP_DMA, //DMA内存申请
						     get_order(buf_size));
		if (mem) {
			unsigned long adr = (unsigned long)mem;
			while (size > 0) {
				/* make sure the frame buffers are never 
				   swapped out of memory *///交换内存
				SetPageReserved(virt_to_page(adr));
				adr += PAGE_SIZE;
				size -= PAGE_SIZE;
			}
			vpfe->fbuffers[i] = mem;
		} else {
			break;
		}
	}
	*count = vpfe->numbuffers = i;     //3    
	dev_dbg(vpfe_dev, "\nEnd of buffer_setup");
	return 0;
}
在这个函数中,我们可以看到会根据用户应用程序设置的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,代码如下:
 
int videobuf_mmap_mapper(struct videobuf_queue *q,
			 struct vm_area_struct *vma)
{
	struct videobuf_mapping *map;
	unsigned int first,last,size,i;
	int retval;

	down(&q->lock);
	retval = -EINVAL;
	if (!(vma->vm_flags & VM_WRITE)) {
		dprintk(1,"mmap app bug: PROT_WRITE please\n");
		goto done;
	}
	if (!(vma->vm_flags & VM_SHARED)) {
		dprintk(1,"mmap app bug: MAP_SHARED please\n");
		goto done;
	}

	/* look for first buffer to map */
	for (first = 0; first < VIDEO_MAX_FRAME; first++) {
		if (NULL == q->bufs[first])
			continue;
		if (V4L2_MEMORY_MMAP != q->bufs[first]->memory)
			continue;
		if (q->bufs[first]->boff == (vma->vm_pgoff << PAGE_SHIFT))//在这里boff对应的为缓存区的物理首地址
,这部分的内容由前期的VIDIOC_QUERYBUF来获取boff,在mmap传入offset时,意味着vm_pgoff等于offset.			break;
	}
	if (VIDEO_MAX_FRAME == first) {
		dprintk(1,"mmap app bug: offset invalid [offset=0x%lx]\n",
			(vma->vm_pgoff << PAGE_SHIFT));
		goto done;
	}

	/* look for last buffer to map */
	for (size = 0, last = first; last < VIDEO_MAX_FRAME; last++) {
		if (NULL == q->bufs[last])
			continue;
		if (V4L2_MEMORY_MMAP != q->bufs[last]->memory)
			continue;
		if (q->bufs[last]->map) {
			retval = -EBUSY;
			goto done;
		}
		size += q->bufs[last]->bsize;
		if (size == (vma->vm_end - vma->vm_start))
			break;
	}
	if (VIDEO_MAX_FRAME == last) {
		dprintk(1,"mmap app bug: size invalid [size=0x%lx]\n",
			(vma->vm_end - vma->vm_start));
		goto done;
	}

	/* create mapping + update buffer list */
	retval = -ENOMEM;
	map = kmalloc(sizeof(struct videobuf_mapping),GFP_KERNEL);
	if (NULL == map)
		goto done;
	for (size = 0, i = first; i <= last; size += q->bufs[i++]->bsize) {
		q->bufs[i]->map   = map;
		q->bufs[i]->baddr = vma->vm_start + size;
	}
	map->count    = 1;
	map->start    = vma->vm_start;
	map->end      = vma->vm_end;
	map->q        = q;
	if(q->buf_type == VIDEOBUF_BUF_LINEAR){
		vma->vm_page_prot = pgprot_writecombine(vma->vm_page_prot);
		if (io_remap_page_range(vma, vma->vm_start,   //建立页表,完成用户空间和物理内存的直接映射
			(vma->vm_pgoff << PAGE_SHIFT),//实际所在的需要映射的物理页地址
			(vma->vm_end - vma->vm_start),
			vma->vm_page_prot)){
			return -EAGAIN;
		}
		vma->vm_flags |= VM_RESERVED | VM_IO;
	}else {
		vma->vm_ops   = &videobuf_vm_ops;
		vma->vm_flags |= VM_DONTEXPAND | VM_RESERVED;
		vma->vm_flags &= ~VM_IO; /* using shared anonymous pages */
	}
	vma->vm_private_data = map;
	dprintk(1,"mmap %p: q=%p %08lx-%08lx pgoff %08lx bufs %d-%d\n",
		map,q,vma->vm_start,vma->vm_end,vma->vm_pgoff,first,last);
	retval = 0;

 done:
	up(&q->lock);
	return retval;
}

int videobuf_set_buftype(struct videobuf_queue *q, enum videobuf_buf_type type)
{
	q->buf_type = type;
	return 0;
}
对于mmap的相关概念理解在这里不做解释,本人也是参考着网上的资料学习而得。整个映射过程简单如图:
DM6446的视频前端VPFE驱动之ioctl控制(视频缓存区,CCDC,decoder)解析之一_第2张图片 
 
     结合此图和mmap在驱动代码中的实现,我们可以分析出申请的缓存区物理地址的boff这个偏移量等于offset,即用户层调用mmap时设置的offset(通过调用返回该参数值)。做到对显存buffer的完全映射。其实对于普通的文件以及/dev/mem linux内核已经都做好了系统调用的驱动实现。而针对自己需要使用mmap实现设备文件盒物理内存的关联即做映射,则需要自己实现mmap的功能。主要实现的函数io_remap_page_range完成页式的物理内存映射,传入的参数分别为vma这个结构体,文件映射部分在用户空间的首地址addr,以及在物理内存中的实际地址页帧号pfn,即物理地址》4K(一个页的大小),以及整个文件映射部分的大小size。实际上这个pfn参数虽然来自于offset参数,但是整个offset先前已经设置为了我们申请的缓存区在物理内存中的偏移值。换句话说,这个和设备文件做关联的内存并不是凭空产生的,而是先前已经获取。这样的合理设计整个缓存区,是整个V4L2这个视频驱动的重要部分。内存的合理分配也显得格外重要。
      具体后续的缓存区队列的相关操作,我在下一篇博文中进行简单的分析和介绍。
 

 
 
 

你可能感兴趣的:(DM6446的视频前端VPFE驱动之ioctl控制(视频缓存区,CCDC,decoder)解析之一)