unflatten_device_tree()

DTB를 Expanded Format으로 변환한다.

  • device_node와 property 구조체를 사용하여 트리 구조로 각 노드와 속성을 연결한다.
  • 기존에 사용하던 DTB 바이너리들도 문자열등을 그대로 사용하므로 삭제되지 않고 유지된다.

unflatten_device_tree-3

 

DTB에서 Full Path Name

arch/arm/boot/dts/twl4030.dtsi

    (...생략...)
    charger: bci {
    	compatible = "ti,twl4030-bci";
    	interrupts = <9>, <2>;
    	bci3v1-supply = <&vusb3v1>;
    };
    (...생략...)
  • 위 device tree 스크립트에서 에서 라벨명으로 charger가 사용되었고 bci가 축약된 노드명이다.
  • Device Tree 버전 0x10이되면서 full path name 대신 위와 같이 bci라는 이름의 축약된 노드명으로 널리사용하게 되었다.
    • 예) bci 또는 bci@10000000과 같이 주소 정보를 포함
  • 위의 노드명을 full path name으로 표기 하면 다음과 같다.
    • /ocp/i2c@48070000/twl@48/bci
      • 루트 노드 -> ocp 노드 -> i2c@48070000 노드 -> trw@48 노드 -> bci 노드
  • 참고: Device trees I: Are we having fun yet? | LWN.net

 

Device Tree Expanded Format

4바이트 단위의 바이너리로 구성된 DTB를 unflatten_device_tree() 함수를 통해 unflatten 과정으로 변환하면 각 노드는 device_node 구조체로 변환되어 전역 of_root  노드가 루트 노드를 가리킨다. 역시 각 노드에 있는 속성들도 property 구조체로 변환되어 해당 노드에 등록되는데 이들은 of_로 시작되는 API에 의해 관리되어 사용된다.

unflatten_device_tree-1a

 

unflatten_device_tree()

drivers/of/fdt.c

/**
 * unflatten_device_tree - create tree of device_nodes from flat blob
 *
 * unflattens the device-tree passed by the firmware, creating the
 * tree of struct device_node. It also fills the "name" and "type"
 * pointers of the nodes so the normal device-tree walking functions
 * can be used.
 */
void __init unflatten_device_tree(void)
{
        __unflatten_device_tree(initial_boot_params, &of_root,
                                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);
}
  • __unflatten_device_tree(initial_boot_params, &of_root,
    early_init_dt_alloc_memory_arch);

    • 4바이트 단위의 바이너리로 구성된 DTB를 파싱하여 expanded format으로 변환한 후 of_root 전역 변수가 가리키게 한다.
  • of_alias_scan(early_init_dt_alloc_memory_arch);
    • 전역 aliases_lookup 리스트에 alias_prop 들을 추가한다.
    • 전역 of_stdout에 “/chosen” 노드의 “stdout-path” 속성 값에 대응하는 노드를 찾아 대입한다.

 

__unflatten_device_tree()

drivers/of/fdt.c

/**
 * __unflatten_device_tree - create tree of device_nodes from flat blob
 *
 * unflattens a device-tree, creating the
 * tree of struct device_node. It also fills the "name" and "type"
 * pointers of the nodes so the normal device-tree walking functions
 * can be used.
 * @blob: The blob to expand
 * @mynodes: The device_node tree created by the call
 * @dt_alloc: An allocator that provides a virtual address to memory
 * for the resulting tree
 */
