2017년 3월 22일 수요일

Zephyr Project 소개

이번 시간에는 최근(2016.2)에 Linux Foundation에서 release한 Zephyr Project(RTOS)에 관하여 소개해 보고자 한다. Zephyr Project(제퍼 프로젝트 - 산들바람)는 이전 시간에 소개한 RIOT OS와 마찬가지로 resource constrained device 즉, 리소스 제한적인 장치 및 IoT를 target으로 하고 있으며, Linux Foundation과 Intel/NXP/Linaro/Synopsys/Nordic 등이 주도하는 Linux에 매우 가까운 OS(linux little brother라는 별명이 있음)로 볼 수 있다. 하지만 실제 kernel 자체는 Linux하고는 전혀 무관하게, Intel Wind River(VxWorks -> Viper -> Zephyr)의 것을 채용하고 있어, 출시한지 얼마 되지 않았음에도 불구하고 안정성에 자신이 있는 모습이다.


이제 release된지 2년도 채 안된(version 1.7까지 release됨) 신생 OS라고도 볼 수 있는 Zephyr OS가, 과연 IoT RTOS 춘추 전국시대를 통일할 수 있을지 지금 부터 자세히 살펴 보도록 하자.


<목차>
1. Zephyr Project 개요
2. Zephyr source build하기
3. Target board(FRDM-K64F)에서 돌려 보기
4. Device drivers and device model
5. Zephyr kernel primer(version 2) : code examples
6. Zephyr kernel 부팅 flow 분석


1. Zephyr Project 개요
지금은 그야말로 IoT용 RTOS 춘추 전국시대이다. 상용 RTOS는 차치하더라도 FreeRTOS, ARM mbed, RIOT OS, Contiki 등 다양한 open source RTOS가 현존하는 상황이다.

이 시점에 Linux foundation이 새롭게 release한 IoT용 RTOS Zephyr(제퍼) ! 그 특징을 정리해 보면 다음과 같다.

"One of the major advantages of Zephyr for me as a microcontroller RTOS and development environment is that it aims to re-use standard open source tools and concepts. Digging more deeply into Zephyr as an RTOS doesn’t not involve going down various rabbit-holes relating to choosing specific IDEs, configuration utilities or proprietary libraries." .... 참고문헌[2] 중에서 발췌 

<Zephyr RTOS의 특징>
  a) Wind River에서 초기 개발한 kernel을 채택(이후 개선)하고 있다 - RIOT OS kernel과 비교해 뭔가 좀 더 복잡해 보인다. 흥미를 유발시키는 kernel이다^^.
      => kernel, driver 등의 API가 독특한 구조로 되어 있어, 사용법에 익숙해 지는데 시간이 필요해 보인다.
RIOT OS < Zephyr OS < Linux
(programming 난이도)

  b) 나머지 부분은 외형적으로 Linux와 상당히 비슷한 면(디렉토리 구조, coding style, configuration, dts, Makefile 등)이 다수 보이며, Linux foundation이 밀고 있어 파급력이 클 것으로 보인다(source code를 봐도 그렇고 뭔가 체계화되어 있다는 느낌이 강하다).
  c) 후발 주자라서 그런지, web page에 OS 관련 설명이 잘 정리되어 있다. 
  d) 아직은 좀 부족하지만, 그래도 생각보다는 많은 device(MPU)를 지원하고 있다(앞으로 더 확대될 것으로 보임).
  e) Linux 환경에서 개발하기에 편리하다(Mac OS X, Windows 8.1 등에서도 개발 가능함). 특히 toolchain을 위한 Zephyr SDK는 매우 설치하기 편하다.
  f) Apache 2.0 license 정책을 따른다.

그림 1.1 Zephyr 개요(1)


그림 1.2 Zephyr 개요(2)


그림 1.3 Zephyr kernel features

그림 1.4 Zephyr driver frameworks & HAL

참고 사항: 그림 1.3 ~ 1.4의 내용 중, fiber services/task services, microkernel/nanokernel 등의 표현은 1.6.0 버젼 이전의 내용(초기 설계시 등장한 개념)으로, 당분간만 유지(legacy code로 존재)한 후 완전히 삭제될 것이라고 한다.


2. Zephyr source build하기
이번 절에서는 Ubuntu 16.04 LTS(64bit)를 기준으로 zephyr source code를 내려 받아 build하는 절차를 소개해 보고자 한다(참고: Zephyr project는 현재 Fedora 25 64-bit에서도 개발 가능하다).

<Ubuntu 16.04 LTS - 패키지 설치>
$ sudo apt-get install git make gcc g++ python3-ply ncurses-dev python-yaml python2 dfu-util

<Zephyr SDK 설치>
  => toolchain(x86, arm, arc, niosII 관련) & sysroot가 포함되어 있음.
  => https://www.zephyrproject.org/downloads/tools에서 Zephyr SDK v0.9 내려 받는다.
  => 재밌게도 zephyr sdk는 (같은 linux foundation project인) yocto project의 sdk 생성 과정을 통해 만들어진 듯 보인다.

$ chmod 755 ./zephyr-sdk-0.9-setup.run
$ ./zephyr-sdk-0.9-setup.run
Verifying archive integrity... All good.
Uncompressing SDK for Zephyr  100%  
Enter target directory for SDK (default: /opt/zephyr-sdk/): /home/chyi/zephyr-sdk
Installing SDK to /home/chyi/zephyr-sdk
Creating directory /home/chyi/zephyr-sdk
Success
 [*] Installing x86 tools... 
 [*] Installing arm tools... 
 [*] Installing arc tools... 
 [*] Installing iamcu tools... 
 [*] Installing nios2 tools... 
 [*] Installing xtensa tools... 
 [*] Installing riscv32 tools... 
 [*] Installing additional host tools... 
Success installing SDK. SDK is ready to be used.

$ export ZEPHYR_GCC_VARIANT=zephyr
$ export ZEPHYR_SDK_INSTALL_DIR=/home/chyi/zephyr-sdk

or

$ cat <<EOF > ~/.zephyrrc
> export ZEPHYR_GCC_VARIANT=zephyr
> export ZEPHYR_SDK_INSTALL_DIR=/home/chyi/zephyr-sdk
> EOF


<Zephyr source download & build>
$ git clone https://gerrit.zephyrproject.org/r/zephyr zephyr-project
$ cd zephyr-project
$ source zephyr-env.sh
  => zephyr 환경을 설정한다.

$ cd samples/hello_world/
  => hello_world app을 build해 보자.
$ ls -la
합계 32
drwxrwxr-x  3 chyi chyi 4096  3월 19 17:14 .
drwxrwxr-x 21 chyi chyi 4096  3월 19 17:14 ..
-rw-rw-r--  1 chyi chyi   77  3월 19 17:14 Makefile
                                                                       <= application makefile
-rw-rw-r--  1 chyi chyi  723  3월 19 17:14 README.rst
-rw-rw-r--  1 chyi chyi   15  3월 19 17:14 prj.conf
                                                                       <= default project conf file(config 내용이 들어 있음)
