Linux内核移植 part2:uboot设备树--生成过程分析

本文从设备树软件控制相关代码进行分析,进而理清设备树相关的知识。

先放一个设备树在内存中的结构图:

Linux内核移植 part2:uboot设备树--生成过程分析_第1张图片

分析来源为$(tree)/lib/fdtdec_test.c

一、数据结构

1.1 文件头

每个dtb都包含如下结构的文件头,用来表示设备树的基础信息。

struct fdt_header {
    fdt32_t magic;           /* magic word FDT_MAGIC */
    fdt32_t totalsize;       /* total size of DT block */
    fdt32_t off_dt_struct;       /* offset to structure */
    fdt32_t off_dt_strings;      /* offset to strings */
    fdt32_t off_mem_rsvmap;      /* offset to memory reserve map */
    fdt32_t version;         /* format version */
    fdt32_t last_comp_version;   /* last compatible version */

    /* version 2 fields below */
    fdt32_t boot_cpuid_phys;     /* Which physical CPU id we're
                        booting on */
    /* version 3 fields below */
    fdt32_t size_dt_strings;     /* size of the strings block */

    /* version 17 fields below */
    fdt32_t size_dt_struct;      /* size of the structure block */
};

1.2 节点头

struct fdt_node_header {
    fdt32_t tag;
    char name[0];
};

二、生成设备树

static int make_fdt(void *fdt, int size, const char *aliases,
            const char *nodes)
{
    char name[20], value[20];
    const char *s;
    int fd;

    // 根据size创建fdt对象,此处size=16KB
    CHECK(fdt_create(fdt, size));
    // 设置保留内存段
    CHECK(fdt_finish_reservemap(fdt));
    // 添加开始节点
    CHECK(fdt_begin_node(fdt, ""));

    CHECK(fdt_begin_node(fdt, "aliases"));
    for (s = aliases; *s;) {
        sprintf(name, "i2c%c", *s);
        sprintf(value, "/i2c%d@0", s[1] - 'a');
        CHECK(fdt_property_string(fdt, name, value));
        s += 2 + (s[2] != '\0');
    }
    CHECK(fdt_end_node(fdt));

    for (s = nodes; *s; s++) {
        sprintf(value, "i2c%d@0", (*s & 0xdf) - 'A');
        CHECK(fdt_begin_node(fdt, value));
        CHECK(fdt_property_string(fdt, "compatible",
            fdtdec_get_compatible(COMPAT_UNKNOWN)));
        if (*s <= 'Z')
            CHECK(fdt_property_string(fdt, "status", "disabled"));
        CHECK(fdt_end_node(fdt));
    }

    CHECK(fdt_end_node(fdt));
    CHECK(fdt_finish(fdt));
    CHECK(fdt_pack(fdt));
#if defined(DEBUG) && defined(CONFIG_SANDBOX)
    fd = os_open("/tmp/fdtdec-text.dtb", OS_O_CREAT | OS_O_WRONLY);
    if (fd == -1) {
        printf("Could not open .dtb file to write\n");
        return -1;
    }
    os_write(fd, fdt, size);
    os_close(fd);
#endif
    return 0;
}

2.1 节点分配

2.1.1 开始分配节点

/* 根据空余空间分配新的节点,新分配的node位于dt_struct下面 */
int fdt_begin_node(void *fdt, const char *name)
{
    struct fdt_node_header *nh;
    int namelen = strlen(name) + 1;

    FDT_SW_CHECK_HEADER(fdt);

    // 分配存放node空间
    nh = _fdt_grab_space(fdt, sizeof(*nh) + FDT_TAGALIGN(namelen));
    if (! nh)
        return -FDT_ERR_NOSPACE;

    // 设置节点tag,name
    nh->tag = cpu_to_fdt32(FDT_BEGIN_NODE);
    memcpy(nh->name, name, namelen);
    return 0;
}

2.1.2 属性节点和字符串块

/* include/libfdt.h */
#define fdt_property_string(fdt, name, str) \
    fdt_property(fdt, name, str, strlen(str)+1)

/* lib/libfdt/fdt_sw.c */
int fdt_property(void *fdt, const char *name, const void *val, int len)
{
    struct fdt_property *prop;
    int nameoff;

    FDT_SW_CHECK_HEADER(fdt);

    /* 在字符串块中搜索有没有名字为name的字符串,返回存放的字符串的地址 
     * 这个地址是从字符串块的底部开始计算偏移的。
     */
    nameoff = _fdt_find_add_string(fdt, name);
    if (nameoff == 0)
        return -FDT_ERR_NOSPACE;

    prop = _fdt_grab_space(fdt, sizeof(*prop) + FDT_TAGALIGN(len));
    if (! prop)
        return -FDT_ERR_NOSPACE;

    /* 属性节点存放了tag,value以及name的偏移和长度 */
    prop->tag = cpu_to_fdt32(FDT_PROP);
    prop->nameoff = cpu_to_fdt32(nameoff);
    prop->len = cpu_to_fdt32(len);
    memcpy(prop->data, val, len);
    return 0;
}

注意: 属性name和value的格式

aliases = "1e 3d"
for (s = aliases; *s;) {
        /* 名字是字符串加数字 */
        sprintf(name, "i2c%c", *s);
        /* value是路径@地址 */
        sprintf(value, "/i2c%d@0", s[1] - 'a');
        CHECK(fdt_property_string(fdt, name, value));
        s += 2 + (s[2] != '\0');
    }

2.1.3 节点结束标记

int fdt_end_node(void *fdt);

执行这个意味着该节点到此结束了。其实就是在node末尾写了一个标识符FDT_END_NODE, 说明该节点结束了,下面开始新的节点定义。

每一个节点的定义都经历了2.1节这样的过程。

2.2 完成设备树

int fdt_finish(void *fdt)

这个函数首先在未分配空间顶部写入FDT_END,然后通过把字符串块移动到结构块下面,消除剩余的未分配空间,再重新计算字符串块中的各个name的偏移,最后调整文件头,因为totalsize变了。

2.3 设备树打包

这个时候还要计算reserved mem大小,reserved mem位于header和dt_struct之间。然后重新计算结构块和字符串块的偏移。基本上就是重复2.2的过程。打包完成后就生成了最终的dtb文件。

你可能感兴趣的:(Bootloader,Linux,Linux内核移植)