25. OP-TEE驅動篇----驅動編譯,加載和初始化(二)

    歷經一年多時間的系統整理合補充,《手機安全和可信應用開發指南: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;	//初始化字符設備的owner
	teedev->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_entry
	b	vector_fast_smc_entry
	b	vector_cpu_on_entry
	b	vector_cpu_off_entry
	b	vector_cpu_resume_entry
	b	vector_cpu_suspend_entry
	b	vector_fiq_entry
	b	vector_system_off_entry
	b	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掛載過程中需要將該內存塊映射到系統內存中。





 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章