QC_CAM内存管理之 kernel 篇(一)
一.相关变量
1.buffer flag
// 对应HAL的CSLMemFlagHw
#define CAM_MEM_FLAG_HW_READ_WRITE (1<<0)
// 下面两个在hal中未使用
#define CAM_MEM_FLAG_HW_READ_ONLY (1<<1)
#define CAM_MEM_FLAG_HW_WRITE_ONLY (1<<2)
// 是否要做dma_buf_kmap, 用于支持cpu直接访问
// dma_buf_kmap可以map出一段连续的虚拟地址,我们只需要call _kmap
// 映射每个单独的page,然后只使用第一次调用_kmap返回的虚拟地址
#define CAM_MEM_FLAG_KMD_ACCESS (1<<3)
// UMD 有在用,未见在kernel中使用
// UMD 是否需要 mmap 出来 UMD VirtualAddr,保存在 CSLBufferInfo 里
#define CAM_MEM_FLAG_UMD_ACCESS (1<<4)
/*
* protected,在HAL中只看到hal的stream buffer会用以及跑在DSP上的buffer会用
* protected buffer也不能被map到hw以及UMD
* HAL中有如下解释
* This is based on assumption that - if any CHI node exists in secure mode,
* it runs on DSP and so set the below flags while importing the HAL buffer.
* 1. Set ProtectedUMD Access so that Node neither map the buffer to
* any camera HW nor to UMD.
* 2. Set DSPSecureAccess to indicate this buffer will be accessed by DSP in secure
* mode. For sink buffers mapping, this flag is not used as of now.
*/
#define CAM_MEM_FLAG_PROTECTED_MODE (1<<5)
// 对应HAL使用camxcmdbufferManager create的buffer,比如sensor的一些参数,IPE,BPS的一些ConfigureIO参数
#define CAM_MEM_FLAG_CMD_BUF_TYPE (1<<6)
#define CAM_MEM_FLAG_PIXEL_BUF_TYPE (1<<7)
#define CAM_MEM_FLAG_STATS_BUF_TYPE (1<<8)
// 对应HAL使用camxcmdbufferManager create打包cmdBuffer的packet buffer
#define CAM_MEM_FLAG_PACKET_BUF_TYPE (1<<9)
// cpu与DMA同时访问的buffer需要设置,用于Cache同步操作,保证一致性,相关可以百度Cache一致性
#define CAM_MEM_FLAG_CACHE (1<<10)
// 从HAL使用来看,IPE,BPS的ConfigureIObuffer会使用,还有IPE BPS的stats buffer会使用.
// 设置此flag后,会从通过gen_pool_alloc从shared_mem_pool中分配内存
#define CAM_MEM_FLAG_HW_SHARED_ACCESS (1<<11)
// 跑在DSP上 buffer 使用
#define CAM_MEM_FLAG_CDSP_OUTPUT (1<<12)
/*
* 官方解释:
* 由ION使用,它将确保在取消映射时不会删除映射,而是在释放ION_缓冲区时删除映射。
*
* 个人解释:
* 是否需要delay unmap,这里指的是iommu
* Msm_dma_iommu_mapping.c (kernel\msm-4.19\drivers\iommu) 11574 2020-8-4
* 追代码发现在函数__msm_dma_map_sg中,如果使能了 DMA_ATTR_NO_DELAYED_UNMAP,那么就会call kref_get(&iommu_map->ref);
* 当我们call csl unmap的时候会call msm_dma_unmap_sg_attrs,调用kref_put(&iommu_map->ref, msm_iommu_map_release); 如果ref = 0,则call msm_iommu_map_release
* csl alloc and map 阶段会调用上述函数,并且在csl map阶段也会调用,试想一下如果不设置DMA_ATTR_NO_DELAYED_UNMAP,那么我们在alloc时调用了iommu的map,
* 然后在csl map也做一次iommu的map,然后call 一次 unmap,那么iommu的map将会被释放,在下一次使用时又需要重新iommu map
*/
#define CAM_MEM_FLAG_DISABLE_DELAYED_UNMAP (1<<13)
二.alloc and map阶段
概述:
我们就看ion_alloc,dma相关的api去看dma系列文章吧
路径:
Ion.c (kernel\msm-4.19\drivers\staging\android\ion) 34556 2020-8-4
*1.struct dma_buf ion_alloc_dmabuf
static const struct dma_buf_ops dma_buf_ops = {
.map_dma_buf = ion_map_dma_buf,
.unmap_dma_buf = ion_unmap_dma_buf,
.mmap = ion_mmap,
.release = ion_dma_buf_release,
.attach = ion_dma_buf_attach,
.detach = ion_dma_buf_detatch,
.begin_cpu_access = ion_dma_buf_begin_cpu_access,
.end_cpu_access = ion_dma_buf_end_cpu_access,
.begin_cpu_access_umapped = ion_dma_buf_begin_cpu_access_umapped,
.end_cpu_access_umapped = ion_dma_buf_end_cpu_access_umapped,
.begin_cpu_access_partial = ion_dma_buf_begin_cpu_access_partial,
.end_cpu_access_partial = ion_dma_buf_end_cpu_access_partial,
.map = ion_dma_buf_kmap,
.unmap = ion_dma_buf_kunmap,
.vmap = ion_dma_buf_vmap,
.vunmap = ion_dma_buf_vunmap,
.get_flags = ion_dma_buf_get_flags,
};
struct dma_buf *ion_alloc_dmabuf(size_t len, unsigned int heap_id_mask,
unsigned int flags)
{
// 遍历所有的heap,通过传入的heap,call 不同的heap->ops->allocate
// heap->ops->allocate
// 路径:Ion_system_heap.c (kernel\msm-4.19\drivers\staging\android\ion) 21342 2020-8-4
plist_for_each_entry(heap, &dev->heaps, node) {
/* if the caller didn't specify this heap id */
if (!((1 << heap->id) & heap_id_mask))
continue;
buffer = ion_buffer_create(heap, dev, len, flags);
if (!IS_ERR(buffer) || PTR_ERR(buffer) == -EINTR)
break;
}
exp_info.ops = &dma_buf_ops; // 这个ops是ion的,dma的ops会在dma_buf_export中获取
exp_info.size = buffer->size;
exp_info.flags = O_RDWR;
exp_info.priv = buffer;
exp_info.exp_name = kasprintf(GFP_KERNEL, "%s-%s-%d-%s", KBUILD_MODNAME,
heap->name, current->tgid, task_comm);
// 下面看一下此函数
// 此函数就是把上面的几个变量填到dmabuf中,并且把file和dmabuf联系起来
dmabuf = dma_buf_export(&exp_info);
}
2.dma_buf_export
分配一个dam_buf,并且绑定ops(ion ops),创建file绑定dam_buf以及ops(dma ops),dam_buf ref + 1, 然后返回dam_buf
static const struct file_operations dma_buf_fops = {
.release = dma_buf_release,
.mmap = dma_buf_mmap_internal,
.llseek = dma_buf_llseek,
.poll = dma_buf_poll,
.unlocked_ioctl = dma_buf_ioctl,
#ifdef CONFIG_COMPAT
.compat_ioctl = dma_buf_ioctl,
#endif
};
struct dma_buf *dma_buf_export(const struct dma_buf_export_info *exp_info){
dmabuf = kzalloc(alloc_size, GFP_KERNEL);
dmabuf->priv = exp_info->priv;
dmabuf->ops = exp_info->ops;
dmabuf->size = exp_info->size;
dmabuf->exp_name = exp_info->exp_name;
dmabuf->owner = exp_info->owner;
init_waitqueue_head(&dmabuf->poll);
dmabuf->cb_excl.poll = dmabuf->cb_shared.poll = &dmabuf->poll;
dmabuf->cb_excl.active = dmabuf->cb_shared.active = 0;
dmabuf->name = bufname;
dmabuf->ktime = ktime_get();
// file 和 dmabuf 绑定,并且file ops 是 dma_buf_fops,上面已经粘贴出来
// 当我们在后面 call dma_buf_fd 的时候,fd 和 file 绑定,在hal层对fd的操作,实际上是链接到 dma_buf_fops
// 比如我们在 UMD Call mmap 则call dma_buf_mmap_internal,实际上 dma_buf_mmap_internal 里会call dmabuf->ops->mmap,也就是ion的mmap
// 并且 file->private_data = priv(dmabuf)
file = anon_inode_getfile(bufname, &dma_buf_fops, dmabuf,
exp_info->flags);
file->f_mode |= FMODE_LSEEK;
dmabuf->file = file;
dma_buf_ref_init(dmabuf);
// 所以 dma_buf_export 也会将dmabuf ref + 1
dma_buf_ref_mod(dmabuf, 1);
}
3.static int32_t cam_mem_get_slot
概述:
获取未使用的slot,用于维护内部的buffer,类似于fd
4.static int cam_mem_util_map_hw_va
1)首先call "cam_mem_util_get_dma_dir"获取DMA的读写模式,对于camera只用到<CAM_MEM_FLAG_HW_READ_WRITE>模式
2)根据flag call 不同的map函数:
① CAM_MEM_FLAG_PROTECTED_MODE>: 会 call "int cam_smmu_map_stage2_iova",
② 非<CAM_MEM_FLAG_PROTECTED_MODE>: 会 call ”int cam_smmu_map_user_iova“
3) 对于cam_smmu_map_user_iova
cam_smmu_map_user_iova
// 此函数中判断了region_id != CAM_SMMU_REGION_SHARED (即flag != CAM_MEM_FLAG_HW_SHARED_ACCESS) 时 len_ptr = (size_t)0;
// 后面讲这里设置的意义
cam_smmu_map_iova_validate_params
// 通过handle计算idx,这里的handle可以理解为devices的handle,对于每个devices是唯一的
// 实际上这个handle是devices ioctl CAM_QUERY_CAP到 KMD 时通过call cam_smmu_get_handle获得的
// 例如:fd 模块在cam_fd_hw_mgr_init中call cam_smmu_get_handle
idx = GET_SMMU_TABLE_IDX(handle);
// 检查 fd 是不是在每个handle(即devices)维护的list中
cam_smmu_check_fd_in_list
// new cam_dma_buff_info *mapping_info保存dma-buf的信息并且加入到链表中
cam_smmu_map_buffer_and_add_to_list
// file->f_count + 1,dam_buf->ref + 1
dma_buf_get
// 这里有对 region_id 做分开处理:
// CAM_SMMU_REGION_SHARED: 会从gen_share_pool分配内存,然后call iommu_map_sg;注意我们在dam_buf_map_attachment中是call iommu_dma_map_sg
// CAM_SMMU_REGION_IO: 正常操作dam_buf_map_attachment,如果没有disable delay map,则会设置DMA_ATTR_DELAYED_UNMAP flag
cam_smmu_map_buffer_validate
4)cam_smmu_map_stage2_iova 与 cam_smmu_map_user_iova大同小异,只是会设置 DMA_ATTR_SKIP_CPU_SYNC flag,应该是PROTECTED_MODE不会有cpu介入访问
5.buffer handle
概述:
1)区别于mmu hanlde,buffer handle是通过 idx(区别于通过mmu handle计算得到的idx) 与 ion_fd做一个计算后得到的
2)CRM MEM 维护了一个 static struct cam_mem_table tbl 全局变量,结构体中有cam_mem_buf_queue bufq[CAM_MEM_BUFQ_MAX]
用来存放buffer handle, ion_fd, flag, mmu handle等, idx通过cam_mem_get_slot得到。所以我们通过buffer handle做逆向运算可以求出idx以及ion_fd
三.map 阶段
概述:
map所要做的只是call dma_buf_get获取dam_buf,然后call cam_mem_util_map_hw_va,间接call dma_buf_attach,以及dam_buf_map_attachment,f_count + 1,dam_buf + 1,最后加入到链表中。
这个链表是干什么的?
变量:iommu_cb_set.cb_info[idx].smmu_buf_list
作用是:每个idx(通过handle得到,可以理解为devices)都会维护一个链表,存放这个devices所import的buffer,当然一个buffer可能会被多个devices import,QC支持在alloc时就map,也可以先alloc后面在map,通过传入的num handle来控制
四.release 阶段
cam_mem_mgr_release
// 通过逆向计算buffer handel获取idx
cam_mem_util_unmap
// 从tal->bufq中拿出ion_fd,mmu handle等
cam_mem_util_unmap_hw_va
// 通过mmu handle,拿到 idx (当前device在数组中的位置),然后把ion_fd所在的list remove掉
// call dma_buf_unmap_attachment dma_buf_detach dma_buf_put等函数
// 要注意的是:alloc阶段分配一个idx,ion_fd,mmu handle放入bufq,map阶段也会分配一个idx,放入fd,mmu handle
// 所以我们在umap哪些devices是根据buf handle来的,buffer handle逆向运算得到哪个idx,就会umap哪些mmu handle代表的devices
cam_smmu_unmap_user_iova