[Linux Kernel] gpio_request_one

초기화 할 때 gpio_request 를 하고 값을 셋팅하는 걸 한방에 할 수 있는 방법!
gpio_request_one 을 쓰시라~

int gpio_request_one(unsigned gpio, unsigned long flags, const char *label);

기존의 GPIOF_ 가 다음과 같으므로

* GPIOF_DIR_IN		- to configure direction as input 
* GPIOF_DIR_OUT		- to configure direction as output

* GPIOF_INIT_LOW - as output, set initial level to LOW
* GPIOF_INIT_HIGH	- as output, set initial level to HIGH

flags 에는 다음과 같은 값들을 쓸 수 있다. 이름을 보면 대충 뭐하는지 다 알 듯.

* GPIOF_IN		- configure as input
* GPIOF_OUT_INIT_LOW	- configured as output, initial level LOW
* GPIOF_OUT_INIT_HIGH	- configured as output, initial level HIGH

그리고 array 로 만들어서 한방에 할 수도 있다.

static struct gpio leds_gpios[] = { 
	{ 32, GPIOF_OUT_INIT_HIGH, "Power LED" }, /* default to ON */
	{ 33, GPIOF_OUT_INIT_LOW,  "Green LED" }, /* default to OFF */
	{ 34, GPIOF_OUT_INIT_LOW,  "Red LED"   }, /* default to OFF */
	{ 35, GPIOF_OUT_INIT_LOW,  "Blue LED"  }, /* default to OFF */
	{ ... },
};

err = gpio_request_array(leds_gpios, ARRAY_SIZE(leds_gpios)); 
if (err)
	...

당연히 array 를 free하는 것도 있다.

gpio_free_array(leds_gpios, ARRAY_SIZE(leds_gpios))

request 할 때 맨 뒤의 label 을 만들 때 kasprintf() 를 이용하면 내부에서 kmalloc 해서 잡아준다. 런타임에 label을 정하거나 할 때 유용히 쓸 수 있다.

 char *label = kasprintf(GFP_KERNEL, “LED %d”, i);

 gpio_request_one(32, GPIOF_OUT_INIT_HIGH, label);

 …
 gpio_free(32);
 kfree(label);

이 문서의 라이센스는 GPL을 따른다(This document is released under the GPL license.)

참고 : 
http://lwn.net/Articles/369182/  

구글도 한다. 자동 checkpatch!

작년 8월부터 10월에 “개발 환경 개선 Git + Gerrit + checkpatch + cleanpatch” 에서 이야기했던대로
회사에서는 아무도 나에게 이런 것을 하라고 한 적은 없지만,

1. Gerrit 시스템 상에서 리눅스 커널에 대한 Change는 자동으로 checkpatch.pl 를 실행해서 해당 결과를 자동으로 comment하고, Error 나 Warning이 있는 경우 해당 패치셋에 -1로 리뷰 점수를 부여하고,
2. White space error, 소스 코드, Kconfig, Makefile 등의 파일의 Execution 권한 에러 등이 있는 경우 자동으로 수정해서 새로운 Patch set으로 올려주는

시스템을 구현해서 도입했다(나중에 연말 임원 회의 때 “우리 부서의 자랑” 같은 발표를 하려고 했던 것으로 알고 있다. 했나?).

그런데 올해 2월 초부터 구글의 gerrit 시스템에도 비슷한 것이 보이기 시작했다. “Kernel code style” 이라는 사용자 이름으로 checkpatch 결과가 붙기 시작한 거다. 내가 구현한 위의 것에서는 1단계까지의 것으로 보인다. 보자마자 ‘어라? 이것들 나랑 비슷한거 만들었네?’ 란 생각에 흥미로움을 감출 수 없었다. 더 재밌는 건 내 스크립트가 comment 하는 형식과 구글의 comment 형식이 거의 같다는 거다. 난 너무 많으면 길어서 내용을 좀 자르긴 했다. 도입 초기에는 에러가 3만개, 6만개씩 있는 패치들도 있었으니까! ㅎㅎㅎㅎ

