25. OP-TEE驱动篇----驱动编译,加载和初始化(二)

    历经一年多时间的系统整理合补充,《手机安全和可信应用开发指南:TrustZone与OP-TEE技术详解 》一书得以出版,书中详细介绍了TEE以及系统安全中的所有内容,全书按照从硬件到软件,从用户空间到内核空间的顺序对TEE技术详细阐述,读者可从用户空间到TEE内核一步一步了解系统安全的所有内容,同时书中也提供了相关的示例代码,读者可根据自身实际需求开发TA。目前该书已在天猫、京东、当当同步上线,链接如下:

当当购买地址

京东购买地址

天猫购买地址

非常感谢在此期间大家的支持以及各位友人的支持和帮助!!!。

  

驱动与secure world之间的数据交互是通过共享内存来完成的,在OP-TEE启动的时候会将作为共享内存的物理内存块reserve出来,具体的可以查看OP-TEE启动代码中的core_init_mmu_map函数。在OP-TEE驱动初始化阶段会将reserve出来作为共享内存的物理内存配置成驱动的内存池,并且通知OP-TEE OS执行相同的动作。配置完成之后,secure world就能从共享内存中获取到来自于REE端的数据。

1. 配置驱动与OP-TEE之间的共享内存

  在驱动做probe操作时,会调用到optee_config_shm_memremap函数来完成OP-TEE驱动和OP-TEE之间共享内存的配置。该函数定义在linux/drivers/tee/optee/core.c文件中。其内容如下:

static struct tee_shm_pool *
optee_config_shm_memremap(optee_invoke_fn *invoke_fn, void **memremaped_shm)
{
	union {
		struct arm_smccc_res smccc;
		struct optee_smc_get_shm_config_result result;
	} res;
	struct tee_shm_pool *pool;
	unsigned long vaddr;
	phys_addr_t paddr;
	size_t size;
	phys_addr_t begin;
	phys_addr_t end;
	void *va;
	struct tee_shm_pool_mem_info priv_info;
	struct tee_shm_pool_mem_info dmabuf_info;

/* 调用smc类操作,通知OP-TEE OS返回被reserve出来的共享内存的物理地址和大小 */
	invoke_fn(OPTEE_SMC_GET_SHM_CONFIG, 0, 0, 0, 0, 0, 0, 0, &res.smccc);
	if (res.result.status != OPTEE_SMC_RETURN_OK) {
		pr_info("shm service not available\n");
		return ERR_PTR(-ENOENT);
	}

/* 判定是否提供secure world中的cache */
	if (res.result.settings != OPTEE_SMC_SHM_CACHED) {
		pr_err("only normal cached shared memory supported\n");
		return ERR_PTR(-EINVAL);
	}

/* 将对齐操作之后的物理内存块的起始地址赋值给paddr,该块内存的大小赋值给size */
	begin = roundup(res.result.start, PAGE_SIZE);
	end = rounddown(res.result.start + res.result.size, PAGE_SIZE);
	paddr = begin;
	size = end - begin;

/* 判定作为共享内存的物理地址块的大小是否大于两个page大小,如果小于则报错,因为驱
   动配置用于dma操作和普通共享内存的大小分别为一个page大小*/
	if (size < 2 * OPTEE_SHM_NUM_PRIV_PAGES * PAGE_SIZE) {
		pr_err("too small shared memory area\n");
		return ERR_PTR(-EINVAL);
	}

// 将共享内存块的物理地址映射到系统内存中,得到映射完成的虚拟地址,存放在va变量中 
	va = memremap(paddr, size, MEMREMAP_WB);
	if (!va) {
		pr_err("shared memory ioremap failed\n");
		return ERR_PTR(-EINVAL);
	}
	vaddr = (unsigned long)va;

/* 配置驱动私有内存空间的虚拟地址的启动地址,物理地址的起始地址以及大小,配置
   dma缓存的虚拟起始地址和物理地址以及大小。dmabuf与privbuf两个相邻,分别为一个
    一个page的大小 */
	priv_info.vaddr = vaddr;
	priv_info.paddr = paddr;
	priv_info.size = OPTEE_SHM_NUM_PRIV_PAGES * PAGE_SIZE;
	dmabuf_info.vaddr = vaddr + OPTEE_SHM_NUM_PRIV_PAGES * PAGE_SIZE;
	dmabuf_info.paddr = paddr + OPTEE_SHM_NUM_PRIV_PAGES * PAGE_SIZE;
	dmabuf_info.size = size - OPTEE_SHM_NUM_PRIV_PAGES * PAGE_SIZE;

/* 将驱动的私有buffer和dma buffer添加到内存池中,以便驱动在使用本身的alloc函数
   的时候能够从私有共享内存和dma buffer中分配内存来使用 */
	pool = tee_shm_pool_alloc_res_mem(&priv_info, &dmabuf_info);
	if (IS_ERR(pool)) {
		memunmap(va);
		goto out;
	}

/* 将驱动与OP-TEE的共享内存赋值给memremaped_shm变量执行的地址 */
	*memremaped_shm = va;
out:
	return pool;	//返回共享内存池的结构体
}

  在secure world中reserve出来的内存块作为驱动与sercure world之间的共享内存使用。在驱动端将会建立一个内存池,以便驱动在需要的使用通过调用驱动的alloc函数来完成共享内存的分配。而共享内存池的建立是通过调用tee_shm_pool_alloc_res_mem来实现的。其函数内容如下:

