v4l2接收流程

news2024/11/19 3:44:28

内核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.coloradmin.cn/o/1298415.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

哈希表的几种实现方式与比较

版权声明 本文原创作者&#xff1a;谷哥的小弟作者博客地址&#xff1a;http://blog.csdn.net/lfdfhl 哈希表概述 哈希表&#xff08;Hash Table&#xff09;是一种常用的数据结构&#xff0c;用于实现键值对的映射关系。它通过哈希函数将键映射到一个特定的索引位置&#xf…

Spring Boot 3 整合 Mybatis-Plus 实现动态数据源切换实战

&#x1f680; 作者主页&#xff1a; 有来技术 &#x1f525; 开源项目&#xff1a; youlai-mall &#x1f343; vue3-element-admin &#x1f343; youlai-boot &#x1f33a; 仓库主页&#xff1a; Gitee &#x1f4ab; Github &#x1f4ab; GitCode &#x1f496; 欢迎点赞…

银河麒麟本地软件源配置方法

软件源介绍 软件源可以理解为软件仓库&#xff0c;当需要安装软件时则会根据源配置去相应的软件源下载软件包&#xff0c;此方法的优点是可以自动解决软件包的依赖关系。常见的软件源有光盘源、硬盘源、FTP源、HTTP源&#xff0c;本文档主要介绍本地软件源的配置方法&#xff…

专注抖音短视频账号矩阵系统源头开发---saas工具

抖音账号|短视频矩阵分发系统 | 多账号管理发布 |MVC架 短视频矩阵分发系统是一种可以帮助企业、机构和个人高效分发短视频的工具。随着社交媒体的不断普及&#xff0c;短视频的使用越来越广泛&#xff0c;因此如何快速而准确地将短视频传播到不同的平台和账号上已经成为了一个…

短剧分销平台搭建:短剧变现新模式

短剧作为今年大热的行业&#xff0c;深受大众追捧&#xff01;短剧剧情紧凑&#xff0c;几乎每一集都有高潮剧情&#xff0c;精准击中了当下网友的碎片化时间。 短剧的形式较为灵活&#xff0c;可以轻松融入各种的元素&#xff0c;比如喜剧、悬疑、爱情等&#xff0c;可以满足…

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

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

postman常用脚本

一、在参数中动态添加开始时间和结束时间的时间戳 1.先在collection中添加参数&#xff0c;这里的作用域是collection&#xff0c;也可以是其他的任何scope 2.在Pre-request Script 中设定开始时间和结束时间参数&#xff0c;比如昨天和今天的时间戳&#xff0c;下面是js代码 …

彻底搞懂零拷贝技术( DMA、PageCache)

DMA 直接内存访问&#xff08;Direct Memory Access&#xff09; 什么是DMA&#xff1f; 在进行数据传输的时候&#xff0c;数据搬运的工作全部交给 DMA 控制器&#xff0c;而 CPU 不再参与&#xff0c;可以去干别的事情。 传统I/O 在没有 DMA 技术前&#xff0c;全程数据…

【图论笔记】克鲁斯卡尔算法(Kruskal)求最小生成树

【图论笔记】克鲁斯卡尔算法&#xff08;Kruskal&#xff09;求最小生成树 适用于 克鲁斯卡尔适合用来求边比较稀疏的图的最小生成树 简记&#xff1a; 将边按照升序排序&#xff0c;选取n-1条边&#xff0c;连通n个顶点。 添加一条边的时候&#xff0c;如何判断能不能添加…

链表OJ—相交链表

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言 1、相交链表的题目&#xff1a; 方法讲解&#xff1a; 图文解析&#xff1a; 代码实现&#xff1a; 总结 前言 世上有两种耀眼的光芒&#xff0c;一种是正在升…

《PySpark大数据分析实战》图书上线啦

《PySpark大数据分析实战》图书上线啦 《PySpark大数据分析实战》图书上线啦特殊的日子关于创作关于数据关于Spark关于PySpark关于图书/专栏 《PySpark大数据分析实战》图书上线啦 特殊的日子 不知不觉一转眼入驻CSDN已经满一年了&#xff0c;这真是一个充满意义的特殊的日子&…

SystemUI下拉通知菜单栏定时自动隐藏

前言 在系统应用开发过程中&#xff0c;常常遇到一些特殊的需求&#xff0c;Android原生的应用并无此适配&#xff0c;此时需要对系统应用进行定制化开发。 目前遇到的这样一个需求&#xff1a;下拉通知菜单栏时&#xff0c;定时8秒后自动关闭通知菜单栏。通知菜单栏为Sytstem…

如何用Python编写俄罗斯方块Tetris游戏?

在本文中&#xff0c;我们将用Python代码构建一个令人惊叹的项目&#xff1a;俄罗斯方块游戏。在这个项目中&#xff0c;我们将使用pygame库来构建游戏。要创建此项目&#xff0c;请确保您的系统中安装了最新版本的Python。让我们开始吧&#xff01; Pygame是一组跨平台的Pyth…

Mysql研学-认识与安装

一 数据库 1 Java的数据存储技术 ① 变量:一个数据存储空间的表示 ② 数组:存储一组相同数据类型的"容器" ③ 集合:存储一组任意引用数据类型的"容器" ④ 配置文件: .properties:基于Properties集合存储(Map集合的具体实例) .xml文件:基于标签存储数据…

centos7 安装 mysql8 详细步骤记录

下载 mysql 8 更新系统&#xff1a; sudo yum update 添加 MySQL Yum存储库&#xff1a; sudo rpm -Uvh https://repo.mysql.com/mysql80-community-release-el7-3.noarch.rpm 安装 MySQL 8&#xff1a; sudo yum install mysql-server 重置密码 查看初始密码&#xff1…

三种入耳检测光感芯片驱动开发比较

三种入耳检测光感芯片驱动开发比较 是否需要申请加入数字音频系统研究开发交流答疑群(课题组)&#xff1f;可加我微信hezkz17, 本群提供音频技术答疑服务&#xff0c;群赠送语音信号处理降噪算法&#xff0c;蓝牙耳机音频&#xff0c;DSP音频项目核心开发资料, 重要的寄存器…

应用层之应用层的网络应用模型————C/S和P2P、域名解析系统DNS、文件传输协议FTP

学习的最大理由是想摆脱平庸&#xff0c;早一天就多一份人生的精彩&#xff1b;迟一天就多一天平庸的困扰。各位小伙伴&#xff0c;如果您&#xff1a; 想系统/深入学习某技术知识点… 一个人摸索学习很难坚持&#xff0c;想组团高效学习… 想写博客但无从下手&#xff0c;急需…

Ubuntu上svn基本使用(gitee提交下载)

目录 环境准备 1. 获取代码到本地 直接获取 获取代码时加入用户名密码 指定版本更新 2. 提交代码 3. 展示代码列表 4. 添加代码文件(目录) 5. 删除gitee仓库中的文件 参考文档链接 环境准备 当前操作系统为Ubuntu22.04LTS gitee 创建仓库时 需要打开svn的支持 sudo…

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;文件下载…

短视频无人实景直播源码技术开发=抖去推saas直播源码

开发无人直播源码技术需要具备一定的编程和网络知识。以下是一些基本的步骤和资源&#xff0c;帮助你进行无人直播源码的开发搭建&#xff1a; 1. 选择编程语言和开发环境&#xff1a;根据你的个人喜好和技术熟练程度&#xff0c;可以选择一些流行的编程语言&#xff0c;如Pyth…