lan78xx: handle statistics counter rollover
authorWoojung Huh <woojung.huh@microchip.com>
Wed, 16 Mar 2016 22:10:40 +0000 (22:10 +0000)
committerDavid S. Miller <davem@davemloft.net>
Sat, 19 Mar 2016 02:27:48 +0000 (22:27 -0400)
Update to handle statistics counter rollover.
Check statistics counter periodically and compensate it when
counter value rolls over at max (20 or 32bits).

Simple mechanism adjusts monitoring timer to allow USB auto suspend.

Signed-off-by: Woojung Huh <woojung.huh@microchip.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
drivers/net/usb/lan78xx.c

index 705c180163c5c2d240ff3eade515c5c156efb230..f20890ee03f33368fd68c6b5fb82f8fd76fa4310 100644 (file)
@@ -36,7 +36,7 @@
 #define DRIVER_AUTHOR  "WOOJUNG HUH <woojung.huh@microchip.com>"
 #define DRIVER_DESC    "LAN78XX USB 3.0 Gigabit Ethernet Devices"
 #define DRIVER_NAME    "lan78xx"
-#define DRIVER_VERSION "1.0.3"
+#define DRIVER_VERSION "1.0.4"
 
 #define TX_TIMEOUT_JIFFIES             (5 * HZ)
 #define THROTTLE_JIFFIES               (HZ / 8)
@@ -86,6 +86,9 @@
 /* default autosuspend delay (mSec)*/
 #define DEFAULT_AUTOSUSPEND_DELAY      (10 * 1000)
 
