S5PV210-uboot解析(三)-start_armboot解析-gd_t和init_sequence函数指针数组

本文详细解析了U-Boot的启动过程,包括CPU初始化、板级初始化、时钟配置、环境变量初始化等关键步骤,并介绍了相关配置宏的作用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


#define DECLARE_GLOBAL_DATA_PTR     register volatile gd_t *gd asm ("r8")
这个宏用来定义gd_t类型的gd指针,后面的asm ("r8")代表指定arm里的r8寄存器存储。gd_t里面存储着很多全局变量供整个uboot使用,里面还有个bd_t存放了开发板的硬件相关的参数,这里用宏定义来声明这个变量,需要用到gd_t的地方就使用宏DECLARE_GLOBAL_DATA_PTR。

这个是在asm-arm/gloal_data.h文件里定义的全局变量gd_t,里面还用到了一个在asm-arm/uboot.h里定义的bd_t。
typedefstruct global_data {
    bd_t        *bd;
    unsigned long flags;
    unsigned long baudrate;
    unsigned long have_console;  /* serial_init() was called */
    unsigned long reloc_off; /* Relocation Offset */
    unsigned long env_addr;  /* Address  of Environment struct */
    unsigned long env_valid; /* Checksum of Environment valid? */
    unsigned long fb_base;   /* base address of frame buffer */
#ifdef CONFIG_VFD
    unsigned char vfd_type;  /* display type */
#endif
#if 0
    unsigned long cpu_clk;   /* CPU clock in Hz!     */
    unsigned long bus_clk;
    phys_size_t ram_size;  /* RAM size */
    unsigned long reset_status;  /* reset status register at boot */
#endif
    void       **jt;      /* jump table */
} gd_t;

typedef struct bd_info {
    int        bi_baudrate;   /* serial console baudrate */
    unsigned long bi_ip_addr;/* IP Address */
    unsigned char bi_enetaddr[6]; /* Ethernet adress */
    struct environment_s          *bi_env;
    ulong           bi_arch_number;/* unique id for this board */
    ulong           bi_boot_params;/* where this board expects params */
    struct             /* RAM configuration */
    {
    ulong start;
    ulong size;
    }           bi_dram[CONFIG_NR_DRAM_BANKS];//CONFIG_NR_DRAM_BANKS 2
#ifdef CONFIG_HAS_ETH1
    /* second onboard ethernet port */
    unsigned char   bi_enet1addr[6];
#endif
} bd_t;



