Linux内核移植教程

描述

内核移植

半导体厂商会从 Linux内核官网下载某个版本,将其移植到自己的 CPU上,测试成功后就会将其开放给该半导体厂商的 CPU开发者。 开发者下载其提供的 Linux内核,然后将其移植到自己的产品上。

本文使用 NXP提供的 Linux内核源码进行移植,文件名为:

linux-imx-rel_imx_4.1.15_2.1.0_ga.tar.bz2

1.NXP官方开发板Linux内核编译测试

编译内核之前需要先在ubuntu上安装 lzop库,另外,图形化配置工具还需要 ncurses库支持,安装命令为:

sudo apt-get install lzop
sudo apt-get install build-essential 
sudo apt-get install libncurses5-dev

在 Ubuntu中新建一个文件夹,然后将 linux内核压缩包拷贝到文件夹中并解压,解压命令为:

tar -vxf linux-imx-rel_imx_4.1.15_2.1.0_ga.tar.bz2

1.1 配置并编译内核

编译 Linux内核之前要先配置 Linux内核。 每个板子都有对应的默认配置文件,保存在“arch/arm/configs”目录中。 imx_v7_defconfig和imx_v7_mfg_defconfig都可以作为 NXP官方开发板 IMX6ULL EVK的默认配置文件,一般使用后者,因为后者编译出来的 zImage可通过 NXP官方提供的 MfgTool工具进行烧写

进入到 Ubuntu中的 Linux源码根目录下,执行如下命令:

#编译之前先清理
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- distclean 	
#配置Linux内核
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- imx_v7_mfg_defconfig 
#编译Linux内核
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- all -j16

编译完成后,会得到两个重要文件:

  • Linux内核镜像文件:存放路径为 arch/arm/boot/zImage
  • IMX6ULL EVK开发板对应的设备树文件:存放路径为 arch/arm/boot/dts/imx6ull-14x14-evk.dtb

1.2 内核启动测试

将上一节中生成的 zImage和 imx6ull-14x14-evk.dtb这两个文件下载到 IMX6U-ALPHA开发板上进行测试。

首先检查 uboot中的环境变量 bootargs

console=ttymxc0,115200 root=/dev/mmcblk1p2 rootwait rw

然后拷贝 zImage和 imx6ull-14x14-evk.dtb这两个文件到 Ubuntu的 tftp目录下

cp arch/arm/boot/zImage /home/andyxi/linux/tftp
cp arch/arm/boot/dts/imx6ull-14x14-evk.dtb /home/andyxi/linux/tftp

最后启动开发板,进入 uboot命令行模式,输入如下命令以上两个文件下载到开发板中并启动

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

内核启动后,如果 EMMC中存在根文件系统,就可以进入到 Linux系统里使用命令进行操作,如下图示

源码

1.3 根文件系统缺失错误

Linux内核启动以后是需要根文件系统的,根文件系统存在哪里由 uboot的 bootargs环境变量决定的,bootargs会传递给 Linux内核作为命令行参数,比如上一节的 bootargs环境变量值为:

console=ttymxc0,115200 root=/dev/mmcblk1p2 rootwait rw

其中“root=/dev/mmcblk1p2”表示根文件系统存储在 /dev/mmcblk1p2中(即EMMC的分区2),IMX6UL-ALPHA开发板在出厂时已经将根文件系统烧写到了 EMMC的分区2中

源码

如果不设置文件系统路径或者路径设置错误,开发板从网络启动后会提示内核崩溃,VFS(虚拟文件系统)不能挂在文件系统。 下图中为不设置文件系统路径以及后续出现的报错信息

源码

2.在Linux内核中添加自已的开发板

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

将arch/arm/configs目录下的 imx_v7_mfg_defconfig文件重新复制一份并命名为 imx_andyxi_emmc_defconfig,此后该文件就是自已开发板的默认配置文件了

cd arch/arm/configs
cp imx_v7_mfg_defconfig imx_andyxi_emmc_defconfig

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

将arch/arm/boot/dts目录下的 imx6ull-14x14-evk.dts文件重新复制一份并命名为 imx6ull-andyxi-emmc.dts,.dts是设备树源码文件,编译Linux的时候会将其编译成.dtb文件

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

修改arch/arm/boot/dts目录下的Makefile文件,添加开发板设备树文件

########## arch/arm/boot/dts/Makefile代码段 ##########
dtb-$(CONFIG_SOC_IMX6ULL) += 		\\
  imx6ull-14x14-ddr3-arm2.dtb		\\
  imx6ull-14x14-ddr3-arm2-adc.dtb		\\
  ......
  ......
  imx6ull-14x14-evk-usb-certi.dtb		\\
  imx6ull-andyxi-emmc.dtb			\\
  ......
  ......

2.3 编译测试

添加完 IMX6UL-ALPHA EMMC开发板后,可以创建一个编译脚本imx6ull_andyxi_emmc.sh

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

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

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

编译完成后,将 zImage和 imx6ull-andyxi-emmc.dtb文件拷贝至 tftp目录下,然后重启开发板,在 uboot命令模式中使用tftp命令下载这两个文件并启动开发板

tftp 80800000 zImage
tftp 83000000 imx6ull-andyxi-emmc.dtb
bootz 8080000083000000

出现如下图示内容就表示 Linux内核启动成功

源码

3.内核网络驱动修改

3.1 使能8线EMMC驱动

IMX6UL-ALPHA EMMC开发板上的EMMC采用8位数据线,而Linux内核里的EMMC默认是4线模式的,可以通过修改为8线模式来提高运行速度

源码

修改方法:直接修改设备树源码文件 imx6ull-andyxi-emmc.dts,修改完成后使用"make dtbs"命令重新编译设备树即可

########## 修改后的imx6ull-andyxi-emmc.dts代码段 ##########
&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";
};

3.2 网络驱动修改

IMX6UL-ALPHA EMMC开发板的网络和 NXP官方开发板的网络硬件不同,网络 PHY芯片由 KSZ8081换为了 LAN8720A,两个网络PHY芯片的复位IO也不同,所以 Linux内核自带的网络驱动无法驱动IMX6UL-ALPHA EMMC开发板的网络,需要做如下更改

修改LAN8720的复位引脚驱动:ENET1复位引脚ENET1_RST连接在IMX6ULL的SNVS_TAMPER7引脚上; ENET2复位引脚ENET_RST连接在IMX6ULL的SNVS_TAMPER8引脚上

在设备树源码文件中找到如下代码段,此处SNVS_TAMPER7/8两个引脚被初始化为了SPI4的IO,所以需要删除

########## imx6ull-andyxi-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  #删除此行
  >;
};

在设备树源码文件中找到如下代码段,此处SNVS_TAMPER7/8两个引脚被设置为了SPI4的功能IO,所以需要删除

########## imx6ull-andyxi-emmc.dts 代码段 ##########
spi4 {
  compatible = "spi-gpio";
  pinctrl-names = "default";
  pinctrl-0 = <&pinctrl_spi4>;
  #pinctrl-assert-gpios = <&gpio5 8 GPIO_ACTIVE_LOW>; #删除此行
  ......
  ......
  #cs-gpios = <&gpio5 7 0>;	#删除此行
};

在设备树源码文件中找到名为“iomuxc_snvs”节点,在里面添加网络复位引脚配置信息

########## imx6ull-andyxi-emmc.dts 代码段 ##########
&iomuxc_snvs
{
pinctrl-names =
"default_snvs";
pinctrl-0 = <&pinctrl_hog_2>
;
imx6ul-evk
{
......
......
# enet1 复位配置
pinctrl_enet1_reset: enet1resetgrp
{
fsl
,pins = <
# used for enet1 reset #
MX6ULL_PAD_SNVS_TAMPER7__GPIO5_IO07 0x10B0
>
;
};
#enet2 复位配置
pinctrl_enet2_reset: enet2resetgrp
{
fsl
,pins = <
# used for enet2 reset #
MX6ULL_PAD_SNVS_TAMPER8__GPIO5_IO08 0x10B0
>
;
};
};
};

修改LAN8720的网络时钟引脚驱动

在设备树源码文件中找到如下代码段,将ENET1和ENET2的网络时钟引脚的电气属性值由0x4001b031(默认值)改为0x4001b009

########## imx6ull-andyxi-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#默认为0x4001b031
  >;
};

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#默认为0x4001b031
  >;
};

修改 fec1和 fec2节点的 pinctrl1-0属性

在设备树源码文件中找到“fec1”和“fec2”这两个节点,修改其中的“pinctrl-0”属性值,修改后的代码如下示

########## imx6ull-andyxi-emmc.dts 代码段 ##########
&fec1 {
  pinctrl-names = "default";
  pinctrl-0 = <&pinctrl_enet1
		 &pinctrl_enet1_reset>;
  phy-mode = "rmii";
......
  status = "okay";
};

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

修改LAN8720的PHY地址

在设备树源码文件中找到如下代码段,设置ENET1的LAN8720A地址(0x0),设置ENET2的LAN8720A地址(0x1),以及其他相关设置,修改后的代码如下示

########## imx6ull-andyxi-emmc.dts 代码段 ##########
&fec1
{
pinctrl-names =
"default";
pinctrl-0 = <&pinctrl_enet1
&pinctrl_enet1_reset>
;
phy-mode =
"rmii";
phy-handle = <ðphy0>
;
phy-
reset-gpios = <&gpio5 7 GPIO_ACTIVE_LOW>;#ENET1复位引脚,低电平有效
phy-
reset-duration = <200>; #复位低电平持续时间为200ms
status =
"okay";
};

&fec2
{
pinctrl-names =
"default";
pinctrl-0 = <&pinctrl_enet2
&pinctrl_enet2_reset>
;
phy-mode =
"rmii";
phy-handle = <ðphy1>
;
phy-
reset-gpios = <&gpio5 8 GPIO_ACTIVE_LOW>;#ENET2复位引脚,低电平有效
phy-
reset-duration = <200>;#复位低电平持续时间为200ms
status =
"okay";

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

ethphy0: ethernet-phy@0
{#ethernet-phy@后面的数字式PHY的地址
compatible =
"ethernet-phy-ieee802.3-c22";
#表明PHY芯片是SMSC公司的,内核会找到PHY芯片驱动来驱动LAN8720A
smsc
,disable-energy-detect;
reg = <0>
; #也表示PHY地址
};

ethphy1: ethernet-phy@1
{#ethernet-phy@后面的数字式PHY的地址
compatible =
"ethernet-phy-ieee802.3-c22";
#表明PHY芯片是SMSC公司的,内核会找到PHY芯片驱动来驱动LAN8720A
smsc
,disable-energy-detect;
reg = <1>
;
};
};
};

修改fec_main.c文件

要在 I.MX6ULL上使用LAN8720A,还需要修改内核源码,打开

drivers/net/ethernet/freescale/fec_main.c,找到函数 fec_probe,在 fec_probe 中加入如下代码

staticintfec_probe(structplatform_device*pdev)
{
structfec_enet_private*fep;
structfec_platform_data*pdata;
structnet_device*ndev;
int i, irq, ret =0;
structresource*r;
conststructof_device_id*of_id;
staticint dev_id;
structdevice_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);
......
return ret;
}

配置Linux内核,使能LAN8720驱动

输入“make menuconfig”,打开图形化配置解密,选择使能LAN8720A的驱动,路径如下:

-> 设备驱动程序 -> 网络设备支持 -> PHY 设备支持和基础结构 -> 用于 SMSC PHY 的驱动程序

源码

修改smsc.c文件

在 Linux中对 LAN8720A进行一次软复位,找到LAN8720A的驱动文件 “drivers/net/phy/smsc.c”,在函数“smsc_phy_reset”中添加LAN8720A复位代码,修改后的代码如下

staticintsmsc_phy_reset(structphy_device*phydev){
int err, phy_reset;
int msec =1;
structdevice_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;
  //从设备树中获取复位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){
    /* set "all capable" mode and reset the phy */
    rc |= MII_LAN83C185_MODE_ALL;
    phy_write(phydev, MII_LAN83C185_SPECIAL_MODES, rc);
  }
  //未修改之前在上面的函数里面,只有Powerdown模式时才会软复位LAN8720A
  //此处将其移出来,这样每次调用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);
return0;
}

因为smsc_phy_reset函数中用到了gpio_direction_output和gpio_set_value函数,所以需要在“smsc.c”中添加如下头文件

#include 
#include

网络驱动测试:修改好设备树和内核后重新编译,下载并启动开发板后,使用如下步骤进行测试

输入"ifconfig -a"来查看开发板中存在的网卡(eth0已打开)

源码

输入"ifconfig eth1 up"命令,可打开eth1(ENET1)

源码

使用"ifconfig eth1 192.168.10.51"命令,配置网卡IP地址,更换网口,成功挂载文件系统后,ping一下Ubuntu主机(192.168.10.100),ping成功说明网络驱动修改成功

源码

4.Linux内核移植总结

Linux内核移植的步骤总结如下:

  • 一般情况下,设计自已的硬件时都会参考半导体厂商官方的开发板
  • 在半导体厂商维护的Linux内核中查找可以参考的板子(半导体厂商官方开发板)
  • 编译出参考板子对应的zImage和.dtb文件,尝试在自已的板子上启动
  • 大部分情况下会启动起来,如果不能的话就需要调试Linux内核
  • 修改相应的驱动,NAND/EMMC/SD卡等,内核已经提供,重点是网络驱动,需要根据自已的外设PHY芯片设置复位引脚、地址信息等
  • Linux内核启动以后需要根文件系统,如果没有的话系统会崩溃

源码

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分