本系列教程以「i.MX6ULL」处理器的ARM开发板为实验基础,学习记录嵌入式Linux开发的各种知识与经验,主要内容包括嵌入式Linux移植,嵌入式Linux驱动开发,嵌入式Linux应用开发等。
本系列教程将以野火的i.MX6ULL eMMC开发板为硬件基础,以「野火EBF6ULL Pro开发板教程」和「正点原子i.MX6ULL阿尔法开发板教程」为参考,进行学习实践。
uboot移植初探
Linux 的移植主要包括3部分:
移植「bootloader 代码」, Linux 系统要启动就必须需要一个 bootloader 程序,也就说芯片上电以后先运行一段bootloader程序。 这段bootloader程序会先初始化DDR等外设, 然后将Linux内核从flash(NAND,NOR FLASH,SD,MMC 等)拷贝到 DDR 中,最后启动 Linux 内核。 bootloader 有很多,常用的就是 U-Boot。
bootloader 和 Linux 内核的关系就跟 PC 上的 BIOS 和 Windows 的关系一样,bootloader 就相当于 BIOS。
移植「Linux 内核」,Linux内核由一系列程序组成,包括负责响应中断的中断服务程序、负责管理多个进程从而分享处理器时间的调度程序、负责管理地址空间的内存管理程序、网络、进程间通信的系统服务程序等。内核负责管理系统的硬件设备。
移植「根文件系统(rootfs)」,Linux 中的根文件系统更像是一个文件夹或者叫做目录,在这个目录里面会有很多的子目录。根目录下和子目录中会有很多的文件,这些文件是 Linux 运行所必须的,比如库、常用的软件和命令、设备文件、配置文件等等。根文件系统里面包含了一些最常用的命令和文件。
「U-Boot、Linux kernel和rootfs」 这三者一起构成了一个完整的Linux系统,一个可以正常使用、功能完善的Linux系统。
本测试使用的开发板为野火的i.MX6ULL eMMC开发板
uboot 的全称是「Universal Boot Loader」,遵循 GPL 协议的开源软件。
uboot 是一个裸机代码,可以看作是一个裸机综合例程。现在的 uboot 已经支持「液晶屏、网络、USB」等高级功能。uboot 官网为 https://www.denx.de/wiki/U-Boot/
可以在uboot官网下载uboot源码,点击左侧 Topics 中的“Source Code
”,然后点击的“FTP Server
” ,进入其 FTP 服务器即可看到 uboot 源码。
但我们移植uboot时一般不会直接用 uboot 官方的源码的,官方的源码是给半导体厂商准备的,半导 体厂商会根据自家的芯片,维护自己芯片对应的uboot。
NXP(freescale)维护的的uboot地址: https://github.com/Freescale/u-boot-fslc
uboot移植并不需要从零开始将 uboot 移植到我们现在所使用的开发板上。因为半导体厂商通常都会自己做一个开发板「原厂开发板」,将uboot移植到他们自己的原厂开发板上,再将这个uboot(原厂BSP 包)发布出去。
市面上的开发板,通常会参考原厂的开发板做硬件,然后在原厂提供的 BSP 包上做修改,如正点原子和野火的 I.MX6ULL 开发板参考的就 是「NXP官方的I.MX6ULL EVK开发板」做的硬件:
嵌入式Linux开发,程序编译通常在电脑端的Linux(如虚拟机中的Ubuntu)下进行编译,Ubuntu 自带gcc 编译器,但该编译器是针对 X86 架构的!而嵌入式Linux是ARM架构的, 所以需要一个在 X86 架构上可以编译 ARM 架构代码的 gcc编译器,即「交叉编译器」。
交叉编译器有很多,本实验使用 Linaro 出品的交叉编译器,下载地址:
https://releases.linaro.org/components/toolchain/binaries/4.9-2017.01/arm-linux-gnueabihf/
在Ubuntu中创建目录:/usr/local/arm,并将下载的gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabihf.tar.xz
复制到此文件中,然后「解压」,解压命令如下:
sudo tar -vxf gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabihf.tar.xz
解压完成以后会生成一个名为“gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabihf”的文件夹,这个文件夹里面就是我们的交叉编译工具链。
然后,需要将该目录「添加到环境变量」中。打开/etc/profile 以后,在最后面输入如下所示内容:
export PATH=$PATH:/usr/local/arm/gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabihf/bin
使用交叉编译器之前还需要「安装其它的库」,命令如下:
sudo apt-get install lsb-core lib32stdc++6
安装完之后,可以「查看交叉编译工具的版本号」,输入如下命令:
arm-linux-gnueabihf-gcc -v
可以看到类似如下打印
以看出当前交叉编译器的版本号为 4.9.4,说明交叉编译工具链安装成功。
编译前还要在Ubuntu 中「安装ncurses 库」,安装命令如下:
sudo apt-get install libncurses5-dev
在Ubuntu中创建存放uboot的目录,如我的目录是:/home/xxpcb/myTest/imx6ull/uboot/nxp_uboot
然后,将「NXP(freescale)的uboot源码」复制进来,这里使用的是「正点原子」提供的NXP官方原版Uboot源码包( uboot-imx-rel_imx_4.1.15_2.1.0_ga.tar.bz2)
然后进行解压:
tar -vxjf uboot-imx-rel_imx_4.1.15_2.1.0_ga.tar.bz2
解压后的源码文件如下:
首先看下「uboot的配置」,configs 目录下有很多跟 I.MX6UL/6ULL 有关的配置,找到与mx6ull相同的,如下图。
因为我这个开发板是emmc版本的,所有就使用这个mx6ull_14x14_evk_emmc_defconfig
。
编译uboot使用下面3条指令:
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- -j8
这3条命令中 :
ARCH=arm
设置目标为 arm 架构
CROSS_COMPILE
指定所使用的交叉编译器。
第1条命令相当于make distclean
,目的是清除工程,一般在第一次编译的时候最好清理一下工程。
第2条指令相当于make mx6ull_14x14_evk_emmc_defconfig
,用于配置 uboot,配置文件为 mx6ull_14x14_evk_emmc_defconfig。
第3条指令相当于make -j8
,也就是使用8核来编译uboot。
为了方便的执行着3条指令,可以「将这些指令写成shell脚本」,比如在uboot源码目录下新建一个build.sh文件,写入如下内容:
#!/bin/bash
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- distclean
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- mx6ull_14x14_evk_emmc_defconfig
make V=1 ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -j8
然后进行编译:
./build.sh
编译完成以后uboot 源码多了一些文件,其中u-boot.bin
就是编译出来的 uboot二进制文件。 uboot是个裸机程序, 因此需要在其前面
加上头部(IVT、 DCD等数据)才能在I.MX6U上执行,u-boot.imx
文件就是添加头部以后的 u-boot.bin。
u-boot.imx 就是我们最终要烧写到开发板中的 uboot 镜像文件。
这是的烧录开发板,实际是要「烧录到SD卡中」,然后将SD卡插入开发板,让开发板从SD卡启动(需要在开发板上设置拨码开关来选择启动方式)。
「正点原子」专门编写了一个小软件用来将编译出来的.bin 文件烧写到 SD 卡中,这个软件叫做“imxdownload
”
将imxdownload 复制到 Ubuntu 中的uboot源码文件夹,再使用如下指令,给予 imxdownload 可执行权限:
chmod 777 imxdownload
然后「电脑USB中插入SD卡(读卡器)」,并在虚拟机中设置usb加载(VMware或VirtualBox虚拟机需要先安装「增强功能」才能使用)
然后可以使用如下指令来查看SD卡的挂载标识符:
ls /dev/sd*
查看输出结果:
这里的/dev/sdb
就是我的SD卡。
❝注:我第一次使用SD卡烧录时,只多出了/dev/sdb,但不知什么情况,用了几次后,再插入SD卡,就会同时多出来/dev/sdb和/dev/sdb1,但实际测 试,仍然把程序烧录到/dev/sdb也能用)。
❞
imxdownload向SD卡烧写led.bin文件,命令格式如下:
./imxdownload u-boot.bin /dev/sdb
❝注意不能烧写到
❞/dev/sda
或sda1
设备里面!那是系统磁盘。
烧写过程会输入如下信息:
烧写的最后一行会显示烧写大小、用时和速度,比如u-boot.bin
烧写到SD卡中的大小是 423KB,用时 1.7s,烧写速度是 236KB/s。
❝注意这个烧写速度,如果这个烧写速度在几百KB/s以下那么就是正常烧写。 如果这个烧写速度大于几十MB/s、甚至几百MB/s那么肯定是烧写失败了! 重新插拔/格式化SD卡或重启ubuntu再试。
❞
烧写完成以后会在当前工程目录下生成一个load.imx
的文件,这个文件就是软件 imxdownload 根据 NXP 官方启动方式介绍的内容, 在 bin 文件前面添加了一些数据头以后生成的。最终烧写到SD卡里面的就是这个imx文件。
烧录完之后,将「SD卡插入开发板启动」,使用「串口连接电脑」,查看uboot启动信息。
设置好串口参数(波特率115200)并打开,按键「复位开发板」。
当串口打印上出现Hit any key to stop autoboot
倒计时的时候「按下
键盘上的回车键」,默认是 3 秒倒计时,在 3 秒倒计时结束以后如果没有按下回车键的话 uboot 就会使用默认参数来启动 Linux 内核了。
如果在 3 秒倒计时结束之前按下回车键,那么就会进入 uboot 的命令行模式:
解读一下这些信息的含义:
=>
表示可以继续与uboot进行「命令交互」。看过了串口的uboot信息,再来看一下板子是实际运行情况:
由于原厂的uboot驱动的屏幕是TFT43AB (480x272),与我这里屏幕不一样,所以「屏幕没有正常显示」(现在的屏幕看起来有许多彩色的小点点),接下来,就是对uboot进行屏幕驱动的修改。
在本篇结束之前,再来研究一下uboot的串口指令。
上面说道,在uboot启动的3 秒倒计时内,串口界面如果按下了回车键,uboot就会输出符号=>
,则「可以继续与uboot进行命令交互」。那可以输入哪些命令呢?
输入help
或?
,然后按下回车即可查看当前 uboot 所支持的命令:
=> help
? - alias for 'help'
base - print or set address offset
bdinfo - print Board Info structure
bmode - sd1|sd2|qspi1|normal|usb|sata|ecspi1:0|ecspi1:1|ecspi1:2|ecspi1:3|esdhc1|esdhc2|esdhc3|esdhc4 [noreset]
bmp - manipulate BMP image data
boot - boot default, i.e., run 'bootcmd'
bootd - boot default, i.e., run 'bootcmd'
bootelf - Boot from an ELF image in memory
bootm - boot application image from memory
bootp - boot image via network using BOOTP/TFTP protocol
bootvx - Boot vxWorks from an ELF image
bootz - boot Linux zImage image from memory
clocks - display clocks
clrlogo - fill the boot logo area with black
cmp - memory compare
coninfo - print console devices and information
cp - memory copy
crc32 - checksum calculation
dcache - enable or disable data cache
dhcp - boot image via network using DHCP/TFTP protocol
dm - Driver model low level access
echo - echo args to console
editenv - edit environment variable
env - environment handling commands
erase - erase FLASH memory
exit - exit script
ext2load- load binary file from a Ext2 filesystem
ext2ls - list files in a directory (default /)
ext4load- load binary file from a Ext4 filesystem
ext4ls - list files in a directory (default /)
ext4size- determine a file's size
ext4write- create a file in the root directory
false - do nothing, unsuccessfully
fatinfo - print information about filesystem
fatload - load binary file from a dos filesystem
fatls - list files in a directory (default /)
fatsize - determine a file's size
fdt - flattened device tree utility commands
flinfo - print FLASH memory information
fstype - Look up a filesystem type
fuse - Fuse sub-system
go - start application at address 'addr'
gpio - query and control gpio pins
help - print command description/usage
i2c - I2C sub-system
icache - enable or disable instruction cache
iminfo - print header information for application image
imxtract- extract a part of a multi-image
itest - return true/false on integer compare
load - load binary file from a filesystem
loadb - load binary file over serial line (kermit mode)
loads - load S-Record file over serial line
loadx - load binary file over serial line (xmodem mode)
loady - load binary file over serial line (ymodem mode)
loop - infinite loop on address range
ls - list files in a directory (default /)
md - memory display
mdio - MDIO utility commands
mii - MII utility commands
mm - memory modify (auto-incrementing address)
mmc - MMC sub system
mmcinfo - display MMC info
mtest - simple RAM read/write test
mw - memory write (fill)
nfs - boot image via network using NFS protocol
nm - memory modify (constant address)
ping - send ICMP ECHO_REQUEST to network host
pmic - PMIC
printenv- print environment variables
protect - enable or disable FLASH write protection
reset - Perform RESET of the CPU
run - run commands in an environment variable
save - save file to a filesystem
saveenv - save environment variables to persistent storage
setenv - set environment variables
setexpr - set environment variable as the result of eval expression
sf - SPI flash sub-system
showvar - print local hushshell variables
size - determine a file's size
sleep - delay execution for some time
source - run script from memory
test - minimal test like /bin/sh
tftpboot- boot image via network using TFTP protocol
true - do nothing, successfully
usb - USB sub-system
usbboot - boot from USB device
version - print monitor, compiler and linker version
=>
命令的具体使用方法,可以输入help 命令名
或? 命令名
查看,以“bootz”这个命令为例:
=> help bootz
bootz - boot Linux zImage image from memory
Usage:
bootz [addr [initrd[:size]] [fdt]]
- boot Linux zImage stored in memory
The argument 'initrd' is optional and specifies the address
of the initrd in memory. The optional argument ':size' allows
specifying the size of RAW initrd.
When booting a Linux kernel which requires a flat device-tree
a third argument is required which is the address of the
device-tree blob. To boot that kernel without an initrd image,
use a '-' for the second argument. If you do not pass a third
a bd_info struct will be passed instead
=>
常用的和信息查询有关的命令有3个:bdinfo
、printenv
和version
。
=> bdinfo
arch_number = 0x00000000
boot_params = 0x80000100
DRAM bank = 0x00000000
-> start = 0x80000000
-> size = 0x20000000
eth0name = FEC1
ethaddr = (not set)
current eth = FEC1
ip_addr = <NULL>
baudrate = 115200 bps
TLB addr = 0x9FFF0000
relocaddr = 0x9FF47000
reloc off = 0x18747000
irq_sp = 0x9EF44EA0
sp start = 0x9EF44E90
FB base = 0x00000000
=>
从打印信息可以得出DRAM的「起始地址和大小、启动参数保存起始地址、波特率、sp(堆栈指针)起始地址」等信息.
=> printenv
baudrate=115200
board_name=EVK
board_rev=14X14
boot_fdt=try
bootcmd=run findfdt;mmc dev ${mmcdev};mmc dev ${mmcdev}; if mmc rescan; then if run loadbootscript; then run bootscript; else if run loadimage; thn run mmcboot; else run netboot; fi; fi; else run netboot; fi
bootcmd_mfg=run mfgtool_args;bootz ${loadaddr} ${initrd_addr} ${fdt_addr};
bootdelay=3
bootscript=echo Running bootscript from mmc ...; source
console=ttymxc0
ethact=FEC1
ethprime=FEC
fdt_addr=0x83000000
fdt_file=undefined
fdt_high=0xffffffff
findfdt=if test $fdt_file = undefined; then if test $board_name = EVK && test $board_rev = 9X9; then setenv fdt_file imx6ull-9x9-evk.dtb; fi; if tst $board_name = EVK && test $board_rev = 14X14; then setenv fdt_file imx6ull-14x14-evk.dtb; fi; if test $fdt_file = undefined; then echo WARNING:Could not determine dtb to use; fi; fi;
image=zImage
initrd_addr=0x83800000
initrd_high=0xffffffff
ip_dyn=yes
loadaddr=0x80800000
loadbootscript=fatload mmc ${mmcdev}:${mmcpart} ${loadaddr} ${script};
loadfdt=fatload mmc ${mmcdev}:${mmcpart} ${fdt_addr} ${fdt_file}
loadimage=fatload mmc ${mmcdev}:${mmcpart} ${loadaddr} ${image}
mfgtool_args=setenv bootargs console=${console},${baudrate} rdinit=/linuxrc g_mass_storage.stall=0 g_mass_storage.removable=1 g_mass_storage.file=fat g_mass_storage.ro=1 g_mass_storage.idVendor=0x066F g_mass_storage.idProduct=0x37FF g_mass_storage.iSerialNumber="" clk_ignore_unused
mmcargs=setenv bootargs console=${console},${baudrate} root=${mmcroot}
mmcautodetect=yes
mmcboot=echo Booting from mmc ...; run mmcargs; if test ${boot_fdt} = yes || test ${boot_fdt} = try; then if run loadfdt; then bootz ${loadaddr} -${fdt_addr}; else if test ${boot_fdt} = try; then bootz; else echo WARN: Cannot load the DT; fi; fi; else bootz; fi;
mmcdev=0
mmcpart=1
mmcroot=/dev/mmcblk0p2 rootwait rw
netargs=setenv bootargs console=${console},${baudrate} root=/dev/nfs ip=dhcp nfsroot=${serverip}:${nfsroot},v3,tcp
netboot=echo Booting from net ...; run netargs; if test ${ip_dyn} = yes; then setenv get_cmd dhcp; else setenv get_cmd tftp; fi; ${get_cmd} ${imag}; if test ${boot_fdt} = yes || test ${boot_fdt} = try; then if ${get_cmd} ${fdt_addr} ${fdt_file}; then bootz ${loadaddr} - ${fdt_addr}; else if est ${boot_fdt} = try; then bootz; else echo WARN: Cannot load the DT; fi; fi; else bootz; fi;
panel=TFT43AB
script=boot.scr
Environment size: 2431/8188 bytes
=>
这里有很多的环境变量, 比如「baudrate、 board_name、 board_rec、 boot_fdt、 bootcmd」等。比如bootdelay这个环境变量就表示 uboot 启动延时时间,默认 bootdelay=3,也就默认延时 3秒。前面说的 3 秒倒计时就是由 bootdelay 定义的。另外uboot中的环境变量都是字符串。
=> version
U-Boot 2016.03 (Jul 11 2021 - 15:22:25 +0800)
arm-linux-gnueabihf-gcc (Linaro GCC 4.9-2017.01) 4.9.4
GNU ld (Linaro_Binutils-2017.01) 2.24.0.20141017 Linaro 2014_11-3-git
=>
当前uboot 版本号为 2016.03,编译日期2021/7/11,编译器为arm-linux-gnueabihf-gcc。
我们介绍了如何使用NXP原厂的uboot进行编译和烧写,将uboot运行在自己的开发板上。NXP原厂的uboot,直接烧录到我的开发板中,LCD的驱动是不正常的,需要进行修改。本篇我们就来继续研究uboot,「使得uboot能匹配我们自己的开发板」。
修改uboot以匹配开发板的方式有两种,一种是在NXP原厂开发板「i.MX 6ULL EVK」的文件上进行修改,另一种仿造NXP的开发板文件,添加自己的开发板文件。
为了能更多的了解uboot,我们使用代码改动较大的第二种方式进行uboot的移植。
在修改uboot之前,先来看一下uboot的源码结构。
uboot的源码如下,这里是源码编译后的结果,包含编译后的文件。
这里文件的含义如下:
首先是「创建自己开发板的配置文件」,该文件可参考原厂开发板的配置文件,在configs
文件夹下,将原来的默认配置文件mx6ull_14x14_evk_emmc_defconfig
复制一份,并重命名为mx6ull_myboard_defconfig
,该文件即用于作为自己开发板的配置文件。
然后进行「内容修改」,将原始内容:
CONFIG_SYS_EXTRA_OPTIONS="IMX_CONFIG=board/freescale/mx6ullevk/imximage.cfg,MX6ULL_EVK_EMMC_REWORK"
CONFIG_ARM=y
CONFIG_ARCH_MX6=y
CONFIG_TARGET_MX6ULL_14X14_EVK=y
CONFIG_CMD_GPIO=y
修改为:
在目录 include/configs
下添加自己开发板对应的头文件,复制mx6ullevk.h
,并重命名为mx6ull_myboard.h
,将文件中的
#ifndef __MX6ULLEVK_CONFIG_H
#define __MX6ULLEVK_CONFIG_H
修改为:
该文件里面有很多宏定义,这些宏定义基本用于配置uboot,如果我们自己要想使能或者禁止uboot的某些功能,那就要在这里面修改。
❝在ubuntu中,可以安装VS Code软件来辅助查看代码,在ubuntu中安装vscode,需要先下载deb格式的安装包,然后使用类似如下的指令即可进行安装:
sudo dpkg -i code_1.58.0-1625728071_amd64.deb
安装完之后,我们可以将图标添加到ubuntu桌面上,ubuntu安装的所有软件图标都在目录
❞/usr/share/applications
中,找到 Visual Studio Code 的图标,然后点击鼠标右键,选择复制到->桌面即可。
uboot中每个板子都有一个对应的文件夹来存放板级文件(如开发板上外设驱动文件等)。NXP的I.MX系列芯片的所有板级文件夹都存放在 board/freescale/
目录下,在这个目录下有个名为mx6ullevk
的文件夹,原厂开发板的板级文件夹。
复制 mx6ullevk,将其重命名为mx6ull_myboard
,进入mx6ull_myboard
目录中, 将其中的mx6ullevk.c
文件重命名为mx6ull_myboard.c
。
首先是修改 board/freescale/mx6ull_myboard 目录下的Makefile
文件
将原始内容:
# (C) Copyright 2015 Freescale Semiconductor, Inc.
#
# SPDX-License-Identifier: GPL-2.0+
#
obj-y := mx6ullevk.o
extra-$(CONFIG_USE_PLUGIN) := plugin.bin
$(obj)/plugin.bin: $(obj)/plugin.o
$(OBJCOPY) -O binary --gap-fill 0xff $<$@
其中的依赖项修改为:
obj-y := mx6ull_myboard.o
这样才会编译mx6ull_myboard.c
这个文件。
然后修改 board/freescale/mx6ull_myboard 目录下的imximage.cfg
文件
将imximage.cfg
中的下面一句:
PLUGIN board/freescale/mx6ullevk/plugin.bin 0x00907000
改为:
PLUGIN board/freescale/mx6ull_myboard/plugin.bin 0x00907000
接着修改 board/freescale/mx6ull_myboard 目录下的Kconfig
文件
将原始内容:
if TARGET_MX6ULL_14X14_EVK || TARGET_MX6ULL_9X9_EVK
config SYS_BOARD
default "mx6ullevk"
config SYS_VENDOR
default "freescale"
config SYS_CONFIG_NAME
default "mx6ullevk"
endif
修改为:
再接着修改 board/freescale/mx6ull_myboard 目录下的MAINTAINERS
文件
将原始内容:
MX6ULLEVK BOARD
M: Peng Fan <peng.fan@nxp.com>
S: Maintained
F: board/freescale/mx6ullevk/
F: include/configs/mx6ullevk.h
F: configs/mx6ull_14x14_evk_defconfig
F: configs/mx6ull_9x9_evk_defconfig
修改为:
将 board/freescale/mx6ull_myboard 目录下原来的mx6ullevk.c
重命名为mx6ull_myboard.c
最后修改arch/arm/cpu/armv7/mx6/
目录下的Kconfig
文件
❝注意这里的Kconfig和
❞board/freescale/mx6ull_myboard
目录下的Kconfig是不一样的。
在207行插入一些内容:
config TARGET_MX6ULL_MYBOARD
bool "Support mx6ull_myboard"
select MX6ULL
select DM
select DM_THERMAL
然后,在最后一行的endif
的前一行添加如下内容:
source"board/freescale/mx6ull_myboard/Kconfig"
在uboot-imx-rel_imx_4.1.15_2.1.0_ga目录下新建一个名为build_myboard.sh
的 shell 脚本,写入如下内容:
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- distclean
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- mx6ull_myboard_defconfig
make V=1 ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -j8
至此,以上完成的工作,相当于将NXP原厂开发板相关的配置文件,重新复制了一份,并对板子名称修改为了自己板子的名字。
此时执行./build_myboard.sh
,等待编译完成后输入如下命令:
grep -nR "mx6ull_myboard.h"
如果有很多文件都引用了这个头文件, 那就说明新板子添加成功:
将uboot进行编译并运行,实际的效果应该和原厂uboot的效果一样(LCD无法显示)。
「总结一下刚才都有哪些修改」:
右端灰色的为原厂开发板的相关文件,黄色的为模仿原厂文件,新添加并修改的自己开发板的文件。
下面进行LCD驱动的修改。
一般uboot中修改驱动都是在对应板子c文件和h文件,即board/freescale/mx6ull_myboard/mx6ull_myboard.c
和 include/configs/mx6ull_myboard.h
这两个文件。
一般修改 LCD 驱动重点注意以下几点:
「正点原子」以及「野火」的I.MX6ULL开发板的LCD原理图和NXP官方的开发板一致,也就是LCD的IO和背光IO都是一样的, 所以IO部分就不用修改了,只需修改之后的LCD参数。
打开文件 mx6ull_myboard.c
,需要修改下面这段内容:
struct display_info_t const displays[] = {{
.bus = MX6UL_LCDIF1_BASE_ADDR,
.addr = 0,
.pixfmt = 24,
.detect = NULL,
.enable = do_enable_parallel_lcd,
.mode = {
.name = "TFT43AB",
.xres = 480,
.yres = 272,
.pixclock = 108695,
.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 中,定义如下:
struct display_info_t {
int bus;
int addr;
int pixfmt;
int (*detect)(struct display_info_tconst *dev);
void (*enable)(struct display_info_tconst *dev);
struct fb_videomode mode;
};
这里的pixfmt
是像素格式,也就是一个像素点是多少位,如果是RGB565的话就是16位,如果是RGB888的话就是24位,一般使用 RGB888。
结构体display_info_t
还有个mode
成员变量,此成员变量也是个结构体,为fb_videomode
,定义在文件 include/linux/fb.h
中,定义如下:
struct fb_videomode {
constchar *name; /* optional */
u32 refresh; /* optional */
u32 xres;
u32 yres;
u32 pixclock;
u32 left_margin;
u32 right_margin;
u32 upper_margin;
u32 lower_margin;
u32 hsync_len;
u32 vsync_len;
u32 sync;
u32 vmode;
u32 flag;
};
结构体b_videomode
里面的成员变量为LCD的参数,这些成员变量函数如下:
name
:LCD 名字,要和环境变量中的 panel 相等xres 、yres
:LCD X 轴和 Y 轴像素数量pixclock
:像素时钟,每个像素时钟周期的长度,单位为皮秒left_margin
:HBP(horizontal back porch),水平同步后肩right_margin
:HFP(horizontal front porch),水平同步前肩upper_margin
:VBP(vertical back porch),垂直同步后肩lower_margin
:VFP(vertical front porch),垂直同步前肩hsync_len
:HSPW(horizontal sync pulse width),行同步脉宽vsync_len
:VSPW(vertical sync pulse width),垂直同步脉宽vmode
:大多数使用 FB_VMODE_NONINTERLACED,也就是不使用隔行扫描。这些参数需要与实用的LCDd的参数一致。
「野火的7寸RGB屏幕」(GT911,800x480)的一些参数如下:
参数 | 值 |
---|---|
width | 800 |
height | 480 |
HBP | 46 |
HFP | 22 |
VBP | 23 |
VFP | 22 |
HSW | 1 |
VSW | 1 |
注意像素时钟pixclock
的计算方法:以野火的 7 寸RGB屏为例,屏幕要求的像素时钟为27.4MHz,因此:pixclock=(1/27400000)*10^12=36496
❝像素时钟就是 RGB LCD 的时钟信号,以 GT911这款屏幕为例,显示一帧图像所需要的时钟数就是:(VSPW+VBP+LINE+VFP) * (HSPW + HBP + HOZVAL + HFP) = (1 + 23 + 480+ 22) * (1+ 46+ 800+ 22)
❞
= 526* 869 = 457094。显示一帧图像需要457094个时钟数, 那么显示60帧就是:457094* 60 = 27425640≈27.4M,所以像素时钟就是27.4MHz
由以上的屏幕参数,可以得出GT911屏幕的配置参数如下:
struct display_info_t const displays[] = {{
.bus = MX6UL_LCDIF1_BASE_ADDR,
.addr = 0,
.pixfmt = 24,
.detect = NULL,
.enable = do_enable_parallel_lcd,
.mode = {
.name = "GT911",
.xres = 800,
.yres = 480,
.pixclock = 36496,
.left_margin = 46, //HBPD
.right_margin = 22, //HFPD
.upper_margin = 23, //VBPD
.lower_margin = 22, //VFPD
.hsync_len = 1, //HSPW
.vsync_len = 1, //VSPW
.sync = 0,
.vmode = FB_VMODE_NONINTERLACED
} } };
另外还要修改include/configs/
路径下的mx6ull_myboard.h
,找到所有如下语句:
panel=TFT43AB
修改为:
panel=GT911 //与mx6ull_myboard.c中修改的名称保持一致
修改完成以后重新编译一遍 uboot 并烧写到 SD 中启动。
将修改后的uboot编译下载以后,LCD 驱动一般就会工作正常了,LCD 上会显示 NXP 的 logo。
但某些情况有可能还会遇到LCD 并没有工作,还是黑屏,这是什么原因呢?
在 uboot 命令模式输入“print
”来查看环境变量 panel 的值,会发现panel的值要是TFT43AB(或其他的,反正不是GT911):
panel=TFT43AB
script=boot.scr
Environment size: 2431/8188 bytes
=>
这是因为之前有将环境变量保存到EMMC中,uboot启动以后会先从EMMC中读取环境变量,如果EMMC中没有环境变量的话才会使用 mx6ull_alientek_emmc.h 中的默认环境变量。
如果EMMC中的环境变量panel不等于GT911,那么LCD显示肯定不正常,我们只需要在uboot中修改panel的值为GT911即可,在uboot的命令模式下输入如下命令:
setenv panel GT911
saveenv
上述命令修改环境变量panel为GT911并保存后,按下复位键重启uboot,此时 LCD 驱动就工作正常了。
I.MX6ULL内部有个以太网MAC外设,也就是ENET,需要外接一个PHY芯片来实现网络通信功能,也就是「内部MAC+外部PHY芯片」的方案。I.MX6ULL有两个网络接口ENET1和ENET2,野火的开发板提供了这两个网络接口,其中ENET1和ENET2都使用是和原厂开发板一样的KSZ8081
作为PHY芯片。
因此,网络驱动部分的uboot不需要修改,下面就只是来测试一下网路功能。
首先将开发板通过网线连接到局域网的路由器中(自己的电脑也要在同一个局域网,这样ubuntu虚拟机则也在同一个局域网)。
然后启动uboot,串口查看相关的打印信息,如下图,可以看到网络端口的FEC1(注意是uboot程序中默认设置的,不是因为网线插在了左边就自动识别FEC1),但是提示网络地址未设置。
下面就来设置一下,首先是设置开发板的IP,在设置之前,先借助Windows电脑的cmd的ping+ip
指令来测试某个IP是否被使用,如我的192.168.5.102
未被使用,就可以设为开发板的IP。
除了设置开发板的IP,还要设置一些其它的网络参数,具体如下:
setenv ipaddr 192.168.5.102//开发板 IP 地址
setenv ethaddr 00:04:9f:04:d2:35//开发板网卡 MAC 地址
setenv gatewayip 192.168.5.1//开发板默认网关
setenv netmask 255.255.255.0//开发板子网掩码
setenv serverip 192.168.5.101//服务器地址,也就是 Ubuntu 地址
saveenv //保存环境变量
开发板的MAC地址是一个长度为48位(6个字节)的地址,每个字节间通过冒号间隔,理论上只要局域网内各网络设备不冲突,该地址可任意设置。
局域网的默认网关和子网掩码需要根据自己的实际情况设置(不知道是多少的,可以借助Windows电脑的cmd中的ipconfig
指令来查看)
服务器的地址就是ubuntu虚拟机的地址(可以通过linux的ifconfig
指令来查看)
打开 include/configs/mx6ull_alientek_emmc.h ,将CONFIG_FEC_ENET_DEV
修改为 0, 重新编译uboot并烧写到SD卡中。
将网线连接到开发板右边的网口上,按照之前的测试方法再次测试:
uboot的最终目的就是启动Linux内核,所以需要通过启动Linux内核来判断uboot移植是否成功。
启动Linux内核。我们测试两种启动Linux内核的方法:
「从EMMC启动」也就是将编译出来的「Linux镜像文件zImage」和「设备树文件」保存在EMMC中,uboot从EMMC中读这两个文件并启动。由于我们板子的EMMC中可能还没有linux镜像文件和设备树文件,所以先不测试这种方法。
「从网络启动」,是指将linux镜像文件和根文件系统都放到Ubuntu下某个指定的文件夹中,然后通过nfs或者tftp等传输方式将系统文件(zImage和设备树文件)从Ubuntu中直接下载到开发板的内存中,EMMC中则不需要有系统文件。这种方式的作用就是方便调试,免去将代码固化到开发板的过程。当然,当开发板掉电,内存的系统文件就没了。
下面就来通过网络调试的方法来测试uboot是否能正常启动Linux内核。
「在测试之前,先来介绍一下在ubuntu虚拟机上如何搭建tftp来传输文件」。
Ubuntu上搭建TFTP服务器,需要安装tftp-hpa
和tftpd-hpa
,命令如下:
sudo apt-get install tftp-hpa tftpd-hpa
sudo apt-get install xinetd
TFTP也需要一个文件夹来存放文件,在用户目录下新建一个目录,示例命令如下:
mkdir /home/xxpcb/myTest/tftpdir
chmod 777 /home/xxpcb/myTest/tftpdir
最后配置 tftp, 安装完成以后,新建文件/etc/xinetd.d/tftp
, 如果没有/etc/xinetd.d 目录的话自行创建,然后在里面输入如下内容:
server tftp
{
socket_type = dgram
protocol = udp
wait = yes
user = root
server = /usr/sbin/in.tftpd
server_args = -s /home/xxpcb/myTest/tftpdir/
disable = no
per_source = 11
cps = 100 2
flags = IPv4
}
完了以后启动tftp服务,命令如下:
sudo service tftpd-hpa start
打开/etc/default/tftpd-hpa
文件,将其修改为如下所示内容:
# /etc/default/tftpd-hpa
TFTP_USERNAME="tftp"
TFTP_DIRECTORY="/home/xxpcb/myTest/tftpdir"
TFTP_ADDRESS=":69"
TFTP_OPTIONS="-l -c -s"
TFTP_DIRECTORY
就是我们上面创建的tftp文件夹目录,以后我们就将所有需要通过TFTP传输的文件都放到这个文件夹里面,并且要给予这些文件相应的权限。
最后输入如下命令, 重启 tftp 服务器:
sudo service tftpd-hpa restart
至此,tftp服务器已经搭建好了,可以先来测试一下功能是否正常。
测试tftp功能是否正常,主要分为两步:
首先是「将某个zImage镜像文件拷贝到ubuntu虚拟机的tftpboot文件夹中」,并且给予 zImage 相应777的权限。
然后是「通过开发板uboot的串口交互指令将文件从ubuntu传输到开发板的内存」。
uboot串口交互指令中的「tftp命令格式」如下:
tftpboot [loadAddress] [[hostIPaddr:]bootfilename]
loadAddress
是文件在DRAM中的存放地址,[[hostIPaddr:]bootfilename]
是要从Ubuntu中下载的文件。
tftp传输文件,不需要输入文件在Ubuntu中的完整路径,只需要输入文件名即可。
比如我们现在「将tftpboot文件夹里面的zImage文件下载到开发板DRAM的0X80800000地址处」,命令如下:
tftp 80800000 zImage
❝注:此次测试时,我的ubuntu虚拟机(作为tftp服务器)的IP变了,所以我又重新设置了ubuntu的IP
❞
设置环境变量
这两个环境变量的具体含义先不展开讨论。
setenv bootargs 'console=ttymxc0,115200 root=/dev/mmcblk1p2 rootwait rw'
setenv bootcmd 'tftp 80800000 zImage; tftp 83000000 imx6ull-14x14-evk-emmc.dtb; bootz 80800000 - 83000000'
saveenv
通过tftp将zImage和设备树下载到板子的RAM中
就是通过网路的方式(tftp)将系统文件下载到板子的内存中,这里使用的「野火提供的yocto的zImage和dtb文件」,将两个文件辅助到ubuntu的tftp服务器目录,依次输入如下指令:
tftp 80800000 zImage
tftp 83000000 imx6ull-14x14-evk-emmc.dtb
bootz 80800000 - 83000000
可以看到「Starting kernel ...」 的字样,表示内核已经启动。
再看看下板子,已经有启动画面了:
在过一会儿,会出现系统的图形界面,只是现在还不能操作,触摸没反应。
至此,uboot的移植基本完成,可以启动Linux内核。启动内核之后,uboot的使命就完成了。
官网:https://www.kernel.org/
NXP 会从linux内核官网下载某个版本,然后将其移植到自己的 CPU上,测试成功后就会将其开放给NXP的CPU开发者。开发者下载 NXP 提供的 Linux 内核,然后将其移植到自己的产品上。
本文我们就使用NXP提供的Linux源码,文件名为:linux-imx-rel_imx_4.1.15_2.1.0_ga.tar.bz2
编译内核之前需要先在ubuntu上安装lzop库,另外,图形化配置工具还需要ncurses库支持,安装命令为:
sudo apt-get install lzop
sudo apt-get install build-essential
sudo apt-get install libncurses5-dev
在Ubuntu中新建一个文件夹,然后将linux内核压缩包拷贝到文件夹中并解压,解压命令为:
tar -vxf linux-imx-rel_imx_4.1.15_2.1.0_ga.tar.bz2
解压完成后
进入该文件夹,新建一个build.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 -j8
给予该脚本可执行权限,然后运行,编译的时候会弹出Linux图形配置界面, 这里不需要做任何的配置, 直接按两下ESC键退出图形界面
之后会自动开始编译Linux内核。
编译完成以后就会在arch/arm/boot
这个目录下生成一个zImage
文件,该文件就是要用的Linux镜像文件。另外也会在arch/arm/boot/dts
下生成很多.dtb 文件,这些.dtb 就是设备树文件。
vmlinux 、Image ,zImage 、uImage 的区别
vmlinux
是ELF格式的文件,是编译出来的最原始的内核文件,编译出来 差不多有16MB,是未压缩的。在实际中我们不会使用vmlinux,而是使用zImage或uImage这样的 Linux 内核镜像文件。
Image
是Linux内核镜像文件,但是Image仅包含可执行的二进制数据。Image就是使用objcopy取消掉vmlinux中的一些其他信息,比如符号表什么的。但是 Image 是没有压缩过的,Image保存在arch/arm/boot目录下,其大小大概在12MB 。
zImage
是经过gzip压缩后的Image,经过压缩以后其大小大概在6MB左右。
uImage
是老版本uboot专用的镜像文件,uImag是在zImage前面加了一个长度为 64字节的“头” ,这个头信息描述了该镜像文件的类型、加载位置、生成时间、大小等信息。但是新的uboot已经支持了 zImage 启动!所以已经很少用到uImage了。
Linux内核编译过程会生成一些文件,下面来看一下编译后的内核源码结构,可以看出多出了一些编译文件
具体描述如下:
arch目录
这个目录是和架构有关的目录,比如arm、arm64、avr32、x86等等架构。每种架构都对应一个目录,在这些目录中又有很多子目录,比如boot、common、configs等等。
block目录 block是Linux下块设备目录, 像SD卡、EMMC、NAND、硬盘等存储设备就属于块设备,block目录中存放着管理块设备的相关文件。
crypto目录 crypto目录里面存放着加密文件,比如常见的crc、crc32、md4、md5、hash等加密算法。
Documentation目录 此目录里面存放着Linux相关的文档,如果要想了解Linux某个功能模块或驱动架构的功能,就可以在Documentation目录中查找有没有对应的文档。
drivers目录 驱动目录文件,此目录根据驱动类型的不同,分门别类进行整理,比如drivers/i2c就是I2C相关驱动目录,drivers/gpio就是GPIO相关的驱动目录,这是我们学习的重点。
firmware 目录 此目录用于存放固件。
fs目录 此目录存放文件系统,比如fs/ext2、fs/ext4、fs/f2fs等,分别是ext2、ext4 和 f2fs等文件系统。
将编译出来的zImage和imx6ull-14x14-evk.dtb复制到Ubuntu中的tftp目录下,之后会通过uboot 的tftp命令将其下载到开发板中。
在测试之前确保uboot中的环境变量bootargs内容如下(使用print指令查看):
console=ttymxc0,115200 root=/dev/mmcblk1p2 rootwait rw
如果不是的话,可以使用如下指令设置一下:
setenv bootargs 'console=ttymxc0,115200 root=/dev/mmcblk1p2 rootwait rw'
saveenv
然后可以测试了,启动开发板,串口中进入uboot命令行模式,然后输入如下命令将zImage和imx6ull-14x14-evk.dtb下载到开发板中并启动:
tftp 80800000 zImage
tftp 83000000 imx6ull-14x14-evk.dtb
bootz 80800000 - 83000000
可以看到内核启动了
最后到了系统登录处,说明Linux系统正常启动了(这次LCD上没有了野火的图形界面,可能是某些固件不匹配吧,先忽略)
Linux内核启动以后是需要根文件系统的,根文件系统存在哪里是由uboot的bootargs环境变量指定, bootargs会传递给Linux内核作为命令行参数 。比如之前设置的root=/dev/mmcblk1p2,也就是说根文件系统存储在/dev/mmcblk1p2中,即EMMC的分区2中。
因为上一篇的测试时,EMMC的分区2中烧写好了根文件系统,所以设置root=/dev/mmcblk1p2,并且内核正常启动。如果我们不设置根文件系统路径,或者说根文件系统路径设置错误的话会出现什么问题?
我们将uboot中的bootargs环境变量改为“console=ttymxc0,115200” ,也就是不填写root的内容了,命令如下:
setenv bootargs 'console=ttymxc0,115200'
saveenv
修改完成以后重新从网络启动,可以看到也是先启动了内核:
但启动以后会有类似如下的错误:
最后会有下面这一行:
Kernel panic - not syncing: VFS: Unable to mount root fs on unknown-block(0,0)
提示内核崩溃,因为VFS(虚拟文件系统)不能挂载根文件系统,目录不存在。即使目录存在,如果根文件系统目录里面是空的依旧会提示内核崩溃。
编译NXP官方I.MX6ULL EVK开发板对应的Linux内核,发现其可以在野火的EMMC版本开发板启动。为了进一步了解Linux内核,我们可以参考官方开发板的设置,在Linux内核中添加自己的开发板。
将arch/arm/configs
目录下的imx_v7_mfg_defconfig
重新复制一份 , 命名为自己开发板,如imx_myboard_defconfig
。
进入arch/arm/boot/dts
目录中,复制一份imx6ull-14x14-evk.dts
,然后将其重命名为imx6ull-myboard.dts
。
然后还需要修改文件arch/arm/boot/dts/Makefile
,找到 dtb-$(CONFIG_SOC_IMX6ULL)
配置项,在此配置项中加入“imx6ull-myboard.dtb” :
这样编译Linux的时候就可以从imx6ull-myboard.dts 编译出 imx6ull-myboard.dtb 文件了。
总结一下以上的修改主要包括:
主要就是对文件复制一份并重命名,唯一修改的是Makefile文件。
新建一个build_myboard.sh,写入如下内容:
#!/bin/sh
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- distclean
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- imx_myboard_defconfig
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- menuconfig
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- all -j8
编译出zImage(arch/arm/boot目录)和imx6ull-myboard.dtb (arch/arm/boot/dts目录)后再次进行Linux启动测试,可以到登录提示,说明Linux内核启动成功。
本篇进行根文件系统的构建,这是Linux移植三大组成部分的最后一步,根文件系统构建好后,就构成了一个基础的、可以运行的嵌入式Linux最小系统。
Linux的根文件系统一般也叫做 rootfs,Linux的根文件系统更像是一个文件夹或者叫做目录,在这个目录里面会有很多的子目录。根目录下和子目录中会有很多的文件,这些文件是Linux运行所必须的,比如库、常用的软件和命令、设备文件、配置文件等等。
根文件系统的这个“根”字就说明了这个文件系统的重要性,它是其他文件系统的根,没有这个“根” ,其他的文件系统或者软件就别想工作。比如我们常用的 ls、mv、ifconfig 等命令其实就是一个个小软件,只是这些软件没有图形界面,而且需要输入命令来运行。这些小软件就保存在根文件系统中。
在构建根文件系统之前,先来看一下根文件系统里面都有些什么内容,根文件系统的目录名字为‘/’ ,就是一个斜杠:
根文件系统的各个文件夹的作用如下:
目录 | 描述 |
---|---|
/bin | 此目录下存放着系统需要的可执行文件,一般都是一些命令,比如 ls、mv 等命令 |
/dev | dev 是 device 的缩写,所以此目录下的文件都是和设备有关的。在Linux下一切皆文件,即使是硬件设备,也是以文件的形式存在的,比如/dev/ttymxc0就表示串口0 |
/etc | 此目录下存放着各种配置文件 |
/lib | lib是library的简称,也就是库的意思,因此此目录下存放着Linux所必须的库文件 |
/mnt | 临时挂载目录,一般是空目录,可以在此目录下创建空的子目录,比如/mnt/sd、/mnt/usb,这样就可以将SD卡或者U盘挂载到/mnt/sd 或者/mnt/usb 目录中 |
/proc | 此目录一般是空的,当Linux系统启动以后会将此目录作为proc文件系统的挂载点,proc是个虚拟文件系统,没有实际的存储设备。proc里面的文件都是临时存在的,一般用来存储系统运行信息文件 |
/usr | 注意,usr不是user的缩写,而是Unix Software Resource的缩写,即Unix操作系统软件资源目录。Linux 一般被称为类Unix操作系统。既然是软件资源目录,因此/usr 目录下也存放着很多软件,一般系统安装完成以后此目录占用的空间最多 |
/var | 此目录存放一些可以改变的数据 |
/sbin | 此目录页用户存放一些可执行文件, 但是此目录下的文件或者说命令只有管理员才能使用,主要用于系统管理 |
/sys | 系统启动以后此目录作为 sysfs 文件系统的挂载点,sysfs是一个类似于 proc文件系统的特殊文件系统,sysfs也是基于RAM的文件系统,也就是说它也没有实际的存储设备。此目录是系统设备管理的重要目录 |
/opt | 可选的文件、软件存放区,由用户选择将哪些文件或软件放到此目录中 |
BusyBox是一个集成了大量的Linux命令(如ls、mv、ifconfig 等命令)和工具的软件。借助BusyBox,进行配置和编译,就可以方便的构建一个嵌入Linux平台所需要的根文件系统。
课程BusyBox官网https://busybox.net/下载源码,如下图。
左侧的“Get BusyBox”栏有一行“Download Source” ,点击“Download Source”即可打开 BusyBox 的下载页。
目前最新的 BusyBox 版本是1.33.1,但这里使用正点原子提供的1.29.0版本的BusyBox(busybox-1.29.0.tar.bz2)
一般在Linux驱动开发的时候都是通过NFS挂载根文件系统的,当调试好之后再将根文件系统烧写到 EMMC或者NAND中,因此需要先在ubuntu虚拟机中构建NFS服务:
sudo apt-get install nfs-kernel-server rpcbind
等待安装完成,在合适的地方新建一个名为“nfs”的文件夹,供NFS服务器使用。
如我的创建目录为:/home/xxpcb/myTest/nfs
在使用NFS之前,还需要先配置NFS,修改配置文件/etc/exports
,在后面添加如下所示内容:
/home/xxpcb/myTest/nfs *(rw,sync,no_root_squash)
最后重启NFS服务即可:
sudo /etc/init.d/nfs-kernel-server restart
正常情况会出现如下图,表示设置成功:
注:我第一次设置时,文件路径中的一个大小写字母搞错了,导致重启NFS时提示失败(如下图),所以在设置时要注意细节!
在nfs服务器目录中创建一个名为rootfs
的子目录,用来存放我们的根文件系统。
将busybox-1.29.0.tar.bz2
发送到Ubuntu中的合适位置(我存放在 /home/xxpcb/myTest/imx6ull/dts)并解压:
tar -vxjf busybox-1.29.0.tar.bz2
解压后的文件如下:
注:这一步可以不修改,这里修改Makefile的目的是为了在编译时,可以不用在指定编译器的架构,从而可以缩短手动输入指令的长度。但我此次测试时,修改Makefile后,输入make指令的命令进行编译时,不指定编译器,还是会提示编译器找不到之类的问题。所以,此次的测试,我就没有修改这个Makefile。
如果坚持要修改Makefile,就是修改如下的地方,指定编译器与架构(本篇进行实验时没有修改)。
现在如果直接编译busybox的,在使用串口工具的时候是不支持中文显示的,中文字符会显示为“?” 。可以通过busybox源码,来取消 busybox对中文显示的限制。
打开文件busybox-1.29.0/libbb/printable_string.c,找到函数printable_string,吧某些程序注释掉,修改后的函数内容如下:
主要就是禁止字符大于0X7F以后 break 和输出‘?’
接着打开文件busybox-1.29.0/libbb/unicode.c,修改如下内容:
有以下几种配置选项:
一般使用默认配置即可,因此使用如下命令先使用默认配置来配置一下 busybox:
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- defconfig
busybox也支持图形化配置,通过图形化配置我们可以进一步选择自己想要的功能,输入如下命令打开图形化配置界面:
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- menuconfig
选项“Build static binary (no shared libs)”用来决定是静态编译还是动态编译,静态编译的话就不需要库文件,但是编译出来的库会很大。动态编译的话要求根文件系统中有库文件,但是编译出来的 busybox 会小很多。这里我们不使用静态编译,所以保持默认不选即可。
这个要勾选,通过按键“y”实现勾选,使得方括号内出现星号
默认会选中“Simplified modutils” ,这里我们要取消勾选!使用键盘上的“n”键取消方括号中的星号。
确保下面的全部选中,默认都是选中的
要将默认没有勾选的Check $LC_ALL
项选中!
最后按两下ESC退出设置,并选择YES保持存。
输入如下指令进行编译:
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf-
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- install CONFIG_PREFIX=/home/xxpcb/myTest/nfs/rootfs
编译完成以后, busybox的所有工具和文件就会被安装到rootfs目录中,如下图:
rootfs目录下有bin、sbin和usr三个目录,以及linuxrc文件。Linux内核linit进程最后会查找用户空间的init程序,找到以后就会运行这个用户空间的init程序,从而切换到用户态。如果bootargs设置init=/linuxrc,那么linuxrc就是可以作为用户空间的init程序。
busybox编译完成后,此时的根文件系统还不能使用, 还需要一些其他的文件。
上面的busybox使用的是动态库编译,所以还需要向根文件系统中添加动态库。
先在rootfs中创建一个名为“lib”的文件夹。lib库文件从交叉编译器中获取,之前搭建交叉编译环境的时候将交叉编译器存放到了“/usr/local/arm/”目录中,进入对应的目录:
cd /usr/local/arm/gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabihf/arm-linux-gnueabihf/libc/lib
此目录下有很多的so和.a 文件,这些就是库文件,将此目录下所有的so和.a文件都拷贝到 rootfs/lib 目录中:
cp *so* *.a /home/xxpcb/myTest/nfs/rootfs/lib/ -d
后面的“-d”表示拷贝符号链接,这里有个比较特殊的库文件:ld-linux-armhf.so.3,此库文件也是个符号链接,相当于 Windows 下的快捷方式。会链接到库 ld-2.19-2014.08-1-git.so 上,输入命令如下指令查看此文件详细信息:
ls ld-linux-armhf.so.3 -l
ld-linux-armhf.so.3 后面有个“->” ,表示其是个软连接文件,链接到文件ld-2.19-2014.08-1-git.so,因为其是一个“快捷方式” ,因此大小只有 24B。但是,ld-linux-armhf.so.3不能作为符号链接,否则的话在根文件系统中执行程序无法执行!所以我们需要重新复制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/arm- linux-gnueabihf/libc/lib 目录中,重新拷贝ld-linux-armhf.so.3,命令如下:
cp ld-linux-armhf.so.3 /home/xxpcb/myTest/nfs/rootfs/lib/
拷贝完成以后再到 rootfs/lib 目录下查看ld-linux-armhf.so.3文件详细信息,此时ld-linux-armhf.so.3 已经不是软连接了,而是实实在在的一个库文件,而且文件大小为 724392B。
继续进入如下目录中:
cd /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/xxpcb/myTest/nfs/rootfs/lib/ -d
rootfs/lib 目录的库文件就这些了,完成以后的rootfs/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/xxpcb/myTest/nfs/rootfs/usr/lib/ -d
完成以后的rootfs/usr/lib目录为:
至此,根文件系统的库文件就全部添加好了,可以在rootfs
目录下使用“du”命令来查看一下/lib和/usr/lib 这两个目录的大小:
du ./lib ./usr/lib/ -sh
在根文件系统中创建其他文件夹,如 dev、proc、mnt、sys、tmp 和 root 等,创建完后的效果:
使用NFS挂载的方式来测试上面创建好的根文件系统rootfs。
uboot里面的bootargs环境变量会设置root的值,需要将root的值改为NFS挂载,设置格式如为:
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>
<server-ip>
:服务器IP,存放根文件系统的Ubuntu的IP地址,比如我的192.168.5.105。<root-dir>
:根文件系统的存放路径,比如我的就是/home/xxpcb/myTest/nfs/rootfs。<nfs-options>
:NFS 的其他可选选项,一般不设置。<client-ip>
:客户端IP ,开发板的IP地址,Linux内核启动以后就会使用此IP地址来配置开发板。我的为92.168.5.102。<gw-ip>
:网关地址,我的就是 192.168.5.1。<netmask>
:子网掩码,我的就是 255.255.255.0。<hostname>
:客户机的名字,一般不设置,此值可以空着。<device>
:设备名,也就是网卡名,一般是 eth0,eth1….,正点原子与野火的开发板均为ENET2为eth0,ENET1为eth1。这里我们使用ENET2,所以网卡名就是 eth0。<autoconf>
:自动配置,一般不使用,所以设置为 off。<dns0-ip>
:DNS0 服务器 IP 地址,不使用。<dns1-ip>
:DNS1 服务器 IP 地址,不使用。根据上面的格式bootargs环境变量的root值如下:
root=/dev/nfs nfsroot=192.168.5.105:/home/xxpcb/myTest/nfs/rootfs,proto=tcp rw ip=192.168.5.102:192.168.5.105:192.168.5.1:255.255.255.0::eth1:off
启动开发板,串口连接开发板,进入uboot命令行模式,然后设置bootargs环境变量,命令如下:
setenv bootargs 'console=ttymxc0,115200 root=/dev/nfs nfsroot=192.168.5.105:/home/xxpcb/myTest/nfs/rootfs,proto=tcp rw ip=192.168.5.102:192.168.5.105:192.168.5.1:255.255.255.0::eth1:off'
saveenv
设置好以后使用“boot”命令启动Linux内核
Linux内核的启动还是按照上一篇介绍的,使用tftp将zImage和设备树传输到开发板中运行。
这里注意一下,因为此次测试,我将zImage和dtb文件移入了tftp目录中的nxp文件夹中,所以传输指令需要修改一下:
setenv bootcmd 'tftp 80800000 nxp/zImage; tftp 83000000 nxp/imx6ull-myboard.dtb; bootz 80800000 - 83000000'
saveenv
然后就可以使用boot命令来进行tftp传输了。
在使用boot命令来进行tftp传输了,启动内核时,出现了NFS根文件系统不能挂载的错误:
VFS: Unable to mount root fs via NFS, trying floppy.
VFS: Cannot open root device "nfs" or unknown-block(2,0): error -6
先是尝试了多种方法,都不能解决问题,这些无效的方法包括:
最后,参考这篇博文:https://blog.csdn.net/InFoport/article/details/90317697
通过在bootargs添加中添加nfsvers=4
,这个选项,就可以正常挂载nfs的文件系统了:
setenv bootargs 'console=ttymxc0,115200 root=/dev/nfs nfsroot=192.168.5.105:/home/xxpcb/myTest/nfs/rootfs,proto=tcp,nfsvers=4 rw ip=192.168.5.102:192.168.5.105:192.168.5.1:255.255.255.0::eth1:off'
注:无效方法中的Linux System Utilities ->Support mounting NFS file,因开启后也不起作用,后续测试就将其改为默认的不勾选。
按下回车键,就进入了文件系统,使用ls命令就可以看到了系统文件。
再使用touch命令来新建一个中文名称的文件,也是OK的。
本篇使用BusyBox来构建根文件系统,并通过NFS网络调试的方式实现根文件系统挂载测试,实测时解决了NFS根文件系统不能挂载的问题,最终根文件系统基本功能测试正常。