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

 

참고

DTB (fdt API)

DTB 관련 API는 다음과 같이 구분된다.

  • fdt로 시작하는 API는 DTB(Device Tree Blob)를 대상으로 동작한다.
  • of로 시작하는 API는 링크드 리스트 구조의 expanded format으로 변환된 Device Tree를 대상으로 동작한다.
    • DTB는 unflatten_device_tree() 함수에의해 변환된다.

 

fdt_get_name()

scripts/dtc/libfdt/fdt_ro.c

const char *fdt_get_name(const void *fdt, int nodeoffset, int *len)
{
        const struct fdt_node_header *nh = _fdt_offset_ptr(fdt, nodeoffset);
        int err;

        if (((err = fdt_check_header(fdt)) != 0)
            || ((err = _fdt_check_node_offset(fdt, nodeoffset)) < 0)) 
                        goto fail;

        if (len)
                *len = strlen(nh->name);

        return nh->name;

 fail:
        if (len)
                *len = err;
        return NULL;
}
  • fdt가 가리키는 DTB에서 nodeoffset에 위치한 노드에서 노드명 주소와 노드명 길이를 알아온다.

 

fdt_get_name-1a

 

_fdt_offset_ptr()

scripts/dtc/libfdt/libfdt_internal.h

static inline const void *_fdt_offset_ptr(const void *fdt, int offset)
{
        return (const char *)fdt + fdt_off_dt_struct(fdt) + offset;
}

DTB의 structure 블럭 시작 주소에 offset를 더한 주소를 리턴한다..

__fdt_offset_ptr-1a

 

fdt_offset_ptr()

scripts/dtc/libfdt/fdt.c

const void *fdt_offset_ptr(const void *fdt, int offset, unsigned int len)
{
        const char *p; 

        if (fdt_version(fdt) >= 0x11)
                if (((offset + len) < offset)
                    || ((offset + len) > fdt_size_dt_struct(fdt)))
                        return NULL;

        p = _fdt_offset_ptr(fdt, offset);

        if (p + len < p)
                return NULL;
        return p;
}

DTB structure 블럭에서 offset 만큼 떨어진 주소를 알아오는데 그 주소의 위치가 DTB structure 블럭을 벗어난 경우 에러로 null을 리턴한다.

  • if (fdt_version(fdt) >= 0x11
    • fdt 주소에 위치한 DTB 버전이 0x11이상인 경우 len이 음수이거나 물리주소를 초과한 경우
  • if (((offset + len) < offset) || ((offset + len) > fdt_size_dt_struct(fdt))) return NULL;
    • offset+len이 DTB structure 블럭 사이즈를 초과한 경우 null로 리턴
  • p = _fdt_offset_ptr(fdt, offset);
    • DTB structure 블럭에서 offset 만큼 떨어진 주소를 알아온다.
  •  if (p + len < p) return NULL;
    • 알아온 주소가 물리 주소를 초과한 경우 null을 리턴한다.

 

unflatten_dt_alloc()

drivers/of/fdt.c

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

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

        return res; 
}
  • 메모리 주소 mem을 align 단위로 round up한 후 리턴하고 메모리 주소 mem에 사이즈를 더한다.

 

fdt_next_tag()

scripts/dtc/libfdt/fdt.c

uint32_t fdt_next_tag(const void *fdt, int startoffset, int *nextoffset)
{
        const uint32_t *tagp, *lenp;
        uint32_t tag;
        int offset = startoffset;
        const char *p;

        *nextoffset = -FDT_ERR_TRUNCATED;
        tagp = fdt_offset_ptr(fdt, offset, FDT_TAGSIZE);
        if (!tagp)
                return FDT_END; /* premature end */
        tag = fdt32_to_cpu(*tagp);
        offset += FDT_TAGSIZE;

        *nextoffset = -FDT_ERR_BADSTRUCTURE;
        switch (tag) {
        case FDT_BEGIN_NODE:
                /* skip name */
                do {
                        p = fdt_offset_ptr(fdt, offset++, 1);
                } while (p && (*p != '\0'));
                if (!p)
                        return FDT_END; /* premature end */
                break;

        case FDT_PROP:
                lenp = fdt_offset_ptr(fdt, offset, sizeof(*lenp));
                if (!lenp)
                        return FDT_END; /* premature end */
                /* skip-name offset, length and value */
                offset += sizeof(struct fdt_property) - FDT_TAGSIZE
                        + fdt32_to_cpu(*lenp);
                break;

        case FDT_END:
        case FDT_END_NODE:
        case FDT_NOP:
                break;

        default:
                return FDT_END;
        }

        if (!fdt_offset_ptr(fdt, startoffset, offset - startoffset))
                return FDT_END; /* premature end */

        *nextoffset = FDT_TAGALIGN(offset);
        return tag;
}

startoffset에 위치한 태그의 값을 알아오고 startoffset 다음에 위치한 태그 offset를 출력 인수 nextoffset에 저장한다.

  • startoffset 위치의 태그번호를 리턴하는데 태그 종류에 따라 출력 인수 nextoffset에 저장되는 offset가 다음과 같이 다르다.
    • FDT_BEGIN_NODE인 경우 4 byte 태그 및 이름을 skip 한 4 byte round up한 offset
    • FDT_PROP인 경우 4 byte 태그, 속성 길이, name offset, 속성명을 skip한 4 byte round up한 offset
    • 그 외의 태그는 4 byte 태그만 더한 offset

아래 그림은 fdt_next_tag()를 두 번 반복 수행하는 모습을 보여준다.

fdt_next_tag-1b

 

 

fdt_first_property_offset()

scripts/dtc/libfdt/fdt_ro.c

int fdt_first_property_offset(const void *fdt, int nodeoffset)
{
        int offset;

        if ((offset = _fdt_check_node_offset(fdt, nodeoffset)) < 0)
                return offset;

        return _nextprop(fdt, offset);
}

노드 내의 처음 속성 offset를 리턴한다. 속성이 없으면 -값 에러를 리턴한다.

  • if ((offset = _fdt_check_node_offset(fdt, nodeoffset)) < 0) return offset;
    • nodeoffset에 위치한 태그가 노드 시작이 아닌 경우 -값 에러(offset)를 리턴한다.
  • return _nextprop(fdt, offset);
    • 처음 발견되는 속성 offset를 리턴한다.

 

아래 그림은 노드에서 처음 발견되는 속성에 대한 offset를 알아오는 것을 표현하였다.

fdt_first_property_offset-1

 

 

_fdt_check_node_offset()

scripts/dtc/libfdt/fdt.c

int _fdt_check_node_offset(const void *fdt, int offset)
{
        if ((offset < 0) || (offset % FDT_TAGSIZE)
            || (fdt_next_tag(fdt, offset, &offset) != FDT_BEGIN_NODE))
                return -FDT_ERR_BADOFFSET;

        return offset;
}

주어진 offset에 있는 태그가 시작 노드인 경우에만 offset를 리턴하고 그렇지 않으면 -값 에러를 리턴한다.

  • offset가 0보다 작거나 4 byte align 되어 있지 않거나 현재 태그가 노드 시작이 아니면 -값의 에러를 리턴하고 그렇지 않은 경우 offset를 리턴한다.

 

_nextprop()

scripts/dtc/libfdt/fdt_ro.c

static int _nextprop(const void *fdt, int offset)
{
        uint32_t tag;
        int nextoffset;

        do {
                tag = fdt_next_tag(fdt, offset, &nextoffset);

                switch (tag) {
                case FDT_END:
                        if (nextoffset >= 0)
                                return -FDT_ERR_BADSTRUCTURE;
                        else
                                return nextoffset;

                case FDT_PROP:
                        return offset;
                }
                offset = nextoffset;
        } while (tag == FDT_NOP);

        return -FDT_ERR_NOTFOUND;
}

해당 노드 내에서 다음 속성 태그 offset을 알아온다. 만일 노드가 끝나면 -값으로 에러를 리턴한다.

  • 태그가 FDT_NOP인 경우에 루프를 돌며 다음 태그를 읽어오다가 FDT_PROP를 만나면 해당 offset를 리턴한다. 만일 FDT_END를 만나는 경우 -값으로 에러를 리턴한다.

 

아래 그림은 _nextprop()를 호출할 때 해당 노드내에서 다음 속성에 대한 offset를 읽어 오는데 노드가 바뀌거나 끝나는 경우 에러를 리턴하는 것을 보여준다.

_nextprop-1

 

fdt_next_property_offset()

scripts/dtc/libfdt/fdt_ro.c

int fdt_next_property_offset(const void *fdt, int offset)
{
        if ((offset = _fdt_check_prop_offset(fdt, offset)) < 0) 
                return offset;
                
        return _nextprop(fdt, offset);
}

노드 내의 다음 속성 offset를 리턴한다. 속성이 없으면 -값 에러를 리턴한다.

  • if ((offset = _fdt_check_prop_offset(fdt, offset)) < 0) return offset;
    • offset에 위치한 태그가 속성이 아닌 경우 -값 에러(offset)를 리턴한다.
  • return _nextprop(fdt, offset);
    • 다음 발견되는 속성 offset를 리턴한다.

 

_fdt_check_prop_offset()

scripts/dtc/libfdt/fdt.c()

int _fdt_check_prop_offset(const void *fdt, int offset)
{
        if ((offset < 0) || (offset % FDT_TAGSIZE)
            || (fdt_next_tag(fdt, offset, &offset) != FDT_PROP))
                return -FDT_ERR_BADOFFSET;

        return offset;
}

