Skytoby

深入理解Android Camera架构六-高通KMD

深入理解Android Camera架构六-高通KMD

一、概览

利用了V4L2可扩展这一特性,高通在相机驱动部分实现了自有的一套KMD框架,该框架通过V4L2标准方法在系统中创建设备节点,将控制接口直接暴露给UMD CSL进行访问,而其内部主要定义了一系列核心模块,包括CRM(Camera Request Manager),用于管理整个KMD的Session/Link的创建销毁以及Request的在子设备间的流转,该模块创建video0设备节点暴露关键接口给UMD,此外还包括了Sync模块,主要负责了UMD/KMD之间的数据同步与传输,创建video1设备节点暴露接口给UMD进行访问,除此之外,为了更精细化地控制一系列的硬件图像处理模块,包括ISP/IPE/Sensor等硬件模块,高通也分别为各自子模块创建了设备节点,进而暴露控制接口给UMD进行访问。

UMD (user mode driver) 和KMD( kernel mode driver)

其中主要目录如下:

  • cam_core/: 关于KMD核心函数的实现都放在这,主要包括了subdev、node、context的一些诸如创建/注册/销毁等标准方法。
  • cam_req_mgr/: CRM的具体实现,用于创建v4l2_device,用于管理所有的子设备,同时生成video设备节点,暴露控制接口给UMD,主要包括了Session/Link的行为管理以及Request的同步与分发,此外,还创建了media_device,用于暴露枚举接口给UMD来轮询查找整个KMD的子设备。
  • cam_sync/: 该部分主要实现了用于保持与UMD的图像数据的同步相关业务逻辑,由于该模块的特殊性,高通直接为其创建了一个单独的video设备节点,暴露了用于同步的一些控制接口。
  • cam_utils/: 一些共有方法的实现,包括debug方法集等
  • cam_smmu/: 高通自己实现了一套smmu api,供KMD使用
  • cam_lrme/: 低分辨率运动估计模块的驱动实现
  • cam_fd/: 人脸识别的驱动程序
  • cam_isp/: isp的驱动程序
  • cam_jpeg/: 编码器,可以通过该驱动完成jpeg的编码工作
  • cam_cdm/: camera data mover,数据移动器的驱动实现,主要用于解析由CSL传入的命令信息,其中包括了寄存器的设置以及图像数据的处理等。
  • cam_cpas/: 该模块主要用于CSL获取camera 平台驱动信息,IPE/BPS电源控制等
  • cam_icp/: image control processor ,图像处理控制器驱动实现
  • cam_sensor_module/: 类传感器的系列硬件模块
  • cam_actuator/: 对焦马达的驱动实现
  • cam_cci/: 实现了用于通讯的CCI接口,其中包括了I2C以及gpio的实现
  • cam_csiphy: 基于MIPI CSI接口的物理层驱动,用于传输图像数据
  • cam_sensor_io: 使用cam_cci,向上实现了控制sensor的IO接口
  • cam_sensor: sensor 的驱动实现
  • cam_sensor_util: sensor相关的公有方法的实现
  • cam_eeprom: eeprom设备的驱动实现
  • cam_ois : 光学防抖设备的驱动实现
  • cam_flash: 闪光灯设备的驱动实现

二、核心模块解析

正如之前介绍的那样,整个框架主要由三个部分组成,CRM/Camera Sync以及子模块,接下来我们以下图为例简单讲解下各自的关系:

在系统初始化时,CRM内部会创建一个v4l2_device结构体,用于管理所有的子设备,与此同时每一个子设备在注册的时候都会创建各自的v4l2_subdev挂载到该v4l2_device上面。此外,CRM会创建一个video0设备节点提供关键接口给CSL来进行访问,而每个子设备也会在系统中生成各自的v4l2-sbudev设备节点,提供接口给CSL进行更为精细化的控制。而其中的Cam Sync在初始化的过程中,也创建了一个v4l2_device设备,并且生成了video1节点给CSL进行控制。这个框架主要就是围绕这三个部分进行的,CRM用于管理Session/Link的创建,控制Request在各个子设备中的流转,子设备受CSL控制进行配置以及图像处理工作,而一旦图像处理完成便会将结果发送至Cam Sync模块,进上传至CSL中。

2.1 CRM(Camera Request Manager)

该模块本质上是一个软件模块,主要做了以下几个事情:

  • 接收来自CSL的Session/Link/Request请求,并且维护其在内核的状态。
  • 在不同pipeline delay的子模块间,同步每一个Request状态,并按照需要发送给每一个子设备。
  • 如果出现错误,负责上传至CSL。
  • 负责针对实时子模块的flush操作。

