第4章 BSP目录结构分析:QNX BSP源码目录布局与关键文件说明
做BSP移植,第一件事是什么?
不是急着改代码,而是先把目录结构摸清楚。我见过不少新手,上来就翻buildfile,结果连文件在哪都找不到。说白了,QNX的BSP目录布局是有套路的。搞懂了这套路,你才能知道去哪改、改什么。
4.1 标准BSP目录布局
QNX官方提供的BSP,目录结构大致是这样的:
board_name/
├── build/
│ ├── common.build
│ ├── board.build
│ └── buildfile
├── images/
├── src/
│ ├── hw/
│ │ ├── startup/
│ │ ├── init/
│ │ └── lib/
│ └── ...
├── patches/
├── doc/
└── Makefile
嗯,这里要注意,board_name就是你目标板的名字。比如我做过的一个i.MX6Q的板子,就叫imx6q_sabrelite。
每个目录的职责很清晰:
| 目录/文件 | 作用 |
|---|---|
| build/ | 构建脚本的核心,放buildfile和构建配置 |
| images/ | 编译生成的镜像文件,比如IFS、OS图像 |
| src/hw/startup/ | 启动代码,板级初始化就在这里 |
| src/hw/init/ | 系统初始化脚本,比如io-pkt、devb-*的启动参数 |
| patches/ | 针对QNX官方代码的补丁 |
| doc/ | 文档,有时候会有readme.txt |
我个人习惯,拿到一个新BSP,先看build/目录。因为这里决定了整个系统的构建方式。
4.2 核心文件:buildfile
buildfile是整个BSP的灵魂。它告诉mkifs工具:你要把哪些东西打包进镜像,怎么配置系统。
一个典型的buildfile长这样:
[virtual=armle-v7,raw] .bootstrap = {
startup-imx6q-sabrelite
path=/proc/boot
}
[+script] .script = {
# 加载驱动
devb-sdmmc-imx6 &
waitfor /dev/sdmmc0
# 启动网络
io-pkt-v6-hc -d imx6 &
waitfor /dev/io-pkt
# 启动文件系统
mount -t qnx6 /dev/sdmmc0t177 /fs/sdmmc0
# 启动shell
[+session] ksh
}
# 包含的文件
libc.so.3
libcam.so.2
...
这里有几个关键点:
- [virtual=armle-v7,raw]:指定虚拟地址和格式。armle-v7是ARM小端模式,raw表示原始二进制。
- .bootstrap:启动加载器,就是startup-*那个文件。它负责最底层的硬件初始化。
- [+script] .script:启动脚本。系统启动后会按顺序执行这里面的命令。
- [+session]:表示启动一个会话,比如shell。
我曾经踩过一个坑:在.script里忘了加waitfor,结果驱动还没加载完,后面的命令就开始执行了。系统启动到一半就卡死。嗯,从那以后我每条命令后面都老老实实加上waitfor。
/proc/boot是启动分区,/dev/sdmmc0是设备节点。别搞混了。
4.3 构建配置:common.build
common.build是共享的构建配置。它定义了一些通用的变量和规则,可以被多个board.build引用。
举个例子:
# common.build
COMMON_CFLAGS = -O2 -Wall -Werror
COMMON_LDFLAGS = -L$(QNX_TARGET)/$(CPUVARDIR)/lib
# 通用驱动列表
DRIVERS = devb-sdmmc io-pkt-v6-hc devc-ser8250
# 通用库
LIBS = libc.so.3 libcam.so.2 libsocket.so.2
为什么要搞个common.build?
你想想看,如果你有多个板子,比如imx6q和imx6dl,它们的很多配置是相同的。把公共部分抽出来,维护起来就轻松多了。
我建议你在common.build里放这些东西:
- 编译器标志(CFLAGS、LDFLAGS)
- 通用的驱动列表
- 通用的库文件
- 通用的启动参数
4.4 板级配置:board.build
board.build是针对特定板子的配置。它通常会include common.build,然后覆盖或添加板级特有的内容。
# board.build
include common.build
# 板级特有配置
BOARD_CFLAGS = -DBOARD_IMX6Q_SABRELITE -DCPU_CORTEX_A9
# 板级驱动
BOARD_DRIVERS = devb-sdmmc-imx6 io-pkt-v6-hc-imx6
# 板级启动参数
BOARD_STARTUP_ARGS = -v -D imx6q_sabrelite
# 内存布局
MEMORY = {
ram = 0x80000000, 0x40000000 # 1GB DDR
flash = 0x60000000, 0x08000000 # 128MB NOR
}
这里有个细节:BOARD_CFLAGS里定义了BOARD_IMX6Q_SABRELITE这个宏。在startup代码里,你可以用这个宏来做条件编译:
#ifdef BOARD_IMX6Q_SABRELITE
// 初始化i.MX6Q的特定外设
imx6q_init_uart();
imx6q_init_sdram();
#endif
这样做的好处是,一套startup代码可以支持多个板子,只要用宏来区分就行。
4.5 三者的协作关系
说白了,这三个文件的关系是这样的:
- buildfile:最终的执行者,它引用common.build和board.build里的变量。
- common.build:提供通用配置,减少重复。
- board.build:提供板级特有配置,实现差异化。
在buildfile里,你通常会这样引用:
# buildfile
include common.build
include board.build
# 使用变量
[+script] .script = {
$(BOARD_DRIVERS)
...
}
这样,当你换板子时,只需要换board.build,buildfile和common.build基本不用动。
我记得有一次,客户要求在一周内支持三块不同的板子。我就是靠这种分层设计,只改了board.build和startup代码,buildfile几乎没动。最后三天就搞定了。
4.6 避坑指南
我曾经犯过的错:
- 在common.build里定义了
DRIVERS,又在board.build里重新定义了DRIVERS,结果common.build的被覆盖了。后来我改用+=来追加。 - buildfile里忘了include common.build,结果编译时一堆未定义的变量。嗯,这种低级错误我犯过一次就记住了。
- board.build里的内存布局写错了,导致系统启动时内存访问异常。建议每次改内存参数后,先用
mkifs -v检查一下。
4.7 小结
这一章我们聊了BSP的目录结构和三个核心文件。你只要记住:
- buildfile是总指挥,决定系统怎么构建
- common.build是公共库,放通用配置
- board.build是板级配置,放差异化内容
搞懂了这些,你就能在BSP的海洋里游刃有余了。下一章,我们深入startup代码,看看系统是怎么从零开始启动的。