加载中…
个人资料
  • 博客等级:
  • 博客积分:
  • 博客访问:
  • 关注人气:
  • 获赠金笔:0支
  • 赠出金笔:0支
  • 荣誉徽章:
正文 字体大小:

arm处理器uboot架构分析

(2021-05-27 09:00:14)
标签:

u-boot

arm

分类: programming
arm处理器u-boot运行从汇编文件arch\arm\cpu\armv8\start.S开始(链接脚本u-boot.lds中定义入口_start),
进入下述区域,第一行加载异常向量表vectors,将 vector 指向的地址放到X0寄存器; 之后调用switch_el, switch_el 是一个类似C函数的宏定义,
======================================
     adr    x0, vectors    
---------------------------------------
   
    switch_el x1, 3f, 2f, 1f
=========================================
switch_el的定义如下,这是一个根据异常等级寄存器内容比较后跳转到不同处理分支的一段代码。CurrentEL为当前异常等级寄存器,其中EL0为非特权等级,即平时应用程序运行时的级别;EL1为特权等级,即操作系统运行时的级别;EL2为虚拟机监视器运行级别,即虚拟机的控制层运行的级别;EL3为切换EL1和EL2级别时需要进入的一个级别,为CPU的最高级别,arm64的异常级别示意图。
arm处理器uboot架构分析

=================================

.macro    switch_el, xreg, el3_label, el2_label, el1_label
    mrs    \xreg, CurrentEL
    cmp    \xreg, 0xc
    b.eq    \el3_label
    cmp    \xreg, 0x8
    b.eq    \el2_label
    cmp    \xreg, 0x4
    b.eq    \el1_label
.endm
=============================================
在检查完成异常等级后,调用lowlevel_init,对使能了宏CONFIG_GICV2或者CONFIG_GICV3情形下对中断GIC初始化
=================================
   
    bl    apply_core_errata

   
    bl    lowlevel_init
======================
之后跳转到arch/arm/lib/crt0.S开始(arm64是crt0_64.S),的_main执行,设置堆栈,建立c运行环境
========================
    bl    _main
============================
_main函数中会调用board_init_f_alloc_reserve为gd在初始堆栈处位置预留空间
-----------------------------------------------
ulong board_init_f_alloc_reserve(ulong top)
{
    
#if CONFIG_VAL(SYS_MALLOC_F_LEN)
    top -= CONFIG_VAL(SYS_MALLOC_F_LEN);
#endif
    
    top = rounddown(top-sizeof(struct global_data), 16);

    return top;
}
----------------------------------------------------------
最后调用c函数board_init_f进入u-boot主体
======================
    bl    board_init_f
========================
void board_init_f(ulong boot_flags)
{
    gd->flags = boot_flags;
    gd->have_console = 0;

    if (initcall_run_list(init_sequence_f))
        hang();

#if !defined(CONFIG_ARM) && !defined(CONFIG_SANDBOX) && \
        !defined(CONFIG_EFI_APP) && !CONFIG_IS_ENABLED(X86_64) && \
        !defined(CONFIG_ARC)
    
    hang();
#endif
}
=================================================
上面的gd定义,在头文件global_data.h中,定义宏DECLARE_GLOBAL_DATA_PTR       
========================================
#ifdef CONFIG_ARM64
#define DECLARE_GLOBAL_DATA_PTR        register volatile gd_t *gd asm ("x18")
#else
#define DECLARE_GLOBAL_DATA_PTR        register volatile gd_t *gd asm ("r9")
#endif
==========================================
而在board_f.c中,申明了DECLARE_GLOBAL_DATA_PTR,即gd初始化为新x18的内容,
-------------------------------------------
#ifdef XTRN_DECLARE_GLOBAL_DATA_PTR
#undef    XTRN_DECLARE_GLOBAL_DATA_PTR
#define XTRN_DECLARE_GLOBAL_DATA_PTR    
DECLARE_GLOBAL_DATA_PTR = (gd_t *)(CONFIG_SYS_INIT_GD_ADDR);
#else
DECLARE_GLOBAL_DATA_PTR;
#endif
------------------------------------------------------
这样board_init_f中的gd的定义就找到了,函数在gd初始化后,运行initcall_run_list()函数,对init_sequence_f 中的函数表调用运行。

init_sequence_f函数表中,fdtdec_setup函数初始化fdt表的地址到gd->fdt_blob  = board_fdt_blob_setup();
fdt标的地址在arm结构中ATAG/FDT地址存放于r2/x0寄存器。

