ESP8266 Bootloader开源代码解析之rboot(一)

回顾

上一篇说了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中(掉电保留)。这样做有几个目标:

  1. 晓得应用哪个ROM区进行启动
  2. 能够配置ROM区地址(更灵便)
  3. 保留版本信息,使更易兼容
  4. 能够晓得以后数据是否正确,有无损坏

查看固件

如果把uint32_t find_image(void)函数简化,是这样的:

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,所以格局也稍有区别。

加载固件

rboot-stage2a.c中:

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, &section, 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代码中执行的。

End

到这里,rboot的加载固件流程就讲完了。rboot还有不少个性,如GPIO抉择ROM,加载多个ROM等,这些性能不太罕用,这里就不打算说了。有须要的能够在评论区留言,我也会视状况持续写下去的。
那么下一篇,就是写zboot啦,作者说是基于rboot上做了改良,有了rboot的根底后,解读起来应该不会太难,大家一起加油啊。