3、创建SoC目录结构:在Zephyr源码中创建vendor和soc目录,配置Kconfig和CMakeLists

好,咱们接着往下走。上一章我们把Zephyr的构建系统框架理清了,现在要动真格的了——在源码里给我们的新芯片安个家。

这一步说白了就是「圈地运动」。你得在Zephyr的树形结构里,给芯片厂商(vendor)和具体的SoC系列分别划出地盘。我刚开始移植的时候,觉得这不就是个mkdir嘛,有啥好讲的?结果目录结构搭错了,后面编译链接各种报错,查了半天才发现是路径没对上。嗯,这里要注意,目录结构直接决定了Kconfig的加载顺序和CMake的搜索路径,一步错步步错。

3.1 整体目录规划

Zephyr的SoC目录遵循一个约定俗成的规则:soc/<vendor>/<series>/<soc_family>。举个例子,如果你在移植一个叫「BlueOcean」公司的「BO3000」系列芯片,那目录大概长这样:

soc/
└── blueocean/
    └── bo3000/
        ├── Kconfig
        ├── Kconfig.soc
        ├── CMakeLists.txt
        ├── linker.ld
        └── soc.c

我个人习惯在vendor目录下再放一个顶层的Kconfig,用来统一管理这个厂商所有系列的配置选项。比如:

soc/blueocean/
├── Kconfig          # 厂商级配置
└── bo3000/
    ├── Kconfig
    └── ...

这样做的好处是,以后同一个厂商出了新系列,你只需要在vendor的Kconfig里加一行source就行,不用到处改。

我的小技巧: vendor目录的名字最好全小写,不要用下划线。Zephyr官方也是这么干的,比如nxp、st、silabs。你想想看,如果混用大小写,后面写Kconfig的source路径时很容易搞错。

3.2 创建vendor级Kconfig

soc/blueocean/Kconfig里,我们放什么?主要是定义厂商相关的配置选项,以及引入各个系列的Kconfig。

# soc/blueocean/Kconfig
config SOC_FAMILY_BLUEOCEAN
    bool
    help
      BlueOcean SoC family support

if SOC_FAMILY_BLUEOCEAN

    # 这里可以放一些厂商通用的配置
    config BLUEOCEAN_HAS_FPU
        bool "FPU support"
        default y

    # 引入具体系列
    source "soc/blueocean/bo3000/Kconfig"

endif # SOC_FAMILY_BLUEOCEAN

注意看,SOC_FAMILY_BLUEOCEAN这个选项是bool类型,但没有prompt。这意味着它不会出现在menuconfig里让用户手动选,而是由具体系列的Kconfig来select它。这是一种很常见的「隐式依赖」写法。

我曾经踩过的坑: 一开始我把厂商级配置写成了menuconfig,结果用户配置时多了一层菜单,而且如果不手动enable,整个系列的代码都不会被编译。后来我改成bool + 自动select,清爽多了。

3.3 创建SoC系列级Kconfig

接下来是重头戏——soc/blueocean/bo3000/Kconfig。这里要定义SoC的具体配置,包括CPU架构、内存布局、外设支持等。

# soc/blueocean/bo3000/Kconfig

# 选择CPU架构
config SOC_SERIES_BO3000
    bool "BlueOcean BO3000 series"
    select ARM
    select CPU_CORTEX_M4
    select SOC_FAMILY_BLUEOCEAN
    help
      BlueOcean BO3000 series based on ARM Cortex-M4

if SOC_SERIES_BO3000

    config SOC_BO3000
        bool "BO3000 SoC"
        default y
        help
          BlueOcean BO3000 SoC

    # 内存配置
    config SRAM_SIZE
        int "SRAM size in KB"
        default 256

    config FLASH_SIZE
        int "Flash size in KB"
        default 1024

    # 时钟配置
    config SYS_CLOCK_HW_CYCLES_PER_SEC
        int
        default 64000000

    # 外设配置
    config UART_BO3000
        bool "UART support"
        default y

    config GPIO_BO3000
        bool "GPIO support"
        default y

endif # SOC_SERIES_BO3000

