正点原子嵌入式linux驱动开发——Linux内核移植

之前的两篇笔记,简单了解了一下Linux内核顶层 Makefile和Linux内核的启动流程,本篇内容来学习一下如何将ST官方提供的Linux内核移植到正点原子的STM32MP157开发板上。通过本章的学习,将掌握如何将半导体厂商提供的Linux BSP包移植到自己的平台上

Linux内核初次编译

编译正点原子出厂Linux源码

之前的Linux内核第一篇笔记,就是编译正点原子STM32MP157开发板出厂的Linux内核,可以去回顾一下。

运行测试

成功编译正点原子出厂Linux系统以后,会得到uImage和对应的stm32mp157d-atk.dtb设
备树,本节就测试一下两个文件能不能正常启动,将uImage和stm32mp157d-atk.dtb这两个文件发送到Ubuntu的TFTP服务器目录下。启动开发板,uboot通过tftp命令从Ubuntu中uImage和stm32mp157d-atk.dtb并启动。为了不用每次手动输入命令,可以直接设置uboot中的bootcmd环境变量为:

setenv bootcmd 'tftp c2000000 uImage;tftp c4000000 stm32mp157d-atk.dtb;bootm c2000000 - (有空格 ) c4000000'
saveenv

启动后Linux输出如下图所示信息:
Linux内核启动log信息
从上图中可以看出,Linux内核已经运行成功,且当前Linux内核编译时间为2022年12月11日16:52:12。

注意!如果出现下图中所示“end Kernel panic”错误为正常现象
根文件系统缺失错误
上图中错误是由于根文件系统缺失导致的,也就是没有指定根文件系统,根文件系统的制作之后会有详细的笔记。

编译ST官方Linux系统

上一节编译了正点原子提供的出厂系统,主要是学习一下Linux系统的编译流程,最终是要将ST原厂提供的Linux系统移植到正点原子的STM32MP157开发板上,因此肯定要先编译一下ST原厂的系统。

ST官方Linux源码打补丁

获取ST官方Linux源码

首先肯定要获取到ST官方的Linux源码,这个已经在之前的学习中获取到了,进入到对应的Linux源码目录,命令如下:

cd /home/zuozhongkai/linux/atk-mp1/stm32mp1-openstlinux-5.4-dunfell-mp1-20-06-24/sources/arm-ostl-linux-gnueabi/linux-stm32mp-5.4.31-r0 //进入 ST官方 Linux源码

ST官方Linux源码如下图所示:
ST官方Linux源码
从上图可以看出,ST官方的Linux源码包有各种.patch补丁文件以及Linux源码压缩包 ,注意上图中有 4个以“fragment”开头的.config文件,这些文件是默认配置文件的补丁文件,用来生成默认配置文件,稍后会讲解怎么操作。

首先解压上图中“linux-5.4.31.tar.xz”这个真正的源码包压缩包,解压命令如下:

tar -vxf linux-5.4.31.tar.xz

解压完成后会得到名为“linux-5.4.31”的Linux源码文件夹,如下图所示:
解压得到Linux源码

内核打补丁

上面已经解压出来了Linux的源码文件,接下来就要对其打补丁,进入到上面解压出来的linux-5.4.31目录,然后执行相应的打补丁命令:

cd linux-5.4.31/ //进入 Linux源码目录
for p in `ls -1 ../*.patch`; do patch -p1 < $p; done //打补丁

打补丁结果如下图所示:
打补丁结果

生成默认配置文件

编译Linux内核的时候也需要使用“make xxx_defconfig”来对其进行默认配置,比如编译
正点原子出厂Linux系统的时候使用了“make stm32mp1_atk_defconfig”,其中stm32mp1_atk_defconfig就是正点原子开发板使用的默认配置文件。但是ST官原厂 Linux内核需要先生成默认配置文件,并且对其进行打补丁,进入Linux内核源码根目录下,然后执行如下命令:

cd linux-5.4.31/ //进入到 linux内核
make ARCH=arm multi_v7_defconfig "fragment*.config" //生成默认配置文件

完成以后结果如下图所示:
生成.config文件
上面只是在Linux源码根目录下生成了.config配置文件,如下图所示:
.config文件
.config文件非常重要,Linux内核的所有配置项最终都保存在.config文件里面,最终编译Linux内核的时候需要读取.config里面的配置项!此时只是生成了.config,还并没有将原来的这些fragment config补丁文件打进去,执行如下两条命令打补丁:

for f in `ls -1 ../fragment*.config`; do scripts/kconfig/merge_config.sh -m -r .config $f; done
yes '' | make ARCH=arm oldconfig