그래서 찾아보니 구글의 커널 개발자 Brian Swetland 가 2009년 9월에 이를 할 수 있으면 좋겠다는 이슈를 올렸었고, 2010년 2월에 hooks 를 이용할 수 있다는 답변으로 closed 되었다. 답변으로 달린 방법이 거의 정확히 1단계를 위해 내가 구현한 방법과 일치하는데, 구글 내부 gerrit 에서도 사용했었는지는 알 수 있는 방법은 없다. 그런데 Nexus S때도, Galaxy Nexus 때도 2월 이전에는 쓴 적이 없단 말이지… ㅎㅎㅎ

동작하는 방식을 조금 보니 아마 커널 Change를 올리는 개발자 그룹이 있어서 해당 그룹에 속한 개발자가 Change를 올리면 checkpatch로 체크를 하는 것 같다.

이글을 쓰다가 TI 커널 브랜치에서부터 시작이 된 것 같아서 찾아보니 TI Gerrit(http://review.omapzoom.org/)은 2011년 7월부터 Ruslan Bilovol 이란 개발자가 자기 Bot을 사용해서 Change-Id check와 checkpatch를 자동으로 돌리는 것을 시작했었다! 난 작년 7월에 산호세에 있었고 7월 말부터 이 일을 맡아서 시작했었으니! 오! 세계 최초라 한 것이 부끄럽구나! …근데 자동으로 고쳐서 올려주는 건 아직 나밖에 안했잖아???? ㅎㅎㅎㅎ

아무튼 내가 생각한 것과 비슷한 생각들, 그리고 비슷한 구현물들을 세계 이곳저곳에서 발견할 수 있다는 게 너무나도 재밌다!!!

p.s.: 나한테 더 성능좋은 PC, 서버들이 더 많이 주어진다면, 아마 더 많은 일을 할 수 있을텐데…
 

[gcc] gcc 확장 __builtin_constant_p(exp)

Documentation/CodingStyle에서 inline disease 에 언급된 (slab 기준) kmalloc 구현을 살펴보다가 눈에 띄어서 정리해 둔다.
__builtin_constant_p(exp) 는 컴파일 타임에 상수로 정해질 수 있는 경우에 1을, 아닌 경우에는 0을 리턴한다. 따라서 이 조건에 따라 다른 코드를 사용함으로 써 최적화할 수 있도록 할 수 있다.

(이 글을 쓰는 시점인 v3.3-rc6 기준)kmalloc 의 경우 다음과 같이 구현되어 있다.

include/linux/slab_def.h

static __always_inline void *kmalloc(size_t size, gfp_t flags)

{
	struct kmem_cache *cachep;
	void *ret;

	if (__builtin_constant_p(size)) {
		int i = 0;

		if (!size)
			return ZERO_SIZE_PTR;

#define CACHE(x) \
		if (size <= x) \
			goto found; \
		else \
			i++;
#include <linux/kmalloc_sizes.h>
#undef CACHE
		return NULL;
found:
#ifdef CONFIG_ZONE_DMA
		if (flags & GFP_DMA)
			cachep = malloc_sizes[i].cs_dmacachep;
		else
#endif
			cachep = malloc_sizes[i].cs_cachep;

		ret = kmem_cache_alloc_trace(size, cachep, flags);

		return ret;
	}
	return __kmalloc(size, flags);
}

이외에도 __always_inline 사용, CACHE(x) 매크로를 상황에 따라 define하고 linux/kmalloc_sizes.h 헤더를 include 하여 사용하는 부분은 매우 흥미롭다. __always_inline 은 gcc의 function attribute 를 이용한다.

이 글의 라이센스는 GPL 을 따른다(This document is released under the GPL licence.).

참고 : 
http://gcc.gnu.org/onlinedocs/gcc-4.6.3/gcc/Other-Builtins.html 

[Linux] scnprintf()

리눅스 커널은 scnprintf 라는 함수를 제공하는데, 이 함수는 snprintf의 일종이라고 볼 수 있다.
snprintf 는 C99 표준에 의해서 버퍼에 실제로 쓰여진 길이를 리턴하지 않고, 쓰여지고자 했던 길이를 리턴하도록 되어 있다. 예를 들어 다음과 같은 소스 코드가 있다면,

#include <stdio.h>

int main(void)
{
        char buffer[10] = { 0, };
        int output = 0;

        output = snprintf(buffer, sizeof(buffer), “%ld”, 1234567890123l);
        printf(“buffer: %s, output: %d\n”, buffer, output);

        return 0;
}

대부분 생각하는 출력은 아마도 “buffer: 123456789, output:9” 와 같을텐데, 이 소스 코드에 해당하는 프로그램은 다음과 같이 output으로 원래 찍으려고 했었던 수의 출력 길이인 13을 출력한다.

dasomoli@dasomoli-ubuntu:~/src/snprintf$ ./a.out
buffer: 123456789, output: 13

그래서 커널은 snprintf 말고도 scnprintf 함수를 제공한다.

요즘은 Device driver의 제어를 위해 ioctl 함수보다는 sysfs 를 이용하는 추세인데, 이를 위한 sysfs의 show 함수에서는 scnprintf를 사용하도록 하고 있다(Documentation/filesystems/sysfs.txt).

참고 : http://lwn.net/Articles/69419/

[Linux] ARM 리눅스 부팅(Booting ARM Linux)

ARM 리눅스 부팅

원문 : http://www.simtec.co.uk/products/SWLINUX/files/booting_article.html
번역 : 양정석(dasomoli@gmailREMOVETHIS.com)

Vincent Sanders

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

  • This document is released under a GPL licence.

  • All trademarks are acknowledged.

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.

2004-06-04

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 리눅스 커널을 시작하기 위한 부트 로더의 필요사항과 절차를 구현 가이드, 예제와 함께 명확히 간결한 용어로 정의합니다.

1. 이 문서에 관해서

이 문서는 2.4.18 이후의 커널이 사용하는 “새로운” 부팅 절차를 설명합니다. 이전의 “struct” 메소드는 사용하지 마세요.

이 문서는 광범위한 종류의 출처(참고문헌 참고)와 저자들로부터의 정보를 포함하고 있습니다. 메인테이너들 또는 ARM 리눅스 메일링 리스트 상에 질문하기 전에 이 출처들을 찾아보는 것이 좋습니다. 이 분야의 대부분의 것들은 이전에 반복적으로 문의 및 답변되었고, 만약 여러분이 기본적인 조사도 하지 않았다면 여러분은 무시될 가능성이 큽니다.

추가적으로 이 문서에서 제공하는 가이드를 따르고, 커널을 시작하는 어셈블러의 모든 미묘한 차이를 이해하는 개발자가 될 필요는 없다는 것을 알아두세요. 또한 대부분의 부팅 문제는 여기 있는 코드와 상관없는 경우가 많았고, 많은 사람들이 말하는 코드는 매우 까다롭고 그 문제에 대해 간파할 수 없는 경우가 많았습니다.

2. 다른 부트로더

새 부트 로더의 작성을 시작하기 전에 개발자들은 이미 존재하는 부트 로더가 적절하지는 않은지 한번 생각해보는 것이 좋습니다. 간단한 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 편리한 기능의 상용 부트로더

3. 개요

ARM 리눅스는 시스템을 초기화하는 특정 머신에서만 동작하는 작은 코드 없이는 시작될 수 없습니다. 몇몇 부트로더는 더 많은 추가 기능을 제공하긴 하지만, ARM 리눅스는 이 작은 일을 하는 부트 로더가 필요합니다. 최소한의 필요사항은 다음과 같습니다:

시스템의 메모리 설정하기.
정확한 메모리 주소에 커널 이미지 로딩.
필요하면 정확한 메모리 주소에 램디스크 로딩.
커널로 전달할 부트 파라미터 초기화.
ARM 리눅스 머신 타입 얻기
적당한 레지스터 값으로 커널로 진입.

부트로더는 일반적으로 이런 기본적인 작업들 뿐만 아니라 시리얼 또는 비디오 콘솔을 초기화해야 합니다. 사실 시리얼 포트는 거의 대부분의 설정을 위해 필수사항으로 생각됩니다.

위의 각 단계를 다음 절들에서 설명합니다.

4. 시스템의 메모리 설정하기

부트 로더는 커널이 시스템의 임시 저장 장소로 사용할 모든 램을 찾아 초기화해야 합니다. 이는 특정 머신에서만 동작하는 방법(모든 램의 크기와 위치를 찾는 내부 알고리즘을 사용하거나, 머신에 달린 램에 관한 정보를 사용하거나, 또는 부트 로더 설계자 관점의 다른 방법 등)으로 수행됩니다.

어떠한 경우에도 모든 셋업은 부트 로더에 의해서 수행되어야 합니다. 커널은 시스템 안의 램의 셋업 또는 설정에 대해서 부트 로더가 제공하는 것 외에는 아무 것도 몰라야 합니다. 커널 안의 machine_fixup()의 사용은 이런 것을 하기 위한 것이 절대 아닙니다. 이 부분에서의 커널과 부트 로더 간의 책임 구별은 명확합니다.

물리 메모리 배치는 ATAG_MEM 파라미터를 사용해서 커널로 전달됩니다. 메모리가 가장 적게 분리되어 있는 것이 좋긴 하지만, 완전히 연속적일 필요는 없습니다. 여러 메모리 지역을 위한 여러개의 ATAG_MEM 블록도 상관없습니다. 연속된 물리 메모리 지역이 있다면 커널은 그 블록들을 합칠 겁니다.

부트 로더는 또한 linux/Documentation/kernel-parameters.txt 에 완벽히 문서화되어 있는 ‘mem=’ 파라미터를 사용해서 커널의 커맨드라인으로 메모리를 조작할 수도 있습니다.

커널 커맨드 라인 ‘mem=’ 는 정의된 메모리 영역의 물리 메모리 위치와 크기를 위해서 mem=<size>[KM][,@<phys_offset>] 문법을 가집니다. mem= 파라미터는 각기 다른 곳의 여러개의 연속되지 않은 메모리 블록을 나타내기 위해 여러 번 쓸 수 있습니다.

5. 커널 이미지 로딩

커널 이미지는 커널 빌드 과정에서 생성된 압축되지 않은 “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 벡터와 페이지 테이블을 위한 공간을 남겨둡니다. 이 관례는 매우 일반적입니다.

6. 램디스크 로딩

램디스크는 많은 시스템상에서 일반적인 요구사항입니다. 그 것은 다른 드라이버나 설정에 접근할 필요없이 사용가능한 루트 파일 시스템을 제공합니다. 완전한 세부사항은  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 코드가 커널을 압축해제 하기 위해서 사용하는 메모리와의 충돌이 없어야 합니다, 아니면 체크 없이 덮어쓰게 될 것입니다.

7. 콘솔 초기화

콘솔은 시스템이 초기화될 때 커널이 무엇을 수행하는 지를 볼 수 있는 방법으로 매우 권장됩니다. 일반적인 경우인 비디오 프레임 버퍼 드라이버나 시리얼 드라이버 같은 적절한 드라이버로 어떤 입출력 장치도 사용될 수 있습니다. ARM 리눅스가 실행되는 시스템은 거의 항상 시리얼 콘솔 포트를 가지고 있습니다.

부트 로더는 부트로더 상에서 한 개의 시리얼 포트를 초기화하고 쓸 수 있도록 만들어야 합니다. 이 것은 그 포트를 사용하기 위한 하드웨어적인 파워 관리 같은 것들을 포함합니다. 이 것은 커널 시리얼 드라이버가 (일반적으로 디버깅 목적이나 타겟과의 통신 용도로 사용되는) 커널 콘솔로 사용되는 시리얼 포트를 자동으로 찾을 수 있도록 합니다.

아니면 부트로더는 linux/Documentation/kernel-parameters.txt에 설명된 Serial format 옵션과 포트를 지정하는 tagged list를 통해 이에 관련된 ‘console=’ 옵션을 커널로 전달할 수 있습니다.

8. 커널 파라미터

부트로더는 커널로 수행한 셋업, 시스템의 메모리의 크기와 영역, 그리고 필요시에는 다른 여러 값들을 설명하는 파라미터를 넘겨야 합니다.

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. 태그 레퍼런스 절에서 설명합니다.

표 3. 유용한 태그의 목록

태그 이름 크기 설명
ATAG_NONE 0x00000000 2 리스트의 끝으로 사용하는 빈 태그
ATAG_CORE 0x54410001 5 (비었다면 2) 리스트의 시작으로 사용하는 첫번째 태그
ATAG_MEM 0x54410002 4 메모리의 물리 영역을 설명
ATAG_VIDEOTEXT 0x54410003 5 VGA text 디스플레이를 설명
ATAG_RAMDISK 0x54410004 5 커널에서 램디스크가 어떻게 사용되는지를 설명
ATAG_INITRD2 0x54420005 4 메모리에 램디스크 이미지가 어디에 있는지를 설명
ATAG_SERIAL 0x54410006 4 64비트의 제품 일련 번호
ATAG_REVISION 0x54410007 3 32비트의 보드 리비전
ATAG_VIDEOLFB 0x54410008 8 vesafb 타입의 프레임 버퍼의 초기값
ATAG_CMDLINE 0x54410009 2 + ((cmdline의 길이 + 3) / 4) 커널로 전달되는 커맨드 라인

구현 목적으로 태그는 다음과 같이 정의될 수 있습니다.

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. 완전한 예제에서 볼 수 있습니다.

9. ARM 리눅스 머신 타입 얻기

부트 로더가 제공할 필요가 있는 추가 정보는 각 ARM 시스템에서 MACH_TYPE으로 불리는 간단하고 유일한 수, 머신 타입 뿐입니다.

머신 타입 넘버는 ARM 리눅스 웹사이트의 Machine Registry 페이지에서 획득할 수 있습니다. 머신 타입은 가능한한 프로젝트 초기에 획득해야 합니다. 커널이 그 스스로(머신 정의값들 등)를 포팅하는 것은 수많은 결과를 만들 수 있고 이후에 정의값들을 바꾸는 것은 수많은 원치 않는 문제를 만들 수 있습니다. 이 값들은 커널 소스 (linux/arch/arm/tools/mach-types) 안에 정의된 리스트에 의해 표현됩니다.

부트 로더는 어떤 방법으로든 실행될 머신의 타입을 획득해야 합니다. 그 방법이 하드 코딩된 값이던 연결된 하드웨어에서 탐색하는 어떤 알고리즘이던 상관없습니다. 그 구현 방법은 문서의 범위를 넘어섭니다. 

10. 커널 시작하기

부트 로더가 다른 모든 단계를 수행했다면 CPU 셋팅에 정확한 값들로 커널의 실행을 시작해야 합니다.

진입 제약 사항은:

CPU 는 IRQ 와 FIQ 모두 꺼진 상태로 SVC (supervisor) 모드에 있어야 합니다.
MMU는 반드시 꺼져 있어야, 즉 물리 램에서 주소를 변환하지 않는 상태로 코드가 실행되어야 합니다.
데이터 캐시는 반드시 꺼져 있어야 합니다.
인스트럭션 캐시는 켜져 있건 꺼져있건 상관없습니다.
CPU 레지스터 0 은 0 이어야 합니다.
CPU 레지스터 1 은 ARM 리눅스 머신 타입이어야 합니다.
CPU 레지스터 2 는 파라미터 리스트의 물리 주소여야 합니다.

부트 로더는 커널 이미지의 첫번째 명령으로 직접 점프하여 커널 이미지를 호출할 것입니다.

A. 태그 레퍼런스

ATAG_CORE

ATAG_CORE — 리스트의 시작으로 사용되는 시작 태그

0x54410001

크기

5 (빈 데이터면 2)

구조체 멤버

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 — 리스트의 끝으로 사용되는 끝 태그

0x00000000

크기

2

구조체 멤버

없음

설명

이 태그는 리스트의 끝을 나타내는데 사용됩니다. 헤더 안의 size 필드가 (2가 아닌) 0 이 되는 유일한 태그입니다.


ATAG_MEM

ATAG_MEM — 물리 메모리 영역을 설명하는데 사용되는 태그

0x54410002

크기

4

구조체 멤버

struct atag_mem {
        u32     size;   /* size of the area */
        u32     start;  /* physical start address */
};

설명

커널이 사용하는 물리 메모리 영역을 설명합니다.


ATAG_VIDEOTEXT

ATAG_VIDEOTEXT — VGA test 타입 디스플레이를 설명하는 데 사용되는 태그

0x54410003

크기

5

구조체 멤버

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 — 커널에 의해 램디스크가 어떻게 사용될 지를 설명하는 태그

0x54410004

크기

5

구조체 멤버

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 — 압축된 램디스크의 물리 위치를 설명합니다.

0x54420005

크기

4

구조체 멤버

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비트 값의 보드 일련 번호 태그

0x54410006

크기

4

구조체 멤버

struct atag_serialnr {
        u32 low;
        u32 high;
};

설명


ATAG_REVISION

ATAG_REVISION — 보드 리비전 태그

0x54410007

크기

3

구조체 멤버

struct atag_revision {
        u32 rev;
};

설명


ATAG_VIDEOLFB

ATAG_VIDEOLFB — 프레임버퍼 타입 디스플레이의 파라미터를 설명하는 태그

0x54410008

크기

8

구조체 멤버

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 — 커널로 커맨드 라인을 전달하는데 사용되는 태그

0x54410009

크기

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;
}

