简介:一份聚焦联发科最新移动SoC设计的ISSCC 2024技术论文,面向芯片架构师、后端电路工程师及移动平台研究者。资源系统梳理了基于4nm工艺的3.4GHz Tri-Gear全乱序ARMv9.2 CPU子系统,涵盖HP/BP/HE三档核心设计、Cortex-X4与OoO Cortex A720效率核心的搭配,以及Logic SRAM、标准单元改进、高带宽电压控制、双轨均衡器、硬件DVFS与PMIC被动放电等关键电路技术。文中还给出HP核心单线程性能提升15% IPC、功耗降低38%、峰值性能提高16%,HE核心功耗节省68%等实测数据,可直接用于理解高端手机SoC的能效设计思路。压缩包为单个PDF文件,共29.94MB,内容完整清晰,已有157人学习。适合希望深入掌握ARMv9.2架构与先进工艺CPU子系统的技术人员阅读。
1. MTK SoC Platform:先把它看成一套系统工程,再看芯片
第一次接触 MTK SoC Platform 的开发者,容易以为它就是一块板子或一颗主控;实际上一打开编译脚本,面对的就是由启动链路、BSP 包、烧录工具、分区策略组成的一整套系统工程。同样是点亮一个新外设,熟悉平台的人半小时就能定位到引脚和驱动层,不熟的人在编译环境上可能先耗一整天。这篇写给做 Android BSP、嵌入式 Linux 和物联网方案的工程师:从平台结构、最小编译,到启动链、分区修改、开机排障,按一线路径逐层拆开。新手能照着上板,熟手能拿边界参数对照自己的流程。平台这行没什么玄学,坑全是版本。
2. MTK SoC 平台的层次结构:硬件、BSP 与 vendor 接口在哪里分界
2.1 一颗 MTK SoC 上,除了 CPU 还有一整张外设地图
刚开始上手的人,最容易犯的错是把注意力全部放在 CPU 型号上。实际上平台里的 CPU 只是其中一块,旁边还排着 GPU、ISP、基带、多媒体编解码器、内存控制器,外面还要配一颗独立的 PMIC 调压芯片。平台化的核心是把这些外设驱动全部收进一组固定框架:CPU 调频走 cpufreq 加厂商自己的调频框架,电源域的开关通过 regulator 框架暴露给内核,基带的通信走独立协议通道而不是普通总线设备。你写驱动时面对的几乎都是封装好的操作接口,而不是一串裸寄存器。所以学平台的第一步不是背寄存器手册,而是弄清某个功能属于哪一层框架,否则连日志都不知道去哪抓。
为什么厂商坚持这套半封闭框架,而不是直接推 Linux 主线?三个原因很现实:内核主线对外设的合并节奏跟不上一颗新 SoC 的量产周期;基带、快充、影像算法是厂商的差异化资产,不会全部开源;平台碎片化太严重,统一框架才可能维持测试覆盖。理解了这三点,你就能明白平台包里为什么躺着那么多补丁和 prebuilt 二进制,也就能接受社区通用驱动在这个平台上经常是"能编过但不稳定"这个事实。
| 组成模块 | 驱动所在层级 | 调试入口 |
|---|---|---|
| CPU 集群 / GPU | kernel cpufreq / 厂商调频框架 | sysfs 调频节点、串口 log |
| PMIC 与电源域 | regulator 框架 + i2c 驱动 | i2cdetect、regulator sysfs |
| 基带 modem | 专有协议栈 + 消息通道 | 厂商 modem 抓 log 工具 |
| 启动与存储 | preloader / LK / 分区表 | scatter 文件、烧录输出 |
| 显示与相机 | v4l2 子设备 / 厂商 HAL | HAL 调试开关、串口 log |
这张表平常未必用得上,系统出问题定位时价值就出来了。比如开机后屏幕不亮,先判断是 kernel 里显示驱动没有 probe 成功,还是 HAL 层拿到了 framebuffer 但显示策略配错,两层日志入口完全不同。我一般拿到新平台时会先把这张表按自己的项目扩充一版,填上具体的 dts 路径和日志开关,后面排障会快很多。
2.2 Android 场景下,代码树的三个边界
在 Android 基线加 MTK 平台的组合里,代码树大致分成三块:开源基线本身、厂商内核补丁和 dts、厂商私有 BSP。方案商能自由改动的主要是第二块,以及 device 目录下的板级配置;厂商私有目录里的 HAL 和二进制库,常规做法是只升级、不批改。
vendor/<vendor>/proprietary/ # 厂商私有 so、firmware、HAL device/<vendor>/<board>/ # 板级配置、overlay、分区策略 kernel-*/arch/arm64/boot/dts/ # 板级 dts 与公共 dtsi out/target/product/<board>/ # 全部编译产物这段目录结构里,device 目录是客制化主战场:预装应用清单、资源覆盖、分区大小、权限配置都在这里;kernel 的 dts 目录决定外设引脚和驱动配置;vendor 私有目录最好只做整包升级,不单独换其中某个二进制。原因很现实:私有库之间按版本配套发布,单独替换一两个,运行时多半暴露 ABI 不匹配,表现为各种奇怪的崩溃而不是编译错误。
所以做平台版本升级,我不主张对着整个代码树 diff。比较稳的做法是把客制化改动维护成三份清单:内核补丁清单、dts 改动清单、device 目录配置清单。升级新平台包后把三份清单重新应用一遍,冲突范围立刻可见,比在全量 diff 里捞自己改过的东西省力得多。这套边界意识,是 MTK 平台移植不翻车的前提。
3. 用最小命令把 MTK 平台编译跑通:lunch、make 与产物定位
3.1 拿到 BSP 包之后先做什么:解压、核对版本、锁环境
厂商交付的形态一般是一份 BSP 压缩包,有时再带一份独立的 kernel 补丁包。解压后的根目录能直接看到 Android 基线目录和 vendor 目录,但我不建议直接开编。第一步先读包内的 README 或版本记录,确认内核补丁要打在哪个基线 tag 上;这一步能省掉后面一连串莫名其妙的编译失败。很多新入门的同事在这步翻车,都是因为内核补丁和基线不匹配,编到一半报一堆 context 冲突。
编译环境上,常见做法是准备一台干净的 Ubuntu LTS 宿主,再开容器跑构建。容器能把 jdk、python、make 的版本差异全部锁死,换机器不换环境;直接在个人电脑上编,依赖被系统升级冲掉是常事。首次接触平台时可以把环境初始化写成一段固定脚本,目录名按实际交付包来,下面是一个可参考的启动流程:
# 解压 BSP 包到固定目录,防止文件散落 mkdir -p ~/mtk_bsp && tar -xzf mtk_bsp_release.tar.gz -C ~/mtk_bsp cd ~/mtk_bsp ls # 找到 Android 基线目录和 vendor 目录 cat README_*.txt # 核对平台版本、kernel 补丁要求 source build/envsetup.sh 2>&1 | tee envsetup.log参数说明:tar 用 -C 指定目标是习惯问题,解压散落一地后期很难收拾;cat README 那一步是排查类问题的高性价比动作,很多人跳过它,后面补丁冲突才回头翻;source envsetup.sh 后加 tee 是因为这个脚本失败时不一定会立刻报非零退出码,留一份日志才好定位。如果交付包带独立 kernel 补丁,先在基线目录上把补丁打了,再进入编译流程。
3.2 选一个 target,跑通首次全编译
环境就绪后,键入 lunch 会列出该平台支持的全部 target,命名一般是 板名-userdebug 这样的结构。量产固件用 user,开发调试用 userdebug;两者差异集中在 adb root 权限、预设调试服务和部分 log 开关。首次接触平台时直接用 userdebug 就行,后面做认证和量产包再切 user。
# -j 参数按物理核数的 1.5 倍左右给,内存建议 16G 起步 source build/envsetup.sh lunch <board>-userdebug make -j24 2>&1 | tee build.log # 编译完成后确认关键产物 ls -l out/target/product/<board>/boot.img \ out/target/product/<board>/vendor.img参数说明:lunch 后面那个 target 名一定要和板子目录实际名称一致,写错的话编出来的是别的板型的产物,烧上去不开机;make -j 给太大内存吃紧,给太小 CPU 跑不满,物理核 1.5 倍左右是通用折中;tee build.log 是必须的,终端刷屏过去之后,失败原因全在日志尾部几百行里。首次全量编译耗时按机器差异从大几十分钟到两小时都正常,期间不需要盯着终端。另外有些交付版本把 preloader 和 LK 的编译独立出来,常见做法是先 make 全量,再按 README 单独编这两个镜像,不要自己猜脚本名,以包里的说明为准。编译失败时先看 build.log 尾部报出的第一个 error,而不是从头翻警告,这是能省半小时的排查顺序。
4. MTK 平台冷启动链路与分区布局:改分区大小从 scatter 下手
4.1 冷启动的接力赛:ROM → preloader → LK → kernel → init
MTK 平台的启动是一条五级接力:芯片内置 Boot ROM 固定不可改,负责初始化最小外设并从存储加载 preloader;preloader 初始化 DDR 和 PMIC,之后加载 LK;LK 负责点亮显示、检测按键,并拉起 boot.img 里的 kernel;kernel 起来后挂载各分区,最后由 init 触发 Android 上层。每一级跳转前都要校验下一级的镜像,这正是"只换了一个 preloader 就开不了机"的根源——上下级镜像配套不齐,信任链直接断掉。
[MTK] preloader start ... [MTK] PMIC init done [MTK] LK boot start ... [LK] load boot image... [Kernel] command line: console=ttyMT0 ...上面的串口输出是典型结构,具体关键字以你手上的平台为准。排查时只需要记住一个规律:串口输出停在哪个阶段的关键字,问题就出在哪一级。停在 preloader 之前,查烧录进 preloader 的镜像和分区地址;停在 LK 阶段,查 LK 配置和 boot 分区内容;已经打出 kernel command line 再死,才轮到查 dts 和驱动。这个判断顺序能把一半的开机故障直接分流,不用每件事都从头查。平时把各阶段的关键字记成一张小表,调试时对照着看,比凭感觉翻 log 可靠得多。
4.2 scatter 分区文件:烧录器与系统侧的事实来源
scatter 文件是官方烧录工具读取分区布局的输入,通常是一个文本文件,描述每个分区的名字、起始地址、大小、对应镜像文件名和类型。它也是改动分区大小绕不开的起点,以下是一段常见格式的示意:
partition_index: SYS0 partition_name: preloader file_name: preloader_<board>.bin type: PATTERN partition_size: 0x400000 physical_start_addr: 0x0 partition_index: SYS1 partition_name: boot file_name: boot.img type: NORMAL partition_size: 0x1000000 physical_start_addr: 0x200000字段说明:type 的 PATTERN 表示该区域是重复镜像,没有实际数据内容,preloader 分区常用;NORMAL 则是普通镜像分区。physical_start_addr 表示分区的物理起始地址,整个文件里的地址必须连续,否则烧录工具会报地址重叠或空洞。改分区大小最典型的翻车点是只改 scatter:比如想让 vendor 变大,就把 scatter 里的 vendor size 改了,烧录是成功了,一开机就 panic。原因是系统侧的分区事实来源不止 scatter 一份,板级配置里的分区 size 常量和 LK 里的分区描述表都得同步改,三处不一致就会在内核挂载分区时直接崩溃。正则的做法是改之前先把 scatter、板级配置、LK 分区表三处打开,统一改完再烧录,并且把改动记录写进提交信息。
另外注意平台演进:较新的平台已经切到动态分区,逻辑分区在 super 分区内部动态划分,scatter 只负责 super、boot、vendor 这类物理槽位。这种平台上再用"改大 system 分区"的老思路基本无效,正确入口是调整 super 内部各逻辑分区的分配,改法完全不同。
4.3 用官方烧录工具做一次最小烧录
首次验证改动时不建议全量烧录,时间和出错面都太大。我一般只用官方烧录工具,勾选 boot 和 vendor 两个分区做最小烧录,配合 scatter 文件完成下载。这样如果新编译的 boot.img 有问题,范围已经被锁死在小改动里。真正需要全量烧录的场景是换了 preloader 或改了分区表,这时候只烧部分镜像反而会因为版本错配出现让人误判的故障。烧录过程中如果下载停在某个分区的中途,优先怀疑 scatter 地址被改坏,或者镜像实际大小超过了声明的分区大小,先回滚改动再试,不要反复拔插设备,那是最后才用的手段。
5. MTK 平台移植的 5 个常见问题:不开机、panic、分区不足
5.1 现象:插电反复重启,串口只有零散输出
原因:八成是 preloader 与烧录的分区布局不匹配,或者 scatter 里地址被改过,preloader 加载不到合法的下一级镜像;少数情况是 PMIC 初始化时序问题,供电没稳定就跳转。解决:先用官方烧录工具重新全量烧录同一版本的 preloader 加全部镜像,排除镜像断层;然后核对 scatter 的 preloader 分区地址和大小是否与交付默认值一致。串口完全无输出的情况则不要先怀疑软件,量一下电源和时钟,确认 PMIC 起来了再动代码。
5.2 现象:内核启动到一半 panic,报 Unable to handle kernel paging request
原因:多数是 dts 里寄存器基地址或时钟编号与芯片实际不一致,驱动 probe 时访问了没有使能的外设地址;也有的是改了 dts 但没编进 boot.img,跑的还是旧设备树。解决:先把 panic 信息里的 PC 地址拿出来,用 vmlinux 反查函数名,确认是哪个驱动;再和厂商参考板卡的 dts 做对比。前提是编内核时开了 DEBUG_INFO,否则只能看到符号名,看不到行号。
# 把 panic 的 PC 地址翻译成函数名和源码位置 # vmlinux 必须取自同一次编译,否则地址对不上 aarch64-linux-gnu-addr2line -e vmlinux -f 0xffffffc0103xxxxx参数说明:-e 指定内核映像文件,-f 表示同时输出函数名,最后的十六进制地址就是 log 里 panic 行的 PC 值。如果翻译结果落在某个 dts 相关驱动的 probe 函数里,基本可以断定是设备树配置错误,重点查该外设的寄存器地址、时钟号和中断号三项。
5.3 现象:i2c 探测不到 PMIC 或充电芯片
原因:最常见的是引脚被 pinctrl 复用成了 GPIO 或其它外设功能,i2c 总线物理上没通;其次是驱动里写死的 i2c 总线序号和实际硬件挂载的控制器不符。解决:先用 i2cdetect -y 扫描对应总线地址,确认设备是否在总线上;再看内核的 pinctrl 调试节点,确认引脚复用状态。修正 dts 中的 pinctrl 配置后在改过的内核上重新验证,别顺手把地址改错,那会把问题引向更深的坑。
5.4 现象:OTA 升级失败,提示 vendor 分区空间不足
原因:预装库和资源文件把 vendor 分区塞满,但分区表没有同步扩大;userdebug 包里的调试符号和多余预装也会明显增大镜像。解决:先对比 vendor.img 实际大小和 scatter 里声明的分区大小,确认是溢出还是余量不足。静态分区平台按 4.2 的三处同步方式调大 vendor;动态分区平台则调整 super 内部对 vendor 的分配。同时把调试符号、无用预装清理掉,往往不动分区也能找回空间。
5.5 现象:换编译机之后,出现之前没见过的诡异报错
原因:宿主环境差异,常见是 python 版本或 jdk 版本和交付平台要求的基线不一致,也有的是 out 目录从旧机器拷贝过来后残留软链接。解决:不要在换机器后沿用旧 out 目录,直接删除重新全量编译;把编译环境收敛成固定容器镜像,版本记录写清楚。碰到只在某台机器上出现的报错,先怀疑环境,不要急着改代码。
6. 把 boot log 关键字录成一张检查表:验证一次平台改动
平台调到最后,比的不是谁会改代码,而是谁能快速确认"这次改动有没有生效"。我的习惯是把串口 boot log 里的阶段关键字抽成一张检查表,每次改完分区或 dts 都照着过一遍:
| 启动阶段 | 关注标记 | 正常后续 |
|---|---|---|
| preloader | PMIC 初始化完成 | 进入 LK 加载 |
| LK | 加载 boot 镜像 | 打出 kernel command line |
| kernel | 分区挂载完成 | 进入 init |
| init | 关键服务拉起 | 出现登录或 adb 可用 |
具体用法是:改完分区后先最小烧录 boot 和 vendor,串口抓 log,用 grep 过滤对应阶段的标记,确认推进到了预期位置再继续;如果停在某一阶段,直接按第五章的对应条目排查。这个流程把每次改动从"感觉能开机"变成"可验证地到达了某个阶段",返工率明显下降。我自己在某个物联网项目上吃过亏:图省事只改了 scatter,没同步 LK 分区表,烧了十多次才定位到问题,之后这张检查表就一直贴在工位旁边。希望帮到你。
本文还有配套的精品资源,点击获取