diff mbox series

[RFC,net-next,2/3] mv88e6xxx: Implement remote management support (RMU)

Message ID 20220818102924.287719-3-mattias.forsblad@gmail.com (mailing list archive)
State Superseded
Delegated to: Netdev Maintainers
Headers show
Series net: dsa: mv88e6xxx: Add RMU support | expand

Checks

Context Check Description
netdev/tree_selection success Clearly marked for net-next
netdev/fixes_present success Fixes tag not required for -next series
netdev/subject_prefix success Link
netdev/cover_letter success Series has a cover letter
netdev/patch_count success Link
netdev/header_inline success No static functions without inline keyword in header files
netdev/build_32bit fail Errors and warnings before: 1275 this patch: 1368
netdev/cc_maintainers warning 1 maintainers not CCed: linux@armlinux.org.uk
netdev/build_clang fail Errors and warnings before: 0 this patch: 3
netdev/module_param success Was 0 now: 0
netdev/verify_signedoff success Signed-off-by tag matches author and committer
netdev/check_selftest success No net selftest shell script
netdev/verify_fixes success No Fixes tag
netdev/build_allmodconfig_warn fail Errors and warnings before: 1275 this patch: 1368
netdev/checkpatch warning WARNING: added, moved or deleted file(s), does MAINTAINERS need updating? WARNING: line length of 81 exceeds 80 columns WARNING: line length of 82 exceeds 80 columns WARNING: line length of 83 exceeds 80 columns WARNING: line length of 84 exceeds 80 columns WARNING: line length of 86 exceeds 80 columns WARNING: line length of 89 exceeds 80 columns WARNING: line length of 91 exceeds 80 columns
netdev/kdoc success Errors and warnings before: 0 this patch: 0
netdev/source_inline success Was 0 now: 0

Commit Message

Mattias Forsblad Aug. 18, 2022, 10:29 a.m. UTC
Implement support for handling RMU layer 3 frames
including receive and transmit.

Signed-off-by: Mattias Forsblad <mattias.forsblad@gmail.com>
---
 drivers/net/dsa/mv88e6xxx/Makefile  |   1 +
 drivers/net/dsa/mv88e6xxx/chip.c    |  19 ++-
 drivers/net/dsa/mv88e6xxx/chip.h    |  20 +++
 drivers/net/dsa/mv88e6xxx/global1.c |  84 +++++++++
 drivers/net/dsa/mv88e6xxx/global1.h |   3 +
 drivers/net/dsa/mv88e6xxx/rmu.c     | 256 ++++++++++++++++++++++++++++
 drivers/net/dsa/mv88e6xxx/rmu.h     |  18 ++
 7 files changed, 399 insertions(+), 2 deletions(-)
 create mode 100644 drivers/net/dsa/mv88e6xxx/rmu.c
 create mode 100644 drivers/net/dsa/mv88e6xxx/rmu.h

Comments

