DTB (of API)

 

of_find_node_by_path()

include/linux/of.h

static inline struct device_node *of_find_node_by_path(const char *path)
{
        return of_find_node_opts_by_path(path, NULL);
}

full path 노드명과 매치되는 노드를 찾는다.

  • alias명으로 검색하는 경우에는 ‘/’ 문자로 시작하지 않아도 된다.

 

of_find_node_opts_by_path()

drivers/of/base.c

/**
 *      of_find_node_opts_by_path - Find a node matching a full OF path
 *      @path: Either the full path to match, or if the path does not
 *             start with '/', the name of a property of the /aliases
 *             node (an alias).  In the case of an alias, the node
 *             matching the alias' value will be returned.
 *      @opts: Address of a pointer into which to store the start of
 *             an options string appended to the end of the path with
 *             a ':' separator.
 *
 *      Valid paths:
 *              /foo/bar        Full path
 *              foo             Valid alias
 *              foo/bar         Valid alias + relative path
 *      
 *      Returns a node pointer with refcount incremented, use
 *      of_node_put() on it when done.
 */     
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 = strchr(path, ':');

        if (opts)
                *opts = separator ? separator + 1 : NULL; 
                                             
        if (strcmp(path, "/") == 0)
                return of_node_get(of_root);

        /* The path could begin with an alias */
        if (*path != '/') {
                int len;
                const char *p = separator;

                if (!p)
                        p = strchrnul(path, '/');
                len = p - path;

                /* of_aliases must not be NULL */
                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;
        }

full path 또는 alias 명과 매치되는 노드를 찾는다. 옵션(‘:’  다음의 문자열)  문자열이 있는 경우 출력 인수가 주어진 경우 저장한다.

  • const char *separator = strchr(path, ‘:’);
    • path에서 ‘:’ 문자 위치를 찾는다.
  • if (opts) *opts = separator ? separator + 1 : NULL;
    • ‘:’문자가 있는 경우 opts에 ‘:’ 다음 문자를 가리키고 없는 경우 null을 대입한다.
  • if (strcmp(path, “/”) == 0) return of_node_get(of_root);
    • 루트 노드인 경우 전역 변수 of_root의 노드를 리턴한다.

