天天看点

QCOM 新架构 camera 数据获取

一.概述

传输过程:sensor->csiphy->dma

平台通过DMA获取从sensor中输出的数据,存放于通过ION分配的内存中。

二.DMA

1. 概念

DMA 传输将数据从一个地址空间复制到另外一个地址空间,不需要cpu介入,这时cup可以处理其他工作。

上面说道DMA把数据放到ION分配的内存中,ION分配的内存,虚拟地址是连续的,可是映射到的物理地址是不连续的,但是DMA操作的物理地址必须是连续的,所以需要使用scatterlist。

ps:这里说的scatterlist,说的是用聚散表管理的内存,就是把一段物理上不连续的内存用链表串起来。CPU看到的内存可以通过MMU做映射,一段连续的虚拟内存可以被映射到不连续的物理内存上, 显然这里是不需要scatterlist这个概念支持的。

2. API函数介绍:

使用DMA需要有exporter 以及 importer ,可以理解为生成者,消费者。在qcom平台新架构中,ION就是生产者,user就是消费者。

1)struct dma_buf * dma_buf_export(void *priv, struct dma_buf_ops *ops,size_t size, int flags)

该函数返回 dma_buf 类型的结构体指针。同时还会创建一个匿名文件绑定在该缓冲区上,因此这个缓冲区可以由其他消费者共享了(实际上此时缓冲区可能并未真正创建,这里只是创建了一个抽象的dma_buf)

在qcom平台此时buffer已经通过ION被创建。

2)int dma_buf_fd(struct dma_buf *dmabuf)

用户程序请求一个文件描述符(fd),该文件描述符指向和缓冲区关联的匿名文件。用户程序可以将文件描述符共享给驱动程序或者用户进程程序。

现在每个消费者可以通过文件描述符fd获取共享缓冲区的引用。

3)struct dma_buf * dma_buf_get(int fd)

该函数返回一个dma_buf的引用,同时增加它的refcount(该值记录着dma_buf被多少消费者引用)。

获取缓冲区应用后,消费者需要将它的设备附着在该缓冲区上,这样可以让生产者知道设备的寻址限制。

4)struct dma_buf_attachment * dma_buf_attach(struct dma_buf *dmabuf, struct device*dev)

该函数返回一个attachment的数据结构,该结构会用于scatterlist的操作。

dma-buf共享框架有一个记录位图,用于管理附着在该共享缓冲区上的消费者。

到这步为止,生产者可以选择不在实际的存储设备上分配该缓冲区,而是等待第一个消费者申请共享内存。

当消费者想要使用共享内存进行DMA操作,那么它就会通过接口dma_buf_map_attachment来访问缓冲区。在调用map_dma_buf前至少有一个消费者与之关联。

5)struct sg_table *  dma_buf_map_attachment(struct dma_buf_attachment *, enumdma_data_direction);

该函数是dma_buf->ops->map_dma_buf的一个封装,它可以对使用该接口的对象隐藏"dma_buf->ops->"

 struct sg_table * 

(*map_dma_buf)(struct dma_buf_attachment *, enumdma_data_direction);

生产者必须实现该函数。它返回一个映射到调用者地址空间的sg_table,该数据结构包含了缓冲区的scatterlist。

如果第一次调用该函数,生产者现在可以扫描附着在共享缓冲区上的消费者,核实附着设备的请求,为缓冲区选择一个合适的物理存储空间。

基于枚举类型dma_data_direction,多个消费者可能同时访问共享内存(比如读操作)。

如果被一个信号中断,map_dma_buf()可能返回-EINTR。

API部分摘自 https://blog.csdn.net/prike/article/details/72874879

三.ION

为什么要使用ION?

为了节省开销,以及不必要的拷贝。通过ION分配的内存,调用ion_share_dma_buf_fd函数得到关联到全局可见的文件描述符fd,在不同的进程中mmap此fd,可以得到此进程中指向此物理的的虚拟地址。

四.代码分析

1. alloc buf

1)int cam_mem_mgr_alloc_and_map(struct cam_mem_mgr_alloc_cmd *cmd) 函数分析

