2020년 11월 6일 금요일

Zephyr RTOS Project

이번 시간에는 3년 전에 한차례 소개한 바 있는 Zephyr RTOS Project를 다시 조명해 보고자 한다. 그 동안 많은 변화(v1.7 -> v2.4.99)가 있었는데, 왜 Zephyr가 앞으로 성공할 수 밖에 없는지 하나 하나 파헤쳐 보도록 하자. 😎



RTOS/IoT OS 계의 새로운 바람~


목차
1. Zephyr reloaded
2. Zephyr 개발 환경 설정
3. Build system 소개
4. Device tree 기반의 device driver model 
5. 아직 못다한 이야기
6. References


아래 내용을 소개하기에 앞서 3년 전에 작성해 둔 아래 posting을 먼저 확인해 보기 바란다.


1. Zephyr reloaded
3년 전 v1.7에 비해 이 글을 쓰고 있는 현재(2020. 11) v2.4.99 버젼이 나오기까지 눈에 띄는 변화는 CMake, Ninja/Make & Python3를 중심으로 하는 build system과 swiss army knife에 해당하는 west(python으로 작성)라는 tool이 추가된 점을 들 수 있다. 또한 200여개 이상의 board를 지원하면서 다양한 soc 및 board 관련 코드가 추가되었고, 많은 보드의 다양한 device를 효과적으로 처리하기 위해 (linux 처럼) device tree를 기반으로 하는 device driver model이 제대로 정착되었다는 점 등을 꼽을 수 있을 것 같다.

주요 구성 요소:
− Python 3: Script interpreter and packages
− CMake/Ninja/Make: Build system
− Device Tree Compiler: Compiles device tree hardware descriptions
− Toolchain: gcc for Arm, RISC-V, x86, etc.
− Debug/Flash Tools: J-Link, pyOCD, OpenOCD, etc.
− West: Custom tool for repository management, build/flash/debug assistance, and image signing
− Zephyr Git repositories: The source code!

주요 특징:
1) Zephyr는 Linux를 사용하기에는 부담이 되는 작은 시스템(resource constrained system)을 target으로 하고 있다.

Linux Foundation's Strategy
Zephyr(resource constrained system) + Linux(resource not constrained system)
=
IoT Devices based on Zephyr + IoT Gateway based on Linux

[그림 1.1] Zephyr RTOS Overview [출처 - 참고 문헌 2]

2) Zephyr는 여타 RTOS(예: FreeRTOS, ARM mbedOS, NuttX 등)와 마찬가지로 RTOS 및 IoT OS를 위해 필요한 왠만한 기능은 이미 다 갖추고 있다.

 
[그림 1.2] Zephyr RTOS Architecture [출처 - 참고 문헌 2]

3) Zephyr는 이제 200개 이상의 board(ARM Cortex-M 계열, x86, ARC, XTENSA 등)를 지원하며, 다양한 상용 제품에 널리 활용되고 있다.

[그림 1.3] Zephyr 지원 보드 - 200개 이상(2020년 현재) [출처 - 참고 문헌 2]

[그림 1.4] Zephyr를 활용한 상용 제품 [출처 - 참고 문헌 2]

4) 문서 정리가 아주 잘 되어 있다.

[그림 1.5] Zephyr Documentation


5) 특정 회사, 개인이 중심이 되어 개발된 project가 아니라 community가 그 중심인 project라는 면에서 Zephyr만이 진정한 의미에서의 open source RTOS 이다.

____________________
아래 내용은 Zephyr 진영에서 작성한 것이긴 하지만, zephyr가 여타 RTOS에 비해 지난 1년간 가장 활발히 개발되고 있는 open source RTOS project임을 보여준다.

[그림 1.6] 지난 해 RTOS 간 commit 횟수 비교 [출처 - 참고 문헌 2]

