U-boot、linux内核、根文件系统移植以及程序

终于这几天把这个移植的流程过了一遍,所以特此回来总结。

U-boot移植

首先是U-boot移植。Linux 系统要启动就必须需要一个 bootloader 程序,也就说芯片上电以后先运行一段bootloader 程序。这段bootloader程序会先初始化DDR等外设,然后将Linux内核从flash(NAND,NOR FLASH, SD, MMC 等)拷贝到 DDR 中,最后启动 Linux 内核。当然了, bootloader 的实际工作要复杂的多,但是它最主要的工作就是启动 Linux 内核, bootloader 和 Linux 内核的关系就跟 PC 上的 BIOS 和 Windows 的关系一样, bootloader 就相当于 BIOS。
首先我们有u-boot源码。这里采用的是正点原子文件夹中的源码。具体如下图所示:

在这里插入图片描述
通过FileZilla将u-boot的源码从windows传到虚拟机中,这里我传入的文件夹为下图所示。

在这里插入图片描述

添加开发板默认配置文件

输入指令:

cd configs
cp mx6ull_14x14_evk_emmc_defconfig mx6ull_alientek_emmc_defconfig

然后将文件 mx6ull_alientek_emmc_defconfig 中的内容改成下面的:

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

添加开发板对应的头文件

在 目 录 include/configs 下 添 加 I.MX6ULL-ALPHA 开 发 板 对 应 的 头 文 件 , 复 制include/configs/mx6ullevk.h,并重命名为 mx6ull_alientek_emmc.h,命令如下:

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

拷贝完成以后将:

#ifndef __MX6ULLEVK_CONFIG_H
#define __MX6ULLEVK_CONFIG_H

改为:

#ifndef __MX6ULL_ALIENTEK_EMMC_CONFIG_H
#define __MX6ULL_ALIENTEK_EMMC_CONFIG_H

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

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

cd board/freescale/
cp mx6ullevk/ -r mx6ull_alientek_emmc

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

cd mx6ull_alientek_emmc
mv mx6ullevk.c mx6ull_alientek_emmc.c

1、修改 mx6ull_alientek_emmc 目录下的 Makefile 文件

# (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 $< $@

改为 mx6ull_alientek_emmc.o,这样才会编译 mx6ull_alientek_emmc.c这个文件。

2、修改 mx6ull_alientek_emmc 目录下的 imximage.cfg 文件

将 imximage.cfg 中的下面一句:

PLUGIN board/freescale/mx6ullevk/plugin.bin 0x00907000

改为:

PLUGIN board/freescale/mx6ull_alientek_emmc /plugin.bin 0x00907000

3、修改 mx6ull_alientek_emmc 目录下的 Kconfig 文件

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 文件

MX6ULLEVK BOARD
M:	Peng Fan <peng.fan@nxp.com>
S:	Maintained
F:	board/freescale/mx6ull_alientek_emmc/
F:	include/configs/mx6ull_alientek_emmc.h
F:	configs/mx6ull_14x14_evk_defconfig
F:	configs/mx6ull_9x9_evk_defconfig

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

uboot 是支持图形界面配置,关于 uboot 的图形界面配置下一章会详细的讲解。修改文件archarch/arm/cpu/armv7/mx6/Kconfig,在 207 行加入如下内容:

config TARGET_MX6ULL_ALIENTEK_EMMC
	bool "Support mx6ull_alientek_emmc"
	select MX6ULL
	select DM
	select DM_THERMAL

在最后一行的 endif 的前一行添加如下内容:
在这里插入图片描述

使用新添加的板子配置编译 uboot

新建一个进行编译的sh文件,文件名字为:mx6ull_alientek_emmc.sh

#!/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- -j1

给予 mx6ll_alientek_emmc.sh 可执行权限,然后运行脚本来完成编译,命令如下:

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

编译完成烧录设置从sd卡进入。打开监视软件。如下图所示:
在这里插入图片描述
当出现下图logo的时候,证明修改u-boot成功。

在这里插入图片描述

LCD 驱动修改

上图由于是我先修改对应屏幕的分辨率,所以NXP的标志才能显示出来。一般默认是需要修改LCD配置的。
一般 uboot 中修改驱动基本都是在 xxx.h 和 xxx.c 这两个文件中进行的, xxx 为板子名称,
比如 mx6ull_alientek_emmc.h 和 mx6ull_alientek_emmc.c 这两个文件。
一般修改 LCD 驱动重点注意以下几点:
①、 LCD 所使用的 GPIO,查看 uboot 中 LCD 的 IO 配置是否正确。
②、 LCD 背光引脚 GPIO 的配置。
③、 LCD 配置参数是否正确。

struct display_info_t const displays[] = {{
	.bus = MX6UL_LCDIF1_BASE_ADDR,
	.addr = 0,
	.pixfmt = 24,
	.detect = NULL,
	.enable	= do_enable_parallel_lcd,
	.mode	= {
		.name			= "ATK-LCD-4.3-800x480",
		.xres           = 800,
		.yres           = 480,
		.pixclock       = 19531,
		.left_margin    = 8,
		.right_margin   = 4,
		.upper_margin   = 2,
		.lower_margin   = 4,
		.hsync_len      = 41,
		.vsync_len      = 10,
		.sync           = 0,
		.vmode          = FB_VMODE_NONINTERLACED
} } };

上面的配置文件是我修改之后的。上面程序定义了一个变量 displays,类型为 display_info_t,这个结构体是 LCD信息结构体,其中包括了 LCD 的分辨率,像素格式, LCD 的各个参数等。 display_info_t 定义在文件 arch/arm/include/asm/imx-common/video.h 中,定义如下:

示例代码 33.2.6.2 display_info 结构体
1 struct display_info_t {
2 int bus;
3 int addr;
4 int pixfmt;
5 int (*detect)(struct display_info_t const *dev);
6 void (*enable)(struct display_info_t const *dev);
7 struct fb_videomode mode;
8 };

pixfmt 是像素格式,也就是一个像素点是多少位,如果是 RGB565 的话就是 16 位,如果是 888 的话就是 24 位,一般使用 RGB888。结构体 display_info_t 还有个 mode 成员变量,此成员变量也是个结构体,为 fb_videomode,定义在文件 include/linux/fb.h 中,定义如下:

示例代码 33.2.6.3 fb_videomode 结构体
1 struct fb_videomode {
2 const char *name; /* optional */
3 u32 refresh; /* optional */
4 u32 xres;
5 u32 yres;
6 u32 pixclock;
7 u32 left_margin;
8 u32 right_margin;
9 u32 upper_margin;
10 u32 lower_margin;
11 u32 hsync_len;
12 u32 vsync_len;
13 u32 sync;
14 u32 vmode;
15 u32 flag;
16 };

结构体 fb_videomode 里面的成员变量为 LCD 的参数,这些成员变量函数如下:
name: LCD 名字,要和环境变量中的 panel 相等。
xres、 yres: LCD X 轴和 Y 轴像素数量。
pixclock:像素时钟,每个像素时钟周期的长度,单位为皮秒。
left_margin: HBP,水平同步后肩。
right_margin: HFP,水平同步前肩。
upper_margin: VBP,垂直同步后肩。
lower_margin: VFP,垂直同步前肩。
hsync_len: HSPW,行同步脉宽。
vsync_len: VSPW,垂直同步脉宽。
vmode: 大多数使用 FB_VMODE_NONINTERLACED,也就是不使用隔行扫描。屏幕要求的像素时钟为 51.2MHz,因此:pixclock=(1/51200000)*10^12=19531。这里计算的原理如下:
屏幕的像素时钟(Pixel Clock)是指每秒钟传输的像素数量,通常以兆赫兹(MHz)为单位。这个参数对于确定屏幕如何刷新和显示图像是非常重要的。像素时钟决定了屏幕能够以多快的速度更新其显示的像素。

在您的问题中,屏幕的像素时钟要求是 51.2 MHz,这意味着每秒钟有 51.2 百万个像素被传输。计算 pixclock 的公式实际上是在计算每个像素的时间周期(即每个像素持续的时间),单位通常是皮秒(ps,1皮秒 = (10^{-12})秒)。
计算步骤

  1. 计算每个像素的时间周期:由于像素时钟频率是每秒钟传输的像素数,因此每个像素的时间周期是像素时钟频率的倒数。即:

\[ \text{时间周期} = \frac{1}{\text{像素时钟频率}} \]

这里的像素时钟频率是 51.2 MHz,即 51,200,000 Hz。

  1. 转换为皮秒:由于屏幕规格书和系统通常需要使用皮秒作为单位,所以我们需要将秒转换为皮秒。1秒等于 (10^{12}) 皮秒。因此,每个像素的时间周期(秒)乘以 (10^{12}) 就得到了皮秒:

\[ \text{pixclock} = \left(\frac{1}{51200000}\right) \times 10^{12} \]

这将给出:

\[ \text{pixclock} = \frac{10^{12}}{51200000} \approx 19531 \text{ ps} \]

打开 mx6ull_alientek_emmc.h,找到所有如下语句:
panel=TFT43AB
将其改为:
panel=ATK-LCD-4.3-800x480。
也就是设置 panel 为 ATK-LCD-4.3-800x480, panel 的值要与display_info_t 中的.name 成员变量的值
一致。修改完成以后重新编译一遍 uboot 并烧写到 SD 中启动。

底板网络驱动修改

I.MX6UL/ULL 有两个网络接口 ENET1 和 ENET2,正点原子的 I.MX6U-ALPHA 开发板提供了这两个网络接口,其中 ENET1 和 ENET2 都使用 LAN8720A 作为 PHY 芯片。 NXP 官方的I.MX6ULL EVK 开发板使用 KSZ8081 这颗 PHY 芯片, LAN8720A 相比 KSZ8081 具有体积小、外围器件少、价格便宜等优点。
下面来看下它们的原理图:

在这里插入图片描述I.MX6U-ALPHA 开发板 ENET1 上连接的 LAN8720A器件地址为 0X0,所示我们要修改 ENET1 网络驱动的话重点就三点:
①、 ENET1 复位引脚初始化。
②、 LAN8720A 的器件 ID。
③、 LAN8720 驱动

下面看另一个原理图。

在这里插入图片描述

1、网络 PHY 地址修改

修改后的内容如下所示:

#ifdef CONFIG_CMD_NET
#define CONFIG_CMD_PING
#define CONFIG_CMD_DHCP
#define CONFIG_CMD_MII
#define CONFIG_FEC_MXC
#define CONFIG_MII
#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_MICREL
#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 公司生产的。所以有三处要修改:
①、修改 ENET1 网络 PHY 的地址。
②、修改 ENET2 网络 PHY 的地址。
③、使能 SMSC 公司的 PHY 驱动

2、删除 uboot 中 74LV595 的驱动代码

uboot 中网络 PHY 芯片地址修改完成以后就是网络复位引脚的驱动修改了,打开mx6ull_alientek_emmc.c,找到如下代码:

示例代码 33.2.7.3 74LV595 引脚
#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,因此我们将示例代码 33.2.7.3 中的代码删除掉,替换为如下所示代码:

#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 中找到如下代码:iox74lv_init 函数是 74LV595 的初始化函数, iox74lv_set 函数用于控制 74LV595 的 IO 输出电平,将这两个函数全部删除掉!
在 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 开发板两个网络复位引脚了。

3、添加 I.MX6U-ALPHA 开发板网络复位引脚驱动

在 mx6ull_alientek_emmc.c 中找到如下所示代码:
结构体数组 fec1_pads 和 fec2_pads 是 ENET1 和 ENET2 这两个网口的 IO 配置参数,在这两个数组中添加两个网口的复位 IO 配置参数,完成以后如下所示:

static iomux_v3_cfg_t const fec1_pads[] = {
	MX6_PAD_GPIO1_IO06__ENET1_MDIO | MUX_PAD_CTRL(MDIO_PAD_CTRL),
	MX6_PAD_GPIO1_IO07__ENET1_MDC | MUX_PAD_CTRL(ENET_PAD_CTRL),
	MX6_PAD_ENET1_TX_DATA0__ENET1_TDATA00 | MUX_PAD_CTRL(ENET_PAD_CTRL),
	MX6_PAD_ENET1_TX_DATA1__ENET1_TDATA01 | MUX_PAD_CTRL(ENET_PAD_CTRL),
	MX6_PAD_ENET1_TX_EN__ENET1_TX_EN | MUX_PAD_CTRL(ENET_PAD_CTRL),
	MX6_PAD_ENET1_TX_CLK__ENET1_REF_CLK1 | MUX_PAD_CTRL(ENET_CLK_PAD_CTRL),
	MX6_PAD_ENET1_RX_DATA0__ENET1_RDATA00 | MUX_PAD_CTRL(ENET_PAD_CTRL),
	MX6_PAD_ENET1_RX_DATA1__ENET1_RDATA01 | MUX_PAD_CTRL(ENET_PAD_CTRL),
	MX6_PAD_ENET1_RX_ER__ENET1_RX_ER | MUX_PAD_CTRL(ENET_PAD_CTRL),
	MX6_PAD_ENET1_RX_EN__ENET1_RX_EN | MUX_PAD_CTRL(ENET_PAD_CTRL),
	MX6_PAD_SNVS_TAMPER7__GPIO5_IO07 | MUX_PAD_CTRL(NO_PAD_CTRL),
};

static iomux_v3_cfg_t const fec2_pads[] = {
	MX6_PAD_GPIO1_IO06__ENET2_MDIO | MUX_PAD_CTRL(MDIO_PAD_CTRL),
	MX6_PAD_GPIO1_IO07__ENET2_MDC | MUX_PAD_CTRL(ENET_PAD_CTRL),

	MX6_PAD_ENET2_TX_DATA0__ENET2_TDATA00 | MUX_PAD_CTRL(ENET_PAD_CTRL),
	MX6_PAD_ENET2_TX_DATA1__ENET2_TDATA01 | MUX_PAD_CTRL(ENET_PAD_CTRL),
	MX6_PAD_ENET2_TX_CLK__ENET2_REF_CLK2 | MUX_PAD_CTRL(ENET_CLK_PAD_CTRL),
	MX6_PAD_ENET2_TX_EN__ENET2_TX_EN | MUX_PAD_CTRL(ENET_PAD_CTRL),

	MX6_PAD_ENET2_RX_DATA0__ENET2_RDATA00 | MUX_PAD_CTRL(ENET_PAD_CTRL),
	MX6_PAD_ENET2_RX_DATA1__ENET2_RDATA01 | MUX_PAD_CTRL(ENET_PAD_CTRL),
	MX6_PAD_ENET2_RX_EN__ENET2_RX_EN | MUX_PAD_CTRL(ENET_PAD_CTRL),
	MX6_PAD_ENET2_RX_ER__ENET2_RX_ER | MUX_PAD_CTRL(ENET_PAD_CTRL),
	MX6_PAD_SNVS_TAMPER8__GPIO5_IO08 | MUX_PAD_CTRL(NO_PAD_CTRL),
};

继续在文件 mx6ull_alientek_emmc.c 中找到函数 setup_iomux_fec,此函数默认代码如下,函数setup
_iomux_fec 就是根据 fec1_pads 和 fec2_pads 这两个网络 IO 配置数组来初始化I.MX6ULL 的网络 IO。我们需要在其中添加网络复位 IO 的初始化代码,并且复位一下 PHY 芯片,修改后的 setup_iomux_fec 函数如下:

static void setup_iomux_fec(int fec_id)
{
	if (fec_id == 0)
	{
		imx_iomux_v3_setup_multiple_pads(fec1_pads,ARRAY_SIZE(fec1_pads));
		gpio_direction_output(ENET1_RESET, 1);
		gpio_set_value(ENET1_RESET, 0);
		mdelay(20);
		gpio_set_value(ENET1_RESET, 1);
	}
	else
	{
		imx_iomux_v3_setup_multiple_pads(fec2_pads,ARRAY_SIZE(fec2_pads));
		gpio_direction_output(ENET2_RESET, 1);
		gpio_set_value(ENET2_RESET, 0);
		mdelay(20);
		gpio_set_value(ENET2_RESET, 1);
	}	
}

第 676 行~679 行和第 685 行~688 行分别对应 ENET1 和 ENET2 的复位 IO 初始化,将这两个 IO 设置为输出并且硬件复位一下 LAN8720A,这个硬件复位很重要!否则可能导致 uboot 无法识别 LAN8720A。

大功基本上告成,还差最后一步, uboot 中的 LAN8720A 驱动有点问题,打开文件drivers/net/phy/phy.c,找到函数 genphy_update_link,这是个通用 PHY 驱动函数,此函数用于更新 PHY 的连接状态和速度。使用 LAN8720A 的时候需要在此函数中添加一些代码,修改后的函数 genphy_update_link 如下所示:

int genphy_update_link(struct phy_device *phydev)
{
	unsigned int mii_reg;

	
	static int lan8720_flag = 0;
	int bmcr_reg = 0;
	if (lan8720_flag == 0) {
	bmcr_reg = phy_read(phydev, MDIO_DEVAD_NONE, MII_BMCR);
 	phy_write(phydev, MDIO_DEVAD_NONE, MII_BMCR, BMCR_RESET);
	while(phy_read(phydev, MDIO_DEVAD_NONE, MII_BMCR) & 0X8000) {
	udelay(100);
	}
	phy_write(phydev, MDIO_DEVAD_NONE, MII_BMCR, bmcr_reg);
	lan8720_flag = 1;
	}

	
	/*
	 * Wait if the link is up, and autonegotiation is in progress
	 * (ie - we're capable and it's not done)
	 */
	mii_reg = phy_read(phydev, MDIO_DEVAD_NONE, MII_BMSR);

	/*
	 * If we already saw the link up, and it hasn't gone down, then
	 * we don't need to wait for autoneg again
	 */
	if (phydev->link && mii_reg & BMSR_LSTATUS)
		return 0;

	if ((phydev->autoneg == AUTONEG_ENABLE) &&
	    !(mii_reg & BMSR_ANEGCOMPLETE)) {
		int i = 0;

		printf("%s Waiting for PHY auto negotiation to complete",
			phydev->dev->name);
		while (!(mii_reg & BMSR_ANEGCOMPLETE)) {
			/*
			 * Timeout reached ?
			 */
			if (i > PHY_ANEG_TIMEOUT) {
				printf(" TIMEOUT !\n");
				phydev->link = 0;
				return 0;
			}

			if (ctrlc()) {
				puts("user interrupt!\n");
				phydev->link = 0;
				return -EINTR;
			}

			if ((i++ % 500) == 0)
				printf(".");

			udelay(1000);	/* 1 ms */
			mii_reg = phy_read(phydev, MDIO_DEVAD_NONE, MII_BMSR);
		}
		printf(" done\n");
		phydev->link = 1;
	} else {
		/* Read the link a second time to clear the latched state */
		mii_reg = phy_read(phydev, MDIO_DEVAD_NONE, MII_BMSR);

		if (mii_reg & BMSR_LSTATUS)
			phydev->link = 1;
		else
			phydev->link = 0;
	}

	return 0;
}

编译完成烧录到sd卡启动,可以看到下图出现了FEC1:
在这里插入图片描述
这里还需要设置一下开发板子IP和服务器ip

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 //保存环境变量

但是如果虚拟机为桥接模式的话,是无法Ping通的。所以这里参考韦东山的双网卡设置。

虚拟机双网卡设置

首先在虚拟机中添加网络适配器这个选项,并设置为桥接模式。具体如下图所示:

在这里插入图片描述
当我们将网线插入电脑上时候,打开网络适配器,这时候会发现对应网线是有一个设备的,这里我们要先记住这个设备名字。具体如下图:
在这里插入图片描述
然后进入虚拟机软件,打开编辑下的虚拟网络编辑器。

在这里插入图片描述

设置下图红框的:
在这里插入图片描述
接下来保存上面的设置,进入虚拟机设置,

在这里插入图片描述
点击options设置网络ip地址。设置和开发板一样的ip段,如果还是ping不通,就去看下是不是防火墙相关原因。
如果正常如下图所示:

在这里插入图片描述

至此,u-boot已经移植完毕。那么如何测试启动linux。根据原子文档分为2种方式。

从 EMMC 启动 Linux 系统

原子文档说拿到手的 I.MX6U-ALPHA 开发板(EMMC 版本)已经将 zImage 文件和设备树文件烧写到了 EMMC 中,所以我们可以直接读取来测试。先检查一下 EMMC 的分区 1 中有没有zImage 文件和设备树文件,

mmc dev 1	//切换到EMMC
fatls mmc 1:1  //查看EMMC分区1里面的文件
fatload mmc 1:1 80800000 zImage //将zimage下载到DDR的0x80800000处
fatload mmc 1:1 83000000 imx6ull-14x14-emmc-7-1024x600-c.dtb //将dtb读取到0X83000000
bootz 80800000 - 83000000 //启动内核

我们输入前面2条指令,会发现在分区是存在zimage镜像和设备树文件的。

在这里插入图片描述

输入剩下的,发现能够正常启动linux,则证明u-boot移植成功。

在这里插入图片描述

从网络启动 Linux 系统

从网络启动linux需要先配置虚拟机这边的设置。需要用到tftp 命令。需要安装 tftp-hpa 和 tftpd-hpa,命令如下:

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

TFTP 也需要一个文件夹来存放文件,在用户目录下新建一个目录,命令如下:

mkdir /home/这里为你虚拟机用户名/linux/tftpboot
chmod 777 /home/这里为你虚拟机用户名/linux/tftpboot

最后配置 tftp,安装完成以后新建文件/etc/xinetd.d/tftp, 如果没有/etc/xinetd.d 目录的话自行创建, 然后在里面输入如下内容:

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

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

1 # /etc/default/tftpd-hpa
2 TFTP_USERNAME="tftp"
4 TFTP_DIRECTORY="/home/这里为你虚拟机用户名/linux/tftpboot"
5 TFTP_ADDRESS=":69"
6 TFTP_OPTIONS="-l -c -s"

TFTP_DIRECTORY 就是我们上面创建的 tftp 文件夹目录,以后我们就将所有需要通过TFTP 传输的文件都放到这个文件夹里面,并且要给予这些文件相应的权限。最后输入如下命令, 重启 tftp 服务器:

sudo service tftpd-hpa restart

这里需要你把zimage和设备树放到tftpboot里面去。

tftp 80800000 zImage		//从tftp服务器下载zimage
tftp 83000000 imx6ull-alientek-emmc.dtb //从tftp服务器下载.dtb	
bootz 80800000 - 83000000 //启动系统

出现下列界面则证明u-boot启动linux内核成功
在这里插入图片描述

Linux 内核移植

NXP 官方原版 Linux 源码已经放到了开发板光盘中,路径为: 1、例程源码->4、 NXP 官方原版 Uboot和 Linux->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 的目录,
这里我存放的路径为:
在这里插入图片描述
重点是.vscode/settings.json 这个文件,由于文件夹比较多,所以这里设置下。
具体设置可以参照之前的一篇博客。
接下来就是开始配置修改各个项。

在 Linux 中添加自己的开发板

添加开发板默认配置文件

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

cd arch/arm/configs
cp imx_v7_mfg_defconfig imx_alientek_emmc_defconfig

打开 imx_alientek_emmc_defconfig 文件,找到“CONFIG_ARCH_MULTI_V6=y”这一行,将其屏蔽掉,

在这里插入图片描述
因为 I.MX6ULL 是 ARMV7 架构的,因此要屏蔽掉 V6 相关选项,否则后面做驱动实验的时候可能会遇到驱动模块无法加载的情况。
以后 imx_alientek_emmc_defconfig 就是正点原子的 EMMC 版开发板默认配置文件了。输入指令:

make imx_alientek_emmc_defconfig

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

进入目录 arch/arm/boot/dts 中,复制一份 imx6ull-14x14-evk.dts,然后将其重命名为 imx6ull-alientek-emmc.dts,命令如下:

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” ,如下所示:

在这里插入图片描述

编译测试

Linux 内核里面已经添加了正点原子 I.MX6UL-ALIPHAEMMC 版 开 发 板 了 , 接 下 接 编 译 测 试 一 下 , 我 们 可 以 创 建 一 个 编 译 脚 本 ,imx6ull_alientek_emmc.sh,脚本内容如下:

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

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

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

1、修改 EMMC 驱动

1、使能 8 线 EMMC 驱动
正点原子 EMMC 版本核心板上的 EMMC 采用的 8 位数据线.具体原理图:
在这里插入图片描述
Linux 内核驱动里面 EMMC 默认是 4 线模式的, 4 线模式肯定没有 8 线模式的速度快,所以本节我们将EMMC 的驱动修改为 8 线模式。4 线模式肯定没有 8 线模式的速度快,所以本节我们将 EMMC 的驱动修改为 8 线模式。修改方法很简单,直接修改设备树即可,打开文件 imx6ull-alientek-emmc.dts,找到如下所示内容:

示例代码 37.4.2.1 imx6ull-alientek-emmc.dts 代码段
734 &usdhc2 {
735 pinctrl-names = "default";
736 pinctrl-0 = <&pinctrl_usdhc2>;
737 non-removable;
738 status = "okay";
739 };

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

示例代码 37.4.2.1 imx6ull-alientek-emmc.dts 代码段
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 };

2、 关闭 EMMC 1.8V 供电选项

从图 37.4.2.1 中可以看出,此时 EMMC 工作电压是 3.3V 的,因此我们要在示例代码 37.4.2.1中的 usdhc2 设备树节点中添加“no-1-8-v” 选项,也就是关闭 1.8V 这个功能选项。 防止内核在运行的时候用 1.8V 去驱动 EMMC,导致 EMMC 驱动出现问题,

示例代码 37.4.2.2 最终的 usdhc2 节点代码段
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 no-1-8-v;
742 status = "okay";
743 };

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

3、底板网络驱动修改

正点原子开发板的网络和 NXP 官方的网络硬件上不同,网络 PHY 芯片由 KSZ8081 换为了 LAN8720A,两个网络 PHY 芯片的复位 IO 也不同。所以 Linux 内核自带的网络驱动是驱动不起来 I.MX6U-ALPHA 开发板上的网络的,需要做修改:

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

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

示例代码 37.4.3.1 imx6ull-alientek-emmc.dts 代码段
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,这不是我们想要的,所以将 588 和 589 这两行删除掉!删除掉以后继续在 imx6ull-alientek-emmc.dts 中找到如下所示代码:

 示例代码 37.4.3.2 imx6ull-alientek-emmc.dts 代码段
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 的复位引脚,而不是 SPI4 的什么功能引脚,因此将示例代码 37.4.3.2 中的第 129 行和第 133 行处的代码删除掉!!否则会干扰到网络复位脚!
在 imx6ull-alientek-emmc.dts 里面找到名为“iomuxc_snvs”的节点(就是直接搜索),然后在此节点下添加网络复位引脚信息:

示例代码 37.4.3.3 iomuxc_snvs 节点添加网络复位信息
1 &iomuxc_snvs {
2 pinctrl-names = "default_snvs";
3 pinctrl-0 = <&pinctrl_hog_2>;
4 imx6ul-evk {
5
...... /*省略掉其他*/
43
44 /*enet1 reset zuozhongkai*/
45 pinctrl_enet1_reset: enet1resetgrp {
46 fsl,pins = <
47 /* used for enet1 reset */
48 MX6ULL_PAD_SNVS_TAMPER7__GPIO5_IO07 0x10B0
49 >;
50 };
51
52 /*enet2 reset zuozhongkai*/
53 pinctrl_enet2_reset: enet2resetgrp {
54 fsl,pins = <
55 /* used for enet2 reset */
56 MX6ULL_PAD_SNVS_TAMPER8__GPIO5_IO08 0x10B0
57 >;
58 };
59 };
60 };

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

示例代码 37.4.3.4 imx6ull-alientek-emmc.dts 代码段
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 };
2、修改 fec1 和 fec2 节点的 pinctrl-0 属性

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

示例代码 37.4.3.5 修改 fec1 和 fec2 的 pinctrl-0 属性
1 &fec1 {
2 pinctrl-names = "default";
3 pinctrl-0 = <&pinctrl_enet1
4 &pinctrl_enet1_reset>;
5 phy-mode = "rmii";
......
9 status = "okay";
10 };
11
12 &fec2 {
13 pinctrl-names = "default";
14 pinctrl-0 = <&pinctrl_enet2
15 &pinctrl_enet2_reset>;
16 phy-mode = "rmii";
......
36 };

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

3、修改 LAN8720A 的 PHY 地址

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

&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>;
		};
	};
};
3、修改 fec_main.c 文件

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

示例代码 37.4.3.8 fec_probe 函数代码段
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。

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

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

5、修改 smsc.c 文件

首先需要找到 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 */ 
	{
		np = of_find_node_by_path("/soc/aips-bus@02100000/ethernet@02188000");
		if(np == NULL) {
	 	return -EINVAL;
	 	}
	}
    if(phydev->addr == 1) /* FEC1 */ 
	{
		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);
	if (!err && msec > 1000)
		msec = 1;
	phy_reset = of_get_named_gpio(np, "phy-reset-gpios", 0);
	if (!gpio_is_valid(phy_reset))
		return;
	gpio_direction_output(phy_reset, 0);
	gpio_set_value(phy_reset, 0);
	msleep(msec);
	gpio_set_value(phy_reset, 1);
	/* If the SMSC PHY is in power down mode, then set it
	 * in all capable mode before using it.
	 */
	int rc = phy_read(phydev, MII_LAN83C185_SPECIAL_MODES);
	if (rc < 0)
		return rc;
	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);

		/* wait end of reset (max 500 ms) */
	}
	phy_write(phydev, MII_BMCR, BMCR_RESET);
	do {
		udelay(10);
		if (timeout-- == 0)
			return -1;
		rc = phy_read(phydev, MII_BMCR);
	} while (rc & BMCR_RESET);
	return 0;
}

编译烧录进去即可。

根文件系统构建

根文件系统首先是内核启动时所 mount(挂载)的第一个文件系统,内核代码映像文件保存在根文件系统中,而系统引导启动程序会在根文件系统挂载之后从中把一些基本的初始化脚本和服务等加载到内存中去运行。嵌入式 Linux 并没有将内核代码镜像保存在根文件系统中,而是保存到了其他地方。比如 NAND Flash 的指定存储地址、EMMC 专用分区中。根文件系统是 Linux 内核启动以后挂载(mount)的第一个文件系统,然后从根文件系统中读取初始化脚本,比如 rcS, inittab 等。根文件系统和 Linux 内核是分开的,单独的 Linux 内核是没法正常工作的,必须要搭配根文件系统。如果不提供根文件系统, Linux 内核在启动的时候就会提示内核崩溃(Kernel panic)的提示,

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、 /sys 目录

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

10、 /opt

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

11、 /sbin 目录

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

BusyBox 构建根文件系统

测试 1.29.0 版本目前还没有出现任何问题,路径为: 1、例程源码->6、 BusyBox 源码->busybox-1.29.0.tar.bz2, BusyBox 准备好以后就可以构建根文件系统了。
在 Linux 驱动开发的时候都是通过 nfs 挂载根文件系统的,nfs设置如下:

NFS 服务开启

后面进行 Linux 驱动开发的时候需要 NFS 启动,因此要先安装并开启 Ubuntu 中的 NFS 服务,使用如下命令安装 NFS 服务:

sudo apt-get install nfs-kernel-server rpcbind

等待安装完成,安装完成以后在用户根目录下创建一个名为“linux”的文件夹,以后所有的东西都放到这个“linux”文件夹里面,在“linux”文件夹里面新建一个名为“nfs”的文件夹,如图所示:

在这里插入图片描述

要先配置 nfs,使用如下命令打开 nfs 配置文件/etc/exports:

sudo vi /etc/exports

打开/etc/exports 以后在后面添加如下所示内容:

/home/这里是你虚拟机的用户名字/linux/nfs *(rw,sync,no_root_squash)

重启 NFS 服务,使用命令如下:

sudo /etc/init.d/nfs-kernel-server restart

比如我的电脑中“/home/这里为你虚拟机的用户名/linux/nfs”就是我设置的 NFS 服务器目录,使用如下命令创建名
为 rootfs 的子目录:

mkdir rootfs

创建好的 rootfs 子目录就用来存放我们的根文件系统了。
将 busybox-1.29.0.tar.bz2 发送到 Ubuntu 中,存放位置大家随便选择。然后使用如下命令将
其解压:

tar -vxjf busybox-1.29.0.tar.bz2

解压后如下图所示:
在这里插入图片描述

1、修改 Makefile,添加编译器

打开 busybox 的顶层 Makefile,添加 ARCH 和 CROSS_COMPILE的值,如下所示:

ARCH ?= arm
CROSS_COMPILE ?=/usr/local/arm/gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabihf/bin/arm-linux-gnueabihf-

2、 busybox 中文字符支持

如果默认直接编译 busybox 的话,在使用 SecureCRT 的时候中文字符是显示不正常的,中文字符会显示为“?”,比如你的中文目录,中文文件都显示为“?”。不知道从哪个版本开始 busybox中的 shell 命令对中文输入即显示做了限制,即使内核支持中文但在 shell 下也依然无法正确显示。打开文件 busybox1.29.0
/libbb/printable_string.c,找到函数 printable_string,缩减后的函数内容如下:

/* vi: set sw=4 ts=4: */
/*
 * Unicode support routines.
 *
 * Copyright (C) 2010 Denys Vlasenko
 *
 * Licensed under GPLv2, see file LICENSE in this source tree.
 */
#include "libbb.h"
#include "unicode.h"

const char* FAST_FUNC printable_string(uni_stat_t *stats, const char *str)
{
	char *dst;
	const char *s;

	s = str;
	while (1) {
		unsigned char c = *s;
		if (c == '\0') {
			/* 99+% of inputs do not need conversion */
			if (stats) {
				stats->byte_count = (s - str);
				stats->unicode_count = (s - str);
				stats->unicode_width = (s - str);
			}
			return str;
		}
		if (c < ' ')
			break;
		/* if (c >= 0x7f)
		 break; */
		s++;
	}

#if ENABLE_UNICODE_SUPPORT
	dst = unicode_conv_to_printable(stats, str);
#else
	{
		char *d = dst = xstrdup(str);
		while (1) {
			unsigned char c = *d;
			if (c == '\0')
				break;
			/*if (c < ' ' || c >= 0x7f)*/
				if( c < ' ')
				*d = '?';
			d++;
		}
		if (stats) {
			stats->byte_count = (d - dst);
			stats->unicode_count = (d - dst);
			stats->unicode_width = (d - dst);
		}
	}
#endif
	return auto_string(dst);
}

接着打开文件 busybox-1.29.0/libbb/unicode.c,找到如下内容:

static char* FAST_FUNC unicode_conv_to_printable2(uni_stat_t *stats, const char *src, unsigned width, int flags)
{
	char *dst;
	unsigned dst_len;
	unsigned uni_count;
	unsigned uni_width;

	if (unicode_status != UNICODE_ON) {
		char *d;
		if (flags & UNI_FLAG_PAD) {
			d = dst = xmalloc(width + 1);
			while ((int)--width >= 0) {
				unsigned char c = *src;
				if (c == '\0') {
					do
						*d++ = ' ';
					while ((int)--width >= 0);
					break;
				}

				/* *d++ = (c >= ' ' && c < 0x7f) ? c : '?'; */
				 *d++ = (c >= ' ') ? c : '?';
				src++;
			}
			*d = '\0';
		} else {
			d = dst = xstrndup(src, width);
			while (*d) {
				unsigned char c = *d;
				/* if (c < ' ' || c >= 0x7f) */
 				if(c < ' ')
					*d = '?';
				d++;
			}
		}
		if (stats) {
			stats->byte_count = (d - dst);
			stats->unicode_count = (d - dst);
			stats->unicode_width = (d - dst);
		}
		return dst;
	}

	dst = NULL;
	uni_count = uni_width = 0;
	dst_len = 0;
	while (1) {
		int w;
		wchar_t wc;

#if ENABLE_UNICODE_USING_LOCALE
		{
			mbstate_t mbst = { 0 };
			ssize_t rc = mbsrtowcs(&wc, &src, 1, &mbst);
			/* If invalid sequence is seen: -1 is returned,
			 * src points to the invalid sequence, errno = EILSEQ.
			 * Else number of wchars (excluding terminating L'\0')
			 * written to dest is returned.
			 * If len (here: 1) non-L'\0' wchars stored at dest,
			 * src points to the next char to be converted.
			 * If string is completely converted: src = NULL.
			 */
			if (rc == 0) /* end-of-string */
				break;
			if (rc < 0) { /* error */
				src++;
				goto subst;
			}
			if (!iswprint(wc))
				goto subst;
		}
#else
		src = mbstowc_internal(&wc, src);
		/* src is advanced to next mb char
		 * wc == ERROR_WCHAR: invalid sequence is seen
		 * else: wc is set
		 */
		if (wc == ERROR_WCHAR) /* error */
			goto subst;
		if (wc == 0) /* end-of-string */
			break;
#endif
		if (CONFIG_LAST_SUPPORTED_WCHAR && wc > CONFIG_LAST_SUPPORTED_WCHAR)
			goto subst;
		w = wcwidth(wc);
		if ((ENABLE_UNICODE_COMBINING_WCHARS && w < 0) /* non-printable wchar */
		 || (!ENABLE_UNICODE_COMBINING_WCHARS && w <= 0)
		 || (!ENABLE_UNICODE_WIDE_WCHARS && w > 1)
		) {
 subst:
			wc = CONFIG_SUBST_WCHAR;
			w = 1;
		}
		width -= w;
		/* Note: if width == 0, we still may add more chars,
		 * they may be zero-width or combining ones */
		if ((int)width < 0) {
			/* can't add this wc, string would become longer than width */
			width += w;
			break;
		}

		uni_count++;
		uni_width += w;
		dst = xrealloc(dst, dst_len + MB_CUR_MAX);
#if ENABLE_UNICODE_USING_LOCALE
		{
			mbstate_t mbst = { 0 };
			dst_len += wcrtomb(&dst[dst_len], wc, &mbst);
		}
#else
		dst_len += wcrtomb_internal(&dst[dst_len], wc);
#endif
	}

	/* Pad to remaining width */
	if (flags & UNI_FLAG_PAD) {
		dst = xrealloc(dst, dst_len + width + 1);
		uni_count += width;
		uni_width += width;
		while ((int)--width >= 0) {
			dst[dst_len++] = ' ';
		}
	}
	dst[dst_len] = '\0';
	if (stats) {
		stats->byte_count = dst_len;
		stats->unicode_count = uni_count;
		stats->unicode_width = uni_width;
	}

	return dst;
}

3、 配置 busybox

根我们编译 Uboot、 Linux kernel 一样,我们要先对 busybox 进行默认的配置,有以下几种
配置选项:
①、 defconfig,缺省配置,也就是默认配置选项。
②、 allyesconfig,全选配置,也就是选中 busybox 的所有功能。
③、 allnoconfig,最小配置。
使用默认配置来配置一下 busybox:

make defconfig

busybox 也支持图形化配置,通过图形化配置我们可以进一步选择自己想要的功能,输入如下命令打开图形化配置界面:

make menuconfig
Location:
-> Settings
-> Build static binary (no shared libs)
Location:
-> Settings
-> vi-style line editing commands
Location:
-> Linux Module Utilities
-> Simplified modutils
Location:
-> Linux System Utilities
> mdev (16 kb) //确保下面的全部选中,默认都是选中的
> 最后就是使能 busybox 的 unicode 编码以支持中文,配置路径如下:
Location:
-> Settings
-> Support Unicode //选中
-> Check $LC_ALL, $LC_CTYPE and $LANG environment variables //选中

4、编译 busybox

配置好 busybox 以后就可以编译了,我们可以指定编译结果的存放目录,我们肯定要将编
译结果存放到前面创建的 rootfs 目录中,输入如下命令:

make
make install CONFIG_PREFIX=/home/这里为你虚拟机的名字/linux/nfs/rootfs

在Linux系统中,启动过程涉及多个关键步骤,其中一个重要的环节是内核(Kernel)加载并最终转交控制权给用户空间的第一个进程,通常是init进程。这个过程中涉及到的几个概念和组件,包括rootfsinitlinuxrcbusybox,我将逐一解释这些概念及它们在启动过程中的作用。

rootfs (根文件系统)

rootfs是Linux启动时挂载的第一个文件系统。它是一个临时的文件系统,直到真正的根文件系统被挂载(通常从一个物理设备上)。在rootfs目录下,你提到的binsbinusr等目录包含了一些基本的二进制程序和系统工具,这些是系统启动初期和运行必需的。

bin 和 sbin 目录

  • bin目录通常包含用户可以运行的基本命令和程序。
  • sbin目录包含系统管理命令,通常只有超级用户(root)可以运行。

usr 目录

usr目录包含用户应用程序和文件,通常是系统安装后额外的应用程序和数据。

linuxrc 文件

linuxrc是一个特殊的脚本或可执行文件,它在某些Linux系统中用于在启动过程中进行初步的系统配置和设置。如果在内核的启动参数(bootargs)中指定了init=/linuxrc,则内核会执行linuxrc作为第一个用户空间程序(即用户空间的init程序)。这个文件通常用于嵌入式系统或特定的环境,其中系统资源有限,需要精简启动过程。

busybox

busybox是一个集成了多个轻量级Unix工具的软件,它在一个单一的可执行文件中提供了许多常见的UNIX工具(如lscatcp等)。在资源有限的嵌入式系统中,busybox可以提供几乎所有标准Linux命令行工具的功能,而占用的空间非常小。

向根文件系统添加 lib 库

1、向 rootfs 的“/lib”目录添加库文件
Linux 中的应用程序一般都是需要动态库的,当然你也可以编译成静态的,但是静态的可执行文件会很大。如果编译为动态的话就需要动态库,所以我们需要向根文件系统中添加动态库。在 rootfs 中创建一个名为“lib”的文件夹,命令如下:

mkdir lib

lib 文件夹创建好了,库文件从哪里来呢? lib 库文件从交叉编译器中获取,前面我们搭建交叉编译环境的时候将交叉编译器存放到了“/usr/local/arm/”目录中。交叉编译器里面有很多的库文件,这些库文件具体是做什么的我们作为初学者肯定不知道,既然我不知道那就简单粗暴的把所有的库文件都放到我们的根文件系统中。
进入如下路径对应的目录:

/usr/local/arm/gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabihf/arm-linuxgnueabihf/libc/lib

此目录下有很多的so(是通配符)和.a 文件,这些就是库文件,将此目录下所有的so*和.a文件都拷贝到 rootfs/lib 目录中, 拷贝命令如下:

cp *so* *.a /home/zuozhongkai/linux/nfs/rootfs/lib/ -d

后面的“-d”表示拷贝符号链接,这里有个比较特殊的库文件: ld-linux-armhf.so.3,是个符号链接,相当于Win
dows 下的快捷方式。会链接到库 ld-2.19-2014.08-1-git.so 上,输入命令“ls ld-linux-armhf.so.3 -l”查看此文件详细信息。
先将 rootfs/lib 中的 ld-linux-armhf.so.3 文件删除掉,命令如下:

rm ld-linux-armhf.so.3

然 后 重 新 进 入 到 /usr/local/arm/gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabihf/armlinux-gnueabihf/libc/lib 目录中,重新拷贝 ld-linux-armhf.so.3,命令如下:

cp ld-linux-armhf.so.3 /home/zuozhongkai/linux/nfs/rootfs/lib/

拷贝完成以后再到 rootfs/lib 目录下查看 ld-linux-armhf.so.3 文件详细信息,
继续进入如下目录中:

/usr/local/arm/gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabihf/arm-linux-gnueabihf/lib

此目录下也有很多的的so和.a 库文件,我们将其也拷贝到 rootfs/lib 目录中,命令如下:

cp *so* *.a /home/zuozhongkai/linux/nfs/rootfs/lib/ -d

2、向 rootfs 的“usr/lib”目录添加库文件
在 rootfs 的 usr 目录下创建一个名为 lib 的目录,将如下目录中的库文件拷贝到 rootfs/usr/lib
目录下:

/usr/local/arm/gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabihf/arm-linux-gnueabihf/libc/usr/lib

将此目录下的 so 和.a 库文件都拷贝到 rootfs/usr/lib 目录中,命令如下:

cp *so* *.a /home/zuozhongkai/linux/nfs/rootfs/usr/lib/ -d

创建其他文件夹

在根文件系统中创建其他文件夹,如 dev、 proc、 mnt、 sys、 tmp 和 root 等

根文件系统初步测试

接下来我们使用测试一下前面创建好的根文件系统 rootfs,测试方法就是使用 NFS 挂载。
uboot 里面的 bootargs 环境变量会设置“root”的值,所以我们将 root 的值改为 NFS 挂载即可。

在 Linux 内核源码里面有相应的文档讲解如何设置,文档为 Documentation/filesystems/nfs/
nfsroot.txt,格式如下:
root=/dev/nfs nfsroot=[<server-ip>:]<root-dir>[,<nfs-options>] ip=<client-ip>:<server-ip>:<gwip>:<netmask>:<hostname>:<device>:<autoconf>:<dns0-ip>:<dns1-ip>
<server-ip>:服务器 IP 地址,也就是存放根文件系统主机的 IP 地址,那就是 Ubuntu 的 IP
地址,比如我的 Ubuntu 主机 IP 地址为 192.168.1.250。
<root-dir>: 根文件系统的存放路径,比如我的就是/home/zuozhongkai/linux/nfs/rootfs。
<nfs-options>: NFS 的其他可选选项,一般不设置。
<client-ip>: 客户端 IP 地址,也就是我们开发板的 IP 地址, Linux 内核启动以后就会使用
此 IP 地址来配置开发板。此地址一定要和 Ubuntu 主机在同一个网段内,并且没有被其他的设
备使用,在 Ubuntu 中使用 ping 命令 ping 一下就知道要设置的 IP 地址有没有被使用,如果不能
ping 通就说明没有被使用,那么就可以设置为开发板的 IP 地址,比如我就可以设置为
192.168.1.251。
<server-ip>: 服务器 IP 地址,前面已经说了。
<gw-ip>: 网关地址,我的就是 192.168.1.1。
<netmask>:子网掩码,我的就是 255.255.255.0。
<hostname>:客户机的名字,一般不设置,此值可以空着。
<device>: 设备名,也就是网卡名,一般是 eth0, eth1….,正点原子的 I.MX6U-ALPHA 开
发板的 ENET2 为 eth0, ENET1 为 eth1。如果你的电脑只有一个网卡,那么基本只能是 eth0。
这里我们使用 ENET2,所以网卡名就是 eth0。
<autoconf>: 自动配置,一般不使用,所以设置为 off。
<dns0-ip>: DNS0 服务器 IP 地址,不使用。
<dns1-ip>: DNS1 服务器 IP 地址,不使用。

根据上面的格式 bootargs 环境变量的 root 值如下:

root=/dev/nfs nfsroot=服务器ip:/home/这里为你虚拟机的名字/linux/nfs/rootfs,proto=tcp rwip=开发板ip:服务器ip:192.168.1.1:255.255.255.0::eth0:off

设置好环境变量,

完善根文件系统

1 创建/etc/init.d/rcS 文件

rcS 是个 shell 脚本, Linux 内核启动以后需要启动一些服务,而 rcS 就是规定启动哪些文件的脚本文件。在 rootfs 中创建/etc/init.d/rcS 文件,然后在 rcS 中输入如下所示内容:

示例代码 38.4.1.1 /etc/init.d/rcS 文件
1 #!/bin/sh
2 3
PATH=/sbin:/bin:/usr/sbin:/usr/bin:$PATH
4 LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/lib:/usr/lib
5 export PATH LD_LIBRARY_PATH
6 mount -a
8 mkdir /dev/pts
9 mount -t devpts devpts /dev/pts
10
11 echo /sbin/mdev > /proc/sys/kernel/hotplug
12 mdev -s

创建好文件/etc/init.d/rcS 以后一定要给其可执行权限!
使用如下命令给予/ec/init.d/rcS 可执行权限:

chmod 777 rcS

2 创建/etc/fstab 文件

在 rootfs 中创建/etc/fstab 文件, fstab 在 Linux 开机以后自动配置哪些需要自动挂载的分区,
格式如下:

<file system> <mount point> <type> <options> <dump> <pass>
<file system>:要挂载的特殊的设备,也可以是块设备,比如/dev/sda 等等。
<mount point>:挂载点。
<type>:文件系统类型,比如 ext2、 ext3、 proc、 romfs、 tmpfs 等等。
<options>:挂载选项,在 Ubuntu 中输入“man mount”命令可以查看具体的选项。一般使
用 defaults,也就是默认选项, defaults 包含了 rw、 suid、 dev、 exec、 auto、 nouser 和 async。
<dump>:为 1 的话表示允许备份,为 0 不备份,一般不备份,因此设置为 0。
<pass>:磁盘检查设置,为 0 表示不检查。根目录‘/’设置为 1,其他的都不能设置为 1,
其他的分区从 2 开始。一般不在 fstab 中挂载根目录,因此这里一般设置为 0。

按照上述格式,在 fstab 文件中输入如下内容:

示例代码 38.4.2.1 /etc/fstab 文件
1 #<file system> <mount point> <type> <options> <dump> <pass>
2 proc           /proc         proc   defaults  0      0
3 tmpfs          /tmp          tmpfs  defaults  0      0
4 sysfs          /sys          sysfs  defaults  0      0

3 创建/etc/inittab 文件

inittab 的详细内容可以参考 busybox 下的文件 examples/inittab。 init 程序会读取/etc/inittab
这个文件, inittab 由若干条指令组成。每条指令的结构都是一样的,由以“:”分隔的 4 个段组
成,格式如下:
:::
:每个指令的标识符,不能重复。但是对于 busybox 的 init 来说, 有着特殊意义。
对于 busybox 而言用来指定启动进程的控制 tty,一般我们将串口或者 LCD 屏幕设置为控
制 tty。
: 对 busybox 来说此项完全没用,所以空着。
:动作,用于指定可能用到的动作。 busybox 支持的动作如表 38.4.3.1 所
示:
在这里插入图片描述
: 具体的动作,比如程序、脚本或命令等
参考 busybox 的 examples/inittab 文件,我们也创建一个/etc/inittab,在里面输入如下内容:

示例代码 38.4.3.1 /etc/inittab 文件
1 #etc/inittab
2 ::sysinit:/etc/init.d/rcS
3 console::askfirst:-/bin/sh
4 ::restart:/sbin/init
5 ::ctrlaltdel:/sbin/reboot
6 ::shutdown:/bin/umount -a -r
7 ::shutdown:/sbin/swapoff -a
posted @ 2024-06-02 20:00  Bathwind_W  阅读(500)  评论(0编辑  收藏  举报