참고문헌

Setting R2 correctly for booting the kernel (explanation of booting requirements). Russell M King.

Makefile defines and symbols. Russell M King.

Bootloader guide. Wookey.

Kernel boot order. Russell M King.

Advice for head.S Debugging. Russell M King.

Linux kernel 2.4 startup. Bill Gatliff.

Blob bootloader. Erik Mouw.

[Linux] Kernel 이미지(zImage) 구조

gzip 으로 압축된 ramdisk 를 포함하는 zImage 는 다음과 같은 구조를 가진다.
zImage
– decompression code
– piggy.gz + piggy trailer
 + piggy.gz
  + piggy
   + kernel image
   + initramfs.cpio.gz + part3
    + initramfs.cpio.gz
     + initramfs.cpio
      + initramfs/
    + padding3
    + part3
  + padding piggy
  + piggy trailer

[Android] boot.img 구성

Android 에서 사용되는 boot.img 는 다음과 같이 구성되어 있다(ICS 기준).
각 구성부는 Page 크기에 맞춰 Align 되어 있다.
– Boot Image Header(1 page) (system/core/mkbootimng/bootimg.h 참고)
 + magic(8 bytes) : ANDROID!
 + kernel size(4 bytes)
 + kernel address(4 bytes)
 + ramdisk size(4 bytes)
 + ramdisk address(4 bytes)
 + second stage size(4 bytes)
 + second stage address(4 bytes)
 + kernel tags physical address(4 bytes)
 + page size(4 bytes)
 + reserved(8 bytes) : Should be 0
 + product name(16 bytes)
 + cmdline(512 bytes)
 + id(32 bytes) : timestamp, checksum, sha1, etc
