2015.05.13 20:01


Table of Content
  1. Intro
  2. 쉬운 API
  3. 어려운  API
  4. 어려운 API 와 쉬운 API?
  5. CPU 소모성 API
  6. CPU 소모가 없는 API
  7. msleep()
  8. msleep_interruptible()
  9. ssleep()
  10. schedule_timeout_interrupt()
  11. schedule_timeout_uninterrupt() 
  12. schedule_timeout() 
  13. ndelay(), udelay(), mdelay()  


1. Intro
 커널에서 시간 지연 함수는 여러가지가 있습니다. 
 굳이 분류를 하자면 쉬운 API, 어려운 API, CPU 소모성 API, CPU 소모가 없는 API 정도로 나눌 수 있습니다. 

2. 쉬운 API
 쉬운 API는 크게 보면 아래와 같습니다. 
  #include <linux/delay.h>
  #include <linux/timer.h>

  msleep()
  ssleep()

 이 API들은 함수명대로 micro  second, milli second, 1 second 을 뜻합니다. 
 그냥 사용하시면 됩니다. 

3. 어려운 API
 어려운 API 는 크게 보면 아래와 같습니다. 

 
  #include <linux/delay.h>
   #include <linux/timer.h>
   
   msleep_interruptible()
   schedule_timeout_interruptible();
   schedule_timerout_uninterruptible();
   schedule_timeout();

 이것도 그냥 사용하면 됩니다.

4. 어려운 API 와 쉬운 API ?
  어려운 API 와 쉬운 API 의 차이점은 무엇일까요? 

  가장 기본이 되는 지연 함수는 schedule_timeout() 입니다. 
  관계를 살펴보면

      ssleep()
    -----------------------------------------
      msleep()

    -----------------------------------------
      schedule_timeout_interruptible(), schedule_timeout_uninterruptible
 
    -----------------------------------------
      schedule_timeout()

 입니다. 위에 그림만으로는 잘 설명이 안될 수도 있는데, 커널 소스를 살펴보면 관계를 확실히 확인 할 수 있습니다. 

  ssleep() 는 msleep() 를 호출합니다. 
  msleep() 는 schedule_timeout_uninterrupt() 를 호출합니다. 
  schedule_timeout_uninterrupt() 는 schedule_timeout() 을 호출합니다. 

 결국 최종으로 호출되는 것은 schedule_timeout() 인데 시간 지연이라는게 인터럽트를 허용할 수도, 아닐수도 있고 또한 사용상의 편의상 ssleep() 나 msleep() 같은 front-end 를 만들어 쓴다고 생각하면 됩니다. 

8. CPU 소모성 API
 지정한 지연시간까지 loop 를 돌면서 cpu 시간을 소모하는 형태의 API 입니다. 
 
 ndelay();
 udelay();
 mdelay();

 위와 같이 delay 라는 이름이 붙은 API 들이 CPU 소모성 API 이며 아래와 같이 구현할 경우에도 CPU 소모성 API 입니다. 
 
 while(time_before(jiffies, j1))
 {
   cpu_relax();
 }

9. CPU 소모가 없는 API
 CPU 점유가 없이 시간 지연을 구현하려면 현재 자기자신한테 할당된 프로세스 시간을 반납하고 대기하는 방식을 사용하면 됩니다. 

 schedule_timeout_interruptible()
 schedule_timeout_uninterruptible()

 등이 그런 계통입니다. 

7. msleep()
  header : 
asm/delay.h
  void msleep(unsigned int msecs);
  unsigned long msleep_interruptible(unsigned int msecs);

 => 밀리세컨드 동안 지연해 줍니다. 지연시간동안에는 인터럽트를 받을 수 없습니다. 

example

 #include <asm/delay.h>

static void example_msleep(void)
{
  msleep(1000);


 
8. msleep_interruptible()
  header : 
asm/delay.h
  unsigned long msleep_interruptible(unsigned int msecs);

 => 밀리세컨드 동안 지연해 줍니다. 지연시간동안에는 인터럽트를 받을 수 있습니다. 

example)

 #include <asm/delay.h>

static void example_msleep(void)
{
  msleep_interruptible(1000);


 
9. ssleep()
  
  header : asm/delay.h
    void ssleep(unsigned int seconds);

 => 초단위로 지연해 줍니다. 지연시간동안에는 인터럽트를 받을 수 없습니다. 

example)

 #include <linux/sched.h>

static void example_ssleep(void)
{
  ssleep(2);



10. schedule_timeout_interruptible()

  
  header : linux/sched.h
    void schedule_timeout_interruptible(unsigned long timeout);

 => timeout 시간만큼 지연해 줍니다.  

example)

 #include <linux/sched.h>

static void example_ssleep(void)
{
  schedule_timeout_interruptible(1 * HZ); /* 1 초간 지연 */


11. schedule_timeout_uninterruptible()

  
  header : linux/sched.h
    void schedule_timeout_uninterruptible(unsigned long timeout);

 => timeout 시간만큼 지연해 줍니다. 지연시간도중에는 인터럽트를 받을 수 없습니다. 
  
  
example)

 #include <linux/sched.h>

static void example_ssleep(void)
{
  schedule_timeout_uninterruptible(1 * HZ); /* 1 초간 지연 */


 
12. schedule_timeout()

  
  header : linux/sched.h
    void schedule_timeout(unsigned long timeout);

 => timeout 시간만큼 지연해 줍니다. 가장 기본이 되는 함수입니다. 그렇기 때문에 직접 호출하여 사용하지는 않습니다. 

example)

 #include <linux/sched.h>

static void __sched schedule_timeout_interruptible(void long timeout)
{
  __set_current_state(TASK_INTERRUPTIBLE);
 
 return schedule_timeout(timeout); 



13. ndelay, udelay, mdelay
   header : linux/delay.h
   #define ndelay(n);
   #define udelay(n); 
   #define mdelay(n);

  busy wait 방식이어서 cpu 로드가 많은 편입니다. 하지만 간단하게 쓸 수 있는 장점이 있습니다. 

example)


#include <linux/delay.h>

void delay_test(void long timeout)
{
  ndelay(1000);
  udelay(1000);
  mdelay(1000); 
 


신고


Posted by injunech
2015.04.10 22:47



디바이스는 하드웨어를 말한다. 

드라이버는 하드웨어를 다루는 소프트웨어를 말한다.

예를 들어보자.

UART 칩이 16개 달려있다고 했을 때 이를 제어하는 드라이버는 하나이다.

하지만 UART 칩이 16개 이니 디바이스는 16개 이다.

 

이제 리눅스 커널로 확장해 보자

arm 코아의 커널을 다루게 되면 항상 수정하거나 참고하는 파일이 있다.

EM-S5PV210  기준으로

arch/arm/mach-s5pv210/mach-ezs5pv210.c  파일이다.

 

이곳에서 보면 struct platform_device  구조체를 흔히 볼수 있다.

이 구조체의 몇몇 멤버를  보자

  • name           문자열 이름
  • id                 정수형 아이디
  • resource      리소스 구조체 포인터 (리소스 구조체는 하드웨의 주소나 인터럽트 정보가 나열된다.)   
  • num_resources    리소스 구조체 개수

다신한번 이 구조체의 이름이 struct platform_device 이다.   즉 디바이스인 것이다.

이 구조체는  아래의 함수를 통해 등록된다.

  • platform_add_devices()       여러개의 디바이스들을 한번에 등록
  • platform_device_register()  하나의 디바이스를 등록

 

 

이제  커널의 driver  디렉토리의 무수한 소스파일들을 보자 

모두다  struct platform_driver  구조체를 품고 있다.

이제 struct platform_driver 구조체의 주요한 멤버를 보자

  • probe         초기 실행함수
  • remove       드라이버 제거시 실행되는 함수
  • driver  =  {  .name       드라이버 이름

이 구조체는  platform_driver_register() 함수를 통해 등록된다.

 

 

이제 흐름을 따라가 보자

커널 시작시 디바이스들이 등록된다.  리소스가 등록되는 것이다.  UART칩이  16개 있다면 디바이스가 16 등록된다.

잠시후에 커널 드라이버가 등록된다.  이때 드라이버의 이름과 동일한 디바이스의 이름이 발견되면 probe() 함수가 호출된다.

동일한 이름의 디바이스가 16개 있다면 이 probe() 함수가 16번 호출된다.

 

참고해야 할것은 struct platform_device 구조체의 id 이다.

디바이스가 하나만 있다면 이값은 -1 로 설정한다.  하지만 2개 이상일 경우 id 는 0 부터 시작해서 각 디바이스마다  고유한 값을 넣는다.,

대게 1씩 순서대로 값을 넣는다.

 

struct platform_device 의 멤버 name 과  struct platform_driver 의  멤버 driver.name 은 동일한 값을 갖는다.

디바이스가 하나일 경우 디바이스의 이름과  드라이버의 이름은 같다.

하지만 디바이스가 여러개 일 경우  드라이버 이름은 보이는 대로의 이름이지만

디바이스의 경우   uart.0   uart.1  이렇게  숫자를 달고 나온다.   id 로 확장된 것이다.

아래의 매크로를 이용하여 디바이스 이름을 얻는다.

  • dev_name(&platform_device->dev)

이제 디바이스 드라이버가  한결 쉬워질 것이다.

신고


Posted by injunech
2015.04.06 12:27


쉘 스크립트를 만들때,  가장 첫 라인에 

#!/bin/bash 

를 왜 써야 하는지에 대하여 알아 보도록 하겠습니다.


쉘 스크립트의 가장 첫 라인에  !/bain/bash 를 쓰게 됨으로 해서, 내가 사용 하려는 명령어 해석기가 bash 쉘 임을 미리 알려주는 것입니다.


일반적으로 스크립트에서 #는 주석기호이지만, 첫라인의 #!/bin/bash 에서의 #은 주석기호가 아닙니다.


스크립트의 가장 첫라인에 있는 #! 은 스크립트의 제일 앞에서 이 파일이 어떤 명령어 해석기의 명령어 집합인지를 시스템에게 알려주는 역할을 합니다.


#! 은 두 바이트의 "매직넘버"("magic number")로서, 실행 가능한 쉘 스크립트라는 것을 나타내는 특별한 표시자입니다.


#! 바로 뒤에 나오는 것은 경로명으로, 스크립트에 들어있는 명령어들을 해석할 프로그램의 위치를 나타내는데 그 프로그램이 쉘인지, 프로그램 언어인지, 유틸리티인지를 나타냅니다. 


이 명령어 해석기가 주석은 무시하면서 스크립트의 첫 번째 줄부터 명령어들을 실행시킵니다. 


거의 대부분의 상업용 유닉스 및 리눅스 에서 기본 본쉘인 #!/bin/sh을 쓰면 비록 Bash 만 가지고 있는 몇몇 기능들을 못 쓰게 

되겠지만 리눅스가 아닌 다른 머신에 쉽게 포팅 할 수 있게 해 줍니다. (이렇게 작성된 스크립트는 POSIX sh 표준을 따르게 됩니다. )

"#!" 뒤에 나오는 경로는 정확히 Full PATH를 기록 해야 합니다. 

만약 PATH를 잘못 적게 되면, 스크립트를 돌렸을 때 거의 대부분 "Command not found"라는 에러 메세지만 보게 될 것입니다.


이상으로 쉘 스크립트 처음에 왜 "#!/bin/bash"를 반드시 써야만 하는지에 대하여 알아 보았습니다.

신고


Posted by injunech
2015.03.26 13:45


디바이스 드라이버 작업을 하다 보면 자주 보게 되는 매크로중으 히나가

바로  container_of 매크로 입니다.

이해가 가는 것 같기도 하고 아니것 같기도 하고 묻는 사람도 있고 해서

오늘은 이 매크로에 대해서 한번 알아 볼가 합니다.


일단 함수 원형을 한번 볼까요?


lxr 사이트에서 긁어 왔습니다.

위치는 include/linux/kernel.h 입니다.


 650#define container_of(ptr, type, member) ({                      \
 651        const typeof( ((type *)0)->member ) *__mptr = (ptr);    \
 652        (type *)( (char *)__mptr - offsetof(type,member) );})


이건 뭐 상당히 거시기 하군요.

뜯기 전에 좀 쉬운 예를 들어 보겠습니다.


 




위의 그림이 보이십니까?

뽀글이 structure 는 인자가 4개 입니다.

시작 번지는 1000 번지구요.  

라면 번지 1000

수프 번지 1008

가격 번지 1016

봉지 번지 1020


이렇게 되는 군요.

그런데 어떤 함수를 호출하면서 가격의 포인터를 넘겨주었습니다.


그래서...  가격포인터를 인자로 받은 함수가 아래와 같습니다.


int   how_much( short *가격)

{

     printk(" 가격위치는 0x%p\n", 가격)

     return 0;

}


자 이제 how_much 함수는 가격포인터를 프린트해줍니다.

당연히 그 값은 1016 번지가 되겠죠.


자 이제 how_much 함수는 가격의 포인터를 알고 있습니다.

처음 그림에서 보면 가격 인자는 뽀글이 자료 구조의 멤버네요?


how_much 에서 short * 형태로 인자를 받았지만

그 포인터가 struct 뽀글이 멤버라는 것을 알고 있고

struct 뽀글이 다른 멤버를 필요로 하는 경우가 생겼습니다.


쉽게 얘기해서 내가 받은 인자를 이용해서 그 인자를 포함하고 있는

자료구조가 필요한 것입니다.


직관적으로 그림에서 보면 뽀글이 자료구조의 주소는 1000 번지 입니다.

가격 포인터가 1016 이므로 16 만 빼면 1000 번지가 나오죠?

이제 우리는 struct 뽀글이 의 변수 위치를 찾은겁니다.


함수를 다시 써보죠.


int   how_much( short *내가격)

{

     struct 뽀글이 *myboggle = containerof(내가격, struct 뽀글이, 가격);


     printk(" 내가격을 멤버로 갖는 boggle 구조체의 위치는 0x%p\n", myboggle)


     myboggle->라면 = 봉지라면;


     return 0;

}


이 결과는 0x1000 이 나오게 됩니다.


컨테이너 매크로에서 필요한 것은 세가지 조건입니다.

현재 알고 있는 변수포인터.

구조체함수원형.

구조체멤버의 변수명.


복잡해 보이기도 하지만 결론적으로는


1000 = container_of(1006, struct 뽀글이, 가격)


이렇게 되는데 1006 이라는 위치를 가르쳐주고

1006 이 뽀글이의 가격이라는 멤버의 주소이다.


뽀글이 구조체에서 가격의 변수 위치는 항상 6이라는 오프셋이니까

1006 - 6 = 1000.