📌 Code commit이 활발하다는 것은 말 그대로 가장 관심이 높다는 의미이기도 하겠지만, 아직 개발할 게 많이 남아있다는 의미일 수도 있겠다. 참고로 우측의 FreeRTOS는 sourceforge => Github으로 전환한 것이 얼마되지 않아 활동량이 많지 않다는 얘기도 있다.
📌 현재까지 RTOS 1위는 FreeRTOS(얼마 전에 AWS에서 인수하여 AWS FreeRTOS로 판매 중)이지만, 그 뒤를 ARM mbedOS가 바짝 뒤 쫒고 있는 듯하다. 하지만 머지 않아 Zephyr가 이들을 제치고 그 위용을 들어낼 날이 곧 올 것으로 믿어 의심치 않는다.
📌 참고로 위 그림에는 없지만 최근 (개인적으로 별로 좋아하지 않는)M$에서도 Azure Sphere라는 RTOS를 밀고 있는 듯하다.

그야말로 RTOS 춘추 전국 시대 ~ 특정 RTOS가 천하를 통일할 것으로는 보지 않는다. 하지만 여러가지 면에서 볼 때 Zephyr는 장래가 매우 촉망되는 RTOS임에 틀림없어 보인다. 😍


2. Zephyr 개발 환경 설정
Zephyr 개발 환경 구축과 관련해서는 아래 site에 아주 정리가 잘 되어 있다(그대로 따라하기만 하면 된다 😋). 기존(make 중심)에 비해 설치 과정이 다소 복잡해 진 느낌이나, west라는 tool이 추가되어 있어 전체적으로 편리해 진 것도 사실이다.


이 절에서는 Ubuntu 18.04를 기준으로 환경 설정을 해 보도록 하겠다. 상세한 내용은 위의 page를 참조하기 바란다.
📌 당연히 Windows, macOS 등에서도 개발 가능하다.

<package 설치>
$ sudo apt update
$ sudo apt upgrade

$ sudo apt install --no-install-recommends git cmake ninja-build gperf \
  ccache dfu-util device-tree-compiler wget \
  python3-dev python3-pip python3-setuptools python3-tk python3-wheel xz-utils file \
  make gcc gcc-multilib g++-multilib libsdl2-dev

<cmake upgrade>
$ cmake --version
cmake version 3.10.2

📌 cmake version 3.13.1 이상이어야 한다. 따라서 아래 내용을 수행해 준다.
$ wget -O - https://apt.kitware.com/keys/kitware-archive-latest.asc 2>/dev/null | sudo apt-key add -
$ sudo apt-add-repository 'deb https://apt.kitware.com/ubuntu/ bionic main'
$ sudo apt update
$ sudo apt install cmake

$ cmake --version
cmake version 3.18.4

<zephyr 설치 및 python 관련 package 설치>
$ pip3 install --user -U west
$ echo 'export PATH=~/.local/bin:"$PATH"' >> ~/.bashrc
$ source ~/.bashrc
📌 python으로 작성된 west는 source code download(마치 repo와 같은 역할)는 물론이고 build 시에도 사용된다.

$ west init ~/zephyrproject
$ cd ~/zephyrproject
chyi@mars:~/zephyrproject$ west update

chyi@mars:~/zephyrproject$ ls -la
합계 28
drwxr-xr-x  7 chyi chyi 4096 10월 30 10:38 .
drwxr-xr-x 21 chyi chyi 4096 10월 30 10:40 ..
drwxr-xr-x  2 chyi chyi 4096 10월 30 10:35 .west
drwxr-xr-x  3 chyi chyi 4096 10월 30 10:38 bootloader
drwxr-xr-x  9 chyi chyi 4096 10월 30 10:39 modules
drwxr-xr-x  5 chyi chyi 4096 10월 30 10:39 tools
drwxr-xr-x 23 chyi chyi 4096 10월 30 10:35 zephyr

chyi@mars:~/zephyrproject$ west zephyr-export
Zephyr (/home/chyi/zephyrproject/zephyr/share/zephyr-package/cmake)
has been added to the user package registry in:
~/.cmake/packages/Zephyr

ZephyrUnittest (/home/chyi/zephyrproject/zephyr/share/zephyrunittest-package/cmake)
has been added to the user package registry in:
~/.cmake/packages/ZephyrUnittest