– Kernel(zImage)
– Ram Disk
– Second stage

[Android] ext4 file system을 위한 빌드 설정 파일

오픈 소스의 Android Build 시스템을 확인해보면 Makefile 에 다음과 같이 system.img 나 userdata.img 등을 만들기 위한 옵션에 따른 처리가 있다.

build/core/Makefile

# #################################################################
# Targets for user images
# #################################################################
INTERNAL_USERIMAGES_EXT_VARIANT :=
ifeq ($(TARGET_USERIMAGES_USE_EXT2),true)
INTERNAL_USERIMAGES_USE_EXT := true
INTERNAL_USERIMAGES_EXT_VARIANT := ext2
else
ifeq ($(TARGET_USERIMAGES_USE_EXT3),true)
INTERNAL_USERIMAGES_USE_EXT := true
INTERNAL_USERIMAGES_EXT_VARIANT := ext3
else
ifeq ($(TARGET_USERIMAGES_USE_EXT4),true)
INTERNAL_USERIMAGES_USE_EXT := true
INTERNAL_USERIMAGES_EXT_VARIANT := ext4
endif
endif
endif

ifneq (true,$(TARGET_USERIMAGES_SPARSE_EXT_DISABLED))

  INTERNAL_USERIMAGES_SPARSE_EXT_FLAG := -s
