小白自述:本章学习笔记虽然是一步步移植uboot,实际是为了学习在移植过程中能够对uboot源码各个功能有更深入地了解。
半导体厂商会将 uboot 移植到他们自己的原厂开发板上,测试好以后就会将这个 uboot 发布出去,这就是大家常说的原厂 BSP 包。然后在原厂提供的 BSP 包上做修改,将 uboot 或者 linux kernel 移植到我们的硬件上。
正点原子的 I.MX6ULL 开发板参考的是 NXP 官方的 I.MX6ULL EVK 开发板做的硬件(文件路径:1、例程源码->4、 NXP 官方原版 Uboot 和 Linux->ubootimx-rel_imx_4.1.15_2.1.0_ga.tar.bz2)。
在编译之前的重要一步:配置文件 配置uboot, configs 目录下有很多跟 I.MX6UL/6ULL 有关的配置: 我们使用 mx6ull_14x14_evk_emmc_defconfig 作为默认配置文件。
命令还是和之前初学uboot一样的编译命令:配置、编译。
NXP官方没有规定ARCH 和 CORSS_COMPILE这两个变量的值,优化之后, 方法一:直接在顶层Makefile这种添加(解决了之前初学uboot时遇到的问题) 如此,直接运行命令:
make mx6ull_14x14_evk_emmc_defconfig make V=1 -j16方法二:直接创建个 shell 脚本就行了, mx6ull_14x14_emmc.sh
#!/bin/bash make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- distclean make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- mx6ull_14x14_evk_emmc_defconfig make V=1 ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -j16然后直接执行脚本即可
./mx6ull_14x14_evk_emmc.sh虽然是官方uboot,但都是I.MX6ULL芯片烧写到板子上也是可以运行的,就是可能一些外设驱动不兼容。
1、 SD 卡和 EMMC 驱动检查
mmc list //查看设备列表 mmc dev 0 //查看SD mmc info //查询信息 mmc dev 1 //查看emmc mmc info //查询信息DRAM、SD 卡和 EMMC都正常驱动。
2、 LCD 驱动检查 如果 uboot 中的 LCD 驱动正确的话,启动 uboot 以后 LCD (正点原子的 4.3 寸 480x272 分辨率的屏幕)上应该会显示出 NXP 的 logo,回头验证一下。
只适用于规定LCD驱动。
3、网络驱动 正点原子开发板的网络芯片复位引脚和 NXP 官方开发板不一样,因此需要修改驱动。
网络驱动不兼容。
接下来我们要做的工作如下: ①、前面我们一直使用着 NXP 官方开发板的 uboot 配置,接下来需要在 uboot 中添加正点原子的 I.MX6ULL 开发板配置。 ②、解决 LCD 驱动和网络驱动的问题。
在/configs下mx6ull_14x14_evk_emmc_defconfig配置文件的基础上修改为mx6ull_alientek_emmc_defconfig配置文件。就是一些基本配置。
修改内容:
CONFIG_SYS_EXTRA_OPTIONS="IMX_CONFIG=board/freescale/mx6ull_alientek_emmc/imximage.cfg,MX6ULL_EVK_EMMC_REWORK" CONFIG_ARM=y CONFIG_ARCH_MX6=y CONFIG_TARGET_MX6ULL_ALIENTEK_EMMC=y CONFIG_CMD_GPIO=y在/include/configs/mx6ullevk.h头文件的基础上修改为mx6ull_alientek_emmc.h头文件。 修改头文件名为:
#ifndef __MX6ULL_ALIENTEK_EMMC_CONFIG_H #define __MX6ULL_ALIENTEK_EMMC_CONFIG_Hmx6ull_alientek_emmc.h 里面有很多宏定义,这些宏定义基本用于配置 uboot,也有一些I.MX6ULL 的配置项目,一些用不到的配置是可以去掉的,精简版。如果需要某个功能的话就在里面添加这个功能对应的 CONFIG_XXX 宏即可,如果不需要某个功能的话就删除掉对应的宏即可。 接下来简单了解一下配置宏定义:
PHYS_SDRAM_SIZE: 板子上 DRAM 的大小, NXP 官方的 9X9 EVK 开发板的话 DRAM 大小就为 256MB,否则的话默认为 512MB。
CONFIG_DISPLAY_CPUINFO: uboot 启动的时候可以输出 CPU 信息。
CONFIG_DISPLAY_BOARDINFO: uboot 启动的时候可以输出板子信息。
CONFIG_SYS_MALLOC_LEN :malloc 内存池大小,这里设置为 16MB。
CONFIG_BOARD_EARLY_INIT_F:这样 board_init_f 函数就会调用board_early_init_f 函数。
CONFIG_BOARD_LATE_INIT:这样 board_init_r 函数就会调用board_late_init 函数。
CONFIG_MXC_UART_BASE :表示串口寄存器基地址,这里使用的串口 1,基地址为 UART1_BASE。UART1_BASE 定义在文件arch/arm/include/asm/arch-mx6/imx-regs.h 中, imx-regs.h 是 I.MX6ULL 寄存器描述文件。
CONFIG_SYS_FSL_ESDHC_ADDR:为 EMMC 所使用接口的寄存器基地址,也就是 USDHC2 的基地址。 CONFIG_SYS_FSL_USDHC_NUM 表示 USDHC 数量。 如果使用NAND 的只能使用一个 USDHC 设备(SD 卡); 如果没有使用 NAND,那么就有两个 USDHC 设备(EMMC 和 SD 卡) 。
CONFIG_MFG_NAND_PARTITION:NAND 的分区设置。
CONFIG_EXTRA_ENV_SETTINGS:此宏会设置 bootargs 这个环境变量,后面我们会详细分析这个宏定义。
CONFIG_BOOTCOMMAND:此宏就是设置环境变量 bootcmd 的值。
CONFIG_CMD_MEMTEST:设置命令 memtest 相关宏定义。
CONFIG_SYS_LOAD_ADDR :表示 linux kernel 在 DRAM 中的加载地址,也就是 linux kernel 在 DRAM 中的存储首地址, CONFIG_LOADADDR=0X80800000。
CONFIG_SYS_HZ :为系统时钟频率,这里为 1000Hz。
CONFIG_STACKSIZE :为栈大小,这里为 128KB。
CONFIG_NR_DRAM_BANKS :为 DRAM BANK 的数量, I.MX6ULL 只有一个 DRAM BANK,我们也只用到了一个 BANK,所以为 1。
PHYS_SDRAM :为 I.MX6ULL 的 DRAM 控制器 MMDC0 所管辖的 DRAM 范围起始地址,也就是 0X80000000。
CONFIG_SYS_SDRAM_BASE: 为 DRAM 的起始地址。
CONFIG_SYS_INIT_RAM_ADDR: 为 I.MX6ULL 内部 IRAM 的起始地址(也就是 OCRAM 的起始地址),为 0X00900000。
CONFIG_SYS_INIT_RAM_SIZE: 为 I.MX6ULL 内部 IRAM 的大小(OCRAM的大小),为 0X00040000=256KB。
CONFIG_SYS_INIT_SP_OFFSET:初始 SP 偏移。
CONFIG_SYS_INIT_SP_ADDR:初始 SP 地址。
CONFIG_SYS_MMC_ENV_DEV:为默认的MMC设备,这里默认为USDHC2,也就是 EMMC,等于 1 。
CONFIG_SYS_MMC_ENV_PART :为模式分区,默认为第 0 个分区。
CONFIG_MMCROOT :设置进入 linux 系统的根文件系统所在的分区,这里设置为"/dev/mmcblk1p2",也就是 EMMC 设备的第 2 个分区。第 0 个分区保存 uboot,第 1 个分区保存 linux 镜像和设备树,第 2 个分区为 Linux 系统的根文件系统。
CONFIG_ENV_SIZE :为环境变量大小,默认为 8KB。
CONFIG_ENV_OFFSET: 为环境变量偏移地址,这里的偏移地址是相对于存储器的首地址。 如果环境变量保存在 EMMC 中的话,环境变量偏移地址为 12* 64KB。 如果环境变量保存在 SPI FLASH 中的话,偏移地址为 768* 1024。 如果环境变量保存在 NAND 中的话,偏移地址为 60<<20(60MB),并且重新设置环境变量的大小为 128KB。
CONFIG_FEC_ENET_DEV: 指定 uboot 所使用的网口, I.MX6ULL 有两个网口,为 0 的时候使用 ENET1,为 1 的时候使用 ENET2。 IMX_FEC_BASE :为 ENET 接口的寄存器首地址。
CONFIG_FEC_MXC_PHYADDR :为网口 PHY 芯片的地址。
CONFIG_FEC_XCV_TYPE: 为PHY 芯片所使用的接口类型, I.MX6U-ALPHA 开发板的两个 PHY 都使用的 RMII 接口。
剩下的都是一些配置宏,比如 CONFIG_VIDEO 宏用于开启 LCD,CONFIG_VIDEO_LOGO 使能 LOGO 显示, CONFIG_CMD_BMP 使能 BMP 图片显示指令。这样就可以在 uboot 中显示图片了,一般用于显示 logo。
uboot 中每个板子都有一个对应的文件夹来存放板级文件,比如开发板上外设驱动文件等等。 NXP 的 I.MX 系列芯片的所有板级文件夹都存放在 board/freescale 目录下。 board/freescale/mx6ullevk文件夹修改为mx6ull_alientek_emmc文件夹。 mx6ull_alientek_emmc 目录中mx6ullevk.c 修改为mx6ull_alientek_emmc.c。
修改 mx6ull_alientek_emmc 目录下的 Makefile 文件 # (C) Copyright 2015 Freescale Semiconductor, Inc. # # SPDX-License-Identifier: GPL-2.0+ # obj-y := mx6ull_alientek_emmc.o extra-$(CONFIG_USE_PLUGIN) := plugin.bin $(obj)/plugin.bin: $(obj)/plugin.o $(OBJCOPY) -O binary --gap-fill 0xff $< $@重点是第 6 行的 obj-y,改为 mx6ull_alientek_emmc.o,这样才会编译 mx6ull_alientek_emmc.c这个文件。
修改 mx6ull_alientek_emmc 目录下的 imximage.cfg 文件 PLUGIN board/freescale/mx6ullevk/plugin.bin 0x00907000 修改为: PLUGIN board/freescale/mx6ull_alientek_emmc/plugin.bin 0x00907000 修改 mx6ull_alientek_emmc 目录下的 Kconfig 文件 if TARGET_MX6ULL_ALIENTEK_EMMC config SYS_BOARD default "mx6ull_alientek_emmc" config SYS_VENDOR default "freescale" config SYS_SOC default "mx6" config SYS_CONFIG_NAME default "mx6ull_alientek_emmc" endif 修改 mx6ull_alientek_emmc 目录下的 MAINTAINERS 文件 MX6ULL_ALIENTEK_EMMC BOARD M: Peng Fan <peng.fan@nxp.com> S: Maintained F: board/freescale/mx6ull_alientek_emmc/ F: include/configs/mx6ull_alientek_emmc.h板级文件夹修改完了,感觉就是将官方的文件夹或者文件改了个名字。
uboot 是支持图形界面配置,修改文件 /arch/arm/cpu/armv7/mx6/Kconfig,在 207 行加入如下内容:
config TARGET_MX6ULL_ALIENTEK_EMMC bool "Support mx6ull_alientek_emmc" select MX6ULL select DM select DM_THERMAL最后一行的 endif 的前一行添加如下内容,指向板级文件的配置文件:
source "board/freescale/mx6ull_alientek_emmc/Kconfig"一般 uboot 中修改驱动基本都是在 xxx.h 和 xxx.c 这两个文件中进行的, xxx 为板子名称。
本小节主要修改mx6ull_alientek_emmc.h 和 mx6ull_alientek_emmc.c 这两个文件,一般修改 LCD 驱动重点注意以下几点: ①、 LCD 所使用的 GPIO,查看 uboot 中 LCD 的 IO 配置是否正确。(正确) ②、 LCD 背光引脚 GPIO 的配置。(正确) ③、 LCD 配置参数是否正确。(需修改)
配置参数
struct display_info_t const displays[] = {{ .bus = MX6UL_LCDIF1_BASE_ADDR, .addr = 0, .pixfmt = 24, .detect = NULL, .enable = do_enable_parallel_lcd, .mode = { .name = "TFT43AB", .xres = 480, .yres = 272, .pixclock = 108695, .left_margin = 8, .right_margin = 4, .upper_margin = 2, .lower_margin = 4, .hsync_len = 41, .vsync_len = 10, .sync = 0, .vmode = FB_VMODE_NONINTERLACED } } };就是根据RGB屏幕的具体参数,修改程序中的参数,本人使用了最便宜的ATK4342。
接着: 修改 mx6ull_alientek_emmc.h文件中所有panel=TFT4342和.name一致。 最后: 烧写到EMMC或者SD卡中,环境变量panel和自己的屏幕.name一致,就可以正常启动LCD了。
补充说明
会发现以上结构体变量的初始化使用了".成员变量",加"."的作用是初始化时不用考虑成员变量的顺序,这种方式在linux驱动中应用广泛。
display_info_t这个结构体是 LCD信息结构体,其中包括了 LCD 的分辨率,像素格式, LCD 的各个参数等,定义 在文件 arch/arm/include/asm/imx-common/video.h 中:
struct display_info_t { int bus; int addr; int pixfmt; int (*detect)(struct display_info_t const *dev); void (*enable)(struct display_info_t const *dev); struct fb_videomode mode; };pixfmt 是像素格式,也就是一个像素点是多少位,使用 RGB888就是24位。 fb_videomode定义在文件 include/linux/fb.h 中:
struct fb_videomode { const char *name; /* optional */ u32 refresh; /* optional */ u32 xres; u32 yres; u32 pixclock; u32 left_margin; u32 right_margin; u32 upper_margin; u32 lower_margin; u32 hsync_len; u32 vsync_len; u32 sync; u32 vmode; u32 flag; };name: LCD 名字,要和环境变量中的 panel 相等。 xres、 yres: LCD X 轴和 Y 轴像素数量。 pixclock:像素时钟,每个像素时钟周期的长度,单位为皮秒。 left_margin: HBP,水平同步后肩。 right_margin: HFP,水平同步前肩。 upper_margin: VBP,垂直同步后肩。 lower_margin: VFP,垂直同步前肩。 hsync_len: HSPW,行同步脉宽。 vsync_len: VSPW,垂直同步脉宽。 vmode: 大多数使用 FB_VMODE_NONINTERLACED,也就是不使用隔行扫描。
比如,正点原子的 7 寸 1024*600 分辨率的屏幕(ATK7016),屏幕要求的像素时钟为 51.2MHz,那么pixclock=(1/51200000)*10^12=19531。 像素时钟为 9MHz,那么pixclock=(1/9000000)*10^12=111111。
②、修改 ENET2 网络 PHY 的地址0x1。
#define CONFIG_FEC_MXC_PHYADDR 0x1③、使能 SMSC 公司的 PHY 驱动。
#define CONFIG_PHY_SMSC 删除 uboot 中 74LV595 的驱动代码 uboot 中网络 PHY 芯片地址修改完成以后就是网络复位引脚的驱动修改了, 74LV595 可用来扩展 IO,NXP正是使用扩展IO来控制复位引脚的,定位mx6ull_alientek_emmc.c文件: ENET1 的复位引脚连接到 SNVS_TAMPER7 上,对应 GPIO5_IO07, ENET2 的复位引脚连接到 SNVS_TAMPER8 上,对应 GPIO5_IO08。 重新使用其中的两个复位引脚。但是但是但是,正点原子的 I.MX6U-ALPHA 开发板并没有使用 74LV595,因此我们将相关的宏定义和函数代码删除掉。 uboot启动的时候,board_init_r 会调用板子初始化函数board_init 函数,board_init 会调用 imx_iomux_v3_setup_multiple_pads 和 iox74lv_init 这两个函数来初始化74lv595 的 GPIO,将这两行删除掉。
1、替换 #define IOX_SDI IMX_GPIO_NR(5, 10) #define IOX_STCP IMX_GPIO_NR(5, 7) #define IOX_SHCP IMX_GPIO_NR(5, 11) #define IOX_OE IMX_GPIO_NR(5, 8) 替换为 #define ENET1_RESET IMX_GPIO_NR(5, 7) #define ENET2_RESET IMX_GPIO_NR(5, 8) 2、删除以下函数 static iomux_v3_cfg_t const iox_pads[] = { /* IOX_SDI */ MX6_PAD_BOOT_MODE0__GPIO5_IO10 | MUX_PAD_CTRL(NO_PAD_CTRL), /* IOX_SHCP */ MX6_PAD_BOOT_MODE1__GPIO5_IO11 | MUX_PAD_CTRL(NO_PAD_CTRL), /* IOX_STCP */ MX6_PAD_SNVS_TAMPER7__GPIO5_IO07 | MUX_PAD_CTRL(NO_PAD_CTRL), /* IOX_nOE */ MX6_PAD_SNVS_TAMPER8__GPIO5_IO08 | MUX_PAD_CTRL(NO_PAD_CTRL), }; static void iox74lv_init(void) { ...... } void iox74lv_set(int index) { ...... } 3、删除函数中的两行 int board_init(void) { ...... imx_iomux_v3_setup_multiple_pads(iox_pads, ARRAY_SIZE(iox_pads)); iox74lv_init(); ...... } 添加 I.MX6U-ALPHA 开发板网络复位引脚驱动 mx6ull_alientek_emmc.c 文件中,结构体数组 fec1_pads 和 fec2_pads 是 ENET1 和 ENET2 这两个网口的 IO 配置参数,在这两个数组中添加两个网口的复位 IO 配置参数,只是存个配置参数: static iomux_v3_cfg_t const fec1_pads[] = { MX6_PAD_GPIO1_IO06__ENET1_MDIO | MUX_PAD_CTRL(MDIO_PAD_CTRL), MX6_PAD_GPIO1_IO07__ENET1_MDC | MUX_PAD_CTRL(ENET_PAD_CTRL), ...... MX6_PAD_ENET1_RX_ER__ENET1_RX_ER | MUX_PAD_CTRL(ENET_PAD_CTRL), MX6_PAD_ENET1_RX_EN__ENET1_RX_EN | MUX_PAD_CTRL(ENET_PAD_CTRL), //添加内容 MX6_PAD_SNVS_TAMPER7__GPIO5_IO07 | MUX_PAD_CTRL(NO_PAD_CTRL), }; static iomux_v3_cfg_t const fec2_pads[] = { MX6_PAD_GPIO1_IO06__ENET2_MDIO | MUX_PAD_CTRL(MDIO_PAD_CTRL), MX6_PAD_GPIO1_IO07__ENET2_MDC | MUX_PAD_CTRL(ENET_PAD_CTRL), ...... MX6_PAD_ENET2_RX_EN__ENET2_RX_EN | MUX_PAD_CTRL(ENET_PAD_CTRL), MX6_PAD_ENET2_RX_ER__ENET2_RX_ER | MUX_PAD_CTRL(ENET_PAD_CTRL), //添加内容 MX6_PAD_SNVS_TAMPER8__GPIO5_IO08 | MUX_PAD_CTRL(NO_PAD_CTRL), };函数 setup_iomux_fec 就是根据 fec1_pads 和 fec2_pads 这两个网络 IO 配置数组来初始化I.MX6ULL 的网络 IO。我们需要在其中添加网络复位 IO 的初始化代码,并且复位一下 PHY 芯片,这才初始化对应的IO口,设置为输出并且硬件复位一下 LAN8720A,这个硬件复位很重要!否则可能导致 uboot 无法识别 LAN8720A。:
static void setup_iomux_fec(int fec_id) { if (fec_id == 0) { imx_iomux_v3_setup_multiple_pads(fec1_pads, ARRAY_SIZE(fec1_pads)); //添加内容 gpio_direction_output(ENET1_RESET, 1); gpio_set_value(ENET1_RESET, 0); mdelay(20); gpio_set_value(ENET1_RESET, 1); } else { imx_iomux_v3_setup_multiple_pads(fec2_pads, ARRAY_SIZE(fec2_pads)); //添加内容 gpio_direction_output(ENET2_RESET, 1); gpio_set_value(ENET2_RESET, 0); mdelay(20); gpio_set_value(ENET2_RESET, 1); } } 修改 drivers/net/phy/phy.c 文件中的函数 genphy_update_link 函数 genphy_update_link,这是个通用 PHY 驱动函数,此函数用于更新 PHY 的连接状态和速度。使用 LAN8720A 的时候需要在此函数中添加一些代码,条件编译代码段,只有使用 SMSC 公司的 PHY 这段代码才会执行: int genphy_update_link(struct phy_device *phydev) { unsigned int mii_reg; #ifdef CONFIG_PHY_SMSC static int lan8720_flag = 0; int bmcr_reg = 0; if (lan8720_flag == 0) { bmcr_reg = phy_read(phydev, MDIO_DEVAD_NONE, MII_BMCR); phy_write(phydev, MDIO_DEVAD_NONE, MII_BMCR, BMCR_RESET); while(phy_read(phydev, MDIO_DEVAD_NONE, MII_BMCR) & 0X8000) { udelay(100); } phy_write(phydev, MDIO_DEVAD_NONE, MII_BMCR, bmcr_reg); lan8720_flag = 1; } #endif /* * Wait if the link is up, and autonegotiation is in progress * (ie - we're capable and it's not done) */ mii_reg = phy_read(phydev, MDIO_DEVAD_NONE, MII_BMSR); ... return 0; }mx6ull_alientek_emmc.c文件中,找到函数checkboard,修改如下,这样就会修改uboot启动后显示的名字:
int checkboard(void) { if (is_mx6ull_9x9_evk()) puts("Board: MX6ULL 9x9 EVK\n"); else puts("Board: MX6ULL ALIENTEK EMMC\n"); return 0; }补充说明
网络驱动部分:MAC+PHY 芯片。 传统的SOC(如2440、4412):只能DM9000 包括MAC+PHY 芯片,依靠类似 SRAM 的访问接口进行通信。 I.MX6UL/ULL:内部 MAC+外部 PHY 芯片,带有一个专用 DMA 实现快速通信。
以太网 MAC 外设,也就是 ENET,I.MX6UL/ULL 有两个网络接口 ENET1 和 ENET2, LAN8720A (体积小、外围器件少、价格便宜)作为 PHY 芯片,但是本人的mini板只设置了一个网络接口ENET1。
接下来介绍LAN8720A芯片,LAN8720A 内部是有寄存器的, I.MX6ULL 会读取 LAN8720 内部寄存器来判断当前的物理链接状态、连接速度(10M 还是 100M)和双工状态(半双工还是全双工)。 ENET1 复位引脚ENET1_RST 接到了 I.M6ULL 的 SNVS_TAMPER7 这个引脚上。I.MX6ULL 通过 MDIO接口来读取 PHY 芯片的内部寄存器 , MDIO 接口有两个引脚, ENET_MDC 和 ENET_MDIO,ENET_MDC 提供时钟, ENET_MDIO 进行数据传输。使用软件寻址,一个 MIDO 接口可以管理 32 个 PHY 芯片,同一个 MDIO 接口下的这些 PHY 使用不同的器件地址来做区分, MIDO 接口通过不同的器件地址即可访问到相应的 PHY 芯片,ENET1对应的PHY(LAN8720A)芯片地址就是0X0。 修改 ENET1 网络驱动的话重点就三点: ①、 ENET1 复位引脚初始化。 ②、 LAN8720A 的器件 ID。 ③、 LAN8720 驱动。
网络驱动修改就是如此,感觉只是大概了解了一下uboot移植工程。
这是uboot 中两个非常重要的环境变量。
bootcmd 和 bootargs 环境变量首先位于文件 include/env_default.h中:
14 env_t environment __PPCENV__ = { 15 ENV_CRC, /* CRC Sum */ 16 #ifdef CONFIG_SYS_REDUNDAND_ENVIRONMENT 17 1, /* Flags: valid */ 18 #endif ...... 31 #ifdef CONFIG_BOOTARGS 32 "bootargs=" CONFIG_BOOTARGS "\0" 33 #endif 34 #ifdef CONFIG_BOOTCOMMAND 35 "bootcmd=" CONFIG_BOOTCOMMAND "\0" 36 #endif ...... 110 };结构体变量environment就是用来保存默认环境变量的,environment 是个 env_t 类型的变量:
156 typedef struct environment_s { 157 uint32_t crc; /* CRC32 over data bytes */ 158 #ifdef CONFIG_SYS_REDUNDAND_ENVIRONMENT 159 unsigned char flags; /* active/obsolete flags */ 160 #endif 161 unsigned char data[ENV_SIZE]; /* Environment data */ 162 } env_tcrc为 CRC值,flags是标志位,data数组就是环境变量值。
在 mx6ull_alientek_emmc.h 文件中通过设置宏CONFIG_BOOTCOMMAND和CONFIG_BOOTARGS 来设置 bootcmd 和 bootargs 的默认值。
bootcmd 保存着 uboot 默认命令, uboot 倒计时结束以后就会执行 bootcmd 中的命令,比如读取 EMMC 或者 NAND Flash 中的 Linux 内核镜像文件和设备树文件到 DRAM 中,然后启动 Linux 内核。
命令boot等价于run bootcmdCONFIG_BOOTCOMMAND宏定义位于mx6ull_alientek_emmc.h 文件中,
#define CONFIG_BOOTCOMMAND \ "run findfdt;" \ "mmc dev ${mmcdev};" \ "mmc dev ${mmcdev}; if mmc rescan; then " \ "if run loadbootscript; then " \ "run bootscript; " \ "else " \ "if run loadimage; then " \ "run mmcboot; " \ "else run netboot; " \ "fi; " \ "fi; " \ "else run netboot; fi"uboot命令的运行使用了类似 shell 脚本语言的方式来编写的,应该就是通过某种方式把字符串翻译成boot命令。
run findfdt 使用的是 uboot 的 run 命令来运行 findfdt, findfdt 是 NXP 自行添加的环境变量,用来查找开发板对应的设备树文件(.dtb)。 run findfdt 的结果就是设置 fdt_file 为 imx6ull-14x14-evk.dtb。补充说明
"findfdt="\ "if test $fdt_file = undefined; then " \ "if test $board_name = EVK && test $board_rev = 9X9; then " \ "setenv fdt_file imx6ull-9x9-evk.dtb; fi; " \ "if test $board_name = EVK && test $board_rev = 14X14; then " \ "setenv fdt_file imx6ull-14x14-evk.dtb; fi; " \ "if test $fdt_file = undefined; then " \ "echo WARNING: Could not determine dtb to use; fi; " \ "fi;\0" \其中fdt_file 是否为 undefined,如果 fdt_file 为 undefined 的话那就要根据板子信息得出所需的.dtb 文件名。此时 fdt_file 为 undefined,所以根据 board_name 和board_rev 来判断实际所需的.dtb 文件, 如果 board_name 为 EVK 并且 board_rev=9x9 的话 fdt_file就为 imx6ull-9x9-evk.dtb。 如果 board_name 为 EVK 并且 board_rev=14x14 的话 fdt_file 就设置为 imx6ull-14x14-evk.dtb。
mmc dev ${mmcdev} 用于切换 mmc 设备, mmcdev 为 1,因此这行代码就是: mmc dev 1,也就是切换到 EMMC 上。
mmc dev ${mmcdev} 切换到 EMMC 上
mmc rescan 扫描看有没有 SD 卡或者 EMMC 存在,如果没有的话就执行 run netboot
netboot 是一个自定义的环境变量,这个变量是从网络启动 Linux 的。如果 mmc 设备存在的话就从mmc 设备启动。 这部分判断是从哪个设备启动系统。
loadbootscript 环境变量 此环境变量:loadbootscript=fatload mmc $ {mmcdev}: $ {mmcpart} $ {loadaddr} $ {script};最终就是loadbootscript=fatload mmc 1:1 0x80800000 boot.scr;就是从 mmc1 的分区 1 中读取文件 boot.src 到 DRAM 的 0X80800000 处。但是 mmc1 的分区 1 中没有 boot.src 这个文件,可以使用命令“ls mmc 1:1”查看一下 mmc1 分区1 中的所有文件,看看有没有 boot.src 这个文件。
如果加载 boot.src 文件成功的话就运行 bootscript 环境变量, bootscript 的内容如下:bootscript=echo Running bootscript from mmc …; source ,因为 boot.src 文件不存在,所以 bootscript 也就不会运行。
如果 loadbootscript 没有找到 boot.src 的话就运行环境变量 loadimage,环境变量:loadimage=fatload mmc $ {mmcdev}: $ {mmcpart} ${loadaddr} $ {image}loadimage 内容如下:loadimage=fatload mmc 1:1 0x80800000 zImage可以看出 loadimage 就是从 mmc1 的分区中读取 zImage 到内存的 0X80800000 处,而 mmc1的分区 1 中存在 zImage。
加载 linux 镜像文件 zImage 成功以后就运行环境变量 mmcboot,否则的话运行netboot 环境变量。 mmcboot 环境变量如下:
第 154 行, 输出信息“Booting from mmc …”。
第 155 行, 运行环境变量 mmcargs, mmcargs 用来设置 bootargs,后面分析 bootargs 的时候再学习。
第156行, 判断boot_fdt是否为yes或者try,根据uboot输出的环境变量信息可知boot_fdt=try。因此会执行 157 行的语句。
第 157 行, 运行环境变量 loadfdt,环境变量 loadfdt ,loadfdt=fatload mmc $ {mmcdev}: ${mmcpart} ${fdt_addr} ${fdt_file},展开就是loadfdt=fatload mmc 1:1 0x83000000 imx6ull-14x14-evk.dtb。
第 158 行,如果读取.dtb 文件成功的话那就调用命令 bootz 启动 linux,展开就是bootz 0x80800000 - 0x83000000 (注意‘-’前后要有空格)。
简化版本
(NXP 官方将 CONFIG_BOOTCOMMAND 写的这么复杂只有一个目的:为了兼容多个板子,所以写了个很复杂的脚本。) 经过分析,浓缩出来的仅仅是 4 行精华:
理解了原子讲解的uboot启动内核流程,仿照这种格式,编写自己的环境变量,nfs启动内核。
mmc dev 1 //切换到 EMMC fatload mmc 1:1 0x80800000 zImage //读取 zImage 到 0x80800000 处 fatload mmc 1:1 0x83000000 imx6ull-alientek-emmc.dtb //读取设备树到 0x83000000 处 bootz 0x80800000 - 0x83000000 //启动 Linux 即,类似shell命令,可以简化如下,应该可以改: #define CONFIG_BOOTCOMMAND \ "mmc dev 1;" \ "fatload mmc 1:1 0x80800000 zImage;" \ "fatload mmc 1:1 0x83000000 imx6ull-alientek-emmc.dtb;" \ "bootz 0x80800000 - 0x83000000;" 等价于,设置大型环境变量: setenv bootcmd 'mmc dev 1; fatload mmc 1:1 80800000 zImage; fatload mmc 1:1 83000000 imx6ullalientek-emmc.dtb; bootz 80800000 - 83000000;'总结:以上写这么复杂,实际就是run运行自定义环境变量,环境变量里带有更多详细的命令,命令再run运行自定义环境变量,如此反复套娃,哈哈哈!!!
bootargs 保存着 uboot 传递给 Linux 内核的参数,bootargs 环境变量是由 mmcargs 设置的:
mmcargs=setenv bootargs console=${console},${baudrate} root=${mmcroot} 展开后: mmcargs=setenv bootargs console= ttymxc0, 115200 root= /dev/mmcblk1p2 rootwait rwbootargs 就是设置了很多的参数的值,这些参数 Linux 内核会使用到,常用的参数有:
1、 console console 用来设置 linux 终端(或者叫控制台),也就是通过什么设备来和 Linux 进行交互,(串口 or LCD 屏幕)。
LCD 屏幕:console=tty1串口:console=ttymxc0,115200常用串口, linux启动以后 I.MX6ULL 的串口 1 在 linux 下的设备文件就是/dev/ttymxc0,console=ttymxc0,115200 综合起来就是设置 ttymxc0(也就是串口 1)作为 Linux 的终端,并且串口波特率设置为 115200。
2、 root root 用来设置根文件系统的位置, root=/dev/mmcblk1p2 用于指明根文件系统存放在mmcblk1 设备的分区 2 中。
EMMC 版本的核心板启动 linux 以后: /dev/mmcblkx(x=0 ~ n)表示 mmc 设备, /dev/mmcblkxpy(x=0~ n,y=1~ n)表示 mmc 设备x 的分区 y。 x=0,SD卡;x=1,EMMC。y=0,1,2,表示3个分区。
rootwait 表示等待 mmc 设备初始化完成以后再挂载,否则的话mmc 设备还没初始化完成就挂载根文件系统会出错的。 rw 表示根文件系统是可以读写的,不加rw 的话可能无法在根文件系统中进行写操作,只能进行读操作。
3、 rootfstype 一般配置 root 一起使用, rootfstype 参数用于指定根文件系统类型,常用类型:ext 格式(默认)、yaffs、 jffs 或 ubifs。
新建一个名为 mx6ull_alientek_emmc.sh 的 shell 脚本(清除、配置文件、编译):
#!/bin/bash make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- distclean make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- mx6ull_alientek_emmc_defconfig make V=1 ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -j16给予 mx6ll_alientek_emmc.sh 可执行权限777,然后运行脚本来完成编译。
之后验证一下mx6ull_alientek_emmc.h 这个头文件有没有被引用,如果有很多文件都引用了mx6ull_alientek_emmc.h这个头文件,那就说明新板子添加成功。
grep -nR "mx6ull_alientek_emmc.h"需要 ncurses 库支持
sudo apt-get install build-essential sudo apt-get install libncurses5-dev打开界面
make mx6ull_alientek_emmc_defconfig make menuconfig前边儿已经学了很多了,这里直接上命令操作了。
从网络启动 linux 系统的唯一目的就是**为了方便调试!**不管是为了调试 linux 系统还是 linux 下的驱动。根据之前学过的网络命令,我们可以通过 nfs 或者 tftp(常用) 从 Ubuntu 中下载 zImage 和设备树文件到DRAM中,再启动内核,根文件系统的话也可以通过 nfs 挂载。
setenv bootargs 'console=ttymxc0,115200 root=/dev/mmcblk1p2 rootwait rw' setenv bootcmd 'tftp 80800000 zImage; tftp 83000000 imx6ull-alientek-emmc.dtb; bootz 80800000 - 83000000' saveenvuboot 移植到此结束,简单总结一下 uboot 移植的过程: ①、不管是购买的开发板还是自己做的开发板,基本都是参考半导体厂商的 demo 板,而半导体厂商会在他们自己的开发板上移植好 uboot、 linux kernel 和 rootfs 等,最终制作好 BSP包提供给用户。我们可以在官方提供的 BSP 包的基础上添加我们的板子,也就是俗称的移植。 ②、我们购买的开发板或者自己做的板子一般都不会原封不动的照抄半导体厂商的 demo板,都会根据实际的情况来做修改,既然有修改就必然涉及到 uboot 下驱动的移植。 ③、一般 uboot 中需要解决串口、 NAND、 EMMC 或 SD 卡、网络和 LCD 驱动,因为 uboot的主要目的就是启动 Linux 内核,所以不需要考虑太多的外设驱动。 ④、在 uboot 中添加自己的板子信息,根据自己板子的实际情况来修改 uboot 中的驱动。
uboot相关的学习就暂时到此为止了,最重要的一点,uboot的终极目标就是启动linux内核,之后它的任务就完成了,我们不必深究源码,只需要理解启动流程、源码框架、驱动相关配置、移植相关的板子信息,接下来就要开始linux内核学习了,这才是嵌入式linux开发的核心技能点。
修改自己板子的:
默认配置文件设备树文件网络驱动注意:Makefile文件中换行符“\”后边儿别随便加空格。
编译内核为镜像文件:
vmlinux:编译最初生成的文件ELF 格式。Image :vmlinux取消掉一些信息的二进制数据。zImage:Image经过 gzip 压缩后的文件(成品)。uImage:为老版本,zImage 前面加了一个长度为 64 字节的“头”。需要安装软件包才能正常编译内核。
sudo apt-get install lzop编译命令:
#!/bin/sh make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- distclean make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihfimx_alientek_emmc_defconfig //make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- menuconfig make V=1 ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- all -j16zImage在/arch/arm下查找。
修改完设备树之后,根目录执行以下命令即可查找编译出的.dtb文件。
make dtbs