7 Linux 内核移植
一、编译 ST 的 Linux 系统
1. 压缩源码
首先先下载 ST 官方源码,之前章节已经下载过了,直接输入以下命令:
cd linux/atk-mpl/stm32mp1-openstlinux-5.4-dunfell-mp1-20-06-24/sources/arm-ostl-linux-gnueabi/linux-stm32mp-5.4.31-r0/
然后压缩 linux-5.4.31.tar.xz 源码包压缩包:
tar -vxf linux-5.4.31.tar.xz
2. 给内核打补丁
输入以下命令:
cd linux-5.4.31/
for p in `ls -1 ../*.patch`; do patch -p1 < $p; done //打补丁
# ls -1 ../*.patch:通过执行 ls 命令列出上一级目录(..)中以 .patch 结尾的所有文件,并使用 -1 选项确保每个文件显示为一行。这将生成一个包含所有补丁文件名的列表。
# for p in ...; do ...; done:通过循环结构遍历列表中的每个元素,并在循环体内执行相应的操作。这里对列表中的每个元素都被赋值给变量 p。
# patch -p1 < $p:对于每个补丁文件 $p,执行 patch 命令来应用补丁文件到当前目录。-p1 选项告诉 patch 命令去除补丁文件中的前缀路径(通常是一个目录),从而使补丁适用于当前目录。
# 循环遍历上一级目录中的所有以 .patch 结尾的文件,并使用 patch 命令将这些补丁文件应用到当前目录中,其中补丁文件的前缀路径被去除
3. 生成默认配置文件
ST 原厂 Linux 内核需要先生成默认配置文件,并且对其进行打补丁,进入 Linux 内核源码根目录下,然后执行如下命令:
make ARCH=arm multi_v7_defconfig "fragment*.config"
# multi_v7_defconfig:指定要使用的配置文件。
# "fragment*.config":用于指定附加的配置片段文件。这里的 "fragment*.config" 是通配符表达式,指定了以 fragment 开头且以 .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
# scripts/kconfig/merge_config.sh -m -r .config $f:执行 merge_config.sh 脚本,将文件 $f 的配置合并到当前目录的 .config 文件中。-m 选项表示使用模块化配置,-r 选项表示要保留已经存在的配置。
# yes '':yes 命令会重复地输出指定的字符串(在本例中是空字符串 ''),直到被终止
# ARM 架构上使用旧的配置 .config 文件执行 make oldconfig,并在自动回答用户提示时使用空字符串来完成配置过程。这样可以自动化配置过程,减少了手动输入配置的需要
至此, Linux 源码根目录下的.config 文件就已经保存了所有的配置项,所以只需要复制一份.config 作为我们的默认配置文件即可,复制命令如下:
cp .config ./arch/arm/configs/stm32mp1_atk_defconfig
Linux 内核全部打完补丁, linux-5.4.31 目录就是我们要移植的 Linux 源码。我们新建一个名为“my_linux”的目录来保存我们要移植的 linux 源码,然后将打完补丁的 linux 源码 linux-5.4.31 拷贝到“my_linux”目录下,命令如下:
# 首先在/linux/atk-mpl创建 linux 目录,然后再linux目录下创建my_linux子目录
cd ~
cd linux/atk-mpl/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 /linux/atk-mpl/linux/my_linux/ -rf //拷贝
二、编译 ST 官方 Linux 源码
1. 修改Makefile
其实跟uboot修改顶层 Makefile 一样,为了减少输入参数,就添加以下代码到顶层 Makefile 中。
创建一个名为“stm32mp157d_atk.sh”的编译脚本,脚本内容如下:
#!/bin/sh
make distclean
make stm32mp1_atk_defconfig
make menuconfig
make uImage dtbs LOADADDR=0XC2000040 -j16
给 stm32mp157d_atk.sh 执行权限:
chmod 777 stm32mp157d_atk.sh // 给予可执行权限
./stm32mp157d_atk.sh // 运行编译脚本
编译完成以后的到 uImage 镜像文件和设备树,其中 STM32MP157 系列的设备树有很多。
2. 修改网络驱动
文件均来自正点原子的包里,下载地址:STM32MP157开发板 — 正点原子资料下载中心 1.0.0 文档
将 motorcomm.c 和 motorcomm_phy.h 分别拷贝到 Linux 源码下的 drivers/net/phy 和include/linux 目录下。拷贝完成以后修改 drivers/net/phy/Makefile 文件,加上下面这句:
obj-$(CONFIG_MOTORCOMM_PHY) += motorcomm.o
# 如果定义了配置选项 CONFIG_MOTORCOMM_PHY 并且其值为 true,那么目标 motorcomm.o 将被添加到目标列表中,进而进行编译。
还需要修改 drivers/net/phy/Kconfig 文件,添加以下代码:
config MOTORCOMM_PHY
tristate "Motorcomm PHYs"
---help---
Supports the YT8010, YT8510, YT8511, YT8512 PHYs.
/* config MOTORCOMM_PHY:定义了一个名为 MOTORCOMM_PHY 的配置选项。这个配置选项可用于在编译内核时启用或禁用与 Motorcomm PHY 相关的功能。
tristate "Motorcomm PHYs":配置选项的类型被设置为 tristate,即可以选择三个状态:y(编译进内核), m(编译为模块)和 n(禁用)。
"Motorcomm PHYs" 是配置选项的显示名称,将显示在配置界面上。
---help---:用来提供配置选项的帮助说明。该行以下的内容将被视为对配置选项的详细描述。
Supports the YT8010, YT8510, YT8511, YT8512 PHYs.:是对配置选项的详细描述,说明该选项的作用是支持 YT8010、YT8510、YT8511 和 YT8512 PHY 模块。
我们可以在 Linux 内核构建过程中选择是否编译或加载与 Motorcomm PHY 相关的功能模块*/
在终端输入 make menuconfig,进入以下路径:
-> Device Drivers
-> Network device support (NETDEVICES [=y])
-> PHY Device support and infrastructure (PHYLIB [=y])
-> <*> Motorcomm PHYs //将 YT8511 驱动编译进内核
./stm32mp157d_atk.sh //运行编译脚本
3. 启动测试
需要两个文件: uImage 和 stm32mp157d-ed1.dtb
首先将 /home/alientek/linux/tftpboot 文件删除,然后把这两个文件复制到tftp目录下并给予执行权限,命令如下:
cp stm32mp157d-ed1.dtb /home/alientek/linux/tftpboot/
cd ..
cp uImage /home/alientek/linux/tftpboot/
cd ~
cd linux/tftpboot/
chmod 777 stm32mp157d-ed1.dtb
chmod 777 uImage
在 uboot 中输入:
tftp c2000000 uImage
tftp c4000000 stm32mp157d_ed1.dtb
bootm c2000000 - c4000000
三、在 Linux 中添加自己的开发板
1. 添加开发板对应的默认配置文件
首先添加开发板对应的默认配置文件,这里输入以下命令:
cd linux/atk-mpl/linux/my_linux/linux-5.4.31/arch/arm/configs
find stm32mp1_atk_defconfig
就可以找到 stm32mp1_atk_defconfig 这个文件了。
2. 添加开发板对应的设备树
① 新建设备树文件
输入以下命令:
cd ~
cd linux/atk-mpl/linux/my_linux/linux-5.4.31/arch/arm/boot/dts/
cp stm32mp15xx-edx.dtsi stm32mp157d-atk.dtsi
cp stm32mp157d-ed1.dts stm32mp157d-atk.dts
修改 stm32mp157d-atk.dts 文件:
:
② 修改 stm32mp157d-atk.dtsi 文件
跟uboot移植类似,PMIC 配置不需要,并在设备树里面添加电源节点信息。
#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";
};
ðernet0 {
status = "okay";
pinctrl-0 = <ðernet0_rgmii_pins_a>;
pinctrl-1 = <ðernet0_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 设备树
打开 arch/arm/boot/dts/Makefile,到“ dtb-$(CONFIG_ARCH_STM32)”配置项,在此配置项中加入“stm32mp157d-atk.dtb”, 表示编译的时候也将 stm32mp157datk.dts 编译为 stm32mp157d-atk.dtb 。
3. 关闭内核模块验证
后续做 Linux 驱动实验的时候我们都是编译驱动模块,然后在系统里面加载,加载的时候系统会验证模块,有时候会验证出错。比如板子运行的系统和编译驱动模块所用的系统不一致的时候。为了方便开发,我们可以关闭内核模块验证。打开默认配置文件 stm32mp1_atk_defconfig,里面有如下所示配置项目:
我们也可以直接在 Linux 的图形化配置界面上关闭掉内核模块验证,输入如下命令打开 Linux 内核图形化配置界面:
# 在 /linux/atk-mpl/linux/my_linux/linux-5.4.31 该目录下
make menuconfig
# 配置路径如下:
-> Enable loadable module support (MODULES [=y])
->Module signature verification
只要通过图形化界面修改了 Linux 内核配置,最好及时将其保存到stm32mp1_atk_defconfig 文件。因为图形化界面修改的配置只是暂时保存到.config 文件里面,一旦使用“make clean”清理工程,那么.config 文件就会被删除掉,所有的配置也就丢失了!
4. 编译测试
进入该目录 /linux/atk-mpl/linux/my_linux/linux-5.4.31,再编译 stm32mp157d_atk.sh。二、3启动测试类似,把文件拷贝到 tftp 服务器目录下。在uboot下输入以下命令。
tftp c2000000 uImage
tftp c4000000 stm32mp157d-atk.dtb
bootm c2000000 - c4000000
有这样的 log 信息就启动成功。
四、烧写系统镜像到 EMMC
我们现在都是通过 tftp 命令从网络上下载测试的,实际产品开发中最终是要将系统烧写到外部 Flash 中的,比如 EMMC。现在我们将 uIamge 和 stm32mp157d-atk.dtb 打包成 ext4 格式,然后通过 STM32CubeProgrammer 烧写到 EMMC 里面,最终启动 EMMC 里面的 Linux 系统。
1. 系统镜像打包
先在 linux/atk-mpl/linux/ 路径下创建子目录 bootfs,然后再把 /linux/tftpboot 路径下的 stm32mp157d-atk.dtb 和 uImage 文件拷贝到 bootfs 里面。
① 新建 ext4 格式磁盘
首先新建一个 ext4 格式的磁盘,然后挂载这个 ext4 格式的磁盘,将 stm32mp157d-atk.dtb 和 uImage 拷贝到这个 ext4 磁盘即可。
cd bootfs
dd if=/dev/zero of=bootfs.ext4 bs=1M count=10
mkfs.ext4 -L bootfs bootfs.ext4 # 使用 mkfs.ext4 将 bootfs.ext4 磁盘格式化为 ext4 格式
# 使用 dd 命令创建一个名为 bootfs.ext4 的磁盘, of 指定磁盘名字为“bootfs.ext4”;bs 指定磁盘输入/输出块大小为 1MB; count 指定磁盘的块数量为 10 个。
# 将会在当前目录下创建一个大小为10MB的bootfs.ext4文件,并用零填充它。
② 将系统镜像拷贝到 ext4 磁盘中
首先创建一个目录用来挂载前面制作制作出来的 bootfs.ext4,比如我这里创建目录/mnt/bootfs,命令如下:
cd /
sudo mkdir /mnt/bootfs
使用 mount 命令将 bootfs.ext4 挂载到/mnt/bootfs 目录下,命令如下:
cd /home/alientek/linux/atk-mp1/linux/bootfs
sudo mount bootfs.ext4 /mnt/bootfs
挂载成功以后就将 uImage 和 stm32mp157d-atk.dtb 拷贝到/mnt/bootfs 目录下,命令如下:
sudo cp uImage stm32mp157d-atk.dtb /mnt/bootfs/
拷贝完成以后使用 umount 卸载/mnt/bootfs 即可,命令如下:
sudo umount /mnt/bootfs
这里我的理解是:先创建.ext4 磁盘,这个磁盘必须挂在到目录下,把需要放在这个磁盘下的东西放在挂载的目录里,最后再卸载该目录下的挂载。
2. 烧写到 EMMC
利用 FileZilla 将 bootfs.ext4 拷贝到 image 目录下:
修改 Flashlayout:
先把拨片波到USB,然后烧写,烧写完成后,把拨片拨到 EMMC 然后 RESET ,在 uboot 输入以下命令来验证是否烧写了 uImage 和stm32mp157d-atk.dtb
ext4ls mmc 1:2
设置 bootcmd 环境变量,从 EMMC 里面读取系统镜像和设备树并启动,命令如下:
setenv bootcmd 'ext4load mmc 1:2 c2000000 uImage;ext4load mmc 1:2 c4000000 stm32mp157d-atk.dtb;bootm c2000000 - c4000000'
saveenv
boot
一般情况下,调试阶段都是用网络调试,也就是 tftp 或 nfs,当最终产品开发完成后,出厂的时候才烧写到 EMMC 里面。
五、文件系统缺失错误
Linux 内核启动以后是需要根文件系统的,根文件系统存在哪里是由 uboot 的 bootargs 环境变量指定的,它会传递给 Linux 内核作为命令行(command line)参数。 没有对应的根文件系统,必须要自己做根文件系统。 在没有根文件系统的情况下, Linux 内核启动的时候就会有下图所示的错误:
也就是提示内核崩溃, VFS(虚拟文件系统)不能挂载根文件系统,因为根文件系统目录不存在。解决方法就是制作根文件系统,并且设置 uboot 的 bootargs 环境变量,指定根文件系统所在的目录。