与前面的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个部分:
- 获取ree和tee通信的函数接口,后面和tee侧通信都是调用了这个函数完成的,函数的实现方式可以暂时参考OPTEE学习笔记 - REE与TEE通信。新版kernel的改动后续会再发出。
- 配置共享内存
- 注册设备
- 队列初始化
下面我们就这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_mgr | struct tee_shm_pool_mgr_ops *ops; alloc, free的操作函数 |
void *private_data; 从共享内存中分配出的private manager使用的内存池 | |
struct tee_shm_pool_mgr *dma_buf_mgr | struct 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);
}