以太网驱动的流程浅析(三)-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_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的信号线如下图所示:
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的信号线如下图所示:
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
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

评论:
功能
最新评论
- wangjing
写得太好了 - wangjing
写得太好了! - DRAM
圖面都沒辦法顯示出來好像掛點了。 - Simbr
bus至少是不是还有个subsystem? - troy
@testtest:只要ldrex-modify-strex... - gh
Linux 内核在 sparse 内存模型基础上实现了vme...
文章分类
随机文章
文章存档
- 2025年4月(5)
- 2024年2月(1)
- 2023年5月(1)
- 2022年10月(1)
- 2022年8月(1)
- 2022年6月(1)
- 2022年5月(1)
- 2022年4月(2)
- 2022年2月(2)
- 2021年12月(1)
- 2021年11月(5)
- 2021年7月(1)
- 2021年6月(1)
- 2021年5月(3)
- 2020年3月(3)
- 2020年2月(2)
- 2020年1月(3)
- 2019年12月(3)
- 2019年5月(4)
- 2019年3月(1)
- 2019年1月(3)
- 2018年12月(2)
- 2018年11月(1)
- 2018年10月(2)
- 2018年8月(1)
- 2018年6月(1)
- 2018年5月(1)
- 2018年4月(7)
- 2018年2月(4)
- 2018年1月(5)
- 2017年12月(2)
- 2017年11月(2)
- 2017年10月(1)
- 2017年9月(5)
- 2017年8月(4)
- 2017年7月(4)
- 2017年6月(3)
- 2017年5月(3)
- 2017年4月(1)
- 2017年3月(8)
- 2017年2月(6)
- 2017年1月(5)
- 2016年12月(6)
- 2016年11月(11)
- 2016年10月(9)
- 2016年9月(6)
- 2016年8月(9)
- 2016年7月(5)
- 2016年6月(8)
- 2016年5月(8)
- 2016年4月(7)
- 2016年3月(5)
- 2016年2月(5)
- 2016年1月(6)
- 2015年12月(6)
- 2015年11月(9)
- 2015年10月(9)
- 2015年9月(4)
- 2015年8月(3)
- 2015年7月(7)
- 2015年6月(3)
- 2015年5月(6)
- 2015年4月(9)
- 2015年3月(9)
- 2015年2月(6)
- 2015年1月(6)
- 2014年12月(17)
- 2014年11月(8)
- 2014年10月(9)
- 2014年9月(7)
- 2014年8月(12)
- 2014年7月(6)
- 2014年6月(6)
- 2014年5月(9)
- 2014年4月(9)
- 2014年3月(7)
- 2014年2月(3)
- 2014年1月(4)
2020-04-17 10:23