本节以正点原子的阿尔法开发板进行说明,主要讲述I.MX6ULL SDK平台和应用移植功能。
板级资源网址: http://www.openedv.com/docs/boards/arm-linux/zdyz-i.mx6ull.html
本节目录如下。
首先介绍芯片的框架和支持的功能,这里参考芯片手册的图形说明。
可以整理出关键信息。
这是对芯片支持外设模块的整理,在后续驱动章节会对其中大部分的模块进行说明。不过本节中最核心的是构建SDK,编译打包成芯片可访问的固件包(类似.img),如果不关心背后的原理,直接看后面相应的构建方法即可。
这部分基础可参考NXP官方文档: i.MX Linux User’s Guide,其中4.3章节有如下说明。
在理解这个表格前,需要先回顾下之前的讲述的ARM启动流程,I.MX6ULL属于比较简单的芯片,其启动流程如下所示。
BootROM => U-Boot => Linux Kernel => System Rootfs
可以看到,第一步就是BootROM加载U-Boot到DRAM中,然后跳转执行。那么BootROM从什么地址加载U-Boot呢?这个地址就是上面表格芯片规定的,固定为0x400(1Kb偏移地址)。BootROM将U-Boot加载到什么地方呢,当然是DRAM中的起始地址0x80000000。此后就交给了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执行的是跳转内核执行的命令,具体流程如下所示。
通过tftp从远程的服务器加载zImage和设备树,然后通过bootz命令执行,具体流程如下所示。
tftp 0x80800000 zImage;
tftp 0x83000000 imx6ull-14x14-emmc-4.3-800x480-c.dtb;
bootz 0x80800000 - 0x83000000
其它的使用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表示挂载的文件系统,具体流程如下。
通过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参数指定可挂载的文件系统,具体流程如下。
可以看到,内核启动后的挂载,主要是读取指定存储文件系统,然后挂载到内存中,之后就可以通过文件系统访问内部文件和执行进程了。
至此,关于Linux启动整个流程从更详细的结构上说明完毕,作为入门的芯片,I.MX6ULL提供了详细的资料和代码,可以深入理解这部分知识。
不过了解本节后续如何制作镜像或烧录内存卡时,肯定好奇为什么要格式化分配成两个区域,其中第一个分区是fatfs,后一个分区是exfat?其实理解上面流程,就应该会有一定认知。
I.MX6ULL使用自己构建的SDK平台,其脚本文件参考I.MX6ULL脚本文件
#进入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.
#进入sdk目录
cdp
#编译内核
./make.sh kernel normal
#编译qemu版本的内核
./make.sh kernel qemu
编译后的文件为package目录下的zImage,imx6ull-qemu.dtb文件。
文件系统支持buildroot,debian,ubuntu和qt-support-linux,详情见文件系统编译。
以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.
驱动设备 | 驱动类型 | 驱动说明 | 内核目录 | 匹配字符串 | 设备节点 |
---|---|---|---|---|---|
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 |
在讲解如何下载之前,讲解下分区相关的实践整理,先说结论。
可以看出,芯片一般限制的是起始地址,后续地址是开发者自行管理。理论上增加recovery分区用于双系统升级备份,dtbo分区用于设备树插件,userdata分区用于u-boot和kernel之间数据互传,这些都是可以实现的。下面开始I.MX6ULL的编译,构建说明。
关于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是NXP官方提供用于芯片下载的工具,可以在裸机芯片下提供软件下载和更新的方法。
有了上述软件,就可以进行后续的代码下载。
注:files路径下的文件才是要更新的固件,firmware目录是用于更新固件的系统,完成后将boot置为1,3,6,7为高,复位即可发现系统已经替换成我们编译的系统。
对于SDCard烧录盘的制作,主要包含SD卡分区和格式化,然后分别在相应区域写入U-Boot,Kernel和文件系统即可。SDCard的分区格式如下。
=======================================
- 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分区时,和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的相应区域,可通过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
构建支持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/
# 之后将文件复制到其中即可
直接开始下一章节说明: RK3568平台编译方法