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 매핑하는 모습을 보여준다.

 

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

 

 

__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에서 부모 노드에서 사용하는 버스를 알아오고 “#addr-cells” 및 “#size-cells” 속성 값을 구해온다. 만일 구해온 주소 셀 크기가 1~4 범위가 아닌 경우 null을 반환한다.
    • 버스 종류: pci, isa, 디폴트 generic

 

        /* 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에서 부모 노드에서 사용하는 버스를 알아오고 “#addr-cells” 및 “#size-cells” 속성 값을 구해온다. 만일 구해온 주소 셀 크기가 1~4 범위가 아닌 경우 null을 반환한다.
    • 버스 종류: pci, isa, 디폴트 generic
  • 코드 라인 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을 반환한다.

 

구조체

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() 함수 포인터 등이 담겨있다.

 

참고

One thought on “DTB (of API)

답글 남기기

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