sh_eth: Mitigate lost statistics updates

The statistics registers have write-clear behaviour, which means we
will lose any increment between the read and write.  Mitigate this by
only clearing when we read a non-zero value, so we will never falsely
report a total of zero.  This also saves time as we only handle
error statistics here and they won't often be incremented.

Signed-off-by: Ben Hutchings <ben.hutchings@codethink.co.uk>
Signed-off-by: David S. Miller <davem@davemloft.net>
This commit is contained in:
Ben Hutchings 2015-02-26 20:35:05 +00:00 committed by David S. Miller
parent e5fd13f476
commit 4398f9c817
1 changed files with 25 additions and 12 deletions

View File

@ -2417,6 +2417,22 @@ static int sh_eth_start_xmit(struct sk_buff *skb, struct net_device *ndev)
return NETDEV_TX_OK; return NETDEV_TX_OK;
} }
/* The statistics registers have write-clear behaviour, which means we
* will lose any increment between the read and write. We mitigate
* this by only clearing when we read a non-zero value, so we will
* never falsely report a total of zero.
*/
static void
sh_eth_update_stat(struct net_device *ndev, unsigned long *stat, int reg)
{
u32 delta = sh_eth_read(ndev, reg);
if (delta) {
*stat += delta;
sh_eth_write(ndev, 0, reg);
}
}
static struct net_device_stats *sh_eth_get_stats(struct net_device *ndev) static struct net_device_stats *sh_eth_get_stats(struct net_device *ndev)
{ {
struct sh_eth_private *mdp = netdev_priv(ndev); struct sh_eth_private *mdp = netdev_priv(ndev);
@ -2427,21 +2443,18 @@ static struct net_device_stats *sh_eth_get_stats(struct net_device *ndev)
if (!mdp->is_opened) if (!mdp->is_opened)
return &ndev->stats; return &ndev->stats;
ndev->stats.tx_dropped += sh_eth_read(ndev, TROCR); sh_eth_update_stat(ndev, &ndev->stats.tx_dropped, TROCR);
sh_eth_write(ndev, 0, TROCR); /* (write clear) */ sh_eth_update_stat(ndev, &ndev->stats.collisions, CDCR);
ndev->stats.collisions += sh_eth_read(ndev, CDCR); sh_eth_update_stat(ndev, &ndev->stats.tx_carrier_errors, LCCR);
sh_eth_write(ndev, 0, CDCR); /* (write clear) */
ndev->stats.tx_carrier_errors += sh_eth_read(ndev, LCCR);
sh_eth_write(ndev, 0, LCCR); /* (write clear) */
if (sh_eth_is_gether(mdp)) { if (sh_eth_is_gether(mdp)) {
ndev->stats.tx_carrier_errors += sh_eth_read(ndev, CERCR); sh_eth_update_stat(ndev, &ndev->stats.tx_carrier_errors,
sh_eth_write(ndev, 0, CERCR); /* (write clear) */ CERCR);
ndev->stats.tx_carrier_errors += sh_eth_read(ndev, CEECR); sh_eth_update_stat(ndev, &ndev->stats.tx_carrier_errors,
sh_eth_write(ndev, 0, CEECR); /* (write clear) */ CEECR);
} else { } else {
ndev->stats.tx_carrier_errors += sh_eth_read(ndev, CNDCR); sh_eth_update_stat(ndev, &ndev->stats.tx_carrier_errors,
sh_eth_write(ndev, 0, CNDCR); /* (write clear) */ CNDCR);
} }
return &ndev->stats; return &ndev->stats;