# 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