目录
一、移植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"