rtems网络移植-实现网卡MDIO通信

本博文适用于bbb的bsp移植。

在本人早些时间写的一篇短博文中提到为rtems移植网络驱动的想法:bbb上rtems的tcp/ip协议移植的想法

其中提到驱动移植主要是移植底层的驱动代码,那么现在有很多系统源码都支持bbb板的网卡,比如android for bbb,ubuntu,redhat等。这些系统的确包含驱动源码,但由于系统过于繁琐,设备树过于庞大,导致移植代码的难度增加不少。

但是却忽略了一个更为普遍的系统:uboot

uboot是绝对支持bbb板的网卡,因为各大linux/unix系统的启动都依靠uboot,而很多开发者都依靠uboot来从服务器加载系统,因此uboot的开发团队绝对会紧跟嵌入式硬件的脚步,可以说uboot对于各种cpu、开发板的支持是最可靠、最快速的了。

而且uboot相比其他系统更为精简,代码移植难度小。

因此接下来开始移植uboot的驱动,整个开发周期应该会持续一个月左右,在过程中,大家有什么问题或者本人有什么疏忽的请留言。


首先查看bbb板上处理器和网卡的连接方式:

要明确一点,bbb板的am3359处理器是自带MAC。

上图可以看出,处理器和网卡的连接方式是GMII方式,同时加上MDIO和网卡的控制寄存器进行通信。

首先弄明白uboot如何进行mdio通信。


查看uboot源码,首先查看网卡的注册和初始化方式:


uboot在start.s启动后首先调用的关于硬件初始化的函数是,eth_initialize函数:

源码:/net/eth.c

int eth_initialize(void)
{
	int num_devices = 0;
	struct udevice *dev;

	eth_common_init();

	/*
	 * Devices need to write the hwaddr even if not started so that Linux
	 * will have access to the hwaddr that u-boot stored for the device.
	 * This is accomplished by attempting to probe each device and calling
	 * their write_hwaddr() operation.
	 */
	uclass_first_device(UCLASS_ETH, &dev);
	if (!dev) {
		printf("No ethernet found.\n");
		bootstage_error(BOOTSTAGE_ID_NET_ETH_START);
	} else {
		char *ethprime = getenv("ethprime");
		struct udevice *prime_dev = NULL;

		if (ethprime)
			prime_dev = eth_get_dev_by_name(ethprime);
		if (prime_dev) {
			eth_set_dev(prime_dev);
			eth_current_changed();
		} else {
			eth_set_dev(NULL);
		}

		bootstage_mark(BOOTSTAGE_ID_NET_ETH_INIT);
		do {
			if (num_devices)
				printf(", ");

			printf("eth%d: %s", dev->seq, dev->name);

			if (ethprime && dev == prime_dev)
				printf(" [PRIME]");

			eth_write_hwaddr(dev);

			uclass_next_device(&dev);
			num_devices++;
		} while (dev);

		putc('\n');
	}

	return num_devices;
}


函数中调用了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
	}
}

其中miiphy_init函数是注册mii设备,让其加入链表。


接着是调用phy_init函数,该函数实现了对于网卡设备的注册和初始化:

int phy_init(void)
{
#ifdef CONFIG_PHY_AQUANTIA
	phy_aquantia_init();
#endif
#ifdef CONFIG_PHY_ATHEROS
	phy_atheros_init();
#endif
#ifdef CONFIG_PHY_BROADCOM
	phy_broadcom_init();
#endif
#ifdef CONFIG_PHY_CORTINA
	phy_cortina_init();
#endif
#ifdef CONFIG_PHY_DAVICOM
	phy_davicom_init();
#endif
#ifdef CONFIG_PHY_ET1011C
	phy_et1011c_init();
#endif
#ifdef CONFIG_PHY_LXT
	phy_lxt_init();
#endif
#ifdef CONFIG_PHY_MARVELL
	phy_marvell_init();
#endif
#ifdef CONFIG_PHY_MICREL
	phy_micrel_init();
#endif
#ifdef CONFIG_PHY_NATSEMI
	phy_natsemi_init();
#endif
#ifdef CONFIG_PHY_REALTEK
	phy_realtek_init();
#endif
#ifdef CONFIG_PHY_SMSC
	phy_smsc_init();
#endif
#ifdef CONFIG_PHY_TERANETICS
	phy_teranetics_init();
#endif
#ifdef CONFIG_PHY_VITESSE
	phy_vitesse_init();
#endif

	return 0;
}


