User Tools

Site Tools


phy_link_update

This is an old revision of the document!


Overview

Observations and proper use of the PHY link status handler.

Issues:

Explanation

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).

struct phy_device

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
$

Examples

freescale/fs_enet/fs_enet-main.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;
}

freescale/gianfar.c

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);
}

freescale/dpaa/dpaa_eth.c

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);
}
phy_link_update.1535457245.txt.gz · Last modified: 2018/08/28 11:54 by rpjday