天天看点

【linux 系统移植④】uboot 进入 main_loop() 前

文章目录

  • ​​uboot 进入 main_loop() 前​​
  • ​​. arch/arm/cpu/u-boot.lds​​
  • ​​. arch/arm/cpu/armv7/start.S​​
  • ​​.设置异常向量表。​​
  • ​​. 定义uboot中断处理函数栈​​
  • ​​. reset 段:进入 SVC 模式、关闭 IRQ、FIQ.....​​
  • ​​. cp15协处理器的工作:修改异常向量表的入口地址为\_start、调用 lowlevel_init 函数.....​​
  • ​​.arch/arm/lib/ctr0.S/\_main​​
  • ​​初始化 board_init_f() 环境​​
  • ​​.调用 board_init_f()​​
  • ​​.serial_init​​
  • ​​.dram_init​​
  • ​​relocate_code​​
  • ​​board_init_r​​

uboot 进入 main_loop() 前

主要文件:start.S,_main(board_init_f,relocate_code,board_init_r)

. arch/arm/cpu/u-boot.lds

链接脚本默认目录。

makefile、relocate_code、uboot.lds.md

. arch/arm/cpu/armv7/start.S

1. 初始化异常向量表
2. 设置 SVC 模式
3. 关中断
4. 配置cp15协处理器
5. 初始化 mmu、cache、tlb(cpu_init_cp15)
6. 板级初始化(cpu_init_crit)      

.设置异常向量表。

#include <asm-offsets.h>
#include <config.h>
#include <version.h>
#include <asm/system.h>
#include <linux/linkage.h>

.globl _start // 声明 _start 为全局符号,_start 会被链接器链接,链接脚本的入口地址
    /*
    设置异常向量表(4B/item):
    0. 复位异常:复位电平有效时,程序跳转到复位处理程序处执行
    1. 未定义指令异常:遇到不能处理的指令时,产生未定义指令异常
    2. 软件中断异常:执行SWI指令产生的异常
    3. 预存指令异常:处理器预取指令的地址不存在,或该地址不允许当前指令访问,产生指令预取终止异常
    4. 数据操作异常:处理器数据访问指令的地址不存在时,或该地址不允许当前指令访问时,产生数据中止异常
    5. 未使用
    6. IRQ:外部中断请求有效,且 CPSR 中的 I 位为 0 时,产生 IRQ 异常
    7. FIQ:快速中断请求引脚有效,且 CPSR 中的 F 位为 0 时,产生 FIQ 异常
    */
_start: b   reset
    ldr pc, _undefined_instruction // 跳转指令
    ldr pc, _software_interrupt
    ldr pc, _prefetch_abort
    ldr pc, _data_abort
    ldr pc, _not_used
    ldr pc, _irq
    ldr pc, _fiq
#ifdef CONFIG_SPL_BUILD
_undefined_instruction: .word _undefined_instruction
_software_interrupt:    .word _software_interrupt
_prefetch_abort:    .word _prefetch_abort
_data_abort:        .word _data_abort
_not_used:      .word _not_used
_irq:           .word _irq
_fiq:           .word _fiq
_pad:           .word 0x12345678 /* now 16*4=64 */
#else // 如果没有定义 CONFIG_SPL_BUILD...
_undefined_instruction: .word undefined_instruction
_software_interrupt:    .word software_interrupt
_prefetch_abort:    .word prefetch_abort
_data_abort:        .word data_abort
_not_used:      .word not_used
_irq:           .word irq
_fiq:           .word fiq
_pad:           .word 0x12345678 /* now 16*4=64 */
#endif  /* CONFIG_SPL_BUILD */

.global _end_vect
_end_vect:

    .balignl 16,0xdeadbeef // 指定接下来的代码要16字节对齐,空缺的用0xdeadbeef(非法值),方便更加高效的访问内存      

. 定义uboot中断处理函数栈

