写在前面:通过uboot的阅读我学到了很多东西,高手写的代码就是不一样。
代码阅读顺序:
1.第一阶段(Stage 1)
第一阶段的启动代码在 cpu\<cpu type>\start.s中,完成的工作主要有:
CPU自身初始化:包括 MMU,Cache,时钟系统,SDRAM 控制器等的初始化
重定位:把自己从非易失性存储器搬移到 RAM中
分配堆栈空间,设置堆栈指针
清零 BSS 数据段
跳转到第二阶段入口函数 start_armboot()
/Uboot114/u-boot-1.1.4/cpu/arm926ejs/start.S
2.第二阶段(Stage 2)
第二阶段是 u-boot 的主体,入口点是 lib_arm\board.c 中的 start_armboot()函数,完成的主要工作包括:
为 U-boot 内部私有数据分配存储空间,并清零
依次调用函数指针数组 init_sequence 中定义的函数进行一系列的初始化
如果系统支持 NOR Flash,调用 flash_init ()和 display_flash_config ()初始化并显示检测到的器件信息(AT91SAM9260EK不需要)
如果系统支持LCD或VFD,调用lcd_setmem()或vfd_setmem()计算帧缓冲(Framebuffer)大小,然后在 BSS 数据段之后为 Framebuffer 分配空间,初始化 gd->fb_base 为Framebuffer的起始地址(AT91SAM9260EK不需要)
调用 mem_malloc_init()进行存储分配系统(类似于 C 语言中的堆)的初始化和空间分配
如果系统支持 NAND Flash,调用 nand_init()进行初始化
如果系统支持 DataFlash,调用 AT91F_DataflashInit()和 dataflash_print_info()进行初始化并显示检测到的器件信息
调用 env_relocate ()进行环境变量的重定位,即从 Flash中搬移到 RAM 中
如果系统支持 VFD,调用 drv_vfd_init()进行 VFD 设备初始化(AT91SAM9260EK 不需要)
从环境变量中读取 IP 地址和 MAC 地址,初始化 gd->bd->bi_ip_addr和gd->bd->bi_enetaddr
调用 jumptable_init()进行跳转表初始化,跳转表在global_data中,具体用途尚不清楚
调用 console_init_r()进行控制台初始化
如果需要,调用 misc_init_r()进行杂项初始化
调用enable_interrupts()打开中断
如果需要,调用 board_late_init()进行单板后期初始化,对于 AT91SAM9260EK,主要是以太网初始化
进入主循环:根据用户的选择启动 linux,或者进入命令循环执行用户输入的命令
/Uboot114/u-boot-1.1.4/lib_arm/board.c
3.U-boot 的初始化
3.1 私有数据 global_data
global_data /Uboot114/u-boot-1.1.4/include/asm-arm/global_data.h
bd_info /Uboot114/u-boot-1.1.4/include/asm-arm/u-boot.h
3.2 初始化序列 init_sequence
Init_sequence是一个函数指针数组,数组中每一个元素都指向一个初始化函数。
init_sequence /Uboot114/u-boot-1.1.4/lib_arm/board.c
(1)cpu_init u-boot-1.1.4\cpu\arm920t\cpu.c
这个函数的功能是设置irq和fiq模式的堆栈起始点。AT91SAM9260EK 没有使用U-boot的中断机制,所以这个函数实际上什么也没做。
(2)board_init \u-boot-1.1.4\board\at91rm9200dk\at91rm9200dk.c
(3)interrupt_init \cpu\arm926ejs\interrupts.c
(4)env_init \common\env_dataflash.c
(5)serial_init \cpu\arm920t\at91rm9200\serial.c
3.3 NAND Flash 初始化
首先初始化NAND Flash接口,包括分配片选,设置片选的时序和模式,配置一些相关的IO口,然后调用nand_probe()检测NAND Flash。nand_probe()函数在common/cmd_nand.c中定义(这个文件实现了所有和NAND Flash相关的功能),调用NanD_ScanChips()函数搜索系统中的NAND Flash芯片(NanD_ScanChips()又调用NanD_IdentChip()检测芯片,实际上就是向NAND FLASH芯片发复位和读 ID命令,根据返回值判断芯片的型号和容量),具体实现细节不再描述。在这个版本的uboot中有一些出入,但是基本过程都差不多
nand_init()完成NAND Flash的初始化。这个函数在board/at91rm9200dk/at91rm9200dk.c中。
3.4 DataFlash 初始化
AT91F_DataflashInit()完成 DataFlash 的初始化。这个函数在 drivers/dataflash.c 中。首先调用AT91F_SpiInit ()初始化SPI接口,然后调用AT91F_DataflashProbe()扫描所有的 SPI 片选,检测DataFlash是否存在,实现原理和NAND Flash类似,都是向芯片发送查询ID命令,根据返回值判断芯片的类型和容量。
AT91F_SpiInit ()函数的定义在 cpu/arm926ejs/at91sam9260/spi.c中, 但是我这套代码中没有,我就把源码粘贴到这里了.
void AT91F_SpiInit(void) {
volatile unsigned int dummy;
AT91F_PIO_CfgPeriph(AT91C_BASE_PIOA,
(AT91C_PA0_SPI0_MISO | AT91C_PA1_SPI0_MOSI |
AT91C_PA2_SPI0_SPCK | AT91C_PA3_SPI0_NPCS0 |
AT91C_PC11_SPI0_NPCS1 | AT91C_PC16_SPI0_NPCS2 |
AT91C_PC17_SPI0_NPCS3),
0);
AT91C_BASE_PMC->PMC_PCER = 1 << AT91C_ID_SPI0;
AT91C_BASE_SPI0->SPI_CR = AT91C_SPI_SWRST;
AT91C_BASE_SPI0->SPI_MR = AT91C_SPI_MSTR | AT91C_SPI_MODFDIS |
AT91C_SPI_PCS;
AT91C_BASE_SPI0->SPI_CSR[0] = (AT91C_SPI_CPOL | (AT91C_SPI_DLYBS &
DATAFLASH_TCSS) |
(AT91C_SPI_DLYBCT & DATAFLASH_TCHS) |
(AT91C_MASTER_CLOCK / AT91C_SPI_CS0_CLK) << 8);
AT91C_BASE_SPI0->SPI_CSR[3] = (AT91C_SPI_CPOL | (AT91C_SPI_DLYBS &
DATAFLASH_TCSS) |
(AT91C_SPI_DLYBCT & DATAFLASH_TCHS) |
(AT91C_MASTER_CLOCK / AT91C_SPI_CS3_CLK) << 8);
AT91C_BASE_SPI0->SPI_CR = AT91C_SPI_SPIEN;
while(!(AT91C_BASE_SPI0->SPI_SR & AT91C_SPI_SPIENS));
// Add tempo to get SPI in a safe state.
// Should be removed for new silicon (Rev B)
udelay(500000);
dummy = AT91C_BASE_SPI0->SPI_SR;
dummy = AT91C_BASE_SPI0->SPI_RDR;
}
AT91F_DataflashProbe()在\board\at91rm9200dk\at45.c
3.5 环境变量重
common/env_common.c 中的 env_relocate()函数实现环境变量的重定位:
3.6 初始化设备
U-boot中设备的类型是 device_t,在 include/devices.h 中定义:
device_t 的主体是一系列操作设备的函数指针,另外还包含了设备名称,标记和私有数据等等。
common/devices.c 中的 devices_init 函数实现设备的初始化
函数 drv_system_init ()总是要执行的,这个函数创建并注册了一个串行口设备和一个空设备(空设备是可选的),在common/devices.c 中定义:
3.7 控制台初始化
控制台初始化分两个阶段:console_init_f()和console_init_r()。console_init_f()完成的功能很简单,只是根据环境变量设置了global_data中的一些数据成分(hasconsole,flag); console_init_r()在common/console.c中定义,完成主要的控制台初始化工作:在设备链表中搜索 stdin,stdout,stderr设备;将搜索结果分别设为置控制台的in, out和err设备。
通过console_setfile()函数可以看出,控制台有一个包含 3 个 device_t 元素的数组stdio_devices,分别对应 stdin,stdout,stderr。通过 stdio_devices[file] = dev 就可以将dev设成设置控制台的某个设备。这样就实现了控制台任意选择设备的功能。这和 linux 的设计思想有点类似。
3.8 单板后期初始化
函数 board_late_init()完成单板后期的初始化,对于AT91SAM9260EK,这个函数在board/at91sam9260ek/at91sam9260ek.c中定义,调用 cpu/arm926ejs/at91sam9260/at91_emac.c中的函数 eth_init()完成以太网的初始化。
我的版本中没有cpu/arm926ejs/at91sam9260/at91_emac.c这个文件下面是部分源码:
int eth_init (bd_t * bd)
{
unsigned int periphAEnable, periphBEnable;
unsigned int val, i;
int ret;
p_mac = AT91C_BASE_EMACB;
#ifdef CONFIG_AT91C_USE_RMII
periphAEnable = ((unsigned int) AT91C_PA21_EMDIO ) |
((unsigned int) AT91C_PA20_EMDC ) |
((unsigned int) AT91C_PA19_ETXCK ) |
((unsigned int) AT91C_PA18_ERXER ) |
((unsigned int) AT91C_PA14_ERX0 ) |
((unsigned int) AT91C_PA17_ERXDV ) |
((unsigned int) AT91C_PA15_ERX1 ) |
((unsigned int) AT91C_PA16_ETXEN ) |
((unsigned int) AT91C_PA12_ETX0 ) |
((unsigned int) AT91C_PA13_ETX1 );
periphBEnable = 0;
#else
periphAEnable = ((unsigned int) AT91C_PA21_EMDIO ) |
((unsigned int) AT91C_PA19_ETXCK ) |
((unsigned int) AT91C_PA20_EMDC ) |
((unsigned int) AT91C_PA18_ERXER ) |
((unsigned int) AT91C_PA14_ERX0 ) |
((unsigned int) AT91C_PA17_ERXDV ) |
((unsigned int) AT91C_PA15_ERX1 ) |
((unsigned int) AT91C_PA16_ETXEN ) |
((unsigned int) AT91C_PA12_ETX0 ) |
((unsigned int) AT91C_PA13_ETX1 );
periphBEnable = ((unsigned int) AT91C_PA27_ERXCK ) |
((unsigned int) AT91C_PA29_ECOL ) |
((unsigned int) AT91C_PA25_ERX2 ) |
((unsigned int) AT91C_PA26_ERX3 ) |
((unsigned int) AT91C_PA22_ETXER ) |
((unsigned int) AT91C_PA10_ETX2 ) |
((unsigned int) AT91C_PA11_ETX3 ) |
((unsigned int) AT91C_PA28_ECRS );
#endif
AT91C_BASE_PIOA->PIO_ASR = periphAEnable;
AT91C_BASE_PIOA->PIO_BSR = periphBEnable;
AT91C_BASE_PIOA->PIO_PDR = (periphAEnable | periphBEnable);
/ * 禁止收发,清除收发状态寄存器 */
p_mac->EMAC_NCR = 0;
p_mac->EMAC_TSR = 0xFFFFFFFF;
p_mac->EMAC_RSR = 0xFFFFFFFF;
/ * 打开 MAC模块的时钟 */
*AT91C_PMC_PCER = 1 << AT91C_ID_EMAC;
/ * 禁止 PA17(RXDV) 的上拉电阻 */
AT91C_BASE_PIOA->PIO_PPUDR = 1 << 17;
/ * 选择 MAC接口为 MII 模式 */
p_mac->EMAC_USRIO = AT91C_EMAC_CLKEN;
#ifdef CONFIG_AT91C_USE_RMII
p_mac->EMAC_USRIO |= AT91C_EMAC_RMII;
#endif
RxBuffIndex = 0;
TxBuffIndex = 0;
/ * 初始化接收缓冲区描述符链表 */
for (i = 0; i < RBF_FRAMEMAX; ++i) {
val = (unsigned int)(rbf_framebuf[i]);
RxtdList[i].addr = val & 0xFFFFFFF8;
RxtdList[i].U_Status.status = 0;
}
RxtdList[RBF_FRAMEMAX-1].addr |= RBF_WRAP;
/ * 初始化发送缓冲区描述符链表 */
for (i = 0; i < TBF_FRAMEMAX; ++i) {
val = (unsigned int)(tbf_framebuf[i]);
TxtdList[i].addr = val & 0xFFFFFFF8;
TxtdList[i].U_Status.status = 0;
TxtdList[i].U_Status.S_Status.BuffUsed = 1;
}
TxtdList[0].U_Status.S_Status.BuffUsed = 0;
TxtdList[TBF_FRAMEMAX-1].U_Status.S_Status.Wrap = 1;
/ * 获取 PHY芯片的操作函数 */
at91sam9260_GetPhyInterface (&PhyOps);
/ * 判断 PHY芯片是否正常连接 */
if (!PhyOps.IsPhyConnected (p_mac))
{
printf ("PHY not connected!\n\r");
return -1;
}
else
printf ("PHY is connected!\n\r");
/ * 调用 PHY芯片的初始化函数:读取 PHY芯片的工作模式,把 MAC的工作模式设
为和 PHY一致 */
ret = PhyOps.Init (p_mac);
if ( !ret && 0 )
{
printf ("MAC: error during MAC initialization\n");
return -1;
}
/ * MAC初始化 */
AT91F_EMACInit(bd, (unsigned int)RxtdList, (unsigned int)TxtdList);
return 0;
}
int AT91F_EMACInit(bd_t * bd,
unsigned int pRxTdList,