安全內核服務架構
重要的數據結構:
struct sw_global global_val;
/**
*@brief
Secure API configuration details for task
*/
typedef struct sa_config_t
創建任務
以創建dispatcher任務爲例。
1. 初始化任務。dispatch_task_init函數初始化安全API配置結構體sa_config_t,主要初始化:
a)服務信息:serviceid、service name。目前OV支持的服務類型見附錄C。
b)stack和heap大小。一般stack4K、heap 64K,heap至少爲256字節。注意,此時並沒有申請空間,只是記錄堆棧的大小。
c) 初始化服務運行態、羣組、服務是否允許多個實例以及此次申請的任務號。服務運行態有用戶態和內核態兩種狀態,dispatcher爲內核態。dispatcher服務屬於一般組羣。值得注意的是,此次創建的任務號需要從全局變量global_val的任務池中申請。global_val有一個任務池global_val.task_id_pool,存儲了所有任務的任務號。
d)填寫應用對此服務的訪問控制列表ACL。可以看出OV對服務進行了簡單的訪問控制。
e)填寫此任務的服務入口,即服務函數地址。
f)爲此服務申請一些私有數據,不同的服務有不同的定義。
2. 創建任務。
a)檢查系統設定是否允許創建此任務。主要檢查訪問控制列表以及服務是否多個實例,如果檢查失敗,返回錯誤。
b)爲此次任務申請heap空間。注意dispatcher這個任務的heap用的是全局變量global_val中的共享heap,不需要申請。
c)初始化任務,即結構體structsw_task。包括task id、service UUID、服務入口、運行模式、guest no、任務名稱(即服務字符串)。
d)爲此任務申請本地存儲空間tls/*!task local storage */,並將此空間映射到安全內存區域或任務指定的內存表中。然後進行將tls的private_date、process、堆棧大小、task id進行初始化。
((structsw_tls*)new_task->tls)->private_data = psa_config->data;
((structsw_tls*)new_task->tls)->process = psa_config->process;
((structsw_tls*)new_task->tls)->heap_size = psa_config->heap_size;
((structsw_tls*)new_task->tls)->min_alloc_size = psa_config->min_alloc_size;
((structsw_tls*)new_task->tls)->task_id = new_task->task_id;
e)根據任務的運行模式(用戶空間、內核空間)申請相應的棧空間,並設置好任務的棧指針。
new_task->task_sp_size= psa_config->stack_size;
if(new_task->mode== TASK_USER_MODE) {
new_task->task_sp= alloc_user_stack(new_task);
}
else{
new_task->task_sp= alloc_kernel_stack(psa_config->stack_size);
}
f)根據任務的運行模式(用戶空間、內核空間)申請相應的堆空間,並初始化tls結構中的堆空間:目前heap使用數爲0,起始地址指向堆開始地址。
heap_info->num_blocks_alloc= 0;
heap_info->heap_vir_addr= heap_start;
g)初始化任務鏈表中的各個鏈,然後用head指向自身,最後用head作爲索引添加到全局變量global_val中的全局任務列表裏。以後全局任務列表就能夠索引到本任務。
link_init(&new_task->head);
link_init(&new_task->ready_head);
link_init(&new_task->wait_head);
link_init(&new_task->file_dev_list);
link_init(&new_task->task_wait_list);
h)將任務狀態設置爲受阻:TASK_STATE_SUSPEND,表明此狀態還沒有準備運行。
3.初始化任務。安全內核的任務切換是直接從CPU寄存器進行切換的,就像是N、S世界直接的切換。爲了保存任務切換時任務的狀態,每個任務都有一個寄存器結構體,並且每個任務都有自己的堆棧,供任務運行時使用。初始化任務就是初始化寄存器結構體struct sw_task_cpu_regs 和堆棧的過程。
a)將寄存器結構體r0指向任務的私有存儲空間tls,初始化其他通用寄存器r1-r12爲0, .lr寄存器設置爲0,pc寄存器設置爲服務的入口地址(即服務的函數入口地址)。
b)設置棧指針sp爲棧頂。
c)設置CPU狀態寄存器spsr。
d) 將此任務添加到全局變量global_val中的準備運行任務列表(global_val.ready_to_run_list)。
至此,任務已經加入到全局任務中的待運行列表,可以執行了。
Monitor模式切換至NS世界
在inttzhyp_init(void)
{
interror;
ns_sys_current= (struct system_context *)global_val.tzhyp_val.ns_world;
s_sys_current= (struct system_context *)global_val.tzhyp_val.s_world;
。。。。
。。。。
}
global_val.tzhyp_val.ns_world爲max_cores*guests_no個struct system_context組成的數組如下: ns/s_sys_current指向數組頭
void global_init(void)
{
global_val.tzhyp_val.ns_world= sw_malloc(MAX_CORES * GUESTS_NO
*sizeof(struct system_context));
global_val.tzhyp_val.s_world= sw_malloc(MAX_CORES
*sizeof(struct system_context));
。。。
。。。
}
切換過程爲s_sys_current和n s_sys_current中cpu狀態的切換
n s_sys_current狀態的初始化
void mon_nscpu_context_init()
{
。。。。
。。。。
#ifdef LINUX_ATAG_BOOT_EN
core_ctxt->r0 = 0;
core_ctxt->r1 = LINUX_MACHINE_ID;
core_ctxt->r2 = (sw_uint)NORMAL_WORLD_RAM_START+ 0x100;
#endif
core_ctxt->spsr_mon= CPSR_RESET_VAL;
#ifdef OTZONE_ASYNC_NOTIFY_SUPPORT
primary_ns_world->notify_data= NULL;
#endif
/*
* Save cp15 reset state
*/
tzhyp_sysregs_save(cp15_ctxt);
}
.macro GET_CPU_ID rt
mrc p15,0, \rt, c0, c0, 5 @ Read CPU IDregister
and \rt, \rt, #0x03 @ Mask off, leaving the CPU ID field
.endm
extern struct system_context*ns_sys_current;
extern struct system_context*s_sys_current;
struct system_context {
/*CPU context */
structcore_context sysctxt_core;
structcp15_context sysctxt_cp15;
#ifdef CONFIG_NEON_SUPPORT
structvfp_context sysctxt_vfp;
#endif
/*Devices */ generic interrupt context
structgic_context sysctxt_gic;
sw_uintguest_no;
#ifdef OTZONE_ASYNC_NOTIFY_SUPPORT
/*!Shared memory for notification */
structotzc_notify_data *notify_data;
sw_uintpending_notify;
#endif
/*
* to make the size a power of 2, so thatmultiplication can be acheived
* by logical shift
*/
#ifndef CONFIG_NEON_SUPPORT
sw_uintpad[8];
#endif
} __attribute__ ((aligned(CACHELINE_SIZE)));
struct core_context {
sw_uintr0;
sw_uintr1;
sw_uintr2;
sw_uintr3;
sw_uintr4;
sw_uintr5;
sw_uintr6;
sw_uintr7;
sw_uintr8;
sw_uintr9;
sw_uintr10;
sw_uintr11;
sw_uintr12;
sw_uintspsr_mon; ///monitor模式沒有sp指針 所以恢復過程中棧指針不變
sw_uintlr_mon;
sw_uintspsr_svc;
sw_uintr13_svc;
sw_uintlr_svc;
sw_uintr13_sys;
sw_uintlr_sys;
sw_uintspsr_abt;
sw_uintr13_abt;
sw_uintlr_abt;
sw_uintspsr_undef;
sw_uintr13_undef;
sw_uintlr_undef;
sw_uintspsr_irq;
sw_uintr13_irq;
sw_uintlr_irq;
};
struct cp15_context {
sw_uintc0_CSSELR; /* Cache Size SelectionRegister */
sw_uintc1_SCTLR; /* System ControlRegister */
sw_uintc1_ACTLR; /* Auxilliary ControlRegister */
sw_uintc2_TTBR0; /* Translation Table BaseRegister 0 */
sw_uintc2_TTBR1; /* Translation Table BaseRegister 1 */
sw_uintc2_TTBCR; /* Translation Table BaseRegister Control */
sw_uintc3_DACR; /* Domain Access ControlRegister */
sw_uintc5_DFSR; /* Data Fault StatusRegister */
sw_uintc5_IFSR; /* Instruction FaultStatus Register */
sw_uintc6_DFAR; /* Data Fault AddressRegister */
sw_uintc6_IFAR; /* Instruction FaultAddress Register */
sw_uintc7_PAR; /* Physical AddressRegister */
sw_uintc10_PRRR; /* PRRR */
sw_uintc10_NMRR; /* NMRR */
sw_uintc12_VBAR; /* VBAR register */
sw_uintc13_FCSEIDR; /* FCSE PID Register */
sw_uintc13_CONTEXTIDR; /* Context ID Register */
sw_uintc13_TPIDRURW; /* User Read/Write Threadand Process ID */
sw_uintc13_TPIDRURO; /* User Read-only Threadand Process ID */
sw_uintc13_TPIDRPRW; /* Privileged only Threadand Process ID */
};
call_non_secure_kernel:
#ifndef CONFIG_SW_DEDICATED_TEE
push {r4, lr} /* the corresponding pops happens from
save_context */
push {r0 - r3}
#ifndef CONFIG_BOOT_SVISOR
b mon_switchto_nsworld
#else
mon_switchto_nsworld_ctx
b switch_to_hyp_mode
#endif /* CONFIG_BOOT_SVISOR */
#else /* CONFIG_SW_DEDICATED_TEE */
push {lr}
mov r0, #0
bl start_secondary_linux
pop {lr}
movs pc, lr
#endif
.func mon_switchto_nsworld
mon_switchto_nsworld:
mon_switchto_nsworld_ctx
///此時lr已恢復成ns世界的lr
push{r0}
scr_nsbit_setr0
pop {r0}
dsb
isb
movs pc, lr
//到ns世界相應指令處執行(即 struct system_context*ns_sys_current中的monitor模式lr)
.endfunc
.macro mon_switchto_nsworld_ctx
GET_CORE_CONTEXTs_sys_current
bl save_context
////執行完bl save_context後 s_sys_current中的monitor模式lr已保存爲smc指令之後的那條指令
GET_CORE_CONTEXTns_sys_current
bl restore_context
@clear local monitor
@-------------------
clrex
.endm
任務切換
安全內核通過中斷swi進行任務的調度,執行全局任務列表中那些待運行的任務。每次執行一次swi中斷,CPU處理一個任務列表中的任務。swi中斷執行後,CPU跳轉到安全中斷向量表中的swi中斷處理函數,此函數執行如下步驟:
1. 保存當前運行的任務的CPU上下文,包括spsr、r0-r12、lr寄存器(此時lr指向被中斷任務正在運行的PC寄存器值)。這些上下文信息保存在SVC模式下的棧頂,形成一個swi_temp_regs結構體。如果此時不保存,那麼這個任務的CPU上下文就會丟失,無法恢復此任務。
2. 將處理器模式轉換爲SYS模式,然後進入C語言的任務上下文切換工作。將處理器轉換爲SYS模式的目的是防止任務上下文切換影響保存在SVC模式棧頂CPU上下文(即spsr、r0-r12、lr寄存器)。
a)從ready to runlist獲取一個將要運行的任務,然後將當前正在運行的任務放入ready to run list。
b)將即將運行的任務狀態修改爲TASK_STATE_RUNNING。
3. 任務之間的上下文切換:
a)將被中斷任務的CPU上下文(在SYS模式下通過全局變量temp_swi_regs獲取對SVC模式中棧的訪問地址)存入任務自己的私有存儲空間的sw_task_cpu_regs結構體(此結構體除了spsr、r0-r12、lr,還有sp寄存器,不過sp應該沒有用):r0-r14、spsr、lr,注意lr賦值給任務CPU上下文存儲器中的pc寄存器,因爲lr目前存儲的就是任務中斷時下一條要運行的指令地址。
b)即將運行的任務將自己的CPU上下文信息:r0-r12、spsr、pc替換SVC模式下棧頂的各個寄存器,返回到swi中斷處理函數,注意的是此時CPU已經被修改爲SYS模式,所以需要通過MSR指令手動修改模式:
msr CPSR_c, #(ARCH_SVC_MODE | IRQ_BIT)
4. 從SVC模式下的棧頂彈出spsr寄存器,這個寄存器代表了即將運行任務的狀態寄存器,然後msr設置處理器,然後依次彈出棧頂的r0-r12,lc寄存器值分別賦值給r0-r12和pc寄存器。至此,處理器的狀態寄存器和pc寄存器都已經被設置爲新任務的CPU上下文,所以,下一步處理器就直接處理新任務。
/**
* @brief Structureto store register in SWI handler
*/
struct swi_temp_regs {
/*! spsr */
sw_uintspsr;
/*! regs r0- r12 */
sw_uintregs[13];
/*! linkregister */
sw_uintlr;
};
/**
* @brief Taskregisters context
*/
struct sw_task_cpu_regs {
/*!Registers r0 -r12 */
sw_uintregs[13];
/*! Stackpointer of the task */
sw_uint sp;
/*! Linkregister of the task */
sw_uint lr;
/*! SPSR ofthe task */
sw_uintspsr;
/*! CurrentPC of the task */
sw_uint pc;
#ifdef CONFIG_USER_PAGE_TABLE_ISOLATION
/*! TTBR0of the current task */
sw_uintttbr;
/*! ASID ofthe current task */
sw_uint asid;
#endif
};
安全內核的文件系統支持
目前OV的功能尚不完善,對與外設(SD卡)的數據交互支持的功能很少,目前只支持虛擬的文件系統,即數據的讀寫(open,read,write函數)實際上都是在內存中的虛擬文件系統中進行,SD卡的驅動函數,初始化函數都沒有實現,目前不能與SD卡進行數據交互。
在secure_main函數中,掛在文件系統的函數如下,
#ifdef CONFIG_FILESYSTEM_SUPPORT
#ifdef CONFIG_MMC_SUPPORT
fs_ret= mount_file_system((sw_short_int*)read_from_disk());
#else
fs_ret= mount_file_system((sw_short_int*)get_sw_fs_start());
#endif
if(fs_ret!= SW_ERROR) {
sw_printk("filesystem successfully mounted in FAT32 \n");
}
#endif
CONFIG_FILESYSTEM_SUPPORT配置項開啓安全內核對文件系統的支持,允許內核以文件的形式讀寫數據,#ifdefCONFIG_MMC_SUPPORT配置項允許內核從SD中加載根文件系統,但是跟蹤代碼後發現,SD卡設備的初始化函數如下,
board_mmc_init()
{
Return-1;
}
可見OV並沒有實現此函數,所以從SD卡加載根文件系統一定失敗,雖然有CONFIG_MMC_SUPPORT配置項,但實際上不支持從SD卡加載根文件系統。
如果CONFIG_MMC_SUPPORT配置項關閉,則在編譯安全內核的過程中,根文件系統也會一併編譯進來,所以內核加載完成後,文件系統已經在內存中了,外部變量_SW_FS_START(在linker.d.s中定義)表示其在內存中的地址。目前安全內核只支持fat32文件系統。mount_file_system函數讀取文件系統的引導塊,超級塊等信息,獲取扇區大小,簇大小,根目錄地址等統計信息,以此填充global_val.fs_context變量。
安全內核中的文件操作系統調用open,read,write,close函數,首先通過swi切換至supervisor模式,之後根據系統調用號調用file_open,file_read,file_write,file_close函數,這些函數根據global_val.fs_context中保存的文件系統的統計信息,計算出文件在文件系統中的位置,並讀寫數據。此時文件的讀寫均是在內存中的虛擬文件系統中進行,並沒有調用SD卡驅動程序與SD卡進行數據交互,實際上OV源碼中也並沒有相應的驅動程序。
安全內核實現了部分對SD卡的文件讀寫功能,實現在mmc.c中的mmc_bread,mmc_bwrite函數中。這些函數先填充mmc_cmd結構體,初始化給SD卡發送的讀寫指令,之後調用mmc->send_cmd函數,向SD卡發送讀寫請求。但是跟蹤代碼發現mmc的send_cmd函數並沒有實現。
安全服務調用
在普通世界裏,OV在內核層部署了一個通信代理,可以理解爲安全世界的驅動程序,負責將普通世界的用戶請求發送給安全世界中的安全服務。用戶層程序通過TEE Client API接口與通信代理通信。通信代理在用戶看來是一個硬件設備,以硬件驅動的方式來使用。下面首先描述用戶程序調用TEE Client API的流程。
注意:此圖與實際有差別,但是流程差不多
中斷向量表:monitor.S是monitor模式下的中斷向量表、cpu_start.S是安全世界的中斷向量表、正常世界的中斷向量表由Linux負責。
TEEC_InitializeContext:打開TEE設備。
TEEC_OpenSession:發送OTZ_CLIENT_IOCTL_SES_OPEN_REQ命令給TEE代理。
1. TEE代理:收到應用層發送的開啓會話請求後,TEE代理組裝SMC命令傳遞給TEE命令中的任務調度器(dispatcher)。儘管TEE代理收到的某個特定任務的服務,但是此次TEE組裝的命令service id爲OTZ_SVC_GLOBAL、service command爲OTZ_GLOBAL_CMD_ID_OPEN_SESSION,而真正的service id被封裝到了命令內容(req_buf_phys),sesssion id被封裝爲返回參數resp_buf_phys。組裝好命令之後,TEE代理利用寄存器r0-r2向S世界傳遞參數,
2. TEE代理調用SMC指令進行NS切換。
register u32 r0asm("r0") = CALL_TRUSTZONE_API;
register u32 r1asm("r1") = cmd_addr;
register u32 r2asm("r2") = OTZ_CMD_TYPE_NS_TO_SECURE;
3. Monitor:將r0-r3寄存器存入全局變量params_stack,設置全局變量valid_params_flag=0x1,然後切換CPU上下文,跳轉到安全世界的調度器。
4. Dispatcher:調度器首先從params_stack中提取出TEE代理組裝的SMC命令結構,然後利用SMC命令結構體中真正的service id按照上面的創建任務流程創建任務,創建任務過程會把新創建的任務號作爲session id寫到SMC命令結構體中,最後調用SMC返回到正常世界。
/* Service entrypoint */
psa_config->entry_point= (sw_uint)&gp_internal_api_test_task;
/* Service process */
psa_config->process =&process_otz_gp_internal_svc;
TEEC_InvokeCommand:發送OTZ_CLIENT_IOCTL_SEND_CMD_REQ命令給TEE代理。
1. TEE代理:1) 組裝SMC命令傳遞給TEE,主要包括:service id、command id、session id、輸入數據、輸出數據 ; 2) 然後調用SMC指令跳轉到安全世界。其中service id爲對應的UUID(此處我們以OTZ_SVC_GP_INTERNAL爲例),session id爲上一步TEE傳遞回來的session id。
2. 任務調度器:將r0-r3寄存器中值作爲任務參數傳遞給任務,然後將此任務加入到準備運行的任務列表(ready to run list)。
3. 調度器通過swi指令調度安全世界中的任務。