#ifdef CONFIG_USE_IRQ // 如果uboot使用中断,这里会声明中断处理函数栈起始地址
/* IRQ stack memory (calculated at run-time) */
.globl IRQ_STACK_START
IRQ_STACK_START:
  .word 0x0badc0de // 中断处理函数栈起始地址

/* IRQ stack memory (calculated at run-time) */
.globl FIQ_STACK_START
FIQ_STACK_START:
  .word 0x0badc0de
#endif

/* IRQ stack memory (calculated at run-time) + 8 bytes */
.globl IRQ_STACK_START_IN
IRQ_STACK_START_IN:
  .word 0x0badc0de      

. reset 段:进入 SVC 模式、关闭 IRQ、FIQ…

// cpu 上电或者重启后执行的代码
reset:
  bl  save_boot_params // 再跳转到save_boot_params, 实际什么也没做,直接返回,毕竟栈没有初始化
  /*
   * 修改 CPSR 寄存器(程序状态寄存器),设置处理器进入 SVC 模式,并且关掉 IRQ、FIQ
   */
  mrs r0, cpsr
  and r1, r0, #0x1f   @ mask mode bits
  teq r1, #0x1a   @ test for HYP mode
  bicne r0, r0, #0x1f   @ clear all mode bits
  orrne r0, r0, #0x13   @ set SVC mode
  orr r0, r0, #0xc0   @ disable FIQ and IRQ
  msr cpsr,r0
...
ENTRY(save_boot_params)
  bx  lr      @ back to my caller // 什么也没做,返回,栈没有初始化,最好不要在这里有操作
ENDPROC(save_boot_params)
  .weak save_boot_params // 如果该函数在其它地方没有定义,则为空函数,有则定义该函数      

. cp15协处理器的工作:修改异常向量表的入口地址为_start、调用 lowlevel_init 函数…

cp15 协处理器的工作:

1. 设置异常向量入口
2. cpu_init_cp15:配置 cp15 协处理器相关寄存器来设置处理器的 MMU、cache 以及 TLB。
3. cpu_init_crit:调用 `lowlevel_init函数` —— ddr、clk 初始化      
/*
 * Setup vector:
 * (OMAP4 spl TEXT_BASE is not 32 byte aligned.
 * Continue to use ROM code vector only in OMAP4 spl)
 */
/*
对cp15协处理器进行操作
 */
#if !(defined(CONFIG_OMAP44XX) && defined(CONFIG_SPL_BUILD))
  /* Set V=0 in CP15 SCTRL register - for VBAR to point to vector */
  mrc p15, 0, r0, c1, c0, 0 @ Read CP15 SCTRL Register
  bic r0, #CR_V   @ V = 0
  mcr p15, 0, r0, c1, c0, 0 @ Write CP15 SCTRL Register

  /* Set vector address in CP15 VBAR register */
  ldr r0, =_start // 修改了ARM默认的异常向量表入口地址,将0x00变为了_start
  mcr p15, 0, r0, c12, c0, 0  @Set VBAR
#endif

  /* the mask ROM code should have PLL and others stable */
#ifndef CONFIG_SKIP_LOWLEVEL_INIT // 如果没有定义这个宏...
  bl  cpu_init_cp15
  bl  cpu_init_crit
#endif

  bl  _main // 跳转到 main start.S 使命结束
...      

.arch/arm/lib/ctr0.S/_main

初始化 board_init_f() 环境

/*这个文件处理uboot启动中与生成目标无关的阶段,即准备C环境。
该文件由 _start.S 进入。
主要执行顺序是:
1. 为调用 board_init_f() 设置初始化环境
    该环境提供了一个栈和存储 GD 数据结构的地方
2. 调用 board_init_f() —— 为硬件做准备
    使用 GD 存储数据 —— relocation destination、future stack、future GD location
(非SPL构建)
3. 建立中间环境
    堆栈和GD为board_init_f()分配的,接下来设置BSS和SS
4. 调用relocate_code()
    将uboot从当前位置前往board_init_f()中得到的目的地址
5. 为调用board_init_r()设置环境
    BSS:初始化为0
    初始化 non-const 数据
    在系统中设置堆栈
    一些CPU需要调用c_runtime_cpu_setup() 为GD做一些工作
6. 前往board_init_r()*/

