Johnny Hughes
2019-02-04 c1f36c28393a7bb126cbf436cd6a4077a5b5c313
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
From 274098c872141c923ee78936e9bf1e9c2597c82b Mon Sep 17 00:00:00 2001
From: Miquel Raynal <miquel.raynal@free-electrons.com>
Date: Mon, 6 Nov 2017 22:56:53 +0100
Subject: [PATCH 08/46] net: mvpp2: add ethtool GOP statistics
 
Add ethtool statistics support by reading the GOP statistics from the
hardware counters. Also implement a workqueue to gather the statistics
every second or some 32-bit counters could overflow.
 
Suggested-by: Stefan Chulski <stefanc@marvell.com>
Signed-off-by: Miquel Raynal <miquel.raynal@free-electrons.com>
Reviewed-by: Andrew Lunn <andrew@lunn.ch>
Signed-off-by: David S. Miller <davem@davemloft.net>
(cherry picked from commit 118d6298f6f0556e54331a6e86de2313d134fdbb)
Signed-off-by: Marcin Wojtas <mw@semihalf.com>
---
 drivers/net/ethernet/marvell/mvpp2.c | 228 ++++++++++++++++++++++++++++++++++-
 1 file changed, 223 insertions(+), 5 deletions(-)
 
diff --git a/drivers/net/ethernet/marvell/mvpp2.c b/drivers/net/ethernet/marvell/mvpp2.c
index fb0d9c0..a79d2ff 100644
--- a/drivers/net/ethernet/marvell/mvpp2.c
+++ b/drivers/net/ethernet/marvell/mvpp2.c
@@ -799,6 +799,42 @@ enum mvpp2_bm_type {
     MVPP2_BM_SWF_SHORT
 };
 
+/* GMAC MIB Counters register definitions */
+#define MVPP21_MIB_COUNTERS_OFFSET        0x1000
+#define MVPP21_MIB_COUNTERS_PORT_SZ        0x400
+#define MVPP22_MIB_COUNTERS_OFFSET        0x0
+#define MVPP22_MIB_COUNTERS_PORT_SZ        0x100
+
+#define MVPP2_MIB_GOOD_OCTETS_RCVD        0x0
+#define MVPP2_MIB_BAD_OCTETS_RCVD        0x8
+#define MVPP2_MIB_CRC_ERRORS_SENT        0xc
+#define MVPP2_MIB_UNICAST_FRAMES_RCVD        0x10
+#define MVPP2_MIB_BROADCAST_FRAMES_RCVD        0x18
+#define MVPP2_MIB_MULTICAST_FRAMES_RCVD        0x1c
+#define MVPP2_MIB_FRAMES_64_OCTETS        0x20
+#define MVPP2_MIB_FRAMES_65_TO_127_OCTETS    0x24
+#define MVPP2_MIB_FRAMES_128_TO_255_OCTETS    0x28
+#define MVPP2_MIB_FRAMES_256_TO_511_OCTETS    0x2c
+#define MVPP2_MIB_FRAMES_512_TO_1023_OCTETS    0x30
+#define MVPP2_MIB_FRAMES_1024_TO_MAX_OCTETS    0x34
+#define MVPP2_MIB_GOOD_OCTETS_SENT        0x38
+#define MVPP2_MIB_UNICAST_FRAMES_SENT        0x40
+#define MVPP2_MIB_MULTICAST_FRAMES_SENT        0x48
+#define MVPP2_MIB_BROADCAST_FRAMES_SENT        0x4c
+#define MVPP2_MIB_FC_SENT            0x54
+#define MVPP2_MIB_FC_RCVD            0x58
+#define MVPP2_MIB_RX_FIFO_OVERRUN        0x5c
+#define MVPP2_MIB_UNDERSIZE_RCVD        0x60
+#define MVPP2_MIB_FRAGMENTS_RCVD        0x64
+#define MVPP2_MIB_OVERSIZE_RCVD            0x68
+#define MVPP2_MIB_JABBER_RCVD            0x6c
+#define MVPP2_MIB_MAC_RCV_ERROR            0x70
+#define MVPP2_MIB_BAD_CRC_EVENT            0x74
+#define MVPP2_MIB_COLLISION            0x78
+#define MVPP2_MIB_LATE_COLLISION        0x7c
+
+#define MVPP2_MIB_COUNTERS_STATS_DELAY        (1 * HZ)
+
 /* Definitions */
 
 /* Shared Packet Processor resources */
