Early ioremap

<kernel v5.0>

Early IO 매핑

커널 부트업 과정에서 paging_init( ) 함수가 수행된 후에 메모리 및 입출력(I/O) 장치들이 가상 주소 공간에 정식으로 매핑되어 사용될 수 있다. 그런데 이러한 동작이 완료되기 전에 반드시 먼저 매핑되어 사용되어야 하는 경우를 위해 먼저(early) 처리할 수 있는 방법을 준비했다. 이러한 API를 사용하는 경우는 특정 시스템의 바이오스 정보 등에 먼저 접근해서 각종 메모리 및 디바이스의 정보를 가져와야 매핑 처리가 가능한 경우다. 주로 이용하는 경우는 다음과 같다.

  • ACPI 테이블에 접근해서 각종 디바이스의 설정 정보를 가져와야 하는 경우
  • EFI 테이블에 접근해서 각종 디바이스의 설정 정보를 가져와야 하는 경우
  • 일부 디바이스에서 특정 설정 정보 등을 읽어와야 하는 경우

 

이른(early) 시간에 io 매핑이 필요한 경우 256K씩 최대 7개의 fixmap 매핑 공간을 사용하여 매핑을 할 수 있게 한다.

 

다음 그림은 early_ioremap() 및 early_ioremap_init() 두 함수의 호출 관계를 보여준다.

 

Early IO 매핑 초기화

early_ioremap_init() – ARM32 & ARM64

ioremap을 사용하기 전에 부트 타임에 early I/O 매핑을 할 수 있도록 준비하는 과정을 자세히 알아본다. 다음 함수는 곧바로 early_ioremap_setup( ) 함수를 호출한다.

arch/arm64/mm/ioremap.c

/*
 * Must be called after early_fixmap_init
 */
void __init early_ioremap_init(void)
{
        early_ioremap_setup();
}

paging_init()이 완료되지 않아 정규 ioremap() 함수를 사용하지 못하지만 이른(early) 시간에 임시적으로 매핑이 필요한  경우를 위해 준비한다.

  • fixmap을 이용하므로 early_fixmap_init() 함수가 먼저 호출되어 초기화가 되어야한다.

 

early_ioremap_setup()

early I/O 매핑을 위해 fixmap 영역에 7개의 256K 가상 주소 영역을 준비하는 과정을 알아본다.

mm/early_ioremap.c

void __init early_ioremap_setup(void)
{
        int i;

        for (i = 0; i < FIX_BTMAPS_SLOTS; i++)
                if (WARN_ON(prev_map[i]))
                        break;

        for (i = 0; i < FIX_BTMAPS_SLOTS; i++)
                slot_virt[i] = __fix_to_virt(FIX_BTMAP_BEGIN - NR_FIX_BTMAPS*i);
}

7개 배열로 이루어진 slot_virt[] 배열에 fixmap에서 early ioremap 용도로 배정된 가상 주소를 배정한다.

  • 코드 라인 5~7에서 FIX_BTMAPS_SLOTS(7) 수만큼 루프를 돌며 전역 prev_map[ ] 배열에 값이 설정된 경우 경고 메시지를 출력한다
    • 7개 각 주소 공간은 최대 256K를 사용할 수 있다.
  • 코드 라인 9~10에서 FIX_BTMAPS_SLOTS(7) 수만큼 루프를 돌며 전역 slot_virt[ ] 배열에 fixmap의 BTMAP 가상 주소를 설정한다. 7개의 fixmap 엔트리가 있고, 각 엔트리들은 fixmap의 BTMAP에 해당하는 가상 주소들을 가리키는데 엔트리 간의 간격은 256K이다.
    • slot_virt[0] = FIX_BTMAP_BEGIN
    • slot_virt[1] = FIX_BTMAP_BEGIN + 256K

 

다음 그림은 early_ioremap_setup( ) 함수가 호출되어 early IO 매핑에 사용되는 fixmap의 BTMAPS 영역이 7개의 slot이 초기화되는 것을 보여준다.

 

주요 API

early_ioremap()

mm/early_ioremap.c

/* Remap an IO device */
void __init __iomem *
early_ioremap(resource_size_t phys_addr, unsigned long size)
{
        return __early_ioremap(phys_addr, size, FIXMAP_PAGE_IO);
}

물리 주소 @phys_addr부터 @size에 대응하는 io 디바이스들을 early 매핑한 후 매핑된 가상 주소를 반환한다.

  • 256K 크기를 최대 7개 까지 io 디바이스를 early 매핑한다.
  • FIXMAP_PAGE_IO
    • ARM32
      • L_PTE_YOUNG | L_PTE_PRESENT | L_PTE_XN | L_PTE_DIRTY | L_PTE_MT_DEV_SHARED | L_PTE_SHARED 송성을 가진다.
    •  ARM64
      • PROT_DEVICE_nGnRE 매핑 속성을 가진다.

 

early_memremap()

mm/early_ioremap.c

/* Remap memory */ 
void __init *
early_memremap(resource_size_t phys_addr, unsigned long size)
{
        return (__force void *)__early_ioremap(phys_addr, size,
                                               FIXMAP_PAGE_NORMAL);
}

물리 주소 @phys_addr부터 @size에 대응하는 메모리 영역을 early 매핑한 후 매핑된 가상 주소를 반환한다.

  • 256K 크기를 최대 7개 까지 메모리 영역을 early 매핑한다.
  • FIXMAP_PAGE_NORMAL
    • ARM32
      • L_PTE_XN 속성이 추가된 커널 메모리 속성이다.
    • ARM64
      • 커널 메모리 속성과 동일하다.

 

__early_ioremap()

mm/early_ioremap.c

static void __init __iomem *
__early_ioremap(resource_size_t phys_addr, unsigned long size, pgprot_t prot)
{
        unsigned long offset;
        resource_size_t last_addr;
        unsigned int nrpages;
        enum fixed_addresses idx;
        int i, slot;

        WARN_ON(system_state != SYSTEM_RUNNING);

        slot = -1;
        for (i = 0; i < FIX_BTMAPS_SLOTS; i++) {
                if (!prev_map[i]) {
                        slot = i;
                        break;
                }
        }

        if (WARN(slot < 0, "%s(%08llx, %08lx) not found slot\n",
                 __func__, (u64)phys_addr, size))
                return NULL;

        /* Don't allow wraparound or zero size */
        last_addr = phys_addr + size - 1;
        if (WARN_ON(!size || last_addr < phys_addr))
                return NULL;

        prev_size[slot] = size;
        /*
         * Mappings have to be page-aligned
         */
        offset = offset_in_page(phys_addr);
        phys_addr &= PAGE_MASK;
        size = PAGE_ALIGN(last_addr + 1) - phys_addr;

        /*
         * Mappings have to fit in the FIX_BTMAP area.
         */
        nrpages = size >> PAGE_SHIFT;
        if (WARN_ON(nrpages > NR_FIX_BTMAPS))
                return NULL;

        /*
         * Ok, go for it..
         */
        idx = FIX_BTMAP_BEGIN - NR_FIX_BTMAPS*slot;
        while (nrpages > 0) {
                if (after_paging_init)
                        __late_set_fixmap(idx, phys_addr, prot);
                else
                        __early_set_fixmap(idx, phys_addr, prot);
                phys_addr += PAGE_SIZE;
                --idx;
                --nrpages;
        }
        WARN(early_ioremap_debug, "%s(%08llx, %08lx) [%d] => %08lx + %08lx\n",
             __func__, (u64)phys_addr, size, slot, offset, slot_virt[slot]);

        prev_map[slot] = (void __iomem *)(offset + slot_virt[slot]);
        return prev_map[slot];
}

요청한 물리 주소와 사이즈를 fixmap의 BTMAPS 슬롯에 해당하는 가상주소에 prot 속성으로 매핑한다. 실패하는 경우 null을 반환한다. arm64에서 early 하게 io 매핑할 수 있는 최대 수는 FIX_BTMAPS_SLOTS(7)개이다.

  • fixmap용 BTMAPS 슬롯 관리 정보
    • slot_virt[]: 부트업 타임에 초기화된 각 슬롯에 해당하는 fixmap 가상 주소 (256K 단위)
    • prev_map[]: fixmap에 매핑된 가상 주소
    • prev_size[]: 매핑된 사이즈

 

  • 코드 라인 12-18에서 사용할 빈 슬롯을 선택한다. FIX_BTMAPS_SLOTS(7) 수만큼 루프를 돌며 전역 prev_map[ ] 값이 설정되지 않았다면 현재 카운터 i 값을 slot에 설정하여 slot을 선택한다.
  • 코드 라인 20~22에서 루프를 도는 동안 빈 슬롯을 못 찾은 경우 경고 메시지를 출력하고 NULL 값을 리턴한다
  • 코드 라인 25~27에서 크기가 0이거나 끝 주소가 시스템 주소 범위를 벗어나는 경우 경고 메시지를 출력하고 NULL 값을 리턴한다.
  • 코드 라인 29~34에서 prev_size[ ]에 요청 크기를 담아둔다. offset은 요청 물리 주소에서 페이지 단위의 나머지 offset만을 계산하고, 물리 주소는 페이지 단위로 내림 정렬한다
  • 코드 라인 35에서 페이지 단위로 정렬된 크기 값을 계산한다. 처음 요청한 물리 시작 주소 + 크기 값을 페이지 단위로 정렬하고, 페이지 단위로 내림 정렬한 물리 주소를 뺀다(최솟값은 페이지 단위가 된다).
  • 코드 라인 40~42에서 크기 값으로 페이지 수를 계산하고 한다. 크기가 256K를 초과한다면 NULL을 리턴한다
  • 코드 라인 48에서 계산된 페이지 수만큼 루프를 돈다.
  • 코드 라인 49~50에서 전역 after_paging_init이 설정된 경우에는 _ _late_clear_fixmap( ) 매크로 함수를 호출한다.
    • 예) arm64 및 x86의 경우 별도의 late 처리 함수 없이 그냥 __set_fixmap() 함수를 호출한다.
  • 코드 라인 51~52에서 반대로 after_paging_init이 설정되지 않은 경우에는 _ _early_set_fixmap( ) 매크로 함수를 호출한다.
    • 예) arm64의 경우 별도의 early 처리 함수 없이 그냥 __set_fixmap() 함수를 호출한다.
    • 예) x64의 경우 별도의 early 처리 함수인 __early_set_fixmap() 함수를 호출한다.
  • 코드 라인 60에서 prev_map[slot]에 슬롯에 해당하는 fixmap 가상 주소 + offset을 더해 설정한다

 

__early_set_fixmap()

arm64/include/asm/fixmap.h

#define __early_set_fixmap __set_fixmap

paging_init()이 완료되기 전에는 early_ioremap() 함수가 호출되는 경우에는 fixmap에 매핑한다.

 

__late_set_fixmap()

mm/early_ioremap.c

#define __late_set_fixmap __set_fixmap

paging_init()이 완료된 후 early_ioremap() 함수가 호출되는 경우에는 fixmap에 매핑한다.

 

기타 API

  • early_memremap_ro()
    • early_memremap()과 동일하나 read only 속성이 추가된다.
  • early_memremap_prot()
    • early_memremap()과 동일하나 @prot_val 속성을 요청하여 추가할 수 있다.

 

참고

댓글 남기기

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