-rw-rw-r--  1 chyi chyi   24  3월 19 17:14 prj_single.conf
drwxrwxr-x  2 chyi chyi 4096  3월 19 21:25 src
                                                                       <= application source code 위치함.
-rw-rw-r--  1 chyi chyi  187  3월 19 17:14 testcase.ini

$ make
  => application을 build한다.
  => kernel & device driver 등이 마치 library인 것 처럼 함께 build된다.
Using /home/chyi/IoT/zephyr-project/boards/x86/qemu_x86/qemu_x86_defconfig as base
Merging /home/chyi/IoT/zephyr-project/tests/include/test.config
Merging /home/chyi/IoT/zephyr-project/kernel/configs/kernel.config
Merging prj.conf
#
# configuration written to .config
#
make[1]: 디렉터리 '/home/chyi/IoT/zephyr-project' 들어감
make[2]: 디렉터리 '/home/chyi/IoT/zephyr-project/samples/hello_world/outdir/qemu_x86' 들어감
  GEN     ./Makefile
scripts/kconfig/conf --silentoldconfig Kconfig
  Using /home/chyi/IoT/zephyr-project as source for kernel
  GEN     ./Makefile
  CHK     include/generated/version.h
  UPD     include/generated/version.h
  HOSTCC  scripts/gen_idt/gen_idt.o
  HOSTLD  scripts/gen_idt/gen_idt
  CHK     misc/generated/configs.c
  UPD     misc/generated/configs.c
  CHK     include/generated/generated_dts_board.h
  UPD     include/generated/generated_dts_board.h
  CHK     include/generated/offsets.h
  UPD     include/generated/offsets.h
  CC      lib/libc/minimal/source/stdlib/strtol.o
  CC      lib/libc/minimal/source/stdlib/strtoul.o
  CC      lib/libc/minimal/source/stdlib/atoi.o
  LD      lib/libc/minimal/source/stdlib/built-in.o
  CC      lib/libc/minimal/source/stdout/fprintf.o
  CC      lib/libc/minimal/source/stdout/prf.o
  CC      lib/libc/minimal/source/stdout/sprintf.o
  CC      lib/libc/minimal/source/stdout/stdout_console.o
  LD      lib/libc/minimal/source/stdout/built-in.o
  CC      lib/libc/minimal/source/string/string.o
  CC      lib/libc/minimal/source/string/strncasecmp.o
  CC      lib/libc/minimal/source/string/strstr.o
  LD      lib/libc/minimal/source/string/built-in.o
  LD      lib/libc/minimal/source/built-in.o
  LD      lib/libc/minimal/built-in.o
  LD      lib/libc/built-in.o
  LD      lib/built-in.o
  CC      misc/printk.o
  CC      misc/generated/configs.o
  LD      misc/generated/built-in.o
  LD      misc/built-in.o
  LD      boards/x86/qemu_x86/built-in.o
  LD      boards/built-in.o
  LD      ext/debug/built-in.o
  LD      ext/fs/built-in.o
  LD      ext/hal/built-in.o
  LD      ext/lib/crypto/built-in.o
  LD      ext/lib/built-in.o
  LD      ext/built-in.o
  LD      subsys/debug/built-in.o
  CC      subsys/logging/sys_log.o
  LD      subsys/logging/built-in.o
  LD      subsys/built-in.o
  LD      tests/built-in.o
  LD      arch/common/built-in.o
  CC      arch/x86/core/cpuhalt.o
  CC      arch/x86/core/msr.o
  CC      arch/x86/core/irq_manage.o
  CC      arch/x86/core/sys_fatal_error_handler.o
  AS      arch/x86/core/crt0.o
  AS      arch/x86/core/cache_s.o
  CC      arch/x86/core/cache.o
  AS      arch/x86/core/excstub.o
  AS      arch/x86/core/intstub.o
  AS      arch/x86/core/swap.o
  CC      arch/x86/core/thread.o
  CC      arch/x86/core/fatal.o
  LD      arch/x86/core/built-in.o
  CC      arch/x86/soc/ia32/soc.o
  LD      arch/x86/soc/ia32/built-in.o
  LD      arch/x86/built-in.o
  LD      arch/built-in.o
  CC      drivers/console/uart_console.o
  LD      drivers/console/built-in.o
  CC      drivers/interrupt_controller/i8259.o
  CC      drivers/interrupt_controller/loapic_intr.o
  CC      drivers/interrupt_controller/system_apic.o
  CC      drivers/interrupt_controller/ioapic_intr.o
  LD      drivers/interrupt_controller/built-in.o
  LD      drivers/random/built-in.o
  CC      drivers/serial/uart_ns16550.o
  LD      drivers/serial/built-in.o
  CC      drivers/timer/hpet.o
  CC      drivers/timer/sys_clock_init.o
  LD      drivers/timer/built-in.o
  LD      drivers/built-in.o
  CC      kernel/version.o
  LD      kernel/built-in.o
  CC      kernel/alert.o
  CC      kernel/device.o
  CC      kernel/errno.o
  CC      kernel/idle.o
  CC      kernel/init.o
  CC      kernel/legacy_offload.o
  CC      kernel/mailbox.o
  CC      kernel/mem_pool.o
  CC      kernel/mem_slab.o
  CC      kernel/msg_q.o
  CC      kernel/mutex.o
  CC      kernel/pipes.o
  CC      kernel/queue.o
  CC      kernel/sched.o
  CC      kernel/sem.o
  CC      kernel/stack.o
  CC      kernel/sys_clock.o
  CC      kernel/system_work_q.o
  CC      kernel/thread.o
  CC      kernel/thread_abort.o
  CC      kernel/timer.o
  CC      kernel/work_q.o
  AR      kernel/lib.a
  CC      src/main.o
  LD      src/built-in.o
  AR      libzephyr.a
  LINK    zephyr.lnk
  SIDT    staticIdt.o
  LINK    zephyr.elf
  BIN     zephyr.bin
make[2]: 디렉터리 '/home/chyi/IoT/zephyr-project/samples/hello_world/outdir/qemu_x86' 나감
make[1]: 디렉터리 '/home/chyi/IoT/zephyr-project' 나감
  => build 결과, outdir/qemu_x86/zephyr.bin 파일이 생성됨.

그림 2.1 hello_world output - zephyr.bin

<결과 실행하기 - qemu_x86>
chyi@earth:~/IoT/zephyr-project/samples/hello_world$ make run
  => qemu_x86 상에서 application을 실행한다.

그림 2.2 qemu_x86 상에서 zephyr.bin 실행하는 모습

참고 사항: qemu를 빠져 나가기 위해서는 Ctrl+a, x 키를 눌러주면 된다.

<make option 확인하기>
$ make help
Cleaning targets:
  clean  - Remove most generated files but keep configuration and backup files
  mrproper  - Remove all generated files + config + various backup files
  distclean  - mrproper + remove editor backup and patch files
  pristine  - Remove the output directory with all generated files

