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

京东购买地址

当当购买地址

天猫购买地址

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

为方便和及时的回复读者对书中或者TEE相关的问题的疑惑(每天必看一次),也为了大家能有一个统一的交流平台。我搭建了一个简单的论坛,网址如下:

https://www.huangtengxq.com/discuz/forum.php

关于您的疑问可在“相关技术讨论“”中发帖,我会逐一回复。也欢迎大家发帖,一起讨论TEE相关的一些有意思的feature。共同交流。同时该论坛中也会添加关于移动端虚拟化的相关技术的板块,欢迎各位共同交流学习

驱动与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; //初始化字符设备的ownerteedev->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_entryb   vector_fast_smc_entryb  vector_cpu_on_entryb    vector_cpu_off_entryb   vector_cpu_resume_entryb    vector_cpu_suspend_entryb   vector_fiq_entryb   vector_system_off_entryb    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挂载过程中需要将该内存块映射到系统内存中。

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

  1. intel x520网卡驱动_手工编译linux桌面内核(二)——硬件驱动的配置 下篇

    前言: 前面的方法讲完了,接下来我们来看看实例(我自己电脑的配置). 这里我只打算列出几项重要的驱动配置来,其它的请自行查阅gentoo wiki! 再次强调,这是我自己电脑的硬件驱动配置,不可能完全 ...

  2. linux系统LCD驱动(三):mtk lcd驱动lcm的加载以及初始化

    上一篇博文(linux系统LCD驱动(二):mtk lcd驱动fb_info初始化)https://blog.csdn.net/Ian22l/article/details/105929192 提到m ...

  3. oracle10 64位odbc,图文教你64位win10添加oracle odbc驱动时提示无法加载oracle如何解决...

    现在电脑已成为我们工作生活的一部分,相信大家在操作电脑时一定会遇到这样那样的问题,64位win10添加oracle odbc驱动时提示无法加载oracle如何解决这个问题就是我们经常会遇到的,这样的问 ...

  4. windows10驱动 x64--- 3环代码加载驱动(二)

    windows10驱动 x64--- 3环代码加载驱动 一:了解驱动加载工具 二:应用层--3环代码示例 一:了解驱动加载工具 平时调试.sys 我们都是用的驱动加载工具:open(打开驱动文件) - ...

  5. nginx php动态编译加载模块.

    #Nginx动态编译加载模块步骤 #查看目前Nginx版本及编译模块 #[root@centos7 ~]# /opt/app/lnmp/nginx-1.12.0/sbin/nginx -V #ngin ...

  6. RequireJS的加载、初始化等等 - 代码格式篇

    文章目录 RequireJS的加载.初始化,设置的方式有好几种. 一.格式 二.加载.初始化 RequireJS的加载.初始化,设置的方式有好几种. 一.格式 要求格式如下 // 1. 要求格式如下 ...

  7. 【Android】再来一篇Fragment懒加载(只加载一次哦)

    本篇文章已授权微信公众号 dasu_Android(大苏)独家发布 使用前需知 2017-7-14更新: 目前有人使用后出现了诸如首次打开显示空白界面,但点击有反应:或来回切换又变空白界面的问题.这些 ...

  8. phalapi-进阶篇3(自动加载和拦截器)

    phalapi-进阶篇3(自动加载和拦截器) 前言 先在这里感谢phalapi框架创始人@dogstar,为我们提供了这样一个优秀的开源框架. 最近工作上事情比较多所以更新比较慢希望大家见谅!这一次带 ...

  9. Centos编译加载toa模块

    什么是toa模块 toa模块是为了让后端的realserver能够看到真实的clientip而不是lvs的dip 安装步骤 安装依赖包 yum -y install kernel-devel gcc ...

  10. Android中apk加固完善篇之内存加载dex方案实现原理(不落地方式加载)

    一.前言 时隔半年,困扰的问题始终是需要解决的,之前也算是没时间弄,今天因为有人在此提起这个问题,那么就不能不解决了,这里写一篇文章记录一下吧.那么是什么问题呢? 就是关于之前的一个话题:Androi ...

最新文章

  1. hdu 4417(线段树OR树状数组)
  2. 数据来源渠道及采集工具_GrowingIO「转化数据回传」| 平台对接免开发,优化投放更便捷...
  3. webpack vue-cli 一有空格和分号就报错
  4. LINQPad工具-linq、sql、IL优化和转换
  5. Java实现 给定三个 int 变量, 求其中的最大值和最小值
  6. 微擎不升级解决头像显示问题。
  7. Java 1.1.5 空串与 Null 串
  8. 计算机中那些事儿(七):近期拆计算机小感
  9. 软件吞噬世界之前 SDS还要解决这些问题
  10. 当启动文档转换负载平衡器服务时出现如下问题The system cannot find the file specified的解决方案...
  11. 微信小程序图片转换成文字_怎么利用微信小程序把图片上的文字转到word文本中?...
  12. Linux下文件夹下子文件全部复制到多个文件夹中
  13. Verilog 参数化位宽转换设计实例
  14. 腾讯自研HIDS「洋葱」后台上云架构演进实践
  15. 几款非常优秀且常用的代码编辑器
  16. 电子计算机技发展趋势,现代电子信息技的现况及发展趋势.ppt
  17. java 生成随机码 字符数字图片等
  18. Tekton实战案例--S2I
  19. 第8章第15节:制作企业宣传册的公司团队第二页面 [PowerPoint精美幻灯片实战教程]
  20. ES6面试、复习干货知识点汇总

热门文章

  1. 互联网产品上线前,做些什么——产品、开发、测试的视角(转载)
  2. php语言中$意思,PHP语言中的lt;gt;符号是什么意思?
  3. SILC 超像素分割代码
  4. linux显示点阵字体,使用文泉驿点阵字体解决Linux中文化问题
  5. VSCode快捷键配置复制一行
  6. 【SDCC讲师专访】腾讯潘安群:腾讯云金融级数据库TDSQL分析
  7. 金三银四求职季,程序员面试必备——编程语言篇
  8. 科学方法 + 目标管理 = 学好英语
  9. nginx打包文件以及解压
  10. 时间中常用时区的英文缩写对照中文释义