build_embed_linux_system

附录一:I.MX6ULL平台SDK操作和移植说明

本节以正点原子的阿尔法开发板进行说明,主要讲述I.MX6ULL SDK平台和应用移植功能。

板级资源网址: http://www.openedv.com/docs/boards/arm-linux/zdyz-i.mx6ull.html

本节目录如下。

summary

首先介绍芯片的框架和支持的功能,这里参考芯片手册的图形说明。

image

可以整理出关键信息。

  1. 单核Cortex-A7,32KB I-Cache,32KB D-Cache
  2. 芯片内存储OCRAM: 128KB,ROM: 96KB
  3. 支持外部接口: LCDIF,CSI interface,CAN*2,Eth*2, USB OTG*2,ADC*2,UART*8,SAI*3,ESAI*3,PWM*8,I2C*4,SPI*4,USDHC*2, KPP
  4. 内部模块: Timer*4,WDOG

这是对芯片支持外设模块的整理,在后续驱动章节会对其中大部分的模块进行说明。不过本节中最核心的是构建SDK,编译打包成芯片可访问的固件包(类似.img),如果不关心背后的原理,直接看后面相应的构建方法即可。

这部分基础可参考NXP官方文档: i.MX Linux User’s Guide,其中4.3章节有如下说明。

image

在理解这个表格前,需要先回顾下之前的讲述的ARM启动流程,I.MX6ULL属于比较简单的芯片,其启动流程如下所示。

BootROM => U-Boot => Linux Kernel => System Rootfs

BootROM加载U-Boot

可以看到,第一步就是BootROM加载U-Boot到DRAM中,然后跳转执行。那么BootROM从什么地址加载U-Boot呢?这个地址就是上面表格芯片规定的,固定为0x400(1Kb偏移地址)。BootROM将U-Boot加载到什么地方呢,当然是DRAM中的起始地址0x80000000。此后就交给了U-Boot处理了,布局和执行流程如下图所示。

image

加载完成后,跳转U-Boot执行。

U-Boot加载内核和设备树

U-Boot加载内核和设备树的方式,理论上是完全由开发者控制的。参考前面关于U-Boot环境变量和命令的说明,bootcmd中的内容,会指定使用何种命令来处理后续执行的流程,这里以常见的SDCard和网络加载进行说明。

# 从sdcard或emmc中加载内核和设备树
fatload mmc 1:1 0x80800000 zImage
fatload mmc 1:1 0x83000000 imx6ull-14x14-emmc-4.3-800x480-c.dtb;
bootz 0x80800000 - 0x83000000

其中fatload执行的是从sdcard或emmc加载文件,表示从fatfs文件系统加载地址,mmc 1:1表示从块设备/dev/mmcblk1p1对应的文件系统中读取内核文件zImage,以及设备树文件。bootz执行的是跳转内核执行的命令,具体流程如下所示。

image

通过tftp从远程的服务器加载zImage和设备树,然后通过bootz命令执行,具体流程如下所示。

tftp 0x80800000 zImage;
tftp 0x83000000 imx6ull-14x14-emmc-4.3-800x480-c.dtb;
bootz 0x80800000 - 0x83000000

image

其它的使用serial,usb,nand的加载方式都类似,都是通过接口或者命令,将zImage和设备树加载到DRAM中,之后采用同样的方式跳转执行。

内核挂载文件系统

完成上一步操作,此时内核就执行了。在内核执行的最后一步,就需要挂载根文件系统,然后执行init进程,完成系统的初始化。这一步依赖于bootargs对应的参数,这个参数可以编译到内核中,也可以由U-Boot传递。不过如果编译到内核,只能通过重新编译内核进行修改,所以大部分情况下都是由U-Boot传递,内容如下。

# 从sdcard或emmc中挂载文件系统
bootargs="console=ttymxc0,115200 panic=5 rootwait root=/dev/mmcblk1p2 earlyprintk rw"

其中root=/dev/mmcblk1p2表示挂载的文件系统,具体流程如下。

image

通过nfs挂载文件系统,对应的bootargs如下所示。