주어진 offset에 있는 태그가 속성인 경우에만 offset를 리턴하고 그렇지 않으면 -값 에러를 리턴한다.

  • offset가 0보다 작거나 4 byte align 되어 있지 않거나 현재 태그가 속성이 아니면 -값의 에러를 리턴하고 그렇지 않은 경우 offset를 리턴한다.

 

fdt_getprop_by_offset()

scripts/dtc/libfdt/fdt_ro.c

const void *fdt_getprop_by_offset(const void *fdt, int offset,
                                  const char **namep, int *lenp)
{
        const struct fdt_property *prop;
                
        prop = fdt_get_property_by_offset(fdt, offset, lenp);
        if (!prop)      
                return NULL;
        if (namep)
                *namep = fdt_string(fdt, fdt32_to_cpu(prop->nameoff));
        return prop->data;
}

속성 값을 리턴하는데 namep 출력 인수는 속성명이 저장된 string 블록의 주소를 저장하고 , lenp에는 데이터 길이를 저장한다. 리턴 값이 음수인 경우는 에러이다.

 

fdt_get_property_by_offset()

scripts/dtc/libfdt/fdt_ro.c

const struct fdt_property *fdt_get_property_by_offset(const void *fdt,
                                                      int offset,
                                                      int *lenp)
{
        int err;
        const struct fdt_property *prop;

        if ((err = _fdt_check_prop_offset(fdt, offset)) < 0) {
                if (lenp)
                        *lenp = err;
                return NULL;
        }

        prop = _fdt_offset_ptr(fdt, offset);

        if (lenp)
                *lenp = fdt32_to_cpu(prop->len);

        return prop;
}

offset 위치의 속성 태그에 위치한 주소를 fdt_property 구조체 포인터로 캐스트하여 리턴하고 lenp 출력 인수에는 데이터 길이를 리턴한다.

 

아래 그림은 속성값이 있는 곳의 주소를 fdt_property 구조체 포인터로 캐스트하여 알아오는 것을 보여준다.

fdt_get_property_by_offset-1

 

fdt_string()

scripts/dtc/libfdt/fdt_ro.c

const char *fdt_string(const void *fdt, int stroffset)
{
        return (const char *)fdt + fdt_off_dt_strings(fdt) + stroffset;
}

DTB에서 주어진 문자열 offset(stroffset)에 대한 주소를 알아온다.

 

fdt_next_node()

scripts/dtc/libfdt/fdt.c

int fdt_next_node(const void *fdt, int offset, int *depth)
{
        int nextoffset = 0;
        uint32_t tag;

        if (offset >= 0)
                if ((nextoffset = _fdt_check_node_offset(fdt, offset)) < 0)
                        return nextoffset;

        do {
                offset = nextoffset;
                tag = fdt_next_tag(fdt, offset, &nextoffset); 

                switch (tag) {
                case FDT_PROP:
                case FDT_NOP:
                        break;

                case FDT_BEGIN_NODE:
                        if (depth)
                                (*depth)++;
                        break;

                case FDT_END_NODE:
                        if (depth && ((--(*depth)) < 0))
                                return nextoffset;
                        break;

                case FDT_END:
                        if ((nextoffset >= 0)
                            || ((nextoffset == -FDT_ERR_TRUNCATED) && !depth))
                                return -FDT_ERR_NOTFOUND;
                        else
                                return nextoffset;
                }
        } while (tag != FDT_BEGIN_NODE);

        return offset;
}

다음 노드의 offset를 리턴하고 출력 인수에 depth를 저장한다.

  • if (offset >= 0) if ((nextoffset = _fdt_check_node_offset(fdt, offset)) < 0) return nextoffset;
    • 주어진 offset가 노드가 아닌 경우 null을 리턴한다.
  • 루프를 돌며 태그 번호와 다음 태그 offset을 알아오고 태그에 따라 다음과 같이 동작한다.
    • FDT_PROP이거나 FDT_NOP인 경우 skip 한다.
    • FDT_BEGIN_NODE인 경우 depth 증가하게 되고 루프를 벗어나게 된다.
    • FDT_END_NODE인 경우 depth가 0이상이면서 감소 시킨 depth가 0보다 작은 경우 nextoffset를 리턴한다. (보통 또 다른 시작 노드)
    • FDT_END인 경우 음수 값인 에러를 리턴한다.

 

DTB 헤더 관련 API

scripts/dtc/libfdt/libfdt.h


#define fdt_get_header(fdt, field) \
        (fdt32_to_cpu(((const struct fdt_header *)(fdt))->field))
#define fdt_magic(fdt)                  (fdt_get_header(fdt, magic))
#define fdt_totalsize(fdt)              (fdt_get_header(fdt, totalsize))
#define fdt_off_dt_struct(fdt)          (fdt_get_header(fdt, off_dt_struct))
#define fdt_off_dt_strings(fdt)         (fdt_get_header(fdt, off_dt_strings))
#define fdt_off_mem_rsvmap(fdt)         (fdt_get_header(fdt, off_mem_rsvmap))
#define fdt_version(fdt)                (fdt_get_header(fdt, version))
#define fdt_last_comp_version(fdt)      (fdt_get_header(fdt, last_comp_version))
#define fdt_boot_cpuid_phys(fdt)        (fdt_get_header(fdt, boot_cpuid_phys))
#define fdt_size_dt_strings(fdt)        (fdt_get_header(fdt, size_dt_strings))
#define fdt_size_dt_struct(fdt)         (fdt_get_header(fdt, size_dt_struct))
  • 디바이스 트리의 헤더에는 10가지의 정보가 있는데 각각에 대해 함수로 제공된다..
  • fdt_magic()
    • DTB 매직 넘버를 알아온다.
  • fdt_totalsize()
    • DTB 전체 사이즈를 알아온다.
  • fdt_off_dt_struct()
    • structure 블럭 시작 offset를 알아온다.
  • fdt_off_dt_strings()
    • string 블럭 시작 offset를 알아온다.
  • fdt_off_mem_rsvmap()
    • memory reserved 블럭 시작 offset를 알아온다.
  • fdt_version()
    • DTB 버전 정보를 알아온다.
  • fdt_last_comp_version
    • DTB 마지막 호환 버전을 알아온다.
  • fdt_boot_cpuid_phys()
  • fdt_size_dt_strings()
    • string 블럭 사이즈를 알아온다.
  • fdt_sizae_dt_struct()
    • structure 블럭 사이즈를 알아온다.

다음은 rpi2 DTB 헤더의 hex 값이다.

fdt_header-1

 

구조체

fdt_header 구조체

scripts/dtc/libfdt/fdt.h

struct fdt_header { 
        uint32_t magic;                  /* magic word FDT_MAGIC */
        uint32_t totalsize;              /* total size of DT block */
        uint32_t off_dt_struct;          /* offset to structure */
        uint32_t off_dt_strings;         /* offset to strings */
        uint32_t off_mem_rsvmap;         /* offset to memory reserve map */
        uint32_t version;                /* format version */
        uint32_t last_comp_version;      /* last compatible version */
                
        /* version 2 fields below */
        uint32_t boot_cpuid_phys;        /* Which physical CPU id we're
                                            booting on */
        /* version 3 fields below */
        uint32_t size_dt_strings;        /* size of the strings block */
                
        /* version 17 fields below */
        uint32_t size_dt_struct;         /* size of the structure block */
};

 

 

fdt_node_header 구조체

scripts/dtc/libfdt/fdt.h

struct fdt_node_header {
        uint32_t tag;
        char name[0];
};
  • tag
    • 1=FDT_BEGIN_NODE
    • 2=FDT_END_NODE
    • 3=FDT_PROP
    • 4=FDT_NOP
    • 9=FDT_END
  • name[0]
    • tag가 1번인 경우 노드명의 첫 글자

 

device_node 구조체

include/linux/of.h

struct device_node {
        const char *name;
        const char *type;
        phandle phandle;
        const char *full_name;
        struct fwnode_handle fwnode; 

        struct  property *properties;
        struct  property *deadprops;    /* removed properties */
        struct  device_node *parent;
        struct  device_node *child;
        struct  device_node *sibling;
        struct  kobject kobj;
        unsigned long _flags;
        void    *data;
#if defined(CONFIG_SPARC)
        const char *path_component_name;
        unsigned int unique_id; 
        struct of_irq_controller *irq_trans;
#endif
};
  • name
    • 주소 없는 compact 스타일 노드명이 담긴다.
    • 예) “”, “chosen”, “uart”
  • type
    • 디바이스 타입명이 담긴다.
    • 예) “memory”, “cpu”, “pci”, “ethernet-phy”, “network”
  •  phandle
    • 노드내 “phandle” 속성이 가리키는 노드
  • full_name
    • full path 노드명이 담긴다.
    • 예) “/”, “/chosen”, “/soc/uart@7e201000”
  • fwnode
    • 0=FWNODE_INVALID
    • 1=FWNODE_OF (초기값)
    • 2=FWNODE_ACPI
  • properties
    •  속성값이 있는 경우 property 노드를 가리키고 없으면 null
  • deadprops
  • parent
    • 부모 노드를 가리키고 자신이 루트 노드인 경우 null
  • child
    • 자식 노드들 중 첫 노드를 가리키고 없으면 null
  • sibling
    • 바로 다음 형재 노드를 가리키고 없으면 null
  • kboj
    • object 라이프 사이클을 관리
  • _flags
  • data
    • 4바이트로 표현 가능한 노드 값이 있거나 데이터 위치를 가리키고 없으면 null

 

property 구조체

include/linux/of.h

