Review and advice, large chunks of the ARM Linux kernel, all around good guy: Russell King
Review, advice and numerous clarifications.: Nicolas Pitre
Review and advice: Erik Mouw, Zwane Mwaikambo, Jeff Sutherland, Ralph Siemsen, Daniel Silverstone, Martin Michlmayr, Michael Stevens, Lesley Mitchell, Matthew Richardson
Review and referenced information (see bibliography): Wookey
Copyright © 2004 Vincent Sanders
While every precaution has been taken in the preparation of this article, the publisher assumes no responsibility for errors or omissions, or for damages resulting from the use of the information contained herein.
Revision History |
Revision 1.00 |
10th May 2004 |
VRS |
Initial Release.
|
Revision 1.10 |
4th June 2004 |
VRS |
Update example code to be more complete. |
Improve wording in places, changes suggested by Nicolas Pitre. |
Update Section 2, “Other bootloaders”. |
Update acknowledgements. |
|
차례
- 1. 이 문서에 관해서
- 2. 다른 부트로더
- 3. 개요
- 4. 시스템의 메모리 설정하기
5. 커널 이미지 로딩
6. 램디스크 로딩
7. 콘솔 초기화
- 8. 커널 파라미터
9. ARM 리눅스 머신 타입 얻기
- 10. 커널 시작
- A. 태그 레퍼런스
- B. 완전한 예제
- 참고문헌
요약
이 문서는 ARM 리눅스 커널을 시작하기 위한 부트 로더의 필요사항과 절차를 구현 가이드, 예제와 함께 명확히 간결한 용어로 정의합니다.
이 문서는 2.4.18 이후의 커널이 사용하는 “새로운” 부팅 절차를 설명합니다. 이전의 “struct” 메소드는 사용하지 마세요.
이 문서는 광범위한 종류의 출처(참고문헌 참고)와 저자들로부터의 정보를 포함하고 있습니다. 메인테이너들 또는 ARM 리눅스 메일링 리스트 상에 질문하기 전에 이 출처들을 찾아보는 것이 좋습니다. 이 분야의 대부분의 것들은 이전에 반복적으로 문의 및 답변되었고, 만약 여러분이 기본적인 조사도 하지 않았다면 여러분은 무시될 가능성이 큽니다.
추가적으로 이 문서에서 제공하는 가이드를 따르고, 커널을 시작하는 어셈블러의 모든 미묘한 차이를 이해하는 개발자가 될 필요는 없다는 것을 알아두세요. 또한 대부분의 부팅 문제는 여기 있는 코드와 상관없는 경우가 많았고, 많은 사람들이 말하는 코드는 매우 까다롭고 그 문제에 대해 간파할 수 없는 경우가 많았습니다.
새 부트 로더의 작성을 시작하기 전에 개발자들은 이미 존재하는 부트 로더가 적절하지는 않은지 한번 생각해보는 것이 좋습니다. 간단한 GPL 부트 로더에서부터 상용으로 제공하는 것까지 다양한 영역의 부트 로더 예제가 있습니다. 여기서 다음 작은 표를 제공합니다만 참고문헌에 있는 문서는 더 많은 솔루션을 제공합니다.
표 1. 부트 로더
이름 |
URL |
설명 |
Blob |
Blob bootloader |
SA11x0 (StrongARM)을 위한 GPL 부트 로더 |
Bootldr |
Bootldr |
GPL과 GPL 아닌 버전을 모두 제공하는 핸드헬드 디바이스에서 주로 사용하는 부트 로더 |
Redboot |
Redboot |
eCos 라이센스의 레드햇 부트로더 |
U-Boot |
U-Boot |
몇 몇 CPU를 지원하는 GPL universal bootloader |
ABLE |
ABLE bootloader |
편리한 기능의 상용 부트로더 |
ARM 리눅스는 시스템을 초기화하는 특정 머신에서만 동작하는 작은 코드 없이는 시작될 수 없습니다. 몇몇 부트로더는 더 많은 추가 기능을 제공하긴 하지만, ARM 리눅스는 이 작은 일을 하는 부트 로더가 필요합니다. 최소한의 필요사항은 다음과 같습니다:
시스템의 메모리 설정하기. |
정확한 메모리 주소에 커널 이미지 로딩. |
필요하면 정확한 메모리 주소에 램디스크 로딩. |
커널로 전달할 부트 파라미터 초기화. |
ARM 리눅스 머신 타입 얻기 |
적당한 레지스터 값으로 커널로 진입. |
부트로더는 일반적으로 이런 기본적인 작업들 뿐만 아니라 시리얼 또는 비디오 콘솔을 초기화해야 합니다. 사실 시리얼 포트는 거의 대부분의 설정을 위해 필수사항으로 생각됩니다.
위의 각 단계를 다음 절들에서 설명합니다.
부트 로더는 커널이 시스템의 임시 저장 장소로 사용할 모든 램을 찾아 초기화해야 합니다. 이는 특정 머신에서만 동작하는 방법(모든 램의 크기와 위치를 찾는 내부 알고리즘을 사용하거나, 머신에 달린 램에 관한 정보를 사용하거나, 또는 부트 로더 설계자 관점의 다른 방법 등)으로 수행됩니다.
어떠한 경우에도 모든 셋업은 부트 로더에 의해서 수행되어야 합니다. 커널은 시스템 안의 램의 셋업 또는 설정에 대해서 부트 로더가 제공하는 것 외에는 아무 것도 몰라야 합니다. 커널 안의 machine_fixup()의 사용은 이런 것을 하기 위한 것이 절대 아닙니다. 이 부분에서의 커널과 부트 로더 간의 책임 구별은 명확합니다.
물리 메모리 배치는 ATAG_MEM 파라미터를 사용해서 커널로 전달됩니다. 메모리가 가장 적게 분리되어 있는 것이 좋긴 하지만, 완전히 연속적일 필요는 없습니다. 여러 메모리 지역을 위한 여러개의 ATAG_MEM 블록도 상관없습니다. 연속된 물리 메모리 지역이 있다면 커널은 그 블록들을 합칠 겁니다.
부트 로더는 또한 linux/Documentation/kernel-parameters.txt 에 완벽히 문서화되어 있는 ‘mem=’ 파라미터를 사용해서 커널의 커맨드라인으로 메모리를 조작할 수도 있습니다.
커널 커맨드 라인 ‘mem=’ 는 정의된 메모리 영역의 물리 메모리 위치와 크기를 위해서 mem=<size>[KM][,@<phys_offset>] 문법을 가집니다. mem= 파라미터는 각기 다른 곳의 여러개의 연속되지 않은 메모리 블록을 나타내기 위해 여러 번 쓸 수 있습니다.
커널 이미지는 커널 빌드 과정에서 생성된 압축되지 않은 “Image” 파일 또는 압축된 zImage 파일입니다.
압축되지 않은 Image 파일은 식별 가능한 매직 넘버를 포함하고 있지 않아서 일반적으로 사용되지 않습니다. 압축된 zImage 형식이 거의 보편적으로 사용됩니다.
zImage 파일은 매직 넘버 뿐만 아니라 여러 장점을 갖고 있습니다. 일반적으로 이미지의 압축 해제는 외부 저장 장치를 읽는 것보다 빠릅니다. 압축 해제 실패의 결과로 이미지의 무결성도 보장할 수 있습니다. 커널은 일반적인 외부 압축 방법보다 더 나은 결과를 알 수 있는 그 내부 구조와 상태에 대한 정보를 가집니다.
zImage는 그 앞 부분 근처에 유용한 정보와 매직 넘버를 가집니다.
표 2. zImage head 코드 안의 유용한 정보
zImage안의 오프셋 |
값 |
설명 |
0x24 |
0x016F2818 |
ARM 리눅스 zImage를 나타내는데 사용하는 매직 넘버 |
0x28 |
시작 주소 |
zImage가 시작하는 주소 |
0x2C |
끝 주소 |
zImage가 끝나는 주소 |
시작과 끝 오프셋은 압축된 이미지의 크기(크기 = 끝 – 시작)를 결정하는데 사용됩니다. 몇몇 부트 로더들은 어떤 데이터가 커널 이미지에 추가된다면 이 정보를 사용합니다. 이 데이터는 일반적으로 초기 램디스크(initrd)를 위해 사용됩니다. 시작 주소는 일반적으로 zImage 코드가 위치에 무관하므로 0 입니다.
zImage 코드의 이용 가능한 주소 공간 안의 어떤 곳에도 로딩될 수 있는 위치에 무관한 코드(Position Independent Code – PIC) 입니다. 압축해제 된 후의 커널 크기는 최대 4 메가 바이트 입니다. 이는 bootpImage 타겟이 사용되었을 때의 initrd를 포함한 제약 사항입니다.
주의
zImage가 어느 곳이든 위치할 수 있다 하더라도 주의해야 합니다. 압축된 커널을 시작하는 것은 이미지를 압축해제할 추가적인 메모리를 필요로 합니다. 이 공간은 확실한 제약사항입니다.
zImage 압축해제 코드는 압축된 데이터를 덮어쓰지 않도록 확인할 겁니다. 커널이 어떤 충돌 같은 것을 발견한다면, 압축된 zImage 데이터 바로 뒤에 이미지를 압축 해제하고, 압축 해제 후에 커널을 재위치시킬 겁니다. zImage가 로딩된 메모리 영역은 그 뒤에 4 메가 바이트보다 큰 공간을 가져야 한다는 것, 즉 ZRELADDR로서 같은 4 메가 바이트 뱅크에 위치한 커널이 기대하는대로 동작하지 않는 것을 명확히 알 수 있습니다.
메모리 안에 어느 곳이든 zImage가 위치할 수 있지만 관례상 물리 메모리의 베이스에서 0x8000 (32K) 오프셋에 로딩됩니다. 이 것은 파라미터 블록이 일반적으로 위치하는 0x100 오프셋, Zero page exception 벡터와 페이지 테이블을 위한 공간을 남겨둡니다. 이 관례는 매우 일반적입니다.
램디스크는 많은 시스템상에서 일반적인 요구사항입니다. 그 것은 다른 드라이버나 설정에 접근할 필요없이 사용가능한 루트 파일 시스템을 제공합니다. 완전한 세부사항은 linux/Documentation/initrd.txt 에서 볼 수 있습니다.
ARM 리눅스가 램디스크를 얻는 방법은 두가지입니다. 첫번째는 빌드 할 때 램디스크를 가져와서 zImage 에 덧붙인 target bootpImage를 따로 빌드하는 겁니다. 이 방법은 부트 로더와의 상호 약속된 것이 없어도 된다는 장점이 있는 반면에 커널 빌드 과정에서 램디스크가 위치할 물리 주소를 (INITRD_PHYS 정의를 사용해서) 알려 주어야 한다는 단점이 4있습니다. 압축 해제된 커널과 initrd의 크기는 4 메가 바이트로 제한됩니다. 이 제한 사항 때문이 이 타겟은 실제로는 별로 사용되지 않습니다.
두번째 방법은 더 널리 사용되는 방법으로 부트로더가 어떤 저장 장치에서 램디스크를 받아 메모리의 지정된 위치에 위치시키는 것입니다. 이 위치는 커널에게 ATAG_INITRD2 와 ATAG_RAMDISK를 사용해서 전달됩니다.
관례적으로 initrd는 물리 주소 베이스의 8메가 바이트에 위치합니다. 위치된 곳이 어디 건간에 부팅 후에 램디스크를 실제 램디스크로 압축해제 하기 위한 충분한 메모리, 즉 zImage + 압축 해제된 zImage + initrd + 압축 해제된 램디스크를 위한 메모리가 있어야 합니다. 압축된 램디스크 메모리는 압축이 해제되고 나면 할당 해제될 겁니다. 램디스크의 위치 제한은 다음과 같습니다:
한 개의 메모리 영역안에 있어야 합니다(다른 ATAG_MEM 파라미터에 의해 정의된 다른 영역에 걸쳐 있으면 안됩니다). |
한 페이지 경계(일반적으로 4k)로 맞추어져 있어야 합니다. |
zImage head 코드가 커널을 압축해제 하기 위해서 사용하는 메모리와의 충돌이 없어야 합니다, 아니면 체크 없이 덮어쓰게 될 것입니다. |
콘솔은 시스템이 초기화될 때 커널이 무엇을 수행하는 지를 볼 수 있는 방법으로 매우 권장됩니다. 일반적인 경우인 비디오 프레임 버퍼 드라이버나 시리얼 드라이버 같은 적절한 드라이버로 어떤 입출력 장치도 사용될 수 있습니다. ARM 리눅스가 실행되는 시스템은 거의 항상 시리얼 콘솔 포트를 가지고 있습니다.
부트 로더는 부트로더 상에서 한 개의 시리얼 포트를 초기화하고 쓸 수 있도록 만들어야 합니다. 이 것은 그 포트를 사용하기 위한 하드웨어적인 파워 관리 같은 것들을 포함합니다. 이 것은 커널 시리얼 드라이버가 (일반적으로 디버깅 목적이나 타겟과의 통신 용도로 사용되는) 커널 콘솔로 사용되는 시리얼 포트를 자동으로 찾을 수 있도록 합니다.
아니면 부트로더는 linux/Documentation/kernel-parameters.txt에 설명된 Serial format 옵션과 포트를 지정하는 tagged list를 통해 이에 관련된 ‘console=’ 옵션을 커널로 전달할 수 있습니다.
부트로더는 커널로 수행한 셋업, 시스템의 메모리의 크기와 영역, 그리고 필요시에는 다른 여러 값들을 설명하는 파라미터를 넘겨야 합니다.
Tagged list는 다음 제약 사항을 따라야 합니다.
리스트는 램에 저장 되어져야만 하고 커널 압축 해제기나 initrd 처리가 덮어쓰지 않는 메모리 영역에 위치해야만 합니다. 권장되는 곳은 RAM의 첫번째 16KiB 안이고, 일반적으로 물리 RAM의 시작에서 (Zero page exception 벡터를 피하기 위해서) 0x100 더하여 진 곳에 위치합니다. |
Tagged list의 물리 주소는 커널로의 진입 시점에 R2 레지스터에 저장되어져 있어야 합니다. 예전에는 이 것이 필수적이지는 않았고, 커널이 물리 램의 시작에서 0x100 더한 곳을 고정값으로 사용했었습니다. 앞으로는 이렇게 하지 않을 겁니다. |
리스트는 커널이 초기 페이지 테이블을 생성하는 0x4000 경계까지 확장되면 안됩니다. |
리스트는 (권장되는 위치를 사용하지 않는다면) word 크기(32비트, 4바이트)로 맞추어져야 합니다. |
리스트는 ATAG_CORE로 시작해서 ATAG_NONE으로 끝나야 합니다. |
리스트는 ATAG_MEM을 적어도 하나는 포함해야 합니다. |
리스트의 각 태그는 양수 32비트로 된 size와 tag, 두개의 값을 포함하는 헤더와 그 태그의 값들로 구성됩니다.
struct atag_header {
u32 size; /* legth of tag in words including this header */
u32 tag; /* tag value */
};
|
데이터를 갖지 않는 ATAG_NONE와 필요하면 데이터를 쓰는 ATAG_CORE를 제외한 각 태그 헤더에는 그 태그와 관련된 데이터가 뒤따릅니다. 데이터의 크기는 헤더 안의 size 필드에 의해 결정되는데 이 값은 헤더 크기를 포함하기 때문에 최소 사이즈는 2입니다. ATAG_NONE은 size 필드를 0으로 셋팅해야 하는 유일한 태그입니다.
한 태그에는 덧붙여지는 정보 크기를 제공하는 필수 구조체 뒤에 추가 데이터를 포함할 겁니다. 이 것은 추후에 커널로 제공하는 데이터를 확장하기 위해서 사용될 수 있습니다. 예를 들면 부트 로더는 ATAG_SERIAL 안에 추가적인 제품 일련 번호 정보를 제공하여 이를 위해 수정된 커널이 이를 읽어갈 수 있도록 할 수 있습니다.
파라미터 리스트의 태그의 순서는 중요하지 않습니다. 중복된 태그에 대한 해석은 태그에 따라 다를 수는 있지만 필요한 만큼 여러번 포함 될 겁니다.
각각의 태그의 데이터는 부록 A. 태그 레퍼런스 절에서 설명합니다.
구현 목적으로 태그는 다음과 같이 정의될 수 있습니다.
struct atag {
struct atag_header hdr;
union {
struct atag_core core;
struct atag_mem mem;
struct atag_videotext videotext;
struct atag_ramdisk ramdisk;
struct atag_initrd2 initrd2;
struct atag_serialnr serialnr;
struct atag_revision revision;
struct atag_videolfb videolfb;
struct atag_cmdline cmdline;
} u;
};
|
리스트를 만들 필요가 있는 구현을 정의해 본다면 코드는 아마 다음과 유사할 겁니다.
#define tag_next(t) ((struct tag *)((u32 *)(t) + (t)->hdr.size))
#define tag_size(type) ((sizeof(struct tag_header) + sizeof(struct type)) >> 2)
static struct atag *params; /* used to point at the current tag */
static void
setup_core_tag(void * address,long pagesize)
{
params = (struct tag *)address; /* Initialise parameters to start at given address */
params->hdr.tag = ATAG_CORE; /* start with the core tag */
params->hdr.size = tag_size(atag_core); /* size the tag */
params->u.core.flags = 1; /* ensure read-only */
params->u.core.pagesize = pagesize; /* systems pagesize (4k) */
params->u.core.rootdev = 0; /* zero root device (typicaly overidden from commandline )*/
params = tag_next(params); /* move pointer to next tag */
}
static void
setup_mem_tag(u32_t start, u32_t len)
{
params->hdr.tag = ATAG_MEM; /* Memory tag */
params->hdr.size = tag_size(atag_mem); /* size tag */
params->u.mem.start = start; /* Start of memory area (physical address) */
params->u.mem.size = len; /* Length of area */
params = tag_next(params); /* move pointer to next tag */
}
static void
setup_end_tag(void)
{
params->hdr.tag = ATAG_NONE; /* Empty tag ends list */
params->hdr.size = 0; /* zero length */
}
static void
setup_tags(void)
{
setup_core_tag(0x100, 4096); /* standard core tag 4k pagesize */
setup_mem_tag(0x10000000, 0x400000); /* 64Mb at 0x10000000 */
setup_mem_tag(0x18000000, 0x400000); /* 64Mb at 0x18000000 */
setup_end_tag(void); /* end of tags */
}
|
여기 있는 코드가 완전하긴 하지만 이 코드는 파마리터 셋을 위한 절대적으로 최소한의 필요사항만을 나타내고 있고, 앞서 설명된 이 절에서의 컨셉을 보여주기 위한 용도로 작성되었습니다. 실제 부트로더는 아마 추가적인 값들을 전달하고 고정값을 사용하는 것보다는 실제 시스템 메모리에서 알아낸 값을 사용할 것입니다. 더 완전한 예제는 부록 B. 완전한 예제에서 볼 수 있습니다.
부트 로더가 제공할 필요가 있는 추가 정보는 각 ARM 시스템에서 MACH_TYPE으로 불리는 간단하고 유일한 수, 머신 타입 뿐입니다.
머신 타입 넘버는 ARM 리눅스 웹사이트의 Machine Registry 페이지에서 획득할 수 있습니다. 머신 타입은 가능한한 프로젝트 초기에 획득해야 합니다. 커널이 그 스스로(머신 정의값들 등)를 포팅하는 것은 수많은 결과를 만들 수 있고 이후에 정의값들을 바꾸는 것은 수많은 원치 않는 문제를 만들 수 있습니다. 이 값들은 커널 소스 (linux/arch/arm/tools/mach-types) 안에 정의된 리스트에 의해 표현됩니다.
부트 로더는 어떤 방법으로든 실행될 머신의 타입을 획득해야 합니다. 그 방법이 하드 코딩된 값이던 연결된 하드웨어에서 탐색하는 어떤 알고리즘이던 상관없습니다. 그 구현 방법은 문서의 범위를 넘어섭니다.
부트 로더가 다른 모든 단계를 수행했다면 CPU 셋팅에 정확한 값들로 커널의 실행을 시작해야 합니다.
진입 제약 사항은:
CPU 는 IRQ 와 FIQ 모두 꺼진 상태로 SVC (supervisor) 모드에 있어야 합니다. |
MMU는 반드시 꺼져 있어야, 즉 물리 램에서 주소를 변환하지 않는 상태로 코드가 실행되어야 합니다. |
데이터 캐시는 반드시 꺼져 있어야 합니다. |
인스트럭션 캐시는 켜져 있건 꺼져있건 상관없습니다. |
CPU 레지스터 0 은 0 이어야 합니다. |
CPU 레지스터 1 은 ARM 리눅스 머신 타입이어야 합니다. |
CPU 레지스터 2 는 파라미터 리스트의 물리 주소여야 합니다. |
부트 로더는 커널 이미지의 첫번째 명령으로 직접 점프하여 커널 이미지를 호출할 것입니다.
A. 태그 레퍼런스
ATAG_CORE
ATAG_CORE — 리스트의 시작으로 사용되는 시작 태그
구조체 멤버
struct atag_core {
u32 flags; /* bit 0 = read-only */
u32 pagesize; /* systems page size (usually 4k) */
u32 rootdev; /* root device number */
};
|
설명
어떤 부트 로더도 전달해야만 하는 기본 정보를 포함하는 리스트의 시작은 다른 구조가 뒤에 붙지 않는 것을 나타내는 길이 2의 이 태그가 되어야 합니다.
ATAG_NONE
ATAG_NONE — 리스트의 끝으로 사용되는 끝 태그
설명
이 태그는 리스트의 끝을 나타내는데 사용됩니다. 헤더 안의 size 필드가 (2가 아닌) 0 이 되는 유일한 태그입니다.
ATAG_MEM
ATAG_MEM — 물리 메모리 영역을 설명하는데 사용되는 태그
구조체 멤버
struct atag_mem {
u32 size; /* size of the area */
u32 start; /* physical start address */
};
|
설명
커널이 사용하는 물리 메모리 영역을 설명합니다.
ATAG_VIDEOTEXT
ATAG_VIDEOTEXT — VGA test 타입 디스플레이를 설명하는 데 사용되는 태그
구조체 멤버
struct atag_videotext {
u8 x; /* width of display */
u8 y; /* height of display */
u16 video_page;
u8 video_mode;
u8 video_cols;
u16 video_ega_bx;
u8 video_lines;
u8 video_isvga;
u16 video_points;
};
|
ATAG_RAMDISK
ATAG_RAMDISK — 커널에 의해 램디스크가 어떻게 사용될 지를 설명하는 태그
구조체 멤버
struct atag_ramdisk {
u32 flags; /* bit 0 = load, bit 1 = prompt */
u32 size; /* decompressed ramdisk size in _kilo_ bytes */
u32 start; /* starting block of floppy-based RAM disk image */
};
|
설명
(초기) 램디스크가 커널에 의해 어떻게 설정될지를 설명합니다. 특히 부트로더가 ATAG_INITRD2를 사용해서 전달하는 압축 해제된 초기 램디스크 이미지를 얻을 수 있을 만큼의 크기를 보장받을 수 있습니다.
ATAG_INITRD2
ATAG_INITRD2 — 압축된 램디스크의 물리 위치를 설명합니다.
구조체 멤버
struct atag_initrd2 {
u32 start; /* physical start address */
u32 size; /* size of compressed ramdisk image in bytes */
};
|
설명
일반적으로 ATAG_RAMDISK와 같이 사용되는 압축된 램디스크 이미지의 위치. 커맨드 라인 파라미터의 ‘root=/dev/ram’으로써 초기 루트 파일 시스템으로 사용될 수 있습니다. 이 태그는 원래 가상 주소를 사용하는 ATAG_INITRD를 대신함으로써 문제가 될 수 있습니다. 모든 새로운 부트 로더는 이 태그를 사용하는 것이 좋습니다.
ATAG_SERIAL
ATAG_SERIAL — 64비트 값의 보드 일련 번호 태그
구조체 멤버
struct atag_serialnr {
u32 low;
u32 high;
};
|
ATAG_REVISION
ATAG_REVISION — 보드 리비전 태그
구조체 멤버
struct atag_revision {
u32 rev;
};
|
ATAG_VIDEOLFB
ATAG_VIDEOLFB — 프레임버퍼 타입 디스플레이의 파라미터를 설명하는 태그
구조체 멤버
struct atag_videolfb {
u16 lfb_width;
u16 lfb_height;
u16 lfb_depth;
u16 lfb_linelength;
u32 lfb_base;
u32 lfb_size;
u8 red_size;
u8 red_pos;
u8 green_size;
u8 green_pos;
u8 blue_size;
u8 blue_pos;
u8 rsvd_size;
u8 rsvd_pos;
};
|
ATAG_CMDLINE
ATAG_CMDLINE — 커널로 커맨드 라인을 전달하는데 사용되는 태그
크기
2 + ((cmdline의 길이 + 3) / 4)
구조체 멤버
struct atag_cmdline {
char cmdline[1]; /* this is the minimum size */
};
|
설명
커널로 커맨드 라인 파라미터를 전달하는데 사용됩니다. 커맨드 라인은 NULL로 끝나야 합니다. cmdline의 길이 변수는 끝내는 문자를 포함한 길이여야 합니다.
B. 완전한 예제
여기 있는 것은 이 문서에서 설명하는 모든 정보를 보여주는 간단한 부트 로더에서 작동하는 예제입니다. 실제 부트로더는 더 많은 코드가 필요합니다. 이 예제는 그저 보여주기 위함입니다.
이 예제 코드는 BSD 라이센스로 배포됩니다. 자유롭게 복사하고, 필요하면 사용할 수 있습니다.
/* example.c
* example ARM Linux bootloader code
* this example is distributed under the BSD licence
*/
/* list of possible tags */
#define ATAG_NONE 0x00000000
#define ATAG_CORE 0x54410001
#define ATAG_MEM 0x54410002
#define ATAG_VIDEOTEXT 0x54410003
#define ATAG_RAMDISK 0x54410004
#define ATAG_INITRD2 0x54420005
#define ATAG_SERIAL 0x54410006
#define ATAG_REVISION 0x54410007
#define ATAG_VIDEOLFB 0x54410008
#define ATAG_CMDLINE 0x54410009
/* structures for each atag */
struct atag_header {
u32 size; /* length of tag in words including this header */
u32 tag; /* tag type */
};
struct atag_core {
u32 flags;
u32 pagesize;
u32 rootdev;
};
struct atag_mem {
u32 size;
u32 start;
};
struct atag_videotext {
u8 x;
u8 y;
u16 video_page;
u8 video_mode;
u8 video_cols;
u16 video_ega_bx;
u8 video_lines;
u8 video_isvga;
u16 video_points;
};
struct atag_ramdisk {
u32 flags;
u32 size;
u32 start;
};
struct atag_initrd2 {
u32 start;
u32 size;
};
struct atag_serialnr {
u32 low;
u32 high;
};
struct atag_revision {
u32 rev;
};
struct atag_videolfb {
u16 lfb_width;
u16 lfb_height;
u16 lfb_depth;
u16 lfb_linelength;
u32 lfb_base;
u32 lfb_size;
u8 red_size;
u8 red_pos;
u8 green_size;
u8 green_pos;
u8 blue_size;
u8 blue_pos;
u8 rsvd_size;
u8 rsvd_pos;
};
struct atag_cmdline {
char cmdline[1];
};
struct atag {
struct atag_header hdr;
union {
struct atag_core core;
struct atag_mem mem;
struct atag_videotext videotext;
struct atag_ramdisk ramdisk;
struct atag_initrd2 initrd2;
struct atag_serialnr serialnr;
struct atag_revision revision;
struct atag_videolfb videolfb;
struct atag_cmdline cmdline;
} u;
};
#define tag_next(t) ((struct tag *)((u32 *)(t) + (t)->hdr.size))
#define tag_size(type) ((sizeof(struct tag_header) + sizeof(struct type)) >> 2)
static struct atag *params; /* used to point at the current tag */
static void
setup_core_tag(void * address,long pagesize)
{
params = (struct tag *)address; /* Initialise parameters to start at given address */
params->hdr.tag = ATAG_CORE; /* start with the core tag */
params->hdr.size = tag_size(atag_core); /* size the tag */
params->u.core.flags = 1; /* ensure read-only */
params->u.core.pagesize = pagesize; /* systems pagesize (4k) */
params->u.core.rootdev = 0; /* zero root device (typicaly overidden from commandline )*/
params = tag_next(params); /* move pointer to next tag */
}
static void
setup_ramdisk_tag(u32_t size)
{
params->hdr.tag = ATAG_RAMDISK; /* Ramdisk tag */
params->hdr.size = tag_size(atag_ramdisk); /* size tag */
params->u.ramdisk.flags = 0; /* Load the ramdisk */
params->u.ramdisk.size = size; /* Decompressed ramdisk size */
params->u.ramdisk.start = 0; /* Unused */
params = tag_next(params); /* move pointer to next tag */
}
static void
setup_initrd2_tag(u32_t start, u32_t size)
{
params->hdr.tag = ATAG_INITRD2; /* Initrd2 tag */
params->hdr.size = tag_size(atag_initrd2); /* size tag */
params->u.initrd2.start = start; /* physical start */
params->u.initrd2.size = size; /* compressed ramdisk size */
params = tag_next(params); /* move pointer to next tag */
}
static void
setup_mem_tag(u32_t start, u32_t len)
{
params->hdr.tag = ATAG_MEM; /* Memory tag */
params->hdr.size = tag_size(atag_mem); /* size tag */
params->u.mem.start = start; /* Start of memory area (physical address) */
params->u.mem.size = len; /* Length of area */
params = tag_next(params); /* move pointer to next tag */
}
static void
setup_cmdline_tag(const char * line)
{
int linelen = strlen(line);
if(!linelen)
return; /* do not insert a tag for an empty commandline */
params->hdr.tag = ATAG_CMDLINE; /* Commandline tag */
params->hdr.size = (sizeof(struct atag_header) + linelen + 1 + 4) >> 2;
strcpy(params->u.cmdline.cmdline,line); /* place commandline into tag */
params = tag_next(params); /* move pointer to next tag */
}
static void
setup_end_tag(void)
{
params->hdr.tag = ATAG_NONE; /* Empty tag ends list */
params->hdr.size = 0; /* zero length */
}
#define DRAM_BASE 0x10000000
#define ZIMAGE_LOAD_ADDRESS DRAM_BASE + 0x8000
#define INITRD_LOAD_ADDRESS DRAM_BASE + 0x800000
static void
setup_tags(parameters)
{
setup_core_tag(parameters, 4096); /* standard core tag 4k pagesize */
setup_mem_tag(DRAM_BASE, 0x4000000); /* 64Mb at 0x10000000 */
setup_mem_tag(DRAM_BASE + 0x8000000, 0x4000000); /* 64Mb at 0x18000000 */
setup_ramdisk_tag(4096); /* create 4Mb ramdisk */
setup_initrd2_tag(INITRD_LOAD_ADDRESS, 0x100000); /* 1Mb of compressed data placed 8Mb into memory */
setup_cmdline_tag("root=/dev/ram0"); /* commandline setting root device */
setup_end_tag(void); /* end of tags */
}
int
start_linux(char *name,char *rdname)
{
void (*theKernel)(int zero, int arch, u32 params);
u32 exec_at = (u32)-1;
u32 parm_at = (u32)-1;
u32 machine_type;
exec_at = ZIMAGE_LOAD_ADDRESS;
parm_at = DRAM_BASE + 0x100
load_image(name, exec_at); /* copy image into RAM */
load_image(rdname, INITRD_LOAD_ADDRESS);/* copy initial ramdisk image into RAM */
setup_tags(parm_at); /* sets up parameters */
machine_type = get_mach_type(); /* get machine type */
irq_shutdown(); /* stop irq */
cpu_op(CPUOP_MMUCHANGE, NULL); /* turn MMU off */
theKernel = (void (*)(int, int, u32))exec_at; /* set the kernel address */
theKernel(0, machine_type, parm_at); /* jump to kernel with register set */
return 0;
}
|