Archive for the ‘memory layout’ Category

arm: mm: memory layout

July 29, 2015

This post is to discuss the memory layout in arm 32-bit architecture. The reference source code here is qualcomm msm kernel release 3.4.0.

Be default, MMU is enabled in arm 32-bit architecture. To understand the memory layout of a arm 32-bit device, it’s needed to figure out the physical and virtual memory layouts of the device, and how the two address spaces map to each other.

config MMU
        bool "MMU-based Paged Memory Management Support"
        default y
        help
          Select if you want MMU-based virtualised addressing space
          support by paged memory management. If unsure, say 'Y'. 

physical memory layout
The physical memory layout of a device is described by the banks of meminfo. Each bank corresponds to a contiguous address space.

struct membank {
        phys_addr_t start; 
        phys_addr_t size;
        unsigned int highmem; 
};

struct meminfo {
        int nr_banks; 
        struct membank bank[NR_BANKS];
};
        
extern struct meminfo meminfo;
/*
 * This keeps memory configuration data used by a couple memory
 * initialization functions, as well as show_mem() for the skipping
 * of holes in the memory map.  It is populated by arm_add_memory().
 */
struct meminfo meminfo;

Take msm8974 as an example. The physical memory of msm8974 starts at 0x00000000. The bootloader might pass memory size to kernel via kernel command line or tags. Assumably, the meminfo has one memory bank ranging from 0x00000000 to 0x80000000.

config PHYS_OFFSET                                                                                                                                                                                        
        hex
        default "0x40800000" if ARCH_MSM9615                                                                                                                                                              
        default "0x80200000" if ARCH_APQ8064
        default "0x80200000" if ARCH_MSM8960                                                                                                                                                              
        default "0x80200000" if ARCH_MSM8930
        default "0x00000000" if ARCH_MSM8974                                                                                                                                                              
        default "0x00000000" if ARCH_APQ8084
        default "0x00000000" if ARCH_MPQ8092                                                                                                                                                              
        default "0x00000000" if ARCH_MSM8226
        default "0x00000000" if ARCH_MSM8610                                                                                                                                                              
        default "0x00000000" if ARCH_MSMSAMARIUM
        default "0x10000000" if ARCH_FSM9XXX                                                                                                                                                              
        default "0x00000000" if ARCH_FSM9900
        default "0x00200000" if ARCH_MSM9625                                                                                                                                                              
        default "0x00000000" if ARCH_MSMKRYPTON
        default "0x00200000" if !MSM_STACKED_MEMORY                                                                                                                                                       
        default "0x00000000" if ARCH_QSD8X50 && MSM_SOC_REV_A
        default "0x20000000" if ARCH_QSD8X50                                                                                                                                                              
        default "0x40200000" if ARCH_MSM8X60
        default "0x10000000"
static int __init parse_tag_mem32(const struct tag *tag)
{
        return arm_add_memory(tag->u.mem.start, tag->u.mem.size);
}
__tagtable(ATAG_MEM, parse_tag_mem32);

During initialization, two memory holes split the default memory bank into three banks. The first memory hole ranges from 93 MB to 218 MB. The second memory holes ranges from 250 MB to 255 MB. In msm devices, the memory holes used by modem are located in the first 256 MB. This makes single modem rom run against DDRs of different sizes.

/ {
        model = "Qualcomm MSM 8974";
        compatible = "qcom,msm8974";
        ......
        memory_hole: qcom,msm-mem-hole {
                compatible = "qcom,msm-mem-hole";
                qcom,memblock-remove = <0x5d00000 0x7d00000
                                        0xfa00000 0x500000>; /* Address and Size of Hole */
        };
        ......
}  
/*
 * Function to scan the device tree and adjust the meminfo table to
 * reflect the memory holes.
 */
/*
 * Function to scan the device tree and adjust the meminfo table to
 * reflect the memory holes.
 */