struct property {
        char    *name;
        int     length;
        void    *value;
        struct property *next;
        unsigned long _flags;
        unsigned int unique_id;
        struct bin_attribute attr;
};
  • name
    • 속성명
    • 예) “name”, “stdout-path”, “reg”
  • length
    • 속성 값 길이
  • value
    • 속성 값이 위치한 곳을 가리키고 없으면 null
  • next
    • 노드내의 다음 속성을 가리키고 없으면 null
  • _flags
  • unique_id
  • attr

 

참고

 

 

request_standard_resources()

<kernel v5.0>

ARM32용 표준 리소스 등록

2 개의 루트 리소스에 관련 표준 리소스를 등록한다.

  • 전역 iomem_resource에 System RAM을 등록하고 커널 코드와 커널 데이터 영역이 System RAM 영역에 포함되는 경우 System RAM의 child 리소스로 추가한다.
    • XIP 커널의 경우 커널 커드는 ROM에 포함되므로 System RAM의 child 리소스로 추가되지 않는다.
  • 전역 ioport_resource에는 lp0~lp2가 머신에 등록되어 있는 경우 리소스로 추가한다.

 

request_standard_resources-1

 

request_standard_resources() – ARM32

arch/arm/kernel/setup.c

static void __init request_standard_resources(const struct machine_desc *mdesc)
{
        struct memblock_region *region;
        struct resource *res;

        kernel_code.start   = virt_to_phys(_text);
        kernel_code.end     = virt_to_phys(__init_begin - 1);
        kernel_data.start   = virt_to_phys(_sdata);
        kernel_data.end     = virt_to_phys(_end - 1);

        for_each_memblock(memory, region) {
                phys_addr_t start = __pfn_to_phys(memblock_region_memory_base_pfn(region));
                phys_addr_t end = __pfn_to_phys(memblock_region_memory_end_pfn(region)) - 1;
                unsigned long boot_alias_start;

                /*
                 * Some systems have a special memory alias which is only
                 * used for booting.  We need to advertise this region to
                 * kexec-tools so they know where bootable RAM is located.
                 */
                boot_alias_start = phys_to_idmap(start);
                if (arm_has_idmap_alias() && boot_alias_start != IDMAP_INVALID_ADDR) {
                        res = memblock_alloc(sizeof(*res), SMP_CACHE_BYTES);
                        res->name = "System RAM (boot alias)";
                        res->start = boot_alias_start;
                        res->end = phys_to_idmap(end);
                        res->flags = IORESOURCE_MEM | IORESOURCE_BUSY;
                        request_resource(&iomem_resource, res);
                }

                res = memblock_alloc(sizeof(*res), SMP_CACHE_BYTES);
                res->name  = "System RAM";
                res->start = start;
                res->end = end;
                res->flags = IORESOURCE_SYSTEM_RAM | IORESOURCE_BUSY;

                request_resource(&iomem_resource, res);

                if (kernel_code.start >= res->start &&
                    kernel_code.end <= res->end)
                        request_resource(res, &kernel_code);
                if (kernel_data.start >= res->start &&
                    kernel_data.end <= res->end)
                        request_resource(res, &kernel_data);
        }

        if (mdesc->video_start) {
                video_ram.start = mdesc->video_start;
                video_ram.end   = mdesc->video_end;
                request_resource(&iomem_resource, &video_ram);
        }

        /*
         * Some machines don't have the possibility of ever
         * possessing lp0, lp1 or lp2
         */
        if (mdesc->reserve_lp0)
                request_resource(&ioport_resource, &lp0);
        if (mdesc->reserve_lp1)
                request_resource(&ioport_resource, &lp1);
        if (mdesc->reserve_lp2)
                request_resource(&ioport_resource, &lp2);
}

표준 리소스를 등록 요청한다.

  • 코드 라인 6~9에서 커널 코드와 커널 데이터 물리 주소를 구한다.
  • 코드 라인 11~29에서 memory memblock을 순회하며 특정 시스템에서 boot용 RAM이 있는 경우 resource 구조체를 할당하고 부팅용 시스템 RAM으로 iomem 리소스로 등록 요청한다.
  • 코드 라인 31~37에서 resource 구조체를 할당하고 시스템 RAM으로 iomem 리소스로 등록 요청한다.
  • 코드 라인 39~41에서 커널 코드영역이 System RAM 영역에 포함된 경우 커널 코드 영역을 System RAM의 child로 추가한다.
  • 코드 라인 42~44에서 커널 데이터영역이 System RAM 영역에 포함된 경우 커널 데이터 영역을 System RAM의 child로 추가한다.
  • 코드 라인 47~51에서 머신에 비디오 정보가 있는 경우 전역 iomem_resource에 비디오 램 리소스 정보를 추가한다.
  • 코드 라인 57~58에서 머신에 reserve_lp0 정보가 있는 경우 전역 ioport_resource에 lp0 리소스를 추가한다.
  • 코드 라인 59~60에서 머신에 reserve_lp1 정보가 있는 경우 전역 ioport_resource에 lp1 리소스를 추가한다.
  • 코드 라인 61~62에서 머신에 reserve_lp2 정보가 있는 경우 전역 ioport_resource에 lp2 리소스를 추가한다.

 

아래 그림은 루트 리소스 iomem_resource에 등록된 System RAM, Video RAM 등을 보여준다. System RAM 내부  Kernel code와 Kernel data도 존재한다. 만일 XIP 커널이라면 Kernel code는 존재하지 않을 것이다.

request_standard_resources-2a

아래 그림은 루트 리소스 ioport_resource에 등록된 lp0~lp2 리소스 등을 보여준다.

request_standard_resources-3a

 


ARM64용 표준 리소스 등록

request_standard_resources() – ARM64

arch/arm64/kernel/setup.c

static void __init request_standard_resources(void)
{
        struct memblock_region *region;
        struct resource *res;
        unsigned long i = 0;

        kernel_code.start   = __pa_symbol(_text);
        kernel_code.end     = __pa_symbol(__init_begin - 1);
        kernel_data.start   = __pa_symbol(_sdata);
        kernel_data.end     = __pa_symbol(_end - 1);

        num_standard_resources = memblock.memory.cnt;
        standard_resources = memblock_alloc_low(num_standard_resources *
                                                sizeof(*standard_resources),
                                                SMP_CACHE_BYTES);

        for_each_memblock(memory, region) {
                res = &standard_resources[i++];
                if (memblock_is_nomap(region)) {
                        res->name  = "reserved";
                        res->flags = IORESOURCE_MEM;
                } else {
                        res->name  = "System RAM";
                        res->flags = IORESOURCE_SYSTEM_RAM | IORESOURCE_BUSY;
                }
                res->start = __pfn_to_phys(memblock_region_memory_base_pfn(region));
                res->end = __pfn_to_phys(memblock_region_memory_end_pfn(region)) - 1;

                request_resource(&iomem_resource, res);

                if (kernel_code.start >= res->start &&
                    kernel_code.end <= res->end)
                        request_resource(res, &kernel_code);
                if (kernel_data.start >= res->start &&
                    kernel_data.end <= res->end)
                        request_resource(res, &kernel_data);
#ifdef CONFIG_KEXEC_CORE
                /* Userspace will find "Crash kernel" region in /proc/iomem. */
                if (crashk_res.end && crashk_res.start >= res->start &&
                    crashk_res.end <= res->end)
                        request_resource(res, &crashk_res);
#endif
        }
}

표준 리소스를 등록 요청한다.

  • 코드 라인 7~10에서 커널 코드와 커널 데이터 물리 주소를 구한다.
  • 코드 라인 12~15에서 memory memblock 수 만큼 리소스 배열을 할당받는다.
  • 코드 라인 17~29에서 memory memblock을 순회하며 nomap 플래그가 설정된 영역은 reserved 영역으로, 그 외의 경우는 시스템 RAM으로  iomem 리소스에 등록 요청한다.
  • 코드 라인 31~33에서 커널 코드영역이 System RAM 영역에 포함된 경우 커널 코드 영역을 System RAM의 child로 추가한다.
  • 코드 라인 34~36에서 커널 데이터영역이 System RAM 영역에 포함된 경우 커널 데이터 영역을 System RAM의 child로 추가한다.
  • 코드 라인 37~42에서 크래시 커널 영역이 설정된 경우 이 영역을 시스템 RAM의 child로 추가한다.

 


Standard Resource

kernel/resource.c

struct resource ioport_resource = {
        .name   = "PCI IO",
        .start  = 0,
        .end    = IO_SPACE_LIMIT,
        .flags  = IORESOURCE_IO,
};
EXPORT_SYMBOL(ioport_resource);

struct resource iomem_resource = {
        .name   = "PCI mem",
        .start  = 0,
        .end    = -1,
        .flags  = IORESOURCE_MEM,
};
EXPORT_SYMBOL(iomem_resource);
  • ioport_resource와 iomem_resource 루트 리소스이다.

 

ARM32용 리소스

arch/arm/kernel/setup.c – ARM32

/*
 * Standard memory resources
 */
static struct resource mem_res[] = {
        {
                .name = "Video RAM",
                .start = 0,
                .end = 0,
                .flags = IORESOURCE_MEM
        },
        {
                .name = "Kernel code",
                .start = 0,
                .end = 0,
                .flags = IORESOURCE_MEM
        },
        {
                .name = "Kernel data",
                .start = 0,
                .end = 0,
                .flags = IORESOURCE_MEM
        }
};