其中针对Session/Link/Request的请求便是通过之前创建的video设备节点将接口暴露给CSL,一旦接收到命令便开始进行处理,而命令主要有以下几个:

  • CAM_REQ_MGR_CREATE_SESSION/CAM_REQ_MGR_DESTROY_SESSION: 分别表示了Session的创建和销毁,该Session保持着与CamX-CHI的一一对应关系。
  • CAM_REQ_MGR_LINK/CAM_REQ_MGR_UNLINK: 分别表示了Link的创建和销毁动作,每一个Session可以包含多条Link,而每一个Link都连接着此次图像采集过程中所需要的子设备,CRM也是通过该Link来管理Request同步与分发的操作。
  • CAM_REQ_MGR_SCHED_REQ:一旦CSL开始下发Request的时候,便可以通过该命令告知KMD,而在KMD中,CRM会将此次Request存入Link中的in_q数组中,当子设备告知准备好了此次Request的处理后,便通知子设备进行配置并处理Request。
  • CAM_REQ_MGR_ALLOC_BUF/CAM_REQ_MGR_RELEASE_BUF: 图像缓冲区的申请与释放,CRM中使用cam_mem_table结构体来管理着申请的缓冲区。

一旦CRM接收了来自CSL的请求,便会在内部进行处理,而其中的一系列业务处理便会通过接下来的几个结构体来完成:

首先在初始化过程中,会去创建一个cam_req_mgr_device。

该结构体有以下几个主要的成员:

  • video: 存储着对应的video_device。
  • v4l2_dev: 保存着初始化过程中创建的v4l2_device。
  • subdev_nodes_created: 标志着从属于v4l2_device的子设备是否都成功创建了设备节点。
  • cam_eventq: v4l2文件描述结构体,其中维护着event事件队列。

之后会去创建一个cam_req_mgr_core_device,该结构体比较简单主要用于维护一个Session链表,在CSL下发创建Session的动作后,会将创建好的Session放入该量表中,同时通过crm_lock保持着业务处理中的同步。

一个Session可以包含很多条Link,其中变量num_links存储了Link数量,数组links存储着所有link,entry变量作为当前session的实体可以嵌入cam_req_mgr_core_device中的session链表中进行统一管理。

在CSL下发CAM_REQ_MGR_LINK命令的时候,会去创建cam_req_mgr_core_link。

该结构体比较复杂,接下来我们主要介绍下几个主要的变量:

  • link_hdl:作为该Link的句柄,区别于其它Link。
  • num_devs: 表示了该条Link上连接了多少个子设备。
  • max_delay: 表示了从属于该Link上的所有子设备具有的最大的Pipeline delay值。
  • l_dev: 存储着所有从属于该Link上的子设备,后续对于子设备的控制都是通过该数组来进行的。
  • req: 该成员主要用于管理下发的request。
  • state: 标志着该Link的状态,而Link状态主要包括了CAM_CRM_LINK_STATE_AVAILABLE/CAM_CRM_LINK_STATE_IDLE/CAM_CRM_LINK_STATE_READY/CAM_CRM_LINK_STATE_ERR几种状态。

创建完Link之后,会将其存入一个存储cam_req_mgr_core_link的全局变量g_links中进行统一管理。

而当下发CAM_REQ_MGR_SCHED_REQ命令的时候,会在内部进行解析,并且将其存入cam_req_mgr_core_link中的cam_req_mgr_req_data中等待后续的流转。

其中in_q变量主要用于存储request,而l_tbl用于记录pipeline delay的相关信息,而apply_data数组用于存储所有的等待处理的request信息。

2.2 Cam Sync

该模块本质上是一个软件模块,用于保持与UMD的图像数据的同步,主要利用了V4L2框架的event机制,由CSL进行事件的等待,一旦数据处理完毕,该模块便可以向上层发送事件,进而,通知CSL取出数据进行下一步处理,其中包括了几个主要ioctl的命令:

  • CAM_SYNC_CREATE: 一旦CSL部分需要创建一个用于同步的实体的时候便下发该命令,而在Cam Sync中,会将传入的信息存入内部的sync_table_row数组中进行管理,并且将生成的sync_obj传入上层。
  • CAM_SYNC_DESTROY: 销毁用于同步的sync实体。
  • CAM_SYNC_REGISTER_PAYLOAD: 通过该命令将一些同步的回调方法注册到Cam Sync中,这样一当数据处理完成,Cam Sync便可以由之前创建的sync_obj来找到相应的回调方法,进而调用该回调方法进行后续处理。
  • CAM_SYNC_DEREGISTER_PAYLOAD:释放之前注册的相关同步实体的信息,包括其回调方法。
  • CAM_SYNC_SIGNAL:该命令主要用于CamX-CHI中软件Node处理完数据之后,通知Cam Sync进行后续处理的目的。

