<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( ) 함수를 호출한다.
- ARM32도 커널 v4.5-rc1 부터 early ioremap을 지원한다.
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 매핑 속성을 가진다.
- ARM32
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
- 커널 메모리 속성과 동일하다.
- ARM32
__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에 매핑한다.
- 커널 v4.1-rc1 부터 부팅 후에도 early_ioremap()을 호출 가능하도록 지원한다. 그 전 까지는 BUG()를 호출하였다.
early_ioremap() API 제공 만료
early_ioremap_reset()
mm/early_ioremap.c
void __init early_ioremap_reset(void) { early_ioremap_shutdown(); after_paging_init = 1; }
정규 페이징이 준비되었으므로 early_ioremap() API를 더 이상 사용하지 말고 정규 ioremap()을 사용해야 하는 시점이다.
- 아키텍처에 따라 이 함수 진행 후에 early_ioremap() 함수가 호출될 때 BUG() 발생토록 한다.
- x86과 arm64의 경우에는 계속 early_ioremap() 함수의 사용을 허용한다.
기타 API
- early_memremap_ro()
- early_memremap()과 동일하나 read only 속성이 추가된다.
- early_memremap_prot()
- early_memremap()과 동일하나 @prot_val 속성을 요청하여 추가할 수 있다.
https://elixir.bootlin.com/linux/v4.11.11/source/arch/arm64/include/asm/fixmap.h#L71 의 코드를 보면
#define FIXADDR_START (FIXADDR_TOP – FIXADDR_SIZE)
enum fixed_addresses {
FIX_HOLE,
…
FIX_BTMAP_END = __end_of_permanent_fixed_addresses,
FIX_BTMAP_BEGIN = FIX_BTMAP_END + TOTAL_FIX_BTMAPS – 1,
…
}; 입니다.
slot_virt[0]~[6] 레이아웃 순서와 FIX_BTMAP_END, FIX_BTMAP_BEGIN layout의 순서가 뒤집혀야 할것 같은데 확인부탁드립니다
enum값을 인텍스로 FIXADDR_TOP에서 ((x) << PAGE_SHIFT)) 해서 계산하는군요 .. 블로그의 그림이 맞는것 같습니다.
안녕하세요? 빠르게 확인하셨군요. 즐거운 하루 되시기 바랍니다. ^^