tags : msm8996 camera
简单记录一下vfe的工作过程,主要针对内存的分配之快进行分析,具体的参数设置暂时不做具体分析。所有分析都基于msm8996平台,在最新的845甚至855平台上这个地方改动看上去还是比较大的,目前暂时没有相关平台的源码,拿到之后再做分析。
相关文件:cam_smmu_api.c、arm_smmu.c
维护所有跟camera相关的设备的smmu,如vfe、cpp、fd等,并提供这些设备对smmu操作的api。
所有camera相关设备的smmu信息通过全局变量iommu_cb_set
保存,该变量的内容在“qcom,msm-cam-smmu”驱动初始化时根据dts的配置填写。
static int cam_smmu_setup_cb(struct cam_context_bank_info *cb,
struct device *dev)
{
……
/* 设置scratch_map,后面smmu attach时会用到 */
rc = cam_smmu_init_scratch_map(&cb->scratch_map,
SCRATCH_ALLOC_START,
SCRATCH_ALLOC_END - SCRATCH_ALLOC_START,
0);
/* 想smmu申请一个domain */
/* create a virtual mapping */
cb->mapping = arm_iommu_create_mapping(msm_iommu_get_bus(dev),
cb->va_start, cb->va_len);
……
/*
* Set the domain attributes
* disable L2 redirect since it decreases
* performance
*/
if (iommu_domain_set_attr(cb->mapping->domain,
DOMAIN_ATTR_COHERENT_HTW_DISABLE,
&disable_htw)) {
……
}
……
}
其中会需要调用mapping->domain = iommu_domain_alloc(bus);
这个地方与arm_smmu_init()
有关。
arm-smmu.c
static int __init arm_smmu_init(void)
{
struct device_node *np;
int ret;
/*
* Play nice with systems that don't have an ARM SMMU by checking that
* an ARM SMMU exists in the system before proceeding with the driver
* and IOMMU bus operation registration.
*/
np = of_find_matching_node(NULL, arm_smmu_of_match);
if (!np)
return 0;
of_node_put(np);
ret = platform_driver_register(&arm_smmu_driver);
if (ret)
return ret;
/* Oh, for a proper bus abstraction */
if (!iommu_present(&platform_bus_type))
bus_set_iommu(&platform_bus_type, &arm_smmu_ops);
#ifdef CONFIG_ARM_AMBA
if (!iommu_present(&amba_bustype))
bus_set_iommu(&amba_bustype, &arm_smmu_ops);
#endif
#ifdef CONFIG_PCI
if (!iommu_present(&pci_bus_type))
bus_set_iommu(&pci_bus_type, &arm_smmu_ops);
#endif
return 0;
}
这里会为bus填写相应的iommu操作:bus->iommu_ops = ops;
,该操作在后面其他设备创建iommu domain时会用到。
相关文件:msm_isp.c、msm_isp47.c、msm_isp_util.c、msm_isp_axi_util.c
相关函数:int vfe_hw_probe(struct platform_device *pdev)
hw_info
的数据来自msm_isp47.c
中,定义如下:
static const struct of_device_id msm_vfe47_dt_match[] = {
{
.compatible = "qcom,vfe47",
.data = &vfe47_hw_info,
},
{}
};
vfe47_hw_info
结构体中定义了该vfe的所有操作方法以及硬件约束。
在create vfe的过程中会调用get_platform_data()
函数来获取平台资源,该函数的实现是通过hw_info
赋值时所获得的vfe47中的platform操作。
该操作主要是从dts中读取平台相关的配置,例如:
vfe_dev->vfe_base
vfe_dev->vfe_vbif_base
vfe_dev->vfe_irq
vfe_dev->vfe_base_size
vfe_dev->vfe_vbif_base_size
vfe_dev->vfe_hw_limit
除此之外还调用了get_regulators()
和get_clks()
函数,这两个函数也是isp_vfe47.c
中实现的。
get_regulators()
查找一个regulators设备,并且获取一个引用。查找的内容被hw_info
和dts指明了。这个东西具体怎么使用的,暂时没有时间去深究,应该是跟电源相关。get_clks()
从dts中读取平台上所有的clk设置。
接着,该函数内还会注册vfe的平台中断,这个中断很关键,每当vfe处理完一帧后都会rise这个中断,然后产生一个tasklet_cmd
事件,该事件通过软中断(?)的方式触发通过tasklet_init()
方式注册的中断响应。这样转换一下的原因目前猜测是为了快速退出中断响应,就像一般处理中断的时候在收到中断后会释放一个信号量,然后直接退出中断,实际的中断处理在其他的task里面。
最后调用了msm_isp_init_bandwidth_mgr()
函数。该函想总线注册了isp_init、isp_ping、isp_pong这三个vector,具体为什么要这么做,关于bus这块还没时间研究……
v4l2这块如果有需要,今后单独做一个笔记,关于subdev这块主要是两个ops,一个是fops,一个是subdev_ops,其实fops就是一张皮,主要响应文件操作,除了对VIDIOC_DQEVENT
事件的响应是由v4l2框架实现的外,其余所有的事件响应都是调用的subdev_ops中的对应函数,所以真正事件响应者是subdev_ops,不同subdev需要实现的差异化功能也是靠subdev_ops来实现的。
相关源码:
int vfe_hw_probe(struct platform_device *pdev)
{
……
/* 所有vfe公用buf_mgr的配置 */
vfe_dev->buf_mgr = &vfe_buf_mgr;
/* 获取vfe_vb2_ops的数据,相关内容见msm_vb2.c文件中的msm_vb2_request_cb()函数 */
v4l2_subdev_notify(&vfe_dev->subdev.sd,
MSM_SD_NOTIFY_REQ_CB, &vfe_vb2_ops);
/* 其实是初始化vfe_buf_mgr结构体,如果已经初始化了就直接返回,其中vfe_buf_mgr中的vb2_ops
* 来自 vfe_vb2_ops,ops来自isp_buf_ops
*/
rc = msm_isp_create_isp_buf_mgr(vfe_dev->buf_mgr,
&vfe_vb2_ops, &pdev->dev,
vfe_dev->hw_info->axi_hw_info->scratch_buf_range);
if (rc < 0) {
pr_err("%s: Unable to create buffer manager\n", __func__);
rc = -EINVAL;
goto probe_fail3;
}
msm_isp_enable_debugfs(vfe_dev, msm_isp_bw_request_history);
vfe_dev->buf_mgr->num_iommu_secure_ctx =
vfe_dev->hw_info->num_iommu_secure_ctx;
/* 设置vfe_buf_mgr为已经初始化的状态,避免不同的vfe重复初始化vfe_buf_mgr的值,因为所有vfe共享这里 */
vfe_dev->buf_mgr->init_done = 1;
vfe_dev->vfe_open_cnt = 0;
……
}
相关文件:msm_isp_util.c,msm_isp.c
相关函数:msm_isp_open_node()
在把vfe注册成为v4l2的subdev时提供的.core
中的.open
方法,相关结构体变量:msm_vfe_v4l2_subdev_ops
。
msm_isp_open_node()
主要是对vfe做一些初始化配置,比如初始化vfe寄存器、vfe参数以及buf等。其中:
vfe_dev->buf_mgr->ops->buf_mgr_init(vfe_dev->buf_mgr,
"msm_isp");
最终调用的是msm_isp_init_isp_buf_mgr()
,对buf进行初始化,该函数在probe vfe设备时通过调用msm_isp_create_isp_buf_mgr()
来进行填充。msm_isp_init_isp_buf_mgr()
函数里面主要获取了iommu handle:rc = cam_smmu_get_handle("vfe", &buf_mgr->iommu_hdl);
,最终通过cam_smmu_api.c中的cam_smmu_create_add_handle_in_table()
函数,根据该vfe的name,来寻找cam smmu初始化时相同名称的设备的编号,通过该编号最终生成iommu handle。
int msm_isp_smmu_attach(struct msm_isp_buf_mgr *buf_mgr,
void *arg)
{
……
rc = cam_smmu_ops(buf_mgr->iommu_hdl,
CAM_SMMU_ATTACH);
……
rc = msm_isp_buf_get_scratch(buf_mgr);
……
}
attach过程中,最核心两句刷就是上面列出来的,一个是cam_smmu_ops()
,该函数最终调用ret = domain->ops->attach_dev(domain, dev)
(arm_smmu.c中的 arm_smmu_attach_dev()
)。这里这句话的作用感觉是启动或者说使能一个设备的iommu映射?虽然网上到处说smmu需要attach到dev上,吧啦吧啦的,但是没有一个地方具体解释过这个函数到底想干嘛,为什么要做这些操作,这些操作对后面产生什么影响以及这些操作需要前期提供哪些铺垫等等……现在暂时没时间彻底去研究这块,这个问题暂时搁置。
一个重要补充:
关于cam_smmu_ops()
函数,里面不仅仅执行了attach,还有一个很重要的作用,就是给dev设置了dma opt:
dma-mapping.c
int arm_iommu_attach_device(struct device *dev,
struct dma_iommu_mapping *mapping)
{
……
/* attach dev */
err = iommu_attach_device(mapping->domain, dev);
……
/* 设置mapping,mapping的值为该cam smmu初始化时调用cam_smmu_setup_cb()生成的 */
dev->archdata.mapping = mapping;
/* 设置dma ops,这个在后面进行dma map相关操作时都会使用,很重要,所以在这里特别记录 */
set_dma_ops(dev, &iommu_ops);
pr_debug("Attached IOMMU controller to %s device.\n", dev_name(dev));
return 0;
}
arm-smmu.c
static int arm_smmu_attach_dev(struct iommu_domain *domain, struct device *dev)
{
……
smmu = find_smmu_for_device(dev);
……
if (!smmu->attach_count) {
/*
* We need an extra power vote if we can't retain register
* settings across a power collapse, or if this is an
* atomic domain (since atomic domains can't sleep during
* unmap, so regulators already need to be on to enable tlb
* invalidation). The result (due to regulator
* refcounting) is that we never disable regulators while a
* client is attached in these cases.
*/
if (!(smmu->options & ARM_SMMU_OPT_REGISTER_SAVE) ||
atomic_ctx) {
ret = arm_smmu_enable_regulators(smmu);
if (ret)
goto err_unlock;
}
ret = arm_smmu_enable_clocks(smmu);
if (ret)
goto err_disable_regulators;
arm_smmu_device_reset(smmu);
arm_smmu_impl_def_programming(smmu);
} else {
ret = arm_smmu_enable_clocks(smmu);
if (ret)
goto err_unlock;
}
smmu->attach_count++;
if (arm_smmu_is_static_cb(smmu)) {
ret = arm_smmu_populate_cb(smmu, smmu_domain, dev);
if (ret) {
dev_err(dev, "Failed to get valid context bank\n");
goto err_disable_clocks;
}
smmu_domain->slave_side_secure = true;
}
/* Ensure that the domain is finalised */
/* 这里会初始化smmu相关配置,包括最终的map sg的操作等 */
ret = arm_smmu_init_domain_context(domain, smmu);
……
/* Looks ok, so add the device to the domain */
cfg = find_smmu_master_cfg(dev);
……
ret = arm_smmu_domain_add_master(smmu_domain, cfg);
……
}
另一个函数:msm_isp_buf_get_scratch(buf_mgr)
,其主要作用是申请一段地址,并且把他与vfe的一段地址通过iommu进行映射,其实现函数如下:
static int cam_smmu_alloc_scratch_buffer_add_to_list(int idx,
size_t virt_len,
size_t phys_len,
unsigned int iommu_dir,
dma_addr_t *virt_addr)
{
……
/* This table will go inside the 'mapping' structure
* where it will be held until put_scratch_buffer is called
*/
table = kzalloc(sizeof(struct sg_table), GFP_KERNEL);
……
rc = sg_alloc_table(table, nents, GFP_KERNEL);
……
page = alloc_pages(GFP_KERNEL, get_order(phys_len));
……
/* Now we create the sg list */
for_each_sg(table->sgl, sg, table->nents, i)
sg_set_page(sg, page, phys_len, 0);
……
rc = cam_smmu_alloc_scratch_va(&iommu_cb_set.cb_info[idx].scratch_map,
virt_len, &iova);
……
if (iommu_map_sg(domain,
iova,
table->sgl,
table->nents,
iommu_dir) != virt_len) {
pr_err("iommu_map_sg() failed");
goto err_iommu_map;
}
/* Now update our mapping information within the cb_set struct */
mapping_info = kzalloc(sizeof(struct cam_dma_buff_info), GFP_KERNEL);
……
mapping_info->ion_fd = 0xDEADBEEF;
mapping_info->buf = NULL;
mapping_info->attach = NULL;
mapping_info->table = table;
mapping_info->paddr = iova;
mapping_info->len = virt_len;
mapping_info->iommu_dir = iommu_dir;
mapping_info->ref_count = 1;
mapping_info->phys_len = phys_len;
……
list_add(&mapping_info->list, &iommu_cb_set.cb_info[idx].smmu_buf_list);
*virt_addr = (dma_addr_t)iova;
……
}
这里通过VIDIOC_MSM_ISP_INPUT_CFG://199
来配置输入参数,例如:输入的clock,format,width等等。
通过VIDIOC_MSM_ISP_REQUEST_STREAM://196
来真正创建stream。这里面有几个关键函数:
msm_isp_axi_create_stream()
:根据user下发的arg,填写vfe_dev->axi_data->stream_info[i]
的相应参数。msm_isp_validate_axi_request()
:根据user下发的arg,填写vfe_dev->axi_data->stream_info[i]
的相应参数,其中包括stream_info->plane_cfg[i]
信息,这个东西在后面pingpong address的时候有用。msm_isp_axi_reserve_wm()
:设置wm信息,这里稍微记录一下:
void msm_isp_axi_reserve_wm(struct vfe_device *vfe_dev,
struct msm_vfe_axi_shared_data *axi_data,
struct msm_vfe_axi_stream *stream_info)
{
int i, j;
for (i = 0; i < stream_info->num_planes; i++) {
for (j = 0; j < axi_data->hw_info->num_wm; j++) {
if (!axi_data->free_wm[j]) {
axi_data->free_wm[j] =
stream_info->stream_handle;
axi_data->wm_image_size[j] =
msm_isp_axi_get_plane_size(
stream_info, i);
axi_data->num_used_wm++;
break;
}
}
……
}
}
首先,wm是啥?这里暂时猜测是Windows Media的缩写,和UB相关(所以最大值是7个?),这里仅为猜测。
其次,这个函数就是在axi中找出planes个数的空闲wm给这个stream使用。
最有,由于后面经常出现wm,所以这里稍微记录一笔。
msm_isp_calculate_framedrop()
:计算帧间隔的配置msm_isp_cfg_stream_scratch()
:initialize the WM ping pong with scratch buffer
1、ION_IOC_ALLOC
通过ion申请一块内存,返回handle。
2、ION_IOC_SHARE
创建一个share dma buf,并返回buf的fd。
这里主要做了两件事:
1、通过msm_isp_get_buf_handle()
函数找到一个没用的bufq,并生成一个bufq的handle
2、初始化该bufq,并根据输入的buf num,在该bufq下面创建相应的buf(msm_isp_buffer
)
该操作的主要作用概括来说就是把之前通过ion申请到的内存给bufq里面的buf使用,并且把这些buf放入准备就绪的buf队列中。
把buf放入准备就绪的buf队列中其实是把bufq
下的buf[x]->list
放入bufq->head
的链表中,比较简单,这里主要记录一下prepare的过程。
static int msm_isp_prepare_v4l2_buf(struct msm_isp_buf_mgr *buf_mgr,
struct msm_isp_buffer *buf_info,
struct msm_isp_qbuf_buffer *qbuf_buf,
uint32_t stream_id)
{
……
for (i = 0; i < qbuf_buf->num_planes; i++) {
mapped_info = &buf_info->mapped_info[i];
mapped_info->buf_fd = qbuf_buf->planes[i].addr;
……
/* buf_mgr->iommu_hdl是在vfe open时通过调用msm_isp_init_isp_buf_mgr()函数获取的”VFE“smmu的
* 相关信息。
* */
ret = cam_smmu_get_phy_addr(buf_mgr->iommu_hdl,
mapped_info->buf_fd,
CAM_SMMU_MAP_RW,
&(mapped_info->paddr),
&(mapped_info->len));
……
mapped_info->paddr += accu_length;
accu_length += qbuf_buf->planes[i].length;
……
}
buf_info->num_planes = qbuf_buf->num_planes;
……
}
static int cam_smmu_map_buffer_and_add_to_list(int idx, int ion_fd,
enum dma_data_direction dma_dir, dma_addr_t *paddr_ptr,
size_t *len_ptr)
{
……
/* 创建一个struct dma_buf_attachment实例,内部的dmabuf->ops->attach没有被调用,因为该指针为NULL */
attach = dma_buf_attach(buf, iommu_cb_set.cb_info[idx].dev);
……
/* 把dma buf的scatter sg table拷贝了一份,并将拷贝的那一份地址赋给table */
table = dma_buf_map_attachment(attach, dma_dir);
……
/* 这里感觉是把一个外设的虚拟地址(iova)给映射到了table里面的物理地址上,这里根据前面的分析
* 应该是调用的dma-mapping.c文件的arm_iommu_map_sg()函数。
*/
rc = msm_dma_map_sg_lazy(iommu_cb_set.cb_info[idx].dev, table->sgl,
table->nents, dma_dir, buf);
……
/* fill up mapping_info */
mapping_info = kzalloc(sizeof(struct cam_dma_buff_info), GFP_KERNEL);
if (!mapping_info) {
pr_err("Error: No enough space!\n");
rc = -ENOSPC;
goto err_unmap_sg;
}
mapping_info->ion_fd = ion_fd;
mapping_info->buf = buf;
mapping_info->attach = attach;
mapping_info->table = table;
/* 那么这里最终的值其实是外设所看到的虚拟地址,这块虚拟地址已经被map到dma buf所占用的物理内存上了 */
mapping_info->paddr = sg_dma_address(table->sgl);
mapping_info->len = (size_t)sg_dma_len(table->sgl);
mapping_info->dir = dma_dir;
mapping_info->ref_count = 1;
/* return paddr and len to client */
*paddr_ptr = sg_dma_address(table->sgl);
*len_ptr = (size_t)sg_dma_len(table->sgl);
……
/* add to the list */
list_add(&mapping_info->list, &iommu_cb_set.cb_info[idx].smmu_buf_list);
return 0;
……
}
user发送VIDIOC_MSM_ISP_UPDATE_STREAM://205消息,其中update_cmd->update_type
为6(UPDATE_STREAM_ADD_BUFQ)。最终是填写的axi_data->stream_info[HANDLE_TO_IDX(update_info->stream_handle)].bufq_handle[bufq_id]
的内容,使bufq与axi中记录的stream绑定。
user发送VIDIOC_MSM_ISP_CFG_STREAM://197消息。
static int msm_isp_start_axi_stream(struct vfe_device *vfe_dev,
struct msm_vfe_axi_stream_cfg_cmd *stream_cfg_cmd,
enum msm_isp_camif_update_state camif_update)
{
……
for (i = 0; i < stream_cfg_cmd->num_streams; i++) {
……
msm_isp_calculate_bandwidth(axi_data, stream_info);
msm_isp_get_stream_wm_mask(stream_info, &wm_reload_mask);
spin_lock_irqsave(&stream_info->lock, flags);
msm_isp_reset_framedrop(vfe_dev, stream_info);
/* 这里是配置stream的写入buf的地址,这个地址应该是vfe看到的虚拟地址
* 该虚拟地址在prepare的时候与通过ion申请的dma buf的物理地址进行了映射
*/
rc = msm_isp_init_stream_ping_pong_reg(vfe_dev, stream_info);
……
spin_unlock_irqrestore(&stream_info->lock, flags);
if (stream_info->num_planes > 1) {
vfe_dev->hw_info->vfe_ops.axi_ops.
cfg_comp_mask(vfe_dev, stream_info);
} else {
vfe_dev->hw_info->vfe_ops.axi_ops.
cfg_wm_irq_mask(vfe_dev, stream_info);
}
stream_info->state = START_PENDING;
……
if (src_state) {
src_mask |= (1 << SRC_TO_INTF(stream_info->stream_src));
wait_for_complete = 1;
} else {
……
/*Configure AXI start bits to start immediately*/
msm_isp_axi_stream_enable_cfg(vfe_dev, stream_info, 0);
stream_info->state = ACTIVE;
vfe_dev->hw_info->vfe_ops.core_ops.reg_update(vfe_dev,
SRC_TO_INTF(stream_info->stream_src));
/*
* Active bit is set in enable_camif for PIX.
* For RDI, set it here
*/
if (SRC_TO_INTF(stream_info->stream_src) >= VFE_RAW_0 &&
SRC_TO_INTF(stream_info->stream_src) <
VFE_SRC_MAX) {
/* Incase PIX and RDI streams are part of same
* session, this will ensure RDI stream will
* have same frame id as of PIX stream
*/
if (stream_cfg_cmd->sync_frame_id_src)
vfe_dev->axi_data.src_info[SRC_TO_INTF(
stream_info->stream_src)].frame_id =
vfe_dev->axi_data.src_info[VFE_PIX_0]
.frame_id;
else
vfe_dev->axi_data.src_info[SRC_TO_INTF(
stream_info->stream_src)].frame_id = 0;
vfe_dev->axi_data.src_info[SRC_TO_INTF(
stream_info->stream_src)].active = 1;
}
}
}
msm_isp_update_stream_bandwidth(vfe_dev, stream_cfg_cmd->hw_state);
vfe_dev->hw_info->vfe_ops.axi_ops.reload_wm(vfe_dev,
vfe_dev->vfe_base, wm_reload_mask);
msm_isp_update_camif_output_count(vfe_dev, stream_cfg_cmd);
for (i = 0; i < VFE_SRC_MAX; i++) {
if ((vfe_dev->axi_data.src_info[i].pix_stream_count ||
vfe_dev->axi_data.src_info[i].raw_stream_count) &&
!vfe_dev->axi_data.src_info[i].flag) {
/*Configure UB*/
vfe_dev->hw_info->vfe_ops.axi_ops.cfg_ub(vfe_dev, i);
/*when start reset overflow state*/
atomic_set(&vfe_dev->error_info.overflow_state,
NO_OVERFLOW);
vfe_dev->axi_data.src_info[i].flag = 1;
}
}
if (camif_update == ENABLE_CAMIF) {
vfe_dev->hw_info->vfe_ops.core_ops.
update_camif_state(vfe_dev, camif_update);
vfe_dev->axi_data.camif_state = CAMIF_ENABLE;
vfe_dev->common_data->dual_vfe_res->epoch_sync_mask = 0;
}
……
}
##随手记录
alloc ion buffer:
ion.c
ion_ioctl(ION_IOC_ALLOC)
__ion_alloc()
ion_buffer_create()
heap->ops->allocate()
heap->ops->map_dma()
for_each_sg()...
share ion buffer:
ion.c
ion_ioctl(ION_IOC_SHARE)
ion_share_dma_buf_fd()
ion_share_dma_buf()
dma-buf.c
dma_buf_export_named()
msm ion init:
msm_ion.c
msm_ion_probe()
msm_ion_heap_create()
ion_cma_heap.c
ion_cma_heap_create()
ion_cma_allocate()
memory to vfe:
msm_isp_util.c
msm_isp_ioctl_unlocked(VIDIOC_MSM_ISP_CFG_STREAM)
msm_isp_axi_util.c
msm_isp_cfg_axi_stream()
msm_isp_start_axi_stream()
msm_isp47.c
msm_vfe47_update_ping_pong_addr()
cycle update ping pong addr:
msm_isp.c
vfe_hw_probe()
rc = vfe_dev->hw_info->vfe_ops.platform_ops.get_platform_data(vfe_dev);
tasklet_init(&vfe_dev->vfe_tasklet, msm_isp_do_tasklet, (unsigned long)vfe_dev);
msm_isp47.c
msm_vfe47_get_platform_data()
rc = msm_camera_register_irq(vfe_dev->pdev, vfe_dev->vfe_irq,
msm_isp_process_irq, IRQF_TRIGGER_RISING, "vfe", vfe_dev);
msm_isp_util.c
msm_isp_process_irq()
msm_isp_enqueue_tasklet_cmd()
msm_isp_do_tasklet()
irq_ops->process_axi_irq(vfe_dev, irq_status0, irq_status1, pingpong_status, &ts);
msm_isp_axi_util.c
msm_isp_process_axi_irq()
dma_buf->priv
== ion_buffer