TCSSM源码阅读

0x01 启动度量模块

1.1 tckx_tcf_bmeasure.h

  • #define MAX_BOOT_HASH_VERSION_NUMBER 8: 最大boot hash 版本数,例如 BIOS、bootloader、kernel 升级后 hash 会变,为了兼容多个合法版本,基准库里一个条目可以保存多个 hash

  • #define MAX_BOOT_EXTERN_SIZE 255

  • #define MAX_BOOT_REFERENCE_ITEM_SIZE (sizeof (struct boot_ref_item) + DEFAULT_HASH_SIZE * MAX_BOOT_HASH_VERSION_NUMBER + MAX_BOOT_EXTERN_SIZE + MAX_PATH_LENGTH)

    定义启动基准项最多占多大内存,

    1
    sizeof(struct boot_ref_item)
    • 固定头部结构大小
    1
    DEFAULT_HASH_SIZE * MAX_BOOT_HASH_VERSION_NUMBER
    • hash 数组最大空间
    1
    MAX_BOOT_EXTERN_SIZE
    • 扩展字段最大空间
    1
    MAX_PATH_LENGTH
    • 名称或路径字段最大空间

定义了存储启动度量存放PCR和扩展PCR的结构体

1
2
3
4
5
6
7
8
9
10
11
struct boot_ref_item_user{
int hash_length; //单个hash长度
int hash_number; //总hash数量
int stage; //启动阶段
int is_control;//bool 控制策略,语义上为bool值,说明阻断启动或报警
int is_enable;//bool 是否启用
char *hash;//这是一个连续缓冲区,总长度为hash_length *hash_number,可能存了多个 hash,挨着排放
char *name; //基准项名称
int extend_size; //扩展区长度
char *extend_buffer; //扩展数据指针
};

定义启动度量结果

1
2
3
4
5
6
7
8
struct boot_measure_record_user{
int hash_length; //最终hash长度
int stage; //启动阶段
uint32_t result; //结果
uint64_t measure_time; //度量时间
char hash[DEFAULT_HASH_SIZE]; //单次测量结果
char *name; //对应对象名称
};

定义了各个启动度量函数,下一节详细说明

1.2 tckx_tcf_bmeasure.c

1.2.1 tckx_tcf_prepare_update_boot_measure_references

此函数作用为整理并生成一份启动度量基准值的更新数据包,函数流程:
mermaid-diagram

