v4l2接收流程

内核media驱动目录结构

目录media/driver,子目录说明如下,主要列举本文中使用到的目录

目录功能
I2C摄像头,解串器(max9296/9295等)
platform控制器的驱动,例如mipi控制等
v4l2_coreioctl 入口等
media\common\videobuf2\通用buffer分配及管理等

    通过此目录结构,我们可以得知: 摄像头的控制路径很多是通过I2C通道。

用户态编程的几个关键IOCTL

VIDIOC_REQBUFS

功能: 向控制器驱动申请接收视频流的buffer个数。如下代码总共申请5个buffer,也就是最多缓存5帧数据。

req.count = 5;
	req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
	req.memory = V4L2_MEMORY_MMAP;
	if (ioctl(fd, VIDIOC_REQBUFS, &req) < 0) {
		printf("Reqbufs fail\n");
		goto err1;
	}

VIDIOC_QUERYBUF

功能: 获取驱动分配的buffer信息,以便用户态进行mmap映射

	for(i = 0; i < req.count; i++) {
		memset(&buf, 0, sizeof(buf));
		planes_buffer = calloc(num_planes, sizeof(*planes_buffer));
		plane_start = calloc(num_planes, sizeof(*plane_start));
		memset(planes_buffer, 0, sizeof(*planes_buffer));
		buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
		buf.memory = V4L2_MEMORY_MMAP;
		buf.m.planes = planes_buffer;
		buf.length = num_planes;
		buf.index = i;
		if (-1 == ioctl (fd, VIDIOC_QUERYBUF, &buf)) {
			printf("Querybuf fail\n");
			req.count = i;
			goto err2;
		}

VIDIOC_DQBUF

功能: 等待有视频数据的buf。可以是阻塞与非阻塞,后续在分析驱动时,着重介绍。

	memset(&buf, 0, sizeof(buf));
		buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
		buf.memory = V4L2_MEMORY_MMAP;
		buf.m.planes = tmp_plane;
		buf.length = num_planes;
		if (ioctl (fd, VIDIOC_DQBUF, &buf) < 0)
			printf("dqbuf fail\n");

VIDIOC_QBUF

功能:虽然如下两个功能最终操作一样,但应用的时间点不一样。

1) 在接收完数据后,用户态应用将buf还给驱动,进而驱动可以继续往buffer里面填数据

if (ioctl (fd, VIDIOC_QBUF, &buf) < 0)
			printf("failture VIDIOC_QBUF\n");

2) 在初始化时,将申请到的buffer挂载到驱动的队列上

for (i = 0; i < req.count; ++i) {
		memset(&buf, 0, sizeof(buf));

		buf.type        = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
		buf.memory      = V4L2_MEMORY_MMAP;
		buf.length   	= num_planes;
		buf.index       = i;
		buf.m.planes 	= (buffers + i)->planes_buffer;

		if (ioctl (fd, VIDIOC_QBUF, &buf) < 0)
			printf ("VIDIOC_QBUF failed\n");
	}

对应代码入口

\kernel\drivers\media\v4l2-core\v4l2-ioctl.c

