ixm6u-系统移植学习笔记

文章目录

开篇点题

I.MX6U 的裸机开发的学习中, 通过 21 个裸机例程我们掌握了I.MX6U 的常用外设及相应的底层原理。在进行 Linux 驱动开发之前肯定需要先将Linux 系统移植到开发板上去,在移植 Linux之前我们需要先移植一个 bootloader 代码,这个 bootloader 代码用于启动 Linux 内核,bootloader有很多,常用的就是 U-Boot。移植好 U-Boot 以后再移植 Linux 内核,移植完 Linux 内核以后Linux 还不能正常启动,还需要再移植一个根文件系统(rootfs),根文件系统里面包含了一些最常用的命令和文件。所以 U-Boot、Linux kernel 和 rootfs 这三者一起构成了一个完整的 Linux 系统,一个可以正常使用、功能完善的 Linux 系统。

一、U-Boot

1.uboot介绍

Linux 系统要启动就必须需要一个 bootloader 程序,也就说芯片上电以后先运行一段bootloader 程序。这段 bootloader 程序会先初始化 DDR 等外设,然后将 Linux 内核从 flash(NAND,NOR FLASH,SD,MMC 等)拷贝到 DDR 中,最后启动 Linux 内核。当然了,bootloader 的实际工作要复杂的多,但是它最主要的工作就是启动 Linux 内核,bootloader 和 Linux 内核的关系就跟 PC 上的 BIOS 和 Windows 的关系一样,bootloader 就相当于 BIOS。

在Windows中, BIOS是一组固化到计算机内主板上一个ROM芯片上的程序,它保存着计算机最重要的基本输入输出的程序、开机后自检程序和系统自启动程序,它可从CMOS中读写系统设置的具体信息。 其主要功能是为计算机提供最底层的、最直接的硬件设置和控制。此外,BIOS还向作业系统提供一些系统参数。系统硬件的变化是由BIOS隐藏,程序使用BIOS功能而不是直接控制硬件。现代作业系统会忽略BIOS提供的抽象层并直接控制硬件组件。

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

有三种 uboot的区别如表 30.1.1 所示:
在这里插入图片描述

那么这三种 uboot 该如何选择呢?首先 uboot 官方的基本是不会用的,因为支持太弱了。最常用的就是半导体厂商或者开发板厂商的 uboot,如果你用的半导体厂商的评估板,那么就使用半导体厂商的 uboot,如果你是购买的第三方开发板,比如正点原子的 I.MX6ULL 开发板,那么就使用正点原子提供的 uboot 源码(也是在半导体厂商的 uboot 上修改的)。当然了,你也可以在购买了第三方开发板以后使用半导体厂商提供的 uboot,只不过有些外设驱动可能不支持,需要自己移植,这个就是我们常说的 uboot 移植

2.uboot编译

具体步骤可以看正点原子的参考手册。
提醒:uboot使用mfgtool下载用的是USB_OTG这个口,查看串口信息的是USB_TTL口。

2.1 shell脚本编译

操作指令:

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- -j12

这三条命令中 ARCH=arm 设置目标为 arm 架构,CROSS_COMPILE 指定所使用的交叉编译器。第一条命令相当于“make distclean”,目的是清除工程,一般在第一次编译的时候最好清理一下工程。但会导致以前的配置被清楚掉,所以要小心使用。

第二条指令相当于“make mx6ull_14x14_ddr512_emmc_defconfig”,用于配置 uboot,配置文件为 mx6ull_14x14_ddr512_emmc_defconfig。前面说了 uboot 是 bootloader 的一种,可以用来引导Linux,但是 uboot 除了引导 Linux 以外还可以引导其它的系统,而且 uboot 还支持其它的架构和外设,比如 USB、网络、SD 卡等。这些都是可以配置的,需要什么功能就使能什么功能。所以在编译 uboot 之前,一定要根据自己的需求配置 uboot。mx6ull_14x14_ddr512_emmc_defconfig就是正点原子针对 I.MX6U-ALPHA 的 EMMC 核心板编写的配置文件。

最后一条指令相当于 “make V=1 -j12”,V=1( 大写V ) 用于设置编译过程的信息输出级别;-j 用于设置主机使用多少线程编译uboot,也就是使用 12 核来编译 uboot。没有12核也没有关系。

编译完成以后 uboot 源码多了一些文件,其中 u-boot.bin 就是编译出来的 uboot二进制文件。uboot 是个裸机程序,因此需要在其前面加上头部(IVT、DCD 等数据)才能在 I.MX6U上执行, u-boot.imx 文件就是添加头部以后的 u-boot.bin,u-boot.imx 就是我们最终要烧写到开发板中的 uboot 镜像文件。从.bin 到 .imx 是自动完成的。

每次编译 uboot 都要输入一长串命令,为了简单起见,我们可以新建一个 shell 脚本文件mx6ull_alientek_emmc.sh,
执行 ./mx6ull_alientek_emmc.sh, 就是执行上面三行代码。

2.2 修改Makefile

因为 make distclean 会导致以前的配置被清楚掉,直接使用 shell脚本文件不妥,写三行命令太长,所以直接将配置信息写入Makefile中:

ARCH ?= arm 
CROSS_COMPILE ?= arm-linux-gnueabihf-

?= : 表示如果前面没有赋值就使用现在赋值的,已经赋值了就使用之前的

3.uboot 烧写与启动

uboot编译后会生成u-boot.bin 文件。使用imxdownload 生成 boot.imx 文件,将此文件下载到sd卡中,使用sd卡启动。
uboot启动会显示启动信息,具体看 正点原子。

这里有个小插曲,在设置uboot的bootdelay的时候,本来的命令是

=> setenv bootdelay 5
=> saveenv
Saving Environment to MMC...
Writing to MMC(0)... done

而我写成了

=>setenv bootdelay = 5

导致了bootdelay 变成 0了,这里我利用mfgtool重新下载也没用,重新下载SD也没用(因为setenv修改的是 DRAM 中的环境变量值),但是使用sd卡启动后,等timeout后,还是会出现命令行,还是可以直接再次修改。
在这里插入图片描述

4.uboot命令使用:配置细节未写出

uboot的命令只能是在开发板的uboot上使用,不是ubuntu。

4.1 help & ?

“help”或者“?”(英文问号),然后按下回车即可查看当前 uboot 所支持的命令。uboot 是可配置的,需要什么命令就使能什么命令,也可以自定义命令。

输入“help(或?) 命令名”既可以查看命令的详细用法。

4.2 信息查询命令bdinfo、printenv 、version

常用的和信息查询有关的命令有 3 个:bdinfo、printenv 和 version。

①bdinfo - print Board Info structure:此命令用于查看板子信息。可以得出 DRAM 的起始地址和大小、启动参数保存起始地址、波特率、sp(堆栈指针)起始地址等信息。

②printenv: 用于输出环境变量信息。uboot 中的环境变量都是字符串,既然叫做环境变量,那么它的作用就和“变量”一样。用于配置环境,故uboot 中的环境变量是可以修改的。

③version: 用于查看 uboot 的版本号。

4.3 环境变量操作命令setev、saveenv

①setenv :用于修改、新建、删除环境变量的值。如下:

修改:setenv bootdelay 5  
新建: setenv author han // 新建一个环境变量author,author 的值为 han
删除: setenv author  // 要删除一个环境变量只要给这个环境变量赋空值即可

有时候我们修改的环境变量值可能会有空格,比如 bootcmd、bootargs 等,这个时候环境变量值就得用单引号括起来,比如下面修改环境变量 bootargs 的值:

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

其中“console=ttymxc0,115200”、“root=/dev/mmcblk1p2”、“rootwait”和“rw”相当于四组“值”,这四组“值”之间用空格隔开,所以需要使用单引号‘’将其括起来,表示这四组“值”都属于环境变量 bootargs。

②saveenv : 用于保存修改后的环境变量,通常和setenv 搭配使用。如下:

=> setenv bootdelay 5
=> saveenv
Saving Environment to MMC...
Writing to MMC(0)... done

一般环境变量是存放在外部 flash 中的,uboot 启动的时候会将环境变量从 flash 读取到 DRAM 中。所以使用命令 setenv 修改的是 DRAM
中的环境变量值,修改以后要使用 saveenv 命令将修改后的环境变量保存到 flash 中,否则的话uboot 下一次重启会继续使用以前的环境变量值。

使用命令 saveenv 保存修改后的环境变量的话会有保存过程提示信息,根据提示可以看出环境变量保存到了 MMC(0)中,也就是 SD 卡中。因为我们现在将 uboot烧写到了 SD 卡里面,所以会保存到 MMC(0)中。

SD卡 是一种基于闪存技术的可移动存储卡,内部包含了控制器和NAND闪存芯片,提供了完整的存储管理功能。SD就是上面说的外部flash。

4.4 内存(DARM)操作命令md、nm、mm、mw、cp 、cmp

内存操作命令就是用于直接对 DRAM 进行读写操作的,常用的内存操作命令有 md、nm、mm、mw、cp 和 cmp。

①md - memory display
用于显示内存值,格式如下:

md[.b, .w, .l] address [# of objects]

注意:uboot 命令中的数字都是十六进制的!不是十进制的!
比如你想查看以 0X80000000 开始的 20 个字节的内存值,显示格式为.b 的话,应该使用如下所示命令:

md.b 80000000 14  // md.b 中间没有空格
md.w 80000000 a
md.l 80000000 5

②nm- memory modify (constant address)
用于修改指定地址的内存值,命令格式如下:

nm [.b, .w, .l] address

操作如下:

=> nm.b 80000000
80000000: ff ? ee
80000000: ee ? q

80000000 表示现在要修改的内存地址,ff 表示地址 0x80000000 现在的数据,?后面就可以输入要修改后的数据 ee,输入完成以后按下回车,然后再输入‘q’即可退出。

③mm - memory modify (auto-incrementing address)
修改指定地址内存值的,使用 mm 修改内存值的时候地址会自增,而使用命令 nm 的话地址不会自增。

mm [.b, .w, .l] address

操作如下:

=> mm.w 80000000
80000000: ffee ? 1111
80000002: ffff ? 2222
80000004: ffff ? 3333
80000006: ffff ? 4444
80000008: ffff ? q
=> md.l 80000000 2
80000000: 22221111 44443333  

④mw - memory write (fill)
mw 用于使用一个指定的数据填充一段内存,命令格式如下:

mw [.b, .w, .l] address value [count]

mw 命令同样可以以.b、.w 和.l 来指定操作格式,address 表示要填充的内存起始地址,value为要填充的数据,count 是填充的长度,操作如下:

=> mw.l 80000000 abcddcba 1
=> md.w 80000000 2
80000000: dcba abcd 

⑤cp - memory copy
数据拷贝命令,用于将 DRAM 中的数据从一段内存拷贝到另一段内存中,或者把 NorFlash 中的数据拷贝到 DRAM 中,格式如下:

cp [.b, .w, .l] source target count

cp 命令同样可以以.b、.w 和.l 来指定操作格式,source 为源地址,target 为目的地址,count为拷贝的长度。

=> md.l 80000100 1
80000100: ffffffff                               ....
=> md.l 80000000 1
80000000: abcddcba                               ....
=> cp.w 80000000 80000100 2
=> md.l 80000100 1
80000100: abcddcba 

我们使用.w 格式将 0x80000000 处的地址拷贝到 0X80000100 处,长度为 0x2 个内存块(0x1 * 2=4 个字节)

⑥cmp - memory compare
比较命令,用于比较两段内存的数据是否相等,命令格式如下:

cmp [.b, .w, .l] addr1 addr2 count

cmp 命令同样可以以.b、.w 和.l 来指定操作格式,addr1 为第一段内存首地址,addr2 为第二段内存首地址,count 为要比较的长度。

=> md.l 80000000 1
80000000: abcddcba                               ....
=> md.l 80000100 1
80000100: abcddcba                               ....
=> cmp.w 80000000 80000100 2
Total of 2 halfword(s) were the same
=> cmp.w 80000000 80000200 2
halfword at 0x80000000 (0xdcba) != halfword at 0x80000200 (0xffff)
Total of 0 halfword(s) were the same

我们使用.w 格式来比较 0x80000000 和 0X80000100 这两个地址数据是否相等,比较长度为 0x2 个内存块(2 *2=4 个字节)

4.5 网络操作命令ping、dhcp、nfs、tftp>从Ubuntu到DRAM

先配置环境变量,我是电脑连wifi,电脑通过网线直连imx6u板子。配置看正点原子的linux网络配置视频。
在这里插入图片描述插曲:uboot 无法ping成功,ping就重启。
解决:https://blog.csdn.net/lhui_0321/article/details/133961516

①ping - send ICMP ECHO_REQUEST to network host
发板的网络能否使用,是否可以和服务器(Ubuntu 主机)进行通信,通过 ping 命令就可以验证

ping pingAddress
=> ping 192.168.10.200  // 电脑以太网端口地址
FEC1 Waiting for PHY auto negotiation to complete....... done
Using FEC1 device
host 192.168.10.200 is alive

注意:只能在 uboot 中 ping 其他的机器,其他机器不能 ping uboot,因为 uboot 没有对 ping命令做处理,如果用其他的机器 ping uboot 的话会失败!

②dhcp - boot image via network using DHCP/TFTP protocol
dhcp 用于从路由器获取 IP 地址,前提得开发板连接到路由器上的, DHCP 不单单是获取 IP 地址,其还会通过 TFTP 来启动 linux 内核。

dhcp [loadAddress] [[hostIPaddr:]bootfilename]

③nfs - boot image via network using NFS protocol
nfs(Network File System)网络文件系统,我们一般使用 uboot 中的 nfs 命令将 Ubuntu 中的文件下载到开发板的 DRAM 中。

nfs [loadAddress] [[hostIPaddr:]bootfilename]

loadAddress 是要保存的 DRAM 地址,[[hostIPaddr:]bootfilename]是要下载的文件地址。

 nfs 80800000 192.168.10.100:/home/han/linux/nfs/zImage

将Ubuntu(IP:192.168.10.100)下的 /home/han/linux/nfs/zImage 镜像文件下载到开发板 DRAM 的 0x80800000这个地址处。最好使用 md 命令显示出来进行查看比较一下。
插曲:imx6u开发板 使用上述命令报错 ERROR: File lookup fail
解决:https://blog.csdn.net/qq_41709234/article/details/123160029

⑩tftpboot - boot image via network using TFTP protocol
tftp 命令的作用和 nfs 命令一样,都是用于通过网络下载东西到 DRAM 中,只是 tftp 命令使用的 TFTP 协议,Ubuntu 主机作为 TFTP 服务器。

tftpboot [loadAddress] [[hostIPaddr:]bootfilename]

下载zImage操作如下:

tftp 80800000 zImage

不需要输入文件在 Ubuntu 中的完整路径,只需要输入文件名即可,因为在配置tftp 时,就已经将绝对路径:/home/han/linux/tftpboot/ 配置好了,故把需要传输的文件放到这个tftpboot 文件里面就可以了。

4.6 (EMMC 和 SD 卡)mmc操作命令

使用 MMC 来代指 EMMC 和 SD 卡,uboot 中常用于操作 MMC 设备的命令为“mmc”。mmc 是一系列的命令,其后可以跟不同的参数.
在这里插入图片描述

4.6.1 mmc info = mmcinfo 打印当前设备信息

mmc info 命令用于输出当前选中的 mmc info 设备的信息.

=> mmc info
Device: FSL_SDHC
Manufacturer ID: 89
OEM: 303
Name: NCard
Tran Speed: 50000000
Rd Block Len: 512
SD version 3.0
High Capacity: Yes
Capacity: 29.1 GiB
Bus Width: 4-bit
Erase Group Size: 512 Bytes
4.6.2 mmc rescan 扫描设备

mmc rescan 命令用于扫描当前开发板上所有的 MMC 设备,包括 EMMC 和 SD 卡。没有输出信息。

4.6.3 mmc list 列出所有设备

mmc list 命令用于来查看当前开发板一共有几个 MMC 设备。

=> mmc list
FSL_SDHC: 0 (SD)
FSL_SDHC: 1
4.6.4 mmc dev 切换当前设备

mmc dev 命令用于切换当前 MMC 设备。格式如下:

mmc dev [dev] [part]

[dev]用来设置要切换的 MMC 设备号,[part]是分区号。如果不写分区号的话默认为分区 0。

=> mmc dev 1 0 //切换。 0 为 SD 卡,1 为 eMMC
switch to partitions #0, OK
mmc1(part 0) is current device
=> mmc info
Device: FSL_SDHC
Manufacturer ID: 15
OEM: 100
Name: 8GTF4
Tran Speed: 52000000
Rd Block Len: 512
MMC version 4.0
High Capacity: Yes
Capacity: 7.3 GiB
Bus Width: 8-bit
Erase Group Size: 512 KiB
4.6.5 mmc part 查看当前设备分区

有时候 SD 卡或者 EMMC 会有多个分区,可以使用命令“mmc part”来查看其分区。

=> mmc part

Partition Map for MMC device 1  --   Partition Type: DOS

Part    Start Sector    Num Sectors     UUID            Type
  1     20480           262144          d5336ba3-01     0c
  2     282624          14987264        d5336ba3-02     83
=> mmc dev 0
switch to partitions #0, OK
mmc0 is current device
=> mmc part

Partition Map for MMC device 0  --   Partition Type: DOS

Part    Start Sector    Num Sectors     UUID            Type
  1     2048            61065216        a31de155-01     0c

此时 EMMC 有两个分区,第一个分区起始扇区为 20480,长度为 262144 个扇区;第二个分区起始扇区为 282624,长度为 14594048 个扇区。如果 EMMC 里面烧写了 Linux 系统的话,EMMC 是有 3 个分区的,第 0 个分区存放 uboot,第 1 个分区存放Linux 镜像文件和设备树,第 2 个分区存放根文件系统。但是在图 中只有两个分区,那是因为第 0 个分区没有格式化,所以识别不出来,实际上第 0 个分区是存在的。

一个新的 SD卡默认只有一个分区,那就是分区 0,所以前面讲解的 uboot 烧写到 SD 卡,其实就是将 u-boot.bin烧写到了 SD 卡的分区 0 里面。

4.6.6 mmc read 读取mmc数据到DRAM

mmc read 命令用于读取 mmc 设备的数据,命令格式如下:

mmc read addr blk# cnt

addr 是数据读取到 DRAM 中的地址,blk 是要读取的块起始地址(十六进制),一个块是 512字节,这里的块和扇区是一个意思,在 MMC 设备中我们通常说扇区,cnt 是要读取的块数量(十六进制)。比如从 EMMC 的第 1536(0x600)个块开始,读取 16(0x10)个块的数据到 DRAM 的0X80800000 地址处,命令如下:

mmc dev 1 0 //切换到 MMC 分区 0
mmc read 80800000 600 10 //读取数据  

通过 md.b 命令查看 0x80800000 处的数据就行了,查看 16*512=8192(0x2000)个字节的数据,命令如下:

md.b 80800000 2000
4.6.7 mmc write 将DRAM数据写到mmc中

要将数据写到 MMC 设备里面,可以使用命令“mmc write”,格式如下:

mmc write addr blk# cnt

addr 是要写入 MMC 中的数据在 DRAM 中的起始地址,blk 是要写入 MMC 的块起始地址(十六进制),cnt 是要写入的块大小,一个块为 512 字节。我们可以使用命令“mmc write”来升级 uboot,也就是在 uboot 中更新 uboot。这里要用到 nfs 或者 tftp 命令,通过 nfs 或者 tftp 命令将新的 u-boot.bin 下载到开发板的 DRAM 中,然后再使用命令“mmc write”将其写入到 MMC设备中。

Ubuntu中

/home/han/linux/IMX6ULL/uboot/alientek_uboot/./mx6ull_alientek_emmc.sh //更新uboot
 cp u-boot.imx /home/han/linux/tftpboot -f

开发板uboot中

=> tftp 80800000 u-boot.imx
Using FEC1 device
TFTP from server 192.168.10.100; our IP address is 192.168.10.50
Filename 'u-boot.imx'.
Load address: 0x80800000
Loading: #########################
         2.4 MiB/s
done
Bytes transferred = 359424 (57c00 hex)

/*可以看出,u-boot.imx 大小为 359424 字节,359424 /512=702,所以我们要向 SD 卡中写入702个块,如果有小数的话就要加 1 个块。
使用命令“mmc write”从 SD 卡分区 0 第 2 个块(扇区)开始烧写,一共烧写 702(0x2be)个块,命令如下:*/
=> mmc dev 0 0
switch to partitions #0, OK
mmc0 is current device
=> mmc write 80800000 2 2be

MMC write: dev # 0, block # 2, count 702 ... 702 blocks written: OK

烧写成功,重启开发板(从 SD 卡启动),重启(reset)以后再输入 version 来查看版本号.
注意:千万不要写 SD 卡或者 EMMC 的前两个块(扇区),里面保存着分区表!

4.6.8 mmc erase 擦除mmc指定块

如果要擦除 MMC 设备的指定块就是用命令“mmc erase”,命令格式如下:

mmc erase blk# cnt

blk 为要擦除的起始块,cnt 是要擦除的数量。没事不要用 mmc erase 来擦除 MMC 设备!!

4.7 mmc FAT 格式文件操作命令fatinfo、fatls、fstype、fatload 、fatwrite

这些文件操作命令只支持 FAT 格式的文件系统!!
有时候需要在 uboot 中对 SD 卡或者 EMMC 中存储的文件进行操作,这时候就要用到文件操作命令。
①fatinfo - print information about filesystem
fatinfo 命令用于查询指定 MMC 设备分区的文件系统信息,格式如下:

fatinfo <interface> [<dev[:part]>]

nterface 表示接口,比如 mmc,dev 是查询的设备号,part 是要查询的分区。比如我们要查询 EMMC 分区 1 的文件系统信息,命令如下:


=> mmc dev 1
switch to partitions #0, OK
mmc1(part 0) is current device
=> mmc part

Partition Map for MMC device 1  --   Partition Type: DOS

Part    Start Sector    Num Sectors     UUID            Type
  1     20480           262144          d5336ba3-01     0c
  2     282624          14987264        d5336ba3-02     83
=> fatinfo mmc 1:0

** Unable to use mmc 1:0 for fatinfo **
=> fatinfo mmc 1:1
Interface:  MMC
  Device 1: Vendor: Man 000015 Snr e25e59f9 Rev: 0.6 Prod: 8GTF4R
            Type: Removable Hard Disk
            Capacity: 7456.0 MB = 7.2 GB (15269888 x 512)
Filesystem: FAT32 "NO NAME    "
=> fatinfo mmc 1:2

** Unable to use mmc 1:2 for fatinfo **

EMMC 分区 1 的文件系统为 FAT32 格式的,而分区0、2不是,无法使用fatinfo,可以使用fstype命令查看是什么格式的。

②fatls - list files in a directory (default /)
fatls 命令用于查询 FAT 格式设备的目录和文件信息,命令格式如下:

fatls <interface> [<dev[:part]>] [directory]

interface 是要查询的接口,比如 mmc,dev 是要查询的设备号,part 是要查询的分区,directory是要查询的目录。比如查询 EMMC 分区 1 中的所有的目录和文件,输入命令:

=> fatls mmc 1:1
  6785480   zimage
    39459   imx6ull-14x14-emmc-4.3-480x272-c.dtb
    39459   imx6ull-14x14-emmc-4.3-800x480-c.dtb
    39459   imx6ull-14x14-emmc-7-800x480-c.dtb
    39459   imx6ull-14x14-emmc-7-1024x600-c.dtb
    39459   imx6ull-14x14-emmc-10.1-1280x800-c.dtb
    40295   imx6ull-14x14-emmc-hdmi.dtb
    40203   imx6ull-14x14-emmc-vga.dtb

8 file(s), 0 dir(s)

emmc 的分区 1 中存放着 8 个文件。

③fstype - Look up a filesystem type
fstype 用于查看 MMC 设备某个分区的文件系统格式,命令格式如下:

fstype <interface> <dev>:<part>

正点原子 EMMC 核心板上的 EMMC 默认有 3 个分区,我们来查看一下这三个分区的文件系统格式,输入命令:

=> fstype mmc 1:0
Failed to mount ext2 filesystem...
** Unrecognized filesystem type **
=> fstype mmc 1:1
fat
=> fstype mmc 1:2
ext4

分区 0 格式未知,因为分区 0 存放的 uboot,并且分区 0 没有格式化,所以文件系统格式未知。分区 1 的格式为 fat,分区 1 用于存放 linux 镜像和设备树。分区 2 的格式为 ext4,用于存放 Linux 的根文件系统(rootfs)。

④fatload - load binary file from a dos filesystem

fatload 命令用于将指定的文件读取到 DRAM 中,命令格式如下:

fatload <interface> [<dev[:part]> [<addr> [<filename> [bytes [pos]]]]]

nterface 为接口,比如 mmc,dev 是设备号,part 是分区,addr 是保存在 DRAM 中的起始地址,filename 是要读取的文件名字。bytes 表示读取多少字节的数据,如果 bytes 为 0 或者省略的话表示读取整个文件。pos 是要读的文件相对于文件首地址的偏移,如果为 0 或者省略的话表示从文件首地址开始读取。我们将 EMMC 分区 1 中的 zImage 文件读取到 DRAM 中的0X80800000 地址处,命令如下:

fatload mmc 1:1 80800000 zImage

⑤fatwrite - write file into a dos filesystem
uboot 默认没有使能 fatwrite 命令,需要修改板子配置头文件,详见参考手册
fatwirte 命令用于将 DRAM 中的数据写入到 MMC 设备中,命令格式如下:

fatwrite <interface> <dev[:part]> <addr> <filename> <bytes>

interface 为接口,比如 mmc,dev 是设备号,part 是分区,addr 是要写入的数据在 DRAM中的起始地址,filename 是写入的数据文件名字,bytes 表示要写入多少字节的数据。我们可以通过 fatwrite 命令在 uboot 中更新 linux 镜像文件和设备树。

首先将正点原子 I.MX6U-ALPHA 开发板提供的 zImage 镜像文件拷贝到 Ubuntu 中的tftpboot 目录下,拷贝完成以后使用命令 tftp 将 zImage 下载到 DRAM 的 0X80800000 地址处,命令如下:

=> tftp 80800000 zImage
Using FEC1 device
TFTP from server 192.168.10.100; our IP address is 192.168.10.50
Filename 'zImage'.
Load address: 0x80800000
Loading: #################################################################
         #################################################################
         #################################################################
         #################################################################
         #################################################################
         #################################################################
         #################################################################
         ########
         2.1 MiB/s
done
Bytes transferred = 6785480 (6789c8 hex)

zImage 大小为 6785480 (0X6789c8 )个字节,接下来使用命令 fatwrite 将其写入到 EMMC 的分区 1 中,文件名字为 zImage,完成以后使用“fatls”命令查看一下 EMMC 分区 1 里面的文件,命令如下:

=> fatwrite mmc 1:1 80800000 zImage 6789c8
writing zImage
6785480 bytes written
=> fatls mmc 1:1
  6785480   zimage
    39459   imx6ull-14x14-emmc-4.3-480x272-c.dtb
    39459   imx6ull-14x14-emmc-4.3-800x480-c.dtb
    39459   imx6ull-14x14-emmc-7-800x480-c.dtb
    39459   imx6ull-14x14-emmc-7-1024x600-c.dtb
    39459   imx6ull-14x14-emmc-10.1-1280x800-c.dtb
    40295   imx6ull-14x14-emmc-hdmi.dtb
    40203   imx6ull-14x14-emmc-vga.dtb

8 file(s), 0 dir(s)

4.8 EXT 格式文件操作命令ext2load、ext2ls、ext4load、ext4ls 、ext4write

boot 有 ext2 和 ext4 这两种格式的文件系统的操作命令,常用的就四个命令,分别为:ext2load、ext2ls、ext4load、ext4ls 和 ext4write。这些命令的含义和使用与 fatload、fatls 和 fatwrite一样,只是 ext2 和 ext4 都是针对 ext 文件系统的。比如 ext4ls 命令,EMMC 的分区 2 就是 ext4格式的,使用 ext4ls 就可以查询 EMMC 的分区 2 中的文件和目录,输入命令:

=> ext4ls mmc 1:2
<DIR>       4096 .
<DIR>       4096 ..
<DIR>      16384 lost+found
<DIR>       4096 sbin
<DIR>       4096 dev
<DIR>       4096 boot
<DIR>       4096 etc
<DIR>       4096 lib
<DIR>       4096 usr
<DIR>       4096 proc
<SYM>          8 tmp
<DIR>       4096 var
<DIR>       4096 opt
<DIR>       4096 home
<DIR>       4096 run
<DIR>       4096 bin
<DIR>       4096 media
<DIR>       4096 mnt
<DIR>       4096 .cache
<DIR>       4096 sys
<DIR>       4096 .config
<DIR>       4096 .local

4.9 NAND操作命令

emmc开发板,无需此命令,详见参考手册P728 /1957.

4.10 BOOT 操作命令bootz、bootm 、boot

uboot 的本质工作是引导 Linux,所以 uboot 肯定有相关的 boot(引导)命令来启动 Linux。常用的跟 boot 有关的命令有:bootz、bootm 和 boot。

①bootz - boot Linux zImage image from memory

要启动 Linux,需要先将 Linux 镜像文件拷贝到 DRAM 中,如果使用到设备树的话也需要将设备树拷贝到 DRAM 中。可以从 EMMC 或者 NAND 等存储设备中将 Linux 镜像和设备树文件拷贝到 DRAM,也可以通过 nfs 或者 tftp 将 Linux 镜像文件和设备树文件下载到 DRAM 中。不管用那种方法,只要能将 Linux 镜像和设备树文件存到 DRAM 中就行,然后使用 bootz 命令来启动,bootz 命令用于启动 zImage 镜像文件,bootz 命令格式如下:

bootz [addr [initrd[:size]] [fdt]]

命令 bootz 有三个参数,addr 是 Linux 镜像文件在 DRAM 中的位置,initrd 是 initrd 文件在DRAM 中的地址,如果不使用 initrd 的话使用‘-’代替即可,fdt 就是设备树文件在 DRAM 中的地址。

通过 tftp 和 bootz 命令来从网络启动 Linux 系统。

=> tftp 80800000 zImage
Using FEC1 device
TFTP from server 192.168.10.100; our IP address is 192.168.10.50
Filename 'zImage'.
Load address: 0x80800000
Loading: #################################################################
         #################################################################
         #################################################################
         #################################################################
         #################################################################
         #################################################################
         #################################################################
         ########
         2.3 MiB/s
done
Bytes transferred = 6785480 (6789c8 hex)
=> tftp 83000000 imx6ull-14x14-emmc-4.3-800x480-c.dtb
Using FEC1 device
TFTP from server 192.168.10.100; our IP address is 192.168.10.50
Filename 'imx6ull-14x14-emmc-4.3-800x480-c.dtb'.
Load address: 0x83000000
Loading: ###
         917 KiB/s
done
Bytes transferred = 39459 (9a23 hex)
=> bootz 80800000 - 83000000
......

从 EMMC 中启动 Linux 系统的话,先使用命令 fatls 查看要下 EMMC 的分区 1 中有没有 Linux 镜像文件和设备树文件,如果没有的话参考 30.4.6 小节中讲解的 fatwrite 命令将 tftpboot 中的 zImage 和 imx6ull-14x14-emmc-4.3-800x480-c.dtb 文件烧写到 EMMC 的分区 1 中只需要使用命令 fatload 将 zImage 和 imx6ull-14x14-emmc-4.3-800x480-c.dtb 从EMMC 的分区 1 中拷贝到 DRAM 中,然后使用命令 bootz 启动即可。

=> fatls mmc 1:1
  6785480   zimage
    39459   imx6ull-14x14-emmc-4.3-480x272-c.dtb
    39459   imx6ull-14x14-emmc-4.3-800x480-c.dtb
    39459   imx6ull-14x14-emmc-7-800x480-c.dtb
    39459   imx6ull-14x14-emmc-7-1024x600-c.dtb
    39459   imx6ull-14x14-emmc-10.1-1280x800-c.dtb
    40295   imx6ull-14x14-emmc-hdmi.dtb
    40203   imx6ull-14x14-emmc-vga.dtb

8 file(s), 0 dir(s)
fatload mmc 1:1 80800000 zImage
fatload mmc 1:1 83000000 imx6ull-14x14-emmc-4.3-800x480-c.dtb
bootz 80800000 - 83000000

②bootm - boot application image from memory
bootm 和 bootz 功能类似,但是 bootm 用于启动 uImage 镜像文件。还用不到。

③boot - boot default, i.e., = run ‘bootcmd’
boot 命令也是用来启动 Linux 系统的,只是 boot 会读取环境变量 bootcmd 来启动 Linux 系统,bootcmd 是一个很重要的环境变量!其名字分为“boot”和“cmd”,也就是“引导”和“命令”,说明这个环境变量保存着引导命令,其实就是启动的命令集合,具体的引导命令内容是可以修改的。
比如我们要想使用 tftp 命令从网络启动 Linux 那么就可以设置 bootcmd 为“tftp80800000 zImage; tftp 83000000 imx6ull-14x14-emmc-4.3-800x480-c.dtb; bootz 80800000 -83000000”,然后使用 saveenv 将 bootcmd 保存起来。然后直接输入 boot 命令即可从网络启动Linux 系统,命令如下:

=> setenv bootcmd 'tftp 80800000 zImage; tftp 83000000 imx6ull-14x14-emmc-4.3-800x480-c.dtb; bootz 80800000 - 83000000'
=> saveenv
Saving Environment to MMC...
Writing to MMC(0)... done
=> boot

前面说过 uboot 倒计时结束以后就会启动 Linux 系统,其实就是执行的 bootcmd 中的启动命令。只要不修改 bootcmd 中的内容,以后每次开机 uboot 倒计时结束以后都会使用 tftp 命令从网络下载 zImage 和 imx6ull-14x14-emmc-4.3-800x480-c.dtb,然后启动 Linux。

如果想从 EMMC 启动那就设置 bootcmd 为“fatload mmc 1:1 80800000 zImage; fatload mmc1:1 83000000 imx6ull-14x14-emmc-4.3-800x480-c.dtb; bootz 80800000 - 83000000”,然后使用 boot
命令启动即可。

setenv bootcmd 'fatload mmc 1:1 80800000 zImage; fatload mmc 1:1 83000000 imx6ull-14x14-emmc-4.3-800x480-c.dtb; bootz 80800000 - 83000000'
savenev
boot

4.11 其他命令reset、go、run 、mtest

①reset - Perform RESET of the CPU
reset 命令顾名思义就是复位的,输入“reset”即可复位重启。

②go - start application at address ‘addr’
go 命令用于跳到指定的地址处执行应用,命令格式如下:

go addr [arg ...]

addr 是应用在 DRAM 中的首地址,我们可以编译一下裸机例程的实验 13_printf,然后将编译出来的 uart.bin 拷贝到 Ubuntu 中的 tftpboot 文件夹里面,注意,这里要拷贝 uart.bin 文件,不需要在前面添加 IVT 信息,因为 uboot 已经初始化好了 DDR 了。使用 tftp 命令将 uart.bin 下载到开发板 DRAM 的 0X87800000 地址处,因为裸机例程的链接首地址就是 0X87800000,最后使用 go 命令启动 uart.bin 这个应用,命令如下:

tftp 87800000 uart.bin
go 87800000

③run - run commands in an environment variable
un 命令用于运行环境变量中定义的命令,比如可以通过“run bootcmd”来运行 bootcmd 中的启动命令,但是 run 命令最大的作用在于运行我们自定义的环境变量。
创建环境变量 mybootemmc、mybootnet,命令如下:

setenv mybootemmc 'fatload mmc 1:1 80800000 zImage; fatload mmc 1:1 83000000 imx6ull-14x14-emmc-4.3-800x480-c.dtb;bootz 80800000 - 83000000'
setenv mybootnet 'tftp 80800000 zImage; tftp 83000000 imx6ull-14x14-emmc-4.3-800x480-c.dtb;bootz 80800000 - 83000000'
saveenv

run mybootemmc
run mybootenet

④mtest - simple RAM read/write test
mtest 命令是一个简单的内存读写测试命令,可以用来测试自己开发板上的 DDR,命令格式如下:

mtest [start [end [pattern [iterations]]]]

二、U-Boot顶层Makefile

上一章我们详细的讲解了 uboot 的使用方法,其实就是各种命令的使用,学会 uboot 使用以后就可以尝试移植 uboot 到自己的开发板上了,但是在移植之前需要我们得先分析一遍 uboot的启动流程源码,得捋一下 uboot 的启动流程,否则移植的时候都不知道该修改那些文件。本章我们就来分析一下正点原子提供的 uboot 源码,重点是分析 uboot 启动流程,而不是整个 uboot源码,uboot 整个源码非常大,我们只看跟我们关心的部分即可。

1.U-Boot工程目录分析

alientek_uboot中uboot编译后的文件:
在这里插入图片描述

在这里插入图片描述
关于重点需要了解那些文件,详见参考手册。

2.VScode工程创建

alientek_uboot中包含了 uboot 的所有文件,uboot中有些文件是不需要的,需要在“.vscode” 文件夹中的 “settings.json”中进行设置。

{
    "search.exclude": {
        "**/node_modules": true,
        "**/bower_components": true,
        "**/*.o":true,
        "**/*.su":true,
        "**/*.cmd":true,
        "arch/arc":true,
			。。。。。。
    },

    "files.exclude": {
        "**/.git": true,
        "**/.svn": true,
        "**/.hg": true,
        "**/CVS": true,
        "**/.DS_Store": true,
  		。。。。。。
     }
}

其中"search.exclude"里面是需要在搜索结果中排除的文件或者文件夹,"files.exclude"是左侧工程目录中需要排除的文件或者文件夹。true 表示排除,false 表示不排除。

3.U-Boot顶层Makefile分析

在阅读 uboot 源码之前,肯定是要先看一下顶层 Makefile,分析 gcc 版本代码的时候一定是先从顶层 Makefile 开始的,然后再是子 Makefile,这样通过层层分析 Makefile 即可了解整个工程的组织结构。顶层 Makefile 也就是 uboot 根目录下的 Makefile 文件。

具体代码的分析详见参考手册,这里只是需要补充的知识点。

3.1 版本号

3.2 MAKEFLAGS 变量

有两个特殊的变量:“SHELL”和“MAKEFLAGS”,这两个变量除非使用“unexport”声明,否则的话在整个 make 的执行过程中,它们的值始终自动的传递给子 make.

3.3 命令输出

V = 1, 整个命令都会输出。
V = 0,仅输出短版本。
make -s,整个命令都不会输出。

3.4 静默输出>$ (firstword x$(MAKEFLAGS)) 的结果

$(firstword x$(MAKEFLAGS))的结果是xrRs,可能是因为
MAKEFLAGS += -rR --include-dir=$(CURDIR)中的rR
x$(MAKEFLAGS) 中的 x
make -s 中的 s

3.5 设置编译结果输出目录>make O=mydir

在 make 的时候使用“O”来指定输出目录,比如“make O=out”就是设置目标文件输出到 out 目录中。这么做是为了将源文件和编译产生的文件分开,当然也可以不指定 O 参数,不指定的话源文件和编译产生的文件都在同一个目录内,一般我们不指定 O 参数。

3.6 代码检查> make C = 1or2

uboot 支持代码检查,使用命令“make C=1”使能代码检查,检查那些需要重新编译的文件。“make C=2”用于检查所有的源码文件.

3.7 模块编译>make M=dir

在 uboot 中允许单独编译某个模块,使用命令“make M=dir ”即可,旧语法“make SUBDIRS=dir”也是支持的。一般情况下我们不会在 uboot 中编译模块,所以此处会编译 all 这个目标。

3.8 获取主机架构和系统

主机架构: shell uname -m
系统:shell uname -s
最终得 HOSTARCH=x86_64,HOSTOS=linux。

3.9 设置目标架构、交叉编译器和配置文件

编 译 uboot 的 时 候 需 要 设 置 目 标 板 架 构 和 交 叉 编 译 器 ,“ make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf-”就是用于设置 ARCH 和 CROSS_COMPILE,
每次编译 uboot 的时候都要在 make 命令后面设置ARCH 和 CROSS_COMPILE,使用起来很麻烦,可以直接修改顶层 Makefile,在里面加入 ARCH和 CROSS_COMPILE 的定义。

定义变量 KCONFIG_CONFIG,uboot 是可以配置的,这里设置配置文件为.config,.config 默认是没有的,需要使用命令“make xxx_defconfig”对 uboot 进行配置,配置完成以后就会在 uboot 根目录下生成.config。默认情况下.config 和xxx_defconfig 内容是一样的,因为.config 就是从 xxx_defconfig 复制过来的。如果后续自行调整了 uboot 的一些配置参数,那么这些新的配置参数就添加到了.config 中,而不是 xxx_defconfig。相当于 xxx_defconfig 只是一些初始配置,而.config 里面的才是实时有效的配置。

3.10 调用 scripts/Kbuild.include

主 Makefile 会调用文件 scripts/Kbuild.include 这个文件,在 uboot 的编译过程中会用到 scripts/Kbuild.include 中的这些变量。

3.11 交叉编译工具变量设置

上面我们只是设置了 CROSS_COMPILE 的名字,但是交叉编译器其他的工具还需要设置。

3.12 导出其他变量

ARCH CPU BOARD VENDOR SOC CPUDIR BOARDDIR
在 uboot 根目录下有个文件叫做 config.mk,这 7 个变量就是在 config.mk 里面定义的,在config.mk 中需要找到 CONFIG_SYS_ARCH、CONFIG_SYS_CPU、CONFIG_SYS_BOARD、
CONFIG_SYS_VENDOR 和 CONFIG_SYS_SOC 这 5 个变量的值。这 5 个变量在 uboot 根目录下的.config 文件中有定义。

3.13 make xxx_defconfig 过程>配置 uboot

在编译 uboot 之前要使用“make xxx_defconfig”命令来配置 uboot.

config-targets = 1
mixed-targets = 0
dot-config = 1

当输入“make xxx_defconfig”的时候就会匹配到%config 目标,目标“%config”依赖于 scripts_basic、outputmakefile 和 FORCE。

%config: scripts_basic outputmakefile FORCE
	$(Q)$(MAKE) $(build)=scripts/kconfig $@

FORCE 是没有规则和依赖的,所以每次都会重新生成 FORCE。当 FORCE 作为其他目标的依赖时,由于 FORCE 总是被更新过的,因此依赖所在的规则总是会执行的。 而outputmakefile 无效。只有 scripts_basic 是有效的。

FORCE:

scripts_basic:
	$(Q)$(MAKE) $(build)=scripts/basic
	$(Q)rm -f .tmp_quiet_recordmcount

变量 build 是在 scripts/Kbuild.include 文件中有定义,

build=-f ./scripts/Makefile.build obj

scripts_basic 展开以后如下:

scripts_basic:
@make -f ./scripts/Makefile.build obj=scripts/basic //也可以没有@,视配置而定
@rm -f . tmp_quiet_recordmcount //也可以没有@

综上,%config命令展开就是:

%config: scripts_basic outputmakefile FORCE
$(Q)$(MAKE) $(build)=scripts/kconfig $@

@make -f ./scripts/Makefile.build obj=scripts/kconfig xxx_defconfig

也就是说“make xxx_defconfig“配置 uboot 的时候如下两行命令会执行脚本scripts/Makefile.build:

@make -f ./scripts/Makefile.build obj=scripts/basic //scripts_basic 目标对应的命令
@make -f ./scripts/Makefile.build obj=scripts/kconfig xxx_defconfig  //%config 目标对应的命令

3.14 Makefile.build 脚本分析>配置 uboot 命令需要

从上一小节可知,“make xxx_defconfig“配置 uboot 的时候如下两行命令会执行脚本scripts/Makefile.build:

@make -f ./scripts/Makefile.build obj=scripts/basic //scripts_basic 目标对应的命令
@make -f ./scripts/Makefile.build obj=scripts/kconfig xxx_defconfig  //%config 目标对应的命令

scripts_basic 目标的作用就是编译出 scripts/basic/fixdep 这个软件。
%config 目标对应的命令最终生成 uboot 根目录下的.config 文件。

在这里插入图片描述

3.15 make 过程

配置好 uboot 以后就可以直接 make 编译了,因为没有指明目标,所以会使用默认目标,主makefile 中的默认目标为:_all

KBUILD_EXTMOD 为 空,不 编 译 模 块,目标_all 依赖于 all。

all 目标依赖$(ALL-y)。

ALL-y += u-boot.srec u-boot.bin u-boot.sym System.map u-boot.cfg binary_size_check
。。。。。。

ALL-y 里面有个 u-boot.bin,这个就是我们最终需要的 uboot 二进制可执行文件,所作的所有工作就是为了它。

u-boot.bin 依赖于 u-boot-nodtb.bin,if_changed 函数可以从 u-boot-nodtb.bin 生成 u-boot.bin 。

目标 u-boot-nodtb.bin 又依赖于 u-boot。

目标 u-boot 依赖于 u-boot_init、u-boot-main 和 u-boot.lds,u-boot_init 和 u-boot-main 是两个变量,

u-boot-init = arch/arm/cpu/armv7/start.o

u-boot-main 等于所有子目录中 built-in.o 的集合。
在这里插入图片描述
图 31.3.15.2 就是“make”命令的执行流程,关于 uboot 的顶层 Makefile 就分析到这里,重点是“make xxx_defconfig”和“make”这两个命令的执行流程:

make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- mx6ull_14x14_ddr512_emmc_defconfig V=1
make xxx_defconfig:用于配置 uboot,这个命令最主要的目的就是生成.config 文件。
`
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- V=1
make:用于编译 uboot,这个命令的主要工作就是生成二进制的 u-boot.bin 文件和其他的一些与 uboot 有关的文件,比如 u-boot.imx 等等。

三、U-Boot 启动流程详解

上一章我们详细的分析了 uboot 的顶层 Makefile,理清了 uboot 的编译流程。本章我们来详细的分析一下 uboot 的启动流程,理清 uboot 是如何启动的。通过对 uboot 启动流程的梳理,我们就可以掌握一些外设是在哪里被初始化的,这样当我们需要修改这些外设驱动的时候就会心里有数。另外,通过分析 uboot 的启动流程可以了解 Linux 内核是如何被启动的。

1.链接脚本 u-boot.lds 详解

要分析 uboot 的启动流程,首先要找到“入口”,找到第一行程序在哪里。程序的链接是由链接脚本来决定的,所以通过链接脚本可以找到程序的入口。如果没有编译过 uboot 的话链接脚本为 arch/arm/cpu/u-boot.lds。但是这个不是最终使用的链接脚本,最终的链接脚本是在这个链接脚本的基础上生成的。编译一下 uboot,编译完成以后就会在 uboot 根目录下生成 u-boot.lds文件。

u-boot.lds文件中
_start:代码当前入口点
.text :是存放代码的

_start 在文件 arch/arm/lib/vectors.S 中有定义
_start 后面就是中断向量表等

 .text :
 {
  *(.__image_copy_start)
  *(.vectors)
  arch/arm/cpu/armv7/start.o (.text*)
  *(.text*)
 }

.text 中.__image_copy_start
u-boot.map 是 uboot 的映射文件,可以从此文件看到某个文件或者函数链接到了哪个地址,__image_copy_start 为 0X87800000,而.text 的起始地址也是0X87800000。

.text 中vectors 段保存中断向量表
vectors.S 的代码是存在 vectors 段中的,vectors 段的起始地址也是 0X87800000,说明整个 uboot 的起始地址就是 0X87800000.

.text 中arch/arm/cpu/armv7/start.o (.text*)
arch/arm/cpu/armv7/start.s 编译出来的代码放到中断向量表后面

然后,在 u-boot.lds 中有一些跟地址有关的“变量”需要我们注意一下,后面分析 u-boot 源码的时候会用到,这些变量要最终编译完成才能确定的.

变量					数值					   描述
__image_copy_start		0x87800000		uboot 拷贝的首地址
__image_copy_end		0x8784e2f0		uboot 拷贝的结束地址
__rel_dyn_start 		0x8784e2f0		.rel.dyn 段起始地址
__rel_dyn_end			0x87856a90		.rel.dyn 段结束地址
_image_binary_end		0x87856a90		 镜像结束地址
__bss_start 			0x8784e2f0		 .bss 段起始地址
__bss_end 				0x878992fc      .bss 段结束地址

这些"变量”值可以在 u-boot.map 文件中查找,除了__image_copy_start以外,其他的变量值每次编译的时候可能会变化,如果修改了 uboot 代码、修改了 uboot 配置、选用不同的优化等级等等都会影响到这些值。所以,一切以实际值为准!

2.U-Boot 启动流程详解

2.1 reset 函数源码详解

从 u-boot.lds 中我们已经知道了入口点是 arch/arm/lib/vectors.S 文件中的_start,

_start:

#ifdef CONFIG_SYS_DV_NOR_BOOT_CFG
	.word	CONFIG_SYS_DV_NOR_BOOT_CFG
#endif

	b	reset
	ldr	pc, _undefined_instruction
	ldr	pc, _software_interrupt
	ldr	pc, _prefetch_abort
	ldr	pc, _data_abort
	ldr	pc, _not_used
	ldr	pc, _irq
	ldr	pc, _fiq
。。。。。。

reset 函数在 arch/arm/cpu/armv7/start.S 里面,
reset 函数跳转到了 save_boot_params 函数
save_boot_params 函数跳转到 save_boot_params_ret 函数

save_boot_params_ret 函数先完成设置 CPU 处于 SVC32 模式,并且关闭FIQ 和 IRQ 这两个中断。如下:

save_boot_params_ret:
	/*
	 * disable interrupts (FIQ and IRQ), also set the cpu to SVC32 mode,
	 * except if in HYP mode already
	 */
	mrs	r0, cpsr
	and	r1, r0, #0x1f		@ mask mode bits
	teq	r1, #0x1a		@ test for HYP mode
	bicne	r0, r0, #0x1f		@ clear all mode bits
	orrne	r0, r0, #0x13		@ set SVC mode
	orr	r0, r0, #0xc0		@ disable FIQ and IRQ
	msr	cpsr,r0

reset 函数中,清除 SCTLR 寄存器中的 bit13,目的就是为了接下来的向量表重定位。

reset 函数中,调用函数 cpu_init_cp15、cpu_init_crit 和_main
函数 cpu_init_cp15 用来设置 CP15 相关的内容,CP15 协处理器一般用于存储系统管理。
函数 cpu_init_crit 内部仅仅是调用了函数 lowlevel_init。接下来就是详细的分析一下 lowlevel_init 和_main 这两个函数。

2.2 lowlevel_init 函数详解>reset中的cpu_init_crit调用

函数 lowlevel_init 在文件 arch/arm/cpu/armv7/lowlevel_init.S 中定义,
其中部分内容如下:
CONFIG_SYS_INIT_RAM_ADDR = IRAM_BASE_ADDR = 0x00900000。
CONFIG_SYS_INIT_RAM_SIZE = 0x00020000 =128KB。
CONFIG_SYS_INIT_SP_OFFSET = 0x00020000 – 256 = 0x1FF00。
CONFIG_SYS_INIT_SP_ADDR = 0x00900000 + 0X1FF00 = 0X0091FF00。
结果如下:
在这里插入图片描述

然后,如下:
在这里插入图片描述
又调用函数 s_init。

2.3 s_init 函数详解>lowlevel_init 函数调用

lowlevel_init 函数后面会调用 s_init 函数,s_init 函数定义在文件arch/arm/cpu/armv7/mx6/soc.c 中,
对 于I.MX6UL/I.MX6ULL 来说,s_init 就是个空函数。从 s_init 函数退出以后进入函数 lowlevel_init,但是 lowlevel_init 函数也执行完成了,返回到了函数 cpu_init_crit,函数 cpu_init_crit 也执行完成了,最终返回到 save_boot_params_ret
在这里插入图片描述

2.4 _main 函数详解

_main 函数定义在文件 arch/arm/lib/crt0.S 中,
其中调用函数 board_init_f_alloc_reserve,得到内存分布如下:
在这里插入图片描述
后面太多太复杂了,就没仔细看。

uboot 中定义了一个指向 结构体gd_t 的指针 gd,里面是一些环境变量配置参数值。

在_main 函数里面调用了 board_init_f、relocate_code、relocate_vectors 和 board_init_r 这 4 个函数,接下来依次看一下这 4 个函数都是干啥的。

2.5 board_init_f 函数详解>_main函数调用

复杂

main 中会调用 board_init_f 函数,board_init_f 函数主要有两个工作:
①、初始化一系列外设,比如串口、定时器,或者打印一些消息等。
②、初始化 gd 的各个成员变量,uboot 会将自己重定位到 DRAM 最后面的地址区域,也就是将自己拷贝到 DRAM 最后面的内存区域中。这么做的目的是给 Linux 腾出空间,防止 Linux kernel 覆盖掉 uboot,将 DRAM 前面的区域完整的空出来。在拷贝之前肯定要给 uboot 各部分分配好内存位置和大小,比如 gd 应该存放到哪个位置,malloc 内存池应该存放到哪个位置等等。这些信息都保存在 gd 的成员变量中,因此要对 gd 的这些成员变量做初始化。最终形成一个完整的内存“分配图”,在后面重定位 uboot 的时候就会用到这个内存“分配图”。
在这里插入图片描述

2.6 relocate_code 函数详解>_main函数调用

relocate_code 函数是用于代码拷贝的,此函数定义在文件 arch/arm/lib/relocate.S 中,

复杂

一个可执行的 bin 文件,其链接地址和运行地址要相等,也就是链接到哪个地址,在运行之前就要拷贝到哪个地址去。现在我们重定位以后,运行地址就和链接地址不同了,使用了一个第三方偏移地址 ,专业术语叫做 Label。这个第三方偏移地址就是实现重定位后运行不会出错的重要原因!

2.7 relocate_vectors 函数详解>_main函数调用

函数 relocate_vectors 用于重定位向量表,此函数定义在文件 relocate.S 中。

2.8 board_init_r 函数详解>_main函数调用

board_init_f 函数里面会调用一系列的函数来初始化一些外设和 gd 的成员变量。但是 board_init_f 并没有初始化所有的外设,还需要做一些后续工作,这些后续工作就是由函数 board_init_r 来完成的,board_init_r 函数定义在文件 common/board_r.c中,

调用 initcall_run_list 函数来执行初始化序列 init_sequence_r,init_sequence_r 是一个函数集合,也是众多初始化函数。

最后调用run_main_loop,主循环,处理命令。

2.9 run_main_loop 函数详解>board_init_r 函数调用

uboot 启动以后会进入 3 秒倒计时,如果在 3 秒倒计时结束之前按下按下回车键,那么就会进入 uboot 的命令模式,如果倒计时结束以后都没有按下回车键,那么就会自动启动 Linux 内核 , 这 个 功 能 就 是 由 run_main_loop 函 数 来 完 成 的 。 run_main_loop 函数定在文件common/board_r.c 中,

run_main_loop函数中有main_loop()函数

main_loop()函数中有autoboot_command 函数,此函数就是检查倒计时是否结束?倒计时结束之前有没有被打断?此函数定义在文件 common/autoboot.c 中。

autoboot_command 函数精简版本
void autoboot_command(const char *s)
{
	if (stored_bootdelay != -1 && s && !abortboot(stored_bootdelay)) {
		run_command_list(s, -1, 0);
	}
}

如果倒计时自然结束那么就执行函数run_command_list,此函数会执行参数 s 指定的一系列命令,也就是环境变量 bootcmd 的命令,bootcmd 里面保存着默认的启动命令,因此 linux 内核启动!这个就是 uboot 中倒计时结束以后自动启动 linux 内核的原理。如果倒计时结束之前按下了键盘上的按键,那么 run_command_list函数就不会执行,相当于 autoboot_command 是个空函数。

main_loop()函数中有cli_loop();
如果倒计时结束之前按下按键,那么就会执行 cli_loop 函数(不管按不按都会执行吧),这个就是命令处理函数,负责接收好处理输入的命令。

2.10 cli_loop 函数详解>run_main_loop中的main_loop()函数调用

li_loop 函数是 uboot 的命令行处理函数,我们在 uboot 中输入各种命令,进行各种操作就是有 cli_loop 来处理的,此函数定义在文件 common/cli.c 中,

复杂,最后调用 cmd_process 函数。

2.11 cmd_process 函数详解> cli_loop 函数调用

在学习 cmd_process 之前先看一下 uboot 中命令是如何定义的。uboot 使用宏 U_BOOT_CMD来定义命令,宏 U_BOOT_CMD 定义在文件 include/command.h 中,

可 以 看 出 U_BOOT_CMD 是 U_BOOT_CMD_COMPLETE 的 特 例 , 将U_BOOT_CMD_COMPLETE 的 最 后 一 个 参 数 设 置 成 NULL 就 是 U_BOOT_CMD 。

宏 U_BOOT_CMD_COMPLETE 又 用 到 了 ll_entry_declare 和U_BOOT_CMD_MKENT_COMPLETE。

U_BOOT_CMD_MKENT_COMPLETE 又用到了宏_CMD_HELP 和_CMD_COMPLETE

总结一下,uboot 中使用 U_BOOT_CMD 来定义一个命令,最终的目的就是为了定义一个cmd_tbl_t 类型的变量,并初始化这个变量的各个成员。uboot 中的每个命令都存储在.u_boot_list段中,每个命令都有一个名为 do_xxx(xxx 为具体的命令名)的函数,这个 do_xxx 函数就是具体的命令处理函数。

cmd_process函数定义在文件 common/command.c 中,
其中,调用函数 find_cmd (cmd)在命令表中找到指定的命令,参数 cmd 就是所查找的命令名字,uboot 中的命令表其实就是 cmd_tbl_t 结构体数组,通过函数 ll_entry_start 得到数组的第一个元素,也就是命令表起始地址。通过函数 ll_entry_count得到数组长度,也就是命令表的长度。最终通过函数 find_cmd_tbl 在命令表中找到所需的命令,每个命令都有一个 name 成员,所以将参数 cmd 与命令表中每个成员的 name 字段都对比一下,如果相等的话就说明找到了这个命令,找到以后就返回这个命令。

找到命令以后肯定就要执行这个命令了,调用函数 cmd_call 来执行具体的命令。
cmd_tbl_t 的 cmd 成员就是具体的命令处理函数,所以调用 cmdtp 的 cmd 成员来处理具体的命令,返回值为命令的执行结果。cmd_process 中会检测 cmd_tbl 的返回值,如果返回值为 CMD_RET_USAGE 的话就会调用cmd_usage 函数输出命令的用法,其实就是输出 cmd_tbl_t 的 usage 成员变量。

3.bootz 启动 Linux 内核过程

3.1 images 全局变量

不管是 bootz 还是 bootm 命令,在启动 Linux 内核的时候都会用到一个重要的全局变量:images。

images 是 bootm_headers_t 类型的全局变量,bootm_headers_t 是个 boot 头结构体,在文件include/image.h 中。

bootm_headers_t 结构体中,os 成员变量是 image_info_t 类型的,为系统镜像信息。结构体image_info_t,也就是系统镜像信息结构体, 此结构体在文件include/image.h 中。

3.2 do_bootz 函数

bootz 命令的执行函数为 do_bootz,在文件 cmd/bootm.c 中,
其中调用 bootz_start 函数。
调用函数 do_bootm_states 来执行不同的 BOOT 阶段。

3.3 bootz_start 函数>do_bootz 函数调用

bootz_srart 函数也定义在文件 cmd/bootm.c 中。
其中,调用函数 do_bootm_states,执行 BOOTM_STATE_START 阶段。
调用 bootz_setup 函数。
调用函数 bootm_find_images 查找 ramdisk 和设备树(dtb)文件

3.4 do_bootm_states 函数>do_bootz 函数调用

其中,调用函数 bootm_os_get_boot_func 来查找系统启动函数。参数 images->os.os 就是系统类型,images.os.os= IH_OS_LINUX,函数返回值就是找到的系统启动函数,这里找到的 Linux 系统启动函数为 do_bootm_linux。

do_bootm_states 会调用 bootm_os_get_boot_func 来查找对应系统的启动函数。

3.5 bootm_os_get_boot_func 函数>do_bootm_states 函数调用

do_bootm_states 会调用 bootm_os_get_boot_func 来查找对应系统的启动函数,此函数定义在文件 common/bootm_os.c 中。

返回boot_os[os],boot_os[]数组中[IH_OS_LINUX] = do_bootm_linux,就是 Linux 系统对应的启动函数:do_bootm_linux。

3.6 do_bootm_linux 函数>bootm_os_get_boot_func 函数调用

do_bootm_linux 就是最终启动 Linux 内核的函数,此函数定义在文件 arch/arm/lib/bootm.c

其中调用函数 boot_jump_linux,boot_jump_linux函数调用函数 kernel_entry,看名字“内核_进入”,说明此函数是进入 Linux 内核的,也就是最终的大 boos!!

在这里插入图片描述

四、U-Boot移植

前面都是使用的正点原子提供的 uboot,本章我们就来学习如何将 NXP 官方的 uboot 移植到正点原子的 I.MX6ULL 开发板上,学习如何在 uboot 中添加我们自己的板子。

1.NXP 官方开发板 uboot 编译测试

1.1 查找 NXP 官方的开发板默认配置文件

uboot 移植的一般流程:
①、在 uboot 中找到参考的开发平台,一般是原厂的开发板。
②、参考原厂开发板移植 uboot 到我们所使用的开发板上。
正点原子的 I.MX6ULL 开发板参考的是 NXP 官方的 I.MX6ULL EVK 开发板做的硬件,因此我们在移植 uboot 的时候就可以以 NXP 官方的 I.MX6ULL EVK 开发板为蓝本。
使用 mx6ull_14x14_evk_emmc_defconfig 作为默认配置文件。

1.2 编译 NXP 官方开发板对应的 uboot

找到 NXP 官方 I.MX6ULL EVK 开发板对应的默认配置文件以后就可以编译一下,使用如下命令编译 uboot:

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

跟之前一样要么在 顶 层 Makefile 中 直 接 给 ARCH 和CORSS_COMPILE 赋值,要么写一个shell脚本。
Makefile 赋值如下:

ARCH ?= arm
CROSS_COMPILE ?= arm-linux-gnueabihf-

shell 脚本名为 mx6ull_14x14_evk_emmc.sh:

  1 #!/bin/bash
  2 make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- distclean
  3 make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- mx6ull_14x14_evk_emmc_defconfig
  4 make V=1 ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf-

1.3 烧写验证与驱动测试

将 imxdownload 软件拷贝到 uboot 源码根目录下,然后使用 imxdownload 软件将 u-boot.bin烧写到 SD 卡中。(这个imxdownload至今还不清楚里面是什么?)

总结一下 NXP 官方 I.MX6ULL EVK 开发板的 uboot 在正点原子 EMMC 版本 I.MX6ULL开发板上的运行情况:
①、uboot 启动正常,DRAM 识别正确,SD 卡和 EMMC 驱动正常。
②、uboot 里面的 LCD 驱动默认是给 4.3 寸 480x272 分辨率的,如果使用的其他分辨率的屏幕需要修改驱动。
③、网络不能工作,识别不出来网络信息,需要修改驱动。

接下来我们要做的工作如下:
①、前面我们一直使用着 NXP 官方开发板的 uboot 配置,接下来需要在 uboot 中添加我们自己的开发板,也就是正点原子的 I.MX6ULL 开发板。
②、解决 LCD 驱动和网络驱动的问题。

2.在 U-Boot 中添加自己的开发板

NXP 官方 uboot 中默认都是 NXP 自己的开发板,虽说我们可以直接在官方的开发板上直接修改(即文件名称都是NXP的,但里面的内容做了修改,是我们自己的),使 uboot 可以完整的运行在我们的板子上。但是从学习的角度来讲,这样我们需要了解到 uboot 是如何添加新平台的。

具体各个文件里面的内容修改详见正点原子参考手册。

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

先在 configs 目录下创建默认配置文件,复制 mx6ull_14x14_evk_emmc_defconfig,然后重命名为 mx6ull_alientek_emmc_defconfig,因为正点原子的开发板就是根据NXP 的 evk 修改的。

2.2 添加开发板对应的头文件>mx6ull_alientek_emmc.h

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

mx6ull_alientek_emmc.h 里面有很多宏定义,这些宏定义基本用于配置 uboot,也有一些I.MX6ULL 的 配 置 项 目 。 如 果 我 们 自 己 要 想 使 能 或 者 禁 止 uboot 的 某 些 功 能 , 那 就 在mx6ull_alientek_emmc.h 里面做修改即可。

2.3 添加开发板对应的板级文件夹>board/freescale/mx6ull_alientek_emmc

uboot 中每个板子都有一个对应的文件夹来存放板级文件,比如开发板上外设驱动文件等等。NXP 的 I.MX 系列芯片的所有板级文件夹都存放在 board/freescale 目录下,在这个目录下有个名为 mx6ullevk 的文件夹,这个文件夹就是 NXP 官方 I.MX6ULL EVK 开发板的板级文件夹。复制 mx6ullevk,将其重命名为 mx6ull_alientek_emmc.

2.4 修改 U-Boot 图形界面配置文件>完成新开发板的添加

uboot 是支持图形界面配置。修改文件arch/arm/cpu/armv7/mx6/Kconfig.

添加如下:

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

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

source "board/freescale/mx6ull_alientek_emmc/Kconfig"

到此为止,I.MX6U-ALPHA 开发板就已经添加到 uboot 中了,接下来就是编译这个新添加的开发板。

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

在 uboot 根目录下新建一个名为 mx6ull_alientek_emmc.sh 的 shell 脚本,和之前两个shell脚本一样,只是xxx_defconfig 改变了。

./mx6ull_alientek_emmc.sh //运行脚本编译 uboot
./imxdownload u-boot.bin /dev/sdb  //下载到SD卡中

2.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 开发板一致,也就是 LCD 的 IO 和背光 IO 都一样的,所以 IO 部分就不用修改了。需要修改的之后 LCD 参数,打开文件 mx6ull_alientek_emmc.c,我的LCD 800x480配置如下:但是白屏显示。

		.name			= "TFT4384",
		.xres           = 800,
		.yres           = 480,
		.pixclock       = 76923,
		.left_margin    = 88,
		.right_margin   = 40,
		.upper_margin   = 32,
		.lower_margin   = 13,
		.hsync_len      = 48,
		.vsync_len      = 3,
		.sync           = 0,

2.7 V2.4 及以后版本底板网络驱动修改

2.7.1 I.MX6U开发板网络简介>内部 MAC+外部 PHY 芯片

I.MX6UL/ULL 内部有个以太网 MAC 外设,也就是 ENET,需要外接一个 PHY 芯片来实现网络通信功能,也就是内部 MAC+外部 PHY 芯片的方案。正点原子的 I.MX6U网络接口ENET2 都使用SR8201F 作为 PHY 芯片。NXP 官方的I.MX6ULL EVK 开发板使用 KSZ8081 这颗 PHY 芯片.正点原子 I.MX6U开发板的 ENET1 引脚与 NXP 官方的 I.MX6ULL EVK 开发板基本一样,唯独复位引脚不同.

SR8201F 内部是有寄存器的,I.MX6ULL 会读取 SR8201F 内部寄存器来判断当前的物理链接状态、连接速度(10M 还是 100M)和双工状态(半双工还是全双工)。I.MX6ULL 通过 MDIO 接口来读取 PHY 芯片的内部寄存器,MDIO 接口有两个引脚,ENET_MDC 和 ENET_MDIO,ENET_MDC 提供时钟,ENET_MDIO 进行数据传输。一个 MDIO 接口可以管理 32 个 PHY 芯片,同一个 MDIO 接口下的这些 PHY 使用不同的器件地址来做区分,MIDO 接口通过不同的器件地址即可访问到相应的 PHY 芯片。I.MX6U-ALPHA 开发板 ENET1 上连接的 SR8201F 器件地址为 0X2,所示我们要修改 ENET2 网络驱动的话重点就三点:

①、ENET2 复位引脚初始化(SNVS_TAMPER8)。
②、SR8201F 的器件 ID(0X2)。
③、SR8201F 驱动
2.7.2 PHY 地址和驱动>网络 PHY 地址修改

首先修改 uboot 中的 ENET1 和 ENET2 的 PHY 地址和驱动.

由于开发板上 SR8201F 的 PHY 地址和默认的配置一样,因此只需要使能REALTEK 公司的 PHY 驱动,将 CONFIG_PHY_MICREL 改为 CONFIG_PHY_REALTEK,但我不知道这个修改体现在哪里,还是说只是修改一个名字而已?
难道是因为IEEE规定了标准的原因。

2.7.3 ENET2 复位引脚初始化>删除 uboot 中 74LV595 的驱动代码

NXP 官方I.MX6ULL EVK 开发板使用 74LV595 来扩展 IO,两个网络的复位引脚就是由 74LV595 来控制的。正点原子的 I.MX6U-ALPHA 开发板并没有使用 74LV595,因此我们将示例代码 33.2.8.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。

2.7.4 添加 I.MX6U 开发板网络复位引脚驱动

结构体数组 fec1_pads 和 fec2_pads 是 ENET1 和 ENET2 这两个网口的 IO 配置参数,在这两个数组中添加两个网口的复位 IO 配置参数。

函数 setup_iomux_fec 就是根据 fec1_pads 和 fec2_pads 这两个网络 IO 配置数组来初始化I.MX6ULL 的网络 IO。我们需要在其中添加网络复位 IO 的初始化代码,并且复位一下 PHY 芯片。

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

然后设置网络环境变量

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

使用ping命令测试。

=>ping 192.168.10.100
Using FEC1 device
host 192.168.10.100 is alive
=> ping 192.168.10.200
Using FEC1 device
host 192.168.10.200 is alive

2.8 其他需要修改的地方>uboot 启动信息Board名称

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

3.bootcmd 和 bootargs 环境变量

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

3.1 环境变量 bootcmd

bootcmd 保存着 uboot 默认命令,uboot 倒计时结束以后就会执行 bootcmd 中的命令。这些命令一般都是用来启动 Linux 内核的。

如果 EMMC 或者 NAND 中没有保存 bootcmd 的值,那么 uboot 就会使用默认的值,板子第一次运行 uboot 的时候都会使用默认值来设置 bootcmd 环境变量,在文件 include/env_default.h中。
env_default.h中,bootcmd 的默认值就是CONFIG_BOOTCOMMAND , bootargs 的 默 认 值 就是 CONFIG_BOOTARGS 。 我 们 可 以 在mx6ull_alientek_emmc.h 文件中通过设置宏 CONFIG_BOOTCOMMAND 来设置 bootcmd 的默认值,如下:

#define CONFIG_BOOTCOMMAND \
	   "run findfdt;" \
	   "mmc dev ${mmcdev};" \
	   "mmc dev ${mmcdev}; if mmc rescan; then " \
		   "if run loadbootscript; then " \
			   "run bootscript; " \
		   "else " \
			   "if run loadimage; then " \
				   "run mmcboot; " \
			   "else run netboot; " \
			   "fi; " \
		   "fi; " \
	   "else run netboot; fi"

最后会运行环境变量 mmcboot。
经过分析,浓缩出来的仅仅是 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 就可简化为:

#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 中的,命令如下:

setenv bootcmd 'mmc dev 1; fatload mmc 1:1 80800000 zImage; fatload mmc 1:1 83000000 imx6ull-alientek-emmc.dtb; bootz 80800000 - 83000000;'

3.2 环境变量 bootargs

bootargs 保存着 uboot 传递给 Linux 内核的参数,在上一小节讲解 bootcmd 的时候说过(我跳过了),bootargs 环境变量是由 mmcargs 设置的。如下:

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

可以看出环境变量 mmcargs 就是设置 bootargs 的值为“console= ttymxc0, 115200 root=/dev/mmcblk1p2 rootwait rw”,bootargs 就是设置了很多的参数的值,这些参数 Linux 内核会使用到,常用的参数有:

①console= ttymxc0, 115200
onsole 用来设置 linux 终端(或者叫控制台),也就是通过什么设备来和 Linux 进行交互,是串口还是 LCD 屏幕?如果是串口的话应该是串口几等等。一般设置串口作为 Linux 终端,这我们就可以在电脑上通过 SecureCRT 来和 linux 交互了。这里设置 console 为 ttymxc0,因为 linux、启动以后 I.MX6ULL 的串口 1 在 linux 下的设备文件就是/dev/ttymxc0,在 Linux 下,一切皆文件。

ttymxc0 后面有个“,115200”,这是设置串口的波特率,console=ttymxc0,115200 综合起来就是设置ttymxc0(也就是串口 1)作为 Linux 的终端,并且串口波特率设置为 115200。

②root=/dev/mmcblk1p2 rootwait rw
root 用来设置根文件系统的位置,root=/dev/mmcblk1p2 用于指明根文件系统存放在mmcblk1 设备的分区 2 中。EMMC 版本的核心板启动 linux 以后会存在/dev/mmcblk0、/dev/mmcblk1、/dev/mmcblk0p1、/dev/mmcblk0p2、/dev/mmcblk1p1 和/dev/mmcblk1p2 这样的文件,其中/dev/mmcblkx(x=0~ n)表示 mmc 设备,而/dev/mmcblkxpy(x=0~ n,y=1~ n)表示 mmc 设备x 的分区 y。在 I.MX6U-ALPHA 开发板中/dev/mmcblk1 表示 EMMC,而/dev/mmcblk1p2 表示EMMC 的分区 2。

root 后面有“rootwait rw”,rootwait 表示等待 mmc 设备初始化完成以后再挂载,否则的话mmc 设备还没初始化完成就挂载根文件系统会出错的。rw 表示根文件系统是可以读写的,不加rw 的话可能无法在根文件系统中进行写操作,只能进行读操作。

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

4.uboot 启动 Linux 测试

uboot 已经移植好了,bootcmd 和 bootargs 这两个重要的环境变量也讲解了,接下来就要测试一下 uboot 能不能完成它的工作:启动 Linux 内核。我们测试两种启动 Linux 内核的方法,一种是直接从 EMMC 启动,一种是从网络启动。

4.1 从 EMMC 启动 Linux 系统

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

=> ls mmc 1:1
  6785480   zimage
    39459   imx6ull-14x14-emmc-4.3-480x272-c.dtb
    39459   imx6ull-14x14-emmc-4.3-800x480-c.dtb
    39459   imx6ull-14x14-emmc-7-800x480-c.dtb
    39459   imx6ull-14x14-emmc-7-1024x600-c.dtb
    39459   imx6ull-14x14-emmc-10.1-1280x800-c.dtb
    40295   imx6ull-14x14-emmc-hdmi.dtb
    40203   imx6ull-14x14-emmc-vga.dtb

8 file(s), 0 dir(s)

此时 EMMC 分区 1 中存在 zimage 和 imx6ull-14x14-emmc-4.3-800x480-c.dtb这两个文件,所以我们可以测试新移植的 uboot 能不能启动 linux 内核。设置 bootargs 和 bootcmd这两个环境变量,设置如下:

setenv bootargs 'console=ttymxc0,115200 root=/dev/mmcblk1p2 rootwait rw'
setenv bootcmd 'mmc dev 1;
saveenv

设置好以后直接输入 boot,或者 run bootcmd 即可启动 Linux 内核。

4.2 从网络启动 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 目录中.

设置 bootargs 和 bootcmd 这两个环境变量,设置如下:

setenv bootargs 'console=ttymxc0,115200 root=/dev/mmcblk1p2 rootwait rw'
setenv bootcmd 'tftp 80800000 zImage; tftp 83000000 imx6ull-14x14-emmc-4.7-800x480-c.dtb; bootz 80800000 - 83000000;'
saveenv

设置好以后直接输入 boot,或者 run bootcmd 即可启动 Linux 内核

5.U-Boot DDR初始化

5.1 裸机下

imxdownload 软件下载,会在 bin 文件头部添加 IVT DCD 数据。

5.2 uboot下

uboot 编译生成 u-boot.imx。u-boot.ix已经包含了 IT DCD 数据。

u-boot.imx 的头部信息是怎么添加的?
u-boot.imx的 DCD 中的 DDR 初始化代码该怎么修改?
uboot 编译会输出

/tools/mkimage   -n  board/freescale/mx6ull_alientek_emmc/imximage.cfg.cfgtmp -Timximage -e 0x87800000 -d u-boot.bin u-boot.imx.

可以看出uboot使用/tools/mkimage 工具,向u-boot.bin 添加
board/freescale/mx6ull_alientek_emmc/imximage.cfg.cfgtmp 文件信息,从而得到 u-boot.imx。

默认只有 imximage.cfg文件,iimximage.cfg.cfgtmp 文件由它生成。mximage.cfg,里面保存的就是 DCD 数据。DDR 初始化也此文件里面。

我们要修改 DDR 初始化代码,就需要修改 imximage.cfg,文件。此文件默认拷贝的 NXP给 IMX6ULLEVK开发板写的,默认是给512MB DDR3L写的。

DDR的校准操作和裸机中的一致。

6.uboot 移植过程总结

①、不管是购买的开发板还是自己做的开发板,基本都是参考半导体厂商的 dmeo 板,而半导体厂商会在他们自己的开发板上移植好 uboot、linux kernel 和 rootfs 等,最终制作好 BSP包提供给用户。我们可以在官方提供的 BSP 包的基础上添加我们的板子,也就是俗称的移植。

②、我们购买的开发板或者自己做的板子一般都不会原封不动的照抄半导体厂商的 demo板,都会根据实际的情况来做修改,既然有修改就必然涉及到 uboot 下驱动的移植。

③、一般 uboot 中需要解决串口、NAND、EMMC 或 SD 卡、网络和 LCD 驱动,因为 uboot的主要目的就是启动 Linux 内核,所以不需要考虑太多的外设驱动。

④、在 uboot 中添加自己的板子信息,根据自己板子的实际情况来修改 uboot 中的驱动。

五、U-Boot 图形化配置及其原理

uboot 可以通过 mx6ull_alientek_emmc_defconfig 来配置,或者通过文件 mx6ull_alientek_emmc.h 来配置 uboot。还有另外一种配置 uboot 的方法,就是图形化配置。

1. U-Boot 图形化配置>来自.config,回至.config

uboot 或 Linux 内核可以通过输入“make menuconfig”来打开图形化配置界面,menuconfig是一套图形化的配置工具,需要 ncurses 库支持。

通过键盘上的“↑”和“↓”键来选择要配置的菜单,按下“Enter”键进入子菜单。菜单中高亮的字母就是此菜单的热键,在键盘上按下此高亮字母对应的键可以快速选中对应的菜单。选中子菜单以后按下“Y”键就会将相应的代码编译进 Uboot 中,菜单前面变为“< * >”。按下“N”键不编译相应的代码,按下“M”键就会将相应的代码编译为模块,菜单前面变为“< M >”。按两下“Esc”键退出,也就是返回到上一级,按下“?”键查看此菜单的帮助信息,按下“/”键打开搜索框,可以在搜索框输入要搜索的内容。

在配置界面下方会有五个按钮,这五个按钮的功能如下:
:选中按钮,和“Enter”键的功能相同,负责选中并进入某个菜单。
:退出按钮,和按两下“Esc”键功能相同,退出当前菜单,返回到上一级。
:帮助按钮,查看选中菜单的帮助信息。
:保存按钮,保存修改后的配置文件。
:加载按钮,加载指定的配置文件。

细心的朋友应该会发现,在 mx6ull_alientek_emmc.h 里面我们配置使能了 dhcp 和 ping 命令,但是在图 34.1.3 中 dhcp 和 ping 前面的“[ ]”并不是“[ * ]”,也就是说不编译 dhcp 和 ping命令,这不是冲突了吗?实际情况是 dhcp 和 ping 命令是会编译的。之所以在图 34.1.3 中没有体现出来时因为我们是直接在 mx6ull_alientek_emmc.h 中定义的宏 CONFIG_CMD_PING 和CONFIG_CMD_DHCP,而 menuconfig 是通过读取.config 文件来判断使能了哪些功能,.config里面并没有宏 CONFIG_CMD_PING 和 CONFIG_CMD_DHCP,所以 menuconfig 就会识别出错。

2.menuconfig 图形化配置原理

2.1 make menuconfig 过程分析

当输入 make menuconfig 以后会匹配到顶层 Makefile 的如下代码

%config: scripts_basic outputmakefile FORCE
		 $(Q)$(MAKE) $(build)=scripts/kconfig $
		 //展开如下:
		 @ make -f ./scripts/Makefile.build obj=scripts/kconfig menuconfig

Makefile.build 会读取 scripts/kconfig/Makefile 中的内容
扩展以后就是:

menuconfig: scripts/kconfig/mconf
	scripts/kconfig/mconf Kconfi

目标 menuconfig 依赖 scripts/kconfig/mconf,因此 scripts/kconfig/mconf.c 这个文件会被编译,生成 mconf 这个可执行文件。目标 menuconfig 对应的规则为 scripts/kconfig/mconf Kconfig,也就是说 mconf 会调用 uboot 根目录下的 Kconfig 文件开始构建图形配置界面。

2.2 Kconfig 语法简介>配置.config 文件

最主要的是记住:menuconfig 是通过读取.config 文件来判断使能了哪些功能,Kconfig 文件的最终目的就是在.config 文件中生成以“CONFIG_”开头的变量。
因为后面学习 Linux 驱动开发的时候可能会涉及到修改 Kconfig,对于 Kconfig 语法我们不需要太深入的去研究,打开 uboot 根目录下的 Kconfig,这个 Kconfig 文件就是顶层 Kconfig。

2.2.1 mainmenu

mainmenu 就是主菜单,也就是输入“make menuconfig”以后打开的默认界面,在顶层 Kconfig 中有如下代码:

mainmenu "U-Boot $UBOOTVERSION Configuration"

上述代码就是定义了一个名为“U-Boot $UBOOTVERSION Configuration”的主菜单,其中UBOOTVERSION=2016.03,因此主菜单名为“U-Boot 2016.03 Configuration”。

2.2.2 source = 调用其他目录下的 Kconfig 文件

和 makefile 一样,Kconfig 也可以调用其他子目录中的 Kconfig 文件,调用方法如下:

source "xxx/Kconfig" //xxx 为具体的目录名,相对路径

顶层 Kconfig 文件调用了很多其他子目录下的 Kcofig 文件,这些子目录下的 Kconfig 文件在主菜单中生成各自的菜单项。

2.2.3 menu/endmenu 条目

menu 用于生成菜单,endmenu 就是菜单结束标志,这两个一般是成对出现的。

menu xxx // xxx 就是子菜单的名称
	.。。。。。
endmenu 

如,“menu “General setup””表示子菜单“General setup”。
在这里插入图片描述

2.2.4 config 条目>具体配置项

顶层 Kconfig 中的“General setup”子菜单内容如下:

menu "General setup"

config LOCALVERSION
	string "Local version - append to U-Boot release"
	help
	  Append an extra string to the end of your U-Boot version.
	  This will show up on your boot log, for example.
	  。。。。。。

config LOCALVERSION_AUTO
	bool "Automatically append version information to the version string"
	default y
	help
	  This will try to automatically determine if the current tree is a
	  release tree by looking for git tags that belong to the current
	  。。。。。。
。。。。。。

在这里插入图片描述
其中大量的“config xxxx”的代码块,也就是 config 条目,就是“General setup”菜单的具体配置项。

“config LOCALVERSION”对应着第一个配置项,“config LOCALVERSION_AUTO”对应着 第 二 个 配 置 项 , 以 此 类 推 。 我 们 以 “ config LOCALVERSION ” 和 “ configLOCALVERSION_AUTO”这两个为例来分析一下 config 配置项的语法。

以 config 关 键 字 开 头 , 后 面 跟 着 LOCALVERSION 和LOCALVERSION_AUTO,这两个就是配置项名字。假如我们使能了 LOCALVERSION_AUTO这个功能,那么就会下.config 文件中生成 CONFIG_LOCALVERSION_AUTO,这个在上一小节讲解如何使能 dns 命令的时候讲过了。由此可知,.config 文件中的“CONFIG_xxx” (xxx 就是具体的配置项名字)就是 Kconfig 文件中 config 关键字后面的配置项名字加上“CONFIG_”前缀。

config 关键字下面的这几行是配置项属性。属性里面描述了配置项的类型、输入提示、依赖关系、帮助信息和默认值等。

config LOCALVERSION
	string "Local version - append to U-Boot release"
	help
		。。。。。。

string 是变量类型,也就是“CONFIG_ LOCALVERSION”的变量类型。可以为:bool、tristate、string、hex 和 int,一共 5 种。最常用的是 bool、tristate 和 string 这三种,bool 类型有两种值:y 和 n,当为 y 的时候表示使能这个配置项,当为 n 的时候就禁止这个配置项。tristate 类型有三种值:y、m 和 n,其中 y 和 n 的涵义与 bool 类型一样,m 表示将这个配置项编译为模块。string 为字符串类型,所以 LOCALVERSION 是个字符串变量,用来存储本地字符串,选中以后即可输入用户定义的本地版本号。

string 后面的“Local version - append to U-Boot release”就是这个配置项在图形界面上的显
示出来的标题。

help 表示帮助信息,告诉我们配置项的含义,当我们按下“h”或“?”弹出来的帮助界面就是 help 的内容。

config LOCALVERSION_AUTO
	bool "Automatically append version information to the version string"
	default y
	help
		。。。。。。

“CONFIG_LOCALVERSION_AUTO”是个 bool 类型,可以通过按下 Y 或N 键来使能或者禁止 CONFIG_LOCALVERSION_AUTO。

“default y”表示 CONFIG_LOCALVERSION_AUTO 的默认值就是 y,所以这一行默认会被选中。

2.2.5 depends on 和 select

打开 arch/Kconfig 文件,在里面有这如下代码:

config SYS_GENERIC_BOARD
	bool
	depends on HAVE_GENERIC_BOARD

choice
	prompt "Architecture select"
	default SANDBOX

config ARC
	bool "ARC architecture"
	select HAVE_PRIVATE_LIBGCC
	select HAVE_GENERIC_BOARD
	select SYS_GENERIC_BOARD
	select SUPPORT_OF_CONTROL

“depends on”说明“SYS_GENERIC_BOARD”项依赖于“HAVE_GENERIC_BOARD”,也就是说“HAVE_GENERIC_BOARD”被选中以后“SYS_GENERIC_BOARD”才能被选中。

“select”表示方向依赖,当选中“ARC”以后,“HAVE_PRIVATE_LIBGCC”、
“HAVE_GENERIC_BOARD”、“SYS_GENERIC_BOARD”和“SUPPORT_OF_CONTROL”这四个也会被选中。

2.2.6 choice/endchoice

在 arch/Kconfig 文件中有如下代码:

11 choice
12 prompt "Architecture select"
13 default SANDBOX
14
15 config ARC
16 bool "ARC architecture"
......
21
22 config ARM
23 bool "ARM architecture"
......
29
30 config AVR32
31 bool "AVR32 architecture"
......
35
36 config BLACKFIN
37 bool "Blackfin architecture"
......
40
41 config M68K
42 bool "M68000 architecture"
......
117
118 endchoic

choice/endchoice 代码段定义了一组可选择项,将多个类似的配置项组合在一起,供用户单选或者多选。示例代码 就是选择处理器架构,可以从 ARC、ARM、AVR32 等这些架构中选择,这里是单选。在 uboot 图形配置界面上选择“Architecture select”,进入以后如图 34.2.2.5所示:
第 12 行的 prompt 给出这个choice/endchoice 段的提示信息为“Architecture select”。
在这里插入图片描述

2.2.7 menuconfig

menuconfig 和 menu 很类似,但是 menuconfig 是个带选项的菜单,其一般用法为:

1 menuconfig MODULES
2 	bool "菜单"
3 if MODULES
4 ...
5 endif # MODULES

第 1 行,定义了一个可选的菜单 MODULES,只有选中了 MODULES 第 3~5 行 if 到 endif
之间的内容才会显示。

如menu “General setup” 下的 menuconfig EXPERT,如图:
在这里插入图片描述前面有“[ ]”说明这个菜单是可选的,当选中这个菜单以后就可以进入到子选项中。子选项如下:
在这里插入图片描述
如果不选择“Configure standard U-Boot features (expert users)”,那么子选项菜单就不会显示出来,进去以后是空白的。
在这里插入图片描述

2.2.8 comment

comment 用 于 注 释 , 也 就 是 在 图 形 化 界 面 中 显 示 一 行 注 释

comment "Generic NAND options"

如下:
在这里插入图片描述

3.添加自定义菜单

图形化配置工具的主要工作就是在.config 下面生成前缀为“CONFIG_”的变量,这些变量一般都要赋值,为 y,m 或 n,在 uboot 源码里面会根据这些变量来决定编译哪个文件。

添加自己的自定义菜单,自定义菜单要求如下:
①、在主界面中添加一个名为“My test menu”,此菜单内部有一个配置项。
②、配置项为“MY_TESTCONFIG”,此配置项处于菜单“My test menu”中。
③、配置项的为变量类型为 bool,默认值为 y。
④、配置项菜单名字为“This is my test config”。
⑤、配置项的帮助内容为“This is a empty config, just for tset!”。

打开顶层 Kconfig,在最后面加入如下代码:

menu "My test menu"

config MY_TESTCONFIG
	bool "This is my test config"
	default y
	help
		This is a empty config, just for test!
endmenu # my test menu

如下:
在这里插入图片描述
在.config 文件中肯定会有“CONFIG_MY_TESTCONFIG=y”这一行
在这里插入图片描述
最主要的是记住:Kconfig 文件的最终目的就是在.config 文件中生成以“CONFIG_”开头的变量。

六、Linux 内核顶层 Makefile 详解

同 uboot 一样,在具体移植之前,我们先来学习一下 Linux 内核的顶层 Makefile 文件,因为顶层 Makefile 控制着 Linux 内核的编译流程。

1.Linux 内核获取

同uboot,有三种版本,linux官方版,NXP半导体厂商版,开发板商家版。

2.Linux 内核初次编译

这里编译 I.MX6U-ALPHA 开发板移植好的 Linux 源码
编译内核之前需要先在 ubuntu 上安装 lzop 库。

sudo apt-get install lzop

写mx6ull_alientek_emmc.sh 的 shell脚本文件

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

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

在顶层Makefile中添加:

 254 ARCH ?= arm
 255 CROSS_COMPILE ?= arm-linux-gnueabihf-

使用 chmod 给予 mx6ull_alientek_emmc.sh 可执行权限,然后运行此 shell 脚本。

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

2.1 什么是设备树

设备树(Device Tree)是一种数据表示方式,用于描述硬件设备的树状层级结构信息,主要用于嵌入式Linux系统中。它是一种可扩展的数据序列化格式,通常以.dts(Device Tree Source)文件形式存在,然后编译成.dtb(Device Tree Blob)文件供Linux内核使用。
设备树的主要优势在于,它允许硬件设计师和软件开发人员独立于操作系统进行工作,从而提高了软硬件的分离。

2.2 什么是zImage

zImage是经过 gzip 压缩后的 Image,Linux 内核镜像文件是一个预先编译好的操作系统的二进制文件,包含了启动和运行操作系统所需的全部代码,可以看 5.5

3.Linux 工程目录分析

每个文件夹的作用详见正点原子参考手册。

4.VSCode 工程创建

使用正点原子移植好的linux源码,在分析 Linux 的顶层 Makefile 之前,先创建 VSCode 工程,创建过程和 uboot 一样。创建好.vscode/settings.json 文件,编译和显示部分文件 。

5.顶层 Makefile 详解

Linux 的顶层 Makefile 和 uboot 的顶层 Makefile 非常相似,因为 uboot 参考了 Linux。可以看uboot的Makefile详解。

1、版本号
2、MAKEFLAGS 变量
3、命令输出
4、静默输出
5、设置编译结果输出目录
6、代码检查
7、模块编译
8、设置目标架构和交叉编译器
9、调用 scripts/Kbuild.include 文件
10、交叉编译工具变量设置
11、头文件路径变量
12、导出变量

5.1 make xxx_defconfig 过程

一次编译 Linux 之前都要使用“make xxx_defconfig”先配置 Linux 内核,在顶层 Makefile中有“%config”这个目标。

%config: scripts_basic outputmakefile FORCE
	$(Q)$(MAKE) $(build)=scripts/kconfig $@
	//展开后
	@make -f ./scripts/Makefile.build obj=scripts/kconfig xxx_defconfig

“make xxx_defconfig”与目标“%config”匹配,因此执行。“%config”依赖scripts_basic、outputmakefile 和 FORCE,“%config”真正有意义的依赖就只有 scripts_basic,scripts_basic 的规则如下:

scripts_basic:
	$(Q)$(MAKE) $(build)=scripts/basic
	$(Q)rm -f .tmp_quiet_recordmcount
	//展开后
scripts_basic:
	@make -f ./scripts/Makefile.build obj=scripts/basic //也可以没有@,视配置而定
	@rm -f . tmp_quiet_recordmcount //也可以没有@

综上,“make xxx_defconfig“配置 Linux 的时候如下两行命令会执行脚本scripts/Makefile.build:

@make -f ./scripts/Makefile.build obj=scripts/basic
@make -f ./scripts/Makefile.build obj=scripts/kconfig xxx_defconfig

5.2 Makefile.build 脚本分析

从上一小节可知,“make xxx_defconfig“配置 Linux 的时候如下两行命令会执行脚本scripts/Makefile.build:

@make -f ./scripts/Makefile.build obj=scripts/basic
@make -f ./scripts/Makefile.build obj=scripts/kconfig xxx_defconfig
5.2.1 scripts_basic 目标对应的命令

scripts_basic 目标对应的命令为:@make -f ./scripts/Makefile.build obj=scripts/basic。
其中,__build 是默认目标,因为命令“@make -f ./scripts/Makefile.build obj=scripts/basic”没有指定目标,所以会使用到默认目标__build。

__build 依赖于 scripts/basic/fixdep 和 scripts/basic/bin2c,所以要先将 scripts/basic/fixdep 和scripts/basic/bin2c.c 这两个文件编译成 fixdep 和 bin2c。

综上所述,scripts_basic 目标的作用就是编译出 scripts/basic/fixdep 和 scripts/basic/bin2c 这两个软件。

5.2.2 %config 目标对应的命令

%config 目 标 对 应 的 命 令 为 : @make -f ./scripts/Makefile.build obj=scripts/kconfigxxx_defconfig。
Makefile.build 会读取 scripts/kconfig/Makefile 中的内容.
其中,目标%_defconfig 与 xxx_defconfig 匹配:

%_defconfig: scripts/kconfig/conf
	@ scripts/kconfig/conf --defconfig=arch/arm/configs/%_defconfig Kconfig

%_defconfig 依赖 scripts/kconfig/conf,所以会编译 scripts/kconfig/conf.c 生成 conf 这个软件。此软件就会将%_defconfig 中的配置输出到.config 文件中,最终生成 Linux kernel 根目录下的.config 文件。

5.3 make 过程

使用命令“make xxx_defconfig”配置好 Linux 内核以后就可以使用“make”或者“make all”命令进行编译。

_all 是默认目标,如果使用命令“make”编译 Linux 的话此目标就会被匹配。

默认目标_all 依赖 all。

目标 all 依赖 vmlinux,所以接下来的重点就是 vmlinux!

vmlinux 在顶层 Makefile 中,其中,vmlinux 的依赖为:scripts/link-vmlinux.sh、$ (head-y) 、$ (init-y)、$ (core-y) 、$ (libs-y) 、$ (drivers-y) 、$(net-y)、arch/arm/kernel/vmlinux.lds 和 FORCE。

1、head-y
head-y 定义在文件 arch/arm/Makefile 中,

head-y = arch/arm/kernel/head.o

2、init-y、drivers-y 和 net-y
在顶层 Makefile 中

init-y = init/built-in.o
drivers-y = drivers/built-in.o sound/built-in.o firmware/built-in.o
net-y = net/built-in.o

3、libs-y
libs-y 基本和 init-y 一样,在顶层 Makefile 中,最终

libs-y = arch/arm/lib/lib.a lib/lib.a arch/arm/lib/built-in.o lib/built-in.o

4、core-y
core-y 和 init-y 也一样,在顶层 Makefile 中,最终 core-y 的值为:

core-y = usr/built-in.o arch/arm/vfp/built-in.o \
arch/arm/vdso/built-in.o arch/arm/kernel/built-in.o \
arch/arm/mm/built-in.o arch/arm/common/built-in.o \
arch/arm/probes/built-in.o arch/arm/net/built-in.o \
arch/arm/crypto/built-in.o arch/arm/firmware/built-in.o \
arch/arm/mach-imx/built-in.o kernel/built-in.o\
mm/built-in.o fs/built-in.o \
ipc/built-in.o security/built-in.o \
crypto/built-in.o block/built-in.o

关于 head-y 、init-y、core-y 、libs-y 、drivers-y 和 net-y 这 6 个变量就讲解到这里。这些
变量都是一些 built-in.o 或.a 等文件,这个和 uboot 一样,都是将相应目录中的源码文件进行编译,然后在各自目录下生成 built-in.o 文件,有些生成了.a 库文件。最终将这些 built-in.o 和.a 文件进行链接即可形成 ELF 格式的可执行文件,也就是 vmlinux!但是链接是需要链接脚本的,vmlinux 的依赖 arch/arm/kernel/vmlinux.lds 就是整个 Linux 的链接脚本。

顶层Makefile 第 933 行 的 命 令 “ +$ (call if_changed,link-vmlinux) ” 表 示 将 $ (callif_changed,link-vmlinux)的结果作为最终生成 vmlinux 的命令,前面的“+”表示该命令结果不可忽略。$ (call if_changed,link-vmlinux)是调用函数 if_changed,link-vmlinux 是函数 if_changed 的参数,函数 if_changed 定义在文件 scripts/Kbuild.include 中

if_changed 中执行 cmd_link-vmlinux ,cmd_link-vmlinux 在顶层 Makefile 中,最终,

cmd_link-vmlinux = /bin/bash scripts/link-vmlinux.sh arm-linux-gnueabihf-ld -EL -p --no-undefined -X --pic-veneer --build-id

cmd_link-vmlinux 会调 用 scripts/link-vmlinux.sh 这个 脚本来链接 出 vmlinux 。

至此我们基本理清了 make 的过程,重点就是将各个子目录下的 built-in.o、.a 等文件链接
在一起,最终生成 vmlinux 这个 ELF 格式的可执行文件。链接脚本为 arch/arm/kernel/vmlinux.lds,链接过程是由 shell 脚本 scripts/link-vmlinux.s 来完成的。接下来的问题就是这些子目录下的 built-in.o、.a 等文件又是如何编译出来的呢?

5.4 built-in.o 文件编译生成过程

vmlinux: scripts/link-vmlinux.sh $(vmlinux-deps) FORCE

vmliux 依赖 vmlinux-deps。

vmlinux-deps := $(KBUILD_LDS) $(KBUILD_VMLINUX_INIT) $(KBUILD_VMLINUX_MAIN)

KBUILD_LDS是链接脚本,这里不考虑,剩下的 KBUILD_VMLINUX_INIT 和 KBUILD_VMLINUX_MAIN 就是各个子目录下的 built-in.o、.a 等文件。

在顶层 Makefile 中有如下代码:

$(sort $(vmlinux-deps)): $(vmlinux-dirs) ;

sort 是排序函数,用于对 vmlinux-deps 的字符串列表进行排序,并且去掉重复的单词。可以看出 vmlinux-deps 依赖 vmlinux-dirs,vmlinux-dirs 也定义在顶层 Makefile 中。
vmlinux-dirs 看名字就知道和目录有关,此变量保存着生成 vmlinux 所需源码文件的目录。

未写完整,详见参考手册。
总的来说,就是有一个文件列表,生成了一个.o 文件列表,然后使用 LD将某个目录下的所有.o 文件链接在一起,最终形成 built-in.o。

5.5 make zImage 过程>vmlinux生成Image、zImage、uImage

vmlinux、Image,zImage、uImage 的区别
①、vmlinux 是编译出来的最原始的内核文件,是未压缩的
②、Image 是 Linux 内核镜像文件,但是 Image 仅包含可执行的二进制数据。Image 就是使用 objcopy 取消掉 vmlinux 中的一些其他信息,比如符号表什么的。但是 Image 是没有压缩过的,Image 保存在 arch/arm/boot 目录下。
③、zImage 是经过 gzip 压缩后的 Image。
④、uImage 是老版本 uboot 专用的镜像文件,uImag 是在 zImage 前面加了一个长度为 64字节的“头”,这个头信息描述了该镜像文件的类型、加载位置、生成时间、大小等信息。但是新的 uboot 已经支持了 zImage 启动!所以已经很少用到 uImage 了,除非你用的很古老的 uboot。
关于 Linux 顶层 Makefile 就讲解到这里,基本和 uboot 的顶层 Makefile 一样,重点在于vmlinux 的生成。最后将 vmlinux 压缩成我们最常用的 zImage 或 uImage 等文件。

七、Linux 内核启动流程

1.链接脚本 vmlinux.lds

首先分析 Linux 内核的连接脚本文件 arch/arm/kernel/vmlinux.lds,通过链接脚本可以找到 Linux 内核的第一行程序是从哪里执行的。

其中有,ENTRY(stext),ENTRY 指 明 了 了 Linux 内 核 入 口 , 入 口 为 stext , stext 定 义 在 文 件arch/arm/kernel/head.S 中 , 因 此 要 分 析 Linux 内 核 的 启 动 流 程 , 就 得 先 从 文 件arch/arm/kernel/head.S 的 stext 处开始分析。

2.Linux 内核启动流程分析

2.1 Linux 内核入口 stext

stext 是 Linux 内核的入口地址,在文件 arch/arm/kernel/head.S 中有如下所示提示内容:
第60行,Linux 内核启动之前要求如下:
①、关闭 MMU。
②、关闭 D-cache。
③、I-Cache 无所谓。
④、r0=0。
⑤、r1=machine nr(也就是机器 ID)。
⑥、r2=atags 或者设备树(dtb)首地址。

Linux 内核的入口点 stext 其实相当于内核的入口函数,

后面比较复杂,详见参考手册。

八、Linux 内核移植

本章我们就来学习一下如何将 NXP 官方提供的 Linux 内核移植到正点原子的 I.MX6U-ALPHA 开发板上。通过本章的学习,我们将掌握如何将半导体厂商提供的 Linux BSP 包移植到我们自己的平台上。

1.创建 VSCode 工程

这里我们使用 NXP 官方提供的 Linux 源码,将其移植到正点原子 I.MX6U-ALPHA 开发板上。NXP 官方原版 Linux 源码已经放到了开发板光盘中,路径为:1、例程源码->4、NXP 官方原版 Uboot 和 Linux->linux-imx-rel_imx_4.1.15_2.1.0_ga.tar.bz2。完成以后创建 VSCode 工程,步骤和 Windows 下一样,重点是.vscode/settings.json 这个文件。

2.NXP 官方开发板 Linux 内核编译

2.1 修改顶层 Makefile

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

2.2 配置并编译 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。

写shell文件“imx6ull-14x14-evk.sh”

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

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。

2.3 Linux 内核启动测试

在测试之前确保 uboot 中的环境变量 bootargs 内容如下:

// 具体参数详见上面的介绍
console=ttymxc0,115200 root=/dev/mmcblk1p2 rootwait rw

将上一小节编译出来的 zImage 和 imx6ull-14x14-evk-emmc.dtb 复制到 Ubuntu 中的 tftp 目录下,因为我们要在 uboot 中使用 tftp 命令将其下载到开发板中。
拷贝完成以后就可以测试了,启动开发板,进入 uboot 命令行模式,然后输入如下命令将zImage 和 imx6ull-14x14-evk.dtb 下载到开发板中并启动:

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

因为EMMC 中的根文件系统存在,我们可以进入到 Linux 系统里面使用命令进行操作。

2.4 根文件系统缺失错误

我们将 uboot 中 的 bootargs 环境 变 量改 为“console=ttymxc0,115200”,也就是不填写 root 的内容了

setenv bootargs 'console=ttymxc0,115200' //设置 bootargs
saveenv //保存

修改完成以后重新从网络启动,启动以后会有如图 37.2.4.1 所示错误:
在这里插入图片描述Kernel panic - not syncing: VFS: Unable to mount root fs on unknown-block(0,0)
也就是提示内核崩溃,因为 VFS(虚拟文件系统)不能挂载根文件系统,因为根文件系统目录不存在。即使根文件系统目录存在,如果根文件系统目录里面是空的依旧会提示内核崩溃。这个就是根文件系统缺失导致的内核崩溃,但是内核是启动了的,只是根文件系统不存在而已。

3.在 Linux 中添加自己的开发板

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

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

打开 imx_alientek_emmc_defconfig 文件,找到“CONFIG_ARCH_MULTI_V6=y”这一行,将其屏蔽掉。因为 I.MX6ULL 是 ARMV7 架构的,因此要屏蔽掉 V6 相关选项,否则后面做驱动实验的时候可能会遇到驱动模块无法加载的情况。(视频P41中讲解了不去掉这一行的解决办法)

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

make imx_alientek_emmc_defconfig

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

添加适合正点原子 EMMC 版开发板的设备树文件,进入目录 arch/arm/boot/dts 中,复制一份 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 的时候就可以从 imx6ull-alientek-emmc.dts 编译出 imx6ull-alientek-emmc.dtb 文件了。

3.3 编译测试

创建shell脚本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 -j12 

执行 shell 脚本 imx6ull_alientek_emmc.sh 编译 Linux 内核,编译完成以后就会在目录 arch/arm/boot 下生成 zImage 镜像文件。在 arch/arm/boot/dts 目录下生成 imx6ull-alientek-emmc.dtb 文件。将这两个文件拷贝到 tftp 目录下,然后重启开发板,在uboot 命令模式中使用 tftp 命令下载这两个文件并启动。

可以修改bootcmd,直接使用tftp启动,免得再输入三行命令:

setenv bootcmd 'tftp 80800000 zImage; tftp 83000000 imx6ull-alientek-emmc.dtb; bootz 80800000 - 83000000'

4.CPU 主频和网络驱动修改

4.1 CPU 主频修改

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

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

root@ATK-IMX6U:~# cat /proc/cpuinfo
processor       : 0
model name      : ARMv7 Processor rev 5 (v7l)
BogoMIPS        : 12.00
Features        : half thumb fastmult vfp edsp neon vfpv3 tls vfpv4 idiva idivt vfpd32 lpae
CPU implementer : 0x41
CPU architecture: 7
CPU variant     : 0x0
CPU part        : 0xc07
CPU revision    : 5

Hardware        : Freescale i.MX6 Ultralite (Device Tree)
Revision        : 0000
Serial          : 0000000000000000

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

查看当前 CPU 的工作频率。进入到目录/sys/bus/cpu/devices/cpu0/cpufreq 中,此目录下会有很多文件。
此目录中记录了 CPU 频率等信息,这些文件的含义如下:
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 频率:

root@ATK-IMX6U:/sys/bus/cpu/devices/cpu0/cpufreq# cat cpuinfo_cur_freq
792000

想要设置为 Ondemand 模式

方法一:修改imx_alientek_emmc_defconfig 文件,详见参考手册p983。修改完成以后重新编译 Linux 内核,编译之前先清理一下工程!因为我们重新修改过默认配 置 文 件 了(xxx_defconfig 生成 .config) , 编 译 完 成 以 后 使 用 新 的 zImage 镜 像 文 件 重 新 启 动 Linux 。

方法二:使用menuconfig。然后编译 Linux内核,一定不要清理工程!因为menuconfig来自.config,直接写入.config,直接make -j12。将新的zImage 放到 tftpboot 文件夹中,重新启动开发板。

2、超频至 700MHz
超频有风险,不介绍。

4.2 修改 EMMC 驱动

1、使能 8 线 EMMC 驱动
正点原子 EMMC 版本核心板上的 EMMC 采用的 8 位数据线,Linux 内核驱动里面 EMMC 默认是 4 线模式的,4 线模式肯定没有 8 线模式的速度快,所以本节我们将 EMMC 的驱动修改为 8 线模式。修改方法很简单,直接修改设备树即可,打开文件 imx6ull-alientek-emmc.dts进行修改。

2、关闭 EMMC 1.8V 供电选项

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

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

4.3 V2.4 及以后版本底板网络驱动修改

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

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

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

SNVS_TAMPER7 和 SNVS_TAMPER8 这两个引脚的,原本是 SPI4 的 IO,需要删除部分代码。

在 imx6ull-alientek-emmc.dts 里面找到名为“iomuxc_snvs”的节点(就是直接搜索),然后在此节点下添加网络复位引脚信息。

后还需要修改一下 ENET1 和 ENET2 的网络时钟引脚配置,继续在 imx6ull-alientek-emmc.dts 中添加。

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

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

3、修改 SR8201F 的 PHY 地址

在 uboot 移植章节中,我们说过 ENET1 的 LAN8720A 地址为 0x2,ENET2 的 LAN8720A地址为 0x1。在 imx6ull-alientek-emmc.dts 中添加。

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

msleep(200); /* 复位结束后至少再延时 150ms 才能继续操作 SR8201F */

根据 SR8201F 收据手册上的要求,SR8201F 在复位结束以后需要等待至少 150ms 才能操作 SR8201F,因此这里添加了一个 200ms 的延时。

5、 网络驱动测试
修改好设备树和 Linux 内核以后重新编译一下make -j12,得到新的 zImage 镜像文件和 imx6ull-alientek-emmc.dtb 设备树文件,使用新的文件启动 Linux 内核。

启动以后使用“ifconfig”命令查看一下当前活动的网卡有哪些。

输入命令“ifconfig -a”来查看一下开发板中存在的所有网卡。

root@ATK-IMX6U:~# ifconfig -a
eth0      Link encap:Ethernet  HWaddr 88:31:50:97:59:2d
          inet addr:192.168.10.251  Bcast:192.168.10.255  Mask:255.255.255.0
          inet6 addr: fe80::8a31:50ff:fe97:592d/64 Scope:Link
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:78 errors:0 dropped:0 overruns:0 frame:0
          TX packets:123 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000
          RX bytes:19183 (18.7 KiB)  TX bytes:38544 (37.6 KiB)

eth1      Link encap:Ethernet  HWaddr 88:91:e3:d0:1f:b7
          BROADCAST MULTICAST DYNAMIC  MTU:1500  Metric:1
          RX packets:0 errors:0 dropped:0 overruns:0 frame:0
          TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000
          RX bytes:0 (0.0 B)  TX bytes:0 (0.0 B)

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:150 errors:0 dropped:0 overruns:0 frame:0
          TX packets:150 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0
          RX bytes:10172 (9.9 KiB)  TX bytes:10172 (9.9 KiB)

sit0      Link encap:UNSPEC  HWaddr 00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00
          NOARP  MTU:1480  Metric:1
          RX packets:0 errors:0 dropped:0 overruns:0 frame:0
          TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0
          RX bytes:0 (0.0 B)  TX bytes:0 (0.0 B)

eth0 和 eth1 才是网络接口的网卡,其中 eth0 对应于 ENET2,eth1 对应于 ENET1。
使用如下命令依次打开 eth0这个网卡:

ifconfig eth0 up

再次输入“ifconfig”命令来查看一下当前活动的网卡:

root@ATK-IMX6U:~# ifconfig
eth0      Link encap:Ethernet  HWaddr 88:31:50:97:59:2d
          inet addr:192.168.10.251  Bcast:192.168.10.255  Mask:255.255.255.0
          inet6 addr: fe80::8a31:50ff:fe97:592d/64 Scope:Link
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:79 errors:0 dropped:0 overruns:0 frame:0
          TX packets:128 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000
          RX bytes:19430 (18.9 KiB)  TX bytes:41494 (40.5 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:150 errors:0 dropped:0 overruns:0 frame:0
          TX packets:150 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0
          RX bytes:10172 (9.9 KiB)  TX bytes:10172 (9.9 KiB)

可以看出,此时 eth0 网卡已经打开,并且工作正常,但是这个网卡还没有 IP 地址,所以不能进行 ping 等操作。使用如下命令给网卡配置 IP 地址:

root@ATK-IMX6U:~# ifconfig eth0 192.168.10.251
root@ATK-IMX6U:~# ifconfig
eth0      Link encap:Ethernet  HWaddr 88:31:50:97:59:2d
          inet addr:192.168.10.251  Bcast:192.168.10.255  Mask:255.255.255.0
          inet6 addr: fe80::8a31:50ff:fe97:592d/64 Scope:Link
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:66 errors:0 dropped:0 overruns:0 frame:0
          TX packets:79 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000
          RX bytes:17773 (17.3 KiB)  TX bytes:18809 (18.3 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:150 errors:0 dropped:0 overruns:0 frame:0
          TX packets:150 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0
          RX bytes:10172 (9.9 KiB)  TX bytes:10172 (9.9 KiB)

使用“ping”命令来 ping 一下自己的主机,如果能 ping 通那说明网络驱动修改成功:

root@ATK-IMX6U:~# ping 192.168.10.100
PING 192.168.10.100 (192.168.10.100) 56(84) bytes of data.
64 bytes from 192.168.10.100: icmp_seq=1 ttl=64 time=3.23 ms
64 bytes from 192.168.10.100: icmp_seq=2 ttl=64 time=2.26 ms
64 bytes from 192.168.10.100: icmp_seq=3 ttl=64 time=2.06 ms
^C
--- 192.168.10.100 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2004ms
rtt min/avg/max/mdev = 2.064/2.520/3.231/0.509 ms
root@ATK-IMX6U:~# ping 192.168.10.200
PING 192.168.10.200 (192.168.10.200) 56(84) bytes of data.
64 bytes from 192.168.10.200: icmp_seq=1 ttl=128 time=8.25 ms
64 bytes from 192.168.10.200: icmp_seq=2 ttl=128 time=2.26 ms
^C
--- 192.168.10.200 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1001ms
rtt min/avg/max/mdev = 2.262/5.260/8.258/2.998 ms

可以看出,ping 成功,说明网络驱动修改成功!我们在后面的构建根文件系统和 Linux 驱动开发中就可以使用网络调试代码啦。

4.4保存修改后的图形化配置文件

%_defconfig 中的配置会输出到.config 文件中。
但是当我们执行“make clean”清理工程以后.config 文件就会被删除掉,因此我们所有的配置内容都会丢失,结果就是前功尽弃,一“删”回到解放前!所以我们在配置完图形界面以后经过测试没有问题,就必须要保存一下配置文件。保存配置的方法有两个。

1、直接另存为.config 文件

既然图形化界面配置后的配置项保存在.config 中,那么就简单粗暴,直接将.config 文件另存 为 imx_alientek_emmc_defconfig , 然 后 其 复 制 到 arch/arm/configs 目 录 下 , 替 换 以 前 的imx_alientek_emmc_defconfig。这样以后执行“make imx_alientek_emmc_defconfig”重新配置Linux 内核的时候就会使用新的配置文件。

2、通过图形界面保存配置文件

在图形界面中保存配置文件,在图形界面中会有“< Save >”选项。

九、根文件系统构建

Linux“三巨头”已经完成了 2 个了,就剩最后一个 rootfs(根文件系统)了,本章我们就来学习一下根文件系统的组成以及如何构建根文件系统。这是 Linux 移植的最后一步,根文件系统构建好以后就意味着我们已经拥有了一个完整的、可以运行的最小系统。以后我们就在这个最小系统上编写、测试 Linux 驱动,移植一些第三方组件,逐步的完善这个最小系统。最终得到一个功能完善、驱动齐全、相对完善的操作系统。

1.根文件系统简介

根文件系统一般也叫做 rootfs,Linux 中的根文件系统更像是一个文件夹或者叫做目录,在这个目录里面会有很多的子目录。根目录下和子目录中会有很多的文件,这些文件是 Linux 运行所必须的,比如库、常用的软件和命令、设备文件、配置文件等等。

根文件系统是Linux系统中所有其他文件系统和目录的起源,‌它是Linux启动时首先需要挂载的文件系统,‌包含了系统引导和使其他文件系统得以挂载所必要的文件。‌根文件系统包括Linux启动时所必须的目录和关键性的文件,还包括了许多应用程序。

根文件系统的这个“根”字就说明了这个文件系统的重要性,它是其他文件系统的根,没有这个“根”,其他的文件系统或者软件就别想工作。比如我们常用的 ls、mv、ifconfig 等命令其实就是一个个小软件,只是这些软件没有图形界面,而且需要输入命令来运行。

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

han@ubuntu:~$ cd /

1.1 bin目录

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

han@ubuntu:/$ ls
bin    dev   initrd.img      lib64       mnt   root  snap      sys  var
boot   etc   initrd.img.old  lost+found  opt   run   srv       tmp  vmlinuz
cdrom  home  lib             media       proc  sbin  swapfile  usr  vmlinuz.old
han@ubuntu:/$ cd bin
han@ubuntu:/bin$ ls
bash           fuser       netstat        ss
brltty         fusermount  networkctl     static-sh
bunzip2        getfacl     nisdomainname  stty
busybox        grep        ntfs-3g        su
bzcat          gunzip      ntfs-3g.probe  sync
bzcmp          gzexe       ntfscat        systemctl
bzdiff         gzip        ntfscluster    systemd
bzegrep        hciconfig   ntfscmp        systemd-ask-password
bzexe          hostname    ntfsfallocate  systemd-escape
bzfgrep        ip          ntfsfix        systemd-hwdb
bzgrep         journalctl  ntfsinfo       systemd-inhibit
bzip2          kbd_mode    ntfsls         systemd-machine-id-setup
bzip2recover   keyctl      ntfsmove       systemd-notify
bzless         kill        ntfsrecover    systemd-sysusers
bzmore         kmod        ntfssecaudit   systemd-tmpfiles
cat            less        ntfstruncate   systemd-tty-ask-password-agent
chacl          lessecho    ntfsusermap    tar
chgrp          lessfile    ntfswipe       tempfile
chmod          lesskey     open           touch
chown          lesspipe    openvt         true
chvt           ln          pidof          udevadm
cp             loadkeys    ping           ulockmgr_server
cpio           login       ping4          umount
dash           loginctl    ping6          uname
date           lowntfs-3g  plymouth       uncompress
dd             ls          ps             unicode_start
df             lsblk       pwd            vdir
dir            lsmod       rbash          wdctl
dmesg          mkdir       readlink       which
dnsdomainname  mknod       red            whiptail
domainname     mktemp      rm             ypdomainname
dumpkeys       more        rmdir          zcat
echo           mount       rnano          zcmp
ed             mountpoint  run-parts      zdiff
efibootdump    mt          sed            zegrep
efibootmgr     mt-gnu      setfacl        zfgrep
egrep          mv          setfont        zforce
false          nano        setupcon       zgrep
fgconsole      nc          sh             zless
fgrep          nc.openbsd  sh.distrib     zmore
findmnt        netcat      sleep          znew

1.2 /dev 目录

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

1.3 /etc 目录

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

1.4 /lib 目录

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

1.5 /mnt 目录

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

1.6 /proc 目录

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

1.7 /usr 目录

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

1.8 /var 目录

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

1.9 /sbin 目录

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

1.10 /sys 目录

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

1.11 /opt

可选的文件、软件存放区,由用户选择将哪些文件或软件放到此目录中。

2. BusyBox 构建根文件系统

2.1 BusyBox简介

BusyBox 是一个集成了大量的 Linux 命令和工具的软件,像 ls、mv、ifconfig 等命令 BusyBox 都会提供。一般下载 BusyBox 的源码,然后配置 BusyBox,选择自己想要的功能,最后编译即可。

2.2 编译 BusyBox 构建根文件系统

一般我们在 Linux 驱动开发的时候都是通过 nfs 挂载根文件系统的,当产品最终上市开卖的时候才会将根文件系统烧写到 EMMC 或者 NAND 中。/home/han/linux/nfs的 NFS 服务器目录的 rootfs 子目录就用来存放我们的根文件系统了。

han@ubuntu:~/linux/nfs$ ls
rootfs  zImage

NFS(‌Network File System)‌是一种网络文件系统,‌它允许客户端通过网络访问服务器上的文件和目录,‌就像访问本地文件系统一样。‌在NFS挂载根文件系统的应用中,‌NFS技术使得开发人员可以通过网络访问和修改根文件系统,‌而无需物理接触存储设备,‌从而简化了开发过程。

NFS挂载根文件系统是一种技术,‌允许在Linux开发环境中提高开发效率。‌具体来说,‌这种技术通过将根文件系统制作成img镜像文件,‌然后烧写入NAND Flash或eMMC中。‌但在开发阶段,‌由于文件系统需要频繁修改,‌如果每次修改都需要重新编译生成镜像并烧写,‌会降低开发效率。‌因此,‌NFS挂载根文件系统技术应运而生。‌通过这项技术,‌根文件系统并不直接烧写入存储设备,‌而是放置在虚拟机中,‌系统启动后通过网络NFS挂载根文件系统,‌从而实现了对根文件系统的访问。‌当根文件系统需要修改时,‌只需在rootfs目录下替换相应的文件,‌无需重新生成镜像并烧写,‌大大提高了开发效率。‌

1、修改 Makefile,添加编译器
同 Uboot 和 Linux 移植一样,打开 busybox 的顶层 Makefile,添加 ARCH 和ROSS_COMPILE的值。
其中, CORSS_COMPILE 使用了绝对路径!主要是为了防止编译出错。

2、busybox 中文字符支持
详见参考手册。

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

make defconfig

busybox 也支持图形化配置,通过图形化配置我们可以进一步选择自己想要的功能,具体详见。

4、编译 busybox
配置好 busybox 以后就可以编译了,我们可以指定编译结果的存放目录,我们肯定要将编译结果存放到前面创建的 rootfs 目录中。

make
make install CONFIG_PREFIX=/home/han/linux/nfs/rootfs

COFIG_PREFIX 指 定 编 译 结 果 的 存 放 目 录 , 比 如 我 存 放 到“/home/han/linux/nfs/rootfs”目录中,等待编译完成。编译完成以后会在 busybox 的所有工具和文件就会被安装到 rootfs 目录中:

han@ubuntu:~/linux/nfs/rootfs$ ls
bin  linuxrc  sbin  usr

可以看出,rootfs 目录下有 bin、sbin 和 usr 这三个目录,以及 linuxrc 这个文件。前面说过linux 内核 init 进程最后会查找用户空间的 init 程序,找到以后就会运行这个用户空间的 init 程序,从而切换到用户态。如果 bootargs 设置 init=/linuxrc,那么 linuxrc 就是可以作为用户空间的 init 程序,所以用户态空间的 init 程序是 busybox 来生成的。busybox 的工作就完成了,但是此时的根文件系统还不能使用,还需要一些其他的文件。

2.3 向根文件系统添加 lib 库

1、向 rootfs 的“/lib”目录添加库文件

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

lib 库文件从交叉编译器中获取,前面我们搭建交叉编译环境的时候将交叉编译器存放到了“/usr/local/arm/”目录中。(question:为啥库文件要从较长编译器中获取?)

然后就是cp一些库文件,详见。

2、向 rootfs 的“usr/lib”目录添加库文件

在 rootfs 的 usr 目录下创建一个名为 lib 的目录,然后cp

2.4 创建其他文件夹

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

3.根文件系统初步测试

测试一下前面创建好的根文件系统 rootfs,测试方法就是使用 NFS 挂载,uboot 里面的 bootargs 环境变量会设置“root”的值,所以我们将 root 的值改为 NFS 挂载即可。在 Linux 内核源码里面有相应的文档讲解如何设置,
文档为 Documentation/filesystems/nfs/nfsroot.txt,格式如下:详见xx

root=/dev/nfs nfsroot=[<server-ip>:]<root-dir>[,<nfs-options>] ip=<client-ip>:<server-
     ip>:<gw-ip>:<netmask>:<hostname>:<device>:<autoconf>:<dns0-ip>:<dns1-ip>

具体:

=> setenv bootargs 'console=ttymxc0,115200  root=/dev/nfs nfsroot=192.168.10.100:/home/han/linux/nfs/rootfs,proto=tcp rw ip=192.168.10.251:192.168.10.100:192.168.10.1:255.255.255.0::eth0:off'
=> saveenv
Saving Environment to MMC...
Writing to MMC(0)... done
=> print
baudrate=115200
board_name=EVK
board_rev=14X14
boot_fdt=try
bootargs=console=ttymxc0,115200  root=/dev/nfs nfsroot=192.168.10.100:/home/han/linux/nfs/rootfs,proto=tcp rw ip=192.168.10.251:192.168.10.100:192.168.10.1:255.255.255.0::eth0:off
······

设置好以后使用“boot”命令启动 Linux 内核。但其中会报错can't run '/etc/init.d/rcS': No such file or directory,即缺失文件,需要完善。

4.完善根文件系统

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

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

创建好文件/etc/init.d/rcS 以后一定要给其可执行权限!

设置好以后就重新启动 Linux 内核,还会报错,还需完善。提示找不到/etc/fstab 文件,还有一些其他的错误,在rcS中的“mount -a”挂载所有根文件系统的时候需要读取/etc/fstab,因为/etc、fstab 里面定义了该挂载哪些文件,接下来就是创建/etc/fstab 文件。

4.2 创建/etc/fstab 文件

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

<file system> <mount point> <type> <options> <dump> <pass>

具体内容详见xx,fstab 文件创建完成以后重新启动 Linux。可以看出,启动成功,而且没有任何错误提示。但是我们要还需要创建一个文件/etc/inittab。

4.3 创建/etc/inittab 文件

inittab 的详细内容可以参考 busybox 下的文件 examples/inittab。init 程序会读取/etc/inittab这个文件,inittab 由若干条指令组成。每条指令的结构都是一样的,由以“:”分隔的 4 个段组成,格式如下:详见xx

<id>:<runlevels>:<action>:<process>

接下来就要对根文件系统进行其他的测试,比如我们自己编写的软件运行是否正常、是否支持软件开机自启动、中文支持是否正常以及能不能链接等。

5.根文件系统其他功能测试

5.1 软件运行测试

我们使用 Linux 的目的就是运行我们自己的软件,我们编译的应用软件一般都使用动态库,使用动态库的话应用软件体积就很小,但是得提供库文件,库文件我们已经添加到了根文件系统中(lib)。

我们编写一个小小的测试软件来测试一下库文件是否工作正常,在根文件系统下创建一个名为“drivers”的文件夹,以后我们学习 Linux 驱动的时候就把所有的实验文件放到这个文件夹里面。在 ubuntu 下使用 vim 编辑器新建一个 hello.c 文件,在 hello.c 里面输入如下内容:

#include<stdio.h>

int main(void){
    while (1)
    {
        printf("hello !!!\r\n");
        sleep(2);
    }
    return 0;
}

hello.c 内容很简单,就是循环输出“hello world”,sleep 相当于 Linux 的延时函数,单位为秒,所以 sleep(2)就是延时 2 秒。编写好以后就是编译,因为我们是要在 ARM 芯片上运行的,所以要用交叉编译器去编译,也就是使用 arm-linux-gnueabihf-gcc 编译,命令如下:arm-linux-gnueabihf-gcc hello.c -o hello
使用 arm-linux-gnueabihf-gcc 将 hello.c 编译为 hello 可执行文件。这个 hello 可执行文件究竟是不是 ARM 使用的呢?使用“file”命令查看文件类型以及编码格式:

han@ubuntu:~/linux/nfs/rootfs/drivers$ file hello
hello: ELF 32-bit LSB shared object, ARM, EABI5 version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux-armhf.so.3, for GNU/Linux 3.2.0, BuildID[sha1]=03389c2d5647386dbadc5d8bf4e716dbfd07ecdd, not stripped

在开发板中输入如下命令来执行这个可执行文件:

cd /drivers //进入 drivers 目录
./hello //执行 hello

/drivers # ./hello
hello !!!
hello !!!
hello !!!
······

终止 hello 的运行,按下“ctrl+c”组合键即可。

hello 执行的时候终端是没法用的,除非使用“ctrl+c”来关闭 hello,那么有没有办法既能让 hello 正常运行,而且终端能够正常使用?
让 hello 进入后台运行就行了,让一个软件进入后台的方法很简单,运行软件的时候加上“&”即可,比如“./hello &”就是让 hello 在后台运行。

在后台运行的软件可以使用“kill -9 pid(进程 ID)”命令来关闭掉,首先使用“ps”命令查看要关闭的软件 PID 是多少,ps 命令用于查看所有当前正在运行的进程,并且会给出进程的 PID。输入“ps”命令:

ps
PID   USER     TIME  COMMAND
    1 0         0:01 init
    2 0         0:00 [kthreadd]
    3 0         0:00 [ksoftirqd/0]
    4 0         0:00 [kworker/0:0]
    5 0         0:00 [kworker/0:0H]
    6 0         0:00 [kworker/u2:0]
    7 0         0:00 [rcu_preempt]
    8 0         0:00 [rcu_sched]
    9 0         0:00 [rcu_bh]
   10 0         0:00 [migration/0]
   11 0         0:00 [khelper]
   12 0         0:00 [kdevtmpfs]
   13 0         0:00 [perf]
   14 0         0:00 [writeback]
   15 0         0:00 [crypto]
   16 0         0:00 [bioset]
   17 0         0:00 [kblockd]
   18 0         0:00 [ata_sff]
   19 0         0:00 [kworker/0:1]
   20 0         0:00 [cfg80211]
   21 0         0:00 [rpciod]
   22 0         0:00 [kswapd0]
   23 0         0:00 [fsnotify_mark]
   24 0         0:00 [nfsiod]
   38 0         0:00 [kworker/u2:1]
   41 0         0:00 [ci_otg]
   42 0         0:00 [irq/21-2040000.]
   43 0         0:00 [irq/225-mmc0]
   44 0         0:00 [irq/50-2190000.]
   45 0         0:00 [irq/226-mmc1]
   46 0         0:00 [ipv6_addrconf]
   47 0         0:00 [deferwq]
   48 0         0:00 [kworker/u2:2]
   49 0         0:00 [irq/205-imx_the]
   50 0         0:00 [mmcqd/0]
   51 0         0:00 [mmcqd/1]
   52 0         0:00 [mmcqd/1boot0]
   53 0         0:00 [mmcqd/1boot1]
   54 0         0:00 [mmcqd/1rpmb]
   55 0         0:00 [kworker/0:2]
   56 0         0:00 [kworker/0:1H]
   62 0         0:00 -/bin/sh
   76 0         0:00 ./hello
   77 0         0:00 ps

可以看出 hello 对应的 PID 为 76,因此我们使用如下命令关闭在后台运行的hello 软件:

kill -9 76

因为 hello 在不断的输出“hello world”所以我们的输入看起来会被打断,其实是没有的,因为我们是输入,而 hello 是输出。在数据流上是没有打断的,只是显示上就好像被打断了,所以只管输入“kill -9 76”即可。

这个就是 Linux 下的软件后台运行以及如何关闭软件的方法,重点就是 3 个操作:软件后面加“&”、使用 ps 查看要关闭的软件 PID、使用“kill -9 pid”来关闭指定的软件。

5.2 中文字符测试

在 ubuntu 中向在 rootfs 目录新建一个名为“中文测试”的文件夹,接着“touch”命令在“中文测试”文件夹中新建一个名为“文档.txt”的文件,并且使用 vim 编辑器在其中输入“z测试文档.txt”,借此来测试一下中文文件名和中文内容显示是否正常。

/ # cd 中文测试/
/中文测试 # ls
测试.txt
/中文测试 # cat 测试.txt
z测试文档.txt/中文测试 #

可以看出,中文内容显示正确,而且中文路径也完全正常,说明我们的根文件系统已经完美支持中文了!

5.3 开机自启动测试

测试 hello 软件的时候都是等 Linux 启动进入根文件系统以后手动输入命令“./hello”来完成的。以hello 这个软件为例,讲解一下如何实现开机自启动。前面我们说过了,进入根文件系统的时候会运行/etc/init.d/rcS 这个 shell 脚本,因此我们可以在这个脚本里面添加自启动相关内容。添加完成以后的/etc/init.d/rcS 文件内容如下:

#!/bin/sh

PATH=/sbin:/bin:/usr/sbin:/usr/bin:$PATH
LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/lib:/usr/lib
runlevel=S
umask 022
export PATH LD_LIBRARY_PATH

mount -a
mkdir /dev/pts
mount -t devpts devpts /dev/pts

echo /sbin/mdev > /proc/sys/kernel/hotplug
mdev -s

cd /drivers  # 进入 drivers 目录,因为要启动的软件存放在 drivers 目录下。
./hello &    # 以后台方式执行 hello 这个软件。
cd /         # 退出 drivers 目录,进入到根目录下。

自启动代码添加完成以后就可以重启开发板,看看 hello 这个软件会不会自动运行。结果如下:

Please press Enter to activate this console. hello !!!
hello !!!
hello !!!

5.4 外网连接测试

我没成功,还是提示 www.baidu.com 是个“bad address”,应该是因为我是电脑和开发板直接连接的问题。

ping 百度、淘宝等这些网站的测试,也就是说看看我们的开发板能不能上网,能不能和我们的局域网外的这些网站进行通信。
通过 ping 命令来 ping 一下百度的官网:www.baidu.com。输入如下命令:

ping www.baidu.com

测试失败,提示 www.baidu.com 是个“bad address”,也就是地址不对,显然我们的地址是正确的。之所以出现这个错误提示是因为 www.baidu.com 的地址解析失败了,并没有解析出其对应的 IP 地址。我们需要配置域名解析服务器的 IP 地址,一般域名解析地址可以设置为所处网络的网关地址,比如 192.168.10.1。也可以设置为 114.114.114.114,这个是运营商的域名解析服务器地址。

在 rootfs 中新建文件/etc/resolv.conf,然后在里面输入如下内容:

nameserver 192.168.10.1
nameserver 114.114.114.114

至此!我们的根文件系统就彻底的制作完成,这个根文件系统最好打包保存一下,防止以后做实验不小心破坏了根文件系统而功亏一篑,又得从头制作根文件系统。uboot、Linux kernel、rootfs 这三个共同构成了一个完整的Linux 系统,现在的系统至少是一个可以正常运行的系统,后面我们就可以在这个系统上完成Linux 驱动开发的学习。(我保存在nfs中)

十、系统烧写

主要是使用MfgTool工具,将 uboot、linux kernel、.dtb(设备树)和 rootfs 这四个文件烧写到板子上的 EMMC、NAND 或 QSPI Flash 等其他存储设备上。详print见XX。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值