Linux驱动的前期学习

本系列基于正点原子的系统移植以及根文件系统的构建系列视频

Linux系统镜像烧写

Windows下使用OTG烧写系统

​ 在Windos使用NXP提供的mfgtool来向开发烧写系统。需要用先将开发板的USB_OTG接口连接到电脑上。

​ Mfgtool工具是向板子先下载一个Linux系统,然后通过这个系统来完成烧写工作。

​ 切记!使用OTG烧写的时候要先把SD卡拔出来,等USB OTG与电脑连接成功以后就可以再将SD卡插进去了。

​ 烧写系统都是烧写到NAND或者EMMC里面,

由于本人使用的是8G的EMMC和512MB的DDR,所以选择了Mfgtool2-eMMC-ddr512-eMMC.vbs

双击如下图所示:

mfg-tool

点击start,开始下载,等待一段时间之后,下载完成 :

烧写emmc

点击stop再点exit,以emmc启动即可

Ubuntu下通过脚本烧写系统

​ 烧写较为麻烦,不建议使用该方式,因此略,想看详细信息可查看正点原子的D盘第1.2讲即可。

U-Boot的编译与烧写

何为u-boot?

​ 1、uboot是一个裸机程序,比较复杂。

​ 2、uboot就是一个bBootloader,作用就是用于启动Linux或其他系统。Uboot最主要的工作就是初始化DDR。因为Linux是运行在DDR里面的。一般Linux镜像zImage(uImage)+设备树(.dtb)存放在SD、EMMC、NAND、SPI FLASH等等外置存储区域。

​ 因此需要将Linux镜像从外置flash拷贝到DDR中,再去启动。

​ Uboot的主要目的就是为系统的启动做准备。

​ Uboot不仅仅能启动Linux,也可以启动其他系统,比如vxworks。

​ Linux不仅仅能通过uboot启动。还可以使用其他方式。

​ Uboot是个通用的bootloader,他支持多种架构。

Uboot获取

​ 1、uboot官网

​ 2、SOC厂商会从uboot官网下载某一个版本的uboot,然后在这个版本的uboot上加入相应的SOC以及驱动。这就是SOC厂商定制版的uboot。NXP官方的I.MX6ULL EVK板子,

​ 3、做开发板的厂商,开发板会参考SOC厂商的板子。开发板必然会和官方的板子不一样。因此开发板厂商又会去修改SOC厂商做好的uboot,以适应自己的板子。

正点原子官方uboot编译

​ 1、编译UBOOT的时候需要先配置

#指定芯片架构、交叉编译器以及最开始先清除一下工程
make AUTH=arm CROSS_COMPILE=arm-linux-gnueabihf- distclean
#生成配置文件
make AUTH=arm CROSS_COMPILE=arm-linux-gnueabihf- mx6ull_14x14_ddr512_emmc_defconfig
#开始编译 V=1代表输出详细的过程
make V=1 ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -j4

​ 2、编译完成以后就会生成一个u-boot.bin。必须向u-boot.bin添加头部信息。Uboot编译最后会通过/tools/mkimage软件添加头部信息,生成u-boot.imx(烧写文件)。替换掉mfgtool中对赢的imx文件

​ 3、为了方便开发,建议直接在uboot顶层Makefile里面设置好ARCH和CORSS_COMPILE这两个变量的值。

U-Boot命令

在启动时进行到uboot命令模式。(不会吧,不会不知道怎么进吧?)

LOG信息简介

U-Boot 2016.03 (Mar 01 2021 - 21:48:37 +0800) #U-Boot版本 编译时间