ENTRY(_main)

/*
 * Set up initial C runtime environment and call board_init_f(0).
 */

#if defined(CONFIG_SPL_BUILD) && defined(CONFIG_SPL_STACK)
    ldr sp, =(CONFIG_SPL_STACK)
#else
    ldr sp, =(CONFIG_SYS_INIT_SP_ADDR) // 加载栈指针到sp中
#endif
    bic sp, sp, #7  /* 8-byte alignment for ABI compliance */ // 8 字节对齐
    sub sp, #GD_SIZE    /* allocate one GD above SP */ // 减去GD_SIZE的大小
    bic sp, sp, #7  /* 8-byte alignment for ABI compliance */
    mov r9, sp      /* GD is above SP */ // r9寄存器保存gd结构体的首地址
    mov r0, #0
    bl  board_init_f

...      
#define DECLARE_GLOBAL_DATA_PTR     register volatile gd_t *gd asm ("r9") // 使用 gd 结构体需要加入宏定义      

.调用 board_init_f()

初始化 gd 
调用执行 init_sequence
  有必要关注的初始化函数:
  arch_cpu_init: 可以先写一个空函数启动uboot
  timer_init 在lib/time.c中有实现,也是空函数,但是有__WEAK关键字,如果自己实现,则会调用自己实现的这个函数
  serial_init
  mark_bootstage
  env_init,   /* initialize environment */
  init_baudrate,    /* initialze baudrate settings */
  serial_init,    /* serial communications setup */
  console_init_f,   /* stage 1 init of console */
  display_banner,   /* say that we are here */
  dram_init      
// board.c
void board_init_f(ulong bootflag)
{
    ...
        
    gd->mon_len = _bss_end_ofs; // 初始化mon_len,代表uboot code的大小
    
    ...
    // 遍历调用 init_sequence 所有函数
    for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) {
        if ((*init_fnc_ptr)() != 0) {
            hang ();
        }
    }

    ...
}

...
//
// 数组内容是指向函数的指针
init_fnc_t *init_sequence[] = {
    arch_cpu_init,      // 可以写一个空函数先启动uboot
    mark_bootstage,
#ifdef CONFIG_OF_CONTROL
    fdtdec_check_fdt,
#endif
#if defined(CONFIG_BOARD_EARLY_INIT_F)
    board_early_init_f,
#endif
    timer_init,     // timer_init在lib/time.c中有实现,也是空函数,但是有__WEAK关键字,如果自己实现,则会调用自己实现的这个函数
#ifdef CONFIG_BOARD_POSTCLK_INIT
    board_postclk_init,
#endif
#ifdef CONFIG_FSL_ESDHC
    get_clocks,
#endif
    env_init,       /* initialize environment */
    init_baudrate,      /* initialze baudrate settings */
    serial_init,        // 精简uboot启动必须
    console_init_f,     // 精简uboot启动必须
    display_banner,     /* say that we are here */
#if defined(CONFIG_DISPLAY_CPUINFO)
    print_cpuinfo,      /* display cpu info (and speed) */
#endif
#if defined(CONFIG_DISPLAY_BOARDINFO)
    checkboard,     /* display board info */
#endif
#if defined(CONFIG_HARD_I2C) || defined(CONFIG_SYS_I2C)
    init_func_i2c,
#endif
    dram_init,      // 精简uboot启动必须
    NULL, // 退出循环
};      
回到 board_init_f,剩余代码将会对sdram空间进行规划      
// board_init_f

// 一部分内存空间隐藏
...
#if defined(CONFIG_SYS_MEM_TOP_HIDE) // CONFIG_SYS_MEM_TOP_HIDE 将一部分内存空间隐藏
  gd->ram_size -= CONFIG_SYS_MEM_TOP_HIDE;
#endif

  addr = CONFIG_SYS_SDRAM_BASE + gd->ram_size; // 可用sdram的顶端