static const struct v4l2_ioctl_info v4l2_ioctls[] = {
	IOCTL_INFO(VIDIOC_QUERYCAP, v4l_querycap, v4l_print_querycap, 0),
	IOCTL_INFO(VIDIOC_ENUM_FMT, v4l_enum_fmt, v4l_print_fmtdesc, 0),
	IOCTL_INFO(VIDIOC_G_FMT, v4l_g_fmt, v4l_print_format, 0),
	IOCTL_INFO(VIDIOC_S_FMT, v4l_s_fmt, v4l_print_format, INFO_FL_PRIO),
	IOCTL_INFO(VIDIOC_REQBUFS, v4l_reqbufs, v4l_print_requestbuffers, INFO_FL_PRIO | INFO_FL_QUEUE),
	IOCTL_INFO(VIDIOC_QUERYBUF, v4l_querybuf, v4l_print_buffer, INFO_FL_QUEUE | INFO_FL_CLEAR(v4l2_buffer, length)),
	IOCTL_INFO(VIDIOC_G_FBUF, v4l_stub_g_fbuf, v4l_print_framebuffer, 0),
	IOCTL_INFO(VIDIOC_S_FBUF, v4l_stub_s_fbuf, v4l_print_framebuffer, INFO_FL_PRIO),
	IOCTL_INFO(VIDIOC_OVERLAY, v4l_overlay, v4l_print_u32, INFO_FL_PRIO),
	IOCTL_INFO(VIDIOC_QBUF, v4l_qbuf, v4l_print_buffer, INFO_FL_QUEUE),

总体而言:由于dma操作对物理地址的要求,buffer的分配由内核驱动完成。进而映射到用户空间,用户空间仅仅负责将数据取出,buffer的管理都有内核统一完成。

因而在内核中抽象出对buffer的统一管理。

buffer的管理

首先,从用户态入手,了解几个关键的数据结构。

struct v4l2_requestbuffers 

req.count = 5;
	req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
	req.memory = V4L2_MEMORY_MMAP;

count: 即申请5个buffer

type: MPLANE,V4L2_BUF_TYPE_VIDEO_CAPTURE 等,影响buffer的分布。

struct v4l2_buffer 

	for(i = 0; i < req.count; i++) {
		memset(&buf, 0, sizeof(buf));
		planes_buffer = calloc(num_planes, sizeof(*planes_buffer));
		plane_start = calloc(num_planes, sizeof(*plane_start));
		memset(planes_buffer, 0, sizeof(*planes_buffer));
		buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
		buf.memory = V4L2_MEMORY_MMAP;
		buf.m.planes = planes_buffer;
		buf.length = num_planes;
		buf.index = i;
		if (-1 == ioctl (fd, VIDIOC_QUERYBUF, &buf)) {
			printf("Querybuf fail\n");
			req.count = i;
			goto err2;
		}

mplane之宏定义

  在让驱动分配内存,已经应用向驱动获取内存信息时,都用到一个字段,即type:

   此字段的取值包括: 

    \kernel\include\uapi\linux\videodev2.h

V4L2_BUF_TYPE_VIDEO_CAPTURE
V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE

const char *v4l2_type_names[] = {
	[0]				   = "0",
	[V4L2_BUF_TYPE_VIDEO_CAPTURE]      = "vid-cap",
	[V4L2_BUF_TYPE_VIDEO_OVERLAY]      = "vid-overlay",
	[V4L2_BUF_TYPE_VIDEO_OUTPUT]       = "vid-out",
	[V4L2_BUF_TYPE_VBI_CAPTURE]        = "vbi-cap",
	[V4L2_BUF_TYPE_VBI_OUTPUT]         = "vbi-out",
	[V4L2_BUF_TYPE_SLICED_VBI_CAPTURE] = "sliced-vbi-cap",
	[V4L2_BUF_TYPE_SLICED_VBI_OUTPUT]  = "sliced-vbi-out",
	[V4L2_BUF_TYPE_VIDEO_OUTPUT_OVERLAY] = "vid-out-overlay",
	[V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE] = "vid-cap-mplane",
	[V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE] = "vid-out-mplane",
	[V4L2_BUF_TYPE_SDR_CAPTURE]        = "sdr-cap",
	[V4L2_BUF_TYPE_SDR_OUTPUT]         = "sdr-out",
	[V4L2_BUF_TYPE_META_CAPTURE]       = "meta-cap",
	[V4L2_BUF_TYPE_META_OUTPUT]	   = "meta-out",
};

此处我们关注两个video_capture

mplane之定义

1)  何为mplane?

  所谓plane,即为一个分量所占的内存空间。mplane即多个plane表示一帧图像。如下图Y U V分布占用一个plane。

  

如下,Y与UV分别占用一个plane 

常见的NV12 NV16等都属于此范畴。

2) 与之相对应的  Interleaved,即一块内存即为完整的一帧图像

例如: YUYV ;YVYU等采用此方式。

了解了上述结构后,不同的存储形式对内存分配的要求不同。如果采用plane方式,则可能需要分配2-3块内存。而如果采用interlaced 方式,则只分配一块内存即可。

内存分配

内存分配入口

\kernel\drivers\media\v4l2-core\v4l2-ioctl.c

static int v4l_reqbufs(const struct v4l2_ioctl_ops *ops,
				struct file *file, void *fh, void *arg)
{
	struct v4l2_requestbuffers *p = arg;
	int ret = check_fmt(file, p->type);

	if (ret)
		return ret;

	CLEAR_AFTER_FIELD(p, capabilities);

	return ops->vidioc_reqbufs(file, fh, p);
}

驱动层接口

kernel\drivers\media\platform\rockchip\cif\capture.c

驱动层最终调用了buff的通用管理接口

static const struct v4l2_ioctl_ops rkcif_v4l2_ioctl_ops = {
	.vidioc_reqbufs = vb2_ioctl_reqbufs,
	.vidioc_querybuf = vb2_ioctl_querybuf,
	.vidioc_create_bufs = vb2_ioctl_create_bufs,
	.vidioc_qbuf = vb2_ioctl_qbuf,
	.vidioc_expbuf = vb2_ioctl_expbuf,
	.vidioc_dqbuf = vb2_ioctl_dqbuf,
	.vidioc_prepare_buf = vb2_ioctl_prepare_buf,
	.vidioc_streamon = vb2_ioctl_streamon,
	.vidioc_streamoff = vb2_ioctl_streamoff,
	.vidioc_enum_input = rkcif_enum_input,
	.vidioc_try_fmt_vid_cap_mplane = rkcif_try_fmt_vid_cap_mplane,
	.vidioc_enum_fmt_vid_cap = rkcif_enum_fmt_vid_cap_mplane,
	.vidioc_s_fmt_vid_cap_mplane = rkcif_s_fmt_vid_cap_mplane,
	.vidioc_g_fmt_vid_cap_mplane = rkcif_g_fmt_vid_cap_mplane,
	.vidioc_querycap = rkcif_querycap,
	.vidioc_s_selection = rkcif_s_selection,
	.vidioc_g_selection = rkcif_g_selection,
	.vidioc_enum_frameintervals = rkcif_enum_frameintervals,
	.vidioc_enum_framesizes = rkcif_enum_framesizes,
	.vidioc_default = rkcif_ioctl_default,
};