其中包括了几个比较重要的结构体,首先在初始化过程中会去创建sync_device结构体,其主要的几个变量如下:

  • vdev: 创建的video_device。
  • v4l2_dev: 创建的v4l2_device设备。
  • sync_table: 用于存储sync_table_row的数组。
  • cam_sync_eventq: v4l2设备描述符结构体,其中维护着event事件队列。

其中最重要的时sync_table中存储的sync_table_row结构体,它代表了整个对应于CSL中的sync object,其中比较重要的变量含义如下:

  • sync_id:该sync object的唯一标识,同时该标识于CSL保持同步。
  • state: 代表了当前sync object的状态。
  • user_payload_list: 存储着该sync object所对应的来自UMD的payload,该payload在KMD中并没有被使用,仅仅存储与KMD中,一旦当前sync object被触发,便直接将其再次传入UMD中。

三、模块初始化

在系统启动初期,整个相机驱动中的各个模块都开始进行加载了,接下来我们依次介绍下:

首先是CRM的初始化,按照linux驱动模块的标准方法,会走到module_init宏声明的驱动结构体中的probe方法,这里是cam_req_mgr_probe方法。

[->kernel\msm-4.19\techpack\camera\drivers\cam_req_mgr\cam_req_mgr_dev.c]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
static int cam_req_mgr_probe(struct platform_device *pdev)
{
int rc;

rc = cam_v4l2_device_setup(&pdev->dev);
if (rc)
return rc;

rc = cam_media_device_setup(&pdev->dev);
if (rc)
goto media_setup_fail;

rc = cam_video_device_setup();
if (rc)
goto video_setup_fail;

g_dev.open_cnt = 0;
mutex_init(&g_dev.cam_lock);
spin_lock_init(&g_dev.cam_eventq_lock);
g_dev.subdev_nodes_created = false;
mutex_init(&g_dev.dev_lock);

rc = cam_req_mgr_util_init();
if (rc) {
CAM_ERR(CAM_CRM, "cam req mgr util init is failed");
goto req_mgr_util_fail;
}

rc = cam_req_mgr_core_device_init();
if (rc) {
CAM_ERR(CAM_CRM, "core device setup failed");
goto req_mgr_core_fail;
}

g_dev.state = true;

if (g_cam_req_mgr_timer_cachep == NULL) {
g_cam_req_mgr_timer_cachep = kmem_cache_create("crm_timer",
sizeof(struct cam_req_mgr_timer), 64,
SLAB_CONSISTENCY_CHECKS | SLAB_RED_ZONE |
SLAB_POISON | SLAB_STORE_USER, NULL);
if (!g_cam_req_mgr_timer_cachep)
CAM_ERR(CAM_CRM,
"Failed to create kmem_cache for crm_timer");
else
CAM_DBG(CAM_CRM, "Name : %s",
g_cam_req_mgr_timer_cachep->name);
}

return rc;

req_mgr_core_fail:
cam_req_mgr_util_deinit();
req_mgr_util_fail:
mutex_destroy(&g_dev.dev_lock);
mutex_destroy(&g_dev.cam_lock);
cam_video_device_cleanup();
video_setup_fail:
cam_media_device_cleanup();
media_setup_fail:
cam_v4l2_device_cleanup();
return rc;
}

在该方法中主要做了以下几个事情:

  • 调用cam_v4l2_device_setup方法,创建并向系统注册用于管理所有子设备的v4l2_device。
  • 调用cam_media_device_setup方法,创建并向系统注册media_device,并且创建了media设备节点,用于CSL枚举KMD中所有设备。
  • 调用cam_video_device_setup方法,创建video_device,并将v4l2_device嵌入到该结构体中,紧接着,使用标准的video注册方法,创建了video0设备节点,其中将g_cam_ioctl_ops方法集作为了video0的扩展方法,CSL下发的有关Session/Link/Request的诸多操作都是通过该方法集来进行分发的,最后将video0 media_entity中的function赋值CAM_VNODE_DEVICE_TYPE,这样CSL便可以通过该function判断出该节点便是CRM了。
  • 调用cam_req_mgr_util_init方法,其中初始化了一个cam_req_mgr_util_hdl_tbl,该结构体中存在一个handle数组,而每一个handle主要用于存储Session、Link以及各个子设备的相关信息,后期在整个图像采集的过程中,都是通过该结构体来找对应的操作实体,进而采取相应的动作。
  • 调用cam_req_mgr_core_device_init方法,该方法中,会去创建并初始化一个cam_req_mgr_core_device结构体,作为全局变量g_crm_core_dev存在于整个框架中,而该结构体中主要包含了用于存储创建的Session的session_head链表,以及用于保护Session临界资源的crm_lock。

其次,是Cam Sync的初始化,整个流程最终会走到驱动结构体中的probe方法中,这里是cam_sync_probe方法。