Configuration targets:

  run <make kconfig-help>

Other generic targets:
  all  - Build all targets marked with [*]
* zephyr  - Build a zephyr application
  run  - Build a zephyr application and run it if board supports emulation
  qemu  - Build a zephyr application and run it in qemu [deprecated]
  qemugdb         - Same as qemu but start a GDB server on port 1234 [deprecated]
  flash  - Build and flash an application
  debug  - Build and debug an application using GDB
  debugserver  - Build and start a GDB server (port 1234 for Qemu targets)
  ram_report  - Build and create RAM usage report
  rom_report  - Build and create ROM usage report

Supported Boards:

  To build an image for one of the supported boards below, run:

  make BOARD=<BOARD NAME>
  in the application directory.

  To flash the image (if supported), run:

  make BOARD=<BOARD NAME> flash

  make BOARD=96b_carbon               - Build for 96b_carbon
  make BOARD=96b_nitrogen             - Build for 96b_nitrogen
  make BOARD=altera_max10             - Build for altera_max10
  make BOARD=arduino_101_ble          - Build for arduino_101_ble
  make BOARD=arduino_101              - Build for arduino_101
  make BOARD=arduino_101_mcuboot      - Build for arduino_101_mcuboot
  make BOARD=arduino_101_sss          - Build for arduino_101_sss
  make BOARD=arduino_due              - Build for arduino_due
  make BOARD=bbc_microbit             - Build for bbc_microbit
  make BOARD=cc3200_launchxl          - Build for cc3200_launchxl
  make BOARD=curie_ble                - Build for curie_ble
  make BOARD=em_starterkit            - Build for em_starterkit
  make BOARD=frdm_k64f                - Build for frdm_k64f
  make BOARD=frdm_kw41z               - Build for frdm_kw41z
  make BOARD=galileo                  - Build for galileo
  make BOARD=hexiwear_k64             - Build for hexiwear_k64
  make BOARD=minnowboard              - Build for minnowboard
  make BOARD=mps2_an385               - Build for mps2_an385
  make BOARD=nrf51_blenano            - Build for nrf51_blenano
  make BOARD=nrf51_pca10028           - Build for nrf51_pca10028
  make BOARD=nrf52840_pca10056        - Build for nrf52840_pca10056
  make BOARD=nrf52_pca10040           - Build for nrf52_pca10040
  make BOARD=nucleo_f103rb            - Build for nucleo_f103rb
  make BOARD=nucleo_f334r8            - Build for nucleo_f334r8
  make BOARD=nucleo_f401re            - Build for nucleo_f401re
  make BOARD=nucleo_f411re            - Build for nucleo_f411re
  make BOARD=nucleo_l476rg            - Build for nucleo_l476rg
  make BOARD=olimexino_stm32          - Build for olimexino_stm32
  make BOARD=panther                  - Build for panther
  make BOARD=panther_ss               - Build for panther_ss
  make BOARD=qemu_cortex_m3           - Build for qemu_cortex_m3
  make BOARD=qemu_nios2               - Build for qemu_nios2
  make BOARD=qemu_riscv32             - Build for qemu_riscv32
  make BOARD=qemu_x86                 - Build for qemu_x86
  make BOARD=qemu_x86_iamcu           - Build for qemu_x86_iamcu
  make BOARD=quark_d2000_crb          - Build for quark_d2000_crb
  make BOARD=quark_se_c1000_ble       - Build for quark_se_c1000_ble
  make BOARD=quark_se_c1000_devboard  - Build for quark_se_c1000_devboard
  make BOARD=quark_se_c1000_ss_devboard - Build for quark_se_c1000_ss_devboard
  make BOARD=sam_e70_xplained         - Build for sam_e70_xplained
  make BOARD=stm3210c_eval            - Build for stm3210c_eval
  make BOARD=stm32373c_eval           - Build for stm32373c_eval
  make BOARD=stm32_mini_a15           - Build for stm32_mini_a15
  make BOARD=tinytile                 - Build for tinytile
  make BOARD=v2m_beetle               - Build for v2m_beetle
  make BOARD=xt-sim                   - Build for xt-sim
  make BOARD=zedboard_pulpino         - Build for zedboard_pulpino


Build flags:

  make V=0|1 [targets] 0 => quiet build (default), 1 => verbose build
  make V=2   [targets] 2 => give reason for rebuild of target
  make O=dir [targets] Locate all output files in "dir", including .config
  make C=1   [targets] Check all c source with $CHECK (sparse by default)
  make C=2   [targets] Force check of all c source with $CHECK
  make RECORDMCOUNT_WARN=1 [targets] Warn about ignored mcount sections
  make W=n   [targets] Enable extra gcc checks, n=1,2,3 where
1: warnings which may be relevant and do not occur too often
2: warnings which occur quite often but may still be relevant
3: more obscure warnings, can most likely be ignored
Multiple levels can be combined with W=12 or W=123

Execute "make" or "make all" to build all targets marked with [*

(*) Toolchain 설치부터 source code build까지 전 과정이 매우 간결하고, 잘 정리되어 있다는 느낌이 든다.


3. Target board(FRDM-K64F)에서 돌려 보기
이번 절에서는 실제 target board인 NXP Freedom(FDRM)-K64F(Cortex-M4)에 몇가지 sample application을 올려 보도록 하겠다.
그림 3.1 NXP Freedom-K64F 보드

그림 3.2 NXP Freedom-K64F 보드 block diagram


그림 3.3 NXP Freedom-K64F 보드 주변 장치

그림 3.4 NXP Freedom-K64F 보드 Arduino R3 헤더

3.1 GPIO interrupt 예제
먼저 사용자가 버튼을 누를 경우(interrupt 발생), console 상으로 메시지가 출력되는 sample code를 살펴 보도록 하자.
-----------------------------------------------------------------------------------------------------------------------------------------------------------------
<samples/basic/button/main.c>
/*
 * Copyright (c) 2016 Open-RnD Sp. z o.o.
 *
 * SPDX-License-Identifier: Apache-2.0
 */

#include <zephyr.h>
#include <board.h>
#include <device.h>
#include <gpio.h>
#include <misc/util.h>
#include <misc/printk.h>

/* change this to use another GPIO port */
#ifdef SW0_GPIO_NAME
#define PORT SW0_GPIO_NAME
#else
#error SW0_GPIO_NAME needs to be set in board.h
#endif

/* change this to use another GPIO pin */
#ifdef SW0_GPIO_PIN
#define PIN     SW0_GPIO_PIN
#else
#error SW0_GPIO_PIN needs to be set in board.h
#endif

/* change to use another GPIO pin interrupt config */
#ifdef SW0_GPIO_INT_CONF
#define EDGE    SW0_GPIO_INT_CONF
#else
/*
 * If SW0_GPIO_INT_CONF not defined used default EDGE value.
 * Change this to use a different interrupt trigger
 */
#define EDGE    (GPIO_INT_EDGE | GPIO_INT_ACTIVE_LOW)
#endif

/* change this to enable pull-up/pull-down */
#define PULL_UP 0

/* Sleep time */
#define SLEEP_TIME 500

void button_pressed(struct device *gpiob, struct gpio_callback *cb, uint32_t pins)  /* interrupt handler 함수 */
{
printk("Button pressed at %d\n", k_cycle_get_32());
}

static struct gpio_callback gpio_cb;

void main(void)
{
struct device *gpiob;

printk("Press the user defined button on the board\n");
gpiob = device_get_binding(PORT);   /* device init section(.init_<something> initlevel sections)에 있는 내용을 name(= PORT)을 가지고 검색하여 struct device 정보를 찾아냄 - driver(controller) 코드에서는 DEVICE_AND_API_INIT macro로 driver(controller = gpio bank)를 초기화함 */
if (!gpiob) {
printk("error\n");
return;
}

gpio_pin_configure(gpiob, PIN, GPIO_DIR_IN | GPIO_INT |  PULL_UP | EDGE);  /* pin 설정(input/interrupt)을 한다  - struct gpio_driver_api의 config() 함수를 호출한다 */

gpio_init_callback(&gpio_cb, button_pressed, BIT(PIN));  /* struct gpio_callback의 handler 함수 pointer에 button_pressed를 대입한다 */

  gpio_add_callback(gpiob, &gpio_cb);   /* struct gpio_driver_api의 manage_callback() 함수를 호출한다. 이때 manage_callback() 함수의  argument로 &gpio_cb를 넘긴다 */
gpio_pin_enable_callback(gpiob, PIN);  /* callback을 enable 한다 */

  while (1) {
uint32_t val = 0;

gpio_pin_read(gpiob, PIN, &val);   /* gpio pin의 값을 읽어 들인다 */
k_sleep(SLEEP_TIME);   /* SLEEP_TIME 만큼 sleep한다 */
}
}
-----------------------------------------------------------------------------------------------------------------------------------------------------------------
<device driver 관련 주요 data structure 분석>               ........................................ (A)

/* static device information - driver instance 별로 ROM에 생성됨 */
struct device_config {
    char    *name;
    int (*init)(struct device *device);
#ifdef CONFIG_DEVICE_POWER_MANAGEMENT
    struct device_pm_ops *dev_pm_ops; /* deprecated */
    int (*device_pm_control)(struct device *device, uint32_t command,
                  void *context);
#endif
    const void *config_info;
};

/* runtime device structure - driver instance 별로 RAM에 생성됨 */
struct device {
    struct device_config *config;
    const void *driver_api;
    void *driver_data;
};

/* device object를 생성하고, 부팅 시 초기화 시켜 주는 macro */
#define DEVICE_AND_API_INIT(dev_name, drv_name, init_fn, data, cfg_info, \
                level, prio, api) \
    \
    static struct device_config _CONCAT(__config_, dev_name) __used \
    __attribute__((__section__(".devconfig.init"))) = { \   /* .text section이 아니라 .devconfig.init section에 배치 - See include/linker/common-rom.ld */
        .name = drv_name, .init = (init_fn), \
        .config_info = (cfg_info) \
    }; \
    _DEPRECATION_CHECK(dev_name, level) \
    static struct device _CONCAT(__device_, dev_name) __used \
    __attribute__((__section__(".init_" #level STRINGIFY(prio)))) = { \  /* .init_<something> initlevel sections에 배치 - See include/linker/common-ram.ld */
         .config = &_CONCAT(__config_, dev_name), \
         .driver_api = api, \
         .driver_data = data \
    }
    => 이 macro로 초기화된 내용(memory에 올려진 device 정보)은 application main routine에서 device_get_binding(name) 함수를 통해 얻을 수 있게 된다.
    => 실제 이 macro 사용과 관련해서는 4절의 코드를 참조하기 바란다.
-----------------------------------------------------------------------------------------------------------------------------------------------------------------

<How to build it>
export ZEPHYR_GCC_VARIANT=zephyr
export ZEPHYR_SDK_INSTALL_DIR=/home/chyi/zephyr-sdk
$ source zephyr-env.sh
  => build를 위한 환경을 설정한다.

$ cd samples/basic/button
make BOARD=frdm_k64f
  => build를 진행한다.

make BOARD=frdm_k64f menuconfig
  => (마치 linux kernel 처럼)configuration을 조정하고 싶으면 이렇게 하자.


그림 3.5 menuconfig 모습 

$ cp outdir/frdm_k64f/zephyr.bin /media/<USERNAME>/MBED/
  => flash에 write한다(해당 directory로 파일을 복사하는 것으로 충분함).

<콘솔 메시지 확인하기>
$ minicom -D /dev/ttyACM0
  => minicom을 실행한 후, target board를 reset한다.
  => 사용자 버튼을 누르면, 아래와 같은 message가 console에 출력된다.


그림 3.6 GPIO interrupt 예제 실행 모습


여기서 잠깐 ! defconfig, dts  and 보드 초기화 관련
Zephyr는 Linux 처럼 아래(그림 3.7)와 같이 defconfig 파일을 가지고 있다. 따라서 원하는 기능만 선택하여 build가 가능(그림 3.5 menuconfig)하다. 또한 재밌게도 device tree(그림 3.8)도 제공한다(단, 아직까지 모든 board에서 device tree를 지원하는 것 같지는 않다).

그림 3.7 boards/arm/frdm_k64f/ frdm_k64f_defconfig

그림 3.8 dts/arm/frdm_k64f.dts

보드 초기화 부분도 linux와 닮은 점이 있다. 아래 코드는 arch/arm/soc/nxp_kinetis/k6x 아래에 있는 soc.c의 내용 중에서 frdm_k64f board를 초기화하는 부분을 발췌한 것이다. SYS_INIT( ) macro는 궁극적으로는 DEVICE_AND_API_INIT( ) macro를 호출하게 된다.

그림 3.9 보드 초기화 코드(arch/arm/soc/nxp_kinetis/k6x/soc.c)

3.2 task synchronization 예제
다음으로 두개의 thread A, B가 동일한 critical section에 동시에 진입하고자 할 때, semaphore를 통해 동기를 맞추는 sample code를 시험해 보도록 하자.
-----------------------------------------------------------------------------------------------------------------------------------------------------------------
<samples/synchronization/main.c>
/* main.c - Hello World demo */

/*
 * Copyright (c) 2012-2014 Wind River Systems, Inc.
 *
 * SPDX-License-Identifier: Apache-2.0
 */

#include <zephyr.h>
#include <misc/printk.h>

/*
 * The hello world demo has two threads that utilize semaphores and sleeping
 * to take turns printing a greeting message at a controlled rate. The demo
 * shows both the static and dynamic approaches for spawning a thread; a real
 * world application would likely use the static approach for both threads.
 */

/* size of stack area used by each thread */
#define STACKSIZE 1024

/* scheduling priority used by each thread */
#define PRIORITY 7

/* delay between greetings (in ms) */
#define SLEEPTIME 500

/*
 * @param my_name      thread identification string
 * @param my_sem       thread's own semaphore
 * @param other_sem    other thread's semaphore
 */
void helloLoop(const char *my_name,     /* thread A, B에서 각각 호출하는 함수 */
       struct k_sem *my_sem, struct k_sem *other_sem)
{
while (1) {
/* take my semaphore: 세마포어 획득 */
k_sem_take(my_sem, K_FOREVER);

/* say "hello" */
printk("%s: Hello World from %s!\n", my_name, CONFIG_ARCH);

/* wait a while, then let other thread have a turn */
k_sleep(SLEEPTIME);
k_sem_give(other_sem);  /* 세마포어 반환 */
}
}

/* define semaphores */
K_SEM_DEFINE(threadA_sem, 1, 1); /* starts off "available" */
K_SEM_DEFINE(threadB_sem, 0, 1); /* starts off "not available" */

/* threadB is a dynamic thread that is spawned by threadA */
void threadB(void *dummy1, void *dummy2, void *dummy3)
{
ARG_UNUSED(dummy1);
ARG_UNUSED(dummy2);
ARG_UNUSED(dummy3);

/* invoke routine to ping-pong hello messages with threadA */
helloLoop(__func__, &threadB_sem, &threadA_sem);
}

char __noinit __stack threadB_stack_area[STACKSIZE];

/* threadA is a static thread that is spawned automatically */
void threadA(void *dummy1, void *dummy2, void *dummy3)
{
ARG_UNUSED(dummy1);
ARG_UNUSED(dummy2);
ARG_UNUSED(dummy3);

/* spawn threadB(threadB 생성) */
k_thread_spawn(threadB_stack_area, STACKSIZE, threadB, NULL, NULL, NULL,
              PRIORITY, 0, K_NO_WAIT);

/* invoke routine to ping-pong hello messages with threadB */
helloLoop(__func__, &threadA_sem, &threadB_sem);
}

/* threadA를 정적으로 선언하고 초기화함 -  struct _static_thread_data 변수 선언 및 필드 설정 */
K_THREAD_DEFINE(threadA_id, STACKSIZE, threadA, NULL, NULL, NULL,
PRIORITY, 0, K_NO_WAIT);   /* 즉시 threadA가 scheduling되도록 함 */
-----------------------------------------------------------------------------------------------------------------------------------------------------------------
아래 내용은 include/kernel.h에 정의되어 있는 K_THREAD_DEFINE( ) macro의 내용을 상세히 분석해 본 것이다.

struct _static_thread_data {
    union {
        char *init_stack;
        struct k_thread *thread;
    };
    unsigned int init_stack_size;
    void (*init_entry)(void *, void *, void *);
    void *init_p1;
    void *init_p2;
    void *init_p3;
    int init_prio;
    uint32_t init_options;
    int32_t init_delay;
    void (*init_abort)(void);
    uint32_t init_groups;
};

#define _THREAD_INITIALIZER(stack, stack_size,                   \
                entry, p1, p2, p3,                   \
                prio, options, delay, abort, groups) \
    {                                                        \
    {.init_stack = (stack)},                                 \                 /* thread를 위한 stack 영역 */
    .init_stack_size = (stack_size),                         \           /* thread stack의 크기 */
    .init_entry = (void (*)(void *, void *, void *))entry,   \     /* thread handler 함수 */
    .init_p1 = (void *)p1,                                   \                /* p1, p2, p3 : thread handler에 넘어가는 파라미터 */
    .init_p2 = (void *)p2,                                   \
    .init_p3 = (void *)p3,                                   \
    .init_prio = (prio),                                     \                   /* thread 우선순위 값 */
    .init_options = (options),                               \              /* thread options */
    .init_delay = (delay),                                   \               /* scheduling delay: 즉시 thread를 실행하지 않을 경우 사용. 즉시 실행할 경우는 K_NO_WAIT 사용해야 함. */
    .init_abort = (abort),                                   \
    .init_groups = (groups),                                 \
    }

#define K_THREAD_DEFINE(name, stack_size,                                \
            entry, p1, p2, p3,                               \
            prio, options, delay)                            \
    char __noinit __stack _k_thread_obj_##name[stack_size];          \
    struct _static_thread_data _k_thread_data_##name __aligned(4)    \
        __in_section(_static_thread_data, static, name) =        \   /* ._static_thread_data.static.* section에 배치 - See include/linker/common-ram.ld */
        _THREAD_INITIALIZER(_k_thread_obj_##name, stack_size,    \
                entry, p1, p2, p3, prio, options, delay, \
                NULL, 0); \
    const k_tid_t name = (k_tid_t)_k_thread_obj_##name