通用分配接口

两套入口

kernel\drivers\media\v4l2-core\videobuf-core.c

int videobuf_reqbufs(struct videobuf_queue *q,
		 struct v4l2_requestbuffers *req)
{
  q->ops->buf_setup(q, &count, &size);
  .......
  ......
  retval = __videobuf_mmap_setup(q, count, size, req->memory);

}

 第二套为我们所关心

 \kernel\drivers\media\common\videobuf2\videobuf2-v4l2.c  

首先 查询,然后分配

int vb2_ioctl_reqbufs(struct file *file, void *priv,
			  struct v4l2_requestbuffers *p)
{

int vb2_core_reqbufs(struct vb2_queue *q, enum vb2_memory memory,
		     unsigned int *count)
{

   /*
	 * Ask the driver how many buffers and planes per buffer it requires.
	 * Driver also sets the size and allocator context for each plane.
     *  获取驱动buffer的数目以及每个bufffer里面plane的数目
	 */
	ret = call_qop(q, queue_setup, q, &num_buffers, &num_planes,
		       plane_sizes, q->alloc_devs);


}

 驱动层提供plane信息

 在通用buffer代码分配内存前,需要了解分配几个内存区域。而这由控制器驱动具体提供。

以 3588 为例:

static struct vb2_ops rkcif_vb2_ops = {
	.queue_setup = rkcif_queue_setup,
	.buf_queue = rkcif_buf_queue,
	.wait_prepare = vb2_ops_wait_prepare,
	.wait_finish = vb2_ops_wait_finish,
	.stop_streaming = rkcif_stop_streaming,
	.start_streaming = rkcif_start_streaming,
};

q->ops = &rkcif_vb2_ops;

//输出mplanes数目,而mplanes数目
static int rkcif_queue_setup(struct vb2_queue *queue,
			     unsigned int *num_buffers,
			     unsigned int *num_planes,
			     unsigned int sizes[],
			     struct device *alloc_ctxs[])
{
	struct rkcif_stream *stream = queue->drv_priv;
	struct rkcif_extend_info *extend_line = &stream->extend_line;
	struct rkcif_device *dev = stream->cifdev;
	const struct v4l2_pix_format_mplane *pixm = NULL;
	const struct cif_output_fmt *cif_fmt;
	const struct cif_input_fmt *in_fmt;
	bool is_extended = false;
	u32 i, height;

	pixm = &stream->pixm;
	cif_fmt = stream->cif_fmt_out;
	in_fmt = stream->cif_fmt_in;