第1条命令是将源码目录下的fragment config补丁打到.config文件里面。

第2条命令是对oldconfig的所有选项都选择yes,这些选项最终都会写入到.config里面。
注意, 这条命令yes后面是两个“ ’ ”,而不是一个“ " ”,不要输入错了!!

完成后如下图所示:
配置完成的.config
至此,Linux源码根目录下的.config文件就已经保存了所有的配置项,所以只需要复制一份 .config作为默认配置文件即可,复制命令如下:

cp .config ./arch/arm/configs/stm32mp1_atk_defconfig

此时在Linux内核的/arch/arm/configs目录下存在一个名为stm32mp1_atk_defconfig的默认配置文件,如下图所示:
默认配置文件
至此,Linux内核全部打完补丁,linux-5.4.31目录就是要移植的Linux源码,但是默认的linux-5.4.31目录路径有点长,不适合阅读和编译。所以可以新建一个名为“my_linux”的目录来保存要移植的linux源码,然后将打完补丁的linux源码linux-5.4.31拷贝到“my_linux”目录下,命令如下:

cd /home/zuozhongkai/linux/atk-mp1/stm32mp1-openstlinux-5.4-dunfell-mp1-20-06-24/sources/arm-ostl-linux-gnueabi/linux-stm32mp-5.4.31-r0
cp linux-5.4.31 /home/zuozhongkai/linux/atk-mp1/linux/my_linux/ -rf //拷贝

拷贝完成以后,可以创建VSCode工程,方便移植和阅读。

编译ST官方Linux源码

上一小节已经准备好了ST官方Linux源码,存放到了my_ulinux目录下,本小节来编译一下这个Linux源码。

修改Makefile

首先修改一下Linux源码的Makefile文件,也可以不修改,但是在编译的时候需要多输入一些参数,为了偷懒,还是修改一下。修改方法已经在跟之前TF-A,uboot以及Linux内核使用的时候讲解过了,就是在Makefile文件里面添加ARCH和CROSS_COMPILE这两个变量的值,如下图所示:
设置ARCH和CROSS_COMPILE变量值
ST官方Linux肯定适配了官方的STM32MP1 EVK开发板,就编译EVK开发板对应的Linux内核和设备树,编译完成以后在正点原子的STM32MP1开发板启动,看看能不能运行,不能的话就要修改相应的文件,这就是Linux的移植。创建一个名为“stm32mp157d_atk.sh”的编译脚本,脚本内容如下:

示例代码17.1.2.1 stm32mp157d_atk.sh文件内容 
1 #!/bin/sh 
2 make distclean 
3 make stm32mp1_atk_defconfig 
4 make menuconfig 
5 make uImage dtbs LOADADDR=0XC2000040 -j16

给予stm32mp157d_atk.sh可执行文件,然后运行此脚本,命令如下:

chmod 777 stm32mp157d_atk.sh //给予可执行权限,仅第一次
./stm32mp157d_atk.sh //运行编译脚本

等待编译完成,编译完成以后的到uImage镜像文件和设备树,其中STM32MP157系列的设备树有很多,如下图所示:
编译得到的.dtb文件
从上图可以看出,此时有很多.dtb文件,这里使用stm32mp157d-ed1.dtb这个文件。

网络驱动修改

V1.2版本核心板网络PHY芯片采用RTL8211,ST官方源码默认已经支持了RTL8211,所以不需要进行任何修改。但是V1.3以后的核心板网络PHY芯片改为了裕太电子的YT8511,ST官方源码默认没有支持YT8511,因此需要自行移植相关的网络驱动。相关驱动以及修改方法已经放到了开发板光盘中。

将motorcomm.c和motorcomm_phy.h分别拷贝到Linux源码下的drivers/net/phy和include/linux目录下。拷贝完成以后修改drivers/net/phy/Makefile文件,加上下面这句:

obj-$(CONFIG_MOTORCOMM_PHY) += motorcomm.o

修改以后的Makefile如下图所示:
修改后的Makefile
另外还需要修改drivers/net/phy/Kconfig文件,加入如下内容:

示例代码17.2.3.1 Kconfig添加的内容 
1 config MOTORCOMM_PHY 
2 tristate "Motorcomm PHYs" 
3 ---help--- 
4 Supports the YT8010, YT8510, YT8511, YT8512 PHYs.

添加完成后的Kconfig文件如下图所示:
修改后的Kconfig
最后输入“make menuconfig”,打开linux内核配置界面,使能YT8511驱动,配置路径如下:

-> Device Drivers
-> Network device support (NETDEVICES [=y])
-> PHY Device support and infrastructure (PHYLIB [=y])
-> <*> Motorcomm PHYs //将 YT8511驱动编译进内核

