以太网驱动的流程浅析(三)-ifconfig的-19错误最底层分析

作者:heaven 发布于:2019-12-27 15:22 分类:Linux内核分析

我们继续沿着上一篇的以太网思路来继续分析,目的是为了学习以太网这块从应用层到底层的整块加载和匹配流程。


【硬件环境】         Imx6ul

Linux kernel版本】   Linux4.1.15

【以太网phy       Realtek8201f

1. 以太网流程分析跟踪

1.1 ifconfig-19错误最底层分析

首先我们来看nxp的以太网驱动代码 

路径:drivers/net/ethernet/freescale


static int
fec_enet_open(struct net_device *ndev)
{
	struct fec_enet_private *fep = netdev_priv(ndev);
	const struct platform_device_id *id_entry =
				platform_get_device_id(fep->pdev);
	int ret;

	pinctrl_pm_select_default_state(&fep->pdev->dev);
	ret = fec_enet_clk_enable(ndev, true);
	if (ret)
		return ret;

	/* I should reset the ring buffers here, but I don't yet know
	 * a simple way to do that.
	 */

	ret = fec_enet_alloc_buffers(ndev);
	if (ret)
		goto err_enet_alloc;

	/* Init MAC prior to mii bus probe */
	fec_restart(ndev);

	/* Probe and connect to PHY when open the interface */
	ret = fec_enet_mii_probe(ndev);
	if (ret)
		goto err_enet_mii_probe;

	napi_enable(&fep->napi);
	phy_start(fep->phy_dev);
	netif_tx_start_all_queues(ndev);

	pm_runtime_get_sync(ndev->dev.parent);
	if ((id_entry->driver_data & FEC_QUIRK_BUG_WAITMODE) &&
	    !fec_enet_irq_workaround(fep))
		pm_qos_add_request(&fep->pm_qos_req,
				   PM_QOS_CPU_DMA_LATENCY,
				   0);
	else
		pm_qos_add_request(&fep->pm_qos_req,
				   PM_QOS_CPU_DMA_LATENCY,
				   PM_QOS_DEFAULT_VALUE);

	device_set_wakeup_enable(&ndev->dev, fep->wol_flag &
				 FEC_WOL_FLAG_ENABLE);
	fep->miibus_up_failed = false;

	return 0;

err_enet_mii_probe:
	fec_enet_free_buffers(ndev);
err_enet_alloc:
	fep->miibus_up_failed = true;
	if (!fep->mii_bus_share)
		pinctrl_pm_select_sleep_state(&fep->pdev->dev);
	return ret;
}




看了后我们可以通过打印信息知道是fec_enet_mii_probe函数返回的-19,继续跟踪进去:

