系统分区


1. 系统启动流程

1.1. ROM CODE

CPU中启动最初的代码是固化在硬件中无法修改的。这段启动代码称为Rom code。它最终会读取flash内一个固定地址上的机器码并跳转执行,这个地址为IPL代码的binary存放的首地址。

当使用的是Spi-nor flash时Rom code会从Flash地址为0的位置读取IPL的代码段并执行;当使用的是spi-nand时,情况会有点复杂,因为要考虑到Spi-nand在制造工艺上存在坏块的情况。

在Spi-nand时,rom code会去从flash的0地址上读取CIS信息,CIS中保存了Spi-nand flash的一些基本属性信息,例如一个Block size,Page size等。CIS中提供的两个关键数据名为BL0 PBA/BL1 PBA,这是Block索引值,Rom code通过这个值可以找到Flash中IPL存放的位置,下图所示,Rom code会判断BL0 PBA所指定的Block是否为坏块或者ECC ERR,若连续三个Block都是坏块或者ECC ERR则从BL1 PBA引导IPL,相同地,在BL1 PBA开始也会连续判断三个block,若都是坏块或者ECC ERR,则无法启动。


图解1 rom code 加载IPL流程

1.2. 系统启动阶段

系统启动分为好几个阶段,每个阶段都有不同的作用,在操作系统跑起来之前一般地有:Rom code,IPL,IPL_CUST,CM4、TEE、VMM、UBOOT,FreeRtos这几个阶段,每个阶段的二进制文件储存在flash中,在启动过程中需要分区来管理每个阶段存放在flash中的位置。除了Rom code直接从cis中读取IPL所在block而后跳转到IPL执行外,其他代码跳转的过程都需要从分区表中读取下一个阶段的代码的分区信息进行跳转。SStar分区表的命名为pni,它被包含在Flash 0地址上的cis中,这部分会在后面有详细的介绍。

下面例举启动阶段中每段代码的具体作用:

  • CIS

    Spinand保存在flash 0地址的位置,Nor保存在IPL分区之后,它包含三个部分内容,第一部分是GCIS,这部分包含一种通用的flash info信息,它的目的是让rom code适配绝大多数flash的参数,使boot能够顺利进到IPL。另外一部分flash info,保存flash的一些基本信息,例如block page count、block count等,下面简称SNI,这些信息会根据flash的种类而改变。最后一部分是partition info,保存的分区信息,后简称PNI,由于只有IPL/IPL_CUST/FreeRtos才能看到PNI中的信息,而当linux系统起来后,分区信息就被替换成uboot的mtdparts走linux原生的分区流程(FreeRtos系统除外)。在mtdparts中除了用于制作One bin的部分的BOOT分区与PNI中有不同之外,其他的分区信息都必须与PNI中相等。不同的是从PNI中可以看到在mtdpars的BOOT分区里面更加细化的分布,例如其中IPL/IPL_CUST/UBOOT具体所在的位置。

  • IPL/IPL_CUST

    CPU上电后首先跑到的是rom code,在这个阶段只有CPU在工作,DDR还没有初始化,在SOC内部提供sram加载一段代码来执行,这段代码称之为IPL,由于sram很小大概只有几十kb,那么IPL的code size必须控制在sram的大小范围内,IPL代码主要会进行DDR初始化,初始化完毕后才可以加载更大的uboot代码或者FreeRtos。

    IPL_CUST会在IPL之后执行,会根据当前板子的实际情况初始化客制化板子硬件的可执行的二进制文件,例如客制化的GPIO管教,IIC配置,另外会去访问env分区,获取bbm的flag,进行bbm修复。Bbm是处理flash可修正的ecc反转位。IPL_CUST最后会根据它编译的配置来决定它下一段代码跑的是哪个,它可以选择跑uboot、CM4、TEE、VMM、Linux kernel。在快起的方案中IPL_CUST可以忽略uboot直接跳转到Linux kernel。

  • UBOOT

    原生开源的bootloader代码。

  • CM4

    sigmastar Iford系统芯片除了搭载2核ARM Cortex-A32内核,额外搭载一颗ARM Cortex-M4低功耗内核,即CM4,CM4单独运行freertos系统,该系统镜像在flash里占单独的分区 PM_RTOS。PM_RTOS 的镜像只会在包含CM4的场景中使用。

  • TEE

    Cortex-A32 CPU又被划分成安全(Secure World)和非安全(Normal World)两个世界,secure world单独运行tee(Open Portable Trusted Execution Environment)系统,也称为OP-TEE,tee 可以看作是与linux kernel同一个层面的一个trusted os,该系统镜像没有单独分区,一起打包在BOOT0/1分区里;non-secure world可能单独运行linux,也可能同时运行linux+freertos双系统,linux和freertos都有各自的分区KERNEL和RTOS。TEE 的镜像只会在包含 trustzone 的场景中使用。

  • VMM

    为了让non-secure world能够同时运行双系统,需要引入VMM(virtual machine monitor)管理cpu和硬件资源分时复用给两个系统使用,需要在IPL_CUST加载rtos和linux之前,先加载vmm镜像;vmm没有单独分区,一起打包在BOOT0/1分区里。

  • EARLYINIT

    设备上电后以最快的速度初始化sensor和采集图像,不需要等到操作系统启动完就能够快速录制图像,满足需要快速抓图的产品要求。需要在IPL加载IPL_CUST之前,先加载earlyinit;earlyinit没有单独分区,一起打包在RTOS/RTOS_BACKUP分区里。