struct tee_shm_pool *
tee_shm_pool_alloc_res_mem(struct tee_shm_pool_mem_info *priv_info,
			   struct tee_shm_pool_mem_info *dmabuf_info)
{
	struct tee_shm_pool *pool = NULL;
	int ret;

/* 从内核空间的memory中分配一块用于存放驱动内存池结构体变量的内存 */
	pool = kzalloc(sizeof(*pool), GFP_KERNEL);
	if (!pool) {
		ret = -ENOMEM;
		goto err;
	}

	/*
	 * Create the pool for driver private shared memory
	 */
/* 调用pool相关函数完成内存池的创建,设定alloc时的分配算法,并将私有共享内存的
   起始虚拟地址,起始物理地址以及大小信息保存到私有共享内存池中 */
	ret = pool_res_mem_mgr_init(&pool->private_mgr, priv_info,
				    3 /* 8 byte aligned */);
	if (ret)
		goto err;

	/*
	 * Create the pool for dma_buf shared memory
	 */
/* 调用pool相关函数完成内存池的创建,设定alloc时的分配算法,并将dma共享内存的
   起始虚拟地址,起始物理地址以及大小信息保存到dma的共享内存池中 */
	ret = pool_res_mem_mgr_init(&pool->dma_buf_mgr, dmabuf_info,
				    PAGE_SHIFT);
	if (ret)
		goto err;

/* 设定销毁共享内存池的接口函数 */
	pool->destroy = pool_res_mem_destroy;
	return pool;	//返回内存池结构体
err:
	if (ret == -ENOMEM)
		pr_err("%s: can't allocate memory for res_mem shared memory pool\n", __func__);
	if (pool && pool->private_mgr.private_data)
		gen_pool_destroy(pool->private_mgr.private_data);
	kfree(pool);
	return ERR_PTR(ret);
}

2. 分配和设置OP-TEE驱动作为被libteec和tee_supplicant使用的设备信息结构体变量

  在OP-TEE驱动做probe操作时会分配和设置两个tee_device结构体变量,分别用来表示被libteec和tee_supplicant使用的设备。分别通过调用tee_device_alloc(&optee_desc, NULL, pool, optee)和tee_device_alloc(&optee_supp_desc, NULL, pool, optee)来实现,主要是设置驱动作为被libteec和tee_supplicant使用的设备的具体操作和表示该设备对应的名称等信息,

  当libteec调用文件操作函数执行打开,关闭等操作/dev/tee0设备文件的时,其最终将调用到optee_desc中具体的函数来实现对应操作。

  当tee_supplicant调用文件操作函数执行打开,关闭等操作/dev/teepriv0设备文件的时,其最终将调用到optee_supp_desc中具体的函数来实现对应操作。

  上述配置操作都是通过调用tee_device_all函数来实现的,该函数内容如下:

struct tee_device *tee_device_alloc(const struct tee_desc *teedesc,
				    struct device *dev,
				    struct tee_shm_pool *pool,
				    void *driver_data)
{
	struct tee_device *teedev;
	void *ret;
	int rc;
	int offs = 0;

/* 参数检查 */
	if (!teedesc || !teedesc->name || !teedesc->ops ||
	    !teedesc->ops->get_version || !teedesc->ops->open ||
	    !teedesc->ops->release || !pool)
		return ERR_PTR(-EINVAL);

/* 从kerner space中分配用于存放tee_device变量的内存 */
	teedev = kzalloc(sizeof(*teedev), GFP_KERNEL);
	if (!teedev) {
		ret = ERR_PTR(-ENOMEM);
		goto err;
	}

/* 判定当前分配的设备结构体是提供给libteec还是tee_supplicant,如果该设备时分配
   给tee_supplicant,则将offs设置成16,offs将会在用于设置设备的id时被使用 */
	if (teedesc->flags & TEE_DESC_PRIVILEGED)
		offs = TEE_NUM_DEVICES / 2;

/* 查找dev_mask中的从Offs开始的第一个为0的bit位,然后将该值作为设备的id值 */
	spin_lock(&driver_lock);
	teedev->id = find_next_zero_bit(dev_mask, TEE_NUM_DEVICES, offs);
	if (teedev->id < TEE_NUM_DEVICES)
		set_bit(teedev->id, dev_mask);
	spin_unlock(&driver_lock);

/* 判定设定的设备id是否超出最大数 */
	if (teedev->id >= TEE_NUM_DEVICES) {
		ret = ERR_PTR(-ENOMEM);
		goto err;
	}

/* 组合出设备名,对于libteec来说,设备名为tee0。对于tee_supplicant来说,设备
    名为teepriv0 */
	snprintf(teedev->name, sizeof(teedev->name), "tee%s%d",
		 teedesc->flags & TEE_DESC_PRIVILEGED ? "priv" : "",
		 teedev->id - offs);

/* 设定设备的class,tee_class在tee_init函数中被分配。设定执行设备release的操作函数
   和dev.parent */
	teedev->dev.class = tee_class;
	teedev->dev.release = tee_release_device;
	teedev->dev.parent = dev;

/* 将设备的主设备号和设备ID组合后转化成dev_t类型 */
	teedev->dev.devt = MKDEV(MAJOR(tee_devt), teedev->id);

/* 设置设备名,驱动被libteec使用时设备名为tee0,驱动被tee_supplicant使用时设备名
    为teepriv0 */
	rc = dev_set_name(&teedev->dev, "%s", teedev->name);
	if (rc) {
		ret = ERR_PTR(rc);
		goto err_devt;
	}

/* 设置驱动作为字符设备的操作函数接口,即指定该驱动在执行open,close, ioctl的函数接口 */
	cdev_init(&teedev->cdev, &tee_fops);
	teedev->cdev.owner = teedesc->owner;	//初始化字符设备的owner
	teedev->cdev.kobj.parent = &teedev->dev.kobj;	//初始化kobj.parent成员

/* 设置设备的私有数据 */
	dev_set_drvdata(&teedev->dev, driver_data);

/* 初始化设备 */
	device_initialize(&teedev->dev);

	/* 1 as tee_device_unregister() does one final tee_device_put() */
	teedev->num_users = 1;	//标记该设备可以被使用
	init_completion(&teedev->c_no_users);
	mutex_init(&teedev->mutex);
	idr_init(&teedev->idr);

/* 设定设备的desc成员,该成员包含设备最终执行具体操作的函数接口 */
	teedev->desc = teedesc;

/* 设置设备的内存池,主要是驱动与secure world之间共享内存的私有共享内存和dma
    操作共享内存 */
	teedev->pool = pool;

	return teedev;
err_devt:
	unregister_chrdev_region(teedev->dev.devt, 1);
err:
	pr_err("could not register %s driver\n",
	       teedesc->flags & TEE_DESC_PRIVILEGED ? "privileged" : "client");
	if (teedev && teedev->id < TEE_NUM_DEVICES) {
		spin_lock(&driver_lock);
		clear_bit(teedev->id, dev_mask);
		spin_unlock(&driver_lock);
	}
	kfree(teedev);
	return ret;
}