static int fec_enet_mii_probe(struct net_device *ndev)
{
	struct fec_enet_private *fep = netdev_priv(ndev);
	struct phy_device *phy_dev = NULL;
	char mdio_bus_id[MII_BUS_ID_SIZE];
	char phy_name[MII_BUS_ID_SIZE + 3];
	int phy_id;
	int dev_id = fep->dev_id;

	fep->phy_dev = NULL;

	if (fep->phy_node) {
		phy_dev = of_phy_connect(ndev, fep->phy_node,
					 &fec_enet_adjust_link, 0,
					 fep->phy_interface);
		if (!phy_dev)
			return -ENODEV;
	} else {
		/* check for attached phy */
		for (phy_id = 0; (phy_id < PHY_MAX_ADDR); phy_id++) {
			if ((fep->mii_bus->phy_mask & (1 << phy_id)))
				continue;
			if (fep->mii_bus->phy_map[phy_id] == NULL)
				continue;
			if (fep->mii_bus->phy_map[phy_id]->phy_id == 0)
				continue;
			if (dev_id--)
				continue;
			strlcpy(mdio_bus_id, fep->mii_bus->id, MII_BUS_ID_SIZE);
			break;
		}

		if (phy_id >= PHY_MAX_ADDR) {
			netdev_info(ndev, "no PHY, assuming direct connection to switch\n");
			strlcpy(mdio_bus_id, "fixed-0", MII_BUS_ID_SIZE);
			phy_id = 0;
		}

		snprintf(phy_name, sizeof(phy_name),
			 PHY_ID_FMT, mdio_bus_id, phy_id);
		phy_dev = phy_connect(ndev, phy_name, &fec_enet_adjust_link,
				      fep->phy_interface);
	}

	if (IS_ERR(phy_dev)) {
		netdev_err(ndev, "could not attach to PHY\n");
		return PTR_ERR(phy_dev);
	}

	/* mask with MAC supported features */
	if (fep->quirks & FEC_QUIRK_HAS_GBIT) {
		phy_dev->supported &= PHY_GBIT_FEATURES;
		phy_dev->supported &= ~SUPPORTED_1000baseT_Half;
#if !defined(CONFIG_M5272)
		phy_dev->supported |= SUPPORTED_Pause;
#endif
	}
	else
		phy_dev->supported &= PHY_BASIC_FEATURES;

	phy_dev->advertising = phy_dev->supported;

	fep->phy_dev = phy_dev;
	fep->link = 0;
	fep->full_duplex = 0;

	netdev_info(ndev, "Freescale FEC PHY driver [%s] (mii_bus:phy_addr=%s, irq=%d)\n",
		    fep->phy_dev->drv->name, dev_name(&fep->phy_dev->dev),
		    fep->phy_dev->irq);

	return 0;
}
#define	EXDEV		18	/* Cross-device link */
#define	ENODEV		19	/* No such device */
#define	ENOTDIR		20	/* Not a directory */
		if (!phy_dev)
			return -ENODEV;

哈哈,看到这里答案出来了,-19就是这里返回的,也就是说phy_devNULL

那为什么呢?我们跟进去看下,of_phy_connect,功能注释已经写的很清楚了

Connect to the phy described in the device tree,从设备树上获取phy的相关描述信息,路径:drivers/of/of_mdio.c


/**
 * of_phy_connect - Connect to the phy described in the device tree
 * @dev: pointer to net_device claiming the phy
 * @phy_np: Pointer to device tree node for the PHY
 * @hndlr: Link state callback for the network device
 * @iface: PHY data interface type
 *
 * Returns a pointer to the phy_device if successful.  NULL otherwise
 */
struct phy_device *of_phy_connect(struct net_device *dev,
				  struct device_node *phy_np,
				  void (*hndlr)(struct net_device *), u32 flags,
				  phy_interface_t iface)
{
	struct phy_device *phy = of_phy_find_device(phy_np);

	if (!phy)
		return NULL;

	phy->dev_flags = flags;

	return phy_connect_direct(dev, phy, hndlr, iface) ? NULL : phy;
}
EXPORT_SYMBOL(of_phy_connect);


既然phy_device是空的,也就是说struct phy_device *phy = of_phy_find_device(phy_np);

没有从这里面拿到phy_device

/**
 * of_phy_find_device - Give a PHY node, find the phy_device
 * @phy_np: Pointer to the phy's device tree node
 *
 * Returns a pointer to the phy_device.
 */
struct phy_device *of_phy_find_device(struct device_node *phy_np)
{
	struct device *d;
	if (!phy_np)
		return NULL;

	d = bus_find_device(&mdio_bus_type, NULL, phy_np, of_phy_match);
	return d ? to_phy_device(d) : NULL;
}
EXPORT_SYMBOL(of_phy_find_device);



看下如下两个函数


/**
 * bus_find_device - device iterator for locating a particular device.
 * @bus: bus type
 * @start: Device to begin with
 * @data: Data to pass to match function
 * @match: Callback function to check device
 *
 * This is similar to the bus_for_each_dev() function above, but it
 * returns a reference to a device that is 'found' for later use, as
 * determined by the @match callback.
 *
 * The callback should return 0 if the device doesn't match and non-zero
 * if it does.  If the callback returns non-zero, this function will
 * return to the caller and not iterate over any more devices.
 */
