1 Introduction
(1) This article is based on the uboot and kernel source code of the hi3516dv300 chip to explain, the uboot version is 2016.11, and the kernel version is 4.9.37;
(2)uboot does not use device tree technology, or traditional tag parameter transfer; the kernel uses device tree technology, and the image is in zImage-dtb format;
(3) The following source code is excerpted from the uboot and kernel of the dv300 chip;
2. The source from which the linux kernel obtains memory information
(1) In the device tree, the starting address, size and other information of the memory can be specified through the "/memory" node;
(2) When uboot starts the kernel, information such as the starting address and size of the memory can be passed to the kernel through the struct tag structure of type ATAG_MEM;
(3) When uboot starts the kernel, information such as the starting address and size of the memory can be passed to the kernel through the struct tag structure of type ATAG_CMDLINE;
The priority for memory information to take effect: ATAG_CMDLINE type tag > ATAG_MEM type tag > device tree "/memory" node
3. The memory address range of the hi3516dv300 chip
(1) By consulting the data sheet, we can see that the "Hi3516DV300 Professional Smart IP Camera SoC User Guide", the memory address range of the dv300 chip is 0x8000_0000-0xFFFF_FFFF, which can support up to 2G memory;
(2) The HiSilicon chip divides the memory into mmz memory and os memory. In the end, the memory that the linux system can manage is smaller than the actual memory size, because a part of the memory needs to be divided as mmz;
4. Specify the memory size in the device tree
/ { ······ memory { device_type = "memory"; reg = <0x82000000 0x20000000>; }; ······ };
(1) The memory node is specially used in the device tree to specify the starting address, size and other information of the memory, which will be parsed when the kernel is started;
(2) The meaning of the above: the starting address of the kernel's memory is 0x82000000(2G), and the memory size is 0x20000000(512M);
5. uboot starts the kernel's BOOTM_ENABLE_MEMORY_TAGS parameter
static void setup_memory_tags(bd_t *bd) { int i; //The memory address may be discontinuous and divided into several pieces of memory. Here, a loop is used to set the information of each piece of memory //There may be multiple tag s of type ATAG_MEM for (i = 0; i < CONFIG_NR_DRAM_BANKS; i++) { params->hdr.tag = ATAG_MEM; //A tag indicating that this is memory information params->hdr.size = tag_size (tag_mem32); params->u.mem.start = bd->bi_dram[i].start; //starting address of memory params->u.mem.size = bd->bi_dram[i].size; //size of memory params = tag_next (params); } }
When the BOOTM_ENABLE_MEMORY_TAGS macro is defined in uboot, the tag parameter of the ATAG_MEM type will be passed to the kernel. The specific start and size of the memory can be seen by analyzing the source code of uboot;
6. The BOOTM_ENABLE_CMDLINE_TAG parameter of the uboot boot kernel
static void setup_commandline_tag(bd_t *bd, char *commandline) { char *p; if (!commandline) return; /* eat leading white space */ for (p = commandline; *p == ' '; p++); /* skip non-existent command lines so the kernel will still * use its default command line. */ if (*p == '\0') return; //Build a tag of type ATAG_CMDLINE params->hdr.tag = ATAG_CMDLINE; params->hdr.size = (sizeof (struct tag_header) + strlen (p) + 1 + 4) >> 2; strcpy (params->u.cmdline.cmdline, p); params = tag_next (params); }
When the BOOTM_ENABLE_CMDLINE_TAG macro is defined in uboot, the tag parameter of type ATAG_CMDLINE will be passed to the kernel. The specific bootarg analysis of the uboot source code shows that;
7. Process analysis of setting memory information when the kernel starts
7.1. Setting information of memory information
//device tree dts file / { ······ memory { device_type = "memory"; reg = <0x82000000 0x20000000>; }; ······ }; //kernel boot parameters ~ # cat /proc/cmdline mem=1408M console=ttyS0,115200 root=/dev/mmcblk0p7 rootfstype=squashfs rootwait //struct tag of type ATAG_MEM of uboot Here directly tell the conclusion: the memory start address is 0 x8000_0000,memory size is 0 x20000000;
(1) If the device tree memory node takes effect: the memory start address is 0x82000000, and the size is 0x20000000(512M);
(2) If the startup parameters take effect: the memory start address is 0x80000000, and the size is 1408M; (the memory start address 0x80000000 can analyze the kernel source code)
(3) If the tag of type ATAG_MEM takes effect: the starting address of the memory is 0x80000000, and the size is 0x20000000(512M);
7.2. The process of kernel parsing memory information
(1) Compile the device tree dts file into a dtb format file, and connect it to the zImage image to form zImage-dtb;
(2)uboot builds tags of ATAG_CMDLINE type and ATAG_MEM type, starts the kernel, and tells the kernel the memory address of the tag;
(3) The kernel will parse the tag transmission parameters in the decompression stage, which will replace the reg attribute value of the "/memory" node of dtb with the tag of type ATAG_MEM, and replace the bootargs attribute of the "/chosen" node of dtb with the tag of type ATAG_CMDLINE value;
(4) Parse the "/memory" node of dtb and register memory information with the kernel;
(5) Parse the bootargs attribute value of the "/chosen" node of dtb and save it to the boot_command_line variable;
(6) Parse the memory information in the boot_command_line variable, and register the memory information with the kernel; the memory information set in the fourth step of parsing the "/memory" node will be overwritten when registering here;
7.3. Kernel related function calls for parsing memory information
start_kernel() setup_arch() setup_machine_fdt() early_init_dt_scan_nodes() of_scan_flat_dt() //Traverse the nodes in the dtb data and find the memory node early_init_dt_scan_chosen() //Parse the bootargs attribute value of the "/chosen" node and save it to the boot_command_line variable early_init_dt_scan_memoryc() //Parse memory nodes of_get_flat_dt_prop() //Get the reg attribute value of the memroy node, that is, the starting address and range of the memory early_init_dt_add_memory_arch() memblock_add(base, size) memblock_add_range(&memblock.memory, base, size, MAX_NUMNODES, 0) //Register the memory start address and range with the kernel parse_early_param() //Parse the boot_command_line variable parse_early_options() parse_args() parse_one() //Parse each setting in bootargs separately do_early_param() early_mem() //Parse "mem=1408M" arm_add_memory() memblock_add() memblock_add_range() //Register the memory start address and range with the kernel
(1) Node analysis reference blog of the device tree: "Analysis of the device tree in the boot phase of the linux kernel";
(2) Analysis reference blog of the boot_command_line variable: "Detailed Explanation of Kernel Boot Parameters cmdline";
(3)uboot passes parameters to the tag of the kernel, refer to the blog: "uboot passes parameters to the kernel in tag mode";
(4) Reference blog for the analysis of tag s by the kernel when the device tree is not used: "Verification of uboot parameter tags in the kernel";
7.4, memblock_add() function
int __init_memblock memblock_add(phys_addr_t base, phys_addr_t size) { printk("memblock_add: [%#016llx-%#016llx], flags %#02lx, %pF @@@@@@@\n", (unsigned long long)base, (unsigned long long)base + size - 1, 0UL, (void *)_RET_IP_); return memblock_add_range(&memblock.memory, base, size, MAX_NUMNODES, 0); }
(1) The memblock_add() function is the interface provided by the memory to register memory information, and the starting address (physical address) and size of the incoming memory;
(2) There is printing inside the memblock_add() function, which is not printed by default, let go of printing to help debugging, I am directly changing it to printk here;
7.5. Kernel startup print analysis
······ - 80000000 , 20000000 memblock_add: [0x00000080000000-0x0000009fffffff], flags 0x0, early_init_dt_scan_memory+0x16c/0x190 ······ memblock_add: [0x00000080000000-0x000000d7ffffff], flags 0x0, arm_add_memory+0x174/0x19c ······
(1) It can be seen from the kernel printing that the kernel sets the memory information twice in total. According to the above process analysis, the first time is set when parsing the "/memory" node, and the second time is set when parsing "mem=1408M" of bootargs of;
(2) The first setting: the memory startup address is 0x80000000, and the memory size is 0x20000000, which just conforms to the tag parameter of uboot's ATAG_MEM type;
(3) The second setting: the starting address of the memory is 0x80000000, and the memory size is 0xd8000000 - 0x80000000 = 1408M, which just matches the "mem=1408M" of bootargs;
7.6. Kernel actual memory information
~ # free total used free shared buffers Mem: 1419980 132504 1287476 448 23700 -/+ buffers: 108804 1311176 Swap: 0 0 0
Through the free command, it can be seen that the actual memory recognized by the kernel is about 1408M(1408M is recognized, but the kernel may reserve some memory and not display it externally), which confirms our previous conclusion that the "mem=1408M" of bootargs is effective;