# 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