嵌入式Linux学习笔记-系统移植篇

本文最后更新于:2022年7月10日 晚上

系统移植篇

LInux系统移植概念

操作系统向下管理硬件(I/O,设备接口),向上提供接口(进程管理+文件IO+网络协议+数据库等,被APP软件调用

bootloader(U-Boot) -> Linux内核 -> 根文件系统(rootfs) 。这三者一起构成了一个完整的Linux系统,一个可以正常使用、功能完善的Linux系统。

开发板上电后首先运行 SOC 内部 iROM 中固化的代码(BL0),这段代码先对基本的软银见环境(时钟等)初始化,然后检测拨码开关获取启动方式,再将对应存储器(SD卡)中的Uboot搬移到内存,然后跳转到uboot运行
uboot开始运行后首先对开发板软硬件环境初始化,然后将 linux内核、设备树(dtb) 、根文件系统(rootfs)从外部存储器(或网络)搬移到内存,然后跳转到linux运行
linux开始运行,先对系统环境初始化,当系统启动完成后,linux再从内存中(或网络)挂在根文件系统

1652410941723

根据启动过程得出移植步骤:

  • uboot移植
  • linux内核移植(包含设备树)
  • 根文件系统移植

uboot简介

嵌入式系统上电后先执行bootloader、先初始化DDR,Flash 等外设,然后将 Linux 内核从 Flash(NAND,NOR FLASH,SD,MMC等)中读取到 DDR 中,最后启动Linux内核。

bootloader和Linux内核的关系就跟PC上的BIOS和Windows的关系一样,bootloader就相当于BIOS。

现成的bootloader软件有很多,比如U-Boot、vivi、RedBoot等等,其中以U-Boot使用最为广泛,为了方便书写,本书会将U-Boot写为uboot。

一般不会直接用uboot官方的U-Boot源码的。uboot官方的uboot源码是给半导体厂商准备的,半导体厂商会下载uboot官方的uboot源码,然后将自家相应的芯片移植进去。也就是说半导体厂商会自己维护一个版本的uboot,这个版本的uboot相当于是他们定制的。既然是定制的,那么肯定对自家的芯片支持会很全,虽然uboot官网的源码中一般也会支持他们的芯片,但是绝对是没有半导体厂商自己维护的uboot全面

NXP官方uboot下载地址:https://source.codeaurora.org/external/imx/

正点原子教程和NXP官方教程中的 linux与uboot下载链接都失效了,上面是最新的链接。这个网站是Linux基金会维护的一个开源网站。有点类似github。
在页面最底部找到 uboot-imx,点进去。
enter description here

点开Tags栏。记住下面的链接地址。
enter description here

找到你想下载的版本,比如我想下载rel_imx_4.1.15_2.1.0_ga版本。
enter description here

通过git clone下载(电脑需预先安装git),地址就是上面记住的链接地址,用-b指定你想下载的tag或分支。注意空格

1
git clone https://source.codeaurora.org/external/imx/linux-imx -b rel_imx_4.1.15_2.1.0_ga

uboot并不是越新越好,新版本只是添加了对新处理器得到支持,越新越冗余

后面我们学习uboot移植的时候就是使用 uboot-imx-rel_imx_4.1.15_2.1.0_ga.tar.bz2 版本

参考资料:

uboot 移植的一般流程:

  1. 在 uboot 中找到参考的开发平台,一般是原厂的开发板。

  2. 参考原厂开发板移植 uboot 到我们所使用的开发板

    alentek-uboot编译烧录测试

  3. 正点原子 uboot 复制到 ubuntu的linux -> uboot -> alentek_uboot下,解压

1
tar -vxjf uboot-imx-2016.03-2.1.0-gee88051-v1.6.tar.bz2
  1. 创建sh文件
    1
    vim mx6ull_alientek_emmc.sh
    填入内容:
    1
    2
    3
    4
    #!/bin/bash
    make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- distclean
    make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- mx6ull_14x14_ddr512_emmc_defconfig
    make V=1 ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -j16
    编译:
    1
    2
    chmod 777 mx6ull_alientek_emmc.sh //给予可执行权限
    ./mx6ull_alientek_emmc.sh
    烧录:
    1
    2
    chmod 777 imxdownload //给予 imxdownload 可执行权限
    ./imxdownload u-boot.bin /dev/sdd //烧写到 SD 卡中,不能烧写到/dev/sda 或 sda1 里面

NXP-uboot编译烧录测试

首先在 Ubuntu 中安装 ncurses 库, 否则编译会报错,安装命令如下:
sudo apt-get install libncurses5-dev

  1. 原版Uboot复制到ubuntu中,解压
    1
    tar -jvxf uboot-imx-rel_imx_4.1.15_2.1.0_ga.tar.bz2 
  2. 创建sh文件
    1
    vim mx6ull_14x14_emmc.sh
    填入内容:
    1
    2
    3
    4
    #!/bin/bash
    make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- distclean
    make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- mx6ull_14x14_evk_emmc_defconfig
    make V=1 ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -j16
  3. 编译:
    1
    2
    chmod 777 mx6ull_14x14_emmc.sh //给予可执行权限
    ./mx6ull_14x14_emmc.sh
  4. 烧录:
    1
    2
    chmod 777 imxdownload //给予 imxdownload 可执行权限
    ./imxdownload u-boot.bin /dev/sdd //烧写到 SD 卡中,不能烧写到/dev/sda 或 sda1 里面

运行输出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
U-Boot 2016.03 (Jun 12 2022 - 10:24:10 +0800)

CPU: Freescale i.MX6ULL rev1.1 69 MHz (running at 396 MHz)
CPU: Industrial temperature grade (-40C to 105C) at 46C
Reset cause: POR
Board: MX6ULL 14x14 EVK
I2C: ready
DRAM: 512 MiB
MMC: FSL_SDHC: 0, FSL_SDHC: 1
unsupported panel ATK-LCD-7-1024x600
In: serial
Out: serial
Err: serial
switch to partitions #0, OK
mmc0 is current device
Net: FEC1
Normal Boot
Hit any key to stop autoboot: 0

可以发现 CPU频率显示69MHz,如果使用 528MHz 的 I.MX6ULL,此处会显示主频为 528MHz。但是如果使用 800MHz 的 I.MX6ULL 的话此处会显示 69MHz,这个是 uboot 文件的bug,只作为输出,不影响实际运行,可以不用管。不管是528MHz还是800MHz的I.MX6ULL,此时都运行在 396MHz。

可以打开 arch\arm\cpu\armv7\mx6\soc.c 文件,修改这个小bug,修改后的如下:

  • 注释代码就是原来的代码,其下面的时修改后的,共两处
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
#define OCOTP_CFG3_SPEED_528MHZ 1
//#define OCOTP_CFG3_SPEED_696MHZ 2
#define OCOTP_CFG3_SPEED_792MHZ 2

u32 get_cpu_speed_grade_hz(void)
{
struct ocotp_regs *ocotp = (struct ocotp_regs *)OCOTP_BASE_ADDR;
struct fuse_bank *bank = &ocotp->bank[0];
struct fuse_bank0_regs *fuse =
(struct fuse_bank0_regs *)bank->fuse_regs;
uint32_t val;

val = readl(&fuse->cfg3);
val >>= OCOTP_CFG3_SPEED_SHIFT;
val &= 0x3;

if (is_cpu_type(MXC_CPU_MX6UL) || is_cpu_type(MXC_CPU_MX6ULL)) {
if (val == OCOTP_CFG3_SPEED_528MHZ)
return 528000000;
else if (val == OCOTP_CFG3_SPEED_792MHZ)
//return 69600000;
return 792000000;
else
return 0;
}

参考资料:

uboot修改移植

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

前置配置,不同的配置决定了编译时会调用不同的文件(包括后面要修改的),是最高一级的配置文件。

  1. 创建自定义配置文件
    1
    2
    cd configs
    cp mx6ull_14x14_evk_emmc_defconfig mx6ull_alientek_emmc_defconfig
    修改内容
    1
    2
    3
    4
    5
    CONFIG_SYS_EXTRA_OPTIONS="IMX_CONFIG=board/freescale/mx6ull_alientek_emmc/imximage.cfg,MX6ULL_EVK_EMMC_REWORK"
    CONFIG_ARM=y
    CONFIG_ARCH_MX6=y
    CONFIG_TARGET_MX6ULL_ALIENTEK_EMMC=y
    CONFIG_CMD_GPIO=y

2. 添加开发板对应的头文件

里面有很多宏定义,这些宏定义基本用于配置 uboot,也有一些
I.MX6ULL 的配置项目。

拷贝头文件

1
cp include/configs/mx6ullevk.h mx6ull_alientek_emmc.h

1
2
#ifndef __MX6ULLEVK_CONFIG_H
#define __MX6ULLEVK_CONFIG_H

改为:

1
2
#ifndef __MX6ULL_ALIENTEK_EMMC_CONFIG_H
#define __MX6ULL_ALIENTEK_EMMC_CONFIG_H

其他内容默认

3. 添加开发板对应的板级文件夹

uboot 中每个板子都有一个对应的文件夹来存放板级文件,比如开发板上外设驱动文件等
等。

复制 mx6ullevk文件夹,将其重命名为 mx6ull_alientek_emmc,命令如下:

1
2
cd board/freescale/
cp mx6ullevk/ -r mx6ull_alientek_emmc

进 入mx6ull_alientek_emmc目 录 中 , 将 其 中 的mx6ullevk.c文 件 重 命 名 为mx6ull_alientek_emmc.c,命令如下:

1
2
cd mx6ull_alientek_emmc
mv mx6ullevk.c mx6ull_alientek_emmc.c

1). 修改 mx6ull_alientek_emmc 目录下的 Makefile 文件

  • 重点是第 6 行的 obj-y,改为 mx6ull_alientek_emmc.o,这样才会编译 mx6ull_alientek_emmc.c
    这个文件。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    # (C) Copyright 2015 Freescale Semiconductor, Inc.
    #
    # SPDX-License-Identifier: GPL-2.0+
    #

    obj-y := mx6ull_alientek_emmc.o

    extra-$(CONFIG_USE_PLUGIN) := plugin.bin
    $(obj)/plugin.bin: $(obj)/plugin.o
    $(OBJCOPY) -O binary --gap-fill 0xff $< $@
    2). 修改 mx6ull_alientek_emmc 目录下的 imximage.cfg 文件
    将 imximage.cfg 中的下面一句:
    1
    PLUGIN board/freescale/mx6ullevk/plugin.bin 0x00907000
    改为:
    1
    PLUGIN board/freescale/mx6ull_alientek_emmc /plugin.bin 0x00907000
    3). 修改 mx6ull_alientek_emmc 目录下的 Kconfig 文件
    修改 Kconfig 文件,修改后的内容如下:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    if TARGET_MX6ULL_ALIENTEK_EMMC

    config SYS_BOARD
    default "mx6ull_alientek_emmc"

    config SYS_VENDOR
    default "freescale"

    config SYS_SOC
    default "mx6"

    config SYS_CONFIG_NAME
    default "mx6ull_alientek_emmc"

    endif
    4). 修改 mx6ull_alientek_emmc 目录下的 MAINTAINERS 文件
    1
    2
    3
    4
    5
    MX6ULL_ALIENTEK_EMMC BOARD
    M: Peng Fan <peng.fan@nxp.com>
    S: Maintained
    F: board/freescale/mx6ull_alientek_emmc/
    F: include/configs/mx6ull_alientek_emmc.h

4. 修改 U-Boot 图形界面配置文件

uboot 是支持图形界面配置,关于 uboot 的图形界面配置下一章会详细的讲解。修改文件
arch/arm/cpu/armv7/mx6/Kconfig(如果用的 I.MX6UL 的话,应该修改 arch/arm/Kconfig 这个文
件),在 207 行加入如下内容:

1
2
3
4
5
config TARGET_MX6ULL_ALIENTEK_EMMC
bool "Support mx6ull_alientek_emmc"
select MX6ULL
select DM
select DM_THERMAL

在最后一行的 endif 的前一行添加如下内容:

1
source "board/freescale/mx6ull_alientek_emmc/Kconfig"

5. 编译新 uboot

在 uboot 根目录下新建一个名为 mx6ull_alientek_emmc.sh 的 shell 脚本,在这个 shell 脚本
里面输入如下内容:

1
2
3
4
#!/bin/bash
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- distclean
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- mx6ull_alientek_emmc_defconfig
make V=1 ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -j16
1
2
3
4
chmod 777 mx6ull_alientek_emmc.sh
//给予可执行权限,一次即可
./mx6ull_alientek_emmc.sh
//运行脚本编译 uboot

等待编译完成 , 编译完成后输入如下命令 , 查看一下添加的mx6ull_alientek_emmc.h 这个头文件有没有被引用。
如果有很多文件都引用了 mx6ull_alientek_emmc.h 这个头文件,那就说明新板子添加成功,如图所示:
grep -nR "mx6ull_alientek_emmc.h"
enter description here

6. LCD驱动修改

一般 uboot 中修改驱动基本都是在 xxx.h 和 xxx.c 这两个文件中进行的,xxx 为板子名称,
比如 mx6ull_alientek_emmc.h 和 mx6ull_alientek_emmc.c 这两个文件。
一般修改 LCD 驱动重点注意以下几点:
①、LCD 所使用的 GPIO,查看 uboot 中 LCD 的 IO 配置是否正确。
②、LCD 背光引脚 GPIO 的配置。
③、LCD 配置参数是否正确。
正点原子的 I.MX6U-ALPHA 开发板 LCD 原理图和 NXP 官方 I.MX6ULL 开发板一致,所以 IO 部分就不用修改了。需要修改的之后 LCD 参数,
打开文件 mx6ull_alientek_emmc.c,找到如下所示内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
struct display_info_t const displays[] = {{
.bus = MX6UL_LCDIF1_BASE_ADDR,
.addr = 0,
.pixfmt = 24,
.detect = NULL,
.enable = do_enable_parallel_lcd,
.mode = {
.name = "TFT7016",
.xres = 1024,
.yres = 600,
.pixclock = 19531,
.left_margin = 140,
.right_margin = 160,
.upper_margin = 20,
.lower_margin = 12,
.hsync_len = 20,
.vsync_len = 3,
.sync = 0,
.vmode = FB_VMODE_NONINTERLACED
} } };
  • pixfmt 是像素格式,也就是一个像素点是多少位,如果是 RGB565 的话就是 16 位,如果是 888 的话就是 24 位,一般使用 RGB888。
  • name:LCD 名字,要和环境变量中的 panel 相等。
  • xres、yres:LCD X 轴和 Y 轴像素数量。
  • pixclock:像素时钟,每个像素时钟周期的长度,单位为皮秒。
    • 以正点原子的 7 寸 1024*600 分辨率的屏幕(ATK7016)为例,屏幕要求的像素时钟为 51.2MHz,因此:pixclock=(1/51200000)*10^12=19531
  • left_margin:HBP,水平同步后肩。
  • right_margin:HFP,水平同步前肩。
  • upper_margin:VBP,垂直同步后肩。
  • lower_margin:VFP,垂直同步前肩。
  • hsync_len:HSPW,行同步脉宽。
  • vsync_len:VSPW,垂直同步脉宽。
  • vmode:大多数使用 FB_VMODE_NONINTERLACED,也就是不使用隔行扫描。

以上参数都可以从lCD的规格书中获取。

打开 mx6ull_alientek_emmc.h,找到所有如下语句:
panel=TFT43AB
将其改为:
panel=TFT7016

也就是设置 panel 为 TFT7016,panel 的值要与示例代码中的.name 成员变量的值一致。修改完成以后重新编译一遍 uboot 并烧写到 SD 中启动。重启以后 LCD 驱动一般就会工作正常了,LCD 上回显示 NXP 的 logo。

但是有可能会遇到LCD 并没有工作,还是黑屏,这是什么原因呢?在 uboot 命令模式输入“print”来查看环境变
量 panel 的值,会发现 panel 的值要是 TFT43AB(或其他的,反正不是 TFT7016),这是因为之前有将环境变量保存到 EMMC 中,uboot 启动以后会先从 EMMC 中读取环境变量,如果 EMMC 中没有环境变量的话才会使用 mx6ull_alientek_emmc.h 中的默认环境变量。如果 EMMC 中的环境变量 panel 不等于 TFT7016,那么 LCD 显示肯定不正常.。
在uboot 中修改 panel 的值为 TFT7016 即可,在 uboot 的命令模式下输入如下命令:

1
2
setenv panel TFT7016
saveenv

7. 网络驱动修改

  1. 网络 PHY 地址修改
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    331 #define CONFIG_FEC_ENET_DEV  1
    332
    333 #if (CONFIG_FEC_ENET_DEV == 0)
    334 #define IMX_FEC_BASE
    ENET_BASE_ADDR
    335 #define CONFIG_FEC_MXC_PHYADDR 0x2
    336 #define CONFIG_FEC_XCV_TYPE RMII
    337 #elif (CONFIG_FEC_ENET_DEV == 1)
    338 #define IMX_FEC_BASE
    ENET2_BASE_ADDR
    339 #define CONFIG_FEC_MXC_PHYADDR 0x1
    340 #define CONFIG_FEC_XCV_TYPE RMII
    341 #endif
    342 #define CONFIG_ETHPRIME "FEC"
    343
    344 #define CONFIG_PHYLIB
    345 #define CONFIG_PHY_MICREL
    346 #endif
    第 331 行的宏 CONFIG_FEC_ENET_DEV 用于选择使用哪个网口,默认为 1,也就是选择ENET2。第 335 行为 ENET1 的 PHY 地址,默认是 0X2,第 339 行为 ENET2 的 PHY 地址,默认为 0x1。正点原子的 I.MX6U-ALPHA 开发板 ENET1 的 PHY 地址为0X0,ENET2 的 PHY 地址为 0X1,所以需要将第 335 行的宏 CONFIG_FEC_MXC_PHYADDR改为 0x0。

第 345 行定了一个宏 CONFIG_PHY_MICREL,此宏用于使能 uboot 中 Micrel 公司的 PHY驱动,KSZ8081 这颗 PHY 芯片就是 Micrel 公司生产的,不过 Micrel 已经被 Microchip 收购了。如果要使用 LAN8720A,那么就得将CONFIG_PHY_MICREL 改为 CONFIG_PHY_SMSC,也就是使能 uboot 中的 SMSC 公司中的 PHY 驱动,因为 LAN8720A 就是 SMSC 公司生产的。所
以示例代码 33.2.7.1 有三处要修改:
①、修改 ENET1 网络 PHY 的地址。
②、修改 ENET2 网络 PHY 的地址。
③、使能 SMSC 公司的 PHY 驱动。
修改后的网络 PHY 地址参数如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#define CONFIG_FEC_ENET_DEV		1

#if (CONFIG_FEC_ENET_DEV == 0)
#define IMX_FEC_BASE ENET_BASE_ADDR
#define CONFIG_FEC_MXC_PHYADDR 0x0
#define CONFIG_FEC_XCV_TYPE RMII
#elif (CONFIG_FEC_ENET_DEV == 1)
#define IMX_FEC_BASE ENET2_BASE_ADDR
#define CONFIG_FEC_MXC_PHYADDR 0x1
#define CONFIG_FEC_XCV_TYPE RMII
#endif
#define CONFIG_ETHPRIME "FEC"

#define CONFIG_PHYLIB
#define CONFIG_PHY_SMSC
#endif
  1. 删除 uboot 中 74LV595 的驱动代码
    uboot 中网络 PHY 芯片地址修改完成以后就是网络复位引脚的驱动修改了,打开mx6ull_alientek_emmc.c,找到如下代码:
    1
    2
    3
    4
    #define IOX_SDI IMX_GPIO_NR(5, 10)
    #define IOX_STCP IMX_GPIO_NR(5, 7)
    #define IOX_SHCP IMX_GPIO_NR(5, 11)
    #define IOX_OE IMX_GPIO_NR(5, 8)
    以 IOX 开头的宏定义是 74LV595 的相关 GPIO,因为 NXP 官方I.MX6ULL EVK 开发板使用 74LV595 来扩展 IO,两个网络的复位引脚就是由 74LV595 来控制的。正点原子的 I.MX6U-ALPHA 开发板并没有使用 74LV595,因此我们将代码删除掉,替换为如下所示代码:
    1
    2
    #define ENET1_RESET IMX_GPIO_NR(5, 7)
    #define ENET2_RESET IMX_GPIO_NR(5, 8)
    ENET1 的复位引脚连接到 SNVS_TAMPER7 上,对应 GPIO5_IO07,ENET2 的复位引脚连接到 SNVS_TAMPER8 上,对应 GPIO5_IO08。继续在 mx6ull_alientek_emmc.c 中找到如下代码:
  • 代码是 74LV595 的 IO 配置参数结构体,将其删除掉。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    static iomux_v3_cfg_t const iox_pads[] = {
    /* IOX_SDI */
    MX6_PAD_BOOT_MODE0__GPIO5_IO10 | MUX_PAD_CTRL(NO_PAD_CTRL),
    /* IOX_SHCP */
    MX6_PAD_BOOT_MODE1__GPIO5_IO11 | MUX_PAD_CTRL(NO_PAD_CTRL),
    /* IOX_STCP */
    MX6_PAD_SNVS_TAMPER7__GPIO5_IO07 | MUX_PAD_CTRL(NO_PAD_CTRL),
    /* IOX_nOE */
    MX6_PAD_SNVS_TAMPER8__GPIO5_IO08 | MUX_PAD_CTRL(NO_PAD_CTRL),
    };

示例继续在mx6ull_alientek_emmc.c 中找到函数 iox74lv_init 函数是 74LV595 的初始化函数,iox74lv_set 函数用于控制 74LV595 的 IO 输出电平,将这两个函数全部删除掉!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
static void iox74lv_init(void)
{
int i;
gpio_direction_output(IOX_OE, 0);
......
gpio_direction_output(IOX_STCP, 1);
};

void iox74lv_set(int index)
{
int i;
......
gpio_direction_output(IOX_STCP, 1);
};

