文章目录
- arch/arm/mm/nommu.c
-
- [adjust_lowmem_bounds 低内存边界调整](#adjust_lowmem_bounds 低内存边界调整)
- [arm_mm_memblock_reserve 内存块保留](#arm_mm_memblock_reserve 内存块保留)
- [paging_init 分页初始化](#paging_init 分页初始化)
- [ARMv7-M (无MMU) `ioremap` 核心实现](#ARMv7-M (无MMU)
ioremap
核心实现) -
- [`__arm_ioremap_caller`: 核心身份映射函数](#
__arm_ioremap_caller
: 核心身份映射函数) - [`arch_ioremap_caller`: 体系结构钩子](#
arch_ioremap_caller
: 体系结构钩子) - [`ioremap` 系列API的实现](#
ioremap
系列API的实现)
- [`__arm_ioremap_caller`: 核心身份映射函数](#
- arch/arm/mm/init.c
-
- [arm_memblock_init 内存块初始化](#arm_memblock_init 内存块初始化)
- [zone_sizes_init 区域大小初始化](#zone_sizes_init 区域大小初始化)
- [bootmem_init 引导内存初始化](#bootmem_init 引导内存初始化)
- arch/arm/mm/proc-macros.S
-
- [dcache_line_size 获取数据缓存行大小](#dcache_line_size 获取数据缓存行大小)
- [define_processor_functions: 创建处理器核心功能分发表](#define_processor_functions: 创建处理器核心功能分发表)
- arch/arm/mm/cache-v7m.S
-
- [v7m_cacheop V7-M 缓存操作](#v7m_cacheop V7-M 缓存操作)
- [dccimvac 按 MVA 到 PoC 清理数据缓存行并使之失效(例如系统DMA)](#dccimvac 按 MVA 到 PoC 清理数据缓存行并使之失效(例如系统DMA))
- [v7m_flush_kern_dcache_area 内核数据缓存区域刷新](#v7m_flush_kern_dcache_area 内核数据缓存区域刷新)
- [通用 ARMv7-M 处理器函数 (`cpu_v7m_*`)](#通用 ARMv7-M 处理器函数 (
cpu_v7m_*
)) -
- [Cortex-M7 专用处理器函数 (`cpu_cm7_*`)](#Cortex-M7 专用处理器函数 (
cpu_cm7_*
))
- [Cortex-M7 专用处理器函数 (`cpu_cm7_*`)](#Cortex-M7 专用处理器函数 (
- arch/arm/mm/cache.c
-
- [v7m_cache_fns V7-M缓存函数](#v7m_cache_fns V7-M缓存函数)
- arch/arm/mm/fault.c
-
- [FSR/IFSR Info: ARM内存访问异常分发表](#FSR/IFSR Info: ARM内存访问异常分发表)
-
- [关于STM32H750 (ARMv7-M架构) 的适用性说明](#关于STM32H750 (ARMv7-M架构) 的适用性说明)
- [`fsr_info` 数组: 数据访问故障 (Data Abort) 处理表](#
fsr_info
数组: 数据访问故障 (Data Abort) 处理表) - [`ifsr_info` 数组: 指令预取故障 (Prefetch Abort) 处理表](#
ifsr_info
数组: 指令预取故障 (Prefetch Abort) 处理表)
- [`hook_fault_code`: ARMv7-A/R 架构的故障状态分派](#
hook_fault_code
: ARMv7-A/R 架构的故障状态分派) - [exceptions_init: 为特定的ARM架构版本注册异常处理钩子](#exceptions_init: 为特定的ARM架构版本注册异常处理钩子)

arch/arm/mm/nommu.c
adjust_lowmem_bounds 低内存边界调整
c
static void __init adjust_lowmem_bounds_mpu(void)
{
unsigned long pmsa = read_cpuid_ext(CPUID_EXT_MMFR0) & MMFR0_PMSA;
switch (pmsa) {
case MMFR0_PMSAv7:
pmsav7_adjust_lowmem_bounds();
break;
case MMFR0_PMSAv8:
pmsav8_adjust_lowmem_bounds();
break;
default:
break;
}
}
void __init adjust_lowmem_bounds(void)
{
phys_addr_t end;
adjust_lowmem_bounds_mpu(); //ARMV7-M 没有 MMFR0_PMSA
end = memblock_end_of_DRAM(); //返回内存结束地址
high_memory = __va(end - 1) + 1;
memblock_set_current_limit(end); //设置memblock的限制地址
}
arm_mm_memblock_reserve 内存块保留
c
void __init arm_mm_memblock_reserve(void)
{
#ifndef CONFIG_CPU_V7M
vectors_base = IS_ENABLED(CONFIG_CPU_CP15) ? setup_vectors_base() : 0;
/*
* Register the exception vector page.
* some architectures which the DRAM is the exception vector to trap,
* alloc_page breaks with error, although it is not NULL, but "0."
*/
memblock_reserve(vectors_base, 2 * PAGE_SIZE);
#else /* ifndef CONFIG_CPU_V7M */
/*
* V7-M 上没有专门的矢量页面。所以什么都不需要在此处保留。
*/
#endif
/*
* 在任何情况下,请始终确保永远不会使用地址 0,因为如果 0 作为合法地址返回,很多事情都会非常混乱。
*/
memblock_reserve(0, 1);
}
paging_init 分页初始化
c
/*
* paging_init() 设置页表,初始化区域内存映射,并设置零页、坏页和坏页表。
*/
void __init paging_init(const struct machine_desc *mdesc)
{
void *zero_page;
/*
* 在 V7-M 上,无需将矢量表复制到专用内存区域。该地址是可配置的,因此可以使用内核映像中的表。
*/
early_trap_init((void *)vectors_base);
mpu_setup(); //V7-M无MPU
/* 分配 Zero 页。 */
zero_page = (void *)memblock_alloc_or_panic(PAGE_SIZE, PAGE_SIZE);
bootmem_init();
empty_zero_page = virt_to_page(zero_page);
//V7-M v7m_cache_fns
flush_dcache_page(empty_zero_page); //cpu_cache.flush_kern_dcache_area(page_address(page), PAGE_SIZE);
}
ARMv7-M (无MMU) ioremap
核心实现
此代码片段揭示了在没有MMU(内存管理单元)的ARM架构 (如运行在STM32H750上的uClinux)中, ioremap
系列函数的最核心、最底层的实现。其根本原理是身份映射(Identity Mapping) , 即虚拟地址等于物理地址。
在这种架构下, ioremap
函数的主要作用不再是进行复杂的地址翻译, 而是承担了两个至关重要的角色:
- 提供API兼容性: 它为设备驱动程序提供了一个与有MMU的系统完全相同的、标准化的API。这使得为全功能Linux编写的驱动程序可以几乎不加修改地被重新编译并运行在无MMU的系统上, 极大地增强了代码的可移植性。
- 强制类型安全和正确的访问方式 : 函数返回一个特殊的指针类型
void __iomem *
。这个类型对编译器和静态分析工具(如sparse
)是一个明确的信号, 表明该指针指向的是IO内存, 而不是普通RAM。任何尝试对__iomem
指针进行直接解引用(如*addr = val;
)的行为都会产生编译警告。这强制开发者必须使用专用的、体系结构相关的函数(如readl
/writel
)来访问这些地址, 这些专用函数内置了必要的内存屏障(memory barrier), 确保了对硬件寄存器的读写操作能够按照预期的顺序、不被编译器或CPU乱序执行优化所干扰地完成。
对于STM32H750这类带有MPU(内存保护单元)但无MMU的微控制器, 缓存策略(caching policy)的设定不是在ioremap
调用时动态完成的, 而是在系统启动早期的底层代码中, 通过配置MPU来静态完成的 。例如, 内核启动时就会将SRAM区域配置为可缓存(cacheable), 而将所有外设寄存器所在的地址范围配置为强序的、非缓存的设备内存(strongly-ordered, non-cacheable device memory)。因此, 当ioremap
被调用时, 它只是返回一个指向已经配置好内存属性的物理地址的指针, 而mtype
参数实际上被底层实现__arm_ioremap_caller
忽略了。
__arm_ioremap_caller
: 核心身份映射函数
c
/*
* __arm_ioremap_caller: ARM架构下ioremap的核心调用实现.
* @phys_addr: 要映射的物理地址.
* @size: 映射区域的大小.
* @mtype: 请求的映射类型(缓存策略).
* @caller: 调用者的返回地址, 用于调试.
*/
void __iomem *__arm_ioremap_caller(phys_addr_t phys_addr, size_t size,
unsigned int mtype, void *caller)
{
/*
* 这是无MMU系统上ioremap的本质:
* 函数直接返回传入的物理地址, 只是将其强制类型转换为 `void __iomem *`.
* 它完全忽略了 size, mtype, 和 caller 参数.
* 1. 忽略 size: 因为没有页表要建立, 只要有起始地址就可以访问.
* 2. 忽略 mtype: 因为缓存策略被认为已由MPU在启动时静态配置好.
* 3. 忽略 caller: 在这个最小化实现中, 放弃了调试追踪功能.
*/
return (void __iomem *)phys_addr;
}
arch_ioremap_caller
: 体系结构钩子
c
/*
* 这是一个函数指针, 作为体系结构特定的ioremap实现的钩子.
* 在内核初始化时, 它可以被设置为指向一个更复杂的、特定于某个ARM板卡的ioremap实现.
* 如果不设置, 则系统会依赖一个默认的实现(在有MMU的系统中通常是__arm_ioremap_caller的另一个版本).
* 在无MMU的uClinux中, 所有的ioremap最终都通过 __arm_ioremap_caller 来执行.
*/
void __iomem * (*arch_ioremap_caller)(phys_addr_t, size_t, unsigned int, void *);
ioremap
系列API的实现
c
/*
* ioremap: 标准的ioremap函数, 用于映射标准的设备内存.
* @res_cookie: 要映射的物理地址 (类型为 resource_size_t, 但本质上传递的是物理地址).
* @size: 映射的大小.
*/
void __iomem *ioremap(resource_size_t res_cookie, size_t size)
{
/*
* 调用核心实现函数, 并传递:
* - mtype = MT_DEVICE: 表示映射为标准的设备内存(强序, 非缓冲, 非缓存).
* - __builtin_return_address(0): 一个编译器内置函数, 获取当前函数的返回地址, 用于追踪调用者.
* 虽然最终被忽略, 但这是API的标准用法.
*/
return __arm_ioremap_caller(res_cookie, size, MT_DEVICE,
__builtin_return_address(0));
}
/* 将ioremap导出, 使其成为一个可供所有内核模块使用的标准API. */
EXPORT_SYMBOL(ioremap);
/*
* ioremap_cache: 用于映射可缓存的IO内存 (例如帧缓冲区).
* @res_cookie: 要映射的物理地址.
* @size: 映射的大小.
*/
void __iomem *ioremap_cache(resource_size_t res_cookie, size_t size)
{
/*
* 调用核心实现函数, 并传递 mtype = MT_DEVICE_CACHED.
* 在无MMU系统上, 这依赖于MPU已经将此段内存配置为可缓存.
*/
return __arm_ioremap_caller(res_cookie, size, MT_DEVICE_CACHED,
__builtin_return_address(0));
}
EXPORT_SYMBOL(ioremap_cache);
/*
* ioremap_wc: 用于映射"写合并"(Write-Combining)的IO内存.
* @res_cookie: 要映射的物理地址.
* @size: 映射的大小.
*/
void __iomem *ioremap_wc(resource_size_t res_cookie, size_t size)
{
/*
* 调用核心实现函数, 并传递 mtype = MT_DEVICE_WC.
* 在无MMU系统上, 这依赖于MPU已经将此段内存配置为支持写合并的设备内存.
*/
return __arm_ioremap_caller(res_cookie, size, MT_DEVICE_WC,
__builtin_return_address(0));
}
EXPORT_SYMBOL(ioremap_wc);
arch/arm/mm/init.c
arm_memblock_init 内存块初始化
c
void __init arm_memblock_init(const struct machine_desc *mdesc)
{
/* 在 memblock 中注册内核文本、内核数据和 initrd. */
memblock_reserve(__pa(KERNEL_START), KERNEL_END - KERNEL_START);
reserve_initrd_mem(); //预留initramfs内存
arm_mm_memblock_reserve();
/* 保留任何特定于平台的 MemBlock 区域*/
//STM32 ARMV7M无该函数
if (mdesc->reserve)
mdesc->reserve();
early_init_fdt_scan_reserved_mem();
/* reserve memory for DMA contiguous allocations */
dma_contiguous_reserve(arm_dma_limit);
arm_memblock_steal_permitted = false;
memblock_dump_all();
}
zone_sizes_init 区域大小初始化
c
static void __init zone_sizes_init(unsigned long min, unsigned long max_low,
unsigned long max_high)
{
unsigned long max_zone_pfn[MAX_NR_ZONES] = { 0 };
#ifdef CONFIG_ZONE_DMA
max_zone_pfn[ZONE_DMA] = min(arm_dma_pfn_limit, max_low);
#endif
max_zone_pfn[ZONE_NORMAL] = max_low;
#ifdef CONFIG_HIGHMEM
max_zone_pfn[ZONE_HIGHMEM] = max_high;
#endif
free_area_init(max_zone_pfn);
}
bootmem_init 引导内存初始化
c
void __init bootmem_init(void)
{
memblock_allow_resize(); //Memblock 允许调整大小
/*
* *max_low = PFN_DOWN(memblock_get_current_limit());
* *min = PFN_UP(memblock_start_of_DRAM());
* *max_high = PFN_DOWN(memblock_end_of_DRAM());
*/
find_limits(&min_low_pfn, &max_low_pfn, &max_pfn);
//进行内存测试
early_memtest((phys_addr_t)min_low_pfn << PAGE_SHIFT,
(phys_addr_t)max_low_pfn << PAGE_SHIFT);
/*
*sparse_init() 尝试从 memblock 分配内存,因此必须在固定预留之后完成
* CONFIG_NUMA
*/
sparse_init();
/*
*现在释放内存 - free_area_init需要由 sparse_init() 初始化的稀疏 mem_map 数组 memmap_init_zone(),否则所有 PFN 都无效。
*/
zone_sizes_init(min_low_pfn, max_low_pfn, max_pfn);
}
arch/arm/mm/proc-macros.S
dcache_line_size 获取数据缓存行大小
asm
/*
* dcache_line_size - 从 ARMv7 上的 CTR 寄存器获取最小 D-cache 行大小。
*/
.macro dcache_line_size, reg, tmp
#ifdef CONFIG_CPU_V7M
movw \tmp, #:lower16:BASEADDR_V7M_SCB + V7M_SCB_CTR
movt \tmp, #:upper16:BASEADDR_V7M_SCB + V7M_SCB_CTR
ldr \tmp, [\tmp]
#else
mrc p15, 0, \tmp, c0, c0, 1 @ read ctr
#endif
lsr \tmp, \tmp, #16 //将 CTR 寄存器的值右移 16 位,提取缓存行大小的编码字段
and \tmp, \tmp, #0xf @ cache line size encoding
mov \reg, #4 @ bytes per word
mov \reg, \reg, lsl \tmp @ actual cache line size
.endm
define_processor_functions: 创建处理器核心功能分发表
这是一个汇编宏 (macro), 其核心作用是作为一个模板, 用于生成一个名为 _processor_functions
的C语言结构体 (在汇编中表现为一张函数指针表)。这个表包含了特定类型CPU所需要的所有核心操作函数的地址, 例如异常处理函数、初始化函数、缓存管理函数和电源管理函数。内核在启动时会根据检测到的CPU类型, 找到对应的功能表, 并使用表中的函数指针来执行硬件相关的操作。这是一种关键的硬件抽象机制。
在单核无MMU的STM32H750平台上的应用
对于STM32H750 (Cortex-M7) 这样的单核无MMU系统, 这个宏通常会被这样调用 (如您之前提供的代码所示):
define_processor_functions v7m, dabort=nommu_early_abort, pabort=legacy_pabort, nommu=1
这里, nommu=1
是一个至关重要的参数。它会在此宏的内部触发条件汇编, 确保生成的函数指针表中:
- 与MMU相关的函数指针 (
set_pte_ext
) 被设置为0 (NULL), 因为硬件上不存在MMU。 - 异常处理函数指针被设置为专门为无MMU系统编写的版本 (
nommu_early_abort
)。
这样, 该宏就为STM32H750量身定做了一套正确的底层操作函数集合, 使得上层内核代码无需关心底层是否有MMU, 就可以正常运行。
c
/*
* .macro: 定义一个名为 define_processor_functions 的汇编宏.
* @name: 必需参数, CPU的名称 (例如 v7m), 用于生成函数和结构体的名字.
* @dabort: 必需参数, 指向数据访问异常处理函数的指针.
* @pabort: 必需参数, 指向指令预取异常处理函数的指针.
* @nommu=0: 可选参数, 默认值为0. 如果设置为1, 表示为无MMU的系统进行配置.
* @suspend=0: 可选参数, 默认值为0. 如果设置为1, 表示该CPU支持挂起/恢复功能.
* @bugs=0: 可选参数, 默认值为0, 指向一个用于处理CPU特有BUG的函数.
*/
.macro define_processor_functions name:req, dabort:req, pabort:req, nommu=0, suspend=0, bugs=0
/*
* 如果我们正在为big.LITTLE架构编译, 并且启用了分支预测器加固,
* 我们需要处理器功能表在启动后仍然可用(不能被放入init段回收掉).
* 对于单核STM32, 这个条件不成立.
*/
#if defined(CONFIG_BIG_LITTLE) && defined(CONFIG_HARDEN_BRANCH_PREDICTOR)
/* .section ".rodata": 切换到只读数据段. */
.section ".rodata"
#endif
/* .type: 告诉链接器, 接下来定义的符号是一个数据对象(#object), 而不是一个函数. */
.type \name\()_processor_functions, #object
/* .align 2: 将下面的数据对齐到 2^2 = 4 字节的边界上. 这对于32位指针数组是必需的. */
.align 2
/* ENTRY: 定义一个全局可见的入口点(符号). \name\() 会被替换, 例如 "v7m_processor_functions". */
ENTRY(\name\()_processor_functions)
/* .word: 在当前位置插入一个32位的字. 下面一系列指令构成了函数指针表. */
.word \dabort // 第1项: 数据访问异常处理函数 (在STM32上是 nommu_early_abort).
.word \pabort // 第2项: 指令预取异常处理函数 (在STM32上是 legacy_pabort).
.word cpu_\name\()_proc_init // 第3项: CPU相关的初始化函数 (例如 cpu_v7m_proc_init).
.word \bugs // 第4项: CPU勘误(bug)处理函数 (在STM32上是 0).
.word cpu_\name\()_proc_fin // 第5项: CPU相关的结束/清理函数.
.word cpu_\name\()_reset // 第6项: CPU软复位函数.
.word cpu_\name\()_do_idle // 第7项: CPU进入空闲状态的函数 (例如执行 WFI 指令).
.word cpu_\name\()_dcache_clean_area // 第8项: 清理(写回)数据缓存中特定区域的函数.
.word cpu_\name\()_switch_mm // 第9项: 切换内存上下文的函数 (在无MMU系统上通常是空操作).
/* .if \nommu: 这是一个条件汇编指令. 如果宏参数 nommu 的值非0 (为真)... */
.if \nommu
.word 0 // ...则在此处插入一个0. 这是因为 set_pte_ext 函数用于设置页表项, 只有在有MMU的系统上才有意义.
.else
/* 否则 (如果 nommu 为0)... */
.word cpu_\name\()_set_pte_ext // ...则插入一个指向 set_pte_ext 函数的指针.
.endif
/* .if \suspend: 如果宏参数 suspend 的值非0... */
.if \suspend
.word cpu_\name\()_suspend_size // ...插入一个指向返回挂起状态所需大小的函数的指针.
/*
* #ifdef CONFIG_ARM_CPU_SUSPEND: 如果内核配置了ARM CPU挂起功能...
*/
#ifdef CONFIG_ARM_CPU_SUSPEND
.word cpu_\name\()_do_suspend // ...插入指向执行挂起操作的函数的指针.
.word cpu_\name\()_do_resume // ...插入指向执行恢复操作的函数的指针.
#else
/* 否则, 插入空指针. */
.word 0
.word 0
#endif
.else
/* 否则 (如果 suspend 为0, 对于STM32即是此种情况), 插入三个空指针. */
.word 0
.word 0
.word 0
.endif
/* .size: 这是一个汇编器指令, 用于计算符号的大小, 并将其存入符号表. */
.size \name\()_processor_functions, . - \name\()_processor_functions // 大小 = 当前地址(.) - 起始地址.
/* 对应开头的 #if. */
#if defined(CONFIG_BIG_LITTLE) && defined(CONFIG_HARDEN_BRANCH_PREDICTOR)
/* .previous: 切换回之前的代码段. */
.previous
#endif
/* .endm: 宏定义结束. */
.endm
arch/arm/mm/cache-v7m.S
v7m_cacheop V7-M 缓存操作
asm
.macro v7m_cacheop, rt, tmp, op, c = al
movw\c \tmp, #:lower16:BASEADDR_V7M_SCB + \op
movt\c \tmp, #:upper16:BASEADDR_V7M_SCB + \op
str\c \rt, [\tmp]
.endm
dccimvac 按 MVA 到 PoC 清理数据缓存行并使之失效(例如系统DMA)
- POC的缓存维护操作可用于在Cortex®-M7数据缓存与外部代理(例如系统DMA)之间同步数据
asm
/*
* dccimvac:按 MVA 到 PoC 清理数据缓存行并使之失效。
*/
.irp c,,eq,ne,cs,cc,mi,pl,vs,vc,hi,ls,ge,lt,gt,le,hs,lo
.macro dccimvac\c, rt, tmp
v7m_cacheop \rt, \tmp, V7M_SCB_DCCIMVAC, \c
.endm
.endr
v7m_flush_kern_dcache_area 内核数据缓存区域刷新
asm
/*
* v7m_flush_kern_dcache_area(void *addr, size_t size)
*
* 确保将页面 kaddr 中保存的数据写回相关页面。
*
* - addr - 内核地址
* - size - 区域大小
*/
SYM_TYPED_FUNC_START(v7m_flush_kern_dcache_area)
dcache_line_size r2, r3 //获取数据缓存行大小
add r1, r0, r1
sub r3, r2, #1
bic r0, r0, r3 //将结束地址对齐缓存行大小
1:
dccimvac r0, r3 @ clean & invalidate D line / unified line
add r0, r0, r2 //将地址 r0 增加一个缓存行大小,移动到下一个缓存行
cmp r0, r1 //比较当前地址是否已达到结束地址。
blo 1b //如果当前地址小于结束地址,跳回标签 1,继续操作下一行
dsb st //数据同步屏障,确保所有缓存操作在返回前完成。这是一个内存屏障,保证缓存清理和失效的效果对后续操作可见。
ret lr
SYM_FUNC_END(v7m_flush_kern_dcache_area)
通用 ARMv7-M 处理器函数 (cpu_v7m_*
)
这些是适用于所有ARMv7-M处理器的默认函数。
arm-asm
/* SYM_TYPED_FUNC_START 是一个宏, 用于定义一个全局可见的函数入口点 */
SYM_TYPED_FUNC_START(cpu_v7m_proc_init)
/* ret lr: 这是一个ARM汇编伪指令, 实际等价于 "bx lr" (Branch and exchange to Link Register). */
/* lr 寄存器通常保存着函数返回地址. 这条指令的含义是: "立即返回". */
/* 这个函数是空的, 表明对于通用的ARMv7-M, 没有特殊的处理器初始化步骤. */
ret lr
SYM_FUNC_END(cpu_v7m_proc_init) /* SYM_FUNC_END 宏用于标记函数结束, 以便计算函数大小. */
SYM_TYPED_FUNC_START(cpu_v7m_proc_fin)
/* 同样, 这个函数也是空的, 表示没有通用的处理器收尾步骤. */
ret lr
SYM_FUNC_END(cpu_v7m_proc_fin)
/*
* cpu_v7m_reset(loc)
*
* 执行一次系统的软复位. 将CPU置于与硬件复位后相同的状态,
* 并跳转到将成为复位向量的地方.
*
* - loc: 用于软复位跳转的目标地址, 该地址在调用此函数时被放入 r0 寄存器.
*/
.align 5 /* .align 5 表示将下面的代码对齐到 2^5 = 32 字节的边界. */
SYM_TYPED_FUNC_START(cpu_v7m_reset)
/* ret r0: 等价于 "bx r0". 这条指令会无条件跳转到 r0 寄存器中包含的地址. */
/* 这是执行软复位的核心: 直接跳转到内核启动代码的入口地址, 实现重新启动. */
ret r0
SYM_FUNC_END(cpu_v7m_reset)
/*
* cpu_v7m_do_idle()
*
* 使处理器空闲(例如, 等待中断).
*
* (调用此函数时)IRQ中断已经被禁用了.
*/
SYM_TYPED_FUNC_START(cpu_v7m_do_idle)
/* wfi: Wait For Interrupt 指令. */
/* 执行这条指令后, CPU会立即停止执行后续指令, 进入低功耗的睡眠状态, 直到一个中断(或调试事件)发生才会被唤醒. */
/* 这是操作系统调度器在没有任务需要运行时, 让CPU节能的核心指令. */
wfi
/* 当被中断唤醒后, 从这里继续执行, 立即返回. */
ret lr
SYM_FUNC_END(cpu_v7m_do_idle)
SYM_TYPED_FUNC_START(cpu_v7m_dcache_clean_area)
/* 通用的ARMv7-M实现是空操作. 这是因为并非所有v7-M核心都有数据缓存(D-cache). */
/* 这个函数作为一个占位符, 等待像Cortex-M7这样有缓存的CPU来提供具体的实现. */
ret lr
SYM_FUNC_END(cpu_v7m_dcache_clean_area)
/*
* 由于没有MMU(内存管理单元), 这里无事可做.
*/
SYM_TYPED_FUNC_START(cpu_v7m_switch_mm)
/* 在有MMU的系统中, 这个函数会切换页表, 实现进程地址空间的隔离. */
/* 在无MMU的STM32H750上, 所有代码共享同一个物理地址空间, 所以切换是无意义的, 函数为空. */
ret lr
SYM_FUNC_END(cpu_v7m_switch_mm)
.globl cpu_v7m_suspend_size /* 声明一个全局可见的符号 */
.equ cpu_v7m_suspend_size, 0 /* .equ: 将一个符号赋值为一个常量. 这里表示挂起状态所需空间为0. */
#ifdef CONFIG_ARM_CPU_SUSPEND /* 如果内核配置了CPU挂起功能 */
SYM_TYPED_FUNC_START(cpu_v7m_do_suspend)
/* 默认的挂起/恢复函数也是空操作, 需要特定平台提供实现. */
ret lr
SYM_FUNC_END(cpu_v7m_do_suspend)
SYM_TYPED_FUNC_START(cpu_v7m_do_resume)
ret lr
SYM_FUNC_END(cpu_v7m_do_resume)
#endif
Cortex-M7 专用处理器函数 (cpu_cm7_*
)
这些是专门为Cortex-M7核心提供的、重写了通用v7-M函数的具体实现。
arm-asm
/*
* 这是 Cortex-M7 专用的 dcache_clean_area 实现, 它取代了上面那个空的 v7m 版本.
* 功能: 将指定内存区域([r0, r0+r1-1])的数据从数据缓存中"清理"(写回)到主内存中.
* 这对于保证DMA操作前内存数据的一致性至关重要.
*/
SYM_TYPED_FUNC_START(cpu_cm7_dcache_clean_area)
/* dcache_line_size 是一个宏, 用于获取数据缓存行的大小(例如32字节), 并存入r2. r3被用作临时寄存器. */
dcache_line_size r2, r3
/* movw/movt: 这两条指令组合在一起, 将一个32位的立即数(地址)加载到 r3 寄存器中. */
/* 加载的是 V7M_SCB_DCCMVAC 寄存器的地址. SCB是系统控制块(System Control Block)的缩写. */
/* DCCMVAC (Data Cache Clean by MVA to PoC) 是一个只写的寄存器, 向它写入一个地址, 就会清理该地址所在的缓存行. */
movw r3, #:lower16:BASEADDR_V7M_SCB + V7M_SCB_DCCMVAC
movt r3, #:upper16:BASEADDR_V7M_SCB + V7M_SCB_DCCMVAC
/* 1: 是一个循环标签 */
1:
/* str r0, [r3]: 将 r0 中的地址写入到 DCCMVAC 寄存器. 这一步执行了对单个缓存行的清理操作. */
str r0, [r3]
/* add r0, r0, r2: 将地址 r0 增加一个缓存行的大小, 指向下一个需要清理的缓存行. */
add r0, r0, r2
/* subs r1, r1, r2: 将剩余要清理的字节数 r1 减去一个缓存行的大小. 's'后缀表示更新条件标志位. */
subs r1, r1, r2
/* bhi 1b: Branch if Higher. 如果 r1 仍然大于0, 则跳转回标签1(1b中的'b'表示向后跳转). */
bhi 1b
/* dsb: Data Synchronization Barrier. 数据同步屏障. */
/* 这是一条关键指令, 它确保所有之前的缓存清理操作都已完成, 才能执行后续的指令. */
dsb
/* 返回. */
ret lr
SYM_FUNC_END(cpu_cm7_dcache_clean_area)
/*
* 这是 Cortex-M7 专用的 proc_fin 实现.
* 功能: 在系统关闭或进程结束的某个阶段, 禁用数据缓存和指令缓存.
*/
SYM_TYPED_FUNC_START(cpu_cm7_proc_fin)
/* movw/movt: 将 V7M_SCB_CCR (Cache Control Register, 缓存控制寄存器) 的地址加载到 r2. */
movw r2, #:lower16:(BASEADDR_V7M_SCB + V7M_SCB_CCR)
movt r2, #:upper16:(BASEADDR_V7M_SCB + V7M_SCB_CCR)
/* ldr r0, [r2]: 从CCR寄存器中读取当前的控制字到 r0. */
ldr r0, [r2]
/* bic r0, r0, #(V7M_SCB_CCR_DC | V7M_SCB_CCR_IC): bic 是 Bit Clear 指令. */
/* 它将 r0 中对应 V7M_SCB_CCR_DC (数据缓存使能) 和 V7M_SCB_CCR_IC (指令缓存使能) 的位清零. */
bic r0, r0, #(V7M_SCB_CCR_DC | V7M_SCB_CCR_IC)
/* str r0, [r2]: 将修改后的值写回CCR寄存器, 从而禁用了两个缓存. */
str r0, [r2]
/* 返回. */
ret lr
SYM_FUNC_END(cpu_cm7_proc_fin)
arch/arm/mm/cache.c
v7m_cache_fns V7-M缓存函数
c
void v7m_flush_icache_all(void);
void v7m_flush_kern_cache_all(void);
void v7m_flush_user_cache_all(void);
void v7m_flush_user_cache_range(unsigned long, unsigned long, unsigned int);
void v7m_coherent_kern_range(unsigned long, unsigned long);
int v7m_coherent_user_range(unsigned long, unsigned long);
void v7m_flush_kern_dcache_area(void *, size_t);
void v7m_dma_map_area(const void *, size_t, int);
void v7m_dma_unmap_area(const void *, size_t, int);
void v7m_dma_flush_range(const void *, const void *);
struct cpu_cache_fns v7m_cache_fns __initconst = {
.flush_icache_all = v7m_flush_icache_all,
.flush_kern_all = v7m_flush_kern_cache_all,
.flush_kern_louis = v7m_flush_kern_cache_all,
.flush_user_all = v7m_flush_user_cache_all,
.flush_user_range = v7m_flush_user_cache_range,
.coherent_kern_range = v7m_coherent_kern_range,
.coherent_user_range = v7m_coherent_user_range,
.flush_kern_dcache_area = v7m_flush_kern_dcache_area,
.dma_map_area = v7m_dma_map_area,
.dma_unmap_area = v7m_dma_unmap_area,
.dma_flush_range = v7m_dma_flush_range,
};
arch/arm/mm/fault.c
FSR/IFSR Info: ARM内存访问异常分发表
这两个数组(fsr_info
和 ifsr_info
)是Linux内核在处理ARM架构CPU的内存访问异常时使用的静态"分发表"或"查找表"。当CPU因为一次错误的内存访问(例如, 访问一个不存在的地址或向只读区域写入)而触发硬件异常时, 它会在一个特殊的寄存器------故障状态寄存器 (FSR) 中设置一个代码来表明错误的原因。内核的异常处理程序会读取这个代码, 并用它作为索引在此数组中查找对应的处理方式, 包括应该调用哪个内核函数、应该向引发问题的用户进程发送什么信号, 以及应该在内核日志中打印什么描述信息。
关于STM32H750 (ARMv7-M架构) 的适用性说明
这是一个非常关键的区别: 您提供的这段代码源自为**"经典"ARM架构 (如ARMv4, ARMv5, ARMv7-A)** 编写的Linux内核。这些架构使用名为FSR (Fault Status Register) 和IFSR (Instruction Fault Status Register) 的寄存器来报告内存错误。
而您指定的STM32H750微控制器使用的是ARMv7-M架构 (Cortex-M7内核) 。ARMv7-M拥有一个更现代、更精细的故障处理机制, 它不使用FSR/IFSR, 而是使用以下三个状态寄存器的组合, 它们共同位于CFSR (Configurable Fault Status Register) 中:
- MMFSR (MemManage Fault Status Register): 报告由MPU (内存保护单元) 检测到的访问冲突, 例如执行位于"从不执行"(XN)区域的代码, 或写入一个只读区域。
- BFSR (BusFault Status Register): 报告在总线访问期间发生的错误, 例如访问一个不存在的内存地址。
- UFSR (UsageFault Status Register): 报告用法错误, 例如执行一条未定义的指令或进行一次非对齐的内存访问。
因此, 虽然您提供的这段代码展示了Linux内核处理内存异常的通用设计原则(即使用查找表来分发异常) , 但其内容(具体的故障码和表项)与ARMv7-M架构不直接对应 。在为STM32H750编译的Linux内核中, 会有功能类似但内容不同 的表, 这些表将由MMFSR
, BFSR
, UFSR
中的故障码来索引。
下面将按您提供的代码本身进行逐行解析, 以解释其设计思想。
fsr_info
数组: 数据访问故障 (Data Abort) 处理表
此表用于处理由数据加载或存储指令(如 LDR
, STR
)引起的内存访问错误。
c
/* 定义一个fsr_info结构体数组, 用于存储数据访问故障(Data Abort)的处理信息. */
static struct fsr_info fsr_info[] = {
/*
* 以下是标准的ARMv3和ARMv4中止。ARMv5将它们定义为"精确"中止。
* "精确"中止意味着CPU报告的故障地址就是导致中止的指令地址。
* 每一行代表一种故障类型, 由FSR寄存器的值(作为数组索引)来确定。
* 结构体的成员依次是: {内核处理函数, 发送给用户进程的信号, 信号的附加码, 内核日志描述字符串}
*/
/* 索引 0: "vector exception" - 向量异常 */
{ do_bad, SIGSEGV, 0, "vector exception" },
/* 索引 1: "alignment exception" - 对齐异常. 当LDR/STR指令试图访问一个未对齐的地址时发生。*/
{ do_bad, SIGBUS, BUS_ADRALN, "alignment exception" },
/* 索引 2: "terminal exception" - 终端异常 */
{ do_bad, SIGKILL, 0, "terminal exception" },
/* 索引 3: "alignment exception" - 对齐异常 (用于后备) */
{ do_bad, SIGBUS, BUS_ADRALN, "alignment exception" },
/* 索引 4: "external abort on linefetch" - 取指令行时发生外部中止 */
{ do_bad, SIGBUS, 0, "external abort on linefetch" },
/* 索引 5: "section translation fault" - 段转换故障. 在有MMU的系统中, 访问一个没有页表项映射的内存段。*/
{ do_translation_fault, SIGSEGV, SEGV_MAPERR, "section translation fault" },
/* 索引 6: "external abort on linefetch" - 取指令行时发生外部中止 (用于后备) */
{ do_bad, SIGBUS, 0, "external abort on linefetch" },
/* 索引 7: "page translation fault" - 页转换故障. 在有MMU的系统中, 访问一个没有页表项映射的内存页。*/
{ do_page_fault, SIGSEGV, SEGV_MAPERR, "page translation fault" },
/* 索引 8: "external abort on non-linefetch" - 非取指令行时发生外部中止 */
{ do_bad, SIGBUS, 0, "external abort on non-linefetch" },
/* 索引 9: "section domain fault" - 段域故障. 在有MMU的系统中, 访问了一个当前任务无权访问的内存域。*/
{ do_bad, SIGSEGV, SEGV_ACCERR, "section domain fault" },
/* 索引 10: "external abort on non-linefetch" - 非取指令行时发生外部中止 (用于后备) */
{ do_bad, SIGBUS, 0, "external abort on non-linefetch" },
/* 索引 11: "page domain fault" - 页域故障. 在有MMU的系统中, 访问了一个当前任务无权访问的内存域。*/
{ do_bad, SIGSEGV, SEGV_ACCERR, "page domain fault" },
/* 索引 12: "external abort on translation" - 转换期间发生外部中止 */
{ do_bad, SIGBUS, 0, "external abort on translation" },
/* 索引 13: "section permission fault" - 段权限故障. 在有MMU的系统中, 试图向一个只读的内存段写入数据。*/
{ do_sect_fault, SIGSEGV, SEGV_ACCERR, "section permission fault" },
/* 索引 14: "external abort on translation" - 转换期间发生外部中止 (用于后备) */
{ do_bad, SIGBUS, 0, "external abort on translation" },
/* 索引 15: "page permission fault" - 页权限故障. 在有MMU的系统中, 试图向一个只读的内存页写入数据。这在无MMU但有MPU的STM32上等价于MPU权限错误。*/
{ do_page_fault, SIGSEGV, SEGV_ACCERR, "page permission fault" },
/*
* 以下是"非精确"中止, 由FSR的第10位指示, 可能无法恢复。
* 仅当CPU中止处理程序支持第10位时才支持这些。
* "非精确"意味着报告的故障地址与导致中止的指令可能不直接对应, 这使得错误恢复变得困难或不可能。
*/
{ do_bad, SIGBUS, 0, "unknown 16" }, /* 未知故障码 16 */
{ do_bad, SIGBUS, 0, "unknown 17" }, /* ... */
{ do_bad, SIGBUS, 0, "unknown 18" },
{ do_bad, SIGBUS, 0, "unknown 19" },
{ do_bad, SIGBUS, 0, "lock abort" }, /* xscale 特有 */
{ do_bad, SIGBUS, 0, "unknown 21" },
{ do_bad, SIGBUS, BUS_OBJERR, "imprecise external abort" }, /* xscale 特有, 非精确外部中止 */
{ do_bad, SIGBUS, 0, "unknown 23" },
{ do_bad, SIGBUS, 0, "dcache parity error" }, /* xscale 特有, 数据缓存奇偶校验错误 */
{ do_bad, SIGBUS, 0, "unknown 25" },
{ do_bad, SIGBUS, 0, "unknown 26" },
{ do_bad, SIGBUS, 0, "unknown 27" },
{ do_bad, SIGBUS, 0, "unknown 28" },
{ do_bad, SIGBUS, 0, "unknown 29" },
{ do_bad, SIGBUS, 0, "unknown 30" },
{ do_bad, SIGBUS, 0, "unknown 31" },
};
ifsr_info
数组: 指令预取故障 (Prefetch Abort) 处理表
此表用于处理因CPU尝试从内存中预取指令时发生的错误。
c
/* 定义一个ifsr_info结构体数组, 用于存储指令预取故障(Prefetch Abort)的处理信息. */
static struct fsr_info ifsr_info[] = {
{ do_bad, SIGBUS, 0, "unknown 0" },
{ do_bad, SIGBUS, 0, "unknown 1" },
/* 索引 2: "debug event" - 调试事件. */
{ do_bad, SIGBUS, 0, "debug event" },
/* 索引 3: "section access flag fault" - 段访问标志故障. */
{ do_bad, SIGSEGV, SEGV_ACCERR, "section access flag fault" },
{ do_bad, SIGBUS, 0, "unknown 4" },
/* 索引 5: "section translation fault" - 段转换故障. 试图从一个未映射的内存段取指令. */
{ do_translation_fault, SIGSEGV, SEGV_MAPERR, "section translation fault" },
/* 索引 6: "page access flag fault" - 页访问标志故障. */
{ do_bad, SIGSEGV, SEGV_ACCERR, "page access flag fault" },
/* 索引 7: "page translation fault" - 页转换故障. 试图从一个未映射的内存页取指令. */
{ do_page_fault, SIGSEGV, SEGV_MAPERR, "page translation fault" },
/* 索引 8: "external abort on non-linefetch" - 非取指令行时发生外部中止. */
{ do_bad, SIGBUS, 0, "external abort on non-linefetch" },
/* 索引 9: "section domain fault" - 段域故障. */
{ do_bad, SIGSEGV, SEGV_ACCERR, "section domain fault" },
{ do_bad, SIGBUS, 0, "unknown 10" },
/* 索引 11: "page domain fault" - 页域故障. */
{ do_bad, SIGSEGV, SEGV_ACCERR, "page domain fault" },
/* 索引 12: "external abort on translation" - 转换期间发生外部中止. */
{ do_bad, SIGBUS, 0, "external abort on translation" },
/* 索引 13: "section permission fault" - 段权限故障. 试图从一个被标记为"从不执行"(XN)的内存段取指令. */
{ do_sect_fault, SIGSEGV, SEGV_ACCERR, "section permission fault" },
/* 索引 14: "external abort on translation" - 转换期间发生外部中止 (用于后备). */
{ do_bad, SIGBUS, 0, "external abort on translation" },
/* 索引 15: "page permission fault" - 页权限故障. 类似索引13, 但针对页. 在STM32上, 这等价于MPU将某区域配置为XN后, CPU仍试图从中取指. */
{ do_page_fault, SIGSEGV, SEGV_ACCERR, "page permission fault" },
/* 后续为未知或未定义的故障码. */
{ do_bad, SIGBUS, 0, "unknown 16" },
{ do_bad, SIGBUS, 0, "unknown 17" },
{ do_bad, SIGBUS, 0, "unknown 18" },
{ do_bad, SIGBUS, 0, "unknown 19" },
{ do_bad, SIGBUS, 0, "unknown 20" },
{ do_bad, SIGBUS, 0, "unknown 21" },
{ do_bad, SIGBUS, 0, "unknown 22" },
{ do_bad, SIGBUS, 0, "unknown 23" },
{ do_bad, SIGBUS, 0, "unknown 24" },
{ do_bad, SIGBUS, 0, "unknown 25" },
{ do_bad, SIGBUS, 0, "unknown 26" },
{ do_bad, SIGBUS, 0, "unknown 27" },
{ do_bad, SIGBUS, 0, "unknown 28" },
{ do_bad, SIGBUS, 0, "unknown 29" },
{ do_bad, SIGBUS, 0, "unknown 30" },
{ do_bad, SIGBUS, 0, "unknown 31" },
};
hook_fault_code
: ARMv7-A/R 架构的故障状态分派
此代码片段定义了一个名为fsr_info
的静态数组和一个名为hook_fault_code
的函数。它们共同构成了一个异常分派表 机制。其核心作用是, 当一个ARM处理器发生内存访问异常(在ARM术语中称为"abort")时, 内核的通用异常处理入口程序会读取硬件的故障状态寄存器(FSR), 并使用其中的故障码(fault code)作为索引在此fsr_info
表中查找, 以决定应该调用哪个具体的处理函数、以及向用户进程发送什么信号。
c
/*
* hook_fault_code: 在运行时(或初始化时)修改 fsr_info 分派表的函数.
*
* @nr: 要修改的条目的索引, 即故障码.
* @fn: 指向新的处理函数的指针. 类型为 int (*)(unsigned long, unsigned int, struct pt_regs *).
* @sig: 新的信号编号.
* @code: 新的信号附加代码.
* @name: 新的故障描述字符串.
*/
void __init
hook_fault_code(int nr, int (*fn)(unsigned long, unsigned int, struct pt_regs *),
int sig, int code, const char *name)
{
/*
* 检查索引 nr 是否在 fsr_info 数组的有效范围内.
*/
if (nr < 0 || nr >= ARRAY_SIZE(fsr_info))
/*
* 如果索引无效, 调用 BUG() 宏.
* BUG() 会立即让内核崩溃(panic), 并打印出有用的调试信息.
* 这是一种强制性的运行时断言, 用于捕捉严重的编程错误.
*/
BUG();
/*
* 如果索引有效, 就用传入的新参数覆盖掉 fsr_info 数组中第 nr 个元素的所有成员.
*/
fsr_info[nr].fn = fn; // 更新处理函数指针
fsr_info[nr].sig = sig; // 更新信号编号
fsr_info[nr].code = code; // 更新信号附加代码
fsr_info[nr].name = name; // 更新描述字符串
}
exceptions_init: 为特定的ARM架构版本注册异常处理钩子
此函数是在内核启动的早期阶段被调用的一个初始化函数。它的核心作用是为特定类型的硬件异常(在此代码片段中特指ARMv6和ARMv7架构引入的一些内存相关的"faults")注册相应的处理函数。通过调用hook_fault_code
,它将一个特定的故障码与一个内核处理函数(如do_translation_fault
或do_bad
)以及一个需要发送给用户空间进程的信号(如SIGSEGV
)关联起来。
在单核无MMU的STM32H750平台上的原理与作用
STM32H750
使用的是 ARM Cortex-M7
内核, 其架构是 ARMv7-M 。这是一个"M" profile(微控制器配置文件)的架构, 它没有MMU, 但通常配备有MPU(内存保护单元)。
- 架构匹配 : 函数中的
cpu_architecture() >= CPU_ARCH_ARMv7
这个条件对于STM32H750是成立的。 - 异常类型差异 : 然而, 代码中提到的"fault code"是针对ARM的"A"和"R" profile(应用和实时配置文件)架构的, 这些架构有MMU, 其内存管理和异常模型与"M" profile有显著不同 。
- "A"/"R" profile的CPU在发生内存访问错误时会生成
Data Abort
或Prefetch Abort
异常, 并提供一个故障状态寄存器(FSR)来指示具体的错误原因, 其编码就是代码中的3
,4
,6
等。do_translation_fault
和section access flag fault
这些错误类型都与MMU的页表转换和权限检查紧密相关。 - ARMv7-M架构则有一套不同的异常模型。它使用
UsageFault
,BusFault
, 和MemManageFault
来处理类似但不同的错误情况(如MPU权限冲突, 非法指令, 无效内存访问等)。这些异常有自己独立的、与A/R profile完全不同的状态寄存器和错误编码。
- "A"/"R" profile的CPU在发生内存访问错误时会生成
CONFIG_ARM_LPAE
: 这个宏代表"大物理地址扩展", 是64位寻址的一部分, 与ARMv7-M无关。
结论 : exceptions_init
这个函数本身是为了带有MMU的ARMv7-A/R架构设计的 。在一个为STM32H750 (ARMv7-M) 正确配置的Linux内核中, 会使用一套完全不同的异常处理初始化代码来设置UsageFault
, BusFault
, MemManageFault
的处理程序。因此, 这个特定的exceptions_init
函数片段将不会被编译进一个标准的STM32H750内核中 , 即使它的#ifndef
条件成立。如果由于配置错误它被包含了, 它所注册的钩子也将永远不会被触发, 因为ARMv7-M的硬件根本不会产生这些故障码。
c
static int
do_translation_fault(unsigned long addr, unsigned int fsr,
struct pt_regs *regs)
{
return 0;
}
/*
* This abort handler always returns "fault".
*/
static int
do_bad(unsigned long addr, unsigned int fsr, struct pt_regs *regs)
{
return 1;
}
/*
* 这个条件编译指令表示, 只有在内核没有配置ARM大物理地址扩展(LPAE)时,
* 下面的代码才会被编译. LPAE是ARMv8/ARMv7的一部分, 用于支持大于32位的物理地址空间.
* ARMv7-M架构 (如STM32H750) 不支持LPAE, 所以这个条件通常为真.
*/
#ifndef CONFIG_ARM_LPAE
/*
* 这是一个静态的初始化函数.
* __init 宏表示此函数仅在内核启动期间执行.
*/
static int __init exceptions_init(void)
{
/*
* 检查CPU的架构版本是否大于或等于ARMv6.
* ARMv7-M (STM32H750) 是 ARMv7 的一个profile, 所以这个条件为真.
*/
if (cpu_architecture() >= CPU_ARCH_ARMv6) {
/*
* hook_fault_code 是一个函数, 用于将一个故障码(fault code)与一个处理程序关联起来.
*
* 参数1, 4:
* 这是ARMv6/v7 A/R profile架构中, 由数据中止(Data Abort)异常产生的故障码,
* 特指 "I-cache maintenance fault" (指令缓存维护故障).
* ARMv7-M 架构不产生此故障码.
*
* 参数2, do_translation_fault:
* 一个指向处理函数的指针, 该函数负责处理与MMU页表转换相关的故障.
*
* 参数3, SIGSEGV:
* 一个信号编号, 表示如果故障发生在用户模式, 应该向该进程发送段错误信号.
*
* 参数4, SEGV_MAPERR:
* 信号的附加代码, 表示这是一个地址未映射的错误.
*
* 参数5, "I-cache maintenance fault":
* 一个描述此故障的字符串, 用于打印调试信息.
*/
hook_fault_code(4, do_translation_fault, SIGSEGV, SEGV_MAPERR,
"I-cache maintenance fault");
}
/*
* 检查CPU的架构版本是否大于或等于ARMv7.
* 对于STM32H750, 这个条件也为真.
*/
if (cpu_architecture() >= CPU_ARCH_ARMv7) {
/*
* TODO注释翻译: ARMv6K中引入了访问标志位故障. 需要在运行时检查'K'扩展.
*/
/*
* 注册故障码 3 和 6 的处理程序.
* 在ARMv7 A/R profile中, 这两个故障码都表示 "section access flag fault"
* (段访问标志位故障), 这是MMU在检查段描述符中的访问权限位时发现的错误.
* ARMv7-M 架构没有段描述符, 也不产生这些故障码.
*
* do_bad 是一个通用的、用于处理未明确分类的严重错误的函数.
*/
hook_fault_code(3, do_bad, SIGSEGV, SEGV_MAPERR,
"section access flag fault");
hook_fault_code(6, do_bad, SIGSEGV, SEGV_MAPERR,
"section access flag fault");
}
/*
* 对于 initcall, 返回0表示初始化成功.
*/
return 0;
}
/*
* 使用 arch_initcall() 宏将 exceptions_init 函数注册为级别为 "3" 的初始化调用.
* 这确保了异常处理的钩子在内核启动的早期、架构相关的设置阶段就已经被建立.
*/
arch_initcall(exceptions_init);
#endif /* !CONFIG_ARM_LPAE */