@@ -15,3 +15,4 @@ mv88e6xxx-objs += port_hidden.o
mv88e6xxx-$(CONFIG_NET_DSA_MV88E6XXX_PTP) += ptp.o
mv88e6xxx-objs += serdes.o
mv88e6xxx-objs += smi.o
+mv88e6xxx-objs += rmu.o
@@ -42,6 +42,7 @@
#include "ptp.h"
#include "serdes.h"
#include "smi.h"
+#include "rmu.h"
static void assert_reg_lock(struct mv88e6xxx_chip *chip)
{
@@ -1529,6 +1530,10 @@ static int mv88e6xxx_trunk_setup(struct mv88e6xxx_chip *chip)
static int mv88e6xxx_rmu_setup(struct mv88e6xxx_chip *chip)
{
+ if (chip->info->ops->rmu_enable)
+ if (!chip->info->ops->rmu_enable(chip))
+ return mv88e6xxx_rmu_init(chip);
+
if (chip->info->ops->rmu_disable)
return chip->info->ops->rmu_disable(chip);
@@ -4090,6 +4095,7 @@ static const struct mv88e6xxx_ops mv88e6085_ops = {
.ppu_disable = mv88e6185_g1_ppu_disable,
.reset = mv88e6185_g1_reset,
.rmu_disable = mv88e6085_g1_rmu_disable,
+ .rmu_enable = mv88e6085_g1_rmu_enable,
.vtu_getnext = mv88e6352_g1_vtu_getnext,
.vtu_loadpurge = mv88e6352_g1_vtu_loadpurge,
.stu_getnext = mv88e6352_g1_stu_getnext,
@@ -4173,6 +4179,7 @@ static const struct mv88e6xxx_ops mv88e6097_ops = {
.pot_clear = mv88e6xxx_g2_pot_clear,
.reset = mv88e6352_g1_reset,
.rmu_disable = mv88e6085_g1_rmu_disable,
+ .rmu_enable = mv88e6085_g1_rmu_enable,
.vtu_getnext = mv88e6352_g1_vtu_getnext,
.vtu_loadpurge = mv88e6352_g1_vtu_loadpurge,
.phylink_get_caps = mv88e6095_phylink_get_caps,
@@ -5292,6 +5299,7 @@ static const struct mv88e6xxx_ops mv88e6352_ops = {
.pot_clear = mv88e6xxx_g2_pot_clear,
.reset = mv88e6352_g1_reset,
.rmu_disable = mv88e6352_g1_rmu_disable,
+ .rmu_enable = mv88e6352_g1_rmu_enable,
.atu_get_hash = mv88e6165_g1_atu_get_hash,
.atu_set_hash = mv88e6165_g1_atu_set_hash,
.vtu_getnext = mv88e6352_g1_vtu_getnext,
@@ -5359,6 +5367,7 @@ static const struct mv88e6xxx_ops mv88e6390_ops = {
.pot_clear = mv88e6xxx_g2_pot_clear,
.reset = mv88e6352_g1_reset,
.rmu_disable = mv88e6390_g1_rmu_disable,
+ .rmu_enable = mv88e6390_g1_rmu_enable,
.atu_get_hash = mv88e6165_g1_atu_get_hash,
.atu_set_hash = mv88e6165_g1_atu_set_hash,
.vtu_getnext = mv88e6390_g1_vtu_getnext,
@@ -5426,6 +5435,7 @@ static const struct mv88e6xxx_ops mv88e6390x_ops = {
.pot_clear = mv88e6xxx_g2_pot_clear,
.reset = mv88e6352_g1_reset,
.rmu_disable = mv88e6390_g1_rmu_disable,
+ .rmu_enable = mv88e6390_g1_rmu_enable,
.atu_get_hash = mv88e6165_g1_atu_get_hash,
.atu_set_hash = mv88e6165_g1_atu_set_hash,
.vtu_getnext = mv88e6390_g1_vtu_getnext,
@@ -5496,6 +5506,7 @@ static const struct mv88e6xxx_ops mv88e6393x_ops = {
.pot_clear = mv88e6xxx_g2_pot_clear,
.reset = mv88e6352_g1_reset,
.rmu_disable = mv88e6390_g1_rmu_disable,
+ .rmu_enable = mv88e6390_g1_rmu_enable,
.atu_get_hash = mv88e6165_g1_atu_get_hash,
.atu_set_hash = mv88e6165_g1_atu_set_hash,
.vtu_getnext = mv88e6390_g1_vtu_getnext,
@@ -6918,6 +6929,8 @@ static const struct dsa_switch_ops mv88e6xxx_switch_ops = {
.crosschip_lag_change = mv88e6xxx_crosschip_lag_change,
.crosschip_lag_join = mv88e6xxx_crosschip_lag_join,
.crosschip_lag_leave = mv88e6xxx_crosschip_lag_leave,
+ .inband_receive = mv88e6xxx_inband_rcv,
+ .master_state_change = mv88e6xxx_rmu_master_change,
};
static int mv88e6xxx_register_switch(struct mv88e6xxx_chip *chip)
@@ -33,6 +33,8 @@
#define MV88E6XXX_MAX_GPIO 16
+#define MV88E6XXX_WAIT_POLL_TIME_MS 200
+
enum mv88e6xxx_egress_mode {
MV88E6XXX_EGRESS_MODE_UNMODIFIED,
MV88E6XXX_EGRESS_MODE_UNTAGGED,
@@ -266,6 +268,7 @@ struct mv88e6xxx_vlan {
struct mv88e6xxx_port {
struct mv88e6xxx_chip *chip;
int port;
+ u64 rmu_raw_stats[64];
struct mv88e6xxx_vlan bridge_pvid;
u64 serdes_stats[2];
u64 atu_member_violation;
@@ -282,6 +285,18 @@ struct mv88e6xxx_port {
struct devlink_region *region;
};
+struct mv88e6xxx_rmu {
+ /* RMU resources */
+ struct net_device *netdev;
+ struct mv88e6xxx_bus_ops *ops;
+ struct completion completion;
+ /* Mutex for RMU operations */
+ struct mutex mutex;
+ u16 got_id;
+ u8 request_cmd;
+ u8 seq_no;
+};
+
enum mv88e6xxx_region_id {
MV88E6XXX_REGION_GLOBAL1 = 0,
MV88E6XXX_REGION_GLOBAL2,
@@ -410,12 +425,16 @@ struct mv88e6xxx_chip {
/* Bridge MST to SID mappings */
struct list_head msts;
+
+ /* RMU resources */
+ struct mv88e6xxx_rmu rmu;
};
struct mv88e6xxx_bus_ops {
int (*read)(struct mv88e6xxx_chip *chip, int addr, int reg, u16 *val);
int (*write)(struct mv88e6xxx_chip *chip, int addr, int reg, u16 val);
int (*init)(struct mv88e6xxx_chip *chip);
+ int (*get_rmon)(struct mv88e6xxx_chip *chip, int port, uint64_t *data);
};
struct mv88e6xxx_mdio_bus {
@@ -637,6 +656,7 @@ struct mv88e6xxx_ops {
/* Remote Management Unit operations */
int (*rmu_disable)(struct mv88e6xxx_chip *chip);
+ int (*rmu_enable)(struct mv88e6xxx_chip *chip);
/* Precision Time Protocol operations */
const struct mv88e6xxx_ptp_ops *ptp_ops;
@@ -466,18 +466,102 @@ int mv88e6085_g1_rmu_disable(struct mv88e6xxx_chip *chip)
MV88E6085_G1_CTL2_RM_ENABLE, 0);
}
+int mv88e6085_g1_rmu_enable(struct mv88e6xxx_chip *chip)
+{
+ int val = MV88E6352_G1_CTL2_RMU_MODE_DISABLED;
+ int upstream_port = -1;
+
+ upstream_port = dsa_switch_upstream_port(chip->ds);
+ dev_dbg(chip->dev, "RMU: Enabling on port %d", upstream_port);
+ if (upstream_port < 0)
+ return -EOPNOTSUPP;
+
+ switch (upstream_port) {
+ case 9:
+ val = MV88E6085_G1_CTL2_RM_ENABLE;
+ break;
+ case 10:
+ val = MV88E6085_G1_CTL2_RM_ENABLE | MV88E6085_G1_CTL2_P10RM;
+ break;
+ 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 val = MV88E6352_G1_CTL2_RMU_MODE_DISABLED;
+ int upstream_port;
+
+ upstream_port = dsa_switch_upstream_port(chip->ds);
+ dev_dbg(chip->dev, "RMU: Enabling on port %d", upstream_port);
+ if (upstream_port < 0)
+ return -EOPNOTSUPP;
+
+ switch (upstream_port) {
+ case 4:
+ val = MV88E6352_G1_CTL2_RMU_MODE_PORT_4;
+ break;
+ case 5:
+ val = MV88E6352_G1_CTL2_RMU_MODE_PORT_5;
+ break;
+ case 6:
+ val = MV88E6352_G1_CTL2_RMU_MODE_PORT_6;
+ break;
+ default:
+ 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 val = MV88E6390_G1_CTL2_RMU_MODE_DISABLED;
+ int upstream_port;
+
+ upstream_port = dsa_switch_upstream_port(chip->ds);
+ dev_dbg(chip->dev, "RMU: Enabling on port %d", upstream_port);
+ if (upstream_port < 0)
+ return -EOPNOTSUPP;
+
+ switch (upstream_port) {
+ case 0:
+ val = MV88E6390_G1_CTL2_RMU_MODE_PORT_0;
+ break;
+ case 1:
+ val = MV88E6390_G1_CTL2_RMU_MODE_PORT_1;
+ break;
+ case 9:
+ val = MV88E6390_G1_CTL2_RMU_MODE_PORT_9;
+ break;
+ case 10:
+ val = MV88E6390_G1_CTL2_RMU_MODE_PORT_10;
+ break;
+ default:
+ 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,
@@ -313,8 +313,11 @@ int mv88e6250_g1_ieee_pri_map(struct mv88e6xxx_chip *chip);
int mv88e6185_g1_set_cascade_port(struct mv88e6xxx_chip *chip, int port);
int mv88e6085_g1_rmu_disable(struct mv88e6xxx_chip *chip);
+int mv88e6085_g1_rmu_enable(struct mv88e6xxx_chip *chip);
int mv88e6352_g1_rmu_disable(struct mv88e6xxx_chip *chip);
+int mv88e6352_g1_rmu_enable(struct mv88e6xxx_chip *chip);
int mv88e6390_g1_rmu_disable(struct mv88e6xxx_chip *chip);
+int mv88e6390_g1_rmu_enable(struct mv88e6xxx_chip *chip);
int mv88e6xxx_g1_set_device_number(struct mv88e6xxx_chip *chip, int index);
new file mode 100644
@@ -0,0 +1,276 @@
+// 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 <asm/unaligned.h>
+#include "rmu.h"
+#include "global1.h"
+
+static int mv88e6xxx_validate_port_mac(struct dsa_switch *ds, struct sk_buff *skb)
+{
+ unsigned char *ethhdr;
+ struct dsa_port *dp;
+ u8 pkt_port;
+
+ pkt_port = (skb->data[7] >> 3) & 0xf;
+ dp = dsa_to_port(ds, pkt_port);
+ if (!dp) {
+ dev_dbg_ratelimited(ds->dev, "RMU: couldn't find port for %d\n", pkt_port);
+ return -ENXIO;
+ }
+
+ /* Check matching MAC */
+ ethhdr = skb_mac_header(skb);
+ if (memcmp(dp->slave->dev_addr, ethhdr, ETH_ALEN)) {
+ dev_dbg_ratelimited(ds->dev, "RMU: mismatching MAC address for request. Rx %pM expecting %pM\n",
+ ethhdr, dp->slave->dev_addr);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+int mv88e6xxx_inband_rcv(struct dsa_switch *ds, struct sk_buff *skb, int seq_no)
+{
+ struct mv88e6xxx_chip *chip = ds->priv;
+ struct mv88e6xxx_port *port;
+ u16 prodnum;
+ u16 format;
+ u16 code;
+ u8 pkt_dev;
+ u8 pkt_prt;
+ int i;
+
+ /* Extract response data */
+ format = get_unaligned_be16(&skb->data[0]);
+ if (format != htons(MV88E6XXX_RMU_RESP_FORMAT_1) &&
+ format != htons(MV88E6XXX_RMU_RESP_FORMAT_2)) {
+ dev_err_ratelimited(chip->dev, "RMU: received unknown format 0x%04x", format);
+ goto out;
+ }
+
+ code = get_unaligned_be16(&skb->data[4]);
+ if (code == ntohs(MV88E6XXX_RMU_RESP_ERROR)) {
+ dev_err_ratelimited(chip->dev, "RMU: error response code 0x%04x", code);
+ goto out;
+ }
+
+ pkt_dev = skb->data[6] & 0x1f;
+ if (!dsa_switch_find(ds->dst->index, pkt_dev)) {
+ dev_err_ratelimited(chip->dev, "RMU: response from unknown chip with index %d\n",
+ pkt_dev);
+ goto out;
+ }
+
+ /* Check sequence number */
+ if (seq_no != chip->rmu.seq_no) {
+ dev_err_ratelimited(chip->dev, "RMU: wrong seqno received %d, expected %d",
+ seq_no, chip->rmu.seq_no);
+ goto out;
+ }
+
+ /* Check response code */
+ switch (chip->rmu.request_cmd) {
+ case MV88E6XXX_RMU_REQ_GET_ID: {
+ if (code == MV88E6XXX_RMU_RESP_CODE_GOT_ID) {
+ prodnum = get_unaligned_be16(&skb->data[2]);
+ chip->rmu.got_id = prodnum;
+ dev_info_ratelimited(chip->dev, "RMU: received id OK with product number: 0x%04x\n",
+ chip->rmu.got_id);
+ } else {
+ dev_dbg_ratelimited(chip->dev,
+ "RMU: unknown response for GET_ID format 0x%04x code 0x%04x",
+ format, code);
+ }
+ break;
+ }
+ case MV88E6XXX_RMU_REQ_DUMP_MIB:
+ if (code == MV88E6XXX_RMU_RESP_CODE_DUMP_MIB &&
+ !mv88e6xxx_validate_port_mac(ds, skb)) {
+ pkt_prt = (skb->data[7] & 0x78) >> 3;
+ port = &chip->ports[pkt_prt];
+ if (!port) {
+ dev_err_ratelimited(chip->dev, "RMU: illegal port number in response: %d\n",
+ pkt_prt);
+ goto out;
+ }
+
+ /* Copy whole array for further
+ * processing according to chip type
+ */
+ for (i = 0; i < MV88E6XXX_RMU_MAX_RMON; i++)
+ port->rmu_raw_stats[i] = get_unaligned_be32(&skb->data[12 + i * 4]);
+ }
+ break;
+ default:
+ dev_err_ratelimited(chip->dev,
+ "RMU: unknown response format 0x%04x and code 0x%04x from chip %d\n",
+ format, code, chip->ds->index);
+ break;
+ }
+
+out:
+ complete(&chip->rmu.completion);
+
+ return 0;
+}
+
+static int mv88e6xxx_rmu_tx(struct mv88e6xxx_chip *chip, int port,
+ const char *msg, int len)
+{
+ const struct dsa_device_ops *tag_ops;
+ const struct dsa_port *dp;
+ unsigned char *ethhdr;
+ unsigned char *data;
+ struct sk_buff *skb;
+
+ dp = dsa_to_port(chip->ds, port);
+ if (!dp || !dp->cpu_dp)
+ return 0;
+
+ tag_ops = dp->cpu_dp->tag_ops;
+ if (!tag_ops)
+ return -ENODEV;
+
+ skb = netdev_alloc_skb(chip->rmu.netdev, 64);
+ if (!skb)
+ return -ENOMEM;
+
+ skb_reserve(skb, 2 * ETH_HLEN + tag_ops->needed_headroom);
+ skb_reset_network_header(skb);
+ skb->pkt_type = PACKET_OUTGOING;
+ skb->dev = chip->rmu.netdev;
+
+ /* Create RMU L3 message */
+ data = skb_put(skb, len);
+ memcpy(data, msg, len);
+
+ ethhdr = skb_mac_header(skb);
+
+ return tag_ops->inband_xmit(skb, dp->slave, ++chip->rmu.seq_no);
+}
+
+static int mv88e6xxx_rmu_send_wait(struct mv88e6xxx_chip *chip, int port,
+ int request, const char *msg, int len)
+{
+ const struct dsa_port *dp;
+ int ret = 0;
+
+ dp = dsa_to_port(chip->ds, port);
+ if (!dp)
+ return 0;
+
+ mutex_lock(&chip->rmu.mutex);
+
+ chip->rmu.request_cmd = request;
+
+ ret = mv88e6xxx_rmu_tx(chip, port, msg, len);
+ if (ret == -ENODEV) {
+ /* Device not ready yet? Try again later */
+ ret = 0;
+ goto out;
+ }
+
+ if (ret) {
+ dev_dbg(chip->dev, "RMU: error transmitting request (%d)", ret);
+ goto out;
+ }
+
+ ret = wait_for_completion_timeout(&chip->rmu.completion,
+ msecs_to_jiffies(MV88E6XXX_WAIT_POLL_TIME_MS));
+ if (ret == 0) {
+ dev_dbg(chip->dev,
+ "RMU: timeout waiting for request %d (%d) on dev:port %d:%d\n",
+ request, ret, chip->ds->index, port);
+ ret = -ETIMEDOUT;
+ }
+
+out:
+ mutex_unlock(&chip->rmu.mutex);
+
+ return ret > 0 ? 0 : ret;
+}
+
+static int mv88e6xxx_rmu_get_id(struct mv88e6xxx_chip *chip, int port)
+{
+ const u8 get_id[8] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
+ int ret = -1;
+
+ if (chip->rmu.got_id)
+ return 0;
+
+ chip->rmu.netdev = dev_get_by_name(&init_net, "chan0");
+ if (!chip->rmu.netdev) {
+ dev_dbg(chip->dev, "RMU: unable to get interface");
+ return -ENODEV;
+ }
+
+ ret = mv88e6xxx_rmu_send_wait(chip, port, MV88E6XXX_RMU_REQ_GET_ID, get_id, 8);
+ if (ret) {
+ dev_dbg(chip->dev, "RMU: error for command GET_ID %d index %d\n", ret,
+ chip->ds->index);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int mv88e6xxx_rmu_stats_get(struct mv88e6xxx_chip *chip, int port, uint64_t *data)
+{
+ u8 dump_mib[8] = { 0x00, 0x01, 0x00, 0x00, 0x10, 0x20, 0x00, 0x00 };
+ int ret;
+
+ if (!chip)
+ return 0;
+
+ ret = mv88e6xxx_rmu_get_id(chip, port);
+ if (ret)
+ return ret;
+
+ /* Send a GET_MIB command */
+ dump_mib[7] = port;
+ ret = mv88e6xxx_rmu_send_wait(chip, port, MV88E6XXX_RMU_REQ_DUMP_MIB, dump_mib, 8);
+ if (ret) {
+ dev_dbg(chip->dev, "RMU: error for command DUMP_MIB %d dev %d:%d\n", ret,
+ chip->ds->index, port);
+ return ret;
+ }
+
+ /* Update MIB for port */
+ if (chip->info->ops->stats_get_stats)
+ return chip->info->ops->stats_get_stats(chip, port, data);
+
+ return 0;
+}
+
+static struct mv88e6xxx_bus_ops mv88e6xxx_bus_ops = {
+ .get_rmon = mv88e6xxx_rmu_stats_get,
+};
+
+int mv88e6xxx_rmu_init(struct mv88e6xxx_chip *chip)
+{
+ dev_info(chip->dev, "RMU: setting up for switch@%d", chip->sw_addr);
+
+ init_completion(&chip->rmu.completion);
+
+ mutex_init(&chip->rmu.mutex);
+
+ chip->rmu.ops = &mv88e6xxx_bus_ops;
+
+ return 0;
+}
+
+void mv88e6xxx_rmu_master_change(struct dsa_switch *ds, const struct net_device *master,
+ bool operational)
+{
+ struct mv88e6xxx_chip *chip = ds->priv;
+
+ if (operational)
+ chip->rmu.ops = &mv88e6xxx_bus_ops;
+ else
+ chip->rmu.ops = NULL;
+}
new file mode 100644
@@ -0,0 +1,33 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * Marvell 88E6xxx Switch Remote Management Unit Support
+ *
+ * Copyright (c) 2022 Mattias Forsblad <mattias.forsblad@gmail.com>
+ *
+ */
+
+#ifndef _MV88E6XXX_RMU_H_
+#define _MV88E6XXX_RMU_H_
+
+#include "chip.h"
+
+#define MV88E6XXX_RMU_MAX_RMON 64
+
+#define MV88E6XXX_RMU_REQ_GET_ID 1
+#define MV88E6XXX_RMU_REQ_DUMP_MIB 2
+
+#define MV88E6XXX_RMU_RESP_FORMAT_1 0x0001
+#define MV88E6XXX_RMU_RESP_FORMAT_2 0x0002
+#define MV88E6XXX_RMU_RESP_ERROR 0xffff
+
+#define MV88E6XXX_RMU_RESP_CODE_GOT_ID 0x0000
+#define MV88E6XXX_RMU_RESP_CODE_DUMP_MIB 0x1020
+
+int mv88e6xxx_rmu_init(struct mv88e6xxx_chip *chip);
+
+int mv88e6xxx_inband_rcv(struct dsa_switch *ds, struct sk_buff *skb, int seq_no);
+
+void mv88e6xxx_rmu_master_change(struct dsa_switch *ds, const struct net_device *master,
+ bool operational);
+
+#endif /* _MV88E6XXX_RMU_H_ */
Implement support for handling RMU layer 3 frames including receive and transmit. Signed-off-by: Mattias Forsblad <mattias.forsblad@gmail.com> --- drivers/net/dsa/mv88e6xxx/Makefile | 1 + drivers/net/dsa/mv88e6xxx/chip.c | 13 ++ drivers/net/dsa/mv88e6xxx/chip.h | 20 ++ drivers/net/dsa/mv88e6xxx/global1.c | 84 +++++++++ drivers/net/dsa/mv88e6xxx/global1.h | 3 + drivers/net/dsa/mv88e6xxx/rmu.c | 276 ++++++++++++++++++++++++++++ drivers/net/dsa/mv88e6xxx/rmu.h | 33 ++++ 7 files changed, 430 insertions(+) create mode 100644 drivers/net/dsa/mv88e6xxx/rmu.c create mode 100644 drivers/net/dsa/mv88e6xxx/rmu.h