I. Main problems:
How does the system load DTS at startup;
Lk And kernel should be investigated.
Ii. References
Shows the DTs loading process:
During the boot process, bootloader (bootable/bootloader/LK by default) selects the appropriate devicetree to load the device into the memory based on the hardware information, and sends the address and other related information to the kernel. In the kernel, a device is created based on the input information.
1. Start with little kernel:
1.1 overall
Statement in lk/ARCH/ARM/crt0.s file: BL kmain
The kmain () function in the lk/kernel/Main. c file is called ()
Kmain ()
| Bootstrap2 ()
| Arch_init ()
| Platform_init ()
| Target_init ()
| Pai_init () // call Init () of apps defined using app_start macro
| Effect_init ()
| Boot_linux_from_mmc ()
| // 1. method 1 of Device Tree
| Dev_tree_get_entry_info ()
|__ Dev_tree_get_entry_info ()
| Memmove ();
| // 2. method 2 of Device Tree
| Dev_tree_appended ()
| Boot_linux ()
| Update_device_tree ()
| Entry (0, machtype, tags_phys); // pass control to Kernel
1.2 Details
Release T. C (bootable \ bootloader \ LK \ app \ release T)
App_start (snapshot T)
. Init = effect_init,
App_end
In cmdt_init () ---> boot_linux_from_mmc (), call dev_tree_get_entry_info (), which is based on the hardware (chipset and platform ID, the actual running information of the system is set and sent from the N side at an earlier stage of system boot, while the information in DT is defined by the "qcom, MSM-ID" attribute of the root node) to select the appropriate DT, the DT will be loaded into the memory, and the address and other information will be sent to the kernel (through the CPU register ).
Void boot_linux (void * kernel, unsigned * tags,
Const char * using line, unsigned machtype,
Void * ramdisk, unsigned ramdisk_size)
{
# If device_tree
// Update the Device Tree
Ret = update_device_tree (void *) tags, final_washline, ramdisk, ramdisk_size );
}
/* Top Level function that updates the Device Tree .*/
Int update_device_tree (void * FDT, const char * using line,
Void * ramdisk, uint32_t ramdisk_size)
{
Int ret = 0;
Uint32_t offset;
/* Check the Device Tree header */
// Check whether the magic number is correct: version and size
Ret = fdt_check_header (FDT );
/* Add padding to make space for new nodes and properties .*/
// Move or resize dtb Buffer
Ret = fdt_open_into (FDT, FDT, fdt_totalsize (FDT) + dtb_pad_size );
/* Get offset of the memory node */
Ret = fdt_path_offset (FDT, "/memory ");
Offset = ret;
Ret = target_dev_tree_mem (FDT, offset );
/* Get offset of the chosen node */
Ret = fdt_path_offset (FDT, "/chosen ");
Offset = ret;
/* Adding the Route line to the chosen node */
Ret = fdt_setprop_string (FDT, offset, (const char *) "bootargs", (const void *) using line );
/* Adding the initrd-start to the chosen node */
Ret = fdt_setprop_u32 (FDT, offset, "Linux, initrd-start", (uint32_t) ramdisk );
If (RET)
/* Adding the initrd-end to the chosen node */
Ret = fdt_setprop_u32 (FDT, offset, "Linux, initrd-end", (uint32_t) ramdisk + ramdisk_size ));
Fdt_pack (FDT );
Return ret;
}
2. Processing in the kernel
Main data streams include:
(1) the initialization process is to scan dtb and convert it into a device tree structure.
(2) parameter transfer during runtime and platform Identification
(3) Integrate the device tree structure into the device driver model of Linux kernel.
1. Compile part of code analysis
The Linux/ARCH/ARM/kernel/head. s file defines the parameter transfer requirements for Bootloader and kernel:
MMU = OFF, D-Cache = off, I-Cache = dont care, R0 = 0, R1 = machine NR, R2 = atags or dtb pointer.
Currently, the kernel supports the old tag list method and the device tree method. R2 may be a pointer to the Device Tree binary file (bootloader must be copied to memory before being passed to the kernel) or a pointer to the tag list. In the startup code of arm assembly (mainly head. S and head-common.S), Machine Type ID and pointer to dtb or atags are saved in the Variable _ machine_arch_type and _ atags_pointer for subsequent C code processing.
Start_kernel ()
| Setup_arch ()
| Setup_machine_fdt () // select machine description according to DT info
2. Obtain the machine descriptor.
// Find the most suitable machine descriptor Based on the device tree information.
Struct machine_desc * _ init setup_machine_fdt (unsigned int dt_phys)
{
/* Scan/chosen node and save the runtime parameter (bootargs) to boot_command_line. In addition, it also processes the properties related to initrd and stores them in the initrd_start and initrd_end global variables */
Of_scan_flat_dt (early_init_dt_scan_chosen, boot_command_line );
/* Scan the root node, obtain the {size, address}-cells information, and save it in the global variables of dt_root_size_cells and dt_root_addr_cells */
Of_scan_flat_dt (early_init_dt_scan_root, null );
/* Scan the memory node in dtb and save the related information in meminfo. The global variable meminfo stores the information related to the system memory. */
Of_scan_flat_dt (early_init_dt_scan_memory, null );
/* Change machine number to match the mdesc we're re using */
_ Machine_arch_type = mdesc_best-> NR;
Return mdesc_best;
}
Runtime parameters are completed when chosen node of dtb is scanned. The specific action is to obtain the value of attributes such as bootargs and initrd of chosen node and save it to the global variable (boot_command_line, initrd_start, initrd_end.
3. Convert dtb to a node in the device node structure.
During system initialization, dtb needs to be converted into a tree structure of device_node for subsequent convenient operations. The specific code is located in setup_arch-> unflatten_device_tree.
Void _ init unflatten_device_tree (void)
{
_ Unflatten_device_tree (initial_boot_params, & allnodes,
Early_init_dt_alloc_memory_arch );
/* Get pointer to "/chosen" and "/aliasas" nodes for use everywhere */
Of_alias_scan (early_init_dt_alloc_memory_arch );
}
The main function of the unflatten_device_tree function is to scan dtb and organize the device node:
(1) Global list. The global variable struct device_node * of_allnodes is the global list pointing to the Device Tree.
(2) tree.
Static void _ unflatten_device_tree (struct boot_param_header * blob,
Struct device_node ** mynodes,
Void * (* dt_alloc) (u64 size, u64 align ))
{
// The health check code is deleted here. For example, check the magic of the dtb header and confirm that the Blob does point to a dtb.
/* The scan process is divided into two rounds. The first round mainly determines the length of the device-tree structure and stores it in the size variable */
Start = (unsigned long) BLOB) +
Be32_to_cpu (blob-> off_dt_struct );
Size = unflatten_dt_node (blob, 0, & START, null, null, 0 );
Size = (size | 3) + 1;
/* During initialization, the corresponding memory is allocated instead of scanning a node or property. In fact, the kernel allocates a large amount of memory at a time, the memory includes all the memory required by struct device_node, node name, and struct property. */
Mem = (unsigned long)
Dt_alloc (size + 4, _ alignof _ (struct device_node ));
(_ Be32 *) MEm) [size/4] = cpu_to_be32 (0 xdeadbeef );
/* This is the second round of scan. The first scan is used to obtain the memory size required to store all nodes and properties. The second scan is to build a device node tree */
Start = (unsigned long) BLOB) +
Be32_to_cpu (blob-> off_dt_struct );
Unflatten_dt_node (blob, mem, & START, null, & allnextp, 0 );
// Verify overflow and of_dt_end.
}
4. Device Driver Model incorporated into Linux Kernel
After Linux kernel introduces a unified device model, bus, driver, and device form an iron triangle in the device model. During driver initialization, a data structure (usually xxx_driver) representing the driver will be mounted to the driver linked list on the bus. The device linked list is divided into two types. One is plug-and-play bus. After a device is inserted, the bus can detect this behavior and dynamically allocate a device data structure (usually xxx_device, such as usb_device). Then, the data structure is mounted to the device linked list on the bus. If the driver and device are full on the bus, how can we make the device encounter the "correct" driver? Is the match function of bus.
The system should dynamically Add the platform_device in the system based on the Device Tree (this process does not only occur on the platform bus, but may also occur on other non-plug-and-play bus, for example, AMBA bus and PCI Bus ). If you want to incorporate the device driver model of Linux kernel, You need to mount device nodes to the corresponding device chain list based on the device_node tree structure (root is of_allnodes. Once this is done, the bus mechanism will schedule appointments between device and driver. Of course, not all device nodes are mounted to the device linked list on the bus, such as CPUs node, memory node, and choose node.
4.1 device node not mounted to the bus
(1) CPUs node Processing
No. Only choose node processing is required.
(2) Memory Processing
Int _ init early_init_dt_scan_memory (unsigned long node, const char * uname,
Int depth, void * Data)
{
Char * type = of_get_flat_dt_prop (node, "device_type", null );
/* During initialization, the call back function is called for each device node. Therefore, we need to filter out nodes irrelevant to the memory block definition. And memory block have two types of nodes. One is that the node name is in the [email protected] format, and the other is that the node defines the device_type attribute and its value is memory. */
If (type = NULL ){
If (depth! = 1 | strcmp (uname, "[email protected]")! = 0)
Return 0;
} Else if (strcmp (type, "Memory ")! = 0)
Return 0;
/* Get the starting address and length of memory. There are two attributes related to this information. One is Linux and the other is usable-memory. However, the latest method is to use the reg attribute. */
Reg = of_get_flat_dt_prop (node, "Linux, usable-memory", & L );
If (REG = NULL)
Reg = of_get_flat_dt_prop (node, "Reg", & L );
If (REG = NULL)
Return 0;
Endp = reg + (L/sizeof (_ be32 ));
/* If the reg attribute value is an array of address and size, how can we retrieve address/size? Because memory node must be the child of the root node, the sum of dt_root_addr_cells (root node's # address-cells attribute value) and dt_root_size_cells (root node's # size-cells attribute value) is address, the entry size of the size array. */
While (endp-Reg)> = (dt_root_addr_cells + dt_root_size_cells )){
U64 base, size;
Base = dt_mem_next_cell (dt_root_addr_cells, & reg );
Size = dt_mem_next_cell (dt_root_size_cells, & reg );
If (size = 0)
Continue;
// Add the specific memory block information to the kernel.
Early_init_dt_add_memory_arch (base, size );
}
Return 0;
}
(3) interrupt controller Processing
Initialization is implemented through start_kernel-> init_irq-> machine_desc-> init_irq. We use Qualcomm MSM 8974 as an example to describe the processing process of Interrupt Controller. The following is the definition of the machine descriptor:/ARCH/ARM/Mach-MSM/board-8974.c
Dt_machine_start (msm8974_dt, "Qualcomm MSM 8974 (flattened Device Tree )")
. Init_irq = msm_dt_init_irq,
. Dt_compat = msm8974_dt_match,
...
Machine_end
Source code file:/ARCH/ARM/Mach-MSM/board-dt.c
Void _ init msm_dt_init_irq (void)
{
Struct device_node * node;
Of_irq_init (irq_match );
Node = of_find_matching_node (null, mpm_match );
}
Of_irq_init: traverses the Device Tree and finds the matching irqchip. The specific code is as follows:
Void _ init of_irq_init (const struct of_device_id * matches)
{
/* Traverse all nodes to find the node that defines the interrupt-controller attribute. If the interrupt-controller attribute is defined, the node is an interrupt controller. */
For_each_matching_node (NP, matches ){
If (! Of_find_property (NP, "interrupt-controller", null ))
Continue;
/* Allocate memory and mount it to the linked list. Of course, the parent-child relationship between controllers is also established based on interrupt-parent. For interrupt controller, it may also be a tree structure. */
Desc = kzarloc (sizeof (* DESC), gfp_kernel );
Desc-> Dev = NP;
Desc-> interrupt_parent = of_irq_find_parent (NP );
If (desc-> interrupt_parent = NP)
Desc-> interrupt_parent = NULL;
List_add_tail (& desc-> list, & intc_desc_list );
}
/* Because the interrupt controller is organized into a tree structure, the initialization sequence needs to be controlled. It should start from the root node and go to the next level interrupt controller in sequence. */
While (! List_empty (& intc_desc_list )){
/* Nodes in the intc_desc_list linked list will be processed one by one. Each processed node will delete the node. When all the nodes are deleted, the entire process is over. */
List_for_each_entry_safe (DESC, temp_desc, & intc_desc_list, list ){
Const struct of_device_id * match;
Int ret;
Of_irq_init_cb_t irq_init_cb;
/* At the beginning, the parent variable is null, and the first parameter is root interrupt controller. After the root node is processed, the parent variable is set to root interrupt controller. Therefore, in the second loop, all parents are processed as the Child interrupt controller of the root interrupt controller. That is, the node of level 1 (if the root is level 0. */
If (desc-> interrupt_parent! = Parent)
Continue;
List_del (& desc-> list); // delete from the linked list
Match = of_match_node (matches, desc-> Dev); // match and initialize
// Match-> data is the initialization function.
If (warn (! Match-> data,
"Of_irq_init: No init function for % s \ n ",
Match-> compatible )){
Kfree (DESC );
Continue;
}
Irq_init_cb = match-> data; // executes the initialization function.
Ret = irq_init_cb (desc-> Dev, desc-> interrupt_parent );
/* Put the processed node into the intc_parent_list linked list, which will be used later */
List_add_tail (& desc-> list, & intc_parent_list );
}
/* For level 0, there is only one root interrupt controller. for level 1, there may be several interrupt controllers. Therefore, we need to traverse these parent interrupt controllers to process the child node of the next level. */
Desc = list_first_entry (& intc_parent_list, typeof (* DESC), list );
List_del (& desc-> list );
Parent = desc-> dev;
Kfree (DESC );
}
}
Only when the node has the attribute definition "interrupt-controller", Linux kernel allocates an interrupt controller Descriptor (struct intc_desc) and mounts it to the queue. You can use the interrupt-parent attribute to determine the hierarchy of each interrupt controller. After scanning the definitions of Interrupt Controller in all device trees, the system starts the matching process. Once an item in the interrupt chip list is matched, the corresponding initialization function is called.
(DT Series 3) How is DTS loaded when the system is started?