struct device *bus_find_device(struct bus_type *bus,
			       struct device *start, void *data,
			       int (*match)(struct device *dev, void *data))
{
	struct klist_iter i;
	struct device *dev;

	if (!bus || !bus->p)
		return NULL;

	klist_iter_init_node(&bus->p->klist_devices, &i,
			     (start ? &start->p->knode_bus : NULL));
	while ((dev = next_device(&i)))
		if (match(dev, data) && get_device(dev))
			break;
	klist_iter_exit(&i);
	return dev;
}
EXPORT_SYMBOL_GPL(bus_find_device);
/* Helper function for of_phy_find_device */
static int of_phy_match(struct device *dev, void *phy_np)
{
	return dev->of_node == phy_np;
}



这里我需要解释下了,bus_find_device(&mdio_bus_type, NULL, phy_np, of_phy_match);

bus总线上找到device,也就是我们的phy_device


return d ? to_phy_device(d) : NULL;
#define to_phy_device(d) container_of(d, struct phy_device, dev)



如果找到就返回phy_device,否则就返回NULL

这个bus是指mdio_bus_type,如下:


d = bus_find_device(&mdio_bus_type, NULL, phy_np, of_phy_match);
struct bus_type mdio_bus_type = {
	.name		= "mdio_bus",
	.match		= mdio_bus_match,
	.pm		= MDIO_BUS_PM_OPS,
	.dev_groups	= mdio_dev_groups,
};
EXPORT_SYMBOL(mdio_bus_type);


1.2 以太网硬件知识

mdio又是什么呢?这就是以太网的知识了,我们看下以太网的硬件接法:

=================================================================

如下内容转自:https://blog.csdn.net/fun_tion/article/details/70270632

1.概述

MII即“媒体独立接口”,也叫“独立于介质的接口”。它是IEEE-802.3定义的以太网行业标准。它包括一个数据接口,以及一个MACPHY之间的管理接口。RMII全称为“简化的媒体独立接口”,是IEEE-802.3u标准中除MII接口之外的另一种实现。(此处内容来源于网络)

 