#define video_ram   mem_res[0]
#define kernel_code mem_res[1]
#define kernel_data mem_res[2]
  • 루트 리소스 iomem_resource에 등록될 Video RAM, Kernel code, Kernel data 리소스의 초기데이터이다.

 

arch/arm/kernel/setup.c – ARM32

static struct resource io_res[] = {
        {
                .name = "reserved",
                .start = 0x3bc,
                .end = 0x3be,
                .flags = IORESOURCE_IO | IORESOURCE_BUSY
        },
        {
                .name = "reserved",
                .start = 0x378,
                .end = 0x37f,
                .flags = IORESOURCE_IO | IORESOURCE_BUSY
        },
        {
                .name = "reserved",
                .start = 0x278,
                .end = 0x27f,
                .flags = IORESOURCE_IO | IORESOURCE_BUSY
        }
};

#define lp0 io_res[0]
#define lp1 io_res[1]
#define lp2 io_res[2]
  • 루트 리소스 ioport_resource 루트 리소스에 등록될 lp0~lp2 리소스의 초기데이터이다.

 

ARM64용 리소스

arch/arm64/kernel/setup.c – ARM64

/*
 * Standard memory resources
 */
static struct resource mem_res[] = {
        {
                .name = "Kernel code",
                .start = 0,
                .end = 0,
                .flags = IORESOURCE_SYSTEM_RAM
        },
        {
                .name = "Kernel data",
                .start = 0,
                .end = 0,
                .flags = IORESOURCE_SYSTEM_RAM
        }
};

 

include/linux/ioport.h

/* I/O resource extended types */
#define IORESOURCE_SYSTEM_RAM           (IORESOURCE_MEM|IORESOURCE_SYSRAM)

 

다음은 rock960 보드의 iomem 리소스를 보여준다.

$ cat /proc/iomem
00200000-f7ffffff : System RAM
  02080000-0303ffff : Kernel code
  03160000-033b6fff : Kernel data
f8000000-f9ffffff : axi-base
fa000000-fbdfffff : MEM
  fa000000-fa0fffff : PCI Bus 0000:01
    fa000000-fa003fff : 0000:01:00.0
      fa000000-fa003fff : nvme
fd000000-fdffffff : apb-base
fe310000-fe313fff : /dwmmc@fe310000
fe320000-fe323fff : /dwmmc@fe320000
fe330000-fe33ffff : mmc1
fe380000-fe39ffff : /usb@fe380000
fe3a0000-fe3bffff : /usb@fe3a0000
fe3c0000-fe3dffff : /usb@fe3c0000
fe3e0000-fe3fffff : /usb@fe3e0000
fe800000-fe807fff : /usb@fe800000/dwc3@fe800000
  fe800000-fe807fff : /usb@fe800000/dwc3@fe800000
fe80c100-fe8fffff : /usb@fe800000/dwc3@fe800000
fe900000-fe907fff : /usb@fe900000/dwc3@fe900000
  fe900000-fe907fff : /usb@fe900000/dwc3@fe900000
fe90c100-fe9fffff : /usb@fe900000/dwc3@fe900000
ff100000-ff1000ff : /saradc@ff100000
ff110000-ff110fff : /i2c@ff110000
ff120000-ff120fff : /i2c@ff120000
ff150000-ff150fff : /i2c@ff150000
ff180000-ff18001f : serial
ff1b0000-ff1b001f : serial
ff1c0000-ff1c0fff : /spi@ff1c0000
ff260000-ff2600ff : /tsadc@ff260000
ff370000-ff37001f : serial
ff3c0000-ff3c0fff : /i2c@ff3c0000
ff3d0000-ff3d0fff : /i2c@ff3d0000
ff650000-ff6507ff : /vpu_service@ff650000
ff650800-ff65083f : /iommu@ff650800
ff660000-ff6603ff : /rkvdec@ff660000
ff660480-ff6604bf : /iommu@ff660480
ff6604c0-ff6604ff : /iommu@ff660480
ff690000-ff69007f : /efuse@ff690000
ff6d0000-ff6d3fff : /amba/dma-controller@ff6d0000
  ff6d0000-ff6d3fff : /amba/dma-controller@ff6d0000
ff6e0000-ff6e3fff : /amba/dma-controller@ff6e0000
  ff6e0000-ff6e3fff : /amba/dma-controller@ff6e0000
ff720000-ff7200ff : /pinctrl/gpio0@ff720000
ff730000-ff7300ff : /pinctrl/gpio1@ff730000
ff780000-ff7800ff : /pinctrl/gpio2@ff780000
ff788000-ff7880ff : /pinctrl/gpio3@ff788000
ff790000-ff7900ff : /pinctrl/gpio4@ff790000
ff7c0000-ff7fffff : /phy@ff7c0000
ff800000-ff83ffff : /phy@ff800000
ff848000-ff8480ff : /watchdog@ff848000
ff870000-ff870fff : /spdif@ff870000
ff880000-ff880fff : /i2s@ff880000
ff8a0000-ff8a0fff : /i2s@ff8a0000
ff8f0000-ff8f05ff : regs
ff8f1c00-ff8f1dff : cabc_lut
ff8f2000-ff8f23ff : gamma_lut
ff8f3f00-ff8f3fff : /iommu@ff8f3f00
ff900000-ff9005ff : regs
ff901c00-ff901dff : cabc_lut
ff902000-ff902fff : gamma_lut
ff903f00-ff903fff : /iommu@ff903f00
ff914000-ff9140ff : /iommu@ff914000
ff915000-ff9150ff : /iommu@ff914000
ff924000-ff9240ff : /iommu@ff924000
ff925000-ff9250ff : /iommu@ff924000
ff940000-ff95ffff : /hdmi@ff940000
ff9a0000-ff9affff : ff9a0000.gpu

 

$ cat /proc/ioports
00000000-000fffff : I/O

 

다음은 qemu 에뮬레이션 상태의 iomem 리소스를 보여준다.

$ cat /proc/iomem
09000000-09000fff : pl011@9000000
  09000000-09000fff : pl011@9000000
09010000-09010fff : pl031@9010000
  09010000-09010fff : rtc-pl031
09030000-09030fff : pl061@9030000
  09030000-09030fff : pl061@9030000
0a003c00-0a003dff : a003c00.virtio_mmio
0a003e00-0a003fff : a003e00.virtio_mmio
10000000-3efeffff : pcie@10000000
3f000000-3fffffff : PCI ECAM
40000000-7fffffff : System RAM
  40080000-4103ffff : Kernel code
  41040000-4119ffff : reserved
  411a0000-413dcfff : Kernel data
  48000000-48008fff : reserved
  7ca00000-7cbfffff : reserved
  7cdef000-7dbfffff : reserved
  7ddc0000-7ddc0fff : reserved
  7ddc1000-7ddeefff : reserved
  7ddf1000-7ddf1fff : reserved
  7ddf2000-7ddf6fff : reserved
  7ddf7000-7ddfefff : reserved
  7ddff000-7fffffff : reserved
8000000000-ffffffffff : pcie@10000000

 

$ cat /proc/ioports
00000000-0000ffff : pcie@10000000

 

참고

__flush_dcache_page()

page 구조체가 가리키는 메모리 영역에 대한 d-cache를 flush 한다.

 

__flush_dcache_page-1

 

__flush_dcache_page()

arch/arm/mm/flush.c

void __flush_dcache_page(struct address_space *mapping, struct page *page)
{
        /*  
         * Writeback any data associated with the kernel mapping of this
         * page.  This ensures that data in the physical page is mutually
         * coherent with the kernels mapping.
         */
        if (!PageHighMem(page)) {
                size_t page_size = PAGE_SIZE << compound_order(page);
                __cpuc_flush_dcache_area(page_address(page), page_size);
        } else {
                unsigned long i;
                if (cache_is_vipt_nonaliasing()) {
                        for (i = 0; i < (1 << compound_order(page)); i++) {
                                void *addr = kmap_atomic(page + i); 
                                __cpuc_flush_dcache_area(addr, PAGE_SIZE);
                                kunmap_atomic(addr);
                        }   
                } else {
                        for (i = 0; i < (1 << compound_order(page)); i++) {
                                void *addr = kmap_high_get(page + i); 
                                if (addr) {
                                        __cpuc_flush_dcache_area(addr, PAGE_SIZE);
                                        kunmap_high(page + i); 
                                }   
                        }   
                }   
        }   

        /*
         * If this is a page cache page, and we have an aliasing VIPT cache,
         * we only need to do one flush - which would be at the relevant
         * userspace colour, which is congruent with page->index.
         */
        if (mapping && cache_is_vipt_aliasing())
                flush_pfn_alias(page_to_pfn(page),
                                page->index << PAGE_CACHE_SHIFT);
}

페이지가 highmem 영역이 아닌 경우 해당 페이지들 영역에 대한 d-cache를 flush 한다.

  • if (!PageHighMem(page)) {
    • 주어진 페이지가 highmem 영역이 아니면
  • size_t page_size = PAGE_SIZE << compound_order(page);
    • size는 PAGE_SIZE x 2^compund_order로 한다.
    • 예) PAGE_SIZE=4K, compound_order=9
      • size=2M
  • __cpuc_flush_dcache_area(page_address(page), page_size);
    • flush_dcache_area에 대한 각 아키텍처 종속 코드 함수를 호출하여 특정 영역 크기에 해당하는 d-cache를 flush 한다.
    • rpi2: v7_flush_kern_dcache_area() 함수를 호출한다.