int __init dt_scan_for_memory_hole(unsigned long node, const char *uname,
                int depth, void *data)
{
        unsigned int *memory_remove_prop;
        unsigned long memory_remove_prop_length;
        unsigned long hole_start;
        unsigned long hole_size;
        unsigned int num_holes = 0;
        int i = 0;

        memory_remove_prop = of_get_flat_dt_prop(node,
                                                "qcom,memblock-remove",
                                                &memory_remove_prop_length);

        if (memory_remove_prop) {
                if (!check_for_compat(node))
                        goto out;
        } else {
                goto out;
        }   

        if (memory_remove_prop) {
                if (!memory_remove_prop_length || (memory_remove_prop_length %
                        (2 * sizeof(unsigned int)) != 0)) {
                        WARN(1, "Memory remove malformed\n");
                        goto out;
                }   

                num_holes = memory_remove_prop_length /
                                        (2 * sizeof(unsigned int));

                for (i = 0; i < (num_holes * 2); i += 2) {
                        hole_start = be32_to_cpu(memory_remove_prop[i]);
                        hole_size = be32_to_cpu(memory_remove_prop[i+1]);

                        adjust_meminfo(hole_start, hole_size);
                }   
        }   

out:
        return 0;
}

virtual memory layout
In arm 32-bit architecture, the size of virtual memory layout is 4GB. The lower 3GB is user space while the higher 1GB is kernel space. Within the kernel address space, vmalloc address space occupies the is higher while lowmem is lower. The default size of vmalloc address space in arm 32-bit is 240 MB. Bootloader might set up vmalloc size by command line or tags. In below example, the size of vmalloc address space is 400 MB and the size of lowmem address space is 600 MB.

choice
        prompt "Memory split"
        default VMSPLIT_3G
        help
          Select the desired split between kernel and user memory.

          If you are not absolutely sure what you are doing, leave this 
          option alone!

        config VMSPLIT_3G
                bool "3G/1G user/kernel split"
        config VMSPLIT_2G
                bool "2G/2G user/kernel split"
        config VMSPLIT_1G
                bool "1G/3G user/kernel split"
endchoice
config PAGE_OFFSET
        hex  
        default 0x40000000 if VMSPLIT_1G
        default 0x80000000 if VMSPLIT_2G
        default 0xC0000000
[ 0.000000] c0 0 Virtual kernel memory layout:
[ 0.000000] c0 0 vector : 0xffff0000 - 0xffff1000 ( 4 kB)
[ 0.000000] c0 0 fixmap : 0xfff00000 - 0xfffe0000 ( 896 kB)
[ 0.000000] c0 0 vmalloc : 0xe6000000 - 0xff000000 ( 400 MB)
[ 0.000000] c0 0 lowmem : 0xc0000000 - 0xe5800000 ( 600 MB)
[ 0.000000] c0 0 pkmap : 0xbfe00000 - 0xc0000000 ( 2 MB)
[ 0.000000] c0 0 modules : 0xbf000000 - 0xbfe00000 ( 14 MB)
[ 0.000000] c0 0 .text : 0xc0008000 - 0xc0b51420 (11558 kB)
[ 0.000000] c0 0 .init : 0xc0c00000 - 0xc0d03f00 (1040 kB)
[ 0.000000] c0 0 .data : 0xc0d04000 - 0xc0e50e70 (1332 kB)

memory mapping

Linear mapping is used to transform addresses between the lowmem address spaces in virtual and physical memory layouts. It’s also used to determine kernel memory mapping during initialization.

#define __virt_to_phys(x)       ((x) - PAGE_OFFSET + PHYS_OFFSET)
#define __phys_to_virt(x)       ((x) - PHYS_OFFSET + PAGE_OFFSET)

In the virtual memory layout, by default, VMALLOC_END is 16 MB below the end of virtual address space. Assume bootloader passes vmalloc=408MB. Then, kernel sets vmalloc_min as VMALLOC_END – 408 MB. VMALLOC_START is the first address higher than vmalloc_min and 8 MB aligned. The difference between vmalloc_min and VMALLOC_START provide a gap between between lowmem address space and vmalloc address space to detect memory corruptions.

In the physical memory layout, the meminfo has two banks separated by a 300 MB hole. Kernel uses virt_to_phys(vmalloc_min) to determine if a bank is highmem or not. If virt_to_phys(vmalloc_min) lies in a bank, then the bank will be divided into two banks. The higher bank is highmen while the lower one is lowmem.

The variable arm_lowmem_limit is equal to the highest address of all lomwmem banks. The variable high_memory is equal to phys_to_virt(arm_lowmem_limit). high_memory functions as the end of lowmem in virtual memory layout.

In a nutshell, the requested vmalloc size determines vmalloc_min and VMALLOC_START in the virtual memory layout. The linear mapping of vmalloc_min determines lowmem and highmem regions in the physical memory layout. Then, the end of the lowmem address space, arm_lowmem_limit, in physical memory layout is linear transformed to virtual address space to determine the variable high_memory, the end of the lowmem address space in virtual memory layout.

