第四章 源码获取与目录结构:从MTK官方获取Bootloader源码
好,咱们进入实战环节的第一步——拿到源码。
很多新手会问:「源码去哪下?是不是得找MTK签NDA?」
嗯,这个问题我当年也困惑过。说实话,MTK的源码获取确实比高通要「接地气」一些。我个人习惯直接走MTK的在线资源平台——MTK Online(也就是常说的MOL)。如果你是授权客户,签完协议后就能拿到完整的BSP包。
但如果你只是个人学习,或者公司还没正式签约,也别急。MTK其实有公开的开源代码仓库,比如GitHub上的mtk-bootloader项目。虽然版本可能不是最新的,但拿来学习LK的结构完全够用。
4.1 源码包的整体布局
拿到源码包后,解压出来你会看到一堆文件夹。别慌,咱们只看核心部分。
MTK 8678的BSP包,典型的目录结构长这样:
alps/
├── bootable/
│ └── bootloader/
│ ├── lk/ # Little Kernel 主目录
│ ├── preloader/ # 一级加载器
│ └── dsp/ # 数字信号处理器固件
├── kernel-4.19/ # Linux内核
├── device/ # 设备配置
├── vendor/ # 厂商定制
└── Makefile # 顶层编译脚本
咱们这课程的重点,就是bootable/bootloader/lk这个目录。说白了,LK就是整个系统启动的「二传手」——Preloader把硬件初始化完,然后跳转到LK,LK再加载Linux内核。
4.2 LK目录结构深度解析
打开lk/目录,你会看到这样的结构:
lk/
├── app/ # 应用层代码
│ ├── mt_boot/ # MTK启动主流程
│ ├── fastboot/ # Fastboot协议实现
│ └── shell/ # 调试命令行
├── arch/ # 架构相关代码
│ └── arm/ # ARMv8-A 架构
│ ├── arm64/ # 64位模式
│ └── include/ # 架构头文件
├── dev/ # 设备驱动
│ ├── keys/ # 按键驱动
│ ├── display/ # 显示驱动
│ └── uart/ # 串口驱动
├── include/ # 全局头文件
├── kernel/ # 内核核心
│ ├── main.c # LK入口函数
│ └── thread.c # 线程管理
├── lib/ # 库函数
│ ├── heap/ # 内存管理
│ └── printf/ # 打印函数
├── platform/ # 平台相关
│ └── mt8678/ # MTK 8678 平台
│ ├── include/ # 平台头文件
│ ├── platform.c# 平台初始化
│ └── rules.mk # 编译规则
├── target/ # 目标板配置
│ └── mt8678_evb/ # 评估板配置
│ ├── include/ # 板级头文件
│ └── board.c # 板级初始化
├── scripts/ # 编译脚本
├── makefile # 顶层Makefile
└── project/ # 项目配置文件
这个结构,说白了就是「分层解耦」的思想。我刚开始看的时候也觉得眼花,但后来发现一个规律:你改得最多的,永远是platform/和target/。
核心经验: 移植Bootloader,90%的工作量集中在platform/mt8678/和target/mt8678_evb/这两个目录。其他目录,除非你要加新功能,否则基本不用动。
4.3 关键文件说明
咱们挑几个最核心的文件,一个一个说清楚。
4.3.1 kernel/main.c — LK的入口
这是LK的main()函数所在地。系统上电后,Preloader跳转过来,第一站就是这里。
// kernel/main.c (简化版)
void lk_main(void) {
// 1. 早期硬件初始化
platform_early_init();
target_early_init();
// 2. 架构初始化
arch_init();
// 3. 内核子系统初始化
kernel_init();
// 4. 启动应用
app_init();
// 5. 进入主循环
while(1) {
// 处理事件
}
}
你看,流程非常清晰。我当年做第一个移植项目时,就是在这个函数里加打印,一步步看系统卡在哪。说白了,调试Bootloader,就是靠串口打印和点灯——别笑,这招最管用。
4.3.2 app/mt_boot/mt_boot.c — MTK启动主流程
这个文件是MTK自己加的应用层代码。它负责:
- 检测按键组合(比如音量减+电源进Fastboot)
- 加载并验证Linux内核镜像
- 处理开机动画显示
- 管理启动模式(正常启动、恢复模式、工厂模式等)
我记得有一次客户反馈说「按音量键进不了Recovery」,查了半天,发现是mt_boot.c里的按键扫描逻辑跟硬件GPIO对不上。嗯,这种坑,你光看代码是看不出来的,必须拿示波器量。
4.3.3 platform/mt8678/platform.c — 平台初始化
这个文件是移植工作的重中之重。它负责:
- 时钟初始化:配置PLL,设置CPU频率、总线频率
- 内存控制器初始化:配置DDR时序、大小
- 中断控制器初始化:GIC的配置
- 定时器初始化:系统Tick的时钟源
// platform/mt8678/platform.c (伪代码)
void platform_early_init(void) {
// 配置ARM Generic Timer
arm_timer_init(26); // 26MHz晶振
// 初始化UART用于调试
uart_init(UART0, 115200);
// 配置PLL
pll_config(PLL_CPU, 2000); // 2GHz
pll_config(PLL_BUS, 800); // 800MHz
dprintf(INFO, "Platform early init done\n");
}
小技巧: 移植时,我习惯先把dprintf加满。每初始化一个模块,就打印一条信息。这样如果系统卡死,你能精确知道是哪个步骤出了问题。
4.3.4 target/mt8678_evb/board.c — 板级配置
这个文件处理跟具体硬件板子相关的配置:
- GPIO复用功能配置
- LED指示灯控制
- 按键映射
- LCD屏幕参数
说白了,platform.c管的是芯片内部的东西,board.c管的是板子上的外设。这两个文件的分工,你心里要有数。
4.3.5 project/mt8678_evb.mk — 项目编译配置
这个文件决定了编译哪些模块、链接哪些库:
# project/mt8678_evb.mk (片段)
TARGET := mt8678_evb
PLATFORM := mt8678
# 使能的功能模块
MODULES += \
dev/keys \
dev/display \
dev/uart \
app/mt_boot \
app/fastboot
# 全局编译选项
GLOBAL_DEFINES += \
MTK_USER_BUILD=1 \
MTK_SECURITY_BOOT=1
这里要注意的是MTK_SECURITY_BOOT这个宏。如果开了安全启动,LK会校验内核签名。我踩过坑——开了安全启动但没配好密钥,结果机器直接变砖。所以,开发阶段建议先关掉。
4.4 源码获取的实操建议
最后,给各位几个实在的建议:
- 版本对齐:拿到源码后,先确认
lk/目录下的README文件,看看版本号。MTK 8678的LK版本通常是1.4.x系列。 - 打补丁:MTK会提供
.patch文件,记得按顺序打上。我见过有人漏了补丁,结果编译出来的Bootloader死活起不来。 - 备份原始代码:在开始修改前,先
git init并提交一次。这样改坏了还能回退。别问我为什么强调这个——说多了都是泪。
一句话总结: 源码获取只是第一步,真正考验人的是理解目录结构背后的设计思想。LK的分层架构——arch、platform、target——说白了就是让你「改一处,动全身」变成「改一处,只动一处」。这个思想,贯穿整个移植过程。
下一章,咱们就动手编译LK,看看能不能跑起来。