现在,我们按照程序的运行顺序,理一下运行逻辑。系统复位后,从Secure的工程开始运行。复位时,CPU看出去,看到全部flash和sram都是安全区域。CPU处于安全状态。
【1】开始执行初始化任务; 【2】首先设置SAU,重塑CPU的世界观,不是所有区域都是安全的,0x0804开始的256K是非安全的,那里应该放非安全工程的代码;0x2001 8000开始的96K是非安全的,那里应该放非安全工程的堆栈。 【3】选项字节的配置让物理Flash的下半部分,具有非安全属性,和CPU视角一致;代码执行,让SRAM的后半部 【4】分配到非安全世界;GPIO 【5】本来都是默认安全的,代码仅保留PC.7是安全的,驱动LED1,其他GPIO引进都分到非安全世界去,其中PB.7驱动LED2。 【6】默认所有中断发生后,都是target到安全世界,即去安全世界里查阅中断向量表,取出中断ISR地址,如果需要,可以把某些中断retarget到非安全世界里。retarget的操作,只能在安全世界执行。 【7】CM33内核有两个systick,在S和NS世界独立运行,中断也是各有一个。现在先使能安全世界的systick,产生1ms定时。 【8】使能GTZC中断,这个中断线,默认是target在安全世界的。 【9】最后安全世界的初始化全部完成,跳到非安全世界执行。
和以前一样,复位向量ResetHandler里先执行SysteInit。
TZ_SAU_Setup是SystemInit里新增的一个操作。它作为内联函数,在《partition_stm32l552xx.h》中实现。主要有四部分功能组成:
首先是设置SAU区域。本来SAU默认是4G地址空间都是安全的,用户需要在这里根据应用,至少挖出3个区域来重新设置,就像这个例程里面,两个NS区域,分别给非安全工程的代码和堆栈,一个NSC给安全工程的被外调用的函数入口。如果要修改的话,不是改函数体里面,在这个样本实现里,拉到文件的最上面,在这里修改宏定义,region起始地址,结尾地址,region的属性。默认不改就是S,0就是NS,1就是NSC。
第二部分,设置系统控制快SCB里和低功耗,中断相关的属性。内核的低功耗,复位,是仅能被安全代码使能,还是安全/非安全代码都可以。还包括上上集讲内核时,提到的是否要把retarget到安全世界的中断向量,优先级都集体抬高,是否要把Bus fault,NMI异常retarget到非安全世界,同时把HardFault优先级提到比NMI异常的优先级还高。在这个gpio toggle例程里,我们都没有用到这些属性,因此都是使用默认设置。
第三部分是关于FPU浮点单元的设置,我们这个gpio toggle例程中没有用到浮点,暂时不去管它,使用默认设置。
第四部分,很重要,如果用户想把某些中断retarget到非安全世界,就在这里配置。改这四个value中的一个或某个。当然如果不在这里修改,也可以后面使用NVIC的API修改,但是一定要是在安全世界里才能执行。这个gpio toggle例程里也没有用到这个功能,但是下一集,我们会用到它。
继续安全项目里的安全属性初始化。GTZC模块,可以配置内容SRAM,外部flash,AHB,APB外设的安全属性。这个例子,我们是配置SRAM的前96K部分为安全,后160K部分为非安全。使用相同的HAL层API:GTZC_MPCBB_ConfigMem,配置区域由第一个参数里指明,SRAM1和SRAM2。其实,根据这个函数的名字,大家可能已经猜到,它是由STM32CubeMX自动生成的,我们确实可以通过CUbeMX在图形界面里配置,下一集我们会演示这个功能。
接下来,在GPIO配置里,更改PB.7原来的安全属性,从安全配置成不安全,它将被非安全工程代码控制,去翻转LED2的亮灭,
这不是一条普通的函数指针,因为有了CMSE_NS_CALL关键字的加持,编译器才会有。
第二大段之前,先看一下IAR窗口,调试一下,看汇编;
>> 执行BLXNS之前 vs. 之后,寄存器上下文
编译器产生的汇编语句,把general register都清零,只有R4有值,但是包含的是NS世界的信息;SP是当前S世界的堆栈,LR此时是调用返回后S世界下一句的地址。R4还是刚才的值,未变;SP自动切换到NS世界的堆栈;LR被硬件刷成0xF,都未暴露S世界的信息
现在,我们跳到非安全世界开始执行了,通过刚才看到的“CMSE_NS_CALL”这个关键字修饰的函数指针,以绝对地址调用的方式跳了过来。先跳到的是非安全工程的向量表里reset handler entry里包含的地址,执行最开始的初始化,结构和安全工程里差不多,也是调用SystemInit,只是这里的SystemInit没有做太多事情,直接跳到非安全工程的main函数里。
在main函数一来,就是调用安全世界的代码。这是怎么实现的呢? 【1】首先安全世界要先定义一些可供非安全世界调用的API, 【2】然后把它的入口放在安全世界里的NSC区域。当然这些都是编译器帮你去做的,只要在定义这些对外函数时,使用编译器的一些关键字。 【3】NS的代码,先跳到NSC区域,执行第一条安全指令SG,把CPU切换到安全状态,然后就可以执行紧随SG指令后面的,跳转到真正安全函数定义的跳转指令了。当然这些也都是编译器会拆解成合适的指令来实现。
可以看到,NS世界调用S的代码时,是知道API函数名字的,不是通过指向绝对地址的指针来调用。
那么NS世界是如何知道它可以调用的S世界的函数名字的呢?
同样也是通过编译器以及IDE的帮忙。
安全项目里有一个特殊的C文件,secure_nsc.c,里面定义了Secure_registerCallback;这个函数在定义时,使用了编译器关键字“CMSE_NS_ENTRY”,那么链接器会把该函数的入口地址连同SG指令,一起放到安全工程定义的NSC区域。同时,在安全项目里,指定把secure_nsc.c生成库文件secure_nsc.o,然后导入到非安全世界里。非安全世界再把secure_nsc.h头文件包含到项目里,它就知道自己可以调用哪些安全世界暴露出来的函数API接口了。
我们来看一下运行时代码是如何从非安全世界,跳到安全世界的。注意观察:跳过去时,编译器做了哪些安排?跳回来时,编译器做了哪些事情。
观察点:简单的是:NS世界的通用寄存器,LR,都带了过去;复杂的是:跳转几次, NS的起点,先到NS的veneer;再到S的NSC区域//SG,再到S的函数实际地方。执行完成后,返回到NS:相同的是要清零用到的通用寄存器(这里是R0、R1、R2),由于NS的LR还有效,直接BXNS LR,即可返回。
小结一下,函数调用引起的从NS跳到S过程中,编译器无需做现场清零工作,共享的通用寄存器内容可以带过去。进入安全世界执行的第一条指令,一定是SG,而且该指令一定是在NSC区域中。
从S世界返回到NS世界,编译器会产生指令来清零现场,以确保安全世界里执行的上下文,不会泄露到非安全世界,最后通过BXNS指令跳转回来。
我们来看一下NS工程的业务逻辑。首先一来,它通过安全世界API的Secure_RegisterCallback,把SecureFault_Callback和SecureError_Callback注册到安全世界里。这两个函数,实际是定义在非安全世界里的。这样当系统异常SecureFault,或者用户中断GTZC发生时,由于全部默认是target到安全世界的,因此会到安全工程里的it.c里,执行对应的ISR。由于注册了非安全世界的异常和错误处理函数,安全中断ISR中,可通过函数指针直接调用这些定义在非安全世界的回调函数。
接下去,非安全工程的初始化还包括:使能NS世界里的systick,也是产生1ms定时;配置GPIO的PB.7,驱动LED2;使能Cache。这个Cache也是属于Securable的外设,默认处于非安全状态,如果想把它配置成安全状态的外设,需要在安全工程的初始化那里,和配置SRAM的安全和非安全区域一样,通过GTZC来配置。
借这个GPIO toggle的例子,我们体会一下上上集介绍CM33内核时,关于异常和中断的retarget概念。 胶片中,左边这个表格,就是来自上上集的课件。系统异常里,有些是铁定target在安全世界的,比如绿色原点标志的Reset/HardFautl/SecureFault;有些异常,是两边各有一套,也很好理解,比如Systick异常,systick本身就是在安全世界和非安全世界各有一个硬件,一旦被使能后,就各自运行起来,到达各自配置的reload值,会触发各自systick的溢出中断。没有道理说,安全世界的systick倒计时到了,触发的中断去非安全世界处理。因此,systick的异常是各有一套。还有一些异常,和用户中断一样,不是banked的,而是通过寄存器来配置,要么target在安全世界,要么target到非安全世界。这些寄存器复位值都是0,意味着所有的可retarget的异常和中断,都是默认在安全世界去处理。结合gpio这个例程,一共根据应用定制实现了3个ISR,其中2个是systick,一个是SecureFault。我们去看看两个工程下各自的it.c。
NS的it.c,只实现了banked的那5个ISR; 关于这个基于TrustZone技术的gpio toggle的例程,就讲解完毕。我们从现成的例程空间讲起,先编译连接,然后下载,体会了TZ应用由两个工程组成。然后按照启动,运行的时间顺序,分别解析了从安全工程开始的初始化,配置,到非安全工程的业务运行,在此过程中,体会了两个世界是如何互相调用另一个世界的代码。
下一期,我设计了一个更加完备的小例程,一方面,我们会从CubeMX入手开始配置,体会CubeMX是如何支持TZ应用的;另一方面,它会覆盖前两集讲到的所有重点的理论知识,比这个gpio toggle的小例子更全面一些。敬请大家持续关注。谢谢观看。
|