* [PATCH net-next 02/17] net: dsa: mv88e6xxx: Introduce Marvell dsa tagger data operation.
From: Luke Howard @ 2026-07-03 7:46 UTC (permalink / raw)
To: Andrew Lunn, Vladimir Oltean, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Simon Horman, Russell King,
Richard Cochran, Florian Fainelli
Cc: Cedric Jehasse, Max Holtmann, Max Hunter, Kieran Tyrrell,
Ryan Wilkins, Mattias Forsblad, netdev, linux-kernel, Luke Howard
In-Reply-To: <20260703-net-next-mv88e6xxx-rmu-v1-0-991a27d78bca@padl.com>
From: Mattias Forsblad <mattias.forsblad@gmail.com>
Support connecting Marvell dsa tagger to the switch driver frame2reg
function to decode received RMU frames.
(Note: during teardown or failed setup, tagger_data may be NULL when
a late FRAME2REG frame arrives.)
Signed-off-by: Luke Howard <lukeh@padl.com>
Signed-off-by: Mattias Forsblad <mattias.forsblad@gmail.com>
Signed-off-by: Andrew Lunn <andrew@lunn.ch>
---
include/linux/dsa/mv88e6xxx.h | 8 ++++++++
net/dsa/tag_dsa.c | 43 ++++++++++++++++++++++++++++++++++++++-----
2 files changed, 46 insertions(+), 5 deletions(-)
diff --git a/include/linux/dsa/mv88e6xxx.h b/include/linux/dsa/mv88e6xxx.h
index 8c3d45eca46bd..1e9460d6f7782 100644
--- a/include/linux/dsa/mv88e6xxx.h
+++ b/include/linux/dsa/mv88e6xxx.h
@@ -6,6 +6,14 @@
#define _NET_DSA_TAG_MV88E6XXX_H
#include <linux/if_vlan.h>
+#include <net/dsa.h>
+
+struct dsa_tagger_data {
+ /* DSA frame decoded to be from the RMU */
+ void (*rmu_frame2reg)(struct dsa_switch *ds,
+ struct sk_buff *skb,
+ u8 seqno);
+};
#define MV88E6XXX_VID_STANDALONE 0
#define MV88E6XXX_VID_BRIDGED (VLAN_N_VID - 1)
diff --git a/net/dsa/tag_dsa.c b/net/dsa/tag_dsa.c
index d5ffee35fbb53..5a95873e87340 100644
--- a/net/dsa/tag_dsa.c
+++ b/net/dsa/tag_dsa.c
@@ -201,14 +201,17 @@ static struct sk_buff *dsa_xmit_ll(struct sk_buff *skb, struct net_device *dev,
static struct sk_buff *dsa_rcv_ll(struct sk_buff *skb, struct net_device *dev,
u8 extra)
{
+ struct dsa_tagger_data *tagger_data;
bool trap = false, trunk = false;
int source_device, source_port;
+ struct dsa_switch *ds;
enum dsa_code code;
enum dsa_cmd cmd;
u8 *dsa_header;
/* The ethertype field is part of the DSA header. */
dsa_header = dsa_etype_header_pos_rx(skb);
+ source_device = dsa_header[0] & 0x1f;
cmd = dsa_header[0] >> 6;
switch (cmd) {
@@ -220,12 +223,20 @@ static struct sk_buff *dsa_rcv_ll(struct sk_buff *skb, struct net_device *dev,
code = (dsa_header[1] & 0x6) | ((dsa_header[2] >> 4) & 1);
switch (code) {
- case DSA_CODE_FRAME2REG:
- /* Remote management is not implemented yet,
- * drop.
- */
+ case DSA_CODE_FRAME2REG: {
+ u8 seqno = dsa_header[3];
+
+ ds = dsa_conduit_find_switch(dev, source_device);
+ if (!ds) {
+ kfree_skb(skb);
+ return NULL;
+ }
+ tagger_data = ds->tagger_data;
+ if (likely(tagger_data && tagger_data->rmu_frame2reg))
+ tagger_data->rmu_frame2reg(ds, skb, seqno);
kfree_skb(skb);
return NULL;
+ }
case DSA_CODE_ARP_MIRROR:
case DSA_CODE_POLICY_MIRROR:
/* Mark mirrored packets to notify any upper
@@ -256,7 +267,6 @@ static struct sk_buff *dsa_rcv_ll(struct sk_buff *skb, struct net_device *dev,
return NULL;
}
- source_device = dsa_header[0] & 0x1f;
source_port = (dsa_header[1] >> 3) & 0x1f;
if (trunk) {
@@ -331,6 +341,25 @@ static struct sk_buff *dsa_rcv_ll(struct sk_buff *skb, struct net_device *dev,
return skb;
}
+static int dsa_tag_connect(struct dsa_switch *ds)
+{
+ struct dsa_tagger_data *tagger_data;
+
+ tagger_data = kzalloc_obj(*tagger_data, GFP_KERNEL);
+ if (!tagger_data)
+ return -ENOMEM;
+
+ ds->tagger_data = tagger_data;
+
+ return 0;
+}
+
+static void dsa_tag_disconnect(struct dsa_switch *ds)
+{
+ kfree(ds->tagger_data);
+ ds->tagger_data = NULL;
+}
+
#if IS_ENABLED(CONFIG_NET_DSA_TAG_DSA)
static struct sk_buff *dsa_xmit(struct sk_buff *skb, struct net_device *dev)
@@ -353,6 +382,8 @@ static const struct dsa_device_ops dsa_netdev_ops = {
.proto = DSA_TAG_PROTO_DSA,
.xmit = dsa_xmit,
.rcv = dsa_rcv,
+ .connect = dsa_tag_connect,
+ .disconnect = dsa_tag_disconnect,
.needed_headroom = DSA_HLEN,
};
@@ -397,6 +428,8 @@ static const struct dsa_device_ops edsa_netdev_ops = {
.proto = DSA_TAG_PROTO_EDSA,
.xmit = edsa_xmit,
.rcv = edsa_rcv,
+ .connect = dsa_tag_connect,
+ .disconnect = dsa_tag_disconnect,
.needed_headroom = EDSA_HLEN,
};
--
2.43.0
^ permalink raw reply related
* [PATCH net-next 03/17] net: dsa: tag_dsa: Add helper to add DSA header to RMU frame
From: Luke Howard @ 2026-07-03 7:46 UTC (permalink / raw)
To: Andrew Lunn, Vladimir Oltean, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Simon Horman, Russell King,
Richard Cochran, Florian Fainelli
Cc: Cedric Jehasse, Max Holtmann, Max Hunter, Kieran Tyrrell,
Ryan Wilkins, Mattias Forsblad, netdev, linux-kernel, Luke Howard
In-Reply-To: <20260703-net-next-mv88e6xxx-rmu-v1-0-991a27d78bca@padl.com>
From: Andrew Lunn <andrew@lunn.ch>
A RMU frame sent to the switch needs a DSA header. Add a helper to the
tag driver to add such a header to an skbuff, and provide access to
the helper via the tagger data structure. This keeps most of the
details of the DSA header in the tag driver.
Signed-off-by: Andrew Lunn <andrew@lunn.ch>
---
include/linux/dsa/mv88e6xxx.h | 3 +++
net/dsa/tag_dsa.c | 24 ++++++++++++++++++++++++
2 files changed, 27 insertions(+)
diff --git a/include/linux/dsa/mv88e6xxx.h b/include/linux/dsa/mv88e6xxx.h
index 1e9460d6f7782..74a885b3bc4cb 100644
--- a/include/linux/dsa/mv88e6xxx.h
+++ b/include/linux/dsa/mv88e6xxx.h
@@ -13,6 +13,9 @@ struct dsa_tagger_data {
void (*rmu_frame2reg)(struct dsa_switch *ds,
struct sk_buff *skb,
u8 seqno);
+ /* Add DSA header to frame to be sent to switch */
+ void (*rmu_reg2frame)(struct dsa_switch *ds,
+ struct sk_buff *skb);
};
#define MV88E6XXX_VID_STANDALONE 0
diff --git a/net/dsa/tag_dsa.c b/net/dsa/tag_dsa.c
index 5a95873e87340..a23c3279c49bd 100644
--- a/net/dsa/tag_dsa.c
+++ b/net/dsa/tag_dsa.c
@@ -341,6 +341,29 @@ static struct sk_buff *dsa_rcv_ll(struct sk_buff *skb, struct net_device *dev,
return skb;
}
+static void dsa_rmu_reg2frame(struct dsa_switch *ds,
+ struct sk_buff *skb)
+{
+ enum dsa_cmd cmd = DSA_CMD_FROM_CPU;
+ u8 tag_dev = ds->index;
+ u8 *dsa_header;
+
+ dsa_header = skb_push(skb, DSA_HLEN);
+ dsa_header[0] = (cmd << 6) | tag_dev;
+ dsa_header[1] = BIT(1) | 0x3e << 2;
+ dsa_header[2] = 6 << 5 | 0xf;
+ dsa_header[3] = 0; /* Sequence number is filled in later */
+
+ if (ds->dst->tag_ops->proto == DSA_TAG_PROTO_EDSA) {
+ u8 *edsa_header = skb_push(skb, 4);
+
+ edsa_header[0] = (ETH_P_EDSA >> 8) & 0xff;
+ edsa_header[1] = ETH_P_EDSA & 0xff;
+ edsa_header[2] = 0x00;
+ edsa_header[3] = 0x00;
+ }
+}
+
static int dsa_tag_connect(struct dsa_switch *ds)
{
struct dsa_tagger_data *tagger_data;
@@ -349,6 +372,7 @@ static int dsa_tag_connect(struct dsa_switch *ds)
if (!tagger_data)
return -ENOMEM;
+ tagger_data->rmu_reg2frame = dsa_rmu_reg2frame;
ds->tagger_data = tagger_data;
return 0;
--
2.43.0
^ permalink raw reply related
* [PATCH net-next 04/17] net: dsa: mv88e6xxx: Add RMU enable/disable for select switches.
From: Luke Howard @ 2026-07-03 7:46 UTC (permalink / raw)
To: Andrew Lunn, Vladimir Oltean, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Simon Horman, Russell King,
Richard Cochran, Florian Fainelli
Cc: Cedric Jehasse, Max Holtmann, Max Hunter, Kieran Tyrrell,
Ryan Wilkins, Mattias Forsblad, netdev, linux-kernel, Luke Howard
In-Reply-To: <20260703-net-next-mv88e6xxx-rmu-v1-0-991a27d78bca@padl.com>
From: Mattias Forsblad <mattias.forsblad@gmail.com>
Add RMU enable and disable functionality for some Marvell SOHO
switches.
Reviewed-by: Andrew Lunn <andrew@lunn.ch>
Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
Signed-off-by: Mattias Forsblad <mattias.forsblad@gmail.com>
Signed-off-by: Andrew Lunn <andrew@lunn.ch>
---
drivers/net/dsa/mv88e6xxx/chip.c | 15 +++++++++
drivers/net/dsa/mv88e6xxx/chip.h | 1 +
drivers/net/dsa/mv88e6xxx/global1.c | 64 +++++++++++++++++++++++++++++++++++++
drivers/net/dsa/mv88e6xxx/global1.h | 3 ++
4 files changed, 83 insertions(+)
diff --git a/drivers/net/dsa/mv88e6xxx/chip.c b/drivers/net/dsa/mv88e6xxx/chip.c
index eada2f71cb7b3..dbfbdb982fc00 100644
--- a/drivers/net/dsa/mv88e6xxx/chip.c
+++ b/drivers/net/dsa/mv88e6xxx/chip.c
@@ -4265,6 +4265,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,
@@ -4343,6 +4344,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,
@@ -4477,6 +4479,7 @@ static const struct mv88e6xxx_ops mv88e6141_ops = {
.hardware_reset_post = mv88e6xxx_g2_eeprom_wait,
.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 = mv88e6352_g1_vtu_getnext,
@@ -4670,6 +4673,7 @@ static const struct mv88e6xxx_ops mv88e6172_ops = {
.hardware_reset_post = mv88e6xxx_g2_eeprom_wait,
.reset = mv88e6352_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,
@@ -4774,6 +4778,7 @@ static const struct mv88e6xxx_ops mv88e6176_ops = {
.hardware_reset_post = mv88e6xxx_g2_eeprom_wait,
.reset = mv88e6352_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,
@@ -4869,6 +4874,7 @@ static const struct mv88e6xxx_ops mv88e6190_ops = {
.hardware_reset_post = mv88e6xxx_g2_eeprom_wait,
.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,
@@ -4928,6 +4934,7 @@ static const struct mv88e6xxx_ops mv88e6190x_ops = {
.hardware_reset_post = mv88e6xxx_g2_eeprom_wait,
.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,
@@ -4985,6 +4992,7 @@ static const struct mv88e6xxx_ops mv88e6191_ops = {
.hardware_reset_post = mv88e6xxx_g2_eeprom_wait,
.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,
@@ -5047,6 +5055,7 @@ static const struct mv88e6xxx_ops mv88e6240_ops = {
.hardware_reset_post = mv88e6xxx_g2_eeprom_wait,
.reset = mv88e6352_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,
@@ -5150,6 +5159,7 @@ static const struct mv88e6xxx_ops mv88e6290_ops = {
.hardware_reset_post = mv88e6xxx_g2_eeprom_wait,
.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,
@@ -5330,6 +5340,7 @@ static const struct mv88e6xxx_ops mv88e6341_ops = {
.hardware_reset_post = mv88e6xxx_g2_eeprom_wait,
.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 = mv88e6352_g1_vtu_getnext,
@@ -5488,6 +5499,7 @@ static const struct mv88e6xxx_ops mv88e6352_ops = {
.hardware_reset_post = mv88e6xxx_g2_eeprom_wait,
.reset = mv88e6352_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,
@@ -5551,6 +5563,7 @@ static const struct mv88e6xxx_ops mv88e6390_ops = {
.hardware_reset_post = mv88e6xxx_g2_eeprom_wait,
.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,
@@ -5615,6 +5628,7 @@ static const struct mv88e6xxx_ops mv88e6390x_ops = {
.hardware_reset_post = mv88e6xxx_g2_eeprom_wait,
.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,
@@ -5682,6 +5696,7 @@ static const struct mv88e6xxx_ops mv88e6393x_ops = {
.hardware_reset_post = mv88e6xxx_g2_eeprom_wait,
.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,
diff --git a/drivers/net/dsa/mv88e6xxx/chip.h b/drivers/net/dsa/mv88e6xxx/chip.h
index e966e7c4cc5de..12fdcb63252eb 100644
--- a/drivers/net/dsa/mv88e6xxx/chip.h
+++ b/drivers/net/dsa/mv88e6xxx/chip.h
@@ -701,6 +701,7 @@ struct mv88e6xxx_ops {
/* Remote Management Unit operations */
int (*rmu_disable)(struct mv88e6xxx_chip *chip);
+ int (*rmu_enable)(struct mv88e6xxx_chip *chip, int port);
/* 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 9820cd5967574..ae0b6e5628184 100644
--- a/drivers/net/dsa/mv88e6xxx/global1.c
+++ b/drivers/net/dsa/mv88e6xxx/global1.c
@@ -536,18 +536,82 @@ 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 port)
+{
+ int val = MV88E6352_G1_CTL2_RMU_MODE_DISABLED;
+
+ switch (port) {
+ case 9:
+ val = MV88E6085_G1_CTL2_RM_ENABLE;
+ break;
+ case 10:
+ val = MV88E6085_G1_CTL2_RM_ENABLE | MV88E6085_G1_CTL2_P10RM;
+ break;
+ default:
+ 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 port)
+{
+ int val = MV88E6352_G1_CTL2_RMU_MODE_DISABLED;
+
+ switch (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:
+ return -EOPNOTSUPP;
+ }
+
+ 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 port)
+{
+ int val = MV88E6390_G1_CTL2_RMU_MODE_DISABLED;
+
+ switch (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:
+ return -EOPNOTSUPP;
+ }
+
+ 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 3dbb7a1b8fe11..4624d1bdfc243 100644
--- a/drivers/net/dsa/mv88e6xxx/global1.h
+++ b/drivers/net/dsa/mv88e6xxx/global1.h
@@ -316,8 +316,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 port);
int mv88e6352_g1_rmu_disable(struct mv88e6xxx_chip *chip);
+int mv88e6352_g1_rmu_enable(struct mv88e6xxx_chip *chip, int port);
int mv88e6390_g1_rmu_disable(struct mv88e6xxx_chip *chip);
+int mv88e6390_g1_rmu_enable(struct mv88e6xxx_chip *chip, int port);
int mv88e6xxx_g1_set_device_number(struct mv88e6xxx_chip *chip, int index);
--
2.43.0
^ permalink raw reply related
* [PATCH net-next 05/17] net: dsa: mv88e6xxx: Add conduit state change handler
From: Luke Howard @ 2026-07-03 7:46 UTC (permalink / raw)
To: Andrew Lunn, Vladimir Oltean, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Simon Horman, Russell King,
Richard Cochran, Florian Fainelli
Cc: Cedric Jehasse, Max Holtmann, Max Hunter, Kieran Tyrrell,
Ryan Wilkins, Mattias Forsblad, netdev, linux-kernel, Luke Howard
In-Reply-To: <20260703-net-next-mv88e6xxx-rmu-v1-0-991a27d78bca@padl.com>
From: Andrew Lunn <andrew@lunn.ch>
When the conduit interface comes up, try to enable the RMU. Failing to
enable the remote management unit is not fatal, not all chips support
it, and some boards have chips which do support RMU, but with wrong
port is connected to the CPU.
When the conduit interface goes down, disable the RMU.
Keep track of the conduit interface, so that RMU frames can be sent to
it.
Co-developed: Mattias Forsblad <mattias.forsblad@gmail.com>
Signed-off-by: Mattias Forsblad <mattias.forsblad@gmail.com>
Signed-off-by: Andrew Lunn <andrew@lunn.ch>
---
drivers/net/dsa/mv88e6xxx/Makefile | 1 +
drivers/net/dsa/mv88e6xxx/chip.c | 2 ++
drivers/net/dsa/mv88e6xxx/chip.h | 3 +++
drivers/net/dsa/mv88e6xxx/rmu.c | 50 ++++++++++++++++++++++++++++++++++++++
drivers/net/dsa/mv88e6xxx/rmu.h | 15 ++++++++++++
5 files changed, 71 insertions(+)
diff --git a/drivers/net/dsa/mv88e6xxx/Makefile b/drivers/net/dsa/mv88e6xxx/Makefile
index b0b08c6f159c6..1f3571061a550 100644
--- a/drivers/net/dsa/mv88e6xxx/Makefile
+++ b/drivers/net/dsa/mv88e6xxx/Makefile
@@ -17,6 +17,7 @@ mv88e6xxx-objs += phy.o
mv88e6xxx-objs += port.o
mv88e6xxx-objs += port_hidden.o
mv88e6xxx-$(CONFIG_NET_DSA_MV88E6XXX_PTP) += ptp.o
+mv88e6xxx-objs += rmu.o
mv88e6xxx-objs += serdes.o
mv88e6xxx-objs += smi.o
mv88e6xxx-objs += switchdev.o
diff --git a/drivers/net/dsa/mv88e6xxx/chip.c b/drivers/net/dsa/mv88e6xxx/chip.c
index dbfbdb982fc00..d6c6834cca2cb 100644
--- a/drivers/net/dsa/mv88e6xxx/chip.c
+++ b/drivers/net/dsa/mv88e6xxx/chip.c
@@ -41,6 +41,7 @@
#include "phy.h"
#include "port.h"
#include "ptp.h"
+#include "rmu.h"
#include "serdes.h"
#include "smi.h"
#include "tcam.h"
@@ -7265,6 +7266,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,
+ .conduit_state_change = mv88e6xxx_rmu_conduit_state_change,
};
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 12fdcb63252eb..76958b588a908 100644
--- a/drivers/net/dsa/mv88e6xxx/chip.h
+++ b/drivers/net/dsa/mv88e6xxx/chip.h
@@ -457,6 +457,9 @@ struct mv88e6xxx_chip {
/* Global2 scratch register config data3 */
u8 g2_scratch_config3;
+
+ /* Remote Management Unit state. */
+ struct net_device *rmu_conduit;
};
#define TCAM_MATCH_SIZE 96
diff --git a/drivers/net/dsa/mv88e6xxx/rmu.c b/drivers/net/dsa/mv88e6xxx/rmu.c
new file mode 100644
index 0000000000000..64e93e2eaf550
--- /dev/null
+++ b/drivers/net/dsa/mv88e6xxx/rmu.c
@@ -0,0 +1,50 @@
+// 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 <net/dsa.h>
+#include "chip.h"
+#include "global1.h"
+#include "rmu.h"
+
+void mv88e6xxx_rmu_conduit_state_change(struct dsa_switch *ds,
+ const struct net_device *conduit,
+ bool operational)
+{
+ struct dsa_port *cpu_dp = conduit->dsa_ptr;
+ struct mv88e6xxx_chip *chip = ds->priv;
+ int port;
+ int ret;
+
+ port = dsa_towards_port(ds, cpu_dp->ds->index, cpu_dp->index);
+
+ mv88e6xxx_reg_lock(chip);
+
+ if (operational && chip->info->ops->rmu_enable) {
+ ret = chip->info->ops->rmu_enable(chip, port);
+ if (ret == -EOPNOTSUPP) {
+ dev_info(chip->dev, "RMU: not usable on this board");
+ goto out;
+ } else if (ret < 0) {
+ dev_err(chip->dev, "RMU: unable to enable on port %d %pe",
+ port, ERR_PTR(ret));
+ goto out;
+ }
+
+ WRITE_ONCE(chip->rmu_conduit, (struct net_device *)conduit);
+
+ dev_dbg(chip->dev, "RMU: Enabled on port %d", port);
+ } else {
+ if (chip->info->ops->rmu_disable)
+ chip->info->ops->rmu_disable(chip);
+
+ WRITE_ONCE(chip->rmu_conduit, NULL);
+ }
+
+out:
+ mv88e6xxx_reg_unlock(chip);
+}
diff --git a/drivers/net/dsa/mv88e6xxx/rmu.h b/drivers/net/dsa/mv88e6xxx/rmu.h
new file mode 100644
index 0000000000000..71c4fc1d4113f
--- /dev/null
+++ b/drivers/net/dsa/mv88e6xxx/rmu.h
@@ -0,0 +1,15 @@
+/* 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_
+
+void mv88e6xxx_rmu_conduit_state_change(struct dsa_switch *ds,
+ const struct net_device *conduit,
+ bool operational);
+#endif /* _MV88E6XXX_RMU_H_ */
--
2.43.0
^ permalink raw reply related
* [PATCH net-next 06/17] net: dsa: mv88e6xxx: Remove mv88e6xxx_rmu_setup()
From: Luke Howard @ 2026-07-03 7:46 UTC (permalink / raw)
To: Andrew Lunn, Vladimir Oltean, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Simon Horman, Russell King,
Richard Cochran, Florian Fainelli
Cc: Cedric Jehasse, Max Holtmann, Max Hunter, Kieran Tyrrell,
Ryan Wilkins, Mattias Forsblad, netdev, linux-kernel, Luke Howard
In-Reply-To: <20260703-net-next-mv88e6xxx-rmu-v1-0-991a27d78bca@padl.com>
From: Andrew Lunn <andrew@lunn.ch>
Now that the RMU is enabled/disabled in the master state change
handler, remove the setup of the RMU in the mv88e6xxx_setup().
Signed-off-by: Andrew Lunn <andrew@lunn.ch>
---
drivers/net/dsa/mv88e6xxx/chip.c | 12 ------------
1 file changed, 12 deletions(-)
diff --git a/drivers/net/dsa/mv88e6xxx/chip.c b/drivers/net/dsa/mv88e6xxx/chip.c
index d6c6834cca2cb..72c93cb84b7e1 100644
--- a/drivers/net/dsa/mv88e6xxx/chip.c
+++ b/drivers/net/dsa/mv88e6xxx/chip.c
@@ -1676,14 +1676,6 @@ static int mv88e6xxx_trunk_setup(struct mv88e6xxx_chip *chip)
return 0;
}
-static int mv88e6xxx_rmu_setup(struct mv88e6xxx_chip *chip)
-{
- if (chip->info->ops->rmu_disable)
- return chip->info->ops->rmu_disable(chip);
-
- return 0;
-}
-
static int mv88e6xxx_pot_setup(struct mv88e6xxx_chip *chip)
{
if (chip->info->ops->pot_clear)
@@ -4080,10 +4072,6 @@ static int mv88e6xxx_setup(struct dsa_switch *ds)
if (err)
goto unlock;
- err = mv88e6xxx_rmu_setup(chip);
- if (err)
- goto unlock;
-
err = mv88e6xxx_rsvd2cpu_setup(chip);
if (err)
goto unlock;
--
2.43.0
^ permalink raw reply related
* [PATCH net-next 07/17] net: dsa: mv88e6xxx: Add connect_tag_protocol handler
From: Luke Howard @ 2026-07-03 7:46 UTC (permalink / raw)
To: Andrew Lunn, Vladimir Oltean, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Simon Horman, Russell King,
Richard Cochran, Florian Fainelli
Cc: Cedric Jehasse, Max Holtmann, Max Hunter, Kieran Tyrrell,
Ryan Wilkins, Mattias Forsblad, netdev, linux-kernel, Luke Howard
In-Reply-To: <20260703-net-next-mv88e6xxx-rmu-v1-0-991a27d78bca@padl.com>
From: Andrew Lunn <andrew@lunn.ch>
When the tagging protocol handler is attached to the DSA driver, it
can validate it is a supported tagging protocol. It can also setup
data shared between the tagger and the switch driver. Ensure a Mavell
DSA tagger is being connected, and setup the handler from RMU in the
shared data. At this point, the handler is just an empty stub, but
later patches will add the needed functionality.
Signed-off-by: Andrew Lunn <andrew@lunn.ch>
---
drivers/net/dsa/mv88e6xxx/chip.c | 18 ++++++++++++++++++
drivers/net/dsa/mv88e6xxx/rmu.c | 6 ++++++
drivers/net/dsa/mv88e6xxx/rmu.h | 3 +++
3 files changed, 27 insertions(+)
diff --git a/drivers/net/dsa/mv88e6xxx/chip.c b/drivers/net/dsa/mv88e6xxx/chip.c
index 72c93cb84b7e1..fe0d0eb8f0813 100644
--- a/drivers/net/dsa/mv88e6xxx/chip.c
+++ b/drivers/net/dsa/mv88e6xxx/chip.c
@@ -7183,6 +7183,23 @@ static int mv88e6xxx_crosschip_lag_leave(struct dsa_switch *ds, int sw_index,
return err_sync ? : err_pvt;
}
+static int mv88e6xxx_connect_tag_protocol(struct dsa_switch *ds,
+ enum dsa_tag_protocol proto)
+{
+ struct dsa_tagger_data *tagger_data = ds->tagger_data;
+
+ switch (proto) {
+ case DSA_TAG_PROTO_DSA:
+ case DSA_TAG_PROTO_EDSA:
+ tagger_data->rmu_frame2reg = mv88e6xxx_rmu_frame2reg_handler;
+ break;
+ default:
+ return -EPROTONOSUPPORT;
+ }
+
+ return 0;
+}
+
static const struct phylink_mac_ops mv88e6xxx_phylink_mac_ops = {
.mac_select_pcs = mv88e6xxx_mac_select_pcs,
.mac_prepare = mv88e6xxx_mac_prepare,
@@ -7255,6 +7272,7 @@ static const struct dsa_switch_ops mv88e6xxx_switch_ops = {
.crosschip_lag_join = mv88e6xxx_crosschip_lag_join,
.crosschip_lag_leave = mv88e6xxx_crosschip_lag_leave,
.conduit_state_change = mv88e6xxx_rmu_conduit_state_change,
+ .connect_tag_protocol = mv88e6xxx_connect_tag_protocol,
};
static int mv88e6xxx_register_switch(struct mv88e6xxx_chip *chip)
diff --git a/drivers/net/dsa/mv88e6xxx/rmu.c b/drivers/net/dsa/mv88e6xxx/rmu.c
index 64e93e2eaf550..bae0ca6564f7a 100644
--- a/drivers/net/dsa/mv88e6xxx/rmu.c
+++ b/drivers/net/dsa/mv88e6xxx/rmu.c
@@ -48,3 +48,9 @@ void mv88e6xxx_rmu_conduit_state_change(struct dsa_switch *ds,
out:
mv88e6xxx_reg_unlock(chip);
}
+
+void mv88e6xxx_rmu_frame2reg_handler(struct dsa_switch *ds,
+ struct sk_buff *skb,
+ u8 seqno)
+{
+}
diff --git a/drivers/net/dsa/mv88e6xxx/rmu.h b/drivers/net/dsa/mv88e6xxx/rmu.h
index 71c4fc1d4113f..c60f32480e317 100644
--- a/drivers/net/dsa/mv88e6xxx/rmu.h
+++ b/drivers/net/dsa/mv88e6xxx/rmu.h
@@ -12,4 +12,7 @@
void mv88e6xxx_rmu_conduit_state_change(struct dsa_switch *ds,
const struct net_device *conduit,
bool operational);
+void mv88e6xxx_rmu_frame2reg_handler(struct dsa_switch *ds,
+ struct sk_buff *skb,
+ u8 seqno);
#endif /* _MV88E6XXX_RMU_H_ */
--
2.43.0
^ permalink raw reply related
* [PATCH net-next 08/17] net: dsa: mv88e6xxx: Add sanity check of received RMU frame
From: Luke Howard @ 2026-07-03 7:46 UTC (permalink / raw)
To: Andrew Lunn, Vladimir Oltean, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Simon Horman, Russell King,
Richard Cochran, Florian Fainelli
Cc: Cedric Jehasse, Max Holtmann, Max Hunter, Kieran Tyrrell,
Ryan Wilkins, Mattias Forsblad, netdev, linux-kernel, Luke Howard
In-Reply-To: <20260703-net-next-mv88e6xxx-rmu-v1-0-991a27d78bca@padl.com>
From: Andrew Lunn <andrew@lunn.ch>
The received RMU frame is expected to be addressed to the MAC address
of the master interface. The sequence number should also match. Two
formats codes are accepted. Add these basic sanity checks, and drop
the frame is they are incorrect. To aid sequence number checking
introduce the use of the DSA inband helpers and its data structures.
Signed-off-by: Andrew Lunn <andrew@lunn.ch>
Signed-off-by: Luke Howard <lukeh@padl.com>
---
drivers/net/dsa/mv88e6xxx/chip.c | 2 ++
drivers/net/dsa/mv88e6xxx/chip.h | 1 +
drivers/net/dsa/mv88e6xxx/rmu.c | 42 ++++++++++++++++++++++++++++++++++++++++
drivers/net/dsa/mv88e6xxx/rmu.h | 9 +++++++++
4 files changed, 54 insertions(+)
diff --git a/drivers/net/dsa/mv88e6xxx/chip.c b/drivers/net/dsa/mv88e6xxx/chip.c
index fe0d0eb8f0813..88cc2326ecc0c 100644
--- a/drivers/net/dsa/mv88e6xxx/chip.c
+++ b/drivers/net/dsa/mv88e6xxx/chip.c
@@ -7462,6 +7462,8 @@ static int mv88e6xxx_probe(struct mdio_device *mdiodev)
if (err)
goto out_g1_atu_prob_irq;
+ dsa_inband_init(&chip->rmu_inband, U8_MAX);
+
err = mv88e6xxx_register_switch(chip);
if (err)
goto out_g1_vtu_prob_irq;
diff --git a/drivers/net/dsa/mv88e6xxx/chip.h b/drivers/net/dsa/mv88e6xxx/chip.h
index 76958b588a908..940ae698659fc 100644
--- a/drivers/net/dsa/mv88e6xxx/chip.h
+++ b/drivers/net/dsa/mv88e6xxx/chip.h
@@ -460,6 +460,7 @@ struct mv88e6xxx_chip {
/* Remote Management Unit state. */
struct net_device *rmu_conduit;
+ struct dsa_inband rmu_inband;
};
#define TCAM_MATCH_SIZE 96
diff --git a/drivers/net/dsa/mv88e6xxx/rmu.c b/drivers/net/dsa/mv88e6xxx/rmu.c
index bae0ca6564f7a..49d67d512fb0f 100644
--- a/drivers/net/dsa/mv88e6xxx/rmu.c
+++ b/drivers/net/dsa/mv88e6xxx/rmu.c
@@ -53,4 +53,46 @@ void mv88e6xxx_rmu_frame2reg_handler(struct dsa_switch *ds,
struct sk_buff *skb,
u8 seqno)
{
+ struct mv88e6xxx_rmu_header *rmu_header;
+ struct mv88e6xxx_chip *chip = ds->priv;
+ struct net_device *conduit;
+ unsigned char *ethhdr;
+ u8 expected_seqno;
+
+ /* Check received destination MAC is the conduit MAC address */
+ conduit = READ_ONCE(chip->rmu_conduit);
+ if (!conduit)
+ goto drop;
+
+ ethhdr = skb_mac_header(skb);
+ if (!ether_addr_equal(conduit->dev_addr, ethhdr)) {
+ dev_dbg_ratelimited(ds->dev, "RMU: mismatched MAC address for request: rx %pM expecting %pM\n",
+ ethhdr, conduit->dev_addr);
+ goto drop;
+ }
+
+ expected_seqno = dsa_inband_seqno(&chip->rmu_inband);
+ if (seqno != expected_seqno) {
+ dev_dbg_ratelimited(ds->dev, "RMU: mismatched seqno for request: rx %d expecting %d\n",
+ seqno, expected_seqno);
+ goto drop;
+ }
+
+ if (skb->len < 4 + sizeof(*rmu_header)) {
+ dev_dbg_ratelimited(ds->dev, "RMU: response too short (%d bytes)\n",
+ skb->len);
+ dsa_inband_complete(&chip->rmu_inband, NULL, 0, -ETIMEDOUT);
+ goto drop;
+ }
+
+ rmu_header = (struct mv88e6xxx_rmu_header *)(skb->data + 4);
+ if (rmu_header->format != MV88E6XXX_RMU_RESP_FORMAT_1 &&
+ rmu_header->format != MV88E6XXX_RMU_RESP_FORMAT_2) {
+ dev_dbg_ratelimited(ds->dev, "RMU: invalid format: rx %d\n",
+ be16_to_cpu(rmu_header->format));
+ goto drop;
+ }
+
+drop:
+ return;
}
diff --git a/drivers/net/dsa/mv88e6xxx/rmu.h b/drivers/net/dsa/mv88e6xxx/rmu.h
index c60f32480e317..69cd3c2636f3d 100644
--- a/drivers/net/dsa/mv88e6xxx/rmu.h
+++ b/drivers/net/dsa/mv88e6xxx/rmu.h
@@ -9,6 +9,15 @@
#ifndef _MV88E6XXX_RMU_H_
#define _MV88E6XXX_RMU_H_
+#define MV88E6XXX_RMU_RESP_FORMAT_1 htons(0x0001)
+#define MV88E6XXX_RMU_RESP_FORMAT_2 htons(0x0002)
+
+struct mv88e6xxx_rmu_header {
+ __be16 format;
+ __be16 prodnr;
+ __be16 code;
+} __packed;
+
void mv88e6xxx_rmu_conduit_state_change(struct dsa_switch *ds,
const struct net_device *conduit,
bool operational);
--
2.43.0
^ permalink raw reply related
* [PATCH net-next 09/17] net: dsa: mv88e6xxx: Get Product ID when enabling RMU
From: Luke Howard @ 2026-07-03 7:46 UTC (permalink / raw)
To: Andrew Lunn, Vladimir Oltean, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Simon Horman, Russell King,
Richard Cochran, Florian Fainelli
Cc: Cedric Jehasse, Max Holtmann, Max Hunter, Kieran Tyrrell,
Ryan Wilkins, Mattias Forsblad, netdev, linux-kernel, Luke Howard
In-Reply-To: <20260703-net-next-mv88e6xxx-rmu-v1-0-991a27d78bca@padl.com>
From: Andrew Lunn <andrew@lunn.ch>
When the RMU is enabled, it is recommended to get the product ID,
which identifies the switch. This is used as a test that communication
with the switch actually works and the RMU can be used. If successful,
mark the RMU usable.
This completes the general infrastructure to send a request and
process the response.
Co-developed: Mattias Forsblad <mattias.forsblad@gmail.com>
Signed-off-by: Mattias Forsblad <mattias.forsblad@gmail.com>
Signed-off-by: Andrew Lunn <andrew@lunn.ch>
Signed-off-by: Luke Howard <lukeh@padl.com>
---
drivers/net/dsa/mv88e6xxx/chip.h | 10 +++
drivers/net/dsa/mv88e6xxx/rmu.c | 151 ++++++++++++++++++++++++++++++++++++++-
drivers/net/dsa/mv88e6xxx/rmu.h | 11 +++
3 files changed, 170 insertions(+), 2 deletions(-)
diff --git a/drivers/net/dsa/mv88e6xxx/chip.h b/drivers/net/dsa/mv88e6xxx/chip.h
index 940ae698659fc..07300c25cbd36 100644
--- a/drivers/net/dsa/mv88e6xxx/chip.h
+++ b/drivers/net/dsa/mv88e6xxx/chip.h
@@ -346,6 +346,15 @@ struct mv88e6xxx_tcam {
struct list_head entries;
};
+enum mv88e6xxx_rmu_state {
+ MV88E6XXX_RMU_DISABLED = 0,
+ MV88E6XXX_RMU_ENABLED = 1,
+ /* Reserved: a future RMU-only mode (e.g. MV88E6XXX_RMU_ONLY_ENABLED)
+ * may be added here to indicate a switch operating purely via RMU
+ * without an MDIO bus.
+ */
+};
+
struct mv88e6xxx_chip {
const struct mv88e6xxx_info *info;
@@ -461,6 +470,7 @@ struct mv88e6xxx_chip {
/* Remote Management Unit state. */
struct net_device *rmu_conduit;
struct dsa_inband rmu_inband;
+ enum mv88e6xxx_rmu_state rmu_state;
};
#define TCAM_MATCH_SIZE 96
diff --git a/drivers/net/dsa/mv88e6xxx/rmu.c b/drivers/net/dsa/mv88e6xxx/rmu.c
index 49d67d512fb0f..65e6bcf4ca938 100644
--- a/drivers/net/dsa/mv88e6xxx/rmu.c
+++ b/drivers/net/dsa/mv88e6xxx/rmu.c
@@ -6,11 +6,143 @@
*
*/
+#include <linux/dsa/mv88e6xxx.h>
#include <net/dsa.h>
#include "chip.h"
#include "global1.h"
#include "rmu.h"
+static const u8 mv88e6xxx_rmu_dest_addr[ETH_ALEN] = {
+ 0x01, 0x50, 0x43, 0x00, 0x00, 0x00
+};
+
+static void mv88e6xxx_rmu_create_l2(struct dsa_switch *ds,
+ struct net_device *conduit,
+ struct sk_buff *skb,
+ bool edsa)
+{
+ struct dsa_tagger_data *tagger_data = ds->tagger_data;
+ struct ethhdr *eth;
+ u8 *header;
+
+ /* Create RMU L2 header. */
+ header = skb_push(skb, 2);
+ /* Two bytes of EtherType, which is ignored by the switch */
+ header[0] = 0;
+ header[1] = 0;
+
+ /* Ask tagger to add {E}DSA header */
+ tagger_data->rmu_reg2frame(ds, skb);
+
+ /* Insert RMU MAC destination address */
+ eth = skb_push(skb, ETH_ALEN * 2);
+ memcpy(eth->h_dest, mv88e6xxx_rmu_dest_addr, ETH_ALEN);
+ ether_addr_copy(eth->h_source, conduit->dev_addr);
+ skb_reset_network_header(skb);
+}
+
+static void mv88e6xxx_rmu_fill_seqno(struct sk_buff *skb, u32 seqno, int offset)
+{
+ u8 *dsa_header = skb->data + offset;
+
+ dsa_header[3] = seqno;
+}
+
+/* 2 MAC address, 2 byte Ethertype, 2 bytes padding, to DSA header */
+static void mv88e6xxx_rmu_fill_seqno_edsa(struct sk_buff *skb, u32 seqno)
+{
+ mv88e6xxx_rmu_fill_seqno(skb, seqno, ETH_ALEN * 2 + 2 + 2);
+}
+
+/* 2 MAC address, to DSA header */
+static void mv88e6xxx_rmu_fill_seqno_dsa(struct sk_buff *skb, u32 seqno)
+{
+ mv88e6xxx_rmu_fill_seqno(skb, seqno, ETH_ALEN * 2);
+}
+
+static int mv88e6xxx_rmu_request(struct mv88e6xxx_chip *chip,
+ const void *req, int req_len,
+ void *resp, unsigned int resp_len,
+ int timeout_ms)
+{
+ struct net_device *conduit;
+ struct sk_buff *skb;
+ unsigned char *data;
+ bool edsa;
+
+ /* Capture conduit once: it can be cleared concurrently when the
+ * conduit goes down (e.g. during reboot). Also guard against the
+ * conduit being torn down out from under us, in which case its
+ * dev_addr may already have been released.
+ */
+ conduit = READ_ONCE(chip->rmu_conduit);
+ if (unlikely(!conduit || !conduit->dev_addr)) {
+ dev_err_ratelimited(chip->dev, "RMU: conduit device unavailable");
+ return -EINVAL;
+ }
+
+ skb = dev_alloc_skb(req_len + EDSA_HLEN + 2 * ETH_ALEN + 2);
+ if (!skb)
+ return -ENOMEM;
+
+ /* Insert RMU request message */
+ data = skb_put(skb, req_len);
+ memcpy(data, req, req_len);
+
+ edsa = chip->tag_protocol == DSA_TAG_PROTO_EDSA;
+
+ if (!chip->ds->tagger_data) {
+ kfree_skb(skb);
+ return -EOPNOTSUPP; /* can happen on teardown */
+ }
+
+ mv88e6xxx_rmu_create_l2(chip->ds, conduit, skb, edsa);
+ skb->dev = conduit;
+
+ return dsa_inband_request(&chip->rmu_inband, skb,
+ (edsa ? mv88e6xxx_rmu_fill_seqno_edsa :
+ mv88e6xxx_rmu_fill_seqno_dsa),
+ resp, resp_len, timeout_ms);
+}
+
+static int mv88e6xxx_rmu_get_id(struct mv88e6xxx_chip *chip)
+{
+ const __be16 req[4] = {
+ MV88E6XXX_RMU_REQ_FORMAT_GET_ID,
+ MV88E6XXX_RMU_REQ_PAD,
+ MV88E6XXX_RMU_REQ_CODE_GET_ID,
+ MV88E6XXX_RMU_REQ_DATA};
+ struct mv88e6xxx_rmu_header resp;
+ int resp_len;
+ int ret = -1;
+
+ resp_len = sizeof(resp);
+ ret = mv88e6xxx_rmu_request(chip, req, sizeof(req),
+ &resp, resp_len,
+ MV88E6XXX_RMU_REQUEST_TIMEOUT_MS);
+ if (ret < 0) {
+ dev_dbg(chip->dev, "RMU: error for command GET_ID %pe\n",
+ ERR_PTR(ret));
+ return ret;
+ }
+
+ if (ret < resp_len) {
+ dev_err(chip->dev, "RMU: GET_ID returned wrong length: rx %d expecting %d\n",
+ ret, resp_len);
+ return -EPROTO;
+ }
+
+ if (resp.code != MV88E6XXX_RMU_RESP_CODE_GOT_ID) {
+ dev_dbg(chip->dev, "RMU: GET_ID returned wrong code %d\n",
+ be16_to_cpu(resp.code));
+ return -EPROTO;
+ }
+
+ dev_dbg(chip->dev, "RMU: product ID %4x\n", be16_to_cpu(resp.prodnr));
+
+ return 0;
+}
+
void mv88e6xxx_rmu_conduit_state_change(struct dsa_switch *ds,
const struct net_device *conduit,
bool operational)
@@ -37,11 +169,22 @@ void mv88e6xxx_rmu_conduit_state_change(struct dsa_switch *ds,
WRITE_ONCE(chip->rmu_conduit, (struct net_device *)conduit);
- dev_dbg(chip->dev, "RMU: Enabled on port %d", port);
+ /* Get the device ID to prove that the RMU works */
+ ret = mv88e6xxx_rmu_get_id(chip);
+ if (ret < 0) {
+ dev_err(chip->dev, "RMU: initialization check failed %pe",
+ ERR_PTR(ret));
+ goto out;
+ }
+ chip->rmu_state = MV88E6XXX_RMU_ENABLED;
+
+ dev_info(chip->dev, "RMU: enabled on port %d via conduit device %s",
+ port, chip->rmu_conduit->name);
} else {
if (chip->info->ops->rmu_disable)
chip->info->ops->rmu_disable(chip);
+ chip->rmu_state = MV88E6XXX_RMU_DISABLED;
WRITE_ONCE(chip->rmu_conduit, NULL);
}
@@ -58,6 +201,8 @@ void mv88e6xxx_rmu_frame2reg_handler(struct dsa_switch *ds,
struct net_device *conduit;
unsigned char *ethhdr;
u8 expected_seqno;
+ int resp_len;
+ int err = 0;
/* Check received destination MAC is the conduit MAC address */
conduit = READ_ONCE(chip->rmu_conduit);
@@ -86,13 +231,15 @@ void mv88e6xxx_rmu_frame2reg_handler(struct dsa_switch *ds,
}
rmu_header = (struct mv88e6xxx_rmu_header *)(skb->data + 4);
+ resp_len = skb->len - 4;
if (rmu_header->format != MV88E6XXX_RMU_RESP_FORMAT_1 &&
rmu_header->format != MV88E6XXX_RMU_RESP_FORMAT_2) {
dev_dbg_ratelimited(ds->dev, "RMU: invalid format: rx %d\n",
be16_to_cpu(rmu_header->format));
- goto drop;
+ err = -EPROTO;
}
+ dsa_inband_complete(&chip->rmu_inband, rmu_header, resp_len, err);
drop:
return;
}
diff --git a/drivers/net/dsa/mv88e6xxx/rmu.h b/drivers/net/dsa/mv88e6xxx/rmu.h
index 69cd3c2636f3d..a81af5a1ad762 100644
--- a/drivers/net/dsa/mv88e6xxx/rmu.h
+++ b/drivers/net/dsa/mv88e6xxx/rmu.h
@@ -9,9 +9,20 @@
#ifndef _MV88E6XXX_RMU_H_
#define _MV88E6XXX_RMU_H_
+#define MV88E6XXX_RMU_WAIT_TIME_MS 20
+#define MV88E6XXX_RMU_REQUEST_TIMEOUT_MS 50
+
+#define MV88E6XXX_RMU_REQ_FORMAT_GET_ID htons(0x0000)
+#define MV88E6XXX_RMU_REQ_FORMAT_SOHO htons(0x0001)
+#define MV88E6XXX_RMU_REQ_PAD htons(0x0000)
+#define MV88E6XXX_RMU_REQ_CODE_GET_ID htons(0x0000)
+#define MV88E6XXX_RMU_REQ_DATA htons(0x0000)
+
#define MV88E6XXX_RMU_RESP_FORMAT_1 htons(0x0001)
#define MV88E6XXX_RMU_RESP_FORMAT_2 htons(0x0002)
+#define MV88E6XXX_RMU_RESP_CODE_GOT_ID htons(0x0000)
+
struct mv88e6xxx_rmu_header {
__be16 format;
__be16 prodnr;
--
2.43.0
^ permalink raw reply related
* [PATCH net-next 10/17] net: dsa: mv88e6xxx: Add RMU register read/write/wait for bit
From: Luke Howard @ 2026-07-03 7:46 UTC (permalink / raw)
To: Andrew Lunn, Vladimir Oltean, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Simon Horman, Russell King,
Richard Cochran, Florian Fainelli
Cc: Cedric Jehasse, Max Holtmann, Max Hunter, Kieran Tyrrell,
Ryan Wilkins, Mattias Forsblad, netdev, linux-kernel, Luke Howard
In-Reply-To: <20260703-net-next-mv88e6xxx-rmu-v1-0-991a27d78bca@padl.com>
From: Andrew Lunn <andrew@lunn.ch>
Add support for reading a register, writing a register, and waiting
for a bit in a register to change, via the remote management unit.
If the RMU is not enabled, return -EOPNOTSUPP, and fall back to MDIO.
Additionally, if the operation times out, fall back to MDIO. Other
errors, such as protocol errors are however reported, rather than
falling back to MDIO.
Signed-off-by: Andrew Lunn <andrew@lunn.ch>
Signed-off-by: Luke Howard <lukeh@padl.com>
---
drivers/net/dsa/mv88e6xxx/chip.c | 18 +++--
drivers/net/dsa/mv88e6xxx/global2.c | 1 +
drivers/net/dsa/mv88e6xxx/rmu.c | 139 ++++++++++++++++++++++++++++++++++++
drivers/net/dsa/mv88e6xxx/rmu.h | 54 +++++++++++++-
4 files changed, 207 insertions(+), 5 deletions(-)
diff --git a/drivers/net/dsa/mv88e6xxx/chip.c b/drivers/net/dsa/mv88e6xxx/chip.c
index 88cc2326ecc0c..6431d25f3cfa2 100644
--- a/drivers/net/dsa/mv88e6xxx/chip.c
+++ b/drivers/net/dsa/mv88e6xxx/chip.c
@@ -65,7 +65,9 @@ int mv88e6xxx_read(struct mv88e6xxx_chip *chip, int addr, int reg, u16 *val)
assert_reg_lock(chip);
- err = mv88e6xxx_smi_read(chip, addr, reg, val);
+ err = mv88e6xxx_rmu_read(chip, addr, reg, val);
+ if (mv88e6xxx_rmu_can_mdio_fallback(chip, err))
+ err = mv88e6xxx_smi_read(chip, addr, reg, val);
if (err)
return err;
@@ -81,7 +83,9 @@ int mv88e6xxx_write(struct mv88e6xxx_chip *chip, int addr, int reg, u16 val)
assert_reg_lock(chip);
- err = mv88e6xxx_smi_write(chip, addr, reg, val);
+ err = mv88e6xxx_rmu_write(chip, addr, reg, val);
+ if (mv88e6xxx_rmu_can_mdio_fallback(chip, err))
+ err = mv88e6xxx_smi_write(chip, addr, reg, val);
if (err)
return err;
@@ -131,8 +135,14 @@ int mv88e6xxx_wait_mask(struct mv88e6xxx_chip *chip, int addr, int reg,
int mv88e6xxx_wait_bit(struct mv88e6xxx_chip *chip, int addr, int reg,
int bit, int val)
{
- return mv88e6xxx_wait_mask(chip, addr, reg, BIT(bit),
- val ? BIT(bit) : 0x0000);
+ int err;
+
+ err = mv88e6xxx_rmu_wait_bit(chip, addr, reg, bit, val);
+
+ if (mv88e6xxx_rmu_can_mdio_fallback(chip, err))
+ err = mv88e6xxx_wait_mask(chip, addr, reg, BIT(bit),
+ val ? BIT(bit) : 0x0000);
+ return err;
}
struct mii_bus *mv88e6xxx_default_mdio_bus(struct mv88e6xxx_chip *chip)
diff --git a/drivers/net/dsa/mv88e6xxx/global2.c b/drivers/net/dsa/mv88e6xxx/global2.c
index 30a6ffa7817b0..834fdb3503f1c 100644
--- a/drivers/net/dsa/mv88e6xxx/global2.c
+++ b/drivers/net/dsa/mv88e6xxx/global2.c
@@ -855,6 +855,7 @@ static void mv88e6097_watchdog_free(struct mv88e6xxx_chip *chip)
static int mv88e6097_watchdog_setup(struct mv88e6xxx_chip *chip)
{
return mv88e6xxx_g2_write(chip, MV88E6352_G2_WDOG_CTL,
+ MV88E6352_G2_WDOG_CTL_RMU_TIMEOUT |
MV88E6352_G2_WDOG_CTL_EGRESS_ENABLE |
MV88E6352_G2_WDOG_CTL_QC_ENABLE |
MV88E6352_G2_WDOG_CTL_SWRESET);
diff --git a/drivers/net/dsa/mv88e6xxx/rmu.c b/drivers/net/dsa/mv88e6xxx/rmu.c
index 65e6bcf4ca938..03c6f47814c85 100644
--- a/drivers/net/dsa/mv88e6xxx/rmu.c
+++ b/drivers/net/dsa/mv88e6xxx/rmu.c
@@ -105,6 +105,145 @@ static int mv88e6xxx_rmu_request(struct mv88e6xxx_chip *chip,
resp, resp_len, timeout_ms);
}
+int mv88e6xxx_rmu_write(struct mv88e6xxx_chip *chip, int addr, int reg, u16 val)
+{
+ __be16 req[] = {
+ MV88E6XXX_RMU_REQ_FORMAT_SOHO,
+ MV88E6XXX_RMU_REQ_PAD,
+ MV88E6XXX_RMU_REQ_CODE_REG_RW,
+ MV88E6XXX_RMU_REQ_RW_0_WRITE(addr, reg),
+ htons(val),
+ MV88E6XXX_RMU_REQ_RW_0_END,
+ MV88E6XXX_RMU_REQ_RW_1_END,
+ };
+ struct mv88e6xxx_rmu_header resp;
+ int resp_len;
+ int ret = -1;
+
+ if (chip->rmu_state == MV88E6XXX_RMU_DISABLED)
+ return -EOPNOTSUPP;
+
+ resp_len = sizeof(resp);
+ ret = mv88e6xxx_rmu_request(chip, req, sizeof(req),
+ &resp, resp_len,
+ MV88E6XXX_RMU_REQUEST_TIMEOUT_MS);
+ if (ret < 0) {
+ dev_dbg(chip->dev, "RMU: error for command REQ_RW:WRITE %pe addr %d reg %d val %04x\n",
+ ERR_PTR(ret), addr, reg, val);
+ return ret;
+ }
+
+ if (ret < resp_len) {
+ dev_err(chip->dev, "RMU: write returned wrong length: rx %d expecting %d\n",
+ ret, resp_len);
+ return -EPROTO;
+ }
+
+ if (resp.code != MV88E6XXX_RMU_RESP_CODE_REG_RW) {
+ dev_err(chip->dev, "RMU: write returned wrong code %d\n",
+ be16_to_cpu(resp.code));
+ return -EPROTO;
+ }
+
+ return 0;
+}
+
+int mv88e6xxx_rmu_read(struct mv88e6xxx_chip *chip, int addr, int reg,
+ u16 *val)
+{
+ __be16 req[] = {
+ MV88E6XXX_RMU_REQ_FORMAT_SOHO,
+ MV88E6XXX_RMU_REQ_PAD,
+ MV88E6XXX_RMU_REQ_CODE_REG_RW,
+ MV88E6XXX_RMU_REQ_RW_0_READ(addr, reg),
+ 0,
+ MV88E6XXX_RMU_REQ_RW_0_END,
+ MV88E6XXX_RMU_REQ_RW_1_END,
+ };
+ struct mv88e6xxx_rmu_rw_resp resp;
+ int resp_len;
+ int ret = -1;
+
+ if (chip->rmu_state == MV88E6XXX_RMU_DISABLED)
+ return -EOPNOTSUPP;
+
+ resp_len = sizeof(resp);
+ ret = mv88e6xxx_rmu_request(chip, req, sizeof(req),
+ &resp, resp_len,
+ MV88E6XXX_RMU_REQUEST_TIMEOUT_MS);
+ if (ret < 0) {
+ dev_dbg(chip->dev, "RMU: error for command REQ_RW:READ %pe addr %d reg %d\n",
+ ERR_PTR(ret), addr, reg);
+ return ret;
+ }
+
+ if (ret < resp_len) {
+ dev_err(chip->dev, "RMU: read returned wrong length: rx %d expecting %d\n",
+ ret, resp_len);
+ return -EPROTO;
+ }
+
+ if (resp.rmu_header.code != MV88E6XXX_RMU_RESP_CODE_REG_RW) {
+ dev_err(chip->dev, "RMU: read returned wrong code %d\n",
+ be16_to_cpu(resp.rmu_header.code));
+ return -EPROTO;
+ }
+
+ *val = ntohs(resp.value);
+
+ return 0;
+}
+
+int mv88e6xxx_rmu_wait_bit(struct mv88e6xxx_chip *chip, int addr, int reg,
+ int bit, int val)
+{
+ __be16 req[] = {
+ MV88E6XXX_RMU_REQ_FORMAT_SOHO,
+ MV88E6XXX_RMU_REQ_PAD,
+ MV88E6XXX_RMU_REQ_CODE_REG_RW,
+ val ? MV88E6XXX_RMU_REQ_RW_0_WAIT_1(addr, reg) :
+ MV88E6XXX_RMU_REQ_RW_0_WAIT_0(addr, reg),
+ htons((bit & 0xf) << 8),
+ MV88E6XXX_RMU_REQ_RW_0_END,
+ MV88E6XXX_RMU_REQ_RW_1_END,
+ };
+ struct mv88e6xxx_rmu_rw_resp resp;
+ int resp_len;
+ int ret = -1;
+
+ if (chip->rmu_state == MV88E6XXX_RMU_DISABLED)
+ return -EOPNOTSUPP;
+
+ resp_len = sizeof(resp);
+ ret = mv88e6xxx_rmu_request(chip, req, sizeof(req),
+ &resp, resp_len,
+ MV88E6XXX_RMU_WAIT_BIT_TIMEOUT_MS);
+ if (ret < 0) {
+ dev_dbg(chip->dev, "RMU: error for command REQ_RW:WAIT %pe\n",
+ ERR_PTR(ret));
+ return ret;
+ }
+
+ if (ret < resp_len) {
+ dev_err(chip->dev, "RMU: wait on bit returned wrong length: rx %d expecting %d\n",
+ ret, resp_len);
+ return -EPROTO;
+ }
+
+ if (resp.rmu_header.code != MV88E6XXX_RMU_RESP_CODE_REG_RW) {
+ dev_err(chip->dev, "RMU: wait on bit returned wrong code %d\n",
+ be16_to_cpu(resp.rmu_header.code));
+ return -EPROTO;
+ }
+
+ if ((ntohs(resp.value) & 0xff) == 0xff) {
+ dev_err(chip->dev, "RMU: wait on bit timed out\n");
+ return -ETIMEDOUT;
+ }
+
+ return 0;
+}
+
static int mv88e6xxx_rmu_get_id(struct mv88e6xxx_chip *chip)
{
const __be16 req[4] = {
diff --git a/drivers/net/dsa/mv88e6xxx/rmu.h b/drivers/net/dsa/mv88e6xxx/rmu.h
index a81af5a1ad762..4b9df3119d517 100644
--- a/drivers/net/dsa/mv88e6xxx/rmu.h
+++ b/drivers/net/dsa/mv88e6xxx/rmu.h
@@ -11,17 +11,45 @@
#define MV88E6XXX_RMU_WAIT_TIME_MS 20
#define MV88E6XXX_RMU_REQUEST_TIMEOUT_MS 50
+#define MV88E6XXX_RMU_WAIT_BIT_TIMEOUT_MS 1000
#define MV88E6XXX_RMU_REQ_FORMAT_GET_ID htons(0x0000)
#define MV88E6XXX_RMU_REQ_FORMAT_SOHO htons(0x0001)
#define MV88E6XXX_RMU_REQ_PAD htons(0x0000)
#define MV88E6XXX_RMU_REQ_CODE_GET_ID htons(0x0000)
+#define MV88E6XXX_RMU_REQ_CODE_REG_RW htons(0x2000)
#define MV88E6XXX_RMU_REQ_DATA htons(0x0000)
+#define MV88E6XXX_RMU_REQ_RW_0_OP_WAIT_1 (0x7 << 10)
+#define MV88E6XXX_RMU_REQ_RW_0_OP_WAIT_0 (0x4 << 10)
+#define MV88E6XXX_RMU_REQ_RW_0_OP_READ (0x2 << 10)
+#define MV88E6XXX_RMU_REQ_RW_0_OP_WRITE (0x1 << 10)
+
+#define MV88E6XXX_RMU_REQ_RW_0_READ(_addr_, _reg_) \
+ htons(MV88E6XXX_RMU_REQ_RW_0_OP_READ | \
+ ((_addr_) << 5) | \
+ (_reg_))
+#define MV88E6XXX_RMU_REQ_RW_0_WRITE(_addr_, _reg_) \
+ htons(MV88E6XXX_RMU_REQ_RW_0_OP_WRITE | \
+ ((_addr_) << 5) | \
+ (_reg_))
+
+#define MV88E6XXX_RMU_REQ_RW_0_WAIT_0(_addr_, _reg_) \
+ htons(MV88E6XXX_RMU_REQ_RW_0_OP_WAIT_0 | \
+ ((_addr_) << 5) | \
+ (_reg_))
+#define MV88E6XXX_RMU_REQ_RW_0_WAIT_1(_addr_, _reg_) \
+ htons(MV88E6XXX_RMU_REQ_RW_0_OP_WAIT_1 | \
+ ((_addr_) << 5) | \
+ (_reg_))
+
+#define MV88E6XXX_RMU_REQ_RW_0_END htons(0xffff)
+#define MV88E6XXX_RMU_REQ_RW_1_END htons(0xffff)
+
#define MV88E6XXX_RMU_RESP_FORMAT_1 htons(0x0001)
#define MV88E6XXX_RMU_RESP_FORMAT_2 htons(0x0002)
-
#define MV88E6XXX_RMU_RESP_CODE_GOT_ID htons(0x0000)
+#define MV88E6XXX_RMU_RESP_CODE_REG_RW htons(0x2000)
struct mv88e6xxx_rmu_header {
__be16 format;
@@ -29,10 +57,34 @@ struct mv88e6xxx_rmu_header {
__be16 code;
} __packed;
+struct mv88e6xxx_rmu_rw_resp {
+ struct mv88e6xxx_rmu_header rmu_header;
+ __be16 cmd;
+ __be16 value;
+ __be16 end0;
+ __be16 end1;
+} __packed;
+
+int mv88e6xxx_rmu_write(struct mv88e6xxx_chip *chip, int addr, int reg,
+ u16 val);
+int mv88e6xxx_rmu_read(struct mv88e6xxx_chip *chip, int addr, int reg,
+ u16 *val);
+int mv88e6xxx_rmu_wait_bit(struct mv88e6xxx_chip *chip, int addr, int reg,
+ int bit, int val);
void mv88e6xxx_rmu_conduit_state_change(struct dsa_switch *ds,
const struct net_device *conduit,
bool operational);
void mv88e6xxx_rmu_frame2reg_handler(struct dsa_switch *ds,
struct sk_buff *skb,
u8 seqno);
+
+/* RMU register access falls back to MDIO when the operation is unsupported or
+ * times out. @chip is unused today, but threading it through here lets a future
+ * RMU-only mode suppress the fallback without touching any of the call sites.
+ */
+static inline bool
+mv88e6xxx_rmu_can_mdio_fallback(struct mv88e6xxx_chip *chip, int err)
+{
+ return err == -EOPNOTSUPP || err == -ETIMEDOUT;
+}
#endif /* _MV88E6XXX_RMU_H_ */
--
2.43.0
^ permalink raw reply related
* [PATCH net-next 11/17] net: dsa: mv88e6xxx: Move available stats into info structure
From: Luke Howard @ 2026-07-03 7:46 UTC (permalink / raw)
To: Andrew Lunn, Vladimir Oltean, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Simon Horman, Russell King,
Richard Cochran, Florian Fainelli
Cc: Cedric Jehasse, Max Holtmann, Max Hunter, Kieran Tyrrell,
Ryan Wilkins, Mattias Forsblad, netdev, linux-kernel, Luke Howard
In-Reply-To: <20260703-net-next-mv88e6xxx-rmu-v1-0-991a27d78bca@padl.com>
From: Andrew Lunn <andrew@lunn.ch>
Different families of switches have different statistics available.
This information is current hard coded into functions, however this
information will also soon be needed when getting statistics from the
RMU. Move it into the info structure.
Signed-off-by: Andrew Lunn <andrew@lunn.ch>
---
drivers/net/dsa/mv88e6xxx/chip.c | 12 ++++++++++++
1 file changed, 12 insertions(+)
diff --git a/drivers/net/dsa/mv88e6xxx/chip.c b/drivers/net/dsa/mv88e6xxx/chip.c
index 6431d25f3cfa2..5c0a1e2b0507d 100644
--- a/drivers/net/dsa/mv88e6xxx/chip.c
+++ b/drivers/net/dsa/mv88e6xxx/chip.c
@@ -1303,6 +1303,9 @@ static size_t mv88e6095_stats_get_stat(struct mv88e6xxx_chip *chip, int port,
const struct mv88e6xxx_hw_stat *stat,
uint64_t *data)
{
+ if (!(stat->type & chip->info->stats_type))
+ return 0;
+
*data = _mv88e6xxx_get_ethtool_stat(chip, stat, port, 0,
MV88E6XXX_G1_STATS_OP_HIST_RX);
return 1;
@@ -1312,6 +1315,9 @@ static size_t mv88e6250_stats_get_stat(struct mv88e6xxx_chip *chip, int port,
const struct mv88e6xxx_hw_stat *stat,
uint64_t *data)
{
+ if (!(stat->type & chip->info->stats_type))
+ return 0;
+
*data = _mv88e6xxx_get_ethtool_stat(chip, stat, port, 0,
MV88E6XXX_G1_STATS_OP_HIST_RX);
return 1;
@@ -1321,6 +1327,9 @@ static size_t mv88e6320_stats_get_stat(struct mv88e6xxx_chip *chip, int port,
const struct mv88e6xxx_hw_stat *stat,
uint64_t *data)
{
+ if (!(stat->type & chip->info->stats_type))
+ return 0;
+
*data = _mv88e6xxx_get_ethtool_stat(chip, stat, port,
MV88E6XXX_G1_STATS_OP_BANK_1_BIT_9,
MV88E6XXX_G1_STATS_OP_HIST_RX);
@@ -1331,6 +1340,9 @@ static size_t mv88e6390_stats_get_stat(struct mv88e6xxx_chip *chip, int port,
const struct mv88e6xxx_hw_stat *stat,
uint64_t *data)
{
+ if (!(stat->type & chip->info->stats_type))
+ return 0;
+
*data = _mv88e6xxx_get_ethtool_stat(chip, stat, port,
MV88E6XXX_G1_STATS_OP_BANK_1_BIT_10,
0);
--
2.43.0
^ permalink raw reply related
* [PATCH net-next 12/17] net: dsa: mv88e6xxx: Centralise common statistics check
From: Luke Howard @ 2026-07-03 7:46 UTC (permalink / raw)
To: Andrew Lunn, Vladimir Oltean, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Simon Horman, Russell King,
Richard Cochran, Florian Fainelli
Cc: Cedric Jehasse, Max Holtmann, Max Hunter, Kieran Tyrrell,
Ryan Wilkins, Mattias Forsblad, netdev, linux-kernel, Luke Howard
In-Reply-To: <20260703-net-next-mv88e6xxx-rmu-v1-0-991a27d78bca@padl.com>
From: Andrew Lunn <andrew@lunn.ch>
With moving information about available statistics into the info
structure, the test becomes identical. Consolidate them into a single
test.
Signed-off-by: Andrew Lunn <andrew@lunn.ch>
---
drivers/net/dsa/mv88e6xxx/chip.c | 12 ------------
1 file changed, 12 deletions(-)
diff --git a/drivers/net/dsa/mv88e6xxx/chip.c b/drivers/net/dsa/mv88e6xxx/chip.c
index 5c0a1e2b0507d..6431d25f3cfa2 100644
--- a/drivers/net/dsa/mv88e6xxx/chip.c
+++ b/drivers/net/dsa/mv88e6xxx/chip.c
@@ -1303,9 +1303,6 @@ static size_t mv88e6095_stats_get_stat(struct mv88e6xxx_chip *chip, int port,
const struct mv88e6xxx_hw_stat *stat,
uint64_t *data)
{
- if (!(stat->type & chip->info->stats_type))
- return 0;
-
*data = _mv88e6xxx_get_ethtool_stat(chip, stat, port, 0,
MV88E6XXX_G1_STATS_OP_HIST_RX);
return 1;
@@ -1315,9 +1312,6 @@ static size_t mv88e6250_stats_get_stat(struct mv88e6xxx_chip *chip, int port,
const struct mv88e6xxx_hw_stat *stat,
uint64_t *data)
{
- if (!(stat->type & chip->info->stats_type))
- return 0;
-
*data = _mv88e6xxx_get_ethtool_stat(chip, stat, port, 0,
MV88E6XXX_G1_STATS_OP_HIST_RX);
return 1;
@@ -1327,9 +1321,6 @@ static size_t mv88e6320_stats_get_stat(struct mv88e6xxx_chip *chip, int port,
const struct mv88e6xxx_hw_stat *stat,
uint64_t *data)
{
- if (!(stat->type & chip->info->stats_type))
- return 0;
-
*data = _mv88e6xxx_get_ethtool_stat(chip, stat, port,
MV88E6XXX_G1_STATS_OP_BANK_1_BIT_9,
MV88E6XXX_G1_STATS_OP_HIST_RX);
@@ -1340,9 +1331,6 @@ static size_t mv88e6390_stats_get_stat(struct mv88e6xxx_chip *chip, int port,
const struct mv88e6xxx_hw_stat *stat,
uint64_t *data)
{
- if (!(stat->type & chip->info->stats_type))
- return 0;
-
*data = _mv88e6xxx_get_ethtool_stat(chip, stat, port,
MV88E6XXX_G1_STATS_OP_BANK_1_BIT_10,
0);
--
2.43.0
^ permalink raw reply related
* [PATCH net-next 13/17] net: dsa: mv88e6xxx: Get some MIB stats via the RMU
From: Luke Howard @ 2026-07-03 7:46 UTC (permalink / raw)
To: Andrew Lunn, Vladimir Oltean, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Simon Horman, Russell King,
Richard Cochran, Florian Fainelli
Cc: Cedric Jehasse, Max Holtmann, Max Hunter, Kieran Tyrrell,
Ryan Wilkins, Mattias Forsblad, netdev, linux-kernel, Luke Howard
In-Reply-To: <20260703-net-next-mv88e6xxx-rmu-v1-0-991a27d78bca@padl.com>
From: Andrew Lunn <andrew@lunn.ch>
It is possible to access the bank 0 and the port statistics using an
RMU request. Bank 1 stats are not available in this way. Issue an RMU
request to get the statistics which are available, and use the big
table of statistics to decode the reply and return statistics in a way
that is compatible with the MDIO method.
Where bank 1 stats cannot be retrieved via RMU, fall back to individual
register reads and writes.
Signed-off-by: Andrew Lunn <andrew@lunn.ch>
Signed-off-by: Luke Howard <lukeh@padl.com>
---
drivers/net/dsa/mv88e6xxx/chip.c | 15 ++++--
drivers/net/dsa/mv88e6xxx/chip.h | 4 ++
drivers/net/dsa/mv88e6xxx/rmu.c | 101 +++++++++++++++++++++++++++++++++++++++
drivers/net/dsa/mv88e6xxx/rmu.h | 20 ++++++++
4 files changed, 137 insertions(+), 3 deletions(-)
diff --git a/drivers/net/dsa/mv88e6xxx/chip.c b/drivers/net/dsa/mv88e6xxx/chip.c
index 6431d25f3cfa2..531a59093ef97 100644
--- a/drivers/net/dsa/mv88e6xxx/chip.c
+++ b/drivers/net/dsa/mv88e6xxx/chip.c
@@ -1337,9 +1337,9 @@ static size_t mv88e6390_stats_get_stat(struct mv88e6xxx_chip *chip, int port,
return 1;
}
-static size_t mv88e6xxx_stats_get_stat(struct mv88e6xxx_chip *chip, int port,
- const struct mv88e6xxx_hw_stat *stat,
- uint64_t *data)
+size_t mv88e6xxx_stats_get_stat(struct mv88e6xxx_chip *chip, int port,
+ const struct mv88e6xxx_hw_stat *stat,
+ uint64_t *data)
{
int ret = 0;
@@ -1360,6 +1360,15 @@ static size_t mv88e6xxx_stats_get_stats(struct mv88e6xxx_chip *chip, int port,
{
const struct mv88e6xxx_hw_stat *stat;
size_t i, j;
+ int err;
+
+ err = mv88e6xxx_rmu_stats(chip, port, data, mv88e6xxx_hw_stats,
+ ARRAY_SIZE(mv88e6xxx_hw_stats));
+ if (err > 0)
+ return err;
+
+ if (!mv88e6xxx_rmu_can_mdio_fallback(chip, err))
+ return err;
for (i = 0, j = 0; i < ARRAY_SIZE(mv88e6xxx_hw_stats); i++) {
stat = &mv88e6xxx_hw_stats[i];
diff --git a/drivers/net/dsa/mv88e6xxx/chip.h b/drivers/net/dsa/mv88e6xxx/chip.h
index 07300c25cbd36..7e483ba8c58d9 100644
--- a/drivers/net/dsa/mv88e6xxx/chip.h
+++ b/drivers/net/dsa/mv88e6xxx/chip.h
@@ -905,4 +905,8 @@ int mv88e6xxx_vtu_walk(struct mv88e6xxx_chip *chip,
void *priv),
void *priv);
+size_t mv88e6xxx_stats_get_stat(struct mv88e6xxx_chip *chip, int port,
+ const struct mv88e6xxx_hw_stat *stat,
+ uint64_t *data);
+
#endif /* _MV88E6XXX_CHIP_H */
diff --git a/drivers/net/dsa/mv88e6xxx/rmu.c b/drivers/net/dsa/mv88e6xxx/rmu.c
index 03c6f47814c85..88b02030a774f 100644
--- a/drivers/net/dsa/mv88e6xxx/rmu.c
+++ b/drivers/net/dsa/mv88e6xxx/rmu.c
@@ -10,6 +10,7 @@
#include <net/dsa.h>
#include "chip.h"
#include "global1.h"
+#include "port.h"
#include "rmu.h"
static const u8 mv88e6xxx_rmu_dest_addr[ETH_ALEN] = {
@@ -105,6 +106,106 @@ static int mv88e6xxx_rmu_request(struct mv88e6xxx_chip *chip,
resp, resp_len, timeout_ms);
}
+int mv88e6xxx_rmu_stats(struct mv88e6xxx_chip *chip, int port,
+ u64 *data,
+ const struct mv88e6xxx_hw_stat *hw_stats,
+ int num_hw_stats)
+{
+ __be16 req[] = {
+ MV88E6XXX_RMU_REQ_FORMAT_SOHO,
+ MV88E6XXX_RMU_REQ_PAD,
+ MV88E6XXX_RMU_REQ_CODE_MIB,
+ htons(port),
+ };
+ const struct mv88e6xxx_hw_stat *stat;
+ struct mv88e6xxx_rmu_mib_resp resp;
+ int i, j, ret;
+ int resp_len;
+ u64 high;
+
+ if (chip->rmu_state == MV88E6XXX_RMU_DISABLED)
+ return -EOPNOTSUPP;
+
+ resp_len = sizeof(resp) - sizeof(resp.data);
+ if (chip->info->stats_type & STATS_TYPE_BANK0)
+ resp_len += MV88E6XXX_RMU_STATS_TYPE_DATA0_LEN;
+ if (chip->info->stats_type & STATS_TYPE_PORT)
+ resp_len += MV88E6XXX_RMU_STATS_TYPE_PORT_LEN;
+ else if (chip->info->stats_type & STATS_TYPE_BANK1)
+ resp_len += MV88E6XXX_RMU_STATS_TYPE_DATA1_LEN;
+
+ ret = mv88e6xxx_rmu_request(chip, req, sizeof(req),
+ &resp, resp_len,
+ MV88E6XXX_RMU_REQUEST_TIMEOUT_MS);
+ if (ret < 0) {
+ dev_dbg(chip->dev, "RMU: error for command MIB %pe\n",
+ ERR_PTR(ret));
+ return ret;
+ }
+
+ if (ret < resp_len) {
+ dev_err(chip->dev, "RMU: MIB returned wrong length: rx %d expecting %d\n",
+ ret, resp_len);
+ return -EPROTO;
+ }
+
+ if (resp.rmu_header.code != MV88E6XXX_RMU_RESP_CODE_MIB) {
+ dev_err(chip->dev, "RMU: MIB returned wrong code %d\n",
+ be16_to_cpu(resp.rmu_header.code));
+ return -EPROTO;
+ }
+
+ for (i = 0, j = 0; i < num_hw_stats; i++) {
+ stat = &hw_stats[i];
+
+ if ((stat->type & chip->info->stats_type) == 0) {
+ /* Not available via RMU, use SMI (if available) */
+ j += mv88e6xxx_stats_get_stat(chip, port, stat, &data[j]);
+ continue;
+ }
+
+ if (stat->type & STATS_TYPE_PORT) {
+ __be16 *port = (__be16 *)resp.data;
+
+ if (chip->info->stats_type & STATS_TYPE_BANK0)
+ port += MV88E6XXX_RMU_STATS_TYPE_DATA0_LEN / 2;
+
+ switch (stat->reg) {
+ case MV88E6XXX_PORT_IN_DISCARD_LO:
+ data[j] = be16_to_cpu(port[0]) << 16;
+ data[j] |= be16_to_cpu(port[1]);
+ break;
+ case MV88E6XXX_PORT_IN_FILTERED:
+ data[j] = be16_to_cpu(port[3]);
+ break;
+ case MV88E6XXX_PORT_OUT_FILTERED:
+ data[j] = be16_to_cpu(port[5]);
+ break;
+ default:
+ return -EINVAL;
+ }
+ }
+
+ if (stat->type & (STATS_TYPE_BANK0 | STATS_TYPE_BANK1)) {
+ int reg = stat->reg;
+
+ if (stat->type & STATS_TYPE_BANK1 &&
+ (chip->info->stats_type & STATS_TYPE_BANK0))
+ reg += MV88E6XXX_RMU_STATS_TYPE_DATA0_LEN / 4;
+
+ data[j] = be32_to_cpu(resp.data[reg]);
+ if (stat->size == 8) {
+ high = be32_to_cpu(resp.data[reg + 1]);
+ data[j] |= (high << 32);
+ }
+ }
+
+ j++;
+ }
+
+ return j;
+}
+
int mv88e6xxx_rmu_write(struct mv88e6xxx_chip *chip, int addr, int reg, u16 val)
{
__be16 req[] = {
diff --git a/drivers/net/dsa/mv88e6xxx/rmu.h b/drivers/net/dsa/mv88e6xxx/rmu.h
index 4b9df3119d517..0865e899cb340 100644
--- a/drivers/net/dsa/mv88e6xxx/rmu.h
+++ b/drivers/net/dsa/mv88e6xxx/rmu.h
@@ -17,6 +17,7 @@
#define MV88E6XXX_RMU_REQ_FORMAT_SOHO htons(0x0001)
#define MV88E6XXX_RMU_REQ_PAD htons(0x0000)
#define MV88E6XXX_RMU_REQ_CODE_GET_ID htons(0x0000)
+#define MV88E6XXX_RMU_REQ_CODE_MIB htons(0x1020)
#define MV88E6XXX_RMU_REQ_CODE_REG_RW htons(0x2000)
#define MV88E6XXX_RMU_REQ_DATA htons(0x0000)
@@ -49,6 +50,7 @@
#define MV88E6XXX_RMU_RESP_FORMAT_1 htons(0x0001)
#define MV88E6XXX_RMU_RESP_FORMAT_2 htons(0x0002)
#define MV88E6XXX_RMU_RESP_CODE_GOT_ID htons(0x0000)
+#define MV88E6XXX_RMU_RESP_CODE_MIB htons(0x1020)
#define MV88E6XXX_RMU_RESP_CODE_REG_RW htons(0x2000)
struct mv88e6xxx_rmu_header {
@@ -65,6 +67,24 @@ struct mv88e6xxx_rmu_rw_resp {
__be16 end1;
} __packed;
+/* number of RMU MIB statistics, by type, in octets */
+#define MV88E6XXX_RMU_STATS_TYPE_DATA0_LEN 128
+#define MV88E6XXX_RMU_STATS_TYPE_PORT_LEN 12
+#define MV88E6XXX_RMU_STATS_TYPE_DATA1_LEN 128
+#define MV88E6XXX_RMU_STATS_TYPE_MAX_LEN (MV88E6XXX_RMU_STATS_TYPE_DATA0_LEN + \
+ MV88E6XXX_RMU_STATS_TYPE_DATA1_LEN)
+
+struct mv88e6xxx_rmu_mib_resp {
+ struct mv88e6xxx_rmu_header rmu_header;
+ __be16 swport;
+ __be32 timestamp;
+ __be32 data[MV88E6XXX_RMU_STATS_TYPE_MAX_LEN / 4];
+} __packed;
+
+int mv88e6xxx_rmu_stats(struct mv88e6xxx_chip *chip, int port,
+ u64 *data,
+ const struct mv88e6xxx_hw_stat *hw_stats,
+ int num_hw_stats);
int mv88e6xxx_rmu_write(struct mv88e6xxx_chip *chip, int addr, int reg,
u16 val);
int mv88e6xxx_rmu_read(struct mv88e6xxx_chip *chip, int addr, int reg,
--
2.43.0
^ permalink raw reply related
* [PATCH net-next 14/17] net: dsa: mv88e6xxx: Time RMU operations and disable if slow
From: Luke Howard @ 2026-07-03 7:46 UTC (permalink / raw)
To: Andrew Lunn, Vladimir Oltean, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Simon Horman, Russell King,
Richard Cochran, Florian Fainelli
Cc: Cedric Jehasse, Max Holtmann, Max Hunter, Kieran Tyrrell,
Ryan Wilkins, Mattias Forsblad, netdev, linux-kernel, Luke Howard
In-Reply-To: <20260703-net-next-mv88e6xxx-rmu-v1-0-991a27d78bca@padl.com>
From: Andrew Lunn <andrew@lunn.ch>
MII access to the switch generally has consistent latency. Sending and
receiving frames on the other hand has a big variance in
latency. Receiver interrupt coalescence can result in delays of
100us. As a result, the RMU can be slower than MDIO.
Measure the latency of the first 16 read requests performed using the
RMU, and compare it to the latency on an MDIO read. If the RMU is
slower than MDIO, do not use if for single register reads, writes and
waiting for bits to be set. Do however use it for retrieving MIB
values since a single RMU requests replaces a large number of MIDO
transactions.
Testing on ZII Devel C, which uses a FEC, and Marvel 370RD using
mvneta has show RMU is slower than MDIO. ZII RAP however, makes use of
bit banging MDIO and an IGB. IGB by default has a short interrupt
coalesce period, making RMU much faster than bit banging, by around 20
times.
Signed-off-by: Andrew Lunn <andrew@lunn.ch>
Signed-off-by: Luke Howard <lukeh@padl.com>
---
drivers/net/dsa/mv88e6xxx/chip.h | 6 +++++
drivers/net/dsa/mv88e6xxx/rmu.c | 58 ++++++++++++++++++++++++++++++++++++----
2 files changed, 59 insertions(+), 5 deletions(-)
diff --git a/drivers/net/dsa/mv88e6xxx/chip.h b/drivers/net/dsa/mv88e6xxx/chip.h
index 7e483ba8c58d9..fdc87d02469b5 100644
--- a/drivers/net/dsa/mv88e6xxx/chip.h
+++ b/drivers/net/dsa/mv88e6xxx/chip.h
@@ -355,6 +355,8 @@ enum mv88e6xxx_rmu_state {
*/
};
+#define MV88E6XXX_RMU_IS_SLOW BIT(0)
+
struct mv88e6xxx_chip {
const struct mv88e6xxx_info *info;
@@ -471,6 +473,10 @@ struct mv88e6xxx_chip {
struct net_device *rmu_conduit;
struct dsa_inband rmu_inband;
enum mv88e6xxx_rmu_state rmu_state;
+ u8 rmu_flags;
+ ktime_t rmu_read_latencies[16];
+ u32 rmu_samples;
+ ktime_t smi_read_latency;
};
#define TCAM_MATCH_SIZE 96
diff --git a/drivers/net/dsa/mv88e6xxx/rmu.c b/drivers/net/dsa/mv88e6xxx/rmu.c
index 88b02030a774f..5660372f41ad7 100644
--- a/drivers/net/dsa/mv88e6xxx/rmu.c
+++ b/drivers/net/dsa/mv88e6xxx/rmu.c
@@ -221,7 +221,8 @@ int mv88e6xxx_rmu_write(struct mv88e6xxx_chip *chip, int addr, int reg, u16 val)
int resp_len;
int ret = -1;
- if (chip->rmu_state == MV88E6XXX_RMU_DISABLED)
+ if (chip->rmu_state == MV88E6XXX_RMU_DISABLED ||
+ (chip->rmu_flags & MV88E6XXX_RMU_IS_SLOW))
return -EOPNOTSUPP;
resp_len = sizeof(resp);
@@ -249,6 +250,33 @@ int mv88e6xxx_rmu_write(struct mv88e6xxx_chip *chip, int addr, int reg, u16 val)
return 0;
}
+static void mv88e6xxx_rmu_read_latency(struct mv88e6xxx_chip *chip,
+ ktime_t latency)
+{
+ ktime_t average = 0;
+ int i;
+
+ if (chip->rmu_samples >= ARRAY_SIZE(chip->rmu_read_latencies))
+ return;
+
+ chip->rmu_read_latencies[chip->rmu_samples++] = latency;
+
+ if (chip->rmu_samples == ARRAY_SIZE(chip->rmu_read_latencies)) {
+ for (i = 0; i < ARRAY_SIZE(chip->rmu_read_latencies); i++)
+ average += chip->rmu_read_latencies[i];
+ average = average / ARRAY_SIZE(chip->rmu_read_latencies);
+
+ dev_dbg(chip->dev, "RMU %lldus, smi %lldus\n",
+ div_u64(average, 1000),
+ div_u64(chip->smi_read_latency, 1000));
+
+ if (chip->smi_read_latency < average)
+ chip->rmu_flags |= MV88E6XXX_RMU_IS_SLOW;
+
+ chip->rmu_samples = U32_MAX;
+ }
+}
+
int mv88e6xxx_rmu_read(struct mv88e6xxx_chip *chip, int addr, int reg,
u16 *val)
{
@@ -263,11 +291,15 @@ int mv88e6xxx_rmu_read(struct mv88e6xxx_chip *chip, int addr, int reg,
};
struct mv88e6xxx_rmu_rw_resp resp;
int resp_len;
- int ret = -1;
+ ktime_t start;
+ int ret;
- if (chip->rmu_state == MV88E6XXX_RMU_DISABLED)
+ if (chip->rmu_state == MV88E6XXX_RMU_DISABLED ||
+ (chip->rmu_flags & MV88E6XXX_RMU_IS_SLOW))
return -EOPNOTSUPP;
+ start = ktime_get();
+
resp_len = sizeof(resp);
ret = mv88e6xxx_rmu_request(chip, req, sizeof(req),
&resp, resp_len,
@@ -290,6 +322,8 @@ int mv88e6xxx_rmu_read(struct mv88e6xxx_chip *chip, int addr, int reg,
return -EPROTO;
}
+ mv88e6xxx_rmu_read_latency(chip, ktime_get() - start);
+
*val = ntohs(resp.value);
return 0;
@@ -312,7 +346,8 @@ int mv88e6xxx_rmu_wait_bit(struct mv88e6xxx_chip *chip, int addr, int reg,
int resp_len;
int ret = -1;
- if (chip->rmu_state == MV88E6XXX_RMU_DISABLED)
+ if (chip->rmu_state == MV88E6XXX_RMU_DISABLED ||
+ (chip->rmu_flags & MV88E6XXX_RMU_IS_SLOW))
return -EOPNOTSUPP;
resp_len = sizeof(resp);
@@ -389,8 +424,10 @@ void mv88e6xxx_rmu_conduit_state_change(struct dsa_switch *ds,
{
struct dsa_port *cpu_dp = conduit->dsa_ptr;
struct mv88e6xxx_chip *chip = ds->priv;
+ ktime_t start;
int port;
int ret;
+ u16 id;
port = dsa_towards_port(ds, cpu_dp->ds->index, cpu_dp->index);
@@ -414,13 +451,24 @@ void mv88e6xxx_rmu_conduit_state_change(struct dsa_switch *ds,
if (ret < 0) {
dev_err(chip->dev, "RMU: initialization check failed %pe",
ERR_PTR(ret));
- goto out;
+ goto out_disable;
}
+
+ start = ktime_get();
+ ret = mv88e6xxx_port_read(chip, 0, MV88E6XXX_PORT_SWITCH_ID,
+ &id);
+ if (ret < 0) {
+ dev_err(chip->dev, "RMU: SMI latency read failed %pe",
+ ERR_PTR(ret));
+ goto out_disable;
+ }
+ chip->smi_read_latency = ktime_get() - start;
chip->rmu_state = MV88E6XXX_RMU_ENABLED;
dev_info(chip->dev, "RMU: enabled on port %d via conduit device %s",
port, chip->rmu_conduit->name);
} else {
+out_disable:
if (chip->info->ops->rmu_disable)
chip->info->ops->rmu_disable(chip);
--
2.43.0
^ permalink raw reply related
* [PATCH net-next 15/17] net: dsa: mv88e6xxx: cancel PTP polling work on .shutdown
From: Luke Howard @ 2026-07-03 7:46 UTC (permalink / raw)
To: Andrew Lunn, Vladimir Oltean, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Simon Horman, Russell King,
Richard Cochran, Florian Fainelli
Cc: Cedric Jehasse, Max Holtmann, Max Hunter, Kieran Tyrrell,
Ryan Wilkins, Mattias Forsblad, netdev, linux-kernel, Luke Howard
In-Reply-To: <20260703-net-next-mv88e6xxx-rmu-v1-0-991a27d78bca@padl.com>
DSA core's dsa_switch_shutdown() only closes CPU port conduits and
unlinks DSA uppers; it does not invoke ds->ops->teardown(). On
.shutdown (reboot), the chip's mv88e6xxx_teardown() therefore never
runs, and the PTP overflow / TAI event delayed works are not
cancelled. They continue to fire as the conduit netdev is torn down
by its own driver, racing the teardown and crashing in the RMU
request path when conduit->dev_addr is released.
Factor the work cancellation out of mv88e6xxx_ptp_free() into a new
mv88e6xxx_ptp_shutdown() helper that leaves the PTP clock registered,
and call it from mv88e6xxx_shutdown_common() before
dsa_switch_shutdown(). mv88e6xxx_ptp_free() now calls the helper
itself, so the regular .remove path is unchanged.
Signed-off-by: Luke Howard <lukeh@padl.com>
---
drivers/net/dsa/mv88e6xxx/chip.c | 6 ++++++
drivers/net/dsa/mv88e6xxx/ptp.c | 14 ++++++++++++--
drivers/net/dsa/mv88e6xxx/ptp.h | 5 +++++
3 files changed, 23 insertions(+), 2 deletions(-)
diff --git a/drivers/net/dsa/mv88e6xxx/chip.c b/drivers/net/dsa/mv88e6xxx/chip.c
index 531a59093ef97..3d92c59de7b5f 100644
--- a/drivers/net/dsa/mv88e6xxx/chip.c
+++ b/drivers/net/dsa/mv88e6xxx/chip.c
@@ -7543,6 +7543,12 @@ static void mv88e6xxx_shutdown(struct mdio_device *mdiodev)
if (!ds)
return;
+ /* DSA core does not call ->teardown on .shutdown, so stop PTP
+ * polling here before the conduit goes away. Otherwise the work
+ * can race teardown and dereference a stale conduit->dev_addr.
+ */
+ mv88e6xxx_ptp_shutdown(ds->priv);
+
dsa_switch_shutdown(ds);
dev_set_drvdata(&mdiodev->dev, NULL);
diff --git a/drivers/net/dsa/mv88e6xxx/ptp.c b/drivers/net/dsa/mv88e6xxx/ptp.c
index f7603573d3a98..f6ce3232d10ff 100644
--- a/drivers/net/dsa/mv88e6xxx/ptp.c
+++ b/drivers/net/dsa/mv88e6xxx/ptp.c
@@ -551,14 +551,24 @@ int mv88e6xxx_ptp_setup(struct mv88e6xxx_chip *chip)
return 0;
}
-/* This must never be called holding the register lock */
-void mv88e6xxx_ptp_free(struct mv88e6xxx_chip *chip)
+/* Cancel any pending PTP polling work. Safe to call from .shutdown
+ * paths where the PTP clock itself stays registered.
+ * This must never be called holding the register lock.
+ */
+void mv88e6xxx_ptp_shutdown(struct mv88e6xxx_chip *chip)
{
if (chip->ptp_clock) {
cancel_delayed_work_sync(&chip->overflow_work);
if (chip->info->ops->ptp_ops->event_work)
cancel_delayed_work_sync(&chip->tai_event_work);
+ }
+}
+/* This must never be called holding the register lock */
+void mv88e6xxx_ptp_free(struct mv88e6xxx_chip *chip)
+{
+ if (chip->ptp_clock) {
+ mv88e6xxx_ptp_shutdown(chip);
ptp_clock_unregister(chip->ptp_clock);
chip->ptp_clock = NULL;
}
diff --git a/drivers/net/dsa/mv88e6xxx/ptp.h b/drivers/net/dsa/mv88e6xxx/ptp.h
index 95bdddb0bf39f..90031b05d2514 100644
--- a/drivers/net/dsa/mv88e6xxx/ptp.h
+++ b/drivers/net/dsa/mv88e6xxx/ptp.h
@@ -68,6 +68,7 @@
int mv88e6xxx_ptp_setup(struct mv88e6xxx_chip *chip);
void mv88e6xxx_ptp_free(struct mv88e6xxx_chip *chip);
+void mv88e6xxx_ptp_shutdown(struct mv88e6xxx_chip *chip);
#define ptp_to_chip(ptp) container_of(ptp, struct mv88e6xxx_chip, \
ptp_clock_info)
@@ -87,6 +88,10 @@ static inline void mv88e6xxx_ptp_free(struct mv88e6xxx_chip *chip)
{
}
+static inline void mv88e6xxx_ptp_shutdown(struct mv88e6xxx_chip *chip)
+{
+}
+
static const struct mv88e6xxx_ptp_ops mv88e6165_ptp_ops = {};
static const struct mv88e6xxx_ptp_ops mv88e6352_ptp_ops = {};
static const struct mv88e6xxx_ptp_ops mv88e6390_ptp_ops = {};
--
2.43.0
^ permalink raw reply related
* [PATCH net-next 16/17] net: dsa: mv88e6xxx: size ATU snapshot buffer and region by num_macs
From: Luke Howard @ 2026-07-03 7:46 UTC (permalink / raw)
To: Andrew Lunn, Vladimir Oltean, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Simon Horman, Russell King,
Richard Cochran, Florian Fainelli
Cc: Cedric Jehasse, Max Holtmann, Max Hunter, Kieran Tyrrell,
Ryan Wilkins, Mattias Forsblad, netdev, linux-kernel, Luke Howard
In-Reply-To: <20260703-net-next-mv88e6xxx-rmu-v1-0-991a27d78bca@padl.com>
The ATU devlink region and its snapshot buffer are both sized by the
number of forwarding databases, but the ATU can hold up to
mv88e6xxx_num_macs() entries (which is 8192 on the 6352 series).
Fixes: bfb255428966 ("net: dsa: mv88e6xxx: Add devlink regions")
Assisted-by: Claude:claude-opus-4-8
Signed-off-by: Luke Howard <lukeh@padl.com>
---
drivers/net/dsa/mv88e6xxx/devlink.c | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/drivers/net/dsa/mv88e6xxx/devlink.c b/drivers/net/dsa/mv88e6xxx/devlink.c
index 6f034841883c6..6d7da381f9f38 100644
--- a/drivers/net/dsa/mv88e6xxx/devlink.c
+++ b/drivers/net/dsa/mv88e6xxx/devlink.c
@@ -380,7 +380,7 @@ static int mv88e6xxx_region_atu_snapshot(struct devlink *dl,
int fid = -1, err = 0, count = 0;
table = kzalloc_objs(struct mv88e6xxx_devlink_atu_entry,
- mv88e6xxx_num_databases(chip));
+ mv88e6xxx_num_macs(chip));
if (!table)
return -ENOMEM;
@@ -776,7 +776,7 @@ int mv88e6xxx_setup_devlink_regions_global(struct dsa_switch *ds)
switch (i) {
case MV88E6XXX_REGION_ATU:
- size = mv88e6xxx_num_databases(chip) *
+ size = mv88e6xxx_num_macs(chip) *
sizeof(struct mv88e6xxx_devlink_atu_entry);
break;
case MV88E6XXX_REGION_VTU:
--
2.43.0
^ permalink raw reply related
* [PATCH net-next 17/17] net: dsa: mv88e6xxx: RMU DUMP_ATU support in devlink
From: Luke Howard @ 2026-07-03 7:46 UTC (permalink / raw)
To: Andrew Lunn, Vladimir Oltean, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Simon Horman, Russell King,
Richard Cochran, Florian Fainelli
Cc: Cedric Jehasse, Max Holtmann, Max Hunter, Kieran Tyrrell,
Ryan Wilkins, Mattias Forsblad, netdev, linux-kernel, Luke Howard
In-Reply-To: <20260703-net-next-mv88e6xxx-rmu-v1-0-991a27d78bca@padl.com>
Support reading an ATU snapshot in a single RMU request.
Signed-off-by: Luke Howard <lukeh@padl.com>
---
drivers/net/dsa/mv88e6xxx/chip.h | 10 +++++
drivers/net/dsa/mv88e6xxx/devlink.c | 67 ++++++++++++++++++++++---------
drivers/net/dsa/mv88e6xxx/rmu.c | 78 +++++++++++++++++++++++++++++++++++++
drivers/net/dsa/mv88e6xxx/rmu.h | 35 +++++++++++++++++
4 files changed, 171 insertions(+), 19 deletions(-)
diff --git a/drivers/net/dsa/mv88e6xxx/chip.h b/drivers/net/dsa/mv88e6xxx/chip.h
index fdc87d02469b5..7bbb649330d55 100644
--- a/drivers/net/dsa/mv88e6xxx/chip.h
+++ b/drivers/net/dsa/mv88e6xxx/chip.h
@@ -205,6 +205,16 @@ struct mv88e6xxx_stu_entry {
u8 state[DSA_MAX_PORTS];
};
+struct mv88e6xxx_devlink_atu_entry {
+ /* The FID is scattered over multiple registers. */
+ u16 fid;
+ u16 atu_op;
+ u16 atu_data;
+ u16 atu_01;
+ u16 atu_23;
+ u16 atu_45;
+};
+
struct mv88e6xxx_bus_ops;
struct mv88e6xxx_irq_ops;
struct mv88e6xxx_gpio_ops;
diff --git a/drivers/net/dsa/mv88e6xxx/devlink.c b/drivers/net/dsa/mv88e6xxx/devlink.c
index 6d7da381f9f38..5c2a59d1145b8 100644
--- a/drivers/net/dsa/mv88e6xxx/devlink.c
+++ b/drivers/net/dsa/mv88e6xxx/devlink.c
@@ -6,6 +6,7 @@
#include "global1.h"
#include "global2.h"
#include "port.h"
+#include "rmu.h"
static int mv88e6xxx_atu_get_hash(struct mv88e6xxx_chip *chip, u8 *hash)
{
@@ -307,16 +308,6 @@ static int mv88e6xxx_region_global_snapshot(struct devlink *dl,
* mv88e6xxx generations
*/
-struct mv88e6xxx_devlink_atu_entry {
- /* The FID is scattered over multiple registers. */
- u16 fid;
- u16 atu_op;
- u16 atu_data;
- u16 atu_01;
- u16 atu_23;
- u16 atu_45;
-};
-
static int mv88e6xxx_region_atu_snapshot_fid(struct mv88e6xxx_chip *chip,
int fid,
struct mv88e6xxx_devlink_atu_entry *table,
@@ -369,6 +360,33 @@ static int mv88e6xxx_region_atu_snapshot_fid(struct mv88e6xxx_chip *chip,
return 0;
}
+static int mv88e6xxx_region_atu_snapshot_rmu(struct mv88e6xxx_chip *chip,
+ struct mv88e6xxx_devlink_atu_entry *table,
+ int *count)
+{
+ unsigned int num_macs = mv88e6xxx_num_macs(chip);
+ u16 cont_code = 0;
+ int err;
+
+ do {
+ if (*count + MV88E6XXX_RMU_MAX_ATU_ENTRIES > num_macs) {
+ err = -ERANGE;
+ break;
+ }
+
+ err = mv88e6xxx_rmu_dump_atu(chip, &table[*count], &cont_code);
+ if (err < 0)
+ break;
+
+ *count += err;
+ } while (cont_code != 0);
+
+ if (err < 0)
+ *count = 0;
+
+ return err;
+}
+
static int mv88e6xxx_region_atu_snapshot(struct devlink *dl,
const struct devlink_region_ops *ops,
struct netlink_ext_ack *extack,
@@ -386,18 +404,29 @@ static int mv88e6xxx_region_atu_snapshot(struct devlink *dl,
mv88e6xxx_reg_lock(chip);
- while (1) {
- fid = find_next_bit(chip->fid_bitmap, MV88E6XXX_N_FID, fid + 1);
- if (fid == MV88E6XXX_N_FID)
- break;
+ err = mv88e6xxx_region_atu_snapshot_rmu(chip, table, &count);
+ if (err >= 0) {
+ /* RMU succeeded, use its results */
+ err = 0;
+ } else if (!mv88e6xxx_rmu_can_mdio_fallback(chip, err)) {
+ kfree(table);
+ goto out;
+ } else {
+ while (1) {
+ fid = find_next_bit(chip->fid_bitmap, MV88E6XXX_N_FID,
+ fid + 1);
+ if (fid == MV88E6XXX_N_FID)
+ break;
- err = mv88e6xxx_region_atu_snapshot_fid(chip, fid, table,
- &count);
- if (err) {
- kfree(table);
- goto out;
+ err = mv88e6xxx_region_atu_snapshot_fid(chip, fid,
+ table, &count);
+ if (err) {
+ kfree(table);
+ goto out;
+ }
}
}
+
*data = (u8 *)table;
out:
mv88e6xxx_reg_unlock(chip);
diff --git a/drivers/net/dsa/mv88e6xxx/rmu.c b/drivers/net/dsa/mv88e6xxx/rmu.c
index 5660372f41ad7..36422039030cd 100644
--- a/drivers/net/dsa/mv88e6xxx/rmu.c
+++ b/drivers/net/dsa/mv88e6xxx/rmu.c
@@ -106,6 +106,84 @@ static int mv88e6xxx_rmu_request(struct mv88e6xxx_chip *chip,
resp, resp_len, timeout_ms);
}
+int mv88e6xxx_rmu_dump_atu(struct mv88e6xxx_chip *chip,
+ struct mv88e6xxx_devlink_atu_entry *entries,
+ u16 *cont_code)
+{
+ __be16 req[] = {
+ MV88E6XXX_RMU_REQ_FORMAT_SOHO,
+ MV88E6XXX_RMU_REQ_PAD,
+ MV88E6XXX_RMU_REQ_CODE_ATU,
+ htons(*cont_code),
+ };
+ struct mv88e6xxx_rmu_atu_resp resp;
+ int resp_len, count;
+ int ret, i;
+
+ if (chip->rmu_state == MV88E6XXX_RMU_DISABLED)
+ return -EOPNOTSUPP;
+
+ resp_len = sizeof(resp);
+ ret = mv88e6xxx_rmu_request(chip, req, sizeof(req),
+ &resp, resp_len,
+ MV88E6XXX_RMU_REQUEST_TIMEOUT_MS);
+ if (ret < 0) {
+ dev_dbg(chip->dev, "RMU: error for command DUMP_ATU %pe\n",
+ ERR_PTR(ret));
+ return ret;
+ }
+
+ /* minimum packet length */
+ resp_len = sizeof(struct mv88e6xxx_rmu_header) + sizeof(__be16); /* cont_code */
+
+ if (ret < resp_len || (ret - resp_len) % sizeof(struct mv88e6xxx_rmu_atu_entry)) {
+ dev_err(chip->dev, "RMU: DUMP_ATU returned wrong length: rx %d expecting %zd\n",
+ ret, sizeof(resp));
+ return -EPROTO;
+ }
+
+ count = (ret - resp_len) / sizeof(struct mv88e6xxx_rmu_atu_entry);
+
+ if (resp.rmu_header.code != MV88E6XXX_RMU_RESP_CODE_ATU) {
+ dev_err(chip->dev, "RMU: DUMP_ATU returned wrong code %d\n",
+ be16_to_cpu(resp.rmu_header.code));
+ return -EPROTO;
+ }
+
+ for (i = 0; i < count; i++) {
+ const struct mv88e6xxx_rmu_atu_entry *src = &resp.entries[i];
+ struct mv88e6xxx_devlink_atu_entry *dst = &entries[i];
+ u16 tmp;
+
+ tmp = ntohs(src->state_trunk_dpv);
+ if (!MV88E6XXX_RMU_ATU_ENTRY_STATE_GET(tmp)) {
+ *cont_code = 0;
+ return i;
+ }
+
+ dst->atu_data = MV88E6XXX_RMU_ATU_ENTRY_STATE_GET(tmp) & 0xf;
+ if (tmp & MV88E6XXX_RMU_ATU_TRUNK)
+ dst->atu_data |= MV88E6XXX_G1_ATU_DATA_TRUNK;
+ dst->atu_data |= (MV88E6XXX_RMU_ATU_DPV_GET(tmp) &
+ mv88e6xxx_port_mask(chip)) << 4;
+
+ tmp = ntohs(src->pri_fid);
+ dst->fid = MV88E6XXX_RMU_ATU_FID_GET(tmp);
+ dst->atu_op = MV88E6XXX_G1_ATU_OP_GET_NEXT_DB;
+
+ dst->atu_01 = ntohs(src->atu_01);
+ dst->atu_23 = ntohs(src->atu_23);
+ dst->atu_45 = ntohs(src->atu_45);
+ }
+
+ if (i == MV88E6XXX_RMU_MAX_ATU_ENTRIES)
+ *cont_code = ntohs(resp.cont_code);
+ else
+ *cont_code = 0;
+
+ return i;
+}
+
int mv88e6xxx_rmu_stats(struct mv88e6xxx_chip *chip, int port,
u64 *data,
const struct mv88e6xxx_hw_stat *hw_stats,
diff --git a/drivers/net/dsa/mv88e6xxx/rmu.h b/drivers/net/dsa/mv88e6xxx/rmu.h
index 0865e899cb340..3a288ff3b113f 100644
--- a/drivers/net/dsa/mv88e6xxx/rmu.h
+++ b/drivers/net/dsa/mv88e6xxx/rmu.h
@@ -17,6 +17,7 @@
#define MV88E6XXX_RMU_REQ_FORMAT_SOHO htons(0x0001)
#define MV88E6XXX_RMU_REQ_PAD htons(0x0000)
#define MV88E6XXX_RMU_REQ_CODE_GET_ID htons(0x0000)
+#define MV88E6XXX_RMU_REQ_CODE_ATU htons(0x1000)
#define MV88E6XXX_RMU_REQ_CODE_MIB htons(0x1020)
#define MV88E6XXX_RMU_REQ_CODE_REG_RW htons(0x2000)
#define MV88E6XXX_RMU_REQ_DATA htons(0x0000)
@@ -50,6 +51,7 @@
#define MV88E6XXX_RMU_RESP_FORMAT_1 htons(0x0001)
#define MV88E6XXX_RMU_RESP_FORMAT_2 htons(0x0002)
#define MV88E6XXX_RMU_RESP_CODE_GOT_ID htons(0x0000)
+#define MV88E6XXX_RMU_RESP_CODE_ATU htons(0x1000)
#define MV88E6XXX_RMU_RESP_CODE_MIB htons(0x1020)
#define MV88E6XXX_RMU_RESP_CODE_REG_RW htons(0x2000)
@@ -67,6 +69,34 @@ struct mv88e6xxx_rmu_rw_resp {
__be16 end1;
} __packed;
+struct mv88e6xxx_rmu_atu_entry {
+ __be16 state_trunk_dpv;
+ __be16 atu_01;
+ __be16 atu_23;
+ __be16 atu_45;
+ __be16 pri_fid;
+} __packed;
+
+#define MV88E6XXX_RMU_MAX_ATU_ENTRIES 48
+
+#define MV88E6XXX_RMU_ATU_ENTRY_STATE_MASK GENMASK(15, 12)
+#define MV88E6XXX_RMU_ATU_ENTRY_STATE_GET(p) FIELD_GET(MV88E6XXX_RMU_ATU_ENTRY_STATE_MASK, p)
+#define MV88E6XXX_RMU_ATU_TRUNK 0x0800
+#define MV88E6XXX_RMU_ATU_DPV_MASK GENMASK(10, 0)
+#define MV88E6XXX_RMU_ATU_DPV_GET(p) FIELD_GET(MV88E6XXX_RMU_ATU_DPV_MASK, p)
+
+#define MV88E6XXX_RMU_ATU_PRI_MASK GENMASK(14, 12)
+#define MV88E6XXX_RMU_ATU_PRI_GET(p) FIELD_GET(MV88E6XXX_RMU_ATU_PRI_MASK, p)
+
+#define MV88E6XXX_RMU_ATU_FID_MASK GENMASK(11, 0)
+#define MV88E6XXX_RMU_ATU_FID_GET(p) FIELD_GET(MV88E6XXX_RMU_ATU_FID_MASK, p)
+
+struct mv88e6xxx_rmu_atu_resp {
+ struct mv88e6xxx_rmu_header rmu_header;
+ struct mv88e6xxx_rmu_atu_entry entries[MV88E6XXX_RMU_MAX_ATU_ENTRIES];
+ __be16 cont_code;
+} __packed;
+
/* number of RMU MIB statistics, by type, in octets */
#define MV88E6XXX_RMU_STATS_TYPE_DATA0_LEN 128
#define MV88E6XXX_RMU_STATS_TYPE_PORT_LEN 12
@@ -81,6 +111,11 @@ struct mv88e6xxx_rmu_mib_resp {
__be32 data[MV88E6XXX_RMU_STATS_TYPE_MAX_LEN / 4];
} __packed;
+struct mv88e6xxx_devlink_atu_entry;
+
+int mv88e6xxx_rmu_dump_atu(struct mv88e6xxx_chip *chip,
+ struct mv88e6xxx_devlink_atu_entry *entries,
+ u16 *cont_code);
int mv88e6xxx_rmu_stats(struct mv88e6xxx_chip *chip, int port,
u64 *data,
const struct mv88e6xxx_hw_stat *hw_stats,
--
2.43.0
^ permalink raw reply related
* Re: [PATCH v2 1/2] net: fman: move IRQ registration after init to prevent NULL deref and UAF
From: 赵金明 @ 2026-07-03 7:54 UTC (permalink / raw)
To: Andrew Lunn
Cc: pabeni, andrew+netdev, davem, edumazet, horms, kuba, linux-kernel,
madalin.bucur, netdev, sean.anderson
In-Reply-To: <19877366-38df-4598-9892-3b295a5238f1@lunn.ch>
>On Thu, Jul 02, 2026 at 05:28:15PM +0800, ZhaoJinming wrote:
>> read_dts_node() registers shared interrupt handlers via
>> devm_request_irq() with fman as dev_id. Two bugs exist in the
>> current code:
>
>Please start a new thread with a new version of the patch. The CI
>system just thinks this is part of the discussion, not something it
>must test.
>
>Please also read:
>
>https://www.kernel.org/doc/html/latest/process/maintainer-netdev.html
>
>and set the Subject: line correctly, etc.
>
>???? Andrew
>
Started a new thread, https://lore.kernel.org/netdev/20260703074324.907294-1-zhaojinming@uniontech.com/T/#t
Thanks,
Jinming
^ permalink raw reply
* Re: [PATCH net-next v4 2/2] net: pse-pd: add Realtek/Broadcom PSE MCU driver
From: Paolo Abeni @ 2026-07-03 7:55 UTC (permalink / raw)
To: Jonas Jelonek, Oleksij Rempel, Kory Maincent, Andrew Lunn,
David S . Miller, Eric Dumazet, Jakub Kicinski, Rob Herring,
Krzysztof Kozlowski, Conor Dooley
Cc: netdev, devicetree, linux-kernel, Daniel Golle, Bjørn Mork
In-Reply-To: <20260630105651.756058-3-jelonek.jonas@gmail.com>
On 6/30/26 12:56 PM, Jonas Jelonek wrote:
> A range of PoE switches use a small microcontroller on the PCB to front
> the actual PSE silicon. The host CPU talks to that MCU over I2C/SMBus or
> UART using a fixed 12-byte request/response protocol with a trailing
> checksum; the PSE chips are managed by the MCU and are not accessed
> directly. The same protocol family is spoken by Realtek and Broadcom PSE
> MCUs, diverging in opcode numbering and a few response layouts, which the
> driver abstracts behind a per-dialect opcode table and parser hooks
> selected by the compatible. The specific PSE chip behind the MCU is
> detected at runtime and only influences per-chip constants (power scaling
> and the per-port cap).
>
> The driver is split into a shared core and two transport modules:
>
> - PSE_REALTEK_MCU: protocol, message framing, dialect machinery, and the
> pse_controller_ops glue.
> - PSE_REALTEK_MCU_I2C / PSE_REALTEK_MCU_UART: transport modules
> registering the MCU on an I2C bus or a serdev port respectively.
>
> The realtek-pse-mcu-* files and PSE_REALTEK_MCU* symbols match the
> realtek,pse-mcu-rtk / realtek,pse-mcu-brcm compatibles: all name the
> Realtek PSE-MCU front-end, not the MCU silicon or the PSE chip behind
> it (see the binding for the prefix rationale). Broadcom PSE MCUs speak
> the same protocol family and are handled by the same shared core
> through the dialect abstraction selected by the '-brcm' compatible.
>
> Power budgeting is left to the MCU firmware; the driver advertises
> PSE_BUDGET_EVAL_STRAT_DYNAMIC (controller-managed budget) accordingly.
>
> Signed-off-by: Jonas Jelonek <jelonek.jonas@gmail.com>
> ---
> MAINTAINERS | 7 +
> drivers/net/pse-pd/Kconfig | 28 +
> drivers/net/pse-pd/Makefile | 3 +
> drivers/net/pse-pd/realtek-pse-mcu-core.c | 1019 +++++++++++++++++++++
> drivers/net/pse-pd/realtek-pse-mcu-i2c.c | 163 ++++
> drivers/net/pse-pd/realtek-pse-mcu-uart.c | 156 ++++
> drivers/net/pse-pd/realtek-pse-mcu.h | 87 ++
> 7 files changed, 1463 insertions(+)
> create mode 100644 drivers/net/pse-pd/realtek-pse-mcu-core.c
> create mode 100644 drivers/net/pse-pd/realtek-pse-mcu-i2c.c
> create mode 100644 drivers/net/pse-pd/realtek-pse-mcu-uart.c
> create mode 100644 drivers/net/pse-pd/realtek-pse-mcu.h
This is quite large, and shouls be split in smaller patches to help
reviewers.
[...]
> +struct rtpse_mcu_dialect {
> + struct rtpse_mcu_opcode opcode[RTPSE_MCU_NUM_CMDS];
> +
> + /*
> + * Response parsers. Each dialect must supply its own; the core calls
> + * these unconditionally rather than carrying a default that would
> + * silently mis-decode bytes from a dialect that forgot to set them.
> + */
> + int (*parse_system_info)(const u8 *payload, struct rtpse_mcu_info *info);
The 2 existing implementation always return 0; you may consider change
it to a void function.
> +static int rtpse_mcu_port_get_voltage(struct pse_controller_dev *pcdev, int id)
> +{
> + struct rtpse_mcu_ctrl *pse = to_rtpse_mcu_ctrl(pcdev);
> + struct rtpse_mcu_port_measurement measurement;
> + int ret;
> + u32 uV;
> +
> + ret = rtpse_mcu_port_get_measurement(pse, id, &measurement);
> + if (ret)
> + return ret;
> +
> + /* 64.45mV per LSB */
> + uV = (u32)measurement.voltage_raw * 64450U;
This cast ^^^^^ should be unneeded.
> + return min_t(u32, uV, INT_MAX);
> +}
> +
> +static int rtpse_mcu_port_enable(struct pse_controller_dev *pcdev, int id)
> +{
> + return rtpse_mcu_port_set_state(to_rtpse_mcu_ctrl(pcdev), id, true);
> +}
> +
> +static int rtpse_mcu_port_disable(struct pse_controller_dev *pcdev, int id)
> +{
> + return rtpse_mcu_port_set_state(to_rtpse_mcu_ctrl(pcdev), id, false);
> +}
> +
> +static int rtpse_mcu_port_get_pw_limit(struct pse_controller_dev *pcdev, int id)
> +{
> + struct rtpse_mcu_ctrl *pse = to_rtpse_mcu_ctrl(pcdev);
> + struct rtpse_mcu_port_ext_config config;
> + int ret;
> +
> + ret = rtpse_mcu_port_get_ext_config(pse, id, &config);
> + if (ret)
> + return ret;
> +
> + return config.max_power * pse->chip->pw_read_lsb_mW;
> +}
> +
> +static int rtpse_mcu_port_set_pw_limit(struct pse_controller_dev *pcdev, int id, int max_mW)
> +{
> + const struct rtpse_mcu_opcode *type_opc, *val_opc;
> + struct rtpse_mcu_ctrl *pse = to_rtpse_mcu_ctrl(pcdev);
> + const struct rtpse_mcu_chip_info *chip = pse->chip;
> + unsigned int prg_val;
> + int ret;
> +
> + if (max_mW < 0 || max_mW > chip->max_mW_per_port)
> + return -ERANGE;
> +
> + type_opc = &pse->dialect->opcode[RTPSE_MCU_CMD_PORT_SET_POWER_LIMIT_TYPE];
> + val_opc = &pse->dialect->opcode[chip->pw_set_cmd];
> + if (!type_opc->valid || !val_opc->valid)
> + return -EOPNOTSUPP;
> +
> + /*
> + * Switch the port to user-defined limit mode first, then program the
> + * limit value. If the second cmd fails, the port is left in
> + * user-defined mode but with the previous limit value; the next
> + * successful set_pw_limit call recovers it.
> + */
> + ret = rtpse_mcu_port_cmd(pse, id, type_opc->op, RTPSE_MCU_PORT_PW_LIMIT_TYPE_USER);
> + if (ret)
> + return ret;
> +
> + prg_val = min_t(unsigned int, max_mW / chip->pw_set_lsb_mW, 0xff);
> +
> + return rtpse_mcu_port_cmd(pse, id, val_opc->op, prg_val);
> +}
> +
> +static int rtpse_mcu_port_get_pw_limit_ranges(struct pse_controller_dev *pcdev, int id,
> + struct pse_pw_limit_ranges *out)
> +{
> + struct ethtool_c33_pse_pw_limit_range *range;
> + struct rtpse_mcu_ctrl *pse = to_rtpse_mcu_ctrl(pcdev);
> +
> + range = kzalloc_obj(*range, GFP_KERNEL);
or just:
range = kzalloc_obj(*range);
> +static int rtpse_mcu_discover(struct rtpse_mcu_ctrl *pse, struct rtpse_mcu_info *info)
> +{
> + struct rtpse_mcu_ext_config ext_config;
> + unsigned long deadline;
> + int ret;
> +
> + /*
> + * The MCU may not answer on the bus yet right after power-up or
> + * enable-gpios assertion: depending on the transport it either stays
> + * silent (-ETIMEDOUT) or does not ACK its address at all (-ENXIO /
> + * -EREMOTEIO). Retry within a bounded wall-time window so a slow boot
> + * still probes, while a genuinely unresponsive MCU fails with its real
> + * error instead of deferring forever and masking it.
> + */
> + deadline = jiffies + msecs_to_jiffies(RTPSE_MCU_BOOT_TIMEOUT_MS);
> + do {
> + ret = rtpse_mcu_get_info(pse, info);
> + if (ret != -ETIMEDOUT && ret != -ENXIO && ret != -EREMOTEIO &&
> + ret != -EAGAIN)
> + break;
> + msleep(RTPSE_MCU_BOOT_RETRY_MS);
> + } while (time_before(jiffies, deadline));
> + if (ret)
> + return dev_err_probe(pse->dev, ret, "failed to read MCU info\n");
> +
> + switch (info->device_id) {
> + case RTPSE_MCU_DEVICE_ID_RTL8238B:
> + pse->chip = &rtl8238b_info;
> + break;
> + case RTPSE_MCU_DEVICE_ID_RTL8239:
> + pse->chip = &rtl8239_info;
> + break;
> + case RTPSE_MCU_DEVICE_ID_RTL8239C:
> + pse->chip = &rtl8239c_info;
> + break;
> + case RTPSE_MCU_DEVICE_ID_BCM59111:
> + pse->chip = &bcm59111_info;
> + break;
> + case RTPSE_MCU_DEVICE_ID_BCM59121:
> + pse->chip = &bcm59121_info;
> + break;
> + default:
> + return dev_err_probe(pse->dev, -EINVAL, "unknown PSE id 0x%x\n",
> + info->device_id);
> + }
> +
> + if (!info->max_ports || info->max_ports > RTPSE_MCU_MAX_PORTS)
> + return dev_err_probe(pse->dev, -EINVAL,
> + "MCU reports invalid port count %u\n", info->max_ports);
> +
> + ret = rtpse_mcu_get_ext_config(pse, &ext_config);
> + if (ret)
> + return dev_err_probe(pse->dev, ret, "failed to read MCU ext config\n");
> +
> + dev_info(pse->dev, "%s MCU, %s (id 0x%04x), %u ports across %u PSE chip(s)\n",
> + pse->dialect->mcu_type_str(info->mcu_type), pse->chip->name,
> + info->device_id, info->max_ports, ext_config.num_of_pses);
The general guidance is to try to avoid unneeded print on dmsg, as they
tend to scary admins, but I personally agree on message on modprobe.
No strong opionion either ways.
/P
^ permalink raw reply
* Re: [PATCH] net: phylink: reject unsupported speed/duplex in ksettings_set() with PHY
From: Nazle Asmade, Muhammad Nazim Amirul @ 2026-07-03 8:01 UTC (permalink / raw)
To: Andrew Lunn, Maxime Chevallier
Cc: linux@armlinux.org.uk, hkallweit1@gmail.com, davem@davemloft.net,
edumazet@google.com, kuba@kernel.org, pabeni@redhat.com,
netdev@vger.kernel.org, linux-kernel@vger.kernel.org
In-Reply-To: <c3a0e35b-a633-487c-bc08-d62f809047a6@lunn.ch>
On 1/7/2026 10:27 pm, Andrew Lunn wrote:
>> I think rejecting these settings makes sense, I'm however wondering
>> wether this is a fix or not, as this will change user-visible behaviour.
>> I'd err to the side of caution and send that to net-next, but maybe
>> Andrew will have more insight :)
>
> net-next seems reasonable.
>
> Andrew
Hi Maxime, Andrew
Sure, will repost it.
BR,
Nazim
^ permalink raw reply
* Re: [PATCH net] gve: fix Rx queue stall on alloc failure
From: Eddie Phillips @ 2026-07-03 8:03 UTC (permalink / raw)
To: Maciej Fijalkowski
Cc: Harshitha Ramamurthy, netdev, joshwash, andrew+netdev, davem,
edumazet, kuba, pabeni, ast, daniel, hawk, john.fastabend, bpf,
sdf, willemb, jordanrhee, nktgrg, maolson, jacob.e.keller,
thostet, csully, bcf, linux-kernel, stable
In-Reply-To: <akUUXT6UwTTD2yOs@boxer>
> I think this deserves to be pulled out of the timer logic?
If by this you mean pull the stats into a separate patch, I agree.
> - couldn't you detect this case within napi poll loop?
It can only be detected after attempting to refill the queue and finding
that we are still below the critical threshold.
> - if not, does it have to be per-q timer? wouldn't one global per pf timer
> satisfy your needs?
There are a few ways a global timer could be implemented,
- The global timer could queue napi for *all* queues, which would
result in a lot of unnecessary work.
- The global timer could iterate over each queue and try to detect
the critical low buffer condition, however this would require
introducing synchronization between the timer and the napis, which
would introduce expensive locking into the hot path.
- The global timer could be paired with a bitmap that stores which
queues need to be serviced.
A `struct timer_list` is only 40 bytes, so the current implemention is
not expensive. Though a global timer is valid, it's not strictly better.
That said, I agree that we can clean up the structure—I will move the
timer state from the individual RX rings to the `gve_priv` structure.
On Wed, Jul 1, 2026 at 6:22 AM Maciej Fijalkowski
<maciej.fijalkowski@intel.com> wrote:
>
> On Wed, Jul 01, 2026 at 12:53:41AM +0000, Harshitha Ramamurthy wrote:
> > From: Eddie Phillips <eddiephillips@google.com>
> >
> > When the system is under extreme memory pressure, page allocations can
> > fail during the Rx buffer refill loop. If the number of buffers posted
> > to hardware falls below a critical low threshold and the refill loop
> > exits due to allocation failures, the queue can stall:
> >
> > 1. The device drops incoming packets because there are no descriptors.
> > 2. Since no packets are processed, no Rx completions are generated.
> > 3. Because no completions occur, NAPI is never scheduled, preventing
> > the refill loop from running again even after memory is freed.
> >
> > This results in a permanent queue stall.
> >
> > Resolve this by introducing a starvation recovery timer for each Rx queue.
> > If the number of buffers posted to hardware falls below a critical low
> > threshold, start a timer to periodically reschedule NAPI. Once NAPI runs
> > and successfully refills the queue above the threshold, the timer is
> > not rescheduled.
> >
> > Also add a new ethtool statistic "rx_critical_low_bufs" to track the
> > number of times the starvation recovery timer is triggered.
>
> I think this deserves to be pulled out of the timer logic?
>
> Two questions tho:
> - couldn't you detect this case within napi poll loop?
> - if not, does it have to be per-q timer? wouldn't one global per pf timer
> satisfy your needs?
>
> >
> > Cc: stable@vger.kernel.org
> > Fixes: 9b8dd5e5ea48 ("gve: DQO: Add RX path")
> > Reviewed-by: Jordan Rhee <jordanrhee@google.com>
> > Signed-off-by: Eddie Phillips <eddiephillips@google.com>
> > Signed-off-by: Harshitha Ramamurthy <hramamurthy@google.com>
> > ---
> > drivers/net/ethernet/google/gve/gve.h | 4 ++++
> > drivers/net/ethernet/google/gve/gve_ethtool.c | 14 +++++++++++++-
> > drivers/net/ethernet/google/gve/gve_rx_dqo.c | 32 ++++++++++++++++++++++++++++++++
> > 3 files changed, 49 insertions(+), 1 deletion(-)
> >
> > diff --git a/drivers/net/ethernet/google/gve/gve.h b/drivers/net/ethernet/google/gve/gve.h
> > index 2f7bd330..8378bef2 100644
> > --- a/drivers/net/ethernet/google/gve/gve.h
> > +++ b/drivers/net/ethernet/google/gve/gve.h
> > @@ -13,6 +13,7 @@
> > #include <linux/netdevice.h>
> > #include <linux/net_tstamp.h>
> > #include <linux/pci.h>
> > +#include <linux/timer.h>
> > #include <linux/ptp_clock_kernel.h>
> > #include <linux/u64_stats_sync.h>
> > #include <net/page_pool/helpers.h>
> > @@ -41,6 +42,7 @@
> >
> > /* Interval to schedule a stats report update, 20000ms. */
> > #define GVE_STATS_REPORT_TIMER_PERIOD 20000
> > +#define GVE_RX_NAPI_RESCHED_MS 20 /* msecs */
> >
> > /* Numbers of NIC tx/rx stats in stats report. */
> > #define NIC_TX_STATS_REPORT_NUM 0
> > @@ -318,6 +320,7 @@ struct gve_rx_ring {
> > u64 rx_copied_pkt; /* free-running total number of copied packets */
> > u64 rx_skb_alloc_fail; /* free-running count of skb alloc fails */
> > u64 rx_buf_alloc_fail; /* free-running count of buffer alloc fails */
> > + u64 rx_critical_low_bufs; /* count of critical low buffer events */
> > u64 rx_desc_err_dropped_pkt; /* free-running count of packets dropped by descriptor error */
> > /* free-running count of unsplit packets due to header buffer overflow or hdr_len is 0 */
> > u64 rx_hsplit_unsplit_pkt;
> > @@ -334,6 +337,7 @@ struct gve_rx_ring {
> > struct gve_queue_resources *q_resources; /* head and tail pointer idx */
> > dma_addr_t q_resources_bus; /* dma address for the queue resources */
> > struct u64_stats_sync statss; /* sync stats for 32bit archs */
> > + struct timer_list starvation_timer; /* for queue starvation recovery */
> >
> > struct gve_rx_ctx ctx; /* Info for packet currently being processed in this ring. */
> >
> > diff --git a/drivers/net/ethernet/google/gve/gve_ethtool.c b/drivers/net/ethernet/google/gve/gve_ethtool.c
> > index a0e0472b..71b6efbf 100644
> > --- a/drivers/net/ethernet/google/gve/gve_ethtool.c
> > +++ b/drivers/net/ethernet/google/gve/gve_ethtool.c
> > @@ -46,6 +46,7 @@ static const char gve_gstrings_main_stats[][ETH_GSTRING_LEN] = {
> > "rx_hsplit_unsplit_pkt",
> > "interface_up_cnt", "interface_down_cnt", "reset_cnt",
> > "page_alloc_fail", "dma_mapping_error", "stats_report_trigger_cnt",
> > + "rx_critical_low_bufs",
> > };
> >
> > static const char gve_gstrings_rx_stats[][ETH_GSTRING_LEN] = {
> > @@ -58,6 +59,7 @@ static const char gve_gstrings_rx_stats[][ETH_GSTRING_LEN] = {
> > "rx_xdp_aborted[%u]", "rx_xdp_drop[%u]", "rx_xdp_pass[%u]",
> > "rx_xdp_tx[%u]", "rx_xdp_redirect[%u]",
> > "rx_xdp_tx_errors[%u]", "rx_xdp_redirect_errors[%u]", "rx_xdp_alloc_fails[%u]",
> > + "rx_critical_low_bufs[%u]",
> > };
> >
> > static const char gve_gstrings_tx_stats[][ETH_GSTRING_LEN] = {
> > @@ -151,12 +153,14 @@ gve_get_ethtool_stats(struct net_device *netdev,
> > {
> > u64 tmp_rx_pkts, tmp_rx_hsplit_pkt, tmp_rx_bytes, tmp_rx_hsplit_bytes,
> > tmp_rx_skb_alloc_fail, tmp_rx_buf_alloc_fail,
> > + tmp_rx_critical_low_bufs,
> > tmp_rx_desc_err_dropped_pkt, tmp_rx_hsplit_unsplit_pkt,
> > tmp_tx_pkts, tmp_tx_bytes,
> > tmp_xdp_tx_errors, tmp_xdp_redirect_errors;
> > u64 rx_buf_alloc_fail, rx_desc_err_dropped_pkt, rx_hsplit_unsplit_pkt,
> > rx_pkts, rx_hsplit_pkt, rx_skb_alloc_fail, rx_bytes, tx_pkts, tx_bytes,
> > - tx_dropped, xdp_tx_errors, xdp_redirect_errors;
> > + rx_critical_low_bufs, tx_dropped, xdp_tx_errors,
> > + xdp_redirect_errors;
> > int rx_base_stats_idx, max_rx_stats_idx, max_tx_stats_idx;
> > int stats_idx, stats_region_len, nic_stats_len;
> > struct stats *report_stats;
> > @@ -197,6 +201,7 @@ gve_get_ethtool_stats(struct net_device *netdev,
> >
> > for (rx_pkts = 0, rx_bytes = 0, rx_hsplit_pkt = 0,
> > rx_skb_alloc_fail = 0, rx_buf_alloc_fail = 0,
> > + rx_critical_low_bufs = 0,
> > rx_desc_err_dropped_pkt = 0, rx_hsplit_unsplit_pkt = 0,
> > xdp_tx_errors = 0, xdp_redirect_errors = 0,
> > ring = 0;
> > @@ -212,6 +217,8 @@ gve_get_ethtool_stats(struct net_device *netdev,
> > tmp_rx_bytes = rx->rbytes;
> > tmp_rx_skb_alloc_fail = rx->rx_skb_alloc_fail;
> > tmp_rx_buf_alloc_fail = rx->rx_buf_alloc_fail;
> > + tmp_rx_critical_low_bufs =
> > + rx->rx_critical_low_bufs;
> > tmp_rx_desc_err_dropped_pkt =
> > rx->rx_desc_err_dropped_pkt;
> > tmp_rx_hsplit_unsplit_pkt =
> > @@ -226,6 +233,7 @@ gve_get_ethtool_stats(struct net_device *netdev,
> > rx_bytes += tmp_rx_bytes;
> > rx_skb_alloc_fail += tmp_rx_skb_alloc_fail;
> > rx_buf_alloc_fail += tmp_rx_buf_alloc_fail;
> > + rx_critical_low_bufs += tmp_rx_critical_low_bufs;
> > rx_desc_err_dropped_pkt += tmp_rx_desc_err_dropped_pkt;
> > rx_hsplit_unsplit_pkt += tmp_rx_hsplit_unsplit_pkt;
> > xdp_tx_errors += tmp_xdp_tx_errors;
> > @@ -269,6 +277,7 @@ gve_get_ethtool_stats(struct net_device *netdev,
> > data[i++] = priv->page_alloc_fail;
> > data[i++] = priv->dma_mapping_error;
> > data[i++] = priv->stats_report_trigger_cnt;
> > + data[i++] = rx_critical_low_bufs;
> > i = GVE_MAIN_STATS_LEN;
> >
> > rx_base_stats_idx = 0;
> > @@ -337,6 +346,8 @@ gve_get_ethtool_stats(struct net_device *netdev,
> > tmp_rx_hsplit_bytes = rx->rx_hsplit_bytes;
> > tmp_rx_skb_alloc_fail = rx->rx_skb_alloc_fail;
> > tmp_rx_buf_alloc_fail = rx->rx_buf_alloc_fail;
> > + tmp_rx_critical_low_bufs =
> > + rx->rx_critical_low_bufs;
> > tmp_rx_desc_err_dropped_pkt =
> > rx->rx_desc_err_dropped_pkt;
> > tmp_xdp_tx_errors = rx->xdp_tx_errors;
> > @@ -381,6 +392,7 @@ gve_get_ethtool_stats(struct net_device *netdev,
> > } while (u64_stats_fetch_retry(&priv->rx[ring].statss,
> > start));
> > i += GVE_XDP_ACTIONS + 3; /* XDP rx counters */
> > + data[i++] = tmp_rx_critical_low_bufs;
> > }
> > } else {
> > i += priv->rx_cfg.num_queues * NUM_GVE_RX_CNTS;
> > diff --git a/drivers/net/ethernet/google/gve/gve_rx_dqo.c b/drivers/net/ethernet/google/gve/gve_rx_dqo.c
> > index 02cba280..303db4fa 100644
> > --- a/drivers/net/ethernet/google/gve/gve_rx_dqo.c
> > +++ b/drivers/net/ethernet/google/gve/gve_rx_dqo.c
> > @@ -18,6 +18,16 @@
> > #include <net/tcp.h>
> > #include <net/xdp_sock_drv.h>
> >
> > +static void gve_rx_starvation_timer(struct timer_list *t)
> > +{
> > + struct gve_rx_ring *rx = timer_container_of(rx, t, starvation_timer);
> > + struct gve_priv *priv = rx->gve;
> > + struct gve_notify_block *block;
> > +
> > + block = &priv->ntfy_blocks[rx->ntfy_id];
> > + napi_schedule(&block->napi);
> > +}
> > +
> > static void gve_rx_free_hdr_bufs(struct gve_priv *priv, struct gve_rx_ring *rx)
> > {
> > struct device *hdev = &priv->pdev->dev;
> > @@ -120,6 +130,7 @@ void gve_rx_stop_ring_dqo(struct gve_priv *priv, int idx)
> >
> > if (rx->dqo.page_pool)
> > page_pool_disable_direct_recycling(rx->dqo.page_pool);
> > + timer_delete_sync(&rx->starvation_timer);
> > gve_remove_napi(priv, ntfy_idx);
> > gve_rx_remove_from_block(priv, idx);
> > gve_rx_reset_ring_dqo(priv, idx);
> > @@ -136,6 +147,8 @@ void gve_rx_free_ring_dqo(struct gve_priv *priv, struct gve_rx_ring *rx,
> > u32 qpl_id;
> > int i;
> >
> > + timer_shutdown_sync(&rx->starvation_timer);
> > +
> > completion_queue_slots = rx->dqo.complq.mask + 1;
> > buffer_queue_slots = rx->dqo.bufq.mask + 1;
> >
> > @@ -232,6 +245,7 @@ int gve_rx_alloc_ring_dqo(struct gve_priv *priv,
> > rx->gve = priv;
> > rx->q_num = idx;
> > rx->packet_buffer_size = cfg->packet_buffer_size;
> > + timer_setup(&rx->starvation_timer, gve_rx_starvation_timer, 0);
> >
> > if (cfg->xdp) {
> > rx->packet_buffer_truesize = GVE_XDP_RX_BUFFER_SIZE_DQO;
> > @@ -365,6 +379,7 @@ void gve_rx_post_buffers_dqo(struct gve_rx_ring *rx)
> > struct gve_rx_compl_queue_dqo *complq = &rx->dqo.complq;
> > struct gve_rx_buf_queue_dqo *bufq = &rx->dqo.bufq;
> > struct gve_priv *priv = rx->gve;
> > + u32 num_bufs_avail_to_hw;
> > u32 num_avail_slots;
> > u32 num_full_slots;
> > u32 num_posted = 0;
> > @@ -400,6 +415,23 @@ void gve_rx_post_buffers_dqo(struct gve_rx_ring *rx)
> > }
> >
> > rx->fill_cnt += num_posted;
> > +
> > + /* If the queue has fewer than GVE_RX_BUF_THRESH_DQO descriptors
> > + * visible to the hardware, and no doorbell was written, the hardware
> > + * is in danger of starving and cannot trigger interrupts. Start the
> > + * timer to periodically reschedule NAPI and recover from starvation.
> > + */
> > + num_bufs_avail_to_hw =
> > + ((bufq->tail & ~(GVE_RX_BUF_THRESH_DQO - 1)) -
> > + bufq->head) & bufq->mask;
> > +
> > + if (num_bufs_avail_to_hw < GVE_RX_BUF_THRESH_DQO) {
> > + u64_stats_update_begin(&rx->statss);
> > + rx->rx_critical_low_bufs++;
> > + u64_stats_update_end(&rx->statss);
> > + mod_timer(&rx->starvation_timer,
> > + jiffies + msecs_to_jiffies(GVE_RX_NAPI_RESCHED_MS));
> > + }
> > }
> >
> > static void gve_rx_skb_csum(struct sk_buff *skb,
> > --
> > 2.55.0.rc2.803.g1fd1e6609c-goog
> >
> >
^ permalink raw reply
* Re: [PATCH net-next 00/17] net: dsa: mv88e6xxx: support for Remote Management Unit
From: Paolo Abeni @ 2026-07-03 8:05 UTC (permalink / raw)
To: Luke Howard, Andrew Lunn, Vladimir Oltean, David S. Miller,
Eric Dumazet, Jakub Kicinski, Simon Horman, Russell King,
Richard Cochran, Florian Fainelli
Cc: Cedric Jehasse, Max Holtmann, Max Hunter, Kieran Tyrrell,
Ryan Wilkins, Mattias Forsblad, netdev, linux-kernel
In-Reply-To: <20260703-net-next-mv88e6xxx-rmu-v1-0-991a27d78bca@padl.com>
On 7/3/26 9:46 AM, Luke Howard wrote:
> This patch set shephards a series of commits made by Andrew Lunn
> and others to support remotely managing Marvell switches over
> Ethernet (as opposed to MDIO) using the Remote Management Unit
> (RMU).
>
> This is the second of four patch sets: the remaining two add
> support for remote management when MDIO is unavailable.
>
> Signed-off-by: Luke Howard <lukeh@padl.com>
You have already 35 patches pending. Please read carefully:
https://elixir.bootlin.com/linux/v7.1.2/source/Documentation/process/maintainer-netdev.rst
and specifically `Limit patches outstanding on mailing list`
Also note that the limit is supposed to be approached/reached by
submitters with a well known history of positive contributions (i.e.
reviews).
I'm deferring (at least) this series. Please repost only after that the
other pending ones have been processed.
/P
^ permalink raw reply
* Re: [PATCH net-next 2/2] net: ethernet: qualcomm: Constify "queue_map" in ppe_ring_queue_map_set()
From: Jie Luo @ 2026-07-03 8:06 UTC (permalink / raw)
To: Krzysztof Kozlowski, Andrew Lunn, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, netdev, linux-kernel
In-Reply-To: <20260702094908.79859-4-krzysztof.kozlowski@oss.qualcomm.com>
On 7/2/2026 5:49 PM, Krzysztof Kozlowski wrote:
> "queue_map" is a pointer to "u32" and is not modified by the
> ppe_ring_queue_map_set() function, thus can be made a pointer to const to
> indicate that function is treating the pointed value read-only. This in
> general makes the code easier to follow and a bit safer.
Thanks for this improvement.
Reviewed-by: Luo Jie <jie.luo@oss.qualcomm.com>
^ permalink raw reply
* Re: [PATCH net-next 1/2] net: ethernet: qualcomm: Unconstify function arguments passed by value
From: Jie Luo @ 2026-07-03 8:07 UTC (permalink / raw)
To: Krzysztof Kozlowski, Andrew Lunn, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, netdev, linux-kernel
In-Reply-To: <20260702094908.79859-3-krzysztof.kozlowski@oss.qualcomm.com>
On 7/2/2026 5:49 PM, Krzysztof Kozlowski wrote:
> There is no benefit in marking "const" a pass-by-value (not a pointer)
> function argument, because it is passed as a copy on the stack. No code
> readability improvements, no additional compiler-time safety for misuse.
> Drop such redundant "const".
Reviewed-by: Luo Jie <jie.luo@oss.qualcomm.com>
^ permalink raw reply
* Re: [PATCH 3/3] net: stmmac: dwmac-socfpga: Add mac-mode DT property support
From: Nazle Asmade, Muhammad Nazim Amirul @ 2026-07-03 8:10 UTC (permalink / raw)
To: Andrew Lunn
Cc: dinguyen@kernel.org, maxime.chevallier@bootlin.com,
rmk+kernel@armlinux.org.uk, krzk+dt@kernel.org,
conor+dt@kernel.org, robh@kernel.org, davem@davemloft.net,
edumazet@google.com, kuba@kernel.org, pabeni@redhat.com,
andrew+netdev@lunn.ch, devicetree@vger.kernel.org,
linux-arm-kernel@lists.infradead.org, netdev@vger.kernel.org,
linux-kernel@vger.kernel.org
In-Reply-To: <e489be4a-5940-46e5-ae06-b78f1c919352@lunn.ch>
On 1/7/2026 10:43 pm, Andrew Lunn wrote:
> On Tue, Jun 30, 2026 at 06:31:08AM -0700, muhammad.nazim.amirul.nazle.asmade@altera.com wrote:
>> From: Nazim Amirul <muhammad.nazim.amirul.nazle.asmade@altera.com>
>>
>> Russell King's commit de696c63c1dc ("net: stmmac: socfpga: convert to
>> use phy_interface") replaced mac_interface with phy_interface in
>> socfpga_get_plat_phymode(), noting that no upstream DTS files set the
>> "mac-mode" property, making the two values identical.
>>
>> The Agilex5 SoCDK TSN Config2 board is an exception: its gmac1 TSN
>> port uses GMII internally in the MAC while the PHY-side interface is
>> RGMII, so mac-mode and phy-mode differ. Without restoring mac_interface
>> support, the MAC is configured with RGMII instead of GMII, causing
>> connectivity failures on this board.
>>
>> Add socfpga_of_get_mac_mode() to read the optional "mac-mode" DT
>> property and store it in a new mac_interface field. When the property
>> is absent, mac_interface falls back to phy_interface, preserving
>> the existing behaviour for all other boards.
>
> I don't actually see a need for mac-mode. From what you are saying,
> there is no choice. The MAC is hard wired to the converter block. So
> you can just look at the compatible. You are going to need to use the
> compatible anyway, to mask the phy-mode to handle the "MAC" doing the
> RGMII delays.
>
> Andrew
>
Hi Maxime, Andrew
Thanks for the reviews!
Hi Andrew,
The challenge with using compatible is that the TSN Config2 board has
two ports — only gmac1 uses the GMII-to-RGMII converter, while gmac2
connects directly to its PHY. A board-level compatible check would need
additional logic to know which port to apply the GMII override to.
With mac-mode, the configuration is per-port in the DTS — consistent
with how altr,emac-splitter works today. I also addressing Maxime's
feedback in v2 by unifying the splitter and mac-mode code paths.
BR,
Nazim
^ permalink raw reply
* Re: [RFC PATCH 3/3] coredump, net: remove `SOCK_COREDUMP`
From: Christian Brauner @ 2026-07-03 8:11 UTC (permalink / raw)
To: John Ericson
Cc: David S . Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
John Ericson, Cong Wang, Kuniyuki Iwashima, Simon Horman,
Christian Brauner, David Rheinsberg, Andy Lutomirski,
Sergei Zimmerman, netdev, linux-fsdevel, Mickaël Salaün,
Günther Noack, Paul Moore, linux-security-module,
linux-kernel
In-Reply-To: <20260703073948.2541875-4-John.Ericson@Obsidian.Systems>
> The utility of the refactors of the last two commits is demonstrated by
> fixing this glaring layer violation: the unix socket implementation
> knowing about the coredump socket caller.
>
> Before, when the only way to connect to a socket was via the UAPI
> `struct sockaddr_un`, the only way to implement the proper logic that
> the kernel needs to resolve the coredump socket path was via hacking it
> into the socket implementation.
>
> In addition to being quite ugly, this layer violation is not great for
> security. The intent is that `SOCK_COREDUMP` can only be used by the
> kernel, and to be clear, I have no reason to believe this is not
> correctly enforced today. But because of the many functions with flags
> arguments, this is not a locally-enforced invariant. Some change, at
> some point, could mess up, and allow user-provided flags to sneak in,
> and this strikes me as a mistake waiting to happen. At that point, a
> user could exploit this to connect to any socket it likes, bypassing
> permission checks.
>
> Now, with the two functions we've just previously factored out, we can
> fix the layering. The custom path lookup logic lives with the coredump
> caller, where it belongs. Once the right `struct path` is found
> (actually just the inode is needed), the coredump caller can resolve a
> `struct sock *` from it, and then directly connect to it. With this
> change, `SOCK_COREDUMP` is no longer needed at all, and can be deleted.
> The layer violation is gone, and the security footgun is gone with it.
>
> As an added bonus, remove `flags` parameters from a number of internal
> functions that no longer need them. They were just taking flags
> parameters for the sake of `SOCK_COREDUMP`, and so with that gone, those
> flag parameters are also no longer needed. If or when there is a new
> flag that motivates them, they can be added back.
>
> Tested that `coredump_socket_protocol_test` still passes.
>
> Signed-off-by: John Ericson <mail@JohnEricson.me>
>
> diff --git a/fs/coredump.c b/fs/coredump.c
> index e68a76ff92a3..e1452021218e 100644
> --- a/fs/coredump.c
> +++ b/fs/coredump.c
> @@ -14,6 +14,7 @@
> #include <linux/perf_event.h>
> #include <linux/highmem.h>
> #include <linux/spinlock.h>
> +#include <linux/cred.h>
> #include <linux/key.h>
> #include <linux/personality.h>
> #include <linux/binfmts.h>
> @@ -21,6 +22,7 @@
> #include <linux/sort.h>
> #include <linux/sched/coredump.h>
> #include <linux/sched/signal.h>
> +#include <linux/sched/task.h>
> #include <linux/sched/task_stack.h>
> #include <linux/utsname.h>
> #include <linux/pid_namespace.h>
> @@ -50,7 +52,6 @@
> #include <net/net_namespace.h>
> #include <net/sock.h>
> #include <uapi/linux/pidfd.h>
> -#include <uapi/linux/un.h>
> #include <uapi/linux/coredump.h>
>
> #include <linux/uaccess.h>
> @@ -668,17 +669,10 @@ static int umh_coredump_setup(struct subprocess_info *info, struct cred *new)
> static bool coredump_sock_connect(struct core_name *cn, struct coredump_params *cprm)
> {
> struct file *file __free(fput) = NULL;
> - struct sockaddr_un addr = {
> - .sun_family = AF_UNIX,
> - };
> - ssize_t addr_len;
> - int retval;
> + struct path root, path;
> struct socket *socket;
> -
> - addr_len = strscpy(addr.sun_path, cn->corename);
> - if (addr_len < 0)
> - return false;
> - addr_len += offsetof(struct sockaddr_un, sun_path) + 1;
> + struct sock *sk;
> + int retval;
>
> /*
> * It is possible that the userspace process which is supposed
> @@ -710,14 +704,37 @@ static bool coredump_sock_connect(struct core_name *cn, struct coredump_params *
> */
> pidfs_coredump(cprm);
>
> - retval = kernel_connect(socket, (struct sockaddr_unsized *)(&addr), addr_len,
> - O_NONBLOCK | SOCK_COREDUMP);
> + /*
> + * Resolve the socket path relative to init's root and with kernel
> + * credentials, and with symlinks, magic links and escaping the
> + * root all forbidden, so the dumping process cannot use its own
> + * filesystem view to redirect its core to an arbitrary socket.
> + */
> + task_lock(&init_task);
> + get_fs_root(init_task.fs, &root);
> + task_unlock(&init_task);
> +
> + scoped_with_kernel_creds()
> + retval = vfs_path_lookup(root.dentry, root.mnt, cn->corename,
> + LOOKUP_BENEATH | LOOKUP_NO_SYMLINKS |
> + LOOKUP_NO_MAGICLINKS, &path);
> + path_put(&root);
> + if (retval)
> + return false;
> +
> + /* Connect directly to the socket bound there, by fd not by name. */
> + sk = unix_lookup_bsd_path(&path, SOCK_STREAM);
> + path_put(&path);
> + if (IS_ERR(sk))
> + return false;
>
> + retval = kernel_unix_connect_direct(sk, socket, O_NONBLOCK);
I don't think dragging the bowels of net/ into fs/coredump.c just for
the sake of getting a purely internal SOCK_COREDUMP flag out of the way
is the right way to go.
The two helpers also make no sense to me and force a bunch of cleanup on
the caller as well.
I suspect the saner thing to do would be to introduce a primitive for
connecting to an AF_UNIX path-based socket directly instead of this
weird dance here, no?
int kernel_unix_connect(const char *path, struct socket *socket, unsigned int o_flags, int type)
then my kthread changes would collapse this into:
scoped_with_init_fs()
retval = kernel_unix_connect(path, socket, O_NONBLOCK, SOCK_STREAM);
> + sock_put(sk);
> if (retval) {
> if (retval == -EAGAIN)
> - coredump_report_failure("Coredump socket %s receive queue full", addr.sun_path);
> + coredump_report_failure("Coredump socket %s receive queue full", cn->corename);
> else
> - coredump_report_failure("Coredump socket connection %s failed %d", addr.sun_path, retval);
> + coredump_report_failure("Coredump socket connection %s failed %d", cn->corename, retval);
> return false;
> }
>
> diff --git a/include/linux/lsm_hook_defs.h b/include/linux/lsm_hook_defs.h
> index 65c9609ec207..3d6fbb6d2628 100644
> --- a/include/linux/lsm_hook_defs.h
> +++ b/include/linux/lsm_hook_defs.h
> @@ -323,8 +323,7 @@ LSM_HOOK(int, 0, watch_key, struct key *key)
> #endif /* CONFIG_SECURITY && CONFIG_KEY_NOTIFICATIONS */
>
> #if defined(CONFIG_SECURITY_NETWORK) && defined(CONFIG_SECURITY_PATH)
> -LSM_HOOK(int, 0, unix_find, const struct path *path, struct sock *other,
> - int flags)
> +LSM_HOOK(int, 0, unix_find, const struct path *path, struct sock *other)
I'm not sure why we would go on altering all kinds of LSM hooks as well.
--
Christian Brauner <brauner@kernel.org>
^ permalink raw reply
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox