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를 수행한다.

 

참고