bootloader是什么?代码不能删除、更改自己,所以我们需要bootloader+application这种架构来设计我们的产品,使用bootloader来删除、更改application的代码。
以一台安卓系统的手机为例,修改手机上的apps需要在安卓系统层面进行;修改安卓系统(刷机)需要进入bootloader(安卓系统上称作fastboot模式);修改bootloader则需要通过一定的工具才能进行。
所以说类比到MCU上,我们要实现能够对MCU的程序进行升级和更改,那么就需要一个前置程序,这个程序就叫做bootloader。
根据上述图中的表达可以看出,bootloader的本质也是一段程序,只不过这段程序相对简单:不需要复杂的例如实时聊天、录音等业务功能、不需要好看的交互界面等。其主要功能就是:
1. 引导下一段程序
2. 提供对下一段程序的修改接口
3. 提供更多的额外底层功能,例如修改CPU频率、显卡频率等
下面对上述几个功能的实现做一个简单的分析
bootloader本质就是一段程序,跑不跑RTOS都无所谓。
在引导下一段程序之前,先了解一下每段程序的执行原理。
所以所谓的通过A引导下一段程序B,本质是A知道B的存储位置,强行让CPU跳转到B的存储位置继续执行。
顺便提一句,每次我们启动电脑的时候,实质是先运行主板上的固定位置的BIOS,然后BIOS中可以选择下一步引导系统的位置,机械硬盘、固态硬盘、U盘,系统起来后,我们还可以手动选择运行哪个程序。
因此引导的指令实际上很简单(大概是这样写的,具体指令忘了):
/* 汇编伪代码 */
LDR R0 0X08200000 // 装载跳转地址
BR R0 // 跳转
但是写汇编就算了,所幸的是,C语言是可以直接操作内存的语言。(python、js这一类的语言不能直接操作内存,所以也就无法直接跳转)
unsigned long do_go_exec(ulong (*entry)(int, char * const []), int argc,
char *const argv[])
{
return entry (argc, argv);
}
static int do_go(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[])
{
ulong addr, rc;
int rcode = 0;
if (argc < 2)
return CMD_RET_USAGE;
addr = simple_strtoul(argv[1], NULL, 16);
printf ("## Starting application at 0x%08lX ...\n", addr);
/*
* pass address parameter as argv[0] (aka command name),
* and all remaining args
*/
rc = do_go_exec ((void *)addr, argc - 1, argv + 1);
if (rc != 0) rcode = 1;
printf ("## Application terminated, rc = 0x%lX\n", rc);
return rcode;
}
可以简单的看到,只需要把目的地地址address强制转换成一个函数,再执行这个函数就可以了。
/* NXP官方的MCU-BOOT中关键代码 */
static void jump_to_application(uint32_t applicationAddress, uint32_t stackPointer)
{
shutdown_cleanup(kShutdownType_Shutdown);
// Create the function call to the user application.
// Static variables are needed since changed the stack pointer out from under the compiler
// we need to ensure the values we are using are not stored on the previous stack
static uint32_t s_stackPointer = 0;
s_stackPointer = stackPointer;
static void (*farewellBootloader)(void) = 0;
farewellBootloader = (void (*)(void))applicationAddress;
// Set the VTOR to the application vector table address.
SCB->VTOR = (uint32_t)APP_VECTOR_TABLE;
// Set stack pointers to the application stack pointer.
__set_MSP(s_stackPointer);
__set_PSP(s_stackPointer);
// Jump to the application.
farewellBootloader();
// Dummy fcuntion call, should never go to this fcuntion call
shutdown_cleanup(kShutdownType_Shutdown);
}
// ...
// ... 通常你需要提前做一些准备工作,例如deinit RCC,copy code into RAM。
static void (*jump)(void) = NULL; //声明一个jump函数
jump = (void (*)void)0x08200000; //假设跳转到0x0820000处继续执行
jump(); //let's jump!
一个合格的bootloader除了能够引导下一段程序,还应该能够修改下一段程序,例如最常见的就是升级下一段程序,专业术语叫做IAP,In Application Programming,实际上我们最经常听到的“帮我装个win10系统”,实际上也算IAP,只是这个词除了嵌入式以外不怎么常用。
修改下一段程序有两种思路:一种是直接把全部的程序代码复制到下一段程序的位置;另一种则是只复制需要升级的少部分的代码,称作差分升级。windows10升级的时候用的是哪一种我就不用多说了吧。(当然是后者)
在单片机上,升级很快,一般而言不需要进行差分升级,直接全量升级即可。直接copy代码到目的地即可,下面写一段我自己用到的从SD卡拷贝升级文件到目的地FLASH的代码:
for(;;){
file->read(&fil, read_buffer, sizeof(read_buffer), &br); // read file from SD card
if(br == 0) break; // end of read
HAL_FLASH_Unlock();
for(int i=0;i<sizeof(read_buffer)/FLASH_PSIZE_WORD;i++) {
HAL_FLASH_Program(FLASH_TYPEPROGRAM_FLASHWORD, APP_FLASH_START+i*FLASH_PSIZE_WORD+read_count*sizeof(read_buffer), (uint32_t)read_buffer+i*FLASH_PSIZE_WORD);
} // copy file to flash
HAL_FLASH_Lock();
memset(read_buffer,0,sizeof(read_buffer));
read_count++;
log_w("copying ... %d * %d", sizeof(read_buffer), read_count);
}
其他功能就供大家自由发挥了,下面列举一些常见的例子:
1.恢复出厂程序
2.烧写/设置设备的序列号、ID、密钥
3.选择引导源:类似BIOS,可以选择硬盘、U盘、光驱等
一.linux开发之uboot移植(一)——初识uboot
常见bootloader介绍
https://github.com/u-boot/u-boot
MCUBOOT: MCU Bootloader for NXP microcontrollers
NXP官方的bootloader关键引导代码:
/* NXP官方的MCU-BOOT中关键代码 */
static void jump_to_application(uint32_t applicationAddress, uint32_t stackPointer)
{
shutdown_cleanup(kShutdownType_Shutdown);
// Create the function call to the user application.
// Static variables are needed since changed the stack pointer out from under the compiler
// we need to ensure the values we are using are not stored on the previous stack
static uint32_t s_stackPointer = 0;
s_stackPointer = stackPointer;
static void (*farewellBootloader)(void) = 0;
farewellBootloader = (void (*)(void))applicationAddress;
// Set the VTOR to the application vector table address.
SCB->VTOR = (uint32_t)APP_VECTOR_TABLE;
// Set stack pointers to the application stack pointer.
__set_MSP(s_stackPointer);
__set_PSP(s_stackPointer);
// Jump to the application.
farewellBootloader();
// Dummy fcuntion call, should never go to this fcuntion call
shutdown_cleanup(kShutdownType_Shutdown);
}
void shutdown_cleanup(shutdown_type_t shutdown)
{
if (shutdown != kShutdownType_Reset)
{
// Clear (flush) the flash cache.
#if !BL_FEATURE_HAS_NO_INTERNAL_FLASH
#if !BL_DEVICE_IS_LPC_SERIES
//flash_cache_clear(NULL);
FTFx_CACHE_ClearCachePrefetchSpeculation(g_bootloaderContext.allFlashCacheState, true);
FTFx_CACHE_ClearCachePrefetchSpeculation(g_bootloaderContext.allFlashCacheState, false);
#endif // !BL_DEVICE_IS_LPC_SERIES
#endif // #if !BL_FEATURE_HAS_NO_INTERNAL_FLASH
}
if (shutdown != kShutdownType_Cleanup)
{
// Shutdown all peripherals because they could be active
uint32_t i;
for (i = 0; g_peripherals[i].typeMask != 0; i++)
{
if (g_peripherals[i].controlInterface->shutdown)
{
g_peripherals[i].controlInterface->shutdown(&g_peripherals[i]);
}
}
}
// If we are permanently exiting the bootloader, there are a few extra things to do.
if (shutdown == kShutdownType_Shutdown)
{
// Turn off global interrupt
lock_acquire();
// Shutdown microseconds driver.
microseconds_shutdown();
// Disable force ROM.
#if defined(RCM_FM_FORCEROM_MASK)
RCM->FM = ((~RCM_FM_FORCEROM_MASK) & RCM->FM) | RCM_FM_FORCEROM(0);
#elif defined(SMC_FM_FORCECFG_MASK)
#if defined(SMC0)
SMC0->FM = ((~SMC_FM_FORCECFG_MASK) & SMC0->FM) | SMC_FM_FORCECFG(0);
#else
SMC->FM = ((~SMC_FM_FORCECFG_MASK) & SMC->FM) | SMC_FM_FORCECFG(0);
#endif
#endif // defined(RCM_FM_FORCEROM_MASK)
// Clear status register (bits are w1c).
#if defined(RCM_MR_BOOTROM_MASK)
RCM->MR = ((~RCM_MR_BOOTROM_MASK) & RCM->MR) | RCM_MR_BOOTROM(3);
#elif defined(SMC_MR_BOOTCFG_MASK)
#if defined(SMC0)
SMC0->MR = ((~SMC_MR_BOOTCFG_MASK) & SMC0->MR) | SMC_MR_BOOTCFG(3);
#else
SMC->MR = ((~SMC_MR_BOOTCFG_MASK) & SMC->MR) | SMC_MR_BOOTCFG(3);
#endif
#endif // defined(RCM_MR_BOOTROM_MASK)
init_interrupts();
// Set the VTOR to default.
SCB->VTOR = kDefaultVectorTableAddress;
// Restore clock to default before leaving bootloader.
configure_clocks(kClockOption_ExitBootloader);
// De-initialize hardware such as disabling port clock gate
deinit_hardware();
// Restore global interrupt.
__enable_irq();
#if BL_FEATURE_BYPASS_WATCHDOG
// De-initialize watchdog
bootloader_watchdog_deinit();
#endif // BL_FEATURE_BYPASS_WATCHDOG
}
// Memory barriers for good measure.
__ISB();
__DSB();
}
/**
\brief Set Main Stack Pointer
\details Assigns the given value to the Main Stack Pointer (MSP).
\param [in] topOfMainStack Main Stack Pointer value to set
*/
__STATIC_INLINE void __set_MSP(uint32_t topOfMainStack)
{
register uint32_t __regMainStackPointer __ASM("msp");
__regMainStackPointer = topOfMainStack;
}