CPU: Freescale i.MX6ULL rev1.1 792 MHz (running at 396 MHz) #CPU信息,时钟频率
CPU: Industrial temperature grade (-40C to 105C) at 43C #工业温度等级以及当前CPU温度
Reset cause: POR #复位模式
Board: I.MX6U ALPHA|MINI #当前设备板
I2C: ready # I2C准备就绪
DRAM: 512 MiB #DDR3
MMC: FSL_SDHC: 0, FSL_SDHC: 1 #EMMC或SD卡 一个SD一个EMMC
Display: ATK-LCD-7-1024x600 (1024x600) #显示屏
Video: 1024x600x24 #音频
In: serial #输入输出错误的打印位置都是串口
Out: serial
Err: serial
switch to partitions #0, OK #切换到0分区
mmc0 is current device #当前运行在MMC0 即SD卡,我当前使用SD卡启动
Net: FEC1 #网络
Normal Boot #正常启动
Hit any key to stop autoboot: 0 #进入uboot命令模式等待时间
=> mmc list #列出MMC EMMC和SD统称为MMC
FSL_SDHC: 0 (SD)
FSL_SDHC: 1

命令

uboot 命令中的数字都是十六进制的!不是十进制的!

查看某一个命令帮助信息,?命令名 (? bdinfo)

信息查询命令

​ 1、bdinfo

arch_number = 0x00000000
boot_params = 0x80000100
DRAM bank = 0x00000000
-> start = 0x80000000
-> size = 0x20000000
eth0name = FEC1
ethaddr = 00:5A:BB:CC:87:FF
current eth = FEC1
ip_addr = 192.168.1.10
baudrate = 115200 bps
TLB addr = 0x9FFF0000
relocaddr = 0x9FF51000
reloc off = 0x18751000
irq_sp = 0x9EF4EEA0
sp start = 0x9EF4EE90
FB base = 0x00000000

​ 2、printenv命令 重要 查看当前板子的环境变量。

​ 3、setenv命令 设置环境变量,也可以自定义环境变量,也可以删除环境变量

​ 4、saveenv 保存环境变量。设置完必须保存

内存操作命令

1、md命令 显示内存值

#md[.b, .w, .l] address [# of objects]
#命令中的[.b .w .l]对应 byte、word 和 long,也就是分别以 1 个字节、2 个字节、4 个字节来显示内存值。address 就是要查看的内存起始地址,[# of objects]表示要查看的数据长度

#查看以 0X80000000 开始的 20 个字节的内存值
md.b 80000000 14

2、nm命令 修改指定地址的内存值

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

3、mm命令 修改指定地址内存值的,使用 mm 修改内存值的时候地址会自增,而使用命令 nm 的话地址不会自增

4、mw命令 用一个指定的数据填充一段内存

mw [.b, .w, .l] address value [count]  
#count 是填充的长度

5、cp命令 DRAM 中的数据从一段内存拷贝到另一段内存中,或者把 Nor Flash 中的数据拷贝到 DRAM 中。

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

6、cmp命令,用于比较两段内存的数据是否相等

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

=> cmp.l 80000000 80000100 10
Total of 16 word(s) were the same

网络操作命令

​ 网线插如到ENET2上,保证开发板和电脑处于同一个网段内。

1、ping命令 是否可以和服务器(Ubuntu 主机)进行通信

ping 192.168.x.x

2、dhcp命令 用于从路由器获取 IP 地址,前提得开发连接到路由器上的,如果开发板是和电脑直连的,那么 dhcp 命令就会失效

dhcp

3、nfs命令 重点 通过 nfs 可以在计算机之间通过网络来分享资源,目的就是为了调试程序。

nfs [loadAddress] [[hostIPaddr:]bootfilename]

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

MMC操作命令

mmc操作命令

FAT格式文件系统操作命令

​ 对于I.MX6U来说,SD/EMMC分为三个分区:

​ 第一个:存放uboot

​ 第二个: 存放Linux zImage,.dtb。FAT

​ 第三个:系统的根文件系统,EXT4

1、fatinfo命令 查询指定 MMC 设置指定分区的文件系统信息

fatinfo <interface> [<dev[:part]>]
#interface 表示接口,比如 mmc,dev 是查询的设备号,part 是要查询的分区
fatinfo mmc 1:1

2、fatls命令 查询 FAT 格式设备的目录和文件信息

