ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

嵌入式Linux开发必知:BSP板级支持包从Bootloader到设备树全解析

嵌入式Linux开发必知:BSP板级支持包从Bootloader到设备树全解析 开发板拿到手第一件事永远是翻资料、找镜像、烧系统烧完开机看到串口里滚出完整的Linux启动日志才觉得这块板子活了。但很少有人停下来问一句这套能让板子跑起来的代码究竟是谁写的它为什么长这样答案就是BSP——Board Support Package板级支持包。我在嵌入式这行泡了十多年带过不少新人发现大家对BSP的理解普遍停留在厂商给的一坨代码这个层面。有人以为BSP就是内核源码有人以为BSP就是驱动合集还有人把BSP和交叉编译工具链混为一谈。这些理解都有点偏。这篇文章我想把BSP这件事从头到尾捋一遍它到底是什么、拆开之后有哪些零件、日常工作里怎么跟它打交道以及维护一个BSP最容易被坑的地方在哪。适合刚入行做嵌入式Linux的工程师、准备从裸机开发转Linux的朋友以及那些拿到厂商BSP包却不知道从哪下手的人。1. BSP的本质为什么Linux内核不能直接跑在每一块板子上先纠正一个最根深蒂固的误解Linux内核本身不认识你的开发板。内核确实支持海量的CPU架构ARM、RISC-V、x86底下还有一堆具体的SoC平台。但支持CPU和支持你的板子之间有巨大的鸿沟。一块开发板不只是SoC还有DDR颗粒、eMMC、SD卡座、以太网PHY芯片、WiFi模组、LCD屏幕接口、音频Codec、各种传感器——这些器件怎么组合在一起的板子设计者说了算内核并不知情。市面上没有两块完全相同的板子。同样是全志的H3芯片友善之臂的NanoPi和香橙派的Orange Pi内存大小、网口位置、LED引脚、供电时序都可能不一样。内核不可能为每一块这样的板子内置一个配置否则内核源码里光板子配置文件就得几万个。所以BSP出现了。它的核心逻辑非常朴素你来告诉我你的板子长什么样我给你一小撮代码和配置让你这个特定的软硬件组合能顺利启动。行业内有个很形象的类比Linux内核是一套标准的精装修方案而你的板子是一套毛坯房。BSP就是装修队它负责根据你的户型原理图、你的预算成本要求、你的生活习惯产品功能去接水管、铺电线、定制柜子。内核提供的是通用能力BSP解决的是具体落地。有一个关键观念要转过来——BSP不是Linux的一部分恰恰相反Linux内核只是BSP里的一个组件。一个完整的BSP包除了内核之外还包含Bootloader、设备树、根文件系统、交叉编译工具链、烧录脚本甚至还包括硬件原理图和勘误表。从代码量的角度讲内核只是其中相对显眼的一块。再深一层BSP这个词在不同语境下含义也不完全一致。芯片原厂比如NXP、Rockchip、全志说的BSP指的是官方评估板的各种板级代码是以原厂EVB为基准做的。方案商说的BSP是在原厂基础上针对自己产品重新裁剪适配的内容经常讲定制BSP。到了硬件工程师和驱动工程师协作的场景里BSP又常被用来泛指除了应用层以外的所有底层软件。不管语义怎么变BSP承担的核心职责不会变把硬件能力接进操作系统。没有BSP内核跑不起来BSP不完整板子的某些功能就形同虚设。2. 拆开一个典型的Linux BSP从按下电源键到用户态都包含什么BSP听起来抽象拆开看就很具体了。随便拿一个Rockchip、NXP或全志的官方BSP包解压之后大概长这样bsp/ ├── u-boot/ # Bootloader源码 ├── kernel/ # 内核源码通常是厂商fork的版本 ├── device-tree/ # 设备树源码或直接在kernel里 ├── buildroot/ # 根文件系统构建系统 ├── toolchain/ # 交叉编译工具链 ├── rkbin/ # 芯片厂商的闭源二进制、ddr初始化等 ├── firmware/ # 固件文件比如WiFi、GPU的固件 ├── output/ # 编译产物烧录镜像都在这里 ├── docs/ # 开发文档、硬件手册、勘误表 ├── tools/ # 烧录工具、打包脚本、密钥工具 └── Makefile # 顶层构建入口这是我见过最典型的形态。不同厂商会有差异全志的BSP喜欢用lichee作为顶层目录名NXP的官方Yocto BSP整个是基于Yocto工程的树莓派则是把固件、内核、rootfs分散在官方仓库里。但万变不离其宗底下几个板块是必须的。2.1 Bootloader硬件的第一个管家板子上电之后CPU最先执行的并不是Linux内核而是Bootloader。在ARM平台现在基本是U-Boot一统天下部分芯片比如高通会用类似ABL的专有Bootloader。Bootloader的活儿其实非常重它在操作系统接管之前要完成初始化DDR内存、配置时钟、初始化串口这样你才能看到日志、加载内核镜像到内存、设置启动参数然后把控制权交给内核。DDR初始化这步特别容易被忽略。DDR的时序参数和你的PCB布线、内存颗粒型号强相关厂商会在Bootloader里放一个针对自家板子调好的DDR初始化二进制这个通常是闭源的。很多人改硬件换了大容量的内存颗粒结果系统起不来多半就是这里出了问题。# 查看U-Boot版本和编译信息是判断BSP来源的第一步 # 在U-Boot命令行里执行 version2.2 内核被改造过的Linux不只是官方主线BSP里的内核目录绝对不是你从kernel.org拉下来那个干干净净的主线。它背后挂着厂商几百个patch有的是把芯片平台代码合进去的有的是把硬件驱动补上的有的是修了主线还没合入的Bug。这一点非常现实。Linux主线代码讲究通用性而BSP讲究的是特定板卡能跑。厂商fork一份内核再打补丁是嵌入式行业公开的玩法。哪怕一颗芯片已经进入主线了厂商仍然会维护自己的内核分支因为在产品生命周期里客户不会频繁更新内核版本稳定和可维护才是第一位。BSP里的内核目录看久了你会发现它不只有Kernel本身的代码还经常混着厂商自己加的驱动模块、测试工具以及一些不太规范但很实用的补丁。2.3 设备树板子硬件的说明书设备树Device Tree简单理解就是一份描述板子硬件资源的表格。U-Boot启动内核时会把这份表格的二进制形式DTBDevice Tree Blob传给内核内核根据它来知道板子上有哪些设备、地址是多少、中断号是多少。举个例子板子上有一颗I2C加速度传感器在设备树里这样描述i2c1 { status okay; accel1c { compatible st,lsm6ds3; reg 0x1c; interrupt-parent gpio1; interrupts 12 IRQ_TYPE_LEVEL_HIGH; }; };它告诉内核三件事I2C1控制器要启用、地址0x1c挂了一颗LSM6DS3传感器、这颗传感器的中断接在GPIO1_12上。至于LSM6DS3这个具体芯片怎么操作那是驱动的事情。设备树只负责描叙世界不负责执行逻辑。2.4 根文件系统让Linux从内核变成系统的最后一环内核启动之后要挂载根文件系统才能跑起init进程进而启动各种服务最终到达shell或者你的应用。BSP里的rootfs通常由Buildroot或Yocto构建。Buildroot适合做小体积、精简的系统一个最小的rootfs可以压到几兆跑起来只有busybox提供的一百多个命令。Yocto更复杂也更强大适合做QA要求高、需要包管理的产品级系统但学习曲线相当陡而且是所有BSP组件里最耗磁盘和CPU的一项。提示真正开发量产产品时rootfs就是你是产品的地基。坑大点深度不够后面所有应用层都会被拖累。这里强烈建议至少懂一样构建工具不然连系统里缺少一个共享库这种问题都排查不了。2.5 烧录工具和脚本厂商生产力最容易忽视的部分很多人拿到BSP就盯着源码文件夹把烧录工具当空气。我个人的体会是烧录工具才体现厂商工程化水平。好的烧录工具应该支持工厂批量化烧录、分区镜像单独烧录、加密签名、量产校验稳定不挑机器。差的工具各种玄学掉盘、USB识别不到。# 用rkdeveloptool烧录Rockchip板子的常见脚本逻辑 cd output rkdeveloptool db rk3588_loader.bin rkdeveloptool wl 0 system.img rkdeveloptool rd这个脚本的本质就是加载Loader、把分区镜像写到eMMC指定偏移最后复位板子。别小看这一步量产产线上的烧录工位玩的就是这一套。工欲善其事必先利其器作为底层工程师学会读懂厂商烧录脚本会帮你省掉大量为什么我板子起不来的排查时间因为很多启动问题根子就在分区表配错了。3. 内核、设备树、驱动BSP工程里最容易混淆的三层关系我面试过不少候选人问板子上有一颗新的USB转串口芯片你要让它在Linux下工作改哪里很多人答写驱动。实际上大概率只需要改设备树把compatible字段指向对应的控制器节点如果控制器本身不支持才需要写驱动。这个是不是需要写驱动的判断就是区分BSP新手和老手的分水岭。3.1 三层各自管的事不一样BSP里的软件工作可以严格拆成三层内核平台代码管理SoC内部的控制器和总线机制比如USB控制器IP怎么初始化、时钟树怎么配、中断控制器怎么工作。这一层基本由芯片原厂维护一般用户不需要动。设备树描述板级资源。CPU是同一个但板子上的网卡地址、LED引脚、I2C总线上挂什么芯片这些是在设备树里描述的。驱动负责操作具体的外设芯片。驱动通过标准接口如I2C、SPI、PCIe、USB和控制器打交道完成对具体芯片的读写控制。用盖房子来理解内核平台代码是整个小区的配电房和水厂设备树是你家的户型图、开关位置、灯具型号驱动是具体的电工师傅——你知道这个灯是智能灯、要怎么接线。配电房不用你自己建芯片原厂建好了但你想在书桌边加一个插座你需要在户型图上标位置改设备树然后让电工来干活驱动可能是现成的也可能是你写的。3.2 一个LED灯背后完整的BSP工作链路以在板子上点亮一颗用户LED为例看BSP三层怎么协作第一步看原理图。LED接在哪个GPIO上高电平点亮还是低电平点亮。假设查到是GPIO3、引脚7高电平点亮。第二步改设备树。找到GPIO控制器节点把LED子节点加上。这里经常用到gpio-leds这个通用驱动leds { compatible gpio-leds; user_led { label user-led; gpios gpio3 7 GPIO_ACTIVE_HIGH; default-state off; linux,default-trigger none; }; };第三步不用改内核代码。gpio-leds是内核已有驱动它会在/proc或/sys下导出控制接口。系统起来之后echo 1 /sys/class/leds/user-led/brightnessLED就亮了。这个例子虽然简单但说明了BSP工作的真相大部分改动发生在设备树层面而不是在写驱动。写一个新驱动的前提是这个硬件在内核和厂商代码里都不存在对应支持。对于很多量产的物联网设备而言90%的BSP工作集中在配置和移植不到10%才是真正的写驱动。3.3 认知误区BSP工程师 写驱动工程师这是刚入行时最容易产生的错误预期。实际项目里BSP工程师更多时间花在翻原理图对引脚、配置设备树、分析启动日志、改内核config、裁剪rootfs、解决系统启动兼容性问题、和硬件工程师吵架不是——写驱动只是其中一项而已。真正需要写驱动的场景往往出现在新传感器首次使用、新无线模组没现成代码、某些专用的外设芯片。这要求驱动工程师对内核的接口模型很熟知道platform driver怎么注册、中断子系统怎么用、DMA怎么申请。4. 拿到一块新板子怎么把BSP跑起来从零开始的实操路径不管你是新手还是老手拿到一份完全陌生的BSP最高效的路径其实非常固定。我每次到新公司接触新平台都是按这个顺序走。4.1 第一步不写任何代码先把原厂BSP完整编译一遍这是最反直觉但最重要的一步。BSP是一个完整系统而不是源码集合在没有完整编译通过之前任何代码改动都是在沙子上盖楼。厂商给的README里一般有编译步骤照着来# 以某个典型BSP为例 cd bsp source build/envsetup.sh lunch evb_board-userdebug make -j$(nproc)这一步能暴露你机器环境的全部问题有没有装必要的库、工具链版本对不对、make版本够不够新、磁盘空间是否充足、网络能不能拉取依赖。把这一步跑通后面的工作才有基础。至于交叉编译工具链正规BSP包一般直接附带了或者通过脚本自动下载。不建议自己到网上去随便找一套同架构工具链配进去同一个架构不同版本的工具链编出来的内核都可能翻车。4.2 第二步烧录原厂没动过的镜像验证板子硬件编译会产生一堆镜像包括Bootloader、内核、rootfs。用厂商的烧录工具全部烧进去确保板子能从eMMC或SD卡正常启动到shell。这同时也在验证板子硬件本身没有大的问题。启动之后跑几个命令确认基础功能正常uname -a # 确认内核版本 dmesg | grep -i error # 看启动日志有没有异常 ls /sys/class/gpio # 确认GPIO子系统注册了 ifconfig -a # 看网络接口枚举情况如果这一步出现起不来的情况优先怀疑烧录姿势不对、分区表没选对、串口波特率配错、电源供电不足。先别怀疑BSP代码有问题——厂商的BSP经过大量测试出问题的概率远低于你自己操作出错。4.3 第三步修改设备树点亮第一颗LED整个BSP里性价比最高的练手任务就是点亮LED。它几乎不需要代码基础但能把Devicetree的整个流程走一遍修改源码、编译DTS、打包镜像、烧录、启动验证。具体操作是在设备树源码里找到LED相关的部分或者自己加一个节点参照上一节那个例子然后编译验证。这一步你会经历完整的改-编-烧-看循环这是BSP开发的日常节奏越早适应越好。4.4 第四步给自己加一个最简单的驱动模块当你能改设备树并对启动流程有了基本概念之后可以尝试写一个最简的字符设备驱动用module_platform_driver注册在probe函数里把GPIO点亮的初始化和设备树里的节点匹配起来。这一步你不一定真的要用它做什么产品功能但做完之后你会对驱动如何匹配设备树节点、probe函数什么时候被调用、模块如何编译进内核或作为.ko加载这些BSP核心概念建立直观体感这是看书学不来的。4.5 这一套流程里最容易踩的坑直接修改干净的主线内核然后从主线编译烧录后发现板子起不来。厂商补丁全丢了当然起不来。改了设备树却忘了重新编译bootloader镜像导致u-boot传给内核的还是旧的DTB。用错设备树源文件。很多BSP里同一颗芯片有多个参考板配置改DTS前先确认用的是不是自己手上这块板子对应的那个dtsi。只改了设备树源码但没确认最终烧录镜像里打包的是不是更新后的DTB。建议烧完以后在板子上用ls /sys/firmware/fdt确认一下大小或者相关字符串是否存在。5. 真实项目里维护BSP必须想的几件事BSP不只是跑起来就行。一个产品从样机走到量产BSP的维护方式会决定团队后面半年的生活质量。这里分享几个真实项目里的经验。5.1 版本策略跟着厂商BSP走而不是跟着主线走很多团队喜欢追新一看到主线有新版本就想往里跳。如果你们用的是NXP/全志/Rockchip这类芯片我的强烈建议是跟着厂商发布的大版本BSP走不要自己去追Linux主线。原因很简单。厂商BSP每发一版都是经过了自家平台验证的里面已经解决了平台适配的一系列问题。你追主线一百个patch可能就有一个让WiFi驱动挂掉还有一个让你的GPU驱动编不过。查这种问题的成本远远高于一开始跟原厂版本。当然产品有强安全需求、或者需要内核新特性的情况下另说但这种升级一定要当成项目来做提前留足测试时间。BSP升级不是我今天换个内核就行它影响的是整个系统的行为。5.2 补丁管理所有的BSP改动都要有硬件的为什么团队里最怕的事情是换了人之后维护者面对一堆patch不知道每一个到底是为什么打的。我自己的习惯是每一个补丁的commit message里必须写清楚硬件发生了什么变化哪颗芯片换型号了、哪个引脚重定义了、为什么现在的实现不够用、这个补丁测试了什么场景。原本的USB Hub芯片U1在量产板上换成了另一家型号 新的芯片在复位时序上和旧的不一致旧的是低电平复位新的是高电平复位 导致部分板卡在低温环境下枚举失败。 此补丁将GPIO1_15的复位极性在设备树中修正为GPIO_ACTIVE_HIGH 经高低温测试通过。这种补丁描述三个月后你自己回来看也能快速进入状态。没有背景信息的补丁就是定时炸弹。5.3 设备树是硬件配置中心改动要有纪律设备树文件里面最容易出现的一个问题是为了临时调试一个东西把某个状态从disabled改成okay忘了改回来结果产品发售的固件里莫名其妙多开了一个没用的I2C控制器不仅耗电还可能冲突。建议在团队里立一个规矩设备树的状态和属性必须有理由且过Review。BSP阶段大家图省事少做的纪律量产阶段会百倍还回来。5.4 要不要把改动推回上游有些团队在完成了产品之后想把自研的驱动补丁推回Linux主线这听起来政治正确实际执行要谨慎判断。把干净、通用、设计良好的驱动推回主线长期看肯定能降低维护成本——至少你后续更新内核时不用自己重新打patch。但推上去意味着你要接受社区review标准你得把代码写到通用而不是只要我的板子能用就行。很多内部驱动的写法比如硬编码某个GPIO、把某个时序写死成一个固定值在社区是过不了审的。如果你的驱动改动非常产品化、非常板级留在内部反而更省事。5.5 量产阶段BSP常见的坑问题根因建议同样的镜像某些板子起不来板子硬件生产差异、DDR颗粒批次不同启动时加入DDR老化测试引导阶段反复验证温度变化导致启动失败Bootloader驱动对温度和电压敏感要求硬件提供高低温下的时序余量测试偶发USB枚举失败USB PHY配置阈值不对保留串口日志必要时抓PHY寄存器对比升级系统后WiFi信号变弱WiFi固件版本和电源管理策略不匹配重新回归射频测试不要仅做功能测试EMMC寿命急剧缩短rootfs频繁写入日志调整log策略挂载tmpfs减少写放大BSP的维护工作到量产阶段会越来越敏感。软件层面看着没问题但硬件轻微波动就会导致启动失败或异常这种玄学问题最后往往都在BSP的某段初始化时序里找到原因。6. 写在最后BSP这个领域越深越老实聊了一圈还是觉得BSP这个方向有个特点最明显干得越久越不敢说自己什么都懂。因为你永远不知道自己明天要面对的芯片原厂会在Bootloader里埋什么雷也不知道硬件工程师会在原理图里留什么惊喜。BSP不是那种能做出一鸣惊人功能的方向它的成就感来自稳定。系统稳定跑一年不出事就是最好的成绩单。如果让我给刚入行的朋友一个最务实的建议就是拿到BSP千万别先改先把厂商默认的东西跑通。你要理解它而不是急着优化它。BSP最值钱的地方恰恰是它用大量看似废话的配置把硬件的那点微妙差异给摆平了。把这些配置是怎么来的弄明白你对Linux底层和硬件结合的那层理解自然就高于身边大多数只会调应用的人了。
返回列表