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就行,不用到处改。
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它。这是一种很常见的「隐式依赖」写法。
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_BO3000和SOC_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扔进去,结果链接时符号重复定义,查了半天才发现是用法不对。
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_SIZE和CONFIG_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语句
comment "BO3000 Kconfig loaded",如果menuconfig里能看到这行注释,就说明Kconfig被正确加载了。这招帮我省了不少排查时间。
好了,到这一步,你的SoC在Zephyr里算是有个「户口」了。下一章我们会基于这个目录结构,开始写board级别的配置,让这块芯片真正跑起来。