esp8266的bootloader(仅为个人笔记) 一

本人初学者,以下仅为个人理解

引导过程(不包含bootloader的其他工作)

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中,堆的开始地址在这里定位)

VMA的其他部分

0x60001100到0x60001400 : 为rtc memory,rtc memory共有0x300字节,分block存储,每个block4字节,这部分的数据可读可写且掉电保护,可以用于bootloader和用户rom进行通信

usercode* load_rom(uint32 readpos)函数

该函数加载位于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;
    }

uint32 system_rtc_mem(int32 addr, void *buff, int32 length, uint32 mode)函数

该函数使用户代码和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;
  }
    }

你可能感兴趣的:(esp8266的bootloader(仅为个人笔记) 一)