Skip to content

Commit

Permalink
net: dsa: mv88e6xxxx: Add RMU functionality.
Browse files Browse the repository at this point in the history
The Marvell SOHO switches supports a secondary control
channel for accessing data in the switch. Special crafted
ethernet frames can access functions in the switch.
These frames is handled by the Remote Management Unit (RMU)
in the switch. Accessing data structures is specially
efficient and lessens the access contention on the MDIO
bus.

Signed-off-by: Mattias Forsblad <mattias.forsblad@gmail.com>
  • Loading branch information
Hexxel authored and intel-lab-lkp committed Sep 9, 2022
1 parent 61fe729 commit 5a8005d
Show file tree
Hide file tree
Showing 5 changed files with 386 additions and 8 deletions.
1 change: 1 addition & 0 deletions drivers/net/dsa/mv88e6xxx/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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
28 changes: 20 additions & 8 deletions drivers/net/dsa/mv88e6xxx/chip.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Expand Down Expand Up @@ -1535,14 +1536,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)
Expand Down Expand Up @@ -6867,6 +6860,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 -EOPNOTSUPP;
}

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,
Expand Down Expand Up @@ -6932,6 +6942,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_change,
.connect_tag_protocol = mv88e6xxx_connect_tag_protocol,
};

static int mv88e6xxx_register_switch(struct mv88e6xxx_chip *chip)
Expand Down
19 changes: 19 additions & 0 deletions drivers/net/dsa/mv88e6xxx/chip.h
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,18 @@ struct mv88e6xxx_port {
struct devlink_region *region;
};

struct mv88e6xxx_rmu {
/* RMU resources */
struct net_device *master_netdev;
const struct mv88e6xxx_bus_ops *smi_ops;
struct mv88e6xxx_bus_ops *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,
Expand Down Expand Up @@ -410,6 +422,9 @@ struct mv88e6xxx_chip {

/* Bridge MST to SID mappings */
struct list_head msts;

/* RMU resources */
struct mv88e6xxx_rmu rmu;
};

struct mv88e6xxx_bus_ops {
Expand Down Expand Up @@ -805,4 +820,8 @@ 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;
}
#endif /* _MV88E6XXX_CHIP_H */
270 changes: 270 additions & 0 deletions drivers/net/dsa/mv88e6xxx/rmu.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,270 @@
// 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 void mv88e6xxx_rmu_create_l2(struct sk_buff *skb, struct dsa_port *dp)
{
struct mv88e6xxx_chip *chip = dp->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, dp->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 */
skb_push(skb, ETH_ALEN * 2 + extra);
eth = (struct ethhdr *)skb->data;
memcpy(eth->h_dest, rmu_dest_addr, ETH_ALEN);
memcpy(eth->h_source, chip->rmu.master_netdev->dev_addr, ETH_ALEN);

if (extra) {
edsa_header = (u8 *)&eth->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, int port,
const void *req, int req_len,
void *resp, unsigned int *resp_len)
{
struct dsa_port *dp;
struct sk_buff *skb;
unsigned char *data;
int ret = 0;

dp = dsa_to_port(chip->ds, port);
if (!dp)
return -EINVAL;

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(skb, dp);

mutex_lock(&chip->rmu.mutex);

ret = dsa_switch_inband_tx(dp->ds, skb, NULL, MV88E6XXX_RMU_WAIT_TIME_MS);
if (ret < 0) {
dev_err(chip->dev, "RMU: timeout waiting for request (%pe) on port %d\n",
ERR_PTR(ret), port);
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);
}

/* We're done with the received data copy, discard it */
kfree_skb(chip->rmu.resp);
chip->rmu.resp = NULL;

out:
mutex_unlock(&chip->rmu.mutex);

return ret > 0 ? 0 : ret;
}

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 rmu_header resp;
int resp_len;
int ret = -1;
u16 format;
u16 code;

resp_len = sizeof(resp);
ret = mv88e6xxx_rmu_send_wait(chip, port, 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 */
format = get_unaligned_be16(&resp.format);
code = get_unaligned_be16(&resp.code);

if (format != MV88E6XXX_RMU_RESP_FORMAT_1 &&
format != MV88E6XXX_RMU_RESP_FORMAT_2 &&
code != MV88E6XXX_RMU_RESP_CODE_GOT_ID) {
net_dbg_ratelimited("RMU: received unknown format 0x%04x code 0x%04x",
format, code);
return -EIO;
}

chip->rmu.prodnr = get_unaligned_be16(&resp.prodnr);

return 0;
}

static void mv88e6xxx_disable_rmu(struct mv88e6xxx_chip *chip)
{
chip->smi_ops = chip->rmu.smi_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->smi_ops = chip->rmu.rmu_ops;

return 0;
}

void mv88e6xxx_master_change(struct dsa_switch *ds, const struct net_device *master,
bool operational)
{
struct mv88e6xxx_chip *chip = ds->priv;
struct dsa_port *cpu_dp;
int port;
int ret;

cpu_dp = master->dsa_ptr;
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;
}
}

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 net_device *dev, struct sk_buff *skb)
{
struct dsa_port *dp = dev->dsa_ptr;
struct dsa_switch *ds = dp->ds;
struct mv88e6xxx_chip *chip;
int source_device;
u8 *dsa_header;
u8 seqno;

/* Decode Frame2Reg DSA portion */
dsa_header = skb->data - 2;

source_device = FIELD_GET(MV88E6XXX_SOURCE_DEV, dsa_header[0]);
ds = dsa_switch_find(ds->dst->index, source_device);
if (!ds) {
net_err_ratelimited("RMU: Didn't find switch with index %d", source_device);
return;
}

if (mv88e6xxx_validate_mac(ds, skb))
return;

chip = ds->priv;
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);

/* Make an copy for further processing in initiator */
chip->rmu.resp = skb_copy(skb, GFP_ATOMIC);
if (!chip->rmu.resp)
return;

dsa_switch_inband_complete(ds, NULL);
}

int mv88e6xxx_rmu_setup(struct mv88e6xxx_chip *chip)
{
mutex_init(&chip->rmu.mutex);

if (chip->info->ops->rmu_disable)
return chip->info->ops->rmu_disable(chip);

return 0;
}

0 comments on commit 5a8005d

Please sign in to comment.