페이지가 highmem 영역이면서 cache가 vipt non-aliasing 타입인 경우 해당 페이지들 영역을 루프를 돌며 각 페이지에 대해 fixmap 매핑한 후 d-cache를 flush 하고 다시 fixmap 매핑을 해지한다.

  •  if (cache_is_vipt_nonaliasing()) {
  • for (i = 0; i < (1 << compound_order(page)); i++) {
    • 2^compound_order 페이지 만큼 루프를 돈다.
  • void *addr = kmap_atomic(page + i);
    • fixmap 영역에 highmem 메모리 한 페이지를 매핑 한다.
    • 참고: Fixmap | 문c
  • __cpuc_flush_dcache_area(addr, PAGE_SIZE);
    • 주어진 1개 가상 주소 페이지 하나에 대해 d-cache flush를 수행한다.
  • kunmap_atomic(addr);
    • fixmap 영역에 매핑된 highmem 페이지를 해지한다.

페이지가 highmem 영역이면서 cache가 vipt non-aliasing 타입이 아닌 경우 해당 페이지들 영역을 루프를 돌며 각 페이지에 대해 이미 kmap 매핑된 가상 주소를 찾아 d-cache를 flush 하고 그 kmap 매핑을 해지한다.

  • for (i = 0; i < (1 << compound_order(page)); i++) {
    • 2^compound_order 페이지 만큼 루프를 돈다.
  • void *addr = kmap_high_get(page + i);
    • kmap 영역에 매핑된 highmem 메모리를 찾는다.
    • 참고: Kmap(Pkmap) | 문c
  • __cpuc_flush_dcache_area(addr, PAGE_SIZE);
    • 찾은 1개 가상 주소 페이지 하나에 대해 d-cache flush를 수행한다.
  • kunmap_high(page + i);
    • kmap 영역에 매핑된 highmem 페이지를 해지한다.

인수로 요청한 매핑이 있는 경우이면서 캐시 타입이 vipt aliasing인 경우 TLB를 flush 한다.

  • if (mapping && cache_is_vipt_aliasing())
    • mapping과 캐시 타입이 vipt aliasing인 경우
  • flush_pfn_alias(page_to_pfn(page), page->index << PAGE_CACHE_SHIFT);
    • 요청 페이지에 대한 TLB를 flush 한다.

 

v7_flush_kern_dcache_area()

arch/arm/mm/cache-v7.S

/*
 *      v7_flush_kern_dcache_area(void *addr, size_t size)
 *
 *      Ensure that the data held in the page kaddr is written back
 *      to the page in question.
 *
 *      - addr  - kernel address
 *      - size  - region size
 */
ENTRY(v7_flush_kern_dcache_area)
        dcache_line_size r2, r3
        add     r1, r0, r1
        sub     r3, r2, #1
        bic     r0, r0, r3
#ifdef CONFIG_ARM_ERRATA_764369
        ALT_SMP(W(dsb))
        ALT_UP(W(nop))
#endif
1:
        mcr     p15, 0, r0, c7, c14, 1          @ clean & invalidate D line / unified line
        add     r0, r0, r2
        cmp     r0, r1
        blo     1b  
        dsb     st  
        ret     lr  
ENDPROC(v7_flush_kern_dcache_area)
  • start(r0) ~ end(r1) 까지 캐시 라인 길이(바이트) 만큼 증가시키면서루프를 돌며 DCCIMVAC를 호출하여 해당 캐시 라인을 비운다.
    • DCCIMVAC(Data Cache Clean & Invalidate MVA to PoC)

 

dcache_line_size()

arch/arm/mm/proc-macros.S

/*
 * dcache_line_size - get the minimum D-cache line size from the CTR register
 * on ARMv7.
 */
        .macro  dcache_line_size, reg, tmp 
        mrc     p15, 0, \tmp, c0, c0, 1         @ read ctr 
        lsr     \tmp, \tmp, #16 
        and     \tmp, \tmp, #0xf                @ cache line size encoding
        mov     \reg, #4                        @ bytes per word
        mov     \reg, \reg, lsl \tmp            @ actual cache line size
        .endm

캐시 라인 워드 사이즈를 읽어온 후 4를 곱하여 reg 레지스터로 리턴한다.

  • CTR.DminLine: 캐시 라인 사이즈로 word 단위이다.

 

flush_pfn_alias()

arch/arm/mm/flush.c

static void flush_pfn_alias(unsigned long pfn, unsigned long vaddr)
{
        unsigned long to = FLUSH_ALIAS_START + (CACHE_COLOUR(vaddr) << PAGE_SHIFT);
        const int zero = 0;

        set_top_pte(to, pfn_pte(pfn, PAGE_KERNEL));

        asm(    "mcrr   p15, 0, %1, %0, c14\n"
        "       mcr     p15, 0, %2, c7, c10, 4"
            :   
            : "r" (to), "r" (to + PAGE_SIZE - 1), "r" (zero)
            : "cc");
}
  • unsigned long va = FLUSH_ALIAS_START + (CACHE_COLOUR(vaddr) << PAGE_SHIFT);
    • ARMv6 VIPT 캐시를 사용하는 경우 CACHE_COLOUR() 값은 0~3이 리턴된다.
      • 실제 캐시 way 단면 사이즈가 4K를 초과하는 경우 페이지 크기와 달라지면서 이에 대한 교정이 필요하다. 따라서 4K를 초과하는 2개의 비트에 대해 페이지별로 coloring을 하기 위해 사용한다.
      • 참고: Cache – VIPT 캐시 컬러링 | 문c
    • FLUSH_ALIAS_START
      • 0xffff_4000
    • va
      • coloring이 존재하지 않는 경우
        • 0xffff_4000
      • coloring이 존재하는 경우
        • 0xffff_4000, 0xffff_5000, 0xffff_6000, 0xffff_7000 중 하나
  • set_top_pte(to, pfn_pte(pfn, PAGE_KERNEL));
    • va 주소가 가리키는 hw pte 엔트리 값의 속성을 x로 설정한다.
  • asm( “mcrr p15, 0, %1, %0, c14\n”
    • block operation으로 %0 ~ %1까지 주소에 Clean & Invalidate Data Cache range 명령을 수행한다.
    • 참고: c7, Cache Operations Register -> Table 3.78
  • ”       mcr     p15, 0, %2, c7, c10, 4″
    • DSB

 

set_top_pte()

arch/arm/mm/mm.h

static inline void set_top_pte(unsigned long va, pte_t pte)
{
        pte_t *ptep = pte_offset_kernel(top_pmd, va);
        set_pte_ext(ptep, pte, 0); 
        local_flush_tlb_kernel_page(va);
}
  • pte_t *ptep = pte_offset_kernel(top_pmd, va);
    • 가장 최상단에 위치한 pmd 테이블에서 va 주소와 매치되는 pmd 엔트리를 통해 찾은 pte 엔트리 주소
  • set_pte_ext(ptep, pte, 0);
  • local_flush_tlb_kernel_page(va);
    • 요청 가상 주소 페이지에 대한 TLB flush를 수행한다.

 

참고

 

Memory Model -4- (APIs)

Memory Model -4- (APIs)

 

for_each_migratetype_order()

include/linux/mmzone.h

#define for_each_migratetype_order(order, type) \
        for (order = 0; order < MAX_ORDER; order++) \
                for (type = 0; type < MIGRATE_TYPES; type++)
  • buddy 메모리 할당자가 사용하는 MAX_ORDER(11)  수 만큼 루프를 돈다.
  • 메모리 hotplug에 대한 이주 플래그 관리를 담당하는 MIGRATE_TYPES 만큼 루프를 돈다.

 


존 및 노드 관련

is_highmem_idx()

include/linux/mmzone.h

static inline int is_highmem_idx(enum zone_type idx)
{
#ifdef CONFIG_HIGHMEM
        return (idx == ZONE_HIGHMEM ||
                (idx == ZONE_MOVABLE && movable_zone == ZONE_HIGHMEM)); 
#else
        return 0;
#endif
}

 

zone_idx()

include/linux/mmzone.h

/*
 * zone_idx() returns 0 for the ZONE_DMA zone, 1 for the ZONE_NORMAL zone, etc.
 */
#define zone_idx(zone)          ((zone) - (zone)->zone_pgdat->node_zones)
  • zone 인덱스 번호를 리턴한다.
    • 예) ZONE_DMA, ZONE_NORMAL을 사용하는 경우 0과 1이 리턴된다.
    • 예) ZONE_NORMAL만 사용하는 경우 0이 리턴된다.

 

set_page_links()

include/linux/mm.h

static inline void set_page_links(struct page *page, enum zone_type zone,
        unsigned long node, unsigned long pfn) 
{
        set_page_zone(page, zone);
        set_page_node(page, node);
#ifdef SECTION_IN_PAGE_FLAGS
        set_page_section(page, pfn_to_section_nr(pfn));
#endif
}
  • page->flags에 zone, node 및 section 정보를 설정한다.

 

set_page_zone()

include/linux/mm.h

static inline void set_page_zone(struct page *page, enum zone_type zone)
{
        page->flags &= ~(ZONES_MASK << ZONES_PGSHIFT);
        page->flags |= (zone & ZONES_MASK) << ZONES_PGSHIFT;
}
  • page->flags에 zone 정보를 설정한다.

 

set_page_node()

include/linux/mm.h

static inline void set_page_node(struct page *page, unsigned long node)
{
        page->flags &= ~(NODES_MASK << NODES_PGSHIFT);
        page->flags |= (node & NODES_MASK) << NODES_PGSHIFT;
}
  • page->flags에 노드 정보를 설정한다.

 

page_zone_id()

include/linux/mm.h

/*
 * The identification function is mainly used by the buddy allocator for
 * determining if two pages could be buddies. We are not really identifying
 * the zone since we could be using the section number id if we do not have
 * node id available in page flags.
 * We only guarantee that it will return the same value for two combinable
 * pages in a zone.
 */
static inline int page_zone_id(struct page *page)
{
        return (page->flags >> ZONEID_PGSHIFT) & ZONEID_MASK;
}

페이지에서 zone id를 추출하여 반환한다.

 


Sprsemem 섹션 관련

set_page_section()

include/linux/mm.h

#ifdef SECTION_IN_PAGE_FLAGS
static inline void set_page_section(struct page *page, unsigned long section)
{
        page->flags &= ~(SECTIONS_MASK << SECTIONS_PGSHIFT);
        page->flags |= (section & SECTIONS_MASK) << SECTIONS_PGSHIFT;
}
#endif
  • page->flags에 섹션 정보를 설정한다.

 

__pfn_to_section()

include/linux/mmzone.h

static inline struct mem_section *__pfn_to_section(unsigned long pfn) 
{
        return __nr_to_section(pfn_to_section_nr(pfn));
}

pfn 값에 대응하는 mem_section 구조체 정보를 리턴한다.

  •  pfn_to_section_nr()
    • pfn 값으로 섹션 번호를 알아온다.
  • __nr_to_section()
    • 섹션 번호로 mem_section 구조체 정보를 리턴한다.

 

pfn_to_section_nr()

include/linux/mmzone.h

#define pfn_to_section_nr(pfn) ((pfn) >> PFN_SECTION_SHIFT)
  • pfn의 섹션(Sparse) 인덱스를 리턴한다.
    • 예) Realview-PBX
      • 섹션 사이즈가 256M 단위(PFN_SECTION_SHIFT=16)이므로 섹션 번호는 0~15까지의 결과

 

__nr_to_section()

include/linux/mmzone.h

static inline struct mem_section *__nr_to_section(unsigned long nr)
{
        if (!mem_section[SECTION_NR_TO_ROOT(nr)])
                return NULL;
        return &mem_section[SECTION_NR_TO_ROOT(nr)][nr & SECTION_ROOT_MASK];
}
  • 섹션 번호로 mem_section 구조체 정보를 리턴한다.

 

SECTION_NR_TO_ROOT()

include/linux/mmzone.h

#define SECTION_NR_TO_ROOT(sec) ((sec) / SECTIONS_PER_ROOT)
  • 섹션 번호로 ROOT 번호를 리턴한다.

 

#ifdef CONFIG_SPARSEMEM_EXTREME
#define SECTIONS_PER_ROOT       (PAGE_SIZE / sizeof (struct mem_section))
#else
#define SECTIONS_PER_ROOT       1
#endif
  • ROOT 하나 당 섹션 수
    • PAGE_SIZE(4K)에 mem_section 구조체가 들어갈 수 있는 수

 

present_section_nr()

include/linux/mmzone.h

static inline int present_section_nr(unsigned long nr)
{
        return present_section(__nr_to_section(nr));
}

섹션 번호에 해당하는 mem_section이 준비되어 있는지 확인한다. 준비되어 있지 않은 경우 해당 섹션은 hole을 의미한다.

  • __nr_to_section()
    • 섹션 번호로 mem_section 구조체 정보를 알아온다.
  • present_section()
    • mem_section 구조체 정보에 섹션이 존재하는지 확인한다.

 

present_section()

include/linux/mmzone.h

static inline int present_section(struct mem_section *section)
{
        return (section && (section->section_mem_map & SECTION_MARKED_PRESENT));
}

mem_section 구조체 정보에 섹션이 존재하는지 확인한다.

  • SECTION_MARKED_PRESENT 식별 비트가 설정되어 있는지 확인한다.

 

__section_mem_map_addr()

include/linux/mmzone.h

static inline struct page *__section_mem_map_addr(struct mem_section *section)
{
        unsigned long map = section->section_mem_map;
        map &= SECTION_MAP_MASK;
        return (struct page *)map;
}

헤당 Sparse memory 섹션에 대한 mem_map 주소를 반환한다.

include/linux/mmzone.h

/*
 * We use the lower bits of the mem_map pointer to store
 * a little bit of information.  There should be at least
 * 3 bits here due to 32-bit alignment.
 */
#define SECTION_MARKED_PRESENT  (1UL<<0)
#define SECTION_HAS_MEM_MAP     (1UL<<1)
#define SECTION_MAP_LAST_BIT    (1UL<<2)
#define SECTION_MAP_MASK        (~(SECTION_MAP_LAST_BIT-1))
#define SECTION_NID_SHIFT       2

 


페이지의 참조 사용 및 사용 해제

get_page()

include/linux/mm.h

static inline void get_page(struct page *page)
{
        page = compound_head(page);
        /*
         * Getting a normal page or the head of a compound page
         * requires to already have an elevated page->_refcount.
         */
        VM_BUG_ON_PAGE(page_ref_count(page) <= 0, page);
        page_ref_inc(page);
}

참조 카운터를 1 증가시킨다.

 

get_page_unless_zero()

include/linux/mm.h

/*
 * Try to grab a ref unless the page has a refcount of zero, return false if
 * that is the case.
 * This can be called when MMU is off so it must not access
 * any of the virtual mappings.
 */
static inline int get_page_unless_zero(struct page *page)
{
        return page_ref_add_unless(page, 1, 0);
}

참조 카운터(p->_refcount)를 읽은 후 0 값과 다른 경우에 한해 증가시킨다. 결과 값이 0이 아니면 true를 반환한다.

 

put_page()

include/linux/mm.h

static inline void put_page(struct page *page)
{
        page = compound_head(page);

        /*
         * For devmap managed pages we need to catch refcount transition from
         * 2 to 1, when refcount reach one it means the page is free and we
         * need to inform the device driver through callback. See
         * include/linux/memremap.h and HMM for details.
         */
        if (put_devmap_managed_page(page))
                return;

        if (put_page_testzero(page))
                __put_page(page);
}

참조 카운터를 1 감소 시킨다. 만일 0이되면 페이지의 회수를 진행한다.

 

put_page_testzero()

include/linux/mm.h

/*
 * Methods to modify the page usage count.
 *
 * What counts for a page usage:
 * - cache mapping   (page->mapping)
 * - private data    (page->private)
 * - page mapped in a task's page tables, each mapping
 *   is counted separately
 *
 * Also, many kernel routines increase the page count before a critical
 * routine so they can be sure the page doesn't go away from under them.
 */

/*
 * Drop a ref, return true if the refcount fell to zero (the page has no users)
 */
static inline int put_page_testzero(struct page *page)
{
        VM_BUG_ON_PAGE(page_ref_count(page) == 0, page);
        return page_ref_dec_and_test(page);
}

페이지의 참조카운터를 감소시키고 0(사용완료)인지 확인하여 사용완료 여부를 반환한다.

  • 0=사용중, 1=사용완료(참조 _count가 0이된 경우)

 


page vs pfn 변환

PFN과 page 구조체 포인터와의 변환은 다음 2개의 API를 사용한다.

include/asm-generic/memory_model.h

#define page_to_pfn __page_to_pfn
#define pfn_to_page __pfn_to_page
  • page_to_pfn()
    • page 구조체 포인터로 pfn 값을 알아온다.
  • pfn_to_page()
    • pfn 값으로 page 구조체 포인터를 알아온다.

 

다음과 같이 flat 및 sparse물리 메모리 모델에 따라 변환 방법이 달라지며, sparse 물리 모델의 경우 vmemmap 사용여부에 따라 다시 2 가지로 나뉜다.

CONFIG_FLATMEM
#define __pfn_to_page(pfn)      (mem_map + ((pfn) - ARCH_PFN_OFFSET))
#define __page_to_pfn(page)     ((unsigned long)((page) - mem_map) + \
                                 ARCH_PFN_OFFSET)
  • __pfn_to_page()
    • ARCH_PFN_OFFSET은 물리 DRAM의 시작 PFN 값을 가리킨다.
    • mem_map[@pfn – 물리 DRAM 시작 PFN]

 

