27. OP-TEE驱动篇----libteec接口在驱动中的实现

来源:互联网 发布:mac查看隐藏文件 编辑:程序博客网 时间:2024/05/01 05:22

  libteec提供给上层使用的接口总共有十个,这十个接口的功能和定义介绍请参阅《21. OP-TEE中TA与CA执行流程-------libteec介绍》,这十个接口通过系统调用最终会调用到驱动中,在接口libteec中调用Open函数的时候,在驱动中就对调用到file_operations结构体变量tee_fops中的Open成员。同理在libteec接口中调用ioctl函数,则在驱动中最终会调用tee_fops中的ioctl函数。本文将介绍驱动中file_operation结构体变量tee_fops中各成员函数的具体实现.

1. libteec和tee_supplicant执行Open操作

  在libteec中调用open函数来打开/dev/tee0设备的时候,最终会调用到tee_fops中的open成员指定的函数指针tee_open,该函数的内容如下:

static int tee_open(struct inode *inode, struct file *filp){struct tee_context *ctx;/* 调用container_of函数,获取设备的tee_device变量的内容 。该变量对于/dev/tee0和/dev/teepriv0设备时不一样的,这点可以在驱动过载的过程中查阅*/ctx = teedev_open(container_of(inode->i_cdev, struct tee_device, cdev));if (IS_ERR(ctx))return PTR_ERR(ctx);filp->private_data = ctx;return 0;}static struct tee_context *teedev_open(struct tee_device *teedev){int rc;struct tee_context *ctx;/* 标记该设备的使用者加一 */if (!tee_device_get(teedev))return ERR_PTR(-EINVAL);/* 分配tee_context结构体体空间 */ctx = kzalloc(sizeof(*ctx), GFP_KERNEL);if (!ctx) {rc = -ENOMEM;goto err;}/* 将tee_context结构体中的teedev变量赋值 */ctx->teedev = teedev;INIT_LIST_HEAD(&ctx->list_shm);/*调用设备的des中的open执行设备级别的open操作*/rc = teedev->desc->ops->open(ctx); if (rc)goto err;return ctx;err:kfree(ctx);tee_device_put(teedev);return ERR_PTR(rc);}
  对于设备级别(/dev/tee0和/dev/teepriv0),最终会调用到optee_open函数,该函数内容如下:

static int optee_open(struct tee_context *ctx){struct optee_context_data *ctxdata;struct tee_device *teedev = ctx->teedev;struct optee *optee = tee_get_drvdata(teedev);/* 分配optee_context_data 结构体变量空间 */ctxdata = kzalloc(sizeof(*ctxdata), GFP_KERNEL);if (!ctxdata)return -ENOMEM;/* 通过teedev的值是否为 optee->supp_teedev,以此来判定当前的Open操作是打开/dev/tee0设备还是/dev/teepriv0设备,如果相等,则表示当前是打开/dev/teepriv0设备 */if (teedev == optee->supp_teedev) {bool busy = true; //标记/dev/teepriv0正在使用mutex_lock(&optee->supp.mutex);if (!optee->supp.ctx) {busy = false;optee->supp.ctx = ctx;}mutex_unlock(&optee->supp.mutex);if (busy) {kfree(ctxdata);return -EBUSY;}}/* 初始化互斥体和队列 */mutex_init(&ctxdata->mutex);INIT_LIST_HEAD(&ctxdata->sess_list);/* 赋值 */ctx->data = ctxdata;return 0;}

2. libteec和tee_supplicant执行release操作

  当在libteec和tee_supplicant打开了对应的设备之后,如果需要release设备,则可以调用该设备的release来实现,在userspace层面调用完成之后,最终会调用到OP-TEE驱动的release成员变量tee_release,该函数内容如下:

static int tee_release(struct inode *inode, struct file *filp){teedev_close_context(filp->private_data);return 0;}static void teedev_close_context(struct tee_context *ctx){struct tee_shm *shm;/* 调用/dev/tee0或者是/dev/teepriv0设备的release操作函数 */ctx->teedev->desc->ops->release(ctx);mutex_lock(&ctx->teedev->mutex);/* 清空设备分配的共享内存,并将其指针指向NULL */list_for_each_entry(shm, &ctx->list_shm, link)shm->ctx = NULL;mutex_unlock(&ctx->teedev->mutex);tee_device_put(ctx->teedev); //设备使用者数量减一,如果已经没有使用者则将desc指向NULLkfree(ctx);}
ctx->teedev->desc->ops->release(ctx);将会执行optee_release函数,其内容如下:
static void optee_release(struct tee_context *ctx){struct optee_context_data *ctxdata = ctx->data;struct tee_device *teedev = ctx->teedev;struct optee *optee = tee_get_drvdata(teedev);struct tee_shm *shm;struct optee_msg_arg *arg = NULL;phys_addr_t parg;struct optee_session *sess;struct optee_session *sess_tmp;if (!ctxdata)return;/* 分配驱动与secure world之间的共享内存 */shm = tee_shm_alloc(ctx, sizeof(struct optee_msg_arg), TEE_SHM_MAPPED);if (!IS_ERR(shm)) {arg = tee_shm_get_va(shm, 0); //获取共享内存的虚拟地址/* * If va2pa fails for some reason, we can't call * optee_close_session(), only free the memory. Secure OS * will leak sessions and finally refuse more sessions, but * we will at least let normal world reclaim its memory. */if (!IS_ERR(arg))tee_shm_va2pa(shm, arg, &parg); //解析共享内存的虚拟地址得到物理地址,存放在parg中}/*遍历存放使用该设备的所有session通知secure worldOP-TEE执行关闭session操作*/list_for_each_entry_safe(sess, sess_tmp, &ctxdata->sess_list, list_node) {list_del(&sess->list_node);if (!IS_ERR_OR_NULL(arg)) {memset(arg, 0, sizeof(*arg));arg->cmd = OPTEE_MSG_CMD_CLOSE_SESSION;arg->session = sess->session_id;optee_do_call_with_arg(ctx, parg);}kfree(sess);}kfree(ctxdata);/* 释放共享内存 */if (!IS_ERR(shm))tee_shm_free(shm);ctx->data = NULL;/* 如果是对/dev/teepriv0设备进行release操作,则指向optee_supp_release操作,释放该设备在使用的时候建立的各种队列 */if (teedev == optee->supp_teedev)optee_supp_release(&optee->supp);}

3. libteec和tee_supplicant中执行ioctl操作

  在libteec中获取OP-TEE版本信息,创建和关闭session,调用TA,分配和注册共享内存和fd以及释放共享内存,接收来自OP-TEE的请求以及回复数据给OP-TEE都是通过ioctl来完成的。在libteec和tee_supplicant中通过带上对应的参数调用ioctl函数来实现对应的操作需求,最终会调用到OP-TEE驱动中file_operation结构体变量tee_fops变量中的tee_ioctl函数,该函数的内容如下:

static long tee_ioctl(struct file *filp, unsigned int cmd, unsigned long arg){struct tee_context *ctx = filp->private_data;//获取设备的私有数据void __user *uarg = (void __user *)arg; //定义指向userspace层面输入参数的指针变量switch (cmd) {/* 和获取OP-TEE OS的版本 */case TEE_IOC_VERSION:return tee_ioctl_version(ctx, uarg);/* 分配,注册,释放共享内存操作 */case TEE_IOC_SHM_ALLOC:return tee_ioctl_shm_alloc(ctx, uarg);/* 注册共享文件句柄 */case TEE_IOC_SHM_REGISTER_FD:return tee_ioctl_shm_register_fd(ctx, uarg);/* 打开CA与TA通信的session */case TEE_IOC_OPEN_SESSION:return tee_ioctl_open_session(ctx, uarg);/* 调用TA,让根据参数中的command ID让TA执行特定的command */case TEE_IOC_INVOKE:return tee_ioctl_invoke(ctx, uarg);/* 通知TA取消掉对某一个来自normal world请求的响应 */case TEE_IOC_CANCEL:return tee_ioctl_cancel(ctx, uarg);/* 关闭CA与TA之间通信的session */case TEE_IOC_CLOSE_SESSION:return tee_ioctl_close_session(ctx, uarg);/* 接收来自secure world的请求 */case TEE_IOC_SUPPL_RECV:return tee_ioctl_supp_recv(ctx, uarg);/* 根据来自secure world的请求执行处理后回复数据给secure world */case TEE_IOC_SUPPL_SEND:return tee_ioctl_supp_send(ctx, uarg);default:return -EINVAL;}}

3.1 获取OP-TEE版本信息

libteec和tee_supplicant调用ioctl来获取OP-TEE OS的版本信息时,驱动会执行tee_ioctl_version函数,该函数内容如下:

static int tee_ioctl_version(struct tee_context *ctx,     struct tee_ioctl_version_data __user *uvers){struct tee_ioctl_version_data vers;/* 调用设备的get_version操作 */ctx->teedev->desc->ops->get_version(ctx->teedev, &vers);/* 判定该操作是来至于tee_supplicant还是libteec */if (ctx->teedev->desc->flags & TEE_DESC_PRIVILEGED)vers.gen_caps |= TEE_GEN_CAP_PRIVILEGED;/* 将获取到的版本信息数据拷贝到userspace层面提供的buffer中 */if (copy_to_user(uvers, &vers, sizeof(vers)))return -EFAULT;return 0;}
而设备的get_version的内容如下:

static void optee_get_version(struct tee_device *teedev,      struct tee_ioctl_version_data *vers){struct tee_ioctl_version_data v = {.impl_id = TEE_IMPL_ID_OPTEE,.impl_caps = TEE_OPTEE_CAP_TZ,.gen_caps = TEE_GEN_CAP_GP,};*vers = v;}

3.2 分配和注册共享内存操作

  当libteec和tee_supplicant需要分配和注册于secure world之间的共享内存时,可以通过调用驱动的ioctl方法来实现,然后驱动调用tee_ioctl_shm_alloc来实现具体的分配,注册共享内存的操作。该函数的内容如下:

static int tee_ioctl_shm_alloc(struct tee_context *ctx,       struct tee_ioctl_shm_alloc_data __user *udata){long ret;struct tee_ioctl_shm_alloc_data data;struct tee_shm *shm;/* 将userspace传递的参数数据拷贝到kernel的buffer中 */if (copy_from_user(&data, udata, sizeof(data)))return -EFAULT;/* Currently no input flags are supported */if (data.flags)return -EINVAL;/* 将共享内存的ID值设置成-1,以便分配好共享内存之后重新赋值 */data.id = -1;/* 调用tee_shm_all函数,从驱动与secure world之间的共享内存池中分配对应大小的内存,并设定对应的ID值 */shm = tee_shm_alloc(ctx, data.size, TEE_SHM_MAPPED | TEE_SHM_DMA_BUF);if (IS_ERR(shm))return PTR_ERR(shm); /* 设定需要返回给userspace的数据 */data.id = shm->id;data.flags = shm->flags;data.size = shm->size;/* 将需要返回的数据从kernespace拷贝到userspace层面 */if (copy_to_user(udata, &data, sizeof(data)))ret = -EFAULT;elseret = tee_shm_get_fd(shm);/* * When user space closes the file descriptor the shared memory * should be freed or if tee_shm_get_fd() failed then it will * be freed immediately. */tee_shm_put(shm); //如果分配的是DMA的buffer则要减少count值return ret;}

而在tee_shm_all中驱动做了什么操作呢?该函数的内容如下:

struct tee_shm *tee_shm_alloc(struct tee_context *ctx, size_t size, u32 flags){struct tee_device *teedev = ctx->teedev;struct tee_shm_pool_mgr *poolm = NULL;struct tee_shm *shm;void *ret;int rc;/* 判定参数是否合法 */if (!(flags & TEE_SHM_MAPPED)) {dev_err(teedev->dev.parent,"only mapped allocations supported\n");return ERR_PTR(-EINVAL);}/* 判定flags,表明该操作只能从驱动的私有共享呢村或者DMA buffer中进行分配 */if ((flags & ~(TEE_SHM_MAPPED | TEE_SHM_DMA_BUF))) {dev_err(teedev->dev.parent, "invalid shm flags 0x%x", flags);return ERR_PTR(-EINVAL);}/* 获取设备结构体 */if (!tee_device_get(teedev))return ERR_PTR(-EINVAL);/* 判定设备的poll成员是否存在,该成员在驱动挂载的时候会被初始化成驱动与secure world之间的共享内存池结构体 */if (!teedev->pool) {/* teedev has been detached from driver */ret = ERR_PTR(-EINVAL);goto err_dev_put;}/* 分配存放shm的kernel空间的内存 */shm = kzalloc(sizeof(*shm), GFP_KERNEL);if (!shm) {ret = ERR_PTR(-ENOMEM);goto err_dev_put;}/* 设定该块共享内存的flag以及对应的使用者 */shm->flags = flags;shm->teedev = teedev;shm->ctx = ctx;/* 通过传入的flags来判定是从驱动的私有共享内存池还是工dma buffer中来进行分配 */if (flags & TEE_SHM_DMA_BUF)poolm = &teedev->pool->dma_buf_mgr;elsepoolm = &teedev->pool->private_mgr;/* 调用pool中对应的alloc操作分配size大小的共享内存 */rc = poolm->ops->alloc(poolm, shm, size);if (rc) {ret = ERR_PTR(rc);goto err_kfree;}/* 获取分配好的共享内存的id */mutex_lock(&teedev->mutex);shm->id = idr_alloc(&teedev->idr, shm, 1, 0, GFP_KERNEL);mutex_unlock(&teedev->mutex);if (shm->id < 0) {ret = ERR_PTR(shm->id);goto err_pool_free;}/* 如果指定的是DMA buffer,则指定相关的operation */if (flags & TEE_SHM_DMA_BUF) {DEFINE_DMA_BUF_EXPORT_INFO(exp_info);exp_info.ops = &tee_shm_dma_buf_ops;exp_info.size = shm->size;exp_info.flags = O_RDWR;exp_info.priv = shm;shm->dmabuf = dma_buf_export(&exp_info);if (IS_ERR(shm->dmabuf)) {ret = ERR_CAST(shm->dmabuf);goto err_rem;}}/* 将分配的内存链接到共享内存队列的末尾 */mutex_lock(&teedev->mutex);list_add_tail(&shm->link, &ctx->list_shm);mutex_unlock(&teedev->mutex);return shm;err_rem:mutex_lock(&teedev->mutex);idr_remove(&teedev->idr, shm->id);mutex_unlock(&teedev->mutex);err_pool_free:poolm->ops->free(poolm, shm);err_kfree:kfree(shm);err_dev_put:tee_device_put(teedev);return ret;}
  从整个过程来看,如果在libteec执行共享内存的分配或者是注册操作时,驱动都会从驱动与secure world的共享内存池中分配一块内存,然后将该分配好的内存的id值返回给libteec,然后在libteec中,如果是调用TEEC_AllocateSharedMemory函数,则会将该共享内存的id值进行mmap,然后将map后的值赋值给shm中的buffer成员。如果调用的是TEEC_RegisterSharedMemory,则会将共享内存id执行mmap之后的值赋值给shm中的shadow_buffer成员。

  由此可见,当libteec中是执行注册共享内存操作时,并不是讲userspace的内存直接共享给secure world,而是将userspace的内存与驱动中分配的一块共享内存做shadow操作,是两者实现一个类似映射的关系。

3.3 libteec中打开session的操作

  当用户调用libteec中的TEEC_OpenSession接口时会执行驱动中ioctl函数的TEE_IOC_OPEN_SESSION分支去执行tee_ioctl_open_session函数,该函数只会在打开了/dev/tee0设备之后才能被使用,其的内容如下:

static int tee_ioctl_open_session(struct tee_context *ctx,  struct tee_ioctl_buf_data __user *ubuf){int rc;size_t n;struct tee_ioctl_buf_data buf;struct tee_ioctl_open_session_arg __user *uarg;struct tee_ioctl_open_session_arg arg;struct tee_ioctl_param __user *uparams = NULL;struct tee_param *params = NULL;bool have_session = false;/* 判定设备是否存在open_session函数 */if (!ctx->teedev->desc->ops->open_session)return -EINVAL;/* 将userspace传入的参数拷贝到kernelspace */if (copy_from_user(&buf, ubuf, sizeof(buf)))return -EFAULT;/* 判定传入的参数的大小是否合法 */if (buf.buf_len > TEE_MAX_ARG_SIZE ||    buf.buf_len < sizeof(struct tee_ioctl_open_session_arg))return -EINVAL;/* 为兼容性64位系统做出转换,并将数据拷贝到arg变量中 */uarg = u64_to_user_ptr(buf.buf_ptr);if (copy_from_user(&arg, uarg, sizeof(arg)))return -EFAULT;if (sizeof(arg) + TEE_IOCTL_PARAM_SIZE(arg.num_params) != buf.buf_len)return -EINVAL;/* 判定userspace层面传递的参数的个数并在kernelspace中分配对应的空间,将该空间的起始地址保存在params,然后userspace中的参数数据存放到params中 */if (arg.num_params) {params = kcalloc(arg.num_params, sizeof(struct tee_param), GFP_KERNEL);if (!params)return -ENOMEM;uparams = uarg->params; /* 将来自userspace的参数数据保存到刚刚分配的params指向的内存中 */rc = params_from_user(ctx, params, arg.num_params, uparams);if (rc)goto out;}/* 调用设备的open_session方法,并将参数传递給该函数  */rc = ctx->teedev->desc->ops->open_session(ctx, &arg, params);if (rc)goto out;have_session = true;if (put_user(arg.session, &uarg->session) ||    put_user(arg.ret, &uarg->ret) ||    put_user(arg.ret_origin, &uarg->ret_origin)) {rc = -EFAULT;goto out;}/* 将从secure world中返回的数据保存到userspace对应的buffer中 */rc = params_to_user(uparams, arg.num_params, params);out:/* * If we've succeeded to open the session but failed to communicate * it back to user space, close the session again to avoid leakage. *//* 如果调用不成功则执行close session操作 */if (rc && have_session && ctx->teedev->desc->ops->close_session)if (params) {/* Decrease ref count for all valid shared memory pointers */for (n = 0; n < arg.num_params; n++)if (tee_param_is_memref(params + n) &&    params[n].u.memref.shm)tee_shm_put(params[n].u.memref.shm);kfree(params);}return rc;}
  调用设备的open_session操作来完成向OP-TEE发送打开与特定TA的session操作,open_session函数的执行流程如下图所示:

  整个调用中
会调用optee_do_call_with_arg函数来完成驱动与secure world之间的交互,该函数的内容如下:
u32 optee_do_call_with_arg(struct tee_context *ctx, phys_addr_t parg){struct optee *optee = tee_get_drvdata(ctx->teedev);struct optee_call_waiter w;struct optee_rpc_param param = { };u32 ret;/* 设定触发smc操作的第一个参数a0的值为OPTEE_SMC_CALL_WITH_ARG    通过OPTEE_SMC_CALL_WITH_ARG值可以知道,该函数将会执行std的smc操作 */param.a0 = OPTEE_SMC_CALL_WITH_ARG;reg_pair_from_64(¶m.a1, ¶m.a2, parg);/* Initialize waiter *//* 初始化调用的等待队列 */optee_cq_wait_init(&optee->call_queue, &w);/*进入到loop循环,触发smc操作并等待secure world的返回*/while (true) {struct arm_smccc_res res;/* 触发smc操作 */optee->invoke_fn(param.a0, param.a1, param.a2, param.a3, param.a4, param.a5, param.a6, param.a7, &res);/* 判定secure world是否超时,如果超时,完成一次啊调用,进入下一次循环    知道secure world端完成open session请求 */if (res.a0 == OPTEE_SMC_RETURN_ETHREAD_LIMIT) {/* * Out of threads in secure world, wait for a thread * become available. */optee_cq_wait_for_completion(&optee->call_queue, &w);} else if (OPTEE_SMC_RETURN_IS_RPC(res.a0)) {/* 处理rpc操作 */param.a0 = res.a0;param.a1 = res.a1;param.a2 = res.a2;param.a3 = res.a3;optee_handle_rpc(ctx, ¶m);} else {/* 创建session完成之后跳出loop,并返回a0的值 */ret = res.a0;break;}}/* * We're done with our thread in secure world, if there's any * thread waiters wake up one. *//* 执行等待队列最后完成操作 */optee_cq_wait_final(&optee->call_queue, &w);return ret;}

3.4 libteec中执行invoke的操作

  当完成了session打开之后,用户就可以调用TEEC_InvokeCommand接口来调用对应的TA中执行特定的操作了,TEEC_InvokeCommand函数最终会调用驱动的tee_ioctl_invoke函数来完成具体的操作。该函数内如如下:

static int tee_ioctl_invoke(struct tee_context *ctx,    struct tee_ioctl_buf_data __user *ubuf){int rc;size_t n;struct tee_ioctl_buf_data buf;struct tee_ioctl_invoke_arg __user *uarg;struct tee_ioctl_invoke_arg arg;struct tee_ioctl_param __user *uparams = NULL;struct tee_param *params = NULL;/* 参数检查 */if (!ctx->teedev->desc->ops->invoke_func)return -EINVAL;/* 数据拷贝到kernel space */if (copy_from_user(&buf, ubuf, sizeof(buf)))return -EFAULT;if (buf.buf_len > TEE_MAX_ARG_SIZE ||    buf.buf_len < sizeof(struct tee_ioctl_invoke_arg))return -EINVAL;uarg = u64_to_user_ptr(buf.buf_ptr);if (copy_from_user(&arg, uarg, sizeof(arg)))return -EFAULT;if (sizeof(arg) + TEE_IOCTL_PARAM_SIZE(arg.num_params) != buf.buf_len)return -EINVAL;/* 组合需要传递到secure world中的参数buffer */if (arg.num_params) {params = kcalloc(arg.num_params, sizeof(struct tee_param), GFP_KERNEL);if (!params)return -ENOMEM;uparams = uarg->params;rc = params_from_user(ctx, params, arg.num_params, uparams);if (rc)goto out;}/* 使用对应的session出发smc操作 */rc = ctx->teedev->desc->ops->invoke_func(ctx, &arg, params);if (rc)goto out;/* 检查和解析返回的数据,并将数据拷贝到userspace用户体用的Buffser中 */if (put_user(arg.ret, &uarg->ret) ||    put_user(arg.ret_origin, &uarg->ret_origin)) {rc = -EFAULT;goto out;}rc = params_to_user(uparams, arg.num_params, params);out:if (params) {/* Decrease ref count for all valid shared memory pointers */for (n = 0; n < arg.num_params; n++)if (tee_param_is_memref(params + n) &&    params[n].u.memref.shm)tee_shm_put(params[n].u.memref.shm);kfree(params);}return rc;}










阅读全文
0 0
原创粉丝点击