# 从nfs中挂载文件系统
bootargs="console=ttymxc0,115200 root=/dev/nfs nfsroot=${serverip}:${nfspath},proto=tcp ip=${ipaddr}:${serverip}:${gateway}:${netmask}::eth0:off earlyprintk rw

其中nfsroot参数指定可挂载的文件系统,具体流程如下。

image

可以看到,内核启动后的挂载,主要是读取指定存储文件系统,然后挂载到内存中,之后就可以通过文件系统访问内部文件和执行进程了。

至此,关于Linux启动整个流程从更详细的结构上说明完毕,作为入门的芯片,I.MX6ULL提供了详细的资料和代码,可以深入理解这部分知识。

不过了解本节后续如何制作镜像或烧录内存卡时,肯定好奇为什么要格式化分配成两个区域,其中第一个分区是fatfs,后一个分区是exfat?其实理解上面流程,就应该会有一定认知。

  1. 这里核心就是fatfts和exfat的区别。fatfs系统结构简单,具有很好的兼容性,支持的文件大小上限为4GB,exfat则针对闪存设备进行了优化,读写速度较快,尤其是在多设备、多系统间的互操作能力更强。对于U-Boot的最重要的功能就是加载内核和设备树,这两个都是不超过10M的小文件,在U-Boot中集成exfat就有些大材小用了, I.MX6ULL使用的U-Boot就通过fatload命令支持从fatfs文件系统中访问文件。目前U-Boot中只支持fatfs,为了能够执行后续加载内核和设备树,所以第一个分区只能是fatfs了。
  2. 那为啥还要第二个分区exfat呢?理论上使用一个分区能否实现功能,从上面内核启动流程看,完全可以,作为文件,内容并没有本质的区别。不过fatfs作为早期的系统,一是有文件大小的限制,另外文件较多时处理会十分慢;作为内核已经支持更先进的exfat系统,那么在建立一个分区,专用来进行kernel访问和处理文件系统,去除文件限制同时性能更强,当然是更好的选择。当然为了数据安全、兼容性以及recovery等功能的支持,添加更多的分区,也是可行的,这部分内容,在讲解到其它芯片的时候,会进行说明。

compiler_option

I.MX6ULL使用自己构建的SDK平台,其脚本文件参考I.MX6ULL脚本文件

make_uboot

#进入sdk目录
cdp

#编译emmc版本的uboot
./make.sh uboot emmc

#编译sdcard版本的uboot
./make.sh uboot sd

#编译nand版本的uboot
./make.sh uboot nand 

编译后的文件为package目录下的u-boot-dtb.imx.

make_kernel

#进入sdk目录
cdp

#编译内核
./make.sh kernel normal

#编译qemu版本的内核
./make.sh kernel qemu

编译后的文件为package目录下的zImage,imx6ull-qemu.dtb文件。

make_rootfs

文件系统支持buildroot,debian,ubuntu和qt-support-linux,详情见文件系统编译

hardware_info

以I.MX6ULL和正点原子的开发板硬件为例,主要包含以下状态。

BOOT_CFG引脚 说明
BOOT_MODE1 BOOT_MODE1为1,BOOT_MODE0时,会执行内部的boot rom代码。初始化选择硬件,同时加载U-Boot到指定RAM中, 并执行
BOOT_MODE0 BOOT_MODE1为0,BOOT_MODE1时,会进入串行下载模式,可通过USB下载使用mgtool下载固件
BOOT_CFG2[3] 为0时从SDHC1上的SD/MMC启动,为1时从SDHC2的SD/MMC启动
BOOT_CFG1[3] 当从SD/MMC启动设置启动速度,从NAND启动时设置NAND数量
BOOT_CFG11[4~7] BOOT_CFG1[7:4]:0000 NOR/OneNAND(EIM)启动; 0001 QSPI 启动; 0011 SPI 启动; 010x SD/eSD/SDXC 启动; 011x MMC/eMMC 启动; 1xxx NAND Flash 启动

引脚和列表关系。

1 2 3 4 5 6 7 8 启动设备
0 1 x x x x x x 串行下载,可以通过 USB 烧写镜像文件(2)
1 0 0 0 0 0 1 0 SD卡启动(1,7)
1 0 1 0 0 1 1 0 EMMC启动(1,3,6,7)
1 0 0 0 0 0 0 1 NAND启动(1,5,8)

串口默认波特率115200.

platform_driver

驱动设备 驱动类型 驱动说明 内核目录 匹配字符串 设备节点
ADC Analog-to-Digital Converter iio设备 drivers/iio/adc/vf610_adc.c compatible = “fsl,vf610-adc” adc1
ASRC Asynchronous Sample Rate Converter 异步采样变换器模块(音频采样) drivers/sound/soc/fsl/fsl_asrc.c compatible = “fsl,imx53-asrc” asrc
CAN Flexible Controller Area Network can通讯接口 drivers/net/can/flexcan/flexcan-core.c compatible = “fsl,imx6ul-flexcan” can1/can2
CRYPTO Cryptographic Controller 加密控制器 drivers/crypto/caam/ctrl.c compatible = “fsl,imx6ul-caam”, “fsl,sec-v4.0” crypto
CSI CMOS Sensor Interface csi摄像头接口 drivers/staging/media/imx/imx7-media-csi.c compatible = “fsl,imx6ul-csi” csi
ECSPI Enhanced Configurable SPI spi通讯总线(spi bus) drivers/spi/spi-imx.c(SPI Bus) compatible = “fsl,imx6ul-ecspi” ecspi1/ecspi2/ecspi3
EPADC Electrophoretic Display Controller 支持匹配tft背板的驱动 drivers/video/fbdev/mxc/mxc_epdc_v2_fb.c compatible = “fsl,imx7d-epdc” epdc
FEC 10/100-Mbps Ethernet MAC 网口模块接口 drivers/net/ethernet/freescale/fec_main.c compatible = “fsl,imx6ul-fec” fec1/fec2
GPMI General Purpose Media Interface nand控制器 drivers/mtd/nand/raw/gpmi-nand/gpmi-nand.c compatible = “fsl,imx6q-gpmi-nand” gpmi
GPT General Purpose Timer 通用定时器 drivers/clocksource/timer-imx-gpt.c compatible = “fsl,imx6sx-gpt” gpt1/gpt2
I2C I2C Controller i2c通讯总线(i2c Bus) drivers/i2c/busses/i2c-imx.c compatible = “fsl,imx21-i2c” i2c1/i2c2
KEYS GPIO In 按键检测功能(/dev/input/eventX) drivers/input/keyboard/gpio-keys.c compatible = “gpio-keys” gpio-keys
KPP Keypad Port (KPP) 按键控制器 drivers/input/keypad.c compatible = “fsl,imx21-kpp” kpp
LCDIF Enhanced LCD Interface 屏幕接口 drivers/video/fbdev/mxsfb.c drivers/gpu/drm/mxsfb.c compatible = “fsl,imx28-lcdif” lcdif
LEDS GPIO Out LED控制引脚(/sys/class/leds/ledX) drivers/leds/leds-gpios.c compatible = “gpio-leds” gpio_leds
OCOTP On-Chip OTP Controller 片内eFUSED otp设置 drivers/nvmem/imx-ocotp.c compatible = “fsl,imx6ul-ocotp” ocotp
OCRAMS On-Chip RAM Memory Controller 片内ram访问空间 arch/arm/mach-imx/pm-imx6.c compatible = “fsl,lpm-sram” ocrams
PWM Pulse Width Modulation pwm功率输出功能 drivers/pwm/pwm-imx27.c compatible = “fsl,imx27-pwm” pwm1 ~ pwm7
RNGB Random Number Generator random设备(/dev/randomX) drivers/char/hw_random/imx-rngc.c(/dev/randomX) compatible = “fsl,imx25-rngb” rngb
RTC Real Timer Count rtc设备(/dev/rtcX) drivers/rtc/rtc-snvs.c compatible = “fsl,sec-v4.0-mon-rtc-lp” snvs_rtc
SAI Synchronous Audio Interface 同步音频接口 drivers/sound/soc/fsl/fsl_sai.c compatible = “fsl,imx6ul-sai” sai1/sai2/sai3
SIM sim module sim卡设备 drivers/mxc/sim/imx_sim.c compatible = “fsl,imx6ul-sim” sim1/sim2
TEMPMON Temperature Monitor 温控监视模块 drivers/thermal/imx_thermal.c compatible = “fsl,imx6ul-tempmon” tempmon
TSC Touch Screen Controller tsc触摸检测接口(/dev/input/eventX) drivers/input/touchscreen/imx6ul_tsc.c compatible = “fsl,imx6ul-tsc” tsc
UART Universal Asynchronous Receiver/Transmitter uart通讯接口 drivers/tty/serial/imx.c compatible = “fsl,imx6q-uart” uart1 ~ uart7
USB Universal Serial Bus Controller USB通讯模块 drivers/usb/phy/phy-mxs-usb.c compatible = “fsl,imx6ul-usb”; usbotg1/usbotg2
USDHC Ultra Secured Digital Host Controller sd/sdio/mmc通讯接口 drivers/mmc/host/sdhci-esdhc-imx.c compatible = “fsl,imx6sx-usdhc”; usdhc1/usdhc2
WDT Watchdog Timer watchdog看门狗功能(/dev/watchdogX) drivers/watchdog/imx2_wdt.c compatible = “fsl,imx21-wdt” wdog1/wdog2/wdog3