int cam_mem_mgr_alloc_and_map(struct cam_mem_mgr_alloc_cmd *cmd)
{
	........
	rc = cam_mem_util_ion_alloc(cmd, // 进入此函数,alloc  buf 以及 share fd,请看下面分析
		&ion_hdl,
		&ion_fd);
	if (rc) {
		CAM_ERR(CAM_CRM, "Ion allocation failed");
		return rc;
	}

	idx = cam_mem_get_slot();
	if (idx < 0) {
		rc = -ENOMEM;
		goto slot_fail;
	}

	if ((cmd->flags & CAM_MEM_FLAG_HW_READ_WRITE) ||
		(cmd->flags & CAM_MEM_FLAG_HW_SHARED_ACCESS) ||
		(cmd->flags & CAM_MEM_FLAG_PROTECTED_MODE)) {

		enum cam_smmu_region_id region;

		if (cmd->flags & CAM_MEM_FLAG_HW_READ_WRITE)
			region = CAM_SMMU_REGION_IO;


		if (cmd->flags & CAM_MEM_FLAG_HW_SHARED_ACCESS)
			region = CAM_SMMU_REGION_SHARED;

		rc = cam_mem_util_map_hw_va(cmd->flags, // 请看 cam_mem_util_map_hw_va 分析
			cmd->mmu_hdls,
			cmd->num_hdl,
			ion_fd,
			&hw_vaddr,
			&len,
			region);
		if (rc)
			goto map_hw_fail;
	}

	mutex_lock(&tbl.bufq[idx].q_lock);
	tbl.bufq[idx].fd = ion_fd; // cam_mem_util_ion_alloc 返回的 share fd 
	tbl.bufq[idx].dma_buf = NULL;
	tbl.bufq[idx].flags = cmd->flags;
	tbl.bufq[idx].buf_handle = GET_MEM_HANDLE(idx, ion_fd);// 通过一些计算得到buf handle,qcom自己搞的玩意
	if (cmd->flags & CAM_MEM_FLAG_PROTECTED_MODE)
		CAM_MEM_MGR_SET_SECURE_HDL(tbl.bufq[idx].buf_handle, true);
	tbl.bufq[idx].kmdvaddr = 0;

	if (cmd->num_hdl > 0)
		tbl.bufq[idx].vaddr = hw_vaddr;
	else
		tbl.bufq[idx].vaddr = 0;

	tbl.bufq[idx].i_hdl = ion_hdl; // ion alloc 返回的ion handle
	tbl.bufq[idx].len = cmd->len;
	tbl.bufq[idx].num_hdl = cmd->num_hdl;
	memcpy(tbl.bufq[idx].hdls, cmd->mmu_hdls,
		sizeof(int32_t) * cmd->num_hdl);
	tbl.bufq[idx].is_imported = false;
	mutex_unlock(&tbl.bufq[idx].q_lock);

	cmd->out.buf_handle = tbl.bufq[idx].buf_handle; // Qcom buf handle
	cmd->out.fd = tbl.bufq[idx].fd; // cam_mem_util_ion_alloc 返回的 share fd ,cmd 后面会被传回hal层,之后的map buf 操作就是传递此fd到kernel
	cmd->out.vaddr = 0;

	CAM_DBG(CAM_CRM, "buf handle: %x, fd: %d, len: %zu",
		cmd->out.buf_handle, cmd->out.fd,
		tbl.bufq[idx].len);

	return rc;

map_hw_fail:
	cam_mem_put_slot(idx);
slot_fail:
	ion_free(tbl.client, ion_hdl);
	return rc;
}
           

2) cam_mem_util_ion_alloc 分析

// cam_mem_util_ion_alloc 分析
static int cam_mem_util_ion_alloc(struct cam_mem_mgr_alloc_cmd *cmd,
	struct ion_handle **hdl,
	int *fd)
{
	.....
	rc = cam_mem_util_get_dma_buf_fd(cmd->len, // 进入此函数
		cmd->align,
		heap_id,
		ion_flag,
		hdl,
		fd);
	......
}

static int cam_mem_util_get_dma_buf_fd(size_t len,
	size_t align,
	unsigned int heap_id_mask,
	unsigned int flags,
	struct ion_handle **hdl,
	int *fd)
{
......
	*hdl = ion_alloc(tbl.client, len, align, heap_id_mask, flags); //分配buf,返回ION handle,hdl->buffer alloc 的首地址,后面会被放到dmabuf->priv变量中,
								       // user在mmap的时候返回
	if (IS_ERR_OR_NULL(*hdl))
		return -ENOMEM;

	*fd = ion_share_dma_buf_fd(tbl.client, *hdl);// share ion handle,返回可以被 mmap 的fd,重点讲解
	if (*fd < 0) {
		CAM_ERR(CAM_CRM, "get fd fail");
		rc = -EINVAL;
		goto get_fd_fail;
	}

	return rc;
.......
}

