Linux编程入门--正点原子Linux驱动开发指南学习2021W27

Posted cheney

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Linux编程入门--正点原子Linux驱动开发指南学习2021W27相关的知识,希望对你有一定的参考价值。

(3)U-Boot 启动流程详解

  1. lowlevel_init 函数详解

函数 lowlevel_init 在文件 arch/arm/cpu/armv7/lowlevel_init.S 中定义

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

ENTRY(lowlevel_init)
    /*
     * Setup a temporary stack. Global data is not available yet.
     */
    ldr    sp, =CONFIG_SYS_INIT_SP_ADDR       /* 宏定义在include/configs/mx6ullevk.h sp 指向 0X91FF00,这属于 IMX6UL/IMX6ULL 的内部 ram*/
    bic    sp, sp, #7 /* 8-byte alignment for ABI compliance */
#ifdef CONFIG_SPL_DM
    mov    r9, #0
#else
    /*
     * Set up global data for boards that still need it. This will be
     * removed soon.
     */
#ifdef CONFIG_SPL_BUILD
    ldr    r9, =gdata
#else
    sub    sp, sp, #GD_SIZE          /* GD_SIZE  = 248 ,定义在 generic-asm-offsets.h*/
    bic    sp, sp, #7    /*八字节对齐*/
    mov    r9, sp         /*SP = 0X0091FF00-248=0X0091FE08*/
#endif
#endif
    /*
     * Save the old lr(passed in ip) and the current lr to stack
     */
    push    {ip, lr}

    /*
     * Call the very early init function. This should do only the
     * absolute bare minimum to get started. It should not:
     *
     * - set up DRAM
     * - use global_data
     * - clear BSS
     * - try to start a console
     *
     * For boards with SPL this should be empty since SPL can do all of
     * this init in the SPL board_init_f() function which is called
     * immediately after this.
     */
    bl    s_init     /*跳转到 s_init ,完成初始化*/
    pop    {ip, pc}   /*弹出堆栈,并将lr的值返回给PC*/
ENDPROC(lowlevel_init)
  1. s_init 函数详解

上一节的初始化跳转到 s_init ,该函数在定义在 arch/arm/cpu/armv7/mx6/soc.c 的808行。

void s_init(void)
{
    struct anatop_regs *anatop = (struct anatop_regs *)ANATOP_BASE_ADDR;
    struct mxc_ccm_reg *ccm = (struct mxc_ccm_reg *)CCM_BASE_ADDR;
    u32 mask480;
    u32 mask528;
    u32 reg, periph1, periph2;

    if (is_cpu_type(MXC_CPU_MX6SX) || is_cpu_type(MXC_CPU_MX6UL) ||
        is_cpu_type(MXC_CPU_MX6ULL) || is_cpu_type(MXC_CPU_MX6SLL))
        return;        //判断到CPU类型是 MXC_CPU_MX6ULL ,返回
        
    /*省略。。。*/
}

函数返回到 cpu_init_crit ,然后又返回到 save_boot_params_ret, 继续执行 _main 函数。

  1. _main 函数详解

_main 函数在文件 arch/arm/lib/crt0.S 中

/*
 * entry point of crt0 sequence
 */

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 = 0X0091FF00
#endif
#if defined(CONFIG_CPU_V7M)    /* 未定义 v7M forbids using SP as BIC destination */
    mov    r3, sp
    bic    r3, r3, #7
    mov    sp, r3
#else
    bic    sp, sp, #7    /* 8-byte alignment for ABI compliance */
#endif
    mov    r0, sp
    bl    board_init_f_alloc_reserve  /*此函数有一个参数,参数为 r0 中的值, 留出早期的 malloc 内存区域和 gd 内存区域,该函数返回 top=0X0091FA00。该函数定义在 common/init/board_init.c*/
    mov    sp, r0   /*将 board_init_f_alloc_reserve 返回值 0X0091FA00 放入堆栈指针*/
    /* set up gd here, outside any C code */
    mov    r9, r0  /* r9 寄存器存放着全局变量 gd 的地址*/
    bl    board_init_f_init_reserve  /*此函数用于初始化 gd, 同时设置 gd->malloc_base=0X0091FB00,这个也就是 early malloc 的起始地址。 该函数定义在 common/init/board_init.c -68*/

    mov    r0, #0
    bl    board_init_f /*初始化 DDR,定时器,完成代码拷贝等等, 定义在common/board_f.c -1037*/