-----------------------------------------------------------------------------------------------------------------------------------------------------------------

<How to build it>
$ cd samples/synchronization
make BOARD=frdm_k64f
  => build를 진행한다.

$ cp outdir/frdm_k64f/zephyr.bin /media/<USERNAME>/MBED/
  => flash에 write한다.

<콘솔 메시지 확인하기>
minicom -D /dev/ttyACM0
  => target board를 reset한다.


그림 3.10 synchronization(2개의 thread간의 semaphore 사용) 예제 실행 모습


3.3 HTTP server 예제
<TBD> - IoT OS의  경우 network 기능이 중요한 만큼 network 관련 예제를 하나 소개해야 함.


4. Device drivers and device model
이번 절에서는 zephyr device driver model(아래 link 참조)에 입각하여, GPIO 관련 controller code(3.1절의 예제와 연관된 내용임)를 분석해 보도록 하겠다. 편의상, NXP가 아닌 STM32 관련 code(Nucleo F103RB 보드를 위해)를 분석해 보기로 하자.

-----------------------------------------------------------------------------------------------------------------------------------------------------------------
<drivers/gpio/gpio_stm32.c>
/*
 * Copyright (c) 2016 Open-RnD Sp. z o.o.
 *
 * SPDX-License-Identifier: Apache-2.0
 */

#include <errno.h>

#include <kernel.h>
#include <device.h>
#include <soc.h>
#include <gpio.h>
#include <clock_control/stm32_clock_control.h>
#include <pinmux/stm32/pinmux_stm32.h>
#include <pinmux.h>
#include <misc/util.h>
#include <interrupt_controller/exti_stm32.h>

#include "gpio_stm32.h"
#include "gpio_utils.h"

/**
 * @brief Common GPIO driver for STM32 MCUs. Each SoC must implement a
 * SoC specific integration glue
 */

/**
 * @brief EXTI interrupt callback
 */
static void gpio_stm32_isr(int line, void *arg)  /* stm32 EXTI interrupt handler */
{
struct device *dev = arg;
struct gpio_stm32_data *data = dev->driver_data;

if (BIT(line) & data->cb_pins) {
_gpio_fire_callbacks(&data->cb, dev, BIT(line));   /* 여기서 application callback 예를 들어, 3.1절의 button_pressed() 함수가 호출된다 */
}
}

