device tree

unflatten_device_tree -> __unflatten_device_tree 분석

jsh91 2023. 5. 17. 10:58

device tree 파일을 자료구조에 넣는 함수이다

 

 

/**
 * 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, NULL, &of_root,
				early_init_dt_alloc_memory_arch, false);

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

	unittest_unflatten_overlay_base();
}

* 3 devicd tree를 파싱하여 자료구조에 넣은 후 of_root와 연결한다

* 6 device tree에서 alias로 작성된 내용을 자료구조에 넣는다

* 8 device tree에서 overlay 관련된 내용을 처리한다

 

 

/**
 * __unflatten_device_tree - create tree of device_nodes from flat blob
 * @blob: The blob to expand
 * @dad: Parent device node
 * @mynodes: The device_node tree created by the call
 * @dt_alloc: An allocator that provides a virtual address to memory
 * for the resulting tree
 * @detached: if true set OF_DETACHED on @mynodes
 *
 * 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.
 *
 * Return: NULL on failure or the memory chunk containing the unflattened
 * device tree on success.
 */

 

 

void *__unflatten_device_tree(const void *blob,
			      struct device_node *dad,
			      struct device_node **mynodes,
			      void *(*dt_alloc)(u64 size, u64 align),
			      bool detached)
{
	int size;
	void *mem;
	int ret;

	if (mynodes)
		*mynodes = NULL;

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

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

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

	/* First pass, scan for size */
	size = unflatten_dt_nodes(blob, NULL, dad, NULL);
	if (size <= 0)
		return NULL;

	size = ALIGN(size, 4);
	pr_debug("  size is %d, allocating...\n", size);

	/* Allocate memory for the expanded device tree */
	mem = dt_alloc(size + 4, __alignof__(struct device_node));
	if (!mem)
		return NULL;

	memset(mem, 0, size);

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

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

	/* Second pass, do actual unflattening */
	ret = unflatten_dt_nodes(blob, mem, dad, mynodes);

	if (be32_to_cpup(mem + size) != 0xdeadbeef)
		pr_warn("End of tree marker overwritten: %08x\n",
			be32_to_cpup(mem + size));

	if (ret <= 0)
		return NULL;

	if (detached && mynodes && *mynodes) {
		of_node_set_flag(*mynodes, OF_DETACHED);
		pr_debug("unflattened tree is detached\n");
	}

	pr_debug(" <- unflatten_device_tree()\n");
	return mem;
}

* 26~29 device tree 파일이 정상적인지 확인한다

* 32 device tree의 size를 알아낸다

* 40 memblock에서 device tree size 만큼 메모리를 할당한다

* 44 할당받은 영역을 0으로 초기화한다

* 46 memblock으로 부터 할당맏은 메모리 마지막에 magic number를 write 한다

* 51 device tree의 값을 자료구조에 삽입한다

* 52 삽입한 후 magic number가 깨졌는지 확인한다

* 66 memblock에서 할당받은 메모리를 return한다

 

위 함수에서 unflatten_dt_nodes 함수가 2번 호출된다

unflatten_dt_nodes의 2번째 인자 mem으로 인해 device tree의 size만 가져올지 자료구조에 삽입할지를 결정한다.

 

 

/**
 * unflatten_dt_nodes - 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
 * @dad: Parent struct device_node
 * @nodepp: The device_node tree created by the call
 *
 * Return: The size of unflattened device tree or error code
 */

 

static int unflatten_dt_nodes(const void *blob,
			      void *mem,
			      struct device_node *dad,
			      struct device_node **nodepp)
{
	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;
	int ret;

	if (nodepp)
		*nodepp = NULL;

	/*
	 * We're unflattening device sub-tree if @dad is valid. There are
	 * possibly multiple nodes in the first level of depth. We need
	 * set @depth to 1 to make fdt_next_node() happy as it bails
	 * immediately when negative @depth is found. Otherwise, the device
	 * nodes except the first one won't be unflattened successfully.
	 */
	if (dad)
		depth = initial_depth = 1;

	root = dad;
	nps[depth] = dad;

	for (offset = 0;
	     offset >= 0 && depth >= initial_depth;
	     offset = fdt_next_node(blob, offset, &depth)) {
		if (WARN_ON_ONCE(depth >= FDT_MAX_DEPTH - 1))
			continue;

		if (!IS_ENABLED(CONFIG_OF_KOBJ) &&
		    !of_fdt_device_is_available(blob, offset))
			continue;

		ret = populate_node(blob, offset, &mem, nps[depth],
				   &nps[depth+1], dryrun);
		if (ret < 0)
			return ret;

		if (!dryrun && nodepp && !*nodepp)
			*nodepp = nps[depth+1];
		if (!dryrun && !root)
			root = nps[depth+1];
	}

	if (offset < 0 && offset != -FDT_ERR_NOTFOUND) {
		pr_err("Error %d processing FDT\n", offset);
		return -EINVAL;
	}

	/*
	 * Reverse the child list. Some drivers assumes node order matches .dts
	 * node order
	 */
	if (!dryrun)
		reverse_nodes(root);

	return mem - base;
}

* 24 dad 는 __unflatten_device_tree() 의 2번재 인자인데 항상 NULL이므로 depth, initial_depth는 1이다

* 30~49 device tree node를 계속 순회 하면서 offset을 가져온다

* 33 depth가 FDT_MAX_DEPTH(64)-1 보다 깊다면 자료구조 삽입하지 않는다

* 37 device tree node의 'state' 문자를 알아와 "ok" or "okay"로 작성 되었을때 true를 반환하여 자료구조에 삽입 할지 결정한다

* 40 node를 자료구조에 삽입한다

