OP-TEE driver(五):libteec库中的接口在驱动中的实现
驱动挂载完成后,CA程序通过调用libteec库中的接口调用OP-TEE驱动来穿透到OP-TEE中,然后调用对应的TA程序。
OP-TEE驱动在挂载完成后会在/dev目录下分别创建两个设备节点,分别为/dev/tee0和/dev/teepriv,对/dev/tee0设备进行相关操作就能够穿透到OP-TEE中实现特定请求的发送。
1、libteec库中的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库中的release操作
当libteec库和tee_supplicant打开了对应的设备后,如果需要释放打开的设备,则可调用该设备的release操作来实现,在用户空间调用该操作后,最终会调用到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);
//设备使用者数量减一。如果已经没有使用者,则将desc指向NULL
tee_device_put(ctx->teedev);
kfree(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 (! IS_ERR(arg))
//解析共享内存的虚拟地址得到物理地址,存放在parg中
tee_shm_va2pa(shm, arg, &parg);
}
/*遍历存放使用该设备的所有session通知OP-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执行get_version操作
在libteec库中获取OP-TEE的版本信息,会调用/dev/tee0的TEE_IOC_VERSION类型的ioctl操作,该操作最终会调用到tee_ioctl_version函数来完成获取OP-TEE版本信息的操作,该函数的内容和注释如下:
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;
}
4、libteec库中的open session操作
当用户调用libteec库中的TEEC_OpenSession接口时会执行OP-TEE驱动中ioctl函数的TEE_IOC_OPEN_SESSION分支去执行tee_ioctl_open_session函数,该函数只会在打开/dev/tee0设备后才能被使用。整个open session的操作流程如图9-3所示。
调用过程中使用optee_do_call_with_arg函数来完成驱动与OP-TEE之间的交互。该函数的内容和说明如下:
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);
/* 初始化调用的等待队列 */
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) {
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;
}
}
/* 执行等待队列最后完成操作 */
optee_cq_wait_final(&optee->call_queue, &w);
return ret;
}
5、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;
}
发现没,咱们在实现CA的十个API调入最后都是调入到driver中了。