ESP8266 Bootloader开源代码解析之rboot(一)segmentfault
上一篇说了rboot的加载流程,主要的是经过makefile将两个程序文件串了起来。这篇文章会对整个加载流程作详细讲解。数据结构
typedef struct { /* magic是经常使用的名称,用来标识这是个结构体,一般存在flash上,而且已经被初始化了 */ uint8_t magic; ///< Our magic, identifies rBoot configuration - should be BOOT_CONFIG_MAGIC /* 用来讲明当前的数据结构适用于哪一个版本,不一样版本常须要考虑兼容性问题 */ uint8_t version; ///< Version of configuration structure - should be BOOT_CONFIG_VERSION /* 当前rboot的启动模式 */ uint8_t mode; ///< Boot loader mode (MODE_STANDARD | MODE_GPIO_ROM | MODE_GPIO_SKIP) /* 当前选择的ROM区,但表示的是接下来要启动的ROM区 */ uint8_t current_rom; ///< Currently selected ROM (will be used for next standard boot) /* MODE_GPIO_ROM模式下选择的ROM区 */ uint8_t gpio_rom; ///< ROM to use for GPIO boot (hardware switch) with mode set to MODE_GPIO_ROM /* 可用的ROM总数 */ uint8_t count; ///< Quantity of ROMs available to boot /* 占位,使前面长度够32位整数 */ uint8_t unused[2]; ///< Padding (not used) /* 每一个ROM区的地址 */ uint32_t roms[MAX_ROMS]; ///< Flash addresses of each ROM #ifdef BOOT_CONFIG_CHKSUM /* 本结构体的校验值 */ uint8_t chksum; ///< Checksum of this configuration structure (if BOOT_CONFIG_CHKSUM defined) #endif } rboot_config;
其实自身的注释就很详细了。
一般作Bootloader的话,都会维护本身的一个数据结构,这个结构体中放着boot配置和信息,而且存放在flash中(掉电保存)。这样作有几个目的:less
若是把uint32_t find_image(void)函数简化,是这样的:ide
uint32_t NOINLINE find_image(void) { uint8_t flag; uint32_t loadAddr; uint32_t flashsize; int32_t romToBoot; uint8_t updateConfig = 0; uint8_t buffer[SECTOR_SIZE]; rboot_config *romconf = (rboot_config*)buffer; rom_header *header = (rom_header*)buffer; ets_printf("\r\nrBoot v1.4.2 - richardaburton@gmail.com\r\n"); // read boot config SPIRead(BOOT_CONFIG_SECTOR * SECTOR_SIZE, buffer, SECTOR_SIZE); // fresh install or old version? if (romconf->magic != BOOT_CONFIG_MAGIC || romconf->version != BOOT_CONFIG_VERSION ) { // create a default config for a standard 2 rom setup ets_printf("Writing default boot config.\r\n"); // write new config sector } // try rom selected in the config, unless overriden by gpio/temp boot romToBoot = romconf->current_rom; // check valid rom number // gpio/temp boots will have already validated this if (romconf->current_rom >= romconf->count) { // if invalid rom selected try rom 0 ets_printf("Invalid rom selected, defaulting to 0.\r\n"); } // check rom is valid loadAddr = check_image(romconf->roms[romToBoot]); // check we have a good rom while (loadAddr == 0) { ets_printf("Rom %d at %x is bad.\r\n", romToBoot, romconf->roms[romToBoot]); // for normal mode try each previous rom // until we find a good one or run out updateConfig = 1; romToBoot--; if (romToBoot < 0) romToBoot = romconf->count - 1; if (romToBoot == romconf->current_rom) { // tried them all and all are bad! ets_printf("No good rom available.\r\n"); return 0; } loadAddr = check_image(romconf->roms[romToBoot]); } // re-write config, if required if (updateConfig) { romconf->current_rom = romToBoot; SPIEraseSector(BOOT_CONFIG_SECTOR); SPIWrite(BOOT_CONFIG_SECTOR * SECTOR_SIZE, buffer, SECTOR_SIZE); } ets_printf("Booting rom %d at %x, load addr %x.\r\n", romToBoot, romconf->roms[romToBoot], loadAddr); // copy the loader to top of iram ets_memcpy((void*)_text_addr, _text_data, _text_len); // return address to load from return loadAddr; }
很大一部分在作有效性判断,简化流程以下:函数
if(romconf->magic != BOOT_CONFIG_MAGIC) { return; } if(romconf->version != BOOT_CONFIG_VERSION) { return; } if(romconf->current_rom >= romconf->count) { return; } for(int i=0; i<romconf->count; i++) { loadAddr = check_image(romconf->roms[i]); if(loadAddr != 0) { break; } } if(loadAddr == 0) { return; } loader(loadAddr);
其中check_image主要是检测了ESP8266固件自己的有效性。
ESP8266生成的bin文件中,实际上是包含了一些外部信息的,如flash模式,flash速度等,还用使用boot的版本,还有最主要的是生成文件的各段的信息。ESP8266有一些历史版本的boot,因此格式也稍有区别。ui
rboot-stage2a.c中:this
usercode* NOINLINE load_rom(uint32_t readpos) { uint8_t sectcount; uint8_t *writepos; uint32_t remaining; usercode* usercode; rom_header header; section_header section; // read rom header SPIRead(readpos, &header, sizeof(rom_header)); readpos += sizeof(rom_header); // create function pointer for entry point usercode = header.entry; // copy all the sections for (sectcount = header.count; sectcount > 0; sectcount--) { // read section header SPIRead(readpos, §ion, sizeof(section_header)); readpos += sizeof(section_header); // get section address and length writepos = section.address; remaining = section.length; while (remaining > 0) { // work out how much to read, up to 16 bytes at a time uint32_t readlen = (remaining < READ_SIZE) ? remaining : READ_SIZE; // read the block SPIRead(readpos, writepos, readlen); readpos += readlen; // increment next write position writepos += readlen; // decrement remaining count remaining -= readlen; } } return usercode; }
前面说到,ESP8266生成的bin文件中包含了各段的信息,包括段地址、大小和内容。load_rom即是遍历bin文件中全部的段,从flash中读取,并加载到对应地址中。而若是没有Bootloader的话,这个工做是在ROM代码中执行的。code
到这里,rboot的加载固件流程就讲完了。rboot还有很多特性,如GPIO选择ROM,加载多个ROM等,这些功能不太经常使用,这里就不打算说了。有须要的能够在评论区留言,我也会视状况继续写下去的。
那么下一篇,就是写zboot啦,做者说是基于rboot上作了改进,有了rboot的基础后,解读起来应该不会太难,你们一块儿加油啊。orm