如下图所示:
使能YT8511驱动
网络驱动修改完成后,按照上一小节的方法重新编译linux内核即可。

启动测试

上一小节已经编译出来了ST官方开发板对应的uImage和 stm32mp157d-ed1.dtb设备树,本章直接在正点原子的STM32MP157开发板上运行,看看ST官方系统能不能运行下去。将uImage和stm32mp157d-ed1.dtb发送到Ubuntu下的tftp服务器目录下,然后通过uboot的tftp命令下载并启动,Linux系统运行log信息如下图所示:
ST官方Linux系统运行结果
从上图可以看出,ST官方开发板的Linux系统在正点原子STM32MP157开发板上启动成功,所以后续的移植就要简单的多(实际不需要移植,直接在ST官方开发板相应文件上修改即可,但是为了学习,还是学习一下如何在Linux源码里面添加自己的开发板),只需要参考ST官方开发板创建一个设备树即可

在Linux中添加自己的开发板

添加开发板对应的默认配置文件

首先就是添加开发板对应的默认配置文件,这里直接使用上文中制作出来的stm32mp1_atk_defconfig文件即可。

添加开发板对应的设备树

新建相应的设备树文件

上面使用ST官方开发板的设备树已经成功启动了Linux系统,但是在实际开发中肯定要添加一份自己开发板所对应的设备树。正点原子STM32MP157开发板是以ST官方的开发板为蓝本制作的,所以设备树也是参考ST官方开发板的。在arch/arm/boot/dts/目录下新建名为“stm32mp157d-atk.dtsi”的设备树头文件,然后将stm32mp15xx-edx.dtsi文件里面的内容全部复制到 stm32mp157d-atk.dtsi里面。

再新建一个名为“stm32mp157d-atk.dts”的文件,将stm32mp157d-ed1.dts文件里面的内容都拷贝到stm32mp157d-atk.dts里面,如下图所示 :
stm32mp157d-atk.dts文件内容
记得一定要将上图中14行原来的“stm32mp15xx-edx.dtsi”头文件改为“stm32mp15d-atk.dtsi”。

修改stm32mp157d-atk.dtsi文件

tm32mp157d-atk.dtsi里面的内容是直接复制的ST官方开发板的stm32mp15xx-edx.dtsi,所以里面的很多配置是针对ST官方开发板的,比如PMIC芯片,这些配置是不需要的,要删除掉。正点原子开发板没有用到集成PMIC芯片,因此还需要在设备树里面添加一些电源节点信息,修改完成以后的stm32mp157d-atk.dtsi文件内容如下所示:

// SPDX-License-Identifier: (GPL-2.0+ OR BSD-3-Clause)
/*
 * Copyright (C) STMicroelectronics 2017 - All Rights Reserved
 * Author: Ludovic Barre <ludovic.barre@st.com> for STMicroelectronics.
 */

#include "stm32mp157-m4-srm.dtsi" 
#include "stm32mp157-m4-srm-pinctrl.dtsi"
#include <dt-bindings/gpio/gpio.h>
#include <dt-bindings/mfd/st,stpmic1.h>

/ {
	memory@c0000000 {
		device_type = "memory";
		reg = <0xC0000000 0x40000000>;
	};

	reserved-memory {
		#address-cells = <1>;
		#size-cells = <1>;
		ranges;

		mcuram2: mcuram2@10000000 {
			compatible = "shared-dma-pool";
			reg = <0x10000000 0x40000>;
			no-map;
		};

		vdev0vring0: vdev0vring0@10040000 {
			compatible = "shared-dma-pool";
			reg = <0x10040000 0x1000>;
			no-map;
		};

		vdev0vring1: vdev0vring1@10041000 {
			compatible = "shared-dma-pool";
			reg = <0x10041000 0x1000>;
			no-map;
		};

		vdev0buffer: vdev0buffer@10042000 {
			compatible = "shared-dma-pool";
			reg = <0x10042000 0x4000>;
			no-map;
		};

		mcuram: mcuram@30000000 {
			compatible = "shared-dma-pool";
			reg = <0x30000000 0x40000>;
			no-map;
		};

		retram: retram@38000000 {
			compatible = "shared-dma-pool";
			reg = <0x38000000 0x10000>;
			no-map;
		};
	};

	vddcore: buck1 {
		compatible = "regulator-fixed";
		regulator-name = "vddcore";
		regulator-min-microvolt = <1200000>;
		regulator-max-microvolt = <1350000>;
		regulator-always-on;
		regulator-boot-on;
	};

	v3v3: regulator-3p3v {
		compatible = "regulator-fixed";
		regulator-name = "v3v3";
		regulator-min-microvolt = <3300000>;
		regulator-max-microvolt = <3300000>;
		regulator-always-on;
		regulator-boot-on;
	};
};