具体来说的话

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
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
int tckx_tcf_prepare_update_boot_measure_references(
const struct boot_ref_item_user *references,int num,
unsigned char *tpcm_id,int tpcm_id_length,
int action, uint64_t replay_counter,
struct boot_references_update **obuffer,int *olen)
{
int i = 0;
int ret = 0;
int ref_opt = 0;
int name_length = 0;
int item_size = 0;
int hash_total_length = 0;
struct boot_ref_item *ref = NULL;
struct boot_references_update *ref_update = NULL;
//防错处理:
if ((num && !references) || !tpcm_id || !olen) return TCDEF_TCF_ERROR_INVALID_PARAM;
if (tpcm_id_length > MAX_TPCM_ID_SIZE){
tckx_error ("tpcm_id_length is too long (%d > %d)\n", tpcm_id_length, MAX_TPCM_ID_SIZE);
return TCDEF_TCF_ERROR_INPUT_EXCEEDED;
}//end
//申请存放打包后数据的内存
if (NULL == (ref_update = tcutils_malloc (sizeof (struct boot_references_update) + MAX_BOOT_REFERENCE_ITEM_SIZE * num))){
tckx_error ("No mem for ref update data!\n");
return TCDEF_TCF_ERROR_NO_MEMORY;
}//end
memset (ref_update, 0, sizeof (struct boot_references_update) + MAX_BOOT_REFERENCE_ITEM_SIZE * num);
for (i = 0; i < num; i ++){
ref = (struct boot_ref_item *)(ref_update->data + ref_opt);
//针对当前数据进行合法性检查
if (!references[i].name || !references[i].hash){
tcutils_free (ref_update);
return TCDEF_TCF_ERROR_INVALID_PARAM;
}
if ((references[i].hash_number < 0) || (references[i].hash_number > MAX_BOOT_HASH_VERSION_NUMBER)){
tckx_error ("Invalid hash version number: %d\n", references[i].hash_number);
tcutils_free (ref_update);
return TCDEF_TCF_ERROR_INVALID_PARAM;
}
if (references[i].hash_length != DEFAULT_HASH_SIZE){
tckx_error ("Invalid hash_length: %d\n", references[i].hash_length);
tcutils_free (ref_update);
return TCDEF_TCF_ERROR_INVALID_PARAM;
}
if (references[i].extend_size && !references[i].extend_buffer){
tckx_error ("extend_size: %d, extend_buffer: %p\n", references[i].extend_size, references[i].extend_buffer);
tcutils_free (ref_update);
return TCDEF_TCF_ERROR_INVALID_PARAM;
}
if (references[i].extend_buffer
&& ((references[i].extend_size <= 0)
|| (references[i].extend_size > MAX_BOOT_EXTERN_SIZE))){
tckx_error ("Invalid extend_size: %d\n", references[i].extend_size);
tcutils_free (ref_update);
return TCDEF_TCF_ERROR_INVALID_PARAM;
}//end
//计算当前数据实际占用多少字节
//算出名字长度并检查是否超长
name_length = strlen ((const char *)references[i].name) + 1;
if (name_length > MAX_PATH_LENGTH){
tckx_error ("Invalid name_length: %d\n", name_length);
tcutils_free (ref_update);
return TCDEF_TCF_ERROR_INVALID_PARAM;
}//end
//算出所有hash加起来的长度
hash_total_length = references[i].hash_length * references[i].hash_number;//end
//计算总长度,强制把扩展数据和名字占据的长度补齐到4的整数倍。底层硬件在读取4字节对齐的内存时才不会出错。
item_size = sizeof (struct boot_ref_item) + hash_total_length
+ TCUTILS_ALIGN_SIZE (references[i].extend_size + name_length, 4);//end
//检查会不会超过一开始申请的总内存容量
if ((ref_opt + item_size) > MAX_BOOT_REFERENCE_ITEM_SIZE * num){
tckx_error ("Invalid item data\n");
tcutils_free (ref_update);
return TCDEF_TCF_ERROR_INVALID_PARAM;
}//end
//使用htons将数字转换成网络字节序
ref->be_hash_length = htons (references[i].hash_length);//end
//验证 is_enable 和 is_control 是不是合法的布尔值
if (!is_bool_value_legal(references[i].is_enable)){
tckx_error ("Invalid item data, is_enable:%d, not in (ture,false)\n", references[i].is_enable);
tcutils_free (ref_update);
return TCDEF_TCF_ERROR_INVALID_PARAM;
}
if (!is_bool_value_legal(references[i].is_control)){
tckx_error ("Invalid item data, is_control:%d, not in (ture,false)\n", references[i].is_control);
tcutils_free (ref_update);
return TCDEF_TCF_ERROR_INVALID_PARAM;
}//end
//使用左移操作 << 和按位或操作 |,把这两个布尔状态压缩进 be_flags 这一个16位的变量里
ref->be_flags = htons ((references[i].is_enable << BOOT_REFERENCE_FLAG_ENABLE)
| (references[i].is_control << BOOT_REFERENCE_FLAG_CONTROL));//end
//随后把阶段值 stage、扩展数据大小等属性都用 htons 转换格式后赋给当前内存块 ref
ref->be_name_length = htons (name_length);
ref->be_hash_number = htons (references[i].hash_number);
if ((references[i].stage < 0) || (references[i].stage > ((1 << (sizeof(ref->be_stage)*8)) - 1))){
tckx_error ("Invalid stage : %d\n", references[i].stage);
tcutils_free (ref_update);
return TCDEF_TCF_ERROR_INVALID_PARAM;
}
ref->be_stage = htons (references[i].stage);
ref->be_extend_size = htons (references[i].extend_size);//end
//把实质性的内容搬进内存
memcpy (ref->data, references[i].hash, references[i].hash_length * references[i].hash_number); /** hash */
if (references[i].extend_buffer) memcpy (ref->data + hash_total_length, references[i].extend_buffer, references[i].extend_size); /** extern data */
memcpy (ref->data + hash_total_length + references[i].extend_size, references[i].name, name_length); /** name */
//把写入游标向后移动 item_size 个字节,开始下一次循环
ref_opt += item_size;//end
//end
}
//逐个填写大内存的全局头部
ref_update->be_size = htonl (sizeof (struct boot_references_update));//头部结构体本身的大小
ref_update->be_action = htonl (action);//操作类型
ref_update->be_replay_counter = htonll (replay_counter);//防重放计数
ref_update->be_item_number = htonl (num);//处理的条目总数
ref_update->be_data_length = htonl (ref_opt);//
memcpy (ref_update->tpcm_id, tpcm_id, tpcm_id_length);
*obuffer = ref_update;
*olen = sizeof (struct boot_references_update) + ref_opt;
return ret;
}

1.2.2 tckx_tcf_update_boot_measure_references

把在上一个函数里打包好的基准值数据包,下发给底层去执行,同时在本地留一份文件备份

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
int tckx_tcf_update_boot_measure_references(struct boot_references_update *references,
const char *uid, int auth_type,
int auth_length, unsigned char *auth)
{
int ret = 0;
int status = 0;
unsigned int references_len = 0;
char file_path[MAX_PATH_LENGTH] = {0};
//上一个函数为了适应底层,把长度信息转换成了大端序。这里为了能在普通系统里做加法计算,调用 ntohl 把大端序换回我们系统正常的数字,加上头部大小,算出整个数据包的真实总长度。
references_len = sizeof(struct boot_references_update) + ntohl(references->be_data_length);//end
//调用 tckx_get_record_file_path,根据策略类型拿到本地存放备份的具体路径。
tckx_get_record_file_path(TC_POLICY_TYPE_BMEASURE_REF, file_path);//end
//调用tckx_save_policy_to_file,把完整的数据包连同用户的认证信息(密码或密钥等),一起写进刚才获取到的本地文件里。
tckx_save_policy_to_file(file_path, references_len, (char *)references,
uid, auth_type, auth_length, auth);//end
//调用tckx_tcs_update_boot_measure_references将数据包发送给TCS层进行处理,底层处理结果会直接赋值给ret
ret = tckx_tcs_update_boot_measure_references(references, uid, auth_type, auth_length, auth);//end
//这里如果返回失败了,为了防止本地账本和底层实际状态对不上,立刻调用 tcutils_sys_remove_files_default 把刚才第二步存的本地文件删掉。然后把状态标记为失败,跳到out结束。
if (ret) {
tcutils_sys_remove_files_default(file_path);
status = SET_POLICY_FAIL;
goto out;
}
out:
tckx_write_version_notices(htonll(references->be_replay_counter),
TC_POLICY_TYPE_BMEASURE_REF, status);
return ret;
}

函数将数据包通过tckx_tcs_update_boot_measure_references发送给TCS层进行处理,此函数具体流程在下一节进行分析,点击此处快速跳转

1.2.3 tcf_boot_measure

调用了tcs里的boot measure

1
2
3
4
5
int tcf_boot_measure (uint32_t stage, uint32_t num,
struct physical_memory_block *block, uint64_t objAddr, uint32_t objLen){

return tcs_boot_measure (stage, num, block, objAddr, objLen);
}

0x02 TCS

2.1 transmit.c

2.1.1 tpcm_transmit

![mermaid-diagram (1)](https://yzsandwimages.oss-cn-beijing.aliyuncs.com/img/mermaid-diagram (1).png)

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
int tpcm_transmit (void *sbuf, int slength, void *rbuf, int *rlength)
{
int ret = 0;
int tddl_fd = 0;
struct tckx_tddl_transmit_buffer *tddl_msg = NULL;
//计算通信类型与复用内存大小
int tddl_comm_type = is_spec_proc_cmd (sbuf) ? TCDEF_TDDL_COMM_COMMAND : TCDEF_TDDL_COMM_TRANSMIT;
int msg_len = (slength > (*rlength) ? slength : (*rlength)) + sizeof (struct tckx_tddl_transmit_buffer);//取“发送长度”和“预期接收最大长度”两者的最大值,加上驱动层结构体 tckx_tddl_transmit_buffer 的头部大小。发出去的数据和收回来的数据,会共用这同一块内存的同一个缓冲区。
if (NULL == (tddl_msg = malloc (msg_len))){
tckx_error ("gst_tddl_msg alloc error!\n");
return TCDEF_TCS_ERROR_NO_MEMORY;
}//end
//组装驱动层消息结构
memset (tddl_msg, 0, sizeof (struct tckx_tddl_transmit_buffer));
tddl_msg->command_length = slength;
tddl_msg->response_max_length = *rlength;
if (sbuf) memcpy (tddl_msg->buffer, sbuf, slength);//end
//打开硬件设备节点,以读写模式 (O_RDWR) 打开 Linux 系统下的字符设备文件 /dev/tckx_tddl_tpcm
if((tddl_fd = open("/dev/tckx_tddl_tpcm", O_RDWR)) < 0){
tckx_error("open tckx_tddl_tpcm fail\n");
free (tddl_msg);
return TCDEF_TCS_ERROR_OPEN_DEV_FAIL;
}//end

//通过 ioctl 通信
if (ioctl (tddl_fd, TDDL_TPCM_IO_COMMAND(tddl_comm_type), tddl_msg)){
close(tddl_fd);
free (tddl_msg);
return TCDEF_TCS_ERROR_KERNEL_COMM;
}//end
//校验硬件执行结果,如果是0,说明芯片没有任何实质回应,直接当做通信失败处理。
if (tddl_msg->res){
ret = tddl_msg->res;
close(tddl_fd);
free (tddl_msg);
return ret;
}//end

if (!tddl_msg->response_length){
close(tddl_fd);
free (tddl_msg);
return TCDEF_TCS_ERROR_KERNEL_COMM;
}

//tckx_print_hex ((uint8_t *)"Recv", gst_tddl_msg->buffer, gst_tddl_msg->rsp_len);
*rlength = tddl_msg->response_length;
memcpy (rbuf, tddl_msg->buffer, tddl_msg->response_length);

close(tddl_fd);
free (tddl_msg);

return 0;
}

2.2 tcs_bmeasure.c

2.2.1 tcs_boot_measure

此函数作用为,将需要进行启动度量的物理内存块信息组装成硬件设备能识别的指令报文,并下发给 TPCM 安全芯片执行度量。函数具体流程图如下

mermaid-1775532731752

函数详细分析:

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
int tcs_boot_measure (uint32_t stage, uint32_t num,
struct physical_memory_block *block, uint64_t objAddr, uint32_t objLen)
{
/**
typedef struct{
COMMAND_HEADER;
uint32_t uiStage;
uint32_t uiNumber;
uint8_t aucBm[...];
uint8_t uiObjLen;
uint8_t uiObj[0];
}bmeasure_req_st;
*/
int i = 0;
int ret = 0;
uint8_t *buffer = NULL;
uint32_t cmdLen = 0;
uint32_t rspLen = CMD_DEFAULT_ALLOC_SIZE / 2;
uint32_t blockLen = num * sizeof (struct physical_memory_block);

tpcm_req_header_st *cmd = NULL;
tpcm_rsp_header_st *rsp = NULL;
uint8_t *ops = 0;
uint64_t trans = 0;
//申请一块固定大小的连续内存 buffer
if (NULL == (buffer = httc_malloc (CMD_DEFAULT_ALLOC_SIZE))){
httc_util_pr_error ("Req Alloc error!\n");
return TSS_ERR_NOMEM;
}
//将这块内存对半分,前半部分指派给发送请求的结构体指针 cmd,后半部分指派给接收响应的结构体指针 rsp。
cmd = (tpcm_req_header_st*)buffer;
rsp = (tpcm_rsp_header_st*) (buffer + CMD_DEFAULT_ALLOC_SIZE / 2);

//计算本次下发的所有数据(请求头、阶段标识、内存块数量、内存块数组总长、对象长度、对象地址)加起来的真实总字节数 cmdLen。
cmdLen = sizeof (tpcm_req_header_st) + sizeof (stage) + sizeof (num) + blockLen + sizeof (objLen) + sizeof (objAddr);
//随后检查这个总长度是否超过了刚才申请的缓冲区最大承受范围,如果超出,释放内存并返回超限错误。
if ((int)cmdLen > (int)CMD_DEFAULT_ALLOC_SIZE){
httc_free (cmd);
httc_util_pr_error ("block is too large!\n");
return TSS_ERR_INPUT_EXCEED;
}

//构造通信头部信息
cmd->uiCmdTag = htonl (TPCM_TAG_REQ_COMMAND);
cmd->uiCmdLength = htonl (cmdLen);
cmd->uiCmdCode = htonl (TPCM_ORD_BootMeasure);

ops = (uint8_t*)cmd + sizeof (tpcm_req_header_st);

/** Insert stage */
*((uint32_t *)ops) = htonl (stage);
ops += 4;
/** Insert num */
*((uint32_t *)ops) = htonl (num);
ops += 4;
/** Insert block */
for (i = 0; i < num; i++){
trans = htonll((block+i)->physical_addr);
memcpy(ops, &trans, sizeof(uint64_t));
ops += 8;
*((uint32_t *)ops) = htonl ((block+i)->length);
ops += 4;
}
/** insert objLen */
*((uint32_t *)ops) = htonl (objLen);
ops += 4;
/** insert objAddress */
trans = htonll(objAddr);
memcpy(ops, &trans, sizeof(uint64_t));

if (0 != (ret = tpcm_transmit (cmd, cmdLen, rsp, (int *)&rspLen))) goto out;

if (TPCM_TAG_RSP_COMMAND != tpcmRspTag(rsp)){
httc_util_pr_error ("Invalid tpcm rsp tag(0x%02X)\n", tpcmRspTag(rsp));
ret = TSS_ERR_BAD_RESPONSE_TAG;
goto out;
}

ret = tpcmRspRetCode (rsp);

out:
if (buffer) httc_free (buffer);
return ret;
}

0x03 TDDL

3.1 tpcm_dev.c

在 Linux 内核中注册一个名为 tckx_tddl 的平台设备,并向上层暴露一个字符设备节点,专门用来接收和分发与 TPCM(可信平台控制模块)相关的底层硬件指令。

3.1.1 全局变量与结构体

1
2
3
4
5
static int tpcm_tddl_cdev_major = TCDEF_TDDL_TPCM_DEV_MAJOR;
static struct cdev tpcm_cdev;
static struct class *tpcm_tddl_cdev_class;
static struct device *tpcm_tddl_cdev_device;
static struct platform_device *tddl_pdev;

3.1.2 tpcm_tddl_ioctl

采用标准的 ioctl 分发模式,先基于 _IOC_TYPE 校验命令类别,再基于 _IOC_NR 将请求转发到三个不同的 TPCM 处理函数中;从接口设计看,该驱动属于典型的命令型字符设备驱动,ioctl 层本身较薄,真正的业务逻辑主要位于 tpcm_tansmit_iotpcm_command_iotpcm_tansmit_no_recv 等下层函数中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
static long tpcm_tddl_ioctl(struct file *filp, unsigned int command, unsigned long arg)
{
if(_IOC_TYPE(command) != TCDEF_TDDL_COMM_TYPE_TPCM){
tckx_error("Invalid command type =%d\n",_IOC_TYPE(command));
return -ENOTTY;
}
switch(_IOC_NR (command))
{
case TCDEF_TDDL_COMM_TRANSMIT:
return tpcm_tansmit_io(arg);
case TCDEF_TDDL_COMM_COMMAND:
return tpcm_command_io(arg);
case TCDEF_TDDL_COMM_NO_RECV:
return tpcm_tansmit_no_recv(arg);
default:
tckx_error("Invalid command number =%d\n",_IOC_NR(command));
return -ENOTTY;
}
}

3.1.3 tddl_probe

platform_device 和当前驱动匹配成功时就调用这个函数完成设备初始化

tddl_probe.drawio

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
static int tddl_probe(struct platform_device *pdev)
{
int ret = 0;
dev_t devno;
//把主设备号 tpcm_tddl_cdev_major 和次设备号 0 组合成 dev_t
devno = MKDEV (tpcm_tddl_cdev_major, 0);//

//注册字符设备号,设备名为 "tckx_tddl_tpcm"
ret = register_chrdev_region(devno, 1, "tckx_tddl_tpcm");
//失败返回
if(ret){
printk ("register_chrdev_region error!\n");
return ret;
}////end

//初始化字符设备对象 tpcm_cdev。将来用户态打开/dev/tckx_tddl_tpcm 后,执行的操作都将落到tpcm_tddl_fops里定义的函数
cdev_init(&tpcm_cdev, &tpcm_tddl_fops);
tpcm_cdev.owner = THIS_MODULE;//设置拥有者为当前模块。

//把刚才初始化好的 cdev 注册到内核字符设备框架中
ret = cdev_add(&tpcm_cdev, devno, 1);
if(ret){
printk ("cdev_add error (%d)!\n", ret);
//此处函数调用存在问题,前面使用register_chrdev_region函数注册设备号,但是这里销毁用的却是unregister_chrdev,应该是unregister_chrdev_region才对。
unregister_chrdev(tpcm_tddl_cdev_major, "tckx_tddl_tpcm");
//cdev_err处理部分确实是使用了unregister_chrdev_region函数销毁,也就是前面的销毁可能没有起到应有的作用
goto cdev_err;
//这里的返回真的是何意味了,前面已经goto到cdev_err了,根本不会执行到这里
return ret;
}

//内核版本兼容处理,不同版本内核中 class_create() 接口形式发生过变化
//宏class_create()用于动态创建设备的逻辑类,并完成部分字段的初始化,然后将其添加进Linux内核系统中。此函数的执行效果就是在目录/sys/class下创建一个新的文件夹,此文件夹的名字为此函数的第二个输入参数,但此文件夹是空的。
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 4, 0)
tpcm_tddl_cdev_class = class_create ("tckx_tddl_tpcm");
#else
tpcm_tddl_cdev_class = class_create (THIS_MODULE, "tckx_tddl_tpcm");
#endif
//错误处理
if(IS_ERR (tpcm_tddl_cdev_class)){
ret = PTR_ERR(tpcm_tddl_cdev_class);
printk ("class_create error (%d)!\n", ret);
goto class_err;
}

//在前面 class 的基础上创建一个 device
//函数device_create()用于动态地创建逻辑设备,并对新的逻辑设备类进行相应的初始化,将其与此函数的第一个参数所代表的逻辑类关联起来,然后将此逻辑设备加到Linux内核系统的设备驱动程序模型中。函数能够自动地在/sys/devices/virtual目录下创建新的逻辑设备目录,在/dev目录下创建与逻辑类对应的设备文件。
tpcm_tddl_cdev_device = device_create(tpcm_tddl_cdev_class, NULL, devno, NULL, "tckx_tddl_tpcm");
if(IS_ERR (tpcm_tddl_cdev_device)){
ret = PTR_ERR(tpcm_tddl_cdev_device);
printk ("device_create error (%d)!\n", ret);
goto device_err;
}

//注册电源管理通知器,注册成功后,当系统执行电源状态切换(如 suspend、resume、hibernate 等)时,内核会遍历该通知链,依次调用每个通知块的 notifier_call 函数,执行必要的动作(例如保存/恢复硬件状态、禁用/启用时钟、处理 DMA 等)
#ifdef PK2004
tddl_nb.notifier_call = tddl_pm_notifier;
register_pm_notifier (&tddl_nb);
#endif

printk("[%s:%d] success!\n", __func__, __LINE__);
return 0;

device_err:
class_destroy (tpcm_tddl_cdev_class);
class_err:
cdev_del(&tpcm_cdev);
cdev_err:
unregister_chrdev_region(MKDEV(tpcm_tddl_cdev_major, 0), 1);
return ret;
}

3.1.4 tddl_remove

驱动卸载,把 probe() 中申请的资源全部释放掉

1
2
3
4
5
6
7
8
9
10
11
12
13
14
static int tddl_remove(struct platform_device *pdev)
{
//注销 PM notifier
#ifdef PK2004
unregister_pm_notifier (&tddl_nb);
#endif
//将申请到的资源全部释放
device_destroy(tpcm_tddl_cdev_class, MKDEV(tpcm_tddl_cdev_major, 0));
class_destroy(tpcm_tddl_cdev_class);
cdev_del(&tpcm_cdev);
unregister_chrdev_region(MKDEV(tpcm_tddl_cdev_major, 0), 1);
printk("[%s:%d] success!\n", __func__, __LINE__);
return 0;
}

3.1.5 tddkdev_init

模块初始化函数,先完成底层 TCM/TDDL 子系统初始化,然后构造一个 platform_device,再注册一个 platform_driver,让两者匹配,最终触发 tddl_probe() 去完成字符设备创建。

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
static int tddldev_init(void)
{
int ret;

ret = tcm_tddl_init();
if(ret){
return ret;
}
tddl_pdev = platform_device_alloc("tckx_tddl", -1);
if (!tddl_pdev)
return -ENOMEM;

ret = platform_device_add(tddl_pdev);
if (ret) {
platform_device_put(tddl_pdev);
return ret;
}
ret = platform_driver_register(&tddl_drv);
if (ret) {
platform_device_unregister(tddl_pdev);
platform_device_put(tddl_pdev);
return ret;
}

return 0;
}

3.2 tpcm_cmd.c

3.2.1 全局变量与结构体

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
unsigned int sync = 0;
unsigned int suspend_waiting = 0;

static tckx_tddl_command_func tpcm_command_func;
static struct tckx_tpcm_power_manager *tpcm_pm;

static DEFINE_MUTEX(transmit_mutex);

int tckx_tddl_set_command_func (tckx_tddl_command_func func)
{
mutex_lock (&transmit_mutex);
tpcm_command_func = func;
mutex_unlock (&transmit_mutex);
return 0;
}
EXPORT_SYMBOL_GPL(tckx_tddl_set_command_func);

3.2.2 tpcm_transmit_cmd

向 TPCM 发送一条命令,接收返回结果,做日志打印和基本响应校验,然后把底层发送函数的返回码返回给上层。

各参数大致含义如下:

  • type:命令发送类型,传给底层 tckx_tpcm_send_command
  • command:待发送的命令缓冲区
  • length:命令长度
  • result:接收返回结果的缓冲区
  • rlength:输入输出参数,一般表示返回缓冲区大小,调用后变成实际返回长度

tpcm_transmit_cmd

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
static int tpcm_transmit_cmd (unsigned int type, void *command, int length, void *result, int *rlength)
{
int ok = 0;
int res = 0;

//系统在准备挂起时,只允许发送休眠相关命令,其他 TPCM/TCM 命令都不允许继续发。
if (suspend_waiting && (TCKX_TPCM_REQ_HIBERNATE != tpcmReqCmd(command))){
tckx_info("TPCM in suspending, can not send tpcm && tcm commands (0x%08x)\n",tpcmReqCmd(command));
return TCDEF_TPCM_ERROR_NOSPACE;
}

//发送前把命令内容按十六进制打印出来
tckx_print_hex("Send", command, (length < 1024) ? length : 1024);
//调用底层发送函数,失败表示通信层失败
if (0 != (ok = tckx_tpcm_send_command (//tckx_tpcm_send_command在tdd里定义
type, command, length, result, rlength))){
tckx_error( "Send command failed,type=%d,ret=0x%08x(%d)!\n", type,ok, ok);//发送失败,打印错误日志
goto out;
}
tckx_print_hex("Recv", result, (*rlength < 1024) ? *rlength : 1024);

out:
#if 1
//如果通信层失败,打印发送包
if (ok){
tckx_do_print_hex ("[Error]Send", command, (length < 1024) ? length : 1024);
}
else{//如果通信层成功,则取响应中的结果码
res = *((uint32_t *)((uint8_t *)result + 8));
//如果 res != 0,说明通信成功了但 TPCM 命令执行失败
if (res){
tckx_do_print_hex ("[Error]Send", command, (length < 1024) ? length : 1024);
tckx_do_print_hex ("[Error]Recv", result, (*rlength < 1024) ? *rlength : 1024);
}
}
#endif
//发送消息和接收都成功
if (!ok && !res){
if (tpcmRspLength(result) != *rlength){
tckx_error("Transmit tcm command error,header return length(%d) != return length(%d)\n",
tpcmRspLength(result), *rlength);
return TCDEF_TCS_ERROR_INVALID_RESPONSE;
}
}
return ok;
}