이렇게 되도록 짜여져 있는 매크로 입니다.


복잡한가요?


정리합니다.

container_of (prt, type, member) 

prt           :  임의의 포인터값.

type         :  ptr 을 멤버로 갖는 자료구조형(구하고자 하는 자료구조의 형).

member    :  자료구조형에서 ptr 의 멤머 이름.




출처 : http://forum.falinux.com/zbxe/index.php?document_srl=531954

신고


Posted by injunech
2015.03.24 15:01


디바이스 트리 작성법 (4편)


7 고급 주제들 

7.1 복잡한 샘플 장치

지금까지 디바이스 트리를 이해하기 위한 기본적인 정의만 다루었습니다. 
이제 좀 더 디바이스 트리를 완벽하게 사용하기 이해서 좀 더 복잡한 하드웨어를 샘플에 추가 할 필요가 있습니다. 

이런 복잡한 하드웨어로 PCI 호스트 브릿지를 추가해 보겠습니다. 
이 샘플 용 PCI 호스트 브릿지는 
0x10180000 를 제어 레지스터 주소로 갖고 
0x80000000 주소를 BARs 영역의 시작 주소를 
갖는 것으로 가정합니다. 
지금까지 사용한 예에 
다음과 같은 PCI 호스트 브릿지 노드를 추가하는 것으로 설명을 시작하겠습니다. 

pci@10180000 {
compatible = "arm,versatile-pci-hostbridge", "pci";
reg = <0x10180000 0x1000>;
interrupts = <8 0>;
};
이 예를 보면 이 PCI 호스트 브릿지는 

제어를 위한 주소가 reg 속성으로 0x10180000 번지부터 크기는 0x1000 의 범위를 가집니다. 
이 번지영역을 이용하여 호스트 브릿지를 제어하게 될겁니다. 
이 호스트 브릿지는 시스템 인터럽트에 8 번 인터럽트를 통해서 인터럽트 처리를 하게 됩니다. 

7.2 PCI 호스트 브릿지

이 섹션은 Host/PCI 브릿지 노드에 대한 설명입니다. 

사전에 양해 드리고 싶은 것은 
이 섹션을 설명할 때 여러분은 이미 PCI 에 대한 기본 지식이 
있을 것이라는 가정을 두고 있습니다 .
이 문서는 PCI 에 대한 학습문서가 아니기 때문에 
관련된 내용의 다른 곳에서 도움을 얻기 바랍니다. 
또한 ePAPR 나 Open Firmware의 PCI Bus Binding에 대한 것을 참조 하시면 많은 도움이 되실 겁니다. 
Freescale MPC5200 에 대한 완벽한 예를 거기서 찾아 보실 수 있습니다. 

7.2.1 PCI 버스 번호 매기기

우선 PCI 버스 번호를 처리하기 위한 보완된 예제를 보겠습니다.

pci@0x10180000 {
compatible = "arm,versatile-pci-hostbridge", "pci";
reg = <0x10180000 0x1000>;
interrupts = <8 0>;
bus-ranges = <0 0>;
};
보통 각각의 PCI 버스들은 해당 버스에 유일한 번호가 매겨집니다. 
이 버스 넘버링은 
위 예에서 보듯이 두개의 셀을 포함하는 bus-ranges 속성을 사용하여 
디바이스 트리내에 PCI 노드로 표현 됩니다. 
이 bus-ranges 속성 값의 
첫번째 셀은 이 노드에 부여된 버스 번호입니다.
즉 0 번 버스라는 의미가 됩니다. 
두번째 셀은 하부의 PCI 버스들의 중 가장 큰 번호를 기술합니다. 
샘플 장치는 하나의 PCI 버스만을 가지기 때문에 이 값은 0이 됩니다. 
7.2.2 PCI 주소 변환

앞에서 다루었던 외부 버스 공간과 비슷하게, 
PCI 주소 공간도 CPU 주소 공간과 하드웨어 적으로 완벽하게 분리되어 있습니다.
그래서 PCI 주소에서 CPU 주소를 얻기 위해서 주소 변환이 필요합니다. 
이 주소 변환에 사용하는 속성은 이전에 설명했던 
ranges
#address-cells
#size-cells 
속성이 사용됩니다. 
이걸 이용해서 주소 변환을 표현하는 예를 보겠습니다.

pci@0x10180000 {
compatible = "arm,versatile-pci-hostbridge", "pci";
reg = <0x10180000 0x1000>;
interrupts = <8 0>;
bus-ranges = <0 0>;

#address-cells = <3>
#size-cells = <2>;
ranges = <0x42000000 0 0x80000000 0x80000000 0 0x20000000
  0x02000000 0 0xa0000000 0xa0000000 0 0x10000000
  0x01000000 0 0x00000000 0xb0000000 0 0x01000000>;
};

위 예를 보면, 
자식 주소( PCI 주소)는 3 개의 셀을 사용합니다. 
이것은 #address-cells 속성에 정의 되어 있습니다. 
그리고 PCI 범위는 2개의 셀로 기입됩니다. 
이것은 #size-cells 셀에 정의 되어 있습니다. 
그리고 부모 주소는 1 개의 셀을 사용하고 있습니다. 
이것은 이전에 상위 노드에서 정의 하고 있습니다. 
우선 왜  PCI 주소를 사용하기 위해서 32 비트 셀이 세개가 필요한지 궁금할 것입니다.
이 세개의 셀들은 다음과 같이 phys.hi, phys.mid 그리고 phys.low 의 순서대로 나열한 자식 주소값입니다. 
phys.hi  셀 값의 의미 : npt000ss bbbbbbbb dddddfff rrrrrrrr
phys.mid 셀 값의 의미 : hhhhhhhh hhhhhhhh hhhhhhhh hhhhhhhh
phys.low 셀 값의 의미 : llllllll llllllll llllllll llllllll
PCI 주소는 64 비트 주소폭을 갖습니다 그래서 phys.mid 와 phys.low. 가 ranges 속성값 중 진짜 자식주소로 사용됩니다. 
그런데, 여기서 조금 복잡하지만 관심을 가질 것은 phys.high 입니다. 
이것은 다음과 같은 비트 필드로 구성되어 있습니다. 
n : 재 배치 가능영역 표시 
p : 프리펫치 가능 영역 표시
t : 어드레스 정렬 표시 
ss : 영역 구분 코드 
00 : 환경 설정 영역
01 : I/O 영역
10 : 32 비트 메모리 영역
11 : 64 비트 메모리 영역 
bbbbbbbb : PCI 버스 번호. PCI 는 계층적으로 구성된다. 그래서 서브 버스로 정의될 수 있는
                                           PCI/PCI 브리지를 가질수도 있다. 
ddddd : 디바이스 번호, 보통 IDSEL 신호와 연결되어 있다. 
fff : 기능 번호 . PCI 디바이스가 여러 기능을 가지고 있을 경우 사용된다. 
rrrrrrrr : 레지스터 번호 : PCI 환경 설정 단계에서 사용된다. 

PCI 주소 변환의 목적상 가장 중요한 필드들은 p 와 ss 입니다. 
phys.hi 안에 있는 p 와 ss 의 값은  PCI 주소 공간이 어떤 특성이 있는지를 나타냅니다. 
이제 샘플에 기입된 ranges 속성에 값을 해석하면 
다음과 같이 세가지 영역에 대한 것을 기술하고 있습니다. 
PCI 주소 0x80000000 를 시작주소로 갖고 32 비트 프리페치 가능한 512 MByte 크기의 메모리 영역은 
                호스트 CPU의 0x80000000 에 맵핑됩니다. 
PCI 주소 0xa0000000 를 시작주소로 갖고 32 비트 프리페치가 불 가능한 256MByte 크기의 메모리 영역은 
                호스트 CPU의 0xa0000000 에 맵핑됩니다.

PCI 주소 0x00000000 를 시작주소로 갖는 16 MByte 크기의 I/O 영역 영역은 
                호스트 CPU의 0xb0000000 에 맵핑됩니다.
운영체제 입장에서 보면 phys.hi 비트 필드는 해당 노드를 PCI 브릿지의 영역으로 다룰때 
주소변환 목적에 상관없는 필드는 무시할 수 있고 이와 관련된 정보정도만 필요합니다. 
그래서 주소 변환 목적에 상관없는 필드는 무시할 수 있다. 
경우에 따라서 어떤 운영체제는 추가적인 필드의 마스크에 대한 필요할 수 있습니다 .
이때 운영체제는 해당 필드 마스크 값을 얻기 위해서 PCI 버스 노드 에서 "pci" 문자열을 이용해서 찾을 것입니다. 
이런 경우라면 필요한 phys.hi 비트값들을 기입할 필요도 있습니다. 

7.3 복잡한 인터럽트 맵핑

이제 가장 골치아픈 PCI 인터럽트 매핑을 다룰 차례입니다. 
PCI 디바이스는 #INTA, #INTB, #INTC,  #INTD. 라고 불리는 네개의 신호선을 이용하여 인터럽트를 트리거 할수 있습니다.
만약 하나의 PCI 디바이스가 여러가지 기능을 가지고 있지 않다면 인터럽트를 #INTA 만 사용할 것입니다. 
그러나, 각 PCI 슬롯 또는 디바이스 입장에서 동일한 인터럽트가 나가지만 
인터럽트 컨트롤러에는 다른 입력으로 연결되어 있습니다. 
보통 하드웨어적으로  인터럽트 네게의 라인을 한 라인씩 시프트 되는 방식으로 연결합니다. 
예를 들어 첫번째 슬롯의 #INTA 는 PCI 인터럽트 컨트롤러에 #INTA 로 연결되지만 
두번째 슬롯의 #INTA 는 PCI 인터럽트 컨트롤러에 #INTB 로 연결되고

세번째 슬롯의 #INTA 는 PCI 인터럽트 컨트롤러에 #INTC 로 연결됩니다. 
다른 인터럽트 역시 이와 같이 쉬프트 한 상태로 연결됩니다. 
이런 연결 특성을 표현하기 위해서 
디바이스 트리는 각 PCI 인터럽트 신호를 인터럽트 콘트롤러에 맵핑하는 방법이 필요합니다. 
이런 복잡한 인터럽트 매핑에 관련된 속성은 다음과 같은 것이 있습니다. 
#interrupt-cells
interrupt-map
interrupt-map-mask

이 속성들이 PCI 인터럽트 맵핑을 기술하는데 사용될 수 있습니다. 
사실, 이 속성들은 PCI 버스의 인터럽트 매핑에 한정되어 사용되는 것은 아닙니다 .
이 속성들은 복합적인 인터럽트 맵을 기술할때 필요해서 정의 된 것이지만, 
주로 PCI의 인터럽트 같이 복잡한 인터럽트 매핑을 다룰때 사용 됩니다. 
아래에 이런 복잡한 PCI 인터럽트 매핑 예를 보여 드리겠습니다. 

pci@0x10180000 {
compatible = "arm,versatile-pci-hostbridge", "pci";
reg = <0x10180000 0x1000>;
interrupts = <8 0>;
bus-ranges = <0 0>;

#address-cells = <3>
#size-cells = <2>;
ranges = <0x42000000 0 0x80000000  0x80000000  0 0x20000000
0x02000000 0 0xa0000000  0xa0000000  0 0x10000000
0x01000000 0 0x00000000  0xb0000000  0 0x01000000>;

#interrupt-cells = <1>;
interrupt-map-mask = <0xf800 0 0 7>;
interrupt-map = <0xc000 0 0 1 &intc  9 3 // 1st slot
 0xc000 0 0 2 &intc 10 3
 0xc000 0 0 3 &intc 11 3
 0xc000 0 0 4 &intc 12 3

 0xc800 0 0 1 &intc 10 3 // 2nd slot
 0xc800 0 0 2 &intc 11 3
 0xc800 0 0 3 &intc 12 3
 0xc800 0 0 4 &intc  9 3>;
};

이 중에서 가장 먼저 interrupt-map 속성을 설명할 것입니다. 

다음 내용을 이해하기 전에 
복잡한 인터럽트 매핑에 대한 정확한 작업 표현 방법은 
        "Open Firmware Recommended Practice: Interrupt Mapping" 를 참조 하시기 바랍니다.
이 내용을 살펴보면 
interrupt-map 속성은 다음과 같은 형식을 가집니다.
interrupt-map = < 자식_인터럽트_정보1  부모_인터럽트_컨트롤러_정보1  부모_인터럽트_정보1"
                  자식_인터럽트_정보2  부모_인터럽트_컨트롤러_정보2  부모_인터럽트_정보2
                  자식_인터럽트_정보3  부모_인터럽트_컨트롤러_정보3  부모_인터럽트_정보3
   ...
>;   
  
위 예제의 한 부분인 
interrupt-map = <0xc000 0 0 1 &intc  9 3 
에서 정의 한 엔트리
0xc000 0 0 1 &intc  9 3 
를 위 정의에 대입하면 

0xc000 0 0 1  <-- 자식 인터럽트 정보1
&intc <-- 부모 인터럽트 컨트롤러 정보1
9 3 <-- 부모 인터럽트 정보 1
에 해당합니다. 
자식 인터럽트 정보1를 표현하기 위해서 사용하는 셀 수는 
#address-cells
#interrupt-cells
에 의해서 정해집니다. 위 예에서는 다음과 같이 지정되어 있는 것을 보실 수 있습니다. 
#address-cells = <3>
#interrupt-cells = <1>;
여기서 #address-cells 셀수로 표현된 주소 정보가 어떤 의미인지는 뒤에서 자세하게 설명합니다. 
#interrupt-cells 셀 수로 표현되는 것은 자식 인터럽트의 번호를 의미 합니다. 
보통 PCI 디바이스의 하드웨어의 인터럽트 번호를 의미 하는데 0으로 시작하지 않고 
        1 로 시작되는 것에 주의 하시기 바랍니다.
PCI 하드웨어 인터럽트 번호는 1 부터 시작합니다. 
이 PCI 주제 이전에 시스템 인터럽트를 설명할 때, 
시스템 인터럽트는 IRQ  번호 와 해당 인터럽트의 검출 방법에 정보가 필요하기 때문에 
인터럽트의 정보를 표현할 셀 수를 #interrupt-cells 속성을 이용하여 2 개의 셀을 지정했습니다. 
그러나 PCI 인터럽트의 검출은 항상 로우 레벨일 경우만 검출되는 것으로 규정되어 있기 때문에 
위 예에서 보듯이 PCI 인터럽트는 1 개의 셀만 지정하고 있습니다. 
이제 이 자식 디바이스 인터럽트를 받는 부모 인터럽트 컨트롤러 정보를 표현하고 있는 부분을 살펴 보겠습니다. 
예를 보듯이 
&intc
와 같이 표현하고 있습니다. 

이것은 이전 예제에 다음과 같이 표현했을때 
intc: interrupt-controller@10140000 {
compatible = "arm,pl190";
reg = <0x10140000 0x1000 >;
interrupt-controller;
#interrupt-cells = <2>;
};
사용했던 라벨 intc 를 참조 하도록 표현한 것입니다. 

이 표현에서 주의 깊게 보아야 하는 것은 
#interrupt-cells 속성입니다. 
이것은 interrupt-map 속성의 엔트리 중 
부모_인터럽트_정보1 를 지정하는 셀 수로 사용됩니다. 
#interrupt-cells = <2> 
로 지정했기 때문에 

부모_인터럽트_정보1 을 표현하기 위해서  2 개의 셀을 사용한다는 것을 알 수 있습니다.
위 예에서 보듯이 2 개의 셀을 사용해서 표현하고 있고 
9 3
이란 의미는 9 번 인터럽트 번호이며 
PCI 의 인터럽트 검출 레벨은 항상 로우 액티브 이므로 
이에 대한 표현 값으로 3을 지정하고 있습니다. 
이제 #address-cells 셀수로 표현된 주소 정보가 어떤 의미인지를 설명하겠습니다. 
위 예를 보면 이 디바이스 트리가 표현하는 샘플 보드는 
interrupt-map 속성의 주석을 보시면 알수 있듯이 
4개의 인터럽트를 가지는 2개의 PCI 슬롯을 가지고 있음을 알수 있습니다. 
그래서 인터럽트 콘트롤러에 8개의 인터럽트 라인에 대한 맵으로 표현하고 있습니다. 
하나의 PCI 버스상에서 각 PCI 디바이스들은 #INTA 등 과 같은 인터럽트 라인을 4 개밖에 없습니다. 
그래서 인터럽트가 발생했을때 어떤 디바이스의 인터럽트인지 구별하는데 문제가 있습니다. 
다행스럽게도, 모든 PCI 디바이스는 사용가능한 유일한 디바이스 번호를 가지고 있습니다.
그래서 이 디바이스 번호를 추출해야 하는데 
이것은 앞에서 다룬 다음과 같은
phys.hi  셀 값의 의미 : npt000ss bbbbbbbb dddddfff rrrrrrrr
phys.mid 셀 값의 의미 : hhhhhhhh hhhhhhhh hhhhhhhh hhhhhhhh
phys.low 셀 값의 의미 : llllllll llllllll llllllll llllllll

주소 정보 중에서 phys.hi 의 정보중 디바이스 번호의 비트 필드 정보가 필요합니다. 
이 부분을 앞에서 설명했던 부분을 다시 한번 나열해 보겠습니다. 
n : 재 배치 가능영역 표시 
p : 프리펫치 가능 영역 표시
t : 어드레스 정렬 표시 
ss : 영역 구분 코드 
00 : 환경 설정 영역
01 : I/O 영역
10 : 32 비트 메모리 영역
11 : 64 비트 메모리 영역 
bbbbbbbb : PCI 버스 번호. PCI 는 계층적으로 구성된다. 그래서 서브 버스로 정의될 수 있는
                                          PCI/PCI 브리지를 가질수도 있다. 
ddddd : 디바이스 번호, 보통 IDSEL 신호와 연결되어 있다. 
fff : 기능 번호 . PCI 디바이스가 여러 기능을 가지고 있을 경우 사용된다. 
rrrrrrrr : 레지스터 번호 : PCI 환경 설정 단계에서 사용된다. 

이 중에 
ddddd : 디바이스 번호, 보통 IDSEL 신호와 연결되어 있다. 
부분이 필요합니다. 

이 부분의 정보를 다루기 위해서 
#address-cells

에 지정된 셀 수의 정보를 나열한 것입니다. 
이때 디바이스 번호의 비트 필드 이외에는 필요 없는 정보이므로 
필요한 ddddd 정보 영역에 대한 것만 다음과 같이 나열한 것입니다 .
0xc000 0 0 
여기서 이 주소 정보의 셀은 
#address-cells = <3>
로 지정하고 있습니다. 

여기에 이 주소를 비트 마스크 하여 원하는 비트의 유효범위를 지정하기 위해서 

interrupt-map-mask = <0xf800 0 0 7>;
와 같이 지정하고 있는데 

0xf800 0 0
는 주소 정보를 추출하기 위한 비트 마스크 값이고 
7
은 인터럽트 번호를 추출하기 위한 비트 마스크 값이 됩니다

이제 interrupt-map 속성을 좀 더 설명해 보겠습니다. 

개별적인 PCI 디바이스의 인터럽트를 구별하기 위해서는 PCI 디바이스 번호와 PCI 인터럽트 번호로 
        구성된 묶음이 필요합니다. 
좀 더 쉽게 이야기 하면, 4개의 셀을 가지고 있는 자식 디바이스의 인터럽트 정보 값을 만들어야 합니다.
phys.hi, phys.mid, phys.low 의 각각에 해당하는 값을 기술하고 이를 위해서 
        #address-cells 속성 값은 3 으로 지정하고 있습니다. 
#INTA, #INTB, #INTC, #INTD 중 하나를 지정하기 위해서 #interrupt-cell 속성값은 1로 지정하고 있습니다. 

interrupt-map-mask 속성은 자식 디바이스 인터럽트 정보와 같이 4개의 셀로 구성됩니다. 
마스크 값의 각 비트들을 1로 사용하여 필드의 비트중 유요한 비트임을 표시합니다. 
이제 위 예제에 표현된 것을 정리 해 보겠습니다. 
이 예에서는 , PCI 슬롯 1 은 디바이스 id 24(0x18)을 부여합니다. 
그리고 PCI 슬롯 2 는 디바이스 id 25(0x19)를 부여합니다. 
이 값에 의해서 각 슬롯별 phys.hi 의 값은 결정합니다 
아래처럼 비트 필드의 ddddd 섹션 을 11 비트 만큼 쉬프트해서 디바이스 번호를 얻게 됩니다. 
그래서 
슬롯 1 에 대한 phys.hi 는 0xC000 이고 
슬롯 2 에 대한 phys.hi 는 0xC800 입니다. 
이 모든 것을 담은 interrupt-map 속성은 아래의 내용을 보여 준다. 
슬롯 1 의 #INTA 는 IRQ9  입니다. 주 인터럽트 콘트롤에서 로우 레벨일때 인식됩니다. 
슬롯 1 의 #INTB 는 IRQ10 입니다. 주 인터럽트 콘트롤에서 로우 레벨일때 인식됩니다. 
슬롯 1 의 #INTC 는 IRQ11 입니다. 주 인터럽트 콘트롤에서 로우 레벨일때 인식됩니다. 
슬롯 1 의 #INTD 는 IRQ12 입니다. 주 인터럽트 콘트롤에서 로우 레벨일때 인식됩니다. 
그리고 
슬롯 2 의 #INTA 는 IRQ10 입니다. 주 인터럽트 콘트롤에서 로우 레벨일때 인식됩니다. 
슬롯 2 의 #INTB 는 IRQ11 입니다. 주 인터럽트 콘트롤에서 로우 레벨일때 인식됩니다. 
슬롯 2 의 #INTC 는 IRQ12 입니다. 주 인터럽트 콘트롤에서 로우 레벨일때 인식됩니다. 
슬롯 2 의 #INTD 는 IRQ9  입니다. 주 인터럽트 콘트롤에서 로우 레벨일때 인식됩니다. 
interrupts = <8 0>; 라고 표현된 속성은 호스트/PCI-브릿지 컨트롤러 자신에게 트리거 될 수 있는 인터럽트들을 기술합니다.
이런 인터럽트들은 PCI 디바이스들이 트리거 할 수 있는 인터럽트들과 혼합하지 마시기 바랍니다. 
마지막으로 설명할 것은. 
interrupt-parent 속성인데 
interrupt-map 속성은 모든 자식 및 손자들 노드들에 대해서 디폴트 인터럽트 콘트롤러를 바꿀 수 있습니다. 
이 PCI 예제에서는, PCI 호스트 브릿지가 디폴트 인터럽트 컨트롤러가 된다는 것을 표현하고 있음을 앞에서 설명했습니다. 
만약에 PCI 버스를 통해 연결된 디바이스가 또 다른 인터럽트 컨트롤러에 바로 연결되어 있다면,
interrupt-parent 속성을 가지도록 기술되어야 합니다 .


신고


Posted by injunech
2015.03.24 15:00


디바이스 트리 작성법 (3편)

4.  인터럽트 처리 방법

디바이스 트리 구조내에서 디바이스들은 트리 구조로 계층화 표현이 가능합니다. 
인터럽트는 이런 계층 구조가 곤란 합니다. 
보통 인터럽트는 디바이스의 하드웨어가 인터럽트를 발생하고 
인터럽트 컨트롤러가 해당 신호를 수신하는 구조로 되어 있습니다. 
그래서 인터럽트는 디바이스 트리의 구조와 별도로 디바이스 노드간에 링크 구조로 표현됩니다. 
인터럽트는 디바이스 노드의 속성의 형태로 표현합니다. 
네개의 속성이 인터럽트 간의 연결을 표현하기 위해서 준비 되어 있습니다.
다음은  네개의 속성에 대한 간단한 설명입니다. 
- interrupt-controller 속성
interrupt-controller 속성 은 값이 없는 빈 속성으로 해당 노드의 디바이스가 
인터럽트 신호를 수신하는 인터럽트 컨트롤러 디바이스 임을 표현합니다. 