[->kernel\msm-4.19\techpack\camera\drivers\cam_sync\cam_sync.c]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
static int cam_sync_probe(struct platform_device *pdev)
{
int rc;
int idx;

sync_dev = kzalloc(sizeof(*sync_dev), GFP_KERNEL);
if (!sync_dev)
return -ENOMEM;

mutex_init(&sync_dev->table_lock);
spin_lock_init(&sync_dev->cam_sync_eventq_lock);

for (idx = 0; idx < CAM_SYNC_MAX_OBJS; idx++)
spin_lock_init(&sync_dev->row_spinlocks[idx]);

sync_dev->vdev = video_device_alloc();
if (!sync_dev->vdev) {
rc = -ENOMEM;
goto vdev_fail;
}

rc = cam_sync_media_controller_init(sync_dev, pdev);
if (rc < 0)
goto mcinit_fail;

sync_dev->vdev->v4l2_dev = &sync_dev->v4l2_dev;

rc = v4l2_device_register(&(pdev->dev), sync_dev->vdev->v4l2_dev);
if (rc < 0)
goto register_fail;

strlcpy(sync_dev->vdev->name, CAM_SYNC_NAME,
sizeof(sync_dev->vdev->name));
sync_dev->vdev->release = video_device_release;
sync_dev->vdev->fops = &cam_sync_v4l2_fops;
sync_dev->vdev->ioctl_ops = &g_cam_sync_ioctl_ops;
sync_dev->vdev->minor = -1;
sync_dev->vdev->vfl_type = VFL_TYPE_GRABBER;
rc = video_register_device(sync_dev->vdev,
VFL_TYPE_GRABBER, -1);
if (rc < 0)
goto v4l2_fail;

cam_sync_init_entity(sync_dev);
video_set_drvdata(sync_dev->vdev, sync_dev);
memset(&sync_dev->sync_table, 0, sizeof(sync_dev->sync_table));
memset(&sync_dev->bitmap, 0, sizeof(sync_dev->bitmap));
#ifndef CONFIG_CAM_MULTI

#else
memset(&sync_dev->sync_instance_map, -1,sizeof(sync_dev->sync_instance_map));
#endif
bitmap_zero(sync_dev->bitmap, CAM_SYNC_MAX_OBJS);

/*
* We treat zero as invalid handle, so we will keep the 0th bit set
* always
*/
set_bit(0, sync_dev->bitmap);

sync_dev->work_queue = alloc_workqueue(CAM_SYNC_WORKQUEUE_NAME,
WQ_HIGHPRI | WQ_UNBOUND, 1);

if (!sync_dev->work_queue) {
CAM_ERR(CAM_SYNC,
"Error: high priority work queue creation failed");
rc = -ENOMEM;
goto v4l2_fail;
}

trigger_cb_without_switch = false;
cam_sync_create_debugfs();
#ifdef CONFIG_MSM_GLOBAL_SYNX
cam_sync_register_synx_bind_ops();
#endif
return rc;

v4l2_fail:
v4l2_device_unregister(sync_dev->vdev->v4l2_dev);
register_fail:
cam_sync_media_controller_cleanup(sync_dev);
mcinit_fail:
video_device_release(sync_dev->vdev);
vdev_fail:
mutex_destroy(&sync_dev->table_lock);
kfree(sync_dev);
return rc;
}

在该方法中主要做了以下几个事情:

  • 创建sync_dev结构体,该结构中通过一个sync_table_row数组来维护着所有的sync objects。
  • 调用cam_sync_media_controller_init方法,用于创建media_deivce设备,并且创建了media设备节点,提供给CSL枚举子设备的能力。
  • 调用v4l2_device_register方法,创建并像系统注册一个v4l2_device结构体,其中用于ioctl的方法集是指向的g_cam_sync_ioctl_ops,一旦CSL有创建/注册sync objects需求的时候,便会最终走到该方法中,从而实现相应的功能。
  • 调用video_register_device方法,生成video1设备节点,暴露控制接口给CSL。
  • 调用cam_sync_init_entity方法,将video1中的meida_entity中function字段赋值CAM_SYNC_DEVICE_TYPE,这样在UMD就可以通过相应的media节点枚举出该模块。

以上两个模块都是具有独立的video设备节点的,但是对于子设备而言,由于代表着相应的硬件设备,同时需要嵌入到整个框架中才能正常运行,所以高通将其抽象成了v4l2_subdev来进行管理,这里主要还是介绍两个比较有代表性的子模块,ISP以及Sensor。

首先来看下ISP的初始化阶段,在其相应的probe方法cam_isp_dev_probe中。