chyi@mars:~/zephyrproject/zephyr$ pip3 install --user -r ~/zephyrproject/zephyr/scripts/requirements.txt

<toolchain 설치>
chyi@mars:~/workspace/Zephyr$ wget https://github.com/zephyrproject-rtos/sdk-ng/releases/download/v0.11.4/zephyr-sdk-0.11.4-setup.run
chyi@mars:~/workspace/Zephyr$ chmod 755 ./zephyr-sdk-0.11.4-setup.run
chyi@mars:~/workspace/Zephyr$ ./zephyr-sdk-0.11.4-setup.run -- -d ~/zephyr-sdk-0.11.4
chyi@mars:~/zephyr-sdk-0.11.4$ ls -la
합계 56
drwxr-xr-x 13 chyi chyi 4096 10월 30 11:05 .
drwxr-xr-x 23 chyi chyi 4096 10월 30 11:04 ..
drwxr-xr-x  8 chyi chyi 4096  6월 25 20:49 aarch64-zephyr-elf
drwxr-xr-x  8 chyi chyi 4096  6월 25 21:50 arc-zephyr-elf
drwxr-xr-x  8 chyi chyi 4096  6월 25 21:56 arm-zephyr-eabi
drwxr-xr-x  3 chyi chyi 4096  6월 25 20:42 cmake
drwxr-xr-x  2 chyi chyi 4096 10월 30 11:05 info-zephyr-sdk-0.11.4
drwxr-xr-x  8 chyi chyi 4096  6월 25 21:00 nios2-zephyr-elf
drwxr-xr-x  8 chyi chyi 4096  6월 25 21:05 riscv64-zephyr-elf
-rw-r--r--  1 chyi chyi    7 10월 30 11:05 sdk_version
drwxr-xr-x  8 chyi chyi 4096  6월 25 21:01 sparc-zephyr-elf
drwxr-xr-x  3 chyi chyi 4096  6월 25 21:15 sysroots
drwxr-xr-x  8 chyi chyi 4096  6월 25 20:50 x86_64-zephyr-elf
drwxrwxr-x  9 chyi chyi 4096 10월 30 11:05 xtensa

chyi@mars:~/workspace/Zephyr$ sudo cp ~/zephyr-sdk-0.11.4/sysroots/x86_64-pokysdk-linux/usr/share/openocd/contrib/60-openocd.rules /etc/udev/rules.d

$ sudo udevadm control --reload

필자는 Linux 환경에 익숙해서인지 terminal 환경에서 개발하는 것을 좋아한다. 하지만 IDE 개발 환경을 선호하는 개발자들도 많은데, 이를 위해 zephyr는 아래의 두가지 방식을 지원한다.

1) Eclipse 환경


2) (Third party에서 개발한) Platform IO(Visual Studio plugin) 환경



자, zephyr 개발 환경이 모두 준비되었으니, 이제부터는 실제 예제를 하나 돌려 보면서 zephyr build system이 어떻게 동작하는지를 살펴 보도록 하자.


3. Build system 소개
이 장에서는 zephyr sample code를 하나 돌려본 후, 실제 build 시스템이 어떻게 구성되어 있는지를 그림을 통해 상세히 파악해 볼 것이다.

a) Target board
예제 코드를 돌려 보기 위해서는 target board가 하나 필요한데, 지금부터는 아래 보드를 사용하기로 한다.
ARM Cortex-M3, 72Mhz, 128KB Flash, 20KB SRAM, ST-Link/V2 debugger 장착
[그림 3.1]  Target Board - STM32 Nucleo F103RB [출처 - 참고문헌 3]

📌 Nucleo F103RB 보드는 $10 ~ 15(한화로 13,000 ~ 14,000원 정도) 정도로 가격이 아주 저렴하다. 또한 ST-Link/V2 debugger(선이 그어져 있는 그림 우측 부분)가 장착되어 있어, 곧 바로 flash writing이 가능하다. 

