本文仅为自己在调试过程中的记录,如有不对地方欢迎讨论:853906167@qq.com
概述:
最近接手一个类似Tbox的车载联网系统项目,作为自动驾驶系统上的联网、诊断、传感器输入等辅助功能,同时上面接了IMU330/Ublox/camera/switch/phy/hsm/4G5G/WIFI/BT/V2X等模块,公司原本是采购的其他供应商的成品,目前公司想自己开发做到全线可控。
主控:imx8qxp_c0(4核A35) DDR4:3GB EMMC:32GB
1.资源和开发方式
imx8qxp版本平台信息为4.14.98_2.3.2,可以基于该版本开发也可以使用最新版本,我是使用这个版本开发,没有使用最新版本
nxp资源获取:Embedded Linux for i.MX Applications Processors | NXP Semiconductors
下拉页面找到相应的Linux 4.14.98_2.3.0条目有详细的资源:
开发方式:
1.yocto 方式 ----拉取、下载、配置、编译代码涉及较多配置项,代码下载编译需要解决很多问题(最好通过代理下载),编译也需要在指定ubuntu版本,第一次需要比较长的时间
2.源码方式开发 ----只需要下载相应的uboot/linux_kernel/toolchain/工具即可,首推该方式,这样后续通过repo分别管理uboot/kernel/tools/toolchain/usrfs也比较方便
note:yocto方式也需要掌握后续的制作rootfs、获取交叉工具链就是基于yocto方式来操作的
uboot源码下载:
1.git clone uboot-imx - i.MX U-Boot -b imx_v2018.03_4.14.98_2.0.0_ga
2.git tag 可以查看选择相应的tag 这里我们选择 rel_imx_4.14.98_2.3.2_patch
3.git checkout -b rel_imx_4.14.98_2.3.2_patch
kernel源码下载:
1.git clone linux-imx - i.MX Linux kernel -b imx_4.14.98_2.0.0_ga
2.git tag 可以查看选择相应的tag 这里我们选择 rel_imx_4.14.98_2.3.2_patch
3.git checkout -b rel_imx_4.14.98_2.3.2_patch
工具链:
工具链脚本sh执行一下就会在/opt目录下生成相关文件,需要编译的时候可以source 下/opt/下的env就可以了
iMX8QXP B0和C0区别:
我们目前使用的Tbox上的imx8qxp是使用的C0版本,这个影响到很多地方,前期因为这个问题踩了很多坑,简单来说C0是修复了很多B0版本报的问题,更新更稳定,nxp官网提供了C0/B0的差异和相关的patch:https://www.nxp.com.cn/docs/en/application-note/AN12770.pdf
软件层面最主要的差异是1.制作bootloader文件flash.bin的时候需要编译C0版本2.uboot的复位起始地址不一样,C0对应的是0地址
2.U-boot
概述:
在linux开发中,一个完整的Linux系统包含Bootloader,Linux kernel和Rootfile,而它的启动顺序也是Bootloader->Linux kernel->Rootfile,后者需要前者提供完整的功能支持。作为启动的第一步,Bootloader是嵌入式系统在加电后执行的第一段代码,在它完成CPU和相关硬件的初始化之后,再将操作系统映像或固化的嵌入式应用程序装载到内存中然后跳转到操作系统所在的空间,启动操作系统运行。linux中使用最广泛的bootloader就是U-boot。uboot本质上是一个裸机程序(不是操作系统),它的入口就是开机自动启动,uboot的唯一出口就是启动内核。uboot还可以执行很多别的任务(如烧录系统),但是其他任务执行完后都可以回到uboot的命令行继续执行uboot命令,而启动内核命令一旦执行就回不来了。简单来说,uboot的生命周期在开机启动,到内核启动。
note:imx8qxp中的bootloader不仅仅包含uboot,还有其他几个文件,然后通过mkimage生成一个flash.bin,主体功能依然是uboot,后续详解
uboot流程:
uboot的启动过程分为两个阶段:
- 第一阶段:通常是开发板的配置等设备初始化代码,需要依赖依赖于SoC体系结构,通常用汇编语言来实现;
- 第二阶段:准备启动内核,执行uboot命令,c语言阶段
第一阶段(汇编):
主要功能包括如下:设置异常向量表、硬件初始化(配置内核、总线、io接口等时钟)、关闭看门狗、关闭MMU、关闭中断、CACHE配置、为第二阶段代码准备ram、复制第二阶段代码到RAM、设置PC指针等.
note:第一阶段主要为soc体系和平台相关的初始化操作,不是我们关注的重点
第二阶段(c语言):
在第一阶段中,为第二阶段的函数调用提供了基础,比如gb结构,堆栈等,第二阶段还会做一些初始化相关操作,我们重点关注下board_init_r函数(board_init_f已经在前面做了一些初始化)
board_init_r中重点循环执行了init_sequence_r中的一系列初始化相关函数,init_sequence_r本身是一个函数集合,里面包括如:malloc/serial/flash/nand/mmc/initr_env/run_main_loop等相关函数
initr_env中获取和设置了uboot中的环境变量,这些环境变量会影响uboot的执行
run_main_loop:
里面重点循环函数main_loop,main_loop中
setenv("ver", version_string); /* set version variable */ ----配置uboot版本信息
cli_init 函数 ----初始化hush shell
bootdelay_process 函数 ----此函数会读取环境变量 bootdelay 和 bootcmd 的内容,然后将 bootdelay 的值赋值给全局变量 stored_bootdelay,返回值为环境变量 bootcmd 的值
autoboot_command 函数 ----等待uboot进入命令行模式超时,如果按下按键进入uboot命令行模式,超时进入run_command_list(s, -1, 0);处理,s为bootcmd环境变量命令字符串,也就是执行环境变量bootcmd字符串中命令,bootcmd 里面保存着默认的启动命令,因此 linux 内核启动!
cli_loop函数 ----如果默认3s超时内,按下按键则进入cli_loop函数处理,负责接收按键输入命令并做相应处理,也就是进入uboot命令行模式
重点分析:
uboot中环境变量:
loadaddr: ----kernel image运行在ram中地址,对应uboot config中的CONFIG_FASTBOOT_BUF_ADDR配置,imx8qxp为0x80280000
fdt_addr: ----dtb文件需要load到ram中的地址,imx8qxp为0x83000000
initrd_addr: ----initramfs等在ram中地址
bootcmd: imx8qxp_mek.h中有相关配置,重点关注CONFIG_BOOTCOMMAND宏定义,里面有相应的load image/dtb策略,通过loadimage命令实现,然后通过booti启动kernel
loadimage: 通过flatload命令load mmc指定分区(mmcblk0p1)上的指定名字的Image到ram中,然后通过booti运行kernel
bootargs: 启动参数,传递给kernel,指定console最核心的工作是指明根文件系统位置,这样内核可以找到文件系统,重点关注console/rdinit/root等参数配置
我们目前使用的配置为:console=ttyLP0,115200 earlycon=lpuart32,0x5a060000,115200 root=/dev/mmcblk0p2 rootwait rw
fatload: uboot中装载linux kernel 到内存的指令
3.UUU (Universal Update Utility)
UUU is an evolution of MFGTools (aka MFGTools v3) and is Freescale/NXP I.MX Chip image deploy tools,you can use uuu scripts to update uboot/kernel/dtb/rootfs on both windows and Linux.
下载地址:Releases · NXPmicro/mfgtools · GitHub ---根据uuu脚本要求下载相应的版本
uuu更新固件前提:让板子boot pin 进入串口下载模式,如短接boot bin
imx8qxp uuu下载过程说明:
uuu先下载bootloader到板子DDR,DDR上跑起来该bootloader,然后通过fastboot协议通信再将相应的bootloader/kernel/dtb/rootfs下载到emmc上。如果只下载bootloader到DDR上那么下次重启bootloader将任然使用老的emmc中的bootloader,新bootloader必须被flash到emmc
note:这里说bootloader而不是使用u-boot是因为这个bootloader除了uboot还包含SCFW(scfw_tcm.bin) / imx-atf(bl31.bin) / SECO(ahab-container.img),通过imx-mkimage一起打包编译而成,后续详解
常用uuu命令介绍:
uuu.exe bootloader ----直接download bootloader文件到ddr中运行,在bootlodaer启动的3s选择时间内可以敲击串口的界面停止uboot,然后再uboot阶段和host通信,如fastboot/tftp等方式
uuu脚本命令:
uuu_version 1.2.39 ----uuu的脚本要求的uuu最低版本
SDPS: ---- i.MX8QXP and i.MX8QM ROM download protocol
boot -f <filename> [-offset 0x0000] ----send image by sdp command
FB: ----Android fastboot protocol,下面详细说明fastboot协议相关命令
flash [-raw2sparse] <partition> <filename> ----通过fastboot,用指定文件烧录指定分区,烧录到emmc等
ucmd <any uboot command> ----相关uboot command下面详解
acmd <any never returned uboot command, like booti, reboot> ----
FBK: ----Android fastboot protocol, implement at initramfs
uboot command:
setenv fastboot_dev mmc ----设置fastboot flash设备,支持mmc/sata
setenv mmcdev 0 ----设置mmc设备号
setenv fastboot_buffer ----fastboot download buffer address
uuu中调用的linux命令:
dd命令: dd --help
1.Linux dd 命令用于读取、转换并输出数据。
2.dd 可从标准输入或文件中读取数据,根据指定的格式来转换数据,再输出到文件、设备或标准输出
note:我们主要用了dd命令来清空指定mmc块设备,或者将相应的bin输出到mmc块设备上
dd if=xxx of=xxx bs=512 count=1
- if 代表输入文件。如果不指定 if,默认就会从 stdin 中读取输入
- of 代表输出文件。如果不指定 of,默认就会将 stdout 作为默认输出
- bs 代表字节为单位的块大小
- count 代表被复制的块数
- seek 代表输出的起始偏移
- /dev/zero 是一个字符设备,会不断返回 0 值字节(\0)
sfdisk命令: sfdisk --help
Linux sfdisk命令是硬盘分区工具程序,可显示分区的设置信息,并检查分区是否正常,同时可以设置分区信息
sfdisk -l 显示分区信息
demo: imx8qxp emmc分区配置例子
FBK: ucmd mmc=`cat /tmp/mmcdev`; PARTSTR=$'10M,200M,0c\n300M,,83\n'; echo "$PARTSTR" | sfdisk --force /dev/mmcblk${mmc}
其中mmc是获取mmc设备号,我们这里应该是0,PARTSTR为具体的分区配置,$'10M,200M,0c\n300M,,83\n' 意思是第一个分区mmcblk0p1的分区是起始10M,大小200M,0c代表fat32文件系统,第二个分区mmcblk0p2的分区起始地址为300M,大小为剩下所有空间,83代表linux文件系统
mkfs.vfat命令:
使用mkfs.vfat命令可以为磁盘分区创建vfat文件系统,uuu中使用该命令创建fast32文件系统给kernel和dtb用
mkfs.ext3命令:
使用mkfs.ext3命令可以为磁盘分区创建ext3文件系统,uuu中使用该命令创建ext3文件系统给rootfs用
4.rootfs根文件系统
概述:
根文件系统是特殊用途的文件系统,必须属于某种文件系统格式(imx8qxp使用的ext3格式),是内核启动时挂在的第一个文件系统,根文件系统包括Linux启动时所必须的目录和关键性的文件,例如Linux启动时都需要有init相关文件,还有其他一些基础的bin和so
linux支持多种文件系统,包括ext2、ext3、vfat、ntfs、iso9660、jffs、yaffs、romfs和nfs等,为了对各类文件系统进行统一管理,Linux引入了虚拟文件系统VFS(Virtual File System),为各类文件系统提供一个统一的操作界面和应用编程接口
根文件系统目录内容简介:
- bin :基本的可执行文件,busybox生成,可以配置具体内容
- opt :添加的软件包
- boot :启动时需要的一些文件
- proc :内核及进程信息的虚拟文件系统,操作系统相关信息可以在这个下面查询
- dev : 设备文件,驱动中创建的设备在这里
- root:root用户目录
- etc: 系统配置文件
- sbin:系统管理的程序,第一个init进程sbin/init在这个地方
- home : 用户目录
- tmp : 临时文件
- lib : 库文件
- usr : 应用程序
- mnt : 挂载文件系统的挂载点
- var : 存放系统日志或一些服务程序的临时文件
note:如果需要定制自己的rootfs可以使用busybox制作,这里不做重点,已经制作好的rootfs在Tbox/tools仓库下的rootfs中,如果需要修改可以直接修改该目录下对应的内容
kernel引导rootfs,rootfs启动:
Linux系统启动过程中通过init_task创建0号idle进程。然后通过kernel_thread创建1号init进程。创建该进程时通过系统调用,在内核空间执行用户空间的/sbin/init程序,通过该程序产生出shell,并依赖init衍生出其他进程。
通过ps命令查看当前系统环境下的进程列表,可以发现1号进程的为init:
kernel中创建1号init进程主要包含下面3中方式:
ramdisk方式:
kernel cmdline需要设置了rdinit=/linuxrc ----如uboot设置bootargs
kernel代码中通过rdinit_setup解析执行
execute_command方式:
kernel cmdline需要设置了init=/sbin/init ----如uboot设置bootargs
在kernel代码中通过init_setup()解析命令行参数"init=",并赋值给execute_command
默认方式:
若以上两种指定init程序的方式均以失败告终,那么内核代码kernel_init()会执行如下4个默认的init程序,若也失败,则内核上报panic
if (!try_to_run_init_process("/sbin/init") ||
!try_to_run_init_process("/etc/init") ||
!try_to_run_init_process("/bin/init") ||
!try_to_run_init_process("/bin/sh"))
note:我们的Tbox板子目前基于该方式,执行了/sbin/init初始化进程,比较新的linux kernel的/sbin/init其实是到systemd的一个软连接