* 45 nodepp가 root node 다음 depth를 가리킨게 한다. (root node populate_node가 끝나고 한번만 호출한다)

nodepp -> of_root(전역변수)

device_node nps[] 배열은  

* 47 dryrun이 아니면서 root가 NULL이라면 다음 depth를 가리키도록 한다. (root node populate_node가 끝나고 한번만 호출한다)

* 61 node를 반전 시킨다

dryrun은 호출 __unflatten_device_tree() 의 2번재 호출 시 '1'로 set 된다

 

     Parent
    /  |  \
  A    B   C
 / \  / \  |
D   E F  G H

함수 호출 후

     Parent
    /  |  \
  C    B   A
  |   / \ / \
  H  G   F E D

 

 

static int 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;
	int len;

	pathp = fdt_get_name(blob, offset, &len);
	if (!pathp) {
		*pnp = NULL;
		return len;
	}

	len++;

	np = unflatten_dt_alloc(mem, sizeof(struct device_node) + len,
				__alignof__(struct device_node));
	if (!dryrun) {
		char *fn;
		of_node_init(np);
		np->full_name = fn = ((char *)np) + sizeof(*np);

		memcpy(fn, pathp, len);

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

* 12 device tree node의 name을 가져온다

* 13~16 name이 없다면 종료한다

* 20 나중에 device tree size만큼 메모리 할당할때 사용 할 size(mem)를 위해 device_node + name size 만큼 증가시킨다

* 22~34 __unflatten_device_tree()  2번째 호출 시 node 생성을 위한 작업

* 24 node init한다

* 25 node의 full_name을 복사한다

* 29~33 node를 부모와 연결한다

* 36 property node에 대한 정보를 parsing 한다

* 37~41 name을 가져오면 nodename을 가져온다(populate_properties 함수 마지막에 마지막 struct property 뒤에 nodename을 작성 하였다)  , name이 없다면 NULL로 저장한다

* 43 새로 생성한 node를 pnp에 넣는다.

 

line 35 코드를 그림을 그리면 아래와 같다

struct device_node 뒤에 full_name을 write후 device_node->full_name에서 가리키게 한다.

 

 

static void populate_properties(const void *blob,
				int offset,
				void **mem,
				struct device_node *np,
				const char *nodename,
				bool dryrun)
{
	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);
		if (!val) {
			pr_warn("Cannot locate property at 0x%x\n", cur);
			continue;
		}

		if (!pname) {
			pr_warn("Cannot find property name at 0x%x\n", cur);
			continue;
		}

		if (!strcmp(pname, "name"))
			has_name = true;

		pp = unflatten_dt_alloc(mem, sizeof(struct property),
					__alignof__(struct property));
		if (dryrun)
			continue;

		/* 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") ||
		    !strcmp(pname, "linux,phandle")) {
			if (!np->phandle)
				np->phandle = be32_to_cpup(val);
		}

		/* And we process the "ibm,phandle" property
		 * used in pSeries dynamic device tree
		 * stuff
		 */
		if (!strcmp(pname, "ibm,phandle"))
			np->phandle = be32_to_cpup(val);

		pp->name   = (char *)pname;
		pp->length = sz;
		pp->value  = (__be32 *)val;
		*pprev     = pp;
		pprev      = &pp->next;
	}

* 13~15 node의 첫번째 property의 offset을 찾고 다음 property offset을 계속 순회한다

* 20 property 의 인자값에 값을 채우고 property data를 return한다

cur : property 위치

pname : property name

sz : property size

 

예를들면

compatible=arm,cortex-a53

cur : compatible 의 시작위치('c')

pname : arm,cortex-a53

sz : 'arm,cortex-a53' size

 

* 31 property를 모두 순회하고 맨 마지막에는 pname이 'name'을 return한다.

* 34 메모리 할당할 size를 만드는 mem변수에 struct property 만큼 더한다

* 36 처음 unflatten_dt_nodes 함수 할 때는 size만 확인하여 더이상 실행하지 않고 다음 property를 순회한다

* 45 pname이 phandle or linux,phandle 일 경우 값을 device_node에 저장한다

phandle = <0x00000035>;

 

* 55 pname이 ibm,phandle일 경우 값을 device_node에 저장한다

* 58~60 property에 name, size, val값을 저장한다

* 61~62 생성한 property를 device_node에 연결한다

마지막 property는 마지막 next에 연결된다

 

	/* With version 0x10 we may not have the name property,
	 * recreate it here from the unit name if absent
	 */
	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;
			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);
		}
	}
}

* 4 property에 name이 없으면 실행한다

* 5 node의 name(nodenoae)을 가져온다

* 8~14 nodename 문자열을 하나씩 확인하면서 '@' 와 '/'+1 의 위치를 알아낸다

pa : @ 위치

ps : /+1 위치

* 16 nodename에 '@'가 '/'보다 앞에 있다면

예를 들면 아래처럼 된다

ps     pa          p
apple@2000'NULL' -> '@' 앞글자 size를 알아낸다 : apple

       ps           p,pa     , pa=NULL -> pa=p, because if(pa<ps) pa=p;
apple/banana'NULL' -> '/' 뒤에 문자 size를 안아낸다 : banana 

        ps         pa         p
apple/banana@2000'NULL' -> '/' 와 '@' 사이에 문자 size를 알아낸다 : banana

 

* 19 struct  property + name 길이만큼 할당 한다

* 21~30 할당받은 영역에 name, len(name size)값을 넣는다, value에 struct property 끝 위치를 가리키고 거기에 nodename에서 구한 문자열을 넣는다.

'populate_node' 함수에서 마지막에 그려놓은 그림과 유사하게 nodename을 저장한다

 

참고자료

http://jake.dothome.co.kr/unflatten_device_tree/