@@ -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)
{
@@ -1228,6 +1229,77 @@ static int mv88e6xxx_get_sset_count(struct dsa_switch *ds, int port, int sset)
return count;
}
+int mv88e6xxx_stats_get_sset_count_rmu(struct dsa_switch *ds, int port, int sset)
+{
+ if (sset != ETH_SS_STATS)
+ return 0;
+
+ return ARRAY_SIZE(mv88e6xxx_hw_stats);
+}
+
+void mv88e6xxx_stats_get_strings_rmu(struct dsa_switch *ds, int port,
+ u32 stringset, uint8_t *data)
+{
+ struct mv88e6xxx_hw_stat *stat;
+ int i;
+
+ if (stringset != ETH_SS_STATS)
+ return;
+
+ for (i = 0; i < ARRAY_SIZE(mv88e6xxx_hw_stats); i++) {
+ stat = &mv88e6xxx_hw_stats[i];
+ memcpy(data + i * ETH_GSTRING_LEN, stat->string, ETH_GSTRING_LEN);
+ }
+}
+
+int mv88e6xxx_state_get_stats_rmu(struct mv88e6xxx_chip *chip, int port, uint64_t *data)
+{
+ const u64 *stats = chip->ports[port].rmu_raw_stats;
+ struct mv88e6xxx_hw_stat *stat;
+ int offset = 0;
+ u64 high;
+ int i, j;
+
+ for (i = 0, j = 0; i < ARRAY_SIZE(mv88e6xxx_hw_stats); i++) {
+ stat = &mv88e6xxx_hw_stats[i];
+ offset = stat->reg;
+
+ if (stat->type & STATS_TYPE_PORT) {
+ /* The offsets for the port counters below
+ * are different in the RMU packet.
+ */
+ switch (stat->reg) {
+ case 0x10: /* sw_in_discards */
+ offset = 0x81;
+ break;
+ case 0x12: /* sw_in_filtered */
+ offset = 0x85;
+ break;
+ case 0x13: /* sw_out_filtered */
+ offset = 0x89;
+ break;
+ default:
+ dev_err(chip->dev,
+ "RMU: port %d wrong offset requested: %d\n",
+ port, stat->reg);
+ return j;
+ }
+ } else if (stat->type & STATS_TYPE_BANK1) {
+ offset += 32;
+ }
+
+ data[j] = stats[offset];
+
+ if (stat->size == 8) {
+ high = stats[offset + 1];
+ data[j] += (high << 32);
+ }
+
+ j++;
+ }
+ return j;
+}
+
static int mv88e6xxx_stats_get_stats(struct mv88e6xxx_chip *chip, int port,
uint64_t *data, int types,
u16 bank1_select, u16 histogram)
@@ -1535,14 +1607,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)
@@ -6867,6 +6931,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->decode_frame2reg = mv88e6xxx_decode_frame2reg_handler;
+ break;
+ default:
+ return -EPROTONOSUPPORT;
+ }
+
+ return 0;
+}
+
static const struct dsa_switch_ops mv88e6xxx_switch_ops = {
.get_tag_protocol = mv88e6xxx_get_tag_protocol,
.change_tag_protocol = mv88e6xxx_change_tag_protocol,
@@ -6932,6 +7013,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,
+ .master_state_change = mv88e6xxx_master_state_change,
+ .connect_tag_protocol = mv88e6xxx_connect_tag_protocol,
};
static int mv88e6xxx_register_switch(struct mv88e6xxx_chip *chip)
@@ -266,6 +266,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 +283,20 @@ struct mv88e6xxx_port {
struct devlink_region *region;
};
+struct mv88e6xxx_rmu {
+ /* RMU resources */
+ struct net_device *master_netdev;
+ const struct mv88e6xxx_bus_ops *smi_ops;
+ const struct dsa_switch_ops *ds_ops;
+ struct dsa_switch_ops *ds_rmu_ops;
+ struct mv88e6xxx_bus_ops *smi_rmu_ops;
+ /* Mutex for RMU operations */
+ struct mutex mutex;
+ u16 prodnr;
+ struct sk_buff *resp;
+ int seqno;
+};
+
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);
+ void (*get_rmon)(struct mv88e6xxx_chip *chip, int port, uint64_t *data);
};
struct mv88e6xxx_mdio_bus {
@@ -805,4 +824,13 @@ static inline void mv88e6xxx_reg_unlock(struct mv88e6xxx_chip *chip)
int mv88e6xxx_fid_map(struct mv88e6xxx_chip *chip, unsigned long *bitmap);
+static inline bool mv88e6xxx_rmu_available(struct mv88e6xxx_chip *chip)
+{
+ return chip->rmu.master_netdev ? 1 : 0;
+}
+
+int mv88e6xxx_state_get_stats_rmu(struct mv88e6xxx_chip *chip, int port, uint64_t *data);
+int mv88e6xxx_stats_get_sset_count_rmu(struct dsa_switch *ds, int port, int sset);
+void mv88e6xxx_stats_get_strings_rmu(struct dsa_switch *ds, int port,
+ u32 stringset, uint8_t *data);
#endif /* _MV88E6XXX_CHIP_H */
new file mode 100644
@@ -0,0 +1,320 @@
+// 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 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 sk_buff *skb)
+{
+ struct mv88e6xxx_chip *chip = ds->priv;
+ struct ethhdr *eth;
+ u8 *edsa_header;
+ u8 *dsa_header;
+ u8 extra = 0;
+
+ if (chip->tag_protocol == DSA_TAG_PROTO_EDSA)
+ extra = 4;
+
+ /* Create RMU L2 header */
+ dsa_header = skb_push(skb, 6);
+ dsa_header[0] = FIELD_PREP(MV88E6XXX_CPU_CODE_MASK, MV88E6XXX_RMU);
+ dsa_header[0] |= FIELD_PREP(MV88E6XXX_TRG_DEV_MASK, ds->index);
+ dsa_header[1] = FIELD_PREP(MV88E6XXX_RMU_CODE_MASK, 1);
+ dsa_header[1] |= FIELD_PREP(MV88E6XXX_RMU_L2_BYTE1_RESV, MV88E6XXX_RMU_L2_BYTE1_RESV_VAL);
+ dsa_header[2] = FIELD_PREP(MV88E6XXX_RMU_PRIO_MASK, MV88E6XXX_RMU_PRIO);
+ dsa_header[2] |= MV88E6XXX_RMU_L2_BYTE2_RESV;
+ dsa_header[3] = ++chip->rmu.seqno;
+ dsa_header[4] = 0;
+ dsa_header[5] = 0;
+
+ /* Insert RMU MAC destination address /w extra if needed */
+ eth = skb_push(skb, ETH_ALEN * 2 + extra);
+ memcpy(eth->h_dest, mv88e6xxx_rmu_dest_addr, ETH_ALEN);
+ ether_addr_copy(eth->h_source, chip->rmu.master_netdev->dev_addr);
+
+ if (extra) {
+ edsa_header = (u8 *)ð->h_proto;
+ 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 mv88e6xxx_rmu_send_wait(struct mv88e6xxx_chip *chip,
+ const void *req, int req_len,
+ void *resp, unsigned int *resp_len)
+{
+ struct sk_buff *skb;
+ unsigned char *data;
+ int ret = 0;
+
+ skb = netdev_alloc_skb(chip->rmu.master_netdev, 64);
+ if (!skb)
+ return -ENOMEM;
+
+ /* Take height for an eventual EDSA header */
+ skb_reserve(skb, 2 * ETH_HLEN + 4);
+ skb_reset_network_header(skb);
+
+ /* Insert RMU request message */
+ data = skb_put(skb, req_len);
+ memcpy(data, req, req_len);
+
+ mv88e6xxx_rmu_create_l2(chip->ds, skb);
+
+ mutex_lock(&chip->rmu.mutex);
+
+ ret = dsa_switch_inband_tx(chip->ds, skb, NULL, MV88E6XXX_RMU_WAIT_TIME_MS);
+ if (!ret) {
+ dev_err(chip->dev, "RMU: error waiting for request (%pe)\n",
+ ERR_PTR(ret));
+ goto out;
+ }
+
+ if (chip->rmu.resp->len > *resp_len) {
+ ret = -EMSGSIZE;
+ } else {
+ *resp_len = chip->rmu.resp->len;
+ memcpy(resp, chip->rmu.resp->data, chip->rmu.resp->len);
+ }
+
+ kfree_skb(chip->rmu.resp);
+ chip->rmu.resp = NULL;
+
+out:
+ mutex_unlock(&chip->rmu.mutex);
+
+ return ret > 0 ? 0 : ret;
+}
+
+static int mv88e6xxx_rmu_validate_response(struct mv88e6xxx_rmu_header *resp, int code)
+{
+ if (be16_to_cpu(resp->format) != MV88E6XXX_RMU_RESP_FORMAT_1 &&
+ be16_to_cpu(resp->format) != MV88E6XXX_RMU_RESP_FORMAT_2 &&
+ be16_to_cpu(resp->code) != code) {
+ net_dbg_ratelimited("RMU: received unknown format 0x%04x code 0x%04x",
+ resp->format, resp->code);
+ return -EIO;
+ }
+
+ return 0;
+}
+
+static int mv88e6xxx_rmu_get_id(struct mv88e6xxx_chip *chip, int port)
+{
+ const u16 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_send_wait(chip, req, sizeof(req),
+ &resp, &resp_len);
+ if (ret) {
+ dev_dbg(chip->dev, "RMU: error for command GET_ID %pe\n", ERR_PTR(ret));
+ return ret;
+ }
+
+ /* Got response */
+ ret = mv88e6xxx_rmu_validate_response(&resp, MV88E6XXX_RMU_RESP_CODE_GOT_ID);
+ if (ret)
+ return ret;
+
+ chip->rmu.prodnr = be16_to_cpu(resp.prodnr);
+
+ return ret;
+}
+
+static void mv88e6xxx_rmu_stats_get(struct mv88e6xxx_chip *chip, int port, uint64_t *data)
+{
+ u16 req[4] = { MV88E6XXX_RMU_REQ_FORMAT_SOHO,
+ MV88E6XXX_RMU_REQ_PAD,
+ MV88E6XXX_RMU_REQ_CODE_DUMP_MIB,
+ MV88E6XXX_RMU_REQ_DATA};
+ struct mv88e6xxx_dump_mib_resp resp;
+ struct mv88e6xxx_port *p;
+ u8 resp_port;
+ int resp_len;
+ int num_mibs;
+ int ret;
+ int i;
+
+ /* Populate port number in request */
+ req[3] = FIELD_PREP(MV88E6XXX_RMU_REQ_DUMP_MIB_PORT_MASK, port);
+
+ resp_len = sizeof(resp);
+ ret = mv88e6xxx_rmu_send_wait(chip, req, sizeof(req),
+ &resp, &resp_len);
+ if (ret) {
+ dev_dbg(chip->dev, "RMU: error for command DUMP_MIB %pe port %d\n",
+ ERR_PTR(ret), port);
+ return;
+ }
+
+ /* Got response */
+ ret = mv88e6xxx_rmu_validate_response(&resp.rmu_header, MV88E6XXX_RMU_RESP_CODE_DUMP_MIB);
+ if (ret)
+ return;
+
+ resp_port = FIELD_GET(MV88E6XXX_SOURCE_PORT, resp.portnum);
+ p = &chip->ports[resp_port];
+ if (!p) {
+ dev_err_ratelimited(chip->dev, "RMU: illegal port number in response: %d\n",
+ resp_port);
+ return;
+ }
+
+ /* Copy MIB array for further processing according to chip type */
+ num_mibs = (resp_len - offsetof(struct mv88e6xxx_dump_mib_resp, mib)) / sizeof(__be32);
+ for (i = 0; i < num_mibs; i++)
+ p->rmu_raw_stats[i] = be32_to_cpu(resp.mib[i]);
+
+ /* Update MIB for port */
+ mv88e6xxx_state_get_stats_rmu(chip, port, data);
+}
+
+static void mv88e6xxx_disable_rmu(struct mv88e6xxx_chip *chip)
+{
+ chip->smi_ops = chip->rmu.smi_ops;
+ chip->ds->ops = chip->rmu.ds_rmu_ops;
+ chip->rmu.master_netdev = NULL;
+
+ if (chip->info->ops->rmu_disable)
+ chip->info->ops->rmu_disable(chip);
+}
+
+static int mv88e6xxx_enable_check_rmu(const struct net_device *master,
+ struct mv88e6xxx_chip *chip, int port)
+{
+ int ret;
+
+ chip->rmu.master_netdev = (struct net_device *)master;
+
+ /* Check if chip is alive */
+ ret = mv88e6xxx_rmu_get_id(chip, port);
+ if (!ret)
+ return ret;
+
+ chip->ds->ops = chip->rmu.ds_rmu_ops;
+ chip->smi_ops = chip->rmu.smi_rmu_ops;
+
+ return 0;
+}
+
+void mv88e6xxx_master_state_change(struct dsa_switch *ds, const struct net_device *master,
+ bool operational)
+{
+ struct dsa_port *cpu_dp = master->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)
+ goto out;
+
+ if (!ret) {
+ dev_dbg(chip->dev, "RMU: Enabled on port %d", port);
+
+ ret = mv88e6xxx_enable_check_rmu(master, chip, port);
+ if (!ret)
+ goto out;
+
+ } else {
+ dev_err(chip->dev, "RMU: Unable to enable on port %d %pe",
+ port, ERR_PTR(ret));
+ goto out;
+ }
+ } else {
+ mv88e6xxx_disable_rmu(chip);
+ }
+
+out:
+ mv88e6xxx_reg_unlock(chip);
+}
+
+static int mv88e6xxx_validate_mac(struct dsa_switch *ds, struct sk_buff *skb)
+{
+ struct mv88e6xxx_chip *chip = ds->priv;
+ unsigned char *ethhdr;
+
+ /* Check matching MAC */
+ ethhdr = skb_mac_header(skb);
+ if (!ether_addr_equal(chip->rmu.master_netdev->dev_addr, ethhdr)) {
+ dev_dbg_ratelimited(ds->dev, "RMU: mismatching MAC address for request. Rx %pM expecting %pM\n",
+ ethhdr, chip->rmu.master_netdev->dev_addr);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+void mv88e6xxx_decode_frame2reg_handler(struct dsa_switch *ds, struct sk_buff *skb)
+{
+ struct mv88e6xxx_chip *chip = ds->priv;
+ u8 *dsa_header;
+ u8 seqno;
+
+ /* Decode Frame2Reg DSA portion */
+ dsa_header = skb->data - 2;
+
+ if (mv88e6xxx_validate_mac(ds, skb))
+ return;
+
+ seqno = dsa_header[3];
+ if (seqno != chip->rmu.seqno) {
+ net_err_ratelimited("RMU: wrong seqno received. Was %d, expected %d",
+ seqno, chip->rmu.seqno);
+ return;
+ }
+
+ /* Pull DSA L2 data */
+ skb_pull(skb, MV88E6XXX_DSA_HLEN);
+
+ /* Get an reference for further processing in initiator */
+ chip->rmu.resp = skb_get(skb);
+
+ dsa_switch_inband_complete(ds, NULL);
+}
+
+int mv88e6xxx_rmu_setup(struct mv88e6xxx_chip *chip)
+{
+ mutex_init(&chip->rmu.mutex);
+
+ /* Remember original ops for restore */
+ chip->rmu.smi_ops = chip->smi_ops;
+ chip->rmu.ds_ops = chip->ds->ops;
+
+ /* Change rmu ops with our own pointer */
+ chip->rmu.smi_rmu_ops = (struct mv88e6xxx_bus_ops *)chip->rmu.smi_ops;
+ chip->rmu.smi_rmu_ops->get_rmon = mv88e6xxx_rmu_stats_get;
+
+ /* Also change get stats strings for our own */
+ chip->rmu.ds_rmu_ops = (struct dsa_switch_ops *)chip->ds->ops;
+ chip->rmu.ds_rmu_ops->get_sset_count = mv88e6xxx_stats_get_sset_count_rmu;
+ chip->rmu.ds_rmu_ops->get_strings = mv88e6xxx_stats_get_strings_rmu;
+
+ /* Start disabled, we'll enable RMU in master_state_change */
+ if (chip->info->ops->rmu_disable)
+ return chip->info->ops->rmu_disable(chip);
+
+ return 0;
+}
new file mode 100644
@@ -0,0 +1,73 @@
+/* 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_DSA_HLEN 4
+
+#define MV88E6XXX_RMU_MAX_RMON 64
+
+#define MV88E6XXX_RMU_WAIT_TIME_MS 20
+
+#define MV88E6XXX_RMU_L2_BYTE1_RESV_VAL 0x3e
+#define MV88E6XXX_RMU 1
+#define MV88E6XXX_RMU_PRIO 6
+#define MV88E6XXX_RMU_RESV2 0xf
+
+#define MV88E6XXX_SOURCE_PORT GENMASK(6, 3)
+#define MV88E6XXX_CPU_CODE_MASK GENMASK(7, 6)
+#define MV88E6XXX_TRG_DEV_MASK GENMASK(4, 0)
+#define MV88E6XXX_RMU_CODE_MASK GENMASK(1, 1)
+#define MV88E6XXX_RMU_PRIO_MASK GENMASK(7, 5)
+#define MV88E6XXX_RMU_L2_BYTE1_RESV GENMASK(7, 2)
+#define MV88E6XXX_RMU_L2_BYTE2_RESV GENMASK(3, 0)
+
+#define MV88E6XXX_RMU_REQ_GET_ID 1
+#define MV88E6XXX_RMU_REQ_DUMP_MIB 2
+
+#define MV88E6XXX_RMU_REQ_FORMAT_GET_ID 0x0000
+#define MV88E6XXX_RMU_REQ_FORMAT_SOHO 0x0001
+#define MV88E6XXX_RMU_REQ_PAD 0x0000
+#define MV88E6XXX_RMU_REQ_CODE_GET_ID 0x0000
+#define MV88E6XXX_RMU_REQ_CODE_DUMP_MIB 0x1020
+#define MV88E6XXX_RMU_REQ_DATA 0x0000
+
+#define MV88E6XXX_RMU_REQ_DUMP_MIB_PORT_MASK GENMASK(4, 0)
+
+#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
+
+struct mv88e6xxx_rmu_header {
+ __be16 format;
+ __be16 prodnr;
+ __be16 code;
+} __packed;
+
+struct mv88e6xxx_dump_mib_resp {
+ struct mv88e6xxx_rmu_header rmu_header;
+ u8 devnum;
+ u8 portnum;
+ __be32 timestamp;
+ __be32 mib[MV88E6XXX_RMU_MAX_RMON];
+} __packed;
+
+int mv88e6xxx_rmu_setup(struct mv88e6xxx_chip *chip);
+
+void mv88e6xxx_master_state_change(struct dsa_switch *ds, const struct net_device *master,
+ bool operational);
+
+void mv88e6xxx_decode_frame2reg_handler(struct dsa_switch *ds, struct sk_buff *skb);
+
+#endif /* _MV88E6XXX_RMU_H_ */