Configuring a DTS and lining up the driver's compatible string is only the visible part of BSP work. Underneath, Linux has to build kernel objects from the flattened blob first. The device tree exists to replace the many static platform_device definitions that used to live in drivers: hardware resources are described once, parsed by common code, and handed to drivers through the device model. That means a DTS node must eventually become a struct device, while the driver only supplies the matching platform_driver.
The path is not a single function. During boot, setup_arch already touches the DTB through setup_machine_fdt and arm_memblock_init. After paging_init, it reaches unflatten_device_tree, where the flattened tree is expanded into device_node objects and aliases are scanned.
void __init unflatten_device_tree(void)
{
__unflatten_device_tree(initial_boot_params, NULL, &of_root,
early_init_dt_alloc_memory_arch, false);
/* Get pointer to "/chosen" and "/aliases" nodes for use everywhere */
of_alias_scan(early_init_dt_alloc_memory_arch);
unittest_unflatten_overlay_base();
}
Two passes over the blob
__unflatten_device_tree is called with the DTB start address, a NULL parent, &of_root as the output node pointer, early_init_dt_alloc_memory_arch as the allocator callback, and detached set to false. It first checks the FDT header. Then unflatten_dt_nodes(blob, NULL, dad, NULL) performs a sizing pass: no node data is filled in yet, but the total space needed for all device_node objects is calculated. The size is aligned to 4 bytes and passed to dt_alloc, which in this path ends up in memblock_alloc. A marker value of 0xdeadbeef is written just past the allocated area, and a second unflatten_dt_nodes pass fills the memory and returns the real node pointers. If the marker was overwritten, the kernel warns. When the tree is detached, OF_DETACHED is set on the root.
This two-pass design matters because the kernel cannot know how many nodes and properties it will create until it has walked the blob. The first walk measures; the second walk builds.
The objects that get built
Each DTS node becomes a struct device_node. Its name comes from the node name, phandle comes from phandle or linux,phandle, and full_name points just past the struct, where the node's unit name is stored. That means one allocation covers sizeof(struct device_node) plus the unit name plus alignment padding. The properties field is a linked list of struct property; parent, child, and sibling links preserve the tree shape. kobj is used when sysfs entries are created.
struct device_node {
const char *name;
phandle phandle;
const char *full_name;
struct fwnode_handle fwnode;
struct property *properties;
struct property *deadprops; /* removed properties */
struct device_node *parent;
struct device_node *child;
struct device_node *sibling;
#if defined(CONFIG_OF_KOBJ)
struct kobject kobj;
#endif
unsigned long _flags;
void *data;
#if defined(CONFIG_SPARC)
unsigned int unique_id;
struct of_irq_controller *irq_trans;
#endif
};
A property is small but central: it stores the name, length, value pointer, and the next property in the chain.
struct property {
char *name;
int length;
void *value;
struct property *next;
#if defined(CONFIG_OF_KOBJ)
struct bin_attribute attr;
#endif
}
populate_properties walks the property offsets in the blob. Ordinary properties are copied into struct property entries and linked together. phandle and linux,phandle are treated differently: they fill np->phandle directly and are not kept in the property list. This is why code can use of_node->phandle without searching the node's properties.