...

// 预留出tlb空间
#if !(defined(CONFIG_SYS_ICACHE_OFF) && defined(CONFIG_SYS_DCACHE_OFF))
  /* reserve TLB table */
  gd->arch.tlb_size = 4096 * 4; // 如果打开了 icache 以及 dcache,则预留出PATABLE_SIZE大小的tlb空间
  addr -= gd->arch.tlb_size; 

  /* round down to next 64 kB limit */
  addr &= ~(0x10000 - 1);

  gd->arch.tlb_addr = addr; // tlb空间存放首地址
  debug("TLB table from %08lx to %08lx\n", addr, addr + gd->arch.tlb_size);
#endif

  /* round down to next 4 kB limit */
  addr &= ~(4096 - 1);
  debug("Top of RAM usable for U-Boot at: %08lx\n", addr);

// 确定 frambuffer
#ifdef CONFIG_LCD
#ifdef CONFIG_FB_ADDR
  gd->fb_base = CONFIG_FB_ADDR; // 获取frambuffer大小
#else
  /* reserve memory for LCD display (always full pages) */
  addr = lcd_setmem(addr);
  gd->fb_base = addr; // framebuffer 首地址
#endif /* CONFIG_FB_ADDR */
#endif /* CONFIG_LCD */

  /*
   * reserve memory for U-Boot code, data & bss
   * round down to next 4 kB limit
   */
  addr -= gd->mon_len; // 为uboot的code留出空间
  addr &= ~(4096 - 1);

  debug("Reserving %ldk for U-Boot at: %08lx\n", gd->mon_len >> 10, addr);

// 确定 addr_sp 位置
#ifndef CONFIG_SPL_BUILD
  /*
   * 预留 malloc len 空间
   */
  addr_sp = addr - TOTAL_MALLOC_LEN;
  debug("Reserving %dk for malloc() at: %08lx\n",
      TOTAL_MALLOC_LEN >> 10, addr_sp);
  /*
   * (permanently) allocate a Board Info struct
   * and a permanent copy of the "global" data
   */
  addr_sp -= sizeof (bd_t);
  bd = (bd_t *) addr_sp;
  gd->bd = bd; // 全局信息 bd_t 结构体空间的首地址存在 gd->bd
  debug("Reserving %zu Bytes for Board Info at: %08lx\n",
      sizeof (bd_t), addr_sp);

#ifdef CONFIG_MACH_TYPE
  gd->bd->bi_arch_number = CONFIG_MACH_TYPE; /* board id for Linux */
#endif

  addr_sp -= sizeof (gd_t);
  id = (gd_t *) addr_sp; // gd_t 结构体的空间首地址在gd->bd
  debug("Reserving %zu Bytes for Global Data at: %08lx\n",
      sizeof (gd_t), addr_sp);

#if defined(CONFIG_OF_SEPARATE) && defined(CONFIG_OF_CONTROL)
  /*
   * If the device tree is sitting immediate above our image then we
   * must relocate it. If it is embedded in the data section, then it
   * will be relocated with other data.
   */
  if (gd->fdt_blob) {
    fdt_size = ALIGN(fdt_totalsize(gd->fdt_blob) + 0x1000, 32);
    addr_sp -= fdt_size;
    new_fdt = (void *)addr_sp;
    debug("Reserving %zu Bytes for FDT at: %08lx\n",
          fdt_size, addr_sp);
  }
#endif

  /* setup stackpointer for exeptions */
  gd->irq_sp = addr_sp; // 异常栈指针
#ifdef CONFIG_USE_IRQ
  addr_sp -= (CONFIG_STACKSIZE_IRQ+CONFIG_STACKSIZE_FIQ);
  debug("Reserving %zu Bytes for IRQ stack at: %08lx\n",
    CONFIG_STACKSIZE_IRQ+CONFIG_STACKSIZE_FIQ, addr_sp);
#endif
  // 留出12字节
  addr_sp -= 12;

  /* 8-byte alignment for ABI compliance */
  addr_sp &= ~0x07;
