继续之前的学习。
// prevent this function being placed inline with main
// to keep main's stack size as small as possible
// don't mark as static or it'll be optimised out when
// using the assembler stub
首先看一下作者给这个函数写下的注释。为了避免此函数内嵌入main函数而占用main函数的栈空间,此函数被关键字NOINLINE
修饰。为了避免此函数在使用汇编存根(stub)时被优化,不能使用static
来修饰此函数。
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;
此函数先声明了一些变量,其中的变量buffer
数组的大小是SECTOR_SIZE
,也就是Flash一个扇区的大小,此处为4KB
。
#ifdef BOOT_BAUDRATE
// soft reset doesn't reset PLL/divider, so leave as configured
if (get_reset_reason() != REASON_SOFT_RESTART) {
uart_div_modify( 0, UART_CLK_FREQ / BOOT_BAUDRATE);
}
#endif
此处建议在Makefile中定义变量RBOOT_BAUDRATE
,因为ESP-12F模组上电默认的74880波特率太过奇怪。此处我将变量RBOOT_BAUDRATE
定义在了变量RBOOT_FW_BASE
的后面,并赋值为115200。
#if defined BOOT_DELAY_MICROS && BOOT_DELAY_MICROS > 0
// delay to slow boot (help see messages when debugging)
ets_delay_us(BOOT_DELAY_MICROS);
#endif
ets_printf("\r\nrBoot v1.4.2 - [email protected]\r\n");
此处建议去掉rboot.h
中//#define BOOT_DELAY_MICROS 2000000
的注释去掉,并给宏定义设置一个合适的值,我将其设置为20000(单位为us)。这样做可以做到等待系统稳定再进行后面的操作。如果我们想设置rBoot阶段串口输出的波特率,就需要在这里等待系统串口稳定。
// read rom header
SPIRead(0, header, sizeof(rom_header));
// print and get flash size
ets_printf("Flash Size: ");
flag = header->flags2 >> 4;
if (flag == 0) {
ets_printf("4 Mbit\r\n");
flashsize = 0x80000;
} else if (flag == 1) {
ets_printf("2 Mbit\r\n");
flashsize = 0x40000;
} else if (flag == 2) {
ets_printf("8 Mbit\r\n");
flashsize = 0x100000;
} else if (flag == 3 || flag == 5) {
ets_printf("16 Mbit\r\n");
#ifdef BOOT_BIG_FLASH
flashsize = 0x200000;
#else
flashsize = 0x100000; // limit to 8Mbit
#endif
} else if (flag == 4 || flag == 6) {
ets_printf("32 Mbit\r\n");
#ifdef BOOT_BIG_FLASH
flashsize = 0x400000;
#else
flashsize = 0x100000; // limit to 8Mbit
#endif
} else if (flag == 8) {
ets_printf("64 Mbit\r\n");
#ifdef BOOT_BIG_FLASH
flashsize = 0x800000;
#else
flashsize = 0x100000; // limit to 8Mbit
#endif
} else if (flag == 9) {
ets_printf("128 Mbit\r\n");
#ifdef BOOT_BIG_FLASH
flashsize = 0x1000000;
#else
flashsize = 0x100000; // limit to 8Mbit
#endif
} else {
ets_printf("unknown\r\n");
// assume at least 4mbit
flashsize = 0x80000;
}
// print spi mode
ets_printf("Flash Mode: ");
if (header->flags1 == 0) {
ets_printf("QIO\r\n");
} else if (header->flags1 == 1) {
ets_printf("QOUT\r\n");
} else if (header->flags1 == 2) {
ets_printf("DIO\r\n");
} else if (header->flags1 == 3) {
ets_printf("DOUT\r\n");
} else {
ets_printf("unknown\r\n");
}
// print spi speed
ets_printf("Flash Speed: ");
flag = header->flags2 & 0x0f;
if (flag == 0) ets_printf("40 MHz\r\n");
else if (flag == 1) ets_printf("26.7 MHz\r\n");
else if (flag == 2) ets_printf("20 MHz\r\n");
else if (flag == 0x0f) ets_printf("80 MHz\r\n");
else ets_printf("unknown\r\n");
这里使用SPI接口将Flash上的头部信息读出并保存在变量header
中。其中包含有Flash大小、模式和速度信息。这些信息我们都可以在Makefile中设置,设置好以后会被编译进bin文件中。对于ESP-12F来说,应该这样设置:SPI_SIZE ?= 4M
,SPIMODE ?= DOUT
,SPI_SPEED ?= 40
。
// 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");
ets_memset(romconf, 0x00, sizeof(rboot_config));
romconf->magic = BOOT_CONFIG_MAGIC;
romconf->version = BOOT_CONFIG_VERSION;
default_config(romconf, flashsize);
// write new config sector
SPIEraseSector(BOOT_CONFIG_SECTOR);
SPIWrite(BOOT_CONFIG_SECTOR * SECTOR_SIZE, buffer, SECTOR_SIZE);
}
// try rom selected in the config, unless overriden by gpio/temp boot
romToBoot = romconf->current_rom;
这里先将Flash第一个扇区的数据读入缓冲区buffer
中,而前面romconf
指向了buffer
。那么此时romconf
就读出了rBoot的配置结构体信息。接着,我们比较magic
与version
是否与之前的宏定义相同。按照rBoot项目的readme中说的,我们在进行boot版本迭代时,可以通过修改version来区分新旧版本。如果此处if为真,则说明Flash中存在新的boot,此时要将Flash的第一个扇区覆盖。之后获取当前需要加载的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");
romToBoot = 0;
romconf->current_rom = 0;
updateConfig = 1;
}
// 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]);
}
之前拿到了当前需要加载的rom的索引。如果之前拿到的索引是否超出rom总数,则认为此索引是无效的,并默认加载第一个rom。之后再去获取rom的地址。如果获取rom地址失败,则说明此rom已经被损坏了。如果所有rom都已经损坏了,那么只能直接返回0,代表没有找到image了。
// 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;
获取到rom的地址后,将当前的rom的索引写入配置结构体中。随后将buffer
写入boot所在的扇区,覆盖了之前的配置,在下次启动时将使用当前的配置。然后,将_text_data
位置,长度为_text_len
的数据加载到_text_addr
中。最后返回rom的地址,find_image()
函数就结束了。
const uint32_t entry_addr = 0x4010fcb4;
const uint32_t _text_addr = 0x4010fc00;
const uint32_t _text_len = 192;
const uint8_t _text_data[] = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x10, 0x00, 0x00, 0x1c, 0x4b, 0x00, 0x40, 0x12, 0xc1, 0xc0, 0xc9, 0xe1, 0x8b, 0x31, 0xcd,
0x02, 0x0c, 0x84, 0xe9, 0xc1, 0xf9, 0xb1, 0x09, 0xf1, 0xd9, 0xd1, 0xc2, 0xcc, 0x08, 0x01, 0xf9,
0xff, 0xc0, 0x00, 0x00, 0xf8, 0x31, 0xe2, 0x01, 0x09, 0x86, 0x10, 0x00, 0x2d, 0x0c, 0x3d, 0x01,
0x0c, 0x84, 0x01, 0xf4, 0xff, 0xc0, 0x00, 0x00, 0x8b, 0xcc, 0x78, 0x01, 0xd8, 0x11, 0x46, 0x09,
0x00, 0x21, 0xef, 0xff, 0x5d, 0x0d, 0xd7, 0xb2, 0x02, 0x20, 0x52, 0x20, 0x2d, 0x0c, 0x3d, 0x07,
0x4d, 0x05, 0x59, 0x51, 0x79, 0x41, 0x01, 0xeb, 0xff, 0xc0, 0x00, 0x00, 0x58, 0x51, 0x78, 0x41,
0x5a, 0xcc, 0x5a, 0x77, 0x50, 0xdd, 0xc0, 0x56, 0x6d, 0xfd, 0x0b, 0x6e, 0x60, 0xe0, 0x74, 0x56,
0x9e, 0xfb, 0x08, 0xf1, 0x2d, 0x0f, 0xc8, 0xe1, 0xd8, 0xd1, 0xe8, 0xc1, 0xf8, 0xb1, 0x12, 0xc1,
0x40, 0x0d, 0xf0, 0x00, 0xfd, 0x00, 0x05, 0xf8, 0xff, 0x0d, 0x0f, 0xa0, 0x02, 0x00, 0x0d, 0xf0,
};
_text_data
、_text_len
、_text_addr
等变量都可以在build目录下的rboot-hex2a.h
中找到。令人意外的是,此文件竟然处于build目录下,这就说明它不是写出来的,而是通过某种方式生成的。
$(RBOOT_BUILD_BASE)/rboot-hex2a.h: $(RBOOT_BUILD_BASE)/rboot-stage2a.elf
@echo "E2 $@"
$(Q) $(ESPTOOL2) -quiet -header $< $@ .text
通过分析Makefile,我们可以看到,rboot-hex2a.h
依赖于rboot-stage2a.elf
,并通过esptool2生成的。关于生成rboot-hex2a.h
的具体过程,可以参考这篇文章:ESP8266 Bootloader开源代码解析之rboot(一)。