u-boot驱动列表构建和初始化扫描
    u-boot驱动结构定义为一个宏,U_BOOT_DRIVER,相关定义为:
====================================

#define U_BOOT_DRIVER(__name)                        \
    ll_entry_declare(struct driver, __name, driver) 

#define ll_entry_declare(_type, _name, _list)                \
    _type _u_boot_list_2_##_list##_2_##_name __aligned(4)        \
            __attribute__((unused,                \
            section(".u_boot_list_2_"#_list"_2_"#_name)))
完全展开:
=====================================
#define U_BOOT_DRIVER(__name)           \

struct driver  _u_boot_list_2_driver_2_##_name  __aligned(4) \
            __attribute__((unused,                \
            section(".u_boot_list_2_"#_list"_2_"#_name)))

 一个以太网驱动为例:U_BOOT_DRIVER(eth_bcmgenet)展开:
U_BOOT_DRIVER(eth_bcmgenet) = {
    .name   = "eth_bcmgenet",
    .id     = UCLASS_ETH,
    .of_match = bcmgenet_eth_ids,
    .of_to_plat = bcmgenet_eth_of_to_plat,
    .probe  = bcmgenet_eth_probe,
    .ops    = &bcmgenet_gmac_eth_ops,
    .priv_auto    = sizeof(struct bcmgenet_eth_priv),
    .plat_auto    = sizeof(struct eth_pdata),
    .flags = DM_FLAG_ALLOC_PRIV_DMA,
};
得到:
struct driver _u_boot_list_2_driver_2_eth_bcmgenet __aligned(4) __attribute__((unused,   
                     section(".u_boot_list_2_driver_2_eth_bcmgenet"))) = {
    .name   = "eth_bcmgenet",
    .id     = UCLASS_ETH,
    .of_match = bcmgenet_eth_ids,
    .of_to_plat = bcmgenet_eth_of_to_plat,
    .probe  = bcmgenet_eth_probe,
    .ops    = &bcmgenet_gmac_eth_ops,
    .priv_auto    = sizeof(struct bcmgenet_eth_priv),
    .plat_auto    = sizeof(struct eth_pdata),
    .flags = DM_FLAG_ALLOC_PRIV_DMA,
};
===================================
在u-boot.lds链接脚本文件中,有规则:
====================================
    . = ALIGN(8);
    .u_boot_list : {
        KEEP(*(SORT(.u_boot_list*)));
    }
========================================
前缀为.u_boot_list的段都会放在.u_boot_list区域中,在文件driver/core/root.c,定义了驱动根节点信息
--------------------------------------------------------------
static struct driver_info root_info = {
    .name        = "root_driver",
};

U_BOOT_DRIVER(root_driver) = {
    .name    = "root_driver",
    .id    = UCLASS_ROOT,
    ACPI_OPS_PTR(&root_acpi_ops)
};

UCLASS_DRIVER(root) = {
    .name    = "root",
    .id    = UCLASS_ROOT,
};
-------------------------------------------------------
在uboot的init_sequence_f初始化函数列表中,函数initf_dm用以扫描dtb文件,创建设备列表和驱动列表,关联设备和驱动。
----------------------------------------------
initf_dm
   ->dm_init_and_scan(bool pre_reloc_only) //  pre_reloc_only = true, 在relocate之前调用
      ->dm_init(CONFIG_IS_ENABLED(OF_LIVE)) //  TRUE,  设置系统设备根节点
        ->device_bind_by_name(NULL, false, &root_info, gd->dm_root); //bind 根设备到gd->dm_root
        -> device_probe(DM_ROOT_NON_CONST);
      ->dm_scan(pre_reloc_only);      // 扫描fdt表, 匹配device和driver
------------------------------------------------
dm_scan(bool pre_reloc_only)
  ->dm_scan_plat(pre_reloc_only);
     -> lists_bind_drivers(DM_ROOT_NON_CONST, pre_reloc_only);    // 绑定driver和device
  ->dm_extended_scan(pre_reloc_only);
     ->dm_scan_fdt(pre_reloc_only);
        ->dm_scan_fdt_node(gd->dm_root, ofnode_root(), pre_reloc_only); //扫描设备树,绑定驱动
           ->lists_bind_fdt(parent, node, NULL, pre_reloc_only);
     ->dm_scan_fdt_ofnode_path(nodes[i], pre_reloc_only);
  ->dm_scan_other(pre_reloc_only);
----------------------------------------------
在设备驱动模型初始化后,uboot继续初始化其他部分,包括串口初始化
==========================
    init_baud_rate,       
    serial_init,       
    console_init_f,       
===========================
以及内存初始化,获得内存大小,如raspberry,通过mailbox系统从GPU获得内存大小
=======================
    dram_init,       
int dram_init(void)
{
    ALLOC_CACHE_ALIGN_BUFFER(struct msg_get_arm_mem, msg, 1);
    int ret;

    BCM2835_MBOX_INIT_HDR(msg);
    BCM2835_MBOX_INIT_TAG(&msg->get_arm_mem, GET_ARM_MEMORY);

    ret = bcm2835_mbox_call_prop(BCM2835_MBOX_PROP_CHAN, &msg->hdr);
    if (ret) {
        printf("bcm2835: Could not query ARM memory size\n");
        return -1;
    }

    gd->ram_size = msg->get_arm_mem.body.resp.mem_size;

    
    gd->ram_size &= ~MMU_SECTION_SIZE;

    return 0;
}
===========================================
在内存初始化后,可以对内存进行测试
#if defined(CONFIG_SYS_DRAM_TEST)
    testdram,
#endif
在内存系统工作后,就可以进行uboot代码迁移,从rom复制到ram运行
调用setup_dest_addr设置uboot的各种内存基址,预留各种内存等,如图所示
arm处理器uboot架构分析
这之后,对ftd等进行迁移
    reloc_fdt,
    reloc_bootstage,
    reloc_bloblist,
    setup_reloc,           这里设置uboot relocat的一些参数

到这里init_sequence_f结束,返回汇编程序
之后进行uboot迁移,(crt0_64.S
-----------------------------------
#if !defined(CONFIG_SPL_BUILD)

    ldr    x0, [x18, #GD_START_ADDR_SP]   
    bic    sp, x0, #0xf   
    ldr    x18, [x18, #GD_NEW_GD]       

    adr    lr, relocation_return
#if CONFIG_POSITION_INDEPENDENT
    
    adrp    x0, _start       
    add    x0, x0, #:lo12:_start
    ldr    x9, _TEXT_BASE       
    sub    x9, x9, x0       
    add    lr, lr, x9
#endif
    
    ldr    x9, [x18, #GD_RELOC_OFF]   
    add    lr, lr, x9   
    ldr    x0, [x18, #GD_RELOCADDR]   
       relocate_code

relocation_return:
-------------------------------------
以relocation的目的地址(gd->relocaddr)为参数,调用relocate_code执行实际的relocation动作,就是将u-boot的代码段、data段、bss段等数据,拷贝到新的位置(gd->relocaddr)
relocate_code 定义在relocate_64.S中
在完成复制后,函数返回的地址就是新的ram中的uboot代码地址,建立运行时环境等,进行复制后的板级初始化,不再返回到汇编代码。
   
    mov    x0, x18               
    ldr    x1, [x18, #GD_RELOCADDR]   
       board_init_r           
===============================
relocate完成之后,真正的C运行环境才算建立了起来,接下来会执行“后置的板级初始化操作
board_init_r 和board_init_f一样运行一个函数数组 中的函数,
其中包括,网络初始化
=============================
#ifdef CONFIG_CMD_NET
    INIT_FUNC_WATCHDOG_RESET
    initr_net,
#endif
===================
initr_net->
   eth_initialize->
      eth_common_init->
          miiphy_init->      // 初始化全局变量mii_devs设备列表和current_mii当前设备
          phy_init->          // phy_drivers作为phy设备链表的头节点,进行各种phy的初始化  
   #ifdef CONFIG_PHY_BROADCOM
               phy_broadcom_init();
#endif
               genphy_init();  //调用phy_register注册通用phy
=============================
int genphy_init(void)
{
    return phy_register(&genphy_driver);      // 添加phy驱动到phy_drivers列表
}
=================================
    run_main_loop,
static int run_main_loop(void)
{
#ifdef CONFIG_SANDBOX
    sandbox_main_loop_init();
#endif
    
    for (;;)
        main_loop();
    return 0;
}
最后调用run_main_loop进入主循环,初始化结束









 

0

阅读 收藏 喜欢 打印举报/Report
  

新浪BLOG意见反馈留言板 欢迎批评指正

新浪简介 | About Sina | 广告服务 | 联系我们 | 招聘信息 | 网站律师 | SINA English | 产品答疑

新浪公司 版权所有