2)摘自《正点原子I.MX6U嵌入式Linux驱动开拓指南》关注官方微旗子暗记"大众号,获取更多资料:正点原子
33.2.7 网络驱动修正
1、I.MX6U-ALPHA开拓板网络简介

I.MX6UL/ULL内部有个以太网MAC外设,也便是ENET,须要外接一个PHY芯片来实现网络通信功能,也便是内部MAC+外部PHY芯片的方案。大家可能听过DM9000这个网络芯片,在一些没有内部MAC的CPU中,比如三星的2440,4412等,就会采取DM9000来实现联网功能。DM9000供应了一个类似SRAM的访问接口,主控CPU通过这个接口即可与DM9000进行通信,DM9000便是一个MAC+PHY芯片。这个方案就相称于外部MAC+外部PHY,那么I.MX6U这样的内部MAC+PHY芯片与DM9000方案比有什么上风吗?那上风大了去了!
首先便是通信效率和速率,一样平常CPU内部的MAC是带有一个专用DMA的,专门用于处理网络数据包,采取SRAM来读写DM9000的速率是压根就没法和内部MAC+外部PHY芯片的速率比。采取外部DM9000完备是无奈之举,谁让2440,4412这些芯片内部没有以太网外设呢,现在又想用有线网络,没有办法只能找个DM9000的方案。从这里也可以看出,三星的2440、4412这些芯片设计之初就不是给工业产品用的,他们是给消费类电子利用的,比如手机、平板等,手机或平板要上网,可以通过WIFI或者4G,我是没有见过哪个手机或者平板上网是要接根网线的。正点原子的I.MX6U-ALPHA开拓板也可以通过WIFI或者4G上网,这个是后话了。
I.MX6UL/ULL有两个网络接口ENET1和ENET2,正点原子的I.MX6U-ALPHA开拓板供应了这两个网络接口,个中ENET1和ENET2都利用LAN8720A作为PHY芯片。NXP官方的I.MX6ULL EVK开拓板利用KSZ8081这颗PHY芯片,LAN8720A比较KSZ8081具有体积小、外围器件少、价格便宜等优点。直策应用KSZ8081固然可以,但是我们在实际的产品中不一定会利用KSZ8081,有时候为了降落本钱会选择其他的PHY芯片,这个时候就有个问题:换了PHY芯片往后网络驱动怎么办?为此,正点原子的I.MX6U-ALPHA开拓板将ENET1和ENET2的PHY换为了LAN8720A,这样就可以给大家讲解改换PHY芯片往后如何调度网络驱动,使网络事情正常。
I.MX6U-ALPHA开拓板ENET1的网络事理图如图33.2.7.1所示:
图33.2.7.1 ENET1事理图
ENET1的网络PHY芯片为LAN8720A,通过RMII接口与I.MX6ULL相连,正点原子I.MX6U-ALPHA开拓板的ENET1引脚与NXP官方的I.MX6ULL EVK开拓板基本一样,唯独复位引脚不同。从图33.2.7.1可以看出,正点原子I.MX6U-ALPHA开拓板的ENET1复位引脚ENET1_RST接到了I.M6ULL的SNVS_TAMPER7这个引脚上。
LAN8720A内部是有寄存器的,I.MX6ULL会读取LAN8720内部寄存器来判断当前的物理链接状态、连接速率(10M还是100M)和双工状态(半双工还是全双工)。I.MX6ULL通过MDIO接口来读取PHY芯片的内部寄存器,MDIO接口有两个引脚,ENET_MDC和ENET_MDIO, ENET_MDC供应时钟,ENET_MDIO进行数据传输。一个MIDO接口可以管理32个PHY芯片,同一个MDIO接口下的这些PHY利用不同的器件地址来做区分,MIDO接口通过不同的器件地址即可访问到相应的PHY芯片。I.MX6U-ALPHA开拓板ENET1上连接的LAN8720A器件地址为0X0,所示我们要修正ENET1网络驱动的话重点就三点:
①、ENET1复位引脚初始化。
②、LAN8720A的器件ID。
③、LAN8720驱动
再来看一下ENET2的事理图,如图33.2.7.2所示:
图33.2.7.2 ENET2事理图
关于ENET2网络驱动的修正也把稳一下三点:
①、ENET2的复位引脚,从图33.2.7.2可以看出,ENET2的复位引脚ENET2_RST接到了I.MX6ULL的SNVS_TAMPER8上。
②、ENET2所利用的PHY芯片器件地址,从图33.2.7.2可以看出,PHY器件地址为0X1。
③、LAN8720驱动,ENET1和ENET2都利用的LAN8720,以是驱动肯定是一样的。
2、网络PHY地址修正
首先修正uboot中的ENET1和ENET2的PHY地址和驱动,打开mx6ull_alientek_emmc.h这个文件,找到如下代码:
示例代码33.2.7.1 网络默认ID配置参数
325 #ifdef CONFIG_CMD_NET
326 #define CONFIG_CMD_PING
327 #define CONFIG_CMD_DHCP
328 #define CONFIG_CMD_MII
329 #define CONFIG_FEC_MXC
330 #define CONFIG_MII
331 #define CONFIG_FEC_ENET_DEV 1
332
333 #if(CONFIG_FEC_ENET_DEV ==0)
334 #define IMX_FEC_BASE ENET_BASE_ADDR
335 #define CONFIG_FEC_MXC_PHYADDR 0x2
336 #define CONFIG_FEC_XCV_TYPE RMII
337 #elif (CONFIG_FEC_ENET_DEV ==1)
338 #define IMX_FEC_BASE ENET2_BASE_ADDR
339 #define CONFIG_FEC_MXC_PHYADDR 0x1
340 #define CONFIG_FEC_XCV_TYPE RMII
341 #endif
342 #define CONFIG_ETHPRIME "FEC"
343
344 #define CONFIG_PHYLIB
345 #define CONFIG_PHY_MICREL
346 #endif
第331行的宏CONFIG_FEC_ENET_DEV用于选择利用哪个网口,默认为1,也便是选择ENET2。第335行为ENET1的PHY地址,默认是0X2,第339行为ENET2的PHY地址,默认为0x1。根据前面的剖析可知,正点原子的I.MX6U-ALPHA开拓板ENET1的PHY地址为0X0,ENET2的PHY地址为0X1,以是须要将第335行的宏CONFIG_FEC_MXC_PHYADDR改为0x0。
第345行定了一个宏CONFIG_PHY_MICREL,此宏用于使能uboot中Micrel公司的PHY驱动,KSZ8081这颗PHY芯片便是Micrel公司生产的,不过Micrel已经被Microchip收购了。如果要利用LAN8720A,那么就得将CONFIG_PHY_MICREL改为CONFIG_PHY_SMSC,也便是使能uboot中的SMSC公司中的PHY驱动,由于LAN8720A便是SMSC公司生产的。以是示例代码33.2.7.1有三处要修正:
①、修正ENET1网络PHY的地址。
②、修正ENET2网络PHY的地址。
③、使能SMSC公司的PHY驱动。
修正后的网络PHY地址参数如下所示:
示例代码33.2.7.2 网络PHY地址配置参数
325 #ifdef CONFIG_CMD_NET
326 #define CONFIG_CMD_PING
327 #define CONFIG_CMD_DHCP
328 #define CONFIG_CMD_MII
329 #define CONFIG_FEC_MXC
330 #define CONFIG_MII
331 #define CONFIG_FEC_ENET_DEV 1
332
333 #if(CONFIG_FEC_ENET_DEV ==0)
334 #define IMX_FEC_BASE ENET_BASE_ADDR
335 #define CONFIG_FEC_MXC_PHYADDR 0x0
336 #define CONFIG_FEC_XCV_TYPE RMII
337 #elif (CONFIG_FEC_ENET_DEV ==1)
338 #define IMX_FEC_BASE ENET2_BASE_ADDR
339 #define CONFIG_FEC_MXC_PHYADDR 0x1
340 #define CONFIG_FEC_XCV_TYPE RMII
341 #endif
342 #define CONFIG_ETHPRIME "FEC"
343
344 #define CONFIG_PHYLIB
345 #define CONFIG_PHY_SMSC
346 #endif
3、删除uboot中74LV595的驱动代码
uboot中网络PHY芯片地址修正完成往后便是网络复位引脚的驱动修正了,打开mx6ull_alientek_emmc.c,找到如下代码:
示例代码33.2.7.3 74LV595引脚
#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)
示例代码33.2.7.3中以IOX开头的宏定义是74LV595的干系GPIO,由于NXP官方I.MX6ULL EVK开拓板利用74LV595来扩展IO,两个网络的复位引脚便是由74LV595来掌握的。正点原子的I.MX6U-ALPHA开拓板并没有利用74LV595,因此我们将示例代码33.2.6.6中的代码删除掉,更换为如下所示代码:
示例代码33.2.7.4 修正后的网络引脚
#define ENET1_RESET IMX_GPIO_NR(5, 7)
#define ENET2_RESET IMX_GPIO_NR(5, 8)
ENET1的复位引脚连接到SNVS_TAMPER7上,对应GPIO5_IO07,ENET2的复位引脚连接到SNVS_TAMPER8上,对应GPIO5_IO08。
连续在mx6ull_alientek_emmc.c中找到如下代码:
示例代码33.2.7.5 74LV595引脚配置
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),
};
同理,示例代码33.2.7.5是74LV595的IO配置参数构造体,将其删除掉。连续在mx6ull_alientek_emmc.c中找到函数iox74lv_init,如下所示:
示例代码33.2.7.6 74LV595初始化函数
staticvoid iox74lv_init(void)
{
int i;
gpio_direction_output(IOX_OE,0);
for(i =7; i >=0; i--){
gpio_direction_output(IOX_SHCP,0);
gpio_direction_output(IOX_SDI, seq[qn_output[i]][0]);
udelay(500);
gpio_direction_output(IOX_SHCP,1);
udelay(500);
}
......
/
shift register will be output to pins
/
gpio_direction_output(IOX_STCP,1);
};
void iox74lv_set(int index)
{
int i;
for(i =7; i >=0; i--){
gpio_direction_output(IOX_SHCP,0);
if(i == index)
gpio_direction_output(IOX_SDI, seq[qn_output[i]][0]);
else
gpio_direction_output(IOX_SDI, seq[qn_output[i]][1]);
udelay(500);
gpio_direction_output(IOX_SHCP,1);
udelay(500);
}
......
/
shift register will be output to pins
/
gpio_direction_output(IOX_STCP,1);
};
iox74lv_init函数是74LV595的初始化函数,iox74lv_set函数用于掌握74LV595的IO输出电平,将这两个函数全部删除掉!
在mx6ull_alientek_emmc.c中找到board_init函数,此函数是板子初始化函数,会被board_init_r调用,board_init函数内容如下:
示例代码33.2.7.7 board_init函数
int board_init(void)
{
......
imx_iomux_v3_setup_multiple_pads(iox_pads, ARRAY_SIZE(iox_pads));
iox74lv_init();
......
return0;
}
board_init会调用imx_iomux_v3_setup_multiple_pads和iox74lv_init这两个函数来初始化74lv595的GPIO,将这两行删除掉。至此,mx6ull_alientek_emmc.c中关于74LV595芯片的驱动代码都删除掉了,接下来便是添加I.MX6U-ALPHA开拓板两个网络复位引脚了。
4、添加I.MX6U-ALPHA开拓板网络复位引脚驱动
在mx6ull_alientek_emmc.c中找到如下所示代码:
示例代码33.2.7.8 默认网络IO构造体数组
640static iomux_v3_cfg_t const fec1_pads[]={
641 MX6_PAD_GPIO1_IO06__ENET1_MDIO | MUX_PAD_CTRL(MDIO_PAD_CTRL),
642 MX6_PAD_GPIO1_IO07__ENET1_MDC | MUX_PAD_CTRL(ENET_PAD_CTRL),
......
649 MX6_PAD_ENET1_RX_ER__ENET1_RX_ER | MUX_PAD_CTRL(ENET_PAD_CTRL),
650 MX6_PAD_ENET1_RX_EN__ENET1_RX_EN | MUX_PAD_CTRL(ENET_PAD_CTRL),
651};
652
653static iomux_v3_cfg_t const fec2_pads[]={
654 MX6_PAD_GPIO1_IO06__ENET2_MDIO | MUX_PAD_CTRL(MDIO_PAD_CTRL),
655 MX6_PAD_GPIO1_IO07__ENET2_MDC | MUX_PAD_CTRL(ENET_PAD_CTRL),
......
664 MX6_PAD_ENET2_RX_EN__ENET2_RX_EN | MUX_PAD_CTRL(ENET_PAD_CTRL),
665 MX6_PAD_ENET2_RX_ER__ENET2_RX_ER | MUX_PAD_CTRL(ENET_PAD_CTRL),
666};
构造体数组fec1_pads和fec2_pads是ENET1和ENET2这两个网口的IO配置参数,在这两个数组中添加两个网口的复位IO配置参数,完成往后如下所示:
示例代码33.2.7.9 添加网络复位IO后的构造体数组
640static iomux_v3_cfg_t const fec1_pads[]={
641 MX6_PAD_GPIO1_IO06__ENET1_MDIO | MUX_PAD_CTRL(MDIO_PAD_CTRL),
642 MX6_PAD_GPIO1_IO07__ENET1_MDC | MUX_PAD_CTRL(ENET_PAD_CTRL),
......
649 MX6_PAD_ENET1_RX_ER__ENET1_RX_ER | MUX_PAD_CTRL(ENET_PAD_CTRL),
650 MX6_PAD_ENET1_RX_EN__ENET1_RX_EN | MUX_PAD_CTRL(ENET_PAD_CTRL),
651 MX6_PAD_SNVS_TAMPER7__GPIO5_IO07 | MUX_PAD_CTRL(NO_PAD_CTRL),
652};
653
654static iomux_v3_cfg_t const fec2_pads[]={
655 MX6_PAD_GPIO1_IO06__ENET2_MDIO | MUX_PAD_CTRL(MDIO_PAD_CTRL),
656 MX6_PAD_GPIO1_IO07__ENET2_MDC | MUX_PAD_CTRL(ENET_PAD_CTRL),
......
665 MX6_PAD_ENET2_RX_EN__ENET2_RX_EN | MUX_PAD_CTRL(ENET_PAD_CTRL),
666 MX6_PAD_ENET2_RX_ER__ENET2_RX_ER | MUX_PAD_CTRL(ENET_PAD_CTRL),
667 MX6_PAD_SNVS_TAMPER8__GPIO5_IO08 | MUX_PAD_CTRL(NO_PAD_CTRL),
668};
示例代码33.2.7.9中,第651行和667行分别是ENET1和ENET2的复位IO配置参数。连续在文件mx6ull_alientek_emmc.c中找到函数setup_iomux_fec,此函数默认代码如下:
示例代码33.2.7.10 setup_iomux_fec函数默认代码
668staticvoid setup_iomux_fec(int fec_id)
669{
670if(fec_id ==0)
671 imx_iomux_v3_setup_multiple_pads(fec1_pads,
672 ARRAY_SIZE(fec1_pads));
673else
674 imx_iomux_v3_setup_multiple_pads(fec2_pads,
675 ARRAY_SIZE(fec2_pads));
676}
函数setup_iomux_fec便是根据fec1_pads和fec2_pads这两个网络IO配置数组来初始化I.MX6ULL的网络IO。我们须要在个中添加网络复位IO的初始化代码,并且复位一下PHY芯片,修正后的setup_iomux_fec函数如下:
示例代码33.2.7.11 修正后的setup_iomux_fec函数
668staticvoid setup_iomux_fec(int fec_id)
669{
670if(fec_id ==0)
671{
672
673 imx_iomux_v3_setup_multiple_pads(fec1_pads,
674 ARRAY_SIZE(fec1_pads));
675
676 gpio_direction_output(ENET1_RESET,1);
677 gpio_set_value(ENET1_RESET,0);
678 mdelay(20);
679 gpio_set_value(ENET1_RESET,1);
680}
681else
682{
683 imx_iomux_v3_setup_multiple_pads(fec2_pads,
684 ARRAY_SIZE(fec2_pads));
685 gpio_direction_output(ENET2_RESET,1);
686 gpio_set_value(ENET2_RESET,0);
687 mdelay(20);
688 gpio_set_value(ENET2_RESET,1);
689}
690}
示例代码33.2.7.11中第676行~679行和第685行~688行分别对应ENET1和ENET2的复位IO初始化,将这两个IO设置为输出并且硬件复位一下LAN8720A,这个硬件复位很主要!
否则可能导致uboot无法识别LAN8720A。
5、修正drivers/net/phy/phy.c文件中的函数genphy_update_link
大功基本上告成,还差末了一步,uboot中的LAN8720A驱动有点问题,打开文件drivers/net/phy/phy.c,找到函数genphy_update_link,这是个通用PHY驱动函数,此函数用于更新PHY的连接状态和速率。利用LAN8720A的时候须要在此函数中添加一些代码,修正后的函数genphy_update_link如下所示:
示例代码33.2.7.12 修正后的genphy_update_link函数
221int genphy_update_link(struct phy_device phydev)
222{
223unsignedint mii_reg;
224
225 #ifdef CONFIG_PHY_SMSC
226staticint lan8720_flag =0;
227int bmcr_reg =0;
228if(lan8720_flag ==0){
229 bmcr_reg = phy_read(phydev, MDIO_DEVAD_NONE, MII_BMCR);
230 phy_write(phydev, MDIO_DEVAD_NONE, MII_BMCR, BMCR_RESET);
231while(phy_read(phydev, MDIO_DEVAD_NONE, MII_BMCR)& 0X8000){
232 udelay(100);
233}
234 phy_write(phydev, MDIO_DEVAD_NONE, MII_BMCR, bmcr_reg);
235 lan8720_flag =1;
236}
237 #endif
238
239/
240 Wait if the link is up, and autonegotiation is in progress
241 (ie - we're capable and it's not done)
242 /
243 mii_reg = phy_read(phydev, MDIO_DEVAD_NONE, MII_BMSR);
......
291
292return0;
293}
225行~237行便是新添加的代码,为条件编译代码段,只有利用SMSC公司的PHY这段代码才会实行(目前只测试了LAN8720A,SMSC公司其他的芯片还未测试)。第229行读取LAN8720A的BMCR寄存器(寄存器地址为0),此寄存器为LAN8720A的配置寄存器,这里先读取此寄存器的默认值并保存起来。230行向寄存器BMCR寄存器写入BMCR_RESET(值为0X8000),由于BMCR的bit15是软件复位掌握位,因此230行便是软件复位LAN8720A,复位完成往后此位会自动清零。第231~233行等待LAN8720A软件复位完成,也便是判断BMCR的bit15位是否为1,为1的话表示还没有复位完成。第234行重新向BMCR寄存器写入以前的值,也便是229行读出的那个值。
至此网络的复位引脚驱动修正完成,重新编译uboot,然后将u-boot.bin烧写到SD卡中并启动,uboot启动信息如图33.2.7.3所示:
图33.2.7.3 uboot启动信息
从图33.2.6.4中可以看到"Net:FEC1"这一行,提示当前利用的FEC1这个网口,也便是ENET2。在uboot中利用网络之前要先设置几个环境变量,命令如下:
setenv ipaddr 192.168.1.55 //开拓板IP地址
setenv ethaddr 00:04:9f:04:d2:35 //开拓板网卡MAC地址
setenv gatewayip 192.168.1.1 //开拓板默认网关
setenv netmask 255.255.255.0 //开拓板子网掩码
setenv serverip 192.168.1.250 //做事器地址,也便是Ubuntu地址
saveenv //保存环境变量
设置好环境变量往后就可以在uboot中利用网络了,用网线将I.MX6U-ALPHA上的ENET2与电脑或者路由器连接起来,担保开拓板和电脑在同一个网段内,通过ping命令来测试一下网络连接,命令如下:
ping 192.168.1.250
结果如图33.2.7.4所示:
图33.2.7.4 ping命令测试
从图33.2.7.4可以看出,有"host 192.168.1.250 is alive"这句,解释ping主机成功,解释ENET2网络事情正常。再来测试一下ENET1的网络是否正常事情,打开mx6ull_alientek_emmc.h,将CONFIG_FEC_ENET_DEV改为0,然后重新编译一下uboot并烧写到SD卡中重启。重启开拓板,uboot输出信息如图33.2.7.5所示:
图33.2.7.5 uboot启动信息
从图33.2.7.5可以出,有"Net:FEC0"这一行,解释当前利用的FEC0这个网卡,也便是ENET1,同样的ping一下主机,结果如图33.2.7.5所示:
图33.2.7.6 ping命令测试
从图33.2.7.6可以看出,ping主机也成功,解释ENET1网络也事情正常,至此,I.MX6U-ALPHA开拓板的两个网络都事情正常了,建议大家将ENET2设置为uboot的默认网卡!
也便是将宏CONFIG_FEC_ENET_DEV设置为1。
在uboot启动信息中会有"Board: MX6ULL 14x14 EVK"这一句,也便是说板子名字为"MX6ULL 14x14 EVK",要将其改为我们所利用的板子名字,比如"MX6ULL ALIENTEK EMMC"或者"MX6ULL ALIENTEK NAND"。打开文件mx6ull_alientek_emmc.c,找到函数checkboard,将其改为如下所示内容:
示例代码33.2.8.1 修正后的checkboard函数
int checkboard(void)
{
if(is_mx6ull_9x9_evk())
puts("Board: MX6ULL 9x9 EVK\n");
else
puts("Board: MX6ULL ALIENTEK EMMC\n");
return0;
}
修正完成往后重新编译uboot并烧写到SD卡中验证,uboot启动信息如图33.2.8.1所示:
图33.2.8.1 uboot启动信息
从图33.2.8.1可以看出,Board变成了"MX6ULL ALIENTEK EMMC"。至此uboot的驱动部分就修正完成了,uboot移植也完成了,uboot的终极目的便是启动Linux内核,以是须要通过启动Linux内核来判断uboot移植是否成功。在启动Linux内核之前我们先来学习两个主要的环境变量bootcmd和bootargs。
33.3 bootcmd和bootargs环境变量uboot中有两个非常主要的环境变量bootcmd和bootargs,接下来看一下这两个环境变量。bootcmd和bootagrs是采取类似shell脚本措辞编写的,里面有很多的变量引用,这些变量实在都是环境变量,有很多是NXP自己定义的。文件mx6ull_alientek_emmc.h中的宏CONFIG_EXTRA_ENV_SETTINGS保存着这些环境变量的默认值,内容如下:
示例代码33.3.1.1 宏CONFIG_EXTRA_ENV_SETTINGS默认值
113 #if defined(CONFIG_SYS_BOOT_NAND)
114 #define CONFIG_EXTRA_ENV_SETTINGS \
115 CONFIG_MFG_ENV_SETTINGS \
116"panel=TFT43AB\0" \
117"fdt_addr=0x83000000\0" \
118"fdt_high=0xffffffff\0" \
......
126"bootz ${loadaddr} - ${fdt_addr}\0"
127
128 #else
129 #define CONFIG_EXTRA_ENV_SETTINGS \
130 CONFIG_MFG_ENV_SETTINGS \
131"script=boot.scr\0" \
132"image=zImage\0" \
133"console=ttymxc0\0" \
134"fdt_high=0xffffffff\0" \
135"initrd_high=0xffffffff\0" \
136"fdt_file=undefined\0" \
......
194"findfdt="\
195"if test $fdt_file = undefined; then " \
196"if test $board_name = EVK && test $board_rev = 9X9; then " \
197"setenv fdt_file imx6ull-9x9-evk.dtb; fi; " \
198"if test $board_name = EVK && test $board_rev = 14X14; then " \
199"setenv fdt_file imx6ull-14x14-evk.dtb; fi; " \
200"if test $fdt_file = undefined; then " \
201"echo WARNING: Could not determine dtb to use; fi; " \
202"fi;\0" \
宏CONFIG_EXTRA_ENV_SETTINGS是个条件编译语句,利用NAND和EMMC的时候宏CONFIG_EXTRA_ENV_SETTINGS的值是不同的。
33.3.1 环境变量bootcmdbootcmd在前面已经说了很多次了,bootcmd保存着uboot默认命令,uboot倒计时结束往后就会实行bootcmd中的命令。这些命令一样平常都是用来启动Linux内核的,比如读取EMMC或者NAND Flash中的Linux内核镜像文件和设备树文件到DRAM中,然后启动Linux内核。可以在uboot启动往后进入命令行设置bootcmd环境变量的值。如果EMMC或者NAND中没有保存bootcmd的值,那么uboot就会利用默认的值,板子第一次运行uboot的时候都会利用默认值来设置bootcmd环境变量。打开文件include/env_default.h,在此文件中有如下所示内容:
示例代码33.3.1.1 默认环境变量
14 env_t environment __PPCENV__ ={
15 ENV_CRC,/ CRC Sum /
16 #ifdef CONFIG_SYS_REDUNDAND_ENVIRONMENT
171,/ Flags: valid /
18 #endif
19{
20 #elif defined(DEFAULT_ENV_INSTANCE_STATIC)
21staticchar default_environment[]={
22 #else
23const uchar default_environment[]={
24 #endif
25 #ifdef CONFIG_ENV_CALLBACK_LIST_DEFAULT
26 ENV_CALLBACK_VAR "=" CONFIG_ENV_CALLBACK_LIST_DEFAULT "\0"
27 #endif
28 #ifdef CONFIG_ENV_FLAGS_LIST_DEFAULT
29 ENV_FLAGS_VAR "=" CONFIG_ENV_FLAGS_LIST_DEFAULT "\0"
30 #endif
31 #ifdef CONFIG_BOOTARGS
32"bootargs=" CONFIG_BOOTARGS "\0"
33 #endif
34 #ifdef CONFIG_BOOTCOMMAND
35"bootcmd=" CONFIG_BOOTCOMMAND "\0"
36 #endif
37 #ifdef CONFIG_RAMBOOTCOMMAND
38"ramboot=" CONFIG_RAMBOOTCOMMAND "\0"
39 #endif
40 #ifdef CONFIG_NFSBOOTCOMMAND
41"nfsboot=" CONFIG_NFSBOOTCOMMAND "\0"
42 #endif
43 #if defined(CONFIG_BOOTDELAY)&&(CONFIG_BOOTDELAY >=0)
44"bootdelay=" __stringify(CONFIG_BOOTDELAY)"\0"
45 #endif
46 #if defined(CONFIG_BAUDRATE)&&(CONFIG_BAUDRATE >=0)
47"baudrate=" __stringify(CONFIG_BAUDRATE)"\0"
48 #endif
49 #ifdef CONFIG_LOADS_ECHO
50"loads_echo=" __stringify(CONFIG_LOADS_ECHO)"\0"
51 #endif
52 #ifdef CONFIG_ETHPRIME
53"ethprime=" CONFIG_ETHPRIME "\0"
54 #endif
55 #ifdef CONFIG_IPADDR
56"ipaddr=" __stringify(CONFIG_IPADDR)"\0"
57 #endif
58 #ifdef CONFIG_SERVERIP
59"serverip=" __stringify(CONFIG_SERVERIP)"\0"
60 #endif
61 #ifdef CONFIG_SYS_AUTOLOAD
62"autoload=" CONFIG_SYS_AUTOLOAD "\0"
63 #endif
64 #ifdef CONFIG_PREBOOT
65"preboot=" CONFIG_PREBOOT "\0"
66 #endif
67 #ifdef CONFIG_ROOTPATH
68"rootpath=" CONFIG_ROOTPATH "\0"
69 #endif
70 #ifdef CONFIG_GATEWAYIP
71"gatewayip=" __stringify(CONFIG_GATEWAYIP)"\0"
72 #endif
73 #ifdef CONFIG_NETMASK
74"netmask=" __stringify(CONFIG_NETMASK)"\0"
75 #endif
76 #ifdef CONFIG_HOSTNAME
77"hostname=" __stringify(CONFIG_HOSTNAME)"\0"
78 #endif
79 #ifdef CONFIG_BOOTFILE
80"bootfile=" CONFIG_BOOTFILE "\0"
81 #endif
82 #ifdef CONFIG_LOADADDR
83"loadaddr=" __stringify(CONFIG_LOADADDR)"\0"
84 #endif
85 #ifdef CONFIG_CLOCKS_IN_MHZ
86"clocks_in_mhz=1\0"
87 #endif
88 #if defined(CONFIG_PCI_BOOTDELAY)&&(CONFIG_PCI_BOOTDELAY >0)
89"pcidelay=" __stringify(CONFIG_PCI_BOOTDELAY)"\0"
90 #endif
91 #ifdef CONFIG_ENV_VARS_UBOOT_CONFIG
92"arch=" CONFIG_SYS_ARCH "\0"
93"cpu=" CONFIG_SYS_CPU "\0"
94"board=" CONFIG_SYS_BOARD "\0"
95"board_name=" CONFIG_SYS_BOARD "\0"
96 #ifdef CONFIG_SYS_VENDOR
97"vendor=" CONFIG_SYS_VENDOR "\0"
98 #endif
99 #ifdef CONFIG_SYS_SOC
100"soc=" CONFIG_SYS_SOC "\0"
101 #endif
102 #endif
103 #ifdef CONFIG_EXTRA_ENV_SETTINGS
104 CONFIG_EXTRA_ENV_SETTINGS
105 #endif
106"\0"
107 #ifdef DEFAULT_ENV_INSTANCE_EMBEDDED
108}
109 #endif
110};
environment是个env_t类型的变量,env_t类型如下:
示例代码33.3.1.2 env_t构造体
156typedefstruct environment_s {
157uint32_t crc;/ CRC32 over data bytes /
158 #ifdef CONFIG_SYS_REDUNDAND_ENVIRONMENT
159unsignedchar flags;/ active/obsolete flags /
160 #endif
161unsignedchar data[ENV_SIZE];/ Environment data /
162} env_t
env_t构造体中的crc为CRC值,flags是标志位,data数组便是环境变量值。因此,environment便是用来保存默认环境变量的,在示例代码33.3.1.1中指定了很多环境变量的默认值,比如bootcmd的默认值便是CONFIG_BOOTCOMMAND,bootargs的默认值便是CONFIG_BOOTARGS。我们可以在mx6ull_alientek_emmc.h文件中通过设置宏CONFIG_BOOTCOMMAND来设置bootcmd的默认值,NXP官方设置的CONFIG_BOOTCOMMAND值如下:
示例代码33.3.1.3 CONFIG_BOOTCOMMAND默认值
204 #define CONFIG_BOOTCOMMAND \
205"run findfdt;" \
206"mmc dev ${mmcdev};" \
207"mmc dev ${mmcdev}; if mmc rescan; then " \
208"if run loadbootscript; then " \
209"run bootscript; " \
210"else " \
211"if run loadimage; then " \
212"run mmcboot; " \
213"else run netboot; " \
214"fi; " \
215"fi; " \
216"else run netboot; fi"
看起来很繁芜的样子!
由于uboot利用了类似shell脚本措辞的办法来编写的,我们一行一行来剖析。
第205行,runfindfdt;利用的是uboot的run命令来运行findfdt,findfdt是NXP自行添加的环境变量。findfdt是用来查找开拓板对应的设备树文件(.dtb)。IMX6ULL EVK的设备树文件为imx6ull-14x14-evk.dtb,findfdt内容如下:
"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" \
findfdt里面用到的变量有fdt_file,board_name,board_rev,这三个变量内容如下:
fdt_file=undefined,board_name=EVK,board_rev=14X14
findfdt做的事情便是判断,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。因此IMX6ULL EVK板子的设备树文件便是imx6ull-14x14-evk.dtb,
因此runfindfdt的结果便是设置fdt_file为imx6ull-14x14-evk.dtb。
第206行,mmc dev ${mmcdev}用于切换mmc设备,mmcdev为1,因此这行代码便是:mmcdev1,也便是切换到EMMC上。
第207行,先实行mmc dev ${mmcdev}切换到EMMC上,然后利用命令mmcrescan扫描看有没有SD卡或者EMMC存在,如果没有的话就直接跳到216行,实行runnetboot,netboot也是一个自定义的环境变量,这个变量是从网络启动Linux的。如果mmc设备存在的话就从mmc设备启动。
第208行,运行loadbootscript环境变量,此环境变量内容如下:
loadbootscript=fatload mmc ${mmcdev}:${mmcpart} ${loadaddr} ${script};
个中mmcdev=1,mmcpart=1,loadaddr=0x80800000,script= boot.scr,因此展开往后便是:
loadbootscript=fatload mmc 1:1 0x80800000 boot.scr;
loadbootscript便是从mmc1的分区1中读取文件boot.src到DRAM的0X80800000处。但是mmc1的分区1中没有boot.src这个文件,可以利用命令"lsmmc 1:1"查看一下mmc1分区1中的所有文件,看看有没有boot.src这个文件。
第209行,如果加载boot.src文件成功的话就运行bootscript环境变量,bootscript的内容如下:
bootscript=echo Running bootscript from mmc ...;
source
由于boot.src文件不存在,以是bootscript也就不会运行。
第211行,如果loadbootscript没有找到boot.src的话就运行环境变量loadimage,环境变量loadimage内容如下:
loadimage=fatload mmc ${mmcdev}:${mmcpart} ${loadaddr} ${image}
个中mmcdev=1,mmcpart=1,loadaddr=0x80800000,image =zImage,展开往后便是:
loadimage=fatload mmc 1:1 0x80800000 zImage
可以看出loadimage便是从mmc1的分区中读取zImage到内存的0X80800000处,而mmc1的分区1中存在zImage。
第212行,加载linux镜像文件zImage成功往后就运行环境变量mmcboot,否则的话运行netboot环境变量。mmcboot环境变量如下:
示例代码33.3.1.4 mmcboot环境变量
154"mmcboot=echo Booting from mmc ...; " \
155"run mmcargs; " \
156"if test ${boot_fdt} = yes || test ${boot_fdt} = try; then " \
157"if run loadfdt; then " \
158"bootz ${loadaddr} - ${fdt_addr}; " \
159"else " \
160"if test ${boot_fdt} = try; then " \
161"bootz; " \
162"else " \
163"echo WARN: Cannot load the DT; " \
164"fi; " \
165"fi; " \
166"else " \
167"bootz; " \
168"fi;\0" \
第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
因此loadfdt的浸染便是从mmc1的分区1中读取imx6ull-14x14-evk.dtb文件并放到0x83000000处。
第158行,如果读取.dtb文件成功的话那就调用命令bootz启动linux,调用方法如下:
bootz ${loadaddr} - ${fdt_addr};
展开便是:
bootz 0x80800000 - 0x83000000 (把稳'-'前后要有空格)
至此Linux内核启动,如此繁芜的设置便是为了从EMMC中读取zImage镜像文件和设备树文件。经由剖析,浓缩出来的仅仅是4行精华:
mmcdev 1 //切换到EMMC
fatload mmc 1:1 0x80800000 zImage //读取zImage到0x80800000处
fatload mmc 1:1 0x83000000 imx6ull-14x14-evk.dtb //读取设备树到0x83000000处
bootz 0x80800000 - 0x83000000 //启动Linux
NXP官方将CONFIG_BOOTCOMMAND写的这么繁芜只有一个目的:为了兼容多个板子,以是写了个很繁芜的脚本。当我们明确知道我们所利用的板子的时候就可以大幅简化宏CONFIG_BOOTCOMMAND的设置,比如我们要从EMMC启动,那么宏CONFIG_BOOTCOMMAND就可简化为:
#define CONFIG_BOOTCOMMAND \
"mmc dev 1;" \
"fatload mmc 1:1 0x80800000 zImage;" \
"fatload mmc 1:1 0x83000000 imx6ull-alientek-emmc.dtb;" \
"bootz 0x80800000 - 0x83000000;"
或者可以直接在uboot中设置bootcmd的值,这个值便是保存到EMMC中的,命令如下:
setenv bootcmd 'mmc dev 1; fatload mmc 1:1 80800000 zImage; fatload mmc 1:1 83000000 imx6ull-alientek-emmc.dtb; bootz 80800000 - 83000000;'
33.3.2 环境变量bootargsbootargs保存着uboot通报给Linux内核的参数,在上一小节讲解bootcmd的时候说过,bootargs环境变量是由mmcargs设置的,mmcargs环境变量如下:
mmcargs=setenv bootargs console=${console},${baudrate} root=${mmcroot}
个中console=ttymxc0,baudrate=115200,mmcroot=/dev/mmcblk1p2 rootwait rw,因此将mmcargs展开往后便是:
mmcargs=setenv bootargs console= ttymxc0, 115200 root= /dev/mmcblk1p2 rootwait rw
可以看出环境变量mmcargs便是设置bootargs的值为"console= ttymxc0, 115200 root= /dev/mmcblk1p2 rootwait rw",bootargs便是设置了很多的参数的值,这些参数Linux内核会利用到,常用的参数有:
1、console
console用来设置linux终端(或者叫掌握台),也便是通过什么设备来和Linux进行交互,是串口还是LCD屏幕?如果是串口的话该当是串口几等等。一样平常设置串口作为Linux终端,这样我们就可以在电脑上通过SecureCRT来和linux交互了。这里设置console为ttymxc0,由于linux启动往后I.MX6ULL的串口1在linux下的设备文件便是/dev/ttymxc0,在Linux下,统统皆文件。
ttymxc0后面有个",115200",这是设置串口的波特率,console=ttymxc0,115200综合起来便是设置ttymxc0(也便是串口1)作为Linux的终端,并且串口波特率设置为115200。
2、root
root用来设置根文件系统的位置,root=/dev/mmcblk1p2用于指明根文件系统存放在mmcblk1设备的分区2中。EMMC版本的核心板启动linux往后会存在/dev/mmcblk0、/dev/mmcblk1、/dev/mmcblk0p1、/dev/mmcblk0p2、/dev/mmcblk1p1和/dev/mmcblk1p2这样的文件,个中/dev/mmcblkx(x=0~n)表示mmc设备,而/dev/mmcblkxpy(x=0~n,y=1~n)表示mmc设备x的分区y。在I.MX6U-ALPHA开拓板中/dev/mmcblk1表示EMMC,而/dev/mmcblk1p2表示EMMC的分区2。
root后面有"rootwaitrw",rootwait表示等待mmc设备初始化完成往后再挂载,否则的话mmc设备还没初始化完造诣挂载根文件系统会出错的。rw表示根文件系统是可以读写的,不加rw的话可能无法在根文件系统中进行写操作,只能进行读操作。
3、rootfstype
此选项一样平常配置root一起利用,rootfstype用于指定根文件系统类型,如果根文件系统为ext格式的话此选项无所谓。如果根文件系统是yaffs、jffs或ubifs的话就须要设置此选项,指定根文件系统的类型。
bootargs常设置的选项就这三个,后面碰着其他选项的话再讲解。
33.4 uboot启动Linux测试uboot已经移植好了,bootcmd和bootargs这两个主要的环境变量也讲解了,接下来就要测试一下uboot能不能完成它的事情:启动Linux内核。我们测试两种启动Linux内核的方法,一种是直接从EMMC启动,一种是从网络启动。
33.4.1 从EMMC启动Linux系统从EMMC启动也便是将编译出来的Linux镜像文件zImage和设备树文件保存在EMMC中,uboot从EMMC中读取这两个文件并启动,这个是我们产品终极的启动办法。但是我们目前还没有讲解如何移植linux和设备树文件,以及如何将zImage和设备树文件保存到EMMC中。不过大家拿得手的I.MX6U-ALPHA开拓板(EMMC版本)已经将zImage文件和设备树文件烧写到了EMMC中,以是我们可以直接读取来测试。先检讨一下EMMC的分区1中有没有zImage文件和设备树文件,输入命令"lsmmc 1:1",结果如图33.4.1.1所示:
图33.4.1.1 EMMC分区1文件
从图33.4.1.1中可以看出,此时EMMC分区1中存在zimage和imx6ull-alientek-emmc.dtb这两个文件,以是我们可以测试新移植的uboot能不能启动linux内核。设置bootargs和bootcmd这两个环境变量,设置如下:
setenv bootargs 'console=ttymxc0,115200 root=/dev/mmcblk1p2 rootwait rw'
setenv bootcmd 'mmc dev 1; fatload mmc 1:1 80800000 zImage; fatload mmc 1:1 83000000 imx6ull-alientek-emmc.dtb; bootz 80800000 - 83000000;'
saveenv
设置好往后直接输入boot,或者runbootcmd即可启动Linux内核,如果Linux内核启动成功的话就会输出如图33.4.1.2所示的启动信息:
图33.4.1.2 linux内核启动成功
33.4.2 从网络启动Linux系统从网络启动linux系统的唯一目的便是为了调试!
不管是为了调试linux系统还是linux下的驱动。每次修正linux系统文件或者linux下的某个驱动往后都要将其烧写到EMMC中去测试,这样太麻烦了。我们可以设置linux从网络启动,也便是将linux镜像文件和根文件系统都放到Ubuntu下某个指定的文件夹中,这样每次重新编译linux内核或者某个linux驱动往后只须要利用cp命令将其拷贝到这个指定的文件夹中即可,这样就不用须要频繁的烧写EMMC,这样就加快了开拓速率。我们可以通过nfs或者tftp从Ubuntu中下载zImage和设备树文件,根文件系统的话也可以通过nfs挂载,不过本小节我们不讲解如何通过nfs挂载根文件系统,这个在讲解根文件系统移植的时候再讲解。这里我们利用tftp从Ubuntu中下载zImage和设备树文件,条件是要将zImage和设备树文件放到Ubuntu下的tftp目录中,详细方法在30.4.4小节讲解tftp命令的时候已经详细的先容过了。
设置bootargs和bootcmd这两个环境变量,设置如下:
setenv bootargs 'console=ttymxc0,115200 root=/dev/mmcblk1p2 rootwait rw'
setenv bootcmd 'tftp 80800000 zImage; tftp 83000000 imx6ull-alientek-emmc.dtb; bootz 80800000 - 83000000'
saveenv
一开始是通过tftp下载zImage和imx6ull-alientek-emmc.dtb这两个文件,过程如下图33.4.1.3所示:
图33.4.1.3 下载过程
下载完成往后便是启动Linux内核,启动过程如图33.4.1.4所示:
图33.4.1.5 Linux启动过程
uboot移植到此结束,大略总结一下uboot移植的过程:
①、不管是购买的开拓板还是自己做的开拓板,基本都是参考半导体厂商的dmeo板,而半导体厂商会在他们自己的开拓板上移植好uboot、linuxkernel和systemfs等,终极制作好BSP包供应给用户。我们可以在官方供应的BSP包的根本上添加我们的板子,也便是俗称的移植。
②、我们购买的开拓板或者自己做的板子一样平常都不会原封不动的照抄半导体厂商的demo板,都会根据实际的情形来做修正,既然有修正就一定涉及到uboot下驱动的移植。
③、一样平常uboot中须要办理串口、NAND、EMMC或SD卡、网络和LCD驱动,由于uboot的紧张目的便是启动Linux内核,以是不须要考虑太多的外设驱动。
④、在uboot中添加自己的板子信息,根据自己板子的实际情形来修正uboot中的驱动