在 mx6ull_alientek_emmc.c 中找到 board_init 函数,此函数是板子初始化函数,会被 board_init_r 调用,board_init 函数内容如下:

  • board_init 会调用 imx_iomux_v3_setup_multiple_pads 和 iox74lv_init 这两个函数来初始化 74lv595 的 GPIO,将这两行删除掉。至此,mx6ull_alientek_emmc.c 中关于 74LV595 芯片的驱动代码都删除掉了,接下来就是添加 I.MX6U-ALPHA 开发板两个网络复位引脚了。
    1
    2
    3
    4
    5
    6
    7
    8
    int board_init(void)
    {
    ......
    imx_iomux_v3_setup_multiple_pads(iox_pads, ARRAY_SIZE(iox_pads));
    iox74lv_init();
    ......
    return 0;
    }
  1. 添加 I.MX6U-ALPHA 开发板网络复位引脚驱动
    在 mx6ull_alientek_emmc.c 中找到如下所示代码并修改如下:
  • 添加了 651和667 两行代码,分别是 ENET1 和 ENET2 的复位 IO 配置参数。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    640 static iomux_v3_cfg_t const fec1_pads[] = {
    641 MX6_PAD_GPIO1_IO06__ENET1_MDIO | MUX_PAD_CTRL(MDIO_PAD_CTRL),
    642 MX6_PAD_GPIO1_IO07__ENET1_MDC | MUX_PAD_CTRL(ENET_PAD_CTRL),
    ......
    649 MX6_PAD_ENET1_RX_ER__ENET1_RX_ER | MUX_PAD_CTRL(ENET_PAD_CTRL),
    650 MX6_PAD_ENET1_RX_EN__ENET1_RX_EN | MUX_PAD_CTRL(ENET_PAD_CTRL),
    651 MX6_PAD_SNVS_TAMPER7__GPIO5_IO07 | MUX_PAD_CTRL(NO_PAD_CTRL),
    652 };
    653
    654 static iomux_v3_cfg_t const fec2_pads[] = {
    655 MX6_PAD_GPIO1_IO06__ENET2_MDIO | MUX_PAD_CTRL(MDIO_PAD_CTRL),
    656 MX6_PAD_GPIO1_IO07__ENET2_MDC | MUX_PAD_CTRL(ENET_PAD_CTRL),
    ......
    665 MX6_PAD_ENET2_RX_EN__ENET2_RX_EN | MUX_PAD_CTRL(ENET_PAD_CTRL),
    666 MX6_PAD_ENET2_RX_ER__ENET2_RX_ER | MUX_PAD_CTRL(ENET_PAD_CTRL),
    667 MX6_PAD_SNVS_TAMPER8__GPIO5_IO08 | MUX_PAD_CTRL(NO_PAD_CTRL),
    668 };
    继续在文件 mx6ull_alientek_emmc.c 中找到函数 setup_iomux_fec,函数 setup_iomux_fec 就是根据 fec1_pads 和 fec2_pads 这两个网络 IO 配置数组来初始化I.MX6ULL 的网络 IO。我们需要在其中添加网络复位 IO 的初始化代码,并且复位一下 PHY 芯片,修改后的 setup_iomux_fec 函数如下
  • 第 676 行679 行和第 685 行688 行分别对应 ENET1 和 ENET2 的复位 IO 初始化,将这两个 IO 设置为输出并且硬件复位一下 LAN8720A,这个硬件复位很重要!否则可能导致 uboot 无法识别 LAN8720A。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    668 static void setup_iomux_fec(int fec_id)
    669 {
    670 if (fec_id == 0)
    671 {
    672
    673 imx_iomux_v3_setup_multiple_pads(fec1_pads,
    674 ARRAY_SIZE(fec1_pads));
    675
    676 gpio_direction_output(ENET1_RESET, 1);
    677 gpio_set_value(ENET1_RESET, 0);
    678 mdelay(20);
    679 gpio_set_value(ENET1_RESET, 1);
    680 }
    681 else
    682 {
    683 imx_iomux_v3_setup_multiple_pads(fec2_pads,
    684 ARRAY_SIZE(fec2_pads));
    685 gpio_direction_output(ENET2_RESET, 1);
    686 gpio_set_value(ENET2_RESET, 0);
    687 mdelay(20);
    688 gpio_set_value(ENET2_RESET, 1);
    689 }
    690 }
  1. 修改 drivers/net/phy/phy.c 文件中的函数 genphy_update_link
    uboot 中的 LAN8720A 驱动有点问题,打开文件drivers/net/phy/phy.c,找到函数 genphy_update_link,这是个通用 PHY 驱动函数,此函数用于更新 PHY 的连接状态和速度。使用 LAN8720A 的时候需要在此函数中添加一些代码,修改后的
    函数 genphy_update_link 如下所示:
    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
    221 int genphy_update_link(struct phy_device *phydev)
    222 {
    223 unsigned int mii_reg;
    224
    225 #ifdef CONFIG_PHY_SMSC
    226 static int lan8720_flag = 0;
    227 int bmcr_reg = 0;
    228 if (lan8720_flag == 0) {
    229 bmcr_reg = phy_read(phydev, MDIO_DEVAD_NONE, MII_BMCR);
    230 phy_write(phydev, MDIO_DEVAD_NONE, MII_BMCR, BMCR_RESET);
    231 while(phy_read(phydev, MDIO_DEVAD_NONE, MII_BMCR) & 0X8000) {
    232 udelay(100);
    233 }
    234 phy_write(phydev, MDIO_DEVAD_NONE, MII_BMCR, bmcr_reg);
    235 lan8720_flag = 1;
    236 }
    237 #endif
    238
    239 /*
    240 * Wait if the link is up, and autonegotiation is in progress
    241 * (ie - we're capable and it's not done)
    242 */
    243 mii_reg = phy_read(phydev, MDIO_DEVAD_NONE, MII_BMSR);
    ......
    291
    292 return 0;
    293 }

225 行237 行就是新添加的代码,为条件编译代码段,只有使用 SMSC 公司的 PHY 这段代码才会执行(目前只测试了 LAN8720A,SMSC 公司其他的芯片还未测试)。第 229 行读取LAN8720A 的 BMCR 寄存器(寄存器地址为 0),此寄存器为 LAN8720A 的配置寄存器,这里先读取此寄存器的默认值并保存起来。230 行向寄存器 BMCR 寄存器写入 BMCR_RESET(值为0X8000),因为 BMCR 的 bit15 是软件复位控制位,因此 230 行就是软件复位 LAN8720A,复位
完成以后此位会自动清零。第 231
233 行等待 LAN8720A 软件复位完成,也就是判断 BMCR的 bit15 位是否为 1,为 1 的话表示还没有复位完成。第 234 行重新向 BMCR 寄存器写入以前的值,也就是 229 行读出的那个值。

至此网络的复位引脚驱动修改完成,重新编译 uboot,然后将 u-boot.bin 烧写到 SD 卡中并启动,uboot 启动信息所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
U-Boot 2016.03 (Jun 12 2022 - 10:24:10 +0800)

CPU: Freescale i.MX6ULL rev1.1 69 MHz (running at 396 MHz)
CPU: Industrial temperature grade (-40C to 105C) at 48C
Reset cause: POR
Board: MX6ULL 14x14 EVK
I2C: ready
DRAM: 512 MiB
MMC: FSL_SDHC: 0, FSL_SDHC: 1
unsupported panel ATK-LCD-7-1024x600
In: serial
Out: serial
Err: serial
switch to partitions #0, OK
mmc0 is current device
Net: FEC1
Normal Boot
Hit any key to stop autoboot: 0

从图 33.2.6.4 中可以看到“Net: FEC1”这一行,提示当前使用的 FEC1 这个网口,也就是 ENET2。在 uboot 中使用网络之前要先设置几个环境变量,命令如下:

1
2
3
4
5
6
setenv ipaddr 192.168.1.55          //开发板 IP 地址
setenv ethaddr b8:ae:1d:01:00:00 //开发板网卡 MAC 地址
setenv gatewayip 192.168.1.1 //开发板默认网关
setenv netmask 255.255.255.0 //开发板子网掩码
setenv serverip 192.168.1.250 //服务器地址,也就是 Ubuntu 地址
saveenv //保存环境变量

设置好环境变量以后就可以在 uboot 中使用网络了,用网线将 I.MX6U-ALPHA 上的 ENET2与电脑或者路由器连接起来,保证开发板和电脑在同一个网段内,通过 ping 命令来测试一下网络连(与ubuntu主机)),命令如下:
ping 192.168.1.250

如果主机一直ping不通,请尝试关闭ubuntu,退出虚拟机(包括后台),然后重新打开虚拟机和ubuntu,再尝试使用开发板ping主机网络。

ps:一次使用时,重启了路由器,PC电脑无线网络重连(我的开发板和PC都连接到路由器)。然后开发板ping网络不同,重启ubuntu也不行,在ubuntu打开浏览器也没有网络,于是退出虚拟机,重开。后一切正常。

如果ping或dhcp后系统不断重启(如下所示),那是因为交叉编译链版本太高,不兼容,请参考 开发环境搭建 章节,重新安装旧版的编译环境。

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

=> ping 192.168.0.198
FEC1 Waiting for PHY auto negotiation to complete... done
Using FEC1 device
data abort
pc : [<9ff83ad0>] lr : [<9ff84d98>]
reloc pc : [<8783bad0>] lr : [<8783cd98>]
sp : 9ef45d08 ip : 00000000 fp : 9ff53520
r10: 00000002 r9 : 9ef45eb8 r8 : 00000000
r7 : 00000001 r6 : 00000000 r5 : 0000002a r4 : 9ffed0ce
r3 : 14000045 r2 : bc00a8c0 r1 : 9ef45d10 r0 : 9ffed0ce
Flags: nZCv IRQs off FIQs off Mode SVC_32
Resetting CPU ...

resetting ...


=> dhcp
FEC1 Waiting for PHY auto negotiation to complete... done
BOOTP broadcast 1
data abort
pc : [<9ff821e0>] lr : [<9ff839e4>]
reloc pc : [<8783a1e0>] lr : [<8783b9e4>]
sp : 9ef45cc0 ip : 00000000 fp : 9ff534c8
r10: 00000001 r9 : 9ef45eb8 r8 : 9ffecbe8
r7 : 0000000e r6 : 9ffeef30 r5 : 00000000 r4 : 9ffed0ce
r3 : 00060101 r2 : 00000008 r1 : 9ffed0a2 r0 : 0000000e
Flags: nZCv IRQs off FIQs off Mode SVC_32
Resetting CPU ...

resetting ...

8. 开发板名称修改

在 uboot 启动信息中会有“Board: MX6ULL 14x14 EVK”这一句,也就是说板子名字为“MX6ULL 14x14 EVK”,要将其改为我们所使用的板子名字,比如“MX6ULL ALIENTEK EMMC”或者“MX6ULL ALIENTEK NAND”。打开文件 mx6ull_alientek_emmc.c,找到函数
checkboard,将其改为如下所示内容:

1
2
3
4
5
6
7
8
int checkboard(void)
{
if (is_mx6ull_9x9_evk())
puts("Board: MX6ULL 9x9 EVK\n");
else
puts("Board: MX6ULL ALIENTEK EMMC\n");
return 0;
}

至此 uboot 的驱动部分就修改完成了,uboot 移植也完成了

9. uboot 启动linux测试

uboot 的最终目的就是启动 Linux 内核,所以需要通过启动 Linux 内核来判断 uboot 移植是否成功。

uboot在linux启动后,就失效了,但会传递一些信息给linux
所以uboot中的设备驱动是不能在linux中使用的,所以linux中也要有上面移植时的那些设备驱动,这在后面linux驱动开发再讲解。

从 EMMC 启动 Linux 系统

从 EMMC 启动也就是将编译出来的 Linux 镜像文件 zImage 和设备树文件保存在 EMMC中,uboot 从 EMMC 中读取这两个文件并启动,这个是我们产品最终的启动方式。但是我们目前还没有讲解如何移植 linux 和设备树文件,以及如何将 zImage 和设备树文件保存到 EMMC中。不过大家拿到手的 I.MX6U-ALPHA 开发板(EMMC 版本)已经将 zImage 文件和设备树文件烧写到了 EMMC 中,所以我们可以直接读取来测试。先检查一下 EMMC 的分区 1 中有没有zImage 文件和设备树文件,输入命令

ls mmc 1:1
结果下所示:

1
2
3
4
5
6
7
8
9
10
11
=> ls mmc 1:1
38823 imx6ull-14x14-emmc-10.1-1280x800-c.dtb
38823 imx6ull-14x14-emmc-4.3-480x272-c.dtb
38823 imx6ull-14x14-emmc-4.3-800x480-c.dtb
38823 imx6ull-14x14-emmc-7-1024x600-c.dtb
38823 imx6ull-14x14-emmc-7-800x480-c.dtb
39655 imx6ull-14x14-emmc-hdmi.dtb
39563 imx6ull-14x14-emmc-vga.dtb
6786272 zimage

8 file(s), 0 dir(s)

uboot启动,进入命令模式,设置如下

1
2
3
setenv bootargs 'console=ttymxc0,115200 root=/dev/mmcblk1p2 rootwait rw'
setenv bootcmd 'mmc dev 1; fatload mmc 1:1 80800000 zImage; fatload mmc 1:1 83000000 imx6ull-14x14-emmc-7-1024x600-c.dtb; bootz 80800000 - 83000000;'
saveenv

设置好以后直接输入 boot,或者 run bootcmd 即可启动 Linux 内核,如果 Linux 内核启动成功的话就会输出如下的启动信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
=> boot
switch to partitions #0, OK
mmc1(part 0) is current device
reading zImage
6786272 bytes read in 226 ms (28.6 MiB/s)
reading imx6ull-14x14-emmc-7-1024x600-c.dtb
38823 bytes read in 20 ms (1.9 MiB/s)
Kernel image @ 0x80800000 [ 0x000000 - 0x678ce0 ]
## Flattened Device Tree blob at 83000000
Booting using the fdt blob at 0x83000000
Using Device Tree in place at 83000000, end 8300c7a6

Starting kernel ...

[ 0.000000] Booting Linux on physical CPU 0x0
......
从网络启动 Linux 系统

前提必须先设置好开发板的网络参数和服务器地址。详见上文 网络驱动修改

从网络启动 linux 系统的唯一目的就是为了调试!不管是为了调试 linux 系统还是 linux 下的驱动。每次修改 linux 系统文件或者 linux 下的某个驱动以后都要将其烧写到 EMMC 中去测试,这样太麻烦了。我们可以设置 linux 从网络启动,也就是将 linux 镜像文件和根文件系统都放到 Ubuntu 下某个指定的文件夹中,这样每次重新编译 linux 内核或者某个 linux 驱动以后只需要使用 cp 命令将其拷贝到这个指定的文件夹中即可,这样就不用需要频繁的烧写 EMMC。我们可以通过 nfs 或者 tftp 从 Ubuntu 中下载 zImage 和设备树文件,根文件系统的话也可以通过 nfs 挂载。

这里我们使用 tftp 从 Ubuntu 中下载 zImage 和设备树文件,前提是要将 zImage 和设备树文件放到 Ubuntu 下的 tftp 目录中

需要在 Ubuntu 上搭建 TFTP 服务器,需要安装 tftp-hpa 和 tftpd-hpa,命令如下:

1
2
sudo apt-get install tftp-hpa tftpd-hpa
sudo apt-get install xinetd

在用户目录下新建一个目录存放文件,命令如下:

1
2
mkdir /home/lonly/linux/tftpboot
chmod 777 /home/lonly/linux/tftpboot

新建文件sudo vi /etc/xinetd.d/tftp,然后在里面输入如下内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
server tftp
{
socket_type = dgram
protocol = udp
wait = yes
user = root
server = /usr/sbin/in.tftpd
server_args = -s /home/lonly/linux/tftpboot/
disable = no
per_source = 11
cps = 100 2
flags = IPv4
}

完了以后启动 tftp 服务,命令如下:
sudo service tftpd-hpa start
打开 sudo vi /etc/default/tftpd-hpa 文件,将其修改为如下所示内容:

1
2
3
4
5
6
# /etc/default/tftpd-hpa

TFTP_USERNAME="tftp"
TFTP_DIRECTORY="/home/lonly/linux/tftpboot"
TFTP_ADDRESS=":69"
TFTP_OPTIONS="-l -c -s"

重启 tftp 服务器:
sudo service tftpd-hpa restart

将 zImage 镜像文件 和 设备树 拷贝到 tftpboot 文件夹中,并且给予 zImage 相应的权限,命令如下:

1
2
3
4
5
cp zImage /home/lonly/linux/tftpboot/
cp imx6ull-14x14-emmc-7-1024x600-c.dtb /home/lonly/linux/tftpboot/
cd /home/lonly/linux/tftpboot/
chmod 777 zImage
chmod 777 imx6ull-14x14-emmc-7-1024x600-c.dtb

uboot启动,设置如下

1
2
3
setenv bootargs 'console=ttymxc0,115200 root=/dev/mmcblk1p2 rootwait rw'
setenv bootcmd 'tftp 80800000 zImage; tftp 83000000 imx6ull-14x14-emmc-7-1024x600-c.dtb; bootz 80800000 - 83000000'
saveenv

重启boot

显示如下:

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
=> boot
Using FEC1 device
TFTP from server 192.168.0.254; our IP address is 192.168.0.111
Filename 'zImage'.
Load address: 0x80800000
Loading: #################################################################
#################################################################
#################################################################
#############################################################T ####
#################################################################
###################################################T ##############
#################################################################
########
204.1 KiB/s
done
Bytes transferred = 6785480 (6789c8 hex)
Using FEC1 device
TFTP from server 192.168.0.254; our IP address is 192.168.0.111
Filename 'imx6ull-14x14-emmc-7-1024x600-c.dtb'.
Load address: 0x83000000
Loading: ###
233.4 KiB/s
done
Bytes transferred = 39327 (999f hex)
Kernel image @ 0x80800000 [ 0x000000 - 0x6789c8 ]
## Flattened Device Tree blob at 83000000
Booting using the fdt blob at 0x83000000
Using Device Tree in place at 83000000, end 8300c99e

Starting kernel ...

bootcmd 和 bootargs 环境变量

bootcmd 和 bootagrs 是采用类似 shell 脚本语言编写的,里面有很多的变量引用,这些变量其实都是环境变量 , 有很多是NXP自 己定义的 。 文件mx6ull_alientek_emmc.h 中的宏 CONFIG_EXTRA_ENV_SETTINGS 保存着这些环境变量的默认值。

  • bootcmd:保存着 uboot 默认命令
  • bootargs:保存着 uboot 传递给 Linux 内核的参数

bootcmd

bootcmd 保存着 uboot 默认命令,uboot 倒计时结束以后就会执行 bootcmd 中的命令。
可以在 uboot 启动以后进入命令行设置 bootcmd 环境变量的值。板子第一次运行 uboot 的时候都会使用默认值来设置 bootcmd 环境变量

文件 include/env_default.h。指定了很多环境变量的默认值,比如 bootcmd 的默认值就是CONFIG_BOOTCOMMAND,bootargs 的默认值就是 CONFIG_BOOTARGS。我们可以在 mx6ull_alientek_emmc.h 文件中通过设置宏 CONFIG_BOOTCOMMAND 来设置 bootcmd 的默认值,

打开文 件 mx6ull_alientek_emmc.h,找到宏 CONFIG_BOOTCOMMAND ,

经过分析,浓缩出来的仅仅是 4 行精华:

1
2
3
4
mmc dev 1	//切换到 EMMC
fatload mmc 1:1 0x80800000 zImage //读取 zImage 到 0x80800000 处
fatload mmc 1:1 0x83000000 imx6ull-14x14-evk.dtb //读取设备树到 0x83000000 处
bootz 0x80800000 - 0x83000000 //启动 Linux

NXP 官方将 CONFIG_BOOTCOMMAND 写的这么复杂只有一个目的:为了兼容多个板子,所以写了个很复杂的脚本。当我们明确知道我们所使用的板子的时候就可以大幅简化宏CONFIG_BOOTCOMMAND的 设 置 , 比如我们要从EMMC启 动 ,那么宏
CONFIG_BOOTCOMMAND 就可简化为:

1
2
3
4
5
#define CONFIG_BOOTCOMMAND \
"mmc dev 1;" \
"fatload mmc 1:1 0x80800000 zImage;" \
"fatload mmc 1:1 0x83000000 imx6ull-alientek-emmc.dtb;" \
"bootz 0x80800000 - 0x83000000;"

或者可以直接在 uboot 启动后,使用命令设置 bootcmd 的值,这个值就是保存到 EMMC 中的,命令如下:

1
setenv bootcmd 'mmc dev 1; fatload mmc 1:1 80800000 zImage; fatload mmc 1:1 83000000 imx6ull-14x14-emmc-7-1024x600-c.dtb; bootz 80800000 - 83000000;'

bootargs

bootargs 保存着 uboot 传递给 Linux 内核的参数,bootargs 环境变量是由 mmcargs 设置的,mmcargs 环境变量如下:
mmcargs=setenv bootargs console=${console},${baudrate} root=${mmcroot}
其中 console=ttymxc0,baudrate=115200,mmcroot=/dev/mmcblk1p2 rootwait rw,因此将mmcargs 展开以后就是:
mmcargs=setenv bootargs console= ttymxc0, 115200 root= /dev/mmcblk1p2 rootwait rw

常用的参数有:

  1. console
    console 用来设置 linux 终端(或者叫控制台),也就是通过什么设备来和 Linux 进行交互,是串口还是 LCD 屏幕?一般设置串口作为 Linux 终端。这里设置 console 为 ttymxc0,因为 linux启动以后 I.MX6ULL 的串口 1 在 linux 下的设备文件就是/dev/ttymxc0,ttymxc0 后面有个“,115200”,这是设置串口的波特率

  2. root
    root 用来设置根文件系统的位置,root=/dev/mmcblk1p2 用于指明根文件系统存放在mmcblk1 设备的分区 2 中。EMMC 版本的核心板启动 linux 以后会存在/dev/mmcblk0、/dev/mmcblk1、/dev/mmcblk0p1、/dev/mmcblk0p2、/dev/mmcblk1p1 和/dev/mmcblk1p2 这样的文件,其中/dev/mmcblkx(x=0n)表示 mmc 设备,而/dev/mmcblkxpy(x=0n,y=1~n)表示 mmc 设备x 的分区 y。在 I.MX6U-ALPHA 开发板中/dev/mmcblk1 表示 EMMC,而/dev/mmcblk1p2 表示EMMC 的分区 2。
    root 后面有“rootwait rw”,rootwait 表示等待 mmc 设备初始化完成以后再挂载,否则的话mmc 设备还没初始化完成就挂载根文件系统会出错的。rw 表示根文件系统是可以读写的,不加rw 的话可能无法在根文件系统中进行写操作,只能进行读操作。

  3. rootfstype

此选项一般配置 root 一起使用,rootfstype 用于指定根文件系统类型,如果根文件系统为ext 格式的话此选项无所谓。如果根文件系统是 yaffs、jffs 或 ubifs 的话就需要设置此选项,指定根文件系统的类型。

linux内核移植

Linux 内核获取

NXP 会从 https://www.kernel.org 下载某个版本的 Linux 内核,然后将其移植到自己的 CPU上,测试成功以后就会将其开放给 NXP 的 CPU 开发者。开发者下载 NXP 提供的 Linux 内核,然后将其移植到自己的产品上。

课参考上文uboot源码下载方式,从网站中下载NXP的linux内核源码
enter description here
rel_imx_4.1.15_2.1.0_ga版本。与正点原子教程一样
enter description here

Linux 内核初次编译

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

1
sudo apt-get install lzop

在 Ubuntu中新建名为 “ alientek_linux ” 的文件夹 , 然后将正点原子移植好的 Linux 源码 linux-imx-4.1.15-2.1.0-g8a006db.tar.bz2 这个压缩包拷贝到前面新建的 alientek_linux 文件夹中并解压,命令如下:

1
tar -vxjf linux-imx-4.1.15-2.1.0-g8a006db.tar.bz2

编译出对应的 Linux 镜像文件。新建名为
“mx6ull_alientek_emmc.sh”的 shell 脚本,然后在这个 shell 脚本里面输入如下所示内容:

1
2
3
4
5
#!/bin/sh
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- distclean
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- imx_v7_defconfig
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- menuconfig
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- all -j16

第 2 行,执行“make distclean”,清理工程,所以 mx6ull_alientek_emmc.sh 每次都会清理一下工程。如果通过图形界面配置了 Linux,但是还没保存新的配置文件,那么就要慎重使用mx6ull_alientek_emmc.sh 编译脚本了,因为它会把你的配置信息都删除掉!
第 3 行,执行“make xxx_defconfig”,配置工程。
第 4 行,执行“make menuconfig”,打开图形配置界面,对 Linux 进行配置,如果不想每次编译都打开图形配置界面的话可以将这一行删除掉。
第 5 行,执行“make”,编译 Linux 源码。

可以看出,Linux 的编译过程基本和 uboot 一样,都要先执行“make xxx_defconfig”来配置一下,然后在执行“make”进行编译。如果需要使用图形界面配置的话就执行“make menuconfig”。

1
2
3
4
sudo chmod 777 mx6ull_alientek_emmc.sh
//给予可执行权限,一次即可
./mx6ull_alientek_emmc.sh
//运行脚本编译 uboot

Linux 的图行界面配置和 uboot 是一样的,这里我们不需要做任何的配置,直接按两下 ESC键退出,退出图形界面以后会自动开始编译 Linux。等待编译完成,大概十几分钟。

enter description here
编译完成以后就会在 arch/arm/boot 这个目录下生成一个叫做 zImage 的文件,zImage 就是我们要用的 Linux 镜像文件。另外也会在 arch/arm/boot/dts 下生成很多.dtb 文件,这些.dtb 就是设备树文件。

Linux 工程目录分析

我们在分析 Linux 之前一定要先在 Ubuntu 中编译一下 Linux,因为编译过程会生成一些文件,而生成的这些恰恰是分析
Linux 不可或缺的文件。编译完成以后使用 tar 压缩命令对其进行压缩并使用 Filezilla 软件将压缩后的 uboot 源码拷贝到 Windows 下。
enter description here

enter description here

1、arch 目录

这个目录是和架构有关的目录,比如 arm、arm64、avr32、x86 等等架构。每种架构都对应一个目录,在这些目录中又有很多子目录,比如 boot、common、configs 等等,以 arch/arm 为例,其子目录如图 35.3.3 所示:
enter description here

图 35.3.3 是 arch/arm 的一部分子目录,这些子目录用于控制系统引导、系统调用、动态调频、主频设置等。arch/arm/configs 目录是不同平台的默认配置文件:xxx_defconfig,如图 35.3.4所示:
enter description here
在 arch/arm/configs 中就包含有 I.MX6U-ALPHA 开发板的默认配置文件:imx_v7_defconfig,执行“make imx_v7_defconfig”即可完成配置。
arch/arm/boot/dts 目录里面是对应开发平台的设备树文件,正点原子 I.MX6U-ALPHA 开发板对应的设备树文件如图 35.3.5 所示:
enter description here
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 等文件系
统。

内核移植

创建VSCode工程

这里我们使用 NXP 官方提供的 Linux 源码,将其移植到正点原子 I.MX6U-ALPHA 开发板上。使用 FileZilla 将其发送到 Ubuntu
中并解压,得到名为 linux-imx-rel_imx_4.1.15_2.1.0_ga 的目录,

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

为了和 NXP 官方的名字区分,将其重命名为 “ linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek”,命令如下:

1
mv linux-imx-rel_imx_4.1.15_2.1.0_ga linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek

完成以后创建 VSCode 工程,
新建 .vscode/settings.json 文件屏蔽不用文件夹显示,内容如下

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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
{
"search.exclude": {
"**/node_modules": true,
"**/bower_components": true,
"**/*.o":true,
"**/*.su":true,
"**/*.cmd":true,
"Documentation":true,

/* 屏蔽不用的架构相关的文件 */
"arch/alpha":true,
"arch/arc":true,
"arch/arm64":true,
"arch/avr32":true,
"arch/[b-z]*":true,
"arch/arm/plat*":true,
"arch/arm/mach-[a-h]*":true,
"arch/arm/mach-[n-z]*":true,
"arch/arm/mach-i[n-z]*":true,
"arch/arm/mach-m[e-v]*":true,
"arch/arm/mach-k*":true,
"arch/arm/mach-l*":true,

/* 屏蔽排除不用的配置文件 */
"arch/arm/configs/[a-h]*":true,
"arch/arm/configs/[j-z]*":true,
"arch/arm/configs/imo*":true,
"arch/arm/configs/in*":true,
"arch/arm/configs/io*":true,
"arch/arm/configs/ix*":true,

/* 屏蔽掉不用的 DTB 文件 */
"arch/arm/boot/dts/[a-h]*":true,
"arch/arm/boot/dts/[k-z]*":true,
"arch/arm/boot/dts/in*":true,
"arch/arm/boot/dts/imx1*":true,
"arch/arm/boot/dts/imx7*":true,
"arch/arm/boot/dts/imx2*":true,
"arch/arm/boot/dts/imx3*":true,
"arch/arm/boot/dts/imx5*":true,
"arch/arm/boot/dts/imx6d*":true,
"arch/arm/boot/dts/imx6q*":true,
"arch/arm/boot/dts/imx6s*":true,
"arch/arm/boot/dts/imx6ul-*":true,
"arch/arm/boot/dts/imx6ull-9x9*":true,
"arch/arm/boot/dts/imx6ull-14x14-ddr*":true,
},
"files.exclude": {
"**/.git": true,
"**/.svn": true,
"**/.hg": true,
"**/CVS": true,
"**/.DS_Store": true,
"**/*.o":true,
"**/*.su":true,
"**/*.cmd":true,
"Documentation":true,

/* 屏蔽不用的架构相关的文件 */
"arch/alpha":true,
"arch/arc":true,
"arch/arm64":true,
"arch/avr32":true,
"arch/[b-z]*":true,
"arch/arm/plat*":true,
"arch/arm/mach-[a-h]*":true,
"arch/arm/mach-[n-z]*":true,
"arch/arm/mach-i[n-z]*":true,
"arch/arm/mach-m[e-v]*":true,
"arch/arm/mach-k*":true,
"arch/arm/mach-l*":true,

/* 屏蔽排除不用的配置文件 */
"arch/arm/configs/[a-h]*":true,
"arch/arm/configs/[j-z]*":true,
"arch/arm/configs/imo*":true,
"arch/arm/configs/in*":true,
"arch/arm/configs/io*":true,
"arch/arm/configs/ix*":true,

/* 屏蔽掉不用的 DTB 文件 */
"arch/arm/boot/dts/[a-h]*":true,
"arch/arm/boot/dts/[k-z]*":true,
"arch/arm/boot/dts/in*":true,
"arch/arm/boot/dts/imx1*":true,
"arch/arm/boot/dts/imx7*":true,
"arch/arm/boot/dts/imx2*":true,
"arch/arm/boot/dts/imx3*":true,
"arch/arm/boot/dts/imx5*":true,
"arch/arm/boot/dts/imx6d*":true,
"arch/arm/boot/dts/imx6q*":true,
"arch/arm/boot/dts/imx6s*":true,
"arch/arm/boot/dts/imx6ul-*":true,
"arch/arm/boot/dts/imx6ull-9x9*":true,
"arch/arm/boot/dts/imx6ull-14x14-ddr*":true,
}
}

NXP内核编译

修改顶层Makefile

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

enter description here
图中第 252 和 253 行分别设置了 ARCH 和 CROSS_COMPILE 这两个变量的值,这样在编译的时候就不用输入很长的命令了。

配置并编译 Linux 内核

和 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 内核:

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

配置完成以后如图 37.2.2.1 所示:
enter description here

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

1
make -j16  //编译 Linux 内核

也可 以 创 建 一 个 编 译 脚 本 ,imx6ull_nxp_emmc.sh,脚本内容如下:

1
2
3
4
5
#!/bin/sh
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- distclean
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- imx_v7_mfg_defconfig
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- menuconfig
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- all -j16

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

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

等待编译完成,结果如图 37.2.2.2 所示:
enter description here

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。

Linux 内核启动测试

将上一小节编译出来的 zImage 和 imx6ull-14x14-evk.dtb 复制到 Ubuntu 中的 tftp 目录下,因为我们要在 uboot 中使用 tftp 命令将其下载到开发板中,拷贝命令如下:

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

拷贝完成以后就可以测试了,启动开发板,进入 uboot 命令行模式,环境变量 bootargs 内容如下:

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

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

1
2
3
setenv bootcmd 'tftp 80800000 zImage; tftp 83000000 imx6ull-14x14-evk.dtb; bootz 80800000 - 83000000'
saveenv
boot

结果图 37.2.3.1 所示:只要出现如下内容就表示成功

enter description here

如果EMMC里没有跟文件系统的话,会显示如下错误

enter description here

1
Kernel panic - not syncing: VFS: Unable to mount root fs on unknown-block(0,0)

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

添加自己的开发板

参考 I.MX6ULL EVK 开发板的设置,在 Linux 内核中添加正点原子的 I.MX6U-ALPHA 开发板。

添加开发板默认配置文件

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

1
2
cd arch/arm/configs
cp imx_v7_mfg_defconfig imx_alientek_emmc_defconfig

以后 imx_alientek_emmc_defconfig 就是正点原子的 EMMC 版开发板默认配置文件了。
以后就可以使用如下命令来配置正点原子 EMMC 版开发板对应的 Linux 内核了:

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

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

1
2
cd arch/arm/boot/dts
cp imx6ull-14x14-evk.dts imx6ull-alientek-emmc.dts

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
400 dtb-$(CONFIG_SOC_IMX6ULL) += \
401 imx6ull-14x14-ddr3-arm2.dtb \
402 imx6ull-14x14-ddr3-arm2-adc.dtb \
403 imx6ull-14x14-ddr3-arm2-cs42888.dtb \
404 imx6ull-14x14-ddr3-arm2-ecspi.dtb \
405 imx6ull-14x14-ddr3-arm2-emmc.dtb \
406 imx6ull-14x14-ddr3-arm2-epdc.dtb \
407 imx6ull-14x14-ddr3-arm2-flexcan2.dtb \
408 imx6ull-14x14-ddr3-arm2-gpmi-weim.dtb \
409 imx6ull-14x14-ddr3-arm2-lcdif.dtb \
410 imx6ull-14x14-ddr3-arm2-ldo.dtb \
411 imx6ull-14x14-ddr3-arm2-qspi.dtb \
412 imx6ull-14x14-ddr3-arm2-qspi-all.dtb \
413 imx6ull-14x14-ddr3-arm2-tsc.dtb \
414 imx6ull-14x14-ddr3-arm2-uart2.dtb \
415 imx6ull-14x14-ddr3-arm2-usb.dtb \
416 imx6ull-14x14-ddr3-arm2-wm8958.dtb \
417 imx6ull-14x14-evk.dtb \
418 imx6ull-14x14-evk-btwifi.dtb \
419 imx6ull-14x14-evk-emmc.dtb \
420 imx6ull-14x14-evk-gpmi-weim.dtb \
421 imx6ull-14x14-evk-usb-certi.dtb \
422 imx6ull-alientek-emmc.dtb \

第 422 行为“imx6ull-alientek-emmc.dtb”,这样编译 Linux 的时候就可以从 imx6ull-alientek-emmc.dts 编译出 imx6ull-alientek-emmc.dtb 文件了。

这里可以先编译测试一下,看是否则正确,再进行下面的修改操作。

1
2
cp arch/arm/boot/zImage /home/lonly/linux/tftpboot/ -f
cp arch/arm/boot/dts/imx6ull-alientek-emmc.dtb /home/lonly/linux/tftpboot/ -f

拷贝完成以后就可以测试了,启动开发板,进入 uboot 命令行模式,环境变量 bootargs 内容如下:

1
2
3
4
setenv bootargs 'console=ttymxc0,115200 root=/dev/mmcblk1p2 rootwait rw'
setenv bootcmd 'tftp 80800000 zImage; tftp 83000000 imx6ull-alientek-emmc.dtb; bootz 80800000 - 83000000'
saveenv
boot

如果出现下述状态,是因为没有根文件系统,使用开发板的话,厂家一般都会在emmc中下载好根文件系统,所以不会出现如下错误,这里并不影响测试linux内核和设备树。

enter description here

这里正点原子视频教程中还出现了emmc设备树问题,视频弹幕反馈和自己实测并不会出现
如果上述启动信息中有该行内容:mmcblk1: mmc1:0001 8GTF4R 7.28 GiB ,表示你的emmc是没问题的(不同内存卡容量可能不同)

1
2
3
4
5
6
7
8
9
Waiting for root device /dev/mmcblk1p2...
hub 1-1:1.0: USB hub found
mmc1: MAN_BKOPS_EN bit is not set
hub 1-1:1.0: 4 ports detected
mmc1: new HS200 MMC card at address 0001
mmcblk1: mmc1:0001 8GTF4R 7.28 GiB
mmcblk1boot0: mmc1:0001 8GTF4R partition 1 4.00 MiB
mmcblk1boot1: mmc1:0001 8GTF4R partition 2 4.00 MiB
mmcblk1rpmb: mmc1:0001 8GTF4R partition 3 512 KiB
主频修改

正点原子 I.MX6U-ALPHA 开发板所使用的 I.MX6ULL 芯片主频都是 792MHz 的。后续可能会生产 528MHz 核心板供企业级批量用户,但是开发板搭配的都是 792MHz 主频的,本节教程也就以 792MHz 的核心板为例讲解。

1、设置 I.MX6U-ALPHA 开发板工作在 792MHz

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

1
cat /proc/cpuinfo

enter description here
在中有 BogoMIPS 这一条,此时 BogoMIPS 为 3.00,BogoMIPS 是 Linux 系统中衡量处理器运行速度的一个“尺子”,我们可以通过 BogoMIPS 值来大致的判断当前处理器的性能。处理器性能越强,主频越高,BogoMIPS 值就越大。BogoMIPS 只是粗略的计算 CPU 性能,并不十分准确。

进入到目录cd /sys/devices/system/cpu/cpu0/cpufreq中,此目录下会有很多文件,此目录中记录了 CPU 频率等信息
enter description here

  • 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 频率:
    1
    cat cpuinfo_cur_freq
    enter description here
    从图可以看出,当前 CPU 频率为 198MHz,工作频率很低!其他的值如下(使用cat查看上述文件):
    1
    2
    3
    4
    5
    6
    7
    8
    cpuinfo_cur_freq = 198000
    cpuinfo_max_freq = 792000
    cpuinfo_min_freq = 198000
    scaling_cur_freq = 198000
    scaling_max_freq = 792000
    scaling_min_freq = 198000
    scaling_available_frequencies = 198000 396000 528000 792000
    scaling_governor = ondemand

调频策略为 ondemand,也就是定期检查负载,然后根据负载情况调节 CPU 频率。查看 stats 目录下的 time_in_state 文件可以看到 CPU 在各频率下的工作时间,命令如下:

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

enter description here
假如我们想让 CPU 一直工作在 792MHz 那该怎么办?很简单,配置 Linux 内核,将调频策略选择为 performance。或者修改 imx_alientek_emmc_defconfig 文件,此文件中有下面几行:
cd arch/arm/configs

1
2
3
4
41 CONFIG_CPU_FREQ_DEFAULT_GOV_ONDEMAND=y
42 CONFIG_CPU_FREQ_GOV_POWERSAVE=y
43 CONFIG_CPU_FREQ_GOV_USERSPACE=y
44 CONFIG_CPU_FREQ_GOV_INTERACTIVE=y

第 41 行,配置 ondemand 为默认调频策略。
第 42 行,使能 powersave 策略。
第 43 行,使能 userspace 策略。
第 44 行,使能 interactive 策略。

在 41 行前面添加:
CONFIG_CPU_FREQ_DEFAULT_GOV_PERFORMANCE=y
结果下所示:

这里正点原子教程中的配置文件是用图形界面生成的,我这是修改的原始的config文件,所以文件内容和修改的地方与教程不同
并且教程中修改是错误的,按教程修改后仍是 Ondemand 策略

1
2
3
4
CONFIG_CPU_FREQ_GOV_POWERSAVE=y
CONFIG_CPU_FREQ_GOV_USERSPACE=y
CONFIG_CPU_FREQ_GOV_ONDEMAND=y
CONFIG_CPU_FREQ_GOV_CONSERVATIVE=y

修改后,重新启动,查看运行频率和运行策略

ATK-IMX6U 是根文件系统中的用户名

1
2
3
4
5
6
root@ATK-IMX6U:~# cd /sys/devices/system/cpu/cpu0/cpufreq
root@ATK-IMX6U:/sys/devices/system/cpu/cpu0/cpufreq# cat cpuinfo_cur_freq
792000
root@ATK-IMX6U:/sys/devices/system/cpu/cpu0/cpufreq# cat scaling_governor
performance

使能 8 线 EMMC 驱动

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

1
2
3
4
5
6
7
8
9
734 &usdhc2 {
735 pinctrl-names = "default", "state_100mhz", "state_200mhz";
736 pinctrl-0 = <&pinctrl_usdhc2_8bit>;
737 pinctrl-1 = <&pinctrl_usdhc2_8bit_100mhz>;
738 pinctrl-2 = <&pinctrl_usdhc2_8bit_200mhz>;
739 bus-width = <8>;
740 non-removable;
741 status = "okay";
742 };

单独编译设备树文件

1
make dtbs
修改网络驱动

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

这里先不修改,查看linux启动信息

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
Configuring network interfaces... fec 20b4000.ethernet eth0: Freescale FEC PHY driver [Generic PHY] (mii_bus:phy_addr=20b4000.ethernet:01, irq=-1)
IPv6: ADDRCONF(NETDEV_UP): eth0: link is not ready
done.
Starting system message bus: Starting Xserver
dbus.
Starting Connection Manager
Starting Dropbear SSH server: dropbear.
Starting rpcbind daemon...done.
starting statd: done
Starting advanced power management daemon: No APM support in kernel
(failed.)
Starting atd: OK
exportfs: can't open /etc/exports for reading
NFS daemon support not enabled in kernel
Starting system log daemon...0
Starting kernel log daemon...0
* Starting Avahi mDNS/DNS-SD Daemon: avahi-daemon [ ok ]
Starting Telephony daemon
Starting Linux NFC daemon
Starting crond: OK
Running local boot scripts (/etc/rc.local).

root@ATK-IMX6U:~# fec 20b4000.ethernet eth0: Link is Up - 100Mbps/Full - flow control rx/tx
IPv6: ADDRCONF(NETDEV_CHANGE): eth0: link becomes ready
random: nonblocking pool is initialized

其中
ethernet eth0: Freescale FEC PHY driver [Generic PHY] (mii_bus:phy_addr=20b4000.ethernet:01, irq=-1)
表示使用默认通用网络驱动

root@ATK-IMX6U:~# fec 20b4000.ethernet eth0: Link is Up - 100Mbps/Full - flow control rx/tx IPv6: ADDRCONF(NETDEV_CHANGE): eth0: link becomes ready
表示端口0已经连接上网络,可以发现,网络是可以用的,

使用ping和ifconfig测试网络都没问题

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

PING 192.168.0.254 (192.168.0.254) 56(84) bytes of data.
64 bytes from 192.168.0.254: icmp_seq=1 ttl=64 time=99.2 ms
64 bytes from 192.168.0.254: icmp_seq=2 ttl=64 time=3.28 ms
^C
--- 192.168.0.254 ping statistics ---
11 packets transmitted, 11 received, 0% packet loss, time 10016ms
rtt min/avg/max/mdev = 2.294/18.786/99.204/32.362 ms
root@ATK-IMX6U:~# ifconfig
eth0 Link encap:Ethernet HWaddr b8:ae:1d:01:00:00
inet addr:192.168.0.174 Bcast:192.168.0.255 Mask:255.255.255.0
inet6 addr: fe80::baae:1dff:fe01:0/64 Scope:Link
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
RX packets:1634 errors:0 dropped:0 overruns:0 frame:0
TX packets:109 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:1000
RX bytes:193151 (188.6 KiB) TX bytes:12995 (12.6 KiB)

lo Link encap:Local Loopback
inet addr:127.0.0.1 Mask:255.0.0.0
inet6 addr: ::1/128 Scope:Host
UP LOOPBACK RUNNING MTU:65536 Metric:1
RX packets:14 errors:0 dropped:0 overruns:0 frame:0
TX packets:14 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:0
RX bytes:1552 (1.5 KiB) TX bytes:1552 (1.5 KiB)


那为什么修改呢,主要是因为使用默认驱动时网络是没有问题,但硬件终究是不同的,不保证后期使用时不会有问题,比如复位网络等功能。所以这里还是要修改一下,将通用网络驱动改为芯片对应的驱动,即SMSC LAN8720

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

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

1
2
3
4
5
6
7
8
584 pinctrl_spi4: spi4grp {
585 fsl,pins = <
586 MX6ULL_PAD_BOOT_MODE0__GPIO5_IO10 0x70a1
587 MX6ULL_PAD_BOOT_MODE1__GPIO5_IO11 0x70a1
588 /* MX6ULL_PAD_SNVS_TAMPER7__GPIO5_IO07 0x70a1 */
589 /* MX6ULL_PAD_SNVS_TAMPER8__GPIO5_IO08 0x80000000 */
590 >;
591 };

588 和 589 行就是初始化 SNVS_TAMPER7 和 SNVS_TAMPER8 这两个引脚的,不过是作为了 SPI4 的 IO(开发板中这两个引脚是LAN芯片的复位引脚),这不是我们想要的,所以将 588 和 589 这两行屏蔽!后继续在 imx6ull-alientek-emmc.dts 中找到如下所示代码:

1
2
3
4
5
6
7
125 spi4 {
126 compatible = "spi-gpio";
127 pinctrl-names = "default";
128 pinctrl-0 = <&pinctrl_spi4>;
129 /* pinctrl-assert-gpios = <&gpio5 8 GPIO_ACTIVE_LOW>; */
......
133 /* cs-gpios = <&gpio5 7 0>; */

第 129 行,设置 GPIO5_IO08 为 SPI4 的一个功能引脚,而 GPIO5_IO08 就是 SNVS_TAMPER8 的 GPIO 功能引脚。
第 133 行,设置 GPIO5_IO07 作为 SPI4 的片选引脚,而 GPIO5_IO07 就是 SNVS_TAMPER7的 GPIO 功能引脚。
现在我们需要 GPIO5_IO07 和 GPIO5_IO08 分别作为 ENET1 和 ENET2 的复位引脚,将示例代码 37.4.3.2 中的第 129 行和第 133 行处的代码屏蔽掉!!否则会干扰到网络复位引脚!

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
&iomuxc_snvs {
pinctrl-names = "default_snvs";
pinctrl-0 = <&pinctrl_hog_2>;
imx6ul-evk {
......
/*省略掉其他*/

/*enet1 reset lonly*/
pinctrl_enet1_reset: enet1resetgrp {
fsl,pins = <
/* used for enet1 reset */
MX6ULL_PAD_SNVS_TAMPER7__GPIO5_IO07 0x10B0
>;

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

第 1 行,imx6ull-alientek-emmc.dts 文件中 iomuxc_snvs 节点。
第 4550 行,ENET1 网络复位引脚配置信息。
第 53
58 行,ENET2 网络复位引脚配置信息。
最后还需要修改一下 ENET1 和 ENET2 的网络时钟引脚配置,继续在 imx6ull-alientek-emmc.dts 中找到如下所示代码:

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
309 pinctrl_enet1: enet1grp {
310 fsl,pins = <
311 MX6UL_PAD_ENET1_RX_EN__ENET1_RX_EN
0x1b0b0
312 MX6UL_PAD_ENET1_RX_ER__ENET1_RX_ER
0x1b0b0
313 MX6UL_PAD_ENET1_RX_DATA0__ENET1_RDATA00 0x1b0b0
314 MX6UL_PAD_ENET1_RX_DATA1__ENET1_RDATA01 0x1b0b0
315 MX6UL_PAD_ENET1_TX_EN__ENET1_TX_EN
0x1b0b0
316 MX6UL_PAD_ENET1_TX_DATA0__ENET1_TDATA00 0x1b0b0
317 MX6UL_PAD_ENET1_TX_DATA1__ENET1_TDATA01 0x1b0b0
318 MX6UL_PAD_ENET1_TX_CLK__ENET1_REF_CLK1 0x4001b009
319 >;
320 };
321
322 pinctrl_enet2: enet2grp {
323 fsl,pins = <
324 MX6UL_PAD_GPIO1_IO07__ENET2_MDC
0x1b0b0
325 MX6UL_PAD_GPIO1_IO06__ENET2_MDIO
0x1b0b0
326 MX6UL_PAD_ENET2_RX_EN__ENET2_RX_EN
0x1b0b0
327 MX6UL_PAD_ENET2_RX_ER__ENET2_RX_ER
0x1b0b0
328 MX6UL_PAD_ENET2_RX_DATA0__ENET2_RDATA00 0x1b0b0
329 MX6UL_PAD_ENET2_RX_DATA1__ENET2_RDATA01 0x1b0b0
330 MX6UL_PAD_ENET2_TX_EN__ENET2_TX_EN
0x1b0b0
331 MX6UL_PAD_ENET2_TX_DATA0__ENET2_TDATA00 0x1b0b0
332 MX6UL_PAD_ENET2_TX_DATA1__ENET2_TDATA01 0x1b0b0
333 MX6UL_PAD_ENET2_TX_CLK__ENET2_REF_CLK2 0x4001b009
334 >;
335 };

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

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
 &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";
......
};

第 34 行,修改后的 fec1 节点“pinctrl-0”属性值。
第 14
15 行,修改后的 fec2 节点“pinctrl-0”属性值。

3、修改 LAN8720A 的 PHY 地址

在 uboot 移植章节中,我们说过 ENET1 的 LAN8720A 地址为 0x0,ENET2 的 LAN8720A地址为 0x1。在 imx6ull-alientek-emmc.dts 中找到如下代码(还是上述“fec1”和“fec2”的这两个节点):

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
171 &fec1 {
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_enet1 &pinctrl_enet1_reset>;
phy-mode = "rmii";
phy-handle = <&ethphy0>;
status = "okay";
178 };

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

186 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>;
198 };
};
200 };

第 171177 行,ENET1 对应的设备树节点。
第 179
200 行,ENET2 对应的设备树节点。但是第 186~198 行的 mdio 节点描述了 ENET1和 ENET2 的 PHY 地址信息。将示例代码 37.4.3.6 改为如下内容:

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

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

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

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

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

第 177 和 178 行,添加了 ENET1 网络复位引脚所使用的 IO 为 GPIO5_IO07,低电平有效。复位低电平信号持续时间为 200ms。
第 188 和 189 行,ENET2 网络复位引脚所使用的 IO 为 GPIO5_IO08,同样低电平有效,持续时间同样为 200ms。
第 198 和 204 行,“smsc,disable-energy-detect”表明 PHY 芯片是 SMSC 公司的,这样 Linux内核就会找到 SMSC 公司的 PHY 芯片驱动来驱动 LAN8720A。
第 196 行,注意“ethernet-phy@”后面的数字是 PHY 的地址,ENET1 的 PHY 地址为 0,所以“@”后面是 0(默认为 2)。
第 199 行,reg 的值也表示 PHY 地址,ENET1 的 PHY 地址为 0,所以 reg=0。
第 202 行,ENET2 的 PHY 地址为 1,因此“@”后面为 1。
第 205 行,因为 ENET2 的 PHY 地址为 1,所以 reg=1。

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

4、修改 fec_main.c 文件

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

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
3438 static int
3439 fec_probe(struct platform_device *pdev)
3440 {
3441 struct fec_enet_private *fep;
3442 struct fec_platform_data *pdata;
3443 struct net_device *ndev;
3444 int i, irq, ret = 0;
3445 struct resource *r;
3446 const struct of_device_id *of_id;
3447 static int dev_id;
3448 struct device_node *np = pdev->dev.of_node, *phy_node;
3449 int num_tx_qs;
3450 int num_rx_qs;
3451
3452 /* 设置 MX6UL_PAD_ENET1_TX_CLK MX6UL_PAD_ENET2_TX_CLK
3453 * 这两个 IO 的复用寄存器的 SION 位为 1
3454 */
3455 void __iomem *IMX6U_ENET1_TX_CLK;
3456 void __iomem *IMX6U_ENET2_TX_CLK;
3457
3458 IMX6U_ENET1_TX_CLK = ioremap(0X020E00DC, 4);
3459 writel(0X14, IMX6U_ENET1_TX_CLK);
3460
3461 IMX6U_ENET2_TX_CLK = ioremap(0X020E00FC, 4);
3462 writel(0X14, IMX6U_ENET2_TX_CLK);
3463
......
3656 return ret;
3657 }

第 3455~3462 就是新加入的代码,如果要在 I.MX6ULL 上使用 LAN8720A 就需要设置ENET1 和 ENET2 的 TX_CLK 引脚复位寄存器的 SION 位为 1。

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

在 imx_alientek_emmc_defconfig 添加如下代码(任意行):

1
CONFIG_SMSC_PHY=y
1
make imx_alientek_emmc_defconfig
6、修改 smsc.c

鉴于 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
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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
static int smsc_phy_reset(struct phy_device *phydev)
{

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

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

21 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;
25 phy_reset = of_get_named_gpio(np, "phy-reset-gpios", 0);
if (!gpio_is_valid(phy_reset))
return;
29 gpio_direction_output(phy_reset, 0);
gpio_set_value(phy_reset, 0);

msleep(msec);
32 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.
*/
41 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);
}

48 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;
}

第 712 行,获取 FEC1 网卡对应的设备节点。
第 14
19 行,获取 FEC2 网卡对应的设备节点。
第 21 行,从设备树中获取“phy-reset-duration”属性信息,也就是复位时间。
第 25 行,从设备树中获取“phy-reset-gpios”属性信息,也就是复位 IO。
第 2932 行,设置 PHY 的复位 IO,复位 LAN8720A。
第 41
48 行,以前的 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 这两个函数,需要添加的头文件如下所示:
#include <linux/of_gpio.h>
#include <linux/io.h>

7. 测试

重新编译linux内核,得到新的镜像文件和设备树文件

1
2
3
4
5
6
starting statd: fec 2188000.ethernet eth1: Freescale FEC PHY driver [SMSC LAN8710/LAN8720] (mii_bus:phy_addr=20b4000.ethernet:00, irq=-1)
IPv6: ADDRCONF(NETDEV_UP): eth1: link is not ready

......
root@ATK-IMX6U:~# fec 20b4000.ethernet eth0: Link is Up - 100Mbps/Full - flow control rx/tx
IPv6: ADDRCONF(NETDEV_CHANGE): eth0: link becomes ready

可以发现网络驱动已经更改为 SMSC LAN8710/LAN8720,并且也已经正常启动。ping测试通过

1
2
3
4
5
6
7
8
9
10
root@ATK-IMX6U:~# ping 192.168.0.254
PING 192.168.0.254 (192.168.0.254) 56(84) bytes of data.
64 bytes from 192.168.0.254: icmp_seq=1 ttl=64 time=1084 ms
64 bytes from 192.168.0.254: icmp_seq=2 ttl=64 time=84.5 ms
64 bytes from 192.168.0.254: icmp_seq=3 ttl=64 time=34.7 ms
^C
--- 192.168.0.254 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2001ms
rtt min/avg/max/mdev = 34.771/401.288/1084.497/483.529 ms, pipe 2
root@ATK-IMX6U:~#

这里linux的网络是直接启动,但是教程中是要再继续设置的。以下作为备忘:
查看网络情况

1
ifconfig

查看所有网卡

1
ifconfig -a

打开网卡

1
2
ifconfig eth0 up
ifconfig eth1 up

设置IP地址

1
2
ifconfig eth0 192.168.1.251
ifconfig eth1 192.168.1.252

根文件系统构建

根文件系统简介

根文件系统一般也叫做 rootfs,那么什么叫根文件系统?看到“文件系统”这四个字,很多人,包括我第一反应就是 FATFS、FAT、EXT4、YAFFS 和 NTFS 等这样的文件系统。在这里,根文件系统并不是 FATFS 这样的文件系统代码,EXT4 这样的文件系统代码属于 Linux 内核的一部分。Linux 中的根文件系统更像是一个文件夹或者叫做目录(在我看来就是一个文件夹,只不过是特殊的文件夹),在这个目录里面会有很多的子目录。根目录下和子目录中会有很多的文件,这些文件是 Linux 运行所必须的,比如库、常用的软件和命令、设备文件、配置文件等等。以后我们说到文件系统,如果不特别指明,统一表示根文件系统。

根文件系统是 Linux 内核启动以后挂载(mount)的第一个文件系统,然后从根文件系统中读取初始化脚本,比如 rcS,inittab 等。根文件系统和 Linux 内核是分开的,单独的 Linux 内核是没法正常工作的,必须要搭配根文件系统。如果不提供根文件系统,Linux 内核在启动的时候就会提示内核崩溃(Kernel panic)的提示。

根文件系统的这个“根”字就说明了这个文件系统的重要性,它是其他文件系统的根,没有这个“根”,其他的文件系统或者软件就别想工作。比如我们常用的 ls、mv、ifconfig 等命令其实就是一个个小软件,只是这些软件没有图形界面,而且需要输入命令来运行。这些小软件就保存在根文件系统中,这些小软件是怎么来的呢?这个就是我们本章教程的目的,教大家来构建自己的根文件系统,这个根文件系统是满足 Linux 运行的最小根文件系统,后续我们可以根据自己的实际工作需求不断的去填充这个最小根文件系统,最终使其成为一个相对完善的根文件系统。

在构建根文件系统之前,我们先来看一下根文件系统里面大概都有些什么内容,以 Ubuntu为例,根文件系统的目录名字为‘/’,没看错就是一个斜杠,所以输入如下命令就可以进入根目录中:

1
cd /   //进入根目录

这里就讲解一些常用的子目录:

1、/bin 目录

看到“bin”大家应该能想到 bin 文件,bin 文件就是可执行文件。所以此目录下存放着系统需要的可执行文件,一般都是一些命令,比如 ls、mv 等命令。此目录下的命令所有的客户都可以使用。

2、/dev 目录

dev 是 device 的缩写,所以此目录下的文件都是和设备有关的,此目录下的文件都是设备文件。在 Linux 下一切皆文件,即使是硬件设备,也是以文件的形式存在的,比如/dev/ttymxc0(I.MX6ULL 根目录会有此文件)就表示 I.MX6ULL 的串口 0,我们要想通过串口 0发送或者接收数据就要操作文件/dev/ttymxc0,通过对文件/dev/ttymxc0 的读写操作来实现串口0 的数据收发。

3、/etc 目录

此目录下存放着各种配置文件,大家可以进入 Ubuntu 的 etc 目录看一下,里面的配置文件非常多!但是在嵌入式 Linux 下此目录会很简洁。

4、/lib 目录

lib 是 library 的简称,也就是库的意思,因此此目录下存放着 Linux 所必须的库文件。这些库文件是共享库,命令和用户编写的应用程序要使用这些库文件。

5、/mnt 目录

临时挂载目录,一般是空目录,可以在此目录下创建空的子目录,比如/mnt/sd、/mnt/usb,这样就可以将 SD 卡或者 U 盘挂载到/mnt/sd 或者/mnt/usb 目录中。

6、/proc 目录

此目录一般是空的,当 Linux 系统启动以后会将此目录作为 proc 文件系统的挂载点,proc是个虚拟文件系统,没有实际的存储设备。proc 里面的文件都是临时存在的,一般用来存储系统运行信息文件。

7、/usr 目录

要注意,usr 不是 user 的缩写,而是 Unix Software Resource 的缩写,也就是 Unix 操作系统软件资源目录。这里有个小知识点,那就是 Linux 一般被称为类 Unix 操作系统,苹果的 MacOS也是类 Unix 操作系统。关于 Linux 和 Unix 操作系统的渊源大家可以直接在网上找 Linux 的发展历史来看。既然是软件资源目录,因此/usr 目录下也存放着很多软件,一般系统安装完成以后此目录占用的空间最多。

8、/var 目录

此目录存放一些可以改变的数据。

9、/sbin 目录

此目录页用户存放一些可执行文件,但是此目录下的文件或者说命令只有管理员才能使用,主要用户系统管理。

10、/sys 目录

系统启动以后此目录作为 sysfs 文件系统的挂载点,sysfs 是一个类似于 proc 文件系统的特殊文件系统,sysfs 也是基于 ram 的文件系统,也就是说它也没有实际的存储设备。此目录是系统设备管理的重要目录,此目录通过一定的组织结构向用户提供详细的内核数据结构信息。

11、/opt

可选的文件、软件存放区,由用户选择将哪些文件或软件放到此目录中。关于 Linux 的根目录就介绍到这里,接下来的构建根文件系统就是研究如何创建上面这些子目录以及子目录中的文件。

BusyBox 构建根文件系统

BusyBox 简介

上一小节说了,根文件系统里面就是一堆的可执行文件和其他文件组成的?难道我们得一个一个的从网上去下载这些文件?显然这是不现实的!那么有没有人或者组织专门干这个事呢?他们负责“收集”这些文件,然后将其打包,像我们这样的开发者可以直接拿来用。答案是有的,它就叫做 BusyBox!其名字分为“Busy”和“Box”,也就是忙碌的盒子。盒子是用来放东西的,忙碌的是因为它要提供根文件系统所需的文件,所以忙碌。BusyBox 是一个集成了大量的 Linux 命令和工具的软件,像 ls、mv、ifconfig 等命令 BusyBox 都会提供。BusyBox 就是一个大的工具箱,这个工具箱里面集成了 Linux 的许多工具和命令。一般下载 BusyBox 的源码,然后配置 BusyBox,选择自己想要的功能,最后编译即可。BusyBox 可以在其官网下载到,官网地址为:https://busybox.net/

官网比较简陋,如图38.2.1.1 所示:
image

在官网左侧的“Get BusyBox”栏有一行“Download Source”,点击“Download Source”即可打开 BusyBox 的下载页,如图 38.2.1.2 所示:

输入中文是时,如果出现下图所示的输入框,是无法正常输入的,此时可以按 Esc 将或鼠标在界面任意处左键单击,就可以消除该输出框。然后才能正常输入中文命令。

输入中文时,输入法的界面(候选词)会显示在电脑其他地方,而不是在当前命令的光标处。

enter description here

打包时,要进入rootfs 文件夹内部打包,不能和文件夹同级。否则下载后,系统不能工作。
可以发现两种打包方式,生成的文件大小是不一样的,具体区别暂不深究,总之要进入文件夹内部再打包。

enter description here

打包错误导致启动linux后出现的错误,linux无法启动

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Waiting for root device /dev/mmcblk1p2...
mmc1: new HS200 MMC card at address 0001
mmcblk1: mmc1:0001 8GTF4R 7.28 GiB
mmcblk1boot0: mmc1:0001 8GTF4R partition 1 4.00 MiB
mmcblk1boot1: mmc1:0001 8GTF4R partition 2 4.00 MiB
mmcblk1rpmb: mmc1:0001 8GTF4R partition 3 512 KiB
mmcblk1: p1 p2
hub 1-1:1.0: USB hub found
hub 1-1:1.0: 4 ports detected
kjournald starting. Commit interval 5 seconds
EXT3-fs (mmcblk1p2): using internal journal
EXT3-fs (mmcblk1p2): mounted filesystem with ordered data mode
VFS: Mounted root (ext3 filesystem) on device 179:2.
devtmpfs: error mounting -2
Freeing unused kernel memory: 404K (8090f000 - 80974000)
Kernel panic - not syncing: No working init found. Try passing init= option to kernel. See Linux Documentation/init.txt for guidance.
---[ end Kernel panic - not syncing: No working init found. Try passing init= option to kernel. See Linux Documentation/init.txt for guidance.

修改Linux文件后,不能直接外部断电或复位重启,此时部分文件还在运行缓存中,弱国要重启,最好使用命令 reboot执行重启,系统会自动保存缓存数据后再重启。或者可以使用 sync 命令强制保存。

否则直接外部重启或断电会导致修改的数据丢失,比如刚修改的文件不会被修改,还保持原样。

系统在运行时,会自动后台不定时保存缓存内容。所以如果怕丢失重要数据,就每次修改后 sync强制更新

系统烧写

使用 NXP 官方提供的 MfgTool 工具通过 USB OTG 口来烧写系统。直接将 uboot、 linux kernel、 .dtb(设备树)和 rootfs 这四个文件烧写到板子上的 EMMC、 NAND 或 QSPI Flash 等其他存储设备上。

MfgTool 工具烧写原理

  1. 将 firmware 目录中的 uboot、 linux kernel 和.dtb(设备树),通过 USB OTG下载到开发板的 DDR 中,目的就是在 DDR 中启动 Linux 系统,为后面的烧写做准备。
  2. 经过第1步的操作,此时 Linux 系统已经运行起来了,系统运行起来以后完成对 EMMC 的格式化、分区等操作。 EMMC 分区建立好后,从 files 中读取要烧写的 uboot、 linux kernel、 .dtb(设备树)和 rootfs 这 4 个文件,然后将其烧写到 EMMC 中,这个就是 MfgTool 的大概工作流程。

1. 连接

  1. MfgTool 工具下载:是 NXP 提供的专门用于给 I.MX 系列 CPU 烧写系统的软件,可以在 NXP 官网下载到。开发板光盘中路径为: 5、开发工具->3、NXP官方原版MFG_TOOL烧写工具->L4.1.15_2.0.0-ga_mfg-tools.tar.gz
  2. 解压。选择 mfgtools-with-rootfs.tar.gz(带rootfs文件系统) 这个压缩包, 解压出一个名为 mfgtools-with-rootfs 的文件夹。
  3. 进入目录 mfgtools-with-rootfs\mfgtools 中,在此目录下有几个文件夹和很多的.vbs 文件,根据不同开发板选择不同的.vbs烧写脚本。其他的.vbs 烧写脚本用不到,因此可以删除掉
    enter description here
  4. 连接USB。MfgTool 是通过 USB OTG 接口将系统烧写进 EMMC 中的
    enter description here
    5、拨码开关拨到 USB 下载模式。

    如果插了 TF 卡,请弹出 TF 卡,否则电脑不能识别 USB!等识别出来以后再插上 TF 卡!==

  5. 按一下开发板的复位键,此时就会进入到 USB 模式,如果是第一次进入 USB 模式的话可能会久一点,这个是免驱的,因此不需要安装驱动

2. 系统烧写

  1. 准备系统文件
    1. 移植编译出来的 uboot 可执行文件: u-boot.imx。
    2. 移植编译出来的 zImage 镜像文件
    3. 开发板对应的.dtb(设备树),对于 I.MX6UALPHA 开发板来说就是 imx6ull-alientek-emmc.dtb。
    4. 构建的根文件系统 rootfs,这里我们需要对 rootfs 进行打包
  2. 双击“ mfgtool2-yocto-mx-evk-emmc.vbs”,打开下载对话框,如图
    enter description here
    如果出现“符合 HID 标准的供应商定义设备”就说明连接正常,可以进行烧写
  3. 进入如下目录中:
    L4.1.15_2.0.0-ga_mfg-tools/mfgtools-with-rootfs/mfgtools/Profiles/Linux/OS Firmware
  4. 进入firmeare 文件夹。使用我们自己的编译出来的 zImage、 u-boot.imx 和 imx6ull-alientekemmc.dtb 这三个文件替换掉表中这三个文件。名字要和表的一致,
    enter description here
  5. 进入files文件夹。同样,用我们编译出来的 zImage、 u-boot.imx 和 imx6ull-alientek-emmc.dtb 和 rootfs 这四个文件替换掉表中四个文件。
    enter description here
    点击“Start”按钮即可开始烧写

网络开机自启动

Linux是一个单体内核,支持真正的抢占式多任务处理(于用户态,和版本2.6系列之后的内核态[27][28])、虚拟内存、共享库、请求分页、共享写时复制可执行体(通过内核同页合并)、内存管理、Internet协议族和线程等功能。

设备驱动程序和内核扩展运行于内核空间(在很多CPU架构中是ring 0),可以完全访问硬件,但也有运行于用户空间的一些例外,例如基于FUSE/CUSE的文件系统,和部分UIO[29][30]。多数人与Linux一起使用的图形系统不运行在内核中。与标准单体内核不同,Linux的设备驱动程序可以轻易的配置为内核模块,并在系统运行期间可直接装载或卸载。也不同于标准单体内核,设备驱动程序可以在特定条件下被抢占;增加这个特征用于正确处理硬件中断并更好的支持对称多处理[28]。出于自愿选择,Linux内核没有二进制内核接口[31]。

硬件也被集成入文件层级中。用户应用到设备驱动的接口是在/dev或/sys目录下的入口文件[32]。进程信息也通过/proc目录映射到文件系统[32]。
1652321949009

shell

Linux操作系统综述
Linux系统组成.md

1652327045769


嵌入式Linux学习笔记-系统移植篇
http://lonlypan.com/2022/07/10/嵌入式Linux学习笔记-系统移植篇/
作者
LonlyPan
发布于
2022年7月10日
许可协议