	*num_planes = cif_fmt->mplanes;

//具体说明根据类型不同而定,例如
static const struct cif_output_fmt out_fmts[] = {
	{
		.fourcc = V4L2_PIX_FMT_NV16,
		.cplanes = 2,
		.mplanes = 1,
		.fmt_val = YUV_OUTPUT_422 | UV_STORAGE_ORDER_UVUV,
		.bpp = { 8, 16 },
		.csi_fmt_val = CSI_WRDDR_TYPE_YUV422,
		.fmt_type = CIF_FMT_TYPE_YUV,
	},

核心分配buffer接口

nel\drivers\media\common\videobuf2\videobuf2-core.c


/*
 * __vb2_queue_alloc() - allocate videobuf buffer structures and (for MMAP type)
 * video buffer memory for all buffers/planes on the queue and initializes the
 * queue
 *
 * Returns the number of buffers successfully allocated.
 */
static int __vb2_queue_alloc(struct vb2_queue *q, enum vb2_memory memory,
			     unsigned int num_buffers, unsigned int num_planes,
			     const unsigned plane_sizes[VB2_MAX_PLANES])
{
	unsigned int buffer, plane;
	struct vb2_buffer *vb;
	int ret;

	/* Ensure that q->num_buffers+num_buffers is below VB2_MAX_FRAME */
	num_buffers = min_t(unsigned int, num_buffers,
			    VB2_MAX_FRAME - q->num_buffers);

	for (buffer = 0; buffer < num_buffers; ++buffer) {
		/* Allocate videobuf buffer structures */
		vb = kzalloc(q->buf_struct_size, GFP_KERNEL);
		if (!vb) {
			dprintk(q, 1, "memory alloc for buffer struct failed\n");
			break;
		}

		vb->state = VB2_BUF_STATE_DEQUEUED;
		vb->vb2_queue = q;
		vb->num_planes = num_planes;
		vb->index = q->num_buffers + buffer;
		vb->type = q->type;
		vb->memory = memory;
		/*
		 * We need to set these flags here so that the videobuf2 core
		 * will call ->prepare()/->finish() cache sync/flush on vb2
		 * buffers when appropriate. However, we can avoid explicit
		 * ->prepare() and ->finish() cache sync for DMABUF buffers,
		 * because DMA exporter takes care of it.
		 */
		if (q->memory != VB2_MEMORY_DMABUF) {
			vb->need_cache_sync_on_prepare = 1;
			vb->need_cache_sync_on_finish = 1;
		}
		for (plane = 0; plane < num_planes; ++plane) {
			vb->planes[plane].length = plane_sizes[plane];
			vb->planes[plane].min_length = plane_sizes[plane];
		}
		call_void_bufop(q, init_buffer, vb);

		q->bufs[vb->index] = vb;

		/* Allocate video buffer memory for the MMAP type */
        //  这里即采用我们的分配接口
		if (memory == VB2_MEMORY_MMAP) {
			ret = __vb2_buf_mem_alloc(vb);
			if (ret) {
				dprintk(q, 1, "failed allocating memory for buffer %d\n",
					buffer);
				q->bufs[vb->index] = NULL;
				kfree(vb);
				break;
			}
			__setup_offsets(vb);
			/*
			 * Call the driver-provided buffer initialization
			 * callback, if given. An error in initialization
			 * results in queue setup failure.
			 */
			ret = call_vb_qop(vb, buf_init, vb);
			if (ret) {
				dprintk(q, 1, "buffer %d %p initialization failed\n",
					buffer, vb);
				__vb2_buf_mem_free(vb);
				q->bufs[vb->index] = NULL;
				kfree(vb);
				break;
			}
		}
	}

	dprintk(q, 3, "allocated %d buffers, %d plane(s) each\n",
		buffer, num_planes);

	return buffer;
}


/*
 * __vb2_buf_mem_alloc() - allocate video memory for the given buffer
 */
static int __vb2_buf_mem_alloc(struct vb2_buffer *vb)
{
	struct vb2_queue *q = vb->vb2_queue;
	void *mem_priv;
	int plane;
	int ret = -ENOMEM;

	/*
	 * Allocate memory for all planes in this buffer
	 * NOTE: mmapped areas should be page aligned
	 */
	for (plane = 0; plane < vb->num_planes; ++plane) {
		/* Memops alloc requires size to be page aligned. */
		unsigned long size = PAGE_ALIGN(vb->planes[plane].length);

		/* Did it wrap around? */
		if (size < vb->planes[plane].length)
			goto free;
        // 此处调用实际的内存分配接口
		mem_priv = call_ptr_memop(vb, alloc,
				q->alloc_devs[plane] ? : q->dev,
				q->dma_attrs, size, q->dma_dir, q->gfp_flags);
		if (IS_ERR_OR_NULL(mem_priv)) {
			if (mem_priv)
				ret = PTR_ERR(mem_priv);
			goto free;
		}

		/* Associate allocator private data with this plane */
		vb->planes[plane].mem_priv = mem_priv;
	}

	return 0;
free:
	/* Free already allocated memory if one of the allocations failed */
	for (; plane > 0; --plane) {
		call_void_memop(vb, put, vb->planes[plane - 1].mem_priv);
		vb->planes[plane - 1].mem_priv = NULL;
	}

	return ret;
}

驱动提供的内存分配接口

kernel\drivers\media\platform\rockchip\cif\capture.c

\kernel\drivers\media\platform\rockchip\cif\hw.c

static int rkcif_init_vb2_queue(struct vb2_queue *q,
				struct rkcif_stream *stream,
				enum v4l2_buf_type buf_type)
{
	struct rkcif_hw *hw_dev = stream->cifdev->hw_dev;

	q->type = buf_type;
	q->io_modes = VB2_MMAP | VB2_DMABUF;
	q->drv_priv = stream;
	q->ops = &rkcif_vb2_ops;
	q->mem_ops = hw_dev->mem_ops;

//驱动探测时,赋值mem_ops
static int rkcif_plat_hw_probe(struct platform_device *pdev)
{
	
	struct rkcif_hw *cif_hw;
	
	cif_hw->mem_ops = &vb2_cma_sg_memops;
    ............................
}

真正内存分配接口

\kernel\drivers\media\common\videobuf2\videobuf2-cma-sg.c

const struct vb2_mem_ops vb2_cma_sg_memops = {
	.alloc		= vb2_cma_sg_alloc,
	.put		= vb2_cma_sg_put,
	.get_userptr	= vb2_cma_sg_get_userptr,
	.put_userptr	= vb2_cma_sg_put_userptr,
	.prepare	= vb2_cma_sg_prepare,
	.finish		= vb2_cma_sg_finish,
	.vaddr		= vb2_cma_sg_vaddr,
	.mmap		= vb2_cma_sg_mmap,
	.num_users	= vb2_cma_sg_num_users,
	.get_dmabuf	= vb2_cma_sg_get_dmabuf,
	.map_dmabuf	= vb2_cma_sg_map_dmabuf,
	.unmap_dmabuf	= vb2_cma_sg_unmap_dmabuf,
	.attach_dmabuf	= vb2_cma_sg_attach_dmabuf,
	.detach_dmabuf	= vb2_cma_sg_detach_dmabuf,
	.cookie		= vb2_cma_sg_cookie,
};

 接口流程综述

    经过了core与具体设备的交替,其中驱动向core层注册的接口包括:

    1) 采用的buffer分配入口注册

    2)  buffer信息的注册。例如控制器的plane信息等

    3)     实际的分配内存接口注册。 

mmap映射