[->kernel\msm-4.19\techpack\camera\drivers\cam_isp\cam_isp_dev.c]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
static int cam_isp_dev_probe(struct platform_device *pdev)
{
int rc = -1;
int i;
struct cam_hw_mgr_intf hw_mgr_intf;
struct cam_node *node;
int iommu_hdl = -1;

g_isp_dev.sd.internal_ops = &cam_isp_subdev_internal_ops;
/* Initialize the v4l2 subdevice first. (create cam_node) */
rc = cam_subdev_probe(&g_isp_dev.sd, pdev, CAM_ISP_DEV_NAME,
CAM_IFE_DEVICE_TYPE);
if (rc) {
CAM_ERR(CAM_ISP, "ISP cam_subdev_probe failed!");
goto err;
}
node = (struct cam_node *) g_isp_dev.sd.token;

memset(&hw_mgr_intf, 0, sizeof(hw_mgr_intf));
rc = cam_isp_hw_mgr_init(pdev->dev.of_node, &hw_mgr_intf, &iommu_hdl);
if (rc != 0) {
CAM_ERR(CAM_ISP, "Can not initialized ISP HW manager!");
goto unregister;
}

for (i = 0; i < CAM_CTX_MAX; i++) {
rc = cam_isp_context_init(&g_isp_dev.ctx_isp[i],
&g_isp_dev.ctx[i],
&node->crm_node_intf,
&node->hw_mgr_intf,
i);
if (rc) {
CAM_ERR(CAM_ISP, "ISP context init failed!");
goto unregister;
}
}

rc = cam_node_init(node, &hw_mgr_intf, g_isp_dev.ctx, CAM_CTX_MAX,
CAM_ISP_DEV_NAME);
if (rc) {
CAM_ERR(CAM_ISP, "ISP node init failed!");
goto unregister;
}

cam_smmu_set_client_page_fault_handler(iommu_hdl,
cam_isp_dev_iommu_fault_handler, node);

mutex_init(&g_isp_dev.isp_mutex);

CAM_INFO(CAM_ISP, "Camera ISP probe complete");

return 0;
unregister:
rc = cam_subdev_remove(&g_isp_dev.sd);
err:
return rc;
}

在该方法中做了如下几个事情:

  • 调用cam_subdev_probe方法,在该方法中,会去注册一个v4l2_subdev,并且将其挂载到CRM中的v4l2_device上,同时还创建了一个node,并且存入了v4l2_subdev中的token中,方便以后进行读取,另外,将方法集赋值为cam_subdev_ops,最后,创建了该v4l2_subdev内部的media_entity, 并且为其function字段赋值为CAM_IFE_DEVICE_TYPE,这样也方便在枚举子设备时分辨出当前节点代表着isp模块。
  • 调用cam_isp_hw_mgr_init方法,该方法用于初始化isp中的硬件模块。
  • 调用cam_isp_context_init方法,该方法中会初始化node,在node内部创建一定数量的context,用于后期的状态维护,并且为每一个context都配置了状态机,以及子状态机来用于管理整个isp模块。

其次来看下Sensor模块的初始化,在其相应的probe方法cam_sensor_driver_i2c_probe中。

[->kernel\msm-4.19\techpack\camera\drivers\cam_sensor_module\cam_sensor\cam_sensor_dev.c]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
static int32_t cam_sensor_driver_i2c_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
int32_t rc = 0;
int i = 0;
struct cam_sensor_ctrl_t *s_ctrl = NULL;
struct cam_hw_soc_info *soc_info = NULL;

if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
CAM_ERR(CAM_SENSOR,
"%s :i2c_check_functionality failed", client->name);
return -EFAULT;
}

/* Create sensor control structure */
s_ctrl = kzalloc(sizeof(*s_ctrl), GFP_KERNEL);
if (!s_ctrl)
return -ENOMEM;

i2c_set_clientdata(client, s_ctrl);

s_ctrl->io_master_info.client = client;
soc_info = &s_ctrl->soc_info;
soc_info->dev = &client->dev;
soc_info->dev_name = client->name;

/* Initialize sensor device type */
s_ctrl->of_node = client->dev.of_node;
s_ctrl->io_master_info.master_type = I2C_MASTER;
s_ctrl->is_probe_succeed = 0;
s_ctrl->open_cnt = 0;
s_ctrl->last_flush_req = 0;

rc = cam_sensor_parse_dt(s_ctrl);
if (rc < 0) {
CAM_ERR(CAM_SENSOR, "cam_sensor_parse_dt rc %d", rc);
goto free_s_ctrl;
}

rc = cam_sensor_init_subdev_params(s_ctrl);
if (rc)
goto free_s_ctrl;

