Linux编程入门--正点原子Linux驱动开发指南学习2021W26
Posted cheney
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Linux编程入门--正点原子Linux驱动开发指南学习2021W26相关的知识,希望对你有一定的参考价值。
十一、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 中断系统详解
- 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。
- 中断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 对应哪个中断那就由半导体厂商根据实际情况去定义了。
- 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 启动流程详解
- 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 这两个函数了。后面再分析。
以上是关于Linux编程入门--正点原子Linux驱动开发指南学习2021W26的主要内容,如果未能解决你的问题,请参考以下文章
Linux编程入门--正点原子Linux驱动开发指南学习2021W23
正点原子I.MX6U-MINI驱动篇1字符设备驱动开发-Hello驱动(不涉及硬件操作)
正点原子Linux阿尔法开发板4.3 寸多点电容触摸屏测试问题