---
# System prepended metadata

title: Zephyr RTOS

---

# Zephyr RTOS
本篇是針對Zephyr RTOS的研究心得整理。並針對在Zephyr RTOS 移植一款 SoC 作出指引。

Zephyr的建置系統非常複雜，而且有點過度複雜。它的建置系統在背後做了很多"魔法"，所以會讓想要trace其流程的人要花很大的力氣才能弄懂。

當你不知道它的建置系統實際做了哪些動作時，可以透過在cmake指令後面加上`--trace-expand`。

譬如使用`cmake -Bbuild -DBOARD=hifive1 samples/philosophers 2>&1 | tee ../zephy.cmake.out.txt`指令。其中
- `-Bbuild`表示建置目錄位於當前目錄下的build目錄。
- `-DBOARD=hifive1`表示針對hifive1電路板進行建置。
- `samples/philosophers`表示要建置的程式碼來源目錄是`samples/philosophers`。
- `2>&1 | tee ../zephyr.cmake.out.txt`是將stdout和stderr輸出同時導向到螢幕和`..zephyr.cmake.out.txt`檔案。

## 建置專案
我計畫先能夠成功建立一個out-of-tree應用程式，再來討論SoC移植的問題。底下將以Andes提供的版本來做說明。

首先將Andes提供的 `zephyr-V5.tgz` 解壓縮到任意目錄。譬如解壓到 `z:\zephyrproject\zephyr-V5`。該目錄底下的結構如下：
```
z:\zephyrproject\
├── zephyr-V5
│   ├── arch
│   ├── boards
│   ├── cmake
│   ├── ...
```

首先從"開始-->程式集"開啟 nds32le-elf-newlib-v5。輸入`cd /cygdrive/z/zephyrproject/`切換到`z:\zephyrproject`目錄。在該目錄下建立一個build.sh檔案，其中內容如下：
```sh
source zephyr-V5/zephyr-env.sh
export ZEPHYR_TOOLCHAIN_VARIANT='cross-compile'
export CROSS_COMPILE="/cygdrive/c/Andestech/AndeSight_STD_v500/toolchains/nds32le-elf-newlib-v5/bin/riscv32-elf-"

#cmake --trace-expand -Bbuild -DBOARD=ae350 app 2>&1 | tee ./zephy.cmake.out.txt
cmake -Bbuild -DBOARD=ae350 app 2>&1 | tee ./zephyr.cmake.out.txt
```

接著在同一目錄下輸入以下指令：
```sh
mkdir app && cd app
touch CMakeLists.txt
touch prj.conf
mkdir src && cd src
touch main.c
```
此時你的目錄結構如下：
```shell=
z:\zephyrproject\
├── app
│   ├── src
│   │   └── main.c
│   ├── CMakeLists.txt 
│   └── prj.conf
└── zephyr-V5
```
注意除了 `prj.conf` 和 `CMakeLists.txt` 這兩個檔案的名稱絕對不能變。`prj.conf`是用來放置Kconfig預設設定值。

接著在`CMakeLists.txt`填入：
```cmake
cmake_minimum_required(VERSION 3.13.1)
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
project(my_zephyr_app)

target_sources(app PRIVATE src/main.c)
```