1.3. 启动Flow

在Linux kernel或者FreeRtos启动之前,CPU会分别执行好几段代码,以下是目前所遇到的情况举例:

  1. Linux平台


    图解2 pure linux 启动流程

  2. DualOs平台


    图解3 DualOs 启动流程

    可以通过Flag在IPL_CUST阶段区分启动步骤,这个Flag可以是某个env,或者是某个gpio管脚的状态,默认是启动走的是蓝线所指的部分。若Flag为真,则走红线部分跳过引导Uboot,Uboot的主要作用是完成升级所需要的nand读写功能以及原生的mtd支持。

1.4. One bin简述

One bin功能是把上图中启动阶段蓝色的部分在ALKAID打包的时候制作成一个binary的形式,在烧录时看到的是一个BOOT分区,而不是具体的IPL/IPL_CUST分区。

One bin的组成方式如上图是不分Spi-nand或者Spi-nor,但是其中具体的细节会有区分,后面章节会有详细的介绍。下表列举出不同的系统中One bin的组成形式:

Boot启动方式 boot.bin组成
Prue linux + uboot IPL + IPL_CUST + UBOOT
Prue linux + uboot + CM4 IPL + IPL_CUST + UBOOT
Prue linux + uboot + TRUST ZONE IPL + IPL_CUST + TEE + UBOOT
Prue linux + uboot + CM4 + TRUST ZONE IPL + IPL_CUST + TEE + UBOOT
DualOs + Uboot IPL + IPL_CUST + VMM + UBOOT
DualOs + Uboot + CM4 IPL + IPL_CUST + VMM + UBOOT
DualOs + Uboot + TRUST ZONE IPL + IPL_CUST + TEE + VMM + UBOOT
DualOs + Uboot + CM4 + TRUST ZONE IPL + IPL_CUST + TEE + VMM + UBOOT

One bin把启动阶段的几个不同的boot代码整合成一个binary,这样做可以简化bootloader阶段分区的复杂程度,从而更易于维护。同时通过把不同的代码binary拼接在一起,可以不用考虑分区大小的对齐要求,从而节省Nor flash的boot代码的binary size。

One bin在空片烧录上与非One bin的系统有不同,烧写的bin和地址如下表:

  • SPINAND

    烧录的bin Flash位置 说明
    cis.bin 0x0/0x20000 两个位置各烧一遍
    boot.bin 0x140000
  • SPINOR

    烧录的bin Flash位置 说明
    boot.bin 0x0

2. 分区介绍

2.1. Flash分区预览

分区表保存在编译输出的partition_layout.txt中,如下图所示,下图的分区配置在使用不同的 ALKAID defconfig 时可能会有差异,请以当前编译的代码为准。

SPI-NOR cm4分区表信息:


图解4 SPI-NOR cm4分区表

SPI-NAND分区表信息:


图解5 SPI-NAND分区表

SPI-NAND pure linux optee分区表信息:


图解6 SPI-NAND pure linux optee分区表

SPI-NAND dualos vmm分区表信息:


图解7 SPI-NAND dualos vmm分区表

SPI-NAND early init分区表信息: earlyinit 没有单独的分区,他与rtos是同一个分区。当rtos烧录到rtos分区后,偏移到rtos分区4M的位置,再烧录earlyinit。

2.2. MISERVICE分区内容

MISERVICE分区的内容被mount到了根目录/config/下面,下表中加深字体的文件是必须保持路径一致的,否则有可能会导致系统无法启动。

└── config

├── iqfile

│ ├── imx307_iqfile.bin

│ ├── iqfile0.bin -> imx307_iqfile.bin ---->Sensor IQ相关的bin

│ ├── iqfile1.bin -> imx307_iqfile.bin

│ ├── iqfile2.bin -> imx307_iqfile.bin

│ ├── iqfile3.bin -> imx307_iqfile.bin

│ └── isp_api.xml

