本文是一篇翻译的文章,翻译自《ARM Cortex-A Series Programmer’s Guide》第13章Boot Code。翻译可能存在纰漏,仅供参考,请对照原文阅读。
文档来自https://developer.arm.com/,由于在移植Linux内核到exynos4412芯片上时,遇见了一些问题,所以决定查找一下官方的资料,顺便翻译一下。
Chapter 13 Boot Code
This chapter considers the boot code running in an ARM processor based system, and focuses on two distinct areas:
本章考虑了在基于ARM处理器的系统中运行的引导代码,并着重于两个不同的领域:
- Code to be run immediately after the core comes out of reset, on a so-called bare-metal system, that is, one in which code is run without the use of an operating system. This is a situation that is often encountered when first starting up a chip or system.
- CPU核心退出复位后立即在所谓的裸机系统上运行代码,即在不使用操作系统的情况下运行代码。 首次启动芯片或系统时经常会遇到这种情况。
- How a bootloader loads and runs the Linux kernel.
- 引导加载程序如何加载和运行Linux内核。
13.1 Booting a bare-metal system
13.1引导裸机系统
When the core has been reset, it will commence execution at the location of the reset vector in the exception vector table (at either address 0x00000000 or 0xFFFF0000, see Table 11-3 on page 11-9). The reset handler code must do some, or all of the following:
核心复位后,它将在异常向量表中的复位向量的位置(地址0x00000000或0xFFFF0000,请参见第11-9页的表11-3)开始执行。 重置处理程序代码必须执行以下某些或全部操作:
- In a multi-core system, enable non-primary cores to sleep See Booting SMP systems on
page 18-17. - 在多核系统中,使非主核进入睡眠状态,请参阅第18-17页的引导SMP系统。
- Initialize exception vectors.
- 初始化异常向量。
- Initialize the memory system, including the MMU.
- 初始化存储系统,包括MMU。
- Initialize core mode stacks and registers.
- 初始化核心模式堆栈和寄存器。
- Initialize any critical I/O devices.
- 初始化所有关键的I / O设备。
- Perform any necessary initialization of NEON or VFP.
- 执行NEON或VFP的任何必要的初始化。
- Enable interrupts.
- 启用中断。
- Change core mode or state.
- 更改核心模式或状态。
- Handle any set-up required for the Secure world (see Chapter 21).
- 处理安全世界所需的任何设置(请参阅第21章)。
- Call the main() application.
- 调用main()应用程序。
The first consideration is placement of the exception vector table. You must make sure that it contains a valid set of instructions that branch to the appropriate handlers.
首先要考虑的是异常向量表的位置。 您必须确保它包含一组分支到适当处理程序的有效指令。
The _start directive in the GNU Assembler tells the linker to locate code at a particular address and can be used to place code in the vector table. The initial vector table will be in non-volatile memory and can contain branch to self instructions (other than the reset vector) as no exceptions are expected at this point. Typically, the reset vector contains a branch to the boot code in ROM. The ROM can be aliased to the address of the exception vector. The ROM then writes to some memory-remap peripheral that maps RAM into address 0 and the real exception vector table is copied into RAM. This means the part of the boot code that handles remapping must be position-independent, as only PC-relative addressing can be used. Example 13-1 shows an example of typical code that can be placed in the exception vector table.
GNU汇编器中的_start指令告诉链接器将代码定位在特定地址,并可用于将代码放置在向量表中。 初始向量表将位于非易失性存储器中,并且可以包含自身指令的分支(复位向量除外),因为此时不希望出现异常。 通常,复位向量包含ROM中引导代码的分支。 ROM可以别名为异常向量的地址。 然后,ROM写入一些将RAM映射到地址0的内存重映射外围设备,然后将实际的异常向量表复制到RAM中。 这意味着引导代码中用于重映射的部分必须与位置无关,因为只能使用PC相对寻址。 例13-1给出了可以放在异常向量表中的典型代码的示例。
Example 13-1 Typical exception vector table code
start
B Reset_Handler
B Undefined_Handler
B SWI_Handler
B Prefetch_Handler
B Data_Handler
NOP @ Reserved vector
B IRQ_Handler
@ FIQ_Handler will follow directly after this table
@FIQ_Handler将紧随此表之后
You might then have to initialize stack pointers for the various modes that your application can make use of. Example 13-2 on page 13-3 gives a simple example, showing code that initializes the stack pointers for FIQ and IRQ modes.
然后,您可能必须为应用程序可以使用的各种模式初始化堆栈指针。 第13-3页的示例13-2提供了一个简单的示例,其中显示了初始化FIQ和IRQ模式的堆栈指针的代码。
Example 13-2 Code to initialize the stack pointers
LDR R0, stack_base
@ Enter each mode in turn and set up the stack pointer
MSR CPSR_c, #Mode_FIQ:OR:I_Bit:OR:F_Bit ;
MOV SP, R0
SUB R0, R0, #FIQ_Stack_Size
MSR CPSR_c, #Mode_IRQ:OR:I_Bit:OR:F_Bit ;
MOV SP, R0
The next step is to set up the caches, MMU and branch predictors. An example of such code is shown in Example 13-3. We begin by disabling the MMU and caches and invalidating the caches and TLB. This example code is for the Cortex-A9 processor. Some of the Cortex-A processors automatically invalidate the L1 and/or L2 caches at reset, others require manual invalidation. You must check the TRM for a particular core to determine which options have been implemented.
下一步是设置缓存,MMU和分支预测器。 例13-3给出了此类代码的示例。 我们首先禁用MMU和缓存,并使缓存和TLB无效。 该示例代码适用于Cortex-A9处理器。 某些Cortex-A处理器在重置时会自动使L1和/或L2缓存无效,而其他处理器则需要手动使无效。 您必须检查特定内核的TRM,以确定已实施了哪些选项。
The MMU TLBs must be invalidated. The branch target predictor hardware might not have to be explicitly invalidated, but it must be enabled by boot code. Branch prediction can safely be enabled at this point; this will improve performance.
MMU TLB必须无效。 分支目标预测变量硬件可能不必显式失效,但必须由引导代码启用。 此时可以安全地启用分支预测; 这将提高性能。
Example 13-3 Setting up caches, MMU and branch predictors
@ Disable MMU
MRC p15, 0, r1, c1, c0, 0 @ Read Control Register configuration data
BIC r1, r1, #0x1
MCR p15, 0, r1, c1, c0, 0 @ Write Control Register configuration data
@ Disable L1 Caches
MRC p15, 0, r1, c1, c0, 0 @ Read Control Register configuration data
BIC r1, r1, #(0x1 << 12) @ Disable I Cache
BIC r1, r1, #(0x1 << 2) @ Disable D Cache
MCR p15, 0, r1, c1, c0, 0 @ Write Control Register configuration data
@ Invalidate L1 Caches
@ Invalidate Instruction cache
MOV r1, #0
MCR p15, 0, r1, c7, c5, 0
@ Invalidate Data cache
@ to make the code general purpose, we calculate the
@ cache size first and loop through each set + way
MRC p15, 1, r0, c0, c0, 0 @ Read Cache Size ID
LDR r3, #0x1ff
AND r0, r3, r0, LSR #13 @ r0 = no. of sets - 1
MOV r1, #0 @ r1 = way counter way_loop
way_loop:
MOV r3, #0 @ r3 = set counter set_loop
set_loop:
MOV r2, r1, LSL #30 @
ORR r2, r3, LSL #5 @ r2 = set/way cache operation format
MCR p15, 0, r2, c7, c6, 2 @ Invalidate line described by r2
ADD r3, r3, #1 @ Increment set counter
CMP r0, r3 @ Last set reached yet?
BGT set_loop @ if not, iterate set_loop
ADD r1, r1, #1 @ else, next
CMP r1, #4 @ Last way reached yet?
BNE way_loop @ if not, iterate way_loop
@ Invalidate TLB
MCR p15, 0, r1, c8, c7, 0
@ Branch Prediction Enable
MOV r1, #0
MRC p15, 0, r1, c1, c0, 0 @ Read Control Register configuration data
ORR r1, r1, #(0x1 << 11) @ Global BP Enable bit
MCR p15, 0, r1, c1, c0, 0 @ Write Control Register configuration data
After this, you can create some translation tables, as shown in the example code of Example 13-4. The variable ttb_address is used to denote the address to be used for the initial translation table. This must be a 16KB area of memory (whose start address is aligned to a 16KB boundary), to which an L1 translation table can be written by this code.
之后,您可以创建一些转换表,如示例13-4的示例代码所示。 变量ttb_address用于表示初始转换表要使用的地址。 这必须是16KB的内存区域(其起始地址与16KB边界对齐),此代码可以向其中写入L1转换表。
Example 13-4 Create translation tables
@ Enable D-side Prefetch
MRC p15, 0, r1, c1, c0, 1 @ Read Auxiliary Control Register
ORR r1, r1, #(0x1 <<2) @ Enable D-side prefetch
MCR p15, 0, r1, c1, c0, 1 ; @ Write Auxiliary Control Register
DSB
ISB
@ DSB causes completion of all cache maintenance operations appearing in program
@ order before the DSB instruction
@ An ISB instruction causes the effect of all branch predictor maintenance
@ operations before the ISB instruction to be visible to all instructions
@ after the ISB instruction.
@ Initialize PageTable
@ We will create a basic L1 page table in RAM, with 1MB sections containing a flat
(VA=PA) mapping, all pages Full Access, Strongly Ordered
@ It would be faster to create this in a read-only section in an assembly file
LDR r0, =2_00000000000000000000110111100010 @ r0 is the non-address part of
descriptor
LDR r1, ttb_address
LDR r3, = 4095 @ loop counter
write_pte
ORR r2, r0, r3, LSL #20 @ OR together address & default PTE bits
STR r2, [r1, r3, LSL #2] @ write PTE to TTB
SUBS r3, r3, #1 @ decrement loop counter
BNE write_pte
@ for the very first entry in the table, we will make it cacheable, normal,
write-back, write allocate
BIC r0, r0, #2_1100 @ clear CB bits
ORR r0, r0, #2_0100 @ inner write-back, write allocate
BIC r0, r0, #2_111000000000000 @ clear TEX bits
ORR r0, r0, #2_101000000000000 @ set TEX as write-back, write allocate
ORR r0, r0, #2_10000000000000000 @ shareable
STR r0, [r1]
@ Initialize MMU
MOV r1,#0x0
MCR p15, 0, r1, c2, c0, 2 @ Write Translation Table Base Control Register
LDR r1, ttb_address
MCR p15, 0, r1, c2, c0, 0 @ Write Translation Table Base Register 0
@ In this simple example, we don't use TRE or Normal Memory Remap Register.
@ Set all Domains to Client
LDR r1, =0x55555555
MCR p15, 0, r1, c3, c0, 0 @ Write Domain Access Control Register
@ Enable MMU
MRC p15, 0, r1, c1, c0, 0 @ Read Control Register configuration data
ORR r1, r1, #0x1 @ Bit 0 is the MMU enable
MCR p15, 0, r1, c1, c0, 0 @ Write Control Register configuration data
The L2 cache, if present, and if running without an operating system, might also require invalidating and enabling at this point. NEON or VFP access must also be enabled. If the system makes use of the TrustZone Security Extensions, it might have to switch to the Normal world when the Secure world is initialized. See Chapter 21 Security for details of this.
L2缓存(如果存在)并且如果在没有操作系统的情况下运行,这时可能还需要使它们无效并启用。 还必须启用NEON或VFP访问。 如果系统使用TrustZone安全扩展,则在初始化安全世界时可能必须切换到“普通”世界。 有关详细信息,请参见第21章。
The next steps will depend on the exact nature of the system. It might be necessary, for example, to zero-initialize memory that will hold uninitialized C variables, copy the initial values of other variables from a ROM image to RAM, and set up application stack and heap spaces. It might also be necessary to initialize C library functions, call top-level constructors (for C++ code) and other standard embedded C initialization.
下一步将取决于系统的确切性质。 例如,可能有必要将存储未初始化的C变量的内存清零,将其他变量的初始值从ROM映像复制到RAM,并设置应用程序堆栈和堆空间。 可能还需要初始化C库函数,调用顶级构造函数(用于C ++代码)和其他标准的嵌入式C初始化。
A common approach is to permit a single core within the cluster to perform system initialization, while the same code, if run on a different core, will cause it to sleep, that is, enter WFI state, as described in Chapter 20. The other cores might be woken after core 0 has created a simple set of L1 translation table entries, as these could be used by all cores in the system. Example 13-5shows example code that determines which core it is running on and either branches to initialization code (if running on core 0), or goes to sleep otherwise. The secondary cores are typically woken up later by an SMP OS.
一种常见的方法是允许集群中的单个内核执行系统初始化,而相同的代码(如果在不同的内核上运行)将使其休眠,即进入WFI状态,如第20章所述。 在核心0创建了一组简单的L1转换表条目之后,可能会唤醒核心,因为这些可以被系统中的所有核心使用。 例13-5显示了示例代码,这些代码确定它在哪个内核上运行,并分支到初始化代码(如果在内核0上运行),否则进入休眠状态。 辅助内核通常在以后由SMP OS唤醒。
Example 13-5 Determining which core is running
@ Only CPU 0 performs initialization. Other CPUs go into WFI
@ to do this, first work out which CPU this is
@ this code typically is run before any other initialization step
MRC p15, 0, r1, c0, c0, 5 @ Read Multiprocessor Affinity Register
AND r1, r1, #0x3 @ Extract CPU ID bits
CMP r1, #0
BEQ initialize @ if we’re on CPU0 goto the start
wait_loop:
@ Other CPUs are left powered-down
.....
.....
.....
initialize:
@ next section of boot code goes here
13.2 Configuration
13.2配置
There are a number of control register bits within the core that will typically be set by boot code. In all cases, for best performance, code must run with the MMU, instruction and data caches and branch prediction enabled. Translation table entries for all regions of memory that are not peripheral I/O devices must be marked as L1 Cacheable and (by default) set to read-allocate, write-back cache policy. For multi-core systems, pages must be marked as Shareable and the broadcasting feature for CP15 maintenance operations must be enabled.
内核中有许多控制寄存器位,这些位通常由启动代码设置。 在所有情况下,为了获得最佳性能,代码必须在MMU,指令和数据缓存以及启用分支预测的情况下运行。 非外围I / O设备的所有内存区域的转换表条目都必须标记为L1可缓存,并且(默认情况下)设置为读分配,写回缓存策略。 对于多核系统,页面必须标记为“可共享”,并且必须启用CP15维护操作的广播功能。
In addition to the CP15 registers required by the ARM architecture, cores typically have registers that control implementation-specific features. Programmers of boot code should refer to the relevant technical reference manual for the correct usage of these.
除了ARM体系结构所需的CP15寄存器外,内核通常还具有控制实现特定功能的寄存器。 引导代码的程序员应参考相关的技术参考手册,以正确使用它们。
13.3 Booting Linux
13.3引导Linux
It is useful to understand what happens from the core coming out of reset and executing its first instruction at the exception base address 0x00000000 or 0xFFFF0000 if HIVECS (known as high vectors) is selected, until the Linux command prompt appears. (See The Vector table on page 11-7.)
如果选择了HIVECS(称为高向量),则了解从内核退出并在异常基地址0x00000000或0xFFFF0000处执行其第一条指令后,直到出现Linux命令提示符之前,内核会发生什么。 (请参阅第11-7页的向量表。)
When the kernel is present in memory, the sequence on an ARM processor based system is similar to what might happen on a desktop computer. However, the bootloading process can be very different, as ARM processor based phones or more deeply embedded devices can lack a hard drive or PC-like BIOS.
当内核存在于内存中时,基于ARM处理器的系统上的序列类似于台式计算机上可能发生的序列。 但是,引导加载过程可能非常不同,因为基于ARM处理器的电话或更深层嵌入式的设备可能缺少硬盘驱动器或类似PC的BIOS。
Typically, what happens when you power the system on is that hardware specific boot code runs from flash or ROM. This code initializes the system, including any necessary hardware peripheral code and then launches the bootloader (for example U-Boot). This initializes main memory and copies the compressed Linux kernel image into main memory (from a flash device, memory on a board, MMC, host PC or elsewhere). The bootloader passes certain initialization parameters to the kernel. The Linux kernel then decompresses itself and initializes its data structures and running user processes, before starting the command shell environment. Let’s take a more detailed look at each of those processes.
通常,在打开系统电源时发生的情况是,特定于硬件的启动代码从闪存或ROM运行。 该代码将初始化系统,包括所有必需的硬件外围设备代码,然后启动引导加载程序(例如U-Boot)。 这将初始化主内存,并将压缩的Linux内核映像复制到主内存中(从闪存设备,板上内存,MMC,主机PC或其他位置)。 引导加载程序将某些初始化参数传递给内核。 然后,Linux内核在启动命令外壳程序环境之前对其自身进行解压缩并初始化其数据结构并运行用户进程。 让我们更详细地研究每个过程。
13.3.1 Reset handler
13.3.1 重置处理程序
There is typically a small amount of system-specific boot monitor code that configures memory controllers and performs other system peripheral initialization. It sets up stacks in memory and typically copies itself from ROM into RAM, before changing the hardware memory mapping so that RAM is mapped to the exception vector address, rather than ROM. In essence this code is independent of that operating system is to be run on the board and performs a function similar to a PC BIOS. When it has completed execution, it will call a Linux bootloader, such as U-Boot.
通常,有少量特定于系统的启动监视代码可配置内存控制器并执行其他系统外围设备初始化。 它在内存中设置堆栈,通常在更改硬件内存映射之前将其自身从ROM复制到RAM,以便将RAM映射到异常向量地址,而不是ROM。 从本质上讲,该代码独立于要在板上运行的操作系统,并且执行与PC BIOS类似的功能。 完成执行后,它将调用Linux引导程序,例如U-Boot。
13.3.2 Bootloader
13.3.2引导程序
Linux requires a certain amount of code to be run out of reset, to initialize the system. This performs the basic tasks required for the kernel to boot:
Linux需要一定数量的代码才能完成复位、初始化系统。 这将执行内核引导所需的基本任务:
• Initializing the memory system and peripherals.
•初始化存储系统和外围设备。
• Loading the kernel image to an appropriate location in memory (and possibly also an initial RAM disk).
•将内核映像加载到内存中的适当位置(也可能是初始RAM磁盘)。
• Generate the boot parameters to be passed to the kernel (including machine type).
•生成要传递给内核的引导参数(包括计算机类型)。
• Set up a console (video or serial) for the kernel.
•为内核设置一个控制台(视频或串行)。
• Enter the kernel.
•输入内核。
The exact steps taken vary between different bootloaders, so for detailed information, refer to documentation for the one that you want to use. U-Boot is a widely used example, but other bootloader possibilities include Apex, Blob, Bootldr and Redboot.
不同的引导加载程序所采取的确切步骤有所不同,因此,有关详细信息,请参阅您要使用的引导程序的文档。 U-Boot是一个广泛使用的示例,但其他引导程序可能包括Apex,Blob,Bootldr和Redboot。
When the bootloader starts, it is typically not present in main memory. It must start by allocating a stack and initializing the core (for example invalidating its caches) and installing itself to main memory. It must also allocate space for global data and for use by malloc() and copy exception vector entries into the appropriate location.
引导加载程序启动时,它通常不存在于主存储器中。 它必须首先分配一个堆栈并初始化内核(例如,使其缓存无效),然后将其安装到主内存中。 它还必须为全局数据分配空间并供malloc()使用,并将异常向量条目复制到适当的位置。
13.3.3 Initialize memory system
13.3.3初始化内存系统
This is very much a board or system specific piece of code. The Linux kernel has no responsibility for the configuration of the RAM in the system. It is presented with the physical memory layout, but has no other knowledge of the memory system. In many systems, the available RAM and its location are fixed and the bootloader task is straightforward. In other systems, code must be written that discovers the amount of RAM available in the system.
这很大程度上是一块板或系统特定的代码。 Linux内核不负责系统中RAM的配置。 它具有物理内存布局,但是没有其他有关内存系统的知识。 在许多系统中,可用的RAM及其位置是固定的,并且引导加载程序的任务很简单。 在其他系统中,必须编写代码以发现系统中可用的RAM数量。
13.3.4 Kernel images
13.3.4内核映像
The kernel image from the build process is typically compressed in zImage format (the conventional name given to the bootable kernel image). Its head code contains a magic number, used to verify the integrity of the decompression, plus start and end addresses. The kernel code is position independent and can be located anywhere in memory. Conventionally, it is placed at a 0x8000 offset from the base of physical RAM. This gives space for the parameter block placed at a 0x100 offset (used for translation tables etc).
构建过程中的内核映像通常以zImage格式(可启动内核映像的常规名称)压缩。 它的头代码包含一个魔术数字(用于验证解压的完整性)以及开始和结束地址。 内核代码与位置无关,并且可以位于内存中的任何位置。 按照惯例,它被放置在与物理RAM基础偏移0x8000的位置。 这为以0x100偏移量放置的参数块提供了空间(用于转换表等)。
Many systems require an initial RAM disk (initrd), as this lets you have a root filesystem available without other drivers being setup. The bootloader can place an initial ramdisk image into memory and pass the location of this to the kernel using ATAG_INITRD2 (a tag that describes the physical location of the compressed RAM disk image) and ATAG_RAMDISK.
许多系统需要一个初始RAM磁盘(initrd),因为这使您无需设置其他驱动程序就可以使用根文件系统。 引导加载程序可以将初始的ramdisk映像放入内存,并使用ATAG_INITRD2(描述压缩RAM磁盘映像的物理位置的标记)将其位置传递给内核。
The bootloader will typically setup a serial port in the target, enabling the kernel serial driver to detect the port and use it for a console. In some systems, another output device such as a video driver can be used as a console. The kernel command line parameter console= can be used to pass the information.
引导加载程序通常会在目标中设置一个串行端口,从而使内核串行驱动程序能够检测到该端口并将其用于控制台。 在某些系统中,其他输出设备(例如视频驱动程序)可以用作控制台。 内核命令行参数console =可用于传递信息。
13.3.5 Kernel parameters using ATAGs
13.3.5使用ATAG的内核参数
Historically, the parameters passed to the kernel are in the form of a tagged list, placed in physical RAM with register R2 holding the address of the list. Tag headers hold two 32-bit unsigned ints, with the first giving the size of the tag in words and the second providing the tag value (indicating the type of tag). For a full list of parameters that can be passed, consult the appropriate documentation. Examples include ATAG_MEM to describe the physical memory map and ATAG_INITRD2 to describe where the compressed ramdisk image is located. The bootloader must also provide an ARM Linux machine type number (MACH_TYPE). This can be a hard-coded value, or the boot code can inspect the available hardware and assign a value accordingly.
从历史上看,传递给内核的参数是以标记列表的形式,放置在物理RAM中,而寄存器R2则保存了列表的地址。 标签标头包含两个32位无符号整数,第一个标头以字为单位提供标签的大小,第二个标头提供标签值(指示标签的类型)。 有关可以传递的参数的完整列表,请参阅相应的文档。 示例包括描述物理内存映射的ATAG_MEM和描述压缩虚拟磁盘映像位于何处的ATAG_INITRD2。 引导加载程序还必须提供ARM Linux机器类型编号(MACH_TYPE)。 这可以是硬编码的值,或者引导代码可以检查可用的硬件并相应地分配一个值。
There is a more flexible, or generic method for passing this information using Flattened Device Trees (FDTs).
有一种更灵活或更通用的方法,可以使用展平的设备树(FDT)传递此信息。
13.3.6 Kernel parameters using Flattened Device Trees
13.3.6使用展平的设备树的内核参数
The Linux device tree or FDT support was introduced for the PowerPC kernel as a part of the merger of a 32-bit and 64-bit kernel to standardize the firmware interface by using an Open Firmware interface for all PowerPC platforms, servers, desktop and embedded. It has become the configuration methodology used in the Linux kernel for PowerPC, Micro Blaze and SPARC architectures.
作为32位和64位内核合并的一部分,引入了针对PowerPC内核的Linux设备树或FDT支持,以通过对所有PowerPC平台,服务器,台式机和嵌入式系统使用开放固件接口来标准化固件接口。 。 它已成为Linux内核中用于PowerPC,Micro Blaze和SPARC体系结构的配置方法。
A device tree is a data structure that describes the hardware configuration. It includes information about processors, memory sizes and banks, interrupt configuration, and peripherals. The data structure is organized as a tree with a single root node named /. With the exception of the root node, each node has a single parent. Each node has a name and can have any number of child nodes. Nodes can also contain named properties values with arbitrary data, and they are expressed in key-value pairs.
设备树是描述硬件配置的数据结构。 它包括有关处理器,内存大小和存储体,中断配置和外围设备的信息。 数据结构被组织为具有单个名为/的根节点的树。 除根节点外,每个节点都有一个父节点。 每个节点都有一个名称,并且可以具有任意数量的子节点。 节点还可以包含具有任意数据的命名属性值,它们以键值对的形式表示。
The device tree data format follows the conventions defined in IEEE standard 1275. To simplify system description, a device tree source format (.dts) is used to express device tree data.
设备树数据格式遵循IEEE标准1275中定义的约定。为了简化系统描述,设备树源格式(.dts)用于表示设备树数据。
A device tree node must comply with the following syntax:
设备树节点必须符合以下语法:
[label:] node-name[@unit-address] {
[properties definitions]
[child nodes]
}
Nodes are defined with a name and a unit-address. Braces mark the beginning and end of the node definition.
用名称和单位地址定义节点。 大括号标记节点定义的开始和结束。
You can use a Device Tree Compiler (DTC) tool to convert the device tree source file (.dts) to the device tree blob (dtb) format. The dtb, or blob, is known as the Flattened Device Tree and is a firmware independent description of the system, in a compressed format that requires no firmware calls to retrieve its properties. The Linux kernel loads the dtb before it loads the operating system.
您可以使用设备树编译器(DTC)工具将设备树源文件(.dts)转换为设备树blob(dtb)格式。 dtb或blob被称为“扁平化设备树”,是系统的固件独立描述,采用压缩格式,不需要固件调用即可检索其属性。 Linux内核在加载操作系统之前先加载dtb。
The chosen node is a placeholder for any environment information that does not belong anywhere else, such as boot arguments for the kernel and default console. Chosen node properties are usually defined by the boot loader, but the dts file can specify a default value.
所选节点是不属于任何其他环境信息的占位符,例如内核的引导参数和默认控制台。 选择的节点属性通常由引导加载程序定义,但是dts文件可以指定默认值。
The following code fragment shows a root node description for an ARM Versatile Platform Board. The model and compatible properties are assigned the name of the platform in the form <manufacturer>,<model>. This string concatenation is the universal identifier for the machine and must be defined at the top node.
以下代码片段显示了ARM Versatile Platform Board的根节点描述。 为模型和兼容属性分配平台名称,格式为<制造商>,<模型>。 此字符串串联是计算机的通用标识符,必须在顶级节点上定义。
/ {
model = "arm,versatilepb";
compatible = "arm,versatilepb";
#address-cells = <1>;
#size-cells = <1>;
memory {
name = "memory";
device_type = "memory";
reg = <0x0 0x08000000>;
};
chosen {
bootargs = “console=ttyAMA0 debug”;
}
};
13.3.7 Kernel entry
13.3.7内核输入
Kernel execution must commence with the core in a fixed state. The bootloader calls the kernel image by branching directly to its first instruction, the start label in arch/arm/boot/compressed/head.S. The MMU and data cache must be disabled. The core must be in Supervisor mode, with CPSR I and F Bits set (IRQ and FIQ disabled). R0 must contain 0, R1 the MACH_TYPE value and R2 the address of the tagged list of parameters.
内核执行必须从内核处于固定状态开始。 引导加载程序通过直接跳转到第一条指令(即arch / arm / boot / compressed / head.S中的开始标签)来调用内核映像。 必须禁用MMU和数据缓存。 内核必须处于Supervisor模式,并设置CPSR I和F位(禁止IRQ和FIQ)。 R0必须包含0,R1必须包含MACH_TYPE值,R2必须包含标记的参数列表的地址。
The first step in getting the kernel working is to decompress it. This is mostly architecture independent. The parameters passed from the bootloader are saved and the caches and MMU are enabled. Checks are made to see if the decompressed image will overwrite the compressed image, before calling decompress_kernel() in arch/arm/boot/compressed/misc.c, The cache is then cleaned and invalidated before being disabled again. We then branch to the kernel startup entry point in arch/arm/kernel/head.S.
使内核正常工作的第一步是将其解压缩。 这主要与体系结构无关。 保存从引导加载程序传递的参数,并启用缓存和MMU。 在arch / arm / boot / compressed / misc.c中调用decompress_kernel()之前,先检查解压缩后的映像是否会覆盖压缩后的映像,然后清除缓存并使其无效,然后再次将其禁用。 然后,我们转到arch / arm / kernel / head.S中的内核启动入口点。
13.3.8 Platform-specific actions
13.3.8 特定于平台的操作
A number of architecture specific tasks are now undertaken. The first checks core type using __lookup_processor_type() that returns a code specifying which core it is running on. The function __lookup_machine_type() is then used (unsurprisingly) to look up machine type. A basic set of translation tables is then defined which map the kernel code. The cache and MMU are initialized and other control registers set. The data segment is copied to RAM and start_kernel() is called.
现在要执行许多特定于体系结构的任务。 第一个使用__lookup_processor_type()检查核心类型,该类型返回指定其在哪个核心上运行的代码。 然后(毫不奇怪)使用函数__lookup_machine_type()查找机器类型。 然后定义一组基本的转换表,这些表映射了内核代码。 缓存和MMU被初始化,其他控制寄存器被设置。 数据段被复制到RAM并调用start_kernel()。
13.3.9 Kernel start-up code
13.3.9 内核启动代码
In principle, the rest of the startup sequence is the same on any architecture, but in fact some functions are still hardware dependent.
原则上,其余启动顺序在任何体系结构上都是相同的,但实际上某些功能仍取决于硬件。
- IRQ interrupts are disabled with local_irq_disable(), while lock_kernel() is used to stop FIQ interrupts from interrupting the kernel. It initializes the tick control, memory system and architecture-specific subsystems and deals with the command line options passed by the bootloader.
IRQ中断通过local_irq_disable()禁用,而lock_kernel()用于阻止FIQ中断中断内核。 它初始化滴答控制,内存系统和特定于体系结构的子系统,并处理引导加载程序传递的命令行选项。 - Stacks are set up and the Linux scheduler is initialized.
设置堆栈并初始化Linux调度程序。 - The various memory areas are set-up and pages allocated.
设置了各种存储区并分配了页。 - The interrupt and exception table and handlers are setup, along with the GIC.
设置中断和异常表以及处理程序以及GIC。 - The system timer is setup and at this point IRQs are enabled. Additional memory system initialization occurs and then a value called BogoMips is used to calibrate the core clock speed.
设置系统计时器,并在此时启用IRQ。还会进行其他内存系统初始化,然后使用一个称为BogoMips的值来校准内核时钟速度。 - Internal components of the kernel are set up, including the filesystem and the initialization process, followed by the thread daemon that creates kernel threads.
设置内核的内部组件,包括文件系统和初始化过程,然后是创建内核线程的线程守护程序。 - The kernel is unlocked (FIQ enabled) and the scheduler started.
内核被解锁(启用FIQ)并启动了调度程序。 - The function do_basic_setup() is called to initialize drivers, sysctl, work queues and network sockets. At this point, the switch to User mode is performed.
调用函数do_basic_setup()来初始化驱动程序,sysctl,工作队列和网络套接字。此时,执行到用户模式的切换。
The memory map used by Linux is shown in Figure 13-1. ZI refers to zero-initialized data. There is a broad split between kernel memory, above address 0xBF000000 and user memory, below that address. Kernel memory uses global mappings, while user memory uses non-global mappings, although both code and data can be shared between processes. As already mentioned, application code starts at 0x1000, leaving the first 4KB page unused, to enable trapping of NULL pointer references.
Linux使用的内存映射如图13-1所示。 ZI指的是零初始化数据。 地址0xBF000000以上的内核内存和该地址以下的用户内存之间有很大的区别。 内核内存使用全局映射,而用户内存使用非全局映射,尽管可以在进程之间共享代码和数据。 如前所述,应用程序代码从0x1000开始,未使用第一个4KB页面,从而可以捕获NULL指针引用。