b) 예제 build해 보기 - synchronization
chyi@mars:~$ cd ~/zephyrproject/
chyi@mars:~/zephyrproject$ cd zephyr/
chyi@mars:~/zephyrproject/zephyr$ west build -b nucleo_f103rb -s samples/synchronization

[그림 3.2] Zephyr 예제 코드 build 모습(1)

[그림 3.3] Zephyr 예제 코드 build 모습(2) - output directory

zephyr.elf 파일이 생성되었으니, west flash 명령을 이용하여 target board에 writing해 보도록 하자.

chyi@mars:~/zephyrproject/zephyr$ west flash

[그림 3.4] west를 이용한 flash writing 모습(1)

[그림 3.5] west를 이용한 flash writing 모습(2) - minicom console(/dev/ttyACM0, 115200, 8N1)

📌 west를 이용해  clean을 하려면 ...
$ west build -t clean          # built 파일을 모두 삭제
$ west build -t pristine      # build 디렉토리를 통째로 날림.
_________________________________________________

앞서 build해 본 바와 같이 zephyr build 과정은 크게 2단계로 나뉜다.

   1) CMake에 의한 configuration 단계 
        => dts/binding/Kconfig 등을 이용하여 주요 header 파일 및 Makefile(or Ninja 파일) 생성
        ex) cmake -B build -GNinja -DBOARD=nucleo_f103rb samples/hello_world
   2) Make or Ninja를 이용한 실제 build 단계
        => 실제 build 절차를 진행하여 elf 파일 등 생성
        ex) ninja -C build or make -C build

📌 Zephyr build system은 CMake, Ninja/Make를 주축으로 하며, 중간 중간에 python script가 적절히 활용되고 있다.

c) CMake에 의한 configuration 단계
이 단계에서는 dts 파일, binding 정보, prj.conf 및 Kconfig 파일 정보 등을 토대로 아래와 같이 autoconf.h, .config, devicetree.h, Makefile or Ninja file 등을 생성한다.

[그림 3.6] CMake에의 configuration 단계(header 파일 생성[출처 - 참고문헌 1]


d) Make or Ninja를 이용한 실제 build 단계
실제 build 단계는 다시 4개의 과정 즉, pre-build, first-pass binary, final binary, post-processing 으로 세분화 될 수 있다.

