OPTEE学习笔记 - 驱动

以optee在REE侧的驱动为例,学习LINUX驱动。可以参考https://blog.csdn.net/shuaifengyun/article/details/72934531

OP-TEE驱动通过subsys_initcall和module_init宏来告知系统在初始化的什么时候去加载OP-TEE驱动,subsys_initcall定义在linux/include/init.h文件中。使用subsys_initcall宏定义的函数最终会被编译到.initcall4.init段中,linux系统在启动的时候会执行initcallx.init段中的所有内容,而使用subsys_initcall宏定义段的执行优先级为4.

module_init的定义和相关扩展在linux/include/linux/module.h文件和linux/include/linux/init.h中,使用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。

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");
		class_destroy(tee_class);
		tee_class = NULL;
	}

	return rc;
}
static int __init optee_driver_init(void)
{
	struct device_node *fw_np;
	struct device_node *np;
	struct optee *optee;

	/* Node is supposed to be below /firmware */

//寻找/sys/firmware/devicetree/base/firmware节点
	fw_np = of_find_node_by_name(NULL, "firmware"); 
	if (!fw_np)
		return -ENODEV;

//寻找/sys/firmware/devicetree/base/firmware/optee节点
	np = of_find_matching_node(fw_np, optee_match); 
	if (!np || !of_device_is_available(np))
		return -ENODEV;

	optee = optee_probe(np);
	of_node_put(np);

	if (IS_ERR(optee))
		return PTR_ERR(optee);

	optee_svc = optee;

	optee_bm_enable();

	return 0;
}
module_init(optee_driver_init);

关于创建class的好处和原因可以参考https://blog.csdn.net/yinwei520/article/details/6592060。下面摘录一些关键词句:

class能够自动创建/dev下的设备节点。当然class还有其另外的作用,且自动创建设备节点的还有udev系统,udev是处于用户空间的,其自动创建设备节点也是依赖于sysfs文件系统中提供的class类。一个类是一个设备的高级视图, 它抽象出低级的实现细节. 驱动可以见到一个SCSI 磁盘或者一个 ATA 磁盘, 在类的级别, 它们都是磁盘. 类允许用户空间基于它们做什么来使用设备, 而不是它们如何被连接或者它们如何工作。

总结下来创建一个tee的class就是为了创建/dev下的设备节点。

下面的optee_driver_init做了剩下的驱动初始化操作

其中of_find_node_by_name和of_find_matching_node是为了找到/sys/firmware/devicetree/base/firmware/optee节点。/sys/firmware/devicetree/base/firmware/optee/compatible的值为"linaro,optee-tz"。而optee_match如下

static const struct of_device_id optee_match[] = {
	{ .compatible = "linaro,optee-tz" },
	{},
};

这个dtb的节点是optee在tee侧初始化以后,启动ree侧linux的时候传入的参数

static int add_optee_dt_node(void *fdt)
{
        int offs;
        int ret;

        if (fdt_path_offset(fdt, "/firmware/optee") >= 0) {
                DMSG("OP-TEE Device Tree node already exists!\n");
                return 0;
        }

        offs = fdt_path_offset(fdt, "/firmware");
        if (offs < 0) {
                offs = fdt_path_offset(fdt, "/");
                if (offs < 0)
                        return -1;
                offs = fdt_add_subnode(fdt, offs, "firmware");
                if (offs < 0)
                        return -1;
        }

        offs = fdt_add_subnode(fdt, offs, "optee");
        if (offs < 0)
                return -1;

        ret = fdt_setprop_string(fdt, offs, "compatible", "linaro,optee-tz");
        if (ret < 0)
                return -1;
        ret = fdt_setprop_string(fdt, offs, "method", "smc");
        if (ret < 0)
                return -1;
        return 0;
}

获取这个device node的目的就是后面在选择invoke_function的时候会根据device node的method值来确定使用smc function还是hvc function。我这里的例子是smc。

下面就进入了optee_driver_init中最重要的optee_probe函数。optee_probe前半段的内容可参考https://blog.csdn.net/orlando19860122/article/details/85529476。这里从注册shared memory说起。

共享内存注册

shared memory大概的意思是ree和tee之间共享一块物理地址,通过这块物理地址进行通信。这块物理地址在tee侧初始化后在ree侧映射成虚拟地址并注册。ree侧首先根据物理地址映射出来的虚拟地址,调用内核函数gen_pool_create创建2块内存池,并保存在tee的struct tee_shm_pool数据结构中

struct tee_shm_pool {
	struct tee_shm_pool_mgr *private_mgr;
	struct tee_shm_pool_mgr *dma_buf_mgr;
};
struct tee_shm_pool_mgr {
	const struct tee_shm_pool_mgr_ops *ops;  //tee操作内存池的函数
	void *private_data; //kernel创建的内存池
};

optee_probe函数中共享内存注册关键函数 

	pool = optee_config_shm_memremap(invoke_fn, &memremaped_shm, sec_caps);
	if (IS_ERR(pool))
		return (void *)pool;
static struct tee_shm_pool *
optee_config_shm_memremap(optee_invoke_fn *invoke_fn, void **memremaped_shm,
			  u32 sec_caps)
{
	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;

//获取共享内存的物理地址和大小
	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);
	}

	if (res.result.settings != OPTEE_SMC_SHM_CACHED) {
		pr_err("only normal cached shared memory supported\n");
		return ERR_PTR(-EINVAL);
	}

	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变量中
	va = memremap(paddr, size, MEMREMAP_WB);
	if (!va) {
		pr_err("shared memory ioremap failed\n");
		return ERR_PTR(-EINVAL);
	}
	vaddr = (unsigned long)va;

	/*
	 * If OP-TEE can work with unregistered SHM, we will use own pool
	 * for private shm
	 */