CONFIG_SPARSEMEM
/*
 * Note: section's mem_map is encoded to reflect its start_pfn.
 * section[i].section_mem_map == mem_map's address - start_pfn;
 */
#define __page_to_pfn(pg)                                       \
({      const struct page *__pg = (pg);                         \
        int __sec = page_to_section(__pg);                      \
        (unsigned long)(__pg - __section_mem_map_addr(__nr_to_section(__sec))); \
})      

#define __pfn_to_page(pfn)                              \
({      unsigned long __pfn = (pfn);                    \
        struct mem_section *__sec = __pfn_to_section(__pfn);    \
        __section_mem_map_addr(__sec) + __pfn;          \
})
  • __pfn_to_page()
    • pfn을 섹션 단위로 바꾼 후 mem_section[][]에 접근하여 섹션에 대한 mam_map[@pfn] 주소를 반환한다.

 

CONFIG_SPARSEMEM & CONFIG_SPARSEMEM_VMEMMAP
/* memmap is virtually contiguous.  */
#define __pfn_to_page(pfn)      (vmemmap + (pfn))
#define __page_to_pfn(page)     (unsigned long)((page) - vmemmap)
  • __pfn_to_page()
    • = mem_map[@pfn]
    • vmemmap = mem_map[0]의 가상 주소가 저장되어 있다.

 


페이지 플래그

include/linux/page-flags.h