3.2.3 tpcm_tansmit_io

把用户态传进来的“发送请求结构”搬到内核里,分配内核发送/接收缓冲区,调用底层 tpcm_transmit_cmd() 完成一次 TPCM 命令通信,再把结果拷回用户态。

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
long tpcm_tansmit_io(unsigned long io_arg)
{
int ret = 0;
uint32_t message_len = 0;
uint32_t response_len = 0;
struct tckx_tddl_transmit_buffer transmit_tmp;
struct tckx_tddl_transmit_buffer *transmit_msg = NULL;
struct tckx_tddl_transmit_buffer *transmit_response = NULL;
unsigned char* send_buffer = NULL;
unsigned char* recv_buffer = NULL;
int send_buffer_len=0, recv_buffer_len=0;
uint32_t cmd_category = (sync ? TCDEF_COMMAND_TYPE_TPCM : TCDEF_COMMAND_TYPE_TPCM_ASYNC);

//从用户态拷固定头,读出长度字段,再决定后续分配多大内存
if (0 != (ret = copy_from_user (&transmit_tmp,
(int __user *)io_arg, sizeof (transmit_tmp)))){
tckx_error("Copy data error, total: %d, left: %d\n",
(int)sizeof (transmit_tmp), ret);
return -EIO;
}

//计算请求和响应结构总长度
message_len = transmit_tmp.command_length + sizeof (struct tckx_tddl_transmit_buffer);//完整请求结构大小,头部 + 实际命令数据
response_len = transmit_tmp.response_max_length + sizeof (struct tckx_tddl_transmit_buffer);//完整响应结构大小,头部 + 最大响应数据空间

//为响应结构分配内存
if (NULL == (transmit_response = kmalloc(response_len, GFP_KERNEL))){
tckx_error("Out of memory!\n");
return -ENOMEM;
}
transmit_response->response_length = transmit_tmp.response_max_length;

//为发送的消息分配内核内存
if (NULL == (transmit_msg = kmalloc (message_len, GFP_KERNEL))){
tckx_error("Out of memory!\n");
ret = -ENOMEM;
goto out;
}
//分配好发送区内存后,再次调用 copy_from_user,把用户态的完整数据全部拉进内核态。如果中途出错,记录错误码并跳转到收尾逻辑。
if (0 != (ret = copy_from_user (transmit_msg, (int __user *)io_arg, message_len))){
tckx_error ("Copy data error, total: %d, left: %d\n", message_len, ret);
ret = -EIO;
goto out;
}

//底层硬件通常要求数据存放在物理地址连续的特定内存区(比如用于 DMA 传输)。这里计算出发送和接收的缓冲区长度,设定了一个最小保底长度 TDDL_TPCM_COMMAND_BUFFER_SIZE。
send_buffer_len = transmit_tmp.command_length>TDDL_TPCM_COMMAND_BUFFER_SIZE ? transmit_tmp.command_length:TDDL_TPCM_COMMAND_BUFFER_SIZE;
recv_buffer_len = transmit_response->response_length>TDDL_TPCM_COMMAND_BUFFER_SIZE ? transmit_response->response_length:TDDL_TPCM_COMMAND_BUFFER_SIZE;
//接着调用硬件专门的内存分配函数 tckx_tpcm_alloc_data_buffer 申请出 send_buffer
if (NULL == (send_buffer = tckx_tpcm_alloc_data_buffer (send_buffer_len))){
tckx_error("Out of memory!\n");
ret = -ENOMEM;
goto out;
}
//申请成功后,用 memcpy 将刚才拉进内核的数据转存进这块硬件专属内存中。
memcpy(send_buffer, transmit_msg->buffer, transmit_msg->command_length);
//随后再次调用硬件内存分配函数
if (NULL == (recv_buffer = tckx_tpcm_alloc_data_buffer(recv_buffer_len))){
tckx_error("Out of memory!\n");
ret = -ENOMEM;
goto out;
}

//数据全在硬件缓冲区落位后,调用上一节分析过的 tpcm_transmit_cmd 函数,把硬件发送区和接收区指针交接给它。执行结束后,硬件状态码保存在 transmit_response->res 中,实际接收到的长度也会被更新。
if (0 != (transmit_response->res = tpcm_transmit_cmd (cmd_category,
send_buffer, transmit_msg->command_length,
recv_buffer, &transmit_response->response_length))){
tckx_error("Transmit tpcm command res: 0x%08x\n", transmit_response->res);
}
//更新最终应答报文的总长度
response_len = transmit_response->response_length + sizeof (struct tckx_tddl_transmit_buffer);
//调用 memcpy 从硬件接收区组装数据并拷回用户态
memcpy(transmit_response->buffer, recv_buffer, transmit_response->response_length);

//最后调用 copy_to_user,把这块内存推回到最初传入的用户态指针 io_arg 所在的空间
if (0 != (ret = copy_to_user ((int __user *)io_arg, transmit_response, response_len))){
tckx_error("Copy data error, total: %d, left: %d\n", response_len, ret);
ret = -ENOMEM;
}
//无论前面执行是成功还是分配内存失败goto跳转的,这里统一清理现场释放内存
out:
if(transmit_msg) kfree(transmit_msg);
if(send_buffer) tckx_tpcm_free_data_buffer(send_buffer);
if(transmit_response) kfree(transmit_response);
if(recv_buffer) tckx_tpcm_free_data_buffer(recv_buffer);
return ret;
}

