作为一名嵌入式Linux开发者,我最近完成了一个颇具挑战性的项目——在正点原子的i.MX6ULL开发板上成功运行了Linux 7.0-rc4主线内核。这个过程中,我经历了从NXP官方BSP到主线内核的完整迁移,遇到了各种意想不到的问题,也积累了不少实战经验。今天,我就把这整个过程详细记录下来,希望能帮助到有同样需求的开发者。
在嵌入式领域,我们通常会使用芯片厂商提供的BSP(Board Support Package),因为它们已经针对特定硬件做了优化和适配。但这次我选择挑战主线内核,主要有以下几个原因:
注意:主线内核虽然优势明显,但对新手来说挑战较大,需要具备一定的Linux内核和设备树知识基础。
我使用的硬件平台是正点原子的i.MX6ULL开发板,主要配置如下:
软件环境方面,我选择了最新的Linux 7.0-rc4内核版本,交叉编译工具链使用ARM GNU Toolchain 15.2。
在Ubuntu 22.04 LTS上,我通过以下步骤配置编译环境:
bash复制# 安装基础依赖
sudo apt update
sudo apt install -y gcc-arm-none-eabi build-essential libncurses-dev \
bison flex libssl-dev libelf-dev bc u-boot-tools device-tree-compiler
# 下载ARM工具链
wget https://developer.arm.com/-/media/Files/downloads/gnu/15.2.rel1/binrel/arm-gnu-toolchain-15.2.rel1-x86_64-arm-none-eabi.tar.xz
tar xvf arm-gnu-toolchain-15.2.rel1-x86_64-arm-none-eabi.tar.xz
export PATH=$PATH:$(pwd)/arm-gnu-toolchain-15.2.rel1-x86_64-arm-none-eabi/bin
主线内核可以从kernel.org获取,我选择使用git克隆最新开发分支:
bash复制git clone git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
cd linux
git checkout v7.0-rc4 -b imx6ull-dev
i.MX6ULL属于ARMv7架构,我们可以使用multi_v7_defconfig作为基础配置:
bash复制make ARCH=arm CROSS_COMPILE=arm-none-eabi- multi_v7_defconfig
然后通过menuconfig进行定制化配置:
bash复制make ARCH=arm CROSS_COMPILE=arm-none-eabi- menuconfig
在配置界面中,需要特别关注以下几个选项:
NXP BSP和主线内核在显示系统架构上有根本性差异:
| 特性 | NXP BSP (6.12.x) | 主线内核 (7.0-rc4) |
|---|---|---|
| 显示框架 | Framebuffer | DRM/KMS |
| 设备树绑定 | 简单display属性 | OF graph (port/endpoint) |
| 驱动模型 | 厂商定制 | 标准DRM驱动 |
| 多显示支持 | 有限 | 完善 |
设备树的编写方式也有显著不同:
BSP风格设备树片段:
dts复制display0: display@0 {
compatible = "fsl,imx6ull-lcdif";
reg = <0x021c8000 0x4000>;
interrupts = <GIC_SPI 5 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&clks IMX6UL_CLK_LCDIF_PIX>,
<&clks IMX6UL_CLK_LCDIF_APB>;
clock-names = "pix", "axi";
status = "okay";
};
主线内核风格设备树片段:
dts复制lcdif: lcdif@021c8000 {
compatible = "fsl,imx6ul-lcdif", "fsl,imx28-lcdif";
reg = <0x021c8000 0x4000>;
interrupts = <GIC_SPI 5 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&clks IMX6UL_CLK_LCDIF_PIX>,
<&clks IMX6UL_CLK_LCDIF_APB>;
clock-names = "pix", "axi";
status = "okay";
port {
lcdif_out: endpoint {
remote-endpoint = <&panel_in>;
};
};
};
主线内核使用DRM (Direct Rendering Manager) 和 KMS (Kernel Mode Setting) 框架来管理显示输出。对于i.MX6ULL,我们需要关注以下几个组件:
完整的显示系统设备树配置包括三个部分:
dts复制&lcdif {
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_lcdif_dat
&pinctrl_lcdif_ctrl>;
status = "okay";
port {
lcdif_out: endpoint {
remote-endpoint = <&panel_in>;
};
};
};
dts复制panel {
compatible = "panel-dpi";
label = "lcd7";
status = "okay";
port {
panel_in: endpoint {
remote-endpoint = <&lcdif_out>;
};
};
panel-timing {
clock-frequency = <33000000>;
hactive = <800>;
vactive = <480>;
hfront-porch = <40>;
hback-porch = <88>;
hsync-len = <48>;
vfront-porch = <13>;
vback-porch = <32>;
vsync-len = <3>;
hsync-active = <0>;
vsync-active = <0>;
de-active = <1>;
pixelclk-active = <0>;
};
};
dts复制pinctrl_lcdif_dat: lcdifdatgrp {
fsl,pins = <
MX6UL_PAD_LCD_DATA00__LCDIF_DATA00 0x79
MX6UL_PAD_LCD_DATA01__LCDIF_DATA01 0x79
MX6UL_PAD_LCD_DATA02__LCDIF_DATA02 0x79
MX6UL_PAD_LCD_DATA03__LCDIF_DATA03 0x79
/* 省略其他数据线 */
>;
};
pinctrl_lcdif_ctrl: lcdifctrlgrp {
fsl,pins = <
MX6UL_PAD_LCD_CLK__LCDIF_CLK 0x79
MX6UL_PAD_LCD_ENABLE__LCDIF_ENABLE 0x79
MX6UL_PAD_LCD_HSYNC__LCDIF_HSYNC 0x79
MX6UL_PAD_LCD_VSYNC__LCDIF_VSYNC 0x79
>;
};
要使DRM显示系统正常工作,需要在内核中启用以下配置:
code复制CONFIG_DRM=y
CONFIG_DRM_FSL_DCU=y
CONFIG_DRM_PANEL_SIMPLE=y
CONFIG_DRM_PANEL_DPI=y
CONFIG_DRM_I2C_ADV7511=y
CONFIG_DRM_IMX_LCDIF=y
CONFIG_DRM_IMX=y
正点原子开发板使用的是GT9147电容触摸芯片,我们需要为其添加设备树节点和驱动支持。
设备树配置:
dts复制&i2c2 {
clock-frequency = <100000>;
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_i2c2>;
status = "okay";
gt9147: touchscreen@14 {
compatible = "goodix,gt9147";
reg = <0x14>;
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_tsc>;
interrupt-parent = <&gpio1>;
interrupts = <9 IRQ_TYPE_EDGE_FALLING>;
reset-gpios = <&gpio5 9 GPIO_ACTIVE_LOW>;
irq-gpios = <&gpio1 9 GPIO_ACTIVE_HIGH>;
touchscreen-size-x = <800>;
touchscreen-size-y = <480>;
touchscreen-inverted-x;
touchscreen-swapped-x-y;
};
};
需要在内核中启用以下触摸屏相关选项:
code复制CONFIG_INPUT_TOUCHSCREEN=y
CONFIG_TOUCHSCREEN_GOODIX=y
CONFIG_TOUCHSCREEN_GT9147=y
i.MX6ULL内置两个MAC控制器,通过KSZ8081 PHY芯片连接。设备树配置如下:
dts复制&fec1 {
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_enet1>;
phy-mode = "rmii";
phy-handle = <ðphy0>;
status = "okay";
mdio {
#address-cells = <1>;
#size-cells = <0>;
ethphy0: ethernet-phy@0 {
compatible = "ethernet-phy-id0022.1560", "ethernet-phy";
reg = <0>;
micrel,led-mode = <1>;
clocks = <&clks IMX6UL_CLK_ENET_REF>;
clock-names = "rmii-ref";
};
};
};
&fec2 {
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_enet2>;
phy-mode = "rmii";
phy-handle = <ðphy1>;
status = "okay";
mdio {
#address-cells = <1>;
#size-cells = <0>;
ethphy1: ethernet-phy@1 {
compatible = "ethernet-phy-id0022.1560", "ethernet-phy";
reg = <1>;
micrel,led-mode = <1>;
clocks = <&clks IMX6UL_CLK_ENET2_REF>;
clock-names = "rmii-ref";
};
};
};
网口无法连接:
网络性能差:
为了正确启动主线内核,需要在U-Boot中设置以下环境变量:
bash复制setenv bootargs 'console=ttymxc0,115200 root=/dev/mmcblk1p2 rootwait rw'
setenv bootcmd 'mmc dev 1; ext4load mmc 1:1 ${loadaddr} /boot/zImage; ext4load mmc 1:1 ${fdt_addr} /boot/imx6ull.dtb; bootz ${loadaddr} - ${fdt_addr}'
saveenv
早期printk调试:
在内核命令行添加earlyprintk参数:
code复制console=ttymxc0,115200 earlyprintk
动态调试:
启用内核动态调试功能:
bash复制echo 'file drivers/gpu/drm/imx/* +p' > /sys/kernel/debug/dynamic_debug/control
性能分析:
使用perf工具分析系统性能:
bash复制perf stat -a sleep 10
perf record -g -a sleep 10
perf report
问题现象:屏幕出现雪花、闪烁或颜色异常
可能原因及解决方案:
问题现象:系统启动后触摸屏完全不工作
排查步骤:
i2cdetect -y 1dmesg | grep goodix问题现象:网络传输速度慢,丢包严重
优化建议:
CONFIG_FSL_EDMA=ybash复制echo 4096 > /proc/sys/net/core/rmem_default
echo 16384 > /proc/sys/net/core/rmem_max
bash复制ethtool -K eth0 rx on tx on tso on gso on gro on
经过这次主线内核移植,我深刻体会到嵌入式Linux开发的复杂性和挑战性。从BSP到主线内核的迁移不仅仅是简单的驱动替换,而是整个架构思维的转变。
几点特别值得分享的经验:
对于想要尝试主线内核移植的开发者,我的建议是:
这个项目已经开源在GitHub上,包含了完整的移植文档和配置文件。如果你在移植过程中遇到问题,欢迎在项目issue中讨论。嵌入式Linux的世界很大,我们一起探索前进。