对于camera 模块除了逻辑之外,我关注的点就内存的使用,后续还会写些内存相关的部分。这里只探讨如何申请buffer。
当前buffer申请分为
enum v4l2_memory {
V4L2_MEMORY_MMAP = 1,
V4L2_MEMORY_USERPTR = 2,
V4L2_MEMORY_OVERLAY = 3,
V4L2_MEMORY_DMABUF = 4,
};
mmap是下面详细要讲的。OVERLAY这种方式似乎没用过,之前咨询过相关人员,似乎overlay是早期类似gralloc buffer这种显存没有统一框架,然后实现的一种camera数据直显的一种方式。
DMABUF和USERPTR很类似,都是在上层申请buffer,然后将指针直接赋值给vb2模块,当然这种申请都要保证物理地址连续。DMABUF需要通过DMA的接口,export出一个fd,在vb2记录的是这个fd的值。接下来看mmap方式
1.hal层调用
V4L2VideoNode::requestBuffers
里面调用
req_buf.memory = memType;
req_buf.count = num_buffers;
req_buf.type = mBufType;
ret = pioctl(mFd, VIDIOC_REQBUFS, &req_buf, mName.c_str());
#define pioctl(fd, ctrlId, attr, name)
SysCall::ioctl(fd, ctrlId, attr)
所以执行的是
SysCall::ioctl(mFd, VIDIOC_REQBUFS, &req_buf);
int SysCall::ioctl(int fd, int request, void *arg)
{
return ::ioctl(fd, request, (void *)arg);
}
Rk对ioctl做了几次封装,只管传入的参数。
memType,num_buffers,mBufType
memType就是前面说的几种buffer类型
num_buffers 一般是申请4个buffer轮训
mBufType,对于capture设备来说,一般都是V4L2_BUF_TYPE_VIDEO_CAPTURE,但是rk用的V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE,这样可以支持多个平面,可以支持更多数据类型。
2.驱动函数
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);
……
CLEAR_AFTER_FIELD(p, memory);
return ops->vidioc_reqbufs(file, fh, p);//执行video节点的ioctl_ops
}
static const struct v4l2_ioctl_ops rkisp1_v4l2_ioctl_ops = {
.vidioc_reqbufs = vb2_ioctl_reqbufs,
看
vb2_ioctl_reqbufs执行的是
vb2_core_reqbufs(vdev->queue, p->memory, &p->count);//进入到vb2_queue
2.1
ret = call_qop(q, queue_setup, q, NULL, &num_buffers, &num_planes,
q->plane_sizes, q->alloc_ctx);
看call_qop执行的是q->ops->queue_setup函数
q->ops = &rkisp1_vb2_ops;
.queue_setup = rkisp1_queue_setup,
函数定义
static int rkisp1_queue_setup(struct vb2_queue *queue,
const void *parg,
unsigned int *num_buffers,
unsigned int *num_planes,
unsigned int sizes[],
void *alloc_ctxs[])
{
……
pixm = &stream->out_fmt;//输出格式
isp_fmt = &stream->out_isp_fmt;//capture格式,会在s_fmt赋值
……
*num_planes = isp_fmt->mplanes;//capture_fmt sp_fmts,根据格式找到对应plane
for (i = 0; i < isp_fmt->mplanes; i++) {
const struct v4l2_plane_pix_format *plane_fmt;
plane_fmt = &pixm->plane_fmt[i];
sizes[i] = plane_fmt->sizeimage;// sizeimage也是在s_fmt中根据rk定义的每种格式的bpp,也就是每个pix要占用的字节数算出来的。
alloc_ctxs[i] = dev->alloc_ctx;//在初始化的时候,dev->alloc_ctx = vb2_dma_contig_init_ctx(dev->v4l2_dev.dev),是vb2_dc_conf结构体,
}
//rk代码里只有yuv格式最后带M的才有多个plane,所以一般都是一个plane
2.2 然后调用 __vb2_queue_alloc申请内存
这个函数是初始化vb2_buffer结构,保存在vb2_queue里
__vb2_queue_alloc(q, memory, num_buffers, num_planes);
static int __vb2_queue_alloc(struct vb2_queue *q, enum vb2_memory memory,
unsigned int num_buffers, unsigned int num_planes)
{
……
vb->state = VB2_BUF_STATE_DEQUEUED;//申请默认为dq状态
……
/* Allocate video buffer memory for the MMAP type */
if (memory == VB2_MEMORY_MMAP) {//为什么没看到DMABUF?
ret = __vb2_buf_mem_alloc(vb);
……
ret = call_vb_qop(vb, buf_init, vb);
……
q->bufs[q->num_buffers + buffer] = vb;//保存到vb2_queue
……
}
static int __vb2_buf_mem_alloc(struct vb2_buffer *vb)
{
struct vb2_queue *q = vb->vb2_queue;
void *mem_priv;
int plane;
for (plane = 0; plane < vb->num_planes; ++plane) {
unsigned long size = PAGE_ALIGN(q->plane_sizes[plane]);
mem_priv = call_ptr_memop(vb, alloc, q->alloc_ctx[plane],
size, q->dma_dir, q->gfp_flags);//gpf表示内存标志,比如GFP_KERNEL,GFP_DMA(DMA应该是预留了空间)
……
vb->planes[plane].mem_priv = mem_priv;//保存参数是vb2_dc_buf类型
vb->planes[plane].length = q->plane_sizes[plane];
}
在kernel/drivers/media/platform/rockchip/isp1/capture.c
rkisp_init_vb2_queue
q->mem_ops = &vb2_dma_contig_memops;
memops里面的alloc是vb2_dc_alloc
这个函数是videobuf2提供的
static void *vb2_dc_alloc(void *alloc_ctx, unsigned long size,
enum dma_data_direction dma_dir, gfp_t gfp_flags)
{
struct vb2_dc_conf *conf = alloc_ctx;
struct device *dev = conf->dev;
struct vb2_dc_buf *buf;
buf = kzalloc(sizeof *buf, GFP_KERNEL);
if (!buf)
return ERR_PTR(-ENOMEM);
buf->attrs = conf->attrs;//这个为NULL
buf->cookie = dma_alloc_attrs(dev, size, &buf->dma_addr,
GFP_KERNEL | gfp_flags, &buf->attrs);
static inline void *dma_alloc_attrs(struct device *dev, size_t size,
dma_addr_t *dma_handle, gfp_t flag,
struct dma_attrs *attrs)
{
struct dma_map_ops *ops = get_dma_ops(dev);
void *cpu_addr;
BUG_ON(!ops);
if (dma_alloc_from_coherent(dev, size, dma_handle, &cpu_addr))
return cpu_addr;
if (!arch_dma_alloc_attrs(&dev, &flag))
return NULL;
if (!ops->alloc)
return NULL;
cpu_addr = ops->alloc(dev, size, dma_handle, flag, attrs);
debug_dma_alloc_coherent(dev, size, *dma_handle, cpu_addr);
return cpu_addr;
}
dma_alloc_from_coherent从预留连续内存池分配,当前用的预留内存有两种形式
1.cma 这个是针对所有的模块都可以申请使用
2.device专属内存,会在dts中配置专属内存起始地址等,只给当前device使用。
rk早期是用过这两种实现方式,后期rk对isp使用iommu。这样就不需要预分配连续内存了,这样可以节省内存,只在申请的时候用。iommu可以把离散的内存块,映射成连续总线地址,这样dma也可以使用。
所以执行的是ops->alloc(dev, size, dma_handle, flag, attrs);
关于rkiommu的文章写了alloc对应的函数
__iommu_alloc_attrs
这其实是分配iova,可以对应多段物理内存。对于dma接口,他是连续的,是以页为单位申请。然后iova和申请的page生成译码表。
注意这个attrs,申请内存的相关属性,在iommu申请的代码中,如果没有指定属性,iommu默认配置成writeombine的,这个其实是读uncached。所以如果发现copy这段内存比较慢,可以改成正常能使用cache的。
一般在申请buffer之后会调用VIDIOC_QUERYBUF
pioctl(mFd , VIDIOC_QUERYBUF, vbuf.get(), mName.c_str())
QUERYBUF传入的是v4l2_buffer结构作为返回。
ops->vidioc_querybuf(file, fh, p); -> vb2_querybuf(vdev->queue, p)
vb2_querybuf先调用vb = q->bufs[b->index];就是获取之前申请的vb2_buffer封装的内存,然后调用vb2_core_querybuf(q, b->index, b)
call_bufop(q, fill_user_buffer, q->bufs[index], pb);
执行的是q->buf_ops-> fill_user_buffer
.fill_user_buffer = __fill_v4l2_buffer,
static int __fill_v4l2_buffer(struct vb2_buffer *vb, void *pb)
{
……
else {
/*
* We use length and offset in v4l2_planes array even for
* single-planar buffers, but userspace does not.
*/
b->length = vb->planes[0].length;
b->bytesused = vb->planes[0].bytesused;
if (q->memory == VB2_MEMORY_MMAP)
b->m.offset = vb->planes[0].m.offset;
else if (q->memory == VB2_MEMORY_USERPTR)
b->m.userptr = vb->planes[0].m.userptr;
else if (q->memory == VB2_MEMORY_DMABUF)
b->m.fd = vb->planes[0].m.fd;
}
其实就是vb的信息转换或者说赋值到用户态的v4l2_buffer,但是前面看mem_priv里面保存了vb2_dc_buf信息(包含了申请的虚拟地址)
在调用QUERYBUF之后,会调用mmap函数。返回映射的handle或者叫地址
来看看mmap函数,原本我以为是公共函数,跟节点没关系,但既然传入节点fd。
发现video节点操作函数有.mmap = vb2_fop_mmap,
vb2_fop_mmap-- > vb2_mmap
快捷键目录标题文本样式列表链接代码片表格注脚注释自定义列表LaTeX 数学公式插入甘特图插入UML图插入Mermaid流程图插入Flowchart流程图插入类图
代码片复制
下面展示一些 内联代码片
。
// A code block
var foo = 'bar';
int vb2_mmap(struct vb2_queue *q, struct vm_area_struct *vma)
{
……//通过传下来的offset找到对应的plane
__find_plane_by_offset(q, off, &buffer, &plane);
……//调用mmap函数
call_memop(vb, mmap, vb->planes[plane].mem_priv, vma);
这里发现传下来的是vm_area_struct,看着和上层调用的参数完全不一致。
这个结构是描述一段虚拟空间,所有vm_area_struct结构体组成一个AVL树。
mmap函数就是要创建一个新的vm_area_struct结构,并将其与文件的物理磁盘地址相连。将应用传下来的参数赋值到vm_area_struct,申请虚拟地址空间。然后这个结构体传入驱动。
因为
.mmap = vb2_dc_mmap
static int vb2_dc_mmap(void *buf_priv, struct vm_area_struct *vma)
{
……
ret = dma_mmap_attrs(buf->dev, vma, buf->cookie,
buf->dma_addr, buf->size, &buf->attrs);
……
vma->vm_flags |= VM_DONTEXPAND | VM_DONTDUMP;
vma->vm_private_data = &buf->handler;
vma->vm_ops = &vb2_common_vm_ops;
//这个open只是增加refcount
vma->vm_ops->open(vma);
static inline int
dma_mmap_attrs(struct device *dev, struct vm_area_struct *vma, void *cpu_addr,
dma_addr_t dma_addr, size_t size, struct dma_attrs *attrs)
{
struct dma_map_ops *ops = get_dma_ops(dev);
BUG_ON(!ops);
if (ops->mmap)
return ops->mmap(dev, vma, cpu_addr, dma_addr, size, attrs);
return dma_common_mmap(dev, vma, cpu_addr, dma_addr, size);
}
ops->mmap是不为空的,执行到iommu 的mmap函数
static int __iommu_mmap_attrs(struct device *dev, struct vm_area_struct *vma,
void *cpu_addr, dma_addr_t dma_addr, size_t size,
struct dma_attrs *attrs)
{
struct vm_struct *area;
int ret;
vma->vm_page_prot = arch_get_dma_pgprot(attrs, vma->vm_page_prot,
is_device_dma_coherent(dev));
if (dma_mmap_from_coherent(dev, vma, cpu_addr, size, &ret))
return ret;
area = find_vm_area(cpu_addr);
if (WARN_ON(!area || !area->pages))
return -ENXIO;
//后面有空看下这个函数
return iommu_dma_mmap(area->pages, size, vma);
}
调用完mmap之后,都是要将buf入队。执行VIDIOC_QBUF
vb2_core_qbuf会调用
if (q->start_streaming_called)
__enqueue_in_driver(vb);
__enqueue_in_driver内部调用
call_void_memop(vb, prepare, vb->planes[plane].mem_priv);
.prepare = vb2_dc_prepare,
会调用对应的这个prepare函数,里面调用
dma_sync_sg_for_device//可以让设备再次获得缓冲区
和rkisp1_buf_queue这里面会
static void rkisp1_buf_queue(struct vb2_buffer *vb)
{
struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
struct rkisp1_buffer *ispbuf = to_rkisp1_buffer(vbuf);
ispbuf->buff_addr[i] = vb2_dma_contig_plane_dma_addr(vb, i);
所以物理地址赋值是在这里。
之后用这个地址去填充isp的寄存器,驱动dma数据就可以搬运到这里