arch 번호로 태그테이블에서 machine을 검색하여 machine_desc 구조체 포인터를 찾고 ATAG를 디바이스 트리 구조로 변경한다.
setup_machine_tags()
- for_each_machine_desc()
- __arch_info_begin ~ __arch_info_end 영역에 위치한 machine_desc 구조체 배열에서 머신 번호가 같은 경우를 찾는다.
- machine_desc 구조체 배열은 .arch.info.init 섹션에 위치한다.
- 만일 machine을 검색하여 찾지 못한 경우 machine table을 덤프하고 정지한다.
- CONFIG_DEPRECATED_PARAM_STRUCT
- ATAG 사용 하기 전에는 PARAM_STRUCT를 사용했다.
- convert_to_tag_list()
- 태그의 처음이 ATAG_CORE가 아니면 PARAM_STRUCT 방식이라고 판단하여 ATAG 구조로 변환한다.
- 처음 태그가 ATAG_CORE가 아닌 경우 “Warning: Neither atags nor dtb found” 경고 메시지를 출력하고 default 태그 구조체를 사용한다.
- fixup 콜백함수가 null이 아닌 경우 fixup 콜백 함수를 수행한다.
- 펌웨어에 문제가 있는 경우를 패치하기 위한 함수가 존재하는 경우 호출
- 예) mach-msm/board-msm7x30.c – msm7x30_fixup() 참고
- 태그가 ATAG_CORE 인 경우
- 물리 메모리 사이즈가 이미 존재하는 경우 태그 정보를 무시하기 위해 squash_mem_tags()를 호출하여 ATAG_MEM을 ATAG_NONE으로 변경한다.
- memblock_phys_mem_size = memblock.memory.total_size
- save_atags()
- 전역 변수 atags_copy 문자열 배열에 태그를 저장
- parse_tags()
- __tagtable_begin 부터 __tagtable_end 위치에 존재하는 태그 테이블에서 하나 씩 비교하여 동일한 태그인 경우 해당 태그의 parse 루틴을 호출한 후 리턴한다.
- 태그 테이블은 .taglist.init 섹션에 위치한다.
- parsing이 실패하면 “Ignoring unrecognised tag”라고 경고 출력한다.
- 각 태그에 대한 파싱 함수 목록
- ATAG_CORE: parse_tag_core()
- ATAG_MEM: parse_tag_mem32()
- ATAG_CMDLINE: parse_tag_cmdline
- ATAG_INITRD: parse_tag_initrd()
- ATAG_INITRD2, parse_tag_initrd2()
- ATAG_VIDEOTEXT: parse_tag_videotext()
- ATAG_RAMDIST: parse_tag_ramdisk()
- ATAG_SERAIL: parse_tag_serialnr()
- ATAG_REVISION: parse_tag_revision()
- __tagtable_begin 부터 __tagtable_end 위치에 존재하는 태그 테이블에서 하나 씩 비교하여 동일한 태그인 경우 해당 태그의 parse 루틴을 호출한 후 리턴한다.
- 물리 메모리 사이즈가 이미 존재하는 경우 태그 정보를 무시하기 위해 squash_mem_tags()를 호출하여 ATAG_MEM을 ATAG_NONE으로 변경한다.
- 마지막으로 전역 변수 boot_command_line에 default_cmd_line 값을 대입한다.
- default_cmd_line은 컴파일 시 초기 설정된 값이 있고 커널 파라메터 설정에 따라 parse_tag_cmdline()을 수행하고 난 후 변경될 수 있다.
const struct machine_desc * __init setup_machine_tags(phys_addr_t __atags_pointer, unsigned int machine_nr) { struct tag *tags = (struct tag *)&default_tags; const struct machine_desc *mdesc = NULL, *p; char *from = default_command_line; default_tags.mem.start = PHYS_OFFSET; /* * locate machine in the list of supported machines. */ for_each_machine_desc(p) if (machine_nr == p->nr) { pr_info("Machine: %s\n", p->name); mdesc = p; break; } if (!mdesc) { early_print("\nError: unrecognized/unsupported machine ID" " (r1 = 0x%08x).\n\n", machine_nr); dump_machine_table(); /* does not return */ } if (__atags_pointer) tags = phys_to_virt(__atags_pointer); else if (mdesc->atag_offset) tags = (void *)(PAGE_OFFSET + mdesc->atag_offset); #if defined(CONFIG_DEPRECATED_PARAM_STRUCT) /* * If we have the old style parameters, convert them to * a tag list. */ if (tags->hdr.tag != ATAG_CORE) convert_to_tag_list(tags); #endif if (tags->hdr.tag != ATAG_CORE) { early_print("Warning: Neither atags nor dtb found\n"); tags = (struct tag *)&default_tags; } if (mdesc->fixup) mdesc->fixup(tags, &from); if (tags->hdr.tag == ATAG_CORE) { if (memblock_phys_mem_size()) squash_mem_tags(tags); save_atags(tags); parse_tags(tags); } /* parse_early_param needs a boot_command_line */ strlcpy(boot_command_line, from, COMMAND_LINE_SIZE); return mdesc; }
ATAG용 Machine 정보
MACHINE_START()
- ATAG용 machine_desc 구조체 선언 매크로
- ATAG용에서는 nr로 검색하므로 nr 값이 중요하다.
- arm용 머신 번호는 arch/arm/tools/mach-types 화일을 참고한다.
- DTB용은 DT_MACHINE_START() 매크로를 사용하고 name으로 검색한다.
- ATAG용에서는 nr로 검색하므로 nr 값이 중요하다.
- __used를 사용하여 이 객체가 참조되지 않아도 컴파일러가 제거하지 않도록 한다.
- MACHINE_END()와 쌍으로 사용한다.
arch/arm/include/asm/mach/arch.h
/* * Set of macros to define architecture features. This is built into * a table by the linker. */ #define MACHINE_START(_type,_name) \ static const struct machine_desc __mach_desc_##_type \ __used \ __attribute__((__section__(".arch.info.init"))) = { \ .nr = MACH_TYPE_##_type, \ .name = _name, #define MACHINE_END \ };
machine_desc 구조체
arch/arm/include/asm/mach/arch.h
struct machine_desc { unsigned int nr; /* architecture number */ const char *name; /* architecture name */ unsigned long atag_offset; /* tagged list (relative) */ const char *const *dt_compat; /* array of device tree * 'compatible' strings */ unsigned int nr_irqs; /* number of IRQs */ #ifdef CONFIG_ZONE_DMA phys_addr_t dma_zone_size; /* size of DMA-able area */ #endif unsigned int video_start; /* start of video RAM */ unsigned int video_end; /* end of video RAM */ unsigned char reserve_lp0 :1; /* never has lp0 */ unsigned char reserve_lp1 :1; /* never has lp1 */ unsigned char reserve_lp2 :1; /* never has lp2 */ enum reboot_mode reboot_mode; /* default restart mode */ unsigned l2c_aux_val; /* L2 cache aux value */ unsigned l2c_aux_mask; /* L2 cache aux mask */ void (*l2c_write_sec)(unsigned long, unsigned); struct smp_operations *smp; /* SMP operations */ bool (*smp_init)(void); void (*fixup)(struct tag *, char **); void (*dt_fixup)(void); void (*init_meminfo)(void); void (*reserve)(void);/* reserve mem blocks */ void (*map_io)(void);/* IO mapping function */ void (*init_early)(void); void (*init_irq)(void); void (*init_time)(void); void (*init_machine)(void); void (*init_late)(void); #ifdef CONFIG_MULTI_IRQ_HANDLER void (*handle_irq)(struct pt_regs *); #endif void (*restart)(enum reboot_mode, const char *); };
라즈베리파이 1 & 2 MACHINE 구조체 선언
arch/arm/mach-bcm2709/bcm2709.c
static const char * const bcm2709_compat[] = { "brcm,bcm2709", "brcm,bcm2708", /* Could use bcm2708 in a pinch */ NULL }; MACHINE_START(BCM2709, "BCM2709") /* Maintainer: Broadcom Europe Ltd. */ #ifdef CONFIG_SMP .smp = smp_ops(bcm2709_smp_ops), #endif .map_io = bcm2709_map_io, .init_irq = bcm2709_init_irq, .init_time = bcm2709_timer_init, .init_machine = bcm2709_init, .init_early = bcm2709_init_early, .reserve = board_reserve, .restart = bcm2709_restart, .dt_compat = bcm2709_compat, MACHINE_END MACHINE_START(BCM2708, "BCM2709") /* Maintainer: Broadcom Europe Ltd. */ #ifdef CONFIG_SMP .smp = smp_ops(bcm2709_smp_ops), #endif .map_io = bcm2709_map_io, .init_irq = bcm2709_init_irq, .init_time = bcm2709_timer_init, .init_machine = bcm2709_init, .init_early = bcm2709_init_early, .reserve = board_reserve, .restart = bcm2709_restart, .dt_compat = bcm2709_compat, MACHINE_END
ATAG Parsing
tag 및 tagtable 구조체
arch/arm/include/uapi/asm/setup.h
struct tag { struct tag_header hdr; union { struct tag_core core; struct tag_mem32 mem; struct tag_videotext videotext; struct tag_ramdisk ramdisk; struct tag_initrd initrd; struct tag_serialnr serialnr; struct tag_revision revision; struct tag_videolfb videolfb; struct tag_cmdline cmdline; /* * Acorn specific */ struct tag_acorn acorn; /* * DC21285 specific */ struct tag_memclk memclk; } u; }; struct tagtable { __u32 tag; int (*parse)(const struct tag *); };
parse_tag()
- 태그 영역에 저장된 태그들 중 아키텍처 번호가 같은 태그들에 연결된 parse 콜백 함수를 호출한다.
/* * Scan the tag table for this tag, and call its parse function. * The tag table is built by the linker from all the __tagtable * declarations. */ static int __init parse_tag(const struct tag *tag) { extern struct tagtable __tagtable_begin, __tagtable_end; struct tagtable *t; for (t = &__tagtable_begin; t < &__tagtable_end; t++) if (tag->hdr.tag == t->tag) { t->parse(tag); break; } return t < &__tagtable_end; }
parse_tag_core()
- 전역 변수 root_mountflags에 루트 마운트 플래그 속성에서 MS_RDONLY를 제거하고 저장
- 전역 변수 ROOT_DEV에 디바이스 번호를 저장한다.
arch/arm/kernel/atags_parse.c
static int __init parse_tag_core(const struct tag *tag) { if (tag->hdr.size > 2) { if ((tag->u.core.flags & 1) == 0) root_mountflags &= ~MS_RDONLY; ROOT_DEV = old_decode_dev(tag->u.core.rootdev); } return 0; } __tagtable(ATAG_CORE, parse_tag_core);
parse_tag_mem32()
- arm_add_memory() 함수를 사용하여 메모리 영역을 추가한다.
static int __init parse_tag_mem32(const struct tag *tag) { return arm_add_memory(tag->u.mem.start, tag->u.mem.size); } __tagtable(ATAG_MEM, parse_tag_mem32);
parse_tag_videotext()
- screen_info 구조체에 파라메터 값들을 저장한다.
#if defined(CONFIG_VGA_CONSOLE) || defined(CONFIG_DUMMY_CONSOLE) static int __init parse_tag_videotext(const struct tag *tag) { screen_info.orig_x = tag->u.videotext.x; screen_info.orig_y = tag->u.videotext.y; screen_info.orig_video_page = tag->u.videotext.video_page; screen_info.orig_video_mode = tag->u.videotext.video_mode; screen_info.orig_video_cols = tag->u.videotext.video_cols; screen_info.orig_video_ega_bx = tag->u.videotext.video_ega_bx; screen_info.orig_video_lines = tag->u.videotext.video_lines; screen_info.orig_video_isVGA = tag->u.videotext.video_isvga; screen_info.orig_video_points = tag->u.videotext.video_points; return 0; } __tagtable(ATAG_VIDEOTEXT, parse_tag_videotext); #endif
parse_tag_ramdisk()
- 전역 변수 rd_image_start에 램디스크 시작 주소를 저장한다.
- 전역 변수 rd_doload와 rd_prompt에 플래그 상태를 저장한다.
- 전역 변수 rd_size에 램디스크 사이즈를 저장한다.
#ifdef CONFIG_BLK_DEV_RAM static int __init parse_tag_ramdisk(const struct tag *tag) { extern int rd_size, rd_image_start, rd_prompt, rd_doload; rd_image_start = tag->u.ramdisk.start; rd_doload = (tag->u.ramdisk.flags & 1) == 0; rd_prompt = (tag->u.ramdisk.flags & 2) == 0; if (tag->u.ramdisk.size) rd_size = tag->u.ramdisk.size; return 0; } __tagtable(ATAG_RAMDISK, parse_tag_ramdisk); #endif
parse_tag_serialnr()
- 전역 변수 system_serial_low와 system_serial_high에 시리얼 low 값과 high 값을 저장한다.
static int __init parse_tag_serialnr(const struct tag *tag) { system_serial_low = tag->u.serialnr.low; system_serial_high = tag->u.serialnr.high; return 0; } __tagtable(ATAG_SERIAL, parse_tag_serialnr);
parse_tag_revision()
- 전역 변수 system_rev에 리비전 정보를 저장한다.
static int __init parse_tag_revision(const struct tag *tag) { system_rev = tag->u.revision.rev; return 0; } __tagtable(ATAG_REVISION, parse_tag_revision);
parse_tag_cmdline()
- 다음 3가지 case에 대해 수행한다.
- CONFIG_CMDLINE_EXTEND
- default_command_line에 ATAG가 전달한 cmdline을 추가한다.
- CONFIG_CMDLINE_FORCE
- ATAG가 전달한 cmdline을 무시하고 default_command_line을 사용한다.
- cmdline 관련 옵션이 없는 경우
- default_command_line에 ATAG가 전달한 cmdline을 겹쳐 쓴다.
- CONFIG_CMDLINE_EXTEND
static int __init parse_tag_cmdline(const struct tag *tag) { #if defined(CONFIG_CMDLINE_EXTEND) strlcat(default_command_line, " ", COMMAND_LINE_SIZE); strlcat(default_command_line, tag->u.cmdline.cmdline, COMMAND_LINE_SIZE); #elif defined(CONFIG_CMDLINE_FORCE) pr_warn("Ignoring tag cmdline (using the default kernel command line)\n"); #else strlcpy(default_command_line, tag->u.cmdline.cmdline, COMMAND_LINE_SIZE); #endif return 0; } __tagtable(ATAG_CMDLINE, parse_tag_cmdline);
기타 함수
arm_add_memory()
arch/arm/kernel/setup.c
int __init arm_add_memory(u64 start, u64 size) { u64 aligned_start; /* * Ensure that start/size are aligned to a page boundary. * Size is rounded down, start is rounded up. */ aligned_start = PAGE_ALIGN(start); if (aligned_start > start + size) size = 0; else size -= aligned_start - start; #ifndef CONFIG_ARCH_PHYS_ADDR_T_64BIT if (aligned_start > ULONG_MAX) { pr_crit("Ignoring memory at 0x%08llx outside 32-bit physical address space\n", (long long)start); return -EINVAL; } if (aligned_start + size > ULONG_MAX) { pr_crit("Truncating memory at 0x%08llx to fit in 32-bit physical address space\n", (long long)start); /* * To ensure bank->start + bank->size is representable in * 32 bits, we use ULONG_MAX as the upper limit rather than 4GB. * This means we lose a page after masking. */ size = ULONG_MAX - aligned_start; } #endif if (aligned_start < PHYS_OFFSET) { if (aligned_start + size <= PHYS_OFFSET) { pr_info("Ignoring memory below PHYS_OFFSET: 0x%08llx-0x%08llx\n", aligned_start, aligned_start + size); return -EINVAL; } pr_info("Ignoring memory below PHYS_OFFSET: 0x%08llx-0x%08llx\n", aligned_start, (u64)PHYS_OFFSET); size -= PHYS_OFFSET - aligned_start; aligned_start = PHYS_OFFSET; } start = aligned_start; size = size & ~(phys_addr_t)(PAGE_SIZE - 1); /* * Check whether this memory region has non-zero size or * invalid node number. */ if (size == 0) return -EINVAL; memblock_add(start, size); return 0; }
- aligned_start = PAGE_ALIGN(start);
- start 주소에 대해 4K round up 한다.
- if (aligned_start > start + size)
- 4K round up 한 aligned_start 주소가 start + size를 초과하는 경우 size를 0으로 변경하고 그렇지 않은 경우 4K round up으로 인해 발생한 그 차이만큼 size에서 뺀다.
- 결국 4K align 되어 남는 하위 메모리는 버리게 된다.
- 예) arm_add_memory(0x1234_5678, 0x1000_0000)
- 물리 메모리 주소 0x1234_5678 부터 256M 크기의 메모리를 추가하라는 요청
- 수행 후 물리 메모리 주소 0x1234_5000 부터 (256M – 0x678) 크기의 메모리를 추가
- CONFIG_ARCH_PHYS_ADDR_T_64BIT
- LPAE 설정 시 사용된다.
- if (aligned_start + size > ULONG_MAX) {
- 추가할 메모리 영역이 32비트 주소의 끝을 초과하는 경우 size를 32비트 이내에 들어갈 수 있도록 조정한다.
- 예) arm_add_memory(0xf000_0000, 0x2000_0000)
- 물리 메모리 주소 0xf000_0000 부터 512M 크기의 메모리를 추가하라는 요청
- 수행 후 물리 메모리 주소 0xf000_0000 부터 0x0fff_ffff 크기의 메모리를 추가
- if (aligned_start < PHYS_OFFSET) {
- 요청한 시작 주소가 물리 메모리 시작 주소보다 작은 경우
- if (aligned_start + size <= PHYS_OFFSET) {
- size 까지 합친 요청 영역이 물리 메모리 시작 주소보다 작아 범위를 아예 벗어난 경우 에러를 경고하고 함수를 리턴한다.
- 요청한 구간이 물리 메모리 이하에서 시작하였다는 것을 경고 출력하고 물리 메모리 시작 주소 이하의 요청 메모리를 제거한 범위를 시작 주소와 사이즈를 재 조정한다.
- 예) arm_add_memory(0x1F00_0000, 0x1000_0000) 이 때 PHYS_OFFSET=0x2000_0000
- aligned_start = 0x2000_0000
- size = 0x0f00_000
- size = size & ~(phys_addr_t)(PAGE_SIZE – 1);
- 사이즈 또한 align 되어 있지 않으면 round down 하여 버린다.
- 예) arm_add_memory(0x2000_0000, 0x1234_5678)
- size = 0x1234_5000
- if (size == 0)
- 추가 할 사이즈가 0이면 함수를 빠져나간다.
- memblock_add(start, size);
- memory memblock 에 메모리 영역을 추가한다.