&cpu0{
	cpu-supply = <&vddcore>;
};

&crc1 {
	status = "okay";
};

&dma1 {
	sram = <&dma_pool>;
};

&dma2 {
	sram = <&dma_pool>;
};

&dts {
	status = "okay";
};

&ethernet0 {
	status = "okay";
	pinctrl-0 = <&ethernet0_rgmii_pins_a>;
	pinctrl-1 = <&ethernet0_rgmii_pins_sleep_a>;
	pinctrl-names = "default", "sleep";
	phy-mode = "rgmii-id";
	max-speed = <1000>;
	phy-handle = <&phy0>;

	mdio0 {
		#address-cells = <1>;
		#size-cells = <0>;
		compatible = "snps,dwmac-mdio";
		phy0: ethernet-phy@0 {
			reg = <0>;
		};
	};
};

&hash1 {
	status = "okay";
};

&ipcc {
	status = "okay";
};

&iwdg2 {
	timeout-sec = <32>;
	status = "okay";
};

&rng1 {
	status = "okay";
};

&rtc {
	status = "okay";
};

&sdmmc1 {
	pinctrl-names = "default", "opendrain", "sleep";
	pinctrl-0 = <&sdmmc1_b4_pins_a>;
	pinctrl-1 = <&sdmmc1_b4_od_pins_a>;
	pinctrl-2 = <&sdmmc1_b4_sleep_pins_a>;
	broken-cd;
	st,neg-edge;
	bus-width = <4>;
	vmmc-supply = <&v3v3>;
	status = "okay";
};

&sdmmc2 {
	pinctrl-names = "default", "opendrain", "sleep";
	pinctrl-0 = <&sdmmc2_b4_pins_a>;
	pinctrl-1 = <&sdmmc2_b4_od_pins_a>;
	pinctrl-2 = <&sdmmc2_b4_sleep_pins_a>;
	non-removable;
	st,neg-edge;
	bus-width = <8>;
	vmmc-supply = <&v3v3>;
	keep-power-in-suspend;
	status = "okay";
};

&sram {
	dma_pool: dma_pool@0 {
		reg = <0x50000 0x10000>;
		pool;
	};
};

&uart4 {
	pinctrl-names = "default", "sleep", "idle";
	pinctrl-0 = <&uart4_pins_a>;
	pinctrl-1 = <&uart4_sleep_pins_a>;
	pinctrl-2 = <&uart4_idle_pins_a>;
	pinctrl-3 = <&uart4_pins_a>;
	/delete-property/dmas;
	/delete-property/dma-names;
	status = "okay";
};

编译stm32mp157d-atk.dts设备树

最后肯定要编译stm32mp157d-atk.dts,得到对应的.dtb文件,打开arch/arm/boot/dts/Makefile,到“dtb-$(CONFIG_ARCH_STM32)”配置项,在此配置项中加入“stm32mp157d-atk.dtb”,完成以后如下图所示:
Makefile文件
上图中第1011行添加了“stm32mp157d-atk.dtb”,表示编译的时候也将stm32mp157d-atk.dts编译为stm32mp157d-atk.dtb。

关闭内核模块验证

后续做Linux驱动实验的时候都是编译驱动模块,然后在系统里面加载,加载的时候系统会验证模块,有时候会验证出错。比如板子运行的系统和编译驱动模块所用的系统不一致的时候,这样为学习带来了很大的不便,为了方便开发,可以关闭内核模块验证。打开默认配置文件stm32mp1_atk_defconfig,里面有如下所示配置项目:

CONFIG_MODULE_SIG=y
CONFIG_MODULE_SIG_ALL=y
CONFIG_MODULE_SIG_SHA256=y
CONFIG_MODULE_SIG_HASH="sha256"

将上面4个配置项屏蔽,如下图所示:
关闭内核模块验证功能
另外,也可以直接在Linux图形化配置界面上关掉内核验证模块,输入如下命令打开Linux图形化配置界面:

make menuconfig

配置路径如下:

-> Enable loadable module support (MODULES [=y])
->Module signature verification

取消对“Module signature verification”选项的选中,如下图所示:
取消内核模块验证
配置完成后退出,打开.config文件,查看还有没有CONFIG_MODULE_SIG配置项,如下图所示:
.config文件
从上图可以看出,COFIG_MODULE_SIG选项没有设置,同样的CONFIG_MODULE_SIG_ALL、CONFIG_MODULE_SIG_SHA256和CONFIG_MODULE_SIG_HASH也一起消失
了。