/*
 * Various page->flags bits:
 *
 * PG_reserved is set for special pages. The "struct page" of such a page
 * should in general not be touched (e.g. set dirty) except by its owner.
 * Pages marked as PG_reserved include:
 * - Pages part of the kernel image (including vDSO) and similar (e.g. BIOS,
 *   initrd, HW tables)
 * - Pages reserved or allocated early during boot (before the page allocator
 *   was initialized). This includes (depending on the architecture) the
 *   initial vmemmap, initial page tables, crashkernel, elfcorehdr, and much
 *   much more. Once (if ever) freed, PG_reserved is cleared and they will
 *   be given to the page allocator.
 * - Pages falling into physical memory gaps - not IORESOURCE_SYSRAM. Trying
 *   to read/write these pages might end badly. Don't touch!
 * - The zero page(s)
 * - Pages not added to the page allocator when onlining a section because
 *   they were excluded via the online_page_callback() or because they are
 *   PG_hwpoison.
 * - Pages allocated in the context of kexec/kdump (loaded kernel image,
 *   control pages, vmcoreinfo)
 * - MMIO/DMA pages. Some architectures don't allow to ioremap pages that are
 *   not marked PG_reserved (as they might be in use by somebody else who does
 *   not respect the caching strategy).
 * - Pages part of an offline section (struct pages of offline sections should
 *   not be trusted as they will be initialized when first onlined).
 * - MCA pages on ia64
 * - Pages holding CPU notes for POWER Firmware Assisted Dump
 * - Device memory (e.g. PMEM, DAX, HMM)
 * Some PG_reserved pages will be excluded from the hibernation image.
 * PG_reserved does in general not hinder anybody from dumping or swapping
 * and is no longer required for remap_pfn_range(). ioremap might require it.
 * Consequently, PG_reserved for a page mapped into user space can indicate
 * the zero page, the vDSO, MMIO pages or device memory.
 *
 * The PG_private bitflag is set on pagecache pages if they contain filesystem
 * specific data (which is normally at page->private). It can be used by
 * private allocations for its own usage.
 *
 * During initiation of disk I/O, PG_locked is set. This bit is set before I/O
 * and cleared when writeback _starts_ or when read _completes_. PG_writeback
 * is set before writeback starts and cleared when it finishes.
 *
 * PG_locked also pins a page in pagecache, and blocks truncation of the file
 * while it is held.
 *
 * page_waitqueue(page) is a wait queue of all tasks waiting for the page
 * to become unlocked.
 *
 * PG_swapbacked is set when a page uses swap as a backing storage.  This are
 * usually PageAnon or shmem pages but please note that even anonymous pages
 * might lose their PG_swapbacked flag when they simply can be dropped (e.g. as
 * a result of MADV_FREE).
 *
 * PG_uptodate tells whether the page's contents is valid.  When a read
 * completes, the page becomes uptodate, unless a disk I/O error happened.
 *
 * PG_referenced, PG_reclaim are used for page reclaim for anonymous and
 * file-backed pagecache (see mm/vmscan.c).
 *
 * PG_error is set to indicate that an I/O error occurred on this page.
 *
 * PG_arch_1 is an architecture specific page state bit.  The generic code
 * guarantees that this bit is cleared for a page when it first is entered into
 * the page cache.
 *
 * PG_hwpoison indicates that a page got corrupted in hardware and contains
 * data with incorrect ECC bits that triggered a machine check. Accessing is
 * not safe since it may cause another machine check. Don't touch!
 */
/*
 * Don't use the pageflags directly.  Use the PageFoo macros.
 *
 * The page flags field is split into two parts, the main flags area
 * which extends from the low bits upwards, and the fields area which
 * extends from the high bits downwards.
 *
 *  | FIELD | ... | FLAGS |
 *  N-1           ^       0
 *               (NR_PAGEFLAGS)
 *
 * The fields area is reserved for fields mapping zone, node (for NUMA) and
 * SPARSEMEM section (for variants of SPARSEMEM that require section ids like
 * SPARSEMEM_EXTREME with !SPARSEMEM_VMEMMAP).
enum pageflags {
        PG_locked,              /* Page is locked. Don't touch. */
        PG_referenced,
        PG_uptodate,
        PG_dirty,
        PG_lru,
        PG_active,
        PG_workingset,
        PG_waiters,             /* Page has waiters, check its waitqueue. Must be bit #7 and in the same byte as "PG_locked" */
        PG_error,
        PG_slab,
        PG_owner_priv_1,        /* Owner use. If pagecache, fs may use*/
        PG_arch_1,
        PG_reserved,
        PG_private,             /* If pagecache, has fs-private data */
        PG_private_2,           /* If pagecache, has fs aux data */
        PG_writeback,           /* Page is under writeback */
        PG_head,                /* A head page */
        PG_mappedtodisk,        /* Has blocks allocated on-disk */
        PG_reclaim,             /* To be reclaimed asap */
        PG_swapbacked,          /* Page is backed by RAM/swap */
        PG_unevictable,         /* Page is "unevictable"  */
#ifdef CONFIG_MMU
        PG_mlocked,             /* Page is vma mlocked */
#endif
#ifdef CONFIG_ARCH_USES_PG_UNCACHED
        PG_uncached,            /* Page has been mapped as uncached */
#endif
#ifdef CONFIG_MEMORY_FAILURE
        PG_hwpoison,            /* hardware poisoned page. Don't touch */
#endif
#if defined(CONFIG_PAGE_IDLE_FLAG) && defined(CONFIG_64BIT)
        PG_young,
        PG_idle,
#endif
#ifdef CONFIG_64BIT
        PG_arch_2,
#endif
#ifdef CONFIG_KASAN_HW_TAGS
        PG_skip_kasan_poison,
#endif
        __NR_PAGEFLAGS,

        /* Filesystems */
        PG_checked = PG_owner_priv_1,

        /* SwapBacked */
        PG_swapcache = PG_owner_priv_1, /* Swap page: swp_entry_t in private */

        /* Two page bits are conscripted by FS-Cache to maintain local caching
         * state.  These bits are set on pages belonging to the netfs's inodes
         * when those inodes are being locally cached.
         */
        PG_fscache = PG_private_2,      /* page backed by cache */

        /* XEN */
        /* Pinned in Xen as a read-only pagetable page. */
        PG_pinned = PG_owner_priv_1,
        /* Pinned as part of domain save (see xen_mm_pin_all()). */
        PG_savepinned = PG_dirty,
        /* Has a grant mapping of another (foreign) domain's page. */
        PG_foreign = PG_owner_priv_1,
        /* Remapped by swiotlb-xen. */
        PG_xen_remapped = PG_owner_priv_1,

        /* SLOB */
        PG_slob_free = PG_private,

        /* Compound pages. Stored in first tail page's flags */
        PG_double_map = PG_workingset,

#ifdef CONFIG_MEMORY_FAILURE
        /*
         * Compound pages. Stored in first tail page's flags.
         * Indicates that at least one subpage is hwpoisoned in the
         * THP.
         */
        PG_has_hwpoisoned = PG_mappedtodisk,
#endif

        /* non-lru isolated movable page */
        PG_isolated = PG_reclaim,

        /* Only valid for buddy pages. Used to track pages that are reported */
        PG_reported = PG_uptodate,
};

 

page->flags에 기록되는 추가 정보

linux/page-flags-layout.h

/*
 * page->flags layout:
 *
 * There are five possibilities for how page->flags get laid out.  The first
 * pair is for the normal case without sparsemem. The second pair is for
 * sparsemem when there is plenty of space for node and section information.
 * The last is when there is insufficient space in page->flags and a separate
 * lookup is necessary.
 *
 * No sparsemem or sparsemem vmemmap: |       NODE     | ZONE |             ... | FLAGS |
 *      " plus space for last_cpupid: |       NODE     | ZONE | LAST_CPUPID ... | FLAGS |
 * classic sparse with space for node:| SECTION | NODE | ZONE |             ... | FLAGS |
 *      " plus space for last_cpupid: | SECTION | NODE | ZONE | LAST_CPUPID ... | FLAGS |
 * classic sparse no space for node:  | SECTION |     ZONE    | ... | FLAGS |
 */

커널 설정에 따라 page->flags에 플래그들 이외에 SECTION, NODE, ZONE 및 LAST_CPUPID 정보 등이 기록된다.

 


Reserved 플래그(예)

PageReserved(), SetPageReserved(), ClearPageReserved(), __ClearPageReserved()

include/linux/page-flags.h

PAGEFLAG(Reserved, reserved) __CLEARPAGEFLAG(Reserved, reserved)
  • PageReserved(), SetPageReserved(), ClearPageReserved() 및 __ClearPageReserved() static inline 함수가 만들어진다.

 

#define PAGEFLAG(uname, lname) TESTPAGEFLAG(uname, lname)               \
        SETPAGEFLAG(uname, lname) CLEARPAGEFLAG(uname, lname)
  • 아래 매크로를 사용하여 PageXXX(), SetPageXXX() 및 ClearPageXXX() static inline 함수가 만들어진다.

 

/*
 * Macros to create function definitions for page flags
 */