+/* statistic update interval (mSec) */
+#define STAT_UPDATE_TIMER              (1 * 1000)
+
 static const char lan78xx_gstrings[][ETH_GSTRING_LEN] = {
        "RX FCS Errors",
        "RX Alignment Errors",
@@ -186,6 +189,56 @@ struct lan78xx_statstage {
        u32 eee_tx_lpi_time;
 };
 
+struct lan78xx_statstage64 {
+       u64 rx_fcs_errors;
+       u64 rx_alignment_errors;
+       u64 rx_fragment_errors;
+       u64 rx_jabber_errors;
+       u64 rx_undersize_frame_errors;
+       u64 rx_oversize_frame_errors;
+       u64 rx_dropped_frames;
+       u64 rx_unicast_byte_count;
+       u64 rx_broadcast_byte_count;
+       u64 rx_multicast_byte_count;
+       u64 rx_unicast_frames;
+       u64 rx_broadcast_frames;
+       u64 rx_multicast_frames;
+       u64 rx_pause_frames;
+       u64 rx_64_byte_frames;
+       u64 rx_65_127_byte_frames;
+       u64 rx_128_255_byte_frames;
+       u64 rx_256_511_bytes_frames;
+       u64 rx_512_1023_byte_frames;
+       u64 rx_1024_1518_byte_frames;
+       u64 rx_greater_1518_byte_frames;
+       u64 eee_rx_lpi_transitions;
+       u64 eee_rx_lpi_time;
+       u64 tx_fcs_errors;
+       u64 tx_excess_deferral_errors;
+       u64 tx_carrier_errors;
+       u64 tx_bad_byte_count;
+       u64 tx_single_collisions;
+       u64 tx_multiple_collisions;
+       u64 tx_excessive_collision;
+       u64 tx_late_collisions;
+       u64 tx_unicast_byte_count;
+       u64 tx_broadcast_byte_count;
+       u64 tx_multicast_byte_count;
+       u64 tx_unicast_frames;
+       u64 tx_broadcast_frames;
+       u64 tx_multicast_frames;
+       u64 tx_pause_frames;
+       u64 tx_64_byte_frames;
+       u64 tx_65_127_byte_frames;
+       u64 tx_128_255_byte_frames;
+       u64 tx_256_511_bytes_frames;
+       u64 tx_512_1023_byte_frames;
+       u64 tx_1024_1518_byte_frames;
+       u64 tx_greater_1518_byte_frames;
+       u64 eee_tx_lpi_transitions;
+       u64 eee_tx_lpi_time;
+};
+
 struct lan78xx_net;
 
 struct lan78xx_priv {
@@ -232,6 +285,15 @@ struct usb_context {
 #define EVENT_DEV_WAKING               6
 #define EVENT_DEV_ASLEEP               7
 #define EVENT_DEV_OPEN                 8
+#define EVENT_STAT_UPDATE              9
+
+struct statstage {
+       struct mutex                    access_lock;    /* for stats access */
+       struct lan78xx_statstage        saved;
+       struct lan78xx_statstage        rollover_count;
+       struct lan78xx_statstage        rollover_max;
+       struct lan78xx_statstage64      curr_stat;
+};
 
 struct lan78xx_net {
        struct net_device       *net;
@@ -272,6 +334,7 @@ struct lan78xx_net {
 
        unsigned                maxpacket;
        struct timer_list       delay;
+       struct timer_list       stat_monitor;
 
        unsigned long           data[5];
 
@@ -284,6 +347,9 @@ struct lan78xx_net {
 
        int                     fc_autoneg;
        u8                      fc_request_control;
+
+       int                     delta;
+       struct statstage        stats;
 };
 
 /* use ethtool to change the level for any given device */
@@ -382,6 +448,93 @@ static int lan78xx_read_stats(struct lan78xx_net *dev,
        return ret;
 }
 
+#define check_counter_rollover(struct1, dev_stats, member) {   \
+       if (struct1->member < dev_stats.saved.member)           \
+               dev_stats.rollover_count.member++;              \
+       }
+
+static void lan78xx_check_stat_rollover(struct lan78xx_net *dev,
+                                       struct lan78xx_statstage *stats)
+{
+       check_counter_rollover(stats, dev->stats, rx_fcs_errors);
+       check_counter_rollover(stats, dev->stats, rx_alignment_errors);
+       check_counter_rollover(stats, dev->stats, rx_fragment_errors);
+       check_counter_rollover(stats, dev->stats, rx_jabber_errors);
+       check_counter_rollover(stats, dev->stats, rx_undersize_frame_errors);
+       check_counter_rollover(stats, dev->stats, rx_oversize_frame_errors);
+       check_counter_rollover(stats, dev->stats, rx_dropped_frames);
+       check_counter_rollover(stats, dev->stats, rx_unicast_byte_count);
+       check_counter_rollover(stats, dev->stats, rx_broadcast_byte_count);
+       check_counter_rollover(stats, dev->stats, rx_multicast_byte_count);
+       check_counter_rollover(stats, dev->stats, rx_unicast_frames);
+       check_counter_rollover(stats, dev->stats, rx_broadcast_frames);
+       check_counter_rollover(stats, dev->stats, rx_multicast_frames);
+       check_counter_rollover(stats, dev->stats, rx_pause_frames);
+       check_counter_rollover(stats, dev->stats, rx_64_byte_frames);
+       check_counter_rollover(stats, dev->stats, rx_65_127_byte_frames);
+       check_counter_rollover(stats, dev->stats, rx_128_255_byte_frames);
+       check_counter_rollover(stats, dev->stats, rx_256_511_bytes_frames);
+       check_counter_rollover(stats, dev->stats, rx_512_1023_byte_frames);
+       check_counter_rollover(stats, dev->stats, rx_1024_1518_byte_frames);
+       check_counter_rollover(stats, dev->stats, rx_greater_1518_byte_frames);
+       check_counter_rollover(stats, dev->stats, eee_rx_lpi_transitions);
+       check_counter_rollover(stats, dev->stats, eee_rx_lpi_time);
+       check_counter_rollover(stats, dev->stats, tx_fcs_errors);
+       check_counter_rollover(stats, dev->stats, tx_excess_deferral_errors);
+       check_counter_rollover(stats, dev->stats, tx_carrier_errors);
+       check_counter_rollover(stats, dev->stats, tx_bad_byte_count);
+       check_counter_rollover(stats, dev->stats, tx_single_collisions);
+       check_counter_rollover(stats, dev->stats, tx_multiple_collisions);
+       check_counter_rollover(stats, dev->stats, tx_excessive_collision);
+       check_counter_rollover(stats, dev->stats, tx_late_collisions);
+       check_counter_rollover(stats, dev->stats, tx_unicast_byte_count);
+       check_counter_rollover(stats, dev->stats, tx_broadcast_byte_count);
+       check_counter_rollover(stats, dev->stats, tx_multicast_byte_count);
+       check_counter_rollover(stats, dev->stats, tx_unicast_frames);
+       check_counter_rollover(stats, dev->stats, tx_broadcast_frames);
+       check_counter_rollover(stats, dev->stats, tx_multicast_frames);
+       check_counter_rollover(stats, dev->stats, tx_pause_frames);
+       check_counter_rollover(stats, dev->stats, tx_64_byte_frames);
+       check_counter_rollover(stats, dev->stats, tx_65_127_byte_frames);
+       check_counter_rollover(stats, dev->stats, tx_128_255_byte_frames);
+       check_counter_rollover(stats, dev->stats, tx_256_511_bytes_frames);
+       check_counter_rollover(stats, dev->stats, tx_512_1023_byte_frames);
+       check_counter_rollover(stats, dev->stats, tx_1024_1518_byte_frames);
+       check_counter_rollover(stats, dev->stats, tx_greater_1518_byte_frames);
+       check_counter_rollover(stats, dev->stats, eee_tx_lpi_transitions);
+       check_counter_rollover(stats, dev->stats, eee_tx_lpi_time);
+
+       memcpy(&dev->stats.saved, stats, sizeof(struct lan78xx_statstage));
+}
+
+static void lan78xx_update_stats(struct lan78xx_net *dev)
+{
+       u32 *p, *count, *max;
+       u64 *data;
+       int i;
+       struct lan78xx_statstage lan78xx_stats;
+
+       if (usb_autopm_get_interface(dev->intf) < 0)
+               return;
+
+       p = (u32 *)&lan78xx_stats;
+       count = (u32 *)&dev->stats.rollover_count;
+       max = (u32 *)&dev->stats.rollover_max;
+       data = (u64 *)&dev->stats.curr_stat;
+
+       mutex_lock(&dev->stats.access_lock);
+
+       if (lan78xx_read_stats(dev, &lan78xx_stats) > 0)
+               lan78xx_check_stat_rollover(dev, &lan78xx_stats);
+
+       for (i = 0; i < (sizeof(lan78xx_stats) / (sizeof(u32))); i++)
+               data[i] = (u64)p[i] + ((u64)count[i] * ((u64)max[i] + 1));
+
+       mutex_unlock(&dev->stats.access_lock);
+
+       usb_autopm_put_interface(dev->intf);
+}
+
 /* Loop until the read is completed with timeout called with phy_mutex held */
 static int lan78xx_phy_wait_not_busy(struct lan78xx_net *dev)
 {
@@ -967,6 +1120,8 @@ static int lan78xx_link_reset(struct lan78xx_net *dev)
                        return -EIO;
 
                phy_mac_interrupt(phydev, 0);
+
+               del_timer(&dev->stat_monitor);
        } else if (phydev->link && !dev->link_on) {
                dev->link_on = true;
 
@@ -1007,6 +1162,12 @@ static int lan78xx_link_reset(struct lan78xx_net *dev)
 
                ret = lan78xx_update_flowcontrol(dev, ecmd.duplex, ladv, radv);
                phy_mac_interrupt(phydev, 1);
+
+               if (!timer_pending(&dev->stat_monitor)) {
+                       dev->delta = 1;
+                       mod_timer(&dev->stat_monitor,
+                                 jiffies + STAT_UPDATE_TIMER);
+               }
        }
 
        return ret;
@@ -1099,20 +1260,12 @@ static void lan78xx_get_stats(struct net_device *netdev,
                              struct ethtool_stats *stats, u64 *data)
 {
        struct lan78xx_net *dev = netdev_priv(netdev);
-       struct lan78xx_statstage lan78xx_stat;
-       u32 *p;
-       int i;
 
-       if (usb_autopm_get_interface(dev->intf) < 0)
-               return;
+       lan78xx_update_stats(dev);
 
-       if (lan78xx_read_stats(dev, &lan78xx_stat) > 0) {
-               p = (u32 *)&lan78xx_stat;
-               for (i = 0; i < (sizeof(lan78xx_stat) / (sizeof(u32))); i++)
-                       data[i] = p[i];
-       }
-
-       usb_autopm_put_interface(dev->intf);
+       mutex_lock(&dev->stats.access_lock);
+       memcpy(data, &dev->stats.curr_stat, sizeof(dev->stats.curr_stat));
+       mutex_unlock(&dev->stats.access_lock);
 }
 
 static void lan78xx_get_wol(struct net_device *netdev,
@@ -2095,6 +2248,32 @@ static int lan78xx_reset(struct lan78xx_net *dev)
        return 0;
 }
 
+static void lan78xx_init_stats(struct lan78xx_net *dev)
+{
+       u32 *p;
+       int i;
+
+       /* initialize for stats update
+        * some counters are 20bits and some are 32bits
+        */
+       p = (u32 *)&dev->stats.rollover_max;
+       for (i = 0; i < (sizeof(dev->stats.rollover_max) / (sizeof(u32))); i++)
+               p[i] = 0xFFFFF;
+
+       dev->stats.rollover_max.rx_unicast_byte_count = 0xFFFFFFFF;
+       dev->stats.rollover_max.rx_broadcast_byte_count = 0xFFFFFFFF;
+       dev->stats.rollover_max.rx_multicast_byte_count = 0xFFFFFFFF;
+       dev->stats.rollover_max.eee_rx_lpi_transitions = 0xFFFFFFFF;
+       dev->stats.rollover_max.eee_rx_lpi_time = 0xFFFFFFFF;
+       dev->stats.rollover_max.tx_unicast_byte_count = 0xFFFFFFFF;
+       dev->stats.rollover_max.tx_broadcast_byte_count = 0xFFFFFFFF;
+       dev->stats.rollover_max.tx_multicast_byte_count = 0xFFFFFFFF;
+       dev->stats.rollover_max.eee_tx_lpi_transitions = 0xFFFFFFFF;
+       dev->stats.rollover_max.eee_tx_lpi_time = 0xFFFFFFFF;
+
+       lan78xx_defer_kevent(dev, EVENT_STAT_UPDATE);
+}
+
 static int lan78xx_open(struct net_device *net)
 {
        struct lan78xx_net *dev = netdev_priv(net);
@@ -2122,6 +2301,8 @@ static int lan78xx_open(struct net_device *net)
                }
        }
 
+       lan78xx_init_stats(dev);
+
        set_bit(EVENT_DEV_OPEN, &dev->flags);
 
        netif_start_queue(net);
@@ -2166,6 +2347,9 @@ int lan78xx_stop(struct net_device *net)
 {
        struct lan78xx_net              *dev = netdev_priv(net);
 
+       if (timer_pending(&dev->stat_monitor))
+               del_timer_sync(&dev->stat_monitor);
+
        phy_stop(net->phydev);
        phy_disconnect(net->phydev);
        net->phydev = NULL;
@@ -2910,6 +3094,13 @@ static void lan78xx_bh(unsigned long param)
        }
 
        if (netif_device_present(dev->net) && netif_running(dev->net)) {
+               /* reset update timer delta */
+               if (timer_pending(&dev->stat_monitor) && (dev->delta != 1)) {
+                       dev->delta = 1;
+                       mod_timer(&dev->stat_monitor,
+                                 jiffies + STAT_UPDATE_TIMER);
+               }
+
                if (!skb_queue_empty(&dev->txq_pend))
                        lan78xx_tx_bh(dev);
 
@@ -2984,6 +3175,17 @@ skip_reset:
                        usb_autopm_put_interface(dev->intf);
                }
        }
+
+       if (test_bit(EVENT_STAT_UPDATE, &dev->flags)) {
+               lan78xx_update_stats(dev);
+
+               clear_bit(EVENT_STAT_UPDATE, &dev->flags);
+
+               mod_timer(&dev->stat_monitor,
+                         jiffies + (STAT_UPDATE_TIMER * dev->delta));
+
+               dev->delta = min((dev->delta * 2), 50);
+       }
 }
 
 static void intr_complete(struct urb *urb)
@@ -3074,6 +3276,15 @@ static const struct net_device_ops lan78xx_netdev_ops = {
        .ndo_vlan_rx_kill_vid   = lan78xx_vlan_rx_kill_vid,
 };
 
+static void lan78xx_stat_monitor(unsigned long param)
+{
+       struct lan78xx_net *dev;
+
+       dev = (struct lan78xx_net *)param;
+
+       lan78xx_defer_kevent(dev, EVENT_STAT_UPDATE);
+}
+
 static int lan78xx_probe(struct usb_interface *intf,
                         const struct usb_device_id *id)
 {
@@ -3120,6 +3331,13 @@ static int lan78xx_probe(struct usb_interface *intf,
        netdev->watchdog_timeo = TX_TIMEOUT_JIFFIES;
        netdev->ethtool_ops = &lan78xx_ethtool_ops;
 
+       dev->stat_monitor.function = lan78xx_stat_monitor;
+       dev->stat_monitor.data = (unsigned long)dev;
+       dev->delta = 1;
+       init_timer(&dev->stat_monitor);
+
+       mutex_init(&dev->stats.access_lock);
+
        ret = lan78xx_bind(dev, intf);
        if (ret < 0)
                goto out2;
@@ -3397,6 +3615,8 @@ int lan78xx_suspend(struct usb_interface *intf, pm_message_t message)
        }
 
        if (test_bit(EVENT_DEV_ASLEEP, &dev->flags)) {
+               del_timer(&dev->stat_monitor);
+
                if (PMSG_IS_AUTO(message)) {
                        /* auto suspend (selective suspend) */
                        ret = lan78xx_read_reg(dev, MAC_TX, &buf);
@@ -3457,6 +3677,12 @@ int lan78xx_resume(struct usb_interface *intf)
        int ret;
        u32 buf;
 
+       if (!timer_pending(&dev->stat_monitor)) {
+               dev->delta = 1;
+               mod_timer(&dev->stat_monitor,
+                         jiffies + STAT_UPDATE_TIMER);
+       }
+
        if (!--dev->suspend_count) {
                /* resume interrupt URBs */
                if (dev->urb_intr && test_bit(EVENT_DEV_OPEN, &dev->flags))