[TOC]
Kconfig
中断控制器
GIC (Generic Interrupt Controller) 通用中断控制器
- GIC 是 ARM 处理器的一种通用中断控制器,用于管理和分发中断信号。GIC 支持多种中断类型,包括外部中断、定时器中断、软件中断等。GIC 通过中断控制器和中断分配器组件实现中断的管理和分发。
GICv2
- GICV2是 ARM 提供的第二版通用中断控制器,广泛应用于 ARMv7 和部分 ARMv8 架构的处理器中。
- 中断管理:支持多达 1020 个中断源,包括外部中断和内部中断。提供中断优先级管理和中断屏蔽功能。
- 分布式架构:包括一个分配器(Distributor)和多个 CPU 接口(CPU Interface),分配器负责中断的分发,CPU 接口负责中断的处理。
- 中断路由:支持将中断路由到特定的 CPU 或多个 CPU,提供灵活的中断处理机制。
- 软件生成中断:支持软件生成中断(SGI),允许处理器之间通过中断进行通信。
GICv3
- GICV3 是 ARM 提供的第三版通用中断控制器,广泛应用于 ARMv8 架构的处理器中。
- 扩展的中断管理:支持多达 1020 个 SPI(共享外部中断)和 960 个 LPI(本地中断),提供更大的中断容量。
提供更细粒度的中断优先级管理和中断屏蔽功能。 - 增强的分布式架构:包括一个分配器(Distributor)、多个 Redistributor 和 CPU 接口,Redistributor 负责将中断分发到特定的 CPU 接口。
- 中断路由和重定向:支持更灵活的中断路由和重定向机制,允许中断在不同的 CPU 之间动态分配。
- 中断翻译服务(ITS):支持中断翻译服务(Interrupt Translation Service, ITS),用于管理和配置 LPI,提供更高效的中断处理。
- 电源管理:支持更高级的电源管理功能,允许在低功耗模式下更高效地管理中断。
NVIC (Nested Vectored Interrupt Controller) 嵌套向量中断控制器
- ARMv7-M 架构的处理器(如 Cortex-M 系列)使用的是嵌入式中断控制器(Nested Vectored Interrupt Controller, NVIC),而不是通用中断控制器(GIC)。NVIC 是专门为嵌入式系统设计的中断控制器,集成在 Cortex-M 处理器内核中,提供了高效的中断管理和处理能力。
- 嵌入式设计:NVIC 集成在 Cortex-M 处理器内核中,专为嵌入式系统设计,提供低延迟的中断响应。
- 中断优先级管理:NVIC 支持多级中断优先级管理,允许开发者为每个中断源设置优先级,从而实现灵活的中断处理。
- 向量表:NVIC 使用向量表来存储中断处理程序的入口地址。向量表通常位于内存的固定位置,处理器在接收到中断时会根据向量表跳转到相应的中断处理程序。
- 中断屏蔽和使能:NVIC 提供中断屏蔽和使能功能,允许开发者在需要时屏蔽或使能特定的中断源。
- 快速中断响应:NVIC 设计为低延迟的中断响应,适用于实时嵌入式系统。
Select the ARM data write cache policy 写入缓存策略
Write-back (WB) 写回
- 特点:
- 写操作:写操作只更新缓存,并将缓存行标记为脏(dirty)。
- 内存更新:外部内存只有在缓存行被驱逐(evicted)或显式清除(cleaned)时才会更新。
- 使用场景:
- 高性能计算:适用于需要高写性能的应用,因为写操作只更新缓存,减少了对外部内存的访问。
- 数据局部性强的应用:适用于数据局部性强的应用,缓存命中率高,减少了内存访问延迟。
- 优点:
- 高写性能:写操作只更新缓存,减少了对外部内存的访问,提高了写性能。
- 减少内存带宽占用:减少了对外部内存的写操作,降低了内存带宽的占用。
- 缺点:
- 数据一致性问题:需要额外的机制来确保缓存和内存之间的数据一致性,可能导致复杂的缓存管理。
- 延迟更新:外部内存的更新可能会延迟,导致数据一致性问题。
Write-through (WT) 写透
- 特点
- 写操作:写操作同时更新缓存和外部内存。
- 内存更新:每次写操作都会更新外部内存,不会将缓存行标记为脏。
- 使用场景
- 数据一致性要求高的应用:适用于需要确保缓存和内存之间数据一致性的应用,如实时系统和数据库。
- 读操作频繁的应用:适用于读操作频繁的应用,因为写操作不会影响读操作的性能。
- 优点
- 数据一致性:每次写操作都会更新外部内存,确保缓存和内存之间的数据一致性。
- 简单的缓存管理:不需要额外的机制来管理缓存和内存之间的数据一致性。
- 缺点
- 写性能较低:每次写操作都会更新外部内存,增加了写操作的延迟。
- 内存带宽占用高:每次写操作都会更新外部内存,增加了内存带宽的占用。。
WWrite allocation (WA) 写分配
- 特点
- 写缺失:在写缺失(write miss)时分配缓存行。
- 缓存行填充:在执行写操作之前,会进行突发读取(burst read)以获取缓存行的数据。
- 使用场景
- 写操作频繁的应用:适用于写操作频繁的应用,因为在写缺失时分配缓存行,提高了写操作的效率。
- 数据局部性较差的应用:适用于数据局部性较差的应用,因为在写缺失时分配缓存行,提高了缓存命中率。
- 优点
- 提高写操作效率:在写缺失时分配缓存行,提高了写操作的效率。
- 提高缓存命中率:在写缺失时分配缓存行,提高了缓存命中率。
- 缺点
- 突发读取开销:在写缺失时需要进行突发读取,增加了读取的开销。
- 复杂的缓存管理:需要额外的机制来管理缓存行的分配和填充。
instruction 指令
SYS_THUMB_BUILD
Thumb 指令集是 ARM 处理器的一种压缩指令集,旨在减少代码大小,同时保持较高的性能。在 Thumb 指令集中,许多常用指令被编码为 16 位(2 字节)宽,而不是标准 ARM 指令集中的 32 位(4 字节)宽。这使得 Thumb 指令集在内存受限的嵌入式系统中非常有用。
使用此标志可使用 Thumb 指令集构建 U-Boot ARM 架构。Thumb 指令集提供更好的代码密度。对于支持 Thumb2 的 ARM 体系结构,此标志将导致 GCC 生成的 Thumb2 代码。
USE_ARCH_MEMCPY MEMMOVE MEMSET 使用体系结构优化的内存操作函数
- 内存操作函数(如 memcpy、memmove、memset)是常用的 C 标准库函数,用于对内存进行复制、移动和设置操作。这些函数通常由编译器提供,但也可以通过使用体系结构优化的内存操作函数来提高性能。
Target select 选择目标芯片
ARCH_STM32
1 | config ARCH_STM32 |
SUPPORT_PASSING_ATAGS
- 支持使用 ATAG 而不是传递一个 devicetree。 这个选项很少使用,并且语义在https://www.kernel.org/doc/Documentation/arm/Booting 第 4a 节。
- ATAG(ARM Tag)是ARM架构中用于传递系统启动信息的一种数据结构。它通常在嵌入式系统中使用,特别是在系统启动时,ATAG会传递给操作系统内核,以便内核能够获取必要的启动参数和硬件信息。
- ATAG由一系列标签(tag)组成,每个标签包含一个标识符和相关的数据。常见的标签类型包括内存大小和位置、命令行参数、初始化RAM磁盘等。每个标签都有一个标准的格式,通常以一个标识符开始,后跟数据长度和具体的数据内容。
- 在系统启动时,启动加载程序(bootloader)会创建并填充ATAG数据结构,然后将其传递给内核。内核解析这些标签,获取系统启动所需的信息,从而正确地初始化系统。
- ATAG在嵌入式系统中非常重要,因为它提供了一种灵活且标准化的方式来传递启动信息,确保操作系统能够适应不同的硬件配置和启动参数。
lds链接脚本 定义入口函数
- 链接脚本定义了ENTRY(_start),即开始入口为_start
1 | OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm") |
include/asm
assembler.h
- 定义了一些宏,用ret开头,用于返回
1 | /* |
- .irp .endr 会展开生成循环中的代码,展开后如下:
1 | .macro ret, reg |
unified.h
- W(instr) 的定义展开为
instr.w
1 | #ifdef CONFIG_THUMB2_KERNEL //在include/generated/autoconf.h中定义 |
io.h
write
- 定义局部变量:首先,定义了一个局部变量 __v,类型为 u32(无符号 32 位整数),并将传入的值 v 赋值给 __v。这样做的目的是确保值 v 在后续操作中不会被修改。
- 内存屏障:调用 __iowmb() 函数,这是一个内存屏障函数,用于确保所有之前的内存写操作在执行后续的 I/O 操作之前完成。内存屏障在多核处理器和复杂的内存系统中非常重要,以确保内存操作的顺序性
- 写入寄存器:调用 __arch_putl(__v, c) 函数,将值 __v 写入到地址 c 所指向的寄存器中。__arch_putl 是一个架构相关的函数,用于执行实际的写操作。
在这段代码中,__iowmb() 使用的是 dmb(数据内存屏障)而不是 dsb(数据同步屏障),主要原因是 dmb 足以确保内存访问的顺序性,而不需要 dsb 提供的更严格的同步保证。
- 原因分析:
- 内存访问顺序: dmb 确保在其之前的所有内存访问操作在其之后的内存访问操作之前完成。这对于确保写操作的顺序性已经足够。例如,在写入寄存器之前,确保所有之前的内存写操作已经完成。
- 性能考虑: dsb 是一种更严格的屏障指令,不仅影响内存访问,还会影响所有类型的指令执行顺序。使用 dsb 会导致更大的性能开销,因为它会强制处理器等待所有之前的指令完成,并刷新所有的缓存和写缓冲区。对于大多数内存写操作来说,这种严格的同步保证通常是不必要的。
- 适用场景: 在大多数情况下,内存写操作只需要确保内存访问的顺序性,而不需要确保所有类型的指令执行顺序。因此,使用 dmb 可以提供足够的保证,同时避免 dsb 带来的性能开销。
DSB的使用场景
- 系统控制寄存器的修改
- 关键的同步操作,例如,在实现自旋锁或其他同步原语时。
- 缓存和写缓冲区的刷新
- 处理器模式切换(如从用户模式切换到内核模式)时,需要确保所有之前的指令和内存操作都已完成。这时需要使用 DSB。
1 | /* Generic virtual read/write. */ |
__iowmb 内存屏障
- DSB(数据同步屏障)和 DMB(数据内存屏障)都是用于控制内存访问顺序的屏障指令,但它们在具体的行为和应用场景上有所不同。
- DSB 是一种更严格的屏障指令,用于确保在其之前的所有内存访问操作(包括读和写)在其之后的内存访问操作之前完成。它不仅影响内存访问,还会影响所有类型的指令执行顺序。DSB 确保所有之前的指令都已完成,并且所有的缓存和写缓冲区都已刷新。
- DMB 是一种较为宽松的屏障指令,用于确保在其之前的所有内存访问操作在其之后的内存访问操作之前完成。与 DSB 不同,DMB 只影响内存访问的顺序,不影响其他类型的指令执行顺序。DMB 确保内存访问的顺序性,但不强制刷新缓存或写缓冲区。
1 |
|
lib
vectors_m.S 设置中断向量表
- 从这里开始执行,即_start,这段代码定义了中断向量表,并且定义了中断处理函数的入口
1 | .section .vectors |
- 反汇编显示,其中
.word 0x24040000
即为SYS_INIT_SP_ADDR
,即堆栈指针地址 - 非循环指令一共16条,循环指令255 - 16 = 239条;一共255条,每条占用4字节,共1020字节(0x3fc)
1 | *(.vectors) |
crt0.S 定义了_main函数
crt0 是 “C runtime zero” 的缩写,通常指的是 C 程序的启动代码。它是一个汇编语言文件,负责在操作系统加载程序后进行一些初始化工作,然后调用程序的 main 函数。crt0 是 C 运行时库的一部分,通常由编译器或链接器自动包含在最终生成的可执行文件中。
重新设置堆栈指针,并调用board_init_f_init_reserve初始化保留空间
ABI(应用二进制接口)是指应用程序与操作系统或其他程序之间的接口标准。ABI 规定了函数调用约定、系统调用约定、数据类型的大小和对齐方式等。遵循 ABI 标准可以确保不同编译器生成的代码能够相互兼容,并且能够正确地与操作系统或其他程序进行交互。
在 ARM 架构中,函数调用时第一个参数通常通过寄存器 r0 传递
1 | ENTRY(_main) |
relocate.S
- relocate_code 函数,用于重新定位代码
- 在 U-Boot 启动过程中,重定位是一个关键步骤,它将 U-Boot 从加载地址移动到运行地址,并修正所有相关的地址引用。
1 | ldr r0, [r9, #GD_RELOCADDR] /* r0 = gd->relocaddr */ |
interrupts_m.c
1 | /* |
setjmp.S 保存和恢复执行环境
1 | // 将接下来的代码放入 .text.setjmp 段,并设置段属性为可执行和可分配。 |
arch/arm/lib/memcpy.S
- 比C库中的memcpy函数更高效的memcpy函数,因为使用了ldmia和stmia指令,可以一次性加载多个寄存器的值,然后一次性存储多个寄存器的值,提高了内存拷贝的效率
- 且处理了不同的对齐情况,提高了内存拷贝的效率
1 |
|
arch/arm/lib/memset.S
- 同理
cpu
armv7m
start.S 定义了reset函数,跳转到_main
- 使用objdump反汇编如下
1 | arch/arm/cpu/armv7m/start.o(.text*) |
- .s包含了asm/assembler.h,该头文件包含了asm/unified.h;其中对W(instr)的定义展开为
instr.w
mov pc, lr
占用2字节,因为是thumb指令,Thumb 指令集是 ARM 处理器的一种压缩指令集,旨在减少代码大小,同时保持较高的性能。在 Thumb 指令集中,许多常用指令被编码为 16 位(2 字节)宽,而不是标准 ARM 指令集中的 32 位(4 字节)宽。这使得 Thumb 指令集在内存受限的嵌入式系统中非常有用。
mpu.c
1 |
|
armv7_mpu.h
内存保护单元 (MPU) 将内存映射划分为多个区域,并定义每个区域的位置、大小、访问权限和内存属性。它支持:
- 每个区域都有独立的属性设置。
- 区域重叠。
- 将内存属性导出到系统。
内存属性会影响对区域的内存访问行为。
- Cortex®M7 MPU 定义:
- 8 个或 16 个单独的内存区域,0-7 或 0-15。
- 背景区域
当内存区域重叠时,内存访问会受到编号最高的区域的属性的影响。例如,区域 7 的属性优先于与区域 7 重叠的任何区域的属性。
Cortex®-M7 MPU 内存映射是统一的。这意味着指令访问和数据访问具有相同的区域设置。
如果程序访问 MPU 禁止的内存位置,处理器将生成 MemManage 故障。这会导致故障异常,并可能导致 OS 环境中的进程终止。在 OS 环境中,内核可以根据要执行的进程动态更新 MPU 区域设置。通常,嵌入式 OS 使用 MPU 进行内存保护。
WT(Write-Through)Write-Through 是一种缓存策略,当处理器写入数据到缓存时,数据会立即写入到主存(内存)中。这种策略确保缓存和主存中的数据始终保持一致,但会导致较高的写操作延迟,因为每次写操作都需要访问主存。
WB(Write-Back)Write-Back 是另一种缓存策略,当处理器写入数据到缓存时,数据只会写入到缓存中,而不会立即写入到主存。只有当缓存行被替换或刷新时,数据才会写入到主存。这种策略可以减少写操作的延迟,提高系统性能,但在缓存和主存之间的数据一致性管理上更加复杂。
alloc 通常指的是缓存分配(allocation).配置是否分配(allocation)是为了控制在特定情况下是否为数据分配新的缓存行。这对于优化系统性能和管理缓存行为非常重要。
- 性能优化:
- 写分配(Write Allocate):在写操作时,如果数据不在缓存中,处理器会将数据从主存加载到缓存中,并为其分配一个新的缓存行。这种策略可以提高后续对该数据的访问性能。
- 无写分配(No Write Allocate):在写操作时,如果数据不在缓存中,处理器不会为其分配新的缓存行,而是直接写入主存。这种策略可以减少缓存的占用,适用于写操作较少的场景。
- 缓存一致性:
- 读分配(Read Allocate):在读操作时,如果数据不在缓存中,处理器会将数据从主存加载到缓存中,并为其分配一个新的缓存行。这种策略可以提高后续对该数据的访问性能。
- 无读分配(No Read Allocate):在读操作时,如果数据不在缓存中,处理器不会为其分配新的缓存行,而是直接从主存读取数据。这种策略可以减少缓存的占用,适用于读操作较少的场景。
- 性能优化:
1 | //访问权限字段 |
cache.c
在 ARM 架构中,PoU(Point of Unification)和 PoC(Point of Coherency)是两个与缓存一致性相关的重要概念。它们用于描述缓存操作的范围和影响。
- PoU(Point of Unification)
PoU 是指统一点,表示指令和数据缓存的统一点。在这个点上,指令缓存和数据缓存的内容是一致的。通常,PoU 是指缓存层次结构中的某个级别,在这个级别上,指令和数据缓存的内容可以被视为统一的。
- 作用
- 指令和数据缓存一致性:确保在 PoU 处,指令缓存和数据缓存的内容是一致的。
- 缓存操作的范围:缓存操作(如清除、无效化)在 PoU 处生效,确保指令和数据缓存的一致性。
- PoC(Point of Coherency)
PoC 是指一致性点,表示缓存和主存之间的一致性点。在这个点上,所有处理器和 DMA 设备都可以看到一致的内存视图。通常,PoC 是指缓存层次结构中的某个级别,在这个级别上,缓存和主存的内容是一致的。
- 作用
- 缓存和主存一致性:确保在 PoC 处,缓存和主存的内容是一致的。
- 缓存操作的范围:缓存操作(如清除、无效化)在 PoC 处生效,确保缓存和主存的一致性。
- 使能缓存的步骤
清除和无效化缓存: 在使能缓存之前,通常需要清除和无效化缓存,以确保缓存中的数据是一致的。这可以防止缓存中的旧数据影响系统的正常运行。
配置缓存属性: 配置缓存的属性,如缓存策略(写回或直写)、缓存大小、缓存行大小等。这些属性决定了缓存的工作方式和性能。
使能缓存: 最后,通过设置特定的控制寄存器,使能指令缓存和数据缓存。
1 | /* PoU : 统一点, Poc: 连贯点 */ |
enable_caches
1 | static u32 *get_action_reg_set_ways(enum cache_action action) |
flush_dcache_range
1 | void flush_dcache_range(unsigned long start, unsigned long stop) |
cpu.c
reset_cpu
1 | /* |
cleanup_before_linux
1 | /* |
borad
mach-stm32
soc.c
- 配置MPU区域
1 | int arch_cpu_init(void) |
stm32h750-art-pi
stm32h750-art-pi.c
1 | int dram_init(void) |
include/configs/stm32h750-art-pi.h
1 | //SDRAM 32MB 分配了24MB给内存 预留了8MB |