static void __unflatten_device_tree(void *blob,
                             struct device_node **mynodes,
                             void * (*dt_alloc)(u64 size, u64 align))
{
        unsigned long size;
        int start;
        void *mem;

        pr_debug(" -> unflatten_device_tree()\n");

        if (!blob) {
                pr_debug("No device tree pointer\n");
                return;
        }

        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;
        }

        /* First pass, scan for size */
        start = 0;
        size = (unsigned long)unflatten_dt_node(blob, NULL, &start, NULL, NULL, 0, true);
        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 = 0;
        unflatten_dt_node(blob, mem, &start, NULL, mynodes, 0, false);
        if (be32_to_cpup(mem + size) != 0xdeadbeef)
                pr_warning("End of tree marker overwritten: %08x\n",
                           be32_to_cpup(mem + size));

        pr_debug(" <- unflatten_device_tree()\n");
}
  • if (fdt_check_header(blob)) {
    • DTB 헤더를 체크하여 지원되지 않는 경우 에러를 출력하고 리턴한다.
  • size = (unsigned long)unflatten_dt_node(blob, NULL, &start, NULL, NULL, 0, true);
    • 가장 마지막 인수 dryrun을 true로 하여 실제 컨버팅 동작을 하지 않고 device_node들과 properties 노드의 구성에 필요한 전체 사이즈의 크기만을 구해온다.
  • mem = dt_alloc(size + 4, __alignof__(struct device_node));
    • memblock을 통해 device_node 구조체 크기 단위로 필요한 size + 4 만큼을 추가한다.
  • *(__be32 *)(mem + size) = cpu_to_be32(0xdeadbeef);
    • 할당된 메모리의 마지막 4바이트에 0xdeadbeef를 경계 침범(like as stack canary)을 모니터링 하기 위해 저장한다.
  • unflatten_dt_node(blob, mem, &start, NULL, mynodes, 0, false);
    • DTB를 파싱하여 device_node 및 property 구조체 배열로 변환한다.
  •  if (be32_to_cpup(mem + size) != 0xdeadbeef)
    • 할당 메모리의 끝에 설치한 경계 침범(like as stack canary) 값이 오염되었는지 확인하여 경고 출력을 한다.

 

unflatten_dt_node()

drivers/of/fdt.c

/**
 * unflatten_dt_node - Alloc and populate a device_node from the flat tree
 * @blob: The parent device tree blob
 * @mem: Memory chunk to use for allocating device nodes and properties
 * @p: pointer to node in flat tree
 * @dad: Parent struct device_node
 * @fpsize: Size of the node path up at the current depth.
 */
static void * unflatten_dt_node(void *blob,
                                void *mem,
                                int *poffset,
                                struct device_node *dad,
                                struct device_node **nodepp,
                                unsigned long fpsize,
                                bool dryrun)
{
        const __be32 *p;
        struct device_node *np;
        struct property *pp, **prev_pp = NULL;
        const char *pathp;
        unsigned int l, allocl;
        static int depth = 0;
        int old_depth;
        int offset;
        int has_name = 0;
        int new_format = 0;

        pathp = fdt_get_name(blob, *poffset, &l);
        if (!pathp)
                return mem;

        allocl = l++;

        /* 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;
                }
        }

노드명(pathp), 노드명 길이(l) 및 할당 길이(allocl)를 알아온다.

  • pathp = fdt_get_name(blob, *poffset, &l);
    • blob의 poffset 노드에서 노드명을 알아오고 l에는 길이를 담아온다.
    • DTB version 0x10에서는 compact 노드명을 사용하고 그 전에는 full path 노드명을 사용하였다.
    • 예) 노드명(compact 스타일)=”uart@7e201000″
      • pathp=”uart@7e201000″을 가리키고, l=13
    • 예) 노드명(full path 스타일)=”/soc/uart@7e201000″
      • pathp=”/soc/uart@7e201000″을 가리키고, l=18
  • if (!pathp) return mem;
    • 노드명이 없는 경우 skip하기 위해 mem을 리턴한다.
  • allocl = l++;
    • allocl(할당 길이)에 l(노드명 길이)을 대입한 후 l을 1만큼 증가시킨다. -> l(노드명 길이)을 1만큼 증가시키고 allocl(할당 길이)에도 l(노드명 길이)을 대입한다.
      • 여기서 설정된 allocl은 기존 버전(0x10 이전)의 DTB를 읽어들일 때 사용한다.
    • 할당 길이에 null-terminated 바이트가 빠진채로 사용되어 버그로 추정되어 확인해보니 버그가 fix되어 있음을 알 수 있었다.

노드명이 ‘/’로 시작하지 않는 경우 즉 compact 노드명을 사용하는 경우 l(노드명 길이), allocl(할당 길이), fpsize(full path size) 등이 조정된다.

  • if ((*pathp) != ‘/’) { new_format = 1;
    • 노드명의 시작이 ‘/’가 아니면 new_format=1을 대입한다.
      • DTB version 0x10의 경우 루트 노드명은 “/”가 아닌 null 문자열로 판단된다
    • new_format
      • 0=노드명에 대해 full path format을 사용
      • 1=노드명에 대해 compact format을 사용
  • if (fpsize == 0) { fpsize = 1; allocl = 2; l = 1; pathp = “”;
    • 이 함수가 재귀 호출되지 않은 경우는 fpsize(full path size)가 0인 루트 노드이다.
      • __unflatten_dt_node() 함수에서 처음 루트 노드부터 호출하는 경우 fpsize=0으로 진입한다.
      • fpsize=1, allocl(할당길이)=2, l(‘/’ 포함한 노드명 길이)=1, pathp(노드명)=””으로 한다.
        • 루트 노드는 ‘/’ + null 로 끝나므로 fpsize가 2이지만 노드명을 “”으로 변경하여 fpsize를 1로 설정한다.
          • pathp=””으로 해놓으면 잠시 후에 중간 연결 문자인 ‘/’ 문자 뒤에 pathp가 추가되어 결국 루트노드는 pathp=”/”가 된다.
  • } else { fpsize += l; allocl = fpsize; }
    • 이 함수가 sub 노드 처리를 위해 재귀 호출된 경우 fpsize가 0이 아닌 값을 가진다.
      • 이러한 경우 fpsize에 l을 더하고,  그 값을 할당길이에도 대입한다.
        • 기존 인수로 진입된 fpsize에는 기존 노드에 대한 full path 길이가 들어가 있다.

아래 그림은 노드명이 full path name으로 바뀌어 저장되는 과정을 설명하였다.

unflatten_dt_node-3a

        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);
                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: %d\n",
                                                pathp, (int)strlen(fn),
                                                l, allocl);
                                }
#endif
                                fn += strlen(fn);
                        }
                        *(fn++) = '/';
                }
                memcpy(fn, pathp, l);

                prev_pp = &np->properties;
                if (dad != NULL) {
                        np->parent = dad;
                        np->sibling = dad->child;
                        dad->child = np;
                }
        }

device_node 구조체를 다음과 같이 구성한다.

  • device_node 구조체 사이즈 + full path name + 1 만큼의 영역을 사용한다
  • np->full_name에 device_node 바로 뒤 full path name 위치를 가리키게 한다.
  • np->kobject 초기화
  • np->fwnode.type = FWNODE_OF로 초기화
  • 부모 노드 밑에 가장 앞에 위치한 자식 노드로 연결을 한다.
    • np->parent에 부모 노드를 가리키게 한다.
    • np->sibling에 보무 노드의 child 노드를 가리키게 한다.
    • dad->child에 현재 노드를 가리키게 한다.
  • 다음 세 항목은 속성 처리 후에 갱신된다.
    • 노드에 모든 속성 등록이 완료된 후에는 np->name에 name 속성의 값을 연결한다.
    • 노드에 phandle 속성명이 있는 경우 np->phandle에 속성데이터가 가리키는 노드를 연결한다.
    • np->properties에 첫 속성을 가리키게한다.

 

  • np = unflatten_dt_alloc(&mem, sizeof(struct device_node) + allocl,
  • __alignof__(struct device_node));
    • device_node 구조체 크기 단위로 device_node 구조체 + 할당길이(full path name 길이)만큼 사용할 np 주소를 얻어내고 mem 주소는 그 만큼 증가한다.
  • if (!dryrun) {
    • 가짜 동작(사이즈만 알아올 목적)이 아닌 경우
  • of_node_init(np);
    • np->kobj를 초기화하고 np->fwnode.type에 FWNODE_OF(1)을 대입한다.
  • np->full_name = fn = ((char *)np) + sizeof(*np);
    • np->full_name과 fn에 full path 노드명을 가리키게 한다.
      • 배정 받은 공간은 device_node 구조체 하나와 full path 노드명이 담긴다.
  • if (new_format) {
    • new_format(DTB에 기록된 노드명이 compact 노드명을 사용)인 경우
  • if (dad && dad->parent) {
    • 부모노드와 부모노드의 부모가 존재하는 경우 즉 현재 노드의 depth가 2 이상인 경우
      • 루트 노드는 보통 depth=0 이다.
  • strcpy(fn, dad->full_name); fn += strlen(fn);
    • fn에 부모노드의 full_name을 복사하고 fn을 fn 문자열 길이만큼 증가시킨다.
  • *(fn++) = ‘/’;    memcpy(fn, pathp, l);
    • fn위치에 ‘/’을 대입한 후 fn을 한 글자만큼 증가시키고 그 위치에 pathp를 l길이만큼 복사한다.
    • [부모 full path 노드명]에 compact 노드 명을 더하기 전에 ‘/’문자열을 먼저 추가한다.
      • [부모 full path 노드명] / <compact 노드명>
        • 예) 부모 full path 노드명=”/abc”, compact 노드명=”def@1000″
          • “/abc/def@1000”
  • prev_pp = &np->properties;
    • prev_pp에 properties(속성 포인터) 를 대입한다.
  • if (dad != NULL) { np->parent = dad; np->sibling = dad->child; dad->child = np; }
    • dad 밑 child 앞에 np를 끼워넣는다.
      • dad 노드가 존재하는 경우 np->parent에 dad 노드를 대입하고 sibling에 dad->child를 대입하고, dad->child에 np를 대입한다.

아래 그림은 a@1000 노드의 서브 노드로 a2 노드가 추가될 때의 상황이다.

unflatten_dt_node-2c

 

        /* process properties */
        for (offset = fdt_first_property_offset(blob, *poffset);
             (offset >= 0);
             (offset = fdt_next_property_offset(blob, offset))) {
                const char *pname;
                u32 sz;

                if (!(p = fdt_getprop_by_offset(blob, offset, &pname, &sz))) {
                        offset = -FDT_ERR_INTERNAL;
                        break;
                }

                if (pname == NULL) {
                        pr_info("Can't find property name in list !\n");
                        break;
                }
                if (strcmp(pname, "name") == 0)
                        has_name = 1;
                pp = unflatten_dt_alloc(&mem, sizeof(struct property),
                                        __alignof__(struct property));
                if (!dryrun) {
                        /* 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(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(p);
                        pp->name = (char *)pname;
                        pp->length = sz;
                        pp->value = (__be32 *)p;
                        *prev_pp = pp;
                        prev_pp = &pp->next;
                }
        }

property 구조체를 다음과 같이 구성한다.

  • property 구조체 사이즈 만큼의 영역을 사용한다
  • pp->name에 DTB의 속성명을 가리키게 한다.
  • pp->length에 속성 값의 사이즈를 대입한다.
  • pp->value에 속성 값을 대입한다.
  • 속성들 중 마지막에 현재 속성을 연결 한다.
    • 기존 속성 pp->next에 현재 속성을 연결한다.

 

  • for (offset = fdt_first_property_offset(blob, *poffset); (offset >= 0); (offset = fdt_next_property_offset(blob, offset))) {
    • poffset에 있는 노드의 첫 속성 부터 마지막 속성까지 루프를 돈다.
  • if (!(p = fdt_getprop_by_offset(blob, offset, &pname, &sz))) { offset = -FDT_ERR_INTERNAL; break; }
    • 속성명을 출력 인수 pname에, 속성 데이터 사이즈를 출력인수 sz에 저장하고 속성 데이터를 p에 알아오는데 발견하지 못하면 offset에 에러 번호를 대입하고 루프를 빠져나간다.
  • if (strcmp(pname, “name”) == 0) has_name = 1;
    • 속성명에 “name”이 있는 경우 has_name=1을 대입한다.
    • has_name
      • 현재 노드 내에 하나도 “name” 속성명이 발견되지 않으면 추가로 “name”이라는 속성명을 만들려고 할 때 사용하는 플래그이다.
      • compact 노드명을 사용하는 DTB version 0x10에는 name 속성이 구성되어 있지 않지만 expanded format을 구성할 때에는 각 노드의 name 속성이 필요하다.
  • pp = unflatten_dt_alloc(&mem, sizeof(struct property), __alignof__(struct property));
    • property 구조체 크기 단위로 property 구조체 크기만큼 사용할 np 주소를 얻어내고 mem 주소는 그 만큼 증가한다..
  • if (!dryrun) {
    • 가짜 동작(사이즈만 알아올 목적)이 아닌 경우
  • if ((strcmp(pname, “phandle”) == 0) || (strcmp(pname, “linux,phandle”) == 0)) { if (np->phandle == 0) np->phandle = be32_to_cpup(p); }
    • 속성명이 “phandle” 또는 “linux,phandle”인 경우 np->phandle(노드의 phandle)이 처음 설정될 때 속성 데이터 p를 대입한다.
  • if (strcmp(pname, “ibm,phandle”) == 0) np->phandle = be32_to_cpup(p);
    • 속성명이 “ibm.phandle”인 경우 np->phandle(노드의 phandle)에 속성 데이터 p를 대입한다.
  •  pp->name = (char *)pname; pp->length = sz; pp->value = (__be32 *)p;
    • 구성할 property 구조체에 name에는 속성명을 가리키고, length에는 sz을 대입하고, value에는 속성 데이터를 대입한다.
  • *prev_pp = pp; prev_pp = &pp->next;
    • 노드의 첫 속성을 새로 만들어진 속성 구조체를 가리키게 하고 prep_pp에 &pp->next를 대입하여 이후에 추가되는 속성이 계속 다음에 추가될 수 있도록 한다.

아래 그림은 cpu@0 노드에 속한 속성들이 연결된 모습을 보여준다.

unflatten_dt_node-4a

 

        /* with version 0x10 we may not have the name property, recreate
         * it here from the unit name if absent
         */
        if (!has_name) {
                const 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 (!dryrun) {
                        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 -> %s\n", pathp,
                                (char *)pp->value);
                }
        }
        if (!dryrun) {
                *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>";
        }

        old_depth = depth;
        *poffset = fdt_next_node(blob, *poffset, &depth);
        if (depth < 0)
                depth = 0;
        while (*poffset > 0 && depth > old_depth)
                mem = unflatten_dt_node(blob, mem, poffset, np, NULL,
                                        fpsize, dryrun);

        if (*poffset < 0 && *poffset != -FDT_ERR_NOTFOUND)
                pr_err("unflatten: error %d processing FDT\n", *poffset);

name property가 없는 경우 새롭게 구성한다.

  • property 구조체 + 속성 값 길이(주소를 제외한 compact 노드명+1) 만큼의 영역을 사용한다
  • pp->name이 “name” 문자열을 가리키게한다.
  • pp->length에 속성 값 길이를 대입한다.
  • pp->value에 property 구조체 다음에 위치한 속성 값을 가리키게 하고 그 위치에 주소를 제외한 compact 노드명을 복사한다.
  • 속성들 중 마지막에 현재 속성을 연결 한다.
    • 기존 속성 pp->next에 현재 속성을 연결한다.

 

  • if (!has_name) {
    • DTB version 0x10은 name 속성이 없다. 따라서 여기서 name 속성을 만든다.
  • while (*p1) {
    • p1이 null이 아닌 동안 루프를 돈다.
  • if ((*p1) == ‘@’) pa = p1;
    • p1이 ‘@’를 만나면 pa에 p1을 대입한다.
  • if ((*p1) == ‘/’) ps = p1 + 1;
    • p1이 ‘/’를 만나면 ps에 p1+1을 대입한다.
  • if (pa < ps) pa = p1;
    • ‘/’ 문자가 ‘@’ 문자 뒤에 있는 경우 즉, 마지막 ‘/’ 문자열 이후에 주소(‘@’ 문자열 없는) 없는 노드명인 경우 pa=p1(루프가 끝이나서 null을 가리킨다)
  • sz = (pa – ps) + 1;
    • 사이즈는 ‘@’ – ‘/’를 뺀 후 1을 더한다.
    • 예) full path 노드명 스타일: pathp=”/abc/a@1000″
      • sz=2 (“a” + 1)
    • 예) compact 노드명 스타일: pathp=”abc@1000″
      • sz=4 (“abc” + 1)
    • 예) compact 노드명 스타일: pathp=”” (루트노드의 경우)
      • sz=1 (“” + 1)
  • pp = unflatten_dt_alloc(&mem, sizeof(struct property) + sz, __alignof__(struct property));
    • 속성 구조체 정보 단위로 속성 구조체 사이즈 + sz (value 값에 대한 문자열 길이+1)만큼 사용할 pp 주소를 얻어내고 mem 주소는 그 만큼 증가한다.

속성명을 “name”으로 하고 length와 value값을 설정한 후 속성을 기존 속성의 뒤에(속성이 없으면 노드밑으로) 추가한다.

  • if (!dryrun) {
    • 가짜 동작(사이즈만 알아올 목적)이 아닌 경우
  • pp->name = “name”; pp->length = sz; pp->value = pp + 1;
    • 속성의 이름을 “name”, 길이도 정하고 값은 속성 구조체 다음 주소를 가리키게 한다.
  • *prev_pp = pp; prev_pp = &pp->next;
    • 속성을 추가한다.
  • memcpy(pp->value, ps, sz – 1);
    • 속성의 value가 가리키는 주소에 ps주소부터 사이즈-1만큼 복사한다.
  • ((char *)pp->value)[sz – 1] = 0;
    • 속성의 value의 마지막값을 null을 저장한다.
  • *prev_pp = NULL;
    • 마지막에 추가된 속성의 next에 null을 대입한다.

만일 노드에 “name” 속성이 없는 경우 노드명에 “<NULL>”을 대입하고 노드에 device_type속성이 없는 경우 노드의 타입명에 “<NULL>”을 대입한다.

  • np->name = of_get_property(np, “name”, NULL);
    • 노드명이 name 속성의 값을 가리키게 한다.
  • np->type = of_get_property(np, “device_type”, NULL);
    • 노드 타입이 device_type 속성 값을 가리키게 한다.
  • if (!np->name) np->name = “<NULL>”;
    • 노드명이 지정되지 않았으면 “<NULL>” 문자열을 가리키게 한다.
  • if (!np->type) np->type = “<NULL>”;
    • 노드 타입이 지정되지 않았으면 “<NULL>” 문자열을 가리키게 한다.

서브 노드에 대한 처리를 하기 위하여 이 함수에 대해 재귀 호출을 한다.

  • old_depth = depth;
    • 로컬 변수 old_depth에 static 변수 depth를 백업한다.
  • if (depth < 0) depth = 0;
    • static 변수 depth가 0보다 작으면 0으로 한다.
  • while (*poffset > 0 && depth > old_depth) mem = unflatten_dt_node(blob, mem, poffset, np, NULL, fpsize, dryrun);
    • poffset가 0보다 크고 static 변수 depth가 로컬 변수 old_depth보다 큰 경우 이 함수를 다시 재귀 호출하여 처리 못한 서브 노드를 구성한다.
  • if (*poffset < 0 && *poffset != -FDT_ERR_NOTFOUND)
    • poffset가 0보다 작으면서 -FDT_ERR_NOTFOUND인 경우가 아니면 DTB 변환 처리에 에러가 있었다고 메시지 출력한다.

다음 그림은 속성명에 name이 없는 경우 마지막에 추가되는 모습을 보여준다.

unflatten_dt_node-5c

 

        /*
         * Reverse the child list. Some drivers assumes node order matches .dts
         * node order
         */
        if (!dryrun && np->child) {
                struct device_node *child = np->child;
                np->child = NULL;
                while (child) {
                        struct device_node *next = child->sibling;
                        child->sibling = np->child;
                        np->child = child;
                        child = next;
                }
        }

        if (nodepp)
                *nodepp = np;

        return mem;
}

child 노드를 expanded format으로 등록할 때 가장 마지막에 등록한 노드가 가장 앞에 등록을 하였기 때문에 순서가 바뀌어 있다. 따라서 노드를 DTB 순서대로 만들기 위해 각 child 노드를 reverse 한다.

  • if (!dryrun && np->child) {
    • 실제 동작인 경우이면서 노드에 child가 있는 경우
  • struct device_node *child = np->child;
    • child에 노드의 첫 번째 child를 저장한다.
  • np->child = NULL;
    • 노드의 child에 일단 null을 넣고
  • while (child) { struct device_node *next = child->sibling; child->sibling = np->child; np->child = child; child = next; }
    • child노드를 sibling하며 연결을 바꾸고 노드의 child를 현재 처리중인 노드에 연결한다.

child 노드가 있는 경우 DTB 순서대로 만들기 위해 각 child 노드를 reverse 한다.

unflatten_dt_node-1c

unflatten_dt_alloc()

drivers/of/fdt.c

static void *unflatten_dt_alloc(void **mem, unsigned long size,
                                       unsigned long align)
{
        void *res;

        *mem = PTR_ALIGN(*mem, align);
        res = *mem;
        *mem += size;

        return res; 
}

mem 값을 align 단위로 round up 하고 리턴하며 입출력 인수 mem 값은 size만큼 증가시킨다.

 

early_init_dt_alloc_memory_arch()

drivers/of/fdt.c

/*
 * called from unflatten_device_tree() to bootstrap devicetree itself
 * Architectures can override this definition if memblock isn't used
 */
void * __init __weak early_init_dt_alloc_memory_arch(u64 size, u64 align)
{
        return __va(memblock_alloc(size, align));
}
  • align 단위로 size 만큼의 공간을 memblock으로 부터 할당 받고 그 가상 주소를 리턴한다.

 

of_alias_scan()

drivers/of/base.c

/**
 * of_alias_scan - Scan all properties of the 'aliases' node
 *
 * The function scans all the properties of the 'aliases' node and populates
 * the global lookup table with the properties.  It returns the
 * number of alias properties found, or an error code in case of failure.
 *
 * @dt_alloc:   An allocator that provides a virtual address to memory
 *              for storing the resulting tree
 */
void of_alias_scan(void * (*dt_alloc)(u64 size, u64 align))
{
        struct property *pp;

        of_aliases = of_find_node_by_path("/aliases");
        of_chosen = of_find_node_by_path("/chosen");
        if (of_chosen == NULL)
                of_chosen = of_find_node_by_path("/chosen@0");

        if (of_chosen) {
                /* linux,stdout-path and /aliases/stdout are for legacy compatibility */
                const char *name = of_get_property(of_chosen, "stdout-path", NULL);
                if (!name)
                        name = of_get_property(of_chosen, "linux,stdout-path", NULL);
                if (IS_ENABLED(CONFIG_PPC) && !name)
                        name = of_get_property(of_aliases, "stdout", NULL);
                if (name)
                        of_stdout = of_find_node_opts_by_path(name, &of_stdout_options);
        }

        if (!of_aliases)
                return;

        for_each_property_of_node(of_aliases, pp) {
                const char *start = pp->name;
                const char *end = start + strlen(start);
                struct device_node *np;
                struct alias_prop *ap;
                int id, len;

                /* Skip those we do not want to proceed */
                if (!strcmp(pp->name, "name") ||
                    !strcmp(pp->name, "phandle") ||
                    !strcmp(pp->name, "linux,phandle"))
                        continue;

                np = of_find_node_by_path(pp->value);
                if (!np)
                        continue;

                /* walk the alias backwards to extract the id and work out
                 * the 'stem' string */
                while (isdigit(*(end-1)) && end > start)
                        end--;
                len = end - start;

                if (kstrtoint(end, 10, &id) < 0)
                        continue;

                /* Allocate an alias_prop with enough space for the stem */
                ap = dt_alloc(sizeof(*ap) + len + 1, 4);
                if (!ap)
                        continue;
                memset(ap, 0, sizeof(*ap) + len + 1);
                ap->alias = start;
                of_alias_add(ap, np, id, start, len);
        }
}

“/chosen” 노드를 검색하여 전역 of_stdout에 “stdout-path”에 연결된 노드를 찾아 대입한다. 그리고 “/aliases” 노드의 속성 중 “name” 및 “phandle”를 찾아 aliases_prop 구조체로 구성하여 전역 aliases_lookup 리스트에 추가한다.

  • of_aliases = of_find_node_by_path(“/aliases”);
    • “/aliases” 노드를 찾아온다.
  • of_chosen = of_find_node_by_path(“/chosen”);
    • “/chosen” 노드를 찾아온다.
  • if (of_chosen == NULL) of_chosen = of_find_node_by_path(“/chosen@0”);
    • of_chosen 노드가 발견되지 않으면 “/chosen@0″으로 다시 한 번 검색한다.
  • if (of_chosen) {
    • “/chosen” 노드가 발견된 경우
  • const char *name = of_get_property(of_chosen, “stdout-path”, NULL);
    • of_chosen 노드에 있는 속성들에서 “stdout-path” 속성명으로 검색하여 찾은 속성 value 값을 name에 저장한다.
    • 검색 결과가 없으면 레거시 호환을 위해 “linux,stdout-path” 및 “stdout” 속성명으로도 검색한다.
  • if (name) of_stdout = of_find_node_opts_by_path(name, &of_stdout_options);
    • name(of_chosen 노드에서 검색한 “stdout-path” 속성의 value 값)으로 노드를 검색하고 전역 of_stdout_options에는 name 문자열에 옵션(‘:’ 문자로 시작하는 문자열) 값이 있는 경우 저장한다.
  • if (!of_aliases) return;
    • 등록된 aliases가 없는 경우 함수를 빠져나간다.
  • for_each_property_of_node(of_aliases, pp) {
    • of_aliases에 속한 모든 속성에 대해 루프를 돈다.
  • if (!strcmp(pp->name, “name”) || !strcmp(pp->name, “phandle”) || !strcmp(pp->name, “linux,phandle”)) continue;
    • 속성명이 “name”, “phandle”, “linux,phandle”인 경우 skip 한다.
  • np = of_find_node_by_path(pp->value);
    • 속성의 value 값으로 노드를 검색한다.
  • if (!np) continue;
    • 속성의 value 값에 대응하는 노드가 발견되지 않으면 skip 한다.
  • while (isdigit(*(end-1)) && end > start) end–;
    • 속성 값에 있는 노드명이 숫자가 있는 경우 숫자가 시작되는 위치를 end에 대입한다.
    • 예) “/abc/def@1000”
      • end=’1′ 문자를 가리킴
  • len = end – start;
    • len에 노드명의 마지막에 있는 주소를 제외한 ‘@’ 문자까지의 노드명의 길이가 담긴다.
    • 예) “/abc/def@1000” -> len=9
    • 예) “/abc” -> len=4
  • if (kstrtoint(end, 10, &id) < 0)  continue;
    • 주소에 대한 문자열을 10진수로 변환하여 id 변수에 저장을 하는데 에러인 경우 skip 한다.
  • ap = dt_alloc(sizeof(*ap) + len + 1, 4);
    • 4바이트 단위의 alias_prop 구조체 사이즈 + len + 1 크기 만큼 memblock을 할당한다.
  • if (!ap) continue;
    • 할당이 실패하면 skip 한다.
  • memset(ap, 0, sizeof(*ap) + len + 1);
    • 할당받은 메모리를 0으로 초기화 한다.
  • ap->alias = start;
    • alias는 속성명을 가리키게 한다.
    • /alias 노드의 각 속성명은 ‘/’ 문자로 시작하지 않는다.
      • 예) name=”uart0″
  • of_alias_add(ap, np, id, start, len);
    • np, id, stem 값을 저장하고 전역 aliases_lookup 리스트에 추가한다.
    • stem 문자열은 속성명에서 주소 부분을 제외하였다.
      • 예) 속성 데이터=”/soc/uart@7e201000″
        • stem=”/soc/uart@”

아래 그림은 DTB를 unflat하여 Expanded Format으로 바꾼 후 6 개의 전역 변수와 연결되어 있는 모습을 보여준다.

of_alias_scan-1d

  • of_stdout
    • /soc/uart@7e201000 노드를 가리킨다.
    • /chosen 노드의 stdout-path가 가리키는 노드가 출력 디바이스로 사용된다.
  •  of_aliases
    • /aliases 노드를 가리킨다.
  •  aliases_lookup
    • /aliases 노드에 담겨있는 모든 속성들을 alias_prop 구조체 형태로 변환한 후 그 들이 연결되어 있다.
  •  of_root
    • 루트 노드를 가리킨다.
  •  of_chosen
    • /chosen 노드를 가리킨다.
  •  of_stdout_option
    • 출력 노드명에 사용된 옵션(‘:’문자로 시작하는) 문자열이 저장된다.

 

구조체

alias_prop 구조체

drivers/of/of_private.h

/**             
 * struct alias_prop - Alias property in 'aliases' node
 * @link:       List node to link the structure in aliases_lookup list
 * @alias:      Alias property name
 * @np:         Pointer to device_node that the alias stands for
 * @id:         Index value from end of alias name
 * @stem:       Alias string without the index
 *                  
 * The structure represents one alias property of 'aliases' node as
 * an entry in aliases_lookup list.
 */             
