移植uboot到具体开发板及分析DHCP命令执行流程

目录

一、移植uboot到具体开发板 

二、分析DHCP命令执行流程


一、移植uboot到具体开发板 

       在我上一篇文章中,将uboot中很多无关的代码都删除了,当然还有很多内容仍然是可以删除的,这里我们在上一篇的基础上,将uboot移植到一款zynq开发板上,但是所用方法对其他平台也有参考意义。

        这里首先需要了解zynq的大体启动过程。zynq的启动过程大概可以分成三部分,如下,

 以上截图来自ug585,首先是片上的BOM代码会首先执行,该代码会将FSBL从选定的启动设备中读出,然后系统由FSBL接管。FSBL会用bit文件对PL进行编程并且将uboot或者裸机代码放到DDR中,所以从这里我们能够得出结论:我们的uboot在DDR中运行,并且这里我们需要三个文件,FSBL、BIT文件和uboot编译出的文件。zynq的启动设备可以是qspi flash、sd卡、jtag等等,那我们如何选择它们呢?

从上图中我们可以看到,我们主要可以通过MIO3、MIO4和MIO5确定的。

        接下来我们开始在uboot中添加我们的开发板了,用vscode打开上一篇文章中处理完的uboot文件夹。

        1、首先在configs文件夹下,新建一个文件,可以取名为myboard_defconfig,将zynq_zc702_defconfig文件中的内容复制进去,以下内容需要说明,

CONFIG_SYS_CONFIG_NAME="myboard"
config SYS_CONFIG_NAME
	string "Board configuration name"
	default "zynq-common"
	help
	  This option contains information about board configuration name.
	  Based on this option include/configs/<CONFIG_SYS_CONFIG_NAME>.h header
	  will be used for board configuration.

CONFIG_SYS_CONFIG_NAME是配置头文件的名字,该名子需要和等下我们在include/configs目录下添加的配置头文件名字一样。

CONFIG_DEFAULT_DEVICE_TREE="myboard"

现在的uboot都是包含设备树的,上面的配置用来指定设备树文件。

CONFIG_SPL开头的配置项都是可以删掉的,

Note: "SPL" stands for "Secondary Program Loader," which is explained in
more detail later in this file.

上面是uboot readme对SPL的解释。网上查了一下,SPL一般用于在内存不足以容纳整个uboot的场景,而我们的uboot是在DDR中运行的,空间足够。

CONFIG_DEBUG_UART开头的配置项都是可以删掉的,其解释如下,

config DEBUG_UART
	bool "Enable an early debug UART for debugging"
	help
	  The debug UART is intended for use very early in U-Boot to debug
	  problems when an ICE or other debug mechanism is not available.

也就是说,当我们想要调试uboot启动早期的问题时,使用该配置是能串口,但是这里我们不需要。

CONFIG_CMD_EEPROM=y
CONFIG_CMD_I2C=y

这两个命令也是我们不需要的,删掉,

CONFIG_PHY_MARVELL=y
CONFIG_PHY_XILINX=y

我们用的以太网phy是REALTEK的,所以上面两个配置不需要。保存关闭。

        2、在arch/arm/dts文件夹下新建myboard.dts文件,这里我们把zynq-zed.dts的内容复制进去,这里注意如下,

serial0 = &uart1;

串口需要根据具体情况选择,我这里用的是串口1,

	memory@0 {
		device_type = "memory";
		reg = <0x0 0x20000000>;
	};

这里注意实际的内存大小。

        3、在board/xilinx/zynq目录下新建一个myboard目录,将zynq-zc702目录下的ps7_init_gpl.c复制进去,这里注意新建目录的名字,

2. If you leave an empty string here, U-Boot will use
	     board/xilinx/zynq/$(CONFIG_DEFAULT_DEVICE_TREE)/ps7_init_gpl.c
	     for Zynq-7000, or
	     board/xilinx/zynqmp/$(CONFIG_DEFAULT_DEVICE_TREE)/psu_init_gpl.c
	     for ZynqMP.

也就是说应该和设备树的命名一样。

        4、在include/configs目录下新建myboard.h文件,只加入如下一句话即可,

#include <configs/zynq-common.h>

另外defconfig文件的名字也不是乱取的,

