关于linux编程:Linux编程入门正点原子Linux驱动开发指南学习2021W26

22次阅读

共计 6464 个字符,预计需要花费 17 分钟才能阅读完成。

十一、U-Boot 启动流程详解

(1)链接脚本 u-boot.lds 详解

在编译实现当前就会在 uboot 根目录下生成 u-boot.lds 文件,从该文件来剖析 U-boot 启动流程。

/*
1. 第 3 行为代码以后入口点:_start,_start 在文件 arch/arm/lib/vectors.S 中有定义
2. __image_copy_start 这个变量是在 u-boot-spl.lds 文件里定义的,是这个链接文件的 .text 段
的开始地址
*/
OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm")
OUTPUT_ARCH(arm)

ENTRY(_start)
SECTIONS
{
 . = 0x00000000;
 . = ALIGN(4);
 .text :
 {*(.__image_copy_start)
  *(.vectors)
  arch/arm/cpu/armv7/start.o (.text*)
  *(.text*)
 }
 . = ALIGN(4);
 .rodata : {*(SORT_BY_ALIGNMENT(SORT_BY_NAME(.rodata*))) }
 . = ALIGN(4);
 .data : {*(.data*)
 }
 . = ALIGN(4);
 . = .;
 . = ALIGN(4);
 .u_boot_list : {KEEP(*(SORT(.u_boot_list*)));
 }
 . = ALIGN(4);
 .image_copy_end :
 {*(.__image_copy_end)
 }
 .rel_dyn_start :
 {*(.__rel_dyn_start)
 }
 .rel.dyn : {*(.rel*)
 }
 .rel_dyn_end :
 {*(.__rel_dyn_end)
 }
 .end :
 {*(.__end)
 }
 _image_binary_end = .;
 . = ALIGN(4096);
 .mmutable : {*(.mmutable)
 }
 .bss_start __rel_dyn_start (OVERLAY) : {KEEP(*(.__bss_start));
  __bss_base = .;
 }
 .bss __bss_base (OVERLAY) : {*(.bss*)
   . = ALIGN(4);
   __bss_limit = .;
 }
 .bss_end __bss_limit (OVERLAY) : {KEEP(*(.__bss_end));
 }
 .dynsym _image_binary_end : {*(.dynsym) }
 .dynbss : {*(.dynbss) }
 .dynstr : {*(.dynstr*) }
 .dynamic : {*(.dynamic*) }
 .plt : {*(.plt*) }
 .interp : {*(.interp*) }
 .gnu.hash : {*(.gnu.hash) }
 .gnu : {*(.gnu*) }
 .ARM.exidx : {*(.ARM.exidx*) }
 .gnu.linkonce.armexidx : {*(.gnu.linkonce.armexidx.*) }
}

在文件 arch/arm/lib/vectors.S 中,能够看到定义了独自的段.section ".vectors", "ax"。_start 前面就是中断向量表。

#include <config.h>

/*
 *************************************************************************
 *
 * Symbol _start is referenced elsewhere, so make it global
 *
 *************************************************************************
 */

.globl _start

/*
 *************************************************************************
 *
 * Vectors have their own section so linker script can map them easily
 *
 *************************************************************************
 */

    .section ".vectors", "ax"

/*
 *************************************************************************
 *
 * Exception vectors as described in ARM reference manuals
 *
 * Uses indirect branch to allow reaching handlers anywhere in memory.
 *
 *************************************************************************
 */

_start:

#ifdef CONFIG_SYS_DV_NOR_BOOT_CFG
    .word    CONFIG_SYS_DV_NOR_BOOT_CFG
#endif

    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

(2)Cortex-A7 中断零碎详解

  1. GIC 控制器总览

这里再补充一些 Cortex-A7 中断的常识,不然前面的剖析有点艰难。
Cortex-A7 也有中断向量表,中断向量表也是在代码的最后面。CortexA7 内核有 8 个异常中断,这 8 个异常中断的中断向量表如下表所示:

