蜗窝科技

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

heaven · · Linux内核分析 · 阅读 16880 · 2019年12月存档

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

【硬件环境】 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_dev是NULL

那为什么呢?我们跟进去看下,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定义的以太网行业标准。它包括一个数据接口,以及一个MAC和PHY之间的管理接口。RMII全称为“简化的媒体独立接口”,是IEEE-802.3u标准中除MII接口之外的另一种实现。(此处内容来源于网络)

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

独立于介质的接口(MII)用于MAC与外接的PHY互联,支持10Mbit/s和100Mbit/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_read与mdiobus_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_device为NULL了,也就是在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

继续讨论

新讨论请用 GitHub 账号在下方发言——评论会保存在本仓库的 Discussions 里,与站点同寿; 再下方的「历史评论」是原站的只读存档。

评论(1 条,只读存档)

以下为原站的历史评论(只读存档);继续讨论请用上方的评论区。

  • 16

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