最后需要将配置项保存到stm32mp1_atk_defconfig里面,打开图形化配置界面,选择<Save>项目,将修改后的所有配置项保存到stm32mp1_atk_defconfig文件里面,如下图所示:
保存默认配置
后续试验中,只要通过图形化界面修改了Linux内核配置,最好及时将其保存到stm32mp1_atk_defconfig文件。因为图形化界面修改的配置只是暂时保存到.config文件里面,一旦使用“make clean”清理工程,那么.config文件就会被删除掉,所有的配置也就丢失了!

关闭内核log信息时间戳

仔细观察会发现Linux系统启动的时候每行信息前面都会打印出相应的时间,如下图所示:
log时间信息输出
从上图可以看出,每行log信息前面都有时间信息,调试时时间信息没什么用而且占据很长空间,可以通过配置内核将其关闭,配置路径如下:

-> Kernel hacking
-> printk and dmesg options
->Show timing information on printks //取消选中

如下图所示:
取消printk时间戳

编译测试

设备树修改好以后就可以编译了,因为只是修改了设备树,所以编译脚本直接用之前创建的stm32mp157d_atk.sh编译脚本即可!

编译完成以后在arch/arm/boot目录下生成uImage镜像,在arch/arm/boot/dts目录下生成stm32mp157d-atk.dtb文件,将这两个文件拷贝到 tftp服务器目录下,然后在uboot中使用tftp命
令下载并运行,命令如下:

tftp c2000000 uImage
tftp c4000000 stm32mp157d-atk.dtb
bootm c2000000 - c4000000

当出现如下图所示log信息后就说明Linux启动运行成功:
Linux运行log信息

烧写系统镜像到EMMC中

上面已经测试过了Linux系统镜像和设备树,但是是通过tftp命令从网络上下载测试的,实际产品开发中最终是要将系统烧写到外部Flash中的,比如EMMC。本节来学习一下如何将uIamge和stm32mp157d-atk.dtb打包成ext4格式,然后通过STM32CubeProgrammer烧写至EMMC中,最终启动EMMC里的Linux系统。

系统镜像打包

首先就是将uImage和stm32mp157d-atk.dtb打包在一起,格式为ext4格式。当然了,也可以向这个ext4格式打包文件里面添加其他的内容,比如图片等,本节只用到uImage和stm32mp157d-atk.dtb。

打包过程在Ubuntu完成,首先新建一个名为“bootfs”的文件夹,然后将uImage和stm32mp157d-atk.dtb这两个文件放到bootfs文件夹下。

新建ext4格式磁盘

首先新建一个ext4格式的磁盘,然后挂载这个ext4格式的磁盘,将stm32mp157d-atk.dtb和uImage拷贝到这个ext4磁盘即可。

进入到bootfs文件夹,然后输入如下命令创建ext4磁盘:

示例代码17.4.1.1 ext4磁盘创建命令 
1 cd bootfs 
2 dd if=/dev/zero of=bootfs.ext4 bs=1M count=10 
3 mkfs.ext4 -L bootfs bootfs.ext4

第1行,进入到bootfs目录。

第2行,使用dd命令创建一个名为bootfs.ext4的磁盘, of指定磁盘名字为“bootfs.ext4”,bs指定磁盘输入/输出块大小为1MB,count指定磁盘的块数量为10个。因此bootfs.ext4只能存放不超过10MB的文件,如果要存放的文件总大小超过10MB,那么就要适当调整count参数的大小。

第3行,使用mkfs.ext4将bootfs.ext4磁盘格式化为ext4格式。

完成以后就会生成名为“bootfs.ext4”的磁盘,如下图所示:
bootfs.ext4磁盘

将系统镜像拷贝到ext4磁盘中

首先创建一个目录用来挂载前面制作制作出来的bootfs.ext4,比如创建目录/mnt/bootfs,命令如下:

sudo mkdir /mnt/bootfs

然后使用mount命令将bootfs.ext4挂载到/mnt/bootfs目录下,命令如下:

cd /home/zuozhongkai/linux/atk-mp1/linux/bootfs
sudo mount bootfs.ext4 /mnt/bootfs/

挂载成功以后就将上图中的uImage和stm32mp157d-atk.dtb拷贝到/mnt/bootfs目录
下,命令如下:

cd /home/zuozhongkai/linux/atk-mp1/linux/bootfs
sudo cp uImage stm32mp157d-atk.dtb /mnt/bootfs/

拷贝完成后使用umount卸载/mnt/bootfs即可,命令如下:

sudo umount /mnt/bootfs

