我们都知道,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 *src_vq,
struct vb2_queue *dst_vq)
{
struct jpeg_ctx *ctx = priv;
int ret;
memset(src_vq, 0, 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, 0, 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, ctx->m2m_ctx, 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, ctx->m2m_ctx, reqbufs);
}
在vidioc_querybuf()调用v4l2_m2m_querybuf(),如
static int jpeg_dec_m2m_querybuf(struct file *file, void *priv,
struct v4l2_buffer *buf)
{
struct jpeg_ctx *ctx = priv;
return v4l2_m2m_querybuf(file, ctx->m2m_ctx, buf);
}
vidioc_qbuf()和vidioc_dqbuf(),如
static int jpeg_dec_m2m_qbuf(struct file *file, void *priv,
struct v4l2_buffer *buf)
{
struct jpeg_ctx *ctx = priv;
return v4l2_m2m_qbuf(file, ctx->m2m_ctx, buf);
}
static int jpeg_dec_m2m_dqbuf(struct file *file, void *priv,
struct v4l2_buffer *buf)
{
struct jpeg_ctx *ctx = priv;
return v4l2_m2m_dqbuf(file, ctx->m2m_ctx, buf);
}
vidioc_streamon()和vidioc_streamoff(),如
static int jpeg_dec_m2m_streamon(struct file *file, void *priv,
enum v4l2_buf_type type)
{
struct jpeg_ctx *ctx = priv;
return v4l2_m2m_streamon(file, ctx->m2m_ctx, type);
}
static int jpeg_dec_m2m_streamoff(struct file *file, void *priv,
enum v4l2_buf_type type)
{
struct jpeg_ctx *ctx = priv;
return v4l2_m2m_streamoff(file, ctx->m2m_ctx, 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, dev->vb2->plane_addr(vb, 0), 0, 0);
else if (dec_param.out_plane == 2) {
jpegv2_set_frame_buf_address(dev->reg_base,
dec_param.out_fmt, dev->vb2->plane_addr(vb, 0), dev->vb2->plane_addr(vb, 1), 0);
} else if (dec_param.out_plane == 3)
jpegv2_set_frame_buf_address(dev->reg_base,
dec_param.out_fmt, dev->vb2->plane_addr(vb, 0),
dev->vb2->plane_addr(vb, 1), dev->vb2->plane_addr(vb, 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了,这个在驱动中调来调去,调得很乱。唉,内核抽象得还不够啊。
随着多媒体驱动的共性越来越多,为了抽象代码,便于开发和维护,内核现在在多媒体视音频驱动目录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 *src_vq,
struct vb2_queue *dst_vq)
{
struct jpeg_ctx *ctx = priv;
int ret;
memset(src_vq, 0, 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, 0, 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, ctx->m2m_ctx, 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, ctx->m2m_ctx, reqbufs);
}
在vidioc_querybuf()调用v4l2_m2m_querybuf(),如
static int jpeg_dec_m2m_querybuf(struct file *file, void *priv,
struct v4l2_buffer *buf)
{
struct jpeg_ctx *ctx = priv;
return v4l2_m2m_querybuf(file, ctx->m2m_ctx, buf);
}
vidioc_qbuf()和vidioc_dqbuf(),如
static int jpeg_dec_m2m_qbuf(struct file *file, void *priv,
struct v4l2_buffer *buf)
{
struct jpeg_ctx *ctx = priv;
return v4l2_m2m_qbuf(file, ctx->m2m_ctx, buf);
}
static int jpeg_dec_m2m_dqbuf(struct file *file, void *priv,
struct v4l2_buffer *buf)
{
struct jpeg_ctx *ctx = priv;
return v4l2_m2m_dqbuf(file, ctx->m2m_ctx, buf);
}
vidioc_streamon()和vidioc_streamoff(),如
static int jpeg_dec_m2m_streamon(struct file *file, void *priv,
enum v4l2_buf_type type)
{
struct jpeg_ctx *ctx = priv;
return v4l2_m2m_streamon(file, ctx->m2m_ctx, type);
}
static int jpeg_dec_m2m_streamoff(struct file *file, void *priv,
enum v4l2_buf_type type)
{
struct jpeg_ctx *ctx = priv;
return v4l2_m2m_streamoff(file, ctx->m2m_ctx, 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, dev->vb2->plane_addr(vb, 0), 0, 0);
else if (dec_param.out_plane == 2) {
jpegv2_set_frame_buf_address(dev->reg_base,
dec_param.out_fmt, dev->vb2->plane_addr(vb, 0), dev->vb2->plane_addr(vb, 1), 0);
} else if (dec_param.out_plane == 3)
jpegv2_set_frame_buf_address(dev->reg_base,
dec_param.out_fmt, dev->vb2->plane_addr(vb, 0),
dev->vb2->plane_addr(vb, 1), dev->vb2->plane_addr(vb, 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了,这个在驱动中调来调去,调得很乱。唉,内核抽象得还不够啊。
本文详细介绍了V4L2多媒体驱动框架中的mem2mem内存管理模块,包括其在v4l2、subdev、mem2mem和videobuf2中的作用。重点解析了mem2mem中的v4l2_m2m_dev结构和v4l2_m2m_ops回调函数,以及在open、close、poll、mmap和ioctl等函数中的应用。通过对mem2mem的深入理解,有助于更好地开发和维护多媒体驱动。
1082

被折叠的 条评论
为什么被折叠?