init_fnc_t *init_sequence[] = {
    cpu_init,      /* basic cpu dependent setup */
int cpu_init (void)
{
    /*
     * setup up stacks if necessary
     */
#ifdef CONFIG_USE_IRQ//宏未定义
    IRQ_STACK_START = _armboot_start - CFG_MALLOC_LEN - CFG_GBL_DATA_SIZE - 4;
    FIQ_STACK_START = IRQ_STACK_START - CONFIG_STACKSIZE_IRQ;
#endif
    return 0;
}
cpu_init没有做任何事,因为在汇编的时候已经将cpu相关的部件都初始化了。
#if defined(CONFIG_SKIP_RELOCATE_UBOOT)//宏未定义
    reloc_init,    /* Set the relocation done flag, must
                   do this AFTER cpu_init(), but as soon
                   as possible */
#endif
    board_init,    /* basic board dependent setup */

int board_init(void)
{
    DECLARE_GLOBAL_DATA_PTR;
#ifdef CONFIG_DRIVER_SMC911X//宏未定义
    smc9115_pre_init();
#endif
 
#ifdef CONFIG_DRIVER_DM9000
    dm9000_pre_init();
#endif
 
    gd->bd->bi_arch_number = MACH_TYPE;
    gd->bd->bi_boot_params = (PHYS_SDRAM_1+0x100);
 
    return 0;
}
这里board_init函数主要做了两件事,一件是运行dm9000_pre_init()函数,另一个是给bi_arch_number和
bi_boot_params变量赋值,bi就是board_info,两个变量都是开发板的参数。
arch_number是机器码,就是uboot给这个开发板定义的一个唯一编号,主要作用就是在uboot和linux内核之间进
行比对和适配。因为嵌入式设备都是高度定制化的,不同的开发板使用的硬件不同,移植的内核镜像也不同,不
能随意更改。
boot_params则是记录板子的SDRAM的物理地址,uboot将来需要得到硬件配置时就读取bi_相关的变量即可。
static void dm9000_pre_init(void)
{
    unsigned int tmp;
 
#if defined(DM9000_16BIT_DATA)//宏定义了
    SROM_BW_REG &= ~(0xf << 4);//SROM_BW_REG=0xE800_0000
    SROM_BW_REG |= (1<<7) | (1<<6) | (1<<5) | (1<<4);
#else
    SROM_BW_REG &= ~(0xf << 4);
    SROM_BW_REG |= (0<<6) | (0<<5) | (0<<4);
#endif
    SROM_BC1_REG = ((0<<28)|(1<<24)|(5<<16)|(1<<12)|(4<<8)|(6<<4)|(0<<0));//uboot
    //SROM_BC1_REG = ((0<<28)|(0<<24)|(5<<16)|(0<<12)|(0<<8)|(0<<4)|(0<<0));//kernel
    tmp = MP01CON_REG;//0xE0200000 + 0x2E0
    tmp &=~(0xf<<4);
    tmp |=(2<<4);
    MP01CON_REG = tmp;
}
不是很清楚具体做什么,查数据手册SROM_BW_REG是设置SROM controller的,相当于配置SROM,
SROM_BC1_REG是配置T周期的,MP01CON_REG是使能GPIO为SROM_CSn[0]。查dm9000是网卡型号,根据函数
名来看这里应该是进行网卡相关配置的一个预处理。
 2017/11/10 增补:
SROM是( SRAM或ROM )的统称,SoC的SROMController其实就是SoC提供的对外总线式连接SRAM/ROM的接
口。如果SoC要外部外接一些SRAM/ROM类的存储芯片(或者伪装成SROM接口的芯片,譬如网卡芯片)就要通
过SROM Controller来连接。网卡接在SROM中好处就是网卡芯片好像一个存储芯片一样被扩展在SoC的一个地址
空间中,主机SoC可以直接用一个地址来访问网卡芯片内部寄存器。
查原理图可知 DM9000 接了 SoC 的 CSn1,即接在了 bank1 上,因此使用 SROMcontroller1 来控制,这里就是
相对应的初始化操作。

    interrupt_init,    /* set up exceptions */
int interrupt_init(void)
{
 
    S5PC11X_TIMERS *const timers = S5PC11X_GetBase_TIMERS();
 
    /* use PWM Timer 4 because it has no output */
    /* prescaler for Timer 4 is 16 */
    timers->TCFG0 = 0x0f00;
    if (timer_load_val == 0) {
        /*
         * for 10 ms clock period @ PCLK with 4 bit divider = 1/2
         * (default) and prescaler = 16. Should be 10390
         * @33.25MHz and  @ 66 MHz
         */
        timer_load_val = get_PCLK() / (16 * 100);
    }
 
    /* load value for 10 ms timeout */
    lastdec = timers->TCNTB4 = timer_load_val;
    /* auto load, manual update of Timer 4 */
    timers->TCON = (timers->TCON & ~0x00700000) | TCON_4_AUTO | TCON_4_UPDATE;
    /* auto load, start Timer 4 */
    timers->TCON = (timers->TCON & ~0x00700000) | TCON_4_AUTO | COUNT_4_ON;
    timestamp = 0;
 
 
    return (0);
}
这里是用来配置时钟的。get_PCLK()函数是用来得到PCLK_PSYS的的时钟频率,get_PCLK又是根
据get_HCLK得到的HCLK的时钟频率,如此,最终只在get_PLLCLK函数中是通过访问寄存器来得
到时钟频率,其他的函数都是通过函数来得到对应CLK的时钟频率。
另外这里使用的是Timer4定时器,这个定时器没有引脚,无法输出pwm波形,所以这个计时只能采
用轮询方式而不能使用中断,典型的应用就是bootdelay,在bootdelay的时候只进行计时和检测按
键,无法做其他操作。

    env_init,      /* initialize environment */

环境变量的初始化,这个函数只是对内存里维护的那一份uboot的env做了基本的初始化或者说是判定(判定里面有没有能用的环境变量)。当前因为我们还没进行环境变量从SD卡到DDR中的relocate,因此当前环境变量是不能用的。在start_armboot函数中调用env_relocate才进行环境变量从SD卡中到DDR中的重定位。重定位之后需要环境变量时才可以从DDR中去取,重定位之前如果要使用环境变量只能从SD卡中去读取。

    init_baudrate,     /* initialze baudrate settings */
static int init_baudrate (void)
{
    char tmp[64]; /* long enough for environment variables */
    int i = getenv_r ("baudrate", tmp, sizeof (tmp));
    gd->bd->bi_baudrate = gd->baudrate = (i > 0)
            ? (int) simple_strtoul (tmp, NULL, 10)
            : CONFIG_BAUDRATE;
 
    return (0);
}
初始化串口通信的波特率,先去环境变量中读取"baudrate"这个环境变量的值。如果读取成功则使
用这个值作为环境变量,记录在gd->baudrate和gd->bd->bi_baudrate中;如果读取不成功则使用
x210_sd.h中的的CONFIG_BAUDRATE的值作为波特率。

    serial_init,       /* serial communications setup */
初始化串口,实际上在汇编阶段已经被初始化过了,跳转到定义发现函数和cpu_init类似,什么都没做。

    console_init_f,    /* stage 1 init of console */
控制台第一阶段初始化,后面的_f表示第一阶段,_r表示第二阶段。这里只是将gd->have_console = 1;其他的都没做。

    display_banner,    /* say that we are here */
显示logo,整个函数实际上只有两句printf ("\n\n%s\n\n", version_string);这一句用来打印uboot版本信息。open_backlight();//lqm.打开背光。实际的display_banner函数中还有几条用来调试的debug()宏。
version_string:
const char version_string[] =
    U_BOOT_VERSION" (" __DATE__ " - " __TIME__ ")"CONFIG_IDENT_STRING;
实际是打印了U_BOOT_VERSION宏,而这个宏是没有在源码中定义的,是通过Makefile传递给uboot的。

#if defined(CONFIG_DISPLAY_CPUINFO)
    print_cpuinfo,     /* display cpu info (and speed) */
int print_cpuinfo(void)
{
    uint set_speed;
    uint tmp;
    uchar result_set;
 
#if defined(CONFIG_CLK_533_133_100_100)
    set_speed = 53300;
#elif defined(CONFIG_CLK_667_166_166_133)
    set_speed = 66700;
#elif defined(CONFIG_CLK_800_200_166_133)
    set_speed = 80000;
#elif defined(CONFIG_CLK_1000_200_166_133)
    set_speed = 100000;
#elif defined(CONFIG_CLK_1200_200_166_133)
    set_speed = 120000;
#else
    set_speed = 100000;
    printf("Any CONFIG_CLK_XXX is not enabled\n");
#endif
    tmp = (set_speed / (get_ARMCLK()/1000000));
 
    if((tmp < 105) && (tmp > 95)){
        result_set = 1;
    else {
        result_set = 0;
    }
 
#ifdef CONFIG_MCP_SINGLE
    printf("\nCPU:  S5PV210@%ldMHz(%s)\n", get_ARMCLK()/1000000, ((result_set == 1) ? "OK" "FAIL"));
#else
    printf("\nCPU:  S5PC110@%ldMHz(%s)\n", get_ARMCLK()/1000000, ((result_set == 1) ? "OK" "FAIL"));
#endif
    printf("        APLL = %ldMHz, HclkMsys = %ldMHz, PclkMsys = %ldMHz\n",
            get_FCLK()/1000000, get_HCLK()/1000000, get_PCLK()/1000000);
#if 1
    printf("    MPLL = %ldMHz, EPLL = %ldMHz\n",
            get_MPLL_CLK()/1000000, get_PLLCLK(EPLL)/1000000);
    printf("               HclkDsys = %ldMHz, PclkDsys = %ldMHz\n",
            get_HCLKD()/1000000, get_PCLKD()/1000000);
    printf("               HclkPsys = %ldMHz, PclkPsys = %ldMHz\n",
            get_HCLKP()/1000000, get_PCLKP()/1000000);
    printf("               SCLKA2M  = %ldMHz\n", get_SCLKA2M()/1000000);
#endif
    puts("Serial = CLKUART ");
 
    return 0;
}
这个函数用来打印cpu信息,主要是时钟频率,打印出的效果:
CPU: S5PV210@1000MHz(OK)
APLL = 1000MHz, HclkMsys = 200MHz, PclkMsys = 100MHz
MPLL = 667MHz, EPLL = 96MHz
HclkDsys = 166MHz, PclkDsys = 83MHz
HclkPsys = 133MHz, PclkPsys = 66MHz
SCLKA2M = 200MHz
Serial = CLKUART

#endif
#if defined(CONFIG_DISPLAY_BOARDINFO)
    checkboard,    /* display board info */
int checkboard(void)
{
#ifdef CONFIG_MCP_SINGLE
#if defined(CONFIG_VOGUES)
    printf("\nBoard:   VOGUESV210\n");
#else
    printf("\nBoard:   X210\n");
#endif //CONFIG_VOGUES
#else
    printf("\nBoard:   X210\n");
#endif
    return (0);
}
这个函数就是用来检查板子的信息,根据定义的宏打印。

#endif
#if defined(CONFIG_HARD_I2C) || defined(CONFIG_SOFT_I2C)
    init_func_i2c,

没有使用i2c,因为对应的宏没有定义,在SI中看到CONFIG_HARD_I2C是红色的,跳转过去后发现实际上是没有被定义的。
#undef CONFIG_S3C64XX_I2C      /* this board has H/W I2C */
#ifdef CONFIG_S3C64XX_I2C
#define CONFIG_HARD_I2C        1

#endif
    dram_init,     /* configure available RAM banks */

int dram_init(void)
{
    DECLARE_GLOBAL_DATA_PTR;
 
    gd->bd->bi_dram[0].start = PHYS_SDRAM_1;
    gd->bd->bi_dram[0].size = PHYS_SDRAM_1_SIZE;
 
#if defined(PHYS_SDRAM_2)
    gd->bd->bi_dram[1].start = PHYS_SDRAM_2;
    gd->bd->bi_dram[1].size = PHYS_SDRAM_2_SIZE;
#endif
 
#if defined(PHYS_SDRAM_3)
    gd->bd->bi_dram[2].start = PHYS_SDRAM_3;
    gd->bd->bi_dram[2].size = PHYS_SDRAM_3_SIZE;
#endif
 
    return 0;
}
初始化bi_dram数组中的相关变量。

    display_dram_config,

显示DRAM的配置。

    NULL,

最后一个元素以NULL结尾,这样在循环中就可以作为条件判断来结束循环。

};

init_sequence函数就是把进行初始化的函数都集中在一起,这里面进行的初始化操作有:
  1. cpu初始化 (实际为空)
  2. 板级初始化 网卡预配置,记录机器码和SDRAM信息
  3. 时钟初始化
  4. 环境变量初始化 这里还没有将环境变量从SD卡迁移到内存,基本都被判定为无效
  5. 串口波特率初始化
  6. 串口初始化 (实际为空)
  7. 控制台初始化第一阶段
  8. 打印banner uboot版本信息和日期时间
  9. 打印cpu信息 各个时钟域的时钟频率
  10. 检查板子信息并打印
  11. 初始化i2c (无效,未配置宏)
  12. 初始化DRAM 配置有几块DRAM和DRAM大小
  13. 打印DRAM配置信息
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值