#define TESTPAGEFLAG(uname, lname)                                      \
static inline int Page##uname(const struct page *page)                  \
                        { return test_bit(PG_##lname, &page->flags); }

#define SETPAGEFLAG(uname, lname)                                       \
static inline void SetPage##uname(struct page *page)                    \
                        { set_bit(PG_##lname, &page->flags); }

#define CLEARPAGEFLAG(uname, lname)                                     \
static inline void ClearPage##uname(struct page *page)                  \
                        { clear_bit(PG_##lname, &page->flags); }

 

#define __CLEARPAGEFLAG(uname, lname)                                   \
static inline void __ClearPage##uname(struct page *page)                \
                        { __clear_bit(PG_##lname, &page->flags); }
  • test_bit()
    • &page->flags의 PG_xxxxx 번호 비트가 set되었는지 여부를 알아온다.
  • set_bit()
    • &page->flags의 PG_xxxxx 번호 비트를 atomic하게 set 한다.
  • clear_bit()
    • &page->flags의 PG_xxxxx 번호 비트를 atomic하게 clear 한다.
  • __clear_bit()
    • &page->flags의 PG_xxxxx 번호 비트를 clear 한다. (non-atomic)

 


일부 플래그의 재편성 (p->page_type)

아래 4개의 PG_buddy, PG_ballon, PG_kmemcg, PG_table 플래그는 p->_mapcount와 같이 사용하는 것으로 바뀌었고, 다시 유니언 선언하여 공유된 p->page_type을 사용한다.

  • 최초 p->flags에서 관리되던 플래그들이 p->_mapcount로 분리되었었다.
  •  후 새 커널에서는 p->_mapcount 대신 유니온으로 공유된 p->page_type를 사용한다. 단  p->_mapcount의 초기 값이 -1(0xffff_ffff)이므로 비트의 설정과 해제는 반대로 사용한다.
    • 예) Set Buddy
      • old 커널: p->_mapcount = PAGE_BUDDY_MAPCOUNT_VALUE(-128)
      • new 커널: p->page_type &= ~0x80
    • 예) Clear Buddy
      • p->_mapcount = PAGE_BUDDY_MAPCOUNT_VALUE(-1)
      • new 커널: p->page_type |= 0x80
    • 참고: mm: split page_type out from _mapcount

 

include/linux/page-flags.h

/*
 * PageBuddy() indicates that the page is free and in the buddy system
 * (see mm/page_alloc.c).
 */
PAGE_TYPE_OPS(Buddy, buddy)

/*
 * PageBalloon() is true for pages that are on the balloon page list
 * (see mm/balloon_compaction.c).
 */
PAGE_TYPE_OPS(Balloon, balloon)

/*
 * If kmemcg is enabled, the buddy allocator will set PageKmemcg() on
 * pages allocated with __GFP_ACCOUNT. It gets cleared on page free.
 */
PAGE_TYPE_OPS(Kmemcg, kmemcg)

/*
 * Marks pages in use as page tables.
 */
PAGE_TYPE_OPS(Table, table)

 

/*
 * For pages that are never mapped to userspace (and aren't PageSlab),
 * page_type may be used.  Because it is initialised to -1, we invert the
 * sense of the bit, so __SetPageFoo *clears* the bit used for PageFoo, and
 * __ClearPageFoo *sets* the bit used for PageFoo.  We reserve a few high and
 * low bits so that an underflow or overflow of page_mapcount() won't be
 * mistaken for a page type value.
 */
#define PAGE_TYPE_BASE  0xf0000000
/* Reserve              0x0000007f to catch underflows of page_mapcount */
#define PAGE_MAPCOUNT_RESERVE   -128
#define PG_buddy        0x00000080
#define PG_balloon      0x00000100
#define PG_kmemcg       0x00000200
#define PG_table        0x00000400

#define PageType(page, flag)                                            \
        ((page->page_type & (PAGE_TYPE_BASE | flag)) == PAGE_TYPE_BASE)

static inline int page_has_type(struct page *page)
{
        return (int)page->page_type < PAGE_MAPCOUNT_RESERVE;
}

#define PAGE_TYPE_OPS(uname, lname)                                     \
static __always_inline int Page##uname(struct page *page)               \
{                                                                       \
        return PageType(page, PG_##lname);                              \
}                                                                       \
static __always_inline void __SetPage##uname(struct page *page)         \
{                                                                       \
        VM_BUG_ON_PAGE(!PageType(page, 0), page);                       \
        page->page_type &= ~PG_##lname;                                 \
}                                                                       \
static __always_inline void __ClearPage##uname(struct page *page)       \
{                                                                       \
        VM_BUG_ON_PAGE(!Page##uname(page), page);                       \
        page->page_type |= PG_##lname;                                  \
}

위의 매크로를 통해 PageBuddy(), __SetPageBuddy(), __ClearPageBuddy() 등의 인라인 함수가 생성된다.

 

 


페이지 블럭 관련

set_pageblock_flags_group()

linux/pageblock-flags.h

#define set_pageblock_flags_group(page, flags, start_bitidx, end_bitidx) \
        set_pfnblock_flags_mask(page, flags, page_to_pfn(page),         \
                        end_bitidx,                                     \
                        (1 << (end_bitidx - start_bitidx + 1)) - 1)

 

set_pfnblock_flags_mask()

mm/page_alloc.c

/**
 * set_pfnblock_flags_mask - Set the requested group of flags for a pageblock_nr_pages block of pages
 * @page: The page within the block of interest
 * @flags: The flags to set
 * @pfn: The target page frame number
 * @end_bitidx: The last bit of interest
 * @mask: mask of bits that the caller is interested in
 */
void set_pfnblock_flags_mask(struct page *page, unsigned long flags,
                                        unsigned long pfn,
                                        unsigned long end_bitidx,
                                        unsigned long mask)
{
        struct zone *zone;      
        unsigned long *bitmap;
        unsigned long bitidx, word_bitidx;
        unsigned long old_word, word;

        BUILD_BUG_ON(NR_PAGEBLOCK_BITS != 4);

        zone = page_zone(page);
        bitmap = get_pageblock_bitmap(zone, pfn);
        bitidx = pfn_to_bitidx(zone, pfn);
        word_bitidx = bitidx / BITS_PER_LONG;
        bitidx &= (BITS_PER_LONG-1);

        VM_BUG_ON_PAGE(!zone_spans_pfn(zone, pfn), page);

        bitidx += end_bitidx;
        mask <<= (BITS_PER_LONG - bitidx - 1);
        flags <<= (BITS_PER_LONG - bitidx - 1); 

        word = ACCESS_ONCE(bitmap[word_bitidx]);
        for (;;) {
                old_word = cmpxchg(&bitmap[word_bitidx], word, (word & ~mask) | flags);
                if (word == old_word)
                        break;
                word = old_word;
        }            
}

 

get_pfnblock_flags_mask()

mm/page_alloc.c

/**
 * get_pfnblock_flags_mask - Return the requested group of flags for the pageblock_nr_pages block of pages              
 * @page: The page within the block of interest
 * @pfn: The target page frame number
 * @end_bitidx: The last bit of interest to retrieve
 * @mask: mask of bits that the caller is interested in
 *              
 * Return: pageblock_bits flags
 */
unsigned long get_pfnblock_flags_mask(struct page *page, unsigned long pfn,
                                        unsigned long end_bitidx,
                                        unsigned long mask)
{
        struct zone *zone;
        unsigned long *bitmap;
        unsigned long bitidx, word_bitidx;
        unsigned long word;

        zone = page_zone(page);
        bitmap = get_pageblock_bitmap(zone, pfn);
        bitidx = pfn_to_bitidx(zone, pfn);
        word_bitidx = bitidx / BITS_PER_LONG;
        bitidx &= (BITS_PER_LONG-1);

        word = bitmap[word_bitidx];
        bitidx += end_bitidx;
        return (word >> (BITS_PER_LONG - bitidx - 1)) & mask;
}

 

get_pageblock_bitmap()

mm/page_alloc.c

/* Return a pointer to the bitmap storing bits affecting a block of pages */
static inline unsigned long *get_pageblock_bitmap(struct zone *zone,
                                                        unsigned long pfn)
{
#ifdef CONFIG_SPARSEMEM
        return __pfn_to_section(pfn)->pageblock_flags;
#else
        return zone->pageblock_flags;
#endif /* CONFIG_SPARSEMEM */
}

@pfn이 포함된 페이지 블럭 비트맵을 반환한다. (usemap)

  • usemap에는 4비트로 표현된 mobility 플래그들이 저장된다.

 

pfn_to_bitidx()

mm/page_alloc.c

static inline int pfn_to_bitidx(struct zone *zone, unsigned long pfn)
{
#ifdef CONFIG_SPARSEMEM
        pfn &= (PAGES_PER_SECTION-1);
        return (pfn >> pageblock_order) * NR_PAGEBLOCK_BITS;
#else
        pfn = pfn - round_down(zone->zone_start_pfn, pageblock_nr_pages);
        return (pfn >> pageblock_order) * NR_PAGEBLOCK_BITS;
#endif /* CONFIG_SPARSEMEM */
}

pfn에 대한 pageblock에서 비트 인덱스를 반환한다.

 

SECTION_BLOCKFLAGS_BITS

include/linux/mmzone.h

#define SECTION_BLOCKFLAGS_BITS \
        ((1UL << (PFN_SECTION_SHIFT - pageblock_order)) * NR_PAGEBLOCK_BITS)

섹션 당 pageblock 비트 수

  • NR_PAGEBLOCK_BITS
    • pageblock에 대해 필요한 비트 수=4
  • PFN_SECTION_SHIFT
    • 섹션 길이 표현에 필요한 비트 수 – 페이지 길이 표현에 필요한 비트 수를 뺀 값
      • arm64: 섹션 길이=27(128M 표현) bits – 12(4KB 표현) bits = 15
  • 예) arm64에서 섹션 크기=128M, pageblock_order=9인 경우
    • SECTION_BLOCKFLAGS_BITS  = 2^(15-9) * 4 bits = 256 bits

 

참고