Observations and proper use of the PHY link status handler.
Issues:
From here:
Connecting to a PHY Sometime during startup, the network driver needs to establish a connection between the PHY device, and the network device. At this time, the PHY's bus and drivers need to all have been loaded, so it is ready for the connection. At this point, there are several ways to connect to the PHY: 1) The PAL handles everything, and only calls the network driver when the link state changes, so it can react. 2) The PAL handles everything except interrupts (usually because the controller has the interrupt registers). 3) The PAL handles everything, but checks in with the driver every second, allowing the network driver to react first to any changes before the PAL does. 4) The PAL serves only as a library of functions, with the network device manually calling functions to update status, and configure the PHY
Scenario 1):
Letting the PHY Abstraction Layer do Everything If you choose option 1 (The hope is that every driver can, but to still be useful to drivers that can't), connecting to the PHY is simple: First, you need a function to react to changes in the link state. This function follows this protocol: static void adjust_link(struct net_device *dev); Next, you need to know the device name of the PHY connected to this device. The name will look something like, "0:00", where the first number is the bus id, and the second is the PHY's address on that bus. Typically, the bus is responsible for making its ID unique. Now, to connect, just call this function: phydev = phy_connect(dev, phy_name, &adjust_link, interface); phydev is a pointer to the phy_device structure which represents the PHY. If phy_connect is successful, it will return the pointer. dev, here, is the pointer to your net_device. Once done, this function will have started the PHY's software state machine, and registered for the PHY's interrupt, if it has one. The phydev structure will be populated with information about the current state, though the PHY will not yet be truly operational at this point. PHY-specific flags should be set in phydev->dev_flags prior to the call to phy_connect() such that the underlying PHY driver can check for flags and perform specific operations based on them. This is useful if the system has put hardware restrictions on the PHY/controller, of which the PHY needs to be aware. interface is a u32 which specifies the connection type used between the controller and the PHY. Examples are GMII, MII, RGMII, and SGMII. For a full list, see include/linux/phy.h Now just make sure that phydev->supported and phydev->advertising have any values pruned from them which don't make sense for your controller (a 10/100 controller may be connected to a gigabit capable PHY, so you would need to mask off SUPPORTED_1000baseT*). See include/linux/ethtool.h for definitions for these bitfields. Note that you should not SET any bits, except the SUPPORTED_Pause and SUPPORTED_AsymPause bits (see below), or the PHY may get put into an unsupported state. Lastly, once the controller is ready to handle network traffic, you call phy_start(phydev). This tells the PAL that you are ready, and configures the PHY to connect to the network. If you want to handle your own interrupts, just set phydev->irq to PHY_IGNORE_INTERRUPT before you call phy_start. Similarly, if you don't want to use interrupts, set phydev->irq to PHY_POLL. When you want to disconnect from the network (even if just briefly), you call phy_stop(phydev).
enum phy_state { PHY_DOWN = 0, PHY_STARTING, PHY_READY, PHY_PENDING, PHY_UP, PHY_AN, PHY_RUNNING, PHY_NOLINK, PHY_FORCING, PHY_CHANGELINK, PHY_HALTED, PHY_RESUMING }; ... struct phy_device { unsigned link:1; // most recently read link state [01] enum phy_state state; int speed; int duplex; };
Printing:
netdev_info(ndev, "Link %s, speed %d, %s\n", phy->link ? "up" : "down", phy->speed, phy->duplex ? "duplex" : "simplex");
$ grep -rwl adjust_link * amd/xgbe/xgbe-mdio.c freescale/fs_enet/fs_enet.h freescale/fs_enet/fs_enet-main.c freescale/gianfar.c freescale/dpaa/dpaa_ethtool.c freescale/dpaa/dpaa_eth.c freescale/fman/mac.c freescale/fman/mac.h freescale/ucc_geth.c hisilicon/hns3/hnae3.h hisilicon/hns/hns_ethtool.c hisilicon/hns/hns_dsaf_xgmac.c hisilicon/hns/hns_dsaf_gmac.c hisilicon/hns/hns_ae_adapt.c hisilicon/hns/hns_dsaf_mac.h hisilicon/hns/hnae.c hisilicon/hns/hns_enet.c hisilicon/hns/hnae.h hisilicon/hns/hns_dsaf_mac.c qualcomm/emac/emac-mac.c $
/*----------------------------------------------------------------------------- * generic link-change handler - should be sufficient for most cases *-----------------------------------------------------------------------------*/ static void generic_adjust_link(struct net_device *dev) { struct fs_enet_private *fep = netdev_priv(dev); struct phy_device *phydev = dev->phydev; int new_state = 0; if (phydev->link) { /* adjust to duplex mode */ if (phydev->duplex != fep->oldduplex) { new_state = 1; fep->oldduplex = phydev->duplex; } if (phydev->speed != fep->oldspeed) { new_state = 1; fep->oldspeed = phydev->speed; } if (!fep->oldlink) { new_state = 1; fep->oldlink = 1; } if (new_state) fep->ops->restart(dev); } else if (fep->oldlink) { new_state = 1; fep->oldlink = 0; fep->oldspeed = 0; fep->oldduplex = -1; } if (new_state && netif_msg_link(fep)) phy_print_status(phydev); } static void fs_adjust_link(struct net_device *dev) { struct fs_enet_private *fep = netdev_priv(dev); unsigned long flags; spin_lock_irqsave(&fep->lock, flags); if(fep->ops->adjust_link) fep->ops->adjust_link(dev); else generic_adjust_link(dev); spin_unlock_irqrestore(&fep->lock, flags); } static int fs_init_phy(struct net_device *dev) { struct fs_enet_private *fep = netdev_priv(dev); struct phy_device *phydev; phy_interface_t iface; fep->oldlink = 0; fep->oldspeed = 0; fep->oldduplex = -1; iface = fep->fpi->use_rmii ? PHY_INTERFACE_MODE_RMII : PHY_INTERFACE_MODE_MII; phydev = of_phy_connect(dev, fep->fpi->phy_node, &fs_adjust_link, 0, iface); if (!phydev) { dev_err(&dev->dev, "Could not attach to PHY\n"); return -ENODEV; } return 0; }
static void adjust_link(struct net_device *dev) { struct gfar_private *priv = netdev_priv(dev); struct phy_device *phydev = dev->phydev; if (unlikely(phydev->link != priv->oldlink || (phydev->link && (phydev->duplex != priv->oldduplex || phydev->speed != priv->oldspeed)))) gfar_update_link_state(priv); }
static void dpaa_adjust_link(struct net_device *net_dev) { struct mac_device *mac_dev; struct dpaa_priv *priv; priv = netdev_priv(net_dev); mac_dev = priv->mac_dev; mac_dev->adjust_link(mac_dev); }
/* Called every time the controller might need to be made * aware of new link state. The PHY code conveys this * information through variables in the ugeth structure, and this * function converts those variables into the appropriate * register values, and can bring down the device if needed. */ static void adjust_link(struct net_device *dev) { struct ucc_geth_private *ugeth = netdev_priv(dev); struct ucc_geth __iomem *ug_regs; struct ucc_fast __iomem *uf_regs; struct phy_device *phydev = ugeth->phydev; int new_state = 0; ug_regs = ugeth->ug_regs; uf_regs = ugeth->uccf->uf_regs; if (phydev->link) { ...
/** *hns_nic_adjust_link - adjust net work mode by the phy stat or new param *@ndev: net device */ static void hns_nic_adjust_link(struct net_device *ndev) { struct hns_nic_priv *priv = netdev_priv(ndev); struct hnae_handle *h = priv->ae_handle; int state = 1; if (ndev->phydev) { h->dev->ops->adjust_link(h, ndev->phydev->speed, ndev->phydev->duplex); state = ndev->phydev->link; } state = state && h->dev->ops->get_status(h); if (state != priv->link) { if (state) { netif_carrier_on(ndev); netif_tx_wake_all_queues(ndev); netdev_info(ndev, "link up\n"); } else { netif_carrier_off(ndev); netdev_info(ndev, "link down\n"); } priv->link = state; } }