fatls <interface> [<dev[:part]>] [directory]
#interface 是要查询的接口,比如 mmc,dev 是要查询的设备号,part 是要查询的分区,directory是要查询的目录
fatls mmc 1:1

3、fstype命令 查看 MMC 设备某个分区的文件系统格式

fstype <interface> <dev>:<part>

4、fatload命令 用于将指定的文件读取到 DRAM 中

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

#interface 为接口,比如 mmc,dev 是设备号,part 是分区,addr 是保存在 DRAM 中的起始地址,filename 是要读取的文件名字。bytes 表示读取多少字节的数据,如果 bytes 为 0 或者省略的话表示读取整个文件。pos 是要读的文件相对于文件首地址的偏移,如果为 0 或者省略的话表示从文件首地址开始读取

fatload mmc 1:1 80800000 zImage

5、fatwrite命令 在 uboot 中更新 linux 镜像文件和设备树

uboot 默认没有使能 fatwrite 命令,需要修改板子配置头文件,比如 mx6ullevk.h、 mx6ull_alientek_emmc.h 等等,板子不同,其配置头文件也不同。找到自己开发板对应的配置头文件然后添加如下一行宏定义来使能 fatwrite 命令:

#define CONFIG_FAT_WRITE /* 使能 fatwrite 命令 */

接口如下

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

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

EXT格式文件系统操作命令

uboot 有 ext2 和 ext4 这两种格式的文件系统的操作命令,常用的就四个命令,分别为:ext2load、ext2ls、ext4load、ext4ls 和 ext4write

这些命令的含义和使用与 fatload、fatls 和 fatwrit一样,只是 ext2 和 ext4 都是针对 ext 文件系统的

NAND操作命令

我并没有使用NAND,使用的是EMMC

1、nand info命令 打印 NAND Flash 信息

2、nand write命令 切换 NAND Flash,如果你的板子支持多片 NAND 的话就可以使用此命令来设置当前所使用的 NAND

3、nand erase命令 擦除 NAND Flash,NAND Flash 的特性决定了在向 NAND Flash 写数据之前一定要先对要写入的区域进行擦除

4、nand write命令 向 NAND 指定地址写入指定的数据

5、nand read命令 从 NAND 中的指定地址读取指定大小的数据到 DRAM 中

BOOT操作命令

1、bootz命令 启动 zImage 镜像文件

要启动Linux必须将zImage,dtb放到DRAM。

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

#addr 是 Linux 镜像文件在 DRAM 中的位置,initrd 是 initrd 文件在DRAM 中的地址,如果不使用 initrd 的话使用‘-’代替即可,fdt 就是设备树文件在 DRAM 中的地址

2、bootm命令 bootm 和 bootz 功能类似,但是 bootm 用于启动 uImage 镜像文件

3、boot命令 boot 命令也是用来启动 Linux 系统的,只是 boot 会读取环境变量 bootcmd 来启动 Linux 系统

其他命令

1、reset命令 复位

2、go命令 用于跳到指定的地址处执行应用

go addr [arg ...]
#addr 是应用在 DRAM 中的首地址

3、run命令 运行环境变量中定义的命令

4、mtest命令 简单的内存读写测试命令,可以用来测试自己开发板上的 DDR

mtest [start [end [pattern [iterations]]]]
#start是要测试的DRAM 开始地址,end 是结束地址,比如我们测试 0X80000000~0X80001000这段内存  
mtest 80000000 80001000

U-Boot源码目录

目录简析

源码目录2

Linux 源码目录

    文件夹
  • arch
  • 这个目录是和架构有关的目录,比如 arm、arm64、avr32、x86 等等架构。
  • 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 相关的驱动目录,
  • fireware
  • 存放固件
  • fs
  • 此目录存放文件系统,比如 fs/ext2、fs/ext4、fs/f2fs 等
  • include
  • 头文件目录
  • init
  • 存放 Linux 内核启动的时候初始化代码
  • ipc
  • IPC 为进程间通信,ipc 目录是进程间通信的具体实现代码。
  • kernel
  • Linux 内核代码。
  • lib
  • 一些公用的库函数。
  • mm
  • 存放内存管理相关代码。
  • net
  • 网络相关代码
  • samples
  • 存放一些示例代码文件
  • scripts
  • 脚本目录,Linux 编译的时候会用到很多脚本文件,这些脚本文件就保存在此目录中
  • security
  • 存放安全相关的文件
  • sound
  • 此目录存放音频相关驱动文件,音频驱动文件并没有存放到 drivers 目录中,而是单独的目录。
  • tools
  • 此目录存放一些编译的时候使用到的工具。
  • usr
  • 此目录存放与 initramfs 有关的代码。
  • virt
  • 此目录存放虚拟机相关文件。

    文件
  • .config
  • .config 保存着 Linux 最终的配置信息,编译 Linux 的时候会读取此文件中 的配置信息。最终根据配置信息来选择编译 Linux 哪些模块,哪些功能
  • Kbuild
  • 有些 Makefile 会读取此文件。
  • Kconfig
  • 图形化配置界面的配置文件。
  • Makefile
  • Linux 顶层 Makefile 文件,
  • README
  • 此文件详细讲解了如何编译 Linux 源码,以及 Linux 源码的目录信息,建议仔细阅读一下 此文件

根文件系统构建

根文件系统一般也叫做 rootfs,那么什么叫根文件系统?看到“文件系统”这四个字,很多人,包括我第一反应就是 FATFS、FAT、EXT4、YAFFS 和 NTFS 等这样的文件系统。在这里,根文件系统并不是 FATFS 这样的文件系统代码,EXT4 这样的文件系统代码属于 Linux 内核的一部分。Linux 中的根文件系统更像是一个文件夹或者叫做目录

根文件系统首先是内核启动时所 mount(挂载)的第一个文件系统,内核代码映像文件保存在根文件系统中,而系统引导启动程序会在根文件系统挂载之后从中把一些基本的初始化脚本和服务等加载到内存中去运行。

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

BusyBox 构建根文件系统

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

编译BusyBox构建根文件系统

一般在 Linux 驱动开发的时候都是通过 nfs 挂载根文件系统的,当产品最终上市开卖的时候才会将根文件系统烧写到 EMMC 或者 NAND 中

向根文件系统添加 lib

Linux 中的应用程序一般都是需要动态库的,当然你也可以编译成静态的,但是静态的可执行文件会很大。如果编译为动态的话就需要动态库,

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

在 rootfs 的 usr 目录下创建一个名为 lib 的目录,将如下目录中的库文件拷贝到 rootfs/usr/lib目录下

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

创建其他文件夹

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

系统烧写

制作好了根文件系统。在实际的产品开发中肯定不可能通过网络来运行,否则没网的时候产品岂就歇菜了。因此我们需要将 uboot、linux kernel、.dtb(设备树)和 rootfs 这四个文件烧写到板子上的 EMMC、NAND 或 QSPI Flash 等其他存储设备上,这样不管有没有网络,产品都可以正常运行。

写在最后

作为一个想入门嵌入式linux的小白,起始对这一章是懵逼的。正点原子在第三盘系统移植篇之后写了这么一段话:

第三篇是系统移植篇,重点就是 uboot、Linux kernel 和 rootfs 的移植,看似简简单单的“移植”两个字,引出的却是一篇300 多页的“爱恨情仇”。授人以鱼不如授人以渔,本可以简简单单的教大家修改哪些文件、添加哪些内容,怎么去编译,然后得到哪些文件。但是这样只能看到表象,并不能深入的了解其原理,为了让大家能够详细的了解整个流程,笔者义无反顾的选择了这条最难走的路,不管是uboot 还是 Linux kernel,从 Makefile 到启动流程,都尽自己最大的努力去阐述清楚。

知其然也要知其所以然才能走的更远!!!