• OP-TEE驱动篇----驱动编译,加载和初始化(一)【转】


    转自:https://blog.csdn.net/shuaifengyun/article/details/72934531

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

    京东购买地址

    当当购买地址

    天猫购买地址

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

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

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

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

     

     OP-TEE驱动主要作用是REE与TEE端进行数据交互的桥梁作用。tee_supplicant和libteec调用接口之后几乎都会首先通过系统调用陷入到kernel space,然后kernel根据传递的参数找到OP-TEE驱动,并命中驱动的operation结构体中的具体处理函数来完成实际的操作,对于OP-TEE驱动,一般都会触发SMC调用,并带参数进入到ARM cortex的monitor模式,在monitor模式中对执行normal world和secure world的切换,待状态切换完成之后,会将驱动端带入的参数传递給OP-TEE中的thread进行进一步的处理。OP-TEE驱动的源代码存放在linux/drivers/tee目录中,其内容如下;

    1. OP-TEE驱动的加载

      OP-TEE驱动的加载过程分为两部分,第一部分是创建class和分配设备号,第二部分就是probe过程。在正式介绍之前首先需要明白两个linux kernel中加载驱动的函数:subsys_initcall和module_init函数。OP-TEE驱动的第一部分是调用subsys_initcall函数来实现,而第二部分则是调用module_init来实现。整个OP-TEE驱动的初始化流程图如下图所示:

    1.1 OP-TEE驱动模块的编译后的存放位置和加载过程

      OP-TEE驱动通过subsys_initcall和module_init宏来告知系统在初始化的什么时候去加载OP-TEE驱动,subsys_initcall定义在linux/include/init.h文件中,内容如下:

    1.  
      #define __define_initcall(fn, id)
    2.  
      static initcall_t __initcall_##fn##id __used
    3.  
      __attribute__((__section__(".initcall" #id ".init"))) = fn;
    4.  
       
    5.  
      #define core_initcall(fn) __define_initcall(fn, 1)
    6.  
      #define core_initcall_sync(fn) __define_initcall(fn, 1s)
    7.  
      #define postcore_initcall(fn) __define_initcall(fn, 2)
    8.  
      #define postcore_initcall_sync(fn) __define_initcall(fn, 2s)
    9.  
      #define arch_initcall(fn) __define_initcall(fn, 3)
    10.  
      #define arch_initcall_sync(fn) __define_initcall(fn, 3s)
    11.  
      #define subsys_initcall(fn) __define_initcall(fn, 4)
    12.  
      #define subsys_initcall_sync(fn) __define_initcall(fn, 4s)
    13.  
      #define fs_initcall(fn) __define_initcall(fn, 5)
    14.  
      #define fs_initcall_sync(fn) __define_initcall(fn, 5s)
    15.  
      #define rootfs_initcall(fn) __define_initcall(fn, rootfs)
    16.  
      #define device_initcall(fn) __define_initcall(fn, 6)
    17.  
      #define device_initcall_sync(fn) __define_initcall(fn, 6s)
    18.  
      #define late_initcall(fn) __define_initcall(fn, 7)
    19.  
      #define late_initcall_sync(fn) __define_initcall(fn, 7s)

      使用subsys_initcall宏定义的函数最终会被编译到.initcall4.init段中,linux系统在启动的时候会执行initcallx.init段中的所有内容,而使用subsys_initcall宏定义段的执行优先级为4.

    module_init的定义和相关扩展在linux/include/linux/module.h文件和linux/include/linux/init.h中,内容如下:

    1.  
      #define device_initcall(fn) __define_initcall(fn, 6)
    2.  
      #define __initcall(fn) device_initcall(fn)
    3.  
      #define module_init(x) __initcall(x);

      由此可见,使用module_init宏构造的函数将会在编译的时候被编译到initcall6.init段中,该段在linux系统启动的过程中的优先等级为6.

      结合上述两点看,在系统加载OP-TEE驱动的时候,首先会执行OP-TEE驱动中使用subsys_init定义的函数,然后再执行使用module_init定义的函数。在OP-TEE驱动源代码中使用subsys_init定义的函数为tee_init,使用module_init定义的函数为optee_driver_init。

    1.2 tee_init函数初始化设备号和class

      该函数定义在linux/drivers/tee/tee_core.c文件中,主要完成class的创建和设备号的分配,其内容如下:

    1.  
      static int __init tee_init(void)
    2.  
      {
    3.  
      int rc;
    4.  
       
    5.  
      /* 分配OP-TEE驱动的class */
    6.  
      tee_class = class_create(THIS_MODULE, "tee");
    7.  
      if (IS_ERR(tee_class)) {
    8.  
      pr_err("couldn't create class ");
    9.  
      return PTR_ERR(tee_class);
    10.  
      }
    11.  
       
    12.  
      /* 分配OP-TEE的设备号 */
    13.  
      rc = alloc_chrdev_region(&tee_devt, 0, TEE_NUM_DEVICES, "tee");
    14.  
      if (rc) {
    15.  
      pr_err("failed to allocate char dev region ");
    16.  
      class_destroy(tee_class);
    17.  
      tee_class = NULL;
    18.  
      }
    19.  
       
    20.  
      return rc;
    21.  
      }

    设备号和class将会在驱动执行probe的时候被使用到

    1.3 optee_driver_init函数执行

      linux启动过程中会执行moudule_init宏定义的函数,在OP-TEE驱动的挂载过程中将会执行optee_driver_init函数,该函数定义在linux/drivers/tee/optee/core.c文件中,其内容如下:

    1.  
      static int __init optee_driver_init(void)
    2.  
      {
    3.  
      struct device_node *fw_np;
    4.  
      struct device_node *np;
    5.  
      struct optee *optee;
    6.  
       
    7.  
      /* Node is supposed to be below /firmware */
    8.  
      /* 从device tree中查找到firware的节点 */
    9.  
      fw_np = of_find_node_by_name(NULL, "firmware");
    10.  
      if (!fw_np)
    11.  
      return -ENODEV;
    12.  
       
    13.  
      /* 匹配device tree中firmware节点下节点为linaro,optee-tz内容的节点 */
    14.  
      np = of_find_matching_node(fw_np, optee_match);
    15.  
      of_node_put(fw_np);
    16.  
      if (!np)
    17.  
      return -ENODEV;
    18.  
       
    19.  
      /* 使用查找到的节点执行OP-TEE驱动的probe操作 */
    20.  
      optee = optee_probe(np);
    21.  
      of_node_put(np);
    22.  
       
    23.  
      if (IS_ERR(optee))
    24.  
      return PTR_ERR(optee);
    25.  
      /* 保存初始化完成之后OP-TEE的设备信息到optee_svc中,以备在卸载的是使用 */
    26.  
      optee_svc = optee;
    27.  
       
    28.  
      return 0;
    29.  
      }

    2. OP-TEE驱动初始化时的probe操作

      OP-TEE驱动在optee_driver_init函数来完成probe操作。该函数首先会通过device tree找到OP-TEE驱动设备信息,然后将获取到的信息传递給optee_probe函数执行probe操作。optee_probe函数内容如下:

    1.  
      static struct optee *optee_probe(struct device_node *np)
    2.  
      {
    3.  
      optee_invoke_fn *invoke_fn;
    4.  
      struct tee_shm_pool *pool;
    5.  
      struct optee *optee = NULL;
    6.  
      void *memremaped_shm = NULL;
    7.  
      struct tee_device *teedev;
    8.  
      u32 sec_caps;
    9.  
      int rc;
    10.  
       
    11.  
      /* 获取OP-TEE驱动在device tree中节点描述内容中定义的执行切换到monitor模式的接口 */
    12.  
      invoke_fn = get_invoke_func(np);
    13.  
      if (IS_ERR(invoke_fn))
    14.  
      return (void *)invoke_fn;
    15.  
       
    16.  
      /* 调用到secure world中获取API的版本信息是否匹配 */
    17.  
      if (!optee_msg_api_uid_is_optee_api(invoke_fn)) {
    18.  
      pr_warn("api uid mismatch ");
    19.  
      return ERR_PTR(-EINVAL);
    20.  
      }
    21.  
       
    22.  
      /* 调用到secure world中获取版本信息检查是否匹配 */
    23.  
      if (!optee_msg_api_revision_is_compatible(invoke_fn)) {
    24.  
      pr_warn("api revision mismatch ");
    25.  
      return ERR_PTR(-EINVAL);
    26.  
      }
    27.  
       
    28.  
      /* 调用到secure world中获取secure world是否reserved share memory */
    29.  
      if (!optee_msg_exchange_capabilities(invoke_fn, &sec_caps)) {
    30.  
      pr_warn("capabilities mismatch ");
    31.  
      return ERR_PTR(-EINVAL);
    32.  
      }
    33.  
       
    34.  
      /*
    35.  
      * We have no other option for shared memory, if secure world
    36.  
      * doesn't have any reserved memory we can use we can't continue.
    37.  
      */
    38.  
      /* 判定sercure world中是否reserve了share memory,如果没有则报错 */
    39.  
      if (!(sec_caps & OPTEE_SMC_SEC_CAP_HAVE_RESERVED_SHM))
    40.  
      return ERR_PTR(-EINVAL);
    41.  
       
    42.  
      /* 配置secure world与驱动之间的share memory,并进行地址映射建立共享内存池 */
    43.  
      pool = optee_config_shm_memremap(invoke_fn, &memremaped_shm);
    44.  
      if (IS_ERR(pool))
    45.  
      return (void *)pool;
    46.  
       
    47.  
      /* 在kernel space内存空间中分配一块内存用于存放OP-TEE驱动的结构体变量 */
    48.  
      optee = kzalloc(sizeof(*optee), GFP_KERNEL);
    49.  
      if (!optee) {
    50.  
      rc = -ENOMEM;
    51.  
      goto err;
    52.  
      }
    53.  
       
    54.  
      /* 将驱动用于实现进入monitor模式的接口赋值到optee结构体中的invoke_fn成员中 */
    55.  
      optee->invoke_fn = invoke_fn;
    56.  
       
    57.  
      /* 分配设备信息,填充被libteec使用的驱动文件信息和operation结构体并创建/dev/tee0文
    58.  
      件,libteec将会使用该文件来使用op-tee驱动 */
    59.  
      teedev = tee_device_alloc(&optee_desc, NULL, pool, optee);
    60.  
      if (IS_ERR(teedev)) {
    61.  
      rc = PTR_ERR(teedev);
    62.  
      goto err;
    63.  
      }
    64.  
      optee->teedev = teedev; //libteec使用的驱动文件信息填充到optee中的teedev成员中
    65.  
       
    66.  
      /* 分配设备信息,填充被tee_supplicant使用的驱动文件信息和operation结构体并创
    67.  
      建/dev/teepriv0文件,tee_supplicant将会使用该文件来使用op-tee驱动 */
    68.  
      teedev = tee_device_alloc(&optee_supp_desc, NULL, pool, optee);
    69.  
      if (IS_ERR(teedev)) {
    70.  
      rc = PTR_ERR(teedev);
    71.  
      goto err;
    72.  
      }
    73.  
      //将tee_supplicant使用的驱动文件信息填充到optee中的supp_teedev成员中
    74.  
      optee->supp_teedev = teedev;
    75.  
       
    76.  
      /* 将被libteec使用的设备信息注册到系统设备中 */
    77.  
      rc = tee_device_register(optee->teedev);
    78.  
      if (rc)
    79.  
      goto err;
    80.  
       
    81.  
      /* 将被tee_supplicant使用的设备信息注册到系统设备中 */
    82.  
      rc = tee_device_register(optee->supp_teedev);
    83.  
      if (rc)
    84.  
      goto err;
    85.  
       
    86.  
      mutex_init(&optee->call_queue.mutex);
    87.  
      INIT_LIST_HEAD(&optee->call_queue.waiters);
    88.  
       
    89.  
      /* 初始化RPC操作队列 */
    90.  
      optee_wait_queue_init(&optee->wait_queue);
    91.  
       
    92.  
      /* 初始化被tee_supplicant用到的用于存放来自TA的请求的队列 */
    93.  
      optee_supp_init(&optee->supp);
    94.  
       
    95.  
      /* 填充optee中的共享内存地址信息和共享内存池信息成员 */
    96.  
      optee->memremaped_shm = memremaped_shm;
    97.  
      optee->pool = pool;
    98.  
       
    99.  
      /* 使能共享内存的cache */
    100.  
      optee_enable_shm_cache(optee);
    101.  
       
    102.  
      pr_info("initialized driver ");
    103.  
      return optee;
    104.  
      err:
    105.  
      if (optee) {
    106.  
      /*
    107.  
      * tee_device_unregister() is safe to call even if the
    108.  
      * devices hasn't been registered with
    109.  
      * tee_device_register() yet.
    110.  
      */
    111.  
      tee_device_unregister(optee->supp_teedev);
    112.  
      tee_device_unregister(optee->teedev);
    113.  
      kfree(optee);
    114.  
      }
    115.  
      if (pool)
    116.  
      tee_shm_pool_free(pool);
    117.  
      if (memremaped_shm)
    118.  
      memunmap(memremaped_shm);
    119.  
      return ERR_PTR(rc);
    120.  
      }

    2.1 获取切换到monitor模式的接口

      normal world穿透到secure world是通过在monitor模式下设定SCR寄存器中的NS位来实现的,OP-TEE驱动被上层调用时,最终会通过出发smc切换到monitor,见数据发送给secure world来进行处理。而用户出发smc请求的接口函数将在驱动初始化的时候被填充到OP-TEE驱动的device info中,在OP-TEE驱动中通过调用get_invoke_func函数来获取,该函数的内如如下:

    1.  
      static optee_invoke_fn *get_invoke_func(struct device_node *np)
    2.  
      {
    3.  
      const char *method;
    4.  
       
    5.  
      pr_info("probing for conduit method from DT. ");
    6.  
       
    7.  
      /* 获取op-tee驱动在device tree中的节点中的method属性的值 */
    8.  
      if (of_property_read_string(np, "method", &method)) {
    9.  
      pr_warn("missing "method" property ");
    10.  
      return ERR_PTR(-ENXIO);
    11.  
      }
    12.  
       
    13.  
      /* 判定op-tee驱动是使用smc的方式还是使用hvc的方式来实现进入monitor模式的操作,
    14.  
      根据method的值与hvc还是smc匹配来决定那种切换方法,并将用于切换到monitor的
    15.  
      接口 */
    16.  
      if (!strcmp("hvc", method))
    17.  
      return optee_smccc_hvc;
    18.  
      else if (!strcmp("smc", method))
    19.  
      return optee_smccc_smc;
    20.  
       
    21.  
      pr_warn("invalid "method" property: %s ", method);
    22.  
      return ERR_PTR(-EINVAL);
    23.  
      }

    以optee_smccc_smc为例,该函数的内容如下:

    1.  
      static void optee_smccc_smc(unsigned long a0, unsigned long a1,
    2.  
      unsigned long a2, unsigned long a3,
    3.  
      unsigned long a4, unsigned long a5,
    4.  
      unsigned long a6, unsigned long a7,
    5.  
      struct arm_smccc_res *res)
    6.  
      {
    7.  
      arm_smccc_smc(a0, a1, a2, a3, a4, a5, a6, a7, res);
    8.  
      }

      也即是函数get_invoke_func执行完成之后会返回arm_smccc_smc函数的地址。arm_smccc_smc函数就是驱动用来将cortex切换到monitor模式的函数,该函数是以汇编的方式编写,定义在linux/arch/arm/kernel/smccc-call.S文件中。如果是64位系统,则该函数定义在linux//arch/arm64/kernel/smccc-call.S目录中,本文以32位系统为例,该函数内容如下:

    1.  
      /*
    2.  
      * Wrap c macros in asm macros to delay expansion until after the
    3.  
      * SMCCC asm macro is expanded.
    4.  
      */
    5.  
      /*SMCCC_SMC宏,触发smc*/
    6.  
      .macro SMCCC_SMC
    7.  
      __SMC(0)
    8.  
      .endm
    9.  
       
    10.  
      /*SMCCC_HVC宏,触发hvc*/
    11.  
      .macro SMCCC_HVC
    12.  
      __HVC(0)
    13.  
      .endm
    14.  
       
    15.  
      /* 定义SMCCC宏,其参数为instr */
    16.  
      .macro SMCCC instr
    17.  
      /* 将normal world中的寄存器入栈,保存现场 */
    18.  
      UNWIND( .fnstart)
    19.  
      mov r12, sp
    20.  
      push {r4-r7}
    21.  
      UNWIND( .save {r4-r7})
    22.  
      ldm r12, {r4-r7}
    23.  
      instr /* 执行instr参数的内容,即执行smc切换 */
    24.  
      pop {r4-r7} /* 出栈操作,恢复现场 */
    25.  
      ldr r12, [sp, #(4 * 4)]
    26.  
      stm r12, {r0-r3}
    27.  
      bx lr
    28.  
      UNWIND( .fnend)
    29.  
      .endm
    30.  
       
    31.  
      /*
    32.  
      * void smccc_smc(unsigned long a0, unsigned long a1, unsigned long a2,
    33.  
      * unsigned long a3, unsigned long a4, unsigned long a5,
    34.  
      * unsigned long a6, unsigned long a7, struct arm_smccc_res *res)
    35.  
      */
    36.  
      ENTRY(arm_smccc_smc)
    37.  
      SMCCC SMCCC_SMC
    38.  
      ENDPROC(arm_smccc_smc)

    2.2 校验API的UID和OP-TEE的版本信息

      驱动加载过程中获取到REE与TEE之间进行交互的接口函数(调用get_invoke_func函数返回的函数地址)之后,op-tee驱动会对API的UID和版本信息进行校验。上述操作是通过调用optee_msg_api_uid_is_optee_api函数和optee_msg_api_revision_is_compatible函数来实现的。两个函数的内容如下:

    1.  
      static bool optee_msg_api_uid_is_optee_api(optee_invoke_fn *invoke_fn)
    2.  
      {
    3.  
      struct arm_smccc_res res;
    4.  
       
    5.  
      /* 调用执行smc操作的接口函数,带入的commond ID为OPTEE_SMC_CALLS_UID */
    6.  
      invoke_fn(OPTEE_SMC_CALLS_UID, 0, 0, 0, 0, 0, 0, 0, &res);
    7.  
       
    8.  
      /* 比较返回的UID的值与在驱动中定义的UID的值是否匹配 */
    9.  
      if (res.a0 == OPTEE_MSG_UID_0 && res.a1 == OPTEE_MSG_UID_1 &&
    10.  
      res.a2 == OPTEE_MSG_UID_2 && res.a3 == OPTEE_MSG_UID_3)
    11.  
      return true;
    12.  
      return false;
    13.  
      }
    14.  
       
    15.  
      static bool optee_msg_api_revision_is_compatible(optee_invoke_fn *invoke_fn)
    16.  
      {
    17.  
      union {
    18.  
      struct arm_smccc_res smccc;
    19.  
      struct optee_smc_calls_revision_result result;
    20.  
      } res;
    21.  
       
    22.  
      /* 调用执行smc操作的接口函数,带入的commond ID为OPTEE_SMC_CALLS_REVISION*/
    23.  
      invoke_fn(OPTEE_SMC_CALLS_REVISION, 0, 0, 0, 0, 0, 0, 0, &res.smccc);
    24.  
       
    25.  
      /* 比较返回的版本信息的值与驱动中定义的版本值是否匹配 */
    26.  
      if (res.result.major == OPTEE_MSG_REVISION_MAJOR &&
    27.  
      (int)res.result.minor >= OPTEE_MSG_REVISION_MINOR)
    28.  
      return true;
    29.  
      return false;
    30.  
      }

    2.3 判定secure world是否预留了驱动与secure world之间的共享内存空间

      驱动与secure world之间需要进行数据的交互,而进行数据交互则需要一定的共享内存来保存sercure world和驱动之间共有的数据。所以在驱动初始化的时候需要检查该共享内存空间是否被预留出来。通过获取secure world中的相关变量的值并判定该flag是否相等来判定secure world是否预留了共享内存空间,在OP-TEE OS启动的时候,执行MMU初始化的时候会初始化该变量。在驱动端通过调用optee_msg_exchange_capabilities函数来获取该变量的值,其内容如下:

    1.  
      static bool optee_msg_exchange_capabilities(optee_invoke_fn *invoke_fn,
    2.  
      u32 *sec_caps)
    3.  
      {
    4.  
      union {
    5.  
      struct arm_smccc_res smccc;
    6.  
      struct optee_smc_exchange_capabilities_result result;
    7.  
      } res;
    8.  
      u32 a1 = 0;
    9.  
       
    10.  
      /*
    11.  
      * TODO This isn't enough to tell if it's UP system (from kernel
    12.  
      * point of view) or not, is_smp() returns the the information
    13.  
      * needed, but can't be called directly from here.
    14.  
      */
    15.  
      if (!IS_ENABLED(CONFIG_SMP) || nr_cpu_ids == 1)
    16.  
      a1 |= OPTEE_SMC_NSEC_CAP_UNIPROCESSOR;
    17.  
       
    18.  
      /* 调用smc操作接口,获取secure world中的变量 */
    19.  
      invoke_fn(OPTEE_SMC_EXCHANGE_CAPABILITIES, a1, 0, 0, 0, 0, 0, 0,
    20.  
      &res.smccc);
    21.  
       
    22.  
      if (res.result.status != OPTEE_SMC_RETURN_OK)
    23.  
      return false;
    24.  
       
    25.  
      *sec_caps = res.result.capabilities; //将返回值中的变量赋值为sec_caps
    26.  
      return true;
    27.  
      }

      当驱动获取到sec_caps的值之后会查看该值是否为宏OPTEE_SMC_SEC_CAP_HAVE_RESERVED_SHM定义的值BIT(0),如果该值不为BIT(0),则会报错,因为在secure world端都没有预留share memory空间,那驱动与secure world之间也就没法传输数据,所以有没有驱动也就没有必要了。

    下一章节将介绍驱动与secure world之间share memory的配置,共享池的设置以及OP-TEE驱动加载的剩下部分

  • 相关阅读:
    文件的上传&预览&下载学习(五)
    文件的上传&预览&下载学习(四)
    MySQL学习(一)大纲
    MySQL学习(四)锁机制
    MySQL学习(五)事务
    小程序在WXML页面添加的data属性,在点击事件中,获取的属性名皆为小写字母
    CSS解决数字,字母自动换行的问题添加wordbreak:breakall; wordwrap:breakword;
    理解CSS盒模型
    浮动
    对于第四章“流程控制”的学习和认识
  • 原文地址:https://www.cnblogs.com/sky-heaven/p/14206678.html
Copyright © 2020-2023  润新知