3. 设备注册

  完成版本检查,共享内存池配置,不同设备的配置之后就需要将这些配置好的设备注册到系统中去。对于被liteec和tee_supplicant使用的设备分别通过调用tee_device_register(optee->teedev)和tee_device_register(optee->supp_teedev)来实现。其中optee->teedev和optee->supp_teedev就是在上一章中被配置好的分别被libteec和tee_supplicant使用的设备结构体。调用tee_device_register函数来实现将设备注册到系统的目的,该函数内容如下:

int tee_device_register(struct tee_device *teedev)
{
	int rc;

/* 判定设备是否已经被注册过 */
	if (teedev->flags & TEE_DEVICE_FLAG_REGISTERED) {
		dev_err(&teedev->dev, "attempt to register twice\n");
		return -EINVAL;
	}

/* 注册字符设备 */
	rc = cdev_add(&teedev->cdev, teedev->dev.devt, 1);
	if (rc) {
		dev_err(&teedev->dev,
			"unable to cdev_add() %s, major %d, minor %d, err=%d\n",
			teedev->name, MAJOR(teedev->dev.devt),
			MINOR(teedev->dev.devt), rc);
		return rc;
	}

/* 将设备添加到linux的设备模块中,在该步中将会在/dev目录下创设备驱动文件节点,即对于
    被libteec使用的设备,在该步将创建/dev/tee0设备驱动文件。对于被tee_supplicant使用
    的设备,在该步将创建/dev/teepriv0设备文件 */
	rc = device_add(&teedev->dev);
	if (rc) {
		dev_err(&teedev->dev,
			"unable to device_add() %s, major %d, minor %d, err=%d\n",
			teedev->name, MAJOR(teedev->dev.devt),
			MINOR(teedev->dev.devt), rc);
		goto err_device_add;
	}

/* 在/sys目录下创建设备的属性文件 */
	rc = sysfs_create_group(&teedev->dev.kobj, &tee_dev_group);
	if (rc) {
		dev_err(&teedev->dev,
			"failed to create sysfs attributes, err=%d\n", rc);
		goto err_sysfs_create_group;
	}

/* 设定该设备以及被注册过 */
	teedev->flags |= TEE_DEVICE_FLAG_REGISTERED;
	return 0;

err_sysfs_create_group:
	device_del(&teedev->dev);
err_device_add:
	cdev_del(&teedev->cdev);
	return rc;
}

4. 两个队列的建立

  在OP-TEE驱动提供两个设备,分别被libteec使用的/dev/tee0和被tee_supplicant使用的/dev/teepriv0。为确保normal world与secure world之间数据交互便利和能在normal world端进行异步处理。OP-TEE驱动在挂载的时候会建立两个类似于消息队列的队列,用于保存normal world的请求数据以及secure world请求。optee_wait_queue_init用于初始化/dev/tee0设备使用的队列,optee_supp_init用于初始化/dev/teepriv0设备使用的队列。其代码分别如下:

void optee_wait_queue_init(struct optee_wait_queue *priv)
{
	mutex_init(&priv->mu);
	INIT_LIST_HEAD(&priv->db);
}

void optee_supp_init(struct optee_supp *supp)
{
	memset(supp, 0, sizeof(*supp));
	mutex_init(&supp->mutex);
	init_completion(&supp->reqs_c);
	idr_init(&supp->idr);
	INIT_LIST_HEAD(&supp->reqs);
	supp->req_id = -1;
}

5. 通知OP-TEE使能一些共享内存的cache

  当一切做完之后,最终就剩下通知OP-TEE使能共享内存的cache了,在驱动挂载过程中调用optee_enable_shm_cache函数来实现,该函数内容如下:

void optee_enable_shm_cache(struct optee *optee)
{
	struct optee_call_waiter w;

	/* We need to retry until secure world isn't busy. */
/* 确定secure world是否ready */
	optee_cq_wait_init(&optee->call_queue, &w);

/* 进入到loop循环,通知secure world执行相应操作,直到返回OK后跳出 */
	while (true) {
		struct arm_smccc_res res;

/* 调用smc操作,通知secure world执行使能共享内存cache的操作 */
		optee->invoke_fn(OPTEE_SMC_ENABLE_SHM_CACHE, 0, 0, 0, 0, 0, 0,
				 0, &res);
		if (res.a0 == OPTEE_SMC_RETURN_OK)
			break;
		optee_cq_wait_for_completion(&optee->call_queue, &w);
	}
	optee_cq_wait_final(&optee->call_queue, &w);
}

6. fast smc与std smc

  在OP-TEE驱动的挂载过程中会使用fast smc的方式从OP-TEE中获取到相关数据,例如从secure world中获取reserve的共享内存信息时就是通过调用如下函数来实现的:

invoke_fn(OPTEE_SMC_GET_SHM_CONFIG, 0, 0, 0, 0, 0, 0, 0, &res.smccc);

  在支持smc操作的32位系统中该函数等价于

arm_smccc_smc(OPTEE_SMC_GET_SHM_CONFIG, 0, 0, 0, 0, 0, 0, 0, &res.smccc);

  而OPTEE_SMC_ENABLE_SHM_CACHE的定义如下:

#define OPTEE_SMC_FUNCID_GET_SHM_CONFIG 7

#define OPTEE_SMC_GET_SHM_CONFIG OPTEE_SMC_FAST_CALL_VAL(OPTEE_SMC_FUNCID_GET_SHM_CONFIG)

  完全展开之后OPTEE_SMC_GET_SHM_CONFIG 宏的值的各个bit中的数值如下如所示:

  在执行smc操作时,cortex会解读第一个参数中的相关位来决定进入到monitor模式后的相关操作,在ARM官方定义了第一个参数(a0)的定义如下

  当bit31为1时,则表示进入monitor模式后会执行fast smc的中断处理函数,而在不带ATF的ARCH32中,monitor的中断向量表如下:

FUNC thread_vector_table , :
UNWIND(	.fnstart)
UNWIND(	.cantunwind)
	b	vector_std_smc_entry
	b	vector_fast_smc_entry
	b	vector_cpu_on_entry
	b	vector_cpu_off_entry
	b	vector_cpu_resume_entry
	b	vector_cpu_suspend_entry
	b	vector_fiq_entry
	b	vector_system_off_entry
	b	vector_system_reset_entry
UNWIND(	.fnend)
END_FUNC thread_vector_table

  由此可以见,驱动在挂载的时候请求共享内存配置数据的请求将会被vector_fast_smc_entry处理。而当请求来自于CA的请求时,驱动会将第一个参数的bi31设置成0,也就是CA的请求会被vector_std_smc_entry进行处理

总结

  OP-TEE的驱动的挂载过程来看,OP-TEE驱动会分别针对libteec和tee_supplicant建立不同的设备/dev/tee0和/dev/teepriv0。并且为两个设备中的des执行各自独有的operation,并建立类似消息队列来存放normal world与secure world之间的请求,这样libteec和tee_supplicant使用OP-TEE驱动的时候就能做到相对的独立。secure world与OP-TEE驱动之间使用共享内存进行数据交互。用于作为共享内存的物理内存块在OP-TEE启动的时做MMU初始化时需要被reserve出来,在OP-TEE挂载过程中需要将该内存块映射到系统内存中。





 

猜你喜欢

转载自blog.csdn.net/shuaifengyun/article/details/72954707