"Turn" (DT series three) when the system starts, how DTS is loaded

Source: Internet
Author: User
Tags call back strcmp

Original URL: http://www.cnblogs.com/biglucky/p/4057481.html

One, the main problem:
When the system starts, it is how to load DTS;
The Lk,kernel should be investigated.


Two: Reference text
The DTS loading process looks like this:




During the start-up process, bootloader (the default is BOOTABLE/BOOTLOADER/LK) chooses the appropriate devicetree to load memory according to the machine hardware information, and transmits the relevant information such as the address to kernel. Kernel, the device is created based on the information that is passed in.
1, starting from little kernel:
1.1 Overall
Lk/arch/arm/crt0. s file in statement: BL Kmain
The call is a function in the Lk/kernel/main.c file: Kmain ()


Kmain ()
|BOOTSTRAP2 ()
|arch_init ()
|platform_init ()
|target_init ()
|apps_init ()//call init () of apps defined using App_start macro
|aboot_init ()
|BOOT_LINUX_FROM_MMC ()
The first method of |//1,device tree
|dev_tree_get_entry_info ()
|__dev_tree_get_entry_info ()
|memmove ();
The second method of |//2,device tree
|dev_tree_appended ()
|boot_linux ()
|update_device_tree ()
|entry (0, Machtype, Tags_phys);//pass control to Kernel
1.2 Detailed Introduction
ABOOT.C (Bootable\bootloader\lk\app\aboot)


