First, preface
In the same way, this article is a supplemental document for the memory initialization article, in the hope that through such a document, a detailed demonstration of how the Linux 4.4.6 kernel extracts information from device tree to complete the memory layout task during the initialization phase. The specific CPU architecture selected is ARM64.
Ii. Construction of memory type region
The memory type is a term for a memblock module (memory management module in the kernel initialization phase), Memblock divides the block into two types: one is memories type, the other is reserved type, Each of the two types of memory region in the system is managed using an array. This section describes how the system constructs an array of memory type during the initialization phase.
1. Scan Device Tree
After the address mapping for the FDT memory area is completed (FIXMAP_REMAP_FDT), the kernel scans the FDT to complete the build of the memories type array. The specific code is in SETUP_MACHINE_FDT--->early_init_dt_scan--->early_init_dt_scan_nodes:
void __init early_init_dt_scan_nodes (void)
{
Of_scan_flat_dt (Early_init_dt_scan_chosen, boot_command_line); ---(1)
Of_scan_flat_dt (Early_init_dt_scan_root, NULL);
Of_scan_flat_dt (Early_init_dt_scan_memory, NULL);-------------(2)
}
(1) The OF_SCAN_FLAT_DT function is used to scan the entire device tree, calling the callback function for each node, so this is actually called for each of the nodes in the device tree Early_init_dt_scan_ The chosen function. This is done because the device tree blob has just completed the address mapping and has not been expanded, we can only use this rather stupid method. This code is mainly addressing chosen node, and parsing, the relevant data into the boot_command_line.
(2) The concept is the same, but for memory node scan.
2. Traditional command line parameter parsing
int __init Early_init_dt_scan_chosen (unsigned long node, const char *uname, int depth, void *data)
{
int l;
const char *p;
if (depth! = 1 | | |!data | |
(strcmp (uname, "chosen")! = 0 && strcmp (uname, "[email protected]")! = 0))
return 0; -------------------------------(1)
EARLY_INIT_DT_CHECK_FOR_INITRD (node); --------------------(2)
/* Retrieve command line */
p = Of_get_flat_dt_prop (node, "Bootargs", &l);
if (P! = NULL && L > 0)
strlcpy (data, p, min (int) L, command_line_size)); ------------(3)
#ifdef Config_cmdline
#ifndef Config_cmdline_force
if (! ( (char *) data) [0])
#endif
strlcpy (data, Config_cmdline, command_line_size);
#endif/* Config_cmdline */-----------------------(4)
return 1;
}
(1) As we said above, Early_init_dt_scan_chosen will be called once for each node in the device tree, so for efficiency, not chosen node we have to hurry. Since chosen node is a child of root node, its depth must be 1. Here depth is not 1 node, node name is not "chosen" or [email protected] and we have no relationship, immediately return.
(2) Parsing the information of the INITRD in chosen node
(3) Parse the Bootargs (command line arguments) in chosen node and copy it to Boot_command_line.
(4) In general, it is possible for the kernel to define a default command line string (config_cmdline), if Bootloader does not pass through the device tree. You might consider using the default parameter. If the system defines the Config_cmdline_force, then the system enforces the default command-line arguments, and bootloader passes the invalid.
3. Memory Node parsing
Int __init early_init_dt_scan_memory (unsigned long node, const char *uname, int depth, void *data)
{
&nbs p; const char *type = of_get_flat_dt_prop (node, "Device_type", NULL);
const __BE32 *reg, *ENDP;
int l;
if (type = = NULL) {
if (!is_enabled (CONFIG_PPC32) | | depth! = 1 | | strcmp (uname, "[email protected]")! = 0)
return 0;
} else if (strcmp (Type, "Memory")! = 0)
return 0;- ----------------------------(1)
reg = Of_get_flat_dt_prop (node, "Linux,usable-memory", &l);
if (reg = = NULL)
reg = Of_get_flat_dt_prop (node, "Reg", &l);---------------(2)
if (reg = = NULL)
return 0;
ENDP = Reg + (l/sizeof (__be32)); --------------------(3)
while ((Endp-reg) >= (Dt_root_addr_cells + dt_root_size_cells)) {
U64 base, size;
Base = Dt_mem_next_cell (Dt_root_addr_cells,?);
Size = Dt_mem_next_cell (Dt_root_size_cells,?); ----------(4)
Early_init_dt_add_memory_arch (base, size);--------------(5)
}
return 0;
}
(1) If the memory node is a child of root node, then it must have the Device_type property and its value is the string "Memory". If not, you can return. But what if node doesn't define the Device_type attribute? Most of the platform can be returned directly, in addition to PPC32, for this platform, if memory node is a deeper node, then it is not device_type attribute, this time can be judged according to node name. Of course, the goal is consistent, not their own focus on node quickly flash people.
(2) The physical address information of the memory node is stored in "Linux,usable-memory" or "Reg" attribute (reg is our common)
(3) l/sizeof (__BE32) is the number of cells for the Reg attribute value, and Reg points to the first CELL,ENDP point to the last cell.
(4) Memory node's Reg attribute value is actually an array, each entry in the array is a base address and a size of two tuples. Parsing the Reg attribute requires two parameters, Dt_root_addr_cells and Dt_root_size_cells, which define the root node's child nodes (for example, Memory node) in the Reg attribute base The number of cells for address and size, if equal to 1, the base address (or size) is represented by a 32-bit cell. For ARMV8, the general Dt_root_addr_cells and Dt_root_size_cells equals 2, indicating that the base address (or size) is represented by a cell of two 32-bit.
Note: The parsing of the two parameters dt_root_addr_cells and Dt_root_size_cells is done in Early_init_dt_scan_root.
(5) For each memory region of the memory mode, call Early_init_dt_add_memory_arch to register the memory area (which is actually done by Memblock_add) to the system.
4. Analyze memory-related early option
Setup_arch---the >parse_early_param function resolves the early options parsing, which leads to the execution of the following code:
static int __init Early_mem (char *p)
{
Memory_limit = Memparse (P, &p) & Page_mask;
return 0;
}
Early_param ("mem", Early_mem);
In the past, when there was no device tree, mem, the command-line parameter, passed the memory bank's information, which was used by the kernel to create the initial layout of the system's RAM. In ARM64, because of the forced use of device tree, the mem startup parameter loses its original meaning, and now it simply defines the maximum memory limit (the largest system memory address), which limits the parameters that DTS passes over.
Iii. Construction of the reserved type region
The definition of reserved memory is mainly done in the FIXMAP_REMAP_FDT and Arm64_memblock_init functions, and we will build each of the memory region of the various reserved type in code order.
1. Keep Fdt occupied memory, the code is as follows:
void *__init FIXMAP_REMAP_FDT (phys_addr_t Dt_phys)
{......
Memblock_reserve (Dt_phys, size);
......}
FIXMAP_REMAP_FDT primarily establishes an address map for FDT, which, at the end of the function, calls Memblock_reserve to reserve the memory for that segment.
2, retain the kernel and initrd occupied content, the code is as follows:
void __init arm64_memblock_init (void)
{
Memblock_enforce_memory_limit (Memory_limit); ----------------(1)
Memblock_reserve (__pa (_text), _end-_text);------------------(2)
#ifdef CONFIG_BLK_DEV_INITRD
if (Initrd_start)
Memblock_reserve (__virt_to_phys (Initrd_start), Initrd_end-initrd_start);---(3)
#endif
......}
(1) We resolved the memory node of DTS, has added to the system a lot of memory type region, of course reserved memory block will have some, such as DTB corresponding memory is reserved. Memory_limit can give the upper limit to these DTS settings, the Memblock_enforce_memory_limit function will modify the base and size of each memory region based on this limit, and will also be greater than memory_ The memory block of limit (including memory type and reserved type) is deleted from the list.
(2) Reserve kernel code, data area, etc. (_text to _end section, the specific content can refer to the kernel link script)
(3) Preserve initital RAMDisk image area (from Initrd_start to Initrd_end area)
3, through the EARLY_INIT_FDT_SCAN_RESERVED_MEM function to analyze the node in DTS, so that the action of preserving memory, the code is as follows:
void __init early_init_fdt_scan_reserved_mem (void)
{
int n;
u64 base, size;
if (!initial_boot_params)------------------------(1)
return;
/* Process header/memreserve/fields */
for (n = 0;; n++) {
; FDT_GET_MEM_RSV (Initial_boot_params, N, &base, &size);--------(2)
if (!size)
break;
Early_init_dt_reserve_memory_arch (base, size, 0);-----------(3)
}
Of_scan_flat_dt (__fdt_scan_reserved_mem, NULL);------------(4)
fdt_ Init_reserved_mem ();
}
(1) Initial_boot_params is actually the FDT corresponding virtual address. Set in the early_init_dt_verify. If there are no valid FDT in the system, then nothing can be scan, return, leave.
(2) Analysis of the/memreserve/fields in FDT, memory retention. A set of memory reserve parameters is defined in the header of FDT, where the specific location is FDT base address + off_mem_rsvmap. Off_mem_rsvmap is a member of the FDT header, as follows:
struct Fdt_header {
......
fdt32_t Off_mem_rsvmap;------/memreserve/fields Offset
......};
The Memreserve in the FDT header can be defined by multiple, each of which is (address,size) two tuples, ending with 0,0.
(3) preserving each/memreserve/fields defined memory region, the bottom layer is achieved through the Memblock_reserve interface function.
(4) The __FDT_SCAN_RESERVED_MEM function is called for each node in the FDT, the Reserved-memory node is scanned, and then the FDT_INIT_RESERVED_MEM function is called to make the action of memory reservation. Refer to the next section for a detailed description.
4, parse the memory of the Reserved-memory node, the code is as follows:
Static int __init __fdt_scan_reserved_mem (unsigned long node, const char *uname,
& nbsp; int depth, void * Data)
{
static int found;
const char *status;
int err;
if (!found && depth = = 1 && strcmp (uname, "reserved-memory") = = 0) {--(1)
if (__reserved_mem_check_root (node)! = 0) {
pr_err ("Reserved memory:unsupported node format, ignoring\n");
return 1;
}
found = 1;---------------------------------(2)
return 0;
} else if (!found) {
return 0; ----------------------------------(3)
} else if (found && depth < 2) {---------------- ---------(4)
return 1;
}
Status = Of_get_flat_dt_prop (node, "status", NULL); ----------------(5)
if (Status && strcmp (status, "okay")! = 0 && strcmp (status, "OK")! = 0)
return 0;
Err = __reserved_mem_reserve_reg (node, uname); ----------------(6)
if (err = =-enoent && of_get_flat_dt_prop (node, "size", NULL))
Fdt_reserved_mem_save_node (node, uname, 0, 0); ---------------(7)
/* Scan Next node */
return 0;
}
(1) The found variable records whether or not to search for a reserved-memory node, if not, our primary goal is to find a reserved-memory node. Features of the Reserved-memory node include: The child node of root node (depth = = 1), node name is "Reserved-memory", which can filter out a bunch of non-articular points, thus speeding up the search speed.
(2) The Reserved-memory node should include the #address-cells, #size-cells, and Range properties, and the attribute values for #address-cells and #size-cells should be equal to the property values of the root node. If the check Pass (__reserved_mem_check_root), then the description found a correct reserved-memory node, you can go down a node. Of course, the next node is often the subnode of the Reserved-memory node, which is the true definition of the nodes that preserve memory for each segment. A more detailed device tree definition for reserved-memory can refer to the Documentation\devicetree\bindings\reserved-memory\reserved-memory.txt file.
(3) Reserved-memory node is not found, OF_SCAN_FLAT_DT will continue to traverse the next node, and in the __fdt_scan_reserved_mem function returns 0 means that the search continues, if return 1, indicates that the search stopped.
(4) If a reserved-memory node is found and a scan of all its subnode is completed, the scan process is exited for the entire reserved memory.
(5) If the status attribute is defined, then the value must be OK or okay, and you can, of course, not define the attribute (this is the general practice).
(6) The definition of reserved memory has two methods, one is static definition, that is, the Reg attribute is defined, at this time, you can call the __reserved_mem_reserve_reg function to resolve the Reg (address,size) of the two-tuple array, Make a reservation for each defined memory region. The actual reserved memory action can call Memblock_reserve or Memblock_remove, which is related to whether the node defines the No-map attribute, and if the No-map attribute is defined, So this memory operating system does not need address mapping at all, that is, this block of memory is not owned by the operating system memory management module to manage, but attributed to the specific driver use (in device tree, the device node can define the Memory-region node to refer to the memory Reserved memory defined in node, refer to the Reserved-memory.txt file for details).
(7) Another way to define reserved memory is to define the size of the memory area (or to define the alignment or Alloc-range further contract dynamically allocated reserved memory properties, However, these properties are all option, but do not specify a specific base address, let the operating system itself to allocate this memory.
5. Reserved reserved-memory node Memory
The Reserved-memory node and its child nodes in device tree statically or dynamically define a number of reserved memory region, the static defined memory region start address and size are determined, As a result, the Memblock module can be called immediately to reserve the memory area, but for the dynamically defined memories REGION,__FDT_SCAN_RESERVED_MEM only the information is stored in the RESERVED_MEM global variable. There is no actual memory reservation action, the specific operation in the FDT_INIT_RESERVED_MEM function, the code is as follows:
void __init fdt_init_reserved_mem (void)
{
int i;
__rmem_check_for_overlap (); -------------------------(1)
for (i = 0; i < Reserved_mem_count; i++) {--Traversal of each reserved memory region
struct Reserved_mem *rmem = &reserved_mem[i];
unsigned long node = rmem->fdt_node;
int Len;
Const __BE32 *prop;
int err = 0;
prop = of_get_flat_dt_prop (node, "Phandle", &len); ---------------(2)
if (!prop)
prop = of_get_flat_dt_prop (node, "Linux,phandle", &len);
if (prop)
Rmem->phandle = Of_read_number (prop, LEN/4);
if (rmem->size = = 0)----------------------------(3)
Err = __reserved_mem_alloc_size (node, rmem->name,
&rmem->base, &rmem->size);
if (err = = 0)
__reserved_mem_init_node (RMEM);--------------------(4)
}
}
(1) Check whether there are overlapping areas between the statically defined reserved memory regions, and if there is overlap, the base and size of the reserved memory region will not be adjusted, just print the error message.
(2) Each node that needs to be referenced by another node needs to define "Phandle" or "Linux,phandle". Although this attribute is not visible in the actual device tree source, the DTC will actually handle it perfectly.
(3) A memory region of size equal to 0 indicates that this is a dynamic assignment Region,base address has not yet been defined, so we need to parse the node through the __reserved_mem_alloc_size function (size, Alignment), and then call Memblock's Alloc interface function for memory block allocation, the final result is to determine the base address and size, and the memory region from memory Type is moved to an array of type reserved. Of course, if the No-map attribute is defined, then this memory will be removed from the system (the memory type and the reserved type array are not defined in this memory).
(4) There are two usage scenarios for reserved memory, one for use by a particular driver, which is naturally handled in a particular driver's initialization function (Probe function). There is also a scenario that is used by all drivers or kernel modules, such as Cma,per-device coherent DMA allocation, and so on, we need to borrow device tree matching mechanism to do this memory initialization action. If you are interested, you can look at the definition of Reservedmem_of_declare, which is no longer described here.
6. Retention of CMA memory via command line parameters
Arm64_memblock_init---The >dma_contiguous_reserve function will retain the CMA memory according to the command line parameters, which is not described in this article, leave it to the CMA documentation.
Iv. Summary
The physical memory layout is managed by the Memblock module, which defines a global variable such as the struct memblock memblock that holds the memory region list for memory type and reserved type. By using these two memory region arrays, we know the layout of all the system memories that the operating system needs to manage.
Linux memory Initialization (iii) memory layout