0%

Linux驱动学习实战(2) Linux Kernal 移植

干什么

  尝试根据正点原子提供的教程将6.1版本的 Linux Kernal 移植到开发板上。

怎么干

NXP 官方开发板 Linux 内核编译

  NXP 提供的 Linux 源码肯定是可以在自己的 I.MX6ULL EVK 开发板上运行下去的,所以我们肯定是以 I.MX6ULL EVK 开发板为参考,然后将 Linux 内核移植到 I.MX6U-ALPHA 开发板上的。

  在顶层 Makefile 中合适的地方添加如下语句:

CROSS_COMPILE = /home/hcw/toolchain/gcc-arm-11.2-2022.02-x86_64-arm-none-linux-gnueabihf/bin/arm-none-linux-gnueabihf-
ARCH = arm

  在终端中输入如下命令

make clean
make imx_v6_v7_defconfig
make -j16

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

  如何将编译好的 linux 的镜像传到开发板中呢,我们在虚拟机上搭建tftp服务器即可,具体操作百度教程。

  搭建好服务器后,我们将编译好的镜像和设备树复制到服务器传输目录,输入如下命令

sudo cp arch/arm/boot/zImage /srv/tftp/ -f
sudo cp arch/arm/boot/dts/imx6ull-14x14-evk.dtb /srv/tftp/ -f

紧接着,串口连接开发板,输入以下命令

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

出现如下界面就算进入成功了:

图 1 进入成功

在 Linux 中添加自己的开发板

添加默认配置文件

  将arch/arm/configs 目录下的 imx_v6_ v7_defconfig 重新复制一份,命名为 imx_hcw_e mmc_defconfig,命令如下

cd arch/arm/configs
cp imx_v6_v7_defconfig imx_hcw_emmc_defconfig

打开imx_hcw_emmc_defconfig 文件,找到“CONFIG_ARCH_MULTI_V6=y”这一行, 将其屏蔽掉。i.MX6ULL 是 ARMv7-A,只启用 v7 可以避免 v6 平台的旧兼容代码被选入,从而减少 Kconfig 冲突和模块 ABI 差异风险。以后 imx_alientek_emmc_defconfig 就是正点原子的 EMMC 版开发板默认配置文件了。运行make imx_hcw_emmc_defconfig。

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

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

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

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

1
2
3
dtb-$(CONFIG_SOC_IMX6UL) += \
imx6ull-hcw-emmc.dtb \
...

编译尝试

  可以创建如下编译脚本

1
2
3
4
make distclean  
make imx_hcw_emmc_defconfig
make menuconfig
make all -j16

编译完成后,将镜像文件和设备树文件复制到 tftp 文件夹,在开发板上通过网络下载后启动。

图 2 添加成功
Linux 内核启动成功,说明我们已经在 NXP 提供的 Linux 内核源码中添加了正点原子 I.MX6UL-ALPHA 开发板。

CPU 主频

  进入到目录/sys/bus/cpu/devices/cpu0/cpufreq 中,此目录下会有很多文件,如图 3 所示:

图 3 文件夹
此目录中记录了CPU频率等信息,这些文件的含义如下:
cpuinfo_cur_freg:当前cpu工作频率,从CPU寄存器读取到的工作频率。
cpuinfo_max_freq:处理器所能运行的最高工作频率(单位:KHz)。
cpuinfo_min_freg:处理器所能运行的最低工作频率(单位:KHz)。
cpuinfo_transition_latency:处理器切换频率所需要的时间(单位:ns)。
scaling_available_frequencies:处理器支持的主频率列表(单位:KHz)。
scaling_available_governors:当前内核中支持的所有goveror(调频)类型。
scaling_cur_freg:保存着cpufreq模块缓存的当前CPU频率,不会对CPU硬件寄存器进行检查。
scaling_driver:该文件保存当前CPU所使用的调频驱动。
scaling_governor:governor(调频)策略,Linux内核一共有5中调频策略:
    1、Performance,最高性能,直接用最高频率,不考虑耗电。
    2、Interactive,一开始直接用最高频率,然后根据CPU负载慢慢降低。
    、Powersave,省电模式,通常以最低频率运行,系统性能会受影响,一般不会用这个!
    4、Userspace,可以在用户空间手动调节频率。
    5、Ondemand,定时检查负载,然后根据负载来调节频率。负载低的时候降低CPU频率,这样省电,负载高的时候提高CPU频率,增加性能。

scaling_max_freq:governor(调频)可以调节的最高频率。
cpuinfo_min_freg:governor(调频)可以调节的最低频率。
stats目录下给出了CPU各种运行频率的统计情况,比如CPU在各频率下的运行时间以及变频次数。
图 4 几个cpufreq信息

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

cat /sys/bus/cpu/devices/cpu0/cpufreq/stats/time_in_state
图 5 各个状态运行时间

  在 menuconfig 中可以对 CPU 运行策略进行调整,运行 make menuconfig 命令打开图形化配置,进入如下路径

CPU Power Management
-> CPU Frequency scaling
-> Default CPUFreq governor

然后就可以选择你想要用的电源策略了。

EMMC驱动修改

  接下来我们修改 EMMC 驱动,正点原子 EMMC 版本核心板上的EMMC采用8位数据线:

图 6 EMMC原理图

  Linux 内核驱动里面 EMMC 默认是 4 线模式的,4 线模式肯定没有 8 线模式的速度快,所 以本节我们将 EMMC 的驱动修改为 8 线模式。修改方法很简单,直接修改设备树即可,打开文件 imx6ull-hcw-emmc.dts,发现其引用了 imx6ul-14x14-evk.dtsi 和 imx6ull.dtsi 两个文件。根据之前移植 U-Boot的经验,我们复制一份 imx6ul-14x14-evk.dtsi 并将其重命名为 imx6ull-hcw-emmc.dtsi,将 imx6ull-hcw-emmc.dts 文件中的路径改掉。然后打开 imx6ull-hcw-emmc.dtsi,在引脚组中添加以下内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
pinctrl_usdhc2_8bit: usdhc2grp8bit {
fsl,pins = <
MX6UL_PAD_NAND_RE_B__USDHC2_CLK 0x17059
MX6UL_PAD_NAND_WE_B__USDHC2_CMD 0x17059
MX6UL_PAD_NAND_DATA00__USDHC2_DATA0 0x17059
MX6UL_PAD_NAND_DATA01__USDHC2_DATA1 0x17059
MX6UL_PAD_NAND_DATA02__USDHC2_DATA2 0x17059
MX6UL_PAD_NAND_DATA03__USDHC2_DATA3 0x17059
MX6UL_PAD_NAND_DATA04__USDHC2_DATA4 0x17059
MX6UL_PAD_NAND_DATA05__USDHC2_DATA5 0x17059
MX6UL_PAD_NAND_DATA06__USDHC2_DATA6 0x17059
MX6UL_PAD_NAND_DATA07__USDHC2_DATA7 0x17059
>;
};

将 usdhc2 的配置改为以下代码

1
2
3
4
5
6
7
8
&usdhc2 {
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_usdhc2_8bit>;
bus-width = <8>;
non-removable;
no-1-8-v;
status = "okay";
};

这里说明物理引脚的复用也切换到了8bit。最后是关闭 1.8V 驱动 emmc 的选项。

网卡驱动修改(这次已失败)

  有了 UBoot 移植的经验,直接操作即可

①、在引脚组中添加以下代码

1
2
3
4
5
6
7
8
9
10
pinctrl_enet1_reset: enet1resetgrp {
fsl,pins = <
MX6ULL_PAD_SNVS_TAMPER7__GPIO5_IO07 0x00000
>;
};
pinctrl_enet2_reset: enet2resetgrp {
fsl,pins = <
MX6UL_PAD_SNVS_TAMPER8__GPIO5_IO08 0x00000
>;
};

②、修改 fec1 和 fec2 节点的 pinctrl-0 属性
先添加 ENET1 网络复位引脚所使用的 IO 为 GPIO5_IO07,低电平有效。复位低电平信号持续时间为 200ms;ENET2 网络复位引脚所使用的 IO 为 GPIO5_IO08,同样低电平有效,持续时间同样为 200ms。smsc,disable-energy-detect 是 SMSC/Microchip PHY 的一个可选特性配置,用于关闭能量检测功能,不参与驱动匹配。修改后的代码如下所示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
&fec1 {

status = "disabled";
};

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

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

ethphy0: ethernet-phy@2 {
compatible = "ethernet-phy-ieee802.3-c22";
reg = <2>;
micrel,led-mode = <1>;
clocks = <&clks IMX6UL_CLK_ENET_REF>;
clock-names = "rmii-ref";

};

ethphy1: ethernet-phy@1 {
compatible = "ethernet-phy-id0022.1560";
reg = <1>;
micrel,led-mode = <1>;
clocks = <&clks IMX6UL_CLK_ENET2_REF>;
clock-names = "rmii-ref";
};
};
};

③、修改 fec_main.c 文件(这是临时验证手段,不推荐长期保留,正确方式应使用设备树 reset 时序。)
要在I.MX6ULL 上使用 SR8201F,需要修改一下 Linux 内核源码,打开 drivers/net/ethernet/freescale/fec_main.c,找到函数 fec_reset_phy,在 fec_reset_phy 函数的最后中加入如下代码:

msleep(200);

测试与修复

  修改好设备树和 Linux 内核以后重新编译一下,得到新的 zImage 镜像文件和 imx6ull-alien tek-emmc.dtb 设备树文件。由图可知EMMC识别成功

图 7 EMMC识别成功

但是输入 ifconfig -a 后,eth0显示网卡连接失败,我们来排查问题。

输入命令 dmesg | grep -E “fec|ethernet|phy|mdio” 查看日志

1
2
3
4
5
6
7
root@ATK-IMX6U:~# dmesg | grep -E "fec|ethernet|phy|mdio"
[ 0.000000] Booting Linux on physical CPU 0x0
[ 4.929905] gpio-505 (eth0-phy): hogged as output/high
[ 4.935809] gpio-506 (eth1-phy): hogged as output/high
[ 5.003515] fec 20b4000.ethernet: failed to get phy-reset-gpios: -16
[ 5.010341] fec: probe of 20b4000.ethernet failed with error -16
[ 5.184764] usbcore: registered new interface driver MOSCHIP usb-ethernet driver

可以看出 phy-reset-gpios 这根 GPIO 已经被别人占用了,FEC 驱动想申请它时失败。-16 在 Linux 里基本就是 EBUSY(资源忙 / 已被占用)。于是 FEC 驱动 probe 失败 → eth0 后面要么不出现,要么出现又立刻废掉。找到使用了gpio5 7/8 引脚的代码并删除。

再次编译 烧录,继续查看日志

1
2
3
4
5
6
7
8
9
root@ATK-IMX6U:~# dmesg | grep -E "fec|ethernet|phy|mdio"
[ 0.000000] Booting Linux on physical CPU 0x0
[ 4.956137] gpio-505 (eth0-phy): hogged as output/high
[ 4.962020] gpio-506 (eth1-phy): hogged as output/high
[ 5.287607] mdio_bus 20b4000.ethernet-1: MDIO device at address 2 is missing.
[ 5.338766] fec 20b4000.ethernet eth0: registered PHC device 0
[ 5.504852] usbcore: registered new interface driver MOSCHIP usb-ethernet driver
[22.560239] Micrel KSZ8081 or KSZ8091 20b4000.ethernet-1:01: attached PHY driver (mii_bus:phy_addr=20b4000.ethernet-1:01, irq=POLL)
[ 25.714492] fec 20b4000.ethernet eth0: Link is Up - 100Mbps/Full - flow control rx/tx

可以看出 gpio 占用的问题已经解决。第五行表明只有一个phy设备,地址为1,另一个未找到。第 8 行说明已经跑通了。接下来我们设置 ip 地址,将其和电脑处于同一网段下,成功ping通!

图 8 网络成功

最终的网口配置代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
&fec1 {

status = "disabled";
};

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

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

ethphy1: ethernet-phy@1 {
compatible = "ethernet-phy-id0022.1560";
reg = <1>;
micrel,led-mode = <1>;
clocks = <&clks IMX6UL_CLK_ENET2_REF>;
clock-names = "rmii-ref";
};
};
};

结语

  通过本次移植,我认识到了 Linux 内核的启动流程和设备初始化方式,同时学习了一些常用的日志查看命令与分析方法。