endif

# $(1): src directory
# $(2): output file
# $(3): mount point
# $(4): ext variant (ext2, ext3, ext4)
# $(5): size of the partition
define build-userimage-ext-target
  @mkdir -p $(dir $(2))
  $(hide) PATH=$(foreach p,$(INTERNAL_USERIMAGES_BINARY_PATHS),$(p):)$$PATH \
          $(MKEXTUSERIMG) $(INTERNAL_USERIMAGES_SPARSE_EXT_FLAG) $(1) $(2) $(4) $(3) $(5)
endef
 
ifeq ($(INTERNAL_USERIMAGES_USE_EXT),true)
## generate an ext image
# $(1): output file
define build-systemimage-target
    @echo “Target system fs image: $(1)”
    $(call build-userimage-ext-target,$(TARGET_OUT),$(1),system,$(INTERNAL_USERIMAGES_EXT_VARIANT),$(BOARD_SYSTEMIMAGE_PARTITION_SIZE))

endef

$(BUILT_SYSTEMIMAGE): $(FULL_SYSTEMIMAGE_DEPS) $(INSTALLED_FILES_FILE)

        $(call build-systemimage-target,$@)
 

그래서 다음과 같이 ext4 사용, 파티션 사이즈, 그리고 sparse 이미지 여부를 설정해주면 그에 맞는 sytstem.img, userdata.img를 얻을 수 있다.
 

