How Linux kernel generates content from Devicetree Plateform_device
1, implementation scenario (versatile Express v2m as an example to illustrate its process)
In Arch/arm/mach-vexpress/v2m.c, for example, the V2m_dt_init function in this file is used to initialize the platform device using the DT device tree structure.
static void __init v2m_dt_init (void)
{
Of_platform_populate (NULL, Of_default_bus_match_table,
V2m_dt_auxdata_lookup, NULL);
......
}
Of_platform_populate implemented in DRIVERS/OF/PLATFORM.C, is the standard function of the. Call Of_platform_populate to add all the platform device to the kernel.
int of_platform_populate (struct Device_node *root,
const struct OF_DEVICE_ID *matches,
const struct Of_dev_auxdata *lookup,
struct device *parent)
{
root = root? Of_node_get (Root): Of_find_node_by_path ("/");
For_each_child_of_node (root, child) {
rc = Of_platform_bus_create (child, matches, lookup, parent, true);
}
......
}
In Of_platform_populate (), if Root is NULL, the root is assigned to roots, and the root node is taken with Of_find_node_by_path ().
struct Device_node *of_find_node_by_path (const char *path)
{
struct Device_node *np = allnodes;
Read_lock (&devtree_lock);
for (; NP; np = Np->allnext) {
if (Np->full_name && (of_node_cmp (np->full_name, path) = = 0)
&& Of_node_get (NP))
Break
}
Read_unlock (&devtree_lock);
return NP;
}
There is a key global variable in this function: Allnodes, which is defined in drivers/of/base.c: struct Device_node *allnodes;
This should be the so-called "Device Tree Data". It should point to the root node of device tree. The question came again, and how did this allnodes come? We know that device tree was compiled by the DTC (device tree Compiler) into a binary file DtB (Ddevice tree Blob) and then loaded into memory by bootloader after the system was power up, and this time there was no device Tree, and in memory there is only a so-called DTB, which is just a bunch of raw DT data starting with a memory address, without a tree structure. Kernel's mission needs to convert this data into a tree structure and then assign the address of the root node of the tree to Allnodes. This process must be very important, because without this device tree all the devices will not be able to initialize, so the formation of this dt tree must be completed when the kernel just started.
In that case, let's take a look at the code for kernel initialization (INIT/MAIN.C).
2, foreshadowing (Initialize device tree)
Kernel/init/main.c
asmlinkage void __init start_kernel (void)
{
Setup_arch (&command_line);
}
This setup_arch is the schema's own setup function, which is the one that participates in the compilation, and in this article it should be Setup_arch () in arch/arm/kernel/setup.c.
Kernel/arch/arm/setup.c
void __init Setup_arch (char **cmdline_p)
{
Mdesc = SETUP_MACHINE_FDT (__atags_pointer);
Unflatten_device_tree ();
}
This time DTB is just loaded into the in-memory. DTB file, which contains not only the data structure, but also some of the file first-class information, kernel need to obtain information about the data structure from this information, and then generate the device tree.
struct Machine_desc * __init setup_machine_fdt (unsigned int dt_phys)
{
struct Boot_param_header *devtree;
Devtree = Phys_to_virt (Dt_phys);
Initial_boot_params = Devtree;
}
Phys_to_virt literally means that a physical address translates to a virtual address, that is to say that __atags_pointer is a physical address, that is, __atags_pointer is really a pointer, and then see the variable devtree it points to a struct boot_ Param_header structural body. Kernel then assigns the pointer to the global variable Initial_boot_params. This means that the kernel will be used to initialize the device tree with the data that the pointer points to.
struct Boot_param_header {
__be32 Magic; /* Magic word Of_dt_header */
__be32 totalsize; /* Total size of DT block */
__be32 off_dt_struct; /* Offset to structure */
__be32 off_dt_strings; /* Offset to strings */
__be32 Off_mem_rsvmap; /* Offset to Memory reserve map */
__BE32 version; /* Format version */
__be32 last_comp_version; /* Last compatible version */
/* Version 2 fields below */
__be32 Boot_cpuid_phys; /* Physical CPU ID we ' re booting on */
/* Version 3 fields below */
__be32 dt_strings_size; /* Size of the DT strings block */
/* Version below fields */
__be32 dt_struct_size; /* Size of the DT structure block */
};
Look at this structure, much like the previously mentioned file header, there are magic number, size, data structure offset, version, etc., kernel should be through this structure to obtain data, and eventually generate the device tree. Now go back to Setup_arch, and sure enough, there is a function in the subsequent code: the node that transforms 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);
}
As you can see, Allnodes is assigned here, and device tree is officially established here.
Device node structure
struct Device_node {
const char *name;----------------------device node name
const char *type;-----------------------properties corresponding to Device_type
Phandle phandle;-----------------------to the Phandle property of the expected node
const char *full_name; ----------------begins with "/", indicating the full path of the node
struct property *properties;-------------The list of properties for the node
struct property *deadprops; ----------if necessary, remove some properties and mount the list into the Deadprops
struct Device_node *parent;------parent, child, and sibling connect all device node.
struct Device_node *child;
struct Device_node *sibling;
struct Device_node *next; --------this pointer allows you to get the next node of the same type
struct Device_node *allnext;-------The pointer to get the node global list next node
struct Proc_dir_entry *pde;--------open to userspace proc interface information
struct Kref kref;-------------the node's reference count
unsigned long _flags;
void *data;
};
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 *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.
}
At this end, the initialization of the device tree is complete, and kernel will initialize each device based on this DT during the subsequent boot process.
3, the process of creating platform device specifically
Then the first part of the description: Key anatomy Of_platform_bus_create () function
Of_platform_populate implemented in DRIVERS/OF/PLATFORM.C, is the standard function of the.
int of_platform_populate (struct Device_node *root,
const struct OF_DEVICE_ID *matches,
const struct Of_dev_auxdata *lookup,
struct device *parent)
{
root = root? Of_node_get (Root): Of_find_node_by_path ("/");
For_each_child_of_node (root, child) {
rc = Of_platform_bus_create (child, matches, lookup, parent, true);
}
......
}
A total of Of_find_node_by_path ("/") was completed in the first and second parts. This starts the Analysis function of_platform_bus_create ().
static int of_platform_bus_create (struct device_node *bus,------the device node to be created
const struct OF_DEVICE_ID *matches,------the list to match
const struct Of_dev_auxdata *lookup,------ancillary data
struct device *parent, bool strict)------Parent point
------whether the strict requires an exact match
{
const struct Of_dev_auxdata *auxdata;
struct Device_node *child;
struct Platform_device *dev;
const char *bus_id = NULL;
void *platform_data = NULL;
int rc = 0;
/* Make sure it have a compatible property */
if (Strict && (!of_get_property (Bus, "compatible", NULL))) {
Pr_debug ("%s ()-skipping%s, no compatible prop\n",
__func__, Bus->full_name);
return 0;
}
Auxdata = of_dev_lookup (lookup, bus);//The Incoming lookup table looks for additional data that matches the device node
if (auxdata) {
bus_id = auxdata->name;//If found, then use statically defined content in the attached data
Platform_data = auxdata->platform_data;
}
In addition to the CPU core provided by/*arm, it designed the AMBA bus to connect each block within the SOC. The peripherals on the SOC that meet this bus standard are called arm Primecell peripherals. If the Compatible property value of a device node is Arm,primecell, you can call Of_amba_device_create to add an Amba device to the Amba bus. */
if (of_device_is_compatible (bus, "Arm,primecell")) {
Of_amba_device_create (bus, bus_id, platform_data, parent);
return 0;
}
If it's not arm Primecell peripherals, then we need to add a platform device to the platform bus.
dev = of_platform_device_create_pdata (bus, bus_id, platform_data, parent);
if (!dev | |!of_match_node (matches, bus))
return 0;
/* A device node may be a bridge device, so repeat calls to of_platform_bus_create to get rid of all device node. */
For_each_child_of_node (bus, child) {
Pr_debug ("Create Child:%s\n", child->full_name);
rc = Of_platform_bus_create (child, matches, lookup, &dev->dev, strict);
if (RC) {
Of_node_put (child);
Break
}
}
return RC;
}
Specifically add platform device code in Of_platform_device_create_pdata, the code is as follows:
struct Platform_device *of_platform_device_create_pdata (
struct Device_node *NP,
const Char *BUS_ID,
void *platform_data,
struct device *parent)
{
struct Platform_device *dev;
The IF (!of_device_is_available (NP))//check Status property is guaranteed to be enable or OK.
return NULL;
In addition to allocating the memory of the struct Platform_device,/*of_device_alloc allocates the resource memory required by the platform device. Of course, this requires parsing the device node's interrupt resources and memory address resources. */
dev = Of_device_alloc (NP, bus_id, parent);
Set other members in the Platform_device
Dev->dev.coherent_dma_mask = Dma_bit_mask (sizeof (dma_addr_t) * 8);
Dev->dev.bus = &platform_bus_type;
Dev->dev.platform_data = Platform_data;
/* We don't fill the DMA Ops for platform devices by default.
* This is currently the responsibility of the Platform code
* To do such, possibly using a device notifier
*/
if (Of_device_add (dev)! = 0) {
Platform_device_put (dev);//Add this platform device to the Unified Appliance Model system
return NULL;
}
return dev;
}
At this point, the Linux kernel has completely generated the corresponding platform device for the contents of the device tree.
(DT series five) Linux kernel How to generate the content in Devicetree plateform_device