├── modparam.json ---->SDK ko insmod参数。可以参考各模块API文档对modparam参数介绍

├── modules ---->SDK KO存放路径。

│ └── 5.10

│ ├── cifs.ko

……

├── lib ---->SDK so存放路径。

│ ├── libmi_isp.so

……

├── dla ---->IPU核运行的firmware。

│ └── ipu_lfw.bin

├── terminfo ---->Console显示相关。

│ └── v

│ ├── vt100

……

└── venc_fw ---->Encoder的firmware

└── chagall.bin

3. CIS分区结构

从上文介绍可以了解,CIS的分区中分为三部分,从flash的0地址开始算起,它分别为GCIS、PNI、SNI。

3.1. PNI

通过工具pnigenerator动态生成partinfo.pni,在编译后会在image/boot/中以单独的文件形式保存。

代码和编译:

  • pnigenerator工具的源码路径

    project/image/makefiletools/src/pnigenerator/pnigenerator.c

  • 利用服务器上的gcc编译

    gcc pnigenerator.c -Wall –o pnigenerator

使用举例:

pnigenerator -c 256 -s 0x10000 -a "0xc000(IPL),0x6000(CIS),0x9000(IPL_CUST),0x9000(TF_A),0x32000(UBOOT),0x1000@0x6F000(ENV)" -m "ipl cis ipl_cust tf_a uboot" -t "0x340000(KERNEL),0x270000(rootfs),0x50000(MISC),0x490000(miservice),0x4E0000(customer)" -o /home/users/project/image/output/images/boot/partinfo.pni

参数定义:

-r: 读取目标PNI文件中的分区并打印。

-c: 所有分区所占FLASH的BLOCK的总和。

-s: FLASH一个BLOCK的data size,单位为byte。

-a: BOOT PART分区的集合,表示成mtdparts的语法格式

-t: SYS PART分区的集合,表示成mtdparts的语法格式

-o: 生成PNI文件的文件名和路径

PNI文件并不是静态地存放在打包project中,它是根据用户使用的编译配置而定,由pnigenerator工具根据传入的配置分区的参数生成一个bin文件。

PNI通过“-a”、“-t”参数传入分区的信息生成文件project/image/output/images/boot/partinfo.pni

3.2. SNI

SNI在ALKAID的board存放的路径:

  • SPINAND

    project/board/$(CHIP)/boot/spinand/partition/flash_list.sni

  • SPINOR

    project/board/$(CHIP)/boot/nor/partition/flash_list.nri

3.3. GCIS

GCIS是一种特殊的SNI,它包含的是通用的FLASH初始化参数,用于给ROM CODE中固化的代码读取用于启动FLASH的参数,最终目的是读取IPL的data,使BOOT阶段启动到IPL中。

SNI在ALKAID的board存放的路径:

  • SPINAND

    project/board/$(CHIP)/boot/spinand/partition/flash.sni

  • NOR

    project/board/$(CHIP)/boot/nor/partition/flash.nri

3.4. CIS分区数据结构

CIS的分区由上述的GCIS、PNI、SNI按照特定的方式拼接而成,通过Makefile脚本动态生成。


图解8 CIS分区数据结构

上图表示的是CIS数据中的结构,GCIS和PNI由于都小于一个PAGE的大小,因此他们各占一个PAGE的空间,SNI_LIST中由于保存了所有的FLASH信息,它会追加到PAGE1的后面。请注意NOR FLASH和SPINAND FLASH的PAGE大小不一定是2k或者4K,它跟FLASH的规格有关,具体请参考FLASH的SPEC。

PAGE的大小可以根据分区的config脚本中定义的cis$(PGSIZE)来定义。

在分区的烧录脚本制作的过程中,CIS分区的bin制作和烧录也是其中一个环节,整个制作过程后面会介绍,CIS的bin的制作在project/image/image.mk中的目标cis_nofsimage中执行。

制作完成后会在project/image/output/images/中生成的cis.bin就包含GCIS/PNI/SNI这三个内容。

4. 分区配置

4.1. 分区配置前注意事项

无论是spi-nor flash上的分区,还是spi-nand flash上的分区。都可归为两类,一类是不建议改动的boot,一类是可以变动的sys,这一章节介绍的分区变更都是在sys这部分上,包括如何配置脚本变更分区顺序,如何打包分区,如何生成烧录脚本,若客户有自己的一套打包和烧录的流程,可以不用关心这一章,需要特别注意的是在上一章中miservice分区中有特别标注出来的文件需要放到指定的路径下系统才能跑起来,所以在制作分区时务必在根目录下创建/config/,并把相关的文件放到此路径下。

4.2. 脚本配置介绍

