u-boot网络启动分析(一) 网络初窥

arch/powerpc/cpu/mpc8xx/cpu.c最近做了一个项目,涉及到了u-boot,走了不少弯路。鉴于u-boot在以后的嵌入式开发中的巨大作用,决定对uBoot源码进行剖析。一说起源码,我的小心脏就已经迫不及待了,鉴于能力有限,有些错误可能也在所难免,希望大家能及时斧正。有一句话说的好,失败是成功它妈,错误嘛就是正确它爸;出错了就要把它攻破搞通,这样才能离那个“更高的善”更近一步。

本文准备以PowerPC的8640d CPU结合Marvell公司的88E1111千兆以太网芯片,对u-boot的网络初始化流程进行分析,u-boot源码采用201510的那个版本。

初始化入口:

u-boot中好多设备的初始化的入口是放在board_init_r函数中的,网络也不例外。由于board_init_r时需要执行许多初始化调用,所以该程序的体态比较庞大,为支持多设备的选择或者裁剪还引入了不少的宏进行区分,这无疑给我们的阅读造成了不小的影响。但是不要怕,我们现在只分析网络相关的代码,所以我们只关注网络相关的代码,这样阅读量瞬间就给降下来了。经过查找,会发现有个叫eth_initialize的函数好像跟网络沾点边,猜的没错,他就是网络初始化的入口函数。该函数定义在net/eth.c中,源码如下:

int eth_initialize(void) { int num_devices = 0; eth_devices = NULL; eth_current = NULL; eth_common_init(); if (!eth_devices) { puts("No ethernet found.\n"); bootstage_error(BOOTSTAGE_ID_NET_ETH_START); } else { struct eth_device *dev = eth_devices; char *ethprime = getenv("ethprime"); bootstage_mark(BOOTSTAGE_ID_NET_ETH_INIT); do { if (dev->index) puts(", "); printf("%s", dev->name); if (ethprime && strcmp(dev->name, ethprime) == 0) { eth_current = dev; puts(" [PRIME]"); } if (strchr(dev->name, ' ')) puts("\nWarning: eth device name has a space!" "\n"); eth_write_hwaddr(dev, "eth", dev->index); dev = dev->next; num_devices++; } while (dev != eth_devices); eth_current_changed(); putc('\n'); } return num_devices; }

该函数一进去就将两个重要的全局结构体指针初始化为NULL,这两个结构体指针指向的结构体类型相同都是struct eth_device类,顾名思义就是都指向了同一类网络设备,两者的差别在于作用域不同,eth_current定义为static这样它便对模块之外隐藏了,因为它是这个模块内部自己使用的,没必要对全局可见。这个思路很好,用好static这个关键字可以减少全局变量的到处使用,调用就要通过专门的函数来进行,这也正好体现了编码中的封装与隐藏思想。我们来看看这个定义吧:

struct eth_device { char name[16]; unsigned char enetaddr[6]; phys_addr_t iobase; int state; int (*init)(struct eth_device *, bd_t *); int (*send)(struct eth_device *, void *packet, int length); int (*recv)(struct eth_device *); void (*halt)(struct eth_device *); #ifdef CONFIG_MCAST_TFTP int (*mcast)(struct eth_device *, const u8 *enetaddr, u8 set); #endif int (*write_hwaddr)(struct eth_device *); struct eth_device *next; int index; void *priv; }; 

static struct eth_device *eth_devices; struct eth_device *eth_current;

跟其他设备的定义一样,网络设备同样定义了设备名、网络设备的地址以及一些有关该设备的一些操作和设备的priv成员。网络设备我们能想到的就是初始化、发包、收报、广播等操作,都在设备中定义好了。该网络结构体不是本文讨论的重点,暂时不予以深入。

在eth_initialize中,有个函数调用十分重要,这个函数就是eth_common_init。同样我们来看一段代码:

static void eth_common_init(void) { bootstage_mark(BOOTSTAGE_ID_NET_ETH_START); #if defined(CONFIG_MII) || defined(CONFIG_CMD_MII) || defined(CONFIG_PHYLIB) miiphy_init(); #endif #ifdef CONFIG_PHYLIB phy_init(); #endif /* * If board-specific initialization exists, call it. * If not, call a CPU-specific one */ if (board_eth_init != __def_eth_init) { if (board_eth_init(gd->bd) < 0) printf("Board Net Initialization Failed\n"); } else if (cpu_eth_init != __def_eth_init) { if (cpu_eth_init(gd->bd) < 0) printf("CPU Net Initialization Failed\n"); } else { #ifndef CONFIG_DM_ETH printf("Net Initialization Skipped\n"); #endif } }

看着这个函数,是不是一眼就看到了那几行打印“XXX Net Initialization XXX”,啊哈,初始化原来藏在这里。然后我们继续深入,看看board_eth_init跟cpu_eth_init的定义吧:

static int __def_eth_init(bd_t *bis) { return -1; } int cpu_eth_init(bd_t *bis) __attribute__((weak, alias("__def_eth_init"))); int board_eth_init(bd_t *bis) __attribute__((weak, alias("__def_eth_init")));

是不是有点傻眼了,不要慌,普及一下:__attribute__是gcc中特有的一种设置属性的方式,可以用来设置变量甚至函数的一些属性。跟本文有关的就是设置cpu_eth_init和board_eth_init的属性了,设置成什么呢?alias属性将同一个模块中的标号(函数名字)设置为另一个标号的别名,在C语言中别名确实不太常见。所以该属性的作用就是将cpu_eth_init和board_eth_init设置为__def_eth_init的一个别名,调用cpu_eth_init和board_eth_init时就是调用__def_eth_init。该程序中,__def_eth_init函数返回了一个-1,调用貌似没有起到初始化的作用。我们再来看看另一个属性weak,对于c/c++来说编译器默认函数和初始化的全局变量为强符号(strong symbol),未初始化的全局变量为弱符号(weak symbol),我们可以通过设置weak属性来将任意一个强符号转换为弱符号,设置成weak symbol后,在连接器进行链接时如果该符号未定义链接器并不会报错而是默认其中为一个特殊的值,以便程序代码能够识别,在链接过程中如果发现了同名的强符号,便以该强符号进行连接,这样便完成了一个函数别名的使用方法。所以该处的cpu_eth_init或 board_eth_init肯定在其他地方定义了。

我们使用grep 命令全局搜索一下,发现跟86处理器相关的定义只有在arch/powerpc/cpu/mpc8xxx/cpu.c中定义了cpu_eth_init。此时心中肯定还有疑问了,这个好像不会编译进8640d的CPU中吧,我们验证一下。查看Makefile我们发现有如下判断,当CPU为86xx时,使用的是8xxx下面的.O文件。所以这个调用就在这里了。

ifeq ($(CPU),mpc86xx) LIBS += arch/powerpc/cpu/mpc8xxx/ddr/libddr.o LIBS += arch/powerpc/cpu/mpc8xxx/lib8xxx.o endif

cpu_eth_init在这里的重定义如下:

int cpu_eth_init(bd_t *bis) { #if defined(CONFIG_ETHER_ON_FCC) fec_initialize(bis); #endif #if defined(CONFIG_UEC_ETH) uec_standard_init(bis); #endif #if defined(CONFIG_TSEC_ENET) || defined(CONFIG_MPC85XX_FEC) tsec_standard_init(bis); #endif #ifdef CONFIG_FMAN_ENET fm_standard_init(bis); #endif #ifdef CONFIG_VSC9953 vsc9953_init(bis); #endif return 0; }

查看sbc8641d.h我们发现,只有宏CONFIG_TSEC_ENET被定义了,所以我们来看看tsec_standard_init这个函数。从tsec_standard_init开始,程序跳转到了跟设备和CPU相关的代码中了,我们使用的是PowerPC 8641d,PHY使用的是88E1111,所以使用tsec_standard_init。

int tsec_standard_init(bd_t *bis) { struct fsl_pq_mdio_info info; info.regs = (struct tsec_mii_mng *)CONFIG_SYS_MDIO_BASE_ADDR; info.name = DEFAULT_MII_NAME; fsl_pq_mdio_init(bis, &info); return tsec_eth_init(bis, tsec_info, ARRAY_SIZE(tsec_info)); }

在tsec_standard_init中会调用tsec_eth_init继续初始化网络设备,tsec_eth_init的作用是将已经配置的网卡(姑且先叫网卡吧,不严谨)进行初始化,网卡的配置参数是通过一个叫tsec_info静态全局结构体数组进传入的。该结构体设置了一些TSEC controller的一些参数,而配置的个数是在sbc8641d.h中通过宏来配置的,对于8641d来说最多有4个TSEC控制器。

int tsec_eth_init(bd_t *bis, struct tsec_info_struct *tsecs, int num) { int i; int ret, count = 0; for (i = 0; i < num; i++) { ret = tsec_initialize(bis, &tsecs[i]); if (ret > 0) count += ret; } return count; }

static int tsec_initialize(bd_t *bis, struct tsec_info_struct *tsec_info) { struct eth_device *dev; int i; struct tsec_private *priv; dev = (struct eth_device *)malloc(sizeof *dev); if (NULL == dev) return 0; memset(dev, 0, sizeof *dev); priv = (struct tsec_private *)malloc(sizeof(*priv)); if (NULL == priv) return 0; priv->regs = tsec_info->regs; priv->phyregs_sgmii = tsec_info->miiregs_sgmii; priv->phyaddr = tsec_info->phyaddr; priv->flags = tsec_info->flags; sprintf(dev->name, tsec_info->devname); priv->interface = tsec_info->interface; priv->bus = miiphy_get_dev_by_name(tsec_info->mii_devname); dev->iobase = 0; dev->priv = priv; dev->init = tsec_init; dev->halt = tsec_halt; dev->send = tsec_send; dev->recv = tsec_recv; #ifdef CONFIG_MCAST_TFTP dev->mcast = tsec_mcast_addr; #endif /* Tell u-boot to get the addr from the env */ for (i = 0; i < 6; i++) dev->enetaddr[i] = 0; eth_register(dev); /* Reset the MAC */ setbits_be32(&priv->regs->maccfg1, MACCFG1_SOFT_RESET); udelay(2); /* Soft Reset must be asserted for 3 TX clocks */ clrbits_be32(&priv->regs->maccfg1, MACCFG1_SOFT_RESET); /* Try to initialize PHY here, and return */ return init_phy(dev); }

tsec_eth_init调用tsec_initialize来将TSEC设备进行初始化,tsec_initialize中设置了网络设备的一系列操作函数,将一些初始化的值保存在了栈中,并通过设备中的priv指针成员进行指向。这里有两个函数调用十分重要,一个是eth_register,另一个是init_phy。eth_register定义在net/eth.c中,主要作用就是将初始化的设备依次挂到eth_devices这个双向设备链表中。

int eth_register(struct eth_device *dev) { struct eth_device *d; static int index; assert(strlen(dev->name) < sizeof(dev->name)); if (!eth_devices) { eth_devices = dev; eth_current = dev; eth_current_changed(); } else { for (d = eth_devices; d->next != eth_devices; d = d->next) ; d->next = dev; } dev->state = ETH_STATE_INIT; dev->next = eth_devices; dev->index = index++; return 0; }

init_phy用来发现那个PHY连接在设备上,然后对该PHY进行相关的设置。代码如下:

static int init_phy(struct eth_device *dev) { struct tsec_private *priv = (struct tsec_private *)dev->priv; struct phy_device *phydev; struct tsec __iomem *regs = priv->regs; u32 supported = (SUPPORTED_10baseT_Half | SUPPORTED_10baseT_Full | SUPPORTED_100baseT_Half | SUPPORTED_100baseT_Full); if (priv->flags & TSEC_GIGABIT) supported |= SUPPORTED_1000baseT_Full; /* Assign a Physical address to the TBI */ out_be32(®s->tbipa, CONFIG_SYS_TBIPA_VALUE); priv->interface = tsec_get_interface(priv); if (priv->interface == PHY_INTERFACE_MODE_SGMII) tsec_configure_serdes(priv); phydev = phy_connect(priv->bus, priv->phyaddr, dev, priv->interface); if (!phydev) return 0; phydev->supported &= supported; phydev->advertising = phydev->supported; priv->phydev = phydev; phy_config(phydev); return 1; }

总结:

本文从软件调用的视角简单分析了一下u-boot网络的初始化流程。并未对驱动以及PHY的初始化进行过多解析,因为这两部分比较复杂,不符合本文的初衷,以后会另外分析。

函数调用关系如图所示:

u-boot网络启动分析(一) 网络初窥_第1张图片








你可能感兴趣的:(u-boot)