正点原子IMX6ULL,UBoot笔记

uboot是什么: 是一个bootloader程序,芯片上电以后就会运行的程序

uboot的作用:
1. 初始化DDR等外设,一系列的裸机程序
2. 将linux内核从flash(emmc,nand等,emmc为例,linux内核是存放在第三区中)拷贝到DDR中
3. 引导启动Linux内核

uboot是一个裸机代码的集合,是遵循GPL协议的开源代码

uboot代码分3种类型
A. uboot官方代码 包含所有常用芯片 一般不直接使用该代码

B. 半导体厂商代码 只维护一个uboot,该uboot专门针对自家的芯片 我们自己移植uboot来适配一个新开发板的时候选择它

C. 开发板厂商代码 开发板厂商在半导体厂商提供的uboot代码基础上加入对自家开发板的支持 (相当于移植好之后的uboot,如果有我们使用的开发板对应的开发板厂商代码就直接拿过来用就ok)

uboot的编译流程和方法:
创建一个.sh文件,文件中存放编译流程命令,给权限,执行脚本编译
脚本内容:
1. make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- distclean
ARCH=arm: 表明代码框架是arm架构
CROSS_COMPILE=arm-linux-gnueabihf-: 表示编译工具的前缀
distclean:清理工程,这里会删除config等配置文件,故如果我们在通过KConfig界面修改了config文件后,不能在执行clean命令
具体代码:
PHONY += distclean

	distclean: mrproper
		@find $(srctree) $(RCS_FIND_IGNORE) \
			\( -name '*.orig' -o -name '*.rej' -o -name '*~' \
			-o -name '*.bak' -o -name '#*#' -o -name '.*.orig' \
			-o -name '.*.rej' -o -name '*%' -o -name 'core' \
			-o -name '*.pyc' \) \
			-type f -print | xargs rm -f
		@rm -f boards.cfg
2. 	make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- mx6ull_14x14_ddr512_emmc_defconfig
	mx6ull_14x14_ddr512_emmc_defconfig: 一个Makefile文件,该文件通常存放在uboot/config中,作为对应开发板的配置文件
	mx6ull_14x14_ddr512_emmc_defconfig文件名称的来源:mx6ull半导体厂商下,芯片大小为14x14,DDR大小为512M,flash为emmc类型
	执行该命令的作用;(生成两个软件和将默认配置文件中的内容输出到.config文件)
		1. 自动生成文件(include/generated/version_autogenerated.h)用于保存该版本uboot信息 (uboot启动中打印的信息,uboot版本,编译工具链,GNU id等信息) 
		2. 自动生成文件(include/generated/timestamp_autogenerated.h)用于保存该uboot的时间戳(表示整个uboot文件是什么时候生成的) 
		3. 最最关键的作用
			%config: scripts_basic outputmakefile FORCE 
				 $(Q)$(MAKE) $(build)=scripts/kconfig $@
			分为两部分: 依赖和命令
			依赖:
			FORCE: 使依赖文件一直比目标新,故命令会每次都被执行
			outputmakefile: 文件无效
			scripts_basic:   编译生成出scripts/basic/fixdep软件
			命令: 生成script/kconfig/conf软件(该软件会将默认配置文件%_defconf中的配置输出到.conf文件中)和将我们的默认配置文件(mx6ull_14x14_ddr512_emmc_defconfig)中的内容输出到.config文件
				.config文件中包含了许多的宏开关和宏变量,这些宏变量控制了整个uboot中的代码
3. 	make V=1 ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -j12 (该命令的作用,将u_boot代码下的所有裸机驱动文件编译生成build_in.o文件,在通过链接脚本u-boot.lds将这些build-in.0
		文件和启动文件链接在一起形成u-boot文件,然后将生成的u-boot文件和其他文件一起生成u-boot-nodtb.bin文件,在以u-boot-notdb.bin文件生成u-boot.bin文件)
		V=1: 用来设置编译过程中的信息输出级别   ; -jn: 设置主机使用多少线程来编译uboot
		make的流程:关键中的关键
		1. make使用默认目标_all
		2. 我们这里是编译整个uboot故_all依赖于all,如果只是编译某个模块_all依赖于modules
		3. all依赖于ALL_y,ALL_y这个变量的值根据我们前面的config中定义的宏变量来决定,但是一定包含以下文件
			ALL-y += u-boot.srec u-boot.bin u-boot.sym System.map u-boot.cfg binary_size_check
			ALL-$(CONFIG_ONENAND_U_BOOT) += u-boot-onenand.bin ... ...
			
		4. 这里重点分析u-boot.bin的由来,u-boot.bin依赖于u-boot-nodtb.bin,而u-boot-nodtb.bin依赖于u-boot文件,而u-boot文件依赖于u-boot_init,u_boot_main,u-boot.lds文件
		boot_init:arch/arm/cpu/armv7/statrt.o  启动文件
		u-boot.lds: 脚本链接文件
		u_boot_main: uboot中所有子目录build-in.o的集合,这个集合就是那些裸机驱动的集合
		注意:我们烧写给开发板中的不是u-boot.bin文件而是u-boot.imx文件,u-boot.imx文件=u-boot文件+ 头部(IVT_DCD等数据),通过imxdownload软件完成转换  
			