[그림 3.7] Ninja or Make를 이용한 build 단계 1(pre-build[출처 - 참고문헌 1]


[그림 3.8] Ninja or Make를 이용한 build 단계 2(first-pass binary[출처 - 참고문헌 1]


[그림 3.9] Ninja or Make를 이용한 build 단계 3(final binary[출처 - 참고문헌 1]


[그림 3.10] Ninja or Make를 이용한 build 단계 4(post-processing[출처 - 참고문헌 1]

각각의 단계를 상세히 소개하는 것은 간단한 일이 아니다. 따라서 보다 자세한 사항은 아래 site 내용을 살펴 보기 바란다.



4. Device tree 기반의 device driver model
이번 장에서는 device tree를 기반으로 하는 zephyr RTOS의 device driver model을 분석해 볼 것이다. 사실 이 부분은 조금 난해할 수 있다. 하지만 반드시 이해해야 하는 부분이기도 하다.

a)  blinky 예제 분석하기
Zephyr의 device driver 동작 원리를 파악하기 전에, 관련 예제 코드( blinky)를 먼저 살펴 보도록 하자.

chyi@mars:~/zephyrproject/zephyr/samples/basic/blinky$ ls -la
합계 28
drwxr-xr-x  3 chyi chyi 4096 11월  2 10:39 .
drwxr-xr-x 10 chyi chyi 4096 10월 30 11:14 ..
-rw-r--r--  1 chyi chyi  188 10월 30 10:35 CMakeLists.txt          # CMake 파일용
-rw-r--r--  1 chyi chyi 1196 10월 30 10:35 README.rst
-rw-r--r--  1 chyi chyi   14 10월 30 10:35 prj.conf                        # kernel config 기술
-rw-r--r--  1 chyi chyi  224 10월 30 10:35 sample.yaml              # binding 파일
drwxr-xr-x  2 chyi chyi 4096 11월  4 11:09 src

[그림 4.1] CMakeLists.txt 파일

[그림 4.2] prj.conf 파일

chyi@mars:~/zephyrproject/zephyr/samples/basic/blinky/src$ ls -la
합계 16
drwxr-xr-x 2 chyi chyi 4096 11월  4 11:09 .
drwxr-xr-x 3 chyi chyi 4096 11월  2 10:39 ..
-rw-r--r-- 1 chyi chyi 1084 10월 30 11:31 main.c

📌 Zephyr application(or device driver)은 모두 위와 같이 구성되어 있다. src 디렉토리 아래에 C & header 파일을 추가해 주면 된다.

main.c 파일을 열어 보니, 예상대로 몇줄 되지 않는다. 하지만 몇 줄 안되는 아래 코드를 제대로 이해하기 위해서는 파악할 내용이 만만치 않다.

[그림 4.3] blinky 예제 코드

우선 제일 먼저 눈에 들어오는 코드 부분은 아래 한 줄이다. 다른 device driver 예제를 살펴 보아도 모두 제일 먼저 이 함수를 호출한다.

dev = device_get_binding(LED0)

____________________________ 위의 코드 분석 시도 ____________________________
위 코드에서 LED0는 아래와 같이 define되어 있다. 

#define LED0    DT_GPIO_LABEL(LED0_NODE, gpios)
#define LED0_NODE DT_ALIAS(led0)
-> 줄여서 이렇게도 볼 수 있다.
#define LED0    DT_GPIO_LABEL(DT_ALIAS(led0), gpios)

📌그렇다면  led0, gpios는 어디에 있는 정보일까 ?

좀더 따라 들어가 보도록 하자. DT_GPIO_LABEL은 include/devicetree/gpio.h 파일에 아래와 같이 정의되어 있다.

#define DT_GPIO_LABEL(node_id, gpio_pha) \
    DT_GPIO_LABEL_BY_IDX(node_id, gpio_pha, 0)
=>
#define DT_GPIO_LABEL_BY_IDX(node_id, gpio_pha, idx) \
    DT_PROP_BY_PHANDLE_IDX(node_id, gpio_pha, idx, label)
=>
#define DT_PROP_BY_PHANDLE_IDX(node_id, phs, idx, prop) \
    DT_PROP(DT_PHANDLE_BY_IDX(node_id, phs, idx), prop)
=>
#define DT_PROP(node_id, prop) DT_CAT(node_id, _P_##prop)    ... include/devicetree.h
=>
#define DT_CAT(node_id, prop_suffix) node_id##prop_suffix

node_id##prop_suffix 가 의미하는 것은 또 무엇일까 ?
________________________________________________________________________

눈치 빠른 분은 예상했겠지만, 위 내용은 device tree와 연관이 있다. 따라서 이 대목에서 우리는 Nucleo f103rb의 device tree 파일 및 binding file(yaml)을 확인해 볼 필요가 있다.

[그림 4.4] zephyr/boards/arm/nucleo_f103rb/nucleo_f103rb.dts 파일

[그림 4.5] zephyr/samples/basic/blinky/sample.yaml

(설명이 길어지는 관계로) 답을 미리 말하자면, 앞에서 분석한 내용 중 led0gpios는 각각 위의 device tree 내용 중 아래 부분에 해당한다. 
______________________________________________
led0 = &green_led_2;

leds {
        compatible = "gpio-leds";    # samples/basic/blinky/sample.yaml 내용과 연관
        green_led_2: led_2 {
               gpios = <&gpioa 5 GPIO_ACTIVE_HIGH>;
               label = "User LD2";
        };
 };
______________________________________________

📌 Linux에서 device driver와 device tree를 연결하는 것이 compatible 속성이었던 것을 기억하는가 ? Zephyr에서는 이 연결 작업을 device driver에서 run time에 하는 것이 아니라 compile time에 device tree와 위의 binding file을 이용하여 진행한다.

위 내용 중에서 device driver에서 실제로 필요로 하는 것은 녹색으로 표시한 GPIO 관련  정보일 것이다. 따라서 (다시 처음 코드로 돌아와) 우리는 아래 코드가 led0(alias 명) -> gpios(속성 명) 를 이용해서 leds 노드 정보를 찾는 과정임을 유추해 볼 수가 있다.

dev = device_get_binding(LED0)

📌그런데 kernel source code 어디에도 device_get_binding( ) 함수가 보이질 않는다 ? 어디에 있을까 ?

아직 필자는 앞에서 살펴본 매우 복잡한 #define 문의 내용이 무엇을 의미하는지를 제대로 설명하지 못했다. 이 내용을 파악하기 위해서는 (아래 page를 참조하여) 좀 더 깊게 들어가 볼 필요가 있다.


b) Zephyr의 device tree
Zephyr는 Linux의 brother OS이다. 따라서 Linux의 많은 장점을 그대로 채용하여 사용하고 있는데, 그 중 하나가 바로 device tree이다. Device tree에 대해서는 본 blog를 통해 여러 차례 소개한 바 있다.


하지만 zephyr는 resource constrained 장치용 OS이다 보니, device tree를 binary 형태(dtb 파일)로 유지(flash memory에 탑재)하는 것도 부담이 된다. 따라서 Linux와는 달리 dtb 파일 형태를 유지하지 않고, pre-compile 단계에서 device tree를 적절히 파싱하여 header 파일로 만든 후, binding 과정(yaml 파일 내용)을 거쳐 elf에 적절히 녹이는 방식을 사용한다(?).

<Linux의 device tree 처리 방식>
dtb 파일 생성 -> memory에 load -> kernel 에서 runtime에 dtb 내용을 읽어 들여 필요한 device 속성 정보 추출 -> device driver에서 이를 사용

[그림 4.6] Linux에서 device tree를 처리하는 방식 [출처 - 참고문헌 4]

<Zephyr의 device tree 처리 방식>
pre-compile 단계에서 device tree  파일로 부터 header 파일 생성 -> build  단계에서 device tree 정보를 이용하여 elf 파일에 포함 -> device driver에서 device_get_binding( ) 함수를 호출하여 사용

아래 그림은 dts 파일로 부터 header 파일이 생성되는 과정을 보여준다.


[그림 4.7] Zephyr에서 device tree를 처리하는 방식 - device tree로 부터 header 전환 과정 [출처 - 참고문헌 1]

📌 중간 결과물인  zephyr.dts는 debugging 용일 뿐 실제로 사용되지는 않는다. 즉 dtb로 변환되어 memory에 load되지 않는다.

이렇게 해서 생성한 header 파일 중 하나가 바로 devicetree_unfixed.h 파일(전부 #define 문으로 구성됨)이다.

[그림 4.8] build/zephyr/include/generated/devicetree_unfixed.h

이후, (너무 모호하게 설명하는 듯 하지만) build 과정에서 device tree 정보가 elf 파일에 적절히 녹아 들어가게 된다(linker.cmd 이용).

c) Device driver model
다음으로 알아볼 내용은 zephyr의 device driver model에 관한 것이다. Zephyr는 device를 위해 아래와 같이 struct devicedevice_get_binding( ) 함수를 정의해 두고 있다(Linux kernel 처럼 복잡한 방식이 아니다). Application code(device driver)에서는 device_get_binding( ) 함수를 이용하여 device(struct device)를 정보를 획득한 후, 이를 이용해 실제 device driver 관련 나머지 작업을 진행한다.

[그림 4.9] struct device - zephyr/include/device.h

앞서 찾을 수 없었던, device_get_binding( ) 함수의 실체는 다음과 같다.
______________________________________________________________________
extern const struct device * z_impl_device_get_binding(const char * name);
static inline const struct device * device_get_binding(const char * name)
{
#ifdef CONFIG_USERSPACE
    if (z_syscall_trap()) {
        return (const struct device *) arch_syscall_invoke1(*(uintptr_t *)&name, K_SYSCALL_DEVICE_GET_BINDING);
    }
#endif
    compiler_barrier();
    return z_impl_device_get_binding(name);
}
______________________________________________________________________
 [코드 4.1] device_get_binding( ) 함수 - 
zephyr/build/zephyr/include/generated/syscalls/device.h

z_impl_device_get_binding( ) 함수에서 device 정보를 찾는 과정을 좀 더 깊게 따라가 보면 다음과 같다.

[그림 4.10] z_impl_device_get_binding( ) 함수 - kernel/device.c

위 함수는 __device_start 부터 __device_end까지의 device를 대상으로 dev->name이 일치하는 무언가(= struct device)를 찾아내는 일을 하고 있는 듯 보인다.

그렇다면, (kernel/device.c에 아래와 같이 선언되어 있는) __device_start ~ __device_end의 실제 내용은 어디에 있을까 ?
extern const struct device __device_start[];
extern const struct device __device_end[];

이를 source code에서는 찾을 수 없어, build 결과물 중에서 뒤져 보기로 한다.

chyi@mars:~/zephyrproject/zephyr/build/zephyr$ grep -rl __device_start *
kernel/libkernel.a
kernel/CMakeFiles/kernel.dir/device.c.obj
linker.cmd
linker_pass_final.cmd
zephyr.elf
zephyr.lst
zephyr.map
zephyr_prebuilt.elf
zephyr_prebuilt.map

그 결과, linker.cmd 파일에서 __device_start 부분을 확인할 수 있었다. 아래 내용은 linker script로 보이는데, 이게 어떤 것을 의미하는 것일까 ?

[그림 4.11] zephyr/build/zephyr/linker.cmd 내용 중에서 발췌

아직 내용이 정확하지는 않지만, 앞서 언급한 대로 "build 단계에서 binding 정보와 일치하는 device tree 정보가 linker.cmd 등을 참조하여 elf에 포함되었고, application(device driver)에서는 device_get_binding( ) 함수를 통해 name이 일치하는 device 정보를 바로 추출하여 사용한다" 정도로 정리가 될 것 같다.
📌 이 부분은 시간을 두고 좀 더 정확하게 분석해 볼 필요가 있다. 😂

d) C/C++ code에서 device tree 접근하기 
앞서 복잡하게 분석해 들어간 #define 문의 정확한 의미를 파악하기 위해서는 아래 글이 도움이 될 것이다.


내용이 길어지는 관계로 아래 내용을 copy & paste하는 것으로 설명을 대신하기로 하겠다.

[그림 4.12] device tree node ID 값을 얻는 방법 [출처 - 참고문헌 1]

📌 위의 내용이 뜻하는 바는 윗 부분의 device tree 내용 중 node 부분을 아래 부분의 DT_XXX( ) macro로 찾을 수 있다는 뜻이다.

앞서도 이미 언급한 바와 같이 zephyr는 device tree blob 형태로 device tree를 사용하지 않고, 이를 header 파일로 적절히 변환(#define 정보 형태로 기술)하고, C code에서는 #define 정보를 이용하여 device tree의 node 정보를 획득하게 된다.

Device tree node를 인식하는 방법으로는 크게 아래와 같이 6가지가 있다(매우 중요한 부분이지만 자세한 설명은 생략한다).
1) Path에 의한 방법
2) Node label을 이용한 방법
3) Alias 정보를 이용한 방법
4) Instance number를 이용한 방법
5) Chosen node를 이용한 방법
6) Parent or child node를 이용한 방법

