8| linux内核移植

linux内核移植

一、获取内核源码

Linux 官网为 https://www.kernel.org,所以你想获取最新的Linux 版本就可以在这个网站上下载,网站界面如图所示:

image-20200819114832709

从图可以看出最新的稳定版 Linux 已经到了 5.1.4,大家没必要追新,因为 4.x 版本的 Linux 和 5.x 版本没有本质上的区别,5.x 更多的是加入了一些新的平台、新的外设驱动而已。NXP 会从 https://www.kernel.org 下载某个版本的 Linux 内核,然后将其移植到自己的 CPU上,测试成功以后就会将其开放给 NXP 的 CPU 开发者。开发者下载 NXP 提供的 Linux 内核,然后将其移植到自己的产品上。本章的移植我们就使用 NXP 提供的Linux 源码。

和uboot源码一样,从对应的开发板厂商或SOC厂商获取,具体看你的开发板参考谁的板子

二、内核浅析

1、内核目录分析

将 Linux 源码进行解压,解压完成以后的目录如图所示:

image-20200819120745881

image-20200819120815125

image-20200819120827827

  • 1 、arch 目录

    这个目录是和架构有关的目录,比如 arm、arm64、avr32、x86 等等架构。每种架构都对应一个目录,在这些目录中又有很多子目录,比如 boot、common、configs 等等

    以 arch/arm 为例,其子目录如图 35.3.2 所示:

    image-20200819121018040

    图 35.3.2 是 arch/arm 的一部分子目录,这些子目录用于控制系统引导、系统调用、动态调频、主频设置等。

    arch/arm/configs 目录是不同平台的默认配置文件:xxx_defconfig,如图 35.3.3所示:

    image-20200819121036268

    arch/arm/configs 中就包含有 I.MX6U-ALPHA 开发板的默认配置文件:imx_v7_defconfig,执行“make imx_v7_defconfig”即可完成配置。

    arch/arm/boot/dts 目录里面是对应开发平台的设备树文件,正点原子 I.MX6U-ALPHA 开发板对应的设备树文件如图 35.3.4 所示:

    image-20200819121223952

    arch/arm/boot 目录下会保存编译出来的 Image 和 zImage 镜像文件,而 zImage 就是我们要用的 linux 镜像文件。

    arch/arm/mach-xxx 目录分别为相应平台的驱动和初始化文件,比如 mach-imx 目录里面就是 I.MX 系列 CPU 的驱动和初始化文件。

  • 2 、block 目录

    block 是 Linux 下块设备目录,像 SD 卡、EMMC、NAND、硬盘等存储设备就属于块设备,block 目录中存放着管理块设备的相关文件。

  • 3 、crypto 目录

    crypto 目录里面存放着加密文件,比如常见的 crc、crc32、md4、md5、hash 等加密算法。

  • 4 、Documentation 目录

    此目录里面存放着 Linux 相关的文档,如果要想了解 Linux 某个功能模块或驱动架构的功能,就可以在 Documentation 目录中查找有没有对应的文档。

  • 5 、drivers 目录

    驱动目录文件,此目录根据驱动类型的不同,分门别类进行整理,比如 drivers/i2c 就是 I2C相关驱动目录,drivers/gpio 就是 GPIO 相关的驱动目录,这是我们学习的重点。

  • 6 、firmware 目录

    此目录用于存放固件。

  • 7 、fs 目录

    此目录存放文件系统,比如 fs/ext2、fs/ext4、fs/f2fs 等,分别是 ext2、ext4 和 f2fs 等文件系统。

  • 8 、include 目录

    头文件目录。

  • 9 、init 目录

    此目录存放 Linux 内核启动的时候初始化代码。

  • 10 、ipc 目录

    IPC 为进程间通信,ipc 目录是进程间通信的具体实现代码。

  • 11 、kernel 目录

    Linux 内核代码。

  • 12 、lib 目录

    lib 是库的意思,lib 目录都是一些公用的库函。

  • 13 、mm 目录

    此目录存放内存管理相关代码。

  • 14 、net 目录

    此目录存放网络相关代码。

  • 15 、samples 目录

    此目录存放一些示例代码文件。

  • 16 、scripts 目录

    脚本目录,Linux 编译的时候会用到很多脚本文件,这些脚本文件就保存在此目录中。

  • 17 、security 目录

    此目录存放安全相关的文件。

  • 18 、sound 目录

    此目录存放音频相关驱动文件,音频驱动文件并没有存放到 drivers 目录中,而是单独的目录。

  • 19 、tools 目录

    此目录存放一些编译的时候使用到的工具。

  • 20 、usr 目录

    此目录存放与 initramfs 有关的代码。

  • 21 、virt 目录

    此目录存放虚拟机相关文件。

  • 22 、.config 文件

    跟 uboot 一样,.config 保存着 Linux 最终的配置信息,编译 Linux 的时候会读取此文件中
    的配置信息。最终根据配置信息来选择编译 Linux 哪些模块,哪些功能。

  • 23 、Kbuild 文件

    有些 Makefile 会读取此文件。

  • 24 、Kconfig

    图形化配置界面的配置文件。

  • 25 、Makefile 文件

    Linux 顶层 Makefile 文件,建议好好阅读一下此文件。

  • 26 、README 文件

    此文件详细讲解了如何编译 Linux 源码,以及 Linux 源码的目录信息,建议仔细阅读一下此文件。

2、顶层Makefile分析

参考正点原子IMX6U驱动开发指南35.5节。

3、内核启动流程分析

参考正点原子IMX6U驱动开发指南36.1、36.2节。

三、内核移植

1、创建VSCode工程

这里我们使用 NXP 官方提供的 Linux 源码,将其移植到正点原子 I.MX6U-ALPHA 开发板上。

源码压缩包linux-imx-rel_imx_4.1.15_2.1.0_ga.tar.bz2。使用FileZilla将其发送到Ubuntu中并解压,得到名为linux-imx-rel_imx_4.1.15_2.1.0_ga的目录,为了和 NXP 官方的名字区分,可 以使用“ mv ”命令对其重命名,我这里将其重命名为 “ linux-imx-rel_imx_4.1.15_2.1.0_ga_lxg”.

命令如下:

mv linux-imx-rel_imx_4.1.15_2.1.0_ga linux-imx-rel_imx_4.1.15_2.1.0_ga_lxg

完成以后创建 VSCode 工程,步骤和 Windows 下一样,重点是.vscode/settings.json 这个文件。

2、NXP 官方开发板 Linux 内核初次编译

编译内核之前需要先在 ubuntu 上安装 lzop 库,否则内核编译会失败!命令如下:

sudo apt-get install lzop

修改顶层 Makefile,直接在顶层 Makefile 文件里面定义 ARCH 和 CROSS_COMPILE 这两个的变量值为 arm 和 arm-linux-gnueabihf-,结果如图 所示:

image-20200819122938067

分别设置了 ARCH 和 CROSS_COMPILE 这两个变量的值,这样在编译的时候就不用输入很长的命令了。

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

make clean  //第一次编译 Linux 内核之前先清理一下
make imx_v7_mfg_defconfig //配置 Linux 内核

image-20200819123316222

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

make -j12  //编译 Linux 内核

等待编译完成,结果如图所示:

image-20200819123555197

Linux 内核编译完成以后会在 arch/arm/boot 目录下生成 zImage 镜像文件,如果使用设备树的话还会在 arch/arm/boot/dts 目录下开发板对应的**.dtb**(设备树)文件,比如 imx6ull-14x14-evk.dtb就是 NXP 官方的 I.MX6ULL EVK 开发板对应的设备树文件。至此我们得到两个文件:
①、Linux 内核镜像文件:zImage。
②、NXP 官方 I.MX6ULL EVK 开发板对应的设备树文件:imx6ull-14x14-evk.dtb。

3、Linux 内核启动测试

在上一小节我们已经得到了 NXP 官方 I.MX6ULL EVK 开发板对应的 zImage 和 imx6ull-14x14-evk.dtb 这两个文件。

现在测试这两个文件能不能在正点原子的 I.MX6U-ALPHA EMMC 版开发板上启动,在测试之前确保 uboot 中的环境变量 bootargs 内容如下:
console=ttymxc0,115200 root=/dev/mmcblk1p2 rootwait rw
将上一小节编译出来的 zImage 和 imx6ull-14x14-evk.dtb 复制到 Ubuntu 中的 tftp 目录下,因为我们要在 uboot 中使用 tftp 命令将其下载到开发板中,拷贝命令如下:

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

拷贝完成以后就可以测试了,启动开发板,进入 uboot 命令行模式,然后输入如下命令将zImage 和 imx6ull-14x14-evk.dtb 下载到开发板中并启动:

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

结果图所示:

image-20200819130548552

4、在linux内核中添加自己的开发板

1、添加默认配置文件

将 arch/arm/configs 目 录 下 的 imx_v7_mfg_defconfig 重 新 复 制 一 份 , 命 名 imx_lxg_emmc_defconfig,命令如下:

cd arch/arm/configs
cp imx_v7_mfg_defconfig imx_lxg_emmc_defconfig

以后 imx_lxg_emmc_defconfig就是我们的 EMMC 版开发板默认配置文件了。完成以后如图 37.3.1.1 所示:

image-20200819131258173

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

添加适合正点原子 EMMC 版开发板的设备树文件,进入目录 arch/arm/boot/dts 中,复制一份 imx6ull-14x14-evk.dts,然后将其重命名为 imx6ull-lxg-emmc.dts,命令如下:

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

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

image-20200819131816436

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

3、编译测试

经过前面两个小节,Linux 内核里面已经添加了我们自己的I.MX6UL-LXG-EMMC 版开发板了,接下接编译测试一下,我们可以创建一个编译脚本,imx6ull_lxg_emmc.sh,脚本内容如下:

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

第 2 行,清理工程。
第 3 行,使用默认配置文件 imx_lxg_emmc_defconfig 来配置 Linux 内核。
第 4 行,打开 Linux 的图形配置界面,如果不需要每次都打开图形配置界面可以删除此行。
第 5 行,编译 Linux。

执行 shell 脚本 imx_lxg_emmc.sh 编译 Linux 内核,命令如下:

chmod 777 imx_lxg_emmc.sh //给予可执行权限
./imx_lxg_emmc.sh //执行 shell 脚本编译内核

编译完成以后就会在目录 arch/arm/boot 下生成 zImage 镜像文件。在 arch/arm/boot/dts 目录下生成 imx6ull-lxg-emmc.dtb 文件。将这两个文件拷贝到 tftp 目录下,然后重启开发板,在uboot 命令模式中使用 tftp 命令下载这两个文件并启动,命令如下:

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

只要出现如图所示内容就表示 Linux 内核启动成功:

image-20200819133111334

红框为linux内核的编译时间

5、部分驱动修改(CPU、NET、EMMC)

1、CPU主频修改

1、在linux下手动修改cpu配置文件或修改linux内核配置文件

正点原子 I.MX6U-ALPHA 开发板所使用的 I.MX6ULL 芯片主频都是 792MHz 的,也就是NXP 官方宣传的 800MHz 版本。这里以此板为例。

1 、设置 I.MX6U-ALPHA 开发板工作在 792MHz
确保 EMMC 中的根文件系统可用!然后重新启动开发板,进入终端(可以输入命令),如图

cat /proc/cpuinfo

image-20200819143359337

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

image-20200819143452149

此目录中记录了 CPU 频率等信息,这些文件的含义如下:
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_governor :governor(调频)策略,Linux 内核一共有 5 中调频策略,

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

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

stats 目录下给出了 CPU 各种运行频率的统计情况,比如 CPU 在各频率下的运行时间以及
变频次数。

使用如下命令查看当前 CPU 频率:cat cpuinfo_cur_freq
结果如图 37.4.1.4 所示

image-20200819143815635

从图 37.4.1.4 可以看出,当前 CPU 频率为 198MHz,工作频率很低!其他的值如下:

cpuinfo_cur_freq = 198000
cpuinfo_max_freq = 792000
cpuinfo_min_freq = 198000
scaling_cur_freq = 198000
scaling_max_freq = 792000
cat scaling_min_freq = 198000
scaling_available_frequencies = 198000 396000 528000 792000
cat scaling_governor = ondemand

可以看出,当前 CPU 支持 198MHz、396MHz、528Mhz 和 792000 四种频率切换,其中调频策略为 ondemand,也就是定期检查负载,然后根据负载情况调节 CPU 频率。因为当前我们开发板并没有做什么工作,因此 CPU 频率降低为 198MHz 以省电。如果开发板做一些高负载的工作,比如播放视频等操作那么 CPU 频率就会提升上去。

查看 stats 目录下的 time_in_state 文件可以看到 CPU 在各频率下的工作时间,命令如下:

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

image-20200819144003337

从图中可以看出,CPU 在 198MHz、396MHz、528MHz 和 792MHz 都工作过,其中 198MHz 的工作时间最长!假如我们想让 CPU 一直工作在 792MHz 那该怎么办?很简单,配置 Linux 内核,将调频策略选择为 performance(直接修改scaling_governor 文件的内容,将ondemand修改为performance)。

或者修改 imx_lxg_emmc_defconfig 文件,此文件中有下面几行:

CONFIG_CPU_FREQ_DEFAULT_GOV_ONDEMAND=y
CONFIG_CPU_FREQ_GOV_POWERSAVE=y
CONFIG_CPU_FREQ_GOV_USERSPACE=y
CONFIG_CPU_FREQ_GOV_INTERACTIVE=y

分别为:

配置 ondemand 为默认调频策略。
使能 powersave 策略。
使能 userspace 策略。
使能 interactive 策略

将示例代码中的第 1 行屏蔽掉,然后在最后面添加:

CONFIG_CPU_FREQ_GOV_ONDEMAND=y

修改完成以后重新编译 Linux 内核,编译之前先清理一下工程!因为我们重新修改过默认配置文件了,编译完成以后使用新的 zImage 镜像文件重新启动 Linux。再次查看/sys/devices/system/cpu/cpu0/cpufreq/ cpuinfo_cur_freq 文件的值,如图 37.4.1.6 所示:

image-20200819144814735

从图可以看出,当前 CPU 频率为 792MHz 了。查看 scaling_governor 文件看一下当前的调频策略,如图 37.4.1.7 所示:

image-20200819144848532

从图 可以看出当前的 CPU 调频策略为 preformance,也就是高性能模式,一直以最高主频运行。

2、使用linux图形化配置界面配置

输入make menuconfig打开 Linux 内核的图形化配置界面

进入如下路径:
CPU Power Management
-> CPU Frequency scaling
-> CPU Frequency scaling
-> Default CPUFreq governor
打开默认调频策略选择界面,选择“performance”,如图所示:

image-20200819145152374

在图中选择“performance”即可,选择以后退出图形化配置界面,然后编译 Linux内核,一定不要清理工程!否则的话我们刚刚的设置就会被清理掉。编译完成以后使用新的zImage 重启 Linux,查看当前 CPU 的工作频率和调频策略。

我们学习的时候为了高性能,大家可以使用 performance 模式。但是在以后的实际产品开发中,从省电的角度考虑,建议大家使用 ondemand 模式,一来可以省电,二来可以减少发热。

3、超频设置

I.MX6ULL 有多种型号,按照工作频率可以分为 528MHz、700Mhz(实际 696MHz),800MHz(实际 792MHz)和 900MHz(实际频率未知,应该在 900MHz 左右)。

声明:
想体验一下高性能的朋友可以体验一下超频,但是!毕竟是超频了的,工作肯定没有在默认频率稳定。如果因为超频带来任何损坏,本文档不负任何责任!

在实际的产品中,禁止任何超频!务必严格按照 I.MX6ULL 手册上给出的标准工作频率来运行!!如果想要更高的性能,请购买相应型号的处理器!看到这里,如果您还是执意要超频,那么就接着往下看,如果要放弃超频,那就跳过本小节。

超频设置其实很简单,修改一下设备树文件 arch/arm/boot/dts/imx6ull.dtsi 即可,打开imx6ull.dtsi,找到下面代码:

cpu0: cpu@0 {
	compatible = "arm,cortex-a7";
	device_type = "cpu";
	reg = <0>;
	clock-latency = <61036>; /* two CLK32 periods */
	operating-points = <
		/* kHz	uV */
		996000	1275000
		792000	1225000
		528000	1175000
		396000	1025000
		198000	950000
	>;
	fsl,soc-operating-points = <
		/* KHz	uV */
		996000	1175000
		792000	1175000
		528000	1175000
		396000	1175000
		198000	1175000
	>;
	fsl,low-power-run;
	clocks = <&clks IMX6UL_CLK_ARM>,

想要超频的小伙伴只需要添加两行代码:如下:

image-20200819150034290

加入了“696000 1225000”,这个就是 696MHz 的支持。
加入了“696000 1175000”,也是对 696MHz 的支持。

修改好以后保存,并且编译设备树,在 Linux 内核源码根目录下输入如下命令编译设备树:

make dtbs

命令“make dtbs”只编译设备树文件,也就是将.dts 编译为.dtb,编译完成以后使用新的设备树文件imx6ull-lxg_emmc.dtb 启动Linux.

重启以后查看文件/sys/devices/system/cpu/cpu0/cpufreq/ scaling_available_frequencies 的内容,如图 37.4.1.10 所示:

image-20200819150158116

从图可以看出,此时支持了 696MHz。如果设置调频策略为 performance,那么处理器就会一直工作在696MHz。可以对比一下工作在528MHz和696MHz下的BogoMIPS的值

6、使能8线EMMC驱动

正点原子 EMMC 版本核心板上的 EMMC 采用的 8 位数据线,原理图如图 37.4.2.1 所示:

image-20200819150756416

Linux 内核驱动里面 EMMC 默认是 4 线模式的,4 线模式肯定没有 8 线模式的速度快,所以本节我们将 EMMC 的驱动修改为 8 线模式。修改方法很简单,直接修改设备树即可,打开文件 imx6ull-lxg-emmc.dts,找到如下所示内容:

&usdhc2 {
	pinctrl-names = "default";
	pinctrl-0 = <&pinctrl_usdhc2>;
	non-removable;
	status = "okay";
};

关于设备树的原理以及内容后面会专门讲解,示例代码中的代码含义我们现在不去纠结,只需要将其改为如下代码即可:

&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>;
	non-removable;
	status = "okay";
};

修改完成以后保存一下 imx6ull-lxg-emmc.dts,然后使用命令“make dtbs”重新编译一下设备树,编译完成以后使用新的设备树重启 Linux 系统即可。

7、修改网络驱动

因为在后面学习 Linux 驱动开发的时候要用到网络调试驱动,所以必须要把网络驱动调试好。在讲解uboot 移植的时候就已经说过了,正点原子开发板的网络和 NXP 官方的网络硬件上不同,网络 PHY 芯片由 KSZ8081 换为了 LAN8720A,两个网络 PHY 芯片的复位 IO 也不同。所以 Linux 内核自带的网络驱动是驱动不起来 I.MX6U-ALPHA 开发板上的网络的,需要做修改。

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

ENET1 复位引脚 ENET1_RST 连接在 I.M6ULL 的 SNVS_TAMPER7 这个引脚上。ENET2的复位引脚 ENET2_RST 连接在 I.MX6ULL 的 SNVS_TAMPER8 上。打开设备树文件 imx6ull-lxg-emmc.dts,找到如下代码:

pinctrl_spi4: spi4grp {
                   fsl,pins = <
                           MX6ULL_PAD_BOOT_MODE0__GPIO5_IO10        0x70a1
                           MX6ULL_PAD_BOOT_MODE1__GPIO5_IO11        0x70a1
                           MX6ULL_PAD_SNVS_TAMPER7__GPIO5_IO07      0x70a1
                           MX6ULL_PAD_SNVS_TAMPER8__GPIO5_IO08      0x80000000
                       >;
               };

示例代码中

MX6ULL_PAD_SNVS_TAMPER7__GPIO5_IO07      0x70a1
MX6ULL_PAD_SNVS_TAMPER8__GPIO5_IO08      0x80000000

就是初始化 SNVS_TAMPER7 和 SNVS_TAMPER8 这两个引脚的,不过看样子好像是作为了 SPI4 的 IO,这不是我们想要的,所以将这两行删除掉!删除掉以后继续在 imx6ull-lxg-emmc.dts 中找到如下所示代码:

spi4 {
	compatible = "spi-gpio";
	pinctrl-names = "default";
	pinctrl-0 = <&pinctrl_spi4>;
	pinctrl-assert-gpios = <&gpio5 8 GPIO_ACTIVE_LOW>;
	status = "okay";
	gpio-sck = <&gpio5 11 0>;
	gpio-mosi = <&gpio5 10 0>;
	cs-gpios = <&gpio5 7 0>;
	num-chipselects = <1>;
       ......
pinctrl-assert-gpios = <&gpio5 8 GPIO_ACTIVE_LOW>;
cs-gpios = <&gpio5 7 0>;

其中这两行分别是

设置 GPIO5_IO08 为 SPI4 的一个功能引脚(我也不清楚具体作为什么功能用),而 GPIO5_IO08 就是 SNVS_TAMPER8 的 GPIO 功能引脚。

设置 GPIO5_IO07 作为 SPI4 的片选引脚,而 GPIO5_IO07 就是 SNVS_TAMPER7的 GPIO 功能引脚。

现在我们需要 GPIO5_IO07 和 GPIO5_IO08 分别作为 ENET1 和 ENET2 的复位引脚,而不是 SPI4 的什么功能引脚,因此将示例代码 中的两行代码删除掉!!否则会干扰到网络复位引脚!

在 imx6ull-alientek-emmc.dts 里面找到名为“iomuxc_snvs”的节点(就是直接搜索),然后在
此节点下添加网络复位引脚信息,添加完成以后的“iomuxc_snvs”的节点内容如下:

&iomuxc_snvs {
	pinctrl-names = "default_snvs";
        pinctrl-0 = <&pinctrl_hog_2>;
        imx6ul-evk {
		......
        ......
		/*enet1 reset firestaradmin*/
		pinctrl_enet1_reset: enet1resetgrp {
			fsl,pins = <
				/* used for enet1 reset */
				MX6ULL_PAD_SNVS_TAMPER7__GPIO5_IO07 0x10B0
			>;
		};

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

最后还需要修改一下 ENET1 和 ENET2 的网络时钟引脚配置,继续在 imx6ull-lxg-emmc.dts 中找到如下所示代码:

pinctrl_enet1: enet1grp {
    fsl,pins = <
        MX6UL_PAD_ENET1_RX_EN__ENET1_RX_EN	0x1b0b0
        MX6UL_PAD_ENET1_RX_ER__ENET1_RX_ER	0x1b0b0
        MX6UL_PAD_ENET1_RX_DATA0__ENET1_RDATA00	0x1b0b0
        MX6UL_PAD_ENET1_RX_DATA1__ENET1_RDATA01	0x1b0b0
        MX6UL_PAD_ENET1_TX_EN__ENET1_TX_EN	0x1b0b0
        MX6UL_PAD_ENET1_TX_DATA0__ENET1_TDATA00	0x1b0b0
        MX6UL_PAD_ENET1_TX_DATA1__ENET1_TDATA01	0x1b0b0
        MX6UL_PAD_ENET1_TX_CLK__ENET1_REF_CLK1	0x4001b009
    >;
};

pinctrl_enet2: enet2grp {
    fsl,pins = <
        MX6UL_PAD_GPIO1_IO07__ENET2_MDC		0x1b0b0
        MX6UL_PAD_GPIO1_IO06__ENET2_MDIO	0x1b0b0
        MX6UL_PAD_ENET2_RX_EN__ENET2_RX_EN	0x1b0b0
        MX6UL_PAD_ENET2_RX_ER__ENET2_RX_ER	0x1b0b0
        MX6UL_PAD_ENET2_RX_DATA0__ENET2_RDATA00	0x1b0b0
        MX6UL_PAD_ENET2_RX_DATA1__ENET2_RDATA01	0x1b0b0
        MX6UL_PAD_ENET2_TX_EN__ENET2_TX_EN	0x1b0b0
        MX6UL_PAD_ENET2_TX_DATA0__ENET2_TDATA00	0x1b0b0
        MX6UL_PAD_ENET2_TX_DATA1__ENET2_TDATA01	0x1b0b0
        MX6UL_PAD_ENET2_TX_CLK__ENET2_REF_CLK2	0x4001b009
    >;
};

两个节点中的最后一行分别为 ENET1 和 ENET2 的网络时钟引脚配置信息,将这两个引脚的电气属性值改为 0x4001b009,原来默认值为 0x4001b031。修改完成以后记得保存一下 imx6ull-lxg-emmc.dts,网络复位以及时钟引脚驱动就修改好了。

2 、修改 fec1 和 和 fec2 节点的 pinctrl-0 属性

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

&fec1 {
	pinctrl-names = "default";
	pinctrl-0 = <&pinctrl_enet1
        		 &pinctrl_enet1_reset>;
	phy-mode = "rmii";
	phy-handle = <&ethphy0>;
	status = "okay";
};

&fec2 {
	pinctrl-names = "default";
	pinctrl-0 = <&pinctrl_enet2
        		 &pinctrl_enet2_reset>;
	phy-mode = "rmii";
	......
    ......
};

3、修改 LAN8720A 的 的 PHY 地址

在 uboot 移植中,我们说过 ENET1 的 LAN8720A 地址为 0x0,ENET2 的 LAN8720A地址为 0x1。在 imx6ull-lxg-emmc.dts 中找到如下代码:

&fec1 {
	pinctrl-names = "default";
	pinctrl-0 = <&pinctrl_enet1
        		 &pinctrl_enet1_reset>;
	phy-mode = "rmii";
	phy-handle = <&ethphy0>;
	status = "okay";
};

&fec2 {
	pinctrl-names = "default";
	pinctrl-0 = <&pinctrl_enet2
        		 &pinctrl_enet2_reset>;
	phy-mode = "rmii";
	phy-handle = <&ethphy1>;
	status = "okay";

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

		ethphy0: ethernet-phy@2 {
			compatible = "ethernet-phy-ieee802.3-c22";
			reg = <2>;
		};

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

上述代码中的

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

	ethphy0: ethernet-phy@2 {
		compatible = "ethernet-phy-ieee802.3-c22";
		reg = <2>;
	};

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

部分为mdio节点,述了 ENET1和 ENET2 的 PHY 地址信息。

将示例代码改为如下内容:

&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@0 {
			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>;
		};
	};
};

先是添加了 ENET1 网络复位引脚所使用的 IO 为 GPIO5_IO07,低电平有效。复位低电平信号持续时间为 200ms。

然后添加ENET2 网络复位引脚所使用的 IO 为 GPIO5_IO08,同样低电平有效,持续时间同样为 200ms。

“smsc,disable-energy-detect”表明 PHY 芯片是 SMSC 公司的,这样 Linux内核就会找到 SMSC 公司的 PHY 芯片驱动来驱动 LAN8720A。

注意“ethernet-phy@”后面的数字是 PHY 的地址,ENET1 的 PHY 地址为 0,所以“@”后面是 0(默认为 2)。

reg 的值也表示 PHY 地址,ENET1 的 PHY 地址为 0,所以 reg=0。

至此,LAN8720A 的 PHY 地址就改好了,保存一下 imx6ull-lxg-emmc.dts 文件。然后使用“make dtbs”命令重新编译一下设备树。

4、修改 fec_main.c 文件

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

static int
fec_probe(struct platform_device *pdev)
{
	struct fec_enet_private *fep;
	struct fec_platform_data *pdata;
	struct net_device *ndev;
	int i, irq, ret = 0;
	struct resource *r;
	const struct of_device_id *of_id;
	static int dev_id;
	struct device_node *np = pdev->dev.of_node, *phy_node;
	int num_tx_qs;
	int num_rx_qs;

	/* 设置 MX6UL_PAD_ENET1_TX_CLK 和 MX6UL_PAD_ENET2_TX_CLK
	* 这两个 IO 的复用寄存器的 SION 位为 1。
	*/
	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);

	fec_enet_get_queue_num(pdev, &num_tx_qs, &num_rx_qs);

	/* Init network device */
	ndev = alloc_etherdev_mqs(sizeof(struct fec_enet_private),
				  num_tx_qs, num_rx_qs);
......

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

5、配置 Linux 内核,使能 LAN8720 驱动

输入命令“make menuconfig”,打开图形化配置界面,选择使能 LAN8720A 的驱动,路径如下:
-> Device Drivers
-> Network device support
-> PHY Device support and infrastructure
-> Drivers for SMSC PHYs

image-20200819160409699

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

6、修改 smsc.c 文件

在修改 smsc.c 文件之前先说点题外话,那就是我是怎么确定要修改 smsc.c 这个文件的。在写本书之前我并没有修改过 smsc.c 这个文件,都是使能 LAN8720A 驱动以后就直接使用。但是我在测试 NFS 挂载文件系统的时候发现文件系统挂载成功率很低!老是提示 NFS 服务器找不到,三四次就有一次挂载失败!很折磨人。NFS 挂载就是通过网络来挂载文件系统,这样做的好处就是方便我们后续调试 Linux 驱动。既然老是挂载失败那么可以肯定的是网络驱动有问题,网络驱动分两部分:内部 MAC+外部 PHY,内部 MAC 驱动是由 NXP 提供的,一般不会出问题,否则的话用户早就给 NXP 反馈了。而且我用 NXP 官方的开发板测试网络是一直正常的,但是 NXP 官方的开发板所使用的 PHY 芯片为KSZ8081。所以只有可能是外部 PHY,也就是LAN8720A 的驱动可能出问题了。

鉴于 LAN8720A有“前车之鉴”,那就是在 uboot 中需要对LAN8720A 进行一次软复位,要设置LAN8720A 的 BMCR(寄存器地址为 0)寄存器 bit15 为 1。所以我猜测,在 Linux 中也需要对 LAN8720A 进行一次软复位。首先需要找到 LAN8720A 的驱动文件,LAN8720A 的驱动文件是 drivers/net/phy/smsc.c,在此文件中有个叫做 smsc_phy_reset 的函数,看名字都知道这是 SMSC PHY 的复位函数,因此,LAN8720A 肯定也会使用到这个复位函数,修改此函数的内容,修改以后的 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 */ {
        /*获取 FEC1 网卡对应的设备节点。*/
        np = of_find_node_by_path("/soc/aips-bus@02100000/ethernet@02188000");
        if(np == NULL) {
            return -EINVAL;
        }
    }

    if(phydev->addr == 1) /* FEC2 */ {
        /*获取 FEC2 网卡对应的设备节点。*/
        np = of_find_node_by_path("/soc/aips-bus@02000000/ethernet@020b4000");
        if(np == NULL) {
            return -EINVAL;
        }
    }
	
    /*从设备树中获取“phy-reset-duration”属性信息,也就是复位时间。*/
    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-gpios”属性信息,也就是复位 IO。*/
    phy_reset = of_get_named_gpio(np, "phy-reset-gpios", 0);
    if (!gpio_is_valid(phy_reset))
        return;

    /*设置 PHY 的复位 IO,复位 LAN8720A*/
    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) {
		int timeout = 50000;

		/* set "all capable" mode and reset the phy */
		rc |= MII_LAN83C185_MODE_ALL;
		phy_write(phydev, MII_LAN83C185_SPECIAL_MODES, rc);
	}
    /*这里我们将软复位代码移出来,这样每次调用 smsc_phy_reset 函数 LAN8720A 都会被软复位*/
    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;
}

最后我们还需要在 drivers/net/phy/smsc.c 文件中添加两个头文件,因为修改后的smsc_phy_reset 函数用到了 gpio_direction_output 和 gpio_set_value 这两个函数,需要添加的头文件如下所示:

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

7、网络驱动测试

修改好设备树和 Linux 内核以后重新编译一下,得到新的 zImage 镜像文件和 imx6ull-lxg-emmc.dtb设备树文件,使用网线将 I.MX6U-ALPHA 开发板的两个网口与路由器或者电脑连接起来,最后使用新的文件启动 Linux 内核。启动以后使用“ifconfig”命令查看一下当前活动的网卡有哪些,结果如图 所示:

image-20200819162229077

从图可以看出,当前没有活动的网卡。

输入命令“ifconfig -a”来查看一下开发板中存在的所有网卡,结果如图所示:

image-20200819162256490

图中 can0 和 can1 为 CAN 接口的网卡,eth0 和 eth1 才是网络接口的网卡,其中eth0 对应于 ENET2,eth1 对应于 ENET1。使用如下命令依次打开 eth0 和 eth1 这两个网卡:

ifconfig eth0 up
ifconfig eth1 up

网卡的打开过程如图 37.4.3.4 所示:

image-20200819162751401

可以看到“SMSC LAN8710/LAN8720”字样,说明当前的网络驱动使用的就是我们前面使能的 SMSC 驱动。再次输入“ifconfig”命令来查看一下当前活动的网卡,结果如图所示:

image-20200819162818948

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

ifconfig eth0 192.168.0.251
ifconfig eth1 192.168.0.252

上述命令配置 eth0 和 eth1 这两个网卡的 IP 地址,注意 IP 地址选择的合理性,一定要和自己的电脑处于同一个网段内,并且没有被其他的设备占用!

设置好以后,使用“ping”命令来 ping 一下自己的主机,如果能 ping 通那说明网络驱动修改成功!比如我的 Ubuntu 主机 IP 地址为 192.168.0.111,使用如下命令 ping 一下:ping 192.168.0.111
结果如图 37.4.3.6 所示:

image-20200819162957546

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

在修改网络驱动的时候我们通过图形界面使能了 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_lxg_emmc_defconfig,然后其复制到 arch/arm/configs 目录下,替换以前的imx_lxg_emmc_defconfig。这样以后执行“make imx_lxg_emmc_defconfig”重新配置Linux 内核的时候就会使用新的配置文件,默认就会使能 LAN8720A 的驱动。

2、通过图形界面保存配置文件

相比于第 1 种直接另存为.config 文件,第 2 种方法就很“文雅”了,在图形界面中保存配置文件,在图形界面中会有“< Save >”选项,如图 37.4.4.1 所示:

image-20200819163450607

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

image-20200819163502121

在图 中输入要保存的文件名,可以带路径,一般是相对路径(相对于 Linux 内核源码根目录)。比如我们要将新的配置文件保存到目录 arch/arm/configs 下,文件名为imx_lxg_emmc_defconfig,也就是用新的配置文件替换掉老的默认配置文件。那么我们在图中输入 “arch/arm/configs/imx_lxg_emmc_defconfig”即可,如图 37.4.4.3 所示:

image-20200819163905798