#else
  addr_sp += 128; /* leave 32 words for abort-stack   */
  gd->irq_sp = addr_sp;
#endif
  interrupt_init();

  debug("New Stack Pointer is: %08lx\n", addr_sp);

#ifdef CONFIG_POST
  post_bootmode_init();
  post_run(NULL, POST_ROM | post_bootmode_get(0));
#endif

  gd->bd->bi_baudrate = gd->baudrate;
  /* Ram ist board specific, so move it to board code ... */
  dram_init_banksize();
  display_dram_config();  /* and display it */

  gd->relocaddr = addr;
  gd->start_addr_sp = addr_sp;
  gd->reloc_off = addr - _TEXT_BASE;
  debug("relocation Offset is: %08lx\n", gd->reloc_off);
  if (new_fdt) {
    memcpy(new_fdt, gd->fdt_blob, fdt_size);
    gd->fdt_blob = new_fdt;
  }
  memcpy(id, (void *)gd, sizeof(gd_t));
}      

.serial_init

gd 中的 have_console 字段就是该函数设置的      
// serial.c
int serial_init(void)
{
  return get_current()->start(); // 串口驱动中给出的默认调试串口结构体,执行start,做一些特定串口初始化:console_init_f 将gd中have_console置1
}
static struct serial_device *get_current(void)
{
  struct serial_device *dev;

  if (!(gd->flags & GD_FLG_RELOC))
    dev = default_serial_console();
  else if (!serial_current) // serial_current 用来存放我们当前要使用的 serial
    dev = default_serial_console(); // default_serial_console 在 srial 驱动中有实现,来返回一个默认的调试串口
  else
    dev = serial_current;

  /* We must have a console device */
  if (!dev) {
#ifdef CONFIG_SPL_BUILD
    puts("Cannot find console\n");
    hang();
#else
    panic("Cannot find console\n");
#endif
  }

  return dev;
}      

.dram_init

对gd的ram_size字段进行设置(dram_init实现可以通过配置文件定义宏定义来实现,也可以通过对ddrc控制器读获取dram信息。)      
// misc.c
int dram_init(void)
{
  gd->ram_size = get_ram_size((long *)PHYS_SDRAM_1, PHYS_SDRAM_1_SIZE);
  return 0;
}
// memsize.c
long get_ram_size(long *base, long maxsize)
{
  volatile long *addr;
  long           save[32];
  long           cnt;
  long           val;
  long           size;
  int            i = 0;

  for (cnt = (maxsize / sizeof (long)) >> 1; cnt > 0; cnt >>= 1) {
    addr = base + cnt;  /* pointer arith! */
    sync ();
    save[i++] = *addr;
    sync ();
    *addr = ~cnt;
  }

  addr = base;
  sync ();
  save[i] = *addr;
  sync ();
  *addr = 0;

  sync ();
  if ((val = *addr) != 0) {
    /* Restore the original data before leaving the function.
     */
    sync ();
    *addr = save[i];
    for (cnt = 1; cnt < maxsize / sizeof(long); cnt <<= 1) {
      addr  = base + cnt;
      sync ();
      *addr = save[--i];
    }
    return (0);
  }

  for (cnt = 1; cnt < maxsize / sizeof (long); cnt <<= 1) {
    addr = base + cnt;  /* pointer arith! */
    val = *addr;
    *addr = save[--i];
    if (val != ~cnt) {
      size = cnt * sizeof (long);
      /* Restore the original data before leaving the function.
       */
      for (cnt <<= 1; cnt < maxsize / sizeof (long); cnt <<= 1) {
        addr  = base + cnt;
        *addr = save[--i];
      }
      return (size);
    }
  }

  return (maxsize);
}      

relocate_code

board_init_r

gd->flags |= GD_FLG_RELOC; // 标志已经relocate
使能cache
调用板级支持函数—— board_init()
serial_initialize();
对malloc预留的空间初始化,起始地址,结束地址,清空。
接下来的代码是做一些外设的初始化
  比如 mmc flash eth,环境变量的设置,还有中断的使能
  ...
