linux内核device tree的初始化流程

ARM 194浏览


linux内核device tree的初始化流程

一、前言

自从内核引入device tree之后,驱动开发者再也不需要因为硬件上的修改而去修改arch/arm/plat-xxx和arch/arm/mach-xxx中的代码了。那么内核是如何将dtb转换成内核能够使用的资源呢?本文就这个问题来做分析。
注:本文涉及的代码基于linux 3.10版本

二、device tree的初始化流程

在内核初始化的时候,dtb被转换成device_node的树状结构,以便后续操作。具体的代码流程如下:
start_kernel->setup_arch->unflatten_device_tree

void __init unflatten_device_tree(void)
{
    __unflatten_device_tree(initial_boot_params, &of_allnodes,
                early_init_dt_alloc_memory_arch);

    /* Get pointer to "/chosen" and "/aliases" nodes for use everywhere */
    of_alias_scan(early_init_dt_alloc_memory_arch);
}

将dtb展开,并将其组成成一个树状结构,主要功能在__unflatten_device_tree函数中实现,具体代码如下:

static void __unflatten_device_tree(struct boot_param_header *blob,  //dtb在内存中的虚拟地址
                 struct device_node **mynodes,  //一个全局的指针,后续可以通过这个指针变量dtb所有的节点
                 void * (*dt_alloc)(u64 size, u64 align)) //内存分配回调函数
{
    unsigned long size;
    void *start, *mem;
    struct device_node **allnextp = mynodes;

    if (!blob) {
        pr_debug("No device tree pointern");
        return;
    }
    ...
    if (be32_to_cpu(blob->magic) != OF_DT_HEADER) {
        pr_err("Invalid device tree blob headern");
        return;
    }

    /* First pass, scan for size */
    start = ((void *)blob) + be32_to_cpu(blob->off_dt_struct);
    size = (unsigned long)unflatten_dt_node(blob, 0, &start, NULL, NULL, 0);
    size = ALIGN(size, 4);

    pr_debug("  size is %lx, allocating...n", size);

    /* Allocate memory for the expanded device tree */
    mem = dt_alloc(size + 4, __alignof__(struct device_node));
    memset(mem, 0, size);

    *(__be32 *)(mem + size) = cpu_to_be32(0xdeadbeef);

    pr_debug("  unflattening %p...n", mem);

    /* Second pass, do actual unflattening */
    start = ((void *)blob) + be32_to_cpu(blob->off_dt_struct);
    unflatten_dt_node(blob, mem, &start, NULL, &allnextp, 0);  //重点在这里
    if (be32_to_cpup(start) != OF_DT_END)
        pr_warning("Weird tag at end of tree: %08xn", be32_to_cpup(start));
    if (be32_to_cpup(mem + size) != 0xdeadbeef)
        pr_warning("End of tree marker overwritten: %08xn",
               be32_to_cpup(mem + size));
    *allnextp = NULL;

    pr_debug(" <- unflatten_device_tree()n");
}

unflatten_dt_node函数真正完成了解析dtb的任务,首先找到dtb的根节点,并创建一个struct device_node ,然后把这个根节点对应的struct device_node 赋值给all_nodes全局变量。再通过递归的方法遍历根节点下面的子节点,最终创建一个树结构。
unflatten_dt_node具体代码如下:

static void * unflatten_dt_node(struct boot_param_header *blob,
                void *mem,
                void **p,
                struct device_node *dad,
                struct device_node ***allnextpp,
                unsigned long fpsize)
{
    struct device_node *np;
    struct property *pp, **prev_pp = NULL;
    char *pathp;
    u32 tag;
    unsigned int l, allocl;
    int has_name = 0;
    int new_format = 0;

    tag = be32_to_cpup(*p);
    if (tag != OF_DT_BEGIN_NODE) {
        pr_err("Weird tag at start of node: %xn", tag);
        return mem;
    }
    *p += 4;
    pathp = *p;
    l = allocl = strlen(pathp) + 1;
    *p = PTR_ALIGN(*p + l, 4);

    /* version 0x10 has a more compact unit name here instead of the full
     * path. we accumulate the full path size using "fpsize", we'll rebuild
     * it later. We detect this because the first character of the name is
     * not '/'.
     */
    if ((*pathp) != '/') {
        new_format = 1;
        if (fpsize == 0) {
            /* root node: special case. fpsize accounts for path
             * plus terminating zero. root node only has '/', so
             * fpsize should be 2, but we want to avoid the first
             * level nodes to have two '/' so we use fpsize 1 here
             */
            fpsize = 1;
            allocl = 2;
            l = 1;
            *pathp = '';
        } else {
            /* account for '/' and path size minus terminal 0
             * already in 'l'
             */
            fpsize += l;
            allocl = fpsize;
        }
    }

    np = unflatten_dt_alloc(&mem, sizeof(struct device_node) + allocl,
                __alignof__(struct device_node));
    if (allnextpp) {
        char *fn;
        np->full_name = fn = ((char *)np) + sizeof(*np);
        if (new_format) {
            /* rebuild full path for new format */
            if (dad && dad->parent) {
                strcpy(fn, dad->full_name);
#ifdef DEBUG
                if ((strlen(fn) + l + 1) != allocl) {
                    pr_debug("%s: p: %d, l: %d, a: %dn",
                        pathp, (int)strlen(fn),
                        l, allocl);
                }
#endif
                fn += strlen(fn);
            }
            *(fn++) = '/';
        }
        memcpy(fn, pathp, l);

        prev_pp = &np->properties;
        **allnextpp = np;
        *allnextpp = &np->allnext;
        if (dad != NULL) {
            np->parent = dad;
            /* we temporarily use the next field as `last_child'*/
            if (dad->next == NULL)
                dad->child = np;
            else
                dad->next->sibling = np;
            dad->next = np;
        }
        kref_init(&np->kref);
    }
    /* process properties */
    while (1) {
        u32 sz, noff;
        char *pname;

        tag = be32_to_cpup(*p);
        if (tag == OF_DT_NOP) {
            *p += 4;
            continue;
        }
        if (tag != OF_DT_PROP)
            break;
        *p += 4;
        sz = be32_to_cpup(*p);
        noff = be32_to_cpup(*p + 4);
        *p += 8;
        if (be32_to_cpu(blob->version) < 0x10)
            *p = PTR_ALIGN(*p, sz >= 8 ? 8 : 4);

        pname = of_fdt_get_string(blob, noff);
        if (pname == NULL) {
            pr_info("Can't find property name in list !n");
            break;
        }
        if (strcmp(pname, "name") == 0)
            has_name = 1;
        l = strlen(pname) + 1;
        pp = unflatten_dt_alloc(&mem, sizeof(struct property),
                    __alignof__(struct property));
        if (allnextpp) {
            /* We accept flattened tree phandles either in
             * ePAPR-style "phandle" properties, or the
             * legacy "linux,phandle" properties.  If both
             * appear and have different values, things
             * will get weird.  Don't do that. */
            if ((strcmp(pname, "phandle") == 0) ||
                (strcmp(pname, "linux,phandle") == 0)) {
                if (np->phandle == 0)
                    np->phandle = be32_to_cpup((__be32*)*p);
            }
            /* And we process the "ibm,phandle" property
             * used in pSeries dynamic device tree
             * stuff */
            if (strcmp(pname, "ibm,phandle") == 0)
                np->phandle = be32_to_cpup((__be32 *)*p);
            pp->name = pname;
            pp->length = sz;
            pp->value = *p;
            *prev_pp = pp;
            prev_pp = &pp->next;
        }
        *p = PTR_ALIGN((*p) + sz, 4);
    }
    /* with version 0x10 we may not have the name property, recreate
     * it here from the unit name if absent
     */
    if (!has_name) {
        char *p1 = pathp, *ps = pathp, *pa = NULL;
        int sz;

        while (*p1) {
            if ((*p1) == '@')
                pa = p1;
            if ((*p1) == '/')
                ps = p1 + 1;
            p1++;
        }
        if (pa < ps)
            pa = p1;
        sz = (pa - ps) + 1;
        pp = unflatten_dt_alloc(&mem, sizeof(struct property) + sz,
                    __alignof__(struct property));
        if (allnextpp) {
            pp->name = "name";
            pp->length = sz;
            pp->value = pp + 1;
            *prev_pp = pp;
            prev_pp = &pp->next;
            memcpy(pp->value, ps, sz - 1);
            ((char *)pp->value)[sz - 1] = 0;
            pr_debug("fixed up name for %s -> %sn", pathp,
                (char *)pp->value);
        }
    }
    if (allnextpp) {
        *prev_pp = NULL;
        np->name = of_get_property(np, "name", NULL);
        np->type = of_get_property(np, "device_type", NULL);

        if (!np->name)
            np->name = "<NULL>";
        if (!np->type)
            np->type = "<NULL>";
    }
    while (tag == OF_DT_BEGIN_NODE || tag == OF_DT_NOP) {
        if (tag == OF_DT_NOP)
            *p += 4;
        else
            mem = unflatten_dt_node(blob, mem, p, np, allnextpp,
                        fpsize);
        tag = be32_to_cpup(*p);
    }
    if (tag != OF_DT_END_NODE) {
        pr_err("Weird tag at end of node: %xn", tag);
        return mem;
    }
    *p += 4;
    return mem;
}

三、总结

device tree的初始化主要工作就是构建device node tree,最重要的是给struct device_node *of_allnodes这个全局变量赋值,它相当于是device tree的根节点,在后续内核的启动中,内核就会拿到这个根节点来初始化dts中描述的各个device。这里说的初始化device是指内核构建各个device结构体的过程,我将在后续的文章中介绍。