memory_layout_mapping_01

example 1

In this example. The lowmem address space in virtual memory layout is as large as the lowmem address space in physical memory layout. It’s correct to use the linear mapping function to transform addresses between the two spaces.

The vmalloc address space in virtual memory layout is almost as large as the highmem address space in physical memory layout. It’s incorrect to use the linear mapping function to transform addresses between the two address spaces.

CONFIG_PAGE_OFFSET 0xC0000000
CONFIG_PHYS_OFFSET 0x80000000
meminfo
nr_banks = 1;
bank[0].start = 0x80000000;
bank[0].size = 0x40000000;
vmalloc size is 400 MB
mapping function:
#define __virt_to_phys(x) ((x) - PAGE_OFFSET + PHYS_OFFSET)
#define __phys_to_virt(x) ((x) - PHYS_OFFSET + PAGE_OFFSET)

memory_layout_ex_01

example 2

Compared with example 1, the vmalloc address space in virtual memory layout is smaller, meanwhile the lowmem address space in physical memory layout is larger.

Both vmalloc address space and lowmem address space are resources. There is a trade off to change the size of vmalloc address space and lowmem address space.

CONFIG_PAGE_OFFSET 0xC0000000
CONFIG_PHYS_OFFSET 0x80000000
meminfo
nr_banks = 1;
bank[0].start = 0x80000000;
bank[0].size = 0x40000000;
vmalloc size is 240 MB
linear mapping function:
#define __virt_to_phys(x) ((x) - PAGE_OFFSET + PHYS_OFFSET)
#define __phys_to_virt(x) ((x) - PHYS_OFFSET + PAGE_OFFSET)

memory_layout_ex_02

example 3

If the physical memory size increases to 2GB, then the highmem address space increases accordingly. In this case, virtual memory layout is less than physical memory layout in kernel space. It’s impossible to use linear mapping to transform each address from virtual address space to physical address space.

CONFIG_PAGE_OFFSET 0xC0000000
CONFIG_PHYS_OFFSET 0x80000000
meminfo
nr_banks = 1;
bank[0].start = 0x80000000;
bank[0].size = 0x80000000;
vmalloc size is 400 MB
linear mapping function:
#define __virt_to_phys(x) ((x) - PAGE_OFFSET + PHYS_OFFSET)
#define __phys_to_virt(x) ((x) - PHYS_OFFSET + PAGE_OFFSET)

memory_layout_ex_03

example 4

If there is a hole in physical memory layout, then it’s possible that the lowmem address space in virtual memory layout is larger than the lowmem address space in physical memory layout. During boot initialization, kernel traverses all lowmem banks and sets page tables for these banks. The address space in virtual memory layout linear mapping to the hole in physical memory layout is not allowed to be accessed since the corresponding page table is not setup.

CONFIG_PAGE_OFFSET 0xC0000000
CONFIG_PHYS_OFFSET 0x80000000
meminfo
nr_banks = 2;
bank[0].start = 0x80000000;
bank[0].size = 136 MB;
bank[1].start = 0x90000000;
bank[1].size = 768 MB;
vmalloc size is 400 MB
mapping function:
#define __virt_to_phys(x) ((x) - PAGE_OFFSET + PHYS_OFFSET)
#define __phys_to_virt(x) ((x) - PHYS_OFFSET + PAGE_OFFSET)

memory_layout_ex_04

example 5

If the hole is in the highmem address space rather than lowmem address space of physical memory layout, then the lowmem in physical memory layout is larger. It’s better to have larger lowmem address space without compromising others.

CONFIG_PAGE_OFFSET 0xC0000000
CONFIG_PHYS_OFFSET 0x80000000
meminfo
nr_banks = 2;
bank[0].start = 0x80000000;
bank[0].size = 768 MB;
bank[1].start = 0xB7800000;;
bank[1].size = 136 MB;
vmalloc size is 400 MB
mapping function:
#define __virt_to_phys(x) ((x) - PAGE_OFFSET + PHYS_OFFSET)
#define __phys_to_virt(x) ((x) - PHYS_OFFSET + PAGE_OFFSET)

memory_layout_ex_05

example 6

If it’s inevitable to have a hole in physical memory layout, then tweaking linear mapping function might help increase lowmem address space size without compromising others.