向量地址 终端类型 中断模式 介绍
0x00 复为中断(RSET) 特权模式(SVC) CPU 复位当前就会进入复位中断,咱们能够在复位中断服务函数外面做一些初始化工作,比方初始化 SP 指针、DDR 等等。
0x04 未定义指令中断(Undefined Instruction) 未定义指令停止模式(Undef) 如果指令不能辨认的话就会产生此中断。
0x08 软中断(Software Interrupt,SWI) 特权模式(SVC) 由 SWI 指令引起的中断,Linux 的零碎调用会用 SWI 指令来引起软中断,通过软中断来陷入到内核空间。
0x0C 指令预取停止中断(Prefetch Abort) 停止模式 预取指令的出错的时候会产生此中断。
0x10 数据拜访停止中断(Data Abort) 停止模式 拜访数据出错的时候会产生此中断。
0x14 未应用(Not Used) 未应用 单元 4
0x18 IRQ 中断(IRQ Interrupt) 内部中断模式(IRQ) 芯片外部的外设中断都会引起此中断的产生。
0x1C FIQ 中断(FIQ Interrupt) 疾速中断模式(FIQ) 疾速解决中断的话就能够应用此中断。

GIC 是 ARM 公司给 Cortex-A/R 内核提供的一个中断控制器,相似 Cortex-M 内核中的 NVIC。目前 GIC 有 4 个版本:V1~V4,V1 是最老的版本,曾经被废除了。V2~V4 目前正在大量的应用。GIC V2 是给 ARMv7-A 架构应用的,I.MX6U 就应用的这个。ARM 针对 GIC V2 就开发出了 GIC400 这个中断控制器 IP 核。当 GIC 接管到内部中断信号当前就会报给 ARM 内核,然而 ARM 内核只提供了四个信号给 GIC 来汇报中断状况:VFIQ(虚构疾速中断)、VIRQ(虚构内部中断)、FIQ 和 IRQ。

  1. 中断 ID

GIC 将泛滥的中断源分为分为三类:

① SPI(Shared Peripheral Interrupt), 共享中断,顾名思义,所有 Core 共享的中断,这个是最常见的,那些内部中断都属于 SPI 中断(留神!不是 SPI 总线那个中断)。比方按键中断、串口中断等等,这些中断所有的 Core 都能够解决,不限定特定 Core。② PPI(Private Peripheral Interrupt),公有中断,GIC 是反对多核的,每个核必定有本人独有的中断。这些独有的中断必定是要指定的外围解决,因而这些中断就叫做公有中断。③ SGI(Software-generated Interrupt),软件中断,由软件触发引起的中断,通过向寄存器 GICD_SGIR 写入数据来触发,零碎会应用 SGI 中断来实现多核之间的通信。

中断源有很多,为了辨别这些不同的中断源必定要给他们调配一个惟一 ID,这些 ID 就是
中断 ID。每一个 CPU 最多反对 1020 个中断 ID,中断 ID 号为 ID0~ID1019。ID0~ID15:这 16 个 ID 调配给 SGI。ID16~ID31:这 16 个 ID 调配给 PPI。ID32~ID1019:这 988 个 ID 调配给 SPI,像 GPIO 中断、串口中断等这些内部中断,至于具体到某个 ID 对应哪个中断那就由半导体厂商依据理论状况去定义了。

  1. GIC 逻辑分块

GIC 架构分为了两个逻辑块:Distributor 和 CPU Interface,也就是散发器端和 CPU 接口端。
Distributor(散发器端):此逻辑块负责解决各个中断事件的散发问题,也就是中断事件应该发送到哪个 CPU Interface 下来。散发器收集所有的中断源,能够管制每个中断的优先级,它总是将优先级最高的中断事件发送到 CPU 接口端。散发器端要做的次要工作如下:
①、全局中断使能管制。
②、管制每一个中断的使能或者敞开。
③、设置每个中断的优先级。
④、设置每个中断的指标处理器列表。
⑤、设置每个内部中断的触发模式:电平触发或边际触发。
⑥、设置每个中断属于组 0 还是组 1。
CPU Interface(CPU 接口端):CPU 接口端听名字就晓得是和 CPU Core 相连接的,每个 CPU Core 都能够在 GIC 中找到一个与之对应的 CPU Interface。CPU 接口端就是散发器和 CPU Core 之间的桥梁,CPU 接口端次要工作如下:
①、使能或者敞开发送到 CPU Core 的中断请求信号。
②、应答中断。
③、告诉中断解决实现。
④、设置优先级掩码,通过掩码来设置哪些中断不须要上报给 CPU Core。
⑤、定义抢占策略。
⑥、当多个中断到来的时候,抉择优先级最高的中断告诉给 CPU Core。

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

  1. reset 函数源码详解

