drivers: net: cpsw: add support to show hw stats via ethtool
authorMugunthan V N <mugunthanvnm@ti.com>
Tue, 23 Jul 2013 10:08:17 +0000 (15:38 +0530)
committerDavid S. Miller <davem@davemloft.net>
Thu, 25 Jul 2013 00:52:32 +0000 (17:52 -0700)
Add support to show CPSW hardware statistics to user via ethtool
so user can find if there were any error reported by hardware or
the system is over loaded duing high data rate transfer.

Signed-off-by: Mugunthan V N <mugunthanvnm@ti.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
drivers/net/ethernet/ti/cpsw.c

index 05a1674e204f772f84eb3d4ff70633942ba7c256..1cd1c00c089f53ab48b4f6b4e3775b493fb12551 100644 (file)
@@ -91,6 +91,7 @@ do {                                                          \
 #define CPSW1_SLAVE_SIZE       0x040
 #define CPSW1_CPDMA_OFFSET     0x100
 #define CPSW1_STATERAM_OFFSET  0x200
+#define CPSW1_HW_STATS         0x400
 #define CPSW1_CPTS_OFFSET      0x500
 #define CPSW1_ALE_OFFSET       0x600
 #define CPSW1_SLIVER_OFFSET    0x700
@@ -99,6 +100,7 @@ do {                                                         \
 #define CPSW2_SLAVE_OFFSET     0x200
 #define CPSW2_SLAVE_SIZE       0x100
 #define CPSW2_CPDMA_OFFSET     0x800
+#define CPSW2_HW_STATS         0x900
 #define CPSW2_STATERAM_OFFSET  0xa00
 #define CPSW2_CPTS_OFFSET      0xc00
 #define CPSW2_ALE_OFFSET       0xd00
@@ -299,6 +301,44 @@ struct cpsw_sliver_regs {
        u32     rx_pri_map;
 };
 
+struct cpsw_hw_stats {
+       u32     rxgoodframes;
+       u32     rxbroadcastframes;
+       u32     rxmulticastframes;
+       u32     rxpauseframes;
+       u32     rxcrcerrors;
+       u32     rxaligncodeerrors;
+       u32     rxoversizedframes;
+       u32     rxjabberframes;
+       u32     rxundersizedframes;
+       u32     rxfragments;
+       u32     __pad_0[2];
+       u32     rxoctets;
+       u32     txgoodframes;
+       u32     txbroadcastframes;
+       u32     txmulticastframes;
+       u32     txpauseframes;
+       u32     txdeferredframes;
+       u32     txcollisionframes;
+       u32     txsinglecollframes;
+       u32     txmultcollframes;
+       u32     txexcessivecollisions;
+       u32     txlatecollisions;
+       u32     txunderrun;
+       u32     txcarriersenseerrors;
+       u32     txoctets;
+       u32     octetframes64;
+       u32     octetframes65t127;
+       u32     octetframes128t255;
+       u32     octetframes256t511;
+       u32     octetframes512t1023;
+       u32     octetframes1024tup;
+       u32     netoctets;
+       u32     rxsofoverruns;
+       u32     rxmofoverruns;
+       u32     rxdmaoverruns;
+};
+
 struct cpsw_slave {
        void __iomem                    *regs;
        struct cpsw_sliver_regs __iomem *sliver;
@@ -332,6 +372,7 @@ struct cpsw_priv {
        struct cpsw_platform_data       data;
        struct cpsw_ss_regs __iomem     *regs;
        struct cpsw_wr_regs __iomem     *wr_regs;
+       u8 __iomem                      *hw_stats;
        struct cpsw_host_regs __iomem   *host_port_regs;
        u32                             msg_enable;
        u32                             version;
@@ -354,6 +395,94 @@ struct cpsw_priv {
        u32 emac_port;
 };
 
+struct cpsw_stats {
+       char stat_string[ETH_GSTRING_LEN];
+       int type;
+       int sizeof_stat;
+       int stat_offset;
+};
+
+enum {
+       CPSW_STATS,
+       CPDMA_RX_STATS,
+       CPDMA_TX_STATS,
+};
+
+#define CPSW_STAT(m)           CPSW_STATS,                             \
+                               sizeof(((struct cpsw_hw_stats *)0)->m), \
+                               offsetof(struct cpsw_hw_stats, m)
+#define CPDMA_RX_STAT(m)       CPDMA_RX_STATS,                            \
+                               sizeof(((struct cpdma_chan_stats *)0)->m), \
+                               offsetof(struct cpdma_chan_stats, m)
+#define CPDMA_TX_STAT(m)       CPDMA_TX_STATS,                            \
+                               sizeof(((struct cpdma_chan_stats *)0)->m), \
+                               offsetof(struct cpdma_chan_stats, m)
+
+static const struct cpsw_stats cpsw_gstrings_stats[] = {
+       { "Good Rx Frames", CPSW_STAT(rxgoodframes) },
+       { "Broadcast Rx Frames", CPSW_STAT(rxbroadcastframes) },
+       { "Multicast Rx Frames", CPSW_STAT(rxmulticastframes) },
+       { "Pause Rx Frames", CPSW_STAT(rxpauseframes) },
+       { "Rx CRC Errors", CPSW_STAT(rxcrcerrors) },
+       { "Rx Align/Code Errors", CPSW_STAT(rxaligncodeerrors) },
+       { "Oversize Rx Frames", CPSW_STAT(rxoversizedframes) },
+       { "Rx Jabbers", CPSW_STAT(rxjabberframes) },
+       { "Undersize (Short) Rx Frames", CPSW_STAT(rxundersizedframes) },
+       { "Rx Fragments", CPSW_STAT(rxfragments) },
+       { "Rx Octets", CPSW_STAT(rxoctets) },
+       { "Good Tx Frames", CPSW_STAT(txgoodframes) },
+       { "Broadcast Tx Frames", CPSW_STAT(txbroadcastframes) },
+       { "Multicast Tx Frames", CPSW_STAT(txmulticastframes) },
+       { "Pause Tx Frames", CPSW_STAT(txpauseframes) },
+       { "Deferred Tx Frames", CPSW_STAT(txdeferredframes) },
+       { "Collisions", CPSW_STAT(txcollisionframes) },
+       { "Single Collision Tx Frames", CPSW_STAT(txsinglecollframes) },
+       { "Multiple Collision Tx Frames", CPSW_STAT(txmultcollframes) },
+       { "Excessive Collisions", CPSW_STAT(txexcessivecollisions) },
+       { "Late Collisions", CPSW_STAT(txlatecollisions) },
+       { "Tx Underrun", CPSW_STAT(txunderrun) },
+       { "Carrier Sense Errors", CPSW_STAT(txcarriersenseerrors) },
+       { "Tx Octets", CPSW_STAT(txoctets) },
+       { "Rx + Tx 64 Octet Frames", CPSW_STAT(octetframes64) },
+       { "Rx + Tx 65-127 Octet Frames", CPSW_STAT(octetframes65t127) },
+       { "Rx + Tx 128-255 Octet Frames", CPSW_STAT(octetframes128t255) },
+       { "Rx + Tx 256-511 Octet Frames", CPSW_STAT(octetframes256t511) },
+       { "Rx + Tx 512-1023 Octet Frames", CPSW_STAT(octetframes512t1023) },
+       { "Rx + Tx 1024-Up Octet Frames", CPSW_STAT(octetframes1024tup) },
+       { "Net Octets", CPSW_STAT(netoctets) },
+       { "Rx Start of Frame Overruns", CPSW_STAT(rxsofoverruns) },
+       { "Rx Middle of Frame Overruns", CPSW_STAT(rxmofoverruns) },
+       { "Rx DMA Overruns", CPSW_STAT(rxdmaoverruns) },
+       { "Rx DMA chan: head_enqueue", CPDMA_RX_STAT(head_enqueue) },
+       { "Rx DMA chan: tail_enqueue", CPDMA_RX_STAT(tail_enqueue) },
+       { "Rx DMA chan: pad_enqueue", CPDMA_RX_STAT(pad_enqueue) },
+       { "Rx DMA chan: misqueued", CPDMA_RX_STAT(misqueued) },
+       { "Rx DMA chan: desc_alloc_fail", CPDMA_RX_STAT(desc_alloc_fail) },
+       { "Rx DMA chan: pad_alloc_fail", CPDMA_RX_STAT(pad_alloc_fail) },
+       { "Rx DMA chan: runt_receive_buf", CPDMA_RX_STAT(runt_receive_buff) },
+       { "Rx DMA chan: runt_transmit_buf", CPDMA_RX_STAT(runt_transmit_buff) },
+       { "Rx DMA chan: empty_dequeue", CPDMA_RX_STAT(empty_dequeue) },
+       { "Rx DMA chan: busy_dequeue", CPDMA_RX_STAT(busy_dequeue) },
+       { "Rx DMA chan: good_dequeue", CPDMA_RX_STAT(good_dequeue) },
+       { "Rx DMA chan: requeue", CPDMA_RX_STAT(requeue) },
+       { "Rx DMA chan: teardown_dequeue", CPDMA_RX_STAT(teardown_dequeue) },
+       { "Tx DMA chan: head_enqueue", CPDMA_TX_STAT(head_enqueue) },
+       { "Tx DMA chan: tail_enqueue", CPDMA_TX_STAT(tail_enqueue) },
+       { "Tx DMA chan: pad_enqueue", CPDMA_TX_STAT(pad_enqueue) },
+       { "Tx DMA chan: misqueued", CPDMA_TX_STAT(misqueued) },
+       { "Tx DMA chan: desc_alloc_fail", CPDMA_TX_STAT(desc_alloc_fail) },
+       { "Tx DMA chan: pad_alloc_fail", CPDMA_TX_STAT(pad_alloc_fail) },
+       { "Tx DMA chan: runt_receive_buf", CPDMA_TX_STAT(runt_receive_buff) },
+       { "Tx DMA chan: runt_transmit_buf", CPDMA_TX_STAT(runt_transmit_buff) },
+       { "Tx DMA chan: empty_dequeue", CPDMA_TX_STAT(empty_dequeue) },
+       { "Tx DMA chan: busy_dequeue", CPDMA_TX_STAT(busy_dequeue) },
+       { "Tx DMA chan: good_dequeue", CPDMA_TX_STAT(good_dequeue) },
+       { "Tx DMA chan: requeue", CPDMA_TX_STAT(requeue) },
+       { "Tx DMA chan: teardown_dequeue", CPDMA_TX_STAT(teardown_dequeue) },
+};
+
+#define CPSW_STATS_LEN ARRAY_SIZE(cpsw_gstrings_stats)
+
 #define napi_to_priv(napi)     container_of(napi, struct cpsw_priv, napi)
 #define for_each_slave(priv, func, arg...)                             \
        do {                                                            \
@@ -723,6 +852,69 @@ static int cpsw_set_coalesce(struct net_device *ndev,
        return 0;
 }
 
+static int cpsw_get_sset_count(struct net_device *ndev, int sset)
+{
+       switch (sset) {
+       case ETH_SS_STATS:
+               return CPSW_STATS_LEN;
+       default:
+               return -EOPNOTSUPP;
+       }
+}
+
+static void cpsw_get_strings(struct net_device *ndev, u32 stringset, u8 *data)
+{
+       u8 *p = data;
+       int i;
+
+       switch (stringset) {
+       case ETH_SS_STATS:
+               for (i = 0; i < CPSW_STATS_LEN; i++) {
+                       memcpy(p, cpsw_gstrings_stats[i].stat_string,
+                              ETH_GSTRING_LEN);
+                       p += ETH_GSTRING_LEN;
+               }
+               break;
+       }
+}
+
+static void cpsw_get_ethtool_stats(struct net_device *ndev,
+                                   struct ethtool_stats *stats, u64 *data)
+{
+       struct cpsw_priv *priv = netdev_priv(ndev);
+       struct cpdma_chan_stats rx_stats;
+       struct cpdma_chan_stats tx_stats;
+       u32 val;
+       u8 *p;
+       int i;
+
+       /* Collect Davinci CPDMA stats for Rx and Tx Channel */
+       cpdma_chan_get_stats(priv->rxch, &rx_stats);
+       cpdma_chan_get_stats(priv->txch, &tx_stats);
+
+       for (i = 0; i < CPSW_STATS_LEN; i++) {
+               switch (cpsw_gstrings_stats[i].type) {
+               case CPSW_STATS:
+                       val = readl(priv->hw_stats +
+                                   cpsw_gstrings_stats[i].stat_offset);
+                       data[i] = val;
+                       break;
+
+               case CPDMA_RX_STATS:
+                       p = (u8 *)&rx_stats +
+                               cpsw_gstrings_stats[i].stat_offset;
+                       data[i] = *(u32 *)p;
+                       break;
+
+               case CPDMA_TX_STATS:
+                       p = (u8 *)&tx_stats +
+                               cpsw_gstrings_stats[i].stat_offset;
+                       data[i] = *(u32 *)p;
+                       break;
+               }
+       }
+}
+
 static inline int __show_stat(char *buf, int maxlen, const char *name, u32 val)
 {
        static char *leader = "........................................";
@@ -1426,6 +1618,9 @@ static const struct ethtool_ops cpsw_ethtool_ops = {
        .set_settings   = cpsw_set_settings,
        .get_coalesce   = cpsw_get_coalesce,
        .set_coalesce   = cpsw_set_coalesce,
+       .get_sset_count         = cpsw_get_sset_count,
+       .get_strings            = cpsw_get_strings,
+       .get_ethtool_stats      = cpsw_get_ethtool_stats,
 };
 
 static void cpsw_slave_init(struct cpsw_slave *slave, struct cpsw_priv *priv,
@@ -1623,6 +1818,7 @@ static int cpsw_probe_dual_emac(struct platform_device *pdev,
        priv_sl2->host_port = priv->host_port;
        priv_sl2->host_port_regs = priv->host_port_regs;
        priv_sl2->wr_regs = priv->wr_regs;
+       priv_sl2->hw_stats = priv->hw_stats;
        priv_sl2->dma = priv->dma;
        priv_sl2->txch = priv->txch;
        priv_sl2->rxch = priv->rxch;
@@ -1780,7 +1976,8 @@ static int cpsw_probe(struct platform_device *pdev)
        switch (priv->version) {
        case CPSW_VERSION_1:
                priv->host_port_regs = ss_regs + CPSW1_HOST_PORT_OFFSET;
-               priv->cpts->reg       = ss_regs + CPSW1_CPTS_OFFSET;
+               priv->cpts->reg      = ss_regs + CPSW1_CPTS_OFFSET;
+               priv->hw_stats       = ss_regs + CPSW1_HW_STATS;
                dma_params.dmaregs   = ss_regs + CPSW1_CPDMA_OFFSET;
                dma_params.txhdp     = ss_regs + CPSW1_STATERAM_OFFSET;
                ale_params.ale_regs  = ss_regs + CPSW1_ALE_OFFSET;
@@ -1791,7 +1988,8 @@ static int cpsw_probe(struct platform_device *pdev)
                break;
        case CPSW_VERSION_2:
                priv->host_port_regs = ss_regs + CPSW2_HOST_PORT_OFFSET;
-               priv->cpts->reg       = ss_regs + CPSW2_CPTS_OFFSET;
+               priv->cpts->reg      = ss_regs + CPSW2_CPTS_OFFSET;
+               priv->hw_stats       = ss_regs + CPSW2_HW_STATS;
                dma_params.dmaregs   = ss_regs + CPSW2_CPDMA_OFFSET;
                dma_params.txhdp     = ss_regs + CPSW2_STATERAM_OFFSET;
                ale_params.ale_regs  = ss_regs + CPSW2_ALE_OFFSET;