基于arm平台,Linux 5.10
1,设备树的执行入口setup_arch
linux最底层的初始化部分在HEAD.s中,这是汇编代码,我们暂且不作过多讨论。
在head.s完成部分初始化之后,就开始调用C语言函数,而被调用的第一个C语言函数就是start_kernel:
asmlinkage __visible void __init __no_sanitize_address start_kernel(void)
{
......
setup_arch(&command_line);
......
}
而对于设备树的处理,基本上就在setup_arch()这个函数中。
可以看到,在start_kernel()中调用了setup_arch(&command_line);
void __init __no_sanitize_address setup_arch(char **cmdline_p)
{
......
//根据传入的设备树dtb首地址完成一些初始化操作
setup_machine_fdt(__fdt_pointer);
//保证设备树dtb本身存在于内存中而不被覆盖
arm64_memblock_init();
//对设备树的具体解析
unflatten_device_tree();
......
}
这三个被调用的函数就是主要的设备树处理函数:
* setup_machine_fdt():根据传入的设备树dtb的首地址完成一些初始化操作。
*arm64_memblock_init():主要是内存相关函数,为设备树保留相应的内存空间,保证设备树dtb本身存在于内存中而不被覆盖。用户可以在设备树中设置保留内存,这一部分同时作了保留指定内存的工作。
* unflatten_device_tree():对设备树具体的解析,事实上在这个函数中所做的工作就是将设备树各节点转换成相应的struct device_node结构体。
2,dtb -> device_node转换过程
kernel V5.10:
start_kernel(void) /* D:\work\source_code\msm-kernel\msm_kernel\init\main.c */
----setup_arch(&command_line); /* D:\work\source_code\msm-kernel\msm_kernel\arch\arm64\kernel\setup.c */
--------setup_machine_fdt(__fdt_pointer); /* D:\work\source_code\msm-kernel\msm_kernel\arch\arm64\kernel\setup.c */
------------early_init_dt_scan(dt_virt)
----------------early_init_dt_scan_nodes();
--------------------of_scan_flat_dt(early_init_dt_scan_chosen, boot_command_line);
--------------------of_scan_flat_dt(early_init_dt_scan_root, NULL);
--------------------of_scan_flat_dt(early_init_dt_scan_memory, NULL);
--------arm64_memblock_init();
------------early_init_fdt_scan_reserved_mem();
----------------early_init_dt_reserve_memory_arch(base, size, false);
--------------------memblock_reserve(base, size);
--------unflatten_device_tree(); /* D:\work\source_code\msm-kernel\msm_kernel\drivers\of\fdt.c */
------------__unflatten_device_tree(initial_boot_params, NULL, &of_root, early_init_dt_alloc_memory_arch, false);
----------------unflatten_dt_nodes(blob, NULL, dad, NULL);
--------------------populate_node(blob, offset, &mem, nps[depth], &nps[depth+1], dryrun))
------------------------np = unflatten_dt_alloc(mem, sizeof(struct device_node) + allocl, __alignof__(struct device_node));
------------------------populate_properties(blob, offset, mem, np, pathp, dryrun);
----------------------------fdt_getprop_by_offset(blob, cur, &pname, &sz);
--------of_alias_scan(early_init_dt_alloc_memory_arch);
--------unittest_unflatten_overlay_base();
3,Device Tree文件(DTB)结构描述的结构体
struct fdt_header {
fdt32_t magic; /* magic word FDT_MAGIC */
fdt32_t totalsize; /* total size of DT block */
fdt32_t off_dt_struct; /* offset to structure */
fdt32_t off_dt_strings; /* offset to strings */
fdt32_t off_mem_rsvmap; /* offset to memory reserve map */
fdt32_t version; /* format version */
fdt32_t last_comp_version; /* last compatible version */
/* version 2 fields below */
fdt32_t boot_cpuid_phys; /* Which physical CPU id we're
booting on */
/* version 3 fields below */
fdt32_t size_dt_strings; /* size of the strings block */
/* version 17 fields below */
fdt32_t size_dt_struct; /* size of the structure block */
};
struct fdt_reserve_entry {
fdt64_t address;
fdt64_t size;
};
struct fdt_node_header {
fdt32_t tag;
char name[0];
};
struct fdt_property {
fdt32_t tag;
fdt32_t len;
fdt32_t nameoff;
char data[0];
};
4,struct device_node/struct property关键结构体
4.1 struct device_node
Device Tree中的每一个node节点经过kernel处理都会生成一个struct device_node的结构体,
struct device_node最终一般会被挂接到具体的struct device结构体。struct device_node结构体描述如下:
struct device_node {
const char *name; /* node的名称,取最后一次“/”和“@”之间子串 */
const char *type; /* device_type的属性名称,没有为<NULL> */
phandle phandle; /* phandle属性值 */
const char *full_name; /* 指向该结构体结束的位置,存放node的路径全名,例如:/chosen */
struct fwnode_handle fwnode;
struct property *properties; /* 指向该节点下的第一个属性,其他属性与该属性链表相接 */
struct property *deadprops; /* removed properties */
struct device_node *parent; /* 父节点 */
struct device_node *child; /* 子节点 */
struct device_node *sibling; /* 姊妹节点,与自己同等级的node */
struct kobject kobj; /* sysfs文件系统目录体现 */
unsigned long _flags; /* 当前node状态标志位,见/include/linux/of.h line124-127 */
void *data;
};
/* flag descriptions (need to be visible even when !CONFIG_OF) */
#define OF_DYNAMIC 1 /* node and properties were allocated via kmalloc */
#define OF_DETACHED 2 /* node has been detached from the device tree*/
#define OF_POPULATED 3 /* device already created for the node */
#define OF_POPULATED_BUS 4 /* of_platform_populate recursed to children of this node */
4.2 struct property
kernel会根据Device Tree的结构解析出kernel能够使用的struct property结构体。
kernel根据Device Tree中所有的属性解析出数据填充struct property结构体。struct property 结构体描述如下:
struct property {
char *name;
int length;
void *value;
struct property *next; //kernel根据Device Tree的文件结构信息转换成struct property结构体,并将同一个node节点下面的所有属性通过property.next指针进行链接,形成一个单链表
#if defined(CONFIG_OF_DYNAMIC) || defined(CONFIG_SPARC)
unsigned long _flags;
#endif
#if defined(CONFIG_OF_PROMTREE)
unsigned int unique_id;
#endif
#if defined(CONFIG_OF_KOBJ)
struct bin_attribute attr;
#endif
};
5,setup_machine_fdt
bool __init early_init_dt_verify(void *params)
{
if (!params)
return false;
/* check device tree validity */
if (fdt_check_header(params))
return false;
/* Setup flat device-tree pointer */
//initial_boot_params: device tree blob的虚拟地址
initial_boot_params = params;
//计算扁平化设备树的crc32校验码
of_fdt_crc32 = crc32_be(~0, initial_boot_params,
fdt_totalsize(initial_boot_params));
return true;
}
扫描设备树中的各个子节点:
void __init early_init_dt_scan_nodes(void)
{
int rc = 0;
/* Retrieve various information from the /chosen node */
rc = of_scan_flat_dt(early_init_dt_scan_chosen, boot_command_line);
if (!rc)
pr_warn("No chosen node found, continuing without\n");
/* Initialize {size,address}-cells info */
of_scan_flat_dt(early_init_dt_scan_root, NULL);
/* Setup memory, calling early_init_dt_add_memory_arch */
of_scan_flat_dt(early_init_dt_scan_memory, NULL);
}
int __init of_scan_flat_dt(int (*it)(unsigned long node,
const char *uname, int depth,
void *data),
void *data);
扫描展开之前的flattened device tree,对于找到的每一个node调用it函数,data作为it函数的一个参数。
5.1 early_init_dt_scan_chosen
dts example :
chosen {
bootargs = "console=ttySAC0,115200n8 root=/dev/mmcblk0p1 rw rootwait ignore_loglevel earlyprintk";
};
解析过程:
/* Retrieve various information from the /chosen node */
rc = of_scan_flat_dt(early_init_dt_scan_chosen, boot_command_line);
int __init early_init_dt_scan_chosen(unsigned long node, const char *uname,
int depth, void *data)
{
int l = 0;
const char *p = NULL;
const void *rng_seed;
char *cmdline = data;
//扫描chosen节点,打印节点的名字和节点在设备树中的深度
pr_debug("search \"chosen\", depth: %d, uname: %s\n", depth, uname);
//chosen节点的深度为1,cmdline指针变量指向bootcmd信息,地址不能为空
if (depth != 1 || !cmdline ||
(strcmp(uname, "chosen") != 0 && strcmp(uname, "chosen@0") != 0))
return 0;
//从扁平设备树中获取initrd的位置
early_init_dt_check_for_initrd(node);
......
/* Retrieve command line unless forcing */
//从bootargs属性中读取cmdline
if (read_dt_cmdline)
p = of_get_flat_dt_prop(node, "bootargs", &l);
......
pr_debug("Command line is: %s\n", (char *)data);
}
解析出来的command line 存储在全局变量boot_command_line中,作为bootloader启动过程中向kernel的传参。
/* Untouched command line saved by arch-specific code. */
char __initdata boot_command_line[COMMAND_LINE_SIZE];
5.2 early_init_dt_scan_root
/* Initialize {size,address}-cells info */
of_scan_flat_dt(early_init_dt_scan_root, NULL);
/**
* early_init_dt_scan_root - fetch the top level address and size cells
*/
int __init early_init_dt_scan_root(unsigned long node, const char *uname,
int depth, void *data)
{
const __be32 *prop;
//扫描根节点的#address-cells 和 #size-cell属性,根节点深度为0
if (depth != 0)
return 0;
dt_root_size_cells = OF_ROOT_NODE_SIZE_CELLS_DEFAULT;
dt_root_addr_cells = OF_ROOT_NODE_ADDR_CELLS_DEFAULT;
//读取根节点的"#size-cells" property
prop = of_get_flat_dt_prop(node, "#size-cells", NULL);
if (prop)
//使用的设备树dtb文件是以大端序方式存储的,转换为cpu字节序,如果CPU字节序也为大端模式直接返回
dt_root_size_cells = be32_to_cpup(prop);
pr_debug("dt_root_size_cells = %x\n", dt_root_size_cells);
//读取根节点的"#address-cells" property
prop = of_get_flat_dt_prop(node, "#address-cells", NULL);
if (prop)
dt_root_addr_cells = be32_to_cpup(prop);
pr_debug("dt_root_addr_cells = %x\n", dt_root_addr_cells);
/* break now */
return 1;
}
解析出来的根节点的#address-cells 和 #size-cells 保存在全局变量中,用来描述地址的属性,即地址的起始位置和所占内存大小。
/* Everything below here references initial_boot_params directly. */
int __initdata dt_root_addr_cells;
int __initdata dt_root_size_cells;
5.3 early_init_dt_scan_memory
dts example:
memory
memory { device_type = "memory"; reg = <0 0 0 0>; };
解析过程:
/* Setup memory, calling early_init_dt_add_memory_arch */
of_scan_flat_dt(early_init_dt_scan_memory, NULL);
/**
* early_init_dt_scan_memory - Look for and parse memory nodes
*/
int __init early_init_dt_scan_memory(unsigned long node, const char *uname,
int depth, void *data)
{
const char *type = of_get_flat_dt_prop(node, "device_type", NULL);
const __be32 *reg, *endp;
int l;
bool hotpluggable;
//device_type需要是memory
/* We are scanning "memory" nodes only */
if (type == NULL || strcmp(type, "memory") != 0)
return 0;
endp = reg + (l / sizeof(__be32));
hotpluggable = of_get_flat_dt_prop(node, "hotpluggable", NULL);
pr_debug("memory scan node %s, reg size %d,\n", uname, l);
//根据address-cells 和 size-cells解析memory reg
while ((endp - reg) >= (dt_root_addr_cells + dt_root_size_cells)) {
u64 base, size;
base = dt_mem_next_cell(dt_root_addr_cells, ®);
size = dt_mem_next_cell(dt_root_size_cells, ®);
if (size == 0)
continue;
pr_debug(" - %llx , %llx\n", (unsigned long long)base,
(unsigned long long)size);
//memblock_add(base, size)
early_init_dt_add_memory_arch(base, size);
if (!hotpluggable)
continue;
//memblock_reserve(base, size)
if (early_init_dt_mark_hotplug_memory_arch(base, size))
pr_warn("failed to mark hotplug range 0x%llx - 0x%llx\n",
base, base + size);
}
return 0;
}
扫描具有device_type = “memory”属性的/memory或者/memory@0节点下面的reg属性值,并把相关信息保存在meminfo中,全局变量meminfo保存了系统内存相关的信息。
所有设备树都需要一个memory设备节点,它描述了系统的物理内存布局。如果系统有多个内存块,可以创建多个memory节点,或者可以在单个memory节点的reg属性中指定这些地址范围和内存空间大小。
6,arm64_memblock_init
dts example:
memreserve
/memreserve/ 0x0000a800 0x000f5800;
reserved-memory
reserved_memory: reserved-memory {
#address-cells = <2>;
#size-cells = <2>;
ranges;
hyp_mem: hyp_region@80000000 {
no-map;
reg = <0x0 0x80000000 0x0 0x600000>;
};
xbl_dtlog_mem: xbl_dtlog_region@80600000 {
no-map;
reg = <0x0 0x80600000 0x0 0x40000>;
};
xbl_ramdump_mem: xbl_ramdump_region@80640000 {
no-map;
reg = <0x0 0x80640000 0x0 0x1c0000>;
};
aop_image_mem: aop_image_region@80800000 {
no-map;
reg = <0x0 0x80800000 0x0 0x60000>;
};
};
解析过程:
/**
* early_init_fdt_scan_reserved_mem() - create reserved memory regions
*
* This function grabs memory from early allocator for device exclusive use
* defined in device tree structures. It should be called by arch specific code
* once the early allocator (i.e. memblock) has been fully activated.
*/
void __init early_init_fdt_scan_reserved_mem(void)
{
int n;
u64 base, size;
if (!initial_boot_params)
return;
/* Process header /memreserve/ fields */
// 解析/memreserve/,告诉内核哪一些内存空间需要被保留而不应该被系统覆盖使用,因为在内核启动时常常需要动态申请大量的内存空间,只有提前进行注册,用户需要使用的内存才不会被系统征用而造成数据覆盖
for (n = 0; ; n++) {
fdt_get_mem_rsv(initial_boot_params, n, &base, &size);
if (!size)
break;
early_init_dt_reserve_memory_arch(base, size, false);
}
// 解析"reserved-memory"节点,分配保留空间
of_scan_flat_dt(__fdt_scan_reserved_mem, NULL);
fdt_init_reserved_mem();
}
有时我们需要在 Linux 内核中预留一部分内存空间用作特殊用途(给安全模块使用,给其它处理器使用,或是给特定的驱动程序使用等),在 Device Tree 中有提供两种方法对预留内存进行配置:memreserve 和 reserved-memory。
1) memreserve
memreserve 的使用方法比较简单,如下所示,会将从地址 0x40000000 开始共 1MB 的内存空间预留出来:
/memreserve/ 0x40000000 0x00100000;
使用 memreserve 预留出来的内存一般无法再被 Linux 系统使用(当然,也可以通过特殊方法让代码固定访问该地址,但这种并非标准用法,在此不展开描述)。
2) reserved-memory
reserved-memory 框架提供了更多样的使用方法,并且与内核的DMA API 和 CMA框架紧密联系。
推荐先阅读一下内核自带的文档 Documentation/devicetree/bindings/reserved-memory/reserved-memory.txt,里面有其详细的语法说明和注意事项(例如 reserved-memory 节点中的 #address-cells 和 #size-cells 的值需要与根节点的保持一致)。
7,unflatten_device_tree
Device Tree的解析首先从unflatten_device_tree()开始,代码列出如下:
/**
* unflatten_device_tree - create tree of device_nodes from flat blob
*
* unflattens the device-tree passed by the firmware, creating the
* tree of struct device_node. It also fills the "name" and "type"
* pointers of the nodes so the normal device-tree walking functions
* can be used.
*/
void __init unflatten_device_tree(void)
{
//参数initial_boot_params指向Device Tree在内存中的首地址,of_root在经过该函数处理之后,会指向根节点(of_root节点的树结构)
//early_init_dt_alloc_memory_arch是一个函数指针,为struct device_node和struct property结构体分配内存的回调函数(callback)
__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();
}
/**
* __unflatten_device_tree - create tree of device_nodes from flat blob
*
* unflattens a device-tree, creating the
* tree of struct device_node. It also fills the "name" and "type"
* pointers of the nodes so the normal device-tree walking functions
* can be used.
* @blob: The blob to expand
* @dad: Parent device node
* @mynodes: The device_node tree created by the call
* @dt_alloc: An allocator that provides a virtual address to memory
* for the resulting tree
* @detached: if true set OF_DETACHED on @mynodes
*
* Returns NULL on failure or the memory chunk containing the unflattened
* device tree on success.
*/
void *__unflatten_device_tree(const void *blob,
struct device_node *dad,
struct device_node **mynodes,
void *(*dt_alloc)(u64 size, u64 align),
bool detached)
{
int size;
void *mem;
pr_debug(" -> unflatten_device_tree()\n");
if (!blob) {
pr_debug("No device tree pointer\n");
return NULL;
}
pr_debug("Unflattening device tree:\n");
pr_debug("magic: %08x\n", fdt_magic(blob));
pr_debug("size: %08x\n", fdt_totalsize(blob));
pr_debug("version: %08x\n", fdt_version(blob));
//检查dtb header是否有效
if (fdt_check_header(blob)) {
pr_err("Invalid device tree blob header\n");
return NULL;
}
/* First pass, scan for size */
//第一次是为了得到Device Tree转换成struct device_node和struct property结构体需要分配的内存大小
size = unflatten_dt_nodes(blob, NULL, dad, NULL);
if (size < 0)
return NULL;
size = ALIGN(size, 4);
pr_debug(" size is %d, allocating...\n", size);
/* Allocate memory for the expanded device tree */
mem = dt_alloc(size + 4, __alignof__(struct device_node));
if (!mem)
return NULL;
memset(mem, 0, size);
*(__be32 *)(mem + size) = cpu_to_be32(0xdeadbeef);
pr_debug(" unflattening %p...\n", mem);
/* Second pass, do actual unflattening */
//第二次调用才是具体填充每一个struct device_node和struct property结构体
unflatten_dt_nodes(blob, mem, dad, mynodes);
if (be32_to_cpup(mem + size) != 0xdeadbeef)
pr_warn("End of tree marker overwritten: %08x\n",
be32_to_cpup(mem + size));
if (detached && mynodes) {
of_node_set_flag(*mynodes, OF_DETACHED);
pr_debug("unflattened tree is detached\n");
}
pr_debug(" <- unflatten_device_tree()\n");
return mem;
}
展开设备树节点unflatten_dt_nodes:
/**
* unflatten_dt_nodes - Alloc and populate a device_node from the flat tree
* @blob: The parent device tree blob
* @mem: Memory chunk to use for allocating device nodes and properties
* @dad: Parent struct device_node
* @nodepp: The device_node tree created by the call
*
* It returns the size of unflattened device tree or error code
*/
static int unflatten_dt_nodes(const void *blob,
void *mem,
struct device_node *dad,
struct device_node **nodepp)
{
struct device_node *root;
int offset = 0, depth = 0, initial_depth = 0;
#define FDT_MAX_DEPTH 64
struct device_node *nps[FDT_MAX_DEPTH];
void *base = mem;
//计算展开的设备树占用内存空间大小的时候dryrun为1
bool dryrun = !base;
if (nodepp)
*nodepp = NULL;
/*
* We're unflattening device sub-tree if @dad is valid. There are
* possibly multiple nodes in the first level of depth. We need
* set @depth to 1 to make fdt_next_node() happy as it bails
* immediately when negative @depth is found. Otherwise, the device
* nodes except the first one won't be unflattened successfully.
*/
if (dad)
depth = initial_depth = 1;
root = dad;
nps[depth] = dad;
//遍历dtb,解析每一个device_node和property
for (offset = 0;
offset >= 0 && depth >= initial_depth;
offset = fdt_next_node(blob, offset, &depth)) {
//子节点的深度不能超过最大深度64
if (WARN_ON_ONCE(depth >= FDT_MAX_DEPTH - 1))
continue;
if (!IS_ENABLED(CONFIG_OF_KOBJ) &&
!of_fdt_device_is_available(blob, offset))
continue;
//填充节点
if (!populate_node(blob, offset, &mem, nps[depth],
&nps[depth+1], dryrun))
return mem - base;
//根节点 of_root
if (!dryrun && nodepp && !*nodepp)
*nodepp = nps[depth+1];
if (!dryrun && !root)
root = nps[depth+1];
}
if (offset < 0 && offset != -FDT_ERR_NOTFOUND) {
pr_err("Error %d processing FDT\n", offset);
return -EINVAL;
}
/*
* Reverse the child list. Some drivers assumes node order matches .dts
* node order
*/
if (!dryrun)
reverse_nodes(root);
return mem - base;
}
填充节点:
static bool populate_node(const void *blob,
int offset,
void **mem,
struct device_node *dad,
struct device_node **pnp,
bool dryrun)
{
struct device_node *np;
const char *pathp;
unsigned int l, allocl;
//获取节点的名字
pathp = fdt_get_name(blob, offset, &l);
if (!pathp) {
*pnp = NULL;
return false;
}
//device node name以 '\0'为结束符
allocl = ++l;
//为device_node 和 节点名字 分配内存空间
np = unflatten_dt_alloc(mem, sizeof(struct device_node) + allocl,
__alignof__(struct device_node));
if (!dryrun) {
char *fn;
//初始化node,设置node的kobj和fwnode->ops
of_node_init(np);
np->full_name = fn = ((char *)np) + sizeof(*np);
//设置node的full_name
memcpy(fn, pathp, l);
//设置node的父亲节点和兄弟节点
if (dad != NULL) {
np->parent = dad;
np->sibling = dad->child;
dad->child = np;
}
}
//填充节点的属性
populate_properties(blob, offset, mem, np, pathp, dryrun);
if (!dryrun) {
np->name = of_get_property(np, "name", NULL);
if (!np->name)
np->name = "<NULL>";
}
*pnp = np;
return true;
}
填充节点的属性:
static void populate_properties(const void *blob,
int offset,
void **mem,
struct device_node *np,
const char *nodename,
bool dryrun)
{
struct property *pp, **pprev = NULL;
int cur;
bool has_name = false;
pprev = &np->properties;
//遍历该节点下的所有属性
for (cur = fdt_first_property_offset(blob, offset);
cur >= 0;
cur = fdt_next_property_offset(blob, cur)) {
const __be32 *val;
const char *pname;
u32 sz;
//获取属性的名字和值 key = value
val = fdt_getprop_by_offset(blob, cur, &pname, &sz);
if (!val) {
pr_warn("Cannot locate property at 0x%x\n", cur);
continue;
}
if (!pname) {
pr_warn("Cannot find property name at 0x%x\n", cur);
continue;
}
//如果有"name" 属性,做个标记
if (!strcmp(pname, "name"))
has_name = true;
//为属性分配内存空间
pp = unflatten_dt_alloc(mem, sizeof(struct property),
__alignof__(struct property));
if (dryrun)
continue;
/* We accept flattened tree phandles either in
* ePAPR-style "phandle" properties, or the
* legacy "linux,phandle" properties. If both
* appear and have different values, things
* will get weird. Don't do that.
*/
//对于"phandle"属性和"linux,phandle"属性,直接填充struct device_node的phandle字段,不放在属性链表中
if (!strcmp(pname, "phandle") ||
!strcmp(pname, "linux,phandle")) {
if (!np->phandle)
np->phandle = be32_to_cpup(val);
}
/* And we process the "ibm,phandle" property
* used in pSeries dynamic device tree
* stuff
*/
if (!strcmp(pname, "ibm,phandle"))
np->phandle = be32_to_cpup(val);
//填充property 结构体成员
pp->name = (char *)pname;
pp->length = sz;
pp->value = (__be32 *)val;
//加入属性链表
*pprev = pp;
pprev = &pp->next;
}
/* With version 0x10 we may not have the name property,
* recreate it here from the unit name if absent
*/
//为每个node节点添加一个name的属性
//node的名称,取最后一次“/”和“@”之间子串
if (!has_name) {
const char *p = nodename, *ps = p, *pa = NULL;
int len;
while (*p) {
if ((*p) == '@')
pa = p;
else if ((*p) == '/')
ps = p + 1;
p++;
}
if (pa < ps)
pa = p;
len = (pa - ps) + 1;
pp = unflatten_dt_alloc(mem, sizeof(struct property) + len,
__alignof__(struct property));
if (!dryrun) {
pp->name = "name";
pp->length = len;
pp->value = pp + 1;
*pprev = pp;
pprev = &pp->next;
memcpy(pp->value, ps, len - 1);
((char *)pp->value)[len - 1] = 0;
pr_debug("fixed up name for %s -> %s\n",
nodename, (char *)pp->value);
}
}
if (!dryrun)
*pprev = NULL;
}
此后,内核就可以根据device_node来创建设备。
参考链接:
https://www.cnblogs.com/schips/p/linux_driver_dtb_to_device_node.html