IMX6ULL2025年最新部署方案:最新的UBootLinux和Rootfs部署正点原子Alpha开发板指南_imx6ull 6.12
正点原子Alpha IMX6ULL开发板2025年最新部署方案:基于Ubuntu24.04平台开发,部署最新的UBoot/Linux和BusyBox Rootfs部署指南
前言
笔者实在绷不住比较旧的方案了,广义流行的方案是使用2016年发布的Uboot来引导Linux4.1.15,配合2018年发布的BusyBox。但是兄弟们,今年已经2025年了,还在用老登东西学习,学不到什么太多的,比如说,现代UBoot已经跟随潮流,将使用源码的硬件描述的传统风格转向了使用Device Tree设备树方案的硬件信息描述了。Linux的内核也发生了不少新的特性,非常值得我们去进行探索。因此,笔者花费了非常长的时间(一天基本没吃没喝高强度部署),探索出了如下的解决方案:
UBoot:nxp-imx/uboot-imx: i.MX U-Boot的2024年4月份公布的分支:If_v2024.04分支
Linux:nxp-imx/linux-imx: i.MX Linux kernel:If_6.12.y分支,实际测试的时候笔者发现版本号是6.12.3
Rootfs: 笔者使用的是busybox,版本号1.36.0,当然你可以选择最新的,但是众所周知,BusyBox的bugs非常多,对于各位若是使用的现代内核(6.8+),请不要选择版本过低的BusyBox,至少,1.32以下的不行,会出现一大堆未定义错误,因为他们不少引用了过时的符号:https://www.busybox.net/downloads/busybox-1.36.0.tar.bz2
笔者需要说明的是,我们的流程是这样的,按照我们的板子的时序,是按照UBoot -> Linux -> (NFS mounted) Rootfs
的顺序,这里面的桥接非常多,当然,笔者也会穿插如果你决定妥协方案(部分新部分旧的事情)的时候,如何做对应的修改。
笔者的操作平台分成了三个部分——
-
我的UBuntu实际物理机(是的,我就没用虚拟机,我是物理笔记本上刷了Arch和Ubuntu双系统,这里笔者默认的是UBuntu,我后面会仔细的说明为什么是Ubuntu)。
-
Windows:实际上是笔者习惯了Windows的XShell和方便的XFTP,板子的串口也是介入了Windows,当然看官朋友没必要整的如此的复杂,但是你需要注意的是——Windows是必须的!我们中间有一个必须的步骤是校验DDR3,辅助我们的UBoot启动的,没有这一步,UBoot肯定启动不起来。所以看我的朋友如果没有Windows,请:
- Windows最小虚拟机就行了,那个DDR_Tester是最小完整可以运行的(Wine没试过,想试一下的朋友自行)
- 自行寻找DDR校验方案,这里笔者就没有进行了
-
板载IMX6ULL的正点原子Alpha2.4版本的开发板,其中,内存的方案是EMMC512MB的,最接近的配置是imx6ull-14x14-evk-emmc型号,也是后面笔者魔改的基准。笔者的板子的LCD是800x480尺寸的LCD,其他的参数笔者会尽量给出。
从0开始配置UBoot
地址给出在这里,请你看清楚,笔者使用到是If_v2024.04分支,很快就要有If_v2025.04分支了,发生了任何的变化笔者都无法预知,所以你可以衡量后下载你的目标版本
git clone https://github.com/nxp-imx/uboot-imx.git
截至到2025年4月9日,我们的默认分支还是If_v2024.04,但看到这篇博客的你不一定了,可能需要显著的切换分支,所以请你留意!
diffs
首先要说的是,UBoot因为相当于上位机中的Grub引导,实际上,UBoot在我们的项目中不应该占据太大的比例,但是你需要注意的是
- 第一,也是最重要的是——现代UBoot转向使用设备树描述硬件信息,因此,你实际上不得不再修改UBoot支持的板机信息的同时,修改设备树的源码,添加上我们支持的设备的信息。
- 第二,uboot的shell中已经移除了nfs指令了,毕竟,大伙都是用更加专门的tftp协议来传递文件。我在查询的时候发现存在说法(请原谅笔者没有考证,我找不到网址了)最后是可以在menuconfig中重新开启。但是这个笔者并不清晰,所以笔者的结论是——移除了nfs指令了,传递文件就是用tftp。
我们进入克隆好的仓库之后,就可以开始大展身手了。
第零步——对于新手,你需要知道每一个文件夹在做什么
下面的内容,一部分是你初次拿到uboot源码的时候所没有的,因为这个表格说明的是编译后的文件的一些信息。
我们会跟一部分代码打上交道,比较重点的就是我们的config文件夹,board文件夹,drivers文件夹,arch/arm/dts文件夹,分别对应的是我们的板级配置信息,板级信息,驱动信息和设备树(驱动信息描述)信息。
第一步——预编译最接近您的板子信息的uboot
请各位朋友们注意,我们需要选择最接近我们的型号的,原生的样板进行编译,确保我们最大体的思路仍然是正确的,不然的话,就会出现各种问题。
上面笔者介绍自己的开发环境的时候已经说过,笔者的环境最接近的是mx6ull_14x14_evk_emmc_defconfig,这个就是我们的板级配置,因此,我们先点火!
make ARCH=arm CROSS_COMPILE=arm-unknown-linux-gnueabihf- distcleanmake ARCH=arm CROSS_COMPILE=arm-unknown-linux-gnueabihf- mx6ull_14x14_evk_emmc_defconfigmake ARCH=arm CROSS_COMPILE=arm-unknown-linux-gnueabihf- -j16
生成出来的文件这些是我们需要知道的。
- u-boot.bin 是 U-Boot 的主要可执行文件。
- u-boot.dtb 是描述硬件信息的设备树文件。
- u-boot-dtb.bin 是包含了 U-Boot 和设备树的合并二进制文件。
- u-boot-dtb.imx 是为 NXP i.MX 系列处理器生成的特定格式的启动镜像,包含了头部IVT DCD信息
这里,如果您
- 使用的是Linux的UUU工具,需要下载的是u-boot-dtb.imx文件,如果是正点原子的imxdownload,请下载u-boot-dtb.bin,注意!千万不要搞错了!不是u-boot.bin!!!(修正1:感谢正点原子IMX6ULL群友大佬(群友:陈)的提醒,这两个似乎是一致的,的确,笔者使用ls- lh和diff工具看了一下,的确一致,但是笔者建议是u-boot-dtb.bin,语义更好,当然,如果您是不一致的,优先烧录后者!)
- 使用的是MFG-TOOLS in Windows的朋友,请自己先上板子试一下,看看能不能启动起来,不过,默认正点原子的板子的朋友,网卡和LCD肯定是工作失败的,这一点简直是毋庸置疑。
第二步:我们的起点,CV一份自己的板级配置
对于UBoot,我们的板级配置在今天,只需要做的修改只有一处:瞄准自己的设备树配置路径。因为我们现在需要使用设备树来描述我们的外设信息了
我们拷贝一份我们刚刚跑起来的板级配置,刚刚我们就说过了,configs文件夹下存放我们的板级信息,我们拷贝一份:
cd configs# 进入板级配置信息文件夹cp mx6ull_14x14_evk_emmc_defconfig mx6ull_charliechen_emmc_defconfig
对于设备树描述的uboot,我们需要改一个地方。
➜ cat mx6ull_14x14_evk_emmc_defconfig | head -n 20CONFIG_ARM=yCONFIG_ARCH_MX6=yCONFIG_SYS_MALLOC_LEN=0x1000000CONFIG_NR_DRAM_BANKS=1CONFIG_SYS_MEMTEST_START=0x80000000CONFIG_SYS_MEMTEST_END=0x88000000CONFIG_ENV_SIZE=0x2000CONFIG_ENV_OFFSET=0xE0000CONFIG_MX6ULL=yCONFIG_TARGET_MX6ULL_14X14_EVK=y# CONFIG_LDO_BYPASS_CHECK is not setCONFIG_SYS_I2C_MXC_I2C1=yCONFIG_SYS_I2C_MXC_I2C2=yCONFIG_DM_GPIO=y# >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>##你需要更改的是这个地方,也就是默认的设备树信息CONFIG_DEFAULT_DEVICE_TREE=\"imx6ull-14x14-evk-emmc\"### <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<CONFIG_SUPPORT_RAW_INITRD=yCONFIG_USE_BOOTCOMMAND=yCONFIG_BOOTCOMMAND=\"run findfdt;mmc dev ${mmcdev};mmc dev ${mmcdev}; if mmc rescan; then if run loadbootscript; then run bootscript; else if run loadimage; then run mmcboot; else run netboot; fi; fi; else run netboot; fi\"CONFIG_BOOTDELAY=3...
改成什么呢?
➜ cat mx6ull_charliechen_emmc_defconfig | head -n 20CONFIG_ARM=yCONFIG_ARCH_MX6=yCONFIG_SYS_MALLOC_LEN=0x1000000CONFIG_NR_DRAM_BANKS=1CONFIG_SYS_MEMTEST_START=0x80000000CONFIG_SYS_MEMTEST_END=0x88000000CONFIG_ENV_SIZE=0x2000CONFIG_ENV_OFFSET=0xE0000CONFIG_MX6ULL=yCONFIG_TARGET_MX6ULL_14X14_EVK=y# CONFIG_LDO_BYPASS_CHECK is not setCONFIG_SYS_I2C_MXC_I2C1=yCONFIG_SYS_I2C_MXC_I2C2=yCONFIG_DM_GPIO=y# >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>##你需要更改的是这个地方,也就是默认的设备树信息CONFIG_DEFAULT_DEVICE_TREE=\"imx6ull-charliechen\"### <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<CONFIG_SUPPORT_RAW_INITRD=yCONFIG_USE_BOOTCOMMAND=yCONFIG_BOOTCOMMAND=\"run findfdt;mmc dev ${mmcdev};mmc dev ${mmcdev}; if mmc rescan; then if run loadbootscript; then run bootscript; else if run loadimage; then run mmcboot; else run netboot; fi; fi; else run netboot; fi\"CONFIG_BOOTDELAY=3# CONFIG_CONSOLE_MUX is not set
请你注意,这个imx6ull-charliechen就会被作为一个prefix,用来被Makefile引用构成一个完整的设备树文件名称:
$(CONFIG_DEFAULT_DEVICE_TREE).dts
因此我们后面的时候,修改的设备树的文件名称,也必须是$(CONFIG_DEFAULT_DEVICE_TREE).dts
,以我的为例子,那显然就是imx6ull-charliechen.dts
文件了。
第三步,复制一份板级信息
这一点,跟正点原子的差别并不大。我们的参考的最接近的板级信息文件夹被放在了board/freescale/mx6ullevk
下了,我们观察一下这个文件夹的结构
我们需要修改哪些内容呢,答案是C文件,Makefile,MAINTAINERS,Kconfig,imximage.cfg,这个事情跟我们的正点原子的教程看起来简直一摸一样。但笔者愿意在这里再罗嗦一次,因为还是有所不同了。
我们第一件事情就是拷贝整个board/freescale/mx6ullevk
,并且将拷贝的文件夹名称改成mx6ull_charliechen_emmc,注意的是,整个文件夹所处的位置是board/freescale/mx6ull_charliechen_emmc
下。
3.1 修改我们的.c文件
其实这个算花功夫,我们这里实际上是添加自己的开发板,请看:
int checkboard(void) {if (is_mx6ul_9x9_evk()) puts(\"Board: MX6UL 9x9 EVK\\n\");else if(is_cpu_type(MXC_CPU_MX6ULZ)) puts(\"Board: MX6UL 14x14 EVK\\n\");else puts(\"Board: IMX6ULL Charliechen EMMC\");return 0; }
这个更改参考的是正点原子imx6ull开发板移植新版本U-boot(uboot2022.04,有设备树)_imx6ull移植最新uboot-CSDN博客,相当于是增加了一部分的逻辑,你可以直接改成puts(\"Board: IMX6ULL Charliechen EMMC\");
这无所谓。
改完之后,文件重命名为mx6ull_charliechen_emmc.c,这个文件的名称在Makefile中还会使用到!
3.2 修订Makefile
我们看看我们的Makefile,我们这一次修改的Makefile是在board/freescale/mx6ull_charliechen_emmc
的!,你需要更改的就是obj-y的内容,这里,更改的名称就是{.c文件名称}.o
,如下所示
# (C) Copyright 2015 Freescale Semiconductor, Inc.## SPDX-License-Identifier:GPL-2.0+#obj-y := mx6ull_charliechen_emmc.oextra-$(CONFIG_USE_PLUGIN) := plugin.bin$(obj)/plugin.bin: $(obj)/plugin.o$(OBJCOPY) -O binary --gap-fill 0xff $< $@
这个时候,我们的Makefile就会打包我们的.c板级文件,从而完成uboot对我们板子信息的收集。
MAINTAINERS的修改
我们参考的板级信息这样写的,
MX6ULLEVK BOARDM:Peng Fan S:MaintainedF:board/freescale/mx6ullevk/F:include/configs/mx6ullevk.hF:configs/mx6ull_14x14_evk_defconfigF:configs/mx6ull_14x14_evk_plugin_defconfigF:configs/mx6ulz_14x14_evk_defconfig
我们修改一下就好了
MX6ULLEVK BOARDM:Peng Fan S:MaintainedF:board/freescale/mx6ull_charliechen_emmc/F:include/configs/mx6ull_charliechen_emmc.hF:configs/mx6ull_charliechen_emmc_defconfigF:configs/mx6ull_14x14_evk_plugin_defconfigF:configs/mx6ulz_14x14_evk_defconfig
这个无所谓的,只是说明我们的维护信息,改上更加完备,正规的开发这里必须要改
修订KConfig文件
我们参考的内容的Kconfig这样写的:
if TARGET_MX6ULL_14X14_EVK || TARGET_MX6ULL_9X9_EVKconfig SYS_BOARDdefault \"mx6ullevk\"config SYS_VENDORdefault \"freescale\"config SYS_CONFIG_NAMEdefault \"mx6ullevk\"config IMX_CONFIGdefault \"board/freescale/mx6ullevk/imximage.cfg\"config TEXT_BASEdefault 0x87800000endif
我们的构建会使用这个玩意,所以请各位注意。修改的时候,还是按照如下的参考进行:
if TARGET_MX6ULL_CHARLIECHEN_EMMC
这是条件语句,只有在配置项 TARGET_MX6ULL_CHARLIECHEN_EMMC
被启用时(例如 .config
中 CONFIG_TARGET_MX6ULL_CHARLIECHEN_EMMC=y
),下面的 config
才会生效。这个宏是通过 configs/
下的 defconfig 文件指定的,这个事情,是我们在稍后的make xxx_defconfig的时候,自动配置的,所以一定要记得改。改成我们的defconfig的前缀的名称。你看,笔者的板级配置信息(放在了configs文件夹下的)是mx6ull_charliechen_emmc_defconfig文件,我们的Makefile会萃取得到mx6ull_charliechen_emmc,这就是我们的if,注意要全部转大写!
config SYS_BOARD
说明当前平台的板级代码目录名,U-Boot会使用它去找 board/$(VENDOR)/$(BOARD)/
目录。依据:对应 board/freescale/mx6ull_charliechen_emmc/
目录的存在。修改方式:如果你创建了新目录 board/freescale/myboard/
,这里就改成 \"myboard\"
。这里跟下面的**config SYS_VENDOR
**都是匹配的
config SYS_VENDOR
指定厂商名,在U-Boot中用于构建路径,例如 board/$(SYS_VENDOR)/$(SYS_BOARD)
。依据:你创建的目录结构中的厂商名,例如 freescale
、nxp
、mycompany
等。修改方式:只要你把代码目录改成 board/mycompany/...
,这里就应同步为 \"mycompany\"
。当然,我们显然是freescale(哦对了,这是因为这个板子最先是freescale飞思卡尔公司维护的,后来被nxp收购了,但是这个板子还得是freescale的请注意!)
config SYS_CONFIG_NAME
这是 include/configs/
下的配置头文件名(不加 .h
),编译时U-Boot会引用 include/configs/mx6ull_charliechen_emmc.h
。如果你换成了 include/configs/myboard.h
,这里就要改成 \"myboard\"
。比如这里笔者就要换成mx6ull_charliechen_emmc
config TEXT_BASE
表示U-Boot镜像的加载地址(通常是链接地址),用于链接脚本 u-boot.lds
中的 TEXT_BASE
宏。直接参考笔者下面给的模板即可
if TARGET_MX6ULL_CHARLIECHEN_EMMCconfig SYS_BOARDdefault \"mx6ull_charliechen_emmc\"config SYS_VENDORdefault \"freescale\"config SYS_CONFIG_NAMEdefault \"mx6ull_charliechen_emmc\"config TEXT_BASEdefault 0x87800000endi
这个KConfig文件也是在我们自己的board/freescale/mx6ull_charliechen_emmc
下。
修订image.cfg文件
修改PLUGIN指向自己的板级文件夹
这个算镜像的插件机制,需要我们修改的,就是PLUGIN指向的.bin的位置。
#ifdef CONFIG_USE_PLUGIN/*PLUGIN plugin-binary-file IRAM_FREE_START_ADDR*/PLUGINboard/freescale/board/freescale/mx6ull_charliechen_emmc/plugin.bin 0x00907000#else
但是下面这个,请大伙必须做,不然一定启动不起来。
校验DDR
校验DDR是必要的,不然我们的UBoot会在检查DDR的内容的时候无法通过,你需要先下载的是DDR TOOLS
DDR Tools的网址在这里,拉到中间偏上的地方
i.MX 6/7 DDR Stress Test Tool - NXP Community
我找了10来分钟,这里直接把连接给出来吧!https://community.nxp.com/pwmxy87654/attachments/pwmxy87654/imx-processors%40tkb/1501/3/ddr_stress_tester_v3.00_setup.exe.zip
还需要下载的是Register Aids,不然的话我们稍后会不知道填写什么内容。
i.MX6UL/ULL/ULZ DRAM Register Programming Aids - NXP Community
现在,我们打开下载的表格文件,切换到第二个表格
请你注意,上面的参数如果你是使用正点原子的EMMC512MB开发板,对于橙色和蓝色框处的部分,一个字都不要差的抄下来,不然DDR校验过不去,到时候很麻烦会。对于其他大小的NAND开发板或者是EMMC开发板,自行参考其他教程!!!搜索IMX6ULL的DDR校验即可
全部搞完之后,你需要做的就是复制第三个表格的内容,创建一个随意的.inc文件,放进去,我是myinc.inc文件,选择困难了用我这个
//=============================================================================//init script for i.MX6UL DDR3//=============================================================================// Revision History// v01//=============================================================================wait = on//=============================================================================// DisableWDOG//=============================================================================省略一大堆内容...setmem /320x021b001c =0x00000000// MMDC0_MDSCR, clear this register (especially the configuration bit as initialization is complete)
下一步就是配置DDR测试的参数了,笔者使用的是512MB的EMMC,因此,这里的参数长这样,这个参考的是博客:
NXP(I.MX6uLL)DDR3实验——DDR3初始化、校准、超频测试_i.mx6 ddr stress test tool-CSDN博客
各位可以前往参考:
Target是MX6ULL,处理器的速度是528MHz,取消勾选校验DCD地址,DDR大小512,DDR的频率是400MHz
之后,把你的板子的OTG接口插上,让板子构成USB的从机,然后点击Calibraton按钮,稍微泡杯茶走一回,你就能拿到结果了
Write leveling calibration MMDC_MPWLDECTRL0 ch0 (0x021b080c) = 0x00000000 MMDC_MPWLDECTRL1 ch0 (0x021b0810) = 0x001F001F Read DQS Gating calibration MPDGCTRL0 PHY0 (0x021b083c) = 0x01380134 MPDGCTRL1 PHY0 (0x021b0840) = 0x00000000 Read calibration MPRDDLCTL PHY0 (0x021b0848) = 0x40402E34 Write calibration MPWRDLCTL PHY0 (0x021b0850) = 0x4040362E
这是笔者校验的结果,不要直接抄,每一个板子是略有不同的!不然uboot会无法启动!(笔者的血与泪),拿到这个信息之后(笔者非常建议你保存一下,我们会用到的),打开我们的image.cfg文件,在这个文件中搜索地址,你看,你搜索地址0x021b080c就会给你定位到这个地方,你看这里就跟默认的不一样,改成0x00000000
DATA 4 0x021B080C 0x00000004
之后如法炮制的修改剩下5个寄存器的值后,我们最后需要做的是修改设备树的一些文件,让我们的UBoot启动起来
第四步,修正设备树
因为从现在开始,UBoot采用的是设备树了,跟Linux一样,所有的设备树全部放到了我们的arch/arm/dts文件夹下了。
4.0 开始我们的分析
分析一下,我们当时旧的配置是魔改了我们的arch/arm/dts/imx6ull-14x14-evk-emmc.dts
(当时是Linux的),这里我们看看这个文件下有啥
#include \"imx6ull-14x14-evk.dts\"&usdhc2 {pinctrl-names = \"default\", \"state_100mhz\", \"state_200mhz\";pinctrl-0 = <&pinctrl_usdhc2_8bit>;pinctrl-1 = <&pinctrl_usdhc2_8bit_100mhz>;pinctrl-2 = <&pinctrl_usdhc2_8bit_200mhz>;bus-width = <8>;non-removable;status = \"okay\";};
嗯,信息被收拢到\"imx6ull-14x14-evk.dts\"
文件去了
/dts-v1/;#include \"imx6ull.dtsi\"#include \"imx6ul-14x14-evk.dtsi\"#include \"imx6ul-14x14-evk-u-boot.dtsi\"...
我们的依赖就这样层层的递进,但中规中矩,实际上需要修改的东西还是那些,笔者是拷贝了imx6ul-14x14-evk.dtsi文件和imx6ul-14x14-evk.dts文件,这个重新作为我们新板子的根基。
cp imx6ul-14x14-evk.dtsi imx6ull-charliechen.dtsicp imx6ul-14x14-evk.dts imx6ull-charliechen.dts
因为我们现在用的新东西,打开我们的imx6ull-charliechen.dts,然后把原先的include第二行的#include \"imx6ul-14x14-evk.dtsi\"改成我们自己的就好了(笑)
#include \"imx6ull.dtsi\"#include \"imx6ull-charliechen.dtsi\"#include \"imx6ul-14x14-evk-u-boot.dtsi\"
爆改启动!
4.1 修订EMMC节点信息
第一件事情,我们这个板子是基于imx6ul-14x14-evk-emmc的,需要额外的补充节点,在我们的arch/arm/dts/imx6ull-14x14-evk-emmc.dts
下偷过来&usdhc2的节点信息。
// SPDX-License-Identifier: (GPL-2.0 OR MIT)//// Copyright (C) 2016 Freescale Semiconductor, Inc./dts-v1/;#include \"imx6ull.dtsi\"#include \"imx6ull-charliechen.dtsi\"#include \"imx6ul-14x14-evk-u-boot.dtsi\"/ {model = \"i.MX6 ULL 14x14 EVK Board\";compatible = \"fsl,imx6ull-14x14-evk\", \"fsl,imx6ull\";};&clks {assigned-clocks = , ;assigned-clock-rates = , ;};&csi {status = \"okay\";};&ov5640 {status = \"okay\";};&usdhc2 {pinctrl-names = \"default\", \"state_100mhz\", \"state_200mhz\";pinctrl-0 = ;pinctrl-1 = ;pinctrl-2 = ;bus-width = ;non-removable;status = \"okay\";};/delete-node/ &sim2;
改完之后就是这样,现在,你可以开始将uboot烧录到自己的板子上了,笔者建议是SD卡。
Core: 79 devices, 22 uclasses, devicetree: separateMMC: FSL_SDHC: 0, FSL_SDHC: 1Loading Environment from MMC... OK[*]-Video Link 0 (800 x 480)[0] lcdif@21c8000, videoIn: serialOut: serialErr: serialswitch to partitions #0, OKmmc0 is current deviceflash target is MMC:0Net: eth1: ethernet@20b4000 [PRIME]Get shared mii bus on ethernet@2188000Error: ethernet@2188000 No valid MAC address found.Fastboot: NormalNormal BootHit any key to stop autoboot: 0 => => =>
呀呼!庆祝一下吧,我们启动是成功了!但是你会发现,嗯,网卡和LCD都罢工了,这就是我们下面工作的意义——修正LCD驱动和网卡驱动(其他不用修,跳板的东西修LCD和)
4.2 修订LCD驱动
查看LCD驱动,翻阅正点原子手册,他是使用了lcdif这个节点信息描述的。
我们先修订LCD驱动的打开部分,回到我们的板级文件夹的
static int setup_lcd(void) { enable_lcdif_clock(LCDIF1_BASE_ADDR, 1); imx_iomux_v3_setup_multiple_pads(lcd_pads, ARRAY_SIZE(lcd_pads)); /* Reset the LCD */ gpio_request(IMX_GPIO_NR(5, 9), \"lcd reset\"); gpio_direction_output(IMX_GPIO_NR(5, 9) , 0); udelay(500); gpio_direction_output(IMX_GPIO_NR(5, 9) , 1); /* Set Brightness to high */ gpio_request(IMX_GPIO_NR(1, 8), \"backlight\"); gpio_direction_output(IMX_GPIO_NR(1, 8) , 1); return 0; }
我们的LCD是没有开关的,可以对/* Reset the LCD */
的代码块进行注释
static int setup_lcd(void) { enable_lcdif_clock(LCDIF1_BASE_ADDR, 1); imx_iomux_v3_setup_multiple_pads(lcd_pads, ARRAY_SIZE(lcd_pads)); /* Reset the LCD */// gpio_request(IMX_GPIO_NR(5, 9), \"lcd reset\");// gpio_direction_output(IMX_GPIO_NR(5, 9) , 0);// udelay(500);// gpio_direction_output(IMX_GPIO_NR(5, 9) , 1); /* Set Brightness to high */ gpio_request(IMX_GPIO_NR(1, 8), \"backlight\"); gpio_direction_output(IMX_GPIO_NR(1, 8) , 1); return 0; }
但笔者这里没这样做,事实证明并不影响。看自己心情吧!下一步比较重要,我们需要修改LCD设备的参数信息。在我们的arch/arm/dts/imx6ull-charliechen.dtsi
下吗,一个搜索lcdif就能抓到
&lcdif {pinctrl-names = \"default\";pinctrl-0 = ;display = ;status = \"okay\";...};
你需要修改成这样
&lcdif {pinctrl-names = \"default\";pinctrl-0 = ;display = ;status = \"okay\";display0: display@0 {bits-per-pixel = ;// 改成24bus-width = ;// 改成24display-timings {native-mode = ;timing0: timing0 {clock-frequency = ;// 我们的LCD时钟是51.2MHz的hactive = ;// 长800vactive = ;// 宽480hfront-porch = ;hback-porch = ;hsync-len = ;vback-porch = ;vfront-porch = ;vsync-len = ;hsync-active = ;vsync-active = ;de-active = ;pixelclk-active = ;};};};};
pinctrl-names = \"default\";
指定默认的引脚控制方案,表示使用默认引脚配置。pinctrl-0 = ;
引用了两个引脚控制节点,分别负责 LCD 的数据线和控制线(如 VSYNC、HSYNC)配置。display = ;
将主显示设备绑定到下面定义的display0
子节点。status = \"okay\";
表示启用该设备节点。
接下来是 display0: display@0
节点,定义了一个 LCD 显示设备的参数:
bits-per-pixel = ;
设置每像素的位数为 24,对应 RGB888,每个颜色通道 8 位。bus-width = ;
设置 LCD 数据总线的宽度为 24 位,即使用 RGB 所有通道并行传输。
接着是 display-timings
块,设置显示屏的时序参数:
native-mode = ;
指定默认的显示模式使用timing0
。timing0
定义了一组特定的显示时序,适配 800x480 分辨率和 51.2MHz 像素时钟的屏幕。具体参数如下:clock-frequency = ;
设置像素时钟频率为 51.2MHz。hactive = ; vactive = ;
设置水平和垂直有效像素,即屏幕分辨率为 800×480。hfront-porch = ; hback-porch = ; hsync-len = ;
设置水平同步时序。vfront-porch = ; vback-porch = ; vsync-len = ;
设置垂直同步时序。hsync-active = ; vsync-active = ;
指定 HSYNC 和 VSYNC 为低电平有效。de-active = ;
设置数据使能(DE)信号为高电平有效。pixelclk-active = ;
表示像素时钟在下降沿采样像素。
这些参数需要结合正点原子使用的LCD型号,找手册看参数。如果实在找不到,笔者的鸣谢reference中有LCD参数设置,或者,你可以直接杀去看正点原子LCD驱动移植的资料
正点原子imx6ull-mini-Linux驱动之Linux LCD 驱动实验(19)_imx6ull lcd帧率改变-CSDN博客
还需要我们修改一下LCD的数据控制引脚的电平信息,这个实际上可改可不改,看你,但是Linux下请一定要改,uboot存活时间短无所谓
pinctrl_lcdif_dat: lcdifdatgrp {fsl,pins = ;};
4.3 修订网卡驱动
我们下面来修改网卡的驱动。当然,我们主要解决的还是SPI4的引脚和我们的网卡复位驱动打架的问题。因此,结合我们的原理图(如果你的板子跟我的一致,看结论就好)就会发现,冲突的引脚是MX6UL_PAD_SNVS_TAMPER7__GPIO5_IO07
和MX6UL_PAD_SNVS_TAMPER8__GPIO5_IO08
,我们的板子上这里用给了网卡驱动了,因此,我们需要做的是
首先搜索这两个引脚,在我们的arch/arm/dts/imx6ull-charliechen.dtsi中只有:
pinctrl_spi4: spi4grp {fsl,pins = ;};
我们给下面的两个控制引脚注释掉。然后,我们在IO矩阵复位设备节点上,添加复位的控制组:
pinctrl_enet1_reset: enet1resetgrp {fsl,pins = ;};pinctrl_enet2_reset: enet2resetgrp {fsl,pins = ;};
你需要注意的是,因为我们现在NXP的板子中MX6UL和MX6ULL的配置合并,这里一定要写笔者这个,不然的话就会找不到符号出错!Linux的配置也是一样的。(这一点跟我们的6.6有不一样了)
以及,我们需要取消任何对MX6UL_PAD_SNVS_TAMPER7__GPIO5_IO07
和MX6UL_PAD_SNVS_TAMPER8__GPIO5_IO08
引脚的印用,这里隶属于我们的设备树的信息,对于笔者这个版本的UBoot。你需要修改的是注释掉我们的cs-gpios
和cs-gpios
两行,
spi-4 {compatible = \"spi-gpio\";pinctrl-names = \"default\";pinctrl-0 = ;status = \"okay\";gpio-sck = ;gpio-mosi = ;/* cs-gpios = ; */num-chipselects = ;#address-cells = ;#size-cells = ;gpio_spi: gpio@0 {compatible = \"fairchild,74hc595\";gpio-controller;#gpio-cells = ;reg = ;registers-number = ;registers-default = /bits/ 8 ;spi-max-frequency = ;/* enable-gpios = ; */};};
然后在我们的网卡上添加复位的信息
&fec1 {pinctrl-names = \"default\";pinctrl-0 = ;// 添加复位控制引脚phy-mode = \"rmii\";phy-handle = ;// 添加这些 -----------------------------------------phy-reset-gpios = ;phy-reset-duration = ;phy-reset-post-delay = ;// 到这里 -----------------------------------------phy-supply = ;status = \"okay\";};&fec2 {pinctrl-names = \"default\";pinctrl-0 = ;// 添加复位控制引脚phy-mode = \"rmii\";phy-handle = ;phy-supply = ;// 添加这些 -----------------------------------------phy-reset-gpios = ;phy-reset-duration = ;phy-reset-post-delay = ;// 到这里 -----------------------------------------status = \"okay\";...
主要添加的就是添加复位控制信息,phy-reset-gpios信息,phy-reset-duration信息和phy-reset-post-delay信息,如上所示
现在,我们重新编译uboot,上号!
启动uboot时,系统会提示ethernet地址没有设置,这是网口的地址。这个error是不影响使用的,u-boot仍然能正常运行,只要设置好参数eth1addr就可以正常运行了。(注意,这个是新的UBoot的区别,不是ethraddr!)
setenv eth0addr b8:ae:1d:01:00:00# MAC地址随意,不要在以太网里撞车就行
设置一下,如果你的网卡工作正常,他会先reset网卡,成功后就会设置好了
剩下的事情就是设置板子的本机IP,服务器(我的Ubuntu上位机)对于使用网线连接的以太网分配IP和我们的网关。
setenv ipaddr本机IPsetenv gatewayip网关IPsetenv serveripUbuntu上位机/虚拟机IPsaveenv
关于设置的讲究,我需要说明的是——你需要在你的ubuntu/其他发行版观察被分配的上位机IP,然后对应的设置网关和板子的IP。举个例子,我看到我的UBuntu在我的以太网局域网中被分配到了10.41.0.3,其中子网掩码是255.255.255.0,说明IP地址的最后8位给了其他设备,我们假设我们想给我们的板子分配10.41.0.2,这个时候,我们首先
➜ ping 10.41.0.2PING 10.41.0.2 (10.41.0.2) 56(84) bytes of data.# 很久是没有回应的
这个IP就是可以被分配给板子的
setenv ipaddr10.41.0.2setenv gatewayip10.41.0.1# 我习惯给1setenv serverip10.41.0.3
我们现在开始测试一下网卡能否正常的手发包
=> ping 10.41.0.3# Using ethernet@20b4000 devicehost 10.41.0.3 is alive
对了,笔者说过了,UBoot是不支持回应ICMP回显报文的,所以在你设置好了之后
➜ ping 10.41.0.2PING 10.41.0.2 (10.41.0.2) 56(84) bytes of data.From 10.41.0.3 icmp_seq=1 Destination Host UnreachableFrom 10.41.0.3 icmp_seq=2 Destination Host UnreachableFrom 10.41.0.3 icmp_seq=3 Destination Host Unreachable
UBoot完结撒花!给我们的小板子来一个合照!
从0开始配置6.12.3Linux
补充(如果你是打算新uboot引导旧内核)
新uboot无法直接启动旧的nxp-linux内核:
Modify /soc/aips-bus@02200000/epdc@0228c000 disablednode to update the SoC serial number is not found.ERROR: system-specific fdt fixup failed: FDT_ERR_NOTFOUND - must RESET the board to recover.FDT creation failed!resetting ...
我们定位问题,最后可以找到的是
if (IS_ENABLED(CONFIG_OF_SYSTEM_SETUP)) {fdt_ret = ft_system_setup(blob, gd->bd);if (fdt_ret) {printf(\"ERROR: system-specific fdt fixup failed: %s\\n\", fdt_strerror(fdt_ret));goto err;}}
锁定ft_system_setup,在我们的arch/arm/mach-imx/mx6/module_fuse.c
中,需要修改的是——
// int nodeoff = fdt_node_offset_by_compatible(blob, -1, \"fsl,imx28-dcp\");int nodeoff = fdt_node_offset_by_compatible(blob, -1, \"fsl,imx6sl-dcp\");
你需要重新编译一下uboot,这是因为适配的新内核上名称不再是imx6sl-dcp而是imx28-dcp,自然没法找到,之后你可以使用正点的旧内核和旧设备树启动。
前言
这里也是最激动人心的部分,我们准备好摆脱老旧的4.1.15了!现在我们需要做的,就是泡上一杯茶,笔者是去上了一个小时的课回来下好的。
这里给出我们的地址。
nxp-imx/linux-imx: i.MX Linux kernel:If_6.12.y分支,截止至2025年的4月10号,还是这个分支
第一步:跟UBoot一样的思路——先试一下
我们仍然按照UBoot类似的思路来编译我们的Linux!UBoot就相当于跳板,在我们敲好了bootz指令之后,UBoot就会把PC指向我们的Linux,一去不复返尔!
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- distclean make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- imx_v7_defconfig make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- all -j16
我们的配置是imx_v7_defconfig,这个是我们默认的配置,对于老内核的朋友应该知道,使用的是imx_v7_mfg_defconfig,这里nxp把他删掉了,我们使用imx_v7_defconfig 作为平替。
下一步就是上板子,不用烧录到SD卡。我们转向使用tftp来完成文件的传输。
第1.5步(新来的朋友看这个)配置TFTP服务器和NFS服务器
如果你是在UBuntu24.04或者是任何新的现代内核开发这个板子,你可以看看这个,一些创新的朋友可能已经做好了,笔者这里建议你直接跳过去。
tftp服务器是我们后面要拿来传递Linux zImage和dtb设备树二进制文件的,nfs是我们准备使用的网络文件系统挂载我们的跟文件系统的,不然难以调试。所以,我们需要做的就是部署TFTP服务器和NFS服务器,关于这个事情:
NFS搭建
这就是好消息,新版本的UBoot和Linux全部支持现代NFS,完全可以直接梭哈。对于ubuntu而言
sudo apt-get install nfs-kernel-server rpcbind
我们需要修改的是nfs的export配置文件:
sudo vim /etc/exports
添加的是——
/home/charliechen/imx6ull/nfs *(rw,sync,no_root_squash,no_subtree_check)
必须是绝对路径,因为我们的跨设备文件系统访问的时候,资源没法解析~
是啥意思。笔者将我们的nfs暴露跟文件系统放在了/home/charliechen/imx6ull/nfs
下,你自己设置一个地方,然后填写你设置的路径即可。这个路径就是我们的nfs可见的文件夹范围的位置了!
注意的是,新版本的NFS上,需要这是的权限是rw,sync,no_root_squash,no_subtree_check,不然我们的最小根文件系统会给你放送init资源文件没有权限送你两个kernel_panic!
之后请重启我们的nfs服务,然后你可以使用exportfs指令看看你设置的对不对
/home/charliechen/imx6ull/nfs(sync,wdelay,hide,no_subtree_check,sec=sys,rw,secure,no_root_squash,no_all_squash)
TFTP搭建
如法炮制,各位需要在自己的发行版部署tftp服务器
IMX6ULL驱动开发uboot篇02_imx6ull uboot-CSDN博客
笔者在这里记录了如何搭建,各位看官看看即可!
测试内核文件传递
我们把编译好的内核和设备树文件放置到我们的TFTP文件夹的根目录下,然后测试一下能不能用,设备树的文件名称我暂时改成了imx6ull-14x14-charliechen-emmc.dtb:
=> tftp 80800000 zImageUsing ethernet@20b4000 deviceTFTP from server 10.41.0.3; our IP address is 10.41.0.2Filename \'zImage\'.Load address: 0x80800000Loading: ################################################################# ################################################################# ################################################################# ################################################################# ################################################################# ################################################################# ################################################################# ################################################################# ################################################################# ################################################################# ############ 2.2 MiB/s
非常好,这是能用的,我们下一步就是直接杀过去写一下我们的测试启动命令行
setenv bootcmd \"tftp 80800000 zImage; tftp 83000000 imx6ull-14x14-charliechen-emmc.dtb;bootz 80800000 - 83000000\"
当然,你会发现启动不起来!因为我们根本还没设置跟文件系统,但是先不着急,我们需要先修复网卡和LCD驱动等信息。我们马上开始
修复Linux的设备驱动信息
我们的根基在arch/arm/boot/dts/nxp/imx/imx6ul-14x14-evk.dtsi,仍然是你需要注意的是,你需要做的事情是稍微拷贝两份文件:
cp arch/arm/boot/dts/nxp/imx/imx6ull-14x14-evk.dts arch/arm/boot/dts/nxp/imx/imx6ull-14x14-charliechen-emmc.dts cp arch/arm/boot/dts/nxp/imx/imx6ul-14x14-evk.dtsi arch/arm/boot/dts/nxp/imx/imx6ull-14x14-charliechen-emmc.dtsi
然后仍然记得在dts文件中修正一下我们的include
#include \"imx6ull.dtsi\"#include \"imx6ull-14x14-charliechen-emmc.dtsi\"
第一步,添加EMMC节点信息
跟uboot一摸一样,从arch/arm/boot/dts/nxp/imx/imx6ull-14x14-evk-emmc.dts中拷贝一份出来,接到我们的arch/arm/boot/dts/nxp/imx/imx6ull-14x14-charliechen-emmc.dts上
// SPDX-License-Identifier: (GPL-2.0 OR MIT)//// Copyright (C) 2016 Freescale Semiconductor, Inc./dts-v1/;#include \"imx6ull.dtsi\"#include \"imx6ul-14x14-evk.dtsi\"/ {model = \"Freescale i.MX6 ULL 14x14 EVK Board\";compatible = \"fsl,imx6ull-14x14-evk\", \"fsl,imx6ull\";};&clks {assigned-clocks = , ;assigned-clock-rates = , ;};&csi {status = \"okay\";};&ov5640 {status = \"okay\";};&usdhc2 {pinctrl-names = \"default\", \"state_100mhz\", \"state_200mhz\";pinctrl-0 = ;pinctrl-1 = ;pinctrl-2 = ;bus-width = ;non-removable;status = \"okay\";};/delete-node/ &sim2;
第二步,修订LCD驱动
&lcdif {assigned-clocks = ;assigned-clock-parents = ;pinctrl-names = \"default\";pinctrl-0 = ;display = ;status = \"okay\";display0: display@0 {bits-per-pixel = ;bus-width = ;display-timings {native-mode = ;timing0: timing0 {clock-frequency = ;hactive = ;vactive = ;hfront-porch = ;hback-porch = ;hsync-len = ;vback-porch = ;vfront-porch = ;vsync-len = ;hsync-active = ;vsync-active = ;de-active = ;pixelclk-active = ;};};};};
没有新鲜的东西,参数仍然是写自己的LCD参数,前面说过了
第三步:修订网络驱动
一样,在控制组这个地方注释我们的MX6UL_PAD_SNVS_TAMPER7__GPIO5_IO07
和MX6UL_PAD_SNVS_TAMPER8__GPIO5_IO08
pinctrl_spi4: spi4grp {fsl,pins = ;};
然后也是删掉所有使用到了这两个引脚的地方:
pi-4 {compatible = \"spi-gpio\";pinctrl-names = \"default\";pinctrl-0 = ;status = \"okay\";sck-gpios = ;mosi-gpios = ;/* cs-gpios = ; */num-chipselects = ;#address-cells = ;#size-cells = ;gpio_spi: gpio@0 {compatible = \"fairchild,74hc595\";gpio-controller;#gpio-cells = ;reg = ;registers-number = ;registers-default = /bits/ 8 ;spi-max-frequency = ;/* enable-gpios = ; */};
然后在iomux矩阵中添加控制引脚
&iomuxc {// ...pinctrl_enet1_reset: enet1resetgrp {fsl,pins = ;};pinctrl_enet2_reset: enet2resetgrp {fsl,pins = ;};}
修订一下我们的fec1和fec2
&fec1 {pinctrl-names = \"default\";pinctrl-0 = ;phy-mode = \"rmii\";phy-reset-gpios = ;phy-reset-duration = ;phy-handle = ;phy-supply = ;status = \"okay\";};&fec2 {pinctrl-names = \"default\";pinctrl-0 = ;phy-mode = \"rmii\";phy-reset-gpios = ;phy-reset-duration = ;phy-handle = ;phy-supply = ;status = \"okay\";mdio {...
是的!跟你起来搞UBoot的流程是完全一致的!我们下一步,就是尝试启动。
如果你有条件,可以向EMMC中先烧写一个完整的系统(使用MFG-TOOLS先烧写默认的正点原子系统给EMMC上),然后做的事情是将你的bootargs改成向EMMC分区启动
setenv bootargs \"console=ttymxc0,115200 root=/dev/mmcblk1p2 rootwait rw\"
这个的意思是使用ttymxc0作为串口输出日志,根文件系统在EMMC中。然后系统起来了之后,测试能不能跑通就是了:
~ # ping 10.41.0.3PING 10.41.0.3 (10.41.0.3): 56 data bytes64 bytes from 10.41.0.3: seq=0 ttl=64 time=1.622 ms64 bytes from 10.41.0.3: seq=1 ttl=64 time=1.756 ms64 bytes from 10.41.0.3: seq=2 ttl=64 time=1.772 ms64 bytes from 10.41.0.3: seq=3 ttl=64 time=1.772 ms64 bytes from 10.41.0.3: seq=4 ttl=64 time=1.701 ms64 bytes from 10.41.0.3: seq=5 ttl=64 time=1.711 ms64 bytes from 10.41.0.3: seq=6 ttl=64 time=1.886 ms
网卡工作正常!你可以去掉我们的console=ttymxc0,115200
,让日志输出在LCD上,看看别不别扭,笔者的如下:
日志肯定长的不一样,我这个是已经挂载了文件系统的(哦对了,我启动的那个开关选错了,记得调成SD卡的模式)
移植我们的Rootfs
编译和下载Rootfs
很好!到这一步,离我们结束已经非常非常的接近了!我们需要移植一个rootfs,然后使用NFS进行我们的挂载。
第一步就是下载1.36.0的busybox,记得解压放到一个位置上。
BusyBox的源码地址:Index of /downloads
BusyBox1.36.0的下载地址:https://www.busybox.net/downloads/busybox-1.36.0.tar.bz2
解压之后呢,我们进入目录,第一件事情是使用默认配置先:
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- defconfig
下一步是使用我们的menuconfig进行一定的微调
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- menuconfig
做如下的更改:
这个部分的内容是跟我们的Rootfs编译一回事,这里可以参考笔者的博客:IMX6ULL驱动开发Linux篇02——移植Rootfs_vfs: mounted root (nfs filesystem) on device 0:15.-CSDN博客
- 保证
Settings->Build static binary (no shared libs)
是取消勾选的- 保证
Settings->vi-style line editing commands
勾选,当然你不喜欢我没意见(不是- 取消勾选
Linux Module Utilities->Simplified modutils
,我们省点事。- 检查
mdev
是否启动了,这里需要检查所有的子项
上面的部分改完了之后呢,我们就可以开始编译了:
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -j12
这一步开始,我们需要下载到一个目录了,还记得我们的NFS目录嘛,需要放到这个下面,笔者是设置了在了NFS根目录下的rootfs下/home/charliechen/imx6ull/nfs/rootfs
下去了。下载的时候,自然也需要指向这个地方
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- CONFIG_PREFIX=/home/charliechen/imx6ull/nfs/rootfs install
现在,你可以尝试启动了!
NFS测试挂载
这个地方问题最多,这里需要注意的是,我们需要修改一下bootargs
setenv bootargs \"console=ttymxc0,115200 root=/dev/nfs nfsroot=10.41.0.3:/home/charliechen/imx6ull/nfs/rootfs,vers=3,proto=tcp rw ip=10.41.0.2:10.41.0.3:10.41.0.1:255.255.255.0::eth0:off\"
bootargs是我们的启动的内核参数,我们全依靠这个玩意告诉我们的内核如何启动。下面我列一下参数如何构成:
笔者直接请我们的AI代劳了:
Deepseek如是说:
console=ttymxc0,115200
- 含义:设置内核控制台(console)的输出设备。
- 参数解析:
ttymxc0
:指定串口设备名,ttymxc0
是 i.MX6ULL 处理器的第一个串口(UART1)。115200
:串口的波特率(数据传输速率),单位是 bps(比特每秒)。- 作用:内核启动信息将通过串口
ttymxc0
输出,波特率为 115200,方便调试。
2.
root=/dev/nfs
- 含义:指定根文件系统(rootfs)的挂载方式。
- 参数解析:
/dev/nfs
:表示根文件系统将通过 NFS(网络文件系统)挂载,而不是本地存储(如 eMMC、SD 卡等)。- 作用:系统启动时会从 NFS 服务器加载根文件系统。
3.
nfsroot=10.41.0.3:/home/charliechen/imx6ull/nfs/rootfs,vers=3,proto=tcp
- 含义:配置 NFS 根文件系统的具体参数。
- 参数解析:
10.41.0.3
:NFS 服务器的 IP 地址。/home/charliechen/imx6ull/nfs/rootfs
:NFS 服务器上共享的根文件系统路径。vers=3
:指定 NFS 协议版本为 NFSv3(兼容性较好)。proto=tcp
:使用 TCP 协议传输数据(比 UDP 更可靠)。- 作用:告诉内核从
10.41.0.3
的指定路径挂载 NFS 根文件系统,使用 NFSv3 和 TCP 协议。
4.
rw
- 含义:挂载根文件系统为可读写(read-write)。
- 作用:默认情况下,NFS 挂载可能是只读(
ro
),这里显式指定为可读写。
5.
ip=10.41.0.2:10.41.0.3:10.41.0.1:255.255.255.0::eth0:off
- 含义:配置网络接口的 IP 地址和相关参数。
- 参数解析(格式为
ip=::::::
):
10.41.0.2
:开发板(客户端)的 IP 地址。10.41.0.3
:NFS 服务器的 IP 地址。10.41.0.1
:网关(Gateway)的 IP 地址。255.255.255.0
:子网掩码(Netmask)。- ``(空):主机名(Hostname),未设置。
eth0
:使用的网络接口(如以太网)。off
:禁用自动配置(如 DHCP)。- 作用:静态配置网络,避免依赖 DHCP。
好,我们总结一下:
Documentation/filesystems/nfs/nfsroot.txt
下是有告诉你如何如何使用nfs挂载的,笔者这里整理一下:root=/dev/nfs nfsroot=[服务器IP]:[根文件系统路径],[NFS 选项] ip=[客户端IP]:[服务器IP]:[网关IP]:[子网掩码]:[主机名]:[设备]:[自动配置]:[DNS0]:[DNS1]
- 服务器 IP 地址是存放根文件系统的主机 IP,例如 我的就是Ubuntu嘛!192.168.137.10,麻烦自己在ubuntu主机上ifconfig一下
- 根文件系统的存放路径例,笔者的是 /home/charliechen/linux/nfs/rootfs。
- NFS 选项一般不设置,但是这里我们强调使用proto=tcp rw,表达使用TCP协议来完成挂载
- 客户端 IP 地址是开发板的 IP,需与服务器同一网段,且未被占用,我选择了 192.168.137.4,这个IP有没有效,很简单,跑到主机上ping一下,要求是没有人应答,说明没有人占用这个IP,这个IP就可以被分配到板子上。
- 网关地址,参考我咋搞uboot的,这里沿用 192.168.137.1。
- 子网掩码例如 255.255.255.0。
- 主机名一般不设置,可留空。
- 设备名是网卡名称,例如 eth0、eth1。本例中使用 ENET2,对应 eth0。
- 自动配置一般设为 off。
- DNS 服务器 IP 一般不使用,可留空。
单走一个boot!不出意外的话,你的板子应当是启动成功了!
[ 7.038479] VFS: Mounted root (nfs filesystem) on device 0:16.[ 7.046515] devtmpfs: mounted[ 7.052536] Freeing unused kernel image (initmem) memory: 1024K[ 7.059361] Run /sbin/init as init process// 上面和下面会有一些报错,这个是我们需要移植的东西
现在,我们准备完善我们的rootfs了!
完善我们的rootfs
征订我们的更加完善的根文件系统
我们创建到保证这些文件夹是都存在的:
bin dev etc lib linuxrc mnt proc root sbin sys tmp usr
补充rcS文件和fstab文件
先补充我们的fstab文件放到我们的etc目录下
# proc /proc proc defaults 0 0tmpfs /tmp tmpfs defaults 0 0sysfs /sys sysfs defaults 0 0
第一行是注释行,以井号开头,说明了后续各行中每个字段的含义。proc文件系统被挂载到/proc目录,这是一种特殊的虚拟文件系统,它不占用磁盘空间,而是作为内核与用户空间交互的接口,提供运行中进程和系统状态的信息。使用proc文件系统类型和defaults挂载选项,表示采用内核预设的默认参数,最后的两个零分别表示dump工具不需要备份这个文件系统,且系统启动时不进行fsck检查。
tmpfs文件系统挂载到/tmp目录,这是一种基于内存的临时文件系统,用于存储临时文件,能够显著提高访问速度但不会持久化存储。同样使用defaults挂载选项,表示使用默认的挂载参数如读写权限和大小限制,后面的两个零含义与proc文件系统相同。sysfs文件系统挂载到/sys目录,这是另一个虚拟文件系统,它向用户空间导出内核数据结构及其属性和链接,对于设备管理和系统配置至关重要。它也采用defaults挂载选项,并且不需要备份和启动检查。这些挂载配置在系统启动时由mount命令自动处理,确保了操作系统核心功能的正常运行
我们需要一份rcS文件放到/etc/init.d
下,这里放下笔者的配置
PATH=/sbin:/bin:/user/sbin:/usr/bin:$PATHLD_LIBRARY_PATH=$LD_LIBRARY_PATH:/lib:/usr/libexport LD_LIBRARY_PATHmount -amkdir -p /dev/ptsmount -t devpts devpts /dev/ptsmdev -s
上面的脚本中,我们第一件事情就是设置了PATH变量
-
PATH
决定了系统在哪些目录中查找可执行文件。 -
这里将
/sbin
、/bin
、/user/sbin
、/usr/bin
添加到PATH
,并保留原有的$PATH
。 -
这样设置确保系统可以找到基本的命令(如
mount
、mkdir
等)。
第二件事情就是设置了动态库的搜索路径
LD_LIBRARY_PATH
用于指定运行时动态链接器(ld.so
)查找共享库(.so
文件)的路径。- 这里将
/lib
和/usr/lib
添加到LD_LIBRARY_PATH
,并保留原有的值。这个事情确保了我们稍后可以运行最基础的动态库程序 - 完事了之后,我们还需要导出到全局,使变量对子进程(如后续启动的程序)可见。不导出的话后续启动的进程可能无法继承该变量,导致找不到动态库。
第三个事情:挂载 /etc/fstab
文件中定义的所有文件系统。保所有必要的文件系统(如 /proc
、/sys
、/tmp
等)已挂载。
第四个事情那就是建立起来类似mdev的子系统:
-t devpts
:指定文件系统类型为devpts
(伪终端文件系统)。devpts /dev/pts
:将devpts
设备挂载到/dev/pts
目录。devpts
用于支持终端设备(如ssh
、telnet
等会话)。不挂载会导致无法创建新的伪终端(如pts/0
、pts/1
)。
现在我们的mdev开始工作!mdev -s
的作用如此:-s
选项表示从 /sys
读取设备信息,并在 /dev
下生成对应的设备节点。
- 动态创建设备节点(如
/dev/ttyS0
、/dev/mmcblk0
等)。 - 不执行会导致设备(如串口、SD 卡)无法访问。
创建inittab文件
::sysinit:/etc/init.d/rcSconsole::askfirst:-/bin/sh::restart:/sbin/init::ctrlaltdel:/sbin/reboot::shutdown:/bin/umount -a -r::shutdown:/sbin/swapoff -a
第一行的 ::sysinit:/etc/init.d/rcS
指定了系统初始化阶段执行的脚本路径,该脚本通常负责基础环境搭建,如挂载 proc 和 sysfs 文件系统、设置主机名以及启动基础服务。紧接着的 console::askfirst:-/bin/sh
配置了控制台交互行为,其中 askfirst 动作会在控制台显示登录提示,等待用户按下回车键后启动指定的 /bin/sh
shell,开头的减号表示这是一个登录 shell,会读取相应用户的 profile 配置。对于系统异常处理,::restart:/sbin/init
定义了系统重启时重新执行 init 进程的路径,确保系统能够重新初始化。而 ::ctrlaltdel:/sbin/reboot
则捕获了 Ctrl+Alt+Del 组合键的触发事件,直接执行 reboot 命令强制重启系统,这在传统系统中用于紧急恢复。最后两行 ::shutdown:/bin/umount -a -r
和 ::shutdown:/sbin/swapoff -a
分别处理系统关闭流程:前者尝试卸载所有文件系统(-a
),若失败则设为只读(-r
)以防止数据损坏,后者则禁用所有交换分区以确保内存数据完整。这些配置共同构成了一个典型的轻量级 Linux 系统的生命周期管理框架,尤其适用于嵌入式或资源受限的环境。
移植libc的一些库,保证最基本的程序可以运行在我们的板子上
下面我们就是移植libc了!笔者的gcc版本是GCC13.3,也是笔者编译内核和uboot的新gcc,我们直接做一件事情即可:
拷贝我们在gcc的.a文件和.so文件直接杀到我们的lib和usr/lib文件当中即可!
cp *.a* *.so* /usr/arm-linux-gnueabihf/lib/ 自己的rootfs/lib -dcp *.a* *.so* /usr/arm-linux-gnueabihf/lib/ 自己的rootfs/usr/lib -d
这样就足够了目前
~ # du -sh /lib27.2M/lib
测试
测试用户态程序
编写基本的hello world:
#include #include int main(){while(1){printf(\"Hello, world!\\n\");sleep(1);}}
使用移植的gcc进行编译后运行
/home/charliechen/test1 # ./helloHello, world!Hello, world!Hello, world!Hello, world!Hello, world!Hello, world!Hello, world!^C # Ctrl C退出
测试驱动与搭建基本开发环境
先创建一个节点:
mknod /dev/ccled c 114 0
然后下一步,我们准备搭建环境。
我们写一个简单的Makefile
KDIR := /home/charliechen/imx6ull/linux-imxCURRENTDIR := $(shell pwd)RTFS_MODULE_TEST_PATH := ~/imx6ull/nfs/rootfs/module_test/MODULE_NAME := ledobj-m := $(MODULE_NAME).o.PHONY: char_dev clean application allchar_dev:make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -C $(KDIR) M=$(CURRENTDIR) modulesapplication:arm-linux-gnueabihf-gcc chrdev_application.c -o chrdev_applicationall:make char_dev;make applicationclean:make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -C $(KDIR) M=$(CURRENTDIR) cleanrm -f chrdev_applicationupload:cp chrdev_application $(RTFS_MODULE_TEST_PATH)cp $(MODULE_NAME).ko $(RTFS_MODULE_TEST_PATH)
几个基本的点:
/home/charliechen/imx6ull/linux-imx指向了我们的构建内核的路径,这里需要替换成你自己的
~/imx6ull/nfs/rootfs/module_test/指向了我们打算部署测试模块的文件夹,自行更改即可
剩下的Makefile内容参考笔者的教程部分,这里不再赘述。
几个测试文件:用户态文件测试:
#include #include #include #include #include #define ISSUE_BUFFER_N (40)static void display_help(const char* app_name){ fprintf(stderr, \"do: %s \\n\" \"op: read : read the data from char dev\\n\" \"op: write: write the data to char dev:\\n\" \" open: turn on the led\\n\" \" close: turn off the led\\n\", app_name);}int main(int argc, char* argv[]){ int check = 0; if(argc < 3){ display_help(argv[0]); return -1; } char* filename = argv[1]; check = open(filename, O_RDWR); if(check < 0){ fprintf(stderr, \"Hey, Error in open filename: %s!\\n\", filename); return -1; } int result = 0; if(!strcmp(argv[2], \"read\")){ // process reading issue printf(\"user process the read issue\\n\"); char buffer[ISSUE_BUFFER_N]; result = read(check, buffer, ISSUE_BUFFER_N); if(result < 0){ fprintf(stderr, \"Hey, Error in read! filename: %s!\\n\", filename); goto close_issue; } printf(\"user receive from driver: %s\\n\", buffer); // done! }else if(!strcmp(argv[2], \"write\")){ // process the write printf(\"args: %d\\n\", argc); if(argc != 4){ display_help(argv[0]); goto close_issue; } printf(\"user process the write issue: %s\\n\", argv[3]); result = write(check, argv[3], strlen(argv[3])); if(result < 0){ fprintf(stderr, \"Hey, Error in write! filename: %s!\\n\", filename); goto close_issue; } }else{ fprintf(stderr, \"Unknown options!\\n\"); display_help(argv[0]); goto close_issue; }close_issue: result = close(check); if(result < 0){ fprintf(stderr, \"Hey, Error in close device! filename: %s!\\n\", filename); return -1; } return 0;}
驱动测试文件
#include #include #include #include #include #include MODULE_LICENSE(\"GPL\");MODULE_AUTHOR(\"charliechen\");#define LED_MAJOR_DEV_N (114)#define LED_DEV_NAME (\"charlies_led\")/* LED Physical Address See the manual*/#define CCM_CCGR1_BASE (0x020C406C)#define GPIO1_IOLED_BASE (0x020E0068)#define GPIO1_IOPAD_BASE (0x020E02F4)#define GPIO1_IODR_BASE (0x0209C000)#define GPIO1_GDIR_BASE (0x0209C004)/* mappings of the io phe*/static void* __iomem LED_CCGR1;static void* __iomem LEDBASE;static void* __iomem LEDPAD_BASE;static void* __iomem LEDDR_BASE;static void* __iomem LEDGDIR_BASE;/* operations cached */static char operations_cached[20];static void __led_turn_on(void){ u32 val = 0; val = readl(LEDDR_BASE); val &= ~(1 << 3); writel(val, LEDDR_BASE);}static void __led_turn_off(void){ u32 val = 0; val = readl(LEDDR_BASE); val |= (1 << 3); writel(val, LEDDR_BASE);}static u8 __fetch_led_status(void){ u32 val = 0; val = readl(LEDDR_BASE); return !(val & (1 << 3));}// static void __led_switch_impl(u8 op)// {// op ? __led_turn_on() : __led_turn_off();// }static void __enable_led_mappings(void){ int val = 0; pr_info(\"Ready to mappings the registers...\\n\"); LED_CCGR1 = ioremap(CCM_CCGR1_BASE, 4); LEDBASE = ioremap(GPIO1_IOLED_BASE, 4); LEDPAD_BASE = ioremap(GPIO1_IOPAD_BASE, 4); LEDDR_BASE = ioremap(GPIO1_IODR_BASE, 4); LEDGDIR_BASE = ioremap(GPIO1_GDIR_BASE, 4); pr_info(\"mappings the registers done!\\n\"); pr_info(\"LED_CCGR1 ioremap to: %p\\n\", LED_CCGR1); pr_info(\"LEDBASE ioremap to: %p\\n\", LEDBASE); pr_info(\"LEDPAD_BASE ioremap to: %p\\n\", LEDPAD_BASE); pr_info(\"LEDDR_BASE ioremap to: %p\\n\", LEDDR_BASE); pr_info(\"LEDGDIR_BASE ioremap to: %p\\n\", LEDGDIR_BASE); pr_info(\"initialize the led registers\\n\"); val = readl(LED_CCGR1); // clear the bits val &= ~(3 << 26); val |= (3 << 26); writel(val, LED_CCGR1); writel(0x5, LEDBASE); writel(0x10B0, LEDPAD_BASE); val = readl(LEDGDIR_BASE); val |= 1 << 3; writel(val, LEDGDIR_BASE); pr_info(\"operations of led is accessable!\\n\");}static void __disable_led_mappings(void){ __led_turn_off(); pr_info(\"set the led turning off...\\n\"); pr_info(\"set the led turning off done!\\n\"); pr_info(\"Ready to unmappings the registers...\\n\"); iounmap(LED_CCGR1); iounmap(LEDBASE); iounmap(LEDPAD_BASE); iounmap(LEDDR_BASE); iounmap(LEDGDIR_BASE); pr_info(\"unmappings the registers done\\n\");}static int led_open(struct inode* inode, struct file* filp){ pr_info(\"\\nled device is opened!\\n\"); return 0;}static int led_close(struct inode* inode, struct file* filp){ pr_info(\"\\nled device is released!\\n\"); return 0;}static ssize_t led_read(struct file* filp, char* buffer, size_t count, loff_t* ppos){ const char* status = \"opened\"; int ret = 0; pr_info(\"\\nled device is reading!\\n\"); if(!__fetch_led_status()){ status = \"closed\"; } ret = copy_to_user(buffer, status, strlen(status)); if(ret < 0) { pr_warn(\"Copy to the user failed\\n\"); return -EFAULT; } return 0;} static ssize_t led_write(struct file* filp,const char* buffer, size_t count, loff_t* ppos){ int check = 0; pr_info(\"\\nled device is ready writing!\\n\"); check = copy_from_user(operations_cached, buffer, count); if(check < 0){ pr_warn(\"Can not copy from user!\\n\"); return -EFAULT; } if(!strcmp(operations_cached, \"open\")){ __led_turn_on(); }else if(!strcmp(operations_cached, \"close\")){ __led_turn_off(); }else{ pr_warn(\"Can not find the indications operations!\\n\" \"check the business: %s\", operations_cached); } return 0;} static struct file_operations led_ops = { .owner = THIS_MODULE, .read = led_read, .write = led_write, .open = led_open, .release = led_close};static int __init led_init(void){ int result = 0; pr_info(\"LED Device is setting up\\n\"); result = register_chrdev(LED_MAJOR_DEV_N, LED_DEV_NAME, &led_ops); if(result < 0){ pr_warn(\"can not register the device!\\n\"); return -EIO; } __enable_led_mappings(); return 0;}static void __exit led_exit(void){ unregister_chrdev(LED_MAJOR_DEV_N, LED_DEV_NAME); __disable_led_mappings(); pr_info(\"LED Device is unhooked!\\n\");}module_init(led_init);module_exit(led_exit);
下一步我们测试一下。
make allmake upload
/module_test # lschrdev_application led.ko
很好,我们测试一下:
/module_test # insmod led.ko [ 1683.934802] LED Device is setting up[ 1683.938458] Ready to mappings the registers...[ 1683.943556] mappings the registers done![ 1683.947758] LED_CCGR1 ioremap to: 0bf83989[ 1683.952650] LEDBASE ioremap to: 139b7e53[ 1683.957123] LEDPAD_BASE ioremap to: 6242cd54[ 1683.961632] LEDDR_BASE ioremap to: c30aeb8d[ 1683.966090] LEDGDIR_BASE ioremap to: d8d7e76e[ 1683.970642] initialize the led registers[ 1683.974581] operations of led is accessable!/module_test # ./chrdev_application /dev/ccled read[ 1692.833090] [ 1692.833090] led device is opened!user process the read issue[ 1692.838451] [ 1692.838451] led device is reading!user receive from driver: closed@[ 1692.846531] [ 1692.846531] led device is released!/module_test # ./chrdev_application /dev/ccled write open[ 1697.176211] [ 1697.176211] led device is opened!args: 4user process the write is[ 1697.182000] [ 1697.182000] led device is ready writing!sue: open[ 1697.190256] [ 1697.190256] led device is released!/module_test # ./chrdev_application /dev/ccled write close[ 1700.050094] [ 1700.050094] led device is opened!args: 4user process the write is[ 1700.056790] [ 1700.056790] led device is ready writing!sue: close[ 1700.065277] [ 1700.065277] led device is released!/module_test # rmmod led.ko [ 1703.280996] set the led turning off...[ 1703.284808] set the led turning off done![ 1703.288831] Ready to unmappings the registers...[ 1703.293992] unmappings the registers done[ 1703.298254] LED Device is unhooked!
日志比较混乱,这是因为printf和pr_info相互争夺串口资源导致的,我们后面做日志level分离即可!
完结撒花!!!!
一补:4.18修正潜在的AutoBoot崩溃
把bootcmd设置成如下即可
setenv bootcmd \'echo \"Starting boot...\"; \\ if tftp 80800000 zImage; then echo \"Kernel loaded\"; \\ if tftp 83000000 imx6ull-14x14-charliechen-emmc.dtb; then echo \"DTB loaded\"; \\ bootz 80800000 - 83000000; \\ else echo \"Failed to load DTB\"; fi; \\ else echo \"Failed to load kernel\"; fi\'saveenv
Much Thanks To The Reference
笔者非常感谢这些开源分享的朋友,没有他们,我没办法做到这里!
UBoot参考的方案:正点原子imx6ull开发板移植新版本U-boot(uboot2022.04,有设备树)_imx6ull移植最新uboot-CSDN博客
DDR校验方案:NXP(I.MX6uLL)DDR3实验——DDR3初始化、校准、超频测试_i.mx6 ddr stress test tool-CSDN博客
LCD参数设置说明:正点原子imx6ull-mini-Linux驱动之Linux LCD 驱动实验(19)_imx6ull lcd帧率改变-CSDN博客
正点原子的教程
笔者自己的IMX6ULL学习系列教程