#if ! defined(CONFIG_SPL_BUILD)

/*
 * Set up intermediate environment (new sp and gd) and call
 * relocate_code(addr_moni). Trick here is that we\'ll return
 * \'here\' but relocated.
 */
/*
重新设置环境(sp 和 gd)、获取 gd->start_addr_sp 的值赋给 sp,在函数 board_init_f
中会初始化 gd 的所有成员变量,其中 gd->start_addr_sp=0X9EF44E90, 所以这里相当于设置 sp=gd->start_addr_sp=0X9EF44E90。 0X9EF44E90 是 DDR 中的地址,说明新的 sp 和 gd 将会存放到 DDR 中,而不是内部的 RAM 了。 GD_START_ADDR_SP=64,
*/
    ldr    sp, [r9, #GD_START_ADDR_SP]    /* sp = gd->start_addr_sp  */
#if defined(CONFIG_CPU_V7M)    /* 未定义 v7M forbids using SP as BIC destination */
    mov    r3, sp
    bic    r3, r3, #7
    mov    sp, r3
#else
    bic    sp, sp, #7    /* 8-byte alignment for ABI compliance */
#endif
    ldr    r9, [r9, #GD_BD]        /* r9 = gd->bd  GD_BD=0,计算出新的 gd 的位置,并赋给 r9*/
    sub    r9, r9, #GD_SIZE        /* new GD is below bd 新的 gd 在 bd 下面,所以 r9 减去 gd 的大小就是新的 gd 的位置,获取到新的 gd 的位置以后赋值给 r9。*/

    adr    lr, here  /*设置 lr 寄存器为 here,这样后面执行其他函数返回的时候就返回到了第 122 行的 here 位置处*/
    ldr    r0, [r9, #GD_RELOC_OFF]        /* r0 = gd->reloc_off  GD_RELOC_OFF=68*/
    
/*lr 寄存器的值加上 r0 寄存器的值,重新赋值给 lr 寄存器。因为接下来要重定位代码,
也就是把代码拷贝到新的地方去(现在的 uboot 存放的起始地址为 0X87800000,下面要
将 uboot 拷贝到 DDR 最后面的地址空间出,将 0X87800000 开始的内存空出来),其中就包括
here,因此 lr 中的 here 要使用重定位后的位置。*/
    add    lr, lr, r0
#if defined(CONFIG_CPU_V7M)
    orr    lr, #1                /* As required by Thumb-only */
#endif
    ldr    r0, [r9, #GD_RELOCADDR]        /* r0 = gd->relocaddr  读取 gd->relocaddr 的值赋给 r0 寄存器,此时 r0 寄存器就保存着 uboot 要拷贝的目的地址,为 0X9FF47000。 GD_RELOCADDR=48*/
    b    relocate_code  /*此函数负责将 uboot 拷贝到新的地方去,此函数定义在文件 arch/arm/lib/relocate.S -67*/
here:
/*
 * now relocate vectors
 */

    bl    relocate_vectors /*对中断向量表做重定位, arch/arm/lib/relocate.S 
 -16*/

/* Set up final (full) environment */

    bl    c_runtime_cpu_setup    /* we still call old routine here arch/arm/cpu/armv7/start.S -77*/
#endif
#if !defined(CONFIG_SPL_BUILD) || defined(CONFIG_SPL_FRAMEWORK)
# ifdef CONFIG_SPL_BUILD
    /* Use a DRAM stack for the rest of SPL, if requested */
    bl    spl_relocate_stack_gd
    cmp    r0, #0
    movne    sp, r0
    movne    r9, r0
# endif
    ldr    r0, =__bss_start    /* this is auto-relocated! */

#ifdef CONFIG_USE_ARCH_MEMSET
    ldr    r3, =__bss_end        /* this is auto-relocated! */
    mov    r1, #0x00000000        /* prepare zero to clear BSS */

    subs    r2, r3, r0        /* r2 = memset len */
    bl    memset
#else
    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 */
#if defined(CONFIG_CPU_V7M)
    itt    lo
#endif
    strlo    r2, [r0]        /* clear 32-bit BSS word */
    addlo    r0, r0, #4        /* move to next */
    blo    clbss_l
#endif                                    /*147-159,清除BSS段*/

#if ! defined(CONFIG_SPL_BUILD)
    bl coloured_LED_init
    bl red_led_on
#endif
    /* call board_init_r(gd_t *id, ulong dest_addr) */
    mov     r0, r9                  /* gd_t 设置 board_init_r 第一个参数*/
    ldr    r1, [r9, #GD_RELOCADDR]    /* dest_addr 设置 board_init_r 第二个参数*/
    /* call board_init_r */
#if defined(CONFIG_SYS_THUMB_BUILD)
    ldr    lr, =board_init_r    /* this is auto-relocated! */
    bx    lr
#else
    ldr    pc, =board_init_r    /* this is auto-relocated!  common/board_r.c -997*/
#endif
    /* we should not return here. */
#endif

ENDPROC(_main)
  1. board_init_f 函数详解

board_init_f 函数主要有两个工作:
①、初始化一系列外设,比如串口、定时器,或者打印一些消息等。
②、初始化 gd 的各个成员变量, uboot 会将自己重定位到 DRAM 最后面的地址区域,也就是将自己拷贝到 DRAM 最后面的内存区域中。这么做的目的是给 Linux 腾出空间,防止 Linux kernel 覆盖掉 uboot,将 DRAM 前面的区域完整的空出来。在拷贝之前肯定要给 uboot 各部分分配好内存位置和大小,比如 gd 应该存放到哪个位置, malloc 内存池应该存放到哪个位置等等。这些信息都保存在 gd 的成员变量中,因此要对 gd 的这些成员变量做初始化。最终形成一个完整的内存“分配图”,在后面重定位 uboot 的时候就会用到这个内存“分配图”。

void board_init_f(ulong boot_flags)
{
#ifdef CONFIG_SYS_GENERIC_GLOBAL_DATA                 /*未定义*/
    /*
     * For some archtectures, global data is initialized and used before
     * calling this function. The data should be preserved. For others,
     * CONFIG_SYS_GENERIC_GLOBAL_DATA should be defined and use the stack
     * here to host global data until relocation.
     */
    gd_t data;

    gd = &data;

    /*
     * Clear global data before it is accessed at debug print
     * in initcall_run_list. Otherwise the debug print probably
     * get the wrong vaule of gd->have_console.
     */
    zero_global_data();
#endif

    gd->flags = boot_flags;
    gd->have_console = 0;
    
/*initcall_run_list() 函数负责依次调用 init_sequence_f 中的初始化函数,这个函数我只能说是略微理解,要让我写,我是写不出的*/
    if (initcall_run_list(init_sequence_f))          
        hang();

#if !defined(CONFIG_ARM) && !defined(CONFIG_SANDBOX) && \\
        !defined(CONFIG_EFI_APP)
    /* NOTREACHED - jump_to_copy() does not return */
    hang();
#endif

    /* Light up LED1 */
    imx6_light_up_led1();
}

用于初始化调用函数的原型如下,

int initcall_run_list(const init_fnc_t init_sequence[])
{
    const init_fnc_t *init_fnc_ptr;

    for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) {
        unsigned long reloc_ofs = 0;
        int ret;

        if (gd->flags & GD_FLG_RELOC)
            reloc_ofs = gd->reloc_off;
#ifdef CONFIG_EFI_APP
        reloc_ofs = (unsigned long)image_base;
#endif
        debug("initcall: %p", (char *)*init_fnc_ptr - reloc_ofs);
        if (gd->flags & GD_FLG_RELOC)
            debug(" (relocated to %p)\\n", (char *)*init_fnc_ptr);
        else
            debug("\\n");
        ret = (*init_fnc_ptr)();
        if (ret) {
            printf("initcall sequence %p failed at call %p (err=%d)\\n",
                   init_sequence,
                   (char *)*init_fnc_ptr - reloc_ofs, ret);
            return -1;
        }
    }
    return 0;
}

被调用的函数列表中有太多行,很多宏定义决定了某些函数是否调用,把这个去除后的函数如下:

static init_fnc_t init_sequence_f[] = {
    setup_mon_len,             /*设置 gd 的 mon_len 成员变量*/
    initf_malloc,              /*初始化malloc内存池大小和malloc指针*/
    initf_console_record,      /*未定义,返回0*/
    arch_cpu_init,           /* basic arch cpu dependent setup */
    initf_dm,                  /*驱动模型的一些初始化, 未定义*/
    arch_cpu_init_dm,          /*未定义*/
    mark_bootstage,           /* need timer, go after init dm 做标记board_init_f*/
    board_early_init_f,        /*I.MX6ULL 用来初始化串口的 IO 配置*/
    timer_init,           /* initialize timer */
    board_postclk_init,        /* I.MX6ULL to Set VDDSOC to 1.175V*/
    get_clocks, 
    env_init,          /* initialize environment */
    init_baud_rate,        /* initialze baudrate settings */
    serial_init,        /* serial communications setup */
    console_init_f,        /* stage 1 init of console 初始控制台,设置仅一个控制台*/
    display_options,    /* say that we are here 输出版本信息 UBoot日期和时间*/
    display_text_info,    /* show debugging info if required 若开启debug, 会输出 text_base、 bss_start、 bss_end*/
    print_cpuinfo,        /* display cpu info (and speed) */
    show_board_info,
    INIT_FUNC_WATCHDOG_INIT     /*空函数*/
    INIT_FUNC_WATCHDOG_RESET    /*空函数*/
    init_func_i2c,
    announce_dram_init,
    dram_init,        /* configure available RAM banks 设置 gd->ram_size */
    post_init_f,             /*上电复位自检*/
    testdram,                /*空函数*/
    setup_dest_addr,         /*设置目的地址,设置 gd->ram_size,gd->ram_top,gd->relocaddr这三个的值*/
    reserve_round_4k,        /*reserve_round_4k 函 数 用 于 对 gd->relocaddr 做 4KB 对 齐 , 因 为gd->relocaddr=0XA0000000,已经是 4K 对齐了,所以调整后不变。*/
    reserve_mmu,            /*留出 MMU 的 TLB 表的位置,分配 MMU 的 TLB 表内存以后会对 gd->relocaddr 做 64K 字节对齐*/
    reserve_trace,          /*留出跟踪调试的内存, I.MX6ULL 没有用到*/ 
    reserve_uboot,          /*留出重定位后的 uboot 所占用的内存区域, uboot 所占用大小由gd->mon_len 所指定,留出 uboot 的空间以后还要对 gd->relocaddr 做 4K 字节对齐,并且重新设置 gd->start_addr_sp*/
    reserve_malloc,
    reserve_board,
    setup_machine,         /*老版本使用,新设备没有用*/
    reserve_global_data,   /*保留出 gd_t 的内存区域*/
    reserve_fdt,           /*留出设备树相关的内存区域*/
    reserve_arch,          /*空函数*/
    reserve_stacks,        /*留出栈空间,16字节对齐*/
    setup_dram_config,     /*设置DRAM,告诉linux DRAM 的起始地址和大小*/
    show_dram_config,      /*显示DRAM信息*/
    display_new_sp,        /*开启debug后,显示 gd->start_addr_sp*/
    reloc_fdt,             /*重定位 fdt,没有用到*/
    setup_reloc,           /*设置 gd 的其他一些成员变量,供后面重定位的时候使用,并且将以前的 gd 拷贝到 gd->new_gd 处。*/
}

以上是关于Linux编程入门--正点原子Linux驱动开发指南学习2021W27的主要内容,如果未能解决你的问题,请参考以下文章

Linux编程入门--正点原子Linux驱动开发指南学习2021W23

正点原子I.MX6U-MINI驱动篇4Linux设备树详解

正点原子I.MX6U-MINI驱动篇1字符设备驱动开发-Hello驱动(不涉及硬件操作)

正点原子Linux阿尔法开发板4.3 寸多点电容触摸屏测试问题

正点原子Linux阿尔法开发板4.3 寸多点电容触摸屏测试问题

正点原子I.MX6U-MINI驱动篇2嵌入式 Linux驱动开发之点灯大法