4. uboot命令详解
	//A. 环境变量命令
	1. 查看当前uboot所支持的命令 			? / help
	2. 查看板子信息 						bdinfo
	3. 输出环境变量信息						printenv
	4. 查看uboot版本号						version
	5. 修改环境变量							setenv + saveenv (环境变量存放在emmc中,uboot启动会将emmc中的环境变量拷贝到DDR中,setenv只是修改DDR中环境变量的值,所以只是
											setenv而没有saveenv该变量只在该次有效果,而saveenv是修改emmc中的环境变量)
	6. 新建环境变量 						setenv 要新建的环境变量名 对应的值 +saveenv
	7. 删除环境变量 						setenv 要删除的环境变量名	+ saveenv ==>将该环境变量赋空值
	
	//B. 内存操作命令
	8. 显示内存值(查看一段指定内存地址保存的值)			md[.b/.w/.l] 要查看的那段内存的首地址  长度	 
		.b/.w/.l代表长度的单位 分别为byte,word,long(1,2,4字节) 注意uboot命令里面的数字都是16进制的
		例如 md.b 80000000 14 ==>查看起始地址为80000000,长度为0x14*1byte大小的这段内存中保存的值 (类比,14=多少块内存,bwl=每块内存多大)
	9. nm 修改指定地址的内存值(单次修改)							nm[.b/.w/.l] 要修改的内存地址 
		例如 我们现在设置80000000处的值为0x12345678
		a. md.l 800000000 1 显示: 80000000: ffffffff
		b. nm.l 80000000  回车
		显示 80000000: ffffffff ?光标
		c. 输入12345678 回车
		d. 输入q 退出
		md.l 80000000 1 显示: 80000000: 12345678
	10. mm 修改指定地址的内存值(连续修改) 							mm[.b/.w/.l] 要修改的那段内存的起始地址
		与nm相比是只要你不输入q你可以一直修改下去
	11. mw 用于使用有关指定的数据填充一段内存						mw[.b/.w/.l] 要填充的那段内存的起始地址		要填充的值  要填充多少块内存
		例如,将起始地址为80000000,每块内存大小为4byte,填充0x14块,填充的数据是0x12345678
		mw.l 80000000 12345678 14
	12. cp 内存地址保存的数据拷贝命令,1.将DRAM中的内存段a中保存的值拷贝到内存段b中 2.将emmc中的数据拷贝到DRAM中			cp[.b/.w/.l] 源内存起始地址 目标内存起始地址 拷贝多少块内存
		例如:将80000000出开始的4*0x14个内存块中的内容,拷贝到80000100中
		cp.l 80000000 80000100 14
	13. cmp命令	内存地址保存的内容比较命令		cmp[.b/.w/.l]	内存地址a的起始地址 	内存地址b的起始地址  比较多少块内容
		例如: 比较80000000 800000100 这两个内存地址的接下来的4*0x14块内存保存的内容
		cmp 80000000 80000100 14  ==>如果显示0x14块内容相等表示这两块内存保存的内容相等
	
	//C. 网络命令
	14. 配置打通开发板和主机服务器的网络
		setenv ipaddr 192.168.1.50				//设置开发板的ip地址,保证开发板和服务器处于同一个网段下
		setenv ethaddr 00:04:9f:04:d2:35 		//设置mac地址,mac地址即物理的网络接口地址,在一个网段下必须唯一 网络驱动,会知道mac地址是网络设备net_device中一个成员变量,修改mac地址 == 修改该成员值 同时专门提供了修改mac的方法和判断mac值是否合理的方法
		setenv gatewayip 192.168.1.1 			//网关
		setenv netmask 255.255.255.0 			//子网掩码
		setenv serverip 192.168.1.250 			//要与开发板通信的服务器ip地址
		saveenv			
	15. ping 验证开发板和服务器之间能否通信		ping 要测试的ip地址b   if(显示alive) 表明能与ip b通信
		注意: 只能通过在uboot命令行中ping服务器来验证网络是否ok,不能在服务器ping 开发板ip(因为uboot没有对ping命令做处理)
		
	16. dhcp	用于从路由器中获取ip地址 		返回一个该路由器所在网段下一个未被使用的ip 
	17. nfs命令(nfs是Network File System 网络文件系统的缩写) 通过网络下载东西到开发板上的DRAM中 
		通常使用nfs命令将Ubuntu(服务器)中的文件下载到开发板上的DRAM中		uboot中nfs命令格式:nfs 要下载到的DRAM地址 服务器地址:要下载的文件的在服务器中的绝对路径
		//NFS使用流程: 在服务器中配置NFS(开启NFS服务,创建一个NFS文件,将要通过nfs下载到开发板中的文件存放在该NFS文件夹下)
		例如: 将文件1.c拷贝到DRAM中的80800000处
		服务器:cp 1.c /home/wjc/linux/nfs(前面配置好的nfs文件)
		uboot: nfs 80800000 192.168.1.250:/home/wjc/linux/nfs/1.c		
	18. tftp命令	通过网络下载东西到开发板上的DRAM中 (与nfs相比,tftp更加好用)
		tftp命令使用tftp协议,在该协议中Ubuntu作为TFTP服务器
		tftp命令的环境配置: 在Ubuntu中安装tftp环境+创建一个tftp文件夹+给tftp文件夹设置可执行权限+修改配置文件
		注意,使用tftp将文件下载到开发板中,需要设置要下载的文件具有可执行权限
		命令格式: tftp 要下载到开发板中DRAM地址 	要下载的文件的名称
		例如:将文件2.c拷贝到DRAM中的80800000处
		服务器: 1.chmod 777 /home/wjc/linux/tftp  2.cp 2.c /home/wjc/linux/tftp  3.chmod 777  /home/wjc/linux/tftp/2.c
		开发板: tftp 80800000 2.c
	
	//D. mmc操作命令(sd卡,nand和emmc flash都是mmc设备)  
	19. mmc info/mmcinfo 输出当前选中的mmc设备的信息  	(主要包括内存大小,设备名称,ID等)
	20. mmc list 查看当前开发板上一共有多少个mmc设备	(备注emmc==emmc设备,sd=sd卡设备)
	21. mmc part 查看当前mmc设备具有多少个分区	(对于块设备驱动来说,不同分区之间,主设备号相同,次设备号不同)
		例如查看mmc设备号为1的具有多少个分区? 	
		mmc dev 1 	==>切换到设备号为1的分区0设备
		mmc part 	==>查看该设备具有的设备分区信息
	22. mmc dev 切换当前mmc设备 		mmc dev [设备号] [分区号] ==>[]代表可写可不写,每个mmc具有一个对应设备号,每个mmc设备又包括多个分区
		mmc dev 0 == mmc dev 0 不写分区号==> 默认切换到分区0
		mmc dev 1 1 切换到mmc中设备号为1的分区1 ==>设置mmc中设备号为1的分区1为当前mmc设备
		
	23. mmc read 读取mmc设备的数据(将mmc设备中的数据读取(拷贝)到DRAM中)			mmc read 读取到的数据存放在DRAM中的地址 要读取的块的起始地址 要读取多少个扇区
		例如: 从EMMC设备的第0x600块开始,读取0x10个块到DRAM中的0x80800000处
		知识点: 块=n个扇区 ,一个页可以有多个块,一个块有多个扇区,一个扇区=512byte 第n个扇区 == 从该mmc设备的分区0开始计算
			mmc dev 1 0 	==>将当前mmc设备切换到我们要读取的EMMC设备的分区0
		mmc read 80800000 	600 	10
	
	24. mmc write 将数据写入到mmc设备(将DRAM中的数据写入(拷贝)到mmc设备中)		mmc write 要被写入到mmc设备的数据在DRAM中的地址		在mmc设备中要写入数据的起始地址  写多少个扇区
		例如:使用tftp+ mmc write实现对uboot是升级
		思路: 将Ubuntu中重新编译,烧写出来的u-boot.imx文件通过tftp下载到DRAM中,然后通过mmc write将存放在DRAM中的u-boot.imx文件写入到sd卡中
		mmc dev 1 0 				//将mmc设备切换到sd卡的分区0 
		tftp 80800000 u-boot.imx	//通过tftp将u-boot.imx文件下载到DRAM的80800000地址处,(注意这里需要将u-boot.imx文件拷贝到tftp文件夹中,并设置权限)
		mmc write 80800000 2 32E	//将DRAM中存放了u-boot.imx文件的数据写入到当前mmc设备(sd卡)的第二块分区开始的连续0x32E个扇区
		mmc partconf 1 1 0 0		//分区配置
		注意点: 不能写SD卡或者EMMC的前两个扇区(扇区0,1),因为里面保存着分区表信息
	25. mmc erase命令	擦除mmc设备的指定块		mmc erase 要擦除的当前mmc设备块起始地址 	要擦除的设备块的数量
		注意点: 最好不要随便使用这个命令
	
	//E: FAT格式文件系统操作命令			(这些命令只适用于FAT格式的文件系统)
	26. fatinfo命令		用于查询指定mmc设备的下不同分区的文件系统类型	fatinfo 	要查询的设备类型	设备号:分区号
		例如我们要查看emmc设备中分区1的文件信息 	文件系统类型:FAT16,FAT32...
		fatinfo mmc 1:1		==> 显示的结果是分区1的文件系统类型,该文件系统的描述信息和该文件系统的大小
	27. fatls命令	用于查询指定设备下分区中存放的文件名称和大小		fatls 		要查看的设备类型	设备号:分区号
		例如我们想要查看emmc设备的分区1中存放了哪些文件
		fatls 	mmc 1:1		==>显示该设备分区下保存的文件名称和文件大小
	28. fstype命令	用于查看mmc设备的每个分区的文件系统格式					fstype		要查看的设备类型	设备号:分区号
		例如我想要查看emmc设备所有分区的文件系统格式
		fstype 	mmc 1:0			==>分区0,格式未知 		因为没有格式化	分区0存放着uboot,
		fstype	mmc	1:1			==>分区1,fat格式						分区1存放着linux内核+设备树文件
		fstype	mmc	1:2			==>分区2,ext4格式						分区2存放着根文件系统(rootfs)
	29. fatload命令	用于将指定的文件读取到DRAM中(与mmc read相比,这个操作对象是文件,mmc read操作对象是mmc设备中的扇区)	
		fatload		要从什么设备中读取文件到DRAM中		设备号:分区号	读取在DRAM中的地址	要读取的文件名称
		例如: 要将emmc设备分区1中所存放的zImage文件读取到DRAM的80800000地址处
		fatload 	mmc		1:1		80800000	zImage
	30	fatwrite命令用于将DRAM中指定的文件写入到其他设备(mmc)中	(与mmc write相比一个操作文件一个操作数据)
		fatwrite	该文件要写入的设备类型	设备号:分区号	要将DRAM中哪里的地址上的文件写入到其他设备中	要写入的文件名称	文件的大小
		例如:通过fatwrite命令和tftp命令来实现对emmc设备中保存的linux镜像文件的更新
		
		1.将新编译出来的zImage文件拷贝到tftp文件夹中,并修改zImage文件的权限
		2. tftp			80800000	zImage										//下载文件到DRAM中
		3. fatwrite		mmc 	1:1		80800000	zImage	zImage文件的大小	//文件从DRAM中写入到emmc分区1中
		4. fatls	mmc		1:1													//查看是否写入ok
	//F: EXT格式文件系统操作命令			(这些命令只适用于EXT格式的文件系统) 
	31.ext4ls命令 	查询ext4文件系统格式下指定设备分区中存放的文件信息		ext4ls 	设备类型	设备号:分区号
		例如要查看emmc设备分区2中保存的根文件系统下存在哪些文件
		ext4ls	mmc		1:2		==>显示rootfs中的文件信息
	//G: boot操作命令,uboot的本质工作是引导Linux启动
	32. bootz命令	用于启动zImage镜像文件	bootz 	linux镜像文件在DRAM中的位置		initrd文件在DRAM中的位置	设备树文件在DRAM中的位置
		例如: 通过tftp+bootz启动linux内核和设备树
		拷贝zImage和设备树文件到tftp文件夹中并修改文件权限
		tftp	80800000	zImage
		tftp	83000000	imx6ul-alientek-emmc.dtb(设备树文件名称)
		bootz	80800000	-	83000000
	33. bootm命令	用于启动uImage镜像文件		bootm uImage镜像文件在DRAM中的位置		initrd文件在DRAM中的位置	设备树文件在DRAM中的位置
	34.	boot命令	用于通过调用bootcmd环境变量来启动linux系统
		例如:我们想通过boot命令来开启linux,那么就设置bootcmd即可
		setenv bootcmd 'tftp	80800000 zImage;tftp 83000000	imx6ul-alientek-emmc.dtb;bootz 80800000 - 83000000 	'
		saveenv
	//H. 其他常用命令
	35. go命令 用于跳到指定的地址处执行应用		go 	要执行的应用在DRAM中的地址
		例如:我们要在开发板中执行Ubuntu中生成的1.bin应用
		tftp 80800000 1.bin
		go 	80800000
	36. reset命令 用于复位重启开发板
	37. run命令	用于运行环境变量中定义的命令(通常是运行自定义的命令)
		例如,我现在自定义一个命令:startlinux 来实现启动linux
		setenv startlinux 'tftp	80800000 zImage;tftp 83000000	imx6ul-alientek-emmc.dtb;bootz 80800000 - 83000000 	'
		saveenv
		run		startlinux
	38. mtest命令	是一个简单的内存读写命令	mtest 	要测试的那块内存的起始地址		终止地址

5.  makefile知识点
	1. make -C 指定的要编译的makefile文件名称				//编译指定的makefile文件
	2. export 变量名										//将这个变量传递给子makefile文件
	3. 命令输出级别		make V=0(默认)						//输出短命令
						make V=1							//输出编译过程中执行的完整编译命令
						make -s					//静默输出(不显示编译命令)
	4. 编译结果输出目录	make O=指定编译结果要输出到的目录,如果不使用 0=指定输出目录话,默认编译的输出结果存放到源文件所在的目录
	5. 模块编译			make M=dir							//单独编译某个模块
	6. 代码检查			make C=1							//检查那些需要重新编译的文件	(C=2:检查所有的源文件)
	7. SHELL和MAKEFLAGS变量默认是会传递给子makefile文件,除非用unexport修饰

6.  uboot的运行流程 
	知识点: 查看一个东西的流程,首先你得找到它的开始,找一份代码的开始文件,你查看它的链接文件即可	
	1. _start入口地址 == uboot的起始地址 ==0x87800000 
	2. 根据拨码开关的值来设置对应的工作模式
	3. 重定向中断向量表
	4. 通过设置CP15寄存器来关闭MMU等设备
	5. 设置SP栈顶指针的位置
	6. 划分DRAM中的区域,(头部区域,sp指针地址,malloc区域,gd区域 ...)
	7. 初始化一系列外设(串口,定时器等)
	8. uboot将自己重定位DRAM最后面的区域(定位到最后面 == 我们可以使用的连续内存最大化), 通过采用位置无关码来解决重定位后的链接地址和运行
	   地址不一致产生的问题
		
	
	
	uboot运行时显示的那些信息来源的跟踪
	**************************************************
	**************************************************
	
	U-Boot 2016.03 (Oct 13 2020 - 23:21:44 +0800)						//display_options,通过串口输出一些信息

	CPU:   Freescale i.MX6ULL rev1.1 69 MHz (running at 396 MHz)		//print_cpuinfo 函数用于打印 CPU 信息
	CPU:   Industrial temperature grade (-40C to 105C) at 36C
	Reset cause: POR													
	Board: MX6ULL 14x14 EVK												//show_board_info 函数用于打印板子信息
	I2C:   ready														//init_func_i2c 函数用于初始化 I2C,初始化完成显示ready
	DRAM:  512 MiB														//announce_dram_init,此函数很简单,就是输出字符串“DRAM",dram_init,只是设置 gd->ram_size 的值,对于该开发板来说就是512MB。\
																		  DRAM 的起始地址为 0X80000000,大小为 0X20000000(512MB)
																		//****** 上面这些信息的初始化在board_init_f中实现,下面的这些初始化信息在board_init_r中实现 **************
																		
	MMC:   FSL_SDHC: 0, FSL_SDHC: 1										//initr_mmc 函数,初始化 EMMC, 可以看出,此时有两个 EMCM 设备,FSL_SDHC:0 和 FSL_SDHC:1。
	Display: ATK-LCD-4.3-800x480 (800x480)								//stdio_add_devices 函数,各种输入输出设备的初始化,如 LCD driver,I.MX6ULL使用 drv_video_init 函数初始化 LCD
	Video: 800x480x24
	In:    serial														// console_init_r 函 数,控 制 台 初 始 化 , 初 始 化 完 成 以 后 此 函 数 会 调 用stdio_print_current_devices \
	Out:   serial															函数来打印出当前的控制台设备,这里表示这里uboot的控制台设备(显示终端)都是串口
	Err:   serial
	switch to partitions #0, OK											//如果环境变量存储在 EMMC 或者 SD 卡中的话此函数会调用 board_late_mmc_env_init 函数来初始化 EMMC/SD																			 来初始化 EMMC/SD,在函数中会切换到正在时候用的 emmc 设备
	mmc0 is current device												  在这个函数中会进行切换到正在时候用的 emmc 设备的操作,这两条显示是 mmc dev 命令的打印
	Net:   FEC1															// initr_net 函 数 , 初 始 化 网 络 设 备,流程:initr_net->eth_initialize->board_eth_init(),这是打印信息
	Normal Boot
	Hit any key to stop autoboot:  0 									//下面第7点会详解
	
	
	知识点: DDR中最终的内存分配图 (开发文档 801页)

7.  uboot倒计时选择功能的实现
    功能模式: uboot启动以后倒计时3s(一个环境变量控制),如果3s倒计时之前按下回车键进入uboot的命令模式,如果倒计时结束还没有按就进自动启动内核
    流程分析:
    1. 先获取环境变量bootdelay(倒计时的秒数)的值
    2. 在将这个时间值作为参数传递给判断倒计时过程中有没有按键按下
    3. 显示倒计时提示语句
    4. 根据这个倒计时秒数做一个循环判断,判断有没有按键按下,每隔1s更新显示一次倒计时秒数,如果倒计时结束还没有按键按下返回0,有按键按下返回1
    5. 接收前面步骤4的返回值来判断下一把该怎么进行,如果没有按键按下就调用boot命令来启动linux函数,如果有按键按下就调用启动uboot命令模式的函数
   
8.  uboot中命令是如何定义的
    通过定义宏U-BOOT-CMD来定义一个命令,在这里过程中会根据我们输入的字符串来创建一个与之对应的cmd_tab_t类型的变量
	例如定义 dhcp命令
	dhcp经过U-BOOT-CMD宏来创建一个cmd_tab_t类型(结构体)的变量
	这个结构体成员包括: 
		命令的名字:dhcp
		命令的最大参数: 3 
		该命令对应的操作函数:do_dhcp()
		... ...
		
9.  uboot命令模式下对输入命令的操作流程
    1. 接收命令行输入的命令
    2. 将接收到的命令和cmd_tab_t数组(保存了所有命令的信息)中每个元素的name成员做比较,如果有找到说明输入的命令是有效的就调用该命令对应的
	   操作函数,操作函数的返回值接收命令的结果

10. bootz启动linux内核的过程	
	通过调用bootz命令来实现linux内核的启动,即通过调用do_bootz()来实现启动linux
	知识点,bootz启动的系统不仅仅可以是linux内核,还可以是其他内核,故不同的系统调用的启动函数不同。linux内核启动的关键是images变量,bootargs环境变量保存着uboot传递给linux kernel的信息
	这里以bootz启动linux系统为例 		输入 bootz 80800000 - 83000000	//80800000:linux镜像在DRAM中的地址	83000000:设备树文件在DRAM中的地址
	1. 设置Linux系统在DRAM中的储存位置,即linux系统镜像的入口点		这里是设置为0x80800000
	2. 判断当前的系统镜像文件是否为linux系统镜像文件		==>这是根据我们输入的linux镜像地址中存放的镜像文件和linux镜像文件去判断,如果是liunx镜像就没有提示信息同时会记录这个镜像文件的信息(起始地址和结束地址),不匹配会打印不匹配信息
	3. 查找ramdisk文件和设备树文件							==>设备树文件到我们输入的设备树地址中获取
	4. 关闭中断
	5. 设置保存系统镜像类型的变量为Linux	(非常重要,因为后面还需要根据这个变量去调用该系统对应的启动函数(do_bootm_linux() ))
	6. 根据系统镜像类型的值选择与之对应的启动函数(do_bootm_linux())
	7. 在启动函数中首先会根据我们前面查找到的设备树的“兼容性”属性(compatible属性)判断我们要启动的linux内核是否支持这个开发板
	8. *************  最后调用kernel_entry()函数  ***************************************
	   关键点:kernel_entry()函数不在是定义在uboot代码中而是定义在linux内核镜像中,而且linux镜像文件的第一行代码就是函数kernel_entry(),我们前面在判断
	   系统镜像是否为Linux系统(步骤2)时保存了linux系统镜像的起始地址,故这里就会根据这个地址而调用到kernel_entry(),从而开始进入到linux kernel的代码 
				
11. kernel_entry函数详解
	kernel_entry函数有3个参数:zero,arch,params,
	第一个参数 zero 必须为 0;第二个参数为机器 ID;第三个参数 ATAGS 或者设备树(DTB)首地址,ATAGS 是传统的方法,用于传递一些命令行信息啥的,如果使用设备树的话就要传递设备树(DTB)。
	kernel_entry(0, machid, r2);  	//前面为什么说images这个全局变量非常重要,因为这些信息都保存在这个变量的成员中。(linux内核起始地址,设备树起始地址,系统类型 ...)

12. uboot移植 (学习将NXP官方uboot移植到我们自己的开发板上)
	1> uboot移植的一般流程
		1. 找到我们要移植的uboot的参考开发平台,一般是原厂(半导体厂商)的原厂开发板
		2. 将要移植的uboot编译烧写到我们的开发板上根据现象来判断需要修改那些东西(一般是LCD,网络驱动等)
	2> 实际操作流程(通用部分)
		1. 根据我们的开发板查找与之对应的开发板默认配置文件
		   默认配置文件存放文件夹:configs		例如我们开发板对应的文件: mx6ull_14x14_evk_emmc_defconfig	mx6ull:芯片的厂家	14x14:芯片尺寸为14x14mm		emmc:flash类型
		2. uboot编译三部曲,clean;编译默认配置文件;编译工程,然后将生成的u-boot.imx烧写到开发板中观察现象
		3. 通过打印信息和命令来对这个uboot进行检查(mmc设备检查(根据命令查看mmc信息是否正确),lcd驱动检查(查看有没有显示logo),网络驱动检查(查看打印信息中有没有表示识别出网络信息) )
		
		4. 添加我们开发板对应的默认配置文件  将前面找到的默认配置文件拷贝一份重命名mx6ull_alientek_emmc_deconfig 来和我们开发板匹配
		   然后修改默认配置文件的内容(修改路径,因为我们更换了默认配置文件的名称)
		5. 添加我们开发板对应的头文件(include/configs/半导体厂商名称.h),先拷贝改名为 mx6ull_alientek_emmc.h 
			这个头文件的作用(非常关键): 这个文件中有很多宏定义,这些宏定义用于配置uboot,决定了我们有使能、禁止uboot的某些功能
			详解: 这个头文件中基本上是"CONFIG_"宏定义,即该文件的主要功能是配置或者裁剪uboot
				  如果需要新功能在头文件里添加对应的CONFIG_XXX宏即可,禁止某些功能,那么删除对应的宏即可
				  基本上我们前面说的uboot流程中干的那些工作都需要看这个文件中定义的宏,比如输出cpu信息,板子信息,串口初始化,logo显示等功能都有对应的宏定义在该头文件
		6. 添加开发板的板级文件夹
			a. 先拷贝一份在重命名 
			b. 修改板级文件夹下的Makefile文件,imximage.cfg文件,Kconfig文件,MAINTAINERS文件和修改U-BOOT的图像界面配置文件
	3> 移植之后造成部分驱动无效的修改流程(移植 差异部分) 根据前面的uboot启动中打印信息和命令模式下命令测试得出结果
		1. LCD驱动修改
		2. 网络驱动修改

13.	bootcmd环境变量
	知识点: bootcmd里面保存着uboot的默认命令,通常这些默认来启动linux内核
	boot命令会调用bootcmd环境变量,uboot倒计时结束就会执行boot命令来调用bootcmd环境变量来启动linux
	bootcmd环境变量的值可以在uboot命令模式下设置,在板子第一次运行uboot的时候会使用uboot代码中的默认值来设置bootcmd环境变量
	默认bootcmd的值 = 'mmc dev 1; fatload mmc 1:1 0x80800000 zImage;fatload mmc 1:1 83000000 imx6ull-alientek-emmc.dtb;bootz 80800000 - 83000000'
	实际上,bootcmd实现默认值的定义非常复杂,因为该默认值需要适配不同的开发板(因为不同开发板的设备数文件是不同的)

14. bootargs环境变量
	知识点:bootargs环境变量中保存着uboot传递给Linux内核的参数
	bootargs环境变量是由环境变量设置的mmcargs
	经典的bootargs环境变量中保存的命令如下: bootargs == console= ttymxc0, 115200 root= /dev/mmcblk1p2 rootwait rw
	console:设置linux终端,即设置通过什么设备和Linux来进行交互	
			ttymxc0 = 开发板上的串口1, linux中一切皆文件,开发板上串口1在linux系统下的设备文件就是/dev/ttymxc0
			115200: 串口1的波特率为115200 ==>设置串口1作为linux的终端,串口1的波特率为115200
	root:	用来设置根文件系统的位置,root = /dev/mmcblk1p2用于说明rootfs存放在mmcblk1的分区2中 (注意bl,l是字母)
			kl表示mmc设备号,p2表示mmc设备的分区2	==>说明rootfs存放在设备类型为:mmc,设备号为:1,分区号:2的地方(EMMC的分区2存放着根文件系统)
			rootwait: 表示等待mmc设备初始化完成之后在挂载根文件系统,如果mmc设备还没有初始化就挂载会出现错误
			rw: 表示根文件系统可能读写,如果不加rw,可能会导致无法在根文件系统中进行写操作,只能进行读操作
	rootfstype: 用于指定根文件系统类型
				关键点: 如果根文件系统为ext格式则可省略该选项,我们这里设置的根文件系统的ext4的故可以省略
				如果非ext格式则需要通过该选项指定根文件系统类型
				
15. U-Boot图像化配置详解
	知识点: 可以通过默认配置文件(半导体厂商名称_开发板名称_flash类型_defconfig),和开发板对应的头文件(半导体厂商_开发板名称_flash类型.h)来配置uboot,最终根据这些文件来形成uboot的最终配置文件.config
	在这里我们实现用图形化界面来配置uboot形成最终的.config文件
	打开图形化界面: make menuconfig
		menuconfig:是一套图形化的配置工具,该工具需要ncurses库支持,故想使用图形化配置工具先按照库来搭建环境
		menuconfig工具重点会使用两个文件
			.congig文件: 图形化配置本质上就是修改这个文件,你对应的操作就是修改这个文件中的对应内容
			Kconfig文件: 图形界面的描述文件,就是描述图形界面中应该出现什么内容
	操作说明:
		************* ****************** ******************
		键盘上的"↑"和"↓"键来选择要配置的菜单
		菜单后面有 "--->" 代表这个菜单还有子菜单,可以通过按下"enter"按键或图形界面中的下标键"select"来进入到它的子菜单
		菜单前要一个[]代表可以配置这个子菜单
			输入 "Y": []/[M] --> [*] ,将相应的代码编译进Uboot中
			输入 "N": [*]/[M] --> [],表示不编译相应的代码
			输入 "M": []/[*] --> [M],表示将相应的代码编译为模块
			按下 "?": 查看现在选中的菜单的帮助信息	==图形界面下标键 "help"
			按下 "/": 表示打开搜索框,可以在搜索框中输入要搜索的内容
			按两下	"Esc": 表示退出当前菜单,返回到上一级菜单	==图形界面下标键 "exit"
			
			下标键 "save": 保存修改后的配置文件(.config)
			下标键 "load":加载指定的配置文件
			
			例如:通过图形界面使能dns功能
			1. make menuconfig 来打开图形配置界面
			2. 通过“entry”来选择 "Command line interface"/"Network commands -->" 选中dns菜单 按下"Y"键,然后一直按两下"ESC"键返回到上一个菜单,最后在是否保存修改的配置时选择"YES"
			3. 查看图形配置的修改效果,打开.config文件,查看是否新增了"CONFIG_CMD_DNS=y"怎么一行
			4. 重新编译uboot工程,注意这里不能用脚本3步骤,因为脚本第一步clean就是删除config文件,一执行我们前面的配置就没了,故只需执行 make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -j16
			5. 验证实际效果; 将编译生成的u-boot.imx烧写到sd卡中,从SD卡中启动uboot,如何进入命令模式,设置dns环境变量的值 "setenv dns 114.114.114.114"(即DNS = 114.114.114.114) 	saveenv 
			   开始验证能否真的可以解析域名 dns www.baidu.com  ==>显示 14.215.177138 可以显示百度的位置的ip,说明dns功能ok
		图形配置界面的优势: 使能一些命令非常方便,不需要去到处找命令的配置宏

16. make menuconfig过程分析 
	静默编译生成可执行文件mconf,mconf软件会调用根目录下的Kconfig文件来构建图形配置界面
	
17. Kconfig语法简介	学习网站: https://blog.csdn.net/lang523493505/article/details/105067899
	我们以uboot根目录下的顶层Kconfig文件为例,学习Kconfig语法
	1. 主菜单名称"U-Boot 2016.03 Configuration"的来源
	     知识点: 主菜单即输入 "make menuconfig" 后默认打开的界面就是主菜单
	     主菜单的字符串的定义代码为 :mainmenu "U-Boot $UBOOTVERSION Configuration" 变量UBOOTVERSION == 2016.03 故显示该值
	2. 调用其他目录下的Kconfig文件 (和Makefile一样,Kconfig也可以调用其他目录下的Kconfig文件)
		  source "xxx/Kconfig"	//xxx为具体的命令名,这个目录路径是相对路径,相对当前Kconfig文件所在的位置		顶层Kconfig包含其他目录下的Kconfig == 子目录下的Kconfig文件在主菜单中生成各自的菜单项					  	
		  include xxx.mk 		//如果没有指定要调用的makefile文件所在的路径的话,先在当前路径下查找,如何查看编译中是否有"-I" /"--include-dir" 选项(指定要调用的Makefile文件所在的路径)
	3. 	menu/endmenu代码段来生成子菜单  不可编辑的菜单	
		知识点: 1. 子菜单: "菜单名 --->" ,这个子菜单下面有其他的条目
					例如定义一个名为"General setup"的子菜单
					menu "General setup"
						... ... 
					endmenu
					结果显示为 General setup --->
		
				2. 条目: "[] 条目名 ",在Kconfig中,条目定义处于menu-endmenu间,代码类型是:config 宏变量(配置项名称),这样定义的代码就会在图形界面显示为条目
					例如在名为"General setup"的子菜单下定义一个名为 "LOCALVERSION_AUTO"的配置项
					menu "General setup"
						config LOCALVERSION_AUTO
							bool "Automatically append version information to the version string"
							default y
							help
								my name is wjc
					endmenu
					结果显示为: General setup ---> 下按"enter进入"后显示为: [*] Automatically append version information to the version string
					default y: 表示这个配置项的默认值就是y,如果没有使用default 命令,那么默认值就是n,**** 关键点: 一个配置项可以有多个默认值,但是只有第一个被定义的值是有效的
					代表了.config文件中宏 CONFIG_LOCALVERSION_AUTO=y (故得: Kconfig文件中config关键字后面的配置项,在config中调用的形式为CONFIG_配置项)
					条目的显示形式为:变量类型 配置项显示在图形界面的字符串(标题)
					变量类型包含5种: bool,tristate,string,hex,int,常用的为bool,tristate,string
					bool类型有两种值,y = [*] = 使能这个条目代表的配置项,即在config文件中定义 CONFIG_配置项=y,n = [] = 禁止这个配置项
					tristate: 在bool的基础上加了个值 m = [M] ==表示将这个配置项编译为模块
					string: 存储本地字符串,就是你可以给这个条目对应的配置项赋字符串值
							例如: 定义字符串配置项 LOCALVERSION (保存版本号)
							config LOCALVERSION
								string "Local version - append to U-Boot release"
								help
									wjc
							结果显示: () Local version - append to U-Boot release
							选中这个条目,按"enter“之后会出现一个输入框,输入我们想输入的值,保存即可,在config文件中,CONFIG_LOCALVERSION = "我们输入的字符串的值"
				3. depends on 命令 :普通依赖
					例如 我们定义配置项A依赖于配置B,即(只有配置项B被选中之后,配置A才可以被操作)
					config A
						bool
						depends on B
					结果为,只有配置项b被选中(y)之后,才可以对配置项A操作(==在config中定义了CONFIG_A宏变量),bool表示配置项A的变量类型是bool型,可以选择(y/n)
				4. select命令 : 表示方向依赖
					例如:当我选中配置项C时,连带着配置项D,E也会被选中
					config C
						bool "if you want to use C"
						select D
						select E
					结果:当我们选择C时(y),那么config文件中会定义 CONFIG_D=y,CONFIG_E=y
				5. choice/endchoice代码段定义了一组可选择项.供用户单选(多选一)
					例如 有4个选项A,B,C,D供你选择
					choice 
						prompt "you select"		#配置项的显示名称
						default	B		#默认选择的配置项
						
						config	A
							bool "A"
						config	B
							bool "B"
						config	C
							bool "C"
						config	D
							bool "D"
						config	E
							bool 			#没带提示信息的条件类型不会实现
					endchoice
					结果先显示这组选择项
						you select(B)	//在prompt会显示当前我们选择的配置项名称
						我们选中这个按"enter"进入之后显示为
						() 	A
						(X)	B		//X表示我们当前选择的配置项,光标移动到要选择的地方,按Select下标键
						()	C	
						()	D
				6. menuconfig: 带选项的菜单,就是说你要想进入到这个菜单里面你必须要选择这个菜单
					类型: [] 菜单名称 -->,与menu相比,前面多了个框,相当于多了个大开关,本来menu修饰的菜单下的条目,只需选择Y就会被配置,现在不仅仅条目要选择上,他所在的menuconfig对应的选项也要选择上
					例如 选择了menuconfig菜单menu1后面会显示配置条目menu2_1和menu2_2
					menuconfig menu1
						bool "menu1"
						default n
						
						if menu1				#如果配置可选择菜单menu1为y,那么执行下面的代码
							config menu2_1
							bool "menu2_1"
							default n
							
							config menu2_2
							bool "menu2_2"
							default y
						endif 
					结果显示:
						[] menu1 --->
						我们选中配置为y之后
							[]	menu2_1
							[*] menu2_2
					
				7.	comment 在图形配置界面显示注释
					comment "i am zhisi"
					结果显示:
						*** i am zhisi ***

你可能感兴趣的:(linux驱动3大组成,uboot,linux)