노드명이 alias인 경우 전역 of_alias에 추가된 모든 속성의 값과 같은 경우 이 속성값으로 노드를 찾아 온다.

  • if (*path != ‘/’) {
    • 노드명이 alias 타입으로 시작한 경우
  • if (!p) p = strchrnul(path, ‘/’);
    • p가 지정되지 않은 경우 p에 ‘/’ 문자열 위치를 대입한다.
  • len = p – path;
    • ‘/’ 문자열 위치에서 속성 첫 문자열 위치를 뺀 수로 compact alias 명의 길이가 된다.
  • for_each_property_of_node(of_aliases, pp) {
    • of_aliases 노드내의 모든 속성 만큼 루프를 돈다.
  • if (strlen(pp->name) == len && !strncmp(pp->name, path, len)) { np = of_find_node_by_path(pp->value); break; }
    • 속성명 길이와 len(alias명 길이)이 같고 그 길이만큼 속성명과 alias 명을 비교하여 같은 경우 속성 값으로 노드를 찾아 루프를 탈출한다.
  • if (!np) return NULL;
    • 찾지 못한 경우 null을 리턴한다.

 

        /* Step down the tree matching path components */
        raw_spin_lock_irqsave(&devtree_lock, flags);
        if (!np)
                np = of_node_get(of_root);
        while (np && *path == '/') {
                path++; /* Increment past '/' delimiter */
                np = __of_find_node_by_path(np, path);
                path = strchrnul(path, '/');
                if (separator && separator < path)
                        break;
        }
        raw_spin_unlock_irqrestore(&devtree_lock, flags);
        return np;
}
EXPORT_SYMBOL(of_find_node_opts_by_path);
  • if (!np) np = of_node_get(of_root);
    • 발견된 노드 정보가 없으면 루트 노드 정보를 가져온다.
  • while (np && *path == ‘/’) {
    • 노드들 중 alias명이 아닌 full path 노드명인 경우만 루프를 돈다.
  • np = __of_find_node_by_path(np, path);
    • 앞에 있는 ‘/’를 제외한 full path 노드명으로 노드를 찾아온다.
  • path = strchrnul(path, ‘/’);
    • full path 노드명에서 두 번째 ‘/’ 문자를 찾는다.
  • if (separator && separator < path) break;
    • 두 번째 ‘/’ 문자열의 위치가 separator보다 큰 경우 루프를 탈출하고 리턴한다.
  • 만일 못찾은 경우null을 리턴한다.

 

__of_find_node_path()

drivers/of/base.c

static struct device_node *__of_find_node_by_path(struct device_node *parent,
                                                const char *path)
{
        struct device_node *child;
        int len;

        len = strcspn(path, "/:");
        if (!len)
                return NULL;

        __for_each_child_of_node(parent, child) {
                const char *name = strrchr(child->full_name, '/');
                if (WARN(!name, "malformed device_node %s\n", child->full_name))
                        continue;
                name++;
                if (strncmp(path, name, len) == 0 && (strlen(name) == len))
                        return child;
        }
        return NULL;
}

child 노드의 compact 노드명과 요청 문자열을 비교하여 같은 경우 해당 child 노드를 리턴하고 찾지 못한 경우 null을 리턴한다. 요청 문자열은 ‘/’ 문자로 시작하면 안된다.

  • len = strcspn(path, “/:”);
    • path 문자열에 ‘/’ 또는 ‘:’ 문자가 발견되기 전 까지의 문자열 수를 len에 대입한다.
  • if (!len) return NULL;
    • 문자열의 길이가 0인 경우 null을 리턴한다.
  • __for_each_child_of_node(parent, child) {
    • parent 노드에 있는 child 노드에 대해 루프를 돈다.
  • const char *name = strrchr(child->full_name, ‘/’);
    • 서브 노드 full name에서 가장 뒤에 있는 ‘/’ 문자를 찾는다.
  • name++;
    • ‘/’ 문자를 skip 한다.
  • if (strncmp(path, name, len) == 0 && (strlen(name) == len)) return child;
    • 서브 노드의 compact 노드명과 path 문자열과 비교하여 같은 경우 해당 서브 노드를 리턴한다.

아래 그림은 지정한 부모 노드 바로 한 단계 밑에 위치한 서브 노드들의 compact 노드명을 대상으로 특정 노드명을 찾는 것을 보여준다.

__of_find_node_by_path-1

__for_each_child_of_node()

drivers/of/base.c

#define __for_each_child_of_node(parent, child) \
        for (child = __of_get_next_child(parent, NULL); child != NULL; \
             child = __of_get_next_child(parent, child))

parent 노드의 child 노드만큼 루프를 돈다.

 

아래 그림은 parent 노드 아래 3개의 child 노드가 있고 1번 부터 3번 까지 순서대로 루프를 도는 것을 보여준다.

__for_each_child_of_node-1

 

__of_get_next_child()

drivers/of/base.c

static struct device_node *__of_get_next_child(const struct device_node *node,
                                                struct device_node *prev)
{
        struct device_node *next;

        if (!node)
                return NULL;

        next = prev ? prev->sibling : node->child;
        for (; next; next = next->sibling)
                if (of_node_get(next))
                        break;
        of_node_put(prev);
        return next;
}

node의 child 노드를 찾는다. prev를 null로 하는 경우 첫 child 노드를 찾고 prev에 child 노드인 경우 다음 노드를 찾는다.

 

아래 그림은 __of_get_next_child() 함수를  3번 호출할 때의 상황을 나타내었다. prev값에 따라서 각각에 대응하는 next 노드를 알아오는 것을 보여준다.

__of_get_next_child-1

 

of_node_get()

drivers/of/dynamic.c

/**
 * of_node_get() - Increment refcount of a node
 * @node:       Node to inc refcount, NULL is supported to simplify writing of
 *              callers
 *
 * Returns node.
 */
struct device_node *of_node_get(struct device_node *node)
{
        if (node)
                kobject_get(&node->kobj);
        return node;
}
EXPORT_SYMBOL(of_node_get);
  • 해당 노드에 대한 참조 카운터를 증가시킨다.

 

of_node_put()

drivers/of/dynamic.c

/**
 * of_node_put() - Decrement refcount of a node
 * @node:       Node to dec refcount, NULL is supported to simplify writing of
 *              callers
 */
void of_node_put(struct device_node *node)
{
        if (node)
                kobject_put(&node->kobj);
}
EXPORT_SYMBOL(of_node_put);
  • 해당 노드에 대한 참조 카운터를 감소시킨다

 

for_each_property_of_node()

include/linux/of.h

#define for_each_property_of_node(dn, pp) \
        for (pp = dn->properties; pp != NULL; pp = pp->next)
  • dn 노드에 속한 모든 속성에 대해 루프를 돈다.

다음 그림은 지정된 노드에 포함된 전체 속성에 대한 루프를 도는 것을 보여준다.

for_each_property_of_node-1

 

of_get_property()

drivers/of/base.c

/*
 * Find a property with a given name for a given node
 * and return the value.
 */
const void *of_get_property(const struct device_node *np, const char *name,
                            int *lenp)
{
        struct property *pp = of_find_property(np, name, lenp);

        return pp ? pp->value : NULL;
}
EXPORT_SYMBOL(of_get_property);
  • devtree_lock으로 보호하면서 지정된 노드의 전체 속성들 중 요청 속성명을 찾아 발견되는 경우 해당 속성 길이를 lenp에 저장하고 해당 속성의 value 값을 리턴한다.

 

of_find_property()

drivers/of/base.c

struct property *of_find_property(const struct device_node *np,
                                  const char *name,
                                  int *lenp)
{
        struct property *pp;
        unsigned long flags;

        raw_spin_lock_irqsave(&devtree_lock, flags);
        pp = __of_find_property(np, name, lenp);
        raw_spin_unlock_irqrestore(&devtree_lock, flags);

        return pp;
}
EXPORT_SYMBOL(of_find_property);
  • devtree_lock으로 보호하면서 지정된 노드의 전체 속성들 중 요청 속성명을 찾아 발견되는 경우 속성 길이를 lenp에 저장하고 해당 속성을 리턴한다.

 

__of_find_property()

drivers/of/base.c

static struct property *__of_find_property(const struct device_node *np,
                                           const char *name, int *lenp)
{
        struct property *pp;

        if (!np)
                return NULL;

        for (pp = np->properties; pp; pp = pp->next) {
                if (of_prop_cmp(pp->name, name) == 0) {
                        if (lenp)
                                *lenp = pp->length;
                        break;
                }
        }

        return pp;
}
  • 지정된 노드의 전체 속성들 중 요청 속성명을 찾아 발견되는 경우 속성 길이를 lenp에 저장하고 해당 속성을 리턴한다.

아래 그림은 지정된 노드에 포함된 속성들에서 속성명으로 검색하는 것을 보여준다.

__of_find_property-1

of_prop_cmp()

include/linux/of.h

#define of_prop_cmp(s1, s2)             strcmp((s1), (s2))

 

of_alias_add()

drivers/of/base.c

static void of_alias_add(struct alias_prop *ap, struct device_node *np,
                         int id, const char *stem, int stem_len)
{
        ap->np = np;
        ap->id = id;
        strncpy(ap->stem, stem, stem_len);
        ap->stem[stem_len] = 0;
        list_add_tail(&ap->link, &aliases_lookup);
        pr_debug("adding DT alias:%s: stem=%s id=%i node=%s\n",
                 ap->alias, ap->stem, ap->id, of_node_full_name(np));
}

alias 속성에 np, id, stem을 저장하고 전역 리스트 aliases_lookup에 추가한다.

 

속성 값을 읽어오는 of_property_read_***() 함수들

아래의 함수들은 인수로 요청한 노드에서 인수로 요청한 속성값으로 검색하여 속성 값을 함수명이 의미하는 데이터 사이즈로 읽어온다.

  • of_property_read_string_index()
  • of_property_read_u32_index()
  • of_property_read_bool()
  • of_property_read_u8()
  • of_property_read_u8_array()
  • of_property_read_u16()
  • of_property_read_u16_array()
  • of_property_read_u32()
  • of_property_read_u32_array()
  • of_property_read_s32()
  • of_property_read_u64()
  • of_property_read_u64_array()
  • of_property_read_string()

 

of_property_read_u32()

include/linux/of.h

static inline int of_property_read_u32(const struct device_node *np,
                                       const char *propname,
                                       u32 *out_value)
{               
        return of_property_read_u32_array(np, propname, out_value, 1);
}
  • np 노드에서 요청한 속성명으로 검색하여 해당 속성 값 중 1 개의 32bit unsigned int 값을 읽어 출력 인수 out_value에 저장한다.

 

of_property_read_u32_array()

drivers/of/base.c

/**
 * of_property_read_u32_array - Find and read an array of 32 bit integers
 * from a property.
 *
 * @np:         device node from which the property value is to be read.
 * @propname:   name of the property to be searched.
 * @out_values: pointer to return value, modified only if return value is 0.
 * @sz:         number of array elements to read 
 *
 * Search for a property in a device node and read 32-bit value(s) from
 * it. Returns 0 on success, -EINVAL if the property does not exist,
 * -ENODATA if property does not have a value, and -EOVERFLOW if the
 * property data isn't large enough.
 *
 * The out_values is modified only if a valid u32 value can be decoded.
 */
int of_property_read_u32_array(const struct device_node *np,
                               const char *propname, u32 *out_values,
                               size_t sz) 
{
        const __be32 *val = of_find_property_value_of_size(np, propname,
                                                (sz * sizeof(*out_values)));

        if (IS_ERR(val))
                return PTR_ERR(val);

        while (sz--)
                *out_values++ = be32_to_cpup(val++);
        return 0;
}
EXPORT_SYMBOL_GPL(of_property_read_u32_array);
  • 현재 노드의 요청 속성을 찾아 데이터 갯 수를 초과하는지 체크하여 이상 없는 경우 out_values에 요청한 sz 수 만큼의 32bit unsigned 값을 저장하고 0을 리턴한다. 실패 시 0이 아닌 수가 리턴된다.

 

of_find_property_value_of_size()

drivers/of/base.c

/**
 * of_find_property_value_of_size
 *
 * @np:         device node from which the property value is to be read.
 * @propname:   name of the property to be searched.
 * @len:        requested length of property value
 *
 * Search for a property in a device node and valid the requested size.
 * Returns the property value on success, -EINVAL if the property does not
 *  exist, -ENODATA if property does not have a value, and -EOVERFLOW if the
 * property data isn't large enough.
 *
 */
static void *of_find_property_value_of_size(const struct device_node *np,
                        const char *propname, u32 len)
{
        struct property *prop = of_find_property(np, propname, NULL);

        if (!prop)
                return ERR_PTR(-EINVAL);
        if (!prop->value)
                return ERR_PTR(-ENODATA);
        if (len > prop->length)
                return ERR_PTR(-EOVERFLOW);

        return prop->value;
}
  • 속성 값을 알아오는데 다음과 같은 경우 에러를 리턴한다.
    • 속성을 발견할 수 없을 때
    • 속성 값을 가리키는 값이 null일 때
    • 속성 값 길이보다 요청 길이가 더 클 때
    • 예) 32bit – reg = <0x0, 0x0>의 경우 prop->length=2 이므로 u32 형으로 sz=2 번까지 읽을 수 있다.

 

매치되는 노드들을 검색

of_find_matching_node_and_match()

drivers/of/base.c

/**
 *      of_find_matching_node_and_match - Find a node based on an of_device_id
 *                                        match table.
 *      @from:          The node to start searching from or NULL, the node
 *                      you pass will not be searched, only the next one
 *                      will; typically, you pass what the previous call
 *                      returned. of_node_put() will be called on it
 *      @matches:       array of of device match structures to search in
 *      @match          Updated to point at the matches entry which matched
 *
 *      Returns a node pointer with refcount incremented, use
 *      of_node_put() on it when done.
 */
struct device_node *of_find_matching_node_and_match(struct device_node *from,
                                        const struct of_device_id *matches,
                                        const struct of_device_id **match)
{
        struct device_node *np; 
        const struct of_device_id *m;
        unsigned long flags;

        if (match)
                *match = NULL;

        raw_spin_lock_irqsave(&devtree_lock, flags);
        for_each_of_allnodes_from(from, np) {
                m = __of_match_node(matches, np); 
                if (m && of_node_get(np)) {
                        if (match)
                                *match = m; 
                        break;
                }    
        }    
        of_node_put(from);
        raw_spin_unlock_irqrestore(&devtree_lock, flags);
        return np;
}
EXPORT_SYMBOL(of_find_matching_node_and_match);
  • raw_spin_lock_irqsave(&devtree_lock, flags);
    • devtree_lock spin lock을 사용하여 device tree를 보호하게 한다.
  • for_each_of_allnodes_from(from, np) {
    • 검색을 시작할 노드부터 끝 노드까지 루프를 돈다.
    • 검색 결과에 검색 시작 노드는 제외한다.
  • m = __of_match_node(matches, np);
    • 가장 적합한 디바이스 매치를 찾는다.
  • if (m && of_node_get(np)) { if (match) *match = m; break; }
    • 노드에 대해 참조 카운터를 증가시키고 출력 인수 match가 지정되어 있으면 찾은 of_device_id 구조체 포인터를 저장하고 루프를 빠져나간다.
  • of_node_put(from);
    • 시작 노드에 대해 참조 카운터를 감소시킨다.
  • raw_spin_unlock_irqrestore(&devtree_lock, flags);
    • 걸었던 spin lock을 release한다.

아래 그림은 psci_of_match[]에 등록된 디바이스를 device tree에서 찾아 of_device_id 구조체 포인터를 match에 저장하고 발견된 노드를 리턴하는 모습을 보여준다.

of_find_matching_node_and_match-1

 

__of_match_node()

drivers/of/base.c

const struct of_device_id *__of_match_node(const struct of_device_id *matches,
                                           const struct device_node *node)
{
        const struct of_device_id *best_match = NULL;
        int score, best_score = 0; 

        if (!matches)
                return NULL;

        for (; matches->name[0] || matches->type[0] || matches->compatible[0]; matches++) {
                score = __of_device_is_compatible(node, matches->compatible,
                                                  matches->type, matches->name);
                if (score > best_score) {
                        best_match = matches;
                        best_score = score;
                }
        }

        return best_match;
}
  • of_device_id 구조체로된 매치 배열 matches에서 현재 노드 정보로 각각에 대해 score를 평가하고 가장 높은 score를 갖은 of_device_id 구조체 포인터를 리턴한다.

 

__of_device_is_compatible()

drivers/of/base.c

/**
 * __of_device_is_compatible() - Check if the node matches given constraints
 * @device: pointer to node
 * @compat: required compatible string, NULL or "" for any match
 * @type: required device_type value, NULL or "" for any match
 * @name: required node name, NULL or "" for any match
 *
 * Checks if the given @compat, @type and @name strings match the
 * properties of the given @device. A constraints can be skipped by
 * passing NULL or an empty string as the constraint.
 *
 * Returns 0 for no match, and a positive integer on match. The return
 * value is a relative score with larger values indicating better
 * matches. The score is weighted for the most specific compatible value
 * to get the highest score. Matching type is next, followed by matching
 * name. Practically speaking, this results in the following priority
 * order for matches:
 *
 * 1. specific compatible && type && name
 * 2. specific compatible && type
 * 3. specific compatible && name
 * 4. specific compatible
 * 5. general compatible && type && name
 * 6. general compatible && type
 * 7. general compatible && name
 * 8. general compatible
 * 9. type && name
 * 10. type
 * 11. name
 */
static int __of_device_is_compatible(const struct device_node *device,
                                     const char *compat, const char *type, const char *name)
{
        struct property *prop;
        const char *cp;
        int index = 0, score = 0;

        /* Compatible match has highest priority */
        if (compat && compat[0]) {
                prop = __of_find_property(device, "compatible", NULL);
                for (cp = of_prop_next_string(prop, NULL); cp;
                     cp = of_prop_next_string(prop, cp), index++) {
                        if (of_compat_cmp(cp, compat, strlen(compat)) == 0) {
                                score = INT_MAX/2 - (index << 2);
                                break;
                        }
                }
                if (!score)
                        return 0;
        }

        /* Matching type is better than matching name */
        if (type && type[0]) {
                if (!device->type || of_node_cmp(type, device->type))
                        return 0;
                score += 2;
        }

        /* Matching name is a bit better than not */
        if (name && name[0]) {
                if (!device->name || of_node_cmp(name, device->name))
                        return 0;
                score++;
        }

        return score;
}

요청한 노드에 대해 compatible 평가 점수를 알아온다.

compat 문자열, type 문자열, name 문자열이 주어진 경우 각각에 매치되지 않으면 실패로 간주하여 0을 리턴한다. 그 외에 compat 문자열이 매치되는 경우 가장 점수가 높고 type 문자열을 매치 시키면 2점이 추가되고 노드명이 매치되면 1점이 추가된다.

  • if (compat && compat[0]) {
    • compat 문자열이 주어졌을 때
  • prop = __of_find_property(device, “compatible”, NULL);
    • 지정된 노드에서 “compatible” 속성을 찾는다.
  • for (cp = of_prop_next_string(prop, NULL); cp; cp = of_prop_next_string(prop, cp), index++) {
    • 루프를 돌며 다음 속성을 알아온다.
  • if (of_compat_cmp(cp, compat, strlen(compat)) == 0) { score = INT_MAX/2 – (index << 2); break; }
    • 대소문자 구분 없이 compat 문자열과 속성 값이 같은 경우 score에 INT_MAX/2 – (index x 4)를 한후 루프를 탈출한다.
  • if (!score) return 0;
    • 루프가 끝날 때까지 score가 0인 경우 실패로 간주하여 0을 리턴한다.
  • if (type && type[0]) {
    • 타입이 주어진 경우
  • if (!device->type || of_node_cmp(type, device->type)) return 0;
    • 대소문자 구분 없이 타입이 매치되지 않으면 실패로 간주하여 0을 리턴한다.
  • score += 2;
    • 타입이 매치된 경우 score에 2를 더한다.
  • if (name && name[0]) {
    • 디바이스명이 지정된 경우
  • if (!device->name || of_node_cmp(name, device->name)) return 0;
    • 대소문자 구분없이 노드명이 매치되지 않으면 실패로 간주하여 0을 리턴한다.
  • 노드명이 매치되면 score에 1을 더한다.

 

 

 

전체 노드 검색

for_each_of_allnodes()

include/linux/of.h

#define for_each_of_allnodes(dn) for_each_of_allnodes_from(NULL, dn)
  • 루트 노드부터 끝까지 루프를 돈다.

for_each_of_allnodes-1

 

for_each_of_allnodes_from()

include/linux/of.h

#define for_each_of_allnodes_from(from, dn) \
        for (dn = __of_find_all_nodes(from); dn; dn = __of_find_all_nodes(dn))

 

__of_find_all_nodes()

drivers/of/base.c

struct device_node *__of_find_all_nodes(struct device_node *prev)
{
        struct device_node *np;
        if (!prev) {
                np = of_root;
        } else if (prev->child) {
                np = prev->child;
        } else {
                /* Walk back up looking for a sibling, or the end of the structure */
                np = prev;
                while (np->parent && !np->sibling)
                        np = np->parent;
                np = np->sibling; /* Might be null at the end of the tree */
        }
        return np;
}
  • if (!prev) { np = of_root;
    • 인수로 받은 prev가 null인 경우 루트 노드부터 시작한다.
  • } else if (prev->child) { np = prev->child;
    • 인수로 받은 prev->child가 존재하는 경우 np를 첫 child 노드로 설정한다.
  • } else { np = prev;
    • child 노드가 없는 경우 np를 prev 노드로 설정한다.
  •  while (np->parent && !np->sibling) np = np->parent;
    • 부모 노드가 존재하고 우측 형재 노드가 없는 경우 부모 노드를 설정한다.
  • np = np->sibling;
    • 우측 형재 노드를 선택한다.
    • 만일 트리의 마지막이라면 null이된다.

아래 그림은 prev 값에 따라 리턴되는 노드를 보여준다.

__of_find_all_nodes-1

__of_find_all_nodes-2

__of_find_all_nodes-3

 

phandle 값을 이용한 파싱

of_parse_phandle_with_args()

drivers/of/base.c

/**
 * of_parse_phandle_with_args() - Find a node pointed by phandle in a list
 * @np:         pointer to a device tree node containing a list
 * @list_name:  property name that contains a list
 * @cells_name: property name that specifies phandles' arguments count
 * @index:      index of a phandle to parse out
 * @out_args:   optional pointer to output arguments structure (will be filled)
 *                               
 * This function is useful to parse lists of phandles and their arguments.
 * Returns 0 on success and fills out_args, on error returns appropriate
 * errno value.
 *      
 * Caller is responsible to call of_node_put() on the returned out_args->np
 * pointer.
 *      
 * Example: 
 *      
 * phandle1: node1 {
 *      #list-cells = <2>;
 * }
 *
 * phandle2: node2 {
 *      #list-cells = <1>;
 * }
 *
 * node3 {
 *      list = <&phandle1 1 2 &phandle2 3>;
 * }
 *
 * To get a device_node of the `node2' node you may call this:
 * of_parse_phandle_with_args(node3, "list", "#list-cells", 1, &args);
 */
int of_parse_phandle_with_args(const struct device_node *np, const char *list_name,
                                const char *cells_name, int index,
                                struct of_phandle_args *out_args)
{
        if (index < 0)
                return -EINVAL;
        return __of_parse_phandle_with_args(np, list_name, cells_name, 0,
                                            index, out_args);
}
EXPORT_SYMBOL(of_parse_phandle_with_args);

요청 노드에서 list_name 속성 값을 읽어 index 값에 해당하는 phandle 노드에서 #으로 시작하는 cells_name 속성 값 수 만큼 phandle 뒤의 인수들을 out_args에 저장한다.

 

__of_parse_phandle_with_args()

drivers/of/base.c

static int __of_parse_phandle_with_args(const struct device_node *np,
                                        const char *list_name,
                                        const char *cells_name,
                                        int cell_count, int index,
                                        struct of_phandle_args *out_args)
{
        const __be32 *list, *list_end;
        int rc = 0, size, cur_index = 0;
        uint32_t count = 0;
        struct device_node *node = NULL;
        phandle phandle;

        /* Retrieve the phandle list property */
        list = of_get_property(np, list_name, &size);
        if (!list)
                return -ENOENT;
        list_end = list + size / sizeof(*list);

        /* Loop over the phandles until all the requested entry is found */
        while (list < list_end) {
                rc = -EINVAL;
                count = 0;

                /*
                 * If phandle is 0, then it is an empty entry with no
                 * arguments.  Skip forward to the next entry.
                 */
                phandle = be32_to_cpup(list++);
                if (phandle) {
                        /*
                         * Find the provider node and parse the #*-cells
                         * property to determine the argument length.
                         *
                         * This is not needed if the cell count is hard-coded
                         * (i.e. cells_name not set, but cell_count is set),
                         * except when we're going to return the found node
                         * below.
                         */
                        if (cells_name || cur_index == index) {
                                node = of_find_node_by_phandle(phandle);
                                if (!node) {
                                        pr_err("%s: could not find phandle\n",
                                                np->full_name);
                                        goto err;
                                }
                        }

                        if (cells_name) {
                                if (of_property_read_u32(node, cells_name,
                                                         &count)) {
                                        pr_err("%s: could not get %s for %s\n",
                                                np->full_name, cells_name,
                                                node->full_name);
                                        goto err;
                                }
                        } else {
                                count = cell_count;
                        }

                        /*
                         * Make sure that the arguments actually fit in the
                         * remaining property data length
                         */
                        if (list + count > list_end) {
                                pr_err("%s: arguments longer than property\n",
                                         np->full_name);
                                goto err;
                        }
                }

요청 np 노드에서 list_name 속성 값을 읽어 index 값에 해당하는 phandle 노드에서 #으로 시작하는 cells_name 속성 값 수 만큼 phandle 뒤의 인수들을 out_args에 저장한다. cells_name이 지정되지 않은 경우 대신 인수 갯 수로 cell_count 수를 사용한다.

  • 코드 라인 14~16에서 np 노드에서 list_name 속성 값과 size(바이트)를 알아온다. 읽어오지 못하는 경우 에러 값으로 -ENOENT 결과를 반환한다.
    • 예) list_name=”clocks = <&clock ABC>, <&clock DEF>; 인 경우 size=16이다.
  • 코드 라인 17~20에서 list_name 속성 값의 끝 주소를 구하고 그 끝 주소직전까지 루프를 돈다.
  • 코드 라인 28~29에서 먼저 phandle 값을 읽어와서 그 값이 주어진 경우
  • 코드 라인 39~46에서 cells_name이 주어졌거나 cur_index가 요청한 index 값이 된 경우 phandle 값에 매치되는 노드를 찾아온다. 만일 못찾은 경우 에러 메시지와 함께 함수를 빠져나간다.
  • 코드 라인 48~55에서 cells_name이 주어진 경우 cells_name 속성 값을 읽어 count에 대입한다. 만일 못찾은 경우 에러 메시지와 함께 함수를 빠져나간다.
  • 코드 라인 56~58에서 cells_name이 주어지지 않은 경우 count 값으로 인수로 전달받은 cell_count를 사용한다.
  • 코드 라인 64~68에서 count 값이 읽을 범위를 벗어난 경우 에러 메시지와 함께 함수를 빠져나간다.

 

                /*
                 * All of the error cases above bail out of the loop, so at
                 * this point, the parsing is successful. If the requested
                 * index matches, then fill the out_args structure and return,
                 * or return -ENOENT for an empty entry.
                 */
                rc = -ENOENT;
                if (cur_index == index) {
                        if (!phandle)
                                goto err;

                        if (out_args) {
                                int i;
                                if (WARN_ON(count > MAX_PHANDLE_ARGS))
                                        count = MAX_PHANDLE_ARGS;
                                out_args->np = node;
                                out_args->args_count = count;
                                for (i = 0; i < count; i++)
                                        out_args->args[i] = be32_to_cpup(list++);
                        } else {
                                of_node_put(node);
                        }

                        /* Found it! return success */
                        return 0;
                }

                of_node_put(node);
                node = NULL;
                list += count;
                cur_index++;
        }

        /*
         * Unlock node before returning result; will be one of:
         * -ENOENT : index is for empty phandle
         * -EINVAL : parsing error on data
         * [1..n]  : Number of phandle (count mode; when index = -1)
         */
        rc = index < 0 ? cur_index : -ENOENT;
 err:
        if (node)
                of_node_put(node);
        return rc;
}
  • 코드 라인 7~26 cur_index가 인수로 요청한 index와 동일하면 out_args에 다음 값들을 대입하고 성공(0) 값으로 함수를 종료한다.
    • out_args->np에 phandle 값으로 찾은 노드를 대입
    • out_args->args_count에 인수 갯 수
    • out_args->args[] phandle 다음의 인수 값(args_count 수 만큼)
  • 코드 라인 28~32에서 cur_index를 증가시키고 다음 인수를 읽기위해 준비한다.
  • 코드 라인 30~44에서 에러 처리를 위해 요청 index가 음수였던 경우 cur_index 값을 반환하고 그렇지 않은 경우 -ENOENT 값으로 함수를 반환한다.

 

다음 주어진 Device Tree와 예)를 살펴보자.

arch/arm/boot/dts/bcm283x.dtsi – raspberrypi 커널 v4.9.y

                clocks: cprman@7e101000 {
                        compatible = "brcm,bcm2835-cprman";
                        #clock-cells = <1>;
                        reg = <0x7e101000 0x2000>;
                        clocks = <&clk_osc>,
                                <&dsi0 0>, <&dsi0 1>, <&dsi0 2>,
                                <&dsi1 0>, <&dsi1 1>, <&dsi1 2>;
                };

                uart0: serial@7e201000 {
                        compatible = "brcm,bcm2835-pl011", "arm,pl011", "arm,primecell";
                        reg = <0x7e201000 0x1000>;
                        interrupts = <2 25>;
                        clocks = <&clocks BCM2835_CLOCK_UART>,
                                 <&clocks BCM2835_CLOCK_VPU>;
                        clock-names = "uartclk", "apb_pclk";
                        arm,primecell-periphid = <0x00241011>;
                };

예) of_parse_phandle_with_args(np, “clocks”, “#clock-cells”, index, &clkspec);   np=”uart0″ device_node를 가리키고, index=0으로 한다.

  • uart0 노드의 “clocks” 속성 값에서 0번째 index가 가리키는 값들은 <&clocks BCM2835_CLOCK_UART>이다. 그 중 phandle이 가리키는 clocks 노드에서 “#clock-cells” 속성 값이 <1>이므로 읽어들일 인수의 갯수는 1이다. 따라서 출력 인수 clkspec에 들어갈 내용은 다음과 같다.
    • clkspec->np = clocks 노드
    • clkspec->args_count = 1
    • clkspec->args[] = {BCM2835_CLOCK_UART}

 

IO 매핑

of_iomap()

drivers/of/address.c

/**
 * of_iomap - Maps the memory mapped IO for a given device_node
 * @device:     the device whose io range will be mapped
 * @index:      index of the io range
 *
 * Returns a pointer to the mapped memory
 */
void __iomem *of_iomap(struct device_node *np, int index)
{
        struct resource res;

        if (of_address_to_resource(np, index, &res))
                return NULL;

        return ioremap(res.start, resource_size(&res));
}
EXPORT_SYMBOL(of_iomap);

요청 디바이스 노드가 사용하는 주소 범위를 IO 매핑한다. 매핑 시 요청 디바이스 노드의 상위 노드가 사용하는 버스에 따라 버스 주소를 물리 주소로 변환할 수도 있다.

 

다음 그림은 인터럽트 컨트롤러가 사용하는 주소 범위가 자식 버스 주소이며 이를 변환하여 부모 버스 주소인 ARM 로컬 물리 주소인 0x3f00_b200부터 0x200 사이즈만큼의 영역을 io 매핑하는 모습을 보여준다. io 매핑한 가상 주소 공간을 반환한다.

  • 버스가 여러 개 중첩이 되는 경우 최상위 버스인 플랫폼 버스까지 변환해야 한다.
    • 예) GPU 디바이스에 달려있는 I2C 장치가 사용하는 버스 주소 —> ARM 로컬 물리주소 –> 매핑된 ARM 가상 주소
    • 예) ARM에 연결된 PCI 버스 주소 —> ARM 로컬 물리 주소 –> 매핑된 ARM 가상 주소
    • 조금 더 복잡한 예) GPU 디바이스에 달려있는 PCI 장치 주소 -> GPU의 PCI 버스 주소 —> ARM 로컬 물리 주소 –> 매핑된 ARM 가상 주소

 

of_address_to_resource()

drivers/of/address.c

/**
 * of_address_to_resource - Translate device tree address and return as resource
 *
 * Note that if your address is a PIO address, the conversion will fail if
 * the physical address can't be internally converted to an IO token with
 * pci_address_to_pio(), that is because it's either called to early or it
 * can't be matched to any host bridge IO space
 */
int of_address_to_resource(struct device_node *dev, int index,
                           struct resource *r)
{
        const __be32    *addrp;
        u64             size;
        unsigned int    flags;
        const char      *name = NULL;

        addrp = of_get_address(dev, index, &size, &flags);
        if (addrp == NULL)
                return -EINVAL;

        /* Get optional "reg-names" property to add a name to a resource */
        of_property_read_string_index(dev, "reg-names", index, &name);

        return __of_address_to_resource(dev, addrp, size, flags, name, r);
}
EXPORT_SYMBOL_GPL(of_address_to_resource);

요청 디바이스 노드가 사용하는 주소 범위를 resource 구조체로 구성하여 반환한다. 요청 디바이스 노드의 상위 노드가 사용하는 버스에 따라 버스 주소를 물리 주소로 변환할 수도 있다.

  • 코드 라인 17~19에서 요청 디바이스 노드가 사용하는 물리 주소와 사이즈 및 플래그를 알아온다. 요청 디바이스 노드의 상위 노드가 사용하는 버스에 따라 버스 주소를 물리 주소로 변환할 수도 있다.
  • 코드 라인 22에서 옵션으로 “reg-names” 속성이 있는 경우 리소스 명을 줄 수 있다.
  • 코드 라인 24에서 주소, 사이즈, 플래그, 리소스명을 사용하여 resource 구조체를 구성하여 반환한다.

 

of_get_address()

drivers/of/address.c

const __be32 *of_get_address(struct device_node *dev, int index, u64 *size,
                    unsigned int *flags)
{
        const __be32 *prop;
        unsigned int psize;
        struct device_node *parent;
        struct of_bus *bus;
        int onesize, i, na, ns;

        /* Get parent & match bus type */
        parent = of_get_parent(dev);
        if (parent == NULL)
                return NULL;
        bus = of_match_bus(parent);
        bus->count_cells(dev, &na, &ns);
        of_node_put(parent);
        if (!OF_CHECK_ADDR_COUNT(na))
                return NULL;

        /* Get "reg" or "assigned-addresses" property */
        prop = of_get_property(dev, bus->addresses, &psize);
        if (prop == NULL)
                return NULL;
        psize /= 4;

        onesize = na + ns;
        for (i = 0; psize >= onesize; psize -= onesize, prop += onesize, i++)
                if (i == index) {
                        if (size)
                                *size = of_read_number(prop + na, ns);
                        if (flags)
                                *flags = bus->get_flags(prop);
                        return prop;
                }
        return NULL;
}
EXPORT_SYMBOL(of_get_address);

요청 디바이스 노드가 사용할 주소(버스 주소 또는 물리 주소), 사이즈, 플래그 값을 구해온다.

  • 코드 라인 11~13에서 부모 노드를 알아온다. 부모 노드가 없는 경우 null을 반환한다.
  • 코드 라인 14~18에서 부모 노드에서 사용하는 버스를 알아오고 “#addr-cells” 및 “#size-cells” 속성 값을 구해온다. 만일 구해온 주소 셀 크기가 1~4 범위가 아닌 경우 null을 반환한다.
    • 버스 종류: pci, isa, 디폴트 generic
  • 코드 라인 21~23에서 pci 버스인 경우 “assigned-addresses”, 그 외의 버스 타입은 “reg” 속성 값을 읽어오고 못 찾은 경우 null을 반환한다.
  • 코드 라인 24~34에서 인덱스에 해당하는 사이즈 값을 읽어오고 버스 타입에 따른 플래그 값을 알아온다.
  • 코드 라인 35에서 해당하는 인덱스가 없는 경우 null을 반환한다.

 

다음 그림은 디폴트 generic 버스를 사용하는 인터럽트 컨트롤러가 사용하는 주소, 사이즈 및 플래그 값을 산출하는 과정을 보여준다.

 

of_match_bus()

drivers/of/address.c

static struct of_bus *of_match_bus(struct device_node *np)
{
        int i;

        for (i = 0; i < ARRAY_SIZE(of_busses); i++)
                if (!of_busses[i].match || of_busses[i].match(np))
                        return &of_busses[i];
        BUG();
        return NULL;
}

요청한 디바이스 노드가 사용하는 버스를 구해온다.

  • pci, isa, 디폴트 generic 버스

 

__of_address_to_resource()

drivers/of/address.c

static int __of_address_to_resource(struct device_node *dev,
                const __be32 *addrp, u64 size, unsigned int flags,
                const char *name, struct resource *r)
{
        u64 taddr;

        if ((flags & (IORESOURCE_IO | IORESOURCE_MEM)) == 0)
                return -EINVAL;
        taddr = of_translate_address(dev, addrp);
        if (taddr == OF_BAD_ADDR)
                return -EINVAL;
        memset(r, 0, sizeof(struct resource));
        if (flags & IORESOURCE_IO) {
                unsigned long port;
                port = pci_address_to_pio(taddr);
                if (port == (unsigned long)-1)
                        return -EINVAL;
                r->start = port;
                r->end = port + size - 1;
        } else {
                r->start = taddr;
                r->end = taddr + size - 1;
        }
        r->flags = flags;
        r->name = name ? name : dev->full_name;

        return 0;
}

요청한 주소를 ARM 물리 주소로 변환하여 리소스 형태로 구성하여 반환한다.

  • 코드 라인 7~8에서 ioresource가 아닌 경우 -EINVAL 결과를 반환한다.
  • 코드 라인 9~11에서 주어진 주소를 버스 변환하여 온다. 만일 변환 테이블 범위에서 매치되지 않는 주소인 경우 -EINVAL 결과를 반환한다.
  • 코드 라인 12~27에서 resource 구조체를 구성한 후 반환한다.

 

버스에 따른 주소 변환

of_translate_address()

drivers/of/address.c

u64 of_translate_address(struct device_node *dev, const __be32 *in_addr)
{
        return __of_translate_address(dev, in_addr, "ranges");
}
EXPORT_SYMBOL(of_translate_address);

 

아래 그림은 단순히 부모 버스 주소와 자식 주소체계를 갖는 1 단계 주소 변환만을 보여준다.

  • 부모 버스 주소:
    • 가장 바깥 플랫폼 버스이므로 ARM 로컬 물리 주소이다.
  • 자식 버스 주소:
    • soc 안에서 선언된 노드들의 주소는 ARM 로컬 물리 주소가 아닌 다른 IO 주소를 따른다.

 

__of_translate_address()

drivers/of/address.c

/*
 * Translate an address from the device-tree into a CPU physical address,
 * this walks up the tree and applies the various bus mappings on the
 * way.
 *
 * Note: We consider that crossing any level with #size-cells == 0 to mean
 * that translation is impossible (that is we are not dealing with a value
 * that can be mapped to a cpu physical address). This is not really specified
 * that way, but this is traditionally the way IBM at least do things
 */
static u64 __of_translate_address(struct device_node *dev,
                                  const __be32 *in_addr, const char *rprop)
{
        struct device_node *parent = NULL;
        struct of_bus *bus, *pbus;
        __be32 addr[OF_MAX_ADDR_CELLS];
        int na, ns, pna, pns;
        u64 result = OF_BAD_ADDR;

        pr_debug("OF: ** translation for device %s **\n", of_node_full_name(dev));

        /* Increase refcount at current level */
        of_node_get(dev);

        /* Get parent & match bus type */
        parent = of_get_parent(dev);
        if (parent == NULL)
                goto bail;
        bus = of_match_bus(parent);

        /* Count address cells & copy address locally */
        bus->count_cells(dev, &na, &ns);
        if (!OF_CHECK_COUNTS(na, ns)) {
                pr_debug("OF: Bad cell count for %s\n", of_node_full_name(dev));
                goto bail;
        }
        memcpy(addr, in_addr, na * 4);
        
        pr_debug("OF: bus is %s (na=%d, ns=%d) on %s\n",
            bus->name, na, ns, of_node_full_name(parent));
        of_dump_addr("OF: translating address:", addr, na);

요청 디바이스 노드에서 읽은 주소를 cpu 물리 메모리 주소로 변환한다. 루트 노드 도달 시까지 변환해 나간다.

  • 코드 라인 26~28에서 부모 노드를 알아온다. 부모 노드가 없는 경우 OF_BASD_ADDR 결과를 반환한다.
  • 코드 라인 29~36에서 부모 노드에서 사용하는 버스를 알아오고 버스의 (*count_cells) 후크 함수를 호출하여 주소셀 크기 및 사이즈셀 크기를 알아온다. 만일 구해온 주소 셀 크기가 1~4 범위가 아닌 경우 null을 반환한다.
    • 버스 종류: pci, isa, 디폴트 generic
    • 버스별로 사용할 ranges 속성에서 주소셀 크기와 사이즈셀 크기
      • pci 버스: 주소=3, 사이즈=2
      • isa 버스: 주소=2, 사이즈=1
      • 그 외:  “#addr-cells” 및 “#size-cells” 속성 값을 구해서 사용

 

        /* Translate */
        for (;;) {
                /* Switch to parent bus */
                of_node_put(dev);
                dev = parent;
                parent = of_get_parent(dev);

                /* If root, we have finished */
                if (parent == NULL) {
                        pr_debug("OF: reached root node\n");
                        result = of_read_number(addr, na);
                        break;
                }

                /* Get new parent bus and counts */
                pbus = of_match_bus(parent);
                pbus->count_cells(dev, &pna, &pns);
                if (!OF_CHECK_COUNTS(pna, pns)) {
                        printk(KERN_ERR "prom_parse: Bad cell count for %s\n",
                               of_node_full_name(dev));
                        break;
                }

                pr_debug("OF: parent bus is %s (na=%d, ns=%d) on %s\n",
                    pbus->name, pna, pns, of_node_full_name(parent));

                /* Apply bus translation */
                if (of_translate_one(dev, bus, pbus, addr, na, ns, pna, rprop))
                        break;

                /* Complete the move up one level */
                na = pna;
                ns = pns;
                bus = pbus;

                of_dump_addr("OF: one level translation:", addr, na);
        }
 bail:
        of_node_put(parent);
        of_node_put(dev);

        return result;
}
  • 코드 라인 2~13에서 루프를 돌며 상위 노드를 알아온다. 최상위 루트 노드인 경우 그냥 해당 주소를 변환없이 사용하고 루프를 탈출한다.
  • 코드 라인 16~22에서 부모 노드에서 사용하는 버스를 알아오고 버스의 (*count_cells) 후크 함수를 호출하여 주소셀 크기 및 사이즈셀 크기를 알아온다. 만일 구해온 주소 셀 크기가 1~4 범위가 아닌 경우 null을 반환한다.
  • 코드 라인 28~29에서 버스 변환 테이블(range)을 참조하여 cpu 물리 주소로 변환해온다. 변환 실패 시 루프를 빠져나온다.
  • 코드 라인 32~37에서 상위 노드로 이동하고 루트 노드까지 계속 변환을 시도한다.
    • 하이 라키 구조의 버스 변환이 필요한 경우를 지원하기 위해 cascade 한다.

 

of_translate_one()

drivers/of/address.c

static int of_translate_one(struct device_node *parent, struct of_bus *bus,
                            struct of_bus *pbus, __be32 *addr,
                            int na, int ns, int pna, const char *rprop)
{
        const __be32 *ranges;
        unsigned int rlen;
        int rone;
        u64 offset = OF_BAD_ADDR;

        /* Normally, an absence of a "ranges" property means we are
         * crossing a non-translatable boundary, and thus the addresses
         * below the current not cannot be converted to CPU physical ones.
         * Unfortunately, while this is very clear in the spec, it's not
         * what Apple understood, and they do have things like /uni-n or
         * /ht nodes with no "ranges" property and a lot of perfectly
         * useable mapped devices below them. Thus we treat the absence of
         * "ranges" as equivalent to an empty "ranges" property which means
         * a 1:1 translation at that level. It's up to the caller not to try
         * to translate addresses that aren't supposed to be translated in
         * the first place. --BenH.
         *
         * As far as we know, this damage only exists on Apple machines, so
         * This code is only enabled on powerpc. --gcl
         */
        ranges = of_get_property(parent, rprop, &rlen);
        if (ranges == NULL && !of_empty_ranges_quirk(parent)) {
                pr_debug("OF: no ranges; cannot translate\n");
                return 1;
        }
        if (ranges == NULL || rlen == 0) {
                offset = of_read_number(addr, na);
                memset(addr, 0, pna * 4);
                pr_debug("OF: empty ranges; 1:1 translation\n");
                goto finish;
        }

        pr_debug("OF: walking ranges...\n");

        /* Now walk through the ranges */
        rlen /= 4;
        rone = na + pna + ns;
        for (; rlen >= rone; rlen -= rone, ranges += rone) {
                offset = bus->map(addr, ranges, na, ns, pna);
                if (offset != OF_BAD_ADDR)
                        break;
        }
        if (offset == OF_BAD_ADDR) {
                pr_debug("OF: not found !\n");
                return 1;
        }
        memcpy(addr, ranges + na, 4 * pna);

 finish:
        of_dump_addr("OF: parent translation for:", addr, pna);
        pr_debug("OF: with offset: %llx\n", (unsigned long long)offset);

        /* Translate it into parent bus space */
        return pbus->translate(addr, offset, pna);
}

부모 노드의 rprops 속성 값을 읽어 범위로 사용하고 지정된 버스 방식으로 주소를 변환한다.

  • 코드 라인 25~29에서 부모 노드의 rprop 속성 값 주소를 읽어 ranges에 대입한다. 속성을 찾지 못하는 경우 1을 반환한다.
    • of_empty_ranges_quirk() 함수는 ppc 아키텍처에서만 동작하고 그 외의 경우 false를 반환한다.
  • 코드 라인 30~35에서 읽어온 속성 값의 길이가 0인 경우 1:1 변환을 하기 위해 주소 값을 offset에 대입하고, addr를 0으로 한 후 finish 레이블로 이동한다.
  • 코드 라인 40~50에서 ranges 값을 사용하여 변환을 시도하고 변환이 모두 실패한 경우 1을 반환한다.
  • 코드 라인 51에서 변환이 성공한 경우 addr에 현재 ranges의 다음 엔트리인 변환 주소를 대입한다.
  • 코드 라인 58에서 addr에 offset 값을 사용하여 변환한다.
    • default 버스인 경우 addr 값에 offset 값을 더한다.

 

3가지 버스

of_busses[] 배열

drivers/of/address.c

/*
 * Array of bus specific translators
 */

static struct of_bus of_busses[] = {
#ifdef CONFIG_OF_ADDRESS_PCI
        /* PCI */
        {
                .name = "pci",
                .addresses = "assigned-addresses",
                .match = of_bus_pci_match,
                .count_cells = of_bus_pci_count_cells,
                .map = of_bus_pci_map,
                .translate = of_bus_pci_translate,
                .get_flags = of_bus_pci_get_flags,
        },
#endif /* CONFIG_OF_ADDRESS_PCI */
        /* ISA */
        {
                .name = "isa",
                .addresses = "reg",
                .match = of_bus_isa_match,
                .count_cells = of_bus_isa_count_cells,
                .map = of_bus_isa_map,
                .translate = of_bus_isa_translate,
                .get_flags = of_bus_isa_get_flags,
        },
        /* Default */
        {
                .name = "default",
                .addresses = "reg", 
                .match = NULL,
                .count_cells = of_bus_default_count_cells,
                .map = of_bus_default_map,
                .translate = of_bus_default_translate,
                .get_flags = of_bus_default_get_flags,
        },
};

default generic 버스는 pci와 isa 버스 매치가 되지 않은 경우에 사용된다. 따라서 (*match) 핸들러가 null이다.

  • pci와 isa는 생략하고 아래에 defaul generic 버스 핸들러 함수들만 설명한다.

 

디폴트 generic 버스 핸들러

of_bus_default_count_cells()

drivers/of/address.c

/*
 * Default translator (generic bus)
 */

static void of_bus_default_count_cells(struct device_node *dev,
                                       int *addrc, int *sizec)
{
        if (addrc)
                *addrc = of_n_addr_cells(dev);
        if (sizec)
                *sizec = of_n_size_cells(dev);
}

현재 노드에서 가장 가까운 상위 노드에 있는 “#addr-cells” 및 “#size-cells” 속성 값을 읽어와서 출력 인수 addrc 및 sizec에 대입한다.  속성 값이 없는 경우 1을 대입한다.

 

of_n_addr_cells()

drivers/of/base.c

int of_n_addr_cells(struct device_node *np)
{
        const __be32 *ip;

        do {
                if (np->parent)
                        np = np->parent;
                ip = of_get_property(np, "#address-cells", NULL);
                if (ip)
                        return be32_to_cpup(ip);
        } while (np->parent);
        /* No #address-cells property for the root node */
        return OF_ROOT_NODE_ADDR_CELLS_DEFAULT;
}
EXPORT_SYMBOL(of_n_addr_cells);

현재 노드에서 가장 가까운 상위 노드에 있는 “#addr-cells” 속성 값을 읽어온다. 찾지 못한 경우 1을 반환한다.

 

of_n_size_cells()

drivers/of/base.c

int of_n_size_cells(struct device_node *np)
{
        const __be32 *ip;

        do {
                if (np->parent)
                        np = np->parent;
                ip = of_get_property(np, "#size-cells", NULL);
                if (ip)
                        return be32_to_cpup(ip);
        } while (np->parent);
        /* No #size-cells property for the root node */
        return OF_ROOT_NODE_SIZE_CELLS_DEFAULT;
}
EXPORT_SYMBOL(of_n_size_cells);

현재 노드에서 가장 가까운 상위 노드에 있는 “#size-cells” 속성 값을 읽어온다. 찾지 못한 경우 1을 반환한다.

 

of_bus_default_map()

drivers/of/address.c

static u64 of_bus_default_map(__be32 *addr, const __be32 *range,
                int na, int ns, int pna)
{
        u64 cp, s, da;

        cp = of_read_number(range, na);
        s  = of_read_number(range + na + pna, ns);
        da = of_read_number(addr, na);

        pr_debug("OF: default map, cp=%llx, s=%llx, da=%llx\n",
                 (unsigned long long)cp, (unsigned long long)s,
                 (unsigned long long)da);

        if (da < cp || da >= (cp + s))
                return OF_BAD_ADDR;
        return da - cp;
}

요청한 주소가 range에 포함된 경우 요청한 주소에서 range 시작 주소를 뺀 주소 값을 반환한다.

  • 코드 라인 6에서 range 주소에서 값을 읽어 cp에 대입한다.
  • 코드 라인 7에서 그 다음 값을 읽어 s에 대입한다.
  • 코드 라인 8에서 주소값을 읽어 da에 대입한다.
  • 코드 라인 14에서 cp보다 da가 작거나 cp+s보다 da가 큰 경우 OF_BAD_ADDR을 반환한다.
  • 코드 라인 15에서 주소 값에서 range 값을 뺀다.

 

예) ranges = <0x7e000000 0x3f000000 0x1000000 0x40000000 0x40000000 0x40000>, addr=0x7e00b200, na=1, ns=1, pna=1인 경우

  • 버스1 시작 주소=0x7e00_0000, 물리 시작 주소=0x3f00_0000, 사이즈=0x1000_0000
  • 버스2 시작 주소=0x4000_0000, 물리 시작 주소=0x4000_0000, 사이즈=0x4_0000

-> addr이 range 범위(0x7e00_0000 ~ (0x7e00_0000 + 0x1000_0000)) 이내인 경우 범위 시작 주소를 뺀 주소 0xb200을 반환한다.

 

of_bus_default_translate()

drivers/of/address.c

static int of_bus_default_translate(__be32 *addr, u64 offset, int na)
{
        u64 a = of_read_number(addr, na);
        memset(addr, 0, na * 4);
        a += offset;
        if (na > 1)
                addr[na - 2] = cpu_to_be32(a >> 32);
        addr[na - 1] = cpu_to_be32(a & 0xffffffffu);

        return 0;
}

addr[]에 offset을 더해 반환한다.

  • 코드 라인 3에서 addr[] 값을 읽어서 64비트 값으로 반환한다. (na=1 또는 2)
  • 코드 라인 5에서 읽은 값에 offset을 더한다.
  • 코드 라인 6~8에서 더한 값을 다시 addr[] 값에 저장한다.
    • 예) addr[0] = 0x1111_1111, addr[1]=0x2222_2222, offset=0x1000_1000_2000_2000, na=2
      • addr[0]=0x2111_2111, addr[1]=0x4222_4222

 

of_bus_default_get_flags()

drivers/of/address.c

static unsigned int of_bus_default_get_flags(const __be32 *addr)
{
        return IORESOURCE_MEM;
}

default 버스의 플래그로 IORESOURCE_MEM을 반환한다.

 

MSI 정보를 읽어 디바이스의 msi_domain 갱신

of_msi_configure()

drivers/of/irq.c

/**
 * of_msi_configure - Set the msi_domain field of a device
 * @dev: device structure to associate with an MSI irq domain
 * @np: device node for that device
 */
void of_msi_configure(struct device *dev, struct device_node *np)
{
        dev_set_msi_domain(dev,
                           of_msi_get_domain(dev, np, DOMAIN_BUS_PLATFORM_MSI));
}
EXPORT_SYMBOL_GPL(of_msi_configure);

플랫폼 디바이스의 디바이스 트리 노드에서 msi 정보를 읽어 플랫폼 디바이스의 msi_domain 필드를 갱신한다.

 

static inline void dev_set_msi_domain(struct device *dev, struct irq_domain *d)
{
#ifdef CONFIG_GENERIC_MSI_IRQ_DOMAIN
        dev->msi_domain = d;
#endif
}

디바이스의 msi_domain 필드를 설정한다.

 

of_msi_get_domain()

drivers/of/irq.c

/**     
 * of_msi_get_domain - Use msi-parent to find the relevant MSI domain
 * @dev: device for which the domain is requested
 * @np: device node for @dev
 * @token: bus type for this domain
 *      
 * Parse the msi-parent property (both the simple and the complex
 * versions), and returns the corresponding MSI domain. 
 *
 * Returns: the MSI domain for this device (or NULL on failure).
 */
struct irq_domain *of_msi_get_domain(struct device *dev,
                                     struct device_node *np,
                                     enum irq_domain_bus_token token)
{
        struct device_node *msi_np;
        struct irq_domain *d;

        /* Check for a single msi-parent property */
        msi_np = of_parse_phandle(np, "msi-parent", 0);
        if (msi_np && !of_property_read_bool(msi_np, "#msi-cells")) {
                d = irq_find_matching_host(msi_np, token);
                if (!d)
                        of_node_put(msi_np);
                return d; 
        }

        if (token == DOMAIN_BUS_PLATFORM_MSI) {
                /* Check for the complex msi-parent version */
                struct of_phandle_args args;
                int index = 0;

                while (!of_parse_phandle_with_args(np, "msi-parent",
                                                   "#msi-cells",
                                                   index, &args)) {
                        d = irq_find_matching_host(args.np, token);
                        if (d)
                                return d;

                        of_node_put(args.np);
                        index++;
                }
        }

        return NULL;
}

플랫폼 디바이스의 디바이스 트리 노드에서 msi 정보를 읽어와서 irq_domain을 반환한다. 검색이 실패한 경우 null을 반환한다.

  • 코드 라인 20에서 인자로 전달받은 디바이스 노드에서 “msi-parent” 속성의 phandle 값이 가리키는 msi 노드를 알아온다.
  • 코드 라인 21~26에서 msi 노드에서 “#msi-cells” 속성이 없는 경우 msi 노드에서 인자로 전달받은 token으로 irq 도메인을 찾아온 후 반환한다.
  • 코드 라인 28~43에서 token이 DOMAIN_BUS_PLATFORM_MSI인 경우에 한해 “msi-parent” 속성의 phandle 리스트가 가리키는 msi 노드들을 대상으로 순회하며 인자로 전달받은 token으로 irq 도메인을 찾아온 후 반환한다.

 

다음 그림은 msi를 사용하는 플랫폼 디바이스에서 irq 도메인을 찾아 플랫폼 디바이스에 저장하는 모습을 보여준다.

 

구조체

of_device_id 구조체

/*
 * Struct used for matching a device
 */
struct of_device_id { 
        char    name[32];
        char    type[32]; 
        char    compatible[128];
        const void *data;
};
  • name
    • 노드명
  • type
    • 디바이스 타입 문자열
  • compatible
    • compatible 디바이스 문자열
  • data
    • 데이터
      • PSCI에서 사용하는 경우 psci_initcall_t() 함수 포인터 등이 담겨있다.

 

참고

5 thoughts to “DTB (of API)”

    1. 아뇨. 전 직장인이고 개인적으로 정리한 자료들입니다.
      따라서 강의 자료에 사용하지는 않았습니다.
      즐거운 하루 되세요.

  1. 안녕하세요, 항상 좋은 자료 잘 보고 많은 공부 하고있습니다.
    개인 티스토리에 공부하는 내용들을 정리해서 올리는데,
    출처를 남기고 별도 편집과 재정리해서 업로드해도 괜찮을지 여쭈어보고 싶습니다.
    감사합니다.

댓글 남기기

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