分区变更分为分区制作、打包和分区烧录脚本制作这三部分,下面流程图的两个分支,左边走的是分区制作、打包流程,右边走的是分区烧录脚本制作的流程,分区烧录脚本仅支持tftp升级脚本,暂不支持usb升级脚本制作。


图解9 脚本配置介绍

4.2.1. 分区配置脚本

分区配置的脚本在:

project/image/config/general/xxx.partition.config

这是总的分区配置信息脚本,所有与分区调整相关的代码都在这个config文件中实现,这个config文件会有很多份,每个不同的chip、spinand分区还是spinor分区都会有各自的分区config文件,这里集成了所有客制化的改动。

通过build config,就可以找到该项目使用的分区config文件是什么,定义的变量是IMAGE_CONFIG。

此配置脚本中,变量IMAGE_CONFIG可以找到对应使用的分区配置脚本。

在config脚本文件中,每个分区由一组变量表示,如下图所示:


图解10 脚本配置介绍

请参考上一章CIS分区结构,CIS分区很特殊,存放的是一组规定的分区配置raw data,这个是一个特殊的分区,特殊的分区由特别的指令生成。

分区分为带文件系统和不带文件系统的,他们的打包和烧录脚本制作完全不一样,带文件系统的分区镜像文件制作可以统一通过工具处理,而不带文件系统的分区,则需要针对每个分区单独处理。

上文介绍了一些公版所使用的分区,例如miservice分区,它可以配置成只读的squashfs文件系统,也可以做成jiffs2或者ubifs,所有公版所使用的分区都由xxx.mk脚本用于填充镜像文件内部的文件内容,很显然地,miservice.mk中就会把mi所需要的所有ko和so拷贝的目标文件夹中,rootfs.mk就会把根文件系统内部所需要的文件拷贝到目标文件夹,例如rootfs.mk中就要拷贝linux所需要的busybox工具。

miservice分区配置举例:


图解11 脚本配置介绍

4.2.2. 分区配置脚本参数介绍

配置参数 参数含义 注意事项
xxx$(RESOURCE) 需要打包的源文件所在文件夹 文件夹中的内容由customer.mk脚本填充
xxx$(PATSIZE) 分区大小,单位为byte
xxx$(PATNAME) 分区名
xxx$(BOOTENV) 用于设置启动所需要的环境变量 目前只支持 rootfs、rtos、vmm、kernel分区配置
xxx$(BOOTCMD) 用于设置分区所需要的BOOTCMD,最终设置进bootcmd中 目前只支持 rootfs、misc、logo、dtb、kernel分区配置
xxx$(BOOTREC) 用于设置备份分区所需要的BOOTCMD,最终设置进uboot环境变量:bootcmd中 目前只支持 rootfs、kernel分区
xxx$(MTDPART) 分区的位置以及大小,最终设置到进uboot环境变量:mtdparts中
xxx$(OTABLK) 指定当前分区在进行 ota 升级时的 mtd 节点 在ota.mk 中解析使用
xxx$(BLKENV) 设置当前分区所需要的环境变量
xxx$(FSTYPE) 分区类型 目前支持:ext4fs、squashfs、jffs2、fwfs、gz(仅ramdisk)
xxx$(OPTIONS) 分区镜像创建时所需要的参数
xxx$(UBIVOLID) UBI 分区的 volume ID
xxx$(MOUNTTG) 分区挂载路径
xxx$(MOUNTPT) 分区挂载节点
xxx$(SYSTAB) flash 总体分区信息描述 用于创建pni分区文件以及构建uboot 环境变量 mtdparts
xxx$(COPIES) 分区源文件在分区内数量 仅在 nand flash 的 ipl以及 ipl_cust 分区使用

option 参数介绍

option 参数用于指定当前分区镜像压缩方式,在 image.mk/rootfs.mk 中进行解析使用

分区文件系统类型 option 参数支持 参数含义
squashfs gzip、lzma、lzo、lz4、xz 压缩方式
fwfs sz 压缩方式
ubifs ubia、ubib 表示当前分区在哪个 ubi 分区中被创建
ramfs sz、xz、gz 压缩方式
无文件系统分区 option 参数支持 参数含义
kernel sz、xz、lz4、mz 指定镜像压缩方式,可以直接在xxx$(RESOURCE)中指定镜像名带有option 所支持后缀
rtos sz、xz、lz4、mz 指定镜像压缩方式,可以直接在xxx$(RESOURCE)中指定镜像名带有option 所支持后缀

4.3. 分区位置变更

在CIS分区的配置中,变量cis$(BOOTTAB0)、cis$(BOOTTAB1)、cis$(SYSTAB)用于配置分区信息。