CONFIG_PAGE_OFFSET 0xC0000000
CONFIG_PHYS_OFFSET 0x80000000
meminfo
nr_banks = 2;
bank[0].start = 0x80000000;
bank[0].size = 136 MB;
bank[1].start = 0x90000000;
bank[1].size = 768 MB;
vmalloc size is 400 MB
mapping function:
#define __phys_to_virt(phys)                            \
        (unsigned long)\
        ((MEM_HOLE_END_PHYS_OFFSET && ((phys) >= MEM_HOLE_END_PHYS_OFFSET)) ? \
        (phys) - MEM_HOLE_END_PHYS_OFFSET + MEM_HOLE_PAGE_OFFSET :      \
        (phys) - PHYS_OFFSET + PAGE_OFFSET)

#define __virt_to_phys(virt)                            \
        (unsigned long)\
        ((MEM_HOLE_END_PHYS_OFFSET && ((virt) >= MEM_HOLE_PAGE_OFFSET)) ? \
        (virt) - MEM_HOLE_PAGE_OFFSET + MEM_HOLE_END_PHYS_OFFSET :      \
        (virt) - PAGE_OFFSET + PHYS_OFFSET)

memory_layout_ex_06

Virtual Memory Reclaim
Virtual Memory Reclaim configuration of msm kernel aims to increase memory usage by tweaking memory mapping. Below examples show how this configuration works.

choice
        prompt "Virtual Memory Reclaim"
        default ENABLE_VMALLOC_SAVING
        help 
          Select the method of reclaiming virtual memory

config DONT_MAP_HOLE_AFTER_MEMBANK0
        bool "Map around the largest hole"
        help 
          Do not map the memory belonging to the largest hole 
          into the virtual space. This results in more lowmem.
          If multiple holes are present, only the largest hole 
          in the first 256MB of memory is not mapped.

config ENABLE_VMALLOC_SAVING
        bool "Reclaim memory for each subsystem"
        help 
          Enable this config to reclaim the virtual space belonging
          to any subsystem which is expected to have a lifetime of
          the entire system. This feature allows lowmem to be non- 
          contiguous.

config NO_VM_RECLAIM
        bool "Do not reclaim memory"
        help 
          Do not reclaim any memory. This might result in less lowmem
          and wasting virtual memory space which could otherwise be
          reclaimed by using any of the other two config options.

example 7

The configuration adopts the default linear mapping function.

CONFIG_PAGE_OFFSET 0xC0000000
CONFIG_PHYS_OFFSET 0x80000000
meminfo
nr_banks = 3;
bank[0].start = 0x80000000;
bank[0].size = 0x05B00000; /* 91 MB */
bank[1].start = 0x05D00000; 
bank[1].size = 0x01800000; /* 24 MB */
bank[2].start = 0x12000000;
bank[2].size = 0x1ED00000; /* 493 MB */
vmalloc size is 400 MB
Virtual Memory Reclaim: CONFIG_NO_VM_RECLAIM
mapping function:
#define __virt_to_phys(x) ((x) - PAGE_OFFSET + PHYS_OFFSET)
#define __phys_to_virt(x) ((x) - PHYS_OFFSET + PAGE_OFFSET)
Virtual kernel memory layout:                                                                                                                                                                                
    vector  : 0xffff0000 - 0xffff1000   (   4 kB) 
    fixmap  : 0xfff00000 - 0xfffe0000   ( 896 kB) 
    vmalloc : 0xe6800000 - 0xff000000   ( 392 MB) 
    lowmem  : 0xc0000000 - 0xe6000000   ( 608 MB) 
    pkmap   : 0xbfe00000 - 0xc0000000   (   2 MB) 
    modules : 0xbf000000 - 0xbfe00000   (  14 MB) 
      .text : 0xc0008000 - 0xc0e0a358   (14345 kB) 
      .init : 0xc0f00000 - 0xc10370c0   (1245 kB) 
      .data : 0xc1038000 - 0xc1135c98   (1016 kB) 
       .bss : 0xc1135cbc - 0xc13ba7b0   (2579 kB)
MemTotal:        1868464 kB
HighTotal:       1460224 kB
LowTotal:         408240 kB

memory_layout_ex_07

example 8

The configuration tweak linear mapping function to avoid mapping to the largest hole in the first 256 MB of physical memory layout. It helps increase the size of lowmem address space in physical memory layout.

