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";
};

&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 设备树

  打开 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 环境变量,指定根文件系统所在的目录。 

posted @ 2023-08-04 15:20  烟儿公主  阅读(359)  评论(0编辑  收藏  举报