/**
 * @brief Configure pin or port
 */
static int gpio_stm32_config(struct device *dev, int access_op,  /* struct gpio_driver_apigpio config 함수 */
    uint32_t pin, int flags)
{
const struct gpio_stm32_config *cfg = dev->config->config_info;
int pincfg;
int map_res;

if (access_op != GPIO_ACCESS_BY_PIN) {
return -ENOTSUP;
}

/* figure out if we can map the requested GPIO
* configuration
*/
map_res = stm32_gpio_flags_to_conf(flags, &pincfg);
if (map_res) {
return map_res;
}

if (stm32_gpio_configure(cfg->base, pin, pincfg, 0)) {
return -EIO;
}

if (flags & GPIO_INT) {
stm32_exti_set_callback(pin, gpio_stm32_isr, dev);

stm32_gpio_enable_int(cfg->port, pin);

if (flags & GPIO_INT_EDGE) {
int edge = 0;

if (flags & GPIO_INT_DOUBLE_EDGE) {
edge = STM32_EXTI_TRIG_RISING |
STM32_EXTI_TRIG_FALLING;
} else if (flags & GPIO_INT_ACTIVE_HIGH) {
edge = STM32_EXTI_TRIG_RISING;
} else {
edge = STM32_EXTI_TRIG_FALLING;
}

stm32_exti_trigger(pin, edge);
}

stm32_exti_enable(pin);
}

return 0;
}

/**
 * @brief Set the pin or port output
 */
static int gpio_stm32_write(struct device *dev, int access_op,   /*  struct gpio_driver_apigpio write 함수 */
   uint32_t pin, uint32_t value)
{
const struct gpio_stm32_config *cfg = dev->config->config_info;

if (access_op != GPIO_ACCESS_BY_PIN) {
return -ENOTSUP;
}

return stm32_gpio_set(cfg->base, pin, value);
}

/**
 * @brief Read the pin or port status
 */
static int gpio_stm32_read(struct device *dev, int access_op,   /*  struct gpio_driver_api: gpio read 함수 */
  uint32_t pin, uint32_t *value)
{
const struct gpio_stm32_config *cfg = dev->config->config_info;

if (access_op != GPIO_ACCESS_BY_PIN) {
return -ENOTSUP;
}

*value = stm32_gpio_get(cfg->base, pin);

return 0;
}

static int gpio_stm32_manage_callback(struct device *dev,   /*  struct gpio_driver_api: callback manage  함수 */
     struct gpio_callback *callback,
     bool set)
{
struct gpio_stm32_data *data = dev->driver_data;

_gpio_manage_callback(&data->cb, callback, set);

return 0;
}

static int gpio_stm32_enable_callback(struct device *dev,   /*  struct gpio_driver_api: callback enable 함수 */
     int access_op, uint32_t pin)
{
struct gpio_stm32_data *data = dev->driver_data;

if (access_op != GPIO_ACCESS_BY_PIN) {
return -ENOTSUP;
}

data->cb_pins |= BIT(pin);

return 0;
}

static int gpio_stm32_disable_callback(struct device *dev,   /*  struct gpio_driver_api: callback disable 함수 */
      int access_op, uint32_t pin)
{
struct gpio_stm32_data *data = dev->driver_data;

if (access_op != GPIO_ACCESS_BY_PIN) {
return -ENOTSUP;
}

data->cb_pins &= ~BIT(pin);

return 0;
}

static const struct gpio_driver_api gpio_stm32_driver = {   /* struct device의 .driver_api 필드를 구성하는 내용을 정의함 */
.config = gpio_stm32_config,
.write = gpio_stm32_write,
.read = gpio_stm32_read,
.manage_callback = gpio_stm32_manage_callback,
.enable_callback = gpio_stm32_enable_callback,
.disable_callback = gpio_stm32_disable_callback,

};

/**
 * @brief Initialize GPIO port
 *
 * Perform basic initialization of a GPIO port. The code will
 * enable the clock for corresponding peripheral.
 *
 * @param dev GPIO device struct
 *
 * @return 0
 */
static int gpio_stm32_init(struct device *device)  /* struct device_config의 .init 필드를 채움 */
{
const struct gpio_stm32_config *cfg = device->config->config_info;

/* enable clock for subsystem */
struct device *clk =
device_get_binding(STM32_CLOCK_CONTROL_NAME);


#if defined(CONFIG_SOC_SERIES_STM32F4X) ||  \
defined(CONFIG_CLOCK_CONTROL_STM32_CUBE)
clock_control_on(clk, (clock_control_subsys_t *) &cfg->pclken);
#else
clock_control_on(clk, cfg->clock_subsys);
#endif
return 0;
}


#if defined(CONFIG_CLOCK_CONTROL_STM32_CUBE)

#define GPIO_DEVICE_INIT(__name, __suffix, __base_addr, __port, __cenr, __bus) \
static const struct gpio_stm32_config gpio_stm32_cfg_## __suffix = { \
.base = (uint32_t *)__base_addr, \
.port = __port, \
.pclken = { .bus = __bus, .enr = __cenr } \
}; \
static struct gpio_stm32_data gpio_stm32_data_## __suffix; \
DEVICE_AND_API_INIT(gpio_stm32_## __suffix, \
   __name, \
   gpio_stm32_init, \
   &gpio_stm32_data_## __suffix, \
   &gpio_stm32_cfg_## __suffix, \
   POST_KERNEL, \
   CONFIG_KERNEL_INIT_PRIORITY_DEVICE, \
   &gpio_stm32_driver);

#else

#ifndef CONFIG_SOC_SERIES_STM32F4X

/* TODO: Change F1 to work similarly to F4 */
#define GPIO_DEVICE_INIT(__name, __suffix, __base_addr, __port, __clock) \
static const struct gpio_stm32_config gpio_stm32_cfg_## __suffix = { \
.base = (uint32_t *)__base_addr, \
.port = __port, \
.clock_subsys = UINT_TO_POINTER(__clock) \
}; \
static struct gpio_stm32_data gpio_stm32_data_## __suffix; \
DEVICE_AND_API_INIT(gpio_stm32_## __suffix, \
   __name, \
   gpio_stm32_init, \
   &gpio_stm32_data_## __suffix, \
   &gpio_stm32_cfg_## __suffix, \
   POST_KERNEL, \
   CONFIG_KERNEL_INIT_PRIORITY_DEVICE, \
   &gpio_stm32_driver);
      /* 이 부분과 관련해서는 3.1절의 (A) 표시 부분을 함께 살펴 보아야 함 - struct device_config 변수를 정의하고, 이어서 struct device 변수를 선언하는 역할을 함 */

#else /* CONFIG_SOC_SERIES_STM32F4X */