至此,uImage和stm32mp157d-atk.dtb就已经打包到了之前的bootfs.ext4中,稍后使用STM32CubeProgrammer软件将其烧写到EMMC里面。烧写之前最好在Windows下打开bootfs.ext4看一下,看看是否已经将uImage和stm32mp157d-atk.dtb打包进去,如下图所示:
bootfs.ext4

烧写到EMMC

接下来就是将上一小节打包好的ext4格式的bootfs.ext4烧写到开发板的EMMC里面,使
用STM32CubeProgrammer软件完成此操作。将bootfs.ext4拷贝到以前创建的images目录下,而后修改FlashLayout文件tf-s.tsv,在后面加入bootfs.ext4少些脚本,如下图所示:
修改后的FlashLayout
上图中第7行就是bootfs.ext4的烧写脚本,设置好以后就可以使用STM32CubeProgrammer烧写系统了,烧写完成以后设置拨码开关从EMMC启动,启动以后进入uboot的命令行,输入如下命令查看EMMC分区2里面是否正确烧写了uImage和stm32mp157d-atk.dtb:

ext4ls mmc 1:2

结果如下图所示:
EMMC分区2
从上图可以看出,此时EMMC的分区2存在uImage和stm32mp157d-atk.dtb这两个文件,设置bootcmd环境变量,从EMMC里面读取系统镜像和设备树并启动,命令如下:

setenv bootcmd 'ext4load mmc 1:2 c2000000 uImage;ext4load mmc 1:2 c4000000 stm32mp157d-atk.dtb;bootm c2000000 - c4000000'
saveenv
boot

如果烧写正常就会从EMMC分区2加载系统并运行,如下图所示:
EMMC启动系统
可以看出,将系统镜像烧写到EMMC里面还是很繁琐的,因此一般都是在产品最终开发完成,出厂的时候才会烧写到EMMC里面。在调试阶段都不会烧写到EMMC里面,而是在uboot里面使用tftp或nfs命令通过网络下载系统镜像并运行,这样当修改了系统以后只需要将系统镜像复制到tftp或nfs服务器目录即可,极大的简化开发方式

根文件系统缺失错误

Linux内核启动以后是需要根文件系统的,根文件系统存在哪里是由uboot的bootargs环境变量指定的,它会传递给Linux内核作为命令行(command line)参数。比如之前uboot移植中设置root=/dev/mmcblk2p3,也就是说根文件系统存储在/dev/mmcblk2p3中,也就是EMMC的分区 3中。 如果不设置根文件系统路径,或者说根文件系统路径设置错误的话会出现什么问题?这个问题是很常见的,在实际的工作中开发一个产品,这个产品的第一版硬件出来以后我们是没有对应的根文件系统可用的,必须要自己做根文件系统。 在没有根文件系统的情况下,Linux内核启动的时候就会有如下图所示的错误:
根文件系统缺失错误
在上图中的最后一行为:

end Kernel panic - not syncing: VFS: Unable to mount root fs on unknown-block(0,0)

也就是提示内核崩溃,VFS(虚拟文件系统)不能挂载根文件系统,因为根文件系统目录不存在。解决方法就是制作根文件系统,并且设置uboot的bootargs环境变量,指定根文件系统所在的目录。

总结

完成Linux内核移植,需要完成以下步骤:

编译ST官方源码

首先进入已经获取得到的Linux源码,进入目录后解压,解压完成后进入源码目录并打补丁,而后生成默认配置文件.config,之后对这个文件继续打补丁,最后复制一份作为自己的默认配置文件并复制到自己的文件夹中。

cd /home/zuozhongkai/linux/atk-mp1/stm32mp1-openstlinux-5.4-dunfell-mp1-20-06-24/sources/arm-ostl-linux-gnueabi/linux-stm32mp-5.4.31-r0 //进入ST官方Linux源码
tar -vxf linux-5.4.31.tar.xz
cd linux-5.4.31/ //进入Linux源码目录
for p in `ls -1 ../*.patch`; do patch -p1 < $p; done //打补丁
make ARCH=arm multi_v7_defconfig "fragment*.config" //生成默认配置文件
for f in `ls -1 ../fragment*.config`; do scripts/kconfig/merge_config.sh -m -r .config $f; done
yes '' | make ARCH=arm oldconfig
cp .config ./arch/arm/configs/stm32mp1_atk_defconfig
cd /home/zuozhongkai/linux/atk-mp1/stm32mp1-openstlinux-5.4-dunfell-mp1-20-06-24/sources/arm-ostl-linux-gnueabi/linux-stm32mp-5.4.31-r0
cp linux-5.4.31 /home/zuozhongkai/linux/atk-mp1/linux/my_linux/ -rf //拷贝

