ethtool: Add phy statistics
authorAndrew Lunn <andrew@lunn.ch>
Wed, 30 Dec 2015 15:28:25 +0000 (16:28 +0100)
committerDavid S. Miller <davem@davemloft.net>
Thu, 31 Dec 2015 05:53:10 +0000 (00:53 -0500)
Ethernet PHYs can maintain statistics, for example errors while idle
and receive errors. Add an ethtool mechanism to retrieve these
statistics, using the same model as MAC statistics.

Signed-off-by: Andrew Lunn <andrew@lunn.ch>
Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
include/linux/phy.h
include/uapi/linux/ethtool.h
net/core/ethtool.c

index 05fde31b6dc6dbe2f97356cea09a961dc1cd3af0..a89cb0eef911eaea6ebf037ed276f9138426bac3 100644 (file)
@@ -589,6 +589,12 @@ struct phy_driver {
        int (*module_eeprom)(struct phy_device *dev,
                             struct ethtool_eeprom *ee, u8 *data);
 
+       /* Get statistics from the phy using ethtool */
+       int (*get_sset_count)(struct phy_device *dev);
+       void (*get_strings)(struct phy_device *dev, u8 *data);
+       void (*get_stats)(struct phy_device *dev,
+                         struct ethtool_stats *stats, u64 *data);
+
        struct device_driver driver;
 };
 #define to_phy_driver(d) container_of(d, struct phy_driver, driver)
index cd1629170103ef77a580a3694472e4a1452613fe..57fa39005e794c65931f8e4cfd425d470693addd 100644 (file)
@@ -542,6 +542,7 @@ struct ethtool_pauseparam {
  *     now deprecated
  * @ETH_SS_FEATURES: Device feature names
  * @ETH_SS_RSS_HASH_FUNCS: RSS hush function names
+ * @ETH_SS_PHY_STATS: Statistic names, for use with %ETHTOOL_GPHYSTATS
  */
 enum ethtool_stringset {
        ETH_SS_TEST             = 0,
@@ -551,6 +552,7 @@ enum ethtool_stringset {
        ETH_SS_FEATURES,
        ETH_SS_RSS_HASH_FUNCS,
        ETH_SS_TUNABLES,
+       ETH_SS_PHY_STATS,
 };
 
 /**
@@ -1225,6 +1227,7 @@ enum ethtool_sfeatures_retval_bits {
 #define ETHTOOL_SRSSH          0x00000047 /* Set RX flow hash configuration */
 #define ETHTOOL_GTUNABLE       0x00000048 /* Get tunable configuration */
 #define ETHTOOL_STUNABLE       0x00000049 /* Set tunable configuration */
+#define ETHTOOL_GPHYSTATS      0x0000004a /* get PHY-specific statistics */
 
 /* compatibility with older code */
 #define SPARC_ETH_GSET         ETHTOOL_GSET
index 09948a726347cf06b31565a8d85cca1cdee590cf..daf04709dd3c695ff5c47f7d30c0f005397db00e 100644 (file)
@@ -191,6 +191,23 @@ static int ethtool_set_features(struct net_device *dev, void __user *useraddr)
        return ret;
 }
 
+static int phy_get_sset_count(struct phy_device *phydev)
+{
+       int ret;
+
+       if (phydev->drv->get_sset_count &&
+           phydev->drv->get_strings &&
+           phydev->drv->get_stats) {
+               mutex_lock(&phydev->lock);
+               ret = phydev->drv->get_sset_count(phydev);
+               mutex_unlock(&phydev->lock);
+
+               return ret;
+       }
+
+       return -EOPNOTSUPP;
+}
+
 static int __ethtool_get_sset_count(struct net_device *dev, int sset)
 {
        const struct ethtool_ops *ops = dev->ethtool_ops;
@@ -204,6 +221,13 @@ static int __ethtool_get_sset_count(struct net_device *dev, int sset)
        if (sset == ETH_SS_TUNABLES)
                return ARRAY_SIZE(tunable_strings);
 
+       if (sset == ETH_SS_PHY_STATS) {
+               if (dev->phydev)
+                       return phy_get_sset_count(dev->phydev);
+               else
+                       return -EOPNOTSUPP;
+       }
+
        if (ops->get_sset_count && ops->get_strings)
                return ops->get_sset_count(dev, sset);
        else
@@ -223,7 +247,17 @@ static void __ethtool_get_strings(struct net_device *dev,
                       sizeof(rss_hash_func_strings));
        else if (stringset == ETH_SS_TUNABLES)
                memcpy(data, tunable_strings, sizeof(tunable_strings));
-       else
+       else if (stringset == ETH_SS_PHY_STATS) {
+               struct phy_device *phydev = dev->phydev;
+
+               if (phydev) {
+                       mutex_lock(&phydev->lock);
+                       phydev->drv->get_strings(phydev, data);
+                       mutex_unlock(&phydev->lock);
+               } else {
+                       return;
+               }
+       } else
                /* ops->get_strings is valid because checked earlier */
                ops->get_strings(dev, stringset, data);
 }
@@ -1401,6 +1435,47 @@ static int ethtool_get_stats(struct net_device *dev, void __user *useraddr)
        return ret;
 }
 
+static int ethtool_get_phy_stats(struct net_device *dev, void __user *useraddr)
+{
+       struct ethtool_stats stats;
+       struct phy_device *phydev = dev->phydev;
+       u64 *data;
+       int ret, n_stats;
+
+       if (!phydev)
+               return -EOPNOTSUPP;
+
+       n_stats = phy_get_sset_count(phydev);
+
+       if (n_stats < 0)
+               return n_stats;
+       WARN_ON(n_stats == 0);
+
+       if (copy_from_user(&stats, useraddr, sizeof(stats)))
+               return -EFAULT;
+
+       stats.n_stats = n_stats;
+       data = kmalloc_array(n_stats, sizeof(u64), GFP_USER);
+       if (!data)
+               return -ENOMEM;
+
+       mutex_lock(&phydev->lock);
+       phydev->drv->get_stats(phydev, &stats, data);
+       mutex_unlock(&phydev->lock);
+
+       ret = -EFAULT;
+       if (copy_to_user(useraddr, &stats, sizeof(stats)))
+               goto out;
+       useraddr += sizeof(stats);
+       if (copy_to_user(useraddr, data, stats.n_stats * sizeof(u64)))
+               goto out;
+       ret = 0;
+
+ out:
+       kfree(data);
+       return ret;
+}
+
 static int ethtool_get_perm_addr(struct net_device *dev, void __user *useraddr)
 {
        struct ethtool_perm_addr epaddr;
@@ -1779,6 +1854,7 @@ int dev_ethtool(struct net *net, struct ifreq *ifr)
        case ETHTOOL_GSSET_INFO:
        case ETHTOOL_GSTRINGS:
        case ETHTOOL_GSTATS:
+       case ETHTOOL_GPHYSTATS:
        case ETHTOOL_GTSO:
        case ETHTOOL_GPERMADDR:
        case ETHTOOL_GUFO:
@@ -1991,6 +2067,9 @@ int dev_ethtool(struct net *net, struct ifreq *ifr)
        case ETHTOOL_STUNABLE:
                rc = ethtool_set_tunable(dev, useraddr);
                break;
+       case ETHTOOL_GPHYSTATS:
+               rc = ethtool_get_phy_stats(dev, useraddr);
+               break;
        default:
                rc = -EOPNOTSUPP;
        }