//我的例子中,配置的是OPTEE_SMC_FUNCID_EXCHANGE_CAPABILITIES,所以走else分支
	if (sec_caps & OPTEE_SMC_SEC_CAP_DYNAMIC_SHM) {
		rc = optee_shm_pool_alloc_pages();
		if (IS_ERR(rc))
			goto err_memunmap;
		priv_mgr = rc;
	} else {
		const size_t sz = OPTEE_SHM_NUM_PRIV_PAGES * PAGE_SIZE;

		rc = tee_shm_pool_mgr_alloc_res_mem(vaddr, paddr, sz,
						    3 /* 8 bytes aligned */);
		if (IS_ERR(rc))
			goto err_memunmap;
		priv_mgr = rc;

		vaddr += sz;
		paddr += sz;
		size -= sz;
	}

	rc = tee_shm_pool_mgr_alloc_res_mem(vaddr, paddr, size, PAGE_SHIFT);
	if (IS_ERR(rc))
		goto err_free_priv_mgr;
	dmabuf_mgr = rc;

	rc = tee_shm_pool_alloc(priv_mgr, dmabuf_mgr); //把2块内存池添加到tee_shm_pool结构体中
	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);
	if (!mgr)
		return ERR_PTR(-ENOMEM);

//通过kernel函数gen_pool_create创建一个内存池,min_alloc_order是内存池最小存储单位的2的阶次,
//例如,如果min_alloc_order=3,即内存池最小存储单位是2^3=8,即8字节对齐
//例如,如果min_alloc_order=12,即内存池最小存储单位是2^12=4096,即4096字节对齐
	mgr->private_data = gen_pool_create(min_alloc_order, -1);
	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; //初始化tee pool的操作函数

	return mgr;
err:
	kfree(mgr);

	return ERR_PTR(rc);
}

---------------------------------------------------------------------------------------
static const struct tee_shm_pool_mgr_ops pool_ops_generic = {
	.alloc = pool_op_gen_alloc,
	.free = pool_op_gen_free,
	.destroy_poolmgr = pool_op_gen_destroy_poolmgr,
};

 设备注册

设备注册是optee_probe的第二部分工作。首先会创建2个设备,一个给libteec调用使用(CA调用TA),一个给tee_supplicant调用使用(TA调用CA)。然后分别将这个2个设备注册进系统。这2个设备分别是/dev/tee0和/dev/teepriv0。所涉及的结构体如下

struct optee {
	struct tee_device *supp_teedev;
	struct tee_device *teedev;
	optee_invoke_fn *invoke_fn;
	struct optee_call_queue call_queue;
	struct optee_wait_queue wait_queue;
	struct optee_supp supp;
	struct tee_shm_pool *pool;
	void *memremaped_shm;
	u32 sec_caps;
};
struct tee_device {
	char name[TEE_MAX_DEV_NAME_LEN];
	const struct tee_desc *desc;
	int id;
	unsigned int flags;

	struct device dev;
	struct cdev cdev;

	size_t num_users;
	struct completion c_no_users;
	struct mutex mutex;	/* protects num_users and idr */

	struct idr idr;
	struct tee_shm_pool *pool;
};
struct tee_shm_pool {
	struct tee_shm_pool_mgr *private_mgr;
	struct tee_shm_pool_mgr *dma_buf_mgr;
};

代码如下

//optee变量保存了tee设备,内存池,queue等变量
//在注册设备的时候通过drive_data设入device中,在后面的opt中会被当作参数传入具体的opt函数中
	optee = kzalloc(sizeof(*optee), GFP_KERNEL);
	if (!optee) {
		rc = -ENOMEM;
		goto err;
	}

	optee->invoke_fn = invoke_fn;
	optee->sec_caps = sec_caps;

//为libteec分配一个设备
	teedev = tee_device_alloc(&optee_desc, NULL, pool, optee);
	if (IS_ERR(teedev)) {
		rc = PTR_ERR(teedev);
		goto err;
	}
	optee->teedev = teedev;

//为tee_supplicant分配一个设备
	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); //向系统中添加/dev/tee0
	if (rc)
		goto err;

	rc = tee_device_register(optee->supp_teedev); //向系统中添加/dev/teepriv0
	if (rc)
		goto err;
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;

//TEE_NUM_DEVICES前一半分配给libteec,后一半给tee_supplicant
	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;
	}

//如果是libteec,则name=tee0,如果是tee_supplicant,则name=teepriv0
	snprintf(teedev->name, sizeof(teedev->name), "tee%s%d",
		 teedesc->flags & TEE_DESC_PRIVILEGED ? "priv" : "",
		 teedev->id - offs);

	teedev->dev.class = tee_class; //最开始通过tee_init创建的class
	teedev->dev.release = tee_release_device;
	teedev->dev.parent = dev; //dev=NULL

	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;
	teedev->cdev.kobj.parent = &teedev->dev.kobj; //字符型设备的parent是前面配置的dev

	dev_set_drvdata(&teedev->dev, driver_data); //把struct optee作为drive_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; //保存两个内存池的索引(这里的pool变量索引了2个内存池)

	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;
}

  两个队列的建立

建立2个队列是optee_probe的最后一步。

	mutex_init(&optee->call_queue.mutex);
	INIT_LIST_HEAD(&optee->call_queue.waiters);
	optee_wait_queue_init(&optee->wait_queue);
	optee_supp_init(&optee->supp);
	optee->memremaped_shm = memremaped_shm;
	optee->pool = pool;

	optee_enable_shm_cache(optee);

	if (optee->sec_caps & OPTEE_SMC_SEC_CAP_DYNAMIC_SHM)
		pr_info("dynamic shared memory is enabled\n");

	pr_info("initialized driver\n");
	return optee;

在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;
}

 

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值