后面提到的 arch/arm/lib/vectors.S 中断向量表外面的 reset,reset 函数在 arch/arm/cpu/armv7/start.S 外面。
在 35 行看到 reset 的定义,在这里又跳转到 save_boot_params。

    .globl    reset
    .globl    save_boot_params_ret

reset:
    /* Allow the board to save important registers */
    b    save_boot_params

在 100 行定义了 save_boot_params,这里又跳转到 save_boot_params_ret。

/*************************************************************************
 *
 * void save_boot_params(u32 r0, u32 r1, u32 r2, u32 r3)
 *    __attribute__((weak));
 *
 * Stack pointer is not yet initialized at this moment
 * Don't save anything to stack even if compiled with -O0
 *
 *************************************************************************/
ENTRY(save_boot_params)
    b    save_boot_params_ret        @ back to my caller

在 38 行定义了 save_boot_params_ret

save_boot_params_ret:
    /*
     * disable interrupts (FIQ and IRQ), also set the cpu to SVC32 mode,
     * except if in HYP mode already
     */
    mrs    r0, cpsr                @ 读取 cpsr 的值存到 r0
    and    r1, r0, #0x1f        @ mask mode bits
    teq    r1, #0x1a        @ test for HYP mode
    bicne    r0, r0, #0x1f        @ clear all mode bits  如果 r1 和 0X1A 不相等, 清空 r0
    orrne    r0, r0, #0x13        @ set SVC(Supervisor) mode 如果 r1 和 0X1A 不相等, 设置 r0 为 #0x13
    orr    r0, r0, #0xc0        @ disable FIQ and IRQ
    msr    cpsr,r0

持续往下,

/*
 * Setup vector:
 * (OMAP4 spl TEXT_BASE is not 32 byte aligned.
 * Continue to use ROM code vector only in OMAP4 spl)
 */
#if !(defined(CONFIG_OMAP44XX) && defined(CONFIG_SPL_BUILD))
    /* Set V=0 in CP15 SCTLR register - for VBAR to point to vector */
    mrc    p15, 0, r0, c1, c0, 0    @ Read CP15 SCTLR Register 读取 CP15 中 c1 寄存器的值到 r0 寄存器中
    bic    r0, #CR_V        @ V = 0  CR_V = (1 << 13),革除 SCTLR 寄存器 bit13
    mcr    p15, 0, r0, c1, c0, 0    @ Write CP15 SCTLR Register 将 r0 寄存器的值重写写入到寄存器 SCTLR 中

    /* Set vector address in CP15 VBAR register */
    ldr    r0, =_start       @ _start 就是整个 uboot 的入口地址,其值为 0X87800000
    mcr    p15, 0, r0, c12, c0, 0    @Set VBAR        将 r0 寄存器的值 (向量表值) 写入到 CP15 的 c12 寄存器中,也就是 VBAR 寄存器。#endif

再持续往下,

    /* the mask ROM code should have PLL and others stable */
#ifndef CONFIG_SKIP_LOWLEVEL_INIT
    bl    cpu_init_cp15        // 该函数在 105 行,用来设置 CP15 相干的内容,比方敞开 MMU 之类的
    bl    cpu_init_crit       
#endif

    bl    _main

这里重点看 cpu_init_crit 函数,在 259 行,

#ifndef CONFIG_SKIP_LOWLEVEL_INIT
/*************************************************************************
 *
 * CPU_init_critical registers
 *
 * setup important registers
 * setup memory timing
 *
 *************************************************************************/
ENTRY(cpu_init_crit)
    /*
     * Jump to board specific initialization...
     * The Mask ROM will have already initialized
     * basic memory. Go here to bump up clock rate and handle
     * wake up conditions.
     */
    b    lowlevel_init        @ go setup pll,mux,memory
ENDPROC(cpu_init_crit)
#endif

能够看到 reset 函数最终跳转到 lowlevel_init 和 _main 这两个函数了。前面再剖析。

正文完
 0