OPTEE学习笔记 - 驱动初始化

与前面的OPTEE学习笔记 - 驱动分享不同,本次基于kernel-5.12版本和aarch64来记录OPTEE在REE侧驱动的初始化过程。

根据前文的描述,tee驱动初始化的起始处仍是subsys_initcall和module_init,只不过在kernel 5.12中,用module_platform_driver替换了module_init,但是效果是一样的。subsys_initcall的优先级要高于module_platform_driver,因此subsys_initcall注册的函数优先执行,即执行了tee_init函数,如下。

static int __init tee_init(void)
{
	int rc;

	tee_class = class_create(THIS_MODULE, "tee");
	if (IS_ERR(tee_class)) {
		pr_err("couldn't create class\n");
		return PTR_ERR(tee_class);
	}

	rc = alloc_chrdev_region(&tee_devt, 0, TEE_NUM_DEVICES, "tee");
	if (rc) {
		pr_err("failed to allocate char dev region\n");
		goto out_unreg_class;
	}

	rc = bus_register(&tee_bus_type);
	if (rc) {
		pr_err("failed to register tee bus\n");
		goto out_unreg_chrdev;
	}

	return 0;

out_unreg_chrdev:
	unregister_chrdev_region(tee_devt, TEE_NUM_DEVICES);
out_unreg_class:
	class_destroy(tee_class);
	tee_class = NULL;

	return rc;
}

subsys_initcall(tee_init);

从代码中可知,tee_init创建了2个全局变量:tee_class和tee_devt,类型分别为class和dev_t。驱动在最开始的时候是要创建一个/dev下的设备,并为这个设备提供驱动服务,我们知道,创建一个设备并绑定ops是要经过以下几个步骤的:

  • alloc_chrdev_region:创建一个字符设备
  • cdev_init:字符设备绑定到ops
  • cdev_device_add:字符设备注册到系统中

那么tee驱动是如何注册这个设备的呢,上面的代码我们能看到已经创建了一个字符型的设备,后面的init和add代码是在哪里以及如何被调用的呢。下面就要接着上面说到module_platform_driver注册的driver了:optee_driver

static struct platform_driver optee_driver = {
	.probe  = optee_probe,
	.remove = optee_remove,
	.driver = {
		.name = "optee",
		.of_match_table = optee_dt_match,
	},
};

optee_driver被调用的时候,是调用了optee_probe来初始化驱动,其中就有我们想看到的cdev_init和cdev_add

optee_probe

optee_probe的函数内容大致分为4个部分:

  1. 获取ree和tee通信的函数接口,后面和tee侧通信都是调用了这个函数完成的,函数的实现方式可以暂时参考OPTEE学习笔记 - REE与TEE通信。新版kernel的改动后续会再发出。
  2. 配置共享内存
  3. 注册设备
  4. 队列初始化

下面我们就这4个部分介绍一下

获取REE和TEE的通信接口

在optee_probe的开始部分会调用get_invoke_func函数来配置invoke_fn

static optee_invoke_fn *get_invoke_func(struct device *dev)
{
	const char *method;

	pr_info("probing for conduit method.\n");

	if (device_property_read_string(dev, "method", &method)) {
		pr_warn("missing \"method\" property\n");
		return ERR_PTR(-ENXIO);
	}

	if (!strcmp("hvc", method))
		return optee_smccc_hvc;
	else if (!strcmp("smc", method))
		return optee_smccc_smc;

	pr_warn("invalid \"method\" property: %s\n", method);
	return ERR_PTR(-EINVAL);
}

static void optee_smccc_smc(unsigned long a0, unsigned long a1,
			    unsigned long a2, unsigned long a3,
			    unsigned long a4, unsigned long a5,
			    unsigned long a6, unsigned long a7,
			    struct arm_smccc_res *res)
{
	arm_smccc_smc(a0, a1, a2, a3, a4, a5, a6, a7, res);
}

获取接口函数这部分比较简单,根据在device tree中节点的描述内容,选择hvc接口还是smc接口,我们aarch64用的是smc接口,即optee_smccc_smc。有了这个接口,后续就可以直接和tee进行通信了

配置共享内存

在optee_probe的中间部分,是配置共享内存的代码,包括如下几段代码

    if (!optee_msg_exchange_capabilities(invoke_fn, &sec_caps)) { //获取optee支持的共享内存类型
		pr_warn("capabilities mismatch\n");
		return -EINVAL;
	}

	/*
	 * Try to use dynamic shared memory if possible
	 */
	if (sec_caps & OPTEE_SMC_SEC_CAP_DYNAMIC_SHM)
		pool = optee_config_dyn_shm(); //配置动态共享内存

	/*
	 * If dynamic shared memory is not available or failed - try static one
	 */
	if (IS_ERR(pool) && (sec_caps & OPTEE_SMC_SEC_CAP_HAVE_RESERVED_SHM))
		pool = optee_config_shm_memremap(invoke_fn, &memremaped_shm); //配置静态共享内存

	if (IS_ERR(pool))
		return PTR_ERR(pool);