BoardConfig.mk

TARGET_USERIMAGES_USE_EXT4 := true
TARGET_USERIMAGES_SPARSE_EXT_DISABLED := true

BOARD_SYSTEMIMAGE_PARTITION_SIZE := 290979840
BOARD_USERDATAIMAGE_PARTITION_SIZE := 134217728
BOARD_FLASH_BLOCK_SIZE := 4096


ext4 sparse 이미지에 대해서는 system/extras/ext4_utils 의 make_ext4fs 툴의 소스 및 헤더파일을 살펴보면 알 수 있다.
 

[git] 어떤 commit 이 어느 브랜치에 있는지 확인하고 싶을 때

tag로 checkout을 한다던가 하게되면 이게 지금 어느 브랜치에서 나온건지를 알 수 없을 때가 있다. 또는 어느 commit 이 어느 브랜치 상에 있는지를 알고 싶을 때 라던가.. 이럴 때 사용할 수 있는 것이
git-what-branch 스크립트다.

https://github.com/SethRobertson/git-what-branch  

$ git-what-branch –allref <commit sha1 id>

 

[Linux] Shell script 에서 문자열 다루기

참고에 매우 자세히 잘 정리되어 있다. 감사.

문자열 길이 구하기
${#string}

문자열 추출
${string:position}
${string:position:length}

문자열 조각 삭제
${string#substring}
 string의 앞에서부터 가장 짧게 일치하는 substring 제거
${string##substring}
 string의 앞에서부터 가장 길게 일치하는 substring 제거

${string%substring}
 string의 뒤에서부터 가장 짧게 일치하는 substring 제거
${string%%substring}
 string의 뒤에서부터 가장 길게 일치하는 substring 제거

참고 : 
http://wonylog.tistory.com/192