App_start (aboot)
. init = Aboot_init,
App_end
In the following aboot_init ()---> boot_linux_from_mmc (), call Dev_tree_get_entry_info (), which will be based on the hardware (chipset and platform ID, The information of the actual running of the system is set by N side at earlier stage of the system boot, and the information in DT is defined by the "Qcom,msm-id" attribute of the root node to select the appropriate DT, then the DT is loaded into the memory, and the address and other information is passed to kernel (through the CPU register).
void Boot_linux (void *kernel, unsigned *tags,
const char *cmdline, unsigned machtype,
void *ramdisk, unsigned ramdisk_size)
{
#if Device_tree


Update Device Tree
ret = Update_device_tree ((void *) tags, final_cmdline, RAMDisk, ramdisk_size);
}


/* Top level function that updates the device tree. */
int Update_device_tree (void *fdt, const char *cmdline,
void *ramdisk, uint32_t ramdisk_size)
{
int ret = 0;
uint32_t offset;


/* Check the device tree header */
Verify that 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 CmdLine to the chosen node */
ret = fdt_setprop_string (FDT, offset, (const char*) "Bootargs", (const void*) cmdline);


/* 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;
}


Processing in the 2,kernel


The main data streams include:
(1) The initialization process, which is to scan the DTB and convert it to device Tree Structure.
(2) Pass-time parameter passing and platform identification
(3) Incorporating device Tree structure into the Linux kernel equipment driver model.


1, code Analysis of the Assembly section
Linux/arch/arm/kernel/head. The s file defines the parameter passing requirements for bootloader and kernel:


MMU = off, D-cache = off, i-cache = dont care, r0 = 0, R1 = machine nr, r2 = atags or DTB pointer.


The current kernel supports the way the old tag list is supported, as well as the way device tree is supported. R2 may be a pointer to the device tree binary file (bootloader to be copied into memory before being passed to the kernel) or a pointer to the tag list. In the startup code of the Assembly section of arm (mainly Head.s and head-common.s), machine type ID and pointers to DTB or atags are saved in variables __machine_arch_type and __atags_ In pointer, this is done for subsequent C code processing.
Start_kernel ()
|setup_arch ()
|SETUP_MACHINE_FDT ()//select machine Description according to DT info
2, Get Machine descriptor
Based on the information of device tree, find the most appropriate machine descriptor.
struct Machine_desc * __init setup_machine_fdt (unsigned int dt_phys)
{
/* Scan/chosen node, save runtime Parameters (Bootargs) to Boot_command_line, also handle INITRD related property, and save in Initrd_start and Initrd_ End of the two global variables */
Of_scan_flat_dt (Early_init_dt_scan_chosen, boot_command_line);
/* Scan the root node for {size,address}-cells information and save in Dt_root_size_cells and Dt_root_addr_cells global variables */
Of_scan_flat_dt (Early_init_dt_scan_root, NULL);
/* Scan memory node in DTB and save related information in Meminfo, global variable Meminfo save system memory related information. */
Of_scan_flat_dt (Early_init_dt_scan_memory, NULL);


/* Change machine number to match the Mdesc we ' re using */
__machine_arch_type = mdesc_best->nr;


return mdesc_best;
}
The run-time parameter is done when scanning DTB's chosen node, and the specific action is to get the value of chosen node's Bootargs, INITRD, and so on, and save it in the global variable (boot_command_line,initrd_ Start, initrd_end).


3, the node that converts the DTB into the structure of device node
In the process of system initialization, we need to convert DTB to node is Device_node tree structure, so as to facilitate the subsequent operation. The specific code is 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 the DTB and organize device node into:
(1) Global list. global variable struct Device_node *of_allnodes is the global list that points 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 removed here, such as checking the magic of the DTB header, confirming that the BLOB does point to a DTB.
/* Scan process is divided into two rounds, the first round is mainly to determine the length of the device-tree structure, stored 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;


/* When initializing, do not scan to a node or property to allocate the corresponding memory, in fact, the kernel is a one-time allocation of a large amount of memory, the memory includes all the struct Device_node, node name, struct The memory required by the property. */
MEM = (unsigned long)
Dt_alloc (size + 4, __alignof__ (struct device_node));
((__BE32 *) mem) [SIZE/4] = Cpu_to_be32 (0xdeadbeef);


/* This is the second scan, the first scan is to get the memory of all node and property required size, the second time is real deal to build device node tree * *
Start = ((unsigned long) blob) +
BE32_TO_CPU (blob->off_dt_struct);
Unflatten_dt_node (BLOB, mem, &start, NULL, &ALLNEXTP, 0);
Check overflow and checksum of_dt_end are omitted here.
}
4, integrated into the Linux kernel device driver model
After the introduction of the unified device model in Linux kernel, bus, driver, and device form the Iron triangle in the device model. When the driver is initialized, a data structure representing the driver (typically Xxx_driver) is attached to the driver linked list on the bus. Device hangs into a linked list in two cases, a Plug and Play type of bus, after inserting a device, the bus can detect this behavior and dynamically assign a device data structure (typically xxx_device, such as Usb_device), then, The data structure is attached to the device linked list on the bus. The bus is full of driver and device, so how do you let device encounter the "right" of the driver? is the match function of the bus.
The system should dynamically increase the platform_device in the system according to the device tree (this process does not occur only on platform bus, but also on other non-plug-and-play buses, such as the AMBA Bus, PCI bus). If you want to incorporate the device driver model of the Linux kernel, you will need to attach each device node to the corresponding bus device list based on the Device_node tree structure (root is of_allnodes). As long as this is done, the bus mechanism will arrange for device and driver appointments. Of course, not all device node will be linked to the list of devices on the bus, such as CPUs node,memory node,choose node.
4.1 Device node with no bus attached
(1) Processing of CPUs node
No, only choose node related processing.
(2) Processing of memory
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);
/* At initialization time, we will invoke the call back function for each device node, so we will filter out the node that is not related to memory block definition. There are two types of nodes defined with memory block, one is node name [email protected] form, and the other is the Device_type attribute defined in node 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;
/* Gets the start address and length information for the memory. There are two properties related to this information, one is linux,usable-memory, but the most recent way 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));
The value of the/*reg property is the address,size array, so how do you take out the address/size? Since memory node must be a child of root node, Dt_root_addr_cells (root node's #address-cells attribute value) and Dt_root_size_cells (root node # The sum of the Size-cells property values is the entry size of the address,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
The specific memory block information is added to the kernel.
Early_init_dt_add_memory_arch (base, size);
}
return 0;
}
(3) Handling of interrupt Controller
Initialization is achieved through START_KERNEL->INIT_IRQ->MACHINE_DESC->INIT_IRQ (). We use Qualcomm MSM 8974 as an example to describe the processing process of the 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 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 function: Traverse the device Tree to find a matching irqchip. The specific code is as follows:
void __init of_irq_init (const struct OF_DEVICE_ID *matches)
{
/* Iterate through all of the node, looking for node that defines the Interrupt-controller attribute, and if the Interrupt-controller attribute is defined, then the node is an interrupt controller. */
For_each_matching_node (NP, matches) {
if (!of_find_property (NP, "Interrupt-controller", NULL))
Continue
/* Allocates memory and hangs it into the linked list, and of course establishes a parent-child relationship between controllers based on Interrupt-parent. For interrupt controller, it can also be a tree-like structure. */
desc = kzalloc (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-like structure, the order of initialization needs to be controlled, starting from the root node and sequentially to the next level of the interrupt controller. */
while (!list_empty (&intc_desc_list)) {
The nodes in the/*intc_desc_list list are processed one after the other, each node is deleted, and when all the nodes are deleted, the whole process is finished. */
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 very beginning, the parent variable is null, ensuring that the first one being processed is the root interrupt controller. After the root node has been processed, the parent variable is set to the root interrupt controller, so the second loop deals with all the child of the parent who is the root interrupt controller. Interrupt Controller. That is, Level 1 (if root is level 0) node. */
if (desc->interrupt_parent! = parent)
Continue


List_del (&desc->list);//delete from 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;//performs the initialization function
ret = IRQ_INIT_CB (Desc->dev, desc->interrupt_parent);
/* The completed node is placed in the Intc_parent_list list, followed by the */
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, so to traverse these parent interrupt controllers, To handle 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 that node has interrupt-controller this attribute definition, then the Linux kernel assigns a interrupt controller descriptor (struct INTC_DESC) and hangs it into the queue. The Interrupt-parent property allows you to determine the hierarchical relationship of each interrupt controller. After the definition of the interrupt controller in all device tree has been scan, the system begins the matching process. Once the items in the interrupt chip list have been matched, the corresponding initialization function is called.

"Turn" (DT series three) when the system starts, how DTS is loaded

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.