在optee侧支持的security capability有以下几种:OPTEE_SMC_SEC_CAP_HAVE_RESERVED_SHM,OPTEE_SMC_SEC_CAP_VIRTUALIZATION,OPTEE_SMC_SEC_CAP_MEMREF_NULL。如果支持共享内存,还会配置OPTEE_SMC_SEC_CAP_DYNAMIC_SHM

为了方便研究共享内存的配置方法,我们以静态内存配置为例


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;
	unsigned long vaddr;
	phys_addr_t paddr;
	size_t size;
	phys_addr_t begin;
	phys_addr_t end;
	void *va;
	struct tee_shm_pool_mgr *priv_mgr;
	struct tee_shm_pool_mgr *dmabuf_mgr;
	void *rc;
	const int sz = OPTEE_SHM_NUM_PRIV_PAGES * PAGE_SIZE;

	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_err("static shm service not available\n");
		return ERR_PTR(-ENOENT);
	}

	if (res.result.settings != OPTEE_SMC_SHM_CACHED) {
		pr_err("only normal cached shared memory supported\n");
		return ERR_PTR(-EINVAL);
	}

    //获取optee提供的共享内存物理首地址和尺寸
	begin = roundup(res.result.start, PAGE_SIZE);
	end = rounddown(res.result.start + res.result.size, PAGE_SIZE);
	paddr = begin;
	size = end - begin;

	if (size < 2 * OPTEE_SHM_NUM_PRIV_PAGES * PAGE_SIZE) {
		pr_err("too small shared memory area\n");
		return ERR_PTR(-EINVAL);
	}

	va = memremap(paddr, size, MEMREMAP_WB); //映射共享内存的物理地址到虚拟地址
	if (!va) {
		pr_err("shared memory ioremap failed\n");
		return ERR_PTR(-EINVAL);
	}
	vaddr = (unsigned long)va;

	rc = tee_shm_pool_mgr_alloc_res_mem(vaddr, paddr, sz,
					    3 /* 8 bytes aligned */); //创建pool mgr来管理共享内存,rc是创建好的mgr
	if (IS_ERR(rc))
		goto err_memunmap;
	priv_mgr = rc; //共享内存前半部分分配给private manager使用

	vaddr += sz;
	paddr += sz;
	size -= sz;

	rc = tee_shm_pool_mgr_alloc_res_mem(vaddr, paddr, size, PAGE_SHIFT); //创建pool mgr来管理共享内存,rc是创建好的mgr
	if (IS_ERR(rc))
		goto err_free_priv_mgr;
	dmabuf_mgr = rc; //共享内存后半部分分配给dma buffer manager使用

	rc = tee_shm_pool_alloc(priv_mgr, dmabuf_mgr);
	if (IS_ERR(rc))
		goto err_free_dmabuf_mgr;

	*memremaped_shm = va;

	return rc;

err_free_dmabuf_mgr:
	tee_shm_pool_mgr_destroy(dmabuf_mgr);
err_free_priv_mgr:
	tee_shm_pool_mgr_destroy(priv_mgr);
err_memunmap:
	memunmap(va);
	return rc;
}


struct tee_shm_pool_mgr *tee_shm_pool_mgr_alloc_res_mem(unsigned long vaddr,
							phys_addr_t paddr,
							size_t size,
							int min_alloc_order)
{
	const size_t page_mask = PAGE_SIZE - 1;
	struct tee_shm_pool_mgr *mgr;
	int rc;

	/* Start and end must be page aligned */
	if (vaddr & page_mask || paddr & page_mask || size & page_mask)
		return ERR_PTR(-EINVAL);

	mgr = kzalloc(sizeof(*mgr), GFP_KERNEL); //创建pool manager
	if (!mgr)
		return ERR_PTR(-ENOMEM);

	mgr->private_data = gen_pool_create(min_alloc_order, -1); //pool manager的private data就是这个pool
	if (!mgr->private_data) {
		rc = -ENOMEM;
		goto err;
	}

	gen_pool_set_algo(mgr->private_data, gen_pool_best_fit, NULL); //配置分配内存算法
	rc = gen_pool_add_virt(mgr->private_data, vaddr, paddr, size, -1); //指定内存池地址和大小
	if (rc) {
		gen_pool_destroy(mgr->private_data);
		goto err;
	}

	mgr->ops = &pool_ops_generic; //配置alloc, free函数的实现,来操作内存池

	return mgr;
err:
	kfree(mgr);

	return ERR_PTR(rc);
}