如之前的TF-A以及U-Boot一样,需要配置ARCH和CROSS_COMPILE,然后写一个脚本来编译就好了。脚本名为“stm32mp157d_atk.sh”,内容如下

示例代码17.1.2.1 stm32mp157d_atk.sh文件内容 
1 #!/bin/sh 
2 make distclean 
3 make stm32mp1_atk_defconfig 
4 make menuconfig 
5 make uImage dtbs LOADADDR=0XC2000040 -j16

之后给予权限并运行脚本即可。

chmod 777 stm32mp157d_atk.sh //给予可执行权限,仅第一次
./stm32mp157d_atk.sh //运行编译脚本

之后需要修改网络驱动,换成自己的PHY芯片,将motorcomm.c和motorcomm_phy.h分别拷贝到Linux源码下的drivers/net/phy和include/linux目录下。拷贝完成以后修改drivers/net/phy/Makefile文件,加上下面这句:

obj-$(CONFIG_MOTORCOMM_PHY) += motorcomm.o

然后修改drivers/net/phy/Kconfig文件,添加如下内容:

示例代码17.2.3.1 Kconfig添加的内容 
1 config MOTORCOMM_PHY 
2 tristate "Motorcomm PHYs" 
3 ---help--- 
4 Supports the YT8010, YT8510, YT8511, YT8512 PHYs.

最后通过“make menuconfig”配置使能YT8511驱动:

-> Device Drivers
-> Network device support (NETDEVICES [=y])
-> PHY Device support and infrastructure (PHYLIB [=y])
-> <*> Motorcomm PHYs //将 YT8511驱动编译进内核

添加开发板

arch/arm/boot/dts/目录下新建名为“stm32mp157d-atk.dtsi”的设备树头文件,然后将stm32mp15xx-edx.dtsi文件里面的内容全部复制到stm32mp157d-atk.dtsi里面。

再新建一个名为“stm32mp157d-atk.dts”的文件,将stm32mp157d-ed1.dts文件里面的内容都拷贝到 stm32mp157d-atk.dts里面。要将14行原来的“stm32mp15xx-edx.dtsi”头文件改为stm32mp15d-atk.dtsi。

修改stm32mp157d-atk.dtsi为如下内容:(添加电源节点信息)

// SPDX-License-Identifier: (GPL-2.0+ OR BSD-3-Clause)
/*
 * Copyright (C) STMicroelectronics 2017 - All Rights Reserved
 * Author: Ludovic Barre <ludovic.barre@st.com> for STMicroelectronics.
 */

#include "stm32mp157-m4-srm.dtsi" 
#include "stm32mp157-m4-srm-pinctrl.dtsi"
#include <dt-bindings/gpio/gpio.h>
#include <dt-bindings/mfd/st,stpmic1.h>

/ {
	memory@c0000000 {
		device_type = "memory";
		reg = <0xC0000000 0x40000000>;
	};

	reserved-memory {
		#address-cells = <1>;
		#size-cells = <1>;
		ranges;

		mcuram2: mcuram2@10000000 {
			compatible = "shared-dma-pool";
			reg = <0x10000000 0x40000>;
			no-map;
		};

		vdev0vring0: vdev0vring0@10040000 {
			compatible = "shared-dma-pool";
			reg = <0x10040000 0x1000>;
			no-map;
		};

		vdev0vring1: vdev0vring1@10041000 {
			compatible = "shared-dma-pool";
			reg = <0x10041000 0x1000>;
			no-map;
		};

		vdev0buffer: vdev0buffer@10042000 {
			compatible = "shared-dma-pool";
			reg = <0x10042000 0x4000>;
			no-map;
		};

		mcuram: mcuram@30000000 {
			compatible = "shared-dma-pool";
			reg = <0x30000000 0x40000>;
			no-map;
		};

		retram: retram@38000000 {
			compatible = "shared-dma-pool";
			reg = <0x38000000 0x10000>;
			no-map;
		};
	};

	vddcore: buck1 {
		compatible = "regulator-fixed";
		regulator-name = "vddcore";
		regulator-min-microvolt = <1200000>;
		regulator-max-microvolt = <1350000>;
		regulator-always-on;
		regulator-boot-on;
	};

	v3v3: regulator-3p3v {
		compatible = "regulator-fixed";
		regulator-name = "v3v3";
		regulator-min-microvolt = <3300000>;
		regulator-max-microvolt = <3300000>;
		regulator-always-on;
		regulator-boot-on;
	};
};

&cpu0{
	cpu-supply = <&vddcore>;
};

&crc1 {
	status = "okay";
};

&dma1 {
	sram = <&dma_pool>;
};

&dma2 {
	sram = <&dma_pool>;
};