然後你就可以輸入以下指令切換到 `z:\zephyrproject\`並開始建置專案：
```shell
cd /cygdrive/z/zephyrproject/
./build.sh
cd build
# make menuconfig
make
```
上面的`make menuconfig`雖然被注釋掉，不過你可以在build目錄下輸入該指令，將會跳出一個設定視窗。不過我為了簡化說明先跳過該步驟。

若成功建置完成，你應該會在 `z:\zephyrproject\build\zephyr\`目錄下看到`zephyr.elf`檔案。此時你就可以利用gdb去把該檔案載入到板子跑看看。


## 自訂電路板
我打算接著從自訂電路板講起。因為直接移植SoC耗的功很大，且通常不會慘到連你會用到的CPU都沒人移植過。所以先找一款最接近你的SoC的Chip來用，把所有週邊先關閉讓程式可以先建置成功再說。

要自訂電路板需要做的事就比較多了。首先在前面建立好的app目錄下建立boards目錄，然後根據`boards/<arch>/<borard>`的結構定義目錄。譬如我的board的名稱是`it9830_evb`，cpu architecture是riscv，所以放置我的board程式檔的路徑是boards/riscv/it9830_evb。讓我們開始動手把所需的目錄和檔案先建立起來：
```shell=
cd /cygdrive/z/zephyrproject/app
mkdir -p boards/riscv/it9830_evb
cd boards/riscv/it9830_evb
touch Kconfig.board
touch Kconfig.defconfig
touch it9830_evb_defconfig
touch it9830_evb.dts
touch board.cmake
touch CMakeLists.txt
```

細節可以看官方文件[Create your board directory](https://docs.zephyrproject.org/latest/guides/porting/board_porting.html#create-your-board-directory)。裡面有四個檔案是必要的，包含：
- `it9830_evb.dts`: 
  
  描述設備，以device tree格式撰寫的。此檔案至少得包含以下內容，否則在CMake設定階段就會出錯。
  ```c=
  /dts-v1/;
  
  // 這裡請包含你的SoC的DTS include檔案
  // 我暫時使用Andes的ae350 SoC DTS，之後會替換掉
  #include <andes_v5_ae350.dtsi>
  
  / {
	model = "ITE IT9830 EVB";
	compatible = "ite,it9830_evb";
  };
  ```
  
  
  
- `Kconfig.board`、`Kconfig.defconfig`、 `it9830_evb_defconfig`:
  
  以 Kconfig 語法撰寫的軟體設定。其中還可細分：
  - Kconfig.board
    
    設定選項。裡面至少要包含一個`BOARD_IT9830_EVB`選項的定義，它看起來像這樣：
    ```python=
    config BOARD_IT9830_EVB
        bool "IT9830 evaluation board"
        depends on SOC_RISCV_ANDES_AE350
    ```
    其中`depends on SOC_RISCV_ANDES_AE350`是我暫時選擇使用Andes AE350做為SoC，等之後移植好SoC後就會把這裡改成我們自己的SoC。
    
  - Kconfig.defconfig
    
    針對 Kconfig 選項的電路板特定預設值。整個檔案的內容應該被`if BOARD_IT9830_EVB` / `endif` 這兩行語句所包圍，如下所示：
    ```python=
    if BOARD_IT9830_EVB
    
    # 官方文件寫說此選項必寫，不過我還未查哪邊會用到
    # 主要還是要刻意讓 CONFIG_BOARD 變數值被預設為電路板名稱
    config BOARD
        default "it9830_evb"
    
    endif
    ```
    
  - it9830_evb_defconfig
    
    it9830_evb電路板特有預設設定值。每當為您的電路板編譯應用程式時，此檔案的內容都會按原樣合併到最終建置目錄的 `.config` 中的 Kconfig 片段。
    
    從描述看起來跟`Kconfig.defconfig`很像，但其實不太一樣。詳細處理細節可以trace `cmake\kconfig.cmake`檔案，透過搜尋關鍵字`kconfig.py`。此檔案是採用`.config`的格式，而非Kconfig語法格式。其實所有`*_defconfig`都是採用`.config`的格式，而其它kconfig相關檔案都是採用Kconfig語法格式，包含`*.defconfig`檔案。這個檔案是決定board要採用哪個SoC的關鍵。為了讓board與SoC綁定，我們將在此檔案內建立類似以下兩個語句：
    
    ```
    CONFIG_SOC_SERIES_XXX = y
    CONFIG_SOC_FAMILY_XXX = y
    ```
    
    這裡要填的內容可以透過閱讀你想使用的SoC底下的相關Kconfig檔案來決定，但我打算走捷徑。我打算利用 menuconfig 或 guiconfig 幫我產生預設選項，再把需要的內容複製到這個檔案就好。因此讓我們先繼續建置步驟。
    
    

建置命令也需要修改一下，加入對`BOARD_ROOT`的定義，這是因為我們的board的程式碼是放在out-of-tree，如果是放在in-tree目錄就不用定義`BOARD_ROOT`。`BOARD_ROOT`可以用相對路徑或絕對路徑定義，如果是用相對路徑，注意該路徑是相對於應用程式根目錄。Zephyr的build script會去尋找`<BOARD_ROOT>/boards/*/*/<BOARD>_defconfig`檔案。因此假設你的board目錄位於`app/boards/riscv/it9830_evb/it9830_evb_defconfig`，你的`BOARD_ROOT`只要定義為`.`就好了。

因此我將我的 build.sh 內容改為如下。
```shell
source zephyr-V5/zephyr-env.sh
export ZEPHYR_TOOLCHAIN_VARIANT='cross-compile'
export CROSS_COMPILE="/cygdrive/c/Andestech/AndeSight_STD_v500/toolchains/nds32le-elf-newlib-v5/bin/riscv32-elf-"

