对于Linux设备树如何转化成struct device
一直心存疑问,想细揪以下其中的流程。
内核:Linux4.9
初始化流程如下:
start_kernel
|--- setup_arch(&command_line)
|--- setup_machine_fdt
|--- early_init_dt_scan_nodes
|--- early_init_dt_scan_chosen 扫描 /chosen node, 并保存参数到boot_command_line
|--- early_init_dt_scan_root 获取根节点{size,address}-cells信息,保存到dt_root_size_cells和dt_root_addr_cells
|--- early_init_dt_scan_memory 扫描memory node,保存相关信息到meminfo中
|--- unflatten_device_tree() 生成 node
|--- rest_init
|--- kernel_thread(kernel_init, NULL, CLONE_FS); -> do_fork -> kernel_init
|--- kernel_init_freeable
|--- do_basic_setup
|--- driver_init
|--- of_core_init 主要是创建 kset(devicetree)
略...
|--- do_initcalls
|--- arch_initcall
|--- of_platform_default_populate_init 根据 node 生成 struct platform_device
调用路径:
start_kernel
|--- setup_arch(&command_line)
|--- setup_machine_fdt
|--- early_init_dt_scan_nodes
|--- early_init_dt_scan_chosen 扫描 /chosen node, 并保存参数到boot_command_line
|--- early_init_dt_scan_root 获取根节点{size,address}-cells信息,保存到dt_root_size_cells和dt_root_addr_cells
|--- early_init_dt_scan_memory 扫描memory node,保存相关信息到meminfo中
of_scan_flat_dt
是遍历整个从DTB
,为每个node
调用传入的函数
void __init early_init_dt_scan_nodes(void)
{
/* 扫描 /chosen node, 并保存参数到boot_command_line */
of_scan_flat_dt(early_init_dt_scan_chosen, boot_command_line);
/* 获取根节点{size,address}-cells信息,保存到dt_root_size_cells和dt_root_addr_cells */
of_scan_flat_dt(early_init_dt_scan_root, NULL);
/* 扫描memory node,保存相关信息到meminfo中 */
of_scan_flat_dt(early_init_dt_scan_memory, NULL);
}
具体代码就不贴了,就贴重点的:
/* Retrieve command line unless forcing */
if (read_dt_cmdline)
p = of_get_flat_dt_prop(node, "bootargs", &l);
if (p != NULL && l > 0) {
if (concat_cmdline) {
int cmdline_len;
int copy_len;
strlcat(cmdline, " ", COMMAND_LINE_SIZE); /* 追加一个 " " */
cmdline_len = strlen(cmdline);
copy_len = COMMAND_LINE_SIZE - cmdline_len - 1;
copy_len = min((int)l, copy_len);
strncpy(cmdline + cmdline_len, p, copy_len); /* 追加到原来的cmdline */
cmdline[cmdline_len + copy_len] = '\0';
} else {
strlcpy(cmdline, p, min((int)l, COMMAND_LINE_SIZE)); /* 直接覆盖原来的cmdline */
}
}
获取根节点{size,address}-cells信息,保存到dt_root_size_cells和dt_root_addr_cells
没啥好说的
/* 只扫描 “memory” 节点 */
if (strcmp(type, "memory") != 0)
return 0;
reg = of_get_flat_dt_prop(node, "linux,usable-memory", &l);
if (reg == NULL)
reg = of_get_flat_dt_prop(node, "reg", &l);
if (reg == NULL)
return 0;
endp = reg + (l / sizeof(__be32));
pr_debug("memory scan node %s, reg size %d,\n", uname, l);
/* 扫描属性,为每个调用 early_init_dt_add_memory_arch 添加到memblock中 */
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);
early_init_dt_add_memory_arch(base, size);
}
return 0;
early_init_dt_add_memory_arch
里面就是做了一堆检查,然后调用 memblock_add(base, size);
将内存添加到系统中
struct device_node
用来抽象设备树中的一个节点,定义如下:
struct device_node {
const char *name;
const char *type;
phandle phandle;
const char *full_name; /* 从"/"开始的绝对路径name */
struct fwnode_handle fwnode;
struct property *properties;
struct property *deadprops; /* 如果需要删除某些属性,kernel并非真的删除,而是挂入到deadprops的列表 */
struct device_node *parent;
struct device_node *child;
struct device_node *sibling;
struct kobject kobj;
unsigned long _flags;
void *data;
#if defined(CONFIG_SPARC)
const char *path_component_name;
unsigned int unique_id;
struct of_irq_controller *irq_trans;
#endif
};
unflatten_device_tree
|--- __unflatten_device_tree(initial_boot_params, NULL, &of_root, alloc, false)
|--- unflatten_dt_nodes(blob, NULL, dad, NULL) 第一轮扫描,获取整体大小
|--- unflatten_dt_nodes(blob, mem, dad, mynodes) 第二轮扫描,进行实际的展开
|--- of_alias_scan
具体生成过程,略。
入口函数为:of_platform_default_populate_init
,功能是根据 node
生成 struct platform_device
其主要调用路径为:
start_ketnel -> doinitcall -> arch_initcall
of_platform_default_populate_init (drivers/of/platform.c)
|--- of_find_node_by_path("/reserved-memory")
|--- of_find_compatible_node(... "ramoops") # 优先处理 compatible="ramoops"的 "/reserved-memory"子节点,我们假设没有
|--- of_platform_device_create(node,NULL,NULL);
|--- of_platform_default_populate(NULL, NULL, NULL);
|--- of_platform_populate(root, of_default_bus_match_table, NULL, NULL);
|--- of_platform_bus_create(child, of_default_bus_match_table, NULL, NULL, true); # 创建自己的dev并遍历子节点
|--- for_each_child_of_node(root, child) # 遍历子节点
|--- of_platform_device_create_pdata(bus, NULL, NULL, NULL)
|--- for_each_child_of_node(bus, child) # 遍历子节点
|--- of_platform_bus_create(child, match, NULL, &dev->dev, true);
|--- of_node_put(child); # 添加引用计数
|--- kobject_put(&child->kobj);
|--- of_node_set_flag(bus, OF_POPULATED_BUS)
所创建的节点结构体为:
struct platform_device {
const char *name;
int id;
bool id_auto;
struct device dev;
u32 num_resources;
struct resource *resource;
const struct platform_device_id *id_entry;
char *driver_override; /* Driver name to force a match */
/* MFD cell pointer */
struct mfd_cell *mfd_cell;
/* arch specific additions */
struct pdev_archdata archdata;
};
根据上面的流程,我们可知 of_platform_bus_create
是最重要的函数。
它会创建自己的节点并递归处理子节点。
of_platform_bus_create函数:
static int of_platform_bus_create(struct device_node *bus,
const struct of_device_id *matches,
const struct of_dev_auxdata *lookup,
struct device *parent, bool strict)
{
const struct of_dev_auxdata *auxdata;
struct device_node *child;
struct platform_device *dev;
const char *bus_id = NULL;
void *platform_data = NULL;
int rc = 0;
/* 如果 node没有 compatible 属性就退出 */
if (strict && (!of_get_property(bus, "compatible", NULL))) {
pr_debug("%s() - skipping %s, no compatible prop\n",
__func__, bus->full_name);
return 0;
}
/* 如果已经创建过,则直接返回 */
if (of_node_check_flag(bus, OF_POPULATED_BUS)) {
pr_debug("%s() - skipping %s, already populated\n",
__func__, bus->full_name);
return 0;
}
/* 检查是不是需要特殊处理的节点, lookup参数传进来为NULL 忽略即可 */
auxdata = of_dev_lookup(lookup, bus);
if (auxdata) {
bus_id = auxdata->name;
platform_data = auxdata->platform_data;
}
/* 特殊处理,略... */
if (of_device_is_compatible(bus, "arm,primecell")) {
/*
* Don't return an error here to keep compatibility with older
* device tree files.
*/
of_amba_device_create(bus, bus_id, platform_data, parent);
return 0;
}
/* 进行具体的 struct platform_device 节点创建 */
dev = of_platform_device_create_pdata(bus, bus_id, platform_data, parent);
if (!dev || !of_match_node(matches, bus))
return 0;
/* 递归处理子节点 */
for_each_child_of_node(bus, child) {
pr_debug(" create child: %s\n", child->full_name);
rc = of_platform_bus_create(child, matches, lookup, &dev->dev, strict);
if (rc) {
of_node_put(child);
break;
}
}
/* 标记该节点已经处理 */
of_node_set_flag(bus, OF_POPULATED_BUS);
return rc;
函数概要:
of_platform_device_create_pdata函数指向具体节点的创建。
代码如下:
/*
* 创建一个 struct platform_device 节点
*/
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;
/* 判断 status 状态 */
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.bus = &platform_bus_type;
dev->dev.platform_data = platform_data; /* 这个 platform_data 应该为NULL*/
/*
* 扫描node的 dma-ranges 属性,并初始化 dev->coherent_dma_mask,dma_mask
* 如果节点有 iommus 属性
* dev->archdata.mapping = sunxi_mapping (arm_setup_iommu_dma_ops 和具体架构有关)
* dev->archdata.dma_ops = coherent ? &iommu_coherent_ops : &iommu_ops
* 否则:
* dev->archdata.dma_ops = coherent ? &arm_coherent_dma_ops : &arm_dma_ops;
*/
of_dma_configure(&dev->dev, dev->dev.of_node);
/* 没配置 CONFIG_GENERIC_MSI_IRQ_DOMAIN,此函数为空 */
of_msi_configure(&dev->dev, dev->dev.of_node);
if (of_device_add(dev) != 0) {
of_dma_deconfigure(&dev->dev);
platform_device_put(dev);
goto err_clear_flag;
}
return dev;
err_clear_flag:
of_node_clear_flag(np, OF_POPULATED);
return NULL;
}
struct platform_device *of_device_alloc(struct device_node *np,
const char *bus_id,
struct device *parent)
{
struct platform_device *dev;
int rc, i, num_reg = 0, num_irq;
struct resource *res, temp_res;
/*
* 分配并初始化一个 platform_device
* 会初始化以下成员:
* pdev.name
* pdev.id
* pdev.dev(device_initialize(&pdev.dev))
* pdev.dev.release = platform_device_release;
* 调用 arch_setup_pdev_archdata(&pdev); 该函数在ARM上为空
*/
dev = platform_device_alloc("", -1);
if (!dev)
return NULL;
/* 统计io资源:主要是检索 'reg'和 'reg-names' 属性 */
while (of_address_to_resource(np, num_reg, &temp_res) == 0)
num_reg++;
/* 统计中断资源: 主要是检索 ‘interrupts’ 属性 */
num_irq = of_irq_count(np);
/*
* 填充上述资源到 pdev->resource
* reg属性指定的地址 res的 flags = IORESOURCE_MEM
* interrupts属性指定的地址 res 的flags = IORESOURCE_IRQ | irqd_get_trigger_type(irq_get_irq_data(irq));
*/
if (num_irq || num_reg) {
res = kzalloc(sizeof(*res) * (num_irq + num_reg), GFP_KERNEL);
if (!res) {
platform_device_put(dev);
return NULL;
}
dev->num_resources = num_reg + num_irq;
dev->resource = res;
for (i = 0; i < num_reg; i++, res++) {
rc = of_address_to_resource(np, i, res);
WARN_ON(rc);
}
if (of_irq_to_resource_table(np, res, num_irq) != num_irq)
pr_debug("not all legacy IRQ resources mapped for %s\n",
np->name);
}
/*
* 初始化 dev 的一些属性
*/
dev->dev.of_node = of_node_get(np);
dev->dev.fwnode = &np->fwnode;
dev->dev.parent = parent ? : &platform_bus;
if (bus_id)
dev_set_name(&dev->dev, "%s", bus_id);
else
of_device_make_bus_id(&dev->dev);
return dev;
}
void of_device_make_bus_id(struct device *dev)
{
struct device_node *node = dev->of_node;
const __be32 *reg;
u64 addr;
const char __maybe_unused *name;
/* Construct the name, using parent nodes if necessary to ensure uniqueness */
while (node->parent) {
/*
* If the address can be translated, then that is as much
* uniqueness as we need. Make it the first component and return
*/
reg = of_get_property(node, "reg", NULL);
if (reg && (addr = of_translate_address(node, reg)) != OF_BAD_ADDR) {
/*
* 一般情况都是走这里
* 所以可看看出 Name = 'reg_addr.node_name.dev_name'
* 此时 dev_name = ""
*/
dev_set_name(dev, dev_name(dev) ? "%llx.%s:%s" : "%llx.%s",
(unsigned long long)addr, node->name,
dev_name(dev));
return;
}
/* format arguments only used if dev_name() resolves to NULL */
dev_set_name(dev, dev_name(dev) ? "%s:%s" : "%s",
strrchr(node->full_name, '/') + 1, dev_name(dev));
node = node->parent;
}
}
小结:
struct platform_device
,且默认初始化以下成员
dev->dev.bus = &platform_bus_type;
dev_set_name(dev, name)
,name生成规则:reg地址.node_name
dev->dev.dev.release = platform_device_release
dev->resource
和 dev->num_resources
:
reg
和 reg-names
属性),可使用 platform_get_resource(pdev, IORESOURCE_MEM, index)
获取interrupts
属性),可使用 platform_get_irq(pdev, index)
获取dev->archdata.dma_ops
(比较重要,影响后续的dma_alloc_xxx
函数),有以下4种情况(这里以ARM架构为例,其余架构差不多)
coherent
属性(即使在设备树有 dma-coherent
属性)&& 有iommu
(即设备树中有iommus
属性)-> 赋值为 iommu_coherent_ops
coherent
属性(即使在设备树有 dma-coherent
属性)&& 无iommu
(即设备树中有iommus
属性)-> 赋值为 iommu_ops
coherent
属性(即使在设备树有 dma-coherent
属性)&& 有iommu
(即设备树中有iommus
属性)-> 赋值为 arm_coherent_dma_ops
coherent
属性(即使在设备树有 dma-coherent
属性)&& 无iommu
(即设备树中有iommus
属性)-> 赋值为 arm_dma_ops