在内核中,经常会听到“设备树”这一概念,根据名字,大致可以理解为计算机系统中的设备按照树型结构进行了组织。
首先,了解一下设备树二进制文件DTB。DTB(Devicetree Blob)是DTS的二进制文件格式,使用DTC工具可以将DTS源文件编译成DTB,随后,引导程序(BIOS)可以将DTB文件所在的地址传递给内核,从而用于内核对设备树的创建。
DTB的文件结构
|-----------------------|
| struct fdt_header |
|-----------------------|
| (free space) |
|-----------------------|
|memory reservation |
|-----------------------|
| (free space) |
|-----------------------|
| structure |
|-----------------------|
| (free space) |
|-----------------------|
| string |
|-----------------------|
| (free space) |
|-----------------------|
其中,关于structure block等每个部分的组成结构如下图所示:
由DTB的文件结构可知DTB主要包含了3个部分:
fdt_header //文件头结构;
structure //存放含Node和Property;
string //存放Property的Name;把Property Name单独分为一个区域的原因是,有很多Property Name是重复的,单独一个区域可以使用指针引用,节约空间。
DTB中struct fdt_header数据结构:
struct fdt_header {
uint32_t magic;
uint32_t totalsize; //total size of DT block
uint32_t off_dt_struct; //offset to structure
uint32_t off_dt_strings; //offset to string
uint32_t off_mem_rsvmap; //offset to memory reserve map
uint32_t version;
uint32_t last_comp_version;
uint32_t boot_cpuid_phys;
uint32_t size_dt_strings; //size of the string block
uint32_t size_dt_struct; //size of the structure block
};
structure主要用来存放节点信息,DTB中每个节点都存在一个节点信息头,即struct fdt_node_header数据结构:
struct fdt_node_header {
fdt32_t tag; //当前节点的类型
char name[0];
};
关于节点中的属性,可以利用struct property_header属性信息头对其进行管理:
struct fdt_property {
fdt32_t tag; //属性标签
fdt32_t len;
fdt32_t nameoff;
char data[0];
};
而对于每种格式的起点和结尾,可以使用5种不同的token来进行表示:
FDT_BEGIN_NODE(0x00000001)
FDT_END_NODE(0x00000002)
FDT_PROP(0x00000003)
FDT_NOP(0x00000004)
FDT_END(0x00000009)
内核通过调用device_tree_init()函数解析DTB文件内容,从而创建设备树结构。其原型如下:
void __init device_tree_init(void)
{
if (!initial_boot_params) //initial_boot_params为DTB文件的入口地址
return;
unflatten_and_copy_device_tree();
}
void __init unflatten_and_copy_device_tree(void)
{
int size;
void *dt;
if (!initial_boot_params) {
pr_warn("No valid device tree found, continuing without\n");
return;
}
size = fdt_totalsize(initial_boot_params);
//获取设备树文件大小
//return fdt32_l(&((const struct fdt_header *)(fdt))->totalsize)
//根据该执行过程,可知,initial_boot_params实际为fdt_header结构体
dt = early_init_dt_alloc_memory_arch(size, roundup_pow_of_two(FDT_V17_SIZE));
//申请fdt_header所需的空间
if (dt) {
memcpy(dt, initial_boot_params, size);
//将fdt_header信息复制到申请的空间
initial_boot_params = dt;
//改变全局变量initial_boot_params指针
}
unflatten_device_tree();
//创建设备树
}
void __init unflatten_device_tree(void)
{
__unflatten_device_tree(initial_boot_params, NULL, &of_root, early_init_dt_alloc_memory_arch, false);
//创建设备树
of_alias_scan(early_init_dt_alloc_memory_arch);
//为设备树中具有别名的设备节点创建别名
unittest_unflatten_overlay_base();
//创建用于单元测试的设备树
}
void *__unflatten_device_tree(const void *blob, struct device_node *dad, struct device_node **mynodes, void *(*dt_alloc)(u64 size, u64 align), bool detached)
//__unflatten_device_tree(initial_boot_params, NULL, &of_root, early_init_dt_alloc_memory_arch, false);
{
int size;
void *mem;
...
if (!blob) {
pr_debug("No device tree pointer\n");
return NULL;
}
...
if (fdt_check_header(blob)) {
//检查DTB文件格式是否正确
pr_err("Invalid device tree blob header\n");
return NULL;
}
size = unflatten_dt_nodes(blob, NULL, dad, NULL);
//此时,该操作只是用来计算设备树所使用的空间
...
size = ALIGN(szie, 4);
//4字节对齐
...
mem = dt_alloc(szie + 4, __alignof__(struct device_node));
//申请设备树地址空间
...
memset(mem, 0, size);
*(__be32 *)(mem+size) = cpu_to_be32(0xdeadbeef);
//结尾标注
...
unflatten_dt_nodes(blob, mem, dad, mynodes);
//创建设备树
...
return mem;
}
static int unflatten_dt_nodes(const void *blob, void *mem, struct device_node *dad, struct device_node *nodepp)
{
//unflatten_dt_nodes(blob, NULL, dad, NULL);
//unflatten_dt_nodes(blob, mem, dad, mynodes);
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;
if (nodepp)
*nodepp = NULL;
if (dad)
depth = inital_depth = 1;
root = dad;
nps[depth] = dad;
for (offset = 0; offset >= 0 && depth >= initial_depth; offset = fdt_next_node(blob, offset, &depth)) {
//fdt_next_node()函数,该函数计算offset在DTB文件中的偏移量。
...
if (!populate_node(blob, offset, &mem, nps[depth], &nps[depth+1], dryrun))
//该函数创建设备节点,即struct device_node。
return mem-base;
//返回设备节点大小
...
}
...
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);
//return ((const char *)fdt + (fdt32_ld(&((const struct fdt_header *)(fdt))->off_dt_struct)) + offset)->name;
//即fdt_node_header->name
...
allocl = ++l;
//name的字符长度
np = unflatten_dt_alloc(mem, sizeof(struct device_node) + allocl, __alignof__(struct device_node));
//设备节点申请空间
if (!dryrun) {
char *fn;
of_node_init(np);
np->full_name = fn = ((char *)np) + sizeof(*np);
memcpy(fn, pathp, l);
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()
{
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);
...
pp = unflatten_dt_alloc(mem, sizeof(struct property), __alignof__(struct property));
...
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;
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;
}
int fdt_next_node(const void *fdt, int offset, int *depth)
//假设该函数为启动阶段第一次被调用,在按照上边的函数,fdt为initial_boot_params,offset为0,*depth为0
{
int nextoffset = 0;
uint32_t tag;
if (offset >= 0)
if ((nextoffset = fdt_check_node_offset_(fdt, offset)) < 0)
//该函数中进行相关的检查之后,返回offset
return nextoffset;
do {
offset = nextoffset;
//假设当前offset为0
tag = fdt_next_tag(fdt, offset, &nextoffset);
//根据offset,查找DTB文件中的标签
switch (tag) {
case FDT_PROP:
case FDT_NOP:
break;
case FDT_BEGIN_NODE:
//如果找到一个设备节点,则depth加1
if (depth)
(*depth)++;
break;
case FDT_END_NODE:
//如果结束了一个设备节点信息的获取,则depth减一
if (depth && ((--(*depth)) < 0))
return nextoffset;
break;
case FDT_END:
if ((nextoffset >= 0) || ((nextoffset == -FDT_ERR_TRUNCATED) && !depth))
return -FDT_ERR_NOTFOUND;
else
return nextoffset;
}
} while (tag != FDT_BEGIN_NODE);
return offset;
}
uint32_t fdt_next_tag(const void *fdt, int startoffset, int *nextoffset)
//查询下一个标签,假设当前为初始阶段,一切都从头开始,则startoffset与nextoffset全部为0
{
const fdt32_t *tagp, *lenp;
uint32_t tag;
int offset = startoffset;
const char *p;
*nextoffset = -FDT_ERR_TRUNCATED;
tagp = fdt_offset_ptr(fdt, offset, FDT_TAGSIZE);
//return (const char *)fdt + (fdt32_ld(&((const struct fdt_header *)(fdt))->off_dt_struct)) + offset;
//可以看出这里从DTB文件中,获取存放设备节点的地址
if (!can_assume(VALID_DTB) && !tagp)
return FDT_END;
tag = fdt32_to_cpu(*tagp);
//读取tagp中的几位,并进行移位组合,构成新的数据传递给tag
offset += FDT_TAGSIZE;
//offset偏移4个字节长度,可见标签存放了四个字节位
*nextoffset = -FDT_ERR_BADSTRUCTURE;
switch (tag) {
case FDT_BEGIN_NODE:
//设备节点的命名
do {
p = fdt_offset_ptr(fdt, offset++, 1);
} while (p && (*p != '\0'));
if (!can_assume(VALID_DTB) && !p)
return FDT_END;
break;
case FDT_PROP:
//设备节点的属性
lenp = fdt_offset_ptr(fdt, offset, sizeof(*lenp));
if (!can_assume(VALID_DTB) && !lenp)
return FDT_END;
offset += sizeof(struct fdt_property) - FDT_TAGSIZE + fdt32_to_cpu(*lenp);
if (!can_assume(LATEST) && fdt_version(fdt) < 0x10 && fdt32_to_cpu(*lenp) >= 8 && ((offset - fdt32_to_cpu(*lenp)) % 8) != 0)
offset += 4;
break;
case FDT_END:
case FDT_END_NODE:
case FDT_NOP:
break;
default:
return FDT_END;
}
if (!fdt_offset_ptr(fdt, startoffste, offset - startoffset))
return FDT_END;
*nextoffset = FDT_TAGALIGN(offset);
return tag;
}
综上,便是设备树的构建过程,关键的地方在与对二进制文件DTB的解析。关于,设备节点之间关系的创建,在源代码中并没有很好的理解,只能看到struct device_node nps数组的应用,具体还需要根据DTB文件安格式以及内容标识来理解。
另外,在简单的了解一下结构体struct device_node。
struct device_node {
const char *name;
phandle phandle;
//typedef u32 phandle
//可见,phandle是用来存放一个地址的。
...
struct property *properties;
//用来存放设备节点的属性
...
struct device_node *parent;
...
void *data;
...
}
通过上边的代码可以知道,设备树的构建过程,即根据dts文件所创间的设备节点,且各个设备节点之间可能存在一定的关联,比如:父子节点,兄弟节点等。当设备节点创建完成后,内核还会根据创建的设备节点来创建platform_device设备。
内核中,关于设备树的执行文件都位于drivers/of目录下,在该目录中有不同的执行文件,其中在platform.c文件中,存在一个特殊的函数,该函数利用了编译器特性进行了声明,并在内核初始化的后期,通过do_initcalls函数来调用执行。
static int __init of_platform_default_populate_init(void)
{
struct device_node *node;
device_links_supplier_sync_state_pause();
if (!of_have_populated_dt())
return -ENODEV;
for_each_matching_node(node, reserved_mem_matches)
of_platform_device_create(node, NULL, NULL);
//根据device_node,即设备节点,来创建platform_device。
node = of_find_node_by_path("/firmware");
//根据“/firmware”路径名来获取设备节点
if (node) {
of_platform_populate(node, NULL, NULL, NULL);
//根据device_node,创建platform_device。
of_node_put(node);
}
fw_devlink_pause();
of_platform_default_populate(NULL, NULL, NULL);
//创建platform_device设备
fw_devlink_resume();
return 0;
}
arch_initcall_sync(of_platform_default_populate_init);
关于arch_initcall_sync函数,其定义如下:
#define arch_initcall_sync(fn) __define_initcall(fn, 3s)
可见,该函数通过do_initcalls函数来调用,从而得到执行。
在上述的of_platform_default_populate_init()函数中,可以看见有三处地方都在创建platform_device设备,其中第二处与第三处只是参数上存在差异。这里先来分析第三处,因为前两处需要进行条件判断,而第三处则不需要。
int of_platform_default_populate(struct device_node *root, const struct of_dev_auxdata *lookup, struct device *parent)
{
return of_platform_populate(root, of_default_bus_match_table, lookup, parent);
}
//关于of_default_bus_match_table,其原型如下:
//const struct of_device_id of_default_bus_match_table[] = {
// {.compatible = "simple-bus"},
// {.compatible = "simple-mfd"},
// {.compatible = "isa"},
//#ifdef CONFIG_ARM_AMBA
// {.compatible = "arm,amba-bus"},
//#endif
// {}
//};
int of_platform_populate(struct device_node *root, const struct of_device_id *matches, const struct of_dev_auxdata *lookup, struct device *parent)
{
//这里分析的为第三次创建,因此参数中除matches外,其余参数均为NULL
struct device_node *child;
int rc = 0;
root = root ? of_node_get(root) : of_find_node_by_path("/");
//当root为空时,此时通过of_find_node_by_path来获取根设备节点,该函数调用of_find_node_opts_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(root, child) {
//该循环遍历所有的设备节点
rc = of_platform_bus_create(child, matches, lookup, parent, true);
//在该函数中,创建设备节点
if (rc) {
of_node_put(child);
break;
}
}
device_links_supplier_sync_state_resume();
of_node_set_flag(root, OF_POPULATED_BUS);
of_node_put(root);
return rc;
}
struct device_node *of_find_node_opts_by_path(const char *path, const char **opts)
{
struct device_node *np = NULL;
struct property *pp;
unsigned long flags;
const char *separator = strchar(path, ":");
if (opts)
*opts = separator ? separator + 1 : NULL;
if (strcmp(path, "/") == 0)
return of_node_get(of_root);
//如果此时路径为“/”,则直接返回of_root节点,of_root为全局变量,为设备树的根节点
if (*path != '/') {
int len;
const char *p = separator;
if (!p)
p = strchrnul(path, '/');
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);
//将设当路径为“/:xxx”时,上述条件不成立,此时,则先获取到根设备节点,随后利用该函数俩遍历所有的子节点找到所需的节点
raw_spin_unlock_irqrestore(&devtree_lock, flags);
return np;
}
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_nodev *child;
struct platform_device *dev;
const char *bus_id = NULL;
void *platform_data = NULL;
int rc = 0;
...
auxdata = of_dev_lookup(lookup, bus);
//以第三处为例分析,则lookup此时为NULL,因此该函数返回NULL
if (auxdata) {
bus_id = auxdata->name;
platform_data = auxdata->platform_data;
}
if (of_device_is_compatible(bus, "arm, primecell")) {
of_amba_device_create(bus, bus_id, platform_data, parent);
return 0;
}
dev = of_platform_device_create_pdata(bus, bus_id, platform_data, parent);
//根据假设的情况,此时除bus为of_root之外,其余参数均为NULL
//该函数定义struct platform_device结构体对象,并对该对象中的struct device属性以及其他属性进行赋值
//最后,调用of_device_add()函数添加该platform_device设备
if (!dev || !of_match_node(matches, bus))
return 0;
for_each_child_of_node(bus, child) {
//当当前节点被创建为platform_device设备之后,开始遍历该节点下的子节点来创建platform device
pr_debug(" create child: %pOF\n", child);
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(),其定义如下:
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;
if (!of_device_is_avaliable(np) || of_node_test_and_set_flag(np, OF_POPULATED))
return NULL;
dev = of_device_alloc(np, bus_id, parent);
//该函数中主要对platform_device设备中的属性进行初始化
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);
//上述过程主要对platform device中的strct device属性进行初始化
if (of_device_add(dev) != 0) {
//添加plarform_device设备
platform_device_put(dev);
goto err_clear_flag;
}
return dev;
...
}
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;
dev = platform_device_alloc("", PLATFORM_DEVID_NONE);
if (!dev)
return NULL;
while (of_address_to_resource(np, num_reg, &temp_res) == 0)
num_reg++;
num_irq = of_irq_count(np);
if (num_irq || num_reg) {
res = kcalloc(num_irq + num_reg, sizeof(*res), 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 leagcy IRQ resources mapped for %p0Fn\n", np);
}
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;
}
int of_device_add(struct platform_device *ofdev)
{
BUG_ON(ofdev->dev.of_node);
ofdev->name = dev_name(&ofdev->dev);
ofdev->id = PLATFORM_DEVID_NONE;
set_dev_node(&ofdev->dev, of_node_to_nid(ofdev->dev.of_node));
return device_add(&ofdev->dev);
}
综上,便是关于设备树的一些简单分析。