  针对RK的芯片,其存在mplane与cplane,两者之间的关系是怎么样的?这里查询到的plane_num是多少?

	for(i = 0; i < req.count; i++) {
			memset(&buf, 0, sizeof(buf));
			planes_buffer = calloc(num_planes, sizeof(*planes_buffer));
			plane_start = calloc(num_planes, sizeof(*plane_start));
			memset(planes_buffer, 0, sizeof(*planes_buffer));
			buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
			buf.memory = V4L2_MEMORY_MMAP;
			buf.m.planes = planes_buffer;  //记录每个plane的长度,偏移量信息
			buf.length = num_planes;
			buf.index = i;
			if (-1 == ioctl (fd, VIDIOC_QUERYBUF, &buf)) {
				printf("Querybuf fail\n");
				req.count = i;
				goto err2;
			}
	
			(buffers + i)->planes_buffer = planes_buffer;
			(buffers + i)->plane_start = plane_start;
			for(j = 0; j < num_planes; j++) { //针对每个plane映射
				printf("plane[%d]: length = %d\n", j, (planes_buffer + j)->length);
				printf("plane[%d]: offset = %d\n", j, (planes_buffer + j)->m.mem_offset);
				(plane_start + j)->start = mmap (NULL /* start anywhere */,
										(planes_buffer + j)->length,
										PROT_READ | PROT_WRITE /* required */,
										MAP_SHARED /* recommended */,
										fd,
										(planes_buffer + j)->m.mem_offset);
				if (MAP_FAILED == (plane_start +j)->start) {
					printf ("mmap failed\n");
					req.count = i;
					goto unmmap;
				}
			}

sun4i-csi驱动

  在 明了内存分配后,通过一个驱动示例,了解其接收数据的流程。

 问题: 数据到来后,通过中断通知,用户通过ioctl dqbuf接口调用时,是否一直阻塞中?

 该驱动结构及流程相对简单,但采用了V4L2的主要接口,利于分析。涉及到的文件

media\platform\sunxi\sun4i-csi

中断入口

  请思考,此处csi->qlock保护的是什么? 

static irqreturn_t sun4i_csi_irq(int irq, void *data)
{
	struct sun4i_csi *csi = data;
	u32 reg;

	reg = readl(csi->regs + CSI_INT_STA_REG);

	/* Acknowledge the interrupts */
	writel(reg, csi->regs + CSI_INT_STA_REG);

	if (!(reg & CSI_INT_FRM_DONE))
		return IRQ_HANDLED;

	spin_lock(&csi->qlock); //这里加锁的目的是什么?保护什么?
	if (sun4i_csi_buffer_flip(csi, csi->sequence++)) {
		dev_warn(csi->dev, "%s: Flip failed\n", __func__);
		sun4i_csi_capture_stop(csi);
	}
	spin_unlock(&csi->qlock);

	return IRQ_HANDLED;
}

static int sun4i_csi_buffer_flip(struct sun4i_csi *csi, unsigned int sequence)
{
	u32 reg = readl(csi->regs + CSI_BUF_CTRL_REG);
	unsigned int next;

	/* Our next buffer is not the current buffer */
	next = !(reg & CSI_BUF_CTRL_DBS);

	/* Report the previous buffer as done */
    //此处调用V4L2的接口,将带有视频帧的buffer通知用户
	sun4i_csi_buffer_mark_done(csi, next, sequence);
    //然后将新的buffer的地址 填写到CSI控制器中,这样控制器就可以采用新的内存
	/* Put a new buffer in there */
	return sun4i_csi_buffer_fill_slot(csi, next);
}

     

  buffer done

    此接口主要完成两个事情:

    1) 将buffer添加到 done_list 队列

     2) 唤醒等候在此工作队列的进程

void vb2_buffer_done(struct vb2_buffer *vb, enum vb2_buffer_state state)
{
    struct vb2_queue *q = vb->vb2_queue;
    /* Add the buffer to the done buffers list */
	spin_lock_irqsave(&q->done_lock, flags);	//锁控制对done链表
    list_add_tail(&vb->done_entry, &q->done_list);
    spin_unlock_irqrestore(&q->done_lock, flags);
    wake_up(&q->done_wq);
}

buffer 的申请

 在一次buffer提交用户后,需要从驱动里面再申请一个buffer填写到控制器的相关寄存器。


static int sun4i_csi_buffer_fill_slot(struct sun4i_csi *csi, unsigned int slot)
{
	struct sun4i_csi_buffer *c_buf;
	struct vb2_v4l2_buffer *v_buf;
	unsigned int plane;

	/*
	 * We should never end up in a situation where we overwrite an
	 * already filled slot.
	 */
	if (WARN_ON(csi->current_buf[slot]))
		return -EINVAL;
    //如下buffer  list即为中断的锁所保护的对象。也就是锁加在此处即可。
    
	if (list_empty(&csi->buf_list))
		return sun4i_csi_setup_scratch_buffer(csi, slot);

	c_buf = list_first_entry(&csi->buf_list, struct sun4i_csi_buffer, list);
	list_del_init(&c_buf->list);

	v_buf = &c_buf->vb;
	csi->current_buf[slot] = v_buf;

	for (plane = 0; plane < csi->fmt.num_planes; plane++) {
		dma_addr_t buf_addr;

		buf_addr = vb2_dma_contig_plane_dma_addr(&v_buf->vb2_buf,
							 plane);
		writel(buf_addr, csi->regs + CSI_BUF_ADDR_REG(plane, slot));
	}

	return 0;
}