CONFIG_PAGE_OFFSET 0xC0000000
CONFIG_PHYS_OFFSET 0x80000000
meminfo
nr_banks = 3;
bank[0].start = 0x80000000;
bank[0].size = 0x05B00000; /* 91 MB */
bank[1].start = 0x05D00000; 
bank[1].size = 0x01800000; /* 24 MB */
bank[2].start = 0x12000000;
bank[2].size = 0x1ED00000; /* 493 MB */
vmalloc size is 400 MB
Virtual Memory Reclaim: CONFIG_DONT_MAP_HOLE_AFTER_MEMBANK0
mapping function:
#define __phys_to_virt(phys)                            \
        (unsigned long)\
        ((MEM_HOLE_END_PHYS_OFFSET && ((phys) >= MEM_HOLE_END_PHYS_OFFSET)) ? \
        (phys) - MEM_HOLE_END_PHYS_OFFSET + MEM_HOLE_PAGE_OFFSET :      \
        (phys) - PHYS_OFFSET + PAGE_OFFSET)

#define __virt_to_phys(virt)                            \
        (unsigned long)\
        ((MEM_HOLE_END_PHYS_OFFSET && ((virt) >= MEM_HOLE_PAGE_OFFSET)) ? \
        (virt) - MEM_HOLE_PAGE_OFFSET + MEM_HOLE_END_PHYS_OFFSET :      \
        (virt) - PAGE_OFFSET + PHYS_OFFSET)
Virtual kernel memory layout:                                                                                                                                                                                
    vector  : 0xffff0000 - 0xffff1000   (   4 kB) 
    fixmap  : 0xfff00000 - 0xfffe0000   ( 896 kB) 
    vmalloc : 0xe6800000 - 0xff000000   ( 392 MB) 
    lowmem  : 0xc0000000 - 0xe6000000   ( 608 MB) 
    pkmap   : 0xbfe00000 - 0xc0000000   (   2 MB) 
    modules : 0xbf000000 - 0xbfe00000   (  14 MB) 
      .text : 0xc0008000 - 0xc0e0a358   (14345 kB) 
      .init : 0xc0f00000 - 0xc10370c0   (1245 kB) 
      .data : 0xc1038000 - 0xc1135c98   (1016 kB) 
       .bss : 0xc1135cbc - 0xc13ba7b0   (2579 kB)
MemTotal:        1868008 kB
HighTotal:       1286144 kB
LowTotal:         581864 kB

memory_layout_ex_08

example 9

This configuration tweaks vmalloc address space distribution to reclaim unused lowmem address space in virtual address space. It could also increase the size of lowmem address space in physical memory layout.

This strategy needs a lot of changes in vmalloc related code.

CONFIG_PAGE_OFFSET 0xC0000000
CONFIG_PHYS_OFFSET 0x80000000
meminfo
nr_banks = 3;
bank[0].start = 0x80000000;
bank[0].size = 0x05B00000; /* 91 MB */
bank[1].start = 0x05D00000; 
bank[1].size = 0x01800000; /* 24 MB */
bank[2].start = 0x12000000;
bank[2].size = 0x1ED00000; /* 493 MB */
vmalloc size is 400 MB
Virtual Memory Reclaim: CONFIG_ENABLE_VMALLOC_SAVING
mapping function:
#define __virt_to_phys(x) ((x) - PAGE_OFFSET + PHYS_OFFSET)
#define __phys_to_virt(x) ((x) - PHYS_OFFSET + PAGE_OFFSET)
Virtual kernel memory layout:
    vector  : 0xffff0000 - 0xffff1000   (   4 kB)
    fixmap  : 0xfff00000 - 0xfffe0000   ( 896 kB)
           vmalloc : 0xf1000000 - 0xff000000   ( 224 MB)
            lowmem : 0xd2000000 - 0xf0d00000   ( 493 MB)
           vmalloc : 0xc7500000 - 0xd2000000   ( 171 MB)
            lowmem : 0xc5d00000 - 0xc7500000   (  24 MB)
           vmalloc : 0xc5b00000 - 0xc5d00000   (   2 MB)
            lowmem : 0xc0000000 - 0xc5b00000   (  91 MB)
            ……
MemTotal:        1868040 kB
HighTotal:       1283072 kB
LowTotal:         584968 kB

memory_layout_ex_09


%d bloggers like this: