imx6ull uboot 使用*

Uboot 学习笔记

linux系统的组成部分

alt text

前面我们学习的是裸机开发,主要使用寄存器操作,实现对外设的驱动,掌握的外设的底层原理。之后就可以把重心放到linux驱动框架上。

在移植linux系统之前,需要

  1. 移植bootloader(用于启动linux内核, bootloader有很多种,常见的是uboot)
  2. 再移植linux内核
  3. 再移植根文件系统rootfs(这个里面包含了最常用的命令和文件)

这三个一起构成了完整的linux系统。

注:大部分移植的工作,芯片原厂(NXP)已经做好了,我们所做的移植,其实就是让他在特定的开发板上跑起来。

uboot 介绍

芯片上电以后,先运行一段bootloader程序,bootloader内部会初始化ddr等外设(RAM)(实际工作复杂的多),然后将Linux 内核从 flash(ROM)(NAND,NOR FLASH,SD,MMC 等)拷贝到 DDR(RAM) 中,最后启动 Linux 内核

有很多现成的 bootloader 软件可以使用,比如 U-Boot、vivi、RedBoot 等

uboot 的全称是 Universal Boot Loader,是一个遵循 GPL 协议的开源软件,是一个裸机代码,可以看作是一个裸机综合例程。

现在的 uboot 已经支持液晶屏、网络、USB 等高级功能。

可以从uboot官网找到uboot的源码,但是这个源码一般是给芯片原厂用的,用来给自家的芯片移植uboot,他们会维护自己的uboot版本。所以我们自己用肯定只能到NXP的官网去下载定制的uboot代码, 但是这个定制uboot都是针对原厂(NXP)的评估板的,如果是我们自己的板子,还需要二次修改NXP官方的uboot,使其适配我们自己的开发板。
alt text

你也可以在购买了第三方开发板以后使用半导体厂商提供的 uboot,只不过有些外设驱动可能不支持,需要自己移植,这个就是我们常说的 uboot 移植。

uboot 编译

我的imx的开发板是512G ddr, 8G emmc, 1G sd卡

#!/bin/bash
# make clean
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- distclean

# set uboot defconfig
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- mx6ull_14x14_ddr512_emmc_defconfig

# cross-compile, -j = cpu core numbers
make V=1 ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -j12

alt text

注:uboot只是引导程序,是一个综合裸机例程,也可以引导其他系统,也可以支持其他架构,也可以拓展其他外设。

所以在编译 uboot 之前,一定要根据自己的需求配置 uboot。mx6ull_14x14_ddr512_emmc_defconfig就是正点原子针对 I.MX6U-ALPHA 的 EMMC 核心板编写的配置文件。

这个配置文件的目录在uboot/configs中,通过make xxx_defconfig来配置 uboot,xxx_defconfig就是不同板子的配置文件

最终会编译出uboot.bin, uboot.imx这些用于烧录的镜像(和之前的裸机程序一样的镜像文件)

uboot 烧录

直接把编译出来的u-boot.bin当成裸机例程烧录到SD卡即可

uboot 网络(驱动开发调试用)

ping底层网络联通(2层,3层)

ping成功仅验证了开发板与主机的底层网络互通(无需 DHCP 参与,依赖静态配置或已有临时 IP)

dhcp

dhcp命令本质是通过 BOOTP(DHCP 的前身)/DHCP 协议向网络请求 IP 地址、网关、启动文件地址等配置,所以需要你的网络中有一台机器运行 DHCP 服务器(Ubuntu主机,用来给设备分配ip)

Ubuntu 默认无 DHCP 服务:与普通 Windows 系统类似,Ubuntu 桌面版(如你的系统)默认未安装和启用 DHCP 服务器(仅包含 DHCP 客户端),开发板发送的广播请求无法被 Ubuntu 响应,因此仍会出现超时,另外Ubuntu 防火墙可能拦截 DHCP 协议的 UDP 67/68 端口,即使后续安装了 DHCP 服务器,也可能因端口被拦截导致交互失败。

Ubuntu主机 安装dhcp服务器

# 更新软件源
sudo apt update
# 安装DHCP服务器
sudo apt install isc-dhcp-server -y

配置dhcp服务器内容

sudo vim /etc/dhcp/dhcpd.conf

# 添加以下内容
# 全局配置
default-lease-time 600; # 默认租期(秒)
max-lease-time 7200; # 最大租期(秒)
option subnet-mask 255.255.255.0; # 子网掩码,与开发板一致
option routers 192.168.1.1; # 网关,与开发板gatewayip一致
option domain-name-servers 223.5.5.5, 8.8.8.8; # DNS服务器

# 子网配置(与开发板网段匹配)
subnet 192.168.1.0 netmask 255.255.255.0 {
range 192.168.1.60 192.168.1.200; # DHCP分配IP池,避开开发板静态IP(192.168.1.50)和serverip(192.168.1.253)
option broadcast-address 192.168.1.255; # 广播地址
}

# (可选)为开发板绑定固定IP(基于MAC地址,避免每次分配不同IP)
host imx6ull {
hardware ethernet b8:ae:1d:01:00:00; # 开发板ethaddr,与你U-Boot配置一致
fixed-address 192.168.1.50; # 绑定的IP,与开发板ipaddr一致
}

指定dhcp服务器使用的网卡

sudo vim /etc/default/isc-dhcp-server

# 修改
INTERFACESv4="eth0" # 若为IPv6可配置INTERFACESv6,此处无需关注

启动Ubuntu主机dhcp服务器

# 启动DHCP服务
sudo systemctl start isc-dhcp-server
# 设置开机自启
sudo systemctl enable isc-dhcp-server
# 查看服务状态(确认是否正常运行)
sudo systemctl status isc-dhcp-server

关闭防火墙,防止阻挡dhcp服务

# 放行UDP 67/68端口
sudo ufw allow 67/udp
sudo ufw allow 68/udp
# 重新加载防火墙规则
sudo ufw reload

nfs服务(尝试失败,一直超时)

NFS 仅用于文件共享(后续开发板挂载根文件系统等场景),为后续dhcp下载linux内核镜像提供文件下载方法

sudo apt-get install nfs-kernel-server rpcbind

sudo vi /etc/exports

#新增你的nfs文件共享下载目录
/home/liangji/LINUX/nfs *(rw,sync,no_root_squash)

tftp服务(尝试成功)

nfs,tftp都是传输文件的协议,uboot会在建立好和ubuntu主机的网络通信之后,就开始用文件传输(底层走的是网络tcp,udp)来下载主机上的zImage(内核镜像)。

我们在uboot里面执行dhcp后,会让ubuntu主机给我们分配一个ip,然后默认开始tftp拉取主机的共享文件。但是文档里面让我自己走nfs拉取,我自己尝试了之后,发现非常难用,也无法保证主机的nfs正确建立。

通过设置主机tftp服务后:

# 安装TFTP服务端(Ubuntu/Debian)
sudo apt install tftpd-hpa tftp-hpa

# 配置TFTP根目录(需与U-Boot中zImage的存放路径一致)
sudo vi /etc/default/tftpd-hpa
# 修改内容:
TFTP_DIRECTORY="/home/liangji/LINUX/tftpboot" # 与NFS共享目录一致,确保zImage在此
TFTP_ADDRESS="0.0.0.0:69"
TFTP_OPTIONS="--secure --create"

# 重启TFTP服务
sudo systemctl restart tftpd-hpa

# 网络防火墙设置
sudo ufw allow 69/udp
sudo ufw reload

ubuntu主机本地验证,发现确实可以正常拉取文件,这样至少能保证主机这边是正常的。

tftp 127.0.0.1
tftp> get zImage
tftp> quit
# 若能获取文件,说明TFTP服务正常

之后uboot端尝试以下拉取,发现直接很快就拉下来了。

# 1. TFTP加载内核到指定地址
tftp 80800000 zImage

# 之后直接执行dhcp,他会自动执行tftp拉取

将 tftpboot 文件夹里面的 zImage 文件下载到开发板 DRAM 的 0X80800000 地址处

uboot RAM 操作

裸机都是对整个设备的寻址空间进行了存储空间映射,所以0x8080 0000, 表示的是RAM的其实地址(SRAM = DDR)

md.(b/w/l) addr(H) count(H)

md.l 80800000 10 //从80800000开始,按l(4字节)为单位,打印0x10个单位

ffffffff ffffffff ffffffff ffffffff
ffffffff ffffffff ffffffff ffffffff
ffffffff ffffffff ffffffff ffffffff
ffffffff ffffffff ffffffff ffffffff

uboot ROM 操作

ROM,磁盘,uboot会读取所有mmc设备(SD卡, EMMC, NAND)

mmc info
mmc rescan
mmc list //查看有哪些mmc设备
mmc dev mmc_id(0,1) //(part分区号,默认0)(分区内容见下面分区分析)

// 读取磁盘(块block = 扇区 = 512字节)
mmc read ram_addr(0x80800000) start_block(H) count_block(H)

// RAM数据写入磁盘
mmc write ram_addr(0x80800000) start_block(H) count_block(H)

可以使用命令mmc write来升级 uboot,也就是在 uboot 中更新 uboot。

  • 注:在运行的uboot中,拉取新的uboot.imx镜像到RAM中,然后写入SD卡(我们启动的地址)(为什么要隔两个块?因为前2个block,保存的是分区表),之后重启即可。
    alt text
    alt text

uboot 文件操作(不再是磁盘字节操作了)

ROM中格式分布:
正常我们在EMMC里面安装了linux系统,那么他会有3个分区:

  1. part 0(无格式): uboot (前面会留出2个块存放分区表,不要随便擦除)
  2. part 1(fat): kernel(zImage) + device tree(dtb)
  3. part 2(ext): rootfs

fat文件系统格式

# info 查询指定 MMC 设备分区的文件系统信息
#fatinfo <interface> [<dev[:part]>]
fatinfo mmc 1:1

# ls 查询 FAT 格式设备的目录和文件信息
#fatls <interface> [<dev[:part]>] [directory]
fatls mmc 1:1

# 查看 MMC 设备某个分区的文件系统格式
# fstype <interface> <dev>:<part>
fstype mmc 1:0
fstype mmc 1:1
fstype mmc 1:2

# 将指定的文件读取到 DRAM 中
# fatload <interface> [<dev[:part]> [<addr> [<filename> [bytes [pos]]]]]
fatload mmc 1:1 80800000 zImage

# 在 uboot 中更新 linux 镜像文件和设备树。我们以更新 linux 镜像文件 zImage为例
# fatwrite <interface> <dev[:part]> <addr> <filename> <bytes>
# 需要修改uboot defconfig
tftp 80800000 zImage
fatwrite mmc 1:1 80800000 zImage 6788f8

ext文件系统格式

# EMMC 的分区 2 就是 ext4格式的,使用 ext4ls 就可以查询 EMMC 的分区 2 中的文件和目录
ext4ls mmc 1:2

# 剩下的后面会提到

mmc0,1分区内容

通过fstype, fatls, fatinfo, 可以查看出emmc(mmc 1), sd(mmc 0)里面的每个分区都是干什么的。

可以看到

sd(mmc 0)

  • part0: 无文件系统
  • part1: fat
    • zImage, dtb
  • part2: fat
    • 空

emmc(mmc 1)

  • part0: 无文件系统
  • part1: fat
    • zImage, dtb
  • part2: ext
    • rootfs

(emmc)(mmc 1:2)rootfs,根文件系统里面的内容是这样:

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

(emmc)(mmc 1:1) 的linux内核镜像+设备树文件

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

这样就可以通过uboot,来从远程下载zimage, dtb,rootfs来更新emmc,来实现在线升级。

分区 0 里面到底是什么?

=> 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 20480 262144 f4ce62e9-01 0c
2 282624 1683456 f4ce62e9-02 0e
=>

严格来说,你执行 mmc part 看到的 Part 1 和 Part 2 是受 MBR(主引导记录) 管理的有格式分区。

而你所谓的“分区 0”(即扇区 0 到扇区 20479 之间的区域),在 i.MX 架构中存放的是最核心的启动镜像:

  • Sector 0 (扇区 0): 存放 MBR 分区表。(前两个块,1k字节)

  • Sector 2 (偏移 1KB 处): 存放 u-boot.imx。

i.MX6ULL 的 bootROM 固化了启动逻辑,它在加电后会固定去 MMC 设备(SD卡或 EMMC)的 1KB 偏移处 寻找启动头(IVT/DCD 等)。

剩余空闲空间: 用于存放环境变量(如果你配置 U-Boot 存放在 MMC 中)或者空闲。

属性,         分区 1 (FAT),         分区 2 (FAT/EXT)
起始扇区, 20480, 282624
起始位置 (MB), 20480×512=10 MB, 282624×512=138 MB
总扇区数, 262144, 1683456
容量计算, 262144×512=128 MB, 1683456×512≈822 MB

所以SD卡为例,我们1G的SD卡,分区0=10MB,分区1=128MB,分区2=822MB

(u-boot.imx = 363KB, zImage = 6MB)

所以绰绰有余

关于环境变量的保存,当你saveenv保存环境变量,你会看到保存到MMC0, sd卡中

=> printenv bootcmd
bootcmd=run findfdt;mmc dev ${mmcdev};mmc dev ${mmcdev}; if mmc rescan; then if run loadbootscript; then run bootscript; else if run loadimage; then run mmcbo ot; else run netboot; fi; fi; else run netboot; fi
=> setenv bootcmd 'tftp 80800000 zImage;tftp 83000000 imx6ull-14x14-emmc-7-1024x600-c.dtb;bootz 80800000 - 83000000'


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

实际环境变量:

对于正点原子的 i.MX6ULL 开发板,默认配置通常如下:

  • U-Boot 程序: 存储在偏移 $1$ KB ($2$ 扇区) 处。
  • 环境变量 (Env): 存储在偏移 $768$ KB (即 $0xC0000$ 字节) 处。
物理地址偏移,             内容,                     说明
0 字节, MBR 分区表, 存放分区信息
1024 字节 (1 KB), u-boot.imx, 启动镜像
..., 空闲, ...
786432 字节 (768 KB), Environment, 这就是你 saveenv 存放 bootcmd 的地方
..., 空闲, ...
10485760 字节 (10 MB), Partition 1 (FAT), 存放 zImage 和 dtb 的地方

uboot boot操作

uboot 的本质工作是引导 Linux,所以 uboot 肯定有相关的 boot(引导)命令来启动 Linux

要启动 Linux,需要先将 Linux 镜像文件拷贝到 DRAM 中,有两种拷贝方式:

  1. 从 EMMC 或者 NAND 等ROM中将 Linux 镜像和设备树文
    件拷贝到 DRAM
  2. tftp 将 Linux 镜像文件和设备树文件下载到 DRAM 中

不管用那种方法,只要能将 Linux 镜像和设备树文件存到 DRAM 中就行, 然后使用 bootz 命令来启动,bootz 命令用于启动 zImage 镜像文件

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

bootz 从远程拉取kernel + dtb到RAM 启动linux内核

因为我们没有根文件系统,所以内核启动后,只会到无法挂在rootfs,然后kernel panic


# 远程拉取kernel镜像
ftp 80800000 zImage

# 远程拉取dtb设备树文件
tftp 83000000 imx6ull-14x14-emmc-7-1024x600-c.dtb

# 启动linux系统,(没有指定rootfs, 所以只会到挂载rootfs停止)
# bootz会从这两个地址开始自动寻找zImage 和 dtb
bootz 80800000 - 83000000

alt text

bootz 本地ROM启动

从 EMMC 中启动 Linux 系统的话只需要使用命令 fatload 将 zImage 和imx6ull-14x14-emmc-7-1024x600-c.dtb 从EMMC 的分区 1 中拷贝到 DRAM 中,然后使用命令 bootz 启动即可

bootm

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

boot 自动化启动命令

boot 命令也是用来启动 Linux 系统的,只是 boot 会读取环境变量 bootcmd 来启动 Linux 系统, bootcmd这个环境变量保存着引导命令,其实就是启动的命令集合

远程拉取启动

setenv bootcmd 'tftp 80800000 zImage; tftp 83000000 imx6ull-14x14-emmc-7-1024x600-c.dtb;
bootz 80800000 - 83000000'
saveenv
boot

前面说过 uboot 倒计时结束以后就会启动 Linux 系统,其实就是执行的 bootcmd 中的启动命令

默认就是远程网络启动

从EMMC中启动

修改bootcmd

setenv bootcmd 'fatload mmc 1:1 80800000 zImage; fatload mmc 1:1 83000000 imx6ull-14x14-
emmc-7-1024x600-c.dtb; bootz 80800000 - 83000000'
savenev
boot

最终kernel开始启动,最终会卡在:

[    3.784511] b310            4096 mmcblk1boot0  (driver?)
[ 3.789845] Kernel panic - not syncing: VFS: Unable to mount root fs on unknown-block(0,0)
[ 3.798121] ---[ end Kernel panic - not syncing: VFS: Unable to mount root fs on unknown-block(0,0)
[ 73.813077] random: nonblocking pool is initialized

没有找到根文件系统,这个很正常,因为没有设置 uboot 的
bootargs 环境变量,关于 bootargs 环境变量后面会讲解

其他命令

reset

reset //复位重启

go

用于跳转到RAM的地方来运行
注:我们从MMC中拷贝东西到RAM中,都是到80800000, 而裸机程序本体的链接地址是因为裸机例程的链接首地址就是 0X87800000

tftp 87800000 printf.bin
go 87800000

复习裸机烧录的镜像的地址结构

run

run 命令用于运行环境变量中定义的命令,

比如可以通过“run bootcmd”来运行 bootcmd 中的启动命令,这在后面调试linux驱动的时候,会频繁需要切换启动方式,用这个来执行环境变量脚本

setenv mybootemmc 'fatload mmc 1:1 80800000 zImage; fatload mmc 1:1 83000000 imx6ull- 14x14-emmc-7-1024x600-c.dtb;bootz 80800000 - 83000000'

setenv mybootnand 'nand read 80800000 4000000 800000;nand read 83000000 6000000 100000;bootz 80800000 - 83000000'

setenv mybootnet 'tftp 80800000 zImage; tftp 83000000imx6ull-14x14-emmc-7-1024x600-c.dtb;
bootz 80800000 - 83000000'

saveenv

#run mybootemmc
#run mybootnet

mtest

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

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

mtest 80000000 80001000