3.2.4 tpcm_command_io

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
long tpcm_command_io(unsigned long io_arg)
{
int ret = 0;
uint32_t message_len = 0;
uint32_t response_len = 0;
struct tckx_tddl_transmit_buffer transmit_tmp;
struct tckx_tddl_transmit_buffer *transmit_msg = NULL;
struct tckx_tddl_transmit_buffer *transmit_response = NULL;
unsigned char* recv_buffer = NULL;
int recv_buffer_len=0;

if (0 != (ret = copy_from_user (&transmit_tmp,
(int __user *)io_arg, sizeof (transmit_tmp)))){
tckx_error("Copy data error, total: %d, left: %d\n",
(int)sizeof (transmit_tmp), ret);
return -EIO;
}

message_len = transmit_tmp.command_length + sizeof (struct tckx_tddl_transmit_buffer);
response_len = transmit_tmp.response_max_length + sizeof (struct tckx_tddl_transmit_buffer);

if (NULL == (transmit_response = tcutil_mem_debug_vmalloc (response_len))){
tckx_error("Out of memory!\n");
return -ENOMEM;
}
transmit_response->response_length = transmit_tmp.response_max_length;

if (NULL == (transmit_msg = tcutil_mem_debug_vmalloc (message_len))){
tckx_error("Out of memory!\n");
ret = -ENOMEM;
goto out;
}

if (0 != (ret = copy_from_user (transmit_msg, (int __user *)io_arg, message_len))){
tckx_error("Copy data error, total: %d, left: %d\n", message_len, ret);
ret = -EIO;
goto out;
}

recv_buffer_len = transmit_response->response_length>TDDL_TPCM_COMMAND_BUFFER_SIZE ? transmit_response->response_length:TDDL_TPCM_COMMAND_BUFFER_SIZE;
if (NULL == (recv_buffer = tckx_tpcm_alloc_data_buffer(recv_buffer_len))){
tckx_error("Out of memory!\n");
ret = -ENOMEM;
goto out;
}

mutex_lock (&transmit_mutex);
if (tpcm_command_func){
if (0 != (transmit_response->res = tpcm_command_func (
transmit_msg->buffer, transmit_msg->command_length,
recv_buffer, &transmit_response->response_length))){
tckx_error("Process tpcm command res: 0x%08x\n", transmit_response->res);
}
}else{
tckx_error("Command func not set\n");
transmit_response->res = TCDEF_TCS_ERROR_KERNEL_COMM;
}
memcpy(transmit_response->buffer, recv_buffer, transmit_response->response_length);
mutex_unlock (&transmit_mutex);
if (0 != (ret = copy_to_user ((int __user *)io_arg, transmit_response, response_len))){
tckx_error("Copy data error, total: %d, left: %d\n", response_len, ret);
ret = -ENOMEM;
}

out:
if(transmit_msg)tcutil_mem_debug_vfree (transmit_msg);
if(transmit_response)tcutil_mem_debug_vfree (transmit_response);
if(recv_buffer)tckx_tpcm_free_data_buffer (recv_buffer);
return ret;
}

3.2.5 tpcm_transmit_cmd_no_recv

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
static int tpcm_transmit_cmd_no_recv (unsigned int type, void *command, int length, void *result, int *rlength)
{
int ok = 0;

if (suspend_waiting && (TCKX_TPCM_REQ_HIBERNATE != tpcmReqCmd(command))){
tckx_info("TPCM in suspending, can not send tpcm && tcm commands (0x%08x)\n",tpcmReqCmd(command));
return TCDEF_TPCM_ERROR_NOSPACE;
}

tckx_print_hex("Send", command, (length < 1024) ? length : 1024);
if (0 != (ok = tckx_tpcm_send_command (
type, command, length, result, rlength))){
tckx_error( "Send command failed,type=%d,ret=0x%08x(%d)!\n", type,ok, ok);
goto out;
}

out:
if (ok){
tckx_do_print_hex ("[Error]Send", command, (length < 1024) ? length : 1024);
}

return ok;
}

TCSSM源码阅读
http://yzsandw.com/2026/03/05/TCSSM源码阅读/
作者
5Y2z
发布于
2026年3月5日
许可协议