dqbuf的实现

   流程走到此处已经需要查看被阻塞进程对收到的buffer的处理了。

   根据上一章 《内存分配》的代码流程,很容易得知dqbuf的代码实现。

\kernel\drivers\media\common\videobuf2\videobuf2-v4l2.c

int vb2_core_dqbuf(struct vb2_queue *q, unsigned int *pindex, void *pb,
		   bool nonblocking)
{
	struct vb2_buffer *vb = NULL;
	int ret;
    
	ret = __vb2_get_done_vb(q, &vb, pb, nonblocking);

    。。。。。。。。。。。。。。。。。
    。。。。。。。。。。。。。。。。。。
    call_void_vb_qop(vb, buf_finish, vb);

}



/*
 * __vb2_get_done_vb() - get a buffer ready for dequeuing
 *
 * Will sleep if required for nonblocking == false.
 */
static int __vb2_get_done_vb(struct vb2_queue *q, struct vb2_buffer **vb,
			     void *pb, int nonblocking)
{
	unsigned long flags;
	int ret = 0;

	/*
	 * Wait for at least one buffer to become available on the done_list.
	 */
     // 这个接口中的函数与中断处理中的wake up工作队列相对应,注意有nonblocking参数,
     // 也就是说支持nonblocking的操作
	ret = __vb2_wait_for_done_vb(q, nonblocking);
	if (ret)
		return ret;

	/*
	 * Driver's lock has been held since we last verified that done_list
	 * is not empty, so no need for another list_empty(done_list) check.
	 */
	spin_lock_irqsave(&q->done_lock, flags); //这里再次对done_lock进行了操作,此处即
    //和 中断处理中,buffer的添加对应了。这里为删除。
	*vb = list_first_entry(&q->done_list, struct vb2_buffer, done_entry);
	/*
	 * Only remove the buffer from done_list if all planes can be
	 * handled. Some cases such as V4L2 file I/O and DVB have pb
	 * == NULL; skip the check then as there's nothing to verify.
	 */
	if (pb)
		ret = call_bufop(q, verify_planes_array, *vb, pb);
	if (!ret)
		list_del(&(*vb)->done_entry);
	spin_unlock_irqrestore(&q->done_lock, flags);

	return ret;
}

//等待done_list  非空

/*
 * __vb2_wait_for_done_vb() - wait for a buffer to become available
 * for dequeuing
 *
 * Will sleep if required for nonblocking == false.
 */
static int __vb2_wait_for_done_vb(struct vb2_queue *q, int nonblocking)
{
	/*
	 * All operations on vb_done_list are performed under done_lock
	 * spinlock protection. However, buffers may be removed from
	 * it and returned to userspace only while holding both driver's
	 * lock and the done_lock spinlock. Thus we can be sure that as
	 * long as we hold the driver's lock, the list will remain not
	 * empty if list_empty() check succeeds.
	 */

	for (;;) { //死循环
        
		int ret;

		if (q->waiting_in_dqbuf) {
			dprintk(q, 1, "another dup()ped fd is waiting for a buffer\n");
			return -EBUSY;
		}

		if (!q->streaming) {
			dprintk(q, 1, "streaming off, will not wait for buffers\n");
			return -EINVAL;
		}

		if (q->error) {
			dprintk(q, 1, "Queue in error state, will not wait for buffers\n");
			return -EIO;
		}

		if (q->last_buffer_dequeued) {
			dprintk(q, 3, "last buffer dequeued already, will not wait for buffers\n");
			return -EPIPE;
		}

		if (!list_empty(&q->done_list)) { 
			/*
			 * Found a buffer that we were waiting for.
			 */
			break;
		}

		if (nonblocking) {
			dprintk(q, 3, "nonblocking and no buffers to dequeue, will not wait\n");
			return -EAGAIN;
		}

		q->waiting_in_dqbuf = 1;
		/*
		 * We are streaming and blocking, wait for another buffer to
		 * become ready or for streamoff. Driver's lock is released to
		 * allow streamoff or qbuf to be called while waiting.
		 */
		call_void_qop(q, wait_prepare, q); //休眠前释放锁vb2_queue->lock
          //为了保证qbuf接口和stream off接口被调用

		/*
		 * All locks have been released, it is safe to sleep now.
		 */
		dprintk(q, 3, "will sleep waiting for buffers\n");
        //将休眠在done_wq的队列上,和中断相对应
		ret = wait_event_interruptible(q->done_wq,
				!list_empty(&q->done_list) || !q->streaming ||
				q->error);

		/*
		 * We need to reevaluate both conditions again after reacquiring
		 * the locks or return an error if one occurred.
		 */
		call_void_qop(q, wait_finish, q);
		q->waiting_in_dqbuf = 0;
		if (ret) {
			dprintk(q, 1, "sleep was interrupted\n");
			return ret;
		}
	}
	return 0;
}

   至此,带有一帧图像的buffer 被传递到用户APP中。

 qbuf的实现

