PowerPC E500 MMU详解
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了PowerPC E500 MMU详解相关的知识,希望对你有一定的参考价值。
参考技术A 1. 什么是MMU?MMU 是 Memory Management Unit 的缩写,中文名称为 内存管理单元 。MMU是负责处理器(CPU)的内存访问请求的计算机硬件,其功能包括虚拟地址到物理地址的转换(即虚拟内存管理)、内存保护、缓存cache的控制等。
MMU是一个与软件密切相关的硬件部件,也是运行linux等依赖MMU内存管理单元操作系统内核机制的最大部件之一。如果处理器没有MMU,CPU内部执行单元产生的内存地址信号将直接通过地址总线发送到芯片引脚,被内存芯片接收,这就是 物理地址(physical address),简称PA 。英文physical代表物理的接触,所以PA就是与内存芯片physically connected的总线上的信号。
如果MMU存在且启用,CPU执行单元产生的地址信号在发送到内存芯片之前将被MMU截获,这个地址信号称为 虚拟地址(virtual address),简称VA ,MMU会负责把VA翻译成另一个地址,然后发到内存芯片地址引脚上,即VA映射成PA。
2. PowerPC E500 MMU
PowerPC (Performance Optimization With Enhanced RISC – Performance Computing,有时简称PPC)是一种 精简指令集(RISC)架构的中央处理器(CPU) ,其基本的设计源自IBM(国际商用机器公司)的POWER(Performance Optimized With Enhanced RISC)。
PowerPC芯片凭借其出色的性能和高度整合和技术先进特性在网络通信应用,工业控制应用,家用数字化,网络存储领域,军工领域,电力系统控制等都具有非常广泛的应用。
PowerPC的E500是飞思卡尔(FreeScale)基于Power Architecture的32位微处理器核心。E500系列核心有三个版本,即 E500v1,E500v2的E500mc 。64位版本的E500mc演变为E5500核心,并于2010年推出。
PowerPC E500 核心(后简称“E500”)采用了两级内存管理单元(MMU)体系结构。 L1-MMU、L2-MMU为该结构中的两个硬件单元。 E500通过L1-MMU和L2-MMU,以及一些辅助寄存器和指令来实现虚实地址的转换。E500v1核心是把32位有效地址转换为32位物理地址(使用41位临时虚拟地址);E500v2核心是把32位有效地址转换为36位物理地址。
L1-MMU特性:
1. 两个4条目的全关联TLB数组(一个用于指令访问,一个用于数据访问支持9种(E500v1)或11种(E500v2)页面大小,即I-L1VSP和D-L1VSP
2. 两个64 entry, 4路set-associative TLB数组(一个用于指令访问,一个用于数据访问),仅支持4kbyte页面,即I-L1TLB4K和D-L1TLB4K。
3. L1 MMU访问与L1缓存访问(地址转换/L1缓存)并行访问可以完全流水线化,这样每个时钟都可以完成一个加载/存储。
4. 执行L1 TLB查找的指令访问与数据访问是并行的。
5. 所有L1 TLB条目都是驻留在L2 MMU中的TLB条目的一个适当子集。
6. 自动执行失效操作,以保持L2 TLB的一致性。
L2-MMU特性:
1. 16个条目,完全关联的统一的L2 TLB数组(用于指令和数据访问)TLB1支持9种(E500v1)或11种(E500v2)页面大小,即TLB1,用于段式映射。
2. 256表项2-way (E500v1)或512-entry 4-way (E500v2)集关联统一L2 TLB array (TLB0)仅支持4kbyte的页面,即TLB0,用于进行页式映射。
3.对 TLB miss异常的支持。
4. TLB1和TLB0由tlbre, tlbwe, tlbsx, tlbsync, tlbivax和mtspr指令进行管理。
5. 执行由当前核心tlbivax指令产生的TLB1和TLB0失效。支持TLB1和TLB0由其他核心执行的tlbivax指令导致的失效操作。
6. TLB1中实现的IPROT位防止了无效,从而保护关键表项(通过设置IPROT位来指定)不会失效。
2.1 Effective-to-Real Address Translation流程
Effective-to-Real Address Translation(虚拟地址到物理地址的翻译)流程如下所示:
因为E500最小页的大小是4 Kbytes,所以低12位通常被用做4K页内的索引并且不需要进行翻译。首先检查L1 MMU(指令或数据)是否命中地址翻译。如果没有,翻译请求将被转发到(指令和数据)L2 MMU中进行处理。
E500v1核心和E500v2核心支持的页大小可见下表,E500v2核心比E500v1核心多支持了1Gbyte和4Gbyte页的大小。
2.2 MMU entry字段
在TLB中,数据都是按行存放的,一行称之为一个entry。一个entry包含了若干bit位下面是MMU entry的bit位含义描述:
V:用来表示当前entry是否有效。为0时表示无效,为1时表示有效。在系统reset之后,所有entry的V位都设为0。
TS:上面地址空间中的AS。为0表示地址空间0,为1表示地址空间1。
TID[0:7]:上面地址空间中的PID。TID为0时,忽略PID的比较。在Linux中,全为0。
EPN[0:19]:虚拟页桢号。根据页大小不同,有效的位数页不同。
RPN[0:19]:物理页桢号。
SIZE[0:3]:表示当前entry的页大小。
PERMIS[0:5]:用于描述当前entry的访问控制位。分别是UR,SR,UW,SW,UX,SX。分别表示用户态和内核态的读/写/执行权限。
WIMGE[0:4]:W表示当前entry是write through还是write back。I表示当前entry是否忽略Cache。M表示对此数据区的访问需要进行存储一致性处理。G表示对相应存储区域进运行保护。E表示当前entry是小端还是大端。
X0,X1:描述一些额外属性。
U[0:3]:自定义用途。
IPROT:当IPROT为1时,表示当前entry被保护。不能使用tlbivax指令无效该entry。只能用tlbwe指令清除。在E500中只有TLB1支持该位,在TLB0中,该位一直为0。
TLB相关指令、寄存器和异常如下表所示:
2.3 MMU相关寄存器
PID0-2寄存器: 用于保存当前进程的PID,该寄存器只有54-63位有效。
MMUCSR0寄存器: 用于使TLB0和TLB1的所有entry无效。该寄存器L2TLB0_FI位使无效TLB0的所有entry;L2TLB1_FI位使无效TLB1的所有entry。
MMUCFG寄存器: 用来保存当前MMU的配置信息,包括PID寄存器数量,大小,TLB的数量等。
TLB0CFG和TLB1CFG寄存器: 用于描述TLB0和TLB1的配置信息,包括entry数量,页表大小等信息。
MAS辅助寄存器 (MAS0–MAS4, MAS6–MAS7,没有实现MAS5),主要作用就是维护MMU中TLB的entry。
MAS0寄存器: 决定写哪个TLB的哪个entry。TLBSEL字段用于选择要操作的TLB,0表示TLB0,1表示TLB1。ESEL字段用于选择TLB的entry。当使用TLB1 时,ESEL字段中低4位有效,用于选择TLB1中的entry。当使用TLB0时,ESEL中只有最低位有效,因为TLB0是两路组相连。NV位用于确定如何替换TLB0中的entry。
MAS1-3中存放的各个字段与TLB entry中的各个字段一一对应。
MAS4寄存器
该寄存器主要用于存放当TLB miss异常发生时,对MAS0-3寄存器自动加载而使用的默认值,也就是说MAS4寄存器的内容是硬件写入的。为了提高TLB miss异常的效率,在异常出现时,E500会用MAS4寄存器中的值,自动填写MAS0-2寄存器中的一部分内容:
MAS0[TLBSEL] <- MAS4[TLBSELD]
MAS1[TID] <- MAS4[TIDSELD]
MAS1[TSIZE] <- MAS4[TSIZED]
MAS2[X0,X1] <- MAS4[X0D,X1D]
MAS2[WIMGE] <- MAS4[WD,ID,MD,GD,ED]
MAS6寄存器: 用于对TLB进行检索,MAS6寄存器提供SPID0和SAS,也就是PID和AS字段。
MAS7寄存器(只E500v2支持): 包含实现的RPN的高阶地址位支持超过32位的物理地址。
2.4 TLB相关指令和实现
SkyEye,中文全称天目全数字实时仿真软件 ,是基于可视化建模的硬件行为级仿真平台,支持用户通过拖拽的方式对硬件进行行为级别的仿真和建模。SkyEye支持PowerPC架构E500处理器的指令集仿真,为了支持运行包含MMU单元的操作系统(例如VxWorks、Linux等),需要实现对MMU的仿真功能,其中包含TLB(Translation Lookaside Buffer,转换检测缓冲区)相关的指令实现和MMU虚实翻译过程。
与TLB相关的指令如下:
tlbre指令: tlbre指令是从L2 MMU中读取单个TLB条目的各字段内容,并写入到MMU辅助(MAS)寄存器的相应字段中。读取的entry条目是由MAS0的TLBSEL、ESEL和MAS2寄存器的EPN字段指定,在读TLB1时,ESEL有效,EPN无效;在读TLB0时,ESEL高2位有效,EPN有效。注意,对于E500v2,如果HID0[EN_MAS7_UPDATE] = 1, MAS7也被更新为TLB表项的物理地址。
tlbwe指令: 该指令是从MMU辅助寄存器(MAS)中提取各字段内容并写入到L2 MMU中的单个TLB条目中。写入的entry条目是由MAS0的TLBSEL、ESEL和MAS2寄存器的EPN字段指定,在写TLB1时,ESEL有效,EPN无效;在写TLB0时,ESEL高2位有效,EPN有效。注意,对于E500v2,MAS7中的RPN字段也写入到选定的TLB条目中 。
tlbsx指令: 指令格式为tlbsx RA,RB。指令作用是使用RA+RB的地址,配合MAS6寄存器对TLB进行查找,如果命中,则将结果放入MAS0-3寄存器。
指令实现:
tlbivax指令: TLB无效操作,这条指令将使所有与此计算出的虚拟地址相对应的TLB表项失效,同样也会使其他处理器上TLB中包含的TBL表项失效。
根据有效地址EA的Bit61位决定是否是无效全部的entry,如果不是,计算出相对应的entry,然后设置entry->v = 0用于使表项失效。
tlbsync指令: 该指令用于同步对TLB entry的读写。主要用途是将tlbivax指令的更新entry广播到系统总线上,用来同步其他处理器,该指令只在SMP系统中有效。
2.5 系统复位后的TLB初始状态
复位后,L1和L2 MMU中的TLB表项全部失效,TLB1的第一个entry需要初始化赋值,如下所示:
参考文献:
[1] PowerPC™ e500 Core Family Reference Manual.pdf
u-boot启动流程详解-基于iTop4412开发板
前言
u-boot的作用:CPU上电后,需要设置很多状态,包括CPU状态、中断状态、MMU状态等,其次要做的就是对硬件资源经行板级初始化、代码重定向等,最后若不进入命令行模式,就会将linux内核从flash(NAND,NOR FLASH,SD,MMC等)拷贝到DDR中,最后启动linux内核。
4412 u-boot启动流程:
A.开机运行iRom中代码
B.BL1阶段(E4412_N.bl1.xxxxG.bin:8KB,三星提供的bin文件,没有源码)
C.BL2(SPL)阶段(bl2.bin :16KB,uboot启动第一阶段代码,在uboot中称为SPL阶段)
D.uboot第二阶段代码(u-boot.bin :小于512KB,uboot启动第二阶段代码)
接下来,我就会按以上流程逐步开始分析uboot启动流程。
1.开机运行iRom中代码
4412内部存在一个大小位64Kb的iRom,物理内存首地址为0x0000_0000。ARM芯片启动时从物理地址0x0000_0000的存储介质中取第一条指令开始运行。三星在iROM中固化了一段启动代码,因此,芯片上电时首先运行iROM中的这段代码。这段代码主要做以下4个工作:
1. 初始化出程序运行的基本环境,比如关闭看门狗、设置栈以及初始化时钟等
2. 读取OM脚判断uboot的启动介质(内部EMMC、外部SD/MMC卡等)
3. 从启动介质加载BL1阶段代码到iRAM中特定的地址(0x0202_1400)处,4412的iRAM有256Kb,起始物理地址是0x0202_0000
4. iROM中的启动代码对BL1代码做完整性校验,校验通过后,跳转到iRAM中的BL1运行
2.BL1阶段
BL1阶段主要做如下工作:
1. 从启动介质拷贝BL2代码(SPL阶段)到iRAM的0x0202_3400处,因此在编译uboot时,BL2的链接地址需要设置为0x0202_3400
./include/configs/itop4412.h
/* MMC SPL */
#define COPY_BL2_FNPTR_ADDR 0x02020030
#define CONFIG_SPL_TEXT_BASE 0x02023400 /* 0x02021410 */
2. 校验BL2的完整性,通过后跳到BL2运行
在BL2中有两个数据Checksum和Signature,BL1对BL2的校验就是检查这两个数据。BL2有效程序必须小于14332B,14KB-4B的地方用于存放Checksum,14KB的地方用于存放Signature
Uboot2015中,编译出uboot的第一阶段SPL后,使用board/Samsung/itop4412/tools/mkitop4412spl.c计算校验和放到14KB-4B的地方。
编译SPL阶段的脚本文件scripts/Makefile.spl中调用mkitop4412spl
3.SPL阶段
至此uboot开始运行,BL2在iRAM中运行,主要的工作是:
1. 初始化时钟、初始化串口、初始化动态内存DRAM
2. 拷贝uboot第二阶段代码到DRAM中,然后跳转到DRAM中执行uboot第二阶段代码
详细过程见下段描述:
3.1.从链接脚本u-boot.lds开始
前言: u-boot.lds -找到u-boot启动入口的钥匙
程序的链接是由链接脚本来决定的,所以通过链接脚本可以找到程序的入口。uboot在未编译之前,可以在uboot代码里的arch/arm/cpu/下找到u-boot.lds,在编译后会基于这个.lds文件,生成最终使用的.lds,4412的lds源码如下:
OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm")
OUTPUT_ARCH(arm)
ENTRY(_start)
SECTIONS
. = 0x00000000;
. = ALIGN(4);
.text :
cpu/arm_cortexa9/start.o (.text)
cpu/arm_cortexa9/s5pc210/cpu_init.o (.text)
board/samsung/smdkc210/lowlevel_init.o (.text)
common/ace_sha1.o (.text)
*(.text)
. = ALIGN(4);
.rodata : *(SORT_BY_ALIGNMENT(SORT_BY_NAME(.rodata*)))
. = ALIGN(4);
.data : *(.data)
. = ALIGN(4);
.got : *(.got)
__u_boot_cmd_start = .;
.u_boot_cmd : *(.u_boot_cmd)
__u_boot_cmd_end = .;
. = ALIGN(4);
__bss_start = .;
.bss : *(.bss)
_end = .;
根据第3行可以确定SPL阶段的入口点是_start。_start在源码arch/arm/lib/vectors.S中有定义:
*
*************************************************************************
*
* 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
SPL阶段的入口代码使用汇编写的,从_start入口点开始,进入复位中断向量执行点reset,开始设置程序运行的基本环境,比如将CPU设置为SVC模式、设置栈、关闭看门狗等;然后进入第一个c函数board_init_f,这个函数中会调用do_lowlevel_init初始化SOC内部的组件,比如系统时钟、串口、DRAM等,然后再调用copy_uboot_to_ram将uboot第二阶段代码拷贝到DRAM,并跳转到uboot第二阶段执行,详细调用过程如下图所示:
4.uboot第二阶段代码
uboot第二阶段的入口点和SPL阶段的入口点是一样的,都是从Arch/arm/lib/ vectors.S的_start进入arch/arm/cpu/armv7/start.S的reset。
SPL阶段已经完成了SOC内部各组件的初始化工作,第二阶段uboot会初始化SOC外部的一些组件比如存储设备EMMC/SD、网卡等,然后会实现uboot代码的主体功能比如环境变量、命令行、启动内核等功能。
以下会对流程进行详细分析:
4.1 arch/arm/lib/vectors.S
_start->reset
工作:uboot第二阶段入口点;跳转到reset
4.2 arch/arm/cpu/armv7/start.S
reset
工作:禁用中断,设置CPU为SVC模式(ARM处理器7种工作模式之一,系统复位或开机、软中断时进入到SVC模式);跳转到_main
4.3 arch/arm/lib/crto.S
_main
工作:
1.在DRAM中设置栈
2.跳转到 board_init_f(uboot启动中的第一个c文件)
4.3.1 commom/board_f.c
直接看源码
void board_init_f(ulong boot_flags)
gd->flags = boot_flags;
gd->have_console = 0;
if (initcall_run_list(init_sequence_f))
hang();
#if !defined(CONFIG_ARM) && !defined(CONFIG_SANDBOX) && \\
!defined(CONFIG_EFI_APP) && !CONFIG_IS_ENABLED(X86_64)
/* NOTREACHED - jump_to_copy() does not return */
hang();
#endif
可以看到函数最主要的语句就是initcall_run_list(init_sequence_f),init_sequence_f是一个函数指针数组,因为存在很多预编译条件判断就不再这里赘述源码了,感兴趣的同学可以直接看源码。init_sequence_f数组里面放了很多初始化gd结构体的函数指针,重要的几个函数如下:
1.int reloc_fdt:重定位设备树
static int reloc_fdt(void)
#ifndef CONFIG_OF_EMBED
if (gd->flags & GD_FLG_SKIP_RELOC)
return 0;
if (gd->new_fdt)
memcpy(gd->new_fdt, gd->fdt_blob, gd->fdt_size);
gd->fdt_blob = gd->new_fdt;
#endif
return 0;
2.env_init初始化env
int env_init(void)
struct env_driver *drv = env_driver_lookup_default();
int ret = -ENOENT;
if (!drv)
return -ENODEV;
if (drv->init)
ret = drv->init();
if (ret == -ENOENT)
gd->env_addr = (ulong)&default_environment[0];
gd->env_valid = ENV_VALID;
return 0;
else if (ret)
debug("%s: Environment failed to init (err=%d)\\n", __func__,
ret);
return ret;
return 0;
3.dram_init:gd->bd中关于DDR配置部分全局变量的赋值(大小 起始地址 等)
int dram_init(void)
/* We do not initialise DRAM here. We just query the size */
gd->ram_size = query_sdram_size();
return 0;
......受限于篇幅,对init_sequence_f函数组感兴趣的小伙伴可以直接去看一下源码。
4.3.2 跳转 relocate
b relocate_code
工作:代码段重定位,因为之前已经重定位过,所以该函数判断完条件后直接返回
4.3.3 跳转 relocate_vectors
b relocate_vectors
工作:重定位中断向量表
4.3.4 清零bss段
bss段通常是指用来存放程序中未初始化的或者初始化为0的全局变量和静态变量的一块内存区域。特点是可读写的,感兴趣的小伙伴可以深入了解一下!
4.3.5 调用board_init_r
/* call board_init_r(gd_t *id, ulong dest_addr) */
mov r0, r9 /* gd_t */
ldr r1, [r9, #GD_RELOCADDR] /* dest_addr */
/* call board_init_r */
#if CONFIG_IS_ENABLED(SYS_THUMB_BUILD)
ldr lr, =board_init_r /* this is auto-relocated! */
bx lr
#else
ldr pc, =board_init_r /* this is auto-relocated! */
#endif
/* we should not return here. */
#endif
之前讲解了 board_init_f 函数,在此函数里面会调用一系列的函数来初始化一些外设和 gd 的成员变量。但是 board_init_f 并没有初始化所有的外设,还需要做一些后续工作,这些后续工作就是由函数 board_init_r 来完成的,board_init_r 函数定义在文件 common/board_r.c中:
void board_init_r(gd_t *new_gd, ulong dest_addr)
/*
* Set up the new global data pointer. So far only x86 does this
* here.
* TODO(sjg@chromium.org): Consider doing this for all archs, or
* dropping the new_gd parameter.
*/
#if CONFIG_IS_ENABLED(X86_64)
arch_setup_gd(new_gd);
#endif
#ifdef CONFIG_NEEDS_MANUAL_RELOC
int i;
#endif
#if !defined(CONFIG_X86) && !defined(CONFIG_ARM) && !defined(CONFIG_ARM64)
gd = new_gd;
#endif
#ifdef CONFIG_NEEDS_MANUAL_RELOC
for (i = 0; i < ARRAY_SIZE(init_sequence_r); i++)
init_sequence_r[i] += gd->reloc_off;
#endif
if (initcall_run_list(init_sequence_r))
hang();
/* NOTREACHED - run_main_loop() does not return */
hang();
第26行,调用 initcall_run_list 函数来执行初始化序列 init_sequence_r,init_sequence_r 是一个函数集合,init_sequence_r 也定义在文件 common/board_r.c 中,由于 init_sequence_f 的内容比较长,里面有大量的条件编译代码,受限于篇幅,也像之前分析最具代表性的几个。
1.initr_serial:初始化传串口
static int initr_serial(void)
serial_initialize();
return 0;
2.interrupt_init:初始化中断
int interrupt_init (void)
/*
* setup up stacks if necessary
*/
IRQ_STACK_START_IN = gd->irq_sp + 8;
return 0;
3.initr_enable_interrupts:使能中断
static int initr_enable_interrupts(void)
enable_interrupts();
return 0;
4.initr_env:初始化环境变量
static int initr_env(void)
/* initialize environment */
if (should_load_env()) // 从指定设备加载环境变量,并验证有效性
env_relocate();
else
set_default_env(NULL); // 失败使用默认的
#ifdef CONFIG_OF_CONTROL
env_set_addr("fdtcontroladdr", gd->fdt_blob);
#endif
/* Initialize from environment */
load_addr = env_get_ulong("loadaddr", 16, load_addr);
return 0;
5.run_main_loop:主循环,处理命令
static int run_main_loop(void)
#ifdef CONFIG_SANDBOX
sandbox_main_loop_init();
#endif
/* main_loop() can return to retry autoboot, if so just run it again */
for (;;)
main_loop();
return 0;
uboot 启动以后会进入 3 秒倒计时,如果在 3 秒倒计时结束之前按下按下回车键,那么就
会进入 uboot 的命令模式,如果倒计时结束以后都没有按下回车键,那么就会自动启动 Linux 内
核,这个功能就是由 run_main_loop 函数来完成的。
6.main_loop:
main_loop()在common/main.c
/* We come here after U-Boot is initialised and ready to process commands */
void main_loop(void)
const char *s;
bootstage_mark_name(BOOTSTAGE_ID_MAIN_LOOP, "main_loop"); // 打印启动进度
#ifdef CONFIG_VERSION_VARIABLE
env_set("ver", version_string); /* set version variable,cmd/version.c */
#endif /* CONFIG_VERSION_VARIABLE */
cli_init(); // 初始化 hushshell 相关的变量
run_preboot_environment_command(); // 获取环境变量 perboot 的内容,perboot是一些预启动命令,一般不使用这个环境变量
#if defined(CONFIG_UPDATE_TFTP)
update_tftp(0UL, NULL, NULL);
#endif /* CONFIG_UPDATE_TFTP */
s = bootdelay_process();
if (cli_process_fdt(&s))
cli_secure_boot_cmd(s);
autoboot_command(s); // 此函数就是检查倒计时是否结束、被打断
cli_loop(); // uboot 的命令行处理函数
panic("No CLI available");
结尾
经过以上四个阶段,uboot便走完了它的一生,接下来便是linux的启动!
参考
1.https://www.cnblogs.com/lztutumo/p/13233094.html 番茄大佬的uboot启动流程,写得比较好,可以看看!
以上是关于PowerPC E500 MMU详解的主要内容,如果未能解决你的问题,请参考以下文章