User Tools

Site Tools


phy_link_update

Differences

This shows you the differences between two versions of the page.

Link to this comparison view

Next revision
Previous revision
phy_link_update [2018/08/27 21:02]
rpjday created
phy_link_update [2018/08/28 11:57] (current)
rpjday [freescale/ucc_geth.c]
Line 2: Line 2:
  
 Observations and proper use of the PHY link status handler. Observations and proper use of the PHY link status handler.
 +
 +Issues:
 +
 +  * Is [[https://​elixir.bootlin.com/​linux/​latest/​ident/​phy_link_change|phy_link_change()]] relevant? Don't think so.
 +
 +===== Explanation =====
 +
 +From [[https://​elixir.bootlin.com/​linux/​latest/​source/​Documentation/​networking/​phy.txt|here]]:​
 +
 +<​code>​
 +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
 +</​code>​
 +
 +Scenario 1):
 +
 +<​code>​
 +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).
 +</​code>​
 +
 +===== struct phy_device =====
 +
 +<​code>​
 +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;
 +};
 +</​code>​
 +
 +Printing:
 +
 +<​code>​
 +netdev_info(ndev,​ "Link %s, speed %d, %s\n",
 +                  phy->​link ​ ? "​up"​ : "​down",​
 +                  phy->​speed,​
 +                  phy->​duplex ? "​duplex"​ : "​simplex"​);​
 +
 +</​code>​
 +
 +===== How many drivers use adjust_link()?​ =====
 +
 +<​code>​
 +$ 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
 +$
 +</​code>​
 +
 +===== Examples =====
 +
 +==== freescale/​fs_enet/​fs_enet-main.c ====
 +
 +<​code>​
 +/​*-----------------------------------------------------------------------------
 + ​* ​ 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;
 +}
 +</​code>​
 +
 +==== freescale/​gianfar.c ====
 +
 +<​code>​
 +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);​
 +}
 +</​code>​
 +
 +==== freescale/​dpaa/​dpaa_eth.c ====
 +
 +<​code>​
 +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);​
 +}
 +</​code>​
 +
 +==== freescale/​ucc_geth.c ====
 +
 +<​code>​
 +/* 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) { ...
 +</​code>​
 +
 +==== hisilicon/​hns/​hns_enet.c ====
 +
 +<​code>​
 +/**
 + ​*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;
 +        }
 +}
 +</​code>​
phy_link_update.1535403746.txt.gz ยท Last modified: 2018/08/27 21:02 by rpjday