&dts {
	status = "okay";
};

&ethernet0 {
	status = "okay";
	pinctrl-0 = <&ethernet0_rgmii_pins_a>;
	pinctrl-1 = <&ethernet0_rgmii_pins_sleep_a>;
	pinctrl-names = "default", "sleep";
	phy-mode = "rgmii-id";
	max-speed = <1000>;
	phy-handle = <&phy0>;

	mdio0 {
		#address-cells = <1>;
		#size-cells = <0>;
		compatible = "snps,dwmac-mdio";
		phy0: ethernet-phy@0 {
			reg = <0>;
		};
	};
};

&hash1 {
	status = "okay";
};

&ipcc {
	status = "okay";
};

&iwdg2 {
	timeout-sec = <32>;
	status = "okay";
};

&rng1 {
	status = "okay";
};

&rtc {
	status = "okay";
};

&sdmmc1 {
	pinctrl-names = "default", "opendrain", "sleep";
	pinctrl-0 = <&sdmmc1_b4_pins_a>;
	pinctrl-1 = <&sdmmc1_b4_od_pins_a>;
	pinctrl-2 = <&sdmmc1_b4_sleep_pins_a>;
	broken-cd;
	st,neg-edge;
	bus-width = <4>;
	vmmc-supply = <&v3v3>;
	status = "okay";
};

&sdmmc2 {
	pinctrl-names = "default", "opendrain", "sleep";
	pinctrl-0 = <&sdmmc2_b4_pins_a>;
	pinctrl-1 = <&sdmmc2_b4_od_pins_a>;
	pinctrl-2 = <&sdmmc2_b4_sleep_pins_a>;
	non-removable;
	st,neg-edge;
	bus-width = <8>;
	vmmc-supply = <&v3v3>;
	keep-power-in-suspend;
	status = "okay";
};

&sram {
	dma_pool: dma_pool@0 {
		reg = <0x50000 0x10000>;
		pool;
	};
};

&uart4 {
	pinctrl-names = "default", "sleep", "idle";
	pinctrl-0 = <&uart4_pins_a>;
	pinctrl-1 = <&uart4_sleep_pins_a>;
	pinctrl-2 = <&uart4_idle_pins_a>;
	pinctrl-3 = <&uart4_pins_a>;
	/delete-property/dmas;
	/delete-property/dma-names;
	status = "okay";
};

最后打开arch/arm/boot/dts/Makefile,到“dtb-$(CONFIG_ARCH_STM32)”配置项,在此配置项中加入“stm32mp157d-atk.dtb”。

关闭内核模块验证:

CONFIG_MODULE_SIG=y
CONFIG_MODULE_SIG_ALL=y
CONFIG_MODULE_SIG_SHA256=y
CONFIG_MODULE_SIG_HASH="sha256"

关闭内核log时间戳,通过menuconfig关闭:

-> Kernel hacking
-> printk and dmesg options
->Show timing information on printks //取消选中

编译测试可以通过tftp来完成,在arch/arm/boot目录uImage镜像,在arch/arm/boot/dts目录下stm32mp157d-atk.dtb文件,将这两个文件拷贝到 tftp服务器目录下,然后在uboot中使用tftp命
令下载并运行,命令如下:

tftp c2000000 uImage
tftp c4000000 stm32mp157d-atk.dtb
bootm c2000000 - c4000000

烧写到EMMC

可以创建一个ext4磁盘,然后将stm32mp157d-atk.dtb和uImage挂载上去。

示例代码17.4.1.1 ext4磁盘创建命令 
1 cd bootfs 
2 dd if=/dev/zero of=bootfs.ext4 bs=1M count=10 
3 mkfs.ext4 -L bootfs bootfs.ext4
sudo mkdir /mnt/bootfs
cd /home/zuozhongkai/linux/atk-mp1/linux/bootfs
sudo mount bootfs.ext4 /mnt/bootfs/
cd /home/zuozhongkai/linux/atk-mp1/linux/bootfs
sudo cp uImage stm32mp157d-atk.dtb /mnt/bootfs/
sudo umount /mnt/bootfs

然后修改之前的FlashLayout文件,添加bootfs.ext4,在设置bootcmd环境变量即可:

setenv bootcmd 'ext4load mmc 1:2 c2000000 uImage;ext4load mmc 1:2 c4000000 stm32mp157d-atk.dtb;bootm c2000000 - c4000000'
saveenv
boot

总结之总结

这一部分移植主要是以使用为主,uboot要好好学一下详细启动,但是Linux内核过于大型,现在大概了解一下,会使用和大致了解启动过程,然后再学会了移植也就可以了。

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值