本人初学者,以下仅为个人理解
sdk加载spiflash的第0个扇区(bootloader从0x0000开始烧,还有bootloader的大小要控制小于4096字节,否则会造成越到第1个扇区,第1个扇区里默认是放用户配置数据的),接着sdk将bootloader中代码数据映射回dram和iram,接着进入bootloader的入口函数,接着bootloader找到第2个扇区开始的应用rom,接着bootloader完成将应用rom对应的要放在iram和dram的代码和数据映射回去,接着bootloader找到应用rom的入口函数地址,然后调用入口函数,此时bootloader工作完成,用户的应用程序开始启动
dram0_0_seg : org = 0x3FFE8000, len = 0x14000 : 输出.data(输入.data等段),输出.rodata(输入.rodata等段),输出.bss(输入.bss等段)
iram1_0_seg : org = 0x40100000, len = 0x8000 : 输出 .text(输入.text等段,这里和应用rom不同,应用rom将输入的.text段放在了irom0_0_seg中,这里做是因为操作flash的代码必须放在iram中)
irom0_0_seg : org = 0x40240000, len = 0x3C000 : 输出.irom0.text(输入.irom.text等段,.irom.text段在代码中除非明确指出否则不会代码默认是编译在.text段的) 注意 : 在别的地方看到有说操作flash的代码不能放在irom中,否则会造成 crash.[DH] ,这能解释为什么bootloader的连接脚本中将代码放在iram中的原因
用size命令查看如下 :
rboot.elf :
section size addr
.rodata 0x418 0x3ffe8000
.bss 0x0 0x3ffe8418
.text 0xac8 0x40100000
没有.data 和 .irom0.text 段 , 即源代码中没有变量(bootloader主要是选择rom,要操作spiflash,所以在输出段中没有.irom0.text段是理所当然的)
这里给下应用rom的输出段分布
section size addr
.text 0x69fe 0x40100000
.data 0x964 0x3ffe8000
.rodata 0x8b4 0x3ffe8968
.bss 0x84c8 0x3ffe9220
.irom0.text 0x37cdc 0x40202010
输出段.text是目标文件中的库函数(放在iram中),输出段.irom0.text是目标文件中的代码部分(放在spiflash上),输出段.data .rodata和.bss是目标文件中变量,常量和未初始化变量(放在dram中,节省空间的话可以更改链接脚本将常量放在irom中,堆的开始地址在这里定位)
0x60001100到0x60001400 : 为rtc memory,rtc memory共有0x300字节,分block存储,每个block4字节,这部分的数据可读可写且掉电保护,可以用于bootloader和用户rom进行通信
该函数加载位于spiflash上的第2个扇区(从0开始)的rom,将rom中对应的应该放在iram和dram的部分数据或指令映射回去并且返回该rom入口函数的地址
usercode定义如下 :
// functions we'll call by address
typedef void usercode(void);
readpos : 地址,应该是spi要read的绝对地址,暂时还没有弄清楚,弄清楚后会修改这里
定义函数变量
uint8 sectcount; // 计数要放入iram和dram的段的数量(如.text .data .rodata 3个段,这三个段不在rom中,要映射回到iram和dram中去)
uint8 *writepos; // 记录每个段对应的首地址(就是iram和dram的地址,将这些段放回到iram和dram的地址中)
uint32 remaining; // 用来记录每个段中剩余的字节数,因为spi一个扇区一个扇区的读写,一个扇区为0x1000,所以不能一次读完,要分很多次读
usercode* usercode; // 函数指针,要将rom的入口函数地址放在这个指针中,以便在全部的段映射回去之后进入应用程序开始运行用户代码
rom_header header; // rom的头,不定义为指针的原因是SPiread函数的限制
section_header section; // section的段头,不定义为指针的原因是SPiread函数的限制
读取rom头
// rom头结构体
typedef struct {
uint8 magic; // 识别作用
uint8 count; // 记录有几个段头
uint8 flags1; // 给出rom的image信息,需要进行位运算
uint8 flags2; // 给出rom的image信息,需要进行位运算
usercode* entry; // 给出入口函数地址
} rom_header;
// 将rom头信号存储在header中
SPIRead(readpos, &header, sizeof(rom_header));
// 使rom的扫描位置进入到下一部分,下一分部是一个段头
readpos += sizeof(rom_header);
保存rom的入口函数的地址
// 入口函数的地址保存在rom头中
usercode = header.entry;
将部分段(.text .data .rodata)映射回iram和dram空间
// 段头结构体
typedef struct {
uint8* address; // 映射到iram或dram的首地址
uint32 length; // 数据长度
} section_header;
// 所有段都分别处理
for (sectcount = header.count; sectcount > 0; sectcount--) {
// 将段头信息存储在section中并且将readpos指定到下一个段头
SPIRead(readpos, §ion, sizeof(section_header));
readpos += sizeof(section_header);
// 保存这个段要映射回去的相应首地址和相应数据长度
writepos = section.address;
remaining = section.length;
// 若数据长度还大于0(表示还没有将数据全部放回去)
while (remaining > 0) {
// 一次spiread只能读写一个扇区,一个扇区为4096byte,将读写长度限制在0x1000以下
uint32 readlen = (remaining < 0x1000) ? remaining : 0x1000;
// 从spiflash中读取数据并且将数据放回到iram或者dram中
SPIRead(readpos, writepos, readlen);
// 读取位置到下一个扇区的首地址
readpos += readlen;
// 写入位置相应也要到下一个扇区
writepos += readlen
// 因为已经读写了一个扇区,所以剩余的数据长度要减去一个扇区的大小
remaining -= readlen;
// while 语句结束
}
// 返回rom的入口函数的地址
return usercode;
}
该函数使用户代码和bootloader进行通信,通过rtc memory
形参
int32 addr // 实际上是block序号,在rtc memory中0~63个block为系统数据,而64~191为用户数据区,每个block为4个字节
void *buff // 要写入的数据地址或者读取的数据地址,具体是那种根据mode决定
int32 length // 要写入或者读取的数据长度,以字节为单位
uint32 mode // mode为宏,如下
#define RBOOT_RTC_READ 1
#define RBOOT_RTC_WRITE 0
函数变量
// 要进行读取或者写入的block的数量,根据长度来得到
int32 blocks;
检验block序号或者读写数据地址是否符合要求
// validate reading a user block
if (addr < 64) return 0; // 如果block序号小于64,则读写区域在系统数据区,函数返回0
if (buff == 0) return 0; // 如果读写的地址为0,则函数返回0
检测读写数据地址是否满足字节对齐以及数据长度是否为4的倍数
// 检测读写地址是否满足字节对齐要求,因为block为4字节对齐,所以读写数据初始地址也必须是这样对齐的,检测对齐的方式是看其地址是否为4的倍数
if (((uint32)buff & 0x3) != 0) return 0;
// 检测长度是否为4的倍数,一个block为4字节,读写数据的长度也要为一个block的倍数
if ((length & 0x3) != 0) return 0;
检测数据长度是否越过rtc memory用户数据区的长度
// 0x300为rtc memory包括系统数据的总长度,(addr * 4)为要读写数据地址在rtc memory中的相对偏移的长度,用总长度减去相对编译的长度就是能够写入到用户数据区的长度
if (length > (0x300 - (addr * 4))) return 0;
接下来进入函数的主要功能部分
// 一个block一个block的处理
// blocks = (length >> 2) 相当于length除以4,这里用了位运算
for (blocks = (length >> 2) - 1; blocks >= 0; blocks--) {
// 以下的ram和rtc同时指向对应的block
// 将buff转换成4字节指针进行与block序号运算
volatile uint32 *ram = ((uint32*)buff) + blocks;
// 加上addr因为前面为系统数据区,0x60001100为rtc memory的开始VMA
volatile uint32 *rtc = ((uint32*)0x60001100) + addr + blocks;
// 如果是写模式,则将用户数据写到rtc中,如果是读模式,则将rtc数据写到ram中
if (mode == RBOOT_RTC_WRITE) {
*rtc = *ram;
} else {
*ram = *rtc;
}
}