
T113-S3这块芯片我断断续续折腾了小半年从最开始的uboot串口乱码到内核启动卡死在driver probe再到Buildroot编译到一半报错退出几乎是每个环节都踩过一遍坑。做完整个项目回头再看发现网上关于这颗芯片的资料虽然不少但大多停留在“怎么编译”的层面真正把uboot、内核、根文件系统串起来讲全栈构建、并且把排错思路讲清楚的真不多。这篇东西就是冲着补这个缺口来的。如果你手上正好有T113-S3的核心板或者准备在类似的全志/晶晨方案上做Linux产品想搞明白从芯片上电到应用跑起来这中间每一层到底发生了什么、出了问题怎么定位那这篇文章应该能帮你省下不少时间。我会按照实际开发的顺序从uboot讲起再到内核和设备树最后落到Buildroot构建根文件系统每一段都配上我在实测中遇到的坑和排查思路。1. 项目整体设计与方案选型1.1 全栈构建到底是在构建什么先把概念理清楚。嵌入式Linux的全栈开发不是说你会写几个应用就完了而是从芯片复位后第一条指令开始把每一层软件都搭起来。对于T113-S3这种SoC完整启动链路是这样的Boot ROM芯片内部固化上电后自动执行负责从SD卡/eMMC/SPI Nor等介质加载SPLSPLSecondary Program Loader属于uboot的一部分负责最基础的时钟、DDR初始化然后加载ATF和uboot主体ATFArm Trusted Firmware提供EL3运行时环境U-Boot proper完整引导程序负责加载内核镜像和设备树Linux内核内核解压初始化、驱动挂载、挂载根文件系统根文件系统BusyBox或者完整发行版提供init进程和应用程序运行环境T113-S3这颗芯片比较特殊的地方在于它集成了双核Cortex-A7和一个RISC-V协处理器同时芯片内部直接封装了DDR3内存。这意味着不做外部DDR颗粒选型和布线硬件设计上确实省事但软件上SPL阶段对DDR控制器的初始化参数必须和内部颗粒完全匹配否则uboot根本起不来。这也是为什么很多人拿到核心板先卡在SPL阶段的原因。1.2 为什么选Buildroot而不是Yocto或纯手动交叉编译搭建根文件系统有三条路线纯手动下载BusyBox源码交叉编译、用Buildroot、用Yocto。三者的取舍非常现实直接决定项目节奏。纯手动方式说穿了就是自己下载BusyBox、glibc、各种库源码一个个交叉编译再手动拷贝到rootfs目录。好处是每一层都透明、可控坏处是依赖关系全得自己背第一次搭建顺利的话也要两三天中间遇到哪个库的configure脚本不认交叉编译环境排查起来非常头痛。产品原型验证阶段这条路太慢。Yocto是另一个极端功能全、可定制性强、社区包多但学习曲线陡峭光是把bitbake的语法和layer机制搞明白就需要一周时间而且首次构建要下载几个G的源码包构建时间也长。如果团队里只有一两个人做BSPYocto很容易变成“构建系统占用的时间比写业务代码还多”。Buildroot恰好卡在中间。它本质是一套基于Kconfig和Makefile的自动化构建框架只需要配置好交叉编译工具链和目标架构然后勾选需要的软件包剩下的事情全部自动化下载源码、打补丁、编译、安装到rootfs目录、最终打包成镜像。整个系统构建时间大概在20到40分钟取决于软件包数量拿来量产和做产品原型都够用。我这次T113-S3项目就用的Buildroot具体版本是2023.02 LTS内核是5.4工具链用的Buildroot内置的gcc 9.2版本跑起来稳定也没发现什么兼容性问题。1.3 T113-S3的资源盘点与系统分区规划在动手编译之前先得把T113-S3的资源摸清。它集成了128MB DDR3CPU频率最高可以到1.2GHz支持RGB/LVDS/MIPI DSI显示接口、百兆以太网内部集成MAC只有MII/RMII接口引出、4路SDIO、6路UART、2路SPI、双CAN等。做低成本Linux产品比如工业HMI、智能网关、简单平板它确实够用。系统分区规划我直接给出实测可行的方案以8GB eMMC为例分区起始偏移大小内容boot08KB256KBSPL由SDK的烧录脚本写入uboot264KB1MBu-boot.bin含ATFboot1.3MB32MBext4存放kernel Image、dtb、boot.scrrootfs33MB剩余ext4Buildroot生成的rootfs这个分区的思路是boot和rootfs分离方便后续OTA升级——只更新boot分区或者只更新rootfs分区互不影响。uboot的环境变量里设置好bootcmd从boot分区读内核再挂载rootfs分区作为根文件系统。2. U-Boot构建与底层细节2.1 交叉编译工具链的选择U-Boot构建第一步是准备好交叉编译工具链。T113-S3的SDK默认用的是arm-none-eabi或者arm-linux-gnueabihf这类工具链。我自己使用的是Buildroot编译产出的工具链——也就是host目录下的arm-buildroot-linux-gnueabihf- 前缀的工具链。好处是工具链和后续内核、根文件系统完全同源避免出现glibc版本不一致导致的“应用程序加载不了”这种玄学问题。如果单独编译uboot可以不用Buildroot直接下载Linaro GCC或者ARM官方工具链。但要注意核内浮点abi必须选对。T113-S3的Cortex-A7支持硬件浮点编译时务必加上-mfloat-abihard -mfpuneon-vfpv4否则内核引导阶段fpsimd相关功能可能出问题。2.2 T113-S3的U-Boot配置与SPL/ATF协同U-Boot对全志芯片有一套非常成熟的框架T113-S3在主线U-Boot中对应sun8i平台。但用主线uboot直接编译T113-S3会比较麻烦因为T113的DDR初始化、PMIC配置等部分厂商还是以闭源方式提供。更稳妥的做法是用全志SDK里带的uboot在其基础上改。下面是我基于T113-S3 SDK的u-boot编译流程。make ARCHarm CROSS_COMPILEarm-buildroot-linux-gnueabihf- t113_i_defconfig make ARCHarm CROSS_COMPILEarm-buildroot-linux-gnueabihf- -j8这里defconfig我直接用了厂商的t113_i_defconfig没手动去调内存参数。编译产物中u-boot.bin是裸的uboot不能直接烧录需要用全志的mksunxi工具打上头部。SDK一般会提供打包脚本直接运行就能生成sunxi-spl.bin和u-boot.itb含ATF。SPL在这里扮演的角色很关键它负责最基础的DDR初始化然后把ATF和uboot proper从启动介质中读入内存。常见错误是SPL阶段串口无任何输出——这种情况八成是DDR初始化没通过或者是波特率、晶振频率的配置和实际硬件不匹配。T113-S3外部晶振默认是24MHz如果你用的核心板换成了16MHz或12MHz晶振SPL阶段串口就会完全没输出因为PLL频率全乱了。排查这类问题首选示波器测晶振引脚确认起振频率再去uboot源码里改CONFIG_SYS_CLK_FREQ。2.3 启动logo与显示初始化很多产品尤其HMI类要求上电后立刻显示logo不等内核起来。这在uboot阶段就要驱动显示控制器。T113-S3自带DE2显示引擎支持RGB和LVDS输出。全志SDK的uboot里对显示部分的初始化路径大致是board_init - sunxi_display_init - 解析disp环境变量里的LCD参数时序、分辨率、色深配置显示控制器和PLL_VIDEO最后把framebuffer内容刷到屏幕。我之前在uboot阶段加开机动画其实是静态logo发现一个非常隐蔽的坑如果内核的设备树里lcd的时序和uboot环境变量里的disp_mode参数不一致uboot下logo显示正常但内核起来后会花屏几秒钟然后才恢复正常。这是因为内核的显示驱动会重新初始化面板如果时序参数不对面板会进入错误的时钟训练状态。排查了很久才意识到是设备树和uboot的display模式没对齐。经验就是uboot里设置的分辨率和刷新率要原封不动地同步到内核设备树里。2.4 分区表与boot.scrU-Boot的环境变量、分区表、引导脚本是三大件。分区表我用的是mtdparts对于eMMC则用mmc parts在uboot命令行可以用mmc part list mmc 1查询分区列表。要确保uboot里的分区起始偏移和烧录脚本一致否则烧进去内核和rootfs都对不上。boot.scr是uboot引导内核的脚本里面写的是load内核镜像到内存、加载dtb到内存、然后bootz执行的指令。我用的boot.scr内容大致如下setenv bootargs consolettyS0,115200 root/dev/mmcblk1p4 rootwait rw load mmc 1:3 0x40200000 boot/Image load mmc 1:3 0x40800000 boot/sun8i-t113.dtb bootz 0x40200000 - 0x40800000注意load mmc 1:3表示mmc设备1eMMC的第3个分区boot分区0x40200000和0x40800000这两个内存地址是经验值只要不和其他阶段的内存布局冲突就行考虑到ATF占据了高位内存这里选择在128MB内存中间偏前的区域是安全的。3. Linux内核配置与设备树调试3.1 内核裁剪与关键配置项T113-S3的内核我用的5.4版本。内核配置基于全志sun8i平台的defconfig打上T113相关补丁后主要关注这几个配置组内核必须选中Device Tree support、ARM EABI、内核大小压缩选项显示驱动相关CONFIG_DRM_SUN4I、CONFIG_DRM_SUN8I_UI/VI、CONFIG_DRM_PANEL_SIMPLE以太网CONFIG_SUN8I_EMAC内部百兆MAC的驱动CAN总线CONFIG_CAN_SUN4IT113的CAN控制器驱动电源管理CONFIG_CPUFREQ_DT用于DVFS调频一个容易踩的坑是CONFIG_CMDLINE_FORCE。全志SDK内核默认会把cmdline写成固定值如果开了这个选项uboot传入的bootargs会被覆盖结果就是root参数失效内核起不来。我一般把CONFIG_CMDLINE_FORCE关掉让uboot的bootargs生效。内核编译命令make ARCHarm CROSS_COMPILEarm-buildroot-linux-gnueabihf- sunxi_defconfig make ARCHarm CROSS_COMPILEarm-buildroot-linux-gnueabihf- -j8生成的内核是arch/arm/boot/zImage如果开了CONFIG_ARM_APPENDED_DTB则是Image看具体配置。前面boot.scr里load的是Image对应的是未压缩的vmlinux或者开了XZ压缩的内核映像是Image.gz加载时需先解压。我习惯直接编译出Image省掉解压步骤只是镜像文件大一点约8MB对eMMC来说无所谓。3.2 设备树的编写与修改设备树是整个BSP里改动最频繁的部分。T113-S3的dtsi在SDK里已经定义好了CPU、内存、中断控制器、GPIO控制器等基础节点我们需要修改的是板级dts文件主要关注chosen节点设置stdout-path为serial0memory节点reg 0x40000000 0x08000000128MB内存的起始地址和大小串口节点确认uart0的pinctrl引脚配置以太网节点配置phy-mode、phy地址、复位GPIO和时钟源LCD节点timing的clock-frequency、hactive、vactive、hfront-porch等参数我遇到过最典型的问题内核启动串口完全没输出但uboot是正常的。一般来说uboot正常说明硬件没大问题那问题就出在内核早期阶段很可能是dts里chosen节点的stdout-path写错了或者对应uart节点被statusdisabled。如果串口没有输出先在uboot命令行用printenv确认bootargs里的console参数正确再用fdtget确认dtb里的chosen节点存在。我就在这上面浪费了不少时间。3.3 常见的内核启动失败排查内核启动卡住的点五花八门我整理一个排查顺序启动卡在“Starting kernel ...”通常是内核无法解压或者dtb地址错了确认load到内存的地址和bootz参数一致。卡在“smp: Bringing up secondary CPUs”多核启动有问题一般是CPU release地址没对或者是ATF版本和内核的PSCI接口不兼容。卡在某个驱动probe比如lcd或者emac一般是dts节点里reg或者interrupt属性写错导致驱动请求资源失败。打开内核的dyndbg可以快速定位。另外一个非常有效的排查手段是开启内核早期的printk在bootargs里加一个earlyprintk这样即使后面驱动崩了也能看到内核日志最后一条到底在哪。这比盲猜强太多。4. Buildroot构建与常见问题排查4.1 Buildroot配置的完整流程Buildroot 是整套全栈构建里最省心也最坑爹的一环。省心在于它帮你处理了大部分依赖坑爹在于一旦它下载源码失败或者某个包编译不过你需要理解它那套包管理规则才能快速解决。我是这样配置的make menuconfigTarget options里选择ARM cortex-A7、EABIhf。Toolchain类型选Buildroot internal工具链C库选glibc。System configuration里设置主机名、root密码、启动脚本路径。Target packages里按需勾选busybox、dropbearSSH、i2c-tools、can-utils、strace、gdbserver这些调试利器。配置完成后直接make在等待编译完成的过程中我一般顺便把rootfs的overlay准备一下——Buildroot支持BR2_ROOTFS_OVERLAY把需要预置到根文件系统的自定义文件放到指定目录编译时自动拷贝进去。这个功能非常好用比如放一个rc.local脚本、预装应用二进制、配置文件等。构建产物在output/images/目录下最关键的是rootfs.ext4和sdcard.img如果配置了genimage。4.2 编译中高频错误与修复思路Buildroot编译期的问题五花八门但有几个高频坑可以说说。第一个坑软件包源码下载失败。Buildroot从上游官网拉源码包国内网络环境下这些下载经常超时或者TLS握手失败。我的做法是修改BR2_PRIMARY_SITE和BR2_BACKUP_SITE指向我本地搭建的源码镜像服务器也可以配置BR2_DL_DIR把下载目录固定到本地已有缓存的路径。在CI环境或多人协作时这个DL_DIR共享非常方便。第二个坑某个包编译报错比如openssl的汇编代码在ARM上不认识某些指令。多数情况是工具链版本和包版本不匹配。解决办法有两个方向要么在menuconfig里升级包版本要么给包打补丁。Buildroot的补丁机制是把补丁文件放到package/xxx/目录下名字带序号即可它会自动应用。我一般在正式项目里锁定稳定版本并把自己打的补丁作为v2系列放进去方便以后升级。第三个坑编译到最后生成rootfs时提示“No space left on device”。这不是磁盘满了而是Buildroot用临时文件系统做rootfs的makedev时inode耗尽解决办法是加大tmpfs空间直接挂载参数加size4G。4.3 运行时问题文件系统报错与启动卡死Buildroot做完镜像烧进去之后运行时问题才叫人抓狂。我遇到过一个诡异现象rootfs前几次启动正常但重启几次后ext4文件系统报错文件损坏。排查下来是ext4的delayed allocation特性在突然掉电时容易丢数据对于工业设备这种可能随时断电的场景建议把rootfs做成只读squashfs数据分区单独挂载可写。这是产品化时必须考虑的问题。还有一类问题Buildroot启动后卡在“Waiting for root device”。这通常是内核没有挂载根文件系统成功要么是root参数指定的分区不对要么是内核缺少对应的文件系统驱动比如没编入ext4支持。先在内核config里确认CONFIG_EXT4_FSy再在uboot命令行确认mmc设备号正确。T113-S3的eMMC控制器在Linux里一般是mmcblk1SD卡是mmcblk0别搞混了。调试这类运行期问题我通常用nfsroot方式启动构建完rootfs后不解包直接在开发机上搭一个NFS共享目录把Buildroot的target目录用NFS导出内核bootargs改成root/dev/nfs nfsroot192.168.1.100:/t113_target。这样改文件系统内容后重启即生效不需要反复烧录迭代效率高很多。等调试完毕再打包成ext4或squashfs烧入设备。4.4 一个完整的异常定位实例说一个我印象最深的排查过程。有次用户反馈设备偶发死机串口打印停在“VFS: Mounted root (ext4 filesystem) read-only”然后就没有任何内核日志了。这个现象很像是rootfs只读挂载导致的用户空间初始化失败。我的排查手段是从两个方向同时进行的。一方面在Buildroot的启动脚本里加/sbin/init前的延迟和日志输出确认用户空间程序能不能正常启动另一方面在内核启动参数里加panic-1让系统在内核panic后重启而不是停在那里。最终定位到问题是内核在ext4挂载后尝试执行init时initramfs检测和rootfs的混乱导致的。具体原因是我在Buildroot里同时开了initramfs和内嵌rootfsBuildroot生成的镜像里包含了两个rootfs启动时内核优先使用initramfs——也就是那8MB的内存文件系统它里面缺少init程序和动态链接器所以起不来。解决办法是Buildroot配置里只保留一种rootfs方式不叠加initramfs。这个案例给我一个很大的启发排查问题时多想想启动链路里是否存在“多层软件叠在一起”的情况并且不要忽视Buildroot配置项之间的互斥关系。类似的情况还有BR2_TARGET_ROOTFS_CPIO和BR2_TARGET_ROOTFS_EXT2同时打开的冲突很多人会踩到。5. 磁盘布局与量产烧录的经验5.1 用genimage生成完整烧录镜像Buildroot内置的genimage工具可以很轻松地生成sdcard.img这种完整烧录镜像。我在Buildroot的board目录下放一个genimage.cfg把boot分区、rootfs分区、uboot镜像和SPL按规划好的偏移拼成一个裸镜像。出来的sdcard.img直接可以用dd烧录sudo dd ifoutput/images/sdcard.img of/dev/sdb bs1M convfsync量产时如果不想让每一台设备都去dd整个镜像可以先烧录一份镜像到eMMC然后用dd把整张eMMC导出为.img再批量用烧录器克隆。这种方式在产线上很常见效率也高但要注意不同批次的eMMC容量可能略有差异克隆前最好统一硬件批次。5.2 SPI Nor与eMMC之间的切换T113-S3支持从SPI Nor、SD卡、eMMC多个介质启动通过芯片的BOOT引脚选择。我在核心板上默认从eMMC启动但开发阶段经常要从SD卡启动刷系统方便回退。这里踩过一个坑SPI Nor flash如果是空的而启动引脚又配置成优先从SPI Nor启动那么即使eMMC里有系统板子也起不来。所以用SD卡刷机时务必确认启动脚位拨到了SD卡优先的位置。开发阶段的“三启动”顺序我实测下来最省心SD卡用于烧录可随时拔掉、eMMC正式系统、SPI Nor放一个最小的uboot救援系统只做网络加载或者串口命令行。这样无论哪个启动介质坏了都有办法用另一个介质把系统救回来。5.3 量产时的校验与设备唯一性量产阶段我习惯在Buildroot里加一个firstboot脚本系统首次启动时读取eMMC的CID序列号结合MAC地址生成一个设备唯一ID写入/etc/device-id再生成SSH host key。这样每台设备出厂状态完全一致但网络身份彼此独立不至于出现两台设备SSH host key相同的安全隐患。这个过程如果在Buildroot构建时做就会导致所有设备一模一样一定要做成“首次启动时生成”的运行时逻辑。这也是很多做网关产品的人容易忽略的生产细节。6. 调优与后续扩展全栈跑通只是第一步T113-S3这种资源有限的芯片调优才是产品化的关键。我主要做了三个方向的优化第一降低内核镜像体积。用CONFIG_CC_OPTIMIZE_FOR_SIZE重新编译内核再把不需要的驱动全部编成模块或者直接去掉内核从8MB降到5MB左右启动时间能压缩几百毫秒。第二缩短uboot阶段的启动延迟。uboot默认会等待用户按键进入命令行这个等待时间在量产固件里要改成0否则每次开机都会停顿。T113-S3的uboot里CONFIG_BOOTDELAY默认是2秒我改成-1直接跳过等待。第三构建rootfs时去掉不需要的时区和locale数据再把BusyBox按需裁剪最终rootfs控制在6MB左右加上内核和uboot整个系统镜像不到20MB。这个体积对eMMC容量紧张的产品非常友好。如果后续要做OTA升级可以基于Buildroot的fwup或者swupdate来做双分区A/B切换。T113-S3的eMMC容量做双rootfs分区非常富余A/B方案虽然占空间但升级失败可以自动回滚对远程设备来说多花这点空间完全值得。最后说一个我个人体会最深的事全栈构建最忌讳的就是“照着手册敲命令敲通了但不知道为什么”。T113-S3的SDK能让你很快出一个可以启动的固件但如果你不去理解boot0和ATF的关系、不去理解uboot的device tree overlay、不去理解Buildroot的package依赖机制一旦出了手册之外的问题就完全抓瞎。我写这篇文章的初衷也是希望读者能把每一步都吃透遇到问题时有自己的判断力和排查路径而不是到处找别人踩过的坑。如果这篇能帮你少踩五个坑、少熬三个夜那我这半年折腾得也算值了。