这里有几个关键点:

  • select ARM:告诉Zephyr这是ARM架构,会引入ARM相关的通用配置
  • select CPU_CORTEX_M4:指定具体内核,这会决定编译器参数和启动代码
  • select SOC_FAMILY_BLUEOCEAN:自动enable厂商级配置
  • SOC_BO3000:这是最终的SoC选项,board级别的Kconfig会依赖它

你可能会问:为什么要有SOC_SERIES_BO3000SOC_BO3000两层?其实这是为了支持一个系列下有多个变体。比如BO3000有A版本和B版本,一个带FPU一个不带,那就可以在SOC_SERIES_BO3000下再细分。

3.4 配置CMakeLists.txt

Kconfig搞定了,接下来是CMake。Zephyr的构建系统会递归查找soc目录下的CMakeLists.txt,所以我们需要告诉它:这个SoC需要编译哪些源文件,链接哪些库。

# soc/blueocean/bo3000/CMakeLists.txt

# 添加SoC级别的源文件
zephyr_sources(soc.c)

# 如果有汇编启动文件
zephyr_sources(vectors.S)

# 添加链接脚本
zephyr_linker_sources(ROM_START linker.ld)

# 添加头文件搜索路径
zephyr_include_directories(include)

这里要注意zephyr_linker_sources这个函数。它用来注册链接脚本片段,Zephyr会把所有注册的片段拼接成最终的链接脚本。我一开始不知道这个,直接把完整的linker.ld扔进去,结果链接时符号重复定义,查了半天才发现是用法不对。

核心要点: SoC级别的CMakeLists.txt只负责SoC本身的代码,不要在这里添加driver或board的源文件。那些应该在对应的目录里处理。职责单一,这是Zephyr的设计哲学。

3.5 编写soc.c和linker.ld

这两个文件是SoC移植的骨架。soc.c通常包含:

  • 系统时钟初始化
  • 中断控制器配置
  • 内存保护单元(MPU)设置
  • 调试接口初始化

一个最简单的soc.c示例:

/* soc/blueocean/bo3000/soc.c */
#include <soc.h>

void soc_early_init(void)
{
    /* 配置系统时钟 */
    SystemClock_Config();

    /* 使能FPU(如果支持) */
#if CONFIG_CPU_CORTEX_M4
    SCB->CPACR |= (3UL << 20) | (3UL << 22);
#endif
}

void soc_init(void)
{
    /* 初始化中断控制器 */
    NVIC_SetPriorityGrouping(3);
}

linker.ld则定义了内存布局:

/* soc/blueocean/bo3000/linker.ld */
#include <zephyr/linker/linker-defs.h>

MEMORY
{
    FLASH (rx) : ORIGIN = 0x08000000, LENGTH = CONFIG_FLASH_SIZE * 1K
    SRAM (rwx) : ORIGIN = 0x20000000, LENGTH = CONFIG_SRAM_SIZE * 1K
}

#include <zephyr/linker/linker-tool.h>

注意看,这里用了CONFIG_FLASH_SIZECONFIG_SRAM_SIZE,它们就是在Kconfig里定义的那两个int选项。这样用户可以通过menuconfig调整内存大小,而不需要改链接脚本。

3.6 验证目录结构

全部创建完后,建议跑一下这个命令验证:

west build -b your_board -t menuconfig

如果能正常弹出配置界面,并且在「SoC Selection」里看到你的BO3000选项,那就说明目录结构和Kconfig都配置正确了。

如果报错说找不到Kconfig文件,多半是路径写错了。我建议你检查:

  • vendor目录下的Kconfig有没有source到系列Kconfig
  • 系列Kconfig里的source路径是不是相对Zephyr根目录
  • 有没有遗漏select语句
调试小技巧: 在Kconfig里加一句comment "BO3000 Kconfig loaded",如果menuconfig里能看到这行注释,就说明Kconfig被正确加载了。这招帮我省了不少排查时间。

好了,到这一步,你的SoC在Zephyr里算是有个「户口」了。下一章我们会基于这个目录结构,开始写board级别的配置,让这块芯片真正跑起来。