int ion_share_dma_buf_fd(struct ion_client *client, struct ion_handle *handle)
{
	struct dma_buf *dmabuf;
	int fd;

	dmabuf = ion_share_dma_buf(client, handle);// 此函数,主要调用 dma_buf_export 返回 struct dma_buf *dmabuf;下面有具体分析
	if (IS_ERR(dmabuf))
		return PTR_ERR(dmabuf);

	fd = dma_buf_fd(dmabuf, O_CLOEXEC); // dma_buf_fd()将步骤ion_share_dma_buf中创建的dam_buf对象关联到全局可见的文件描述符fd,同时通过ioctl方法将fd传递给应用层。
	if (fd < 0)
		dma_buf_put(dmabuf);
	return fd; // 返回fd
}

struct dma_buf *ion_share_dma_buf(struct ion_client *client,
						struct ion_handle *handle)
{
    ......
	buffer = handle->buffer; // ion alloc buf, 下面会放到 exp_info.priv
	ion_buffer_get(buffer);
	mutex_unlock(&client->lock);

	exp_info.ops = &dma_buf_ops; // ion ops,后面 dma_buf_map_attachment 中会调用其中的 ion_map_dma_buf 方法,mmap时调用 ion_mmap
	exp_info.size = buffer->size;
	exp_info.flags = O_RDWR;
	exp_info.priv = buffer; //赋值给priv变量,后面import会用到,此buffer就是存放stream数据的

	dmabuf = dma_buf_export(&exp_info); // dma_buf_export(dma架构中的exporter) 创建dma_buf对象
	if (IS_ERR(dmabuf)) {
	  ion_buffer_put(buffer);
	  return dmabuf;
	}

	return dmabuf;
}
           

3)alloc 之后的 map

//  cam_mem_util_map_hw_va 分析

cam_mem_util_map_hw_va

    cam_smmu_map_user_iova

        cam_smmu_check_fd_in_list  // 检查fd是否以及存在

        cam_smmu_map_buffer_and_add_to_list // add fd to list

            dma_buf_get  // dma_buf_get(fd)获取dma_buf对象,importer使用,每个消费者可以通过文件描述符fd获取共享缓冲区的引用,该函数返回一个dma_buf的引用,同时增加它的refcount(该值记录着dma_buf被多少消费者引/用)。获取缓冲区引用后,消费者需要将它的设备附着在该缓冲区上,这样可以让生产者知道设备的寻址限制。

            cam_smmu_map_buffer_validate // 验证buffer有效性

                dma_buf_attach          // 该函数返回一个attachment的数据结构,该结构会用于scatter list的操作。

                             // dma-buf共享框架有一个记录位图,用于管理附着在该共享缓冲区上的消费者。

                             // 到这步为止,生产者可以选择不在实际的存储设备上分配该缓冲区,而是等待第一个消费者申请共享内存。

                dma_buf_map_attachment // 当消费者想要使用共享内存进行DMA操作,那么它就会通过接口                                                                                                  // dma_buf_map_attachment来访问缓冲区。在调用map_dma_buf前至少有一个消费者                                                                   // 与之关联。

dma_buf_map_attachment 中调用 ion_map_dma_buf 分析

// dma_buf_map_attachment 中调用 ion_map_dma_buf 分析
static struct sg_table *ion_map_dma_buf(struct dma_buf_attachment *attachment,
					enum dma_data_direction direction)
{
	struct dma_buf *dmabuf = attachment->dmabuf;
	struct ion_buffer *buffer = dmabuf->priv;
	struct sg_table *table;

	table = ion_dupe_sg_table(buffer->sg_table); // 重新alloc一个sg_table,并把buffer->sg_table->sgl拷贝到新的sg_table中
	if (!table)
		return NULL;

	ion_buffer_sync_for_device(buffer, attachment->dev, direction); // 同步ion buffer 到 DMA
	return table;
}

static struct sg_table *ion_dupe_sg_table(struct sg_table *orig_table)
{
	int ret, i;
	struct scatterlist *sg, *sg_orig;
	struct sg_table *table;

	table = kzalloc(sizeof(*table), GFP_KERNEL);
	if (!table)
		return NULL;

	ret = sg_alloc_table(table, orig_table->nents, GFP_KERNEL); // 分配一个有 orig_table->nents 个 scatterlist的sg_table
	if (ret) {
		kfree(table);
		return NULL;
	}

	sg_orig = orig_table->sgl;
	for_each_sg(table->sgl, sg, table->nents, i) { // 拷贝 sg_orig 到 table->sgl
		memcpy(sg, sg_orig, sizeof(*sg));
		sg_orig = sg_next(sg_orig);
	}
	return table;
}