Andrew Lunn Aug. 18, 2022, 1:21 p.m. UTC | #1
>  static int mv88e6xxx_rmu_setup(struct mv88e6xxx_chip *chip)
>  {
> +	int ret = 0;
> +
>  	if (chip->info->ops->rmu_disable)
> -		return chip->info->ops->rmu_disable(chip);
> +		ret = chip->info->ops->rmu_disable(chip);
>  
> -	return 0;
> +	if (chip->info->ops->rmu_enable) {
> +		ret += chip->info->ops->rmu_enable(chip);
> +		ret += mv88e6xxx_rmu_init(chip);

EINVAL + EOPNOTSUPP = hard to find error code/bug.

I've not looked at rmu_enable() yet, but there are restrictions about
what ports can be used with RMU. So you have to assume some boards use
the wrong port for the CPU or upstream DSA port, and so will need to
return an error code. But such an error code should not be fatal, MDIO
can still be used.

> +	}
> +
> +	return ret;
>  }
>  
> +int mv88e6085_g1_rmu_enable(struct mv88e6xxx_chip *chip)
> +{
> +	int val = MV88E6352_G1_CTL2_RMU_MODE_DISABLED;
> +	int upstream_port = -1;
> +
> +	upstream_port = dsa_switch_upstream_port(chip->ds);
> +	dev_err(chip->dev, "RMU: Enabling on port %d", upstream_port);

dev_dbg()

> +	if (upstream_port < 0)
> +		return -1;

EOPNOTSUPP.

> +
> +	switch (upstream_port) {
> +	case 9:
> +		val = MV88E6085_G1_CTL2_RM_ENABLE;
> +		break;
> +	case 10:
> +		val = MV88E6085_G1_CTL2_RM_ENABLE | MV88E6085_G1_CTL2_P10RM;
> +		break;

Does 6085 really have 10 ports? It does!

> +	default:
> +		break;

return -EOPNOTSUPP.

> +	}
> +
> +	return mv88e6xxx_g1_ctl2_mask(chip, MV88E6085_G1_CTL2_P10RM |
> +				      MV88E6085_G1_CTL2_RM_ENABLE, val);
> +}
> +
>  int mv88e6352_g1_rmu_disable(struct mv88e6xxx_chip *chip)
>  {
>  	return mv88e6xxx_g1_ctl2_mask(chip, MV88E6352_G1_CTL2_RMU_MODE_MASK,
>  				      MV88E6352_G1_CTL2_RMU_MODE_DISABLED);
>  }
>  
> +int mv88e6352_g1_rmu_enable(struct mv88e6xxx_chip *chip)
> +{
> +	int val = MV88E6352_G1_CTL2_RMU_MODE_DISABLED;
> +	int upstream_port;
> +
> +	upstream_port = dsa_switch_upstream_port(chip->ds);
> +	dev_err(chip->dev, "RMU: Enabling on port %d", upstream_port);
> +	if (upstream_port < 0)
> +		return -1;
> +
> +	switch (upstream_port) {
> +	case 4:
> +		val = MV88E6352_G1_CTL2_RMU_MODE_PORT_4;
> +		break;
> +	case 5:
> +		val = MV88E6352_G1_CTL2_RMU_MODE_PORT_5;
> +		break;
> +	case 6:
> +		val = MV88E6352_G1_CTL2_RMU_MODE_PORT_6;
> +		break;
> +	default:
> +		break;

Same comments as above.


> diff --git a/drivers/net/dsa/mv88e6xxx/rmu.c b/drivers/net/dsa/mv88e6xxx/rmu.c
> new file mode 100644
> index 000000000000..ac68eef12521
> --- /dev/null
> +++ b/drivers/net/dsa/mv88e6xxx/rmu.c
> @@ -0,0 +1,256 @@
> +// SPDX-License-Identifier: GPL-2.0-or-later
> +/*
> + * Marvell 88E6xxx Switch Remote Management Unit Support
> + *
> + * Copyright (c) 2022 Mattias Forsblad <mattias.forsblad@gmail.com>
> + *
> + */
> +
> +#include "rmu.h"
> +#include "global1.h"
> +
> +#define MAX_RMON 64
> +#define RMON_REPLY 2
> +
> +#define RMU_REQ_GET_ID                 1
> +#define RMU_REQ_DUMP_MIB               2
> +
> +#define RMU_RESP_FORMAT_1              0x0001
> +#define RMU_RESP_FORMAT_2              0x0002
> +
> +#define RMU_RESP_CODE_GOT_ID           0x0000
> +#define RMU_RESP_CODE_DUMP_MIB         0x1020

These should all go into rmu.h. Please also follow the naming
convention, add the MV88E6XXX_ prefix.


> +
> +int mv88e6xxx_inband_rcv(struct dsa_switch *ds, struct sk_buff *skb, int seq_no)
> +{
> +	struct mv88e6xxx_chip *chip = ds->priv;
> +	struct mv88e6xxx_port *port;
> +	__be16 *prodnum;
> +	__be16 *format;
> +	__be16 *code;
> +	__be32 *mib_data;
> +	u8 pkt_dev;
> +	u8 pkt_prt;
> +	int i;

Reverse christmass tree.

> +
> +	if (!skb || !chip)
> +		return 0;

We generally don't do this sort of defensive programming. Can these be
NULL?

> +
> +	/* Extract response data */
> +	format = (__be16 *)&skb->data[0];

You have no idea of the alignment of data, so you should not cast it
to a pointer type and dereference it. Take a look at the unaligned
helpers.

> +	if (*format != htons(RMU_RESP_FORMAT_1) && (*format != htons(RMU_RESP_FORMAT_2))) {
> +		dev_err(chip->dev, "RMU: received unknown format 0x%04x", *format);

rate limit all error messages please.

> +		goto out;
> +	}
> +
> +	code = (__be16 *)&skb->data[4];
> +	if (*code == ntohs(0xffff)) {
> +		netdev_err(skb->dev, "RMU: error response code 0x%04x", *code);
> +		goto out;
> +	}
> +
> +	pkt_dev = skb->data[6] & 0x1f;

Please replace all these magic numbers with #define in rmu.h.

> +	if (pkt_dev >= DSA_MAX_SWITCHES) {
> +		netdev_err(skb->dev, "RMU: response from unknown chip %d\n", *code);
> +		goto out;
> +	}

That is a good first step, but it is not sufficient to prove the
switch actually exists.

> +
> +	/* Check sequence number */
> +	if (seq_no != chip->rmu.seq_no) {
> +		netdev_err(skb->dev, "RMU: wrong seqno received %d, expected %d",
> +			   seq_no, chip->rmu.seq_no);
> +		goto out;
> +	}
> +
> +	/* Check response code */
> +	switch (chip->rmu.request_cmd) {

Maybe a non-issue, i've not looked hard enough: What is the
relationship between ds passed to this function and pkt_dev? Does ds
belong to pkt_dev, or is ds the chip connected to the host? There are
a few boards with multiple chip in a tree, so we need to get this
mapping correct. This is something i can test sometime later, i have
such boards.

> +	case RMU_REQ_GET_ID: {
> +		if (*code == RMU_RESP_CODE_GOT_ID) {
> +			prodnum = (__be16 *)&skb->data[2];
> +			chip->rmu.got_id = *prodnum;
> +			dev_info(chip->dev, "RMU: received id OK with product number: 0x%04x\n",
> +				 chip->rmu.got_id);
> +		} else {
> +			dev_err(chip->dev,
> +				"RMU: unknown response for GET_ID format 0x%04x code 0x%04x",
> +				*format, *code);
> +		}
> +		break;
> +	}
> +	case RMU_REQ_DUMP_MIB:
> +		if (*code == RMU_RESP_CODE_DUMP_MIB) {
> +			pkt_prt = (skb->data[7] & 0x78) >> 3;
> +			mib_data = (__be32 *)&skb->data[12];
> +			port = &chip->ports[pkt_prt];
> +			if (!port) {
> +				dev_err(chip->dev, "RMU: illegal port number in response: %d\n",
> +					pkt_prt);
> +				goto out;
> +			}
> +
> +			/* Copy whole array for further
> +			 * processing according to chip type
> +			 */
> +			for (i = 0; i < MAX_RMON; i++)
> +				port->rmu_raw_stats[i] = mib_data[i];

This needs some more thought, which i don't have time for right now.

     Andrew
Mattias Forsblad Aug. 19, 2022, 5:28 a.m. UTC | #2
On 2022-08-18 15:21, Andrew Lunn wrote:
>>  static int mv88e6xxx_rmu_setup(struct mv88e6xxx_chip *chip)
>>  {
>> +	int ret = 0;
>> +
>>  	if (chip->info->ops->rmu_disable)
>> -		return chip->info->ops->rmu_disable(chip);
>> +		ret = chip->info->ops->rmu_disable(chip);
>>  
>> -	return 0;
>> +	if (chip->info->ops->rmu_enable) {
>> +		ret += chip->info->ops->rmu_enable(chip);
>> +		ret += mv88e6xxx_rmu_init(chip);
> 
> EINVAL + EOPNOTSUPP = hard to find error code/bug.
> 
> I've not looked at rmu_enable() yet, but there are restrictions about
> what ports can be used with RMU. So you have to assume some boards use
> the wrong port for the CPU or upstream DSA port, and so will need to
> return an error code. But such an error code should not be fatal, MDIO
> can still be used.
> 

Agree, I'll fix to handle that case.

>> +	}
>> +
>> +	return ret;
>>  }
>>  
>> +int mv88e6085_g1_rmu_enable(struct mv88e6xxx_chip *chip)
>> +{
>> +	int val = MV88E6352_G1_CTL2_RMU_MODE_DISABLED;
>> +	int upstream_port = -1;
>> +
>> +	upstream_port = dsa_switch_upstream_port(chip->ds);
>> +	dev_err(chip->dev, "RMU: Enabling on port %d", upstream_port);
> 
> dev_dbg()

I'll tone it down.

> 
>> +	if (upstream_port < 0)
>> +		return -1;
> 
> EOPNOTSUPP.
> 

Ofc. Thanks.

>> +
>> +	switch (upstream_port) {
>> +	case 9:
>> +		val = MV88E6085_G1_CTL2_RM_ENABLE;
>> +		break;
>> +	case 10:
>> +		val = MV88E6085_G1_CTL2_RM_ENABLE | MV88E6085_G1_CTL2_P10RM;
>> +		break;
> 
> Does 6085 really have 10 ports? It does!
> 

:)

>> +	default:
>> +		break;
> 
> return -EOPNOTSUPP.
> 

Will fix.

>> +	}
>> +
>> +	return mv88e6xxx_g1_ctl2_mask(chip, MV88E6085_G1_CTL2_P10RM |
>> +				      MV88E6085_G1_CTL2_RM_ENABLE, val);
>> +}
>> +
>>  int mv88e6352_g1_rmu_disable(struct mv88e6xxx_chip *chip)
>>  {
>>  	return mv88e6xxx_g1_ctl2_mask(chip, MV88E6352_G1_CTL2_RMU_MODE_MASK,
>>  				      MV88E6352_G1_CTL2_RMU_MODE_DISABLED);
>>  }
>>  
>> +int mv88e6352_g1_rmu_enable(struct mv88e6xxx_chip *chip)
>> +{
>> +	int val = MV88E6352_G1_CTL2_RMU_MODE_DISABLED;
>> +	int upstream_port;
>> +
>> +	upstream_port = dsa_switch_upstream_port(chip->ds);
>> +	dev_err(chip->dev, "RMU: Enabling on port %d", upstream_port);
>> +	if (upstream_port < 0)
>> +		return -1;
>> +
>> +	switch (upstream_port) {
>> +	case 4:
>> +		val = MV88E6352_G1_CTL2_RMU_MODE_PORT_4;
>> +		break;
>> +	case 5:
>> +		val = MV88E6352_G1_CTL2_RMU_MODE_PORT_5;
>> +		break;
>> +	case 6:
>> +		val = MV88E6352_G1_CTL2_RMU_MODE_PORT_6;
>> +		break;
>> +	default:
>> +		break;
> 
> Same comments as above.
> 
> 
>> diff --git a/drivers/net/dsa/mv88e6xxx/rmu.c b/drivers/net/dsa/mv88e6xxx/rmu.c
>> new file mode 100644
>> index 000000000000..ac68eef12521
>> --- /dev/null
>> +++ b/drivers/net/dsa/mv88e6xxx/rmu.c
>> @@ -0,0 +1,256 @@
>> +// SPDX-License-Identifier: GPL-2.0-or-later
>> +/*
>> + * Marvell 88E6xxx Switch Remote Management Unit Support
>> + *
>> + * Copyright (c) 2022 Mattias Forsblad <mattias.forsblad@gmail.com>
>> + *
>> + */
>> +
>> +#include "rmu.h"
>> +#include "global1.h"
>> +
>> +#define MAX_RMON 64
>> +#define RMON_REPLY 2
>> +
>> +#define RMU_REQ_GET_ID                 1
>> +#define RMU_REQ_DUMP_MIB               2
>> +
>> +#define RMU_RESP_FORMAT_1              0x0001
>> +#define RMU_RESP_FORMAT_2              0x0002
>> +
>> +#define RMU_RESP_CODE_GOT_ID           0x0000
>> +#define RMU_RESP_CODE_DUMP_MIB         0x1020
> 
> These should all go into rmu.h. Please also follow the naming
> convention, add the MV88E6XXX_ prefix.
> 
> 

Will add prefix.

>> +
>> +int mv88e6xxx_inband_rcv(struct dsa_switch *ds, struct sk_buff *skb, int seq_no)
>> +{
>> +	struct mv88e6xxx_chip *chip = ds->priv;
>> +	struct mv88e6xxx_port *port;
>> +	__be16 *prodnum;
>> +	__be16 *format;
>> +	__be16 *code;
>> +	__be32 *mib_data;
>> +	u8 pkt_dev;
>> +	u8 pkt_prt;
>> +	int i;
> 
> Reverse christmass tree.
> 

Will fix. Doesn't checkpatch find these?

>> +
>> +	if (!skb || !chip)
>> +		return 0;
> 
> We generally don't do this sort of defensive programming. Can these be
> NULL?
> 

Will investigate.

>> +
>> +	/* Extract response data */
>> +	format = (__be16 *)&skb->data[0];
> 
> You have no idea of the alignment of data, so you should not cast it
> to a pointer type and dereference it. Take a look at the unaligned
> helpers.
> 

Can you point me to an example please?

>> +	if (*format != htons(RMU_RESP_FORMAT_1) && (*format != htons(RMU_RESP_FORMAT_2))) {
>> +		dev_err(chip->dev, "RMU: received unknown format 0x%04x", *format);
> 
> rate limit all error messages please.
> 

Yes, will fix.

>> +		goto out;
>> +	}
>> +
>> +	code = (__be16 *)&skb->data[4];
>> +	if (*code == ntohs(0xffff)) {
>> +		netdev_err(skb->dev, "RMU: error response code 0x%04x", *code);
>> +		goto out;
>> +	}
>> +
>> +	pkt_dev = skb->data[6] & 0x1f;
> 
> Please replace all these magic numbers with #define in rmu.h.
> 

Will fix.

>> +	if (pkt_dev >= DSA_MAX_SWITCHES) {
>> +		netdev_err(skb->dev, "RMU: response from unknown chip %d\n", *code);
>> +		goto out;
>> +	}
> 
> That is a good first step, but it is not sufficient to prove the
> switch actually exists.
> 

Will do additional checks.

>> +
>> +	/* Check sequence number */
>> +	if (seq_no != chip->rmu.seq_no) {
>> +		netdev_err(skb->dev, "RMU: wrong seqno received %d, expected %d",
>> +			   seq_no, chip->rmu.seq_no);
>> +		goto out;
>> +	}
>> +
>> +	/* Check response code */
>> +	switch (chip->rmu.request_cmd) {
> 
> Maybe a non-issue, i've not looked hard enough: What is the
> relationship between ds passed to this function and pkt_dev? Does ds
> belong to pkt_dev, or is ds the chip connected to the host? There are
> a few boards with multiple chip in a tree, so we need to get this
> mapping correct. This is something i can test sometime later, i have
> such boards.
> 

I've tested this implementation in a system with multiple switchcores
and it works.

>> +	case RMU_REQ_GET_ID: {
>> +		if (*code == RMU_RESP_CODE_GOT_ID) {
>> +			prodnum = (__be16 *)&skb->data[2];
>> +			chip->rmu.got_id = *prodnum;
>> +			dev_info(chip->dev, "RMU: received id OK with product number: 0x%04x\n",
>> +				 chip->rmu.got_id);
>> +		} else {
>> +			dev_err(chip->dev,
>> +				"RMU: unknown response for GET_ID format 0x%04x code 0x%04x",
>> +				*format, *code);
>> +		}
>> +		break;
>> +	}
>> +	case RMU_REQ_DUMP_MIB:
>> +		if (*code == RMU_RESP_CODE_DUMP_MIB) {
>> +			pkt_prt = (skb->data[7] & 0x78) >> 3;
>> +			mib_data = (__be32 *)&skb->data[12];
>> +			port = &chip->ports[pkt_prt];
>> +			if (!port) {
>> +				dev_err(chip->dev, "RMU: illegal port number in response: %d\n",
>> +					pkt_prt);
>> +				goto out;
>> +			}
>> +
>> +			/* Copy whole array for further
>> +			 * processing according to chip type
>> +			 */
>> +			for (i = 0; i < MAX_RMON; i++)
>> +				port->rmu_raw_stats[i] = mib_data[i];
> 
> This needs some more thought, which i don't have time for right now.
> 
>      Andrew
Andrew Lunn Aug. 19, 2022, 12:29 p.m. UTC | #3
> >> +int mv88e6xxx_inband_rcv(struct dsa_switch *ds, struct sk_buff *skb, int seq_no)
> >> +{
> >> +	struct mv88e6xxx_chip *chip = ds->priv;
> >> +	struct mv88e6xxx_port *port;
> >> +	__be16 *prodnum;
> >> +	__be16 *format;
> >> +	__be16 *code;
> >> +	__be32 *mib_data;
> >> +	u8 pkt_dev;
> >> +	u8 pkt_prt;
> >> +	int i;
> > 
> > Reverse christmass tree.
> > 
> 
> Will fix. Doesn't checkpatch find these?

No it does not :-(

It is only something which netdev enforces.

> >> +
> >> +	/* Extract response data */
> >> +	format = (__be16 *)&skb->data[0];
> > 
> > You have no idea of the alignment of data, so you should not cast it
> > to a pointer type and dereference it. Take a look at the unaligned
> > helpers.
> > 
> 
> Can you point me to an example please?

#include <asm/unaligned.h>

then you can use functions like get_unaligned_be16(),
get_unaligned_le32(), put_unaligned_le32().

On X86 unaligned accesses are cheap, so these macros do nothing. For
ARM they are expensive so split it into byte accesses.

> I've tested this implementation in a system with multiple switchcores
> and it works.

Cool, there are not many such boards, but RMU really helps these if
they are sharing the same MDIO bus.

     Andrew
diff mbox series

Patch

diff --git a/drivers/net/dsa/mv88e6xxx/Makefile b/drivers/net/dsa/mv88e6xxx/Makefile
index c8eca2b6f959..105d7bd832c9 100644
--- a/drivers/net/dsa/mv88e6xxx/Makefile
+++ b/drivers/net/dsa/mv88e6xxx/Makefile
@@ -15,3 +15,4 @@  mv88e6xxx-objs += port_hidden.o
 mv88e6xxx-$(CONFIG_NET_DSA_MV88E6XXX_PTP) += ptp.o
 mv88e6xxx-objs += serdes.o
 mv88e6xxx-objs += smi.o
+mv88e6xxx-objs += rmu.o
diff --git a/drivers/net/dsa/mv88e6xxx/chip.c b/drivers/net/dsa/mv88e6xxx/chip.c
index 07e9a4da924c..888c6e47dd16 100644
--- a/drivers/net/dsa/mv88e6xxx/chip.c
+++ b/drivers/net/dsa/mv88e6xxx/chip.c
@@ -42,6 +42,7 @@ 
 #include "ptp.h"
 #include "serdes.h"
 #include "smi.h"
+#include "rmu.h"
 
 static void assert_reg_lock(struct mv88e6xxx_chip *chip)
 {
@@ -1529,10 +1530,17 @@  static int mv88e6xxx_trunk_setup(struct mv88e6xxx_chip *chip)
 
 static int mv88e6xxx_rmu_setup(struct mv88e6xxx_chip *chip)
 {
+	int ret = 0;
+
 	if (chip->info->ops->rmu_disable)
-		return chip->info->ops->rmu_disable(chip);
+		ret = chip->info->ops->rmu_disable(chip);
 
-	return 0;
+	if (chip->info->ops->rmu_enable) {
+		ret += chip->info->ops->rmu_enable(chip);
+		ret += mv88e6xxx_rmu_init(chip);
+	}
+
+	return ret;
 }
 
 static int mv88e6xxx_pot_setup(struct mv88e6xxx_chip *chip)
@@ -4090,6 +4098,7 @@  static const struct mv88e6xxx_ops mv88e6085_ops = {
 	.ppu_disable = mv88e6185_g1_ppu_disable,
 	.reset = mv88e6185_g1_reset,
 	.rmu_disable = mv88e6085_g1_rmu_disable,
+	.rmu_enable = mv88e6085_g1_rmu_enable,
 	.vtu_getnext = mv88e6352_g1_vtu_getnext,
 	.vtu_loadpurge = mv88e6352_g1_vtu_loadpurge,
 	.stu_getnext = mv88e6352_g1_stu_getnext,
@@ -4173,6 +4182,7 @@  static const struct mv88e6xxx_ops mv88e6097_ops = {
 	.pot_clear = mv88e6xxx_g2_pot_clear,
 	.reset = mv88e6352_g1_reset,
 	.rmu_disable = mv88e6085_g1_rmu_disable,
+	.rmu_enable = mv88e6085_g1_rmu_enable,
 	.vtu_getnext = mv88e6352_g1_vtu_getnext,
 	.vtu_loadpurge = mv88e6352_g1_vtu_loadpurge,
 	.phylink_get_caps = mv88e6095_phylink_get_caps,
@@ -5292,6 +5302,7 @@  static const struct mv88e6xxx_ops mv88e6352_ops = {
 	.pot_clear = mv88e6xxx_g2_pot_clear,
 	.reset = mv88e6352_g1_reset,
 	.rmu_disable = mv88e6352_g1_rmu_disable,
+	.rmu_enable = mv88e6352_g1_rmu_enable,
 	.atu_get_hash = mv88e6165_g1_atu_get_hash,
 	.atu_set_hash = mv88e6165_g1_atu_set_hash,
 	.vtu_getnext = mv88e6352_g1_vtu_getnext,
@@ -5359,6 +5370,7 @@  static const struct mv88e6xxx_ops mv88e6390_ops = {
 	.pot_clear = mv88e6xxx_g2_pot_clear,
 	.reset = mv88e6352_g1_reset,
 	.rmu_disable = mv88e6390_g1_rmu_disable,
+	.rmu_enable = mv88e6390_g1_rmu_enable,
 	.atu_get_hash = mv88e6165_g1_atu_get_hash,
 	.atu_set_hash = mv88e6165_g1_atu_set_hash,
 	.vtu_getnext = mv88e6390_g1_vtu_getnext,
@@ -5426,6 +5438,7 @@  static const struct mv88e6xxx_ops mv88e6390x_ops = {
 	.pot_clear = mv88e6xxx_g2_pot_clear,
 	.reset = mv88e6352_g1_reset,
 	.rmu_disable = mv88e6390_g1_rmu_disable,
+	.rmu_enable = mv88e6390_g1_rmu_enable,
 	.atu_get_hash = mv88e6165_g1_atu_get_hash,
 	.atu_set_hash = mv88e6165_g1_atu_set_hash,
 	.vtu_getnext = mv88e6390_g1_vtu_getnext,
@@ -5496,6 +5509,7 @@  static const struct mv88e6xxx_ops mv88e6393x_ops = {
 	.pot_clear = mv88e6xxx_g2_pot_clear,
 	.reset = mv88e6352_g1_reset,
 	.rmu_disable = mv88e6390_g1_rmu_disable,
+	.rmu_enable = mv88e6390_g1_rmu_enable,
 	.atu_get_hash = mv88e6165_g1_atu_get_hash,
 	.atu_set_hash = mv88e6165_g1_atu_set_hash,
 	.vtu_getnext = mv88e6390_g1_vtu_getnext,
@@ -6918,6 +6932,7 @@  static const struct dsa_switch_ops mv88e6xxx_switch_ops = {
 	.crosschip_lag_change	= mv88e6xxx_crosschip_lag_change,
 	.crosschip_lag_join	= mv88e6xxx_crosschip_lag_join,
 	.crosschip_lag_leave	= mv88e6xxx_crosschip_lag_leave,
+	.inband_receive         = mv88e6xxx_inband_rcv,
 };
 
 static int mv88e6xxx_register_switch(struct mv88e6xxx_chip *chip)
diff --git a/drivers/net/dsa/mv88e6xxx/chip.h b/drivers/net/dsa/mv88e6xxx/chip.h
index e693154cf803..024f45cc1476 100644
--- a/drivers/net/dsa/mv88e6xxx/chip.h
+++ b/drivers/net/dsa/mv88e6xxx/chip.h
@@ -33,6 +33,8 @@ 
 
 #define MV88E6XXX_MAX_GPIO	16
 
+#define MV88E6XXX_WAIT_POLL_TIME_MS		200
+
 enum mv88e6xxx_egress_mode {
 	MV88E6XXX_EGRESS_MODE_UNMODIFIED,
 	MV88E6XXX_EGRESS_MODE_UNTAGGED,
@@ -266,6 +268,7 @@  struct mv88e6xxx_vlan {
 struct mv88e6xxx_port {
 	struct mv88e6xxx_chip *chip;
 	int port;
+	u64 rmu_raw_stats[64];
 	struct mv88e6xxx_vlan bridge_pvid;
 	u64 serdes_stats[2];
 	u64 atu_member_violation;
@@ -282,6 +285,18 @@  struct mv88e6xxx_port {
 	struct devlink_region *region;
 };
 
+struct mv88e6xxx_rmu {
+	/* RMU resources */
+	struct net_device *netdev;
+	struct mv88e6xxx_bus_ops *ops;
+	struct completion completion;
+	/* Mutex for RMU operations */
+	struct mutex mutex;
+	u16 got_id;
+	u8 request_cmd;
+	u8 seq_no;
+};
+
 enum mv88e6xxx_region_id {
 	MV88E6XXX_REGION_GLOBAL1 = 0,
 	MV88E6XXX_REGION_GLOBAL2,
@@ -410,12 +425,16 @@  struct mv88e6xxx_chip {
 
 	/* Bridge MST to SID mappings */
 	struct list_head msts;
+
+	/* RMU resources */
+	struct mv88e6xxx_rmu rmu;
 };
 
 struct mv88e6xxx_bus_ops {
 	int (*read)(struct mv88e6xxx_chip *chip, int addr, int reg, u16 *val);
 	int (*write)(struct mv88e6xxx_chip *chip, int addr, int reg, u16 val);
 	int (*init)(struct mv88e6xxx_chip *chip);
+	int (*get_rmon)(struct mv88e6xxx_chip *chip, int port, uint64_t *data);
 };
 
 struct mv88e6xxx_mdio_bus {
@@ -637,6 +656,7 @@  struct mv88e6xxx_ops {
 
 	/* Remote Management Unit operations */
 	int (*rmu_disable)(struct mv88e6xxx_chip *chip);
+	int (*rmu_enable)(struct mv88e6xxx_chip *chip);
 
 	/* Precision Time Protocol operations */
 	const struct mv88e6xxx_ptp_ops *ptp_ops;
diff --git a/drivers/net/dsa/mv88e6xxx/global1.c b/drivers/net/dsa/mv88e6xxx/global1.c
index 5848112036b0..ba756d918e13 100644
--- a/drivers/net/dsa/mv88e6xxx/global1.c
+++ b/drivers/net/dsa/mv88e6xxx/global1.c
@@ -466,18 +466,102 @@  int mv88e6085_g1_rmu_disable(struct mv88e6xxx_chip *chip)
 				      MV88E6085_G1_CTL2_RM_ENABLE, 0);
 }
 
+int mv88e6085_g1_rmu_enable(struct mv88e6xxx_chip *chip)
+{
+	int val = MV88E6352_G1_CTL2_RMU_MODE_DISABLED;
+	int upstream_port = -1;
+
+	upstream_port = dsa_switch_upstream_port(chip->ds);
+	dev_err(chip->dev, "RMU: Enabling on port %d", upstream_port);
+	if (upstream_port < 0)
+		return -1;
+
+	switch (upstream_port) {
+	case 9:
+		val = MV88E6085_G1_CTL2_RM_ENABLE;
+		break;
+	case 10:
+		val = MV88E6085_G1_CTL2_RM_ENABLE | MV88E6085_G1_CTL2_P10RM;
+		break;
+	default:
+		break;
+	}
+
+	return mv88e6xxx_g1_ctl2_mask(chip, MV88E6085_G1_CTL2_P10RM |
+				      MV88E6085_G1_CTL2_RM_ENABLE, val);
+}
+
 int mv88e6352_g1_rmu_disable(struct mv88e6xxx_chip *chip)
 {
 	return mv88e6xxx_g1_ctl2_mask(chip, MV88E6352_G1_CTL2_RMU_MODE_MASK,
 				      MV88E6352_G1_CTL2_RMU_MODE_DISABLED);
 }
 
+int mv88e6352_g1_rmu_enable(struct mv88e6xxx_chip *chip)
+{
+	int val = MV88E6352_G1_CTL2_RMU_MODE_DISABLED;
+	int upstream_port;
+
+	upstream_port = dsa_switch_upstream_port(chip->ds);
+	dev_err(chip->dev, "RMU: Enabling on port %d", upstream_port);
+	if (upstream_port < 0)
+		return -1;
+
+	switch (upstream_port) {
+	case 4:
+		val = MV88E6352_G1_CTL2_RMU_MODE_PORT_4;
+		break;
+	case 5:
+		val = MV88E6352_G1_CTL2_RMU_MODE_PORT_5;
+		break;
+	case 6:
+		val = MV88E6352_G1_CTL2_RMU_MODE_PORT_6;
+		break;
+	default:
+		break;
+	}
+
+	return mv88e6xxx_g1_ctl2_mask(chip, MV88E6352_G1_CTL2_RMU_MODE_MASK,
+			val);
+}
+
 int mv88e6390_g1_rmu_disable(struct mv88e6xxx_chip *chip)
 {
 	return mv88e6xxx_g1_ctl2_mask(chip, MV88E6390_G1_CTL2_RMU_MODE_MASK,
 				      MV88E6390_G1_CTL2_RMU_MODE_DISABLED);
 }
 
+int mv88e6390_g1_rmu_enable(struct mv88e6xxx_chip *chip)
+{
+	int val = MV88E6390_G1_CTL2_RMU_MODE_DISABLED;
+	int upstream_port;
+
+	upstream_port = dsa_switch_upstream_port(chip->ds);
+	dev_err(chip->dev, "RMU: Enabling on port %d", upstream_port);
+	if (upstream_port < 0)
+		return -1;
+
+	switch (upstream_port) {
+	case 0:
+		val = MV88E6390_G1_CTL2_RMU_MODE_PORT_0;
+		break;
+	case 1:
+		val = MV88E6390_G1_CTL2_RMU_MODE_PORT_1;
+		break;
+	case 9:
+		val = MV88E6390_G1_CTL2_RMU_MODE_PORT_9;
+		break;
+	case 10:
+		val = MV88E6390_G1_CTL2_RMU_MODE_PORT_10;
+		break;
+	default:
+		break;
+	}
+
+	return mv88e6xxx_g1_ctl2_mask(chip, MV88E6390_G1_CTL2_RMU_MODE_MASK,
+			val);
+}
+
 int mv88e6390_g1_stats_set_histogram(struct mv88e6xxx_chip *chip)
 {
 	return mv88e6xxx_g1_ctl2_mask(chip, MV88E6390_G1_CTL2_HIST_MODE_MASK,
diff --git a/drivers/net/dsa/mv88e6xxx/global1.h b/drivers/net/dsa/mv88e6xxx/global1.h
index 65958b2a0d3a..7e786503734a 100644
--- a/drivers/net/dsa/mv88e6xxx/global1.h
+++ b/drivers/net/dsa/mv88e6xxx/global1.h
@@ -313,8 +313,11 @@  int mv88e6250_g1_ieee_pri_map(struct mv88e6xxx_chip *chip);
 int mv88e6185_g1_set_cascade_port(struct mv88e6xxx_chip *chip, int port);
 
 int mv88e6085_g1_rmu_disable(struct mv88e6xxx_chip *chip);
+int mv88e6085_g1_rmu_enable(struct mv88e6xxx_chip *chip);
 int mv88e6352_g1_rmu_disable(struct mv88e6xxx_chip *chip);
+int mv88e6352_g1_rmu_enable(struct mv88e6xxx_chip *chip);
 int mv88e6390_g1_rmu_disable(struct mv88e6xxx_chip *chip);
+int mv88e6390_g1_rmu_enable(struct mv88e6xxx_chip *chip);
 
 int mv88e6xxx_g1_set_device_number(struct mv88e6xxx_chip *chip, int index);
 
diff --git a/drivers/net/dsa/mv88e6xxx/rmu.c b/drivers/net/dsa/mv88e6xxx/rmu.c
new file mode 100644
index 000000000000..ac68eef12521
--- /dev/null
+++ b/drivers/net/dsa/mv88e6xxx/rmu.c
@@ -0,0 +1,256 @@ 
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Marvell 88E6xxx Switch Remote Management Unit Support
+ *
+ * Copyright (c) 2022 Mattias Forsblad <mattias.forsblad@gmail.com>
+ *
+ */
+
+#include "rmu.h"
+#include "global1.h"
+
+#define MAX_RMON 64
+#define RMON_REPLY 2
+
+#define RMU_REQ_GET_ID                 1
+#define RMU_REQ_DUMP_MIB               2
+
+#define RMU_RESP_FORMAT_1              0x0001
+#define RMU_RESP_FORMAT_2              0x0002
+
+#define RMU_RESP_CODE_GOT_ID           0x0000
+#define RMU_RESP_CODE_DUMP_MIB         0x1020
+
+int mv88e6xxx_inband_rcv(struct dsa_switch *ds, struct sk_buff *skb, int seq_no)
+{
+	struct mv88e6xxx_chip *chip = ds->priv;
+	struct mv88e6xxx_port *port;
+	__be16 *prodnum;
+	__be16 *format;
+	__be16 *code;
+	__be32 *mib_data;
+	u8 pkt_dev;
+	u8 pkt_prt;
+	int i;
+
+	if (!skb || !chip)
+		return 0;
+
+	/* Extract response data */
+	format = (__be16 *)&skb->data[0];
+	if (*format != htons(RMU_RESP_FORMAT_1) && (*format != htons(RMU_RESP_FORMAT_2))) {
+		dev_err(chip->dev, "RMU: received unknown format 0x%04x", *format);
+		goto out;
+	}
+
+	code = (__be16 *)&skb->data[4];
+	if (*code == ntohs(0xffff)) {
+		netdev_err(skb->dev, "RMU: error response code 0x%04x", *code);
+		goto out;
+	}
+
+	pkt_dev = skb->data[6] & 0x1f;
+	if (pkt_dev >= DSA_MAX_SWITCHES) {
+		netdev_err(skb->dev, "RMU: response from unknown chip %d\n", *code);
+		goto out;
+	}
+
+	/* Check sequence number */
+	if (seq_no != chip->rmu.seq_no) {
+		netdev_err(skb->dev, "RMU: wrong seqno received %d, expected %d",
+			   seq_no, chip->rmu.seq_no);
+		goto out;
+	}
+
+	/* Check response code */
+	switch (chip->rmu.request_cmd) {
+	case RMU_REQ_GET_ID: {
+		if (*code == RMU_RESP_CODE_GOT_ID) {
+			prodnum = (__be16 *)&skb->data[2];
+			chip->rmu.got_id = *prodnum;
+			dev_info(chip->dev, "RMU: received id OK with product number: 0x%04x\n",
+				 chip->rmu.got_id);
+		} else {
+			dev_err(chip->dev,
+				"RMU: unknown response for GET_ID format 0x%04x code 0x%04x",
+				*format, *code);
+		}
+		break;
+	}
+	case RMU_REQ_DUMP_MIB:
+		if (*code == RMU_RESP_CODE_DUMP_MIB) {
+			pkt_prt = (skb->data[7] & 0x78) >> 3;
+			mib_data = (__be32 *)&skb->data[12];
+			port = &chip->ports[pkt_prt];
+			if (!port) {
+				dev_err(chip->dev, "RMU: illegal port number in response: %d\n",
+					pkt_prt);
+				goto out;
+			}
+
+			/* Copy whole array for further
+			 * processing according to chip type
+			 */
+			for (i = 0; i < MAX_RMON; i++)
+				port->rmu_raw_stats[i] = mib_data[i];
+		}
+		break;
+	default:
+		dev_err(chip->dev,
+			"RMU: unknown response format 0x%04x and code 0x%04x from chip %d\n",
+			*format, *code, chip->ds->index);
+		break;
+	}
+
+out:
+	complete(&chip->rmu.completion);
+
+	return 0;
+}
+
+static int mv88e6xxx_rmu_tx(struct mv88e6xxx_chip *chip, int port,
+			    const char *msg, int len)
+{
+	const struct dsa_device_ops *tag_ops;
+	const struct dsa_port *dp;
+	unsigned char *data;
+	struct sk_buff *skb;
+
+	dp = dsa_to_port(chip->ds, port);
+	if (!dp || !dp->cpu_dp)
+		return 0;
+
+	tag_ops = dp->cpu_dp->tag_ops;
+	if (!tag_ops)
+		return -ENODEV;
+
+	skb = netdev_alloc_skb(chip->rmu.netdev, 64);
+	if (!skb)
+		return -ENOMEM;
+
+	skb_reserve(skb, 2 * ETH_HLEN + tag_ops->needed_headroom);
+	skb_reset_network_header(skb);
+	skb->pkt_type = PACKET_OUTGOING;
+	skb->dev = chip->rmu.netdev;
+
+	/* Create RMU L3 message */
+	data = skb_put(skb, len);
+	memcpy(data, msg, len);
+
+	return tag_ops->inband_xmit(skb, dp->slave, ++chip->rmu.seq_no);
+}
+
+static int mv88e6xxx_rmu_send_wait(struct mv88e6xxx_chip *chip, int port,
+				   int request, const char *msg, int len)
+{
+	const struct dsa_port *dp;
+	struct net_device *master;
+	int ret = 0;
+
+	dp = dsa_to_port(chip->ds, port);
+	if (!dp)
+		return 0;
+
+	master = dp->master;
+
+	mutex_lock(&chip->rmu.mutex);
+
+	chip->rmu.request_cmd = request;
+
+	ret = mv88e6xxx_rmu_tx(chip, port, msg, len);
+	if (ret == -ENODEV) {
+		/* Device not ready yet? Try again later */
+		ret = 0;
+		goto out;
+	}
+
+	if (ret) {
+		dev_err(chip->dev, "RMU: error transmitting request (%d)", ret);
+		goto out;
+	}
+
+	ret = wait_for_completion_timeout(&chip->rmu.completion,
+					  msecs_to_jiffies(MV88E6XXX_WAIT_POLL_TIME_MS));
+	if (ret == 0) {
+		dev_err(chip->dev,
+			"RMU: timeout waiting for request %d (%d) on dev:port %d:%d\n",
+			request, ret, chip->ds->index, port);
+		ret = -ETIMEDOUT;
+	}
+
+out:
+	mutex_unlock(&chip->rmu.mutex);
+
+	return ret > 0 ? 0 : ret;
+}
+
+static int mv88e6xxx_rmu_get_id(struct mv88e6xxx_chip *chip, int port)
+{
+	const u8 get_id[8] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
+	int ret = -1;
+
+	if (chip->rmu.got_id)
+		return 0;
+
+	chip->rmu.netdev = dev_get_by_name(&init_net, "chan0");
+	if (!chip->rmu.netdev) {
+		dev_err(chip->dev, "RMU: unable to get interface");
+		return -ENODEV;
+	}
+
+	ret = mv88e6xxx_rmu_send_wait(chip, port, RMU_REQ_GET_ID, get_id, 8);
+	if (ret) {
+		dev_err(chip->dev, "RMU: error for command GET_ID %d index %d\n", ret,
+			chip->ds->index);
+		return ret;
+	}
+
+	return 0;
+}
+
+int mv88e6xxx_rmu_stats_get(struct mv88e6xxx_chip *chip, int port, uint64_t *data)
+{
+	u8 dump_mib[8] = { 0x00, 0x01, 0x00, 0x00, 0x10, 0x20, 0x00, 0x00 };
+	int ret;
+
+	if (!chip)
+		return 0;
+
+	ret = mv88e6xxx_rmu_get_id(chip, port);
+	if (ret)
+		return ret;
+
+	/* Send a GET_MIB command */
+	dump_mib[7] = port;
+	ret = mv88e6xxx_rmu_send_wait(chip, port, RMU_REQ_DUMP_MIB, dump_mib, 8);
+	if (ret) {
+		dev_err(chip->dev, "RMU: error for command DUMP_MIB %d dev %d:%d\n", ret,
+			chip->ds->index, port);
+		return ret;
+	}
+
+	/* Update MIB for port */
+	if (chip->info->ops->stats_get_stats)
+		return chip->info->ops->stats_get_stats(chip, port, data);
+
+	return 0;
+}
+
+static struct mv88e6xxx_bus_ops mv88e6xxx_bus_ops = {
+	.get_rmon = mv88e6xxx_rmu_stats_get,
+};
+
+int mv88e6xxx_rmu_init(struct mv88e6xxx_chip *chip)
+{
+	int ret = 0;
+
+	dev_info(chip->dev, "RMU: setting up for switch@%d", chip->sw_addr);
+
+	init_completion(&chip->rmu.completion);
+
+	mutex_init(&chip->rmu.mutex);
+
+	chip->rmu.ops = &mv88e6xxx_bus_ops;
+
+	return ret;
+}
diff --git a/drivers/net/dsa/mv88e6xxx/rmu.h b/drivers/net/dsa/mv88e6xxx/rmu.h
new file mode 100644
index 000000000000..3f74e952cad9
--- /dev/null
+++ b/drivers/net/dsa/mv88e6xxx/rmu.h
@@ -0,0 +1,18 @@ 
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * Marvell 88E6xxx Switch Remote Management Unit Support
+ *
+ * Copyright (c) 2022 Mattias Forsblad <mattias.forsblad@gmail.com>
+ *
+ */
+
+#ifndef _MV88E6XXX_RMU_H_
+#define _MV88E6XXX_RMU_H_
+
+#include "chip.h"
+
+int mv88e6xxx_rmu_init(struct mv88e6xxx_chip *chip);
+
+int mv88e6xxx_inband_rcv(struct dsa_switch *ds, struct sk_buff *skb, int seq_no);
+
+#endif /* _MV88E6XXX_RMU_H_ */