安全内核服务架构
重要的数据结构:
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指令调度安全世界中的任务。