끝으로, 아래 내용은 그림 4.8에서 한 차례 언급했던  build/zephyr/include/generated/devicetree_unfixed.h 파일을 보여준다.

[그림 4.13] device tree가 header로 변경된 모습

e) blinky 예제의 나머지 부분 
이 부분은 크게 어려운 부분이 아니다. 먼저 (A)에서는 GPIO 5번 pin(GPIO_ACTIVE_HIGH)을 output mode로 설정하고, (B)에서는 무한 loop을 돌면서 GPIO 5번 pin의 값을 5초 간격으로 1 -> 0 -> 1 ... 로 설정 변경해 준다. 그 결과 당연히 LED가 On -> Off -> On ... 을 반복하게 된다.

_________________________________________________
    ret = gpio_pin_configure(dev, PIN, GPIO_OUTPUT_ACTIVE | FLAGS);   ...... (A)
    if (ret < 0) {
        return;
    }

    while (1) {
        gpio_pin_set(dev, PIN, (int)led_is_on);    ...... (B)
        led_is_on = !led_is_on;
        k_msleep(SLEEP_TIME_MS);
    }
_________________________________________________

Zephyr는 Linux의 장점 중 하나인 device tree 개념을 그대로 채용하여, 많은 device를 효과적으로 기술하고 있다. 하지만 resource constrained라는 환경 탓에 Linux와는 달리 다소 복잡한 내부 구조를 가져갈 수 밖에 없게 된 듯 보인다.

