上一篇笔记STM32MP157芯片的开发环境,之后就直接简写为MP1。为了保证安全ARM推出了 Arm Trusted Firmware的可信固件,简称 TF-A。它是一个开源的软件,最早是用在Armv8-A,ST也在MP1里面使用到了TF-A。它的作用就是隔离硬件,为硬件提供一个安全环境并且提供安全服务。
TF-A初步使用
智能设备的安全问题是一个物联网产品非常重要的环节, ARM为此提供了TrustZone解决方案,TrustZone将CPU的工作状态分为了Secure World(安全世界)和Normal World(非安全世界),涉及到安全相关的内容运行在安全世界,比如指纹、密码等,其他的操作都在非安全世界运行,比如应用程序。TrustZone是一种硬件解决方案,只要学会使用就好了,不用过多探究,不是linux驱动开发的重点。
系统源码获取
与TrustZone对应的,我们需要一套软件来配合TrustZone,TF-A应运而生,TF-A全称是Arm Trusted Firmware,有些资料也叫做 ATF,一般中文资料叫做 ARM可信固件。 MP1内部集成了TrustZone,因此ST也提供了TF-A相关源码,TF-A会先初始化DDR等外设,把Uboot从Flash(NAND、NOR FLASH、SD、MMC 等)拷贝到DDR中。我们不可能直接去官方网站下载TF-A的源码,这样的开发难度太大,半导体厂商都会从TF-A官网下载源码,然后修改适配自己
的芯片,把自家的芯片加进去。在实际项目开发中直接使用半导体原厂给提供的TF-A即可。ST官方TF-A、uboot、 kernel等源码下载链接为: ST官方系统源码,打开以后如下图所示:
中的 STM32MP1Dev就是官方的源码包,里面包括 TF-A uboot kernel等源码,点击“ Get Software”下载整个系统源码。这里我们已经下载下来并放到了开发板光盘中,就是en.SOURCES-stm32mp1-openstlinux-5-4-dunfell-mp1-20-06-24.tar.xz。
在Ubuntu中创建一个目录存放源码,然后将ST官方系统源码发送到Ubuntu中。这里我在前面创建的“linux”目录下新建一个名为“stm32mp1”的目录存放所有源码,命令如下:
cd linux/ //进入linux目录 mkdir stm32mp1 //创建stm32mp1目录 |
用FileZilla将ST官方源码压缩包en.SOURCES-stm32mp1-openstlinux-5-4-dunfell-mp1-20-06-24.tar.xz拷贝到“atk-mp1”目录下。解压压缩包,命令如下:
tar -xvf en.SOURCES-stm32mp1-openstlinux-5-4-dunfell-mp1-20-06-24.tar.xz |
解压完成以后会得到一个名为“stm32mp1-openstlinux-5.4-dunfell-mp1-20-06-24”的文件夹;进入stm32mp1-openstlinux-5.4-dunfell-mp1-20-06-24/sources/arm-ostl-linux-gnueabi目录下,里面就是uboot、optee、tf-a、kernel源码,而这5个源码文件夹的含义如下图所示:
上图中共有 5个源码,本教程就用到前三个:u-boot-stm32mp-2020.01-r0、linux-stm32mp-5.4.31-r0和tf-a-stm32mp-2.2.r1-r0,也就是TF-A、Uboot和Linux Kernel。传统的linux学习中不需要TF-A的,只需要uboot和Linux Kernel,但是MP1带有安全硬件,因此加入了TF-A。
首先TF-A并不是ST自己做的, ST只是将自己的MP1芯片移植到了TF-A上,上图中的tf-a-stm32mp-2.2.r1-r0就是ST自己修改后的TF-A源码,直接使用ST修改后的 TF-A源码即可,包括uboot、linux kernel都是这样的。
tf-a-stm32mp-2.2.r1-r0支持ST所有的 MP1芯片,也支持各种启动方式,例如:EMMC、NAND、NOR FLASH等等。 tf-a-stm32mp-2.2.r1-r0里面包含了 ST自家所有的MP1评估板,正点原子的STM32MP157开发板参考了ST官方的STM32MP157C-EV1开发板,因此,后续的移植都是以STM32MP157C-EV1开发板为蓝本,在此基础上进行修改。
tf-a-stm32mp-2.2.r1-r0目录文件一共有5个文件,这5个文件的含义如下图所示:
TF-A源码打补丁
上图中的TF-A源码还不能直接使用,需要对其打补丁。ST也是在TF-A官方的源码上将自己的MP1系列芯片添加进去的,因此必然涉及到对官方TF-A源码的修改,修改的部分就是“补丁”,补丁文件后缀为.patch,之前图片中的0001-st-update-v2.2-r2.0.0.patch就是ST为TF-A官方源码做的补丁文件,补丁文件里面描述了修改源码中的哪些文件,应该添加或删除哪些代码或文件,打完补丁后的TF-A才是真正需要的。因此,打补
丁需要两个文件:
- 补丁文件,在此处就是0001-st-update-v2.2-r2.0.0.patch;
- 需要打补丁的源文件,在此处就是tf-a-stm32mp-2.2.r1-r0.tar.gz。
首先需要先解压tf-a-stm32mp-2.2.r1-r0.tar.gz,命令如下所示:
tar -vxf tf-a-stm32mp-2.2.r1-r0.tar.gz |
解压完就会在当前目录下生成“tf-a-stm32mp-2.2.r1”目录;接下来就是给TF-A源码打补丁,在解压好的TF-A的目录下运行以下命令:
cd tf-a-stm32mp-2.2.r1 //进入TF-A源码 for p in `ls -1 ../*.patch`; do patch -p1 < $p; done //打补丁 |
这条命令的意思是把上一层目录下的所有“ “.patch”后缀的文件都通过patch命令打补丁到TF-A的源码目录,在这里就是将001-st-update-v2.2-r2.0.0.patch这个补丁打入到TF-A源码里面,过程如下图所示:
由于ST官方源码目录太长,为了方便调试,在stm32mp1目录下创建一个名为“tf-a”的子
目录,然后就打完补丁后的tf-a-stm32mp-2.2.r1-r0目录下的所有文件都拷贝到tf-a下,命令如下:
cd tf-a-stm32mp-2.2.r1-r0/ cp * /home/zuozhongkai/linux/atk-mp1/tf-a/ -rf |
以上命令的cp之后的具体目录看你自己是怎么创建的,更换一下就可以了,拷贝完成后tf-a目录应如下图所示:
创建VSCode工程
为了方便阅读TF-A源码,可以创建一个VSCode工程,然后打开VSCode,点击:文件->打开文件夹,选中打完补丁后的tf-a-stm32mp-2.2.r1-r0文件夹,打开以后的VSCode如下图所示:
左侧的资源管理器就是TF-A的工程目录结构。最后我们保存一下工作区, 点击:文件 ->将工作区另存为…,打开保存工作区对话框,将工作区保存到TF-A 源码根目录下,设置文件名为“ tf-a”,如下图所示:
保存以后就会在TF-A源码根目录下多出一个名为“tf-a.code-workspace”的文件,这样一个完整的 VSCode工程就建立起来了。
编译和烧录TF-A
通过上文得到的TF-A源码是ST提供的,这个TF-A肯定是不能直接在正点原子的STM32MP157开发板上运行的,需要移植和修改。但是在正式移植TF-A之前需要了解如何编译TF-A,并烧写到正点原子的STM32MP157开发板上,这里我们直接编译正点原子已经修改好的TF-A。
stm32wrapper4dbg工具安装
编译TF-A或者Uboot的时候需要用到stm32wrapper4dbg这个工具,否则编译会报错。ST提供了这个工具的码,我们需要在Ubuntu下编译并安装这个源码,源码的下载地址为:stm32wrapper4dbg下载地址,这个正点原子是已经在开发光盘中提供了的,就是stm32wrapper4dbg-master.zip。 将源码压缩包拷贝到Ubuntu下,然后进行解压,命令如下:
unzip stm32wrapper4dbg-master.zip //解压 |
解压完成以后就会得到一个名为“stm32wrapper4dbg-master”的文件夹,进入到此文件夹里面,然后编译并安 装,命令如下:
cd stm32wrapper4dbg-master //进入到此文件夹 make //编译 |
编译完成以后就会得到一个名为“stm32wrapper4dbg”的工具;将编译出来的stm32wrapper4dbg工具拷贝到 Ubuntu的 /usr/bin目录下,命令如下:
sudo cp stm32wrapper4dbg /usr/bin |
拷贝完成以后就可以直接在终端中使用stm32wrapper4dbg这个工具了,输入如下命令查看
帮助信息:
stm32wrapper4dbg -s |
如果输出下图所示内容就说明stm32wrapper4dbg工具安装成功:
编译正点原子官方TF-A
准备正点原子出厂TF-A源码
首先安装设备树编译相关命令,输入如下命令:
sudo apt-get install device-tree-compiler |
在Ubuntu的stm32mp1目录下新建一个名为“alientek_tf-a”的子目录,然后把正点原子修改好的TF-A源码拷贝到alientek_tf-a这个目录下。正点原子修改好的TF-A源码已经放到了开发板光盘中,就是tf-a-stm32mp-2.2.r1-g463d4d8-v1.0.tar.bz2。 拷贝完成以后解压,输入如下命令:
tar -xvf tf-a-stm32mp-2.2.r1-g463d4d8-v1.0.tar.bz2 |
解压完成以后alientek_tf-a文件夹就会有tf-a-stm32mp-2.2.r1文件夹,这就是正点原子针对自己的STM32MP157开发板修改过的TF-A源码,Makefile.sdk是一会编译TF-A要用到的Makefile。
修改Makefile.sdk
首先,TF-A是有自己的Makefile文件的,而且真正编译的时候也是要用TF-A自己的Makefile。Makefile.sdk是ST自己编写的,也是我们一会编译TF-A的时候要用到的,Makefile.sdk里面主要定义了一些编译属性,比如要使用的交叉编译器、编译的一些选项等等,Makefile.sdk最终会调用 TF-A内部的 Makefile来编译TF-A。默认情况下Makefile.sdk里面使
用的是ST官方的交叉编译器 (arm-ostl-linux-gnueabi-gcc),但是正点原子的文档教程中用的是通用交叉编译器arm-none-linux-gnueabihf-gcc,因此我们需要修改Makefile.sdk,将交叉编译器改为我们目前所使
用的。打开Makefile.sdk,然后找到CROSS_COMPILE,将其改为“arm-none-linux-gnueabihf-”。
编译TF-A
准备工作都就绪以后就可以编译正点原子出厂TF-A了,进入到tf-a-stm32mp-2.2.r1目录里面,然后运行执行如下命令编译TF-A:
cd tf-a-stm32mp-2.2.r1/ //进入到正点原子出厂 TF-A的源码目录 make -f ../Makefile.sdk all //编译 TF-A |
‘-f’的意思是重新指定Makefile,在这里就是指定Makefile.sdk,编译成功会出现下图所示:
编译完成以后会在上一层目录,也就是alientek_tf-a下生成一个名为“build”的目录,进入build目录下,一共有三个子目录:optee、serialboot和trusted,如下图所示:
我们只关注trusted目录下的文件,此目录下就保存了MP1所有型号的TF-A固件,包括正点原子开发板所使用的tf-a-stm32mp157d-atk-trusted.stm32,如下图所示:
TF-A烧录到EMMC
使用STM32CubeProgrammer将TF-A烧写到开发板里面,STM32CubeProgrammer支持通过UART、USB、STLINK来烧写系统,本教程全部采用USB烧写,也就是通过开发板上的USB_OTG口来烧写系统。
准备烧写材料
在Windows下通过STM32CubeProgrammer来烧写TF-A,新建一个目录来存放烧写镜像文件,例如创建一个名为“images”的目录。然后将开发板光盘里面的这两个文件先拷贝到images目录下:tf-a-stm32mp157d-atk-serialboot.stm32, u-boot.stm32,全部都是正点原子官方写的。
最后就是前面编译出来,真正要烧写的 tf-a-stm32mp157d-atk-trusted.stm32,通过FileZilla将 Ubuntu里面的tf-a-stm32mp157d-atk-trusted.stm32发送到images目录下。
正点原子提供的这两个文件是干嘛的呢?正点原子的教程是这么说的,和STM32CubeProgrammer软件的设计以及烧写过程有关:tf-a-stm32mp157d-atk-serialboot.stm32中间有个“serialboot”,也就是串行BOOT,说明是和启动有关的;此固件用来初始化USB、DDR等外设,DDR初始化了以后就可以运行uboot了(uboot里面会初始化EMMC、NAND等外设,而且uboot会提供很强大的EMMC操作指令);也就是说,启动uboot的目的就是为了操作EMMC、NAND,这样就可以在uboot里面通过相关的命令将tf-a-stm32mp157d-atk-trusted.stm32写到EMMC或者NAND里面。
总结来说,正点原子的教程认为,需要tf-a-stm32mp157d-atk-serialboot.stm32和u-boot.stm32的原因,就是为了将tf-a-stm32mp157d-atk-trusted.stm32烧写到EMMC、NAND、SD卡里面。
准备FlashLayout
以上三个文件的烧写设置需要通过STM32CubeProgrammer脚本文件来定义,STM32CubeProgrammer脚本文件后缀为.tsv,ST官方也叫做FlashLayout。这里可以直接在正点原子提供的tsv文件基础上修改,将开源的开发光盘中的atk_emmc-stm32mp157d-atk-qt.tsv拷贝到前面的 images前目录下,然后将其重命名为“tf-a.tsv”,完成后 images目录如下图所示:
.tsv是文本格式的,很容易阅读,关于.tsv语法的详细讲解,请参考: tsv详解。用Notepad++软件打开tf-a.tsv,默认格式时看不出什么的,tsv对格式有要求,必须要设置一下 Notepad++软件,点击“视图->显示符号->显示空格与制表符”, 设置好以后的tf-a.tsv如下图所示:
tf-a.tsv中,黄色箭头代表是TAB键,TAB键越多黄色箭头就越长,如果用空格键的话就会显示‘_’。tsv语法要求只能用TAB键,不能用空格!以‘#’开头为注释,所以上图第一行为注释 。
修改tf-a.tsv文件内容如下图所示:
上图中一共有5行,第1行为注释,一共7列,这7列的含义如下图所示:
接下来详细介绍一下这7个配置:
1.Opt域
Opt是第一个项,此选项通过‘-’、‘P’、‘D’和‘E’这四个字符定义操作方法,首选的是‘-’和‘P’。
- ‘-’:none,也就是空选项,分区或者设备无需修改,如果Device域为none,那么Opt强制为‘ ‘-’;
- ‘P’:向分区或者设备烧写固件。
STM32CubeProgrammer本质是通过uboot来烧写系统的,也就是先把uboot加载到板子的DDR里面并运行,然后使用uboot来烧写系统。uboot会请求需要烧写的二进制文件,然后将其烧写到指定的分区或者Falsh设备里面。
针对‘P’,还可以搭配一下两个参数:
- ‘E’:空分区或设备,表示对应的分区或设备不更新,相关的Id项会被跳过;
- ‘D’:删除分区或设备。
允许的组合如下:
- ‘-’:空选型;
- ‘P’:更新分区或设备,也就是向分区或设备烧写固件;
- ‘PE’:不更新,也就是指定某个分区或者设备不需要烧写固件,这样我们就可以单独只更新tf-a,uboot,kernel或rootfs;
- ‘PD’:删除并更新,也可以写作‘DP’;
- ‘PDE’:删除并且保持为空,也可写作‘PED/DPE/DEP/EPD/EDP’。
2.Id域
STM32CubeProgrammer通过Id域来确定烧写方法,会通过Id域来识别下一个要烧写到设备里面的二进制文件:
- ROM或FSBL:二进制文件要加载到RAM;
- SSBL(uboot):二进制文件要烧写到Flash中。
FlashLayout支持的Id范围如下图所示:
其中0X01和0X03这两个Id是给FSBL和SSBL留着的,它们会被加载到RAM中。一些默认的Id含义如下图所示:
3.Name域
Name域为一段字符串,也就是目标内存段的名字。
4.Type域
Type域仅仅用于uboot,用来选择需要更新的Flash区域:
- SD卡或者EMMC设备对应GPT分区;
- 原始的Flash设备,如NAND、NOR等对应MTD分区。
SD/EMMC和NAND/NOR所支持的Type类型如下图所示:
正点原子STM32MP157开发板为EMMC类型,所以我们只需要知道EMMC下的Type域含义:
- Binary:原始的二进制文件;
- FileSystem:linux文件系统,为ext2/ext4/fat格式;
- System:Linux内核。
5.Device域
Device域指定Uboot设备树定义的设备和索引 (从0开始),不同的设备其设备名字和索引不同:
- mmc+索引 :如mmc0、mmc1、mmc2等,对应SD卡或EMMC;比如SD卡和EMMC分别接到MP1的SDMMC1和SDMMC2接口上,那么SD卡和EMMC分别为mmc0和mmc1;
- nor+索引 :如nor0,对应NOR或者QUADSPI Flash;
- nand+索引 :如nand0,对应连接到FMC总线上的并行NAND Flash;
- spi-nand+索引 :如spi-nand0,对应连接到QSPI上的串行NAND Flash;
- none:RAM,也就是将固件加载到RAM里面,仅允许启动阶段使用,而且Type域要为Binary,Offset域要为0,Opt域为‘-’;
- ram+索引 :如ram0,烧写服务讲固件加载到RAM中运行。
6.Offset域
Offset就是偏移,支持的值如下:
- boot1:EMMC的第一个启动区域分区;
- boot2:EMMC的第二个启动区域分区;
- 数字:具体的偏移值,单位为字节。
7.Binary域
STM32CubeProgrammer软件要使用的二进制文件。
通过USB烧写TF-A
首先设置开发板拨码开关,设置为000,也就是从 USB启动,然后复位开发板!
首先通过USB Type-C线将开发板的USB_OTG和USB_TTL连接到电脑上,如下图所示:
按照上图将板子的USB串口与USB OTG连接到电脑以后还要检查一下连接是否成功,打开电脑的设备管理器,查看CH340和DFU模式是否存在,如下图所示:
连接没问题的话就可以使用STM32CubeProgrammer来烧写TF-A了,打开STM32CubeProgrammer,选择USB连接方式,Port选择 USB1,如下图所示:
USB设置好以后点击右上角的“Connect”来连接开发板,连接成功以后左下角的log区域就会输出一些信息,右侧中间的数据区域也会显示开发板默认的分区情况,右下角会显示目标板信息,如下图所示:
STM32CubeProgrammer要使用FlashLayout文件来烧写系统,也就是前面我们创建的tf-a.tsv。点击“Open File”,打开 tf-a.tsv,如下图所示:
注意 ,如果没有看到的“Browse”和 Download”这两个按钮的话,把STM32CubeProgrammer界面放大一点,尤其是左右托大,因为软件默认界面会把这两个按钮挡住!
一切准备就绪以后就可以点击上图中的“Download”按钮开始下载,下方的log区域就会显示烧写过程,烧写完成以后就会有提示信息提示完成烧写。
TF-A运行
自己编译的TF-A已经烧写到了开发板中,接下来就是测试能不能运行,打开MobaXterm软件,设置好与开发板连接的串口,波特率选择115200。
设置开发板拨码开关为010,也就是从 EMMC启动,然后复位开发板!
注意,由于开发板默认已经烧写了整套Linux系统,所以直接启动的话会启动整个系统,包括我们刚刚烧写的自己编译的TF-A,原有的uboot、Linux系统等。TF-A是最先启动的,也就是最前的就是我们自己编译的 TF-A,如下图所示:
TF-A启动的时候会打印出编译时间,我们可以通过编译时间来判断是否为我们自己编译的TF-A。比如上图中 TF-A的编译时间为2020年11月11号10:53:02,这个时间是正点原子开发教程编译的时间,说明目前正在运行的 TF-A就是自行编译的正点原子出厂TF-A,也就是tf-a-stm32mp157d-atk-trusted.stm32。
总结
这一章节的内容,就是初步介绍了TF-A,然后根据正点原子给的源代码,进行初步的TF-A代码编译以及移植,学习了如何编译并适配自己的MP1开发板,然后完成了通过STM32CubeProgrammer完成TF-A的烧写操作,并通过运行时的log信息判断TF-A是否烧写成功。