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 에 메모리 영역을 추가한다.