int vb2_core_qbuf(struct vb2_queue *q, unsigned int index, void *pb,
		  struct media_request *req)
{

     /*
	 * Add to the queued buffers list, a buffer will stay on it until
	 * dequeued in dqbuf.
	 */
     注意: 这里对queued_list的操作没有锁。那么buffer从list里面删除的时候和这个add的过程不冲突
     ,这个过程又在哪里呢?
	orig_state = vb->state;
	list_add_tail(&vb->queued_entry, &q->queued_list);
	q->queued_count++;
	q->waiting_for_buffers = false;
	vb->state = VB2_BUF_STATE_QUEUED;

    /*
	 * If already streaming, give the buffer to driver for processing.
	 * If not, the buffer will be given to driver on next streamon.
	 */
    这里将buffer 由具体的驱动管理。为何?
	if (q->start_streaming_called)
		__enqueue_in_driver(vb);


}

 控制器驱动回收buffer

static void sun4i_csi_buffer_queue(struct vb2_buffer *vb)
{
	struct sun4i_csi *csi = vb2_get_drv_priv(vb->vb2_queue);
	struct sun4i_csi_buffer *buf = vb2_to_csi_buffer(vb);
	unsigned long flags;
    
    //此处由用户空间操作,进行buffer归还驱动,相关链表加锁
	spin_lock_irqsave(&csi->qlock, flags);  
	list_add_tail(&buf->list, &csi->buf_list);
	spin_unlock_irqrestore(&csi->qlock, flags);
}

  至此,完成了视频数据接收过程。

总结

整个视频流接收过程中,涉及到buffer的管理,都是从具体驱动进行分配而来的。

1) 从驱动获得buffer,填写到控制器中,并将其从空闲list删除;

2) 收到数据后,此buffer被添加到 buffer done list。

3) 视频数据由用户处理完毕后,再添加到空闲list。

其中在空闲buffer的获取和放回时,都涉及到具体的控制器驱动,控制器驱动要实现buf_queue,由qbuf  ioctl系统调用。而done buffer的管理则由v4l2 core部分就可以完成。

相应的增加两个锁。

 

    

后续部分: 

camera驱动与platform驱动

camera驱动与platform调用及加载关系。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/232205.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

个人博客网站如何实现https重定向(301)到http

对于个人网站站注册比较少的&#xff0c;服务器配置不是很好的&#xff0c;没必要https,https跳转到http是要时间的&#xff0c;会影响网站打开的速度。免费的https每年都要更换。个人博客网站https有一段时间了&#xff0c;而且很多页面都有收录排名&#xff0c;现在已去掉htt…

Qt生成动态链接库并使用动态链接库

项目结构 整个工程由一个主程序构成和一个模块构成(dll)。整个工程的结构目录如下 Define.priMyProject.proMyProject.pro.user ---bin ---MainProgrammain.cppMainProgram.proMainProgram.pro.userwidget.cppwidget.hwidget.ui ---MathDllMathDll.proMathDll.pro.userMyMath.…

语音神经科学-01.The cortical organization of speech processing

The cortical organization of speech processing: Feedback control and predictive coding the context of a dual-stream model&#xff08;在双流模型的背景下&#xff0c;语音处理的皮层组织涉及到反馈控制和预测编码。&#xff09; 想了解双流模型&#xff0c;可以参考&…

Android 从assets读取文件装载成Bitmap,Kotlin

Android 从assets读取文件装载成Bitmap&#xff0c;Kotlin /*** fileName assets里面图片文件名*/fun readBitmapFromAssets(ctx: Context, fileName: String): Bitmap? {val assetManager: AssetManager ctx.assetsvar bitmap: Bitmap? nulltry {val inputStream assetMa…

对String类的操作 (超细节+演示)

[本节目标] 1.认识String类 2.了解String类的基本用法 3.熟练掌握String类的常见操作 4.认识字符串常量池 5.认识StringBuffer和StringBuilder 1.String类的重要性 在C语言中已经涉及到字符串了&#xff0c;但是在C语言中要表示字符串只能使用字符数组或者字符指针&…

redis主从复制【面试必看】

在分布式系统中&#xff0c;希望使用多个服务器来部署redis&#xff0c;存在以下几种redis的部署方式 主从模式主从哨兵集群模式 主从模式 在若干个redis节点中&#xff0c;有的是主节点&#xff0c;有的是从节点 假设有三个物理服务器&#xff08;称为是三个节点&#xff…

1-4、JDK目录结构

语雀原文链接 文章目录 1、目录结构2、JDK中rt.jar、tools.jar和dt.jar作用3、bin目录部分说明&#xff08;基本工具&#xff09; 1、目录结构 bin目录&#xff1a;包含一些用于开发Java程序的工具&#xff0c;例如&#xff1a;编译工具(javac.exe)、运行工具 (java.exe) 、打…

【论文阅读笔记】序列数据的数据增强方法综述