어찌 되었든, 복잡한 내부 구조하고는 무관하게 개발자 입장에서는 몇가지 사실만 제대로 알고 있다면, zephyr 환경에서의 device driver 개발은 그리 어렵지 않을 것으로 보인다.


5. 아직 못다한 이야기
필자가 blog에 글을 올리는 이유는 모든 것을 다 알고 있는 상태에서 이를 남들에게 과시하기 위해서가 절대 아니다. 필자가 열심히 study한 내용을 글로 정리해 보는 것은 필자 자신에게 가장 큰 도움(정리를 하다 보면 아는 부분과 모르는 부분을 정확히 알 수 있게됨)이 되기 때문이다. 더불어 이렇게 어렵사리 정리한 글이 (도움의 손길이 필요한) 다른 이들에게 조금이나마 도움이 된다면 그 또한 나쁘지 않겠다는 생각에서 그렇게 하는 것이다. 갑자기 넉두리를 ㅎㅎ 😀

아직도 zephyr 관련하여 설명이 충분히 이루어지지 못한 부분(필자가 아직 제대로 파악하지 못한 부분)이 많이 남아 있다. 하지만 여기가 끝이 아니다. 오히려 이제 부터가 시작이다. Zephyr가 완벽히 이해될 때까지 계속 고민하고 또 고민할 것이다.

