[Linux]学习笔记系列 -- [arm][mm]

文章目录

  • 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的实现)
  • 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_*))
  • 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架构版本注册异常处理钩子)

https://github.com/wdfk-prog/linux-study

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函数的主要作用不再是进行复杂的地址翻译, 而是承担了两个至关重要的角色:

  1. 提供API兼容性: 它为设备驱动程序提供了一个与有MMU的系统完全相同的、标准化的API。这使得为全功能Linux编写的驱动程序可以几乎不加修改地被重新编译并运行在无MMU的系统上, 极大地增强了代码的可移植性。
  2. 强制类型安全和正确的访问方式 : 函数返回一个特殊的指针类型 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_infoifsr_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_faultdo_bad)以及一个需要发送给用户空间进程的信号(如SIGSEGV)关联起来。

在单核无MMU的STM32H750平台上的原理与作用

STM32H750 使用的是 ARM Cortex-M7 内核, 其架构是 ARMv7-M 。这是一个"M" profile(微控制器配置文件)的架构, 它没有MMU, 但通常配备有MPU(内存保护单元)。

  1. 架构匹配 : 函数中的cpu_architecture() >= CPU_ARCH_ARMv7这个条件对于STM32H750是成立的。
  2. 异常类型差异 : 然而, 代码中提到的"fault code"是针对ARM的"A"和"R" profile(应用和实时配置文件)架构的, 这些架构有MMU, 其内存管理和异常模型与"M" profile有显著不同
    • "A"/"R" profile的CPU在发生内存访问错误时会生成Data AbortPrefetch Abort异常, 并提供一个故障状态寄存器(FSR)来指示具体的错误原因, 其编码就是代码中的 3, 4, 6 等。do_translation_faultsection access flag fault这些错误类型都与MMU的页表转换和权限检查紧密相关。
    • ARMv7-M架构则有一套不同的异常模型。它使用UsageFault, BusFault, 和 MemManageFault来处理类似但不同的错误情况(如MPU权限冲突, 非法指令, 无效内存访问等)。这些异常有自己独立的、与A/R profile完全不同的状态寄存器和错误编码。
  3. 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 */