struct alias_prop {
        struct list_head link;
        const char *alias;
        struct device_node *np;
        int id;
        char stem[0]; 
};
  • link
    • 링크드 리스트
  • alias
    • alias 속성명
      • 예) “chosen”, “uart0”
  • np
    • 노드(device_node)를 가리킨다.
  • id
    • 노드의 메모리 또는 포트가 사용하는 주소
      • 예) 노드명이 serial@12000 인 경우 id=12000
  • stem
    • index(id)를 제외한 full path 노드명
      • 예) “/soc/uart@”

 

각 구조체의 Memblock 할당 시 사이즈

  • device_node
    • 생성할 때 마다 full path 노드명 공간이 추가 할당된다.
    • name, type, data 등은 기존 DTB에 있는 문자열이나 값을 가리킨다.
  • property
    • 생성할 때 마다 property 사이즈만큼 공간이 할당된다. 그러나 DTB에 없는 name 속성을 추가 생성해야 하는 경우에는 property 속성 이외에도 주소제외 노드명 공간을 추가 할당한다.
  • alias_prop
    • 생성할 때 마다 주소제외 노드명 공간이 추가 할당되고 이 공간은 stem 문자열이 사용하는 공간이다.

unflatten_device_tree-2

 

참고

 

 

 

답글 남기기

이메일은 공개되지 않습니다. 필수 입력창은 * 로 표시되어 있습니다.