本文详细记录了移植uboot2022到I.MX6ULL上的过程和方法,并提出作者在移植中涉及的疑
问以及解答。希望对你有所帮助。
注:Uboot要能启动内核,需要配置好的.img文件和uboot环境变量,本文不涉及uboot环境变
量配置,只讲解如何修改uboot源码中的文件,最终生成.imx文件,适配开发板硬件。
目录
3.2 修改./include/configs/环境变量头文件
3.4 修改./arch/arm/mach-imx中的Kconfig文件
4.4 修改包含时钟配置的设备树(imx6ull-wdd.dtsi)
4.5 修改pinctrl电气属性(imx6ul-wdd.dtsi)
5.2.2.修改./board/freescale/mx6ull_wdd/mx6ull_wdd.c
一、要修改的文件框架
需要修改的文件,用tree列出来,如下所示,如果你用的不是imx系列控制器,你需要分别在以下的文件路径中找到适配硬件的文件,看文件名字就很清楚。
其中值得说明的是 ./arch/arm/dts 这个目录,存放设备树文件的目录。最后编译出的Uboot有很多类型,一般常用的是u-boot.bin和 u-boot-dtb.img。前者是包含uboot完整镜像,后者是在前者的基础上包含了设备树的二进制.dtb可执行文件,本文最终编译得到的是u-boot-dtb.imx。后缀为.imx是为了烧写进I.MX系列处理器的存储介质中的,而.img是通用的uboot镜像。
Uboot的设备树是用来配置Uboot引导的过程,若你想用网络比如tftp启动内核,你就需要去Uboot的设备树中配置以太网;同理,本文使用EMMC,稍后会在设备树中修改EMMC节点。Uboo的核心功能是引导内核启动,但其设备树并不会随着内核启动而失效,它会工作在内核级别,依然静态调控着系统。
二、获取源码
Uboot社区源码地址:Index of /pub/u-boot/
下载红框中的压缩包到linux中,并用 " tar -xjvf u-boot-2022.10.tar.bz2 "命令解压,得到一个文件夹。
三、修改配置文件
3.1 修改defconfig文件
defconfig文件是配置文件,用户可以在该文件中配置功能。 对于imx6ull,defconfig文件是 “configs/mx6ull_14x14_evk_defconfig” ,这个文件包含了编译出的uboot所具备的功能。在这里面写入配置,会被解析脚本,成为宏定义,写入某个头文件,最终编译时,各部分原代码会根据你写入的内容来执行源代码中的#ifdef。
首先复制一份配置文件,并改名(本文中,我把所有14x14_evk都改成了wdd)
cp configs/mx6ull_14x14_evk_defconfig configs/mx6ull_wdd_defconfig
在配置文件中修改第9和第11行
第9行是你需要编译出的目标,这个宏会在board/freescale/mx6ull中的Kconfig用到。
第11行使编译时所需要包含设备树的名称,在arch/arm/dts/中会有这个文件。
3.2 修改./include/configs/环境变量头文件
这个文件是修改Uboot环境变量的,本文不涉及环境变量的配置。同理,复制一份。
cp include/configs/mx6ullevk.h include/configs/mx6ull_wdd.h
在这个文件中我们唯一需要做的就是修改一下头文件定义。如果你不想看理论,只关注移植实操,可以直接跳到 3.3 修改./board中的板级文件夹 。
接下来我发表一些我的理解来尝试分析一下uboot启动环境变量是如何配置的,又是如何启动的。先来看几个重要的环境变量。
script是uboot启动的自动化脚本配置文件,启动时根据boot.scr文件的配置来启动。
console=ttymxc0是串口,用串口作为控制台,你的硬件如果有lcd,你可以在这里修改配置,使lcd成为终端;
fdt_high和initrd_high的地址值会让内核自动分配比该地址低的地址给二者,如果设置太低,可能会出问题,所以设置成最高地址后,启动时内核会在所有地址空间中分配一个合适的地址;
fdt_file是设备树文件,这里的设备树是内核的设备树,不是uboot的设备树,后面单独说;
fdt_addr是设备树在内存中的地址,设备树二进制可执行文件是存在emmc中的,但运行时需要在内存中,这个地址就指向了设备树从emmc中拷贝到内存中的地址。
boot_fdt可以理解成一个标志位,在启动时用到了。
mmcdev是你的mmc的设备号,通常是从0开始,但具体要看个人硬件。
mmcpart是mmc的分区,如果你不太清楚,可以在uboot命令行中使用命令mmcpart来查看。
mmcroot是根文件系统挂载的位置及属性,图中根文件系统就在/dev/mmcblk1p2,rw表示可读写,rootwait 是一个内核启动参数,告诉内核在启动时等待根文件系统设备准备好之后再继续。 以上说明根文件系统在emmc设备1中的第二个分区,启动时,会把这个分区作为根文件系统传递给内核。
mmcargs是mmc启动配置,它配置了串口为终端,配置了波特率,配置了根文件系统的属性。
我觉得重要的环境变量就这些,然后我们来看拷贝内核和启动过程。
loadbootscript的作用是加载一个配置的脚本文件script,它执行的命令:fatload mmc,代表从一个fat文件系统的存储介质(EMMC、SD卡等)上拷贝数据,${mmcdev}和${mmcart}在环境变量中已经配置好,是你要从哪个emmc设备的哪个分区拷贝。那拷贝到哪里去?在后面的变量${loadaddr}这个变量,最后的script通常指代的是脚本文件。
总结一下,就是从指定的emmc分区中拷贝了一个文件到指定的内存地址罢了。
这个明白了,接下来的loadimage和loadfdt也是一样的。也就是把内核和设备树加载到一个设定的内存地址。但这里有个疑惑,为什么内核加载的地址和脚本文件地址是一样的呢?其实是为了uboot的内存管理和空间资源的利用考虑。启动脚本文件的加载和执行必须在内核镜像加载之前完成,以便正确地设置好启动环境。所以,选择与内核镜像加载位置相同或相邻的位置,可以确保脚本被加载并立即执行,不会出现加载顺序错乱的问题。
再者,就是run mmcargs,就是从emmc启动了。上面说了,boot_fdt是一个标志位,这里如果boot_fdt是try,就会执行run loadfdt去加载设备树,然后用bootz去启动loadaddr和fdt_addr地址的文件,也就是内核镜像和设备树。至此,UBoot从EMMC启动完毕。
关于设备树再说一点,这里如果设备树是undefined,就会去判断你的处理器型号,给你分配合适的设备树,如果你想用自己编译好的设备树,就在环境变量中修改,然后把.dtb烧入到emmc中即可。比如我修改成了emmc中自己的设备树。
如果你不想用EMMC中配置好的Uboot环境变量,或者你想自己配置环境变量。你就可以在这个文件中修改环境变量,并在uboot命令行中先执行 "env default -a" 把环境变量恢复成编译时的环境变量,通过测试后,再执行saveenv保存到你的emmc中。
这里给出几个参考的点:fdt_file写成自己的设备树;确定好mmcpart的分区;还有最重要的一点是确定你的emmc介质是fat文件格式还是ext2等,这个如果没有确定好就会出现下面这个错误,因为我网络也没配置,自然就启动不了。
3.3 修改./board中的板级文件夹
先复制一个整个文件夹
cp /board/freescale/m6ullevk /board/freescale/mx6ull_wdd -r
cd /board/freescale/mx6ull_wdd
修改.c文件
mv mx6ullevk.c mx6ull_wdd.c
修改Makefile文件
修改Kconifg文件
修改MAINTAINERS文件
最后两行都是配置文件,不属于我们的配置,可以直接删掉。
修改imximage.cfg文件,只需要修改一下32行的路径名即可。
3.4 修改./arch/arm/mach-imx中的Kconfig文件
在469行加入如下内容,注意修改目标名称。
在 718行添加source变量,路径为board下自己定义好的板级配置包的路径。
四、修改设备树适配开发板EMMC
4.1 查看硬件原理图
原理图上不同的开发板连接引脚不同,在4.5修改电气属性时,修改成你的开发板连接的emmc引脚即可。
emmc控制器是支持1、4、8位的数据传输,8位是最快的数据传输方式。同时配置emmc时需要有三种不同的速率:低中高速。低速模式用于初始化阶段(如卡的识别、设置)。此时,eMMC 仅进行基本的通讯,传输速度较低。当 eMMC 设备初始化后,会进入较高的速度模式进行更快的数据传输。此时,eMMC 数据总线可能会进入较高的频率。在一些高级操作中,如在读取或写入大量数据时,eMMC 可能会使用更高的速率(如 UHS-I、UHS-II 或类似的高速模式)。这种模式通常要求对引脚进行更高精度的配置,以支持更高的信号频率和更严格的电气规范。
我们这里配置了三种不同的速率,主要是为了兼容各个工作模式。
在我的原理图中,数据引脚和NAND的数据引脚连在一起了,所以pinctrl配置NAND的引脚即可。
4.2 熟悉设备树包含结构
找到arch/arm/dts/imx6ull-14x14-evk.dts这个文件
可以看到它还包含着另外两个设备树文件,其中imx6ul-14x14-evk.dtsi是pinctrl配置文件,imx6ull.dtsi是配置引脚的时钟的设备树文件。
还是先复制一份
cd arch/arm/dts
cp imx6ull-14x14-evk.dts imx6ull-wdd.dts
cp imx6ul-14x14-evk.dtsi imx6ul-wdd.dtsi
cp imx6ull.dtsi imx6ull-wdd.dtsi
4.3 修改顶层设备树(imx6ull-wdd.dts)
在imx6ull-wdd.dts中新增一个设备节点,用于配置emmc,内容如下:
&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>;
no-1-8-v;
broken-cd;
keep-power-in-suspend;
wakeup-source;
non-removable;
status = "okay";
};
这里设置了三个pinctrl控制器。
bus-width设置了8位的传输数据位宽;
no-1-8-v设置了不支持1.8V电压;
broken-cd 表示忽略该 eMMC 控制器的卡检测信号,否则该信号存在问题可能导致emmc无法正常工作时;
keep-power-in-suspend 这个配置项表示在设备进入挂起状态时,eMMC 设备的电源应保持打开状态。也就是加大功耗,保持电源常开。
non-removable 表示该 eMMC 存储是不可移除的。因为我的开发板emmc设备是焊接上去的,所以加上不可以移除,防止误判。如果你的开发板emmc模块是可以拔插的,这一项不要写。
4.4 修改包含时钟配置的设备树(imx6ull-wdd.dtsi)
找到usdhc2这个节点,将这个节点下修成成下图所示。
&usdhc2 {
compatible = "fsl,imx6ull-usdhc", "fsl,imx6sx-usdhc";
assigned-clocks = <&clks IMX6UL_CLK_USDHC2_SEL>, <&clks IMX6UL_CLK_USDHC2>;
assigned-clock-parents = <&clks IMX6UL_CLK_PLL2_PFD2>;
assigned-clock-rates = <0>, <132000000>;
};
4.5 修改pinctrl电气属性(imx6ul-wdd.dtsi)
在iomux节点下新增三个节点,内容如下
&iomux{
.
.
.
.
pinctrl_usdhc2_8bit: usdhc2grp_8bit {
fsl,pins = <
MX6UL_PAD_NAND_RE_B__USDHC2_CLK 0x10069
MX6UL_PAD_NAND_WE_B__USDHC2_CMD 0x17059
MX6UL_PAD_NAND_DATA00__USDHC2_DATA0 0x17059
MX6UL_PAD_NAND_DATA01__USDHC2_DATA1 0x17059
MX6UL_PAD_NAND_DATA02__USDHC2_DATA2 0x17059
MX6UL_PAD_NAND_DATA03__USDHC2_DATA3 0x17059
MX6UL_PAD_NAND_DATA04__USDHC2_DATA4 0x17059
MX6UL_PAD_NAND_DATA05__USDHC2_DATA5 0x17059
MX6UL_PAD_NAND_DATA06__USDHC2_DATA6 0x17059
MX6UL_PAD_NAND_DATA07__USDHC2_DATA7 0x17059
>;
};
pinctrl_usdhc2_8bit_100mhz: usdhc2grp_8bit_100mhz {
fsl,pins = <
MX6UL_PAD_NAND_RE_B__USDHC2_CLK 0x100b9
MX6UL_PAD_NAND_WE_B__USDHC2_CMD 0x170b9
MX6UL_PAD_NAND_DATA00__USDHC2_DATA0 0x170b9
MX6UL_PAD_NAND_DATA01__USDHC2_DATA1 0x170b9
MX6UL_PAD_NAND_DATA02__USDHC2_DATA2 0x170b9
MX6UL_PAD_NAND_DATA03__USDHC2_DATA3 0x170b9
MX6UL_PAD_NAND_DATA04__USDHC2_DATA4 0x170b9
MX6UL_PAD_NAND_DATA05__USDHC2_DATA5 0x170b9
MX6UL_PAD_NAND_DATA06__USDHC2_DATA6 0x170b9
MX6UL_PAD_NAND_DATA07__USDHC2_DATA7 0x170b9
>;
};
pinctrl_usdhc2_8bit_200mhz: usdhc2grp_8bit_200mhz {
fsl,pins = <
MX6UL_PAD_NAND_RE_B__USDHC2_CLK 0x100f9
MX6UL_PAD_NAND_WE_B__USDHC2_CMD 0x170f9
MX6UL_PAD_NAND_DATA00__USDHC2_DATA0 0x170f9
MX6UL_PAD_NAND_DATA01__USDHC2_DATA1 0x170f9
MX6UL_PAD_NAND_DATA02__USDHC2_DATA2 0x170f9
MX6UL_PAD_NAND_DATA03__USDHC2_DATA3 0x170f9
MX6UL_PAD_NAND_DATA04__USDHC2_DATA4 0x170f9
MX6UL_PAD_NAND_DATA05__USDHC2_DATA5 0x170f9
MX6UL_PAD_NAND_DATA06__USDHC2_DATA6 0x170f9
MX6UL_PAD_NAND_DATA07__USDHC2_DATA7 0x170f9
>;
};
.
.
.
.
}
可以看到这里配置的其实就是原理图中显示出来的几个引脚的电气属性,每一组pinctrl其实只有一位数据被修改,也就是引脚的驱动能力,驱动能力强,速率就快。
4.6修改.dts的Makefile
找到./arch/arm/dts/Makfile并打开,找到CONFIG_MX6ULL,加入我们自己的设备树。这里只需要添加后缀为.dts的设备树文件即可,也就是顶层设备树。
五、编译与其他配置
5.1编译uboot
到此为止,如果你的配置都正确,你可以在uboot的根目录写一个脚本去编译uboot。而不用每次都手工配置。
vi build.sh
在里面写入如下内容 ,要记得带上一个设备树选项,不然会报错。
#! /bin/bash
make ARCH=arm CROSS_COMPILE=这里写你的交叉编译工具链名称(后面横杠别删)- distclean
make ARCH=arm CROSS_COMPILE=这里写你的交叉编译工具链名称(同上)- mx6ull_wdd_defconfig
make ARCH=arm CROSS_COMPILE=这里写你的交叉编译工具链名称(同上)- DEVICE_TREE=imx6ull-wdd -j8
再赋权,执行
sudo chmod 0777 build.sh
./build.sh
不出意外,编译通过,得到.imx文件
5.2其他配置
如果到这里你还不满意,你可以修改一些信息,在启动时打印出你的信息。
5.2.1修改imx6ull-wdd.dts
打开文件,在compatible属性中把开发板的名字改成自己的。上面一行model是CPU的名字。
5.2.2.修改./board/freescale/mx6ull_wdd/mx6ull_wdd.c
找到这个位置,使用env_set函数修改名字,可以把其他都注释掉。这里修改的名字信息会存在uboot的环境变量中。根据函数名字也很好理解,毕竟是env_set嘛。
再找到checkboard这个函数,这里是启动时会打印的信息,修改puts的内容,就可以打印了。
六、上机测试 
可以看到自己移植的uboot已经生效了,时间和打印信息都对得上,也从emmc启动了。但箭头所指不知道为什么还会出现一个can't set block device。目前自己能力和时间有效,以后如果发现这个bug怎么解决再补充上,目前不再追究了,你就说跑没跑起来吧?
这是我第一次移植uboot,发表了自己的一些拙见,希望能帮到你们,如果有不对的地方,还请指出,共同学习。