1.  Create a new directory to hold your board specific code. Add any
    files you need. In your board directory, you will need at least
    the "Makefile" and a "<board>.c".
2.  Create a new configuration file "include/configs/<board>.h" for
    your board.
3.  If you're porting U-Boot to a new CPU, then also create a new
    directory to hold your CPU specific code. Add any files you need.
4.  Run "make <board>_defconfig" with your new name.
5.  Type "make", and you should get a working "u-boot.srec" file
    to be installed on your target system.
6.  Debug and solve any problems that might arise.
    [Of course, this last step is much harder than it sounds.]

这是根目录readme中的一段话,也就是说上面的<board>就是CONFIG_SYS_CONFIG_NAME!

        完成完上面的四步骤之后,我们就可以进行编译了。

make CROSS_COMPILE=arm-linux-gnueabihf- ARCH=arm myboard_defconfig
make CROSS_COMPILE=arm-linux-gnueabihf- ARCH=arm 

二、分析DHCP命令执行流程

        启动uboot后会看到打印如下的信息,

最后是进行auto negotiation的打印信息,这里我们不希望进行该操作,那么如何删除它呢?也以此为例让我们更好的理解uboot执行流程。

        首先根据这个打印信息我们能够定位到下面这个函数,

int genphy_update_link(struct phy_device *phydev)
{
	unsigned int mii_reg;

	/*
	 * 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);

	/*
	 * If we already saw the link up, and it hasn't gone down, then
	 * we don't need to wait for autoneg again
	 */
	if (phydev->link && mii_reg & BMSR_LSTATUS)
		return 0;

	if ((phydev->autoneg == AUTONEG_ENABLE) &&
	    !(mii_reg & BMSR_ANEGCOMPLETE)) {
		int i = 0;


		printf("%s Waiting for PHY auto negotiation to complete",

而genphy_update_link函数是被rtl8211e_startup函数调用的,

static int rtl8211e_startup(struct phy_device *phydev)
{
	int ret;

	ret = genphy_update_link(phydev);
	if (ret)
		return ret;

	return genphy_parse_link(phydev);
}

这个函数是RTL8211E_driver的成员函数,

/* Support for RTL8211E-VB-CG, RTL8211E-VL-CG and RTL8211EG-VB-CG PHYs */
static struct phy_driver RTL8211E_driver = {
	.name = "RealTek RTL8211E",
	.uid = 0x1cc915,
	.mask = 0xffffff,
	.features = PHY_GBIT_FEATURES,
	.config = &rtl8211x_config,
	.startup = &rtl8211e_startup,
	.shutdown = &genphy_shutdown,
};

这个成员函数在drivers/net/zynq_gem.c中的zynq_gem_init函数中被调用,

	ret = phy_startup(priv->phydev);
	if (ret)
		return ret;

而zynq_gem_init是uboot驱动声明中的一个函数,

U_BOOT_DRIVER(zynq_gem) = {
	.name	= "zynq_gem",
	.id	= UCLASS_ETH,
	.of_match = zynq_gem_ids,
	.ofdata_to_platdata = zynq_gem_ofdata_to_platdata,
	.probe	= zynq_gem_probe,
	.remove	= zynq_gem_remove,
	.ops	= &zynq_gem_ops,
	.priv_auto_alloc_size = sizeof(struct zynq_gem_priv),
	.platdata_auto_alloc_size = sizeof(struct eth_pdata),
};

U_BOOT_DRIVER用来声明uboot驱动,其中的of_match成员用来和设备树节点进行匹配的,

   - Scan through the device tree definitions. U-Boot looks at top-level
nodes in the the device tree. It looks at the compatible string in each node
and uses the of_match table of the U_BOOT_DRIVER() structure to find the
right driver for each node. In this case, the of_match table may provide a
driver_data value, but platdata cannot be provided until later.

其中还有一个很重要的成员,就是id,它用来指定该驱动属于哪一个类的。到目前,我们知道auto negotiation的打印信息来自于一个驱动代码中的函数被调用而产生的,那么是哪一个函数调用这个驱动代码呢?答案在下面,

int eth_init(void)
{
	char *ethact = env_get("ethact");
	char *ethrotate = env_get("ethrotate");
	struct udevice *current = NULL;
	struct udevice *old_current;
	int ret = -ENODEV;

	/*
	 * When 'ethrotate' variable is set to 'no' and 'ethact' variable
	 * is already set to an ethernet device, we should stick to 'ethact'.
	 */
	if ((ethrotate != NULL) && (strcmp(ethrotate, "no") == 0)) {
		if (ethact) {
			current = eth_get_dev_by_name(ethact);
			if (!current)
				return -EINVAL;
		}
	}

	if (!current) {
		current = eth_get_dev();
		if (!current) {
			printf("No ethernet found.\n");
			return -ENODEV;
		}
	}

	old_current = current;
	do {
		if (current) {
			debug("Trying %s\n", current->name);

			if (device_active(current)) {

				ret = eth_get_ops(current)->start(current);
				if (ret >= 0) {
					struct eth_device_priv *priv =
						current->uclass_priv;

					priv->state = ETH_STATE_ACTIVE;
					return 0;
				}

没错,就是ret = eth_get_ops(current)->start(current);这句话,它的含义就是从current中获得ops成员,并调用其中的start成员。而current是由eth_get_dev()获得的,

struct udevice *eth_get_dev(void)
{
	struct eth_uclass_priv *uc_priv;

	uc_priv = eth_get_uclass_priv();
	if (!uc_priv->current)
		eth_errno = uclass_first_device(UCLASS_ETH,
				    &uc_priv->current);
	return uc_priv->current;
}

上面是eth_get_dev函数的定义,该函数通过调用uclass_first_device函数,从UCLASS_ETH类中获得驱动。

/**
 * uclass_first_device() - Get the first device in a uclass
 *
 * The device returned is probed if necessary, and ready for use
 *
 * This function is useful to start iterating through a list of devices which
 * are functioning correctly and can be probed.
 *
 * @id: Uclass ID to look up
 * @devp: Returns pointer to the first device in that uclass if no error
 * occurred, or NULL if there is no first device, or an error occurred with
 * that device.
 * @return 0 if OK (found or not found), other -ve on error
 */
int uclass_first_device(enum uclass_id id, struct udevice **devp);

        我们继续追踪代码,eth_init函数由net_loop函数调用,net_loop函数又由netboot_common函数调用,可以将netboot_common函数的第一个参数proto打印出来发现,调用netboot_common函数在形参第一个位置输入的协议是DHCP,所以netboot_common被do_dhcp函数调用,

#if defined(CONFIG_CMD_DHCP)
static int do_dhcp(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])
{
	return netboot_common(DHCP, cmdtp, argc, argv);
}

U_BOOT_CMD(
	dhcp,	3,	1,	do_dhcp,
	"boot image via network using DHCP/TFTP protocol",
	"[loadAddress] [[hostIPaddr:]bootfilename]"
);

从上面代码大家能够看出do_dhcp是命令执行时会调用的函数。也就是说,uboot启动后会执行一些命令,这些命令执行时就调用了do_dhcp函数,而uboot启动后执行哪些命令,是由如下配置决定的,

CONFIG_BOOTCOMMAND="run $modeboot || run distro_bootcmd"

我们重点关注下distro_bootcmd,该环境变量的注释如下,

	"distro_bootcmd=" BOOTENV_SET_SCSI_NEED_INIT                      \
		"for target in ${boot_targets}; do "                      \
			"run bootcmd_${target}; "                         \
		"done\0"

也就是说,会根据boot_targets环境变量的数值执行启动命令,而boot_targets注释如下,

#define BOOTENV_BOOT_TARGETS \
	"boot_targets=" BOOT_TARGET_DEVICES(BOOTENV_DEV_NAME) "\0"

我们再找下BOOT_TARGET_DEVICES,

#define BOOT_TARGET_DEVICES(func) \
	BOOT_TARGET_DEVICES_MMC(func) \
	BOOT_TARGET_DEVICES_USB(func) \
	BOOT_TARGET_DEVICES_PXE(func) \
	BOOT_TARGET_DEVICES_DHCP(func)

以DHCP为例,展开之后会执行如下内容,

#define BOOTENV_DEV_NAME_DHCP(devtypeu, devtypel, instance) \
	"dhcp "

        综上所述,如果不想打印auto negotiation的打印信息,我们可以直接,将配置改成如下即可,

CONFIG_BOOTCOMMAND="run $modeboot"

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值