linux内核移植和uboot移植总体上差不多

环境搭建

解压内核文件,这里改名如图一

图一

安装 lzop 库,否则内核编译会失败,提示“ recipe for target ‘arch/arm/boot/compressed/piggy.lzo

sudo apt-get install lzop

一、修改顶层Makefile

创建打开工程目录

直接在顶层 Makefile 文件里面定义 ARCH 和 CROSS_COMPILE 这两个的变量值为

“arm” 和 “arm-linux-gnueabihf-”

修改如图二

图二·

 二、配置并编译 Linux 内核

和 uboot 一样,在编译 Linux 内核之前要先配置 Linux 内核。每个板子都有其对应的默认
配 置 文 件 , 这 些 默 认 配 置 文 件 保 存 在 arch/arm/configs 目 录 中
这里使用 imx_v7_mfg_defconfig 这个默认配置文件,首先此配置文件默认支持 I.MX6UL 这款芯片,而且重要的一点就是此文件编译出来的 zImage 可以通过 NXP 官方提供的 MfgTool 工具烧写
imx_v7_mfg_defconfig 中的“mfg”的意思就是 MfgTool
Linux 源码根目录下,执行如下命令配置 Linux 内核:

make clean

make imx_v7_mfg_defconfig

第一次编译 Linux 内核之前先清理一下,然后配置,如图三

图三

 配置完成以后就可以编译了,使用如下命令编译 Linux 内核

make -j16

(编译没出现错误直接往下看)

若编译出现make[2]: *** [scripts/Makefile.host:100:scripts/dtc/dtc] 错误 1
                  make[1]: *** [scripts/Makefile.build:403:scripts/dtc] 错误 2

  可能是ubantu版本和gcc版本过高,下面修改一下,输入命令,在640行前面加“extern”

sudo vim scripts/dtc/dtc-lexer.lex.c_shipped 

 保存后重新编译,如图四

图四

 Linux 内核编译完成以后会在 arch/arm/boot 目录下生成 zImage 镜像文件

如果使用设备树的话还会在 arch/arm/boot/dts 目录下开发板对应的.dtb(设备树)文件

至此得到两个文件:
①、 Linux 内核镜像文件: zImage。
②、 NXP 官方 I.MX6ULL EVK 开发板对应的设备树文件: imx6ull-14x14-evk.dtb

三、Linux 内核启动测试

得到这两个文件能不能启动呢?得测试。

编译出来的 zImage 和 imx6ull-14x14-evk.dtb 复制到 Ubuntu 中的 tftp 目录下
因为要在 uboot 中使用 tftp 命令将其下载到开发板中,命令如下,如图五 

cp arch/arm/boot/zImage /home/ubantu22/tftpboot/ -f
cp arch/arm/boot/dts/imx6ull-14x14-evk.dtb /home/ubantu22/tftpboot/ -f

图五

启动开发板,进入 uboot 命令行模式,确保 uboot 中环境变量 bootargs 内容,输入命令如下:

setenv bootargs 'console=ttymxc0,115200 root=/dev/mmcblk1p2 rootwait rw'

saveent

开发板和ubantu可以ping通

然后输入如下命令,将zImage 和 imx6ull-14x14-evk.dtb 下载到开发板中并启动,如图六

tftp 80800000 zImage
tftp 83000000 imx6ull-14x14-evk.dtb
bootz 80800000 - 83000000

图六

没有根文件系统会卡在“Starting kernel ...”

根文件系统缺失错误

Linux 内核启动以后是需要根文件系统的,根文件系统存在哪里是由 uboot 的 bootargs 环境
变 量 指 定 , bootargs 会 传 递 给 Linux 内 核 作 为 命 令 行 参 数。实际的工作中开发一个产品,这个产品的第一版硬件出来以后是没有对应的根文件系统可用的,必须要自己做根文件系统。在构建出对应的根文件系统之前 Linux 内核是没有根文件系统可用的

 没有根文件系统,Linux 内核启动后会出现什么问题呢?将 uboot 中 bootargs 环境变量修改如下

setenv bootargs 'console=ttymxc0,115200'

saveent

修改完成以后重新从网络启动,启动以后会有如图七

图七

最后这行,也就是提示内核崩溃,因为 VFS(虚拟文件系统)不能挂载根文件系统,因为根文件系统目录不存在。即使根文件系统目录存在,如果根文件系统目录里面是空的依旧会提示内核崩溃。
这个就是根文件系统缺失导致的内核崩溃,但是内核是启动了的,只是根文件系统不存在而已

这个后续会添加进入

四、在 Linux 中添加自己的开发板

1 添加开发板默认配置文件

将 arch/arm/configs 目 录 下 的 imx_v7_mfg_defconfig 重 新 复 制 一 份 ,

命 名 为imx_my_emmc_defconfig,命令如下:

cd arch/arm/configs
cp imx_v7_mfg_defconfig imx_my_emmc_defconfig

 以后imx_my_emmc_defconfig 就是开发板默认配置文件,可以使用来配置开发板对应的 Linux 内核

2 添加开发板对应的设备树文件

进入目录 arch/arm/boot/dts 中,复制一份 imx6ull-14x14-evk.dts

然后将其重命名为 imx6ull-my-emmc.dts,命令如下

cd arch/arm/boot/dts
cp imx6ull-14x14-evk.dts imx6ull-my-emmc.dts

 .dts 是设备树源码文件,编译 Linux 的时候会将其编译为.dtb 文件。imx6ull-my-emmc.dts创 建 好 还 需 要 修 改 文 件 arch/arm/boot/dts/Makefile ,找 到 “ dtb-$(CONFIG_SOC_IMX6ULL)”配置项,在此配置项422行中加入“imx6ull-my-emmc.dtb”

这样编译 Linux 的时候就可以从 imx6ull-my-emmc.dts 编译出 imx6ull-my-emmc.dtb 文件了

Linux 内核里面已经添加了开 发 板 了 , 接 下 接 编 译 测 试 一 下 , 创 建 一 个 编 译 脚 本 ,
在内核源码根目录下,创建imx6ull_my_emmc.sh,脚本内容如下:

   #!/bin/sh
   make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- distclean
   make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- imx_my_emmc_defconfig
   make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- menuconfig
   make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- all -j16  

第 2 行,清理工程。
第 3 行,使用默认配置文件 imx_my_emmc_defconfig 来配置 Linux 内核。
第 4 行,打开 Linux 的图形配置界面,如果不需要每次都打开图形配置界面可以删除此行。
第 5 行,编译 Linux。
执行 shell 脚本 imx6ull_my_emmc.sh 编译 Linux 内核, 命令如下:

chmod 777 imx6ull_my_emmc.sh

./imx6ull_my_emmc.sh

 编译完成以后就会在目录 arch/arm/boot 下生成 zImage 镜像文件

在 arch/arm/boot/dts 目录下生成 imx6ull-my-emmc.dtb 文件

 使能 8 线 EMMC 驱动,EMMC 版本核心板上的 EMMC 采用的 8 位数据线,原理图如图

 Linux 内核驱动里面 EMMC 默认是 4 线模式的, 4 线模式肯定没有 8 线模式的速度快,所
以将 EMMC 的驱动修改为 8 线模式

直接修改设备树即可,打开文件 imx6ull-my-emmc.dts,找到如下所示内容

打开 arch/arm/boot/dts/imx6ull-my-emmc.dts文件,找到下面内容

 然后找到同路径下的imx6ull-14x14-evk-emmc.dts

 把usdhc2里面12行到18行的内容复制来替换imx6ull-my-emmc.dts文件的内容,修改如下

修改完之后输入如下命令编译一下设备树

make dtbs

 将这zImgae和imx6ull-my-emmc.dts两个文件拷贝到 tftp 目录下

重启开发板,在uboot 命令模式中使用 tftp 命令下载这两个文件并启动,命令如下:

tftp 80800000 zImage
tftp 83000000 imx6ull-my-emmc.dtb
bootz 80800000 – 83000000

 如果在启动出现”Bad Linux ARM zImage magic!“

重新修改一下bootcmd和bootargs,保存并重启开发板,重新tftp下载文件并启动内核即可 

setenv bootargs 'console=ttymxc0,115200 root=/dev/mmcblk1p2 rootwait rw'

setenv bootcmd 'tftp 80800000 zImage; tftp 83000000 imx6ull-my-emmc.dtb; bootz
80800000 - 83000000' 

启动内核后可以看到成功了

 五、CPU 主频和网络驱动修改

1、cpu主频修改


开发板所使用的 I.MX6ULL 芯片主频都是 792MHz 的,也就是NXP 官方宣传的 800MHz 版本

确保 EMMC 中的根文件系统可用!然后重新启动开发板,进入终端,输入命令查看 cpu 信息:

cat /proc/cpuinfo

 BogoMIPS 这一条,此时 BogoMIPS 为 3.00, BogoMIPS 是 Linux 系统中衡量处理器运行速度的一个“尺子”,处理器性能越强,主频越高, BogoMIPS 值就越大,只是粗略的计算 CPU 性能,并不十分准确,只能判断大致,而且并没有看到当前 CPU 的工作频率,那用另一种方法查看当前 CPU 的工作频率。进入到目录/sys/bus/cpu/devices/cpu0/cpufreq 中,此目录下会有很多文件

cpuinfo_cur_freq:当前 cpu 工作频率,从 CPU 寄存器读取到的工作频率。
cpuinfo_max_freq:处理器所能运行的最高工作频率(单位: KHz)。
cpuinfo_min_freq :处理器所能运行的最低工作频率(单位: KHz)。
cpuinfo_transition_latency:处理器切换频率所需要的时间(单位:ns)。
scaling_available_frequencies:处理器支持的主频率列表(单位: KHz)。
scaling_available_governors:当前内核中支持的所有 governor(调频)类型。
scaling_cur_freq:保存cpufreq 模块缓存的当前 CPU 频率,不会对 CPU 硬件寄存器进行检查。
scaling_driver:该文件保存当前 CPU 所使用的调频驱动。

scaling_max_freq: governor(调频)可以调节的最高频率。
cpuinfo_min_freq: governor(调频)可以调节的最低频率。

scaling_governor: governor(调频)策略, Linux 内核一共有 5 中调频策略:

①、 Performance,最高性能,直接用最高频率,不考虑耗电。
②、 Interactive,一开始直接用最高频率,然后根据 CPU 负载慢慢降低。
③、 Powersave,省电模式,通常以最低频率运行,系统性能会受影响,一般不会用这个!
④、 Userspace,可以在用户空间手动调节频率。
⑤、 Ondemand,定时检查负载,然后根据负载来调节频率。负载低的时候降低 CPU 频率,
这样省电,负载高的时候提高 CPU 频率,增加性能。

stats 目录下给出了 CPU 各种运行频率的统计情况,比如 CPU 在各频率下的运行时间以及
变频次数。
使用如下命令查看当前 CPU 频率

cat cpuinfo_cur_freq

 当前 CPU 频率为 198MHz,工作频率很低(不一定和这里的频率一样)

当前 CPU 支持 198MHz、 396MHz、 528Mhz 和 792MHz 四种频率切换,其中调频策略为 ondemand,也就是定期检查负载,然后根据负载情况调节 CPU 频率
查看 stats 目录下的 time_in_state 文件可以看到 CPU 在各频率下的工作时间,命令如下:

cat /sys/bus/cpu/devices/cpu0/cpufreq/stats/time_in_state

 CPU 在 198MHz、 396MHz、 528MHz 和 792MHz 都工作过,其中 198MHz 的工作时间最长

下面通过图形化界面方式配置 Linux 内核的 CPU 调频策略,输入“ make menuconfig”打开 Linux 内核的图形化配置界面,进入如下路径:
CPU Power Management-> CPU Frequency scaling-> Default CPUFreq governor
打开默认调频策略选择界面,选择“Ondemand”


选择以后退出图形化配置界面,提示保存就保存,然后编译 Linux内核,一定不要清理工程!

否则的话我们刚刚的设置就会被清理掉。

重新编译一下

make -j16

编译完成以后使用新的zImage 重启 Linux,查看当前 CPU 的工作频率和调频策略

在这个模式下就会自动调整频率了 

2、修改网络驱动

开发板的网络和 NXP 官方的网络硬件上不同,网络 PHY 芯片由 KSZ8081 换为了 LAN8720A,两个网络 PHY 芯片的复位 IO 也不同

①修改 LAN8720 的复位以及网络时钟引脚驱动

ENET1 复位引脚 ENET1_RST 连接在 I.M6ULL 的 SNVS_TAMPER7 这个引脚上。

ENET2的复位引脚 ENET2_RST 连接在 I.MX6ULL 的 SNVS_TAMPER8 上。

打开设备树文件 imx6ull-my-emmc.dts,找到如下代码:

 将 588 和 589 这两行删除掉!IO被使用了,这不是想要的,所以删除掉!

删除掉以后继续在 imx6ull-alientek-emmc.dts 中找到如下所示代码:

 129 行和第 133 行处的代码删除掉!否则会干扰到网络复位引脚!

继续找到名为“iomuxc_snvs”的节点添加网络复位引脚信息,直接在后面添加,如下

        /*enet1 reset */
         pinctrl_enet1_reset: enet1resetgrp {
                         fsl,pins = <
                         /* used for enet1 reset */
                                 MX6ULL_PAD_SNVS_TAMPER7__GPIO5_IO07 0x10B0
                         >;
                     };    
        /*enet2 reset*/
        pinctrl_enet2_reset: enet2resetgrp {
                        fsl,pins = <
                        /* used for enet2 reset */
                                MX6ULL_PAD_SNVS_TAMPER8__GPIO5_IO08 0x10B0
                        >;
                    };

 继续在 imx6ull-alientekemmc.dts 中找到pinctrl_enet1: enet1grp
如下所示代码,修改如下

这是修改 ENET1 和 ENET2 的网络时钟引脚配置,原来默认值为 0x4001b031。

修改 fec1 和 fec2 节点的 pinctrl-0 属性
在 imx6ull-alientek-emmc.dts 文件中找到名为“fec1”和“fec2”的这两个节点,修改其中的
“pinctrl-0”属性值,修改以后如下所示:

&pinctrl_enet1_reset>;
&pinctrl_enet2_reset>;

 添加176行和183行

 ②修改 LAN8720A 的 PHY 地址

 ENET1 的 LAN8720A 地址为 0x0, ENET2 的 LAN8720A地址为 0x1

继续在fec1/2修改,修改如下

&fec1 {
    pinctrl-names = "default";
    pinctrl-0 = <&pinctrl_enet1
                &pinctrl_enet1_reset>;
    phy-mode = "rmii";
    phy-handle = <&ethphy0>;
    phy-reset-gpios = <&gpio5 7 GPIO_ACTIVE_LOW>;
    phy-reset-duration = <200>;
    status = "okay";
};

&fec2 {
    pinctrl-names = "default";
    pinctrl-0 = <&pinctrl_enet2
                &pinctrl_enet2_reset>;
    phy-mode = "rmii";
    phy-handle = <&ethphy1>;
    phy-reset-gpios = <&gpio5 8 GPIO_ACTIVE_LOW>;
    phy-reset-duration = <200>;
    status = "okay";

    mdio {
        #address-cells = <1>;
        #size-cells = <0>;

        ethphy0: ethernet-phy@2 {
            compatible = "ethernet-phy-ieee802.3-c22";
            smsc,disable-energy-detect;
            reg = <0>;
        };

        ethphy1: ethernet-phy@1 {
            compatible = "ethernet-phy-ieee802.3-c22";
            smsc,disable-energy-detect;
            reg = <1>;
        };
    };
};

保存、使用“make dtbs”命令重新编译一下设备树

③修改 fec_main.c 文件

要 在 I.MX6ULL 上 使 用 LAN8720A , 需 要 修 改 一 下 Linux 内 核 源 码 , 打 开
drivers/net/ethernet/freescale/fec_main.c,找到函数 fec_probe,在 fec_probe 中加入如下代码:

void __iomem *IMX6U_ENET1_TX_CLK;
    void __iomem *IMX6U_ENET2_TX_CLK;

    IMX6U_ENET1_TX_CLK = ioremap(0X020E00DC, 4);
    writel(0X14, IMX6U_ENET1_TX_CLK);

    IMX6U_ENET2_TX_CLK = ioremap(0X020E00FC, 4);
    writel(0X14, IMX6U_ENET2_TX_CLK);

 3455-3462就是新加入代码

在 I.MX6ULL 上使用 LAN8720A 就需要设置ENET1 和 ENET2 的 TX_CLK 引脚复位寄存器的 SION 位为 1

④配置 Linux 内核,使能 LAN8720 驱动

在linux内核源码根目录下输入命令“make menuconfig”,打开图形化配置界面

选择使能 LAN8720A 的驱动,路径如下:

Device Drivers-> Network device support-> PHY Device support and infrastructure

-> Drivers for SMSC PHYs

 按下 'y' 键,选择将“Drivers for SMSC PHYs”编译到 Linux 内核中,因此“<>”里面变为了“*”。 LAN8720A 是 SMSC 公司出品的,因此勾选这个以后就会编译 LAN8720 驱动,配置好以后退出配置界面,然后重新编译一下 Linux 内核

⑤修改 smsc.c 文件

使能 LAN8720A 驱动以后就直接使用。但是在测试 NFS 挂载文件系统的时候发现文件系统挂载成功率很低!在 uboot 中需要对LAN8720A 进行一次软复位,要设置 LAN8720A 的 BMCR(寄存器地址为 0)寄存器 bit15 为 1。所以在 Linux 中也需要对 LAN8720A 进行一次软复位
LAN8720A 的驱动文件是 drivers/net/phy/smsc.c,在此文件中有个叫做 smsc_phy_reset 的函数,
修改以后的 smsc_phy_reset函数内容如下所示:

static int smsc_phy_reset(struct phy_device *phydev)
{
    int err, phy_reset;
    int msec = 1;
    struct device_node *np;
    int timeout = 50000;
    if (phydev->addr == 0) /* FEC1 */
    {
        np = of_find_node_by_path("/soc/aips-bus@02100000/ethernet@02188000");
        if (np == NULL)
        {
            return -EINVAL;
        }
    }

    if (phydev->addr == 1) /* FEC2 */
    {
        np = of_find_node_by_path("/soc/aips-bus@02000000/ethernet@020b4000");
        if (np == NULL)
        {
            return -EINVAL;
        }
    }

    err = of_property_read_u32(np, "phy-reset-duration", &msec);
    /* A sane reset duration should not be longer than 1s */
    if (!err && msec > 1000)
        msec = 1;
    phy_reset = of_get_named_gpio(np, "phy-reset-gpios", 0);
    if (!gpio_is_valid(phy_reset))
        return;

    gpio_direction_output(phy_reset, 0);
    gpio_set_value(phy_reset, 0);
    msleep(msec);
    gpio_set_value(phy_reset, 1);
    int rc = phy_read(phydev, MII_LAN83C185_SPECIAL_MODES);
    if (rc < 0)
        return rc;

    /* If the SMSC PHY is in power down mode, then set it
     * in all capable mode before using it.
     */
    if ((rc & MII_LAN83C185_MODE_MASK) == MII_LAN83C185_MODE_POWERDOWN) {

        /* set "all capable" mode and reset the phy */
        rc |= MII_LAN83C185_MODE_ALL;
        phy_write(phydev, MII_LAN83C185_SPECIAL_MODES, rc);
    }
    phy_write(phydev, MII_BMCR, BMCR_RESET);

        /* wait end of reset (max 500 ms) */
        do {
            udelay(10);
            if (timeout-- == 0)
                return -1;
            rc = phy_read(phydev, MII_BMCR);
        } while (rc & BMCR_RESET);
   
    return 0;
}

要添加两个头文件

#include <linux/of_gpio.h>
#include <linux/io.h>

 

 网络驱动测试

修改好设备树和 Linux 内核以后重新编译一下

make -j16

开发板与电脑同一网段下

最后使用新的文件启动 Linux 内核。启动以后命令查看一下当前活动的网卡有哪些

ifconfig -a

 

 eth0 对应于 ENET2, eth1 对应于 ENET1。使用如下命令依次打开 eth0 和 eth1 这两个网卡:

ifconfig eth0 up
ifconfig eth1 up

 

 可以看到“SMSC LAN8710/LAN8720”字样,说明当前网络驱动使用的是前面使能的 SMSC 驱动

输入“ifconfig”命令来查看一下当前活动的网卡

 可以看出,此时 eth0 和 eth1 两个网卡都已经打开,并且工作正常,但是这两个网卡都还没
有 IP 地址,所以不能进行 ping 等操作。使用如下命令给两个网卡配置 IP 地址:

ifconfig eth0 192.168.1.221
ifconfig eth1 192.168.1.223

IP 地址选择的合理性,一定要和自己的电脑处于同一个网段内,并且没有被其他的设备占用!设置好以后,使用“ping”命令来 ping 一下自己的主机,如果能 ping 通那说明网络驱动修改成功!


  ping 成功,说明网络驱动修改成功!

在后面的构建根文件系统和 Linux 驱动开发中就可以使用网络调试代码

六、保存修改后的图形化配置文件

在修改网络驱动的时候我们通过图形界面使能了 LAN8720A 的驱动,使能以后会在.config中存在代码:“CONFIG_SMSC_PHY=y”

打开 drivers/net/phy/Makefile,有代码“obj-$(CONFIG_SMSC_PHY) += smsc.o”

当 CONFIG_SMSC_PHY=y 的时候就会编译 smsc.c 这个文件, smsc.c 就是 LAN8720A 的驱
动文件。但是当我们执行“make clean”清理工程以后.config 文件就会被删除掉,因此所有的配置内容都会丢失,所以在配置完图形界面以后经过测试没有问题,就必须要保存一下配置文件。

保存配置的方法有两个

1、 直接另存为.config 文件

既然图形化界面配置后的配置项保存在.config 中,那么就简单粗暴,直接将.config 文件另存为 imx_my_emmc_defconfig,然后其复制到 arch/arm/configs 目录下,替换以前的imx_my_emmc_defconfig。这样以后执行“ make imx_my_emmc_defconfig”重新配置
Linux 内核的时候就会使用新的配置文件,默认就会使能 LAN8720A 的驱动。

2、通过图形界面保存配置文件
在图形界面中保存配置文件,在图形界面中会有“< Save >”选项


通过键盘的“→”键,移动到“< Save >”选项,然后按下回车键,打开文件名输入对话框

 输入要保存的文件名,可以带路径,一般是相对路径(相对于 Linux 内核源码 根目 录 )。 比如要 将新 的配 置文 件保存 到目 录 arch/arm/configs下,文件名为imx_my_emmc_defconfig,也就是用新的配置文件替换掉老的默认配置文件。

那在图中输入“arch/arm/configs/imx_my_emmc_defconfig”即可

 ​​​​​

设置好文件名后选择下方的“ < Ok >”按钮,保存文件并退出。退出以后再打开imx_my_emmc_defconfig文件,就会在此文件中找到“CONFIG_SMSC_PHY=y”这一行

 关于 Linux 内核的移植就讲解到这里,简单总结一下移植步骤:
①、在 Linux 内核中查找可以参考的板子,一般都是半导体厂商自己做的开发板。
②、编译出参考板子对应的 zImage 和.dtb 文件。
③、使用参考开发板的zImage 文件和.dtb 文件在所使用的板子上启动 Linux 内核,看能否启动。
④、如果能启动的话就万事大吉,如果不能启动那就悲剧了,需要调试 Linux 内核。不过一般都会参考半导体官方的开发板设计自己的硬件,所以大部分情况下都会启动起来。启动Linux 内核用到的外设不多,一般就 DRAM(Uboot 都初始化好的)和串口。作为终端使用的串口一般都会参考半导体厂商的 Demo 板。
⑤、修改相应的驱动,像 NAND Flash、 EMMC、 SD 卡等驱动官方的 Linux 内核都是已经提供好了,基本不会出问题。重点是网络驱动,因为 Linux 驱动开发一般都要通过网络调试代码,所以一定要确保网络驱动工作正常。如果是处理器内部 MAC+外部 PHY 这种网络方案的话,一般网络驱动都很好处理,因为在 Linux 内核中是有外部 PHY 通用驱动的。只要设置好复位引脚、 PHY 地址信息基本上都可以驱动起来。
⑥、 Linux 内核启动以后需要根文件系统,如果没有根文件系统的话肯定会崩溃,所以确定 Linux
内核移植成功以后就要开始根文件系统的构建

 rootfs(根文件系统)在下一篇文章

Logo

更多推荐