#define GPIO_DEVICE_INIT(__name, __suffix, __base_addr, __port, __cenr) \
static const struct gpio_stm32_config gpio_stm32_cfg_## __suffix = { \
.base = (uint32_t *)__base_addr, \
.port = __port, \
.pclken = { .bus = STM32F4X_CLOCK_BUS_AHB1, .enr = __cenr }, \
}; \
static struct gpio_stm32_data gpio_stm32_data_## __suffix; \
DEVICE_AND_API_INIT(gpio_stm32_## __suffix, \
   __name, \
   gpio_stm32_init, \
   &gpio_stm32_data_## __suffix, \
   &gpio_stm32_cfg_## __suffix, \
   POST_KERNEL, \
   CONFIG_KERNEL_INIT_PRIORITY_DEVICE, \
   &gpio_stm32_driver);
#endif
#endif /* CONFIG_CLOCK_CONTROL_STM32_CUBE */

#ifdef CONFIG_GPIO_STM32_PORTA
GPIO_DEVICE_INIT("GPIOA", a, GPIOA_BASE, STM32_PORTA,         /* GPIOA bank - 이 부분은 struct device_config의 config_info field의 내용을 구성하게 됨 */
#ifdef CONFIG_CLOCK_CONTROL_STM32_CUBE
STM32_PERIPH_GPIOA, STM32_CLOCK_BUS_GPIO
#else
#ifdef CONFIG_SOC_SERIES_STM32F1X
STM32F10X_CLOCK_SUBSYS_IOPA
| STM32F10X_CLOCK_SUBSYS_AFIO
#elif CONFIG_SOC_SERIES_STM32F4X
STM32F4X_CLOCK_ENABLE_GPIOA
#endif
#endif /* CONFIG_CLOCK_CONTROL_STM32_CUBE */
);
#endif /* CONFIG_GPIO_STM32_PORTA */

#ifdef CONFIG_GPIO_STM32_PORTB
GPIO_DEVICE_INIT("GPIOB", b, GPIOB_BASE, STM32_PORTB,  /* GPIOB bank - 이 부분은 struct device_config의 config_info field의 내용을 구성하게 됨 */
#ifdef CONFIG_CLOCK_CONTROL_STM32_CUBE
STM32_PERIPH_GPIOB, STM32_CLOCK_BUS_GPIO
#else
#ifdef CONFIG_SOC_SERIES_STM32F1X
STM32F10X_CLOCK_SUBSYS_IOPB
| STM32F10X_CLOCK_SUBSYS_AFIO
#elif CONFIG_SOC_SERIES_STM32F4X
STM32F4X_CLOCK_ENABLE_GPIOB
#endif
#endif /* CONFIG_CLOCK_CONTROL_STM32_CUBE */
);
#endif /* CONFIG_GPIO_STM32_PORTB */

#ifdef CONFIG_GPIO_STM32_PORTC
GPIO_DEVICE_INIT("GPIOC", c, GPIOC_BASE, STM32_PORTC,  /* GPIOC bank - 이 부분은 struct device_config의 config_info field의 내용을 구성하게 됨 */
#ifdef CONFIG_CLOCK_CONTROL_STM32_CUBE
STM32_PERIPH_GPIOC, STM32_CLOCK_BUS_GPIO
#else
#ifdef CONFIG_SOC_SERIES_STM32F1X
STM32F10X_CLOCK_SUBSYS_IOPC
| STM32F10X_CLOCK_SUBSYS_AFIO
#elif CONFIG_SOC_SERIES_STM32F4X
STM32F4X_CLOCK_ENABLE_GPIOC
#endif
#endif /* CONFIG_CLOCK_CONTROL_STM32_CUBE */
);
#endif /* CONFIG_GPIO_STM32_PORTC */

#ifdef CONFIG_GPIO_STM32_PORTD
GPIO_DEVICE_INIT("GPIOD", d, GPIOD_BASE, STM32_PORTD,  /* GPIOD bank - 이 부분은 struct device_config의 config_info field의 내용을 구성하게 됨 */
#ifdef CONFIG_CLOCK_CONTROL_STM32_CUBE
STM32_PERIPH_GPIOD, STM32_CLOCK_BUS_GPIO
#else
#ifdef CONFIG_SOC_SERIES_STM32F1X
STM32F10X_CLOCK_SUBSYS_IOPD
| STM32F10X_CLOCK_SUBSYS_AFIO
#elif CONFIG_SOC_SERIES_STM32F4X
STM32F4X_CLOCK_ENABLE_GPIOD
#endif
#endif /* CONFIG_CLOCK_CONTROL_STM32_CUBE */
);
#endif /* CONFIG_GPIO_STM32_PORTD */

#ifdef CONFIG_GPIO_STM32_PORTE
GPIO_DEVICE_INIT("GPIOE", e, GPIOE_BASE, STM32_PORTE,  /* GPIOE bank - 이 부분은 struct device_config의 config_info field의 내용을 구성하게 됨 */
#ifdef CONFIG_CLOCK_CONTROL_STM32_CUBE
STM32_PERIPH_GPIOE, STM32_CLOCK_BUS_GPIO
#else
#ifdef CONFIG_SOC_SERIES_STM32F1X
STM32F10X_CLOCK_SUBSYS_IOPE
| STM32F10X_CLOCK_SUBSYS_AFIO
#elif CONFIG_SOC_SERIES_STM32F4X
STM32F4X_CLOCK_ENABLE_GPIOE
#endif
#endif /* CONFIG_CLOCK_CONTROL_STM32_CUBE */
);
#endif /* CONFIG_GPIO_STM32_PORTE */

#ifdef CONFIG_GPIO_STM32_PORTF
GPIO_DEVICE_INIT("GPIOF", f, GPIOF_BASE, STM32_PORTF,  /* GPIOF bank - 이 부분은 struct device_config의 config_info field의 내용을 구성하게 됨 */
#ifdef CONFIG_CLOCK_CONTROL_STM32_CUBE
STM32_PERIPH_GPIOF, STM32_CLOCK_BUS_GPIO
#else
#ifdef CONFIG_SOC_SERIES_STM32F1X
STM32F10X_CLOCK_SUBSYS_IOPF
| STM32F10X_CLOCK_SUBSYS_AFIO
#elif CONFIG_SOC_SERIES_STM32F4X
STM32F4X_CLOCK_ENABLE_GPIOF
#endif
#endif /* CONFIG_CLOCK_CONTROL_STM32_CUBE */
);
#endif /* CONFIG_GPIO_STM32_PORTF */

#ifdef CONFIG_GPIO_STM32_PORTG
GPIO_DEVICE_INIT("GPIOG", g, GPIOG_BASE, STM32_PORTG,  /* GPIOG bank - 이 부분은 struct device_config의 config_info field의 내용을 구성하게 됨 */
#ifdef CONFIG_CLOCK_CONTROL_STM32_CUBE
STM32_PERIPH_GPIOG, STM32_CLOCK_BUS_GPIO
#else
#ifdef CONFIG_SOC_SERIES_STM32F1X
STM32F10X_CLOCK_SUBSYS_IOPG
| STM32F10X_CLOCK_SUBSYS_AFIO
#elif CONFIG_SOC_SERIES_STM32F4X
STM32F4X_CLOCK_ENABLE_GPIOG
#endif
#endif /* CONFIG_CLOCK_CONTROL_STM32_CUBE */
);
#endif /* CONFIG_GPIO_STM32_PORTG */