cis$(BOOTTAB0)cis$(BOOTTAB1)用于在one bin制作中指定其A-B分区的做法,这种做法会在PNI中标识其中使用的BOOT是否是活动的,目前在spi-nand分区中有引入A-B分区,其中BOOTTAB0是活动的分区,当BOOT在烧录出现异常或者出现坏块的情况下会从BOOT直接跳到BOOT_BAK的备份分区中,并在PNI中重新标识活动的分区,下次启动时会自动跑到活动的分区中。

BOOTTAB0、BOOTTAB1和SYSTAB为分区表的定义,它使用的语法格式是mtdparts所支持的,格式可以写成:

partition0_size(partition0_name), partition0_size(partition0_name),……

例如下面CIS和IPL分区的描述格式:

0x14000(CIS),0x60000(IPL)

每个分区都由逗号隔开。若没有指定分区的起始位置,则从第一个分区开始算,分区默认是从FLASH的0地址开始。若需要从分区的指定位置配置,则按照以下语法格式:

partition0_size@offset(partition0_name)

指定了分区的起始位置后,在这个分区后面追加的分区如果没有指定其位置,它的起始位置则会在当前分区之后的分区位置逐个增加。

若当前分区在分区表中是最后一个,则可以用一个比较方便的方法自适应分区的大小。

例如spinand的UBI分区,在分区表最后,它可以表示成:

xxxx(xxxx), xxxx(xxxx),-(UBI)

分区表解析的逻辑会参考当前flash配置的大小自适应UBI的大小。

分区位置的修改就是改变cis$(BOOTTAB0)、cis$(BOOTTAB1)、cis$(SYSTAB)这三个变量里面所放分区的位置。一般地BOOT部分的分区是不会改变其位置的,SYSTAB改变的比较常见。

4.4. 增加修改删除jiffs2分区

在spinor的flash上一般用jiffs2作为可读写的分区的文件系统,如下定义了customer分区的相关配置:

customer$(RESOURCE)   = $(OUTPUTDIR)/customer
customer$(FSTYPE)    = jffs2
customer$(PATSIZE)   = 0x5C0000
customer$(MOUNTTG)  = /customer
customer$(MOUNTPT)  = mtd:customer
customer$(OPTIONS)  = ro
customer$(MTDPART) = $(customer$(PATSIZE))(customer)
customer$(OTABLK) = /dev/mtdblock6

customer$(RESOURCE): 需要打包的源文件所在文件夹,这个文件夹中的内容由customer.mk脚本填充。

customer$(FSTYPE): 打包的文件系统类型。

customer$(PATSIZE): 分区大小,单位为byte。

customer$(MOUNTTG)、customer$(MOUNTPT)、customer$(OPTIONS): 分区在板子起来后需要mount的路径以及参数。rootfs.mk中会处理这些参数。

customer$(OTABLK): 用于OTA升级打包所需的升级设备节点的路径。

4.4.1. 修改

若要修改分区,一般是修改文件系统和分区大小。

当修改文件大小后,要认真核对所有分区的大小累加起来有没有超过flash的size。

可以在partition_layout.txt中查看分区size是否有溢出。

4.4.2. 增加分区

aaa$(RESOUCE)   = $(OUTPUTDIR)/aaa
aaa$(FSTYPE)    = jffs2
aaa$(PATSIZE)   = 0x6B0000
aaa$(MOUNTTG)   = /aaa
aaa$(MOUNTPT)   = mtd:aaa
aaa$(OPTIONS)   = rw
aaa$(OTABLK)   = /dev/mtdblockX
  1. 假设增加一个分区名为aaa,然后配置其大小等信息。

  2. 分区相关的变量配置完成之后,在cis$(SYSTAB) = "xxx"中指定其放置的位置。

  3. 在变量“IMAGE_LIST”后追加“aaa”。

  4. 在表示需要mount的节点的变量“USR_MOUNT_BLOCKS”后追加“aaa”。

  5. 在分区打包前需要在脚本中添加往$(OUTPUT)/aaa拷贝文件的逻辑。

4.4.3. 删除分区

按照增加分区的操作,反过来执行。

4.4.4. 烧录脚本

系统在script.mk中会根据分区的配置自动生成。

4.5. 增加修改删除squashfs分区

Squashfs的分区变更与jiffs2分区变更大同小异,主要在xxx$(FSTYPE)上有不同,除去rootfs这个比较特别的分区之外,miservice分区制作成squashfs:

miservice$(RESOUCE)   = $(OUTPUTDIR)/tvconfig/config
miservice$(FSTYPE)    = squashfs
miservice$(PATSIZE)   = 0x180000
miservice$(MOUNTTG)  = /config
miservice$(MOUNTPT)  = /dev/mtdblock3
miservice$(OPTIONS)   = ro

