DM365启动之—RBL、UBL分析
这段时间项目刚刚完成,下个项目还在等公司的安排,希望还是DAVINCI平台吧,其实呢,也无所谓,只是对DAVINCI还是稍稍有点感情,难得有点时间,鬼使神差的看起UBL的源代码来了,顺便做点笔记,看了总得留点痕迹吧,写的不好,大家多多指教哦,呵呵,费话有点多,转正题吧
系统加电或复位后,CPU通常都从某个由CPU制造商预先安排的地址上取指令。DM365提供了各种各样的启动方式,总体上可分为从外部存储器接口AEMIF(NOR Flash/OneNand)引导启动和从ARM内部ROM(AIROM)引导启动两种模式。引导启动模式由BTSEL[2:0]引脚的设置状态。
BTSEL[2:0]=001时,系统加电/复位后ARM处理器从AEMIF 执行引导启动代码,即从外部存储器OneNand或Nor Flash启动。
AIROM支持以下启动模式
BTSEL[2:0]=000,ARM NAND Boot
BTSEL[2:0]=010,ARM MMC/SD Boot
BTSEL[2:0]=011,ARM UART Boot
BTSEL[2:0]=100, ARM USB Boot
BTSEL[2:0]=101,ARM SPI Boot
BTSEL[2:0]=110,ARM EMAC Boot
BTSEL[2:0]=111,ARM HPI Boot
ARM ROM bootloader (RBL) 为内部ROM固化的bootloader。
xxxxxxxxxxxxxxxxxxxxxxxxx
由于DM365处理器AIRAM空间只有32KB及NAND Flash不支持XIP技术等原因,所以DaVinci EVM采取AIROM启动方式,要想完成从NAND Flash引导启动,操作系统的任务至少需要三个阶段代码,启动流程见下图:
xxxxxxxxxxxxxxxxxxxxxxx
以ARM NAND Boot模式为例,下面为RBL详细流程:
1、初始化RAM1的高2K栈空间(0x7800-0x7fff)。RAM的最后4个字节(0x7ffc-0x8000),来于写UBL的块号。
2、禁止所有中断,IRQ和FIQ。
3、外部引脚DEEPSLEEPZ/GIO0在芯片为NAND模式启动复位时必须被置高。
4、从ROM的表中读出NAND的设备ID和参数。
5、根据NAND flash的参数初始化NAND区域。
6、从BLOCKS(Only Page 0 of blocks 1 to 24 will be read and searched for the
magic number)中搜索UBL 幻数(magic number),读出幻数(magic number) 是为了确保当前块可用,当找到有效的 UBL幻数(magic number)后,把相应的块号(block number)写到ARM 内部RAM最后4个字节地址上(0x7ffc-0x8000)。
7、UBL描述符由以下参数组成(所有UBL参数位宽都是32位):
-入口点地址:加载UBL之后的绝对入口点,必须在0x0020-0x781C之间。
-UBL占用的NAND的页数:必须是相邻的页,可以跨越多个块,总字节数必须小于等于30KB(IRAM大小为32K, 最高2K用于栈空间)。
-UBL的起始块:可以与UBL描述符在同一个块。
-UBL的起始页:不可以与UBL描述符在同一页,因为加载的都是完整的页。
8、使能硬件ECC错误检测,复制UBL从NAND flash到IRAM,如果检测到一个4位ECC的读错误,UBL将通过ECC纠正算法来纠正错误。如果是由其他的错误导致读失败的,指示符会继续在下一个新的块中搜索直到找到UBL描述符,一直搜索到block 24。没有的话就从SD卡启动。
9、在UBL入口处把控制权移交给UBL。
10、NAND的安全启动模式是在PLL旁通模式中完成的,它不使用快速EMIF,DMA或者I-Cache。在其他模式下,使用以上的一种组合。例如,在UBL_MAGIC_PLL_DMA_IC_FAST模式下激活其他四个设置,它因该是最快的NAND启动模式。
以下是启动流图:
xxxxxxxxxxx
UBL源码在PSP包里的board_utilities/flash_utils目录下,主要是COMMON目录和各子平台的目录如DM36x等,其中除了UBL的源码外还有CCS下JTAG的擦除烧写源码,串口烧写源码等。
入门代码是汇编文件start.S,主要是切换操作模式,建立堆栈等,然后跳转到main函数,进入到board_utilities/flash_utils/Common/ubl/src目录下的C文件 ubl.c中。
main函数主要调用了LOCAL_boot函数来进行实质的引导功能,然后跳转到entrypoint执行Uboot。在LOCAL_boot函数中主要是Device_init()和NANDBOOT_copy()两个函数。
Device_init()函数来进行平台的最底层初始化,包括电源域,时钟,DDR,EMIF,UART,I2C,TIMER等 。首先屏蔽和清除中断,然后调用DEVICE_PSCInit函数实现对各模块的电源时钟使能,实质是调用PSC电源时钟管理模块的寄存器实现;然后调用DEVICE_pinmuxControl函数决定复用引脚的功能选择;接着调用DM36x/common/src/device.c下的DEVICE_PLL1Init函数、DEVICE_PLL1Init函数实现了PLL1、PLL2的配置,预分频,倍频,后分频,分频到各个模块;接着调用DEVICE_DDR2Init函数来配置DDR控制器,这是UBL中重要的一部分,如果硬件电路需要更换内存芯片的话,需要在UBL中修改这个函数,即按照芯片手册来配置DDR控制寄存器中的相关参数,比如时序,BANK数,页大小等。而后调用DEVICE_EMIFInit函数来配置EMIF模块,调用DEVICE_UART0Init函数来配置串口0,调用DEVICE_TIMER0Init函数来配置TIMER0,调用 DEVICE_I2C0Init函数来配置I2C控制器,都是操作某一模块的控制寄存器实现。
当读取到启动模式配置寄存器(BOOTCFG)值时,如果BTSEL[2:0]=000,则调用NAND初始化函数NAND_init()及复制代码函数NANDBOOT_copy()将存储在NAND中紧随其后的Bootload代码复制到DDR2中。
下面为源代码分析。
注:在说明源代码的时候,为了缩短篇幅,删除了代码的部分DEBUG信息
// Main entry point
void main(void)
{
// Call to real boot function code
LOCAL_boot();
// Jump to entry point
APPEntry = (void (*)(void)) gEntryPoint; /*UBL结束,交还入口地址,执行U-BOOT*/
(*APPEntry)();
}
函数实体唯一调用了LOCAL_boot()函数,下面我们来看看,里面具体做了什么,下面为LOCAL_boot()函数源码。
static Uint32 LOCAL_boot(void)
{
DEVICE_BootMode bootMode;
// Read boot mode
bootMode = DEVICE_bootMode();
if (bootMode == DEVICE_BOOTMODE_UART) {
// Wait until the RBL is done using the UART.
while((UART0->LSR & 0x40) == 0 );
}
// Platform Initialization
if ( DEVICE_init() != E_PASS ) {
DEBUG_printString(" initialization failed!\r\n");
asm(" MOV PC, #0");
}else{
//DEBUG_printString(" initialization passed!\r\n");
}
// Set RAM pointer to beginning of RAM space
UTIL_setCurrMemPtr(0);
// Select Boot Mode
#ifdefined(UBL_NAND) {
// Copy binary image application from NAND to RAM
if (NANDBOOT_copy() != E_PASS) {
DEBUG_printString("NAND Boot failed.\r\n");
LOCAL_bootAbort();
}
}
#elif defined(UBL_NOR) {
// Copy binary application image from NOR to RAM
if (NORBOOT_copy() != E_PASS){
DEBUG_printString("NOR Boot failed.\r\n");
LOCAL_bootAbort();
}
}
#elif defined(UBL_SD_MMC) {
// Copy binary of application image from SD/MMC card to RAM
if (SDMMCBOOT_copy() != E_PASS){
DEBUG_printString("SD/MMC Boot failed.\r\n");
LOCAL_bootAbort();
}
}
#else {
UARTBOOT_copy();
}
#endif
DEVICE_TIMER0Stop();
return E_PASS;
}
程序流程还是很简单,先通过调用DEVICE_bootMode函数来判断启动方式(通过读取BOOTCFG的值),而后调用了DEVICE_init函数来进行平台的最底层初始化,包括电源域,时钟,DDR,EMIF,UART,I2C,TIMER等,都是操作某一模块的控制寄存器实现。
接着通过判断不同的引导方式,采取不同的处理办法,以 NAND启动为例,将调用NANDBOOT_copy函数。此函数将NAND中的某些内容(就是UBOOT)搬移到RAM中,而后 UBL结束,控制权正式交给UBOOT。接下来我们来分析下DEVICE_init与NANDBOOT_copy的具体实现,DEVICE_init源代码如下:
Uint32 DEVICE_init()
{
Uint32 status = E_PASS;
// Mask all interrupts
AINTC->INTCTL = 0x4;
AINTC->EABASE = 0x0;
AINTC->EINT0 = 0x0;
AINTC->EINT1 = 0x0;
// Clear all interrupts
AINTC->FIQ0 = 0xFFFFFFFF;
AINTC->FIQ1 = 0xFFFFFFFF;
AINTC->IRQ0 = 0xFFFFFFFF;
AINTC->IRQ1 = 0xFFFFFFFF;
#ifndef SKIP_LOW_LEVEL_INIT
POR_RESET();
// System PSC setup - enable all
DEVICE_PSCInit();
/*管脚复用的配置*/
DEVICE_pinmuxControl(0,0xFFFFFFFF,0x00FD0000); // All Video Inputs,Y0-Y7全部作为video in(不作 为GPIO),GIO43作为SD1的clk,McBsp开启,MMCSD0关闭
DEVICE_pinmuxControl(1,0xFFFFFFFF,0x00145555); // All Video Outputs,视频Cout0-Cout7作为色度信号输出使能,场消隐/行消隐同步信号使能,LCD的OE功能关闭
DEVICE_pinmuxControl(2,0xFFFFFFFF,0x000000DA); // EMIFA,总线使能,
DEVICE_pinmuxControl(3,0xFFFFFFFF,0x00180000); // SPI0, SPI1, UART1, I2C, SD0, SD1, McBSP0, CLKOUTs,串口1使能,其他均作为GPIO,网卡没使能
DEVICE_pinmuxControl(4,0xFFFFFFFF,0x55555555); //SI1-SPI4使能,MMCSD1 使能
GPIO->DIR02 &= 0xfeffffff;
GPIO->CLRDATA02 = 0x01000000;
// System PLL setup
if (status == E_PASS) status |= DEVICE_PLL1Init(PLL1_Mult);
// DDR PLL setup
if (status == E_PASS) status |= DEVICE_PLL2Init();
// DDR2 module setup
if (status == E_PASS) status |= DEVICE_DDR2Init();
#endif
// AEMIF Setup
if (status == E_PASS) status |= DEVICE_EMIFInit();
// UART0 Setup
if (status == E_PASS) status |= DEVICE_UART0Init();
// TIMER0 Setup
if (status == E_PASS) status |= DEVICE_TIMER0Init();
// I2C0 Setup
if (status == E_PASS) status |= DEVICE_I2C0Init();
return status;
}
接着我们来看NANDBOOT_copy(),UBL的主要工作都在此函数中实现。
// Function to find out where the application is and copy to RAM
Uint32 NANDBOOT_copy()
{
NAND_InfoHandle hNandInfo;
Uint32 count,blockNum;
Uint32 i;
Uint32 magicNum;
Uint8 *rxBuf; // RAM receive buffer
Uint32 block,page;
Uint32 readError = E_FAIL;
Bool failedOnceAlready = FALSE;
// Maximum application size is 16 MB,Allocate memory from the ad-hoc heap
rxBuf = (Uint8*)UTIL_allocMem((APP_IMAGE_SIZE>>1));
blockNum = DEVICE_NAND_UBL_SEARCH_START_BLOCK;
// NAND Initialization,打开 NandFlash设备,将NandFlash的硬件信息放在hNandInfo数据结构内。
hNandInfo = NAND_open(EMIFStart, (Uint8) DEVICE_emifBusWidth());
if (hNandInfo == NULL) {
DEBUG_printString("hNandInfo == NULL\n");
return E_FAIL;
}
NAND_startAgain:
if (blockNum > DEVICE_NAND_UBL_SEARCH_END_BLOCK) {
return E_FAIL; // NAND boot failed and return fail to main
}
/*Read data about Application starting at START_APP_BLOCK_NUM, Page 0 and possibly going until block END_APP_BLOCK_NUM, Page 0*/
/*U-boot一般存在于DEVICE_NAND_UBL_SEARCH_START_BLOCK以后块的第0页,ubl试着从 DEVICE_NAND_UBL_SEARCH_START_BLOCK块向后搜索每块的第0页,找到后,前24个字节分别记录着u-boot描述,如入口函数等,并把记录存入rxBuf中。*/
for(count=blockNum; count <= DEVICE_NAND_UBL_SEARCH_END_BLOCK; count++) {
if(NAND_readPage(hNandInfo,count,0,rxBuf) != E_PASS)
continue;
magicNum = ((Uint32 *)rxBuf)[0];
/* Valid magic number found */
if((magicNum & 0xFFFFFF00) == MAGIC_NUMBER_VALID) {
blockNum = count;
break;
}
}
// Never found valid header in any page 0 of any of searched blocks
if (count > DEVICE_NAND_UBL_SEARCH_END_BLOCK) {
DEBUG_printString("No valid boot image found!\r\n");
return E_FAIL;
}
// Fill in NandBoot header
gNandBoot.entryPoint = *(((Uint32 *)(&rxBuf[4]))); /* The first "long" is entry point for Application*/
gNandBoot.numPage = *(((Uint32 *)(&rxBuf[8]))); /* The second "long" is the number of pages */
gNandBoot.block = *(((Uint32 *)(&rxBuf[12]))); /* The third "long" is the block where Application is stored in NAND */
gNandBoot.page = *(((Uint32 *)(&rxBuf[16]))); /* The fourth "long" is the page number where Application is stored in NAND */
gNandBoot.ldAddress = *(((Uint32 *)(&rxBuf[20]))); /* The fifth "long" is the Application load address */
// If the application is already in binary format, then our received buffer can point to the specified load address
// instead of the temp location used for storing an S-record
// Checking for the UBL_MAGIC_DMA guarantees correct usage with the
// Spectrum Digital CCS flashing tool, flashwriter_nand.out
if ((magicNum == UBL_MAGIC_BIN_IMG) || (magicNum == UBL_MAGIC_DMA)) {
// Set the copy location to final run location
rxBuf = (Uint8 *)gNandBoot.ldAddress;
// Free temp memory rxBuf used to point to
UTIL_setCurrMemPtr((void *)((Uint32)UTIL_getCurrMemPtr() - (APP_IMAGE_SIZE>>1)));
}
NAND_retry:
/* initialize block and page number to be used for read */
block = gNandBoot.block;
page = gNandBoot.page;
// Perform the actual copying of the application from NAND to RAM
for(i=0;i
// if page goes beyond max number of pages increment block number and reset page number
if(page >= hNandInfo->pagesPerBlock) {
page = 0;
block++;
}
readError = NAND_readPage(hNandInfo,block,page++,(&rxBuf[i*(hNandInfo->dataBytesPerPage)])); /* Copy the data */
// We attempt to read the app data twice. If we fail twice then we go look for a new
// application header in the NAND flash at the next block.
if(readError != E_PASS) {
if(failedOnceAlready) {
blockNum++;
goto NAND_startAgain;
} else {
failedOnceAlready = TRUE;
goto NAND_retry;
}
}
}
// Application was read correctly, so set entrypoint
gEntryPoint = gNandBoot.entryPoint;
return E_PASS;
}
到此,UBL的工作全部完全,接下来的工作交给U-BOOT。