Skip to content

Commit

Permalink
ethtool: Add phy statistics
Browse files Browse the repository at this point in the history
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>
  • Loading branch information
Andrew Lunn authored and David S. Miller committed Dec 31, 2015
1 parent a3748a9 commit f3a4094
Show file tree
Hide file tree
Showing 3 changed files with 89 additions and 1 deletion.
6 changes: 6 additions & 0 deletions include/linux/phy.h
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
3 changes: 3 additions & 0 deletions include/uapi/linux/ethtool.h
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -551,6 +552,7 @@ enum ethtool_stringset {
ETH_SS_FEATURES,
ETH_SS_RSS_HASH_FUNCS,
ETH_SS_TUNABLES,
ETH_SS_PHY_STATS,
};

/**
Expand Down Expand Up @@ -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
Expand Down
81 changes: 80 additions & 1 deletion net/core/ethtool.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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
Expand All @@ -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);
}
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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;
}
Expand Down

0 comments on commit f3a4094

Please sign in to comment.