2.独立于介质的接口(MII

独立于介质的接口(MII)用于MAC与外接的PHY互联,支持10Mbit/s100Mbit/s数据传输模式。MII的信号线如下图所示:

图像 92.jpg

MII_TX_CLK:发送数据使用的时钟信号,对于10M/s的数据传输,此时钟为2.5MHz,对于100M/s的数据传输,此时钟为25MHz 
MII_RX_CLK
:接收数据使用的时钟信号,对于10M/s的数据传输,此时钟为2.5MHz,对于100M/s的数据传输,此时钟为25MHz 
MII_TX_EN
:传输使能信号,此信号必需与数据前导符的起始位同步出现,并在传输完毕前一直保持。                                 
MII_TXD[3:0]
:发送数据线,每次传输4位数据,数据在MII_TX_EN信号有效时有效。MII_TXD[0]是数据的最低位,MII_TXD[3]是最高位。当MII_TX_EN信号无效时,PHY忽略传输的数据。 
MII_CRS
:载波侦听信号,仅工作在半双工模式下,由PHY控制,当发送或接收的介质非空闲时,使能此信号。 PHY必需保证MII_CRS信号在发生冲突的整个时间段内都保持有效,不需要此信号与发送/接收的时钟同步。 
MII_COL
:冲突检测信号,仅工作在半双工模式下,由PHY控制,当检测到介质发生冲突时,使能此信号,并且在整个冲突的持续时间内,保持此信号有效。此信号不需要和发送/接收的时钟同步。 
MII_RXD[3:0]
:接收数据线,每次接收4位数据,数据在MII_RX_DV信号有效时有效。MII_RXD[0]是数据的最低位,MII_RXD[3]是最高位。当MII_RX_EN无效,而MII_RX_ER有效时,MII_RXD[3:0]数据值代表特定的信息(请参考表194)   
MII_RX_DV
:接收数据使能信号,由PHY控制,当PHY准备好数据供MAC接收时,使能该信号。此信号必需和帧数据的首位同步出现,并保持有效直到数据传输完成。在传送最后4位数据后的第一个时钟之前,此信号必需变为无效状态。为了正确的接收一个帧,有效电平不能滞后于数据线上的SFD位出现。 
MII_RX_ER
:接收出错信号,保持一个或多个时钟周期(MII_RX_CLK)的有效状态,表明MAC在接收过程中检测到错误。具体错误原因需配合MII_RX_DV的状态及MII_RXD[3:0]的数据值。 
3.
精简的独立于介质的接口(RMII

精简的独立于介质接口(RMII)规范减少了以太网通信所需要的引脚数。根据IEEE802.3标准,MII接口需要16个数据和控制信号引脚,而RMII标准则将引脚数减少到了7个。RMII具有以下特性:

时钟信号需要提高到50MHz   
MAC
和外部的以太网PHY需要使用同样的时钟源   
使用2位宽度的数据收发   
RMII
的信号线如下图所示:

图像 93.jpg

如上内容转自:https://blog.csdn.net/fun_tion/article/details/70270632

1.3 Mdio总线没有找到phy_device

接下来回归到软件层面,那以太网的通信收发数据包就是使用MDC/MDIO这样的硬件接口

软件的接口是:mdiobus_readmdiobus_write


/**
 * mdiobus_read - Convenience function for reading a given MII mgmt register
 * @bus: the mii_bus struct
 * @addr: the phy address
 * @regnum: register number to read
 *
 * NOTE: MUST NOT be called from interrupt context,
 * because the bus read/write functions may wait for an interrupt
 * to conclude the operation.
 */
int mdiobus_read(struct mii_bus *bus, int addr, u32 regnum)
{
	int retval;

	BUG_ON(in_interrupt());

	mutex_lock(&bus->mdio_lock);
	retval = bus->read(bus, addr, regnum);
	mutex_unlock(&bus->mdio_lock);

	return retval;
}
EXPORT_SYMBOL(mdiobus_read);
/**
 * mdiobus_write - Convenience function for writing a given MII mgmt register
 * @bus: the mii_bus struct
 * @addr: the phy address
 * @regnum: register number to write
 * @val: value to write to @regnum
 *
 * NOTE: MUST NOT be called from interrupt context,
 * because the bus read/write functions may wait for an interrupt
 * to conclude the operation.
 */
int mdiobus_write(struct mii_bus *bus, int addr, u32 regnum, u16 val)
{
	int err;

	BUG_ON(in_interrupt());

	mutex_lock(&bus->mdio_lock);
	err = bus->write(bus, addr, regnum, val);
	mutex_unlock(&bus->mdio_lock);

	return err;
}
EXPORT_SYMBOL(mdiobus_write);
/**
 * mdio_bus_match - determine if given PHY driver supports the given PHY device
 * @dev: target PHY device
 * @drv: given PHY driver
 *
 * Description: Given a PHY device, and a PHY driver, return 1 if
 *   the driver supports the device.  Otherwise, return 0.
 */
static int mdio_bus_match(struct device *dev, struct device_driver *drv)
{
	struct phy_device *phydev = to_phy_device(dev);
	struct phy_driver *phydrv = to_phy_driver(drv);

	if (of_driver_match_device(dev, drv))
		return 1;

	if (phydrv->match_phy_device)
		return phydrv->match_phy_device(phydev);

	return (phydrv->phy_id & phydrv->phy_id_mask) ==
		(phydev->phy_id & phydrv->phy_id_mask);
}



那这块最终的read / write的实现函数在哪里呢?去以太网控制器drvier里看就好了fec_main.c中:


fec_enet_mii_init==>

        fep->mii_bus->name = "fec_enet_mii_bus";
	fep->mii_bus->read = fec_enet_mdio_read;
	fep->mii_bus->write = fec_enet_mdio_write;
	snprintf(fep->mii_bus->id, MII_BUS_ID_SIZE, "%s-%x",
		pdev->name, fep->dev_id + 1);
	fep->mii_bus->priv = fep;
	fep->mii_bus->parent = &pdev->dev;
static int fec_enet_mdio_read(struct mii_bus *bus, int mii_id, int regnum)
{
	struct fec_enet_private *fep = bus->priv;
	unsigned long time_left;

	fep->mii_timeout = 0;
	init_completion(&fep->mdio_done);

	/* start a read op */
	writel(FEC_MMFR_ST | FEC_MMFR_OP_READ |
		FEC_MMFR_PA(mii_id) | FEC_MMFR_RA(regnum) |
		FEC_MMFR_TA, fep->hwp + FEC_MII_DATA);

	/* wait for end of transfer */
	time_left = wait_for_completion_timeout(&fep->mdio_done,
			usecs_to_jiffies(FEC_MII_TIMEOUT));
	if (time_left == 0) {
		fep->mii_timeout = 1;
		netdev_err(fep->netdev, "MDIO read timeout\n");
		return -ETIMEDOUT;
	}

	/* return value */
	return FEC_MMFR_DATA(readl(fep->hwp + FEC_MII_DATA));
}



static int fec_enet_mdio_write(struct mii_bus *bus, int mii_id, int regnum,
			   u16 value)
{
	struct fec_enet_private *fep = bus->priv;
	unsigned long time_left;

	fep->mii_timeout = 0;
	init_completion(&fep->mdio_done);

	/* start a write op */
	writel(FEC_MMFR_ST | FEC_MMFR_OP_WRITE |
		FEC_MMFR_PA(mii_id) | FEC_MMFR_RA(regnum) |
		FEC_MMFR_TA | FEC_MMFR_DATA(value),
		fep->hwp + FEC_MII_DATA);

	/* wait for end of transfer */
	time_left = wait_for_completion_timeout(&fep->mdio_done,
			usecs_to_jiffies(FEC_MII_TIMEOUT));
	if (time_left == 0) {
		fep->mii_timeout = 1;
		netdev_err(fep->netdev, "MDIO write timeout\n");
		return -ETIMEDOUT;
	}

	return 0;
}
/*
 * The Bus class for PHYs.  Devices which provide access to
 * PHYs should register using this structure
 */
struct mii_bus {
	const char *name;
	char id[MII_BUS_ID_SIZE];
	void *priv;
	int (*read)(struct mii_bus *bus, int phy_id, int regnum);
	int (*write)(struct mii_bus *bus, int phy_id, int regnum, u16 val);
	int (*reset)(struct mii_bus *bus);

	/*
	 * A lock to ensure that only one thing can read/write
	 * the MDIO bus at a time
	 */
	struct mutex mdio_lock;

	struct device *parent;
	enum {
		MDIOBUS_ALLOCATED = 1,
		MDIOBUS_REGISTERED,
		MDIOBUS_UNREGISTERED,
		MDIOBUS_RELEASED,
	} state;
	struct device dev;

	/* list of all PHYs on bus */
	struct phy_device *phy_map[PHY_MAX_ADDR];

	/* PHY addresses to be ignored when probing */
	u32 phy_mask;

	/*
	 * Pointer to an array of interrupts, each PHY's
	 * interrupt at the index matching its address
	 */
	int *irq;
};
#define to_mii_bus(d) container_of(d, struct mii_bus, dev)


回归到刚刚的-19错误最终发现是phy_deviceNULL了,也就是在mdio bus上没有找到对应的phy_device,那么从这里我们可以猜想注册的时候是否根本就没注册进去呢?或者是注册成功了后,在某个阶段phy_device消失了?带着这个疑问我们就要看下以太网控制器加载的流程了。

2. 网址分享

http://stackoverflow.com/questions/5308090/set-ip-address-using-siocsifaddr-ioctl

http://www.ibm.com/support/knowledgecenter/ssw_aix_72/com.ibm.aix.commtrf2/ioctl_socket_control_operations.htm

https://lkml.org/lkml/2017/2/3/396

linux PHY驱动

http://www.latelee.org/programming-under-linux/linux-phy-driver.html

Linux PHY几个状态的跟踪

http://www.latelee.org/programming-under-linux/linux-phy-state.html

第十六章PHY -基于Linux3.10

https://blog.csdn.net/shichaog/article/details/44682931

评论:

16
2020-04-17 10:23
以前也遇到过,phy间歇性注册不上,看reset信号不太标准,原因是reset信号对应的一颗上拉电阻阻值贴错了,导致电压没有拉到足够高,导致reset可能成功可能失败

发表评论:

Copyright @ 2013-2015 蜗窝科技 All rights reserved. Powered by emlog