#cmake --trace-expand -Bbuild -DBOARD=it9830_evb -DBOARD_ROOT="." app 2>&1 | tee ./zephyr.cmake.out.txt
cmake -Bbuild -DBOARD=it9830_evb -DBOARD_ROOT="." app 2>&1 | tee ./zephyr.cmake.out.txt
```

修改後先運行一下`./build.sh`。這個script只是讓我們能透過cmake設定階段。完整命令如下：
```shell
cd /cygdrive/z/zephyrproject/
./build.sh
```
接著切換到build目錄，然後執行`make menuconfig`。：
```shell
cd /cygdrive/z/zephyrproject/build
make menuconfig
```
關鍵是SoC/CPU/Configuration Selection下的東西。這裡至少選擇

- SoC/CPU/Configuration Selection
    - Andes V5 SoC Series Implementation
- Hardware Configuration
    - Andes V5 SoC Selection
        - Andes AE350 SoC Implementation
    - CPU Architecture of SoC
        - RISCV32 CPU Architecture
    - Enable cache
        - Disable Andes V5 L2 cache
    - Enable Andes V5 Hardware DSP
    - Enable Andes V5 Physical Memory Attribute (PMA)

儲存後退出。然後找到 `z:\zephyrproject\build\zephyr\.config`，把`CONFG_SOC`開頭的選項貼到it9830_evb_defconfig。我一開始先貼了以下內容：
```cmake
CONFIG_SOC="ae350"
CONFIG_SOC_SERIES="andes_v5"
CONFIG_SOC_SERIES_RISCV_ANDES_V5=y
CONFIG_SOC_FAMILY_RISCV_PRIVILEGE=y
CONFIG_SOC_FAMILY="riscv-privilege"
CONFIG_SOC_RISCV_ANDES_AE350=y
CONFIG_RISCV_RV32=y
CONFIG_CACHE_ENABLE=y
CONFIG_SOC_ANDES_V5_HWDSP=y
CONFIG_SOC_ANDES_V5_PFT=y
CONFIG_SOC_ANDES_V5_PMA=y
```
再嘗試執行build.sh，會出現一些error，主要都是說某些Kconfig選項是不帶有提示(prompt)，不應給使用者設定。像是"SOC"、"SOC_SERIES"、"SOC_FAMILY_RISCV_PRIVILEGE"、"SOC_FAMILY"。把那些選項砍一砍後只剩：
```cmake
CONFIG_SOC_SERIES_RISCV_ANDES_V5=y
CONFIG_SOC_RISCV_ANDES_AE350=y
CONFIG_RISCV_RV32=y
CONFIG_CACHE_ENABLE=y
CONFIG_SOC_ANDES_V5_HWDSP=y
CONFIG_SOC_ANDES_V5_PFT=y
CONFIG_SOC_ANDES_V5_PMA=y
```
這時候嘗試再次執行`build.sh` -> `make`。會出現以下error。

```
/cygdrive/c/Andestech/AndeSight_STD_v500/toolchains/nds32le-elf-newlib-v5/bin/../lib/gcc/riscv32-elf/10.2.1/../../../../riscv32-elf/bin/ld: zephyr_prebuilt.elf section `vector' will not fit in region `RAM'
/cygdrive/c/Andestech/AndeSight_STD_v500/toolchains/nds32le-elf-newlib-v5/bin/../lib/gcc/riscv32-elf/10.2.1/../../../../riscv32-elf/bin/ld: region `RAM' overflowed by 13040 bytes
/cygdrive/c/Andestech/AndeSight_STD_v500/toolchains/nds32le-elf-newlib-v5/bin/../lib/gcc/riscv32-elf/10.2.1/../../../../riscv32-elf/bin/ld: kernel/libkernel.a(sched.c.obj): in function `z_reset_time_slice':
/cygdrive/z/zephyrproject/zephyr-V5/kernel/sched.c:375: undefined reference to `z_clock_elapsed'
/cygdrive/c/Andestech/AndeSight_STD_v500/toolchains/nds32le-elf-newlib-v5/bin/../lib/gcc/riscv32-elf/10.2.1/../../../../riscv32-elf/bin/ld: kernel/libkernel.a(timeout.c.obj): in function `elapsed':
/cygdrive/z/zephyrproject/zephyr-V5/kernel/timeout.c:69: undefined reference to `z_clock_elapsed'
Memory region         Used Size  Region Size  %age Used
             RAM:       13040 B         0 GB       inf%
        IDT_LIST:          25 B         2 KB      1.22%
collect2: error: ld returned 1 exit status
make[2]: *** [zephyr/CMakeFiles/zephyr_prebuilt.dir/build.make:95: zephyr/zephyr_prebuilt.elf] Error 1
make[1]: *** [CMakeFiles/Makefile2:763: zephyr/CMakeFiles/zephyr_prebuilt.dir/all] Error 2
make: *** [Makefile:84: all] Error 2


```

看起來一個和 RAM 區間太小有關，還有一個錯誤是找不到`z_clock_elapsed` 函式。

首先檢查最終的 linker script，位於`build/zephyr/linker.cmd`：
```
 OUTPUT_ARCH("riscv")
 OUTPUT_FORMAT("elf32-littleriscv")
_pma_region_min_align = 4096;
MEMORY
{
    RAM (rwx) : ORIGIN = 0x0, LENGTH = ((0) << 10)
    IDT_LIST (wx) : ORIGIN = 0xFFFFF7FF, LENGTH = 2K
}
ENTRY("entry")
...
```
可以看到RAM的部分LENGTH竟然是0。接著我們要找出影響最終RAM的LENGTH設定的位置。在`build/zephyr/linker.cmd.dep`可看到最終linker script主要是從哪些檔案的內容收集來的。`
```
zephyr/linker.cmd: \
 /cygdrive/z/zephyrproject/zephyr-V5/soc/riscv/riscv-privilege/andes_v5/linker.ld \
 /cygdrive/z/zephyrproject/build/zephyr/include/generated/autoconf.h \
 /cygdrive/z/zephyrproject/zephyr-V5/soc/riscv/riscv-privilege/andes_v5/ae350/linker.ld \
 /cygdrive/z/zephyrproject/zephyr-V5/soc/riscv/riscv-privilege/andes_v5/ae350/soc.h \
 /cygdrive/z/zephyrproject/zephyr-V5/soc/riscv/riscv-privilege/common/./soc_common.h \
 ...
```
接著我們可以在 `zephyr-V5/soc/riscv/riscv-privilege/andes_v5/ae350/linker.ld`找到
```
RAM (rwx) : ORIGIN = CONFIG_SRAM_BASE_ADDRESS, LENGTH = KB(CONFIG_SRAM_SIZE)
```
得知RAM的LENGTH是由CONFIG_SRAM_SIZE的值決定。而與SRAM_SIZE有關的設定定義在`zephyr-V5/arch/Kconfig`。
```
# Workaround for not being able to have commas in macro arguments
DT_CHOSEN_Z_SRAM := zephyr,sram

config SRAM_SIZE
	int "SRAM Size in kB"
	default $(dt_chosen_reg_size_int,$(DT_CHOSEN_Z_SRAM),0,K)
	help
	  The SRAM size in kB. The default value comes from /chosen/zephyr,sram in
	  devicetree. The user should generally avoid changing it via menuconfig or
	  in configuration files.

```
可以看出SRAM_SIZE的預設值採用 Kconfig Preprocessor Functions 寫成。細節可以看 https://docs.zephyrproject.org/latest/guides/build/kconfig/preprocessor-functions.html。基本上上面那段是從device tree內取出/chosen部分定義的zephyr,sram的size值。這意味著如果我們要修改`CONFIG_SRAM_SIZE`的值，有兩種方法，一種是需要修改`it9830_evb.dts`，另一種方法就是修改`it9830_evb_defconfig`直接加入`CONFIG_SRAM_SIZE=16384`(即16384KB = 16MB，注意CONFIG_SRAM_SIZE的單位是KB)。

我打算採用直接在`it9830_evb_defconfig`加入設定值的方式。等到後面移植SoC的部份再來補齊device tree的內容。

剩下找不到 `z_clock_elapsed()` 函式的問題。這其實只要在`it9830_evb_defconfig`加入`CONFIG_RISCV_MACHINE_TIMER=y`就可以解決。細節請自行trace code。

所以我們第一版`it9830_evb_defconfig`的完整內容如下
```
CONFIG_SOC_SERIES_RISCV_ANDES_V5=y
CONFIG_SOC_RISCV_ANDES_AE350=y
CONFIG_RISCV_RV32=y
CONFIG_CACHE_ENABLE=y
CONFIG_SOC_ANDES_V5_HWDSP=y
CONFIG_SOC_ANDES_V5_PFT=y
CONFIG_SOC_ANDES_V5_PMA=y
CONFIG_SRAM_SIZE=16777216
CONFIG_RISCV_MACHINE_TIMER=y
```


## 自訂 SoC
接下來是重點部分啦，也是官網文件幾乎沒提的部分。

SOC的目錄命名規則為`soc/<architecture>/<soc_family>/<soc_series>/`，只是riscv底下的目錄分類的比ARM還混亂。假設我有一款晶片，其

- architecture: riscv
- soc_family: riscv-ite
- soc_series: it983x

則其目錄路徑應為`soc/riscv/riscv-ite/it983x/`。因此，我需要在`z:\zephyrproject\app`下建立`soc/riscv/riscv-ite/it983x/`，然後建立一些需要的檔案。所以首先執行：

```
 cd /cygdrive/z/zephyrproject/app/
 mkdir -p soc/riscv/riscv-ite/it983x/
 cd soc/riscv/riscv-ite/
 touch CMakeLists.txt
 touch Kconfig
 touch Kconfig.defconfig
 touch Kconfig.soc
 cd it983x
 touch CMakeLists.txt
 touch Kconfig.defconfig.series
 touch Kconfig.series
 touch Kconfig.soc
 touch linker.ld
 touch soc.c
 touch soc.h
```
### Kconfig

接著我們從處理那堆 Kconfig 檔案開始。首先我們從build目錄的`build\CMakeFiles\menuconfig.dir\build.make`檔案看起。可以看到如下內容：

```makefile
CMakeFiles/menuconfig:
	cd /cygdrive/z/zephyrproject/build/zephyr/kconfig && ... /usr/bin/python3.8.exe /cygdrive/z/zephyrproject/zephyr-V5/scripts/kconfig/menuconfig.py /cygdrive/z/zephyrproject/zephyr-V5/Kconfig

menuconfig: CMakeFiles/menuconfig
menuconfig: CMakeFiles/menuconfig.dir/build.make
```

可以看出當你執行`make menuconfig`時，`CMakeFiles/menuconfig`內的命令會先被執行，而其中底下的命令

```
zephyr-V5/scripts/kconfig/menuconfig.py /cygdrive/z/zephyrproject/zephyr-V5/Kconfig
```

會導致 `menuconfig.py`  被執行並且餵給該python腳本的root kconfig檔案路徑為`/cygdrive/z/zephyrproject/zephyr-V5/Kconfig`。因此要知道最終menuconfig產生的選單長相為何，我們得先從`/cygdrive/z/zephyrproject/zephyr-V5/Kconfig`看起。

`/cygdrive/z/zephyrproject/zephyr-V5/Kconfig`內容如下：

```python
mainmenu "Zephyr Kernel Configuration"

source "Kconfig.zephyr"
```

而`Kconfig.zephyr`內容如下：

```
menu "Modules"
  source "$(KCONFIG_BINARY_DIR)/Kconfig.modules"
  source "modules/Kconfig"
endmenu

source "boards/shields/*/Kconfig.defconfig"
source "$(BOARD_DIR)/Kconfig.defconfig"
source "$(KCONFIG_BINARY_DIR)/Kconfig.soc.defconfig"

source "boards/Kconfig"
source "soc/Kconfig"
source "arch/Kconfig"
...
```

其中與soc相關的語句有

```
source "$(KCONFIG_BINARY_DIR)/Kconfig.soc.defconfig"
...
source "soc/Kconfig"
```

可以很簡單的從名字區分出`Kconfig.soc.defconfig`是放置設定選項預設值的，而`soc/Kconfig`是放置設定選項的。我們先從`soc/Kconfig`看起。

#### `soc/kconfig`

`soc/kconfig`內容如下：

```
choice
	prompt "SoC/CPU/Configuration Selection"

    source "$(KCONFIG_BINARY_DIR)/Kconfig.soc"
endchoice

menu "Hardware Configuration"
    source "$(KCONFIG_BINARY_DIR)/Kconfig.soc.arch"
    ...
endmenu
```

可看出選擇SoC的部分應放在`$(KCONFIG_BINARY_DIR)/Kconfig.soc`內。而且由於外面被`choice...endchoice`包圍的關係，其內容只能是config entry，且採單選型式。

- `$(KCONFIG_BINARY_DIR)/Kconfig.soc`

  實際位於`build\Kconfig\Kconfig.soc`。可看出其主要用來include 

  - `zephyr/soc/$(ARCH)/*/Kconfig.soc`和
  - `app/soc/$(ARCH)/*/Kconfig.soc` (如果你的soc目錄是採用out-of-tree的型式擺放)。

  檔案。

- `$(KCONFIG_BINARY_DIR)/Kconfig.soc.arch`

  實際位於`build\Kconfig\Kconfig.soc.arch`。可看出其主要用來include 

  - `zephyr/soc/$(ARCH)/Kconfig`
  - `zephyr/soc/$(ARCH)/*/Kconfig`
  - `app/soc/$(ARCH)/Kconfig` (如果你的soc目錄是採用out-of-tree的型式擺放)。
  - `app/soc/$(ARCH)/*/Kconfig` (如果你的soc目錄是採用out-of-tree的型式擺放)。

  檔案。

- `$(KCONFIG_BINARY_DIR)/Kconfig.soc.defconfig`

  這是在`zephyr/Kconfig.zephyr`內被包含的。不過都同屬`$(KCONFIG_BINARY_DIR)`開頭，因此順便放在這裡解說。

  此檔案實際位於`build\Kconfig\Kconfig.soc.defconfig`。可看出其主要用來include 

  - `zephyr-V5/soc/$(ARCH)/*/Kconfig.defconfig`
  - `app/soc/$(ARCH)/*/Kconfig.defconfig` (如果你的soc目錄是採用out-of-tree的型式擺放)。

而這些放置在`$(KCONFIG_BINARY_DIR)/`下的檔案的內容怎麼產生，可以從`zephyr\cmake\kconfig.cmake`看出，zephyr建置系統會從每個 SOC_ROOT 目錄，找出是否存在 

1. `soc/$(ARCH)/*/Kconfig.defconfig`
2. `soc/$(ARCH)/*/Kconfig.soc`
3. `soc/$(ARCH)/Kconfig`
4. `soc/$(ARCH)/*/Kconfig`

然後產生出`$(KCONFIG_BINARY_DIR)/`下的那幾個檔案。

這裡我先整理一下目前我們看的到的Kconfig樹狀圖 (注意這裡我列的是v2.4.0版的Kconfig樹狀圖，在2.6.99版後又有變動)：

```
zephyr/Kconfig
└── zephyr/Kconfig.zephyr
    ├── $(KCONFIG_BINARY_DIR)/Kconfig.modules
    ├── modules/Kconfig
    │
    ├── zephyr/boards/shields/*/Kconfig.defconfig"
    ├── $(BOARD_DIR)/Kconfig.defconfig
    ├── $(KCONFIG_BINARY_DIR)/Kconfig.soc.defconfig
    │   ├── app/soc/$(ARCH)/$(SOC_F)/Kconfig.defconfig
    │   └── zephyr/soc/$(ARCH)/$(SOC_F)/Kconfig.defconfig
    │
    ├── zephyr/boards/Kconfig
    ├── zephyr/soc/Kconfig
    │   ├── $(KCONFIG_BINARY_DIR)/Kconfig.soc
    │   │   ├── app/soc/$(ARCH)/$(SOC_F)/Kconfig.soc
    │   │   └── zephyr/soc/$(ARCH)/$(SOC_F)/Kconfig.soc
    │   ├── $(KCONFIG_BINARY_DIR)/Kconfig.soc.arch
    │   │   ├── app/soc/$(ARCH)/Kconfig
    │   │   ├── app/soc/$(ARCH)/$(SOC_F)/Kconfig
    │   │   ├── zephyr/soc/$(ARCH)/Kconfig
    │   │   └── zephyr/soc/$(ARCH)/$(SOC_F)/Kconfig
    │   └── zephyr/subsys/logging/Kconfig.template.log_config
    ├── ...
    └── "$(TOOLCHAIN_KCONFIG_DIR)/Kconfig"
```

#### 定義`SOC_FAMILY`、`SOC_SERIES`、`SOC` 

無論如何，==在soc目錄下的這些Kconfig內，你需要定義SOC_FAMILY、SOC_SERIES、SOC (SOC_SERIES是一定要定義的，SOC_FAMILY和SOC可根據的晶片狀況省略，不過若省略的話步驟就不會跟底下一樣)。==

為了避免路徑太攏長，我後面都用SOC_F代替SOC_FAMILY，SOC_S代替SOC_SERIES。

- `SOC_FAMILY`通常定義在`soc/$(ARCH)/$(SOC_F)/Kconfig`。
  - `soc/$(ARCH)/$(SOC_F)/Kconfig`除用來定義`SOC_FAMILY`外，還通常包含`soc/$(ARCH)/$(SOC_F)/$(SOC_S)/Kconfig.soc`的內容。
  - `soc/$(ARCH)/$(SOC_F)/Kconfig`和`soc/$(ARCH)/$(SOC_F)/$(SOC_S)/Kconfig.soc`都是放在`menu "Hardware Configuration"`下，因此都是用來放置與SoC有關的細部設定選項。其中：
    - `soc/$(ARCH)/$(SOC_F)/Kconfig`是針對整個**SOC family**的細部設定選項。
    - `soc/$(ARCH)/$(SOC_F)/$(SOC_S)/Kconfig.soc`是針對整個**SOC series**的細部設定選項。
- `SOC_SERIES`通常定義在`soc/$(ARCH)/$(SOC_F)/$(SOC_S)/Kconfig.series`。
- `SOC`通常定義在`soc/$(ARCH)/$(SOC_F)/$(SOC_S)/Kconfig.soc`。

##### 定義`SOC_FAMILY`

首先定義`SOC_FAMILY`。

- 開啟`soc/$(ARCH)/$(SOC_F)/Kconfig`，填入

  ```
  config SOC_FAMILY_$(SOC_F)
  	bool
  	
  if SOC_FAMILY_$(SOC_F)
  
  config SOC_FAMILY
  	string
  	default "$(SOC_F)"
  
  rsource "*/Kconfig.soc"
  
  endif
  ```

假設我的

- `$(ARCH)` 是 `riscv`
- `$(SOC_F) ` 是 `riscv-ite`

則在`soc/riscv/riscv-ite/Kconfig`填入以下內容：

```
config SOC_FAMILY_RISCV_ITE
	bool

if SOC_FAMILY_RISCV_ITE

config SOC_FAMILY
    string
	default "riscv-ite"

rsource "*/Kconfig.soc"

endif # SOC_FAMILY_RISCV_ITE
```

此刻的Kconfig樹狀圖 (注意這裡我列的是v2.4.0版的Kconfig樹狀圖，在2.6.99版後又有變動)：

```
zephyr/Kconfig
└── zephyr/Kconfig.zephyr
    ├── $(KCONFIG_BINARY_DIR)/Kconfig.modules
    ├── modules/Kconfig
    │
    ├── zephyr/boards/shields/*/Kconfig.defconfig"
    ├── $(BOARD_DIR)/Kconfig.defconfig
    ├── $(KCONFIG_BINARY_DIR)/Kconfig.soc.defconfig
    │   ├── app/soc/$(ARCH)/$(SOC_F)/Kconfig.defconfig
    │   └── zephyr/soc/$(ARCH)/$(SOC_F)/Kconfig.defconfig
    │
    ├── zephyr/boards/Kconfig
    ├── zephyr/soc/Kconfig
    │   ├── $(KCONFIG_BINARY_DIR)/Kconfig.soc
    │   │   ├── app/soc/$(ARCH)/$(SOC_F)/Kconfig.soc
    │   │   └── zephyr/soc/$(ARCH)/$(SOC_F)/Kconfig.soc
    │   ├── $(KCONFIG_BINARY_DIR)/Kconfig.soc.arch
    │   │   ├── app/soc/$(ARCH)/Kconfig
    │   │   ├── app/soc/$(ARCH)/$(SOC_F)/Kconfig (定義SOC_FAMILY)
    │   │   │   └── app/soc/$(ARCH)/$(SOC_F)/$(SOC_S)/Kconfig.soc
    │   │   ├── zephyr/soc/$(ARCH)/Kconfig
    │   │   └── zephyr/soc/$(ARCH)/$(SOC_F)/Kconfig
    │   └── zephyr/subsys/logging/Kconfig.template.log_config
    ├── ...
    └── "$(TOOLCHAIN_KCONFIG_DIR)/Kconfig"
```

##### 定義`SOC_SERIES`

- 開啟`soc/$(ARCH)/$(SOC_F)/$(SOC_S)/Kconfig.series`，填入

    ```
    config SOC_SERIES_$(SOC_S)
        bool "XXX SOC series implementation"
        select $(ARCH)
        select SOC_FAMILY_$(SOC_F)
        help
          Enable support for XXX SOC series
    ```

- 開啟`soc/$(ARCH)/$(SOC_F)/$(SOC_S)/Kconfig.defconfig.series`

  ```
  if SOC_SERIES_$(SOC_S)
  
  config SOC_SERIES
  	default "$(SOC_S)"
  
  endif
  ```
  

可以看出這裡和定義`SOC_FAMILY`的作法又有點不一樣，預設值被放在另一個檔案。

這裡有一個問題是，`Kconfig.series`和`Kconfig.defconfig.series`目前不存在Kconfig樹中，因此要找地方include這兩個檔案。通常：

- `Kconfig.series`

  放在`zephyr\soc\$(ARCH)\$(SOC_F)\Kconfig.soc`或`app\soc\$(ARCH)\$(SOC_F)\Kconfig.soc`

- `Kconfig.defconfig.series`

  放在`zephyr\soc\$(ARCH)\$(SOC_F)\Kconfig.defconfig`或`app\soc\$(ARCH)\$(SOC_F)\Kconfig.defconfig`

更新一下Kconfig樹狀圖 (注意這裡我列的是v2.4.0版的Kconfig樹狀圖，在2.6.99版後又有變動)：

```
zephyr/Kconfig
└── zephyr/Kconfig.zephyr
    ├── $(KCONFIG_BINARY_DIR)/Kconfig.modules
    ├── modules/Kconfig
    │
    ├── zephyr/boards/shields/*/Kconfig.defconfig"
    ├── $(BOARD_DIR)/Kconfig.defconfig
    ├── $(KCONFIG_BINARY_DIR)/Kconfig.soc.defconfig
    │   ├── app/soc/$(ARCH)/$(SOC_F)/Kconfig.defconfig
    │   │   └── app/soc/$(ARCH)/$(SOC_F)/$(SOC_S)/Kconfig.defconfig.series
    │   └── zephyr/soc/$(ARCH)/$(SOC_F)/Kconfig.defconfig
    │
    ├── zephyr/boards/Kconfig
    ├── zephyr/soc/Kconfig
    │   ├── $(KCONFIG_BINARY_DIR)/Kconfig.soc
    │   │   ├── app/soc/$(ARCH)/$(SOC_F)/Kconfig.soc
    │   │   │   └── app/soc/$(ARCH)/$(SOC_F)/$(SOC_S)/Kconfig.series (定義SOC_SERIES)
    │   │   └── zephyr/soc/$(ARCH)/$(SOC_F)/Kconfig.soc
    │   ├── $(KCONFIG_BINARY_DIR)/Kconfig.soc.arch
    │   │   ├── app/soc/$(ARCH)/Kconfig
    │   │   ├── app/soc/$(ARCH)/$(SOC_F)/Kconfig (定義SOC_FAMILY)
    │   │   │   └── app/soc/$(ARCH)/$(SOC_F)/$(SOC_S)/Kconfig.soc 
    │   │   ├── zephyr/soc/$(ARCH)/Kconfig
    │   │   └── zephyr/soc/$(ARCH)/$(SOC_F)/Kconfig
    │   └── zephyr/subsys/logging/Kconfig.template.log_config
    ├── ...
    └── "$(TOOLCHAIN_KCONFIG_DIR)/Kconfig"
```




假設我的

- `$(ARCH)` 是 `riscv`
- `$(SOC_F) ` 是 `riscv-ite`
- `$(SOC_S)`是`it983x`

則在`soc/riscv/riscv-ite/it983x/Kconfig.series`填入以下內容：

```
config SOC_SERIES_IT983X
	bool "IT983x SOC series implementation"
	select RISCV
	select SOC_FAMILY_RISCV_ITE
	help
	    Enable support for IT983x SOC series
```

而在`soc/riscv/riscv-ite/it983x/Kconfig.defconfig.series`填入

```
if SOC_SERIES_IT983X

config SOC_SERIES
	default "it983x"

endif
```

為了要找地方include這兩個檔案，因此還需要修改：

- `app\soc\riscv\riscv-ite\Kconfig.soc`，填入

  ```
  rsource "*/Kconfig.series"
  ```

- `app\soc\riscv\riscv-ite\Kconfig.defconfig`，填入

  ```
  rsource "*/Kconfig.defconfig.series"
  ```

##### 定義`SOC`

開啟`soc/$(ARCH)/$(SOC_F)/$(SOC_S)/Kconfig.soc`，填入

```
choice
	prompt "XXX SoC Selection"
	depends on SOC_SERIES_$(SOC_S)

config SOC_$(SOC)
	bool "$(SOC)"

endchoice
```

開啟`soc/$(ARCH)/$(SOC_F)/$(SOC_S)/Kconfig.defconfig.$(SOC)`，填入

```
if SOC_$(SOC)

config SOC
	default "$(SOC)"

endif
```

這裡有一個問題是，`Kconfig.defconfig.$(SOC)`目前不存在Kconfig樹中，因此要找地方include這一個檔案。通常放在`Kconfig.defconfig.series`：

```
if SOC_SERIES_$(SOC_S)

rsource "Kconfig.defconfig.$(SOC)"

config SOC_SERIES
	default "it983x"

endif
```
更新一下Kconfig樹狀圖 (注意這裡我列的是v2.4.0版的Kconfig樹狀圖，在2.6.99版後又有變動)：

```
zephyr/Kconfig
└── zephyr/Kconfig.zephyr
    ├── $(KCONFIG_BINARY_DIR)/Kconfig.modules
    ├── modules/Kconfig
    │
    ├── zephyr/boards/shields/*/Kconfig.defconfig"
    ├── $(BOARD_DIR)/Kconfig.defconfig
    ├── $(KCONFIG_BINARY_DIR)/Kconfig.soc.defconfig
    │   ├── app/soc/$(ARCH)/$(SOC_F)/Kconfig.defconfig
    │   │   └── app/soc/$(ARCH)/$(SOC_F)/$(SOC_S)/Kconfig.defconfig.series
    │   │       └── app/soc/$(ARCH)/$(SOC_F)/$(SOC_S)/Kconfig.defconfig.$(SOC)
    │   └── zephyr/soc/$(ARCH)/$(SOC_F)/Kconfig.defconfig
    │
    ├── zephyr/boards/Kconfig
    ├── zephyr/soc/Kconfig
    │   ├── $(KCONFIG_BINARY_DIR)/Kconfig.soc
    │   │   ├── app/soc/$(ARCH)/$(SOC_F)/Kconfig.soc
    │   │   │   └── app/soc/$(ARCH)/$(SOC_F)/$(SOC_S)/Kconfig.series (定義SOC_SERIES)
    │   │   └── zephyr/soc/$(ARCH)/$(SOC_F)/Kconfig.soc
    │   ├── $(KCONFIG_BINARY_DIR)/Kconfig.soc.arch
    │   │   ├── app/soc/$(ARCH)/Kconfig
    │   │   ├── app/soc/$(ARCH)/$(SOC_F)/Kconfig (定義SOC_FAMILY)
    │   │   │   └── app/soc/$(ARCH)/$(SOC_F)/$(SOC_S)/Kconfig.soc 
    │   │   ├── zephyr/soc/$(ARCH)/Kconfig
    │   │   └── zephyr/soc/$(ARCH)/$(SOC_F)/Kconfig
    │   └── zephyr/subsys/logging/Kconfig.template.log_config
    ├── ...
    └── "$(TOOLCHAIN_KCONFIG_DIR)/Kconfig"
```



假設我的

- `$(ARCH)` 是 `riscv`
- `$(SOC_F) ` 是 `riscv-ite`
- `$(SOC_S)`是`it983x`
- `$(SOC)`是`IT9830`


開啟`soc/riscv/riscv-ite/it983x/Kconfig.soc`，填入

```
choice
	prompt "XXX SoC Selection"
	depends on SOC_SERIES_IT983X

config SOC_IT9830
	bool "IT9830"

endchoice
```

開啟`soc/riscv/riscv-ite/it983x/Kconfig.defconfig.IT9830`，填入

```
if SOC_IT9830

config SOC
	default "IT9830"

endif
```

這裡有一個問題是，`Kconfig.defconfig.IT9830`目前不存在Kconfig樹中，因此要找地方include這一個檔案。通常放在`Kconfig.defconfig.series`：

```
if SOC_SERIES_IT983X

rsource "Kconfig.defconfig.IT9830"

config SOC_SERIES
	default "it983x"

endif
```

### 修改電路板設定

此時可以先修改電路板設定轉成使用我們剛建立的SOC。

- `app\boards\riscv\it9830_evb\it9830_evb_defconfig`

  ```
  CONFIG_SOC_SERIES_IT983X=y
  CONFIG_SOC_IT9830=y
  # CONFIG_SYS_CLOCK_HW_CYCLES_PER_SEC 必須設定為與CPU同頻率，這裡我暫時設為12MHz
  # 不設定這個選項在Cmake設定時期就會出現error
  CONFIG_SYS_CLOCK_HW_CYCLES_PER_SEC=12000000
  ```

- app\boards\riscv\it9830_evb\Kconfig.board

  ```
  config BOARD_IT9830_EVB
      bool "IT9830 evaluation board"
      depends on SOC_IT983X
  ```

- app\boards\riscv\it9830_evb\it9830_evb.dts

  ```
  /dts-v1/;
  
  // 至少要 include 這個 dtsi 檔案，否則在 gen_define.py 會回被錯誤
  #include <skeleton.dtsi>
  
  / {
    model = "ITE IT9830 EVB";
    compatible = "ite,it9830_evb";
  };
  ```




修改建置時的SOC_ROOT參數。修改 z:\zephyrproject\build.sh
```shell=
source zephyr-V5/zephyr-env.sh
export ZEPHYR_TOOLCHAIN_VARIANT='cross-compile'
export CROSS_COMPILE="/cygdrive/c/Andestech/AndeSight_STD_v500/toolchains/nds32le-elf-newlib-v5/bin/riscv32-elf-"

#cmake --trace-expand -Bbuild -DBOARD=it9830_evb -DBOARD_ROOT="." -DSOC_ROOT="." app 2>&1 | tee ./zephyr.cmake.out.txt
cmake -Bbuild -DBOARD=it9830_evb -DBOARD_ROOT="." -DSOC_ROOT="." app 2>&1 | tee ./zephyr.cmake.out.txt
```

如果你是採用`west`工具來建置專案，可以使用以下命令

```
west -v build -b it9830_evb ./app -- --trace-expand -DBOARD_ROOT="." -DSOC_ROOT="."  2>&1 | tee ./zephyr.cmake.out.txt 
```

你會發現這時候建置專案仍然有一些錯誤，這些錯誤都根據你使用的arch不同而有所不同，因此這也是為何官方文件無法針對SoC移植提供一個標準的作法指南。

因此我會建議直接從最接近的SoC複製一整個目錄來修改，並參考 [Enabling Zephyr on Your Hardware Platform](https://elinux.org/images/a/ad/Enabling-Zephyr-on-Your-Hardware-Platform-Diego-Sueiro-Sepura-Embarcados-1.pdf)這份文件。

###### tags: Zephyr RTOS