这里网卡lan8710是smsc公司的,选择phy_smsc_init函数进行编译:

该函数实现如下:

/drivers/net/phy/smsc.c

#include <miiphy.h>

/* This code does not check the partner abilities. */
static int smsc_parse_status(struct phy_device *phydev)
{
	int mii_reg;

	mii_reg = phy_read(phydev, MDIO_DEVAD_NONE, MII_BMSR);

	if (mii_reg & (BMSR_100FULL | BMSR_100HALF))
		phydev->speed = SPEED_100;
	else
		phydev->speed = SPEED_10;

	if (mii_reg & (BMSR_10FULL | BMSR_100FULL))
		phydev->duplex = DUPLEX_FULL;
	else
		phydev->duplex = DUPLEX_HALF;

	return 0;
}

static int smsc_startup(struct phy_device *phydev)
{
	genphy_update_link(phydev);
	smsc_parse_status(phydev);
	return 0;
}

static struct phy_driver lan8700_driver = {
	.name = "SMSC LAN8700",
	.uid = 0x0007c0c0,
	.mask = 0xffff0,
	.features = PHY_BASIC_FEATURES,
	.config = &genphy_config_aneg,
	.startup = &smsc_startup,
	.shutdown = &genphy_shutdown,
};

static struct phy_driver lan911x_driver = {
	.name = "SMSC LAN911x Internal PHY",
	.uid = 0x0007c0d0,
	.mask = 0xffff0,
	.features = PHY_BASIC_FEATURES,
	.config = &genphy_config_aneg,
	.startup = &smsc_startup,
	.shutdown = &genphy_shutdown,
};

static struct phy_driver lan8710_driver = {
	.name = "SMSC LAN8710/LAN8720",
	.uid = 0x0007c0f0,
	.mask = 0xffff0,
	.features = PHY_BASIC_FEATURES,
	.config = &genphy_config_aneg,
	.startup = &genphy_startup,
	.shutdown = &genphy_shutdown,
};

int phy_smsc_init(void)
{
	phy_register(&lan8710_driver);
	phy_register(&lan911x_driver);
	phy_register(&lan8700_driver);

	return 0;
}

可以看到对于lan8710a网卡进行了phy_register,也就是把网卡设备加入了链表。



接着回到eth_common_init函数,往下执行board_eth_init函数:

该函数定义在board.c文件中,包括了bbb板的具体的配置过程,非常重要

具体实现如下:

int board_eth_init(bd_t *bis)
{
	int rv, n = 0;
	uint8_t mac_addr[6];
	uint32_t mac_hi, mac_lo;
	__maybe_unused struct am335x_baseboard_id header;

	/* try reading mac address from efuse */
	mac_lo = readl(&cdev->macid0l);
	mac_hi = readl(&cdev->macid0h);
	mac_addr[0] = mac_hi & 0xFF;
	mac_addr[1] = (mac_hi & 0xFF00) >> 8;
	mac_addr[2] = (mac_hi & 0xFF0000) >> 16;
	mac_addr[3] = (mac_hi & 0xFF000000) >> 24;
	mac_addr[4] = mac_lo & 0xFF;
	mac_addr[5] = (mac_lo & 0xFF00) >> 8;

#if (defined(CONFIG_DRIVER_TI_CPSW) && !defined(CONFIG_SPL_BUILD)) || \
	(defined(CONFIG_SPL_ETH_SUPPORT) && defined(CONFIG_SPL_BUILD))
	if (!getenv("ethaddr")) {
		printf("<ethaddr> not set. Validating first E-fuse MAC\n");

		if (is_valid_ethaddr(mac_addr))
			eth_setenv_enetaddr("ethaddr", mac_addr);
	}

#ifdef CONFIG_DRIVER_TI_CPSW

	mac_lo = readl(&cdev->macid1l);
	mac_hi = readl(&cdev->macid1h);
	mac_addr[0] = mac_hi & 0xFF;
	mac_addr[1] = (mac_hi & 0xFF00) >> 8;
	mac_addr[2] = (mac_hi & 0xFF0000) >> 16;
	mac_addr[3] = (mac_hi & 0xFF000000) >> 24;
	mac_addr[4] = mac_lo & 0xFF;
	mac_addr[5] = (mac_lo & 0xFF00) >> 8;

	if (!getenv("eth1addr")) {
		if (is_valid_ethaddr(mac_addr))
			eth_setenv_enetaddr("eth1addr", mac_addr);
	}

	if (read_eeprom(&header) < 0)
		puts("Could not get board ID.\n");

	if (board_is_bone(&header) || board_is_bone_lt(&header) ||
	    board_is_idk(&header)) {
		writel(MII_MODE_ENABLE, &cdev->miisel);
		cpsw_slaves[0].phy_if = cpsw_slaves[1].phy_if =
				PHY_INTERFACE_MODE_MII;
	} else {
		writel((RGMII_MODE_ENABLE | RGMII_INT_DELAY), &cdev->miisel);
		cpsw_slaves[0].phy_if = cpsw_slaves[1].phy_if =
				PHY_INTERFACE_MODE_RGMII;
	}

	rv = cpsw_register(&cpsw_data);
	if (rv < 0)
		printf("Error %d registering CPSW switch\n", rv);
	else
		n += rv;
#endif

	/*
	 *
	 * CPSW RGMII Internal Delay Mode is not supported in all PVT
	 * operating points.  So we must set the TX clock delay feature
	 * in the AR8051 PHY.  Since we only support a single ethernet
	 * device in U-Boot, we only do this for the first instance.
	 */
#define AR8051_PHY_DEBUG_ADDR_REG	0x1d
#define AR8051_PHY_DEBUG_DATA_REG	0x1e
#define AR8051_DEBUG_RGMII_CLK_DLY_REG	0x5
#define AR8051_RGMII_TX_CLK_DLY		0x100

	if (board_is_evm_sk(&header) || board_is_gp_evm(&header)) {
		const char *devname;
		devname = miiphy_get_current_dev();

		miiphy_write(devname, 0x0, AR8051_PHY_DEBUG_ADDR_REG,
				AR8051_DEBUG_RGMII_CLK_DLY_REG);
		miiphy_write(devname, 0x0, AR8051_PHY_DEBUG_DATA_REG,
				AR8051_RGMII_TX_CLK_DLY);
	}
#endif
#if defined(CONFIG_USB_ETHER) && \
	(!defined(CONFIG_SPL_BUILD) || defined(CONFIG_SPL_USBETH_SUPPORT))
	if (is_valid_ethaddr(mac_addr))
		eth_setenv_enetaddr("usbnet_devaddr", mac_addr);

	rv = usb_eth_initialize(bis);
	if (rv < 0)
		printf("Error %d registering USB_ETHER\n", rv);
	else
		n += rv;
#endif
	return n;
}

分析该函数:

首先读取mac的地址,然后将其设置为环境变量,便于在命令行下查看

往下使能了MII模式,writel(MII_MODE_ENABLE, &cdev->miisel),调用这个系统io函数,将MII_MODE_ENABLE这个宏定义对应的值写入&cdev->miisel这个寄存器中,而寄存器的地址为CTRL_DEVICE_BASE+miisel,也就是CTRL_DEVICE_BASE这个基本地址加上miisel偏移量,CTRL_DEVICE_BASE这个宏的值是0x4A101000,定义在arch/arm/include/asm/arch-am33xx/hardware_am33xx.h文件中。

MII_MODE_ENABLE这个值的定义在cpu.h中:

#define MII_MODE_ENABLE        (GMII1_SEL_MII | GMII2_SEL_MII)

#define GMII1_SEL_MII        0x0

#define GMII2_SEL_MII        0x0


使能后board_eth_init继续调用cpsw_register函数,在这个函数中,会进行与网卡mdio通信,查看网卡寄存器中的网卡ID。

具体函数在/drivers/net/phy/phy.c文件中:

int __weak get_phy_id(struct mii_dev *bus, int addr, int devad, u32 *phy_id)
{
	int phy_reg;

	/* Grab the bits from PHYIR1, and put them
	 * in the upper half */
	phy_reg = bus->read(bus, addr, devad, MII_PHYSID1);

	if (phy_reg < 0)
		return -EIO;

	*phy_id = (phy_reg & 0xffff) << 16;

	/* Grab the bits from PHYIR2, and put them in the lower half */
	phy_reg = bus->read(bus, addr, devad, MII_PHYSID2);

	if (phy_reg < 0)
		return -EIO;

	*phy_id |= (phy_reg & 0xffff);

	return 0;
}

该网卡存放ID的寄存器为PHYSID1和PHYSID2,因此就需要读取这两个寄存器。

实现读取寄存器的具体命令是:bus->read(bus, addr, devad, MII_PHYSID1)


这个总线读取函数其实就是CPSW_mdio_read函数,实现如下:

文件位于:/drivers/netcpsw.c

static int cpsw_mdio_read(struct mii_dev *bus, int phy_id,
				int dev_addr, int phy_reg)
{
	int data;
	u32 reg;

	if (phy_reg & ~PHY_REG_MASK || phy_id & ~PHY_ID_MASK)
		return -EINVAL;

	wait_for_user_access();
	reg = (USERACCESS_GO | USERACCESS_READ | (phy_reg << 21) |
	       (phy_id << 16));
	__raw_writel(reg, &mdio_regs->user[0].access);
	reg = wait_for_user_access();

	data = (reg & USERACCESS_ACK) ? (reg & USERACCESS_DATA) : -1;
	return data;
}


在这个函数中首先调用wait_for_user_access函数:


static inline u32 wait_for_user_access(void)
{
	u32 reg = 0;
	int timeout = MDIO_TIMEOUT;

	while (timeout-- &&
	((reg = __raw_readl(&mdio_regs->user[0].access)) & USERACCESS_GO))
		udelay(10);

	if (timeout == -1) {
		printf("wait_for_user_access Timeout\n");
		return -ETIMEDOUT;
	}
	return reg;
}

该函数的作用就是不断读取网卡寄存器的某个值,判断是否能够进行access,如果可以返回非0值,否则返回负数。

然后回到上面的函数,调用__raw_writel函数对寄存器进行写值,值为reg = (USERACCESS_GO | USERACCESS_READ | (phy_reg << 21) | (phy_id << 16)); 表示要进行通信和读取寄存器了,让网卡做好准备。

寄存器地址为&mdio_regs->user[0].access,写完后,继续调用reg = wait_for_user_access(); 读取寄存器的值。

然后将返回的reg值进行判断:data = (reg & USERACCESS_ACK) ? (reg & USERACCESS_DATA) : -1;

如果reg & USERACCESS_ACK是正数,也就是网卡寄存器做出了回应,表示数据是对的,那么就reg & USERACCESS_DATA,将其赋值给data。否则data赋值为-1,表示没有数据读取到。


读取完寄存器后返回到get_phy_id函数,phy_reg = bus->read(bus, addr, devad, MII_PHYSID1);

这个时候phy_reg的值就是读取的寄存器的值了

然后接着读下一个寄存器:phy_reg = bus->read(bus, addr, devad, MII_PHYSID2);

因为网卡id一个寄存器放不下,分为两个寄存器。两个寄存器都读到后,进行简单的运算就可得到完整的网卡ID了。


以上就是第一步的工作,有什么欠妥的地方请各位大神指正。以后基本每周更新一篇本周的进展。


  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值