@@ -826,6 +862,7 @@ struct mvpp2 {
     struct clk *axi_clk;
 
     /* List of pointers to port structures */
+    int port_count;
     struct mvpp2_port **port_list;
 
     /* Aggregated TXQs */
@@ -847,6 +884,12 @@ struct mvpp2 {
 
     /* Maximum number of RXQs per port */
     unsigned int max_port_rxqs;
+
+    /* Workqueue to gather hardware statistics with its lock */
+    struct mutex gather_stats_lock;
+    struct delayed_work stats_work;
+    char queue_name[30];
+    struct workqueue_struct *stats_queue;
 };
 
 struct mvpp2_pcpu_stats {
@@ -891,6 +934,7 @@ struct mvpp2_port {
 
     /* Per-port registers' base address */
     void __iomem *base;
+    void __iomem *stats_base;
 
     struct mvpp2_rx_queue **rxqs;
     unsigned int nrxqs;
@@ -909,6 +953,7 @@ struct mvpp2_port {
     u16 tx_ring_size;
     u16 rx_ring_size;
     struct mvpp2_pcpu_stats __percpu *stats;
+    u64 *ethtool_stats;
 
     phy_interface_t phy_interface;
     struct device_node *phy_node;
@@ -4778,9 +4823,136 @@ static void mvpp2_port_loopback_set(struct mvpp2_port *port)
     writel(val, port->base + MVPP2_GMAC_CTRL_1_REG);
 }
 
+struct mvpp2_ethtool_counter {
+    unsigned int offset;
+    const char string[ETH_GSTRING_LEN];
+    bool reg_is_64b;
+};
+
+static u64 mvpp2_read_count(struct mvpp2_port *port,
+                const struct mvpp2_ethtool_counter *counter)
+{
+    u64 val;
+
+    val = readl(port->stats_base + counter->offset);
+    if (counter->reg_is_64b)
+        val += (u64)readl(port->stats_base + counter->offset + 4) << 32;
+
+    return val;
+}
+
+/* Due to the fact that software statistics and hardware statistics are, by
+ * design, incremented at different moments in the chain of packet processing,
+ * it is very likely that incoming packets could have been dropped after being
+ * counted by hardware but before reaching software statistics (most probably
+ * multicast packets), and in the oppposite way, during transmission, FCS bytes
+ * are added in between as well as TSO skb will be split and header bytes added.
+ * Hence, statistics gathered from userspace with ifconfig (software) and
+ * ethtool (hardware) cannot be compared.
+ */
+static const struct mvpp2_ethtool_counter mvpp2_ethtool_regs[] = {
+    { MVPP2_MIB_GOOD_OCTETS_RCVD, "good_octets_received", true },
+    { MVPP2_MIB_BAD_OCTETS_RCVD, "bad_octets_received" },
+    { MVPP2_MIB_CRC_ERRORS_SENT, "crc_errors_sent" },
+    { MVPP2_MIB_UNICAST_FRAMES_RCVD, "unicast_frames_received" },
+    { MVPP2_MIB_BROADCAST_FRAMES_RCVD, "broadcast_frames_received" },
+    { MVPP2_MIB_MULTICAST_FRAMES_RCVD, "multicast_frames_received" },
+    { MVPP2_MIB_FRAMES_64_OCTETS, "frames_64_octets" },
+    { MVPP2_MIB_FRAMES_65_TO_127_OCTETS, "frames_65_to_127_octet" },
+    { MVPP2_MIB_FRAMES_128_TO_255_OCTETS, "frames_128_to_255_octet" },
+    { MVPP2_MIB_FRAMES_256_TO_511_OCTETS, "frames_256_to_511_octet" },
+    { MVPP2_MIB_FRAMES_512_TO_1023_OCTETS, "frames_512_to_1023_octet" },
+    { MVPP2_MIB_FRAMES_1024_TO_MAX_OCTETS, "frames_1024_to_max_octet" },
+    { MVPP2_MIB_GOOD_OCTETS_SENT, "good_octets_sent", true },
+    { MVPP2_MIB_UNICAST_FRAMES_SENT, "unicast_frames_sent" },
+    { MVPP2_MIB_MULTICAST_FRAMES_SENT, "multicast_frames_sent" },
+    { MVPP2_MIB_BROADCAST_FRAMES_SENT, "broadcast_frames_sent" },
+    { MVPP2_MIB_FC_SENT, "fc_sent" },
+    { MVPP2_MIB_FC_RCVD, "fc_received" },
+    { MVPP2_MIB_RX_FIFO_OVERRUN, "rx_fifo_overrun" },
+    { MVPP2_MIB_UNDERSIZE_RCVD, "undersize_received" },
+    { MVPP2_MIB_FRAGMENTS_RCVD, "fragments_received" },
+    { MVPP2_MIB_OVERSIZE_RCVD, "oversize_received" },
+    { MVPP2_MIB_JABBER_RCVD, "jabber_received" },
+    { MVPP2_MIB_MAC_RCV_ERROR, "mac_receive_error" },
+    { MVPP2_MIB_BAD_CRC_EVENT, "bad_crc_event" },
+    { MVPP2_MIB_COLLISION, "collision" },
+    { MVPP2_MIB_LATE_COLLISION, "late_collision" },
+};
+
+static void mvpp2_ethtool_get_strings(struct net_device *netdev, u32 sset,
+                      u8 *data)
+{
+    if (sset == ETH_SS_STATS) {
+        int i;
+
+        for (i = 0; i < ARRAY_SIZE(mvpp2_ethtool_regs); i++)
+            memcpy(data + i * ETH_GSTRING_LEN,
+                   &mvpp2_ethtool_regs[i].string, ETH_GSTRING_LEN);
+    }
+}
+
+static void mvpp2_gather_hw_statistics(struct work_struct *work)
+{
+    struct delayed_work *del_work = to_delayed_work(work);
+    struct mvpp2 *priv = container_of(del_work, struct mvpp2, stats_work);
+    struct mvpp2_port *port;
+    u64 *pstats;
+    int i, j;
+
+    mutex_lock(&priv->gather_stats_lock);
+
+    for (i = 0; i < priv->port_count; i++) {
+        if (!priv->port_list[i])
+            continue;
+
+        port = priv->port_list[i];
+        pstats = port->ethtool_stats;
+        for (j = 0; j < ARRAY_SIZE(mvpp2_ethtool_regs); j++)
+            *pstats++ += mvpp2_read_count(port,
+                              &mvpp2_ethtool_regs[j]);
+    }
+
+    /* No need to read again the counters right after this function if it
+     * was called asynchronously by the user (ie. use of ethtool).
+     */
+    cancel_delayed_work(&priv->stats_work);
+    queue_delayed_work(priv->stats_queue, &priv->stats_work,
+               MVPP2_MIB_COUNTERS_STATS_DELAY);
+
+    mutex_unlock(&priv->gather_stats_lock);
+}
+
+static void mvpp2_ethtool_get_stats(struct net_device *dev,
+                    struct ethtool_stats *stats, u64 *data)
+{
+    struct mvpp2_port *port = netdev_priv(dev);
+
+    /* Update statistics for all ports, copy only those actually needed */
+    mvpp2_gather_hw_statistics(&port->priv->stats_work.work);
+
+    mutex_lock(&port->priv->gather_stats_lock);
+    memcpy(data, port->ethtool_stats,
+           sizeof(u64) * ARRAY_SIZE(mvpp2_ethtool_regs));
+    mutex_unlock(&port->priv->gather_stats_lock);
+}
+
+static int mvpp2_ethtool_get_sset_count(struct net_device *dev, int sset)
+{
+    if (sset == ETH_SS_STATS)
+        return ARRAY_SIZE(mvpp2_ethtool_regs);
+
+    return -EOPNOTSUPP;
+}
+
 static void mvpp2_port_reset(struct mvpp2_port *port)
 {
     u32 val;
+    unsigned int i;
+
+    /* Read the GOP statistics to reset the hardware counters */
+    for (i = 0; i < ARRAY_SIZE(mvpp2_ethtool_regs); i++)
+        mvpp2_read_count(port, &mvpp2_ethtool_regs[i]);
 
     val = readl(port->base + MVPP2_GMAC_CTRL_2_REG) &
             ~MVPP2_GMAC_PORT_RESET_MASK;
@@ -6916,6 +7088,10 @@ static int mvpp2_open(struct net_device *dev)
     if (priv->hw_version == MVPP22)
         mvpp22_init_rss(port);
 
+    /* Start hardware statistics gathering */
+    queue_delayed_work(priv->stats_queue, &priv->stats_work,
+               MVPP2_MIB_COUNTERS_STATS_DELAY);
+
     return 0;
 
 err_free_link_irq:
@@ -6960,6 +7136,9 @@ static int mvpp2_stop(struct net_device *dev)
     mvpp2_cleanup_rxqs(port);
     mvpp2_cleanup_txqs(port);
 
+    cancel_delayed_work_sync(&priv->stats_work);
+    flush_workqueue(priv->stats_queue);
+
     return 0;
 }
 
@@ -7271,6 +7450,9 @@ static const struct ethtool_ops mvpp2_eth_tool_ops = {
     .get_drvinfo    = mvpp2_ethtool_get_drvinfo,
     .get_ringparam    = mvpp2_ethtool_get_ringparam,
     .set_ringparam    = mvpp2_ethtool_set_ringparam,
+    .get_strings    = mvpp2_ethtool_get_strings,
+    .get_ethtool_stats = mvpp2_ethtool_get_stats,
+    .get_sset_count    = mvpp2_ethtool_get_sset_count,
     .get_link_ksettings = phy_ethtool_get_link_ksettings,
     .set_link_ksettings = phy_ethtool_set_link_ksettings,
 };
@@ -7674,6 +7856,10 @@ static int mvpp2_port_probe(struct platform_device *pdev,
             err = PTR_ERR(port->base);
             goto err_free_irq;
         }
+
+        port->stats_base = port->priv->lms_base +
+                   MVPP21_MIB_COUNTERS_OFFSET +
+                   port->gop_id * MVPP21_MIB_COUNTERS_PORT_SZ;
     } else {
         if (of_property_read_u32(port_node, "gop-port-id",
                      &port->gop_id)) {
@@ -7683,15 +7869,26 @@ static int mvpp2_port_probe(struct platform_device *pdev,
         }
 
         port->base = priv->iface_base + MVPP22_GMAC_BASE(port->gop_id);
+        port->stats_base = port->priv->iface_base +
+                   MVPP22_MIB_COUNTERS_OFFSET +
+                   port->gop_id * MVPP22_MIB_COUNTERS_PORT_SZ;
     }
 
-    /* Alloc per-cpu stats */
+    /* Alloc per-cpu and ethtool stats */
     port->stats = netdev_alloc_pcpu_stats(struct mvpp2_pcpu_stats);
     if (!port->stats) {
         err = -ENOMEM;
         goto err_free_irq;
     }
 
+    port->ethtool_stats = devm_kcalloc(&pdev->dev,
+                       ARRAY_SIZE(mvpp2_ethtool_regs),
+                       sizeof(u64), GFP_KERNEL);
+    if (!port->ethtool_stats) {
+        err = -ENOMEM;
+        goto err_free_stats;
+    }
+
     mvpp2_port_copy_mac_addr(dev, priv, port_node, &mac_from);
 
     port->tx_ring_size = MVPP2_MAX_TXD;
@@ -8014,7 +8211,7 @@ static int mvpp2_probe(struct platform_device *pdev)
     struct mvpp2 *priv;
     struct resource *res;
     void __iomem *base;
-    int port_count, i;
+    int i;
     int err;
 
     priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
@@ -8129,14 +8326,14 @@ static int mvpp2_probe(struct platform_device *pdev)
         goto err_mg_clk;
     }
 
-    port_count = of_get_available_child_count(dn);
-    if (port_count == 0) {
+    priv->port_count = of_get_available_child_count(dn);
+    if (priv->port_count == 0) {
         dev_err(&pdev->dev, "no ports enabled\n");
         err = -ENODEV;
         goto err_mg_clk;
     }
 
-    priv->port_list = devm_kcalloc(&pdev->dev, port_count,
+    priv->port_list = devm_kcalloc(&pdev->dev, priv->port_count,
                        sizeof(*priv->port_list),
                        GFP_KERNEL);
     if (!priv->port_list) {
@@ -8153,6 +8350,24 @@ static int mvpp2_probe(struct platform_device *pdev)
         i++;
     }
 
+    /* Statistics must be gathered regularly because some of them (like
+     * packets counters) are 32-bit registers and could overflow quite
+     * quickly. For instance, a 10Gb link used at full bandwidth with the
+     * smallest packets (64B) will overflow a 32-bit counter in less than
+     * 30 seconds. Then, use a workqueue to fill 64-bit counters.
+     */
+    mutex_init(&priv->gather_stats_lock);
+    snprintf(priv->queue_name, sizeof(priv->queue_name),
+         "stats-wq-%s%s", netdev_name(priv->port_list[0]->dev),
+         priv->port_count > 1 ? "+" : "");
+    priv->stats_queue = create_singlethread_workqueue(priv->queue_name);
+    if (!priv->stats_queue) {
+        err = -ENOMEM;
+        goto err_mg_clk;
+    }
+
+    INIT_DELAYED_WORK(&priv->stats_work, mvpp2_gather_hw_statistics);
+
     platform_set_drvdata(pdev, priv);
     return 0;
 
@@ -8174,6 +8389,9 @@ static int mvpp2_remove(struct platform_device *pdev)
     struct device_node *port_node;
     int i = 0;
 
+    destroy_workqueue(priv->stats_queue);
+    mutex_destroy(&priv->gather_stats_lock);
+
     for_each_available_child_of_node(dn, port_node) {
         if (priv->port_list[i])
             mvpp2_port_remove(priv->port_list[i]);
-- 
2.7.4