a) Kernel Services
RTOS kernel programming은 application 단의 thread programming(예: pthread)에 익숙한 분들에게는 그다지 어려운 내용이 아니다. 아래 page 내용을 참조하여 sample code를 분석해 보기 바란다.



b) 부팅 flow와 application main( ) 함수 호출 과정
Zephyr의 version이 올라가면서 많은 부분이 수정되었다. Kernel code도 예외는 아니었는데, zephyr의 부팅 코드 흐름을 다시 정리해 보면 다음과 같다. 끝 부분에 main( ) 함수 호출 부분이 보이는가 ? 이 곳에서 application에 있는 main( ) 함수가 호출되는 것이다. 참고로 application 입장에서는 zephyr kernel(zephyr/kernel 디렉토리 코드)은 라이브러리(libkernel.a)에 불과하다.


[그림 5.1] zephyr booting flow - arm32 기준

📌 예제 코드를 보다 보면, main() 함수가 없는 경우가 있다(그 대신에 thread를 생성하는 코드만 있음). 이 경에는 main thread(kernel code)는 실행을 종료하게 되며, application에서 생성한 thread만이 동작하게 된다.

c) 새로운 보드에 application 추가하기

...

d) CMake & Ninja

...

e) Device tree bindings
...

To be continued ...


6. References
[1] https://docs.zephyrproject.org/
[2] Zephyr Project: Unlocking Innovation with an Open Source RTOS, Kate Stewart, Linux Foundation
[3] https://www.st.com/en/evaluation-tools/nucleo-f103rb.html
[4] https://events.static.linuxfound.org/sites/events/files/slides/petazzoni-device-tree-dummies.pdf


Slowboot