【论文阅读笔记】序列数据的数据增强方法综述 摘要 这篇论文探讨了在深度学习模型中由于对精度的要求不断提高导致模型框架结构变得更加复杂和深层的趋势。随着模型参数量的增加&#xff0c;训练模型需要更多的数据&#xff0c;但人工标注数据的成本高昂&#xff0c;且由于客观…

kettle完成mysql表与表之间的更新和插入

版本&#xff1a;20231209 kettle完成数据库表与表之间的转换非常的简单&#xff0c;只需要在输入模块选择&#xff1a;输入表&#xff1b;在输出模块选择&#xff1a;插入和更新表模块 实例展示&#xff1a;将表stu1的数据同步到stu2&#xff0c;并覆盖掉stu2原本的数据。 cr…

Ubuntu 18.04使用Qemu和GDB搭建运行内核的环境

安装busybox 参考博客&#xff1a; 使用GDBQEMU调试Linux内核环境搭建 一文教你如何使用GDBQemu调试Linux内核 ubuntu22.04搭建qemu环境测试内核 交叉编译busybox 编译busybox出现Library m is needed, can’t exclude it (yet)的解释 S3C2440 制作最新busybox文件系统 https:…

【Com通信】Com模块详细介绍

目录 前言 1. Com模块功能介绍 2.关键概念理解 3.功能详细设计 3.1 Introduction 3.2 General Functionality 3.2.1 AUTOSAR COM basis 3.2.2 Signal Values 3.2.3 Endianness Conversion and Sign Extension 3.2.4 Filtering 3.2.5 Signal Gateway 3.3 Normal Ope…

Bean的作用域和生命周期

1. Bean的作用域 前引例子4 现在有一个公共的Bean对象&#xff0c;提供给A用户和B用户使用&#xff0c;然而在使用的时候A用户将Bean对象的数据进行修改&#xff0c;此时B得到的Bean对象是否是原来的Bean对象呢&#xff1f; Componentpublic class Users {Beanpublic User us…

人工智能原理复习--搜索策略(二)

文章目录 上一篇启发式搜索与或图搜索博弈下一篇 上一篇 人工智能原理复习–搜索策略&#xff08;一&#xff09; 启发式搜索 提高一般图搜索效率的关键是优化OPEN表中节点的排序方式 最理想的情况是每次排序OPEN表表首n总在解答路径上 全局排序–对OPEN表中的所有节点进行…

馆藏档案管理系统和数字档案管理系统的区别

馆藏档案管理系统是指传统的档案管理系统&#xff0c;主要包括档案馆内纸质档案的收集、整理、存储、保护、利用等工作。它的主要特点是针对纸质档案的管理&#xff0c;需要建立大量的文件柜、卷宗和保管设备&#xff0c;检索和借阅需要现场操作。 专久智能数字档案管理系统则是…

文献速递:多模态影像组学文献分享:多模态图注意力网络用于COVID-19预后预测

文献速递&#xff1a;多模态影像组学文献分享&#xff1a;多模态图注意力网络用于COVID-19预后预测 01 文献速递介绍 在处理像 COVID-19 这样的新出现的疾病时&#xff0c;患者和疾病特定因素&#xff08;例如&#xff0c;体重或已知共病&#xff09;对疾病的即时进展的影响…

使用智能AI文心一言处理采集数据

简数采集器支持调用百度智能AI文心一言大模型API接口&#xff0c;可对采集的数据进行研究分析&#xff0c;内容创作。 文心一言API使用方法如下&#xff1a; 目录 1. 采集数据 2. 申请API 3. 对接文心一言API 4. 设置文心一言API的执行指令 5. 使用文心一言API处理采集数…

案例061:基于微信小程序的互助学习系统

文末获取源码 开发语言&#xff1a;Java 框架&#xff1a;SSM JDK版本&#xff1a;JDK1.8 数据库&#xff1a;mysql 5.7 开发软件&#xff1a;eclipse/myeclipse/idea Maven包&#xff1a;Maven3.5.4 小程序框架&#xff1a;uniapp 小程序开发软件&#xff1a;HBuilder X 小程序…

54.grpc实现文件上传和下载

文章目录 一&#xff1a;简介1. 什么是grpc2. 为什么我们要用grpc 二&#xff1a;grpc的hello world1、 定义hello.proto文件2、生成xxx_grpc.pb.go文件3、生成xxx.pb.go结构体文件4、编写服务代码service.go5、编写客户端代码client.go 三、服务端流式传输&#xff1a;文件下载…

pycharm安装

1.先去官网下载pycharm 2.下载python3.8 3.修改pip镜像 4.如果有环境变量没加的加一下

一加 12 Pop-up快闪活动来袭,十城联动火爆开启

12 月 9 日&#xff0c;一加 12 Pop-up 快闪活动在北京、深圳、上海、广州等十城联动开启&#xff0c;各地加油欢聚快闪现场&#xff0c;抢先体验与购买一加 12。作为一加十年超越之作&#xff0c;一加 12 全球首发拥有医疗级护眼方案和行业第一 4500nit 峰值亮度的 2K 东方屏、…