[TOC]
OF 在 Linux 内核中,of
通常是 Open Firmware 的缩写。Open Firmware 是一种硬件设备描述标准,用于描述设备树(Device Tree)。设备树是一种数据结构,用于以树状层次结构描述硬件的布局和配置,特别是在嵌入式系统和 ARM 架构中广泛使用。
of 表示与设备树(Device Tree)相关的驱动代码。
fdt.c
表示与 Flat Device Tree (FDT,扁平设备树)相关的实现。
设备树的作用 设备树的主要作用是将硬件信息从内核代码中分离出来,使得内核可以通过解析设备树来获取硬件配置,而无需硬编码。这种方式提高了内核的可移植性和灵活性。
在 Linux 内核中,fwnode
和 node
是两种不同的结构体,分别用于描述硬件设备的固件信息和设备树节点信息。它们的主要区别在于用途和适用场景。
fwnode和node的区别 1. fwnode_handle
fwnode_handle
是一个通用的固件节点(firmware node)抽象,用于描述硬件设备的固件信息。它可以适配多种固件接口,例如设备树(Device Tree)、ACPI(Advanced Configuration and Power Interface)等。
特点
通用性 :fwnode_handle
是一个抽象层,能够统一表示不同固件接口(如设备树和 ACPI)的节点信息。
适配性 : 内核通过 fwnode_handle
提供了一组通用的操作接口,屏蔽了底层固件实现的差异。
灵活性 : 适用于需要支持多种固件接口的驱动程序。
常见字段 fwnode_handle
是一个结构体,通常包含以下字段:
ops
: 指向操作函数的指针,用于实现具体固件接口的操作。
secondary
: 指向辅助固件节点的指针。
data
: 用于存储与固件节点相关的私有数据。
适用场景
ACPI 和设备树的统一处理。
驱动程序中需要与固件节点交互时使用,例如中断映射、GPIO 配置等。
2. device_node
device_node
是设备树(Device Tree)中用于描述硬件设备的节点结构。设备树是一种硬件描述语言,广泛用于嵌入式系统中。
特点
专用性 :device_node
专门用于设备树,不能直接适配其他固件接口(如 ACPI)。
层次性 : 设备树节点具有父子关系,device_node
通过指针字段表示这种层次结构。
静态性 : 设备树通常在系统启动时加载,节点信息在运行时不会动态变化。
常见字段 device_node
是一个结构体,通常包含以下字段:
name
: 节点名称。
type
: 节点类型。
parent
: 指向父节点的指针。
child
和 sibling
: 用于表示子节点和兄弟节点的指针。
properties
: 节点的属性列表。
适用场景
嵌入式系统中基于设备树的硬件描述。
驱动程序中解析设备树节点以获取硬件配置信息。
3. 区别总结
特性
fwnode_handle
device_node
用途
通用固件节点抽象
专用于设备树节点
适用固件接口
支持设备树、ACPI 等多种固件接口
仅支持设备树
灵活性
高,适配多种固件实现
低,仅适用于设备树
层次结构
无直接层次关系
支持父子关系和兄弟关系
使用场景
驱动程序中统一处理固件节点
嵌入式系统中解析设备树
4. 关联关系
fwnode_handle
可以通过适配器与 device_node
关联。例如,of_node_to_fwnode()
函数可以将 device_node
转换为 fwnode_handle
。
在设备树场景中,fwnode_handle
的实现通常基于 device_node
。
5. 示例代码 从 device_node
转换为 fwnode_handle
1 2 struct device_node *node = ...; struct fwnode_handle *fwnode = of_node_to_fwnode(node);
从 fwnode_handle
转换为 device_node
1 2 struct fwnode_handle *fwnode = ...; struct device_node *node = to_of_node(fwnode);
6. 总结
fwnode_handle
是一个通用的固件节点抽象,适用于需要支持多种固件接口的场景。
device_node
是设备树的专用节点结构,适用于嵌入式系统中基于设备树的硬件描述。
在设备树场景中,fwnode_handle
和 device_node
可以通过适配器互相转换。
include/linux/of.h _OF_DECLARE 设备树兼容性表的宏定义 1 2 3 4 5 6 7 8 9 10 11 #if defined(CONFIG_OF) && !defined(MODULE) #define _OF_DECLARE(table, name, compat, fn, fn_type) \ static const struct of_device_id __of_table_##name \ __used __section("__" #table "_of_table" ) \ __aligned(__alignof__(struct of_device_id)) \ = { .compatible = compat, \ .data = (fn == (fn_type)NULL) ? fn : fn } #else #define _OF_DECLARE(table, name, compat, fn, fn_type) \ _OF_DECLARE_STUB(table, name, compat, fn, fn_type) #endif
of_node_init 初始化设备节点 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 static inline void of_node_init (struct device_node *node) { #if defined(CONFIG_OF_KOBJ) kobject_init(&node->kobj, &of_node_ktype); #endif fwnode_init(&node->fwnode, &of_fwnode_ops); }
for_each_matching_node_and_match 遍历匹配的节点和匹配项
注意按照FDT的顺序遍历,每个节点都会调用一次匹配函数,matches可以有多个匹配项
1 2 3 #define for_each_matching_node_and_match(dn, matches, match) \ for (dn = of_find_matching_node_and_match(NULL, matches, match); \ dn; dn = of_find_matching_node_and_match(dn, matches, match))
for_each_of_allnodes_from 遍历所有节点 1 2 #define for_each_of_allnodes_from(from, dn) \ for (dn = __of_find_all_nodes(from); dn; dn = __of_find_all_nodes(dn))
of_for_each_phandle 遍历 phandle 句柄 1 2 3 4 5 #define of_for_each_phandle(it, err, np, ln, cn, cc) \ for (of_phandle_iterator_init((it), (np), (ln), (cn), (cc)), \ err = of_phandle_iterator_next(it); \ err == 0; \ err = of_phandle_iterator_next(it))
DEFINE_FREE(device_node) 1 DEFINE_FREE(device_node, struct device_node *, if (_T) of_node_put(_T))
of_read_number 读取大数字 1 2 3 4 5 6 7 8 static inline u64 of_read_number (const __be32 *cell, int size) { u64 r = 0 ; for (; size--; cell++) r = (r << 32 ) | be32_to_cpu(*cell); return r; }
include/linux/of_reserved_mem.h RESERVEDMEM_OF_DECLARE 预留内存区域的设备树兼容性表的宏定义 1 2 3 4 5 #ifdef CONFIG_OF_RESERVED_MEM #define RESERVEDMEM_OF_DECLARE(name, compat, init) \ _OF_DECLARE(reservedmem, name, compat, init, reservedmem_of_init_fn) #endif
drivers/of/address.c of_match_bus 匹配设备树总线 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 static const struct of_bus of_busses [] = {#ifdef CONFIG_PCI { .name = "pci" , .addresses = "assigned-addresses" , .match = of_bus_pci_match, .count_cells = of_bus_pci_count_cells, .map = of_bus_pci_map, .translate = of_bus_default_flags_translate, .flag_cells = 1 , .get_flags = of_bus_pci_get_flags, }, #endif { .name = "isa" , .addresses = "reg" , .match = of_bus_isa_match, .count_cells = of_bus_isa_count_cells, .map = of_bus_isa_map, .translate = of_bus_default_flags_translate, .flag_cells = 1 , .get_flags = of_bus_isa_get_flags, }, { .name = "default-flags" , .addresses = "reg" , .match = of_bus_default_flags_match, .count_cells = of_bus_default_count_cells, .map = of_bus_default_flags_map, .translate = of_bus_default_flags_translate, .flag_cells = 1 , .get_flags = of_bus_default_flags_get_flags, }, { .name = "default" , .addresses = "reg" , .match = of_bus_default_match, .count_cells = of_bus_default_count_cells, .map = of_bus_default_map, .translate = of_bus_default_translate, .get_flags = of_bus_default_get_flags, }, }; static unsigned int of_bus_default_get_flags (const __be32 *addr) { return IORESOURCE_MEM; } static void of_bus_default_count_cells (struct device_node *dev, int *addrc, int *sizec) { if (addrc) *addrc = of_n_addr_cells(dev); if (sizec) *sizec = of_n_size_cells(dev); } static int of_bus_default_match (struct device_node *np) { return of_property_present(np, "#address-cells" ); } static const struct of_bus *of_match_bus (struct device_node *np) { int i; for (i = 0 ; i < ARRAY_SIZE(of_busses); i++) if (!of_busses[i].match || of_busses[i].match(np)) return &of_busses[i]; return NULL ; }
__of_get_address 获取设备树地址 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 const __be32 *__of_get_address(struct device_node *dev, int index, int bar_no, u64 *size, unsigned int *flags) { const __be32 *prop; unsigned int psize; struct device_node *parent __free (device_node ) = of_get_parent(dev); const struct of_bus *bus ; int onesize, i, na, ns; if (parent == NULL ) return NULL ; bus = of_match_bus(parent); if (!bus || (strcmp (bus->name, "pci" ) && (bar_no >= 0 ))) return NULL ; prop = of_get_property(dev, bus->addresses, &psize); if (prop == NULL ) return NULL ; psize /= 4 ; bus->count_cells(dev, &na, &ns); if (!OF_CHECK_ADDR_COUNT(na)) return NULL ; onesize = na + ns; for (i = 0 ; psize >= onesize; psize -= onesize, prop += onesize, i++) { u32 val = be32_to_cpu(prop[0 ]); if (((bar_no >= 0 ) && ((val & 0xff ) == ((bar_no * 4 ) + PCI_BASE_ADDRESS_0))) || ((index >= 0 ) && (i == index))) { if (size) *size = of_read_number(prop + na, ns); if (flags) *flags = bus->get_flags(prop); return prop; } } return NULL ; } EXPORT_SYMBOL(__of_get_address);
of_translate_one 将设备树地址转换为 CPU 物理地址 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 static int of_translate_one (const struct device_node *parent, const struct of_bus *bus, const struct of_bus *pbus, __be32 *addr, int na, int ns, int pna, const char *rprop) { const __be32 *ranges; unsigned int rlen; int rone; u64 offset = OF_BAD_ADDR; ranges = of_get_property(parent, rprop, &rlen); if (ranges == NULL && !of_empty_ranges_quirk(parent) && strcmp (rprop, "dma-ranges" )) { pr_debug("no ranges; cannot translate\n" ); return 1 ; } if (ranges == NULL || rlen == 0 ) { offset = of_read_number(addr, na); memset (addr + pbus->flag_cells, 0 , (pna - pbus->flag_cells) * 4 ); pr_debug("empty ranges; 1:1 translation\n" ); goto finish; } pr_debug("walking ranges...\n" ); rlen /= 4 ; rone = na + pna + ns; for (; rlen >= rone; rlen -= rone, ranges += rone) { offset = bus->map (addr, ranges, na, ns, pna, bus->flag_cells); if (offset != OF_BAD_ADDR) break ; } if (offset == OF_BAD_ADDR) { pr_debug("not found !\n" ); return 1 ; } memcpy (addr, ranges + na, 4 * pna); finish: of_dump_addr("parent translation for:" , addr, pna); pr_debug("with offset: %llx\n" , offset); return pbus->translate(addr, offset, pna); }
of_translate_address 将设备树地址转换为 CPU 物理地址 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 static u64 __of_translate_address(struct device_node *node, struct device_node *(*get_parent)(const struct device_node *), const __be32 *in_addr, const char *rprop, struct device_node **host) { struct device_node *dev __free (device_node ) = of_node_get(node); struct device_node *parent __free (device_node ) = get_parent(dev); const struct of_bus *bus , *pbus ; __be32 addr[OF_MAX_ADDR_CELLS]; int na, ns, pna, pns; pr_debug("** translation for device %pOF **\n" , dev); *host = NULL ; if (parent == NULL ) return OF_BAD_ADDR; bus = of_match_bus(parent); if (!bus) return OF_BAD_ADDR; bus->count_cells(dev, &na, &ns); if (!OF_CHECK_COUNTS(na, ns)) { pr_debug("Bad cell count for %pOF\n" , dev); return OF_BAD_ADDR; } memcpy (addr, in_addr, na * 4 ); pr_debug("bus is %s (na=%d, ns=%d) on %pOF\n" , bus->name, na, ns, parent); of_dump_addr("translating address:" , addr, na); for (;;) { struct logic_pio_hwaddr *iorange ; of_node_put(dev); dev = parent; parent = get_parent(dev); if (parent == NULL ) { pr_debug("reached root node\n" ); return of_read_number(addr, na); } iorange = find_io_range_by_fwnode(&dev->fwnode); if (iorange && (iorange->flags != LOGIC_PIO_CPU_MMIO)) { u64 result = of_read_number(addr + 1 , na - 1 ); pr_debug("indirectIO matched(%pOF) 0x%llx\n" , dev, result); *host = no_free_ptr(dev); return result; } pbus = of_match_bus(parent); if (!pbus) return OF_BAD_ADDR; pbus->count_cells(dev, &pna, &pns); if (!OF_CHECK_COUNTS(pna, pns)) { pr_err("Bad cell count for %pOF\n" , dev); return OF_BAD_ADDR; } pr_debug("parent bus is %s (na=%d, ns=%d) on %pOF\n" , pbus->name, pna, pns, parent); if (of_translate_one(dev, bus, pbus, addr, na, ns, pna, rprop)) return OF_BAD_ADDR; na = pna; ns = pns; bus = pbus; of_dump_addr("one level translation:" , addr, na); } unreachable(); } u64 of_translate_address (struct device_node *dev, const __be32 *in_addr) { struct device_node *host ; u64 ret; ret = __of_translate_address(dev, of_get_parent, in_addr, "ranges" , &host); if (host) { of_node_put(host); return OF_BAD_ADDR; } return ret; } EXPORT_SYMBOL(of_translate_address);
__of_address_resource_bounds 计算地址资源边界 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 VISIBLE_IF_KUNIT int __of_address_resource_bounds(struct resource *r, u64 start, u64 size) { if (overflows_type(start, r->start)) return -EOVERFLOW; r->start = start; if (!size) r->end = wrapping_sub(typeof (r->end), r->start, 1 ); else if (size && check_add_overflow(r->start, size - 1 地址资源边界 &r->end)) return -EOVERFLOW; return 0 ; } EXPORT_SYMBOL_IF_KUNIT(__of_address_resource_bounds);
of_address_to_resource 将设备树地址转换为资源 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 static int __of_address_to_resource(struct device_node *dev, int index, int bar_no, struct resource *r) { u64 taddr; const __be32 *addrp; u64 size; unsigned int flags; const char *name = NULL ; addrp = __of_get_address(dev, index, bar_no, &size, &flags); if (addrp == NULL ) return -EINVAL; if (index >= 0 ) of_property_read_string_index(dev, "reg-names" , index, &name); if (flags & IORESOURCE_MEM) taddr = of_translate_address(dev, addrp); else if (flags & IORESOURCE_IO) taddr = of_translate_ioport(dev, addrp, size); else return -EINVAL; if (taddr == OF_BAD_ADDR) return -EINVAL; memset (r, 0 , sizeof (struct resource)); if (of_mmio_is_nonposted(dev)) flags |= IORESOURCE_MEM_NONPOSTED; r->flags = flags; r->name = name ? name : dev->full_name; return __of_address_resource_bounds(r, taddr, size); } int of_address_to_resource (struct device_node *dev, int index, struct resource *r) { return __of_address_to_resource(dev, index, -1 , r); } EXPORT_SYMBOL_GPL(of_address_to_resource);
of_iomap 映射给定device_node的内存映射 IO 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 void __iomem *of_iomap (struct device_node *np, int index) { struct resource res ; if (of_address_to_resource(np, index, &res)) return NULL ; if (res.flags & IORESOURCE_MEM_NONPOSTED) return ioremap_np(res.start, resource_size(&res)); else return ioremap(res.start, resource_size(&res)); } EXPORT_SYMBOL(of_iomap);
drivers/of/base.c __of_find_node_by_path 根据路径查找节点 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 static struct device_node *__of_get_next_child (const struct device_node *node , struct device_node *prev ) { struct device_node *next ; if (!node) return NULL ; next = prev ? prev->sibling : node->child; of_node_get(next); of_node_put(prev); return next; } #define __for_each_child_of_node(parent, child) \ for (child = __of_get_next_child(parent, NULL); child != NULL; \ child = __of_get_next_child(parent, child)) struct device_node *__of_find_node_by_path (const struct device_node *parent , const char *path ) { struct device_node *child ; int len; len = strcspn (path, "/:" ); if (!len) return NULL ; __for_each_child_of_node(parent, child) { const char *name = kbasename(child->full_name); if (strncmp (path, name, len) == 0 && (strlen (name) == len)) return child; } return NULL ; }
__of_find_node_by_full_path 根据完整路径返回节点
对于节点来说,节点和其父路径节点都会of_node_put
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 struct device_node *__of_find_node_by_full_path (struct device_node *node , const char *path ) { const char *separator = strchr (path, ':' ); while (node && *path == '/' ) { struct device_node *tmp = node; path++; node = __of_find_node_by_path(node, path); of_node_put(tmp); path = strchrnul(path, '/' ); if (separator && separator < path) break ; } return node; }
of_find_node_opts_by_path 查找与完整 OF 路径匹配的节点 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 struct device_node *of_find_node_opts_by_path (const char *path, const char **opts) { struct device_node *np = NULL ; const struct property *pp ; unsigned long flags; const char *separator = strchr (path, ':' ); if (opts) *opts = separator ? separator + 1 : NULL ; if (strcmp (path, "/" ) == 0 ) return of_node_get(of_root); if (*path != '/' ) { int len; const char *p = strchrnul(path, '/' ); if (separator && separator < p) p = separator; len = p - path; if (!of_aliases) return NULL ; for_each_property_of_node(of_aliases, pp) { if (strlen (pp->name) == len && !strncmp (pp->name, path, len)) { np = of_find_node_by_path(pp->value); break ; } } if (!np) return NULL ; path = p; } raw_spin_lock_irqsave(&devtree_lock, flags); if (!np) np = of_node_get(of_root); np = __of_find_node_by_full_path(np, path); raw_spin_unlock_irqrestore(&devtree_lock, flags); return np; } EXPORT_SYMBOL(of_find_node_opts_by_path);
of_find_matching_node_and_match 查找与设备树匹配的节点 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 struct device_node *of_find_matching_node_and_match (struct device_node *from, const struct of_device_id *matches, const struct of_device_id **match) { struct device_node *np ; const struct of_device_id *m ; unsigned long flags; if (match) *match = NULL ; raw_spin_lock_irqsave(&devtree_lock, flags); for_each_of_allnodes_from(from, np) { m = __of_match_node(matches, np); if (m && of_node_get(np)) { if (match) *match = m; break ; } } of_node_put(from); raw_spin_unlock_irqrestore(&devtree_lock, flags); return np; } EXPORT_SYMBOL(of_find_matching_node_and_match);
__of_find_all_nodes 遍历所有节点
该函数用于遍历设备树中的所有节点,并返回下一个节点的指针。
如果 prev 为 NULL,则返回根节点。
如果 prev 有子节点,则返回第一个子节点。
如果 prev 没有子节点,则向上遍历父节点,直到找到一个有兄弟节点的节点,并返回该兄弟节点。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 struct device_node *__of_find_all_nodes (struct device_node *prev ){ struct device_node *np ; if (!prev) { np = of_root; } else if (prev->child) { np = prev->child; } else { np = prev; while (np->parent && !np->sibling) np = np->parent; np = np->sibling; } return np; }
of_device_is_compatible 检查节点是否与给定的约束匹配 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 static int __of_device_is_compatible(const struct device_node *device, const char *compat, const char *type, const char *name) { const struct property *prop ; const char *cp; int index = 0 , score = 0 ; if (compat && compat[0 ]) { prop = __of_find_property(device, "compatible" , NULL ); for (cp = of_prop_next_string(prop, NULL ); cp; cp = of_prop_next_string(prop, cp), index++) { if (of_compat_cmp(cp, compat, strlen (compat)) == 0 ) { score = INT_MAX/2 - (index << 2 ); break ; } } if (!score) return 0 ; } if (type && type[0 ]) { if (!__of_node_is_type(device, type)) return 0 ; score += 2 ; } if (name && name[0 ]) { if (!of_node_name_eq(device, name)) return 0 ; score++; } return score; } int of_device_is_compatible (const struct device_node *device, const char *compat) { unsigned long flags; int res; raw_spin_lock_irqsave(&devtree_lock, flags); res = __of_device_is_compatible(device, compat, NULL , NULL ); raw_spin_unlock_irqrestore(&devtree_lock, flags); return res; } EXPORT_SYMBOL(of_device_is_compatible);
__of_match_node 查找与设备树匹配的节点
该函数用于查找与设备树匹配的节点,并返回最佳匹配的节点。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 static const struct of_device_id *__of_match_node (const struct of_device_id *matches , const struct device_node *node ) { const struct of_device_id *best_match = NULL ; int score, best_score = 0 ; if (!matches) return NULL ; for (; matches->name[0 ] || matches->type[0 ] || matches->compatible[0 ]; matches++) { score = __of_device_is_compatible(node, matches->compatible, matches->type, matches->name); if (score > best_score) { best_match = matches; best_score = score; } } return best_match; }
__of_find_property 查找设备树属性 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 #define of_prop_cmp(s1, s2) strcmp((s1), (s2)) static struct property *__of_find_property (const struct device_node *np , const char *name , int *lenp ) { struct property *pp ; if (!np) return NULL ; for (pp = np->properties; pp; pp = pp->next) { if (of_prop_cmp(pp->name, name) == 0 ) { if (lenp) *lenp = pp->length; break ; } } return pp; } struct property *of_find_property (const struct device_node *np, const char *name, int *lenp) { struct property *pp ; unsigned long flags; raw_spin_lock_irqsave(&devtree_lock, flags); pp = __of_find_property(np, name, lenp); raw_spin_unlock_irqrestore(&devtree_lock, flags); return pp; } EXPORT_SYMBOL(of_find_property);
of_get_property 查找设备树属性 1 2 3 4 5 6 7 8 9 10 11 const void *of_get_property (const struct device_node *np, const char *name, int *lenp) { const struct property *pp = of_find_property(np, name, lenp); return pp ? pp->value : NULL ; } EXPORT_SYMBOL(of_get_property);
of_phandle_iterator_args 获取 phandle 迭代器参数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 int of_phandle_iterator_args (struct of_phandle_iterator *it, uint32_t *args, int size) { int i, count; count = it->cur_count; if (WARN_ON(size < count)) count = size; for (i = 0 ; i < count; i++) args[i] = be32_to_cpup(it->cur++); return count; }
of_phandle_iterator_init 初始化 phandle 迭代器 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 int of_phandle_iterator_init (struct of_phandle_iterator *it, const struct device_node *np, const char *list_name, const char *cells_name, int cell_count) { const __be32 *list ; int size; memset (it, 0 , sizeof (*it)); if (cell_count < 0 && !cells_name) return -EINVAL; list = of_get_property(np, list_name, &size); if (!list ) return -ENOENT; it->cells_name = cells_name; it->cell_count = cell_count; it->parent = np; it->list_end = list + size / sizeof (*list ); it->phandle_end = list ; it->cur = list ; return 0 ; } EXPORT_SYMBOL_GPL(of_phandle_iterator_init);
of_phandle_iterator_next 迭代器的下一个 phandle 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 int of_phandle_iterator_next (struct of_phandle_iterator *it) { uint32_t count = 0 ; if (it->node) { of_node_put(it->node); it->node = NULL ; } if (!it->cur || it->phandle_end >= it->list_end) return -ENOENT; it->cur = it->phandle_end; it->phandle = be32_to_cpup(it->cur++); if (it->phandle) { it->node = of_find_node_by_phandle(it->phandle); if (it->cells_name) { if (!it->node) { pr_err("%pOF: could not find phandle %d\n" , it->parent, it->phandle); goto err; } if (of_property_read_u32(it->node, it->cells_name, &count)) { if (it->cell_count >= 0 ) { count = it->cell_count; } else { pr_err("%pOF: could not get %s for %pOF\n" , it->parent, it->cells_name, it->node); goto err; } } } else { count = it->cell_count; } if (it->cur + count > it->list_end) { if (it->cells_name) pr_err("%pOF: %s = %d found %td\n" , it->parent, it->cells_name, count, it->list_end - it->cur); else pr_err("%pOF: phandle %s needs %d, found %td\n" , it->parent, of_node_full_name(it->node), count, it->list_end - it->cur); goto err; } } it->phandle_end = it->cur + count; it->cur_count = count; return 0 ; err: if (it->node) { of_node_put(it->node); it->node = NULL ; } return -EINVAL; } EXPORT_SYMBOL_GPL(of_phandle_iterator_next);
__of_parse_phandle_with_args 解析设备树句柄 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 int __of_parse_phandle_with_args(const struct device_node *np, const char *list_name, const char *cells_name, int cell_count, int index, struct of_phandle_args *out_args) { struct of_phandle_iterator it ; int rc, cur_index = 0 ; if (index < 0 ) return -EINVAL; of_for_each_phandle(&it, rc, np, list_name, cells_name, cell_count) { rc = -ENOENT; if (cur_index == index) { if (!it.phandle) goto err; if (out_args) { int c; c = of_phandle_iterator_args(&it, out_args->args, MAX_PHANDLE_ARGS); out_args->np = it.node; out_args->args_count = c; } else { of_node_put(it.node); } return 0 ; } cur_index++; } err: of_node_put(it.node); return rc; } EXPORT_SYMBOL(__of_parse_phandle_with_args); static inline int of_parse_phandle_with_args (const struct device_node *np, const char *list_name, const char *cells_name, int index, struct of_phandle_args *out_args) { int cell_count = -1 ; if (!cells_name) cell_count = 0 ; return __of_parse_phandle_with_args(np, list_name, cells_name, cell_count, index, out_args); } static inline struct device_node *of_parse_phandle (const struct device_node *np, const char *phandle_name, int index) { struct of_phandle_args args ; if (__of_parse_phandle_with_args(np, phandle_name, NULL , 0 , index, &args)) return NULL ; return args.np; }
of_phandle_cache_hash 计算 phandle 哈希值 1 2 3 4 5 6 static struct device_node *phandle_cache [OF_PHANDLE_CACHE_SZ ];static u32 of_phandle_cache_hash (phandle handle) { return hash_32(handle, OF_PHANDLE_CACHE_BITS); }
of_find_node_by_phandle 查找节点 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 struct device_node *of_find_node_by_phandle (phandle handle) { struct device_node *np = NULL ; unsigned long flags; u32 handle_hash; if (!handle) return NULL ; handle_hash = of_phandle_cache_hash(handle); raw_spin_lock_irqsave(&devtree_lock, flags); if (phandle_cache[handle_hash] && handle == phandle_cache[handle_hash]->phandle) np = phandle_cache[handle_hash]; if (!np) { for_each_of_allnodes(np) if (np->phandle == handle && !of_node_check_flag(np, OF_DETACHED)) { phandle_cache[handle_hash] = np; break ; } } of_node_get(np); raw_spin_unlock_irqrestore(&devtree_lock, flags); return np; } EXPORT_SYMBOL(of_find_node_by_phandle);
of_get_parent 获取节点的父节点(如果有) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 struct device_node *of_get_parent (const struct device_node *node) { struct device_node *np ; unsigned long flags; if (!node) return NULL ; raw_spin_lock_irqsave(&devtree_lock, flags); np = of_node_get(node->parent); raw_spin_unlock_irqrestore(&devtree_lock, flags); return np; } EXPORT_SYMBOL(of_get_parent);
of_bus_n_addr_cells of_bus_n_size_cells 获取地址单元数 获取大小单元数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 int of_bus_n_addr_cells (struct device_node *np) { u32 cells; for (; np; np = np->parent) { if (!of_property_read_u32(np, "#address-cells" , &cells)) return cells; WARN_ONCE(!EXCLUDED_DEFAULT_CELLS_PLATFORMS, "Missing '#address-cells' in %pOF\n" , np); } return OF_ROOT_NODE_ADDR_CELLS_DEFAULT; } int of_n_addr_cells (struct device_node *np) { if (np->parent) np = np->parent; return of_bus_n_addr_cells(np); } EXPORT_SYMBOL(of_n_addr_cells); int of_bus_n_size_cells (struct device_node *np) { u32 cells; for (; np; np = np->parent) { if (!of_property_read_u32(np, "#size-cells" , &cells)) return cells; WARN_ONCE(!EXCLUDED_DEFAULT_CELLS_PLATFORMS, "Missing '#size-cells' in %pOF\n" , np); } return OF_ROOT_NODE_SIZE_CELLS_DEFAULT; } int of_n_size_cells (struct device_node *np) { if (np->parent) np = np->parent; return of_bus_n_size_cells(np); } EXPORT_SYMBOL(of_n_size_cells);
of_core_init: 初始化设备树核心并创建sysfs表示 此函数在Linux内核启动初期被调用, 它的核心作用是解析已经加载到内存中的设备树 (Device Tree), 并在 sysfs
文件系统中 (通常位于 /sys/firmware/devicetree/
) 创建一个与之对应的目录结构。这使得用户空间程序和内核其他部分可以通过标准的文件接口来访问设备树的节点和属性。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 #define OF_PHANDLE_CACHE_BITS 7 #define OF_PHANDLE_CACHE_SZ BIT(OF_PHANDLE_CACHE_BITS) static struct device_node *phandle_cache [OF_PHANDLE_CACHE_SZ ];static u32 of_phandle_cache_hash (phandle handle) { return hash_32(handle, OF_PHANDLE_CACHE_BITS); } void __init of_core_init (void ) { struct device_node *np ; of_platform_register_reconfig_notifier(); mutex_lock(&of_mutex); of_kset = kset_create_and_add("devicetree" , NULL , firmware_kobj); if (!of_kset) { mutex_unlock(&of_mutex); pr_err("failed to register existing nodes\n" ); return ; } for_each_of_allnodes(np) { __of_attach_node_sysfs(np); if (np->phandle && !phandle_cache[of_phandle_cache_hash(np->phandle)]) phandle_cache[of_phandle_cache_hash(np->phandle)] = np; } mutex_unlock(&of_mutex); if (of_root) proc_symlink("device-tree" , NULL , "/sys/firmware/devicetree/base" ); }
drivers/of/dynamic.c of_node_put 节点的递减引用计数 1 2 3 4 5 6 7 8 9 10 void of_node_put (struct device_node *node) { if (node) kobject_put(&node->kobj); } EXPORT_SYMBOL(of_node_put);
of_node_get 节点的递增引用计数 1 2 3 4 5 6 7 8 9 10 11 12 13 struct device_node *of_node_get (struct device_node *node) { if (node) kobject_get(&node->kobj); return node; } EXPORT_SYMBOL(of_node_get);
of_reconfig_chain: 设备树重配置事件的阻塞式通知链头 这是一个静态全局变量,它定义并初始化了一个阻塞式通知链的头部。所有对设备树动态重配置事件感兴趣的模块,都会将它们的通知器(notifier)注册到这个链上。
1 2 3 4 5 6 static BLOCKING_NOTIFIER_HEAD (of_reconfig_chain) ;
of_reconfig_notifier_register: 注册一个设备树重配置通知器 这是一个上层封装函数,为其他模块提供了一个清晰的、特定于设备树重配置的注册接口。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 int of_reconfig_notifier_register (struct notifier_block *nb) { return blocking_notifier_chain_register(&of_reconfig_chain, nb); } EXPORT_SYMBOL_GPL(of_reconfig_notifier_register);
这个代码片段展示了Linux内核中事件发布的核心机制,即如何触发一个阻塞式通知链(blocking notifier chain)来广播一个事件。of_reconfig_notify
是特定于设备树重配置事件的发布函数,而blocking_notifier_call_chain
是通用的、用于调用任何阻塞式通知链的API。
这套机制在STM32H750这样的单核无MMU系统上是事件驱动架构的基石。其核心是blocking_notifier_call_chain
使用的读写信号量(rwsem
)。当一个事件被发布时,该函数会获取一个读锁 。
在单核抢占式内核中,这个读锁允许多个任务并发地读取(即发布事件),但会阻塞任何尝试获取写锁 (即注册/注销订阅者)的任务,直到所有读者都完成。
最重要的是,这个锁机制确立了一个契约 :由于调用者持有的是一个允许休眠的锁,因此链上所有订阅者的回调函数都被允许阻塞或休眠 (例如,等待I/O)。这使得订阅者可以执行复杂的操作,而不必担心破坏原子上下文。blocking_notifier
的”blocking”正源于此。
of_reconfig_notify: 发布一个设备树重配置通知 此函数是设备树子系统中用于广播“设备树已发生变化”事件的入口点。当内核的其他部分(如设备树覆盖管理器)需要通知系统一个节点已被添加或移除时,就会调用此函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 int of_reconfig_notify (unsigned long action, struct of_reconfig_data *p) { int rc; struct of_reconfig_data *pr = p; of_changeset_action_debug("notify: " , action, pr->dn, pr->prop); rc = blocking_notifier_call_chain(&of_reconfig_chain, action, p); return notifier_to_errno(rc); }
drivers/of/fdt.c initial_boot_params fdt的指针地址 1 2 3 4 5 6 7 void *initial_boot_params __ro_after_init;bool __init early_init_dt_verify (void *dt_virt, phys_addr_t dt_phys) { initial_boot_params = dt_virt; initial_boot_params_pa = dt_phys; }
early_init_dt_scan_root 获取顶级地址和大小单元格 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 int __init early_init_dt_scan_root (void ) { const __be32 *prop; const void *fdt = initial_boot_params; int node = fdt_path_offset(fdt, "/" ); if (node < 0 ) return -ENODEV; dt_root_size_cells = OF_ROOT_NODE_SIZE_CELLS_DEFAULT; dt_root_addr_cells = OF_ROOT_NODE_ADDR_CELLS_DEFAULT; prop = of_get_flat_dt_prop(node, "#size-cells" , NULL ); if (!WARN(!prop, "No '#size-cells' in root node\n" )) dt_root_size_cells = be32_to_cpup(prop); pr_debug("dt_root_size_cells = %x\n" , dt_root_size_cells); prop = of_get_flat_dt_prop(node, "#address-cells" , NULL ); if (!WARN(!prop, "No '#address-cells' in root node\n" )) dt_root_addr_cells = be32_to_cpup(prop); pr_debug("dt_root_addr_cells = %x\n" , dt_root_addr_cells); return 0 ; }
early_init_dt_verify 验证fdt格式与校验 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 bool __init early_init_dt_verify (void *dt_virt, phys_addr_t dt_phys) { if (!dt_virt) return false ; if (fdt_check_header(dt_virt)) return false ; initial_boot_params = dt_virt; initial_boot_params_pa = dt_phys; of_fdt_crc32 = crc32_be(~0 , initial_boot_params, fdt_totalsize(initial_boot_params)); early_init_dt_scan_root(); return true ; }
of_flat_dt_match 如果 node 与兼容值列表匹配,则返回 true 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 static int __init of_flat_dt_match (unsigned long node, const char *const *compat) { unsigned int tmp, score = 0 ; if (!compat) return 0 ; while (*compat) { tmp = of_fdt_is_compatible(initial_boot_params, node, *compat); if (tmp && (score == 0 || (tmp < score))) score = tmp; compat++; } return score; }
of_flat_dt_match_machine 设备树匹配机器描述符 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 const void * __init of_flat_dt_match_machine (const void *default_match, const void * (*get_next_compat)(const char * const **)) { const void *data = NULL ; const void *best_data = default_match; const char *const *compat; unsigned long dt_root; unsigned int best_score = ~1 , score = 0 ; dt_root = of_get_flat_dt_root(); while ((data = get_next_compat(&compat))) { score = of_flat_dt_match(dt_root, compat); if (score > 0 && score < best_score) { best_data = data; best_score = score; } } if (!best_data) { const char *prop; int size; pr_err("\n unrecognized device tree list:\n[ " ); prop = of_get_flat_dt_prop(dt_root, "compatible" , &size); if (prop) { while (size > 0 ) { printk("'%s' " , prop); size -= strlen (prop) + 1 ; prop += strlen (prop) + 1 ; } } printk("]\n\n" ); return NULL ; } pr_info("Machine model: %s\n" , of_flat_dt_get_machine_name()); return best_data; }
early_init_dt_check_for_initrd 检查 initrd(初始 RAM 磁盘) 的位置
在设备树中,linux,initrd-start 是一个属性名称,通常用于指定初始 RAM 磁盘(initrd)的起始地址。初始 RAM 磁盘是一个临时的根文件系统,Linux 内核在启动时会加载它,以便完成系统初始化(例如加载驱动程序或挂载真正的根文件系统)。
在u-boot中可以识别到Ramdisk
文件,并且进行加载到内存中,然后添加到设备树中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 [2025/3/31 20:05:25 122] ## Loading ramdisk (any) from FIT Image at 90080000 ... [2025/3/31 20:05:25 125] Using 'conf0' configuration [2025/3/31 20:05:25 129] Trying 'ramdisk' ramdisk subimage [2025/3/31 20:05:25 135] Description: Ramdisk for for RT-Thread STM32H750i-ART-PI board [2025/3/31 20:05:25 139] Created: 2025-03-31 12:01:54 UTC [2025/3/31 20:05:25 142] Type: RAMDisk Image [2025/3/31 20:05:25 145] Compression: uncompressed [2025/3/31 20:05:25 149] Data Start: 0x9021c9d4 [2025/3/31 20:05:25 153] Data Size: 2097152 Bytes = 2 MiB [2025/3/31 20:05:25 153] Architecture: ARM [2025/3/31 20:05:25 157] OS: Linux [2025/3/31 20:05:25 162] Load Address: unavailable [2025/3/31 20:05:25 162] Entry Point: unavailable [2025/3/31 20:05:25 163] Hash algo: sha1 [2025/3/31 20:05:25 169] Hash value: cf0cfc63b905e83fa53905c0bbbbac5e01124b6f [2025/3/31 20:05:25 440] Verifying Hash Integrity ... sha1+ OK [2025/3/31 20:05:25 636] Loading Ramdisk to c1600000, end c1800000 ... OK [2025/3/31 20:05:25 716] Starting kernel ... [2025/3/31 20:05:25 716] [2025/3/31 20:05:31 847] [ 0.000000] OF: fdt: initrd_start=0xc1600000 initrd_end=0xc1800000
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 static void __init early_init_dt_check_for_initrd (unsigned long node) { u64 start, end; int len; const __be32 *prop; if (!IS_ENABLED(CONFIG_BLK_DEV_INITRD)) return ; pr_debug("Looking for initrd properties... " ); prop = of_get_flat_dt_prop(node, "linux,initrd-start" , &len); if (!prop) return ; start = of_read_number(prop, len/4 ); prop = of_get_flat_dt_prop(node, "linux,initrd-end" , &len); if (!prop) return ; end = of_read_number(prop, len/4 ); if (start > end) return ; __early_init_dt_declare_initrd(start, end); phys_initrd_start = start; phys_initrd_size = end - start; pr_debug("initrd_start=0x%llx initrd_end=0x%llx\n" , start, end); }
early_init_dt_scan_chosen 扫描/chosen
节点
bootargs
参数:由u-boot传递给内核的命令行参数。 u-boot会把其在env环境变量中的bootargs
参数设置覆盖fdt参数 1 [2025/3/31 20:05:31 854] [ 0.000000] OF: fdt: Command line is: console=ttySTM0,115200 root=/dev/ram loglevel=8
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 int __init early_init_dt_scan_chosen (char *cmdline) { int l, node; const char *p; const void *rng_seed; const void *fdt = initial_boot_params; node = fdt_path_offset(fdt, "/chosen" ); if (node < 0 ) node = fdt_path_offset(fdt, "/chosen@0" ); if (node < 0 ) goto handle_cmdline; chosen_node_offset = node; early_init_dt_check_for_initrd(node); early_init_dt_check_for_elfcorehdr(node); rng_seed = of_get_flat_dt_prop(node, "rng-seed" , &l); if (rng_seed && l > 0 ) { add_bootloader_randomness(rng_seed, l); fdt_nop_property(initial_boot_params, node, "rng-seed" ); of_fdt_crc32 = crc32_be(~0 , initial_boot_params, fdt_totalsize(initial_boot_params)); } p = of_get_flat_dt_prop(node, "bootargs" , &l); if (p != NULL && l > 0 ) strscpy(cmdline, p, min(l, COMMAND_LINE_SIZE)); handle_cmdline: #ifdef CONFIG_CMDLINE #if defined(CONFIG_CMDLINE_EXTEND) strlcat(cmdline, " " , COMMAND_LINE_SIZE); strlcat(cmdline, CONFIG_CMDLINE, COMMAND_LINE_SIZE); #elif defined(CONFIG_CMDLINE_FORCE) strscpy(cmdline, CONFIG_CMDLINE, COMMAND_LINE_SIZE); #else if (!((char *)cmdline)[0 ]) strscpy(cmdline, CONFIG_CMDLINE, COMMAND_LINE_SIZE); #endif #endif pr_debug("Command line is: %s\n" , (char *)cmdline); return 0 ; }
early_init_dt_add_memory_arch 将内存块添加到内存块列表中。它会检查内存块的大小和对齐方式,并确保它们在有效范围内。
early_init_dt_add_memory_arch
函数用于将内存块添加到内存块列表中。它会检查内存块的大小和对齐方式,并确保它们在有效范围内。
memblock_add
函数用于将内存块添加到内存块管理器中。它会更新内存块的状态,以便在后续的内存分配中使用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 void __init __weak early_init_dt_add_memory_arch (u64 base, u64 size) { const u64 phys_offset = MIN_MEMBLOCK_ADDR; if (size < PAGE_SIZE - (base & ~PAGE_MASK)) { pr_warn("Ignoring memory block 0x%llx - 0x%llx\n" , base, base + size); return ; } if (!PAGE_ALIGNED(base)) { size -= PAGE_SIZE - (base & ~PAGE_MASK); base = PAGE_ALIGN(base); } size &= PAGE_MASK; if (base > MAX_MEMBLOCK_ADDR) { pr_warn("Ignoring memory block 0x%llx - 0x%llx\n" , base, base + size); return ; } if (base + size - 1 > MAX_MEMBLOCK_ADDR) { pr_warn("Ignoring memory range 0x%llx - 0x%llx\n" , ((u64)MAX_MEMBLOCK_ADDR) + 1 , base + size); size = MAX_MEMBLOCK_ADDR - base + 1 ; } if (base + size < phys_offset) { pr_warn("Ignoring memory block 0x%llx - 0x%llx\n" , base, base + size); return ; } if (base < phys_offset) { pr_warn("Ignoring memory range 0x%llx - 0x%llx\n" , base, phys_offset); size -= phys_offset - base; base = phys_offset; } memblock_add(base, size); }
early_init_dt_scan_memory 查找并解析内存节点 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 int __init early_init_dt_scan_memory (void ) { int node, found_memory = 0 ; const void *fdt = initial_boot_params; fdt_for_each_subnode(node, fdt, 0 ) { const char *type = of_get_flat_dt_prop(node, "device_type" , NULL ); const __be32 *reg, *endp; int l; bool hotpluggable; if (type == NULL || strcmp (type, "memory" ) != 0 ) continue ; if (!of_fdt_device_is_available(fdt, node)) continue ; 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 ) continue ; endp = reg + (l / sizeof (__be32)); hotpluggable = of_get_flat_dt_prop(node, "hotpluggable" , NULL ); pr_debug("memory scan node %s, reg size %d,\n" , fdt_get_name(fdt, node, NULL ), l); 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" , base, size); early_init_dt_add_memory_arch(base, size); found_memory = 1 ; if (!hotpluggable) continue ; if (memblock_mark_hotplug(base, size)) pr_warn("failed to mark hotplug range 0x%llx - 0x%llx\n" , base, base + size); } } return found_memory; }
early_init_dt_check_for_usable_mem_range 从平面树解码可用内存范围位置 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 static unsigned long chosen_node_offset = -FDT_ERR_NOTFOUND;#define MAX_USABLE_RANGES 2 void __init early_init_dt_check_for_usable_mem_range (void ) { struct memblock_region rgn [MAX_USABLE_RANGES ] = {0 }; const __be32 *prop, *endp; int len, i; unsigned long node = chosen_node_offset; if ((long )node < 0 ) return ; pr_debug("Looking for usable-memory-range property... " ); prop = of_get_flat_dt_prop(node, "linux,usable-memory-range" , &len); if (!prop || (len % (dt_root_addr_cells + dt_root_size_cells))) return ; endp = prop + (len / sizeof (__be32)); for (i = 0 ; i < MAX_USABLE_RANGES && prop < endp; i++) { rgn[i].base = dt_mem_next_cell(dt_root_addr_cells, &prop); rgn[i].size = dt_mem_next_cell(dt_root_size_cells, &prop); pr_debug("cap_mem_regions[%d]: base=%pa, size=%pa\n" , i, &rgn[i].base, &rgn[i].size); } memblock_cap_memory_range(rgn[0 ].base, rgn[0 ].size); for (i = 1 ; i < MAX_USABLE_RANGES && rgn[i].size; i++) memblock_add(rgn[i].base, rgn[i].size); }
early_init_dt_scan_nodes 扫描设备树节点 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 void __init early_init_dt_scan_nodes (void ) { int rc; rc = early_init_dt_scan_chosen(boot_command_line); if (rc) pr_warn("No chosen node found, continuing without\n" ); early_init_dt_scan_memory(); early_init_dt_check_for_usable_mem_range(); }
early_init_fdt_scan_reserved_mem 创建保留内存区域 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 void __init early_init_fdt_scan_reserved_mem (void ) { int n; int res; u64 base, size; if (!initial_boot_params) return ; fdt_scan_reserved_mem(); fdt_reserve_elfcorehdr(); for (n = 0 ; ; n++) { res = fdt_get_mem_rsv(initial_boot_params, n, &base, &size); if (res) { pr_err("Invalid memory reservation block index %d\n" , n); break ; } if (!size) break ; memblock_reserve(base, size); } }
__dtb_empty_root_begin 和 __dtb_empty_root_end 1 2 3 4 5 extern uint8_t __dtb_empty_root_begin[];extern uint8_t __dtb_empty_root_end[];
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 quiet_cmd_wrap_S_dtb = WRAP $@ cmd_wrap_S_dtb = { \ symbase=__$(patsubst .%,%,$(suffix $< ) )_$(subst -,_,$(notdir $* ) ); \ echo '\ echo '.section $(builtin-dtb-section),"a" '; \ echo '.balign STRUCT_ALIGNMENT'; \ echo ".global $${symbase}_begin" ; \ echo "$${symbase}_begin:" ; \ echo '.incbin "$< " '; \ echo ".global $${symbase}_end" ; \ echo "$${symbase}_end:" ; \ echo '.balign STRUCT_ALIGNMENT'; \ } > $@ $(obj) /%.dtb.S: $(obj) /%.dtb FORCE $(call if_changed,wrap_S_dtb) $(obj) /%.dtbo.S: $(obj) /%.dtbo FORCE $(call if_changed,wrap_S_dtb)
1 2 3 4 5 6 7 8 9 10 //drivers/of/empty_root.dtb.S #include <asm-generic/vmlinux.lds.h> .section .rodata,"a" .balign STRUCT_ALIGNMENT .global __dtb_empty_root_begin __dtb_empty_root_begin: .incbin "drivers/of/empty_root.dtb" .global __dtb_empty_root_end __dtb_empty_root_end: .balign STRUCT_ALIGNMENT
1 2 3 4 5 6 7 8 9 /dts-v1/ ; / { #address-cells = <0x02>; #size-cells = <0x02>; };
populate_properties 填充属性 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 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; 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 ; } if (!strcmp (pname, "name" )) has_name = true ; pp = unflatten_dt_alloc(mem, sizeof (struct property), __alignof__(struct property)); if (dryrun) continue ; if (!strcmp (pname, "phandle" ) || !strcmp (pname, "linux,phandle" )) { if (!np->phandle) np->phandle = be32_to_cpup(val); } if (!strcmp (pname, "ibm,phandle" )) np->phandle = be32_to_cpup(val); pp->name = (char *)pname; pp->length = sz; pp->value = (__be32 *)val; *pprev = pp; pprev = &pp->next; } 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; 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); } } }
populate_node 填充设备节点 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 static int 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; int len; pathp = fdt_get_name(blob, offset, &len); if (!pathp) { *pnp = NULL ; return len; } len++; np = unflatten_dt_alloc(mem, sizeof (struct device_node) + len, __alignof__(struct device_node)); if (!dryrun) { char *fn; of_node_init(np); np->full_name = fn = ((char *)np) + sizeof (*np); memcpy (fn, pathp, len); 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 0 ; }
unflatten_dt_nodes 从平面树分配并填充device_node 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 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; bool dryrun = !base; int ret; if (nodepp) *nodepp = NULL ; if (dad) depth = initial_depth = 1 ; root = dad; nps[depth] = dad; for (offset = 0 ; offset >= 0 && depth >= initial_depth; offset = fdt_next_node(blob, offset, &depth)) { if (WARN_ON_ONCE(depth >= FDT_MAX_DEPTH - 1 )) continue ; if (!IS_ENABLED(CONFIG_OF_KOBJ) && !of_fdt_device_is_available(blob, offset)) continue ; ret = populate_node(blob, offset, &mem, nps[depth], &nps[depth+1 ], dryrun); if (ret < 0 ) return ret; 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; } if (!dryrun) reverse_nodes(root); return mem - base; }
__unflatten_device_tree 展开固件传递的设备树,创建 struct device_node 树 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 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; int ret; if (mynodes) *mynodes = NULL ; 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)); if (fdt_check_header(blob)) { pr_err("Invalid device tree blob header\n" ); return NULL ; } 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); 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); ret = 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 (ret <= 0 ) return NULL ; if (detached && mynodes && *mynodes) { of_node_set_flag(*mynodes, OF_DETACHED); pr_debug("unflattened tree is detached\n" ); } pr_debug(" <- unflatten_device_tree()\n" ); return mem; }
unflatten_device_tree 从平面 blob 创建 device_nodes 树 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 void __init unflatten_device_tree (void ) { void *fdt = initial_boot_params; fdt_scan_reserved_mem_reg_nodes(); if (!fdt) { fdt = (void *) __dtb_empty_root_begin; if (fdt_totalsize(fdt) > __dtb_empty_root_end - __dtb_empty_root_begin) { pr_err("invalid size in dtb_empty_root\n" ); return ; } of_fdt_crc32 = crc32_be(~0 , fdt, fdt_totalsize(fdt)); fdt = copy_device_tree(fdt); } __unflatten_device_tree(fdt, NULL , &of_root, early_init_dt_alloc_memory_arch, false ); of_alias_scan(early_init_dt_alloc_memory_arch); unittest_unflatten_overlay_base(); }
drivers/of/kobj.c __of_attach_node_sysfs: 在sysfs中为设备树节点创建目录 此函数的核心作用是接收一个设备树节点(struct device_node
),并在sysfs
文件系统中为其创建一个对应的目录。它处理了节点的层级关系,确保设备树的树状结构在sysfs
中被正确地复制出来。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 int __of_attach_node_sysfs(struct device_node *np){ const char *name; struct kobject *parent ; struct property *pp ; int rc; if (!IS_ENABLED(CONFIG_SYSFS) || !of_kset) return 0 ; np->kobj.kset = of_kset; if (!np->parent) { name = safe_name(&of_kset->kobj, "base" ); parent = NULL ; } else { name = safe_name(&np->parent->kobj, kbasename(np->full_name)); parent = &np->parent->kobj; } if (!name) return -ENOMEM; rc = kobject_add(&np->kobj, parent, "%s" , name); kfree(name); if (rc) return rc; for_each_property_of_node(np, pp) __of_add_property_sysfs(np, pp); of_node_get(np); return 0 ; }
__of_add_property_sysfs: 在sysfs中为设备树属性创建文件 此函数被__of_attach_node_sysfs
调用,负责将单个设备树属性(如compatible
, reg
等)暴露为sysfs
中的一个二进制文件。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 int __of_add_property_sysfs(struct device_node *np, struct property *pp){ int rc; bool secure = strncmp (pp->name, "security-" , 9 ) == 0 ; if (!IS_ENABLED(CONFIG_SYSFS)) return 0 ; if (!of_kset || !of_node_is_attached(np)) return 0 ; sysfs_bin_attr_init(&pp->attr); pp->attr.attr.name = safe_name(&np->kobj, pp->name); pp->attr.attr.mode = secure ? 0400 : 0444 ; pp->attr.size = secure ? 0 : pp->length; pp->attr.read_new = of_node_property_read; rc = sysfs_create_bin_file(&np->kobj, &pp->attr); WARN(rc, "error adding attribute %s to node %pOF\n" , pp->name, np); return rc; }
drivers/of/of_reserved_mem.c 静态全局变量 1 2 3 4 static struct reserved_mem reserved_mem_array [MAX_RESERVED_REGIONS ] __initdata ;static struct reserved_mem *reserved_mem __refdata = reserved_mem_array;static int total_reserved_mem_cnt = MAX_RESERVED_REGIONS;static int reserved_mem_count;
__reserved_mem_check_root 检查 /reserved-memory 中提供的 #size、#address 单元格是否与当前实现支持的值匹配,并检查是否提供了 ranges 属性 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 static int __init __reserved_mem_check_root(unsigned long node){ const __be32 *prop; prop = of_get_flat_dt_prop(node, "#size-cells" , NULL ); if (!prop || be32_to_cpup(prop) != dt_root_size_cells) return -EINVAL; prop = of_get_flat_dt_prop(node, "#address-cells" , NULL ); if (!prop || be32_to_cpup(prop) != dt_root_addr_cells) return -EINVAL; prop = of_get_flat_dt_prop(node, "ranges" , NULL ); if (!prop) return -EINVAL; return 0 ; }
early_init_dt_alloc_reserved_memory_arch 早期初始化设备树分配保留内存 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 static int __init early_init_dt_alloc_reserved_memory_arch (phys_addr_t size, phys_addr_t align, phys_addr_t start, phys_addr_t end, bool nomap, phys_addr_t *res_base) { phys_addr_t base; int err = 0 ; end = !end ? MEMBLOCK_ALLOC_ANYWHERE : end; align = !align ? SMP_CACHE_BYTES : align; base = memblock_phys_alloc_range(size, align, start, end); if (!base) return -ENOMEM; *res_base = base; if (nomap) { err = memblock_mark_nomap(base, size); if (err) memblock_phys_free(base, size); } if (!err) kmemleak_ignore_phys(base); return err; }
__reserved_mem_init_node 调用区域特定的预留内存初始化代码
arch/arm/kernel/vmlinux.lds
1 2 __reservedmem_of_table = .; KEEP(*(__reservedmem_of_table)) KEEP(*(__reservedmem_of_table_end)) . = ALIGN(8 );
vmlinux.map,这里匹配了kernel/dma/coherent.c
的代码
1 2 3 4 5 6 7 0x00000000c02a1308 . = ALIGN (0x8 ) 0x00000000c02a1308 __reservedmem_of_table = . *(__reservedmem_of_table) __reservedmem_of_table 0x00000000c02a1308 0xc4 kernel/dma/coherent.o *(__reservedmem_of_table_end) __reservedmem_of_table_end
__reservedmem_of_table
是一个符号,表示预留内存区域的设备树兼容性表的起始地址。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 static int __init __reserved_mem_init_node(struct reserved_mem *rmem){ extern const struct of_device_id __reservedmem_of_table []; const struct of_device_id *i ; int ret = -ENOENT; for (i = __reservedmem_of_table; i < &__rmem_of_table_sentinel; i++) { reservedmem_of_init_fn initfn = i->data; const char *compat = i->compatible; if (!of_flat_dt_is_compatible(rmem->fdt_node, compat)) continue ; ret = initfn(rmem); if (ret == 0 ) { pr_info("initialized node %s, compatible id %s\n" , rmem->name, compat); break ; } } return ret; }
fdt_init_reserved_mem_node 此函数用于调用预留内存区域的特定于区域的初始化函数。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 static void __init fdt_init_reserved_mem_node (struct reserved_mem *rmem) { unsigned long node = rmem->fdt_node; int err = 0 ; bool nomap; nomap = of_get_flat_dt_prop(node, "no-map" , NULL ) != NULL ; err = __reserved_mem_init_node(rmem); if (err != 0 && err != -ENOENT) { pr_info("node %s compatible matching fail\n" , rmem->name); if (nomap) memblock_clear_nomap(rmem->base, rmem->size); else memblock_phys_free(rmem->base, rmem->size); } else { phys_addr_t end = rmem->base + rmem->size - 1 ; bool reusable = (of_get_flat_dt_prop(node, "reusable" , NULL )) != NULL ; pr_info("%pa..%pa (%lu KiB) %s %s %s\n" , &rmem->base, &end, (unsigned long )(rmem->size / SZ_1K), nomap ? "nomap" : "map" , reusable ? "reusable" : "non-reusable" , rmem->name ? rmem->name : "unknown" ); } }
fdt_reserved_mem_save_node 保存 FDT 节点以进行第二次通过初始化 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 static void __init fdt_reserved_mem_save_node (unsigned long node, const char *uname, phys_addr_t base, phys_addr_t size) { struct reserved_mem *rmem = &reserved_mem[reserved_mem_count]; if (reserved_mem_count == total_reserved_mem_cnt) { pr_err("not enough space for all defined regions.\n" ); return ; } rmem->fdt_node = node; rmem->name = uname; rmem->base = base; rmem->size = size; fdt_init_reserved_mem_node(rmem); reserved_mem_count++; return ; }
__reserved_mem_alloc_size 预留内存分配 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 static int __init __reserved_mem_alloc_size(unsigned long node, const char *uname){ int t_len = (dt_root_addr_cells + dt_root_size_cells) * sizeof (__be32); phys_addr_t start = 0 , end = 0 ; phys_addr_t base = 0 , align = 0 , size; int len; const __be32 *prop; bool nomap; int ret; prop = of_get_flat_dt_prop(node, "size" , &len); if (!prop) return -EINVAL; if (len != dt_root_size_cells * sizeof (__be32)) { pr_err("invalid size property in '%s' node.\n" , uname); return -EINVAL; } size = dt_mem_next_cell(dt_root_size_cells, &prop); prop = of_get_flat_dt_prop(node, "alignment" , &len); if (prop) { if (len != dt_root_addr_cells * sizeof (__be32)) { pr_err("invalid alignment property in '%s' node.\n" , uname); return -EINVAL; } align = dt_mem_next_cell(dt_root_addr_cells, &prop); } nomap = of_get_flat_dt_prop(node, "no-map" , NULL ) != NULL ; if (IS_ENABLED(CONFIG_CMA) && of_flat_dt_is_compatible(node, "shared-dma-pool" ) && of_get_flat_dt_prop(node, "reusable" , NULL ) && !nomap) align = max_t (phys_addr_t , align, CMA_MIN_ALIGNMENT_BYTES); prop = of_get_flat_dt_prop(node, "alloc-ranges" , &len); if (prop) { if (len % t_len != 0 ) { pr_err("invalid alloc-ranges property in '%s', skipping node.\n" , uname); return -EINVAL; } while (len > 0 ) { start = dt_mem_next_cell(dt_root_addr_cells, &prop); end = start + dt_mem_next_cell(dt_root_size_cells, &prop); base = 0 ; ret = __reserved_mem_alloc_in_range(size, align, start, end, nomap, &base); if (ret == 0 ) { pr_debug("allocated memory for '%s' node: base %pa, size %lu MiB\n" , uname, &base, (unsigned long )(size / SZ_1M)); break ; } len -= t_len; } } else { ret = early_init_dt_alloc_reserved_memory_arch(size, align, 0 , 0 , nomap, &base); if (ret == 0 ) pr_debug("allocated memory for '%s' node: base %pa, size %lu MiB\n" , uname, &base, (unsigned long )(size / SZ_1M)); } if (base == 0 ) { pr_err("failed to allocate memory for node '%s': size %lu MiB\n" , uname, (unsigned long )(size / SZ_1M)); return -ENOMEM; } fdt_reserved_mem_save_node(node, uname, base, size); return 0 ; }
fdt_scan_reserved_mem 扫描单个 FDT 节点以获取预留内存 1 2 3 4 5 6 7 8 9 10 11 12 reserved-memory { #address-cells = <0x01> ; #size-cells = <0x01> ; ranges; linux,cma { compatible = "shared-dma-pool" ; no-map ; size = <0x100000 >; linux,dma-default ; }; };
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 int __init fdt_scan_reserved_mem (void ) { int node, child; int dynamic_nodes_cnt = 0 , count = 0 ; int dynamic_nodes[MAX_RESERVED_REGIONS]; const void *fdt = initial_boot_params; node = fdt_path_offset(fdt, "/reserved-memory" ); if (node < 0 ) return -ENODEV; if (__reserved_mem_check_root(node) != 0 ) { pr_err("Reserved memory: unsupported node format, ignoring\n" ); return -EINVAL; } fdt_for_each_subnode(child, fdt, node) { const char *uname; int err; if (!of_fdt_device_is_available(fdt, child)) continue ; uname = fdt_get_name(fdt, child, NULL ); err = __reserved_mem_reserve_reg(child, uname); if (!err) count++; if (err == -ENOENT && of_get_flat_dt_prop(child, "size" , NULL )) { dynamic_nodes[dynamic_nodes_cnt] = child; dynamic_nodes_cnt++; } } for (int i = 0 ; i < dynamic_nodes_cnt; i++) { const char *uname; int err; child = dynamic_nodes[i]; uname = fdt_get_name(fdt, child, NULL ); err = __reserved_mem_alloc_size(child, uname); if (!err) count++; } total_reserved_mem_cnt = count; return 0 ; }
alloc_reserved_mem_array 使用 memblock 为 reserved_mem 数组分配内存
这里需要重新动态分配给reserved_mem,因为之前的静态数组会在初始化结束后清空
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 static void __init alloc_reserved_mem_array (void ) { struct reserved_mem *new_array ; size_t alloc_size, copy_size, memset_size; alloc_size = array_size(total_reserved_mem_cnt, sizeof (*new_array)); if (alloc_size == SIZE_MAX) { pr_err("Failed to allocate memory for reserved_mem array with err: %d" , -EOVERFLOW); return ; } new_array = memblock_alloc(alloc_size, SMP_CACHE_BYTES); if (!new_array) { pr_err("Failed to allocate memory for reserved_mem array with err: %d" , -ENOMEM); return ; } copy_size = array_size(reserved_mem_count, sizeof (*new_array)); if (copy_size == SIZE_MAX) { memblock_free(new_array, alloc_size); total_reserved_mem_cnt = MAX_RESERVED_REGIONS; pr_err("Failed to allocate memory for reserved_mem array with err: %d" , -EOVERFLOW); return ; } memset_size = alloc_size - copy_size; memcpy (new_array, reserved_mem, copy_size); memset (new_array + reserved_mem_count, 0 , memset_size); reserved_mem = new_array; }
__rmem_check_for_overlap 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 static void __init __rmem_check_for_overlap(void ){ int i; if (reserved_mem_count < 2 ) return ; sort(reserved_mem, reserved_mem_count, sizeof (reserved_mem[0 ]), __rmem_cmp, NULL ); for (i = 0 ; i < reserved_mem_count - 1 ; i++) { struct reserved_mem *this , *next ; this = &reserved_mem[i]; next = &reserved_mem[i + 1 ]; if (this->base + this->size > next->base) { phys_addr_t this_end, next_end; this_end = this->base + this->size; next_end = next->base + next->size; pr_err("OVERLAP DETECTED!\n%s (%pa--%pa) overlaps with %s (%pa--%pa)\n" , this->name, &this->base, &this_end, next->name, &next->base, &next_end); } } }
fdt_scan_reserved_mem_reg_nodes 扫描 DT 并存储使用 “reg” 属性定义的保留内存区域的信息 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 void __init fdt_scan_reserved_mem_reg_nodes (void ) { int t_len = (dt_root_addr_cells + dt_root_size_cells) * sizeof (__be32); const void *fdt = initial_boot_params; phys_addr_t base, size; const __be32 *prop; int node, child; int len; if (!fdt) return ; node = fdt_path_offset(fdt, "/reserved-memory" ); if (node < 0 ) { pr_info("Reserved memory: No reserved-memory node in the DT\n" ); return ; } alloc_reserved_mem_array(); if (__reserved_mem_check_root(node)) { pr_err("Reserved memory: unsupported node format, ignoring\n" ); return ; } fdt_for_each_subnode(child, fdt, node) { const char *uname; prop = of_get_flat_dt_prop(child, "reg" , &len); if (!prop) continue ; if (!of_fdt_device_is_available(fdt, child)) continue ; uname = fdt_get_name(fdt, child, NULL ); if (len && len % t_len != 0 ) { pr_err("Reserved memory: invalid reg property in '%s', skipping node.\n" , uname); continue ; } if (len > t_len) pr_warn("%s() ignores %d regions in node '%s'\n" , __func__, len / t_len - 1 , uname); base = dt_mem_next_cell(dt_root_addr_cells, &prop); size = dt_mem_next_cell(dt_root_size_cells, &prop); if (size) fdt_reserved_mem_save_node(child, uname, base, size); } __rmem_check_for_overlap(); }
该函数是核心的事件处理程序。当设备树发生变化时,它被内核的通知器(notifier)框架调用,根据变化的类型(添加或移除节点)来创建或销毁平台设备。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 static int of_platform_notify (struct notifier_block *nb, unsigned long action, void *arg) { struct of_reconfig_data *rd = arg; struct platform_device *pdev_parent , *pdev ; bool children_left; struct device_node *parent ; switch (of_reconfig_get_state_change(action, rd)) { case OF_RECONFIG_CHANGE_ADD: parent = rd->dn->parent; if (!of_node_is_root(parent) && !of_node_check_flag(parent, OF_POPULATED_BUS)) return NOTIFY_OK; if (of_node_check_flag(rd->dn, OF_POPULATED)) return NOTIFY_OK; rd->dn->fwnode.flags &= ~FWNODE_FLAG_NOT_DEVICE; pdev_parent = of_find_device_by_node(parent); pdev = of_platform_device_create(rd->dn, NULL , pdev_parent ? &pdev_parent->dev : NULL ); platform_device_put(pdev_parent); if (pdev == NULL ) { pr_err("%s: failed to create for '%pOF'\n" , __func__, rd->dn); return notifier_from_errno(-EINVAL); } break ; case OF_RECONFIG_CHANGE_REMOVE: if (!of_node_check_flag(rd->dn, OF_POPULATED)) return NOTIFY_OK; pdev = of_find_device_by_node(rd->dn); if (pdev == NULL ) return NOTIFY_OK; of_platform_device_destroy(&pdev->dev, &children_left); platform_device_put(pdev); break ; } return NOTIFY_OK; }
这是一个静态的全局变量,它定义了一个“通知器块”(notifier_block),将上面实现的 of_platform_notify
函数注册到内核的通知系统中。
1 2 3 4 5 6 7 8 9 10 11 12 static struct notifier_block platform_of_notifier = { .notifier_call = of_platform_notify, };
这是一个简单的包装函数,它的唯一作用就是在内核初始化期间,调用内核API将上面定义的 platform_of_notifier
注册到设备树重配置的通知链中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 void of_platform_register_reconfig_notifier (void ) { WARN_ON(of_reconfig_notifier_register(&platform_of_notifier)); }
此函数是Linux内核中执行”设备树填充”(Device Tree Population)操作的核心引擎 。它的根本作用是遍历一个指定的设备树节点的所有子节点, 并为每一个符合条件的子节点创建一个platform_device
实例 。这个函数是连接静态硬件描述(设备树)和动态内核对象(设备驱动模型)之间的关键桥梁 。
of_platform_populate
的原理可以被理解为一个受控的、单层遍历的设备实例化器 。与旧的、可能递归深入的函数不同, 此函数只处理其直接子节点, 这使得设备创建的逻辑更清晰、更可控。
工作流程:
确定根节点 : 函数首先确定从哪里开始遍历。如果调用者提供了一个root
节点, 它就从该节点开始; 如果root
为NULL
, 它会默认从设备树的绝对根路径/
开始。它会增加根节点的引用计数, 以确保在遍历过程中该节点不会被意外释放。
暂停设备链接同步 : 在开始批量创建设备之前, 它会调用device_links_supplier_sync_state_pause()
。这是一个性能优化, 当一次性创建大量设备时, 暂时禁用设备之间链接状态的同步可以减少不必要的开销。
遍历子节点 : 这是最核心的一步。它使用for_each_child_of_node_scoped
宏来安全地遍历root
节点的所有直接子节点。
调用创建函数 : 对于每一个子节点child
, 它都会调用of_platform_bus_create
函数。of_platform_bus_create
(此文件中未显示, 但其行为是关键)会执行以下判断:
它会检查child
节点是否拥有一个compatible
属性。如果没有, 它会被忽略。
它会检查child
的compatible
属性是否与传入的matches
列表(即总线类型列表)相匹配。
如果child
的compatible
不在matches
列表中 , 那么of_platform_bus_create
就会认为这个child
是一个独立的、需要被实例化的设备 , 于是它会为这个child
创建一个platform_device
实例。
如果child
的compatible
在matches
列表中 , 那么它会被认为是一个新的”总线”或”容器”, of_platform_bus_create
不会 为它创建设备, 而是会**递归地调用of_platform_populate
**来处理这个新总线下的子节点。
恢复与标记 : 遍历完成后, 它会调用device_links_supplier_sync_state_resume()
恢复设备链接的同步。然后, 它会在root
节点上设置一个OF_POPULATED_BUS
标志, 表明这个总线节点下的设备已经被填充过了, 以避免重复操作。
释放引用 : 最后, 它调用of_node_put
来减少在第一步中增加的根节点引用计数。
在STM32H750上的应用:
当我们之前分析的of_platform_default_populate
调用of_platform_populate(NULL, match_table, NULL, NULL)
时:
此函数会从设备树的根(/
)开始。
它找到/soc
节点。of_platform_bus_create
会被调用。
在of_platform_bus_create
内部, 它发现/soc
节点的compatible = "simple-bus"
与match_table
匹配。因此, 它不会为/soc
创建设备, 而是会递归调用of_platform_populate
, 并将/soc
作为新的root
节点。
现在, of_platform_populate
以/soc
为根节点开始新一轮的遍历。
它遍历/soc
的子节点, 如spi1
, i2c1
等。
对于spi1
, of_platform_bus_create
会被调用。它检查到spi1
的compatible = "st,stm32h7-spi"
, 这不 在match_table
中。因此, of_platform_bus_create
会为spi1
创建一个platform_device
。
这个过程对所有/soc
下的外设节点重复进行。
最终结果是, 设备树中所有代表具体硬件外设的节点都被实例化成了内核可以管理的platform_device
对象, 为后续驱动的probe
过程铺平了道路。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 int of_platform_populate (struct device_node *root, const struct of_device_id *matches, const struct of_dev_auxdata *lookup, struct device *parent) { int rc = 0 ; 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); device_links_supplier_sync_state_pause(); for_each_child_of_node_scoped(root, child) { rc = of_platform_bus_create(child, matches, lookup, parent, true ); if (rc) break ; } device_links_supplier_sync_state_resume(); of_node_set_flag(root, OF_POPULATED_BUS); of_node_put(root); return rc; } EXPORT_SYMBOL_GPL(of_platform_populate);
平台设备默认填充 (Population) 此代码片段是Linux内核设备树(Device Tree)处理和平台设备模型 之间至关重要的粘合剂。它的核心作用是在内核启动的早期阶段, 自动地、有选择地遍历设备树, 并为其中的硬件节点创建相应的struct platform_device
实例 。这些platform_device
实例是内核设备模型中的实体, 后续具体的平台驱动程序(platform driver)将会被绑定到这些实体上。
这个过程的根本原理可以被描述为**”按规则的硬件抽象实例化”**。内核不会为设备树中的每一个节点都创建一个平台设备。相反, of_platform_default_populate_init
函数作为一个总调度器, 按照一套预设的规则和优先级来执行”填充”(populate)操作:
处理特殊情况 : 它首先会处理一些特定于体系结构(如PowerPC)或特殊用途的节点, 例如为早期启动的显示设备或特定类型的预留内存(reserved-memory
)区域手动创建平台设备。
处理固件节点 : 它会查找/firmware
节点并填充其下的所有子节点, 为它们创建设备。
处理总线节点 : 这是最关键的一步。它调用of_platform_default_populate
, 这个函数定义了一系列”总线”或”容器”类型的compatible
字符串(如simple-bus
, arm,amba-bus
等)。然后, 它调用更底层的of_platform_populate
函数, 该函数会: a. 从设备树的根开始, 查找所有匹配这个总线compatible
列表的节点。 b. 对于每一个找到的总线节点, 它会遍历其下一级的子节点 。 c. 如果一个子节点本身看起来像一个独立的设备(即它有自己的compatible
属性, 但这个属性不 在总线列表中), 那么内核就会为这个子节点创建一个platform_device
实例 并注册到系统中。
这个过程完成后, 原本只是静态数据描述的设备树, 就被转换成了内核中一个个”活”的、可以与驱动程序交互的struct device
对象。arch_initcall_sync
确保了这个关键的实例化过程在所有平台驱动程序开始探测之前就已经完成。
在STM32H750上的应用:
STM32H750的设备树(.dts
文件)描述了其所有的片上外设。典型的结构如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 / { soc { compatible = "simple-bus" ; #address-cells = <1> ; #size-cells = <1> ; spi1: spi@40013000 { compatible = "st,stm32h7-spi" ; reg = <0x40013000 0x400 >; ... }; i2c1: i2c@40012000 { compatible = "st,stm32-i2c" ; reg = <0x40012000 0x400 >; ... }; }; };
当of_platform_default_populate_init
被调用时:
最后的of_platform_default_populate(NULL, NULL, NULL)
会被执行。
它会找到soc
节点, 因为其compatible
是”simple-bus”。
底层的of_platform_populate
会开始遍历soc
的子节点。
它看到spi1
节点, 发现其compatible
是”st,stm32h7-spi”, 这不 在总线列表中。于是, 内核会为spi1
创建一个platform_device
。
它看到i2c1
节点, 同样为其创建一个platform_device
。
这个过程持续进行, 为所有在soc
下定义的、具有compatible
属性的外设创建平台设备。
稍后, 当stm32h7-spi
驱动和stm32-i2c
驱动被加载时, 内核平台总线核心就会发现这些已经创建好的设备, 并调用相应驱动的probe
函数。
因此, 这段代码是所有STM32片上外设驱动能够被成功探测和加载的绝对前提 。
代码详解 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 int of_platform_default_populate (struct device_node *root, const struct of_dev_auxdata *lookup, struct device *parent) { static const struct of_device_id match_table [] = { { .compatible = "simple-bus" , }, { .compatible = "simple-mfd" , }, { .compatible = "isa" , }, #ifdef CONFIG_ARM_AMBA { .compatible = "arm,amba-bus" , }, #endif {} }; return of_platform_populate(root, match_table, lookup, parent); } EXPORT_SYMBOL_GPL(of_platform_default_populate);
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 static const struct of_device_id reserved_mem_matches [] = { { .compatible = "phram" }, { .compatible = "qcom,rmtfs-mem" }, {} }; static int __init of_platform_default_populate_init (void ) { struct device_node *node ; device_links_supplier_sync_state_pause(); if (IS_ENABLED(CONFIG_PPC)) { } else { for_each_matching_node(node, reserved_mem_matches) of_platform_device_create(node, NULL , NULL ); node = of_find_node_by_path("/firmware" ); if (node) { of_platform_populate(node, NULL , NULL , NULL ); of_node_put(node); } node = of_get_compatible_child(of_chosen, "simple-framebuffer" ); if (node) { sysfb_disable(NULL ); of_platform_device_create(node, NULL , NULL ); of_node_put(node); } of_platform_default_populate(NULL , NULL , NULL ); } return 0 ; } arch_initcall_sync(of_platform_default_populate_init);
此代码片段展示了Linux内核中负责将一个设备树(Device Tree)节点实例化为具体的platform_device
内核对象的”工厂”函数 。这是内核设备模型与设备树硬件描述之间最直接、最关键的链接点。当内核的填充(population)代码(如of_platform_populate
)决定一个设备树节点代表一个需要被管理的硬件设备时, 就会调用这个API。
它的核心原理可以被理解为一个严格的、带状态追踪的四步实例化流程 :
资格审查 : 在做任何事情之前, 函数首先检查该设备树节点是否”合格”。
of_device_is_available(np)
: 检查设备树中该节点的status
属性是否为”okay”或”ok”。如果status
是”disabled”, 则该设备被认为是禁用的, 不应被创建。
of_node_test_and_set_flag(np, OF_POPULATED)
: 这是一个原子 操作, 用来检查并设置OF_POPULATED
标志。它能确保对于同一个设备树节点, platform_device
只会被创建一次 。如果该标志已被设置, 意味着设备已被创建, 函数会立即返回, 从而防止了系统中出现代表同一硬件的重复设备对象。
分配与初始化 : 如果资格审查通过, 函数调用of_device_alloc
来在内存中分配一个struct platform_device
对象, 并用设备树节点中的信息(如名称)对其进行基础初始化。然后, 它会为这个新设备设置一些关键的默认属性:
设置默认的DMA掩码。
将其.bus
指针明确地指向platform_bus_type
, 这正式将其”身份”定义为一个平台设备。
处理platform_data
(一个用于传递非设备树数据的遗留机制)。
注册到内核 : 调用of_device_add
将这个新创建和配置好的设备正式注册 到内核的设备模型核心中。这是最关键的一步, 从此刻起:
该设备会出现在sysfs
文件系统的/sys/devices/platform/
目录下。
平台总线(platform_bus_type
)会被通知有一个新设备加入, 并会立即触发总线的match
流程, 尝试为这个新设备寻找一个匹配的驱动程序。
健壮的错误处理 : 如果在注册步骤(of_device_add
)失败, 函数会执行一个完整的”回滚”操作:
调用platform_device_put
来释放已分配的platform_device
结构体的内存。
调用of_node_clear_flag(np, OF_POPULATED)
来清除在第一步中设置的OF_POPULATED
标志, 将设备树节点恢复到”未被创建”的状态 , 从而允许后续的代码再次尝试为它创建设备。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 static 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 ; pr_debug("create platform device: %pOF\n" , np); if (!of_device_is_available(np) || of_node_test_and_set_flag(np, OF_POPULATED)) return NULL ; dev = of_device_alloc(np, bus_id, parent); if (!dev) goto err_clear_flag; dev->dev.coherent_dma_mask = DMA_BIT_MASK(32 ); if (!dev->dev.dma_mask) dev->dev.dma_mask = &dev->dev.coherent_dma_mask; dev->dev.bus = &platform_bus_type; dev->dev.platform_data = platform_data; of_msi_configure(&dev->dev, dev->dev.of_node); if (of_device_add(dev) != 0 ) { platform_device_put(dev); goto err_clear_flag; } return dev; err_clear_flag: of_node_clear_flag(np, OF_POPULATED); return NULL ; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 struct platform_device *of_platform_device_create (struct device_node *np, const char *bus_id, struct device *parent) { return of_platform_device_create_pdata(np, bus_id, NULL , parent); } EXPORT_SYMBOL(of_platform_device_create);
drivers/of/property.c of_property_match_string 在列表中查找字符串并返回索引 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 int of_property_match_string (const struct device_node *np, const char *propname, const char *string ) { const struct property *prop = of_find_property(np, propname, NULL ); size_t l; int i; const char *p, *end; if (!prop) return -EINVAL; if (!prop->value) return -ENODATA; p = prop->value; end = p + prop->length; for (i = 0 ; p < end; i++, p += l) { l = strnlen(p, end - p) + 1 ; if (p + l > end) return -EILSEQ; pr_debug("comparing %s with %s\n" , string , p); if (strcmp (string , p) == 0 ) return i; } return -ENODATA; } EXPORT_SYMBOL_GPL(of_property_match_string);
of_property_read_string_index 读取字符串列表属性 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 int of_property_read_string_helper (const struct device_node *np, const char *propname, const char **out_strs, size_t sz, int skip) { const struct property *prop = of_find_property(np, propname, NULL ); int l = 0 , i = 0 ; const char *p, *end; if (!prop) return -EINVAL; if (!prop->value) return -ENODATA; p = prop->value; end = p + prop->length; for (i = 0 ; p < end && (!out_strs || i < skip + sz); i++, p += l) { l = strnlen(p, end - p) + 1 ; if (p + l > end) return -EILSEQ; if (out_strs && i >= skip) *out_strs++ = p; } i -= skip; return i <= 0 ? -ENODATA : i; } EXPORT_SYMBOL_GPL(of_property_read_string_helper); static inline int of_property_read_string_index (const struct device_node *np, const char *propname, int index, const char **output) { int rc = of_property_read_string_helper(np, propname, output, 1 , index); return rc < 0 ? rc : 0 ; }