static void ion_buffer_sync_for_device(struct ion_buffer *buffer,
				       struct device *dev,
				       enum dma_data_direction dir)
{
	struct ion_vma_list *vma_list;
	int pages = PAGE_ALIGN(buffer->size) / PAGE_SIZE; // 计算 pages
	int i;

	pr_debug("%s: syncing for device %s\n", __func__,
		 dev ? dev_name(dev) : "null");

	if (!ion_buffer_fault_user_mappings(buffer))
		return;

	mutex_lock(&buffer->lock);
	for (i = 0; i < pages; i++) {
		struct page *page = buffer->pages[i]; // 将 ION buffer 拆分为 pages

		if (ion_buffer_page_is_dirty(page))
			ion_pages_sync_for_device(dev, ion_buffer_page(page), // scatter ion buffer 到 DMA
							PAGE_SIZE, dir);

		ion_buffer_page_clean(buffer->pages + i);
	}
	list_for_each_entry(vma_list, &buffer->vmas, list) {
		struct vm_area_struct *vma = vma_list->vma;

		zap_page_range(vma, vma->vm_start, vma->vm_end - vma->vm_start,
			       NULL);
	}
	mutex_unlock(&buffer->lock);

}

void ion_pages_sync_for_device(struct device *dev, struct page *page,
		size_t size, enum dma_data_direction dir)
{
	struct scatterlist sg;

	WARN_ONCE(!dev, "A device is required for dma_sync\n");

	sg_init_table(&sg, 1);
	sg_set_page(&sg, page, size, 0);
	/*
	 * This is not correct - sg_dma_address needs a dma_addr_t that is valid
	 * for the targeted device, but this works on the currently targeted
	 * hardware.
	 */
	sg_dma_address(&sg) = page_to_phys(page); // 转换 page 为物理地址 ,赋值给 sg->dma_address 为 dma_addr_t 类型
	dma_sync_sg_for_device(dev, &sg, 1, dir); // DMA debug?
}
           

总之:此函数作用就是把ion分配的buffer的虚拟地址对应的物理地址,

2. map buffer

分析log,发现只有IPE调用了,CSLMAP操作。在HAL层调用CSLALLOC时,已经把buffer给到DMA,并且CSLALLOC成功后,HAL会调用mmap,拿到user空间的虚拟地址,然后插入到链表中。

五.更正一些错误

上面是刚刚接触qc camera新架构时写的,由于以前比较稚嫩,所以写文章还是有些错误的,我这里更正一下,以前错误的解释就不删除了,可以提醒自己。

1.CSLAlloc call ioctl 时会传递 cam_mem_mgr_alloc_cmd allocCmd,code如下:

/* CAM_REQ_MGR_ALLOC_BUF */
struct cam_mem_mgr_alloc_cmd {
	uint64_t len;
	uint64_t align;
	int32_t mmu_hdls[CAM_MEM_MMU_MAX_HANDLE];
	uint32_t num_hdl;
	uint32_t flags;
	struct cam_mem_alloc_out_params out;
};
           

1)mmu_hdls是每个camera hw device唯一的,可以理解为alloc传递的mmu_hdls是哪个device的就是在分配内存给哪个device用,当然后续还可以通过CSLMAP来让其他device也使用。实际上这个mmu_hdls是devices在UMD ioctl CAM_QUERY_CAP到 KMD通过call cam_smmu_get_handle获得的

2)cam smmu维护着iommu_cb_set.cb_info[idx].smmu_buf_list链表,用来记录每个device所map的buffer,idx = GET_SMMU_TABLE_IDX(handle);其实这里可以直接把idx理解为device,毕竟mmu_hdls是唯一的

3)其他的参数就不解释了

2.某些时候CSLALLOC时已经MAP了,为什么还要调用CSLMAP?

1)Device 在 UMD call CSLAlloc 时,不知道这个buffer以后还会给哪些device用,所以当其他Device使用时需要call CSLMAP

2)Device 在 UMD call CSLAlloc 时,传递的 num_hdl = 0 时,KMD是不会进行map操作,只是分配一个buffer,需要CSLMAP后device才能使用

3.device为什么需要CSLMAP buffer?

CSLMAP有一个主要的作用是call  dma_buf_attach 以及 dma_buf_map_attachment,这样我们才能通过DMA传输DRAM上的数据到Device,具体dma相关可以参考https://blog.csdn.net/hexiaolong2009/category_9542494.html写的很好。

继续阅读