Memory mapping and management during AARCH64 startup (based on kernel-4.9)

1. Identity map and kernel image map will be performed in the assembly stage

Identity map: refers to mapping the physical address of the idmap_text area to an equivalent virtual address. After this mapping is completed, its virtual address is equal to the physical address. The idmap_text area is some code related to opening the MMU. The mapping ensures that the execution and addressing of the code will not go wrong before and after the MMU is opened, so that it can be switched seamlessly.

Kernel image map: This mapping operation is to map the physical address range corresponding to KERNLE IMAGE to the virtual address of physical address + PAGE_OFFSET.

Remarks:
idmap_text: This code is included in the kernel image, so it will go through the above two maps, that is to say, the text area will be mapped twice, once to the virtual address equal to the physical address, and the other time It is mapped to the virtual address corresponding to the physical address +PAGE_OFFSET. So this code is possible to run at two addresses, then its implementation must be PIC, that is, address-independent.

arch/arm64/kernel/head.S:

 ENTRY(stext)
     bl  preserve_boot_args
     bl  el2_setup           // Drop to EL1, w0=cpu_boot_mode
     adrp    x23, __PHYS_OFFSET
     and x23, x23, MIN_KIMG_ALIGN - 1    // KASLR offset, defaults to 0
     bl  set_cpu_boot_mode_flag
     bl  __create_page_tables
     /*
      * The following calls CPU setup code, see arch/arm64/mm/proc.S for
      * details.
      * On return, the CPU will be ready for the MMU to be turned on and
      * the TCR will have been set.
      */
     bl  __cpu_setup         // initialise processor
     b   __primary_switch
 ENDPROC(stext)

__create_page_tables mainly executes the identity map and the kernel image map:

  __create_page_tables:
......
     create_pgd_entry x0, x3, x5, x6
     mov x5, x3              // __pa(__idmap_text_start)
     adr_l   x6, __idmap_text_end        // __pa(__idmap_text_end)
     create_block_map x0, x7, x3, x5, x6

     /*
      * Map the kernel image (starting with PHYS_OFFSET).
      */
     adrp    x0, swapper_pg_dir
     mov_q   x5, KIMAGE_VADDR + TEXT_OFFSET  // compile time __va(_text)
     add x5, x5, x23         // add KASLR displacement
     create_pgd_entry x0, x5, x3, x6
     adrp    x6, _end            // runtime __pa(_end)
     adrp    x3, _text           // runtime __pa(_text)
     sub x6, x6, x3          // _end - _text
     add x6, x6, x5          // runtime __va(_end)
     create_block_map x0, x7, x3, x5, x6
 ......

Among them, create_pgd_entry is called to create page table entries, and create_block_map is called to map the last physical address

2.dtb map