struct tee_shm_pool *tee_shm_pool_alloc(struct tee_shm_pool_mgr *priv_mgr,
					struct tee_shm_pool_mgr *dmabuf_mgr)
{
	struct tee_shm_pool *pool;

	if (!check_mgr_ops(priv_mgr) || !check_mgr_ops(dmabuf_mgr))
		return ERR_PTR(-EINVAL);

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

	pool->private_mgr = priv_mgr;
	pool->dma_buf_mgr = dmabuf_mgr;

	return pool;
}

从代码中可以看出,整块共享内存被分成了2部分,前面的一部分分给private manager使用,用于驱动传递一些参数或者获取一些结果,8字节对齐,需要多少页的大小,可配(配置CONFIG_OPTEE_SHM_NUM_PRIV_PAGES);后面的一部分分给dma buffer,用来传递大块的数据,页对齐。

经过optee_config_shm_memremap,我们得到了一个 tee_shm_pool 的变量,用来管理共享内存池。他的结构可以如下描述:

struct tee_shm_pool 
struct tee_shm_pool_mgr *private_mgrstruct tee_shm_pool_mgr_ops *ops; alloc, free的操作函数
void *private_data; 从共享内存中分配出的private manager使用的内存池
struct tee_shm_pool_mgr *dma_buf_mgrstruct tee_shm_pool_mgr_ops *ops; alloc, free的操作函数
void *private_data; 从共享内存中分配出的dma buffer manager使用的内存池

注册设备

下面就到了我们在开篇提到的,cdev_init和cdev_device_add的实现

	optee = kzalloc(sizeof(*optee), GFP_KERNEL);
	if (!optee) {
		rc = -ENOMEM;
		goto err;
	}

	optee->invoke_fn = invoke_fn;
	optee->sec_caps = sec_caps;

	teedev = tee_device_alloc(&optee_desc, NULL, pool, optee);
	if (IS_ERR(teedev)) {
		rc = PTR_ERR(teedev);
		goto err;
	}
	optee->teedev = teedev;

	teedev = tee_device_alloc(&optee_supp_desc, NULL, pool, optee);
	if (IS_ERR(teedev)) {
		rc = PTR_ERR(teedev);
		goto err;
	}
	optee->supp_teedev = teedev;

	rc = tee_device_register(optee->teedev);
	if (rc)
		goto err;

	rc = tee_device_register(optee->supp_teedev);
	if (rc)
		goto err;

上面的代码中创建了一个optee的变量,最终,这个变量会被当做optee的driver data附着在device结构体上,这个变量索引了所有optee相关的重要数据。首先他记录了invoke_fn和sec_caps,这些是前面初始化的时候得到的;然后有记录了teedev,这个变量的创建在tee_device_alloc中完成。同时,cdev和dev的绑定在tee_device_register函数中完成。

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, max_id;
	int offs = 0;

	if (!teedesc || !teedesc->name || !teedesc->ops ||
	    !teedesc->ops->get_version || !teedesc->ops->open ||
	    !teedesc->ops->release || !pool)
		return ERR_PTR(-EINVAL);

	teedev = kzalloc(sizeof(*teedev), GFP_KERNEL);
	if (!teedev) {
		ret = ERR_PTR(-ENOMEM);
		goto err;
	}

	max_id = TEE_NUM_DEVICES / 2;

	if (teedesc->flags & TEE_DESC_PRIVILEGED) {
		offs = TEE_NUM_DEVICES / 2;
		max_id = TEE_NUM_DEVICES;
	}

	spin_lock(&driver_lock);
	teedev->id = find_next_zero_bit(dev_mask, max_id, offs);
	if (teedev->id < max_id)
		set_bit(teedev->id, dev_mask);
	spin_unlock(&driver_lock);

	if (teedev->id >= max_id) {
		ret = ERR_PTR(-ENOMEM);
		goto err;
	}

	snprintf(teedev->name, sizeof(teedev->name), "tee%s%d",
		 teedesc->flags & TEE_DESC_PRIVILEGED ? "priv" : "",
		 teedev->id - offs);

	teedev->dev.class = tee_class;
	teedev->dev.release = tee_release_device;
	teedev->dev.parent = dev;

	teedev->dev.devt = MKDEV(MAJOR(tee_devt), teedev->id);

	rc = dev_set_name(&teedev->dev, "%s", teedev->name);
	if (rc) {
		ret = ERR_PTR(rc);
		goto err_devt;
	}

	cdev_init(&teedev->cdev, &tee_fops);
	teedev->cdev.owner = teedesc->owner;

	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);

	teedev->desc = teedesc;
	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;
}

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;
	}

	teedev->dev.groups = tee_dev_groups;

	rc = cdev_device_add(&teedev->cdev, &teedev->dev); //cdev和dev绑定到了一起,注册进系统
	if (rc) {
		dev_err(&teedev->dev,
			"unable to cdev_device_add() %s, major %d, minor %d, err=%d\n",
			teedev->name, MAJOR(teedev->dev.devt),
			MINOR(teedev->dev.devt), rc);
		return rc;
	}

	teedev->flags |= TEE_DEVICE_FLAG_REGISTERED;
	return 0;
}