进入死循环
for (;;) {
  main_loop();
}      
// cr0.s
...
here:
/* Set up final (full) environment */

    bl  c_runtime_cpu_setup /* we still call old routine here */
    // bss 段清空
    ldr r0, =__bss_start    /* this is auto-relocated! */
    ldr r1, =__bss_end      /* this is auto-relocated! */

    mov r2, #0x00000000     /* prepare zero to clear BSS */

clbss_l:cmp r0, r1          /* while not at end of BSS */
    strlo   r2, [r0]        /* clear 32-bit BSS word */
    addlo   r0, r0, #4      /* move to next */
    blo clbss_l

    bl coloured_LED_init    // 实现上电后指示灯亮
    bl red_led_on

    /* call board_init_r(gd_t *id, ulong dest_addr) */
    mov     r0, r9                  /* gd_t */
    ldr r1, [r9, #GD_RELOCADDR] /* dest_addr */
    /* call board_init_r */
    ldr pc, =board_init_r   /* this is auto-relocated! */

    /* we should not return here. */

#endif

ENDPROC(_main)

// ...
ENTRY(c_runtime_cpu_setup)
/*
 * 如果icache是enable,则无效掉icache,保证从sdram中更新指令到cache中
 */
#ifndef CONFIG_SYS_ICACHE_OFF
    mcr p15, 0, r0, c7, c5, 0   @ invalidate icache
    mcr     p15, 0, r0, c7, c10, 4  @ DSB 
    mcr     p15, 0, r0, c7, c5, 4   @ ISB 
#endif
/*
 * 更新异常向量表首地址
 */
    /* Set vector address in CP15 VBAR register */
    ldr     r0, =_start
    mcr     p15, 0, r0, c12, c0, 0  @Set VBAR
 
    bx  lr  
 
ENDPROC(c_runtime_cpu_setup)      
// board.c
void board_init_r(gd_t *id, ulong dest_addr)
{
  ulong malloc_start;
#if !defined(CONFIG_SYS_NO_FLASH)
  ulong flash_size;
#endif

  gd->flags |= GD_FLG_RELOC;  // 置位 gd->flags,标志已经relocate
  bootstage_mark_name(BOOTSTAGE_ID_START_UBOOT_R, "board_init_r"); // 调试用

  monitor_flash_len = _end_ofs;

  /* Enable caches */
  enable_caches();

  debug("monitor flash len: %08lX\n", monitor_flash_len);
  board_init(); /* Setup chipselects */
  /*
   * TODO: printing of the clock inforamtion of the board is now
   * implemented as part of bdinfo command. Currently only support for
   * davinci SOC's is added. Remove this check once all the board
   * implement this.
   */
#ifdef CONFIG_CLOCKS
  set_cpu_clk_info(); /* Setup clock information */
#endif
  serial_initialize(); // drivers/serial/serial.c 所有串口驱动都会实现一个xxxx_serial_initialize函数,并且添加到serial_initialize中,总结一下,serial_initialize工作是将所有serial驱动中所有串口注册到serial_devices链表中,然后找到指定的默认串口。

  debug("Now running in RAM - U-Boot at: %08lx\n", dest_addr);

#ifdef CONFIG_LOGBUFFER
  logbuff_init_ptrs();
#endif
#ifdef CONFIG_POST
  post_output_backlog();
#endif

  /* The Malloc area is immediately below the monitor copy in DRAM */
  malloc_start = dest_addr - TOTAL_MALLOC_LEN;
  mem_malloc_init (malloc_start, TOTAL_MALLOC_LEN); // 对malloc预留的空间初始化,起始地址,结束地址,清空。

#ifdef CONFIG_ARCH_EARLY_INIT_R
  arch_early_init_r();
#endif
  power_init_board();

#if !defined(CONFIG_SYS_NO_FLASH)
  puts("Flash: ");

  flash_size = flash_init();
  if (flash_size > 0) {
# ifdef CONFIG_SYS_FLASH_CHECKSUM
    print_size(flash_size, "");
    /*
     * Compute and print flash CRC if flashchecksum is set to 'y'
     *
     * NOTE: Maybe we should add some WATCHDOG_RESET()? XXX
     */
    if (getenv_yesno("flashchecksum") == 1) {
      printf("  CRC: %08X", crc32(0,
        (const unsigned char *) CONFIG_SYS_FLASH_BASE,
        flash_size));
    }
    putc('\n');
# else  /* !CONFIG_SYS_FLASH_CHECKSUM */
    print_size(flash_size, "\n");
# endif /* CONFIG_SYS_FLASH_CHECKSUM */
  } else {
    puts(failed);
    hang();
  }
#endif

#if defined(CONFIG_CMD_NAND)
  puts("NAND:  ");
  nand_init();    /* go init the NAND */
#endif

#if defined(CONFIG_CMD_ONENAND)
  onenand_init();
#endif

#ifdef CONFIG_GENERIC_MMC
  puts("MMC:   ");
  mmc_initialize(gd->bd);
#endif

#ifdef CONFIG_HAS_DATAFLASH
  AT91F_DataflashInit();
  dataflash_print_info();
#endif

  /* initialize environment */
  if (should_load_env())
    env_relocate();
  else
    set_default_env(NULL);

#if defined(CONFIG_CMD_PCI) || defined(CONFIG_PCI)
  arm_pci_init();
#endif

  stdio_init(); /* get the devices list going. */

  jumptable_init();

#if defined(CONFIG_API)
  /* Initialize API */
  api_init();
#endif

  console_init_r(); /* fully init console as a device */

#ifdef CONFIG_DISPLAY_BOARDINFO_LATE
# ifdef CONFIG_OF_CONTROL
  /* Put this here so it appears on the LCD, now it is ready */
  display_fdt_model(gd->fdt_blob);
# else
  checkboard();
# endif
#endif

#if defined(CONFIG_ARCH_MISC_INIT)
  /* miscellaneous arch dependent initialisations */
  arch_misc_init();
#endif
#if defined(CONFIG_MISC_INIT_R)
  /* miscellaneous platform dependent initialisations */
  misc_init_r();
#endif

  /* enable exceptions */
  enable_interrupts();

  /* Initialize from environment */
  load_addr = getenv_ulong("loadaddr", 16, load_addr);

#ifdef CONFIG_BOARD_LATE_INIT
  board_late_init();
#endif

#ifdef CONFIG_BITBANGMII
  bb_miiphy_init();
#endif
#if defined(CONFIG_CMD_NET)
  puts("Net:   ");
  eth_initialize(gd->bd);
#if defined(CONFIG_RESET_PHY_R)
  debug("Reset Ethernet PHY\n");
  reset_phy();
#endif
#endif

#ifdef CONFIG_POST
  post_run(NULL, POST_RAM | post_bootmode_get(0));
#endif

#if defined(CONFIG_PRAM) || defined(CONFIG_LOGBUFFER)
  /*
   * Export available size of memory for Linux,
   * taking into account the protected RAM at top of memory
   */
  {
    ulong pram = 0;
    uchar memsz[32];

#ifdef CONFIG_PRAM
    pram = getenv_ulong("pram", 10, CONFIG_PRAM);
#endif
#ifdef CONFIG_LOGBUFFER
#ifndef CONFIG_ALT_LB_ADDR
    /* Also take the logbuffer into account (pram is in kB) */
    pram += (LOGBUFF_LEN + LOGBUFF_OVERHEAD) / 1024;
#endif
#endif
    sprintf((char *)memsz, "%ldk", (gd->ram_size / 1024) - pram);
    setenv("mem", (char *)memsz);
  }
#endif

  /* main_loop() can return to retry autoboot, if so just run it again. */
  for (;;) {
    main_loop();
  }

  /* NOTREACHED - no way out of command loop except booting */
}