- interrupt-parent
interrupt-parent 속성을 통해서 라벨 참조 형태로 지정하는 디바이스를 
디바이스 트리의 계층 구조에서 
가장 상위의 인터럽트 컨트롤러로 정의 하게 됩니다. 
interrupt-parent 속성을 갖지 않은 노드들은 그들의 부모 노드중 
interrupt-parent 속성이 선언된 노드의 interrupt-parent 속성을 상속 받습니다. 
- #interrupt-cells 속성
#interrupt-cells 속성에 지정된 값은 interrupts 속성에서 인터럽트에 대한 정보를 속성값으로 표현할때 
사용될 셀수를 나타냅니다. ( #address-cells 와 #size-cells 과 비슷한 역활을 합니다.  )
interrupt-controller 속성이 선언된 노드에 같이 선언됩니다. 
- interrupts 
interrupts  속성은 디바이스가 발생하는 인터럽트 출력 신호에 대한 정보의 리스트를 값으로 표현합니다. 
interrupts 의 속성에 표현되는 한개의 인터럽트에 대한 정보는 여러개의 셀로 구성됩니다. 
이 인터럽트는 인터럽트 컨트롤러에 연결된 인터럽트 입력에 정보 입니다. 
아래 예에 보듯이 대부분의 디바이스는 보통 하나의 인터럽트 출력만 있습니다. 
그러나 어떤 디바이스는 여러개의 인터럽트 출력이 있을 수 있습니다. 
interrupts 의 속성 값에 지정되는 정보의 의미는 인터럽트 컨트롤러에 의존적입니다. 
인터럽트 콘트롤러 마다 인터럽트를 다루기 위한 나름대로의 특성이 있으므로 이에 맞는 것으로 정의하고 
이에 따라서 셀수가 결정됩니다. 

아래 코드는 Coyote's Revenge 샘플 장치에 인터럽트와 관련된 것을 추가 하였습니다. 

/ {
compatible = "acme,coyotes-revenge";
#address-cells = <1>;
#size-cells = <1>;
interrupt-parent = <&intc>;
cpus {
#address-cells = <1>;
#size-cells = <0>;
cpu@0 {
compatible = "arm,cortex-a9";
reg = <0>;
};
cpu@1 {
compatible = "arm,cortex-a9";
reg = <1>;
};
};
serial@101f0000 {
compatible = "arm,pl011";
reg = <0x101f0000 0x1000 >;
interrupts = < 1 0 >;
};
serial@101f2000 {
compatible = "arm,pl011";
reg = <0x101f2000 0x1000 >;
interrupts = < 2 0 >;
};
gpio@101f3000 {
compatible = "arm,pl061";
reg = <0x101f3000 0x1000
0x101f4000 0x0010>;
interrupts = < 3 0 >;
};
intc: interrupt-controller@10140000 {
compatible = "arm,pl190";
reg = <0x10140000 0x1000 >;
interrupt-controller;
#interrupt-cells = <2>;
};
spi@10115000 {
compatible = "arm,pl022";
reg = <0x10115000 0x1000 >;
interrupts = < 4 0 >;
};
external-bus {
#address-cells = <2>
#size-cells = <1>;
ranges = <0 0  0x10100000   0x10000     // Chipselect 1, Ethernet
1 0  0x10160000   0x10000     // Chipselect 2, i2c controller
2 0  0x30000000   0x1000000>; // Chipselect 3, NOR Flash
ethernet@0,0 {
compatible = "smc,smc91c111";
reg = <0 0 0x1000>;
interrupts = < 5 2 >;
};
i2c@1,0 {
compatible = "acme,a1234-i2c-bus";
#address-cells = <1>;
#size-cells = <0>;
reg = <1 0 0x1000>;
interrupts = < 6 2 >;
rtc@58 {
compatible = "maxim,ds1338";
reg = <58>;
interrupts = < 7 3 >;
};
};
flash@2,0 {
compatible = "samsung,k8f1315ebm", "cfi-flash";
reg = <2 0 0x4000000>;
};
};
};
주목해야 할 몇 가지:
위 코드에 표현하고 있는 샘플 장치에는 
노드 이름이 interrupt-controller@10140000 로 지정된 한개의 인터럽트 컨트롤러를 가지고 있다. 

여기서 특이하게 'intc:' 라고 하는 라벨이 인터럽트 컨트롤러 노드에 추가된 것을 볼 수 있습니다. 
이 라벨은 루트 노드에 interrupt-parent 속성에서 사용합니다. 
interrupt-parent = <&intc>;
이 interrupt-parent 속성 값은 시스템의 디폴트 인터럽트 컨트롤러를 지정합니다. 
자식 노드들은 해당 인터럽트를 수신하는 것이 어떤 것인가를 지정하지 않고 
상위 노드에서 interrupt-parent 속성으로 지정된 인터럽트 컨트롤러를 해당 디바이스 노드에 연결된 
인터럽트 컨트롤러를 디폴트로 지정합니다. 
왜냐하면 모든 자식 노드들은 해당 속성을 쓰지 않아도 상위 노드에 지정된  interrupt-parent 속성이 상속되기 때문이다. 
각 디바이스의 interrupts 속성은 각각의 인터럽트 입력 라인을 기술하기 위해서 사용합니다.
이때 인터럽트 컨트롤러를 표현한 노드에 정의된 #interrupt-cells 속성은 
interrupts 속성에서 하나의 인터럽트에 대한 기술을 하기 위한 셀 수를 지정합니다. 
위 예에서 "#interrupt-cells" 은 2 입니다. 그래서 하나의 인터럽트 입력을 표현하기 위해서는 2 개의 셀이 필요합니다. 
이 예에서는 인터럽트 라인 번호를 표현하기 위해서 첫번째 셀이 쓰입니다. 
그리고 두번째 셀은 액티브 되는 인터럽트 레벨의 조건이 레벨 하이인지 로우인지 엣지인지를 나타냅니다. 
여러분이 다루고자 하는 인터럽트 컨트롤러에 인터럽트 입력에 대한 부분을 다루고자 한다면 
해당 인터럽트 컨트롤러의 매뉴얼을 참조해야 합니다. 
5.  디바이스 표현 데이터

지금까지 다룬 공통된 속성이외에, 여러분이 필요하다고 생각되는 임의의 속성이나 자식 노드들을 
노드에 포함시킬 수 있습니다. 
만약에 디바이스 트리에 여러분이 다루는 운영체제에서 필요로 하는 어떤 데이터를 추가 하고 싶다면
다음과 같은 규칙 하에 추가 하시기 바랍니다. 
첫째, 새로운 디바이스 노드에 추가할 속성 이름은 표준 속성 이름과 충돌을 방지하기 위해서 제조사 접두사를 붙이싶시오.
두번째, 속성의 의미와 자식 노드에 대한 것은 
운영체제의 디바이스 드라이버 개발자가 해당 내용을 이해할 수 있도록 바인딩 문서에 문서화 하시기 바랍니다. 
여기서 디바이스 트리에서 바인딩이라고 하는 의미는 
디바이스 트리 안에서 디바이스가 표현되는 것을 의미합니다. 
바인딩관 관련되어 기술되는 문서에는 해당 디바이스 노드에 
새로 만들어지는 속성은 어떤 의미인지, 속성은 어떤 값을 갖을 수 있는지 , 자식 노드를 가지고 있는지, 
기술하고자 하는 디바이스가 어떤 디바이스인지가 표현되어야 합니다. 
디바이스를 표현하는 노드 각각은 compatible 속성 값에 각각이 서로 구별될 수 있는 고유의 문자열을 사용하여야 합니다. 
새로운 디바이스들에 대한 바인딩은 이 문서가 있는 사이트의 위키에 문서화 되어야 합니다.
문서 형식과 검토 프로세스에 대해서 설명하는 메인 페이지를 보시기 바랍니다 
세번째, devicetree-discuss@lists.ozlabs.org 메일링 리스트에 새로 만들어진 바인딩과 관련된 검토 내용을 
        포스팅하시기 바랍니다.
포스팅을 하시면 나중에 발생할 수 있는 많은 예상되는 문제들 중 공통된 실수를 잡을 수 있습니다. 

6.  특별한 노드들 

6.1 aliases 노드

어떤 노드는 노드 이름으로 /external-bus/ethernet@0,0 와 같이 풀 패쓰 즉 긴 이름을 사용합니다. 
또는 우리는 이런 긴 이름보다는 eth0  와 같은 짧은 이름을 종종 사용합니다. 
그래서 디바이스 트리를 볼 때 "eth0 가 무슨 디바이스일까?" 라는 생각으로 보게 됩니다 .
이런 경우들에 대해서 디바이스 트리는 aliases 라는 노드를 제공하여 
쉽게 긴이름을 짧은 이름으로 대치하게 하거나 일반적으로 알수 있는 용어로 바꾸어 사용할 수 있도록 
해 줍니다. 
예를 들면:

aliases {
ethernet0 = &eth0;
serial0 = &serial0;
};
즉 ethernet0 는 eth0 라는 이름과 같다는 것을 표현해 줄 수 있습니다. 

aliases 안에 기술되는 형식은 

별명  = &라벨;
형태가 됩니다. 

이런 문법 표현은 별명에 해당하는 문자열을 라벨에 대한 참조로 사용할 수 있도록 합니다. 
이런 표현은 운영체제가 디바이스 의 식별자를 부여하는 방법으로 환영할 만한 것입니다. 

그리고 위 표현 방법은 의미는 interrupt-parent 속성 값을 표현 할때 사용했던 "< &label >" 와는 다른 것임을 주의 하십시오. 
6.2 chosen 노드

chosen 노드는 실제 디바이스를 디바이스 트리에 표현하는 것은 아닙니다 .

단지, 부트 아큐먼트와 같은, 펌웨어에서 운영 체제에 전달된 데이터를 파씽하기 위한 방법으로 제공됩니다.
일반적으로 .dts 소스 파일들내에 기술된 chosen 노드는 왼쪽 키 부분이 비어 있습니다. 
         그리고 그 정보는 부팅 동안만 유효합니다.
예제 시스템에서, 펌웨어는 chosen 노드를 다음과 같이 추가 하도록 하겠습니다.

chosen {
bootargs = "root=/dev/nfs rw nfsroot=192.168.1.1 console=ttyS0,115200";
};


신고

'Programming > Linux/Unix' 카테고리의 다른 글

[Kernel Macro] container_of 매크로  (0) 2015.03.26
디바이스 트리 작성법 (4편)  (0) 2015.03.24
디바이스 트리 작성법 (3편)  (0) 2015.03.24
디바이스 트리 작성법 (2편)  (1) 2015.03.24
디바이스 트리 작성법 (1편)  (0) 2015.03.24
[Linux] Module 관련  (0) 2015.03.13


Posted by injunech
2015.03.24 14:59


디바이스 트리 작성법 (2편)

3 주소지정 방법
디바이스의 주소를 디바이스 트리에 표현하기 위해서 정해진 속성에는 다음 세가지가 있습니다. 

#address-cells
#size-cells
reg
이 속성은 서로 연관 관계를 가지고 있습니다. 
"#address-cells" 속성과 "#size-cells" 속성은 reg 속성의데이터에 대한 갯수 규칙을 지정합니다. 
"#address-cells" 속성과 "#size-cells" 은 부모 노드에서 지정하고 
reg 속성 은 자식 노드에 지정합니다. 
우선 reg 속성에 대해서 알아 보겠습니다. 
reg 속성은 다음과 같은 형식을 가집니다. 
"reg = <주소1 길이1 [주소2 길이2] [주소3 길이3] ... >"
주소와 길이가 하나의 묶음이 됩니다. 
보드에 있는 디바이스를 제어하기 위해서 하나의 주소일수도 있고 여러개의 주소를 가지고 있을 수 있습니다. 
또 각 주소는 연속적일 수도 있고 불연속 적일 수 도 있습니다. 
그래서 시작주소와 시작주소부터의 주소 범위를 하나의 묶음으로 지정하고 이것을 나열하게 됩니다. 
여기서 우선 셀(cell) 이라는 용어에 대해서 알아 보겠습니다. 
주소나 길이를 나타내기 위해서는 32비트 정수값을 사용합니다. 
예를 들면 0x12345678 또는 0 처럼 표현할 수 있습니다. 
이런 값 하나를 셀이라고 합니다. 
결론적으로 셀은 32비트 정수값이라고 보시면 됩니다. 
reg 는 다음과 같이 셀들의 데이터 집합으로 표현합니다.
reg = <0x0C00 0x0 0xFFFF02 0x3333>
여기서 0x0C00 0x0 0xFFFF02 0x3333 이 셀들입니다. 
그런데 여러분은 이 값들의 의미가 이런 식으로 될것이라고 예상 할 수 있을 겁니다. 
0x0C00  : 시작 주소 , 0x0  : 주소 범위 크기 
0xFFFF02 : 시작 주소 , 0x3333 : 주소 범위 크기 
예.. 이렇게 생각하면 맞을 수 도 있고 틀릴수도 있습니다. 
만약에 64비트 주소 체계라면 어떻게 하시겠습니까?
아니면 특수한 주소라서 32비트로 모두 표현할수 없다면?
이런 문제를 해결하기 위해서 
"#address-cells" 속성과 "#size-cells" 속성이 있는 겁니다. 
보통 다음과 같이 reg 의 셀들이 주소를 지정하기 위해서 몇개의 셀을 사용할지를 결정하고 
길이를 지정하기 위해서 몇개의 셀들을 사용할지를 결정해 주는 것이 바로 
"#address-cells" 속성과 "#size-cells" 속성입니다. 
이름 처럼 
#address-cells 속성은 reg 속성 값에 시작 주소를 지정하기 위해서 몇개의 셀을 사용할 것인가를 지정합니다. 
#size-cells 속성은 reg 속성 값에 길이를 지정하기 이해서 몇개의 셀을 사용할 것인가를 지정합니다. 
개념 설명을 하기 위해서 시작 주소는 2개의 셀을 가지고 길이는 1개의 셀을 가질 경우에 대한 예를 보여 드리겠습니다. 
실제 사용 방법은 CPU 와 디바이스를 추가 할때 다시 설명할 것입니다. 
parent {
#address-cells = <2>;
#size-cells = <1>;
child {
:
reg = <0xC0000000 0x0000 1024>;
};
child {
:
reg = <0xD0000000 0x0000 1024 0xE0000000 0x0000 2048>;
};
};
샘플 예를 보시면 아시겠지만 
#address-cells  속성과 #size-cells 은 자식 노드에만 영향을 줍니다. 
자신에 대한 셀 에 대한 지정은 상위 노드에서 지정해야 합니다. 
이제 주소 지정과 관련하여 CPU 부터 디바이스 까지 살펴 보겠습니다. 

3.1 CPU 주소 지정

보통 주소라 하면 머리속에 떠오는 것이 하나는 버스 주소일 겁니다. 
하지만 주소라는 것을 이런 제한적인 개념으로 보시지 말고 
어떤 디바이스를 접근하거나 다루기 위한 구별자로 보시는 것이 편합니다. 
CPU 의 주소 지정 역시 버스 주소가 아닌 구별자로써 의미를 갖습니다.
다음 CPU 노드에 대한 표현은 CPU 주소 지정에 대한 간단한 예입니다. 
cpus {
#address-cells = <1>;
#size-cells = <0>;
cpu@0 {
compatible = "arm,cortex-a9";
reg = <0>;
};
cpu@1 {
compatible = "arm,cortex-a9";
reg = <1>;
};
};

각 CPU 의 선언에는 주소를 이용하여 하나의 고유 ID 를 주소로 부여하고,
크기에 대한 정보는 없음을 보여 주고 있습니다. 

위 예에서 cpus 노드에 "#address-cells" 은 1 로 설정하고 "#size-cells"은 0 으로 설정하고 있습니다. 

이 의미는  cpu 각각을 표현ㅎ는 자식 노드의 reg 속성 값의 셀들에 주소는 1개의 셀로 되어 있고 길이 값은 셀을 사용하지 않으므로 아예 없음을 나타냅니다.

좀 쉽게 이야기하면 reg 속성값에 셀들이 모두 주소란 이야기지요.. 

이전에 노드 이름을 지정할 때 주소는 구별하기 위한 값으로 어떤 것을 사용해도 된다고 말씀드렸습니다. 
하.. 지.. 만...
규정에는 노드의 reg 속성값으로 사용된 첫번째 주소 값을 노드 이름의 장치 주소에 사용하도록 하고 있습니다. 
위 예도 이름에 대하여 이런 룰을 적용하고 있습니다.
3.2 디바이스 메모리 주소 지정

이제 디바이스 에 주소 정보를 표현하는 방법을 알아 보겠습니다. 
CPU 를 다루는 것보다 조금 더 복잡하지만 속성의 사용법은 다른게 없습니다. 
우선 다음 예를 보아 주시기 바랍니다. 
/ {
#address-cells = <1>;
#size-cells = <1>;
...
serial@101f0000 {
compatible = "arm,pl011";
reg = <0x101f0000 0x1000 >;
};
serial@101f2000 {
compatible = "arm,pl011";
reg = <0x101f2000 0x1000 >;
};
gpio@101f3000 {
compatible = "arm,pl061";
reg = <0x101f3000 0x1000
0x101f4000 0x0010>;
};
interrupt-controller@10140000 {
compatible = "arm,pl190";
reg = <0x10140000 0x1000 >;
};
spi@10115000 {
compatible = "arm,pl022";
reg = <0x10115000 0x1000 >;
};
...
};
우선 디바이스 주소에 정보를 다루기 위해서 reg 속성을 사용합니다. 
그리고 reg 속성의 값에서 주소와 길이를 나타내기 위해서 사용하는 셀의 갯수를 나타내기 위해서 
먼저 각 디바이스 노드를 포함하는 부모 노드인 "/" 노드에서 
#address-cells 를 사용하여 시작 주소를 표현하기 위해 몇개의 셀을 사용하는지를 나타내고 있습니다.
여기서는 1 이므로 한개의 셀 즉 32 비트 부호없는 정수를 사용해서 시작 주소를 나타냄을 표현하고 있습니다.
마찬가지로 #size-cells 를 사용해서 길이를 나타내기 위해서 몇개의 셀을 사용하지도 나타내고 있습니다. 
일반적으로 이 셀 수는 32 비트 시스템에서는 1을 64 비트 시스템에서는 2를 사용합니다. 
위예에서  GPIO 디바이스 주소를 살표보면 
GPIO 디바이스 주소는 0x101f3000 ~ 0x101f3fff 와 0x101f4000 ~ 0x101f400f 의  두 개 주소 범위가 부여되고 있음을 알 수 있습니다. 
그런데 
노드들은 하부에 자식 노드들이 포함될 수 있고 해당 자식 노드 역시 reg 속성을 가질 수 있습니다. 
이때 #address-cells 과 #size-cells 은 특별히 아래에서 선언하지 않으면 상속되어 

계속 적용됩니다.
그런데 상위에서는 하나의 주소값만이 필요하지만 하부 노드에서는 두개의 셀로 구성된 주소값이 필요할 수도 있습니다. 
일단 예를 보겠습니다. 
external-bus {
#address-cells = <2>
#size-cells = <1>;
ethernet@0,0 {
compatible = "smc,smc91c111";
reg = <0 0 0x1000>;
};
i2c@1,0 {
compatible = "acme,a1234-i2c-bus";
reg = <1 0 0x1000>;
rtc@58 {
compatible = "maxim,ds1338";
};
};
flash@2,0 {
compatible = "samsung,k8f1315ebm", "cfi-flash";
reg = <2 0 0x4000000>;
};
};

이 예를 보면 
#address-cells 속성에 2 를 부여하고 있음을 볼 수 있습니다. 
즉 주소를 표현하기 위해서 2개의 셀을 사용하고 있습니다. 
왜냐하면 i2c 에 대한 표현을 하기 위해서는 
버스 와 버스의 주소에 대한 두개의 정보가 필요하기 때문입니다. 
다른 말로 칩 선택 번호, 오프셋 이라고 하면 될 듯 합니다. 
이때 가장 상위의 노드 , 여기서는 external-bus 가 그 노드에 해당되는데, 
이 곳에서 #address-cells 과 #size-cells 로 그 하부의 노드들의 reg 속성 값에 대한 
사용 셀 수를 지정하고 있습니다. 
어떤 디바이스는 주소를 표현하는데 하나의 셀을 사용할 것이고 
어떤 디바이스는 두개의 셀을 사용할 것입니다. 
이런 경우에는 그 하부에 다시 셀에 대한 설정을 하지 않는다면 
가장 상위의 셀 수 에 대한 정의가 모두 적용되므로 
가장 많은 셀 수를 사용하는 디바이스 노드에 대한 수로 지정해 버리고 
하부의 모든 노드에 동일하게 표현하는 겁니다. 
여기서 기억해야 할 것이 
노드이름에 대한 장치 주소는 reg 속성의 첫번째 주소 지정의 값을 사용해야 한다고 했습니다. 
위 예에서 플래쉬의 경우를 보면 
노드 명을 "flash@2,0" 을 사용하고 있습니다. ',' 로 분리하고 있는데 
노드의 reg 속성에 첫번째 주소를 지정하기 위해서 2 와 0 을 사용하고 있습니다 .
이것을 ',' 로 분리해서 지정하고 있음을 기억해 주시기 바랍니다. 
3.3 직접 메모리 맵에 대상이 안되는 장치들 

어떤 디바이스들은 프로세서 버스에 직접 메모리 맵핑이 될 수 없는 것들이 있습니다. 
이런 예를 다음과 같이 들어 보겠습니다.

i2c@1,0 {
compatible = "acme,a1234-i2c-bus";
#address-cells = <1>;
#size-cells = <0>;
reg = <1 0 0x1000>;
rtc@58 {
compatible = "maxim,ds1338";
reg = <58>;
};
};
maxim 의 ds1338 디바이스는 i2c 버스 상에서 주소가 필요 합니다. 
이것은 CPU 의 로컬 버스 주소는 아니기 때문에 메모리 맵핑이 되지 않은 경우입니다. 
이런 경우에는 #address-cells 와 #size-cells 를 이용해서 하부에 대한 
셀 수를 재 지정합니다. 
이때는 길이에 대한 것이 없는 경우입니다. 

3.4 Ranges 속성(주소 번역)

지금까지 어떻게 디바이스에 주소를 부여하는지를 다루어 봤습니다. 
하지만 지금 까지 다룬 주소는 각 디바이스 노드의 입장에서 주소를 다룬 것입니다. 
i2c 버스에 연결된 디바이스를 제어하기 위해서는 i2c 버스 상에 표현되는 주소만 필요할 것입니다. 
CPU 에 연결된 시리얼 디바이스라면 CPU 와 연결된 로컬 버스상의 주소를 다룰 것입니다. 
하지만 i2c 버스에 연결된 디바이스를 CPU 입장에서 다룬다면 
CPU 는 로컬 버스 주소와 i2c 버스간에 변환 방법을 알아야 합니다. 
왜냐하면 CPU 입장에서는 i2c 버스의 주소 정보는 로컬 버스에 적용할 수 없기 때문입니다. 
디바이스 트리의 루트 노드는 보드 그 자체이고 루트 노드의 주소는 cpu 가 접근하는 주소 공간입니다. 

그래서 모든 주소 정보는 CPU  의 입장에서 해석 가능해야 합니다. 
조금 더 구체적으로 이야기 하면 
i2c 버스에 연결된 디바이스는 CPU의 주소 공간에서 직접적으로 접근이 불가능합니다.
반드시 i2c 컨트롤러를 이용하여 접근합니다. 
즉 i2c 버스에 연결된 디바이스는 i2c 버스에서 선택하고 접근할 수 있는 주소 뿐만아니라 
이런 정보를 적용할 i2c 컨트롤러와 연관된 주소도 알려 주어야 합니다. 
물론 모든 디바이스가 이런 제한을 갖는 것은 아닙니다. 

만약 serial 디바이스 가 CPU 의 도메인 공간에서 접근 할수 있는 로컬 버스에 연결되어 있고 맵핑되어 있다면
serial 디바이스 의 노드에 표현된 노드의 reg 속성에 있는 주소 정보는 CPU 입장에서는 바로 사용할 수 있습니다. 
그러나 앞에서 예를 든 i2c 버스에 연결된 디바이스는 
CPU 가 접근 가능한 i2c 컨트롤러와 관련된 부가 주소 정보를 더 제공해야 합니다.
정리하면 모든 디바이스들은 각 디바이스 노드의 주소 도메인과 
CPU 의 주소 도메인간의 변환이 가능하도록 관련된 정보를 제공해야합니다.
이런 정보를 제공하는 것이 바로 ranges 속성입니다. 
즉 부모 도메인과 자식 도메인간의 주소 변환 정보를 이 ranges 속성을 통해서 제공하는 것입니다. 
아래에 예는 ranges 속성을 어떻게 사용하는지에 대한 것 입니다. 

/ {
compatible = "acme,coyotes-revenge";
#address-cells = <1>;
#size-cells = <1>;
...
external-bus {
#address-cells = <2>
#size-cells = <1>;
ranges = <0 0  0x10100000   0x10000     // Chipselect 1, Ethernet
     1 0  0x10160000   0x10000     // Chipselect 2, i2c controller
  2 0  0x30000000   0x1000000>; // Chipselect 3, NOR Flash
ethernet@0,0 {
compatible = "smc,smc91c111";
reg = <0 0 0x1000>;
};
i2c@1,0 {
compatible = "acme,a1234-i2c-bus";
#address-cells = <1>;
#size-cells = <0>;
reg = <1 0 0x1000>;
rtc@58 {
compatible = "maxim,ds1338";
reg = <58>;
};
};
flash@2,0 {
compatible = "samsung,k8f1315ebm", "cfi-flash";
reg = <2 0 0x4000000>;
};
};
};
ranges 속성은 주소 변환에 관련된 정보를 제공하는 리스트 구조를 가집니다. 
ranges 속성의 값은 다음과 같은 형식을 갖습니다. 
ranges = <
자식주소1 부모주소1 자식주소크기1
자식주소2 부모주소2 자식주소크기2
자식주소3 부모주소3 자식주소크기3
자식주소4 부모주소4 자식주소크기4
 >; 

위 형식에서 보듯이 자식주소, 부모주소, 그 자식들의 주소 공간의 크기 의 묶음들의 리스트로 표현됩니다.
이 값을 지정하는 방식은 앞에서 설명했듯이 셀이라는 단위로 지정됩니다. 
그렇다면 각 주소와 크기를 나타내기 위해서 몇개의 셀이 사용되는지를 지정하는 속성이 예상됩니다. 
실제로 사용은 이미 앞에서 설명한 reg 에 사용된 것이 그대로 사용됩니다. 
자식 주소와 부모 주소의 셀 수를 지정하는 것은 이전에 사용했던 "#address-cells" 을 사용합니다. 
크기의 셀 수를 지정하는 것 역시 #size-cells 를 사용합니다. 
그런데 이 #address-cells 속성과 #size-cells 은 부모용과 자식용이 따로 없습니다. 
그렇기 때문에 부모 노드에서 지정한 #address-cells 속성 은 부모 주소의 셀수를 지정하고 
자식 노드에서 지정한 #address-cells 속성 은 자식 주소의 셀수를 지정합니다. 
자식 주소 크기를 나타내는 셀 수는 자식 노드에 선언된 #size-cells 속성에 지정된 크기를 셀 수로 사용합니다. 
이 설명을 아래예에 나타냈습니다. 
외쪽 화살표와 함께 사용한 주석을 잘 보시기 바랍니다. 
/ {
compatible = "acme,coyotes-revenge";
#address-cells = <1>;  <--- ranges 속성 값에서 부모 주소 셀 수 지정
#size-cells = <1>;
...
external-bus {
#address-cells = <2> <-- ranges 속성 값에서 자식 주소 셀 수 지정
#size-cells = <1>;  <-- ranges 속성 값에서 자식 주소 크기 셀 수 지정
ranges = <0 0  0x10100000   0x10000     // Chipselect 1, Ethernet
     1 0  0x10160000   0x10000     // Chipselect 2, i2c controller
  2 0  0x30000000   0x1000000>; // Chipselect 3, NOR Flash
        ...
보시면 아시겠지만 
외부버스에 선언하는 ranges 속성에서 표시하는 자식 주소는 2개의 셀을 갖습니다. 
부모 주소는 1 개의 셀을 갖고 크기 역시 1개의 셀만을 사용하게 됩니다. 
그래서 위 예에서 ranges 속성에 첫번째 주소 변환 정보인
ranges = <0 0  0x10100000   0x10000
은 0 과 0 이 자식 주소 정보이고 0x10100000 은 부모 즉 CPU 입장에서 로컬 버스 주소가 되고 0x10000 은 자식 주소 크기가 됩니다. 
이것을 외부버스에 대한 해석을 한다면 다음과 같이 해석이 됩니다. 

칩 셀렉트 0 번의 오프셋 0은 0x10100000..0x1010ffff 의 주소 범위로 맵핑됩니다. 
칩 셀렉트 1 번의 오프셋 0은 0x10160000..0x1016ffff 의 주소 범위로 맵핑됩니다. 
칩 셀렉트 2 번의 오프셋 0은 0x30000000..0x10000000 의 주소 범위로 맵핑됩니다. 

(***) 아래 부분은 오역이 있을 수 있으므로 영어가 되시는 분은 꼭 원문을 확인해 보실것!!!
만약 부모와 자식 주소 공간이 동일하다면, 값이 빈 ranges 속성을 사용할 수 있습니다.
값이 빈 ranges 속성이 있다는 것은 자식의 주소 공간과 부모의 주소 공간이 1:1로  맵핑된다는 것을 의미합니다.
1:1 맵핑 되는 경우에도 ranges 속성을 사용해서 주소 변환 정보를 제공할 필요가 있을까 하는 궁금증이 생길수 있습니다 .
이런 경우의 예로 로컬 버스와 전혀 다른 주소 공간을 갖는 PCI 같은 특정 버스는 운영체제입장에서 로컬 메모리 공간으로 맵핑되어야 하고 보여야 합니다. 
또 다른 경우라면 어떤 디바이스는 DMA 엔진을 갖을 수 있는데 이와 관련된 처리를 하려면 실제 버스 상의 물리적 주소값을 알 필요가 있습니다. 
디바이스들이 동일한 소프트웨어적으로 프로그램 가능한 물리적 주소를 모두 공유하는 경우에 디바이스는 서로 구룹 지어질 필요도 있기 때문입니다. 
어떻든지 간에, 1:1 맵핑은 운영체제가 필요로 하거나 하드웨어 디자인에 따라서 필요하고 사용됩니다. 
여러분은 i2c@1,0  노드에 ranges 속성이 없음을 알려야 합니다. 
그 이유는 외부버스와 다르게, i2c 버스위에 있는 디바이스는 CPU 주소 영역에 메모리 주소 맵핑을 할 수 없기 때문입니다.
대신에, CPU는 rtc@58 디바이스는 i2c@1,0 를 통해서 간접적으로 접근합니다. 
ranges 속성이 없음은 해당 디바이스의 부모 이외엔 어떤 디바이스도 직접적인 접근을 할수 없음을 의미합니다.

(***) 여기까지 확인해 보실 것..
원문 
Alternately, if the parent and child address spaces are identical, then a node can instead add an empty ranges property. The presence of an empty ranges property means addresses in the child address space are mapped 1:1 onto the parent address space.
You might ask why address translation is used at all when it could all be written with 1:1 mapping. Some busses (like PCI) have entirely different address spaces whose details need to be exposed to the operating system. Others have DMA engines which need to know the real address on the bus. Sometimes devices need to be grouped together because they all share the same software programmable physical address mapping. Whether or not 1:1 mappings should be used depends a lot on the information needed by the Operating system, and on the hardware design.
You should also notice that there is no ranges property in the i2c@1,0 node. The reason for this is that unlike the external bus, devices on the i2c bus are not memory mapped on the CPU's address domain. Instead, the CPU indirectly accesses the rtc@58 device via the i2c@1,0 device. The lack of a ranges property means that a device cannot be directly accessed by any device other than it's parent.


신고

'Programming > Linux/Unix' 카테고리의 다른 글

디바이스 트리 작성법 (4편)  (0) 2015.03.24
디바이스 트리 작성법 (3편)  (0) 2015.03.24
디바이스 트리 작성법 (2편)  (1) 2015.03.24
디바이스 트리 작성법 (1편)  (0) 2015.03.24
[Linux] Module 관련  (0) 2015.03.13
[Makefile] Kbuild 와 Module Compile  (0) 2015.03.09


Posted by injunech
2015.03.24 14:58


디바이스 트리 작성법 (1편)

이 문서는 디바이스 트리 작성법을 설명한 
http://devicetree.org/Device_Tree_Usage  웹 문서를 번역 및 의역한 것입니다. 
오역 및 잘못된 내용이 있을 수 있습니다. 

제목 : 디바이스 트리 작성법

이 문서는 새로 만들 하드웨어에 대한 디바이스 트리를 어떻게 작성하는지를 다룹니다. 

이 문서가 작성된 목적은 디바이스 트리에 대한 개념을 설명하고 디바이스 트리에 디바이스를 
어떻게 표현하는 가를 설명한 것입니다. 
이 문서는 가장 기본적인 내용만 다루고 있기 때문에 
더 자세한 디바이스 트리의 데이터 포맷이 알고 싶다면 ePAPR 기술 설명서를 참고 하는 것이 좋습니다. 

목차

1. 기본 데이터 형식
2. 기본적인 개념
2.1 장치예
2.2 최초 구조체
2.3 CPU들 
2.4 노드 이름들 
2.5 디바이스들 
2.6 compatible 속성 이해하기 
3. 주소지정 방법
3.1 CPU 주소 지정
3.2 장치 메모리 맵
3.3 메모리 맵에 대상이 안되는 장치들 
3.4 주소 변환 범위들
4.  인터럽트 처리 방법
5.  디바이스 표현 데이터
6.  특별한 노드들 
6.1 별칭 노드들 
6.2 선택 노드
7 조금더 복잡한 주제들
7.1 복잡한 장치 예
7.2 PCI 호스트 브릿지
7.2.1 PCI 버스 번호 매기기
7.2.2 PCI 주소 변환
7.3 복잡한 인터럽트 맵핑
8 참조할 만한 것들 

본문 

1 기본 데이터 형식

디바이스 트리는 노드들로 구성된 트리 구조를 갖습니다.
노드는 또 다른 노드들과 property 라고 불리는 속성을 갖습니다. 
속성은 키 와 값으로 구성된 한쌍으로 이루어져 있습니다. 
예를 들어, 아래는 .dts 확장자가 붙은 파일의 간단한 트리의 내용이다. 
아래에 내용은 보통 확장자가 dts로 붙는 파일에 디바이스 트리를 코딩한 예 입니다. 
/ {
node1 {
a-string-property = "A string";
a-string-list-property = "first string", "second string";
a-byte-data-property = [0x01 0x23 0x34 0x56];
child-node1 {
first-child-property;
second-child-property = <1>;
a-string-property = "Hello, world";
};
child-node2 {
};
};
node2 {
an-empty-property;
a-cell-property = <1 2 3 4>; /* each number (cell) is a uint32 */
child-node1 {
};
};
};
이 코드는 실제로 사용할 수 있는 것은 아닙니다. 
디바이스 트리의 표기법을 설명하기 좋게 만든 임의의 코드입니다. 
위 예에서 
"/" 는 디바이스 트리에 표현하고 싶은 전체 장치의 최상위 루트 노드라는 의미를 갖습니다. 
이후에 설명하겠지만 노드는 다음과 같은 가장 기초적인 형태를 가지고 있습니다. 
노드명 { 노드에 대한 내부 내용 };
내용이 빈 루트 노드를 표현 하라면 다음과 같이 
/ {  }; 
로 표현하겠죠..
위 예제에서는 이 "/" 노드는 다시 "node1" 과 "node2"라는 두개의 자식 노드를 가지고 있습니다. 
이걸 정리하면 다음과 같이 표현 할 수 있습니다. 
/ {  
node1 {  };
node2 {  };
}; 

"node1" 은 "child-node1" 과 "child-node2" 라는 자식 노드를 가지고 있고 , 
"node2" 는 "child-node1" 만을 가지고 있습니다. 
위 예를 이 부분만을 정리하면 다음과 같이 표현 할 수 있습니다. 
/ {  
node1 {  
child-node1 {
};
child-node2 {
};
};
node2 {  
child-node1 {
};
};
}; 
노드는 자식 노드뿐만 아니고 속성을 가질 수 있습니다. 
속성은 키와 값으로 구성된 한 쌍입니다.

키는 반드시 있어야 하지만 키에 대한 값은 없을 수 도 있고,

만약 있다면 임의의 바이트 배열 입니다. 
속성의 형식은 다음과 같습니다. 
키 = 값;
'= ' 앞에 키에 대한 아스키 문자열 형태로 표현합니다 
'=' 뒤에 값에 대한 표현 방법은 뒤쪽에 다시 설명합니다. 
이런 키값의 실제 코딩 예는 다음과 같습니다. 
compatible = "arm,cortex-a9";
속성 값에 대한 데이터 형은 따로 정의 되어 있지 않습니다. 
그러나 디바이스 트리를 편집하고 제작자나 다른 사람이 알아 보기 위해서 몇가지 표현 방법을 사용합니다. 
실제 디바이스 트리의 파일이 컴파일 되어 디바이스 트리의 바이너리 데이터에는 
속성값은 순수하게 이진 바이트 배열의 값들일 뿐입니다. 
이 바이트 배열 값을 해석하는 것은 이 디바이스 트리를 사용하는 운영체제가 알아서 해석해야 합니다. 

이제 속성 값을 기술하기 위한 가장 기본적인 표현 방법을 알아 보겠습니다. 
속성 값에 NULL 로 끝나는 문자열을 쓰고 싶다면 쌍 따옴표를 사용하여 문자열을 표현합니다. 
다음이 그 예 입니다. 
string-property = "a string"
32비트 부호없는 정수형은 "<" 와 ">"를 이용하여 표현합니다. 다음이 그 예 입니다. 
cell-property = <0xbeef 123 0xabcd1234>
이진 데이터는 "[" 와 "]" 를 이용하여 표기한다. 다음이 그 예 입니다. 
binary-property = [0x01 0x23 0x45 0x67];
위의 세가지를 혼합해서 속성값을 표현할때는 각 데이터 표현형태를 ',' 를 사용해서 분리하여 하나의 값으로 표현 할 수 있습니다. 
다음이 그 예 입니다. 
mixed-property = "a string", [0x01 0x23 0x45 0x67], <0x12345678>;
',' 는 여러 문자열을 나열할때도 사용할 수 있습니다. 다음이 그 예 입니다. 
string-list = "red fish", "blue fish";

2.  기본적인 작성법

디바이스 트리를 어떻게 작성하는지를 설명하기 위해서, 
간단한 샘플 장치를 예로 하여 단계별로 디바이스 트리를 만들어 가 보겠습니다. 
2.1 디바이스 트리를 설명할 샘플 장치 소개

앞으로 만들 디바이스 트리를 위해서 ARM versatile 와 비슷한 임베디드 보드를 가정하겠습니다. 
제작사는 Acme 라는 회사이고 보드 이름은 "Coyote's Revenge" 라고 하죠.
이 보드는 한개의 32 비트 ARM CPU 프로세서로 구성되고
이 프로세서의 로컬 버스에는 시리얼 포트와 SPI 버스 컨트롤러, I2C 컨트롤러, 인터럽트 컨트롤러 및 외부 버스 브릿지가 있고
각각은 로컬 버스 메모리에 맵핑 되어 있다고 가정합니다. 
메모리는 256MB 크기의 SDRAM 이고 0 번지에 맵핑되어 있습니다.
2 개의 시리얼 포트는 0x101F1000 번지와 0x101F2000 번지에 맵핑되어 있습니다.
GPIO 컨트롤러는 0x101F3000 에 주소가 맵핑 되고
SPI 컨트롤러는 0x10170000 에 주소가 맵핑 됩니다. 
이 SPI 컨트롤러에는 여러 디바이스가 SPI 하부 버스에 연결되어 있다고 가정합니다. 

MMC 슬롯도 있는데 MMC 슬롯의 SS 핀은 GPIO #1 에 연결되어 있다고 가정합니다. 
외부 버스 브릿지에는 
SMC 사의 SMC91111 이더넷 장치가 외부 버스에 0x10100000 주소로 맵핑되어 연결되어 있습니다.
또한 I2C 컨트롤러도 0x10160000에 맵핑되어 있고 
이 I2C 컨트롤러의 I2C 버스에 Maxim DS1338 리얼타임클럭이 
슬레이브 주소 1101000 (0x58) 으로 할당되어 연결되어 있다고 가정합니다. 
64MB NOR 플래쉬도 외부 버스에 0x30000000에 주소가 맵핑되어 연결되어 있다고 가정합니다. 

2.2 최초 구조

앞에서 가정한 보드에 대한 디바이스 파일을 작성하기 위해서 
가장 처음 만들 형태는 다음과 같습니다. 

이 구조는 디바이스 트리의 최소한의 형태가 됩니다. 
루트 노드로 표현하면 앞에서 설명했듯이 다음과 같은 형태가 됩니다. 
/ { };
보드 자체가 이 노드인 것이죠..
뒤에서 설명하겠지만 '/' 가 노드 이름입니다. 여기서는 보드 전체를 의미하는 "루트" 가 이름이 됩니다. 
'/'는 디바이스 트리에 딱 한번만 나와야 합니다.

이제 이 노드에 보드에 대한 고유한 식별자를 지정해야 합니다. 
다음과 같이 샘플 보드에 대한 루트 노드의 식별자를 지정합니다. 
/ {
compatible = "acme,coyotes-revenge";
};

"compatible" 란 속성을 이용하여 우리가 표현하고자 하는 보드에 대하여 기술하고 있습니다. 

compatible 속성은 항상 "제조사,모델" 순으로 문자열 형태로 표현합니다. 

수많은 개발자가 디바이스 트리를 만들기 때문에 동일한 이름들을 사용할 수 있습니다. 
이렇게 동일한 이름을 사용하게 되면 운영체제는 다루려는 디바이스나 보드를 구별할 수 없습니다. 
이런 동일한 이름을 사용하는 것을 네임스페이스 충돌 문제라고 합니다. 
그래서 네임스페이스 충돌 문제를 회피하기 위해서는 제조사명과 함께 장치명을 정확하게 기술하는 것은 매우 중요합니다. 
원칙적을 운영체제는 이 compatible 속성 값을 이용해서 다루고자 하는 디바이스에 대한 정보를 찾아 갑니다. 
보통은 운영체제가 원하는 디바이스에 맞는 정보를 찾을때 compatible 속성 값 전체를 비교해 갑니다. 
그리고 정상적이라면 가장 상위부터 찾기 시작합니다. 
2.3 CPU에 대한 표현
보드 자체에 대한 것을 디바이스 트리에 표현했으므로 해당 보드에 있는 CPU에 대한 내용을 추가 할 것입니다. 
CPU 에 대한 추가는 "cpus" 라는노드 이름을 갖는 노드를 이용해서 표현합니다. 
다음은 ARM Cortex A9 듀얼 코어 시스템일 경우를 표현한 예입니다. 

/ {
compatible = "acme,coyotes-revenge";
cpus {
cpu@0 {
compatible = "arm,cortex-a9";
};
cpu@1 {
compatible = "arm,cortex-a9";
};
};
};
이 예를 보면 cpus 노드는 다시 cpu@0  와 cpu@1 이라는 형식의 이름을 갖는 자식 노드를 가집니다.
이 자식 노드들은 어떤 cpu인가의 정보를 표현하기 위해서 compatible 속성을 사용합니다. 
이 compatible 속성은 앞에서 설명했듯이 "제조사,모델" 형식의 값을 가집니다. 
더 많은 속성들이 각 CPU 노드에 추가될 수 있지만, 지금은 가장 기본적인 개념을 이해하기 위한 것들만 다루겠습니다.

2.4 노드 이름 규칙

위 예에서 각 cpu 의 노드 이름을 보듯이 노드들의 이름을 표현하는 고유의 규칙이 있습니다.

모든 노드는 "이름@장치주소" 와 같은 형태로 표현합니다. 
"@장치주소"는 동일한 디바이스를 나타내거난 동일한 내용을 나타내는 노드가 없다면 생략 가능합니다. 
"/" 난 cpus 가 바로 그런 예 입니다. 
이 형식에서 "이름" 부분은 최대 31 개의 문자 길이를 갖는 간단한 아스키 문자열로 표현합니다.
보통 노드의 이름은 장치의 종류를 나타낼 수 있는 이름으로 짓습니다.
예를 들면
3com 이더넷 아답타를 나타내는 노드는 3com509 이라고 이름짓지 않고 ethernet 이라고 이름을 짓습니다.
노드이름의 "장치주소" 부분은 "이름"이 장치의 종류를 나타내므로 각각의 장치들을 구별하기 위해서 사용됩니다. 

노드 이름에서의 "장치주소"는 단지 노드들이 다른 노드임을 구별하기 위한 용도로 사용됩니다. 
실제로 운영체제에서 디바이스를 다루기 위해서 사용하는 장치 주소는 각 노드의 속성중 reg 속성에 실제 정보가 지정 됩니다. 
하지만 보통은 노드 이름의 "장치주소"의 값과 reg 속성에 지정되는 값을 일치시켜서 사용 합니다. 
reg 속성은 뒷쪽에서 다시 자세하게 설명합니다. 
cpus 에 포함되어 있는 각 cpu 가 여러개이기 때문에 각각의  cpu 노드들은 형제 노드가 됩니다. 
형제 노드들의 이름은 유일해야 하기 때문에 
보통 동일한 이름에 해당하는 디바이스의 주소들을 이용해서 이름을 짓습니다. 
위 예에서는 cpu@0 , cpu@1 과 같이 표현하고 있습니다. 
시리얼 장치가 여러개가 있다면 해당 시리얼 장치의 주소를 이용해서 구별하는 것이 일반적입니다. 
에를 들면 serial@101f1000 , serial@101f2000 와 같은 형식으로 하는 것입니다. 
노드의 이름에 대한 자세한 설명은 ePAPR 기술 설명서의 2.2.1 섹션을 참조하시면 좋습니다. 

2.5 디바이스에 대한 표현 

아래 예는 앞에서 만든 디바이스 트리에 보드에 포함된 디바이스에 대한 노드를 추가 한 예입니다. 
이 예에서는 각 디바이스의 주소나 인터럽트와 같은 내용에 대한 것은 포함하지 않고 있습니다 .
각각의 속성을 설명할때 마다 추가 할 것입니다. 
/ {
compatible = "acme,coyotes-revenge";
cpus {
cpu@0 {
compatible = "arm,cortex-a9";
};
cpu@1 {
compatible = "arm,cortex-a9";
};
};
serial@101F0000 {
compatible = "arm,pl011";
};
serial@101F2000 {
compatible = "arm,pl011";
};
gpio@101F3000 {
compatible = "arm,pl061";
};
interrupt-controller@10140000 {
compatible = "arm,pl190";
};
spi@10115000 {
compatible = "arm,pl022";
};
external-bus {
ethernet@0,0 {
compatible = "smc,smc91c111";
};
i2c@1,0 {
compatible = "acme,a1234-i2c-bus";
rtc@58 {
compatible = "maxim,ds1338";
};
};
flash@2,0 {
compatible = "samsung,k8f1315ebm", "cfi-flash";
};
};
};

이 예로 든 코드는 운영체제가 쓸수 있을 정도로 기술한 내용이 완전하지 않습니다. 
왜냐하면 디바이스를 다루기 위한 필요한 속성들과 
디바이스들 사이의 연결관계에 대한 정보를 아직까지 기술하지 않았기 때문입니다. 
하지만 보드에 디바이스가 어떤 계층 구조를 가지고 있는지를 알 수 있습니다. 

이와 관련된 정보는 곧 추가 될 것이다. 

위 예 중 일부를 설명하면 
external-bus 라고 이름을 갖는 것은 보드가 외부 버스가 있음을 표현하고 있고,
그 외부 버스는 다시 이더넷, i2c 버스, 플래쉬가 있음을 알 수 있습니다. 
각각은 자식 노드로 표현되고 있습니다. 
또한 i2c 버스는 하부에 또 다른 디바이스가 있음을 포함된 자식 노드들로 표현합니다.
보통 보드의 구성 상태를 나타내는 이런 계층 구조는 CPU의 관점에서 보여줍니다. 
위 예는 다음과 같은 것들도 알 수 있습니다. 
- 모든 디바이스 노드는 compatible 속성을 갖습니다. 
- 플래쉬 노드는 compatible 속성에 2 개의 문자열을 포함하고 있습니다. 
왜 이런지는 뒷쪽에서 알려 줄 것입니다.
- 앞에서 언급했듯이, 노드명은 디바이스 타입을 반영하는 것이지 특정한 모델을 지칭하는 것이 아닙니다 
보통 일반적으로 사용가능한 노드의 이름 목록은 ePAPR 기술 설명서 섹션 2.2.2 를 참고하면 알수 있습니다. 
2.6 compatible 속성 사용법
디바이스 트리에 디바이스를 표현하는 모든 노드는 compatible 속성을 가져야 합니다. 
compatible 속성은 운영체제에서 동작하는 디바이스 드라이버가 다룰 디바이스에 대한 정보를 찾기 위한 키 값입니다. 
compatible 은 하나의 문자열만 가지는 것이 아니고 여러개의 문자열들이 나열된 리스트입니다. 
이 문자열 리스트의 첫번째 문자열은 디바이스를 정확하게 구별하기 위해서 
"<제조사>,<모델>" 형식으로 표현해야 합니다. 
첫번째 문자열 이외에는 이런 형식을 따르지 않아도 되며, 보통은 호환 가능한 다른 디바이스를 구별하기 위한 문자열이 됩니다. 
예를 들면,
프리스케일 MPC8349 프로세스는 내부에 National Semiconductor ns16550와 호환 가능한 인터페스를 가지는 시리얼 디바이스를 가지고 있습니다. 
그렇기 때문에 MPC8349 보드를 기술하는 디바이스 트리의 시리얼 디바이스 노드의 compatible 속성은 다음과 같이 표현합니다.
compatible = "fsl,mpc8349-uart", "ns16550"; 
이 보드에서 동작하는 운영체제는 해당 디바이스를 다루는 디바이스 드라이버를 찾기 위해서 
먼저 운영체제는 "fsl,mpc8349-uart" 문자열을 인식할 수 있는 디바이스 드라이버를 찾을 것이고 , 만약 발견에 실패한다면 
"ns16550"의 문자열을 인식할 수 있는 디바이스 드라이버를 찾을 것입니다. 
운영체제의 디바이스 드라이버 구현 방법에 따라서는 
디바이스 드라이버가 자신이 다룰 디바이스 정보를 얻기 위해서 
먼저 디바이스 트리에 제공된 각 노드를 탐색하면서 compatible  속성에 선언된 문자열 리스트에 
"fsl,mpc8349-uart"의 문자열을 포함하거나 "ns16550" 를 포함한 
노드를 발견하면 이에 대한 처리가 진행될 것입니다. 

결국 위 예에서 
"fsl,mpc8349-uart" 라고 기술한 것은 해당 디바이스 자체를 표현한 것이고,
"ns16550" 는 내부 레지스터를 다루는 방법이 National Semiconductor 16550 UART 와 호환 가능하다는 의미이다. 
주의:
위 예에서 "ns16550" 에는 제조사 접두사를 사용하지 않고 있습니다. 

모든 새로운 선언되어 지는 compatible 속성 값은 "mpc8349-uart" 처럼 제조사 접두사를 사용해야 합니다. 
"ns16550"는 이미 널리 알려져 있기 때문에 굳이 제조사 접두사를 붙이지 않은 것입니다. 

하지만 여러분이 작성할때는 반드시 제조사 접두사를 붙여 주어야 합니다. 
이런 룰을 지키는 것이 새로운 디바이스를 처리하는 디바이스 드라이버가 확실하게 연결되는 것을 보장합니다. 
경고:
compatible 의 속성값에 "fsl,mpc83xx-uart" 와 같은 형식으로 와일드 문자를 사용하지 마십시오.
왜냐하면 여러분이 해당 칩셋간에 호환성이 있기 때문에 모두 적용이 가능하다고 믿고 와일드 카드를 사용하여 
기술하시겠지만 나중에 반도체 판매자들은 아마도 이런 가정이 쓸모 없게 할 가능성이 농후 합니다. 
만약 이런 사태가 발생하면 수습이 어렵다는 것을 확신합니다. 
가급적 해당 칩의 정확한 모델명을 사용하십시오.


출처 : http://forum.falinux.com/zbxe/index.php?filter=search&mid=lecture_tip&search_target=title&search_keyword=%EB%94%94%EB%B0%94%EC%9D%B4%EC%8A%A4+%ED%8A%B8%EB%A6%AC&document_srl=784561


신고

'Programming > Linux/Unix' 카테고리의 다른 글

디바이스 트리 작성법 (3편)  (0) 2015.03.24
디바이스 트리 작성법 (2편)  (1) 2015.03.24
디바이스 트리 작성법 (1편)  (0) 2015.03.24
[Linux] Module 관련  (0) 2015.03.13
[Makefile] Kbuild 와 Module Compile  (0) 2015.03.09
[Makefile] Device Driver  (0) 2015.03.09


Posted by injunech
2015.03.13 17:16



모듈과 커널 버전

모듈은 커널 버전과 관련이 많습니다. 커널 버전에 맞춰서 모듈이 빌드되며, 커널의 버전과 모듈의 버전이 일치해야만 이용할 수 있습니다. 커널 소스에서 커널의 버전은 include /linux/version.h에서 확인할 수 있습니다.

#define UTS_RELEASE "2.6.14.6"
#define LINUX_VERSION_CODE 132622
#define KERNEL_VERSION(a,b,c) (((a) << 16 ) + ((b) <<8)  +  (c))

따라서, 특정 버전 이상에서만 동작하는 모듈이라던가, 커널 버전에 따라 다르게 동작하는 모듈을 작성하고 싶다면 이를 활용해서 다음과 같은 코드를 작성할 수 있습니다.

#if LINUX_VERSION_CODE >= KERNEL_VERSION( 2, 6, 10)
MODULE_PARM( user_name, "charp");
#else
MODUILE_PARM( user_name, "s");
#elseif

MODULE_PARM()의 의미나 사용법은 나중에 설명을 한다. 여기서는 VERSION_CODE와 KERNEL_VERSION()사용한 방식만 이해하면 됩니다. KERNEL_VERSION()은 커널 버전 번호를 받아서 정수값으로 만들어 줍니다.  해당하는 커널 버전을 미리 정수 값으로 만들어둔 것이 LINUX_VERSION_CODE이고, 이것은 모든 커널에 선언되어 있습니다.

모듈과 static 선언

모듈 안에서만 사용되는 변수나 함수에는 static 키워드가 선언되어 있습니다. static은 메모리 내에 정적으로 위치하게 만드는 할을 하기 때문에 해당 소스 코드 내에서 전역으로 사용될 수 있습니다. 변수에 사용되면 전역변수가 되며, 함수에 사용되면 전역 함수가 되겟지요. 실제로 .c 코드 안에서 사용되는 모든 C 함수나 함수 바깥에 선언된 변수는 어디서나 상ㅇ할 수 있기 때문에 어떤 의미로는 항상 전역이라고 할 수 있습니다. 함수나 전역 변수에 사용된 static 의 다른 의미는 다른 .c소스 코드에서 접근할 수 없게하는 범위 지정자 입니다.

리눅스 커널은 커널 전체가 하나의 프로그램입니다. 그러니까 같은 이름을 가진 변수나 함수를 만들 수 없습니다. 누군가 A라는 모듈에서 hello()함수를 작성해서 커널 영역에 로드했다면, 또 다른 hello() 함수를 가진 모듈 B는 로드 할 수 없게 됩니다. 이 함수가 모듈 안에서만 사용되고, 다른 커널에서 참조할 필요가 전혀 없다면 이런 파일들은 커널 영역에서 보이지 않게 해야 합니다. 때문에 static을 사용합니다. init_module()과 cleanup_module()함수는 다소 예외적인 경우로는 이 함수는 static을 사용하지 않아도 커널 영역에 공개 되지 않습니다. 실제로, 이들 함수는 __init_module_inline()과 __exit_module() 함수는 다소 예외적인 경우로 이 함수는 static을 사용하지 않아도 커널 영역에 공개 되지 않습니다.  실제로 이들 함수는 __init_module_inline()과 __exit_module_inline() 같은 인라인 함수로 대체되어 실행되고, 이들 함수는 모두 static으로 선언되어 있기 때문에 커널 영역에서 공개되지 않습니다. 커널 영역에 공개된 함수는 /proc/ksyms(2.4커널) 이나 /proc/kallsysms(2.6커널)에서 확인 할 수 있습니다.

typedef int (*__init_module_fun_t)(void)
                            └ init_module()의 원형
typedef void (*__cleanup_module_func_t)(void);
#define module_init(x) \              └ cleanup_module()의 원형
   int init_module(void) __attribute__(alias(#3))); \
   static inline _init_module_func_t __init_module_inline(void) \
    └  커널 영역에 고액되지 않는 지역 함수로 선언됩니다.
   {  return x; }

#define module_exit (x) \
  void cleanup_module(void) __attribute__((alias(#x))); \

  static inline __cleanup_module_func_t__cleanup_module_inline(void) \
    └  커널 영역에 고액되지 않는 지역 함수로 선언됩니다.
  { return x; }
static으로 선언되어 있으면, 외부 C 소스 코드에서 extern 선언으로 참조할 수 없습ㄴ디ㅏ. 2.5 커널에서는 모듈 내에서 static 없이 선언된 함수는 커널 영역에 공개되었지만 2.6에서는 공개되지 않았습니다.

모듈과 커널 심볼

모듈은 커널 영역에서 실행되기 때문에 표준 C 함수들은 사용할 수 없습니다. 대신에, 커널 영역에서 사용할 수 있는 함수나 자료 구조의 목록을 커널에서 제공합니다. 이 목록은 2.4 커널에서는 /proc/ksyms, 2.6 커널에서는 /proc/kallsyms를 통해 제공합니다. printk()함수도 커널 심볼 파일에서 확인할 수 있습니다.

2.6 커널의 심볼

# grep printk /proc/kallsyms
.........     ┌ 심폴의 타입 정보
c011d6b0  T printk
c011d6b0  T vprintk
c011d6b0  T __printk_ratelimit
........                               ┌ 심폴의 타입 정보
c011d6b0  U printk        [vmhgfs]
c011d6b0  U printk        [vmxnet]
      |            └ 심볼 이름
                 └ 심볼 이름

심볼 타입

설명

A

절대 주소로 변경되지 않음

B

bss 영역의 심볼

C

공용 심볼로 초기화되지 않은 변수를 의미

D

data 영역의 심볼로 초기화된 변수를 의미

R

rdata 영역, 즉 읽기 전용변수를 의미

T

text 영역의 심볼로 함수를 의미

U

해당 모듈에서 정의하지 않은 (undefined) 심폴을 의미

I

다른 심볼에 대한 간접(Indirect) 참조를 의미하는 GNU 확장

N

디버깅 심볼

- 심볼의 타입 -

printk() 함수를 사용하는 모듈이 있으면 printk()함수를 이용하는 모듈 이름과 함께 printk()에 대한 심볼 타입이 "U"로 나타납니다. 직접 작성한 모듈에서 printk() 함수를 정의한 것이 아니기 떄문에 그렇습니다. 심볼 타입이 대문자로 사용되면 전역 심볼을 의미하고, 소문자로 되어 있으면 지역 심볼을 의미합니다.


심볼 : 2.4와 2.6의 차이

2.4 커널에서는 모듈 안에서 static으로 선언되지 않은 함수들이 모두 커널 심볼로 공개되어 있습니다. 2.6커널에서는 static으로 선언되지 않은 함수여도, 커널 심볼로 등록 하지 않으면 공개되지 않게 변경되었습니다.

커널 심볼로 공개시키는 방법은 EXPORT_SYMBL()이나 EXPORT_SYMBOL_GPL()과 같은 매크로를 사용해서 공개할 심볼을 지정하기만 하면 됩니다. printk() 함수의 경우 kernel/printk.c 에서 다음과 같은 부분을 확인할 수 있습니다.

kernel/printk.c

EXPORT_SYMBOL(printk);

이와 같이 정의되어 있기 때문에 외부 심볼로 공개되어 있으며, 커널이나 모듈 내에서 자유롭게 사용할 수 있습니다.

2.4 커널의 심볼
[/usr/src/linux-2.4.x.x #] grep printk /proc/ksyms

       ┌ 커널 내 심볼의 시작 주소
c0114aa0 printk_R1b7d4074
                       
└ 커널 버전과 심볼의 CRC 정보를 합한 값

2.6 커널의 /proc/kallsyms에서는 버전 정보를 제공하지 않습니다.
심볼과 관련된 매크로는 include/linux/module.h에 정의되어 있으며 알아야 될 것은 다음과 같습니다.

종류

설명

EXPORT_SYMBOL(var)

심볼을 공개합니다.

EXPORT_SYMBOL_NOVERS(var)

심볼을 버전정보 없이 공개합니다.

EXPORT_SYMBOL_GPL(var)

EXPORT_SYMBOL과 동일 하지만 GPL과 호환되는 라이선스로 등록한 모듈에서만 심볼을 볼 수 있습니다.

EXPORT_NO_SYMBOLS

모듈 내에서 어떤 심볼도 공개하지 않음을 정의합니다.

 - 심볼 매크로  -

EXPORT_NO_SYMBOLS는 모듀 내의 어디서나 한 줄만 넣어주면 되며, 해당 모듈에서 어떤 심볼도 공개하지 않음을 의미합니다. EXPORT_SYMBOL_GPL()은 MODULE_LICENSE()과 관련이 깊습니다.

모듈의 라이선스

리눅스 커널은 GPL라이선스를 따릅니다. 그리고 GPL 라이선스가 적용된 소스 코드를 수정한 경우 수정된 소스 코드를 공개해야 합니다. 일부 업체들은 오픈 소스를 이용해서 제품을 개발하지만 수정된 소스 코드를 공개해야 합니다. 일부 업체들은 오픈 소스를 이용해서 제품을 개발하지만 수정된 소스 코드를 공개하지 않아 물의를 사기도 했습니다. 매년 라이선스와 관련된 분쟁이 증가하고 있습니다. 때문에 모듈에서도 제작자가 원하는 라이선스를 지정하도록 했으면, 이 라이선스를 지정하지 않으면, 오픈소스인 커널에 상업적인 코드가 추가될 수 있고, 이는 커널의 오픈 소스를 위배할 수 있다는 경고를 보여줍니다.

module license 'unspecified' taints kernel.

현재 까지는 MODULE_LICENSE()를 통해 라이선스를 명시하지 않아도 문제는 없지만, 이런 라이선스 논쟁 이후 EXPORT_SYMBOL_GPL()이 추가 되었습니다. 이는, GPL과 호환되는 라이선스를 지정하지 않은 모듈에서는 커널의 기능을 사용할 수 없게 하는 정책을 추가하기 위한 것으로, 현재 이와 관련된 불이익은 없지만, 필요한 경우 GPL 호환 라이선스를 따르지 않는 상업적 코드들은 GPL코드를 이용할 수 없게 하기 위한 조치를 반영한것입니다.

MODULE_LICENSE()를 통해 지저앟ㄹ 수 있는 라이선스는 다음과 같다.

라이선스 종류

의미

GPL

GNU Public License v2 또는 이상

GPL v2

GNU Public License v2

GPL and additional rights

GNU Public License v2 right and more

Dual BSD/GPL

GNU Public License v2 또는 BSD 라이선스 선택

DUAL MPL/GPL

GPL v2 또는 모질라 라이선스 선택

Proprietary

비자유 소프트웨어



출처 : 리눅스 커널 프로그래밍  저 한동훈,원일용, 하홍준  / 한빛 미디어

신고

'Programming > Linux/Unix' 카테고리의 다른 글

디바이스 트리 작성법 (2편)  (1) 2015.03.24
디바이스 트리 작성법 (1편)  (0) 2015.03.24
[Linux] Module 관련  (0) 2015.03.13
[Makefile] Kbuild 와 Module Compile  (0) 2015.03.09
[Makefile] Device Driver  (0) 2015.03.09
Makefile 문법 및 관련 내용  (0) 2015.03.09


Posted by injunech
2015.03.09 17:38


Document/kbuild/makefile.txt 및 Document/kbuild/modules.txt 문서를 번역편집한 문서정리

출처좌표 : http://deepbluedawn.wordpress.com/2009/07/31/169/ 



이 문서는 리눅스 커널 2.6.x에 사용되는 kbuild System의 module build과정에 대해 설명하기 위한 문서입니다.Linux Kernel소스의 Documents/kbuild/makefiles.txt 및 Documents/kbuild/modules.txt문서를 번역및 편집한 것입니다. 따라서 상당부분이 난해하기도 하고 직역을 한 부분이 있으니 이해해 주셨으면 합니다. 번역이 매끄럽지 못한 부분은 반드시 원문을 읽어 영어 단어의 뜻을 파악하시기 바랍니다.

1.Kbuild system?

일반적인 리눅스 Application 개발자라 하더라도 커널 Makefile이 어떻게 동작하는지 아는 것이 좋은 경우가 많다. 단순히 새로운 커널을 컴파일해 설치해 사용하는 사용자가 아니고 시스템을 아우르는 어플리케이션 개발자라면 커널이 어떻게 만들어지는 알고 있는 것이 도움이 될 때가 많기 때문이다. 예를 들어 현재 설정에 의해 어떤 모듈이 어떤 파일로 이루어져 있는지 알수 있으면 그 파일들을 조사해 수정하거나 하는 일이 가능하다.

kbuild system은 리눅스 버젼 2.6.x대에 도입된 새로운 kernel build system이다.  kbuild는 kernel source tree 내부의 모듈들과 kernel source tree 외부의 모듈들에 대한 build를 지원한다.(후자의 경우 외부 혹은 “out-of-tree” 모듈이라고 불리우며 현재 개발 중이거나, 혹은 Kernel의 source tree에 포함되지  않는 module을 가리킬때 사용된다.)

외부 모듈 개발자들은 모든 복잡성을 숨길수 있는 간단한 하나의 makefile을 제공해야 하며, 이 makefile을 사용, 누구나  make 명령어를 통하여 module을 build할 수 있어야 한다.

1.1 Goal 정의

Goal 정의는 kbuild Makefile의 가장 중요한 부분이다. Goal은 Build과정을 통하여 최종적으로 만들어져할 것, 특별한 컴파일 옵션, 사용되야할 하위디렉토리를 정의한다. 가장 간단한 kbuild makefile은 다음과 같은 한 줄을 갖는다.

예:

obj-y += foo.o


이 예가 말하는 것은 이 디렉토리 내에 foo.o(foo.c나 foo.S로부터 만들어질)란 이름의 한개의 오브젝트가 있다는 것이다. 만약 foo.o가 모듈로 만들어진다면 obj-m이란 변수가 사용된다. 그래서 다음과 같은 예처럼 된다.

예:

obj-$(CONFIG_FOO) += foo.o


$(CONFIG_FOO)는 y(built-in을 의미)나 m(모듈을 의미)의 값을 갖는다. 만약 CONFIG_FOO가 y나 m의 값을 갖지 않는다면 이 파일은 컴파일되거나 링크되지 않는다.


1.2 Built-in 오브젝트 Goal (obj-y)

kbuild Makefile은 $(obj-y) 리스트 내에 vmlinux에 필요한 오브젝트 파일을 지정해 놓는다. kbuild는 모든 $(obj-y) 파일을 컴파일한다. 그리고 이런 모든 파일을 하나의 build-in.o로 만들기 위해 “$(LD) -r”을 부른다.

$(obj-y)에 기록된 파일의 순서는 매우 중요하다. 중복되서 나열되는 것도 허용되지만 첫번째로 나오는 것이 built-in.o에 링크되고 그 이후의 것은 무시된다. 어떤 기능 들은(예를 들어 module_init()나 __initcall) 부팅하는 동안 나타나는 순서대로 불려지기 때문에 링크 순서도 중요하다. 그래서 순서를 바꾸게되면 디스크 등의 드라이버가 사용되는 순서가 바뀌는 등의 일 때문에 디스크의 번호도 바뀔수 있다.

예:

#drivers/isdn/i4l/Makefile

# Makefile for the kernel ISDN subsystem and device drivers.

# Each configuration option enables a list of files.

obj-$(CONFIG_ISDN) += isdn.o

obj-$(CONFIG_ISDN_PPP_BSDCOMP) += isdn_bsdcomp.o


1.3. Loadable module goals(obj-m)

$(obj-m)은 적재 가능한 모듈을 만들 때 사용된다.

모듈은 하나의 소스 코드나 여러 개의 소스 코드에서 만들어질 수 있다. 하나의 소스 코드로 만들어지는 경우엔 그냥 $(obj-m)에 더하기만 하면 된다.

예:

#drivers/isdn/i4l/Makefile

obj-$(CONFIG_ISDN_PPP_BSDCOMP) += isdn_bsdcomp.o


Note: 이 예에서 $(CONFIG_ISDN_PPP_BSDCOMP)는 ‘m’을 나타낸다.


만약에 커널 모듈이 여러 개의 소스 파일로부터 만들어지면 위에 나온 것과 같은 방식으로 지정하면된다. kbuild는 만들고자하는 모듈이 어느 부분에서 오는지를 알면되고 $(<module_name>-objs)에 지정해 주면 된다.

예:

#drivers/isdn/i4l/Makefile

obj-$(CONFIG_ISDN) += isdn.o

isdn-objs := isdn_net_lib.o isdn_v110.o isdn_common.o


이 예에서 모듈 이름은 isdn.o이고 kbuild는 $(isdn-objs)안에 있는 오브젝트를 컴파일 한 후에 “$(LD) -r”을 실행해 isdn.o를 만들어낸다. kbuild는 오브젝트를 접미사 -objs와 -y에 의해 만들어지는 복합 오브젝트로 인식할 수 있다. 만약 오브젝트가 복합 오브젝트의 일부라면 Makefile이 CONFIG_ 값을 사용하도록 할 수 있다.

예:

#fs/ext2/Makefile

obj-$(CONFIG_EXT2_FS) += ext2.o

ext2-y := balloc.o bitmap.o

ext2-$(CONFIG_EXT2_FS_XATTR) += xattr.o

이 예에선 xattr.o는 $(CONFIG_EXT2_FS_XATTR)이 ‘y’인 경우에만 복합 오브젝트인 ext2.o의 일부 뿐이다. Note: 물론 커널 안에 모듈을 포함하는 경우에도 위와 같은 문법은 그대로 적용된다. 그래서 CONFIG_EXT2_FS=y인 경우 kbuild는 ext2.o 파일을 만들어 built-in.o에 링크하게된다.


2.외부 모듈을 build하는 방법


kbuild를 사용하여 외부 모듈을 build하기 위해서는 다음의 두 가지 사항이 구비 되어 있어야 한다.

Full source tree와 이를 사용하여 build된 kernel image가 있어야 한다.

Kernel을 build 할때 사용된 target들의 일부 부분이 module build를 위하여 사용 가능하여야 한다.


2.1 외부 modules을 build하는 방법

외부 module을 build하는 일반적인 3가지 방법에 대해서 알아보자.


일반적인 module build 방법

make -C <path-to-kernel> M=’pwd’

현재 동작중인 kernel을 참조하여 module을 build하는 경우.

make -C /lib/modules/’uname -r’/build M=’pwd’

Build된 kernel을 install하는 경우

make -C <path-to-kernel> M=’pwd’ modules_install

여기에서 간단히 짚고 넘어가야 할 부분은 세가지 정도이다. 먼저 make에서 -C option은 뒤의 parameter로 directory를 변경하라는 의미이고 uname -r 은 현재 운영중인 커널의 릴리즈 버젼 정보를 출력해 주는 명령어이다.

M과 modules_install등은 추후 설명할 build target 들이다.


2.2 외부 modules을 build하는 방법


make -C <path-to-kernel> M=’pwd’: pwd로 지정되어 있는 현재 directory의 모듈을 build하며, build과정에서 생성된 모든 출력 파일은 같은 directory에 저장된다. kernel source에 대한 갱신은 이루어지지 않으며, 반드시 적어도 한번은 kernel source가 성공적으로 build되어 있어야 한다.

make -C <path-to-kernel> M=’pwd’ module: 위의 방법에서 명시적으로 “module” 이라고 하는 target을 선언한 것이며 동작은 위의 서술과 동일하다.

make -C <path-to-kernel> M=’pwd’ modules_install: Build된 module을 설치한다. 설치되는 기본 directory는 /lib/modules/<kernel-version>/extra 이지만, prefix INSTALL_MOD_PATH를 통하여 설치되는 경로를 지정할 수 있다.

make -C <path-to-kernel> M=’pwd’ clean: 모듈 build과정에서 생성된 모든 file들을 삭제하며, kernel source directory는 변경되지 않는다.

make -C <path-to-kernel> M=’pwd’ help: 외부 모듈을 build하는데 필요한 build target들을 설명하는 명령이다.

2.3 사용가능한 option


make -C <path-to-kernel> M=’pwd’ M= 옵션은 kbuild에게 “외부 모듈”이 build되고 있다라는 사실을 알리는 옵션이며 SUBDIRS= 의 backward compatibility도 지원한다.


2.4 모듈 build를 위한 kernel tree 준비하기


외부 모듈을 build하기 위한 정보를 생성하기 위하여 kernel build target인 modules_prepare가 반드시 사용되어야 한다. (CONFIG_MODVERSIONS가 설정되어 있는 경우에도 modules_prepare target은 Module.symvers를 생성하지는 않는다. 조심할 것)


2.5 모듈의 파일 하나씩 build하기


kbuild는 하나의 모듈을 구성하는 각각의 파일들을 따로 build 할 수 있다.

(예)


make -C <path-to-kernel> M=’pwd’ bar.lst

make -C <path-to-kernel> M=’pwd’ bar.o

make -C <path-to-kernel> M=’pwd’ foo.ko

make -C <path-to-kernel> M=’pwd’ /


3. Kbuild/Makefile을 사용한 예제


kbuild system은 linux kernel을 효과적으로 build하기 위한 framework이다. Linux kernel과 밀접한 관련이 있는 kernel module은 다음과 같은 이유때문에 kbuild system을 사용하여야 한다.


전체 build system의 변경사항에 대하여 compatible하기 위하여

gcc등 compilation에 필요한 tool들과 해당 옵션들에 대하여 kernel build와 동일한 규칙을 적용하기 위하여

아래의 file들로 구성되어진 모듈을 compile하기 위하여 Makefile 하나를 생성해 보도록 하자.


8123_if.c

8123_if.h

8123_pci.c

8123_bin.o_shipped <- binary blob


3.1 모듈과 Kernel을 위한 공유 Makefile


외부 모듈은 항상 argument없이 단순한 ‘make’명령만으로 해당 module을 build할수 있는 wrapper Makefile을 포함하여야 한다.

Example 1:

–> filename: Makefile

ifneq ($(KERNELRELEASE),)

# kbuild part of makefile

obj-m  := 8123.o

8123-y := 8123_if.o 8123_pci.o 8123_bin.o

else

# Normal Makefile


KERNELDIR := /lib/modules/`uname -r`/build

all::

$(MAKE) -C $(KERNELDIR) M=`pwd` $@


# Module specific targets

genbin:

echo “X” > 8123_bin.o_shipped

endif


위의 예제에서 KERNELRELEASE 변수는 하나의 Makefile을 두 부분으로 나누는 역할을 수행한다. 위의 예제에서 make utility는 kbuild를 위한 두줄의 선언문만 인식을 못함에 비해 kbuild system은 두 라인의 선언문만 인식할 수 있다. 최근 version의 kernel에서 kbuild system은 먼저 Kbuild를 찾고 두번째 옵션으로 Makefile을 찾도록 되어 있다. Kbuild 파일을 사용하여 위의 예제에서 제공된 Makefile은 아래의 두가지 파일로 분리할 수 있다.


Example 2:

–> filename: Kbuild

obj-m  := 8123.o

8123-y := 8123_if.o 8123_pci.o 8123_bin.o

–> filename: Makefile

KERNELDIR := /lib/modules/`uname -r`/build

all::

$(MAKE) -C $(KERNELDIR) M=`pwd` $@

# Module specific targets

genbin:

echo “X” > 8123_bin.o_shipped


위의 예제에서는 상당히 작은 2개의 파일을 얻을 수 있었다. 그러나, 이렇게 작은 경우의 파일에 대해서 이러한 분리가 과현 실효성이 있는가에 대한 의문을 가질수 있으나, 매우 큰 Makefile의 경우, 이렇게 분리하는 것이 실효성을 가질수도 있다라는 점에 주목하자.

신고

'Programming > Linux/Unix' 카테고리의 다른 글

디바이스 트리 작성법 (2편)  (1) 2015.03.24
디바이스 트리 작성법 (1편)  (0) 2015.03.24
[Linux] Module 관련  (0) 2015.03.13
[Makefile] Kbuild 와 Module Compile  (0) 2015.03.09
[Makefile] Device Driver  (0) 2015.03.09
Makefile 문법 및 관련 내용  (0) 2015.03.09


Posted by injunech

티스토리 툴바