When the above map is executed, the MMU has been opened and started to enter the C code running phase, then the next step is to map the dtb.

 void __init setup_arch(char **cmdline_p)
 {
     pr_info("Boot CPU: AArch64 Processor [%08x]\n", read_cpuid_id());

     sprintf(init_utsname()->machine, UTS_MACHINE);
     init_mm.start_code = (unsigned long) _text;
     init_mm.end_code   = (unsigned long) _etext;
     init_mm.end_data   = (unsigned long) _edata;
     init_mm.brk    = (unsigned long) _end;

     *cmdline_p = boot_command_line;

     early_fixmap_init();  //创建了dtb对应地址中间level级别的页表entry
     early_ioremap_init(); //执行各个模块的早期ioremap(此时内存管理系统还没有加载)

     setup_machine_fdt(__fdt_pointer);//其中会调用到fixmap_remap_fdt来创建最后一个level的页表entry,完成dtb最终的映射

     parse_early_param();

     /*
      *  Unmask asynchronous aborts after bringing up possible earlycon.
      * (Report possible System Errors once we can report this occurred)
      */
     local_async_enable();

     /*
      * TTBR0 is only used for the identity mapping at this stage. Make it
      * point to zero page to avoid speculatively fetching new entries.
      */
     cpu_uninstall_idmap();

     xen_early_init();
     efi_init();
     arm64_memblock_init();

     paging_init();

......

In the execution of setup_arch, early_fixmap_init() will be performed first. This function is used to map dtb, but it will only create the page table entry of the middle level of the physical address corresponding to the DTB, and the page table mapping of the last level is Leave it for later to create by fixmap_remap_fdt. This function is called in setup_machine_fdt.

static void __init setup_machine_fdt(phys_addr_t dt_phys)
{
    void *dt_virt = fixmap_remap_fdt(dt_phys);//创建DTB地址对应的最后一个level的页表entry,完成dtb最终的map

    if (!dt_virt || !early_init_dt_scan(dt_virt)) {
        pr_crit("\n"
            "Error: invalid device tree blob at physical address %pa (virtual address 0x%p)\n"
            "The dtb must be 8-byte aligned and must not exceed 2 MB in size\n"
            "\nPlease check your bootloader.",
            &dt_phys, dt_virt);

        while (true)
            cpu_relax();
    }

    machine_name = arch_read_machine_name();
    if (machine_name) {
        dump_stack_set_arch_desc("%s (DT)", machine_name);
        pr_info("Machine: %s\n", machine_name);
    }
}

The mapping here is fixmap. The so-called fixmap is a fixed mapping. It requires us to clearly know the physical address we want to map, and map this address to the virtual address we want to map. Of course, there may be some one-sidedness here, because in the implementation of the fixmap mechanism, there is also a function to support dynamic allocation of virtual addresses. This function is mainly used for temporary fixmap mapping, and the mapping of dtb belongs to permanent mapping. The virtual address of this mapping is after startup. has always been valid.

3.early ioremap

Since the fixmap mechanism can complete the mapping of dtb, it can also be used for other modules. For some hardware that needs to work before the memory management system is up, we can use this mechanism to map memory for these hardware drivers. As mentioned above, fixmap can support temporary mapping, which is used to perform early ioremap.

After each module uses the address of the early ioremap, it needs to release the mapped virtual address as soon as possible, so that it can be repeatedly applied for and used by other modules.

/*
 * Must be called after early_fixmap_init
 */
void __init early_ioremap_init(void)
{
    early_ioremap_setup();
}

As can be seen from the comment description of this function, its implementation is dependent on fixmap, so it must be run after early_fixmap_init.

void __init early_ioremap_setup(void)
{
    int i;
    for (i = 0; i < FIX_BTMAPS_SLOTS; i++)
        if (WARN_ON(prev_map[i]))
            break;
    for (i = 0; i < FIX_BTMAPS_SLOTS; i++)
        slot_virt[i] = __fix_to_virt(FIX_BTMAP_BEGIN - NR_FIX_BTMAPS*i);
}

4. Loading system memory

(1) memory type memory loading:

After completing the above memory mapping, we can access the kernel and dtb, then we can spy on the system memory layout through dtb, and then we can further add and manage the system memory. This step is mainly done in setup_machine_fdt.
We need to tell the system through this step what the physical memory layout is. Here, the memory type includes all memory and reserved memory. That is to say, the memory type actually contains reserved memory. As can be seen from its defined name, it represents all memory.

 void __init setup_arch(char **cmdline_p)
 {
     pr_info("Boot CPU: AArch64 Processor [%08x]\n", read_cpuid_id());

     sprintf(init_utsname()->machine, UTS_MACHINE);
     init_mm.start_code = (unsigned long) _text;
     init_mm.end_code   = (unsigned long) _etext;
     init_mm.end_data   = (unsigned long) _edata;
     init_mm.brk    = (unsigned long) _end;

     *cmdline_p = boot_command_line;

     early_fixmap_init();
     early_ioremap_init();

     setup_machine_fdt(__fdt_pointer); //dtb映射和解析

     parse_early_param();

     /*      
      *  Unmask asynchronous aborts after bringing up possible earlycon.
      * (Report possible System Errors once we can report this occurred)
      */ 
     local_async_enable();

     /*
      * TTBR0 is only used for the identity mapping at this stage. Make it
      * point to zero page to avoid speculatively fetching new entries.
      */ 
     cpu_uninstall_idmap();

     xen_early_init();
     efi_init();
     arm64_memblock_init();
static void __init setup_machine_fdt(phys_addr_t dt_phys)
{       
    void *dt_virt = fixmap_remap_fdt(dt_phys);

    if (!dt_virt || !early_init_dt_scan(dt_virt)) {
        pr_crit("\n"
            "Error: invalid device tree blob at physical address %pa (virtual address 0x%p)\n
            "The dtb must be 8-byte aligned and must not exceed 2 MB in size\n"
            "\nPlease check your bootloader.",
            &dt_phys, dt_virt);

        while (true)
            cpu_relax();
    }

    machine_name = arch_read_machine_name();
    if (machine_name) {
        dump_stack_set_arch_desc("%s (DT)", machine_name);
        pr_info("Machine: %s\n", machine_name);
    }
}
bool __init early_init_dt_scan(void *params)
{      
    bool status; 

    status = early_init_dt_verify(params);
    if (!status)
        return false;

    early_init_dt_scan_nodes();
    return true;
}  
void __init early_init_dt_scan_nodes(void)
{
    /* Retrieve various information from the /chosen node */
    of_scan_flat_dt(early_init_dt_scan_chosen, boot_command_line);

    /* Initialize {size,address}-cells info */
    of_scan_flat_dt(early_init_dt_scan_root, NULL);
    /* Setup memory, calling early_init_dt_add_memory_arch */
    of_scan_flat_dt(early_init_dt_scan_memory, NULL);
}  

The above code is traced back and will eventually be executed to of_scan_flat_dt(early_init_dt_scan_memory, NULL); this step will manage the memory information in dtb by adding memblock_add to the memblock_type linked list corresponding to memblock.memory.

The code flow of the memory type region loading process:

setup_arch>>setup_machine_fdt>>early_init_dt_scan>>early_init_dt_scan_nodes>>
early_init_dt_scan_memory>>memblock_add>>memblock_add_range(&memblock.memory, base, size, MAX_NUMNODES, 0);
(2) reserved type memory loading

What kind of memory should be set as reserved?

When this part of the physical memory area cannot be managed by the memory management system, that is to say, it has a specific purpose and cannot be released and re-allocated, we set this memory as a reserved area, such as dtb/kernel image/initrd, etc. Wait.
Note: The memory type region includes the reserved type region. That is to say, the reserved type region is a subset of the memory region.

dtb reserved area:

From the previous article, we know that the dtb fixed map creates the last level page table in the fixmap_remap_fdt function, that is, in this function, we find the corresponding physical memory area and set it as reserved.


 void *__init fixmap_remap_fdt(phys_addr_t dt_phys)
 {
     void *dt_virt;
     int size;

     dt_virt = __fixmap_remap_fdt(dt_phys, &size, PAGE_KERNEL_RO);
     if (!dt_virt)
         return NULL;

     memblock_reserve(dt_phys, size);//加入到保留区域
     return dt_virt;
 }

Kernel image and initrd reserved area:


 void __init arm64_memblock_init(void)
 {
......
     memblock_reserve(__pa_symbol(_text), _end - _text);  //kernel image保留区
 #ifdef CONFIG_BLK_DEV_INITRD
     if (initrd_start) {
         memblock_reserve(initrd_start, initrd_end - initrd_start); //initrd保留区
         /* the generic initrd code expects virtual addresses */
         initrd_start = __phys_to_virt(initrd_start);
         initrd_end = __phys_to_virt(initrd_end);
     }
 #endif
     early_init_fdt_scan_reserved_mem();  //dts中配置为保留的区域
......
}

The reserved area in dts:

As written in the above code, in addition to the special-purpose areas above, the user's configuration in fdt is also retrieved in arm64_memblock_init. If the user has configured reserve memory in dts, the system will also perform the reservation action.

 void __init early_init_fdt_scan_reserved_mem(void) 
 {
     int n;
     u64 base, size; 

     if (!initial_boot_params)
         return;

     /* Process header /memreserve/ fields */
     for (n = 0; ; n++) {
         fdt_get_mem_rsv(initial_boot_params, n, &base, &size);
         if (!size)
             break;
         early_init_dt_reserve_memory_arch(base, size, 0);
     }

     of_scan_flat_dt(__fdt_scan_reserved_mem, NULL);
     fdt_init_reserved_mem();
 }  

Summary: Through the above series of operations, the memory has been placed in the two regions of memory type and reserved type. Now the memory has been managed by the memblock module. This is only the first step after startup, and subsequent memory will be added. Go to the partner system to manage.

Reference article: Wowo Technology http://www.wowotech.net/memory_management/memory-layout.html

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=324549759&siteId=291194637