与jiffs2分区一样,系统在script.mk中会根据分区的配置自动生成烧录脚本。

4.6. 增加修改删除ubifs分区

UBIFS分区一般应用在spinand上作为可读写的分区文件系统。

UBIFS的所有的分区都属于UBI的mtd block中的一个子分区。一个普通的UBI分区变量设置如下:

miservice$(RESOUCE)   = $(OUTPUTDIR)/miservice/config
miservice$(FSTYPE)    = ubifs
miservice$(PATSIZE)   = 0xA00000
miservice$(MOUNTTG)  = /config
miservice$(MOUNTPT)  = ubi0:miservice
miservice$(OPTIONS)   = rw

与jiffs2分区变更方法不同的是,ubifs分区不需要添加mtdpart讯息,也就是无需在cis$(SYSTAB) = xxx中添加分区位置。

4.7. 增加修改删除LFS/FWFS分区

Littlefs或者Fwfs分区的好处是可以在uboot/linux/rtk中访问。

MISC分区用与存放系统配置脚本相关的文件,例如屏参信息:

misc$(RESOUCE) = $(OUTPUTDIR)/misc
misc$(FSTYPE) = lfs
misc$(PATSIZE) = 0x60000
misc$(MOUNTTG) = /misc
misc$(MOUNTPT) = /dev/mtd4
misc$(OPTIONS) = rw
misc$(MTDPART) = $(misc$(PATSIZE))(MISC)
misc$(OTABLK) = /dev/mtdblock4

修改方式请参考squashfs或者jffs2。

4.8. 多UBI分区

mtdparts中规划了ubi分区的位置,大多数情况只使用一个ubi分区,一般在sys part的结尾,用-UBI定义,在UBI分区中又有自己的子分区定义,称之为volume,例如miservice就可以定义成UBI分区的一个volume。config脚本中可以配置成多个UBI分区,每个分区中可以配置其拥有的volume。

若有多个ubi分区,可以叫ubia或者ubib,例如如下定义:

ubia$(PATSIZE) = 0x1400000
ubia$(MTDPART) = $(ubia$(PATSIZE))(ubia)
ubib$(MTDPART) = -(ubib)

在sys part定义以上ubi分区所处的位置:

cis$(SYSTAB) = xxx,xxx,$(ubia$(MTDPART)),$(ubib$(MTDPART))

在bootargs中添加两个ubi分区的配置,kernel启动会按照这个顺序初始化ubi分区:

ubi.mtd=ubia,2048 ubi.mtd=ubib,2048

volume的定义中,变量BlockName$(OPTIONS)用于定义它是在哪个ubi分区中被创建,例如,miservice分区在ubia中创建:

miservice$(OPTIONS)  = ubia

这里注意在ubi volume mount的地方要带参数表示是当前使用的ubi节点,如:

miservice$(MOUNTPT) = ubi0:miservice

这个序号是根据bootargs中定义的多ubi.mtd=xxx的顺序来判断的。

4.9. Ubinize

ubinize是ubi分区打包时所提供的一个功能,它可以把ubi分区生成的volume文件转换成flash的image文件。因此在烧录时直接用flash的read/write烧写ubi分区,替换使用ubi命令烧写volume。

在配置上,首先要定义一个ubi分区名,ubinize功能对ubi的mtdparts分区名有要求,它必须是以小写的“ubi”开头,后面带序号。

例如有多个ubi分区ubia ubib要做成image的形式,把ubia、ubib添加到IMAGE_LIST中,添加是为了自动生成ubi image的升级脚本,而不是ubi volume的升级脚本。

使用打包脚本提供的变量:

BlockName$(UBIVOLID)和BlockName$(OPTIONS)

例如:

miservice$(UBIVOLID) = 0

miservice$(OPTIONS)  = ubia

以上表示的是在ubinize制作image的过程中miservice是ubia这个分区image中第0个volume。

当多个volume配置在同一个ubi分区中,每个volume的配置要根据UBIVOLID序号排列。

4.10. 特殊分区处理