Aliases and chosen
After unflattening, of_alias_scan looks for /aliases and /chosen or /chosen@0. If a chosen node exists, it reads stdout-path, then falls back to linux,stdout-path; on PPC it can also read stdout from the aliases node. The resolved node becomes of_stdout, and the path options are stored in of_stdout_options. That controls where early console output goes.
If /aliases exists, the function iterates its properties, skipping name, phandle, and linux,phandle. Each remaining value is resolved to a device_node. The code then strips trailing digits from the property name to get the alias stem and parses the numeric id. An alias_prop is allocated, filled, and added to the global aliases_lookup list. This gives the kernel a lookup table for aliases without turning every alias entry into a device.
What qualifies as a platform_device
Not every device_node becomes a platform_device. A node generally needs a compatible property, and population normally starts from the first-level nodes under the root. Children of those nodes are not automatically treated as devices. If a node's compatible contains one of the bus values simple-bus, simple-mfd, isa, or arm,amba-bus, and that node itself was successfully registered, then its children with compatible can also be converted. The root node is an exception: even if it has a compatible property, it is not processed as a platform device.
The hardware resources do get translated. A node's reg property becomes struct resource entries inside the platform_device, and interrupts becomes IRQ resources. Resource-related names such as reg-names and interrupt-names are handled in the same area. Other properties are not copied into platform data. Instead, the platform_device has a struct device dev, and dev.of_node points back to the original device_node. Drivers can then read the tree directly through pdev->dev.of_node.
The initcall and default bus table
Population is triggered by of_platform_default_populate_init, registered with arch_initcall_sync. During do_initcalls, the kernel walks from early initcalls through core, postcore, arch, subsystem, fs, device, and late initcalls, so this hook is reached. The function checks whether the DT was populated, handles /reserved-memory with a ramoops child explicitly because that area lacks a normal compatible, and then calls of_platform_default_populate(NULL, NULL, NULL). That forwards to of_platform_populate with of_default_bus_match_table.
const struct of_device_id of_default_bus_match_table[] = {
{ .compatible = "simple-bus", },
{ .compatible = "simple-mfd", },
{ .compatible = "isa", },
#ifdef CONFIG_ARM_AMBA
{ .compatible = "arm,amba-bus", },
#endif /* CONFIG_ARM_AMBA */
{} /* Empty terminated list */
};
This table is the rule set for recursion. A node matching one of those strings is treated as a bus, so its children can be considered for platform device creation.
Walking from root to bus
of_platform_populate starts at the given root or at / when root is NULL. It walks each child and calls of_platform_bus_create for it. The root is marked OF_POPULATED_BUS when the walk finishes.
int of_platform_populate(struct device_node *root,
const struct of_device_id *matches,
const struct of_dev_auxdata *lookup,
struct device *parent)
{
struct device_node *child;
int rc = 0;
// Get the device_node for the root from the device tree
root = root ? of_node_get(root) : of_find_node_by_path("/");
if (!root)
return -EINVAL;
pr_debug("%s()\n", __func__);
pr_debug(" starting at: %pOF\n", root);
// Walk all child nodes
for_each_child_of_node(root, child) {
// Create a bus for each first-level child under the root
// For example, /r1 and /r2, not /r1/s1
rc = of_platform_bus_create(child, matches, lookup, parent, true);
if (rc) {
of_node_put(child);
break;
}
}
of_node_set_flag(root, OF_POPULATED_BUS);
of_node_put(root);
return rc;
}
EXPORT_SYMBOL_GPL(of_platform_populate);
Inside of_platform_bus_create, the current node is first turned into a platform device by of_platform_device_create_pdata. If that fails, or if the node does not match the default bus table, the function returns and does not descend. If it does match, each child is passed back into of_platform_bus_create with &dev->dev as the parent. In other words, a bus node is registered as a device, and its children are recursively treated as devices on that bus.
Resources and registration
of_platform_device_create_pdata allocates the platform device with of_device_alloc, attaches it to platform_bus_type, stores platform data, configures MSI, and calls of_device_add. On failure it clears OF_POPULATED.
of_device_alloc is where the resource conversion becomes concrete. It counts reg entries with of_address_to_resource until the call fails, counts interrupts with of_irq_count, allocates one struct resource array for both, sets dev->num_resources and dev->resource, then converts the reg and IRQ entries. It also sets dev->dev.of_node to the device_node, sets dev->dev.fwnode to &np->fwnode, and uses platform_bus as the parent when no parent is supplied.
Finally, of_device_add calls device_add(&ofdev->dev). That registers the embedded struct device with the driver model and creates the user-space nodes under sysfs. From there the platform bus matching path runs, the compatible match is evaluated, and the driver's probe can be reached. The DTS-to-driver chain is complete only after the node has passed through the flattened blob, the device_node tree, the platform population walk, the resource conversion, and finally device registration.