LV05-02-linux内核-03-linux内核移植
本文主要是对NXP官方提供的内核进行移植,使其适配我们自己的开发板的相关笔记。若笔记中有错误或者不合适的地方,欢迎批评指正😃。
点击查看使用工具及版本
Windows版本 | windows11 |
Ubuntu版本 | Ubuntu16.04的64位版本 |
VMware® Workstation 16 Pro | 16.2.3 build-19376536 |
终端软件 | MobaXterm(Professional Edition v23.0 Build 5042 (license)) |
Linux开发板 | 正点原子 i.MX6ULL Linux 阿尔法开发板 |
uboot | NXP官方提供的uboot,NXP提供的版本为uboot-imx-rel_imx_4.1.15_2.1.0_ga(使用的uboot版本为U-Boot 2016.03) |
linux内核 | linux-4.15(NXP官方提供) |
Win32DiskImager | Win32DiskImager v1.0 |
点击查看本文参考资料
分类 | 网址 | 说明 |
官方网站 | https://www.arm.com/ | ARM官方网站,在这里我们可以找到Cotex-Mx以及ARMVx的一些文档 |
https://www.nxp.com.cn/ | NXP官方网站 | |
https://www.nxpic.org.cn/ | NXP 官方社区 | |
https://u-boot.readthedocs.io/en/latest/ | u-boot官网 | |
https://www.kernel.org/ | linux内核官网 |
点击查看相关文件下载
分类 | 网址 | 说明 |
NXP | https://github.com/nxp-imx | NXP imx开发资源GitHub组织,里边会有u-boot和linux内核的仓库 |
https://elixir.bootlin.com/linux/latest/source | 在线阅读linux kernel源码 | |
nxp-imx/linux-imx/releases/tag/rel_imx_4.1.15_2.1.0_ga | NXP linux内核仓库tags中的rel_imx_4.1.15_2.1.0_ga | |
nxp-imx/uboot-imx/releases/tag/rel_imx_4.1.15_2.1.0_ga | NXP u-boot仓库tags中的rel_imx_4.1.15_2.1.0_ga | |
I.MX6ULL | i.MX 6ULL Applications Processors for Industrial Products | I.MX6ULL 芯片手册(datasheet,可以在线查看) |
i.MX 6ULL Applications ProcessorReference Manual | I.MX6ULL 参考手册(下载后才能查看,需要登录NXP官网) |
一、添加开发板
1. 添加开发板默认配置文件
将linux内核源码目录中 arch/arm/configs 目录下的 imx_v7_mfg_defconfig 重新复制一份 , 命名 为imx_alpha_emmc_defconfig:
1 | cp arch/arm/configs/imx_v7_defconfig arch/arm/configs/imx_alpha_emmc_defconfig |
以后 imx_alpha_emmc_defconfig 就是正点原子的 EMMC 版开发板默认配置文件了。完成以后如图所示:
以后就可以使用如下命令来配置正点原子 EMMC 版开发板对应的 Linux 内核了
1 | make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- imx_alpha_emmc_defconfig |
2. 添加开发板对应的设备树文件
添加适合正点原子 EMMC 版开发板的设备树文件,进入目录 arch/arm/boot/dts 中,复制一份 imx6ull-14x14-evk.dts,然后将其重命名为 imx6ull-alpha-emmc.dts :
1 | cp arch/arm/boot/dts/imx6ull-14x14-evk.dts arch/arm/boot/dts/imx6ull-alpha-emmc.dts |
.dts 是设备树源码文件,编译 Linux 的时候会将其编译为.dtb 文件。imx6ull-alpha-emmc.dts创建好以后我们还需要修改文件arch/arm/boot/dts/Makefile ,找 到 “ dtb-$(CONFIG_SOC_IMX6ULL)”配置项,在此配置项中加入“imx6ull-alpha-emmc.dtb” ,如下
所示 :
1 | #...... |
3. 编译测试
Linux 内核里面已经添加了正点原子 I.MX6UL-ALIPHA EMMC 版 开 发 板 了 , 接 下接编译测试一下,我们可以创建一个编译脚本——1.sh,脚本内容如下 :
1 | !/bin/sh |
然后我们执行此脚本就会开始编译内核和设备树文件,一般应该是没问题的,编译完成后我们可以得到如下两个文件:
4. 启动测试
编译完成以后就会在目录 arch/arm/boot 下生成 zImage 镜像文件。在 arch/arm/boot/dts 目录下生成 imx6ull-alpha-emmc.dtb 文件。将这两个文件拷贝到 tftp 目录下:
1 | cp arch/arm/boot/zImage ~/3tftp/imx6ull/ |
然后重启开发板(按照前边从tftp加载内核和设备树,从nfs挂载根文件系统,根文件系统可以先随便找一个能用的),在uboot 命令模式中使用 tftp 命令下载这两个文件并启动,命令如下:
1 | => tftp 80800000 /imx6ull/zImage |
然后应该可以正常启动:
我们可以看一下启动后的linux:
会发现这里有报错,并且未成功加载根文件系统,这里就是说网络配置有问题,我们前边知道,ALPHA使用的网络驱动与NXP官方的EVK评估板是不一样的,网络 PHY 芯片由 KSZ8081 换为了 LAN8720A,两个网络 PHY 芯片的复位 IO 也不同。所以 Linux 内核自带的网络驱动是驱动不起来 I.MX6U-ALPHA 开发板上的网络的,所以这里网口驱动其实是无法使用的,linux无法通过网口来进行网络相关的活动,需要做修改。
二、修改网络驱动
1. 设备树修改
1.1 修改 LAN8720 的复位以及网络时钟引脚驱动
ENET1 复位引脚 ENET1_RST 连接在 I.M6ULL 的 SNVS_TAMPER7 这个引脚上。 ENET2的复位引脚 ENET2_RST 连接在 I.MX6ULL 的 SNVS_TAMPER8 上。打开设备树文件 imx6ull-alpha-emmc.dts:
第 588 和 589 行就是初始化 SNVS_TAMPER7 和 SNVS_TAMPER8 这两个引脚的,不过看样子好像是作为了 SPI4 的 IO,这不是我们想要的,所以将 588 和 589 这两行删除掉。修改成如下所示代码:
1 | pinctrl_spi4: spi4grp { |
删除掉以后继续在 imx6ull-apha-emmc.dts 中找到如下所示代码:
第 129 行,设置 GPIO5_IO08 为 SPI4 的一个功能引脚(我也不清楚具体作为什么功能用),而 GPIO5_IO08 就是 SNVS_TAMPER8 的 GPIO 功能引脚。
第 133 行,设置 GPIO5_IO07 作为 SPI4 的片选引脚,而 GPIO5_IO07 就是 SNVS_TAMPER7的 GPIO 功能引脚。现在我们需要 GPIO5_IO07 和 GPIO5_IO08 分别作为 ENET1 和 ENET2 的复位引脚,而不是 SPI4 的什么功能引脚,因此将上图中的第 129 行和第 133 行处的代码删除掉,否则会干扰到网络复位引脚! 修改后如下所示:
1 | spi4 { |
在 imx6ull-alpha-emmc.dts 里面找到名为 “iomuxc_snvs” 的节点(就是直接搜索),然后在此节点下添加网络复位引脚信息,添加完成以后的 “ iomuxc_snvs ” 的节点内容如下:
1 | &iomuxc_snvs { |
最后还需要修改一下 ENET1 和 ENET2 的网络时钟引脚配置, 继续在 imx6ull-alpha-emmc.dts 中找到如下所示代码:
第 318 和 333 行, 分别为 ENET1 和 ENET2 的网络时钟引脚配置信息,将这两个引脚的电气属性值改为 0x4001b009,原来默认值为 0x4001b031。修改完成以后记得保存一下 imx6ull-alientek-emmc.dts,网络复位以及时钟引脚驱动就修改好了。
1 | pinctrl_enet1: enet1grp { |
1.2 修改 fec1 和 fec2 节点的 pinctrl-0 属性
在 imx6ull-alpha-emmc.dts 文件中找到名为“fec1”和“fec2”的这两个节点,修改其中的“pinctrl-0”属性值,修改以后如下所示 :
1 | &fec1 { |
1.3 修改 LAN8720A 的 PHY 地址
在 uboot 移植章节中,我们知道 ENET1 的 LAN8720A 地址为 0x0, ENET2 的 LAN8720A地址为 0x1。在 imx6ull-alpha-emmc.dts 中找到如下代码:
第 171~177 行, ENET1 对应的设备树节点。
第 179200 行, ENET2 对应的设备树节点。但是第 186198 行的 mdio 节点描述了 ENET1和 ENET2 的 PHY 地址信息。将上图改为如下内容:
1 | &fec1 { |
修改的地方如下:
第 176 和 177 行,添加了 ENET1 网络复位引脚所使用的 IO 为 GPIO5_IO07,低电平有效。复位低电平信号持续时间为 200ms。
第 186 和 187 行, ENET2 网络复位引脚所使用的 IO 为 GPIO5_IO08,同样低电平有效,持续时间同样为 200ms。
第 196 和 202 行,“smsc,disable-energy-detect”表明 PHY 芯片是 SMSC 公司的,这样 Linux内核就会找到 SMSC 公司的 PHY 芯片驱动来驱动 LAN8720A。
第 194 行,注意“ethernet-phy@”后面的数字是 PHY 的地址, ENET1 的 PHY 地址为 0,所以“@”后面是 0(默认为 2)。
第 197 行, reg 的值也表示 PHY 地址, ENET1 的 PHY 地址为 0,所以 reg=0。
第 200 行, ENET2 的 PHY 地址为 1,因此“@”后面为 1。
第 203 行,因为 ENET2 的 PHY 地址为 1,所以 reg=1。
1.4 设备树编译
至此, LAN8720A 的 PHY 地址就改好了,保存一下 imx6ull-alpha-emmc.dts 文件。然后使用以下命令重新编译一下设备树。
1 | make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- dtbs # 只编译设备树 |
编译完成后会有如下提示:
2. 内核源码修改
2.1 修改 fec_main.c 文件
要在 I.MX6ULL 上 使 用 LAN8720A , 需要修改一下 Linux 内核源码,打开 drivers/net/ethernet/freescale/fec_main.c,找到函数 fec_probe,在 fec_probe 中加入如下代码:
1 | /* 设置 MX6UL_PAD_ENET1_TX_CLK 和 MX6UL_PAD_ENET2_TX_CLK |
第 3452 ~ 3461 就是新加入的代码,如果要在 I.MX6ULL 上使用 LAN8720A 就需要设置ENET1 和 ENET2 的 TX_CLK 引脚复位寄存器的 SION 位为 1。
2.2 配置 Linux 内核,使能 LAN8720 驱动
输入命令下边的命令:
1 | make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- menuconfig |
打开图形化配置界面,选择使能 LAN8720A 的驱动,路径如下:
1 | .config - Linux/arm 4.1.15 Kernel Configuration |
选择将“ Drivers for SMSC PHYs ”编译到 Linux 内核中,因此“< >”里面变为了“ * ”。 LAN8720A 是 SMSC 公司出品的,因此勾选这个以后就会编译LAN8720 驱动,配置好以后退出配置界面,然后重新编译一下 Linux 内核即可。
2.3 修改 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函数内容如下所示:
1 | static int smsc_phy_reset(struct phy_device *phydev) |
第 7~14 行,获取 FEC1 网卡对应的设备节点。
第 16~23 行,获取 FEC2 网卡对应的设备节点。
第 25 行,从设备树中获取“phy-reset-duration”属性信息,也就是复位时间。
第 29 行,从设备树中获取“phy-reset-gpios”属性信息,也就是复位 IO。
第 33~36 行,设置 PHY 的复位 IO,复位 LAN8720A。
第 45~52 行,以前的 smsc_phy_reset 函数会判断 LAN8720 是否处于 Powerdown 模式,只有处于 Powerdown 模式的时候才会软复位 LAN8720。这里我们将软复位代码移出来,这样每次调用 smsc_phy_reset 函数 LAN8720A 都会被软复位。最后我们还需要在 drivers/net/phy/smsc.c 文件中添加两个头文件,因为修改后的smsc_phy_reset 函数用到了 gpio_direction_output 和 gpio_set_value 这两个函数,需要添加的头文件如下所示:
1 |
3. 图形化配置文件处理
在修改网络驱动的时候我们通过图形界面使能了 LAN8720A 的驱动,使能以后会在.config中存在如下代码:
打开 drivers/net/phy/Makefile,有如下代码:
当 CONFIG_SMSC_PHY=y 的时候就会编译 smsc.c 这个文件, smsc.c 就是 LAN8720A 的驱动文件。但是当我们执行“make distclean”清理工程以后.config 文件就会被删除掉,因此我们所有的配置内容都会丢失,结果就是前功尽弃!所以我们在配置完图形界面以后经过测试没有问题,就必须要保存一下配置文件,保存配置的方法有两个。
3.1 直接另存为.config 文件
既然图形化界面配置后的配置项保存在.config 中,那么我们就可以直接将.config 文件另存为 imx_alpha_emmc_defconfig,然后其复制到arch/arm/configs 目录下,替换以前的imx_alpha_emmc_defconfig 。这样以后执行“ make imx_alpha_emmc_defconfig”重新配置Linux 内核的时候就会使用新的配置文件,默认就会使能 LAN8720A 的驱动。
1 | cp .config arch/arm/configs/imx_alpha_emmc_defconfig |
3.2 通过图形界面保存配置文件
我们还有一种方式在图形界面中保存配置文件,在图形界面中会有“< Save >”选项,如图所示:
通过键盘的“→”键,移动到“< Save >”选项,然后按下回车键,打开文件名输入对话框,如图所示:
在图位置中输入要保存的文件名,可以带路径,一般是相对路径(相对于 Linux 内核源码根目录 )。比如我们要将新的配置文件保存到目录arch/arm/configs 下,文件名为 imx_alpha_emmc_defconfig,也就是用新的配置文件替换掉老的默认配置文件。那么我们在图中输入“arch/arm/configs/imx_alpha_emmc_defconfig”即可:
1 | arch/arm/configs/imx_alpha_emmc_defconfig |
设置好文件名以后选择下方的“ < Ok >”按钮,保存文件并退出。退出以后再打开 arch/arm/configs/imx_alpha_emmc_defconfig 文件,就会在此文件中找到“CONFIG_SMSC_PHY=y”这一行:
同样的,使用“make imx_alientek_emmc_defconfig”重新配置 Linux 内核的时候, LAN8720A的驱动就会使能,并被编译进 Linux 镜像文件 zImage 中。
4. 网口测试
4.1 文件准备
我们这里由于还要编译内核,所以直接使用下边的命令一步到位:
1 | make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- all -j16 |
编译完成后我们将设备树和zImage文件拷贝到tftp目录下:
1 | cp arch/arm/boot/zImage ~/3tftp/imx6ull/ |
修改好设备树和 Linux 内核以后重新编译一下,得到新的 zImage 镜像文件和 imx6ull-alpha-emmc.dtb 设备树文件,使用网线将 I.MX6U-ALPHA 开发板的两个网口与路由器或者电脑连接起来,最后使用新的文件启动 Linux 内核。
4.2 bootamd与bootargs
uboot的这两个参数内容如下:
1 | => setenv bootcmd 'tftp 80800000 /imx6ull/zImage\;tftp 83000000 /imx6ull/imx6ull-alpha-emmc.dtb\;bootz 80800000 - 83000000' |
下边的 bootargs 其实就是给linux内核传递的参数,会配置好IP地址,并且从NFS挂载根文件系统。上边的bootm表示我们从tftp下载zImage和设备树。
4.2 启动测试
4.2.1 启动过程
若是网络驱动移植成功,并且有根文件系统的话就会正常挂载了(我这里用的是我自己移植的busybox根文件系统):
- (1)u-boot启动
- (2)下载zImage
- (3)下载设备树
- (4)启动内核
- (5)挂载NFS根文件系统
4.2.2 网络配置
启动以后使用“ifconfig -a”命令查看一下当前活动的网卡有哪些,结果如图所示:、
1 | root@imx: /$ ifconfig -a |
中 can0 和 can1 为 CAN 接口的网卡, eth0 和 eth1 才是网络接口的网卡,其中eth0 对应于 ENET2, eth1 对应于 ENET1。使用如下命令依次打开 eth0 和 eth1 这两个网卡:
1 | ifconfig eth0 up |
注意,我这里是接了eth0,并且用于挂载根文件系统,所以它是直接启动的,要是连接了eth1的话可以用上边的命令启动网卡。我们可以通过以下命令配置开发板的IP地址:
1 | ifconfig eth0 192.168.1.251 |
我们也可以使用ping命令来查看是否与同一网段的ip可以ping通:
三、移植总结
关于 Linux 内核的移植就学习到这里,简单总结一下移植步骤:
(1)在 Linux 内核中查找可以参考的板子,一般都是半导体厂商自己做的开发板。
(2)编译出参考板子对应的 zImage 和.dtb 文件。
(3)使用参考板子的 zImage 文件和.dtb 文件在我们所使用的板子上启动 Linux 内核,看能否启动。
(4)如果能启动的话就万事大吉,如果不能启动那就悲剧了,需要调试 Linux 内核。不过一般都会参考半导体官方的开发板设计自己的硬件,所以大部分情况下都会启动起来。启动Linux 内核用到的外设不多,一般就 DRAM(Uboot 都初始化好的)和串口。作为终端使用的串口一般都会参考半导体厂商的 Demo 板。
(5)修改相应的驱动,像 NAND Flash、 EMMC、 SD 卡等驱动官方的 Linux 内核都是已经提供好了,基本不会出问题。重点是网络驱动,因为 Linux 驱动开发一般都要通过网络调试代码,所以一定要确保网络驱动工作正常。如果是处理器内部 MAC+外部 PHY 这种网络方案的话,一般网络驱动都很好处理,因为在 Linux 内核中是有外部 PHY 通用驱动的。只要设置好复位引脚、 PHY 地址信息基本上都可以驱动起来。
(6)Linux 内核启动以后需要根文件系统,如果没有根文件系统的话肯定会崩溃,所以确定 Linux内核移植成功以后就要开始根文件系统的构建。