download

在讲解如何下载之前,讲解下分区相关的实践整理,先说结论。

  1. 对于支持Linux的SOC芯片,其分区都是可以由开发者自己管理的,可以任意拆分,存放recovery数据,userdata数据等
  2. 其中u-boot在存储中的起始地址(带spl或trust-os的芯片,则是这两个部分的地址)是固定,这个一般由ROM芯片设计时确认,后面的空间则由开发者自行分配管理
  3. 对于Android或者部分官方SDK限定生成的系统,其管理分区在开发包中有限定,理论上可以修改,不过难度高,容易出问题,直接使用对应分区即可

可以看出,芯片一般限制的是起始地址,后续地址是开发者自行管理。理论上增加recovery分区用于双系统升级备份,dtbo分区用于设备树插件,userdata分区用于u-boot和kernel之间数据互传,这些都是可以实现的。下面开始I.MX6ULL的编译,构建说明。

sdk_compiler

关于sdk的编译脚本详细见: make.sh编译脚本

#编译emmc版本uboot
./make.sh uboot emmc 

#编译sd版本uboot
./make.sh uboot sd 

#编译nand版本uboot
./make.sh uboot nand
#编译内核
./make.sh kernel 
#编译buildroot
./make.sh rootfs buildroot

#编译debian系统
./make.sh rootfs debian

#编译uboot系统
./make.sh rootfs ubuntu 

mgtool

mgtool是NXP官方提供用于芯片下载的工具,可以在裸机芯片下提供软件下载和更新的方法。

  1. Uboot – uboot.bin(对于emmc则文件名为i.MX6Ull-14x14-emmc-4.3-800x480-c.bin)
  2. Kernel – zImage,i.MX6Ull-14x14-emmc-4.3-800x480-c.dts
  3. 文件系统 – rootfs.tar.bz2

有了上述软件,就可以进行后续的代码下载。

  1. mgtool的工具使用官方提供,在正点原子的产品路径里存在:A盘>5.开发工具>4.正点原子修改过的MFG_TOOL烧写工具>mfgTool,通关将拨码开关置到仅2为高,然后复位,使用Mfgtool2-eMMC-ddr256-eMMC.vbs指令进行下载测试。
  2. mgtool的工作步骤如下。

注:files路径下的文件才是要更新的固件,firmware目录是用于更新固件的系统,完成后将boot置为1,3,6,7为高,复位即可发现系统已经替换成我们编译的系统。

sdcard

对于SDCard烧录盘的制作,主要包含SD卡分区和格式化,然后分别在相应区域写入U-Boot,Kernel和文件系统即可。SDCard的分区格式如下。

image

=======================================
- U-Boot Region(1kb offset)
  #only for i.MX6Ull, other may difference
=======================================
- Partition 1: kernel + dtbs(FAT32格式)
  #system region, store kernel and dtbs
=======================================
- Partition 2: rootfs(EXT3)
  #filesystm region
=======================================

进行创建sdcard下载环境的脚本。

rootfs=rootfs.tar.bz2
uboot=u-boot-i.MX6Ull-14x14-ddr512-emmc.imx #u-boot的名称,根据实际修改
sd_dev=/dev/sdf #u盘对应得地址,sda为系统盘,一定不能使用

#umount device
for i in `ls -1 ${sd_dev}?`; do
    echo "umout device '$i'"
    umount $i 2>/dev/null
done

#1. 清除SDCard内原数据
dd if=/dev/zero of=${sd_dev} bs=1024 count=1024

#2. 将SD卡分成2个区, 第一个区FAT32,第二个区EXT3
cat << END | fdisk -H 255 -S 63 $sd_dev
n
p
1

+64M
n
p
2


t
1
c
a
1
w
END

echo "formatting ${sd_dev}1 ..."
partition1=${sd_dev}1
if [ ! -b ${partition1} ]; then
    partition1=${sd_dev}1
fi
if [ -b ${partition1} ]; then
    mkfs.vfat -F 32 -n "boot" ${partition1}
else
    echo "formatting boot failed"
fi

echo "formatting ${device}2 ..."
partition2="${sd_dev}"2
if [ ! -b "${partition2}" ]; then
    partition2=${sd_dev}2
fi
if [ -b ${partition2} ]; then
    mkfs.ext3 -F -L "rootfs" ${partition2}
else
    echo "formatting rootfs failed"
fi

while [ ! -e ${sd_dev} ]
do
    sleep 1
    echo "wait for ${sd_dev} appear"
done

#3.烧录uboot到起始偏移1K地址(由芯片决定的地址)
echo "burn ${uboot} to ${sd_dev}"
dd if="${uboot}" of="${sd_dev}" bs=1024 seek=1 conv=fsync
sync
echo "burn "${uboot}" to "${sd_dev}" finished!"
sleep 2

#4.将zImage和dtb拷贝到boot目录
echo "start copy..."
mkdir -p 777 /tmp/sdk/
mount "${sd_dev}"1 /tmp/sdk/
cp -r *.dtb /tmp/sdk/
cp -r zImage /tmp/sdk/
umount /tmp/sdk/
sleep 2

#5.将文件系统解压到rootfs目录
mount ${sd_dev}2 /tmp/sdk/
tar -xvf ${rootfs} -C /tmp/sdk/
umount /tmp/sdk/
sleep 2

echo "burn all to ${sd_dev} finished, success!"

emmc

在EMMC运行环境下,更新emmc内的代码。

image

EMMC分区时,和SDCard的一致,具体格式如下。

=======================================
- U-Boot Region, start-offset 1kb
=======================================
- Partition 1: Kernel + dtbs(FAT32格式)
=======================================
- Partition 2: rootfs(EXT3)
=======================================
# 1. 更新u-boot地址代码(将u-boot文件写入块0偏移1k的地方)
# EMMC为1
mmcdev=1

echo 0 > /sys/block/mmcblk1boot0/force_ro
dd if=u-boot-i.MX6Ull-14x14-ddr512-emmc.imx of=/dev/mmcblk1boot0 bs=1024 seek=1 conv=fsync
echo 1 > /sys/block/mmcblk1boot0/force_ro
sync

# 2. 更新设备树和内核
mmc bootpart enable 1 1 /dev/mmcblk1
cp zImage /run/media/mmcblk1p1
cp *.dtb /run/media/mmcblk1p1
sync

# 3. 更新文件系统
# 正在运行的系统无法格式化,需要用SDCard或者nfs更新文件系统
rm -rf /run/media/mmcblk1p2/*
tar -xvf rootfs.tar.bz2 -C /run/media/mmcblk1p2/
sync

nand

关于Nand的更新,同样是将数据写入到Nand的相应区域,可通过cat /proc/mtd查看, 以I.MX6ULL为例,分区如下:

分区 存储内容 说明 地址范围
mtd0 u-boot u-boot分区 00400000
mtd1 env 环境变量分区 00020000
mtd2 logo 显示logo分区 00100000
mtd3 dtb 设备树分区 00100000
mtd4 kernel 内核分区 008000000
mtd5 rootfs 文件系统分区 1f1e0000
# flash_erase [fs_block] <start_offset> <block_count>

#1. 更新u-boot地址代码
flash_erase /dev/mtd0 0 0
kobs-ng init -x -v --chip_0_device_path=/dev/mtd0 u-boot-i.MX6Ull-14x14-ddr256-nand.imx
sync

#2. 更新设备树
flash_erase /dev/mtd3 0 0
nandwrite -p /dev/mtd3 /home/root/i.MX6Ull-14x14-nand-4.3-480x272-c.dtb
nandwrite -s 0x20000 -p /dev/mtd3 /home/root/i.MX6Ull-14x14-nand-4.3-800x480-c.dtb
sync

#3. 更新内核
flash_erase /dev/mtd4 0 0
nandwrite -p /dev/mtd4 zImage
sync

#4. 更新文件系统(需要从SD卡或网络启动,才能更新文件系统到nand中,在本系统执行下不能够直接更新文件系统)
flash_erase /dev/mtd5 0 0
ubiformat /dev/mtd5                     # 格式化mtd5分区
ubiattach /dev/ubi_ctrl -m 5            # 在UBI控制设备上挂载一个物理卷,-m指定mtd设备的编号
ubimkvol /dev/ubi0 -Nrootfs -m          # 指定/dev/ubi0设备上创建一个名为rootfs的卷,根据剩余空间自行分配 
mkdir -p /mnt/mtd5                      # 创建挂载/dev/mtd5的地方
mount -t ubifs ubi0:rootfs /mnt/mtd5    # 将ubi0上的rootfs卷挂载到/mnt/mtd5上
tar jxvfm rootfs.tar.bz2 -C /mnt/mtd5/  # 解压文件到/mnt/mtd5中
sync

umount /mnt/mtd5
rm -rf /mnt/mtd5
ubidetach -p /dev/mtd5                  # 将mtd5从/dev/ubi0设备上移除
sync

image

构建支持SDCard下载的镜像,使用balenaEtcher烧录到SD卡中。

######### 创建镜像格式文件,第一次需要 ###############
#创建一个用于存储所有信息的硬盘
dd if=/dev/zero of=update.img bs=1M count=4096

#将磁盘转换成虚拟设备(这里是/dev/loop0)
dev_loop=$(losetup -f --show update.img)
filename=$(basename "$dev_loop")

if [ ! -f ${dev_loop} ]; then
    echo "create failed, please check!"
    exit 0
fi

cat << END | fdisk -H 255 -S 63 ${dev_loop}
n
p
1

+64M
n
p
2


t
1
c
a
1
w
END

#需要u盘重启连接,不过我们是内部的,无法重启,因此这一步报错正常
#Re-reading the partition table failed.: Invalid argument
sudo kpartx -av ${dev_loop}

partition1=/dev/mapper/"${filename}"p1
partition2=/dev/mapper/"${filename}"p2

sudo mkfs.vfat -F 32 -n "boot" ${partition1}
sudo mkfs.ext3 -F -L "rootfs" ${partition2}
sudo mkdir -p part1
sudo mkdir -p part2

sudo mount ${partition1} part1/
sudo mount ${partition2} part2/

sudo cp -fv zImage *.dtb  *.scr part1/
sudo cp -fv rootfs/* part2/

sudo umount part1/
sudo umount part2/

#uboot写入
#写入uboot
sudo dd if=u-boot-dtb.imx of=update.img bs=1024 seek=1 conv=notrunc

sudo kpartx -d ${dev_loop}
sudo losetup -d ${dev_loop}

#使用镜像烧录工具(balenaEtcher),写入SD卡即可
https://etcher.balena.io/#download-etcher/

后续更新流程如下。

# 将update.img设备关联到/dev/loop上
dev_loop=$(losetup -f --show update.img)
filename=$(basename "$dev_loop")

# 用于在loop设备上创建分区映射的工具
sudo kpartx -av ${dev_loop}

partition1=/dev/mapper/"${filename}"p1
partition2=/dev/mapper/"${filename}"p2

# 将分区文件挂载到part中
sudo mount ${partition1} part1/
sudo mount ${partition2} part2/

# 之后将文件复制到其中即可

next_chapter

返回目录

直接开始下一章节说明: RK3568平台编译方法