#ifdef CONFIG_GPIO_STM32_PORTH
GPIO_DEVICE_INIT("GPIOH", h, GPIOH_BASE, STM32_PORTH,  /* GPIOH bank - 이 부분은 struct device_config의 config_info field의 내용을 구성하게 됨 */
#ifdef CONFIG_CLOCK_CONTROL_STM32_CUBE
STM32_PERIPH_GPIOH, STM32_CLOCK_BUS_GPIO
#else
#ifdef CONFIG_SOC_SERIES_STM32F4X
STM32F4X_CLOCK_ENABLE_GPIOH
#endif
#endif /* CONFIG_CLOCK_CONTROL_STM32_CUBE */
);
#endif /* CONFIG_GPIO_STM32_PORTH */
-----------------------------------------------------------------------------------------------------------------------------------------------------------------

(*) Device & driver model은 linux의 그것과 비교하여 커다란 차이(전혀 다른 방식임)가 있으나, coding 관점에서 보면 linux의 style과 유사하다고 볼 수 있다.


5. Zephyr kernel primer(version 2) : code examples
아래 page에는 zephyr kernel programming 기법(예를 들어, thread, timer, message queue, work queue 등등)과 관련하여 다양한 방법이 상세히 소개되어 있다.



이 내용 중, 일부를 실제로 동작 가능하도록 간단히 작업해 보았는데, 이와 관련한 코드는 아래 github에서 확인할 수 있다.


6. Zephyr kernel 부팅 flow 분석 
마지막으로 Zephyr OS의 booting flow(ARM cortex-m 기준, Pseudo code style로 정리함)를 분석해 봄으로써, 이번 posting을 마무리도록 하겠다.

-----------------------------------------------------------------------------------------------------------------------------------------------------------------
<kernel/init.c>
[system reset]
 |
V
SECTION_SUBSEC_FUNC(TEXT,_reset_section,__reset) ... arch/arm/core/cortex_m/reset.S
 |
V
_PrepC( ) ...arch/arm/core/cortex_m/prep_c.c
 |
V
_Cstart( )
 |
V
{
prepare_multithreading(dummy_thread)
=> _main, idle thread 초기화 및 architecture-specific 초기화 작업 진행 
{
_IntLibInit( )
              {
                        for (; irq < CONFIG_NUM_IRQS; irq++)
                                  NVIC_SetPriority((IRQn_Type)irq, _IRQ_PRIO_OFFSET)
                                     => arm cortex-m interrupt 초기화 작업 진행 
              }

_new_thread(_main_stack, MAIN_STACK_SIZE,
_main, NULL, NULL, NULL, CONFIG_MAIN_THREAD_PRIORITY, K_ESSENTIAL)
                     => _main thread 생성 
|
V
_main( )
{
_sys_device_do_config_level(_SYS_INIT_LEVEL_POST_KERNEL)
_sys_device_do_config_level(_SYS_INIT_LEVEL_SECONDARY)
_sys_device_do_config_level(_SYS_INIT_LEVEL_NANOKERNEL)
_sys_device_do_config_level(_SYS_INIT_LEVEL_MICROKERNEL)
_sys_device_do_config_level(_SYS_INIT_LEVEL_APPLICATION)
=> device 초기화 작업 진행

_init_static_threads( )

main( )
=> 여기서 application main 함수를 호출함.
}

_new_thread(_idle_stack, IDLE_STACK_SIZE,
idle, NULL, NULL, NULL, K_LOWEST_THREAD_PRIO, K_ESSENTIAL);
                                      => idle thread 생성

nanoArchInit( )
=> device specific 초기화 작업 진행
{  
/* arm code 기준 - cpu 초기화 작업 진행 */
_InterruptStackSetup( );
_ExcSetup( );
_FaultInit( );
_CpuIdleInit( );
}
}

_sys_device_do_config_level(_SYS_INIT_LEVEL_PRIMARY)
_sys_device_do_config_level(_SYS_INIT_LEVEL_PRE_KERNEL_1)
_sys_device_do_config_level(_SYS_INIT_LEVEL_PRE_KERNEL_2)
=> device 초기화 작업 진행(나머지 부분) 

PRINT_BOOT_BANNER( )
=> boot banner 출력

switch_to_main_thread( )
=> main thread(_main)로 전환 !
     {
            _arch_switch_to_main_thread(_main_stack, MAIN_STACK_SIZE, _main)
     }
}
-----------------------------------------------------------------------------------------------------------------------------------------------------------------
<arch/arm/include/kernel_arch_func.h>   /* main thread로 switching하는  부분 */
static ALWAYS_INLINE void
_arch_switch_to_main_thread(char *main_stack, size_t main_stack_size, _thread_entry_t _main)
{
    /* get high address of the stack, i.e. its start (stack grows down) */
    char *start_of_main_stack;

    start_of_main_stack = main_stack + main_stack_size;
    start_of_main_stack = (void *)STACK_ROUND_DOWN(start_of_main_stack);

    _current = (void *)main_stack;

    /* the ready queue cache already contains the main thread */

    __asm__ __volatile__(

        /* move to main() thread stack */
        "msr PSP, %0 \t\n"

        /* unlock interrupts */
#ifdef CONFIG_ARMV6_M
        "cpsie i \t\n"
#elif defined(CONFIG_ARMV7_M)
        "movs %%r1, #0 \n\t"
        "msr BASEPRI, %%r1 \n\t"
#else
#error Unknown ARM architecture
#endif /* CONFIG_ARMV6_M */

        /* branch to _thread_entry(_main, 0, 0, 0) */
        "mov %%r0, %1 \n\t"
        "bx %2 \t\n"

        /* never gets here */

        :
        : "r"(start_of_main_stack),
          "r"(_main), "r"(_thread_entry)

        : "r0", "r1", "sp"
    );

    CODE_UNREACHABLE;
}
-----------------------------------------------------------------------------------------------------------------------------------------------------------------

RIOT OS의 kernel booting flow(아래 link - 5절 참조)와 비교해 볼 때 (개인적인 견해이지만)Zephyr가 좀 더 복잡한 느낌이다.

이상으로 IoT OS 계의 새로운 바람, Zephyr Project에 관하여 간단히 살펴 보았다. 앞으로 Zephyr OS가 Linux와 같은 열풍을 몰고 올 수 있을지 무척 기대가 된다.


References
1. https://www.zephyrproject.org/doc/getting_started/getting_started.html
2. https://www.linkedin.com/pulse/writing-simple-device-driver-zephyr-bill-fletcher
3. Zephyr Device Driver and Device Model, Dr. Yann-Hang Lee

Slowboot


댓글 2개: