概述我们都知道,V4L2是一种标准的多媒体驱动框架,但它只是起到了承接上层调用到设备驱动代码的作用,但是每个多媒体驱动的内存管理是独立的,比如fimc,jpeg,mfc等的内存都是自己独立申请,释放。 随着多媒体驱动的共性越来越多,为了抽象代码,便于开发和维护,内核现在在多媒体视音频驱动目录drivers/medis/video/下已经逐渐统一了规范。主要分为几大块,可以归纳为v4l2,subdev, 我们都知道,V4L2是一种标准的多媒体驱动框架,但它只是起到了承接上层调用到设备驱动代码的作用,但是每个多媒体驱动的内存管理是独立的,比如fimc,jpeg,mfc等的内存都是自己独立申请,释放。 随着多媒体驱动的共性越来越多,为了抽象代码,便于开发和维护,内核现在在多媒体视音频驱动目录drivers/medis/vIDeo/下已经逐渐统一了规范。主要分为几大块,可以归纳为v4l2,subdev,mem2mem,vIDeobuf2: (1)v4L2 包括像v4l2-common.c,v4l2-dev.c,v4l2-device.c, v4l2-ioctl.c等文件。这些代码是实现v4l2框架的。一般vIDeo设备驱动调用此框架的函数在上层/dev/下创建vIDeo设备。下面所说的实现vIDeo设备驱动也是指这个意思。 (2)subdev子设备模块 主要是V4L2-subdev.c,这是实现V4L2 i2c子设备的。可以举个例子来理解,fimc是AP上的平台设备,而camera是挂在fimc上的设备,可以把fimc实现为标准的v4l2驱动,而camera实现为subdev子设备驱动,fimc通过一个v4l2_subdev结构指针来调用camera驱动。上层应用只管跟fimc打交道就行了,不需要去跟camera交互。 (3)mem2mem内存管理模块 主要是v4l2-mem2mem.c文件,这是被实现vIDeo设备驱动调用的,它负责内存的管理。记住,它只是一个中间的管理者,因为真正分配内存的不是mem2mem,而是vIDeobuf2。mem2mem是通过调用vIDeobuf2模块管理内存的。 (4)vIDeobuf2内存分配模块 主要包括vIDeobuf2-xxx.c文件。这是最终分配内存的地方,旧版本是vIDeobuf。它一般是被mem2mem或者实现vIDeo设备驱动调用。 mem2mem模块分析 首先要创建v4l2_m2m_dev结构: struct v4l2_m2m_dev { struct v4l2_m2m_ctx *curr_ctx; /*当前运行的实例上下文*/ struct List_head job_queue; /*所有要运行的实例的链表*/ spinlock_t job_spinlock; /*自旋锁,保护job_queue链表的*/ struct v4l2_m2m_ops *m2m_ops; /*mem2mem驱动回调函数,需要你自己写的驱动实现*/ }; struct v4l2_m2m_ops { voID (*device_run)(voID *priv); /*在这回调函数实现中,开始工作(数据流传输)。这个回调函数必须实现,并且这个函数调用完毕后,数据流不会停止工作,必须调用v4l2_m2m_job_finish()停止数据流工作*/ int (*job_ready)(voID *priv); /*这回调函数是可选实现的,一般不使用*/ voID (*job_abort)(voID *priv); /*必须实现,异常处理函数,通知驱动马上停止数据流工作。当通知完成后,也必须调用v4l2_m2m_job_finish()*/ voID (*lock)(voID *priv); voID (*unlock)(voID *priv); }; v4l2_m2m_dev一般内嵌在你自己的设备驱动的私有结构体中,一般为指针。在我们自己的驱动中,一般是先实现v4l2_m2m_ops函数结构体,一般只实现device_run()和job_abort()函数。 然后在驱动probe时调用v4l2_m2m_init()函数初始化。主要是初始化v4l2_m2m_dev结构体和赋值v4l2_m2m_ops结构体,供回调使用。在驱动卸载时调用v4l2_m2m_release()函数释放。 我们之前已经说了,我们实现的是vIDeo设备驱动,也就是会在/dev/目录下创建vIDeo设备,它是一个字符设备,那肯定有open,close,poll,mmap,ioctl函数的实现啦。 我们逐渐来看在这些函数中,需要对mem2mem做些什么。 最首先是open()函数了, 一般先声明自己一个contex,并内嵌一个struct v4l2_m2m_ctx,如 struct jpeg_ctx { 。。。。。 struct jpeg_dev *dev; struct v4l2_m2m_ctx *m2m_ctx; 。。。。。 }; struct v4l2_m2m_ctx定义如下: struct v4l2_m2m_ctx { struct v4l2_m2m_dev *m2m_dev; /* Capture 队列上下文(也就是用来数据流要写进内存的) */ struct v4l2_m2m_queue_ctx cap_q_ctx; /* Output 队列上下文(也就是用来数据流要从内存中读出的) queue context */ struct v4l2_m2m_queue_ctx out_q_ctx; /* For device job queue */ struct List_head queue; unsigned long job_flags; wait_queue_head_t finished; /* Instance private data */ voID *priv; /*私有变量*/ }; 我们看到,数据流控制,上面都是用了Capture和Output队列上下文来管理 。对于JPEG驱动编码来说,capture是编码后的内存,output是要编码的图像内存。 struct v4l2_m2m_queue_ctx { struct vb2_queue q; /*只限于内部使用,mem2mem对内存的管理最多是使用vb2_queue来管理了*/ /* Queue for buffers ready to be processed as soon as this * instance receives access to the device */ struct List_head rdy_queue; spinlock_t rdy_spinlock; u8 num_rdy; }; rdy_queue是所有准备工作queue的链表,num_rdy是准备工作的queue个数。也就是一个queue就要工作一次,就要调用一次device_run()。而q应该是当前要工作的queue。 这里题外话,vb2_queue是一个vIDeobuf2队列。这涉及到vIDeobuf2的内容了,定义在vIDeobuf2-core.h中。 struct vb2_queue { ... const struct vb2_ops *ops; /*回调函数,需要驱动实现*/ const struct vb2_mem_ops *mem_ops; /*回调函数,需要驱动实现,一般用来分配内存*/ ... struct vb2_buffer *bufs[VIDEO_MAX_FRAME]; /*内存的buffer*/ unsigned int num_buffers; /*buffer个数*/ }; 上面的ops和mem_ops都要驱动去实现 (我多处说到的驱动就是指自己写的驱动),mem_ops一般赋为vb2_cma_phys_memops或者vb2_ion_memops等,这是内核已经写好的。 而最终对数据进行存储的就是struct vb2_buffer结构了。 struct vb2_buffer { ... struct v4l2_plane v4l2_planes[VIDEO_MAX_PLAnes]; unsigned int num_planes; ... struct vb2_plane planes[VIDEO_MAX_PLAnes]; unsigned int num_planes_mapped; }; 它还记录buffer是使用多少个plane,就是使用多少个平面。一般为1、2或3。v4l2_plane跟vb2_plane就具体不知道有啥区别了,但是在驱动中一般使用vb2_plane, 它里面有一个mem_priv就是plane的地址。 不想越扯越远了,继续回到open()函数当中。调用v4l2_m2m_ctx_init()初始化m2m_ctx,一般把自己实现的ctx(如jpeg_ctx)传进这个函数作为private变量。 ctx->m2m_ctx = v4l2_m2m_ctx_init(dev->m2m_dev_dec,ctx,queue_init_dec); /** * 一般被驱动的open()函数调用 */ struct v4l2_m2m_ctx *v4l2_m2m_ctx_init(struct v4l2_m2m_dev *m2m_dev,voID *drv_priv,int (*queue_init)(voID *priv,struct vb2_queue *src_vq,struct vb2_queue *dst_vq)) { ... ret = queue_init(drv_priv,&out_q_ctx->q,&cap_q_ctx->q); ... } 其中queue_init()是回调函数,也是需要驱动去实现,而在queue_init中,一般要对out_q_ctx->q,cap_q_ctx->q两个vb2_queue赋值,其中就包括ops和mem_ops两个函数 *** 作集。 举个例子,在JPEG驱动中的queue_init()为 static int queue_init_enc(voID *priv,struct vb2_queue *dst_vq) { struct jpeg_ctx *ctx = priv; int ret; memset(src_vq,sizeof(*src_vq)); src_vq->type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE; src_vq->io_modes = VB2_MMAP | VB2_USERPTR; src_vq->drv_priv = ctx; src_vq->buf_struct_size = sizeof(struct v4l2_m2m_buffer); src_vq->ops = &jpeg_enc_vb2_qops; /*赋值ops,这是jpeg自己实现的,其实最终也是调用了mem2mem和vb2的函数*/ src_vq->mem_ops = ctx->dev->vb2->ops; /*赋值mem_ops,其实就是vb2_cma_phys_memops或vb2_ion_memops*/ ret = vb2_queue_init(src_vq); if (ret) return ret; memset(dst_vq,sizeof(*dst_vq)); dst_vq->type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; dst_vq->io_modes = VB2_MMAP | VB2_USERPTR; dst_vq->drv_priv = ctx; dst_vq->buf_struct_size = sizeof(struct v4l2_m2m_buffer); dst_vq->ops = &jpeg_enc_vb2_qops; dst_vq->mem_ops = ctx->dev->vb2->ops; return vb2_queue_init(dst_vq); } 我们可以看到,最终都要调用vb2_queue_init()初始化一个队列。到此,open()对mem2mem的 *** 作已经做完。 close()函数就不讲了,肯定是跟open()差不多相反的工作。 poll()函数需要调用v4l2_m2m_poll(),如 static unsigned int jpeg_m2m_poll(struct file *file,struct poll_table_struct *wait) { struct jpeg_ctx *ctx = file->private_data; return v4l2_m2m_poll(file,ctx->m2m_ctx,wait); } mmap()需要调用v4l2_m2m_mmap()函数,如 static int jpeg_m2m_mmap(struct file *file,struct vm_area_struct *vma) { struct jpeg_ctx *ctx = file->private_data; return v4l2_m2m_mmap(file,vma); } ioctl()一般赋值为vIDeo_ioctl2(),实际上就是调用到自己实现的ioctl()函数中。 在所有的ioctl中,一般是在vidioc_reqbufs()调用v4l2_m2m_reqbufs(),如 static int jpeg_dec_m2m_reqbufs(struct file *file,voID *priv,struct v4l2_requestbuffers *reqbufs) { struct jpeg_ctx *ctx = priv; struct vb2_queue *vq; vq = v4l2_m2m_get_vq(ctx->m2m_ctx,reqbufs->type); if (vq->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) ctx->dev->vb2->set_cacheable(ctx->dev->alloc_ctx,ctx->input_cacheable); else if (vq->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) ctx->dev->vb2->set_cacheable(ctx->dev->alloc_ctx,ctx->output_cacheable); return v4l2_m2m_reqbufs(file,reqbufs); } 在vidioc_querybuf()调用v4l2_m2m_querybuf(),如 static int jpeg_dec_m2m_querybuf(struct file *file,struct v4l2_buffer *buf) { struct jpeg_ctx *ctx = priv; return v4l2_m2m_querybuf(file,buf); } vidioc_qbuf()和vidioc_dqbuf(),如 static int jpeg_dec_m2m_qbuf(struct file *file,struct v4l2_buffer *buf) { struct jpeg_ctx *ctx = priv; return v4l2_m2m_qbuf(file,buf); } static int jpeg_dec_m2m_dqbuf(struct file *file,struct v4l2_buffer *buf) { struct jpeg_ctx *ctx = priv; return v4l2_m2m_dqbuf(file,buf); } vidioc_streamon()和vidioc_streamoff(),如 static int jpeg_dec_m2m_streamon(struct file *file,enum v4l2_buf_type type) { struct jpeg_ctx *ctx = priv; return v4l2_m2m_streamon(file,type); } static int jpeg_dec_m2m_streamoff(struct file *file,enum v4l2_buf_type type) { struct jpeg_ctx *ctx = priv; return v4l2_m2m_streamoff(file,type); } 其中调用了v4l2_m2m_streamon()之后,就会调到 v4l2_m2m_ops的device_run()中开启数据流了。在device_run()中可以通过mem2mem的一些函数,取出源内存地址和目的内存地址。设置到相应寄存器中。如举个jpeg例子 static voID jpeg_device_dec_run(voID *priv) { struct jpeg_ctx *ctx = priv; struct jpeg_dev *dev = ctx->dev; struct vb2_buffer *vb = NulL; ... vb = v4l2_m2m_next_src_buf(ctx->m2m_ctx); /*这里取出是out queue,也就是从内存中读出来的,只有一个平面*/ jpegv2_set_stream_buf_address(dev->reg_base,dev->vb2->plane_addr(vb,0)); /*dst是capture queue,可能存在多个平面*/ vb = v4l2_m2m_next_dst_buf(ctx->m2m_ctx); if (dec_param.out_plane == 1) jpegv2_set_frame_buf_address(dev->reg_base,dec_param.out_fmt,0),0); else if (dec_param.out_plane == 2) { jpegv2_set_frame_buf_address(dev->reg_base,1),0); } else if (dec_param.out_plane == 3) jpegv2_set_frame_buf_address(dev->reg_base,2)); /*开始启动数据流*/ jpegv2_set_enc_dec_mode(dev->reg_base,DECoding); ... } 上面说过了,调用了device_run()之后 ,它不会做清理工作,所以在数据流执行完后,要做一些清理工作。如 static irqreturn_t jpeg_irq(int irq,voID *priv) { ... if (ctrl->mode == ENCoding) ctx = v4l2_m2m_get_curr_priv(ctrl->m2m_dev_enc); else ctx = v4l2_m2m_get_curr_priv(ctrl->m2m_dev_dec); ... /*移除两个 *** 作完成的buffer?*/ src_vb = v4l2_m2m_src_buf_remove(ctx->m2m_ctx); dst_vb = v4l2_m2m_dst_buf_remove(ctx->m2m_ctx); ... if (ctrl->irq_ret == OK_ENC_OR_DEC) { v4l2_m2m_buf_done(src_vb,VB2_BUF_STATE_DONE); /*设置buffer的状态?不知道里面真正做什么*/ v4l2_m2m_buf_done(dst_vb,VB2_BUF_STATE_DONE); } else { v4l2_m2m_buf_done(src_vb,VB2_BUF_STATE_ERROR); v4l2_m2m_buf_done(dst_vb,VB2_BUF_STATE_ERROR); } if (ctrl->mode == ENCoding) v4l2_m2m_job_finish(ctrl->m2m_dev_enc,ctx->m2m_ctx); /*最后记住要调v4l2_m2m_job_finish()清理*/ else v4l2_m2m_job_finish(ctrl->m2m_dev_dec,ctx->m2m_ctx); ... } 接下来要研究vIDeobuf2了,这个在驱动中调来调去,调得很乱。唉,内核抽象得还不够啊。 总结
以上是内存溢出为你收集整理的mem2mem解析全部内容,希望文章能够帮你解决mem2mem解析所遇到的程序开发问题。
如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。
评论列表(0条)