diff mbox series

[v3,net-next,17/24] net: dsa: sja1105: Add support for ethtool port counters

Message ID 20190413012822.30931-18-olteanv@gmail.com
State Changes Requested
Delegated to: David Miller
Headers show
Series NXP SJA1105 DSA driver | expand

Commit Message

Vladimir Oltean April 13, 2019, 1:28 a.m. UTC
Signed-off-by: Vladimir Oltean <olteanv@gmail.com>
Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
---
Changes in v3:
None.

Changes in v2:
None functional. Moved the IS_ET() and IS_PQRS() device identification
macros here since they are not used in earlier patches.

 drivers/net/dsa/sja1105/Makefile              |   1 +
 drivers/net/dsa/sja1105/sja1105.h             |   7 +-
 drivers/net/dsa/sja1105/sja1105_ethtool.c     | 414 ++++++++++++++++++
 drivers/net/dsa/sja1105/sja1105_main.c        |   3 +
 .../net/dsa/sja1105/sja1105_static_config.h   |  21 +
 5 files changed, 445 insertions(+), 1 deletion(-)
 create mode 100644 drivers/net/dsa/sja1105/sja1105_ethtool.c

Comments

Jiri Pirko April 13, 2019, 8:53 p.m. UTC | #1
Sat, Apr 13, 2019 at 03:28:15AM CEST, olteanv@gmail.com wrote:
>Signed-off-by: Vladimir Oltean <olteanv@gmail.com>
>Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
>---
>Changes in v3:
>None.
>
>Changes in v2:
>None functional. Moved the IS_ET() and IS_PQRS() device identification
>macros here since they are not used in earlier patches.
>
> drivers/net/dsa/sja1105/Makefile              |   1 +
> drivers/net/dsa/sja1105/sja1105.h             |   7 +-
> drivers/net/dsa/sja1105/sja1105_ethtool.c     | 414 ++++++++++++++++++
> drivers/net/dsa/sja1105/sja1105_main.c        |   3 +
> .../net/dsa/sja1105/sja1105_static_config.h   |  21 +
> 5 files changed, 445 insertions(+), 1 deletion(-)
> create mode 100644 drivers/net/dsa/sja1105/sja1105_ethtool.c
>
>diff --git a/drivers/net/dsa/sja1105/Makefile b/drivers/net/dsa/sja1105/Makefile
>index ed00840802f4..bb4404c79eb2 100644
>--- a/drivers/net/dsa/sja1105/Makefile
>+++ b/drivers/net/dsa/sja1105/Makefile
>@@ -3,6 +3,7 @@ obj-$(CONFIG_NET_DSA_SJA1105) += sja1105.o
> sja1105-objs := \
>     sja1105_spi.o \
>     sja1105_main.o \
>+    sja1105_ethtool.o \
>     sja1105_clocking.o \
>     sja1105_static_config.o \
>     sja1105_dynamic_config.o \
>diff --git a/drivers/net/dsa/sja1105/sja1105.h b/drivers/net/dsa/sja1105/sja1105.h
>index 4c9df44a4478..80b20bdd8f9c 100644
>--- a/drivers/net/dsa/sja1105/sja1105.h
>+++ b/drivers/net/dsa/sja1105/sja1105.h
>@@ -120,8 +120,13 @@ typedef enum {
> int sja1105_clocking_setup_port(struct sja1105_private *priv, int port);
> int sja1105_clocking_setup(struct sja1105_private *priv);
> 
>-/* From sja1105_dynamic_config.c */
>+/* From sja1105_ethtool.c */
>+void sja1105_get_ethtool_stats(struct dsa_switch *ds, int port, u64 *data);
>+void sja1105_get_strings(struct dsa_switch *ds, int port,
>+			 u32 stringset, u8 *data);
>+int sja1105_get_sset_count(struct dsa_switch *ds, int port, int sset);
> 
>+/* From sja1105_dynamic_config.c */
> int sja1105_dynamic_config_read(struct sja1105_private *priv,
> 				enum sja1105_blk_idx blk_idx,
> 				int index, void *entry);
>diff --git a/drivers/net/dsa/sja1105/sja1105_ethtool.c b/drivers/net/dsa/sja1105/sja1105_ethtool.c
>new file mode 100644
>index 000000000000..c082599702bd
>--- /dev/null
>+++ b/drivers/net/dsa/sja1105/sja1105_ethtool.c
>@@ -0,0 +1,414 @@
>+// SPDX-License-Identifier: GPL-2.0
>+/* Copyright (c) 2018-2019, Vladimir Oltean <olteanv@gmail.com>
>+ */
>+#include "sja1105.h"
>+
>+#define SIZE_MAC_AREA		(0x02 * 4)
>+#define SIZE_HL1_AREA		(0x10 * 4)
>+#define SIZE_HL2_AREA		(0x4 * 4)
>+#define SIZE_QLEVEL_AREA	(0x8 * 4) /* 0x4 to 0xB */

Please use prefixes for defines like this. For example "SIZE_MAC_AREA"
sounds way too generic.

[...]


>+static void
>+sja1105_port_status_hl1_unpack(void *buf,
>+			       struct sja1105_port_status_hl1 *status)
>+{
>+	/* Make pointer arithmetic work on 4 bytes */
>+	u32 *p = (u32 *)buf;

You don't need to cast void *. Please avoid it in the whole patchset.

[...]


>+	if (!IS_PQRS(priv->info->device_id))
>+		return;
>+
>+	memset(data + k, 0, ARRAY_SIZE(sja1105pqrs_extra_port_stats) *
>+			sizeof(u64));
>+	for (i = 0; i < 8; i++) {

Array size instead of "8"?


>+		data[k++] = status.hl2.qlevel_hwm[i];
>+		data[k++] = status.hl2.qlevel[i];
>+	}

[...]


> 
>+#define IS_PQRS(device_id) \
>+	(((device_id) == SJA1105PR_DEVICE_ID) || \
>+	 ((device_id) == SJA1105QS_DEVICE_ID))
>+#define IS_ET(device_id) \
>+	(((device_id) == SJA1105E_DEVICE_ID) || \
>+	 ((device_id) == SJA1105T_DEVICE_ID))
>+/* P and R have same Device ID, and differ by Part Number */
>+#define IS_P(device_id, part_nr) \
>+	(((device_id) == SJA1105PR_DEVICE_ID) && \
>+	 ((part_nr) == SJA1105P_PART_NR))
>+#define IS_R(device_id, part_nr) \
>+	(((device_id) == SJA1105PR_DEVICE_ID) && \
>+	 ((part_nr) == SJA1105R_PART_NR))
>+/* Same do Q and S */
>+#define IS_Q(device_id, part_nr) \
>+	(((device_id) == SJA1105QS_DEVICE_ID) && \
>+	 ((part_nr) == SJA1105Q_PART_NR))
>+#define IS_S(device_id, part_nr) \

Please have a prefix for macros like this. "IS_S" sounds way too
generic...


>+	(((device_id) == SJA1105QS_DEVICE_ID) && \
>+	 ((part_nr) == SJA1105S_PART_NR))
>+
> struct sja1105_general_params_entry {
> 	u64 vllupformat;
> 	u64 mirr_ptacu;
>-- 
>2.17.1
>
Vladimir Oltean April 13, 2019, 9:55 p.m. UTC | #2
On Sat, 13 Apr 2019 at 23:53, Jiri Pirko <jiri@resnulli.us> wrote:
>
> Sat, Apr 13, 2019 at 03:28:15AM CEST, olteanv@gmail.com wrote:
> >Signed-off-by: Vladimir Oltean <olteanv@gmail.com>
> >Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
> >---
> >Changes in v3:
> >None.
> >
> >Changes in v2:
> >None functional. Moved the IS_ET() and IS_PQRS() device identification
> >macros here since they are not used in earlier patches.
> >
> > drivers/net/dsa/sja1105/Makefile              |   1 +
> > drivers/net/dsa/sja1105/sja1105.h             |   7 +-
> > drivers/net/dsa/sja1105/sja1105_ethtool.c     | 414 ++++++++++++++++++
> > drivers/net/dsa/sja1105/sja1105_main.c        |   3 +
> > .../net/dsa/sja1105/sja1105_static_config.h   |  21 +
> > 5 files changed, 445 insertions(+), 1 deletion(-)
> > create mode 100644 drivers/net/dsa/sja1105/sja1105_ethtool.c
> >
> >diff --git a/drivers/net/dsa/sja1105/Makefile b/drivers/net/dsa/sja1105/Makefile
> >index ed00840802f4..bb4404c79eb2 100644
> >--- a/drivers/net/dsa/sja1105/Makefile
> >+++ b/drivers/net/dsa/sja1105/Makefile
> >@@ -3,6 +3,7 @@ obj-$(CONFIG_NET_DSA_SJA1105) += sja1105.o
> > sja1105-objs := \
> >     sja1105_spi.o \
> >     sja1105_main.o \
> >+    sja1105_ethtool.o \
> >     sja1105_clocking.o \
> >     sja1105_static_config.o \
> >     sja1105_dynamic_config.o \
> >diff --git a/drivers/net/dsa/sja1105/sja1105.h b/drivers/net/dsa/sja1105/sja1105.h
> >index 4c9df44a4478..80b20bdd8f9c 100644
> >--- a/drivers/net/dsa/sja1105/sja1105.h
> >+++ b/drivers/net/dsa/sja1105/sja1105.h
> >@@ -120,8 +120,13 @@ typedef enum {
> > int sja1105_clocking_setup_port(struct sja1105_private *priv, int port);
> > int sja1105_clocking_setup(struct sja1105_private *priv);
> >
> >-/* From sja1105_dynamic_config.c */
> >+/* From sja1105_ethtool.c */
> >+void sja1105_get_ethtool_stats(struct dsa_switch *ds, int port, u64 *data);
> >+void sja1105_get_strings(struct dsa_switch *ds, int port,
> >+                       u32 stringset, u8 *data);
> >+int sja1105_get_sset_count(struct dsa_switch *ds, int port, int sset);
> >
> >+/* From sja1105_dynamic_config.c */
> > int sja1105_dynamic_config_read(struct sja1105_private *priv,
> >                               enum sja1105_blk_idx blk_idx,
> >                               int index, void *entry);
> >diff --git a/drivers/net/dsa/sja1105/sja1105_ethtool.c b/drivers/net/dsa/sja1105/sja1105_ethtool.c
> >new file mode 100644
> >index 000000000000..c082599702bd
> >--- /dev/null
> >+++ b/drivers/net/dsa/sja1105/sja1105_ethtool.c
> >@@ -0,0 +1,414 @@
> >+// SPDX-License-Identifier: GPL-2.0
> >+/* Copyright (c) 2018-2019, Vladimir Oltean <olteanv@gmail.com>
> >+ */
> >+#include "sja1105.h"
> >+
> >+#define SIZE_MAC_AREA         (0x02 * 4)
> >+#define SIZE_HL1_AREA         (0x10 * 4)
> >+#define SIZE_HL2_AREA         (0x4 * 4)
> >+#define SIZE_QLEVEL_AREA      (0x8 * 4) /* 0x4 to 0xB */
>
> Please use prefixes for defines like this. For example "SIZE_MAC_AREA"
> sounds way too generic.
>

What you propose sounds nice but then consistency would be a concern,
so I'd have to redo the entire driver. And then there are tables named
like "L2 Address Lookup Parameters Table", and as if
SIZE_L2_LOOKUP_PARAMS_DYN_CMD_ET wasn't long enough, a prefix would
top it off.
I don't think it's as much of an issue for the reader (for the
compiler it clearly isn't, as it's restricted to this C file only) as
it is for tools like ctags?

> [...]
>
>
> >+static void
> >+sja1105_port_status_hl1_unpack(void *buf,
> >+                             struct sja1105_port_status_hl1 *status)
> >+{
> >+      /* Make pointer arithmetic work on 4 bytes */
> >+      u32 *p = (u32 *)buf;
>
> You don't need to cast void *. Please avoid it in the whole patchset.
>

Ok.

> [...]
>
>
> >+      if (!IS_PQRS(priv->info->device_id))
> >+              return;
> >+
> >+      memset(data + k, 0, ARRAY_SIZE(sja1105pqrs_extra_port_stats) *
> >+                      sizeof(u64));
> >+      for (i = 0; i < 8; i++) {
>
> Array size instead of "8"?
>

Perhaps SJA1105_NUM_TC, since the egress queue occupancy levels are
per traffic class. The size of the array is 16.

>
> >+              data[k++] = status.hl2.qlevel_hwm[i];
> >+              data[k++] = status.hl2.qlevel[i];
> >+      }
>
> [...]
>
>
> >
> >+#define IS_PQRS(device_id) \
> >+      (((device_id) == SJA1105PR_DEVICE_ID) || \
> >+       ((device_id) == SJA1105QS_DEVICE_ID))
> >+#define IS_ET(device_id) \
> >+      (((device_id) == SJA1105E_DEVICE_ID) || \
> >+       ((device_id) == SJA1105T_DEVICE_ID))
> >+/* P and R have same Device ID, and differ by Part Number */
> >+#define IS_P(device_id, part_nr) \
> >+      (((device_id) == SJA1105PR_DEVICE_ID) && \
> >+       ((part_nr) == SJA1105P_PART_NR))
> >+#define IS_R(device_id, part_nr) \
> >+      (((device_id) == SJA1105PR_DEVICE_ID) && \
> >+       ((part_nr) == SJA1105R_PART_NR))
> >+/* Same do Q and S */
> >+#define IS_Q(device_id, part_nr) \
> >+      (((device_id) == SJA1105QS_DEVICE_ID) && \
> >+       ((part_nr) == SJA1105Q_PART_NR))
> >+#define IS_S(device_id, part_nr) \
>
> Please have a prefix for macros like this. "IS_S" sounds way too
> generic...
>

Ok, I can think of a more descriptive name, since there are under 5
occurrences of these macros after the reorg, now that I have the
sja1105_info structure which also holds per-revision function
pointers.

>
> >+      (((device_id) == SJA1105QS_DEVICE_ID) && \
> >+       ((part_nr) == SJA1105S_PART_NR))
> >+
> > struct sja1105_general_params_entry {
> >       u64 vllupformat;
> >       u64 mirr_ptacu;
> >--
> >2.17.1
> >


Thanks!
-Vladimir
Jiri Pirko April 14, 2019, 8:34 a.m. UTC | #3
Sat, Apr 13, 2019 at 11:55:52PM CEST, olteanv@gmail.com wrote:
>On Sat, 13 Apr 2019 at 23:53, Jiri Pirko <jiri@resnulli.us> wrote:
>>
>> Sat, Apr 13, 2019 at 03:28:15AM CEST, olteanv@gmail.com wrote:
>> >Signed-off-by: Vladimir Oltean <olteanv@gmail.com>
>> >Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
>> >---
>> >Changes in v3:
>> >None.
>> >
>> >Changes in v2:
>> >None functional. Moved the IS_ET() and IS_PQRS() device identification
>> >macros here since they are not used in earlier patches.
>> >
>> > drivers/net/dsa/sja1105/Makefile              |   1 +
>> > drivers/net/dsa/sja1105/sja1105.h             |   7 +-
>> > drivers/net/dsa/sja1105/sja1105_ethtool.c     | 414 ++++++++++++++++++
>> > drivers/net/dsa/sja1105/sja1105_main.c        |   3 +
>> > .../net/dsa/sja1105/sja1105_static_config.h   |  21 +
>> > 5 files changed, 445 insertions(+), 1 deletion(-)
>> > create mode 100644 drivers/net/dsa/sja1105/sja1105_ethtool.c
>> >
>> >diff --git a/drivers/net/dsa/sja1105/Makefile b/drivers/net/dsa/sja1105/Makefile
>> >index ed00840802f4..bb4404c79eb2 100644
>> >--- a/drivers/net/dsa/sja1105/Makefile
>> >+++ b/drivers/net/dsa/sja1105/Makefile
>> >@@ -3,6 +3,7 @@ obj-$(CONFIG_NET_DSA_SJA1105) += sja1105.o
>> > sja1105-objs := \
>> >     sja1105_spi.o \
>> >     sja1105_main.o \
>> >+    sja1105_ethtool.o \
>> >     sja1105_clocking.o \
>> >     sja1105_static_config.o \
>> >     sja1105_dynamic_config.o \
>> >diff --git a/drivers/net/dsa/sja1105/sja1105.h b/drivers/net/dsa/sja1105/sja1105.h
>> >index 4c9df44a4478..80b20bdd8f9c 100644
>> >--- a/drivers/net/dsa/sja1105/sja1105.h
>> >+++ b/drivers/net/dsa/sja1105/sja1105.h
>> >@@ -120,8 +120,13 @@ typedef enum {
>> > int sja1105_clocking_setup_port(struct sja1105_private *priv, int port);
>> > int sja1105_clocking_setup(struct sja1105_private *priv);
>> >
>> >-/* From sja1105_dynamic_config.c */
>> >+/* From sja1105_ethtool.c */
>> >+void sja1105_get_ethtool_stats(struct dsa_switch *ds, int port, u64 *data);
>> >+void sja1105_get_strings(struct dsa_switch *ds, int port,
>> >+                       u32 stringset, u8 *data);
>> >+int sja1105_get_sset_count(struct dsa_switch *ds, int port, int sset);
>> >
>> >+/* From sja1105_dynamic_config.c */
>> > int sja1105_dynamic_config_read(struct sja1105_private *priv,
>> >                               enum sja1105_blk_idx blk_idx,
>> >                               int index, void *entry);
>> >diff --git a/drivers/net/dsa/sja1105/sja1105_ethtool.c b/drivers/net/dsa/sja1105/sja1105_ethtool.c
>> >new file mode 100644
>> >index 000000000000..c082599702bd
>> >--- /dev/null
>> >+++ b/drivers/net/dsa/sja1105/sja1105_ethtool.c
>> >@@ -0,0 +1,414 @@
>> >+// SPDX-License-Identifier: GPL-2.0
>> >+/* Copyright (c) 2018-2019, Vladimir Oltean <olteanv@gmail.com>
>> >+ */
>> >+#include "sja1105.h"
>> >+
>> >+#define SIZE_MAC_AREA         (0x02 * 4)
>> >+#define SIZE_HL1_AREA         (0x10 * 4)
>> >+#define SIZE_HL2_AREA         (0x4 * 4)
>> >+#define SIZE_QLEVEL_AREA      (0x8 * 4) /* 0x4 to 0xB */
>>
>> Please use prefixes for defines like this. For example "SIZE_MAC_AREA"
>> sounds way too generic.
>>
>
>What you propose sounds nice but then consistency would be a concern,
>so I'd have to redo the entire driver. And then there are tables named

Yep


>like "L2 Address Lookup Parameters Table", and as if
>SIZE_L2_LOOKUP_PARAMS_DYN_CMD_ET wasn't long enough, a prefix would
>top it off.

It's a tradeoff. But most of the time, it is doable. Then the code is
easier to read.


>I don't think it's as much of an issue for the reader (for the
>compiler it clearly isn't, as it's restricted to this C file only) as
>it is for tools like ctags?
>
>> [...]
>>
>>
>> >+static void
>> >+sja1105_port_status_hl1_unpack(void *buf,
>> >+                             struct sja1105_port_status_hl1 *status)
>> >+{
>> >+      /* Make pointer arithmetic work on 4 bytes */
>> >+      u32 *p = (u32 *)buf;
>>
>> You don't need to cast void *. Please avoid it in the whole patchset.
>>
>
>Ok.
>
>> [...]
>>
>>
>> >+      if (!IS_PQRS(priv->info->device_id))
>> >+              return;
>> >+
>> >+      memset(data + k, 0, ARRAY_SIZE(sja1105pqrs_extra_port_stats) *
>> >+                      sizeof(u64));
>> >+      for (i = 0; i < 8; i++) {
>>
>> Array size instead of "8"?
>>
>
>Perhaps SJA1105_NUM_TC, since the egress queue occupancy levels are
>per traffic class. The size of the array is 16.
>
>>
>> >+              data[k++] = status.hl2.qlevel_hwm[i];
>> >+              data[k++] = status.hl2.qlevel[i];
>> >+      }
>>
>> [...]
>>
>>
>> >
>> >+#define IS_PQRS(device_id) \
>> >+      (((device_id) == SJA1105PR_DEVICE_ID) || \
>> >+       ((device_id) == SJA1105QS_DEVICE_ID))
>> >+#define IS_ET(device_id) \
>> >+      (((device_id) == SJA1105E_DEVICE_ID) || \
>> >+       ((device_id) == SJA1105T_DEVICE_ID))
>> >+/* P and R have same Device ID, and differ by Part Number */
>> >+#define IS_P(device_id, part_nr) \
>> >+      (((device_id) == SJA1105PR_DEVICE_ID) && \
>> >+       ((part_nr) == SJA1105P_PART_NR))
>> >+#define IS_R(device_id, part_nr) \
>> >+      (((device_id) == SJA1105PR_DEVICE_ID) && \
>> >+       ((part_nr) == SJA1105R_PART_NR))
>> >+/* Same do Q and S */
>> >+#define IS_Q(device_id, part_nr) \
>> >+      (((device_id) == SJA1105QS_DEVICE_ID) && \
>> >+       ((part_nr) == SJA1105Q_PART_NR))
>> >+#define IS_S(device_id, part_nr) \
>>
>> Please have a prefix for macros like this. "IS_S" sounds way too
>> generic...
>>
>
>Ok, I can think of a more descriptive name, since there are under 5
>occurrences of these macros after the reorg, now that I have the
>sja1105_info structure which also holds per-revision function
>pointers.
>
>>
>> >+      (((device_id) == SJA1105QS_DEVICE_ID) && \
>> >+       ((part_nr) == SJA1105S_PART_NR))
>> >+
>> > struct sja1105_general_params_entry {
>> >       u64 vllupformat;
>> >       u64 mirr_ptacu;
>> >--
>> >2.17.1
>> >
>
>
>Thanks!
>-Vladimir
diff mbox series

Patch

diff --git a/drivers/net/dsa/sja1105/Makefile b/drivers/net/dsa/sja1105/Makefile
index ed00840802f4..bb4404c79eb2 100644
--- a/drivers/net/dsa/sja1105/Makefile
+++ b/drivers/net/dsa/sja1105/Makefile
@@ -3,6 +3,7 @@  obj-$(CONFIG_NET_DSA_SJA1105) += sja1105.o
 sja1105-objs := \
     sja1105_spi.o \
     sja1105_main.o \
+    sja1105_ethtool.o \
     sja1105_clocking.o \
     sja1105_static_config.o \
     sja1105_dynamic_config.o \
diff --git a/drivers/net/dsa/sja1105/sja1105.h b/drivers/net/dsa/sja1105/sja1105.h
index 4c9df44a4478..80b20bdd8f9c 100644
--- a/drivers/net/dsa/sja1105/sja1105.h
+++ b/drivers/net/dsa/sja1105/sja1105.h
@@ -120,8 +120,13 @@  typedef enum {
 int sja1105_clocking_setup_port(struct sja1105_private *priv, int port);
 int sja1105_clocking_setup(struct sja1105_private *priv);
 
-/* From sja1105_dynamic_config.c */
+/* From sja1105_ethtool.c */
+void sja1105_get_ethtool_stats(struct dsa_switch *ds, int port, u64 *data);
+void sja1105_get_strings(struct dsa_switch *ds, int port,
+			 u32 stringset, u8 *data);
+int sja1105_get_sset_count(struct dsa_switch *ds, int port, int sset);
 
+/* From sja1105_dynamic_config.c */
 int sja1105_dynamic_config_read(struct sja1105_private *priv,
 				enum sja1105_blk_idx blk_idx,
 				int index, void *entry);
diff --git a/drivers/net/dsa/sja1105/sja1105_ethtool.c b/drivers/net/dsa/sja1105/sja1105_ethtool.c
new file mode 100644
index 000000000000..c082599702bd
--- /dev/null
+++ b/drivers/net/dsa/sja1105/sja1105_ethtool.c
@@ -0,0 +1,414 @@ 
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2018-2019, Vladimir Oltean <olteanv@gmail.com>
+ */
+#include "sja1105.h"
+
+#define SIZE_MAC_AREA		(0x02 * 4)
+#define SIZE_HL1_AREA		(0x10 * 4)
+#define SIZE_HL2_AREA		(0x4 * 4)
+#define SIZE_QLEVEL_AREA	(0x8 * 4) /* 0x4 to 0xB */
+
+struct sja1105_port_status_mac {
+	u64 n_runt;
+	u64 n_soferr;
+	u64 n_alignerr;
+	u64 n_miierr;
+	u64 typeerr;
+	u64 sizeerr;
+	u64 tctimeout;
+	u64 priorerr;
+	u64 nomaster;
+	u64 memov;
+	u64 memerr;
+	u64 invtyp;
+	u64 intcyov;
+	u64 domerr;
+	u64 pcfbagdrop;
+	u64 spcprior;
+	u64 ageprior;
+	u64 portdrop;
+	u64 lendrop;
+	u64 bagdrop;
+	u64 policeerr;
+	u64 drpnona664err;
+	u64 spcerr;
+	u64 agedrp;
+};
+
+struct sja1105_port_status_hl1 {
+	u64 n_n664err;
+	u64 n_vlanerr;
+	u64 n_unreleased;
+	u64 n_sizeerr;
+	u64 n_crcerr;
+	u64 n_vlnotfound;
+	u64 n_ctpolerr;
+	u64 n_polerr;
+	u64 n_rxfrmsh;
+	u64 n_rxfrm;
+	u64 n_rxbytesh;
+	u64 n_rxbyte;
+	u64 n_txfrmsh;
+	u64 n_txfrm;
+	u64 n_txbytesh;
+	u64 n_txbyte;
+};
+
+struct sja1105_port_status_hl2 {
+	u64 n_qfull;
+	u64 n_part_drop;
+	u64 n_egr_disabled;
+	u64 n_not_reach;
+	u64 qlevel_hwm[8]; /* Only for P/Q/R/S */
+	u64 qlevel[8];     /* Only for P/Q/R/S */
+};
+
+struct sja1105_port_status {
+	struct sja1105_port_status_mac mac;
+	struct sja1105_port_status_hl1 hl1;
+	struct sja1105_port_status_hl2 hl2;
+};
+
+static void
+sja1105_port_status_mac_unpack(void *buf,
+			       struct sja1105_port_status_mac *status)
+{
+	/* Make pointer arithmetic work on 4 bytes */
+	u32 *p = (u32 *)buf;
+
+	sja1105_unpack(p + 0x0, &status->n_runt,       31, 24, 4);
+	sja1105_unpack(p + 0x0, &status->n_soferr,     23, 16, 4);
+	sja1105_unpack(p + 0x0, &status->n_alignerr,   15,  8, 4);
+	sja1105_unpack(p + 0x0, &status->n_miierr,      7,  0, 4);
+	sja1105_unpack(p + 0x1, &status->typeerr,      27, 27, 4);
+	sja1105_unpack(p + 0x1, &status->sizeerr,      26, 26, 4);
+	sja1105_unpack(p + 0x1, &status->tctimeout,    25, 25, 4);
+	sja1105_unpack(p + 0x1, &status->priorerr,     24, 24, 4);
+	sja1105_unpack(p + 0x1, &status->nomaster,     23, 23, 4);
+	sja1105_unpack(p + 0x1, &status->memov,        22, 22, 4);
+	sja1105_unpack(p + 0x1, &status->memerr,       21, 21, 4);
+	sja1105_unpack(p + 0x1, &status->invtyp,       19, 19, 4);
+	sja1105_unpack(p + 0x1, &status->intcyov,      18, 18, 4);
+	sja1105_unpack(p + 0x1, &status->domerr,       17, 17, 4);
+	sja1105_unpack(p + 0x1, &status->pcfbagdrop,   16, 16, 4);
+	sja1105_unpack(p + 0x1, &status->spcprior,     15, 12, 4);
+	sja1105_unpack(p + 0x1, &status->ageprior,     11,  8, 4);
+	sja1105_unpack(p + 0x1, &status->portdrop,      6,  6, 4);
+	sja1105_unpack(p + 0x1, &status->lendrop,       5,  5, 4);
+	sja1105_unpack(p + 0x1, &status->bagdrop,       4,  4, 4);
+	sja1105_unpack(p + 0x1, &status->policeerr,     3,  3, 4);
+	sja1105_unpack(p + 0x1, &status->drpnona664err, 2,  2, 4);
+	sja1105_unpack(p + 0x1, &status->spcerr,        1,  1, 4);
+	sja1105_unpack(p + 0x1, &status->agedrp,        0,  0, 4);
+}
+
+static void
+sja1105_port_status_hl1_unpack(void *buf,
+			       struct sja1105_port_status_hl1 *status)
+{
+	/* Make pointer arithmetic work on 4 bytes */
+	u32 *p = (u32 *)buf;
+
+	sja1105_unpack(p + 0xF, &status->n_n664err,    31,  0, 4);
+	sja1105_unpack(p + 0xE, &status->n_vlanerr,    31,  0, 4);
+	sja1105_unpack(p + 0xD, &status->n_unreleased, 31,  0, 4);
+	sja1105_unpack(p + 0xC, &status->n_sizeerr,    31,  0, 4);
+	sja1105_unpack(p + 0xB, &status->n_crcerr,     31,  0, 4);
+	sja1105_unpack(p + 0xA, &status->n_vlnotfound, 31,  0, 4);
+	sja1105_unpack(p + 0x9, &status->n_ctpolerr,   31,  0, 4);
+	sja1105_unpack(p + 0x8, &status->n_polerr,     31,  0, 4);
+	sja1105_unpack(p + 0x7, &status->n_rxfrmsh,    31,  0, 4);
+	sja1105_unpack(p + 0x6, &status->n_rxfrm,      31,  0, 4);
+	sja1105_unpack(p + 0x5, &status->n_rxbytesh,   31,  0, 4);
+	sja1105_unpack(p + 0x4, &status->n_rxbyte,     31,  0, 4);
+	sja1105_unpack(p + 0x3, &status->n_txfrmsh,    31,  0, 4);
+	sja1105_unpack(p + 0x2, &status->n_txfrm,      31,  0, 4);
+	sja1105_unpack(p + 0x1, &status->n_txbytesh,   31,  0, 4);
+	sja1105_unpack(p + 0x0, &status->n_txbyte,     31,  0, 4);
+	status->n_rxfrm  += status->n_rxfrmsh  << 32;
+	status->n_rxbyte += status->n_rxbytesh << 32;
+	status->n_txfrm  += status->n_txfrmsh  << 32;
+	status->n_txbyte += status->n_txbytesh << 32;
+}
+
+static void
+sja1105_port_status_hl2_unpack(void *buf,
+			       struct sja1105_port_status_hl2 *status)
+{
+	/* Make pointer arithmetic work on 4 bytes */
+	u32 *p = (u32 *)buf;
+
+	sja1105_unpack(p + 0x3, &status->n_qfull,        31,  0, 4);
+	sja1105_unpack(p + 0x2, &status->n_part_drop,    31,  0, 4);
+	sja1105_unpack(p + 0x1, &status->n_egr_disabled, 31,  0, 4);
+	sja1105_unpack(p + 0x0, &status->n_not_reach,    31,  0, 4);
+}
+
+static void
+sja1105pqrs_port_status_qlevel_unpack(void *buf,
+				      struct sja1105_port_status_hl2 *status)
+{
+	/* Make pointer arithmetic work on 4 bytes */
+	u32 *p = (u32 *)buf;
+	int i;
+
+	for (i = 0; i < 8; i++) {
+		sja1105_unpack(p + i, &status->qlevel_hwm[i], 24, 16, 4);
+		sja1105_unpack(p + i, &status->qlevel[i],      8,  0, 4);
+	}
+}
+
+static int sja1105_port_status_get_mac(struct sja1105_private *priv,
+				       struct sja1105_port_status_mac *status,
+				       int port)
+{
+	const struct sja1105_regs *regs = priv->info->regs;
+	u8 packed_buf[SIZE_MAC_AREA] = {0};
+	int rc;
+
+	/* MAC area */
+	rc = sja1105_spi_send_packed_buf(priv, SPI_READ, regs->mac[port],
+					 packed_buf, SIZE_MAC_AREA);
+	if (rc < 0)
+		return rc;
+
+	sja1105_port_status_mac_unpack(packed_buf, status);
+
+	return 0;
+}
+
+static int sja1105_port_status_get_hl1(struct sja1105_private *priv,
+				       struct sja1105_port_status_hl1 *status,
+				       int port)
+{
+	const struct sja1105_regs *regs = priv->info->regs;
+	u8 packed_buf[SIZE_HL1_AREA] = {0};
+	int rc;
+
+	rc = sja1105_spi_send_packed_buf(priv, SPI_READ, regs->mac_hl1[port],
+					 packed_buf, SIZE_HL1_AREA);
+	if (rc < 0)
+		return rc;
+
+	sja1105_port_status_hl1_unpack(packed_buf, status);
+
+	return 0;
+}
+
+static int sja1105_port_status_get_hl2(struct sja1105_private *priv,
+				       struct sja1105_port_status_hl2 *status,
+				       int port)
+{
+	const struct sja1105_regs *regs = priv->info->regs;
+	u8 packed_buf[SIZE_QLEVEL_AREA] = {0};
+	int rc;
+
+	rc = sja1105_spi_send_packed_buf(priv, SPI_READ, regs->mac_hl2[port],
+					 packed_buf, SIZE_HL2_AREA);
+	if (rc < 0)
+		return rc;
+
+	sja1105_port_status_hl2_unpack(packed_buf, status);
+
+	if (IS_ET(priv->info->device_id))
+		/* Code below is strictly P/Q/R/S specific. */
+		return 0;
+
+	rc = sja1105_spi_send_packed_buf(priv, SPI_READ, regs->qlevel[port],
+					 packed_buf, SIZE_QLEVEL_AREA);
+	if (rc < 0)
+		return rc;
+
+	sja1105pqrs_port_status_qlevel_unpack(packed_buf, status);
+
+	return 0;
+}
+
+static int sja1105_port_status_get(struct sja1105_private *priv,
+				   struct sja1105_port_status *status,
+				   int port)
+{
+	int rc;
+
+	rc = sja1105_port_status_get_mac(priv, &status->mac, port);
+	if (rc < 0)
+		return rc;
+	rc = sja1105_port_status_get_hl1(priv, &status->hl1, port);
+	if (rc < 0)
+		return rc;
+	rc = sja1105_port_status_get_hl2(priv, &status->hl2, port);
+	if (rc < 0)
+		return rc;
+
+	return 0;
+}
+
+static char sja1105_port_stats[][ETH_GSTRING_LEN] = {
+	/* MAC-Level Diagnostic Counters */
+	"n_runt",
+	"n_soferr",
+	"n_alignerr",
+	"n_miierr",
+	/* MAC-Level Diagnostic Flags */
+	"typeerr",
+	"sizeerr",
+	"tctimeout",
+	"priorerr",
+	"nomaster",
+	"memov",
+	"memerr",
+	"invtyp",
+	"intcyov",
+	"domerr",
+	"pcfbagdrop",
+	"spcprior",
+	"ageprior",
+	"portdrop",
+	"lendrop",
+	"bagdrop",
+	"policeerr",
+	"drpnona664err",
+	"spcerr",
+	"agedrp",
+	/* High-Level Diagnostic Counters */
+	"n_n664err",
+	"n_vlanerr",
+	"n_unreleased",
+	"n_sizeerr",
+	"n_crcerr",
+	"n_vlnotfound",
+	"n_ctpolerr",
+	"n_polerr",
+	"n_rxfrm",
+	"n_rxbyte",
+	"n_txfrm",
+	"n_txbyte",
+	"n_qfull",
+	"n_part_drop",
+	"n_egr_disabled",
+	"n_not_reach",
+};
+
+static char sja1105pqrs_extra_port_stats[][ETH_GSTRING_LEN] = {
+	/* Queue Levels */
+	"qlevel_hwm_0",
+	"qlevel_hwm_1",
+	"qlevel_hwm_2",
+	"qlevel_hwm_3",
+	"qlevel_hwm_4",
+	"qlevel_hwm_5",
+	"qlevel_hwm_6",
+	"qlevel_hwm_7",
+	"qlevel_0",
+	"qlevel_1",
+	"qlevel_2",
+	"qlevel_3",
+	"qlevel_4",
+	"qlevel_5",
+	"qlevel_6",
+	"qlevel_7",
+};
+
+void sja1105_get_ethtool_stats(struct dsa_switch *ds, int port, u64 *data)
+{
+	struct sja1105_private *priv = ds->priv;
+	struct sja1105_port_status status = {0};
+	int rc, i, k = 0;
+
+	rc = sja1105_port_status_get(priv, &status, port);
+	if (rc < 0) {
+		dev_err(ds->dev, "Failed to read port %d counters: %d\n",
+			port, rc);
+		return;
+	}
+	memset(data, 0, ARRAY_SIZE(sja1105_port_stats) * sizeof(u64));
+	data[k++] = status.mac.n_runt;
+	data[k++] = status.mac.n_soferr;
+	data[k++] = status.mac.n_alignerr;
+	data[k++] = status.mac.n_miierr;
+	data[k++] = status.mac.typeerr;
+	data[k++] = status.mac.sizeerr;
+	data[k++] = status.mac.tctimeout;
+	data[k++] = status.mac.priorerr;
+	data[k++] = status.mac.nomaster;
+	data[k++] = status.mac.memov;
+	data[k++] = status.mac.memerr;
+	data[k++] = status.mac.invtyp;
+	data[k++] = status.mac.intcyov;
+	data[k++] = status.mac.domerr;
+	data[k++] = status.mac.pcfbagdrop;
+	data[k++] = status.mac.spcprior;
+	data[k++] = status.mac.ageprior;
+	data[k++] = status.mac.portdrop;
+	data[k++] = status.mac.lendrop;
+	data[k++] = status.mac.bagdrop;
+	data[k++] = status.mac.policeerr;
+	data[k++] = status.mac.drpnona664err;
+	data[k++] = status.mac.spcerr;
+	data[k++] = status.mac.agedrp;
+	data[k++] = status.hl1.n_n664err;
+	data[k++] = status.hl1.n_vlanerr;
+	data[k++] = status.hl1.n_unreleased;
+	data[k++] = status.hl1.n_sizeerr;
+	data[k++] = status.hl1.n_crcerr;
+	data[k++] = status.hl1.n_vlnotfound;
+	data[k++] = status.hl1.n_ctpolerr;
+	data[k++] = status.hl1.n_polerr;
+	data[k++] = status.hl1.n_rxfrm;
+	data[k++] = status.hl1.n_rxbyte;
+	data[k++] = status.hl1.n_txfrm;
+	data[k++] = status.hl1.n_txbyte;
+	data[k++] = status.hl2.n_qfull;
+	data[k++] = status.hl2.n_part_drop;
+	data[k++] = status.hl2.n_egr_disabled;
+	data[k++] = status.hl2.n_not_reach;
+
+	if (!IS_PQRS(priv->info->device_id))
+		return;
+
+	memset(data + k, 0, ARRAY_SIZE(sja1105pqrs_extra_port_stats) *
+			sizeof(u64));
+	for (i = 0; i < 8; i++) {
+		data[k++] = status.hl2.qlevel_hwm[i];
+		data[k++] = status.hl2.qlevel[i];
+	}
+}
+
+void sja1105_get_strings(struct dsa_switch *ds, int port,
+			 u32 stringset, u8 *data)
+{
+	struct sja1105_private *priv = ds->priv;
+	u8 *p = data;
+	int i;
+
+	switch (stringset) {
+	case ETH_SS_STATS:
+		for (i = 0; i < ARRAY_SIZE(sja1105_port_stats); i++) {
+			strlcpy(p, sja1105_port_stats[i], ETH_GSTRING_LEN);
+			p += ETH_GSTRING_LEN;
+		}
+		if (!IS_PQRS(priv->info->device_id))
+			return;
+		for (i = 0; i < ARRAY_SIZE(sja1105pqrs_extra_port_stats); i++) {
+			strlcpy(p, sja1105pqrs_extra_port_stats[i],
+				ETH_GSTRING_LEN);
+			p += ETH_GSTRING_LEN;
+		}
+		break;
+	}
+}
+
+int sja1105_get_sset_count(struct dsa_switch *ds, int port, int sset)
+{
+	int count = ARRAY_SIZE(sja1105_port_stats);
+	struct sja1105_private *priv = ds->priv;
+
+	if (sset != ETH_SS_STATS)
+		return -EOPNOTSUPP;
+
+	if (IS_PQRS(priv->info->device_id))
+		count += ARRAY_SIZE(sja1105pqrs_extra_port_stats);
+
+	return count;
+}
+
diff --git a/drivers/net/dsa/sja1105/sja1105_main.c b/drivers/net/dsa/sja1105/sja1105_main.c
index a0851d1c9d89..9d28436fe466 100644
--- a/drivers/net/dsa/sja1105/sja1105_main.c
+++ b/drivers/net/dsa/sja1105/sja1105_main.c
@@ -1240,6 +1240,9 @@  static const struct dsa_switch_ops sja1105_switch_ops = {
 	.get_tag_protocol	= sja1105_get_tag_protocol,
 	.setup			= sja1105_setup,
 	.adjust_link		= sja1105_adjust_link,
+	.get_strings		= sja1105_get_strings,
+	.get_ethtool_stats	= sja1105_get_ethtool_stats,
+	.get_sset_count		= sja1105_get_sset_count,
 	.port_fdb_dump		= sja1105_fdb_dump,
 	.port_fdb_add		= sja1105_fdb_add,
 	.port_fdb_del		= sja1105_fdb_del,
diff --git a/drivers/net/dsa/sja1105/sja1105_static_config.h b/drivers/net/dsa/sja1105/sja1105_static_config.h
index c71ef9d366db..395d817e5fed 100644
--- a/drivers/net/dsa/sja1105/sja1105_static_config.h
+++ b/drivers/net/dsa/sja1105/sja1105_static_config.h
@@ -78,6 +78,27 @@  enum sja1105_blk_idx {
 #define SJA1105R_PART_NO			0x9A86
 #define SJA1105S_PART_NO			0x9A87
 
+#define IS_PQRS(device_id) \
+	(((device_id) == SJA1105PR_DEVICE_ID) || \
+	 ((device_id) == SJA1105QS_DEVICE_ID))
+#define IS_ET(device_id) \
+	(((device_id) == SJA1105E_DEVICE_ID) || \
+	 ((device_id) == SJA1105T_DEVICE_ID))
+/* P and R have same Device ID, and differ by Part Number */
+#define IS_P(device_id, part_nr) \
+	(((device_id) == SJA1105PR_DEVICE_ID) && \
+	 ((part_nr) == SJA1105P_PART_NR))
+#define IS_R(device_id, part_nr) \
+	(((device_id) == SJA1105PR_DEVICE_ID) && \
+	 ((part_nr) == SJA1105R_PART_NR))
+/* Same do Q and S */
+#define IS_Q(device_id, part_nr) \
+	(((device_id) == SJA1105QS_DEVICE_ID) && \
+	 ((part_nr) == SJA1105Q_PART_NR))
+#define IS_S(device_id, part_nr) \
+	(((device_id) == SJA1105QS_DEVICE_ID) && \
+	 ((part_nr) == SJA1105S_PART_NR))
+
 struct sja1105_general_params_entry {
 	u64 vllupformat;
 	u64 mirr_ptacu;