s_ctrl->i2c_data.per_frame =
kzalloc(sizeof(struct i2c_settings_array) *
MAX_PER_FRAME_ARRAY, GFP_KERNEL);
if (s_ctrl->i2c_data.per_frame == NULL) {
rc = -ENOMEM;
goto unreg_subdev;
}
if (s_ctrl->soc_info.index == 3) {
s_ctrl->hdmi_in_power_status = false;
}
CAM_INFO(CAM_SENSOR, "cam_sensor_driver_i2c_probe, index: %d",
s_ctrl->soc_info.index);

INIT_LIST_HEAD(&(s_ctrl->i2c_data.init_settings.list_head));
INIT_LIST_HEAD(&(s_ctrl->i2c_data.config_settings.list_head));
INIT_LIST_HEAD(&(s_ctrl->i2c_data.streamon_settings.list_head));
INIT_LIST_HEAD(&(s_ctrl->i2c_data.streamoff_settings.list_head));
INIT_LIST_HEAD(&(s_ctrl->i2c_data.poweron_reg_settings.list_head));
INIT_LIST_HEAD(&(s_ctrl->i2c_data.poweroff_reg_settings.list_head));
INIT_LIST_HEAD(&(s_ctrl->i2c_data.read_settings.list_head));

for (i = 0; i < MAX_PER_FRAME_ARRAY; i++)
INIT_LIST_HEAD(&(s_ctrl->i2c_data.per_frame[i].list_head));

s_ctrl->bridge_intf.device_hdl = -1;
s_ctrl->bridge_intf.link_hdl = -1;
s_ctrl->bridge_intf.ops.get_dev_info = cam_sensor_publish_dev_info;
s_ctrl->bridge_intf.ops.link_setup = cam_sensor_establish_link;
s_ctrl->bridge_intf.ops.apply_req = cam_sensor_apply_request;
s_ctrl->bridge_intf.ops.flush_req = cam_sensor_flush_request;

s_ctrl->sensordata->power_info.dev = soc_info->dev;

return rc;
unreg_subdev:
cam_unregister_subdev(&(s_ctrl->v4l2_dev_str));
free_s_ctrl:
kfree(s_ctrl);
return rc;
}

主要做了以下几个事情:

  • 调用cam_sensor_parse_dt方法获取dts中定义的硬件信息。
  • 调用cam_sensor_init_subdev_params方法,该方法中会创建v4l2_subdev,然后挂载到CRM中的v4l2_device中,并且将sensor的私有方法集cam_sensor_internal_ops赋值给v4l2_subdev结构体中的ops,这样一旦操作相应的子设备节点,便最终会走到该方法集中,关于Sensor的一些操作便可以放到这个里面进行处理。最终将创建的v4l2_subdev中的media_entity中functon赋值为CAM_SENSOR_DEVICE_TYPE,方便CSL进行枚举Sensor设备。

通过上面的两个子设备的初始化代码梳理,不难发现,并没有进行设备节点的创建,那关于节点的创建动作发生在哪一个阶段呢? 为了解决这个疑问我们不得不先介绍下linux两个宏定义,一个是module_init,另一个便是late_initcall,两者都是为了声明初始化函数,但是执行时间有一个先后顺序,而late_initcall一般在所有module_init定义的方法都运行完成之后才会被运行,而针对所有子设备的节点的创建便是在这里完成的,在该方法中主要做了以下工作:

  • 调用cam_dev_mgr_create_subdev_nodes方法,而在该方法中会去调用v4l2标准方法v4l2_device_register_subdev_nodes来统一创建挂载在CRM中v4l2_device下的子设备节点。

至此,整个KMD框架便初始化完成,现在便静静等待CSL下发请求。

四、处理UMD CSL请求

整个KMD的初始化动作在linux内核启动的时候完成的,要稍早于CamX-CHI整个框架的初始化,所以在CamX-CHI进行初始化的时候,KMD框架的各个资源节点都已准备妥当,接下来我们就以CamX-CHI的初始化开始详细描述下整个KMD处理来自CSL请求的流程。

4.1 获取模块资源

在CamX-CHI初始化的时候,并不知道内核驱动部分是个什么状态,所以需要打开所有的media设备节点来枚举查询每一个驱动模块。

首先,打开media0,根据CAM_VNODE_DEVICE_TYPE信枚举并找到KMD框架中的CRM模块,并调用标准open方法来打开该设备,该动作最终会调用到cam_req_mgr_open方法,该方法主要做了以下几个工作:

  • 调用v4l2_fh_open方法,打开v4l2文件。
  • 调用cam_mem_mgr_init方法,初始化了内存管理模块,为之后的缓冲区的申请与释放做好准备。
  • 更新CRM状态为CAM_MEM_MGR_INITIALIZED。