KERNEL、uboot、IPL、logo、cis等分区属于特殊分区,这些分区没有特别的文件系统,所以无法在Makefile脚本中做集中处理,必须做特别处理。因此添加一个特殊的分区需要清楚了解如下两个步骤(这里举例的是kernel分区):

  1. image.mk中需要写上分区打包的脚本命令

    kernel_nofsimage:
        @echo [[$@]]
        cp -rvf $($(patsubst %_nofsimage,%,$@)$(RESOUCE)) (IMAGEDIR)/$(patsubst %_nofsimage,%,$@)
    
  2. 在script.mk中写上分区烧录脚本生成的命令

    kernel_$(FLASH_TYPE)__script:
        @echo "# <- this is for comment / total file size must be less than 4KB" > $(SCRIPTDIR)/[[kernel.es
        @echo tftp $(TFTPDOWNLOADADDR) kernel >> $(SCRIPTDIR)/[[kernel.es
        @echo $(FLASH_PROBE) >> $(SCRIPTDIR)/[[kernel.es
        @echo $(FLASH_ERASE_PART) KERNEL >> $(SCRIPTDIR)/[[kernel.es
        @echo $(FLASH_WRITE_PART) $(TFTPDOWNLOADADDR) KERNEL \$${filesize} >> $(SCRIPTDIR)/[[kernel.es
        @echo "% <- this is end of file symbol" >> $(SCRIPTDIR)/[[kernel.es
        @echo kernel-image done!!!
    

5. 分区配置客制化界面

5.1. 分区配置客制化规则

上一章介绍分区配置的改动主要集中在config脚本文件上,若公版提供的分区配置脚本无法满足一个新平台上的需求。请添加自己的config脚本。

原则上在分区打包的脚本中,不允许改动以下脚本:

image/Makefile
image/image.mk
image/scripts.mk
image/config/general/rootfs.mk
image/config/general/ramdisk.mk
image/config/general/miservice.mk
image/config/general/customer.mk
image/config/general/nor.ramfs.partition.config          ->   Nor flash rootfs为ramfs
image/config/general/nor.squashfs.partition.config      ->   Nor flash rootfs为squashfs
image/config/general/nor-ramdisk.rtos.squashfs.partition.config ->   Nor flash ramdisk rtos
image/config/general/spinand.squashfs.partition.config   ->   Spinand flash rootfs为squashfs
image/config/general/spinand.ramfs.partition.config    ->   Spinand flash rootfs为ramfs
image/config/general/spinand-ramdisk.rtos.partition.config   ->   Spinand flash ramdis rtos

目前公版有提供一些入口提供客制化改动的接口,在每个分区Block定义中可以自行添加一些变量,做到在升级时插入一些客制化的指令:

  • BlockName$(BLKENV)

    config文件中,可以按需求添加每个分区block在升级时要写入的env,此env能同时兼容tftp/usb/sd/ota升级时的写入。例如kernel最后会往env中写入自己的image data size:

    kernel$(BLKENV) := kernel_file_size $$(printf 0x%x `stat -c "%s" $(kernel$(RESOUCE))`)\n
    kernel$(BLKENV) += recovery_file_size $$(printf 0x%x `stat -c "%s" $(kernel$(RESOUCE))`)
    
  • BlockName$(CUSMK)

    按需求添加每个分区的内容生成脚本是否走客制化的.mk文件,若未定义则使用默认脚本。(默认脚本: rootfs.mk/miservice.mk/ramdisk.mk/misc.mk)

  • BlockName$(CUSCMD)

    每个分区block在升级后插入自己的脚本指令,定义它的指令。

6. ONE BIN

6.1. ONE BIN分区

ONE BIN功能是基于ALKAID的打包脚本通过dd命令使BOOT PART分区合并成一个bin,以uboot和linux kernel的角度来看,可以在uboot中输入命令mtdpart看到当前的分区规划,与pni不同的是BOOT PART这部分,整个BOOT PART不再细化内部分区,而是由一个BOOT分区构成,如下图所示:

NOR FLASH


图解12 nor 分区构成

SPINAND FLASH


图解13 nand 分区构成

ONE BIN打包完成后会在output/images/下面生成一个boot.bin,boot.bin由ipl/ipl_cust/uboot的image拼接而成,nor flash会额外再加上cis分区。

6.2. SPINAND的BOOT分区

在nand flash上,BOOT0/BOOT1属于A/B分区部分,ROM code默认是从BOOT0分区中开启,如果BOOT0分区发生ECC ERROR或者遇到坏块,则会跳到BOOT1分区,并在下次启动后,默认从BOOT1分区中开启,这样可以保证在升级的过程中,BOOT的部分建立了一套备份机制,避免升级BOOT失败后无法进入UBOOT cmd line。

上文所述,ONE BIN部分是有dd命令拼接而成,具体构成如下:


图解14 nand flash ONE BIN 分区构成

上图橙色的部分表示的是MTD PART中分区的大小,boot.bin表示实际做出来的one bin大小,紫色的部分代表pni中在这部分的layout。

IPL/IPL_CUST的image一般不会超过一个block的size,因此它们和它们的Backup各自占用3个block的大小,这个大小是固定不变的。

UBOOT的data占用了2个block的大小,由于uboot经常被客制化,uboot的image大小有可能会变化,因此配置2个block不能保证所有的情况,若要修改,请修改config文件中的uboot$(DATASIZE) = 0x40000

6.3. SPINAND BOOT分区坏块处理

在UBOOT之上预留了4个BLOCK的GAP,这部分在config文件中是强制预留的,这4个BLOCK是为了充分利用IPL和IPL_CUST的BK BLK(它们各自有两个backup,加起来为4)。强制预留代码:

uboot$(PATSIZE) = $(call sum, $(uboot$(DATASIZE)) $(call multiplyhex, $(FLASH_BLK_SIZE), 4))

预留的目的是处理坏块的情况。从上图可知,MTD PART的BOOT分区比要烧录的boot.bin大4个block的大小,当使用tftp烧录或者用烧录器烧录代码的时候,若遇到坏块,数据会自动写到下一个block,那么在当前位置之后的数据都会整体位移。

假设在IPL_CUST DATA的位置发生坏块,那么烧录后如下所示:


图解15 出现坏块后 ONE BIN 结构

在IPL_CUST后面的所有数据都位移了一个block,那么在uboot的data也会跟着位移一个block,GAP则会被占用一个block。在没有坏块的情况下,整个boot分区允许最大4个block的坏块,这4个坏块并不是任意位置的,其要满足在IPL/IPL_CUST分区中至少有一个block是好的。超过4个block或者不满足上述条件则会跳到BOOT1分区,保证能够顺利启动。

6.4. SPINAND的BL0 PBA/BL1 PBA标志位

使用工具SpiNandInfoEditor.exe打开flash.sni或者flash_list.sni后会看到如下的栏位:


图解16 boot.bin 所在 flash 位置

这两个栏位是给rom code找IPL分区的,在ALKAID/project/board中找到的sni文件默认填的都是0,单位是block count,这表示rom code都是从默认的地址找IPL0和IPL1,默认的地址是IPL0: 0x140000、IPL1:0x1A0000。显然跟当前分区配置是不匹配的,因此在cis.bin制作的过程中,会使用shell脚本把flash.sni和flash_list.sni里面的BL0 PBA和BL1 PBA的填上正确的值,对应的就是BOOT0/BOOT1分区在flash上首地址对应的block offset。

6.5. SPINOR的BOOT分区

SPINOR目前默认没有做A/B分区,在BOOT的部分与SPINAND有区别,SPINOR的BOOT分区包含CIS,BOOT分区是从0地址开始的,BOOT分区的结构图如下图。


图解17 nor flash ONE BIN 分区构成

SPINOR由于不需要处理坏块,IPL、IPL_CUST没有backup block,在boot部分的分区需要关注分区首地址、结束地址和分区大小对齐的问题,之所以以4k对齐,是为了节省空间。在boot中IPL/IPL_CUST/UBOOT/CIS分区首地址和分区大小都必须以一个nor flash的page对齐,page大小是4k,ENV分区不在BOOT分区里面,但是它要求ENV分区的结束地址需要以nor flash一个block的大小对齐,一般为64k,那么根据下图的layout,在UBOOT和ENV之间就会有一个GAP来保证这个对齐的要求。这个GAP与spinand的作用不一样,除了保证对齐要求之外,它还有一个功能,那就是应对UBOOT/IPL/IPL_CUS的data部分数据变化导致其在PNI内部分区变化所做的缓冲机制,其目的就是保证即使在PNI中这些分区变化了,但是MTD的BOOT分区和env分区是不变的。从而给ota升级BOOT时预留一个更大的变化量(主要原因是考虑到ota升级分区是无法变化的)。

NOR flash的分区中IPL/IPL_CUST/CIS/UBOOT的分区大小在脚本中都自动做了4k对齐,使用者只需要调整MTD的BOOT PARTITION的大小,从而起到增大或者减少GAP的效果。

BOOT的大小是有限制的,其大小加上ENV的分区大小必须以block size对齐,设定其大小的变量是:

boot$(PATSIZE) = 0x4F000

6.6. ONE BIN特别注意

ONE BIN的分区配置若需要修改,请先认真阅读本章节,仅可以适当修改spinand的uboot$(DATASIZE)和spinor的boot$(PATSIZE)来应对IPL/IPL_CUS/UBOOT的image文件大小变化的情况,修改后请找FAE确认。若修改不当,容易造成生产环节BOOT无法启动的问题。

若打包流程未参照我司的ALKAID,特别注意,在spinand的sni_list以及gcis中的BL0 PBA和BL1 PBA需要根据当前的BOOT0/BOOT1位置填入正确的值,也可以在确认BOOT分区无异后,使用ALKAID编译出来的BOOT的partition layout以及boot.bin。

若需要自行制作boot.bin,请参考本章节的说明,具体做法请参考脚本image.mk中的Makefile的函数updatecis,以及目标cis_nofsimage、boot_images。