Home | Projects | Notes > Linux Kernel Analysis > head.S

head.S

 

Kernel Startup Entry Point

The following comment sets the stage for the kernel's early initialization process, ensuring that key hardware settings and data structures are appropriately handled before the MMU is enabled and the kernel switches to more advanced memory management and virtual addressing.

Why does the kernel startup entry point not care about the I-cache on/off state?

During the early kernel startup, the primary concern is to ensure that the basic hardware setup, data structures, and essential features like MMU are correctly initialized. The impact of I-cache being on or off might not be critical at this point, and its state can be appropriately handled later in the kernel's initialization process when more advanced memory management and virtual addressing are in place. Therefore, the code does not specifically require the I-cache state to be either on or off during the early kernel startup with MMU off and D-cache off.

When the MMU is off and the D-cache is off, the kernel operates in a simple flat memory model, where virtual addresses are the same as physical addresses. In this mode, there is no translation of virtual addresses to physical addresses, and memory accesses are performed directly using physical addresses.

The role of the D-cache is to store frequently accessed data to speed up memory access. However, with the D-cache off and MMU off, memory accesses are not cached, so any changes to memory will be immediately visible to all parts of the kernel and other hardware.

On the other hand, the role of the I-cache is to store frequently accessed instructions to speed up instruction fetches. Whether the I-cache is on or off may not have a significant impact at this very early stage of kernel startup. The kernel's initialization code is relatively small and straightforward at this point, and the code execution mainly involves a sequence of instructions that set up essential data structures, manage hardware, and enable features like MMU and caches.

Since the kernel is executed from memory, even if the I-cache is off, the instruction fetches are still likely to be fast because there is no virtual memory translation overhead, and the code is readily available in memory.

 

__INIT is a macro defined in include/linux/init.h:

When the code is assembled, the assembly code inside the __HEAD macro will be placed in the .head.text section with the appropriate flags, ensuring that it is both allocatable (a) and marked as executable (x) in the final output. The section name and flags can be adjusted based on the specific needs of the project.

 

Image Header Expected by Linux Bootloaders

The image header provides essential information for the boot-loader to load and execute the kernel properly. The bootloader recognizes the signature (EFI signature NOP) and the magic number to identify this as an ARM64 executable. It then uses the entry point (primary_entry) to start executing the kernel and considers the load offset, kernel size, and flags to manage memory allocation and other operations correctly.

The image header conforms to the header format defined in the documentation Documentation/arm64/booting.rst section 4.

L4: efi_signature_nop is a macro defined in the file arch/arm64/kernel/eif-header.S:

 

__INIT

__INIT is a macro defined in include/linux/init.h:

When the code is assembled, the assembly code inside the __INIT macro will be placed in the .init.text section with the appropriate flags, ensuring that it is both allocatable (a) and marked as executable (x) in the final output. The section name and flags can be adjusted based on the specific needs of the project.

 

record_mmu_state

The record_mmu_state subroutine is designed to handle the endianness and MMU state at different exception levels (EL) in the ARM64 processor architecture. The code handles toggling the endianness, clearing the MMU enable bit, and applying workarounds if necessary before writing the modified values back to the relevant control registers. The subroutine ensures that memory accesses issued before the init_kernel_el() function occur in the correct byte order and with the appropriate MMU state.

record_mmu_state records the MMU on/off state in x19 upon return.

Think of the following four possible scenarios:

  1. Cache on, MMU on (x19 = 1)

    Cache invalidation not necessary since the cache will already contain valid data

  2. Cache on, MMU off (x19 = 0)

    Cache invalidation necessary since the validity of the cache contents will not be guaranteed at the moment when the MMU turns on

  3. Cache off, MMU on (x19 = 0)

    Cache invalidation necessary since the validity of the cache contents is not guaranteed

  4. Cache off, MMU off (x19 = 0)

    Cache invalidation necessary since the validity of the cache contents will not be guaranteed at the moment when the MMU turns on

x19 is typically used to store the value of the System Control Register (SCTLR) during early boot.

 

preserve_boot_args

The preserve_boot_args subroutine ensures that the important bootloader arguments are preserved in memory for the kernel's use. Additionally, it handles cache invalidation, especially when the MMU is off, to ensure data consistency during the early stages of the kernel's execution.

L7: adr_l is a macro defined in arch/arm64/include/asm/assembler.h:

Due to the limitation bit-field length in expressing 64-bit address, the macro adr_l is introduced. It uses the "page address" and the "offset" to express 64-bit address. (Commonly seen in arm64)

Can be written as the following C code:

 

dcache_inval_poc (arch/arm64/mm/cache.S)

The function dcache_inval_poc is used to invalidate and clean (flush) the data cache for a specific memory range [start, end). It handles cache-line alignment for both the start and end addresses and uses the civac and ivac cache maintenance instructions to perform cleaning and invalidation operations, respectively. The function iterates over each cache line in the specified range and ensures data coherency between the cache and memory.

SYM_FUNC_START(function_name) is a symbol defining the start of the function_name function. It is typically used for debugging and symbol tracking purposes.

L12: dcache_line_size is a macro defined in arch/arm64/include/asm/assembler.h.

C representation of this function block:

 

create_idmap

create_idmap is responsible for setting up the identity mapping (ID map) for specific memory regions in the ARM64 Linux kernel. The ID map establishes a 1:1 mapping between physical addresses and virtual addresses, allowing direct access to physical memory using virtual addresses.

 

create_idmap (arch/arm64/mm/mmu.c)

The function create_idmap() is used to create an identity mapping for a specific memory region, called the ID map, in the ARM64 Linux kernel. The identity mapping allows direct access to physical memory using virtual addresses without any translation.