在打开video0之后,会另起一个线程用于监听video的事件,这样就建立了与底层的双向通讯,而在此之前,需要通过ioctl方法将CSL需要监听的事件下发到驱动层,其中包括以下几个事件:

  • V4L_EVENT_CAM_REQ_MGR_SOF/V4L_EVENT_CAM_REQ_MGR_SOF_BOOT_TS: 一旦底层产生的SOF事件,便会向CSL发送该事件。
  • V4L_EVENT_CAM_REQ_MGR_ERROR: 一旦底层产生了错误,会向上抛出该事件。

一旦CSL获取了CRM模块信息成功之后,便开始枚举查找各个子模块了,其中会先去打开Sensor子设备,获取硬件信息,并且存入CSL中,然后再依次获取其它诸如IFE/IPE等硬件子模块并获取各自的信息,并存入CSL中,为之后的数据流转做好准备。

以上动作都完成之后,便开始查询Cam Sync模块了,基本流程与CRM大致相同:

  • 调用open方法打开video1,该方法最终会调用内核部分的cam_sync_open方法,而该方法中会调用v4l2_fh_open方法,从而打开v4l2文件。
  • 调用ioctl方法,订阅针对CAM_SYNC_V4L_EVENT_ID_CB_TRIG事件的监听 ,而对于该事件,一般是在子模块处理数据完成之后,会触发Cam Sync发送该事件至上层。

4.2 打开Session

好了,到这里,整个CamX初始化过程对于底层的请求都已经完成了,一旦用户打开相机应用之后,经过层层调用最终会去打开Session,进而调用video0的相应的ioctl方法传入CAM_REQ_MGR_CREATE_SESSION命令开始在驱动层打开Session的操作,而在驱动部分,会调用到CRM中的cam_req_mgr_create_session方法,在该方法中,会去创建一个用于代表session的handle,并将其存入全局静态变量hdl_tbl中。紧接着会去初始化该session中的link,其中该session管理着两个link数组,一个是用于初始化的links_init数组,一个是用于运行起来之后使用的links数组,这里的会首先初始化所有的links_init中的link,在使用的时候,会从该数组去取出一个空闲的link放入links中进行管理。

4.3 打开设备

在打开Session之后,随着Pipeline的创建,CamX会通过调用CSL中的相应Node的ioctl方法,下发CAM_ACQUIRE_DEV命令,来依次打开底层硬件设备,这里我们还是以ISP为例进行分析:

  • 一旦CSL调用了ISP设备节点的ioctl并且下发了CAM_ACQUIRE_DEV命令,并会通过层层调用一直调到__cam_node_handle_acquire_dev方法,在该方法中会首先去在ISP对应的node中的存储空闲context的队列中获取一个context。
  • 紧接着,调用了cam_context_handle_acquire_dev方法,来通过调用之前获取的context的对用的状态机方法集中的acquire_dev方法来打开isp设备,而在该方法中,会调用cam_create_device_hdl方法,将当前session handle以及isp操作方法集存入存入hdl_tbl中,之后crm会通过该方法集操作isp模块。之后会将当前isp context状态更新为CAM_CTX_ACQUIRED,并且初始化了用于管理request的active_req_list/wati_req_list/pending_req_list/pending_req_list/free_req_list链表,并且将初始化好req_list都挂载到free链表中。

除了ISP,会根据不同的图像采集需求,打开不同的子设备,基本流程差不多,都是通过下发CAM_ACQUIRE_DEV命令来完成的,这里我们便不进行赘述了。

在打开所有的子设备之后,紧接着需要将它们链接起来形成一个拓扑结构,方便各个子模块的管理。而这个动作还是通过调用CRM对应的ioctl下发CAM_REQ_MGR_LINK命令来完成的,该动作会经过层层调用,一直调用到CRM中的cam_req_mgr_link方法,接下来我们具体介绍下该方法的主要动作:

  • 调用__cam_req_mgr_reserve_link方法,在该方法中,首先会去从当前Session中的links_init数组中取出一个空闲的link,将其存入links数组,并且初始化其中的用于管理所有的request的in_q队列。
  • 调用cam_create_device_hdl,创建link对应的handle,并且存入hdl_tbl中。
  • 调用__cam_req_mgr_create_subdevs方法,初始化用于存储处于当前Link中的所有子设备。
  • 调用__cam_req_mgr_setup_link_info方法,该方法首先会去调用该link中的所有子设备的get_dev_info方法来获取设备信息,然后会去依次调用hdl_tbl中的链接在此Link上的所有子设备的setup_link方法,来连接子设备,同时也将CRM的一些回调方法通过该方式注入到子设备中,使其具有通知CRM的能力。

更新该Link状态为CAM_CRM_LINK_STATE_READY,并且创建了一个工作队列用于操作的异步处理。

4.5 开启数据流

一旦整个Link创建完成之后,便可以开启数据流了,该动作通过CSL控制每一个子设备来完成,这里还是以ISP为例进行分析:

由于在CamX初始化过程中已经存有打开的ISP文件句柄,所有通过调用起iotcl方法下发CAM_START_DEV命令来通知底层ISP模块开始进行数据流程传输,该命令首先会走到node,然后通过node下发到context,然后调用当前context的状态机对应的start_dev方法,而在该方法中,会首先更新当前context状态为CAM_CTX_ACTIVATED,然后通过操作底层硬件管理模块开始数据流的处理。

除了ISP,还有Sensor/FLash等模块也是需要开启数据流,为之后的Request的下发做好准备。

4.6 下发Request

一旦开启了整个数据处理流程,便可以接收Request请求了,而该动作依然还是通过CRM来完成,调用其ioctl方法,传入CRM_WORKQ_TASK_SCHED_REQ命令,该动作最终会到达内核CRM中的cam_req_mgr_schedule_request方法,而方法会将此次任务封装成task交由工作队列进行异步处理,而在工作队列中最终会调用其回调方法cam_req_mgr_process_sched_req,该方法主要做了如下工作:

  • 取出该request从属的link,并且将其中的in_q取出,找到一个空闲的slot,并将该slot便作为此次request在内核中的实体。
  • 更新该slot的状态为CRM_SLOT_STATUS_REQ_ADDED,并且将link中的open_req_cnt计数加1。

从上面的梳理不难看出,下发Request的操作并不复杂,其中并没有一个实际的Request下发到子设备的动作,所以很自然地会产生一个疑问,没有下发Request的动作,那CRM是如何来驱动整个Request的流转的呢? 所以接下来我们来进一步介绍下,整个Request的流转机制。

4.7 子设备处理数据

当CSL下发Request到KMD之后,便会进入到DRQ中进行流转,通过之前对于CamX的学习,想必大家应该已经熟悉了整个DRQ的运行机制,DRQ的每一个Node都会有一定依赖关系,一旦某个Node满足依赖关系之后,便会调用其ProcessRequest开始进行此次的Request处理,而该动作会将图像数据的以及配置信息打包,通过调用ioctl方法下发CAM_CONFIG_DEV到具体的子设备节点来将配置写入KMD子设备中,而一旦子设备收到此次请求之后,会调用当前context的状态机所对应的config_dev方法,接下来我们具体介绍下其中的所作的动作:

  • 将此次配置信息包括图像数据放入硬件管理模块中,但是此时并不进行处理,等待处理指示。
  • 将此次Request信息封装一下,通过调用之前setup_link传入的回调方法集中的add_req方法通知CRM,而在CRM中,会首先通过一系列的判断,如果条件满足了便将此次request对应的slot状态更新为CRM_REQ_STATE_READY,并将该request存入pending队列中。

由上面的分析,发现该过程中并没有进行实际的硬件配置或者处理,此时便需要等待SOF的事件,来驱动接下来的操作,而SOF事件是ISP来通知CRM的,具体流程如下:

  • EPOCH中断产生,触发回调方法__cam_isp_ctx_notify_sof_in_activated_state,在该方法中会封装事件,并且通过调用CRM中传入的回调方法notify_trigger将事件发送至CRM中。
  • 一旦CRM收取到SOF事件,便会去找到对应的满足要求的request,并且调用__cam_req_mgr_process_req方法通知相应的子设备进行配置。
  • 最后ISP会将此次SOF事件通过V4L2 event机制发送至UMD,通知到CSL中。

4.8 数据操作完成

当CamX中的各自Node完成了下发Request的操作之后,便会等待数据的处理完成,一旦完成便会触发buf_done中断,进而告知context,最终会调用cam_sync_signal方法来通知Cam Sync,而在Cam Sync中会通过子设备调用cam_sync_signal时传入的sync_id在sync_table_row找到相应的sync object,最终通过event机制,将此次处理完成的事件传入UMD CSL中,进而进行后续处理。

等到最后一个Node处理完成之后,此次Request的处理便宣告完成。

五、总结

之前QCamera & Mm-Camera架构采用的相机驱动比较简单,主要就承担了硬件的上下电以及读写寄存器的任务,并且控制方向都是从上到下,并且控制逻辑由UMD负责。但是随着时代的发展,相机硬件模块越发复杂,所以用于直接控制硬件的驱动层也需要承担更为复杂的控制任务,通过上面的分析,我们可以看到,高通重新设计了一套优秀的KMD框架,在其中加入了更多复杂的控制逻辑,以达到精细化控制底层硬件模块的目的,其中比较重要的是CRM对于子设备的横向控制,这样的好处很明显,降低了UMD控制驱动的难度,UMD只需要将请求通过V4L2框架中的设备节点下发至KMD中,之后便由KMD中的CRM来统一管理,适时地将请求下发给各个子设备,进而控制着底层硬件模块。