下面整理一下tee_device的主要结构

struct tee_device

 

char name

"tee0"或者“teepriv0”

struct tee_desc *desc

.name = DRIVER_NAME "-clnt",

.ops = &optee_ops, 包含optee_open, optee_release等操作

.owner = THIS_MODULE,

struct cdev cdev

 

struct device dev

.class = tee_class; tee_init创建的全局变量

.release = tee_release_device; 函数

.parent = NULL;

.devt = MKDEV(MAJOR(tee_devt), teedev->id); tee_devt是tee_init创建的全局变量

其他 

以上代码就解决了我们开篇时提出的问题,设备通过cdev_init和cdev_device_add注册到了系统里。同时,tee_fops也被绑定到了cdev上,这里tee_fops包含open, release, ioctl等操作。userspace通过ioctl调用到kernel,会先调用到tee_fops下的ioctl接口,即tee_ioctl,tee_ioctl而后会通过dev找到tee_device类型的变量teedev,然后调用其的teedev->desc->ops对应的处理函数进行处理。这里会调用到optee相对应的处理函数。

队列初始化

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

	INIT_LIST_HEAD(&optee->call_queue.waiters);
	optee_wait_queue_init(&optee->wait_queue);
	optee_supp_init(&optee->supp);
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;
}

以REE发送指令到TEE侧为例,在使用队列的时候首先通过optee_cq_wait_init(&optee->call_queue, &w);把自己的请求queue到队列里,然后到TEE侧做一次访问,如果由于TEE侧没有空闲线程来接收请求而被拒绝,则调用optee_cq_wait_for_completion(&optee->call_queue, &w)函数,此函数使用kernel的complete机制进入等待。当其他的任务完成时,就会调用optee_cq_complete_one函数,从队列中再找一个等待的任务开始执行。等待的逻辑相关函数如下:


static void optee_cq_wait_init(struct optee_call_queue *cq,
			       struct optee_call_waiter *w)
{
	/*
	 * We're preparing to make a call to secure world. In case we can't
	 * allocate a thread in secure world we'll end up waiting in
	 * optee_cq_wait_for_completion().
	 *
	 * Normally if there's no contention in secure world the call will
	 * complete and we can cleanup directly with optee_cq_wait_final().
	 */
	mutex_lock(&cq->mutex);

	/*
	 * We add ourselves to the queue, but we don't wait. This
	 * guarantees that we don't lose a completion if secure world
	 * returns busy and another thread just exited and try to complete
	 * someone.
	 */
	init_completion(&w->c); //初始化completion,用于kernel的complete机制
	list_add_tail(&w->list_node, &cq->waiters);

	mutex_unlock(&cq->mutex);
}

static void optee_cq_wait_for_completion(struct optee_call_queue *cq,
					 struct optee_call_waiter *w)
{
	wait_for_completion(&w->c); //进入等待

	mutex_lock(&cq->mutex);

	/* Move to end of list to get out of the way for other waiters */
	list_del(&w->list_node);
	reinit_completion(&w->c);
	list_add_tail(&w->list_node, &cq->waiters);

	mutex_unlock(&cq->mutex);
}

static void optee_cq_complete_one(struct optee_call_queue *cq)
{
	struct optee_call_waiter *w;

	list_for_each_entry(w, &cq->waiters, list_node) {
		if (!completion_done(&w->c)) {
			complete(&w->c); //从cq中找一个未完成的任务,complete它。这会导致对应的wait_for_completion退出等待
			break;
		}
	}
}

static void optee_cq_wait_final(struct optee_call_queue *cq,
				struct optee_call_waiter *w)
{
	/*
	 * We're done with the call to secure world. The thread in secure
	 * world that was used for this call is now available for some
	 * other task to use.
	 */
	mutex_lock(&cq->mutex);

	/* Get out of the list */
	list_del(&w->list_node); //任务完成,删除这个任务

	/* Wake up one eventual waiting task */
	optee_cq_complete_one(cq); //再complete一个其他任务

	/*
	 * If we're completed we've got a completion from another task that
	 * was just done with its call to secure world. Since yet another
	 * thread now is available in secure world wake up another eventual
	 * waiting task.
	 */
	if (completion_done(&w->c))
		optee_cq_complete_one(cq);

	mutex_unlock(&cq->mutex);
}

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值